rastack 0.0.34 → 0.0.36
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 +4 -0
- package/dist/deploy/config.d.ts +33 -2
- package/dist/deploy/config.js +97 -18
- package/dist/deploy/index.d.ts +1 -0
- package/dist/deploy/index.js +1 -0
- package/dist/deploy/io.d.ts +19 -0
- package/dist/deploy/io.js +81 -0
- package/dist/deploy/zip.d.ts +24 -0
- package/dist/deploy/zip.js +95 -0
- package/dist/rastack-ci.js +1 -1
- package/dist/rastack-init.d.ts +18 -7
- package/dist/rastack-init.js +187 -18
- package/dist/rastack-update.d.ts +18 -0
- package/dist/rastack-update.js +139 -0
- package/dist/rastack.d.ts +3 -2
- package/dist/rastack.js +16 -8
- package/dist/update.d.ts +48 -0
- package/dist/update.js +110 -0
- package/package.json +1 -1
- package/src/deploy/config.ts +125 -17
- package/src/deploy/index.ts +1 -0
- package/src/deploy/io.ts +88 -1
- package/src/deploy/zip.ts +109 -0
- package/src/rastack-ci.ts +1 -1
- package/src/rastack-init.ts +271 -21
- package/src/rastack-update.ts +124 -0
- package/src/rastack.ts +16 -8
- package/src/update.ts +114 -0
- package/test/deploy.spec.ts +93 -0
- package/test/update.spec.ts +95 -0
- package/dist/rastack-deploy.d.ts +0 -20
- package/dist/rastack-deploy.js +0 -196
- package/src/rastack-deploy.ts +0 -257
package/dist/rastack-init.js
CHANGED
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
/**
|
|
4
|
-
* `rastack init` —
|
|
4
|
+
* `rastack init` — scaffold a repo's deploy config + GitHub Actions AND stand the
|
|
5
|
+
* serverless backend up.
|
|
5
6
|
*
|
|
6
7
|
* rastack init [--app <slug>] [--owner <o>] [--repo <r>] [--branch <b>]
|
|
7
|
-
* [--region <r>] [--
|
|
8
|
+
* [--region <r>] [--prefix <p>] [--namespace <n>]
|
|
9
|
+
* [--force] [--no-workflows] [--no-deploy] [--print] [--yes]
|
|
8
10
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* This is the one command that takes you from nothing to deployed. It:
|
|
12
|
+
* 1. writes `.rastack/deploy.json` + `.github/workflows/{ci,deploy-backend}.yml`
|
|
13
|
+
* (GitHub coordinates default from the `origin` remote; missing fields are
|
|
14
|
+
* prompted for on a TTY);
|
|
15
|
+
* 2. resolves the tier-0 bootstrap stack's artifacts bucket — and CREATES the
|
|
16
|
+
* bootstrap stack if it does not exist yet;
|
|
17
|
+
* 3. uploads the Lambda placeholder bundle to that bucket;
|
|
18
|
+
* 4. deploys the per-app backend (app.yaml) with the resolved ArtifactsBucket;
|
|
19
|
+
* 5. reads the stack Outputs back into the config and prints the two GitHub repo
|
|
20
|
+
* Variables CI needs.
|
|
21
|
+
*
|
|
22
|
+
* Thereafter the recurring code deploy is CI's job — `rastack ci deploy`, which
|
|
23
|
+
* `rastack deploy` also runs. `--no-deploy` scaffolds only; `--print` shows the
|
|
24
|
+
* exact single-line `aws cloudformation deploy` commands without running them.
|
|
14
25
|
*/
|
|
15
26
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
16
27
|
if (k2 === undefined) k2 = k;
|
|
@@ -55,6 +66,8 @@ function parseFlags(argv) {
|
|
|
55
66
|
const patch = { github: {} };
|
|
56
67
|
let force = false;
|
|
57
68
|
let noWorkflows = false;
|
|
69
|
+
let noDeploy = false;
|
|
70
|
+
let print = false;
|
|
58
71
|
let nonInteractive = false;
|
|
59
72
|
for (let i = 0; i < argv.length; i++) {
|
|
60
73
|
const a = argv[i];
|
|
@@ -78,12 +91,22 @@ function parseFlags(argv) {
|
|
|
78
91
|
case "--prefix":
|
|
79
92
|
patch.namePrefix = next();
|
|
80
93
|
break;
|
|
94
|
+
case "--namespace":
|
|
95
|
+
patch.namespace = next();
|
|
96
|
+
break;
|
|
81
97
|
case "--force":
|
|
82
98
|
force = true;
|
|
83
99
|
break;
|
|
84
100
|
case "--no-workflows":
|
|
85
101
|
noWorkflows = true;
|
|
86
102
|
break;
|
|
103
|
+
case "--no-deploy":
|
|
104
|
+
noDeploy = true;
|
|
105
|
+
break;
|
|
106
|
+
case "--print":
|
|
107
|
+
case "--dry-run":
|
|
108
|
+
print = true;
|
|
109
|
+
break;
|
|
87
110
|
case "--yes":
|
|
88
111
|
case "-y":
|
|
89
112
|
case "--non-interactive":
|
|
@@ -96,7 +119,7 @@ function parseFlags(argv) {
|
|
|
96
119
|
}
|
|
97
120
|
}
|
|
98
121
|
}
|
|
99
|
-
return { patch, force, noWorkflows, nonInteractive };
|
|
122
|
+
return { patch, force, noWorkflows, noDeploy, print, nonInteractive };
|
|
100
123
|
}
|
|
101
124
|
/** Seed a config: existing file → flags → git remote → defaults. */
|
|
102
125
|
function seedConfig(flags) {
|
|
@@ -133,25 +156,156 @@ async function promptMissing(cfg) {
|
|
|
133
156
|
});
|
|
134
157
|
});
|
|
135
158
|
}
|
|
159
|
+
/** Yes/no confirmation with a default of "no" — gates the cloud-mutating steps. */
|
|
160
|
+
async function confirm(question) {
|
|
161
|
+
return (0, io_1.withPrompt)(async (rl) => {
|
|
162
|
+
const answer = await (0, io_1.ask)(rl, question, "no");
|
|
163
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
/** Pad `Key Value` rows into an aligned block. */
|
|
167
|
+
function table(rows) {
|
|
168
|
+
const width = Math.max(0, ...rows.map((r) => r.key.length));
|
|
169
|
+
return rows.map((r) => ` ${r.key.padEnd(width)} ${r.value}`).join("\n");
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Read the app.yaml stack Outputs into a `DeployOutputs`, skipping any that are
|
|
173
|
+
* not resolvable yet (best-effort — a freshly created stack has them all).
|
|
174
|
+
*/
|
|
175
|
+
function readAppOutputs(cfg) {
|
|
176
|
+
const outputs = {};
|
|
177
|
+
for (const { key, field } of config_1.APP_STACK_OUTPUTS) {
|
|
178
|
+
const value = (0, io_1.stackOutput)(cfg.stackName, key, cfg.region);
|
|
179
|
+
if (value)
|
|
180
|
+
outputs[field] = value;
|
|
181
|
+
}
|
|
182
|
+
return outputs;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Print the exact commands `init` would run, without touching the cloud
|
|
186
|
+
* (`--print`). The artifacts bucket is read from the bootstrap stack when the
|
|
187
|
+
* AWS CLI is available, else shown as a placeholder.
|
|
188
|
+
*/
|
|
189
|
+
function printPlan(cfg) {
|
|
190
|
+
const bucket = (0, io_1.awsAvailable)()
|
|
191
|
+
? (0, io_1.stackOutput)((0, config_1.bootstrapStackName)(cfg), config_1.BOOTSTRAP_ARTIFACTS_OUTPUT, cfg.region)
|
|
192
|
+
: null;
|
|
193
|
+
const withBucket = (0, config_1.mergeConfig)(cfg, {
|
|
194
|
+
artifactsBucket: bucket ?? "<ArtifactsBucketName from bootstrap stack>",
|
|
195
|
+
});
|
|
196
|
+
console.log(`\n Deployment plan (${cfg.stackName} @ ${cfg.region})\n`);
|
|
197
|
+
console.log(` ── 1. Bootstrap stack (${(0, config_1.bootstrapStackName)(cfg)}) — run only if it does not exist ──`);
|
|
198
|
+
console.log("\n " + (0, config_1.bootstrapStackCommand)(cfg) + "\n");
|
|
199
|
+
console.log(" ── 2. Per-app backend (app.yaml) ──");
|
|
200
|
+
console.log("\n " + (0, config_1.deployStackCommand)(withBucket) + "\n");
|
|
201
|
+
console.log(" ── 3. Wire these GitHub repo Variables from the stack Outputs ──");
|
|
202
|
+
console.log(table((0, config_1.repoVariables)(cfg).map((v) => ({
|
|
203
|
+
key: v.name,
|
|
204
|
+
value: v.value || `<${v.from}>`,
|
|
205
|
+
}))) + "\n");
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Stand the infrastructure up: ensure the bootstrap stack + artifacts bucket,
|
|
209
|
+
* upload the Lambda placeholder, deploy app.yaml, and read the Outputs back into
|
|
210
|
+
* the config. Returns the config enriched with the resolved buckets + outputs.
|
|
211
|
+
*/
|
|
212
|
+
async function standUp(cfg) {
|
|
213
|
+
if (!(0, io_1.awsAvailable)()) {
|
|
214
|
+
console.error(" ✗ the AWS CLI is not on PATH. Install it and configure credentials,\n" +
|
|
215
|
+
" or re-run with --print to see the commands, or --no-deploy to skip.");
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
// 1. Resolve the artifacts bucket; bootstrap the tier-0 stack if it is absent.
|
|
219
|
+
let artifactsBucket = (0, io_1.stackOutput)((0, config_1.bootstrapStackName)(cfg), config_1.BOOTSTRAP_ARTIFACTS_OUTPUT, cfg.region);
|
|
220
|
+
let templatesBucket = (0, io_1.stackOutput)((0, config_1.bootstrapStackName)(cfg), config_1.BOOTSTRAP_TEMPLATES_OUTPUT, cfg.region);
|
|
221
|
+
if (!artifactsBucket) {
|
|
222
|
+
console.log(`\n▸ Bootstrap stack ${(0, config_1.bootstrapStackName)(cfg)} not found — creating it (once per account+region).`);
|
|
223
|
+
deployBootstrap(cfg);
|
|
224
|
+
artifactsBucket = (0, io_1.stackOutput)((0, config_1.bootstrapStackName)(cfg), config_1.BOOTSTRAP_ARTIFACTS_OUTPUT, cfg.region);
|
|
225
|
+
templatesBucket = (0, io_1.stackOutput)((0, config_1.bootstrapStackName)(cfg), config_1.BOOTSTRAP_TEMPLATES_OUTPUT, cfg.region);
|
|
226
|
+
if (!artifactsBucket) {
|
|
227
|
+
console.error(" ✗ bootstrap stack created but its ArtifactsBucket output could not be read.");
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
console.log(` ✓ artifacts bucket: ${artifactsBucket}`);
|
|
232
|
+
// 2. Upload the placeholder the function is created from.
|
|
233
|
+
console.log("\n▸ Uploading the Lambda placeholder bundle");
|
|
234
|
+
if (!(0, io_1.uploadPlaceholder)(artifactsBucket, cfg.region)) {
|
|
235
|
+
console.error(" ✗ deploy/cloudformation/placeholder/bootstrap not found — cannot create the function.");
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
// 3. Deploy the per-app backend with the resolved bucket wired in.
|
|
239
|
+
cfg = (0, config_1.mergeConfig)(cfg, {
|
|
240
|
+
artifactsBucket,
|
|
241
|
+
templatesBucket: templatesBucket ?? undefined,
|
|
242
|
+
});
|
|
243
|
+
console.log(`\n▸ Deploying ${cfg.stackName}`);
|
|
244
|
+
try {
|
|
245
|
+
(0, io_1.awsRun)((0, config_1.deployStackArgs)(cfg));
|
|
246
|
+
}
|
|
247
|
+
catch (err) {
|
|
248
|
+
console.error(`\n ✗ aws cloudformation deploy failed for ${cfg.stackName}.\n ${err.message}`);
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
251
|
+
// 4. Read the Outputs back into the config so CI has them without a live lookup.
|
|
252
|
+
const outputs = readAppOutputs(cfg);
|
|
253
|
+
cfg = (0, config_1.mergeConfig)(cfg, { outputs });
|
|
254
|
+
(0, io_1.saveConfig)(cfg);
|
|
255
|
+
return cfg;
|
|
256
|
+
}
|
|
257
|
+
/** Deploy the bootstrap stack, retrying once against an existing OIDC provider. */
|
|
258
|
+
function deployBootstrap(cfg) {
|
|
259
|
+
try {
|
|
260
|
+
(0, io_1.awsRun)((0, config_1.bootstrapStackArgs)(cfg));
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
// The most common cause is an account that already has a GitHub OIDC
|
|
264
|
+
// provider (only one is allowed per URL). Retry telling the template to
|
|
265
|
+
// reuse it instead of creating a second.
|
|
266
|
+
console.log(" • retrying bootstrap, reusing the account's existing GitHub OIDC provider");
|
|
267
|
+
try {
|
|
268
|
+
(0, io_1.awsRun)((0, config_1.bootstrapStackArgs)(cfg, true));
|
|
269
|
+
}
|
|
270
|
+
catch (err) {
|
|
271
|
+
console.error(`\n ✗ bootstrap stack deploy failed.\n ${err.message}`);
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
function printSummary(cfg) {
|
|
277
|
+
console.log(`\n ✓ ${cfg.app} is deployed (${cfg.stackName} @ ${cfg.region})`);
|
|
278
|
+
if (cfg.outputs?.apiUrl)
|
|
279
|
+
console.log(` API: ${cfg.outputs.apiUrl}`);
|
|
280
|
+
console.log("\n Wire these GitHub repo Variables so CI can ship code (Settings → Variables):");
|
|
281
|
+
console.log(table((0, config_1.repoVariables)(cfg).map((v) => ({
|
|
282
|
+
key: v.name,
|
|
283
|
+
value: v.value || `<${v.from}>`,
|
|
284
|
+
}))));
|
|
285
|
+
console.log(`\n Then every push to ${cfg.github.branch} ships code via \`rastack ci deploy\`` +
|
|
286
|
+
` (also runnable locally as \`rastack deploy\`).\n`);
|
|
287
|
+
}
|
|
136
288
|
async function main(argv) {
|
|
137
289
|
const flags = parseFlags(argv);
|
|
138
290
|
let cfg = seedConfig(flags);
|
|
139
291
|
if ((0, io_1.canPrompt)(flags.nonInteractive)) {
|
|
140
|
-
console.log("\n rastack init — scaffold the deploy config +
|
|
292
|
+
console.log("\n rastack init — scaffold the deploy config + stand the backend up\n");
|
|
141
293
|
cfg = await promptMissing(cfg);
|
|
142
294
|
}
|
|
143
295
|
const errors = (0, config_1.validateConfig)(cfg);
|
|
144
|
-
//
|
|
145
|
-
//
|
|
296
|
+
// Any deploy needs a complete config; a still-incomplete one is fatal unless we
|
|
297
|
+
// are only scaffolding files.
|
|
146
298
|
if (errors.length) {
|
|
147
|
-
const
|
|
299
|
+
const fatal = !flags.noDeploy || flags.nonInteractive;
|
|
300
|
+
const label = fatal ? "✗" : "⚠";
|
|
148
301
|
for (const e of errors)
|
|
149
302
|
console.error(` ${label} ${e}`);
|
|
150
|
-
if (
|
|
151
|
-
console.error(`\n ${errors.length} problem(s). Pass the missing values as flags.`);
|
|
303
|
+
if (fatal) {
|
|
304
|
+
console.error(`\n ${errors.length} problem(s). Pass the missing values as flags, or --no-deploy to scaffold only.`);
|
|
152
305
|
process.exit(1);
|
|
153
306
|
}
|
|
154
307
|
}
|
|
308
|
+
// 1. Scaffold the config + workflows.
|
|
155
309
|
const files = (0, workflows_1.scaffoldFiles)(cfg, { noWorkflows: flags.noWorkflows });
|
|
156
310
|
const written = [];
|
|
157
311
|
const skipped = [];
|
|
@@ -170,10 +324,25 @@ async function main(argv) {
|
|
|
170
324
|
console.log(` ✓ wrote ${w}`);
|
|
171
325
|
for (const s of skipped)
|
|
172
326
|
console.log(` • skipped ${s} (exists — pass --force to overwrite)`);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
327
|
+
// 2. Deploy (unless opted out).
|
|
328
|
+
if (flags.print) {
|
|
329
|
+
printPlan(cfg);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
if (flags.noDeploy) {
|
|
333
|
+
console.log(`\n Config scaffolded. Run \`rastack init\` again (without --no-deploy) to deploy,\n` +
|
|
334
|
+
` or \`rastack init --print\` to see the exact commands.\n`);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if ((0, io_1.canPrompt)(flags.nonInteractive)) {
|
|
338
|
+
const ok = await confirm(`Deploy ${cfg.stackName} to AWS ${cfg.region} now? (creates the bootstrap stack if needed) [y/N]`);
|
|
339
|
+
if (!ok) {
|
|
340
|
+
console.log("\n Skipped the deploy. Re-run to deploy, or `rastack init --print` for the commands.\n");
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
cfg = await standUp(cfg);
|
|
345
|
+
printSummary(cfg);
|
|
177
346
|
}
|
|
178
347
|
if (require.main === module) {
|
|
179
348
|
main(process.argv.slice(2)).catch((err) => {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `rastack update` — update the CLI in place.
|
|
4
|
+
*
|
|
5
|
+
* rastack update [<version>] [--version <v>] [--check] [--dry-run]
|
|
6
|
+
*
|
|
7
|
+
* (no args) update to the latest published version
|
|
8
|
+
* <version> pin a target, e.g. `rastack update 0.0.40`
|
|
9
|
+
* --check report current vs. latest, install nothing
|
|
10
|
+
* --dry-run print the npm command that would run, don't run it
|
|
11
|
+
*
|
|
12
|
+
* `rastack` is a global npm package installed into a self-contained per-user
|
|
13
|
+
* prefix (`~/.rastack`, via the hosted installer). Updating re-runs
|
|
14
|
+
* `npm install -g rastack@<target>` against that SAME prefix — derived from where
|
|
15
|
+
* the running CLI lives — so it never scatters a second copy into npm's default
|
|
16
|
+
* global location. Pure decisions live in `update.ts`; this is the IO wrapper.
|
|
17
|
+
*/
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* `rastack update` — update the CLI in place.
|
|
5
|
+
*
|
|
6
|
+
* rastack update [<version>] [--version <v>] [--check] [--dry-run]
|
|
7
|
+
*
|
|
8
|
+
* (no args) update to the latest published version
|
|
9
|
+
* <version> pin a target, e.g. `rastack update 0.0.40`
|
|
10
|
+
* --check report current vs. latest, install nothing
|
|
11
|
+
* --dry-run print the npm command that would run, don't run it
|
|
12
|
+
*
|
|
13
|
+
* `rastack` is a global npm package installed into a self-contained per-user
|
|
14
|
+
* prefix (`~/.rastack`, via the hosted installer). Updating re-runs
|
|
15
|
+
* `npm install -g rastack@<target>` against that SAME prefix — derived from where
|
|
16
|
+
* the running CLI lives — so it never scatters a second copy into npm's default
|
|
17
|
+
* global location. Pure decisions live in `update.ts`; this is the IO wrapper.
|
|
18
|
+
*/
|
|
19
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
22
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
23
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
24
|
+
}
|
|
25
|
+
Object.defineProperty(o, k2, desc);
|
|
26
|
+
}) : (function(o, m, k, k2) {
|
|
27
|
+
if (k2 === undefined) k2 = k;
|
|
28
|
+
o[k2] = m[k];
|
|
29
|
+
}));
|
|
30
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
31
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
32
|
+
}) : function(o, v) {
|
|
33
|
+
o["default"] = v;
|
|
34
|
+
});
|
|
35
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
36
|
+
var ownKeys = function(o) {
|
|
37
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
38
|
+
var ar = [];
|
|
39
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
40
|
+
return ar;
|
|
41
|
+
};
|
|
42
|
+
return ownKeys(o);
|
|
43
|
+
};
|
|
44
|
+
return function (mod) {
|
|
45
|
+
if (mod && mod.__esModule) return mod;
|
|
46
|
+
var result = {};
|
|
47
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
48
|
+
__setModuleDefault(result, mod);
|
|
49
|
+
return result;
|
|
50
|
+
};
|
|
51
|
+
})();
|
|
52
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
|
+
const child_process_1 = require("child_process");
|
|
54
|
+
const fs_1 = require("fs");
|
|
55
|
+
const path = __importStar(require("path"));
|
|
56
|
+
const update_1 = require("./update");
|
|
57
|
+
/** The installed rastack package directory (…/rastack), one up from dist/. */
|
|
58
|
+
const PKG_DIR = path.join(__dirname, "..");
|
|
59
|
+
const PKG_JSON = path.join(PKG_DIR, "package.json");
|
|
60
|
+
/** Read the version from a package.json on disk, or "unknown" if unreadable. */
|
|
61
|
+
function readVersion(file) {
|
|
62
|
+
try {
|
|
63
|
+
return JSON.parse((0, fs_1.readFileSync)(file, "utf-8")).version ?? "unknown";
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return "unknown";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** Capture `npm <args>` stdout, or null when npm is absent / the call fails. */
|
|
70
|
+
function npmCapture(args) {
|
|
71
|
+
try {
|
|
72
|
+
return (0, child_process_1.execFileSync)("npm", args, {
|
|
73
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
74
|
+
})
|
|
75
|
+
.toString()
|
|
76
|
+
.trim();
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function main(argv) {
|
|
83
|
+
const flags = (0, update_1.parseUpdateFlags)(argv);
|
|
84
|
+
const current = readVersion(PKG_JSON);
|
|
85
|
+
const prefix = (0, update_1.globalPrefixFromPackageDir)(PKG_DIR);
|
|
86
|
+
// The npm prefix must land the update where this CLI actually lives.
|
|
87
|
+
const env = { ...process.env };
|
|
88
|
+
if (prefix)
|
|
89
|
+
env.npm_config_prefix = prefix;
|
|
90
|
+
if (flags.check) {
|
|
91
|
+
const latest = npmCapture(["view", "rastack@latest", "version"]);
|
|
92
|
+
if (!latest) {
|
|
93
|
+
console.error(" ✗ could not reach the npm registry to check for updates.");
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
console.log(` rastack ${current} (installed) — latest is ${latest}`);
|
|
97
|
+
if ((0, update_1.updateAvailable)(current, latest)) {
|
|
98
|
+
console.log(` ▸ run \`rastack update\` to upgrade to ${latest}.`);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
console.log(" ✓ you are on the latest version.");
|
|
102
|
+
}
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const args = (0, update_1.npmInstallArgs)(flags.version);
|
|
106
|
+
if (flags.dryRun) {
|
|
107
|
+
const pfx = prefix ? `npm_config_prefix=${prefix} ` : "";
|
|
108
|
+
console.log(`\n ${pfx}npm ${args.join(" ")}\n`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (!prefix) {
|
|
112
|
+
// Running from a source checkout (not a global install) — nothing to self-update.
|
|
113
|
+
console.error(" ✗ rastack is not running from a global install, so `update` does not apply.\n" +
|
|
114
|
+
" Reinstall with the hosted installer, or `npm install -g rastack` to manage it.");
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
console.log(`\n▸ Updating rastack (${current} → ${flags.version}) in ${prefix}\n`);
|
|
118
|
+
try {
|
|
119
|
+
// Re-runs the developer's own npm to update this global package — no
|
|
120
|
+
// untrusted input, and execFileSync (no shell) avoids any injection.
|
|
121
|
+
// nosemgrep: javascript.lang.security.detect-child-process.detect-child-process
|
|
122
|
+
(0, child_process_1.execFileSync)("npm", args, { stdio: "inherit", env });
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
console.error(`\n ✗ npm failed to update rastack. Re-run for detail:\n npm install -g ${(0, update_1.npmInstallArgs)(flags.version)[2]}\n ${err.message}`);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
// Re-read the (now replaced) package.json to report where we landed.
|
|
129
|
+
const updated = readVersion(PKG_JSON);
|
|
130
|
+
if (updated === current) {
|
|
131
|
+
console.log(`\n ✓ rastack is up to date (${updated}).\n`);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
console.log(`\n ✓ rastack updated ${current} → ${updated}.\n`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (require.main === module) {
|
|
138
|
+
main(process.argv.slice(2));
|
|
139
|
+
}
|
package/dist/rastack.d.ts
CHANGED
|
@@ -15,8 +15,9 @@
|
|
|
15
15
|
* rastack wasm Compile the API to WebAssembly (in-browser local dev)
|
|
16
16
|
* rastack scan [files...] [--fail-on-pii] Scan CSVs for PII and schema info
|
|
17
17
|
* rastack seed [--env dev] [--source gs://] Load CSVs into Iceberg namespace
|
|
18
|
-
* rastack init [--app slug] [--force]
|
|
19
|
-
* rastack deploy [--
|
|
18
|
+
* rastack init [--app slug] [--force] Scaffold config + GitHub Actions AND stand the backend up
|
|
19
|
+
* rastack deploy [--plan] Ship code to the Lambda (alias for `rastack ci deploy`)
|
|
20
20
|
* rastack ci <check|build|deploy> [--plan] Run the CI pipeline (keeps Actions a one-liner)
|
|
21
|
+
* rastack update [<version>] [--check] Update the CLI in place (self-update)
|
|
21
22
|
*/
|
|
22
23
|
export {};
|
package/dist/rastack.js
CHANGED
|
@@ -16,9 +16,10 @@
|
|
|
16
16
|
* rastack wasm Compile the API to WebAssembly (in-browser local dev)
|
|
17
17
|
* rastack scan [files...] [--fail-on-pii] Scan CSVs for PII and schema info
|
|
18
18
|
* rastack seed [--env dev] [--source gs://] Load CSVs into Iceberg namespace
|
|
19
|
-
* rastack init [--app slug] [--force]
|
|
20
|
-
* rastack deploy [--
|
|
19
|
+
* rastack init [--app slug] [--force] Scaffold config + GitHub Actions AND stand the backend up
|
|
20
|
+
* rastack deploy [--plan] Ship code to the Lambda (alias for `rastack ci deploy`)
|
|
21
21
|
* rastack ci <check|build|deploy> [--plan] Run the CI pipeline (keeps Actions a one-liner)
|
|
22
|
+
* rastack update [<version>] [--check] Update the CLI in place (self-update)
|
|
22
23
|
*/
|
|
23
24
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
24
25
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
@@ -113,17 +114,23 @@ switch (command) {
|
|
|
113
114
|
run("seed.js");
|
|
114
115
|
break;
|
|
115
116
|
case "init":
|
|
116
|
-
//
|
|
117
|
+
// Scaffold config + GitHub Actions and stand the serverless backend up.
|
|
117
118
|
run("rastack-init.js", rest);
|
|
118
119
|
break;
|
|
119
120
|
case "deploy":
|
|
120
|
-
//
|
|
121
|
-
|
|
121
|
+
// Ship code to the Lambda — an alias for the CI code deploy. Infrastructure
|
|
122
|
+
// is stood up once by `rastack init`; `deploy` only ever ships code.
|
|
123
|
+
run("rastack-ci.js", ["deploy", ...rest]);
|
|
122
124
|
break;
|
|
123
125
|
case "ci":
|
|
124
126
|
// Run a CI pipeline phase (check | build | deploy).
|
|
125
127
|
run("rastack-ci.js", rest);
|
|
126
128
|
break;
|
|
129
|
+
case "update":
|
|
130
|
+
case "upgrade":
|
|
131
|
+
// Self-update the globally-installed CLI in place.
|
|
132
|
+
run("rastack-update.js", rest);
|
|
133
|
+
break;
|
|
127
134
|
case "generate":
|
|
128
135
|
case "gen":
|
|
129
136
|
// Proxy to existing rastack codegen pipeline
|
|
@@ -141,8 +148,9 @@ switch (command) {
|
|
|
141
148
|
` rastack wasm Compile the API to WebAssembly (browser local dev)\n` +
|
|
142
149
|
` rastack scan [files...] [--fail-on-pii]\n` +
|
|
143
150
|
` rastack seed [--env dev|staging] [--source gs://bucket/ | ./myapp-dev/]\n` +
|
|
144
|
-
` rastack init [--app slug] [--owner o] [--repo r] [--
|
|
145
|
-
` rastack deploy [--
|
|
146
|
-
` rastack ci <check|build|deploy> [--plan] Run the CI pipeline (cleaner Actions)\n`
|
|
151
|
+
` rastack init [--app slug] [--owner o] [--repo r] [--print] [--no-deploy] Scaffold + stand the backend up\n` +
|
|
152
|
+
` rastack deploy [--plan] Ship code to the Lambda (alias for ci deploy)\n` +
|
|
153
|
+
` rastack ci <check|build|deploy> [--plan] Run the CI pipeline (cleaner Actions)\n` +
|
|
154
|
+
` rastack update [<version>] [--check] [--dry-run] Update the CLI in place (self-update)\n`);
|
|
147
155
|
process.exit(1);
|
|
148
156
|
}
|
package/dist/update.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure core of `rastack update` — the CLI's self-updater.
|
|
3
|
+
*
|
|
4
|
+
* `rastack` is installed with `npm install -g rastack` into a self-contained
|
|
5
|
+
* per-user prefix (`~/.rastack`, wired by web/public/setup.{sh,ps1}), so updating
|
|
6
|
+
* is just re-running that global install against the SAME prefix the running CLI
|
|
7
|
+
* lives in — then reporting the version change. Everything decidable without IO
|
|
8
|
+
* lives here (flag parsing, the npm argv, deriving the prefix from the install
|
|
9
|
+
* path, semver comparison) so it is unit-tested without npm or the network;
|
|
10
|
+
* `rastack-update.ts` is the thin wrapper that shells out.
|
|
11
|
+
*/
|
|
12
|
+
export interface UpdateFlags {
|
|
13
|
+
/** `latest`, or a pinned semver like `0.0.40`. */
|
|
14
|
+
version: string;
|
|
15
|
+
/** Only report current vs. latest — do not install. */
|
|
16
|
+
check: boolean;
|
|
17
|
+
/** Print the npm command that would run, without running it. */
|
|
18
|
+
dryRun: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Parse `rastack update` args. Accepts `--check`, `--dry-run`/`--print`, and a
|
|
22
|
+
* version either as `--version <v>` or as a bare positional (`rastack update
|
|
23
|
+
* 0.0.40`). Unknown values default to `latest`.
|
|
24
|
+
*/
|
|
25
|
+
export declare function parseUpdateFlags(argv: string[]): UpdateFlags;
|
|
26
|
+
/** The npm package spec to install for a target version. */
|
|
27
|
+
export declare function installSpec(version: string): string;
|
|
28
|
+
/** The `npm <args>` (after the `npm` binary) that installs the target globally. */
|
|
29
|
+
export declare function npmInstallArgs(version: string): string[];
|
|
30
|
+
/**
|
|
31
|
+
* Derive the npm global prefix from the installed rastack package directory, so
|
|
32
|
+
* the update lands in the SAME location the running CLI was installed to (not
|
|
33
|
+
* npm's default global prefix, which may differ from the per-user `~/.rastack`).
|
|
34
|
+
*
|
|
35
|
+
* Global installs put the package at `<prefix>/lib/node_modules/rastack` (POSIX)
|
|
36
|
+
* or `<prefix>/node_modules/rastack` (Windows). Returns null when rastack is not
|
|
37
|
+
* under a `node_modules` at all — e.g. running from a source checkout — where
|
|
38
|
+
* self-update does not apply.
|
|
39
|
+
*/
|
|
40
|
+
export declare function globalPrefixFromPackageDir(pkgDir: string): string | null;
|
|
41
|
+
/**
|
|
42
|
+
* Compare two dotted versions numerically (major.minor.patch; any pre-release
|
|
43
|
+
* suffix is ignored). Returns -1 if a < b, 1 if a > b, 0 if equal — enough to
|
|
44
|
+
* tell `--check` whether an update is available.
|
|
45
|
+
*/
|
|
46
|
+
export declare function compareSemver(a: string, b: string): number;
|
|
47
|
+
/** True when `latest` is a strictly newer version than `current`. */
|
|
48
|
+
export declare function updateAvailable(current: string, latest: string): boolean;
|
package/dist/update.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Pure core of `rastack update` — the CLI's self-updater.
|
|
4
|
+
*
|
|
5
|
+
* `rastack` is installed with `npm install -g rastack` into a self-contained
|
|
6
|
+
* per-user prefix (`~/.rastack`, wired by web/public/setup.{sh,ps1}), so updating
|
|
7
|
+
* is just re-running that global install against the SAME prefix the running CLI
|
|
8
|
+
* lives in — then reporting the version change. Everything decidable without IO
|
|
9
|
+
* lives here (flag parsing, the npm argv, deriving the prefix from the install
|
|
10
|
+
* path, semver comparison) so it is unit-tested without npm or the network;
|
|
11
|
+
* `rastack-update.ts` is the thin wrapper that shells out.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.parseUpdateFlags = parseUpdateFlags;
|
|
15
|
+
exports.installSpec = installSpec;
|
|
16
|
+
exports.npmInstallArgs = npmInstallArgs;
|
|
17
|
+
exports.globalPrefixFromPackageDir = globalPrefixFromPackageDir;
|
|
18
|
+
exports.compareSemver = compareSemver;
|
|
19
|
+
exports.updateAvailable = updateAvailable;
|
|
20
|
+
/**
|
|
21
|
+
* Parse `rastack update` args. Accepts `--check`, `--dry-run`/`--print`, and a
|
|
22
|
+
* version either as `--version <v>` or as a bare positional (`rastack update
|
|
23
|
+
* 0.0.40`). Unknown values default to `latest`.
|
|
24
|
+
*/
|
|
25
|
+
function parseUpdateFlags(argv) {
|
|
26
|
+
let version = "latest";
|
|
27
|
+
let check = false;
|
|
28
|
+
let dryRun = false;
|
|
29
|
+
for (let i = 0; i < argv.length; i++) {
|
|
30
|
+
const a = argv[i];
|
|
31
|
+
switch (a) {
|
|
32
|
+
case "--check":
|
|
33
|
+
check = true;
|
|
34
|
+
break;
|
|
35
|
+
case "--dry-run":
|
|
36
|
+
case "--print":
|
|
37
|
+
dryRun = true;
|
|
38
|
+
break;
|
|
39
|
+
case "--version":
|
|
40
|
+
case "--to":
|
|
41
|
+
version = argv[++i] ?? "latest";
|
|
42
|
+
break;
|
|
43
|
+
default:
|
|
44
|
+
// A bare, non-flag token is taken as the target version.
|
|
45
|
+
if (!a.startsWith("-"))
|
|
46
|
+
version = a;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return { version, check, dryRun };
|
|
50
|
+
}
|
|
51
|
+
/** The npm package spec to install for a target version. */
|
|
52
|
+
function installSpec(version) {
|
|
53
|
+
return !version || version === "latest" ? "rastack" : `rastack@${version}`;
|
|
54
|
+
}
|
|
55
|
+
/** The `npm <args>` (after the `npm` binary) that installs the target globally. */
|
|
56
|
+
function npmInstallArgs(version) {
|
|
57
|
+
return [
|
|
58
|
+
"install",
|
|
59
|
+
"-g",
|
|
60
|
+
installSpec(version),
|
|
61
|
+
"--no-fund",
|
|
62
|
+
"--no-audit",
|
|
63
|
+
"--loglevel=error",
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Derive the npm global prefix from the installed rastack package directory, so
|
|
68
|
+
* the update lands in the SAME location the running CLI was installed to (not
|
|
69
|
+
* npm's default global prefix, which may differ from the per-user `~/.rastack`).
|
|
70
|
+
*
|
|
71
|
+
* Global installs put the package at `<prefix>/lib/node_modules/rastack` (POSIX)
|
|
72
|
+
* or `<prefix>/node_modules/rastack` (Windows). Returns null when rastack is not
|
|
73
|
+
* under a `node_modules` at all — e.g. running from a source checkout — where
|
|
74
|
+
* self-update does not apply.
|
|
75
|
+
*/
|
|
76
|
+
function globalPrefixFromPackageDir(pkgDir) {
|
|
77
|
+
const norm = pkgDir.replace(/\\/g, "/");
|
|
78
|
+
// Length-preserving normalization, so indices map back onto the original.
|
|
79
|
+
const posix = norm.lastIndexOf("/lib/node_modules/");
|
|
80
|
+
if (posix !== -1)
|
|
81
|
+
return pkgDir.slice(0, posix);
|
|
82
|
+
const win = norm.lastIndexOf("/node_modules/");
|
|
83
|
+
if (win !== -1)
|
|
84
|
+
return pkgDir.slice(0, win);
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Compare two dotted versions numerically (major.minor.patch; any pre-release
|
|
89
|
+
* suffix is ignored). Returns -1 if a < b, 1 if a > b, 0 if equal — enough to
|
|
90
|
+
* tell `--check` whether an update is available.
|
|
91
|
+
*/
|
|
92
|
+
function compareSemver(a, b) {
|
|
93
|
+
const parts = (v) => v
|
|
94
|
+
.replace(/^v/, "")
|
|
95
|
+
.split("-")[0]
|
|
96
|
+
.split(".")
|
|
97
|
+
.map((n) => parseInt(n, 10) || 0);
|
|
98
|
+
const pa = parts(a);
|
|
99
|
+
const pb = parts(b);
|
|
100
|
+
for (let i = 0; i < 3; i++) {
|
|
101
|
+
const d = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
102
|
+
if (d !== 0)
|
|
103
|
+
return d < 0 ? -1 : 1;
|
|
104
|
+
}
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
107
|
+
/** True when `latest` is a strictly newer version than `current`. */
|
|
108
|
+
function updateAvailable(current, latest) {
|
|
109
|
+
return compareSemver(current, latest) < 0;
|
|
110
|
+
}
|