@eighty4/dank 0.0.4-1 → 0.0.4-3

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.
@@ -1,210 +1,191 @@
1
- import { spawn } from 'node:child_process';
2
- import { basename, isAbsolute, resolve } from 'node:path';
3
- // up to date representation of dank.config.ts services
1
+ import { spawn } from "node:child_process";
2
+ import { basename, isAbsolute, resolve } from "node:path";
4
3
  const running = [];
5
4
  let signal;
6
- // batch of services that must be stopped before starting new services
7
5
  let updating = null;
8
- export function startDevServices(c, _signal) {
9
- signal = _signal;
10
- if (c.services?.length) {
11
- for (const s of c.services) {
12
- running.push({ s, process: startService(s) });
13
- }
6
+ function startDevServices(services, _signal) {
7
+ signal = _signal;
8
+ if (services?.length) {
9
+ for (const s of services) {
10
+ running.push({ s, process: startService(s) });
11
+ }
12
+ }
13
+ return {
14
+ http: {
15
+ get running() {
16
+ return running.map(({ s }) => s.http).filter((http) => !!http);
17
+ }
14
18
  }
15
- return {
16
- http: {
17
- get running() {
18
- return running.map(({ s }) => s.http).filter(http => !!http);
19
- },
20
- },
21
- };
19
+ };
22
20
  }
23
- export function updateDevServices(c) {
24
- if (!c.services?.length) {
25
- if (running.length) {
26
- if (updating === null) {
27
- updating = { stopping: [], starting: [] };
28
- }
29
- running.forEach(({ s, process }) => {
30
- if (process) {
31
- stopService(s, process);
32
- }
33
- else {
34
- removeFromUpdating(s);
35
- }
36
- });
37
- running.length = 0;
21
+ function updateDevServices(services) {
22
+ if (!services?.length) {
23
+ if (running.length) {
24
+ if (updating === null) {
25
+ updating = { stopping: [], starting: [] };
26
+ }
27
+ running.forEach(({ s, process: process2 }) => {
28
+ if (process2) {
29
+ stopService(s, process2);
30
+ } else {
31
+ removeFromUpdating(s);
38
32
  }
33
+ });
34
+ running.length = 0;
39
35
  }
40
- else {
41
- if (updating === null) {
42
- updating = { stopping: [], starting: [] };
43
- }
44
- const keep = [];
45
- const next = [];
46
- for (const s of c.services) {
47
- let found = false;
48
- for (let i = 0; i < running.length; i++) {
49
- const p = running[i].s;
50
- if (matchingConfig(s, p)) {
51
- found = true;
52
- keep.push(i);
53
- break;
54
- }
55
- }
56
- if (!found) {
57
- next.push(s);
58
- }
59
- }
60
- for (let i = running.length - 1; i >= 0; i--) {
61
- if (!keep.includes(i)) {
62
- const { s, process } = running[i];
63
- if (process) {
64
- stopService(s, process);
65
- }
66
- else {
67
- removeFromUpdating(s);
68
- }
69
- running.splice(i, 1);
70
- }
36
+ } else {
37
+ if (updating === null) {
38
+ updating = { stopping: [], starting: [] };
39
+ }
40
+ const keep = [];
41
+ const next = [];
42
+ for (const s of services) {
43
+ let found = false;
44
+ for (let i = 0; i < running.length; i++) {
45
+ const p = running[i].s;
46
+ if (matchingConfig(s, p)) {
47
+ found = true;
48
+ keep.push(i);
49
+ break;
71
50
  }
72
- if (updating.stopping.length) {
73
- for (const s of next) {
74
- if (!updating.starting.find(queued => matchingConfig(queued, s))) {
75
- updating.starting.push(s);
76
- }
77
- }
51
+ }
52
+ if (!found) {
53
+ next.push(s);
54
+ }
55
+ }
56
+ for (let i = running.length - 1; i >= 0; i--) {
57
+ if (!keep.includes(i)) {
58
+ const { s, process: process2 } = running[i];
59
+ if (process2) {
60
+ stopService(s, process2);
61
+ } else {
62
+ removeFromUpdating(s);
78
63
  }
79
- else {
80
- updating = null;
81
- for (const s of next) {
82
- running.push({ s, process: startService(s) });
83
- }
64
+ running.splice(i, 1);
65
+ }
66
+ }
67
+ if (updating.stopping.length) {
68
+ for (const s of next) {
69
+ if (!updating.starting.find((queued) => matchingConfig(queued, s))) {
70
+ updating.starting.push(s);
84
71
  }
72
+ }
73
+ } else {
74
+ updating = null;
75
+ for (const s of next) {
76
+ running.push({ s, process: startService(s) });
77
+ }
85
78
  }
79
+ }
86
80
  }
87
- function stopService(s, process) {
88
- opPrint(s, 'stopping');
89
- updating.stopping.push(s);
90
- process.kill();
81
+ function stopService(s, process2) {
82
+ opPrint(s, "stopping");
83
+ updating.stopping.push(s);
84
+ process2.kill();
91
85
  }
92
86
  function matchingConfig(a, b) {
93
- if (a.command !== b.command) {
94
- return false;
95
- }
96
- if (a.cwd !== b.cwd) {
97
- return false;
98
- }
99
- if (!a.env && !b.env) {
100
- return true;
101
- }
102
- else if (a.env && !b.env) {
103
- return false;
104
- }
105
- else if (!a.env && b.env) {
87
+ if (a.command !== b.command) {
88
+ return false;
89
+ }
90
+ if (a.cwd !== b.cwd) {
91
+ return false;
92
+ }
93
+ if (!a.env && !b.env) {
94
+ return true;
95
+ } else if (a.env && !b.env) {
96
+ return false;
97
+ } else if (!a.env && b.env) {
98
+ return false;
99
+ } else if (Object.keys(a.env).length !== Object.keys(b.env).length) {
100
+ return false;
101
+ } else {
102
+ for (const k of Object.keys(a.env)) {
103
+ if (!b.env[k]) {
106
104
  return false;
107
- }
108
- else if (Object.keys(a.env).length !== Object.keys(b.env).length) {
105
+ } else if (a.env[k] !== b.env[k]) {
109
106
  return false;
107
+ }
110
108
  }
111
- else {
112
- for (const k of Object.keys(a.env)) {
113
- if (!b.env[k]) {
114
- return false;
115
- }
116
- else if (a.env[k] !== b.env[k]) {
117
- return false;
118
- }
119
- }
120
- }
121
- return true;
109
+ }
110
+ return true;
122
111
  }
123
112
  function startService(s) {
124
- opPrint(s, 'starting');
125
- const splitCmdAndArgs = s.command.split(/\s+/);
126
- const cmd = splitCmdAndArgs[0];
127
- const args = splitCmdAndArgs.length === 1 ? [] : splitCmdAndArgs.slice(1);
128
- const spawned = spawn(cmd, args, {
129
- cwd: resolveCwd(s.cwd),
130
- env: s.env,
131
- signal,
132
- detached: false,
133
- shell: false,
134
- });
135
- const stdoutLabel = logLabel(s.cwd, cmd, args, 32);
136
- spawned.stdout.on('data', chunk => printChunk(stdoutLabel, chunk));
137
- const stderrLabel = logLabel(s.cwd, cmd, args, 31);
138
- spawned.stderr.on('data', chunk => printChunk(stderrLabel, chunk));
139
- spawned.on('error', e => {
140
- if (e.name !== 'AbortError') {
141
- const cause = 'code' in e && e.code === 'ENOENT'
142
- ? 'program not found'
143
- : e.message;
144
- opPrint(s, 'error: ' + cause);
145
- }
146
- removeFromRunning(s);
147
- });
148
- spawned.on('exit', () => {
149
- opPrint(s, 'exited');
150
- removeFromRunning(s);
151
- removeFromUpdating(s);
152
- });
153
- return spawned;
113
+ 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);
125
+ spawned.stdout.on("data", (chunk) => printChunk(stdoutLabel, chunk));
126
+ const stderrLabel = logLabel(s.cwd, cmd, args, 31);
127
+ spawned.stderr.on("data", (chunk) => printChunk(stderrLabel, chunk));
128
+ 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
+ removeFromRunning(s);
134
+ });
135
+ spawned.on("exit", () => {
136
+ opPrint(s, "exited");
137
+ removeFromRunning(s);
138
+ removeFromUpdating(s);
139
+ });
140
+ return spawned;
154
141
  }
155
142
  function removeFromRunning(s) {
156
- for (let i = 0; i < running.length; i++) {
157
- if (matchingConfig(running[i].s, s)) {
158
- running.splice(i, 1);
159
- return;
160
- }
143
+ for (let i = 0; i < running.length; i++) {
144
+ if (matchingConfig(running[i].s, s)) {
145
+ running.splice(i, 1);
146
+ return;
161
147
  }
148
+ }
162
149
  }
163
150
  function removeFromUpdating(s) {
164
- if (updating !== null) {
165
- for (let i = 0; i < updating.stopping.length; i++) {
166
- if (matchingConfig(updating.stopping[i], s)) {
167
- updating.stopping.splice(i, 1);
168
- if (!updating.stopping.length) {
169
- updating.starting.forEach(startService);
170
- updating = null;
171
- return;
172
- }
173
- }
151
+ if (updating !== null) {
152
+ for (let i = 0; i < updating.stopping.length; i++) {
153
+ if (matchingConfig(updating.stopping[i], s)) {
154
+ updating.stopping.splice(i, 1);
155
+ if (!updating.stopping.length) {
156
+ updating.starting.forEach(startService);
157
+ updating = null;
158
+ return;
174
159
  }
160
+ }
175
161
  }
162
+ }
176
163
  }
177
164
  function printChunk(label, c) {
178
- for (const l of parseChunk(c))
179
- console.log(label, l);
165
+ for (const l of parseChunk(c))
166
+ console.log(label, l);
180
167
  }
181
168
  function parseChunk(c) {
182
- return c
183
- .toString()
184
- .replace(/\r?\n$/, '')
185
- .split(/\r?\n/);
169
+ return c.toString().replace(/\r?\n$/, "").split(/\r?\n/);
186
170
  }
187
171
  function resolveCwd(p) {
188
- if (!p || isAbsolute(p)) {
189
- return p;
190
- }
191
- else {
192
- return resolve(process.cwd(), p);
193
- }
172
+ if (!p || isAbsolute(p)) {
173
+ return p;
174
+ } else {
175
+ return resolve(process.cwd(), p);
176
+ }
194
177
  }
195
178
  function opPrint(s, msg) {
196
- console.log(opLabel(s), msg);
179
+ console.log(opLabel(s), msg);
197
180
  }
198
181
  function opLabel(s) {
199
- return `\`${s.cwd ? s.cwd + ' ' : ''}${s.command}\``;
182
+ return `\`${s.cwd ? s.cwd + " " : ""}${s.command}\``;
200
183
  }
201
184
  function logLabel(cwd, cmd, args, ansiColor) {
202
- cwd = !cwd
203
- ? './'
204
- : cwd.startsWith('/')
205
- ? `/.../${basename(cwd)}`
206
- : cwd.startsWith('.')
207
- ? cwd
208
- : `./${cwd}`;
209
- return `\u001b[${ansiColor}m[\u001b[1m${cmd}\u001b[22m ${args.join(' ')} \u001b[2;3m${cwd}\u001b[22;23m]\u001b[0m`;
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
187
  }
188
+ export {
189
+ startDevServices,
190
+ updateDevServices
191
+ };
@@ -0,0 +1,35 @@
1
+ import { watch as createWatch } from "node:fs/promises";
2
+ async function watch(p, signal, fire) {
3
+ const delayFire = 90;
4
+ const timeout = 100;
5
+ let changes = {};
6
+ try {
7
+ for await (const { filename } of createWatch(p, {
8
+ recursive: true,
9
+ signal
10
+ })) {
11
+ if (filename) {
12
+ if (!changes[filename]) {
13
+ const now = Date.now();
14
+ changes[filename] = now + delayFire;
15
+ setTimeout(() => {
16
+ const now2 = Date.now();
17
+ for (const [filename2, then] of Object.entries(changes)) {
18
+ if (then <= now2) {
19
+ fire(filename2);
20
+ delete changes[filename2];
21
+ }
22
+ }
23
+ }, timeout);
24
+ }
25
+ }
26
+ }
27
+ } catch (e) {
28
+ if (e.name !== "AbortError") {
29
+ throw e;
30
+ }
31
+ }
32
+ }
33
+ export {
34
+ watch
35
+ };
@@ -2,6 +2,7 @@ import type { Plugin as EsbuildPlugin } from 'esbuild';
2
2
  export type DankConfig = {
3
3
  esbuild?: EsbuildConfig;
4
4
  pages: Record<`/${string}`, `${string}.html` | PageMapping>;
5
+ devPages?: Record<`/__${string}`, `${string}.html` | DevPageMapping>;
5
6
  port?: number;
6
7
  previewPort?: number;
7
8
  services?: Array<DevService>;
@@ -10,6 +11,10 @@ export type PageMapping = {
10
11
  pattern?: RegExp;
11
12
  webpage: `${string}.html`;
12
13
  };
14
+ export type DevPageMapping = {
15
+ label: string;
16
+ webpage: `${string}.html`;
17
+ };
13
18
  export type DevService = {
14
19
  command: string;
15
20
  cwd?: string;
@@ -24,4 +29,11 @@ export type EsbuildConfig = {
24
29
  port?: number;
25
30
  };
26
31
  export type EsbuildLoader = 'base64' | 'binary' | 'copy' | 'dataurl' | 'empty' | 'file' | 'json' | 'text';
27
- export declare function defineConfig(c: Partial<DankConfig>): Promise<DankConfig>;
32
+ export type DankDetails = {
33
+ dev: boolean;
34
+ production: boolean;
35
+ mode: 'build' | 'serve';
36
+ };
37
+ export type DankConfigFunction = (dank: DankDetails) => Partial<DankConfig> | Promise<Partial<DankConfig>>;
38
+ export declare function defineConfig(config: Partial<DankConfig>): Partial<DankConfig>;
39
+ export declare function defineConfig(config: DankConfigFunction): DankConfigFunction;
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "@eighty4/dank",
3
- "version": "0.0.4-1",
3
+ "version": "0.0.4-3",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "author": "Adam McKee Bennett <adam.be.g84d@gmail.com>",
7
+ "engines": {
8
+ "node": ">=24"
9
+ },
7
10
  "description": "Multi-page development system for CDN-deployed websites",
8
11
  "keywords": [
9
12
  "frontend",
@@ -34,17 +37,20 @@
34
37
  "typescript": "^5.9.2"
35
38
  },
36
39
  "files": [
37
- "client/esbuild.js",
40
+ "client/client.js",
38
41
  "lib/*.ts",
39
42
  "lib_js/*.js",
40
43
  "lib_types/*.d.ts"
41
44
  ],
42
45
  "scripts": {
43
- "build": "tsc && tsc -p tsconfig.client.json && tsc -p tsconfig.exports.json",
46
+ "build": "pnpm build:client && pnpm build:lib",
47
+ "build:client": "node scripts/build_client.ts",
48
+ "build:lib": "tsc && tsc -p tsconfig.exports.json",
49
+ "build:release": "pnpm build && ./scripts/prepare_release.ts",
44
50
  "fmt": "prettier --write .",
45
51
  "fmtcheck": "prettier --check .",
46
52
  "test": "node --test \"test/**/*.test.ts\"",
47
53
  "test:rebuild": "pnpm build && pnpm test",
48
- "typecheck": "tsc --noEmit"
54
+ "typecheck": "tsc --noEmit && tsc -p tsconfig.client.json --noEmit"
49
55
  }
50
56
  }
package/client/esbuild.js DELETED
@@ -1,91 +0,0 @@
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
- }