@jsenv/core 31.1.4 → 32.0.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/dist/js/v8_coverage.js +36 -23
- package/dist/main.js +493 -546
- package/package.json +5 -5
- package/src/basic_fetch.js +42 -0
- package/src/build/build.js +81 -93
- package/src/build/start_build_server.js +32 -112
- package/src/dev/file_service.js +49 -66
- package/src/dev/start_dev_server.js +43 -94
- package/src/execute/execute.js +4 -1
- package/src/execute/runtimes/node/node_child_process.js +0 -1
- package/src/jsenv_internal_directory.js +18 -0
- package/src/kitchen/kitchen.js +2 -0
- package/src/lookup_package_directory.js +34 -0
- package/src/plugins/explorer/jsenv_plugin_explorer.js +5 -4
- package/src/plugins/plugins.js +4 -16
- package/src/plugins/url_analysis/webmanifest/webmanifest_urls.js +10 -0
- package/src/plugins/url_resolution/jsenv_plugin_url_resolution.js +2 -2
- package/src/test/coverage/v8_coverage_node_directory.js +4 -2
- package/src/test/execute_plan.js +6 -67
- package/src/test/execute_test_plan.js +199 -45
- package/src/watch_source_files.js +50 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "32.0.0",
|
|
4
4
|
"description": "Tool to develop, test and build js projects",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -69,16 +69,16 @@
|
|
|
69
69
|
"@jsenv/abort": "4.2.4",
|
|
70
70
|
"@jsenv/ast": "3.0.3",
|
|
71
71
|
"@jsenv/babel-plugins": "1.1.5",
|
|
72
|
-
"@jsenv/filesystem": "4.2.
|
|
72
|
+
"@jsenv/filesystem": "4.2.1",
|
|
73
73
|
"@jsenv/importmap": "1.2.1",
|
|
74
74
|
"@jsenv/integrity": "0.0.1",
|
|
75
75
|
"@jsenv/log": "3.3.4",
|
|
76
76
|
"@jsenv/node-esm-resolution": "1.0.1",
|
|
77
|
-
"@jsenv/plugin-bundling": "2.1.
|
|
78
|
-
"@jsenv/server": "15.0.
|
|
77
|
+
"@jsenv/plugin-bundling": "2.1.1",
|
|
78
|
+
"@jsenv/server": "15.0.1",
|
|
79
79
|
"@jsenv/sourcemap": "1.0.9",
|
|
80
80
|
"@jsenv/uneval": "1.6.0",
|
|
81
|
-
"@jsenv/url-meta": "
|
|
81
|
+
"@jsenv/url-meta": "8.0.0",
|
|
82
82
|
"@jsenv/urls": "1.2.8",
|
|
83
83
|
"@jsenv/utils": "2.0.1",
|
|
84
84
|
"@paralleldrive/cuid2": "2.2.0",
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export const basicFetch = async (
|
|
2
|
+
url,
|
|
3
|
+
{ method = "GET", headers = {} } = {},
|
|
4
|
+
) => {
|
|
5
|
+
let requestModule
|
|
6
|
+
if (url.startsWith("http:")) {
|
|
7
|
+
requestModule = await import("node:http")
|
|
8
|
+
} else {
|
|
9
|
+
requestModule = await import("node:https")
|
|
10
|
+
}
|
|
11
|
+
const { request } = requestModule
|
|
12
|
+
|
|
13
|
+
const urlObject = new URL(url)
|
|
14
|
+
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
const req = request({
|
|
17
|
+
hostname: urlObject.hostname,
|
|
18
|
+
port: urlObject.port,
|
|
19
|
+
path: urlObject.pathname,
|
|
20
|
+
method,
|
|
21
|
+
headers,
|
|
22
|
+
})
|
|
23
|
+
req.on("response", (response) => {
|
|
24
|
+
req.setTimeout(0)
|
|
25
|
+
let responseBody = ""
|
|
26
|
+
response.setEncoding("utf8")
|
|
27
|
+
response.on("data", (chunk) => {
|
|
28
|
+
responseBody += chunk
|
|
29
|
+
})
|
|
30
|
+
response.on("end", () => {
|
|
31
|
+
req.destroy()
|
|
32
|
+
if (response.headers["content-type"] === "application/json") {
|
|
33
|
+
resolve(JSON.parse(responseBody))
|
|
34
|
+
} else {
|
|
35
|
+
resolve(responseBody)
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
req.on("error", reject)
|
|
40
|
+
req.end()
|
|
41
|
+
})
|
|
42
|
+
}
|
package/src/build/build.js
CHANGED
|
@@ -28,10 +28,9 @@ import {
|
|
|
28
28
|
urlToRelativeUrl,
|
|
29
29
|
} from "@jsenv/urls"
|
|
30
30
|
import {
|
|
31
|
-
|
|
31
|
+
assertAndNormalizeDirectoryUrl,
|
|
32
32
|
ensureEmptyDirectory,
|
|
33
33
|
writeFileSync,
|
|
34
|
-
registerDirectoryLifecycle,
|
|
35
34
|
comparePathnames,
|
|
36
35
|
} from "@jsenv/filesystem"
|
|
37
36
|
import { Abort, raceProcessTeardownEvents } from "@jsenv/abort"
|
|
@@ -54,6 +53,8 @@ import {
|
|
|
54
53
|
findHtmlNode,
|
|
55
54
|
} from "@jsenv/ast"
|
|
56
55
|
|
|
56
|
+
import { determineJsenvInternalDirectoryUrl } from "../jsenv_internal_directory.js"
|
|
57
|
+
import { watchSourceFiles } from "../watch_source_files.js"
|
|
57
58
|
import { createUrlGraph } from "../kitchen/url_graph.js"
|
|
58
59
|
import { createKitchen } from "../kitchen/kitchen.js"
|
|
59
60
|
import { RUNTIME_COMPAT } from "../kitchen/compat/runtime_compat.js"
|
|
@@ -94,12 +95,13 @@ export const defaultRuntimeCompat = {
|
|
|
94
95
|
/**
|
|
95
96
|
* Generate an optimized version of source files into a directory
|
|
96
97
|
* @param {Object} buildParameters
|
|
97
|
-
* @param {string|url} buildParameters.
|
|
98
|
+
* @param {string|url} buildParameters.sourceDirectoryUrl
|
|
98
99
|
* Directory containing source files
|
|
100
|
+
* @param {object} buildParameters.entryPoints
|
|
101
|
+
* Object where keys are paths to source files and values are their future name in the build directory.
|
|
102
|
+
* Keys are relative to sourceDirectoryUrl
|
|
99
103
|
* @param {string|url} buildParameters.buildDirectoryUrl
|
|
100
104
|
* Directory where optimized files will be written
|
|
101
|
-
* @param {object} buildParameters.entryPoints
|
|
102
|
-
* Describe entry point paths and control their names in the build directory
|
|
103
105
|
* @param {object} buildParameters.runtimeCompat
|
|
104
106
|
* Code generated will be compatible with these runtimes
|
|
105
107
|
* @param {string} [buildParameters.assetsDirectory=""]
|
|
@@ -124,10 +126,10 @@ export const build = async ({
|
|
|
124
126
|
signal = new AbortController().signal,
|
|
125
127
|
handleSIGINT = true,
|
|
126
128
|
logLevel = "info",
|
|
127
|
-
|
|
129
|
+
sourceDirectoryUrl,
|
|
130
|
+
entryPoints = {},
|
|
128
131
|
buildDirectoryUrl,
|
|
129
132
|
assetsDirectory = "",
|
|
130
|
-
entryPoints = {},
|
|
131
133
|
|
|
132
134
|
runtimeCompat = defaultRuntimeCompat,
|
|
133
135
|
base = runtimeCompat.node ? "./" : "/",
|
|
@@ -145,9 +147,7 @@ export const build = async ({
|
|
|
145
147
|
versioningViaImportmap = true,
|
|
146
148
|
lineBreakNormalization = process.platform === "win32",
|
|
147
149
|
|
|
148
|
-
|
|
149
|
-
"./src/": true,
|
|
150
|
-
},
|
|
150
|
+
sourceFilesConfig = {},
|
|
151
151
|
cooldownBetweenFileEvents,
|
|
152
152
|
watch = false,
|
|
153
153
|
|
|
@@ -166,20 +166,42 @@ export const build = async ({
|
|
|
166
166
|
`${unexpectedParamNames.join(",")}: there is no such param`,
|
|
167
167
|
)
|
|
168
168
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
169
|
+
sourceDirectoryUrl = assertAndNormalizeDirectoryUrl(
|
|
170
|
+
sourceDirectoryUrl,
|
|
171
|
+
"sourceDirectoryUrl",
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
if (typeof entryPoints !== "object" || entryPoints === null) {
|
|
175
|
+
throw new TypeError(`entryPoints must be an object, got ${entryPoints}`)
|
|
174
176
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
177
|
+
const keys = Object.keys(entryPoints)
|
|
178
|
+
keys.forEach((key) => {
|
|
179
|
+
if (!key.startsWith("./")) {
|
|
180
|
+
throw new TypeError(
|
|
181
|
+
`entryPoints keys must start with "./", found ${key}`,
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
const value = entryPoints[key]
|
|
185
|
+
if (typeof value !== "string") {
|
|
186
|
+
throw new TypeError(
|
|
187
|
+
`entryPoints values must be strings, found "${value}" on key "${key}"`,
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
if (value.includes("/")) {
|
|
191
|
+
throw new TypeError(
|
|
192
|
+
`entryPoints values must be plain strings (no "/"), found "${value}" on key "${key}"`,
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
})
|
|
196
|
+
buildDirectoryUrl = assertAndNormalizeDirectoryUrl(
|
|
197
|
+
buildDirectoryUrl,
|
|
198
|
+
"buildDirectoryUrl",
|
|
199
|
+
)
|
|
200
|
+
if (!["filename", "search_param"].includes(versioningMethod)) {
|
|
178
201
|
throw new TypeError(
|
|
179
|
-
`
|
|
202
|
+
`versioningMethod must be "filename" or "search_param", got ${versioning}`,
|
|
180
203
|
)
|
|
181
204
|
}
|
|
182
|
-
buildDirectoryUrl = buildDirectoryUrlValidation.value
|
|
183
205
|
}
|
|
184
206
|
|
|
185
207
|
const operation = Abort.startOperation()
|
|
@@ -195,12 +217,6 @@ export const build = async ({
|
|
|
195
217
|
})
|
|
196
218
|
}
|
|
197
219
|
|
|
198
|
-
assertEntryPoints({ entryPoints })
|
|
199
|
-
if (!["filename", "search_param"].includes(versioningMethod)) {
|
|
200
|
-
throw new Error(
|
|
201
|
-
`Unexpected "versioningMethod": must be "filename", "search_param"; got ${versioning}`,
|
|
202
|
-
)
|
|
203
|
-
}
|
|
204
220
|
if (assetsDirectory && assetsDirectory[assetsDirectory.length - 1] !== "/") {
|
|
205
221
|
assetsDirectory = `${assetsDirectory}/`
|
|
206
222
|
}
|
|
@@ -211,11 +227,14 @@ export const build = async ({
|
|
|
211
227
|
directoryToClean = new URL(assetsDirectory, buildDirectoryUrl).href
|
|
212
228
|
}
|
|
213
229
|
}
|
|
230
|
+
const jsenvInternalDirectoryUrl =
|
|
231
|
+
determineJsenvInternalDirectoryUrl(sourceDirectoryUrl)
|
|
232
|
+
|
|
214
233
|
const asFormattedBuildUrl = (generatedUrl, reference) => {
|
|
215
234
|
if (base === "./") {
|
|
216
235
|
const urlRelativeToParent = urlToRelativeUrl(
|
|
217
236
|
generatedUrl,
|
|
218
|
-
reference.parentUrl ===
|
|
237
|
+
reference.parentUrl === sourceDirectoryUrl
|
|
219
238
|
? buildDirectoryUrl
|
|
220
239
|
: reference.parentUrl,
|
|
221
240
|
)
|
|
@@ -279,7 +298,8 @@ build ${entryPointKeys.length} entry points`)
|
|
|
279
298
|
const rawGraphKitchen = createKitchen({
|
|
280
299
|
signal,
|
|
281
300
|
logLevel,
|
|
282
|
-
rootDirectoryUrl,
|
|
301
|
+
rootDirectoryUrl: sourceDirectoryUrl,
|
|
302
|
+
jsenvInternalDirectoryUrl,
|
|
283
303
|
urlGraph: rawGraph,
|
|
284
304
|
build: true,
|
|
285
305
|
runtimeCompat,
|
|
@@ -304,7 +324,7 @@ build ${entryPointKeys.length} entry points`)
|
|
|
304
324
|
},
|
|
305
325
|
},
|
|
306
326
|
...getCorePlugins({
|
|
307
|
-
rootDirectoryUrl,
|
|
327
|
+
rootDirectoryUrl: sourceDirectoryUrl,
|
|
308
328
|
urlGraph: rawGraph,
|
|
309
329
|
runtimeCompat,
|
|
310
330
|
|
|
@@ -323,7 +343,7 @@ build ${entryPointKeys.length} entry points`)
|
|
|
323
343
|
sourcemaps,
|
|
324
344
|
sourcemapsSourcesContent,
|
|
325
345
|
writeGeneratedFiles,
|
|
326
|
-
outDirectoryUrl: new URL(
|
|
346
|
+
outDirectoryUrl: new URL("build/", jsenvInternalDirectoryUrl),
|
|
327
347
|
})
|
|
328
348
|
|
|
329
349
|
const buildUrlsGenerator = createBuildUrlsGenerator({
|
|
@@ -347,12 +367,13 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
|
|
|
347
367
|
const bundlers = {}
|
|
348
368
|
const finalGraph = createUrlGraph()
|
|
349
369
|
const urlAnalysisPlugin = jsenvPluginUrlAnalysis({
|
|
350
|
-
rootDirectoryUrl,
|
|
370
|
+
rootDirectoryUrl: sourceDirectoryUrl,
|
|
351
371
|
...urlAnalysis,
|
|
352
372
|
})
|
|
353
373
|
const finalGraphKitchen = createKitchen({
|
|
354
374
|
logLevel,
|
|
355
375
|
rootDirectoryUrl: buildDirectoryUrl,
|
|
376
|
+
jsenvInternalDirectoryUrl,
|
|
356
377
|
urlGraph: finalGraph,
|
|
357
378
|
build: true,
|
|
358
379
|
runtimeCompat,
|
|
@@ -643,7 +664,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
|
|
|
643
664
|
sourcemapsSourcesContent,
|
|
644
665
|
sourcemapsSourcesRelative: !versioning,
|
|
645
666
|
writeGeneratedFiles,
|
|
646
|
-
outDirectoryUrl: new URL("
|
|
667
|
+
outDirectoryUrl: new URL("postbuild/", jsenvInternalDirectoryUrl),
|
|
647
668
|
})
|
|
648
669
|
const finalEntryUrls = []
|
|
649
670
|
|
|
@@ -653,7 +674,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
|
|
|
653
674
|
})
|
|
654
675
|
try {
|
|
655
676
|
if (writeGeneratedFiles) {
|
|
656
|
-
await ensureEmptyDirectory(new URL(
|
|
677
|
+
await ensureEmptyDirectory(new URL(`build/`, sourceDirectoryUrl))
|
|
657
678
|
}
|
|
658
679
|
const rawUrlGraphLoader = createUrlGraphLoader(
|
|
659
680
|
rawGraphKitchen.kitchenContext,
|
|
@@ -662,7 +683,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
|
|
|
662
683
|
const [entryReference, entryUrlInfo] =
|
|
663
684
|
rawGraphKitchen.kitchenContext.prepareEntryPoint({
|
|
664
685
|
trace: { message: `"${key}" in entryPoints parameter` },
|
|
665
|
-
parentUrl:
|
|
686
|
+
parentUrl: sourceDirectoryUrl,
|
|
666
687
|
type: "entry_point",
|
|
667
688
|
specifier: key,
|
|
668
689
|
})
|
|
@@ -899,7 +920,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
|
|
|
899
920
|
try {
|
|
900
921
|
if (writeGeneratedFiles) {
|
|
901
922
|
await ensureEmptyDirectory(
|
|
902
|
-
new URL(
|
|
923
|
+
new URL(`postbuild/`, jsenvInternalDirectoryUrl),
|
|
903
924
|
)
|
|
904
925
|
}
|
|
905
926
|
const finalUrlGraphLoader = createUrlGraphLoader(
|
|
@@ -909,7 +930,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
|
|
|
909
930
|
const [finalEntryReference, finalEntryUrlInfo] =
|
|
910
931
|
finalGraphKitchen.kitchenContext.prepareEntryPoint({
|
|
911
932
|
trace: { message: `entryPoint` },
|
|
912
|
-
parentUrl:
|
|
933
|
+
parentUrl: sourceDirectoryUrl,
|
|
913
934
|
type: "entry_point",
|
|
914
935
|
specifier: entryUrl,
|
|
915
936
|
})
|
|
@@ -1128,6 +1149,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
|
|
|
1128
1149
|
const versioningKitchen = createKitchen({
|
|
1129
1150
|
logLevel: logger.level,
|
|
1130
1151
|
rootDirectoryUrl: buildDirectoryUrl,
|
|
1152
|
+
jsenvInternalDirectoryUrl,
|
|
1131
1153
|
urlGraph: finalGraph,
|
|
1132
1154
|
build: true,
|
|
1133
1155
|
runtimeCompat,
|
|
@@ -1244,10 +1266,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
|
|
|
1244
1266
|
sourcemapsSourcesContent,
|
|
1245
1267
|
sourcemapsSourcesRelative: true,
|
|
1246
1268
|
writeGeneratedFiles,
|
|
1247
|
-
outDirectoryUrl: new URL(
|
|
1248
|
-
".jsenv/postbuild/",
|
|
1249
|
-
finalGraphKitchen.rootDirectoryUrl,
|
|
1250
|
-
),
|
|
1269
|
+
outDirectoryUrl: new URL("postbuild/", jsenvInternalDirectoryUrl),
|
|
1251
1270
|
})
|
|
1252
1271
|
const versioningUrlGraphLoader = createUrlGraphLoader(
|
|
1253
1272
|
versioningKitchen.kitchenContext,
|
|
@@ -1686,39 +1705,33 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
|
|
|
1686
1705
|
|
|
1687
1706
|
startBuild()
|
|
1688
1707
|
let startTimeout
|
|
1689
|
-
const
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
watchFilesTask
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
cooldownBetweenFileEvents,
|
|
1705
|
-
keepProcessAlive: true,
|
|
1706
|
-
recursive: true,
|
|
1707
|
-
added: ({ relativeUrl }) => {
|
|
1708
|
-
clientFileChangeCallback({ relativeUrl, event: "added" })
|
|
1709
|
-
},
|
|
1710
|
-
updated: ({ relativeUrl }) => {
|
|
1711
|
-
clientFileChangeCallback({ relativeUrl, event: "modified" })
|
|
1708
|
+
const stopWatchingSourceFiles = watchSourceFiles(
|
|
1709
|
+
sourceDirectoryUrl,
|
|
1710
|
+
({ url, event }) => {
|
|
1711
|
+
if (watchFilesTask) {
|
|
1712
|
+
watchFilesTask.happen(
|
|
1713
|
+
`${url.slice(sourceDirectoryUrl.length)} ${event}`,
|
|
1714
|
+
)
|
|
1715
|
+
watchFilesTask = null
|
|
1716
|
+
}
|
|
1717
|
+
buildAbortController.abort()
|
|
1718
|
+
// setTimeout is to ensure the abortController.abort() above
|
|
1719
|
+
// is properly taken into account so that logs about abort comes first
|
|
1720
|
+
// then logs about re-running the build happens
|
|
1721
|
+
clearTimeout(startTimeout)
|
|
1722
|
+
startTimeout = setTimeout(startBuild, 20)
|
|
1712
1723
|
},
|
|
1713
|
-
|
|
1714
|
-
|
|
1724
|
+
{
|
|
1725
|
+
sourceFilesConfig,
|
|
1726
|
+
keepProcessAlive: true,
|
|
1727
|
+
cooldownBetweenFileEvents,
|
|
1715
1728
|
},
|
|
1716
|
-
|
|
1729
|
+
)
|
|
1717
1730
|
operation.addAbortCallback(() => {
|
|
1718
|
-
|
|
1731
|
+
stopWatchingSourceFiles()
|
|
1719
1732
|
})
|
|
1720
1733
|
await firstBuildPromise
|
|
1721
|
-
return
|
|
1734
|
+
return stopWatchingSourceFiles
|
|
1722
1735
|
}
|
|
1723
1736
|
|
|
1724
1737
|
const findKey = (map, value) => {
|
|
@@ -1743,31 +1756,6 @@ const injectVersionIntoBuildUrl = ({ buildUrl, version, versioningMethod }) => {
|
|
|
1743
1756
|
return versionedUrl
|
|
1744
1757
|
}
|
|
1745
1758
|
|
|
1746
|
-
const assertEntryPoints = ({ entryPoints }) => {
|
|
1747
|
-
if (typeof entryPoints !== "object" || entryPoints === null) {
|
|
1748
|
-
throw new TypeError(`entryPoints must be an object, got ${entryPoints}`)
|
|
1749
|
-
}
|
|
1750
|
-
const keys = Object.keys(entryPoints)
|
|
1751
|
-
keys.forEach((key) => {
|
|
1752
|
-
if (!key.startsWith("./")) {
|
|
1753
|
-
throw new TypeError(
|
|
1754
|
-
`unexpected key in entryPoints, all keys must start with ./ but found ${key}`,
|
|
1755
|
-
)
|
|
1756
|
-
}
|
|
1757
|
-
const value = entryPoints[key]
|
|
1758
|
-
if (typeof value !== "string") {
|
|
1759
|
-
throw new TypeError(
|
|
1760
|
-
`unexpected value in entryPoints, all values must be strings found ${value} for key ${key}`,
|
|
1761
|
-
)
|
|
1762
|
-
}
|
|
1763
|
-
if (value.includes("/")) {
|
|
1764
|
-
throw new TypeError(
|
|
1765
|
-
`unexpected value in entryPoints, all values must be plain strings (no "/") but found ${value} for key ${key}`,
|
|
1766
|
-
)
|
|
1767
|
-
}
|
|
1768
|
-
})
|
|
1769
|
-
}
|
|
1770
|
-
|
|
1771
1759
|
const isUsed = (urlInfo) => {
|
|
1772
1760
|
// nothing uses this url anymore
|
|
1773
1761
|
// - versioning update inline content
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { existsSync } from "node:fs"
|
|
17
|
-
import { parentPort } from "node:worker_threads"
|
|
18
17
|
import {
|
|
19
18
|
jsenvAccessControlAllowedHeaders,
|
|
20
19
|
startServer,
|
|
@@ -22,46 +21,32 @@ import {
|
|
|
22
21
|
jsenvServiceCORS,
|
|
23
22
|
jsenvServiceErrorHandler,
|
|
24
23
|
} from "@jsenv/server"
|
|
25
|
-
import {
|
|
26
|
-
validateDirectoryUrl,
|
|
27
|
-
registerDirectoryLifecycle,
|
|
28
|
-
} from "@jsenv/filesystem"
|
|
24
|
+
import { assertAndNormalizeDirectoryUrl } from "@jsenv/filesystem"
|
|
29
25
|
import { Abort, raceProcessTeardownEvents } from "@jsenv/abort"
|
|
30
26
|
import { createLogger, createTaskLog } from "@jsenv/log"
|
|
31
|
-
import { getCallerPosition } from "@jsenv/urls"
|
|
32
|
-
|
|
33
|
-
import { createReloadableWorker } from "@jsenv/core/src/helpers/worker_reload.js"
|
|
34
27
|
|
|
35
28
|
/**
|
|
36
29
|
* Start a server for build files.
|
|
37
30
|
* @param {Object} buildServerParameters
|
|
38
|
-
* @param {string|url} buildServerParameters.rootDirectoryUrl Root directory of the project
|
|
39
31
|
* @param {string|url} buildServerParameters.buildDirectoryUrl Directory where build files are written
|
|
40
32
|
* @return {Object} A build server object
|
|
41
33
|
*/
|
|
42
34
|
export const startBuildServer = async ({
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
logLevel,
|
|
46
|
-
serverLogLevel = "warn",
|
|
47
|
-
https,
|
|
48
|
-
http2,
|
|
49
|
-
acceptAnyIp,
|
|
50
|
-
hostname,
|
|
35
|
+
buildDirectoryUrl,
|
|
36
|
+
buildMainFilePath = "index.html",
|
|
51
37
|
port = 9779,
|
|
52
38
|
services = [],
|
|
39
|
+
acceptAnyIp,
|
|
40
|
+
hostname,
|
|
41
|
+
https,
|
|
42
|
+
http2,
|
|
43
|
+
logLevel,
|
|
44
|
+
serverLogLevel = "warn",
|
|
45
|
+
|
|
46
|
+
signal = new AbortController().signal,
|
|
47
|
+
handleSIGINT = true,
|
|
53
48
|
keepProcessAlive = true,
|
|
54
49
|
|
|
55
|
-
rootDirectoryUrl,
|
|
56
|
-
buildDirectoryUrl,
|
|
57
|
-
buildIndexPath = "index.html",
|
|
58
|
-
buildServerFiles = {
|
|
59
|
-
"./package.json": true,
|
|
60
|
-
"./jsenv.config.mjs": true,
|
|
61
|
-
},
|
|
62
|
-
buildServerAutoreload = false,
|
|
63
|
-
buildServerMainFile = getCallerPosition().url,
|
|
64
|
-
cooldownBetweenFileEvents,
|
|
65
50
|
...rest
|
|
66
51
|
}) => {
|
|
67
52
|
// params validation
|
|
@@ -72,40 +57,31 @@ export const startBuildServer = async ({
|
|
|
72
57
|
`${unexpectedParamNames.join(",")}: there is no such param`,
|
|
73
58
|
)
|
|
74
59
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
)
|
|
80
|
-
}
|
|
81
|
-
rootDirectoryUrl = rootDirectoryUrlValidation.value
|
|
82
|
-
const buildDirectoryUrlValidation = validateDirectoryUrl(buildDirectoryUrl)
|
|
83
|
-
if (!buildDirectoryUrlValidation.valid) {
|
|
84
|
-
throw new TypeError(
|
|
85
|
-
`buildDirectoryUrl ${buildDirectoryUrlValidation.message}, got ${buildDirectoryUrlValidation}`,
|
|
86
|
-
)
|
|
87
|
-
}
|
|
88
|
-
buildDirectoryUrl = buildDirectoryUrlValidation.value
|
|
60
|
+
buildDirectoryUrl = assertAndNormalizeDirectoryUrl(
|
|
61
|
+
buildDirectoryUrl,
|
|
62
|
+
"buildDirectoryUrl",
|
|
63
|
+
)
|
|
89
64
|
|
|
90
|
-
if (
|
|
91
|
-
if (typeof
|
|
65
|
+
if (buildMainFilePath) {
|
|
66
|
+
if (typeof buildMainFilePath !== "string") {
|
|
92
67
|
throw new TypeError(
|
|
93
|
-
`
|
|
68
|
+
`buildMainFilePath must be a string, got ${buildMainFilePath}`,
|
|
94
69
|
)
|
|
95
70
|
}
|
|
96
|
-
if (
|
|
97
|
-
|
|
71
|
+
if (buildMainFilePath[0] === "/") {
|
|
72
|
+
buildMainFilePath = buildMainFilePath.slice(1)
|
|
98
73
|
} else {
|
|
99
|
-
const
|
|
100
|
-
|
|
74
|
+
const buildMainFileUrl = new URL(buildMainFilePath, buildDirectoryUrl)
|
|
75
|
+
.href
|
|
76
|
+
if (!buildMainFileUrl.startsWith(buildDirectoryUrl)) {
|
|
101
77
|
throw new Error(
|
|
102
|
-
`
|
|
78
|
+
`buildMainFilePath must be relative, got ${buildMainFilePath}`,
|
|
103
79
|
)
|
|
104
80
|
}
|
|
105
|
-
|
|
81
|
+
buildMainFilePath = buildMainFileUrl.slice(buildDirectoryUrl.length)
|
|
106
82
|
}
|
|
107
|
-
if (!existsSync(new URL(
|
|
108
|
-
|
|
83
|
+
if (!existsSync(new URL(buildMainFilePath, buildDirectoryUrl))) {
|
|
84
|
+
buildMainFilePath = null
|
|
109
85
|
}
|
|
110
86
|
}
|
|
111
87
|
}
|
|
@@ -124,59 +100,6 @@ export const startBuildServer = async ({
|
|
|
124
100
|
})
|
|
125
101
|
}
|
|
126
102
|
|
|
127
|
-
let reloadableWorker
|
|
128
|
-
if (buildServerAutoreload) {
|
|
129
|
-
reloadableWorker = createReloadableWorker(buildServerMainFile)
|
|
130
|
-
if (reloadableWorker.isPrimary) {
|
|
131
|
-
const buildServerFileChangeCallback = ({ relativeUrl, event }) => {
|
|
132
|
-
const url = new URL(relativeUrl, rootDirectoryUrl).href
|
|
133
|
-
logger.info(`file ${event} ${url} -> restarting server...`)
|
|
134
|
-
reloadableWorker.reload()
|
|
135
|
-
}
|
|
136
|
-
const stopWatchingBuildServerFiles = registerDirectoryLifecycle(
|
|
137
|
-
rootDirectoryUrl,
|
|
138
|
-
{
|
|
139
|
-
watchPatterns: {
|
|
140
|
-
...buildServerFiles,
|
|
141
|
-
[buildServerMainFile]: true,
|
|
142
|
-
".jsenv/": false,
|
|
143
|
-
},
|
|
144
|
-
cooldownBetweenFileEvents,
|
|
145
|
-
keepProcessAlive: false,
|
|
146
|
-
recursive: true,
|
|
147
|
-
added: ({ relativeUrl }) => {
|
|
148
|
-
buildServerFileChangeCallback({ relativeUrl, event: "added" })
|
|
149
|
-
},
|
|
150
|
-
updated: ({ relativeUrl }) => {
|
|
151
|
-
buildServerFileChangeCallback({ relativeUrl, event: "modified" })
|
|
152
|
-
},
|
|
153
|
-
removed: ({ relativeUrl }) => {
|
|
154
|
-
buildServerFileChangeCallback({ relativeUrl, event: "removed" })
|
|
155
|
-
},
|
|
156
|
-
},
|
|
157
|
-
)
|
|
158
|
-
operation.addAbortCallback(() => {
|
|
159
|
-
stopWatchingBuildServerFiles()
|
|
160
|
-
reloadableWorker.terminate()
|
|
161
|
-
})
|
|
162
|
-
const worker = await reloadableWorker.load()
|
|
163
|
-
const messagePromise = new Promise((resolve) => {
|
|
164
|
-
worker.once("message", resolve)
|
|
165
|
-
})
|
|
166
|
-
const origin = await messagePromise
|
|
167
|
-
// if (!keepProcessAlive) {
|
|
168
|
-
// worker.unref()
|
|
169
|
-
// }
|
|
170
|
-
return {
|
|
171
|
-
origin,
|
|
172
|
-
stop: () => {
|
|
173
|
-
stopWatchingBuildServerFiles()
|
|
174
|
-
reloadableWorker.terminate()
|
|
175
|
-
},
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
103
|
const startBuildServerTask = createTaskLog("start build server", {
|
|
181
104
|
disabled: !logger.levels.info,
|
|
182
105
|
})
|
|
@@ -211,7 +134,7 @@ export const startBuildServer = async ({
|
|
|
211
134
|
name: "jsenv:build_files_service",
|
|
212
135
|
handleRequest: createBuildFilesService({
|
|
213
136
|
buildDirectoryUrl,
|
|
214
|
-
|
|
137
|
+
buildMainFilePath,
|
|
215
138
|
}),
|
|
216
139
|
},
|
|
217
140
|
jsenvServiceErrorHandler({
|
|
@@ -229,9 +152,6 @@ export const startBuildServer = async ({
|
|
|
229
152
|
logger.info(`- ${server.origins[key]}`)
|
|
230
153
|
})
|
|
231
154
|
logger.info(``)
|
|
232
|
-
if (reloadableWorker && reloadableWorker.isWorker) {
|
|
233
|
-
parentPort.postMessage(server.origin)
|
|
234
|
-
}
|
|
235
155
|
return {
|
|
236
156
|
origin: server.origin,
|
|
237
157
|
stop: () => {
|
|
@@ -240,13 +160,13 @@ export const startBuildServer = async ({
|
|
|
240
160
|
}
|
|
241
161
|
}
|
|
242
162
|
|
|
243
|
-
const createBuildFilesService = ({ buildDirectoryUrl,
|
|
163
|
+
const createBuildFilesService = ({ buildDirectoryUrl, buildMainFilePath }) => {
|
|
244
164
|
return (request) => {
|
|
245
165
|
const urlIsVersioned = new URL(request.url).searchParams.has("v")
|
|
246
|
-
if (
|
|
166
|
+
if (buildMainFilePath && request.resource === "/") {
|
|
247
167
|
request = {
|
|
248
168
|
...request,
|
|
249
|
-
resource: `/${
|
|
169
|
+
resource: `/${buildMainFilePath}`,
|
|
250
170
|
}
|
|
251
171
|
}
|
|
252
172
|
return fetchFileSystem(
|