lorre-blocks 0.1.0 → 0.2.0
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 +1 -1
- package/dist/index.js +337 -115
- package/package.json +14 -9
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# lorre-blocks
|
|
2
2
|
|
|
3
|
-
CLI for [lorre-blocks](https://github.com/
|
|
3
|
+
CLI for [lorre-blocks](https://github.com/odama-dev/lorre-blocks) — a shadcn-style
|
|
4
4
|
component library. Instead of installing a package and importing from `node_modules`,
|
|
5
5
|
this CLI **copies component source directly into your project**, so you own and can
|
|
6
6
|
edit every line.
|
package/dist/index.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/init.ts
|
|
7
|
-
import { promises as
|
|
8
|
-
import
|
|
7
|
+
import { promises as fs4 } from "fs";
|
|
8
|
+
import path4 from "path";
|
|
9
9
|
import * as p from "@clack/prompts";
|
|
10
10
|
import color from "picocolors";
|
|
11
11
|
|
|
@@ -13,7 +13,7 @@ import color from "picocolors";
|
|
|
13
13
|
import { promises as fs } from "fs";
|
|
14
14
|
import path from "path";
|
|
15
15
|
var CONFIG_FILE = "components.json";
|
|
16
|
-
var DEFAULT_REGISTRY = "
|
|
16
|
+
var DEFAULT_REGISTRY = "https://lorre-blocks.vercel.app";
|
|
17
17
|
var DEFAULT_ALIASES = {
|
|
18
18
|
components: "@/components",
|
|
19
19
|
ui: "@/components/ui",
|
|
@@ -48,104 +48,12 @@ async function writeConfig(cwd, config) {
|
|
|
48
48
|
);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
// src/commands/init.ts
|
|
52
|
-
async function runInit(options) {
|
|
53
|
-
const { cwd } = options;
|
|
54
|
-
p.intro(color.bgCyan(color.black(" lorre-blocks init ")));
|
|
55
|
-
if (await configExists(cwd)) {
|
|
56
|
-
const overwrite = options.yes ? true : await p.confirm({
|
|
57
|
-
message: "components.json already exists. Overwrite it?",
|
|
58
|
-
initialValue: false
|
|
59
|
-
});
|
|
60
|
-
if (p.isCancel(overwrite) || !overwrite) {
|
|
61
|
-
p.cancel("Init aborted; existing components.json kept.");
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
const tsx = await fileExists(path2.join(cwd, "tsconfig.json"));
|
|
66
|
-
const registry = options.registry ?? (options.yes ? DEFAULT_REGISTRY : await promptText(
|
|
67
|
-
"Registry URL",
|
|
68
|
-
DEFAULT_REGISTRY
|
|
69
|
-
));
|
|
70
|
-
const config = {
|
|
71
|
-
$schema: "https://lorre-blocks.dev/schema.json",
|
|
72
|
-
registry,
|
|
73
|
-
tsx,
|
|
74
|
-
aliases: { ...DEFAULT_ALIASES }
|
|
75
|
-
};
|
|
76
|
-
await writeConfig(cwd, config);
|
|
77
|
-
p.outro(
|
|
78
|
-
`${color.green("\u2714")} Wrote components.json. Now run ${color.cyan(
|
|
79
|
-
"lorre-blocks add button"
|
|
80
|
-
)}.`
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
async function promptText(message, initial) {
|
|
84
|
-
const value = await p.text({ message, placeholder: initial, defaultValue: initial });
|
|
85
|
-
if (p.isCancel(value)) {
|
|
86
|
-
p.cancel("Init cancelled.");
|
|
87
|
-
process.exit(0);
|
|
88
|
-
}
|
|
89
|
-
return value || initial;
|
|
90
|
-
}
|
|
91
|
-
async function fileExists(f) {
|
|
92
|
-
try {
|
|
93
|
-
await fs2.access(f);
|
|
94
|
-
return true;
|
|
95
|
-
} catch {
|
|
96
|
-
return false;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// src/commands/add.ts
|
|
101
|
-
import { promises as fs5 } from "fs";
|
|
102
|
-
import path5 from "path";
|
|
103
|
-
import * as p2 from "@clack/prompts";
|
|
104
|
-
import color2 from "picocolors";
|
|
105
|
-
|
|
106
|
-
// src/utils/registry.ts
|
|
107
|
-
async function fetchRegistryItem(registry, name) {
|
|
108
|
-
const base = registry.replace(/\/$/, "");
|
|
109
|
-
const url = `${base}/r/${name}.json`;
|
|
110
|
-
let res;
|
|
111
|
-
try {
|
|
112
|
-
res = await fetch(url);
|
|
113
|
-
} catch (err) {
|
|
114
|
-
throw new Error(
|
|
115
|
-
`Could not reach the registry at ${url}. Is it running / correct?
|
|
116
|
-
${err.message}`
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
if (res.status === 404) {
|
|
120
|
-
throw new Error(`Component "${name}" was not found in the registry (${url}).`);
|
|
121
|
-
}
|
|
122
|
-
if (!res.ok) {
|
|
123
|
-
throw new Error(`Failed to fetch ${url} (HTTP ${res.status}).`);
|
|
124
|
-
}
|
|
125
|
-
return await res.json();
|
|
126
|
-
}
|
|
127
|
-
async function resolveTree(registry, names) {
|
|
128
|
-
const resolved = /* @__PURE__ */ new Map();
|
|
129
|
-
async function visit(name) {
|
|
130
|
-
if (resolved.has(name)) return;
|
|
131
|
-
const item = await fetchRegistryItem(registry, name);
|
|
132
|
-
for (const dep of item.registryDependencies ?? []) {
|
|
133
|
-
await visit(dep);
|
|
134
|
-
}
|
|
135
|
-
if (!resolved.has(name)) resolved.set(name, item);
|
|
136
|
-
}
|
|
137
|
-
for (const name of names) {
|
|
138
|
-
await visit(name);
|
|
139
|
-
}
|
|
140
|
-
return [...resolved.values()];
|
|
141
|
-
}
|
|
142
|
-
|
|
143
51
|
// src/utils/package-manager.ts
|
|
144
|
-
import { promises as
|
|
145
|
-
import
|
|
52
|
+
import { promises as fs2 } from "fs";
|
|
53
|
+
import path2 from "path";
|
|
146
54
|
import { spawn } from "child_process";
|
|
147
55
|
async function detectPackageManager(cwd) {
|
|
148
|
-
const has = async (f) =>
|
|
56
|
+
const has = async (f) => fs2.access(path2.join(cwd, f)).then(() => true).catch(() => false);
|
|
149
57
|
if (await has("pnpm-lock.yaml")) return "pnpm";
|
|
150
58
|
if (await has("yarn.lock")) return "yarn";
|
|
151
59
|
if (await has("bun.lockb")) return "bun";
|
|
@@ -170,7 +78,6 @@ async function installDependencies(cwd, pm, deps) {
|
|
|
170
78
|
const child = spawn(pm, args, {
|
|
171
79
|
cwd,
|
|
172
80
|
stdio: "inherit",
|
|
173
|
-
// On Windows, package managers are .cmd shims that need a shell.
|
|
174
81
|
shell: process.platform === "win32"
|
|
175
82
|
});
|
|
176
83
|
child.on("error", reject);
|
|
@@ -182,33 +89,34 @@ async function installDependencies(cwd, pm, deps) {
|
|
|
182
89
|
}
|
|
183
90
|
|
|
184
91
|
// src/utils/paths.ts
|
|
185
|
-
import { promises as
|
|
186
|
-
import
|
|
92
|
+
import { promises as fs3 } from "fs";
|
|
93
|
+
import path3 from "path";
|
|
187
94
|
async function resolveBaseDir(cwd) {
|
|
188
95
|
for (const file of ["tsconfig.json", "jsconfig.json"]) {
|
|
189
96
|
try {
|
|
190
|
-
const raw = await
|
|
97
|
+
const raw = await fs3.readFile(path3.join(cwd, file), "utf8");
|
|
191
98
|
const json = JSON.parse(stripJsonComments(raw));
|
|
192
99
|
const paths = json?.compilerOptions?.paths;
|
|
193
100
|
const baseUrl = json?.compilerOptions?.baseUrl ?? ".";
|
|
194
101
|
const target = paths?.["@/*"]?.[0];
|
|
195
102
|
if (target) {
|
|
196
103
|
const dir = target.replace(/\/\*$/, "");
|
|
197
|
-
return
|
|
104
|
+
return path3.resolve(cwd, baseUrl, dir);
|
|
198
105
|
}
|
|
199
106
|
} catch {
|
|
107
|
+
continue;
|
|
200
108
|
}
|
|
201
109
|
}
|
|
202
110
|
try {
|
|
203
|
-
const srcStat = await
|
|
204
|
-
if (srcStat.isDirectory()) return
|
|
111
|
+
const srcStat = await fs3.stat(path3.join(cwd, "src"));
|
|
112
|
+
if (srcStat.isDirectory()) return path3.join(cwd, "src");
|
|
205
113
|
} catch {
|
|
206
114
|
}
|
|
207
115
|
return cwd;
|
|
208
116
|
}
|
|
209
117
|
function aliasToDir(alias, baseDir) {
|
|
210
118
|
const rel = alias.replace(/^@\//, "");
|
|
211
|
-
return
|
|
119
|
+
return path3.join(baseDir, rel);
|
|
212
120
|
}
|
|
213
121
|
function targetDirForType(type, aliases, baseDir) {
|
|
214
122
|
switch (type) {
|
|
@@ -232,10 +140,7 @@ function rewriteImports(content, config) {
|
|
|
232
140
|
let result = content;
|
|
233
141
|
for (const [from, to] of replacements) {
|
|
234
142
|
if (from === to) continue;
|
|
235
|
-
const pattern = new RegExp(
|
|
236
|
-
`(["'\`])${escapeRegExp(from)}(?=["'\`/])`,
|
|
237
|
-
"g"
|
|
238
|
-
);
|
|
143
|
+
const pattern = new RegExp(`(["'\`])${escapeRegExp(from)}(?=["'\`/])`, "g");
|
|
239
144
|
result = result.replace(pattern, `$1${to}`);
|
|
240
145
|
}
|
|
241
146
|
return result;
|
|
@@ -247,7 +152,211 @@ function stripJsonComments(input) {
|
|
|
247
152
|
return input.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1").replace(/,(\s*[}\]])/g, "$1");
|
|
248
153
|
}
|
|
249
154
|
|
|
155
|
+
// src/utils/registry.ts
|
|
156
|
+
async function fetchRegistryItem(registry, name) {
|
|
157
|
+
const base = registry.replace(/\/$/, "");
|
|
158
|
+
const url = `${base}/r/${name}.json`;
|
|
159
|
+
let res;
|
|
160
|
+
try {
|
|
161
|
+
res = await fetch(url);
|
|
162
|
+
} catch (err) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
`Could not reach the registry at ${url}. Is it running / correct?
|
|
165
|
+
${err.message}`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
if (res.status === 404) {
|
|
169
|
+
throw new Error(`Component "${name}" was not found in the registry (${url}).`);
|
|
170
|
+
}
|
|
171
|
+
if (!res.ok) {
|
|
172
|
+
throw new Error(`Failed to fetch ${url} (HTTP ${res.status}).`);
|
|
173
|
+
}
|
|
174
|
+
return await res.json();
|
|
175
|
+
}
|
|
176
|
+
async function fetchIndex(registry) {
|
|
177
|
+
const base = registry.replace(/\/$/, "");
|
|
178
|
+
const url = `${base}/r/index.json`;
|
|
179
|
+
const res = await fetch(url);
|
|
180
|
+
if (!res.ok) throw new Error(`Failed to fetch ${url} (HTTP ${res.status}).`);
|
|
181
|
+
return await res.json();
|
|
182
|
+
}
|
|
183
|
+
async function fetchTheme(registry) {
|
|
184
|
+
const base = registry.replace(/\/$/, "");
|
|
185
|
+
const url = `${base}/r/theme.json`;
|
|
186
|
+
const res = await fetch(url);
|
|
187
|
+
if (!res.ok) throw new Error(`Failed to fetch ${url} (HTTP ${res.status}).`);
|
|
188
|
+
const data = await res.json();
|
|
189
|
+
return data.css;
|
|
190
|
+
}
|
|
191
|
+
async function resolveTree(registry, names) {
|
|
192
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
193
|
+
async function visit(name) {
|
|
194
|
+
if (resolved.has(name)) return;
|
|
195
|
+
const item = await fetchRegistryItem(registry, name);
|
|
196
|
+
for (const dep of item.registryDependencies ?? []) {
|
|
197
|
+
await visit(dep);
|
|
198
|
+
}
|
|
199
|
+
if (!resolved.has(name)) resolved.set(name, item);
|
|
200
|
+
}
|
|
201
|
+
for (const name of names) {
|
|
202
|
+
await visit(name);
|
|
203
|
+
}
|
|
204
|
+
return [...resolved.values()];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// src/utils/css.ts
|
|
208
|
+
var THEME_START = "/* lorre-blocks theme start */";
|
|
209
|
+
var THEME_END = "/* lorre-blocks theme end */";
|
|
210
|
+
var TAILWIND_IMPORT = '@import "tailwindcss";';
|
|
211
|
+
function hasTheme(cssContent) {
|
|
212
|
+
return cssContent.includes(THEME_START);
|
|
213
|
+
}
|
|
214
|
+
function injectThemeBlock(cssContent, themeCss) {
|
|
215
|
+
const block = `${THEME_START}
|
|
216
|
+
${themeCss.trim()}
|
|
217
|
+
${THEME_END}`;
|
|
218
|
+
if (hasTheme(cssContent)) {
|
|
219
|
+
return cssContent.replace(
|
|
220
|
+
new RegExp(
|
|
221
|
+
`${escapeRegExp2(THEME_START)}[\\s\\S]*?${escapeRegExp2(THEME_END)}`
|
|
222
|
+
),
|
|
223
|
+
block
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
const trimmed = cssContent.trimEnd();
|
|
227
|
+
if (trimmed.includes(TAILWIND_IMPORT)) {
|
|
228
|
+
const lines = trimmed.split("\n");
|
|
229
|
+
const idx = lines.findIndex((l) => l.trim() === TAILWIND_IMPORT);
|
|
230
|
+
lines.splice(idx + 1, 0, "", block);
|
|
231
|
+
return lines.join("\n") + "\n";
|
|
232
|
+
}
|
|
233
|
+
const prefix = trimmed.length > 0 ? `${trimmed}
|
|
234
|
+
|
|
235
|
+
` : "";
|
|
236
|
+
return `${TAILWIND_IMPORT}
|
|
237
|
+
|
|
238
|
+
${prefix}${block}
|
|
239
|
+
`;
|
|
240
|
+
}
|
|
241
|
+
function escapeRegExp2(s) {
|
|
242
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// src/commands/init.ts
|
|
246
|
+
var BASE_DEPENDENCIES = [
|
|
247
|
+
"clsx",
|
|
248
|
+
"tailwind-merge",
|
|
249
|
+
"class-variance-authority",
|
|
250
|
+
"tw-animate-css"
|
|
251
|
+
];
|
|
252
|
+
var GLOBAL_CSS_CANDIDATES = [
|
|
253
|
+
"src/app/globals.css",
|
|
254
|
+
"app/globals.css",
|
|
255
|
+
"src/index.css",
|
|
256
|
+
"src/styles/globals.css",
|
|
257
|
+
"styles/globals.css"
|
|
258
|
+
];
|
|
259
|
+
async function runInit(options) {
|
|
260
|
+
const { cwd } = options;
|
|
261
|
+
p.intro(color.bgCyan(color.black(" lorre-blocks init ")));
|
|
262
|
+
if (await configExists(cwd)) {
|
|
263
|
+
const overwrite = options.yes ? true : await p.confirm({
|
|
264
|
+
message: "components.json already exists. Overwrite it?",
|
|
265
|
+
initialValue: false
|
|
266
|
+
});
|
|
267
|
+
if (p.isCancel(overwrite) || !overwrite) {
|
|
268
|
+
p.cancel("Init aborted; existing components.json kept.");
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const tsx = await fileExists(path4.join(cwd, "tsconfig.json"));
|
|
273
|
+
const registry = options.registry ?? (options.yes ? DEFAULT_REGISTRY : await promptText("Registry URL", DEFAULT_REGISTRY));
|
|
274
|
+
const config = {
|
|
275
|
+
$schema: "https://lorre-blocks.dev/schema.json",
|
|
276
|
+
registry,
|
|
277
|
+
tsx,
|
|
278
|
+
aliases: { ...DEFAULT_ALIASES }
|
|
279
|
+
};
|
|
280
|
+
await writeConfig(cwd, config);
|
|
281
|
+
p.log.success("Wrote components.json");
|
|
282
|
+
const pm = await detectPackageManager(cwd);
|
|
283
|
+
const depSpinner = p.spinner();
|
|
284
|
+
depSpinner.start(`Installing base dependencies with ${pm}`);
|
|
285
|
+
try {
|
|
286
|
+
await installDependencies(cwd, pm, BASE_DEPENDENCIES);
|
|
287
|
+
depSpinner.stop(`Installed: ${BASE_DEPENDENCIES.join(", ")}`);
|
|
288
|
+
} catch (err) {
|
|
289
|
+
depSpinner.stop("Dependency install failed.");
|
|
290
|
+
p.cancel(err.message);
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
const baseDir = await resolveBaseDir(cwd);
|
|
294
|
+
try {
|
|
295
|
+
const utils = await fetchRegistryItem(registry, "utils");
|
|
296
|
+
for (const file of utils.files) {
|
|
297
|
+
const dir = targetDirForType(file.type, config.aliases, baseDir);
|
|
298
|
+
await fs4.mkdir(dir, { recursive: true });
|
|
299
|
+
const dest = path4.join(dir, path4.basename(file.path));
|
|
300
|
+
await fs4.writeFile(dest, rewriteImports(file.content, config), "utf8");
|
|
301
|
+
p.log.success(`Wrote ${path4.relative(cwd, dest)}`);
|
|
302
|
+
}
|
|
303
|
+
} catch (err) {
|
|
304
|
+
p.log.warn(`Could not write utils: ${err.message}`);
|
|
305
|
+
}
|
|
306
|
+
try {
|
|
307
|
+
const themeCss = await fetchTheme(registry);
|
|
308
|
+
const cssPath = await resolveGlobalCss(cwd);
|
|
309
|
+
const existing = await readIfExists(cssPath);
|
|
310
|
+
const next = injectThemeBlock(existing ?? "", themeCss);
|
|
311
|
+
await fs4.mkdir(path4.dirname(cssPath), { recursive: true });
|
|
312
|
+
await fs4.writeFile(cssPath, next, "utf8");
|
|
313
|
+
p.log.success(
|
|
314
|
+
`${existing === null ? "Created" : "Updated"} ${path4.relative(cwd, cssPath)} with the theme`
|
|
315
|
+
);
|
|
316
|
+
} catch (err) {
|
|
317
|
+
p.log.warn(`Could not set up the theme: ${err.message}`);
|
|
318
|
+
}
|
|
319
|
+
p.outro(
|
|
320
|
+
`${color.green("\u2714")} Ready. Now run ${color.cyan("lorre-blocks add button")}.`
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
async function resolveGlobalCss(cwd) {
|
|
324
|
+
for (const candidate of GLOBAL_CSS_CANDIDATES) {
|
|
325
|
+
if (await fileExists(path4.join(cwd, candidate))) {
|
|
326
|
+
return path4.join(cwd, candidate);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return path4.join(cwd, GLOBAL_CSS_CANDIDATES[0]);
|
|
330
|
+
}
|
|
331
|
+
async function readIfExists(file) {
|
|
332
|
+
try {
|
|
333
|
+
return await fs4.readFile(file, "utf8");
|
|
334
|
+
} catch {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
async function promptText(message, initial) {
|
|
339
|
+
const value = await p.text({ message, placeholder: initial, defaultValue: initial });
|
|
340
|
+
if (p.isCancel(value)) {
|
|
341
|
+
p.cancel("Init cancelled.");
|
|
342
|
+
process.exit(0);
|
|
343
|
+
}
|
|
344
|
+
return value || initial;
|
|
345
|
+
}
|
|
346
|
+
async function fileExists(f) {
|
|
347
|
+
try {
|
|
348
|
+
await fs4.access(f);
|
|
349
|
+
return true;
|
|
350
|
+
} catch {
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
250
355
|
// src/commands/add.ts
|
|
356
|
+
import { promises as fs5 } from "fs";
|
|
357
|
+
import path5 from "path";
|
|
358
|
+
import * as p2 from "@clack/prompts";
|
|
359
|
+
import color2 from "picocolors";
|
|
251
360
|
async function runAdd(options) {
|
|
252
361
|
const { cwd, components } = options;
|
|
253
362
|
p2.intro(color2.bgCyan(color2.black(" lorre-blocks add ")));
|
|
@@ -262,17 +371,17 @@ async function runAdd(options) {
|
|
|
262
371
|
p2.cancel("Specify at least one component, e.g. `lorre-blocks add button`.");
|
|
263
372
|
process.exit(1);
|
|
264
373
|
}
|
|
265
|
-
const
|
|
266
|
-
|
|
374
|
+
const spinner3 = p2.spinner();
|
|
375
|
+
spinner3.start("Resolving components from the registry");
|
|
267
376
|
let tree;
|
|
268
377
|
try {
|
|
269
378
|
tree = await resolveTree(config.registry, components);
|
|
270
379
|
} catch (err) {
|
|
271
|
-
|
|
380
|
+
spinner3.stop("Failed to resolve components.");
|
|
272
381
|
p2.cancel(err.message);
|
|
273
382
|
process.exit(1);
|
|
274
383
|
}
|
|
275
|
-
|
|
384
|
+
spinner3.stop(
|
|
276
385
|
`Resolved ${tree.length} item(s): ${tree.map((t) => t.name).join(", ")}`
|
|
277
386
|
);
|
|
278
387
|
const npmDeps = [...new Set(tree.flatMap((t) => t.dependencies ?? []))];
|
|
@@ -327,9 +436,116 @@ async function fileExists2(f) {
|
|
|
327
436
|
}
|
|
328
437
|
}
|
|
329
438
|
|
|
439
|
+
// src/commands/list.ts
|
|
440
|
+
import * as p3 from "@clack/prompts";
|
|
441
|
+
import color3 from "picocolors";
|
|
442
|
+
async function runList(options) {
|
|
443
|
+
const config = await readConfig(options.cwd);
|
|
444
|
+
const registry = options.registry ?? config?.registry ?? DEFAULT_REGISTRY;
|
|
445
|
+
p3.intro(color3.bgCyan(color3.black(" lorre-blocks list ")));
|
|
446
|
+
let items;
|
|
447
|
+
try {
|
|
448
|
+
items = await fetchIndex(registry);
|
|
449
|
+
} catch (err) {
|
|
450
|
+
p3.cancel(err.message);
|
|
451
|
+
process.exit(1);
|
|
452
|
+
}
|
|
453
|
+
const width = Math.max(...items.map((i) => i.name.length), 4);
|
|
454
|
+
const lines = items.map(
|
|
455
|
+
(i) => ` ${color3.green(i.name.padEnd(width))} ${color3.dim(i.description ?? "")}`
|
|
456
|
+
);
|
|
457
|
+
p3.log.message(`${items.length} component(s):
|
|
458
|
+
${lines.join("\n")}`);
|
|
459
|
+
p3.outro(`Add one with ${color3.cyan("lorre-blocks add <name>")}.`);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// src/commands/diff.ts
|
|
463
|
+
import { promises as fs6 } from "fs";
|
|
464
|
+
import path6 from "path";
|
|
465
|
+
import * as p4 from "@clack/prompts";
|
|
466
|
+
import color4 from "picocolors";
|
|
467
|
+
import { createTwoFilesPatch } from "diff";
|
|
468
|
+
async function runDiff(options) {
|
|
469
|
+
const { cwd } = options;
|
|
470
|
+
p4.intro(color4.bgCyan(color4.black(" lorre-blocks diff ")));
|
|
471
|
+
const config = await readConfig(cwd);
|
|
472
|
+
if (!config) {
|
|
473
|
+
p4.cancel("No components.json found. Run `lorre-blocks init` first.");
|
|
474
|
+
process.exit(1);
|
|
475
|
+
}
|
|
476
|
+
const baseDir = await resolveBaseDir(cwd);
|
|
477
|
+
let names = options.components;
|
|
478
|
+
if (names.length === 0) {
|
|
479
|
+
const uiDir = targetDirForType("registry:ui", config.aliases, baseDir);
|
|
480
|
+
const present = await listComponentNames(uiDir);
|
|
481
|
+
const index = await fetchIndex(config.registry).catch(() => []);
|
|
482
|
+
const known = new Set(index.map((i) => i.name));
|
|
483
|
+
names = present.filter((n) => known.has(n));
|
|
484
|
+
if (names.length === 0) {
|
|
485
|
+
p4.cancel("No known components found in your project to diff.");
|
|
486
|
+
process.exit(0);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
let changed = 0;
|
|
490
|
+
for (const name of names) {
|
|
491
|
+
const item = await fetchRegistryItem(config.registry, name);
|
|
492
|
+
for (const file of item.files) {
|
|
493
|
+
const dir = targetDirForType(file.type, config.aliases, baseDir);
|
|
494
|
+
const dest = path6.join(dir, path6.basename(file.path));
|
|
495
|
+
const local = await readIfExists2(dest);
|
|
496
|
+
const registryContent = rewriteImports(file.content, config);
|
|
497
|
+
if (local === null) {
|
|
498
|
+
p4.log.warn(`${name}: ${path6.relative(cwd, dest)} not added yet`);
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
if (local === registryContent) {
|
|
502
|
+
p4.log.success(`${name}: up to date`);
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
changed++;
|
|
506
|
+
const patch = createTwoFilesPatch(
|
|
507
|
+
"your version",
|
|
508
|
+
"registry",
|
|
509
|
+
local,
|
|
510
|
+
registryContent,
|
|
511
|
+
"",
|
|
512
|
+
""
|
|
513
|
+
);
|
|
514
|
+
p4.log.message(`${color4.bold(name)} \u2014 ${path6.relative(cwd, dest)}
|
|
515
|
+
${colorizePatch(patch)}`);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
p4.outro(
|
|
519
|
+
changed === 0 ? `${color4.green("\u2714")} Everything matches the registry.` : `${changed} file(s) differ from the registry.`
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
function colorizePatch(patch) {
|
|
523
|
+
return patch.split("\n").map((line) => {
|
|
524
|
+
if (line.startsWith("+") && !line.startsWith("+++")) return color4.green(line);
|
|
525
|
+
if (line.startsWith("-") && !line.startsWith("---")) return color4.red(line);
|
|
526
|
+
if (line.startsWith("@@")) return color4.cyan(line);
|
|
527
|
+
return color4.dim(line);
|
|
528
|
+
}).join("\n");
|
|
529
|
+
}
|
|
530
|
+
async function listComponentNames(dir) {
|
|
531
|
+
try {
|
|
532
|
+
const entries = await fs6.readdir(dir);
|
|
533
|
+
return entries.filter((f) => /\.(tsx|jsx|ts|js)$/.test(f)).map((f) => f.replace(/\.(tsx|jsx|ts|js)$/, ""));
|
|
534
|
+
} catch {
|
|
535
|
+
return [];
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
async function readIfExists2(file) {
|
|
539
|
+
try {
|
|
540
|
+
return await fs6.readFile(file, "utf8");
|
|
541
|
+
} catch {
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
330
546
|
// src/index.ts
|
|
331
547
|
var program = new Command();
|
|
332
|
-
program.name("lorre-blocks").description("Add lorre-blocks components to your project by copying their source in.").version("0.
|
|
548
|
+
program.name("lorre-blocks").description("Add lorre-blocks components to your project by copying their source in.").version("0.2.0");
|
|
333
549
|
program.command("init").description("Create a components.json config in your project.").option("-c, --cwd <path>", "working directory", process.cwd()).option("-r, --registry <url>", "registry base URL").option("-y, --yes", "skip prompts and accept defaults", false).action(async (opts) => {
|
|
334
550
|
await runInit({
|
|
335
551
|
cwd: opts.cwd,
|
|
@@ -345,6 +561,12 @@ program.command("add").description("Add one or more components to your project."
|
|
|
345
561
|
overwrite: opts.overwrite
|
|
346
562
|
});
|
|
347
563
|
});
|
|
564
|
+
program.command("list").description("List the components available in the registry.").option("-c, --cwd <path>", "working directory", process.cwd()).option("-r, --registry <url>", "registry base URL").action(async (opts) => {
|
|
565
|
+
await runList({ cwd: opts.cwd, registry: opts.registry });
|
|
566
|
+
});
|
|
567
|
+
program.command("diff").description("Show how your local components differ from the registry.").argument("[components...]", "component names; omit to diff all present").option("-c, --cwd <path>", "working directory", process.cwd()).action(async (components, opts) => {
|
|
568
|
+
await runDiff({ cwd: opts.cwd, components });
|
|
569
|
+
});
|
|
348
570
|
program.parseAsync(process.argv).catch((err) => {
|
|
349
571
|
console.error(err);
|
|
350
572
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lorre-blocks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Add lorre-blocks components to your project by copying their source in.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,10 +9,6 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"dist"
|
|
11
11
|
],
|
|
12
|
-
"scripts": {
|
|
13
|
-
"build": "tsup",
|
|
14
|
-
"dev": "tsup --watch"
|
|
15
|
-
},
|
|
16
12
|
"keywords": [
|
|
17
13
|
"cli",
|
|
18
14
|
"components",
|
|
@@ -23,21 +19,30 @@
|
|
|
23
19
|
"license": "MIT",
|
|
24
20
|
"repository": {
|
|
25
21
|
"type": "git",
|
|
26
|
-
"url": "git+https://github.com/
|
|
22
|
+
"url": "git+https://github.com/odama-dev/lorre-blocks.git",
|
|
27
23
|
"directory": "packages/cli"
|
|
28
24
|
},
|
|
29
|
-
"homepage": "https://github.com/
|
|
25
|
+
"homepage": "https://github.com/odama-dev/lorre-blocks#readme",
|
|
30
26
|
"publishConfig": {
|
|
31
27
|
"access": "public"
|
|
32
28
|
},
|
|
33
29
|
"dependencies": {
|
|
34
30
|
"@clack/prompts": "^0.9.1",
|
|
35
31
|
"commander": "^13.0.0",
|
|
32
|
+
"diff": "^9.0.0",
|
|
36
33
|
"picocolors": "^1.1.1"
|
|
37
34
|
},
|
|
38
35
|
"devDependencies": {
|
|
36
|
+
"@types/diff": "^7.0.0",
|
|
39
37
|
"@types/node": "^22.10.5",
|
|
40
38
|
"tsup": "^8.3.5",
|
|
41
|
-
"typescript": "^5.7.2"
|
|
39
|
+
"typescript": "^5.7.2",
|
|
40
|
+
"vitest": "^4.1.10"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsup",
|
|
44
|
+
"dev": "tsup --watch",
|
|
45
|
+
"test": "vitest run",
|
|
46
|
+
"typecheck": "tsc --noEmit"
|
|
42
47
|
}
|
|
43
|
-
}
|
|
48
|
+
}
|