@eighty4/dank 0.0.5-2 → 0.0.5-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,89 @@
1
+ import website from 'DANK:sw'
2
+
3
+ declare const self: ServiceWorkerGlobalScope
4
+
5
+ self.addEventListener('install', (e: ExtendableEvent) =>
6
+ e.waitUntil(populateCache()),
7
+ )
8
+
9
+ self.addEventListener('activate', (e: ExtendableEvent) =>
10
+ e.waitUntil(cleanupCaches()),
11
+ )
12
+
13
+ self.addEventListener('fetch', (e: FetchEvent) =>
14
+ e.respondWith(handleRequest(e.request)),
15
+ )
16
+
17
+ const PREFIX_APP_CACHE_KEY = 'DANK-website-'
18
+ const APP_CACHE_KEY: string = PREFIX_APP_CACHE_KEY + website.cacheKey
19
+
20
+ async function populateCache() {
21
+ const cache = await self.caches.open(APP_CACHE_KEY)
22
+ const previousCacheKey = await swapCurrentCacheKey()
23
+ if (!previousCacheKey) {
24
+ await cache.addAll(website.files)
25
+ } else {
26
+ const previousCache = await self.caches.open(previousCacheKey)
27
+ await Promise.all(
28
+ website.files.map(async f => {
29
+ const previouslyCached = await previousCache.match(f)
30
+ if (previouslyCached) {
31
+ await cache.put(f, previouslyCached)
32
+ } else {
33
+ await cache.add(f)
34
+ }
35
+ }),
36
+ )
37
+ }
38
+ }
39
+
40
+ async function swapCurrentCacheKey(): Promise<string | null> {
41
+ const META_CACHE_KEY = 'DANK-meta'
42
+ const CACHE_KEY_URL = '/DANK/current'
43
+ const metaCache = await self.caches.open(META_CACHE_KEY)
44
+ const previousCacheKeyResponse = await metaCache.match(CACHE_KEY_URL)
45
+ const previousCacheKey = previousCacheKeyResponse
46
+ ? await previousCacheKeyResponse.text()
47
+ : null
48
+ await metaCache.put(
49
+ CACHE_KEY_URL,
50
+ new Response(APP_CACHE_KEY, {
51
+ headers: {
52
+ 'Content-Type': 'text/plain',
53
+ },
54
+ }),
55
+ )
56
+ return previousCacheKey
57
+ }
58
+
59
+ async function cleanupCaches() {
60
+ const cacheKeys = await self.caches.keys()
61
+ for (const cacheKey of cacheKeys) {
62
+ if (cacheKey !== APP_CACHE_KEY) {
63
+ await self.caches.delete(cacheKey)
64
+ }
65
+ }
66
+ }
67
+
68
+ // todo implement page mapping url rewrites here
69
+ // url.pathname = mappedUrlPath
70
+ async function handleRequest(req: Request): Promise<Response> {
71
+ const url = new URL(req.url)
72
+ if (req.method === 'GET' && !bypassCache(url)) {
73
+ const cache = await caches.open(APP_CACHE_KEY)
74
+ const fromCache = await cache.match(url)
75
+ if (fromCache) {
76
+ return fromCache
77
+ }
78
+ }
79
+ return fetch(req)
80
+ }
81
+
82
+ // todo support RegExp
83
+ function bypassCache(url: URL): boolean {
84
+ return (
85
+ website.bypassCache?.hosts?.includes(url.host) ||
86
+ website.bypassCache?.paths?.includes(url.pathname as `/${string}`) ||
87
+ false
88
+ )
89
+ }
package/lib/bin.ts CHANGED
@@ -6,20 +6,21 @@ import { serveWebsite } from './serve.ts'
6
6
 
