rastack 0.0.37 → 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 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.38](https://github.com/theserverkid/reactapistack/compare/v0.0.37...v0.0.38) (2026-07-11)
6
+
5
7
  ### [0.0.37](https://github.com/theserverkid/reactapistack/compare/v0.0.36...v0.0.37) (2026-07-11)
6
8
 
7
9
  ### [0.0.36](https://github.com/theserverkid/reactapistack/compare/v0.0.35...v0.0.36) (2026-07-11)
@@ -109,10 +109,23 @@ 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
131
  /** The in-repo CloudFormation templates `rastack init` deploys from. */
@@ -18,11 +18,12 @@
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
28
  exports.deployStackArgs = deployStackArgs;
28
29
  exports.deployStackCommand = deployStackCommand;
@@ -149,10 +150,43 @@ function parseGitHubRemote(url) {
149
150
  return null;
150
151
  return { owner, repo };
151
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
+ }
152
184
  /**
153
185
  * The `--parameter-overrides` for `aws cloudformation deploy` of app.yaml,
154
186
  * derived from the config. Order and keys match the template's `Parameters`.
155
- * `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).
156
190
  */
157
191
  function stackParameters(cfg) {
158
192
  const params = [
@@ -165,6 +199,8 @@ function stackParameters(cfg) {
165
199
  ];
166
200
  if (cfg.artifactsBucket)
167
201
  params.push({ key: "ArtifactsBucket", value: cfg.artifactsBucket });
202
+ if (!cognitoAdvancedSecuritySupported(cfg.region))
203
+ params.push({ key: "AdvancedSecurity", value: "OFF" });
168
204
  return params;
169
205
  }
170
206
  /** The in-repo CloudFormation templates `rastack init` deploys from. */
@@ -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)("npm", args, {
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)("npm", args, { stdio: "inherit", env });
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rastack",
3
- "version": "0.0.37",
3
+ "version": "0.0.38",
4
4
  "description": "",
5
5
  "main": "runtime.ts",
6
6
  "types": "runtime.ts",
@@ -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,6 +278,8 @@ 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
 
@@ -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("npm", args, {
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("npm", args, { stdio: "inherit", env });
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}`;
@@ -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,
@@ -168,7 +169,10 @@ describe("parseGitHubRemote", () => {
168
169
 
169
170
  describe("stack parameters + launch command", () => {
170
171
  it("emits the app.yaml parameters in template order", () => {
171
- const params = stackParameters(base({ artifactsBucket: "art-bucket" }));
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
+ );
172
176
  expect(params.map((p) => p.key)).toEqual([
173
177
  "AppName",
174
178
  "NamePrefix",
@@ -181,9 +185,24 @@ describe("stack parameters + launch command", () => {
181
185
  });
182
186
 
183
187
  it("omits ArtifactsBucket when unset (it has no template default)", () => {
184
- expect(stackParameters(base()).map((p) => p.key)).not.toContain(
185
- "ArtifactsBucket",
188
+ expect(
189
+ stackParameters(base({ region: "us-east-1" })).map((p) => p.key),
190
+ ).not.toContain("ArtifactsBucket");
191
+ });
192
+
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",
186
202
  );
203
+
204
+ const virginia = stackParameters(base({ region: "us-east-1" }));
205
+ expect(virginia.map((p) => p.key)).not.toContain("AdvancedSecurity");
187
206
  });
188
207
 
189
208
  it("always launches from the in-repo --template-file (deploy takes no URL)", () => {
@@ -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", () => {