@decohere/flux 0.0.1-dev.11 → 0.0.1-dev.111
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 +247 -0
- package/commonjs/src/deferred-script-runner.js +56 -0
- package/commonjs/src/flux.command.js +2 -2
- package/commonjs/src/flux.js +56 -0
- package/commonjs/src/flux.module.js +8 -7
- package/commonjs/src/hot-reload-server.js +302 -215
- package/commonjs/src/index.js +34 -2
- package/commonjs/src/rewrite-serve-specifier.js +59 -0
- package/commonjs/src/run.action.js +47 -0
- package/commonjs/src/run.command.js +65 -0
- package/commonjs/src/serve.action.js +76 -5
- package/commonjs/src/serve.command.js +34 -3
- package/commonjs/src/slice-serve.js +130 -0
- package/commonjs/src/source-module-loader.js +311 -0
- package/commonjs/src/static-imports.js +37 -0
- package/es6/src/deferred-script-runner.js +46 -0
- package/es6/src/flux.command.js +2 -2
- package/es6/src/flux.js +41 -0
- package/es6/src/flux.module.js +8 -7
- package/es6/src/hot-reload-server.js +283 -172
- package/es6/src/index.js +5 -3
- package/es6/src/rewrite-serve-specifier.js +49 -0
- package/es6/src/run.action.js +37 -0
- package/es6/src/run.command.js +55 -0
- package/es6/src/serve.action.js +71 -7
- package/es6/src/serve.command.js +34 -3
- package/es6/src/slice-serve.js +105 -0
- package/es6/src/source-module-loader.js +255 -0
- package/es6/src/static-imports.js +27 -0
- package/package.json +7 -6
- package/types/src/deferred-script-runner.d.ts +18 -0
- package/types/src/flux.d.ts +43 -0
- package/types/src/flux.module.d.ts +2 -0
- package/types/src/hot-reload-server.d.ts +55 -22
- 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 +11 -1
- package/types/src/serve.command.d.ts +6 -0
- package/types/src/slice-serve.d.ts +52 -0
- package/types/src/source-module-loader.d.ts +81 -0
- package/types/src/static-imports.d.ts +5 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "SourceModuleLoader", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return SourceModuleLoader;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _nodemodule = require("node:module");
|
|
12
|
+
const _nodepath = /*#__PURE__*/ _interop_require_default(require("node:path"));
|
|
13
|
+
const _nodeurl = require("node:url");
|
|
14
|
+
const _decorators = require("@decohere/crucible/decorators");
|
|
15
|
+
const _forge = require("@decohere/forge");
|
|
16
|
+
const _mesh = require("@decohere/mesh");
|
|
17
|
+
const _io = require("@decohere/nexus/io");
|
|
18
|
+
const _scribe = require("@decohere/scribe");
|
|
19
|
+
const _rewriteservespecifier = require("./rewrite-serve-specifier.js");
|
|
20
|
+
const _staticimports = require("./static-imports.js");
|
|
21
|
+
function _interop_require_default(obj) {
|
|
22
|
+
return obj && obj.__esModule ? obj : {
|
|
23
|
+
default: obj
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function _getRequireWildcardCache(nodeInterop) {
|
|
27
|
+
if (typeof WeakMap !== "function") return null;
|
|
28
|
+
var cacheBabelInterop = new WeakMap();
|
|
29
|
+
var cacheNodeInterop = new WeakMap();
|
|
30
|
+
return (_getRequireWildcardCache = function(nodeInterop) {
|
|
31
|
+
return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
|
|
32
|
+
})(nodeInterop);
|
|
33
|
+
}
|
|
34
|
+
function _interop_require_wildcard(obj, nodeInterop) {
|
|
35
|
+
if (!nodeInterop && obj && obj.__esModule) {
|
|
36
|
+
return obj;
|
|
37
|
+
}
|
|
38
|
+
if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
|
|
39
|
+
return {
|
|
40
|
+
default: obj
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
var cache = _getRequireWildcardCache(nodeInterop);
|
|
44
|
+
if (cache && cache.has(obj)) {
|
|
45
|
+
return cache.get(obj);
|
|
46
|
+
}
|
|
47
|
+
var newObj = {
|
|
48
|
+
__proto__: null
|
|
49
|
+
};
|
|
50
|
+
var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
|
|
51
|
+
for(var key in obj){
|
|
52
|
+
if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
53
|
+
var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
|
|
54
|
+
if (desc && (desc.get || desc.set)) {
|
|
55
|
+
Object.defineProperty(newObj, key, desc);
|
|
56
|
+
} else {
|
|
57
|
+
newObj[key] = obj[key];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
newObj.default = obj;
|
|
62
|
+
if (cache) {
|
|
63
|
+
cache.set(obj, newObj);
|
|
64
|
+
}
|
|
65
|
+
return newObj;
|
|
66
|
+
}
|
|
67
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
68
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
69
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
70
|
+
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;
|
|
71
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
72
|
+
}
|
|
73
|
+
class SourceModuleLoader {
|
|
74
|
+
logger = _scribe.scribe.logger(SourceModuleLoader.name);
|
|
75
|
+
transpiler = new _forge.SwcTranspiler();
|
|
76
|
+
disposed = false;
|
|
77
|
+
hooksRegistered = false;
|
|
78
|
+
workspace;
|
|
79
|
+
buildConfig = "debug";
|
|
80
|
+
fileInfoMap = new Map();
|
|
81
|
+
transpileCache = new Map();
|
|
82
|
+
/** Bumped when a file's cached code is dropped, so an up-front compile already running can't put it back. */ transpileVersions = new Map();
|
|
83
|
+
/** The source files each entry's walk reached, so a reboot only walks out from what changed. */ reachedFrom = new Map();
|
|
84
|
+
/** Up-front compiles in flight, shared by entries booting at the same time. */ compilingAhead = new Map();
|
|
85
|
+
/** SWC options per project, built on first use and kept until the next `configure()`. */ swcConfigs = new Map();
|
|
86
|
+
aliasPaths = {};
|
|
87
|
+
aliasBaseUrl = _nodepath.default.resolve(".");
|
|
88
|
+
versionFileUrl = (fileUrl)=>fileUrl;
|
|
89
|
+
onResolved;
|
|
90
|
+
getWorkspace() {
|
|
91
|
+
return this.workspace;
|
|
92
|
+
}
|
|
93
|
+
getBuildConfig() {
|
|
94
|
+
return this.buildConfig;
|
|
95
|
+
}
|
|
96
|
+
hasSourceFile(absolutePath) {
|
|
97
|
+
return this.fileInfoMap.has(absolutePath);
|
|
98
|
+
}
|
|
99
|
+
sourceFilePaths() {
|
|
100
|
+
return this.fileInfoMap.keys();
|
|
101
|
+
}
|
|
102
|
+
setSourceFile(absolutePath, project) {
|
|
103
|
+
this.fileInfoMap.set(absolutePath, {
|
|
104
|
+
project
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
async configure(options) {
|
|
108
|
+
this.disposed = false;
|
|
109
|
+
// A new Map, not clear(): an up-front compile from before can't write into this one.
|
|
110
|
+
this.transpileCache = new Map();
|
|
111
|
+
this.reachedFrom = new Map();
|
|
112
|
+
this.swcConfigs.clear();
|
|
113
|
+
this.buildConfig = options.buildConfig;
|
|
114
|
+
this.workspace = options.workspace ?? await (0, _forge.createWorkspace)();
|
|
115
|
+
this.versionFileUrl = options.versionFileUrl ?? ((fileUrl)=>fileUrl);
|
|
116
|
+
this.onResolved = options.onResolved;
|
|
117
|
+
this.fileInfoMap = await this.buildFileInfoMap(this.buildConfig);
|
|
118
|
+
const aliases = this.workspace.getPackageAliases(this.buildConfig);
|
|
119
|
+
this.aliasBaseUrl = aliases.baseUrl;
|
|
120
|
+
this.aliasPaths = aliases.paths;
|
|
121
|
+
}
|
|
122
|
+
registerHooks() {
|
|
123
|
+
if (this.hooksRegistered) return;
|
|
124
|
+
this.hooksRegistered = true;
|
|
125
|
+
// Compiled code carries inline source maps; Node only applies them to stack traces when
|
|
126
|
+
// this is on. Without it, errors point at compiled line numbers (#506).
|
|
127
|
+
process.setSourceMapsEnabled(true);
|
|
128
|
+
(0, _nodemodule.registerHooks)({
|
|
129
|
+
resolve: (specifier, context, next)=>{
|
|
130
|
+
if (this.disposed) return next(specifier, context);
|
|
131
|
+
const parentUrl = context.parentURL ?? "";
|
|
132
|
+
const rewritten = (0, _rewriteservespecifier.rewriteServeSpecifier)({
|
|
133
|
+
specifier,
|
|
134
|
+
parentUrl,
|
|
135
|
+
isWatchedTs: (filePath)=>this.fileInfoMap.has(filePath),
|
|
136
|
+
resolveBare: (bare)=>_mesh.packageAliases.resolvePath(bare, this.aliasPaths, this.aliasBaseUrl),
|
|
137
|
+
versionFileUrl: this.versionFileUrl
|
|
138
|
+
}) ?? specifier;
|
|
139
|
+
const result = next(rewritten, context);
|
|
140
|
+
this.onResolved?.(result, parentUrl);
|
|
141
|
+
return result;
|
|
142
|
+
},
|
|
143
|
+
load: (url, context, next)=>{
|
|
144
|
+
if (this.disposed) return next(url, context);
|
|
145
|
+
const result = this.loadTs(url);
|
|
146
|
+
if (result != null) {
|
|
147
|
+
return {
|
|
148
|
+
format: "module",
|
|
149
|
+
source: result.source,
|
|
150
|
+
shortCircuit: true
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
return next(url, context);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
async importEntry(absolutePath) {
|
|
158
|
+
await this.compileAhead(_nodepath.default.resolve(absolutePath));
|
|
159
|
+
const url = this.versionFileUrl(_io.io.pathToFileUrl(_nodepath.default.resolve(absolutePath)));
|
|
160
|
+
return Promise.resolve(url).then((p)=>/*#__PURE__*/ _interop_require_wildcard(require(p)));
|
|
161
|
+
}
|
|
162
|
+
invalidateTranspile(...absolutePaths) {
|
|
163
|
+
for (const fullPath of absolutePaths){
|
|
164
|
+
this.transpileCache.delete(fullPath);
|
|
165
|
+
this.transpileVersions.set(fullPath, (this.transpileVersions.get(fullPath) ?? 0) + 1);
|
|
166
|
+
this.transpiler.invalidateContentCache(new _io.FileEx({
|
|
167
|
+
fullPath
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
dispose() {
|
|
172
|
+
this.disposed = true;
|
|
173
|
+
this.transpileCache = new Map();
|
|
174
|
+
this.reachedFrom = new Map();
|
|
175
|
+
this.swcConfigs.clear();
|
|
176
|
+
this.fileInfoMap.clear();
|
|
177
|
+
this.workspace?.[Symbol.dispose]();
|
|
178
|
+
this.workspace = undefined;
|
|
179
|
+
}
|
|
180
|
+
async buildFileInfoMap(buildConfig) {
|
|
181
|
+
const projects = this.workspace?.getProjects(buildConfig) ?? [];
|
|
182
|
+
const entries = await Promise.all(projects.map(async (project)=>{
|
|
183
|
+
const files = await project.getAllFiles();
|
|
184
|
+
return files.map((file)=>[
|
|
185
|
+
file.fullPath,
|
|
186
|
+
{
|
|
187
|
+
project
|
|
188
|
+
}
|
|
189
|
+
]);
|
|
190
|
+
}));
|
|
191
|
+
return new Map(entries.flat());
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Compiles the entry and the source files it imports, before Node imports it (#661). The load
|
|
195
|
+
* hook is synchronous, so it would compile one file at a time on the main thread; async SWC
|
|
196
|
+
* runs on the thread pool. Walks out from the entry, compiling each file's imports in parallel,
|
|
197
|
+
* so files the app never imports (tests, other apps) are not compiled. A reboot walks out only
|
|
198
|
+
* from the files it reached before that are no longer cached (the saved ones), so their new
|
|
199
|
+
* imports are found without visiting the rest of the graph again.
|
|
200
|
+
*
|
|
201
|
+
* A file that fails here is left out of the cache instead of failing the boot: the load hook
|
|
202
|
+
* then compiles it again and throws as before, naming the file. The walk is a hint; whatever
|
|
203
|
+
* it misses is compiled by the load hook when Node gets to it.
|
|
204
|
+
*/ async compileAhead(entryPath) {
|
|
205
|
+
const reached = this.reachedFrom.get(entryPath);
|
|
206
|
+
const starts = reached == null ? [
|
|
207
|
+
entryPath
|
|
208
|
+
] : [
|
|
209
|
+
...reached
|
|
210
|
+
].filter((fullPath)=>!this.transpileCache.has(fullPath));
|
|
211
|
+
const seen = new Set(reached);
|
|
212
|
+
for (const start of starts)seen.delete(start);
|
|
213
|
+
const visit = async (fullPath)=>{
|
|
214
|
+
if (seen.has(fullPath)) return;
|
|
215
|
+
seen.add(fullPath);
|
|
216
|
+
const code = await this.compiledAhead(fullPath);
|
|
217
|
+
if (code == null) return;
|
|
218
|
+
const parentUrl = (0, _nodeurl.pathToFileURL)(fullPath).toString();
|
|
219
|
+
await Promise.all(_staticimports.staticImports.scan(code).map((specifier)=>{
|
|
220
|
+
const target = this.sourcePathOf(specifier, parentUrl);
|
|
221
|
+
return target == null ? undefined : visit(target);
|
|
222
|
+
}));
|
|
223
|
+
};
|
|
224
|
+
await Promise.all(starts.map(visit));
|
|
225
|
+
this.reachedFrom.set(entryPath, seen);
|
|
226
|
+
}
|
|
227
|
+
/** The file's compiled code, from the cache or compiled now; undefined if it isn't a source. */ compiledAhead(fullPath) {
|
|
228
|
+
const fileInfo = this.fileInfoMap.get(fullPath);
|
|
229
|
+
if (fileInfo == null) return Promise.resolve(undefined);
|
|
230
|
+
const cached = this.transpileCache.get(fullPath);
|
|
231
|
+
if (cached != null) return Promise.resolve(cached);
|
|
232
|
+
const inFlight = this.compilingAhead.get(fullPath);
|
|
233
|
+
if (inFlight != null) return inFlight;
|
|
234
|
+
const version = this.transpileVersions.get(fullPath);
|
|
235
|
+
const cache = this.transpileCache;
|
|
236
|
+
const compiling = this.transpiler.transformFileAsync(new _io.FileEx({
|
|
237
|
+
fullPath
|
|
238
|
+
}), _forge.BuildTargets.es6, {}, this.swcConfigFor(fileInfo.project)).then((response)=>{
|
|
239
|
+
if (response.outcome !== "success") return undefined;
|
|
240
|
+
// Saved, reconfigured or disposed while compiling: don't cache stale code.
|
|
241
|
+
const current = !this.disposed && cache === this.transpileCache && version === this.transpileVersions.get(fullPath);
|
|
242
|
+
if (current) cache.set(fullPath, response.code);
|
|
243
|
+
return response.code;
|
|
244
|
+
}).finally(()=>this.compilingAhead.delete(fullPath));
|
|
245
|
+
this.compilingAhead.set(fullPath, compiling);
|
|
246
|
+
return compiling;
|
|
247
|
+
}
|
|
248
|
+
/** The source file a specifier names, mapped the way the resolve hook maps it. */ sourcePathOf(specifier, parentUrl) {
|
|
249
|
+
const url = (0, _rewriteservespecifier.rewriteServeSpecifier)({
|
|
250
|
+
specifier,
|
|
251
|
+
parentUrl,
|
|
252
|
+
isWatchedTs: (filePath)=>this.fileInfoMap.has(filePath),
|
|
253
|
+
resolveBare: (bare)=>_mesh.packageAliases.resolvePath(bare, this.aliasPaths, this.aliasBaseUrl),
|
|
254
|
+
versionFileUrl: (fileUrl)=>fileUrl
|
|
255
|
+
});
|
|
256
|
+
return url == null ? null : (0, _nodeurl.fileURLToPath)(url);
|
|
257
|
+
}
|
|
258
|
+
loadTs(url) {
|
|
259
|
+
if (!url.startsWith("file:")) return null;
|
|
260
|
+
// fileURLToPath, not URL.pathname: decodes %20 and gives Windows paths, so files
|
|
261
|
+
// whose path has a space still match Mesh's list (#511).
|
|
262
|
+
const cleanPath = (0, _nodeurl.fileURLToPath)(url);
|
|
263
|
+
const fileInfo = this.fileInfoMap.get(cleanPath);
|
|
264
|
+
if (fileInfo == null) return null;
|
|
265
|
+
const cached = this.transpileCache.get(cleanPath);
|
|
266
|
+
if (cached != null) return {
|
|
267
|
+
source: cached
|
|
268
|
+
};
|
|
269
|
+
const file = new _io.FileEx({
|
|
270
|
+
fullPath: cleanPath
|
|
271
|
+
});
|
|
272
|
+
const response = this.transpiler.transformFileSync(file, _forge.BuildTargets.es6, {}, this.swcConfigFor(fileInfo.project));
|
|
273
|
+
if (response.outcome !== "success") {
|
|
274
|
+
this.logger.error(`Failed to transpile ${cleanPath}`, response);
|
|
275
|
+
throw new Error(`Failed to transpile ${cleanPath}`);
|
|
276
|
+
}
|
|
277
|
+
this.transpileCache.set(cleanPath, response.code);
|
|
278
|
+
return {
|
|
279
|
+
source: response.code
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* The project's SWC options, built once: rebuilding them for every file was about a quarter
|
|
284
|
+
* of load time (#513). Import specifiers are left as written, without `jsc.paths` or
|
|
285
|
+
* `resolveFully`: the resolve hook maps them to source files (aliases, `.js` to `.ts`,
|
|
286
|
+
* directory indexes), so SWC does not resolve them a second time against the disk.
|
|
287
|
+
*/ swcConfigFor(project) {
|
|
288
|
+
const cached = this.swcConfigs.get(project);
|
|
289
|
+
if (cached != null) return cached;
|
|
290
|
+
const base = this.transpiler.createBaseConfig(_forge.BuildTargets.es6, {
|
|
291
|
+
tsConfig: project.getTsConfigSync(),
|
|
292
|
+
userConfig: project.getSwcConfigSync(),
|
|
293
|
+
aliasPaths: false,
|
|
294
|
+
workspace: this.workspace
|
|
295
|
+
});
|
|
296
|
+
// `module` is the ES module config (`type: "es6"` above); the spread loses the narrowing.
|
|
297
|
+
const module = {
|
|
298
|
+
...base.module,
|
|
299
|
+
resolveFully: false
|
|
300
|
+
};
|
|
301
|
+
const config = {
|
|
302
|
+
...base,
|
|
303
|
+
module
|
|
304
|
+
};
|
|
305
|
+
this.swcConfigs.set(project, config);
|
|
306
|
+
return config;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
SourceModuleLoader = _ts_decorate([
|
|
310
|
+
(0, _decorators.injectable)()
|
|
311
|
+
], SourceModuleLoader);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "staticImports", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return staticImports;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
/**
|
|
12
|
+
* The module specifiers a compiled ES module names in `import … from`, `export … from`, bare
|
|
13
|
+
* `import "x"` and literal `import("x")`.
|
|
14
|
+
*
|
|
15
|
+
* Only a hint for compiling ahead (#661): a specifier it misses is compiled when Node loads it,
|
|
16
|
+
* and one it finds by mistake (an import written inside a string) costs one extra compile. So a
|
|
17
|
+
* scan of SWC's output is enough; it never decides what runs.
|
|
18
|
+
*/ const STATIC = /\b(?:import|export)\s*(?:[\w$*{},\s]+?\s*from\s*)?["']([^"'\n]+)["']/g;
|
|
19
|
+
const DYNAMIC = /\bimport\s*\(\s*["']([^"'\n]+)["']\s*\)/g;
|
|
20
|
+
function scan(code) {
|
|
21
|
+
const found = new Set();
|
|
22
|
+
for (const pattern of [
|
|
23
|
+
STATIC,
|
|
24
|
+
DYNAMIC
|
|
25
|
+
]){
|
|
26
|
+
for (const match of code.matchAll(pattern)){
|
|
27
|
+
const specifier = match[1];
|
|
28
|
+
if (specifier != null) found.add(specifier);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return [
|
|
32
|
+
...found
|
|
33
|
+
];
|
|
34
|
+
}
|
|
35
|
+
const staticImports = {
|
|
36
|
+
scan
|
|
37
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
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;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
}
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
8
|
+
import { injectable } from "@decohere/crucible/decorators";
|
|
9
|
+
import { detectPackageManager, packageManagerFromString } from "@decohere/nexus/interop";
|
|
10
|
+
import { scribe } from "@decohere/scribe";
|
|
11
|
+
export class DeferredScriptRunner {
|
|
12
|
+
logger = scribe.logger(DeferredScriptRunner.name);
|
|
13
|
+
children = [];
|
|
14
|
+
start(options) {
|
|
15
|
+
const pm = detectPackageManager(options.workspaceRoot) ?? packageManagerFromString("pnpm");
|
|
16
|
+
this.logger.info(`deferred ${options.label} — running ${pm.name} run ${options.script} in ${options.cwd}`);
|
|
17
|
+
const child = spawn(pm.name, [
|
|
18
|
+
"run",
|
|
19
|
+
options.script
|
|
20
|
+
], {
|
|
21
|
+
cwd: options.cwd,
|
|
22
|
+
stdio: "inherit",
|
|
23
|
+
env: process.env,
|
|
24
|
+
shell: false
|
|
25
|
+
});
|
|
26
|
+
child.on("exit", (code, signal)=>{
|
|
27
|
+
if (signal) return;
|
|
28
|
+
if (code != null && code !== 0) {
|
|
29
|
+
this.logger.warn(`deferred ${options.label} exited with ${code}`);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
this.children.push(child);
|
|
33
|
+
return child;
|
|
34
|
+
}
|
|
35
|
+
async [Symbol.asyncDispose]() {
|
|
36
|
+
for (const child of this.children){
|
|
37
|
+
if (!child.killed) {
|
|
38
|
+
child.kill("SIGTERM");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
this.children.length = 0;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
DeferredScriptRunner = _ts_decorate([
|
|
45
|
+
injectable()
|
|
46
|
+
], DeferredScriptRunner);
|
package/es6/src/flux.command.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DecohereCommand } from "@decohere/nexus/cli";
|
|
2
|
+
import { RunCommand } from "./run.command.js";
|
|
2
3
|
import { ServeCommand } from "./serve.command.js";
|
|
3
4
|
export class FluxCommand extends DecohereCommand {
|
|
4
5
|
constructor(commands, handler){
|
|
@@ -6,6 +7,7 @@ export class FluxCommand extends DecohereCommand {
|
|
|
6
7
|
this.exitOverride();
|
|
7
8
|
this.registerSubcommand({
|
|
8
9
|
serve: ServeCommand,
|
|
10
|
+
run: RunCommand,
|
|
9
11
|
...commands
|
|
10
12
|
}, handler);
|
|
11
13
|
}
|
|
@@ -13,5 +15,3 @@ export class FluxCommand extends DecohereCommand {
|
|
|
13
15
|
return this.help();
|
|
14
16
|
}
|
|
15
17
|
}
|
|
16
|
-
|
|
17
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi9ob21lL3J1bm5lci93b3JrL2RlY29oZXJlL2RlY29oZXJlL3Byb2plY3RzL2ZsdXgvc3JjL2ZsdXguY29tbWFuZC50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgdHlwZSB7IEFjdGlvbnMgfSBmcm9tIFwiQGRlY29oZXJlL25leHVzL2NsaVwiO1xuaW1wb3J0IHtcblx0dHlwZSBDb21tYW5kSGFuZGxlcixcblx0dHlwZSBDb21tYW5kUmVzcG9uc2UsXG5cdHR5cGUgQ29tbWFuZFR5cGUsXG5cdERlY29oZXJlQ29tbWFuZCxcbn0gZnJvbSBcIkBkZWNvaGVyZS9uZXh1cy9jbGlcIjtcbmltcG9ydCB7IFNlcnZlQ29tbWFuZCB9IGZyb20gXCIuL3NlcnZlLmNvbW1hbmQuanNcIjtcblxuZXhwb3J0IGNsYXNzIEZsdXhDb21tYW5kIGV4dGVuZHMgRGVjb2hlcmVDb21tYW5kIHtcblx0cHVibGljIGNvbnN0cnVjdG9yKGNvbW1hbmRzOiBSZWNvcmQ8c3RyaW5nLCBDb21tYW5kVHlwZT4sIGhhbmRsZXI6IENvbW1hbmRIYW5kbGVyKSB7XG5cdFx0c3VwZXIoRmx1eENvbW1hbmQpO1xuXHRcdHRoaXMuZXhpdE92ZXJyaWRlKCk7XG5cdFx0dGhpcy5yZWdpc3RlclN1YmNvbW1hbmQoeyBzZXJ2ZTogU2VydmVDb21tYW5kLCAuLi5jb21tYW5kcyB9LCBoYW5kbGVyKTtcblx0fVxuXG5cdHB1YmxpYyBydW4oX2FjdGlvbnM6IEFjdGlvbnMsIF9vcHRpb25zOiBuZXZlcik6IFByb21pc2U8Q29tbWFuZFJlc3BvbnNlPiB7XG5cdFx0cmV0dXJuIHRoaXMuaGVscCgpO1xuXHR9XG59XG4iXSwibmFtZXMiOlsiRGVjb2hlcmVDb21tYW5kIiwiU2VydmVDb21tYW5kIiwiRmx1eENvbW1hbmQiLCJjb21tYW5kcyIsImhhbmRsZXIiLCJleGl0T3ZlcnJpZGUiLCJyZWdpc3RlclN1YmNvbW1hbmQiLCJzZXJ2ZSIsInJ1biIsIl9hY3Rpb25zIiwiX29wdGlvbnMiLCJoZWxwIl0sIm1hcHBpbmdzIjoiQUFDQSxTQUlDQSxlQUFlLFFBQ1Qsc0JBQXNCO0FBQzdCLFNBQVNDLFlBQVksUUFBUSxxQkFBcUI7QUFFbEQsT0FBTyxNQUFNQyxvQkFBb0JGO0lBQ2hDLFlBQW1CRyxRQUFxQyxFQUFFQyxPQUF1QixDQUFFO1FBQ2xGLEtBQUssQ0FBQ0Y7UUFDTixJQUFJLENBQUNHLFlBQVk7UUFDakIsSUFBSSxDQUFDQyxrQkFBa0IsQ0FBQztZQUFFQyxPQUFPTjtZQUFjLEdBQUdFLFFBQVE7UUFBQyxHQUFHQztJQUMvRDtJQUVPSSxJQUFJQyxRQUFpQixFQUFFQyxRQUFlLEVBQTRCO1FBQ3hFLE9BQU8sSUFBSSxDQUFDQyxJQUFJO0lBQ2pCO0FBQ0QifQ==
|
package/es6/src/flux.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { HotReloadServer } from "./hot-reload-server.js";
|
|
3
|
+
import { SourceModuleLoader } from "./source-module-loader.js";
|
|
4
|
+
/** Process-level one-shot loader so a second `run()` reconfigures instead of stacking hooks. */ const runLoader = new SourceModuleLoader();
|
|
5
|
+
async function run(entry, options = {}) {
|
|
6
|
+
const trimmed = entry.trim();
|
|
7
|
+
if (trimmed == null || trimmed === "") {
|
|
8
|
+
throw new Error("flux.run requires an entry file path");
|
|
9
|
+
}
|
|
10
|
+
const absoluteEntry = path.resolve(trimmed);
|
|
11
|
+
await runLoader.configure({
|
|
12
|
+
workspace: options.workspace,
|
|
13
|
+
buildConfig: options.configuration ?? "debug"
|
|
14
|
+
});
|
|
15
|
+
runLoader.registerHooks();
|
|
16
|
+
process.argv = [
|
|
17
|
+
process.execPath,
|
|
18
|
+
absoluteEntry,
|
|
19
|
+
...options.args ?? []
|
|
20
|
+
];
|
|
21
|
+
return runLoader.importEntry(absoluteEntry);
|
|
22
|
+
}
|
|
23
|
+
async function serve(options = {}) {
|
|
24
|
+
const server = new HotReloadServer();
|
|
25
|
+
await server.start({
|
|
26
|
+
buildConfig: options.configuration ?? "debug",
|
|
27
|
+
project: options.project,
|
|
28
|
+
projects: options.projects,
|
|
29
|
+
workspace: options.workspace,
|
|
30
|
+
rebootDebounceMs: options.rebootDebounceMs
|
|
31
|
+
});
|
|
32
|
+
return server;
|
|
33
|
+
}
|
|
34
|
+
function dispose() {
|
|
35
|
+
runLoader.dispose();
|
|
36
|
+
}
|
|
37
|
+
export const flux = {
|
|
38
|
+
run,
|
|
39
|
+
serve,
|
|
40
|
+
dispose
|
|
41
|
+
};
|
package/es6/src/flux.module.js
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createWorkspace } from "@decohere/forge";
|
|
2
2
|
import { ModuleManifest } from "@decohere/nexus/cli";
|
|
3
|
+
import { DeferredScriptRunner } from "./deferred-script-runner.js";
|
|
3
4
|
import { FluxCommand } from "./flux.command.js";
|
|
4
5
|
import { HotReloadServer } from "./hot-reload-server.js";
|
|
6
|
+
import { RunAction } from "./run.action.js";
|
|
5
7
|
import { ServeAction } from "./serve.action.js";
|
|
6
8
|
export class FluxModule extends ModuleManifest {
|
|
7
9
|
id = "flux";
|
|
8
10
|
command = FluxCommand;
|
|
9
11
|
actions = {
|
|
10
|
-
serve: ServeAction
|
|
12
|
+
serve: ServeAction,
|
|
13
|
+
run: RunAction
|
|
11
14
|
};
|
|
12
15
|
registerServices(builder) {
|
|
13
16
|
builder.registerSingleton(HotReloadServer);
|
|
17
|
+
builder.registerSingleton(DeferredScriptRunner);
|
|
14
18
|
}
|
|
15
19
|
createWorkspace(options) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
});
|
|
20
|
+
// Same workspace as `flux.run` / `flux.serve` and forge (#516).
|
|
21
|
+
return createWorkspace(options);
|
|
19
22
|
}
|
|
20
23
|
}
|
|
21
|
-
|
|
22
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi9ob21lL3J1bm5lci93b3JrL2RlY29oZXJlL2RlY29oZXJlL3Byb2plY3RzL2ZsdXgvc3JjL2ZsdXgubW9kdWxlLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IFdvcmtzcGFjZUZhY3RvcnkgfSBmcm9tIFwiQGRlY29oZXJlL21lc2hcIjtcbmltcG9ydCB0eXBlIHsgV29ya3NwYWNlRmFjdG9yeU9wdGlvbnMgfSBmcm9tIFwiQGRlY29oZXJlL25leHVzL2NsaVwiO1xuaW1wb3J0IHsgTW9kdWxlTWFuaWZlc3QgfSBmcm9tIFwiQGRlY29oZXJlL25leHVzL2NsaVwiO1xuaW1wb3J0IHR5cGUgeyBDb250YWluZXJCdWlsZGVyIH0gZnJvbSBcIkBkZWNvaGVyZS9uZXh1cy9jb21wb3NpdGlvblwiO1xuaW1wb3J0IHsgRmx1eENvbW1hbmQgfSBmcm9tIFwiLi9mbHV4LmNvbW1hbmQuanNcIjtcbmltcG9ydCB7IEhvdFJlbG9hZFNlcnZlciB9IGZyb20gXCIuL2hvdC1yZWxvYWQtc2VydmVyLmpzXCI7XG5pbXBvcnQgeyBTZXJ2ZUFjdGlvbiB9IGZyb20gXCIuL3NlcnZlLmFjdGlvbi5qc1wiO1xuXG5leHBvcnQgY2xhc3MgRmx1eE1vZHVsZSBleHRlbmRzIE1vZHVsZU1hbmlmZXN0IHtcblx0cHVibGljIHJlYWRvbmx5IGlkID0gXCJmbHV4XCI7XG5cdHB1YmxpYyByZWFkb25seSBjb21tYW5kID0gRmx1eENvbW1hbmQ7XG5cdHB1YmxpYyByZWFkb25seSBhY3Rpb25zID0ge1xuXHRcdHNlcnZlOiBTZXJ2ZUFjdGlvbixcblx0fTtcblxuXHRwdWJsaWMgcmVnaXN0ZXJTZXJ2aWNlcyhidWlsZGVyOiBDb250YWluZXJCdWlsZGVyKSB7XG5cdFx0YnVpbGRlci5yZWdpc3RlclNpbmdsZXRvbihIb3RSZWxvYWRTZXJ2ZXIpO1xuXHR9XG5cblx0cHVibGljIG92ZXJyaWRlIGNyZWF0ZVdvcmtzcGFjZShvcHRpb25zOiBXb3Jrc3BhY2VGYWN0b3J5T3B0aW9ucykge1xuXHRcdHJldHVybiBXb3Jrc3BhY2VGYWN0b3J5LmNyZWF0ZSh7IGNvbmZpZ0ZpbGU6IG9wdGlvbnMuY29uZmlnRmlsZSB9KTtcblx0fVxufVxuIl0sIm5hbWVzIjpbIldvcmtzcGFjZUZhY3RvcnkiLCJNb2R1bGVNYW5pZmVzdCIsIkZsdXhDb21tYW5kIiwiSG90UmVsb2FkU2VydmVyIiwiU2VydmVBY3Rpb24iLCJGbHV4TW9kdWxlIiwiaWQiLCJjb21tYW5kIiwiYWN0aW9ucyIsInNlcnZlIiwicmVnaXN0ZXJTZXJ2aWNlcyIsImJ1aWxkZXIiLCJyZWdpc3RlclNpbmdsZXRvbiIsImNyZWF0ZVdvcmtzcGFjZSIsIm9wdGlvbnMiLCJjcmVhdGUiLCJjb25maWdGaWxlIl0sIm1hcHBpbmdzIjoiQUFBQSxTQUFTQSxnQkFBZ0IsUUFBUSxpQkFBaUI7QUFFbEQsU0FBU0MsY0FBYyxRQUFRLHNCQUFzQjtBQUVyRCxTQUFTQyxXQUFXLFFBQVEsb0JBQW9CO0FBQ2hELFNBQVNDLGVBQWUsUUFBUSx5QkFBeUI7QUFDekQsU0FBU0MsV0FBVyxRQUFRLG9CQUFvQjtBQUVoRCxPQUFPLE1BQU1DLG1CQUFtQko7SUFDZkssS0FBSyxPQUFPO0lBQ1pDLFVBQVVMLFlBQVk7SUFDdEJNLFVBQVU7UUFDekJDLE9BQU9MO0lBQ1IsRUFBRTtJQUVLTSxpQkFBaUJDLE9BQXlCLEVBQUU7UUFDbERBLFFBQVFDLGlCQUFpQixDQUFDVDtJQUMzQjtJQUVnQlUsZ0JBQWdCQyxPQUFnQyxFQUFFO1FBQ2pFLE9BQU9kLGlCQUFpQmUsTUFBTSxDQUFDO1lBQUVDLFlBQVlGLFFBQVFFLFVBQVU7UUFBQztJQUNqRTtBQUNEIn0=
|