@decohere/flux 0.0.1-dev.13 → 0.0.1-dev.130
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/LICENSE +15 -0
- package/README.md +273 -0
- package/commonjs/src/deferred-script-runner.js +56 -0
- package/commonjs/src/flux.command.js +3 -3
- package/commonjs/src/flux.js +56 -0
- package/commonjs/src/flux.module.js +9 -8
- package/commonjs/src/hot-reload-server.js +307 -219
- package/commonjs/src/index.js +34 -2
- package/commonjs/src/rewrite-serve-specifier.js +61 -0
- package/commonjs/src/run.action.js +47 -0
- package/commonjs/src/run.command.js +65 -0
- package/commonjs/src/serve.action.js +78 -7
- package/commonjs/src/serve.command.js +35 -4
- package/commonjs/src/slice-serve.js +130 -0
- package/commonjs/src/source-module-loader.js +388 -0
- package/commonjs/src/static-imports.js +37 -0
- package/commonjs/src/transpile-disk-cache.js +148 -0
- package/es6/src/deferred-script-runner.js +46 -0
- package/es6/src/flux.command.js +3 -3
- package/es6/src/flux.js +41 -0
- package/es6/src/flux.module.js +9 -8
- package/es6/src/hot-reload-server.js +287 -175
- package/es6/src/index.js +5 -3
- package/es6/src/rewrite-serve-specifier.js +51 -0
- package/es6/src/run.action.js +37 -0
- package/es6/src/run.command.js +55 -0
- package/es6/src/serve.action.js +73 -9
- package/es6/src/serve.command.js +35 -4
- package/es6/src/slice-serve.js +105 -0
- package/es6/src/source-module-loader.js +332 -0
- package/es6/src/static-imports.js +27 -0
- package/es6/src/transpile-disk-cache.js +142 -0
- package/package.json +17 -6
- package/types/src/deferred-script-runner.d.ts +18 -0
- package/types/src/flux.command.d.ts +2 -2
- package/types/src/flux.d.ts +43 -0
- package/types/src/flux.module.d.ts +6 -4
- package/types/src/hot-reload-server.d.ts +57 -23
- package/types/src/index.d.ts +8 -1
- package/types/src/rewrite-serve-specifier.d.ts +14 -0
- package/types/src/run.action.d.ts +15 -0
- package/types/src/run.command.d.ts +11 -0
- package/types/src/serve.action.d.ts +13 -3
- package/types/src/serve.command.d.ts +7 -1
- package/types/src/slice-serve.d.ts +52 -0
- package/types/src/source-module-loader.d.ts +104 -0
- package/types/src/static-imports.d.ts +5 -0
- package/types/src/transpile-disk-cache.d.ts +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 Typesmiths
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# @decohere/flux
|
|
2
|
+
|
|
3
|
+
In-process TypeScript load for Node.js ESM apps — either one-shot (`flux run`) or hot-reload serve (`flux serve`).
|
|
4
|
+
|
|
5
|
+
Use Flux when you want to run source `.ts` directly. Flux transpiles with the **Forge** SWC compiler and maps imports to source with path aliases the workspace package builds dynamically from the workspace (`package.json` `name` → source `main`). Do not use Flux as a browser bundler, a worker-thread HMR, or a production process manager.
|
|
6
|
+
|
|
7
|
+
Point Flux at a tree of TypeScript packages (`package.json` at each project root). [`@decohere/workspace`](../workspace/README.md) is how that tree is deduced — you do not run Flux against the workspace package. Layout and glob overrides: [the workspace package — workspace layout](../workspace/README.md#workspace-layout).
|
|
8
|
+
|
|
9
|
+
This package is the Flux API. You use it through the `flux` CLI (`flux run` / `flux serve`), not by importing `@decohere/flux` from application code. The [Programmatic API](#programmatic-api) is only for embedding Flux in another tool.
|
|
10
|
+
|
|
11
|
+
Related: [`@decohere/workspace`](../workspace/README.md) (package discovery), [Forge](../forge/README.md) (SWC + debug aliases). The `flux` command ships in `@decohere/cli`.
|
|
12
|
+
|
|
13
|
+
## When to use
|
|
14
|
+
|
|
15
|
+
| Use Flux | Use something else |
|
|
16
|
+
|---|---|
|
|
17
|
+
| Local debug of Node apps with HMR (`serve`) | Production (`node` on Forge release output, Docker, orchestrator) |
|
|
18
|
+
| One-shot TS CLI / script without a pre-build (`run`) | Published package `bin` entries (stay built JS) |
|
|
19
|
+
| Packages resolved by `package.json` name | Browser UI (Vite / `ng serve` / … via package scripts) |
|
|
20
|
+
| Clean `Symbol.asyncDispose` on reboot (`serve`) | Worker threads / `createRequire` |
|
|
21
|
+
|
|
22
|
+
## `run` vs `serve`
|
|
23
|
+
|
|
24
|
+
| | `flux run <entry.ts>` | `flux serve` |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| Compiles `.ts` on load | ✅ | ✅ |
|
|
27
|
+
| File watch / HMR reboot | ❌ | ✅ |
|
|
28
|
+
| Entry | Any path you pass | Package `main` (`main.ts` / `main.tsx`) |
|
|
29
|
+
| Process | Exits when the entry finishes | Long-lived until Ctrl+C (SIGINT) or SIGTERM |
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
The `flux` command is on `@decohere/cli` (this package is the API). Add the CLI with your package manager:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pnpm add -D @decohere/cli
|
|
37
|
+
# npm install --save-dev @decohere/cli
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
That installs the `flux`, `forge`, and `decohere` bins. Then:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pnpm exec flux --help
|
|
44
|
+
flux run projects/greeter/src/cli.ts
|
|
45
|
+
flux serve
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Scaffolded apps already depend on `@decohere/cli` — use those scripts (`pnpm dev`) or `pnpm exec flux`.
|
|
49
|
+
|
|
50
|
+
## CLI
|
|
51
|
+
|
|
52
|
+
Host options (`--root-dir`, `--config-file`, `--log-level`) are parsed before the subcommand. `--root-dir` (default: the current folder) is the workspace root. `--config-file` (default `.decohere.js` in the root, if present) is optional workspace config for package discovery. Omit it to use [workspace defaults](../workspace/README.md#workspace-layout).
|
|
53
|
+
|
|
54
|
+
### `flux run` — one-shot script
|
|
55
|
+
|
|
56
|
+
Any TypeScript file. The process exits when that module finishes evaluating.
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
// projects/greeter/src/cli.ts
|
|
60
|
+
const name = process.argv[2] ?? "world";
|
|
61
|
+
console.log(`hello ${name}`);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
flux run projects/greeter/src/cli.ts
|
|
66
|
+
# hello world
|
|
67
|
+
|
|
68
|
+
flux run projects/greeter/src/cli.ts -- Alice
|
|
69
|
+
# hello Alice
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
| Flag / arg | Default | Meaning |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| `<entry>` | (required) | Path to a TypeScript entry file |
|
|
75
|
+
| `[args…]` | — | Forwarded to the entry after argv rewrite (`process.argv.slice(2)`) |
|
|
76
|
+
| `--configuration <name>` | `debug` | Build configuration name |
|
|
77
|
+
|
|
78
|
+
Before import, `run` sets `process.argv` to `[node, absoluteEntry, …args]` so the file behaves like a real Node bin. Use `--` when the entry’s own flags would otherwise be parsed by Flux (`flux run ./src/cli.ts -- --verbose`).
|
|
79
|
+
|
|
80
|
+
### `flux serve` — long-lived process (HMR)
|
|
81
|
+
|
|
82
|
+
Use `serve` when the program should stay up until you kill it (HTTP server, worker). Flux watches source, disposes the previous entry, and re-imports on change.
|
|
83
|
+
|
|
84
|
+
`serve` boots the file named in that package’s `package.json` `main`. It must be `main.ts` / `main.tsx` (see [Classification](#classification-convention)). Snippet from `projects/my-api/package.json`:
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"name": "my-api",
|
|
89
|
+
"main": "main.ts"
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
// projects/my-api/main.ts
|
|
95
|
+
import { createServer } from "node:http";
|
|
96
|
+
|
|
97
|
+
const server = createServer((_req, res) => {
|
|
98
|
+
res.end("ok\n");
|
|
99
|
+
}).listen(3000);
|
|
100
|
+
|
|
101
|
+
console.log("listening on 3000");
|
|
102
|
+
|
|
103
|
+
export default {
|
|
104
|
+
async [Symbol.asyncDispose]() {
|
|
105
|
+
await new Promise<void>((resolve, reject) => {
|
|
106
|
+
server.close((err) => (err != null ? reject(err) : resolve()));
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
These are three ways to start the same serve (flags combine; `flux serve --project my-api --reboot-debounce-ms 500` is valid):
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
flux serve
|
|
116
|
+
flux serve --project my-api
|
|
117
|
+
flux serve --reboot-debounce-ms 500
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
| | What it does |
|
|
121
|
+
|---|---|
|
|
122
|
+
| `flux serve` | Boots the first discovered project (the workspace package lists packages by `package.json` `name`, A→Z). |
|
|
123
|
+
| `--project my-api` | Boots that package by name. |
|
|
124
|
+
| `--reboot-debounce-ms 500` | Same serve, but waits 500ms after the last save before rebooting (default 250ms). |
|
|
125
|
+
|
|
126
|
+
Use `--project` or [directory slices](#directory-slice-flux-serve-token) when the first package is not the app you want.
|
|
127
|
+
|
|
128
|
+
Ctrl+C (SIGINT) or SIGTERM (Docker, CI, process managers) disposes the entry, then the CLI exits with code 0.
|
|
129
|
+
|
|
130
|
+
| Flag / arg | Default | Meaning |
|
|
131
|
+
|---|---|---|
|
|
132
|
+
| `[slices…]` | — | One or more module tokens among discovered packages (union; see **Directory slice**) |
|
|
133
|
+
| `--configuration <name>` | `debug` | Build configuration name (not a file path) |
|
|
134
|
+
| `--project <name>` | first discovered package | Single package name to serve (not with slices) |
|
|
135
|
+
| `--reboot-debounce-ms <ms>` | `250` | Quiet window after the last affecting save before a reboot. See [Reboot debounce](#reboot-debounce) before raising it. |
|
|
136
|
+
|
|
137
|
+
On a watched change that reaches a served entry, Flux invalidates that subgraph, calls `asyncDispose` on the previous default export, and re-imports. Rapid saves are trailing-debounced and serialized (overlapping dispose cannot run). A change that never reaches an entry does nothing. Each reboot logs `flux reboot` / `flux ready` at info (project names, triggering files, elapsed ms) so the event stays visible when the app boot is chatty. File-watch detail stays at trace.
|
|
138
|
+
|
|
139
|
+
### Reboot debounce
|
|
140
|
+
|
|
141
|
+
Saves less than the window apart (250ms by default) become one reboot. Saves further apart each reboot, and a save during a reboot folds into one follow-up reboot. Whatever the timing, the app ends up on the last save; a reboot in between may fail (an import not yet updated) and logs the error.
|
|
142
|
+
|
|
143
|
+
The window adds its length to every reload, including a single save. Measured on a rename across six files:
|
|
144
|
+
|
|
145
|
+
| Gap between saves | Reboots at 250ms | Reboots at 2000ms |
|
|
146
|
+
|---|---|---|
|
|
147
|
+
| 0–100ms: parallel edits by an agent, a formatter, a checkout | 1 | 1 |
|
|
148
|
+
| 400ms–1.5s | 6 | 1 |
|
|
149
|
+
| 4s: an agent's edits made one tool call at a time | 6 | 6 |
|
|
150
|
+
| Time from the last save to the app ready | ~0.3s | ~2s |
|
|
151
|
+
|
|
152
|
+
So keep the default. An agent's saves in one go land inside it, and its one-at-a-time saves are seconds apart, beyond any window short enough for a person's save. Raise it only for a tool you know saves at a steady pace inside your chosen window.
|
|
153
|
+
|
|
154
|
+
### Dispose on reboot
|
|
155
|
+
|
|
156
|
+
`serve` loads the entry’s **default export**. If that object implements `Symbol.asyncDispose`, Flux calls it:
|
|
157
|
+
|
|
158
|
+
- before each HMR reboot (so the next boot can bind the same port / reopen the DB)
|
|
159
|
+
- on SIGINT / SIGTERM / CLI teardown
|
|
160
|
+
|
|
161
|
+
The `export default { async [Symbol.asyncDispose]() { … } }` in the server example above is that contract. Close servers, pools, and timers there — not in a `process.on("SIGINT")` handler Flux does not own.
|
|
162
|
+
|
|
163
|
+
**HTTP APIs** scaffold `export default app`. `App.dispose` disposes the root DI container, then Scribe. Constructed services that implement `Symbol.asyncDispose` (or `Symbol.dispose`) are torn down automatically — including the HTTP provider. Background work (timers, feeds, pollers) belongs in a [hosted service](../http/README.md#hosted-services): its signal is aborted and it is stopped on every reboot, so nothing keeps running into the next one. Put teardown on the service; do not wrap the default export, and do not keep workers in globals across reboots.
|
|
164
|
+
|
|
165
|
+
Flux clears its reference to the object *before* calling dispose, so a failed boot cannot dispose the same instance twice.
|
|
166
|
+
|
|
167
|
+
## Directory slice (`flux serve <token>…`)
|
|
168
|
+
|
|
169
|
+
A slice is a path token. Flux keeps workspace packages whose directory sits under that token, then [classifies](#classification-convention) each one. `flux serve shop` and `flux serve projects/shop` are the same token.
|
|
170
|
+
|
|
171
|
+
Example workspace (every leaf directory here has its own `package.json`):
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
projects/
|
|
175
|
+
app.web/ # scripts.dev: vite → UI
|
|
176
|
+
shop/
|
|
177
|
+
api/ # main: main.ts → Node app
|
|
178
|
+
worker/ # main: main.ts → Node app
|
|
179
|
+
web/ # scripts.dev: vite → UI
|
|
180
|
+
domain/ # main: src/index.ts → library
|
|
181
|
+
shop-admin.api/ # main: main.ts → other module
|
|
182
|
+
billing/
|
|
183
|
+
api/ # main: main.ts → Node app
|
|
184
|
+
domain/ # main: src/index.ts → library
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`flux serve shop` matches `projects/shop` and children (and `projects/shop.*`). It does **not** match `shop-admin.api`.
|
|
188
|
+
|
|
189
|
+
| | In-process (HMR) | Spawn `scripts.dev` / `serve` | Skip |
|
|
190
|
+
|---|---|---|---|
|
|
191
|
+
| `flux serve shop` | `shop/api`, `shop/worker` | `shop/web` | `shop/domain` |
|
|
192
|
+
| `flux serve app` | — | `app.web` | — |
|
|
193
|
+
| `flux serve shop-admin` | `shop-admin.api` | — | — |
|
|
194
|
+
| `flux serve app shop billing` | `shop/api`, `shop/worker`, `billing/api` | `app.web`, `shop/web` | `shop/domain`, `billing/domain` |
|
|
195
|
+
|
|
196
|
+
Multiple tokens are a union. Use slices **or** `--project`, not both.
|
|
197
|
+
|
|
198
|
+
Matching is path-safe:
|
|
199
|
+
|
|
200
|
+
- Nested: `shop` → `projects/shop`, `projects/shop/api`
|
|
201
|
+
- Flat dotted packages: `shop` → `projects/shop.web`, `projects/shop.domain`; `app` → `projects/app.web`
|
|
202
|
+
- Does **not** match siblings that only share a string prefix (`shop` ≠ `projects/shop-admin.api` — pass `shop-admin` explicitly)
|
|
203
|
+
|
|
204
|
+
Named serve profiles in `.decohere.js` (e.g. `shop-stack: ["app", "shop", "billing"]`) are deferred.
|
|
205
|
+
|
|
206
|
+
### Classification (convention)
|
|
207
|
+
|
|
208
|
+
| Kind | How Flux decides | Behavior |
|
|
209
|
+
|---|---|---|
|
|
210
|
+
| **Foreign / UI** | `scripts.dev` or `scripts.serve` that does **not** invoke `flux` | Spawn `pnpm`/`npm`/`yarn` `run <script>` in that package cwd. Flux does not HMR-boot it. |
|
|
211
|
+
| **Flux app** | `package.json` `main` ends in `main.ts` / `main.tsx`, and no foreign serve script | Boot in-process (one HMR graph per app). |
|
|
212
|
+
| **Library** | Everything else (e.g. `main`: `src/index.ts`) | Skip — imported via debug aliases. |
|
|
213
|
+
|
|
214
|
+
Foreign scripts win over `main.ts` so Vite (and similar) apps are never loaded in the Node HMR hook. The **package script** is the contract, not a hard-coded Vite check.
|
|
215
|
+
|
|
216
|
+
Logs name skipped libraries and deferred packages (script + cwd). Child processes are stopped when the CLI exits (SIGINT or SIGTERM → dispose).
|
|
217
|
+
|
|
218
|
+
## Compile cache
|
|
219
|
+
|
|
220
|
+
Flux keeps the code it compiles on disk, so starting again compiles only what changed. On a cold start with nothing changed, it reads every file from the cache instead of compiling it (`pnpm bench flux-load`, `flux.load.cold-cached`).
|
|
221
|
+
|
|
222
|
+
- **Where:** `<workspace>/node_modules/.cache/decohere/flux`. When the workspace has no `node_modules`, it's under `$XDG_CACHE_HOME/decohere`, then the OS temp directory.
|
|
223
|
+
- **Settings:** `DECOHERE_CACHE_DIR=<dir>` moves the cache; `DECOHERE_CACHE=0` turns it off.
|
|
224
|
+
- **When it's used:** entries are keyed by the file's path and source, the SWC options it compiles with and SWC's version. Any change to those is a miss, never stale code.
|
|
225
|
+
- **Concurrency:** entries are written to a temp file and renamed into place, so processes starting at once share one cache safely.
|
|
226
|
+
- **Housekeeping:** entries untouched for 30 days are pruned, at most once a day. To clear the cache, delete the folder. `forge clean` leaves it alone, because cleaning runs before every build.
|
|
227
|
+
- **If it can't be used:** when flux can't read or write the folder, it warns once and runs without the cache.
|
|
228
|
+
|
|
229
|
+
## Limits
|
|
230
|
+
|
|
231
|
+
Flux does not support `createRequire`, worker threads, or browser HMR. Reloads always dispose and re-import the entry — there is no `import.meta.hot.accept()` boundary yet.
|
|
232
|
+
|
|
233
|
+
**Restart `flux serve` after changing any of these** — they're read once at start, and saving them doesn't reboot:
|
|
234
|
+
|
|
235
|
+
- `tsconfig.json`, `.swcrc`, or `.decohere.js`
|
|
236
|
+
- a `package.json` (name, `main`, dependencies)
|
|
237
|
+
- adding or removing a package under `projects/`
|
|
238
|
+
|
|
239
|
+
Only source files (`.ts`) that a served entry imports trigger a reboot.
|
|
240
|
+
|
|
241
|
+
**Memory grows over a long session.** Each reboot imports changed modules under a new `?version=` URL, and Node's ES module cache never releases the old versions. That's fine for a working day; if a session runs for days with thousands of reboots, restart it.
|
|
242
|
+
|
|
243
|
+
Flux uses **Forge’s compiler**, with one SWC config per project built once per run. SWC leaves imports as written; Flux's resolve hook maps each one to its TypeScript source: `@scope/pkg` and `@scope/pkg/sub` through the the workspace package aliases (and tsconfig `paths`), `./file.js` and `./file` to `file.ts` or `file.tsx`, `./file.mjs` to `file.mts`, `./file.cjs` to `file.cts` (which runs as CommonJS), and `./dir` to `dir/index.ts`. Doing it in one place, instead of in SWC (`jsc.paths`, `resolveFully`) and again in the hook, made loading about 20% faster once Node and SWC are warm (`pnpm bench flux-load`). Before Node imports an entry, Flux compiles it and the files it imports, following their imports out from the entry, in parallel on SWC's thread pool. The load hook is synchronous and would otherwise compile one file at a time; files the app never imports, such as tests, are not compiled. A file that fails to compile there is compiled again when Node loads it, and that load fails with the file's name, as before. That is a debug rule so you can edit a library and hit HMR without a pre-build or `node_modules` workspace symlink. Release still resolves through real `node_modules` (published `exports` / `main`).
|
|
244
|
+
|
|
245
|
+
Why aliases, and a tree of where a symlink misses `src/index.ts`: [Forge — debug path aliases](../forge/README.md#debug-path-aliases-not-node_modules-links).
|
|
246
|
+
|
|
247
|
+
## Programmatic API
|
|
248
|
+
|
|
249
|
+
Use this only when you need to **embed** Flux (tests, a custom host). Day-to-day, invoke `flux run` / `flux serve` — do not import `@decohere/flux` from application code.
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
import { flux } from "@decohere/flux";
|
|
253
|
+
|
|
254
|
+
// One-shot compile-on-load (no watch) — same idea as `flux run`
|
|
255
|
+
await flux.run("/abs/path/to/entry.ts");
|
|
256
|
+
await flux.run("./tool.ts", { configuration: "debug", args: ["--flag"] });
|
|
257
|
+
|
|
258
|
+
// Long-lived HMR — same idea as `flux serve` (in-process apps)
|
|
259
|
+
const server = await flux.serve({
|
|
260
|
+
configuration: "debug",
|
|
261
|
+
project: "my-api", // optional single package
|
|
262
|
+
// projects: ["api", "svc"], // optional multi-entry
|
|
263
|
+
rebootDebounceMs: 500, // optional; default 250
|
|
264
|
+
});
|
|
265
|
+
server.on("reboot", () => {}); // after dispose + boot attempt for affected entries
|
|
266
|
+
await server.dispose();
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
`configuration` is the workspace configuration **name** (`debug`, `release`, …), matching `--configuration`. Defaults to `debug`. Pass `workspace` in tests to skip `WorkspaceFactory`.
|
|
270
|
+
|
|
271
|
+
`flux.run` keeps Node ESM hooks registered for the process so the imported graph can still load `.ts`. Do not dispose the loader after a successful run.
|
|
272
|
+
|
|
273
|
+
Slice helpers (`classifySliceProjects`, `normalizeSlicePrefix`, …) and the engine types (`SourceModuleLoader`, `HotReloadServer`) stay exported for tests and tooling; the CLI and `flux` facade are the primary surfaces.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "DeferredScriptRunner", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return DeferredScriptRunner;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _nodechild_process = require("node:child_process");
|
|
12
|
+
const _decorators = require("@decohere/crucible/decorators");
|
|
13
|
+
const _interop = require("@decohere/devkit/interop");
|
|
14
|
+
const _scribe = require("@decohere/scribe");
|
|
15
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
16
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
17
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
18
|
+
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
19
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
20
|
+
}
|
|
21
|
+
class DeferredScriptRunner {
|
|
22
|
+
logger = _scribe.scribe.logger(DeferredScriptRunner.name);
|
|
23
|
+
children = [];
|
|
24
|
+
start(options) {
|
|
25
|
+
const pm = (0, _interop.detectPackageManager)(options.workspaceRoot) ?? (0, _interop.packageManagerFromString)("pnpm");
|
|
26
|
+
this.logger.info(`deferred ${options.label} — running ${pm.name} run ${options.script} in ${options.cwd}`);
|
|
27
|
+
const child = (0, _nodechild_process.spawn)(pm.name, [
|
|
28
|
+
"run",
|
|
29
|
+
options.script
|
|
30
|
+
], {
|
|
31
|
+
cwd: options.cwd,
|
|
32
|
+
stdio: "inherit",
|
|
33
|
+
env: process.env,
|
|
34
|
+
shell: false
|
|
35
|
+
});
|
|
36
|
+
child.on("exit", (code, signal)=>{
|
|
37
|
+
if (signal) return;
|
|
38
|
+
if (code != null && code !== 0) {
|
|
39
|
+
this.logger.warn(`deferred ${options.label} exited with ${code}`);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
this.children.push(child);
|
|
43
|
+
return child;
|
|
44
|
+
}
|
|
45
|
+
async [Symbol.asyncDispose]() {
|
|
46
|
+
for (const child of this.children){
|
|
47
|
+
if (!child.killed) {
|
|
48
|
+
child.kill("SIGTERM");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
this.children.length = 0;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
DeferredScriptRunner = _ts_decorate([
|
|
55
|
+
(0, _decorators.injectable)()
|
|
56
|
+
], DeferredScriptRunner);
|
|
@@ -8,7 +8,8 @@ Object.defineProperty(exports, "FluxCommand", {
|
|
|
8
8
|
return FluxCommand;
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
|
-
const _cli = require("@decohere/
|
|
11
|
+
const _cli = require("@decohere/devkit/cli");
|
|
12
|
+
const _runcommand = require("./run.command.js");
|
|
12
13
|
const _servecommand = require("./serve.command.js");
|
|
13
14
|
class FluxCommand extends _cli.DecohereCommand {
|
|
14
15
|
constructor(commands, handler){
|
|
@@ -16,6 +17,7 @@ class FluxCommand extends _cli.DecohereCommand {
|
|
|
16
17
|
this.exitOverride();
|
|
17
18
|
this.registerSubcommand({
|
|
18
19
|
serve: _servecommand.ServeCommand,
|
|
20
|
+
run: _runcommand.RunCommand,
|
|
19
21
|
...commands
|
|
20
22
|
}, handler);
|
|
21
23
|
}
|
|
@@ -23,5 +25,3 @@ class FluxCommand extends _cli.DecohereCommand {
|
|
|
23
25
|
return this.help();
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
|
-
|
|
27
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi9ob21lL3J1bm5lci93b3JrL2RlY29oZXJlL2RlY29oZXJlL3Byb2plY3RzL2ZsdXgvc3JjL2ZsdXguY29tbWFuZC50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgdHlwZSB7IEFjdGlvbnMgfSBmcm9tIFwiQGRlY29oZXJlL25leHVzL2NsaVwiO1xuaW1wb3J0IHtcblx0dHlwZSBDb21tYW5kSGFuZGxlcixcblx0dHlwZSBDb21tYW5kUmVzcG9uc2UsXG5cdHR5cGUgQ29tbWFuZFR5cGUsXG5cdERlY29oZXJlQ29tbWFuZCxcbn0gZnJvbSBcIkBkZWNvaGVyZS9uZXh1cy9jbGlcIjtcbmltcG9ydCB7IFNlcnZlQ29tbWFuZCB9IGZyb20gXCIuL3NlcnZlLmNvbW1hbmQuanNcIjtcblxuZXhwb3J0IGNsYXNzIEZsdXhDb21tYW5kIGV4dGVuZHMgRGVjb2hlcmVDb21tYW5kIHtcblx0cHVibGljIGNvbnN0cnVjdG9yKGNvbW1hbmRzOiBSZWNvcmQ8c3RyaW5nLCBDb21tYW5kVHlwZT4sIGhhbmRsZXI6IENvbW1hbmRIYW5kbGVyKSB7XG5cdFx0c3VwZXIoRmx1eENvbW1hbmQpO1xuXHRcdHRoaXMuZXhpdE92ZXJyaWRlKCk7XG5cdFx0dGhpcy5yZWdpc3RlclN1YmNvbW1hbmQoeyBzZXJ2ZTogU2VydmVDb21tYW5kLCAuLi5jb21tYW5kcyB9LCBoYW5kbGVyKTtcblx0fVxuXG5cdHB1YmxpYyBydW4oX2FjdGlvbnM6IEFjdGlvbnMsIF9vcHRpb25zOiBuZXZlcik6IFByb21pc2U8Q29tbWFuZFJlc3BvbnNlPiB7XG5cdFx0cmV0dXJuIHRoaXMuaGVscCgpO1xuXHR9XG59XG4iXSwibmFtZXMiOlsiRmx1eENvbW1hbmQiLCJEZWNvaGVyZUNvbW1hbmQiLCJjb21tYW5kcyIsImhhbmRsZXIiLCJleGl0T3ZlcnJpZGUiLCJyZWdpc3RlclN1YmNvbW1hbmQiLCJzZXJ2ZSIsIlNlcnZlQ29tbWFuZCIsInJ1biIsIl9hY3Rpb25zIiwiX29wdGlvbnMiLCJoZWxwIl0sIm1hcHBpbmdzIjoiOzs7OytCQVNhQTs7O2VBQUFBOzs7cUJBSE47OEJBQ3NCO0FBRXRCLE1BQU1BLG9CQUFvQkMsb0JBQWU7SUFDL0MsWUFBbUJDLFFBQXFDLEVBQUVDLE9BQXVCLENBQUU7UUFDbEYsS0FBSyxDQUFDSDtRQUNOLElBQUksQ0FBQ0ksWUFBWTtRQUNqQixJQUFJLENBQUNDLGtCQUFrQixDQUFDO1lBQUVDLE9BQU9DLDBCQUFZO1lBQUUsR0FBR0wsUUFBUTtRQUFDLEdBQUdDO0lBQy9EO0lBRU9LLElBQUlDLFFBQWlCLEVBQUVDLFFBQWUsRUFBNEI7UUFDeEUsT0FBTyxJQUFJLENBQUNDLElBQUk7SUFDakI7QUFDRCJ9
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "flux", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return flux;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _nodepath = /*#__PURE__*/ _interop_require_default(require("node:path"));
|
|
12
|
+
const _hotreloadserver = require("./hot-reload-server.js");
|
|
13
|
+
const _sourcemoduleloader = require("./source-module-loader.js");
|
|
14
|
+
function _interop_require_default(obj) {
|
|
15
|
+
return obj && obj.__esModule ? obj : {
|
|
16
|
+
default: obj
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** Process-level one-shot loader so a second `run()` reconfigures instead of stacking hooks. */ const runLoader = new _sourcemoduleloader.SourceModuleLoader();
|
|
20
|
+
async function run(entry, options = {}) {
|
|
21
|
+
const trimmed = entry.trim();
|
|
22
|
+
if (trimmed == null || trimmed === "") {
|
|
23
|
+
throw new Error("flux.run requires an entry file path");
|
|
24
|
+
}
|
|
25
|
+
const absoluteEntry = _nodepath.default.resolve(trimmed);
|
|
26
|
+
await runLoader.configure({
|
|
27
|
+
workspace: options.workspace,
|
|
28
|
+
buildConfig: options.configuration ?? "debug"
|
|
29
|
+
});
|
|
30
|
+
runLoader.registerHooks();
|
|
31
|
+
process.argv = [
|
|
32
|
+
process.execPath,
|
|
33
|
+
absoluteEntry,
|
|
34
|
+
...options.args ?? []
|
|
35
|
+
];
|
|
36
|
+
return runLoader.importEntry(absoluteEntry);
|
|
37
|
+
}
|
|
38
|
+
async function serve(options = {}) {
|
|
39
|
+
const server = new _hotreloadserver.HotReloadServer();
|
|
40
|
+
await server.start({
|
|
41
|
+
buildConfig: options.configuration ?? "debug",
|
|
42
|
+
project: options.project,
|
|
43
|
+
projects: options.projects,
|
|
44
|
+
workspace: options.workspace,
|
|
45
|
+
rebootDebounceMs: options.rebootDebounceMs
|
|
46
|
+
});
|
|
47
|
+
return server;
|
|
48
|
+
}
|
|
49
|
+
function dispose() {
|
|
50
|
+
runLoader.dispose();
|
|
51
|
+
}
|
|
52
|
+
const flux = {
|
|
53
|
+
run,
|
|
54
|
+
serve,
|
|
55
|
+
dispose
|
|
56
|
+
};
|
|
@@ -8,25 +8,26 @@ Object.defineProperty(exports, "FluxModule", {
|
|
|
8
8
|
return FluxModule;
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
|
-
const
|
|
12
|
-
const _cli = require("@decohere/
|
|
11
|
+
const _forge = require("@decohere/forge");
|
|
12
|
+
const _cli = require("@decohere/devkit/cli");
|
|
13
|
+
const _deferredscriptrunner = require("./deferred-script-runner.js");
|
|
13
14
|
const _fluxcommand = require("./flux.command.js");
|
|
14
15
|
const _hotreloadserver = require("./hot-reload-server.js");
|
|
16
|
+
const _runaction = require("./run.action.js");
|
|
15
17
|
const _serveaction = require("./serve.action.js");
|
|
16
18
|
class FluxModule extends _cli.ModuleManifest {
|
|
17
19
|
id = "flux";
|
|
18
20
|
command = _fluxcommand.FluxCommand;
|
|
19
21
|
actions = {
|
|
20
|
-
serve: _serveaction.ServeAction
|
|
22
|
+
serve: _serveaction.ServeAction,
|
|
23
|
+
run: _runaction.RunAction
|
|
21
24
|
};
|
|
22
25
|
registerServices(builder) {
|
|
23
26
|
builder.registerSingleton(_hotreloadserver.HotReloadServer);
|
|
27
|
+
builder.registerSingleton(_deferredscriptrunner.DeferredScriptRunner);
|
|
24
28
|
}
|
|
25
29
|
createWorkspace(options) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
});
|
|
30
|
+
// Same workspace as `flux.run` / `flux.serve` and forge.
|
|
31
|
+
return (0, _forge.createWorkspace)(options);
|
|
29
32
|
}
|
|
30
33
|
}
|
|
31
|
-
|
|
32
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi9ob21lL3J1bm5lci93b3JrL2RlY29oZXJlL2RlY29oZXJlL3Byb2plY3RzL2ZsdXgvc3JjL2ZsdXgubW9kdWxlLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IFdvcmtzcGFjZUZhY3RvcnkgfSBmcm9tIFwiQGRlY29oZXJlL21lc2hcIjtcbmltcG9ydCB0eXBlIHsgV29ya3NwYWNlRmFjdG9yeU9wdGlvbnMgfSBmcm9tIFwiQGRlY29oZXJlL25leHVzL2NsaVwiO1xuaW1wb3J0IHsgTW9kdWxlTWFuaWZlc3QgfSBmcm9tIFwiQGRlY29oZXJlL25leHVzL2NsaVwiO1xuaW1wb3J0IHR5cGUgeyBDb250YWluZXJCdWlsZGVyIH0gZnJvbSBcIkBkZWNvaGVyZS9uZXh1cy9jb21wb3NpdGlvblwiO1xuaW1wb3J0IHsgRmx1eENvbW1hbmQgfSBmcm9tIFwiLi9mbHV4LmNvbW1hbmQuanNcIjtcbmltcG9ydCB7IEhvdFJlbG9hZFNlcnZlciB9IGZyb20gXCIuL2hvdC1yZWxvYWQtc2VydmVyLmpzXCI7XG5pbXBvcnQgeyBTZXJ2ZUFjdGlvbiB9IGZyb20gXCIuL3NlcnZlLmFjdGlvbi5qc1wiO1xuXG5leHBvcnQgY2xhc3MgRmx1eE1vZHVsZSBleHRlbmRzIE1vZHVsZU1hbmlmZXN0IHtcblx0cHVibGljIHJlYWRvbmx5IGlkID0gXCJmbHV4XCI7XG5cdHB1YmxpYyByZWFkb25seSBjb21tYW5kID0gRmx1eENvbW1hbmQ7XG5cdHB1YmxpYyByZWFkb25seSBhY3Rpb25zID0ge1xuXHRcdHNlcnZlOiBTZXJ2ZUFjdGlvbixcblx0fTtcblxuXHRwdWJsaWMgcmVnaXN0ZXJTZXJ2aWNlcyhidWlsZGVyOiBDb250YWluZXJCdWlsZGVyKSB7XG5cdFx0YnVpbGRlci5yZWdpc3RlclNpbmdsZXRvbihIb3RSZWxvYWRTZXJ2ZXIpO1xuXHR9XG5cblx0cHVibGljIG92ZXJyaWRlIGNyZWF0ZVdvcmtzcGFjZShvcHRpb25zOiBXb3Jrc3BhY2VGYWN0b3J5T3B0aW9ucykge1xuXHRcdHJldHVybiBXb3Jrc3BhY2VGYWN0b3J5LmNyZWF0ZSh7IGNvbmZpZ0ZpbGU6IG9wdGlvbnMuY29uZmlnRmlsZSB9KTtcblx0fVxufVxuIl0sIm5hbWVzIjpbIkZsdXhNb2R1bGUiLCJNb2R1bGVNYW5pZmVzdCIsImlkIiwiY29tbWFuZCIsIkZsdXhDb21tYW5kIiwiYWN0aW9ucyIsInNlcnZlIiwiU2VydmVBY3Rpb24iLCJyZWdpc3RlclNlcnZpY2VzIiwiYnVpbGRlciIsInJlZ2lzdGVyU2luZ2xldG9uIiwiSG90UmVsb2FkU2VydmVyIiwiY3JlYXRlV29ya3NwYWNlIiwib3B0aW9ucyIsIldvcmtzcGFjZUZhY3RvcnkiLCJjcmVhdGUiLCJjb25maWdGaWxlIl0sIm1hcHBpbmdzIjoiOzs7OytCQVFhQTs7O2VBQUFBOzs7c0JBUm9CO3FCQUVGOzZCQUVIO2lDQUNJOzZCQUNKO0FBRXJCLE1BQU1BLG1CQUFtQkMsbUJBQWM7SUFDN0JDLEtBQUssT0FBTztJQUNaQyxVQUFVQyx3QkFBVyxDQUFDO0lBQ3RCQyxVQUFVO1FBQ3pCQyxPQUFPQyx3QkFBVztJQUNuQixFQUFFO0lBRUtDLGlCQUFpQkMsT0FBeUIsRUFBRTtRQUNsREEsUUFBUUMsaUJBQWlCLENBQUNDLGdDQUFlO0lBQzFDO0lBRWdCQyxnQkFBZ0JDLE9BQWdDLEVBQUU7UUFDakUsT0FBT0Msc0JBQWdCLENBQUNDLE1BQU0sQ0FBQztZQUFFQyxZQUFZSCxRQUFRRyxVQUFVO1FBQUM7SUFDakU7QUFDRCJ9
|