@statorjs/stator 2.12.0 → 2.13.0
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/package.json +3 -2
- package/src/cli/commands/test.ts +18 -1
- package/src/cli/stator.js +9 -10
- package/src/register.js +21 -0
- package/src/server/dev-vite.ts +37 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@statorjs/stator",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.0",
|
|
4
4
|
"description": "Server-canonical web framework: business logic in composable state machines, UI as a thin renderer binding machine outputs to DOM positions. Ships TypeScript source, loaded natively by its own toolchain — no build step.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"types": "./src/server/dev.ts",
|
|
48
48
|
"default": "./src/server/dev.ts"
|
|
49
49
|
},
|
|
50
|
+
"./register": "./src/register.js",
|
|
50
51
|
"./build": {
|
|
51
52
|
"types": "./src/build/index.ts",
|
|
52
53
|
"default": "./src/build/index.ts"
|
|
@@ -105,7 +106,7 @@
|
|
|
105
106
|
"pino-pretty": "^13.2.0",
|
|
106
107
|
"vite": "^6.4.5",
|
|
107
108
|
"vitest": "^3.2.7",
|
|
108
|
-
"@statorjs/stator": "2.
|
|
109
|
+
"@statorjs/stator": "2.13.0"
|
|
109
110
|
},
|
|
110
111
|
"scripts": {
|
|
111
112
|
"typecheck": "tsc --noEmit",
|
package/src/cli/commands/test.ts
CHANGED
|
@@ -3,6 +3,23 @@ import { existsSync } from 'node:fs'
|
|
|
3
3
|
import { join } from 'node:path'
|
|
4
4
|
import type { CliContext } from '../run.ts'
|
|
5
5
|
|
|
6
|
+
/** `@statorjs/stator/register`, by absolute URL so it resolves whatever the
|
|
7
|
+
* runner's working directory. */
|
|
8
|
+
const REGISTER = new URL('../../register.js', import.meta.url).href
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The runner's environment: `NODE_OPTIONS` gains `--import` of Stator's
|
|
12
|
+
* loader, kept alongside anything already there. Stator ships TypeScript,
|
|
13
|
+
* and a runner hands `node_modules` to Node — so Stator, and every package
|
|
14
|
+
* built on it (direct or transitive, hoisted or not), would otherwise reach
|
|
15
|
+
* Node unloadable. Child processes and workers inherit it.
|
|
16
|
+
*/
|
|
17
|
+
export function testEnv(env: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {
|
|
18
|
+
const flag = `--import ${REGISTER}`
|
|
19
|
+
const current = env.NODE_OPTIONS?.trim() ?? ''
|
|
20
|
+
return { ...env, NODE_OPTIONS: current.includes(flag) ? current : `${current} ${flag}`.trim() }
|
|
21
|
+
}
|
|
22
|
+
|
|
6
23
|
/**
|
|
7
24
|
* Run the test suite. The runner is a deliberately swappable seam — today it
|
|
8
25
|
* wraps the app's local Vitest; as the platform matures this becomes
|
|
@@ -22,7 +39,7 @@ export async function run(ctx: CliContext): Promise<void> {
|
|
|
22
39
|
}
|
|
23
40
|
const args = ctx.rest.length > 0 ? ctx.rest : ['run']
|
|
24
41
|
await new Promise<void>((resolvePromise, reject) => {
|
|
25
|
-
const child = spawn(bin, args, { stdio: 'inherit', cwd: ctx.root })
|
|
42
|
+
const child = spawn(bin, args, { stdio: 'inherit', cwd: ctx.root, env: testEnv() })
|
|
26
43
|
child.on('error', reject)
|
|
27
44
|
child.on('exit', (code) =>
|
|
28
45
|
code === 0 ? resolvePromise() : reject(new Error(`tests failed (exit ${code})`)),
|
package/src/cli/stator.js
CHANGED
|
@@ -21,16 +21,15 @@ if (typeof register !== 'function') {
|
|
|
21
21
|
process.exit(1)
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
process.setSourceMapsEnabled?.(true)
|
|
24
|
+
// The loader and source maps — the same registration `--import
|
|
25
|
+
// @statorjs/stator/register` gives a process the CLI doesn't start. Source
|
|
26
|
+
// maps: every transform in the owned pipeline (the TS loader, the dev
|
|
27
|
+
// loader's `.stator` compile) already EMITS an inline map, but V8 ignores
|
|
28
|
+
// maps when capturing stacks unless the process opts in — the runtime
|
|
29
|
+
// equivalent of `--enable-source-maps`, which a bin script can't put on its
|
|
30
|
+
// own invocation. (`.stator` traces resolve to the compiled server module —
|
|
31
|
+
// right file, generated lines — until the compiler emits its own map.)
|
|
32
|
+
await import('../register.js')
|
|
34
33
|
|
|
35
34
|
// The real CLI is TypeScript, transformed on import by the loader just registered.
|
|
36
35
|
await import('./run.ts')
|
package/src/register.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// `--import @statorjs/stator/register`: Stator's TS loader for any process
|
|
2
|
+
// the `stator` CLI doesn't start — a test runner launched by an editor,
|
|
3
|
+
// `npx vitest`, `node scripts/x.ts`. Stator ships TypeScript source, and Node
|
|
4
|
+
// won't strip types inside node_modules (or, from a linked checkout, strips
|
|
5
|
+
// only a subset), so anything that imports Stator, or a package built on it,
|
|
6
|
+
// needs this loader. `stator dev`/`start`/`build` register it themselves and
|
|
7
|
+
// `stator test` passes it to the runner. Plain JS so Node can import it with
|
|
8
|
+
// no loader in place yet.
|
|
9
|
+
import nodeModule from 'node:module'
|
|
10
|
+
|
|
11
|
+
if (typeof nodeModule.register !== 'function') {
|
|
12
|
+
throw new Error(
|
|
13
|
+
`@statorjs/stator/register needs module.register — Node 18.19+ or 20.6+ (found ${process.version}).`,
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
nodeModule.register('./cli/loader.js', import.meta.url)
|
|
18
|
+
|
|
19
|
+
// Every transform the loader makes emits an inline map; V8 only applies
|
|
20
|
+
// them to stack traces when the process opts in.
|
|
21
|
+
process.setSourceMapsEnabled?.(true)
|
package/src/server/dev-vite.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto'
|
|
2
|
-
import { existsSync } from 'node:fs'
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
3
3
|
import { readFile } from 'node:fs/promises'
|
|
4
4
|
import { createServer as createHttpServer } from 'node:http'
|
|
5
|
-
import { relative, resolve } from 'node:path'
|
|
5
|
+
import { join, relative, resolve } from 'node:path'
|
|
6
6
|
import { getRequestListener } from '@hono/node-server'
|
|
7
7
|
import type { Hono } from 'hono'
|
|
8
8
|
import { createServer as createViteServer, type ViteDevServer } from 'vite'
|
|
@@ -36,6 +36,36 @@ import { isStatorQueryRoute } from './routing.ts'
|
|
|
36
36
|
* whose render-context doesn't match the one the templates resolve against, and
|
|
37
37
|
* `read()` would throw. (This mirrors how Astro/SvelteKit run SSR through Vite.)
|
|
38
38
|
*/
|
|
39
|
+
/**
|
|
40
|
+
* The app's direct dependencies that depend on Stator, for `ssr.noExternal`
|
|
41
|
+
* above. Transitional, like this whole module (removed with the Vite exit):
|
|
42
|
+
* the native dev server and production load one module graph, where the
|
|
43
|
+
* dual-instance trap can't happen. Direct dependencies only — a deeper one
|
|
44
|
+
* still loads (`@statorjs/stator/register` handles that) but as a second
|
|
45
|
+
* instance.
|
|
46
|
+
*/
|
|
47
|
+
function statorDependents(root: string): string[] {
|
|
48
|
+
const read = (path: string): Record<string, Record<string, string> | undefined> | null => {
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(readFileSync(path, 'utf8'))
|
|
51
|
+
} catch {
|
|
52
|
+
return null
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const app = read(join(root, 'package.json'))
|
|
56
|
+
if (!app) return []
|
|
57
|
+
return Object.keys({ ...app.dependencies, ...app.devDependencies }).filter((name) => {
|
|
58
|
+
if (name === '@statorjs/stator') return false
|
|
59
|
+
const pkg = read(join(root, 'node_modules', name, 'package.json'))
|
|
60
|
+
return Boolean(
|
|
61
|
+
pkg &&
|
|
62
|
+
[pkg.dependencies, pkg.peerDependencies, pkg.optionalDependencies].some(
|
|
63
|
+
(deps) => deps && '@statorjs/stator' in deps,
|
|
64
|
+
),
|
|
65
|
+
)
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
39
69
|
export async function createViteDevApp(config: DevAppConfig): Promise<DevApp> {
|
|
40
70
|
// Load .env before anything reads process.env. No-op if the CLI already
|
|
41
71
|
// loaded it; covers a direct `createDevApp` call (a hand-written dev entry).
|
|
@@ -61,8 +91,11 @@ export async function createViteDevApp(config: DevAppConfig): Promise<DevApp> {
|
|
|
61
91
|
// imports of a machine resolve to a `{ name }` stub (SSR loads are
|
|
62
92
|
// untouched — `options.ssr` gates it).
|
|
63
93
|
plugins: [stator(), machineStub({ machinesDir: resolve(config.machinesDir) })],
|
|
64
|
-
// The framework's own source is TS that Vite must transform, not
|
|
65
|
-
|
|
94
|
+
// The framework's own source is TS that Vite must transform, not
|
|
95
|
+
// externalize — and so must packages built on it, or they load through
|
|
96
|
+
// Node with a second Stator instance (the dual-instance trap: a
|
|
97
|
+
// component they render finds no render context).
|
|
98
|
+
ssr: { noExternal: [/@statorjs\/stator/, ...statorDependents(resolve(config.root))] },
|
|
66
99
|
logLevel: 'warn',
|
|
67
100
|
})
|
|
68
101
|
|