@aws-blocks/core 0.1.7 → 0.1.11
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/db-naming.d.ts +17 -5
- package/dist/db-naming.d.ts.map +1 -1
- package/dist/db-naming.js +18 -6
- package/dist/db-naming.test.js +44 -3
- package/dist/hosting.d.ts +49 -0
- package/dist/hosting.d.ts.map +1 -1
- package/dist/hosting.js +47 -8
- package/dist/hosting.test.js +60 -0
- package/dist/scripts/deploy.d.ts.map +1 -1
- package/dist/scripts/deploy.js +4 -2
- package/dist/scripts/dev-server-reclaim.test.d.ts +2 -0
- package/dist/scripts/dev-server-reclaim.test.d.ts.map +1 -0
- package/dist/scripts/dev-server-reclaim.test.js +352 -0
- package/dist/scripts/dev-server.d.ts +168 -0
- package/dist/scripts/dev-server.d.ts.map +1 -1
- package/dist/scripts/dev-server.js +357 -25
- package/dist/scripts/ensure-secrets.d.ts +5 -2
- package/dist/scripts/ensure-secrets.d.ts.map +1 -1
- package/dist/scripts/ensure-secrets.js +14 -6
- package/dist/scripts/external-migrations-step.d.ts.map +1 -1
- package/dist/scripts/external-migrations-step.js +5 -1
- package/dist/scripts/index.d.ts +1 -1
- package/dist/scripts/index.d.ts.map +1 -1
- package/dist/scripts/index.js +1 -1
- package/dist/scripts/process-tree.d.ts +41 -2
- package/dist/scripts/process-tree.d.ts.map +1 -1
- package/dist/scripts/process-tree.js +83 -3
- package/dist/scripts/sandbox.d.ts.map +1 -1
- package/dist/scripts/sandbox.js +10 -1
- package/dist/scripts/stack-id.d.ts +25 -0
- package/dist/scripts/stack-id.d.ts.map +1 -1
- package/dist/scripts/stack-id.js +25 -0
- package/dist/scripts/stack-id.test.js +51 -1
- package/dist/telemetry/client.d.ts +3 -1
- package/dist/telemetry/client.d.ts.map +1 -1
- package/dist/telemetry/client.js +20 -24
- package/dist/telemetry/telemetry-send-worker.d.ts +2 -0
- package/dist/telemetry/telemetry-send-worker.d.ts.map +1 -0
- package/dist/telemetry/telemetry-send-worker.js +58 -0
- package/dist/telemetry/telemetry.test.js +77 -1
- package/dist/telemetry/trackCommand.d.ts +1 -1
- package/dist/telemetry/trackCommand.js +3 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/db-naming.test.ts +50 -5
- package/src/db-naming.ts +18 -6
- package/src/hosting.test.ts +79 -0
- package/src/hosting.ts +105 -12
- package/src/scripts/deploy.ts +4 -2
- package/src/scripts/dev-server-reclaim.test.ts +430 -0
- package/src/scripts/dev-server.ts +428 -25
- package/src/scripts/ensure-secrets.ts +17 -6
- package/src/scripts/external-migrations-step.ts +5 -1
- package/src/scripts/index.ts +1 -1
- package/src/scripts/process-tree.ts +101 -3
- package/src/scripts/sandbox.ts +10 -1
- package/src/scripts/stack-id.test.ts +61 -1
- package/src/scripts/stack-id.ts +26 -0
- package/src/telemetry/client.ts +22 -30
- package/src/telemetry/telemetry-send-worker.ts +60 -0
- package/src/telemetry/telemetry.test.ts +91 -1
- package/src/telemetry/trackCommand.ts +3 -3
- package/src/version.ts +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-tree.d.ts","sourceRoot":"","sources":["../../src/scripts/process-tree.ts"],"names":[],"mappings":"AAaA,+EAA+E;AAC/E,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;CACjD;AAED,6GAA6G;AAC7G,UAAU,cAAc;IACtB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED
|
|
1
|
+
{"version":3,"file":"process-tree.d.ts","sourceRoot":"","sources":["../../src/scripts/process-tree.ts"],"names":[],"mappings":"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"}
|
|
@@ -19,10 +19,11 @@ import { spawnSync } from 'node:child_process';
|
|
|
19
19
|
* denied): such a run did NOT reap the tree, so the caller must fall back to a
|
|
20
20
|
* direct `child.kill` rather than treat the leak as handled. (`child.kill`
|
|
21
21
|
* cannot reap the orphaned grandchild either, but the fallback is cheap and
|
|
22
|
-
* strictly correct — we never silently swallow a failed tree-kill.)
|
|
23
|
-
*
|
|
22
|
+
* strictly correct — we never silently swallow a failed tree-kill.) Runs with a
|
|
23
|
+
* 3s `timeout` so a wedged `taskkill` can't stall teardown; a timed-out run
|
|
24
|
+
* surfaces as `{error}` and degrades to the fallback. Never throws.
|
|
24
25
|
*/
|
|
25
|
-
export function windowsTreeKill(pid, runner = (command, args) => spawnSync(command, args, { stdio: 'ignore', windowsHide: true })) {
|
|
26
|
+
export function windowsTreeKill(pid, runner = (command, args) => spawnSync(command, args, { stdio: 'ignore', windowsHide: true, timeout: 3000 })) {
|
|
26
27
|
try {
|
|
27
28
|
const { status, error } = runner('taskkill', ['/T', '/F', '/PID', String(pid)]);
|
|
28
29
|
// Couldn't even spawn taskkill (e.g. not on PATH) → not handled; fall back.
|
|
@@ -84,6 +85,85 @@ export function killFrontendTree(child, signal = 'SIGTERM', platform = process.p
|
|
|
84
85
|
// Process already exited; nothing to do.
|
|
85
86
|
}
|
|
86
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Find the PIDs of processes holding a TCP *listener* on `port`, so a fresh dev
|
|
90
|
+
* server can reclaim a port left bound by a crashed / SIGKILL'd predecessor (its
|
|
91
|
+
* orphaned backend, or a detached Vite grandchild) instead of colliding on it.
|
|
92
|
+
* This mirrors the `lsof -ti:<port>` discovery the `cleanup` script already uses
|
|
93
|
+
* — it does NOT introduce a new port-to-PID mechanism.
|
|
94
|
+
*
|
|
95
|
+
* - **POSIX**: `lsof -ti tcp:<port> -sTCP:LISTEN` — `-t` prints bare PIDs and the
|
|
96
|
+
* `-sTCP:LISTEN` state filter restricts the match to the *listener*, so a
|
|
97
|
+
* transient client socket on the same port is never targeted.
|
|
98
|
+
* - **Windows**: `netstat -ano -p tcp`, keeping the trailing PID column of
|
|
99
|
+
* `LISTENING` rows whose local address ends in `:<port>`.
|
|
100
|
+
*
|
|
101
|
+
* Best-effort and never throws: a missing tool, a non-zero exit ("nothing is
|
|
102
|
+
* listening"), or unparseable output all yield `[]`. The `spawnSync` runs with a
|
|
103
|
+
* 3s `timeout` so a hung `lsof`/`netstat` (e.g. an unresponsive NFS mount) can't
|
|
104
|
+
* block the event loop during startup — a timed-out probe returns `{error}`,
|
|
105
|
+
* which the `catch` degrades to `[]`. PIDs `<= 1` are dropped
|
|
106
|
+
* defensively (never target init / the whole current group). `runner`/`platform`
|
|
107
|
+
* are injected for tests.
|
|
108
|
+
*/
|
|
109
|
+
export function findListenerPids(port, runner = (command, args) => spawnSync(command, args, { encoding: 'utf-8', windowsHide: true, timeout: 3000 }), platform = process.platform) {
|
|
110
|
+
try {
|
|
111
|
+
const pids = new Set();
|
|
112
|
+
if (platform === 'win32') {
|
|
113
|
+
const { stdout } = runner('netstat', ['-ano', '-p', 'tcp']);
|
|
114
|
+
if (!stdout)
|
|
115
|
+
return [];
|
|
116
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
117
|
+
if (!/LISTENING/i.test(line))
|
|
118
|
+
continue;
|
|
119
|
+
// Columns: Proto Local-Address Foreign-Address State PID
|
|
120
|
+
const cols = line.trim().split(/\s+/);
|
|
121
|
+
const local = cols[1] ?? '';
|
|
122
|
+
if (!local.endsWith(`:${port}`))
|
|
123
|
+
continue;
|
|
124
|
+
const pid = Number(cols[cols.length - 1]);
|
|
125
|
+
if (Number.isInteger(pid) && pid > 1)
|
|
126
|
+
pids.add(pid);
|
|
127
|
+
}
|
|
128
|
+
return [...pids];
|
|
129
|
+
}
|
|
130
|
+
const { stdout } = runner('lsof', ['-ti', `tcp:${port}`, '-sTCP:LISTEN']);
|
|
131
|
+
if (!stdout)
|
|
132
|
+
return [];
|
|
133
|
+
for (const token of stdout.split(/\s+/)) {
|
|
134
|
+
const pid = Number(token.trim());
|
|
135
|
+
if (Number.isInteger(pid) && pid > 1)
|
|
136
|
+
pids.add(pid);
|
|
137
|
+
}
|
|
138
|
+
return [...pids];
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Force-terminate whatever process (and, on POSIX, its process group) currently
|
|
146
|
+
* holds a port — used by the dev server's startup / EADDRINUSE *reclaim* path on
|
|
147
|
+
* a PID discovered via {@link findListenerPids}, i.e. a process this dev server
|
|
148
|
+
* did NOT spawn. Reuses {@link killFrontendTree} (POSIX `-pid` group kill /
|
|
149
|
+
* Windows `taskkill /T`, with a direct-`kill` fallback) so reclaim reaps exactly
|
|
150
|
+
* like our own frontend teardown — no bespoke kill mechanism. Best-effort; never
|
|
151
|
+
* throws (a since-exited PID just yields ESRCH, swallowed by killFrontendTree).
|
|
152
|
+
*/
|
|
153
|
+
export function killListenerTree(pid, signal = 'SIGTERM', platform = process.platform, killFn = (p, s) => process.kill(p, s), winTreeKill = windowsTreeKill) {
|
|
154
|
+
killFrontendTree({
|
|
155
|
+
pid,
|
|
156
|
+
kill: (s) => {
|
|
157
|
+
try {
|
|
158
|
+
process.kill(pid, s ?? signal);
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
}, signal, platform, killFn, winTreeKill);
|
|
166
|
+
}
|
|
87
167
|
const defaultSleep = (ms) => new Promise((res) => {
|
|
88
168
|
setTimeout(res, ms).unref?.();
|
|
89
169
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../src/scripts/sandbox.ts"],"names":[],"mappings":"AA+BA,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yFAAyF;IACzF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE,cAAc,+
|
|
1
|
+
{"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../src/scripts/sandbox.ts"],"names":[],"mappings":"AA+BA,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yFAAyF;IACzF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE,cAAc,+BAkMzD;AAED,wBAAsB,cAAc,CAAC,WAAW,EAAE,MAAM,iBAwCvD"}
|
package/dist/scripts/sandbox.js
CHANGED
|
@@ -41,7 +41,9 @@ export async function startSandbox(options) {
|
|
|
41
41
|
process.env.BLOCKS_STAGE = 'sandbox';
|
|
42
42
|
// Provision connection string to SSM SecureString.
|
|
43
43
|
// On first deploy, creates the parameter. On subsequent deploys, updates if changed.
|
|
44
|
-
|
|
44
|
+
// projectRoot is process.cwd() — the same value passed to cdk as --context
|
|
45
|
+
// projectRoot below — so the written name matches the name resolved at synth.
|
|
46
|
+
const secrets = await ensureSecrets('sandbox', process.cwd());
|
|
45
47
|
if (secrets.created.length > 0) {
|
|
46
48
|
console.log(`🔐 Created secrets: ${secrets.created.join(', ')}`);
|
|
47
49
|
}
|
|
@@ -59,6 +61,13 @@ export async function startSandbox(options) {
|
|
|
59
61
|
try {
|
|
60
62
|
runSync("npm", [
|
|
61
63
|
"exec", "cdk", "--", "deploy",
|
|
64
|
+
// `--all`: an app that uses Lambda@Edge (e.g. a Next.js route with
|
|
65
|
+
// `export const runtime = 'edge'`) synthesizes a SECOND stack
|
|
66
|
+
// (`edge-lambda-stack-*`, region us-east-1) in addition to the main
|
|
67
|
+
// hosting stack. Without `--all`, CDK refuses with "specify which
|
|
68
|
+
// stacks to use". Deploying every stack in a sandbox app is the
|
|
69
|
+
// intended behavior, so select them all.
|
|
70
|
+
"--all",
|
|
62
71
|
"--require-approval", "never",
|
|
63
72
|
"--outputs-file", `${outDir}/outputs.json`,
|
|
64
73
|
"--context", `projectRoot=${process.cwd()}`,
|
|
@@ -7,6 +7,31 @@ export declare function getStackId(projectRoot?: string): string;
|
|
|
7
7
|
* Get or create a per-machine sandbox identifier.
|
|
8
8
|
* Stored in `.blocks-sandbox/sandbox-id.txt` (gitignored).
|
|
9
9
|
* Format: `<username(8)>-<random(6)>` — identifies the developer's sandbox.
|
|
10
|
+
*
|
|
11
|
+
* Get-or-create (lazy init): returns the existing id, or generates and persists
|
|
12
|
+
* one on first call. The file is the shared sync point — once written, every
|
|
13
|
+
* later caller and every process reads the same id, so the secret writer
|
|
14
|
+
* (`ensureSecrets`) and synth derive identical names.
|
|
10
15
|
*/
|
|
11
16
|
export declare function getSandboxId(projectRoot?: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* The full CloudFormation stack name for a deployment.
|
|
19
|
+
*
|
|
20
|
+
* Single source of truth for the stack-name scheme (D-012): production is
|
|
21
|
+
* `<stackId>-prod`; a sandbox is `<stackId>-<sandboxId>`. The CDK templates name
|
|
22
|
+
* the stack with this function, and the external-DB connection-string parameter
|
|
23
|
+
* name (`dbConnectionParameterName`) is derived from it — so a deployed stack and
|
|
24
|
+
* the parameter holding its database credentials can never use divergent names.
|
|
25
|
+
*
|
|
26
|
+
* This function reads committed config (`.blocks/config.json`, throws if absent
|
|
27
|
+
* — D-012) and resolves the sandbox id via {@link getSandboxId} (get-or-create):
|
|
28
|
+
* the first caller materializes `.blocks-sandbox/sandbox-id.txt`, every later
|
|
29
|
+
* caller reads the same value. Because that file persists and is shared across
|
|
30
|
+
* processes, the secret writer (`ensureSecrets`) and synth resolve identical
|
|
31
|
+
* names. Production does not use the sandbox id.
|
|
32
|
+
*/
|
|
33
|
+
export declare function getStackName(opts: {
|
|
34
|
+
sandbox: boolean;
|
|
35
|
+
projectRoot?: string;
|
|
36
|
+
}): string;
|
|
12
37
|
//# sourceMappingURL=stack-id.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stack-id.d.ts","sourceRoot":"","sources":["../../src/scripts/stack-id.ts"],"names":[],"mappings":"AAiBA;;;GAGG;AACH,wBAAgB,UAAU,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAavD;AAED
|
|
1
|
+
{"version":3,"file":"stack-id.d.ts","sourceRoot":"","sources":["../../src/scripts/stack-id.ts"],"names":[],"mappings":"AAiBA;;;GAGG;AACH,wBAAgB,UAAU,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAavD;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAWzD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAGrF"}
|
package/dist/scripts/stack-id.js
CHANGED
|
@@ -29,6 +29,11 @@ export function getStackId(projectRoot) {
|
|
|
29
29
|
* Get or create a per-machine sandbox identifier.
|
|
30
30
|
* Stored in `.blocks-sandbox/sandbox-id.txt` (gitignored).
|
|
31
31
|
* Format: `<username(8)>-<random(6)>` — identifies the developer's sandbox.
|
|
32
|
+
*
|
|
33
|
+
* Get-or-create (lazy init): returns the existing id, or generates and persists
|
|
34
|
+
* one on first call. The file is the shared sync point — once written, every
|
|
35
|
+
* later caller and every process reads the same id, so the secret writer
|
|
36
|
+
* (`ensureSecrets`) and synth derive identical names.
|
|
32
37
|
*/
|
|
33
38
|
export function getSandboxId(projectRoot) {
|
|
34
39
|
const root = projectRoot || process.cwd();
|
|
@@ -44,6 +49,26 @@ export function getSandboxId(projectRoot) {
|
|
|
44
49
|
writeFileSync(filePath, id);
|
|
45
50
|
return id;
|
|
46
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* The full CloudFormation stack name for a deployment.
|
|
54
|
+
*
|
|
55
|
+
* Single source of truth for the stack-name scheme (D-012): production is
|
|
56
|
+
* `<stackId>-prod`; a sandbox is `<stackId>-<sandboxId>`. The CDK templates name
|
|
57
|
+
* the stack with this function, and the external-DB connection-string parameter
|
|
58
|
+
* name (`dbConnectionParameterName`) is derived from it — so a deployed stack and
|
|
59
|
+
* the parameter holding its database credentials can never use divergent names.
|
|
60
|
+
*
|
|
61
|
+
* This function reads committed config (`.blocks/config.json`, throws if absent
|
|
62
|
+
* — D-012) and resolves the sandbox id via {@link getSandboxId} (get-or-create):
|
|
63
|
+
* the first caller materializes `.blocks-sandbox/sandbox-id.txt`, every later
|
|
64
|
+
* caller reads the same value. Because that file persists and is shared across
|
|
65
|
+
* processes, the secret writer (`ensureSecrets`) and synth resolve identical
|
|
66
|
+
* names. Production does not use the sandbox id.
|
|
67
|
+
*/
|
|
68
|
+
export function getStackName(opts) {
|
|
69
|
+
const base = getStackId(opts.projectRoot);
|
|
70
|
+
return opts.sandbox ? `${base}-${getSandboxId(opts.projectRoot)}` : `${base}-prod`;
|
|
71
|
+
}
|
|
47
72
|
function getUsername() {
|
|
48
73
|
try {
|
|
49
74
|
return execSync('git config user.name', { encoding: 'utf-8' }).trim();
|
|
@@ -5,7 +5,7 @@ import assert from 'node:assert';
|
|
|
5
5
|
import { mkdirSync, writeFileSync, rmSync, readFileSync } from 'node:fs';
|
|
6
6
|
import { join } from 'node:path';
|
|
7
7
|
import { tmpdir } from 'node:os';
|
|
8
|
-
import { getStackId, getSandboxId } from './stack-id.js';
|
|
8
|
+
import { getStackId, getSandboxId, getStackName } from './stack-id.js';
|
|
9
9
|
describe('getStackId', () => {
|
|
10
10
|
let tmpDir;
|
|
11
11
|
afterEach(() => {
|
|
@@ -52,3 +52,53 @@ describe('getSandboxId', () => {
|
|
|
52
52
|
assert.strictEqual(getSandboxId(tmpDir), 'alice-abc123');
|
|
53
53
|
});
|
|
54
54
|
});
|
|
55
|
+
describe('getStackName', () => {
|
|
56
|
+
let tmpDir;
|
|
57
|
+
afterEach(() => {
|
|
58
|
+
if (tmpDir)
|
|
59
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
60
|
+
});
|
|
61
|
+
it('production is <stackId>-prod', () => {
|
|
62
|
+
tmpDir = join(tmpdir(), `stack-name-prod-${Date.now()}`);
|
|
63
|
+
mkdirSync(join(tmpDir, '.blocks'), { recursive: true });
|
|
64
|
+
writeFileSync(join(tmpDir, '.blocks', 'config.json'), JSON.stringify({ stackId: 'my-app-k7x2mf' }));
|
|
65
|
+
assert.strictEqual(getStackName({ sandbox: false, projectRoot: tmpDir }), 'my-app-k7x2mf-prod');
|
|
66
|
+
});
|
|
67
|
+
it('sandbox is <stackId>-<sandboxId>', () => {
|
|
68
|
+
tmpDir = join(tmpdir(), `stack-name-sbx-${Date.now()}`);
|
|
69
|
+
mkdirSync(join(tmpDir, '.blocks'), { recursive: true });
|
|
70
|
+
writeFileSync(join(tmpDir, '.blocks', 'config.json'), JSON.stringify({ stackId: 'my-app-k7x2mf' }));
|
|
71
|
+
mkdirSync(join(tmpDir, '.blocks-sandbox'), { recursive: true });
|
|
72
|
+
writeFileSync(join(tmpDir, '.blocks-sandbox', 'sandbox-id.txt'), 'alice-0d7e1c');
|
|
73
|
+
assert.strictEqual(getStackName({ sandbox: true, projectRoot: tmpDir }), 'my-app-k7x2mf-alice-0d7e1c');
|
|
74
|
+
});
|
|
75
|
+
it('throws actionable error when config is missing (fail fast, no silent fallback)', () => {
|
|
76
|
+
tmpDir = join(tmpdir(), `stack-name-missing-${Date.now()}`);
|
|
77
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
78
|
+
assert.throws(() => getStackName({ sandbox: false, projectRoot: tmpDir }), /\.blocks\/config\.json not found/);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
describe('getStackName sandbox id (get-or-create)', () => {
|
|
82
|
+
let tmpDir;
|
|
83
|
+
afterEach(() => {
|
|
84
|
+
if (tmpDir)
|
|
85
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
86
|
+
});
|
|
87
|
+
it('creates and persists sandbox-id.txt on first call when missing', () => {
|
|
88
|
+
tmpDir = join(tmpdir(), `stack-name-getorcreate-${Date.now()}`);
|
|
89
|
+
mkdirSync(join(tmpDir, '.blocks'), { recursive: true });
|
|
90
|
+
writeFileSync(join(tmpDir, '.blocks', 'config.json'), JSON.stringify({ stackId: 'test-app' }));
|
|
91
|
+
// No .blocks-sandbox dir yet — getStackName creates the id rather than throwing.
|
|
92
|
+
const name = getStackName({ sandbox: true, projectRoot: tmpDir });
|
|
93
|
+
assert.match(name, /^test-app-[a-z0-9]+-[a-f0-9]{6}$/);
|
|
94
|
+
// Persisted so later callers/processes resolve the identical name.
|
|
95
|
+
const stored = readFileSync(join(tmpDir, '.blocks-sandbox', 'sandbox-id.txt'), 'utf-8').trim();
|
|
96
|
+
assert.strictEqual(name, `test-app-${stored}`);
|
|
97
|
+
});
|
|
98
|
+
it('reuses the same id across calls', () => {
|
|
99
|
+
tmpDir = join(tmpdir(), `stack-name-getorcreate-idem-${Date.now()}`);
|
|
100
|
+
mkdirSync(join(tmpDir, '.blocks'), { recursive: true });
|
|
101
|
+
writeFileSync(join(tmpDir, '.blocks', 'config.json'), JSON.stringify({ stackId: 'test-app' }));
|
|
102
|
+
assert.strictEqual(getStackName({ sandbox: true, projectRoot: tmpDir }), getStackName({ sandbox: true, projectRoot: tmpDir }));
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -52,7 +52,9 @@ export declare function buildAndSendEvent(opts: BuildAndSendEventOptions): void;
|
|
|
52
52
|
/**
|
|
53
53
|
* Send a pre-built telemetry event to the collection endpoint.
|
|
54
54
|
*
|
|
55
|
-
*
|
|
55
|
+
* Spawns a detached subprocess that performs the HTTPS POST independently of
|
|
56
|
+
* the parent CLI process. This ensures the request completes even when the
|
|
57
|
+
* parent exits on failure paths before an in-process request would flush.
|
|
56
58
|
* Debug output available via `NODE_DEBUG=blocks-telemetry`.
|
|
57
59
|
*/
|
|
58
60
|
export declare function sendEvent(event: BlocksTelemetryEvent): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/telemetry/client.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/telemetry/client.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAYjF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,GAAG,SAAS,CAWzD;AAwBD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,wBAAwB,GAAG,oBAAoB,CAwD/E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,wBAAwB,GAAG,IAAI,CAatE;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,oBAAoB,GAAG,IAAI,CAgC3D"}
|
package/dist/telemetry/client.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { request as httpsRequest } from 'node:https';
|
|
2
|
-
import { request as httpRequest } from 'node:http';
|
|
3
1
|
import { existsSync, readFileSync, mkdirSync, openSync, writeSync, closeSync, writeFileSync, constants } from 'node:fs';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
4
3
|
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { debuglog } from 'node:util';
|
|
6
6
|
import { CORE_VERSION } from '../version.js';
|
|
7
7
|
import { Scope } from '../common/index.js';
|
|
@@ -11,7 +11,6 @@ import { getInstallationId, getProjectId, generateEventId } from './identifiers.
|
|
|
11
11
|
// Debug log: writes to stderr via NODE_DEBUG=blocks-telemetry (developer-facing, for troubleshooting)
|
|
12
12
|
const debug = debuglog('blocks-telemetry');
|
|
13
13
|
const DEFAULT_ENDPOINT = 'https://blocks-telemetry.us-east-1.api.aws/metrics';
|
|
14
|
-
const TIMEOUT_MS = 500;
|
|
15
14
|
const TELEMETRY_VERSION = '1.0.0';
|
|
16
15
|
function getEndpoint() {
|
|
17
16
|
return process.env.BLOCKS_TELEMETRY_ENDPOINT || DEFAULT_ENDPOINT;
|
|
@@ -172,7 +171,9 @@ export function buildAndSendEvent(opts) {
|
|
|
172
171
|
/**
|
|
173
172
|
* Send a pre-built telemetry event to the collection endpoint.
|
|
174
173
|
*
|
|
175
|
-
*
|
|
174
|
+
* Spawns a detached subprocess that performs the HTTPS POST independently of
|
|
175
|
+
* the parent CLI process. This ensures the request completes even when the
|
|
176
|
+
* parent exits on failure paths before an in-process request would flush.
|
|
176
177
|
* Debug output available via `NODE_DEBUG=blocks-telemetry`.
|
|
177
178
|
*/
|
|
178
179
|
export function sendEvent(event) {
|
|
@@ -187,27 +188,22 @@ export function sendEvent(event) {
|
|
|
187
188
|
catch { }
|
|
188
189
|
}
|
|
189
190
|
debug('sending event to %s (%d bytes)', endpoint, Buffer.byteLength(payload));
|
|
190
|
-
const
|
|
191
|
-
const
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
method: 'POST',
|
|
198
|
-
headers: {
|
|
199
|
-
'Content-Type': 'application/json',
|
|
200
|
-
'Content-Length': Buffer.byteLength(payload),
|
|
201
|
-
},
|
|
202
|
-
timeout: TIMEOUT_MS,
|
|
203
|
-
}, (res) => {
|
|
204
|
-
debug('event sent (status=%d)', res.statusCode);
|
|
205
|
-
res.resume();
|
|
191
|
+
const dir = path.dirname(fileURLToPath(import.meta.url));
|
|
192
|
+
const workerPath = path.join(dir, 'telemetry-send-worker.js');
|
|
193
|
+
const child = spawn(process.execPath, [workerPath, endpoint], {
|
|
194
|
+
detached: true,
|
|
195
|
+
stdio: ['pipe', 'ignore', 'ignore'],
|
|
196
|
+
// Clear NODE_OPTIONS so inherited flags (e.g. --conditions=cdk) don't interfere
|
|
197
|
+
env: { ...process.env, NODE_OPTIONS: '' },
|
|
206
198
|
});
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
199
|
+
child.stdin.on('error', (err) => { debug('stdin write failed: %s', err.message); });
|
|
200
|
+
// Payload is small (<1KB JSON) so it fits in the kernel pipe buffer (~64KB)
|
|
201
|
+
// and survives the parent closing its fd on exit.
|
|
202
|
+
child.stdin.write(payload);
|
|
203
|
+
child.stdin.end();
|
|
204
|
+
child.on('error', (err) => { debug('spawn failed: %s', err.message); });
|
|
205
|
+
child.unref();
|
|
206
|
+
debug('spawned telemetry subprocess (pid=%d)', child.pid);
|
|
211
207
|
}
|
|
212
208
|
catch {
|
|
213
209
|
// Telemetry must never throw or affect the user's command
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry-send-worker.d.ts","sourceRoot":"","sources":["../../src/telemetry/telemetry-send-worker.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Telemetry send worker — spawned as a detached subprocess.
|
|
5
|
+
* Reads JSON payload from stdin, POSTs it to the endpoint (argv[2]).
|
|
6
|
+
*
|
|
7
|
+
* Uses only Node built-ins — no project imports — so the compiled .js
|
|
8
|
+
* runs with bare `node` (no tsx needed).
|
|
9
|
+
*/
|
|
10
|
+
import { request as httpsRequest } from 'node:https';
|
|
11
|
+
import { request as httpRequest } from 'node:http';
|
|
12
|
+
const TIMEOUT_MS = 500;
|
|
13
|
+
const debug = (process.env.NODE_DEBUG || '').includes('blocks-telemetry');
|
|
14
|
+
const endpoint = process.argv[2];
|
|
15
|
+
if (!endpoint)
|
|
16
|
+
process.exit(1);
|
|
17
|
+
let payload = '';
|
|
18
|
+
process.stdin.setEncoding('utf-8');
|
|
19
|
+
process.stdin.on('data', (chunk) => { payload += chunk; });
|
|
20
|
+
process.stdin.on('end', () => {
|
|
21
|
+
try {
|
|
22
|
+
const url = new URL(endpoint);
|
|
23
|
+
const isHttps = url.protocol === 'https:';
|
|
24
|
+
const requestFn = isHttps ? httpsRequest : httpRequest;
|
|
25
|
+
const req = requestFn({
|
|
26
|
+
hostname: url.hostname,
|
|
27
|
+
port: url.port || (isHttps ? '443' : '80'),
|
|
28
|
+
path: url.pathname + url.search,
|
|
29
|
+
method: 'POST',
|
|
30
|
+
headers: {
|
|
31
|
+
'Content-Type': 'application/json',
|
|
32
|
+
'Content-Length': Buffer.byteLength(payload),
|
|
33
|
+
},
|
|
34
|
+
timeout: TIMEOUT_MS,
|
|
35
|
+
}, (res) => {
|
|
36
|
+
res.resume();
|
|
37
|
+
if (debug)
|
|
38
|
+
process.stderr.write(`BLOCKS-TELEMETRY: sent (status=${res.statusCode})\n`);
|
|
39
|
+
process.exit(0);
|
|
40
|
+
});
|
|
41
|
+
req.on('error', (e) => {
|
|
42
|
+
if (debug)
|
|
43
|
+
process.stderr.write(`BLOCKS-TELEMETRY: error: ${e.message}\n`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
});
|
|
46
|
+
req.on('timeout', () => {
|
|
47
|
+
if (debug)
|
|
48
|
+
process.stderr.write(`BLOCKS-TELEMETRY: timed out\n`);
|
|
49
|
+
req.destroy();
|
|
50
|
+
process.exit(1);
|
|
51
|
+
});
|
|
52
|
+
req.write(payload);
|
|
53
|
+
req.end();
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
@@ -10,7 +10,7 @@ import { isCI, detectOS, detectNodeVersion, detectPackageManager, detectAgent, c
|
|
|
10
10
|
import { trackCommand, classifyError } from './trackCommand.js';
|
|
11
11
|
import { buildAndSendEvent, buildEvent, sendEvent, getTelemetryFilePath } from './client.js';
|
|
12
12
|
import { getInstallationId, getProjectId, generateEventId } from './identifiers.js';
|
|
13
|
-
import { spawnSync } from 'node:child_process';
|
|
13
|
+
import { spawnSync, spawn as spawnChild } from 'node:child_process';
|
|
14
14
|
import { Scope } from '../common/index.js';
|
|
15
15
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
16
|
describe('telemetry/consent', () => {
|
|
@@ -453,6 +453,82 @@ describe('telemetry/client', () => {
|
|
|
453
453
|
process.env = { ...originalEnv };
|
|
454
454
|
});
|
|
455
455
|
});
|
|
456
|
+
describe('telemetry/send-worker', () => {
|
|
457
|
+
it('worker POSTs payload from stdin to endpoint', async () => {
|
|
458
|
+
const received = [];
|
|
459
|
+
const server = await new Promise((resolve) => {
|
|
460
|
+
const s = createServer((req, res) => {
|
|
461
|
+
let body = '';
|
|
462
|
+
req.on('data', (chunk) => { body += chunk; });
|
|
463
|
+
req.on('end', () => {
|
|
464
|
+
received.push(body);
|
|
465
|
+
res.writeHead(200);
|
|
466
|
+
res.end();
|
|
467
|
+
});
|
|
468
|
+
});
|
|
469
|
+
s.listen(0, '127.0.0.1', () => resolve(s));
|
|
470
|
+
});
|
|
471
|
+
const addr = server.address();
|
|
472
|
+
const endpoint = `http://127.0.0.1:${addr.port}/collect`;
|
|
473
|
+
const payload = JSON.stringify({ test: true, command: 'dev' });
|
|
474
|
+
const workerPath = join(__dirname, 'telemetry-send-worker.js');
|
|
475
|
+
const exitCode = await new Promise((resolve) => {
|
|
476
|
+
const proc = spawnChild(process.execPath, [workerPath, endpoint], {
|
|
477
|
+
stdio: ['pipe', 'ignore', 'ignore'],
|
|
478
|
+
env: { ...process.env, NODE_OPTIONS: '' },
|
|
479
|
+
});
|
|
480
|
+
proc.stdin.write(payload);
|
|
481
|
+
proc.stdin.end();
|
|
482
|
+
proc.on('close', (code) => resolve(code));
|
|
483
|
+
});
|
|
484
|
+
assert.strictEqual(exitCode, 0);
|
|
485
|
+
assert.strictEqual(received.length, 1);
|
|
486
|
+
assert.deepStrictEqual(JSON.parse(received[0]), { test: true, command: 'dev' });
|
|
487
|
+
server.close();
|
|
488
|
+
});
|
|
489
|
+
it('worker exits with 1 on unreachable endpoint', async () => {
|
|
490
|
+
const payload = JSON.stringify({ test: true });
|
|
491
|
+
const workerPath = join(__dirname, 'telemetry-send-worker.js');
|
|
492
|
+
const exitCode = await new Promise((resolve) => {
|
|
493
|
+
const proc = spawnChild(process.execPath, [workerPath, 'http://127.0.0.1:1/unreachable'], {
|
|
494
|
+
stdio: ['pipe', 'ignore', 'ignore'],
|
|
495
|
+
env: { ...process.env, NODE_OPTIONS: '' },
|
|
496
|
+
});
|
|
497
|
+
proc.stdin.write(payload);
|
|
498
|
+
proc.stdin.end();
|
|
499
|
+
proc.on('close', (code) => resolve(code));
|
|
500
|
+
});
|
|
501
|
+
assert.strictEqual(exitCode, 1);
|
|
502
|
+
});
|
|
503
|
+
it('worker writes debug output to stderr when NODE_DEBUG is set', async () => {
|
|
504
|
+
const server = await new Promise((resolve) => {
|
|
505
|
+
const s = createServer((req, res) => {
|
|
506
|
+
let body = '';
|
|
507
|
+
req.on('data', (chunk) => { body += chunk; });
|
|
508
|
+
req.on('end', () => { res.writeHead(200); res.end(); });
|
|
509
|
+
});
|
|
510
|
+
s.listen(0, '127.0.0.1', () => resolve(s));
|
|
511
|
+
});
|
|
512
|
+
const addr = server.address();
|
|
513
|
+
const endpoint = `http://127.0.0.1:${addr.port}/collect`;
|
|
514
|
+
const payload = JSON.stringify({ test: true });
|
|
515
|
+
const workerPath = join(__dirname, 'telemetry-send-worker.js');
|
|
516
|
+
const result = await new Promise((resolve) => {
|
|
517
|
+
const proc = spawnChild(process.execPath, [workerPath, endpoint], {
|
|
518
|
+
stdio: ['pipe', 'ignore', 'pipe'],
|
|
519
|
+
env: { ...process.env, NODE_OPTIONS: '', NODE_DEBUG: 'blocks-telemetry' },
|
|
520
|
+
});
|
|
521
|
+
let stderr = '';
|
|
522
|
+
proc.stderr.on('data', (chunk) => { stderr += chunk.toString(); });
|
|
523
|
+
proc.stdin.write(payload);
|
|
524
|
+
proc.stdin.end();
|
|
525
|
+
proc.on('close', (code) => resolve({ code, stderr }));
|
|
526
|
+
});
|
|
527
|
+
assert.strictEqual(result.code, 0);
|
|
528
|
+
assert.ok(result.stderr.includes('BLOCKS-TELEMETRY: sent (status=200)'), `Expected debug output, got: ${result.stderr}`);
|
|
529
|
+
server.close();
|
|
530
|
+
});
|
|
531
|
+
});
|
|
456
532
|
describe('telemetry/trackCommand integration', () => {
|
|
457
533
|
const originalEnv = { ...process.env };
|
|
458
534
|
afterEach(() => {
|
|
@@ -12,7 +12,7 @@ export declare function classifyError(error: unknown): {
|
|
|
12
12
|
*
|
|
13
13
|
* Measures wall-clock duration, classifies errors, and sends a single telemetry event
|
|
14
14
|
* after the command completes (success or failure). The telemetry send is fire-and-forget
|
|
15
|
-
* and bounded by a
|
|
15
|
+
* and bounded by a 500ms timeout — it will never delay the command or affect its exit code.
|
|
16
16
|
*
|
|
17
17
|
* If telemetry is disabled (via env var or config), the function is executed directly
|
|
18
18
|
* with zero overhead.
|
|
@@ -20,10 +20,10 @@ export function classifyError(error) {
|
|
|
20
20
|
if (msg.includes('cdk synth') || msg.includes('synthesis')) {
|
|
21
21
|
return { code: 'CDK_SYNTH_FAILED', phase: 'synth' };
|
|
22
22
|
}
|
|
23
|
-
if (msg.includes('cdk deploy') || msg.includes('deployment failed')) {
|
|
23
|
+
if (msg.includes('cdk deploy') || msg.includes('deployment failed') || (msg.includes('cdk') && msg.includes('deploy') && msg.includes('exited with code'))) {
|
|
24
24
|
return { code: 'CDK_DEPLOY_FAILED', phase: 'deploy' };
|
|
25
25
|
}
|
|
26
|
-
if (msg.includes('cdk destroy') || msg.includes('destroy failed')) {
|
|
26
|
+
if (msg.includes('cdk destroy') || msg.includes('destroy failed') || (msg.includes('cdk') && msg.includes('destroy') && msg.includes('exited with code'))) {
|
|
27
27
|
return { code: 'CDK_DESTROY_FAILED', phase: 'destroy' };
|
|
28
28
|
}
|
|
29
29
|
if (msg.includes('npm install') || msg.includes('npm err')) {
|
|
@@ -48,7 +48,7 @@ export function classifyError(error) {
|
|
|
48
48
|
*
|
|
49
49
|
* Measures wall-clock duration, classifies errors, and sends a single telemetry event
|
|
50
50
|
* after the command completes (success or failure). The telemetry send is fire-and-forget
|
|
51
|
-
* and bounded by a
|
|
51
|
+
* and bounded by a 500ms timeout — it will never delay the command or affect its exit code.
|
|
52
52
|
*
|
|
53
53
|
* If telemetry is disabled (via env var or config), the function is executed directly
|
|
54
54
|
* with zero overhead.
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const CORE_VERSION = "0.1.
|
|
1
|
+
export declare const CORE_VERSION = "0.1.11";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,YAAY,
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,YAAY,WAAW,CAAC"}
|
package/dist/version.js
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.11';
|