rastack 0.0.41 → 0.0.43
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/CHANGELOG.md +15 -0
- package/dist/deploy/config.d.ts +23 -7
- package/dist/deploy/config.js +29 -12
- package/dist/deploy/io.d.ts +15 -0
- package/dist/deploy/io.js +32 -2
- package/dist/rastack-bootstrap.d.ts +2 -1
- package/dist/rastack-bootstrap.js +14 -6
- package/dist/rastack-init.d.ts +3 -2
- package/dist/rastack-init.js +33 -40
- package/dist/rastack-update.js +16 -8
- package/dist/templates/cloudformation/app.yaml +635 -0
- package/dist/templates/cloudformation/bootstrap.yaml +342 -0
- package/dist/templates/cloudformation/placeholder/bootstrap +28 -0
- package/dist/update.d.ts +11 -0
- package/dist/update.js +14 -0
- package/package.json +2 -2
- package/scripts/copy-templates.js +42 -0
- package/src/deploy/config.ts +35 -10
- package/src/deploy/io.ts +34 -2
- package/src/rastack-bootstrap.ts +17 -6
- package/src/rastack-init.ts +39 -56
- package/src/rastack-update.ts +22 -9
- package/src/update.ts +14 -0
- package/test/deploy.spec.ts +53 -0
- package/test/update.spec.ts +13 -0
package/src/rastack-bootstrap.ts
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* [--region <r>] [--prefix <p>]
|
|
10
10
|
* [--reuse-oidc] [--print] [--yes]
|
|
11
11
|
*
|
|
12
|
-
* It deploys `
|
|
12
|
+
* It deploys the `bootstrap.yaml` template bundled with the `rastack` package
|
|
13
|
+
* (resolved by `resolveDeployAsset`, so it works from any repo), which creates:
|
|
13
14
|
* • the S3 buckets that hold the Lambda placeholder + hosted templates, and
|
|
14
15
|
* • the GitHub OIDC provider + trust that lets CI assume a deploy role.
|
|
15
16
|
* It then uploads the Lambda placeholder bundle to the artifacts bucket, so the
|
|
@@ -43,6 +44,7 @@ import {
|
|
|
43
44
|
canPrompt,
|
|
44
45
|
gitRemoteUrl,
|
|
45
46
|
loadConfig,
|
|
47
|
+
resolveDeployAsset,
|
|
46
48
|
stackOutput,
|
|
47
49
|
uploadPlaceholder,
|
|
48
50
|
withPrompt,
|
|
@@ -152,8 +154,9 @@ function printPlan(cfg: DeployConfig, reuseOidc: boolean): void {
|
|
|
152
154
|
console.log(
|
|
153
155
|
`\n Bootstrap plan (${bootstrapStackName(cfg)} @ ${cfg.region})\n`,
|
|
154
156
|
);
|
|
157
|
+
const template = resolveDeployAsset("bootstrap.yaml") ?? undefined;
|
|
155
158
|
console.log(" ── Tier-0 bootstrap stack (bootstrap.yaml) ──");
|
|
156
|
-
console.log("\n " + bootstrapStackCommand(cfg, reuseOidc) + "\n");
|
|
159
|
+
console.log("\n " + bootstrapStackCommand(cfg, reuseOidc, template) + "\n");
|
|
157
160
|
console.log(
|
|
158
161
|
" Then `rastack init` (or `rastack bootstrap` again) resolves this stack's\n" +
|
|
159
162
|
" ArtifactsBucket to create app backends. Nothing here is app-specific.\n",
|
|
@@ -166,8 +169,16 @@ function printPlan(cfg: DeployConfig, reuseOidc: boolean): void {
|
|
|
166
169
|
* already ask to reuse it.
|
|
167
170
|
*/
|
|
168
171
|
function deployBootstrap(cfg: DeployConfig, reuseOidc: boolean): void {
|
|
172
|
+
const template = resolveDeployAsset("bootstrap.yaml");
|
|
173
|
+
if (!template) {
|
|
174
|
+
console.error(
|
|
175
|
+
" ✗ bootstrap.yaml template not found in the rastack package. Reinstall\n" +
|
|
176
|
+
" rastack (`npm i -g rastack`) or run from the framework repo.",
|
|
177
|
+
);
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
169
180
|
try {
|
|
170
|
-
awsRun(bootstrapStackArgs(cfg, reuseOidc));
|
|
181
|
+
awsRun(bootstrapStackArgs(cfg, reuseOidc, template));
|
|
171
182
|
} catch (err) {
|
|
172
183
|
if (reuseOidc) {
|
|
173
184
|
console.error(
|
|
@@ -182,7 +193,7 @@ function deployBootstrap(cfg: DeployConfig, reuseOidc: boolean): void {
|
|
|
182
193
|
" • retrying bootstrap, reusing the account's existing GitHub OIDC provider",
|
|
183
194
|
);
|
|
184
195
|
try {
|
|
185
|
-
awsRun(bootstrapStackArgs(cfg, true));
|
|
196
|
+
awsRun(bootstrapStackArgs(cfg, true, template));
|
|
186
197
|
} catch (retryErr) {
|
|
187
198
|
console.error(
|
|
188
199
|
`\n ✗ bootstrap stack deploy failed.\n ${(retryErr as Error).message}`,
|
|
@@ -237,8 +248,8 @@ function standUp(cfg: DeployConfig, reuseOidc: boolean): void {
|
|
|
237
248
|
console.log("\n▸ Uploading the Lambda placeholder bundle");
|
|
238
249
|
if (!uploadPlaceholder(artifactsBucket, cfg.region)) {
|
|
239
250
|
console.error(
|
|
240
|
-
" ✗
|
|
241
|
-
" was not uploaded. `rastack init` will upload it on the first app deploy.",
|
|
251
|
+
" ✗ Lambda placeholder bundle not found in the rastack package — the\n" +
|
|
252
|
+
" placeholder was not uploaded. `rastack init` will upload it on the first app deploy.",
|
|
242
253
|
);
|
|
243
254
|
} else {
|
|
244
255
|
console.log(" ✓ placeholder uploaded");
|
package/src/rastack-init.ts
CHANGED
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
* 1. writes `.rastack/deploy.json` + `.github/workflows/{ci,deploy-backend}.yml`
|
|
13
13
|
* (GitHub coordinates default from the `origin` remote; missing fields are
|
|
14
14
|
* prompted for on a TTY);
|
|
15
|
-
* 2. resolves the tier-0 bootstrap stack's artifacts bucket —
|
|
16
|
-
* bootstrap
|
|
15
|
+
* 2. resolves the tier-0 bootstrap stack's artifacts bucket — erroring with a
|
|
16
|
+
* pointer to `rastack bootstrap` when that account+region stack is missing
|
|
17
|
+
* (init is per-app and never creates account-level infrastructure);
|
|
17
18
|
* 3. uploads the Lambda placeholder bundle to that bucket;
|
|
18
19
|
* 4. deploys the per-app backend (app.yaml) with the resolved ArtifactsBucket;
|
|
19
20
|
* 5. reads the stack Outputs back into the config and prints the two GitHub repo
|
|
@@ -33,8 +34,6 @@ import {
|
|
|
33
34
|
DeployConfig,
|
|
34
35
|
DeployOutputs,
|
|
35
36
|
PartialDeployConfig,
|
|
36
|
-
bootstrapStackArgs,
|
|
37
|
-
bootstrapStackCommand,
|
|
38
37
|
bootstrapStackName,
|
|
39
38
|
deployStackArgs,
|
|
40
39
|
deployStackCommand,
|
|
@@ -56,6 +55,7 @@ import {
|
|
|
56
55
|
ghAvailable,
|
|
57
56
|
gitRemoteUrl,
|
|
58
57
|
loadConfig,
|
|
58
|
+
resolveDeployAsset,
|
|
59
59
|
saveConfig,
|
|
60
60
|
setRepoVariable,
|
|
61
61
|
stackOutput,
|
|
@@ -209,15 +209,18 @@ function printPlan(cfg: DeployConfig): void {
|
|
|
209
209
|
const withBucket = mergeConfig(cfg, {
|
|
210
210
|
artifactsBucket: bucket ?? "<ArtifactsBucketName from bootstrap stack>",
|
|
211
211
|
});
|
|
212
|
+
const appTemplate = resolveDeployAsset("app.yaml") ?? undefined;
|
|
212
213
|
console.log(`\n Deployment plan (${cfg.stackName} @ ${cfg.region})\n`);
|
|
213
214
|
console.log(
|
|
214
|
-
` ──
|
|
215
|
+
` ── Prerequisite: the tier-0 bootstrap stack (${bootstrapStackName(cfg)}) must exist ──`,
|
|
215
216
|
);
|
|
216
|
-
console.log("\n " + bootstrapStackCommand(cfg) + "\n");
|
|
217
|
-
console.log(" ── 2. Per-app backend (app.yaml) ──");
|
|
218
|
-
console.log("\n " + deployStackCommand(withBucket) + "\n");
|
|
219
217
|
console.log(
|
|
220
|
-
|
|
218
|
+
` Create it once per account+region (init does not): rastack bootstrap --region ${cfg.region}\n`,
|
|
219
|
+
);
|
|
220
|
+
console.log(" ── 1. Per-app backend (app.yaml) — what init deploys ──");
|
|
221
|
+
console.log("\n " + deployStackCommand(withBucket, appTemplate) + "\n");
|
|
222
|
+
console.log(
|
|
223
|
+
" ── 2. Wire these GitHub repo Variables from the stack Outputs ──",
|
|
221
224
|
);
|
|
222
225
|
console.log(
|
|
223
226
|
table(
|
|
@@ -246,38 +249,32 @@ async function standUp(
|
|
|
246
249
|
process.exit(1);
|
|
247
250
|
}
|
|
248
251
|
|
|
249
|
-
// 1. Resolve the artifacts bucket
|
|
250
|
-
|
|
252
|
+
// 1. Resolve the artifacts bucket from the tier-0 bootstrap stack. That stack
|
|
253
|
+
// is an account+region once-off — the S3 buckets that host the templates +
|
|
254
|
+
// Lambda placeholder, and the GitHub OIDC provider. `init` is a per-app
|
|
255
|
+
// command and deliberately does NOT create it: standing up account-level
|
|
256
|
+
// infrastructure is `rastack bootstrap`'s job (run once by an account admin),
|
|
257
|
+
// so a missing bootstrap stack is a clear, actionable error, not an implicit
|
|
258
|
+
// account mutation buried inside an app deploy.
|
|
259
|
+
const artifactsBucket = stackOutput(
|
|
251
260
|
bootstrapStackName(cfg),
|
|
252
261
|
BOOTSTRAP_ARTIFACTS_OUTPUT,
|
|
253
262
|
cfg.region,
|
|
254
263
|
);
|
|
255
|
-
|
|
264
|
+
const templatesBucket = stackOutput(
|
|
256
265
|
bootstrapStackName(cfg),
|
|
257
266
|
BOOTSTRAP_TEMPLATES_OUTPUT,
|
|
258
267
|
cfg.region,
|
|
259
268
|
);
|
|
260
269
|
if (!artifactsBucket) {
|
|
261
|
-
console.
|
|
262
|
-
`\n
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
BOOTSTRAP_ARTIFACTS_OUTPUT,
|
|
268
|
-
cfg.region,
|
|
269
|
-
);
|
|
270
|
-
templatesBucket = stackOutput(
|
|
271
|
-
bootstrapStackName(cfg),
|
|
272
|
-
BOOTSTRAP_TEMPLATES_OUTPUT,
|
|
273
|
-
cfg.region,
|
|
270
|
+
console.error(
|
|
271
|
+
`\n ✗ Bootstrap stack ${bootstrapStackName(cfg)} not found in ${cfg.region}.\n` +
|
|
272
|
+
" It is the once-per-account+region step that hosts the CloudFormation\n" +
|
|
273
|
+
" templates and the Lambda placeholder every app deploy reads. Create it\n" +
|
|
274
|
+
" first (an account admin does this once), then re-run `rastack init`:\n\n" +
|
|
275
|
+
` rastack bootstrap --region ${cfg.region}\n`,
|
|
274
276
|
);
|
|
275
|
-
|
|
276
|
-
console.error(
|
|
277
|
-
" ✗ bootstrap stack created but its ArtifactsBucket output could not be read.",
|
|
278
|
-
);
|
|
279
|
-
process.exit(1);
|
|
280
|
-
}
|
|
277
|
+
process.exit(1);
|
|
281
278
|
}
|
|
282
279
|
console.log(` ✓ artifacts bucket: ${artifactsBucket}`);
|
|
283
280
|
|
|
@@ -285,7 +282,7 @@ async function standUp(
|
|
|
285
282
|
console.log("\n▸ Uploading the Lambda placeholder bundle");
|
|
286
283
|
if (!uploadPlaceholder(artifactsBucket, cfg.region)) {
|
|
287
284
|
console.error(
|
|
288
|
-
" ✗
|
|
285
|
+
" ✗ Lambda placeholder bundle not found in the rastack package — cannot create the function.",
|
|
289
286
|
);
|
|
290
287
|
process.exit(1);
|
|
291
288
|
}
|
|
@@ -295,9 +292,17 @@ async function standUp(
|
|
|
295
292
|
artifactsBucket,
|
|
296
293
|
templatesBucket: templatesBucket ?? undefined,
|
|
297
294
|
});
|
|
295
|
+
const appTemplate = resolveDeployAsset("app.yaml");
|
|
296
|
+
if (!appTemplate) {
|
|
297
|
+
console.error(
|
|
298
|
+
" ✗ app.yaml template not found in the rastack package. Reinstall rastack\n" +
|
|
299
|
+
" (`npm i -g rastack`) or run from the framework repo.",
|
|
300
|
+
);
|
|
301
|
+
process.exit(1);
|
|
302
|
+
}
|
|
298
303
|
console.log(`\n▸ Deploying ${cfg.stackName}`);
|
|
299
304
|
try {
|
|
300
|
-
awsRun(deployStackArgs(cfg));
|
|
305
|
+
awsRun(deployStackArgs(cfg, appTemplate));
|
|
301
306
|
} catch (err) {
|
|
302
307
|
console.error(
|
|
303
308
|
`\n ✗ aws cloudformation deploy failed for ${cfg.stackName}.\n ${(err as Error).message}`,
|
|
@@ -370,28 +375,6 @@ function wireRepoVariables(cfg: DeployConfig, interactive: boolean): void {
|
|
|
370
375
|
}
|
|
371
376
|
}
|
|
372
377
|
|
|
373
|
-
/** Deploy the bootstrap stack, retrying once against an existing OIDC provider. */
|
|
374
|
-
function deployBootstrap(cfg: DeployConfig): void {
|
|
375
|
-
try {
|
|
376
|
-
awsRun(bootstrapStackArgs(cfg));
|
|
377
|
-
} catch {
|
|
378
|
-
// The most common cause is an account that already has a GitHub OIDC
|
|
379
|
-
// provider (only one is allowed per URL). Retry telling the template to
|
|
380
|
-
// reuse it instead of creating a second.
|
|
381
|
-
console.log(
|
|
382
|
-
" • retrying bootstrap, reusing the account's existing GitHub OIDC provider",
|
|
383
|
-
);
|
|
384
|
-
try {
|
|
385
|
-
awsRun(bootstrapStackArgs(cfg, true));
|
|
386
|
-
} catch (err) {
|
|
387
|
-
console.error(
|
|
388
|
-
`\n ✗ bootstrap stack deploy failed.\n ${(err as Error).message}`,
|
|
389
|
-
);
|
|
390
|
-
process.exit(1);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
378
|
function printSummary(cfg: DeployConfig): void {
|
|
396
379
|
console.log(
|
|
397
380
|
`\n ✓ ${cfg.app} is deployed (${cfg.stackName} @ ${cfg.region})`,
|
|
@@ -483,7 +466,7 @@ async function main(argv: string[]): Promise<void> {
|
|
|
483
466
|
|
|
484
467
|
if (canPrompt()) {
|
|
485
468
|
const ok = await confirm(
|
|
486
|
-
`Deploy ${cfg.stackName} to AWS ${cfg.region} now? (
|
|
469
|
+
`Deploy ${cfg.stackName} to AWS ${cfg.region} now? (requires the bootstrap stack to exist) [y/N]`,
|
|
487
470
|
);
|
|
488
471
|
if (!ok) {
|
|
489
472
|
console.log(
|
package/src/rastack-update.ts
CHANGED
|
@@ -17,13 +17,14 @@
|
|
|
17
17
|
* global location. Pure decisions live in `update.ts`; this is the IO wrapper.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import { execFileSync } from "child_process";
|
|
20
|
+
import { execFileSync, execSync } from "child_process";
|
|
21
21
|
import { readFileSync } from "fs";
|
|
22
22
|
import * as path from "path";
|
|
23
23
|
import {
|
|
24
24
|
globalPrefixFromPackageDir,
|
|
25
25
|
npmBinary,
|
|
26
26
|
npmInstallArgs,
|
|
27
|
+
npmShellCommand,
|
|
27
28
|
npmSpawnShell,
|
|
28
29
|
parseUpdateFlags,
|
|
29
30
|
updateAvailable,
|
|
@@ -52,15 +53,28 @@ function readVersion(file: string): string {
|
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Run npm and return stdout, or throw on failure. Windows must launch the
|
|
58
|
+
* `npm.cmd` shim through a shell; we pass one command string to `execSync`
|
|
59
|
+
* rather than an args array with `shell: true` (Node 22+ DEP0190 — args would be
|
|
60
|
+
* concatenated unescaped). The only non-literal token, the version, is already
|
|
61
|
+
* sanitized (`sanitizeVersion`). POSIX keeps the shell-free `execFile`.
|
|
62
|
+
*/
|
|
63
|
+
function runNpm(
|
|
64
|
+
args: string[],
|
|
65
|
+
opts: { stdio: "inherit" | ["ignore", "pipe", "pipe"]; env?: NodeJS.ProcessEnv },
|
|
66
|
+
): Buffer | null {
|
|
67
|
+
// nosemgrep: javascript.lang.security.detect-child-process.detect-child-process
|
|
68
|
+
return NPM_SHELL
|
|
69
|
+
? execSync(npmShellCommand(NPM, args), opts)
|
|
70
|
+
: execFileSync(NPM, args, opts);
|
|
71
|
+
}
|
|
72
|
+
|
|
55
73
|
/** Capture `npm <args>` stdout, or null when npm is absent / the call fails. */
|
|
56
74
|
function npmCapture(args: string[]): string | null {
|
|
57
75
|
try {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
shell: NPM_SHELL,
|
|
61
|
-
})
|
|
62
|
-
.toString()
|
|
63
|
-
.trim();
|
|
76
|
+
const out = runNpm(args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
77
|
+
return (out ?? "").toString().trim();
|
|
64
78
|
} catch {
|
|
65
79
|
return null;
|
|
66
80
|
}
|
|
@@ -116,8 +130,7 @@ function main(argv: string[]): void {
|
|
|
116
130
|
// the `.cmd` shim is launched through a shell (EINVAL otherwise); the only
|
|
117
131
|
// caller-supplied token — the version — is sanitized in `parseUpdateFlags`,
|
|
118
132
|
// and every other arg is a fixed literal, so nothing injectable reaches it.
|
|
119
|
-
|
|
120
|
-
execFileSync(NPM, args, { stdio: "inherit", env, shell: NPM_SHELL });
|
|
133
|
+
runNpm(args, { stdio: "inherit", env });
|
|
121
134
|
} catch (err) {
|
|
122
135
|
console.error(
|
|
123
136
|
`\n ✗ npm failed to update rastack. Re-run for detail:\n npm install -g ${npmInstallArgs(flags.version)[2]}\n ${(err as Error).message}`,
|
package/src/update.ts
CHANGED
|
@@ -90,6 +90,20 @@ export function npmSpawnShell(platform: string = process.platform): boolean {
|
|
|
90
90
|
return platform === "win32";
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
/**
|
|
94
|
+
* The single command string used to launch npm on platforms that must go through
|
|
95
|
+
* a shell (Windows — see `npmSpawnShell`). Node 22+ deprecates passing an args
|
|
96
|
+
* *array* together with `shell: true` (DEP0190: the args are concatenated, not
|
|
97
|
+
* escaped), so the shell path builds one string and runs it via `execSync`
|
|
98
|
+
* instead. Joining with spaces is safe here because every token is a fixed
|
|
99
|
+
* literal except the version, which `sanitizeVersion` constrains to
|
|
100
|
+
* `[A-Za-z0-9._-]` (no shell metacharacters). POSIX never uses this — it keeps
|
|
101
|
+
* the shell-free, array-based `execFile`, which has no concatenation at all.
|
|
102
|
+
*/
|
|
103
|
+
export function npmShellCommand(binary: string, args: string[]): string {
|
|
104
|
+
return [binary, ...args].join(" ");
|
|
105
|
+
}
|
|
106
|
+
|
|
93
107
|
/** The npm package spec to install for a target version. */
|
|
94
108
|
export function installSpec(version: string): string {
|
|
95
109
|
return !version || version === "latest" ? "rastack" : `rastack@${version}`;
|
package/test/deploy.spec.ts
CHANGED
|
@@ -243,6 +243,19 @@ describe("stack parameters + launch command", () => {
|
|
|
243
243
|
"deploy",
|
|
244
244
|
]);
|
|
245
245
|
});
|
|
246
|
+
|
|
247
|
+
it("accepts a template-file override (the package-resolved path)", () => {
|
|
248
|
+
// `init` runs in a consumer repo that carries no framework IaC, so it passes
|
|
249
|
+
// the path the template was resolved to inside the `rastack` package.
|
|
250
|
+
const cfg = base({ artifactsBucket: "art" });
|
|
251
|
+
const abs = "/opt/rastack/dist/templates/cloudformation/app.yaml";
|
|
252
|
+
expect(deployStackArgs(cfg, abs)).toContain(abs);
|
|
253
|
+
expect(deployStackCommand(cfg, abs)).toContain(`--template-file ${abs}`);
|
|
254
|
+
// Default is unchanged when no override is given.
|
|
255
|
+
expect(deployStackCommand(cfg)).toContain(
|
|
256
|
+
"--template-file deploy/cloudformation/app.yaml",
|
|
257
|
+
);
|
|
258
|
+
});
|
|
246
259
|
});
|
|
247
260
|
|
|
248
261
|
describe("bootstrap stack", () => {
|
|
@@ -281,6 +294,21 @@ describe("bootstrap stack", () => {
|
|
|
281
294
|
expect(`aws ${bootstrapStackArgs(base()).join(" ")}`).toBe(cmd);
|
|
282
295
|
});
|
|
283
296
|
|
|
297
|
+
it("accepts a template-file override (the package-resolved path)", () => {
|
|
298
|
+
const abs = "/opt/rastack/dist/templates/cloudformation/bootstrap.yaml";
|
|
299
|
+
expect(bootstrapStackArgs(base(), false, abs)).toContain(abs);
|
|
300
|
+
expect(bootstrapStackCommand(base(), true, abs)).toContain(
|
|
301
|
+
`--template-file ${abs}`,
|
|
302
|
+
);
|
|
303
|
+
// Reuse-OIDC + override compose without disturbing the default path.
|
|
304
|
+
expect(bootstrapStackArgs(base(), true, abs)).toContain(
|
|
305
|
+
"CreateOIDCProvider=false",
|
|
306
|
+
);
|
|
307
|
+
expect(bootstrapStackCommand(base())).toContain(
|
|
308
|
+
"--template-file deploy/cloudformation/bootstrap.yaml",
|
|
309
|
+
);
|
|
310
|
+
});
|
|
311
|
+
|
|
284
312
|
it("validates only the account-level fields, not the per-app ones", () => {
|
|
285
313
|
// A config with no `app` is fine for bootstrap — it never touches app.yaml.
|
|
286
314
|
const cfg = normalizeConfig({
|
|
@@ -513,3 +541,28 @@ describe("scaffold", () => {
|
|
|
513
541
|
expect(files.map((f) => f.path)).toEqual([CONFIG_PATH]);
|
|
514
542
|
});
|
|
515
543
|
});
|
|
544
|
+
|
|
545
|
+
describe("resolveDeployAsset — bundled template resolution", () => {
|
|
546
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
547
|
+
const { resolveDeployAsset } = require("../src/deploy/io");
|
|
548
|
+
const path = require("path");
|
|
549
|
+
|
|
550
|
+
it("resolves the framework templates to an existing file", () => {
|
|
551
|
+
for (const name of ["app.yaml", "bootstrap.yaml"]) {
|
|
552
|
+
const resolved = resolveDeployAsset(name);
|
|
553
|
+
expect(resolved).not.toBeNull();
|
|
554
|
+
expect(path.isAbsolute(resolved)).toBe(true);
|
|
555
|
+
expect(resolved.endsWith(name)).toBe(true);
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
it("resolves the Lambda placeholder bundle", () => {
|
|
560
|
+
const resolved = resolveDeployAsset(path.join("placeholder", "bootstrap"));
|
|
561
|
+
expect(resolved).not.toBeNull();
|
|
562
|
+
expect(resolved.endsWith(path.join("placeholder", "bootstrap"))).toBe(true);
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
it("returns null for an asset that does not exist", () => {
|
|
566
|
+
expect(resolveDeployAsset("does-not-exist.yaml")).toBeNull();
|
|
567
|
+
});
|
|
568
|
+
});
|
package/test/update.spec.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
installSpec,
|
|
5
5
|
npmBinary,
|
|
6
6
|
npmInstallArgs,
|
|
7
|
+
npmShellCommand,
|
|
7
8
|
npmSpawnShell,
|
|
8
9
|
parseUpdateFlags,
|
|
9
10
|
sanitizeVersion,
|
|
@@ -67,6 +68,18 @@ describe("update — npm spec + args", () => {
|
|
|
67
68
|
expect(npmSpawnShell("linux")).toBe(false);
|
|
68
69
|
expect(npmSpawnShell("darwin")).toBe(false);
|
|
69
70
|
});
|
|
71
|
+
|
|
72
|
+
it("joins the shell command as one string (avoids the Node DEP0190 warning)", () => {
|
|
73
|
+
// The Windows path runs npm via `execSync(<string>)` rather than
|
|
74
|
+
// `execFileSync(npm, args, { shell: true })`, which Node 22+ deprecates
|
|
75
|
+
// (args concatenated, not escaped). The sanitized version keeps it safe.
|
|
76
|
+
expect(npmShellCommand("npm.cmd", npmInstallArgs("latest"))).toBe(
|
|
77
|
+
"npm.cmd install -g rastack --no-fund --no-audit --loglevel=error",
|
|
78
|
+
);
|
|
79
|
+
expect(npmShellCommand("npm.cmd", npmInstallArgs("0.0.42"))).toContain(
|
|
80
|
+
"rastack@0.0.42",
|
|
81
|
+
);
|
|
82
|
+
});
|
|
70
83
|
});
|
|
71
84
|
|
|
72
85
|
describe("update — version sanitization", () => {
|