Skip to content

Commit 144800a

Browse files
committed
feat: add aws amplify preset
1 parent 55bb3b4 commit 144800a

File tree

6 files changed

+318
-0
lines changed

6 files changed

+318
-0
lines changed

docs/content/2.deploy/0.index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ When running Nitro in development mode, Nitro will always use a special preset c
1919

2020
When deploying to the production using CI/CD, Nitro tries to automatically detect the provider environment and set the right one without any additional configuration. Currently, providers below can be auto-detected with zero config.
2121

22+
- [aws amplify](/deploy/providers/aws-amplify)
2223
- [azure](/deploy/providers/azure)
2324
- [cloudflare pages](/deploy/providers/cloudflare#cloudflare-pages)
2425
- [netlify](/deploy/providers/netlify)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# AWS Amplify Hosting
2+
3+
Deploy Nitro apps to [AWS Amplify Hosting](https://aws.amazon.com/amplify/).
4+
5+
**Preset:** `aws_amplify` ([switch to this preset](/deploy/#changing-the-deployment-preset))
6+
7+
::alert
8+
**Zero Config Provider**
9+
:br
10+
Integration with this provider is possible with zero configuration. ([Learn More](/deploy/#zero-config-providers))
11+
::
12+
13+
::alert{type="warning"}
14+
This preset is a work in progress and only available on [nightly release channel](https://nitro.unjs.io/guide/getting-started#nightly-release-channel)
15+
::

src/presets/aws-amplify.ts

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import { fileURLToPath } from "node:url";
2+
import { resolve } from "node:path";
3+
import { writeFile } from "node:fs/promises";
4+
import { joinURL } from "ufo";
5+
import { defineNitroPreset } from "../preset";
6+
import type { Nitro } from "../types";
7+
import {
8+
AmplifyDeployManifest,
9+
AmplifyRoute,
10+
AmplifyRouteTarget,
11+
} from "../types/presets/aws-amplify";
12+
13+
export const awsAmplify = defineNitroPreset({
14+
extends: "node-server",
15+
entry: fileURLToPath(new URL("entry.ts", import.meta.url)),
16+
output: {
17+
dir: "{{ rootDir }}/.amplify-hosting",
18+
serverDir: "{{ output.dir }}/compute/default",
19+
publicDir: "{{ output.dir }}/static{{ baseURL }}",
20+
},
21+
commands: {
22+
preview: "node ./compute/default/server.js",
23+
},
24+
hooks: {
25+
async compiled(nitro) {
26+
await writeAmplifyFiles(nitro);
27+
},
28+
},
29+
});
30+
31+
async function writeAmplifyFiles(nitro: Nitro) {
32+
const outDir = nitro.options.output.dir;
33+
34+
// Generate routes
35+
const routes: AmplifyRoute[] = [];
36+
37+
let hasWildcardPublicAsset = false;
38+
39+
if (nitro.options.awsAmplify?.imageOptimization) {
40+
const { path, cacheControl } = nitro.options.awsAmplify?.imageOptimization;
41+
routes.push({
42+
path,
43+
target: {
44+
kind: "ImageOptimization",
45+
cacheControl,
46+
},
47+
});
48+
}
49+
50+
const computeTarget = {
51+
kind: "Compute",
52+
src: "default",
53+
} as AmplifyRouteTarget;
54+
55+
for (const publicAsset of nitro.options.publicAssets) {
56+
if (!publicAsset.baseURL || publicAsset.baseURL === "/") {
57+
hasWildcardPublicAsset = true;
58+
continue;
59+
}
60+
routes.push({
61+
path: `${publicAsset.baseURL!.replace(/\/$/, "")}/*`,
62+
target: {
63+
cacheControl:
64+
publicAsset.maxAge > 0
65+
? `public, max-age=${publicAsset.maxAge}, immutable`
66+
: undefined,
67+
kind: "Static",
68+
},
69+
fallback: publicAsset.fallthrough ? computeTarget : undefined,
70+
});
71+
}
72+
if (hasWildcardPublicAsset) {
73+
routes.push({
74+
path: "/*.*",
75+
target: {
76+
kind: "Static",
77+
},
78+
fallback: computeTarget,
79+
});
80+
}
81+
routes.push({
82+
path: "/*",
83+
target: computeTarget,
84+
fallback: hasWildcardPublicAsset
85+
? {
86+
kind: "Static",
87+
}
88+
: undefined,
89+
});
90+
91+
// Prefix with baseURL
92+
for (const route of routes) {
93+
if (route.path !== "/*") {
94+
route.path = joinURL(nitro.options.baseURL, route.path);
95+
}
96+
}
97+
98+
// Generate deploy-manifest.json
99+
const deployManifest: AmplifyDeployManifest = {
100+
version: 1,
101+
routes,
102+
imageSettings: nitro.options.awsAmplify?.imageSettings || undefined,
103+
computeResources: [
104+
{
105+
name: "default",
106+
entrypoint: "server.js",
107+
runtime: "nodejs18.x",
108+
},
109+
],
110+
framework: {
111+
name: nitro.options.framework.name || "nitro",
112+
version: nitro.options.framework.version || "0.0.0",
113+
},
114+
};
115+
await writeFile(
116+
resolve(outDir, "deploy-manifest.json"),
117+
JSON.stringify(deployManifest, null, 2)
118+
);
119+
120+
// Write server.js (CJS)
121+
await writeFile(
122+
resolve(outDir, "compute/default/server.js"),
123+
`import("./index.mjs")`
124+
);
125+
}

src/runtime/entries/aws-amplify.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import "#internal/nitro/virtual/polyfill";
2+
import { Server } from "node:http";
3+
import { toNodeListener } from "h3";
4+
import { nitroApp } from "#internal/nitro/app";
5+
6+
const server = new Server(toNodeListener(nitroApp.h3App));
7+
8+
// @ts-ignore
9+
server.listen(3000, (err) => {
10+
if (err) {
11+
console.error(err);
12+
} else {
13+
console.log(`Listening on http://localhost:3000 (AWS Amplify Hosting)`);
14+
}
15+
});

src/types/presets/aws-amplify.ts

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
export interface AmplifyComputeConfig {
2+
/**
3+
* The name property dictates the name of the provisioned compute resource. It is also the name
4+
* of the sub-directory in the `compute` folder in the deployment bundle.
5+
*/
6+
name: string;
7+
/**
8+
* The runtime property dictates the runtime of the provisioned compute resource.
9+
* Values are subset of https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html
10+
*/
11+
runtime: "nodejs16.x" | "nodejs18.x";
12+
13+
/**
14+
* Specifies the starting file from which code will run for the given compute resource.
15+
* The specified file should exist inside the given sub-directory that represents a compute resource.
16+
*
17+
* @example `"entrypoint.js"`
18+
*/
19+
entrypoint: string;
20+
}
21+
22+
export type AmplifyRouteTarget =
23+
| { kind: "Static"; cacheControl?: string }
24+
| { kind: "ImageOptimization"; cacheControl?: string }
25+
| {
26+
kind: "Compute";
27+
/**
28+
* A string that indicates the name of the sub-directory in the given deployment bundle that
29+
* contains the primitive's executable code. Valid and required only for the Compute primitive.
30+
* The value here should point to one of the compute resources present in the given
31+
* deployment bundle. The only supported value for this field is default.
32+
*/
33+
src: string;
34+
};
35+
36+
export type AmplifyRoute = {
37+
/**
38+
* The path defines a glob pattern that matches incoming request paths (excluding querystring).
39+
* The first match in a given list of rules determines which routing rule is applied to the incoming request.
40+
* Only the following wilcard characters are supported as far as pattern matching is concerned: `*` (matches 0 or more characters)
41+
*
42+
* _Note_: The "/*" pattern is called a catch-all pattern and will match all incoming requests.
43+
* It is special because fallback routing is only supported for catch-all routes.
44+
*
45+
*/
46+
path: string;
47+
48+
/**
49+
* An object that dictates the target to route the matched request to.
50+
*/
51+
target: AmplifyRouteTarget;
52+
53+
/** An object that dictates the target to fallback to if the original target returns a 404. */
54+
fallback?: AmplifyRouteTarget;
55+
};
56+
57+
export type AmplifyImageSettings = {
58+
/** Array of supported image widths */
59+
sizes: number[];
60+
61+
/**
62+
* Array of allowed external domains that can use Image Optimization.
63+
* Leave empty for only allowing the deployment domain to use Image Optimization.
64+
*/
65+
domains: string[];
66+
67+
/**
68+
* Array of allowed external patterns that can use Image Optimization.
69+
* Similar to `domains` but provides more control with RegExp.
70+
*/
71+
remotePatterns: {
72+
/** The protocol of the allowed remote pattern. Can be `http` or `https`. */
73+
protocol?: "http" | "https";
74+
/**
75+
* The hostname of the allowed remote pattern.
76+
* Can be literal or wildcard. Single `*` matches a single subdomain.
77+
* Double `**` matches any number of subdomains.
78+
* We will disallow blanket wildcards of `**` with nothing else.
79+
*/
80+
hostname: string;
81+
/** The port of the allowed remote pattern. */
82+
port?: string;
83+
/** The pathname of the allowed remote pattern. */
84+
pathname?: string;
85+
}[];
86+
87+
/** Array of allowed output image formats. */
88+
formats?: (
89+
| "image/avif"
90+
| "image/webp"
91+
| "image/gif"
92+
| "image/png"
93+
| "image/jpeg"
94+
)[];
95+
96+
/** Cache duration (in seconds) for the optimized images. */
97+
minimumCacheTTL?: number;
98+
99+
/** Allow SVG input image URLs. This is disabled by default for security purposes. */
100+
dangerouslyAllowSVG?: boolean;
101+
};
102+
103+
export interface AmplifyDeployManifest {
104+
/** The `version` property dictates the version of the Deployment Specification that has been implemented */
105+
version: 1;
106+
107+
/**
108+
* The routes property allows framework authors to leverage the routing rules primitive.
109+
* Routing rules provide a mechanism by which incoming request paths can be routed to a specific target
110+
* in the deployment bundle. Routing rules only dictate the destination of an incoming request and they are
111+
* applied after rewrite/redirect rules have already transformed the request.
112+
*
113+
* Limits for routing rules:
114+
* - A limit of 25 rules will be enforced on the routes array
115+
*/
116+
routes?: AmplifyRoute[];
117+
118+
/**
119+
* The imageSettings property allows framework authors to customize the behavior of the image optimization primitive,
120+
* which provides on-demand optimization of images at runtime.
121+
*/
122+
imageSettings?: AmplifyImageSettings;
123+
/**
124+
* Metadata about the provisioned compute resource(s). Each item in the array is an object that contains metadata
125+
* about that compute resource.
126+
*
127+
* For example, given the following directory structure:
128+
* ```
129+
* .amplify
130+
* └── compute
131+
* └── default
132+
* └── index.js
133+
* ```
134+
* The `computeResources` property would look like:
135+
* ```
136+
* [
137+
* {
138+
* name: 'default',
139+
* runtime: 'nodejs16.x',
140+
* entrypoint: 'index.js',
141+
* }
142+
* ]
143+
* ```
144+
*/
145+
computeResources?: AmplifyComputeConfig[];
146+
147+
// Framework Metadata
148+
framework: {
149+
name: string;
150+
version: string;
151+
};
152+
}
153+
154+
export interface AWSAmplifyOptions {
155+
imageOptimization?: {
156+
path?: string;
157+
cacheControl?: string;
158+
};
159+
imageSettings?: AmplifyImageSettings;
160+
}

src/types/presets/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { AWSAmplifyOptions } from "./aws-amplify";
12
import { AzureOptions } from "./azure";
23
import { CloudflareOptions } from "./cloudflare";
34
import { FirebaseOptions } from "./firebase";
@@ -8,4 +9,5 @@ export interface PresetOptions {
89
cloudflare: CloudflareOptions;
910
firebase: FirebaseOptions;
1011
vercel: VercelOptions;
12+
awsAmplify: AWSAmplifyOptions;
1113
}

0 commit comments

Comments
 (0)