@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
package/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # @alzulejos/laranja
2
+
3
+ The `laranja` command — code-first deploys for Node.js apps to your own AWS account.
4
+
5
+ ```bash
6
+ npm install -D @alzulejos/laranja
7
+ npx laranja deploy
8
+ ```
9
+
10
+ ```
11
+ laranja init sign in + scaffold laranja.config.ts, link a dashboard project
12
+ laranja plan preview the planned resources, diff against the live stack
13
+ laranja deploy deploy into your AWS account
14
+ laranja destroy tear it all down
15
+ laranja logs tail CloudWatch logs for a deployed function
16
+ laranja eject generate an owned CDK project (Pro)
17
+ laranja logout remove the stored API key
18
+
19
+ --stage, -s <name> target a stage (dev/staging/prod); overrides config
20
+ --verbose, -v stream full CDK/CloudFormation output
21
+ ```
22
+
23
+ Each stage is its own CloudFormation stack (`‹name›-‹stage›`), so one repo can
24
+ drive separate dev/staging/prod pipelines — `laranja deploy --stage prod`.
25
+
26
+ Requires Node 20+ and AWS credentials on the standard chain (`aws configure` / SSO / `AWS_*`). The AWS CDK toolkit is embedded — no separate install.
27
+
28
+ 📖 **Full docs:** https://laranja.io/docs
package/dist/aws.d.ts ADDED
@@ -0,0 +1,79 @@
1
+ /** Resolve the AWS account id from the active credentials. */
2
+ export declare function getAccountId(region: string): Promise<string>;
3
+ /** Kind of laranja-generated Lambda, inferred from its CDK logical id. */
4
+ export type LambdaKind = "http" | "cron" | "queue" | "lambda";
5
+ /** A deployed Lambda discovered from the live CloudFormation stack. */
6
+ export interface DeployedLambda {
7
+ kind: LambdaKind;
8
+ /** CloudFormation logical id, e.g. "HttpFn" / "CronnightlyReportFn". */
9
+ logicalId: string;
10
+ /** Physical function name (== CloudWatch log group suffix). */
11
+ functionName: string;
12
+ /** CloudWatch log group, e.g. "/aws/lambda/myapp-app-dev". */
13
+ logGroupName: string;
14
+ }
15
+ /** Infer the kind of laranja Lambda from its CDK logical id prefix. */
16
+ export declare function lambdaKind(logicalId: string): LambdaKind;
17
+ /**
18
+ * List the Lambda functions in a deployed laranja stack by querying the live
19
+ * CloudFormation stack (the durable source of truth — no local state needed).
20
+ * Throws a friendly error if the stack doesn't exist (i.e. nothing deployed).
21
+ */
22
+ export declare function listStackLambdas(region: string, stackName: string): Promise<DeployedLambda[]>;
23
+ /**
24
+ * A laranja *node* Lambda discovered in a deployed stack — one of the functions
25
+ * that maps to a dashboard node (http proxy / cron / queue consumer / worker).
26
+ * Identified by its CDK logical-id prefix, which excludes CDK-internal helper
27
+ * Lambdas (log-retention, custom resources) that share the stack.
28
+ */
29
+ export interface PriorNodeLambda {
30
+ logicalId: string;
31
+ functionName: string;
32
+ }
33
+ export interface StackSnapshot {
34
+ /**
35
+ * Every physical resource id in the stack — used to decide CREATED vs UPDATED
36
+ * (a resource whose pinned physical name is already present is being modified).
37
+ */
38
+ physicalIds: Set<string>;
39
+ /** laranja node Lambdas present before this deploy — used to detect REMOVED. */
40
+ nodeLambdas: PriorNodeLambda[];
41
+ /**
42
+ * Pinned names of the EventBridge schedules present before this deploy — one per
43
+ * logical cron (`<app>-<id>-<stage>`), whether the cron owns its Lambda or shares
44
+ * a worker one. A cron removed from code deletes its schedule but NOT the shared
45
+ * worker Lambda, so the schedule — not the Lambda — is what reveals the removal.
46
+ */
47
+ scheduleNames: Set<string>;
48
+ /**
49
+ * SQS queue names present before this deploy — one per logical queue (`q.name`).
50
+ * Same story as schedules: a grouped queue removed from code deletes its queue
51
+ * while its shared worker Lambda lives on.
52
+ */
53
+ queueNames: Set<string>;
54
+ }
55
+ /**
56
+ * Snapshot a deployed stack BEFORE the next deploy, so the resource report can
57
+ * label each resource CREATED / UPDATED / REMOVED against live AWS state (no
58
+ * local state to persist — the stack itself is the source of truth).
59
+ *
60
+ * laranja pins deterministic physical names — a Lambda's is its `<app>-<label>-<stage>`
61
+ * function name, a dashboard's is `<app>-<stage>` — the same values `report.ts`
62
+ * reconstructs, so membership checks line up. Returns an EMPTY snapshot when the
63
+ * stack doesn't exist yet (first deploy → everything is CREATED) and, best-effort,
64
+ * on any other error so a telemetry hiccup never blocks a deploy.
65
+ */
66
+ export declare function getStackSnapshot(region: string, stackName: string): Promise<StackSnapshot>;
67
+ /**
68
+ * Tear a deployed stack down by name — no synth needed, CloudFormation deletes
69
+ * by stack name. Returns false if there's nothing to delete (no such stack).
70
+ *
71
+ * We grab the stack's unique id first and wait on THAT: once deletion finishes
72
+ * the name no longer resolves, so a name-based waiter can't observe completion.
73
+ */
74
+ export declare function deleteStack(region: string, stackName: string): Promise<boolean>;
75
+ /**
76
+ * Whether the account/region has been CDK-bootstrapped, detected via the SSM
77
+ * version parameter the bootstrap stack writes.
78
+ */
79
+ export declare function isBootstrapped(region: string, qualifier?: string): Promise<boolean>;
package/dist/aws.js ADDED
@@ -0,0 +1,170 @@
1
+ import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
2
+ import { SSMClient, GetParameterCommand } from "@aws-sdk/client-ssm";
3
+ import { CloudFormationClient, ListStackResourcesCommand, DescribeStacksCommand, DeleteStackCommand, waitUntilStackDeleteComplete, } from "@aws-sdk/client-cloudformation";
4
+ /** Resolve the AWS account id from the active credentials. */
5
+ export async function getAccountId(region) {
6
+ const sts = new STSClient({ region });
7
+ let res;
8
+ try {
9
+ res = await sts.send(new GetCallerIdentityCommand({}));
10
+ }
11
+ catch (err) {
12
+ const msg = err instanceof Error ? err.message : String(err);
13
+ throw new Error(`Couldn't authenticate with AWS (${msg}). ` +
14
+ 'Configure credentials (e.g. `aws configure` / SSO) or set "profile" in laranja.config.ts.');
15
+ }
16
+ if (!res.Account) {
17
+ throw new Error("Could not determine the AWS account from your credentials.");
18
+ }
19
+ return res.Account;
20
+ }
21
+ /** Infer the kind of laranja Lambda from its CDK logical id prefix. */
22
+ export function lambdaKind(logicalId) {
23
+ if (logicalId.startsWith("HttpFn"))
24
+ return "http";
25
+ if (logicalId.startsWith("Cron"))
26
+ return "cron";
27
+ if (logicalId.startsWith("Consumer"))
28
+ return "queue";
29
+ return "lambda";
30
+ }
31
+ /**
32
+ * List the Lambda functions in a deployed laranja stack by querying the live
33
+ * CloudFormation stack (the durable source of truth — no local state needed).
34
+ * Throws a friendly error if the stack doesn't exist (i.e. nothing deployed).
35
+ */
36
+ export async function listStackLambdas(region, stackName) {
37
+ const cfn = new CloudFormationClient({ region });
38
+ const out = [];
39
+ let nextToken;
40
+ try {
41
+ do {
42
+ const res = await cfn.send(new ListStackResourcesCommand({ StackName: stackName, NextToken: nextToken }));
43
+ for (const r of res.StackResourceSummaries ?? []) {
44
+ if (r.ResourceType !== "AWS::Lambda::Function" || !r.PhysicalResourceId)
45
+ continue;
46
+ out.push({
47
+ kind: lambdaKind(r.LogicalResourceId ?? ""),
48
+ logicalId: r.LogicalResourceId ?? "",
49
+ functionName: r.PhysicalResourceId,
50
+ logGroupName: `/aws/lambda/${r.PhysicalResourceId}`,
51
+ });
52
+ }
53
+ nextToken = res.NextToken;
54
+ } while (nextToken);
55
+ }
56
+ catch (err) {
57
+ const name = err instanceof Error ? err.name : "";
58
+ if (name === "ValidationError") {
59
+ // CloudFormation returns ValidationError for a non-existent stack.
60
+ throw new Error(`No deployed stack "${stackName}" in ${region}. Run \`laranja deploy\` first.`);
61
+ }
62
+ throw err;
63
+ }
64
+ // Stable, readable order: http first, then crons, then queues.
65
+ const rank = { http: 0, cron: 1, queue: 2, lambda: 3 };
66
+ return out.sort((a, b) => rank[a.kind] - rank[b.kind] || a.functionName.localeCompare(b.functionName));
67
+ }
68
+ /**
69
+ * CDK logical ids for laranja's node Lambdas, from laranja-cdk `stack.ts`:
70
+ * `HttpFn`, `Cron<id>Fn`, `Consumer<id>Fn`, `Worker<id>Fn` (CDK appends a hash
71
+ * suffix, so match by prefix). Nothing CDK creates on its own matches these.
72
+ */
73
+ const NODE_LAMBDA_LOGICAL_ID = /^(HttpFn|Cron.*Fn|Consumer.*Fn|Worker.*Fn)/;
74
+ /**
75
+ * The trailing name segment of a CloudFormation physical id, however the resource
76
+ * type encodes it: a plain name, an ARN (`.../schedule/default/<name>`), a
77
+ * group‑qualified schedule (`default|<name>`), or an SQS URL (`.../<name>`). Our
78
+ * pinned names never contain `/` or `|`, so the last segment is always the name.
79
+ */
80
+ function physicalTail(id) {
81
+ return id.split(/[|/]/).pop() ?? id;
82
+ }
83
+ /**
84
+ * Snapshot a deployed stack BEFORE the next deploy, so the resource report can
85
+ * label each resource CREATED / UPDATED / REMOVED against live AWS state (no
86
+ * local state to persist — the stack itself is the source of truth).
87
+ *
88
+ * laranja pins deterministic physical names — a Lambda's is its `<app>-<label>-<stage>`
89
+ * function name, a dashboard's is `<app>-<stage>` — the same values `report.ts`
90
+ * reconstructs, so membership checks line up. Returns an EMPTY snapshot when the
91
+ * stack doesn't exist yet (first deploy → everything is CREATED) and, best-effort,
92
+ * on any other error so a telemetry hiccup never blocks a deploy.
93
+ */
94
+ export async function getStackSnapshot(region, stackName) {
95
+ const cfn = new CloudFormationClient({ region });
96
+ const physicalIds = new Set();
97
+ const nodeLambdas = [];
98
+ const scheduleNames = new Set();
99
+ const queueNames = new Set();
100
+ let nextToken;
101
+ try {
102
+ do {
103
+ const res = await cfn.send(new ListStackResourcesCommand({ StackName: stackName, NextToken: nextToken }));
104
+ for (const r of res.StackResourceSummaries ?? []) {
105
+ if (r.PhysicalResourceId)
106
+ physicalIds.add(r.PhysicalResourceId);
107
+ if (!r.PhysicalResourceId)
108
+ continue;
109
+ if (r.ResourceType === "AWS::Lambda::Function" && r.LogicalResourceId && NODE_LAMBDA_LOGICAL_ID.test(r.LogicalResourceId)) {
110
+ nodeLambdas.push({ logicalId: r.LogicalResourceId, functionName: r.PhysicalResourceId });
111
+ }
112
+ else if (r.ResourceType === "AWS::Scheduler::Schedule") {
113
+ scheduleNames.add(physicalTail(r.PhysicalResourceId));
114
+ }
115
+ else if (r.ResourceType === "AWS::SQS::Queue") {
116
+ queueNames.add(physicalTail(r.PhysicalResourceId));
117
+ }
118
+ }
119
+ nextToken = res.NextToken;
120
+ } while (nextToken);
121
+ }
122
+ catch {
123
+ // Missing stack (ValidationError) or any transient error → treat as no prior
124
+ // state. Worst case a resource is labelled CREATED instead of UPDATED, which
125
+ // is exactly the pre-existing behaviour, so this never regresses a deploy.
126
+ return { physicalIds: new Set(), nodeLambdas: [], scheduleNames: new Set(), queueNames: new Set() };
127
+ }
128
+ return { physicalIds, nodeLambdas, scheduleNames, queueNames };
129
+ }
130
+ /**
131
+ * Tear a deployed stack down by name — no synth needed, CloudFormation deletes
132
+ * by stack name. Returns false if there's nothing to delete (no such stack).
133
+ *
134
+ * We grab the stack's unique id first and wait on THAT: once deletion finishes
135
+ * the name no longer resolves, so a name-based waiter can't observe completion.
136
+ */
137
+ export async function deleteStack(region, stackName) {
138
+ const cfn = new CloudFormationClient({ region });
139
+ let stackId;
140
+ try {
141
+ const desc = await cfn.send(new DescribeStacksCommand({ StackName: stackName }));
142
+ stackId = desc.Stacks?.[0]?.StackId;
143
+ }
144
+ catch (err) {
145
+ // CloudFormation returns ValidationError for a non-existent stack.
146
+ if (err instanceof Error && err.name === "ValidationError")
147
+ return false;
148
+ throw err;
149
+ }
150
+ await cfn.send(new DeleteStackCommand({ StackName: stackName }));
151
+ await waitUntilStackDeleteComplete({ client: cfn, maxWaitTime: 1800 }, { StackName: stackId ?? stackName });
152
+ return true;
153
+ }
154
+ /**
155
+ * Whether the account/region has been CDK-bootstrapped, detected via the SSM
156
+ * version parameter the bootstrap stack writes.
157
+ */
158
+ export async function isBootstrapped(region, qualifier = "hnb659fds") {
159
+ const ssm = new SSMClient({ region });
160
+ try {
161
+ const res = await ssm.send(new GetParameterCommand({ Name: `/cdk-bootstrap/${qualifier}/version` }));
162
+ return Boolean(res.Parameter?.Value);
163
+ }
164
+ catch (err) {
165
+ if (err instanceof Error && err.name === "ParameterNotFound")
166
+ return false;
167
+ throw err;
168
+ }
169
+ }
170
+ //# sourceMappingURL=aws.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aws.js","sourceRoot":"","sources":["../src/aws.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EAAE,SAAS,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,EACL,oBAAoB,EACpB,yBAAyB,EACzB,qBAAqB,EACrB,kBAAkB,EAClB,4BAA4B,GAC7B,MAAM,gCAAgC,CAAC;AAExC,8DAA8D;AAC9D,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,MAAc;IAC/C,MAAM,GAAG,GAAG,IAAI,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IACtC,IAAI,GAAG,CAAC;IACR,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,wBAAwB,CAAC,EAAE,CAAC,CAAC,CAAC;IACzD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,MAAM,IAAI,KAAK,CACb,mCAAmC,GAAG,KAAK;YACzC,2FAA2F,CAC9F,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAC;IAChF,CAAC;IACD,OAAO,GAAG,CAAC,OAAO,CAAC;AACrB,CAAC;AAgBD,uEAAuE;AACvE,MAAM,UAAU,UAAU,CAAC,SAAiB;IAC1C,IAAI,SAAS,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,MAAM,CAAC;IAClD,IAAI,SAAS,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC;IAChD,IAAI,SAAS,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,OAAO,CAAC;IACrD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,MAAc,EAAE,SAAiB;IACtE,MAAM,GAAG,GAAG,IAAI,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IACjD,MAAM,GAAG,GAAqB,EAAE,CAAC;IACjC,IAAI,SAA6B,CAAC;IAClC,IAAI,CAAC;QACH,GAAG,CAAC;YACF,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,yBAAyB,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;YAC1G,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,sBAAsB,IAAI,EAAE,EAAE,CAAC;gBACjD,IAAI,CAAC,CAAC,YAAY,KAAK,uBAAuB,IAAI,CAAC,CAAC,CAAC,kBAAkB;oBAAE,SAAS;gBAClF,GAAG,CAAC,IAAI,CAAC;oBACP,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,iBAAiB,IAAI,EAAE,CAAC;oBAC3C,SAAS,EAAE,CAAC,CAAC,iBAAiB,IAAI,EAAE;oBACpC,YAAY,EAAE,CAAC,CAAC,kBAAkB;oBAClC,YAAY,EAAE,eAAe,CAAC,CAAC,kBAAkB,EAAE;iBACpD,CAAC,CAAC;YACL,CAAC;YACD,SAAS,GAAG,GAAG,CAAC,SAAS,CAAC;QAC5B,CAAC,QAAQ,SAAS,EAAE;IACtB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QAClD,IAAI,IAAI,KAAK,iBAAiB,EAAE,CAAC;YAC/B,mEAAmE;YACnE,MAAM,IAAI,KAAK,CAAC,sBAAsB,SAAS,QAAQ,MAAM,iCAAiC,CAAC,CAAC;QAClG,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;IACD,+DAA+D;IAC/D,MAAM,IAAI,GAA+B,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IACnF,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC;AACzG,CAAC;AAaD;;;;GAIG;AACH,MAAM,sBAAsB,GAAG,4CAA4C,CAAC;AAyB5E;;;;;GAKG;AACH,SAAS,YAAY,CAAC,EAAU;IAC9B,OAAO,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC;AACtC,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,MAAc,EAAE,SAAiB;IACtE,MAAM,GAAG,GAAG,IAAI,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IACjD,MAAM,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;IACtC,MAAM,WAAW,GAAsB,EAAE,CAAC;IAC1C,MAAM,aAAa,GAAG,IAAI,GAAG,EAAU,CAAC;IACxC,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;IACrC,IAAI,SAA6B,CAAC;IAClC,IAAI,CAAC;QACH,GAAG,CAAC;YACF,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,yBAAyB,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;YAC1G,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,sBAAsB,IAAI,EAAE,EAAE,CAAC;gBACjD,IAAI,CAAC,CAAC,kBAAkB;oBAAE,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC;gBAChE,IAAI,CAAC,CAAC,CAAC,kBAAkB;oBAAE,SAAS;gBACpC,IAAI,CAAC,CAAC,YAAY,KAAK,uBAAuB,IAAI,CAAC,CAAC,iBAAiB,IAAI,sBAAsB,CAAC,IAAI,CAAC,CAAC,CAAC,iBAAiB,CAAC,EAAE,CAAC;oBAC1H,WAAW,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,iBAAiB,EAAE,YAAY,EAAE,CAAC,CAAC,kBAAkB,EAAE,CAAC,CAAC;gBAC3F,CAAC;qBAAM,IAAI,CAAC,CAAC,YAAY,KAAK,0BAA0B,EAAE,CAAC;oBACzD,aAAa,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC;gBACxD,CAAC;qBAAM,IAAI,CAAC,CAAC,YAAY,KAAK,iBAAiB,EAAE,CAAC;oBAChD,UAAU,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC;gBACrD,CAAC;YACH,CAAC;YACD,SAAS,GAAG,GAAG,CAAC,SAAS,CAAC;QAC5B,CAAC,QAAQ,SAAS,EAAE;IACtB,CAAC;IAAC,MAAM,CAAC;QACP,6EAA6E;QAC7E,6EAA6E;QAC7E,2EAA2E;QAC3E,OAAO,EAAE,WAAW,EAAE,IAAI,GAAG,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,aAAa,EAAE,IAAI,GAAG,EAAE,EAAE,UAAU,EAAE,IAAI,GAAG,EAAE,EAAE,CAAC;IACtG,CAAC;IACD,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,aAAa,EAAE,UAAU,EAAE,CAAC;AACjE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAAc,EAAE,SAAiB;IACjE,MAAM,GAAG,GAAG,IAAI,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IAEjD,IAAI,OAA2B,CAAC;IAChC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,qBAAqB,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;QACjF,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC;IACtC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,mEAAmE;QACnE,IAAI,GAAG,YAAY,KAAK,IAAI,GAAG,CAAC,IAAI,KAAK,iBAAiB;YAAE,OAAO,KAAK,CAAC;QACzE,MAAM,GAAG,CAAC;IACZ,CAAC;IAED,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,kBAAkB,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;IACjE,MAAM,4BAA4B,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,OAAO,IAAI,SAAS,EAAE,CAAC,CAAC;IAC5G,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,MAAc,EAAE,SAAS,GAAG,WAAW;IAC1E,MAAM,GAAG,GAAG,IAAI,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IACtC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,mBAAmB,CAAC,EAAE,IAAI,EAAE,kBAAkB,SAAS,UAAU,EAAE,CAAC,CAAC,CAAC;QACrG,OAAO,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IACvC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,KAAK,IAAI,GAAG,CAAC,IAAI,KAAK,mBAAmB;YAAE,OAAO,KAAK,CAAC;QAC3E,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env node
2
+ import path from "node:path";
3
+ import { existsSync, statSync } from "node:fs";
4
+ import { init } from "./commands/init.js";
5
+ import { logout } from "./commands/logout.js";
6
+ import { deploy } from "./commands/deploy.js";
7
+ import { plan } from "./commands/plan.js";
8
+ import { destroy } from "./commands/destroy.js";
9
+ import { eject } from "./commands/eject.js";
10
+ import { logs } from "./commands/logs.js";
11
+ import { beginRun, buildFailureReport, writeFailureReport, sendFailureReport } from "./diagnostics.js";
12
+ import * as ui from "./ui.js";
13
+ const HELP = `laranja — code-first deploy for Node apps
14
+
15
+ Usage:
16
+ laranja <command> [project-dir]
17
+
18
+ Commands:
19
+ init Scaffold a laranja.config.ts (prompts for + stores your API key)
20
+ logout Remove the stored API key (~/.laranja/auth.json)
21
+ plan Preview what a deploy would change (created/changed/unchanged)
22
+ deploy Deploy into your AWS account (uses local credentials)
23
+ destroy Tear down the deployed stack
24
+ logs Tail CloudWatch logs for a deployed function
25
+ eject Generate an owned, editable CDK project (paid)
26
+
27
+ Flags:
28
+ --stage, -s <name> Deployment stage to target, e.g. dev/staging/prod
29
+ (overrides config; deploy/plan/destroy/logs/eject)
30
+ --verbose, -v Show full CDK/CloudFormation output (deploy)
31
+ --strict deploy: fail if any env("...") declared in code has no
32
+ value set locally/in CI (default: deploy + warn)
33
+ --all logs: tail every function (multiplexed)
34
+ --no-follow logs: print recent history and exit (no live tail)
35
+ --since <dur> logs: history look-back, e.g. 30s, 15m, 1h, 2d (default 1h)
36
+
37
+ project-dir defaults to the current directory.
38
+ `;
39
+ /**
40
+ * Pull a `--name <value>` (or alias) flag out of args, returning the value and
41
+ * recording the indices it consumed. Tracking consumed indices lets positional
42
+ * parsing (project-dir, the logs function name) skip a flag's value instead of
43
+ * treating it as a positional. Last occurrence wins.
44
+ */
45
+ function flagValue(args, names, consumed) {
46
+ let value;
47
+ for (let i = 0; i < args.length; i++) {
48
+ if (names.includes(args[i])) {
49
+ value = args[i + 1];
50
+ consumed.add(i);
51
+ consumed.add(i + 1);
52
+ }
53
+ }
54
+ return value;
55
+ }
56
+ async function main() {
57
+ const [command, ...rest] = process.argv.slice(2);
58
+ // Value-flags first, so their values aren't mistaken for positional args.
59
+ const consumed = new Set();
60
+ const stage = flagValue(rest, ["--stage", "-s"], consumed);
61
+ const since = flagValue(rest, ["--since"], consumed);
62
+ const positionals = rest.filter((a, i) => !a.startsWith("-") && !consumed.has(i));
63
+ const projectDir = path.resolve(positionals[0] ?? ".");
64
+ const verbose = rest.includes("--verbose") || rest.includes("-v");
65
+ beginRun(command ?? "(none)", projectDir);
66
+ switch (command) {
67
+ case "init":
68
+ await init(projectDir);
69
+ break;
70
+ case "logout":
71
+ await logout();
72
+ break;
73
+ case "deploy":
74
+ await deploy(projectDir, {
75
+ verbose,
76
+ stage,
77
+ strict: rest.includes("--strict"),
78
+ });
79
+ break;
80
+ case "plan":
81
+ await plan(projectDir, { stage });
82
+ break;
83
+ case "destroy":
84
+ await destroy(projectDir, { stage });
85
+ break;
86
+ case "logs": {
87
+ // Positionals: an existing directory is the project dir; anything else is
88
+ // the function name. So `logs api`, `logs ./app`, and `logs ./app api` all work.
89
+ let dir = ".";
90
+ let name;
91
+ for (const p of positionals) {
92
+ if (existsSync(p) && statSync(p).isDirectory())
93
+ dir = p;
94
+ else
95
+ name = p;
96
+ }
97
+ await logs(path.resolve(dir), {
98
+ name,
99
+ all: rest.includes("--all"),
100
+ follow: !rest.includes("--no-follow"),
101
+ since,
102
+ stage,
103
+ });
104
+ break;
105
+ }
106
+ case "eject":
107
+ await eject(projectDir, { force: rest.includes("--force"), stage });
108
+ break;
109
+ case "help":
110
+ case "--help":
111
+ case "-h":
112
+ case undefined:
113
+ console.log(HELP);
114
+ break;
115
+ default:
116
+ console.error(`Unknown command: ${command}\n`);
117
+ console.log(HELP);
118
+ process.exitCode = 1;
119
+ }
120
+ }
121
+ main().catch(async (err) => {
122
+ const report = buildFailureReport(err);
123
+ const logPath = writeFailureReport(report);
124
+ const sent = await sendFailureReport(report);
125
+ console.error(`\n ${ui.red(`❌ ${report.reason}`)}`);
126
+ console.error(` ${ui.dim(`failed at step: ${report.step}`)}`);
127
+ if (logPath)
128
+ console.error(` ${ui.dim(`report logged to ${logPath}`)}`);
129
+ if (sent)
130
+ console.error(` ${ui.dim("report sent to dashboard")}`);
131
+ console.error(` ${ui.dim("re-run with --verbose for full output")}\n`);
132
+ process.exitCode = 1;
133
+ });
134
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export declare function deploy(projectDir: string, opts?: {
2
+ verbose?: boolean;
3
+ stage?: string;
4
+ strict?: boolean;
5
+ }): Promise<void>;
@@ -0,0 +1,146 @@
1
+ import path from "node:path";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { Toolkit, StackSelectionStrategy, StackParameters, BootstrapEnvironments } from "@aws-cdk/toolkit-lib";
4
+ import { envParamName, handlerLabel, loadConfig, patchDeployment, postDeploymentResources, resolveApiKey, resolveDeclaredEnv, } from "@alzulejos/laranja-core";
5
+ import { buildRemoteAssembly } from "../pipeline.js";
6
+ import { getAccountId, getStackSnapshot, isBootstrapped } from "../aws.js";
7
+ import { buildDeployedResources } from "../report.js";
8
+ import { reportSafely } from "../lifecycle.js";
9
+ import { step, note } from "../diagnostics.js";
10
+ import { applyAwsEnv, confirm, requireRegion } from "../io.js";
11
+ import { LaranjaIoHost, makeActivityHandler } from "../iohost.js";
12
+ import * as ui from "../ui.js";
13
+ export async function deploy(projectDir, opts = {}) {
14
+ const started = Date.now();
15
+ // Synth always happens on the laranja server; we need the API key before
16
+ // touching AWS, so fail fast.
17
+ const apiKey = resolveApiKey();
18
+ if (!apiKey) {
19
+ throw new Error("Set LARANJA_API_KEY (or run `laranja init`) to deploy.");
20
+ }
21
+ // loadConfig raises a clear "run `laranja init`" error if this directory isn't
22
+ // linked yet (empty name/projectId); pipeline enforces projectId before synth.
23
+ step("load config");
24
+ const config = await loadConfig(projectDir, { stage: opts.stage });
25
+ const region = requireRegion(config.region);
26
+ note({ project: config.name, stage: config.stage, region });
27
+ applyAwsEnv({ region, profile: config.profile });
28
+ ui.header(`deploy ${config.name} ${ui.dim(config.stage)} ${ui.dim("→")} ${region}`);
29
+ step("resolve account");
30
+ const account = await getAccountId(region);
31
+ note({ account });
32
+ ui.step("🔑", "account", account);
33
+ // The server synthesizes the template (from the IR + asset hashes we send); we
34
+ // only bundle + fingerprint locally, then deploy with the user's own AWS creds.
35
+ step("server build (scan/bundle/synth)");
36
+ const built = await buildRemoteAssembly(projectDir, { region, account, stage: opts.stage }, apiKey);
37
+ const { ir, stackName, cdkOutDir, deploymentId, projectId } = built;
38
+ note({ deploymentId, stackName });
39
+ const lambdaCount = (ir.http ? 1 : 0) + ir.crons.length + ir.queues.length;
40
+ const routesLabel = ir.http ? `${ir.http.routes.length} routes` : "no http";
41
+ ui.step("📦", "server build", `${routesLabel} · ${ir.crons.length} crons · ${ir.queues.length} queues → ${lambdaCount} λ`);
42
+ // /synth opened the deployment row; report its lifecycle to the dashboard
43
+ // (STARTED before AWS → SUCCESS/FAILED after).
44
+ await reportSafely("report start", () => patchDeployment(deploymentId, { status: "STARTED", region }, apiKey, projectId));
45
+ // Resolve the code-discovered env("...") keys from this machine's process.env.
46
+ // Values are passed to CloudFormation as stack Parameters at deploy time (never
47
+ // baked into the template / IR). Unset keys are left unspecified, so on an
48
+ // update CloudFormation keeps the previous value (UsePreviousValue) and on a
49
+ // first deploy the Parameter default ("") applies.
50
+ const { resolved, missing } = resolveDeclaredEnv(ir.envKeys);
51
+ const envParams = {};
52
+ for (const [key, value] of Object.entries(resolved))
53
+ envParams[envParamName(key)] = value;
54
+ // --strict fails before we deploy; otherwise we deploy and warn at the end
55
+ // (laranja speeds you up, it doesn't babysit).
56
+ if (missing.length && opts.strict) {
57
+ throw new Error(`Missing values for env declared in code: ${missing.join(", ")}.\n` +
58
+ ` Set them in your shell / CI (repo secrets) and re-run, or drop --strict.`);
59
+ }
60
+ const ioHost = new LaranjaIoHost(opts.verbose);
61
+ const toolkit = new Toolkit({ ioHost });
62
+ step("bootstrap check");
63
+ if (!(await isBootstrapped(region))) {
64
+ step("bootstrap");
65
+ ui.step("🥾", "bootstrap", "first deploy to this account/region");
66
+ ui.note("creates a one-time S3 asset bucket + IAM roles in YOUR account");
67
+ if (!(await confirm(" proceed? (y/N)"))) {
68
+ console.log("\n aborted — bootstrap later, then re-run deploy.\n");
69
+ return;
70
+ }
71
+ const sp = ui.spinner("bootstrapping");
72
+ try {
73
+ await toolkit.bootstrap(BootstrapEnvironments.fromList([`aws://${account}/${region}`]));
74
+ sp.succeed("bootstrapped");
75
+ }
76
+ catch (err) {
77
+ sp.fail("bootstrap failed");
78
+ throw err;
79
+ }
80
+ }
81
+ // Snapshot what's already in the stack BEFORE we deploy, so the resource report
82
+ // can label each resource CREATED / UPDATED / REMOVED (empty on a first deploy).
83
+ const priorStack = await getStackSnapshot(region, stackName);
84
+ step("deploy to AWS");
85
+ const cx = await toolkit.fromAssemblyDirectory(cdkOutDir);
86
+ const outputsFile = path.join(projectDir, ".laranja", "outputs.json");
87
+ const sp = ui.spinner("deploying stack");
88
+ ioHost.onActivity = opts.verbose ? undefined : makeActivityHandler(sp);
89
+ try {
90
+ await toolkit.deploy(cx, {
91
+ stacks: { strategy: StackSelectionStrategy.ALL_STACKS },
92
+ // Supply env values; unspecified keys keep their previous value on update.
93
+ parameters: StackParameters.withExisting(envParams),
94
+ outputsFile,
95
+ });
96
+ sp.succeed(`deployed in ${Math.round((Date.now() - started) / 1000)}s`);
97
+ }
98
+ catch (err) {
99
+ sp.fail("deploy failed");
100
+ await reportSafely("report failure", () => patchDeployment(deploymentId, { status: "FAILED" }, apiKey, projectId));
101
+ throw err;
102
+ }
103
+ let out = {};
104
+ if (existsSync(outputsFile)) {
105
+ const outputs = JSON.parse(readFileSync(outputsFile, "utf8"));
106
+ out = outputs[stackName] ?? {};
107
+ console.log();
108
+ if (out.HttpUrl)
109
+ ui.step("🌐", "http", out.HttpUrl);
110
+ if (ir.crons.length) {
111
+ ui.step("⏰", "cron", ir.crons.map((c) => handlerLabel(c)).join(", "));
112
+ }
113
+ if (ir.queues.length)
114
+ ui.step("📨", "queue", ir.queues.map((q) => q.name).join(", "));
115
+ }
116
+ // Report the outcome + deployed inventory to the dashboard (success only POSTs
117
+ // resources, per the lifecycle contract).
118
+ step("report success");
119
+ const resources = buildDeployedResources({
120
+ ir,
121
+ region,
122
+ account,
123
+ outputs: out,
124
+ missingEnv: missing,
125
+ priorPhysicalIds: priorStack.physicalIds,
126
+ priorNodeLambdas: priorStack.nodeLambdas,
127
+ priorScheduleNames: priorStack.scheduleNames,
128
+ priorQueueNames: priorStack.queueNames,
129
+ });
130
+ await reportSafely("report success", () => patchDeployment(deploymentId, { status: "SUCCESS" }, apiKey, projectId));
131
+ await reportSafely("report resources", () => postDeploymentResources(deploymentId, { resources }, apiKey, projectId));
132
+ ui.step("📊", "reported", `${resources.length} resource(s) → dashboard`);
133
+ // Surface teardowns explicitly — a resource dropped from code is gone from AWS
134
+ // after this deploy, so the user should see it, not just infer it from the graph.
135
+ const removed = resources.filter((r) => r.action === "REMOVED");
136
+ if (removed.length) {
137
+ ui.step("🗑️", "removed", removed.map((r) => `${r.name} (${r.type})`).join(", "));
138
+ }
139
+ if (missing.length) {
140
+ console.log();
141
+ ui.warn(`deployed without values for: ${missing.join(", ")}`);
142
+ ui.note("these env vars weren't set locally/in CI — set them and re-run deploy to populate them.");
143
+ }
144
+ console.log(`\n ${ui.orange("✨ live")} ${ui.dim(opts.verbose ? "" : "(run with --verbose for full CDK output)")}\n`);
145
+ }
146
+ //# sourceMappingURL=deploy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deploy.js","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export declare function destroy(projectDir: string, opts?: {
2
+ stage?: string;
3
+ }): Promise<void>;