rastack 0.0.35 → 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 +2 -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/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
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.36](https://github.com/theserverkid/reactapistack/compare/v0.0.35...v0.0.36) (2026-07-11)
|
|
6
|
+
|
|
5
7
|
### [0.0.35](https://github.com/theserverkid/reactapistack/compare/v0.0.34...v0.0.35) (2026-07-11)
|
|
6
8
|
|
|
7
9
|
### [0.0.34](https://github.com/theserverkid/reactapistack/compare/v0.0.33...v0.0.34) (2026-07-11)
|
package/dist/deploy/config.d.ts
CHANGED
|
@@ -117,12 +117,43 @@ export interface StackParameter {
|
|
|
117
117
|
export declare function stackParameters(cfg: DeployConfig): StackParameter[];
|
|
118
118
|
/** The S3 URL app.yaml is launched from, when the templates bucket is known. */
|
|
119
119
|
export declare function templateUrl(cfg: DeployConfig): string | null;
|
|
120
|
+
/** The in-repo CloudFormation templates `rastack init` deploys from. */
|
|
121
|
+
export declare const APP_TEMPLATE_FILE = "deploy/cloudformation/app.yaml";
|
|
122
|
+
export declare const BOOTSTRAP_TEMPLATE_FILE = "deploy/cloudformation/bootstrap.yaml";
|
|
123
|
+
/** The argv that creates the app's infrastructure (app.yaml). */
|
|
124
|
+
export declare function deployStackArgs(cfg: DeployConfig): string[];
|
|
120
125
|
/**
|
|
121
126
|
* The one-time `aws cloudformation deploy` command that creates the app's
|
|
122
|
-
* infrastructure. Launched from the hosted template URL when the
|
|
123
|
-
* bucket is known, else from the in-repo `
|
|
127
|
+
* infrastructure (app.yaml). Launched from the hosted template URL when the
|
|
128
|
+
* templates bucket is known, else from the in-repo `app.yaml`. `rastack init`
|
|
129
|
+
* runs this for you; it is also printed by `--print` so it can be run by hand.
|
|
124
130
|
*/
|
|
125
131
|
export declare function deployStackCommand(cfg: DeployConfig): string;
|
|
132
|
+
/**
|
|
133
|
+
* The tier-0 bootstrap stack (bootstrap.yaml) — the S3 buckets that hold the
|
|
134
|
+
* Lambda placeholder + hosted templates, and the GitHub OIDC provider. One per
|
|
135
|
+
* account **and region**; its name is derived from the prefix so `init` can find
|
|
136
|
+
* it (and its `ArtifactsBucket`) without extra config.
|
|
137
|
+
*/
|
|
138
|
+
export declare function bootstrapStackName(cfg: DeployConfig): string;
|
|
139
|
+
/**
|
|
140
|
+
* `--parameter-overrides` for bootstrap.yaml. `reuseOidc` re-runs against an
|
|
141
|
+
* account that already has a GitHub OIDC provider (AWS allows only one per URL):
|
|
142
|
+
* the template then derives the existing provider's ARN itself.
|
|
143
|
+
*/
|
|
144
|
+
export declare function bootstrapStackParameters(cfg: DeployConfig, reuseOidc?: boolean): StackParameter[];
|
|
145
|
+
/** The argv that creates the bootstrap stack (bootstrap.yaml). */
|
|
146
|
+
export declare function bootstrapStackArgs(cfg: DeployConfig, reuseOidc?: boolean): string[];
|
|
147
|
+
/** The one-time `aws cloudformation deploy` command that creates the bootstrap stack. */
|
|
148
|
+
export declare function bootstrapStackCommand(cfg: DeployConfig, reuseOidc?: boolean): string;
|
|
149
|
+
/** Stack Output keys `init` reads back off the bootstrap stack. */
|
|
150
|
+
export declare const BOOTSTRAP_ARTIFACTS_OUTPUT = "ArtifactsBucketName";
|
|
151
|
+
export declare const BOOTSTRAP_TEMPLATES_OUTPUT = "TemplatesBucketName";
|
|
152
|
+
/** app.yaml stack Output key → the `DeployOutputs` field it populates. */
|
|
153
|
+
export declare const APP_STACK_OUTPUTS: {
|
|
154
|
+
key: string;
|
|
155
|
+
field: keyof DeployOutputs;
|
|
156
|
+
}[];
|
|
126
157
|
/** GitHub repo Variable to wire (name + resolved value, if known from outputs). */
|
|
127
158
|
export interface RepoVariable {
|
|
128
159
|
name: string;
|
package/dist/deploy/config.js
CHANGED
|
@@ -18,14 +18,19 @@
|
|
|
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
27
|
exports.templateUrl = templateUrl;
|
|
28
|
+
exports.deployStackArgs = deployStackArgs;
|
|
28
29
|
exports.deployStackCommand = deployStackCommand;
|
|
30
|
+
exports.bootstrapStackName = bootstrapStackName;
|
|
31
|
+
exports.bootstrapStackParameters = bootstrapStackParameters;
|
|
32
|
+
exports.bootstrapStackArgs = bootstrapStackArgs;
|
|
33
|
+
exports.bootstrapStackCommand = bootstrapStackCommand;
|
|
29
34
|
exports.repoVariables = repoVariables;
|
|
30
35
|
exports.lambdaEnv = lambdaEnv;
|
|
31
36
|
exports.DEFAULT_LAMBDA = {
|
|
@@ -169,28 +174,102 @@ function templateUrl(cfg) {
|
|
|
169
174
|
return null;
|
|
170
175
|
return `https://${cfg.templatesBucket}.s3.${cfg.region}.amazonaws.com/app.yaml`;
|
|
171
176
|
}
|
|
177
|
+
/** The in-repo CloudFormation templates `rastack init` deploys from. */
|
|
178
|
+
exports.APP_TEMPLATE_FILE = "deploy/cloudformation/app.yaml";
|
|
179
|
+
exports.BOOTSTRAP_TEMPLATE_FILE = "deploy/cloudformation/bootstrap.yaml";
|
|
180
|
+
/**
|
|
181
|
+
* The argv (after `aws`) for an `aws cloudformation deploy`. `init` feeds this to
|
|
182
|
+
* `execFileSync` (no shell), and the printed command string is just this joined
|
|
183
|
+
* with spaces — so what is shown is exactly what runs.
|
|
184
|
+
*/
|
|
185
|
+
function cfnDeployArgs(template, stackName, region, params) {
|
|
186
|
+
const t = template.url
|
|
187
|
+
? ["--template-url", template.url]
|
|
188
|
+
: ["--template-file", template.file];
|
|
189
|
+
return [
|
|
190
|
+
"cloudformation",
|
|
191
|
+
"deploy",
|
|
192
|
+
...t,
|
|
193
|
+
"--stack-name",
|
|
194
|
+
stackName,
|
|
195
|
+
"--capabilities",
|
|
196
|
+
"CAPABILITY_NAMED_IAM",
|
|
197
|
+
"--region",
|
|
198
|
+
region,
|
|
199
|
+
"--parameter-overrides",
|
|
200
|
+
...params.map((p) => `${p.key}=${p.value}`),
|
|
201
|
+
];
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Render an argv as a single-line command string. Deliberately ONE line: a
|
|
205
|
+
* backslash-continued command pasted into Windows PowerShell breaks — there `\`
|
|
206
|
+
* is not a line-continuation, so every `--flag` is parsed as its own statement
|
|
207
|
+
* (`Missing expression after unary operator '--'`). A single line pastes cleanly
|
|
208
|
+
* into bash, zsh and PowerShell alike.
|
|
209
|
+
*/
|
|
210
|
+
function awsCommand(args) {
|
|
211
|
+
return `aws ${args.join(" ")}`;
|
|
212
|
+
}
|
|
213
|
+
/** The argv that creates the app's infrastructure (app.yaml). */
|
|
214
|
+
function deployStackArgs(cfg) {
|
|
215
|
+
const src = templateUrl(cfg);
|
|
216
|
+
return cfnDeployArgs(src ? { url: src } : { file: exports.APP_TEMPLATE_FILE }, cfg.stackName, cfg.region, stackParameters(cfg));
|
|
217
|
+
}
|
|
172
218
|
/**
|
|
173
219
|
* The one-time `aws cloudformation deploy` command that creates the app's
|
|
174
|
-
* infrastructure. Launched from the hosted template URL when the
|
|
175
|
-
* bucket is known, else from the in-repo `
|
|
220
|
+
* infrastructure (app.yaml). Launched from the hosted template URL when the
|
|
221
|
+
* templates bucket is known, else from the in-repo `app.yaml`. `rastack init`
|
|
222
|
+
* runs this for you; it is also printed by `--print` so it can be run by hand.
|
|
176
223
|
*/
|
|
177
224
|
function deployStackCommand(cfg) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
225
|
+
return awsCommand(deployStackArgs(cfg));
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* The tier-0 bootstrap stack (bootstrap.yaml) — the S3 buckets that hold the
|
|
229
|
+
* Lambda placeholder + hosted templates, and the GitHub OIDC provider. One per
|
|
230
|
+
* account **and region**; its name is derived from the prefix so `init` can find
|
|
231
|
+
* it (and its `ArtifactsBucket`) without extra config.
|
|
232
|
+
*/
|
|
233
|
+
function bootstrapStackName(cfg) {
|
|
234
|
+
return `${cfg.namePrefix}-bootstrap`;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* `--parameter-overrides` for bootstrap.yaml. `reuseOidc` re-runs against an
|
|
238
|
+
* account that already has a GitHub OIDC provider (AWS allows only one per URL):
|
|
239
|
+
* the template then derives the existing provider's ARN itself.
|
|
240
|
+
*/
|
|
241
|
+
function bootstrapStackParameters(cfg, reuseOidc = false) {
|
|
242
|
+
const params = [
|
|
243
|
+
{ key: "NamePrefix", value: cfg.namePrefix },
|
|
244
|
+
{ key: "GitHubOwner", value: cfg.github.owner },
|
|
245
|
+
{ key: "PublisherRepo", value: cfg.github.repo },
|
|
246
|
+
{ key: "PublisherBranch", value: cfg.github.branch },
|
|
247
|
+
];
|
|
248
|
+
if (reuseOidc)
|
|
249
|
+
params.push({ key: "CreateOIDCProvider", value: "false" });
|
|
250
|
+
return params;
|
|
251
|
+
}
|
|
252
|
+
/** The argv that creates the bootstrap stack (bootstrap.yaml). */
|
|
253
|
+
function bootstrapStackArgs(cfg, reuseOidc = false) {
|
|
254
|
+
return cfnDeployArgs({ file: exports.BOOTSTRAP_TEMPLATE_FILE }, bootstrapStackName(cfg), cfg.region, bootstrapStackParameters(cfg, reuseOidc));
|
|
255
|
+
}
|
|
256
|
+
/** The one-time `aws cloudformation deploy` command that creates the bootstrap stack. */
|
|
257
|
+
function bootstrapStackCommand(cfg, reuseOidc = false) {
|
|
258
|
+
return awsCommand(bootstrapStackArgs(cfg, reuseOidc));
|
|
193
259
|
}
|
|
260
|
+
/** Stack Output keys `init` reads back off the bootstrap stack. */
|
|
261
|
+
exports.BOOTSTRAP_ARTIFACTS_OUTPUT = "ArtifactsBucketName";
|
|
262
|
+
exports.BOOTSTRAP_TEMPLATES_OUTPUT = "TemplatesBucketName";
|
|
263
|
+
/** app.yaml stack Output key → the `DeployOutputs` field it populates. */
|
|
264
|
+
exports.APP_STACK_OUTPUTS = [
|
|
265
|
+
{ key: "FunctionName", field: "functionName" },
|
|
266
|
+
{ key: "ApiUrl", field: "apiUrl" },
|
|
267
|
+
{ key: "DeployRoleArn", field: "deployRoleArn" },
|
|
268
|
+
{ key: "UserPoolId", field: "userPoolId" },
|
|
269
|
+
{ key: "UserPoolClientId", field: "userPoolClientId" },
|
|
270
|
+
{ key: "JwksUrl", field: "jwksUrl" },
|
|
271
|
+
{ key: "WarehouseBucketName", field: "warehouseBucket" },
|
|
272
|
+
];
|
|
194
273
|
/**
|
|
195
274
|
* The GitHub repo **Variables** CI reads — `AWS_DEPLOY_ROLE_ARN` and
|
|
196
275
|
* `AWS_REGION` (see docs/serverless-deploy.md §3). Values come from the stack
|
package/dist/deploy/index.d.ts
CHANGED
package/dist/deploy/index.js
CHANGED
package/dist/deploy/io.d.ts
CHANGED
|
@@ -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
|
+
}
|
package/dist/rastack-ci.js
CHANGED
|
@@ -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`
|
|
106
|
+
console.error(" ✗ no .rastack/deploy.json found. Run `rastack init` first.");
|
|
107
107
|
process.exit(1);
|
|
108
108
|
return;
|
|
109
109
|
}
|
package/dist/rastack-init.d.ts
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* `rastack init` —
|
|
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>] [--
|
|
7
|
+
* [--region <r>] [--prefix <p>] [--namespace <n>]
|
|
8
|
+
* [--force] [--no-workflows] [--no-deploy] [--print] [--yes]
|
|
7
9
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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 {};
|