@aws-blocks/core 0.2.0 → 0.4.0

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.
Files changed (178) hide show
  1. package/README.md +13 -0
  2. package/dist/api.d.ts.map +1 -1
  3. package/dist/api.js +32 -0
  4. package/dist/api.test.js +22 -0
  5. package/dist/bb-utils.d.ts +1 -0
  6. package/dist/bb-utils.d.ts.map +1 -1
  7. package/dist/bb-utils.js +3 -0
  8. package/dist/cdk/apigateway-account.d.ts +33 -0
  9. package/dist/cdk/apigateway-account.d.ts.map +1 -0
  10. package/dist/cdk/apigateway-account.js +60 -0
  11. package/dist/cdk/blocks-backend.d.ts +43 -9
  12. package/dist/cdk/blocks-backend.d.ts.map +1 -1
  13. package/dist/cdk/blocks-backend.js +69 -73
  14. package/dist/cdk/blocks-backend.test.js +78 -69
  15. package/dist/cdk/blocks-defaults.d.ts +92 -2
  16. package/dist/cdk/blocks-defaults.d.ts.map +1 -1
  17. package/dist/cdk/blocks-defaults.js +22 -2
  18. package/dist/cdk/blocks-defaults.test.js +21 -2
  19. package/dist/cdk/blocks-stack.test.js +94 -37
  20. package/dist/cdk/compute/compute-registry.d.ts +19 -0
  21. package/dist/cdk/compute/compute-registry.d.ts.map +1 -0
  22. package/dist/cdk/compute/compute-registry.js +38 -0
  23. package/dist/cdk/compute/compute.d.ts +2 -0
  24. package/dist/cdk/compute/compute.d.ts.map +1 -1
  25. package/dist/cdk/compute/compute.js +8 -0
  26. package/dist/cdk/compute/default-compute-factory.d.ts +34 -0
  27. package/dist/cdk/compute/default-compute-factory.d.ts.map +1 -0
  28. package/dist/cdk/compute/default-compute-factory.js +3 -0
  29. package/dist/cdk/config-registry.d.ts +34 -4
  30. package/dist/cdk/config-registry.d.ts.map +1 -1
  31. package/dist/cdk/config-registry.js +83 -25
  32. package/dist/cdk/config-registry.test.d.ts +2 -0
  33. package/dist/cdk/config-registry.test.d.ts.map +1 -0
  34. package/dist/cdk/config-registry.test.js +115 -0
  35. package/dist/cdk/index.d.ts +76 -7
  36. package/dist/cdk/index.d.ts.map +1 -1
  37. package/dist/cdk/index.js +99 -9
  38. package/dist/cdk/internal.d.ts +1 -0
  39. package/dist/cdk/internal.d.ts.map +1 -1
  40. package/dist/client/index.d.ts +1 -1
  41. package/dist/client/index.d.ts.map +1 -1
  42. package/dist/client/index.js +1 -1
  43. package/dist/common/config.d.ts +34 -0
  44. package/dist/common/config.d.ts.map +1 -1
  45. package/dist/common/config.js +45 -3
  46. package/dist/common/config.test.js +19 -0
  47. package/dist/common/index.d.ts +8 -0
  48. package/dist/common/index.d.ts.map +1 -1
  49. package/dist/errors.d.ts +16 -0
  50. package/dist/errors.d.ts.map +1 -1
  51. package/dist/errors.js +20 -0
  52. package/dist/hosting-secrets.d.ts +48 -0
  53. package/dist/hosting-secrets.d.ts.map +1 -0
  54. package/dist/hosting-secrets.js +57 -0
  55. package/dist/hosting-secrets.test.d.ts +2 -0
  56. package/dist/hosting-secrets.test.d.ts.map +1 -0
  57. package/dist/hosting-secrets.test.js +81 -0
  58. package/dist/hosting.d.ts +97 -6
  59. package/dist/hosting.d.ts.map +1 -1
  60. package/dist/hosting.js +91 -23
  61. package/dist/hosting.test.js +182 -10
  62. package/dist/index.cdk.d.ts +13 -12
  63. package/dist/index.cdk.d.ts.map +1 -1
  64. package/dist/index.cdk.js +13 -9
  65. package/dist/index.d.ts +8 -7
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +9 -5
  68. package/dist/lambda-handler.d.ts +17 -0
  69. package/dist/lambda-handler.d.ts.map +1 -1
  70. package/dist/lambda-handler.js +52 -3
  71. package/dist/lambda-handler.test.js +196 -1
  72. package/dist/pipeline/index.d.ts +24 -2
  73. package/dist/pipeline/index.d.ts.map +1 -1
  74. package/dist/pipeline/index.js +41 -1
  75. package/dist/pipeline.test.d.ts +2 -0
  76. package/dist/pipeline.test.d.ts.map +1 -0
  77. package/dist/pipeline.test.js +47 -0
  78. package/dist/rpc.d.ts +21 -0
  79. package/dist/rpc.d.ts.map +1 -1
  80. package/dist/rpc.js +34 -0
  81. package/dist/rpc.test.js +38 -1
  82. package/dist/scripts/config.d.ts +15 -0
  83. package/dist/scripts/config.d.ts.map +1 -0
  84. package/dist/scripts/config.js +29 -0
  85. package/dist/scripts/deploy.d.ts.map +1 -1
  86. package/dist/scripts/deploy.js +4 -0
  87. package/dist/scripts/dev-server.d.ts +8 -0
  88. package/dist/scripts/dev-server.d.ts.map +1 -1
  89. package/dist/scripts/dev-server.js +31 -0
  90. package/dist/scripts/generate-client.d.ts.map +1 -1
  91. package/dist/scripts/generate-client.js +9 -4
  92. package/dist/scripts/generate-client.test.js +19 -4
  93. package/dist/scripts/index.d.ts +13 -10
  94. package/dist/scripts/index.d.ts.map +1 -1
  95. package/dist/scripts/index.js +11 -8
  96. package/dist/scripts/preflight-credentials.d.ts +30 -0
  97. package/dist/scripts/preflight-credentials.d.ts.map +1 -0
  98. package/dist/scripts/preflight-credentials.js +108 -0
  99. package/dist/scripts/preflight-credentials.test.d.ts +2 -0
  100. package/dist/scripts/preflight-credentials.test.d.ts.map +1 -0
  101. package/dist/scripts/preflight-credentials.test.js +77 -0
  102. package/dist/scripts/sandbox-args.test.d.ts +2 -0
  103. package/dist/scripts/sandbox-args.test.d.ts.map +1 -0
  104. package/dist/scripts/sandbox-args.test.js +77 -0
  105. package/dist/scripts/sandbox-empty-buckets.test.d.ts +2 -0
  106. package/dist/scripts/sandbox-empty-buckets.test.d.ts.map +1 -0
  107. package/dist/scripts/sandbox-empty-buckets.test.js +171 -0
  108. package/dist/scripts/sandbox.d.ts +106 -0
  109. package/dist/scripts/sandbox.d.ts.map +1 -1
  110. package/dist/scripts/sandbox.js +255 -40
  111. package/dist/scripts/secret.d.ts +15 -0
  112. package/dist/scripts/secret.d.ts.map +1 -0
  113. package/dist/scripts/secret.js +29 -0
  114. package/dist/scripts/secret.test.d.ts +2 -0
  115. package/dist/scripts/secret.test.d.ts.map +1 -0
  116. package/dist/scripts/secret.test.js +28 -0
  117. package/dist/scripts/typegen.d.ts +15 -0
  118. package/dist/scripts/typegen.d.ts.map +1 -0
  119. package/dist/scripts/typegen.js +16 -0
  120. package/dist/secret-naming.d.ts +58 -0
  121. package/dist/secret-naming.d.ts.map +1 -0
  122. package/dist/secret-naming.js +67 -0
  123. package/dist/secret-naming.test.d.ts +2 -0
  124. package/dist/secret-naming.test.d.ts.map +1 -0
  125. package/dist/secret-naming.test.js +40 -0
  126. package/dist/version.d.ts +1 -1
  127. package/dist/version.js +1 -1
  128. package/package.json +4 -1
  129. package/src/api.test.ts +25 -0
  130. package/src/api.ts +39 -0
  131. package/src/bb-utils.ts +3 -0
  132. package/src/cdk/apigateway-account.ts +66 -0
  133. package/src/cdk/blocks-backend.test.ts +291 -272
  134. package/src/cdk/blocks-backend.ts +88 -83
  135. package/src/cdk/blocks-defaults.test.ts +25 -2
  136. package/src/cdk/blocks-defaults.ts +94 -2
  137. package/src/cdk/blocks-stack.test.ts +186 -118
  138. package/src/cdk/compute/compute-registry.ts +45 -0
  139. package/src/cdk/compute/compute.ts +10 -0
  140. package/src/cdk/compute/default-compute-factory.ts +38 -0
  141. package/src/cdk/config-registry.test.ts +135 -0
  142. package/src/cdk/config-registry.ts +92 -34
  143. package/src/cdk/index.ts +129 -10
  144. package/src/cdk/internal.ts +1 -0
  145. package/src/client/index.ts +1 -1
  146. package/src/common/config.test.ts +21 -0
  147. package/src/common/config.ts +47 -3
  148. package/src/common/index.ts +8 -0
  149. package/src/errors.ts +21 -0
  150. package/src/hosting-secrets.test.ts +106 -0
  151. package/src/hosting-secrets.ts +106 -0
  152. package/src/hosting.test.ts +272 -63
  153. package/src/hosting.ts +200 -68
  154. package/src/index.cdk.ts +80 -38
  155. package/src/index.ts +35 -16
  156. package/src/lambda-handler.test.ts +215 -1
  157. package/src/lambda-handler.ts +54 -2
  158. package/src/pipeline/index.ts +64 -11
  159. package/src/pipeline.test.ts +55 -0
  160. package/src/rpc.test.ts +44 -1
  161. package/src/rpc.ts +41 -0
  162. package/src/scripts/config.ts +35 -0
  163. package/src/scripts/deploy.ts +5 -0
  164. package/src/scripts/dev-server.ts +40 -0
  165. package/src/scripts/generate-client.test.ts +43 -8
  166. package/src/scripts/generate-client.ts +70 -65
  167. package/src/scripts/index.ts +20 -17
  168. package/src/scripts/preflight-credentials.test.ts +110 -0
  169. package/src/scripts/preflight-credentials.ts +131 -0
  170. package/src/scripts/sandbox-args.test.ts +92 -0
  171. package/src/scripts/sandbox-empty-buckets.test.ts +191 -0
  172. package/src/scripts/sandbox.ts +287 -39
  173. package/src/scripts/secret.test.ts +35 -0
  174. package/src/scripts/secret.ts +35 -0
  175. package/src/scripts/typegen.ts +18 -0
  176. package/src/secret-naming.test.ts +59 -0
  177. package/src/secret-naming.ts +99 -0
  178. package/src/version.ts +1 -1
