@eighty4/dank 0.0.5-2 → 0.0.5-4
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/client/ServiceWorker.ts +89 -0
- package/lib/bin.ts +6 -5
- package/lib/build.ts +57 -5
- package/lib/build_tag.ts +119 -15
- package/lib/config.ts +56 -1
- package/lib/dank.ts +44 -2
- package/lib/esbuild.ts +1 -0
- package/lib/http.ts +3 -3
- package/lib/public.ts +4 -4
- package/lib/registry.ts +59 -31
- package/lib/service_worker.ts +63 -0
- package/lib_js/bin.js +6 -5
- package/lib_js/build.js +40 -4
- package/lib_js/build_tag.js +74 -14
- package/lib_js/config.js +43 -0
- package/lib_js/dank.js +2 -0
- package/lib_js/esbuild.js +1 -0
- package/lib_js/http.js +2 -2
- package/lib_js/public.js +1 -1
- package/lib_js/registry.js +39 -13
- package/lib_js/service_worker.js +47 -0
- package/lib_types/dank.d.ts +22 -0
- package/lib_types/service_worker.d.ts +10 -0
- package/package.json +3 -2
package/lib/registry.ts
CHANGED
|
@@ -3,19 +3,12 @@ import { writeFile } from 'node:fs/promises'
|
|
|
3
3
|
import { join } from 'node:path/posix'
|
|
4
4
|
import type { BuildResult } from 'esbuild'
|
|
5
5
|
import type { ResolvedDankConfig } from './config.ts'
|
|
6
|
-
import type { PageMapping } from './dank.ts'
|
|
6
|
+
import type { PageMapping, WebsiteManifest } from './dank.ts'
|
|
7
7
|
import { LOG } from './developer.ts'
|
|
8
8
|
import { Resolver, type DankDirectories } from './dirs.ts'
|
|
9
9
|
import type { EntryPoint } from './esbuild.ts'
|
|
10
10
|
import { HtmlEntrypoint } from './html.ts'
|
|
11
11
|
|
|
12
|
-
// summary of a website build
|
|
13
|
-
export type WebsiteManifest = {
|
|
14
|
-
buildTag: string
|
|
15
|
-
files: Set<string>
|
|
16
|
-
pageUrls: Set<string>
|
|
17
|
-
}
|
|
18
|
-
|
|
19
12
|
// result of an esbuild build from the context of the config's entrypoints
|
|
20
13
|
// path of entrypoint is the reference point to lookup from a dependent page
|
|
21
14
|
export type BuildManifest = {
|
|
@@ -67,12 +60,13 @@ export type UrlRewriteProvider = {
|
|
|
67
60
|
// manages website resources during `dank build` and `dank serve`
|
|
68
61
|
export class WebsiteRegistry extends EventEmitter<WebsiteRegistryEvents> {
|
|
69
62
|
// paths of bundled esbuild outputs, as built by esbuild
|
|
70
|
-
#bundles: Set
|
|
63
|
+
#bundles: Set<`/${string}`> = new Set()
|
|
71
64
|
#c: ResolvedDankConfig
|
|
72
65
|
// public dir assets
|
|
73
|
-
#copiedAssets: Set
|
|
66
|
+
#copiedAssets: Set<`/${string}`> | null = null
|
|
74
67
|
// map of entrypoints to their output path
|
|
75
68
|
#entrypointHrefs: Record<string, string | null> = {}
|
|
69
|
+
#otherOutputs: Set<`/${string}`> | null = null
|
|
76
70
|
#pages: Record<`/${string}`, WebpageRegistration> = {}
|
|
77
71
|
readonly #resolver: Resolver
|
|
78
72
|
#workers: Array<WorkerManifest> | null = null
|
|
@@ -87,7 +81,7 @@ export class WebsiteRegistry extends EventEmitter<WebsiteRegistryEvents> {
|
|
|
87
81
|
return this.#c
|
|
88
82
|
}
|
|
89
83
|
|
|
90
|
-
set copiedAssets(copiedAssets: Array
|
|
84
|
+
set copiedAssets(copiedAssets: Array<`/${string}`> | null) {
|
|
91
85
|
this.#copiedAssets =
|
|
92
86
|
copiedAssets === null ? null : new Set(copiedAssets)
|
|
93
87
|
}
|
|
@@ -96,6 +90,14 @@ export class WebsiteRegistry extends EventEmitter<WebsiteRegistryEvents> {
|
|
|
96
90
|
return Object.values(this.#pages).map(p => p.html)
|
|
97
91
|
}
|
|
98
92
|
|
|
93
|
+
async manifest(): Promise<WebsiteManifest> {
|
|
94
|
+
return {
|
|
95
|
+
buildTag: await this.#c.buildTag(),
|
|
96
|
+
files: this.files(),
|
|
97
|
+
pageUrls: Object.keys(this.#pages) as Array<`/${string}`>,
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
99
101
|
get pageUrls(): Array<string> {
|
|
100
102
|
return Object.keys(this.#pages)
|
|
101
103
|
}
|
|
@@ -159,6 +161,27 @@ export class WebsiteRegistry extends EventEmitter<WebsiteRegistryEvents> {
|
|
|
159
161
|
return this.#workers
|
|
160
162
|
}
|
|
161
163
|
|
|
164
|
+
// add a build output that does is manually injected into build output,
|
|
165
|
+
// not from HTML processing, public directory, or esbuild entrypoints
|
|
166
|
+
async addBuildOutput(url: `/${string}`, content: string) {
|
|
167
|
+
if (
|
|
168
|
+
this.#pages[url] ||
|
|
169
|
+
this.#bundles.has(url) ||
|
|
170
|
+
this.#otherOutputs?.has(url) ||
|
|
171
|
+
this.#copiedAssets?.has(url)
|
|
172
|
+
) {
|
|
173
|
+
throw Error('build already has a ' + url)
|
|
174
|
+
}
|
|
175
|
+
if (this.#otherOutputs === null) this.#otherOutputs = new Set()
|
|
176
|
+
this.#otherOutputs.add(url)
|
|
177
|
+
const outputPath = join(
|
|
178
|
+
this.#c.dirs.projectRootAbs,
|
|
179
|
+
this.#c.dirs.buildDist,
|
|
180
|
+
url,
|
|
181
|
+
)
|
|
182
|
+
await writeFile(outputPath, content)
|
|
183
|
+
}
|
|
184
|
+
|
|
162
185
|
buildRegistry(): BuildRegistry {
|
|
163
186
|
return new BuildRegistry(
|
|
164
187
|
this.#c.dirs,
|
|
@@ -171,13 +194,18 @@ export class WebsiteRegistry extends EventEmitter<WebsiteRegistryEvents> {
|
|
|
171
194
|
this.#configDiff()
|
|
172
195
|
}
|
|
173
196
|
|
|
174
|
-
files():
|
|
175
|
-
const files = new Set
|
|
197
|
+
files(): Array<`/${string}`> {
|
|
198
|
+
const files = new Set<`/${string}`>()
|
|
176
199
|
for (const pageUrl of Object.keys(this.#pages))
|
|
177
|
-
files.add(
|
|
200
|
+
files.add(
|
|
201
|
+
pageUrl === '/'
|
|
202
|
+
? '/index.html'
|
|
203
|
+
: (`${pageUrl}/index.html` as `/${string}`),
|
|
204
|
+
)
|
|
178
205
|
for (const f of this.#bundles) files.add(f)
|
|
179
206
|
if (this.#copiedAssets) for (const f of this.#copiedAssets) files.add(f)
|
|
180
|
-
|
|
207
|
+
if (this.#otherOutputs) for (const f of this.#otherOutputs) files.add(f)
|
|
208
|
+
return Array.from(files)
|
|
181
209
|
}
|
|
182
210
|
|
|
183
211
|
mappedHref(lookup: string): string {
|
|
@@ -189,23 +217,15 @@ export class WebsiteRegistry extends EventEmitter<WebsiteRegistryEvents> {
|
|
|
189
217
|
}
|
|
190
218
|
}
|
|
191
219
|
|
|
192
|
-
async writeManifest(
|
|
193
|
-
const manifest = this.#manifest(
|
|
220
|
+
async writeManifest(): Promise<WebsiteManifest> {
|
|
221
|
+
const manifest = await this.#manifest()
|
|
194
222
|
await writeFile(
|
|
195
223
|
join(
|
|
196
224
|
this.#c.dirs.projectRootAbs,
|
|
197
225
|
this.#c.dirs.buildRoot,
|
|
198
226
|
'website.json',
|
|
199
227
|
),
|
|
200
|
-
JSON.stringify(
|
|
201
|
-
{
|
|
202
|
-
buildTag,
|
|
203
|
-
files: Array.from(manifest.files),
|
|
204
|
-
pageUrls: Array.from(manifest.pageUrls),
|
|
205
|
-
},
|
|
206
|
-
null,
|
|
207
|
-
4,
|
|
208
|
-
),
|
|
228
|
+
JSON.stringify(manifest, null, 4),
|
|
209
229
|
)
|
|
210
230
|
return manifest
|
|
211
231
|
}
|
|
@@ -303,18 +323,18 @@ export class WebsiteRegistry extends EventEmitter<WebsiteRegistryEvents> {
|
|
|
303
323
|
delete this.#pages[urlPath]
|
|
304
324
|
}
|
|
305
325
|
|
|
306
|
-
#manifest(
|
|
326
|
+
async #manifest(): Promise<WebsiteManifest> {
|
|
307
327
|
return {
|
|
308
|
-
buildTag,
|
|
328
|
+
buildTag: await this.#c.buildTag(),
|
|
309
329
|
files: this.files(),
|
|
310
|
-
pageUrls:
|
|
330
|
+
pageUrls: Object.keys(this.#pages) as Array<`/${string}`>,
|
|
311
331
|
}
|
|
312
332
|
}
|
|
313
333
|
|
|
314
334
|
#onBuildManifest: OnBuildComplete = (build: BuildManifest) => {
|
|
315
335
|
// collect built bundle entrypoint hrefs
|
|
316
336
|
for (const [outPath, entrypoint] of Object.entries(build.bundles)) {
|
|
317
|
-
this.#bundles.add(outPath)
|
|
337
|
+
this.#bundles.add(ensurePath(outPath))
|
|
318
338
|
if (entrypoint) {
|
|
319
339
|
this.#entrypointHrefs[entrypoint] = outPath
|
|
320
340
|
}
|
|
@@ -413,7 +433,7 @@ export class BuildRegistry {
|
|
|
413
433
|
for (const [outPath, output] of Object.entries(
|
|
414
434
|
result.metafile.outputs,
|
|
415
435
|
)) {
|
|
416
|
-
bundles[outPath.replace(/^build[/\\]dist/, '')] =
|
|
436
|
+
bundles[outPath.replace(/^build[/\\](dist|watch)/, '')] =
|
|
417
437
|
output.entryPoint || null
|
|
418
438
|
}
|
|
419
439
|
let workers: BuildManifest['workers'] = null
|
|
@@ -438,3 +458,11 @@ export class BuildRegistry {
|
|
|
438
458
|
})
|
|
439
459
|
}
|
|
440
460
|
}
|
|
461
|
+
|
|
462
|
+
function ensurePath(path: string): `/${string}` {
|
|
463
|
+
if (path.startsWith('/')) {
|
|
464
|
+
return path as `/${string}`
|
|
465
|
+
} else {
|
|
466
|
+
throw Error(`expect build dist path ${path} to start with /`)
|
|
467
|
+
}
|
|
468
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { join } from 'node:path'
|
|
2
|
+
import esbuild from 'esbuild'
|
|
3
|
+
import type { ServiceWorkerBuild } from './dank.ts'
|
|
4
|
+
|
|
5
|
+
export type ServiceWorkerCaching = {
|
|
6
|
+
cacheKey: string
|
|
7
|
+
bypassCache?: {
|
|
8
|
+
hosts?: Array<string>
|
|
9
|
+
paths?: Array<`/${string}`>
|
|
10
|
+
}
|
|
11
|
+
files: Array<`/${string}`>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function createServiceWorker(
|
|
15
|
+
caching: ServiceWorkerCaching,
|
|
16
|
+
): Promise<ServiceWorkerBuild> {
|
|
17
|
+
return {
|
|
18
|
+
outputs: [
|
|
19
|
+
{
|
|
20
|
+
content: await buildServiceWorkerBackend(caching),
|
|
21
|
+
url: '/sw.js',
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function buildServiceWorkerBackend(
|
|
28
|
+
caching: ServiceWorkerCaching,
|
|
29
|
+
): Promise<string> {
|
|
30
|
+
const result = await esbuild.build({
|
|
31
|
+
logLevel: 'silent',
|
|
32
|
+
absWorkingDir: join(import.meta.dirname, '../client'),
|
|
33
|
+
entryPoints: ['ServiceWorker.ts'],
|
|
34
|
+
treeShaking: true,
|
|
35
|
+
target: 'ES2022',
|
|
36
|
+
bundle: true,
|
|
37
|
+
minify: true,
|
|
38
|
+
format: 'iife',
|
|
39
|
+
platform: 'browser',
|
|
40
|
+
write: false,
|
|
41
|
+
metafile: true,
|
|
42
|
+
plugins: [
|
|
43
|
+
{
|
|
44
|
+
name: 'DANK:sw',
|
|
45
|
+
setup(build: esbuild.PluginBuild) {
|
|
46
|
+
build.onResolve({ filter: /DANK:sw/ }, () => {
|
|
47
|
+
return {
|
|
48
|
+
path: join(import.meta.dirname, 'DANK.sw.json'),
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
build.onLoad(
|
|
52
|
+
{ filter: /DANK\.sw\.json$/, namespace: 'file' },
|
|
53
|
+
async () => ({
|
|
54
|
+
contents: JSON.stringify(caching),
|
|
55
|
+
loader: 'json',
|
|
56
|
+
}),
|
|
57
|
+
)
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
})
|
|
62
|
+
return new TextDecoder().decode(result.outputFiles[0].contents)
|
|
63
|
+
}
|
package/lib_js/bin.js
CHANGED
|
@@ -4,19 +4,20 @@ import { DankError } from "./errors.js";
|
|
|
4
4
|
import { serveWebsite } from "./serve.js";
|
|
5
5
|
function printHelp(task2) {
|
|
6
6
|
if (!task2 || task2 === "build") {
|
|
7
|
-
console.log("dank build [--minify] [--production]");
|
|
7
|
+
console.log("dank build [--minify] [--production] [--service-worker]");
|
|
8
8
|
}
|
|
9
9
|
if (!task2 || task2 === "serve") {
|
|
10
10
|
console.log(
|
|
11
11
|
// 'dank serve [--minify] [--preview] [--production]',
|
|
12
|
-
"dank serve [--minify] [--production]"
|
|
12
|
+
"dank serve [--minify] [--production] [--service-worker]"
|
|
13
13
|
);
|
|
14
14
|
}
|
|
15
15
|
console.log("\nOPTIONS:");
|
|
16
16
|
if (!task2 || task2 === "serve")
|
|
17
|
-
console.log(" --log-http
|
|
18
|
-
console.log(" --minify
|
|
19
|
-
console.log(" --production
|
|
17
|
+
console.log(" --log-http print access logs");
|
|
18
|
+
console.log(" --minify minify sources");
|
|
19
|
+
console.log(" --production build for production release");
|
|
20
|
+
console.log(" --service-worker build service worker");
|
|
20
21
|
if (task2) {
|
|
21
22
|
console.log();
|
|
22
23
|
console.log("use `dank -h` for details on all commands");
|
package/lib_js/build.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { createBuildTag } from "./build_tag.js";
|
|
4
3
|
import { loadConfig } from "./config.js";
|
|
5
4
|
import { createGlobalDefinitions } from "./define.js";
|
|
6
5
|
import { esbuildWebpages, esbuildWorkers } from "./esbuild.js";
|
|
@@ -10,8 +9,7 @@ async function buildWebsite(c) {
|
|
|
10
9
|
if (!c) {
|
|
11
10
|
c = await loadConfig("build", process.cwd());
|
|
12
11
|
}
|
|
13
|
-
|
|
14
|
-
console.log(c.flags.minify ? c.flags.production ? "minified production" : "minified" : "unminified", "build", buildTag, "building in ./build/dist");
|
|
12
|
+
console.log(c.flags.minify ? c.flags.production ? "minified production" : "minified" : "unminified", "build", await c.buildTag(), "building in ./build/dist");
|
|
15
13
|
await rm(c.dirs.buildRoot, { recursive: true, force: true });
|
|
16
14
|
await mkdir(c.dirs.buildDist, { recursive: true });
|
|
17
15
|
for (const subdir of Object.keys(c.pages).filter((url) => url !== "/")) {
|
|
@@ -19,7 +17,7 @@ async function buildWebsite(c) {
|
|
|
19
17
|
}
|
|
20
18
|
await mkdir(join(c.dirs.buildRoot, "metafiles"), { recursive: true });
|
|
21
19
|
const registry = await buildWebpages(c, createGlobalDefinitions(c));
|
|
22
|
-
return await registry.writeManifest(
|
|
20
|
+
return await registry.writeManifest();
|
|
23
21
|
}
|
|
24
22
|
async function buildWebpages(c, define) {
|
|
25
23
|
const registry = new WebsiteRegistry(c);
|
|
@@ -35,6 +33,7 @@ async function buildWebpages(c, define) {
|
|
|
35
33
|
await Promise.all(registry.htmlEntrypoints.map(async (html) => {
|
|
36
34
|
await writeFile(join(c.dirs.buildDist, html.url, "index.html"), html.output(registry));
|
|
37
35
|
}));
|
|
36
|
+
await buildServiceWorker(registry);
|
|
38
37
|
return registry;
|
|
39
38
|
}
|
|
40
39
|
async function rewriteWorkerUrls(dirs, registry) {
|
|
@@ -65,6 +64,43 @@ async function rewriteWorkerUrls(dirs, registry) {
|
|
|
65
64
|
function createWorkerRegex(workerCtor, workerUrl) {
|
|
66
65
|
return new RegExp(`new(?:\\s|\\r?\\n)+${workerCtor}(?:\\s|\\r?\\n)*\\((?:\\s|\\r?\\n)*['"]${workerUrl}['"](?:\\s|\\r?\\n)*\\)`, "g");
|
|
67
66
|
}
|
|
67
|
+
async function buildServiceWorker(registry) {
|
|
68
|
+
const serviceWorkerBuilder = registry.config.serviceWorkerBuilder;
|
|
69
|
+
if (serviceWorkerBuilder) {
|
|
70
|
+
const website = await registry.manifest();
|
|
71
|
+
const serviceWorkerBuild = await serviceWorkerBuilder({ website });
|
|
72
|
+
validateServiceWorkerBuild(serviceWorkerBuild);
|
|
73
|
+
serviceWorkerBuild.outputs.map(async (output, i) => {
|
|
74
|
+
try {
|
|
75
|
+
return await registry.addBuildOutput(output.url, output.content);
|
|
76
|
+
} catch {
|
|
77
|
+
console.log(`ServiceWorkerBuild.outputs[${i}].url \`${output.url}\` is already a url in the build output.`);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function validateServiceWorkerBuild(serviceWorkerBuild) {
|
|
84
|
+
if (serviceWorkerBuild === null || typeof serviceWorkerBuild === "undefined") {
|
|
85
|
+
console.log(`ServiceWorkerBuild is ${serviceWorkerBuild}.`);
|
|
86
|
+
console.log("\nMake sure the builder function `serviceWorker` in `dank.config.ts` is returning a ServiceWorkerBuild.");
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
const testUrlPattern = /^\/.*\.js$/;
|
|
90
|
+
const valid = true;
|
|
91
|
+
serviceWorkerBuild.outputs.forEach((output, i) => {
|
|
92
|
+
if (!output.content?.length) {
|
|
93
|
+
console.log(`ServiceWorkerBuild.outputs[${i}].content is empty.`);
|
|
94
|
+
}
|
|
95
|
+
if (!output.url?.length || !testUrlPattern.test(output.url)) {
|
|
96
|
+
console.log(`ServiceWorkerBuild.outputs[${i}].url is not a valid \`/*.js\` path.`);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
if (!valid) {
|
|
100
|
+
console.log("\nCheck your `serviceWorker` config in `dank.config.ts`.");
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
68
104
|
export {
|
|
69
105
|
buildWebsite,
|
|
70
106
|
createWorkerRegex,
|
package/lib_js/build_tag.js
CHANGED
|
@@ -1,21 +1,81 @@
|
|
|
1
1
|
import { exec } from "node:child_process";
|
|
2
|
-
async function createBuildTag(flags) {
|
|
2
|
+
async function createBuildTag(projectDir, flags, buildTagSource) {
|
|
3
|
+
if (typeof buildTagSource === "function") {
|
|
4
|
+
buildTagSource = await buildTagSource({ production: flags.production });
|
|
5
|
+
}
|
|
6
|
+
if (typeof buildTagSource === "undefined" || buildTagSource === null) {
|
|
7
|
+
buildTagSource = await resolveExpressionDefault(projectDir);
|
|
8
|
+
}
|
|
9
|
+
if (typeof buildTagSource !== "string") {
|
|
10
|
+
throw TypeError("DankConfig.buildTag must resolve to a string expession");
|
|
11
|
+
}
|
|
12
|
+
const params = {};
|
|
3
13
|
const now = /* @__PURE__ */ new Date();
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
const paramPattern = new RegExp(/{{\s*(?<name>[a-z][A-Za-z]+)\s*}}/g);
|
|
15
|
+
let paramMatch;
|
|
16
|
+
let buildTag = buildTagSource;
|
|
17
|
+
let offset = 0;
|
|
18
|
+
while ((paramMatch = paramPattern.exec(buildTagSource)) != null) {
|
|
19
|
+
const paramName = paramMatch.groups.name.trim();
|
|
20
|
+
let paramValue;
|
|
21
|
+
if (params[paramName]) {
|
|
22
|
+
paramValue = params[paramName];
|
|
23
|
+
} else {
|
|
24
|
+
paramValue = params[paramName] = await getParamValue(projectDir, paramName, now, buildTagSource);
|
|
25
|
+
}
|
|
26
|
+
buildTag = buildTag.substring(0, paramMatch.index + offset) + paramValue + buildTag.substring(paramMatch.index + paramMatch[0].length + offset);
|
|
27
|
+
offset += paramValue.length - paramMatch[0].length;
|
|
28
|
+
}
|
|
29
|
+
const validate = /^[A-Za-z\d][A-Za-z\d-_\.]+$/;
|
|
30
|
+
if (!validate.test(buildTag)) {
|
|
31
|
+
throw Error(`build tag ${buildTag} does not pass pattern ${validate.source} validation`);
|
|
32
|
+
}
|
|
33
|
+
return buildTag;
|
|
34
|
+
}
|
|
35
|
+
async function resolveExpressionDefault(projectDir) {
|
|
36
|
+
const base = "{{ date }}-{{ timeMS }}";
|
|
37
|
+
const isGitRepo = await new Promise((res) => exec("git rev-parse --is-inside-work-tree", { cwd: projectDir }, (err) => res(!err)));
|
|
38
|
+
return isGitRepo ? base + "-{{ gitHash }}" : base;
|
|
39
|
+
}
|
|
40
|
+
async function getParamValue(projectDir, name, now, buildTagSource) {
|
|
41
|
+
switch (name) {
|
|
42
|
+
case "date":
|
|
43
|
+
return getDate(now);
|
|
44
|
+
case "gitHash":
|
|
45
|
+
try {
|
|
46
|
+
return await getGitHash(projectDir);
|
|
47
|
+
} catch (e) {
|
|
48
|
+
if (e === "not-repo") {
|
|
49
|
+
throw Error(`buildTag cannot use \`gitHash\` in \`${buildTagSource}\` outside of a git repository`);
|
|
50
|
+
} else {
|
|
51
|
+
throw e;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
case "timeMS":
|
|
55
|
+
return getTimeMS(now);
|
|
56
|
+
default:
|
|
57
|
+
throw Error(name + " is not a supported build tag param");
|
|
17
58
|
}
|
|
18
59
|
}
|
|
60
|
+
function getDate(now) {
|
|
61
|
+
return now.toISOString().substring(0, 10);
|
|
62
|
+
}
|
|
63
|
+
async function getGitHash(projectDir) {
|
|
64
|
+
return await new Promise((res, rej) => exec("git rev-parse --short HEAD", { cwd: projectDir }, (err, stdout, stderr) => {
|
|
65
|
+
if (err) {
|
|
66
|
+
if (stderr.includes("not a git repository")) {
|
|
67
|
+
rej("not-repo");
|
|
68
|
+
} else {
|
|
69
|
+
rej(err);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
res(stdout.trim());
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
function getTimeMS(now) {
|
|
76
|
+
const ms = now.getUTCMilliseconds() + now.getUTCSeconds() * 1e3 + now.getUTCMinutes() * 1e3 * 60 + now.getUTCHours() * 1e3 * 60 * 60;
|
|
77
|
+
return String(ms).padStart(8, "0");
|
|
78
|
+
}
|
|
19
79
|
export {
|
|
20
80
|
createBuildTag
|
|
21
81
|
};
|
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,10 +25,13 @@ async function loadConfig(mode, projectRootAbs) {
|
|
|
24
25
|
return c;
|
|
25
26
|
}
|
|
26
27
|
class DankConfigInternal {
|
|
28
|
+
#buildTag = null;
|
|
29
|
+
#buildTagBuilder;
|
|
27
30
|
#dirs;
|
|
28
31
|
#flags;
|
|
29
32
|
#mode;
|
|
30
33
|
#modulePath;
|
|
34
|
+
#serviceWorkerBuilder;
|
|
31
35
|
#dankPort = DEFAULT_DEV_PORT;
|
|
32
36
|
#esbuildPort = DEFAULT_ESBUILD_PORT;
|
|
33
37
|
#esbuild;
|
|
@@ -67,14 +71,26 @@ class DankConfigInternal {
|
|
|
67
71
|
get services() {
|
|
68
72
|
return this.#services;
|
|
69
73
|
}
|
|
74
|
+
get serviceWorkerBuilder() {
|
|
75
|
+
return this.#serviceWorkerBuilder;
|
|
76
|
+
}
|
|
77
|
+
buildTag() {
|
|
78
|
+
if (this.#buildTag === null) {
|
|
79
|
+
this.#buildTag = createBuildTag(this.#dirs.projectRootAbs, this.#flags, this.#buildTagBuilder);
|
|
80
|
+
}
|
|
81
|
+
return this.#buildTag;
|
|
82
|
+
}
|
|
70
83
|
async reload() {
|
|
71
84
|
const userConfig = await resolveConfig(this.#modulePath, resolveDankDetails(this.#mode, this.#flags));
|
|
85
|
+
this.#buildTag = null;
|
|
86
|
+
this.#buildTagBuilder = userConfig.buildTag;
|
|
72
87
|
this.#dankPort = resolveDankPort(this.#flags, userConfig);
|
|
73
88
|
this.#esbuildPort = resolveEsbuildPort(this.#flags, userConfig);
|
|
74
89
|
this.#esbuild = Object.freeze(userConfig.esbuild);
|
|
75
90
|
this.#pages = Object.freeze(normalizePages(userConfig.pages));
|
|
76
91
|
this.#devPages = Object.freeze(userConfig.devPages);
|
|
77
92
|
this.#services = Object.freeze(userConfig.services);
|
|
93
|
+
this.#serviceWorkerBuilder = userConfig.serviceWorker;
|
|
78
94
|
}
|
|
79
95
|
}
|
|
80
96
|
function resolveDankPort(flags, userConfig) {
|
|
@@ -99,10 +115,12 @@ function resolveDankDetails(mode, flags) {
|
|
|
99
115
|
function validateDankConfig(c) {
|
|
100
116
|
try {
|
|
101
117
|
validatePorts(c);
|
|
118
|
+
validateBuildTag(c.buildTag);
|
|
102
119
|
validatePages(c.pages);
|
|
103
120
|
validateDevPages(c.devPages);
|
|
104
121
|
validateDevServices(c.services);
|
|
105
122
|
validateEsbuildConfig(c.esbuild);
|
|
123
|
+
validateServiceWorker(c.serviceWorker);
|
|
106
124
|
} catch (e) {
|
|
107
125
|
throw e;
|
|
108
126
|
}
|
|
@@ -119,6 +137,31 @@ function validatePorts(c) {
|
|
|
119
137
|
}
|
|
120
138
|
}
|
|
121
139
|
}
|
|
140
|
+
function validateBuildTag(buildTag) {
|
|
141
|
+
if (buildTag === null) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
switch (typeof buildTag) {
|
|
145
|
+
case "undefined":
|
|
146
|
+
case "string":
|
|
147
|
+
case "function":
|
|
148
|
+
return;
|
|
149
|
+
default:
|
|
150
|
+
throw Error("DankConfig.buildTag must be a string or function");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function validateServiceWorker(serviceWorker) {
|
|
154
|
+
if (serviceWorker === null) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
switch (typeof serviceWorker) {
|
|
158
|
+
case "undefined":
|
|
159
|
+
case "function":
|
|
160
|
+
return;
|
|
161
|
+
default:
|
|
162
|
+
throw Error("DankConfig.serviceWorker must be a function");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
122
165
|
function validateEsbuildConfig(esbuild) {
|
|
123
166
|
if (esbuild?.loaders !== null && typeof esbuild?.loaders !== "undefined") {
|
|
124
167
|
if (typeof esbuild.loaders !== "object") {
|
package/lib_js/dank.js
CHANGED
package/lib_js/esbuild.js
CHANGED
|
@@ -44,6 +44,7 @@ async function esbuildWorkers(r, define, entryPoints) {
|
|
|
44
44
|
function commonBuildOptions(r) {
|
|
45
45
|
const p = workersPlugin(r.buildRegistry());
|
|
46
46
|
return {
|
|
47
|
+
absWorkingDir: r.config.dirs.projectRootAbs,
|
|
47
48
|
assetNames: "assets/[name]-[hash]",
|
|
48
49
|
bundle: true,
|
|
49
50
|
format: "esm",
|
package/lib_js/http.js
CHANGED
|
@@ -92,9 +92,9 @@ function createLogWrapper(handler) {
|
|
|
92
92
|
}
|
|
93
93
|
function createBuiltDistFilesFetcher(dirs, manifest) {
|
|
94
94
|
return (url, _headers, res, notFound) => {
|
|
95
|
-
if (manifest.pageUrls.
|
|
95
|
+
if (manifest.pageUrls.includes(url.pathname)) {
|
|
96
96
|
streamFile(join(dirs.projectRootAbs, dirs.buildDist, url.pathname, "index.html"), res);
|
|
97
|
-
} else if (manifest.files.
|
|
97
|
+
} else if (manifest.files.includes(url.pathname)) {
|
|
98
98
|
streamFile(join(dirs.projectRootAbs, dirs.buildDist, url.pathname), res);
|
|
99
99
|
} else {
|
|
100
100
|
notFound();
|
package/lib_js/public.js
CHANGED
|
@@ -36,7 +36,7 @@ async function recursiveCopyAssets(dirs, dir = "") {
|
|
|
36
36
|
madeDir = true;
|
|
37
37
|
}
|
|
38
38
|
await copyFile(join(listingDir, p), join(to, p));
|
|
39
|
-
copied.push(
|
|
39
|
+
copied.push(`/${join(dir, p).replaceAll("\\", "/")}`);
|
|
40
40
|
}
|
|
41
41
|
} catch (e) {
|
|
42
42
|
console.error("stat error", e);
|