@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/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
|
@@ -7,8 +7,7 @@ export type DankDirectories = {
|
|
|
7
7
|
buildWatch: string
|
|
8
8
|
buildDist: string
|
|
9
9
|
pages: string
|
|
10
|
-
|
|
11
|
-
projectResolved: string
|
|
10
|
+
pagesAbs: string
|
|
12
11
|
projectRootAbs: string
|
|
13
12
|
public: string
|
|
14
13
|
}
|
|
@@ -17,18 +16,18 @@ export async function defaultProjectDirs(
|
|
|
17
16
|
projectRootAbs: string,
|
|
18
17
|
): Promise<Readonly<DankDirectories>> {
|
|
19
18
|
if (!isAbsolute(projectRootAbs)) {
|
|
20
|
-
throw Error()
|
|
19
|
+
throw Error('must use an absolute project root path')
|
|
20
|
+
}
|
|
21
|
+
if ((await realpath(projectRootAbs)) !== projectRootAbs) {
|
|
22
|
+
throw Error('must use a real project root path')
|
|
21
23
|
}
|
|
22
|
-
const projectResolved = await realpath(projectRootAbs)
|
|
23
24
|
const pages = 'pages'
|
|
24
|
-
const pagesResolved = join(projectResolved, pages)
|
|
25
25
|
return Object.freeze({
|
|
26
26
|
buildRoot: 'build',
|
|
27
27
|
buildDist: join('build', 'dist'),
|
|
28
28
|
buildWatch: join('build', 'watch'),
|
|
29
29
|
pages,
|
|
30
|
-
|
|
31
|
-
projectResolved,
|
|
30
|
+
pagesAbs: join(projectRootAbs, pages),
|
|
32
31
|
projectRootAbs,
|
|
33
32
|
public: 'public',
|
|
34
33
|
})
|
|
@@ -63,8 +62,8 @@ export class Resolver {
|
|
|
63
62
|
|
|
64
63
|
// `p` is expected to be a relative path resolvable from the project dir
|
|
65
64
|
isProjectSubpathInPagesDir(p: string): boolean {
|
|
66
|
-
return resolve(join(this.#dirs.
|
|
67
|
-
this.#dirs.
|
|
65
|
+
return resolve(join(this.#dirs.projectRootAbs, p)).startsWith(
|
|
66
|
+
this.#dirs.pagesAbs,
|
|
68
67
|
)
|
|
69
68
|
}
|
|
70
69
|
|
package/lib/http.ts
CHANGED
|
@@ -18,7 +18,7 @@ import type {
|
|
|
18
18
|
WebsiteManifest,
|
|
19
19
|
WebsiteRegistry,
|
|
20
20
|
} from './registry.ts'
|
|
21
|
-
import type {
|
|
21
|
+
import type { DevServices } from './services.ts'
|
|
22
22
|
|
|
23
23
|
export type FrontendFetcher = (
|
|
24
24
|
url: URL,
|
|
@@ -33,7 +33,7 @@ export function startWebServer(
|
|
|
33
33
|
dirs: DankDirectories,
|
|
34
34
|
urlRewriteProvider: UrlRewriteProvider,
|
|
35
35
|
frontendFetcher: FrontendFetcher,
|
|
36
|
-
|
|
36
|
+
devServices: DevServices,
|
|
37
37
|
) {
|
|
38
38
|
const serverAddress = 'http://localhost:' + port
|
|
39
39
|
const handler = (req: IncomingMessage, res: ServerResponse) => {
|
|
@@ -47,7 +47,7 @@ export function startWebServer(
|
|
|
47
47
|
req,
|
|
48
48
|
url,
|
|
49
49
|
headers,
|
|
50
|
-
|
|
50
|
+
devServices,
|
|
51
51
|
flags,
|
|
52
52
|
dirs,
|
|
53
53
|
urlRewriteProvider,
|
|
@@ -69,7 +69,7 @@ async function onNotFound(
|
|
|
69
69
|
req: IncomingMessage,
|
|
70
70
|
url: URL,
|
|
71
71
|
headers: Headers,
|
|
72
|
-
|
|
72
|
+
devServices: DevServices,
|
|
73
73
|
flags: DankFlags,
|
|
74
74
|
dirs: DankDirectories,
|
|
75
75
|
urlRewriteProvider: UrlRewriteProvider,
|
|
@@ -87,7 +87,7 @@ async function onNotFound(
|
|
|
87
87
|
return
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
-
const fetchResponse = await tryHttpServices(req, url, headers,
|
|
90
|
+
const fetchResponse = await tryHttpServices(req, url, headers, devServices)
|
|
91
91
|
if (fetchResponse) {
|
|
92
92
|
sendFetchResponse(res, fetchResponse)
|
|
93
93
|
} else {
|
|
@@ -131,14 +131,13 @@ async function tryHttpServices(
|
|
|
131
131
|
req: IncomingMessage,
|
|
132
132
|
url: URL,
|
|
133
133
|
headers: Headers,
|
|
134
|
-
|
|
134
|
+
devServices: DevServices,
|
|
135
135
|
): Promise<Response | null> {
|
|
136
136
|
if (url.pathname.startsWith('/.well-known/')) {
|
|
137
137
|
return null
|
|
138
138
|
}
|
|
139
139
|
const body = await collectReqBody(req)
|
|
140
|
-
const
|
|
141
|
-
for (const httpService of running) {
|
|
140
|
+
for (const httpService of devServices.httpServices) {
|
|
142
141
|
const proxyUrl = new URL(url)
|
|
143
142
|
proxyUrl.port = `${httpService.port}`
|
|
144
143
|
try {
|
|
@@ -198,7 +197,7 @@ export function createBuiltDistFilesFetcher(
|
|
|
198
197
|
if (manifest.pageUrls.has(url.pathname)) {
|
|
199
198
|
streamFile(
|
|
200
199
|
join(
|
|
201
|
-
dirs.
|
|
200
|
+
dirs.projectRootAbs,
|
|
202
201
|
dirs.buildDist,
|
|
203
202
|
url.pathname,
|
|
204
203
|
'index.html',
|
|
@@ -207,7 +206,7 @@ export function createBuiltDistFilesFetcher(
|
|
|
207
206
|
)
|
|
208
207
|
} else if (manifest.files.has(url.pathname)) {
|
|
209
208
|
streamFile(
|
|
210
|
-
join(dirs.
|
|
209
|
+
join(dirs.projectRootAbs, dirs.buildDist, url.pathname),
|
|
211
210
|
res,
|
|
212
211
|
)
|
|
213
212
|
} else {
|
package/lib/serve.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
startWebServer,
|
|
14
14
|
} from './http.ts'
|
|
15
15
|
import { WebsiteRegistry, type UrlRewrite } from './registry.ts'
|
|
16
|
-
import {
|
|
16
|
+
import { DevServices, type ManagedServiceLabel } from './services.ts'
|
|
17
17
|
import { watch } from './watch.ts'
|
|
18
18
|
|
|
19
19
|
let c: ResolvedDankConfig
|
|
@@ -21,20 +21,18 @@ let c: ResolvedDankConfig
|
|
|
21
21
|
export async function serveWebsite(): Promise<never> {
|
|
22
22
|
c = await loadConfig('serve', process.cwd())
|
|
23
23
|
await rm(c.dirs.buildRoot, { force: true, recursive: true })
|
|
24
|
-
const abortController = new AbortController()
|
|
25
|
-
process.once('exit', () => abortController.abort())
|
|
26
24
|
if (c.flags.preview) {
|
|
27
|
-
await startPreviewMode(
|
|
25
|
+
await startPreviewMode()
|
|
28
26
|
} else {
|
|
29
|
-
await startDevMode(
|
|
27
|
+
await startDevMode()
|
|
30
28
|
}
|
|
31
29
|
return new Promise(() => {})
|
|
32
30
|
}
|
|
33
31
|
|
|
34
|
-
async function startPreviewMode(
|
|
32
|
+
async function startPreviewMode() {
|
|
35
33
|
const manifest = await buildWebsite(c)
|
|
36
34
|
const frontend = createBuiltDistFilesFetcher(c.dirs, manifest)
|
|
37
|
-
const devServices =
|
|
35
|
+
const devServices = launchDevServices()
|
|
38
36
|
const urlRewrites: Array<UrlRewrite> = Object.keys(c.pages)
|
|
39
37
|
.sort()
|
|
40
38
|
.map(url => {
|
|
@@ -50,8 +48,17 @@ async function startPreviewMode(signal: AbortSignal) {
|
|
|
50
48
|
c.dirs,
|
|
51
49
|
{ urlRewrites },
|
|
52
50
|
frontend,
|
|
53
|
-
devServices
|
|
51
|
+
devServices,
|
|
54
52
|
)
|
|
53
|
+
const controller = new AbortController()
|
|
54
|
+
watch('dank.config.ts', controller.signal, async filename => {
|
|
55
|
+
console.log(filename, 'was updated!')
|
|
56
|
+
console.log(
|
|
57
|
+
'config updates are not hot reloaded during `dank serve --preview`',
|
|
58
|
+
)
|
|
59
|
+
console.log('restart DANK to reload configuration')
|
|
60
|
+
controller.abort()
|
|
61
|
+
})
|
|
55
62
|
}
|
|
56
63
|
|
|
57
64
|
type BuildContextState =
|
|
@@ -61,12 +68,12 @@ type BuildContextState =
|
|
|
61
68
|
| 'disposing'
|
|
62
69
|
| null
|
|
63
70
|
|
|
64
|
-
async function startDevMode(
|
|
71
|
+
async function startDevMode() {
|
|
65
72
|
const registry = new WebsiteRegistry(c)
|
|
66
73
|
await mkdir(c.dirs.buildWatch, { recursive: true })
|
|
67
74
|
let buildContext: BuildContextState = null
|
|
68
75
|
|
|
69
|
-
watch('dank.config.ts',
|
|
76
|
+
watch('dank.config.ts', async filename => {
|
|
70
77
|
LOG({
|
|
71
78
|
realm: 'serve',
|
|
72
79
|
message: 'config watch event',
|
|
@@ -80,10 +87,10 @@ async function startDevMode(signal: AbortSignal) {
|
|
|
80
87
|
return
|
|
81
88
|
}
|
|
82
89
|
registry.configSync()
|
|
83
|
-
|
|
90
|
+
devServices.update(c.services)
|
|
84
91
|
})
|
|
85
92
|
|
|
86
|
-
watch(c.dirs.pages,
|
|
93
|
+
watch(c.dirs.pages, { recursive: true }, filename => {
|
|
87
94
|
LOG({
|
|
88
95
|
realm: 'serve',
|
|
89
96
|
message: 'pages dir watch event',
|
|
@@ -163,15 +170,8 @@ async function startDevMode(signal: AbortSignal) {
|
|
|
163
170
|
resetBuildContext()
|
|
164
171
|
|
|
165
172
|
const frontend = createDevServeFilesFetcher(c.esbuildPort, c.dirs, registry)
|
|
166
|
-
const devServices =
|
|
167
|
-
startWebServer(
|
|
168
|
-
c.dankPort,
|
|
169
|
-
c.flags,
|
|
170
|
-
c.dirs,
|
|
171
|
-
registry,
|
|
172
|
-
frontend,
|
|
173
|
-
devServices.http,
|
|
174
|
-
)
|
|
173
|
+
const devServices = launchDevServices()
|
|
174
|
+
startWebServer(c.dankPort, c.flags, c.dirs, registry, frontend, devServices)
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
async function startEsbuildWatch(
|
|
@@ -220,3 +220,47 @@ async function writeHtml(html: HtmlEntrypoint, output: string) {
|
|
|
220
220
|
})
|
|
221
221
|
await writeFile(path, output)
|
|
222
222
|
}
|
|
223
|
+
|
|
224
|
+
function launchDevServices(): DevServices {
|
|
225
|
+
const services = new DevServices(c.services)
|
|
226
|
+
services.on('error', (label, cause) =>
|
|
227
|
+
console.log(formatServiceLabel(label), 'errored:', cause),
|
|
228
|
+
)
|
|
229
|
+
services.on('exit', (label, code) => {
|
|
230
|
+
if (code) {
|
|
231
|
+
console.log(formatServiceLabel(label), 'exited', code)
|
|
232
|
+
} else {
|
|
233
|
+
console.log(formatServiceLabel(label), 'exited')
|
|
234
|
+
}
|
|
235
|
+
})
|
|
236
|
+
services.on('launch', label =>
|
|
237
|
+
console.log(formatServiceLabel(label), 'starting'),
|
|
238
|
+
)
|
|
239
|
+
services.on('stdout', (label, output) =>
|
|
240
|
+
printServiceOutput(label, 32, output),
|
|
241
|
+
)
|
|
242
|
+
services.on('stderr', (label, output) =>
|
|
243
|
+
printServiceOutput(label, 31, output),
|
|
244
|
+
)
|
|
245
|
+
return services
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function formatServiceLabel(label: ManagedServiceLabel): string {
|
|
249
|
+
return `| \u001b[2m${label.cwd}\u001b[22m ${label.command} |`
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function formatServiceOutputLabel(
|
|
253
|
+
label: ManagedServiceLabel,
|
|
254
|
+
color: 31 | 32,
|
|
255
|
+
): string {
|
|
256
|
+
return `\u001b[${color}m${formatServiceLabel(label)}\u001b[39m`
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function printServiceOutput(
|
|
260
|
+
label: ManagedServiceLabel,
|
|
261
|
+
color: 31 | 32,
|
|
262
|
+
output: Array<string>,
|
|
263
|
+
) {
|
|
264
|
+
const formattedLabel = formatServiceOutputLabel(label, color)
|
|
265
|
+
for (const line of output) console.log(formattedLabel, line)
|
|
266
|
+
}
|