create-spatius-app 0.1.0 → 0.1.2
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/README.md +2 -4
- package/dist/cli.js +7547 -23
- package/dist/cli.js.map +1 -1
- package/dist/templates.js +7655 -103
- package/dist/templates.js.map +1 -1
- package/package.json +2 -2
- package/schemas/result-v3.schema.json +170 -0
- package/templates/cloudflare-livekit/AGENTS.md +4 -0
- package/templates/cloudflare-livekit/README.md +44 -1
- package/templates/cloudflare-livekit/agent/README.md +0 -4
- package/templates/cloudflare-livekit/agent/pyproject.toml +2 -2
- package/templates/cloudflare-livekit/agent/src/agent.py +10 -0
- package/templates/cloudflare-livekit/agent/tests/test_agent_startup.py +21 -0
- package/templates/cloudflare-livekit/agent/uv.lock +21 -21
- package/templates/cloudflare-livekit/npmrc +0 -3
- package/templates/cloudflare-livekit/package.json +2 -2
- package/templates/cloudflare-livekit/pnpm-lock.yaml +101 -0
- package/templates/cloudflare-livekit/scripts/dev-web.mjs +12 -0
- package/templates/cloudflare-livekit/scripts/dev.mjs +29 -0
- package/templates/cloudflare-livekit/scripts/wait-for-agent.mjs +20 -0
- package/templates/cloudflare-livekit/tests/dev-fixtures.mjs +12 -1
- package/templates/cloudflare-livekit/tests/dev-supervision.test.mjs +108 -5
|
@@ -25,7 +25,7 @@ export const credentials = {
|
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
const fakeRuntime = `
|
|
28
|
-
import { appendFileSync, existsSync } from 'node:fs';
|
|
28
|
+
import { appendFileSync, existsSync, writeFileSync } from 'node:fs';
|
|
29
29
|
import { spawn } from 'node:child_process';
|
|
30
30
|
const role = process.argv[2];
|
|
31
31
|
const args = process.argv.slice(3);
|
|
@@ -57,6 +57,17 @@ if (role === 'lk' && args[0] === '--version') {
|
|
|
57
57
|
} else if (role === 'grandchild') {
|
|
58
58
|
hold(); ready();
|
|
59
59
|
} else {
|
|
60
|
+
if (role !== 'web' && process.env.SPATIUS_DEV_READY_FILE && process.env.DEV_TEST_RUNTIME_HOLD === '1') {
|
|
61
|
+
const register = () => {
|
|
62
|
+
event('registered');
|
|
63
|
+
writeFileSync(process.env.SPATIUS_DEV_READY_FILE, '');
|
|
64
|
+
};
|
|
65
|
+
if (process.env.DEV_TEST_REGISTER) {
|
|
66
|
+
const poll = setInterval(() => {
|
|
67
|
+
if (existsSync(process.env.DEV_TEST_REGISTER)) { clearInterval(poll); register(); }
|
|
68
|
+
}, 20);
|
|
69
|
+
} else register();
|
|
70
|
+
}
|
|
60
71
|
if (process.env.DEV_TEST_GRANDCHILD === '1') {
|
|
61
72
|
spawnGrandchild();
|
|
62
73
|
}
|
|
@@ -37,7 +37,7 @@ async function supervise(f, extraEnv) {
|
|
|
37
37
|
const sourcePackage = JSON.parse(
|
|
38
38
|
await readFile(join(templateDirectory, 'package.json'), 'utf8'),
|
|
39
39
|
);
|
|
40
|
-
// Exercise the production
|
|
40
|
+
// Exercise the production startup gate and supervisor with npm, available alongside
|
|
41
41
|
// Node in both generated projects and root CI. Only PM selection changes.
|
|
42
42
|
const scripts = Object.fromEntries(
|
|
43
43
|
['dev', 'dev:web', 'agent:dev'].map((name) => [
|
|
@@ -102,7 +102,7 @@ for (const [name, side, code] of [
|
|
|
102
102
|
['agent fails', 'agent', '29'],
|
|
103
103
|
]) {
|
|
104
104
|
test(
|
|
105
|
-
`
|
|
105
|
+
`root dev supervisor: ${name} stops its sibling and all grandchildren`,
|
|
106
106
|
{ timeout: 15000 },
|
|
107
107
|
async (t) => {
|
|
108
108
|
const f = await fixture(t);
|
|
@@ -128,12 +128,14 @@ for (const [name, side, code] of [
|
|
|
128
128
|
assert.match(result.output, /SIGTERM/);
|
|
129
129
|
assertNoCredentials(result.output);
|
|
130
130
|
await assertStopped(events.map(({ pid }) => pid));
|
|
131
|
+
// A bounded CLI probe may select Python fallback on a busy machine.
|
|
132
|
+
// Supervision must launch exactly one runtime through either supported path.
|
|
131
133
|
assert.equal(
|
|
132
134
|
(await readEvents(f.events)).filter(
|
|
133
135
|
({ role, args, type }) =>
|
|
134
|
-
|
|
136
|
+
['lk', 'uv', 'python'].includes(role) &&
|
|
135
137
|
type === 'invoked' &&
|
|
136
|
-
args.
|
|
138
|
+
args.includes('src/agent.py'),
|
|
137
139
|
).length,
|
|
138
140
|
1,
|
|
139
141
|
);
|
|
@@ -142,7 +144,7 @@ for (const [name, side, code] of [
|
|
|
142
144
|
}
|
|
143
145
|
|
|
144
146
|
test(
|
|
145
|
-
'
|
|
147
|
+
'root dev supervisor: Ctrl+C stops both services and their grandchildren',
|
|
146
148
|
{
|
|
147
149
|
timeout: 15000,
|
|
148
150
|
skip:
|
|
@@ -201,3 +203,104 @@ test(
|
|
|
201
203
|
assertNoCredentials(result.output);
|
|
202
204
|
},
|
|
203
205
|
);
|
|
206
|
+
|
|
207
|
+
for (const fallback of [false, true]) {
|
|
208
|
+
test(
|
|
209
|
+
`web waits for registration (${fallback ? 'Python fallback' : 'lk'})`,
|
|
210
|
+
{ timeout: 15000 },
|
|
211
|
+
async (t) => {
|
|
212
|
+
const f = await fixture(t, { uv: true });
|
|
213
|
+
const registration = join(f.root, 'register');
|
|
214
|
+
const run = await supervise(f, {
|
|
215
|
+
DEV_TEST_RUNTIME_HOLD: '1',
|
|
216
|
+
DEV_TEST_REGISTER: registration,
|
|
217
|
+
DEV_TEST_WEB_EXIT: '0',
|
|
218
|
+
...(fallback ? { DEV_TEST_VERSION: 'lk version 2.17.0' } : {}),
|
|
219
|
+
});
|
|
220
|
+
const before = await waitForEvents(f.events, (events) =>
|
|
221
|
+
events.some(
|
|
222
|
+
({ role, type }) =>
|
|
223
|
+
role === (fallback ? 'uv' : 'lk') && type === 'ready',
|
|
224
|
+
),
|
|
225
|
+
);
|
|
226
|
+
await delay(200);
|
|
227
|
+
assert.ok(
|
|
228
|
+
!(await readEvents(f.events)).some(({ role }) => role === 'web'),
|
|
229
|
+
);
|
|
230
|
+
const readyPath = before.find(({ env }) => env.SPATIUS_DEV_READY_FILE)
|
|
231
|
+
?.env.SPATIUS_DEV_READY_FILE;
|
|
232
|
+
assert.ok(readyPath);
|
|
233
|
+
await writeFile(registration, 'register');
|
|
234
|
+
const after = await waitForEvents(f.events, (events) =>
|
|
235
|
+
events.some(({ role, type }) => role === 'web' && type === 'ready'),
|
|
236
|
+
);
|
|
237
|
+
assert.ok(
|
|
238
|
+
after.findIndex(({ type }) => type === 'registered') <
|
|
239
|
+
after.findIndex(({ role }) => role === 'web'),
|
|
240
|
+
);
|
|
241
|
+
await writeFile(f.env.DEV_TEST_RELEASE, 'exit');
|
|
242
|
+
const result = await run.done;
|
|
243
|
+
assert.match(result.output, /LiveKit agent registered/);
|
|
244
|
+
assertNoCredentials(result.output);
|
|
245
|
+
await assert.rejects(readFile(readyPath), { code: 'ENOENT' });
|
|
246
|
+
await assertStopped(after.map(({ pid }) => pid));
|
|
247
|
+
},
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
test(
|
|
252
|
+
'agent failure before registration never starts web',
|
|
253
|
+
{ timeout: 15000 },
|
|
254
|
+
async (t) => {
|
|
255
|
+
const f = await fixture(t);
|
|
256
|
+
const run = await supervise(f, { DEV_TEST_RUNTIME_EXIT: '29' });
|
|
257
|
+
const result = await run.done;
|
|
258
|
+
assert.notEqual(result.code, 0);
|
|
259
|
+
assert.ok(!(await readEvents(f.events)).some(({ role }) => role === 'web'));
|
|
260
|
+
assertNoCredentials(result.output);
|
|
261
|
+
},
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
test(
|
|
265
|
+
'registration timeout stops the agent without starting web',
|
|
266
|
+
{ timeout: 15000 },
|
|
267
|
+
async (t) => {
|
|
268
|
+
const f = await fixture(t);
|
|
269
|
+
// Accelerate only the copied fixture's deadline; exercise the real web gate.
|
|
270
|
+
const path = join(f.root, 'scripts', 'wait-for-agent.mjs');
|
|
271
|
+
await writeFile(
|
|
272
|
+
path,
|
|
273
|
+
(await readFile(path, 'utf8')).replace('60_000', '1500'),
|
|
274
|
+
);
|
|
275
|
+
const run = await supervise(f, {
|
|
276
|
+
DEV_TEST_RUNTIME_HOLD: '1',
|
|
277
|
+
DEV_TEST_REGISTER: join(f.root, 'never-register'),
|
|
278
|
+
});
|
|
279
|
+
const result = await run.done;
|
|
280
|
+
assert.notEqual(result.code, 0);
|
|
281
|
+
assert.match(result.output, /LiveKit agent did not register/);
|
|
282
|
+
const events = await readEvents(f.events);
|
|
283
|
+
assert.ok(!events.some(({ role }) => role === 'web'));
|
|
284
|
+
await assertStopped(events.map(({ pid }) => pid));
|
|
285
|
+
assertNoCredentials(result.output);
|
|
286
|
+
},
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
test(
|
|
290
|
+
'Ctrl+C while waiting for registration stops the agent without starting web',
|
|
291
|
+
{ timeout: 15000, skip: windows },
|
|
292
|
+
async (t) => {
|
|
293
|
+
const f = await fixture(t);
|
|
294
|
+
const run = await supervise(f, {
|
|
295
|
+
DEV_TEST_RUNTIME_HOLD: '1',
|
|
296
|
+
DEV_TEST_REGISTER: join(f.root, 'never-register'),
|
|
297
|
+
});
|
|
298
|
+
const events = await waitForEvents(f.events, (events) =>
|
|
299
|
+
events.some(({ role, type }) => role === 'lk' && type === 'ready'),
|
|
300
|
+
);
|
|
301
|
+
process.kill(-run.child.pid, 'SIGINT');
|
|
302
|
+
await run.done;
|
|
303
|
+
assert.ok(!(await readEvents(f.events)).some(({ role }) => role === 'web'));
|
|
304
|
+
await assertStopped(events.map(({ pid }) => pid));
|
|
305
|
+
},
|
|
306
|
+
);
|