@eighty4/dank 0.0.4-2 → 0.0.4

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.
@@ -0,0 +1 @@
1
+ new EventSource("http://127.0.0.1:3995/esbuild").addEventListener("change",o=>{let{updated:a}=JSON.parse(o.data),e=new Set;for(let s of a)e.add(s);let d=Array.from(e).filter(s=>s.endsWith(".css"));if(d.length){console.log("esbuild css updates",d);let s={};for(let t of document.getElementsByTagName("link"))if(t.getAttribute("rel")==="stylesheet"){let n=new URL(t.href);(n.host=location.host)&&(s[n.pathname]=t)}let r=!1;for(let t of d){let n=s[t];if(n){let c=n.cloneNode();c.href=`${t}?${Math.random().toString(36).slice(2)}`,c.onload=()=>n.remove(),n.parentNode.insertBefore(c,n.nextSibling),r=!0}}r&&p()}if(d.length<e.size){let s=Array.from(e).filter(t=>!t.endsWith(".css")),r=new Set;for(let t of document.getElementsByTagName("script"))if(t.src.length){let n=new URL(t.src);(n.host=location.host)&&r.add(n.pathname)}s.some(t=>r.has(t))&&(console.log("esbuild js updates require reload"),y())}});function p(){let o=l("green","23995");o.style.opacity="0",o.animate([{opacity:0},{opacity:1},{opacity:1},{opacity:1},{opacity:.75},{opacity:.5},{opacity:.25},{opacity:0}],{duration:400,iterations:1,direction:"normal",easing:"linear"}),document.body.appendChild(o),Promise.all(o.getAnimations().map(a=>a.finished)).then(()=>o.remove())}var i=null;function y(){i||(i=l("orange","33995"),i.style.opacity="0",i.style.pointerEvents="none",i.animate([{opacity:0},{opacity:1}],{duration:400,iterations:1,direction:"normal",easing:"ease-in",fill:"forwards"}),document.body.appendChild(i))}function l(o,a){let e=document.createElement("div");return e.style.border="6px dashed "+o,e.style.zIndex=a,e.style.position="fixed",e.style.top=e.style.left="1px",e.style.height=e.style.width="calc(100% - 2px)",e.style.boxSizing="border-box",e}
package/lib/bin.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { buildWebsite } from './build.ts'
4
- import { loadConfig } from './config.ts'
4
+ import { DankError } from './errors.ts'
5
5
  import { serveWebsite } from './serve.ts'
6
6
 
