@eighty4/dank 0.0.4 → 0.0.5-1
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/config.ts +3 -2
- package/lib/developer.ts +83 -26
- package/lib/dirs.ts +28 -5
- package/lib/esbuild.ts +9 -3
- package/lib/html.ts +2 -1
- package/lib/registry.ts +4 -6
- package/lib/serve.ts +8 -1
- package/lib/services.ts +60 -34
- package/lib_js/config.js +1 -1
- package/lib_js/dirs.js +22 -4
- package/lib_js/esbuild.js +5 -2
- package/lib_js/html.js +2 -1
- package/lib_js/registry.js +3 -3
- package/lib_js/serve.js +1 -1
- package/lib_js/services.js +46 -20
- package/package.json +2 -2
package/lib/config.ts
CHANGED
|
@@ -45,14 +45,15 @@ export async function loadConfig(
|
|
|
45
45
|
throw Error()
|
|
46
46
|
}
|
|
47
47
|
const modulePath = resolve(projectRootAbs, DEFAULT_CONFIG_PATH)
|
|
48
|
+
const dirs = await defaultProjectDirs(projectRootAbs)
|
|
48
49
|
LOG({
|
|
49
50
|
realm: 'config',
|
|
50
51
|
message: 'loading config module',
|
|
51
52
|
data: {
|
|
53
|
+
dirs,
|
|
52
54
|
modulePath,
|
|
53
55
|
},
|
|
54
56
|
})
|
|
55
|
-
const dirs = await defaultProjectDirs(projectRootAbs)
|
|
56
57
|
const c = new DankConfigInternal(mode, modulePath, dirs)
|
|
57
58
|
await c.reload()
|
|
58
59
|
return c
|
|
@@ -149,7 +150,7 @@ async function resolveConfig(
|
|
|
149
150
|
modulePath: string,
|
|
150
151
|
details: DankDetails,
|
|
151
152
|
): Promise<DankConfig> {
|
|
152
|
-
const module = await import(
|
|
153
|
+
const module = await import(`file:${modulePath}?${Date.now()}`)
|
|
153
154
|
const c: Partial<DankConfig> =
|
|
154
155
|
typeof module.default === 'function'
|
|
155
156
|
? await module.default(details)
|
package/lib/developer.ts
CHANGED
|
@@ -32,44 +32,101 @@ export type LogEvent = {
|
|
|
32
32
|
|
|
33
33
|
type LogEventData =
|
|
34
34
|
| LogEventDatum
|
|
35
|
-
|
|
|
36
|
-
|
|
|
37
|
-
|
|
|
35
|
+
| LogEventDataArray
|
|
36
|
+
| LogEventDataRecord
|
|
37
|
+
| LogEventDataSet
|
|
38
38
|
|
|
39
39
|
type LogEventDatum = boolean | number | string | null | undefined
|
|
40
40
|
|
|
41
|
+
type LogEventDataArray = Array<LogEventDatum> | Array<LogEventDataRecord>
|
|
42
|
+
|
|
43
|
+
type LogEventDataRecord = Record<
|
|
44
|
+
string,
|
|
45
|
+
LogEventDatum | LogEventDataArray | LogEventDataSet
|
|
46
|
+
>
|
|
47
|
+
|
|
48
|
+
type LogEventDataSet = Set<LogEventDatum>
|
|
49
|
+
|
|
41
50
|
function toStringLogEvent(logEvent: LogEvent): string {
|
|
42
51
|
const when = new Date().toISOString()
|
|
43
52
|
const message = `[${logEvent.realm}] ${logEvent.message}\n${when}\n`
|
|
44
|
-
if (
|
|
53
|
+
if (logEvent.data) {
|
|
54
|
+
const data: string = Object.keys(logEvent.data)
|
|
55
|
+
.sort()
|
|
56
|
+
.map(key => toStringData(key, logEvent.data![key]))
|
|
57
|
+
.join('')
|
|
58
|
+
return `${message}\n${data}`
|
|
59
|
+
} else {
|
|
45
60
|
return message
|
|
46
61
|
}
|
|
47
|
-
let data = ''
|
|
48
|
-
for (const k of Object.keys(logEvent.data).sort()) {
|
|
49
|
-
data += `\n ${k} = ${toStringData(logEvent.data[k])}`
|
|
50
|
-
}
|
|
51
|
-
return `${message}${data}\n`
|
|
52
62
|
}
|
|
53
63
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
64
|
+
const PAD = ' '
|
|
65
|
+
const nextIndent = (pad: string) => pad + PAD
|
|
66
|
+
|
|
67
|
+
function toStringData(
|
|
68
|
+
key: string,
|
|
69
|
+
data: LogEventData,
|
|
70
|
+
pad: string = PAD,
|
|
71
|
+
): string {
|
|
72
|
+
const prepend = `${pad}${key} = `
|
|
73
|
+
if (isDataAbsentOrScalar(data)) {
|
|
74
|
+
return `${prepend}${toStringDatum(data)}`
|
|
57
75
|
}
|
|
58
|
-
if (
|
|
59
|
-
|
|
60
|
-
typeof datum === 'object' &&
|
|
61
|
-
datum.constructor.name === 'Object'
|
|
62
|
-
) {
|
|
63
|
-
datum = Object.entries(datum).map(([k, v]) => `${k} = ${v}`)
|
|
76
|
+
if (data instanceof Set) {
|
|
77
|
+
data = Array.from(data)
|
|
64
78
|
}
|
|
65
|
-
if (Array.isArray(
|
|
66
|
-
|
|
67
|
-
return '[]'
|
|
68
|
-
} else {
|
|
69
|
-
return `[\n ${datum.join('\n ')}\n ]`
|
|
70
|
-
}
|
|
79
|
+
if (Array.isArray(data)) {
|
|
80
|
+
return `${prepend}${toStringArray(data, pad)}`
|
|
71
81
|
} else {
|
|
72
|
-
return `${
|
|
82
|
+
return `${prepend}${toStringRecord(data, pad)}`
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function toStringDatum(datum: LogEventDatum): string {
|
|
87
|
+
return `${datum}\n`
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function toStringArray(array: LogEventDataArray, padEnding: string): string {
|
|
91
|
+
if (array.length === 0) {
|
|
92
|
+
return '[]'
|
|
93
|
+
}
|
|
94
|
+
const padIndent = nextIndent(padEnding)
|
|
95
|
+
const content = array
|
|
96
|
+
.map(datum => {
|
|
97
|
+
if (isDataAbsentOrScalar(datum)) {
|
|
98
|
+
return toStringDatum(datum)
|
|
99
|
+
} else {
|
|
100
|
+
return toStringRecord(datum, padIndent)
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
.join(padIndent)
|
|
104
|
+
return `[\n${padIndent}${content}${padEnding}]\n`
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function toStringRecord(record: LogEventDataRecord, padEnding: string): string {
|
|
108
|
+
const keys = Object.keys(record)
|
|
109
|
+
if (keys.length === 0) {
|
|
110
|
+
return '{}'
|
|
111
|
+
}
|
|
112
|
+
const padIndent = nextIndent(padEnding)
|
|
113
|
+
const content = keys
|
|
114
|
+
.map(key => toStringData(key, record[key], padIndent))
|
|
115
|
+
.join('')
|
|
116
|
+
return `{\n${content}${padEnding}}\n`
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function isDataAbsentOrScalar(
|
|
120
|
+
data: LogEventData,
|
|
121
|
+
): data is undefined | null | string | boolean | number {
|
|
122
|
+
switch (typeof data) {
|
|
123
|
+
case 'undefined':
|
|
124
|
+
case 'string':
|
|
125
|
+
case 'boolean':
|
|
126
|
+
case 'number':
|
|
127
|
+
return true
|
|
128
|
+
default:
|
|
129
|
+
return data === null
|
|
73
130
|
}
|
|
74
131
|
}
|
|
75
132
|
|
|
@@ -79,7 +136,7 @@ function logToConsoleAndFile(out: string) {
|
|
|
79
136
|
}
|
|
80
137
|
|
|
81
138
|
function logToConsole(out: string) {
|
|
82
|
-
console.log(
|
|
139
|
+
console.log(out)
|
|
83
140
|
}
|
|
84
141
|
|
|
85
142
|
function logToFile(out: string) {
|
package/lib/dirs.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { realpath } from 'node:fs/promises'
|
|
2
2
|
import { dirname, isAbsolute, join, resolve } from 'node:path'
|
|
3
|
-
import { cwd } from 'node:process'
|
|
4
3
|
|
|
5
4
|
export type DankDirectories = {
|
|
6
5
|
buildRoot: string
|
|
@@ -17,9 +16,7 @@ export type DankDirectories = {
|
|
|
17
16
|
export async function defaultProjectDirs(
|
|
18
17
|
projectRootAbs: string,
|
|
19
18
|
): Promise<Readonly<DankDirectories>> {
|
|
20
|
-
if (!projectRootAbs) {
|
|
21
|
-
projectRootAbs = cwd()
|
|
22
|
-
} else if (!isAbsolute(projectRootAbs)) {
|
|
19
|
+
if (!isAbsolute(projectRootAbs)) {
|
|
23
20
|
throw Error()
|
|
24
21
|
}
|
|
25
22
|
const projectResolved = await realpath(projectRootAbs)
|
|
@@ -40,9 +37,17 @@ export async function defaultProjectDirs(
|
|
|
40
37
|
export type ResolveError = 'outofbounds'
|
|
41
38
|
|
|
42
39
|
export class Resolver {
|
|
40
|
+
static create(dirs: DankDirectories): Resolver {
|
|
41
|
+
if (process.platform === 'win32') {
|
|
42
|
+
return new WindowsResolver(dirs)
|
|
43
|
+
} else {
|
|
44
|
+
return new Resolver(dirs)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
43
48
|
#dirs: DankDirectories
|
|
44
49
|
|
|
45
|
-
constructor(dirs: DankDirectories) {
|
|
50
|
+
protected constructor(dirs: DankDirectories) {
|
|
46
51
|
this.#dirs = dirs
|
|
47
52
|
}
|
|
48
53
|
|
|
@@ -68,6 +73,10 @@ export class Resolver {
|
|
|
68
73
|
return this.isProjectSubpathInPagesDir(join(this.#dirs.pages, p))
|
|
69
74
|
}
|
|
70
75
|
|
|
76
|
+
projectPathFromAbsolute(p: string) {
|
|
77
|
+
return p.replace(this.#dirs.projectRootAbs, '').substring(1)
|
|
78
|
+
}
|
|
79
|
+
|
|
71
80
|
// resolve a pages subpath from a resource within the pages directory by a relative href
|
|
72
81
|
// `from` is expected to be a pages resource fs path starting with `pages/` and ending with filename
|
|
73
82
|
// the result will be a pages subpath and will not have the pages dir prefix
|
|
@@ -81,3 +90,17 @@ export class Resolver {
|
|
|
81
90
|
}
|
|
82
91
|
}
|
|
83
92
|
}
|
|
93
|
+
|
|
94
|
+
class WindowsResolver extends Resolver {
|
|
95
|
+
constructor(dirs: DankDirectories) {
|
|
96
|
+
super(dirs)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
projectPathFromAbsolute(p: string): string {
|
|
100
|
+
return super.projectPathFromAbsolute(p).replaceAll('\\', '/')
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
resolveHrefInPagesDir(from: string, href: string): string | ResolveError {
|
|
104
|
+
return super.resolveHrefInPagesDir(from, href).replaceAll('\\', '/')
|
|
105
|
+
}
|
|
106
|
+
}
|
package/lib/esbuild.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises'
|
|
2
|
+
// import { sep as windowsSep } from 'node:path/win32'
|
|
3
|
+
// import { sep as posixSep } from 'node:path/posix'
|
|
2
4
|
import esbuild, {
|
|
3
5
|
type BuildContext,
|
|
4
6
|
type BuildOptions,
|
|
@@ -123,6 +125,7 @@ export function workersPlugin(r: BuildRegistry): Plugin {
|
|
|
123
125
|
let contents = await readFile(args.path, 'utf8')
|
|
124
126
|
let offset = 0
|
|
125
127
|
let errors: Array<PartialMessage> | undefined = undefined
|
|
128
|
+
let clientScript: string | undefined = undefined
|
|
126
129
|
for (const workerCtorMatch of contents.matchAll(
|
|
127
130
|
WORKER_CTOR_REGEX,
|
|
128
131
|
)) {
|
|
@@ -143,9 +146,11 @@ export function workersPlugin(r: BuildRegistry): Plugin {
|
|
|
143
146
|
if (isIndexCommented(contents, workerCtorMatch.index)) {
|
|
144
147
|
continue
|
|
145
148
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
+
if (!clientScript) {
|
|
150
|
+
clientScript = r.resolver.projectPathFromAbsolute(
|
|
151
|
+
args.path,
|
|
152
|
+
)
|
|
153
|
+
}
|
|
149
154
|
const workerUrl = workerCtorMatch.groups!.url.substring(
|
|
150
155
|
1,
|
|
151
156
|
workerCtorMatch.groups!.url.length - 1,
|
|
@@ -174,6 +179,7 @@ export function workersPlugin(r: BuildRegistry): Plugin {
|
|
|
174
179
|
const workerUrlPlaceholder = workerEntryPoint
|
|
175
180
|
.replace(/^pages/, '')
|
|
176
181
|
.replace(/\.(t|m?j)s$/, '.js')
|
|
182
|
+
.replaceAll('\\', '/')
|
|
177
183
|
const workerCtorReplacement = `new ${workerCtor}('${workerUrlPlaceholder}'${workerCtorMatch.groups!.end}`
|
|
178
184
|
contents =
|
|
179
185
|
contents.substring(0, workerCtorMatch.index + offset) +
|
package/lib/html.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import EventEmitter from 'node:events'
|
|
2
2
|
import { readFile } from 'node:fs/promises'
|
|
3
|
-
import { dirname, isAbsolute,
|
|
3
|
+
import { dirname, isAbsolute, relative, resolve } from 'node:path'
|
|
4
|
+
import { join } from 'node:path/posix'
|
|
4
5
|
import { extname } from 'node:path/posix'
|
|
5
6
|
import {
|
|
6
7
|
defaultTreeAdapter,
|
package/lib/registry.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import EventEmitter from 'node:events'
|
|
2
2
|
import { writeFile } from 'node:fs/promises'
|
|
3
|
-
import { join } from 'node:path'
|
|
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
7
|
import { LOG } from './developer.ts'
|
|
7
8
|
import { Resolver, type DankDirectories } from './dirs.ts'
|
|
8
9
|
import type { EntryPoint } from './esbuild.ts'
|
|
9
10
|
import { HtmlEntrypoint } from './html.ts'
|
|
10
|
-
import type { PageMapping } from './dank.ts'
|
|
11
11
|
|
|
12
12
|
// summary of a website build
|
|
13
13
|
export type WebsiteManifest = {
|
|
@@ -80,7 +80,7 @@ export class WebsiteRegistry extends EventEmitter<WebsiteRegistryEvents> {
|
|
|
80
80
|
constructor(config: ResolvedDankConfig) {
|
|
81
81
|
super()
|
|
82
82
|
this.#c = config
|
|
83
|
-
this.#resolver =
|
|
83
|
+
this.#resolver = Resolver.create(config.dirs)
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
get config(): ResolvedDankConfig {
|
|
@@ -257,6 +257,7 @@ export class WebsiteRegistry extends EventEmitter<WebsiteRegistryEvents> {
|
|
|
257
257
|
this.#setWebpageBundles(html.url, entrypoints),
|
|
258
258
|
)
|
|
259
259
|
this.emit('webpage', html)
|
|
260
|
+
html.emit('change')
|
|
260
261
|
}
|
|
261
262
|
|
|
262
263
|
#configPageUpdate(urlPath: `/${string}`, mapping: PageMapping) {
|
|
@@ -399,9 +400,7 @@ export class BuildRegistry {
|
|
|
399
400
|
return this.#resolver
|
|
400
401
|
}
|
|
401
402
|
|
|
402
|
-
// resolve web worker imported by a webpage module
|
|
403
403
|
addWorker(worker: Omit<WorkerManifest, 'dependentEntryPoint'>) {
|
|
404
|
-
// todo normalize path
|
|
405
404
|
if (!this.#workers) {
|
|
406
405
|
this.#workers = [worker]
|
|
407
406
|
} else {
|
|
@@ -433,7 +432,6 @@ export class BuildRegistry {
|
|
|
433
432
|
}
|
|
434
433
|
}
|
|
435
434
|
}
|
|
436
|
-
|
|
437
435
|
this.#onComplete({
|
|
438
436
|
bundles,
|
|
439
437
|
workers,
|
package/lib/serve.ts
CHANGED
|
@@ -66,7 +66,14 @@ async function startDevMode(signal: AbortSignal) {
|
|
|
66
66
|
await mkdir(c.dirs.buildWatch, { recursive: true })
|
|
67
67
|
let buildContext: BuildContextState = null
|
|
68
68
|
|
|
69
|
-
watch('dank.config.ts', signal, async
|
|
69
|
+
watch('dank.config.ts', signal, async filename => {
|
|
70
|
+
LOG({
|
|
71
|
+
realm: 'serve',
|
|
72
|
+
message: 'config watch event',
|
|
73
|
+
data: {
|
|
74
|
+
file: filename,
|
|
75
|
+
},
|
|
76
|
+
})
|
|
70
77
|
try {
|
|
71
78
|
await c.reload()
|
|
72
79
|
} catch (ignore) {
|
package/lib/services.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type ChildProcess,
|
|
3
|
+
type ChildProcessWithoutNullStreams,
|
|
4
|
+
execSync,
|
|
5
|
+
spawn,
|
|
6
|
+
} from 'node:child_process'
|
|
2
7
|
import { basename, isAbsolute, resolve } from 'node:path'
|
|
3
8
|
import type { DevService, ResolvedDankConfig } from './config.ts'
|
|
4
9
|
|
|
@@ -15,6 +20,18 @@ export type HttpService = NonNullable<DevService['http']>
|
|
|
15
20
|
// up to date representation of dank.config.ts services
|
|
16
21
|
const running: Array<{ s: DevService; process: ChildProcess | null }> = []
|
|
17
22
|
|
|
23
|
+
// on Windows process.kill() and AbortSignal will not kill the process spawned with cmd.exe
|
|
24
|
+
if (process.platform === 'win32') {
|
|
25
|
+
process.once('SIGINT', () => process.exit())
|
|
26
|
+
process.once('exit', () => {
|
|
27
|
+
for (const { process } of running) {
|
|
28
|
+
if (process) {
|
|
29
|
+
execSync(`taskkill /pid ${process.pid} /T /F`)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
18
35
|
let signal: AbortSignal
|
|
19
36
|
|
|
20
37
|
// batch of services that must be stopped before starting new services
|
|
@@ -140,34 +157,23 @@ function matchingConfig(a: DevService, b: DevService): boolean {
|
|
|
140
157
|
|
|
141
158
|
function startService(s: DevService): ChildProcess {
|
|
142
159
|
opPrint(s, 'starting')
|
|
143
|
-
const
|
|
144
|
-
const cmd = splitCmdAndArgs[0]
|
|
145
|
-
const args = splitCmdAndArgs.length === 1 ? [] : splitCmdAndArgs.slice(1)
|
|
146
|
-
const spawned = spawn(cmd, args, {
|
|
147
|
-
cwd: resolveCwd(s.cwd),
|
|
148
|
-
env: s.env,
|
|
149
|
-
signal,
|
|
150
|
-
detached: false,
|
|
151
|
-
shell: false,
|
|
152
|
-
})
|
|
160
|
+
const spawned = spawnService(s)
|
|
153
161
|
|
|
154
|
-
const stdoutLabel = logLabel(s
|
|
162
|
+
const stdoutLabel = logLabel(s, 32)
|
|
155
163
|
spawned.stdout.on('data', chunk => printChunk(stdoutLabel, chunk))
|
|
156
164
|
|
|
157
|
-
const stderrLabel = logLabel(s
|
|
165
|
+
const stderrLabel = logLabel(s, 31)
|
|
158
166
|
spawned.stderr.on('data', chunk => printChunk(stderrLabel, chunk))
|
|
159
167
|
|
|
160
168
|
spawned.on('error', e => {
|
|
161
|
-
if (e.name !== 'AbortError') {
|
|
162
|
-
const cause =
|
|
163
|
-
'code' in e && e.code === 'ENOENT'
|
|
164
|
-
? 'program not found'
|
|
165
|
-
: e.message
|
|
166
|
-
opPrint(s, 'error: ' + cause)
|
|
167
|
-
}
|
|
168
169
|
removeFromRunning(s)
|
|
170
|
+
if (e.name === 'AbortError') {
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
const cause =
|
|
174
|
+
'code' in e && e.code === 'ENOENT' ? 'program not found' : e.message
|
|
175
|
+
opPrint(s, 'error: ' + cause)
|
|
169
176
|
})
|
|
170
|
-
|
|
171
177
|
spawned.on('exit', () => {
|
|
172
178
|
opPrint(s, 'exited')
|
|
173
179
|
removeFromRunning(s)
|
|
@@ -176,6 +182,31 @@ function startService(s: DevService): ChildProcess {
|
|
|
176
182
|
return spawned
|
|
177
183
|
}
|
|
178
184
|
|
|
185
|
+
function spawnService(s: DevService): ChildProcessWithoutNullStreams {
|
|
186
|
+
const splitCmdAndArgs = s.command.split(/\s+/)
|
|
187
|
+
const program = splitCmdAndArgs[0]
|
|
188
|
+
const args = splitCmdAndArgs.length === 1 ? [] : splitCmdAndArgs.slice(1)
|
|
189
|
+
const env = s.env ? { ...process.env, ...s.env } : undefined
|
|
190
|
+
const cwd = resolveCwd(s.cwd)
|
|
191
|
+
if (process.platform === 'win32') {
|
|
192
|
+
return spawn('cmd', ['/c', program, ...args], {
|
|
193
|
+
cwd,
|
|
194
|
+
env,
|
|
195
|
+
detached: false,
|
|
196
|
+
shell: false,
|
|
197
|
+
windowsHide: true,
|
|
198
|
+
})
|
|
199
|
+
} else {
|
|
200
|
+
return spawn(program, args, {
|
|
201
|
+
cwd,
|
|
202
|
+
env,
|
|
203
|
+
signal,
|
|
204
|
+
detached: false,
|
|
205
|
+
shell: false,
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
179
210
|
function removeFromRunning(s: DevService) {
|
|
180
211
|
for (let i = 0; i < running.length; i++) {
|
|
181
212
|
if (matchingConfig(running[i].s, s)) {
|
|
@@ -227,18 +258,13 @@ function opLabel(s: DevService) {
|
|
|
227
258
|
return `\`${s.cwd ? s.cwd + ' ' : ''}${s.command}\``
|
|
228
259
|
}
|
|
229
260
|
|
|
230
|
-
function logLabel(
|
|
231
|
-
cwd
|
|
232
|
-
cmd: string,
|
|
233
|
-
args: Array<string>,
|
|
234
|
-
ansiColor: number,
|
|
235
|
-
): string {
|
|
236
|
-
cwd = !cwd
|
|
261
|
+
function logLabel(s: DevService, ansiColor: number): string {
|
|
262
|
+
s.cwd = !s.cwd
|
|
237
263
|
? './'
|
|
238
|
-
: cwd.startsWith('/')
|
|
239
|
-
? `/.../${basename(cwd)}`
|
|
240
|
-
: cwd.startsWith('.')
|
|
241
|
-
? cwd
|
|
242
|
-
: `./${cwd}`
|
|
243
|
-
return `\u001b[${ansiColor}m[\u001b[1m${
|
|
264
|
+
: s.cwd.startsWith('/')
|
|
265
|
+
? `/.../${basename(s.cwd)}`
|
|
266
|
+
: s.cwd.startsWith('.')
|
|
267
|
+
? s.cwd
|
|
268
|
+
: `./${s.cwd}`
|
|
269
|
+
return `\u001b[${ansiColor}m[\u001b[1m${s.command}\u001b[22m \u001b[2;3m${s.cwd}\u001b[22;23m]\u001b[0m`
|
|
244
270
|
}
|
package/lib_js/config.js
CHANGED
|
@@ -84,7 +84,7 @@ function resolveEsbuildPort(flags, userConfig) {
|
|
|
84
84
|
return flags.esbuildPort || userConfig.esbuild?.port || DEFAULT_ESBUILD_PORT;
|
|
85
85
|
}
|
|
86
86
|
async function resolveConfig(modulePath, details) {
|
|
87
|
-
const module = await import(__rewriteRelativeImportExtension(
|
|
87
|
+
const module = await import(__rewriteRelativeImportExtension(`file:${modulePath}?${Date.now()}`));
|
|
88
88
|
const c = typeof module.default === "function" ? await module.default(details) : module.default;
|
|
89
89
|
validateDankConfig(c);
|
|
90
90
|
return c;
|
package/lib_js/dirs.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { realpath } from "node:fs/promises";
|
|
2
2
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
3
|
-
import { cwd } from "node:process";
|
|
4
3
|
async function defaultProjectDirs(projectRootAbs) {
|
|
5
|
-
if (!projectRootAbs) {
|
|
6
|
-
projectRootAbs = cwd();
|
|
7
|
-
} else if (!isAbsolute(projectRootAbs)) {
|
|
4
|
+
if (!isAbsolute(projectRootAbs)) {
|
|
8
5
|
throw Error();
|
|
9
6
|
}
|
|
10
7
|
const projectResolved = await realpath(projectRootAbs);
|
|
@@ -22,6 +19,13 @@ async function defaultProjectDirs(projectRootAbs) {
|
|
|
22
19
|
});
|
|
23
20
|
}
|
|
24
21
|
class Resolver {
|
|
22
|
+
static create(dirs) {
|
|
23
|
+
if (process.platform === "win32") {
|
|
24
|
+
return new WindowsResolver(dirs);
|
|
25
|
+
} else {
|
|
26
|
+
return new Resolver(dirs);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
25
29
|
#dirs;
|
|
26
30
|
constructor(dirs) {
|
|
27
31
|
this.#dirs = dirs;
|
|
@@ -42,6 +46,9 @@ class Resolver {
|
|
|
42
46
|
isPagesSubpathInPagesDir(p) {
|
|
43
47
|
return this.isProjectSubpathInPagesDir(join(this.#dirs.pages, p));
|
|
44
48
|
}
|
|
49
|
+
projectPathFromAbsolute(p) {
|
|
50
|
+
return p.replace(this.#dirs.projectRootAbs, "").substring(1);
|
|
51
|
+
}
|
|
45
52
|
// resolve a pages subpath from a resource within the pages directory by a relative href
|
|
46
53
|
// `from` is expected to be a pages resource fs path starting with `pages/` and ending with filename
|
|
47
54
|
// the result will be a pages subpath and will not have the pages dir prefix
|
|
@@ -55,6 +62,17 @@ class Resolver {
|
|
|
55
62
|
}
|
|
56
63
|
}
|
|
57
64
|
}
|
|
65
|
+
class WindowsResolver extends Resolver {
|
|
66
|
+
constructor(dirs) {
|
|
67
|
+
super(dirs);
|
|
68
|
+
}
|
|
69
|
+
projectPathFromAbsolute(p) {
|
|
70
|
+
return super.projectPathFromAbsolute(p).replaceAll("\\", "/");
|
|
71
|
+
}
|
|
72
|
+
resolveHrefInPagesDir(from, href) {
|
|
73
|
+
return super.resolveHrefInPagesDir(from, href).replaceAll("\\", "/");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
58
76
|
export {
|
|
59
77
|
Resolver,
|
|
60
78
|
defaultProjectDirs
|
package/lib_js/esbuild.js
CHANGED
|
@@ -83,6 +83,7 @@ function workersPlugin(r) {
|
|
|
83
83
|
let contents = await readFile(args.path, "utf8");
|
|
84
84
|
let offset = 0;
|
|
85
85
|
let errors = void 0;
|
|
86
|
+
let clientScript = void 0;
|
|
86
87
|
for (const workerCtorMatch of contents.matchAll(WORKER_CTOR_REGEX)) {
|
|
87
88
|
if (!WORKER_URL_REGEX.test(workerCtorMatch.groups.url)) {
|
|
88
89
|
if (!errors)
|
|
@@ -93,7 +94,9 @@ function workersPlugin(r) {
|
|
|
93
94
|
if (isIndexCommented(contents, workerCtorMatch.index)) {
|
|
94
95
|
continue;
|
|
95
96
|
}
|
|
96
|
-
|
|
97
|
+
if (!clientScript) {
|
|
98
|
+
clientScript = r.resolver.projectPathFromAbsolute(args.path);
|
|
99
|
+
}
|
|
97
100
|
const workerUrl = workerCtorMatch.groups.url.substring(1, workerCtorMatch.groups.url.length - 1);
|
|
98
101
|
const workerEntryPoint = r.resolver.resolveHrefInPagesDir(clientScript, workerUrl);
|
|
99
102
|
if (workerEntryPoint === "outofbounds") {
|
|
@@ -103,7 +106,7 @@ function workersPlugin(r) {
|
|
|
103
106
|
continue;
|
|
104
107
|
}
|
|
105
108
|
const workerCtor = workerCtorMatch.groups.ctor;
|
|
106
|
-
const workerUrlPlaceholder = workerEntryPoint.replace(/^pages/, "").replace(/\.(t|m?j)s$/, ".js");
|
|
109
|
+
const workerUrlPlaceholder = workerEntryPoint.replace(/^pages/, "").replace(/\.(t|m?j)s$/, ".js").replaceAll("\\", "/");
|
|
107
110
|
const workerCtorReplacement = `new ${workerCtor}('${workerUrlPlaceholder}'${workerCtorMatch.groups.end}`;
|
|
108
111
|
contents = contents.substring(0, workerCtorMatch.index + offset) + workerCtorReplacement + contents.substring(workerCtorMatch.index + workerCtorMatch[0].length + offset);
|
|
109
112
|
offset += workerCtorReplacement.length - workerCtorMatch[0].length;
|
package/lib_js/html.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import EventEmitter from "node:events";
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
|
-
import { dirname, isAbsolute,
|
|
3
|
+
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
4
|
+
import { join } from "node:path/posix";
|
|
4
5
|
import { extname } from "node:path/posix";
|
|
5
6
|
import { defaultTreeAdapter, parse, parseFragment, serialize } from "parse5";
|
|
6
7
|
import { DankError } from "./errors.js";
|
package/lib_js/registry.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import EventEmitter from "node:events";
|
|
2
2
|
import { writeFile } from "node:fs/promises";
|
|
3
|
-
import { join } from "node:path";
|
|
3
|
+
import { join } from "node:path/posix";
|
|
4
4
|
import { Resolver } from "./dirs.js";
|
|
5
5
|
import { HtmlEntrypoint } from "./html.js";
|
|
6
6
|
class WebsiteRegistry extends EventEmitter {
|
|
@@ -17,7 +17,7 @@ class WebsiteRegistry extends EventEmitter {
|
|
|
17
17
|
constructor(config) {
|
|
18
18
|
super();
|
|
19
19
|
this.#c = config;
|
|
20
|
-
this.#resolver =
|
|
20
|
+
this.#resolver = Resolver.create(config.dirs);
|
|
21
21
|
}
|
|
22
22
|
get config() {
|
|
23
23
|
return this.#c;
|
|
@@ -132,6 +132,7 @@ class WebsiteRegistry extends EventEmitter {
|
|
|
132
132
|
};
|
|
133
133
|
html.on("entrypoints", (entrypoints) => this.#setWebpageBundles(html.url, entrypoints));
|
|
134
134
|
this.emit("webpage", html);
|
|
135
|
+
html.emit("change");
|
|
135
136
|
}
|
|
136
137
|
#configPageUpdate(urlPath, mapping) {
|
|
137
138
|
const existingRegistration = this.#pages[urlPath];
|
|
@@ -218,7 +219,6 @@ class BuildRegistry {
|
|
|
218
219
|
get resolver() {
|
|
219
220
|
return this.#resolver;
|
|
220
221
|
}
|
|
221
|
-
// resolve web worker imported by a webpage module
|
|
222
222
|
addWorker(worker) {
|
|
223
223
|
if (!this.#workers) {
|
|
224
224
|
this.#workers = [worker];
|
package/lib_js/serve.js
CHANGED
|
@@ -36,7 +36,7 @@ async function startDevMode(signal) {
|
|
|
36
36
|
const registry = new WebsiteRegistry(c);
|
|
37
37
|
await mkdir(c.dirs.buildWatch, { recursive: true });
|
|
38
38
|
let buildContext = null;
|
|
39
|
-
watch("dank.config.ts", signal, async () => {
|
|
39
|
+
watch("dank.config.ts", signal, async (filename) => {
|
|
40
40
|
try {
|
|
41
41
|
await c.reload();
|
|
42
42
|
} catch (ignore) {
|
package/lib_js/services.js
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
1
|
+
import { execSync, spawn } from "node:child_process";
|
|
2
2
|
import { basename, isAbsolute, resolve } from "node:path";
|
|
3
3
|
const running = [];
|
|
4
|
+
if (process.platform === "win32") {
|
|
5
|
+
process.once("SIGINT", () => process.exit());
|
|
6
|
+
process.once("exit", () => {
|
|
7
|
+
for (const { process: process2 } of running) {
|
|
8
|
+
if (process2) {
|
|
9
|
+
execSync(`taskkill /pid ${process2.pid} /T /F`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
}
|
|
4
14
|
let signal;
|
|
5
15
|
let updating = null;
|
|
6
16
|
function startDevServices(services, _signal) {
|
|
@@ -111,26 +121,18 @@ function matchingConfig(a, b) {
|
|
|
111
121
|
}
|
|
112
122
|
function startService(s) {
|
|
113
123
|
opPrint(s, "starting");
|
|
114
|
-
const
|
|
115
|
-
const
|
|
116
|
-
const args = splitCmdAndArgs.length === 1 ? [] : splitCmdAndArgs.slice(1);
|
|
117
|
-
const spawned = spawn(cmd, args, {
|
|
118
|
-
cwd: resolveCwd(s.cwd),
|
|
119
|
-
env: s.env,
|
|
120
|
-
signal,
|
|
121
|
-
detached: false,
|
|
122
|
-
shell: false
|
|
123
|
-
});
|
|
124
|
-
const stdoutLabel = logLabel(s.cwd, cmd, args, 32);
|
|
124
|
+
const spawned = spawnService(s);
|
|
125
|
+
const stdoutLabel = logLabel(s, 32);
|
|
125
126
|
spawned.stdout.on("data", (chunk) => printChunk(stdoutLabel, chunk));
|
|
126
|
-
const stderrLabel = logLabel(s
|
|
127
|
+
const stderrLabel = logLabel(s, 31);
|
|
127
128
|
spawned.stderr.on("data", (chunk) => printChunk(stderrLabel, chunk));
|
|
128
129
|
spawned.on("error", (e) => {
|
|
129
|
-
if (e.name !== "AbortError") {
|
|
130
|
-
const cause = "code" in e && e.code === "ENOENT" ? "program not found" : e.message;
|
|
131
|
-
opPrint(s, "error: " + cause);
|
|
132
|
-
}
|
|
133
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);
|
|
134
136
|
});
|
|
135
137
|
spawned.on("exit", () => {
|
|
136
138
|
opPrint(s, "exited");
|
|
@@ -139,6 +141,30 @@ function startService(s) {
|
|
|
139
141
|
});
|
|
140
142
|
return spawned;
|
|
141
143
|
}
|
|
144
|
+
function spawnService(s) {
|
|
145
|
+
const splitCmdAndArgs = s.command.split(/\s+/);
|
|
146
|
+
const program = splitCmdAndArgs[0];
|
|
147
|
+
const args = splitCmdAndArgs.length === 1 ? [] : splitCmdAndArgs.slice(1);
|
|
148
|
+
const env = s.env ? { ...process.env, ...s.env } : void 0;
|
|
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
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
142
168
|
function removeFromRunning(s) {
|
|
143
169
|
for (let i = 0; i < running.length; i++) {
|
|
144
170
|
if (matchingConfig(running[i].s, s)) {
|
|
@@ -181,9 +207,9 @@ function opPrint(s, msg) {
|
|
|
181
207
|
function opLabel(s) {
|
|
182
208
|
return `\`${s.cwd ? s.cwd + " " : ""}${s.command}\``;
|
|
183
209
|
}
|
|
184
|
-
function logLabel(
|
|
185
|
-
cwd = !cwd ? "./" : cwd.startsWith("/") ? `/.../${basename(cwd)}` : cwd.startsWith(".") ? cwd : `./${cwd}`;
|
|
186
|
-
return `\x1B[${ansiColor}m[\x1B[1m${
|
|
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`;
|
|
187
213
|
}
|
|
188
214
|
export {
|
|
189
215
|
startDevServices,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eighty4/dank",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5-1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Adam McKee Bennett <adam.be.g84d@gmail.com>",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"build": "pnpm build:client && pnpm build:lib",
|
|
47
47
|
"build:client": "node scripts/build_client.ts",
|
|
48
48
|
"build:lib": "tsc && tsc -p tsconfig.exports.json",
|
|
49
|
-
"build:release": "pnpm build &&
|
|
49
|
+
"build:release": "pnpm build && node scripts/prepare_release.ts",
|
|
50
50
|
"fmt": "prettier --write .",
|
|
51
51
|
"fmtcheck": "prettier --check .",
|
|
52
52
|
"test": "node --test \"test/**/*.test.ts\"",
|