@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.
- package/dist/{bench-NKKDz3ld.js → bench-DZHgmlhL.js} +170 -12
- package/dist/bench-DZHgmlhL.js.map +1 -0
- package/dist/bin.js +21 -2
- package/dist/bin.js.map +1 -1
- package/dist/catalog.generated-B_ds7MPV.js +83 -0
- package/dist/catalog.generated-B_ds7MPV.js.map +1 -0
- package/dist/index.d.ts +20 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/package.json +4 -3
- package/dist/bench-NKKDz3ld.js.map +0 -1
|
@@ -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 =
|
|
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 ??
|
|
431
|
-
const source =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 ??
|
|
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 ===
|
|
1887
|
-
else if (ch ===
|
|
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 {
|
|
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-
|
|
2271
|
+
//# sourceMappingURL=bench-DZHgmlhL.js.map
|