@@ -75,6 +75,31 @@ export function buildBlocksConfig(port, isSandbox) {
75
75
  export function isBlocksConfigRequest(method, pathname) {
76
76
  return method === 'GET' && pathname === `${BLOCKS_SANDBOX_PREFIX}/config.json`;
77
77
  }
78
+ /**
79
+ * Bootstrap the type-safe `getSecret`/`getConfig` key generation for the dev
80
+ * session. Auto-detected and non-fatal: scans the app for `secret()`/`config()`
81
+ * calls and, only if it finds any, generates the augmentation `.d.ts` and starts a
82
+ * watcher that regenerates on save — so a single `npm run dev` gives type-safe keys
83
+ * with no second command. Returns a `stop()` (or `undefined` when the app declares
84
+ * no secrets, typegen is unavailable, or it is disabled). The watcher is `unref`'d
85
+ * so it can never block the dev server's shutdown.
86
+ */
87
+ async function startTypegenWatch() {
88
+ try {
89
+ const { scanValueKeys, watchHostingValues } = await import('@aws-blocks/hosting/scripts');
90
+ const scan = await scanValueKeys();
91
+ if (scan.secretKeys.length === 0 && scan.configKeys.length === 0) {
92
+ return undefined; // app doesn't use secret()/config() → nothing to type, skip silently
93
+ }
94
+ console.log('🔑 Type-safe secret()/config() keys — watching for changes...');
95
+ return await watchHostingValues({ unref: true });
96
+ }
97
+ catch (error) {
98
+ // Never break the dev server over typegen (e.g. `typescript` not installed).
99
+ console.warn(`⚠️ hosting-typegen skipped: ${error instanceof Error ? error.message : String(error)}`);
100
+ return undefined;
101
+ }
102
+ }
78
103
  /**
79
104
  * Initialize all building blocks that have an initialize() method.
80
105
  * This is the "local deploy" phase - mirrors CDK deploy.
@@ -790,6 +815,11 @@ export async function startDevServer(options) {
790
815
  console.log('📝 Generating client code...');
791
816
  await writeClientCode(resolvedPath, clientPath);
792
817
  }
818
+ // Type-safe getSecret/getConfig: generate + watch the key augmentation so the
819
+ // getters autocomplete and reject typos, regenerating on save — all under this
820
+ // one `npm run dev`. Auto-detected (no-op unless the app uses secret()/config())
821
+ // and non-fatal.
822
+ const stopTypegen = options.typegen === false ? undefined : await startTypegenWatch();
793
823
  // ── Startup reclaim ──────────────────────────────────────────────────────
794
824
  // Free any port left bound by a crashed / SIGKILL'd predecessor before we bind
795
825
  // the front door or spawn the `--strictPort` frontend. tsx-watch only gives the
@@ -875,6 +905,7 @@ export async function startDevServer(options) {
875
905
  clearTimeout(respawnTimer);
876
906
  respawnTimer = null;
877
907
  }
908
+ stopTypegen?.(); // tear down the typegen watcher (unref'd, but close it cleanly)
878
909
  // Detach our own listeners so repeated signals can't pile up handlers.
879
910
  for (const sig of signals)
880
911
  process.removeListener(sig, cleanup);
@@ -1 +1 @@
1
- {"version":3,"file":"generate-client.d.ts","sourceRoot":"","sources":["../../src/scripts/generate-client.ts"],"names":[],"mappings":"AAQA;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA4DhF;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,cAAc,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI/F"}
1
+ {"version":3,"file":"generate-client.d.ts","sourceRoot":"","sources":["../../src/scripts/generate-client.ts"],"names":[],"mappings":"AASA;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAgEhF;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,cAAc,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI/F"}
@@ -1,8 +1,9 @@
1
1
  // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
- import { writeFileSync, mkdirSync } from 'fs';
4
- import { dirname } from 'path';
5
- import { pathToFileURL } from 'url';
3
+ import { isManagedValue } from '@aws-blocks/hosting';
4
+ import { mkdirSync, writeFileSync } from 'node:fs';
5
+ import { dirname } from 'node:path';
6
+ import { pathToFileURL } from 'node:url';
6
7
  import { API_NAMESPACE_MARKER } from '../api.js';
7
8
  /**
8
9
  * Generate browser-safe client code from backend exports.
@@ -43,6 +44,10 @@ export async function generateClientCode(foundationPath) {
43
44
  const isScope = typeof value?.id === 'string' && typeof value?.fullId === 'string';
44
45
  if (isScope)
45
46
  continue;
47
+ // A secret()/config() marker is a deferred value, not an API — skip it, or
48
+ // `export const k = secret('K')` in index.ts would become a bogus HTTP namespace.
49
+ if (isManagedValue(value))
50
+ continue;
46
51
  // Fallback: any exported function/object gets an ApiNamespace proxy.
47
52
  if ((typeof value === 'function' || typeof value === 'object') && value !== null) {
48
53
  lines.push(`export const ${name} = __BLOCKS_ApiNamespaceClient__('${name}');`);
@@ -50,7 +55,7 @@ export async function generateClientCode(foundationPath) {
50
55
  }
51
56
  }
52
57
  // Emit generateClient — creates new ApiNamespaceClient instances with the given url
53
- const nsEntries = namespaceNames.map(n => ` ${n}: __BLOCKS_ApiNamespaceClient__('${n}', config),`).join('\n');
58
+ const nsEntries = namespaceNames.map((n) => ` ${n}: __BLOCKS_ApiNamespaceClient__('${n}', config),`).join('\n');
54
59
  lines.push('');
55
60
  lines.push(`export const generateClient = (config) => ({\n${nsEntries}\n});`);
56
61
  const header = [
@@ -1,11 +1,11 @@
1
1
  // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
- import { describe, it, beforeEach, afterEach } from 'node:test';
4
3
  import assert from 'node:assert';
4
+ import { afterEach, beforeEach, describe, it } from 'node:test';
5
+ import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
6
+ import { tmpdir } from 'node:os';
7
+ import { join } from 'node:path';
5
8
  import { generateClientCode } from './generate-client.js';
6
- import { writeFileSync, mkdirSync, rmSync } from 'fs';
7
- import { join } from 'path';
8
- import { tmpdir } from 'os';
9
9
  /**
10
10
  * Regression test: generateClientCode collects middleware from whatever
11
11
  * the backend registers via registerClientMiddleware(). In sandbox mode,
@@ -45,4 +45,19 @@ describe('generateClientCode — middleware collection', () => {
45
45
  assert.ok(code.includes("import '@aws-blocks/bb-realtime/mock-middleware'"), 'Generated client must include mock-middleware in local dev mode');
46
46
  assert.ok(!code.includes('aws-middleware'), 'Generated client must NOT include aws-middleware in local dev mode');
47
47
  });
48
+ it('skips secret()/config() markers — they are values, not API namespaces', async () => {
49
+ // A secret()/config() marker is branded with Symbol.for('@aws-blocks/hosting.ManagedValue');
50
+ // build one inline so the fixture needs no imports. A real export still gets a proxy.
51
+ const backendPath = join(tmpDir, 'backend.mjs');
52
+ writeFileSync(backendPath, `
53
+ const BRAND = Symbol.for('@aws-blocks/hosting.ManagedValue');
54
+ export const stripeKey = { [BRAND]: true, key: 'STRIPE_KEY', kind: 'secret' };
55
+ export const featureFlags = { [BRAND]: true, key: 'FEATURE_FLAGS', kind: 'config' };
56
+ export function realApi() {}
57
+ `);
58
+ const code = await generateClientCode(backendPath);
59
+ assert.ok(!code.includes("__BLOCKS_ApiNamespaceClient__('stripeKey')"), 'a secret() marker must not become an HTTP namespace');
60
+ assert.ok(!code.includes("__BLOCKS_ApiNamespaceClient__('featureFlags')"), 'a config() marker must not become an HTTP namespace');
61
+ assert.ok(code.includes("export const realApi = __BLOCKS_ApiNamespaceClient__('realApi')"), 'a real (non-marker) export still becomes a namespace');
62
+ });
48
63
  });
@@ -1,13 +1,16 @@
1
- export { startDevServer, type DevServerOptions } from './dev-server.js';
2
- export { startSandbox, destroySandbox, type SandboxOptions } from './sandbox.js';
1
+ export { type BuildAndSendEventOptions, buildAndSendEvent, type CommandName, type CommandState, classifyError, trackCommand, } from '../telemetry/index.js';
2
+ export { listConfig, removeConfig, runConfigCli, setConfig } from './config.js';
3
+ export { type ConsoleOptions, openConsole } from './console.js';
4
+ export { type DeployOptions, deploy } from './deploy.js';
5
+ export { type DestroyOptions, destroy } from './destroy.js';
6
+ export { type DevServerOptions, startDevServer } from './dev-server.js';
7
+ export { ensureSecrets, loadEnvFile, loadProductionEnv } from './ensure-secrets.js';
3
8
  export { generateClientCode, writeClientCode } from './generate-client.js';
4
9
  export { generateSpec, writeSpec } from './generate-spec.js';
5
- export { validateSpec, type SpecValidationError } from './validate-spec.js';
6
- export { deploy, type DeployOptions } from './deploy.js';
7
- export { destroy, type DestroyOptions } from './destroy.js';
8
- export { openConsole, type ConsoleOptions } from './console.js';
9
- export { ensureSecrets, loadProductionEnv, loadEnvFile } from './ensure-secrets.js';
10
- export { trackCommand, buildAndSendEvent, classifyError, type CommandName, type CommandState, type BuildAndSendEventOptions, } from '../telemetry/index.js';
11
- export { telemetry, type TelemetryOptions } from './telemetry.js';
12
- export { getStackId, getSandboxId, getStackName } from './stack-id.js';
10
+ export { destroySandbox, type SandboxOptions, startSandbox } from './sandbox.js';
11
+ export { listSecrets, removeSecret, runSecretCli, setSecret } from './secret.js';
12
+ export { getSandboxId, getStackId, getStackName } from './stack-id.js';
13
+ export { type TelemetryOptions, telemetry } from './telemetry.js';
14
+ export { runTypegenCli } from './typegen.js';
15
+ export { type SpecValidationError, validateSpec } from './validate-spec.js';
13
16
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/scripts/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AACjF,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC3E,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,MAAM,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACpF,OAAO,EACL,YAAY,EACZ,iBAAiB,EACjB,aAAa,EACb,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,wBAAwB,GAC9B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/scripts/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACN,KAAK,wBAAwB,EAC7B,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,aAAa,EACb,YAAY,GACZ,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAChF,OAAO,EAAE,KAAK,cAAc,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,KAAK,cAAc,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,KAAK,gBAAgB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACpF,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC3E,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjF,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACvE,OAAO,EAAE,KAAK,gBAAgB,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,KAAK,mBAAmB,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
@@ -1,14 +1,17 @@
1
1
  // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
+ export { buildAndSendEvent, classifyError, trackCommand, } from '../telemetry/index.js';
4
+ export { listConfig, removeConfig, runConfigCli, setConfig } from './config.js';
5
+ export { openConsole } from './console.js';
6
+ export { deploy } from './deploy.js';
7
+ export { destroy } from './destroy.js';
3
8
  export { startDevServer } from './dev-server.js';
4
- export { startSandbox, destroySandbox } from './sandbox.js';
9
+ export { ensureSecrets, loadEnvFile, loadProductionEnv } from './ensure-secrets.js';
5
10
  export { generateClientCode, writeClientCode } from './generate-client.js';
6
11
  export { generateSpec, writeSpec } from './generate-spec.js';
7
- export { validateSpec } from './validate-spec.js';
8
- export { deploy } from './deploy.js';
9
- export { destroy } from './destroy.js';
10
- export { openConsole } from './console.js';
11
- export { ensureSecrets, loadProductionEnv, loadEnvFile } from './ensure-secrets.js';
12
- export { trackCommand, buildAndSendEvent, classifyError, } from '../telemetry/index.js';
12
+ export { destroySandbox, startSandbox } from './sandbox.js';
13
+ export { listSecrets, removeSecret, runSecretCli, setSecret } from './secret.js';
14
+ export { getSandboxId, getStackId, getStackName } from './stack-id.js';
13
15
  export { telemetry } from './telemetry.js';
14
- export { getStackId, getSandboxId, getStackName } from './stack-id.js';
16
+ export { runTypegenCli } from './typegen.js';
17
+ export { validateSpec } from './validate-spec.js';
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Resolve the region for the STS probe from the environment. Returns `null`
3
+ * when neither `AWS_REGION` nor `AWS_DEFAULT_REGION` is set — the caller then
4
+ * skips the probe rather than guessing a region, because a hardcoded guess
5
+ * (e.g. `us-east-1`) belongs to a different **partition** than GovCloud
6
+ * (`us-gov-*`) or China (`cn-*`) and would fail against a partition the user
7
+ * isn't even deploying to.
8
+ */
9
+ export declare function resolveProbeRegion(env?: NodeJS.ProcessEnv): string | null;
10
+ /**
11
+ * Probe that resolves when valid AWS credentials are available for `region` and
12
+ * rejects otherwise. Injectable so the guard can be unit-tested without network
13
+ * or real credentials; production callers use the default {@link stsProbe}.
14
+ */
15
+ export type CredentialProbe = (region: string) => Promise<void>;
16
+ /**
17
+ * Verify AWS credentials before a deploy command spends time synthesizing, and
18
+ * fail fast with actionable guidance when they're missing, expired, or invalid.
19
+ *
20
+ * Skips silently (with a warning) when no region can be resolved from the
21
+ * environment, and treats network/service errors as non-fatal — see the module
22
+ * doc for the rationale.
23
+ *
24
+ * @param command - The npm script name, used in the messages (e.g. `sandbox`, `deploy`).
25
+ * @param probe - Credential probe; defaults to STS GetCallerIdentity. Override in tests.
26
+ * @param env - Environment to resolve the region from. Defaults to `process.env`.
27
+ * @throws {Error} With actionable guidance when the probe reports a credential error.
28
+ */
29
+ export declare function assertAwsCredentials(command: string, probe?: CredentialProbe, env?: NodeJS.ProcessEnv): Promise<void>;
30
+ //# sourceMappingURL=preflight-credentials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preflight-credentials.d.ts","sourceRoot":"","sources":["../../src/scripts/preflight-credentials.ts"],"names":[],"mappings":"AAmCA;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,GAAG,IAAI,CAEtF;AAED;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAyBhE;;;;;;;;;;;;GAYG;AACH,wBAAsB,oBAAoB,CACzC,OAAO,EAAE,MAAM,EACf,KAAK,GAAE,eAA0B,EACjC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAClC,OAAO,CAAC,IAAI,CAAC,CAoCf"}
@@ -0,0 +1,108 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Pre-deploy AWS credential check.
5
+ *
6
+ * `cdk deploy` spends ~10 seconds synthesizing the app before it makes its
7
+ * first AWS call, so a missing or expired credential surfaces only *after* that
8
+ * wasted synth — as an opaque CDK/CloudFormation error that doesn't name the
9
+ * real cause. Calling STS GetCallerIdentity up front turns that into an
10
+ * immediate, actionable message the moment a deploy command starts.
11
+ *
12
+ * The check is intentionally conservative: it fails fast **only** on a real
13
+ * credential problem (missing/expired/invalid). A network or service error
14
+ * (STS unreachable, throttled, disabled in a region) is not treated as a
15
+ * credential failure — it warns and lets the deploy proceed, since blocking on
16
+ * an unverifiable probe would reject a deploy that might have worked.
17
+ */
18
+ /**
19
+ * Error `name`s that mean the credentials themselves are missing, expired, or
20
+ * invalid — the cases where the "configure your credentials" remediation is
21
+ * correct. Anything else (network, throttling, an explicit `AccessDenied` — which
22
+ * actually proves the identity resolved) is surfaced as itself instead.
23
+ */
24
+ const CREDENTIAL_ERROR_NAMES = new Set([
25
+ 'CredentialsProviderError',
26
+ 'ExpiredToken',
27
+ 'ExpiredTokenException',
28
+ 'InvalidClientTokenId',
29
+ 'UnrecognizedClientException',
30
+ 'SignatureDoesNotMatch',
31
+ 'TokenRefreshRequired',
32
+ ]);
33
+ /**
34
+ * Resolve the region for the STS probe from the environment. Returns `null`
35
+ * when neither `AWS_REGION` nor `AWS_DEFAULT_REGION` is set — the caller then
36
+ * skips the probe rather than guessing a region, because a hardcoded guess
37
+ * (e.g. `us-east-1`) belongs to a different **partition** than GovCloud
38
+ * (`us-gov-*`) or China (`cn-*`) and would fail against a partition the user
39
+ * isn't even deploying to.
40
+ */
41
+ export function resolveProbeRegion(env = process.env) {
42
+ return env.AWS_REGION || env.AWS_DEFAULT_REGION || null;
43
+ }
44
+ /**
45
+ * Default probe: STS GetCallerIdentity via the SDK's standard credential chain
46
+ * (env vars, shared config/credentials, SSO, container/instance roles).
47
+ *
48
+ * Dynamically imported so the STS client is only loaded when a deploy actually
49
+ * runs — never during dev-server startup or a mock unit test. Bounded with a
50
+ * short request timeout and a single attempt (matching `common/config.ts`) so a
51
+ * bad network can't make this hang *longer* than the synth it's replacing.
52
+ */
53
+ const stsProbe = async (region) => {
54
+ const { STSClient, GetCallerIdentityCommand } = await import('@aws-sdk/client-sts');
55
+ const client = new STSClient({
56
+ region,
57
+ maxAttempts: 1,
58
+ requestHandler: { connectionTimeout: 2000, requestTimeout: 3000 },
59
+ });
60
+ try {
61
+ await client.send(new GetCallerIdentityCommand({}));
62
+ }
63
+ finally {
64
+ client.destroy();
65
+ }
66
+ };
67
+ /**
68
+ * Verify AWS credentials before a deploy command spends time synthesizing, and
69
+ * fail fast with actionable guidance when they're missing, expired, or invalid.
70
+ *
71
+ * Skips silently (with a warning) when no region can be resolved from the
72
+ * environment, and treats network/service errors as non-fatal — see the module
73
+ * doc for the rationale.
74
+ *
75
+ * @param command - The npm script name, used in the messages (e.g. `sandbox`, `deploy`).
76
+ * @param probe - Credential probe; defaults to STS GetCallerIdentity. Override in tests.
77
+ * @param env - Environment to resolve the region from. Defaults to `process.env`.
78
+ * @throws {Error} With actionable guidance when the probe reports a credential error.
79
+ */
80
+ export async function assertAwsCredentials(command, probe = stsProbe, env = process.env) {
81
+ const region = resolveProbeRegion(env);
82
+ if (!region) {
83
+ console.warn(`⚠️ Skipping the AWS credential pre-check for \`npm run ${command}\`: ` +
84
+ 'no region set in AWS_REGION / AWS_DEFAULT_REGION. The deploy will surface any credential error itself.');
85
+ return;
86
+ }
87
+ try {
88
+ await probe(region);
89
+ }
90
+ catch (error) {
91
+ // Use the error *name* only — never the raw message, which for an STS
92
+ // authorization failure embeds the caller ARN and account id (and this
93
+ // Error propagates through telemetry).
94
+ const name = error instanceof Error && error.name ? error.name : 'UnknownError';
95
+ if (CREDENTIAL_ERROR_NAMES.has(name)) {
96
+ throw new Error(`AWS credentials could not be verified for \`npm run ${command}\` (${name}).\n` +
97
+ 'Configure AWS credentials — for example:\n' +
98
+ ' • run `aws configure` (or `aws sso login`), or\n' +
99
+ ' • set AWS_PROFILE to a configured profile, or\n' +
100
+ ' • export AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY (and AWS_SESSION_TOKEN for temporary credentials).\n' +
101
+ `Then re-run \`npm run ${command}\`.`);
102
+ }
103
+ // Not a credential problem (network, throttling, STS disabled in-region, …).
104
+ // Don't block a deploy that might still work — warn and continue.
105
+ console.warn(`⚠️ Could not verify AWS credentials for \`npm run ${command}\` (${name}); ` +
106
+ 'continuing — the deploy will report any real error.');
107
+ }
108
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=preflight-credentials.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preflight-credentials.test.d.ts","sourceRoot":"","sources":["../../src/scripts/preflight-credentials.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,77 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import assert from 'node:assert';
4
+ import { describe, it, mock } from 'node:test';
5
+ import { assertAwsCredentials, resolveProbeRegion } from './preflight-credentials.js';
6
+ const REGION_ENV = { AWS_REGION: 'us-east-1' };
7
+ /** Build an Error with a specific `name`, as the AWS SDK throws. */
8
+ function namedError(name, message = 'raw sdk detail') {
9
+ const err = new Error(message);
10
+ err.name = name;
11
+ return err;
12
+ }
13
+ describe('resolveProbeRegion', () => {
14
+ it('prefers AWS_REGION', () => {
15
+ assert.strictEqual(resolveProbeRegion({ AWS_REGION: 'eu-west-1', AWS_DEFAULT_REGION: 'us-east-2' }), 'eu-west-1');
16
+ });
17
+ it('falls back to AWS_DEFAULT_REGION', () => {
18
+ assert.strictEqual(resolveProbeRegion({ AWS_DEFAULT_REGION: 'ap-south-1' }), 'ap-south-1');
19
+ });
20
+ it('returns null when neither is set (caller then skips the probe)', () => {
21
+ assert.strictEqual(resolveProbeRegion({}), null);
22
+ });
23
+ });
24
+ describe('assertAwsCredentials', () => {
25
+ it('resolves silently when the credential probe succeeds', async () => {
26
+ let calledRegion;
27
+ await assert.doesNotReject(() => assertAwsCredentials('sandbox', async (region) => {
28
+ calledRegion = region;
29
+ }, REGION_ENV));
30
+ assert.strictEqual(calledRegion, 'us-east-1');
31
+ });
32
+ it('throws actionable guidance on a real credential error, naming the command', async () => {
33
+ await assert.rejects(() => assertAwsCredentials('sandbox', async () => { throw namedError('CredentialsProviderError'); }, REGION_ENV), (err) => {
34
+ assert.match(err.message, /npm run sandbox/);
35
+ assert.match(err.message, /aws configure|AWS_PROFILE|AWS_ACCESS_KEY_ID/);
36
+ assert.match(err.message, /CredentialsProviderError/); // the name, for debugging
37
+ return true;
38
+ });
39
+ });
40
+ it('treats an expired token as a credential error', async () => {
41
+ await assert.rejects(() => assertAwsCredentials('deploy', async () => { throw namedError('ExpiredToken'); }, REGION_ENV), (err) => {
42
+ assert.match(err.message, /npm run deploy/);
43
+ assert.match(err.message, /aws sso login|AWS_PROFILE/);
44
+ return true;
45
+ });
46
+ });
47
+ it('does NOT leak the raw error message (ARN / account id)', async () => {
48
+ const arnMessage = 'User: arn:aws:iam::123456789012:user/alice is not authorized to perform sts:GetCallerIdentity';
49
+ await assert.rejects(() => assertAwsCredentials('deploy', async () => { throw namedError('InvalidClientTokenId', arnMessage); }, REGION_ENV), (err) => {
50
+ assert.doesNotMatch(err.message, /arn:aws:iam/);
51
+ assert.doesNotMatch(err.message, /123456789012/);
52
+ return true;
53
+ });
54
+ });
55
+ it('does not throw on a network/service error — warns and continues', async () => {
56
+ const warn = mock.method(console, 'warn', () => { });
57
+ try {
58
+ await assert.doesNotReject(() => assertAwsCredentials('sandbox', async () => { throw namedError('TimeoutError'); }, REGION_ENV));
59
+ assert.ok(warn.mock.calls.some((c) => String(c.arguments[0]).includes('Could not verify')), 'should warn that credentials could not be verified');
60
+ }
61
+ finally {
62
+ warn.mock.restore();
63
+ }
64
+ });
65
+ it('skips the probe (with a warning) when no region is resolvable', async () => {
66
+ const warn = mock.method(console, 'warn', () => { });
67
+ let probed = false;
68
+ try {
69
+ await assertAwsCredentials('sandbox', async () => { probed = true; }, {});
70
+ assert.strictEqual(probed, false, 'probe must not run without a region');
71
+ assert.ok(warn.mock.calls.some((c) => String(c.arguments[0]).includes('Skipping the AWS credential pre-check')), 'should warn that the pre-check was skipped');
72
+ }
73
+ finally {
74
+ warn.mock.restore();
75
+ }
76
+ });
77
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sandbox-args.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox-args.test.d.ts","sourceRoot":"","sources":["../../src/scripts/sandbox-args.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,77 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { describe, it } from 'node:test';
4
+ import assert from 'node:assert';
5
+ import { buildSandboxDeployArgs, sandboxFailureRecoveryHint } from './sandbox.js';
6
+ import { buildCdkDeployArgs } from './deploy-stream.js';
7
+ /**
8
+ * Return the value that follows `flag` in `args`, failing with a clear message
9
+ * if the flag is absent. Reading `args[indexOf(flag) + 1]` directly silently
10
+ * reads `args[0]` when the flag is missing (indexOf returns -1), which turns a
11
+ * dropped flag into a confusing value mismatch instead of a "flag missing".
12
+ *
13
+ * NOTE: resolves only the FIRST occurrence of `flag`. `--context` legitimately
14
+ * appears twice in this argv (projectRoot, then sandboxMode=true), so do not
15
+ * use `valueAfter(args, '--context')` expecting the second value — it silently
16
+ * returns the first. Assert repeated flags with `args.includes(...)` instead.
17
+ */
18
+ function valueAfter(args, flag) {
19
+ const i = args.indexOf(flag);
20
+ assert.ok(i !== -1, `expected ${flag} in: ${args.join(' ')}`);
21
+ return args[i + 1];
22
+ }
23
+ describe('buildSandboxDeployArgs — express mode (sandbox only)', () => {
24
+ const args = buildSandboxDeployArgs({
25
+ outDir: '.blocks-sandbox',
26
+ projectRoot: '/app',
27
+ backendPath: 'aws-blocks/index.ts',
28
+ });
29
+ it('enables CloudFormation Express Mode via --express', () => {
30
+ assert.ok(args.includes('--express'), `expected --express in: ${args.join(' ')}`);
31
+ });
32
+ it('pairs express mode with the direct deployment method', () => {
33
+ assert.strictEqual(valueAfter(args, '--method'), 'direct');
34
+ });
35
+ it('leaves express-mode rollback at its default (off) — no --rollback', () => {
36
+ // Express Mode disables automatic rollback by default; we keep that default
37
+ // for the throwaway sandbox loop. Passing --rollback here would silently
38
+ // give up the speedup.
39
+ assert.ok(!args.includes('--rollback'), `sandbox must not force --rollback: ${args.join(' ')}`);
40
+ });
41
+ it('deploys every stack so Lambda@Edge apps synth cleanly', () => {
42
+ assert.ok(args.includes('--all'), `expected --all in: ${args.join(' ')}`);
43
+ });
44
+ it('keeps the non-interactive deploy contract and sandbox context', () => {
45
+ assert.deepStrictEqual(args.slice(0, 4), ['exec', 'cdk', '--', 'deploy']);
46
+ assert.strictEqual(valueAfter(args, '--require-approval'), 'never');
47
+ assert.strictEqual(valueAfter(args, '--outputs-file'), '.blocks-sandbox/outputs.json');
48
+ assert.ok(args.includes('sandboxMode=true'), `expected sandboxMode=true in: ${args.join(' ')}`);
49
+ assert.strictEqual(valueAfter(args, '--app'), 'npm exec tsx -- -C cdk aws-blocks/index.ts');
50
+ });
51
+ it('interpolates the provided outDir and projectRoot', () => {
52
+ const custom = buildSandboxDeployArgs({ outDir: 'out', projectRoot: '/other', backendPath: 'b.ts' });
53
+ assert.strictEqual(valueAfter(custom, '--outputs-file'), 'out/outputs.json');
54
+ assert.strictEqual(valueAfter(custom, '--context'), 'projectRoot=/other');
55
+ });
56
+ it('does NOT leak express mode into the production deploy path', () => {
57
+ // A production deploy must keep a reviewable change set and full
58
+ // stabilization with rollback: neither `--express` nor `--method direct`
59
+ // (both sandbox-only speedups) may appear in buildCdkDeployArgs.
60
+ const prod = buildCdkDeployArgs({ projectRoot: '/app', outputsFile: '.blocks-sandbox/outputs.json' });
61
+ assert.ok(!prod.includes('--express'), `production deploy must not use --express: ${prod.join(' ')}`);
62
+ assert.ok(!prod.includes('--method'), `production deploy must not use --method: ${prod.join(' ')}`);
63
+ });
64
+ });
65
+ describe('sandboxFailureRecoveryHint — no silent dead-end after a failed express deploy', () => {
66
+ const hint = sandboxFailureRecoveryHint();
67
+ it('points to the repo-native destroy + redeploy recovery', () => {
68
+ assert.match(hint, /npm run sandbox:destroy/);
69
+ assert.match(hint, /npm run sandbox\b/);
70
+ });
71
+ it('covers the manual UPDATE_ROLLBACK_FAILED escape hatch', () => {
72
+ assert.match(hint, /continue-update-rollback/);
73
+ });
74
+ it('explains why (rollback is off under express mode)', () => {
75
+ assert.match(hint, /rollback is off|automatic rollback/i);
76
+ });
77
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sandbox-empty-buckets.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox-empty-buckets.test.d.ts","sourceRoot":"","sources":["../../src/scripts/sandbox-empty-buckets.test.ts"],"names":[],"mappings":""}