7
7
  function printHelp(task?: 'build' | 'serve'): never {
8
8
  if (!task || task === 'build') {
9
- console.log('dank build [--minify] [--production]')
9
+ console.log('dank build [--minify] [--production] [--service-worker]')
10
10
  }
11
11
  if (!task || task === 'serve') {
12
12
  console.log(
13
13
  // 'dank serve [--minify] [--preview] [--production]',
14
- 'dank serve [--minify] [--production]',
14
+ 'dank serve [--minify] [--production] [--service-worker]',
15
15
  )
16
16
  }
17
17
  console.log('\nOPTIONS:')
18
18
  if (!task || task === 'serve')
19
- console.log(' --log-http print access logs')
20
- console.log(' --minify minify sources')
19
+ console.log(' --log-http print access logs')
20
+ console.log(' --minify minify sources')
21
21
  // if (!task || task === 'serve') console.log(' --preview pre-bundle and build ServiceWorker')
22
- console.log(' --production build for production release')
22
+ console.log(' --production build for production release')
23
+ console.log(' --service-worker build service worker')
23
24
  if (task) {
24
25
  console.log()
25
26
  console.log('use `dank -h` for details on all commands')
package/lib/build.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import { mkdir, readFile, rm, writeFile } from 'node:fs/promises'
2
2
  import { join } from 'node:path'
3
- import { createBuildTag } from './build_tag.ts'
4
3
  import { loadConfig, type ResolvedDankConfig } from './config.ts'
4
+ import type { ServiceWorkerBuild, WebsiteManifest } from './dank.ts'
5
5
  import { type DefineDankGlobal, createGlobalDefinitions } from './define.ts'
6
6
  import type { DankDirectories } from './dirs.ts'
7
7
  import { esbuildWebpages, esbuildWorkers } from './esbuild.ts'
8
8
  import { copyAssets } from './public.ts'
9
- import { type WebsiteManifest, WebsiteRegistry } from './registry.ts'
9
+ import { WebsiteRegistry } from './registry.ts'
10
10
 
11
11
  export async function buildWebsite(
12
12
  c?: ResolvedDankConfig,
@@ -14,7 +14,6 @@ export async function buildWebsite(
14
14
  if (!c) {
15
15
  c = await loadConfig('build', process.cwd())
16
16
  }
17
- const buildTag = await createBuildTag(c.flags)
18
17
  console.log(
19
18
  c.flags.minify
20
19
  ? c.flags.production
@@ -22,7 +21,7 @@ export async function buildWebsite(
22
21
  : 'minified'
23
22
  : 'unminified',
24
23
  'build',
25
- buildTag,
24
+ await c.buildTag(),
26
25
  'building in ./build/dist',
27
26
  )
28
27
  await rm(c.dirs.buildRoot, { recursive: true, force: true })
@@ -32,7 +31,7 @@ export async function buildWebsite(
32
31
  }
33
32
  await mkdir(join(c.dirs.buildRoot, 'metafiles'), { recursive: true })
34
33
  const registry = await buildWebpages(c, createGlobalDefinitions(c))
35
- return await registry.writeManifest(buildTag)
34
+ return await registry.writeManifest()
36
35
  }
37
36
 
38
37
  // builds all webpage entrypoints in one esbuild.build context to support code splitting
@@ -63,6 +62,7 @@ async function buildWebpages(
63
62
  )
64
63
  }),
65
64
  )
65
+ await buildServiceWorker(registry)
66
66
  return registry
67
67
  }
68
68
 
@@ -128,3 +128,55 @@ export function createWorkerRegex(
128
128
  'g',
129
129
  )
130
130
  }
131
+
132
+ async function buildServiceWorker(registry: WebsiteRegistry) {
133
+ const serviceWorkerBuilder = registry.config.serviceWorkerBuilder
134
+ if (serviceWorkerBuilder) {
135
+ const website = await registry.manifest()
136
+ const serviceWorkerBuild = await serviceWorkerBuilder({ website })
137
+ validateServiceWorkerBuild(serviceWorkerBuild)
138
+ serviceWorkerBuild.outputs.map(async (output, i) => {
139
+ try {
140
+ return await registry.addBuildOutput(output.url, output.content)
141
+ } catch {
142
+ console.log(
143
+ `ServiceWorkerBuild.outputs[${i}].url \`${output.url}\` is already a url in the build output.`,
144
+ )
145
+ process.exit(1)
146
+ }
147
+ })
148
+ }
149
+ }
150
+
151
+ function validateServiceWorkerBuild(
152
+ serviceWorkerBuild: ServiceWorkerBuild,
153
+ ): void | never {
154
+ if (
155
+ serviceWorkerBuild === null ||
156
+ typeof serviceWorkerBuild === 'undefined'
157
+ ) {
158
+ console.log(`ServiceWorkerBuild is ${serviceWorkerBuild}.`)
159
+ console.log(
160
+ '\nMake sure the builder function \`serviceWorker\` in \`dank.config.ts\` is returning a ServiceWorkerBuild.',
161
+ )
162
+ process.exit(1)
163
+ }
164
+ const testUrlPattern = /^\/.*\.js$/
165
+ const valid = true
166
+ serviceWorkerBuild.outputs.forEach((output, i) => {
167
+ if (!output.content?.length) {
168
+ console.log(`ServiceWorkerBuild.outputs[${i}].content is empty.`)
169
+ }
170
+ if (!output.url?.length || !testUrlPattern.test(output.url)) {
171
+ console.log(
172
+ `ServiceWorkerBuild.outputs[${i}].url is not a valid \`/*.js\` path.`,
173
+ )
174
+ }
175
+ })
176
+ if (!valid) {
177
+ console.log(
178
+ '\nCheck your \`serviceWorker\` config in \`dank.config.ts\`.',
179
+ )
180
+ process.exit(1)
181
+ }
182
+ }
package/lib/build_tag.ts CHANGED
@@ -1,25 +1,129 @@
1
1
  import { exec } from 'node:child_process'
