tiendu 0.6.1 → 0.8.0
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/README.md +116 -41
- package/bin/tiendu.js +134 -57
- package/lib/api.mjs +18 -16
- package/lib/archive.mjs +2 -1
- package/lib/build.mjs +259 -135
- package/lib/config.mjs +68 -12
- package/lib/dev.mjs +30 -32
- package/lib/init.mjs +116 -106
- package/lib/preview.mjs +63 -42
- package/lib/publish.mjs +15 -17
- package/lib/pull.mjs +9 -6
- package/lib/push.mjs +19 -15
- package/lib/stores.mjs +91 -0
- package/lib/ui.mjs +138 -0
- package/lib/update-check.mjs +8 -4
- package/package.json +1 -1
package/lib/build.mjs
CHANGED
|
@@ -10,8 +10,7 @@ import {
|
|
|
10
10
|
} from "node:fs/promises";
|
|
11
11
|
import path from "node:path";
|
|
12
12
|
import * as esbuild from "esbuild";
|
|
13
|
-
import
|
|
14
|
-
import { readThemeConfig } from "./config.mjs";
|
|
13
|
+
import { getThemePipelineConfig, readThemeConfig } from "./config.mjs";
|
|
15
14
|
import {
|
|
16
15
|
flattenAssetLogicalPath,
|
|
17
16
|
getAssetImportFilter,
|
|
@@ -22,17 +21,37 @@ import {
|
|
|
22
21
|
} from "./assets.mjs";
|
|
23
22
|
import { listFilesRecursive } from "./fs-utils.mjs";
|
|
24
23
|
import { createCssPostCssPlugin } from "./postcss.mjs";
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
import * as ui from "./ui.mjs";
|
|
25
|
+
|
|
26
|
+
const THEME_SOURCE_OUTPUT_DIRS = [
|
|
27
|
+
"layout",
|
|
28
|
+
"templates",
|
|
29
|
+
"sections",
|
|
30
|
+
"blocks",
|
|
31
|
+
"snippets",
|
|
32
|
+
"config",
|
|
33
|
+
];
|
|
27
34
|
const LIQUID_LIKE_EXTENSIONS = new Set([".liquid", ".html", ".htm"]);
|
|
28
35
|
const ENTRY_SOURCE_EXTENSIONS = new Set([".js", ".ts", ".css"]);
|
|
29
|
-
const NESTED_ASSET_PATH_PATTERN =
|
|
36
|
+
const NESTED_ASSET_PATH_PATTERN =
|
|
37
|
+
/\/assets\/([A-Za-z0-9._-]+(?:\/[A-Za-z0-9._/-]+)+)([?#][A-Za-z0-9=&._-]+)?/g;
|
|
38
|
+
|
|
39
|
+
const INSTANCE_FILE_PATTERNS = [
|
|
40
|
+
/^templates\/[^/]+\.json$/,
|
|
41
|
+
/^sections\/[^/]+\.json$/,
|
|
42
|
+
/^config\/settings_data\.json$/,
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
export const isInstanceFile = (relativePath) => {
|
|
46
|
+
const normalized = relativePath.split(path.sep).join("/");
|
|
47
|
+
return INSTANCE_FILE_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
48
|
+
};
|
|
30
49
|
|
|
31
50
|
/**
|
|
32
|
-
* Discover JS/TS and CSS entry points from src/layout
|
|
51
|
+
* Discover optional JS/TS and CSS entry points from src/layout/templates or layout/templates.
|
|
33
52
|
* Returns separate maps for JS and CSS to avoid key collisions.
|
|
34
53
|
*/
|
|
35
|
-
const discoverEntryPoints = async (
|
|
54
|
+
const discoverEntryPoints = async (rootDir) => {
|
|
36
55
|
const jsEntries = {};
|
|
37
56
|
const cssEntries = {};
|
|
38
57
|
|
|
@@ -40,24 +59,29 @@ const discoverEntryPoints = async (srcDir) => {
|
|
|
40
59
|
["layout", "layout"],
|
|
41
60
|
["templates", "template"],
|
|
42
61
|
]) {
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
files
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
62
|
+
const sourceCandidates = [path.join(rootDir, "src", dir), path.join(rootDir, dir)];
|
|
63
|
+
|
|
64
|
+
for (const dirPath of sourceCandidates) {
|
|
65
|
+
let files;
|
|
66
|
+
try {
|
|
67
|
+
files = await readdir(dirPath);
|
|
68
|
+
} catch {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
for (const file of files) {
|
|
72
|
+
const ext = path.extname(file);
|
|
73
|
+
if (![".js", ".ts", ".css"].includes(ext)) continue;
|
|
74
|
+
const name = path.basename(file, ext);
|
|
75
|
+
const key = `${prefix}-${name}.bundle`;
|
|
76
|
+
const fullPath = path.join(dirPath, file);
|
|
77
|
+
if (ext === ".css") {
|
|
78
|
+
cssEntries[key] = fullPath;
|
|
79
|
+
} else {
|
|
80
|
+
jsEntries[key] = fullPath;
|
|
81
|
+
}
|
|
60
82
|
}
|
|
83
|
+
|
|
84
|
+
break;
|
|
61
85
|
}
|
|
62
86
|
}
|
|
63
87
|
|
|
@@ -105,9 +129,11 @@ const rewriteDirectAssetPaths = (source, knownAssetLogicalPaths) =>
|
|
|
105
129
|
return flattened ? `/assets/${flattened}${suffix}` : match;
|
|
106
130
|
});
|
|
107
131
|
|
|
108
|
-
const shouldCopyThemeSourceFile = (sourceRelativePath) => {
|
|
132
|
+
const shouldCopyThemeSourceFile = (sourceRelativePath, outputRelativePath, skipInstances = false) => {
|
|
109
133
|
const extension = path.extname(sourceRelativePath).toLowerCase();
|
|
110
|
-
|
|
134
|
+
if (ENTRY_SOURCE_EXTENSIONS.has(extension)) return false;
|
|
135
|
+
if (skipInstances && isInstanceFile(outputRelativePath)) return false;
|
|
136
|
+
return true;
|
|
111
137
|
};
|
|
112
138
|
|
|
113
139
|
const copyThemeSourceFile = async (
|
|
@@ -123,23 +149,39 @@ const copyThemeSourceFile = async (
|
|
|
123
149
|
|
|
124
150
|
if (LIQUID_LIKE_EXTENSIONS.has(path.extname(src).toLowerCase())) {
|
|
125
151
|
const source = await readFile(src, "utf-8");
|
|
126
|
-
await writeFile(
|
|
152
|
+
await writeFile(
|
|
153
|
+
dest,
|
|
154
|
+
rewriteDirectAssetPaths(source, knownAssetLogicalPaths),
|
|
155
|
+
"utf-8",
|
|
156
|
+
);
|
|
127
157
|
return;
|
|
128
158
|
}
|
|
129
159
|
|
|
130
160
|
await copyFile(src, dest);
|
|
131
161
|
};
|
|
132
162
|
|
|
133
|
-
const copyThemeFiles = async (
|
|
163
|
+
const copyThemeFiles = async (
|
|
164
|
+
rootDir,
|
|
165
|
+
distDir,
|
|
166
|
+
themeSourceDirs,
|
|
167
|
+
knownAssetLogicalPaths,
|
|
168
|
+
skipInstances = false,
|
|
169
|
+
) => {
|
|
134
170
|
for (const sourceDir of themeSourceDirs) {
|
|
135
171
|
const absoluteSourceDir = path.join(rootDir, sourceDir.sourceRelativeDir);
|
|
136
172
|
const absoluteFiles = await listFilesRecursive(absoluteSourceDir);
|
|
137
173
|
|
|
138
174
|
for (const absolutePath of absoluteFiles) {
|
|
139
175
|
const nestedRelativePath = path.relative(absoluteSourceDir, absolutePath);
|
|
140
|
-
const sourceRelativePath = path.join(
|
|
141
|
-
|
|
142
|
-
|
|
176
|
+
const sourceRelativePath = path.join(
|
|
177
|
+
sourceDir.sourceRelativeDir,
|
|
178
|
+
nestedRelativePath,
|
|
179
|
+
);
|
|
180
|
+
const outputRelativePath = path.join(
|
|
181
|
+
sourceDir.outputRelativeDir,
|
|
182
|
+
nestedRelativePath,
|
|
183
|
+
);
|
|
184
|
+
if (!shouldCopyThemeSourceFile(sourceRelativePath, outputRelativePath, skipInstances)) continue;
|
|
143
185
|
await copyThemeSourceFile(
|
|
144
186
|
rootDir,
|
|
145
187
|
distDir,
|
|
@@ -155,11 +197,17 @@ const shouldTriggerTailwindCssRebuild = (relativePath) => {
|
|
|
155
197
|
const normalizedPath = relativePath.split(path.sep).join("/");
|
|
156
198
|
const extension = path.extname(normalizedPath).toLowerCase();
|
|
157
199
|
|
|
158
|
-
if (
|
|
200
|
+
if (
|
|
201
|
+
!["layout/", "templates/", "sections/", "blocks/", "snippets/"].some(
|
|
202
|
+
(prefix) => normalizedPath.startsWith(prefix),
|
|
203
|
+
)
|
|
204
|
+
) {
|
|
159
205
|
return false;
|
|
160
206
|
}
|
|
161
207
|
|
|
162
|
-
return [".liquid", ".html", ".htm", ".js", ".ts", ".json", ".md"].includes(
|
|
208
|
+
return [".liquid", ".html", ".htm", ".js", ".ts", ".json", ".md"].includes(
|
|
209
|
+
extension,
|
|
210
|
+
);
|
|
163
211
|
};
|
|
164
212
|
|
|
165
213
|
const createSourceAssetImportPlugin = (rootDir, sourceDirs) => ({
|
|
@@ -173,7 +221,11 @@ const createSourceAssetImportPlugin = (rootDir, sourceDirs) => ({
|
|
|
173
221
|
const candidatePath = path.isAbsolute(args.path)
|
|
174
222
|
? args.path
|
|
175
223
|
: path.resolve(args.resolveDir, args.path);
|
|
176
|
-
const assetInfo = await getAssetSourceInfo(
|
|
224
|
+
const assetInfo = await getAssetSourceInfo(
|
|
225
|
+
rootDir,
|
|
226
|
+
candidatePath,
|
|
227
|
+
sourceDirs,
|
|
228
|
+
);
|
|
177
229
|
if (!assetInfo) return null;
|
|
178
230
|
|
|
179
231
|
return {
|
|
@@ -183,14 +235,17 @@ const createSourceAssetImportPlugin = (rootDir, sourceDirs) => ({
|
|
|
183
235
|
};
|
|
184
236
|
});
|
|
185
237
|
|
|
186
|
-
build.onLoad(
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
238
|
+
build.onLoad(
|
|
239
|
+
{ filter: /.*/, namespace: "tiendu-source-asset" },
|
|
240
|
+
async (args) => {
|
|
241
|
+
const assetInfo = args.pluginData;
|
|
242
|
+
return {
|
|
243
|
+
contents: `export default ${JSON.stringify(`/${assetInfo.outputRelativePath}`)};`,
|
|
244
|
+
loader: "js",
|
|
245
|
+
watchFiles: [assetInfo.absolutePath],
|
|
246
|
+
};
|
|
247
|
+
},
|
|
248
|
+
);
|
|
194
249
|
},
|
|
195
250
|
});
|
|
196
251
|
|
|
@@ -206,14 +261,17 @@ const runEntryBuilds = async (jsBuildOptions, cssBuildOptions) => {
|
|
|
206
261
|
* @param {{ watch?: boolean }} options
|
|
207
262
|
* @returns {Promise<{ ok: boolean, cleanup?: () => Promise<void> }>}
|
|
208
263
|
*/
|
|
209
|
-
export const build = async ({ watch: watchMode = false } = {}) => {
|
|
264
|
+
export const build = async ({ watch: watchMode = false, skipInstances = false } = {}) => {
|
|
210
265
|
const rootDir = process.cwd();
|
|
211
|
-
const srcDir = path.join(rootDir, "src");
|
|
212
266
|
const distDir = path.join(rootDir, "dist");
|
|
213
267
|
|
|
214
268
|
const themeConfig = await readThemeConfig();
|
|
215
|
-
|
|
216
|
-
|
|
269
|
+
const pipeline = getThemePipelineConfig(themeConfig);
|
|
270
|
+
|
|
271
|
+
if (pipeline.postcss && !pipeline.compileStyles) {
|
|
272
|
+
ui.log.error(
|
|
273
|
+
"Invalid tiendu.config.json: pipeline.postcss requires pipeline.compileStyles to be enabled.",
|
|
274
|
+
);
|
|
217
275
|
return { ok: false };
|
|
218
276
|
}
|
|
219
277
|
|
|
@@ -222,10 +280,14 @@ export const build = async ({ watch: watchMode = false } = {}) => {
|
|
|
222
280
|
await mkdir(distDir, { recursive: true });
|
|
223
281
|
|
|
224
282
|
const themeSourceDirs = await getThemeSourceDirs(rootDir);
|
|
225
|
-
const staticAssetSourceDirs = await getStaticAssetSourceDirs(rootDir, {
|
|
283
|
+
const staticAssetSourceDirs = await getStaticAssetSourceDirs(rootDir, {
|
|
284
|
+
refresh: true,
|
|
285
|
+
});
|
|
226
286
|
|
|
227
287
|
// Discover entry points (JS and CSS separately to avoid key collisions)
|
|
228
|
-
const
|
|
288
|
+
const discoveredEntries = await discoverEntryPoints(rootDir);
|
|
289
|
+
const jsEntries = pipeline.compileScripts ? discoveredEntries.jsEntries : {};
|
|
290
|
+
const cssEntries = pipeline.compileStyles ? discoveredEntries.cssEntries : {};
|
|
229
291
|
const jsCount = Object.keys(jsEntries).length;
|
|
230
292
|
const cssCount = Object.keys(cssEntries).length;
|
|
231
293
|
const entryCount = jsCount + cssCount;
|
|
@@ -236,7 +298,9 @@ export const build = async ({ watch: watchMode = false } = {}) => {
|
|
|
236
298
|
let staticAssetOwners = new Map();
|
|
237
299
|
const knownAssetLogicalPaths = new Set();
|
|
238
300
|
const cssPlugins = [];
|
|
239
|
-
const jsPlugins =
|
|
301
|
+
const jsPlugins = pipeline.compileScripts
|
|
302
|
+
? [createSourceAssetImportPlugin(rootDir, staticAssetSourceDirs)]
|
|
303
|
+
: [];
|
|
240
304
|
|
|
241
305
|
try {
|
|
242
306
|
staticAssetOwners = await syncStaticAssets(
|
|
@@ -246,7 +310,7 @@ export const build = async ({ watch: watchMode = false } = {}) => {
|
|
|
246
310
|
staticAssetSourceDirs,
|
|
247
311
|
);
|
|
248
312
|
} catch (error) {
|
|
249
|
-
|
|
313
|
+
ui.log.error(`Static asset build failed: ${error.message}`);
|
|
250
314
|
return { ok: false };
|
|
251
315
|
}
|
|
252
316
|
|
|
@@ -255,42 +319,83 @@ export const build = async ({ watch: watchMode = false } = {}) => {
|
|
|
255
319
|
}
|
|
256
320
|
|
|
257
321
|
// Copy theme files after asset paths are known
|
|
258
|
-
await copyThemeFiles(
|
|
322
|
+
await copyThemeFiles(
|
|
323
|
+
rootDir,
|
|
324
|
+
distDir,
|
|
325
|
+
themeSourceDirs,
|
|
326
|
+
knownAssetLogicalPaths,
|
|
327
|
+
skipInstances,
|
|
328
|
+
);
|
|
259
329
|
|
|
260
|
-
if (cssCount > 0) {
|
|
330
|
+
if (cssCount > 0 && pipeline.postcss) {
|
|
261
331
|
try {
|
|
262
332
|
cssPlugins.push(
|
|
263
|
-
await createCssPostCssPlugin(rootDir, {
|
|
333
|
+
await createCssPostCssPlugin(rootDir, {
|
|
334
|
+
sourceDirs: staticAssetSourceDirs,
|
|
335
|
+
}),
|
|
264
336
|
);
|
|
265
337
|
} catch (error) {
|
|
266
|
-
|
|
338
|
+
ui.log.error(`CSS pipeline failed to initialize: ${error.message}`);
|
|
267
339
|
return { ok: false };
|
|
268
340
|
}
|
|
269
341
|
}
|
|
270
342
|
|
|
271
343
|
if (entryCount === 0) {
|
|
272
|
-
|
|
273
|
-
|
|
344
|
+
const hasThemeFiles =
|
|
345
|
+
themeSourceDirs.length > 0 || staticAssetSourceDirs.length > 0;
|
|
346
|
+
|
|
347
|
+
if (!hasThemeFiles) {
|
|
348
|
+
ui.log.error("No theme source files or entry points found.");
|
|
349
|
+
return { ok: false };
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (!watchMode) {
|
|
353
|
+
ui.log.success("Prepared theme files to dist/.");
|
|
354
|
+
return { ok: true };
|
|
355
|
+
}
|
|
274
356
|
}
|
|
275
357
|
|
|
276
358
|
const outdir = path.join(distDir, "assets");
|
|
277
|
-
const jsBuildOptions =
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
359
|
+
const jsBuildOptions =
|
|
360
|
+
jsCount > 0
|
|
361
|
+
? {
|
|
362
|
+
entryPoints: jsEntries,
|
|
363
|
+
bundle: true,
|
|
364
|
+
format: "esm",
|
|
365
|
+
target: "es2020",
|
|
366
|
+
outdir,
|
|
367
|
+
logLevel: "warning",
|
|
368
|
+
write: true,
|
|
369
|
+
resolveExtensions: [".ts", ".tsx", ".js", ".jsx", ".json"],
|
|
370
|
+
plugins: jsPlugins,
|
|
371
|
+
}
|
|
372
|
+
: null;
|
|
373
|
+
const cssBuildOptions =
|
|
374
|
+
cssCount > 0
|
|
375
|
+
? {
|
|
376
|
+
entryPoints: cssEntries,
|
|
377
|
+
bundle: true,
|
|
378
|
+
outdir,
|
|
379
|
+
logLevel: "warning",
|
|
380
|
+
write: true,
|
|
381
|
+
plugins: cssPlugins,
|
|
382
|
+
}
|
|
383
|
+
: null;
|
|
283
384
|
|
|
284
385
|
if (!watchMode) {
|
|
285
386
|
// One-shot build
|
|
286
387
|
try {
|
|
287
388
|
await runEntryBuilds(jsBuildOptions, cssBuildOptions);
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
389
|
+
if (entryCount === 0) {
|
|
390
|
+
ui.log.success("Prepared theme files to dist/.");
|
|
391
|
+
} else {
|
|
392
|
+
ui.log.success(
|
|
393
|
+
`Built ${entryCount} entry point${entryCount === 1 ? "" : "s"} to dist/`,
|
|
394
|
+
);
|
|
395
|
+
}
|
|
291
396
|
return { ok: true };
|
|
292
397
|
} catch (error) {
|
|
293
|
-
|
|
398
|
+
ui.log.error(`Build failed: ${error.message}`);
|
|
294
399
|
return { ok: false };
|
|
295
400
|
}
|
|
296
401
|
}
|
|
@@ -312,14 +417,18 @@ export const build = async ({ watch: watchMode = false } = {}) => {
|
|
|
312
417
|
contexts.push(cssCtx);
|
|
313
418
|
}
|
|
314
419
|
} catch (error) {
|
|
315
|
-
|
|
420
|
+
ui.log.error(`Build failed: ${error.message}`);
|
|
316
421
|
for (const ctx of contexts) await ctx.dispose();
|
|
317
422
|
return { ok: false };
|
|
318
423
|
}
|
|
319
424
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
425
|
+
if (entryCount === 0) {
|
|
426
|
+
ui.log.success("Prepared theme files. Watching for changes...");
|
|
427
|
+
} else {
|
|
428
|
+
ui.log.success(
|
|
429
|
+
`Built ${entryCount} entry point${entryCount === 1 ? "" : "s"}. Watching for changes...`,
|
|
430
|
+
);
|
|
431
|
+
}
|
|
323
432
|
|
|
324
433
|
// Watch theme directories for Liquid/static asset changes and copy to dist
|
|
325
434
|
const themeWatchers = [];
|
|
@@ -340,9 +449,9 @@ export const build = async ({ watch: watchMode = false } = {}) => {
|
|
|
340
449
|
|
|
341
450
|
try {
|
|
342
451
|
await cssCtx.rebuild();
|
|
343
|
-
console.log("
|
|
452
|
+
console.log("CSS bundles updated");
|
|
344
453
|
} catch (error) {
|
|
345
|
-
|
|
454
|
+
ui.log.warn(`Error rebuilding CSS: ${error.message}`);
|
|
346
455
|
} finally {
|
|
347
456
|
cssRebuildInFlight = false;
|
|
348
457
|
if (cssRebuildQueued) {
|
|
@@ -380,83 +489,98 @@ export const build = async ({ watch: watchMode = false } = {}) => {
|
|
|
380
489
|
knownAssetLogicalPaths.delete(result.logicalPath);
|
|
381
490
|
}
|
|
382
491
|
|
|
383
|
-
console.log(
|
|
492
|
+
console.log(
|
|
493
|
+
`${result.type === "delete" ? "DELETE" : "UPDATE"} ${result.outputRelativePath}`,
|
|
494
|
+
);
|
|
384
495
|
};
|
|
385
496
|
|
|
386
497
|
for (const sourceDir of themeSourceDirs) {
|
|
387
498
|
const dirPath = path.join(rootDir, sourceDir.sourceRelativeDir);
|
|
388
499
|
|
|
389
|
-
const watcher = watch(
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
500
|
+
const watcher = watch(
|
|
501
|
+
dirPath,
|
|
502
|
+
{ recursive: true },
|
|
503
|
+
(eventType, filename) => {
|
|
504
|
+
if (!filename) return;
|
|
505
|
+
const normalizedFilename = filename.split(path.sep).join("/");
|
|
506
|
+
const sourceRelativePath = `${sourceDir.sourceRelativeDir}/${normalizedFilename}`;
|
|
507
|
+
const outputRelativePath = `${sourceDir.outputRelativeDir}/${normalizedFilename}`;
|
|
508
|
+
|
|
509
|
+
const existing = debounceMap.get(sourceRelativePath);
|
|
510
|
+
if (existing) clearTimeout(existing);
|
|
511
|
+
|
|
512
|
+
const timer = setTimeout(async () => {
|
|
513
|
+
debounceMap.delete(sourceRelativePath);
|
|
514
|
+
try {
|
|
515
|
+
if (!shouldCopyThemeSourceFile(sourceRelativePath, outputRelativePath, skipInstances)) {
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const fileStat = await stat(
|
|
520
|
+
path.join(rootDir, sourceRelativePath),
|
|
521
|
+
).catch(() => null);
|
|
522
|
+
if (fileStat && fileStat.isFile()) {
|
|
523
|
+
await copyThemeSourceFile(
|
|
524
|
+
rootDir,
|
|
525
|
+
distDir,
|
|
526
|
+
sourceRelativePath,
|
|
527
|
+
outputRelativePath,
|
|
528
|
+
knownAssetLogicalPaths,
|
|
529
|
+
);
|
|
530
|
+
console.log(`UPDATE ${outputRelativePath}`);
|
|
531
|
+
} else if (!fileStat) {
|
|
532
|
+
// File deleted — remove from dist
|
|
533
|
+
const dest = path.join(distDir, outputRelativePath);
|
|
534
|
+
await rm(dest, { force: true });
|
|
535
|
+
console.log(`DELETE ${outputRelativePath}`);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (
|
|
539
|
+
pipeline.compileStyles &&
|
|
540
|
+
shouldTriggerTailwindCssRebuild(outputRelativePath)
|
|
541
|
+
) {
|
|
542
|
+
queueCssRebuild();
|
|
543
|
+
}
|
|
544
|
+
} catch (error) {
|
|
545
|
+
ui.log.warn(
|
|
546
|
+
`Error copying ${sourceRelativePath}: ${error.message}`,
|
|
415
547
|
);
|
|
416
|
-
console.log(`⟳ ${outputRelativePath}`);
|
|
417
|
-
} else if (!fileStat) {
|
|
418
|
-
// File deleted — remove from dist
|
|
419
|
-
const dest = path.join(distDir, outputRelativePath);
|
|
420
|
-
await rm(dest, { force: true });
|
|
421
|
-
console.log(`✕ ${outputRelativePath}`);
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
if (shouldTriggerTailwindCssRebuild(outputRelativePath)) {
|
|
425
|
-
queueCssRebuild();
|
|
426
548
|
}
|
|
427
|
-
}
|
|
428
|
-
p.log.warn(`Error copying ${sourceRelativePath}: ${error.message}`);
|
|
429
|
-
}
|
|
430
|
-
}, DEBOUNCE_MS);
|
|
549
|
+
}, DEBOUNCE_MS);
|
|
431
550
|
|
|
432
|
-
|
|
433
|
-
|
|
551
|
+
debounceMap.set(sourceRelativePath, timer);
|
|
552
|
+
},
|
|
553
|
+
);
|
|
434
554
|
|
|
435
555
|
themeWatchers.push(watcher);
|
|
436
556
|
}
|
|
437
557
|
|
|
438
558
|
for (const assetDir of await getStaticAssetSourceDirs(rootDir)) {
|
|
439
|
-
const watcher = watch(
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
559
|
+
const watcher = watch(
|
|
560
|
+
assetDir.absoluteDir,
|
|
561
|
+
{ recursive: true },
|
|
562
|
+
(eventType, filename) => {
|
|
563
|
+
if (!filename) return;
|
|
564
|
+
const relativePath = `${assetDir.relativeDir}/${filename.split(path.sep).join("/")}`;
|
|
565
|
+
|
|
566
|
+
const existing = debounceMap.get(relativePath);
|
|
567
|
+
if (existing) clearTimeout(existing);
|
|
568
|
+
|
|
569
|
+
const timer = setTimeout(async () => {
|
|
570
|
+
debounceMap.delete(relativePath);
|
|
571
|
+
try {
|
|
572
|
+
await handleStaticAssetChange(relativePath);
|
|
573
|
+
} catch (error) {
|
|
574
|
+
const errorLabel = error.message.includes("Asset collision")
|
|
575
|
+
? "Asset collision"
|
|
576
|
+
: "Error compiling";
|
|
577
|
+
ui.log.warn(`${errorLabel} ${relativePath}: ${error.message}`);
|
|
578
|
+
}
|
|
579
|
+
}, DEBOUNCE_MS);
|
|
457
580
|
|
|
458
|
-
|
|
459
|
-
|
|
581
|
+
debounceMap.set(relativePath, timer);
|
|
582
|
+
},
|
|
583
|
+
);
|
|
460
584
|
|
|
461
585
|
themeWatchers.push(watcher);
|
|
462
586
|
}
|