@aws-blocks/core 0.1.4 → 0.1.7

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 (58) hide show
  1. package/dist/cdk/index.d.ts +1 -1
  2. package/dist/cdk/index.d.ts.map +1 -1
  3. package/dist/cdk/index.js +1 -1
  4. package/dist/client/index.d.ts +1 -1
  5. package/dist/client/index.d.ts.map +1 -1
  6. package/dist/client/index.js +1 -1
  7. package/dist/errors.d.ts +28 -0
  8. package/dist/errors.d.ts.map +1 -1
  9. package/dist/errors.js +27 -1
  10. package/dist/errors.test.d.ts +2 -0
  11. package/dist/errors.test.d.ts.map +1 -0
  12. package/dist/errors.test.js +47 -0
  13. package/dist/hosting.d.ts +22 -1
  14. package/dist/hosting.d.ts.map +1 -1
  15. package/dist/index.cdk.d.ts +1 -1
  16. package/dist/index.cdk.d.ts.map +1 -1
  17. package/dist/index.cdk.js +1 -1
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +1 -1
  21. package/dist/scripts/dev-server-supervisor.test.d.ts +2 -0
  22. package/dist/scripts/dev-server-supervisor.test.d.ts.map +1 -0
  23. package/dist/scripts/dev-server-supervisor.test.js +551 -0
  24. package/dist/scripts/dev-server.d.ts +73 -0
  25. package/dist/scripts/dev-server.d.ts.map +1 -1
  26. package/dist/scripts/dev-server.js +279 -29
  27. package/dist/scripts/index.d.ts +1 -0
  28. package/dist/scripts/index.d.ts.map +1 -1
  29. package/dist/scripts/index.js +1 -0
  30. package/dist/scripts/process-tree.d.ts +126 -0
  31. package/dist/scripts/process-tree.d.ts.map +1 -0
  32. package/dist/scripts/process-tree.js +198 -0
  33. package/dist/scripts/sandbox.d.ts.map +1 -1
  34. package/dist/scripts/sandbox.js +41 -3
  35. package/dist/scripts/stack-id.d.ts +12 -0
  36. package/dist/scripts/stack-id.d.ts.map +1 -0
  37. package/dist/scripts/stack-id.js +54 -0
  38. package/dist/scripts/stack-id.test.d.ts +2 -0
  39. package/dist/scripts/stack-id.test.d.ts.map +1 -0
  40. package/dist/scripts/stack-id.test.js +54 -0
  41. package/dist/version.d.ts +1 -1
  42. package/dist/version.js +1 -1
  43. package/package.json +1 -1
  44. package/src/cdk/index.ts +1 -1
  45. package/src/client/index.ts +1 -1
  46. package/src/errors.test.ts +55 -0
  47. package/src/errors.ts +32 -1
  48. package/src/hosting.ts +22 -1
  49. package/src/index.cdk.ts +1 -1
  50. package/src/index.ts +1 -1
  51. package/src/scripts/dev-server-supervisor.test.ts +621 -0
  52. package/src/scripts/dev-server.ts +316 -27
  53. package/src/scripts/index.ts +1 -0
  54. package/src/scripts/process-tree.ts +245 -0
  55. package/src/scripts/sandbox.ts +40 -3
  56. package/src/scripts/stack-id.test.ts +63 -0
  57. package/src/scripts/stack-id.ts +61 -0
  58. package/src/version.ts +1 -1
@@ -11,6 +11,7 @@ import { trackCommand } from '../telemetry/trackCommand.js';
11
11
  import { buildAndSendEvent } from '../telemetry/client.js';
12
12
  import { getCdkTelemetryEnv } from './cdk-telemetry-env.js';
13
13
  import { runSync, spawnCommand } from './run-command.js';
14
+ import { terminateProcessTree } from './process-tree.js';
14
15
 
