@eighty4/dank 0.0.5-1 → 0.0.5-3
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/lib/build.ts +1 -2
- package/lib/build_tag.ts +119 -15
- package/lib/config.ts +28 -0
- package/lib/dank.ts +10 -2
- package/lib/dirs.ts +8 -9
- package/lib/http.ts +9 -10
- package/lib/serve.ts +65 -21
- package/lib/services.ts +242 -204
- package/lib/watch.ts +38 -7
- package/lib_js/build.js +1 -2
- package/lib_js/build_tag.js +74 -14
- package/lib_js/config.js +20 -0
- package/lib_js/dirs.js +6 -6
- package/lib_js/http.js +8 -9
- package/lib_js/serve.js +45 -14
- package/lib_js/services.js +158 -169
- package/lib_js/watch.js +14 -5
- package/lib_types/dank.d.ts +5 -0
- package/package.json +1 -1
package/lib_js/config.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isAbsolute, resolve } from "node:path";
|
|
2
|
+
import { createBuildTag } from "./build_tag.js";
|
|
2
3
|
import { defaultProjectDirs } from "./dirs.js";
|
|
3
4
|
import { resolveFlags as lookupDankFlags } from "./flags.js";
|
|
4
5
|
var __rewriteRelativeImportExtension = function(path, preserveJsx) {
|
|
@@ -24,6 +25,7 @@ async function loadConfig(mode, projectRootAbs) {
|
|
|
24
25
|
return c;
|
|
25
26
|
}
|
|
26
27
|
class DankConfigInternal {
|
|
28
|
+
#buildTag;
|
|
27
29
|
#dirs;
|
|
28
30
|
#flags;
|
|
29
31
|
#mode;
|
|
@@ -67,8 +69,12 @@ class DankConfigInternal {
|
|
|
67
69
|
get services() {
|
|
68
70
|
return this.#services;
|
|
69
71
|
}
|
|
72
|
+
async buildTag() {
|
|
73
|
+
return await createBuildTag(this.#dirs.projectRootAbs, this.#flags, this.#buildTag);
|
|
74
|
+
}
|
|
70
75
|
async reload() {
|
|
71
76
|
const userConfig = await resolveConfig(this.#modulePath, resolveDankDetails(this.#mode, this.#flags));
|
|
77
|
+
this.#buildTag = userConfig.buildTag;
|
|
72
78
|
this.#dankPort = resolveDankPort(this.#flags, userConfig);
|
|
73
79
|
this.#esbuildPort = resolveEsbuildPort(this.#flags, userConfig);
|
|
74
80
|
this.#esbuild = Object.freeze(userConfig.esbuild);
|
|
@@ -99,6 +105,7 @@ function resolveDankDetails(mode, flags) {
|
|
|
99
105
|
function validateDankConfig(c) {
|
|
100
106
|
try {
|
|
101
107
|
validatePorts(c);
|
|
108
|
+
validateBuildTag(c.buildTag);
|
|
102
109
|
validatePages(c.pages);
|
|
103
110
|
validateDevPages(c.devPages);
|
|
104
111
|
validateDevServices(c.services);
|
|
@@ -119,6 +126,19 @@ function validatePorts(c) {
|
|
|
119
126
|
}
|
|
120
127
|
}
|
|
121
128
|
}
|
|
129
|
+
function validateBuildTag(buildTag) {
|
|
130
|
+
if (buildTag === null) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
switch (typeof buildTag) {
|
|
134
|
+
case "undefined":
|
|
135
|
+
case "string":
|
|
136
|
+
case "function":
|
|
137
|
+
return;
|
|
138
|
+
default:
|
|
139
|
+
throw Error("DankConfig.buildTag must be a string or function");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
122
142
|
function validateEsbuildConfig(esbuild) {
|
|
123
143
|
if (esbuild?.loaders !== null && typeof esbuild?.loaders !== "undefined") {
|
|
124
144
|
if (typeof esbuild.loaders !== "object") {
|
package/lib_js/dirs.js
CHANGED
|
@@ -2,18 +2,18 @@ import { realpath } from "node:fs/promises";
|
|
|
2
2
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
3
3
|
async function defaultProjectDirs(projectRootAbs) {
|
|
4
4
|
if (!isAbsolute(projectRootAbs)) {
|
|
5
|
-
throw Error();
|
|
5
|
+
throw Error("must use an absolute project root path");
|
|
6
|
+
}
|
|
7
|
+
if (await realpath(projectRootAbs) !== projectRootAbs) {
|
|
8
|
+
throw Error("must use a real project root path");
|
|
6
9
|
}
|
|
7
|
-
const projectResolved = await realpath(projectRootAbs);
|
|
8
10
|
const pages = "pages";
|
|
9
|
-
const pagesResolved = join(projectResolved, pages);
|
|
10
11
|
return Object.freeze({
|
|
11
12
|
buildRoot: "build",
|
|
12
13
|
buildDist: join("build", "dist"),
|
|
13
14
|
buildWatch: join("build", "watch"),
|
|
14
15
|
pages,
|
|
15
|
-
|
|
16
|
-
projectResolved,
|
|
16
|
+
pagesAbs: join(projectRootAbs, pages),
|
|
17
17
|
projectRootAbs,
|
|
18
18
|
public: "public"
|
|
19
19
|
});
|
|
@@ -40,7 +40,7 @@ class Resolver {
|
|
|
40
40
|
}
|
|
41
41
|
// `p` is expected to be a relative path resolvable from the project dir
|
|
42
42
|
isProjectSubpathInPagesDir(p) {
|
|
43
|
-
return resolve(join(this.#dirs.
|
|
43
|
+
return resolve(join(this.#dirs.projectRootAbs, p)).startsWith(this.#dirs.pagesAbs);
|
|
44
44
|
}
|
|
45
45
|
// `p` is expected to be a relative path resolvable from the pages dir
|
|
46
46
|
isPagesSubpathInPagesDir(p) {
|
package/lib_js/http.js
CHANGED
|
@@ -4,7 +4,7 @@ import { createServer } from "node:http";
|
|
|
4
4
|
import { extname, join } from "node:path";
|
|
5
5
|
import { Readable } from "node:stream";
|
|
6
6
|
import mime from "mime";
|
|
7
|
-
function startWebServer(port, flags, dirs, urlRewriteProvider, frontendFetcher,
|
|
7
|
+
function startWebServer(port, flags, dirs, urlRewriteProvider, frontendFetcher, devServices) {
|
|
8
8
|
const serverAddress = "http://localhost:" + port;
|
|
9
9
|
const handler = (req, res) => {
|
|
10
10
|
if (!req.url || !req.method) {
|
|
@@ -12,13 +12,13 @@ function startWebServer(port, flags, dirs, urlRewriteProvider, frontendFetcher,
|
|
|
12
12
|
} else {
|
|
13
13
|
const url = new URL(serverAddress + req.url);
|
|
14
14
|
const headers = convertHeadersToFetch(req.headers);
|
|
15
|
-
frontendFetcher(url, headers, res, () => onNotFound(req, url, headers,
|
|
15
|
+
frontendFetcher(url, headers, res, () => onNotFound(req, url, headers, devServices, flags, dirs, urlRewriteProvider, res));
|
|
16
16
|
}
|
|
17
17
|
};
|
|
18
18
|
createServer(flags.logHttp ? createLogWrapper(handler) : handler).listen(port);
|
|
19
19
|
console.log(flags.preview ? "preview" : "dev", `server is live at http://127.0.0.1:${port}`);
|
|
20
20
|
}
|
|
21
|
-
async function onNotFound(req, url, headers,
|
|
21
|
+
async function onNotFound(req, url, headers, devServices, flags, dirs, urlRewriteProvider, res) {
|
|
22
22
|
if (req.method === "GET" && extname(url.pathname) === "") {
|
|
23
23
|
const urlRewrite = tryUrlRewrites(flags, dirs, urlRewriteProvider.urlRewrites, url);
|
|
24
24
|
if (urlRewrite) {
|
|
@@ -26,7 +26,7 @@ async function onNotFound(req, url, headers, httpServices, flags, dirs, urlRewri
|
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
-
const fetchResponse = await tryHttpServices(req, url, headers,
|
|
29
|
+
const fetchResponse = await tryHttpServices(req, url, headers, devServices);
|
|
30
30
|
if (fetchResponse) {
|
|
31
31
|
sendFetchResponse(res, fetchResponse);
|
|
32
32
|
} else {
|
|
@@ -46,13 +46,12 @@ function tryUrlRewrites(flags, dirs, urlRewrites, url) {
|
|
|
46
46
|
const urlRewrite = urlRewrites.find((urlRewrite2) => urlRewrite2.pattern.test(url.pathname));
|
|
47
47
|
return urlRewrite ? join(flags.preview ? dirs.buildDist : dirs.buildWatch, urlRewrite.url, "index.html") : null;
|
|
48
48
|
}
|
|
49
|
-
async function tryHttpServices(req, url, headers,
|
|
49
|
+
async function tryHttpServices(req, url, headers, devServices) {
|
|
50
50
|
if (url.pathname.startsWith("/.well-known/")) {
|
|
51
51
|
return null;
|
|
52
52
|
}
|
|
53
53
|
const body = await collectReqBody(req);
|
|
54
|
-
const
|
|
55
|
-
for (const httpService of running) {
|
|
54
|
+
for (const httpService of devServices.httpServices) {
|
|
56
55
|
const proxyUrl = new URL(url);
|
|
57
56
|
proxyUrl.port = `${httpService.port}`;
|
|
58
57
|
try {
|
|
@@ -94,9 +93,9 @@ function createLogWrapper(handler) {
|
|
|
94
93
|
function createBuiltDistFilesFetcher(dirs, manifest) {
|
|
95
94
|
return (url, _headers, res, notFound) => {
|
|
96
95
|
if (manifest.pageUrls.has(url.pathname)) {
|
|
97
|
-
streamFile(join(dirs.
|
|
96
|
+
streamFile(join(dirs.projectRootAbs, dirs.buildDist, url.pathname, "index.html"), res);
|
|
98
97
|
} else if (manifest.files.has(url.pathname)) {
|
|
99
|
-
streamFile(join(dirs.
|
|
98
|
+
streamFile(join(dirs.projectRootAbs, dirs.buildDist, url.pathname), res);
|
|
100
99
|
} else {
|
|
101
100
|
notFound();
|
|
102
101
|
}
|
package/lib_js/serve.js
CHANGED
|
@@ -6,46 +6,51 @@ import { createGlobalDefinitions } from "./define.js";
|
|
|
6
6
|
import { esbuildDevContext } from "./esbuild.js";
|
|
7
7
|
import { createBuiltDistFilesFetcher, createDevServeFilesFetcher, startWebServer } from "./http.js";
|
|
8
8
|
import { WebsiteRegistry } from "./registry.js";
|
|
9
|
-
import {
|
|
9
|
+
import { DevServices } from "./services.js";
|
|
10
10
|
import { watch } from "./watch.js";
|
|
11
11
|
let c;
|
|
12
12
|
async function serveWebsite() {
|
|
13
13
|
c = await loadConfig("serve", process.cwd());
|
|
14
14
|
await rm(c.dirs.buildRoot, { force: true, recursive: true });
|
|
15
|
-
const abortController = new AbortController();
|
|
16
|
-
process.once("exit", () => abortController.abort());
|
|
17
15
|
if (c.flags.preview) {
|
|
18
|
-
await startPreviewMode(
|
|
16
|
+
await startPreviewMode();
|
|
19
17
|
} else {
|
|
20
|
-
await startDevMode(
|
|
18
|
+
await startDevMode();
|
|
21
19
|
}
|
|
22
20
|
return new Promise(() => {
|
|
23
21
|
});
|
|
24
22
|
}
|
|
25
|
-
async function startPreviewMode(
|
|
23
|
+
async function startPreviewMode() {
|
|
26
24
|
const manifest = await buildWebsite(c);
|
|
27
25
|
const frontend = createBuiltDistFilesFetcher(c.dirs, manifest);
|
|
28
|
-
const devServices =
|
|
26
|
+
const devServices = launchDevServices();
|
|
29
27
|
const urlRewrites = Object.keys(c.pages).sort().map((url) => {
|
|
30
28
|
const mapping = c.pages[url];
|
|
31
29
|
return typeof mapping !== "object" || !mapping.pattern ? null : { url, pattern: mapping.pattern };
|
|
32
30
|
}).filter((mapping) => mapping !== null);
|
|
33
|
-
startWebServer(c.dankPort, c.flags, c.dirs, { urlRewrites }, frontend, devServices
|
|
31
|
+
startWebServer(c.dankPort, c.flags, c.dirs, { urlRewrites }, frontend, devServices);
|
|
32
|
+
const controller = new AbortController();
|
|
33
|
+
watch("dank.config.ts", controller.signal, async (filename) => {
|
|
34
|
+
console.log(filename, "was updated!");
|
|
35
|
+
console.log("config updates are not hot reloaded during `dank serve --preview`");
|
|
36
|
+
console.log("restart DANK to reload configuration");
|
|
37
|
+
controller.abort();
|
|
38
|
+
});
|
|
34
39
|
}
|
|
35
|
-
async function startDevMode(
|
|
40
|
+
async function startDevMode() {
|
|
36
41
|
const registry = new WebsiteRegistry(c);
|
|
37
42
|
await mkdir(c.dirs.buildWatch, { recursive: true });
|
|
38
43
|
let buildContext = null;
|
|
39
|
-
watch("dank.config.ts",
|
|
44
|
+
watch("dank.config.ts", async (filename) => {
|
|
40
45
|
try {
|
|
41
46
|
await c.reload();
|
|
42
47
|
} catch (ignore) {
|
|
43
48
|
return;
|
|
44
49
|
}
|
|
45
50
|
registry.configSync();
|
|
46
|
-
|
|
51
|
+
devServices.update(c.services);
|
|
47
52
|
});
|
|
48
|
-
watch(c.dirs.pages,
|
|
53
|
+
watch(c.dirs.pages, { recursive: true }, (filename) => {
|
|
49
54
|
if (extname(filename) === ".html") {
|
|
50
55
|
registry.htmlEntrypoints.forEach((html) => {
|
|
51
56
|
if (html.fsPath === filename) {
|
|
@@ -99,8 +104,8 @@ async function startDevMode(signal) {
|
|
|
99
104
|
registry.on("entrypoints", () => resetBuildContext());
|
|
100
105
|
resetBuildContext();
|
|
101
106
|
const frontend = createDevServeFilesFetcher(c.esbuildPort, c.dirs, registry);
|
|
102
|
-
const devServices =
|
|
103
|
-
startWebServer(c.dankPort, c.flags, c.dirs, registry, frontend, devServices
|
|
107
|
+
const devServices = launchDevServices();
|
|
108
|
+
startWebServer(c.dankPort, c.flags, c.dirs, registry, frontend, devServices);
|
|
104
109
|
}
|
|
105
110
|
async function startEsbuildWatch(registry) {
|
|
106
111
|
const entryPoints = registry.webpageAndWorkerEntryPoints;
|
|
@@ -121,6 +126,32 @@ async function writeHtml(html, output) {
|
|
|
121
126
|
const path = join(dir, "index.html");
|
|
122
127
|
await writeFile(path, output);
|
|
123
128
|
}
|
|
129
|
+
function launchDevServices() {
|
|
130
|
+
const services = new DevServices(c.services);
|
|
131
|
+
services.on("error", (label, cause) => console.log(formatServiceLabel(label), "errored:", cause));
|
|
132
|
+
services.on("exit", (label, code) => {
|
|
133
|
+
if (code) {
|
|
134
|
+
console.log(formatServiceLabel(label), "exited", code);
|
|
135
|
+
} else {
|
|
136
|
+
console.log(formatServiceLabel(label), "exited");
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
services.on("launch", (label) => console.log(formatServiceLabel(label), "starting"));
|
|
140
|
+
services.on("stdout", (label, output) => printServiceOutput(label, 32, output));
|
|
141
|
+
services.on("stderr", (label, output) => printServiceOutput(label, 31, output));
|
|
142
|
+
return services;
|
|
143
|
+
}
|
|
144
|
+
function formatServiceLabel(label) {
|
|
145
|
+
return `| \x1B[2m${label.cwd}\x1B[22m ${label.command} |`;
|
|
146
|
+
}
|
|
147
|
+
function formatServiceOutputLabel(label, color) {
|
|
148
|
+
return `\x1B[${color}m${formatServiceLabel(label)}\x1B[39m`;
|
|
149
|
+
}
|
|
150
|
+
function printServiceOutput(label, color, output) {
|
|
151
|
+
const formattedLabel = formatServiceOutputLabel(label, color);
|
|
152
|
+
for (const line of output)
|
|
153
|
+
console.log(formattedLabel, line);
|
|
154
|
+
}
|
|
124
155
|
export {
|
|
125
156
|
serveWebsite
|
|
126
157
|
};
|
package/lib_js/services.js
CHANGED
|
@@ -1,97 +1,130 @@
|
|
|
1
1
|
import { execSync, spawn } from "node:child_process";
|
|
2
|
+
import EventEmitter from "node:events";
|
|
2
3
|
import { basename, isAbsolute, resolve } from "node:path";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
4
|
+
class ManagedServiceLabel {
|
|
5
|
+
#command;
|
|
6
|
+
#cwd;
|
|
7
|
+
constructor(spec) {
|
|
8
|
+
this.#command = spec.command;
|
|
9
|
+
this.#cwd = !spec.cwd ? "./" : spec.cwd.startsWith("/") ? `/.../${basename(spec.cwd)}` : spec.cwd.startsWith(".") ? spec.cwd : `./${spec.cwd}`;
|
|
10
|
+
}
|
|
11
|
+
get command() {
|
|
12
|
+
return this.#command;
|
|
13
|
+
}
|
|
14
|
+
get cwd() {
|
|
15
|
+
return this.#cwd;
|
|
16
|
+
}
|
|
13
17
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
class ManagedService extends EventEmitter {
|
|
19
|
+
#label;
|
|
20
|
+
#process;
|
|
21
|
+
#spec;
|
|
22
|
+
// #status: ManagedServiceStatus = 'starting'
|
|
23
|
+
constructor(spec) {
|
|
24
|
+
super();
|
|
25
|
+
this.#label = new ManagedServiceLabel(spec);
|
|
26
|
+
this.#spec = spec;
|
|
27
|
+
this.#process = this.#start();
|
|
28
|
+
}
|
|
29
|
+
get spec() {
|
|
30
|
+
return this.#spec;
|
|
31
|
+
}
|
|
32
|
+
get httpSpec() {
|
|
33
|
+
return this.#spec.http;
|
|
22
34
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
35
|
+
matches(other) {
|
|
36
|
+
return matchingConfig(this.#spec, other);
|
|
37
|
+
}
|
|
38
|
+
kill() {
|
|
39
|
+
if (this.#process)
|
|
40
|
+
killProcess(this.#process);
|
|
41
|
+
}
|
|
42
|
+
#start() {
|
|
43
|
+
const { path, args } = parseCommand(this.#spec.command);
|
|
44
|
+
const env = this.#spec.env ? { ...process.env, ...this.#spec.env } : void 0;
|
|
45
|
+
const cwd = !this.#spec.cwd || isAbsolute(this.#spec.cwd) ? this.#spec.cwd : resolve(process.cwd(), this.#spec.cwd);
|
|
46
|
+
const spawned = spawnProcess(path, args, env, cwd);
|
|
47
|
+
this.emit("launch", this.#label);
|
|
48
|
+
spawned.stdout.on("data", (chunk) => this.emit("stdout", this.#label, parseChunk(chunk)));
|
|
49
|
+
spawned.stderr.on("data", (chunk) => this.emit("stderr", this.#label, parseChunk(chunk)));
|
|
50
|
+
spawned.on("error", (e) => {
|
|
51
|
+
if (e.name === "AbortError") {
|
|
52
|
+
return;
|
|
27
53
|
}
|
|
28
|
-
|
|
29
|
-
|
|
54
|
+
const cause = "code" in e && e.code === "ENOENT" ? "program not found" : e.message;
|
|
55
|
+
this.emit("error", this.#label, cause);
|
|
56
|
+
});
|
|
57
|
+
spawned.on("exit", (code, signal) => this.emit("exit", this.#label, code || signal));
|
|
58
|
+
return spawned;
|
|
59
|
+
}
|
|
30
60
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
61
|
+
const killProcess = process.platform === "win32" ? (p) => execSync(`taskkill /pid ${p.pid} /T /F`) : (p) => p.kill();
|
|
62
|
+
const spawnProcess = process.platform === "win32" ? (path, args, env, cwd) => spawn("cmd", ["/c", path, ...args], {
|
|
63
|
+
cwd,
|
|
64
|
+
env,
|
|
65
|
+
detached: false,
|
|
66
|
+
shell: false,
|
|
67
|
+
windowsHide: true
|
|
68
|
+
}) : (path, args, env, cwd) => spawn(path, args, {
|
|
69
|
+
cwd,
|
|
70
|
+
env,
|
|
71
|
+
detached: false,
|
|
72
|
+
shell: false
|
|
73
|
+
});
|
|
74
|
+
class DevServices extends EventEmitter {
|
|
75
|
+
#running;
|
|
76
|
+
constructor(services) {
|
|
77
|
+
super();
|
|
78
|
+
this.#running = services ? this.#start(services) : [];
|
|
79
|
+
if (process.platform === "win32") {
|
|
80
|
+
process.once("SIGINT", () => process.exit());
|
|
45
81
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
for (let i = running.length - 1; i >= 0; i--) {
|
|
67
|
-
if (!keep.includes(i)) {
|
|
68
|
-
const { s, process: process2 } = running[i];
|
|
69
|
-
if (process2) {
|
|
70
|
-
stopService(s, process2);
|
|
71
|
-
} else {
|
|
72
|
-
removeFromUpdating(s);
|
|
73
|
-
}
|
|
74
|
-
running.splice(i, 1);
|
|
75
|
-
}
|
|
82
|
+
process.once("exit", this.shutdown);
|
|
83
|
+
}
|
|
84
|
+
get httpServices() {
|
|
85
|
+
return this.#running.map((s) => s.httpSpec).filter((http) => !!http);
|
|
86
|
+
}
|
|
87
|
+
shutdown = () => {
|
|
88
|
+
this.#running.forEach((s) => {
|
|
89
|
+
s.kill();
|
|
90
|
+
s.removeAllListeners();
|
|
91
|
+
});
|
|
92
|
+
this.#running.length = 0;
|
|
93
|
+
};
|
|
94
|
+
update(services) {
|
|
95
|
+
if (!services?.length) {
|
|
96
|
+
this.shutdown();
|
|
97
|
+
} else if (!matchingConfigs(this.#running.map((s) => s.spec), services)) {
|
|
98
|
+
this.shutdown();
|
|
99
|
+
this.#running = this.#start(services);
|
|
76
100
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
101
|
+
}
|
|
102
|
+
#start(services) {
|
|
103
|
+
return services.map((spec) => {
|
|
104
|
+
const service = new ManagedService(spec);
|
|
105
|
+
service.on("error", (label, cause) => this.emit("error", label, cause));
|
|
106
|
+
service.on("exit", (label, code) => this.emit("exit", label, code));
|
|
107
|
+
service.on("launch", (label) => this.emit("launch", label));
|
|
108
|
+
service.on("stdout", (label, output) => this.emit("stdout", label, output));
|
|
109
|
+
service.on("stderr", (label, output) => this.emit("stderr", label, output));
|
|
110
|
+
return service;
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function matchingConfigs(a, b) {
|
|
115
|
+
if (a.length !== b.length) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
const crossRef = [...a];
|
|
119
|
+
for (const toFind of b) {
|
|
120
|
+
const found = crossRef.findIndex((spec) => matchingConfig(spec, toFind));
|
|
121
|
+
if (found === -1) {
|
|
122
|
+
return false;
|
|
83
123
|
} else {
|
|
84
|
-
|
|
85
|
-
for (const s of next) {
|
|
86
|
-
running.push({ s, process: startService(s) });
|
|
87
|
-
}
|
|
124
|
+
crossRef.splice(found, 1);
|
|
88
125
|
}
|
|
89
126
|
}
|
|
90
|
-
|
|
91
|
-
function stopService(s, process2) {
|
|
92
|
-
opPrint(s, "stopping");
|
|
93
|
-
updating.stopping.push(s);
|
|
94
|
-
process2.kill();
|
|
127
|
+
return true;
|
|
95
128
|
}
|
|
96
129
|
function matchingConfig(a, b) {
|
|
97
130
|
if (a.command !== b.command) {
|
|
@@ -119,99 +152,55 @@ function matchingConfig(a, b) {
|
|
|
119
152
|
}
|
|
120
153
|
return true;
|
|
121
154
|
}
|
|
122
|
-
function
|
|
123
|
-
|
|
124
|
-
const spawned = spawnService(s);
|
|
125
|
-
const stdoutLabel = logLabel(s, 32);
|
|
126
|
-
spawned.stdout.on("data", (chunk) => printChunk(stdoutLabel, chunk));
|
|
127
|
-
const stderrLabel = logLabel(s, 31);
|
|
128
|
-
spawned.stderr.on("data", (chunk) => printChunk(stderrLabel, chunk));
|
|
129
|
-
spawned.on("error", (e) => {
|
|
130
|
-
removeFromRunning(s);
|
|
131
|
-
if (e.name === "AbortError") {
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
const cause = "code" in e && e.code === "ENOENT" ? "program not found" : e.message;
|
|
135
|
-
opPrint(s, "error: " + cause);
|
|
136
|
-
});
|
|
137
|
-
spawned.on("exit", () => {
|
|
138
|
-
opPrint(s, "exited");
|
|
139
|
-
removeFromRunning(s);
|
|
140
|
-
removeFromUpdating(s);
|
|
141
|
-
});
|
|
142
|
-
return spawned;
|
|
155
|
+
function parseChunk(c) {
|
|
156
|
+
return c.toString().replace(/\r?\n$/, "").split(/\r?\n/);
|
|
143
157
|
}
|
|
144
|
-
function
|
|
145
|
-
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const cwd = resolveCwd(s.cwd);
|
|
150
|
-
if (process.platform === "win32") {
|
|
151
|
-
return spawn("cmd", ["/c", program, ...args], {
|
|
152
|
-
cwd,
|
|
153
|
-
env,
|
|
154
|
-
detached: false,
|
|
155
|
-
shell: false,
|
|
156
|
-
windowsHide: true
|
|
157
|
-
});
|
|
158
|
-
} else {
|
|
159
|
-
return spawn(program, args, {
|
|
160
|
-
cwd,
|
|
161
|
-
env,
|
|
162
|
-
signal,
|
|
163
|
-
detached: false,
|
|
164
|
-
shell: false
|
|
165
|
-
});
|
|
158
|
+
function parseCommand(command) {
|
|
159
|
+
command = command.trimStart();
|
|
160
|
+
const programSplitIndex = command.indexOf(" ");
|
|
161
|
+
if (programSplitIndex === -1) {
|
|
162
|
+
return { path: command.trim(), args: [] };
|
|
166
163
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
164
|
+
const path = command.substring(0, programSplitIndex);
|
|
165
|
+
const args = [];
|
|
166
|
+
let argStart = programSplitIndex + 1;
|
|
167
|
+
let withinLiteral = false;
|
|
168
|
+
for (let i = 0; i < command.length; i++) {
|
|
169
|
+
const c = command[i];
|
|
170
|
+
if (!withinLiteral) {
|
|
171
|
+
if (c === "'" || c === '"') {
|
|
172
|
+
withinLiteral = c;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (c === "\\") {
|
|
176
|
+
i++;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
173
179
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if (matchingConfig(updating.stopping[i], s)) {
|
|
180
|
-
updating.stopping.splice(i, 1);
|
|
181
|
-
if (!updating.stopping.length) {
|
|
182
|
-
updating.starting.forEach(startService);
|
|
183
|
-
updating = null;
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
180
|
+
if (withinLiteral) {
|
|
181
|
+
if (c === withinLiteral) {
|
|
182
|
+
withinLiteral = false;
|
|
183
|
+
args.push(command.substring(argStart + 1, i));
|
|
184
|
+
argStart = i + 1;
|
|
186
185
|
}
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (c === " " && i > argStart) {
|
|
189
|
+
const maybeArg2 = command.substring(argStart, i).trim();
|
|
190
|
+
if (maybeArg2.length) {
|
|
191
|
+
args.push(maybeArg2);
|
|
192
|
+
}
|
|
193
|
+
argStart = i + 1;
|
|
187
194
|
}
|
|
188
195
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
console.log(label, l);
|
|
193
|
-
}
|
|
194
|
-
function parseChunk(c) {
|
|
195
|
-
return c.toString().replace(/\r?\n$/, "").split(/\r?\n/);
|
|
196
|
-
}
|
|
197
|
-
function resolveCwd(p) {
|
|
198
|
-
if (!p || isAbsolute(p)) {
|
|
199
|
-
return p;
|
|
200
|
-
} else {
|
|
201
|
-
return resolve(process.cwd(), p);
|
|
196
|
+
const maybeArg = command.substring(argStart, command.length).trim();
|
|
197
|
+
if (maybeArg.length) {
|
|
198
|
+
args.push(maybeArg);
|
|
202
199
|
}
|
|
203
|
-
}
|
|
204
|
-
function opPrint(s, msg) {
|
|
205
|
-
console.log(opLabel(s), msg);
|
|
206
|
-
}
|
|
207
|
-
function opLabel(s) {
|
|
208
|
-
return `\`${s.cwd ? s.cwd + " " : ""}${s.command}\``;
|
|
209
|
-
}
|
|
210
|
-
function logLabel(s, ansiColor) {
|
|
211
|
-
s.cwd = !s.cwd ? "./" : s.cwd.startsWith("/") ? `/.../${basename(s.cwd)}` : s.cwd.startsWith(".") ? s.cwd : `./${s.cwd}`;
|
|
212
|
-
return `\x1B[${ansiColor}m[\x1B[1m${s.command}\x1B[22m \x1B[2;3m${s.cwd}\x1B[22;23m]\x1B[0m`;
|
|
200
|
+
return { path, args };
|
|
213
201
|
}
|
|
214
202
|
export {
|
|
215
|
-
|
|
216
|
-
|
|
203
|
+
DevServices,
|
|
204
|
+
ManagedServiceLabel,
|
|
205
|
+
parseCommand
|
|
217
206
|
};
|
package/lib_js/watch.js
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import { watch as createWatch } from "node:fs/promises";
|
|
2
|
-
async function watch(p,
|
|
2
|
+
async function watch(p, signalFireOrOpts, fireOrUndefined) {
|
|
3
|
+
let opts;
|
|
4
|
+
let fire;
|
|
5
|
+
if (signalFireOrOpts instanceof AbortSignal) {
|
|
6
|
+
opts = { signal: signalFireOrOpts };
|
|
7
|
+
} else if (typeof signalFireOrOpts === "object") {
|
|
8
|
+
opts = signalFireOrOpts;
|
|
9
|
+
} else {
|
|
10
|
+
fire = signalFireOrOpts;
|
|
11
|
+
}
|
|
12
|
+
if (opts && typeof fireOrUndefined === "function") {
|
|
13
|
+
fire = fireOrUndefined;
|
|
14
|
+
}
|
|
3
15
|
const delayFire = 90;
|
|
4
16
|
const timeout = 100;
|
|
5
17
|
let changes = {};
|
|
6
18
|
try {
|
|
7
|
-
for await (const { filename } of createWatch(p, {
|
|
8
|
-
recursive: true,
|
|
9
|
-
signal
|
|
10
|
-
})) {
|
|
19
|
+
for await (const { filename } of createWatch(p, opts)) {
|
|
11
20
|
if (filename) {
|
|
12
21
|
if (!changes[filename]) {
|
|
13
22
|
const now = Date.now();
|
package/lib_types/dank.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Plugin as EsbuildPlugin } from 'esbuild';
|
|
2
2
|
export type DankConfig = {
|
|
3
|
+
buildTag?: string | BuildTagBuilder;
|
|
3
4
|
esbuild?: EsbuildConfig;
|
|
4
5
|
pages: Record<`/${string}`, `${string}.html` | PageMapping>;
|
|
5
6
|
devPages?: Record<`/__${string}`, `${string}.html` | DevPageMapping>;
|
|
@@ -7,6 +8,10 @@ export type DankConfig = {
|
|
|
7
8
|
previewPort?: number;
|
|
8
9
|
services?: Array<DevService>;
|
|
9
10
|
};
|
|
11
|
+
export type BuildTagParams = {
|
|
12
|
+
production: boolean;
|
|
13
|
+
};
|
|
14
|
+
export type BuildTagBuilder = (build: BuildTagParams) => Promise<string> | string;
|
|
10
15
|
export type PageMapping = {
|
|
11
16
|
pattern?: RegExp;
|
|
12
17
|
webpage: `${string}.html`;
|