@shortwind/cli 0.1.0-beta.2 → 0.1.0-beta.4

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.
@@ -116,6 +116,56 @@ function createRegistrySource(origin) {
116
116
  if (origin.startsWith("http://") || origin.startsWith("https://")) return httpSource(origin);
117
117
  return fileSource(origin);
118
118
  }
119
+ async function resolveSource(origin) {
120
+ if (origin && origin !== "bundled:@shortwind/catalog") return createRegistrySource(origin);
121
+ return defaultCatalogSource();
122
+ }
123
+ const CATALOG_PACKAGE = "@shortwind/catalog";
124
+ const NPM_TIMEOUT_MS = 3e3;
125
+ async function defaultCatalogSource() {
126
+ try {
127
+ const cdn = httpSource(`https://cdn.jsdelivr.net/npm/${CATALOG_PACKAGE}@${await resolveCatalogVersion()}/dist/registry`);
128
+ await cdn.loadPresets();
129
+ return cdn;
130
+ } catch {
131
+ return bundledSource();
132
+ }
133
+ }
134
+ async function resolveCatalogVersion() {
135
+ const res = await fetch(`https://registry.npmjs.org/${CATALOG_PACKAGE}`, {
136
+ signal: AbortSignal.timeout(NPM_TIMEOUT_MS),
137
+ headers: { accept: "application/vnd.npm.install-v1+json" }
138
+ });
139
+ if (!res.ok) throw new Error(`npm: ${res.status}`);
140
+ const tags = (await res.json())["dist-tags"] ?? {};
141
+ const version = tags["latest"] ?? tags["beta"];
142
+ if (!version) throw new Error("no published catalog version");
143
+ return version;
144
+ }
145
+ const BUNDLED_ORIGIN = "bundled:@shortwind/catalog";
146
+ function bundledSource() {
147
+ let cache = null;
148
+ const load = () => {
149
+ cache ??= import("./catalog.generated-B_ds7MPV.js");
150
+ return cache;
151
+ };
152
+ return {
153
+ origin: BUNDLED_ORIGIN,
154
+ async loadPresets() {
155
+ return (await load()).CATALOG_PRESETS;
156
+ },
157
+ async loadFamily(family) {
158
+ assertValidFamilyName(family);
159
+ const { CATALOG_RECIPES } = await load();
160
+ const css = CATALOG_RECIPES[family];
161
+ if (css === void 0) throw new Error(`unknown family: ${family}`);
162
+ return css;
163
+ },
164
+ async listAllFamilies() {
165
+ return [...(await load()).CATALOG_FAMILIES];
166
+ }
167
+ };
168
+ }
119
169
  function fileSource(origin) {
120
170
  const root = origin.startsWith("file://") ? fileURLToPath(origin) : origin;
121
171
  return {
@@ -423,12 +473,47 @@ function addImport(source, line) {
423
473
  return source.slice(0, lastEnd) + `\n${line}` + source.slice(lastEnd);
424
474
  }
425
475
  //#endregion
476
+ //#region src/agents-file.ts
477
+ const MARKER = "skills/shortwind/SKILL.md";
478
+ function line(skillRel) {
479
+ return `For UI, prefer Shortwind \`@recipe\` class names (e.g. \`@card\`, \`@btn-primary\`, \`@row\`) over raw Tailwind where a recipe fits — full catalog in \`${skillRel}\`.`;
480
+ }
481
+ const CANDIDATES = ["AGENTS.md", "CLAUDE.md"];
482
+ async function wireAgentsInstructions(cwd, skillPath) {
483
+ const pointer = line(path.relative(cwd, skillPath).split(path.sep).join("/"));
484
+ let touched = null;
485
+ for (const name of CANDIDATES) {
486
+ const file = path.join(cwd, name);
487
+ if (!existsSync(file)) continue;
488
+ const current = await readFile(file, "utf8");
489
+ if (current.includes(MARKER)) {
490
+ touched ??= {
491
+ path: file,
492
+ action: "skipped"
493
+ };
494
+ continue;
495
+ }
496
+ await writeFile(file, current + (current.endsWith("\n\n") ? "" : current.endsWith("\n") ? "\n" : "\n\n") + pointer + "\n");
497
+ return {
498
+ path: file,
499
+ action: "appended"
500
+ };
501
+ }
502
+ if (touched) return touched;
503
+ const target = path.join(cwd, "AGENTS.md");
504
+ await writeFile(target, `# AGENTS.md\n\n${pointer}\n`);
505
+ return {
506
+ path: target,
507
+ action: "created"
508
+ };
509
+ }
510
+ //#endregion
426
511
  //#region src/init.ts
427
- const DEFAULT_REGISTRY = "https://shortwind.dev/registry";
512
+ const DEFAULT_REGISTRY = BUNDLED_ORIGIN;
428
513
  async function init(options) {
429
514
  const cwd = path.resolve(options.cwd);
430
- const registry = options.registry ?? "https://shortwind.dev/registry";
431
- const source = createRegistrySource(registry);
515
+ const registry = options.registry ?? DEFAULT_REGISTRY;
516
+ const source = await resolveSource(registry);
432
517
  const shape = detectProject(cwd);
433
518
  const families = await resolveFamilies(options.preset, source);
434
519
  const pkgs = pickPackages(shape.bundler);
@@ -450,6 +535,7 @@ async function init(options) {
450
535
  await writeSkillMd(skillPath, recipesDir, families);
451
536
  const theme = await scaffoldTheme(cwd);
452
537
  const bundlerConfig = await wireBundler(cwd, shape.bundler);
538
+ const agentsFile = await wireAgentsInstructions(cwd, skillPath);
453
539
  return {
454
540
  packageManager: shape.packageManager,
455
541
  preset: options.preset,
@@ -466,7 +552,9 @@ async function init(options) {
466
552
  themeAction: theme.action,
467
553
  bundlerConfigPath: bundlerConfig.configPath,
468
554
  bundlerConfigAction: bundlerConfig.action,
469
- ...bundlerConfig.snippet ? { bundlerConfigSnippet: bundlerConfig.snippet } : {}
555
+ ...bundlerConfig.snippet ? { bundlerConfigSnippet: bundlerConfig.snippet } : {},
556
+ agentsFilePath: agentsFile.path,
557
+ agentsFileAction: agentsFile.action
470
558
  };
471
559
  }
472
560
  async function resolveFamilies(preset, source) {
@@ -698,7 +786,7 @@ async function add(options) {
698
786
  const cwd = path.resolve(options.cwd);
699
787
  const config = await readConfig(cwd);
700
788
  const registry = options.registry ?? config.registry;
701
- const source = createRegistrySource(registry);
789
+ const source = await resolveSource(registry);
702
790
  const recipesDir = path.join(cwd, config.recipesDir);
703
791
  await mkdir(recipesDir, { recursive: true });
704
792
  const lock = await readLockfile(recipesDir);
@@ -854,11 +942,55 @@ async function newFamily(options) {
854
942
  };
855
943
  }
856
944
  //#endregion
945
+ //#region src/commands/reseal.ts
946
+ async function reseal(options) {
947
+ const cwd = path.resolve(options.cwd);
948
+ const config = await readConfig(cwd);
949
+ const recipesDir = path.join(cwd, config.recipesDir);
950
+ const families = options.families && options.families.length > 0 ? options.families : installedFamilies(recipesDir);
951
+ const lock = await readLockfile(recipesDir);
952
+ const resealed = [];
953
+ const unchanged = [];
954
+ const notFound = [];
955
+ const noHeader = [];
956
+ for (const family of families) {
957
+ const file = path.join(recipesDir, `${family}.css`);
958
+ if (!existsSync(file)) {
959
+ notFound.push(family);
960
+ continue;
961
+ }
962
+ const source = readFileSync(file, "utf8");
963
+ const header = extractHeader(source);
964
+ if (!header) {
965
+ noHeader.push(family);
966
+ continue;
967
+ }
968
+ const sha = computeBodySha(source);
969
+ if (sha === header.sha && lock.families[family]?.sha === sha) {
970
+ unchanged.push(family);
971
+ continue;
972
+ }
973
+ await writeFile(file, rewriteHeaderSha(source, sha));
974
+ lock.families[family] = {
975
+ version: header.version,
976
+ sha
977
+ };
978
+ resealed.push(family);
979
+ }
980
+ await writeLockfile(recipesDir, lock);
981
+ return {
982
+ resealed,
983
+ unchanged,
984
+ notFound,
985
+ noHeader
986
+ };
987
+ }
988
+ //#endregion
857
989
  //#region src/commands/preset.ts
858
990
  async function preset(options) {
859
991
  const cwd = path.resolve(options.cwd);
860
992
  const config = await readConfig(cwd);
861
- const source = createRegistrySource(options.registry ?? config.registry);
993
+ const source = await resolveSource(options.registry ?? config.registry);
862
994
  if (options.name === "none") throw new Error("Use `shortwind remove` to uninstall families; preset 'none' is for `init` only.");
863
995
  const presets = await source.loadPresets();
864
996
  const all = await source.listAllFamilies();
@@ -887,7 +1019,7 @@ async function ls(options) {
887
1019
  });
888
1020
  let available = [];
889
1021
  if (!options.installedOnly) {
890
- const source = createRegistrySource(options.registry ?? config.registry);
1022
+ const source = await resolveSource(options.registry ?? config.registry);
891
1023
  try {
892
1024
  available = await source.listAllFamilies();
893
1025
  } catch {
@@ -1061,7 +1193,7 @@ async function upgrade(options) {
1061
1193
  const cwd = path.resolve(options.cwd);
1062
1194
  const config = await readConfig(cwd);
1063
1195
  const registry = options.registry ?? config.registry;
1064
- const source = options.source ?? createRegistrySource(registry);
1196
+ const source = options.source ?? await resolveSource(registry);
1065
1197
  const recipesDir = path.join(cwd, config.recipesDir);
1066
1198
  const installed = installedFamilies(recipesDir);
1067
1199
  const targets = options.families && options.families.length > 0 ? options.families : installed;
@@ -1766,6 +1898,7 @@ function checkDynamicClass(file, dynamicTokens) {
1766
1898
  const CLASS_ATTR_STR_RE = /\b(?:class|className)\s*=\s*(["'])([^"']*)\1/g;
1767
1899
  const CLASS_ATTR_BRACE_RE = /\b(?:class|className)\s*=\s*\{/g;
1768
1900
  const STRING_LITERAL_RE = /(["'`])((?:\\.|(?!\1)[^\\])*)\1/g;
1901
+ const CALL_EXPANDER_RE = /\b(?:cva|tv)\s*\(/g;
1769
1902
  function extractClassUsages(source) {
1770
1903
  const usages = [];
1771
1904
  for (const m of source.matchAll(CLASS_ATTR_STR_RE)) {
@@ -1804,6 +1937,28 @@ function extractClassUsages(source) {
1804
1937
  });
1805
1938
  }
1806
1939
  }
1940
+ for (const m of source.matchAll(CALL_EXPANDER_RE)) {
1941
+ const openParen = (m.index ?? 0) + m[0].length - 1;
1942
+ const close = findMatchingDelimiter(source, openParen, "(", ")");
1943
+ if (close === -1) continue;
1944
+ const inner = source.slice(openParen + 1, close);
1945
+ for (const sm of inner.matchAll(STRING_LITERAL_RE)) {
1946
+ const value = sm[2] ?? "";
1947
+ if (value.length === 0) continue;
1948
+ const literalStart = openParen + 1 + (sm.index ?? 0);
1949
+ const valueStart = literalStart + 1;
1950
+ const { tokens, dynamicTokens } = tokenizeClassString(source, value, valueStart);
1951
+ if (!tokens.some((t) => t.value.startsWith("@")) && dynamicTokens.length === 0) continue;
1952
+ usages.push({
1953
+ fileOffset: literalStart,
1954
+ valueStart,
1955
+ raw: value,
1956
+ tokens,
1957
+ dynamicTokens,
1958
+ fixable: false
1959
+ });
1960
+ }
1961
+ }
1807
1962
  return usages;
1808
1963
  }
1809
1964
  function tokenizeClassString(source, value, valueStart) {
@@ -1838,6 +1993,9 @@ function tokenizeClassString(source, value, valueStart) {
1838
1993
  };
1839
1994
  }
1840
1995
  function findMatchingBrace(source, openIdx) {
1996
+ return findMatchingDelimiter(source, openIdx, "{", "}");
1997
+ }
1998
+ function findMatchingDelimiter(source, openIdx, open, close) {
1841
1999
  let depth = 1;
1842
2000
  let i = openIdx + 1;
1843
2001
  while (i < source.length && depth > 0) {
@@ -1883,8 +2041,8 @@ function findMatchingBrace(source, openIdx) {
1883
2041
  }
1884
2042
  continue;
1885
2043
  }
1886
- if (ch === "{") depth++;
1887
- else if (ch === "}") depth--;
2044
+ if (ch === open) depth++;
2045
+ else if (ch === close) depth--;
1888
2046
  i++;
1889
2047
  }
1890
2048
  return depth === 0 ? i - 1 : -1;
@@ -2108,6 +2266,6 @@ function formatBenchTable(result) {
2108
2266
  return lines.join("\n");
2109
2267
  }
2110
2268
  //#endregion
2111
- export { extractHeader as A, readLockfile as C, detectProject as D, resolvePresetFamilies as E, rewriteHeaderSha as M, sealRecipeFile as N, buildHeaderLine as O, init as S, createRegistrySource as T, newFamily as _, formatFindingsText as a, renameFamilyInSource as b, UpgradeError as c, BuildError as d, build as f, NewFamilyError as g, preset as h, extractClassUsages as i, normalizeBody as j, computeBodySha as k, upgrade as l, ls as m, formatBenchTable as n, lint as o, formatLsText as p, ALL_RULES as r, verify as s, bench as t, dev as u, remove as v, writeLockfile as w, DEFAULT_REGISTRY as x, add as y };
2269
+ export { computeBodySha as A, init as C, resolvePresetFamilies as D, createRegistrySource as E, normalizeBody as M, rewriteHeaderSha as N, detectProject as O, sealRecipeFile as P, DEFAULT_REGISTRY as S, writeLockfile as T, NewFamilyError as _, formatFindingsText as a, add as b, UpgradeError as c, BuildError as d, build as f, reseal as g, preset as h, extractClassUsages as i, extractHeader as j, buildHeaderLine as k, upgrade as l, ls as m, formatBenchTable as n, lint as o, formatLsText as p, ALL_RULES as r, verify as s, bench as t, dev as u, newFamily as v, readLockfile as w, renameFamilyInSource as x, remove as y };
2112
2270
 
2113
- //# sourceMappingURL=bench-NKKDz3ld.js.map
2271
+ //# sourceMappingURL=bench-DZHgmlhL.js.map