@eighty4/dank 0.0.5-0 → 0.0.5-2
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/developer.ts +83 -26
- package/lib/dirs.ts +8 -9
- package/lib/http.ts +9 -10
- package/lib/serve.ts +65 -21
- package/lib/services.ts +253 -189
- package/lib/watch.ts +38 -7
- 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 +161 -146
- package/lib_js/watch.js +14 -5
- package/package.json +1 -1
package/lib/services.ts
CHANGED
|
@@ -1,114 +1,228 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type ChildProcess,
|
|
3
|
+
type ChildProcessWithoutNullStreams,
|
|
4
|
+
execSync,
|
|
5
|
+
spawn,
|
|
6
|
+
} from 'node:child_process'
|
|
7
|
+
import EventEmitter from 'node:events'
|
|
2
8
|
import { basename, isAbsolute, resolve } from 'node:path'
|
|
3
9
|
import type { DevService, ResolvedDankConfig } from './config.ts'
|
|
4
10
|
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
11
|
+
export class ManagedServiceLabel {
|
|
12
|
+
#command: string
|
|
13
|
+
#cwd: string
|
|
14
|
+
|
|
15
|
+
constructor(spec: DevService) {
|
|
16
|
+
this.#command = spec.command
|
|
17
|
+
this.#cwd = !spec.cwd
|
|
18
|
+
? './'
|
|
19
|
+
: spec.cwd.startsWith('/')
|
|
20
|
+
? `/.../${basename(spec.cwd)}`
|
|
21
|
+
: spec.cwd.startsWith('.')
|
|
22
|
+
? spec.cwd
|
|
23
|
+
: `./${spec.cwd}`
|
|
24
|
+
}
|
|
8
25
|
|
|
9
|
-
|
|
10
|
-
|
|
26
|
+
get command(): string {
|
|
27
|
+
return this.#command
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get cwd(): string {
|
|
31
|
+
return this.#cwd
|
|
32
|
+
}
|
|
11
33
|
}
|
|
12
34
|
|
|
13
35
|
export type HttpService = NonNullable<DevService['http']>
|
|
14
36
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
37
|
+
export type DevServiceEvents = {
|
|
38
|
+
error: [label: ManagedServiceLabel, cause: string]
|
|
39
|
+
exit: [label: ManagedServiceLabel, code: number | string]
|
|
40
|
+
launch: [label: ManagedServiceLabel]
|
|
41
|
+
stdout: [label: ManagedServiceLabel, output: Array<string>]
|
|
42
|
+
stderr: [label: ManagedServiceLabel, output: Array<string>]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
class ManagedService extends EventEmitter<DevServiceEvents> {
|
|
46
|
+
#label: ManagedServiceLabel
|
|
47
|
+
#process: ChildProcess | null
|
|
48
|
+
#spec: DevService
|
|
49
|
+
// #status: ManagedServiceStatus = 'starting'
|
|
50
|
+
|
|
51
|
+
constructor(spec: DevService) {
|
|
52
|
+
super()
|
|
53
|
+
this.#label = new ManagedServiceLabel(spec)
|
|
54
|
+
this.#spec = spec
|
|
55
|
+
this.#process = this.#start()
|
|
35
56
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return running.map(({ s }) => s.http).filter(http => !!http)
|
|
40
|
-
},
|
|
41
|
-
},
|
|
57
|
+
|
|
58
|
+
get spec(): DevService {
|
|
59
|
+
return this.#spec
|
|
42
60
|
}
|
|
43
|
-
}
|
|
44
61
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
next.push(s)
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
for (let i = running.length - 1; i >= 0; i--) {
|
|
81
|
-
if (!keep.includes(i)) {
|
|
82
|
-
const { s, process } = running[i]
|
|
83
|
-
if (process) {
|
|
84
|
-
stopService(s, process)
|
|
85
|
-
} else {
|
|
86
|
-
removeFromUpdating(s)
|
|
87
|
-
}
|
|
88
|
-
running.splice(i, 1)
|
|
62
|
+
get httpSpec(): HttpService | undefined {
|
|
63
|
+
return this.#spec.http
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
matches(other: DevService): boolean {
|
|
67
|
+
return matchingConfig(this.#spec, other)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
kill() {
|
|
71
|
+
if (this.#process) killProcess(this.#process)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
#start(): ChildProcess {
|
|
75
|
+
const { path, args } = parseCommand(this.#spec.command)
|
|
76
|
+
const env = this.#spec.env
|
|
77
|
+
? { ...process.env, ...this.#spec.env }
|
|
78
|
+
: undefined
|
|
79
|
+
const cwd =
|
|
80
|
+
!this.#spec.cwd || isAbsolute(this.#spec.cwd)
|
|
81
|
+
? this.#spec.cwd
|
|
82
|
+
: resolve(process.cwd(), this.#spec.cwd)
|
|
83
|
+
const spawned = spawnProcess(path, args, env, cwd)
|
|
84
|
+
this.emit('launch', this.#label)
|
|
85
|
+
spawned.stdout.on('data', chunk =>
|
|
86
|
+
this.emit('stdout', this.#label, parseChunk(chunk)),
|
|
87
|
+
)
|
|
88
|
+
spawned.stderr.on('data', chunk =>
|
|
89
|
+
this.emit('stderr', this.#label, parseChunk(chunk)),
|
|
90
|
+
)
|
|
91
|
+
spawned.on('error', e => {
|
|
92
|
+
if (e.name === 'AbortError') {
|
|
93
|
+
return
|
|
89
94
|
}
|
|
95
|
+
const cause =
|
|
96
|
+
'code' in e && e.code === 'ENOENT'
|
|
97
|
+
? 'program not found'
|
|
98
|
+
: e.message
|
|
99
|
+
this.emit('error', this.#label, cause)
|
|
100
|
+
})
|
|
101
|
+
spawned.on('exit', (code, signal) =>
|
|
102
|
+
this.emit('exit', this.#label, code || signal!),
|
|
103
|
+
)
|
|
104
|
+
return spawned
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
type SpawnProcess = (
|
|
109
|
+
program: string,
|
|
110
|
+
args: Array<string>,
|
|
111
|
+
env: NodeJS.ProcessEnv | undefined,
|
|
112
|
+
cwd: string | undefined,
|
|
113
|
+
) => ChildProcessWithoutNullStreams
|
|
114
|
+
|
|
115
|
+
type KillProcess = (p: ChildProcess) => void
|
|
116
|
+
|
|
117
|
+
const killProcess: KillProcess =
|
|
118
|
+
process.platform === 'win32'
|
|
119
|
+
? p => execSync(`taskkill /pid ${p.pid} /T /F`)
|
|
120
|
+
: p => p.kill()
|
|
121
|
+
|
|
122
|
+
const spawnProcess: SpawnProcess =
|
|
123
|
+
process.platform === 'win32'
|
|
124
|
+
? (
|
|
125
|
+
path: string,
|
|
126
|
+
args: Array<string>,
|
|
127
|
+
env: NodeJS.ProcessEnv | undefined,
|
|
128
|
+
cwd: string | undefined,
|
|
129
|
+
) =>
|
|
130
|
+
spawn('cmd', ['/c', path, ...args], {
|
|
131
|
+
cwd,
|
|
132
|
+
env,
|
|
133
|
+
detached: false,
|
|
134
|
+
shell: false,
|
|
135
|
+
windowsHide: true,
|
|
136
|
+
})
|
|
137
|
+
: (
|
|
138
|
+
path: string,
|
|
139
|
+
args: Array<string>,
|
|
140
|
+
env: NodeJS.ProcessEnv | undefined,
|
|
141
|
+
cwd: string | undefined,
|
|
142
|
+
) =>
|
|
143
|
+
spawn(path, args, {
|
|
144
|
+
cwd,
|
|
145
|
+
env,
|
|
146
|
+
detached: false,
|
|
147
|
+
shell: false,
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
export class DevServices extends EventEmitter<DevServiceEvents> {
|
|
151
|
+
#running: Array<ManagedService>
|
|
152
|
+
|
|
153
|
+
constructor(services: ResolvedDankConfig['services']) {
|
|
154
|
+
super()
|
|
155
|
+
this.#running = services ? this.#start(services) : []
|
|
156
|
+
if (process.platform === 'win32') {
|
|
157
|
+
process.once('SIGINT', () => process.exit())
|
|
90
158
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
159
|
+
process.once('exit', this.shutdown)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
get httpServices(): Array<HttpService> {
|
|
163
|
+
return this.#running.map(s => s.httpSpec).filter(http => !!http)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
shutdown = () => {
|
|
167
|
+
this.#running.forEach(s => {
|
|
168
|
+
s.kill()
|
|
169
|
+
s.removeAllListeners()
|
|
170
|
+
})
|
|
171
|
+
this.#running.length = 0
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
update(services: ResolvedDankConfig['services']) {
|
|
175
|
+
if (!services?.length) {
|
|
176
|
+
this.shutdown()
|
|
177
|
+
} else if (
|
|
178
|
+
!matchingConfigs(
|
|
179
|
+
this.#running.map(s => s.spec),
|
|
180
|
+
services,
|
|
181
|
+
)
|
|
182
|
+
) {
|
|
183
|
+
this.shutdown()
|
|
184
|
+
this.#running = this.#start(services)
|
|
104
185
|
}
|
|
105
186
|
}
|
|
187
|
+
|
|
188
|
+
#start(
|
|
189
|
+
services: NonNullable<ResolvedDankConfig['services']>,
|
|
190
|
+
): Array<ManagedService> {
|
|
191
|
+
return services.map(spec => {
|
|
192
|
+
const service = new ManagedService(spec)
|
|
193
|
+
service.on('error', (label, cause) =>
|
|
194
|
+
this.emit('error', label, cause),
|
|
195
|
+
)
|
|
196
|
+
service.on('exit', (label, code) => this.emit('exit', label, code))
|
|
197
|
+
service.on('launch', label => this.emit('launch', label))
|
|
198
|
+
service.on('stdout', (label, output) =>
|
|
199
|
+
this.emit('stdout', label, output),
|
|
200
|
+
)
|
|
201
|
+
service.on('stderr', (label, output) =>
|
|
202
|
+
this.emit('stderr', label, output),
|
|
203
|
+
)
|
|
204
|
+
return service
|
|
205
|
+
})
|
|
206
|
+
}
|
|
106
207
|
}
|
|
107
208
|
|
|
108
|
-
function
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
209
|
+
function matchingConfigs(
|
|
210
|
+
a: Array<DevService>,
|
|
211
|
+
b: NonNullable<ResolvedDankConfig['services']>,
|
|
212
|
+
): boolean {
|
|
213
|
+
if (a.length !== b.length) {
|
|
214
|
+
return false
|
|
215
|
+
}
|
|
216
|
+
const crossRef = [...a]
|
|
217
|
+
for (const toFind of b) {
|
|
218
|
+
const found = crossRef.findIndex(spec => matchingConfig(spec, toFind))
|
|
219
|
+
if (found === -1) {
|
|
220
|
+
return false
|
|
221
|
+
} else {
|
|
222
|
+
crossRef.splice(found, 1)
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return true
|
|
112
226
|
}
|
|
113
227
|
|
|
114
228
|
function matchingConfig(a: DevService, b: DevService): boolean {
|
|
@@ -138,72 +252,6 @@ function matchingConfig(a: DevService, b: DevService): boolean {
|
|
|
138
252
|
return true
|
|
139
253
|
}
|
|
140
254
|
|
|
141
|
-
function startService(s: DevService): ChildProcess {
|
|
142
|
-
opPrint(s, 'starting')
|
|
143
|
-
const splitCmdAndArgs = s.command.split(/\s+/)
|
|
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
|
-
})
|
|
153
|
-
|
|
154
|
-
const stdoutLabel = logLabel(s.cwd, cmd, args, 32)
|
|
155
|
-
spawned.stdout.on('data', chunk => printChunk(stdoutLabel, chunk))
|
|
156
|
-
|
|
157
|
-
const stderrLabel = logLabel(s.cwd, cmd, args, 31)
|
|
158
|
-
spawned.stderr.on('data', chunk => printChunk(stderrLabel, chunk))
|
|
159
|
-
|
|
160
|
-
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
|
-
removeFromRunning(s)
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
spawned.on('exit', () => {
|
|
172
|
-
opPrint(s, 'exited')
|
|
173
|
-
removeFromRunning(s)
|
|
174
|
-
removeFromUpdating(s)
|
|
175
|
-
})
|
|
176
|
-
return spawned
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function removeFromRunning(s: DevService) {
|
|
180
|
-
for (let i = 0; i < running.length; i++) {
|
|
181
|
-
if (matchingConfig(running[i].s, s)) {
|
|
182
|
-
running.splice(i, 1)
|
|
183
|
-
return
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function removeFromUpdating(s: DevService) {
|
|
189
|
-
if (updating !== null) {
|
|
190
|
-
for (let i = 0; i < updating.stopping.length; i++) {
|
|
191
|
-
if (matchingConfig(updating.stopping[i], s)) {
|
|
192
|
-
updating.stopping.splice(i, 1)
|
|
193
|
-
if (!updating.stopping.length) {
|
|
194
|
-
updating.starting.forEach(startService)
|
|
195
|
-
updating = null
|
|
196
|
-
return
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function printChunk(label: string, c: Buffer) {
|
|
204
|
-
for (const l of parseChunk(c)) console.log(label, l)
|
|
205
|
-
}
|
|
206
|
-
|
|
207
255
|
function parseChunk(c: Buffer): Array<string> {
|
|
208
256
|
return c
|
|
209
257
|
.toString()
|
|
@@ -211,34 +259,50 @@ function parseChunk(c: Buffer): Array<string> {
|
|
|
211
259
|
.split(/\r?\n/)
|
|
212
260
|
}
|
|
213
261
|
|
|
214
|
-
function
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
262
|
+
export function parseCommand(command: string): {
|
|
263
|
+
path: string
|
|
264
|
+
args: Array<string>
|
|
265
|
+
} {
|
|
266
|
+
command = command.trimStart()
|
|
267
|
+
const programSplitIndex = command.indexOf(' ')
|
|
268
|
+
if (programSplitIndex === -1) {
|
|
269
|
+
return { path: command.trim(), args: [] }
|
|
219
270
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
271
|
+
const path = command.substring(0, programSplitIndex)
|
|
272
|
+
const args: Array<string> = []
|
|
273
|
+
let argStart = programSplitIndex + 1
|
|
274
|
+
let withinLiteral: false | "'" | '"' = false
|
|
275
|
+
for (let i = 0; i < command.length; i++) {
|
|
276
|
+
const c = command[i]
|
|
277
|
+
if (!withinLiteral) {
|
|
278
|
+
if (c === "'" || c === '"') {
|
|
279
|
+
withinLiteral = c
|
|
280
|
+
continue
|
|
281
|
+
}
|
|
282
|
+
if (c === '\\') {
|
|
283
|
+
i++
|
|
284
|
+
continue
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (withinLiteral) {
|
|
288
|
+
if (c === withinLiteral) {
|
|
289
|
+
withinLiteral = false
|
|
290
|
+
args.push(command.substring(argStart + 1, i))
|
|
291
|
+
argStart = i + 1
|
|
292
|
+
}
|
|
293
|
+
continue
|
|
294
|
+
}
|
|
295
|
+
if (c === ' ' && i > argStart) {
|
|
296
|
+
const maybeArg = command.substring(argStart, i).trim()
|
|
297
|
+
if (maybeArg.length) {
|
|
298
|
+
args.push(maybeArg)
|
|
299
|
+
}
|
|
300
|
+
argStart = i + 1
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
const maybeArg = command.substring(argStart, command.length).trim()
|
|
304
|
+
if (maybeArg.length) {
|
|
305
|
+
args.push(maybeArg)
|
|
306
|
+
}
|
|
307
|
+
return { path, args }
|
|
244
308
|
}
|
package/lib/watch.ts
CHANGED
|
@@ -1,18 +1,49 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
watch as createWatch,
|
|
3
|
+
type WatchOptionsWithStringEncoding,
|
|
4
|
+
} from 'node:fs/promises'
|
|
5
|
+
|
|
6
|
+
type WatchCallback = (filename: string) => void
|
|
7
|
+
|
|
8
|
+
export async function watch(p: string, fire: WatchCallback): Promise<void>
|
|
2
9
|
|
|
3
10
|
export async function watch(
|
|
4
11
|
p: string,
|
|
5
12
|
signal: AbortSignal,
|
|
6
|
-
fire:
|
|
7
|
-
)
|
|
13
|
+
fire: WatchCallback,
|
|
14
|
+
): Promise<void>
|
|
15
|
+
|
|
16
|
+
export async function watch(
|
|
17
|
+
p: string,
|
|
18
|
+
opts: WatchOptionsWithStringEncoding,
|
|
19
|
+
fire: WatchCallback,
|
|
20
|
+
): Promise<void>
|
|
21
|
+
|
|
22
|
+
export async function watch(
|
|
23
|
+
p: string,
|
|
24
|
+
signalFireOrOpts:
|
|
25
|
+
| AbortSignal
|
|
26
|
+
| WatchCallback
|
|
27
|
+
| WatchOptionsWithStringEncoding,
|
|
28
|
+
fireOrUndefined?: WatchCallback,
|
|
29
|
+
): Promise<void> {
|
|
30
|
+
let opts: WatchOptionsWithStringEncoding | undefined
|
|
31
|
+
let fire: WatchCallback
|
|
32
|
+
if (signalFireOrOpts instanceof AbortSignal) {
|
|
33
|
+
opts = { signal: signalFireOrOpts }
|
|
34
|
+
} else if (typeof signalFireOrOpts === 'object') {
|
|
35
|
+
opts = signalFireOrOpts
|
|
36
|
+
} else {
|
|
37
|
+
fire = signalFireOrOpts
|
|
38
|
+
}
|
|
39
|
+
if (opts && typeof fireOrUndefined === 'function') {
|
|
40
|
+
fire = fireOrUndefined
|
|
41
|
+
}
|
|
8
42
|
const delayFire = 90
|
|
9
43
|
const timeout = 100
|
|
10
44
|
let changes: Record<string, number> = {}
|
|
11
45
|
try {
|
|
12
|
-
for await (const { filename } of createWatch(p, {
|
|
13
|
-
recursive: true,
|
|
14
|
-
signal,
|
|
15
|
-
})) {
|
|
46
|
+
for await (const { filename } of createWatch(p, opts)) {
|
|
16
47
|
if (filename) {
|
|
17
48
|
if (!changes[filename]) {
|
|
18
49
|
const now = Date.now()
|
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
|
}
|