@pajh/buldng 0.0.4 → 0.0.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/package.json +7 -2
- package/src/MIME.json +11 -0
- package/src/buldng-async.js +20 -0
- package/src/buldng-files.js +59 -0
- package/src/buldng-html.js +262 -0
- package/src/buldng-lib.js +67 -411
- package/src/buldng-modal.ts +348 -0
- package/src/buldng-ops.js +21 -6
- package/src/buldng-typescript.js +77 -0
- package/src/buldng-validate.js +11 -4
- package/src/buldng-web.ts +55 -180
- package/src/buldng.css +130 -0
- package/src/dev-errors.js +16 -0
- package/src/layout-err.ts +171 -0
- package/src/layout.ts +4 -0
- package/src/serve-lib.js +574 -0
- package/src/serve.js +194 -0
- package/src/types/global.d.ts +9 -0
- package/src/wrapper.html +2 -0
- package/src/buldng-dev-errors.js +0 -84
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
export type ModalType =
|
|
2
|
+
| "info"
|
|
3
|
+
| "confirmation"
|
|
4
|
+
| "destructive"
|
|
5
|
+
| "error";
|
|
6
|
+
|
|
7
|
+
export type ModalIcon = "info" | "warning" | "error" | null;
|
|
8
|
+
|
|
9
|
+
export type ModalResult = "ok" | "cancel" | "dismiss";
|
|
10
|
+
|
|
11
|
+
export interface ModalSettings {
|
|
12
|
+
title?: string;
|
|
13
|
+
icon?: ModalIcon;
|
|
14
|
+
autoDismiss?: number;
|
|
15
|
+
dismissOnEsc?: boolean;
|
|
16
|
+
dismissOnBackdrop?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const MODAL_ROOT_ID = "b-modal-root";
|
|
20
|
+
let modalId = 0;
|
|
21
|
+
|
|
22
|
+
interface ModalRoot {
|
|
23
|
+
element: HTMLElement;
|
|
24
|
+
created: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getModalRoot(): ModalRoot {
|
|
28
|
+
const existingRoot = document.getElementById(MODAL_ROOT_ID);
|
|
29
|
+
if (existingRoot instanceof HTMLElement) {
|
|
30
|
+
return { element: existingRoot, created: false };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const createdRoot = document.createElement("div");
|
|
34
|
+
createdRoot.id = MODAL_ROOT_ID;
|
|
35
|
+
document.body.appendChild(createdRoot);
|
|
36
|
+
return { element: createdRoot, created: true };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function getIconText(icon: ModalIcon): string {
|
|
40
|
+
if (icon === "info") {
|
|
41
|
+
return "i";
|
|
42
|
+
}
|
|
43
|
+
if (icon === "warning") {
|
|
44
|
+
return "!";
|
|
45
|
+
}
|
|
46
|
+
if (icon === "error") {
|
|
47
|
+
return "!";
|
|
48
|
+
}
|
|
49
|
+
return "";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function getDefaultIcon(type: ModalType): ModalIcon {
|
|
53
|
+
if (type === "destructive") {
|
|
54
|
+
return "warning";
|
|
55
|
+
}
|
|
56
|
+
if (type === "error") {
|
|
57
|
+
return "error";
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function getButtonLabels(type: ModalType): { primary: string; secondary: string | null } {
|
|
63
|
+
if (type === "confirmation") {
|
|
64
|
+
return { primary: "OK", secondary: "Cancel" };
|
|
65
|
+
}
|
|
66
|
+
if (type === "destructive") {
|
|
67
|
+
return { primary: "Delete", secondary: "Cancel" };
|
|
68
|
+
}
|
|
69
|
+
return { primary: "OK", secondary: null };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getDefaultDismissal(type: ModalType): boolean {
|
|
73
|
+
return type !== "destructive";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function getInitialFocus(dialog: HTMLElement, primaryButton: HTMLButtonElement): HTMLElement {
|
|
77
|
+
const focusable = dialog.querySelector<HTMLElement>(
|
|
78
|
+
"button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])"
|
|
79
|
+
);
|
|
80
|
+
if (focusable !== null) {
|
|
81
|
+
return focusable;
|
|
82
|
+
}
|
|
83
|
+
return primaryButton;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function isInsideDialog(target: EventTarget | null, dialog: HTMLElement): boolean {
|
|
87
|
+
return target instanceof Node && dialog.contains(target);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
class ModalController {
|
|
91
|
+
private readonly type: ModalType;
|
|
92
|
+
private readonly content: string;
|
|
93
|
+
private readonly settings: ModalSettings;
|
|
94
|
+
private readonly root: HTMLElement;
|
|
95
|
+
private readonly ownsRoot: boolean;
|
|
96
|
+
private readonly previousFocus: HTMLElement | null;
|
|
97
|
+
private readonly dialog: HTMLElement;
|
|
98
|
+
private readonly backdrop: HTMLElement;
|
|
99
|
+
private readonly primaryButton: HTMLButtonElement;
|
|
100
|
+
private readonly secondaryButton: HTMLButtonElement | null;
|
|
101
|
+
private readonly keydownHandler: (event: KeyboardEvent) => void;
|
|
102
|
+
private timeoutId: number | null;
|
|
103
|
+
private settled: boolean;
|
|
104
|
+
private resolveResult: ((result: ModalResult) => void) | null;
|
|
105
|
+
|
|
106
|
+
public constructor(
|
|
107
|
+
type: ModalType,
|
|
108
|
+
content: string,
|
|
109
|
+
settings: ModalSettings
|
|
110
|
+
) {
|
|
111
|
+
this.type = type;
|
|
112
|
+
this.content = content;
|
|
113
|
+
this.settings = settings;
|
|
114
|
+
const modalRoot = getModalRoot();
|
|
115
|
+
this.root = modalRoot.element;
|
|
116
|
+
this.ownsRoot = modalRoot.created;
|
|
117
|
+
this.previousFocus = document.activeElement instanceof HTMLElement
|
|
118
|
+
? document.activeElement
|
|
119
|
+
: null;
|
|
120
|
+
this.dialog = this.createDialog();
|
|
121
|
+
this.backdrop = this.createBackdrop();
|
|
122
|
+
this.primaryButton = this.createPrimaryButton();
|
|
123
|
+
this.secondaryButton = this.createSecondaryButton();
|
|
124
|
+
this.addHeader();
|
|
125
|
+
this.addContent();
|
|
126
|
+
this.addFooter();
|
|
127
|
+
this.timeoutId = null;
|
|
128
|
+
this.settled = false;
|
|
129
|
+
this.resolveResult = null;
|
|
130
|
+
this.keydownHandler = (event: KeyboardEvent) => {
|
|
131
|
+
this.handleKeydown(event);
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
public open(): Promise<ModalResult> {
|
|
136
|
+
this.root.appendChild(this.backdrop);
|
|
137
|
+
this.backdrop.appendChild(this.dialog);
|
|
138
|
+
this.registerListeners();
|
|
139
|
+
|
|
140
|
+
const initialFocus = getInitialFocus(this.dialog, this.primaryButton);
|
|
141
|
+
initialFocus.focus();
|
|
142
|
+
|
|
143
|
+
if (this.settings.autoDismiss !== undefined && this.settings.autoDismiss > 0) {
|
|
144
|
+
this.timeoutId = window.setTimeout(() => {
|
|
145
|
+
this.finish("dismiss");
|
|
146
|
+
}, this.settings.autoDismiss);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return new Promise<ModalResult>((resolve) => {
|
|
150
|
+
this.resolveResult = resolve;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private createDialog(): HTMLElement {
|
|
155
|
+
const dialog = document.createElement("div");
|
|
156
|
+
dialog.className = `buldng-modal ${this.type}`;
|
|
157
|
+
dialog.setAttribute("role", "dialog");
|
|
158
|
+
dialog.setAttribute("aria-modal", "true");
|
|
159
|
+
dialog.tabIndex = -1;
|
|
160
|
+
return dialog;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private createBackdrop(): HTMLElement {
|
|
164
|
+
const backdrop = document.createElement("div");
|
|
165
|
+
backdrop.className = "buldng-modal-backdrop";
|
|
166
|
+
backdrop.setAttribute("aria-hidden", "true");
|
|
167
|
+
return backdrop;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private createPrimaryButton(): HTMLButtonElement {
|
|
171
|
+
const button = document.createElement("button");
|
|
172
|
+
button.type = "button";
|
|
173
|
+
button.className = "buldng-modal-button buldng-modal-button-primary";
|
|
174
|
+
button.textContent = getButtonLabels(this.type).primary;
|
|
175
|
+
return button;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private createSecondaryButton(): HTMLButtonElement | null {
|
|
179
|
+
const labels = getButtonLabels(this.type);
|
|
180
|
+
if (labels.secondary === null) {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const button = document.createElement("button");
|
|
185
|
+
button.type = "button";
|
|
186
|
+
button.className = "buldng-modal-button buldng-modal-button-secondary";
|
|
187
|
+
button.textContent = labels.secondary;
|
|
188
|
+
return button;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private addHeader(): void {
|
|
192
|
+
const hasTitle = this.settings.title !== undefined && this.settings.title.length > 0;
|
|
193
|
+
const icon = this.settings.icon === undefined
|
|
194
|
+
? getDefaultIcon(this.type)
|
|
195
|
+
: this.settings.icon;
|
|
196
|
+
const hasIcon = icon !== null;
|
|
197
|
+
|
|
198
|
+
if (!hasTitle && !hasIcon) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const header = document.createElement("div");
|
|
203
|
+
header.className = "buldng-modal-header";
|
|
204
|
+
|
|
205
|
+
if (hasIcon) {
|
|
206
|
+
const iconElement = document.createElement("span");
|
|
207
|
+
iconElement.className = "buldng-modal-icon";
|
|
208
|
+
iconElement.setAttribute("aria-hidden", "true");
|
|
209
|
+
iconElement.textContent = getIconText(icon);
|
|
210
|
+
header.appendChild(iconElement);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (hasTitle) {
|
|
214
|
+
const title = document.createElement("h2");
|
|
215
|
+
title.className = "buldng-modal-title";
|
|
216
|
+
title.textContent = this.settings.title as string;
|
|
217
|
+
modalId += 1;
|
|
218
|
+
const titleId = `buldng-modal-title-${modalId}`;
|
|
219
|
+
title.id = titleId;
|
|
220
|
+
this.dialog.setAttribute("aria-labelledby", titleId);
|
|
221
|
+
header.appendChild(title);
|
|
222
|
+
this.dialog.appendChild(header);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
this.dialog.appendChild(header);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private addContent(): void {
|
|
230
|
+
const content = document.createElement("div");
|
|
231
|
+
content.className = "buldng-modal-content";
|
|
232
|
+
content.innerHTML = this.content;
|
|
233
|
+
this.dialog.appendChild(content);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private addFooter(): void {
|
|
237
|
+
const footer = document.createElement("div");
|
|
238
|
+
footer.className = "buldng-modal-footer";
|
|
239
|
+
|
|
240
|
+
if (this.secondaryButton !== null) {
|
|
241
|
+
footer.appendChild(this.secondaryButton);
|
|
242
|
+
}
|
|
243
|
+
footer.appendChild(this.primaryButton);
|
|
244
|
+
this.dialog.appendChild(footer);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private registerListeners(): void {
|
|
248
|
+
this.primaryButton.addEventListener("click", () => {
|
|
249
|
+
this.finish("ok");
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
if (this.secondaryButton !== null) {
|
|
253
|
+
this.secondaryButton.addEventListener("click", () => {
|
|
254
|
+
this.finish("cancel");
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
this.backdrop.addEventListener("click", (event: MouseEvent) => {
|
|
259
|
+
if (isInsideDialog(event.target, this.dialog)) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const dismissOnBackdrop = this.settings.dismissOnBackdrop === undefined
|
|
263
|
+
? getDefaultDismissal(this.type)
|
|
264
|
+
: this.settings.dismissOnBackdrop;
|
|
265
|
+
if (dismissOnBackdrop === true) {
|
|
266
|
+
this.finish("dismiss");
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
document.addEventListener("keydown", this.keydownHandler);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private handleKeydown(event: KeyboardEvent): void {
|
|
274
|
+
if (event.key === "Escape") {
|
|
275
|
+
const dismissOnEsc = this.settings.dismissOnEsc === undefined
|
|
276
|
+
? getDefaultDismissal(this.type)
|
|
277
|
+
: this.settings.dismissOnEsc;
|
|
278
|
+
if (dismissOnEsc === true) {
|
|
279
|
+
event.preventDefault();
|
|
280
|
+
this.finish("dismiss");
|
|
281
|
+
}
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (event.key === "Tab") {
|
|
286
|
+
this.keepFocusInside(event);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private keepFocusInside(event: KeyboardEvent): void {
|
|
291
|
+
const focusable = Array.from(this.dialog.querySelectorAll<HTMLElement>(
|
|
292
|
+
"button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])"
|
|
293
|
+
));
|
|
294
|
+
if (focusable.length === 0) {
|
|
295
|
+
event.preventDefault();
|
|
296
|
+
this.dialog.focus();
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const first = focusable[0];
|
|
301
|
+
const last = focusable[focusable.length - 1];
|
|
302
|
+
const active = document.activeElement;
|
|
303
|
+
|
|
304
|
+
if (event.shiftKey === true && active === first) {
|
|
305
|
+
event.preventDefault();
|
|
306
|
+
last.focus();
|
|
307
|
+
} else if (event.shiftKey === false && active === last) {
|
|
308
|
+
event.preventDefault();
|
|
309
|
+
first.focus();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
private finish(result: ModalResult): void {
|
|
314
|
+
if (this.settled === true) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
this.settled = true;
|
|
318
|
+
|
|
319
|
+
if (this.timeoutId !== null) {
|
|
320
|
+
window.clearTimeout(this.timeoutId);
|
|
321
|
+
this.timeoutId = null;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
document.removeEventListener("keydown", this.keydownHandler);
|
|
325
|
+
this.backdrop.remove();
|
|
326
|
+
if (this.ownsRoot === true && this.root.childElementCount === 0) {
|
|
327
|
+
this.root.remove();
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (this.previousFocus !== null && document.contains(this.previousFocus)) {
|
|
331
|
+
this.previousFocus.focus();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (this.resolveResult !== null) {
|
|
335
|
+
this.resolveResult(result);
|
|
336
|
+
this.resolveResult = null;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export function showModal(
|
|
342
|
+
type: ModalType,
|
|
343
|
+
content: string,
|
|
344
|
+
settings: ModalSettings = {}
|
|
345
|
+
): Promise<ModalResult> {
|
|
346
|
+
const controller = new ModalController(type, content, settings);
|
|
347
|
+
return controller.open();
|
|
348
|
+
}
|
package/src/buldng-ops.js
CHANGED
|
@@ -5,7 +5,7 @@ import fs from "node:fs";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import esbuild from "esbuild";
|
|
7
7
|
|
|
8
|
-
import { assertSourceRead, assertDestWrite, looksLikeFilename, ensureWorkFile } from "./buldng-validate.js";
|
|
8
|
+
import { assertSourceRead, assertDestWrite, trackInput, looksLikeFilename, ensureWorkFile } from "./buldng-validate.js";
|
|
9
9
|
export const OPS = {
|
|
10
10
|
file: op_file,
|
|
11
11
|
literal: op_literal,
|
|
@@ -107,10 +107,13 @@ export function op_compile(config, children) {
|
|
|
107
107
|
const result = esbuild.buildSync({
|
|
108
108
|
entryPoints: [entry],
|
|
109
109
|
bundle: true,
|
|
110
|
+
metafile: true,
|
|
110
111
|
...compileOptions,
|
|
111
112
|
write: false
|
|
112
113
|
});
|
|
113
114
|
|
|
115
|
+
trackEsbuildInputs(result);
|
|
116
|
+
|
|
114
117
|
if (result.errors?.length) {
|
|
115
118
|
throw new Error("Build failed:\n" + result.errors.map(e => e.text).join("\n"));
|
|
116
119
|
}
|
|
@@ -121,16 +124,19 @@ export function op_compile(config, children) {
|
|
|
121
124
|
// Otherwise compile from stdin
|
|
122
125
|
const srcText = child.execute();
|
|
123
126
|
|
|
124
|
-
|
|
127
|
+
const result = esbuild.buildSync({
|
|
125
128
|
stdin: {
|
|
126
129
|
contents: srcText,
|
|
127
130
|
resolveDir: process.cwd(),
|
|
128
131
|
sourcefile: "input.ts"
|
|
129
132
|
},
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
133
|
+
bundle: true,
|
|
134
|
+
metafile: true,
|
|
135
|
+
...compileOptions,
|
|
136
|
+
write: false
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
trackEsbuildInputs(result);
|
|
134
140
|
|
|
135
141
|
if (result.errors?.length) {
|
|
136
142
|
throw new Error("Build failed:\n" + result.errors.map(e => e.text).join("\n"));
|
|
@@ -139,6 +145,15 @@ export function op_compile(config, children) {
|
|
|
139
145
|
return result.outputFiles[0].text;
|
|
140
146
|
}
|
|
141
147
|
|
|
148
|
+
function trackEsbuildInputs(result) {
|
|
149
|
+
for (const input of Object.keys(result.metafile?.inputs ?? {})) {
|
|
150
|
+
const abs = path.resolve(input);
|
|
151
|
+
if (fs.existsSync(abs) && fs.statSync(abs).isFile()) {
|
|
152
|
+
trackInput(abs);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
142
157
|
// --------------------------------------------------
|
|
143
158
|
// MATERIALISE — write file if identical or new
|
|
144
159
|
// --------------------------------------------------
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// buldng-typescript.js
|
|
2
|
+
// typescript build support for buldng
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import { createRequire } from "module";
|
|
8
|
+
import { execSync } from "child_process";
|
|
9
|
+
|
|
10
|
+
function findTSCFromResolved(tsPath) {
|
|
11
|
+
// tsPath → /.../typescript/lib/typescript.js
|
|
12
|
+
let dir = path.dirname(tsPath); // /.../typescript/lib
|
|
13
|
+
dir = path.dirname(dir); // /.../typescript
|
|
14
|
+
|
|
15
|
+
while (true) {
|
|
16
|
+
const candidate = path.join(dir, ".bin", "tsc");
|
|
17
|
+
if (fs.existsSync(candidate)) {
|
|
18
|
+
return candidate;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const parent = path.dirname(dir);
|
|
22
|
+
if (parent === dir) break; // reached filesystem root
|
|
23
|
+
dir = parent;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function findGlobalTSC() {
|
|
30
|
+
try {
|
|
31
|
+
const globalTSC = execSync("which tsc").toString().trim();
|
|
32
|
+
return globalTSC || null;
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function typescriptCheck() {
|
|
39
|
+
const require = createRequire(import.meta.url);
|
|
40
|
+
|
|
41
|
+
let tsPath = null;
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
tsPath = require.resolve("typescript");
|
|
45
|
+
} catch {
|
|
46
|
+
tsPath = null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let tscPath = null;
|
|
50
|
+
|
|
51
|
+
if (tsPath) {
|
|
52
|
+
// Local / nested / linked TypeScript
|
|
53
|
+
tscPath = findTSCFromResolved(tsPath);
|
|
54
|
+
} else {
|
|
55
|
+
// Try global TypeScript
|
|
56
|
+
tscPath = findGlobalTSC();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!tscPath) {
|
|
60
|
+
console.log("TypeScript not found — skipping TS checks");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log("Using TypeScript compiler:", tscPath);
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
execSync(`${tscPath} --noEmit`, { stdio: "inherit" });
|
|
68
|
+
} catch (err) {
|
|
69
|
+
// If tsc exists but cannot run → internal error
|
|
70
|
+
if (err.code === "ENOENT") {
|
|
71
|
+
throw new Error("Internal TypeScript error — 'tsc' binary exists but cannot be executed");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// If tsc runs but reports TS errors → build failure
|
|
75
|
+
throw new Error("TypeScript check failed");
|
|
76
|
+
}
|
|
77
|
+
}
|
package/src/buldng-validate.js
CHANGED
|
@@ -158,10 +158,17 @@ export function getInputs() {
|
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
export function trackInput(absPath) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
161
|
+
const abs = path.resolve(absPath);
|
|
162
|
+
|
|
163
|
+
if (DEST) {
|
|
164
|
+
const relativeToDest = path.relative(DEST, abs);
|
|
165
|
+
const isInsideDest = relativeToDest === "" ||
|
|
166
|
+
(!relativeToDest.startsWith("..") && !path.isAbsolute(relativeToDest));
|
|
167
|
+
|
|
168
|
+
if (isInsideDest) return;
|
|
164
169
|
}
|
|
170
|
+
|
|
171
|
+
INPUTS.add(abs);
|
|
165
172
|
}
|
|
166
173
|
|
|
167
174
|
export function looksLikeFilename(s) {
|
|
@@ -204,4 +211,4 @@ export function ensureWorkFile(x, fieldName = "value") {
|
|
|
204
211
|
}
|
|
205
212
|
|
|
206
213
|
throw new Error(`${fieldName} must be WorkFile or string`);
|
|
207
|
-
}
|
|
214
|
+
}
|