rastack 0.0.36 → 0.0.38
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 +17 -7
- package/dist/deploy/config.js +51 -20
- package/dist/rastack-update.js +4 -2
- package/dist/update.d.ts +8 -0
- package/dist/update.js +11 -0
- package/package.json +1 -1
- package/src/deploy/config.ts +52 -19
- package/src/rastack-update.ts +6 -2
- package/src/update.ts +11 -0
- package/test/deploy.spec.ts +27 -13
- package/test/update.spec.ts +9 -0
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.38](https://github.com/theserverkid/reactapistack/compare/v0.0.37...v0.0.38) (2026-07-11)
|
|
6
|
+
|
|
7
|
+
### [0.0.37](https://github.com/theserverkid/reactapistack/compare/v0.0.36...v0.0.37) (2026-07-11)
|
|
8
|
+
|
|
5
9
|
### [0.0.36](https://github.com/theserverkid/reactapistack/compare/v0.0.35...v0.0.36) (2026-07-11)
|
|
6
10
|
|
|
7
11
|
### [0.0.35](https://github.com/theserverkid/reactapistack/compare/v0.0.34...v0.0.35) (2026-07-11)
|
package/dist/deploy/config.d.ts
CHANGED
|
@@ -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
|
|
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;
|
|
@@ -109,14 +109,25 @@ export interface StackParameter {
|
|
|
109
109
|
key: string;
|
|
110
110
|
value: string;
|
|
111
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Regions where Cognito advanced security (threat protection) is NOT offered, as
|
|
114
|
+
* of 2026-07. `AWS::Cognito::UserPool` carrying a `UserPoolAddOns` block is
|
|
115
|
+
* undeployable there — CloudFormation's early-validation ResourceExistenceCheck
|
|
116
|
+
* rejects the change set — so app.yaml must omit the add-on (AdvancedSecurity=OFF)
|
|
117
|
+
* in these regions. A denylist (not an allowlist) so new/standard regions keep
|
|
118
|
+
* the secure default; AWS expands coverage over time, so revisit when they do.
|
|
119
|
+
*/
|
|
120
|
+
export declare const REGIONS_WITHOUT_COGNITO_ASF: ReadonlySet<string>;
|
|
121
|
+
/** Whether Cognito advanced security can be enabled in a region. */
|
|
122
|
+
export declare function cognitoAdvancedSecuritySupported(region: string): boolean;
|
|
112
123
|
/**
|
|
113
124
|
* The `--parameter-overrides` for `aws cloudformation deploy` of app.yaml,
|
|
114
125
|
* derived from the config. Order and keys match the template's `Parameters`.
|
|
115
|
-
* `ArtifactsBucket` (which has no template default) is included only when set
|
|
126
|
+
* `ArtifactsBucket` (which has no template default) is included only when set;
|
|
127
|
+
* `AdvancedSecurity` is forced OFF only in regions that lack the feature (else
|
|
128
|
+
* the template's ENFORCED default stands).
|
|
116
129
|
*/
|
|
117
130
|
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;
|
|
120
131
|
/** The in-repo CloudFormation templates `rastack init` deploys from. */
|
|
121
132
|
export declare const APP_TEMPLATE_FILE = "deploy/cloudformation/app.yaml";
|
|
122
133
|
export declare const BOOTSTRAP_TEMPLATE_FILE = "deploy/cloudformation/bootstrap.yaml";
|
|
@@ -124,9 +135,8 @@ export declare const BOOTSTRAP_TEMPLATE_FILE = "deploy/cloudformation/bootstrap.
|
|
|
124
135
|
export declare function deployStackArgs(cfg: DeployConfig): string[];
|
|
125
136
|
/**
|
|
126
137
|
* The one-time `aws cloudformation deploy` command that creates the app's
|
|
127
|
-
* infrastructure (app.yaml)
|
|
128
|
-
*
|
|
129
|
-
* runs this for you; it is also printed by `--print` so it can be run by hand.
|
|
138
|
+
* infrastructure (app.yaml), from the in-repo template. `rastack init` runs this
|
|
139
|
+
* for you; it is also printed by `--print` so it can be run by hand.
|
|
130
140
|
*/
|
|
131
141
|
export declare function deployStackCommand(cfg: DeployConfig): string;
|
|
132
142
|
/**
|
package/dist/deploy/config.js
CHANGED
|
@@ -18,13 +18,13 @@
|
|
|
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.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;
|
|
21
|
+
exports.APP_STACK_OUTPUTS = exports.BOOTSTRAP_TEMPLATES_OUTPUT = exports.BOOTSTRAP_ARTIFACTS_OUTPUT = exports.BOOTSTRAP_TEMPLATE_FILE = exports.APP_TEMPLATE_FILE = exports.REGIONS_WITHOUT_COGNITO_ASF = 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
|
+
exports.cognitoAdvancedSecuritySupported = cognitoAdvancedSecuritySupported;
|
|
26
27
|
exports.stackParameters = stackParameters;
|
|
27
|
-
exports.templateUrl = templateUrl;
|
|
28
28
|
exports.deployStackArgs = deployStackArgs;
|
|
29
29
|
exports.deployStackCommand = deployStackCommand;
|
|
30
30
|
exports.bootstrapStackName = bootstrapStackName;
|
|
@@ -150,10 +150,43 @@ function parseGitHubRemote(url) {
|
|
|
150
150
|
return null;
|
|
151
151
|
return { owner, repo };
|
|
152
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Regions where Cognito advanced security (threat protection) is NOT offered, as
|
|
155
|
+
* of 2026-07. `AWS::Cognito::UserPool` carrying a `UserPoolAddOns` block is
|
|
156
|
+
* undeployable there — CloudFormation's early-validation ResourceExistenceCheck
|
|
157
|
+
* rejects the change set — so app.yaml must omit the add-on (AdvancedSecurity=OFF)
|
|
158
|
+
* in these regions. A denylist (not an allowlist) so new/standard regions keep
|
|
159
|
+
* the secure default; AWS expands coverage over time, so revisit when they do.
|
|
160
|
+
*/
|
|
161
|
+
exports.REGIONS_WITHOUT_COGNITO_ASF = new Set([
|
|
162
|
+
"af-south-1", // Cape Town
|
|
163
|
+
"ap-east-1", // Hong Kong
|
|
164
|
+
"ap-east-2", // Taipei
|
|
165
|
+
"ap-south-2", // Hyderabad
|
|
166
|
+
"ap-southeast-3", // Jakarta
|
|
167
|
+
"ap-southeast-4", // Melbourne
|
|
168
|
+
"ap-southeast-5", // Malaysia
|
|
169
|
+
"ap-southeast-7", // Thailand
|
|
170
|
+
"ca-west-1", // Calgary
|
|
171
|
+
"eu-central-2", // Zurich
|
|
172
|
+
"eu-south-2", // Spain
|
|
173
|
+
"me-central-1", // UAE
|
|
174
|
+
"mx-central-1", // Mexico (Central)
|
|
175
|
+
"us-gov-east-1",
|
|
176
|
+
"us-gov-west-1",
|
|
177
|
+
"cn-north-1", // Beijing
|
|
178
|
+
"cn-northwest-1", // Ningxia
|
|
179
|
+
]);
|
|
180
|
+
/** Whether Cognito advanced security can be enabled in a region. */
|
|
181
|
+
function cognitoAdvancedSecuritySupported(region) {
|
|
182
|
+
return !exports.REGIONS_WITHOUT_COGNITO_ASF.has(region);
|
|
183
|
+
}
|
|
153
184
|
/**
|
|
154
185
|
* The `--parameter-overrides` for `aws cloudformation deploy` of app.yaml,
|
|
155
186
|
* derived from the config. Order and keys match the template's `Parameters`.
|
|
156
|
-
* `ArtifactsBucket` (which has no template default) is included only when set
|
|
187
|
+
* `ArtifactsBucket` (which has no template default) is included only when set;
|
|
188
|
+
* `AdvancedSecurity` is forced OFF only in regions that lack the feature (else
|
|
189
|
+
* the template's ENFORCED default stands).
|
|
157
190
|
*/
|
|
158
191
|
function stackParameters(cfg) {
|
|
159
192
|
const params = [
|
|
@@ -166,14 +199,10 @@ function stackParameters(cfg) {
|
|
|
166
199
|
];
|
|
167
200
|
if (cfg.artifactsBucket)
|
|
168
201
|
params.push({ key: "ArtifactsBucket", value: cfg.artifactsBucket });
|
|
202
|
+
if (!cognitoAdvancedSecuritySupported(cfg.region))
|
|
203
|
+
params.push({ key: "AdvancedSecurity", value: "OFF" });
|
|
169
204
|
return params;
|
|
170
205
|
}
|
|
171
|
-
/** The S3 URL app.yaml is launched from, when the templates bucket is known. */
|
|
172
|
-
function templateUrl(cfg) {
|
|
173
|
-
if (!cfg.templatesBucket)
|
|
174
|
-
return null;
|
|
175
|
-
return `https://${cfg.templatesBucket}.s3.${cfg.region}.amazonaws.com/app.yaml`;
|
|
176
|
-
}
|
|
177
206
|
/** The in-repo CloudFormation templates `rastack init` deploys from. */
|
|
178
207
|
exports.APP_TEMPLATE_FILE = "deploy/cloudformation/app.yaml";
|
|
179
208
|
exports.BOOTSTRAP_TEMPLATE_FILE = "deploy/cloudformation/bootstrap.yaml";
|
|
@@ -181,15 +210,19 @@ exports.BOOTSTRAP_TEMPLATE_FILE = "deploy/cloudformation/bootstrap.yaml";
|
|
|
181
210
|
* The argv (after `aws`) for an `aws cloudformation deploy`. `init` feeds this to
|
|
182
211
|
* `execFileSync` (no shell), and the printed command string is just this joined
|
|
183
212
|
* with spaces — so what is shown is exactly what runs.
|
|
213
|
+
*
|
|
214
|
+
* Always `--template-file`: the high-level `aws cloudformation deploy` command
|
|
215
|
+
* accepts *only* a local template file. `--template-url` is exclusive to the
|
|
216
|
+
* low-level `create-stack`/`update-stack`/`create-change-set` commands, so
|
|
217
|
+
* pairing it with `deploy` fails with `the following arguments are required:
|
|
218
|
+
* --template-file`. `init` runs in the repo, where both templates are present.
|
|
184
219
|
*/
|
|
185
|
-
function cfnDeployArgs(
|
|
186
|
-
const t = template.url
|
|
187
|
-
? ["--template-url", template.url]
|
|
188
|
-
: ["--template-file", template.file];
|
|
220
|
+
function cfnDeployArgs(templateFile, stackName, region, params) {
|
|
189
221
|
return [
|
|
190
222
|
"cloudformation",
|
|
191
223
|
"deploy",
|
|
192
|
-
|
|
224
|
+
"--template-file",
|
|
225
|
+
templateFile,
|
|
193
226
|
"--stack-name",
|
|
194
227
|
stackName,
|
|
195
228
|
"--capabilities",
|
|
@@ -212,14 +245,12 @@ function awsCommand(args) {
|
|
|
212
245
|
}
|
|
213
246
|
/** The argv that creates the app's infrastructure (app.yaml). */
|
|
214
247
|
function deployStackArgs(cfg) {
|
|
215
|
-
|
|
216
|
-
return cfnDeployArgs(src ? { url: src } : { file: exports.APP_TEMPLATE_FILE }, cfg.stackName, cfg.region, stackParameters(cfg));
|
|
248
|
+
return cfnDeployArgs(exports.APP_TEMPLATE_FILE, cfg.stackName, cfg.region, stackParameters(cfg));
|
|
217
249
|
}
|
|
218
250
|
/**
|
|
219
251
|
* The one-time `aws cloudformation deploy` command that creates the app's
|
|
220
|
-
* infrastructure (app.yaml)
|
|
221
|
-
*
|
|
222
|
-
* runs this for you; it is also printed by `--print` so it can be run by hand.
|
|
252
|
+
* infrastructure (app.yaml), from the in-repo template. `rastack init` runs this
|
|
253
|
+
* for you; it is also printed by `--print` so it can be run by hand.
|
|
223
254
|
*/
|
|
224
255
|
function deployStackCommand(cfg) {
|
|
225
256
|
return awsCommand(deployStackArgs(cfg));
|
|
@@ -251,7 +282,7 @@ function bootstrapStackParameters(cfg, reuseOidc = false) {
|
|
|
251
282
|
}
|
|
252
283
|
/** The argv that creates the bootstrap stack (bootstrap.yaml). */
|
|
253
284
|
function bootstrapStackArgs(cfg, reuseOidc = false) {
|
|
254
|
-
return cfnDeployArgs(
|
|
285
|
+
return cfnDeployArgs(exports.BOOTSTRAP_TEMPLATE_FILE, bootstrapStackName(cfg), cfg.region, bootstrapStackParameters(cfg, reuseOidc));
|
|
255
286
|
}
|
|
256
287
|
/** The one-time `aws cloudformation deploy` command that creates the bootstrap stack. */
|
|
257
288
|
function bootstrapStackCommand(cfg, reuseOidc = false) {
|
package/dist/rastack-update.js
CHANGED
|
@@ -54,6 +54,8 @@ const child_process_1 = require("child_process");
|
|
|
54
54
|
const fs_1 = require("fs");
|
|
55
55
|
const path = __importStar(require("path"));
|
|
56
56
|
const update_1 = require("./update");
|
|
57
|
+
/** The npm executable for this platform (`npm.cmd` on Windows, else `npm`). */
|
|
58
|
+
const NPM = (0, update_1.npmBinary)();
|
|
57
59
|
/** The installed rastack package directory (…/rastack), one up from dist/. */
|
|
58
60
|
const PKG_DIR = path.join(__dirname, "..");
|
|
59
61
|
const PKG_JSON = path.join(PKG_DIR, "package.json");
|
|
@@ -69,7 +71,7 @@ function readVersion(file) {
|
|
|
69
71
|
/** Capture `npm <args>` stdout, or null when npm is absent / the call fails. */
|
|
70
72
|
function npmCapture(args) {
|
|
71
73
|
try {
|
|
72
|
-
return (0, child_process_1.execFileSync)(
|
|
74
|
+
return (0, child_process_1.execFileSync)(NPM, args, {
|
|
73
75
|
stdio: ["ignore", "pipe", "pipe"],
|
|
74
76
|
})
|
|
75
77
|
.toString()
|
|
@@ -119,7 +121,7 @@ function main(argv) {
|
|
|
119
121
|
// Re-runs the developer's own npm to update this global package — no
|
|
120
122
|
// untrusted input, and execFileSync (no shell) avoids any injection.
|
|
121
123
|
// nosemgrep: javascript.lang.security.detect-child-process.detect-child-process
|
|
122
|
-
(0, child_process_1.execFileSync)(
|
|
124
|
+
(0, child_process_1.execFileSync)(NPM, args, { stdio: "inherit", env });
|
|
123
125
|
}
|
|
124
126
|
catch (err) {
|
|
125
127
|
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}`);
|
package/dist/update.d.ts
CHANGED
|
@@ -23,6 +23,14 @@ export interface UpdateFlags {
|
|
|
23
23
|
* 0.0.40`). Unknown values default to `latest`.
|
|
24
24
|
*/
|
|
25
25
|
export declare function parseUpdateFlags(argv: string[]): UpdateFlags;
|
|
26
|
+
/**
|
|
27
|
+
* The npm executable to spawn for a platform. On Windows npm is a `.cmd` shim,
|
|
28
|
+
* not a real executable, so `execFile("npm", …)` (no shell) fails with
|
|
29
|
+
* `spawnSync npm ENOENT` — the direct-spawn path only resolves real binaries,
|
|
30
|
+
* never `PATHEXT` scripts. Spawn `npm.cmd` there; POSIX keeps plain `npm`.
|
|
31
|
+
* (`aws` is unaffected — it installs as `aws.exe`, a real executable.)
|
|
32
|
+
*/
|
|
33
|
+
export declare function npmBinary(platform?: string): string;
|
|
26
34
|
/** The npm package spec to install for a target version. */
|
|
27
35
|
export declare function installSpec(version: string): string;
|
|
28
36
|
/** The `npm <args>` (after the `npm` binary) that installs the target globally. */
|
package/dist/update.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
14
|
exports.parseUpdateFlags = parseUpdateFlags;
|
|
15
|
+
exports.npmBinary = npmBinary;
|
|
15
16
|
exports.installSpec = installSpec;
|
|
16
17
|
exports.npmInstallArgs = npmInstallArgs;
|
|
17
18
|
exports.globalPrefixFromPackageDir = globalPrefixFromPackageDir;
|
|
@@ -48,6 +49,16 @@ function parseUpdateFlags(argv) {
|
|
|
48
49
|
}
|
|
49
50
|
return { version, check, dryRun };
|
|
50
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* The npm executable to spawn for a platform. On Windows npm is a `.cmd` shim,
|
|
54
|
+
* not a real executable, so `execFile("npm", …)` (no shell) fails with
|
|
55
|
+
* `spawnSync npm ENOENT` — the direct-spawn path only resolves real binaries,
|
|
56
|
+
* never `PATHEXT` scripts. Spawn `npm.cmd` there; POSIX keeps plain `npm`.
|
|
57
|
+
* (`aws` is unaffected — it installs as `aws.exe`, a real executable.)
|
|
58
|
+
*/
|
|
59
|
+
function npmBinary(platform = process.platform) {
|
|
60
|
+
return platform === "win32" ? "npm.cmd" : "npm";
|
|
61
|
+
}
|
|
51
62
|
/** The npm package spec to install for a target version. */
|
|
52
63
|
function installSpec(version) {
|
|
53
64
|
return !version || version === "latest" ? "rastack" : `rastack@${version}`;
|
package/package.json
CHANGED
package/src/deploy/config.ts
CHANGED
|
@@ -63,7 +63,7 @@ export interface DeployConfig {
|
|
|
63
63
|
resourcesDir: string;
|
|
64
64
|
/** Where `rastack compile` writes the manifest + OpenAPI. */
|
|
65
65
|
outDir: string;
|
|
66
|
-
/** Bootstrap templates bucket that hosts `app.yaml` (for
|
|
66
|
+
/** Bootstrap templates bucket that hosts `app.yaml` (recorded for reference). */
|
|
67
67
|
templatesBucket?: string;
|
|
68
68
|
/** Bootstrap artifacts bucket holding the Lambda placeholder bundle. */
|
|
69
69
|
artifactsBucket?: string;
|
|
@@ -227,10 +227,45 @@ export interface StackParameter {
|
|
|
227
227
|
value: string;
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
+
/**
|
|
231
|
+
* Regions where Cognito advanced security (threat protection) is NOT offered, as
|
|
232
|
+
* of 2026-07. `AWS::Cognito::UserPool` carrying a `UserPoolAddOns` block is
|
|
233
|
+
* undeployable there — CloudFormation's early-validation ResourceExistenceCheck
|
|
234
|
+
* rejects the change set — so app.yaml must omit the add-on (AdvancedSecurity=OFF)
|
|
235
|
+
* in these regions. A denylist (not an allowlist) so new/standard regions keep
|
|
236
|
+
* the secure default; AWS expands coverage over time, so revisit when they do.
|
|
237
|
+
*/
|
|
238
|
+
export const REGIONS_WITHOUT_COGNITO_ASF: ReadonlySet<string> = new Set([
|
|
239
|
+
"af-south-1", // Cape Town
|
|
240
|
+
"ap-east-1", // Hong Kong
|
|
241
|
+
"ap-east-2", // Taipei
|
|
242
|
+
"ap-south-2", // Hyderabad
|
|
243
|
+
"ap-southeast-3", // Jakarta
|
|
244
|
+
"ap-southeast-4", // Melbourne
|
|
245
|
+
"ap-southeast-5", // Malaysia
|
|
246
|
+
"ap-southeast-7", // Thailand
|
|
247
|
+
"ca-west-1", // Calgary
|
|
248
|
+
"eu-central-2", // Zurich
|
|
249
|
+
"eu-south-2", // Spain
|
|
250
|
+
"me-central-1", // UAE
|
|
251
|
+
"mx-central-1", // Mexico (Central)
|
|
252
|
+
"us-gov-east-1",
|
|
253
|
+
"us-gov-west-1",
|
|
254
|
+
"cn-north-1", // Beijing
|
|
255
|
+
"cn-northwest-1", // Ningxia
|
|
256
|
+
]);
|
|
257
|
+
|
|
258
|
+
/** Whether Cognito advanced security can be enabled in a region. */
|
|
259
|
+
export function cognitoAdvancedSecuritySupported(region: string): boolean {
|
|
260
|
+
return !REGIONS_WITHOUT_COGNITO_ASF.has(region);
|
|
261
|
+
}
|
|
262
|
+
|
|
230
263
|
/**
|
|
231
264
|
* The `--parameter-overrides` for `aws cloudformation deploy` of app.yaml,
|
|
232
265
|
* derived from the config. Order and keys match the template's `Parameters`.
|
|
233
|
-
* `ArtifactsBucket` (which has no template default) is included only when set
|
|
266
|
+
* `ArtifactsBucket` (which has no template default) is included only when set;
|
|
267
|
+
* `AdvancedSecurity` is forced OFF only in regions that lack the feature (else
|
|
268
|
+
* the template's ENFORCED default stands).
|
|
234
269
|
*/
|
|
235
270
|
export function stackParameters(cfg: DeployConfig): StackParameter[] {
|
|
236
271
|
const params: StackParameter[] = [
|
|
@@ -243,15 +278,11 @@ export function stackParameters(cfg: DeployConfig): StackParameter[] {
|
|
|
243
278
|
];
|
|
244
279
|
if (cfg.artifactsBucket)
|
|
245
280
|
params.push({ key: "ArtifactsBucket", value: cfg.artifactsBucket });
|
|
281
|
+
if (!cognitoAdvancedSecuritySupported(cfg.region))
|
|
282
|
+
params.push({ key: "AdvancedSecurity", value: "OFF" });
|
|
246
283
|
return params;
|
|
247
284
|
}
|
|
248
285
|
|
|
249
|
-
/** The S3 URL app.yaml is launched from, when the templates bucket is known. */
|
|
250
|
-
export function templateUrl(cfg: DeployConfig): string | null {
|
|
251
|
-
if (!cfg.templatesBucket) return null;
|
|
252
|
-
return `https://${cfg.templatesBucket}.s3.${cfg.region}.amazonaws.com/app.yaml`;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
286
|
/** The in-repo CloudFormation templates `rastack init` deploys from. */
|
|
256
287
|
export const APP_TEMPLATE_FILE = "deploy/cloudformation/app.yaml";
|
|
257
288
|
export const BOOTSTRAP_TEMPLATE_FILE = "deploy/cloudformation/bootstrap.yaml";
|
|
@@ -260,20 +291,24 @@ export const BOOTSTRAP_TEMPLATE_FILE = "deploy/cloudformation/bootstrap.yaml";
|
|
|
260
291
|
* The argv (after `aws`) for an `aws cloudformation deploy`. `init` feeds this to
|
|
261
292
|
* `execFileSync` (no shell), and the printed command string is just this joined
|
|
262
293
|
* with spaces — so what is shown is exactly what runs.
|
|
294
|
+
*
|
|
295
|
+
* Always `--template-file`: the high-level `aws cloudformation deploy` command
|
|
296
|
+
* accepts *only* a local template file. `--template-url` is exclusive to the
|
|
297
|
+
* low-level `create-stack`/`update-stack`/`create-change-set` commands, so
|
|
298
|
+
* pairing it with `deploy` fails with `the following arguments are required:
|
|
299
|
+
* --template-file`. `init` runs in the repo, where both templates are present.
|
|
263
300
|
*/
|
|
264
301
|
function cfnDeployArgs(
|
|
265
|
-
|
|
302
|
+
templateFile: string,
|
|
266
303
|
stackName: string,
|
|
267
304
|
region: string,
|
|
268
305
|
params: StackParameter[],
|
|
269
306
|
): string[] {
|
|
270
|
-
const t = template.url
|
|
271
|
-
? ["--template-url", template.url]
|
|
272
|
-
: ["--template-file", template.file!];
|
|
273
307
|
return [
|
|
274
308
|
"cloudformation",
|
|
275
309
|
"deploy",
|
|
276
|
-
|
|
310
|
+
"--template-file",
|
|
311
|
+
templateFile,
|
|
277
312
|
"--stack-name",
|
|
278
313
|
stackName,
|
|
279
314
|
"--capabilities",
|
|
@@ -298,9 +333,8 @@ function awsCommand(args: string[]): string {
|
|
|
298
333
|
|
|
299
334
|
/** The argv that creates the app's infrastructure (app.yaml). */
|
|
300
335
|
export function deployStackArgs(cfg: DeployConfig): string[] {
|
|
301
|
-
const src = templateUrl(cfg);
|
|
302
336
|
return cfnDeployArgs(
|
|
303
|
-
|
|
337
|
+
APP_TEMPLATE_FILE,
|
|
304
338
|
cfg.stackName,
|
|
305
339
|
cfg.region,
|
|
306
340
|
stackParameters(cfg),
|
|
@@ -309,9 +343,8 @@ export function deployStackArgs(cfg: DeployConfig): string[] {
|
|
|
309
343
|
|
|
310
344
|
/**
|
|
311
345
|
* The one-time `aws cloudformation deploy` command that creates the app's
|
|
312
|
-
* infrastructure (app.yaml)
|
|
313
|
-
*
|
|
314
|
-
* runs this for you; it is also printed by `--print` so it can be run by hand.
|
|
346
|
+
* infrastructure (app.yaml), from the in-repo template. `rastack init` runs this
|
|
347
|
+
* for you; it is also printed by `--print` so it can be run by hand.
|
|
315
348
|
*/
|
|
316
349
|
export function deployStackCommand(cfg: DeployConfig): string {
|
|
317
350
|
return awsCommand(deployStackArgs(cfg));
|
|
@@ -352,7 +385,7 @@ export function bootstrapStackArgs(
|
|
|
352
385
|
reuseOidc = false,
|
|
353
386
|
): string[] {
|
|
354
387
|
return cfnDeployArgs(
|
|
355
|
-
|
|
388
|
+
BOOTSTRAP_TEMPLATE_FILE,
|
|
356
389
|
bootstrapStackName(cfg),
|
|
357
390
|
cfg.region,
|
|
358
391
|
bootstrapStackParameters(cfg, reuseOidc),
|
package/src/rastack-update.ts
CHANGED
|
@@ -22,11 +22,15 @@ import { readFileSync } from "fs";
|
|
|
22
22
|
import * as path from "path";
|
|
23
23
|
import {
|
|
24
24
|
globalPrefixFromPackageDir,
|
|
25
|
+
npmBinary,
|
|
25
26
|
npmInstallArgs,
|
|
26
27
|
parseUpdateFlags,
|
|
27
28
|
updateAvailable,
|
|
28
29
|
} from "./update";
|
|
29
30
|
|
|
31
|
+
/** The npm executable for this platform (`npm.cmd` on Windows, else `npm`). */
|
|
32
|
+
const NPM = npmBinary();
|
|
33
|
+
|
|
30
34
|
/** The installed rastack package directory (…/rastack), one up from dist/. */
|
|
31
35
|
const PKG_DIR = path.join(__dirname, "..");
|
|
32
36
|
const PKG_JSON = path.join(PKG_DIR, "package.json");
|
|
@@ -43,7 +47,7 @@ function readVersion(file: string): string {
|
|
|
43
47
|
/** Capture `npm <args>` stdout, or null when npm is absent / the call fails. */
|
|
44
48
|
function npmCapture(args: string[]): string | null {
|
|
45
49
|
try {
|
|
46
|
-
return execFileSync(
|
|
50
|
+
return execFileSync(NPM, args, {
|
|
47
51
|
stdio: ["ignore", "pipe", "pipe"],
|
|
48
52
|
})
|
|
49
53
|
.toString()
|
|
@@ -102,7 +106,7 @@ function main(argv: string[]): void {
|
|
|
102
106
|
// Re-runs the developer's own npm to update this global package — no
|
|
103
107
|
// untrusted input, and execFileSync (no shell) avoids any injection.
|
|
104
108
|
// nosemgrep: javascript.lang.security.detect-child-process.detect-child-process
|
|
105
|
-
execFileSync(
|
|
109
|
+
execFileSync(NPM, args, { stdio: "inherit", env });
|
|
106
110
|
} catch (err) {
|
|
107
111
|
console.error(
|
|
108
112
|
`\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
|
@@ -50,6 +50,17 @@ export function parseUpdateFlags(argv: string[]): UpdateFlags {
|
|
|
50
50
|
return { version, check, dryRun };
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* The npm executable to spawn for a platform. On Windows npm is a `.cmd` shim,
|
|
55
|
+
* not a real executable, so `execFile("npm", …)` (no shell) fails with
|
|
56
|
+
* `spawnSync npm ENOENT` — the direct-spawn path only resolves real binaries,
|
|
57
|
+
* never `PATHEXT` scripts. Spawn `npm.cmd` there; POSIX keeps plain `npm`.
|
|
58
|
+
* (`aws` is unaffected — it installs as `aws.exe`, a real executable.)
|
|
59
|
+
*/
|
|
60
|
+
export function npmBinary(platform: string = process.platform): string {
|
|
61
|
+
return platform === "win32" ? "npm.cmd" : "npm";
|
|
62
|
+
}
|
|
63
|
+
|
|
53
64
|
/** The npm package spec to install for a target version. */
|
|
54
65
|
export function installSpec(version: string): string {
|
|
55
66
|
return !version || version === "latest" ? "rastack" : `rastack@${version}`;
|
package/test/deploy.spec.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
bootstrapStackCommand,
|
|
12
12
|
bootstrapStackName,
|
|
13
13
|
bootstrapStackParameters,
|
|
14
|
+
cognitoAdvancedSecuritySupported,
|
|
14
15
|
APP_STACK_OUTPUTS,
|
|
15
16
|
lambdaEnv,
|
|
16
17
|
mergeConfig,
|
|
@@ -19,7 +20,6 @@ import {
|
|
|
19
20
|
repoVariables,
|
|
20
21
|
scaffoldFiles,
|
|
21
22
|
stackParameters,
|
|
22
|
-
templateUrl,
|
|
23
23
|
validateConfig,
|
|
24
24
|
zipStore,
|
|
25
25
|
renderCiWorkflow,
|
|
@@ -169,7 +169,10 @@ describe("parseGitHubRemote", () => {
|
|
|
169
169
|
|
|
170
170
|
describe("stack parameters + launch command", () => {
|
|
171
171
|
it("emits the app.yaml parameters in template order", () => {
|
|
172
|
-
|
|
172
|
+
// A region WITH Cognito advanced security, so AdvancedSecurity is left off.
|
|
173
|
+
const params = stackParameters(
|
|
174
|
+
base({ region: "us-east-1", artifactsBucket: "art-bucket" }),
|
|
175
|
+
);
|
|
173
176
|
expect(params.map((p) => p.key)).toEqual([
|
|
174
177
|
"AppName",
|
|
175
178
|
"NamePrefix",
|
|
@@ -182,30 +185,41 @@ describe("stack parameters + launch command", () => {
|
|
|
182
185
|
});
|
|
183
186
|
|
|
184
187
|
it("omits ArtifactsBucket when unset (it has no template default)", () => {
|
|
185
|
-
expect(
|
|
186
|
-
"
|
|
187
|
-
);
|
|
188
|
+
expect(
|
|
189
|
+
stackParameters(base({ region: "us-east-1" })).map((p) => p.key),
|
|
190
|
+
).not.toContain("ArtifactsBucket");
|
|
188
191
|
});
|
|
189
192
|
|
|
190
|
-
it("
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
193
|
+
it("forces AdvancedSecurity=OFF only in regions lacking Cognito ASF", () => {
|
|
194
|
+
// af-south-1 (the repo default) has no threat protection — a User Pool with
|
|
195
|
+
// a UserPoolAddOns block is undeployable, so the override must be emitted.
|
|
196
|
+
expect(cognitoAdvancedSecuritySupported("af-south-1")).toBe(false);
|
|
197
|
+
expect(cognitoAdvancedSecuritySupported("us-east-1")).toBe(true);
|
|
198
|
+
|
|
199
|
+
const capeTown = stackParameters(base({ region: "af-south-1" }));
|
|
200
|
+
expect(capeTown.find((p) => p.key === "AdvancedSecurity")?.value).toBe(
|
|
201
|
+
"OFF",
|
|
194
202
|
);
|
|
203
|
+
|
|
204
|
+
const virginia = stackParameters(base({ region: "us-east-1" }));
|
|
205
|
+
expect(virginia.map((p) => p.key)).not.toContain("AdvancedSecurity");
|
|
195
206
|
});
|
|
196
207
|
|
|
197
|
-
it("launches
|
|
208
|
+
it("always launches from the in-repo --template-file (deploy takes no URL)", () => {
|
|
209
|
+
// `aws cloudformation deploy` accepts ONLY `--template-file`; `--template-url`
|
|
210
|
+
// is exclusive to create-stack/update-stack and makes deploy reject the
|
|
211
|
+
// command. Even with a known templates bucket, the local file is used.
|
|
198
212
|
const hosted = deployStackCommand(
|
|
199
213
|
base({ templatesBucket: "tpl", artifactsBucket: "art" }),
|
|
200
214
|
);
|
|
201
|
-
expect(hosted).toContain(
|
|
202
|
-
|
|
203
|
-
);
|
|
215
|
+
expect(hosted).toContain("--template-file deploy/cloudformation/app.yaml");
|
|
216
|
+
expect(hosted).not.toContain("--template-url");
|
|
204
217
|
expect(hosted).toContain("--stack-name rastack-airline");
|
|
205
218
|
expect(hosted).toContain("ArtifactsBucket=art");
|
|
206
219
|
|
|
207
220
|
const local = deployStackCommand(base());
|
|
208
221
|
expect(local).toContain("--template-file deploy/cloudformation/app.yaml");
|
|
222
|
+
expect(local).not.toContain("--template-url");
|
|
209
223
|
});
|
|
210
224
|
|
|
211
225
|
it("emits ONE line (PowerShell-safe — no backslash continuations)", () => {
|
package/test/update.spec.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
compareSemver,
|
|
3
3
|
globalPrefixFromPackageDir,
|
|
4
4
|
installSpec,
|
|
5
|
+
npmBinary,
|
|
5
6
|
npmInstallArgs,
|
|
6
7
|
parseUpdateFlags,
|
|
7
8
|
updateAvailable,
|
|
@@ -47,6 +48,14 @@ describe("update — npm spec + args", () => {
|
|
|
47
48
|
]);
|
|
48
49
|
expect(npmInstallArgs("0.0.40")[2]).toBe("rastack@0.0.40");
|
|
49
50
|
});
|
|
51
|
+
|
|
52
|
+
it("spawns npm.cmd on Windows and plain npm elsewhere", () => {
|
|
53
|
+
// npm is a `.cmd` shim on Windows; execFile (no shell) can't launch a bare
|
|
54
|
+
// `npm` there, so the wrapper must target `npm.cmd`. POSIX keeps `npm`.
|
|
55
|
+
expect(npmBinary("win32")).toBe("npm.cmd");
|
|
56
|
+
expect(npmBinary("linux")).toBe("npm");
|
|
57
|
+
expect(npmBinary("darwin")).toBe("npm");
|
|
58
|
+
});
|
|
50
59
|
});
|
|
51
60
|
|
|
52
61
|
describe("update — global prefix derivation", () => {
|