15
16
  /**
16
17
  * Import the backend definition to populate the Scope BB registry.
@@ -147,6 +148,12 @@ export async function startSandbox(options: SandboxOptions) {
147
148
  `--app`, `npm exec tsx -- -C cdk ${backendPath}`
148
149
  ], {
149
150
  stdio: ["ignore", "pipe", "pipe"],
151
+ // Own process group on POSIX so cleanup can reap the whole `cdk watch` tree
152
+ // (npx → cdk → node) via terminateProcessTree, not just the npx shell — a
153
+ // bare kill() would orphan the real cdk-watch node process, the same
154
+ // shell-only-kill leak this PR eliminates for the dev server. Windows has no
155
+ // groups; terminateProcessTree reaps the tree via taskkill.
156
+ detached: process.platform !== 'win32',
150
157
  env: { ...process.env, NODE_OPTIONS: "--conditions=cdk", ...getCdkTelemetryEnv('sandbox') },
151
158
  });
152
159
 
@@ -166,6 +173,12 @@ export async function startSandbox(options: SandboxOptions) {
166
173
  const devServer = spawnCommand(cmd, args, {
167
174
  stdio: "inherit",
168
175
  shell: true,
176
+ // Own process group on POSIX so cleanup can signal the whole dev-server
177
+ // tree (shell → tsx → node). The node dev server then runs its own SIGTERM
178
+ // handler — the ~2s terminateFrontend drain that reaps the *detached* Vite
179
+ // great-grandchild — which a bare `devServer.kill()` (the shell only) never
180
+ // triggers. Windows has no groups; terminateProcessTree reaps via taskkill.
181
+ detached: process.platform !== 'win32',
169
182
  env: {
170
183
  ...process.env,
171
184
  NODE_OPTIONS: '',
@@ -173,12 +186,36 @@ export async function startSandbox(options: SandboxOptions) {
173
186
  },
174
187
  });
175
188
 
176
- const cleanup = () => {
189
+ let cleaningUp = false;
190
+ const cleanup = async () => {
191
+ if (cleaningUp) return; // idempotent — a second signal must not re-enter
192
+ cleaningUp = true;
177
193
  console.log("\n\n🛑 Stopping local processes...");
178
194
  console.log(" (AWS resources are still running)");
179
195
  console.log("\n To destroy AWS resources, run: npm run sandbox:destroy\n");
180
- cdkWatch.kill();
181
- devServer.kill();
196
+ // Reap BOTH child trees the way the dev server reaps Vite — a process-group
197
+ // SIGTERM→SIGKILL via the shared terminateProcessTree — instead of a bare
198
+ // kill() that signals only the npx/shell parent and orphans the real
199
+ // grandchild (cdk-watch's node, or the dev server's detached Vite). Run them
200
+ // concurrently so the cdk-watch teardown doesn't serialize on top of the dev
201
+ // server's longer drain.
202
+ //
203
+ // Only the dev-server child owns the `:3100` port-free wait: its own SIGTERM
204
+ // handler runs terminateFrontend (a ~2s drain that reaps the detached Vite
205
+ // great-grandchild AND polls until the port frees), so we give it the longer
206
+ // 6s budget (> that ~2s drain) — a hung dev server still escalates to a tree
207
+ // SIGKILL and we exit regardless, so shutdown can never wedge. cdk watch
208
+ // holds no local port, so a bounded tree-kill is all it needs.
209
+ //
210
+ // That a group SIGTERM (terminateProcessTree → killFrontendTree's
211
+ // `process.kill(-pid, 'SIGTERM')`) actually reaches the *nested* node dev
212
+ // server and runs its own SIGTERM handler — the load-bearing assumption of
213
+ // the 6s budget above — is verified by the "group SIGTERM reaches a nested
214
+ // node child" integration test in dev-server-supervisor.test.ts.
215
+ await Promise.all([
216
+ terminateProcessTree(cdkWatch, 2000),
217
+ terminateProcessTree(devServer, 6000),
218
+ ]);
182
219
  process.exit(0);
183
220
  };
184
221
 
@@ -0,0 +1,63 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { describe, it, afterEach } from 'node:test';
5
+ import assert from 'node:assert';
6
+ import { mkdirSync, writeFileSync, rmSync, readFileSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import { tmpdir } from 'node:os';
9
+
10
+ import { getStackId, getSandboxId } from './stack-id.js';
11
+
12
+ describe('getStackId', () => {
13
+ let tmpDir: string;
14
+
15
+ afterEach(() => {
16
+ if (tmpDir) rmSync(tmpDir, { recursive: true, force: true });
17
+ });
18
+
19
+ it('reads stackId from .blocks/config.json', () => {
20
+ tmpDir = join(tmpdir(), `stack-id-test-${Date.now()}`);
21
+ mkdirSync(join(tmpDir, '.blocks'), { recursive: true });
22
+ writeFileSync(join(tmpDir, '.blocks', 'config.json'), JSON.stringify({ stackId: 'test-abc123' }));
23
+ assert.strictEqual(getStackId(tmpDir), 'test-abc123');
24
+ });
25
+
26
+ it('throws actionable error when config is missing', () => {
27
+ tmpDir = join(tmpdir(), `stack-id-test-missing-${Date.now()}`);
28
+ mkdirSync(tmpDir, { recursive: true });
29
+ assert.throws(() => getStackId(tmpDir), /\.blocks\/config\.json not found/);
30
+ });
31
+
32
+ it('throws actionable error when stackId key is missing', () => {
33
+ tmpDir = join(tmpdir(), `stack-id-test-nokey-${Date.now()}`);
34
+ mkdirSync(join(tmpDir, '.blocks'), { recursive: true });
35
+ writeFileSync(join(tmpDir, '.blocks', 'config.json'), JSON.stringify({ other: 'value' }));
36
+ assert.throws(() => getStackId(tmpDir), /\.blocks\/config\.json not found/);
37
+ });
38
+ });
39
+
40
+ describe('getSandboxId', () => {
41
+ let tmpDir: string;
42
+
43
+ afterEach(() => {
44
+ if (tmpDir) rmSync(tmpDir, { recursive: true, force: true });
45
+ });
46
+
47
+ it('generates and persists a sandbox id', () => {
48
+ tmpDir = join(tmpdir(), `sandbox-id-test-${Date.now()}`);
49
+ mkdirSync(tmpDir, { recursive: true });
50
+ const id = getSandboxId(tmpDir);
51
+ assert.match(id, /^[a-z0-9]+-[a-f0-9]{6}$/);
52
+ // Verify persisted
53
+ const stored = readFileSync(join(tmpDir, '.blocks-sandbox', 'sandbox-id.txt'), 'utf-8').trim();
54
+ assert.strictEqual(stored, id);
55
+ });
56
+
57
+ it('returns existing id on subsequent calls', () => {
58
+ tmpDir = join(tmpdir(), `sandbox-id-test-idem-${Date.now()}`);
59
+ mkdirSync(join(tmpDir, '.blocks-sandbox'), { recursive: true });
60
+ writeFileSync(join(tmpDir, '.blocks-sandbox', 'sandbox-id.txt'), 'alice-abc123');
61
+ assert.strictEqual(getSandboxId(tmpDir), 'alice-abc123');
62
+ });
63
+ });
@@ -0,0 +1,61 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
5
+ import { join, dirname } from 'node:path';
6
+ import { execSync } from 'node:child_process';
7
+ import { randomBytes } from 'node:crypto';
8
+
9
+ interface BlocksConfig {
10
+ stackId?: string;
11
+ [key: string]: unknown;
12
+ }
13
+
14
+ function randomSuffix(length: number): string {
15
+ return randomBytes(length).toString('hex').slice(0, length);
16
+ }
17
+
18
+ /**
19
+ * Get the stackId from `.blocks/config.json` in the project root.
20
+ * This is the stable project identifier used as the base for CloudFormation stack names.
21
+ */
22
+ export function getStackId(projectRoot?: string): string {
23
+ const root = projectRoot || process.cwd();
24
+ const configPath = join(root, '.blocks', 'config.json');
25
+ try {
26
+ const config: BlocksConfig = JSON.parse(readFileSync(configPath, 'utf-8'));
27
+ if (!config.stackId) throw new Error('missing key');
28
+ return config.stackId;
29
+ } catch {
30
+ throw new Error(
31
+ `.blocks/config.json not found or missing stackId — it is created by create-blocks-app and should be committed. ` +
32
+ `To fix manually, create ${configPath} with: { "stackId": "<your-app-name>" }`
33
+ );
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Get or create a per-machine sandbox identifier.
39
+ * Stored in `.blocks-sandbox/sandbox-id.txt` (gitignored).
40
+ * Format: `<username(8)>-<random(6)>` — identifies the developer's sandbox.
41
+ */
42
+ export function getSandboxId(projectRoot?: string): string {
43
+ const root = projectRoot || process.cwd();
44
+ const filePath = join(root, '.blocks-sandbox', 'sandbox-id.txt');
45
+ if (existsSync(filePath)) return readFileSync(filePath, 'utf-8').trim();
46
+ const dir = dirname(filePath);
47
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
48
+ const username = getUsername().toLowerCase().replace(/[^a-z0-9]/g, '').slice(0, 8) || 'dev';
49
+ const random = randomSuffix(6);
50
+ const id = `${username}-${random}`;
51
+ writeFileSync(filePath, id);
52
+ return id;
53
+ }
54
+
55
+ function getUsername(): string {
56
+ try {
57
+ return execSync('git config user.name', { encoding: 'utf-8' }).trim();
58
+ } catch {
59
+ return process.env.USER || process.env.USERNAME || 'user';
60
+ }
61
+ }
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // Auto-generated by scripts/generate-version.mjs — do not edit manually
2
- export const CORE_VERSION = '0.1.4';
2
+ export const CORE_VERSION = '0.1.7';