@fluid-app/fluid-cli-theme-dev 0.1.21 → 0.1.23
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 +14 -0
- package/dist/index.mjs +155 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -4
- package/.turbo/turbo-build.log +0 -16
- package/.turbo/turbo-typecheck.log +0 -4
- package/jest.config.cjs +0 -21
- package/jest.mocks/fluid-cli.ts +0 -33
- package/src/__tests__/plugin-state.test.ts +0 -186
- package/src/api.ts +0 -28
- package/src/commands/dev.ts +0 -186
- package/src/commands/init.ts +0 -51
- package/src/commands/lint.ts +0 -186
- package/src/commands/navigate.ts +0 -259
- package/src/commands/pull.ts +0 -242
- package/src/commands/push.ts +0 -220
- package/src/commands/theme.ts +0 -23
- package/src/index.ts +0 -12
- package/src/plugin-state.ts +0 -171
- package/src/theme/dev-server/hot-reload.ts +0 -65
- package/src/theme/dev-server/index.ts +0 -145
- package/src/theme/dev-server/proxy.ts +0 -125
- package/src/theme/dev-server/sse.ts +0 -43
- package/src/theme/dev-server/watcher.ts +0 -54
- package/src/theme/file.ts +0 -104
- package/src/theme/fluid-ignore.ts +0 -64
- package/src/theme/mime-type.ts +0 -45
- package/src/theme/root.ts +0 -54
- package/src/theme/syncer.ts +0 -338
- package/src/theme-config.ts +0 -34
- package/src/theme-picker.ts +0 -164
- package/src/workspace.ts +0 -71
- package/tsconfig.json +0 -10
- package/tsdown.config.ts +0 -19
- /package/{skills → dist/skills}/themes-review/SKILL.md +0 -0
- /package/{skills → dist/skills}/themes-review/references/blocks-vs-sections.md +0 -0
- /package/{skills → dist/skills}/themes-review/references/css-js-hygiene.md +0 -0
- /package/{skills → dist/skills}/themes-review/references/dead-code.md +0 -0
- /package/{skills → dist/skills}/themes-review/references/dynamism.md +0 -0
- /package/{skills → dist/skills}/themes-review/references/editor-attributes.md +0 -0
- /package/{skills → dist/skills}/themes-review/references/examples.md +0 -0
- /package/{skills → dist/skills}/themes-review/references/fairshare-attributes.md +0 -0
- /package/{skills → dist/skills}/themes-review/references/global-settings.md +0 -0
- /package/{skills → dist/skills}/themes-review/references/liquid-correctness.md +0 -0
- /package/{skills → dist/skills}/themes-review/references/navigation.md +0 -0
- /package/{skills → dist/skills}/themes-review/references/performance.md +0 -0
- /package/{skills → dist/skills}/themes-review/references/security-accessibility.md +0 -0
- /package/{skills → dist/skills}/themes-review/references/setting-types.md +0 -0
package/README.md
CHANGED
|
@@ -84,6 +84,20 @@ Interactively select a route and open it in the browser (requires a running dev
|
|
|
84
84
|
fluid theme navigate
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
+
### `fluid theme skills install`
|
|
88
|
+
|
|
89
|
+
Copy the bundled theme AI skills (e.g. `themes-review`) into the current directory so an
|
|
90
|
+
agent can load them. Defaults to `.agents/skills/`, the tool-neutral convention for agent
|
|
91
|
+
skills:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
fluid theme skills install # → .agents/skills/
|
|
95
|
+
fluid theme skills install --dir .claude/skills # custom location
|
|
96
|
+
fluid theme skills install --force # overwrite existing skills without prompting
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Existing skills are left untouched unless you confirm the overwrite (or pass `--force`).
|
|
100
|
+
|
|
87
101
|
## Theme Directory Structure
|
|
88
102
|
|
|
89
103
|
A valid theme directory must contain at least one of: `templates/`, `assets/`, or `config/`.
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { getAuthToken, readConfig, updateConfig } from "@fluid-app/fluid-cli";
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
5
|
import { createHash } from "node:crypto";
|
|
6
6
|
import http from "node:http";
|
|
@@ -10,6 +10,7 @@ import chalk from "chalk";
|
|
|
10
10
|
import prompts from "prompts";
|
|
11
11
|
import ora from "ora";
|
|
12
12
|
import { execFileSync } from "node:child_process";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
13
14
|
//#region ../../platform/api-client-core/src/fetch-client.ts
|
|
14
15
|
/**
|
|
15
16
|
* API Error class compatible with fluid-admin's ApiError
|
|
@@ -2369,15 +2370,167 @@ function createNavigateCommand() {
|
|
|
2369
2370
|
});
|
|
2370
2371
|
}
|
|
2371
2372
|
//#endregion
|
|
2373
|
+
//#region src/fs/replace-directory.ts
|
|
2374
|
+
/**
|
|
2375
|
+
* Replace `target` with a fresh copy of `source` without ever leaving `target`
|
|
2376
|
+
* missing or partially written.
|
|
2377
|
+
*
|
|
2378
|
+
* Filesystem copies are not atomic, so a naive "delete then copy" loses the
|
|
2379
|
+
* original if the copy fails (permissions, no disk space, an interrupted
|
|
2380
|
+
* process). This stages the copy in a sibling directory and only swaps it into
|
|
2381
|
+
* place once it has fully succeeded; an existing `target` is moved aside to a
|
|
2382
|
+
* sibling backup first and restored if the swap fails. Because the whole
|
|
2383
|
+
* directory is replaced, files removed or renamed in `source` do not linger.
|
|
2384
|
+
*
|
|
2385
|
+
* Staging and backup directories live beside `target`, so its parent must
|
|
2386
|
+
* already exist and be on the same filesystem — that keeps the swap a cheap,
|
|
2387
|
+
* atomic rename rather than a cross-device copy.
|
|
2388
|
+
*
|
|
2389
|
+
* Not safe against a second process racing on the same `target`; intended for
|
|
2390
|
+
* single-process CLI use.
|
|
2391
|
+
*/
|
|
2392
|
+
function replaceDirectory(source, target) {
|
|
2393
|
+
const staging = reserveSiblingPath(target, "staging");
|
|
2394
|
+
try {
|
|
2395
|
+
cpSync(source, staging, { recursive: true });
|
|
2396
|
+
} catch (error) {
|
|
2397
|
+
rmSync(staging, {
|
|
2398
|
+
recursive: true,
|
|
2399
|
+
force: true
|
|
2400
|
+
});
|
|
2401
|
+
throw error;
|
|
2402
|
+
}
|
|
2403
|
+
if (!existsSync(target)) {
|
|
2404
|
+
swapIntoPlace(staging, target, null);
|
|
2405
|
+
return;
|
|
2406
|
+
}
|
|
2407
|
+
const backup = reserveSiblingPath(target, "backup");
|
|
2408
|
+
try {
|
|
2409
|
+
renameSync(target, backup);
|
|
2410
|
+
} catch (error) {
|
|
2411
|
+
rmSync(staging, {
|
|
2412
|
+
recursive: true,
|
|
2413
|
+
force: true
|
|
2414
|
+
});
|
|
2415
|
+
throw error;
|
|
2416
|
+
}
|
|
2417
|
+
swapIntoPlace(staging, target, backup);
|
|
2418
|
+
}
|
|
2419
|
+
function swapIntoPlace(staging, target, backup) {
|
|
2420
|
+
try {
|
|
2421
|
+
renameSync(staging, target);
|
|
2422
|
+
} catch (error) {
|
|
2423
|
+
rmSync(staging, {
|
|
2424
|
+
recursive: true,
|
|
2425
|
+
force: true
|
|
2426
|
+
});
|
|
2427
|
+
if (backup !== null) restoreBackup(backup, target, error);
|
|
2428
|
+
throw error;
|
|
2429
|
+
}
|
|
2430
|
+
if (backup !== null) rmSync(backup, {
|
|
2431
|
+
recursive: true,
|
|
2432
|
+
force: true
|
|
2433
|
+
});
|
|
2434
|
+
}
|
|
2435
|
+
function restoreBackup(backup, target, cause) {
|
|
2436
|
+
try {
|
|
2437
|
+
renameSync(backup, target);
|
|
2438
|
+
} catch {
|
|
2439
|
+
throw new Error(`Failed to replace ${target}; its previous contents are preserved at ${backup}.`, { cause });
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
function reserveSiblingPath(basePath, label) {
|
|
2443
|
+
let candidate = `${basePath}.${label}`;
|
|
2444
|
+
for (let n = 1; existsSync(candidate); n += 1) candidate = `${basePath}.${label}.${n}`;
|
|
2445
|
+
return candidate;
|
|
2446
|
+
}
|
|
2447
|
+
//#endregion
|
|
2448
|
+
//#region src/skills/install.ts
|
|
2449
|
+
function listSkillNames(skillsDir) {
|
|
2450
|
+
if (!existsSync(skillsDir)) return [];
|
|
2451
|
+
return readdirSync(skillsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => existsSync(join(skillsDir, name, "SKILL.md"))).sort();
|
|
2452
|
+
}
|
|
2453
|
+
async function installSkills(options) {
|
|
2454
|
+
const { sourceDir, targetRoot, force, confirmOverwrite } = options;
|
|
2455
|
+
const installed = [];
|
|
2456
|
+
const skipped = [];
|
|
2457
|
+
mkdirSync(targetRoot, { recursive: true });
|
|
2458
|
+
for (const name of listSkillNames(sourceDir)) {
|
|
2459
|
+
const from = join(sourceDir, name);
|
|
2460
|
+
const to = join(targetRoot, name);
|
|
2461
|
+
if (existsSync(to) && !force && !await confirmOverwrite(name)) {
|
|
2462
|
+
skipped.push(name);
|
|
2463
|
+
continue;
|
|
2464
|
+
}
|
|
2465
|
+
replaceDirectory(from, to);
|
|
2466
|
+
installed.push(name);
|
|
2467
|
+
}
|
|
2468
|
+
return {
|
|
2469
|
+
installed,
|
|
2470
|
+
skipped
|
|
2471
|
+
};
|
|
2472
|
+
}
|
|
2473
|
+
//#endregion
|
|
2474
|
+
//#region src/commands/skills.ts
|
|
2475
|
+
const DEFAULT_TARGET_DIR = ".agents/skills";
|
|
2476
|
+
function resolveBundledSkillsDir() {
|
|
2477
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
2478
|
+
const candidates = [
|
|
2479
|
+
join(here, "skills"),
|
|
2480
|
+
join(here, "..", "skills"),
|
|
2481
|
+
join(here, "..", "..", "skills")
|
|
2482
|
+
];
|
|
2483
|
+
for (const dir of candidates) if (listSkillNames(dir).length > 0) return dir;
|
|
2484
|
+
let dir = here;
|
|
2485
|
+
for (let depth = 0; depth < 6; depth++) {
|
|
2486
|
+
const candidate = join(dir, "skills");
|
|
2487
|
+
if (listSkillNames(candidate).length > 0) return candidate;
|
|
2488
|
+
dir = dirname(dir);
|
|
2489
|
+
}
|
|
2490
|
+
throw new Error("Could not locate the bundled theme skills — this is a packaging bug.");
|
|
2491
|
+
}
|
|
2492
|
+
function createSkillsCommand() {
|
|
2493
|
+
const skills = new Command("skills").description("Manage the bundled Fluid theme AI skills");
|
|
2494
|
+
skills.command("install").description("Copy the bundled theme skills into the current directory (default: .agents/skills/)").option("-d, --dir <path>", "Directory to install into", DEFAULT_TARGET_DIR).option("-f, --force", "Overwrite existing skills without prompting").action(async (opts) => {
|
|
2495
|
+
const sourceDir = resolveBundledSkillsDir();
|
|
2496
|
+
if (listSkillNames(sourceDir).length === 0) {
|
|
2497
|
+
console.error("No bundled skills found to install.");
|
|
2498
|
+
process.exit(1);
|
|
2499
|
+
}
|
|
2500
|
+
const targetRoot = resolve(process.cwd(), opts.dir);
|
|
2501
|
+
const { installed, skipped } = await installSkills({
|
|
2502
|
+
sourceDir,
|
|
2503
|
+
targetRoot,
|
|
2504
|
+
force: Boolean(opts.force),
|
|
2505
|
+
confirmOverwrite: async (name) => {
|
|
2506
|
+
const res = await prompts({
|
|
2507
|
+
type: "confirm",
|
|
2508
|
+
name: "overwrite",
|
|
2509
|
+
message: `${chalk.yellow(name)} already exists in ${opts.dir}. Overwrite?`,
|
|
2510
|
+
initial: false
|
|
2511
|
+
}, { onCancel: () => process.exit(130) });
|
|
2512
|
+
return Boolean(res.overwrite);
|
|
2513
|
+
}
|
|
2514
|
+
});
|
|
2515
|
+
for (const name of installed) console.log(`${chalk.green("✓")} ${name} → ${join(opts.dir, name)}`);
|
|
2516
|
+
for (const name of skipped) console.log(`${chalk.dim(`· skipped ${name} (kept existing)`)}`);
|
|
2517
|
+
const parts = [installed.length > 0 ? `${installed.length} installed` : null, skipped.length > 0 ? `${skipped.length} skipped` : null].filter(Boolean);
|
|
2518
|
+
console.log(`\n${chalk.bold(parts.join(", ") || "Nothing to do")} in ${targetRoot}`);
|
|
2519
|
+
if (installed.length > 0) console.log(chalk.dim("Restart your agent session to pick up the new skills."));
|
|
2520
|
+
});
|
|
2521
|
+
return skills;
|
|
2522
|
+
}
|
|
2523
|
+
//#endregion
|
|
2372
2524
|
//#region src/commands/theme.ts
|
|
2373
2525
|
function registerThemeCommand(ctx) {
|
|
2374
|
-
const cmd = new Command("theme").description("Theme developer workflow — dev server, push, pull, lint, init");
|
|
2526
|
+
const cmd = new Command("theme").description("Theme developer workflow — dev server, push, pull, lint, init, skills");
|
|
2375
2527
|
cmd.addCommand(createDevCommand());
|
|
2376
2528
|
cmd.addCommand(createPushCommand());
|
|
2377
2529
|
cmd.addCommand(createPullCommand());
|
|
2378
2530
|
cmd.addCommand(createLintCommand());
|
|
2379
2531
|
cmd.addCommand(createInitCommand());
|
|
2380
2532
|
cmd.addCommand(createNavigateCommand());
|
|
2533
|
+
cmd.addCommand(createSkillsCommand());
|
|
2381
2534
|
ctx.program.addCommand(cmd);
|
|
2382
2535
|
}
|
|
2383
2536
|
//#endregion
|