@vegastack/design 0.1.0 → 0.1.1
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/bin/check-updates.mjs +97 -37
- package/package.json +3 -6
package/bin/check-updates.mjs
CHANGED
|
@@ -163,16 +163,43 @@ function walk(dir, out = []) {
|
|
|
163
163
|
return out;
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
|
|
167
|
-
|
|
166
|
+
// Read a copied-in file. Returns { file, name, content, header? } where header is the parsed
|
|
167
|
+
// provenance line when present. The header is the fast path — but the REAL `shadcn add`
|
|
168
|
+
// pipeline strips leading comments during its transform, so most consumer copies have NO
|
|
169
|
+
// header. Headerless files are identified by filename against the registry index and compared
|
|
170
|
+
// by alias-normalized CONTENT instead (see `normalizeForCompare`).
|
|
171
|
+
function readInstalled(file) {
|
|
172
|
+
let content;
|
|
168
173
|
try {
|
|
169
|
-
|
|
170
|
-
firstLine = fd.slice(0, fd.indexOf('\n') === -1 ? fd.length : fd.indexOf('\n'));
|
|
174
|
+
content = readFileSync(file, 'utf8');
|
|
171
175
|
} catch {
|
|
172
176
|
return null;
|
|
173
177
|
}
|
|
178
|
+
const firstLine = content.slice(0, content.indexOf('\n') === -1 ? content.length : content.indexOf('\n'));
|
|
174
179
|
const m = PROVENANCE_RE.exec(firstLine);
|
|
175
|
-
|
|
180
|
+
const name = basename(file).replace(/\.tsx?$/, '');
|
|
181
|
+
return m
|
|
182
|
+
? { file, name: m[1], content, header: { version: m[2], hash: `sha256-${m[3]}` } }
|
|
183
|
+
: { file, name, content, header: null };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Strip a provenance header (with its optional following blank line) from file content.
|
|
187
|
+
function stripHeader(content) {
|
|
188
|
+
return content.replace(/^\/\/ @vegastack \S+@\S+ sha256-\S+\r?\n(?:\r?\n)?/, '');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Normalize content for the headerless comparison: drop the provenance header, unify line
|
|
193
|
+
* endings, rewrite the registry's canonical `@/…` import prefix to the consumer's alias root
|
|
194
|
+
* (the same class of rewrite `shadcn add` performs), and ignore trailing whitespace.
|
|
195
|
+
* Consumers on the default `@/*` alias need no rewrite at all.
|
|
196
|
+
*/
|
|
197
|
+
function normalizeForCompare(content, aliasRoot) {
|
|
198
|
+
let s = stripHeader(content).replace(/\r\n/g, '\n');
|
|
199
|
+
if (aliasRoot && aliasRoot !== '@') {
|
|
200
|
+
s = s.replace(/((?:from\s*|import\s*|import\(\s*|require\(\s*))(['"])@\//g, (_, head, q) => `${head}${q}${aliasRoot}/`);
|
|
201
|
+
}
|
|
202
|
+
return s.trimEnd() + '\n';
|
|
176
203
|
}
|
|
177
204
|
|
|
178
205
|
function globToRe(pattern) {
|
|
@@ -223,19 +250,8 @@ export async function main(argv) {
|
|
|
223
250
|
const idxUrl = indexUrl(urlTemplate);
|
|
224
251
|
const dir = resolveComponentsDir(cwd, opts.dir, componentsJson);
|
|
225
252
|
|
|
226
|
-
//
|
|
227
|
-
|
|
228
|
-
if (opts.filter) {
|
|
229
|
-
const res = opts.filter.split(',').map((s) => globToRe(s.trim()));
|
|
230
|
-
installed = installed.filter((c) => res.some((re) => re.test(c.name)));
|
|
231
|
-
}
|
|
232
|
-
if (installed.length === 0) {
|
|
233
|
-
if (opts.json) console.log(JSON.stringify({ registry: idxUrl, checked: 0, updates: 0, items: [] }, null, 2));
|
|
234
|
-
else console.log(`No VegaStack components found in ${dir}. (Add some with \`shadcn add @vegastack/<name>\`.)`);
|
|
235
|
-
return 0;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// fetch the registry index once
|
|
253
|
+
// fetch the registry index once (needed up front: headerless files are identified by
|
|
254
|
+
// matching their filename against the index's item names)
|
|
239
255
|
let index;
|
|
240
256
|
try {
|
|
241
257
|
const res = await fetch(idxUrl, { headers });
|
|
@@ -252,22 +268,62 @@ export async function main(argv) {
|
|
|
252
268
|
const remote = new Map();
|
|
253
269
|
for (const item of index.items ?? []) remote.set(item.name, { version: item.meta?.version, integrity: item.meta?.integrity });
|
|
254
270
|
|
|
255
|
-
//
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
.
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
+
// scan for installed VegaStack components:
|
|
272
|
+
// - headered files (our own tooling / older CLIs preserve the provenance line) — always included
|
|
273
|
+
// - headerless files whose basename matches a registry item — the REAL `shadcn add` strips
|
|
274
|
+
// the header, so this is the normal consumer case
|
|
275
|
+
// - headerless files NOT in the index are skipped (they're the consumer's own components)
|
|
276
|
+
let installed = walk(dir)
|
|
277
|
+
.map(readInstalled)
|
|
278
|
+
.filter(Boolean)
|
|
279
|
+
.filter((c) => c.header || remote.has(c.name));
|
|
280
|
+
if (opts.filter) {
|
|
281
|
+
const res = opts.filter.split(',').map((s) => globToRe(s.trim()));
|
|
282
|
+
installed = installed.filter((c) => res.some((re) => re.test(c.name)));
|
|
283
|
+
}
|
|
284
|
+
if (installed.length === 0) {
|
|
285
|
+
if (opts.json) console.log(JSON.stringify({ registry: idxUrl, checked: 0, updates: 0, items: [] }, null, 2));
|
|
286
|
+
else console.log(`No VegaStack components found in ${dir}. (Add some with \`shadcn add @vegastack/<name>\`.)`);
|
|
287
|
+
return 0;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// the consumer's alias root ('@' default; '~', 'src', … supported) for content normalization
|
|
291
|
+
const aliasRoot = (componentsJson?.aliases?.components ?? '@/components').split('/')[0];
|
|
292
|
+
|
|
293
|
+
// Resolve each installed file to a status:
|
|
294
|
+
// - headered: compare the header's item hash against the index integrity (fast, no item fetch)
|
|
295
|
+
// - headerless: fetch the item and compare alias-normalized CONTENT; equal → current,
|
|
296
|
+
// different → 'drift' (an upstream update OR local edits — `add --diff` disambiguates)
|
|
297
|
+
const itemUrlFor = (name) =>
|
|
298
|
+
urlTemplate.includes('{name}') ? urlTemplate.replace('{name}', name) : `${urlTemplate.replace(/\/$/, '')}/${name}.json`;
|
|
299
|
+
|
|
300
|
+
async function resolveStatus(c) {
|
|
301
|
+
const r = remote.get(c.name);
|
|
302
|
+
if (!r) return { name: c.name, current: c.header?.version ?? null, latest: null, status: 'missing' };
|
|
303
|
+
if (c.header) {
|
|
304
|
+
const status = r.integrity && c.header.hash === r.integrity ? 'current' : 'update';
|
|
305
|
+
return { name: c.name, current: c.header.version, latest: r.version ?? null, status };
|
|
306
|
+
}
|
|
307
|
+
try {
|
|
308
|
+
const res = await fetch(itemUrlFor(c.name), { headers });
|
|
309
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
310
|
+
const item = await res.json();
|
|
311
|
+
const base = basename(c.file);
|
|
312
|
+
const entry = (item.files ?? []).find((f) => basename(f.target ?? f.path ?? '') === base) ?? (item.files ?? [])[0];
|
|
313
|
+
const remoteContent = entry?.content ?? '';
|
|
314
|
+
const same = normalizeForCompare(remoteContent, aliasRoot) === normalizeForCompare(c.content, aliasRoot);
|
|
315
|
+
return { name: c.name, current: null, latest: r.version ?? null, status: same ? 'current' : 'drift' };
|
|
316
|
+
} catch (err) {
|
|
317
|
+
return { name: c.name, current: null, latest: r.version ?? null, status: 'drift', note: `item fetch failed: ${err.message}` };
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const rows = (await Promise.all(installed.map(resolveStatus))).sort((a, b) => {
|
|
322
|
+
const rank = { update: 0, drift: 0, current: 1, missing: 2 };
|
|
323
|
+
return rank[a.status] - rank[b.status] || a.name.localeCompare(b.name);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
const updates = rows.filter((r) => r.status === 'update' || r.status === 'drift');
|
|
271
327
|
|
|
272
328
|
if (opts.json) {
|
|
273
329
|
console.log(JSON.stringify({ registry: idxUrl, checked: rows.length, updates: updates.length, items: rows }, null, 2));
|
|
@@ -284,14 +340,18 @@ export async function main(argv) {
|
|
|
284
340
|
})();
|
|
285
341
|
console.log(`\nChecking ${rows.length} VegaStack component(s) against ${host} …\n`);
|
|
286
342
|
const nameW = Math.max(...rows.map((r) => r.name.length), 4);
|
|
287
|
-
const GLYPH = { update: color.yellow('⬆'), current: color.green('✓'), missing: color.dim('?') };
|
|
343
|
+
const GLYPH = { update: color.yellow('⬆'), drift: color.yellow('≈'), current: color.green('✓'), missing: color.dim('?') };
|
|
288
344
|
for (const r of rows) {
|
|
289
|
-
const ver =
|
|
345
|
+
const ver =
|
|
346
|
+
r.status === 'update' ? `${r.current} → ${r.latest ?? '?'}` :
|
|
347
|
+
r.status === 'drift' ? `→ ${r.latest ?? '?'}` :
|
|
348
|
+
(r.current ?? r.latest ?? '—');
|
|
290
349
|
const note =
|
|
291
350
|
r.status === 'update' ? color.yellow('update available') :
|
|
351
|
+
r.status === 'drift' ? color.yellow('differs from registry (update or local edits — review with --diff)') :
|
|
292
352
|
r.status === 'current' ? color.dim('up to date') :
|
|
293
353
|
color.dim('not in registry (renamed/removed)');
|
|
294
|
-
console.log(` ${GLYPH[r.status]} ${r.name.padEnd(nameW)} ${ver.padEnd(16)} ${note}`);
|
|
354
|
+
console.log(` ${GLYPH[r.status]} ${r.name.padEnd(nameW)} ${String(ver).padEnd(16)} ${note}`);
|
|
295
355
|
}
|
|
296
356
|
console.log('');
|
|
297
357
|
if (updates.length) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vegastack/design",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "VegaStack design system — cn utility, icon runtime, Tailwind v4 preset, and the vegastack-design CLI (tokens ship separately as @vegastack/design-tokens)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -44,20 +44,17 @@
|
|
|
44
44
|
"lucide-react": "^1.24.0",
|
|
45
45
|
"tailwind-merge": "^3.6.0",
|
|
46
46
|
"thesvg": "^3.2.6",
|
|
47
|
+
"tw-animate-css": "^1.4.0",
|
|
47
48
|
"@vegastack/design-tokens": "0.1.0"
|
|
48
49
|
},
|
|
49
50
|
"peerDependencies": {
|
|
50
51
|
"react": "^19.0.0",
|
|
51
52
|
"react-dom": "^19.0.0",
|
|
52
|
-
"tailwindcss": "^4.3.2"
|
|
53
|
-
"tw-animate-css": "^1.4.0"
|
|
53
|
+
"tailwindcss": "^4.3.2"
|
|
54
54
|
},
|
|
55
55
|
"peerDependenciesMeta": {
|
|
56
56
|
"tailwindcss": {
|
|
57
57
|
"optional": true
|
|
58
|
-
},
|
|
59
|
-
"tw-animate-css": {
|
|
60
|
-
"optional": true
|
|
61
58
|
}
|
|
62
59
|
},
|
|
63
60
|
"devDependencies": {
|