@alzulejos/laranja 0.2.4

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.
Files changed (65) hide show
  1. package/README.md +28 -0
  2. package/dist/aws.d.ts +79 -0
  3. package/dist/aws.js +170 -0
  4. package/dist/aws.js.map +1 -0
  5. package/dist/cli.d.ts +2 -0
  6. package/dist/cli.js +134 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/commands/deploy.d.ts +5 -0
  9. package/dist/commands/deploy.js +146 -0
  10. package/dist/commands/deploy.js.map +1 -0
  11. package/dist/commands/destroy.d.ts +3 -0
  12. package/dist/commands/destroy.js +60 -0
  13. package/dist/commands/destroy.js.map +1 -0
  14. package/dist/commands/eject.d.ts +9 -0
  15. package/dist/commands/eject.js +45 -0
  16. package/dist/commands/eject.js.map +1 -0
  17. package/dist/commands/init.d.ts +1 -0
  18. package/dist/commands/init.js +103 -0
  19. package/dist/commands/init.js.map +1 -0
  20. package/dist/commands/logout.d.ts +5 -0
  21. package/dist/commands/logout.js +15 -0
  22. package/dist/commands/logout.js.map +1 -0
  23. package/dist/commands/logs.d.ts +25 -0
  24. package/dist/commands/logs.js +135 -0
  25. package/dist/commands/logs.js.map +1 -0
  26. package/dist/commands/plan.d.ts +10 -0
  27. package/dist/commands/plan.js +47 -0
  28. package/dist/commands/plan.js.map +1 -0
  29. package/dist/diagnostics.d.ts +31 -0
  30. package/dist/diagnostics.js +85 -0
  31. package/dist/diagnostics.js.map +1 -0
  32. package/dist/index.d.ts +8 -0
  33. package/dist/index.js +8 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/io.d.ts +9 -0
  36. package/dist/io.js +28 -0
  37. package/dist/io.js.map +1 -0
  38. package/dist/iohost.d.ts +36 -0
  39. package/dist/iohost.js +71 -0
  40. package/dist/iohost.js.map +1 -0
  41. package/dist/lifecycle.d.ts +12 -0
  42. package/dist/lifecycle.js +21 -0
  43. package/dist/lifecycle.js.map +1 -0
  44. package/dist/nest-build.d.ts +10 -0
  45. package/dist/nest-build.js +73 -0
  46. package/dist/nest-build.js.map +1 -0
  47. package/dist/pipeline.d.ts +34 -0
  48. package/dist/pipeline.js +115 -0
  49. package/dist/pipeline.js.map +1 -0
  50. package/dist/plan-summary.d.ts +23 -0
  51. package/dist/plan-summary.js +131 -0
  52. package/dist/plan-summary.js.map +1 -0
  53. package/dist/project-link.d.ts +26 -0
  54. package/dist/project-link.js +77 -0
  55. package/dist/project-link.js.map +1 -0
  56. package/dist/report.d.ts +51 -0
  57. package/dist/report.js +183 -0
  58. package/dist/report.js.map +1 -0
  59. package/dist/resource-types.d.ts +9 -0
  60. package/dist/resource-types.js +19 -0
  61. package/dist/resource-types.js.map +1 -0
  62. package/dist/ui.d.ts +50 -0
  63. package/dist/ui.js +216 -0
  64. package/dist/ui.js.map +1 -0
  65. package/package.json +34 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export { printPlan } from "./pipeline.js";
