@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
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
|
package/dist/aws.js.map
ADDED
|
@@ -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
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
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -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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"}
|