@eighty4/dank 0.0.4-1 → 0.0.4-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/client/esbuild.js CHANGED
@@ -1,91 +1 @@
1
- new EventSource('http://127.0.0.1:3995/esbuild').addEventListener('change', (e) => {
2
- const { updated } = JSON.parse(e.data);
3
- const changes = new Set();
4
- for (const c of updated)
5
- changes.add(c);
6
- const cssUpdates = Array.from(changes).filter(p => p.endsWith('.css'));
7
- if (cssUpdates.length) {
8
- console.log('esbuild css updates', cssUpdates);
9
- const cssLinks = {};
10
- for (const elem of document.getElementsByTagName('link')) {
11
- if (elem.getAttribute('rel') === 'stylesheet') {
12
- const url = new URL(elem.href);
13
- if ((url.host = location.host)) {
14
- cssLinks[url.pathname] = elem;
15
- }
16
- }
17
- }
18
- let swappedCss = false;
19
- for (const cssUpdate of cssUpdates) {
20
- const cssLink = cssLinks[cssUpdate];
21
- if (cssLink) {
22
- const next = cssLink.cloneNode();
23
- next.href = `${cssUpdate}?${Math.random().toString(36).slice(2)}`;
24
- next.onload = () => cssLink.remove();
25
- cssLink.parentNode.insertBefore(next, cssLink.nextSibling);
26
- swappedCss = true;
27
- }
28
- }
29
- if (swappedCss) {
30
- addCssUpdateIndicator();
31
- }
32
- }
33
- if (cssUpdates.length < changes.size) {
34
- const jsUpdates = Array.from(changes).filter(p => !p.endsWith('.css'));
35
- const jsScripts = new Set();
36
- for (const elem of document.getElementsByTagName('script')) {
37
- if (elem.src.length) {
38
- const url = new URL(elem.src);
39
- if ((url.host = location.host)) {
40
- jsScripts.add(url.pathname);
41
- }
42
- }
43
- }
44
- if (jsUpdates.some(jsUpdate => jsScripts.has(jsUpdate))) {
45
- console.log('esbuild js updates require reload');
46
- addJsReloadIndicator();
47
- }
48
- }
49
- });
50
- export function addCssUpdateIndicator() {
51
- const indicator = createUpdateIndicator('green', '9999');
52
- indicator.style.opacity = '0';
53
- indicator.animate([
54
- { opacity: 0 },
55
- { opacity: 1 },
56
- { opacity: 1 },
57
- { opacity: 1 },
58
- { opacity: 0.75 },
59
- { opacity: 0.5 },
60
- { opacity: 0.25 },
61
- { opacity: 0 },
62
- ], {
63
- duration: 400,
64
- iterations: 1,
65
- direction: 'normal',
66
- easing: 'linear',
67
- });
68
- document.body.appendChild(indicator);
69
- Promise.all(indicator.getAnimations().map(a => a.finished)).then(() => indicator.remove());
70
- }
71
- function addJsReloadIndicator() {
72
- const indicator = createUpdateIndicator('orange', '9000');
73
- indicator.style.opacity = '0';
74
- indicator.animate([{ opacity: 0 }, { opacity: 1 }], {
75
- duration: 400,
76
- iterations: 1,
77
- direction: 'normal',
78
- easing: 'ease-in',
79
- });
80
- document.body.appendChild(indicator);
81
- }
82
- function createUpdateIndicator(color, zIndex) {
83
- const indicator = document.createElement('div');
84
- indicator.style.border = '6px dashed ' + color;
85
- indicator.style.zIndex = zIndex;
86
- indicator.style.position = 'fixed';
87
- indicator.style.top = indicator.style.left = '1px';
88
- indicator.style.height = indicator.style.width = 'calc(100% - 2px)';
89
- indicator.style.boxSizing = 'border-box';
90
- return indicator;
91
- }
1
+ new EventSource("http://127.0.0.1:3995/esbuild").addEventListener("change",t=>{const{updated:i}=JSON.parse(t.data),e=new Set;for(const s of i)e.add(s);const c=Array.from(e).filter(s=>s.endsWith(".css"));if(c.length){console.log("esbuild css updates",c);const s={};for(const o of document.getElementsByTagName("link"))if(o.getAttribute("rel")==="stylesheet"){const n=new URL(o.href);(n.host=location.host)&&(s[n.pathname]=o)}let a=!1;for(const o of c){const n=s[o];if(n){const r=n.cloneNode();r.href=`${o}?${Math.random().toString(36).slice(2)}`,r.onload=()=>n.remove(),n.parentNode.insertBefore(r,n.nextSibling),a=!0}}a&&l()}if(c.length<e.size){const s=Array.from(e).filter(o=>!o.endsWith(".css")),a=new Set;for(const o of document.getElementsByTagName("script"))if(o.src.length){const n=new URL(o.src);(n.host=location.host)&&a.add(n.pathname)}s.some(o=>a.has(o))&&(console.log("esbuild js updates require reload"),p())}});function l(){const t=d("green","9999");t.style.opacity="0",t.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(t),Promise.all(t.getAnimations().map(i=>i.finished)).then(()=>t.remove())}function p(){const t=d("orange","9000");t.style.opacity="0",t.animate([{opacity:0},{opacity:1}],{duration:400,iterations:1,direction:"normal",easing:"ease-in"}),document.body.appendChild(t)}function d(t,i){const e=document.createElement("div");return e.style.border="6px dashed "+t,e.style.zIndex=i,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}export{l as addCssUpdateIndicator};
package/lib/bin.ts CHANGED
@@ -71,7 +71,7 @@ const task: 'build' | 'serve' = (function resolveTask() {
71
71
  return task
72
72
  })()
