@fluid-app/fluid-cli-theme-dev 0.1.11 → 0.1.13
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/.turbo/turbo-build.log +5 -7
- package/dist/index.mjs +265 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -3
- package/src/commands/dev.ts +1 -1
- package/src/commands/push.ts +8 -1
- package/src/theme/dev-server/index.ts +22 -2
- package/src/theme/file.ts +28 -0
- package/src/theme/syncer.ts +20 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluid-app/fluid-cli-theme-dev",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"description": "Fluid CLI plugin for theme developer workflows — dev server, push, pull, init",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.mjs",
|
|
@@ -30,9 +30,10 @@
|
|
|
30
30
|
"@types/prompts": "^2.4.9",
|
|
31
31
|
"tsdown": "^0.21.0",
|
|
32
32
|
"typescript": "^5",
|
|
33
|
-
"@fluid-app/typescript-config": "0.0.0",
|
|
34
33
|
"@fluid-app/api-client-core": "0.1.0",
|
|
35
|
-
"@fluid-app/
|
|
34
|
+
"@fluid-app/theme-schema": "0.1.0",
|
|
35
|
+
"@fluid-app/themes-api-client": "0.1.0",
|
|
36
|
+
"@fluid-app/typescript-config": "0.0.0"
|
|
36
37
|
},
|
|
37
38
|
"engines": {
|
|
38
39
|
"node": ">=18.0.0"
|
package/src/commands/dev.ts
CHANGED
|
@@ -147,7 +147,7 @@ export function createDevCommand(): Command {
|
|
|
147
147
|
editorUrl,
|
|
148
148
|
},
|
|
149
149
|
themeRoot,
|
|
150
|
-
{ host: opts.host, port, reloadMode },
|
|
150
|
+
{ host: opts.host, port, reloadMode, validate: !opts.force },
|
|
151
151
|
(address) => {
|
|
152
152
|
console.log(`\n Dev server: ${address}`);
|
|
153
153
|
console.log(` Web editor: ${editorUrl}`);
|
package/src/commands/push.ts
CHANGED
|
@@ -177,12 +177,19 @@ export function createPushCommand(): Command {
|
|
|
177
177
|
|
|
178
178
|
const result = await syncer.uploadTheme({
|
|
179
179
|
delete: !opts.nodelete,
|
|
180
|
+
validate: !opts.force,
|
|
180
181
|
onProgress: (d, total) => {
|
|
181
182
|
spinner.text = `Pushing ${d}/${total} files…`;
|
|
182
183
|
},
|
|
183
184
|
});
|
|
184
185
|
|
|
185
|
-
if (result.
|
|
186
|
+
if (result.validationFailed) {
|
|
187
|
+
spinner.fail(
|
|
188
|
+
`Schema validation failed (${result.errors.length} error(s)). Use --force to skip.`,
|
|
189
|
+
);
|
|
190
|
+
for (const e of result.errors) console.error(` ${e}`);
|
|
191
|
+
process.exit(1);
|
|
192
|
+
} else if (result.errors.length) {
|
|
186
193
|
spinner.warn(`Pushed with ${result.errors.length} error(s).`);
|
|
187
194
|
for (const e of result.errors) console.error(` ${e}`);
|
|
188
195
|
} else {
|
|
@@ -23,7 +23,7 @@ export async function startDevServer(
|
|
|
23
23
|
api: ApiClient,
|
|
24
24
|
theme: DevServerTheme,
|
|
25
25
|
themeRoot: ThemeRoot,
|
|
26
|
-
opts: DevServerOptions,
|
|
26
|
+
opts: DevServerOptions & { validate?: boolean },
|
|
27
27
|
onReady?: (address: string) => void,
|
|
28
28
|
): Promise<() => void> {
|
|
29
29
|
const sse = new SSEStream();
|
|
@@ -33,13 +33,23 @@ export async function startDevServer(
|
|
|
33
33
|
|
|
34
34
|
// ── Initial sync ─────────────────────────────────────────────────────────
|
|
35
35
|
console.log(`\nSyncing theme ${theme.name} (#${theme.id})…`);
|
|
36
|
-
await syncer.uploadTheme({
|
|
36
|
+
const syncResult = await syncer.uploadTheme({
|
|
37
37
|
delete: true,
|
|
38
|
+
validate: opts.validate,
|
|
38
39
|
onProgress: (done, total) => {
|
|
39
40
|
process.stdout.write(`\r Uploading ${done}/${total} files…`);
|
|
40
41
|
},
|
|
41
42
|
});
|
|
42
43
|
process.stdout.write("\n");
|
|
44
|
+
if (syncResult.validationFailed) {
|
|
45
|
+
console.error(
|
|
46
|
+
`\nSchema validation failed (${syncResult.errors.length} error(s)). Use --force to skip.\n`,
|
|
47
|
+
);
|
|
48
|
+
for (const e of syncResult.errors) console.error(` ${e}`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
} else if (syncResult.errors.length > 0) {
|
|
51
|
+
for (const e of syncResult.errors) console.error(` ${e}`);
|
|
52
|
+
}
|
|
43
53
|
|
|
44
54
|
// ── File watcher ─────────────────────────────────────────────────────────
|
|
45
55
|
const stopWatcher = watchTheme(
|
|
@@ -48,6 +58,16 @@ export async function startDevServer(
|
|
|
48
58
|
const changed = [...modified, ...added];
|
|
49
59
|
|
|
50
60
|
for (const file of changed) {
|
|
61
|
+
// Validate schema on liquid files during dev (warn, don't block)
|
|
62
|
+
if (opts.validate && file.isLiquid) {
|
|
63
|
+
const diagnostics = file.validateSchema();
|
|
64
|
+
for (const d of diagnostics) {
|
|
65
|
+
const prefix =
|
|
66
|
+
d.severity === "error" ? "Schema error" : "Schema warning";
|
|
67
|
+
console.warn(`\n[${prefix}] ${file.relativePath}: ${d.message}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
51
71
|
pendingUpdates.add(file.relativePath);
|
|
52
72
|
try {
|
|
53
73
|
await syncer.uploadFile(file);
|
package/src/theme/file.ts
CHANGED
|
@@ -8,6 +8,11 @@ import {
|
|
|
8
8
|
import { extname, basename, relative, dirname } from "node:path";
|
|
9
9
|
import { createHash } from "node:crypto";
|
|
10
10
|
import { mimeTypeFor, type MimeType } from "./mime-type.js";
|
|
11
|
+
import {
|
|
12
|
+
validateSchemaText,
|
|
13
|
+
type Diagnostic,
|
|
14
|
+
type BlocksSchemaType,
|
|
15
|
+
} from "@fluid-app/theme-schema";
|
|
11
16
|
|
|
12
17
|
export class ThemeFile {
|
|
13
18
|
readonly absolutePath: string;
|
|
@@ -65,4 +70,27 @@ export class ThemeFile {
|
|
|
65
70
|
size(): number {
|
|
66
71
|
return statSync(this.absolutePath).size;
|
|
67
72
|
}
|
|
73
|
+
|
|
74
|
+
get isTemplate(): boolean {
|
|
75
|
+
// Template files (home_page, product, etc.) expect blocks as objects.
|
|
76
|
+
// Section files expect blocks as arrays.
|
|
77
|
+
const parts = this.relativePath.split(/[/\\]/);
|
|
78
|
+
return (
|
|
79
|
+
parts[0] === "templates" &&
|
|
80
|
+
parts.length >= 3 &&
|
|
81
|
+
parts[1] !== "sections" &&
|
|
82
|
+
parts[1] !== "blocks" &&
|
|
83
|
+
parts[1] !== "components"
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
validateSchema(): Diagnostic[] {
|
|
88
|
+
if (!this.isLiquid) return [];
|
|
89
|
+
|
|
90
|
+
const blocksSchemaType: BlocksSchemaType = this.isTemplate
|
|
91
|
+
? "object"
|
|
92
|
+
: "array";
|
|
93
|
+
|
|
94
|
+
return validateSchemaText(this.read(), { blocksSchemaType });
|
|
95
|
+
}
|
|
68
96
|
}
|
package/src/theme/syncer.ts
CHANGED
|
@@ -11,6 +11,7 @@ export interface SyncResult {
|
|
|
11
11
|
downloaded: number;
|
|
12
12
|
deleted: number;
|
|
13
13
|
errors: string[];
|
|
14
|
+
validationFailed: boolean;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export class Syncer {
|
|
@@ -201,6 +202,7 @@ export class Syncer {
|
|
|
201
202
|
async uploadTheme(
|
|
202
203
|
opts: {
|
|
203
204
|
delete?: boolean;
|
|
205
|
+
validate?: boolean;
|
|
204
206
|
onProgress?: (done: number, total: number) => void;
|
|
205
207
|
} = {},
|
|
206
208
|
): Promise<SyncResult> {
|
|
@@ -212,8 +214,25 @@ export class Syncer {
|
|
|
212
214
|
deleted: 0,
|
|
213
215
|
downloaded: 0,
|
|
214
216
|
errors: [],
|
|
217
|
+
validationFailed: false,
|
|
215
218
|
};
|
|
216
219
|
|
|
220
|
+
// Schema validation pass
|
|
221
|
+
if (opts.validate) {
|
|
222
|
+
for (const file of localFiles) {
|
|
223
|
+
if (!file.isLiquid) continue;
|
|
224
|
+
const diagnostics = file.validateSchema();
|
|
225
|
+
const errors = diagnostics.filter((d) => d.severity === "error");
|
|
226
|
+
for (const d of errors) {
|
|
227
|
+
result.errors.push(`${file.relativePath}: ${d.message}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (result.errors.length > 0) {
|
|
231
|
+
result.validationFailed = true;
|
|
232
|
+
return result;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
217
236
|
const toUpload = localFiles.filter((f) => f.exists && this.hasChanged(f));
|
|
218
237
|
let done = 0;
|
|
219
238
|
for (const file of toUpload) {
|
|
@@ -258,6 +277,7 @@ export class Syncer {
|
|
|
258
277
|
downloaded: 0,
|
|
259
278
|
skipped: 0,
|
|
260
279
|
errors: [],
|
|
280
|
+
validationFailed: false,
|
|
261
281
|
};
|
|
262
282
|
|
|
263
283
|
let done = 0;
|