foldcn 0.0.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/dist/bin.js +1126 -0
- package/package.json +34 -0
package/dist/bin.js
ADDED
|
@@ -0,0 +1,1126 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __returnValue = (v) => v;
|
|
4
|
+
function __exportSetter(name, newValue) {
|
|
5
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
+
}
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, {
|
|
10
|
+
get: all[name],
|
|
11
|
+
enumerable: true,
|
|
12
|
+
configurable: true,
|
|
13
|
+
set: __exportSetter.bind(all, name)
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// src/bin.ts
|
|
18
|
+
import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
|
|
19
|
+
import * as NodeServices from "@effect/platform-node/NodeServices";
|
|
20
|
+
import { Effect as Effect12 } from "effect";
|
|
21
|
+
|
|
22
|
+
// src/cli.ts
|
|
23
|
+
import { Effect as Effect11 } from "effect";
|
|
24
|
+
import { Command as Command4 } from "effect/unstable/cli";
|
|
25
|
+
|
|
26
|
+
// src/command/add/add.ts
|
|
27
|
+
import { Console, Effect as Effect8, FileSystem as FileSystem6, Option as Option3, Path as Path4 } from "effect";
|
|
28
|
+
import { Argument, Command, Flag } from "effect/unstable/cli";
|
|
29
|
+
|
|
30
|
+
// ../registry/dist/registry/index.js
|
|
31
|
+
var exports_registry = {};
|
|
32
|
+
__export(exports_registry, {
|
|
33
|
+
Manifest: () => Manifest,
|
|
34
|
+
ItemType: () => ItemType,
|
|
35
|
+
ItemFile: () => ItemFile,
|
|
36
|
+
Item: () => Item,
|
|
37
|
+
CssVars: () => CssVars,
|
|
38
|
+
Css: () => Css
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// ../registry/dist/registry/registry.js
|
|
42
|
+
import { Schema } from "effect";
|
|
43
|
+
var ItemType = Schema.Literals([
|
|
44
|
+
"registry:ui",
|
|
45
|
+
"registry:lib",
|
|
46
|
+
"registry:block",
|
|
47
|
+
"registry:theme",
|
|
48
|
+
"registry:file",
|
|
49
|
+
"registry:example"
|
|
50
|
+
]).annotate({ identifier: "Foldcn.Registry.ItemType" });
|
|
51
|
+
var ItemFile = Schema.Struct({
|
|
52
|
+
path: Schema.String,
|
|
53
|
+
type: ItemType,
|
|
54
|
+
target: Schema.optionalKey(Schema.String),
|
|
55
|
+
content: Schema.optionalKey(Schema.String)
|
|
56
|
+
}).annotate({ identifier: "Foldcn.Registry.ItemFile" });
|
|
57
|
+
var CssVars = Schema.Struct({
|
|
58
|
+
theme: Schema.optionalKey(Schema.Record(Schema.String, Schema.String)),
|
|
59
|
+
light: Schema.optionalKey(Schema.Record(Schema.String, Schema.String)),
|
|
60
|
+
dark: Schema.optionalKey(Schema.Record(Schema.String, Schema.String))
|
|
61
|
+
}).annotate({ identifier: "Foldcn.Registry.CssVars" });
|
|
62
|
+
var Css = Schema.Record(Schema.String, Schema.Union([Schema.String, Schema.suspend(() => Css)])).annotate({ identifier: "Foldcn.Registry.Css" });
|
|
63
|
+
var Item = Schema.Struct({
|
|
64
|
+
$schema: Schema.optionalKey(Schema.String),
|
|
65
|
+
name: Schema.String,
|
|
66
|
+
type: ItemType,
|
|
67
|
+
title: Schema.optionalKey(Schema.String),
|
|
68
|
+
description: Schema.optionalKey(Schema.String),
|
|
69
|
+
dependencies: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
70
|
+
devDependencies: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
71
|
+
registryDependencies: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
72
|
+
files: Schema.optionalKey(Schema.Array(ItemFile)),
|
|
73
|
+
cssVars: Schema.optionalKey(CssVars),
|
|
74
|
+
css: Schema.optionalKey(Css),
|
|
75
|
+
docs: Schema.optionalKey(Schema.String),
|
|
76
|
+
categories: Schema.optionalKey(Schema.Array(Schema.String))
|
|
77
|
+
}).annotate({ identifier: "Foldcn.Registry.Item" });
|
|
78
|
+
var Manifest = Schema.Struct({
|
|
79
|
+
$schema: Schema.optionalKey(Schema.String),
|
|
80
|
+
name: Schema.String,
|
|
81
|
+
homepage: Schema.String,
|
|
82
|
+
items: Schema.Array(Item)
|
|
83
|
+
}).annotate({ identifier: "Foldcn.Registry.Manifest" });
|
|
84
|
+
// ../registry/dist/componentsConfig/index.js
|
|
85
|
+
var exports_componentsConfig = {};
|
|
86
|
+
__export(exports_componentsConfig, {
|
|
87
|
+
defaultCss: () => defaultCss,
|
|
88
|
+
defaultBaseColor: () => defaultBaseColor,
|
|
89
|
+
defaultAliases: () => defaultAliases,
|
|
90
|
+
Config: () => Config,
|
|
91
|
+
Aliases: () => Aliases
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// ../registry/dist/componentsConfig/componentsConfig.js
|
|
95
|
+
import { Schema as Schema2 } from "effect";
|
|
96
|
+
var Aliases = Schema2.Struct({
|
|
97
|
+
ui: Schema2.String,
|
|
98
|
+
lib: Schema2.String
|
|
99
|
+
}).annotate({ identifier: "Foldcn.ComponentsConfig.Aliases" });
|
|
100
|
+
var Config = Schema2.Struct({
|
|
101
|
+
$schema: Schema2.optionalKey(Schema2.String),
|
|
102
|
+
css: Schema2.String,
|
|
103
|
+
baseColor: Schema2.String,
|
|
104
|
+
aliases: Aliases,
|
|
105
|
+
registries: Schema2.optionalKey(Schema2.Record(Schema2.String, Schema2.String))
|
|
106
|
+
}).annotate({ identifier: "Foldcn.ComponentsConfig.Config" });
|
|
107
|
+
var defaultCss = "src/styles.css";
|
|
108
|
+
var defaultBaseColor = "neutral";
|
|
109
|
+
var defaultAliases = {
|
|
110
|
+
ui: "src/components/ui",
|
|
111
|
+
lib: "src/lib"
|
|
112
|
+
};
|
|
113
|
+
// ../registry/dist/resolveTree/index.js
|
|
114
|
+
var exports_resolveTree = {};
|
|
115
|
+
__export(exports_resolveTree, {
|
|
116
|
+
resolve: () => resolve,
|
|
117
|
+
mergeItems: () => mergeItems
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// ../registry/dist/resolveTree/resolveTree.js
|
|
121
|
+
import { Effect, Option } from "effect";
|
|
122
|
+
var fetchConcurrency = 8;
|
|
123
|
+
var resolve = (rootSpecs, getItem) => Effect.gen(function* () {
|
|
124
|
+
const seenKeys = new Set;
|
|
125
|
+
const seenRequests = new Set;
|
|
126
|
+
const ordered = [];
|
|
127
|
+
let frontier = rootSpecs.map((spec) => [spec, Option.none()]);
|
|
128
|
+
while (frontier.length > 0) {
|
|
129
|
+
const requests = frontier.filter(([spec, maybeOrigin]) => {
|
|
130
|
+
const requestKey = `${Option.getOrElse(maybeOrigin, () => "")}::${spec}`;
|
|
131
|
+
if (seenRequests.has(requestKey)) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
seenRequests.add(requestKey);
|
|
135
|
+
return true;
|
|
136
|
+
});
|
|
137
|
+
const fetched = yield* Effect.forEach(requests, ([spec, maybeOrigin]) => getItem(spec, maybeOrigin), {
|
|
138
|
+
concurrency: fetchConcurrency
|
|
139
|
+
});
|
|
140
|
+
const next = [];
|
|
141
|
+
for (const { key, item } of fetched) {
|
|
142
|
+
if (seenKeys.has(key)) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
seenKeys.add(key);
|
|
146
|
+
ordered.push(item);
|
|
147
|
+
for (const dependency of item.registryDependencies ?? []) {
|
|
148
|
+
next.push([dependency, Option.some(key)]);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
frontier = next;
|
|
152
|
+
}
|
|
153
|
+
return mergeItems(ordered);
|
|
154
|
+
});
|
|
155
|
+
var mergeItems = (items) => {
|
|
156
|
+
const warnings = [];
|
|
157
|
+
const dependencies = mergeDependencyLists(items.map((item) => item.dependencies ?? []), warnings);
|
|
158
|
+
const devDependencies = mergeDependencyLists(items.map((item) => item.devDependencies ?? []), warnings);
|
|
159
|
+
const files = [];
|
|
160
|
+
const seenFiles = new Map;
|
|
161
|
+
for (const item of items) {
|
|
162
|
+
for (const file of item.files ?? []) {
|
|
163
|
+
const fileKey = file.target ?? file.path;
|
|
164
|
+
const existing = seenFiles.get(fileKey);
|
|
165
|
+
if (existing === undefined) {
|
|
166
|
+
seenFiles.set(fileKey, file);
|
|
167
|
+
files.push(file);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (existing.content !== file.content) {
|
|
171
|
+
warnings.push(`Skipped duplicate file "${fileKey}" from item "${item.name}" with divergent content`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
let cssVars = {};
|
|
176
|
+
let css = {};
|
|
177
|
+
for (const item of items) {
|
|
178
|
+
if (item.cssVars !== undefined) {
|
|
179
|
+
cssVars = mergeCssVars(cssVars, item.cssVars, item.name, warnings);
|
|
180
|
+
}
|
|
181
|
+
if (item.css !== undefined) {
|
|
182
|
+
css = mergeCss(css, item.css, item.name, warnings);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return { items, dependencies, devDependencies, files, cssVars, css, warnings };
|
|
186
|
+
};
|
|
187
|
+
var packageName = (spec) => {
|
|
188
|
+
const versionSeparator = spec.lastIndexOf("@");
|
|
189
|
+
if (versionSeparator > 0) {
|
|
190
|
+
return spec.slice(0, versionSeparator);
|
|
191
|
+
}
|
|
192
|
+
return spec;
|
|
193
|
+
};
|
|
194
|
+
var mergeDependencyLists = (lists, warnings) => {
|
|
195
|
+
const byName = new Map;
|
|
196
|
+
for (const list of lists) {
|
|
197
|
+
for (const spec of list) {
|
|
198
|
+
const name = packageName(spec);
|
|
199
|
+
const existing = byName.get(name);
|
|
200
|
+
if (existing === undefined) {
|
|
201
|
+
byName.set(name, spec);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (existing !== spec) {
|
|
205
|
+
warnings.push(`Kept dependency "${existing}" over conflicting "${spec}"`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return Array.from(byName.values());
|
|
210
|
+
};
|
|
211
|
+
var mergeVarRecord = (existing, incoming, scope, itemName, warnings) => {
|
|
212
|
+
if (incoming === undefined) {
|
|
213
|
+
return existing;
|
|
214
|
+
}
|
|
215
|
+
if (existing === undefined) {
|
|
216
|
+
return incoming;
|
|
217
|
+
}
|
|
218
|
+
const merged = { ...existing };
|
|
219
|
+
for (const [key, value] of Object.entries(incoming)) {
|
|
220
|
+
const current = merged[key];
|
|
221
|
+
if (current === undefined) {
|
|
222
|
+
merged[key] = value;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (current !== value) {
|
|
226
|
+
warnings.push(`Kept ${scope} variable "${key}: ${current}" over "${value}" from item "${itemName}"`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return merged;
|
|
230
|
+
};
|
|
231
|
+
var mergeCssVars = (existing, incoming, itemName, warnings) => {
|
|
232
|
+
const theme = mergeVarRecord(existing.theme, incoming.theme, "theme", itemName, warnings);
|
|
233
|
+
const light = mergeVarRecord(existing.light, incoming.light, "light", itemName, warnings);
|
|
234
|
+
const dark = mergeVarRecord(existing.dark, incoming.dark, "dark", itemName, warnings);
|
|
235
|
+
return {
|
|
236
|
+
...theme === undefined ? {} : { theme },
|
|
237
|
+
...light === undefined ? {} : { light },
|
|
238
|
+
...dark === undefined ? {} : { dark }
|
|
239
|
+
};
|
|
240
|
+
};
|
|
241
|
+
var mergeCss = (existing, incoming, itemName, warnings) => {
|
|
242
|
+
const merged = { ...existing };
|
|
243
|
+
for (const [selector, value] of Object.entries(incoming)) {
|
|
244
|
+
const current = merged[selector];
|
|
245
|
+
if (current === undefined) {
|
|
246
|
+
merged[selector] = value;
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
if (typeof current === "object" && typeof value === "object") {
|
|
250
|
+
merged[selector] = mergeCss(current, value, itemName, warnings);
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
if (current !== value) {
|
|
254
|
+
warnings.push(`Kept css "${selector}" over conflicting definition from item "${itemName}"`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return merged;
|
|
258
|
+
};
|
|
259
|
+
// ../registry/dist/cssMerge/index.js
|
|
260
|
+
var exports_cssMerge = {};
|
|
261
|
+
__export(exports_cssMerge, {
|
|
262
|
+
merge: () => merge,
|
|
263
|
+
CssParseError: () => CssParseError
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// ../registry/dist/cssMerge/cssMerge.js
|
|
267
|
+
import { Effect as Effect2, Schema as Schema3 } from "effect";
|
|
268
|
+
import postcss from "postcss";
|
|
269
|
+
|
|
270
|
+
class CssParseError extends Schema3.TaggedErrorClass()("CssParseError", {
|
|
271
|
+
message: Schema3.String
|
|
272
|
+
}) {
|
|
273
|
+
}
|
|
274
|
+
var darkVariantParams = "(&:is(.dark *))";
|
|
275
|
+
var radiusScale = {
|
|
276
|
+
"--radius-sm": "calc(var(--radius) - 4px)",
|
|
277
|
+
"--radius-md": "calc(var(--radius) - 2px)",
|
|
278
|
+
"--radius-lg": "var(--radius)",
|
|
279
|
+
"--radius-xl": "calc(var(--radius) + 4px)"
|
|
280
|
+
};
|
|
281
|
+
var merge = (options) => Effect2.try({
|
|
282
|
+
try: () => mergeSync(options),
|
|
283
|
+
catch: (error) => new CssParseError({ message: String(error) })
|
|
284
|
+
});
|
|
285
|
+
var mergeSync = (options) => {
|
|
286
|
+
const root = postcss.parse(options.css);
|
|
287
|
+
const isOverwriting = options.isOverwriting ?? false;
|
|
288
|
+
const hasCssVars = options.cssVars !== undefined && Object.values(options.cssVars).some((vars) => vars !== undefined && Object.keys(vars).length > 0);
|
|
289
|
+
if (hasCssVars && options.cssVars !== undefined) {
|
|
290
|
+
ensureDarkVariant(root);
|
|
291
|
+
applyCssVars(root, options.cssVars, isOverwriting);
|
|
292
|
+
applyThemeMapping(root, options.cssVars);
|
|
293
|
+
}
|
|
294
|
+
if (options.extraCss !== undefined) {
|
|
295
|
+
applyExtraCss(root, options.extraCss);
|
|
296
|
+
}
|
|
297
|
+
let output = root.toResult().css;
|
|
298
|
+
output = output.replace(/(\n\s*\n)+/g, `
|
|
299
|
+
|
|
300
|
+
`);
|
|
301
|
+
return `${output.trimEnd()}
|
|
302
|
+
`;
|
|
303
|
+
};
|
|
304
|
+
var isAtRule = (node, name) => node.type === "atrule" && node.name === name;
|
|
305
|
+
var findAtRule = (root, name, params) => root.nodes.find((node) => isAtRule(node, name) && (params === undefined || node.params === params));
|
|
306
|
+
var lastAtRule = (root, name) => {
|
|
307
|
+
const matches = root.nodes.filter((node) => isAtRule(node, name));
|
|
308
|
+
return matches.at(-1);
|
|
309
|
+
};
|
|
310
|
+
var ensureDarkVariant = (root) => {
|
|
311
|
+
const existing = root.nodes.find((node) => isAtRule(node, "custom-variant") && node.params.startsWith("dark"));
|
|
312
|
+
if (existing !== undefined) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const variantNode = postcss.atRule({
|
|
316
|
+
name: "custom-variant",
|
|
317
|
+
params: `dark ${darkVariantParams}`,
|
|
318
|
+
raws: { semicolon: true, before: `
|
|
319
|
+
|
|
320
|
+
` }
|
|
321
|
+
});
|
|
322
|
+
const lastImport = lastAtRule(root, "import");
|
|
323
|
+
if (lastImport !== undefined) {
|
|
324
|
+
root.insertAfter(lastImport, variantNode);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
const firstNode = root.nodes.at(0);
|
|
328
|
+
if (firstNode !== undefined) {
|
|
329
|
+
root.insertAfter(firstNode, variantNode);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
root.append(variantNode);
|
|
333
|
+
};
|
|
334
|
+
var upsertThemeNode = (root) => {
|
|
335
|
+
const existing = findAtRule(root, "theme", "inline");
|
|
336
|
+
if (existing !== undefined) {
|
|
337
|
+
return existing;
|
|
338
|
+
}
|
|
339
|
+
const themeNode = postcss.atRule({
|
|
340
|
+
name: "theme",
|
|
341
|
+
params: "inline",
|
|
342
|
+
nodes: [],
|
|
343
|
+
raws: { semicolon: true, between: " ", before: `
|
|
344
|
+
|
|
345
|
+
` }
|
|
346
|
+
});
|
|
347
|
+
root.append(themeNode);
|
|
348
|
+
return themeNode;
|
|
349
|
+
};
|
|
350
|
+
var upsertRule = (root, selector) => {
|
|
351
|
+
const existing = root.nodes.find((node) => node.type === "rule" && node.selector === selector);
|
|
352
|
+
if (existing !== undefined) {
|
|
353
|
+
return existing;
|
|
354
|
+
}
|
|
355
|
+
const ruleNode = postcss.rule({
|
|
356
|
+
selector,
|
|
357
|
+
nodes: [],
|
|
358
|
+
raws: { semicolon: true, between: " ", before: `
|
|
359
|
+
|
|
360
|
+
` }
|
|
361
|
+
});
|
|
362
|
+
root.append(ruleNode);
|
|
363
|
+
return ruleNode;
|
|
364
|
+
};
|
|
365
|
+
var upsertDeclaration = (container, prop, value, isOverwriting) => {
|
|
366
|
+
const existing = container.nodes?.find((node) => node.type === "decl" && node.prop === prop);
|
|
367
|
+
const declaration = postcss.decl({ prop, value, raws: { semicolon: true, before: `
|
|
368
|
+
` } });
|
|
369
|
+
if (existing !== undefined) {
|
|
370
|
+
if (isOverwriting && existing.value !== value) {
|
|
371
|
+
existing.replaceWith(declaration);
|
|
372
|
+
}
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
container.append(declaration);
|
|
376
|
+
};
|
|
377
|
+
var normalizeProp = (name) => `--${name.replace(/^--/, "")}`;
|
|
378
|
+
var applyCssVars = (root, cssVars, isOverwriting) => {
|
|
379
|
+
if (cssVars.theme !== undefined && Object.keys(cssVars.theme).length > 0) {
|
|
380
|
+
const themeNode = upsertThemeNode(root);
|
|
381
|
+
for (const [name, value] of Object.entries(cssVars.theme)) {
|
|
382
|
+
upsertDeclaration(themeNode, normalizeProp(name), value, isOverwriting);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
const selectorVars = [
|
|
386
|
+
[":root", cssVars.light],
|
|
387
|
+
[".dark", cssVars.dark]
|
|
388
|
+
];
|
|
389
|
+
for (const [selector, vars] of selectorVars) {
|
|
390
|
+
if (vars === undefined || Object.keys(vars).length === 0) {
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
const ruleNode = upsertRule(root, selector);
|
|
394
|
+
for (const [name, value] of Object.entries(vars)) {
|
|
395
|
+
upsertDeclaration(ruleNode, normalizeProp(name), value, isOverwriting);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
var isColorValue = (value) => value.startsWith("hsl") || value.startsWith("rgb") || value.startsWith("#") || value.startsWith("oklch") || value.includes("--color-");
|
|
400
|
+
var applyThemeMapping = (root, cssVars) => {
|
|
401
|
+
const scopes = [cssVars.light, cssVars.dark].filter((vars) => vars !== undefined);
|
|
402
|
+
const names = Array.from(new Set(scopes.flatMap((vars) => Object.keys(vars))));
|
|
403
|
+
if (names.length === 0) {
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
const themeNode = upsertThemeNode(root);
|
|
407
|
+
for (const name of names) {
|
|
408
|
+
const value = scopes.map((vars) => vars[name]).find((found) => found !== undefined);
|
|
409
|
+
if (value === undefined) {
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
if (name === "radius") {
|
|
413
|
+
for (const [prop2, radiusValue] of Object.entries(radiusScale)) {
|
|
414
|
+
upsertDeclaration(themeNode, prop2, radiusValue, false);
|
|
415
|
+
}
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
const bare = name.replace(/^--/, "");
|
|
419
|
+
const prop = isColorValue(value) ? `--color-${bare}` : `--${bare}`;
|
|
420
|
+
upsertDeclaration(themeNode, prop, `var(--${bare})`, false);
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
var atRuleHeader = (selector) => {
|
|
424
|
+
const match = selector.match(/^@([a-zA-Z-]+)\s*(.*)$/);
|
|
425
|
+
if (match === null) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
const name = match[1];
|
|
429
|
+
const params = match[2];
|
|
430
|
+
if (name === undefined) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
return [name, params ?? ""];
|
|
434
|
+
};
|
|
435
|
+
var applyExtraCss = (root, extraCss) => {
|
|
436
|
+
for (const [selector, value] of Object.entries(extraCss)) {
|
|
437
|
+
const header = atRuleHeader(selector);
|
|
438
|
+
if (header !== undefined) {
|
|
439
|
+
applyAtRule(root, header[0], header[1], value);
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
if (typeof value === "object") {
|
|
443
|
+
const ruleNode = upsertRule(root, selector);
|
|
444
|
+
applyBody(ruleNode, value);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
var applyAtRule = (root, name, params, value) => {
|
|
449
|
+
if (name === "import" || name === "plugin" || name === "custom-variant") {
|
|
450
|
+
const exists = root.nodes.some((node) => isAtRule(node, name) && node.params === params);
|
|
451
|
+
if (!exists) {
|
|
452
|
+
const atRuleNode2 = postcss.atRule({ name, params, raws: { semicolon: true, before: `
|
|
453
|
+
` } });
|
|
454
|
+
const lastImport = lastAtRule(root, "import");
|
|
455
|
+
if (lastImport !== undefined) {
|
|
456
|
+
root.insertAfter(lastImport, atRuleNode2);
|
|
457
|
+
} else {
|
|
458
|
+
root.prepend(atRuleNode2);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
if (typeof value === "object" && Object.keys(value).length === 0) {
|
|
464
|
+
const exists = root.nodes.some((node) => isAtRule(node, name) && node.params === params);
|
|
465
|
+
if (!exists) {
|
|
466
|
+
root.append(postcss.atRule({ name, params, raws: { semicolon: true, before: `
|
|
467
|
+
|
|
468
|
+
` } }));
|
|
469
|
+
}
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
if (name === "keyframes") {
|
|
473
|
+
const themeNode = upsertThemeNode(root);
|
|
474
|
+
const existing2 = themeNode.nodes?.find((node) => node.type === "atrule" && isAtRule(node, "keyframes") && node.params === params);
|
|
475
|
+
const keyframesNode = postcss.atRule({
|
|
476
|
+
name,
|
|
477
|
+
params,
|
|
478
|
+
nodes: [],
|
|
479
|
+
raws: { semicolon: true, between: " ", before: `
|
|
480
|
+
` }
|
|
481
|
+
});
|
|
482
|
+
if (existing2 !== undefined) {
|
|
483
|
+
existing2.replaceWith(keyframesNode);
|
|
484
|
+
} else {
|
|
485
|
+
themeNode.append(keyframesNode);
|
|
486
|
+
}
|
|
487
|
+
if (typeof value === "object") {
|
|
488
|
+
applyBody(keyframesNode, value);
|
|
489
|
+
}
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
const existing = root.nodes.find((node) => isAtRule(node, name) && node.params === params);
|
|
493
|
+
const atRuleNode = existing ?? (() => {
|
|
494
|
+
const created = postcss.atRule({
|
|
495
|
+
name,
|
|
496
|
+
params,
|
|
497
|
+
nodes: [],
|
|
498
|
+
raws: { semicolon: true, between: " ", before: `
|
|
499
|
+
|
|
500
|
+
` }
|
|
501
|
+
});
|
|
502
|
+
root.append(created);
|
|
503
|
+
return created;
|
|
504
|
+
})();
|
|
505
|
+
if (typeof value === "object") {
|
|
506
|
+
applyBody(atRuleNode, value);
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
var applyBody = (container, body) => {
|
|
510
|
+
for (const [key, value] of Object.entries(body)) {
|
|
511
|
+
if (typeof value === "string" && key.startsWith("@")) {
|
|
512
|
+
const header2 = atRuleHeader(key);
|
|
513
|
+
if (header2 !== undefined) {
|
|
514
|
+
const [name, params] = header2;
|
|
515
|
+
const exists = container.nodes?.some((node) => node.type === "atrule" && node.name === name && node.params === params);
|
|
516
|
+
if (exists !== true) {
|
|
517
|
+
container.append(postcss.atRule({ name, params, raws: { semicolon: true, before: `
|
|
518
|
+
` } }));
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
if (typeof value === "string") {
|
|
524
|
+
upsertDeclaration(container, key, value, true);
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
const header = atRuleHeader(key);
|
|
528
|
+
if (header !== undefined) {
|
|
529
|
+
const [name, params] = header;
|
|
530
|
+
if (typeof value === "object" && Object.keys(value).length === 0) {
|
|
531
|
+
const exists = container.nodes?.some((node) => node.type === "atrule" && node.name === name && node.params === params);
|
|
532
|
+
if (exists !== true) {
|
|
533
|
+
container.append(postcss.atRule({ name, params, raws: { semicolon: true, before: `
|
|
534
|
+
` } }));
|
|
535
|
+
}
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
const existing2 = container.nodes?.find((node) => node.type === "atrule" && node.name === name && node.params === params);
|
|
539
|
+
const nested2 = existing2 ?? (() => {
|
|
540
|
+
const created = postcss.atRule({
|
|
541
|
+
name,
|
|
542
|
+
params,
|
|
543
|
+
nodes: [],
|
|
544
|
+
raws: { semicolon: true, between: " ", before: `
|
|
545
|
+
` }
|
|
546
|
+
});
|
|
547
|
+
container.append(created);
|
|
548
|
+
return created;
|
|
549
|
+
})();
|
|
550
|
+
applyBody(nested2, value);
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
const existing = container.nodes?.find((node) => node.type === "rule" && node.selector === key);
|
|
554
|
+
const nested = existing ?? (() => {
|
|
555
|
+
const created = postcss.rule({
|
|
556
|
+
selector: key,
|
|
557
|
+
nodes: [],
|
|
558
|
+
raws: { semicolon: true, between: " ", before: `
|
|
559
|
+
` }
|
|
560
|
+
});
|
|
561
|
+
container.append(created);
|
|
562
|
+
return created;
|
|
563
|
+
})();
|
|
564
|
+
applyBody(nested, value);
|
|
565
|
+
}
|
|
566
|
+
};
|
|
567
|
+
// src/constants/constants.ts
|
|
568
|
+
var defaultRegistryTemplate = "https://foldcn.dev/r/{name}.json";
|
|
569
|
+
var registryUrlEnvVar = "FOLDCN_REGISTRY_URL";
|
|
570
|
+
var itemSchemaUrl = "https://foldcn.dev/schema/registry-item.json";
|
|
571
|
+
var componentsSchemaUrl = "https://foldcn.dev/schema/components.json";
|
|
572
|
+
// src/error/error.ts
|
|
573
|
+
import { Schema as Schema4 } from "effect";
|
|
574
|
+
|
|
575
|
+
class JsonParseError extends Schema4.TaggedErrorClass()("JsonParseError", {
|
|
576
|
+
path: Schema4.String,
|
|
577
|
+
message: Schema4.String
|
|
578
|
+
}) {
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
class UnknownRegistryDependencyError extends Schema4.TaggedErrorClass()("UnknownRegistryDependencyError", {
|
|
582
|
+
itemName: Schema4.String,
|
|
583
|
+
dependency: Schema4.String
|
|
584
|
+
}) {
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
class ConfigNotFoundError extends Schema4.TaggedErrorClass()("ConfigNotFoundError", {
|
|
588
|
+
cwd: Schema4.String
|
|
589
|
+
}) {
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
class RegistryFetchError extends Schema4.TaggedErrorClass()("RegistryFetchError", {
|
|
593
|
+
spec: Schema4.String,
|
|
594
|
+
message: Schema4.String
|
|
595
|
+
}) {
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
class RegistryNotConfiguredError extends Schema4.TaggedErrorClass()("RegistryNotConfiguredError", {
|
|
599
|
+
namespace: Schema4.String
|
|
600
|
+
}) {
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
class UnsafeTargetError extends Schema4.TaggedErrorClass()("UnsafeTargetError", {
|
|
604
|
+
target: Schema4.String
|
|
605
|
+
}) {
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
class FileConflictError extends Schema4.TaggedErrorClass()("FileConflictError", {
|
|
609
|
+
paths: Schema4.Array(Schema4.String)
|
|
610
|
+
}) {
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
class InstallFailedError extends Schema4.TaggedErrorClass()("InstallFailedError", {
|
|
614
|
+
packageManager: Schema4.String,
|
|
615
|
+
invocation: Schema4.String,
|
|
616
|
+
exitCode: Schema4.Number
|
|
617
|
+
}) {
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
class StylesheetNotFoundError extends Schema4.TaggedErrorClass()("StylesheetNotFoundError", {
|
|
621
|
+
path: Schema4.String
|
|
622
|
+
}) {
|
|
623
|
+
}
|
|
624
|
+
// src/installer/installer.ts
|
|
625
|
+
import { Effect as Effect4, FileSystem as FileSystem2, Path } from "effect";
|
|
626
|
+
import { ChildProcess } from "effect/unstable/process";
|
|
627
|
+
|
|
628
|
+
// src/json/json.ts
|
|
629
|
+
import { Effect as Effect3, FileSystem } from "effect";
|
|
630
|
+
var readJsonFile = (filePath) => Effect3.gen(function* () {
|
|
631
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
632
|
+
const text = yield* fileSystem.readFileString(filePath);
|
|
633
|
+
return yield* Effect3.try({
|
|
634
|
+
try: () => JSON.parse(text),
|
|
635
|
+
catch: (error2) => new JsonParseError({ path: filePath, message: String(error2) })
|
|
636
|
+
});
|
|
637
|
+
});
|
|
638
|
+
var writeJsonFile = (filePath, value) => Effect3.gen(function* () {
|
|
639
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
640
|
+
yield* fileSystem.writeFileString(filePath, `${JSON.stringify(value, null, 2)}
|
|
641
|
+
`);
|
|
642
|
+
});
|
|
643
|
+
// src/installer/installer.ts
|
|
644
|
+
var lockfilePackageManagers = [
|
|
645
|
+
["bun.lock", "bun"],
|
|
646
|
+
["bun.lockb", "bun"],
|
|
647
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
648
|
+
["yarn.lock", "yarn"],
|
|
649
|
+
["package-lock.json", "npm"]
|
|
650
|
+
];
|
|
651
|
+
var defaultPackageManager = "pnpm";
|
|
652
|
+
var detectPackageManager = (cwd) => Effect4.gen(function* () {
|
|
653
|
+
const fileSystem = yield* FileSystem2.FileSystem;
|
|
654
|
+
const path = yield* Path.Path;
|
|
655
|
+
const packageJsonPath = path.join(cwd, "package.json");
|
|
656
|
+
const hasPackageJson = yield* fileSystem.exists(packageJsonPath);
|
|
657
|
+
if (hasPackageJson) {
|
|
658
|
+
const packageJson = yield* readJsonFile(packageJsonPath).pipe(Effect4.orElseSucceed(() => ({})));
|
|
659
|
+
const declared = packageJson.packageManager;
|
|
660
|
+
if (declared !== undefined) {
|
|
661
|
+
const separator = declared.indexOf("@");
|
|
662
|
+
return separator > 0 ? declared.slice(0, separator) : declared;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
for (const [lockfile, packageManager] of lockfilePackageManagers) {
|
|
666
|
+
const hasLockfile = yield* fileSystem.exists(path.join(cwd, lockfile));
|
|
667
|
+
if (hasLockfile) {
|
|
668
|
+
return packageManager;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
return defaultPackageManager;
|
|
672
|
+
});
|
|
673
|
+
var install = (plan) => Effect4.gen(function* () {
|
|
674
|
+
const invocations = [];
|
|
675
|
+
if (plan.dependencies.length > 0) {
|
|
676
|
+
invocations.push(["add", ...plan.dependencies]);
|
|
677
|
+
}
|
|
678
|
+
if (plan.devDependencies.length > 0) {
|
|
679
|
+
invocations.push(["add", "-D", ...plan.devDependencies]);
|
|
680
|
+
}
|
|
681
|
+
for (const invocationArgs of invocations) {
|
|
682
|
+
const exitCode = yield* Effect4.scoped(Effect4.gen(function* () {
|
|
683
|
+
const handle = yield* ChildProcess.make(plan.packageManager, invocationArgs, {
|
|
684
|
+
cwd: plan.cwd,
|
|
685
|
+
stdout: "inherit",
|
|
686
|
+
stderr: "inherit"
|
|
687
|
+
});
|
|
688
|
+
return yield* handle.exitCode;
|
|
689
|
+
}));
|
|
690
|
+
if (exitCode !== 0) {
|
|
691
|
+
return yield* new InstallFailedError({
|
|
692
|
+
packageManager: plan.packageManager,
|
|
693
|
+
invocation: invocationArgs.join(" "),
|
|
694
|
+
exitCode
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
});
|
|
699
|
+
// src/projectContext/projectContext.ts
|
|
700
|
+
import { Effect as Effect5, FileSystem as FileSystem3, Path as Path2, Schema as Schema5 } from "effect";
|
|
701
|
+
var load = (cwd) => Effect5.gen(function* () {
|
|
702
|
+
const fileSystem = yield* FileSystem3.FileSystem;
|
|
703
|
+
const path = yield* Path2.Path;
|
|
704
|
+
const cwdAbsolute = path.resolve(cwd);
|
|
705
|
+
const configPath = path.join(cwdAbsolute, "components.json");
|
|
706
|
+
const hasConfig = yield* fileSystem.exists(configPath);
|
|
707
|
+
if (!hasConfig) {
|
|
708
|
+
return yield* new ConfigNotFoundError({ cwd: cwdAbsolute });
|
|
709
|
+
}
|
|
710
|
+
const configJson = yield* readJsonFile(configPath);
|
|
711
|
+
const config = yield* Schema5.decodeUnknownEffect(exports_componentsConfig.Config)(configJson);
|
|
712
|
+
const context = {
|
|
713
|
+
cwd: cwdAbsolute,
|
|
714
|
+
config,
|
|
715
|
+
cssPath: path.resolve(cwdAbsolute, config.css),
|
|
716
|
+
uiDir: path.resolve(cwdAbsolute, config.aliases.ui),
|
|
717
|
+
libDir: path.resolve(cwdAbsolute, config.aliases.lib)
|
|
718
|
+
};
|
|
719
|
+
return context;
|
|
720
|
+
});
|
|
721
|
+
// src/registryClient/registryClient.ts
|
|
722
|
+
import { Effect as Effect6, Option as Option2, Path as Path3, Schema as Schema6 } from "effect";
|
|
723
|
+
var namePlaceholder = "{name}";
|
|
724
|
+
var isUrl = (spec) => spec.includes("://");
|
|
725
|
+
var isPathSpec = (spec) => spec.startsWith(".") || spec.startsWith("/") || spec.endsWith(".json");
|
|
726
|
+
var templateFromLocation = (location, separator) => {
|
|
727
|
+
const lastSeparator = location.lastIndexOf(separator);
|
|
728
|
+
if (lastSeparator < 0) {
|
|
729
|
+
return `${namePlaceholder}.json`;
|
|
730
|
+
}
|
|
731
|
+
return `${location.slice(0, lastSeparator)}${separator}${namePlaceholder}.json`;
|
|
732
|
+
};
|
|
733
|
+
var normalizeTemplate = (registry2) => {
|
|
734
|
+
if (registry2.includes(namePlaceholder)) {
|
|
735
|
+
return registry2;
|
|
736
|
+
}
|
|
737
|
+
const trimmed = registry2.endsWith("/") ? registry2.slice(0, -1) : registry2;
|
|
738
|
+
return `${trimmed}/${namePlaceholder}.json`;
|
|
739
|
+
};
|
|
740
|
+
var makeGetItem = (options) => Effect6.gen(function* () {
|
|
741
|
+
const path = yield* Path3.Path;
|
|
742
|
+
const itemCache = new Map;
|
|
743
|
+
const keyTemplates = new Map;
|
|
744
|
+
const resolveLocation = (spec, maybeOrigin) => Effect6.gen(function* () {
|
|
745
|
+
if (isUrl(spec)) {
|
|
746
|
+
return { location: spec, template: templateFromLocation(spec, "/") };
|
|
747
|
+
}
|
|
748
|
+
if (isPathSpec(spec)) {
|
|
749
|
+
const location = path.resolve(options.cwd, spec);
|
|
750
|
+
return { location, template: templateFromLocation(location, path.sep) };
|
|
751
|
+
}
|
|
752
|
+
if (spec.startsWith("@")) {
|
|
753
|
+
const separator = spec.indexOf("/");
|
|
754
|
+
const namespace = separator < 0 ? spec : spec.slice(0, separator);
|
|
755
|
+
const name = separator < 0 ? "" : spec.slice(separator + 1);
|
|
756
|
+
const template2 = options.registries?.[namespace];
|
|
757
|
+
if (template2 === undefined) {
|
|
758
|
+
return yield* new RegistryNotConfiguredError({ namespace });
|
|
759
|
+
}
|
|
760
|
+
return { location: template2.replace(namePlaceholder, name), template: template2 };
|
|
761
|
+
}
|
|
762
|
+
const originTemplate = Option2.flatMap(maybeOrigin, (origin) => Option2.fromNullishOr(keyTemplates.get(origin)));
|
|
763
|
+
const template = Option2.getOrElse(originTemplate, () => options.defaultTemplate);
|
|
764
|
+
return { location: template.replace(namePlaceholder, spec), template };
|
|
765
|
+
});
|
|
766
|
+
const fetchItemJson = (location) => {
|
|
767
|
+
if (isUrl(location)) {
|
|
768
|
+
return Effect6.tryPromise({
|
|
769
|
+
try: async () => {
|
|
770
|
+
const response = await fetch(location);
|
|
771
|
+
if (!response.ok) {
|
|
772
|
+
throw new Error(`HTTP ${response.status}`);
|
|
773
|
+
}
|
|
774
|
+
return response.json();
|
|
775
|
+
},
|
|
776
|
+
catch: (error2) => new RegistryFetchError({ spec: location, message: String(error2) })
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
return readJsonFile(location).pipe(Effect6.catchTag("PlatformError", (error2) => Effect6.fail(new RegistryFetchError({ spec: location, message: String(error2) }))));
|
|
780
|
+
};
|
|
781
|
+
const getItem = (spec, maybeOrigin) => Effect6.gen(function* () {
|
|
782
|
+
const resolved = yield* resolveLocation(spec, maybeOrigin);
|
|
783
|
+
keyTemplates.set(resolved.location, resolved.template);
|
|
784
|
+
const cached = itemCache.get(resolved.location);
|
|
785
|
+
if (cached !== undefined) {
|
|
786
|
+
const found = { key: resolved.location, item: cached };
|
|
787
|
+
return found;
|
|
788
|
+
}
|
|
789
|
+
const itemJson = yield* fetchItemJson(resolved.location);
|
|
790
|
+
const item = yield* Schema6.decodeUnknownEffect(exports_registry.Item)(itemJson);
|
|
791
|
+
itemCache.set(resolved.location, item);
|
|
792
|
+
const fetched = { key: resolved.location, item };
|
|
793
|
+
return fetched;
|
|
794
|
+
});
|
|
795
|
+
return getItem;
|
|
796
|
+
});
|
|
797
|
+
// src/styleWriter/styleWriter.ts
|
|
798
|
+
import { Effect as Effect7, FileSystem as FileSystem5 } from "effect";
|
|
799
|
+
var hasEntries = (record) => record !== undefined && Object.keys(record).length > 0;
|
|
800
|
+
var applyStyles = (options) => Effect7.gen(function* () {
|
|
801
|
+
const hasWork = hasEntries(options.cssVars.theme) || hasEntries(options.cssVars.light) || hasEntries(options.cssVars.dark) || hasEntries(options.css);
|
|
802
|
+
if (!hasWork) {
|
|
803
|
+
return false;
|
|
804
|
+
}
|
|
805
|
+
const fileSystem = yield* FileSystem5.FileSystem;
|
|
806
|
+
const hasStylesheet = yield* fileSystem.exists(options.cssPath);
|
|
807
|
+
if (!hasStylesheet) {
|
|
808
|
+
return yield* new StylesheetNotFoundError({ path: options.cssPath });
|
|
809
|
+
}
|
|
810
|
+
const current = yield* fileSystem.readFileString(options.cssPath);
|
|
811
|
+
const merged = yield* exports_cssMerge.merge({
|
|
812
|
+
css: current,
|
|
813
|
+
cssVars: options.cssVars,
|
|
814
|
+
extraCss: options.css,
|
|
815
|
+
isOverwriting: options.isOverwriting
|
|
816
|
+
});
|
|
817
|
+
if (merged === current) {
|
|
818
|
+
return false;
|
|
819
|
+
}
|
|
820
|
+
yield* fileSystem.writeFileString(options.cssPath, merged);
|
|
821
|
+
return true;
|
|
822
|
+
});
|
|
823
|
+
// src/command/add/add.ts
|
|
824
|
+
var componentsArgument = Argument.string("components").pipe(Argument.withDescription("Component names, @namespace/names, URLs, or paths"), Argument.variadic({ min: 1 }));
|
|
825
|
+
var cwdFlag = Flag.string("cwd").pipe(Flag.withAlias("c"), Flag.withDescription("Working directory"), Flag.withDefault("."));
|
|
826
|
+
var overwriteFlag = Flag.boolean("overwrite").pipe(Flag.withAlias("o"), Flag.withDescription("Overwrite existing files"));
|
|
827
|
+
var dryRunFlag = Flag.boolean("dry-run").pipe(Flag.withDescription("Print the plan without writing anything"));
|
|
828
|
+
var registryFlag = Flag.string("registry").pipe(Flag.withDescription("Registry URL template, URL, or local directory overriding the default registry"), Flag.optional);
|
|
829
|
+
var stripRegistrySourcePrefix = (filePath) => {
|
|
830
|
+
const segments = filePath.split("/");
|
|
831
|
+
const registryIndex = segments.indexOf("registry");
|
|
832
|
+
if (registryIndex >= 0 && segments.length > registryIndex + 2) {
|
|
833
|
+
return segments.slice(registryIndex + 2).join("/");
|
|
834
|
+
}
|
|
835
|
+
return segments.at(-1) ?? filePath;
|
|
836
|
+
};
|
|
837
|
+
var planFiles = (context, files) => Effect8.gen(function* () {
|
|
838
|
+
const path = yield* Path4.Path;
|
|
839
|
+
const planned = [];
|
|
840
|
+
for (const file of files) {
|
|
841
|
+
const content = file.content ?? "";
|
|
842
|
+
if (file.target !== undefined) {
|
|
843
|
+
if (file.target.startsWith("/") || file.target.split("/").includes("..")) {
|
|
844
|
+
return yield* new UnsafeTargetError({ target: file.target });
|
|
845
|
+
}
|
|
846
|
+
const absolutePath2 = path.resolve(context.cwd, file.target);
|
|
847
|
+
planned.push({ absolutePath: absolutePath2, relativePath: file.target, content });
|
|
848
|
+
continue;
|
|
849
|
+
}
|
|
850
|
+
const relativeToKind = stripRegistrySourcePrefix(file.path);
|
|
851
|
+
const baseDir = file.type === "registry:lib" ? context.libDir : context.uiDir;
|
|
852
|
+
const absolutePath = path.resolve(baseDir, relativeToKind);
|
|
853
|
+
planned.push({ absolutePath, relativePath: path.relative(context.cwd, absolutePath), content });
|
|
854
|
+
}
|
|
855
|
+
return planned;
|
|
856
|
+
});
|
|
857
|
+
var runAdd = ({ components, cwd, dryRun, overwrite, registry: registry2 }) => Effect8.gen(function* () {
|
|
858
|
+
const fileSystem = yield* FileSystem6.FileSystem;
|
|
859
|
+
const path = yield* Path4.Path;
|
|
860
|
+
const context = yield* load(cwd);
|
|
861
|
+
const environmentTemplate = Option3.fromNullishOr(globalThis.process?.env?.[registryUrlEnvVar]);
|
|
862
|
+
const defaultTemplate = Option3.match(Option3.orElse(Option3.map(registry2, normalizeTemplate), () => environmentTemplate), {
|
|
863
|
+
onNone: () => defaultRegistryTemplate,
|
|
864
|
+
onSome: (template) => template
|
|
865
|
+
});
|
|
866
|
+
const getItem = yield* makeGetItem({
|
|
867
|
+
cwd: context.cwd,
|
|
868
|
+
defaultTemplate,
|
|
869
|
+
registries: context.config.registries
|
|
870
|
+
});
|
|
871
|
+
const tree = yield* exports_resolveTree.resolve(components, getItem);
|
|
872
|
+
for (const warning of tree.warnings) {
|
|
873
|
+
yield* Console.warn(warning);
|
|
874
|
+
}
|
|
875
|
+
const writableFiles = tree.files.filter((file) => file.type !== "registry:example");
|
|
876
|
+
const allPlanned = yield* planFiles(context, writableFiles);
|
|
877
|
+
const planned = [];
|
|
878
|
+
const conflicts = [];
|
|
879
|
+
for (const file of allPlanned) {
|
|
880
|
+
const exists = yield* fileSystem.exists(file.absolutePath);
|
|
881
|
+
if (!exists) {
|
|
882
|
+
planned.push(file);
|
|
883
|
+
continue;
|
|
884
|
+
}
|
|
885
|
+
const existing = yield* fileSystem.readFileString(file.absolutePath);
|
|
886
|
+
if (existing === file.content) {
|
|
887
|
+
continue;
|
|
888
|
+
}
|
|
889
|
+
if (overwrite) {
|
|
890
|
+
planned.push(file);
|
|
891
|
+
} else {
|
|
892
|
+
conflicts.push(file.relativePath);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
if (conflicts.length > 0) {
|
|
896
|
+
return yield* new FileConflictError({ paths: conflicts });
|
|
897
|
+
}
|
|
898
|
+
if (dryRun) {
|
|
899
|
+
yield* Console.log(`Would write ${planned.length} files:`);
|
|
900
|
+
for (const file of planned) {
|
|
901
|
+
yield* Console.log(` ${file.relativePath}`);
|
|
902
|
+
}
|
|
903
|
+
if (tree.dependencies.length > 0) {
|
|
904
|
+
yield* Console.log(`Would install: ${tree.dependencies.join(" ")}`);
|
|
905
|
+
}
|
|
906
|
+
if (tree.devDependencies.length > 0) {
|
|
907
|
+
yield* Console.log(`Would install (dev): ${tree.devDependencies.join(" ")}`);
|
|
908
|
+
}
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
for (const file of planned) {
|
|
912
|
+
yield* fileSystem.makeDirectory(path.dirname(file.absolutePath), { recursive: true });
|
|
913
|
+
yield* fileSystem.writeFileString(file.absolutePath, file.content);
|
|
914
|
+
}
|
|
915
|
+
if (tree.dependencies.length > 0 || tree.devDependencies.length > 0) {
|
|
916
|
+
const packageManager = yield* detectPackageManager(context.cwd);
|
|
917
|
+
yield* install({
|
|
918
|
+
cwd: context.cwd,
|
|
919
|
+
packageManager,
|
|
920
|
+
dependencies: tree.dependencies,
|
|
921
|
+
devDependencies: tree.devDependencies
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
const hasThemeItem = tree.items.some((item) => item.type === "registry:theme");
|
|
925
|
+
const changedStylesheet = yield* applyStyles({
|
|
926
|
+
cssPath: context.cssPath,
|
|
927
|
+
cssVars: tree.cssVars,
|
|
928
|
+
css: tree.css,
|
|
929
|
+
isOverwriting: hasThemeItem
|
|
930
|
+
});
|
|
931
|
+
const summary = [
|
|
932
|
+
`Added ${tree.items.length} items (${planned.length} files)`,
|
|
933
|
+
...changedStylesheet ? [`Updated ${context.config.css}`] : []
|
|
934
|
+
];
|
|
935
|
+
for (const line of summary) {
|
|
936
|
+
yield* Console.log(line);
|
|
937
|
+
}
|
|
938
|
+
});
|
|
939
|
+
var addCommand = Command.make("add", {
|
|
940
|
+
components: componentsArgument,
|
|
941
|
+
cwd: cwdFlag,
|
|
942
|
+
dryRun: dryRunFlag,
|
|
943
|
+
overwrite: overwriteFlag,
|
|
944
|
+
registry: registryFlag
|
|
945
|
+
}, ({ components, cwd, dryRun, overwrite, registry: registry2 }) => runAdd({ components, cwd, dryRun, overwrite, registry: registry2 })).pipe(Command.withDescription("Add components from a registry to your project"));
|
|
946
|
+
// src/command/build/build.ts
|
|
947
|
+
import { Console as Console2, Effect as Effect9, FileSystem as FileSystem7, Path as Path5, Schema as Schema7 } from "effect";
|
|
948
|
+
import { Argument as Argument2, Command as Command2, Flag as Flag2 } from "effect/unstable/cli";
|
|
949
|
+
var registryArgument = Argument2.string("registry").pipe(Argument2.withDescription("Path to the registry manifest"), Argument2.withDefault("./registry.json"));
|
|
950
|
+
var outputFlag = Flag2.string("output").pipe(Flag2.withAlias("o"), Flag2.withDescription("Destination directory for built registry JSON"), Flag2.withDefault("./public/r"));
|
|
951
|
+
var cwdFlag2 = Flag2.string("cwd").pipe(Flag2.withAlias("c"), Flag2.withDescription("Working directory"), Flag2.withDefault("."));
|
|
952
|
+
var isLocalName = (spec) => !spec.includes("://") && !spec.startsWith("@") && !spec.startsWith(".") && !spec.startsWith("/");
|
|
953
|
+
var validateLocalDependencies = (manifest) => Effect9.gen(function* () {
|
|
954
|
+
const names = new Set(manifest.items.map((item) => item.name));
|
|
955
|
+
for (const item of manifest.items) {
|
|
956
|
+
for (const dependency of item.registryDependencies ?? []) {
|
|
957
|
+
if (isLocalName(dependency) && !names.has(dependency)) {
|
|
958
|
+
return yield* new UnknownRegistryDependencyError({ itemName: item.name, dependency });
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
});
|
|
963
|
+
var buildCommand = Command2.make("build", { registry: registryArgument, output: outputFlag, cwd: cwdFlag2 }, ({ cwd, output, registry: registry2 }) => Effect9.gen(function* () {
|
|
964
|
+
const fileSystem = yield* FileSystem7.FileSystem;
|
|
965
|
+
const path = yield* Path5.Path;
|
|
966
|
+
const cwdAbsolute = path.resolve(cwd);
|
|
967
|
+
const manifestPath = path.resolve(cwdAbsolute, registry2);
|
|
968
|
+
const manifestJson = yield* readJsonFile(manifestPath);
|
|
969
|
+
const manifest = yield* Schema7.decodeUnknownEffect(exports_registry.Manifest)(manifestJson);
|
|
970
|
+
yield* validateLocalDependencies(manifest);
|
|
971
|
+
const outputDir = path.resolve(cwdAbsolute, output);
|
|
972
|
+
yield* fileSystem.makeDirectory(outputDir, { recursive: true });
|
|
973
|
+
for (const item of manifest.items) {
|
|
974
|
+
const files = yield* Effect9.forEach(item.files ?? [], (file) => Effect9.gen(function* () {
|
|
975
|
+
const content = yield* fileSystem.readFileString(path.resolve(cwdAbsolute, file.path));
|
|
976
|
+
return { ...file, content };
|
|
977
|
+
}));
|
|
978
|
+
const builtItem = {
|
|
979
|
+
...item,
|
|
980
|
+
$schema: itemSchemaUrl,
|
|
981
|
+
...files.length > 0 ? { files } : {}
|
|
982
|
+
};
|
|
983
|
+
const encoded = yield* Schema7.encodeEffect(exports_registry.Item)(builtItem);
|
|
984
|
+
const itemPath = path.join(outputDir, `${item.name}.json`);
|
|
985
|
+
yield* fileSystem.writeFileString(itemPath, `${JSON.stringify(encoded, null, 2)}
|
|
986
|
+
`);
|
|
987
|
+
}
|
|
988
|
+
const encodedManifest = yield* Schema7.encodeEffect(exports_registry.Manifest)(manifest);
|
|
989
|
+
yield* fileSystem.writeFileString(path.join(outputDir, "registry.json"), `${JSON.stringify(encodedManifest, null, 2)}
|
|
990
|
+
`);
|
|
991
|
+
yield* Console2.log(`Built ${manifest.items.length} items to ${path.relative(cwdAbsolute, outputDir)}`);
|
|
992
|
+
})).pipe(Command2.withDescription("Build registry JSON from a registry.json manifest"));
|
|
993
|
+
// src/command/init/init.ts
|
|
994
|
+
import { Console as Console3, Effect as Effect10, FileSystem as FileSystem8, Path as Path6 } from "effect";
|
|
995
|
+
import { Command as Command3, Flag as Flag3 } from "effect/unstable/cli";
|
|
996
|
+
var cwdFlag3 = Flag3.string("cwd").pipe(Flag3.withAlias("c"), Flag3.withDescription("Working directory"), Flag3.withDefault("."));
|
|
997
|
+
var cssFlag = Flag3.string("css").pipe(Flag3.withDescription("Path to the project stylesheet"), Flag3.withDefault(exports_componentsConfig.defaultCss));
|
|
998
|
+
var baseColorFlag = Flag3.string("base-color").pipe(Flag3.withDescription("Base color theme"), Flag3.withDefault(exports_componentsConfig.defaultBaseColor));
|
|
999
|
+
var forceFlag = Flag3.boolean("force").pipe(Flag3.withDescription("Overwrite an existing components.json"));
|
|
1000
|
+
var registryFlag2 = Flag3.string("registry").pipe(Flag3.withDescription("Registry URL template, URL, or local directory overriding the default registry"), Flag3.optional);
|
|
1001
|
+
var aliasPathsKey = "@/*";
|
|
1002
|
+
var aliasPathsValue = "./src/*";
|
|
1003
|
+
var injectTsconfigAlias = (cwd) => Effect10.gen(function* () {
|
|
1004
|
+
const fileSystem = yield* FileSystem8.FileSystem;
|
|
1005
|
+
const path = yield* Path6.Path;
|
|
1006
|
+
const tsconfigPath = path.join(cwd, "tsconfig.json");
|
|
1007
|
+
const hasTsconfig = yield* fileSystem.exists(tsconfigPath);
|
|
1008
|
+
if (!hasTsconfig) {
|
|
1009
|
+
yield* Console3.warn('No tsconfig.json found; add "paths": { "@/*": ["./src/*"] } manually');
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
const parsed = yield* readJsonFile(tsconfigPath).pipe(Effect10.orElseSucceed(() => {
|
|
1013
|
+
return;
|
|
1014
|
+
}));
|
|
1015
|
+
if (parsed === undefined || typeof parsed !== "object") {
|
|
1016
|
+
yield* Console3.warn('Could not parse tsconfig.json; add "paths": { "@/*": ["./src/*"] } manually');
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
const tsconfig = parsed;
|
|
1020
|
+
const compilerOptions = tsconfig.compilerOptions ?? {};
|
|
1021
|
+
const paths = compilerOptions["paths"] ?? {};
|
|
1022
|
+
if (paths[aliasPathsKey] !== undefined) {
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
const nextTsconfig = {
|
|
1026
|
+
...tsconfig,
|
|
1027
|
+
compilerOptions: {
|
|
1028
|
+
...compilerOptions,
|
|
1029
|
+
paths: { ...paths, [aliasPathsKey]: [aliasPathsValue] }
|
|
1030
|
+
}
|
|
1031
|
+
};
|
|
1032
|
+
yield* writeJsonFile(tsconfigPath, nextTsconfig);
|
|
1033
|
+
yield* Console3.log("Added @/* path alias to tsconfig.json");
|
|
1034
|
+
});
|
|
1035
|
+
var viteAliasBlock = [
|
|
1036
|
+
" resolve: {",
|
|
1037
|
+
" alias: [{ find: '@', replacement: fileURLToPath(new URL('./src', import.meta.url)) }],",
|
|
1038
|
+
" },"
|
|
1039
|
+
].join(`
|
|
1040
|
+
`);
|
|
1041
|
+
var injectViteAlias = (cwd) => Effect10.gen(function* () {
|
|
1042
|
+
const fileSystem = yield* FileSystem8.FileSystem;
|
|
1043
|
+
const path = yield* Path6.Path;
|
|
1044
|
+
const viteConfigPath = path.join(cwd, "vite.config.ts");
|
|
1045
|
+
const hasViteConfig = yield* fileSystem.exists(viteConfigPath);
|
|
1046
|
+
if (!hasViteConfig) {
|
|
1047
|
+
yield* Console3.warn("No vite.config.ts found; add a resolve.alias mapping '@' to ./src manually");
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
const source = yield* fileSystem.readFileString(viteConfigPath);
|
|
1051
|
+
if (source.includes("resolve:") || source.includes("find: '@'")) {
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
const anchor = `export default defineConfig({
|
|
1055
|
+
`;
|
|
1056
|
+
if (!source.includes(anchor)) {
|
|
1057
|
+
yield* Console3.warn("Could not update vite.config.ts; add a resolve.alias mapping '@' to ./src manually");
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
const withAlias = source.replace(anchor, `${anchor}${viteAliasBlock}
|
|
1061
|
+
`);
|
|
1062
|
+
const withImport = withAlias.includes("from 'node:url'") ? withAlias : `import { fileURLToPath } from 'node:url'
|
|
1063
|
+
${withAlias}`;
|
|
1064
|
+
yield* fileSystem.writeFileString(viteConfigPath, withImport);
|
|
1065
|
+
yield* Console3.log("Added @ resolve alias to vite.config.ts");
|
|
1066
|
+
});
|
|
1067
|
+
var initCommand = Command3.make("init", {
|
|
1068
|
+
baseColor: baseColorFlag,
|
|
1069
|
+
css: cssFlag,
|
|
1070
|
+
cwd: cwdFlag3,
|
|
1071
|
+
force: forceFlag,
|
|
1072
|
+
registry: registryFlag2
|
|
1073
|
+
}, ({ baseColor, css, cwd, force, registry: registry2 }) => Effect10.gen(function* () {
|
|
1074
|
+
const fileSystem = yield* FileSystem8.FileSystem;
|
|
1075
|
+
const path = yield* Path6.Path;
|
|
1076
|
+
const cwdAbsolute = path.resolve(cwd);
|
|
1077
|
+
const hasPackageJson = yield* fileSystem.exists(path.join(cwdAbsolute, "package.json"));
|
|
1078
|
+
if (!hasPackageJson) {
|
|
1079
|
+
return yield* new ConfigNotFoundError({ cwd: cwdAbsolute });
|
|
1080
|
+
}
|
|
1081
|
+
const packageJson = yield* readJsonFile(path.join(cwdAbsolute, "package.json")).pipe(Effect10.orElseSucceed(() => ({})));
|
|
1082
|
+
const dependencies = packageJson.dependencies ?? {};
|
|
1083
|
+
if (dependencies["foldkit"] === undefined) {
|
|
1084
|
+
yield* Console3.warn("foldkit is not a dependency of this project; foldcn components require a FoldKit app");
|
|
1085
|
+
}
|
|
1086
|
+
const configPath = path.join(cwdAbsolute, "components.json");
|
|
1087
|
+
const hasConfig = yield* fileSystem.exists(configPath);
|
|
1088
|
+
if (hasConfig && !force) {
|
|
1089
|
+
yield* Console3.warn("components.json already exists; pass --force to overwrite");
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
yield* writeJsonFile(configPath, {
|
|
1093
|
+
$schema: componentsSchemaUrl,
|
|
1094
|
+
css,
|
|
1095
|
+
baseColor,
|
|
1096
|
+
aliases: exports_componentsConfig.defaultAliases
|
|
1097
|
+
});
|
|
1098
|
+
yield* Console3.log("Wrote components.json");
|
|
1099
|
+
yield* injectTsconfigAlias(cwdAbsolute);
|
|
1100
|
+
yield* injectViteAlias(cwdAbsolute);
|
|
1101
|
+
const hasStylesheet = yield* fileSystem.exists(path.resolve(cwdAbsolute, css));
|
|
1102
|
+
if (!hasStylesheet) {
|
|
1103
|
+
yield* fileSystem.makeDirectory(path.dirname(path.resolve(cwdAbsolute, css)), { recursive: true });
|
|
1104
|
+
yield* fileSystem.writeFileString(path.resolve(cwdAbsolute, css), `@import 'tailwindcss';
|
|
1105
|
+
`);
|
|
1106
|
+
yield* Console3.log(`Created ${css}`);
|
|
1107
|
+
}
|
|
1108
|
+
yield* runAdd({
|
|
1109
|
+
components: [`theme-${baseColor}`, "utils"],
|
|
1110
|
+
cwd: cwdAbsolute,
|
|
1111
|
+
dryRun: false,
|
|
1112
|
+
overwrite: true,
|
|
1113
|
+
registry: registry2
|
|
1114
|
+
});
|
|
1115
|
+
yield* Console3.log("Project initialized. Try: foldcn add button");
|
|
1116
|
+
})).pipe(Command3.withDescription("Initialize components.json and the base theme in a FoldKit project"));
|
|
1117
|
+
// src/cli.ts
|
|
1118
|
+
var version = "0.0.0";
|
|
1119
|
+
var command = Command4.make("foldcn").pipe(Command4.withDescription("shadcn-style component distribution for FoldKit"), Command4.withSubcommands([addCommand, buildCommand, initCommand]));
|
|
1120
|
+
var run = Effect11.fn("FoldcnCli.run")(function* (argv) {
|
|
1121
|
+
return yield* Command4.runWith(command, { version })(argv);
|
|
1122
|
+
});
|
|
1123
|
+
var main = Command4.run(command, { version });
|
|
1124
|
+
|
|
1125
|
+
// src/bin.ts
|
|
1126
|
+
NodeRuntime.runMain(main.pipe(Effect12.provide(NodeServices.layer)));
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/package.json",
|
|
3
|
+
"name": "foldcn",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"description": "shadcn-style CLI for FoldKit: add copy-in components from a registry",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"bin": {
|
|
9
|
+
"foldcn": "./dist/bin.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "bun build src/bin.ts --outdir dist --target node --format esm --external effect --external @effect/platform-node --external postcss",
|
|
19
|
+
"test": "vitest run --passWithNoTests",
|
|
20
|
+
"typecheck": "tsc --noEmit"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@effect/platform-node": "4.0.0-beta.88",
|
|
24
|
+
"effect": "4.0.0-beta.88",
|
|
25
|
+
"postcss": "8.5.16"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@effect/vitest": "4.0.0-beta.88",
|
|
29
|
+
"@foldcn/registry": "0.0.0",
|
|
30
|
+
"@types/node": "24.12.2",
|
|
31
|
+
"typescript": "6.0.3",
|
|
32
|
+
"vitest": "4.1.9"
|
|
33
|
+
}
|
|
34
|
+
}
|