@awsless/cli 0.0.46-next.7 → 0.0.46-next.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +247 -86
- package/dist/build-json-schema.js +87 -2
- package/dist/stack.json +1 -1
- package/dist/stack.stage.json +1 -1
- package/package.json +10 -10
package/dist/bin.js
CHANGED
|
@@ -2234,6 +2234,66 @@ var InstanceDefaultSchema = z19.object({
|
|
|
2234
2234
|
// src/feature/router/schema.ts
|
|
2235
2235
|
import { days as days3, minutes as minutes2, parse as parse3 } from "@awsless/duration";
|
|
2236
2236
|
import { z as z20 } from "zod";
|
|
2237
|
+
|
|
2238
|
+
// src/feature/router/pattern.ts
|
|
2239
|
+
var PARAM_TOKEN = /\{([a-zA-Z_][a-zA-Z0-9_]*)\}|\*/g;
|
|
2240
|
+
var escapeRegex = (value) => {
|
|
2241
|
+
return value.replace(/[|\\{}()[\]^$+*?.\-]/g, "\\$&");
|
|
2242
|
+
};
|
|
2243
|
+
var compileRoutePattern = (pattern) => {
|
|
2244
|
+
if (!pattern.startsWith("/")) {
|
|
2245
|
+
throw new ExpectedError(`Route pattern "${pattern}" must start with a slash (/)`);
|
|
2246
|
+
}
|
|
2247
|
+
if (pattern === "/*") {
|
|
2248
|
+
return { key: pattern };
|
|
2249
|
+
}
|
|
2250
|
+
const params = [];
|
|
2251
|
+
let regex = "";
|
|
2252
|
+
let stars = 0;
|
|
2253
|
+
let last = 0;
|
|
2254
|
+
let token;
|
|
2255
|
+
PARAM_TOKEN.lastIndex = 0;
|
|
2256
|
+
while (token = PARAM_TOKEN.exec(pattern)) {
|
|
2257
|
+
regex += escapeRegex(pattern.slice(last, token.index));
|
|
2258
|
+
const param = token[1];
|
|
2259
|
+
if (param) {
|
|
2260
|
+
if (params.includes(param)) {
|
|
2261
|
+
throw new ExpectedError(`Duplicate param "${param}" in route pattern "${pattern}"`);
|
|
2262
|
+
}
|
|
2263
|
+
params.push(param);
|
|
2264
|
+
regex += "([^/]+)";
|
|
2265
|
+
} else {
|
|
2266
|
+
stars++;
|
|
2267
|
+
regex += ".*";
|
|
2268
|
+
}
|
|
2269
|
+
last = PARAM_TOKEN.lastIndex;
|
|
2270
|
+
}
|
|
2271
|
+
if (params.length === 0 && stars === 0) {
|
|
2272
|
+
return { key: pattern };
|
|
2273
|
+
}
|
|
2274
|
+
regex += escapeRegex(pattern.slice(last));
|
|
2275
|
+
const root2 = pattern.split("/")[1] ?? "";
|
|
2276
|
+
if (root2 === "" || root2.includes("*") || root2.includes("{")) {
|
|
2277
|
+
throw new ExpectedError(
|
|
2278
|
+
`The first path segment of route pattern "${pattern}" must be static when the pattern contains params or wildcards.`
|
|
2279
|
+
);
|
|
2280
|
+
}
|
|
2281
|
+
if (root2.includes(".")) {
|
|
2282
|
+
throw new ExpectedError(
|
|
2283
|
+
`The first path segment of route pattern "${pattern}" can't contain a dot when the pattern contains params or wildcards.`
|
|
2284
|
+
);
|
|
2285
|
+
}
|
|
2286
|
+
if (params.length === 0 && pattern === `/${root2}/*`) {
|
|
2287
|
+
return { key: pattern };
|
|
2288
|
+
}
|
|
2289
|
+
return {
|
|
2290
|
+
key: `/${root2}/*`,
|
|
2291
|
+
match: `^${regex}$`,
|
|
2292
|
+
params: params.length > 0 ? params : void 0
|
|
2293
|
+
};
|
|
2294
|
+
};
|
|
2295
|
+
|
|
2296
|
+
// src/feature/router/schema.ts
|
|
2237
2297
|
var ErrorResponsePathSchema = z20.string().describe(
|
|
2238
2298
|
[
|
|
2239
2299
|
"The path to the custom error page that you want to return to the viewer when your origin returns the HTTP status code specified.",
|
|
@@ -2263,6 +2323,28 @@ var ErrorResponseSchema = z20.union([
|
|
|
2263
2323
|
})
|
|
2264
2324
|
]).optional();
|
|
2265
2325
|
var RouteSchema = z20.string().regex(/^\//, "Route must start with a slash (/)").regex(/^\/([^/*.]+)?$/, 'Router paths mount a single segment without dots, like "/api".');
|
|
2326
|
+
var RoutesSchema = z20.record(
|
|
2327
|
+
ResourceIdSchema.describe("The router id to add your routes to."),
|
|
2328
|
+
z20.record(z20.string().regex(/^\//, "Route must start with a slash (/)"), FunctionSchema).superRefine((routes, ctx) => {
|
|
2329
|
+
for (const pattern of Object.keys(routes)) {
|
|
2330
|
+
try {
|
|
2331
|
+
compileRoutePattern(pattern);
|
|
2332
|
+
} catch (error) {
|
|
2333
|
+
ctx.addIssue({
|
|
2334
|
+
code: z20.ZodIssueCode.custom,
|
|
2335
|
+
path: [pattern],
|
|
2336
|
+
message: error instanceof Error ? error.message : `Invalid route pattern: ${pattern}`
|
|
2337
|
+
});
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
}).describe(
|
|
2341
|
+
[
|
|
2342
|
+
"Define the routes and the lambda function that should handle them.",
|
|
2343
|
+
'Routes can be an exact path like "/sitemap.xml", a wildcard like "/sitemap/*", or contain params like "/sitemap/{locale}/{page}.xml".',
|
|
2344
|
+
'Param values are passed to the function as "x-param-[NAME]" request headers.'
|
|
2345
|
+
].join("\n")
|
|
2346
|
+
)
|
|
2347
|
+
).optional().describe("Add routes to your global Router that link a path pattern to a lambda function.");
|
|
2266
2348
|
var VisibilitySchema = z20.boolean().default(false).describe("Whether to enable CloudWatch metrics for the WAF rule.");
|
|
2267
2349
|
var WafSettingsSchema = z20.object({
|
|
2268
2350
|
rateLimiter: z20.object({
|
|
@@ -3371,14 +3453,13 @@ var TestsSchema = z44.union([
|
|
|
3371
3453
|
]).describe("Define the location of your tests for your stack.").optional();
|
|
3372
3454
|
|
|
3373
3455
|
// src/config/stack.ts
|
|
3374
|
-
var DependsSchema = ResourceIdSchema.array().optional().describe("Define the stacks that this stack is depended on.");
|
|
3375
3456
|
var NameSchema = ResourceIdSchema.refine((name) => !["base", "hostedzones"].includes(name), {
|
|
3376
3457
|
message: `Stack name can't be a reserved name.`
|
|
3377
3458
|
}).describe("Stack name.");
|
|
3378
3459
|
var StackSchema = z45.object({
|
|
3379
3460
|
$schema: z45.string().optional(),
|
|
3380
3461
|
name: NameSchema,
|
|
3381
|
-
|
|
3462
|
+
routes: RoutesSchema,
|
|
3382
3463
|
commands: CommandsSchema,
|
|
3383
3464
|
// auth: AuthSchema,
|
|
3384
3465
|
// http: HttpSchema,
|
|
@@ -4480,11 +4561,6 @@ var bundleFeature = defineFeature({
|
|
|
4480
4561
|
addLayer,
|
|
4481
4562
|
addPermission
|
|
4482
4563
|
});
|
|
4483
|
-
},
|
|
4484
|
-
onStack(ctx) {
|
|
4485
|
-
const bundle = ctx.shared.get("bundle", "main");
|
|
4486
|
-
ctx.onEnv(bundle.addEnv);
|
|
4487
|
-
ctx.onPermission(bundle.addPermission);
|
|
4488
4564
|
}
|
|
4489
4565
|
});
|
|
4490
4566
|
|
|
@@ -4795,7 +4871,7 @@ var configFeature = defineFeature({
|
|
|
4795
4871
|
ctx.addEnv(`CONFIG_${constantCase4(name)}`, name);
|
|
4796
4872
|
}
|
|
4797
4873
|
if (configs.length) {
|
|
4798
|
-
ctx.
|
|
4874
|
+
ctx.addGlobalPermission({
|
|
4799
4875
|
actions: [
|
|
4800
4876
|
"ssm:GetParameter",
|
|
4801
4877
|
"ssm:GetParameters",
|
|
@@ -6397,7 +6473,7 @@ var queueFeature = defineFeature({
|
|
|
6397
6473
|
});
|
|
6398
6474
|
}
|
|
6399
6475
|
ctx.addEnv(`QUEUE_${constantCase6(ctx.stack.name)}_${constantCase6(id)}_URL`, queue2.url);
|
|
6400
|
-
ctx.
|
|
6476
|
+
ctx.addGlobalPermission({
|
|
6401
6477
|
actions: [
|
|
6402
6478
|
"sqs:SendMessage",
|
|
6403
6479
|
"sqs:ReceiveMessage",
|
|
@@ -6757,7 +6833,7 @@ var searchFeature = defineFeature({
|
|
|
6757
6833
|
}
|
|
6758
6834
|
);
|
|
6759
6835
|
ctx.addEnv(`SEARCH_${constantCase8(ctx.stack.name)}_${constantCase8(id)}_DOMAIN`, openSearch.endpointV2);
|
|
6760
|
-
ctx.
|
|
6836
|
+
ctx.addGlobalPermission({
|
|
6761
6837
|
actions: ["es:ESHttp*"],
|
|
6762
6838
|
resources: [
|
|
6763
6839
|
//
|
|
@@ -8677,13 +8753,13 @@ var jobFeature = defineFeature({
|
|
|
8677
8753
|
const group = new Group25(ctx.stack, "job", id);
|
|
8678
8754
|
createFargateJob(group, ctx, "job", id, props);
|
|
8679
8755
|
}
|
|
8680
|
-
ctx.
|
|
8756
|
+
ctx.addGlobalPermission({
|
|
8681
8757
|
actions: ["ecs:RunTask"],
|
|
8682
8758
|
resources: [
|
|
8683
8759
|
`arn:aws:ecs:${ctx.appConfig.region}:*:task-definition/${ctx.app.name}--${ctx.stackConfig.name}--*`
|
|
8684
8760
|
]
|
|
8685
8761
|
});
|
|
8686
|
-
ctx.
|
|
8762
|
+
ctx.addGlobalPermission({
|
|
8687
8763
|
actions: ["iam:PassRole"],
|
|
8688
8764
|
resources: ["*"],
|
|
8689
8765
|
conditions: {
|
|
@@ -9220,7 +9296,7 @@ var metricFeature = defineFeature({
|
|
|
9220
9296
|
onStack(ctx) {
|
|
9221
9297
|
const bundle = ctx.shared.get("bundle", "main");
|
|
9222
9298
|
const namespace = `awsless/${kebabCase11(ctx.app.name)}/${kebabCase11(ctx.stack.name)}`;
|
|
9223
|
-
ctx.
|
|
9299
|
+
ctx.addGlobalPermission({
|
|
9224
9300
|
actions: ["cloudwatch:PutMetricData"],
|
|
9225
9301
|
resources: ["*"],
|
|
9226
9302
|
conditions: {
|
|
@@ -9299,7 +9375,7 @@ var metricFeature = defineFeature({
|
|
|
9299
9375
|
import { days as days10, seconds as seconds7, toSeconds as toSeconds13, years } from "@awsless/duration";
|
|
9300
9376
|
import { Group as Group29 } from "@terraforge/core";
|
|
9301
9377
|
import { aws as aws30 } from "@terraforge/aws";
|
|
9302
|
-
import { camelCase as camelCase9, constantCase as constantCase15 } from "change-case";
|
|
9378
|
+
import { camelCase as camelCase9, constantCase as constantCase15, kebabCase as kebabCase12 } from "change-case";
|
|
9303
9379
|
|
|
9304
9380
|
// src/feature/router/router-code.ts
|
|
9305
9381
|
import { minutes as minutes8, seconds as seconds6, toSeconds as toSeconds12 } from "@awsless/duration";
|
|
@@ -9498,6 +9574,38 @@ function isValidRoute(route, method) {
|
|
|
9498
9574
|
return true;
|
|
9499
9575
|
}
|
|
9500
9576
|
|
|
9577
|
+
function matchRoute(value, path, method) {
|
|
9578
|
+
const list = Array.isArray(value) ? value : [value];
|
|
9579
|
+
|
|
9580
|
+
for(const i in list) {
|
|
9581
|
+
const route = list[i];
|
|
9582
|
+
|
|
9583
|
+
if(!isValidRoute(route, method)) {
|
|
9584
|
+
continue;
|
|
9585
|
+
}
|
|
9586
|
+
|
|
9587
|
+
if(route.match) {
|
|
9588
|
+
const found = path.match(new RegExp(route.match));
|
|
9589
|
+
|
|
9590
|
+
if(!found) {
|
|
9591
|
+
continue;
|
|
9592
|
+
}
|
|
9593
|
+
|
|
9594
|
+
const params = {};
|
|
9595
|
+
|
|
9596
|
+
if(route.params) {
|
|
9597
|
+
for(const p in route.params) {
|
|
9598
|
+
params[route.params[p]] = found[Number(p) + 1];
|
|
9599
|
+
}
|
|
9600
|
+
}
|
|
9601
|
+
|
|
9602
|
+
return { route: route, params: params };
|
|
9603
|
+
}
|
|
9604
|
+
|
|
9605
|
+
return { route: route };
|
|
9606
|
+
}
|
|
9607
|
+
}
|
|
9608
|
+
|
|
9501
9609
|
async function findRoute(path, method, prefix) {
|
|
9502
9610
|
// only route selection is normalized, the forwarded uri stays untouched
|
|
9503
9611
|
if (path.length > 1 && path.slice(-1) === '/') {
|
|
@@ -9509,14 +9617,34 @@ async function findRoute(path, method, prefix) {
|
|
|
9509
9617
|
|
|
9510
9618
|
for(const i in keys) {
|
|
9511
9619
|
const key = keys[i];
|
|
9620
|
+
let value;
|
|
9512
9621
|
|
|
9513
9622
|
try {
|
|
9514
|
-
|
|
9623
|
+
value = await store.get(prefix + key, { format: 'json' });
|
|
9624
|
+
} catch (e) {
|
|
9625
|
+
continue;
|
|
9626
|
+
}
|
|
9627
|
+
|
|
9628
|
+
// Route lists that are too big for a single key value pair
|
|
9629
|
+
// are sharded over multiple entries behind a route index.
|
|
9630
|
+
if(value && value.list) {
|
|
9631
|
+
for(let n = 0; n < value.list; n++) {
|
|
9632
|
+
try {
|
|
9633
|
+
const route = await store.get(prefix + key + '#' + n, { format: 'json' });
|
|
9634
|
+
const result = matchRoute(route, path, method);
|
|
9635
|
+
|
|
9636
|
+
if(result) {
|
|
9637
|
+
return result;
|
|
9638
|
+
}
|
|
9639
|
+
} catch (e) {}
|
|
9640
|
+
}
|
|
9641
|
+
} else {
|
|
9642
|
+
const result = matchRoute(value, path, method);
|
|
9515
9643
|
|
|
9516
|
-
if(
|
|
9517
|
-
return
|
|
9644
|
+
if(result) {
|
|
9645
|
+
return result;
|
|
9518
9646
|
}
|
|
9519
|
-
}
|
|
9647
|
+
}
|
|
9520
9648
|
}
|
|
9521
9649
|
}
|
|
9522
9650
|
|
|
@@ -9639,15 +9767,36 @@ async function handler(event) {
|
|
|
9639
9767
|
|
|
9640
9768
|
${prefixCode}
|
|
9641
9769
|
|
|
9642
|
-
const
|
|
9770
|
+
const result = await findRoute(path, request.method, prefix);
|
|
9643
9771
|
|
|
9644
|
-
if(!
|
|
9772
|
+
if(!result) {
|
|
9645
9773
|
return {
|
|
9646
9774
|
statusCode: 404,
|
|
9647
9775
|
statusDescription: 'Not Found'
|
|
9648
9776
|
};
|
|
9649
9777
|
}
|
|
9650
9778
|
|
|
9779
|
+
const route = result.route;
|
|
9780
|
+
|
|
9781
|
+
// A client provided param header can never reach the origin.
|
|
9782
|
+
const spoofed = [];
|
|
9783
|
+
|
|
9784
|
+
for(const name in headers) {
|
|
9785
|
+
if(name.indexOf('x-param-') === 0) {
|
|
9786
|
+
spoofed.push(name);
|
|
9787
|
+
}
|
|
9788
|
+
}
|
|
9789
|
+
|
|
9790
|
+
for(const i in spoofed) {
|
|
9791
|
+
delete headers[spoofed[i]];
|
|
9792
|
+
}
|
|
9793
|
+
|
|
9794
|
+
if(result.params) {
|
|
9795
|
+
for(const name in result.params) {
|
|
9796
|
+
headers['x-param-' + name.toLowerCase()] = { value: encodeURIComponent(result.params[name]) };
|
|
9797
|
+
}
|
|
9798
|
+
}
|
|
9799
|
+
|
|
9651
9800
|
if(route.requestHeaders) {
|
|
9652
9801
|
for(const name in route.requestHeaders) {
|
|
9653
9802
|
headers[name] = { value: route.requestHeaders[name] };
|
|
@@ -9700,6 +9849,31 @@ async function handler(event) {
|
|
|
9700
9849
|
`;
|
|
9701
9850
|
|
|
9702
9851
|
// src/feature/router/index.ts
|
|
9852
|
+
var MAX_VALUE_SIZE = 1e3;
|
|
9853
|
+
var ORIGIN_PLACEHOLDER = "x".repeat(64);
|
|
9854
|
+
var assertRouteValueSize = (key, route) => {
|
|
9855
|
+
const withOrigin = (entry) => {
|
|
9856
|
+
return entry.type === "lambda" ? { ...entry, domainName: ORIGIN_PLACEHOLDER } : entry;
|
|
9857
|
+
};
|
|
9858
|
+
for (const entry of Array.isArray(route) ? route : [route]) {
|
|
9859
|
+
if (Buffer.byteLength(JSON.stringify(withOrigin(entry)), "utf8") > MAX_VALUE_SIZE) {
|
|
9860
|
+
throw new ExpectedError(`The route value of the "${key}" route key is too large.`);
|
|
9861
|
+
}
|
|
9862
|
+
}
|
|
9863
|
+
};
|
|
9864
|
+
var createRouteStoreEntries = (key, route) => {
|
|
9865
|
+
const value = JSON.stringify(route);
|
|
9866
|
+
if (!Array.isArray(route) || Buffer.byteLength(value, "utf8") <= MAX_VALUE_SIZE) {
|
|
9867
|
+
return [{ key, value }];
|
|
9868
|
+
}
|
|
9869
|
+
return [
|
|
9870
|
+
{ key, value: JSON.stringify({ list: route.length }) },
|
|
9871
|
+
...route.map((entry, index) => ({
|
|
9872
|
+
key: `${key}#${index}`,
|
|
9873
|
+
value: JSON.stringify(entry)
|
|
9874
|
+
}))
|
|
9875
|
+
];
|
|
9876
|
+
};
|
|
9703
9877
|
var routerFeature = defineFeature({
|
|
9704
9878
|
name: "router",
|
|
9705
9879
|
onApp(ctx) {
|
|
@@ -9745,12 +9919,13 @@ var routerFeature = defineFeature({
|
|
|
9745
9919
|
if (Object.hasOwn(routes, `${id}:${key}`)) {
|
|
9746
9920
|
throw new ExpectedError(`Duplicate route key: ${key} in the "${id}" router`);
|
|
9747
9921
|
}
|
|
9922
|
+
assertRouteValueSize(`${id}:${key}`, route);
|
|
9748
9923
|
routes[`${id}:${key}`] = route;
|
|
9749
9924
|
}
|
|
9750
9925
|
for (const dependency of options?.dependsOn ?? []) {
|
|
9751
9926
|
routeDependencies.add(dependency);
|
|
9752
9927
|
}
|
|
9753
|
-
if (Object.values(newRoutes).some((route) => route.type === "lambda")) {
|
|
9928
|
+
if (Object.values(newRoutes).flat().some((route) => route.type === "lambda")) {
|
|
9754
9929
|
hasLambdaRoutes = true;
|
|
9755
9930
|
}
|
|
9756
9931
|
});
|
|
@@ -10152,12 +10327,15 @@ var routerFeature = defineFeature({
|
|
|
10152
10327
|
storeArn: routeStore.arn,
|
|
10153
10328
|
functionVersion: bundle.lambda.version,
|
|
10154
10329
|
routes: $resolve([routes, lambdaUrlHost], (routes2, lambdaUrlHost2) => {
|
|
10155
|
-
|
|
10156
|
-
|
|
10157
|
-
|
|
10158
|
-
|
|
10330
|
+
const withOrigin = (route) => {
|
|
10331
|
+
return route.type === "lambda" ? { ...route, domainName: lambdaUrlHost2 } : route;
|
|
10332
|
+
};
|
|
10333
|
+
return Object.entries(routes2).flatMap(
|
|
10334
|
+
([key, route]) => createRouteStoreEntries(
|
|
10335
|
+
key,
|
|
10336
|
+
Array.isArray(route) ? route.map(withOrigin) : withOrigin(route)
|
|
10159
10337
|
)
|
|
10160
|
-
|
|
10338
|
+
);
|
|
10161
10339
|
})
|
|
10162
10340
|
},
|
|
10163
10341
|
{
|
|
@@ -10217,6 +10395,41 @@ var routerFeature = defineFeature({
|
|
|
10217
10395
|
ctx.bind(`ROUTER_${constantCase15(id)}_ENDPOINT`, domainName);
|
|
10218
10396
|
}
|
|
10219
10397
|
}
|
|
10398
|
+
},
|
|
10399
|
+
onStack(ctx) {
|
|
10400
|
+
for (const [id, patterns] of Object.entries(ctx.stackConfig.routes ?? {})) {
|
|
10401
|
+
if (!ctx.appConfig.defaults.router?.[id]) {
|
|
10402
|
+
throw new FileError(ctx.stackConfig.file, `Router "${id}" is not defined on the app level.`);
|
|
10403
|
+
}
|
|
10404
|
+
const addRoutes = ctx.shared.entry("router", "addRoutes", id);
|
|
10405
|
+
const grouped = {};
|
|
10406
|
+
for (const [pattern, props] of Object.entries(patterns)) {
|
|
10407
|
+
const compiled = compileRoutePattern(pattern);
|
|
10408
|
+
const slug = kebabCase12(pattern).slice(0, 20);
|
|
10409
|
+
const routeKey = formatRouteKey(ctx.stack.name, "route", `${slug || "root"}-${shortId(pattern)}`);
|
|
10410
|
+
registerBundleFunction(ctx, routeKey, props);
|
|
10411
|
+
grouped[compiled.key] ??= [];
|
|
10412
|
+
grouped[compiled.key].push({
|
|
10413
|
+
type: "lambda",
|
|
10414
|
+
forwardHost: true,
|
|
10415
|
+
urlEncodedQueryString: true,
|
|
10416
|
+
match: compiled.match,
|
|
10417
|
+
params: compiled.params,
|
|
10418
|
+
requestHeaders: {
|
|
10419
|
+
[ROUTE_HEADER]: routeKey
|
|
10420
|
+
}
|
|
10421
|
+
});
|
|
10422
|
+
}
|
|
10423
|
+
const merged = {};
|
|
10424
|
+
for (const [key, list3] of Object.entries(grouped)) {
|
|
10425
|
+
if (list3.length === 1) {
|
|
10426
|
+
merged[key] = list3[0];
|
|
10427
|
+
} else {
|
|
10428
|
+
merged[key] = [...list3.filter((route) => route.match), ...list3.filter((route) => !route.match)];
|
|
10429
|
+
}
|
|
10430
|
+
}
|
|
10431
|
+
addRoutes(merged);
|
|
10432
|
+
}
|
|
10220
10433
|
}
|
|
10221
10434
|
});
|
|
10222
10435
|
|
|
@@ -10602,14 +10815,6 @@ var SharedData = class {
|
|
|
10602
10815
|
};
|
|
10603
10816
|
|
|
10604
10817
|
// src/app.ts
|
|
10605
|
-
var assertDepsExists = (stack, stacks) => {
|
|
10606
|
-
for (const dep of stack.depends ?? []) {
|
|
10607
|
-
const found = stacks.find((i) => i.name === dep);
|
|
10608
|
-
if (!found) {
|
|
10609
|
-
throw new FileError(stack.file, `Stack "${stack.name}" depends on a stack "${dep}" that doesn't exist.`);
|
|
10610
|
-
}
|
|
10611
|
-
}
|
|
10612
|
-
};
|
|
10613
10818
|
var createApp = (props) => {
|
|
10614
10819
|
const app = new App2(props.appConfig.name);
|
|
10615
10820
|
const zones = new Stack(app, "zones");
|
|
@@ -10633,17 +10838,10 @@ var createApp = (props) => {
|
|
|
10633
10838
|
const bindListeners = [];
|
|
10634
10839
|
const globalEnv = [];
|
|
10635
10840
|
const globalEnvListeners = [];
|
|
10636
|
-
const allLocalEnv = {};
|
|
10637
|
-
const allLocalEnvListeners = {};
|
|
10638
10841
|
const globalPermissions = [];
|
|
10639
10842
|
const globalPermissionCallbacks = [];
|
|
10640
10843
|
const appPermissions = [];
|
|
10641
10844
|
const appPermissionCallbacks = [];
|
|
10642
|
-
const allStackPermissions = {};
|
|
10643
|
-
const allStackPermissionCallbacks = {};
|
|
10644
|
-
for (const stackConfig of props.stackConfigs) {
|
|
10645
|
-
assertDepsExists(stackConfig, props.stackConfigs);
|
|
10646
|
-
}
|
|
10647
10845
|
for (const feature of features) {
|
|
10648
10846
|
feature.onBefore?.({
|
|
10649
10847
|
...props,
|
|
@@ -10716,14 +10914,6 @@ var createApp = (props) => {
|
|
|
10716
10914
|
}
|
|
10717
10915
|
for (const stackConfig of props.stackConfigs) {
|
|
10718
10916
|
const stack = new Stack(app, stackConfig.name);
|
|
10719
|
-
const localEnvListeners = [];
|
|
10720
|
-
const localEnv = [];
|
|
10721
|
-
const stackPermissions = [];
|
|
10722
|
-
const stackPermissionCallbacks = [];
|
|
10723
|
-
allStackPermissions[stack.name] = stackPermissions;
|
|
10724
|
-
allStackPermissionCallbacks[stack.name] = stackPermissionCallbacks;
|
|
10725
|
-
allLocalEnvListeners[stack.name] = localEnvListeners;
|
|
10726
|
-
allLocalEnv[stack.name] = localEnv;
|
|
10727
10917
|
for (const feature of features) {
|
|
10728
10918
|
feature.onStack?.({
|
|
10729
10919
|
...props,
|
|
@@ -10737,7 +10927,6 @@ var createApp = (props) => {
|
|
|
10737
10927
|
shared,
|
|
10738
10928
|
onPermission(callback) {
|
|
10739
10929
|
globalPermissionCallbacks.push(callback);
|
|
10740
|
-
stackPermissionCallbacks.push(callback);
|
|
10741
10930
|
},
|
|
10742
10931
|
addGlobalPermission(permission) {
|
|
10743
10932
|
globalPermissions.push(permission);
|
|
@@ -10745,9 +10934,6 @@ var createApp = (props) => {
|
|
|
10745
10934
|
addAppPermission(permission) {
|
|
10746
10935
|
appPermissions.push(permission);
|
|
10747
10936
|
},
|
|
10748
|
-
addStackPermission(permission) {
|
|
10749
|
-
stackPermissions.push(permission);
|
|
10750
|
-
},
|
|
10751
10937
|
addWarning(props2) {
|
|
10752
10938
|
warnings.push(props2);
|
|
10753
10939
|
},
|
|
@@ -10811,10 +10997,10 @@ var createApp = (props) => {
|
|
|
10811
10997
|
bindListeners.push(cb);
|
|
10812
10998
|
},
|
|
10813
10999
|
addEnv(name, value) {
|
|
10814
|
-
|
|
11000
|
+
globalEnv.push({ name, value });
|
|
10815
11001
|
},
|
|
10816
11002
|
onEnv(cb) {
|
|
10817
|
-
|
|
11003
|
+
globalEnvListeners.push(cb);
|
|
10818
11004
|
},
|
|
10819
11005
|
onReady(cb) {
|
|
10820
11006
|
readyListeners.push(cb);
|
|
@@ -10824,16 +11010,6 @@ var createApp = (props) => {
|
|
|
10824
11010
|
}
|
|
10825
11011
|
});
|
|
10826
11012
|
}
|
|
10827
|
-
for (const callback of stackPermissionCallbacks) {
|
|
10828
|
-
for (const permission of stackPermissions) {
|
|
10829
|
-
callback(permission);
|
|
10830
|
-
}
|
|
10831
|
-
}
|
|
10832
|
-
for (const listener of localEnvListeners) {
|
|
10833
|
-
for (const env of localEnv) {
|
|
10834
|
-
listener(env.name, env.value);
|
|
10835
|
-
}
|
|
10836
|
-
}
|
|
10837
11013
|
}
|
|
10838
11014
|
for (const callback of appPermissionCallbacks) {
|
|
10839
11015
|
for (const permission of appPermissions) {
|
|
@@ -10855,24 +11031,6 @@ var createApp = (props) => {
|
|
|
10855
11031
|
listener(name, value);
|
|
10856
11032
|
}
|
|
10857
11033
|
}
|
|
10858
|
-
for (const stackConfig of props.stackConfigs) {
|
|
10859
|
-
const envListeners = allLocalEnvListeners[stackConfig.name];
|
|
10860
|
-
const permissionCallbacks = allStackPermissionCallbacks[stackConfig.name];
|
|
10861
|
-
for (const dependency of stackConfig.depends ?? []) {
|
|
10862
|
-
const permissions = allStackPermissions[dependency];
|
|
10863
|
-
const env = allLocalEnv[dependency];
|
|
10864
|
-
for (const permission of permissions) {
|
|
10865
|
-
for (const callback of permissionCallbacks) {
|
|
10866
|
-
callback(permission);
|
|
10867
|
-
}
|
|
10868
|
-
}
|
|
10869
|
-
for (const entry of env) {
|
|
10870
|
-
for (const listener of envListeners) {
|
|
10871
|
-
listener(entry.name, entry.value);
|
|
10872
|
-
}
|
|
10873
|
-
}
|
|
10874
|
-
}
|
|
10875
|
-
}
|
|
10876
11034
|
const ready = () => {
|
|
10877
11035
|
for (const listener of readyListeners) {
|
|
10878
11036
|
listener();
|
|
@@ -13507,10 +13665,13 @@ program.on("option:no-cache", () => {
|
|
|
13507
13665
|
commands10.forEach((fn) => fn(program));
|
|
13508
13666
|
|
|
13509
13667
|
// src/bin.ts
|
|
13510
|
-
var interrupt = (code) => () => {
|
|
13668
|
+
var interrupt = (signal, code) => () => {
|
|
13511
13669
|
process.stdout.write("\x1B[?25h");
|
|
13670
|
+
if (signal === "SIGINT" && process.listenerCount(signal) > 1) {
|
|
13671
|
+
return;
|
|
13672
|
+
}
|
|
13512
13673
|
process.exit(code);
|
|
13513
13674
|
};
|
|
13514
|
-
process.on("SIGINT", interrupt(130));
|
|
13515
|
-
process.on("SIGTERM", interrupt(143));
|
|
13675
|
+
process.on("SIGINT", interrupt("SIGINT", 130));
|
|
13676
|
+
process.on("SIGTERM", interrupt("SIGTERM", 143));
|
|
13516
13677
|
program.parse(process.argv);
|
|
@@ -483,6 +483,70 @@ var InstanceDefaultSchema = z15.object({
|
|
|
483
483
|
// src/feature/router/schema.ts
|
|
484
484
|
import { days as days3, minutes as minutes2, parse as parse3 } from "@awsless/duration";
|
|
485
485
|
import { z as z16 } from "zod";
|
|
486
|
+
|
|
487
|
+
// src/error.ts
|
|
488
|
+
var ExpectedError = class extends Error {
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
// src/feature/router/pattern.ts
|
|
492
|
+
var PARAM_TOKEN = /\{([a-zA-Z_][a-zA-Z0-9_]*)\}|\*/g;
|
|
493
|
+
var escapeRegex = (value) => {
|
|
494
|
+
return value.replace(/[|\\{}()[\]^$+*?.\-]/g, "\\$&");
|
|
495
|
+
};
|
|
496
|
+
var compileRoutePattern = (pattern) => {
|
|
497
|
+
if (!pattern.startsWith("/")) {
|
|
498
|
+
throw new ExpectedError(`Route pattern "${pattern}" must start with a slash (/)`);
|
|
499
|
+
}
|
|
500
|
+
if (pattern === "/*") {
|
|
501
|
+
return { key: pattern };
|
|
502
|
+
}
|
|
503
|
+
const params = [];
|
|
504
|
+
let regex = "";
|
|
505
|
+
let stars = 0;
|
|
506
|
+
let last = 0;
|
|
507
|
+
let token;
|
|
508
|
+
PARAM_TOKEN.lastIndex = 0;
|
|
509
|
+
while (token = PARAM_TOKEN.exec(pattern)) {
|
|
510
|
+
regex += escapeRegex(pattern.slice(last, token.index));
|
|
511
|
+
const param = token[1];
|
|
512
|
+
if (param) {
|
|
513
|
+
if (params.includes(param)) {
|
|
514
|
+
throw new ExpectedError(`Duplicate param "${param}" in route pattern "${pattern}"`);
|
|
515
|
+
}
|
|
516
|
+
params.push(param);
|
|
517
|
+
regex += "([^/]+)";
|
|
518
|
+
} else {
|
|
519
|
+
stars++;
|
|
520
|
+
regex += ".*";
|
|
521
|
+
}
|
|
522
|
+
last = PARAM_TOKEN.lastIndex;
|
|
523
|
+
}
|
|
524
|
+
if (params.length === 0 && stars === 0) {
|
|
525
|
+
return { key: pattern };
|
|
526
|
+
}
|
|
527
|
+
regex += escapeRegex(pattern.slice(last));
|
|
528
|
+
const root = pattern.split("/")[1] ?? "";
|
|
529
|
+
if (root === "" || root.includes("*") || root.includes("{")) {
|
|
530
|
+
throw new ExpectedError(
|
|
531
|
+
`The first path segment of route pattern "${pattern}" must be static when the pattern contains params or wildcards.`
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
if (root.includes(".")) {
|
|
535
|
+
throw new ExpectedError(
|
|
536
|
+
`The first path segment of route pattern "${pattern}" can't contain a dot when the pattern contains params or wildcards.`
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
if (params.length === 0 && pattern === `/${root}/*`) {
|
|
540
|
+
return { key: pattern };
|
|
541
|
+
}
|
|
542
|
+
return {
|
|
543
|
+
key: `/${root}/*`,
|
|
544
|
+
match: `^${regex}$`,
|
|
545
|
+
params: params.length > 0 ? params : void 0
|
|
546
|
+
};
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
// src/feature/router/schema.ts
|
|
486
550
|
var ErrorResponsePathSchema = z16.string().describe(
|
|
487
551
|
[
|
|
488
552
|
"The path to the custom error page that you want to return to the viewer when your origin returns the HTTP status code specified.",
|
|
@@ -512,6 +576,28 @@ var ErrorResponseSchema = z16.union([
|
|
|
512
576
|
})
|
|
513
577
|
]).optional();
|
|
514
578
|
var RouteSchema = z16.string().regex(/^\//, "Route must start with a slash (/)").regex(/^\/([^/*.]+)?$/, 'Router paths mount a single segment without dots, like "/api".');
|
|
579
|
+
var RoutesSchema = z16.record(
|
|
580
|
+
ResourceIdSchema.describe("The router id to add your routes to."),
|
|
581
|
+
z16.record(z16.string().regex(/^\//, "Route must start with a slash (/)"), FunctionSchema).superRefine((routes, ctx) => {
|
|
582
|
+
for (const pattern of Object.keys(routes)) {
|
|
583
|
+
try {
|
|
584
|
+
compileRoutePattern(pattern);
|
|
585
|
+
} catch (error) {
|
|
586
|
+
ctx.addIssue({
|
|
587
|
+
code: z16.ZodIssueCode.custom,
|
|
588
|
+
path: [pattern],
|
|
589
|
+
message: error instanceof Error ? error.message : `Invalid route pattern: ${pattern}`
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}).describe(
|
|
594
|
+
[
|
|
595
|
+
"Define the routes and the lambda function that should handle them.",
|
|
596
|
+
'Routes can be an exact path like "/sitemap.xml", a wildcard like "/sitemap/*", or contain params like "/sitemap/{locale}/{page}.xml".',
|
|
597
|
+
'Param values are passed to the function as "x-param-[NAME]" request headers.'
|
|
598
|
+
].join("\n")
|
|
599
|
+
)
|
|
600
|
+
).optional().describe("Add routes to your global Router that link a path pattern to a lambda function.");
|
|
515
601
|
var VisibilitySchema = z16.boolean().default(false).describe("Whether to enable CloudWatch metrics for the WAF rule.");
|
|
516
602
|
var WafSettingsSchema = z16.object({
|
|
517
603
|
rateLimiter: z16.object({
|
|
@@ -1619,14 +1705,13 @@ var TestsSchema = z40.union([
|
|
|
1619
1705
|
]).describe("Define the location of your tests for your stack.").optional();
|
|
1620
1706
|
|
|
1621
1707
|
// src/config/stack.ts
|
|
1622
|
-
var DependsSchema = ResourceIdSchema.array().optional().describe("Define the stacks that this stack is depended on.");
|
|
1623
1708
|
var NameSchema = ResourceIdSchema.refine((name) => !["base", "hostedzones"].includes(name), {
|
|
1624
1709
|
message: `Stack name can't be a reserved name.`
|
|
1625
1710
|
}).describe("Stack name.");
|
|
1626
1711
|
var StackSchema = z41.object({
|
|
1627
1712
|
$schema: z41.string().optional(),
|
|
1628
1713
|
name: NameSchema,
|
|
1629
|
-
|
|
1714
|
+
routes: RoutesSchema,
|
|
1630
1715
|
commands: CommandsSchema,
|
|
1631
1716
|
// auth: AuthSchema,
|
|
1632
1717
|
// http: HttpSchema,
|