@pajh/buldng 0.0.3 → 0.0.5
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/package.json +19 -10
- package/src/MIME.json +11 -0
- package/src/buldng-async.js +20 -0
- package/src/buldng-files.js +58 -0
- package/src/buldng-html.js +262 -0
- package/src/buldng-lib.js +90 -0
- package/src/buldng-modal.ts +348 -0
- package/{dist → src}/buldng-ops.js +8 -8
- package/src/buldng-typescript.js +77 -0
- package/src/buldng-web.ts +142 -0
- package/{dist → src}/buldng.css +130 -0
- package/src/dev-errors.js +16 -0
- package/src/layout-err.ts +171 -0
- package/{dist → src}/layout.ts +4 -0
- package/src/serve-lib.js +567 -0
- package/src/serve.js +193 -0
- package/src/types/global.d.ts +19 -0
- package/{dist → src}/wrapper.html +2 -0
- package/dist/buldng-lib.js +0 -408
- package/dist/types/global.d.ts +0 -10
- /package/{dist → src}/buldng-validate.js +0 -0
- /package/{dist → src}/clean.sh +0 -0
- /package/{dist → src}/init.js +0 -0
- /package/{dist → src}/workfile.js +0 -0
package/package.json
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pajh/buldng",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"main": "
|
|
6
|
-
"files": [
|
|
5
|
+
"main": "./src/buldng-lib.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"src"
|
|
8
|
+
],
|
|
7
9
|
"scripts": {
|
|
8
|
-
"
|
|
10
|
+
"check": "tsc --noEmit",
|
|
11
|
+
"build": "npm run check"
|
|
9
12
|
},
|
|
10
13
|
"dependencies": {
|
|
11
|
-
|
|
12
|
-
"typescript": "^7.0.2"
|
|
14
|
+
"esbuild": "^0.28.2"
|
|
13
15
|
},
|
|
14
16
|
"devDependencies": {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
"typescript": "^6.0.3"
|
|
18
|
+
},
|
|
19
|
+
"exports": {
|
|
20
|
+
".": "./src/buldng-lib.js",
|
|
21
|
+
"./web": "./src/buldng-web.ts",
|
|
22
|
+
"./error": "./src/layout-err.ts",
|
|
23
|
+
"./MIME.json": "./src/MIME.json"
|
|
24
|
+
},
|
|
25
|
+
"bin": {
|
|
26
|
+
"buldng-serve": "./src/serve.js"
|
|
27
|
+
}
|
|
19
28
|
}
|
package/src/MIME.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Experimental async building functions
|
|
2
|
+
|
|
3
|
+
export function task(fn) {
|
|
4
|
+
return new Promise((resolve, reject) => {
|
|
5
|
+
try {
|
|
6
|
+
fn();
|
|
7
|
+
resolve();
|
|
8
|
+
} catch (err) {
|
|
9
|
+
reject(err);
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function parallel(...jobs) {
|
|
15
|
+
return Promise.all(jobs);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function finalise(fn) {
|
|
19
|
+
return fn();
|
|
20
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// buldng-files.js
|
|
2
|
+
// file handling functions for buldng
|
|
3
|
+
import { getDest, setSource, setDest, assertDestWrite, assertSourceRead, getInputs, looksLikeFilename, looksLikeHTML,
|
|
4
|
+
looksLikeCSS, looksLikeJS } from "./buldng-validate.js";
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
|
|
8
|
+
// --------------------------------------------------
|
|
9
|
+
// mkdir("puzzle")
|
|
10
|
+
// --------------------------------------------------
|
|
11
|
+
export function mkdir(relDir) {
|
|
12
|
+
const abs = assertDestWrite(relDir);
|
|
13
|
+
fs.mkdirSync(abs, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// --------------------------------------------------
|
|
17
|
+
// deepCopy — raw filesystem path
|
|
18
|
+
// --------------------------------------------------
|
|
19
|
+
export function deepCopy(from, to) {
|
|
20
|
+
let DEST = getDest();
|
|
21
|
+
|
|
22
|
+
console.log(`deepCopy: ${from} → ${to}`);
|
|
23
|
+
|
|
24
|
+
const absFrom = path.resolve(from);
|
|
25
|
+
|
|
26
|
+
if (to.includes("..")) {
|
|
27
|
+
throw new Error("SECURITY: Illegal path traversal in deepCopy()");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const absTo = to === "/" ? DEST : path.resolve(DEST, to);
|
|
31
|
+
|
|
32
|
+
if (!absTo.startsWith(DEST)) {
|
|
33
|
+
throw new Error(`SECURITY: deepCopy target escapes dest:
|
|
34
|
+
dest: ${DEST}
|
|
35
|
+
target: ${absTo}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!fs.existsSync(absFrom)) {
|
|
39
|
+
throw new Error(`deepCopy: source does not exist: ${absFrom}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function copyRecursive(src, dst) {
|
|
43
|
+
const stat = fs.statSync(src);
|
|
44
|
+
|
|
45
|
+
if (stat.isDirectory()) {
|
|
46
|
+
fs.mkdirSync(dst, { recursive: true });
|
|
47
|
+
const entries = fs.readdirSync(src);
|
|
48
|
+
for (const entry of entries) {
|
|
49
|
+
copyRecursive(path.join(src, entry), path.join(dst, entry));
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
|
53
|
+
fs.copyFileSync(src, dst);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
copyRecursive(absFrom, absTo);
|
|
58
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// buldng-html.js — HTML assembly subsystem
|
|
2
|
+
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
6
|
+
import { WorkFile } from "./workfile.js";
|
|
7
|
+
import {
|
|
8
|
+
ensureWorkFile,
|
|
9
|
+
ensureLeadingSlash
|
|
10
|
+
} from "./buldng-validate.js";
|
|
11
|
+
import {
|
|
12
|
+
replace,
|
|
13
|
+
append,
|
|
14
|
+
materialise,
|
|
15
|
+
compile
|
|
16
|
+
} from "./buldng-ops.js";
|
|
17
|
+
|
|
18
|
+
// --------------------------------------------------
|
|
19
|
+
// Registry for shell + projectCSS
|
|
20
|
+
// --------------------------------------------------
|
|
21
|
+
const registry = new Map();
|
|
22
|
+
|
|
23
|
+
export function reg(key, value) {
|
|
24
|
+
if (typeof key !== "string") {
|
|
25
|
+
throw new Error(`reg(): key must be a string`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const wf = ensureWorkFile(value, "reg");
|
|
29
|
+
|
|
30
|
+
if (registry.has(key)) {
|
|
31
|
+
console.log(`reg(): overwriting existing key '${key}'`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
registry.set(key, wf);
|
|
35
|
+
return wf;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function makeLinkOp({ name, ext, pre, post }) {
|
|
39
|
+
return function linkOp(input) {
|
|
40
|
+
|
|
41
|
+
// Case 1: string
|
|
42
|
+
if (typeof input === "string") {
|
|
43
|
+
if (!input.endsWith(ext)) {
|
|
44
|
+
throw new Error(`${name}(): expected a ${ext} filename or WorkFile`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const fixed = ensureLeadingSlash(input);
|
|
48
|
+
|
|
49
|
+
return new WorkFile(
|
|
50
|
+
"literal",
|
|
51
|
+
{ value: pre + fixed + post },
|
|
52
|
+
[],
|
|
53
|
+
name
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Case 2: WorkFile
|
|
58
|
+
if (input instanceof WorkFile) {
|
|
59
|
+
return new WorkFile(
|
|
60
|
+
"wrap",
|
|
61
|
+
{ pre, post },
|
|
62
|
+
[input],
|
|
63
|
+
name
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
throw new Error(`${name}(): expected a ${ext} filename or WorkFile`);
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const csslink = makeLinkOp({
|
|
72
|
+
name: "csslink",
|
|
73
|
+
ext: ".css",
|
|
74
|
+
pre: `<link rel="stylesheet" href="`,
|
|
75
|
+
post: `">`
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
export const scriptlink = makeLinkOp({
|
|
79
|
+
name: "scriptlink",
|
|
80
|
+
ext: ".js",
|
|
81
|
+
pre: `<script type="module" src="`,
|
|
82
|
+
post: `"></script>`
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// --------------------------------------------------
|
|
86
|
+
// Helper: absolute path to package files
|
|
87
|
+
// --------------------------------------------------
|
|
88
|
+
function packageFileAbs(filename) {
|
|
89
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
90
|
+
const __dirname = dirname(__filename);
|
|
91
|
+
return join(__dirname, filename);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// --------------------------------------------------
|
|
95
|
+
// buldngHTMLWrapper()
|
|
96
|
+
// --------------------------------------------------
|
|
97
|
+
export function buldngHTMLWrapper(source) {
|
|
98
|
+
const srcWF = ensureWorkFile(source, "source");
|
|
99
|
+
|
|
100
|
+
// Compile buldng's layout.ts (lazy)
|
|
101
|
+
const layoutTS = new WorkFile("file", { absPath: packageFileAbs("layout.ts") }, []);
|
|
102
|
+
const layoutJS = compile(layoutTS);
|
|
103
|
+
|
|
104
|
+
// Save compiled layout.js to site root
|
|
105
|
+
layoutJS.save("layout.js");
|
|
106
|
+
|
|
107
|
+
// Save buldng.css to site root
|
|
108
|
+
const buldngCSS = new WorkFile("file", { absPath: packageFileAbs("buldng.css") }, []);
|
|
109
|
+
buldngCSS.save("buldng.css");
|
|
110
|
+
|
|
111
|
+
// Wrap project shell in wrapper.html
|
|
112
|
+
const wrapperWF = new WorkFile("file", { absPath: packageFileAbs("wrapper.html") }, []);
|
|
113
|
+
return replace(wrapperWF, "<!--CAMERA-STYLE-->", srcWF);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// --------------------------------------------------
|
|
117
|
+
// pageHTML()
|
|
118
|
+
// --------------------------------------------------
|
|
119
|
+
const _warned = {
|
|
120
|
+
shell: false,
|
|
121
|
+
projectCSS: false
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export function pageHTML({ app, css = null, js = null, out }) {
|
|
125
|
+
if (!out || typeof out !== "string") {
|
|
126
|
+
throw new Error("pageHTML(): 'out' must be a non-empty string");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!app) {
|
|
130
|
+
throw new Error("pageHTML(): 'app' is required and cannot be null");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const appWF = ensureWorkFile(app, "app");
|
|
134
|
+
|
|
135
|
+
const shellWF = registry.get("shell") ?? null;
|
|
136
|
+
const projectCSS = registry.get("projectCSS") ?? null;
|
|
137
|
+
|
|
138
|
+
if (!shellWF && !_warned.shell) {
|
|
139
|
+
console.warn(
|
|
140
|
+
"pageHTML(): no 'shell' registered — building raw page HTML.\n" +
|
|
141
|
+
"To register a shell use: reg('shell', file`layout/shell.html`)"
|
|
142
|
+
);
|
|
143
|
+
_warned.shell = true;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!projectCSS && !_warned.projectCSS) {
|
|
147
|
+
console.warn(
|
|
148
|
+
"pageHTML(): no 'projectCSS' registered — page will not include project CSS.\n" +
|
|
149
|
+
"To register project CSS use: reg('projectCSS', file`layout/buldr.css`)"
|
|
150
|
+
);
|
|
151
|
+
_warned.projectCSS = true;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
let htmlWF = shellWF
|
|
155
|
+
? replace(shellWF, "<!--APP-->", appWF)
|
|
156
|
+
: appWF;
|
|
157
|
+
|
|
158
|
+
// Insert JS
|
|
159
|
+
if (js) {
|
|
160
|
+
const jsWF = resolveAsset(js, {
|
|
161
|
+
ext: ".js",
|
|
162
|
+
tag: "scriptlink",
|
|
163
|
+
wrap: scriptlink,
|
|
164
|
+
autoPrefix: "js"
|
|
165
|
+
});
|
|
166
|
+
htmlWF = replace(htmlWF, "<!--SCRIPTS-->", jsWF);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Insert CSS (project first)
|
|
170
|
+
const cssWFs = [];
|
|
171
|
+
|
|
172
|
+
if (projectCSS) {
|
|
173
|
+
cssWFs.push(resolveAsset(projectCSS, {
|
|
174
|
+
ext: ".css",
|
|
175
|
+
tag: "csslink",
|
|
176
|
+
wrap: csslink,
|
|
177
|
+
autoPrefix: "css"
|
|
178
|
+
}));
|
|
179
|
+
console.log(`${out}: has projectCSS`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (css) {
|
|
183
|
+
cssWFs.push(resolveAsset(css, {
|
|
184
|
+
ext: ".css",
|
|
185
|
+
tag: "csslink",
|
|
186
|
+
wrap: csslink,
|
|
187
|
+
autoPrefix: "css"
|
|
188
|
+
}));
|
|
189
|
+
console.log(`${out}: has pageCSS`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (cssWFs.length === 1) {
|
|
193
|
+
htmlWF = replace(htmlWF, "<!--CSS-->", cssWFs[0]);
|
|
194
|
+
} else if (cssWFs.length > 1) {
|
|
195
|
+
const cssListWF = append(...cssWFs);
|
|
196
|
+
htmlWF = replace(htmlWF, "<!--CSS-->", cssListWF);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return htmlWF.save(out);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// --------------------------------------------------
|
|
203
|
+
// resolveAsset()
|
|
204
|
+
// --------------------------------------------------
|
|
205
|
+
let autoCounter = 1;
|
|
206
|
+
|
|
207
|
+
function resolveAsset(input, {
|
|
208
|
+
ext,
|
|
209
|
+
tag,
|
|
210
|
+
wrap,
|
|
211
|
+
autoPrefix
|
|
212
|
+
}) {
|
|
213
|
+
if (input == null) return null;
|
|
214
|
+
|
|
215
|
+
if (typeof input === "string") {
|
|
216
|
+
if (!input.endsWith(ext)) {
|
|
217
|
+
throw new Error(`pageHTML(${ext}): expected a ${ext} filename, got '${input}'`);
|
|
218
|
+
}
|
|
219
|
+
return wrap(input);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (!(input instanceof WorkFile)) {
|
|
223
|
+
throw new Error(`pageHTML(${ext}): expected string or WorkFile, got ${typeof input}`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (input.tag === tag) {
|
|
227
|
+
return input;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (input.op === "literal") {
|
|
231
|
+
const value = input.config.value;
|
|
232
|
+
|
|
233
|
+
if (value.endsWith(ext)) {
|
|
234
|
+
return wrap(input);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
throw new Error(
|
|
238
|
+
`pageHTML(${ext}): literal WorkFile does not end in ${ext} → '${value}'`
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (input.op === "materialise") {
|
|
243
|
+
const target = input.config.target;
|
|
244
|
+
|
|
245
|
+
if (!target.endsWith(ext)) {
|
|
246
|
+
throw new Error(
|
|
247
|
+
`pageHTML(${ext}): materialise target '${target}' does not end in ${ext}`
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return wrap(input);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const autoName = `${autoPrefix}${autoCounter++}${ext}`;
|
|
255
|
+
|
|
256
|
+
console.warn(
|
|
257
|
+
`pageHTML(${ext}): received non-literal WorkFile; auto-materialising as ${autoName}`
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
const mat = materialise(input, autoName);
|
|
261
|
+
return wrap(mat);
|
|
262
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// buldng-lib.js — the main buldng build library
|
|
2
|
+
import esbuild from "esbuild";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
6
|
+
import { createRequire } from "node:module";
|
|
7
|
+
import { WorkFile } from "./workfile.js";
|
|
8
|
+
import {
|
|
9
|
+
getDest, setSource, setDest, assertDestWrite, assertSourceRead, getInputs, looksLikeFilename, looksLikeHTML,
|
|
10
|
+
looksLikeCSS, looksLikeJS, ensureLeadingSlash, ensureWorkFile
|
|
11
|
+
} from "./buldng-validate.js";
|
|
12
|
+
import { file, literal, wrap, append, replace, compile, materialise }
|
|
13
|
+
from "./buldng-ops.js";
|
|
14
|
+
export { file, literal, wrap, append, replace, compile, materialise }
|
|
15
|
+
from "./buldng-ops.js";
|
|
16
|
+
export {
|
|
17
|
+
assertSourceRead, assertDestWrite, setSource, setDest, looksLikeFilename,
|
|
18
|
+
looksLikeHTML, looksLikeCSS, looksLikeJS, ensureWorkFile, ensureLeadingSlash
|
|
19
|
+
} from "./buldng-validate.js";
|
|
20
|
+
import { task, parallel, finalise } from "./buldng-async.js";
|
|
21
|
+
import { typescriptCheck } from "./buldng-typescript.js";
|
|
22
|
+
import { mkdir, deepCopy } from "./buldng-files.js";
|
|
23
|
+
import {
|
|
24
|
+
reg,
|
|
25
|
+
csslink,
|
|
26
|
+
scriptlink,
|
|
27
|
+
pageHTML,
|
|
28
|
+
buldngHTMLWrapper
|
|
29
|
+
} from "./buldng-html.js";
|
|
30
|
+
|
|
31
|
+
export default {
|
|
32
|
+
setSource,
|
|
33
|
+
setDest,
|
|
34
|
+
mkdir,
|
|
35
|
+
deepCopy,
|
|
36
|
+
task,
|
|
37
|
+
parallel,
|
|
38
|
+
finalise,
|
|
39
|
+
file,
|
|
40
|
+
literal,
|
|
41
|
+
wrap,
|
|
42
|
+
append,
|
|
43
|
+
replace,
|
|
44
|
+
compile,
|
|
45
|
+
materialise,
|
|
46
|
+
task,
|
|
47
|
+
parallel,
|
|
48
|
+
finalise,
|
|
49
|
+
csslink,
|
|
50
|
+
scriptlink,
|
|
51
|
+
pageHTML,
|
|
52
|
+
buldngHTMLWrapper,
|
|
53
|
+
reg,
|
|
54
|
+
printHeader,
|
|
55
|
+
typescriptCheck
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
59
|
+
const pkg = JSON.parse(readFileSync(join(here, "../package.json"), "utf8"));
|
|
60
|
+
|
|
61
|
+
let headerPrinted = false;
|
|
62
|
+
export function printHeader(importMeta) {
|
|
63
|
+
if (headerPrinted) return;
|
|
64
|
+
headerPrinted = true;
|
|
65
|
+
|
|
66
|
+
const version = pkg.version;
|
|
67
|
+
const timestamp = new Date().toLocaleString("en-IE", { hour12: false });
|
|
68
|
+
|
|
69
|
+
const callerFile = fileURLToPath(importMeta.url);
|
|
70
|
+
const callerDir = dirname(callerFile);
|
|
71
|
+
|
|
72
|
+
console.log(`*local* buldng build system v${version} — ${timestamp}`);
|
|
73
|
+
console.log(`${callerDir}/${callerFile.split("/").pop()}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function header() {
|
|
77
|
+
printHeader(import.meta);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// --------------------------------------------------
|
|
81
|
+
// build-inputs.json manifest
|
|
82
|
+
// --------------------------------------------------
|
|
83
|
+
process.on("exit", () => {
|
|
84
|
+
if (process.env.BULDNG_HOT !== "1") return;
|
|
85
|
+
let DEST = getDest();
|
|
86
|
+
let INPUTS = getInputs();
|
|
87
|
+
const manifest = Array.from(INPUTS);
|
|
88
|
+
const out = path.join(DEST, "build-inputs.json");
|
|
89
|
+
fs.writeFileSync(out, JSON.stringify(manifest, null, 2));
|
|
90
|
+
});
|