@eighty4/dank 0.0.5-4 → 0.0.5-6

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
@@ -3,6 +3,7 @@ import { createBuildTag } from './build_tag.ts'
3
3
  import type {
4
4
  DankConfig,
5
5
  DankDetails,
6
+ DevPageMapping,
6
7
  EsbuildConfig,
7
8
  PageMapping,
8
9
  ServiceWorkerBuilder,
@@ -33,12 +34,16 @@ export type ResolvedDankConfig = {
33
34
  get esbuildPort(): number
34
35
  get esbuild(): Readonly<Omit<EsbuildConfig, 'port'>> | undefined
35
36
  get pages(): Readonly<Record<`/${string}`, PageMapping>>
36
- get devPages(): Readonly<DankConfig['devPages']>
37
+ get devPages(): Readonly<
38
+ Record<`/${string}`, Omit<DevPageMapping & PageMapping, 'pattern'>>
39
+ >
37
40
  get services(): Readonly<DankConfig['services']>
38
41
  get serviceWorkerBuilder(): DankConfig['serviceWorker']
39
42
 
40
43
  buildTag(): Promise<string>
41
44
 
45
+ pageMappings(): Record<`/${string}`, PageMapping>
46
+
42
47
  reload(): Promise<void>
43
48
  }
44
49
 
@@ -77,7 +82,7 @@ class DankConfigInternal implements ResolvedDankConfig {
77
82
  #esbuildPort: number = DEFAULT_ESBUILD_PORT
78
83
  #esbuild: Readonly<Omit<EsbuildConfig, 'port'>> | undefined
79
84
  #pages: Readonly<Record<`/${string}`, PageMapping>> = {}
80
- #devPages: Readonly<DankConfig['devPages']>
85
+ #devPages: Readonly<ResolvedDankConfig['devPages']> = {}
81
86
  #services: Readonly<DankConfig['services']>
82
87
 
83
88
  constructor(
@@ -119,7 +124,7 @@ class DankConfigInternal implements ResolvedDankConfig {
119
124
  return this.#pages
120
125
  }
121
126
 
122
- get devPages(): Readonly<DankConfig['devPages']> {
127
+ get devPages(): Readonly<ResolvedDankConfig['devPages']> {
123
128
  return this.#devPages
124
129
  }
125
130
 
@@ -142,6 +147,17 @@ class DankConfigInternal implements ResolvedDankConfig {
142
147
  return this.#buildTag
143
148
  }
144
149
 
150
+ pageMappings(): ResolvedDankConfig['pages'] {
151
+ if (this.#mode === 'serve') {
152
+ return {
153
+ ...this.#pages,
154
+ ...this.#devPages,
155
+ }
156
+ } else {
157
+ return this.#pages
158
+ }
159
+ }
160
+
145
161
  async reload() {
146
162
  const userConfig = await resolveConfig(
147
163
  this.#modulePath,
@@ -153,7 +169,7 @@ class DankConfigInternal implements ResolvedDankConfig {
153
169
  this.#esbuildPort = resolveEsbuildPort(this.#flags, userConfig)
154
170
  this.#esbuild = Object.freeze(userConfig.esbuild)
155
171
  this.#pages = Object.freeze(normalizePages(userConfig.pages))
156
- this.#devPages = Object.freeze(userConfig.devPages)
172
+ this.#devPages = Object.freeze(normalizeDevPages(userConfig.devPages))
157
173
  this.#services = Object.freeze(userConfig.services)
158
174
  this.#serviceWorkerBuilder = userConfig.serviceWorker
159
175
  }
@@ -433,3 +449,27 @@ function normalizePages(
433
449
  }
434
450
  return result
435
451
  }
452
+
453
+ function normalizeDevPages(
454
+ pages: DankConfig['devPages'],
455
+ ): Record<string, Omit<DevPageMapping & PageMapping, 'pattern'>> {
456
+ if (pages) {
457
+ const result: Record<
458
+ string,
459
+ Omit<DevPageMapping & PageMapping, 'pattern'>
460
+ > = {}
461
+ for (const [url, mapping] of Object.entries(pages)) {
462
+ if (typeof mapping === 'string') {
463
+ result[url] = {
464
+ label: '',
465
+ webpage: mapping,
466
+ }
467
+ } else {
468
+ result[url] = mapping
469
+ }
470
+ }
471
+ return result
472
+ } else {
473
+ return {}
474
+ }
475
+ }
package/lib/dirs.ts CHANGED
@@ -60,29 +60,62 @@ export class Resolver {
60
60
  return join(this.#dirs.projectRootAbs, ...p)
61
61
  }
62
62
 
63
- // `p` is expected to be a relative path resolvable from the project dir
64
- isProjectSubpathInPagesDir(p: string): boolean {
65
- return resolve(join(this.#dirs.projectRootAbs, p)).startsWith(
63
+ isPagesSubpathResolvedToPagesDirSubpath(p: string): boolean {
64
+ return verifySubpathInRoot(this.#dirs.pagesAbs, this.#dirs.pagesAbs, p)
65
+ }
66
+
67
+ isPagesSubpathResolvedToProjectDirSubpath(p: string): boolean {
68
+ return verifySubpathInRoot(
69
+ this.#dirs.pagesAbs,
70
+ this.#dirs.projectRootAbs,
71
+ p,
72
+ )
73
+ }
74
+
75
+ isProjectSubpathResolvedToPagesDirSubpath(p: string): boolean {
76
+ return verifySubpathInRoot(
77
+ this.#dirs.projectRootAbs,
66
78
  this.#dirs.pagesAbs,
79
+ p,
67
80
  )
68
81
  }
69
82
 
70
- // `p` is expected to be a relative path resolvable from the pages dir
71
- isPagesSubpathInPagesDir(p: string): boolean {
72
- return this.isProjectSubpathInPagesDir(join(this.#dirs.pages, p))
83
+ isProjectSubpathResolvedToProjectDirSubpath(p: string): boolean {
84
+ return verifySubpathInRoot(
85
+ this.#dirs.projectRootAbs,
86
+ this.#dirs.projectRootAbs,
87
+ p,
88
+ )
73
89
  }
74
90
 
75
91
  projectPathFromAbsolute(p: string) {
76
92
  return p.replace(this.#dirs.projectRootAbs, '').substring(1)
77
93
  }
78
94
 
79
- // resolve a pages subpath from a resource within the pages directory by a relative href
80
95
  // `from` is expected to be a pages resource fs path starting with `pages/` and ending with filename
81
- // the result will be a pages subpath and will not have the pages dir prefix
82
- // returns 'outofbounds' if the relative path does not resolve to a file within the pages dir
83
- resolveHrefInPagesDir(from: string, href: string): string | ResolveError {
96
+ // `href` is a source relative path to another source used by a script src, link href or Worker ctor URL
97
+ // returns 'outofbounds' if the resolved path is not in the pages directory
98
+ resolvePagesRelativeHrefInPagesDir(
99
+ from: string,
100
+ href: string,
101
+ ): string | ResolveError {
84
102
  const p = join(dirname(from), href)
85
- if (this.isProjectSubpathInPagesDir(p)) {
103
+ if (this.isProjectSubpathResolvedToPagesDirSubpath(p)) {
104
+ return p
105
+ } else {
106
+ return 'outofbounds'
107
+ }
108
+ }
109
+
110
+ // `from` is expected to be a pages resource fs path starting with `pages/` and ending with filename
111
+ // `href` is a source relative path to another source used by a script src, link href or Worker ctor URL
112
+ // returns 'outofbounds' if the resolved path is not in the project directory
113
+ resolvePagesRelativeHrefInProjectDir(
114
+ from: string,
115
+ href: string,
116
+ ): string | ResolveError {
117
+ const p = join(dirname(from), href)
118
+ if (this.isProjectSubpathResolvedToProjectDirSubpath(p)) {
86
119
  return p
87
120
  } else {
88
121
  return 'outofbounds'
@@ -90,6 +123,14 @@ export class Resolver {
90
123
  }
91
124
  }
92
125
 
126
+ function verifySubpathInRoot(
127
+ resolveFrom: string,
128
+ expectWithin: string,
129
+ testSubpath: string,
130
+ ): boolean {
131
+ return resolve(join(resolveFrom, testSubpath)).startsWith(expectWithin)
132
+ }
133
+
93
134
  class WindowsResolver extends Resolver {
94
135
  constructor(dirs: DankDirectories) {
95
136
  super(dirs)
@@ -99,7 +140,21 @@ class WindowsResolver extends Resolver {
99
140
  return super.projectPathFromAbsolute(p).replaceAll('\\', '/')
100
141
  }
101
142
 
102
- resolveHrefInPagesDir(from: string, href: string): string | ResolveError {
103
- return super.resolveHrefInPagesDir(from, href).replaceAll('\\', '/')
143
+ resolvePagesRelativeHrefInPagesDir(
144
+ from: string,
145
+ href: string,
146
+ ): string | ResolveError {
147
+ return super
148
+ .resolvePagesRelativeHrefInPagesDir(from, href)
149
+ .replaceAll('\\', '/')
150
+ }
151
+
152
+ resolvePagesRelativeHrefInProjectDir(
153
+ from: string,
154
+ href: string,
155
+ ): string | ResolveError {
156
+ return super
157
+ .resolvePagesRelativeHrefInProjectDir(from, href)
158
+ .replaceAll('\\', '/')
104
159
  }
105
160
  }
package/lib/esbuild.ts CHANGED
@@ -156,10 +156,11 @@ export function workersPlugin(r: BuildRegistry): Plugin {
156
156
  1,
157
157
  workerCtorMatch.groups!.url.length - 1,
158
158
  )
159
- const workerEntryPoint = r.resolver.resolveHrefInPagesDir(
160
- clientScript,
161
- workerUrl,
162
- )
159
+ const workerEntryPoint =
160
+ r.resolver.resolvePagesRelativeHrefInProjectDir(
161
+ clientScript,
162
+ workerUrl,
163
+ )
163
164
  if (workerEntryPoint === 'outofbounds') {
164
165
  if (!errors) errors = []
165
166
  errors.push(
@@ -256,7 +257,7 @@ function outofboundsWorkerUrlCtorArg(
256
257
  ): PartialMessage {
257
258
  return {
258
259
  id: 'worker-url-outofbounds',
259
- text: `The ${workerCtorMatch.groups!.ctor} constructor URL arg \`${workerCtorMatch.groups!.url}\` cannot resolve to a path outside of the pages directory`,
260
+ text: `The ${workerCtorMatch.groups!.ctor} constructor URL arg \`${workerCtorMatch.groups!.url}\` cannot resolve to a path outside of the project directory`,
260
261
  location,
261
262
  }
262
263
  }
package/lib/html.ts CHANGED
@@ -237,7 +237,11 @@ export class HtmlEntrypoint extends EventEmitter<HtmlEntrypointEvents> {
237
237
  )
238
238
  return false
239
239
  }
240
- if (!this.#resolver.isPagesSubpathInPagesDir(p.fsPath)) {
240
+ if (
241
+ !this.#resolver.isPagesSubpathResolvedToPagesDirSubpath(
242
+ p.fsPath,
243
+ )
244
+ ) {
241
245
  this.#error(
242
246
  `partials cannot be referenced from outside the pages dir like \`${p.specifier}\` in webpage \`${join(this.#c.dirs.pages, this.#fsPath)}\``,
243
247
  )
@@ -395,7 +399,7 @@ export class HtmlEntrypoint extends EventEmitter<HtmlEntrypointEvents> {
395
399
  elem: Element,
396
400
  ): ImportedScript {
397
401
  const inPath = join(this.#c.dirs.pages, dirname(this.#fsPath), href)
398
- if (!this.#resolver.isProjectSubpathInPagesDir(inPath)) {
402
+ if (!this.#resolver.isPagesSubpathResolvedToPagesDirSubpath(inPath)) {
399
403
  throw new DankError(
400
404
  `href \`${href}\` in webpage \`${join(this.#c.dirs.pages, this.#fsPath)}\` cannot reference sources outside of the pages directory`,
401
405
  )
package/lib/registry.ts CHANGED
@@ -231,11 +231,10 @@ export class WebsiteRegistry extends EventEmitter<WebsiteRegistryEvents> {
231
231
  }
232
232
 
233
233
  #configDiff() {
234
- const updatePages: ResolvedDankConfig['pages'] = this.#c.devPages
235
- ? { ...this.#c.pages, ...this.#c.devPages }
236
- : { ...this.#c.pages }
237
234
  const prevPages = new Set(Object.keys(this.#pages))
238
- for (const [urlPath, mapping] of Object.entries(updatePages)) {
235
+ for (const [urlPath, mapping] of Object.entries(
236
+ this.#c.pageMappings(),
237
+ )) {
239
238
  const existingPage = prevPages.delete(urlPath as `/${string}`)
240
239
  if (existingPage) {
241
240
  this.#configPageUpdate(urlPath as `/${string}`, mapping)
package/lib_js/config.js CHANGED
@@ -36,7 +36,7 @@ class DankConfigInternal {
36
36
  #esbuildPort = DEFAULT_ESBUILD_PORT;
37
37
  #esbuild;
38
38
  #pages = {};
39
- #devPages;
39
+ #devPages = {};
40
40
  #services;
41
41
  constructor(mode, modulePath, dirs) {
42
42
  this.#dirs = dirs;
@@ -80,6 +80,16 @@ class DankConfigInternal {
80
80
  }
81
81
  return this.#buildTag;
82
82
  }
83
+ pageMappings() {
84
+ if (this.#mode === "serve") {
85
+ return {
86
+ ...this.#pages,
87
+ ...this.#devPages
88
+ };
89
+ } else {
90
+ return this.#pages;
91
+ }
92
+ }
83
93
  async reload() {
84
94
  const userConfig = await resolveConfig(this.#modulePath, resolveDankDetails(this.#mode, this.#flags));
85
95
  this.#buildTag = null;
@@ -88,7 +98,7 @@ class DankConfigInternal {
88
98
  this.#esbuildPort = resolveEsbuildPort(this.#flags, userConfig);
89
99
  this.#esbuild = Object.freeze(userConfig.esbuild);
90
100
  this.#pages = Object.freeze(normalizePages(userConfig.pages));
91
- this.#devPages = Object.freeze(userConfig.devPages);
101
+ this.#devPages = Object.freeze(normalizeDevPages(userConfig.devPages));
92
102
  this.#services = Object.freeze(userConfig.services);
93
103
  this.#serviceWorkerBuilder = userConfig.serviceWorker;
94
104
  }
@@ -280,6 +290,24 @@ function normalizePages(pages) {
280
290
  }
281
291
  return result;
282
292
  }
293
+ function normalizeDevPages(pages) {
294
+ if (pages) {
295
+ const result = {};
296
+ for (const [url, mapping] of Object.entries(pages)) {
297
+ if (typeof mapping === "string") {
298
+ result[url] = {
299
+ label: "",
300
+ webpage: mapping
301
+ };
302
+ } else {
303
+ result[url] = mapping;
304
+ }
305
+ }
306
+ return result;
307
+ } else {
308
+ return {};
309
+ }
310
+ }
283
311
  export {
284
312
  loadConfig
285
313
  };
package/lib_js/dirs.js CHANGED
@@ -38,30 +38,47 @@ class Resolver {
38
38
  absProjectPath(...p) {
39
39
  return join(this.#dirs.projectRootAbs, ...p);
40
40
  }
41
- // `p` is expected to be a relative path resolvable from the project dir
42
- isProjectSubpathInPagesDir(p) {
43
- return resolve(join(this.#dirs.projectRootAbs, p)).startsWith(this.#dirs.pagesAbs);
41
+ isPagesSubpathResolvedToPagesDirSubpath(p) {
42
+ return verifySubpathInRoot(this.#dirs.pagesAbs, this.#dirs.pagesAbs, p);
44
43
  }
45
- // `p` is expected to be a relative path resolvable from the pages dir
46
- isPagesSubpathInPagesDir(p) {
47
- return this.isProjectSubpathInPagesDir(join(this.#dirs.pages, p));
44
+ isPagesSubpathResolvedToProjectDirSubpath(p) {
45
+ return verifySubpathInRoot(this.#dirs.pagesAbs, this.#dirs.projectRootAbs, p);
46
+ }
47
+ isProjectSubpathResolvedToPagesDirSubpath(p) {
48
+ return verifySubpathInRoot(this.#dirs.projectRootAbs, this.#dirs.pagesAbs, p);
49
+ }
50
+ isProjectSubpathResolvedToProjectDirSubpath(p) {
51
+ return verifySubpathInRoot(this.#dirs.projectRootAbs, this.#dirs.projectRootAbs, p);
48
52
  }
49
53
  projectPathFromAbsolute(p) {
50
54
  return p.replace(this.#dirs.projectRootAbs, "").substring(1);
51
55
  }
52
- // resolve a pages subpath from a resource within the pages directory by a relative href
53
56
  // `from` is expected to be a pages resource fs path starting with `pages/` and ending with filename
54
- // the result will be a pages subpath and will not have the pages dir prefix
55
- // returns 'outofbounds' if the relative path does not resolve to a file within the pages dir
56
- resolveHrefInPagesDir(from, href) {
57
+ // `href` is a source relative path to another source used by a script src, link href or Worker ctor URL
58
+ // returns 'outofbounds' if the resolved path is not in the pages directory
59
+ resolvePagesRelativeHrefInPagesDir(from, href) {
57
60
  const p = join(dirname(from), href);
58
- if (this.isProjectSubpathInPagesDir(p)) {
61
+ if (this.isProjectSubpathResolvedToPagesDirSubpath(p)) {
62
+ return p;
63
+ } else {
64
+ return "outofbounds";
65
+ }
66
+ }
67
+ // `from` is expected to be a pages resource fs path starting with `pages/` and ending with filename
68
+ // `href` is a source relative path to another source used by a script src, link href or Worker ctor URL
69
+ // returns 'outofbounds' if the resolved path is not in the project directory
70
+ resolvePagesRelativeHrefInProjectDir(from, href) {
71
+ const p = join(dirname(from), href);
72
+ if (this.isProjectSubpathResolvedToProjectDirSubpath(p)) {
59
73
  return p;
60
74
  } else {
61
75
  return "outofbounds";
62
76
  }
63
77
  }
64
78
  }
79
+ function verifySubpathInRoot(resolveFrom, expectWithin, testSubpath) {
80
+ return resolve(join(resolveFrom, testSubpath)).startsWith(expectWithin);
81
+ }
65
82
  class WindowsResolver extends Resolver {
66
83
  constructor(dirs) {
67
84
  super(dirs);
@@ -69,8 +86,11 @@ class WindowsResolver extends Resolver {
69
86
  projectPathFromAbsolute(p) {
70
87
  return super.projectPathFromAbsolute(p).replaceAll("\\", "/");
71
88
  }
72
- resolveHrefInPagesDir(from, href) {
73
- return super.resolveHrefInPagesDir(from, href).replaceAll("\\", "/");
89
+ resolvePagesRelativeHrefInPagesDir(from, href) {
90
+ return super.resolvePagesRelativeHrefInPagesDir(from, href).replaceAll("\\", "/");
91
+ }
92
+ resolvePagesRelativeHrefInProjectDir(from, href) {
93
+ return super.resolvePagesRelativeHrefInProjectDir(from, href).replaceAll("\\", "/");
74
94
  }
75
95
  }
76
96
  export {
package/lib_js/esbuild.js CHANGED
@@ -99,7 +99,7 @@ function workersPlugin(r) {
99
99
  clientScript = r.resolver.projectPathFromAbsolute(args.path);
100
100
  }
101
101
  const workerUrl = workerCtorMatch.groups.url.substring(1, workerCtorMatch.groups.url.length - 1);
102
- const workerEntryPoint = r.resolver.resolveHrefInPagesDir(clientScript, workerUrl);
102
+ const workerEntryPoint = r.resolver.resolvePagesRelativeHrefInProjectDir(clientScript, workerUrl);
103
103
  if (workerEntryPoint === "outofbounds") {
104
104
  if (!errors)
105
105
  errors = [];
@@ -159,7 +159,7 @@ function locationFromMatch(args, contents, match) {
159
159
  function outofboundsWorkerUrlCtorArg(location, workerCtorMatch) {
160
160
  return {
161
161
  id: "worker-url-outofbounds",
162
- text: `The ${workerCtorMatch.groups.ctor} constructor URL arg \`${workerCtorMatch.groups.url}\` cannot resolve to a path outside of the pages directory`,
162
+ text: `The ${workerCtorMatch.groups.ctor} constructor URL arg \`${workerCtorMatch.groups.url}\` cannot resolve to a path outside of the project directory`,
163
163
  location
164
164
  };
165
165
  }
package/lib_js/html.js CHANGED
@@ -115,7 +115,7 @@ class HtmlEntrypoint extends EventEmitter {
115
115
  this.#error(`partials cannot be referenced with an absolute path like \`${p.specifier}\` in webpage \`${join(this.#c.dirs.pages, this.#fsPath)}\``);
116
116
  return false;
117
117
  }
118
- if (!this.#resolver.isPagesSubpathInPagesDir(p.fsPath)) {
118
+ if (!this.#resolver.isPagesSubpathResolvedToPagesDirSubpath(p.fsPath)) {
119
119
  this.#error(`partials cannot be referenced from outside the pages dir like \`${p.specifier}\` in webpage \`${join(this.#c.dirs.pages, this.#fsPath)}\``);
120
120
  return false;
121
121
  }
@@ -222,7 +222,7 @@ class HtmlEntrypoint extends EventEmitter {
222
222
  }
223
223
  #parseImport(type, href, elem) {
224
224
  const inPath = join(this.#c.dirs.pages, dirname(this.#fsPath), href);
225
- if (!this.#resolver.isProjectSubpathInPagesDir(inPath)) {
225
+ if (!this.#resolver.isPagesSubpathResolvedToPagesDirSubpath(inPath)) {
226
226
  throw new DankError(`href \`${href}\` in webpage \`${join(this.#c.dirs.pages, this.#fsPath)}\` cannot reference sources outside of the pages directory`);
227
227
  }
228
228
  let outPath = join(dirname(this.#fsPath), href);
@@ -125,9 +125,8 @@ class WebsiteRegistry extends EventEmitter {
125
125
  return manifest;
126
126
  }
127
127
  #configDiff() {
128
- const updatePages = this.#c.devPages ? { ...this.#c.pages, ...this.#c.devPages } : { ...this.#c.pages };
129
128
  const prevPages = new Set(Object.keys(this.#pages));
130
- for (const [urlPath, mapping] of Object.entries(updatePages)) {
129
+ for (const [urlPath, mapping] of Object.entries(this.#c.pageMappings())) {
131
130
  const existingPage = prevPages.delete(urlPath);
132
131
  if (existingPage) {
133
132
  this.#configPageUpdate(urlPath, mapping);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eighty4/dank",
3
- "version": "0.0.5-4",
3
+ "version": "0.0.5-6",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "author": "Adam McKee Bennett <adam.be.g84d@gmail.com>",