@pajh/buldng 0.0.1

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.
Files changed (2) hide show
  1. package/dist/buldng-lib.js +261 -0
  2. package/package.json +10 -0
@@ -0,0 +1,261 @@
1
+ // tools/build-lib.js
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import esbuild from "esbuild";
6
+
7
+ // --------------------------------------------------
8
+ // GLOBAL SOURCE & DEST ROOTS
9
+ // --------------------------------------------------
10
+
11
+ let SRC = null;
12
+ let DEST = null;
13
+
14
+ // --------------------------------------------------
15
+ // SETTERS WITH SAFETY CHECKS
16
+ // --------------------------------------------------
17
+
18
+ export function setSource(dir) {
19
+ const abs = path.resolve(dir);
20
+ SRC = abs;
21
+ validateRoots();
22
+ }
23
+
24
+ export function setDest(dir) {
25
+ const abs = path.resolve(dir);
26
+ DEST = abs;
27
+ validateRoots();
28
+ }
29
+
30
+ const INPUTS = new Set();
31
+
32
+ function trackInput(absPath) {
33
+ if (!SRC || !DEST) return;
34
+ if (absPath.startsWith(SRC) && !absPath.startsWith(DEST)) {
35
+ INPUTS.add(absPath);
36
+ }
37
+ }
38
+
39
+ function validateRoots() {
40
+ if (!SRC || !DEST) return;
41
+
42
+ // src cannot be inside dest, dest cannot be inside src
43
+ if (SRC.startsWith(DEST) || DEST.startsWith(SRC)) {
44
+ throw new Error(`SECURITY: src and dest cannot be nested:
45
+ src: ${SRC}
46
+ dest: ${DEST}`);
47
+ }
48
+ }
49
+
50
+ // --------------------------------------------------
51
+ // PATH SAFETY HELPERS
52
+ // --------------------------------------------------
53
+
54
+ function assertSourceRead(relPath) {
55
+ if (!SRC) throw new Error("SECURITY: Source root not set");
56
+ if (relPath.includes("..")) throw new Error("SECURITY: Illegal path traversal in read()");
57
+ const abs = path.resolve(SRC, relPath);
58
+ if (!abs.startsWith(SRC)) throw new Error("SECURITY: Read outside src/");
59
+ trackInput(abs); // ← NEW
60
+ return abs;
61
+ }
62
+
63
+ function assertDestWrite(relPath) {
64
+ if (!DEST) throw new Error("SECURITY: Dest root not set");
65
+ if (relPath.includes("..")) throw new Error("SECURITY: Illegal path traversal in save()");
66
+ const abs = path.resolve(DEST, relPath);
67
+ if (!abs.startsWith(DEST)) throw new Error("SECURITY: Write outside dest/");
68
+ return abs;
69
+ }
70
+
71
+ // --------------------------------------------------
72
+ // File object — fluent API
73
+ // --------------------------------------------------
74
+
75
+ class File {
76
+ constructor(content) {
77
+ this.content = content;
78
+ }
79
+
80
+ // Fluent append: read another file from src and append its contents
81
+ append(relPath) {
82
+ const abs = assertSourceRead(relPath);
83
+ const extra = fs.readFileSync(abs, "utf8");
84
+ return new File(this.content + "\n" + extra);
85
+ }
86
+
87
+ // Save relative to dest
88
+ save(relTarget) {
89
+ const out = assertDestWrite(relTarget);
90
+ fs.mkdirSync(path.dirname(out), { recursive: true });
91
+ fs.writeFileSync(out, this.content, "utf8");
92
+ console.log(`Saved: ${out}`);
93
+ return out;
94
+ }
95
+ }
96
+
97
+ // --------------------------------------------------
98
+ // read("file") → File
99
+ // --------------------------------------------------
100
+
101
+ export function read(relPath) {
102
+ const abs = assertSourceRead(relPath);
103
+ const content = fs.readFileSync(abs, "utf8");
104
+ return new File(content);
105
+ }
106
+
107
+ // --------------------------------------------------
108
+ // append(["a.css","b.css"]) → File
109
+ // --------------------------------------------------
110
+
111
+ export function append(relPaths) {
112
+ const parts = relPaths.map(p => {
113
+ const abs = assertSourceRead(p);
114
+ return fs.readFileSync(abs, "utf8");
115
+ });
116
+ return new File(parts.join("\n"));
117
+ }
118
+
119
+ // --------------------------------------------------
120
+ // stitch({ shell, app, css, js }) → File
121
+ // --------------------------------------------------
122
+
123
+ export function stitch({ shell, app, css = [], js = [] }) {
124
+ const shellAbs = assertSourceRead(shell);
125
+ const appAbs = assertSourceRead(app);
126
+
127
+ let html = fs.readFileSync(shellAbs, "utf8");
128
+ const appHtml = fs.readFileSync(appAbs, "utf8");
129
+
130
+ html = html.replace("<!--APP-->", appHtml);
131
+
132
+ const cssLinks = css
133
+ .map(c => `<link rel="stylesheet" href="/${c}">`)
134
+ .join("\n");
135
+ html = html.replace("<!--CSS-->", cssLinks);
136
+
137
+ const jsScripts = js
138
+ .map(j => `<script type="module" src="/${j}"></script>`)
139
+ .join("\n");
140
+ html = html.replace("<!--SCRIPTS-->", jsScripts);
141
+
142
+ return new File(html);
143
+ }
144
+
145
+ // --------------------------------------------------
146
+ // compile("pages/puzzle.ts") → File
147
+ // --------------------------------------------------
148
+
149
+ export function compile(relTsPath) {
150
+ const abs = assertSourceRead(relTsPath);
151
+
152
+ const result = esbuild.buildSync({
153
+ entryPoints: [abs],
154
+ bundle: true,
155
+ minify: true,
156
+ sourcemap: true,
157
+ format: "esm",
158
+ target: "esnext",
159
+ write: false
160
+ });
161
+
162
+ if (result.errors.length > 0) {
163
+ throw new Error(
164
+ "Build failed:\n" +
165
+ result.errors.map(e => e.text).join("\n")
166
+ );
167
+ }
168
+
169
+ return new File(result.outputFiles[0].text);
170
+ }
171
+
172
+ // --------------------------------------------------
173
+ // mkdir("puzzle")
174
+ // --------------------------------------------------
175
+
176
+ export function mkdir(relDir) {
177
+ const abs = assertDestWrite(relDir);
178
+ fs.mkdirSync(abs, { recursive: true });
179
+ }
180
+
181
+ export function deepCopy(from, to) {
182
+ if (!DEST) throw new Error("SECURITY: Dest root not set");
183
+ console.log(`deepCopy: ${from} → ${to}`);
184
+ // from is raw filesystem path, resolve it normally
185
+ const absFrom = path.resolve(from);
186
+
187
+ // to is relative to DEST
188
+ if (to.includes("..")) {
189
+ throw new Error("SECURITY: Illegal path traversal in deepCopy()");
190
+ }
191
+
192
+ const absTo = to === "/" ? DEST : path.resolve(DEST, to);
193
+
194
+ // Ensure target is inside DEST
195
+ if (!absTo.startsWith(DEST)) {
196
+ throw new Error(`SECURITY: deepCopy target escapes dest:
197
+ dest: ${DEST}
198
+ target: ${absTo}`);
199
+ }
200
+
201
+ // Ensure source exists
202
+ if (!fs.existsSync(absFrom)) {
203
+ throw new Error(`deepCopy: source does not exist: ${absFrom}`);
204
+ }
205
+
206
+ // Recursively copy
207
+ function copyRecursive(src, dst) {
208
+ const stat = fs.statSync(src);
209
+
210
+ if (stat.isDirectory()) {
211
+ fs.mkdirSync(dst, { recursive: true });
212
+ const entries = fs.readdirSync(src);
213
+ for (const entry of entries) {
214
+ const srcEntry = path.join(src, entry);
215
+ const dstEntry = path.join(dst, entry);
216
+ copyRecursive(srcEntry, dstEntry);
217
+ }
218
+ } else {
219
+ // File
220
+ fs.mkdirSync(path.dirname(dst), { recursive: true });
221
+ fs.copyFileSync(src, dst);
222
+ }
223
+ }
224
+
225
+ copyRecursive(absFrom, absTo);
226
+ }
227
+
228
+ // --------------------------------------------------
229
+ // rm_dest("dir") — remove a file or directory inside DEST
230
+ // --------------------------------------------------
231
+
232
+ export function rmDest(relPath) {
233
+ if (!DEST) throw new Error("SECURITY: Dest root not set");
234
+ if (relPath.includes("..")) {
235
+ throw new Error("SECURITY: Illegal path traversal in rmDest()");
236
+ }
237
+
238
+ const abs = path.resolve(DEST, relPath);
239
+
240
+ if (!abs.startsWith(DEST)) {
241
+ throw new Error(`SECURITY: rmDest target escapes dest:
242
+ dest: ${DEST}
243
+ target: ${abs}`);
244
+ }
245
+
246
+ if (!fs.existsSync(abs)) {
247
+ console.log(`rmDest: nothing to remove: ${abs}`);
248
+ return;
249
+ }
250
+
251
+ fs.rmSync(abs, { recursive: true, force: true });
252
+ console.log(`rmDest: removed ${abs}`);
253
+ }
254
+
255
+
256
+ process.on("exit", () => {
257
+ if (!DEST) return;
258
+ const manifest = Array.from(INPUTS);
259
+ const out = path.join(DEST, "build-inputs.json");
260
+ fs.writeFileSync(out, JSON.stringify(manifest, null, 2));
261
+ });
package/package.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "@pajh/buldng",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "main": "dist/buldng-lib.js",
6
+ "files": ["dist"],
7
+ "scripts": {
8
+ "build": "cp src/buldng-lib.js dist/buldng-lib.js"
9
+ }
10
+ }