@kznlabs/labsui 0.1.10 → 0.1.11
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 +9 -5
- package/dist/index.js +55 -34
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -60,7 +60,9 @@ Run it again any time: existing files are kept.
|
|
|
60
60
|
Resolves dependencies through the registry (`data-table` brings `table`,
|
|
61
61
|
`checkbox`, `pagination`, …), writes each file under your alias folder and
|
|
62
62
|
installs the npm packages it imports. Existing files are kept unless you pass
|
|
63
|
-
`--overwrite
|
|
63
|
+
`--overwrite`; a kept dependency whose code differs from this version is
|
|
64
|
+
named, with the `labsui update` that brings it level, since the new item may
|
|
65
|
+
need it. If your alias isn't `@/`, imports are rewritten to match.
|
|
64
66
|
Components that live once in the root layout (`toast`, `confirm`) print the
|
|
65
67
|
line to add there. A mistyped name gets a suggestion: `buton` suggests `button`.
|
|
66
68
|
|
|
@@ -109,16 +111,18 @@ changes. `labsui.json` records which labsui version wrote each item, and
|
|
|
109
111
|
The base comes from JSR, which keeps every release's files. Offline, with a
|
|
110
112
|
hosted registry, or for items installed before versions were recorded,
|
|
111
113
|
edited files are left as they are. `labsui diff` lists installed items that
|
|
112
|
-
differ from this version
|
|
113
|
-
version's file
|
|
114
|
+
differ from this version, marking those that differ only in blank lines or
|
|
115
|
+
indentation; `labsui diff dialog` prints the patch from this version's file
|
|
116
|
+
to yours, or says `dialog` isn't installed.
|
|
114
117
|
|
|
115
118
|
### `doctor`
|
|
116
119
|
|
|
117
120
|
Checks the setup and prints a fix for each problem: React 19 and Tailwind v4,
|
|
118
121
|
the path alias and `labsui.json`, the stylesheet import, `MotionProvider` and
|
|
119
122
|
the theme script, `<Toaster />` and `<Confirmer />` mounted when installed,
|
|
120
|
-
missing npm packages
|
|
121
|
-
|
|
123
|
+
missing npm packages, and files your installed components import but the
|
|
124
|
+
project lacks. It also lists installed items that differ from this version,
|
|
125
|
+
your edits or an older install. Exits with 1 when something is
|
|
122
126
|
broken, so it can run in CI.
|
|
123
127
|
|
|
124
128
|
It reads your code, not a framework's conventions: it works the same in any
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { resolve as resolve2 } from "node:path";
|
|
|
5
5
|
import { parseArgs } from "node:util";
|
|
6
6
|
|
|
7
7
|
// src/commands/add.ts
|
|
8
|
-
import { existsSync as existsSync3, mkdirSync, realpathSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
8
|
+
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, realpathSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
9
9
|
import { basename, dirname, isAbsolute, join as join3, relative as relative2, resolve } from "node:path";
|
|
10
10
|
|
|
11
11
|
// src/install.ts
|
|
@@ -62,6 +62,16 @@ function installDeps(cwd, deps, opts) {
|
|
|
62
62
|
return true;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
// src/lines.ts
|
|
66
|
+
var lines = (text) => text.replace(/\r\n/g, `
|
|
67
|
+
`).replace(/\n$/, "").split(`
|
|
68
|
+
`);
|
|
69
|
+
function sameCode(a, b) {
|
|
70
|
+
const strip = (text) => lines(text).map((line) => line.trim()).filter(Boolean).join(`
|
|
71
|
+
`);
|
|
72
|
+
return strip(a) === strip(b);
|
|
73
|
+
}
|
|
74
|
+
|
|
65
75
|
// src/project.ts
|
|
66
76
|
import { existsSync as existsSync2, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
67
77
|
import { join as join2, relative } from "node:path";
|
|
@@ -17811,7 +17821,7 @@ async function fetchAllItems(source) {
|
|
|
17811
17821
|
// jsr.json
|
|
17812
17822
|
var jsr_default = {
|
|
17813
17823
|
name: "@kznlabs/labsui",
|
|
17814
|
-
version: "0.1.
|
|
17824
|
+
version: "0.1.11",
|
|
17815
17825
|
license: "MIT",
|
|
17816
17826
|
exports: "./src/index.ts",
|
|
17817
17827
|
publish: {
|
|
@@ -17861,6 +17871,7 @@ async function add(cwd, config, names, opts) {
|
|
|
17861
17871
|
fresh: [],
|
|
17862
17872
|
written: [],
|
|
17863
17873
|
kept: [],
|
|
17874
|
+
outdated: [],
|
|
17864
17875
|
packages: [],
|
|
17865
17876
|
mounts: [],
|
|
17866
17877
|
dryRun: opts.dryRun
|
|
@@ -17897,6 +17908,8 @@ function writeItem(item, base, cwd, config, opts, result, named) {
|
|
|
17897
17908
|
if (existsSync3(target) && !opts.overwrite) {
|
|
17898
17909
|
if (named)
|
|
17899
17910
|
result.kept.push(shown);
|
|
17911
|
+
else if (!result.outdated.includes(item.name) && !sameCode(readFileSync2(target, "utf8"), rewriteAlias(file.content, config.alias)))
|
|
17912
|
+
result.outdated.push(item.name);
|
|
17900
17913
|
continue;
|
|
17901
17914
|
}
|
|
17902
17915
|
if (!opts.dryRun) {
|
|
@@ -17918,6 +17931,8 @@ ${fileList(result.written)}`);
|
|
|
17918
17931
|
if (result.kept.length)
|
|
17919
17932
|
console.log(`${yellow("•")} Kept ${result.kept.length} existing file(s) (--overwrite replaces them):
|
|
17920
17933
|
${fileList(result.kept)}`);
|
|
17934
|
+
if (result.outdated.length)
|
|
17935
|
+
console.log(`${yellow("•")} Kept dependencies that differ from this version (older or edited): ${result.outdated.join(", ")}. The new items may need this version: ${bold(`labsui update ${result.outdated.join(" ")}`)}`);
|
|
17921
17936
|
if (result.installCommand)
|
|
17922
17937
|
console.log(`${yellow("•")} Install these yourself: ${bold(result.installCommand)}`);
|
|
17923
17938
|
else if (result.packages.length)
|
|
@@ -17932,16 +17947,8 @@ ${bold("Mount once")} in the root layout, inside <MotionProvider>:`);
|
|
|
17932
17947
|
}
|
|
17933
17948
|
|
|
17934
17949
|
// src/commands/diff.ts
|
|
17935
|
-
import { existsSync as existsSync4, readFileSync as
|
|
17950
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
|
|
17936
17951
|
import { relative as relative3 } from "node:path";
|
|
17937
|
-
var lines = (text) => text.replace(/\r\n/g, `
|
|
17938
|
-
`).replace(/\n$/, "").split(`
|
|
17939
|
-
`);
|
|
17940
|
-
function sameCode(a, b) {
|
|
17941
|
-
const strip = (text) => lines(text).map((line) => line.trim()).filter(Boolean).join(`
|
|
17942
|
-
`);
|
|
17943
|
-
return strip(a) === strip(b);
|
|
17944
|
-
}
|
|
17945
17952
|
function matches(a, b) {
|
|
17946
17953
|
const out = new Int32Array(a.length).fill(-1);
|
|
17947
17954
|
let i = 0;
|
|
@@ -18016,9 +18023,8 @@ function unifiedDiff(from, to, fromName, toName, context = 3) {
|
|
|
18016
18023
|
return out.join(`
|
|
18017
18024
|
`);
|
|
18018
18025
|
}
|
|
18019
|
-
async function installedItems(config, base) {
|
|
18020
|
-
|
|
18021
|
-
return all.flatMap((item) => {
|
|
18026
|
+
async function installedItems(config, base, all) {
|
|
18027
|
+
return (all ?? await fetchAllItems(config.registry)).flatMap((item) => {
|
|
18022
18028
|
const files = item.files.map((file) => safeTarget(base, file.target ?? file.path));
|
|
18023
18029
|
return files.some((file) => existsSync4(file)) ? [{ item, files }] : [];
|
|
18024
18030
|
});
|
|
@@ -18026,8 +18032,12 @@ async function installedItems(config, base) {
|
|
|
18026
18032
|
async function diff(cwd, config, names, version) {
|
|
18027
18033
|
const base = projectBase(cwd, config);
|
|
18028
18034
|
const wanted = new Set(names.map(itemName));
|
|
18029
|
-
const
|
|
18030
|
-
const
|
|
18035
|
+
const all = await fetchAllItems(config.registry);
|
|
18036
|
+
const unknown = [...wanted].filter((name) => !all.some((item) => item.name === name));
|
|
18037
|
+
if (unknown.length)
|
|
18038
|
+
throw new CliError(`Unknown item(s): ${unknown.join(", ")}. Search finds the names.`);
|
|
18039
|
+
const installed = await installedItems(config, base, all);
|
|
18040
|
+
const out = [...wanted].filter((name) => !installed.some(({ item }) => item.name === name)).map((name) => ({ name, installed: false, files: [] }));
|
|
18031
18041
|
for (const { item, files } of installed) {
|
|
18032
18042
|
if (wanted.size && !wanted.has(item.name))
|
|
18033
18043
|
continue;
|
|
@@ -18038,7 +18048,7 @@ async function diff(cwd, config, names, version) {
|
|
|
18038
18048
|
return;
|
|
18039
18049
|
const shown = relative3(cwd, local).replaceAll("\\", "/");
|
|
18040
18050
|
const theirs = rewriteAlias(file.content, config.alias);
|
|
18041
|
-
const yours =
|
|
18051
|
+
const yours = readFileSync3(local, "utf8");
|
|
18042
18052
|
const patch = unifiedDiff(theirs, yours, `labsui@${version}/${file.path}`, shown);
|
|
18043
18053
|
if (!patch)
|
|
18044
18054
|
return;
|
|
@@ -18053,14 +18063,16 @@ async function diff(cwd, config, names, version) {
|
|
|
18053
18063
|
});
|
|
18054
18064
|
});
|
|
18055
18065
|
if (changed.length)
|
|
18056
|
-
out.push({ name: item.name, files: changed });
|
|
18066
|
+
out.push({ name: item.name, installed: true, files: changed });
|
|
18057
18067
|
}
|
|
18058
18068
|
return out;
|
|
18059
18069
|
}
|
|
18060
18070
|
function printDiff(items, full) {
|
|
18061
18071
|
if (!items.length)
|
|
18062
18072
|
return console.log(`${green("✓")} Installed items match this labsui version.`);
|
|
18063
|
-
for (const item of items)
|
|
18073
|
+
for (const item of items) {
|
|
18074
|
+
if (!item.installed)
|
|
18075
|
+
console.log(` ${item.name.padEnd(24)} ${dim(`not installed (labsui add ${item.name})`)}`);
|
|
18064
18076
|
for (const file of item.files) {
|
|
18065
18077
|
if (!full) {
|
|
18066
18078
|
console.log(` ${item.name.padEnd(24)} ${green(`+${file.added}`)} ${red(`-${file.removed}`)} ${dim(file.path)}${file.formatting ? dim(" (formatting only)") : ""}`);
|
|
@@ -18070,13 +18082,14 @@ function printDiff(items, full) {
|
|
|
18070
18082
|
`))
|
|
18071
18083
|
console.log(line.startsWith("+") ? green(line) : line.startsWith("-") ? red(line) : line.startsWith("@@") ? dim(line) : line);
|
|
18072
18084
|
}
|
|
18085
|
+
}
|
|
18073
18086
|
if (!full)
|
|
18074
18087
|
console.log(`
|
|
18075
18088
|
${dim("labsui diff <name> shows the changes; add <name> --overwrite takes this version.")}`);
|
|
18076
18089
|
}
|
|
18077
18090
|
|
|
18078
18091
|
// src/commands/doctor.ts
|
|
18079
|
-
import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as
|
|
18092
|
+
import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync2 } from "node:fs";
|
|
18080
18093
|
import { join as join4, relative as relative4 } from "node:path";
|
|
18081
18094
|
|
|
18082
18095
|
// src/merge.ts
|
|
@@ -18196,13 +18209,13 @@ async function doctor(cwd, registryFlag) {
|
|
|
18196
18209
|
else {
|
|
18197
18210
|
const css = findCss(cwd, config.root);
|
|
18198
18211
|
const shown = css && relative4(cwd, css).replaceAll("\\", "/");
|
|
18199
|
-
if (css &&
|
|
18212
|
+
if (css && readFileSync4(css, "utf8").includes("labsui.css"))
|
|
18200
18213
|
add("styles", "pass", `${shown} imports labsui.css`);
|
|
18201
18214
|
else
|
|
18202
18215
|
add("styles", "fail", css ? `${shown} does not import labsui.css.` : "No stylesheet imports Tailwind.", 'Run npx @kznlabs/labsui init, or add @import "<path>/styles/labsui.css"; after @import "tailwindcss".');
|
|
18203
18216
|
}
|
|
18204
18217
|
const sources = sourceFiles(cwd === base ? cwd : base).concat(cwd === base ? [] : sourceFiles(join4(cwd, "app"))).filter((file) => !owned.has(file));
|
|
18205
|
-
const texts = sources.map((source) =>
|
|
18218
|
+
const texts = sources.map((source) => readFileSync4(source, "utf8"));
|
|
18206
18219
|
const used = (needle) => texts.some((text) => text.includes(needle));
|
|
18207
18220
|
for (const [needle, label, fix] of [
|
|
18208
18221
|
[
|
|
@@ -18235,18 +18248,23 @@ async function doctor(cwd, registryFlag) {
|
|
|
18235
18248
|
add("packages", "fail", `Missing npm packages: ${missing.map(packageName).join(", ")}`, installCommand(cwd, missing).join(" "));
|
|
18236
18249
|
else if (items.length)
|
|
18237
18250
|
add("packages", "pass", "npm packages installed");
|
|
18238
|
-
const
|
|
18239
|
-
const
|
|
18251
|
+
const alias = config.alias.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
18252
|
+
const local = new RegExp(`from "(?:${alias}((?:components|lib)/[\\w/-]+)|\\./([\\w-]+))"`, "g");
|
|
18253
|
+
const resolves = (path) => [".ts", ".tsx"].some((ext) => existsSync5(path + ext));
|
|
18254
|
+
const broken = items.flatMap(({ item, files }) => files.filter((file) => existsSync5(file)).flatMap((file) => [...readFileSync4(file, "utf8").matchAll(local)].flatMap(([, fromAlias, sibling]) => {
|
|
18255
|
+
const path = fromAlias ? join4(base, fromAlias) : join4(file, "..", sibling ?? "");
|
|
18256
|
+
return resolves(path) ? [] : [`${item.name} → ${path.split(/[\\/]/).pop()}`];
|
|
18257
|
+
})));
|
|
18240
18258
|
if (broken.length)
|
|
18241
18259
|
add("files", "fail", `Missing files: ${broken.join(", ")}`, `npx @kznlabs/labsui add ${[...new Set(broken.map((line) => line.split(" → ")[1]))].join(" ")}`);
|
|
18242
|
-
const unresolved = items.flatMap(({ files }) => files).filter((file) => existsSync5(file) && hasConflictMarkers(
|
|
18260
|
+
const unresolved = items.flatMap(({ files }) => files).filter((file) => existsSync5(file) && hasConflictMarkers(readFileSync4(file, "utf8"))).map((file) => relative4(cwd, file).replaceAll("\\", "/"));
|
|
18243
18261
|
if (unresolved.length)
|
|
18244
18262
|
add("merge", "fail", `Unresolved update conflicts in ${unresolved.join(", ")}`, "Pick between the <<<<<<< yours and >>>>>>> labsui sides, then delete the markers.");
|
|
18245
18263
|
const drift = (code) => items.filter(({ item, files }) => item.files.some((file, i) => {
|
|
18246
18264
|
const local = files[i];
|
|
18247
18265
|
if (!local || !existsSync5(local))
|
|
18248
18266
|
return false;
|
|
18249
|
-
const yours =
|
|
18267
|
+
const yours = readFileSync4(local, "utf8");
|
|
18250
18268
|
const theirs = rewriteAlias(file.content, config.alias);
|
|
18251
18269
|
const norm = (text) => text.replace(/\r\n/g, `
|
|
18252
18270
|
`).trimEnd();
|
|
@@ -18281,7 +18299,7 @@ ${result.ok ? green(bold("Setup OK")) : red(bold(`${fails} problem(s)`))}${warns
|
|
|
18281
18299
|
}
|
|
18282
18300
|
|
|
18283
18301
|
// src/commands/init.ts
|
|
18284
|
-
import { readFileSync as
|
|
18302
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
18285
18303
|
import { dirname as dirname2, join as join5, relative as relative5 } from "node:path";
|
|
18286
18304
|
function missingPeers(cwd, warnings) {
|
|
18287
18305
|
const pkg = readPackage(cwd);
|
|
@@ -18304,7 +18322,7 @@ function wireStylesheet(cwd, root, opts, warnings) {
|
|
|
18304
18322
|
let spec = relative5(dirname2(css), join5(cwd, root, "styles/labsui.css")).replaceAll("\\", "/");
|
|
18305
18323
|
if (!spec.startsWith("."))
|
|
18306
18324
|
spec = `./${spec}`;
|
|
18307
|
-
const text =
|
|
18325
|
+
const text = readFileSync5(css, "utf8");
|
|
18308
18326
|
const clashes = [
|
|
18309
18327
|
...new Set([
|
|
18310
18328
|
...text.matchAll(/(--(?:background|foreground|primary|border|radius))\s*:|(font-family)\s*:/g)
|
|
@@ -18475,7 +18493,7 @@ ${dim(`labsui view ${hits[0]?.name} shows props and an example.`)}`);
|
|
|
18475
18493
|
}
|
|
18476
18494
|
|
|
18477
18495
|
// src/commands/update.ts
|
|
18478
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as
|
|
18496
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
18479
18497
|
import { dirname as dirname3, relative as relative6 } from "node:path";
|
|
18480
18498
|
var jsrBase = async (version) => {
|
|
18481
18499
|
try {
|
|
@@ -18505,7 +18523,7 @@ async function update(cwd, config, names, opts, loadBase = jsrBase) {
|
|
|
18505
18523
|
for (const { item, files } of installed) {
|
|
18506
18524
|
const shown = (file) => relative6(cwd, file).replaceAll("\\", "/");
|
|
18507
18525
|
const next = item.files.map((file) => norm(rewriteAlias(file.content, config.alias)));
|
|
18508
|
-
const local = files.map((file) => existsSync6(file) ? norm(
|
|
18526
|
+
const local = files.map((file) => existsSync6(file) ? norm(readFileSync6(file, "utf8")) : undefined);
|
|
18509
18527
|
if (local.every((line, i) => line === next[i])) {
|
|
18510
18528
|
items.push({ name: item.name, status: "current", files: [], conflicts: 0 });
|
|
18511
18529
|
continue;
|
|
@@ -18783,7 +18801,7 @@ var TOOLS = [
|
|
|
18783
18801
|
{
|
|
18784
18802
|
name: "add",
|
|
18785
18803
|
title: "Add labsUI items",
|
|
18786
|
-
description: "Install labsUI items into the project: copies each item and everything it imports, then installs their npm packages. Existing files are kept, never overwritten. Returns the files written, the packages installed and anything to mount once in the root layout. Needs init first; dryRun previews. To bring installed items to a newer version, use update instead.",
|
|
18804
|
+
description: "Install labsUI items into the project: copies each item and everything it imports, then installs their npm packages. Existing files are kept, never overwritten. Returns the files written, the packages installed, dependencies kept that differ from this version (call update on them) and anything to mount once in the root layout. Needs init first; dryRun previews. To bring installed items to a newer version, use update instead.",
|
|
18787
18805
|
inputSchema: {
|
|
18788
18806
|
type: "object",
|
|
18789
18807
|
properties: { names, dryRun },
|
|
@@ -18804,7 +18822,7 @@ var TOOLS = [
|
|
|
18804
18822
|
{
|
|
18805
18823
|
name: "diff",
|
|
18806
18824
|
title: "Diff installed labsUI items",
|
|
18807
|
-
description: "Compare installed labsUI items with this labsUI version. Returns a unified patch per changed file, from the registry file to the project file (+ lines exist only in the project), with formatting: true when only blank lines or indentation differ; matching items are left out, and when nothing differs it says so. Use it to see local edits before update. Needs init.",
|
|
18825
|
+
description: "Compare installed labsUI items with this labsUI version. Returns a unified patch per changed file, from the registry file to the project file (+ lines exist only in the project), with formatting: true when only blank lines or indentation differ; matching items are left out, a named item that isn't installed comes back with installed: false, and when nothing differs it says so. Use it to see local edits before update. Needs init.",
|
|
18808
18826
|
inputSchema: {
|
|
18809
18827
|
type: "object",
|
|
18810
18828
|
properties: { names: installed }
|
|
@@ -18866,6 +18884,9 @@ async function callTool(ctx, name, args) {
|
|
|
18866
18884
|
const lines = [
|
|
18867
18885
|
`${added.dryRun ? "Would write" : "Wrote"} ${added.written.length} file(s)${added.written.length ? `: ${added.written.join(", ")}` : ""}.`,
|
|
18868
18886
|
...added.kept.length ? [`Kept existing: ${added.kept.join(", ")}.`] : [],
|
|
18887
|
+
...added.outdated.length ? [
|
|
18888
|
+
`Dependencies already here differ from this version (older or edited): ${added.outdated.join(", ")}. The new items may not compile against them; call update with these names.`
|
|
18889
|
+
] : [],
|
|
18869
18890
|
...added.installCommand ? [`Install: ${added.installCommand}`] : added.packages.length ? [`Installed ${added.packages.join(", ")}.`] : [],
|
|
18870
18891
|
...added.mounts.map((mount) => `Mount once in the root layout, inside <MotionProvider>: ${mount.tag} from "${mount.from}".`)
|
|
18871
18892
|
];
|
|
@@ -18890,9 +18911,9 @@ async function callTool(ctx, name, args) {
|
|
|
18890
18911
|
if (!config)
|
|
18891
18912
|
throw new CliError("labsUI is not set up here. Call the init tool first.");
|
|
18892
18913
|
const items = await diff(ctx.cwd, { ...config, ...ctx.registry && { registry: ctx.registry } }, list(args.names), ctx.version);
|
|
18893
|
-
const body = items.length ? items.flatMap((i) => i.files.map((file) => `\`\`\`diff
|
|
18914
|
+
const body = items.length ? items.flatMap((i) => i.installed ? i.files.map((file) => `\`\`\`diff
|
|
18894
18915
|
${file.patch}
|
|
18895
|
-
\`\`\``)).join(`
|
|
18916
|
+
\`\`\``) : [`${i.name} is not installed; add installs it.`]).join(`
|
|
18896
18917
|
|
|
18897
18918
|
`) : "Installed items match this labsui version.";
|
|
18898
18919
|
return text(body, { items });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kznlabs/labsui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "Add labsUI components to your React + Tailwind v4 project: clean primitives you own, with smooth spring motion.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"mcpName": "io.github.kznlabs/labsui",
|