@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.
- package/README.md +28 -0
- package/dist/aws.d.ts +79 -0
- package/dist/aws.js +170 -0
- package/dist/aws.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +134 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/deploy.d.ts +5 -0
- package/dist/commands/deploy.js +146 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/destroy.d.ts +3 -0
- package/dist/commands/destroy.js +60 -0
- package/dist/commands/destroy.js.map +1 -0
- package/dist/commands/eject.d.ts +9 -0
- package/dist/commands/eject.js +45 -0
- package/dist/commands/eject.js.map +1 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +103 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/logout.d.ts +5 -0
- package/dist/commands/logout.js +15 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/logs.d.ts +25 -0
- package/dist/commands/logs.js +135 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/plan.d.ts +10 -0
- package/dist/commands/plan.js +47 -0
- package/dist/commands/plan.js.map +1 -0
- package/dist/diagnostics.d.ts +31 -0
- package/dist/diagnostics.js +85 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/io.d.ts +9 -0
- package/dist/io.js +28 -0
- package/dist/io.js.map +1 -0
- package/dist/iohost.d.ts +36 -0
- package/dist/iohost.js +71 -0
- package/dist/iohost.js.map +1 -0
- package/dist/lifecycle.d.ts +12 -0
- package/dist/lifecycle.js +21 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/nest-build.d.ts +10 -0
- package/dist/nest-build.js +73 -0
- package/dist/nest-build.js.map +1 -0
- package/dist/pipeline.d.ts +34 -0
- package/dist/pipeline.js +115 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/plan-summary.d.ts +23 -0
- package/dist/plan-summary.js +131 -0
- package/dist/plan-summary.js.map +1 -0
- package/dist/project-link.d.ts +26 -0
- package/dist/project-link.js +77 -0
- package/dist/project-link.js.map +1 -0
- package/dist/report.d.ts +51 -0
- package/dist/report.js +183 -0
- package/dist/report.js.map +1 -0
- package/dist/resource-types.d.ts +9 -0
- package/dist/resource-types.js +19 -0
- package/dist/resource-types.js.map +1 -0
- package/dist/ui.d.ts +50 -0
- package/dist/ui.js +216 -0
- package/dist/ui.js.map +1 -0
- 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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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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"}
|
package/dist/iohost.d.ts
ADDED
|
@@ -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":"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"}
|
|
@@ -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 {};
|
package/dist/pipeline.js
ADDED
|
@@ -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":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EACL,UAAU,EACV,SAAS,EACT,QAAQ,EACR,eAAe,EACf,eAAe,GAEhB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,4BAA4B,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACtG,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAyBxC;;;;;;GAMG;AACH,KAAK,UAAU,aAAa,CAAC,UAAkB,EAAE,GAAa;IAC5D,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;IAClD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAC/C,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC7C,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAEhD,MAAM,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAElD,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,UAAU,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;IAClE,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,0EAA0E,CAAC,CAAC;IAC9F,CAAC;IACD,IAAI,CAAC,cAAc,CAAC,CAAC;IACrB,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC;IACxC,kBAAkB,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IACnC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IACxB,iFAAiF;IACjF,iFAAiF;IACjF,8EAA8E;IAC9E,MAAM,MAAM,GAAG,EAAE,CAAC,GAAG,CAAC,SAAS,KAAK,MAAM,CAAC;IAC3C,MAAM,SAAS,GAAG,MAAM,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,wBAAwB,CAAC,UAAU,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC7G,mFAAmF;IACnF,mFAAmF;IACnF,MAAM,eAAe,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,IAAY,EAAE,EAAE,CAAC,wBAAwB,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC1G,MAAM,OAAO,GAAG,eAAe,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,eAAe,EAAE,CAAC,CAAC;IAC1F,MAAM,QAAQ,GAAG,MAAM,aAAa,CAAC,OAAO,EAAE;QAC5C,QAAQ;QACR,QAAQ;QACR,SAAS,EAAE,EAAE,CAAC,GAAG,CAAC,SAAS;QAC3B,UAAU;KACX,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IAE5C,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;AAC/G,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,UAAkB,EAClB,GAAa,EACb,MAAc;IAEd,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;IAEpG,IAAI,CAAC,cAAc,CAAC,CAAC;IACrB,IAAI,GAAG,CAAC;IACR,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,SAAS,CACnB,EAAE,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,EAAE,EAAE,MAAM,EAAE,EACrF,MAAM,EACN,SAAS,CACV,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,eAAe;YAAE,MAAM,IAAI,KAAK,CAAC,eAAe,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC,CAAC;QAC1F,MAAM,GAAG,CAAC;IACZ,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,KAAK,gBAAgB,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,8EAA8E,CAAC,CAAC;IAClG,CAAC;IAED,IAAI,CAAC,mBAAmB,CAAC,CAAC;IAC1B,oBAAoB,CAAC;QACnB,MAAM,EAAE,SAAS;QACjB,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,QAAQ;QACR,MAAM;QACN,OAAO,EAAE,GAAG,CAAC,OAAO;KACrB,CAAC,CAAC;IAEH,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,CAAC,YAAY,EAAE,SAAS,EAAE,CAAC;AACxG,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,UAAkB,EAClB,GAAa,EACb,MAAc;IAEd,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;IAEpG,IAAI,GAAG,CAAC;IACR,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,QAAQ,CAClB,EAAE,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,EAAE,EAAE,MAAM,EAAE,EACrF,MAAM,EACN,SAAS,CACV,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,eAAe;YAAE,MAAM,IAAI,KAAK,CAAC,eAAe,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC,CAAC;QACzF,MAAM,GAAG,CAAC;IACZ,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,SAAS,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;IAC/D,CAAC;IAED,oBAAoB,CAAC;QACnB,MAAM,EAAE,SAAS;QACjB,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,QAAQ;QACR,MAAM;QACN,OAAO,EAAE,GAAG,CAAC,OAAO;KACrB,CAAC,CAAC;IAEH,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC;AACrF,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,EAAW;IACnC,OAAO,CAAC,GAAG,CACT,EAAE,CAAC,IAAI;QACL,CAAC,CAAC,cAAc,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,2CAA2C;QAChF,CAAC,CAAC,oCAAoC,CACzC,CAAC;IACF,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC,KAAK,CAAC,MAAM,0CAA0C,CAAC,CAAC;IACrF,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC,MAAM,CAAC,MAAM,+BAA+B,CAAC,CAAC;AAC7E,CAAC"}
|
|
@@ -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
|