2
+ export type { Assembly } from "./pipeline.js";
3
+ export { init } from "./commands/init.js";
4
+ export { deploy } from "./commands/deploy.js";
5
+ export { plan } from "./commands/plan.js";
6
+ export { destroy } from "./commands/destroy.js";
7
+ export { eject } from "./commands/eject.js";
8
+ export { logs } from "./commands/logs.js";
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export { printPlan } from "./pipeline.js";
2
+ export { init } from "./commands/init.js";
3
+ export { deploy } from "./commands/deploy.js";
4
+ export { plan } from "./commands/plan.js";
5
+ export { destroy } from "./commands/destroy.js";
6
+ export { eject } from "./commands/eject.js";
7
+ export { logs } from "./commands/logs.js";
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC"}
package/dist/io.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ /** Ask a yes/no question on the terminal. Defaults to no. */
2
+ export declare function confirm(question: string): Promise<boolean>;
3
+ /** Resolve the deploy region from config/env, or throw a clear error. */
4
+ export declare function requireRegion(configRegion: string | undefined): string;
5
+ /** Apply config-derived credentials/region to the environment for the AWS SDK + toolkit. */
6
+ export declare function applyAwsEnv(opts: {
7
+ region: string;
8
+ profile?: string;
9
+ }): void;
package/dist/io.js ADDED
@@ -0,0 +1,28 @@
1
+ import readline from "node:readline/promises";
2
+ /** Ask a yes/no question on the terminal. Defaults to no. */
3
+ export async function confirm(question) {
4
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
5
+ try {
6
+ const answer = (await rl.question(`${question} `)).trim().toLowerCase();
7
+ return answer === "y" || answer === "yes";
8
+ }
9
+ finally {
10
+ rl.close();
11
+ }
12
+ }
13
+ /** Resolve the deploy region from config/env, or throw a clear error. */
14
+ export function requireRegion(configRegion) {
15
+ const region = configRegion ?? process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION;
16
+ if (!region) {
17
+ throw new Error('No AWS region. Set "region" in laranja.config.ts (or AWS_REGION).');
18
+ }
19
+ return region;
20
+ }
21
+ /** Apply config-derived credentials/region to the environment for the AWS SDK + toolkit. */
22
+ export function applyAwsEnv(opts) {
23
+ if (opts.profile)
24
+ process.env.AWS_PROFILE = opts.profile;
25
+ process.env.AWS_REGION = opts.region;
26
+ process.env.CDK_DISABLE_VERSION_CHECK = "1";
27
+ }
28
+ //# sourceMappingURL=io.js.map
package/dist/io.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"io.js","sourceRoot":"","sources":["../src/io.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,wBAAwB,CAAC;AAE9C,6DAA6D;AAC7D,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,QAAgB;IAC5C,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACtF,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,GAAG,QAAQ,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACxE,OAAO,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,KAAK,CAAC;IAC5C,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,aAAa,CAAC,YAAgC;IAC5D,MAAM,MAAM,GAAG,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IACxF,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,mEAAmE,CAAC,CAAC;IACvF,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,WAAW,CAAC,IAA0C;IACpE,IAAI,IAAI,CAAC,OAAO;QAAE,OAAO,CAAC,GAAG,CAAC,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC;IACzD,OAAO,CAAC,GAAG,CAAC,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC;IACrC,OAAO,CAAC,GAAG,CAAC,yBAAyB,GAAG,GAAG,CAAC;AAC9C,CAAC"}
@@ -0,0 +1,36 @@
1
+ import { NonInteractiveIoHost } from "@aws-cdk/toolkit-lib";
2
+ import { type Spinner } from "./ui.js";
3
+ type IoMsg = Parameters<NonInteractiveIoHost["notify"]>[0];
4
+ /** The subset of a CloudFormation stack-activity event we surface. */
5
+ export interface ResourceActivity {
6
+ event?: {
7
+ ResourceStatus?: string;
8
+ LogicalResourceId?: string;
9
+ ResourceType?: string;
10
+ PhysicalResourceId?: string;
11
+ ResourceStatusReason?: string;
12
+ };
13
+ progress?: {
14
+ completed?: number;
15
+ total?: number;
16
+ formatted?: string;
17
+ };
18
+ }
19
+ /**
20
+ * IO host for the CDK toolkit that keeps the console clean: by default it hides
21
+ * CDK's verbose stream, forwards per-resource stack activity to `onActivity`, and
22
+ * only prints errors directly. Pass `verbose` to stream the full CDK output.
23
+ */
24
+ export declare class LaranjaIoHost extends NonInteractiveIoHost {
25
+ private readonly verbose;
26
+ /** Called for each CloudFormation resource event during deploy/destroy. */
27
+ onActivity?: (activity: ResourceActivity) => void;
28
+ constructor(verbose?: boolean);
29
+ notify(msg: IoMsg): Promise<void>;
30
+ }
31
+ /**
32
+ * Build a stack-activity handler that drives a spinner: live N/total progress plus
33
+ * a "✓" line per Lambda/queue as it completes, and a "✗" line on failures.
34
+ */
35
+ export declare function makeActivityHandler(sp: Spinner, verb?: string): (a: ResourceActivity) => void;
36
+ export {};
package/dist/iohost.js ADDED
@@ -0,0 +1,71 @@
1
+ import { NonInteractiveIoHost } from "@aws-cdk/toolkit-lib";
2
+ import { dim, green, red } from "./ui.js";
3
+ /** Message code the toolkit uses for per-resource CloudFormation stack activity. */
4
+ const STACK_ACTIVITY = "CDK_TOOLKIT_I5502";
5
+ /**
6
+ * IO host for the CDK toolkit that keeps the console clean: by default it hides
7
+ * CDK's verbose stream, forwards per-resource stack activity to `onActivity`, and
8
+ * only prints errors directly. Pass `verbose` to stream the full CDK output.
9
+ */
10
+ export class LaranjaIoHost extends NonInteractiveIoHost {
11
+ verbose;
12
+ /** Called for each CloudFormation resource event during deploy/destroy. */
13
+ onActivity;
14
+ constructor(verbose = false) {
15
+ super();
16
+ this.verbose = verbose;
17
+ }
18
+ async notify(msg) {
19
+ if (this.verbose) {
20
+ await super.notify(msg);
21
+ return;
22
+ }
23
+ if (msg.code === STACK_ACTIVITY && this.onActivity) {
24
+ this.onActivity(msg.data);
25
+ return;
26
+ }
27
+ // Never swallow errors — everything else is intentionally hidden.
28
+ if (msg.level === "error") {
29
+ process.stderr.write(` ${red("✗")} ${msg.message}\n`);
30
+ }
31
+ }
32
+ }
33
+ /** Resource types worth announcing as they finish (the rest are infra plumbing). */
34
+ const ANNOUNCE = {
35
+ "AWS::Lambda::Function": "λ",
36
+ "AWS::SQS::Queue": "📨",
37
+ };
38
+ /**
39
+ * Build a stack-activity handler that drives a spinner: live N/total progress plus
40
+ * a "✓" line per Lambda/queue as it completes, and a "✗" line on failures.
41
+ */
42
+ export function makeActivityHandler(sp, verb = "deploying") {
43
+ const announced = new Set();
44
+ return (a) => {
45
+ const ev = a.event ?? {};
46
+ const status = ev.ResourceStatus ?? "";
47
+ const progress = a.progress?.total ? `${a.progress.completed}/${a.progress.total}` : (a.progress?.formatted ?? "");
48
+ if (ev.LogicalResourceId) {
49
+ // Keep the live line to a single terminal row. Long logical ids otherwise
50
+ // wrap, which breaks the spinner's in-place redraw (it leaves one wrapped
51
+ // line behind per animation frame). Clip the variable part before styling
52
+ // so we never cut an ANSI escape mid-sequence.
53
+ const detail = `${status} ${ev.LogicalResourceId}`;
54
+ const room = Math.max(12, (process.stdout.columns ?? 80) - verb.length - progress.length - 8);
55
+ const clipped = detail.length > room ? `${detail.slice(0, room - 1)}…` : detail;
56
+ sp.update(`${verb} ${progress} ${dim(clipped)}`);
57
+ }
58
+ const icon = ANNOUNCE[ev.ResourceType ?? ""];
59
+ if (icon && status.endsWith("_COMPLETE") && !status.startsWith("DELETE") && !announced.has(ev.LogicalResourceId ?? "")) {
60
+ announced.add(ev.LogicalResourceId ?? "");
61
+ const name = ev.ResourceType === "AWS::SQS::Queue"
62
+ ? (ev.PhysicalResourceId?.split("/").pop() ?? ev.LogicalResourceId)
63
+ : (ev.PhysicalResourceId ?? ev.LogicalResourceId);
64
+ sp.log(` ${green("✓")} ${icon} ${name}`);
65
+ }
66
+ if (status.endsWith("_FAILED")) {
67
+ sp.log(` ${red("✗")} ${ev.LogicalResourceId ?? ""} ${dim(ev.ResourceStatusReason ?? "")}`);
68
+ }
69
+ };
70
+ }
71
+ //# sourceMappingURL=iohost.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"iohost.js","sourceRoot":"","sources":["../src/iohost.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAgB,MAAM,SAAS,CAAC;AAgBxD,oFAAoF;AACpF,MAAM,cAAc,GAAG,mBAAmB,CAAC;AAE3C;;;;GAIG;AACH,MAAM,OAAO,aAAc,SAAQ,oBAAoB;IAIxB;IAH7B,2EAA2E;IACpE,UAAU,CAAwC;IAEzD,YAA6B,UAAU,KAAK;QAC1C,KAAK,EAAE,CAAC;QADmB,YAAO,GAAP,OAAO,CAAQ;IAE5C,CAAC;IAEQ,KAAK,CAAC,MAAM,CAAC,GAAU;QAC9B,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACxB,OAAO;QACT,CAAC;QACD,IAAI,GAAG,CAAC,IAAI,KAAK,cAAc,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACnD,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAwB,CAAC,CAAC;YAC9C,OAAO;QACT,CAAC;QACD,kEAAkE;QAClE,IAAI,GAAG,CAAC,KAAK,KAAK,OAAO,EAAE,CAAC;YAC1B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;CACF;AAED,oFAAoF;AACpF,MAAM,QAAQ,GAA2B;IACvC,uBAAuB,EAAE,GAAG;IAC5B,iBAAiB,EAAE,IAAI;CACxB,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,EAAW,EAAE,IAAI,GAAG,WAAW;IACjE,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;IACpC,OAAO,CAAC,CAAC,EAAE,EAAE;QACX,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,EAAE,CAAC,cAAc,IAAI,EAAE,CAAC;QACvC,MAAM,QAAQ,GAAG,CAAC,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,SAAS,IAAI,CAAC,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC;QACnH,IAAI,EAAE,CAAC,iBAAiB,EAAE,CAAC;YACzB,0EAA0E;YAC1E,0EAA0E;YAC1E,0EAA0E;YAC1E,+CAA+C;YAC/C,MAAM,MAAM,GAAG,GAAG,MAAM,IAAI,EAAE,CAAC,iBAAiB,EAAE,CAAC;YACnD,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YAC9F,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC;YAChF,EAAE,CAAC,MAAM,CAAC,GAAG,IAAI,IAAI,QAAQ,KAAK,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACpD,CAAC;QAED,MAAM,IAAI,GAAG,QAAQ,CAAC,EAAE,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC;QAC7C,IAAI,IAAI,IAAI,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,iBAAiB,IAAI,EAAE,CAAC,EAAE,CAAC;YACvH,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC;YAC1C,MAAM,IAAI,GACR,EAAE,CAAC,YAAY,KAAK,iBAAiB;gBACnC,CAAC,CAAC,CAAC,EAAE,CAAC,kBAAkB,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,iBAAiB,CAAC;gBACnE,CAAC,CAAC,CAAC,EAAE,CAAC,kBAAkB,IAAI,EAAE,CAAC,iBAAiB,CAAC,CAAC;YACtD,EAAE,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC,CAAC;QAC5C,CAAC;QACD,IAAI,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YAC/B,EAAE,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,iBAAiB,IAAI,EAAE,IAAI,GAAG,CAAC,EAAE,CAAC,oBAAoB,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;QAC9F,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Fire a *post-gate* dashboard status report without letting a reporting failure
3
+ * mask the AWS operation. Deploy/destroy still succeed (or fail) on their own
4
+ * terms even if the dashboard is unreachable — we just warn.
5
+ *
6
+ * Use this ONLY for lifecycle telemetry that runs after the command is already
7
+ * authorized (STARTED / SUCCESS / FAILED / resources). Never wrap the permission
8
+ * gate — the call that authenticates the key and opens the deployment/teardown
9
+ * row — in this; that call must be fail-closed so an auth error aborts before we
10
+ * touch the user's cloud.
11
+ */
12
+ export declare function reportSafely(what: string, fn: () => Promise<unknown>): Promise<void>;
@@ -0,0 +1,21 @@
1
+ import * as ui from "./ui.js";
2
+ /**
3
+ * Fire a *post-gate* dashboard status report without letting a reporting failure
4
+ * mask the AWS operation. Deploy/destroy still succeed (or fail) on their own
5
+ * terms even if the dashboard is unreachable — we just warn.
6
+ *
7
+ * Use this ONLY for lifecycle telemetry that runs after the command is already
8
+ * authorized (STARTED / SUCCESS / FAILED / resources). Never wrap the permission
9
+ * gate — the call that authenticates the key and opens the deployment/teardown
10
+ * row — in this; that call must be fail-closed so an auth error aborts before we
11
+ * touch the user's cloud.
12
+ */
13
+ export async function reportSafely(what, fn) {
14
+ try {
15
+ await fn();
16
+ }
17
+ catch (err) {
18
+ ui.warn(`couldn't ${what} to the dashboard (${err instanceof Error ? err.message : String(err)})`);
19
+ }
20
+ }
21
+ //# sourceMappingURL=lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAE9B;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAY,EAAE,EAA0B;IACzE,IAAI,CAAC;QACH,MAAM,EAAE,EAAE,CAAC;IACb,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,EAAE,CAAC,IAAI,CAAC,YAAY,IAAI,sBAAsB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACrG,CAAC;AACH,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Map a source entry the scanner found (the HTTP bootstrap, the workers module, or
3
+ * a provider file) to the compiled file the user's build produced, and return its
4
+ * absolute path. Works for both a plain `tsc` output tree and a webpacked single
5
+ * `dist/main.js` (same path either way).
6
+ *
7
+ * Throws an actionable error if the compiled file is missing — the user must build
8
+ * (`npm run build` / `nest build`) before deploying, so the DI metadata exists.
9
+ */
10
+ export declare function resolveNestCompiledEntry(projectDir: string, sourceEntry: string): string;
@@ -0,0 +1,73 @@
1
+ import path from "node:path";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ /**
4
+ * Nest deploys reuse the user's OWN compiled build output rather than re-bundling
5
+ * their `.ts` from source. Reason: Nest's DI reads `design:paramtypes` metadata at
6
+ * runtime, and only the user's `tsc`/`nest build` (`emitDecoratorMetadata: true`)
7
+ * emits it — esbuild-from-source drops it, breaking DI in the Lambda. The compiled
8
+ * JS already has the metadata baked in, so we point the shim at that instead.
9
+ *
10
+ * This resolves where the user's build put the compiled entry, given the source
11
+ * entry the scanner recorded (e.g. `src/main.ts` -> `dist/main.js`).
12
+ */
13
+ /** Strip `//` and `/* *\/` comments so a JSONC tsconfig still parses. */
14
+ function parseJsonc(text) {
15
+ const noBlock = text.replace(/\/\*[\s\S]*?\*\//g, "");
16
+ const noLine = noBlock.replace(/(^|[^:])\/\/.*$/gm, "$1");
17
+ return JSON.parse(noLine);
18
+ }
19
+ function readJsonc(file) {
20
+ if (!existsSync(file))
21
+ return undefined;
22
+ try {
23
+ return parseJsonc(readFileSync(file, "utf8"));
24
+ }
25
+ catch {
26
+ return undefined;
27
+ }
28
+ }
29
+ /** The user's source root (default "src") and compiled out dir (default "dist"). */
30
+ function resolveBuildDirs(projectDir) {
31
+ const nestCli = readJsonc(path.join(projectDir, "nest-cli.json"));
32
+ const tsconfig = readJsonc(path.join(projectDir, "tsconfig.json"));
33
+ const sourceRoot = nestCli?.sourceRoot ?? "src";
34
+ const outDir = nestCli?.compilerOptions?.outDir ??
35
+ tsconfig?.compilerOptions?.outDir ??
36
+ "dist";
37
+ return { sourceRoot, outDir };
38
+ }
39
+ /**
40
+ * Map a source entry the scanner found (the HTTP bootstrap, the workers module, or
41
+ * a provider file) to the compiled file the user's build produced, and return its
42
+ * absolute path. Works for both a plain `tsc` output tree and a webpacked single
43
+ * `dist/main.js` (same path either way).
44
+ *
45
+ * Throws an actionable error if the compiled file is missing — the user must build
46
+ * (`npm run build` / `nest build`) before deploying, so the DI metadata exists.
47
+ */
48
+ export function resolveNestCompiledEntry(projectDir, sourceEntry) {
49
+ const { sourceRoot, outDir } = resolveBuildDirs(projectDir);
50
+ const jsEntry = sourceEntry.replace(/\.(ts|tsx|mts|cts)$/, ".js");
51
+ // tsc mirrors each file under `outDir` at its path relative to the *computed*
52
+ // rootDir — which is NOT necessarily `nest-cli.json`'s `sourceRoot`. tsc infers
53
+ // rootDir as the common ancestor of all compiled files, so a single `.ts` outside
54
+ // `src` widens it to the project root. laranja projects always have a root-level
55
+ // `laranja.config.ts`, so `src/main.ts` typically lands at `dist/src/main.js`, not
56
+ // `dist/main.js`. Check both layouts (existence disambiguates; `deleteOutDir`
57
+ // leaves only the real one). A webpacked single-file build is the first candidate.
58
+ const candidates = [
59
+ // rootDir === sourceRoot -> dist/main.js
60
+ path.join(outDir, path.relative(sourceRoot, jsEntry)),
61
+ // rootDir === projectDir -> dist/src/main.js
62
+ path.join(outDir, jsEntry),
63
+ ];
64
+ for (const rel of candidates) {
65
+ const abs = path.join(projectDir, rel);
66
+ if (existsSync(abs))
67
+ return abs;
68
+ }
69
+ throw new Error(`Nest build output not found (looked for ${candidates.join(" and ")}). ` +
70
+ `Build your app first (e.g. \`npm run build\`) so laranja can package the ` +
71
+ `compiled output — Nest's dependency injection needs the metadata your build emits.`);
72
+ }
73
+ //# sourceMappingURL=nest-build.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nest-build.js","sourceRoot":"","sources":["../src/nest-build.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEnD;;;;;;;;;GASG;AAEH,yEAAyE;AACzE,SAAS,UAAU,CAAC,IAAY;IAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC;IACtD,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,mBAAmB,EAAE,IAAI,CAAC,CAAC;IAC1D,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;AAC5B,CAAC;AAED,SAAS,SAAS,CAAC,IAAY;IAC7B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IACxC,IAAI,CAAC;QACH,OAAO,UAAU,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAwB,CAAC;IACvE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,oFAAoF;AACpF,SAAS,gBAAgB,CAAC,UAAkB;IAC1C,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC;IAClE,MAAM,QAAQ,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC;IACnE,MAAM,UAAU,GAAI,OAAO,EAAE,UAAqB,IAAI,KAAK,CAAC;IAC5D,MAAM,MAAM,GACT,OAAO,EAAE,eAAe,EAAE,MAAiB;QAC3C,QAAQ,EAAE,eAAe,EAAE,MAAiB;QAC7C,MAAM,CAAC;IACT,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC;AAChC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,wBAAwB,CAAC,UAAkB,EAAE,WAAmB;IAC9E,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAC5D,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,qBAAqB,EAAE,KAAK,CAAC,CAAC;IAElE,8EAA8E;IAC9E,gFAAgF;IAChF,kFAAkF;IAClF,iFAAiF;IACjF,mFAAmF;IACnF,8EAA8E;IAC9E,mFAAmF;IACnF,MAAM,UAAU,GAAG;QACjB,2CAA2C;QAC3C,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACrD,+CAA+C;QAC/C,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC;KAC3B,CAAC;IACF,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;QACvC,IAAI,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO,GAAG,CAAC;IAClC,CAAC;IACD,MAAM,IAAI,KAAK,CACb,2CAA2C,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK;QACtE,2EAA2E;QAC3E,oFAAoF,CACvF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,34 @@
1
+ import { type InfraIR } from "@alzulejos/laranja-core";
2
+ export interface Assembly {
3
+ ir: InfraIR;
4
+ stackName: string;
5
+ /** Absolute path to the synthesized cloud assembly (cdk.out). */
6
+ cdkOutDir: string;
7
+ region?: string;
8
+ /** The server-synthesized CloudFormation template (set by the plan build). */
9
+ template?: Record<string, unknown>;
10
+ }
11
+ /** A deploy assembly also carries the dashboard deployment id to echo back. */
12
+ export interface RemoteAssembly extends Assembly {
13
+ deploymentId: string;
14
+ /** The validated dashboard project id (sent as `x-project-id` on lifecycle calls). */
15
+ projectId: string;
16
+ }
17
+ interface BuildEnv {
18
+ region?: string;
19
+ account?: string;
20
+ stage?: string;
21
+ }
22
+ /**
23
+ * Server build for a deploy: prepare upload -> `/synth` (opens a deployment row)
24
+ * -> assemble the returned template into a deployable cloud assembly.
25
+ */
26
+ export declare function buildRemoteAssembly(projectDir: string, env: BuildEnv, apiKey: string): Promise<RemoteAssembly>;
27
+ /**
28
+ * Server build for a plan: prepare upload -> `/diff` (read-only synth, NO
29
+ * deployment row) -> assemble the returned template so the toolkit can diff it
30
+ * against the deployed stack.
31
+ */
32
+ export declare function buildPlanAssembly(projectDir: string, env: BuildEnv, apiKey: string): Promise<Assembly>;
33
+ export declare function printPlan(ir: InfraIR): void;
34
+ export {};
@@ -0,0 +1,115 @@
1
+ import path from "node:path";
2
+ import { rmSync } from "node:fs";
3
+ import { loadConfig, postSynth, postDiff, ApiRequestError, apiErrorMessage, } from "@alzulejos/laranja-core";
4
+ import { scan } from "@alzulejos/laranja-scanner";
5
+ import { generateEntries } from "@alzulejos/laranja-runtime";
6
+ import { bundleEntries, computeAssetHashes, assembleFromTemplate } from "@alzulejos/laranja-assembly";
7
+ import { writeResourceTypes } from "./resource-types.js";
8
+ import { resolveNestCompiledEntry } from "./nest-build.js";
9
+ import { step } from "./diagnostics.js";
10
+ /**
11
+ * Scan -> generate shims -> bundle -> fingerprint, into ephemeral `.laranja/`.
12
+ * Synth happens on the laranja SERVER, never here — we bundle locally only to
13
+ * fingerprint each handler (so the server template's `<hash>.zip` keys match) and
14
+ * to have the zips on hand for the toolkit to upload. The source never leaves the
15
+ * machine; only the IR + hashes cross the wire.
16
+ */
17
+ async function prepareUpload(projectDir, env) {
18
+ const outRoot = path.join(projectDir, ".laranja");
19
+ const entryDir = path.join(outRoot, "entries");
20
+ const buildDir = path.join(outRoot, "build");
21
+ const cdkOutDir = path.join(outRoot, "cdk.out");
22
+ rmSync(outRoot, { recursive: true, force: true });
23
+ const config = await loadConfig(projectDir, { stage: env.stage });
24
+ if (!config.projectId) {
25
+ throw new Error("This project isn't linked to laranja — run `laranja init` to connect it.");
26
+ }
27
+ step("scan project");
28
+ const ir = scan({ projectDir, config });
29
+ writeResourceTypes(projectDir, ir);
30
+ step("bundle handlers");
31
+ // Nest: point every shim at the user's COMPILED output (DI metadata intact), not
32
+ // their .ts source — the HTTP bootstrap, the workers(AppModule) module, and each
33
+ // class-based provider. Express bundles straight from source (all undefined).
34
+ const isNest = ir.app.framework === "nest";
35
+ const httpEntry = isNest && ir.http ? resolveNestCompiledEntry(projectDir, ir.http.handlerEntry) : undefined;
36
+ // Nest worker shims resolve both the provider file and each workers(...) module to
37
+ // their compiled paths through this — so multiple DI roots need no extra plumbing.
38
+ const resolveCompiled = isNest ? (file) => resolveNestCompiledEntry(projectDir, file) : undefined;
39
+ const entries = generateEntries(ir, { projectDir, entryDir, httpEntry, resolveCompiled });
40
+ const handlers = await bundleEntries(entries, {
41
+ entryDir,
42
+ buildDir,
43
+ framework: ir.app.framework,
44
+ projectDir,
45
+ });
46
+ const assets = computeAssetHashes(handlers);
47
+ return { projectId: config.projectId, ir, handlers, assets, cdkOutDir, region: env.region ?? config.region };
48
+ }
49
+ /**
50
+ * Server build for a deploy: prepare upload -> `/synth` (opens a deployment row)
51
+ * -> assemble the returned template into a deployable cloud assembly.
52
+ */
53
+ export async function buildRemoteAssembly(projectDir, env, apiKey) {
54
+ const { projectId, ir, handlers, assets, cdkOutDir, region } = await prepareUpload(projectDir, env);
55
+ step("server synth");
56
+ let res;
57
+ try {
58
+ res = await postSynth({ project: ir.app.name, stage: ir.app.stage, artifact: "cloudformation", ir, assets }, apiKey, projectId);
59
+ }
60
+ catch (err) {
61
+ if (err instanceof ApiRequestError)
62
+ throw new Error(apiErrorMessage("Synth failed", err));
63
+ throw err;
64
+ }
65
+ if (res.artifact !== "cloudformation") {
66
+ throw new Error("Server returned a CDK project; expected a CloudFormation template to deploy.");
67
+ }
68
+ step("assemble template");
69
+ assembleFromTemplate({
70
+ outdir: cdkOutDir,
71
+ stackName: res.stackName,
72
+ template: res.template,
73
+ handlers,
74
+ region,
75
+ account: env.account,
76
+ });
77
+ return { ir, stackName: res.stackName, cdkOutDir, region, deploymentId: res.deploymentId, projectId };
78
+ }
79
+ /**
80
+ * Server build for a plan: prepare upload -> `/diff` (read-only synth, NO
81
+ * deployment row) -> assemble the returned template so the toolkit can diff it
82
+ * against the deployed stack.
83
+ */
84
+ export async function buildPlanAssembly(projectDir, env, apiKey) {
85
+ const { projectId, ir, handlers, assets, cdkOutDir, region } = await prepareUpload(projectDir, env);
86
+ let res;
87
+ try {
88
+ res = await postDiff({ project: ir.app.name, stage: ir.app.stage, artifact: "cloudformation", ir, assets }, apiKey, projectId);
89
+ }
90
+ catch (err) {
91
+ if (err instanceof ApiRequestError)
92
+ throw new Error(apiErrorMessage("Plan failed", err));
93
+ throw err;
94
+ }
95
+ if (!res.stackName || !res.template) {
96
+ throw new Error("Server did not return a template to diff.");
97
+ }
98
+ assembleFromTemplate({
99
+ outdir: cdkOutDir,
100
+ stackName: res.stackName,
101
+ template: res.template,
102
+ handlers,
103
+ region,
104
+ account: env.account,
105
+ });
106
+ return { ir, stackName: res.stackName, cdkOutDir, region, template: res.template };
107
+ }
108
+ export function printPlan(ir) {
109
+ console.log(ir.http
110
+ ? ` HTTP: ${ir.http.routes.length} route(s) → 1 proxy Lambda + Function URL`
111
+ : ` HTTP: disabled (workers-only)`);
112
+ console.log(` Cron: ${ir.crons.length} job(s) → Lambda + EventBridge rule each`);
113
+ console.log(` Queues: ${ir.queues.length} → SQS + consumer Lambda each`);
114
+ }
115
+ //# sourceMappingURL=pipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import { type InfraIR } from "@alzulejos/laranja-core";
2
+ /**
3
+ * Minimal structural view of a CDK `TemplateDiff` — only the bits we read. A
4
+ * real `@aws-cdk/cloudformation-diff` `TemplateDiff` satisfies this shape, so we
5
+ * avoid a direct dependency on that (transitive) package's types.
6
+ */
7
+ interface ResourceChange {
8
+ readonly isAddition: boolean;
9
+ readonly isRemoval: boolean;
10
+ }
11
+ export interface StackDiffView {
12
+ readonly resources: {
13
+ readonly changes: Record<string, ResourceChange>;
14
+ };
15
+ }
16
+ /**
17
+ * Print the laranja plan table: one row per app concept (HTTP / each cron / each
18
+ * queue) tagged created / changed / unchanged / removed, plus an AWS-resource
19
+ * tally. Built from the IR (for the friendly rows), the synthesized template (the
20
+ * full resource set), and the diff against the live stack (what actually changed).
21
+ */
22
+ export declare function summarizePlan(ir: InfraIR, template: Record<string, unknown>, diff: StackDiffView): void;
23
+ export {};
@@ -0,0 +1,131 @@
1
+ import { describeSchedule } from "@alzulejos/laranja-core";
2
+ import * as ui from "./ui.js";
3
+ const MARK = {
4
+ created: ui.green("+"),
5
+ changed: ui.orange("~"),
6
+ removed: ui.red("-"),
7
+ unchanged: ui.dim("="),
8
+ };
9
+ const PAINT = {
10
+ created: ui.green,
11
+ changed: ui.orange,
12
+ removed: ui.red,
13
+ unchanged: ui.dim,
14
+ };
15
+ /** Mirror how CDK derives logical-id prefixes from construct ids (drops non-alphanumerics). */
16
+ const idKey = (s) => s.replace(/[^A-Za-z0-9]/g, "");
17
+ function entitiesFromIr(ir) {
18
+ const out = [];
19
+ if (ir.http) {
20
+ const n = ir.http.routes.length;
21
+ out.push({
22
+ kind: "HTTP",
23
+ name: "http",
24
+ detail: `${n} route${n === 1 ? "" : "s"} → proxy Lambda + Function URL`,
25
+ prefixes: ["Http"],
26
+ });
27
+ }
28
+ // A worker module is one consolidated Lambda (WorkerXFn) hosting its grouped
29
+ // crons/queues; the per-handler triggers below still own their own resources.
30
+ for (const w of ir.workers ?? []) {
31
+ const nCron = ir.crons.filter((c) => c.workersId === w.id).length;
32
+ const nQueue = ir.queues.filter((q) => q.workersId === w.id).length;
33
+ if (nCron + nQueue === 0)
34
+ continue;
35
+ const parts = [nCron && `${nCron} cron${nCron === 1 ? "" : "s"}`, nQueue && `${nQueue} queue${nQueue === 1 ? "" : "s"}`];
36
+ out.push({
37
+ kind: "Worker",
38
+ name: w.id,
39
+ detail: `${parts.filter(Boolean).join(" + ")} → one Lambda`,
40
+ prefixes: [`Worker${idKey(w.id)}`],
41
+ });
42
+ }
43
+ for (const c of ir.crons) {
44
+ // Schedule + role are per-cron (Cron<id>Schedule/Role) whether or not the
45
+ // function is shared, so this prefix is right either way.
46
+ out.push({ kind: "Cron", name: c.id, detail: describeSchedule(c.schedule), prefixes: [`Cron${idKey(c.id)}`] });
47
+ }
48
+ for (const q of ir.queues) {
49
+ // The SQS queue is always per-@Queue; a standalone queue also owns its consumer
50
+ // (Consumer<id>Fn), whereas a grouped one runs in its worker Lambda above.
51
+ const prefixes = q.workersId ? [`Queue${idKey(q.id)}`] : [`Queue${idKey(q.id)}`, `Consumer${idKey(q.id)}`];
52
+ out.push({ kind: "Queue", name: q.id, detail: q.workersId ? "SQS → worker Lambda" : "SQS + consumer Lambda", prefixes });
53
+ }
54
+ return out;
55
+ }
56
+ /** Status for every logical id in the new template, plus any resources the diff removed. */
57
+ function resourceStatuses(template, diff) {
58
+ const out = new Map();
59
+ const resources = template.Resources ?? {};
60
+ for (const id of Object.keys(resources))
61
+ out.set(id, "unchanged");
62
+ for (const [id, change] of Object.entries(diff.resources?.changes ?? {})) {
63
+ out.set(id, change.isAddition ? "created" : change.isRemoval ? "removed" : "changed");
64
+ }
65
+ return out;
66
+ }
67
+ /** Assign a logical id to the entity whose prefix it matches longest (so "daily" never steals "dailyreport"). */
68
+ function entityFor(logicalId, entities) {
69
+ let best;
70
+ let bestLen = 0;
71
+ for (const e of entities) {
72
+ for (const p of e.prefixes) {
73
+ if (p.length > bestLen && logicalId.startsWith(p)) {
74
+ best = e;
75
+ bestLen = p.length;
76
+ }
77
+ }
78
+ }
79
+ return best;
80
+ }
81
+ /** Collapse an entity's per-resource statuses into one. */
82
+ function rollup(statuses) {
83
+ if (statuses.length === 0)
84
+ return "unchanged";
85
+ if (statuses.every((s) => s === "created"))
86
+ return "created";
87
+ if (statuses.every((s) => s === "removed"))
88
+ return "removed";
89
+ if (statuses.some((s) => s !== "unchanged"))
90
+ return "changed";
91
+ return "unchanged";
92
+ }
93
+ /**
94
+ * Print the laranja plan table: one row per app concept (HTTP / each cron / each
95
+ * queue) tagged created / changed / unchanged / removed, plus an AWS-resource
96
+ * tally. Built from the IR (for the friendly rows), the synthesized template (the
97
+ * full resource set), and the diff against the live stack (what actually changed).
98
+ */
99
+ export function summarizePlan(ir, template, diff) {
100
+ const entities = entitiesFromIr(ir);
101
+ const statuses = resourceStatuses(template, diff);
102
+ const perEntity = new Map(entities.map((e) => [e, []]));
103
+ for (const [id, status] of statuses) {
104
+ const e = entityFor(id, entities);
105
+ if (e)
106
+ perEntity.get(e).push(status);
107
+ }
108
+ console.log(`\n Plan for ${ui.bold(`"${ir.app.name}"`)}\n`);
109
+ // One aligned row per app concept: "<mark> <name> <kind> <detail>".
110
+ const nameW = Math.max(...entities.map((e) => e.name.length), 0);
111
+ const kindW = Math.max(...entities.map((e) => e.kind.length), 0);
112
+ for (const e of entities) {
113
+ const status = rollup(perEntity.get(e));
114
+ const name = PAINT[status](e.name.padEnd(nameW));
115
+ const kind = ui.dim(e.kind.padEnd(kindW));
116
+ console.log(` ${MARK[status]} ${name} ${kind} ${ui.dim(e.detail)}`);
117
+ }
118
+ const all = [...statuses.values()];
119
+ const count = (s) => all.filter((x) => x === s).length;
120
+ const tally = [
121
+ count("created") && ui.green(`+${count("created")} created`),
122
+ count("changed") && ui.orange(`~${count("changed")} changed`),
123
+ count("removed") && ui.red(`-${count("removed")} removed`),
124
+ ui.dim(`=${count("unchanged")} unchanged`),
125
+ ].filter(Boolean);
126
+ console.log(`\n ${ui.dim(`${all.length} AWS resources`)} ${tally.join(" ")}`);
127
+ if (all.every((s) => s === "unchanged")) {
128
+ console.log(` ${ui.dim("No changes — your deployed stack already matches this code.")}`);
129
+ }
130
+ }
131
+ //# sourceMappingURL=plan-summary.js.map