lorre-blocks 0.1.1 → 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/dist/index.js +334 -109
- package/package.json +12 -7
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
|
|
|
@@ -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";
|
|
@@ -181,34 +89,34 @@ async function installDependencies(cwd, pm, deps) {
|
|
|
181
89
|
}
|
|
182
90
|
|
|
183
91
|
// src/utils/paths.ts
|
|
184
|
-
import { promises as
|
|
185
|
-
import
|
|
92
|
+
import { promises as fs3 } from "fs";
|
|
93
|
+
import path3 from "path";
|
|
186
94
|
async function resolveBaseDir(cwd) {
|
|
187
95
|
for (const file of ["tsconfig.json", "jsconfig.json"]) {
|
|
188
96
|
try {
|
|
189
|
-
const raw = await
|
|
97
|
+
const raw = await fs3.readFile(path3.join(cwd, file), "utf8");
|
|
190
98
|
const json = JSON.parse(stripJsonComments(raw));
|
|
191
99
|
const paths = json?.compilerOptions?.paths;
|
|
192
100
|
const baseUrl = json?.compilerOptions?.baseUrl ?? ".";
|
|
193
101
|
const target = paths?.["@/*"]?.[0];
|
|
194
102
|
if (target) {
|
|
195
103
|
const dir = target.replace(/\/\*$/, "");
|
|
196
|
-
return
|
|
104
|
+
return path3.resolve(cwd, baseUrl, dir);
|
|
197
105
|
}
|
|
198
106
|
} catch {
|
|
199
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) {
|
|
@@ -244,7 +152,211 @@ function stripJsonComments(input) {
|
|
|
244
152
|
return input.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1").replace(/,(\s*[}\]])/g, "$1");
|
|
245
153
|
}
|
|
246
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
|
+
|
|
247
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";
|
|
248
360
|
async function runAdd(options) {
|
|
249
361
|
const { cwd, components } = options;
|
|
250
362
|
p2.intro(color2.bgCyan(color2.black(" lorre-blocks add ")));
|
|
@@ -259,17 +371,17 @@ async function runAdd(options) {
|
|
|
259
371
|
p2.cancel("Specify at least one component, e.g. `lorre-blocks add button`.");
|
|
260
372
|
process.exit(1);
|
|
261
373
|
}
|
|
262
|
-
const
|
|
263
|
-
|
|
374
|
+
const spinner3 = p2.spinner();
|
|
375
|
+
spinner3.start("Resolving components from the registry");
|
|
264
376
|
let tree;
|
|
265
377
|
try {
|
|
266
378
|
tree = await resolveTree(config.registry, components);
|
|
267
379
|
} catch (err) {
|
|
268
|
-
|
|
380
|
+
spinner3.stop("Failed to resolve components.");
|
|
269
381
|
p2.cancel(err.message);
|
|
270
382
|
process.exit(1);
|
|
271
383
|
}
|
|
272
|
-
|
|
384
|
+
spinner3.stop(
|
|
273
385
|
`Resolved ${tree.length} item(s): ${tree.map((t) => t.name).join(", ")}`
|
|
274
386
|
);
|
|
275
387
|
const npmDeps = [...new Set(tree.flatMap((t) => t.dependencies ?? []))];
|
|
@@ -324,9 +436,116 @@ async function fileExists2(f) {
|
|
|
324
436
|
}
|
|
325
437
|
}
|
|
326
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
|
+
|
|
327
546
|
// src/index.ts
|
|
328
547
|
var program = new Command();
|
|
329
|
-
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");
|
|
330
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) => {
|
|
331
550
|
await runInit({
|
|
332
551
|
cwd: opts.cwd,
|
|
@@ -342,6 +561,12 @@ program.command("add").description("Add one or more components to your project."
|
|
|
342
561
|
overwrite: opts.overwrite
|
|
343
562
|
});
|
|
344
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
|
+
});
|
|
345
570
|
program.parseAsync(process.argv).catch((err) => {
|
|
346
571
|
console.error(err);
|
|
347
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",
|
|
@@ -33,11 +29,20 @@
|
|
|
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
|
+
}
|