7
7
  function printHelp(task?: 'build' | 'serve'): never {
@@ -71,16 +71,14 @@ const task: 'build' | 'serve' = (function resolveTask() {
71
71
  return task
72
72
  })()
73
73
 
74
- const c = await loadConfig(task)
75
-
76
74
  try {
77
75
  switch (task) {
78
76
  case 'build':
79
- await buildWebsite(c)
77
+ await buildWebsite()
80
78
  console.log(green('done'))
81
79
  process.exit(0)
82
80
  case 'serve':
83
- await serveWebsite(c)
81
+ await serveWebsite()
84
82
  }
85
83
  } catch (e: unknown) {
86
84
  errorExit(e)
@@ -88,13 +86,12 @@ try {
88
86
 
89
87
  function printError(e: unknown) {
90
88
  if (e !== null) {
91
- if (typeof e === 'string') {
92
- console.error(red('error:'), e)
93
- } else if (e instanceof Error) {
89
+ if (e instanceof DankError) {
94
90
  console.error(red('error:'), e.message)
95
- if (e.stack) {
96
- console.error(e.stack)
97
- }
91
+ } else if (e instanceof Error) {
92
+ console.error(red('error:'), e.stack ?? e.message)
93
+ } else {
94
+ console.error(red('error:'), e)
98
95
  }
99
96
  }
100
97
  }
package/lib/build.ts CHANGED
@@ -1,20 +1,23 @@
1
1
  import { mkdir, readFile, rm, writeFile } from 'node:fs/promises'
2
2
  import { join } from 'node:path'
3
3
  import { createBuildTag } from './build_tag.ts'
4
- import type { DankConfig } from './dank.ts'
4
+ import { loadConfig, type ResolvedDankConfig } from './config.ts'
5
5
  import { type DefineDankGlobal, createGlobalDefinitions } from './define.ts'
6
- import { esbuildWebpages, esbuildWorkers, type EntryPoint } from './esbuild.ts'
7
- import { resolveBuildFlags, type DankBuild } from './flags.ts'
8
- import { HtmlEntrypoint } from './html.ts'
9
- import { type WebsiteManifest, WebsiteRegistry } from './metadata.ts'
6
+ import type { DankDirectories } from './dirs.ts'
7
+ import { esbuildWebpages, esbuildWorkers } from './esbuild.ts'
10
8
  import { copyAssets } from './public.ts'
9
+ import { type WebsiteManifest, WebsiteRegistry } from './registry.ts'
11
10
 
12
- export async function buildWebsite(c: DankConfig): Promise<WebsiteManifest> {
13
- const build: DankBuild = resolveBuildFlags()
14
- const buildTag = await createBuildTag(build)
11
+ export async function buildWebsite(
12
+ c?: ResolvedDankConfig,
13
+ ): Promise<WebsiteManifest> {
14
+ if (!c) {
15
+ c = await loadConfig('build', process.cwd())
16
+ }
17
+ const buildTag = await createBuildTag(c.flags)
15
18
  console.log(
16
- build.minify
17
- ? build.production
19
+ c.flags.minify
20
+ ? c.flags.production
18
21
  ? 'minified production'
19
22
  : 'minified'
20
23
  : 'unminified',
@@ -22,85 +25,52 @@ export async function buildWebsite(c: DankConfig): Promise<WebsiteManifest> {
22
25
  buildTag,
23
26
  'building in ./build/dist',
24
27
  )
25
- await rm(build.dirs.buildRoot, { recursive: true, force: true })
26
- await mkdir(build.dirs.buildDist, { recursive: true })
28
+ await rm(c.dirs.buildRoot, { recursive: true, force: true })
29
+ await mkdir(c.dirs.buildDist, { recursive: true })
27
30
  for (const subdir of Object.keys(c.pages).filter(url => url !== '/')) {
28
- await mkdir(join(build.dirs.buildDist, subdir), { recursive: true })
31
+ await mkdir(join(c.dirs.buildDist, subdir), { recursive: true })
29
32
  }
30
- await mkdir(join(build.dirs.buildRoot, 'metafiles'), { recursive: true })
31
- const registry = new WebsiteRegistry(build)
32
- registry.pageUrls = Object.keys(c.pages)
33
- registry.copiedAssets = await copyAssets(build)
34
- await buildWebpages(c, registry, build, createGlobalDefinitions(build))
33
+ await mkdir(join(c.dirs.buildRoot, 'metafiles'), { recursive: true })
34
+ const registry = await buildWebpages(c, createGlobalDefinitions(c))
35
35
  return await registry.writeManifest(buildTag)
36
36
  }
37
37
 
38
- // builds all webpage entrypoints in one esbuild.build context
39
- // to support code splitting
38
+ // builds all webpage entrypoints in one esbuild.build context to support code splitting
40
39
  // returns all built assets URLs and webpage URLs from DankConfig.pages
41
40
  async function buildWebpages(
42
- c: DankConfig,
43
- registry: WebsiteRegistry,
44
- build: DankBuild,
41
+ c: ResolvedDankConfig,
45
42
  define: DefineDankGlobal,
46
- ) {
47
- // create HtmlEntrypoint for each webpage and collect awaitable esbuild entrypoints
48
- const loadingEntryPoints: Array<Promise<Array<EntryPoint>>> = []
49
- const htmlEntrypoints: Array<HtmlEntrypoint> = []
50
- for (const [urlPath, mapping] of Object.entries(c.pages)) {
51
- const fsPath = typeof mapping === 'string' ? mapping : mapping.webpage
52
- const html = new HtmlEntrypoint(
53
- build,
54
- registry.resolver,
55
- urlPath,
56
- fsPath,
57
- )
58
- loadingEntryPoints.push(new Promise(res => html.on('entrypoints', res)))
59
- htmlEntrypoints.push(html)
60
- }
61
-
62
- // collect esbuild entrypoints from every HtmlEntrypoint
63
- const uniqueEntryPoints: Set<string> = new Set()
64
- const buildEntryPoints: Array<EntryPoint> = []
65
- for (const pageEntryPoints of await Promise.all(loadingEntryPoints)) {
66
- for (const entryPoint of pageEntryPoints) {
67
- if (!uniqueEntryPoints.has(entryPoint.in)) {
68
- buildEntryPoints.push(entryPoint)
69
- }
70
- }
71
- }
72
-
73
- await esbuildWebpages(build, registry, define, buildEntryPoints, c.esbuild)
43
+ ): Promise<WebsiteRegistry> {
44
+ const registry = new WebsiteRegistry(c)
45
+ registry.configSync()
46
+ registry.copiedAssets = await copyAssets(c.dirs)
47
+ await Promise.all(registry.htmlEntrypoints.map(html => html.process()))
48
+ await esbuildWebpages(registry, define, registry.webpageEntryPoints)
74
49
 
75
50
  // todo recursively build workers on building workers that create workers
76
- const workerEntryPoints = registry.workerEntryPoints()
51
+ const workerEntryPoints = registry.workerEntryPoints
77
52
  if (workerEntryPoints?.length) {
78
- await esbuildWorkers(
79
- build,
80
- registry,
81
- define,
82
- workerEntryPoints,
83
- c.esbuild,
84
- )
53
+ await esbuildWorkers(registry, define, workerEntryPoints)
85
54
  }
86
- await rewriteWorkerUrls(build, registry)
55
+ await rewriteWorkerUrls(c.dirs, registry)
87
56
 
88
57
  // write out html output with rewritten hrefs
89
58
  await Promise.all(
90
- htmlEntrypoints.map(async html => {
59
+ registry.htmlEntrypoints.map(async html => {
91
60
  await writeFile(
92
- join(build.dirs.buildDist, html.url, 'index.html'),
61
+ join(c.dirs.buildDist, html.url, 'index.html'),
93
62
  html.output(registry),
94
63
  )
95
64
  }),
96
65
  )
66
+ return registry
97
67
  }
98
68
 
99
69
  export async function rewriteWorkerUrls(
100
- build: DankBuild,
70
+ dirs: DankDirectories,
101
71
  registry: WebsiteRegistry,
102
72
  ) {
103
- const workers = registry.workers()
73
+ const workers = registry.workers
104
74
  if (!workers) {
105
75
  return
106
76
  }
@@ -113,7 +83,7 @@ export async function rewriteWorkerUrls(
113
83
  const readingFiles = Promise.all(
114
84
  dependentBundlePaths.map(async p => {
115
85
  bundleOutputs[p] = await readFile(
116
- join(build.dirs.projectRootAbs, build.dirs.buildDist, p),
86
+ join(dirs.projectRootAbs, dirs.buildDist, p),
117
87
  'utf8',
118
88
  )
119
89
  }),
@@ -125,8 +95,8 @@ export async function rewriteWorkerUrls(
125
95
  for (const w of workers) {
126
96
  rewriteChains[registry.mappedHref(w.dependentEntryPoint)].push(s =>
127
97
  s.replace(
128
- createWorkerRegex(w.workerUrlPlaceholder),
129
- `new Worker('${registry.mappedHref(w.workerEntryPoint)}')`,
98
+ createWorkerRegex(w.workerCtor, w.workerUrlPlaceholder),
99
+ `new ${w.workerCtor}('${registry.mappedHref(w.workerEntryPoint)}')`,
130
100
  ),
131
101
  )
132
102
  }
@@ -142,16 +112,19 @@ export async function rewriteWorkerUrls(
142
112
  result = rewriteFn(result)
143
113
  }
144
114
  await writeFile(
145
- join(build.dirs.projectRootAbs, build.dirs.buildDist, p),
115
+ join(dirs.projectRootAbs, dirs.buildDist, p),
146
116
  result,
147
117
  )
148
118
  }),
149
119
  )
150
120
  }
151
121
 
152
- export function createWorkerRegex(workerUrl: string): RegExp {
122
+ export function createWorkerRegex(
123
+ workerCtor: 'Worker' | 'SharedWorker',
124
+ workerUrl: string,
125
+ ): RegExp {
153
126
  return new RegExp(
154
- `new(?:\\s|\\r?\\n)+Worker(?:\\s|\\r?\\n)*\\((?:\\s|\\r?\\n)*['"]${workerUrl}['"](?:\\s|\\r?\\n)*\\)`,
127
+ `new(?:\\s|\\r?\\n)+${workerCtor}(?:\\s|\\r?\\n)*\\((?:\\s|\\r?\\n)*['"]${workerUrl}['"](?:\\s|\\r?\\n)*\\)`,
155
128
  'g',
156
129
  )
157
130
  }
package/lib/build_tag.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { exec } from 'node:child_process'
2
- import type { DankBuild } from './flags.ts'
2
+ import type { DankFlags } from './flags.ts'
3
3
 
4
- export async function createBuildTag(build: DankBuild): Promise<string> {
4
+ export async function createBuildTag(flags: DankFlags): Promise<string> {
5
5
  const now = new Date()
6
6
  const ms =
7
7
  now.getUTCMilliseconds() +
@@ -11,7 +11,7 @@ export async function createBuildTag(build: DankBuild): Promise<string> {
11
11
  const date = now.toISOString().substring(0, 10)
12
12
  const time = String(ms).padStart(8, '0')
13
13
  const when = `${date}-${time}`
14
- if (build.production) {
14
+ if (flags.production) {
15
15
  const gitHash = await new Promise((res, rej) =>
16
16
  exec('git rev-parse --short HEAD', (err, stdout) => {
17
17
  if (err) rej(err)
package/lib/config.ts CHANGED
@@ -6,15 +6,45 @@ import type {
6
6
  PageMapping,
7
7
  } from './dank.ts'
8
8
  import { LOG } from './developer.ts'
9
- import { isProductionBuild } from './flags.ts'
9
+ import { defaultProjectDirs, type DankDirectories } from './dirs.ts'
10
+ import {
11
+ resolveFlags as lookupDankFlags,
12
+ type DankFlags as DankFlags,
13
+ } from './flags.ts'
10
14
 
11
- const CFG_P = './dank.config.ts'
15
+ const DEFAULT_DEV_PORT = 3000
16
+ const DEFAULT_PREVIEW_PORT = 4000
17
+ const DEFAULT_ESBUILD_PORT = 3995
18
+
19
+ const DEFAULT_CONFIG_PATH = './dank.config.ts'
20
+
21
+ export type { DevService } from './dank.ts'
22
+
23
+ export type ResolvedDankConfig = {
24
+ // static from process boot
25
+ get dirs(): Readonly<DankDirectories>
26
+ get flags(): Readonly<Omit<DankFlags, 'dankPort' | 'esbuildPort'>>
27
+ get mode(): 'build' | 'serve'
28
+
29
+ // reloadable from `dank.config.ts` with `reload()`
30
+ get dankPort(): number
31
+ get esbuildPort(): number
32
+ get esbuild(): Readonly<Omit<EsbuildConfig, 'port'>> | undefined
33
+ get pages(): Readonly<Record<`/${string}`, PageMapping>>
34
+ get devPages(): Readonly<DankConfig['devPages']>
35
+ get services(): Readonly<DankConfig['services']>
36
+
37
+ reload(): Promise<void>
38
+ }
12
39
 
13
40
  export async function loadConfig(
14
41
  mode: 'build' | 'serve',
15
- path: string = CFG_P,
16
- ): Promise<DankConfig> {
17
- const modulePath = resolveConfigPath(path)
42
+ projectRootAbs: string,
43
+ ): Promise<ResolvedDankConfig> {
44
+ if (!isAbsolute(projectRootAbs)) {
45
+ throw Error()
46
+ }
47
+ const modulePath = resolve(projectRootAbs, DEFAULT_CONFIG_PATH)
18
48
  LOG({
19
49
  realm: 'config',
20
50
  message: 'loading config module',
@@ -22,37 +52,119 @@ export async function loadConfig(
22
52
  modulePath,
23
53
  },
24
54
  })
25
- const c = await resolveConfig(mode, modulePath)
26
- normalizePagePaths(c.pages)
55
+ const dirs = await defaultProjectDirs(projectRootAbs)
56
+ const c = new DankConfigInternal(mode, modulePath, dirs)
57
+ await c.reload()
27
58
  return c
28
59
  }
29
60
 
30
- export function resolveConfigPath(path: string): string {
31
- if (isAbsolute(path)) {
32
- return path
33
- } else {
34
- return resolve(process.cwd(), path)
61
+ class DankConfigInternal implements ResolvedDankConfig {
62
+ #dirs: Readonly<DankDirectories>
63
+ #flags: Readonly<DankFlags>
64
+ #mode: 'build' | 'serve'
65
+ #modulePath: string
66
+
67
+ #dankPort: number = DEFAULT_DEV_PORT
68
+ #esbuildPort: number = DEFAULT_ESBUILD_PORT
69
+ #esbuild: Readonly<Omit<EsbuildConfig, 'port'>> | undefined
70
+ #pages: Readonly<Record<`/${string}`, PageMapping>> = {}
71
+ #devPages: Readonly<DankConfig['devPages']>
72
+ #services: Readonly<DankConfig['services']>
73
+
74
+ constructor(
75
+ mode: 'build' | 'serve',
76
+ modulePath: string,
77
+ dirs: DankDirectories,
78
+ ) {
79
+ this.#dirs = dirs
80
+ this.#flags = lookupDankFlags()
81
+ this.#mode = mode
82
+ this.#modulePath = modulePath
83
+ }
84
+
85
+ get dankPort(): number {
86
+ return this.#dankPort
87
+ }
88
+
89
+ get esbuildPort(): number {
90
+ return this.#esbuildPort
91
+ }
92
+
93
+ get esbuild(): Omit<EsbuildConfig, 'port'> | undefined {
94
+ return this.#esbuild
95
+ }
96
+
97
+ get dirs(): Readonly<DankDirectories> {
98
+ return this.#dirs
99
+ }
100
+
101
+ get flags(): Readonly<Omit<DankFlags, 'dankPort' | 'esbuildPort'>> {
102
+ return this.#flags
103
+ }
104
+
105
+ get mode(): 'build' | 'serve' {
106
+ return this.#mode
107
+ }
108
+
109
+ get pages(): Readonly<Record<`/${string}`, PageMapping>> {
110
+ return this.#pages
111
+ }
112
+
113
+ get devPages(): Readonly<DankConfig['devPages']> {
114
+ return this.#devPages
115
+ }
116
+
117
+ get services(): Readonly<DankConfig['services']> {
118
+ return this.#services
119
+ }
120
+
121
+ async reload() {
122
+ const userConfig = await resolveConfig(
123
+ this.#modulePath,
124
+ resolveDankDetails(this.#mode, this.#flags),
125
+ )
126
+ this.#dankPort = resolveDankPort(this.#flags, userConfig)
127
+ this.#esbuildPort = resolveEsbuildPort(this.#flags, userConfig)
128
+ this.#esbuild = Object.freeze(userConfig.esbuild)
129
+ this.#pages = Object.freeze(normalizePages(userConfig.pages))
130
+ this.#devPages = Object.freeze(userConfig.devPages)
131
+ this.#services = Object.freeze(userConfig.services)
35
132
  }
36
133
  }
37
134
 
38
- export async function resolveConfig(
39
- mode: 'build' | 'serve',
135
+ function resolveDankPort(flags: DankFlags, userConfig: DankConfig): number {
136
+ return (
137
+ flags.dankPort ||
138
+ (flags.preview
139
+ ? userConfig.previewPort || userConfig.port || DEFAULT_PREVIEW_PORT
140
+ : userConfig.port || DEFAULT_DEV_PORT)
141
+ )
142
+ }
143
+
144
+ function resolveEsbuildPort(flags: DankFlags, userConfig: DankConfig): number {
145
+ return flags.esbuildPort || userConfig.esbuild?.port || DEFAULT_ESBUILD_PORT
146
+ }
147
+
148
+ async function resolveConfig(
40
149
  modulePath: string,
150
+ details: DankDetails,
41
151
  ): Promise<DankConfig> {
42
152
  const module = await import(`${modulePath}?${Date.now()}`)
43
153
  const c: Partial<DankConfig> =
44
154
  typeof module.default === 'function'
45
- ? await module.default(resolveDankDetails(mode))
155
+ ? await module.default(details)
46
156
  : module.default
47
157
  validateDankConfig(c)
48
158
  return c as DankConfig
49
159
  }
50
160
 
51
- function resolveDankDetails(mode: 'build' | 'serve'): DankDetails {
52
- const production = isProductionBuild()
161
+ function resolveDankDetails(
162
+ mode: 'build' | 'serve',
163
+ flags: DankFlags,
164
+ ): DankDetails {
53
165
  return {
54
- dev: !production,
55
- production,
166
+ dev: !flags.production,
167
+ production: flags.production,
56
168
  mode,
57
169
  }
58
170
  }
@@ -61,6 +173,7 @@ function validateDankConfig(c: Partial<DankConfig>) {
61
173
  try {
62
174
  validatePorts(c)
63
175
  validatePages(c.pages)
176
+ validateDevPages(c.devPages)
64
177
  validateDevServices(c.services)
65
178
  validateEsbuildConfig(c.esbuild)
66
179
  } catch (e: any) {
@@ -140,6 +253,46 @@ function validatePages(pages?: DankConfig['pages']) {
140
253
  }
141
254
  }
142
255
 
256
+ function validateDevPages(devPages?: DankConfig['devPages']) {
257
+ if (devPages) {
258
+ for (const [urlPath, mapping] of Object.entries(devPages)) {
259
+ if (!urlPath.startsWith('/__')) {
260
+ throw Error(
261
+ `DankConfig.devPages['${urlPath}'] must start \`${urlPath}\` with a \`/__\` path prefix`,
262
+ )
263
+ }
264
+ if (typeof mapping === 'string') {
265
+ if (!mapping.endsWith('.html')) {
266
+ throw Error(
267
+ `DankConfig.devPages['${urlPath}'] must configure an html file or DevPageMapping config`,
268
+ )
269
+ }
270
+ } else if (typeof mapping === 'object') {
271
+ if (
272
+ typeof mapping.label !== 'string' ||
273
+ !mapping.label.length
274
+ ) {
275
+ throw Error(
276
+ `DankConfig.devPages['${urlPath}'].label must declare a label`,
277
+ )
278
+ }
279
+ if (
280
+ typeof mapping.webpage !== 'string' ||
281
+ !mapping.webpage.endsWith('.html')
282
+ ) {
283
+ throw Error(
284
+ `DankConfig.devPages['${urlPath}'].webpage must configure an html file`,
285
+ )
286
+ }
287
+ } else {
288
+ throw Error(
289
+ `DankConfig.devPages['${urlPath}'] must be a DevPageMapping config with \`label\` and \`webpage\` values`,
290
+ )
291
+ }
292
+ }
293
+ }
294
+ }
295
+
143
296
  function validatePageMapping(urlPath: string, mapping: PageMapping) {
144
297
  if (
145
298
  mapping.webpage === null ||
@@ -209,16 +362,18 @@ function validateDevServices(services: DankConfig['services']) {
209
362
  }
210
363
  }
211
364
 
212
- function normalizePagePaths(pages: DankConfig['pages']) {
365
+ function normalizePages(
366
+ pages: DankConfig['pages'],
367
+ ): Record<`/${string}`, PageMapping> {
368
+ const result: Record<`/${string}`, PageMapping> = {}
213
369
  for (const [pageUrl, mapping] of Object.entries(pages)) {
214
- if (typeof mapping === 'string') {
215
- pages[pageUrl as `/${string}`] = normalizePagePath(mapping)
216
- } else {
217
- mapping.webpage = normalizePagePath(mapping.webpage)
218
- }
370
+ const mappedMapping =
371
+ typeof mapping === 'string' ? { webpage: mapping } : mapping
372
+ mappedMapping.webpage = mappedMapping.webpage.replace(
373
+ /^\.\//,
374
+ '',
375
+ ) as `${string}.html`
376
+ result[pageUrl as `/${string}`] = mappedMapping
219
377
  }
220
- }
221
-
222
- function normalizePagePath(p: `${string}.html`): `${string}.html` {
223
- return p.replace(/^\.\//, '') as `${string}.html`
378
+ return result
224
379
  }
package/lib/dank.ts CHANGED
@@ -13,6 +13,8 @@ export type DankConfig = {
13
13
  // cdn url rewriting can be simulated with PageMapping
14
14
  pages: Record<`/${string}`, `${string}.html` | PageMapping>
15
15
 
16
+ devPages?: Record<`/__${string}`, `${string}.html` | DevPageMapping>
17
+
16
18
  // port of `dank serve` frontend dev server
17
19
  // used for `dan serve --preview` if previewPort not specified
18
20
  port?: number
@@ -32,6 +34,11 @@ export type PageMapping = {
32
34
  webpage: `${string}.html`
33
35
  }
34
36
 
37
+ export type DevPageMapping = {
38
+ label: string
39
+ webpage: `${string}.html`
40
+ }
41
+
35
42
  export type DevService = {
36
43
  command: string
37
44
  cwd?: string
package/lib/define.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { DankBuild } from './flags.ts'
1
+ import type { ResolvedDankConfig } from './config.ts'
2
2
 
3
3
  export type DankGlobal = {
4
4
  IS_DEV: boolean
@@ -9,9 +9,11 @@ type DefineDankGlobalKey = 'dank.IS_DEV' | 'dank.IS_PROD'
9
9
 
10
10
  export type DefineDankGlobal = Record<DefineDankGlobalKey, string>
11
11
 
12
- export function createGlobalDefinitions(build: DankBuild): DefineDankGlobal {
12
+ export function createGlobalDefinitions(
13
+ c: ResolvedDankConfig,
14
+ ): DefineDankGlobal {
13
15
  return {
14
- 'dank.IS_DEV': JSON.stringify(!build.production),
15
- 'dank.IS_PROD': JSON.stringify(build.production),
16
+ 'dank.IS_DEV': JSON.stringify(!c.flags.production),
17
+ 'dank.IS_PROD': JSON.stringify(c.flags.production),
16
18
  }
17
19
  }
package/lib/developer.ts CHANGED
@@ -4,6 +4,9 @@ import os from 'node:os'
4
4
  import { dirname, resolve } from 'node:path'
5
5
  import packageJson from '../package.json' with { type: 'json' }
6
6
 
7
+ const CONSOLE =
8
+ process.env.DANK_LOG_CONSOLE === '1' ||
9
+ process.env.DANK_LOG_CONSOLE === 'true'
7
10
  const FILE = process.env.DANK_LOG_FILE
8
11
  const ROLLING =
9
12
  process.env.DANK_LOG_ROLLING === '1' ||
@@ -33,7 +36,7 @@ type LogEventData =
33
36
  | Set<LogEventDatum>
34
37
  | Record<string, LogEventDatum>
35
38
 
36
- type LogEventDatum = boolean | number | string | null
39
+ type LogEventDatum = boolean | number | string | null | undefined
37
40
 
38
41
  function toStringLogEvent(logEvent: LogEvent): string {
39
42
  const when = new Date().toISOString()
@@ -70,8 +73,17 @@ function toStringData(datum: LogEventData): string {
70
73
  }
71
74
  }
72
75
 
73
- function logToFile(logEvent: LogEvent) {
74
- logs.push(toStringLogEvent(logEvent))
76
+ function logToConsoleAndFile(out: string) {
77
+ logToConsole(out)
78
+ logToFile(out)
79
+ }
80
+
81
+ function logToConsole(out: string) {
82
+ console.log('\n' + out)
83
+ }
84
+
85
+ function logToFile(out: string) {
86
+ logs.push(out)
75
87
  if (!initialized) {
76
88
  initialized = true
77
89
  preparing = prepareLogFile().catch(onPrepareLogFileError)
@@ -114,4 +126,21 @@ function onPrepareLogFileError(e: any) {
114
126
  process.exit(1)
115
127
  }
116
128
 
117
- export const LOG = FILE?.length ? logToFile : () => {}
129
+ function makeLogger(
130
+ logDelegate: (out: string) => void,
131
+ ): (logEvent: LogEvent) => void {
132
+ return logEvent => logDelegate(toStringLogEvent(logEvent))
133
+ }
134
+
135
+ export const LOG = (function resolveLogFn() {
136
+ if (CONSOLE && FILE?.length) {
137
+ return makeLogger(logToConsoleAndFile)
138
+ }
139
+ if (CONSOLE) {
140
+ return makeLogger(logToConsole)
141
+ }
142
+ if (FILE?.length) {
143
+ return makeLogger(logToFile)
144
+ }
145
+ return () => {}
146
+ })()