2
+ import type { DankConfig } from './dank.ts'
2
3
  import type { DankFlags } from './flags.ts'
3
4
 
4
- export async function createBuildTag(flags: DankFlags): Promise<string> {
5
+ export async function createBuildTag(
6
+ projectDir: string,
7
+ flags: DankFlags,
8
+ buildTagSource?: DankConfig['buildTag'],
9
+ ): Promise<string> {
10
+ if (typeof buildTagSource === 'function') {
11
+ buildTagSource = await buildTagSource({ production: flags.production })
12
+ }
13
+ if (typeof buildTagSource === 'undefined' || buildTagSource === null) {
14
+ buildTagSource = await resolveExpressionDefault(projectDir)
15
+ }
16
+ if (typeof buildTagSource !== 'string') {
17
+ throw TypeError(
18
+ 'DankConfig.buildTag must resolve to a string expession',
19
+ )
20
+ }
21
+ const params: BuildTagParams = {}
5
22
  const now = new Date()
23
+ const paramPattern = new RegExp(/{{\s*(?<name>[a-z][A-Za-z]+)\s*}}/g)
24
+ let paramMatch: RegExpExecArray | null
25
+ let buildTag = buildTagSource
26
+ let offset = 0
27
+ while ((paramMatch = paramPattern.exec(buildTagSource)) != null) {
28
+ const paramName = paramMatch.groups!.name.trim() as keyof BuildTagParams
29
+ let paramValue: string
30
+ if (params[paramName]) {
31
+ paramValue = params[paramName]
32
+ } else {
33
+ paramValue = params[paramName] = await getParamValue(
34
+ projectDir,
35
+ paramName,
36
+ now,
37
+ buildTagSource,
38
+ )
39
+ }
40
+ buildTag =
41
+ buildTag.substring(0, paramMatch.index + offset) +
42
+ paramValue +
43
+ buildTag.substring(paramMatch.index + paramMatch[0].length + offset)
44
+ offset += paramValue.length - paramMatch[0].length
45
+ }
46
+ const validate = /^[A-Za-z\d][A-Za-z\d-_\.]+$/
47
+ if (!validate.test(buildTag)) {
48
+ throw Error(
49
+ `build tag ${buildTag} does not pass pattern ${validate.source} validation`,
50
+ )
51
+ }
52
+ return buildTag
53
+ }
54
+
55
+ async function resolveExpressionDefault(projectDir: string): Promise<string> {
56
+ const base = '{{ date }}-{{ timeMS }}'
57
+ const isGitRepo = await new Promise(res =>
58
+ exec('git rev-parse --is-inside-work-tree', { cwd: projectDir }, err =>
59
+ res(!err),
60
+ ),
61
+ )
62
+ return isGitRepo ? base + '-{{ gitHash }}' : base
63
+ }
64
+
65
+ type BuildTagParams = {
66
+ date?: string
67
+ gitHash?: string
68
+ timeMS?: string
69
+ }
70
+
71
+ async function getParamValue(
72
+ projectDir: string,
73
+ name: keyof BuildTagParams,
74
+ now: Date,
75
+ buildTagSource: string,
76
+ ): Promise<string> {
77
+ switch (name) {
78
+ case 'date':
79
+ return getDate(now)
80
+ case 'gitHash':
81
+ try {
82
+ return await getGitHash(projectDir)
83
+ } catch (e) {
84
+ if (e === 'not-repo') {
85
+ throw Error(
86
+ `buildTag cannot use \`gitHash\` in \`${buildTagSource}\` outside of a git repository`,
87
+ )
88
+ } else {
89
+ throw e
90
+ }
91
+ }
92
+ case 'timeMS':
93
+ return getTimeMS(now)
94
+ default:
95
+ throw Error(name + ' is not a supported build tag param')
96
+ }
97
+ }
98
+
99
+ function getDate(now: Date): string {
100
+ return now.toISOString().substring(0, 10)
101
+ }
102
+
103
+ async function getGitHash(projectDir: string): Promise<string> {
104
+ return await new Promise((res, rej) =>
105
+ exec(
106
+ 'git rev-parse --short HEAD',
107
+ { cwd: projectDir },
108
+ (err, stdout, stderr) => {
109
+ if (err) {
110
+ if (stderr.includes('not a git repository')) {
111
+ rej('not-repo')
112
+ } else {
113
+ rej(err)
114
+ }
115
+ }
116
+ res(stdout.trim())
117
+ },
118
+ ),
119
+ )
120
+ }
121
+
122
+ function getTimeMS(now: Date): string {
6
123
  const ms =
7
124
  now.getUTCMilliseconds() +
8
125
  now.getUTCSeconds() * 1000 +
9
126
  now.getUTCMinutes() * 1000 * 60 +
10
127
  now.getUTCHours() * 1000 * 60 * 60
11
- const date = now.toISOString().substring(0, 10)
12
- const time = String(ms).padStart(8, '0')
13
- const when = `${date}-${time}`
14
- if (flags.production) {
15
- const gitHash = await new Promise((res, rej) =>
16
- exec('git rev-parse --short HEAD', (err, stdout) => {
17
- if (err) rej(err)
18
- res(stdout.trim())
19
- }),
20
- )
21
- return `${when}-${gitHash}`
22
- } else {
23
- return when
24
- }
128
+ return String(ms).padStart(8, '0')
25
129
  }
package/lib/config.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  import { isAbsolute, resolve } from 'node:path'
2
+ import { createBuildTag } from './build_tag.ts'
2
3
  import type {
3
4
  DankConfig,
4
5
  DankDetails,
5
6
  EsbuildConfig,
6
7
  PageMapping,
8
+ ServiceWorkerBuilder,
7
9
  } from './dank.ts'
8
10
  import { LOG } from './developer.ts'
9
11
  import { defaultProjectDirs, type DankDirectories } from './dirs.ts'
@@ -21,7 +23,7 @@ const DEFAULT_CONFIG_PATH = './dank.config.ts'
21
23
  export type { DevService } from './dank.ts'
22
24
 
23
25
  export type ResolvedDankConfig = {
24
- // static from process boot
26
+ // static config that does not hot reload during `dank serve`
25
27
  get dirs(): Readonly<DankDirectories>
26
28
  get flags(): Readonly<Omit<DankFlags, 'dankPort' | 'esbuildPort'>>
27
29
  get mode(): 'build' | 'serve'
@@ -33,6 +35,9 @@ export type ResolvedDankConfig = {
33
35
  get pages(): Readonly<Record<`/${string}`, PageMapping>>
34
36
  get devPages(): Readonly<DankConfig['devPages']>
35
37
  get services(): Readonly<DankConfig['services']>
38
+ get serviceWorkerBuilder(): DankConfig['serviceWorker']
39
+
40
+ buildTag(): Promise<string>
36
41
 
37
42
  reload(): Promise<void>
38
43
  }
@@ -60,10 +65,13 @@ export async function loadConfig(
60
65
  }
61
66
 
62
67
  class DankConfigInternal implements ResolvedDankConfig {
68
+ #buildTag: Promise<string> | null = null
69
+ #buildTagBuilder: DankConfig['buildTag']
63
70
  #dirs: Readonly<DankDirectories>
64
71
  #flags: Readonly<DankFlags>
65
72
  #mode: 'build' | 'serve'
66
73
  #modulePath: string
74
+ #serviceWorkerBuilder?: ServiceWorkerBuilder
67
75
 
68
76
  #dankPort: number = DEFAULT_DEV_PORT
69
77
  #esbuildPort: number = DEFAULT_ESBUILD_PORT
@@ -119,17 +127,35 @@ class DankConfigInternal implements ResolvedDankConfig {
119
127
  return this.#services
120
128
  }
121
129
 
130
+ get serviceWorkerBuilder(): DankConfig['serviceWorker'] {
131
+ return this.#serviceWorkerBuilder
132
+ }
133
+
134
+ buildTag(): Promise<string> {
135
+ if (this.#buildTag === null) {
136
+ this.#buildTag = createBuildTag(
137
+ this.#dirs.projectRootAbs,
138
+ this.#flags,
139
+ this.#buildTagBuilder,
140
+ )
141
+ }
142
+ return this.#buildTag
143
+ }
144
+
122
145
  async reload() {
123
146
  const userConfig = await resolveConfig(
124
147
  this.#modulePath,
125
148
  resolveDankDetails(this.#mode, this.#flags),
126
149
  )
150
+ this.#buildTag = null
151
+ this.#buildTagBuilder = userConfig.buildTag
127
152
  this.#dankPort = resolveDankPort(this.#flags, userConfig)
128
153
  this.#esbuildPort = resolveEsbuildPort(this.#flags, userConfig)
129
154
  this.#esbuild = Object.freeze(userConfig.esbuild)
130
155
  this.#pages = Object.freeze(normalizePages(userConfig.pages))
131
156
  this.#devPages = Object.freeze(userConfig.devPages)
132
157
  this.#services = Object.freeze(userConfig.services)
158
+ this.#serviceWorkerBuilder = userConfig.serviceWorker
133
159
  }
134
160
  }
135
161
 
@@ -173,10 +199,12 @@ function resolveDankDetails(
173
199
  function validateDankConfig(c: Partial<DankConfig>) {
174
200
  try {
175
201
  validatePorts(c)
202
+ validateBuildTag(c.buildTag)
176
203
  validatePages(c.pages)
177
204
  validateDevPages(c.devPages)
178
205
  validateDevServices(c.services)
179
206
  validateEsbuildConfig(c.esbuild)
207
+ validateServiceWorker(c.serviceWorker)
180
208
  } catch (e: any) {
181
209
  LOG({
182
210
  realm: 'config',
@@ -202,6 +230,33 @@ function validatePorts(c: Partial<DankConfig>) {
202
230
  }
203
231
  }
204
232
 
233
+ function validateBuildTag(buildTag: DankConfig['buildTag']) {
234
+ if (buildTag === null) {
235
+ return
236
+ }
237
+ switch (typeof buildTag) {
238
+ case 'undefined':
239
+ case 'string':
240
+ case 'function':
241
+ return
242
+ default:
243
+ throw Error('DankConfig.buildTag must be a string or function')
244
+ }
245
+ }
246
+
247
+ function validateServiceWorker(serviceWorker: DankConfig['serviceWorker']) {
248
+ if (serviceWorker === null) {
249
+ return
250
+ }
251
+ switch (typeof serviceWorker) {
252
+ case 'undefined':
253
+ case 'function':
254
+ return
255
+ default:
256
+ throw Error('DankConfig.serviceWorker must be a function')
257
+ }
258
+ }
259
+
205
260
  function validateEsbuildConfig(esbuild?: EsbuildConfig) {
206
261
  if (esbuild?.loaders !== null && typeof esbuild?.loaders !== 'undefined') {
207
262
  if (typeof esbuild.loaders !== 'object') {
package/lib/dank.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import type { Plugin as EsbuildPlugin } from 'esbuild'
2
2
 
3
3
  export type DankConfig = {
4
- // used for releases and service worker caching
5
- // buildTag?: (() => Promise<string> | string) | string
4
+ // used for service worker caching
5
+ buildTag?: string | BuildTagBuilder
6
6
 
7
7
  // customize esbuild configs
8
8
  esbuild?: EsbuildConfig
@@ -24,8 +24,20 @@ export type DankConfig = {
24
24
 
25
25
  // dev services launched during `dank serve`
26
26
  services?: Array<DevService>
27
+
28
+ // generate a service worker for `dank build --production`
29
+ // and when previewing with `dank serve --preview`
30
+ serviceWorker?: ServiceWorkerBuilder
31
+ }
32
+
33
+ export type BuildTagParams = {
34
+ production: boolean
27
35
  }
28
36
 
37
+ export type BuildTagBuilder = (
38
+ build: BuildTagParams,
39
+ ) => Promise<string> | string
40
+
29
41
  // extend an html entrypoint with url rewriting similar to cdn configurations
30
42
  // after trying all webpage, bundle and asset paths, mapping patterns
31
43
  // will be tested in the alphabetical order of the webpage paths
@@ -91,3 +103,33 @@ export function defineConfig(
91
103
  ): Partial<DankConfig> | DankConfigFunction {
92
104
  return config
93
105
  }
106
+
107
+ // summary of a website build, written to `build` dir
108
+ // and provided via ServiceWorkerParams to build a service worker from
109
+ export type WebsiteManifest = {
110
+ buildTag: string
111
+ files: Array<`/${string}`>
112
+ pageUrls: Array<`/${string}`>
113
+ }
114
+
115
+ export type ServiceWorkerParams = {
116
+ website: WebsiteManifest
117
+ }
118
+
119
+ export type ServiceWorkerBuild = {
120
+ // outputs will be written to the build's dist
121
+ // and added to the manifest written to website.json
122
+ outputs: Array<{
123
+ url: `/${string}.js`
124
+ content: string
125
+ }>
126
+ }
127
+
128
+ export type ServiceWorkerBuilder = (
129
+ params: ServiceWorkerParams,
130
+ ) => ServiceWorkerBuild | Promise<ServiceWorkerBuild>
131
+
132
+ export {
133
+ createServiceWorker,
134
+ type ServiceWorkerCaching,
135
+ } from './service_worker.ts'
package/lib/esbuild.ts CHANGED
@@ -75,6 +75,7 @@ export async function esbuildWorkers(
75
75
  function commonBuildOptions(r: WebsiteRegistry): BuildOptions {
76
76
  const p = workersPlugin(r.buildRegistry())
77
77
  return {
78
+ absWorkingDir: r.config.dirs.projectRootAbs,
78
79
  assetNames: 'assets/[name]-[hash]',
79
80
  bundle: true,
80
81
  format: 'esm',
package/lib/http.ts CHANGED
@@ -10,12 +10,12 @@ import {
10
10
  import { extname, join } from 'node:path'
11
11
  import { Readable } from 'node:stream'
12
12
  import mime from 'mime'
13
+ import type { WebsiteManifest } from './dank.ts'
13
14
  import type { DankDirectories } from './dirs.ts'
14
15
  import type { DankFlags } from './flags.ts'
15
16
  import type {
16
17
  UrlRewrite,
17
18
  UrlRewriteProvider,
18
- WebsiteManifest,
19
19
  WebsiteRegistry,
20
20
  } from './registry.ts'
21
21
  import type { DevServices } from './services.ts'
@@ -194,7 +194,7 @@ export function createBuiltDistFilesFetcher(
194
194
  res: ServerResponse,
195
195
  notFound: () => void,
196
196
  ) => {
197
- if (manifest.pageUrls.has(url.pathname)) {
197
+ if (manifest.pageUrls.includes(url.pathname as `/${string}`)) {
198
198
  streamFile(
199
199
  join(
200
200
  dirs.projectRootAbs,
@@ -204,7 +204,7 @@ export function createBuiltDistFilesFetcher(
204
204
  ),
205
205
  res,
206
206
  )
207
- } else if (manifest.files.has(url.pathname)) {
207
+ } else if (manifest.files.includes(url.pathname as `/${string}`)) {
208
208
  streamFile(
209
209
  join(dirs.projectRootAbs, dirs.buildDist, url.pathname),
210
210
  res,
package/lib/public.ts CHANGED
@@ -5,7 +5,7 @@ import type { DankDirectories } from './dirs.ts'
5
5
 
6
6
  export async function copyAssets(
7
7
  dirs: DankDirectories,
8
- ): Promise<Array<string> | null> {
8
+ ): Promise<Array<`/${string}`> | null> {
9
9
  try {
10
10
  const stats = await stat(dirs.public)
11
11
  if (stats.isDirectory()) {
@@ -24,8 +24,8 @@ const IGNORE = platform() === 'darwin' ? ['.DS_Store'] : []
24
24
  async function recursiveCopyAssets(
25
25
  dirs: DankDirectories,
26
26
  dir: string = '',
27
- ): Promise<Array<string>> {
28
- const copied: Array<string> = []
27
+ ): Promise<Array<`/${string}`>> {
28
+ const copied: Array<`/${string}`> = []
29
29
  const to = join(dirs.buildDist, dir)
30
30
  let madeDir = dir === ''
31
31
  const listingDir = join(dirs.public, dir)
@@ -45,7 +45,7 @@ async function recursiveCopyAssets(
45
45
  madeDir = true
46
46
  }
47
47
  await copyFile(join(listingDir, p), join(to, p))
48
- copied.push('/' + join(dir, p).replaceAll('\\', '/'))
48
+ copied.push(`/${join(dir, p).replaceAll('\\', '/')}`)
49
49
  }
50
50
  } catch (e) {
51
51
  console.error('stat error', e)