@awsless/awsless 0.0.1

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 ADDED
@@ -0,0 +1,2389 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ __require,
4
+ definePlugin
5
+ } from "./chunk-6KILQ5DR.js";
6
+
7
+ // src/cli/program.ts
8
+ import { Command } from "commander";
9
+
10
+ // src/app.ts
11
+ import { App as App4, DefaultStackSynthesizer } from "aws-cdk-lib";
12
+
13
+ // src/stack.ts
14
+ import { Arn, Stack } from "aws-cdk-lib";
15
+ import { PolicyStatement } from "aws-cdk-lib/aws-iam";
16
+
17
+ // src/cli/style.ts
18
+ import chalk from "chalk";
19
+ var symbol = {
20
+ info: "\u2139",
21
+ success: "\u2714",
22
+ warning: "\u26A0",
23
+ question: "?",
24
+ error: "\u2716",
25
+ ellipsis: "\u2026",
26
+ pointerSmall: "\u203A",
27
+ // line: '─',
28
+ pointer: "\u276F"
29
+ };
30
+ var style = {
31
+ primary: chalk.bold.hex("#FF9000"),
32
+ // title: chalk.white,
33
+ normal: chalk.white,
34
+ label: chalk.white.bold,
35
+ placeholder: chalk.dim,
36
+ link: chalk.cyan,
37
+ info: chalk.blue,
38
+ success: chalk.green,
39
+ warning: chalk.yellow,
40
+ error: chalk.red,
41
+ attr: chalk.yellow,
42
+ cursor: chalk.bgWhite.blackBright
43
+ };
44
+
45
+ // src/cli/logger.ts
46
+ var queue = [];
47
+ var debugError = (error) => {
48
+ queue.push({
49
+ date: /* @__PURE__ */ new Date(),
50
+ type: style.error.dim("error"),
51
+ // color: 'red',
52
+ // type: 'error',
53
+ message: typeof error === "string" ? error : error instanceof Error ? style.error(error.message || "") : JSON.stringify(error)
54
+ });
55
+ };
56
+ var debug = (...parts) => {
57
+ queue.push({
58
+ date: /* @__PURE__ */ new Date(),
59
+ type: style.warning.dim("debug"),
60
+ // color: 'yellow',
61
+ // type: 'debug',
62
+ message: parts.map((part) => typeof part === "string" ? part : JSON.stringify(part)).join(" ")
63
+ });
64
+ };
65
+ var flushDebug = () => {
66
+ return queue.splice(0, queue.length);
67
+ };
68
+
69
+ // src/util/param.ts
70
+ import { DeleteParameterCommand, GetParameterCommand, GetParametersByPathCommand, ParameterType, PutParameterCommand, SSMClient } from "@aws-sdk/client-ssm";
71
+ var configParameterPrefix = (config2) => {
72
+ return `/${config2.stage}/awsless/${config2.name}`;
73
+ };
74
+ var Params = class {
75
+ constructor(config2) {
76
+ this.config = config2;
77
+ this.client = new SSMClient({
78
+ credentials: config2.credentials,
79
+ region: config2.region
80
+ });
81
+ }
82
+ client;
83
+ getName(name) {
84
+ return `${configParameterPrefix(this.config)}/${name}`;
85
+ }
86
+ async get(name) {
87
+ debug("Get remote config value");
88
+ debug("Name:", style.info(name));
89
+ let result;
90
+ try {
91
+ result = await this.client.send(new GetParameterCommand({
92
+ Name: this.getName(name),
93
+ WithDecryption: true
94
+ }));
95
+ } catch (error) {
96
+ if (error instanceof Error && error.name === "ParameterNotFound") {
97
+ debug("Parameter not found");
98
+ return;
99
+ }
100
+ throw error;
101
+ }
102
+ const value = result.Parameter?.Value;
103
+ debug("Value:", style.info(value));
104
+ debug("Done getting remote config value");
105
+ return value;
106
+ }
107
+ async set(name, value) {
108
+ debug("Save remote config value");
109
+ debug("Name:", style.info(name));
110
+ debug("Value:", style.info(value));
111
+ await this.client.send(new PutParameterCommand({
112
+ Type: ParameterType.STRING,
113
+ Name: this.getName(name),
114
+ Value: value,
115
+ Overwrite: true
116
+ }));
117
+ debug("Done saving remote config value");
118
+ }
119
+ async delete(name) {
120
+ debug("Delete remote config value");
121
+ debug("Name:", style.info(name));
122
+ try {
123
+ await this.client.send(new DeleteParameterCommand({
124
+ Name: this.getName(name)
125
+ }));
126
+ } catch (error) {
127
+ if (error instanceof Error && error.name === "ParameterNotFound") {
128
+ debug("Remote config value was already deleted");
129
+ return;
130
+ }
131
+ throw error;
132
+ }
133
+ debug("Done deleting remote config value");
134
+ }
135
+ async list() {
136
+ debug("Load remote config values");
137
+ const result = await this.client.send(new GetParametersByPathCommand({
138
+ Path: configParameterPrefix(this.config),
139
+ WithDecryption: true,
140
+ MaxResults: 10,
141
+ Recursive: true
142
+ }));
143
+ debug("Done loading remote config values");
144
+ const values = {};
145
+ result.Parameters?.forEach((param) => {
146
+ const name = param.Name.substring(configParameterPrefix(this.config).length).substring(1);
147
+ values[name] = param.Value || "";
148
+ });
149
+ return values;
150
+ }
151
+ };
152
+
153
+ // src/stack.ts
154
+ var toStack = ({ config: config2, assets, app, stackConfig, plugins }) => {
155
+ const stackName = `${config2.name}-${stackConfig.name}`;
156
+ const stack = new Stack(app, stackConfig.name, {
157
+ stackName,
158
+ tags: {
159
+ APP: config2.name,
160
+ STAGE: config2.stage,
161
+ STACK: stackConfig.name
162
+ }
163
+ });
164
+ debug("Define stack:", style.info(stackConfig.name));
165
+ const bindings = [];
166
+ const bind = (cb) => {
167
+ bindings.push(cb);
168
+ };
169
+ debug("Run plugin onStack listeners");
170
+ const functions = plugins.map((plugin) => plugin.onStack?.({
171
+ config: config2,
172
+ assets,
173
+ app,
174
+ stack,
175
+ stackConfig,
176
+ bind
177
+ })).filter(Boolean).flat().filter(Boolean);
178
+ if (stack.node.children.length === 0) {
179
+ throw new Error(`Stack ${style.info(stackConfig.name)} has no resources defined`);
180
+ }
181
+ bindings.forEach((cb) => functions.forEach(cb));
182
+ const allowConfigParameters = new PolicyStatement({
183
+ actions: [
184
+ "ssm:GetParameter",
185
+ "ssm:GetParameters",
186
+ "ssm:GetParametersByPath"
187
+ ],
188
+ resources: [
189
+ Arn.format({
190
+ service: "ssm",
191
+ resource: "parameter",
192
+ resourceName: configParameterPrefix(config2)
193
+ })
194
+ // Fn.sub('arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter' + configParameterPrefix(config)),
195
+ ]
196
+ });
197
+ functions.forEach((lambda) => lambda.addToRolePolicy(allowConfigParameters));
198
+ return {
199
+ stack,
200
+ depends: stackConfig.depends
201
+ };
202
+ };
203
+
204
+ // src/util/path.ts
205
+ import { join } from "path";
206
+ var rootDir = process.cwd();
207
+ var outDir = join(rootDir, ".awsless");
208
+ var assemblyDir = join(outDir, "assembly");
209
+ var functionDir = join(outDir, "function");
210
+
211
+ // src/stack/app-bootstrap.ts
212
+ import { Stack as Stack3 } from "aws-cdk-lib";
213
+
214
+ // src/plugins/cron/index.ts
215
+ import { z as z10 } from "zod";
216
+
217
+ // src/plugins/cron/schema/schedule.ts
218
+ import { Schedule } from "aws-cdk-lib/aws-events";
219
+ import { z } from "zod";
220
+ import { awsCronExpressionValidator } from "aws-cron-expression-validator";
221
+ var RateExpressionSchema = z.custom((value) => {
222
+ return z.string().regex(/rate\([0-9]+ (seconds?|minutes?|hours?|days?)\)/).refine((rate) => {
223
+ const [str] = rate.substring(5).split(" ");
224
+ const number = parseInt(str);
225
+ return number > 0;
226
+ }).safeParse(value).success;
227
+ }, "Invalid rate expression").transform(Schedule.expression);
228
+ var CronExpressionSchema = z.custom((value) => {
229
+ return z.string().startsWith("cron(").endsWith(")").safeParse(value).success;
230
+ }, "Invalid cron expression").superRefine((value, ctx) => {
231
+ const cron = value.substring(5, value.length - 1);
232
+ try {
233
+ awsCronExpressionValidator(cron);
234
+ } catch (error) {
235
+ if (error instanceof Error) {
236
+ ctx.addIssue({
237
+ code: z.ZodIssueCode.custom,
238
+ message: error.message
239
+ });
240
+ } else {
241
+ ctx.addIssue({
242
+ code: z.ZodIssueCode.custom,
243
+ message: "Invalid cron expression"
244
+ });
245
+ }
246
+ }
247
+ }).transform(Schedule.expression);
248
+ var ScheduleExpressionSchema = RateExpressionSchema.or(CronExpressionSchema);
249
+
250
+ // src/plugins/cron/index.ts
251
+ import { Rule } from "aws-cdk-lib/aws-events";
252
+
253
+ // src/util/resource.ts
254
+ import { constantCase, paramCase, pascalCase } from "change-case";
255
+ var toId = (resource, id) => {
256
+ return pascalCase(`${resource}-${id}`);
257
+ };
258
+ var toName = (stack, id) => {
259
+ return paramCase(`${stack.stackName}-${id}`);
260
+ };
261
+ var toEnvKey = (resource, id) => {
262
+ return constantCase(`RESOURCE_${resource}_${id}`);
263
+ };
264
+ var addResourceEnvironment = (stack, resource, id, lambda) => {
265
+ const key = toEnvKey(resource, id);
266
+ const value = toName(stack, id);
267
+ lambda.addEnvironment(key, value, {
268
+ removeInEdge: true
269
+ });
270
+ };
271
+
272
+ // src/plugins/function/index.ts
273
+ import { z as z9 } from "zod";
274
+
275
+ // src/schema/duration.ts
276
+ import { z as z2 } from "zod";
277
+ import { Duration as CDKDuration } from "aws-cdk-lib/core";
278
+ function toDuration(duration) {
279
+ const [count, unit] = duration.split(" ");
280
+ const countNum = parseInt(count);
281
+ const unitLower = unit.toLowerCase();
282
+ if (unitLower.startsWith("second")) {
283
+ return CDKDuration.seconds(countNum);
284
+ } else if (unitLower.startsWith("minute")) {
285
+ return CDKDuration.minutes(countNum);
286
+ } else if (unitLower.startsWith("hour")) {
287
+ return CDKDuration.hours(countNum);
288
+ } else if (unitLower.startsWith("day")) {
289
+ return CDKDuration.days(countNum);
290
+ }
291
+ return CDKDuration.days(0);
292
+ }
293
+ var DurationSchema = z2.custom((value) => {
294
+ return z2.string().regex(/[0-9]+ (seconds?|minutes?|hours?|days?)/).safeParse(value).success;
295
+ }, "Invalid duration").transform(toDuration);
296
+
297
+ // src/schema/local-file.ts
298
+ import { access, constants } from "fs/promises";
299
+ import { z as z3 } from "zod";
300
+ var LocalFileSchema = z3.string().refine(async (path) => {
301
+ try {
302
+ await access(path, constants.R_OK);
303
+ } catch (error) {
304
+ return false;
305
+ }
306
+ return true;
307
+ }, `File doesn't exist`);
308
+
309
+ // src/plugins/function/index.ts
310
+ import { Code, Function } from "aws-cdk-lib/aws-lambda";
311
+
312
+ // src/plugins/function/schema/runtime.ts
313
+ import { Runtime as CdkRuntime } from "aws-cdk-lib/aws-lambda";
314
+ import { z as z4 } from "zod";
315
+ var runtimes = {
316
+ "container": CdkRuntime.FROM_IMAGE,
317
+ "rust": CdkRuntime.PROVIDED_AL2,
318
+ "nodejs16.x": CdkRuntime.NODEJS_16_X,
319
+ "nodejs18.x": CdkRuntime.NODEJS_18_X,
320
+ "python3.9": CdkRuntime.PYTHON_3_9,
321
+ "python3.10": CdkRuntime.PYTHON_3_10,
322
+ "go1.x": CdkRuntime.PROVIDED_AL2,
323
+ "go": CdkRuntime.PROVIDED_AL2
324
+ };
325
+ var toRuntime = (runtime) => {
326
+ return runtimes[runtime];
327
+ };
328
+ var RuntimeSchema = z4.enum(Object.keys(runtimes)).transform(toRuntime);
329
+
330
+ // src/plugins/function/schema/architecture.ts
331
+ import { Architecture as CdkArchitecture } from "aws-cdk-lib/aws-lambda";
332
+ import { z as z5 } from "zod";
333
+ var toArchitecture = (architecture) => {
334
+ return architecture === "x86_64" ? CdkArchitecture.X86_64 : CdkArchitecture.ARM_64;
335
+ };
336
+ var ArchitectureSchema = z5.enum(["x86_64", "arm_64"]).transform(toArchitecture);
337
+
338
+ // src/schema/resource-id.ts
339
+ import { z as z6 } from "zod";
340
+ var ResourceIdSchema = z6.string().min(3).max(24).regex(/[a-z\-]+/, "Invalid resource ID");
341
+
342
+ // src/schema/size.ts
343
+ import { Size as CDKSize } from "aws-cdk-lib/core";
344
+ import { z as z7 } from "zod";
345
+ function toSize(size) {
346
+ const [count, unit] = size.split(" ");
347
+ const countNum = parseInt(count);
348
+ if (unit === "KB") {
349
+ return CDKSize.kibibytes(countNum);
350
+ } else if (unit === "MB") {
351
+ return CDKSize.mebibytes(countNum);
352
+ } else if (unit === "GB") {
353
+ return CDKSize.gibibytes(countNum);
354
+ }
355
+ throw new TypeError(`Invalid size ${size}`);
356
+ }
357
+ var SizeSchema = z7.custom((value) => {
358
+ return z7.string().regex(/[0-9]+ (KB|MB|GB)/).safeParse(value).success;
359
+ }, "Invalid size").transform(toSize);
360
+
361
+ // src/plugins/function/util/build-worker.ts
362
+ import { Worker } from "worker_threads";
363
+ var cjs = typeof __require !== "undefined";
364
+ var importESM = `
365
+ import { bundle } from "@awsless/code";
366
+ import { createHash } from "crypto";
367
+ import { parentPort, workerData } from "worker_threads";
368
+ `;
369
+ var importCJS = `
370
+ const { bundle } = require("@awsless/code");
371
+ const { createHash } = require("crypto");
372
+ const { parentPort, workerData } = require("worker_threads");
373
+ `;
374
+ var workerCode = `
375
+ ${cjs ? importCJS : importESM}
376
+
377
+ const build = async (file) => {
378
+ const { code, map } = await bundle(file, {
379
+ format: 'esm',
380
+ sourceMap: true,
381
+ minimize: true,
382
+ onwarn: () => {},
383
+ moduleSideEffects: (id) => file === id,
384
+ external: (importee) => (
385
+ importee.startsWith('aws-sdk') ||
386
+ importee.startsWith('@aws-sdk')
387
+ ),
388
+ })
389
+
390
+ const hash = createHash('sha1').update(code).digest('hex')
391
+
392
+ parentPort.postMessage(JSON.stringify({
393
+ handler: 'index.default',
394
+ hash,
395
+ files: [
396
+ { name: 'index.js', code, map: map?.toString() }
397
+ ]
398
+ }))
399
+ }
400
+
401
+ build(workerData)
402
+ `;
403
+ var defaultBuild = async (file) => {
404
+ return new Promise((resolve, reject) => {
405
+ const worker = new Worker(workerCode, { workerData: file, eval: true });
406
+ const cleanUp2 = () => {
407
+ worker.removeAllListeners();
408
+ worker.terminate();
409
+ };
410
+ worker.on("message", (data) => {
411
+ resolve(JSON.parse(data.toString("utf8")));
412
+ cleanUp2();
413
+ });
414
+ worker.on("error", (data) => {
415
+ reject(data);
416
+ cleanUp2();
417
+ });
418
+ worker.on("exit", (code) => {
419
+ if (code !== 0) {
420
+ reject(new Error(`Worker exited with code ${code}`));
421
+ cleanUp2();
422
+ }
423
+ });
424
+ });
425
+ };
426
+
427
+ // src/plugins/function/util/build.ts
428
+ import JSZip from "jszip";
429
+ import { basename, join as join2 } from "path";
430
+ import { mkdir, writeFile } from "fs/promises";
431
+ import { filesize } from "filesize";
432
+ var zipFiles = (files) => {
433
+ const zip = new JSZip();
434
+ for (const file of files) {
435
+ zip.file(file.name, file.code);
436
+ }
437
+ return zip.generateAsync({
438
+ type: "nodebuffer",
439
+ compression: "DEFLATE",
440
+ compressionOptions: {
441
+ level: 9
442
+ }
443
+ });
444
+ };
445
+ var writeBuildHash = async (config2, stack, id, hash) => {
446
+ const funcPath = join2(functionDir, config2.name, stack.artifactId, id);
447
+ const versionFile = join2(funcPath, "HASH");
448
+ await writeFile(versionFile, hash);
449
+ };
450
+ var writeBuildFiles = async (config2, stack, id, files) => {
451
+ const bundle = await zipFiles(files);
452
+ const funcPath = join2(functionDir, config2.name, stack.artifactId, id);
453
+ const filesPath = join2(funcPath, "files");
454
+ const bundleFile = join2(funcPath, "bundle.zip");
455
+ debug("Bundle size of", style.info(join2(config2.name, stack.artifactId, id)), "is", style.attr(filesize(bundle.byteLength)));
456
+ await mkdir(filesPath, { recursive: true });
457
+ await writeFile(bundleFile, bundle);
458
+ await Promise.all(files.map(async (file) => {
459
+ const fileName = join2(filesPath, file.name);
460
+ await mkdir(basename(fileName), { recursive: true });
461
+ await writeFile(fileName, file.code);
462
+ if (file.map) {
463
+ const mapName = join2(filesPath, `${file.name}.map`);
464
+ await writeFile(mapName, file.map);
465
+ }
466
+ }));
467
+ return {
468
+ file: bundleFile,
469
+ size: bundle.byteLength
470
+ };
471
+ };
472
+
473
+ // src/plugins/function/util/publish.ts
474
+ import { join as join3 } from "path";
475
+ import { readFile } from "fs/promises";
476
+ import { GetObjectCommand, ObjectCannedACL, PutObjectCommand, S3Client, StorageClass } from "@aws-sdk/client-s3";
477
+
478
+ // src/stack/bootstrap.ts
479
+ import { CfnOutput, RemovalPolicy, Stack as Stack2 } from "aws-cdk-lib";
480
+ import { Bucket, BucketAccessControl } from "aws-cdk-lib/aws-s3";
481
+ var assetBucketName = (config2) => {
482
+ return `awsless-bootstrap-${config2.account}-${config2.region}`;
483
+ };
484
+ var assetBucketUrl = (config2, stackName) => {
485
+ const bucket = assetBucketName(config2);
486
+ return `https://s3-${config2.region}.amazonaws.com/${bucket}/${stackName}/cloudformation.json`;
487
+ };
488
+ var version = "2";
489
+ var bootstrapStack = (config2, app) => {
490
+ const stack = new Stack2(app, "bootstrap", {
491
+ stackName: `awsless-bootstrap`
492
+ });
493
+ new Bucket(stack, "assets", {
494
+ bucketName: assetBucketName(config2),
495
+ versioned: true,
496
+ accessControl: BucketAccessControl.PRIVATE,
497
+ removalPolicy: RemovalPolicy.DESTROY
498
+ });
499
+ new CfnOutput(stack, "version", {
500
+ exportName: "version",
501
+ value: version
502
+ });
503
+ return stack;
504
+ };
505
+ var shouldDeployBootstrap = async (client, name) => {
506
+ debug("Check bootstrap status");
507
+ const info = await client.get(name);
508
+ return !info || info.outputs.version !== version || !["CREATE_COMPLETE", "UPDATE_COMPLETE"].includes(info.status);
509
+ };
510
+
511
+ // src/plugins/function/util/publish.ts
512
+ var publishFunctionAsset = async (config2, stack, id) => {
513
+ const bucket = assetBucketName(config2);
514
+ const key = `${config2.name}/${stack.artifactId}/function/${id}.zip`;
515
+ const funcPath = join3(functionDir, config2.name, stack.artifactId, id);
516
+ const bundleFile = join3(funcPath, "bundle.zip");
517
+ const hashFile = join3(funcPath, "HASH");
518
+ const hash = await readFile(hashFile, "utf8");
519
+ const file = await readFile(bundleFile);
520
+ const client = new S3Client({
521
+ credentials: config2.credentials,
522
+ region: config2.region
523
+ });
524
+ let getResult;
525
+ try {
526
+ getResult = await client.send(new GetObjectCommand({
527
+ Bucket: bucket,
528
+ Key: key
529
+ }));
530
+ } catch (error) {
531
+ if (error instanceof Error && error.name === "NoSuchKey") {
532
+ } else {
533
+ throw error;
534
+ }
535
+ }
536
+ if (getResult?.Metadata?.hash === hash) {
537
+ return getResult.VersionId;
538
+ }
539
+ const putResult = await client.send(new PutObjectCommand({
540
+ Bucket: bucket,
541
+ Key: key,
542
+ Body: file,
543
+ ACL: ObjectCannedACL.private,
544
+ StorageClass: StorageClass.STANDARD,
545
+ Metadata: {
546
+ hash
547
+ }
548
+ }));
549
+ return putResult.VersionId;
550
+ };
551
+
552
+ // src/plugins/function/schema/retry-attempts.ts
553
+ import { z as z8 } from "zod";
554
+ var RetryAttempts = z8.number().int().min(0).max(2);
555
+
556
+ // src/util/byte-size.ts
557
+ import { filesize as filesize2 } from "filesize";
558
+ var formatByteSize = (size) => {
559
+ const [number, unit] = filesize2(size).toString().split(" ");
560
+ return style.attr(number) + style.attr.dim(unit);
561
+ };
562
+
563
+ // src/plugins/function/index.ts
564
+ var FunctionSchema = z9.union([
565
+ LocalFileSchema,
566
+ z9.object({
567
+ file: LocalFileSchema,
568
+ timeout: DurationSchema.optional(),
569
+ runtime: RuntimeSchema.optional(),
570
+ memorySize: SizeSchema.optional(),
571
+ architecture: ArchitectureSchema.optional(),
572
+ ephemeralStorageSize: SizeSchema.optional(),
573
+ retryAttempts: RetryAttempts,
574
+ environment: z9.record(z9.string(), z9.string()).optional()
575
+ })
576
+ ]);
577
+ var schema = z9.object({
578
+ defaults: z9.object({
579
+ function: z9.object({
580
+ timeout: DurationSchema.default("10 seconds"),
581
+ runtime: RuntimeSchema.default("nodejs18.x"),
582
+ memorySize: SizeSchema.default("128 MB"),
583
+ architecture: ArchitectureSchema.default("arm_64"),
584
+ ephemeralStorageSize: SizeSchema.default("512 MB"),
585
+ retryAttempts: RetryAttempts.default(2),
586
+ environment: z9.record(z9.string(), z9.string()).optional()
587
+ }).default({})
588
+ }).default({}),
589
+ stacks: z9.object({
590
+ functions: z9.record(
591
+ ResourceIdSchema,
592
+ FunctionSchema
593
+ ).optional()
594
+ }).array()
595
+ });
596
+ var functionPlugin = definePlugin({
597
+ name: "function",
598
+ schema,
599
+ onStack(context) {
600
+ return Object.entries(context.stackConfig.functions || {}).map(([id, fileOrProps]) => {
601
+ return toFunction(context, id, fileOrProps);
602
+ });
603
+ }
604
+ });
605
+ var toFunction = ({ config: config2, stack, stackConfig, assets }, id, fileOrProps) => {
606
+ const props = typeof fileOrProps === "string" ? { ...config2.defaults?.function, file: fileOrProps } : { ...config2.defaults?.function, ...fileOrProps };
607
+ const lambda = new Function(stack, toId("function", id), {
608
+ functionName: toName(stack, id),
609
+ handler: "index.default",
610
+ code: Code.fromInline("export default () => {}"),
611
+ ...props,
612
+ memorySize: props.memorySize.toMebibytes()
613
+ });
614
+ lambda.addEnvironment("APP", config2.name, { removeInEdge: true });
615
+ lambda.addEnvironment("STAGE", config2.stage, { removeInEdge: true });
616
+ lambda.addEnvironment("STACK", stackConfig.name, { removeInEdge: true });
617
+ if (lambda.runtime.toString().startsWith("nodejs")) {
618
+ lambda.addEnvironment("AWS_NODEJS_CONNECTION_REUSE_ENABLED", "1", {
619
+ removeInEdge: true
620
+ });
621
+ }
622
+ assets.add({
623
+ stack: stackConfig,
624
+ resource: "function",
625
+ resourceName: id,
626
+ async build() {
627
+ const result = await defaultBuild(props.file);
628
+ const bundle = await writeBuildFiles(config2, stack, id, result.files);
629
+ await writeBuildHash(config2, stack, id, result.hash);
630
+ const func = lambda.node.defaultChild;
631
+ func.handler = result.handler;
632
+ return {
633
+ size: formatByteSize(bundle.size)
634
+ };
635
+ },
636
+ async publish() {
637
+ const version2 = await publishFunctionAsset(config2, stack, id);
638
+ const func = lambda.node.defaultChild;
639
+ func.code = {
640
+ s3Bucket: assetBucketName(config2),
641
+ s3Key: `${config2.name}/${stack.artifactId}/function/${id}.zip`,
642
+ s3ObjectVersion: version2
643
+ };
644
+ }
645
+ });
646
+ return lambda;
647
+ };
648
+
649
+ // src/plugins/cron/index.ts
650
+ import { LambdaFunction } from "aws-cdk-lib/aws-events-targets";
651
+ var cronPlugin = definePlugin({
652
+ name: "cron",
653
+ schema: z10.object({
654
+ stacks: z10.object({
655
+ crons: z10.record(ResourceIdSchema, z10.object({
656
+ consumer: FunctionSchema,
657
+ schedule: ScheduleExpressionSchema,
658
+ description: z10.string().max(512).optional()
659
+ })).optional()
660
+ }).array()
661
+ }),
662
+ onStack(context) {
663
+ return Object.entries(context.stackConfig.crons || {}).map(([id, props]) => {
664
+ const lambda = toFunction(context, id, props.consumer);
665
+ const target = new LambdaFunction(lambda);
666
+ new Rule(context.stack, toId("cron", id), {
667
+ ruleName: toName(context.stack, id),
668
+ schedule: props.schedule,
669
+ description: props.description,
670
+ targets: [target]
671
+ });
672
+ return lambda;
673
+ });
674
+ }
675
+ });
676
+
677
+ // src/plugins/queue.ts
678
+ import { z as z11 } from "zod";
679
+ import { Queue } from "aws-cdk-lib/aws-sqs";
680
+ import { SqsEventSource } from "aws-cdk-lib/aws-lambda-event-sources";
681
+ var queuePlugin = definePlugin({
682
+ name: "queue",
683
+ schema: z11.object({
684
+ defaults: z11.object({
685
+ queue: z11.object({
686
+ // fifo: z.boolean().default(false),
687
+ retentionPeriod: DurationSchema.default("7 days"),
688
+ visibilityTimeout: DurationSchema.default("30 seconds"),
689
+ deliveryDelay: DurationSchema.default("0 seconds"),
690
+ receiveMessageWaitTime: DurationSchema.default("0 seconds"),
691
+ maxMessageSize: SizeSchema.default("256 KB")
692
+ }).default({})
693
+ }).default({}),
694
+ stacks: z11.object({
695
+ queues: z11.record(ResourceIdSchema, z11.union([
696
+ LocalFileSchema,
697
+ z11.object({
698
+ consumer: FunctionSchema,
699
+ // fifo: z.boolean().optional(),
700
+ retentionPeriod: DurationSchema.optional(),
701
+ visibilityTimeout: DurationSchema.optional(),
702
+ deliveryDelay: DurationSchema.optional(),
703
+ receiveMessageWaitTime: DurationSchema.optional(),
704
+ maxMessageSize: SizeSchema.optional()
705
+ })
706
+ ])).optional()
707
+ }).array()
708
+ }),
709
+ onStack(ctx) {
710
+ const { stack, config: config2, stackConfig, bind } = ctx;
711
+ return Object.entries(stackConfig.queues || {}).map(([id, functionOrProps]) => {
712
+ const props = typeof functionOrProps === "string" ? { ...config2.defaults.queue, consumer: functionOrProps } : { ...config2.defaults.queue, ...functionOrProps };
713
+ const queue2 = new Queue(stack, toId("queue", id), {
714
+ queueName: toName(stack, id),
715
+ ...props,
716
+ maxMessageSizeBytes: props.maxMessageSize.toBytes()
717
+ });
718
+ const lambda = toFunction(ctx, id, props.consumer);
719
+ lambda.addEventSource(new SqsEventSource(queue2));
720
+ bind((lambda2) => {
721
+ queue2.grantSendMessages(lambda2);
722
+ addResourceEnvironment(stack, "queue", id, lambda2);
723
+ });
724
+ return lambda;
725
+ });
726
+ }
727
+ });
728
+
729
+ // src/plugins/table/index.ts
730
+ import { z as z16 } from "zod";
731
+ import { BillingMode, Table } from "aws-cdk-lib/aws-dynamodb";
732
+
733
+ // src/plugins/table/schema/class-type.ts
734
+ import { TableClass } from "aws-cdk-lib/aws-dynamodb";
735
+ import { z as z12 } from "zod";
736
+ var types = {
737
+ "standard": TableClass.STANDARD,
738
+ "standard-infrequent-access": TableClass.STANDARD_INFREQUENT_ACCESS
739
+ };
740
+ var TableClassSchema = z12.enum(Object.keys(types)).transform((value) => {
741
+ return types[value];
742
+ });
743
+
744
+ // src/plugins/table/schema/attribute.ts
745
+ import { AttributeType } from "aws-cdk-lib/aws-dynamodb";
746
+ import { z as z13 } from "zod";
747
+ var types2 = {
748
+ string: AttributeType.STRING,
749
+ number: AttributeType.NUMBER,
750
+ binary: AttributeType.BINARY
751
+ };
752
+ var AttributeSchema = z13.enum(Object.keys(types2)).transform((value) => types2[value]);
753
+
754
+ // src/plugins/table/schema/key.ts
755
+ import { z as z14 } from "zod";
756
+ var KeySchema = z14.string().min(1).max(255);
757
+
758
+ // src/plugins/table/schema/projection-type.ts
759
+ import { ProjectionType } from "aws-cdk-lib/aws-dynamodb";
760
+ import { z as z15 } from "zod";
761
+ var types3 = {
762
+ "all": ProjectionType.ALL,
763
+ "keys-only": ProjectionType.KEYS_ONLY
764
+ };
765
+ var ProjectionTypeSchema = z15.union([
766
+ z15.enum(Object.keys(types3)).transform((value) => ({
767
+ ProjectionType: types3[value]
768
+ })),
769
+ z15.array(KeySchema).min(0).max(20).transform((keys) => ({
770
+ ProjectionType: ProjectionType.INCLUDE,
771
+ NonKeyAttributes: keys
772
+ }))
773
+ ]);
774
+
775
+ // src/plugins/table/index.ts
776
+ var tablePlugin = definePlugin({
777
+ name: "table",
778
+ schema: z16.object({
779
+ stacks: z16.object({
780
+ tables: z16.record(
781
+ ResourceIdSchema,
782
+ z16.object({
783
+ hash: KeySchema,
784
+ sort: KeySchema.optional(),
785
+ fields: z16.record(z16.string(), AttributeSchema),
786
+ class: TableClassSchema.default("standard"),
787
+ pointInTimeRecovery: z16.boolean().default(false),
788
+ timeToLiveAttribute: z16.string().optional(),
789
+ indexes: z16.record(z16.string(), z16.object({
790
+ hash: KeySchema,
791
+ sort: KeySchema.optional(),
792
+ projection: ProjectionTypeSchema.default("all")
793
+ })).optional()
794
+ }).refine((props) => {
795
+ return (
796
+ // Check the hash key
797
+ props.fields.hasOwnProperty(props.hash) && // Check the sort key
798
+ (!props.sort || props.fields.hasOwnProperty(props.sort)) && // Check all indexes
799
+ !Object.values(props.indexes || {}).map((index) => (
800
+ // Check the index hash key
801
+ props.fields.hasOwnProperty(index.hash) && // Check the index sort key
802
+ (!index.sort || props.fields.hasOwnProperty(index.sort))
803
+ )).includes(false)
804
+ );
805
+ }, "Hash & Sort keys must be defined inside the table fields")
806
+ ).optional()
807
+ }).array()
808
+ }),
809
+ onStack({ stack, stackConfig, bind }) {
810
+ Object.entries(stackConfig.tables || {}).map(([id, props]) => {
811
+ const buildKey = (attr) => {
812
+ return { name: attr, type: props.fields[attr] };
813
+ };
814
+ const table = new Table(stack, toId("table", id), {
815
+ tableName: toName(stack, id),
816
+ partitionKey: buildKey(props.hash),
817
+ sortKey: props.sort ? buildKey(props.sort) : void 0,
818
+ billingMode: BillingMode.PAY_PER_REQUEST,
819
+ pointInTimeRecovery: props.pointInTimeRecovery,
820
+ timeToLiveAttribute: props.timeToLiveAttribute,
821
+ tableClass: props.class
822
+ });
823
+ Object.entries(props.indexes || {}).forEach(([indexName, entry]) => {
824
+ table.addGlobalSecondaryIndex({
825
+ indexName,
826
+ partitionKey: buildKey(entry.hash),
827
+ sortKey: entry.sort ? buildKey(entry.sort) : void 0,
828
+ ...entry.projection
829
+ });
830
+ });
831
+ bind((lambda) => {
832
+ table.grantReadWriteData(lambda);
833
+ addResourceEnvironment(stack, "table", id, lambda);
834
+ });
835
+ });
836
+ }
837
+ });
838
+
839
+ // src/plugins/store.ts
840
+ import { z as z17 } from "zod";
841
+ import { Bucket as Bucket2, BucketAccessControl as BucketAccessControl2 } from "aws-cdk-lib/aws-s3";
842
+ import { RemovalPolicy as RemovalPolicy2 } from "aws-cdk-lib";
843
+ var storePlugin = definePlugin({
844
+ name: "store",
845
+ schema: z17.object({
846
+ stacks: z17.object({
847
+ stores: z17.array(ResourceIdSchema).optional()
848
+ }).array()
849
+ }),
850
+ onStack({ stack, stackConfig, bind }) {
851
+ (stackConfig.stores || []).forEach((id) => {
852
+ const bucket = new Bucket2(stack, toId("store", id), {
853
+ bucketName: toName(stack, id),
854
+ accessControl: BucketAccessControl2.PRIVATE,
855
+ removalPolicy: RemovalPolicy2.DESTROY
856
+ });
857
+ bind((lambda) => {
858
+ bucket.grantReadWrite(lambda), addResourceEnvironment(stack, "store", id, lambda);
859
+ });
860
+ });
861
+ }
862
+ });
863
+
864
+ // src/plugins/topic.ts
865
+ import { z as z18 } from "zod";
866
+ import { Topic } from "aws-cdk-lib/aws-sns";
867
+ import { SnsEventSource } from "aws-cdk-lib/aws-lambda-event-sources";
868
+ import { Arn as Arn2, ArnFormat } from "aws-cdk-lib";
869
+ import { PolicyStatement as PolicyStatement2 } from "aws-cdk-lib/aws-iam";
870
+ var topicPlugin = definePlugin({
871
+ name: "topic",
872
+ schema: z18.object({
873
+ stacks: z18.object({
874
+ topics: z18.record(ResourceIdSchema, FunctionSchema).optional()
875
+ }).array()
876
+ }),
877
+ onBootstrap({ config: config2, stack }) {
878
+ const allTopicNames = config2.stacks.map((stack2) => {
879
+ return Object.keys(stack2.topics || {});
880
+ }).flat();
881
+ const uniqueTopicNames = [...new Set(allTopicNames)];
882
+ uniqueTopicNames.forEach((id) => {
883
+ new Topic(stack, toId("topic", id), {
884
+ topicName: `${config2.name}-${id}`,
885
+ displayName: id
886
+ });
887
+ });
888
+ },
889
+ onStack(ctx) {
890
+ const { config: config2, stack, stackConfig, bind } = ctx;
891
+ bind((lambda) => {
892
+ lambda.addToRolePolicy(new PolicyStatement2({
893
+ actions: ["sns:publish"],
894
+ resources: ["*"]
895
+ }));
896
+ });
897
+ return Object.entries(stackConfig.topics || {}).map(([id, props]) => {
898
+ const lambda = toFunction(ctx, id, props);
899
+ const topic = Topic.fromTopicArn(
900
+ stack,
901
+ toId("topic", id),
902
+ Arn2.format({
903
+ arnFormat: ArnFormat.NO_RESOURCE_NAME,
904
+ service: "sns",
905
+ resource: `${config2.name}-${id}`
906
+ }, stack)
907
+ );
908
+ lambda.addEventSource(new SnsEventSource(topic));
909
+ return lambda;
910
+ });
911
+ }
912
+ });
913
+
914
+ // src/plugins/search.ts
915
+ import { z as z19 } from "zod";
916
+ import { CfnCollection } from "aws-cdk-lib/aws-opensearchserverless";
917
+ import { PolicyStatement as PolicyStatement3 } from "aws-cdk-lib/aws-iam";
918
+ var searchPlugin = definePlugin({
919
+ name: "search",
920
+ schema: z19.object({
921
+ stacks: z19.object({
922
+ searchs: z19.array(ResourceIdSchema).optional()
923
+ }).array()
924
+ }),
925
+ onStack({ stack, stackConfig, bind }) {
926
+ (stackConfig.searchs || []).forEach((id) => {
927
+ const collection = new CfnCollection(stack, toId("search", id), {
928
+ name: toName(stack, id),
929
+ type: "SEARCH"
930
+ });
931
+ bind((lambda) => {
932
+ lambda.addToRolePolicy(new PolicyStatement3({
933
+ actions: ["aoss:APIAccessAll"],
934
+ resources: [collection.attrArn]
935
+ }));
936
+ });
937
+ });
938
+ }
939
+ });
940
+
941
+ // src/plugins/index.ts
942
+ var defaultPlugins = [
943
+ functionPlugin,
944
+ cronPlugin,
945
+ queuePlugin,
946
+ tablePlugin,
947
+ storePlugin,
948
+ topicPlugin,
949
+ searchPlugin
950
+ ];
951
+
952
+ // src/stack/app-bootstrap.ts
953
+ var appBootstrapStack = ({ config: config2, app, assets }) => {
954
+ const stack = new Stack3(app, "bootstrap", {
955
+ stackName: `${config2.name}-bootstrap`
956
+ });
957
+ const plugins = [
958
+ ...defaultPlugins,
959
+ ...config2.plugins || []
960
+ ];
961
+ debug("Run plugin onBootstrap listeners");
962
+ plugins.forEach((plugin) => plugin.onBootstrap?.({ config: config2, stack, app, assets }));
963
+ return stack;
964
+ };
965
+
966
+ // src/util/deployment.ts
967
+ var flattenDependencyTree = (stacks) => {
968
+ const list3 = [];
969
+ const walk = (stacks2) => {
970
+ stacks2.forEach((node) => {
971
+ list3.push(node);
972
+ walk(node.children);
973
+ });
974
+ };
975
+ walk(stacks);
976
+ return list3;
977
+ };
978
+ var createDependencyTree = (stacks, startingLevel) => {
979
+ const list3 = stacks.map(({ stack, config: config2 }) => ({
980
+ stack,
981
+ depends: config2?.depends?.map((dep) => dep.name) || []
982
+ }));
983
+ const findChildren = (list4, parents, level) => {
984
+ const children = [];
985
+ const rests = [];
986
+ for (const item of list4) {
987
+ const isChild = item.depends.filter((dep) => !parents.includes(dep)).length === 0;
988
+ if (isChild) {
989
+ children.push(item);
990
+ } else {
991
+ rests.push(item);
992
+ }
993
+ }
994
+ if (!rests.length) {
995
+ return children.map(({ stack }) => ({
996
+ stack,
997
+ level,
998
+ children: []
999
+ }));
1000
+ }
1001
+ return children.map(({ stack }) => {
1002
+ return {
1003
+ stack,
1004
+ level,
1005
+ children: findChildren(rests, [...parents, stack.artifactId], level + 1)
1006
+ };
1007
+ });
1008
+ };
1009
+ return findChildren(list3, [], startingLevel);
1010
+ };
1011
+ var createDeploymentLine = (stacks) => {
1012
+ const flat = flattenDependencyTree(stacks);
1013
+ const line = [];
1014
+ flat.forEach((node) => {
1015
+ const level = node.level;
1016
+ if (!line[level]) {
1017
+ line[level] = [];
1018
+ }
1019
+ line[level].push(node.stack);
1020
+ });
1021
+ return line;
1022
+ };
1023
+
1024
+ // src/util/assets.ts
1025
+ var Assets = class {
1026
+ assets = {};
1027
+ id = 0;
1028
+ add(opts) {
1029
+ if (!this.assets[opts.stack.name]) {
1030
+ this.assets[opts.stack.name] = [];
1031
+ }
1032
+ this.assets[opts.stack.name].push({
1033
+ ...opts,
1034
+ id: this.id++
1035
+ });
1036
+ }
1037
+ list() {
1038
+ return this.assets;
1039
+ }
1040
+ forEach(cb) {
1041
+ Object.values(this.assets).forEach((assets) => {
1042
+ cb(assets[0].stack, assets);
1043
+ });
1044
+ }
1045
+ map(cb) {
1046
+ return Object.values(this.assets).map((assets) => {
1047
+ return cb(assets[0].stack, assets);
1048
+ });
1049
+ }
1050
+ };
1051
+
1052
+ // src/app.ts
1053
+ var makeApp = (config2) => {
1054
+ return new App4({
1055
+ outdir: assemblyDir,
1056
+ defaultStackSynthesizer: new DefaultStackSynthesizer({
1057
+ fileAssetsBucketName: assetBucketName(config2),
1058
+ fileAssetPublishingRoleArn: "",
1059
+ generateBootstrapVersionRule: false
1060
+ })
1061
+ });
1062
+ };
1063
+ var getAllDepends = (filters) => {
1064
+ const list3 = [];
1065
+ const walk = (deps) => {
1066
+ deps.forEach((dep) => {
1067
+ !list3.includes(dep) && list3.push(dep);
1068
+ dep.depends && walk(dep.depends);
1069
+ });
1070
+ };
1071
+ walk(filters);
1072
+ return list3;
1073
+ };
1074
+ var toApp = async (config2, filters) => {
1075
+ const assets = new Assets();
1076
+ const app = makeApp(config2);
1077
+ const stacks = [];
1078
+ const plugins = [
1079
+ ...defaultPlugins,
1080
+ ...config2.plugins || []
1081
+ ];
1082
+ debug("Plugins detected:", plugins.map((plugin) => style.info(plugin.name)).join(", "));
1083
+ debug("Run plugin onApp listeners");
1084
+ plugins.forEach((plugin) => plugin.onApp?.({ config: config2, app, assets }));
1085
+ debug("Stack filters:", filters.map((filter) => style.info(filter)).join(", "));
1086
+ const filterdStacks = filters.length === 0 ? config2.stacks : getAllDepends(
1087
+ // config.stacks,
1088
+ config2.stacks.filter((stack) => filters.includes(stack.name))
1089
+ );
1090
+ for (const stackConfig of filterdStacks) {
1091
+ const { stack } = toStack({
1092
+ config: config2,
1093
+ stackConfig,
1094
+ assets,
1095
+ plugins,
1096
+ app
1097
+ });
1098
+ stacks.push({ stack, config: stackConfig });
1099
+ }
1100
+ let dependencyTree;
1101
+ const bootstrap2 = appBootstrapStack({ config: config2, app, assets });
1102
+ if (bootstrap2.node.children.length === 0) {
1103
+ dependencyTree = createDependencyTree(stacks, 0);
1104
+ } else {
1105
+ dependencyTree = [{
1106
+ stack: bootstrap2,
1107
+ level: 0,
1108
+ children: createDependencyTree(stacks, 1)
1109
+ }];
1110
+ }
1111
+ return {
1112
+ app,
1113
+ assets,
1114
+ plugins,
1115
+ stackNames: filterdStacks.map((stack) => stack.name),
1116
+ dependencyTree
1117
+ };
1118
+ };
1119
+
1120
+ // src/cli/ui/layout/basic.ts
1121
+ var br = () => {
1122
+ return "\n";
1123
+ };
1124
+ var hr = () => {
1125
+ return (term) => {
1126
+ term.out.write([
1127
+ style.placeholder("\u2500".repeat(term.out.width())),
1128
+ br()
1129
+ ]);
1130
+ };
1131
+ };
1132
+
1133
+ // src/cli/ui/layout/logs.ts
1134
+ var previous = /* @__PURE__ */ new Date();
1135
+ var logs = () => {
1136
+ if (!process.env.VERBOSE) {
1137
+ return [];
1138
+ }
1139
+ const logs2 = flushDebug();
1140
+ return [
1141
+ hr(),
1142
+ br(),
1143
+ " ".repeat(3),
1144
+ style.label("Debug Logs:"),
1145
+ br(),
1146
+ br(),
1147
+ logs2.map((log) => {
1148
+ const diff = log.date.getTime() - previous.getTime();
1149
+ const time = `+${diff}`.padStart(7);
1150
+ previous = log.date;
1151
+ return [
1152
+ style.attr(`${time}${style.attr.dim("ms")}`),
1153
+ " [ ",
1154
+ log.type,
1155
+ " ] ",
1156
+ log.message,
1157
+ br(),
1158
+ log.type === "error" ? br() : ""
1159
+ ];
1160
+ }),
1161
+ br(),
1162
+ hr()
1163
+ ];
1164
+ };
1165
+
1166
+ // src/cli/ui/layout/footer.ts
1167
+ var footer = () => {
1168
+ return [
1169
+ br(),
1170
+ logs()
1171
+ ];
1172
+ };
1173
+
1174
+ // src/config.ts
1175
+ import { join as join4 } from "path";
1176
+
1177
+ // src/util/account.ts
1178
+ import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
1179
+ var getAccountId = async (credentials, region) => {
1180
+ const client = new STSClient({ credentials, region });
1181
+ const result = await client.send(new GetCallerIdentityCommand({}));
1182
+ return result.Account;
1183
+ };
1184
+
1185
+ // src/util/credentials.ts
1186
+ import { fromIni } from "@aws-sdk/credential-providers";
1187
+ var getCredentials = (profile) => {
1188
+ return fromIni({
1189
+ profile
1190
+ });
1191
+ };
1192
+
1193
+ // src/config.ts
1194
+ import { load } from "ts-import";
1195
+
1196
+ // src/schema/app.ts
1197
+ import { z as z23 } from "zod";
1198
+
1199
+ // src/schema/stack.ts
1200
+ import { z as z20 } from "zod";
1201
+ var StackSchema = z20.object({
1202
+ name: ResourceIdSchema,
1203
+ depends: z20.array(z20.lazy(() => StackSchema)).optional()
1204
+ });
1205
+
1206
+ // src/schema/region.ts
1207
+ import { z as z21 } from "zod";
1208
+ var US = ["us-east-2", "us-east-1", "us-west-1", "us-west-2"];
1209
+ var AF = ["af-south-1"];
1210
+ var AP = ["ap-east-1", "ap-south-2", "ap-southeast-3", "ap-southeast-4", "ap-south-1", "ap-northeast-3", "ap-northeast-2", "ap-southeast-1", "ap-southeast-2", "ap-northeast-1"];
1211
+ var CA = ["ca-central-1"];
1212
+ var EU = ["eu-central-1", "eu-west-1", "eu-west-2", "eu-south-1", "eu-west-3", "eu-south-2", "eu-north-1", "eu-central-2"];
1213
+ var ME = ["me-south-1", "me-central-1"];
1214
+ var SA = ["sa-east-1"];
1215
+ var regions = [
1216
+ ...US,
1217
+ ...AF,
1218
+ ...AP,
1219
+ ...CA,
1220
+ ...EU,
1221
+ ...ME,
1222
+ ...SA
1223
+ ];
1224
+ var RegionSchema = z21.enum(regions);
1225
+
1226
+ // src/schema/plugin.ts
1227
+ import { z as z22 } from "zod";
1228
+ var PluginSchema = z22.object({
1229
+ name: z22.string(),
1230
+ schema: z22.custom().optional(),
1231
+ depends: z22.array(z22.lazy(() => PluginSchema)).optional(),
1232
+ onBootstrap: z22.function().optional(),
1233
+ onStack: z22.function().returns(z22.any()).optional(),
1234
+ onApp: z22.function().optional()
1235
+ // bind: z.function().optional(),
1236
+ });
1237
+
1238
+ // src/schema/app.ts
1239
+ var AppSchema = z23.object({
1240
+ name: ResourceIdSchema,
1241
+ region: RegionSchema,
1242
+ profile: z23.string(),
1243
+ stage: z23.string().regex(/[a-z]+/).default("prod"),
1244
+ defaults: z23.object({}).default({}),
1245
+ stacks: z23.array(StackSchema).min(1),
1246
+ plugins: z23.array(PluginSchema).optional()
1247
+ });
1248
+
1249
+ // src/config.ts
1250
+ var importConfig = async (options) => {
1251
+ debug("Import config file");
1252
+ const fileName = join4(process.cwd(), options.configFile || "awsless.config.ts");
1253
+ const module = await load(fileName, {
1254
+ transpileOptions: {
1255
+ cache: {
1256
+ dir: join4(outDir, "config")
1257
+ }
1258
+ }
1259
+ });
1260
+ const appConfig = typeof module.default === "function" ? await module.default({
1261
+ profile: options.profile,
1262
+ region: options.region,
1263
+ stage: options.stage
1264
+ }) : module.default;
1265
+ debug("Validate config file");
1266
+ const plugins = [
1267
+ ...defaultPlugins,
1268
+ ...appConfig.plugins || []
1269
+ ];
1270
+ let schema2 = AppSchema;
1271
+ for (const plugin of plugins) {
1272
+ if (plugin.schema) {
1273
+ schema2 = schema2.and(plugin.schema);
1274
+ }
1275
+ }
1276
+ const config2 = await schema2.parseAsync(appConfig);
1277
+ debug("Final config:", config2.stacks);
1278
+ debug("Load credentials", style.info(config2.profile));
1279
+ const credentials = getCredentials(config2.profile);
1280
+ debug("Load AWS account ID");
1281
+ const account = await getAccountId(credentials, config2.region);
1282
+ debug("Account ID:", style.info(account));
1283
+ return {
1284
+ ...config2,
1285
+ account,
1286
+ credentials
1287
+ };
1288
+ };
1289
+
1290
+ // src/cli/ui/layout/list.ts
1291
+ var list = (data) => {
1292
+ const padding = 3;
1293
+ const gap = 1;
1294
+ const size = Object.keys(data).reduce((total, name) => {
1295
+ return name.length > total ? name.length : total;
1296
+ }, 0);
1297
+ return Object.entries(data).map(([name, value]) => [
1298
+ " ".repeat(padding),
1299
+ style.label((name + ":").padEnd(size + gap + 1)),
1300
+ value,
1301
+ br()
1302
+ ]);
1303
+ };
1304
+
1305
+ // src/cli/ui/layout/header.ts
1306
+ var header = (config2) => {
1307
+ return [
1308
+ br(),
1309
+ list({
1310
+ App: config2.name,
1311
+ Stage: config2.stage,
1312
+ Region: config2.region,
1313
+ Profile: config2.profile
1314
+ }),
1315
+ br()
1316
+ ];
1317
+ };
1318
+
1319
+ // src/cli/lib/signal.ts
1320
+ var Signal = class {
1321
+ constructor(value) {
1322
+ this.value = value;
1323
+ }
1324
+ subs = /* @__PURE__ */ new Set();
1325
+ get() {
1326
+ return this.value;
1327
+ }
1328
+ set(value) {
1329
+ this.value = value;
1330
+ this.subs.forEach((sub) => sub(value));
1331
+ }
1332
+ update(cb) {
1333
+ this.set(cb(this.value));
1334
+ }
1335
+ subscribe(cb) {
1336
+ this.subs.add(cb);
1337
+ return () => {
1338
+ this.subs.delete(cb);
1339
+ };
1340
+ }
1341
+ };
1342
+ var derive = (deps, factory) => {
1343
+ const values = deps.map((dep) => dep.get());
1344
+ const signal = new Signal(factory(...values));
1345
+ deps.forEach((dep) => {
1346
+ dep.subscribe(() => {
1347
+ const values2 = deps.map((dep2) => dep2.get());
1348
+ signal.set(factory(...values2));
1349
+ });
1350
+ });
1351
+ return signal;
1352
+ };
1353
+
1354
+ // src/cli/ui/layout/spinner.ts
1355
+ var frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
1356
+ var length = frames.length;
1357
+ var createSpinner = () => {
1358
+ const index = new Signal(0);
1359
+ const frame = derive([index], (index2) => style.info(frames[index2 % length]));
1360
+ const interval = setInterval(() => {
1361
+ index.update((i) => i + 1);
1362
+ }, 80);
1363
+ return [
1364
+ frame,
1365
+ () => {
1366
+ clearInterval(interval);
1367
+ }
1368
+ ];
1369
+ };
1370
+
1371
+ // src/cli/ui/layout/dialog.ts
1372
+ var dialog = (type, lines) => {
1373
+ const padding = 3;
1374
+ const icon = style[type](symbol[type].padEnd(padding));
1375
+ return lines.map((line, i) => {
1376
+ if (i === 0) {
1377
+ return icon + line;
1378
+ } else {
1379
+ return " ".repeat(padding) + line;
1380
+ }
1381
+ }).join(br()) + br();
1382
+ };
1383
+ var loadingDialog = (message) => {
1384
+ const [icon, stop] = createSpinner();
1385
+ const description = new Signal(message);
1386
+ const time = new Signal("");
1387
+ const start = /* @__PURE__ */ new Date();
1388
+ return (term) => {
1389
+ term.out.write([
1390
+ icon,
1391
+ " ",
1392
+ description,
1393
+ " ",
1394
+ time,
1395
+ br()
1396
+ ]);
1397
+ return (message2) => {
1398
+ const end = /* @__PURE__ */ new Date();
1399
+ const diff = end.getTime() - start.getTime();
1400
+ description.set(message2);
1401
+ time.set(style.attr(diff) + style.attr.dim("ms"));
1402
+ stop();
1403
+ icon.set(style.success(symbol.success));
1404
+ };
1405
+ };
1406
+ };
1407
+
1408
+ // src/cli/lib/interface.ts
1409
+ import { createInterface, emitKeypressEvents } from "readline";
1410
+ import { exec } from "child_process";
1411
+ var parseAction = (key) => {
1412
+ if (key.meta && key.name !== "escape") {
1413
+ return;
1414
+ }
1415
+ if (key.ctrl) {
1416
+ if (key.name === "a")
1417
+ return "first";
1418
+ if (key.name === "c")
1419
+ return "abort";
1420
+ if (key.name === "d")
1421
+ return "abort";
1422
+ if (key.name === "e")
1423
+ return "last";
1424
+ if (key.name === "g")
1425
+ return "reset";
1426
+ }
1427
+ if (key.name === "return")
1428
+ return "submit";
1429
+ if (key.name === "enter")
1430
+ return "submit";
1431
+ if (key.name === "backspace")
1432
+ return "delete";
1433
+ if (key.name === "delete")
1434
+ return "deleteForward";
1435
+ if (key.name === "abort")
1436
+ return "abort";
1437
+ if (key.name === "escape")
1438
+ return "exit";
1439
+ if (key.name === "tab" && key.shift)
1440
+ return "previous";
1441
+ if (key.name === "tab")
1442
+ return "next";
1443
+ if (key.name === "up")
1444
+ return "up";
1445
+ if (key.name === "down")
1446
+ return "down";
1447
+ if (key.name === "right")
1448
+ return "right";
1449
+ if (key.name === "left")
1450
+ return "left";
1451
+ return "input";
1452
+ };
1453
+ var Interface = class {
1454
+ constructor(input) {
1455
+ this.input = input;
1456
+ this.readline = createInterface({ input: this.input, escapeCodeTimeout: 50 });
1457
+ emitKeypressEvents(this.input, this.readline);
1458
+ this.hideCursor();
1459
+ if (this.input.isTTY) {
1460
+ this.input.setRawMode(true);
1461
+ }
1462
+ }
1463
+ // private subscriber: Actions | undefined
1464
+ readline;
1465
+ unref() {
1466
+ this.showCursor();
1467
+ this.input.unref();
1468
+ }
1469
+ captureInput(actions) {
1470
+ debug("Subscribe to user input...");
1471
+ const keypress = (value, key) => {
1472
+ const action = parseAction(key);
1473
+ if (typeof action === "undefined") {
1474
+ this.bell();
1475
+ } else if (action === "abort") {
1476
+ this.showCursor();
1477
+ process.exit(1);
1478
+ } else {
1479
+ const cb = actions[action];
1480
+ if (typeof cb === "function") {
1481
+ cb(value, key);
1482
+ } else {
1483
+ this.bell();
1484
+ }
1485
+ }
1486
+ };
1487
+ this.input.on("keypress", keypress);
1488
+ return () => {
1489
+ this.input.off("keypress", keypress);
1490
+ debug("Unsubscribe to user input");
1491
+ };
1492
+ }
1493
+ hideCursor() {
1494
+ if (this.input.isTTY) {
1495
+ this.input.write("\x1B[?25l");
1496
+ }
1497
+ }
1498
+ showCursor() {
1499
+ if (this.input.isTTY) {
1500
+ this.input.write("\x1B[?25h");
1501
+ }
1502
+ }
1503
+ bell() {
1504
+ if (this.input.isTTY) {
1505
+ exec("afplay /System/Library/Sounds/Tink.aiff");
1506
+ }
1507
+ }
1508
+ };
1509
+
1510
+ // src/cli/lib/renderer.ts
1511
+ var Renderer = class {
1512
+ constructor(output, ins) {
1513
+ this.output = output;
1514
+ this.ins = ins;
1515
+ }
1516
+ fragments = [];
1517
+ unsubs = [];
1518
+ timeout;
1519
+ screen = [];
1520
+ width() {
1521
+ return this.output.columns;
1522
+ }
1523
+ height() {
1524
+ return this.output.rows;
1525
+ }
1526
+ write(fragment) {
1527
+ if (Array.isArray(fragment)) {
1528
+ fragment.forEach((i) => this.write(i));
1529
+ return;
1530
+ }
1531
+ if (typeof fragment === "function") {
1532
+ return fragment({ out: this, in: this.ins });
1533
+ }
1534
+ this.fragments.push(fragment);
1535
+ this.update();
1536
+ return fragment;
1537
+ }
1538
+ update() {
1539
+ clearTimeout(this.timeout);
1540
+ this.timeout = setTimeout(() => {
1541
+ this.flush();
1542
+ }, 0);
1543
+ }
1544
+ flush() {
1545
+ clearTimeout(this.timeout);
1546
+ const walk = (fragment) => {
1547
+ if (typeof fragment === "string") {
1548
+ return fragment;
1549
+ }
1550
+ if (Array.isArray(fragment)) {
1551
+ return fragment.map(walk).join("");
1552
+ }
1553
+ this.unsubs.push(fragment.subscribe(() => {
1554
+ this.update();
1555
+ }));
1556
+ return walk(fragment.get());
1557
+ };
1558
+ this.unsubs.forEach((unsub) => unsub());
1559
+ this.unsubs = [];
1560
+ const screen = walk(this.fragments).split("\n");
1561
+ const oldSize = this.screen.length;
1562
+ const newSize = screen.length;
1563
+ const size = Math.max(oldSize, newSize);
1564
+ const height = this.height();
1565
+ const start = Math.max(oldSize - height, 0);
1566
+ for (let y = start; y < size; y++) {
1567
+ const line = screen[y];
1568
+ if (line !== this.screen[y]) {
1569
+ if (y > oldSize) {
1570
+ const x = (this.screen[y - 1]?.length || 0) - 1;
1571
+ this.output.cursorTo?.(x, y - 1 - start);
1572
+ this.output.write?.("\n" + line);
1573
+ } else {
1574
+ this.output.cursorTo?.(0, y - start);
1575
+ this.output.write?.(line);
1576
+ }
1577
+ this.output.clearLine?.(1);
1578
+ }
1579
+ }
1580
+ this.screen = screen;
1581
+ }
1582
+ clear() {
1583
+ let count = this.output.rows;
1584
+ while (count--) {
1585
+ this.output.write("\n");
1586
+ }
1587
+ this.output.cursorTo?.(0, 0);
1588
+ this.output.clearScreenDown?.();
1589
+ }
1590
+ };
1591
+
1592
+ // src/cli/lib/terminal.ts
1593
+ var createTerminal = (input = process.stdin, output = process.stdout) => {
1594
+ const ins = new Interface(input);
1595
+ const outs = new Renderer(output, ins);
1596
+ return { in: ins, out: outs };
1597
+ };
1598
+
1599
+ // src/cli/ui/layout/logo.ts
1600
+ var logo = () => {
1601
+ return [
1602
+ style.warning("\u26A1\uFE0F "),
1603
+ style.primary("AWS"),
1604
+ style.primary.dim("LESS"),
1605
+ br()
1606
+ ];
1607
+ };
1608
+
1609
+ // src/cli/ui/layout/layout.ts
1610
+ var layout = async (cb) => {
1611
+ const term = createTerminal();
1612
+ term.out.clear();
1613
+ term.out.write(logo());
1614
+ try {
1615
+ const options = program.optsWithGlobals();
1616
+ const config2 = await importConfig(options);
1617
+ term.out.write(header(config2));
1618
+ await cb(config2, term.out.write.bind(term.out), term);
1619
+ } catch (error) {
1620
+ if (error instanceof Error) {
1621
+ term.out.write(dialog("error", [error.message]));
1622
+ } else if (typeof error === "string") {
1623
+ term.out.write(dialog("error", [error]));
1624
+ } else {
1625
+ term.out.write(dialog("error", [JSON.stringify(error)]));
1626
+ }
1627
+ debugError(error);
1628
+ } finally {
1629
+ debug("Exit");
1630
+ term.out.write(footer());
1631
+ term.in.unref();
1632
+ setTimeout(() => {
1633
+ process.exit(0);
1634
+ }, 50);
1635
+ }
1636
+ };
1637
+
1638
+ // src/cli/ui/layout/flex-line.ts
1639
+ var stripEscapeCode = (str) => {
1640
+ return str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "");
1641
+ };
1642
+ var flexLine = (term, left, right, reserveSpace = 0) => {
1643
+ const deps = [...left, ...right];
1644
+ const strings = deps.filter((dep) => typeof dep === "string");
1645
+ const signals = deps.filter((dep) => dep instanceof Signal);
1646
+ const stringSize = stripEscapeCode(strings.join("")).length;
1647
+ return new Signal([
1648
+ ...left,
1649
+ derive(signals, (...deps2) => {
1650
+ const signalSize = stripEscapeCode(deps2.join("")).length;
1651
+ const size = term.out.width() - signalSize - stringSize - reserveSpace;
1652
+ return style.placeholder("\u2500".repeat(size));
1653
+ }),
1654
+ ...right
1655
+ ]);
1656
+ };
1657
+
1658
+ // src/cli/ui/complex/asset.ts
1659
+ var assetBuilder = (assets) => {
1660
+ return async (term) => {
1661
+ const done = term.out.write(loadingDialog("Building stack assets..."));
1662
+ const groups = new Signal([br()]);
1663
+ term.out.write(groups);
1664
+ const stackNameSize = Math.max(...Object.keys(assets.list()).map((stack) => stack.length));
1665
+ await Promise.all(assets.map(async (stack, assets2) => {
1666
+ const group = new Signal([]);
1667
+ groups.update((groups2) => [...groups2, group]);
1668
+ await Promise.all(assets2.map(async (asset) => {
1669
+ const [icon, stop] = createSpinner();
1670
+ const start = /* @__PURE__ */ new Date();
1671
+ const details = new Signal({});
1672
+ const line = flexLine(term, [
1673
+ icon,
1674
+ " ",
1675
+ style.label(stack.name),
1676
+ " ".repeat(stackNameSize - stack.name.length),
1677
+ " ",
1678
+ style.placeholder(symbol.pointerSmall),
1679
+ " ",
1680
+ style.warning(asset.resource),
1681
+ " ",
1682
+ style.placeholder(symbol.pointerSmall),
1683
+ " ",
1684
+ style.info(asset.resourceName),
1685
+ " "
1686
+ ], [
1687
+ " ",
1688
+ derive([details], (details2) => {
1689
+ return Object.entries(details2).map(([key, value]) => {
1690
+ return `${style.label(key)}: ${value}`;
1691
+ }).join(" / ");
1692
+ }),
1693
+ br()
1694
+ ]);
1695
+ group.update((group2) => [...group2, line]);
1696
+ const data = await asset.build?.();
1697
+ const time = (/* @__PURE__ */ new Date()).getTime() - start.getTime();
1698
+ details.set({
1699
+ ...data,
1700
+ time: style.attr(time) + style.attr.dim("ms")
1701
+ });
1702
+ icon.set(style.success(symbol.success));
1703
+ stop();
1704
+ }));
1705
+ }));
1706
+ done("Done building stack assets");
1707
+ };
1708
+ };
1709
+
1710
+ // src/util/cleanup.ts
1711
+ import { mkdir as mkdir2, rm } from "fs/promises";
1712
+ var cleanUp = async () => {
1713
+ debug("Clean up assembly & asset files");
1714
+ const paths = [
1715
+ assemblyDir,
1716
+ functionDir
1717
+ ];
1718
+ await Promise.all(paths.map((path) => rm(path, {
1719
+ recursive: true,
1720
+ force: true,
1721
+ maxRetries: 2
1722
+ })));
1723
+ await Promise.all(paths.map((path) => mkdir2(path, {
1724
+ recursive: true
1725
+ })));
1726
+ };
1727
+
1728
+ // src/cli/command/build.ts
1729
+ var build = (program2) => {
1730
+ program2.command("build").argument("[stack...]", "Optionally filter stacks to build").description("Build your app").action(async (filters) => {
1731
+ await layout(async (config2, write) => {
1732
+ const { app, assets } = await toApp(config2, filters);
1733
+ await cleanUp();
1734
+ await write(assetBuilder(assets));
1735
+ app.synth();
1736
+ });
1737
+ });
1738
+ };
1739
+
1740
+ // src/stack/client.ts
1741
+ import { CloudFormationClient, CreateStackCommand, DeleteStackCommand, DescribeStacksCommand, GetTemplateCommand, OnFailure, TemplateStage, UpdateStackCommand, ValidateTemplateCommand, waitUntilStackCreateComplete, waitUntilStackDeleteComplete, waitUntilStackUpdateComplete } from "@aws-sdk/client-cloudformation";
1742
+ import { S3Client as S3Client2, PutObjectCommand as PutObjectCommand2, ObjectCannedACL as ObjectCannedACL2, StorageClass as StorageClass2 } from "@aws-sdk/client-s3";
1743
+ var StackClient = class {
1744
+ // 30 seconds
1745
+ constructor(config2) {
1746
+ this.config = config2;
1747
+ this.client = new CloudFormationClient({
1748
+ credentials: config2.credentials,
1749
+ region: config2.region
1750
+ });
1751
+ }
1752
+ client;
1753
+ maxWaitTime = 60 * 30;
1754
+ // 30 minutes
1755
+ maxDelay = 30;
1756
+ shouldUploadTemplate(stack) {
1757
+ const body = JSON.stringify(stack.template);
1758
+ const size = Buffer.byteLength(body, "utf8");
1759
+ return size > 5e4;
1760
+ }
1761
+ templateProp(stack) {
1762
+ return this.shouldUploadTemplate(stack) ? {
1763
+ TemplateUrl: assetBucketUrl(this.config, stack.stackName)
1764
+ } : {
1765
+ TemplateBody: JSON.stringify(stack.template)
1766
+ };
1767
+ }
1768
+ async upload(stack) {
1769
+ debug("Upload the", style.info(stack.id), "stack to awsless assets bucket");
1770
+ const client = new S3Client2({
1771
+ credentials: this.config.credentials,
1772
+ region: this.config.region
1773
+ });
1774
+ await client.send(new PutObjectCommand2({
1775
+ Bucket: assetBucketName(this.config),
1776
+ Key: `${stack.stackName}/cloudformation.json`,
1777
+ Body: JSON.stringify(stack.template),
1778
+ ACL: ObjectCannedACL2.private,
1779
+ StorageClass: StorageClass2.STANDARD_IA
1780
+ }));
1781
+ }
1782
+ async create(stack, capabilities) {
1783
+ debug("Create the", style.info(stack.id), "stack");
1784
+ await this.client.send(new CreateStackCommand({
1785
+ StackName: stack.stackName,
1786
+ EnableTerminationProtection: false,
1787
+ OnFailure: OnFailure.DELETE,
1788
+ Capabilities: capabilities,
1789
+ ...this.templateProp(stack)
1790
+ }));
1791
+ await waitUntilStackCreateComplete({
1792
+ client: this.client,
1793
+ maxWaitTime: this.maxWaitTime,
1794
+ maxDelay: this.maxDelay
1795
+ }, {
1796
+ StackName: stack.stackName
1797
+ });
1798
+ }
1799
+ async update(stack, capabilities) {
1800
+ debug("Update the", style.info(stack.id), "stack");
1801
+ await this.client.send(new UpdateStackCommand({
1802
+ StackName: stack.stackName,
1803
+ Capabilities: capabilities,
1804
+ ...this.templateProp(stack)
1805
+ }));
1806
+ await waitUntilStackUpdateComplete({
1807
+ client: this.client,
1808
+ maxWaitTime: this.maxWaitTime,
1809
+ maxDelay: this.maxDelay
1810
+ }, {
1811
+ StackName: stack.stackName
1812
+ });
1813
+ }
1814
+ async validate(stack) {
1815
+ debug("Validate the", style.info(stack.id), "stack");
1816
+ const result = await this.client.send(new ValidateTemplateCommand({
1817
+ ...this.templateProp(stack)
1818
+ }));
1819
+ return result.Capabilities;
1820
+ }
1821
+ async get(name) {
1822
+ debug("Get stack info for:", style.info(name));
1823
+ let result;
1824
+ try {
1825
+ result = await this.client.send(new DescribeStacksCommand({
1826
+ StackName: name
1827
+ }));
1828
+ } catch (error) {
1829
+ if (error instanceof Error && error.name === "ValidationError" && error.message.includes("does not exist")) {
1830
+ return;
1831
+ }
1832
+ throw error;
1833
+ }
1834
+ const stack = result.Stacks?.[0];
1835
+ if (!stack) {
1836
+ debug("Stack not found");
1837
+ return;
1838
+ }
1839
+ const resultTemplate = await this.client.send(new GetTemplateCommand({
1840
+ StackName: name,
1841
+ TemplateStage: TemplateStage.Original
1842
+ }));
1843
+ const outputs = {};
1844
+ stack.Outputs?.forEach((output) => {
1845
+ outputs[output.OutputKey] = output.OutputValue;
1846
+ });
1847
+ debug("Status for: ", style.info(name), "is", stack.StackStatus);
1848
+ return {
1849
+ status: stack.StackStatus,
1850
+ reason: stack.StackStatusReason,
1851
+ outputs,
1852
+ template: resultTemplate.TemplateBody,
1853
+ updatedAt: stack.LastUpdatedTime || stack.CreationTime,
1854
+ createdAt: stack.CreationTime
1855
+ };
1856
+ }
1857
+ async deploy(stack) {
1858
+ const data = await this.get(stack.stackName);
1859
+ debug("Deploy:", style.info(stack.stackName));
1860
+ if (data?.template === JSON.stringify(stack.template)) {
1861
+ debug("No stack changes");
1862
+ return false;
1863
+ }
1864
+ if (this.shouldUploadTemplate(stack)) {
1865
+ await this.upload(stack);
1866
+ }
1867
+ const capabilities = await this.validate(stack);
1868
+ if (!data) {
1869
+ await this.create(stack, capabilities);
1870
+ } else if (data.status.includes("IN_PROGRESS")) {
1871
+ throw new Error(`Stack is in progress: ${data.status}`);
1872
+ } else {
1873
+ await this.update(stack, capabilities);
1874
+ }
1875
+ return true;
1876
+ }
1877
+ async delete(name) {
1878
+ const data = await this.get(name);
1879
+ debug("Delete the", style.info(name), "stack");
1880
+ if (!data) {
1881
+ debug("Already deleted");
1882
+ return;
1883
+ }
1884
+ await this.client.send(new DeleteStackCommand({
1885
+ StackName: name
1886
+ }));
1887
+ await waitUntilStackDeleteComplete({
1888
+ client: this.client,
1889
+ maxWaitTime: this.maxWaitTime,
1890
+ maxDelay: this.maxDelay
1891
+ }, {
1892
+ StackName: name
1893
+ });
1894
+ }
1895
+ };
1896
+
1897
+ // src/cli/error.ts
1898
+ var Cancelled = class extends Error {
1899
+ constructor() {
1900
+ super("Cancelled");
1901
+ }
1902
+ };
1903
+
1904
+ // src/cli/ui/prompt/toggle.ts
1905
+ var togglePrompt = (label, options = {}) => {
1906
+ return (term) => new Promise((resolve) => {
1907
+ const { initial = false, active = "on", inactive = "off" } = options;
1908
+ const icon = new Signal(style.info(symbol.question));
1909
+ const sep = new Signal(style.placeholder(symbol.pointerSmall));
1910
+ const mid = style.placeholder("/");
1911
+ const activeText = new Signal(active);
1912
+ const inactiveText = new Signal(inactive);
1913
+ let value = initial;
1914
+ const activate = () => {
1915
+ activeText.set(style.success.underline(active));
1916
+ inactiveText.set(style.normal(inactive));
1917
+ value = true;
1918
+ };
1919
+ const deactivate = () => {
1920
+ activeText.set(style.normal(active));
1921
+ inactiveText.set(style.success.underline(inactive));
1922
+ value = false;
1923
+ };
1924
+ const toggle = () => {
1925
+ !value ? activate() : deactivate();
1926
+ };
1927
+ const reset = () => {
1928
+ initial ? activate() : deactivate();
1929
+ };
1930
+ reset();
1931
+ const release = term.in.captureInput({
1932
+ reset,
1933
+ exit() {
1934
+ release();
1935
+ icon.set(style.error(symbol.error));
1936
+ sep.set(symbol.ellipsis);
1937
+ resolve(false);
1938
+ },
1939
+ submit() {
1940
+ release();
1941
+ icon.set(style.success(symbol.success));
1942
+ sep.set(symbol.ellipsis);
1943
+ resolve(value);
1944
+ },
1945
+ input(chr) {
1946
+ switch (chr) {
1947
+ case " ":
1948
+ toggle();
1949
+ break;
1950
+ case "1":
1951
+ activate();
1952
+ break;
1953
+ case "0":
1954
+ deactivate();
1955
+ break;
1956
+ }
1957
+ },
1958
+ delete: deactivate,
1959
+ left: deactivate,
1960
+ right: activate,
1961
+ down: deactivate,
1962
+ up: activate
1963
+ });
1964
+ term.out.write([icon, " ", style.label(label), " ", sep, " ", inactiveText, " ", mid, " ", activeText, br()]);
1965
+ });
1966
+ };
1967
+
1968
+ // src/cli/ui/prompt/confirm.ts
1969
+ var confirmPrompt = (label, options = {}) => {
1970
+ return togglePrompt(label, {
1971
+ ...options,
1972
+ inactive: "no",
1973
+ active: "yes"
1974
+ });
1975
+ };
1976
+
1977
+ // src/cli/ui/complex/bootstrap.ts
1978
+ var bootstrapDeployer = (config2) => {
1979
+ return async (term) => {
1980
+ debug("Initializing bootstrap");
1981
+ const app = makeApp(config2);
1982
+ const client = new StackClient(config2);
1983
+ const bootstrap2 = bootstrapStack(config2, app);
1984
+ const shouldDeploy = await shouldDeployBootstrap(client, bootstrap2.stackName);
1985
+ if (shouldDeploy) {
1986
+ term.out.write(dialog("warning", [`Your app hasn't been bootstrapped yet`]));
1987
+ const confirmed = await term.out.write(confirmPrompt("Would you like to bootstrap?"));
1988
+ if (!confirmed) {
1989
+ throw new Cancelled();
1990
+ }
1991
+ const done = term.out.write(loadingDialog("Bootstrapping..."));
1992
+ const assembly = app.synth();
1993
+ await client.deploy(assembly.stacks[0]);
1994
+ done("Done deploying the bootstrap stack");
1995
+ } else {
1996
+ term.out.write(dialog("success", [
1997
+ "App has already been bootstrapped"
1998
+ ]));
1999
+ }
2000
+ debug("Bootstrap initialized");
2001
+ };
2002
+ };
2003
+
2004
+ // src/cli/command/bootstrap.ts
2005
+ var bootstrap = (program2) => {
2006
+ program2.command("bootstrap").description("Create the awsless bootstrap stack").action(async () => {
2007
+ await layout(async (config2, write) => {
2008
+ await write(bootstrapDeployer(config2));
2009
+ });
2010
+ });
2011
+ };
2012
+
2013
+ // src/cli/ui/complex/stack-tree.ts
2014
+ var stackTree = (nodes, statuses) => {
2015
+ return (term) => {
2016
+ const render = (nodes2, deep = 0, parents = []) => {
2017
+ const size = nodes2.length - 1;
2018
+ nodes2.forEach((node, i) => {
2019
+ const id = node.stack.artifactId;
2020
+ const status2 = statuses[id];
2021
+ const first = i === 0 && deep === 0;
2022
+ const last = i === size;
2023
+ const more = i < size;
2024
+ const line = flexLine(term, [
2025
+ ...parents.map((parent) => {
2026
+ return style.label(
2027
+ parent ? "\u2502".padEnd(3) : " ".repeat(3)
2028
+ );
2029
+ }),
2030
+ style.label(
2031
+ first && size === 0 ? " " : first ? "\u250C\u2500" : last ? "\u2514\u2500" : "\u251C\u2500"
2032
+ ),
2033
+ " ",
2034
+ style.info(id),
2035
+ " "
2036
+ ], [
2037
+ // style.placeholder(' [ '),
2038
+ " ",
2039
+ status2,
2040
+ // style.placeholder(' ] '),
2041
+ br()
2042
+ ]);
2043
+ term.out.write(line);
2044
+ render(node.children, deep + 1, [...parents, more]);
2045
+ });
2046
+ };
2047
+ render(nodes);
2048
+ };
2049
+ };
2050
+
2051
+ // src/cli/ui/__components/basic.ts
2052
+ var br2 = () => {
2053
+ return "\n";
2054
+ };
2055
+
2056
+ // src/cli/ui/__components/spinner.ts
2057
+ var frames2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
2058
+ var length2 = frames2.length;
2059
+ var createSpinner2 = () => {
2060
+ const index = new Signal(0);
2061
+ const frame = derive([index], (index2) => style.info(frames2[index2 % length2]));
2062
+ const interval = setInterval(() => {
2063
+ index.update((i) => i + 1);
2064
+ }, 80);
2065
+ return [
2066
+ frame,
2067
+ () => {
2068
+ clearInterval(interval);
2069
+ }
2070
+ ];
2071
+ };
2072
+
2073
+ // src/cli/ui/__components/dialog.ts
2074
+ var dialog2 = (type, lines) => {
2075
+ const padding = 3;
2076
+ const icon = style[type](symbol[type].padEnd(padding));
2077
+ return lines.map((line, i) => {
2078
+ if (i === 0) {
2079
+ return icon + line;
2080
+ } else {
2081
+ return " ".repeat(padding) + line;
2082
+ }
2083
+ }).join(br2()) + br2();
2084
+ };
2085
+ var loadingDialog2 = (message) => {
2086
+ const [icon, stop] = createSpinner2();
2087
+ const description = new Signal(message);
2088
+ const time = new Signal("");
2089
+ const start = /* @__PURE__ */ new Date();
2090
+ return (term) => {
2091
+ term.out.write([
2092
+ icon,
2093
+ " ",
2094
+ description,
2095
+ " ",
2096
+ time,
2097
+ br2()
2098
+ ]);
2099
+ return (message2) => {
2100
+ const end = /* @__PURE__ */ new Date();
2101
+ const diff = end.getTime() - start.getTime();
2102
+ description.set(message2);
2103
+ time.set(style.attr(diff) + style.attr.dim("ms"));
2104
+ stop();
2105
+ icon.set(style.success(symbol.success));
2106
+ };
2107
+ };
2108
+ };
2109
+
2110
+ // src/cli/command/status.ts
2111
+ var status = (program2) => {
2112
+ program2.command("status").argument("[stacks...]", "Optionally filter stacks to lookup status").description("View the application status").action(async (filters) => {
2113
+ await layout(async (config2, write) => {
2114
+ const { app, assets, dependencyTree } = await toApp(config2, filters);
2115
+ await cleanUp();
2116
+ await write(assetBuilder(assets));
2117
+ write(br2());
2118
+ const assembly = app.synth();
2119
+ const doneLoading = write(loadingDialog2("Loading stack information..."));
2120
+ const client = new StackClient(config2);
2121
+ const statuses = [];
2122
+ const stackStatuses = {};
2123
+ assembly.stacks.forEach((stack) => {
2124
+ stackStatuses[stack.id] = new Signal(style.info("Loading..."));
2125
+ });
2126
+ write(br2());
2127
+ write(stackTree(dependencyTree, stackStatuses));
2128
+ write(br2());
2129
+ debug("Load metadata for all deployed stacks on AWS");
2130
+ await Promise.all(assembly.stacks.map(async (stack, i) => {
2131
+ const info = await client.get(stack.stackName);
2132
+ const name = stack.id;
2133
+ const signal = stackStatuses[name];
2134
+ await new Promise((resolve) => setTimeout(resolve, i * 1e3));
2135
+ if (!info) {
2136
+ signal.set(style.error("non-existent"));
2137
+ statuses.push("non-existent");
2138
+ } else if (info.template !== JSON.stringify(stack.template)) {
2139
+ signal.set(style.warning("out-of-date"));
2140
+ statuses.push("out-of-date");
2141
+ } else {
2142
+ signal.set(style.success("up-to-date"));
2143
+ statuses.push("up-to-date");
2144
+ }
2145
+ }));
2146
+ doneLoading("Done loading stack information");
2147
+ debug("Done loading data for all deployed stacks on AWS");
2148
+ if (statuses.includes("non-existent") || statuses.includes("out-of-date")) {
2149
+ write(dialog2("warning", ["Your app has undeployed changes !!!"]));
2150
+ } else {
2151
+ write(dialog2("success", ["Your app has not been changed"]));
2152
+ }
2153
+ });
2154
+ });
2155
+ };
2156
+
2157
+ // src/cli/command/deploy.ts
2158
+ var deploy = (program2) => {
2159
+ program2.command("deploy").argument("[stacks...]", "Optionally filter stacks to deploy").description("Deploy your app to AWS").action(async (filters) => {
2160
+ await layout(async (config2, write) => {
2161
+ await write(bootstrapDeployer(config2));
2162
+ const { app, stackNames, assets, dependencyTree } = await toApp(config2, filters);
2163
+ const formattedFilter = stackNames.map((i) => style.info(i)).join(style.placeholder(", "));
2164
+ debug("Stacks to deploy", formattedFilter);
2165
+ const deployAll = filters.length === 0;
2166
+ const deploySingle = filters.length === 1;
2167
+ const confirm = await write(confirmPrompt(deployAll ? `Are you sure you want to deploy ${style.warning("all")} stacks?` : deploySingle ? `Are you sure you want to deploy the ${formattedFilter} stack?` : `Are you sure you want to deploy the [ ${formattedFilter} ] stacks?`));
2168
+ if (!confirm) {
2169
+ throw new Cancelled();
2170
+ }
2171
+ await cleanUp();
2172
+ await write(assetBuilder(assets));
2173
+ write(br());
2174
+ const donePublishing = write(loadingDialog("Publishing stack assets to AWS..."));
2175
+ await Promise.all(assets.map(async (_, assets2) => {
2176
+ await Promise.all(assets2.map(async (asset) => {
2177
+ await asset.publish?.();
2178
+ }));
2179
+ }));
2180
+ donePublishing("Done publishing stack assets to AWS");
2181
+ const assembly = app.synth();
2182
+ const statuses = {};
2183
+ assembly.stacks.map((stack) => {
2184
+ statuses[stack.id] = new Signal(style.info("waiting"));
2185
+ });
2186
+ const doneDeploying = write(loadingDialog("Deploying stacks to AWS..."));
2187
+ write(br());
2188
+ write(stackTree(dependencyTree, statuses));
2189
+ const client = new StackClient(config2);
2190
+ const deploymentLine = createDeploymentLine(dependencyTree);
2191
+ for (const stacks of deploymentLine) {
2192
+ await Promise.allSettled(stacks.map(async (stack) => {
2193
+ const stackArtifect = assembly.stacks.find((item) => item.id === stack.artifactId);
2194
+ statuses[stack.artifactId].set(style.warning("deploying"));
2195
+ try {
2196
+ await client.deploy(stackArtifect);
2197
+ } catch (error) {
2198
+ debugError(error);
2199
+ statuses[stack.artifactId].set(style.error("failed"));
2200
+ throw error;
2201
+ }
2202
+ statuses[stack.artifactId].set(style.success("deployed"));
2203
+ }));
2204
+ }
2205
+ doneDeploying("Done deploying stacks to AWS");
2206
+ });
2207
+ });
2208
+ };
2209
+
2210
+ // src/cli/ui/prompt/text.ts
2211
+ var textPrompt = (label, options = {}) => {
2212
+ return (term) => {
2213
+ return new Promise((resolve) => {
2214
+ const done = new Signal(false);
2215
+ const cursor = new Signal(0);
2216
+ const icon = new Signal(style.info(symbol.question));
2217
+ const value = new Signal([]);
2218
+ const custom = derive([value], options.renderer ?? ((value2) => value2));
2219
+ const formatted = derive([custom, cursor, done], (value2, cursor2, done2) => {
2220
+ if (done2) {
2221
+ return value2.join("");
2222
+ }
2223
+ return [...value2, " "].map((chr, i) => {
2224
+ return i === cursor2 ? style.cursor(chr) : chr;
2225
+ }).join("");
2226
+ });
2227
+ const sep = new Signal(style.placeholder(symbol.pointerSmall));
2228
+ const release = term.in.captureInput({
2229
+ reset() {
2230
+ value.set([]);
2231
+ cursor.set(0);
2232
+ },
2233
+ exit() {
2234
+ release();
2235
+ done.set(true);
2236
+ icon.set(style.success(symbol.success));
2237
+ sep.set(symbol.ellipsis);
2238
+ value.set([]);
2239
+ resolve("");
2240
+ },
2241
+ submit() {
2242
+ release();
2243
+ done.set(true);
2244
+ icon.set(style.success(symbol.success));
2245
+ sep.set(symbol.ellipsis);
2246
+ resolve(value.get().join(""));
2247
+ },
2248
+ input: (chr) => {
2249
+ value.update((value2) => [
2250
+ ...value2.slice(0, cursor.get()),
2251
+ chr,
2252
+ ...value2.slice(cursor.get())
2253
+ ]);
2254
+ cursor.update((cursor2) => cursor2 + 1);
2255
+ },
2256
+ delete() {
2257
+ value.update((value2) => [...value2].filter((_, i) => i !== cursor.get() - 1));
2258
+ cursor.update((cursor2) => Math.max(0, cursor2 - 1));
2259
+ },
2260
+ left() {
2261
+ cursor.update((cursor2) => Math.max(0, cursor2 - 1));
2262
+ },
2263
+ right() {
2264
+ cursor.update((cursor2) => Math.min(value.get().length, cursor2 + 1));
2265
+ }
2266
+ });
2267
+ term.out.write([icon, " ", style.label(label), " ", sep, " ", formatted, br()]);
2268
+ });
2269
+ };
2270
+ };
2271
+
2272
+ // src/cli/command/config/set.ts
2273
+ var set = (program2) => {
2274
+ program2.command("set <name>").description("Set a config value").action(async (name) => {
2275
+ await layout(async (config2, write) => {
2276
+ const params = new Params(config2);
2277
+ write(list({
2278
+ "Set config parameter": style.info(name)
2279
+ }));
2280
+ write(br());
2281
+ const value = await write(textPrompt("Enter config value"));
2282
+ if (value === "") {
2283
+ write(dialog("error", [`Provided config value can't be empty`]));
2284
+ } else {
2285
+ const done = write(loadingDialog(`Saving remote config parameter`));
2286
+ await params.set(name, value);
2287
+ done(`Done saving remote config parameter`);
2288
+ }
2289
+ });
2290
+ });
2291
+ };
2292
+
2293
+ // src/cli/command/config/get.ts
2294
+ var get = (program2) => {
2295
+ program2.command("get <name>").description("Get a config value").action(async (name) => {
2296
+ await layout(async (config2, write) => {
2297
+ const params = new Params(config2);
2298
+ const done = write(loadingDialog(`Getting remote config parameter`));
2299
+ const value = await params.get(name);
2300
+ done(`Done getting remote config parameter`);
2301
+ write(br());
2302
+ write(list({
2303
+ Name: name,
2304
+ Value: value || style.error("(empty)")
2305
+ }));
2306
+ });
2307
+ });
2308
+ };
2309
+
2310
+ // src/cli/command/config/delete.ts
2311
+ var del = (program2) => {
2312
+ program2.command("delete <name>").description("Delete a config value").action(async (name) => {
2313
+ await layout(async (config2, write) => {
2314
+ const params = new Params(config2);
2315
+ write(dialog("warning", [`Your deleting the ${style.info(name)} config parameter`]));
2316
+ const confirm = await write(confirmPrompt("Are you sure?"));
2317
+ if (!confirm) {
2318
+ throw new Cancelled();
2319
+ }
2320
+ const done = write(loadingDialog(`Deleting remote config parameter`));
2321
+ const value = await params.get(name);
2322
+ await params.delete(name);
2323
+ done(`Done deleting remote config parameter`);
2324
+ write(br());
2325
+ write(list({
2326
+ Name: name,
2327
+ Value: value || style.error("(empty)")
2328
+ }));
2329
+ });
2330
+ });
2331
+ };
2332
+
2333
+ // src/cli/command/config/list.ts
2334
+ var list2 = (program2) => {
2335
+ program2.command("list").description(`List all config value's`).action(async () => {
2336
+ await layout(async (config2, write) => {
2337
+ const params = new Params(config2);
2338
+ const done = write(loadingDialog("Loading config parameters..."));
2339
+ const values = await params.list();
2340
+ done("Done loading config values");
2341
+ if (Object.keys(values).length > 0) {
2342
+ write(br());
2343
+ write(list(values));
2344
+ } else {
2345
+ write(dialog("warning", ["No config parameters found"]));
2346
+ }
2347
+ });
2348
+ });
2349
+ };
2350
+
2351
+ // src/cli/command/config/index.ts
2352
+ var commands = [
2353
+ set,
2354
+ get,
2355
+ del,
2356
+ list2
2357
+ ];
2358
+ var config = (program2) => {
2359
+ const command = program2.command("config").description("Manage config values");
2360
+ commands.forEach((cb) => cb(command));
2361
+ };
2362
+
2363
+ // src/cli/program.ts
2364
+ var program = new Command();
2365
+ program.name("awsless");
2366
+ program.option("--config-file <string>", "The config file location");
2367
+ program.option("--stage <string>", "The stage to use, defaults to prod stage", "prod");
2368
+ program.option("--profile <string>", "The AWS profile to use");
2369
+ program.option("--region <string>", "The AWS region to use");
2370
+ program.option("-m --mute", "Mute sound effects");
2371
+ program.option("-v --verbose", "Print verbose logs");
2372
+ program.on("option:verbose", () => {
2373
+ process.env.VERBOSE = program.opts().verbose ? "1" : void 0;
2374
+ });
2375
+ var commands2 = [
2376
+ bootstrap,
2377
+ status,
2378
+ build,
2379
+ deploy,
2380
+ config
2381
+ // diff,
2382
+ // remove,
2383
+ // test,
2384
+ // test2,
2385
+ ];
2386
+ commands2.forEach((command) => command(program));
2387
+
2388
+ // src/bin.ts
2389
+ program.parse(process.argv);