stera-ui 0.5.0 → 0.6.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/add-JUBSRCDJ.js +304 -0
- package/dist/chunk-2Y64FOD3.js +161 -0
- package/dist/{chunk-3KADOWKC.js → chunk-4E46BULJ.js} +8 -1
- package/dist/chunk-BJNLGEZJ.js +100 -0
- package/dist/chunk-KGMF6UW4.js +21 -0
- package/dist/chunk-KKUJRJ2N.js +209 -0
- package/dist/{chunk-F4NT3JC6.js → chunk-SIXUH4HI.js} +47 -20
- package/dist/cli.js +19 -1339
- package/dist/index.d.ts +3 -3
- package/dist/index.js +14 -10
- package/dist/init-E7O5WAUI.js +796 -0
- package/dist/list-M73QTIWR.js +44 -0
- package/dist/registry/index.d.ts +16 -10
- package/dist/registry/index.js +4 -1
- package/dist/schema/index.d.ts +43 -201
- package/dist/schema/index.js +3 -1
- package/dist/utils/index.d.ts +7 -2
- package/dist/utils/index.js +3 -2
- package/package.json +2 -2
- package/dist/chunk-T3QWOVJR.js +0 -60
package/dist/cli.js
CHANGED
|
@@ -1,1353 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/cli.ts
|
|
4
|
-
import { Command } from "commander";
|
|
5
|
-
|
|
6
|
-
// src/commands/add.ts
|
|
7
|
-
import path6 from "path";
|
|
8
|
-
|
|
9
|
-
// src/utils/resolve-config.ts
|
|
10
|
-
import fs from "fs";
|
|
11
|
-
import path from "path";
|
|
12
|
-
var CONFIG_FILE = "stera.config.json";
|
|
13
|
-
var LEGACY_CONFIG_FILE = "components.json";
|
|
14
|
-
function migrateConfig(raw) {
|
|
15
|
-
if (!("version" in raw) && "tailwind" in raw) {
|
|
16
|
-
const legacy = raw;
|
|
17
|
-
return {
|
|
18
|
-
version: 1,
|
|
19
|
-
css: legacy.tailwind.css || "src/styles/globals.css",
|
|
20
|
-
aliases: legacy.aliases
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
return raw;
|
|
24
|
-
}
|
|
25
|
-
var warnedLegacyFilename = false;
|
|
26
|
-
function warnLegacyFilenameOnce(legacyPath) {
|
|
27
|
-
if (warnedLegacyFilename) return;
|
|
28
|
-
warnedLegacyFilename = true;
|
|
29
|
-
console.warn(
|
|
30
|
-
` \u26A0 Found ${LEGACY_CONFIG_FILE}. Rename to ${CONFIG_FILE} \u2014 legacy filename support will be removed in a future release.
|
|
31
|
-
(${legacyPath})`
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
function findConfigPath(cwd) {
|
|
35
|
-
let dir = path.resolve(cwd);
|
|
36
|
-
while (true) {
|
|
37
|
-
const configPath = path.join(dir, CONFIG_FILE);
|
|
38
|
-
if (fs.existsSync(configPath)) return configPath;
|
|
39
|
-
const legacyPath = path.join(dir, LEGACY_CONFIG_FILE);
|
|
40
|
-
if (fs.existsSync(legacyPath)) return legacyPath;
|
|
41
|
-
const parent = path.dirname(dir);
|
|
42
|
-
if (parent === dir) return null;
|
|
43
|
-
dir = parent;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
function loadConfig(cwd) {
|
|
47
|
-
const configPath = findConfigPath(cwd);
|
|
48
|
-
if (!configPath) return null;
|
|
49
|
-
if (path.basename(configPath) === LEGACY_CONFIG_FILE) {
|
|
50
|
-
warnLegacyFilenameOnce(configPath);
|
|
51
|
-
}
|
|
52
|
-
try {
|
|
53
|
-
const raw = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
54
|
-
return migrateConfig(raw);
|
|
55
|
-
} catch {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// src/registry/index.ts
|
|
61
|
-
var REGISTRY_URL = process.env.REGISTRY_URL ?? "https://ui.stera.sh/r";
|
|
62
|
-
var _indexCache = null;
|
|
63
|
-
async function fetchJSON(url) {
|
|
64
|
-
const res = await fetch(url);
|
|
65
|
-
if (!res.ok) {
|
|
66
|
-
throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`);
|
|
67
|
-
}
|
|
68
|
-
return res.json();
|
|
69
|
-
}
|
|
70
|
-
async function fetchRegistryIndex() {
|
|
71
|
-
if (_indexCache) return _indexCache;
|
|
72
|
-
const data = await fetchJSON(
|
|
73
|
-
`${REGISTRY_URL}/index.json`
|
|
74
|
-
);
|
|
75
|
-
_indexCache = data.items;
|
|
76
|
-
return _indexCache;
|
|
77
|
-
}
|
|
78
|
-
async function fetchRegistryItem(name) {
|
|
79
|
-
return fetchJSON(`${REGISTRY_URL}/${name}.json`);
|
|
80
|
-
}
|
|
81
|
-
async function getComponent(name) {
|
|
82
|
-
const index = await fetchRegistryIndex();
|
|
83
|
-
return index.find((item) => item.name === name);
|
|
84
|
-
}
|
|
85
|
-
async function getAllComponents() {
|
|
86
|
-
return fetchRegistryIndex();
|
|
87
|
-
}
|
|
88
|
-
async function resolveDependencies(names) {
|
|
89
|
-
const resolved = /* @__PURE__ */ new Map();
|
|
90
|
-
const queue = [...names];
|
|
91
|
-
while (queue.length > 0) {
|
|
92
|
-
const name = queue.shift();
|
|
93
|
-
if (resolved.has(name)) continue;
|
|
94
|
-
let item;
|
|
95
|
-
try {
|
|
96
|
-
item = await fetchRegistryItem(name);
|
|
97
|
-
} catch {
|
|
98
|
-
console.error(`Component "${name}" not found in registry.`);
|
|
99
|
-
process.exit(1);
|
|
100
|
-
}
|
|
101
|
-
resolved.set(name, item);
|
|
102
|
-
if (item.registryDependencies) {
|
|
103
|
-
for (const dep of item.registryDependencies) {
|
|
104
|
-
if (!resolved.has(dep)) {
|
|
105
|
-
queue.push(dep);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return Array.from(resolved.values());
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// src/utils/write-files.ts
|
|
114
|
-
import fs2 from "fs";
|
|
115
|
-
import path3 from "path";
|
|
116
|
-
import readline from "readline/promises";
|
|
117
|
-
|
|
118
|
-
// src/utils/resolve-paths.ts
|
|
119
|
-
import path2 from "path";
|
|
120
|
-
import { loadConfig as loadConfig2, createMatchPath } from "tsconfig-paths";
|
|
121
|
-
var TYPE_TO_ALIAS = {
|
|
122
|
-
"registry:ui": "ui",
|
|
123
|
-
"registry:lib": "lib",
|
|
124
|
-
"registry:hook": "hooks",
|
|
125
|
-
"registry:component": "components"
|
|
126
|
-
};
|
|
127
|
-
function resolveAlias(alias, projectRoot) {
|
|
128
|
-
const tsConfig = loadConfig2(projectRoot);
|
|
129
|
-
if (tsConfig.resultType === "success" && Object.keys(tsConfig.paths).length > 0) {
|
|
130
|
-
const hasMatchingPrefix = Object.keys(tsConfig.paths).some((key) => {
|
|
131
|
-
const prefix = key.replace(/\/\*$/, "");
|
|
132
|
-
return alias === prefix || alias.startsWith(`${prefix}/`);
|
|
133
|
-
});
|
|
134
|
-
if (hasMatchingPrefix) {
|
|
135
|
-
const match = createMatchPath(tsConfig.absoluteBaseUrl, tsConfig.paths);
|
|
136
|
-
const resolved = match(alias, void 0, () => true, [".ts", ".tsx"]);
|
|
137
|
-
if (resolved) {
|
|
138
|
-
return path2.relative(projectRoot, resolved);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
throw new Error(
|
|
143
|
-
`Could not resolve alias "${alias}" from tsconfig.json. Ensure compilerOptions.paths defines a mapping for it (e.g. "${alias}/*"), then re-run. See https://ui.stera.sh/docs/installation.`
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
function resolveOutputPath(config, projectRoot, file) {
|
|
147
|
-
const alias = TYPE_TO_ALIAS[file.type];
|
|
148
|
-
if (alias) {
|
|
149
|
-
const dir = resolveAlias(config.aliases[alias], projectRoot);
|
|
150
|
-
const filename = path2.basename(file.path);
|
|
151
|
-
return path2.join(projectRoot, dir, filename);
|
|
152
|
-
}
|
|
153
|
-
if (file.type === "registry:style") {
|
|
154
|
-
const cssDir = path2.dirname(config.css);
|
|
155
|
-
const filename = path2.basename(file.path);
|
|
156
|
-
return path2.join(projectRoot, cssDir, filename);
|
|
157
|
-
}
|
|
158
|
-
return path2.join(projectRoot, file.path);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// src/utils/write-files.ts
|
|
162
|
-
async function confirmOverwrite(relativePath) {
|
|
163
|
-
if (!process.stdin.isTTY) {
|
|
164
|
-
console.log(` Skipping ${relativePath} (already exists, use --overwrite to replace)`);
|
|
165
|
-
return false;
|
|
166
|
-
}
|
|
167
|
-
const rl = readline.createInterface({
|
|
168
|
-
input: process.stdin,
|
|
169
|
-
output: process.stdout
|
|
170
|
-
});
|
|
171
|
-
const answer = await rl.question(` ${relativePath} already exists. Overwrite? (y/N) `);
|
|
172
|
-
rl.close();
|
|
173
|
-
return answer.toLowerCase() === "y";
|
|
174
|
-
}
|
|
175
|
-
async function writeComponentFiles(items, config, projectRoot, options = {}) {
|
|
176
|
-
const written = [];
|
|
177
|
-
const skipped = [];
|
|
178
|
-
for (const item of items) {
|
|
179
|
-
for (const file of item.files ?? []) {
|
|
180
|
-
const outputPath = resolveOutputPath(config, projectRoot, file);
|
|
181
|
-
const relativePath = path3.relative(projectRoot, outputPath);
|
|
182
|
-
if (fs2.existsSync(outputPath)) {
|
|
183
|
-
const existing = fs2.readFileSync(outputPath, "utf-8");
|
|
184
|
-
if (existing === file.content) {
|
|
185
|
-
skipped.push(relativePath);
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
if (!options.overwrite) {
|
|
189
|
-
const confirmed = await confirmOverwrite(relativePath);
|
|
190
|
-
if (!confirmed) {
|
|
191
|
-
skipped.push(relativePath);
|
|
192
|
-
continue;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
const dir = path3.dirname(outputPath);
|
|
197
|
-
fs2.mkdirSync(dir, { recursive: true });
|
|
198
|
-
fs2.writeFileSync(outputPath, file.content ?? "", "utf-8");
|
|
199
|
-
written.push(relativePath);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
return { written, skipped };
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// src/utils/install-deps.ts
|
|
206
|
-
import fs3 from "fs";
|
|
207
|
-
import path4 from "path";
|
|
208
|
-
import { detect } from "@antfu/ni";
|
|
209
|
-
import { execa } from "execa";
|
|
210
|
-
|
|
211
|
-
// src/utils/format.ts
|
|
212
|
-
var BRAND = "\x1B[38;2;166;244;48m";
|
|
213
|
-
var DIM = "\x1B[2m";
|
|
214
|
-
var RESET = "\x1B[0m";
|
|
215
|
-
var enabled = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
216
|
-
var wrap = (code, text) => enabled ? `${code}${text}${RESET}` : text;
|
|
217
|
-
var brand = (text) => wrap(BRAND, text);
|
|
218
|
-
var dim = (text) => wrap(DIM, text);
|
|
219
|
-
var LOGO = brand("\u273B");
|
|
220
|
-
var CHECK = brand("\u2713");
|
|
221
|
-
var CROSS = "\u2717";
|
|
222
|
-
|
|
223
|
-
// src/utils/spinner.ts
|
|
224
|
-
var FRAMES = ["\u2022", "\u2726", "\u2736", "\u273B"].map(brand);
|
|
225
|
-
var INTERVAL_MS = 150;
|
|
226
|
-
function createSpinner(text) {
|
|
227
|
-
const isTTY = process.stdout.isTTY;
|
|
228
|
-
if (!isTTY) {
|
|
229
|
-
process.stdout.write(` ${text}...
|
|
230
|
-
`);
|
|
231
|
-
return {
|
|
232
|
-
succeed(msg) {
|
|
233
|
-
process.stdout.write(` ${CHECK} ${msg}
|
|
234
|
-
`);
|
|
235
|
-
},
|
|
236
|
-
fail(msg) {
|
|
237
|
-
process.stdout.write(` ${CROSS} ${msg}
|
|
238
|
-
`);
|
|
239
|
-
}
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
let frameIndex = 0;
|
|
243
|
-
const interval = setInterval(() => {
|
|
244
|
-
process.stdout.write(`\r ${FRAMES[frameIndex % FRAMES.length]} ${text}`);
|
|
245
|
-
frameIndex++;
|
|
246
|
-
}, INTERVAL_MS);
|
|
247
|
-
const clear = () => {
|
|
248
|
-
clearInterval(interval);
|
|
249
|
-
process.stdout.write("\r" + " ".repeat(text.length + 6) + "\r");
|
|
250
|
-
};
|
|
251
|
-
return {
|
|
252
|
-
succeed(msg) {
|
|
253
|
-
clear();
|
|
254
|
-
process.stdout.write(` ${CHECK} ${msg}
|
|
255
|
-
`);
|
|
256
|
-
},
|
|
257
|
-
fail(msg) {
|
|
258
|
-
clear();
|
|
259
|
-
process.stdout.write(` ${CROSS} ${msg}
|
|
260
|
-
`);
|
|
261
|
-
}
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// src/utils/install-deps.ts
|
|
266
|
-
async function detectPackageManager(cwd) {
|
|
267
|
-
const detected = await detect({ programmatic: true, cwd });
|
|
268
|
-
if (detected === "pnpm@6" || detected === "pnpm") return "pnpm";
|
|
269
|
-
if (detected === "yarn@berry" || detected === "yarn") return "yarn";
|
|
270
|
-
if (detected === "bun") return "bun";
|
|
271
|
-
if (detected === "npm") return "npm";
|
|
272
|
-
const userAgent = process.env.npm_config_user_agent || "";
|
|
273
|
-
if (userAgent.startsWith("pnpm")) return "pnpm";
|
|
274
|
-
if (userAgent.startsWith("yarn")) return "yarn";
|
|
275
|
-
if (userAgent.startsWith("bun")) return "bun";
|
|
276
|
-
return "npm";
|
|
277
|
-
}
|
|
278
|
-
function getInstallArgs(pm, deps) {
|
|
279
|
-
switch (pm) {
|
|
280
|
-
case "pnpm":
|
|
281
|
-
return ["pnpm", ["add", ...deps]];
|
|
282
|
-
case "yarn":
|
|
283
|
-
return ["yarn", ["add", ...deps]];
|
|
284
|
-
case "bun":
|
|
285
|
-
return ["bun", ["add", ...deps]];
|
|
286
|
-
default:
|
|
287
|
-
return ["npm", ["install", ...deps]];
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
async function installDependencies(deps, cwd) {
|
|
291
|
-
if (deps.length === 0) return;
|
|
292
|
-
const pkgPath = path4.join(cwd, "package.json");
|
|
293
|
-
let existing = /* @__PURE__ */ new Set();
|
|
294
|
-
if (fs3.existsSync(pkgPath)) {
|
|
295
|
-
try {
|
|
296
|
-
const pkg = JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
|
|
297
|
-
existing = new Set(Object.keys({ ...pkg.dependencies, ...pkg.devDependencies }));
|
|
298
|
-
} catch {
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
const toInstall = deps.filter((dep) => !existing.has(dep));
|
|
302
|
-
if (toInstall.length === 0) return;
|
|
303
|
-
const pm = await detectPackageManager(cwd);
|
|
304
|
-
const [cmd, args] = getInstallArgs(pm, toInstall);
|
|
305
|
-
const spinner = createSpinner(`Installing with ${pm}`);
|
|
306
|
-
try {
|
|
307
|
-
await execa(cmd, args, { cwd });
|
|
308
|
-
spinner.succeed(`Installed with ${pm}`);
|
|
309
|
-
} catch (error) {
|
|
310
|
-
spinner.fail(`Install failed`);
|
|
311
|
-
if (error instanceof Error && "stdout" in error && typeof error.stdout === "string" && error.stdout.includes("ERR_PNPM_ADDING_TO_ROOT")) {
|
|
312
|
-
throw new Error(
|
|
313
|
-
`Cannot install dependencies at a pnpm workspace root.
|
|
314
|
-
Run stera-ui init from within a specific workspace package instead,
|
|
315
|
-
or use "pnpm add -w <package>" manually to install at the root.`
|
|
316
|
-
);
|
|
317
|
-
}
|
|
318
|
-
throw error;
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// src/utils/transform.ts
|
|
323
|
-
function applyTransforms(content, ctx, transforms2) {
|
|
324
|
-
return transforms2.reduce((acc, fn) => fn(acc, ctx), content);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// src/utils/transform-imports.ts
|
|
328
|
-
var DEFAULT_ALIASES = {
|
|
329
|
-
utils: "@/lib/utils",
|
|
330
|
-
ui: "@/components/ui",
|
|
331
|
-
components: "@/components",
|
|
332
|
-
lib: "@/lib",
|
|
333
|
-
hooks: "@/hooks"
|
|
334
|
-
};
|
|
335
|
-
var transformImports = (content, ctx) => {
|
|
336
|
-
if (!ctx.filename.endsWith(".ts") && !ctx.filename.endsWith(".tsx")) {
|
|
337
|
-
return content;
|
|
338
|
-
}
|
|
339
|
-
const { aliases } = ctx.config;
|
|
340
|
-
const rules = [];
|
|
341
|
-
if (aliases.utils !== DEFAULT_ALIASES.utils) {
|
|
342
|
-
rules.push({ type: "exact", from: DEFAULT_ALIASES.utils, to: aliases.utils });
|
|
343
|
-
}
|
|
344
|
-
if (aliases.ui !== DEFAULT_ALIASES.ui) {
|
|
345
|
-
rules.push({ type: "prefix", from: DEFAULT_ALIASES.ui, to: aliases.ui });
|
|
346
|
-
}
|
|
347
|
-
if (aliases.components !== DEFAULT_ALIASES.components) {
|
|
348
|
-
rules.push({ type: "prefix", from: DEFAULT_ALIASES.components, to: aliases.components });
|
|
349
|
-
}
|
|
350
|
-
if (aliases.lib !== DEFAULT_ALIASES.lib) {
|
|
351
|
-
rules.push({ type: "prefix", from: DEFAULT_ALIASES.lib, to: aliases.lib });
|
|
352
|
-
}
|
|
353
|
-
if (aliases.hooks !== DEFAULT_ALIASES.hooks) {
|
|
354
|
-
rules.push({ type: "prefix", from: DEFAULT_ALIASES.hooks, to: aliases.hooks });
|
|
355
|
-
}
|
|
356
|
-
if (rules.length === 0) {
|
|
357
|
-
return content;
|
|
358
|
-
}
|
|
359
|
-
return content.replace(
|
|
360
|
-
/from\s+(["'])([^"']+)\1/g,
|
|
361
|
-
(match, quote, importPath) => {
|
|
362
|
-
for (const rule of rules) {
|
|
363
|
-
if (rule.type === "exact" && importPath === rule.from) {
|
|
364
|
-
return `from ${quote}${rule.to}${quote}`;
|
|
365
|
-
}
|
|
366
|
-
if (rule.type === "prefix" && importPath.startsWith(rule.from + "/")) {
|
|
367
|
-
const rest = importPath.slice(rule.from.length);
|
|
368
|
-
return `from ${quote}${rule.to}${rest}${quote}`;
|
|
369
|
-
}
|
|
370
|
-
if (rule.type === "prefix" && importPath === rule.from) {
|
|
371
|
-
return `from ${quote}${rule.to}${quote}`;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
return match;
|
|
375
|
-
}
|
|
376
|
-
);
|
|
377
|
-
};
|
|
378
|
-
|
|
379
|
-
// src/utils/write-stera-css.ts
|
|
380
|
-
import fs4 from "fs/promises";
|
|
381
|
-
import path5 from "path";
|
|
382
|
-
function stripImportLines(content) {
|
|
383
|
-
const lines = content.split("\n");
|
|
384
|
-
const imports = [];
|
|
385
|
-
const kept = [];
|
|
386
|
-
for (const line of lines) {
|
|
387
|
-
if (line.trim().match(/^@import\s+["'].+["']/)) {
|
|
388
|
-
if (!line.trim().match(/^@import\s+["']tailwindcss["']/)) {
|
|
389
|
-
imports.push(line.trim());
|
|
390
|
-
}
|
|
391
|
-
} else {
|
|
392
|
-
kept.push(line);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
return { content: kept.join("\n"), imports };
|
|
396
|
-
}
|
|
397
|
-
async function writeSteraUiCss(config, cwd) {
|
|
398
|
-
const item = await fetchRegistryItem("globals");
|
|
399
|
-
const rawContent = item.files?.[0]?.content ?? "";
|
|
400
|
-
const { content, imports } = stripImportLines(rawContent);
|
|
401
|
-
const cssDir = path5.dirname(path5.resolve(cwd, config.css));
|
|
402
|
-
const steraUiPath = path5.join(cssDir, "stera-ui.css");
|
|
403
|
-
await fs4.mkdir(cssDir, { recursive: true });
|
|
404
|
-
await fs4.writeFile(steraUiPath, content, "utf-8");
|
|
405
|
-
return {
|
|
406
|
-
steraUiPath,
|
|
407
|
-
extraImports: imports,
|
|
408
|
-
dependencies: item.dependencies ?? []
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// src/commands/add.ts
|
|
413
|
-
var transforms = [transformImports];
|
|
414
|
-
async function add(components, options) {
|
|
415
|
-
const cwd = options.cwd ? path6.resolve(options.cwd) : process.cwd();
|
|
416
|
-
const configPath = findConfigPath(cwd);
|
|
417
|
-
if (!configPath) {
|
|
418
|
-
console.error(` ${CROSS} ${CONFIG_FILE} not found. Run "stera-ui init" first.`);
|
|
419
|
-
process.exit(1);
|
|
420
|
-
}
|
|
421
|
-
const config = loadConfig(cwd);
|
|
422
|
-
if (!config) {
|
|
423
|
-
console.error(` ${CROSS} Failed to parse ${CONFIG_FILE}.`);
|
|
424
|
-
process.exit(1);
|
|
425
|
-
}
|
|
426
|
-
const projectRoot = path6.dirname(configPath);
|
|
427
|
-
if (components.includes("globals")) {
|
|
428
|
-
if (components.length > 1) {
|
|
429
|
-
console.error(
|
|
430
|
-
` ${CROSS} "stera-ui add globals" refreshes design tokens and cannot be combined with other components.`
|
|
431
|
-
);
|
|
432
|
-
console.error(
|
|
433
|
-
` Run "stera-ui add globals" and "stera-ui add ${components.filter((c) => c !== "globals").join(" ")}" separately.`
|
|
434
|
-
);
|
|
435
|
-
process.exit(1);
|
|
436
|
-
}
|
|
437
|
-
const spinner2 = createSpinner("Refreshing base styles");
|
|
438
|
-
let result;
|
|
439
|
-
try {
|
|
440
|
-
result = await writeSteraUiCss(config, projectRoot);
|
|
441
|
-
} catch (err) {
|
|
442
|
-
spinner2.fail("Failed to refresh base styles");
|
|
443
|
-
throw err;
|
|
444
|
-
}
|
|
445
|
-
spinner2.succeed(
|
|
446
|
-
`Refreshed ${path6.relative(projectRoot, result.steraUiPath)}`
|
|
447
|
-
);
|
|
448
|
-
console.log("");
|
|
449
|
-
return;
|
|
450
|
-
}
|
|
451
|
-
const spinner = createSpinner("Fetching registry");
|
|
452
|
-
try {
|
|
453
|
-
for (const name of components) {
|
|
454
|
-
const item = await getComponent(name);
|
|
455
|
-
if (!item) {
|
|
456
|
-
spinner.fail(`Component "${name}" not found in registry.`);
|
|
457
|
-
console.error(` Run "stera-ui list" to see available components.`);
|
|
458
|
-
process.exit(1);
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
} catch (err) {
|
|
462
|
-
spinner.fail("Failed to fetch registry");
|
|
463
|
-
throw err;
|
|
464
|
-
}
|
|
465
|
-
let resolved;
|
|
466
|
-
try {
|
|
467
|
-
resolved = await resolveDependencies(components);
|
|
468
|
-
} catch (err) {
|
|
469
|
-
spinner.fail("Failed to fetch registry");
|
|
470
|
-
throw err;
|
|
471
|
-
}
|
|
472
|
-
spinner.succeed("Registry resolved");
|
|
473
|
-
for (const item of resolved) {
|
|
474
|
-
for (const file of item.files ?? []) {
|
|
475
|
-
if (file.content) {
|
|
476
|
-
file.content = applyTransforms(
|
|
477
|
-
file.content,
|
|
478
|
-
{ config, filename: file.path },
|
|
479
|
-
transforms
|
|
480
|
-
);
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
const requested = new Set(components);
|
|
485
|
-
console.log("\n Installing components\n");
|
|
486
|
-
for (const item of resolved) {
|
|
487
|
-
const tag = requested.has(item.name) ? "" : dim(" (dependency)");
|
|
488
|
-
console.log(` \u2192 ${item.name}${tag}`);
|
|
489
|
-
}
|
|
490
|
-
const allNpmDeps = [];
|
|
491
|
-
for (const item of resolved) {
|
|
492
|
-
if (item.dependencies) {
|
|
493
|
-
allNpmDeps.push(...item.dependencies);
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
const uniqueNpmDeps = [...new Set(allNpmDeps)].sort();
|
|
497
|
-
if (uniqueNpmDeps.length > 0) {
|
|
498
|
-
console.log("\n npm packages\n");
|
|
499
|
-
for (const dep of uniqueNpmDeps) {
|
|
500
|
-
console.log(` \u2192 ${dep}`);
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
console.log("");
|
|
504
|
-
const { written, skipped } = await writeComponentFiles(
|
|
505
|
-
resolved,
|
|
506
|
-
config,
|
|
507
|
-
projectRoot,
|
|
508
|
-
{ overwrite: options.overwrite }
|
|
509
|
-
);
|
|
510
|
-
for (const file of written) {
|
|
511
|
-
console.log(` ${CHECK} ${file}`);
|
|
512
|
-
}
|
|
513
|
-
for (const file of skipped) {
|
|
514
|
-
console.log(` ${dim(`\xB7 ${file} (unchanged)`)}`);
|
|
515
|
-
}
|
|
516
|
-
if (uniqueNpmDeps.length > 0) {
|
|
517
|
-
console.log("");
|
|
518
|
-
await installDependencies(uniqueNpmDeps, projectRoot);
|
|
519
|
-
}
|
|
520
|
-
console.log("");
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
// src/commands/init.ts
|
|
524
|
-
import fs10 from "fs";
|
|
525
|
-
import fsp from "fs/promises";
|
|
526
|
-
import path10 from "path";
|
|
527
|
-
import { confirm, select } from "@inquirer/prompts";
|
|
528
|
-
|
|
529
|
-
// src/utils/detect-project.ts
|
|
530
|
-
import fs5 from "fs";
|
|
531
|
-
import path7 from "path";
|
|
532
|
-
import { loadConfig as loadConfig3 } from "tsconfig-paths";
|
|
533
|
-
function hasFile(cwd, filenames) {
|
|
534
|
-
return filenames.some((f) => fs5.existsSync(path7.join(cwd, f)));
|
|
535
|
-
}
|
|
536
|
-
function findTailwindCssFile(cwd) {
|
|
537
|
-
const candidates = [
|
|
538
|
-
"src/app/globals.css",
|
|
539
|
-
"src/styles/globals.css",
|
|
540
|
-
"src/index.css",
|
|
541
|
-
"app/globals.css",
|
|
542
|
-
"styles/globals.css",
|
|
543
|
-
"index.css"
|
|
544
|
-
];
|
|
545
|
-
for (const candidate of candidates) {
|
|
546
|
-
const fullPath = path7.join(cwd, candidate);
|
|
547
|
-
if (!fs5.existsSync(fullPath)) continue;
|
|
548
|
-
const content = fs5.readFileSync(fullPath, "utf-8");
|
|
549
|
-
if (content.includes('@import "tailwindcss"') || content.includes("@import 'tailwindcss'") || content.includes("@tailwind base")) {
|
|
550
|
-
return candidate;
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
return null;
|
|
554
|
-
}
|
|
555
|
-
function detectProject(cwd) {
|
|
556
|
-
const hasSrc = fs5.existsSync(path7.join(cwd, "src"));
|
|
557
|
-
let framework = "other";
|
|
558
|
-
if (hasFile(cwd, ["next.config.ts", "next.config.js", "next.config.mjs"])) {
|
|
559
|
-
framework = "next";
|
|
560
|
-
} else if (hasFile(cwd, ["vite.config.ts", "vite.config.js", "vite.config.mjs"])) {
|
|
561
|
-
framework = "vite";
|
|
562
|
-
}
|
|
563
|
-
let rsc = false;
|
|
564
|
-
if (framework === "next") {
|
|
565
|
-
const appDir = hasSrc ? "src/app" : "app";
|
|
566
|
-
rsc = fs5.existsSync(path7.join(cwd, appDir));
|
|
567
|
-
}
|
|
568
|
-
const existingCssFile = findTailwindCssFile(cwd);
|
|
569
|
-
let layoutFile = null;
|
|
570
|
-
if (framework === "next") {
|
|
571
|
-
const appDir = hasSrc ? "src/app" : "app";
|
|
572
|
-
for (const ext of ["tsx", "jsx", "ts", "js"]) {
|
|
573
|
-
const candidate = `${appDir}/layout.${ext}`;
|
|
574
|
-
if (fs5.existsSync(path7.join(cwd, candidate))) {
|
|
575
|
-
layoutFile = candidate;
|
|
576
|
-
break;
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
} else if (framework === "vite") {
|
|
580
|
-
if (fs5.existsSync(path7.join(cwd, "index.html"))) {
|
|
581
|
-
layoutFile = "index.html";
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
return { hasSrc, framework, rsc, existingCssFile, layoutFile };
|
|
585
|
-
}
|
|
586
|
-
function detectAliasPrefix(cwd) {
|
|
587
|
-
let tsConfig;
|
|
588
|
-
try {
|
|
589
|
-
tsConfig = loadConfig3(cwd);
|
|
590
|
-
} catch {
|
|
591
|
-
return null;
|
|
592
|
-
}
|
|
593
|
-
if (tsConfig.resultType !== "success" || Object.keys(tsConfig.paths).length === 0) {
|
|
594
|
-
return null;
|
|
595
|
-
}
|
|
596
|
-
for (const [alias, targets] of Object.entries(tsConfig.paths)) {
|
|
597
|
-
if (targets.includes("./*") || targets.includes("./src/*") || targets.includes("./app/*")) {
|
|
598
|
-
return alias.replace(/\/\*$/, "");
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
const first = Object.keys(tsConfig.paths)[0];
|
|
602
|
-
return first ? first.replace(/\/\*$/, "") : null;
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
// src/utils/detect-fonts.ts
|
|
606
|
-
import fs6 from "fs";
|
|
607
|
-
import path8 from "path";
|
|
608
|
-
function readIfExists(filePath) {
|
|
609
|
-
try {
|
|
610
|
-
return fs6.readFileSync(filePath, "utf-8");
|
|
611
|
-
} catch {
|
|
612
|
-
return null;
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
function detectNextFont(layoutContent) {
|
|
616
|
-
const hasNextFont = /import.*from\s+['"]next\/font/.test(layoutContent) || /import.*from\s+['"]@next\/font/.test(layoutContent) || /import.*from\s+['"]geist\/font/.test(layoutContent);
|
|
617
|
-
let nextFontVariable = null;
|
|
618
|
-
const varMatch = layoutContent.match(/variable:\s*['"](--.+?)['"]/);
|
|
619
|
-
if (varMatch) {
|
|
620
|
-
nextFontVariable = varMatch[1];
|
|
621
|
-
}
|
|
622
|
-
return { hasNextFont, nextFontVariable };
|
|
623
|
-
}
|
|
624
|
-
function detectFontFace(cssContent) {
|
|
625
|
-
const hasFontFace = /@font-face\s*\{/.test(cssContent);
|
|
626
|
-
const fontFamilies = [];
|
|
627
|
-
const regex = /font-family:\s*['"]?([^'";}\n]+?)['"]?\s*;/g;
|
|
628
|
-
let match;
|
|
629
|
-
while ((match = regex.exec(cssContent)) !== null) {
|
|
630
|
-
const family = match[1].trim();
|
|
631
|
-
if (family && !fontFamilies.includes(family)) {
|
|
632
|
-
fontFamilies.push(family);
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
return { hasFontFace, fontFamilies };
|
|
636
|
-
}
|
|
637
|
-
function detectGoogleFonts(content) {
|
|
638
|
-
return content.includes("fonts.googleapis.com") || content.includes("fonts.gstatic.com");
|
|
639
|
-
}
|
|
640
|
-
function detectFonts(cwd, project) {
|
|
641
|
-
const result = {
|
|
642
|
-
hasNextFont: false,
|
|
643
|
-
hasFontFace: false,
|
|
644
|
-
hasGoogleFontsLink: false,
|
|
645
|
-
fontFamilies: [],
|
|
646
|
-
nextFontVariable: null
|
|
647
|
-
};
|
|
648
|
-
if (project.layoutFile) {
|
|
649
|
-
const layoutContent = readIfExists(path8.join(cwd, project.layoutFile));
|
|
650
|
-
if (layoutContent) {
|
|
651
|
-
const nextFontResult = detectNextFont(layoutContent);
|
|
652
|
-
result.hasNextFont = nextFontResult.hasNextFont;
|
|
653
|
-
result.nextFontVariable = nextFontResult.nextFontVariable;
|
|
654
|
-
if (detectGoogleFonts(layoutContent)) {
|
|
655
|
-
result.hasGoogleFontsLink = true;
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
if (project.existingCssFile) {
|
|
660
|
-
const cssContent = readIfExists(path8.join(cwd, project.existingCssFile));
|
|
661
|
-
if (cssContent) {
|
|
662
|
-
const fontFaceResult = detectFontFace(cssContent);
|
|
663
|
-
result.hasFontFace = fontFaceResult.hasFontFace;
|
|
664
|
-
result.fontFamilies.push(...fontFaceResult.fontFamilies);
|
|
665
|
-
if (detectGoogleFonts(cssContent)) {
|
|
666
|
-
result.hasGoogleFontsLink = true;
|
|
667
|
-
}
|
|
668
|
-
const importRegex = /@import\s+['"](.+?)['"]/g;
|
|
669
|
-
let importMatch;
|
|
670
|
-
while ((importMatch = importRegex.exec(cssContent)) !== null) {
|
|
671
|
-
const importPath = importMatch[1];
|
|
672
|
-
const resolvedPath = path8.join(
|
|
673
|
-
cwd,
|
|
674
|
-
path8.dirname(project.existingCssFile),
|
|
675
|
-
importPath
|
|
676
|
-
);
|
|
677
|
-
const importedContent = readIfExists(resolvedPath);
|
|
678
|
-
if (importedContent) {
|
|
679
|
-
const imported = detectFontFace(importedContent);
|
|
680
|
-
if (imported.hasFontFace) result.hasFontFace = true;
|
|
681
|
-
for (const family of imported.fontFamilies) {
|
|
682
|
-
if (!result.fontFamilies.includes(family)) {
|
|
683
|
-
result.fontFamilies.push(family);
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
for (const htmlFile of ["index.html", "src/index.html"]) {
|
|
691
|
-
const htmlContent = readIfExists(path8.join(cwd, htmlFile));
|
|
692
|
-
if (htmlContent && detectGoogleFonts(htmlContent)) {
|
|
693
|
-
result.hasGoogleFontsLink = true;
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
return result;
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
// src/utils/patch-css-vars.ts
|
|
700
|
-
import fs7 from "fs";
|
|
701
|
-
function patchCssVariables(filePath, patches) {
|
|
702
|
-
const content = fs7.readFileSync(filePath, "utf-8");
|
|
703
|
-
const lines = content.split("\n");
|
|
704
|
-
const patched = [];
|
|
705
|
-
for (let i = 0; i < lines.length; i++) {
|
|
706
|
-
for (const [prop, newValue] of Object.entries(patches)) {
|
|
707
|
-
const regex = new RegExp(`(\\s*${escapeRegex(prop)}:\\s*).+?(;)`);
|
|
708
|
-
if (regex.test(lines[i])) {
|
|
709
|
-
lines[i] = lines[i].replace(regex, `$1${newValue}$2`);
|
|
710
|
-
if (!patched.includes(prop)) patched.push(prop);
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
if (patched.length > 0) {
|
|
715
|
-
fs7.writeFileSync(filePath, lines.join("\n"), "utf-8");
|
|
716
|
-
}
|
|
717
|
-
return patched;
|
|
718
|
-
}
|
|
719
|
-
function escapeRegex(str) {
|
|
720
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
// src/utils/update-fonts.ts
|
|
724
|
-
import { existsSync, promises as fs8 } from "fs";
|
|
725
|
-
import path9 from "path";
|
|
726
2
|
import {
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
SyntaxKind,
|
|
730
|
-
VariableDeclarationKind
|
|
731
|
-
} from "ts-morph";
|
|
732
|
-
var ROOT_FONT_VARIABLES = /* @__PURE__ */ new Set([
|
|
733
|
-
"--font-sans",
|
|
734
|
-
"--font-serif",
|
|
735
|
-
"--font-mono"
|
|
736
|
-
]);
|
|
737
|
-
function massageTreeForFonts(fonts, project) {
|
|
738
|
-
const result = {
|
|
739
|
-
cssVars: {},
|
|
740
|
-
dependencies: [],
|
|
741
|
-
cssImports: []
|
|
742
|
-
};
|
|
743
|
-
if (fonts.length === 0) return result;
|
|
744
|
-
const isNext = project.framework === "next";
|
|
745
|
-
for (const font of fonts) {
|
|
746
|
-
if (isNext) {
|
|
747
|
-
result.cssVars[font.font.variable] = `var(${font.font.variable})`;
|
|
748
|
-
} else {
|
|
749
|
-
const fontName = font.name.replace(/^font-/, "");
|
|
750
|
-
const fontSourceDep = font.font.dependency ?? `@fontsource-variable/${fontName}`;
|
|
751
|
-
result.dependencies.push(fontSourceDep);
|
|
752
|
-
result.cssImports.push(`@import "${fontSourceDep}";`);
|
|
753
|
-
result.cssVars[font.font.variable] = font.font.family;
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
return result;
|
|
757
|
-
}
|
|
758
|
-
async function updateFonts(fonts, project, cwd, options = {}) {
|
|
759
|
-
if (fonts.length === 0) return;
|
|
760
|
-
if (project.framework !== "next") return;
|
|
761
|
-
const layoutPath = findLayoutFile(project, cwd);
|
|
762
|
-
if (!layoutPath) return;
|
|
763
|
-
if (options.silent) {
|
|
764
|
-
try {
|
|
765
|
-
const layoutContent = await fs8.readFile(layoutPath, "utf-8");
|
|
766
|
-
const updatedContent = transformLayoutFonts(layoutContent, fonts);
|
|
767
|
-
if (updatedContent !== layoutContent) {
|
|
768
|
-
await fs8.writeFile(layoutPath, updatedContent, "utf-8");
|
|
769
|
-
}
|
|
770
|
-
} catch {
|
|
771
|
-
}
|
|
772
|
-
return;
|
|
773
|
-
}
|
|
774
|
-
const spinner = createSpinner("Updating fonts");
|
|
775
|
-
try {
|
|
776
|
-
const layoutContent = await fs8.readFile(layoutPath, "utf-8");
|
|
777
|
-
const updatedContent = transformLayoutFonts(layoutContent, fonts);
|
|
778
|
-
if (updatedContent !== layoutContent) {
|
|
779
|
-
await fs8.writeFile(layoutPath, updatedContent, "utf-8");
|
|
780
|
-
}
|
|
781
|
-
spinner.succeed("Updated layout with font imports");
|
|
782
|
-
} catch {
|
|
783
|
-
spinner.fail("Failed to update fonts");
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
function findLayoutFile(project, cwd) {
|
|
787
|
-
if (!project.layoutFile) return null;
|
|
788
|
-
const fullPath = path9.join(cwd, project.layoutFile);
|
|
789
|
-
return existsSync(fullPath) ? fullPath : null;
|
|
790
|
-
}
|
|
791
|
-
function transformLayoutFonts(input, fonts) {
|
|
792
|
-
const project = new Project({ compilerOptions: {} });
|
|
793
|
-
const sourceFile = project.createSourceFile("layout.tsx", input, {
|
|
794
|
-
scriptKind: ScriptKind.TSX
|
|
795
|
-
});
|
|
796
|
-
const googleFonts = fonts.filter((f) => f.font.provider === "google");
|
|
797
|
-
const fontVariableNames = [];
|
|
798
|
-
const fontUtilityClasses = [];
|
|
799
|
-
for (const font of googleFonts) {
|
|
800
|
-
const importName = font.font.import;
|
|
801
|
-
if (!importName) continue;
|
|
802
|
-
const existingImport = sourceFile.getImportDeclaration(
|
|
803
|
-
(decl) => decl.getModuleSpecifierValue() === "next/font/google"
|
|
804
|
-
);
|
|
805
|
-
let hasExistingImport = false;
|
|
806
|
-
if (existingImport) {
|
|
807
|
-
const namedImports = existingImport.getNamedImports();
|
|
808
|
-
hasExistingImport = namedImports.some(
|
|
809
|
-
(imp) => imp.getName() === importName
|
|
810
|
-
);
|
|
811
|
-
if (!hasExistingImport) {
|
|
812
|
-
existingImport.addNamedImport(importName);
|
|
813
|
-
}
|
|
814
|
-
} else {
|
|
815
|
-
sourceFile.addImportDeclaration({
|
|
816
|
-
moduleSpecifier: "next/font/google",
|
|
817
|
-
namedImports: [importName]
|
|
818
|
-
});
|
|
819
|
-
}
|
|
820
|
-
const varName = getFontVariableName(importName, font.font.variable);
|
|
821
|
-
const fontOptions = buildFontOptions(font);
|
|
822
|
-
const existingVarDecl = findFontVariableDeclaration(
|
|
823
|
-
sourceFile,
|
|
824
|
-
font.font.variable
|
|
825
|
-
);
|
|
826
|
-
if (hasExistingImport && !existingVarDecl && isRootFontVariable(font.font.variable)) {
|
|
827
|
-
continue;
|
|
828
|
-
}
|
|
829
|
-
if (existingVarDecl) {
|
|
830
|
-
existingVarDecl.setInitializer(`${importName}(${fontOptions})`);
|
|
831
|
-
if (existingVarDecl.getName() !== varName) {
|
|
832
|
-
existingVarDecl.rename(varName);
|
|
833
|
-
}
|
|
834
|
-
} else {
|
|
835
|
-
const insertPosition = findInsertPosition(sourceFile);
|
|
836
|
-
const statement = sourceFile.insertVariableStatement(insertPosition, {
|
|
837
|
-
declarationKind: VariableDeclarationKind.Const,
|
|
838
|
-
declarations: [
|
|
839
|
-
{
|
|
840
|
-
name: varName,
|
|
841
|
-
initializer: `${importName}(${fontOptions})`
|
|
842
|
-
}
|
|
843
|
-
]
|
|
844
|
-
});
|
|
845
|
-
statement.appendWhitespace("\n");
|
|
846
|
-
}
|
|
847
|
-
fontVariableNames.push(varName);
|
|
848
|
-
if (shouldApplyFontUtilityToHtml(font)) {
|
|
849
|
-
fontUtilityClasses.push(font.font.variable.replace("--", ""));
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
const fontFamilyClasses = /* @__PURE__ */ new Set(["font-sans", "font-serif", "font-mono"]);
|
|
853
|
-
const lastFontFamilyClass = [...fontUtilityClasses].reverse().find((cls) => fontFamilyClasses.has(cls));
|
|
854
|
-
const filteredUtilityClasses = fontUtilityClasses.filter(
|
|
855
|
-
(cls) => !fontFamilyClasses.has(cls)
|
|
856
|
-
);
|
|
857
|
-
if (lastFontFamilyClass) {
|
|
858
|
-
filteredUtilityClasses.unshift(lastFontFamilyClass);
|
|
859
|
-
}
|
|
860
|
-
if (fontVariableNames.length > 0) {
|
|
861
|
-
updateHtmlClassName(sourceFile, fontVariableNames, filteredUtilityClasses);
|
|
862
|
-
}
|
|
863
|
-
return sourceFile.getFullText();
|
|
864
|
-
}
|
|
865
|
-
function buildFontOptions(font) {
|
|
866
|
-
const options = {};
|
|
867
|
-
if (font.font.subsets?.length) options.subsets = font.font.subsets;
|
|
868
|
-
if (font.font.weight?.length) options.weight = font.font.weight;
|
|
869
|
-
options.variable = font.font.variable;
|
|
870
|
-
return JSON.stringify(options).replace(/"([^"]+)":/g, "$1:").replace(/"/g, "'");
|
|
871
|
-
}
|
|
872
|
-
function isRootFontVariable(variable) {
|
|
873
|
-
return ROOT_FONT_VARIABLES.has(variable);
|
|
874
|
-
}
|
|
875
|
-
function shouldApplyFontUtilityToHtml(font) {
|
|
876
|
-
return !font.font.selector && isRootFontVariable(font.font.variable);
|
|
877
|
-
}
|
|
878
|
-
function getFontVariableName(importName, variable) {
|
|
879
|
-
const baseName = toCamelCase(importName);
|
|
880
|
-
if (isRootFontVariable(variable)) return baseName;
|
|
881
|
-
return `${baseName}${toPascalCase(variable.replace(/^--font-/, ""))}`;
|
|
882
|
-
}
|
|
883
|
-
function toCamelCase(str) {
|
|
884
|
-
return str.split("_").map(
|
|
885
|
-
(part, index) => index === 0 ? part.toLowerCase() : part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()
|
|
886
|
-
).join("");
|
|
887
|
-
}
|
|
888
|
-
function toPascalCase(str) {
|
|
889
|
-
return str.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
890
|
-
}
|
|
891
|
-
function findFontVariableDeclaration(sourceFile, variable) {
|
|
892
|
-
for (const statement of sourceFile.getVariableStatements()) {
|
|
893
|
-
for (const declaration of statement.getDeclarations()) {
|
|
894
|
-
const initializer = declaration.getInitializer();
|
|
895
|
-
if (!initializer || initializer.getKind() !== SyntaxKind.CallExpression)
|
|
896
|
-
continue;
|
|
897
|
-
const args = initializer.getArguments();
|
|
898
|
-
if (args.length === 0) continue;
|
|
899
|
-
const argText = args[0].getText();
|
|
900
|
-
if (argText.includes("variable:") && argText.includes(variable)) {
|
|
901
|
-
return declaration;
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
return null;
|
|
906
|
-
}
|
|
907
|
-
function findInsertPosition(sourceFile) {
|
|
908
|
-
const imports = sourceFile.getImportDeclarations();
|
|
909
|
-
if (imports.length > 0) {
|
|
910
|
-
return imports[imports.length - 1].getChildIndex() + 1;
|
|
911
|
-
}
|
|
912
|
-
return 0;
|
|
913
|
-
}
|
|
914
|
-
function updateHtmlClassName(sourceFile, fontVariableNames, fontUtilityClasses) {
|
|
915
|
-
const jsxElements = sourceFile.getDescendantsOfKind(
|
|
916
|
-
SyntaxKind.JsxOpeningElement
|
|
917
|
-
);
|
|
918
|
-
for (const element of jsxElements) {
|
|
919
|
-
if (element.getTagNameNode().getText() !== "html") continue;
|
|
920
|
-
const newUtilityClasses = fontUtilityClasses.map((cls) => `"${cls}"`);
|
|
921
|
-
const newVarExpressions = fontVariableNames.map(
|
|
922
|
-
(name) => `${name}.variable`
|
|
923
|
-
);
|
|
924
|
-
const allNewArgs = [...newUtilityClasses, ...newVarExpressions];
|
|
925
|
-
const classNameAttr = element.getAttribute("className");
|
|
926
|
-
if (!classNameAttr) {
|
|
927
|
-
element.addAttribute({
|
|
928
|
-
name: "className",
|
|
929
|
-
initializer: `{cn(${allNewArgs.join(", ")})}`
|
|
930
|
-
});
|
|
931
|
-
ensureCnImport(sourceFile);
|
|
932
|
-
return;
|
|
933
|
-
}
|
|
934
|
-
if (classNameAttr.getKind() !== SyntaxKind.JsxAttribute) return;
|
|
935
|
-
const jsxAttr = classNameAttr.asKindOrThrow(SyntaxKind.JsxAttribute);
|
|
936
|
-
const initializer = jsxAttr.getInitializer();
|
|
937
|
-
if (!initializer) return;
|
|
938
|
-
if (initializer.getKind() === SyntaxKind.StringLiteral) {
|
|
939
|
-
const currentValue = initializer.getText().slice(1, -1);
|
|
940
|
-
ensureCnImport(sourceFile);
|
|
941
|
-
jsxAttr.setInitializer(
|
|
942
|
-
`{cn("${currentValue}", ${allNewArgs.join(", ")})}`
|
|
943
|
-
);
|
|
944
|
-
} else if (initializer.getKind() === SyntaxKind.JsxExpression) {
|
|
945
|
-
const jsxExpr = initializer.asKindOrThrow(SyntaxKind.JsxExpression);
|
|
946
|
-
const expr = jsxExpr.getExpression();
|
|
947
|
-
if (!expr) return;
|
|
948
|
-
const exprText = expr.getText();
|
|
949
|
-
if (exprText.startsWith("cn(")) {
|
|
950
|
-
const hasAllFontVars = newVarExpressions.every(
|
|
951
|
-
(v) => exprText.includes(v)
|
|
952
|
-
);
|
|
953
|
-
const hasAllUtilityClasses = fontUtilityClasses.every(
|
|
954
|
-
(cls) => exprText.includes(`"${cls}"`)
|
|
955
|
-
);
|
|
956
|
-
if (hasAllFontVars && hasAllUtilityClasses) continue;
|
|
957
|
-
let cleanedExpr = exprText;
|
|
958
|
-
for (const varExpr of newVarExpressions) {
|
|
959
|
-
cleanedExpr = cleanedExpr.replace(
|
|
960
|
-
new RegExp(`,?\\s*${varExpr.replace(".", "\\.")}`, "g"),
|
|
961
|
-
""
|
|
962
|
-
);
|
|
963
|
-
}
|
|
964
|
-
cleanedExpr = cleanedExpr.replace(/cn\(\s*,/, "cn(");
|
|
965
|
-
const newExpr = cleanedExpr.replace(
|
|
966
|
-
/\)$/,
|
|
967
|
-
`, ${allNewArgs.join(", ")})`
|
|
968
|
-
);
|
|
969
|
-
jsxExpr.replaceWithText(`{${newExpr}}`);
|
|
970
|
-
} else if (/^\w+\.variable$/.test(exprText)) {
|
|
971
|
-
ensureCnImport(sourceFile);
|
|
972
|
-
jsxExpr.replaceWithText(`{cn(${allNewArgs.join(", ")})}`);
|
|
973
|
-
} else if (exprText.startsWith("`") && exprText.endsWith("`")) {
|
|
974
|
-
const cnArgs = parseTemplateLiteralToCnArgs(exprText);
|
|
975
|
-
ensureCnImport(sourceFile);
|
|
976
|
-
const allNewArgsSet = new Set(allNewArgs);
|
|
977
|
-
const fontFamilyLiterals = new Set(
|
|
978
|
-
["font-sans", "font-serif", "font-mono"].map((c) => `"${c}"`)
|
|
979
|
-
);
|
|
980
|
-
const cleanedCnArgs = cnArgs.filter(
|
|
981
|
-
(arg) => !allNewArgsSet.has(arg) && !fontFamilyLiterals.has(arg)
|
|
982
|
-
);
|
|
983
|
-
jsxExpr.replaceWithText(
|
|
984
|
-
`{cn(${[...cleanedCnArgs, ...allNewArgs].join(", ")})}`
|
|
985
|
-
);
|
|
986
|
-
} else {
|
|
987
|
-
ensureCnImport(sourceFile);
|
|
988
|
-
jsxExpr.replaceWithText(`{cn(${exprText}, ${allNewArgs.join(", ")})}`);
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
function ensureCnImport(sourceFile) {
|
|
994
|
-
const existingImport = sourceFile.getImportDeclaration(
|
|
995
|
-
(decl) => decl.getNamedImports().some((imp) => imp.getName() === "cn")
|
|
996
|
-
);
|
|
997
|
-
if (!existingImport) {
|
|
998
|
-
const utilsImport = sourceFile.getImportDeclaration(
|
|
999
|
-
(decl) => decl.getModuleSpecifierValue().includes("/lib/utils")
|
|
1000
|
-
);
|
|
1001
|
-
if (utilsImport) {
|
|
1002
|
-
const namedImports = utilsImport.getNamedImports();
|
|
1003
|
-
if (!namedImports.some((imp) => imp.getName() === "cn")) {
|
|
1004
|
-
utilsImport.addNamedImport("cn");
|
|
1005
|
-
}
|
|
1006
|
-
} else {
|
|
1007
|
-
sourceFile.addImportDeclaration({
|
|
1008
|
-
moduleSpecifier: "@/lib/utils",
|
|
1009
|
-
namedImports: ["cn"]
|
|
1010
|
-
});
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
function parseTemplateLiteralToCnArgs(templateLiteral) {
|
|
1015
|
-
const staticArgs = [];
|
|
1016
|
-
const variableArgs = [];
|
|
1017
|
-
const content = templateLiteral.slice(1, -1);
|
|
1018
|
-
const parts = content.split(/(\$\{[^}]+\})/);
|
|
1019
|
-
for (const part of parts) {
|
|
1020
|
-
if (!part) continue;
|
|
1021
|
-
if (part.startsWith("${") && part.endsWith("}")) {
|
|
1022
|
-
const expr = part.slice(2, -1).trim();
|
|
1023
|
-
if (expr) variableArgs.push(expr);
|
|
1024
|
-
} else {
|
|
1025
|
-
const staticParts = part.trim().split(/\s+/).filter(Boolean);
|
|
1026
|
-
for (const staticPart of staticParts) {
|
|
1027
|
-
staticArgs.push(`"${staticPart}"`);
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
return [...staticArgs, ...variableArgs];
|
|
1032
|
-
}
|
|
1033
|
-
|
|
1034
|
-
// src/utils/css-imports.ts
|
|
1035
|
-
import fs9 from "fs/promises";
|
|
1036
|
-
async function insertImportLine(filePath, importStatement) {
|
|
1037
|
-
const content = await fs9.readFile(filePath, "utf-8");
|
|
1038
|
-
const importPath = importStatement.match(/["']([^"']+)["']/)?.[1];
|
|
1039
|
-
if (importPath) {
|
|
1040
|
-
const escaped = importPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1041
|
-
if (new RegExp(`@import\\s+["']${escaped}["']`).test(content)) return;
|
|
1042
|
-
}
|
|
1043
|
-
const lines = content.split("\n");
|
|
1044
|
-
let lastImportIndex = -1;
|
|
1045
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1046
|
-
if (lines[i].trim().startsWith("@import")) {
|
|
1047
|
-
lastImportIndex = i;
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
if (lastImportIndex >= 0) {
|
|
1051
|
-
lines.splice(lastImportIndex + 1, 0, importStatement);
|
|
1052
|
-
} else {
|
|
1053
|
-
lines.unshift(importStatement);
|
|
1054
|
-
}
|
|
1055
|
-
await fs9.writeFile(filePath, lines.join("\n"), "utf-8");
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
// src/commands/init.ts
|
|
1059
|
-
var DEFAULT_FONT_NAMES = ["font-geist", "font-geist-mono"];
|
|
1060
|
-
function buildConfig(project, strategy, aliasPrefix) {
|
|
1061
|
-
let cssPath;
|
|
1062
|
-
if (project.existingCssFile) {
|
|
1063
|
-
cssPath = project.existingCssFile;
|
|
1064
|
-
} else if (project.hasSrc) {
|
|
1065
|
-
cssPath = "src/styles/globals.css";
|
|
1066
|
-
} else {
|
|
1067
|
-
cssPath = "styles/globals.css";
|
|
1068
|
-
}
|
|
1069
|
-
return {
|
|
1070
|
-
$schema: "https://ui.stera.sh/schema.json",
|
|
1071
|
-
version: 1,
|
|
1072
|
-
css: cssPath,
|
|
1073
|
-
aliases: {
|
|
1074
|
-
components: `${aliasPrefix}/components`,
|
|
1075
|
-
utils: `${aliasPrefix}/lib/utils`,
|
|
1076
|
-
ui: `${aliasPrefix}/components/ui`,
|
|
1077
|
-
lib: `${aliasPrefix}/lib`,
|
|
1078
|
-
hooks: `${aliasPrefix}/hooks`
|
|
1079
|
-
},
|
|
1080
|
-
fonts: {
|
|
1081
|
-
strategy
|
|
1082
|
-
}
|
|
1083
|
-
};
|
|
1084
|
-
}
|
|
1085
|
-
async function promptFontStrategy(detected, skipPrompts) {
|
|
1086
|
-
const hasExistingFonts = detected.hasNextFont || detected.hasFontFace || detected.hasGoogleFontsLink;
|
|
1087
|
-
const defaultStrategy = hasExistingFonts ? "keep-existing" : "stera-default";
|
|
1088
|
-
if (skipPrompts) return defaultStrategy;
|
|
1089
|
-
if (hasExistingFonts) {
|
|
1090
|
-
const sources = [];
|
|
1091
|
-
if (detected.fontFamilies.length > 0) {
|
|
1092
|
-
sources.push(detected.fontFamilies.join(", "));
|
|
1093
|
-
}
|
|
1094
|
-
if (detected.hasNextFont) sources.push("next/font");
|
|
1095
|
-
if (detected.hasGoogleFontsLink) sources.push("Google Fonts");
|
|
1096
|
-
console.log(` Detected existing fonts: ${sources.join(" via ")}
|
|
1097
|
-
`);
|
|
1098
|
-
}
|
|
1099
|
-
const choices = [];
|
|
1100
|
-
if (hasExistingFonts) {
|
|
1101
|
-
choices.push({
|
|
1102
|
-
value: "keep-existing",
|
|
1103
|
-
name: "Keep my current fonts",
|
|
1104
|
-
description: "Update CSS variables to use your existing fonts"
|
|
1105
|
-
});
|
|
1106
|
-
}
|
|
1107
|
-
choices.push({
|
|
1108
|
-
value: "stera-default",
|
|
1109
|
-
name: "Use Stera UI default fonts (Geist Sans + Geist Mono)",
|
|
1110
|
-
description: "Optimized variable fonts via next/font or fontsource"
|
|
1111
|
-
});
|
|
1112
|
-
choices.push({
|
|
1113
|
-
value: "skip",
|
|
1114
|
-
name: "Skip font setup",
|
|
1115
|
-
description: "Configure fonts manually later"
|
|
1116
|
-
});
|
|
1117
|
-
const strategy = await select({
|
|
1118
|
-
message: "How would you like to set up fonts?",
|
|
1119
|
-
choices,
|
|
1120
|
-
default: defaultStrategy
|
|
1121
|
-
});
|
|
1122
|
-
console.log("");
|
|
1123
|
-
return strategy;
|
|
1124
|
-
}
|
|
1125
|
-
async function fetchDefaultFonts() {
|
|
1126
|
-
const spinner = createSpinner("Fetching fonts");
|
|
1127
|
-
const fonts = [];
|
|
1128
|
-
try {
|
|
1129
|
-
for (const name of DEFAULT_FONT_NAMES) {
|
|
1130
|
-
try {
|
|
1131
|
-
const item = await fetchRegistryItem(name);
|
|
1132
|
-
if (item.type === "registry:font") {
|
|
1133
|
-
fonts.push(item);
|
|
1134
|
-
}
|
|
1135
|
-
} catch {
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
} catch (err) {
|
|
1139
|
-
spinner.fail("Failed to fetch fonts");
|
|
1140
|
-
throw err;
|
|
1141
|
-
}
|
|
1142
|
-
spinner.succeed("Fonts resolved");
|
|
1143
|
-
return fonts;
|
|
1144
|
-
}
|
|
1145
|
-
async function executeFontStrategy(strategy, project, detected, config, cwd) {
|
|
1146
|
-
if (strategy === "skip") return;
|
|
1147
|
-
const cssDir = path10.dirname(path10.resolve(cwd, config.css));
|
|
1148
|
-
const steraUiPath = path10.join(cssDir, "stera-ui.css");
|
|
1149
|
-
if (strategy === "keep-existing") {
|
|
1150
|
-
const fontFamily = detected.fontFamilies.length > 0 ? detected.fontFamilies[0] : null;
|
|
1151
|
-
const fontValue = detected.nextFontVariable ? `var(${detected.nextFontVariable})` : fontFamily ?? "Geist";
|
|
1152
|
-
const patched = patchCssVariables(steraUiPath, {
|
|
1153
|
-
"--font-heading": fontValue,
|
|
1154
|
-
"--font-sans": fontValue
|
|
1155
|
-
});
|
|
1156
|
-
if (patched.length > 0) {
|
|
1157
|
-
console.log(` ${CHECK} Updated font variables to use ${fontValue}`);
|
|
1158
|
-
}
|
|
1159
|
-
return;
|
|
1160
|
-
}
|
|
1161
|
-
const fonts = await fetchDefaultFonts();
|
|
1162
|
-
if (fonts.length === 0) return;
|
|
1163
|
-
const fontTree = massageTreeForFonts(fonts, project);
|
|
1164
|
-
if (fontTree.dependencies.length > 0) {
|
|
1165
|
-
await installDependencies(fontTree.dependencies, cwd);
|
|
1166
|
-
}
|
|
1167
|
-
if (fontTree.cssImports.length > 0) {
|
|
1168
|
-
for (const importLine of fontTree.cssImports) {
|
|
1169
|
-
await insertImportLine(steraUiPath, importLine);
|
|
1170
|
-
}
|
|
1171
|
-
console.log(` ${CHECK} Added font imports to ${path10.relative(cwd, steraUiPath)}`);
|
|
1172
|
-
}
|
|
1173
|
-
if (Object.keys(fontTree.cssVars).length > 0) {
|
|
1174
|
-
const patched = patchCssVariables(steraUiPath, fontTree.cssVars);
|
|
1175
|
-
if (patched.length > 0) {
|
|
1176
|
-
console.log(` ${CHECK} Updated font variables in ${path10.relative(cwd, steraUiPath)}`);
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
await updateFonts(fonts, project, cwd);
|
|
1180
|
-
}
|
|
1181
|
-
async function init(options) {
|
|
1182
|
-
const cwd = options.cwd ? path10.resolve(options.cwd) : process.cwd();
|
|
1183
|
-
console.log(`
|
|
1184
|
-
${LOGO} Stera UI
|
|
1185
|
-
`);
|
|
1186
|
-
const existing = findConfigPath(cwd);
|
|
1187
|
-
if (existing) {
|
|
1188
|
-
const existingName = path10.basename(existing);
|
|
1189
|
-
if (existingName === LEGACY_CONFIG_FILE) {
|
|
1190
|
-
console.log(
|
|
1191
|
-
` Found legacy ${LEGACY_CONFIG_FILE} at ${existing}`
|
|
1192
|
-
);
|
|
1193
|
-
const shouldRename = options.yes ? true : await confirm({
|
|
1194
|
-
message: `Rename it to ${CONFIG_FILE}?`,
|
|
1195
|
-
default: true
|
|
1196
|
-
});
|
|
1197
|
-
if (shouldRename) {
|
|
1198
|
-
const renamed = path10.join(path10.dirname(existing), CONFIG_FILE);
|
|
1199
|
-
fs10.renameSync(existing, renamed);
|
|
1200
|
-
console.log(` ${CHECK} Renamed to ${CONFIG_FILE}`);
|
|
1201
|
-
console.log(' Run "stera-ui add <component>" to add components.\n');
|
|
1202
|
-
} else {
|
|
1203
|
-
console.log(
|
|
1204
|
-
` Keeping ${LEGACY_CONFIG_FILE} for now. Legacy filename support will be removed in a future release.
|
|
1205
|
-
`
|
|
1206
|
-
);
|
|
1207
|
-
}
|
|
1208
|
-
return;
|
|
1209
|
-
}
|
|
1210
|
-
console.log(` ${CONFIG_FILE} already exists at ${existing}`);
|
|
1211
|
-
console.log(' Run "stera-ui add <component>" to add components.\n');
|
|
1212
|
-
return;
|
|
1213
|
-
}
|
|
1214
|
-
const pkgPath = path10.join(cwd, "package.json");
|
|
1215
|
-
if (!fs10.existsSync(pkgPath)) {
|
|
1216
|
-
console.error(
|
|
1217
|
-
" Error: No package.json found. Please run this in a project directory."
|
|
1218
|
-
);
|
|
1219
|
-
process.exit(1);
|
|
1220
|
-
}
|
|
1221
|
-
const project = detectProject(cwd);
|
|
1222
|
-
const aliasPrefix = detectAliasPrefix(cwd);
|
|
1223
|
-
if (!aliasPrefix) {
|
|
1224
|
-
console.error("");
|
|
1225
|
-
console.error(" Error: No import alias found in your tsconfig.json file.");
|
|
1226
|
-
console.error(
|
|
1227
|
-
' Stera UI requires a path alias (e.g. "@/*": ["./src/*"]) in compilerOptions.paths.'
|
|
1228
|
-
);
|
|
1229
|
-
console.error(
|
|
1230
|
-
" See https://ui.stera.sh/docs/installation for setup instructions per framework."
|
|
1231
|
-
);
|
|
1232
|
-
console.error("");
|
|
1233
|
-
process.exit(1);
|
|
1234
|
-
}
|
|
1235
|
-
const frameworkName = project.framework === "next" ? "Next.js" : project.framework === "vite" ? "Vite" : null;
|
|
1236
|
-
const parts = [];
|
|
1237
|
-
if (frameworkName) parts.push(frameworkName);
|
|
1238
|
-
if (project.hasSrc) parts.push("src/ directory");
|
|
1239
|
-
if (parts.length > 0) {
|
|
1240
|
-
console.log(` Detected ${parts.join(" project with ")} project
|
|
1241
|
-
`);
|
|
1242
|
-
}
|
|
1243
|
-
const detectedFonts = detectFonts(cwd, project);
|
|
1244
|
-
const strategy = await promptFontStrategy(
|
|
1245
|
-
detectedFonts,
|
|
1246
|
-
options.yes ?? false
|
|
1247
|
-
);
|
|
1248
|
-
const config = buildConfig(project, strategy, aliasPrefix);
|
|
1249
|
-
const configPath = path10.join(cwd, CONFIG_FILE);
|
|
1250
|
-
fs10.writeFileSync(
|
|
1251
|
-
configPath,
|
|
1252
|
-
JSON.stringify(config, null, 2) + "\n",
|
|
1253
|
-
"utf-8"
|
|
1254
|
-
);
|
|
1255
|
-
console.log(` ${CHECK} Created ${CONFIG_FILE}`);
|
|
1256
|
-
const fetchSpinner = createSpinner("Fetching base styles");
|
|
1257
|
-
let steraCssResult;
|
|
1258
|
-
try {
|
|
1259
|
-
steraCssResult = await writeSteraUiCss(config, cwd);
|
|
1260
|
-
} catch (err) {
|
|
1261
|
-
fetchSpinner.fail("Failed to fetch base styles");
|
|
1262
|
-
throw err;
|
|
1263
|
-
}
|
|
1264
|
-
fetchSpinner.succeed("Base styles resolved");
|
|
1265
|
-
console.log(
|
|
1266
|
-
` ${CHECK} Created ${path10.relative(cwd, steraCssResult.steraUiPath)}`
|
|
1267
|
-
);
|
|
1268
|
-
const globalsPath = project.existingCssFile ? path10.resolve(cwd, project.existingCssFile) : path10.resolve(cwd, config.css);
|
|
1269
|
-
if (fs10.existsSync(globalsPath)) {
|
|
1270
|
-
for (const imp of steraCssResult.extraImports) {
|
|
1271
|
-
await insertImportLine(globalsPath, imp);
|
|
1272
|
-
}
|
|
1273
|
-
await insertImportLine(globalsPath, '@import "./stera-ui.css";');
|
|
1274
|
-
console.log(
|
|
1275
|
-
` ${CHECK} Added @import to ${path10.relative(cwd, globalsPath)}`
|
|
1276
|
-
);
|
|
1277
|
-
} else {
|
|
1278
|
-
await fsp.mkdir(path10.dirname(globalsPath), { recursive: true });
|
|
1279
|
-
const scaffold = [
|
|
1280
|
-
'@import "tailwindcss";',
|
|
1281
|
-
...steraCssResult.extraImports,
|
|
1282
|
-
'@import "./stera-ui.css";'
|
|
1283
|
-
].join("\n") + "\n";
|
|
1284
|
-
await fsp.writeFile(globalsPath, scaffold, "utf-8");
|
|
1285
|
-
console.log(` ${CHECK} Created ${path10.relative(cwd, globalsPath)}`);
|
|
1286
|
-
}
|
|
1287
|
-
await executeFontStrategy(
|
|
1288
|
-
strategy,
|
|
1289
|
-
project,
|
|
1290
|
-
detectedFonts,
|
|
1291
|
-
config,
|
|
1292
|
-
cwd
|
|
1293
|
-
);
|
|
1294
|
-
if (steraCssResult.dependencies.length > 0) {
|
|
1295
|
-
const deps = [...new Set(steraCssResult.dependencies)].sort();
|
|
1296
|
-
console.log("");
|
|
1297
|
-
await installDependencies(deps, cwd);
|
|
1298
|
-
}
|
|
1299
|
-
console.log("");
|
|
1300
|
-
console.log(" Next steps:");
|
|
1301
|
-
console.log(" stera-ui add <component>");
|
|
1302
|
-
if (strategy === "skip") {
|
|
1303
|
-
console.log("");
|
|
1304
|
-
console.log(` ${dim("Font setup was skipped. See https://ui.stera.sh for font configuration docs.")}`);
|
|
1305
|
-
}
|
|
1306
|
-
console.log("");
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
// src/commands/list.ts
|
|
1310
|
-
async function list() {
|
|
1311
|
-
const spinner = createSpinner("Fetching registry");
|
|
1312
|
-
let items;
|
|
1313
|
-
try {
|
|
1314
|
-
items = await getAllComponents();
|
|
1315
|
-
} catch (err) {
|
|
1316
|
-
spinner.fail("Failed to fetch registry");
|
|
1317
|
-
throw err;
|
|
1318
|
-
}
|
|
1319
|
-
spinner.succeed("Registry resolved");
|
|
1320
|
-
const groups = /* @__PURE__ */ new Map();
|
|
1321
|
-
for (const item of items) {
|
|
1322
|
-
const type = item.type.replace("registry:", "");
|
|
1323
|
-
if (!groups.has(type)) groups.set(type, []);
|
|
1324
|
-
groups.get(type).push(item);
|
|
1325
|
-
}
|
|
1326
|
-
console.log(`
|
|
1327
|
-
${LOGO} Available components
|
|
1328
|
-
`);
|
|
1329
|
-
for (const [type, groupItems] of groups) {
|
|
1330
|
-
console.log(` ${type}`);
|
|
1331
|
-
for (const item of groupItems) {
|
|
1332
|
-
const deps = item.registryDependencies?.length ? dim(` (depends on: ${item.registryDependencies.join(", ")})`) : "";
|
|
1333
|
-
console.log(` ${item.name}${deps}`);
|
|
1334
|
-
}
|
|
1335
|
-
console.log("");
|
|
1336
|
-
}
|
|
1337
|
-
console.log(` ${dim(`${items.length} components available`)}
|
|
1338
|
-
`);
|
|
1339
|
-
}
|
|
3
|
+
CROSS
|
|
4
|
+
} from "./chunk-KGMF6UW4.js";
|
|
1340
5
|
|
|
1341
6
|
// src/cli.ts
|
|
7
|
+
import { createRequire } from "module";
|
|
8
|
+
import { Command } from "commander";
|
|
9
|
+
var pkg = createRequire(import.meta.url)("../package.json");
|
|
1342
10
|
var program = new Command();
|
|
1343
|
-
program.name("stera-ui").description("Add Stera UI components to your project.").version(
|
|
11
|
+
program.name("stera-ui").description("Add Stera UI components to your project.").version(pkg.version);
|
|
1344
12
|
program.command("init").description("Initialize Stera UI in your project.").option("-c, --cwd <path>", "Working directory", process.cwd()).option("-y, --yes", "Skip confirmation prompts").action(async (options) => {
|
|
13
|
+
const { init } = await import("./init-E7O5WAUI.js");
|
|
1345
14
|
await init(options);
|
|
1346
15
|
});
|
|
1347
16
|
program.command("add").description("Add components to your project.").argument("<components...>", "Components to add").option("-c, --cwd <path>", "Working directory", process.cwd()).option("-y, --yes", "Skip confirmation prompts").option("-o, --overwrite", "Overwrite existing files without prompting").action(async (components, options) => {
|
|
17
|
+
const { add } = await import("./add-JUBSRCDJ.js");
|
|
1348
18
|
await add(components, options);
|
|
1349
19
|
});
|
|
1350
20
|
program.command("list").description("List all available components.").action(async () => {
|
|
21
|
+
const { list } = await import("./list-M73QTIWR.js");
|
|
1351
22
|
await list();
|
|
1352
23
|
});
|
|
1353
|
-
program.
|
|
24
|
+
program.parseAsync().catch((error) => {
|
|
25
|
+
if (error instanceof Error && error.name === "ExitPromptError") {
|
|
26
|
+
process.exit(130);
|
|
27
|
+
}
|
|
28
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
29
|
+
console.error(`
|
|
30
|
+
${CROSS} ${message}
|
|
31
|
+
`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
});
|