@fluid-app/fluid-cli-theme-dev 0.1.28 → 0.1.30

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/dist/index.mjs CHANGED
@@ -52,6 +52,12 @@ function getRequestIdFromJsonBody(body) {
52
52
  return getStringRequestId(record.request_id) ?? getStringRequestId(record.requestId) ?? (meta && typeof meta === "object" && !Array.isArray(meta) ? getStringRequestId(meta.request_id) ?? getStringRequestId(meta.requestId) : void 0);
53
53
  }
54
54
  /**
55
+ * Type guard for ApiError
56
+ */
57
+ function isApiError(error) {
58
+ return error instanceof ApiError;
59
+ }
60
+ /**
55
61
  * Creates a configured fetch client instance
56
62
  */
57
63
  function createFetchClient(config) {
@@ -1258,6 +1264,45 @@ async function deleteThemeResource(client, application_theme_id, body) {
1258
1264
  return client.delete(`/api/application_themes/${application_theme_id}/resources`, { body });
1259
1265
  }
1260
1266
  //#endregion
1267
+ //#region src/theme/stylesheet-keys.ts
1268
+ const STYLESHEET_KEY_PATTERN = /^(styles\.css|global_styles\.css|[^/]+\/[^/]+\/styles\.css)$/;
1269
+ function isStylesheetKey(key) {
1270
+ return STYLESHEET_KEY_PATTERN.test(key);
1271
+ }
1272
+ //#endregion
1273
+ //#region src/theme/format-error.ts
1274
+ const STYLESHEET_MIGRATION_SKILL = "template-stylesheet-to-asset-migration";
1275
+ function isStylesheetKeyRejection(error) {
1276
+ if (error.status !== 422) return false;
1277
+ if (/stylesheet.*no longer accepted/i.test(error.message)) return true;
1278
+ if (error.data && typeof error.data === "object") {
1279
+ const resourceErrors = error.data.application_theme_resource;
1280
+ if (resourceErrors && typeof resourceErrors.key === "string" && /stylesheet.*no longer accepted/i.test(resourceErrors.key)) return true;
1281
+ }
1282
+ return false;
1283
+ }
1284
+ /**
1285
+ * Extract a human-readable message from a caught error. ApiError's default
1286
+ * `toString` prefixes with `ApiError:` and the class name; this returns the
1287
+ * API's own `error_message` verbatim so the CLI surfaces messages like
1288
+ * "This stylesheet key is no longer accepted. Upload stylesheets as
1289
+ * theme-level assets." directly.
1290
+ *
1291
+ * When the error is the legacy stylesheet key rejection specifically,
1292
+ * a hint is appended pointing to the bundled migration skill — the
1293
+ * caller needs to move the per-template `styles.css` bytes to `assets/`
1294
+ * and stop pushing the deprecated column key.
1295
+ */
1296
+ function formatError(e) {
1297
+ if (isApiError(e)) {
1298
+ const status = e.status ? ` [${e.status}]` : "";
1299
+ const hint = isStylesheetKeyRejection(e) ? `\n ↳ Run \`fluid theme skills install\` — the bundled \`${STYLESHEET_MIGRATION_SKILL}\` skill can help you migrate this to a theme-level asset.` : "";
1300
+ return `${e.message}${status}${hint}`;
1301
+ }
1302
+ if (e instanceof Error) return e.message;
1303
+ return String(e);
1304
+ }
1305
+ //#endregion
1261
1306
  //#region src/theme/syncer.ts
1262
1307
  var Syncer = class {
1263
1308
  checksums = /* @__PURE__ */ new Map();
@@ -1394,7 +1439,7 @@ var Syncer = class {
1394
1439
  await this.uploadFile(file);
1395
1440
  result.uploaded++;
1396
1441
  } catch (e) {
1397
- result.errors.push(`Upload ${file.relativePath}: ${e}`);
1442
+ result.errors.push(`Upload ${file.relativePath}: ${formatError(e)}`);
1398
1443
  }
1399
1444
  opts.onProgress?.(++done, toUpload.length);
1400
1445
  }
@@ -1405,7 +1450,7 @@ var Syncer = class {
1405
1450
  await this.deleteRemoteFile(key);
1406
1451
  result.deleted++;
1407
1452
  } catch (e) {
1408
- result.errors.push(`Delete ${key}: ${e}`);
1453
+ result.errors.push(`Delete ${key}: ${formatError(e)}`);
1409
1454
  }
1410
1455
  }
1411
1456
  return result;
@@ -1443,17 +1488,21 @@ var Syncer = class {
1443
1488
  }
1444
1489
  result.downloaded++;
1445
1490
  } catch (e) {
1446
- result.errors.push(`Download ${resource.key}: ${e}`);
1491
+ result.errors.push(`Download ${resource.key}: ${formatError(e)}`);
1447
1492
  }
1448
1493
  opts.onProgress?.(++done, resources.length);
1449
1494
  }
1450
1495
  if (opts.delete) {
1451
1496
  const remoteKeys = new Set(resources.map((r) => r.key));
1452
- for (const file of this.themeRoot.files()) if (!remoteKeys.has(file.relativePath)) try {
1453
- const { unlinkSync } = await import("node:fs");
1454
- unlinkSync(file.absolutePath);
1455
- result.deleted++;
1456
- } catch {}
1497
+ for (const file of this.themeRoot.files()) {
1498
+ if (remoteKeys.has(file.relativePath)) continue;
1499
+ if (isStylesheetKey(file.relativePath)) continue;
1500
+ try {
1501
+ const { unlinkSync } = await import("node:fs");
1502
+ unlinkSync(file.absolutePath);
1503
+ result.deleted++;
1504
+ } catch {}
1505
+ }
1457
1506
  }
1458
1507
  return result;
1459
1508
  }
@@ -2490,6 +2539,12 @@ function resolveBundledSkillsDir() {
2490
2539
  }
2491
2540
  throw new Error("Could not locate the bundled theme skills — this is a packaging bug.");
2492
2541
  }
2542
+ function skillsWouldShipWithTheme(cwd, targetRoot) {
2543
+ if (!new ThemeRoot(cwd).isValid()) return false;
2544
+ const rel = relative(cwd, targetRoot);
2545
+ if (rel.startsWith("..")) return false;
2546
+ return !rel.split(sep).some((segment) => segment.startsWith("."));
2547
+ }
2493
2548
  function createSkillsCommand() {
2494
2549
  const skills = new Command("skills").description("Manage the bundled Fluid theme AI skills");
2495
2550
  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) => {
@@ -2499,6 +2554,7 @@ function createSkillsCommand() {
2499
2554
  process.exit(1);
2500
2555
  }
2501
2556
  const targetRoot = resolve(process.cwd(), opts.dir);
2557
+ if (skillsWouldShipWithTheme(process.cwd(), targetRoot)) console.log(`${chalk.yellow("⚠")} ${chalk.bold(opts.dir)} is not a hidden directory — 'fluid theme push' will upload these skills as theme files. Install into a dot-directory like ${chalk.cyan(DEFAULT_TARGET_DIR)} to keep them local.`);
2502
2558
  const { installed, skipped } = await installSkills({
2503
2559
  sourceDir,
2504
2560
  targetRoot,