@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 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(`${modulePath}?${Date.now()}`)
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
- | Array<LogEventDatum>
36
- | Set<LogEventDatum>
37
- | Record<string, LogEventDatum>
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 (!logEvent.data) {
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
- function toStringData(datum: LogEventData): string {
55
- if (datum instanceof Set) {
56
- datum = Array.from(datum)
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
- datum !== null &&
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(datum)) {
66
- if (datum.length === 0) {
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 `${datum}`
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('\n' + out)
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
- const clientScript = args.path
147
- .replace(r.dirs.projectResolved, '')
148
- .substring(1)
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, join, relative, resolve } from 'node:path'
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 = new Resolver(config.dirs)
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 { type ChildProcess, spawn } from 'node:child_process'
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 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
- })
160
+ const spawned = spawnService(s)
153
161
 
154
- const stdoutLabel = logLabel(s.cwd, cmd, args, 32)
162
+ const stdoutLabel = logLabel(s, 32)
155
163
  spawned.stdout.on('data', chunk => printChunk(stdoutLabel, chunk))
156
164
 
157
- const stderrLabel = logLabel(s.cwd, cmd, args, 31)
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: string | undefined,
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${cmd}\u001b[22m ${args.join(' ')} \u001b[2;3m${cwd}\u001b[22;23m]\u001b[0m`
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(`${modulePath}?${Date.now()}`));
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
- const clientScript = args.path.replace(r.dirs.projectResolved, "").substring(1);
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, join, relative, resolve } from "node:path";
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";
@@ -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 = new Resolver(config.dirs);
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) {
@@ -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 splitCmdAndArgs = s.command.split(/\s+/);
115
- const cmd = splitCmdAndArgs[0];
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.cwd, cmd, args, 31);
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(cwd, cmd, args, ansiColor) {
185
- cwd = !cwd ? "./" : cwd.startsWith("/") ? `/.../${basename(cwd)}` : cwd.startsWith(".") ? cwd : `./${cwd}`;
186
- return `\x1B[${ansiColor}m[\x1B[1m${cmd}\x1B[22m ${args.join(" ")} \x1B[2;3m${cwd}\x1B[22;23m]\x1B[0m`;
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.4",
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 && ./scripts/prepare_release.ts",
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\"",