rastack 0.0.35 → 0.0.37

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 CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [0.0.37](https://github.com/theserverkid/reactapistack/compare/v0.0.36...v0.0.37) (2026-07-11)
6
+
7
+ ### [0.0.36](https://github.com/theserverkid/reactapistack/compare/v0.0.35...v0.0.36) (2026-07-11)
8
+
5
9
  ### [0.0.35](https://github.com/theserverkid/reactapistack/compare/v0.0.34...v0.0.35) (2026-07-11)
6
10
 
7
11
  ### [0.0.34](https://github.com/theserverkid/reactapistack/compare/v0.0.33...v0.0.34) (2026-07-11)
@@ -64,7 +64,7 @@ export interface DeployConfig {
64
64
  resourcesDir: string;
65
65
  /** Where `rastack compile` writes the manifest + OpenAPI. */
66
66
  outDir: string;
67
- /** Bootstrap templates bucket that hosts `app.yaml` (for the launch command). */
67
+ /** Bootstrap templates bucket that hosts `app.yaml` (recorded for reference). */
68
68
  templatesBucket?: string;
69
69
  /** Bootstrap artifacts bucket holding the Lambda placeholder bundle. */
70
70
  artifactsBucket?: string;
@@ -115,14 +115,42 @@ export interface StackParameter {
115
115
  * `ArtifactsBucket` (which has no template default) is included only when set.
116
116
  */
117
117
  export declare function stackParameters(cfg: DeployConfig): StackParameter[];
118
- /** The S3 URL app.yaml is launched from, when the templates bucket is known. */
119
- export declare function templateUrl(cfg: DeployConfig): string | null;
118
+ /** The in-repo CloudFormation templates `rastack init` deploys from. */
119
+ export declare const APP_TEMPLATE_FILE = "deploy/cloudformation/app.yaml";
120
+ export declare const BOOTSTRAP_TEMPLATE_FILE = "deploy/cloudformation/bootstrap.yaml";
121
+ /** The argv that creates the app's infrastructure (app.yaml). */
122
+ export declare function deployStackArgs(cfg: DeployConfig): string[];
120
123
  /**
121
124
  * The one-time `aws cloudformation deploy` command that creates the app's
122
- * infrastructure. Launched from the hosted template URL when the templates
123
- * bucket is known, else from the in-repo `deploy/cloudformation/app.yaml`.
125
+ * infrastructure (app.yaml), from the in-repo template. `rastack init` runs this
126
+ * for you; it is also printed by `--print` so it can be run by hand.
124
127
  */
125
128
  export declare function deployStackCommand(cfg: DeployConfig): string;
129
+ /**
130
+ * The tier-0 bootstrap stack (bootstrap.yaml) — the S3 buckets that hold the
131
+ * Lambda placeholder + hosted templates, and the GitHub OIDC provider. One per
132
+ * account **and region**; its name is derived from the prefix so `init` can find
133
+ * it (and its `ArtifactsBucket`) without extra config.
134
+ */
135
+ export declare function bootstrapStackName(cfg: DeployConfig): string;
136
+ /**
137
+ * `--parameter-overrides` for bootstrap.yaml. `reuseOidc` re-runs against an
138
+ * account that already has a GitHub OIDC provider (AWS allows only one per URL):
139
+ * the template then derives the existing provider's ARN itself.
140
+ */
141
+ export declare function bootstrapStackParameters(cfg: DeployConfig, reuseOidc?: boolean): StackParameter[];
142
+ /** The argv that creates the bootstrap stack (bootstrap.yaml). */
143
+ export declare function bootstrapStackArgs(cfg: DeployConfig, reuseOidc?: boolean): string[];
144
+ /** The one-time `aws cloudformation deploy` command that creates the bootstrap stack. */
145
+ export declare function bootstrapStackCommand(cfg: DeployConfig, reuseOidc?: boolean): string;
146
+ /** Stack Output keys `init` reads back off the bootstrap stack. */
147
+ export declare const BOOTSTRAP_ARTIFACTS_OUTPUT = "ArtifactsBucketName";
148
+ export declare const BOOTSTRAP_TEMPLATES_OUTPUT = "TemplatesBucketName";
149
+ /** app.yaml stack Output key → the `DeployOutputs` field it populates. */
150
+ export declare const APP_STACK_OUTPUTS: {
151
+ key: string;
152
+ field: keyof DeployOutputs;
153
+ }[];
126
154
  /** GitHub repo Variable to wire (name + resolved value, if known from outputs). */
127
155
  export interface RepoVariable {
128
156
  name: string;
@@ -18,14 +18,18 @@
18
18
  * thin IO wrappers that read/write the file and shell out.
19
19
  */
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
- exports.DEFAULTS = exports.DEFAULT_LAMBDA = void 0;
21
+ exports.APP_STACK_OUTPUTS = exports.BOOTSTRAP_TEMPLATES_OUTPUT = exports.BOOTSTRAP_ARTIFACTS_OUTPUT = exports.BOOTSTRAP_TEMPLATE_FILE = exports.APP_TEMPLATE_FILE = exports.DEFAULTS = exports.DEFAULT_LAMBDA = void 0;
22
22
  exports.normalizeConfig = normalizeConfig;
23
23
  exports.validateConfig = validateConfig;
24
24
  exports.mergeConfig = mergeConfig;
25
25
  exports.parseGitHubRemote = parseGitHubRemote;
26
26
  exports.stackParameters = stackParameters;
27
- exports.templateUrl = templateUrl;
27
+ exports.deployStackArgs = deployStackArgs;
28
28
  exports.deployStackCommand = deployStackCommand;
29
+ exports.bootstrapStackName = bootstrapStackName;
30
+ exports.bootstrapStackParameters = bootstrapStackParameters;
31
+ exports.bootstrapStackArgs = bootstrapStackArgs;
32
+ exports.bootstrapStackCommand = bootstrapStackCommand;
29
33
  exports.repoVariables = repoVariables;
30
34
  exports.lambdaEnv = lambdaEnv;
31
35
  exports.DEFAULT_LAMBDA = {
@@ -163,34 +167,104 @@ function stackParameters(cfg) {
163
167
  params.push({ key: "ArtifactsBucket", value: cfg.artifactsBucket });
164
168
  return params;
165
169
  }
166
- /** The S3 URL app.yaml is launched from, when the templates bucket is known. */
167
- function templateUrl(cfg) {
168
- if (!cfg.templatesBucket)
169
- return null;
170
- return `https://${cfg.templatesBucket}.s3.${cfg.region}.amazonaws.com/app.yaml`;
170
+ /** The in-repo CloudFormation templates `rastack init` deploys from. */
171
+ exports.APP_TEMPLATE_FILE = "deploy/cloudformation/app.yaml";
172
+ exports.BOOTSTRAP_TEMPLATE_FILE = "deploy/cloudformation/bootstrap.yaml";
173
+ /**
174
+ * The argv (after `aws`) for an `aws cloudformation deploy`. `init` feeds this to
175
+ * `execFileSync` (no shell), and the printed command string is just this joined
176
+ * with spaces — so what is shown is exactly what runs.
177
+ *
178
+ * Always `--template-file`: the high-level `aws cloudformation deploy` command
179
+ * accepts *only* a local template file. `--template-url` is exclusive to the
180
+ * low-level `create-stack`/`update-stack`/`create-change-set` commands, so
181
+ * pairing it with `deploy` fails with `the following arguments are required:
182
+ * --template-file`. `init` runs in the repo, where both templates are present.
183
+ */
184
+ function cfnDeployArgs(templateFile, stackName, region, params) {
185
+ return [
186
+ "cloudformation",
187
+ "deploy",
188
+ "--template-file",
189
+ templateFile,
190
+ "--stack-name",
191
+ stackName,
192
+ "--capabilities",
193
+ "CAPABILITY_NAMED_IAM",
194
+ "--region",
195
+ region,
196
+ "--parameter-overrides",
197
+ ...params.map((p) => `${p.key}=${p.value}`),
198
+ ];
199
+ }
200
+ /**
201
+ * Render an argv as a single-line command string. Deliberately ONE line: a
202
+ * backslash-continued command pasted into Windows PowerShell breaks — there `\`
203
+ * is not a line-continuation, so every `--flag` is parsed as its own statement
204
+ * (`Missing expression after unary operator '--'`). A single line pastes cleanly
205
+ * into bash, zsh and PowerShell alike.
206
+ */
207
+ function awsCommand(args) {
208
+ return `aws ${args.join(" ")}`;
209
+ }
210
+ /** The argv that creates the app's infrastructure (app.yaml). */
211
+ function deployStackArgs(cfg) {
212
+ return cfnDeployArgs(exports.APP_TEMPLATE_FILE, cfg.stackName, cfg.region, stackParameters(cfg));
171
213
  }
172
214
  /**
173
215
  * The one-time `aws cloudformation deploy` command that creates the app's
174
- * infrastructure. Launched from the hosted template URL when the templates
175
- * bucket is known, else from the in-repo `deploy/cloudformation/app.yaml`.
216
+ * infrastructure (app.yaml), from the in-repo template. `rastack init` runs this
217
+ * for you; it is also printed by `--print` so it can be run by hand.
176
218
  */
177
219
  function deployStackCommand(cfg) {
178
- const src = templateUrl(cfg);
179
- const templateArg = src
180
- ? `--template-url ${src}`
181
- : "--template-file deploy/cloudformation/app.yaml";
182
- const overrides = stackParameters(cfg)
183
- .map((p) => `${p.key}=${p.value}`)
184
- .join(" ");
185
- return [
186
- "aws cloudformation deploy",
187
- templateArg,
188
- `--stack-name ${cfg.stackName}`,
189
- "--capabilities CAPABILITY_NAMED_IAM",
190
- `--region ${cfg.region}`,
191
- `--parameter-overrides ${overrides}`,
192
- ].join(" \\\n ");
220
+ return awsCommand(deployStackArgs(cfg));
221
+ }
222
+ /**
223
+ * The tier-0 bootstrap stack (bootstrap.yaml) — the S3 buckets that hold the
224
+ * Lambda placeholder + hosted templates, and the GitHub OIDC provider. One per
225
+ * account **and region**; its name is derived from the prefix so `init` can find
226
+ * it (and its `ArtifactsBucket`) without extra config.
227
+ */
228
+ function bootstrapStackName(cfg) {
229
+ return `${cfg.namePrefix}-bootstrap`;
230
+ }
231
+ /**
232
+ * `--parameter-overrides` for bootstrap.yaml. `reuseOidc` re-runs against an
233
+ * account that already has a GitHub OIDC provider (AWS allows only one per URL):
234
+ * the template then derives the existing provider's ARN itself.
235
+ */
236
+ function bootstrapStackParameters(cfg, reuseOidc = false) {
237
+ const params = [
238
+ { key: "NamePrefix", value: cfg.namePrefix },
239
+ { key: "GitHubOwner", value: cfg.github.owner },
240
+ { key: "PublisherRepo", value: cfg.github.repo },
241
+ { key: "PublisherBranch", value: cfg.github.branch },
242
+ ];
243
+ if (reuseOidc)
244
+ params.push({ key: "CreateOIDCProvider", value: "false" });
245
+ return params;
246
+ }
247
+ /** The argv that creates the bootstrap stack (bootstrap.yaml). */
248
+ function bootstrapStackArgs(cfg, reuseOidc = false) {
249
+ return cfnDeployArgs(exports.BOOTSTRAP_TEMPLATE_FILE, bootstrapStackName(cfg), cfg.region, bootstrapStackParameters(cfg, reuseOidc));
250
+ }
251
+ /** The one-time `aws cloudformation deploy` command that creates the bootstrap stack. */
252
+ function bootstrapStackCommand(cfg, reuseOidc = false) {
253
+ return awsCommand(bootstrapStackArgs(cfg, reuseOidc));
193
254
  }
255
+ /** Stack Output keys `init` reads back off the bootstrap stack. */
256
+ exports.BOOTSTRAP_ARTIFACTS_OUTPUT = "ArtifactsBucketName";
257
+ exports.BOOTSTRAP_TEMPLATES_OUTPUT = "TemplatesBucketName";
258
+ /** app.yaml stack Output key → the `DeployOutputs` field it populates. */
259
+ exports.APP_STACK_OUTPUTS = [
260
+ { key: "FunctionName", field: "functionName" },
261
+ { key: "ApiUrl", field: "apiUrl" },
262
+ { key: "DeployRoleArn", field: "deployRoleArn" },
263
+ { key: "UserPoolId", field: "userPoolId" },
264
+ { key: "UserPoolClientId", field: "userPoolClientId" },
265
+ { key: "JwksUrl", field: "jwksUrl" },
266
+ { key: "WarehouseBucketName", field: "warehouseBucket" },
267
+ ];
194
268
  /**
195
269
  * The GitHub repo **Variables** CI reads — `AWS_DEPLOY_ROLE_ARN` and
196
270
  * `AWS_REGION` (see docs/serverless-deploy.md §3). Values come from the stack
@@ -7,3 +7,4 @@
7
7
  export * from "./config";
8
8
  export * from "./ci";
9
9
  export * from "./workflows";
10
+ export * from "./zip";
@@ -23,3 +23,4 @@ Object.defineProperty(exports, "__esModule", { value: true });
23
23
  __exportStar(require("./config"), exports);
24
24
  __exportStar(require("./ci"), exports);
25
25
  __exportStar(require("./workflows"), exports);
26
+ __exportStar(require("./zip"), exports);
@@ -18,6 +18,25 @@ export declare function saveConfig(cfg: DeployConfig, cwd?: string): string;
18
18
  export declare function gitRemoteUrl(cwd?: string): string | null;
19
19
  /** True when we can prompt the user (a TTY, and not explicitly opted out). */
20
20
  export declare function canPrompt(nonInteractive: boolean): boolean;
21
+ /** Capture `aws <args>` stdout, or null when the CLI is absent / the call fails. */
22
+ export declare function awsCapture(args: string[], cwd?: string): string | null;
23
+ /** Run `aws <args>` streaming output; throws on a non-zero exit. */
24
+ export declare function awsRun(args: string[], cwd?: string): void;
25
+ /** True when the AWS CLI is on PATH. */
26
+ export declare function awsAvailable(): boolean;
27
+ /**
28
+ * A single Output value of a CloudFormation stack, or null when the stack does
29
+ * not exist or the key is unset. `describe-stacks` prints `None` for a missing
30
+ * key with `--output text`, which we normalize to null.
31
+ */
32
+ export declare function stackOutput(stackName: string, key: string, region: string): string | null;
33
+ /**
34
+ * Package the in-repo Lambda placeholder and upload it to the bootstrap
35
+ * artifacts bucket at `placeholders/bootstrap.zip` — the object app.yaml's
36
+ * function is *created from*. Idempotent (the zip is deterministic); safe to run
37
+ * on every `init`. Returns false when the placeholder file is missing.
38
+ */
39
+ export declare function uploadPlaceholder(artifactsBucket: string, region: string, cwd?: string): boolean;
21
40
  /**
22
41
  * Ask a single question with a default, echoed as `label [default]: `. Returns
23
42
  * the trimmed answer, or the default on an empty line.
package/dist/deploy/io.js CHANGED
@@ -43,14 +43,21 @@ exports.loadConfig = loadConfig;
43
43
  exports.saveConfig = saveConfig;
44
44
  exports.gitRemoteUrl = gitRemoteUrl;
45
45
  exports.canPrompt = canPrompt;
46
+ exports.awsCapture = awsCapture;
47
+ exports.awsRun = awsRun;
48
+ exports.awsAvailable = awsAvailable;
49
+ exports.stackOutput = stackOutput;
50
+ exports.uploadPlaceholder = uploadPlaceholder;
46
51
  exports.ask = ask;
47
52
  exports.withPrompt = withPrompt;
48
53
  const child_process_1 = require("child_process");
49
54
  const fs = __importStar(require("fs"));
55
+ const os = __importStar(require("os"));
50
56
  const path = __importStar(require("path"));
51
57
  const readline = __importStar(require("readline"));
52
58
  const workflows_1 = require("./workflows");
53
59
  const config_1 = require("./config");
60
+ const zip_1 = require("./zip");
54
61
  /** Absolute path to the deploy config for a project root. */
55
62
  function configPath(cwd = process.cwd()) {
56
63
  return path.join(cwd, workflows_1.CONFIG_PATH);
@@ -94,6 +101,80 @@ function gitRemoteUrl(cwd = process.cwd()) {
94
101
  function canPrompt(nonInteractive) {
95
102
  return !nonInteractive && Boolean(process.stdin.isTTY);
96
103
  }
104
+ // ── AWS CLI boundary ─────────────────────────────────────────────────────────
105
+ // `init` shells out to the developer's own AWS CLI (execFileSync — no shell, so
106
+ // no injection surface) to stand the infrastructure up. Read helpers capture
107
+ // output and return null on failure; mutating helpers stream to the console and
108
+ // throw so the caller can report which step failed.
109
+ /** Capture `aws <args>` stdout, or null when the CLI is absent / the call fails. */
110
+ function awsCapture(args, cwd = process.cwd()) {
111
+ try {
112
+ const out = (0, child_process_1.execFileSync)("aws", args, {
113
+ cwd,
114
+ stdio: ["ignore", "pipe", "pipe"],
115
+ });
116
+ return out.toString().trim();
117
+ }
118
+ catch {
119
+ return null;
120
+ }
121
+ }
122
+ /** Run `aws <args>` streaming output; throws on a non-zero exit. */
123
+ function awsRun(args, cwd = process.cwd()) {
124
+ // nosemgrep: javascript.lang.security.detect-child-process.detect-child-process
125
+ (0, child_process_1.execFileSync)("aws", args, { cwd, stdio: "inherit" });
126
+ }
127
+ /** True when the AWS CLI is on PATH. */
128
+ function awsAvailable() {
129
+ return awsCapture(["--version"]) !== null;
130
+ }
131
+ /**
132
+ * A single Output value of a CloudFormation stack, or null when the stack does
133
+ * not exist or the key is unset. `describe-stacks` prints `None` for a missing
134
+ * key with `--output text`, which we normalize to null.
135
+ */
136
+ function stackOutput(stackName, key, region) {
137
+ const val = awsCapture([
138
+ "cloudformation",
139
+ "describe-stacks",
140
+ "--stack-name",
141
+ stackName,
142
+ "--region",
143
+ region,
144
+ "--query",
145
+ `Stacks[0].Outputs[?OutputKey=='${key}'].OutputValue`,
146
+ "--output",
147
+ "text",
148
+ ]);
149
+ if (!val || val === "None")
150
+ return null;
151
+ return val;
152
+ }
153
+ /**
154
+ * Package the in-repo Lambda placeholder and upload it to the bootstrap
155
+ * artifacts bucket at `placeholders/bootstrap.zip` — the object app.yaml's
156
+ * function is *created from*. Idempotent (the zip is deterministic); safe to run
157
+ * on every `init`. Returns false when the placeholder file is missing.
158
+ */
159
+ function uploadPlaceholder(artifactsBucket, region, cwd = process.cwd()) {
160
+ const src = path.join(cwd, "deploy/cloudformation/placeholder/bootstrap");
161
+ if (!fs.existsSync(src))
162
+ return false;
163
+ const zip = (0, zip_1.zipStore)([
164
+ { name: "bootstrap", data: fs.readFileSync(src), mode: 0o755 },
165
+ ]);
166
+ const tmp = path.join(os.tmpdir(), "rastack-placeholder-bootstrap.zip");
167
+ fs.writeFileSync(tmp, zip);
168
+ awsRun([
169
+ "s3",
170
+ "cp",
171
+ tmp,
172
+ `s3://${artifactsBucket}/placeholders/bootstrap.zip`,
173
+ "--region",
174
+ region,
175
+ ]);
176
+ return true;
177
+ }
97
178
  /**
98
179
  * Ask a single question with a default, echoed as `label [default]: `. Returns
99
180
  * the trimmed answer, or the default on an empty line.
@@ -0,0 +1,24 @@
1
+ /**
2
+ * A tiny, dependency-free ZIP writer used by `rastack init` to package the Lambda
3
+ * placeholder bundle (`placeholders/bootstrap.zip`) before uploading it to the
4
+ * bootstrap artifacts bucket.
5
+ *
6
+ * Why hand-roll it: `init` runs on the developer's machine — including Windows
7
+ * PowerShell, which has no `zip` binary — so shelling out to `zip` is not
8
+ * portable. Entries are STORED (no compression); the placeholder is ~1 KB, so
9
+ * compression buys nothing and STORE keeps this pure and trivial to verify. The
10
+ * one detail that matters for Lambda is the Unix executable bit on `bootstrap`,
11
+ * which is carried in the central-directory external attributes.
12
+ */
13
+ /** A file to place in the archive. `mode` defaults to `0o644` (or `0o755` via helper). */
14
+ export interface ZipEntry {
15
+ name: string;
16
+ data: Buffer;
17
+ mode?: number;
18
+ }
19
+ /**
20
+ * Build a ZIP archive from `entries` and return its bytes. Deterministic: the
21
+ * same inputs always produce the same buffer (a fixed DOS timestamp, no
22
+ * compression), so the placeholder upload is idempotent.
23
+ */
24
+ export declare function zipStore(entries: ZipEntry[]): Buffer;
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ /**
3
+ * A tiny, dependency-free ZIP writer used by `rastack init` to package the Lambda
4
+ * placeholder bundle (`placeholders/bootstrap.zip`) before uploading it to the
5
+ * bootstrap artifacts bucket.
6
+ *
7
+ * Why hand-roll it: `init` runs on the developer's machine — including Windows
8
+ * PowerShell, which has no `zip` binary — so shelling out to `zip` is not
9
+ * portable. Entries are STORED (no compression); the placeholder is ~1 KB, so
10
+ * compression buys nothing and STORE keeps this pure and trivial to verify. The
11
+ * one detail that matters for Lambda is the Unix executable bit on `bootstrap`,
12
+ * which is carried in the central-directory external attributes.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.zipStore = zipStore;
16
+ const CRC_TABLE = (() => {
17
+ const table = new Int32Array(256);
18
+ for (let n = 0; n < 256; n++) {
19
+ let c = n;
20
+ for (let k = 0; k < 8; k++)
21
+ c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
22
+ table[n] = c;
23
+ }
24
+ return table;
25
+ })();
26
+ function crc32(buf) {
27
+ let c = 0xffffffff;
28
+ for (let i = 0; i < buf.length; i++)
29
+ c = CRC_TABLE[(c ^ buf[i]) & 0xff] ^ (c >>> 8);
30
+ return (c ^ 0xffffffff) >>> 0;
31
+ }
32
+ // Fixed 1980-01-01 00:00 DOS timestamp — deterministic output (no wall-clock).
33
+ const DOS_TIME = 0;
34
+ const DOS_DATE = 0x0021;
35
+ /**
36
+ * Build a ZIP archive from `entries` and return its bytes. Deterministic: the
37
+ * same inputs always produce the same buffer (a fixed DOS timestamp, no
38
+ * compression), so the placeholder upload is idempotent.
39
+ */
40
+ function zipStore(entries) {
41
+ const locals = [];
42
+ const centrals = [];
43
+ let offset = 0;
44
+ for (const entry of entries) {
45
+ const name = Buffer.from(entry.name, "utf-8");
46
+ const crc = crc32(entry.data);
47
+ const size = entry.data.length;
48
+ const mode = entry.mode ?? 0o644;
49
+ const local = Buffer.alloc(30);
50
+ local.writeUInt32LE(0x04034b50, 0); // local file header signature
51
+ local.writeUInt16LE(20, 4); // version needed
52
+ local.writeUInt16LE(0, 6); // flags
53
+ local.writeUInt16LE(0, 8); // method: STORE
54
+ local.writeUInt16LE(DOS_TIME, 10);
55
+ local.writeUInt16LE(DOS_DATE, 12);
56
+ local.writeUInt32LE(crc, 14);
57
+ local.writeUInt32LE(size, 18); // compressed size
58
+ local.writeUInt32LE(size, 22); // uncompressed size
59
+ local.writeUInt16LE(name.length, 26);
60
+ local.writeUInt16LE(0, 28); // extra length
61
+ locals.push(local, name, entry.data);
62
+ const central = Buffer.alloc(46);
63
+ central.writeUInt32LE(0x02014b50, 0); // central directory signature
64
+ central.writeUInt16LE((3 << 8) | 20, 4); // version made by: Unix host
65
+ central.writeUInt16LE(20, 6); // version needed
66
+ central.writeUInt16LE(0, 8); // flags
67
+ central.writeUInt16LE(0, 10); // method: STORE
68
+ central.writeUInt16LE(DOS_TIME, 12);
69
+ central.writeUInt16LE(DOS_DATE, 14);
70
+ central.writeUInt32LE(crc, 16);
71
+ central.writeUInt32LE(size, 20);
72
+ central.writeUInt32LE(size, 24);
73
+ central.writeUInt16LE(name.length, 28);
74
+ central.writeUInt16LE(0, 30); // extra length
75
+ central.writeUInt16LE(0, 32); // comment length
76
+ central.writeUInt16LE(0, 34); // disk number
77
+ central.writeUInt16LE(0, 36); // internal attributes
78
+ central.writeUInt32LE((mode & 0xffff) << 16, 38); // external attrs: Unix mode
79
+ central.writeUInt32LE(offset, 42); // local header offset
80
+ centrals.push(central, name);
81
+ offset += local.length + name.length + entry.data.length;
82
+ }
83
+ const localBytes = Buffer.concat(locals);
84
+ const centralBytes = Buffer.concat(centrals);
85
+ const eocd = Buffer.alloc(22);
86
+ eocd.writeUInt32LE(0x06054b50, 0); // end of central directory signature
87
+ eocd.writeUInt16LE(0, 4); // disk number
88
+ eocd.writeUInt16LE(0, 6); // central dir start disk
89
+ eocd.writeUInt16LE(entries.length, 8); // entries on this disk
90
+ eocd.writeUInt16LE(entries.length, 10); // total entries
91
+ eocd.writeUInt32LE(centralBytes.length, 12); // central dir size
92
+ eocd.writeUInt32LE(localBytes.length, 16); // central dir offset
93
+ eocd.writeUInt16LE(0, 20); // comment length
94
+ return Buffer.concat([localBytes, centralBytes, eocd]);
95
+ }
@@ -103,7 +103,7 @@ function main(argv) {
103
103
  const { phase, plan, opts } = parseFlags(argv);
104
104
  const cfg = (0, io_1.loadConfig)();
105
105
  if (!cfg) {
106
- console.error(" ✗ no .rastack/deploy.json found. Run `rastack init` (or `rastack deploy`) first.");
106
+ console.error(" ✗ no .rastack/deploy.json found. Run `rastack init` first.");
107
107
  process.exit(1);
108
108
  return;
109
109
  }
@@ -1,14 +1,25 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * `rastack init` — template the base GitHub Actions + deploy config into a repo.
3
+ * `rastack init` — scaffold a repo's deploy config + GitHub Actions AND stand the
4
+ * serverless backend up.
4
5
  *
5
6
  * rastack init [--app <slug>] [--owner <o>] [--repo <r>] [--branch <b>]
6
- * [--region <r>] [--force] [--no-workflows] [--yes]
7
+ * [--region <r>] [--prefix <p>] [--namespace <n>]
8
+ * [--force] [--no-workflows] [--no-deploy] [--print] [--yes]
7
9
  *
8
- * Scaffolds a new project (or reconfigures an existing one) so it is ready to
9
- * deploy: `.rastack/deploy.json` plus `.github/workflows/{ci,deploy-backend}.yml`,
10
- * both of which run `rastack ci`. GitHub coordinates default from the repo's
11
- * `origin` remote; missing fields are prompted for on a TTY. Existing files are
12
- * left untouched unless `--force`.
10
+ * This is the one command that takes you from nothing to deployed. It:
11
+ * 1. writes `.rastack/deploy.json` + `.github/workflows/{ci,deploy-backend}.yml`
12
+ * (GitHub coordinates default from the `origin` remote; missing fields are
13
+ * prompted for on a TTY);
14
+ * 2. resolves the tier-0 bootstrap stack's artifacts bucket — and CREATES the
15
+ * bootstrap stack if it does not exist yet;
16
+ * 3. uploads the Lambda placeholder bundle to that bucket;
17
+ * 4. deploys the per-app backend (app.yaml) with the resolved ArtifactsBucket;
18
+ * 5. reads the stack Outputs back into the config and prints the two GitHub repo
19
+ * Variables CI needs.
20
+ *
21
+ * Thereafter the recurring code deploy is CI's job — `rastack ci deploy`, which
22
+ * `rastack deploy` also runs. `--no-deploy` scaffolds only; `--print` shows the
23
+ * exact single-line `aws cloudformation deploy` commands without running them.
13
24
  */
14
25
  export {};