@eighty4/dank 0.0.1-4 → 0.0.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/README.md +20 -16
- package/client/esbuild.js +26 -15
- package/lib/config.ts +2 -1
- package/lib/dank.ts +8 -1
- package/lib/esbuild.ts +2 -0
- package/lib/http.ts +111 -29
- package/lib/serve.ts +255 -51
- package/lib/services.ts +156 -11
- package/lib_js/config.js +2 -1
- package/lib_js/dank.js +6 -0
- package/lib_js/esbuild.js +2 -0
- package/lib_js/http.js +89 -22
- package/lib_js/serve.js +211 -38
- package/lib_js/services.js +146 -10
- package/lib_types/dank.d.ts +1 -1
- package/package.json +1 -1
package/lib/serve.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { mkdir, readFile, rm } from 'node:fs/promises'
|
|
2
|
-
import { join, resolve } from 'node:path'
|
|
1
|
+
import { mkdir, readFile, rm, watch as _watch } from 'node:fs/promises'
|
|
2
|
+
import { extname, join, resolve } from 'node:path'
|
|
3
|
+
import type { BuildContext } from 'esbuild'
|
|
3
4
|
import { buildWebsite } from './build.ts'
|
|
5
|
+
import { loadConfig } from './config.ts'
|
|
4
6
|
import type { DankConfig } from './dank.ts'
|
|
5
7
|
import { createGlobalDefinitions } from './define.ts'
|
|
6
8
|
import { esbuildDevContext } from './esbuild.ts'
|
|
@@ -8,12 +10,10 @@ import { isPreviewBuild } from './flags.ts'
|
|
|
8
10
|
import { HtmlEntrypoint } from './html.ts'
|
|
9
11
|
import {
|
|
10
12
|
createBuiltDistFilesFetcher,
|
|
11
|
-
|
|
13
|
+
createDevServeFilesFetcher,
|
|
12
14
|
createWebServer,
|
|
13
|
-
type FrontendFetcher,
|
|
14
15
|
} from './http.ts'
|
|
15
|
-
import {
|
|
16
|
-
import { startDevServices } from './services.ts'
|
|
16
|
+
import { startDevServices, updateDevServices } from './services.ts'
|
|
17
17
|
|
|
18
18
|
const isPreview = isPreviewBuild()
|
|
19
19
|
|
|
@@ -25,66 +25,226 @@ const ESBUILD_PORT = 2999
|
|
|
25
25
|
|
|
26
26
|
export async function serveWebsite(c: DankConfig): Promise<never> {
|
|
27
27
|
await rm('build', { force: true, recursive: true })
|
|
28
|
-
let frontend: FrontendFetcher
|
|
29
28
|
if (isPreview) {
|
|
30
|
-
|
|
31
|
-
frontend = createBuiltDistFilesFetcher(dir, files)
|
|
29
|
+
await startPreviewMode(c)
|
|
32
30
|
} else {
|
|
33
|
-
const
|
|
34
|
-
|
|
31
|
+
const abortController = new AbortController()
|
|
32
|
+
await startDevMode(c, abortController.signal)
|
|
35
33
|
}
|
|
36
|
-
createWebServer(PORT, frontend).listen(PORT)
|
|
37
|
-
console.log(
|
|
38
|
-
isPreview ? 'preview' : 'dev server',
|
|
39
|
-
`is live at http://127.0.0.1:${PORT}`,
|
|
40
|
-
)
|
|
41
|
-
startDevServices(c)
|
|
42
34
|
return new Promise(() => {})
|
|
43
35
|
}
|
|
44
36
|
|
|
45
|
-
async function
|
|
37
|
+
async function startPreviewMode(c: DankConfig) {
|
|
38
|
+
const { dir, files } = await buildWebsite(c)
|
|
39
|
+
const frontend = createBuiltDistFilesFetcher(dir, files)
|
|
40
|
+
createWebServer(PORT, frontend).listen(PORT)
|
|
41
|
+
console.log(`preview is live at http://127.0.0.1:${PORT}`)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// todo changing partials triggers update on html pages
|
|
45
|
+
async function startDevMode(c: DankConfig, signal: AbortSignal) {
|
|
46
46
|
const watchDir = join('build', 'watch')
|
|
47
47
|
await mkdir(watchDir, { recursive: true })
|
|
48
|
-
await
|
|
48
|
+
const clientJS = await loadClientJS()
|
|
49
|
+
const pagesByUrlPath: Record<string, WebpageMetadata> = {}
|
|
50
|
+
const entryPointsByUrlPath: Record<string, Set<string>> = {}
|
|
51
|
+
let buildContext: BuildContext | 'starting' | 'dirty' | 'disposing' | null =
|
|
52
|
+
null
|
|
49
53
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
watch('dank.config.ts', signal, async () => {
|
|
55
|
+
let updated: DankConfig
|
|
56
|
+
try {
|
|
57
|
+
updated = await loadConfig()
|
|
58
|
+
} catch (ignore) {
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
const prevPages = new Set(Object.keys(pagesByUrlPath))
|
|
62
|
+
await Promise.all(
|
|
63
|
+
Object.entries(updated.pages).map(async ([urlPath, srcPath]) => {
|
|
64
|
+
c.pages[urlPath as `/${string}`] = srcPath
|
|
65
|
+
if (pagesByUrlPath[urlPath]) {
|
|
66
|
+
prevPages.delete(urlPath)
|
|
67
|
+
if (pagesByUrlPath[urlPath].srcPath !== srcPath) {
|
|
68
|
+
await updatePage(urlPath)
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
await addPage(urlPath, srcPath)
|
|
72
|
+
}
|
|
73
|
+
}),
|
|
74
|
+
)
|
|
75
|
+
for (const prevPage of Array.from(prevPages)) {
|
|
76
|
+
delete c.pages[prevPage as `/${string}`]
|
|
77
|
+
deletePage(prevPage)
|
|
78
|
+
}
|
|
79
|
+
updateDevServices(updated)
|
|
80
|
+
})
|
|
54
81
|
|
|
55
|
-
|
|
56
|
-
|
|
82
|
+
watch('pages', signal, filename => {
|
|
83
|
+
if (extname(filename) === '.html') {
|
|
84
|
+
for (const [urlPath, srcPath] of Object.entries(c.pages)) {
|
|
85
|
+
if (srcPath === filename) {
|
|
86
|
+
updatePage(urlPath)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
})
|
|
57
91
|
|
|
58
92
|
await Promise.all(
|
|
59
|
-
Object.entries(c.pages).map(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
93
|
+
Object.entries(c.pages).map(([urlPath, srcPath]) =>
|
|
94
|
+
addPage(urlPath, srcPath),
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
async function addPage(urlPath: string, srcPath: string) {
|
|
99
|
+
const metadata = await processWebpage({
|
|
100
|
+
clientJS,
|
|
101
|
+
outDir: watchDir,
|
|
102
|
+
pagesDir: 'pages',
|
|
103
|
+
srcPath,
|
|
104
|
+
urlPath,
|
|
105
|
+
})
|
|
106
|
+
pagesByUrlPath[urlPath] = metadata
|
|
107
|
+
entryPointsByUrlPath[urlPath] = new Set(
|
|
108
|
+
metadata.entryPoints.map(e => e.in),
|
|
109
|
+
)
|
|
110
|
+
if (buildContext !== null) {
|
|
111
|
+
resetBuildContext()
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function deletePage(urlPath: string) {
|
|
116
|
+
delete pagesByUrlPath[urlPath]
|
|
117
|
+
delete entryPointsByUrlPath[urlPath]
|
|
118
|
+
resetBuildContext()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function updatePage(urlPath: string) {
|
|
122
|
+
const update = await processWebpage({
|
|
123
|
+
clientJS,
|
|
124
|
+
outDir: watchDir,
|
|
125
|
+
pagesDir: 'pages',
|
|
126
|
+
srcPath: c.pages[urlPath as `/${string}`],
|
|
127
|
+
urlPath,
|
|
128
|
+
})
|
|
129
|
+
const entryPointUrls = new Set(update.entryPoints.map(e => e.in))
|
|
130
|
+
if (!hasSameValues(entryPointUrls, entryPointsByUrlPath[urlPath])) {
|
|
131
|
+
entryPointsByUrlPath[urlPath] = entryPointUrls
|
|
132
|
+
resetBuildContext()
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function collectEntrypoints(): Array<{ in: string; out: string }> {
|
|
137
|
+
const sources: Set<string> = new Set()
|
|
138
|
+
return Object.values(pagesByUrlPath)
|
|
139
|
+
.flatMap(({ entryPoints }) => entryPoints)
|
|
140
|
+
.filter(entryPoint => {
|
|
141
|
+
if (sources.has(entryPoint.in)) {
|
|
142
|
+
return false
|
|
143
|
+
} else {
|
|
144
|
+
sources.add(entryPoint.in)
|
|
145
|
+
return true
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function resetBuildContext() {
|
|
151
|
+
if (buildContext === 'starting' || buildContext === 'dirty') {
|
|
152
|
+
buildContext = 'dirty'
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
if (buildContext === 'disposing') {
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
if (buildContext !== null) {
|
|
159
|
+
const prev = buildContext
|
|
160
|
+
buildContext = 'disposing'
|
|
161
|
+
prev.dispose().then(() => {
|
|
162
|
+
buildContext = null
|
|
163
|
+
resetBuildContext()
|
|
164
|
+
})
|
|
165
|
+
} else {
|
|
166
|
+
startEsbuildWatch(collectEntrypoints()).then(ctx => {
|
|
167
|
+
if (buildContext === 'dirty') {
|
|
168
|
+
buildContext = null
|
|
169
|
+
resetBuildContext()
|
|
170
|
+
} else {
|
|
171
|
+
buildContext = ctx
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
buildContext = await startEsbuildWatch(collectEntrypoints())
|
|
178
|
+
const frontend = createDevServeFilesFetcher({
|
|
179
|
+
pages: c.pages,
|
|
180
|
+
pagesDir: watchDir,
|
|
181
|
+
proxyPort: ESBUILD_PORT,
|
|
182
|
+
publicDir: 'public',
|
|
183
|
+
})
|
|
184
|
+
createWebServer(PORT, frontend).listen(PORT)
|
|
185
|
+
console.log(`dev server is live at http://127.0.0.1:${PORT}`)
|
|
186
|
+
startDevServices(c, signal)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function hasSameValues(a: Set<string>, b: Set<string>): boolean {
|
|
190
|
+
if (a.size !== b.size) {
|
|
191
|
+
return false
|
|
192
|
+
}
|
|
193
|
+
for (const v in a) {
|
|
194
|
+
if (!b.has(v)) {
|
|
195
|
+
return false
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return true
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
type WebpageInputs = {
|
|
202
|
+
clientJS: string
|
|
203
|
+
outDir: string
|
|
204
|
+
pagesDir: string
|
|
205
|
+
srcPath: string
|
|
206
|
+
urlPath: string
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
type WebpageMetadata = {
|
|
210
|
+
entryPoints: Array<{ in: string; out: string }>
|
|
211
|
+
srcPath: string
|
|
212
|
+
urlPath: string
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async function processWebpage(inputs: WebpageInputs): Promise<WebpageMetadata> {
|
|
216
|
+
const html = await HtmlEntrypoint.readFrom(
|
|
217
|
+
inputs.urlPath,
|
|
218
|
+
join(inputs.pagesDir, inputs.srcPath),
|
|
82
219
|
)
|
|
220
|
+
await html.injectPartials()
|
|
221
|
+
if (inputs.urlPath !== '/') {
|
|
222
|
+
await mkdir(join(inputs.outDir, inputs.urlPath), { recursive: true })
|
|
223
|
+
}
|
|
224
|
+
const entryPoints: Array<{ in: string; out: string }> = []
|
|
225
|
+
html.collectScripts().forEach(scriptImport => {
|
|
226
|
+
entryPoints.push({
|
|
227
|
+
in: scriptImport.in,
|
|
228
|
+
out: scriptImport.out,
|
|
229
|
+
})
|
|
230
|
+
})
|
|
231
|
+
html.rewriteHrefs()
|
|
232
|
+
html.appendScript(inputs.clientJS)
|
|
233
|
+
await html.writeTo(inputs.outDir)
|
|
234
|
+
return {
|
|
235
|
+
entryPoints,
|
|
236
|
+
srcPath: inputs.srcPath,
|
|
237
|
+
urlPath: inputs.urlPath,
|
|
238
|
+
}
|
|
239
|
+
}
|
|
83
240
|
|
|
241
|
+
async function startEsbuildWatch(
|
|
242
|
+
entryPoints: Array<{ in: string; out: string }>,
|
|
243
|
+
): Promise<BuildContext> {
|
|
84
244
|
const ctx = await esbuildDevContext(
|
|
85
245
|
createGlobalDefinitions(),
|
|
86
246
|
entryPoints,
|
|
87
|
-
|
|
247
|
+
'build/watch',
|
|
88
248
|
)
|
|
89
249
|
|
|
90
250
|
await ctx.watch()
|
|
@@ -92,11 +252,55 @@ async function startEsbuildWatch(c: DankConfig): Promise<{ port: number }> {
|
|
|
92
252
|
await ctx.serve({
|
|
93
253
|
host: '127.0.0.1',
|
|
94
254
|
port: ESBUILD_PORT,
|
|
95
|
-
servedir: watchDir,
|
|
96
255
|
cors: {
|
|
97
256
|
origin: 'http://127.0.0.1:' + PORT,
|
|
98
257
|
},
|
|
99
258
|
})
|
|
100
259
|
|
|
101
|
-
return
|
|
260
|
+
return ctx
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function loadClientJS() {
|
|
264
|
+
return await readFile(
|
|
265
|
+
resolve(import.meta.dirname, join('..', 'client', 'esbuild.js')),
|
|
266
|
+
'utf-8',
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async function watch(
|
|
271
|
+
p: string,
|
|
272
|
+
signal: AbortSignal,
|
|
273
|
+
fire: (filename: string) => void,
|
|
274
|
+
) {
|
|
275
|
+
const delayFire = 90
|
|
276
|
+
const timeout = 100
|
|
277
|
+
let changes: Record<string, number> = {}
|
|
278
|
+
try {
|
|
279
|
+
for await (const { filename } of _watch(p, {
|
|
280
|
+
recursive: true,
|
|
281
|
+
signal,
|
|
282
|
+
})) {
|
|
283
|
+
if (filename) {
|
|
284
|
+
if (!changes[filename]) {
|
|
285
|
+
const now = Date.now()
|
|
286
|
+
changes[filename] = now + delayFire
|
|
287
|
+
setTimeout(() => {
|
|
288
|
+
const now = Date.now()
|
|
289
|
+
for (const [filename, then] of Object.entries(
|
|
290
|
+
changes,
|
|
291
|
+
)) {
|
|
292
|
+
if (then <= now) {
|
|
293
|
+
fire(filename)
|
|
294
|
+
delete changes[filename]
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}, timeout)
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
} catch (e: any) {
|
|
302
|
+
if (e.name !== 'AbortError') {
|
|
303
|
+
throw e
|
|
304
|
+
}
|
|
305
|
+
}
|
|
102
306
|
}
|
package/lib/services.ts
CHANGED
|
@@ -1,22 +1,125 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process'
|
|
1
|
+
import { type ChildProcess, spawn } from 'node:child_process'
|
|
2
2
|
import { basename, isAbsolute, resolve } from 'node:path'
|
|
3
3
|
import type { DankConfig, DevService } from './dank.ts'
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// up to date representation of dank.config.ts services
|
|
6
|
+
const running: Array<{ s: DevService; process: ChildProcess | null }> = []
|
|
7
|
+
|
|
8
|
+
let signal: AbortSignal
|
|
9
|
+
|
|
10
|
+
// batch of services that must be stopped before starting new services
|
|
11
|
+
let updating: null | {
|
|
12
|
+
stopping: Array<DevService>
|
|
13
|
+
starting: Array<DevService>
|
|
14
|
+
} = null
|
|
15
|
+
|
|
16
|
+
export function startDevServices(c: DankConfig, _signal: AbortSignal) {
|
|
17
|
+
signal = _signal
|
|
6
18
|
if (c.services?.length) {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
19
|
+
for (const s of c.services) {
|
|
20
|
+
running.push({ s, process: startService(s) })
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function updateDevServices(c: DankConfig) {
|
|
26
|
+
if (!c.services?.length) {
|
|
27
|
+
if (running.length) {
|
|
28
|
+
if (updating === null) {
|
|
29
|
+
updating = { stopping: [], starting: [] }
|
|
30
|
+
}
|
|
31
|
+
running.forEach(({ s, process }) => {
|
|
32
|
+
if (process) {
|
|
33
|
+
stopService(s, process)
|
|
34
|
+
} else {
|
|
35
|
+
removeFromUpdating(s)
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
running.length = 0
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
if (updating === null) {
|
|
42
|
+
updating = { stopping: [], starting: [] }
|
|
43
|
+
}
|
|
44
|
+
const keep = []
|
|
45
|
+
const next: Array<DevService> = []
|
|
46
|
+
for (const s of c.services) {
|
|
47
|
+
let found = false
|
|
48
|
+
for (let i = 0; i < running.length; i++) {
|
|
49
|
+
const p = running[i].s
|
|
50
|
+
if (matchingConfig(s, p)) {
|
|
51
|
+
found = true
|
|
52
|
+
keep.push(i)
|
|
53
|
+
break
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (!found) {
|
|
57
|
+
next.push(s)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (let i = running.length - 1; i >= 0; i--) {
|
|
61
|
+
if (!keep.includes(i)) {
|
|
62
|
+
const { s, process } = running[i]
|
|
63
|
+
if (process) {
|
|
64
|
+
stopService(s, process)
|
|
65
|
+
} else {
|
|
66
|
+
removeFromUpdating(s)
|
|
67
|
+
}
|
|
68
|
+
running.splice(i, 1)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (updating.stopping.length) {
|
|
72
|
+
for (const s of next) {
|
|
73
|
+
if (
|
|
74
|
+
!updating.starting.find(queued => matchingConfig(queued, s))
|
|
75
|
+
) {
|
|
76
|
+
updating.starting.push(s)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
updating = null
|
|
81
|
+
for (const s of next) {
|
|
82
|
+
running.push({ s, process: startService(s) })
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function stopService(s: DevService, process: ChildProcess) {
|
|
89
|
+
opPrint(s, 'stopping')
|
|
90
|
+
updating!.stopping.push(s)
|
|
91
|
+
process.kill()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function matchingConfig(a: DevService, b: DevService): boolean {
|
|
95
|
+
if (a.command !== b.command) {
|
|
96
|
+
return false
|
|
97
|
+
}
|
|
98
|
+
if (a.cwd !== b.cwd) {
|
|
99
|
+
return false
|
|
100
|
+
}
|
|
101
|
+
if (!a.env && !b.env) {
|
|
102
|
+
return true
|
|
103
|
+
} else if (a.env && !b.env) {
|
|
104
|
+
return false
|
|
105
|
+
} else if (!a.env && b.env) {
|
|
106
|
+
return false
|
|
107
|
+
} else if (Object.keys(a.env!).length !== Object.keys(b.env!).length) {
|
|
108
|
+
return false
|
|
109
|
+
} else {
|
|
110
|
+
for (const k of Object.keys(a.env!)) {
|
|
111
|
+
if (!b.env![k]) {
|
|
112
|
+
return false
|
|
113
|
+
} else if (a.env![k] !== b.env![k]) {
|
|
114
|
+
return false
|
|
11
115
|
}
|
|
12
|
-
} catch (e) {
|
|
13
|
-
ac.abort()
|
|
14
|
-
throw e
|
|
15
116
|
}
|
|
16
117
|
}
|
|
118
|
+
return true
|
|
17
119
|
}
|
|
18
120
|
|
|
19
|
-
function startService(s: DevService
|
|
121
|
+
function startService(s: DevService): ChildProcess {
|
|
122
|
+
opPrint(s, 'starting')
|
|
20
123
|
const splitCmdAndArgs = s.command.split(/\s+/)
|
|
21
124
|
const cmd = splitCmdAndArgs[0]
|
|
22
125
|
const args = splitCmdAndArgs.length === 1 ? [] : splitCmdAndArgs.slice(1)
|
|
@@ -34,9 +137,43 @@ function startService(s: DevService, signal: AbortSignal) {
|
|
|
34
137
|
const stderrLabel = logLabel(s.cwd, cmd, args, 31)
|
|
35
138
|
spawned.stderr.on('data', chunk => printChunk(stderrLabel, chunk))
|
|
36
139
|
|
|
140
|
+
spawned.on('error', e => {
|
|
141
|
+
const cause =
|
|
142
|
+
'code' in e && e.code === 'ENOENT' ? 'program not found' : e.message
|
|
143
|
+
opPrint(s, 'error: ' + cause)
|
|
144
|
+
removeFromRunning(s)
|
|
145
|
+
})
|
|
146
|
+
|
|
37
147
|
spawned.on('exit', () => {
|
|
38
|
-
|
|
148
|
+
opPrint(s, 'exited')
|
|
149
|
+
removeFromRunning(s)
|
|
150
|
+
removeFromUpdating(s)
|
|
39
151
|
})
|
|
152
|
+
return spawned
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function removeFromRunning(s: DevService) {
|
|
156
|
+
for (let i = 0; i < running.length; i++) {
|
|
157
|
+
if (matchingConfig(running[i].s, s)) {
|
|
158
|
+
running.splice(i, 1)
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function removeFromUpdating(s: DevService) {
|
|
165
|
+
if (updating !== null) {
|
|
166
|
+
for (let i = 0; i < updating.stopping.length; i++) {
|
|
167
|
+
if (matchingConfig(updating.stopping[i], s)) {
|
|
168
|
+
updating.stopping.splice(i, 1)
|
|
169
|
+
if (!updating.stopping.length) {
|
|
170
|
+
updating.starting.forEach(startService)
|
|
171
|
+
updating = null
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
40
177
|
}
|
|
41
178
|
|
|
42
179
|
function printChunk(label: string, c: Buffer) {
|
|
@@ -58,6 +195,14 @@ function resolveCwd(p?: string): string | undefined {
|
|
|
58
195
|
}
|
|
59
196
|
}
|
|
60
197
|
|
|
198
|
+
function opPrint(s: DevService, msg: string) {
|
|
199
|
+
console.log(opLabel(s), msg)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function opLabel(s: DevService) {
|
|
203
|
+
return `\`${s.cwd ? s.cwd + ' ' : ''}${s.command}\``
|
|
204
|
+
}
|
|
205
|
+
|
|
61
206
|
function logLabel(
|
|
62
207
|
cwd: string | undefined,
|
|
63
208
|
cmd: string,
|
package/lib_js/config.js
CHANGED
|
@@ -9,7 +9,8 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
9
9
|
import { isAbsolute, resolve } from 'node:path';
|
|
10
10
|
const CFG_P = './dank.config.ts';
|
|
11
11
|
export async function loadConfig(path = CFG_P) {
|
|
12
|
-
const
|
|
12
|
+
const modulePath = `${resolveConfigPath(path)}?${Date.now()}`;
|
|
13
|
+
const module = await import(__rewriteRelativeImportExtension(modulePath));
|
|
13
14
|
return await module.default;
|
|
14
15
|
}
|
|
15
16
|
export function resolveConfigPath(path) {
|
package/lib_js/dank.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export async function defineConfig(c) {
|
|
2
2
|
validatePages(c.pages);
|
|
3
3
|
validateDevServices(c.services);
|
|
4
|
+
normalizePagePaths(c.pages);
|
|
4
5
|
return c;
|
|
5
6
|
}
|
|
6
7
|
function validatePages(pages) {
|
|
@@ -47,3 +48,8 @@ function validateDevServices(services) {
|
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
50
|
}
|
|
51
|
+
function normalizePagePaths(pages) {
|
|
52
|
+
for (const urlPath of Object.keys(pages)) {
|
|
53
|
+
pages[urlPath] = pages[urlPath].replace(/^\.\//, '');
|
|
54
|
+
}
|
|
55
|
+
}
|
package/lib_js/esbuild.js
CHANGED
|
@@ -26,6 +26,8 @@ export async function esbuildDevContext(define, entryPoints, outdir) {
|
|
|
26
26
|
entryPoints: removeEntryPointOutExt(entryPoints),
|
|
27
27
|
outdir,
|
|
28
28
|
...webpageBuildOptions,
|
|
29
|
+
metafile: false,
|
|
30
|
+
write: false,
|
|
29
31
|
});
|
|
30
32
|
}
|
|
31
33
|
export async function esbuildWebpages(define, entryPoints, outdir) {
|