@invisibleloop/pulse 0.20.0 → 0.20.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/.claude/commands/pulse-start.md +11 -0
- package/.claude/commands/pulse-stop.md +5 -0
- package/CLAUDE.md +4 -2
- package/docs/.claude/coverage-check.js +153 -84
- package/docs/src/pages/store.js +4 -5
- package/examples/.claude/coverage-check.js +153 -84
- package/package.json +1 -1
- package/public/.pulse-ui-version +1 -1
- package/scripts/build.js +30 -10
- package/scripts/strip-server.test.js +31 -2
- package/src/agent/commands/pulse-start.md +6 -4
- package/src/agent/commands/pulse-stop.md +1 -1
- package/src/agent/coverage-check.js +154 -90
- package/src/agent/guide-server.md +3 -5
- package/src/agent/workflow.md +16 -2
- package/src/cli/cli.test.js +125 -0
- package/src/cli/dev.js +26 -5
- package/src/cli/discover.js +26 -0
- package/src/cli/index.js +5 -2
- package/src/cli/scaffold.js +2 -2
- package/src/cli/start.js +5 -1
- package/src/mcp/mcp.test.js +44 -0
- package/src/mcp/server.js +406 -21
- package/src/runtime/ssr.js +5 -2
- package/src/server/index.js +19 -7
- package/src/server/server.test.js +24 -0
- package/types/server.d.ts +3 -0
package/src/server/index.js
CHANGED
|
@@ -593,9 +593,13 @@ export async function createServer(entries, options = {}) {
|
|
|
593
593
|
|
|
594
594
|
const healthPath = healthCheck === true ? '/healthz' : (healthCheck || null)
|
|
595
595
|
|
|
596
|
+
// Store definition — `let` so the dev server can hot-swap it via updateStore()
|
|
597
|
+
// when pulse.store.js changes, the same way updateSpecs() swaps page specs.
|
|
598
|
+
let storeDef = store
|
|
599
|
+
|
|
596
600
|
// Validate store at startup — fail fast before the server accepts connections
|
|
597
|
-
if (
|
|
598
|
-
const { valid, errors } = validateStore(
|
|
601
|
+
if (storeDef) {
|
|
602
|
+
const { valid, errors } = validateStore(storeDef)
|
|
599
603
|
if (!valid) {
|
|
600
604
|
throw new Error(`Invalid Pulse store:\n${errors.map(e => ` — ${e}`).join('\n')}`)
|
|
601
605
|
}
|
|
@@ -897,8 +901,8 @@ export async function createServer(entries, options = {}) {
|
|
|
897
901
|
// Global store — resolve once per request, attach to ctx before guard/server fetchers.
|
|
898
902
|
// Use global fetcherTimeout for store fetchers; spec-level override applied below.
|
|
899
903
|
ctx.fetcherTimeout = fetcherTimeout ?? null
|
|
900
|
-
if (
|
|
901
|
-
ctx.store = await resolveStoreState(
|
|
904
|
+
if (storeDef) {
|
|
905
|
+
ctx.store = await resolveStoreState(storeDef, ctx)
|
|
902
906
|
}
|
|
903
907
|
|
|
904
908
|
const spec = resolveSpec(match.spec, hydrateMap)
|
|
@@ -1020,11 +1024,11 @@ export async function createServer(entries, options = {}) {
|
|
|
1020
1024
|
// string path — they are small, must carry an explicit status, and POST
|
|
1021
1025
|
// bodies are already consumed.
|
|
1022
1026
|
if (responseStatus !== 200 || req.method === 'POST') {
|
|
1023
|
-
await handleStringResponse(spec, ctx, req, res, extraBody, dev, canonicalBase, nonce, runtimeBundle, defaultCache,
|
|
1027
|
+
await handleStringResponse(spec, ctx, req, res, extraBody, dev, canonicalBase, nonce, runtimeBundle, defaultCache, storeDef, csp, faviconPath, responseStatus, livePath)
|
|
1024
1028
|
} else if (stream) {
|
|
1025
|
-
await handleStreamResponse(spec, ctx, req, res, extraBody, dev, canonicalBase, nonce, runtimeBundle, defaultCache,
|
|
1029
|
+
await handleStreamResponse(spec, ctx, req, res, extraBody, dev, canonicalBase, nonce, runtimeBundle, defaultCache, storeDef, csp, faviconPath, livePath)
|
|
1026
1030
|
} else {
|
|
1027
|
-
await handleStringResponse(spec, ctx, req, res, extraBody, dev, canonicalBase, nonce, runtimeBundle, defaultCache,
|
|
1031
|
+
await handleStringResponse(spec, ctx, req, res, extraBody, dev, canonicalBase, nonce, runtimeBundle, defaultCache, storeDef, csp, faviconPath, 200, livePath)
|
|
1028
1032
|
}
|
|
1029
1033
|
|
|
1030
1034
|
} catch (err) {
|
|
@@ -1119,6 +1123,14 @@ export async function createServer(entries, options = {}) {
|
|
|
1119
1123
|
notFoundSpec = newSpecs.find(s => s.route === '*') || null
|
|
1120
1124
|
router = buildRouter(newSpecs.filter(s => s.route !== '*'))
|
|
1121
1125
|
allSpecs = newSpecs
|
|
1126
|
+
},
|
|
1127
|
+
/** Swap the store definition at runtime (dev hot reload of pulse.store.js). */
|
|
1128
|
+
updateStore(newStoreDef) {
|
|
1129
|
+
if (newStoreDef) {
|
|
1130
|
+
const { valid, errors } = validateStore(newStoreDef)
|
|
1131
|
+
if (!valid) throw new Error(`Invalid Pulse store:\n${errors.map(e => ` — ${e}`).join('\n')}`)
|
|
1132
|
+
}
|
|
1133
|
+
storeDef = newStoreDef || null
|
|
1122
1134
|
}
|
|
1123
1135
|
}
|
|
1124
1136
|
}
|
|
@@ -1294,6 +1294,30 @@ await test('without a "*" spec the default 404 still works', async () => {
|
|
|
1294
1294
|
})
|
|
1295
1295
|
})
|
|
1296
1296
|
|
|
1297
|
+
await test('custom 404 works in production mode (manifest with runtime bundle present)', async () => {
|
|
1298
|
+
// Regression: in production the manifest sets runtimeBundle, which made
|
|
1299
|
+
// wrapDocument call isBundle(spec.hydrate) — undefined for non-hydrated pages
|
|
1300
|
+
// like a typical 404 spec → TypeError → 500 instead of the custom 404.
|
|
1301
|
+
// Dev never hit it because runtimeBundle is '' there.
|
|
1302
|
+
const prodManifest = { _runtime: '/dist/runtime-abcd1234.js' }
|
|
1303
|
+
await withServer([helloSpec, notFoundPageSpec], { stream: true, manifest: prodManifest }, async (port) => {
|
|
1304
|
+
const { status, body } = await get(port, '/nope')
|
|
1305
|
+
assert(status === 404, `Expected 404 in production mode, got ${status}`)
|
|
1306
|
+
assert(body.includes('Custom not found'), `Expected custom 404 content: ${body.slice(0, 200)}`)
|
|
1307
|
+
})
|
|
1308
|
+
})
|
|
1309
|
+
|
|
1310
|
+
await test('non-hydrated pages render on the buffered path in production mode', async () => {
|
|
1311
|
+
// Same root cause, different surface: stream: false + production manifest
|
|
1312
|
+
// crashed EVERY zero-JS page, not just the 404.
|
|
1313
|
+
const prodManifest = { _runtime: '/dist/runtime-abcd1234.js' }
|
|
1314
|
+
await withServer([helloSpec], { stream: false, manifest: prodManifest }, async (port) => {
|
|
1315
|
+
const { status, body } = await get(port, '/hello')
|
|
1316
|
+
assert(status === 200, `Expected 200, got ${status}`)
|
|
1317
|
+
assert(body.includes('Hello world'), 'Expected page content')
|
|
1318
|
+
})
|
|
1319
|
+
})
|
|
1320
|
+
|
|
1297
1321
|
// ---------------------------------------------------------------------------
|
|
1298
1322
|
|
|
1299
1323
|
console.log('\nServer-side forms (spec.submit) + CSRF\n')
|
package/types/server.d.ts
CHANGED
|
@@ -175,6 +175,9 @@ export interface ServerInstance {
|
|
|
175
175
|
/** Swap the spec list at runtime (used by the dev server for hot reload). */
|
|
176
176
|
updateSpecs(specs: PulseSpec[]): void
|
|
177
177
|
|
|
178
|
+
/** Swap the store definition at runtime (dev hot reload of pulse.store.js). Validates; throws on a bad definition. */
|
|
179
|
+
updateStore(storeDef: PulseStoreDefinition | null): void
|
|
180
|
+
|
|
178
181
|
/**
|
|
179
182
|
* Broadcast a store patch to every connected live client (requires the
|
|
180
183
|
* `live` option). Pages subscribed via spec.store merge the patch and
|