@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.
- package/dist/cdk/index.d.ts +1 -1
- package/dist/cdk/index.d.ts.map +1 -1
- package/dist/cdk/index.js +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/errors.d.ts +28 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +27 -1
- package/dist/errors.test.d.ts +2 -0
- package/dist/errors.test.d.ts.map +1 -0
- package/dist/errors.test.js +47 -0
- package/dist/hosting.d.ts +22 -1
- package/dist/hosting.d.ts.map +1 -1
- package/dist/index.cdk.d.ts +1 -1
- package/dist/index.cdk.d.ts.map +1 -1
- package/dist/index.cdk.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/scripts/dev-server-supervisor.test.d.ts +2 -0
- package/dist/scripts/dev-server-supervisor.test.d.ts.map +1 -0
- package/dist/scripts/dev-server-supervisor.test.js +551 -0
- package/dist/scripts/dev-server.d.ts +73 -0
- package/dist/scripts/dev-server.d.ts.map +1 -1
- package/dist/scripts/dev-server.js +279 -29
- package/dist/scripts/index.d.ts +1 -0
- package/dist/scripts/index.d.ts.map +1 -1
- package/dist/scripts/index.js +1 -0
- package/dist/scripts/process-tree.d.ts +126 -0
- package/dist/scripts/process-tree.d.ts.map +1 -0
- package/dist/scripts/process-tree.js +198 -0
- package/dist/scripts/sandbox.d.ts.map +1 -1
- package/dist/scripts/sandbox.js +41 -3
- package/dist/scripts/stack-id.d.ts +12 -0
- package/dist/scripts/stack-id.d.ts.map +1 -0
- package/dist/scripts/stack-id.js +54 -0
- package/dist/scripts/stack-id.test.d.ts +2 -0
- package/dist/scripts/stack-id.test.d.ts.map +1 -0
- package/dist/scripts/stack-id.test.js +54 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/cdk/index.ts +1 -1
- package/src/client/index.ts +1 -1
- package/src/errors.test.ts +55 -0
- package/src/errors.ts +32 -1
- package/src/hosting.ts +22 -1
- package/src/index.cdk.ts +1 -1
- package/src/index.ts +1 -1
- package/src/scripts/dev-server-supervisor.test.ts +621 -0
- package/src/scripts/dev-server.ts +316 -27
- package/src/scripts/index.ts +1 -0
- package/src/scripts/process-tree.ts +245 -0
- package/src/scripts/sandbox.ts +40 -3
- package/src/scripts/stack-id.test.ts +63 -0
- package/src/scripts/stack-id.ts +61 -0
- package/src/version.ts +1 -1
package/src/scripts/sandbox.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
181
|
-
|
|
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.
|
|
2
|
+
export const CORE_VERSION = '0.1.7';
|