73
73
 
74
- const c = await loadConfig()
74
+ const c = await loadConfig(task)
75
75
 
76
76
  try {
77
77
  switch (task) {
package/lib/build.ts CHANGED
@@ -9,10 +9,8 @@ import { HtmlEntrypoint } from './html.ts'
9
9
  import { type WebsiteManifest, WebsiteRegistry } from './metadata.ts'
10
10
  import { copyAssets } from './public.ts'
11
11
 
12
- export async function buildWebsite(
13
- c: DankConfig,
14
- build: DankBuild = resolveBuildFlags(),
15
- ): Promise<WebsiteManifest> {
12
+ export async function buildWebsite(c: DankConfig): Promise<WebsiteManifest> {
13
+ const build: DankBuild = resolveBuildFlags()
16
14
  const buildTag = await createBuildTag(build)
17
15
  console.log(
18
16
  build.minify
package/lib/config.ts CHANGED
@@ -1,12 +1,30 @@
1
1
  import { isAbsolute, resolve } from 'node:path'
2
- import type { DankConfig } from './dank.ts'
2
+ import type {
3
+ DankConfig,
4
+ DankDetails,
5
+ EsbuildConfig,
6
+ PageMapping,
7
+ } from './dank.ts'
8
+ import { LOG } from './developer.ts'
9
+ import { isProductionBuild } from './flags.ts'
3
10
 
4
11
  const CFG_P = './dank.config.ts'
5
12
 
6
- export async function loadConfig(path: string = CFG_P): Promise<DankConfig> {
7
- const modulePath = `${resolveConfigPath(path)}?${Date.now()}`
8
- const module = await import(modulePath)
9
- return await module.default
13
+ export async function loadConfig(
14
+ mode: 'build' | 'serve',
15
+ path: string = CFG_P,
16
+ ): Promise<DankConfig> {
17
+ const modulePath = resolveConfigPath(path)
18
+ LOG({
19
+ realm: 'config',
20
+ message: 'loading config module',
21
+ data: {
22
+ modulePath,
23
+ },
24
+ })
25
+ const c = await resolveConfig(mode, modulePath)
26
+ normalizePagePaths(c.pages)
27
+ return c
10
28
  }
11
29
 
12
30
  export function resolveConfigPath(path: string): string {
@@ -16,3 +34,191 @@ export function resolveConfigPath(path: string): string {
16
34
  return resolve(process.cwd(), path)
17
35
  }
18
36
  }
37
+
38
+ export async function resolveConfig(
39
+ mode: 'build' | 'serve',
40
+ modulePath: string,
41
+ ): Promise<DankConfig> {
42
+ const module = await import(`${modulePath}?${Date.now()}`)
43
+ const c: Partial<DankConfig> =
44
+ typeof module.default === 'function'
45
+ ? await module.default(resolveDankDetails(mode))
46
+ : module.default
47
+ validateDankConfig(c)
48
+ return c as DankConfig
49
+ }
50
+
51
+ function resolveDankDetails(mode: 'build' | 'serve'): DankDetails {
52
+ const production = isProductionBuild()
53
+ return {
54
+ dev: !production,
55
+ production,
56
+ mode,
57
+ }
58
+ }
59
+
60
+ function validateDankConfig(c: Partial<DankConfig>) {
61
+ try {
62
+ validatePorts(c)
63
+ validatePages(c.pages)
64
+ validateDevServices(c.services)
65
+ validateEsbuildConfig(c.esbuild)
66
+ } catch (e: any) {
67
+ LOG({
68
+ realm: 'config',
69
+ message: 'validation error',
70
+ data: {
71
+ error: e.message,
72
+ },
73
+ })
74
+ throw e
75
+ }
76
+ }
77
+
78
+ function validatePorts(c: Partial<DankConfig>) {
79
+ if (c.port !== null && typeof c.port !== 'undefined') {
80
+ if (typeof c.port !== 'number') {
81
+ throw Error('DankConfig.port must be a number')
82
+ }
83
+ }
84
+ if (c.previewPort !== null && typeof c.previewPort !== 'undefined') {
85
+ if (typeof c.previewPort !== 'number') {
86
+ throw Error('DankConfig.previewPort must be a number')
87
+ }
88
+ }
89
+ }
90
+
91
+ function validateEsbuildConfig(esbuild?: EsbuildConfig) {
92
+ if (esbuild?.loaders !== null && typeof esbuild?.loaders !== 'undefined') {
93
+ if (typeof esbuild.loaders !== 'object') {
94
+ throw Error(
95
+ 'DankConfig.esbuild.loaders must be a map of extensions to esbuild loaders',
96
+ )
97
+ } else {
98
+ for (const [ext, loader] of Object.entries(esbuild.loaders)) {
99
+ if (typeof loader !== 'string') {
100
+ throw Error(
101
+ `DankConfig.esbuild.loaders['${ext}'] must be a string of a loader name`,
102
+ )
103
+ }
104
+ }
105
+ }
106
+ }
107
+ if (esbuild?.plugins !== null && typeof esbuild?.plugins !== 'undefined') {
108
+ if (!Array.isArray(esbuild.plugins)) {
109
+ throw Error(
110
+ 'DankConfig.esbuild.plugins must be an array of esbuild plugins',
111
+ )
112
+ }
113
+ }
114
+ if (esbuild?.port !== null && typeof esbuild?.port !== 'undefined') {
115
+ if (typeof esbuild.port !== 'number') {
116
+ throw Error('DankConfig.esbuild.port must be a number')
117
+ }
118
+ }
119
+ }
120
+
121
+ function validatePages(pages?: DankConfig['pages']) {
122
+ if (
123
+ pages === null ||
124
+ typeof pages === 'undefined' ||
125
+ Object.keys(pages).length === 0
126
+ ) {
127
+ throw Error('DankConfig.pages is required')
128
+ }
129
+ for (const [urlPath, mapping] of Object.entries(pages)) {
130
+ if (typeof mapping === 'string' && mapping.endsWith('.html')) {
131
+ continue
132
+ }
133
+ if (typeof mapping === 'object') {
134
+ validatePageMapping(urlPath, mapping)
135
+ continue
136
+ }
137
+ throw Error(
138
+ `DankConfig.pages['${urlPath}'] must configure an html file`,
139
+ )
140
+ }
141
+ }
142
+
143
+ function validatePageMapping(urlPath: string, mapping: PageMapping) {
144
+ if (
145
+ mapping.webpage === null ||
146
+ typeof mapping.webpage !== 'string' ||
147
+ !mapping.webpage.endsWith('.html')
148
+ ) {
149
+ throw Error(
150
+ `DankConfig.pages['${urlPath}'].webpage must configure an html file`,
151
+ )
152
+ }
153
+ if (mapping.pattern === null || typeof mapping.pattern === 'undefined') {
154
+ return
155
+ }
156
+ if (
157
+ typeof mapping.pattern === 'object' &&
158
+ mapping.pattern.constructor.name === 'RegExp'
159
+ ) {
160
+ return
161
+ }
162
+ throw Error(`DankConfig.pages['${urlPath}'].pattern must be a RegExp`)
163
+ }
164
+
165
+ function validateDevServices(services: DankConfig['services']) {
166
+ if (services === null || typeof services === 'undefined') {
167
+ return
168
+ }
169
+ if (!Array.isArray(services)) {
170
+ throw Error(`DankConfig.services must be an array`)
171
+ }
172
+ for (let i = 0; i < services.length; i++) {
173
+ const s = services[i]
174
+ if (s.command === null || typeof s.command === 'undefined') {
175
+ throw Error(`DankConfig.services[${i}].command is required`)
176
+ } else if (typeof s.command !== 'string' || s.command.length === 0) {
177
+ throw Error(
178
+ `DankConfig.services[${i}].command must be a non-empty string`,
179
+ )
180
+ }
181
+ if (s.cwd !== null && typeof s.cwd !== 'undefined') {
182
+ if (typeof s.cwd !== 'string' || s.cwd.trim().length === 0) {
183
+ throw Error(
184
+ `DankConfig.services[${i}].cwd must be a non-empty string`,
185
+ )
186
+ }
187
+ }
188
+ if (s.env !== null && typeof s.env !== 'undefined') {
189
+ if (typeof s.env !== 'object') {
190
+ throw Error(
191
+ `DankConfig.services[${i}].env must be an env variable map`,
192
+ )
193
+ }
194
+ for (const [k, v] of Object.entries(s.env)) {
195
+ if (typeof v !== 'string') {
196
+ throw Error(
197
+ `DankConfig.services[${i}].env[${k}] must be a string`,
198
+ )
199
+ }
200
+ }
201
+ }
202
+ if (s.http !== null && typeof s.http !== 'undefined') {
203
+ if (typeof s.http.port !== 'number') {
204
+ throw Error(
205
+ `DankConfig.services[${i}].http.port must be a number`,
206
+ )
207
+ }
208
+ }
209
+ }
210
+ }
211
+
212
+ function normalizePagePaths(pages: DankConfig['pages']) {
213
+ 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
+ }
219
+ }
220
+ }
221
+
222
+ function normalizePagePath(p: `${string}.html`): `${string}.html` {
223
+ return p.replace(/^\.\//, '') as `${string}.html`
224
+ }
package/lib/dank.ts CHANGED
@@ -66,157 +66,21 @@ export type EsbuildLoader =
66
66
  | 'json'
67
67
  | 'text'
68
68
 
69
- export async function defineConfig(
70
- c: Partial<DankConfig>,
71
- ): Promise<DankConfig> {
72
- if (c.port !== null && typeof c.port !== 'undefined') {
73
- if (typeof c.port !== 'number') {
74
- throw Error('DankConfig.port must be a number')
75
- }
76
- }
77
- if (c.previewPort !== null && typeof c.previewPort !== 'undefined') {
78
- if (typeof c.previewPort !== 'number') {
79
- throw Error('DankConfig.previewPort must be a number')
80
- }
81
- }
82
- validatePages(c.pages)
83
- validateDevServices(c.services)
84
- validateEsbuildConfig(c.esbuild)
85
- normalizePagePaths(c.pages!)
86
- return c as DankConfig
87
- }
88
-
89
- function validateEsbuildConfig(esbuild?: EsbuildConfig) {
90
- if (esbuild?.loaders !== null && typeof esbuild?.loaders !== 'undefined') {
91
- if (typeof esbuild.loaders !== 'object') {
92
- throw Error(
93
- 'DankConfig.esbuild.loaders must be a map of extensions to esbuild loaders',
94
- )
95
- } else {
96
- for (const [ext, loader] of Object.entries(esbuild.loaders)) {
97
- if (typeof loader !== 'string') {
98
- throw Error(
99
- `DankConfig.esbuild.loaders['${ext}'] must be a string of a loader name`,
100
- )
101
- }
102
- }
103
- }
104
- }
105
- if (esbuild?.plugins !== null && typeof esbuild?.plugins !== 'undefined') {
106
- if (!Array.isArray(esbuild.plugins)) {
107
- throw Error(
108
- 'DankConfig.esbuild.plugins must be an array of esbuild plugins',
109
- )
110
- }
111
- }
112
- if (esbuild?.port !== null && typeof esbuild?.port !== 'undefined') {
113
- if (typeof esbuild.port !== 'number') {
114
- throw Error('DankConfig.esbuild.port must be a number')
115
- }
116
- }
117
- }
118
-
119
- function validatePages(pages?: DankConfig['pages']) {
120
- if (
121
- pages === null ||
122
- typeof pages === 'undefined' ||
123
- Object.keys(pages).length === 0
124
- ) {
125
- throw Error('DankConfig.pages is required')
126
- }
127
- for (const [urlPath, mapping] of Object.entries(pages)) {
128
- if (typeof mapping === 'string' && mapping.endsWith('.html')) {
129
- continue
130
- }
131
- if (typeof mapping === 'object') {
132
- validatePageMapping(urlPath, mapping)
133
- continue
134
- }
135
- throw Error(
136
- `DankConfig.pages['${urlPath}'] must configure an html file`,
137
- )
138
- }
69
+ // DankConfigFunction arg details about a dank process used when building DankConfig
70
+ export type DankDetails = {
71
+ dev: boolean
72
+ production: boolean
73
+ mode: 'build' | 'serve'
139
74
  }
140
75
 
141
- function validatePageMapping(urlPath: string, mapping: PageMapping) {
142
- if (
143
- mapping.webpage === null ||
144
- typeof mapping.webpage !== 'string' ||
145
- !mapping.webpage.endsWith('.html')
146
- ) {
147
- throw Error(
148
- `DankConfig.pages['${urlPath}'].webpage must configure an html file`,
149
- )
150
- }
151
- if (mapping.pattern === null || typeof mapping.pattern === 'undefined') {
152
- return
153
- }
154
- if (
155
- typeof mapping.pattern === 'object' &&
156
- mapping.pattern.constructor.name === 'RegExp'
157
- ) {
158
- return
159
- }
160
- throw Error(`DankConfig.pages['${urlPath}'].pattern must be a RegExp`)
161
- }
162
-
163
- function validateDevServices(services: DankConfig['services']) {
164
- if (services === null || typeof services === 'undefined') {
165
- return
166
- }
167
- if (!Array.isArray(services)) {
168
- throw Error(`DankConfig.services must be an array`)
169
- }
170
- for (let i = 0; i < services.length; i++) {
171
- const s = services[i]
172
- if (s.command === null || typeof s.command === 'undefined') {
173
- throw Error(`DankConfig.services[${i}].command is required`)
174
- } else if (typeof s.command !== 'string' || s.command.length === 0) {
175
- throw Error(
176
- `DankConfig.services[${i}].command must be a non-empty string`,
177
- )
178
- }
179
- if (s.cwd !== null && typeof s.cwd !== 'undefined') {
180
- if (typeof s.cwd !== 'string' || s.cwd.trim().length === 0) {
181
- throw Error(
182
- `DankConfig.services[${i}].cwd must be a non-empty string`,
183
- )
184
- }
185
- }
186
- if (s.env !== null && typeof s.env !== 'undefined') {
187
- if (typeof s.env !== 'object') {
188
- throw Error(
189
- `DankConfig.services[${i}].env must be an env variable map`,
190
- )
191
- }
192
- for (const [k, v] of Object.entries(s.env)) {
193
- if (typeof v !== 'string') {
194
- throw Error(
195
- `DankConfig.services[${i}].env[${k}] must be a string`,
196
- )
197
- }
198
- }
199
- }
200
- if (s.http !== null && typeof s.http !== 'undefined') {
201
- if (typeof s.http.port !== 'number') {
202
- throw Error(
203
- `DankConfig.services[${i}].http.port must be a number`,
204
- )
205
- }
206
- }
207
- }
208
- }
209
-
210
- function normalizePagePaths(pages: DankConfig['pages']) {
211
- for (const [pageUrl, mapping] of Object.entries(pages)) {
212
- if (typeof mapping === 'string') {
213
- pages[pageUrl as `/${string}`] = normalizePagePath(mapping)
214
- } else {
215
- mapping.webpage = normalizePagePath(mapping.webpage)
216
- }
217
- }
218
- }
76
+ export type DankConfigFunction = (
77
+ dank: DankDetails,
78
+ ) => Partial<DankConfig> | Promise<Partial<DankConfig>>
219
79
 
220
- function normalizePagePath(p: `${string}.html`): `${string}.html` {
221
- return p.replace(/^\.\//, '') as `${string}.html`
80
+ export function defineConfig(config: Partial<DankConfig>): Partial<DankConfig>
81
+ export function defineConfig(config: DankConfigFunction): DankConfigFunction
82
+ export function defineConfig(
83
+ config: Partial<DankConfig> | DankConfigFunction,
84
+ ): Partial<DankConfig> | DankConfigFunction {
85
+ return config
222
86
  }
@@ -0,0 +1,117 @@
1
+ import { createWriteStream, type WriteStream } from 'node:fs'
2
+ import { mkdir, rm } from 'node:fs/promises'
3
+ import os from 'node:os'
4
+ import { dirname, resolve } from 'node:path'
5
+ import packageJson from '../package.json' with { type: 'json' }
6
+
7
+ const FILE = process.env.DANK_LOG_FILE
8
+ const ROLLING =
9
+ process.env.DANK_LOG_ROLLING === '1' ||
10
+ process.env.DANK_LOG_ROLLING === 'true'
11
+
12
+ const logs: Array<string> = []
13
+ let initialized = false
14
+ let preparing: Promise<void>
15
+ let stream: WriteStream
16
+
17
+ export type LogEvent = {
18
+ realm:
19
+ | 'build'
20
+ | 'serve'
21
+ | 'assets'
22
+ | 'config'
23
+ | 'html'
24
+ | 'registry'
25
+ | 'services'
26
+ message: string
27
+ data?: Record<string, LogEventData>
28
+ }
29
+
30
+ type LogEventData =
31
+ | LogEventDatum
32
+ | Array<LogEventDatum>
33
+ | Set<LogEventDatum>
34
+ | Record<string, LogEventDatum>
35
+
36
+ type LogEventDatum = boolean | number | string | null
37
+
38
+ function toStringLogEvent(logEvent: LogEvent): string {
39
+ const when = new Date().toISOString()
40
+ const message = `[${logEvent.realm}] ${logEvent.message}\n${when}\n`
41
+ if (!logEvent.data) {
42
+ return message
43
+ }
44
+ let data = ''
45
+ for (const k of Object.keys(logEvent.data).sort()) {
46
+ data += `\n ${k} = ${toStringData(logEvent.data[k])}`
47
+ }
48
+ return `${message}${data}\n`
49
+ }
50
+
51
+ function toStringData(datum: LogEventData): string {
52
+ if (datum instanceof Set) {
53
+ datum = Array.from(datum)
54
+ }
55
+ if (
56
+ datum !== null &&
57
+ typeof datum === 'object' &&
58
+ datum.constructor.name === 'Object'
59
+ ) {
60
+ datum = Object.entries(datum).map(([k, v]) => `${k} = ${v}`)
61
+ }
62
+ if (Array.isArray(datum)) {
63
+ if (datum.length === 0) {
64
+ return '[]'
65
+ } else {
66
+ return `[\n ${datum.join('\n ')}\n ]`
67
+ }
68
+ } else {
69
+ return `${datum}`
70
+ }
71
+ }
72
+
73
+ function logToFile(logEvent: LogEvent) {
74
+ logs.push(toStringLogEvent(logEvent))
75
+ if (!initialized) {
76
+ initialized = true
77
+ preparing = prepareLogFile().catch(onPrepareLogFileError)
78
+ }
79
+ preparing.then(syncLogs)
80
+ }
81
+
82
+ async function prepareLogFile() {
83
+ const path = resolve(FILE!)
84
+ if (!ROLLING) {
85
+ await rm(path, { force: true })
86
+ }
87
+ await mkdir(dirname(path), { recursive: true })
88
+ stream = createWriteStream(path, { flags: 'a' })
89
+ console.log('debug logging to', FILE)
90
+ logSystemDetails()
91
+ }
92
+
93
+ function logSystemDetails() {
94
+ stream.write(`\
95
+ ---
96
+ os: ${os.type()}
97
+ build: ${os.version()}
98
+ cpu: ${os.arch()}
99
+ cores: ${os.availableParallelism()}
100
+ ${process.versions.bun ? `bun ${process.versions.bun}` : `node ${process.version}`}
101
+ dank: ${packageJson.version}
102
+ \n`)
103
+ }
104
+
105
+ function syncLogs() {
106
+ if (!logs.length) return
107
+ const content = logs.join('\n') + '\n'
108
+ logs.length = 0
109
+ stream.write(content)
110
+ }
111
+
112
+ function onPrepareLogFileError(e: any) {
113
+ console.error(`init log file \`${FILE}\` error: ${e.message}`)
114
+ process.exit(1)
115
+ }
116
+
117
+ export const LOG = FILE?.length ? logToFile : () => {}
package/lib/flags.ts CHANGED
@@ -87,7 +87,7 @@ const isPreviewBuild = () =>
87
87
 
88
88
  // `dank build` will minify sources and append git release tag to build tag
89
89
  // `dank serve` will pre-bundle with service worker and minify
90
- const isProductionBuild = () =>
90
+ export const isProductionBuild = () =>
91
91
  process.env.PRODUCTION === 'true' || process.argv.includes('--production')
92
92
 
93
93
  // `dank serve` dank port for frontend webserver