bejamas 0.0.0-canary.0138fd3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -0
- package/dist/generate-mdx-BSRQJPrF.js +1205 -0
- package/dist/generate-mdx-BSRQJPrF.js.map +1 -0
- package/dist/index.js +3717 -0
- package/dist/index.js.map +1 -0
- package/dist/utils-Cxc_kZGv.js +9757 -0
- package/dist/utils-Cxc_kZGv.js.map +1 -0
- package/package.json +69 -0
- package/src/tailwind.css +636 -0
- package/tailwind.css +1 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,3717 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { $ as PRESET_RADII, A as getSemanticIconNameFromLucideExport, B as getHeadingFontValue, C as BASE_COLORS, D as buildRegistryTheme, E as ICON_LIBRARIES, F as designSystemConfigSchema, G as DEFAULT_PRESET_CONFIG, H as getStyleDefaultRadius, I as getDocumentDirection, J as PRESET_FONTS, K as PRESET_BASE_COLORS, L as getDocumentLanguage, M as renderSemanticIconSvgWithAttributeString, N as DEFAULT_DESIGN_SYSTEM_CONFIG, O as ICON_LIBRARY_COLLECTIONS, P as RTL_LANGUAGE_VALUES, Q as PRESET_MENU_COLORS, R as getFontPackageName, S as BEJAMAS_COMPONENTS_SCHEMA_URL, T as resolveRegistryUrl, U as getStyleId, V as getRadiusValue, W as normalizeDesignSystemConfig, X as PRESET_ICON_LIBRARIES, Y as PRESET_FONT_HEADINGS, Z as PRESET_MENU_ACCENTS, _ as getConfig, at as isPresetCode, b as logger, d as parseJsDocMetadata, et as PRESET_STYLES, g as spinner, it as encodePreset, j as getSemanticIconNameFromLucidePath, k as SEMANTIC_ICON_NAMES, nt as V1_CHART_COLOR_MAP, o as extractFrontmatter, ot as fonts, p as resolveUiRoot, q as PRESET_CHART_COLORS, rt as decodePreset, tt as PRESET_THEMES, v as getWorkspaceConfig, w as buildUiUrl, x as highlighter, y as getProjectInfo, z as getFontValue } from "./utils-Cxc_kZGv.js";
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { createRequire } from "module";
|
|
5
|
+
import path, { extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import path$1 from "path";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
import { existsSync, promises, readFileSync, readdirSync } from "node:fs";
|
|
10
|
+
import fs from "fs-extra";
|
|
11
|
+
import fg from "fast-glob";
|
|
12
|
+
import fs$1 from "node:fs/promises";
|
|
13
|
+
import { bold, cyan, dim, green, magenta, red, white, yellow } from "kleur/colors";
|
|
14
|
+
import os from "os";
|
|
15
|
+
import dotenv from "dotenv";
|
|
16
|
+
import { detect } from "@antfu/ni";
|
|
17
|
+
import { execa } from "execa";
|
|
18
|
+
import prompts from "prompts";
|
|
19
|
+
import * as tar from "tar";
|
|
20
|
+
import os$1 from "node:os";
|
|
21
|
+
|
|
22
|
+
//#region src/registry/context.ts
|
|
23
|
+
let context = { headers: {} };
|
|
24
|
+
function clearRegistryContext() {
|
|
25
|
+
context.headers = {};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/preflights/preflight-init.ts
|
|
30
|
+
async function preFlightInit(options) {
|
|
31
|
+
const errors = {};
|
|
32
|
+
if (!fs.existsSync(options.cwd) || !fs.existsSync(path$1.resolve(options.cwd, "package.json"))) {
|
|
33
|
+
errors["1"] = true;
|
|
34
|
+
return {
|
|
35
|
+
errors,
|
|
36
|
+
projectInfo: null
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
errors,
|
|
41
|
+
projectInfo: null
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/utils/astro-fonts.ts
|
|
47
|
+
const ASTRO_CONFIG_BLOCK_START = "// bejamas:astro-fonts:start";
|
|
48
|
+
const ASTRO_CONFIG_BLOCK_END = "// bejamas:astro-fonts:end";
|
|
49
|
+
const ASTRO_LAYOUT_BLOCK_START = "<!-- bejamas:astro-fonts:start -->";
|
|
50
|
+
const ASTRO_LAYOUT_BLOCK_END = "<!-- bejamas:astro-fonts:end -->";
|
|
51
|
+
const ASTRO_FONT_CONSTANT = "BEJAMAS_ASTRO_FONTS";
|
|
52
|
+
const ASTRO_CONFIG_CANDIDATES = [
|
|
53
|
+
"astro.config.mjs",
|
|
54
|
+
"apps/web/astro.config.mjs",
|
|
55
|
+
"apps/docs/astro.config.mjs"
|
|
56
|
+
];
|
|
57
|
+
const ASTRO_LAYOUT_CANDIDATES = ["src/layouts/Layout.astro", "apps/web/src/layouts/Layout.astro"];
|
|
58
|
+
const STARLIGHT_HEAD_RELATIVE_PATH = "apps/docs/src/components/Head.astro";
|
|
59
|
+
const ASTRO_FONT_PROVIDERS = ["fontsource", "google"];
|
|
60
|
+
const ASTRO_FONTSOURCE_FONT_NAMES = /* @__PURE__ */ new Set(["geist", "geist-mono"]);
|
|
61
|
+
const MANAGED_BODY_FONT_CLASSES = /* @__PURE__ */ new Set(["font-mono", "font-serif"]);
|
|
62
|
+
function compactSource$1(source) {
|
|
63
|
+
return source.replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
|
|
64
|
+
}
|
|
65
|
+
function escapeRegExp$2(value) {
|
|
66
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
67
|
+
}
|
|
68
|
+
function upsertImport(source, moduleName, importedName, fallbackNames = []) {
|
|
69
|
+
const importPattern = new RegExp(`import\\s*\\{([^}]+)\\}\\s*from\\s*["']${escapeRegExp$2(moduleName)}["'];?`);
|
|
70
|
+
if (importPattern.test(source)) return source.replace(importPattern, (_match, importsSource) => {
|
|
71
|
+
const imports = importsSource.split(",").map((value) => value.trim()).filter(Boolean);
|
|
72
|
+
if (!imports.includes(importedName)) imports.push(importedName);
|
|
73
|
+
for (const name of fallbackNames) if (!imports.includes(name)) imports.unshift(name);
|
|
74
|
+
return `import { ${Array.from(new Set(imports)).join(", ")} } from "${moduleName}";`;
|
|
75
|
+
});
|
|
76
|
+
const importLine = `import { ${Array.from(/* @__PURE__ */ new Set([...fallbackNames, importedName])).join(", ")} } from "${moduleName}";`;
|
|
77
|
+
const frontmatterEnd = source.indexOf("---", 3);
|
|
78
|
+
if (source.startsWith("---") && frontmatterEnd !== -1) return `${source.slice(0, frontmatterEnd).trimEnd()}\n${importLine}\n${source.slice(frontmatterEnd)}`;
|
|
79
|
+
return `${importLine}\n${source}`;
|
|
80
|
+
}
|
|
81
|
+
function buildAstroConfigFontBlock(fontsToSerialize) {
|
|
82
|
+
const body = fontsToSerialize.length === 0 ? "" : `\n${fontsToSerialize.map((font) => ` {\n provider: fontProviders.${font.provider}(),\n name: ${JSON.stringify(font.name)},\n cssVariable: ${JSON.stringify(font.cssVariable)},\n subsets: ${JSON.stringify(font.subsets)},\n },`).join("\n")}\n`;
|
|
83
|
+
return `${ASTRO_CONFIG_BLOCK_START}\n/** @type {NonNullable<import("astro").AstroUserConfig["fonts"]>} */\nconst ${ASTRO_FONT_CONSTANT} = [${body}];\n${ASTRO_CONFIG_BLOCK_END}`;
|
|
84
|
+
}
|
|
85
|
+
function buildAstroLayoutFontBlock(fontsToSerialize) {
|
|
86
|
+
const body = fontsToSerialize.map((font) => `<Font cssVariable="${font.cssVariable}" />`).join("\n");
|
|
87
|
+
return `${ASTRO_LAYOUT_BLOCK_START}\n${body}\n${ASTRO_LAYOUT_BLOCK_END}`;
|
|
88
|
+
}
|
|
89
|
+
function bodyFontClassFromCssVariable(cssVariable) {
|
|
90
|
+
if (cssVariable === "--font-mono" || cssVariable === "--font-serif") return fontClassFromCssVariable(cssVariable);
|
|
91
|
+
return "";
|
|
92
|
+
}
|
|
93
|
+
function upsertManagedBodyFontClass(source, cssVariable) {
|
|
94
|
+
const bodyClass = bodyFontClassFromCssVariable(cssVariable);
|
|
95
|
+
const bodyTagPattern = /<body\b([^>]*?)(\/?)>/m;
|
|
96
|
+
if (!bodyTagPattern.test(source)) return source;
|
|
97
|
+
return source.replace(bodyTagPattern, (_match, attrs, selfClosing) => {
|
|
98
|
+
const classPattern = /\sclass=(["'])(.*?)\1/m;
|
|
99
|
+
const classMatch = attrs.match(classPattern);
|
|
100
|
+
const quote = classMatch?.[1] ?? "\"";
|
|
101
|
+
const existingClasses = (classMatch?.[2] ?? "").split(/\s+/).filter(Boolean).filter((className) => !MANAGED_BODY_FONT_CLASSES.has(className));
|
|
102
|
+
const nextClasses = bodyClass ? Array.from(/* @__PURE__ */ new Set([...existingClasses, bodyClass])) : existingClasses;
|
|
103
|
+
let nextAttrs = attrs.replace(classPattern, "");
|
|
104
|
+
if (nextClasses.length > 0) nextAttrs = `${nextAttrs} class=${quote}${nextClasses.join(" ")}${quote}`;
|
|
105
|
+
return `<body${nextAttrs}${selfClosing}>`;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
function parseManagedAstroFonts(source) {
|
|
109
|
+
const markerPattern = new RegExp(`${escapeRegExp$2(ASTRO_CONFIG_BLOCK_START)}[\\s\\S]*?const\\s+${ASTRO_FONT_CONSTANT}\\s*=\\s*\\[([\\s\\S]*?)\\];[\\s\\S]*?${escapeRegExp$2(ASTRO_CONFIG_BLOCK_END)}`);
|
|
110
|
+
const match = source.match(markerPattern);
|
|
111
|
+
if (!match) return [];
|
|
112
|
+
return (match[1].match(/\{[\s\S]*?\}/g) ?? []).map((entry) => {
|
|
113
|
+
const name = entry.match(/name:\s*"([^"]+)"/)?.[1];
|
|
114
|
+
const cssVariable = entry.match(/cssVariable:\s*"([^"]+)"/)?.[1];
|
|
115
|
+
const provider = entry.match(/provider:\s*fontProviders\.([a-z]+)\(\)/)?.[1];
|
|
116
|
+
const subsetsSource = entry.match(/subsets:\s*(\[[^\]]*\])/s)?.[1];
|
|
117
|
+
if (!name || !cssVariable || !provider || !ASTRO_FONT_PROVIDERS.includes(provider)) return null;
|
|
118
|
+
let subsets = ["latin"];
|
|
119
|
+
if (subsetsSource) try {
|
|
120
|
+
const parsed = JSON.parse(subsetsSource);
|
|
121
|
+
if (Array.isArray(parsed) && parsed.length > 0 && parsed.every((value) => typeof value === "string")) subsets = parsed;
|
|
122
|
+
} catch {
|
|
123
|
+
subsets = ["latin"];
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
name,
|
|
127
|
+
cssVariable,
|
|
128
|
+
provider,
|
|
129
|
+
subsets
|
|
130
|
+
};
|
|
131
|
+
}).filter((value) => value !== null);
|
|
132
|
+
}
|
|
133
|
+
function normalizeManagedAstroFontSubsets(subsets) {
|
|
134
|
+
if (!subsets?.length) return ["latin"];
|
|
135
|
+
return [subsets[0], ...subsets.slice(1)];
|
|
136
|
+
}
|
|
137
|
+
function replaceManagedBlock(source, nextBlock, start, end) {
|
|
138
|
+
const blockPattern = new RegExp(`${escapeRegExp$2(start)}[\\s\\S]*?${escapeRegExp$2(end)}`, "m");
|
|
139
|
+
if (blockPattern.test(source)) return source.replace(blockPattern, nextBlock);
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
function stripExperimentalFontsProperty(source) {
|
|
143
|
+
const experimentalMatch = source.match(/experimental\s*:\s*\{/);
|
|
144
|
+
if (!experimentalMatch || experimentalMatch.index === void 0) return source;
|
|
145
|
+
const propertyStart = experimentalMatch.index;
|
|
146
|
+
const openingBraceIndex = propertyStart + experimentalMatch[0].lastIndexOf("{");
|
|
147
|
+
let depth = 0;
|
|
148
|
+
let closingBraceIndex = -1;
|
|
149
|
+
let quote = null;
|
|
150
|
+
for (let index = openingBraceIndex; index < source.length; index += 1) {
|
|
151
|
+
const character = source[index];
|
|
152
|
+
const previousCharacter = index > 0 ? source[index - 1] : "";
|
|
153
|
+
if (quote) {
|
|
154
|
+
if (character === quote && previousCharacter !== "\\") quote = null;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (character === "\"" || character === "'" || character === "`") {
|
|
158
|
+
quote = character;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (character === "{") {
|
|
162
|
+
depth += 1;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (character === "}") {
|
|
166
|
+
depth -= 1;
|
|
167
|
+
if (depth === 0) {
|
|
168
|
+
closingBraceIndex = index;
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (closingBraceIndex === -1) return source;
|
|
174
|
+
const nextBlockContent = source.slice(openingBraceIndex + 1, closingBraceIndex).replace(/(^|\n)\s*fonts\s*:\s*BEJAMAS_ASTRO_FONTS\s*,?\s*(?=\n|$)/g, "$1").replace(/\n{3,}/g, "\n\n");
|
|
175
|
+
let propertyEnd = closingBraceIndex + 1;
|
|
176
|
+
while (propertyEnd < source.length && /\s/.test(source[propertyEnd])) propertyEnd += 1;
|
|
177
|
+
if (source[propertyEnd] === ",") propertyEnd += 1;
|
|
178
|
+
if (nextBlockContent.trim().length === 0) return `${source.slice(0, propertyStart)}${source.slice(propertyEnd)}`;
|
|
179
|
+
const rebuiltProperty = `experimental: {${nextBlockContent}\n }`;
|
|
180
|
+
return `${source.slice(0, propertyStart)}${rebuiltProperty}${source.slice(closingBraceIndex + 1)}`;
|
|
181
|
+
}
|
|
182
|
+
function patchAstroConfigSource(source, fontsToSerialize, options = {}) {
|
|
183
|
+
let next = upsertImport(source, "astro/config", "fontProviders", ["defineConfig"]);
|
|
184
|
+
const fontBlock = buildAstroConfigFontBlock(fontsToSerialize);
|
|
185
|
+
const replacedBlock = replaceManagedBlock(next, fontBlock, ASTRO_CONFIG_BLOCK_START, ASTRO_CONFIG_BLOCK_END);
|
|
186
|
+
if (replacedBlock) next = replacedBlock;
|
|
187
|
+
else {
|
|
188
|
+
const lastImport = Array.from(next.matchAll(/^import .*$/gm)).at(-1);
|
|
189
|
+
if (lastImport?.index !== void 0) {
|
|
190
|
+
const insertAt = lastImport.index + lastImport[0].length;
|
|
191
|
+
next = `${next.slice(0, insertAt)}\n\n${fontBlock}${next.slice(insertAt)}`;
|
|
192
|
+
} else next = `${fontBlock}\n\n${next}`;
|
|
193
|
+
}
|
|
194
|
+
next = stripExperimentalFontsProperty(next);
|
|
195
|
+
if (!/^\s*fonts:\s*BEJAMAS_ASTRO_FONTS\b/m.test(next)) next = next.replace(/defineConfig\(\s*\{/, `defineConfig({\n fonts: ${ASTRO_FONT_CONSTANT},`);
|
|
196
|
+
if (options.starlightHeadOverride) {
|
|
197
|
+
if (next.includes("Head: \"./src/components/Head.astro\"")) return compactSource$1(next);
|
|
198
|
+
const starlightComponentsPattern = /starlight\(\{\s*([\s\S]*?)components:\s*\{([\s\S]*?)\}/m;
|
|
199
|
+
if (starlightComponentsPattern.test(next)) next = next.replace(starlightComponentsPattern, (_match, before, inner) => `starlight({${before}components: {\n Head: "./src/components/Head.astro",${inner ? `${inner}` : ""}\n }`);
|
|
200
|
+
else next = next.replace(/starlight\(\s*\{/, `starlight({\n components: {\n Head: "./src/components/Head.astro",\n },`);
|
|
201
|
+
}
|
|
202
|
+
return compactSource$1(next);
|
|
203
|
+
}
|
|
204
|
+
function patchAstroLayoutSource(source, fontsToSerialize, activeFontCssVariable) {
|
|
205
|
+
let next = upsertImport(source, "astro:assets", "Font");
|
|
206
|
+
const fontBlock = buildAstroLayoutFontBlock(fontsToSerialize);
|
|
207
|
+
const replacedBlock = replaceManagedBlock(next, fontBlock, ASTRO_LAYOUT_BLOCK_START, ASTRO_LAYOUT_BLOCK_END);
|
|
208
|
+
if (replacedBlock) next = replacedBlock;
|
|
209
|
+
else if (next.includes("<head>")) next = next.replace("<head>", `<head>\n ${fontBlock.replace(/\n/g, "\n ")}`);
|
|
210
|
+
else if (next.includes("</head>")) next = next.replace("</head>", ` ${fontBlock.replace(/\n/g, "\n ")}\n</head>`);
|
|
211
|
+
else next = `${next}\n${fontBlock}\n`;
|
|
212
|
+
next = upsertManagedBodyFontClass(next, activeFontCssVariable);
|
|
213
|
+
return compactSource$1(next);
|
|
214
|
+
}
|
|
215
|
+
function patchStarlightHeadSource(source, fontsToSerialize) {
|
|
216
|
+
let next = upsertImport(source, "@astrojs/starlight/components/Head.astro", "DefaultHead");
|
|
217
|
+
next = upsertImport(next, "astro:assets", "Font");
|
|
218
|
+
const fontBlock = buildAstroLayoutFontBlock(fontsToSerialize);
|
|
219
|
+
const replacedBlock = replaceManagedBlock(next, fontBlock, ASTRO_LAYOUT_BLOCK_START, ASTRO_LAYOUT_BLOCK_END);
|
|
220
|
+
if (replacedBlock) next = replacedBlock;
|
|
221
|
+
else if (next.includes("<DefaultHead")) next = next.replace(/<DefaultHead[^>]*\/>/, (match) => `${match}\n${fontBlock}`);
|
|
222
|
+
else next = `${next.trimEnd()}\n\n<DefaultHead />\n${fontBlock}\n`;
|
|
223
|
+
return compactSource$1(next);
|
|
224
|
+
}
|
|
225
|
+
function toManagedAstroFont(fontName) {
|
|
226
|
+
const normalized = fontName.replace(/^font-heading-/, "").replace(/^font-/, "");
|
|
227
|
+
const font = fontName.startsWith("font-heading-") ? getHeadingFontValue(normalized) : getFontValue(normalized);
|
|
228
|
+
if (!font || font.type !== "registry:font" || !ASTRO_FONT_PROVIDERS.includes(font.font.provider)) return null;
|
|
229
|
+
return {
|
|
230
|
+
name: font.title?.replace(/\s+\(Heading\)$/, "") ?? font.font.import.replace(/_/g, " "),
|
|
231
|
+
cssVariable: font.font.variable,
|
|
232
|
+
provider: ASTRO_FONTSOURCE_FONT_NAMES.has(normalized) ? "fontsource" : "google",
|
|
233
|
+
subsets: normalizeManagedAstroFontSubsets(font.font.subsets)
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function mergeManagedAstroFonts(currentFonts, nextFont) {
|
|
237
|
+
return [...currentFonts.filter((font) => font.cssVariable !== nextFont.cssVariable), nextFont].sort((left, right) => left.cssVariable.localeCompare(right.cssVariable));
|
|
238
|
+
}
|
|
239
|
+
function fontClassFromCssVariable(cssVariable) {
|
|
240
|
+
return `font-${cssVariable.replace(/^--font-/, "")}`;
|
|
241
|
+
}
|
|
242
|
+
async function patchFileIfExists(filePath, transformer) {
|
|
243
|
+
if (!await fs.pathExists(filePath)) return;
|
|
244
|
+
const current = await fs.readFile(filePath, "utf8");
|
|
245
|
+
const next = transformer(current);
|
|
246
|
+
if (next !== current) await fs.writeFile(filePath, next, "utf8");
|
|
247
|
+
}
|
|
248
|
+
async function collectPackageJsonsFromComponents(projectPath) {
|
|
249
|
+
const componentJsonFiles = await fg("**/components.json", {
|
|
250
|
+
cwd: projectPath,
|
|
251
|
+
ignore: [
|
|
252
|
+
"**/node_modules/**",
|
|
253
|
+
"**/dist/**",
|
|
254
|
+
"**/.astro/**"
|
|
255
|
+
]
|
|
256
|
+
});
|
|
257
|
+
const packageJsonFiles = /* @__PURE__ */ new Set();
|
|
258
|
+
for (const relativePath of componentJsonFiles) {
|
|
259
|
+
const absolutePath = path.resolve(projectPath, relativePath);
|
|
260
|
+
const cssPath = (await fs.readJson(absolutePath))?.tailwind?.css;
|
|
261
|
+
if (typeof cssPath !== "string" || cssPath.length === 0) continue;
|
|
262
|
+
let currentDir = path.dirname(path.resolve(path.dirname(absolutePath), cssPath));
|
|
263
|
+
while (true) {
|
|
264
|
+
const packageJsonPath = path.resolve(currentDir, "package.json");
|
|
265
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
266
|
+
packageJsonFiles.add(packageJsonPath);
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
const parentDir = path.dirname(currentDir);
|
|
270
|
+
if (parentDir === currentDir) break;
|
|
271
|
+
currentDir = parentDir;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return packageJsonFiles;
|
|
275
|
+
}
|
|
276
|
+
async function upsertStarlightHeadFile(projectPath, fontsToSerialize) {
|
|
277
|
+
const headPath = path.resolve(projectPath, STARLIGHT_HEAD_RELATIVE_PATH);
|
|
278
|
+
const next = patchStarlightHeadSource(await fs.pathExists(headPath) ? await fs.readFile(headPath, "utf8") : "---\nimport DefaultHead from \"@astrojs/starlight/components/Head.astro\";\nimport { Font } from \"astro:assets\";\n---\n\n<DefaultHead />\n", fontsToSerialize);
|
|
279
|
+
await fs.ensureDir(path.dirname(headPath));
|
|
280
|
+
await fs.writeFile(headPath, next, "utf8");
|
|
281
|
+
}
|
|
282
|
+
async function readManagedAstroFontsFromProject(projectPath) {
|
|
283
|
+
for (const relativePath of ASTRO_CONFIG_CANDIDATES) {
|
|
284
|
+
const absolutePath = path.resolve(projectPath, relativePath);
|
|
285
|
+
if (!await fs.pathExists(absolutePath)) continue;
|
|
286
|
+
const parsed = parseManagedAstroFonts(await fs.readFile(absolutePath, "utf8"));
|
|
287
|
+
if (parsed.length > 0) return parsed;
|
|
288
|
+
}
|
|
289
|
+
return [];
|
|
290
|
+
}
|
|
291
|
+
async function cleanupAstroFontPackages(projectPath) {
|
|
292
|
+
const managedPackages = new Set(fonts.filter((font) => font.type === "registry:font").map((font) => getFontPackageName(font.name.replace(/^font-heading-/, "").replace(/^font-/, ""))));
|
|
293
|
+
const packageJsonFiles = await collectPackageJsonsFromComponents(projectPath);
|
|
294
|
+
await Promise.all(Array.from(packageJsonFiles).map(async (absolutePath) => {
|
|
295
|
+
const current = await fs.readJson(absolutePath);
|
|
296
|
+
let changed = false;
|
|
297
|
+
for (const bucket of ["dependencies", "devDependencies"]) {
|
|
298
|
+
const currentBucket = current[bucket];
|
|
299
|
+
if (!currentBucket || typeof currentBucket !== "object") continue;
|
|
300
|
+
for (const packageName of managedPackages) if (packageName in currentBucket) {
|
|
301
|
+
delete currentBucket[packageName];
|
|
302
|
+
changed = true;
|
|
303
|
+
}
|
|
304
|
+
if (Object.keys(currentBucket).length === 0) {
|
|
305
|
+
delete current[bucket];
|
|
306
|
+
changed = true;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (changed) await fs.writeJson(absolutePath, current, { spaces: 2 });
|
|
310
|
+
}));
|
|
311
|
+
}
|
|
312
|
+
async function syncAstroFontsInProject(projectPath, fontsToSerialize, activeFontCssVariable) {
|
|
313
|
+
await Promise.all(ASTRO_CONFIG_CANDIDATES.map((relativePath) => patchFileIfExists(path.resolve(projectPath, relativePath), (source) => patchAstroConfigSource(source, fontsToSerialize, { starlightHeadOverride: relativePath === "apps/docs/astro.config.mjs" }))));
|
|
314
|
+
await Promise.all(ASTRO_LAYOUT_CANDIDATES.map((relativePath) => patchFileIfExists(path.resolve(projectPath, relativePath), (source) => patchAstroLayoutSource(source, fontsToSerialize, activeFontCssVariable))));
|
|
315
|
+
const docsConfigPath = path.resolve(projectPath, "apps/docs/astro.config.mjs");
|
|
316
|
+
if (await fs.pathExists(docsConfigPath)) await upsertStarlightHeadFile(projectPath, fontsToSerialize);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
//#endregion
|
|
320
|
+
//#region src/utils/apply-design-system.ts
|
|
321
|
+
const CREATE_BLOCK_START = "/* bejamas:create:start */";
|
|
322
|
+
const CREATE_BLOCK_END = "/* bejamas:create:end */";
|
|
323
|
+
const SHADCN_TAILWIND_IMPORT = "@import \"shadcn/tailwind.css\";";
|
|
324
|
+
const BEJAMAS_TAILWIND_IMPORT = "@import \"bejamas/tailwind.css\";";
|
|
325
|
+
const MANAGED_TAILWIND_IMPORTS = /* @__PURE__ */ new Set([SHADCN_TAILWIND_IMPORT, BEJAMAS_TAILWIND_IMPORT]);
|
|
326
|
+
const MANAGED_ICON_PACKAGES = new Set(ICON_LIBRARIES.map((library) => library.packageName));
|
|
327
|
+
const FALLBACK_ICON_PACKAGE_VERSION = "latest";
|
|
328
|
+
const TEMPLATE_APP_UI_IMPORT = "import { appUi } from \"@/i18n/ui\";";
|
|
329
|
+
const TEMPLATE_I18N_SOURCES = {
|
|
330
|
+
astro: `const RTL_LANGUAGES = ["ar", "fa", "he"] as const;
|
|
331
|
+
|
|
332
|
+
export type TemplateLanguage = "en" | (typeof RTL_LANGUAGES)[number];
|
|
333
|
+
|
|
334
|
+
export const CURRENT_LANGUAGE: TemplateLanguage = "en";
|
|
335
|
+
|
|
336
|
+
const ui = {
|
|
337
|
+
en: {
|
|
338
|
+
metadataTitle: "bejamas/ui Astro project",
|
|
339
|
+
welcomeMessage: "Welcome to {project} Astro project.",
|
|
340
|
+
getStartedMessage: "Get started by editing src/pages/index.astro.",
|
|
341
|
+
readDocs: "Read docs",
|
|
342
|
+
getCustomDemo: "Get custom demo",
|
|
343
|
+
},
|
|
344
|
+
ar: {
|
|
345
|
+
metadataTitle: "مشروع Astro من bejamas/ui",
|
|
346
|
+
welcomeMessage: "مرحبًا بك في مشروع Astro الخاص بـ {project}.",
|
|
347
|
+
getStartedMessage: "ابدأ بتحرير src/pages/index.astro.",
|
|
348
|
+
readDocs: "اقرأ التوثيق",
|
|
349
|
+
getCustomDemo: "احصل على عرض توضيحي مخصص",
|
|
350
|
+
},
|
|
351
|
+
fa: {
|
|
352
|
+
metadataTitle: "پروژه Astro با bejamas/ui",
|
|
353
|
+
welcomeMessage: "به پروژه Astro با {project} خوش آمدید.",
|
|
354
|
+
getStartedMessage: "برای شروع src/pages/index.astro را ویرایش کنید.",
|
|
355
|
+
readDocs: "مطالعه مستندات",
|
|
356
|
+
getCustomDemo: "درخواست دموی سفارشی",
|
|
357
|
+
},
|
|
358
|
+
he: {
|
|
359
|
+
metadataTitle: "פרויקט Astro עם bejamas/ui",
|
|
360
|
+
welcomeMessage: "ברוכים הבאים לפרויקט Astro של {project}.",
|
|
361
|
+
getStartedMessage: "התחילו בעריכת src/pages/index.astro.",
|
|
362
|
+
readDocs: "קריאת התיעוד",
|
|
363
|
+
getCustomDemo: "קבלת דמו מותאם",
|
|
364
|
+
},
|
|
365
|
+
} as const satisfies Record<TemplateLanguage, Record<string, string>>;
|
|
366
|
+
|
|
367
|
+
export const appUi = {
|
|
368
|
+
lang: CURRENT_LANGUAGE,
|
|
369
|
+
dir: RTL_LANGUAGES.includes(
|
|
370
|
+
CURRENT_LANGUAGE as (typeof RTL_LANGUAGES)[number],
|
|
371
|
+
)
|
|
372
|
+
? "rtl"
|
|
373
|
+
: "ltr",
|
|
374
|
+
...ui[CURRENT_LANGUAGE],
|
|
375
|
+
};
|
|
376
|
+
`,
|
|
377
|
+
monorepo: `const RTL_LANGUAGES = ["ar", "fa", "he"] as const;
|
|
378
|
+
|
|
379
|
+
export type TemplateLanguage = "en" | (typeof RTL_LANGUAGES)[number];
|
|
380
|
+
|
|
381
|
+
export const CURRENT_LANGUAGE: TemplateLanguage = "en";
|
|
382
|
+
|
|
383
|
+
const ui = {
|
|
384
|
+
en: {
|
|
385
|
+
metadataTitle: "bejamas/ui Astro project",
|
|
386
|
+
welcomeMessage: "Welcome to {project} Astro monorepo.",
|
|
387
|
+
getStartedMessage: "Get started by editing apps/web/src/pages/index.astro.",
|
|
388
|
+
readDocs: "Read docs",
|
|
389
|
+
getCustomDemo: "Get custom demo",
|
|
390
|
+
},
|
|
391
|
+
ar: {
|
|
392
|
+
metadataTitle: "مشروع Astro من bejamas/ui",
|
|
393
|
+
welcomeMessage: "مرحبًا بك في مشروع Astro الأحادي من {project}.",
|
|
394
|
+
getStartedMessage: "ابدأ بتحرير apps/web/src/pages/index.astro.",
|
|
395
|
+
readDocs: "اقرأ التوثيق",
|
|
396
|
+
getCustomDemo: "احصل على عرض توضيحي مخصص",
|
|
397
|
+
},
|
|
398
|
+
fa: {
|
|
399
|
+
metadataTitle: "پروژه Astro با bejamas/ui",
|
|
400
|
+
welcomeMessage: "به مونوریپوی Astro با {project} خوش آمدید.",
|
|
401
|
+
getStartedMessage:
|
|
402
|
+
"برای شروع apps/web/src/pages/index.astro را ویرایش کنید.",
|
|
403
|
+
readDocs: "مطالعه مستندات",
|
|
404
|
+
getCustomDemo: "درخواست دموی سفارشی",
|
|
405
|
+
},
|
|
406
|
+
he: {
|
|
407
|
+
metadataTitle: "פרויקט Astro עם bejamas/ui",
|
|
408
|
+
welcomeMessage: "ברוכים הבאים למונורפו Astro של {project}.",
|
|
409
|
+
getStartedMessage: "התחילו בעריכת apps/web/src/pages/index.astro.",
|
|
410
|
+
readDocs: "קריאת התיעוד",
|
|
411
|
+
getCustomDemo: "קבלת דמו מותאם",
|
|
412
|
+
},
|
|
413
|
+
} as const satisfies Record<TemplateLanguage, Record<string, string>>;
|
|
414
|
+
|
|
415
|
+
export const appUi = {
|
|
416
|
+
lang: CURRENT_LANGUAGE,
|
|
417
|
+
dir: RTL_LANGUAGES.includes(
|
|
418
|
+
CURRENT_LANGUAGE as (typeof RTL_LANGUAGES)[number],
|
|
419
|
+
)
|
|
420
|
+
? "rtl"
|
|
421
|
+
: "ltr",
|
|
422
|
+
...ui[CURRENT_LANGUAGE],
|
|
423
|
+
};
|
|
424
|
+
`
|
|
425
|
+
};
|
|
426
|
+
const TEMPLATE_PAGE_VARIANTS = [
|
|
427
|
+
{
|
|
428
|
+
matchers: [
|
|
429
|
+
"import { Button } from \"@/ui/button\";",
|
|
430
|
+
"Astro project.",
|
|
431
|
+
"Get started by editing src/pages/index.astro."
|
|
432
|
+
],
|
|
433
|
+
i18nSource: `---
|
|
434
|
+
import Layout from "@/layouts/Layout.astro";
|
|
435
|
+
import { appUi } from "@/i18n/ui";
|
|
436
|
+
import { Button } from "@/ui/button";
|
|
437
|
+
|
|
438
|
+
const [welcomePrefix, welcomeSuffix = ""] =
|
|
439
|
+
appUi.welcomeMessage.split("{project}");
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
<Layout>
|
|
443
|
+
<div
|
|
444
|
+
class="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20"
|
|
445
|
+
>
|
|
446
|
+
<div class="flex flex-col gap-6 row-start-2 items-center sm:items-start">
|
|
447
|
+
<img src="/bejamas.svg" alt="bejamas/ui" class="w-8 dark:invert" />
|
|
448
|
+
<div class="space-y-2">
|
|
449
|
+
<p class="text-sm">
|
|
450
|
+
{welcomePrefix}<code
|
|
451
|
+
class="font-bold relative bg-muted rounded p-1"
|
|
452
|
+
>@bejamas/ui</code
|
|
453
|
+
>{welcomeSuffix}
|
|
454
|
+
</p>
|
|
455
|
+
<p class="text-sm">
|
|
456
|
+
{appUi.getStartedMessage}
|
|
457
|
+
</p>
|
|
458
|
+
</div>
|
|
459
|
+
<div class="flex gap-2">
|
|
460
|
+
<Button as="a" href="https://ui.bejamas.com/docs"
|
|
461
|
+
>{appUi.readDocs}</Button
|
|
462
|
+
>
|
|
463
|
+
<Button as="a" href="https://bejamas.com/get-in-touch" variant="outline"
|
|
464
|
+
>{appUi.getCustomDemo}</Button
|
|
465
|
+
>
|
|
466
|
+
</div>
|
|
467
|
+
</div>
|
|
468
|
+
</div>
|
|
469
|
+
</Layout>
|
|
470
|
+
`
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
matchers: [
|
|
474
|
+
"import { Button } from \"@repo/ui/components/button\";",
|
|
475
|
+
"width=\"32\"",
|
|
476
|
+
"Astro monorepo.",
|
|
477
|
+
"Get started by editing apps/web/src/pages/index.astro."
|
|
478
|
+
],
|
|
479
|
+
i18nSource: `---
|
|
480
|
+
import Layout from "@/layouts/Layout.astro";
|
|
481
|
+
import { appUi } from "@/i18n/ui";
|
|
482
|
+
import { Button } from "@repo/ui/components/button";
|
|
483
|
+
|
|
484
|
+
const [welcomePrefix, welcomeSuffix = ""] =
|
|
485
|
+
appUi.welcomeMessage.split("{project}");
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
<Layout>
|
|
489
|
+
<div
|
|
490
|
+
class="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20"
|
|
491
|
+
>
|
|
492
|
+
<div class="flex flex-col gap-6 row-start-2 items-center sm:items-start">
|
|
493
|
+
<img
|
|
494
|
+
src="/bejamas.svg"
|
|
495
|
+
alt="bejamas/ui"
|
|
496
|
+
class="w-8 dark:invert"
|
|
497
|
+
width="32"
|
|
498
|
+
height="32"
|
|
499
|
+
alt="bejamas/ui logo"
|
|
500
|
+
/>
|
|
501
|
+
<div class="space-y-2">
|
|
502
|
+
<p class="text-sm">
|
|
503
|
+
{welcomePrefix}<code
|
|
504
|
+
class="font-bold relative bg-muted rounded p-1"
|
|
505
|
+
>@bejamas/ui</code
|
|
506
|
+
>{welcomeSuffix}
|
|
507
|
+
</p>
|
|
508
|
+
<p class="text-sm">
|
|
509
|
+
{appUi.getStartedMessage}
|
|
510
|
+
</p>
|
|
511
|
+
</div>
|
|
512
|
+
<div class="flex gap-2">
|
|
513
|
+
<Button as="a" href="https://ui.bejamas.com/docs"
|
|
514
|
+
>{appUi.readDocs}</Button
|
|
515
|
+
>
|
|
516
|
+
<Button as="a" href="https://bejamas.com/get-in-touch" variant="outline"
|
|
517
|
+
>{appUi.getCustomDemo}</Button
|
|
518
|
+
>
|
|
519
|
+
</div>
|
|
520
|
+
</div>
|
|
521
|
+
</div>
|
|
522
|
+
</Layout>
|
|
523
|
+
`
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
matchers: [
|
|
527
|
+
"import { Button } from \"@repo/ui/components/button\";",
|
|
528
|
+
"<img src=\"/bejamas.svg\" alt=\"bejamas/ui\" class=\"w-8 dark:invert\" />",
|
|
529
|
+
"Astro monorepo.",
|
|
530
|
+
"Get started by editing apps/web/src/pages/index.astro."
|
|
531
|
+
],
|
|
532
|
+
i18nSource: `---
|
|
533
|
+
import Layout from "@/layouts/Layout.astro";
|
|
534
|
+
import { appUi } from "@/i18n/ui";
|
|
535
|
+
import { Button } from "@repo/ui/components/button";
|
|
536
|
+
|
|
537
|
+
const [welcomePrefix, welcomeSuffix = ""] =
|
|
538
|
+
appUi.welcomeMessage.split("{project}");
|
|
539
|
+
---
|
|
540
|
+
|
|
541
|
+
<Layout>
|
|
542
|
+
<div
|
|
543
|
+
class="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20"
|
|
544
|
+
>
|
|
545
|
+
<div class="flex flex-col gap-6 row-start-2 items-center sm:items-start">
|
|
546
|
+
<img src="/bejamas.svg" alt="bejamas/ui" class="w-8 dark:invert" />
|
|
547
|
+
<div class="space-y-2">
|
|
548
|
+
<p class="text-sm">
|
|
549
|
+
{welcomePrefix}<code
|
|
550
|
+
class="font-bold relative bg-muted rounded p-1"
|
|
551
|
+
>@bejamas/ui</code
|
|
552
|
+
>{welcomeSuffix}
|
|
553
|
+
</p>
|
|
554
|
+
<p class="text-sm">
|
|
555
|
+
{appUi.getStartedMessage}
|
|
556
|
+
</p>
|
|
557
|
+
</div>
|
|
558
|
+
<div class="flex gap-2">
|
|
559
|
+
<Button as="a" href="https://ui.bejamas.com/docs"
|
|
560
|
+
>{appUi.readDocs}</Button
|
|
561
|
+
>
|
|
562
|
+
<Button as="a" href="https://bejamas.com/get-in-touch" variant="outline"
|
|
563
|
+
>{appUi.getCustomDemo}</Button
|
|
564
|
+
>
|
|
565
|
+
</div>
|
|
566
|
+
</div>
|
|
567
|
+
</div>
|
|
568
|
+
</Layout>
|
|
569
|
+
`
|
|
570
|
+
}
|
|
571
|
+
];
|
|
572
|
+
function escapeRegExp$1(value) {
|
|
573
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
574
|
+
}
|
|
575
|
+
function compactCss(source) {
|
|
576
|
+
return source.replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
|
|
577
|
+
}
|
|
578
|
+
function compactSource(source) {
|
|
579
|
+
return source.replace(/\r\n/g, "\n").replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
|
|
580
|
+
}
|
|
581
|
+
function stripLegacyCreateBlock(source) {
|
|
582
|
+
const fullBlockPattern = new RegExp(`\\n*${escapeRegExp$1(CREATE_BLOCK_START)}[\\s\\S]*?${escapeRegExp$1(CREATE_BLOCK_END)}\\n*`, "m");
|
|
583
|
+
const danglingBlockPattern = new RegExp(`\\n*${escapeRegExp$1(CREATE_BLOCK_START)}[\\s\\S]*$`, "m");
|
|
584
|
+
return compactCss(source.replace(fullBlockPattern, "\n\n").replace(danglingBlockPattern, "\n\n"));
|
|
585
|
+
}
|
|
586
|
+
function buildCssVarBlock(selector, vars) {
|
|
587
|
+
return [
|
|
588
|
+
`${selector} {`,
|
|
589
|
+
...Object.entries(vars).map(([key, value]) => ` --${key}: ${value};`),
|
|
590
|
+
"}"
|
|
591
|
+
].join("\n");
|
|
592
|
+
}
|
|
593
|
+
function replaceTopLevelBlock(source, selector, nextBlock) {
|
|
594
|
+
const pattern = new RegExp(`^${escapeRegExp$1(selector)}\\s*\\{[\\s\\S]*?^\\}`, "m");
|
|
595
|
+
if (pattern.test(source)) return source.replace(pattern, nextBlock);
|
|
596
|
+
const layerIndex = source.indexOf("@layer base");
|
|
597
|
+
if (layerIndex !== -1) return `${source.slice(0, layerIndex).trimEnd()}\n\n${nextBlock}\n\n${source.slice(layerIndex).trimStart()}`;
|
|
598
|
+
return `${source.trimEnd()}\n\n${nextBlock}\n`;
|
|
599
|
+
}
|
|
600
|
+
function upsertImports(source, imports) {
|
|
601
|
+
const cleanedLines = source.split("\n").filter((line) => {
|
|
602
|
+
const trimmed = line.trim();
|
|
603
|
+
if (MANAGED_TAILWIND_IMPORTS.has(trimmed)) return false;
|
|
604
|
+
return !trimmed.startsWith("@import \"@fontsource-variable/");
|
|
605
|
+
});
|
|
606
|
+
let insertAt = -1;
|
|
607
|
+
for (let index = 0; index < cleanedLines.length; index += 1) {
|
|
608
|
+
if (cleanedLines[index].trim().startsWith("@import ")) {
|
|
609
|
+
insertAt = index;
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
if (insertAt !== -1 && cleanedLines[index].trim() !== "") break;
|
|
613
|
+
}
|
|
614
|
+
const uniqueImports = imports.filter((line, index) => imports.indexOf(line) === index);
|
|
615
|
+
if (insertAt === -1) return compactCss([
|
|
616
|
+
...uniqueImports,
|
|
617
|
+
"",
|
|
618
|
+
...cleanedLines
|
|
619
|
+
].join("\n"));
|
|
620
|
+
cleanedLines.splice(insertAt + 1, 0, ...uniqueImports);
|
|
621
|
+
return compactCss(cleanedLines.join("\n"));
|
|
622
|
+
}
|
|
623
|
+
function upsertManagedTailwindImport(source) {
|
|
624
|
+
const nextImport = resolveManagedTailwindImport(source);
|
|
625
|
+
const cleanedLines = source.split("\n").filter((line) => !MANAGED_TAILWIND_IMPORTS.has(line.trim()));
|
|
626
|
+
let insertAt = -1;
|
|
627
|
+
for (let index = 0; index < cleanedLines.length; index += 1) {
|
|
628
|
+
if (cleanedLines[index].trim().startsWith("@import ")) {
|
|
629
|
+
insertAt = index;
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
if (insertAt !== -1 && cleanedLines[index].trim() !== "") break;
|
|
633
|
+
}
|
|
634
|
+
if (insertAt === -1) return compactCss([
|
|
635
|
+
nextImport,
|
|
636
|
+
"",
|
|
637
|
+
...cleanedLines
|
|
638
|
+
].join("\n"));
|
|
639
|
+
cleanedLines.splice(insertAt + 1, 0, nextImport);
|
|
640
|
+
return compactCss(cleanedLines.join("\n"));
|
|
641
|
+
}
|
|
642
|
+
function resolveManagedTailwindImport(source) {
|
|
643
|
+
if (source.includes(SHADCN_TAILWIND_IMPORT)) return SHADCN_TAILWIND_IMPORT;
|
|
644
|
+
if (source.includes(BEJAMAS_TAILWIND_IMPORT)) return BEJAMAS_TAILWIND_IMPORT;
|
|
645
|
+
return BEJAMAS_TAILWIND_IMPORT;
|
|
646
|
+
}
|
|
647
|
+
function normalizeSourceForComparison(source) {
|
|
648
|
+
return source.replace(/\r\n/g, "\n").replace(/\s+/g, " ").trim();
|
|
649
|
+
}
|
|
650
|
+
function upsertFrontmatterImport(source, importLine) {
|
|
651
|
+
if (source.includes(importLine)) return source;
|
|
652
|
+
const frontmatterEnd = source.indexOf("\n---", 3);
|
|
653
|
+
if (source.startsWith("---\n") && frontmatterEnd !== -1) return `${source.slice(0, frontmatterEnd).trimEnd()}\n${importLine}${source.slice(frontmatterEnd)}`;
|
|
654
|
+
return `${importLine}\n${source}`;
|
|
655
|
+
}
|
|
656
|
+
function getTemplateI18nVariant(filepath) {
|
|
657
|
+
return filepath.includes(`${path.sep}apps${path.sep}web${path.sep}`) ? "monorepo" : "astro";
|
|
658
|
+
}
|
|
659
|
+
function buildTemplateI18nSource(variant, language) {
|
|
660
|
+
return compactSource(TEMPLATE_I18N_SOURCES[variant].replace("export const CURRENT_LANGUAGE: TemplateLanguage = \"en\";", `export const CURRENT_LANGUAGE: TemplateLanguage = "${language}";`));
|
|
661
|
+
}
|
|
662
|
+
function transformTemplateLayoutToI18n(source) {
|
|
663
|
+
if (source.includes(TEMPLATE_APP_UI_IMPORT)) return source;
|
|
664
|
+
let next = upsertFrontmatterImport(source, TEMPLATE_APP_UI_IMPORT);
|
|
665
|
+
next = next.replace(/<html([^>]*?)\slang=(\"[^\"]*\"|\{[^}]+\})([^>]*)>/, "<html$1$3>").replace(/<html([^>]*?)\sdir=(\"[^\"]*\"|\{[^}]+\})([^>]*)>/, "<html$1$3>");
|
|
666
|
+
next = next.replace(/<html([^>]*)>/, "<html$1 lang={appUi.lang} dir={appUi.dir}>");
|
|
667
|
+
next = next.replace(/<title>[\s\S]*?<\/title>/, "<title>{appUi.metadataTitle}</title>");
|
|
668
|
+
return compactSource(next);
|
|
669
|
+
}
|
|
670
|
+
function transformStarterPageToI18n(source) {
|
|
671
|
+
if (source.includes(TEMPLATE_APP_UI_IMPORT)) return source;
|
|
672
|
+
const normalized = normalizeSourceForComparison(source);
|
|
673
|
+
const match = TEMPLATE_PAGE_VARIANTS.find((variant) => variant.matchers.every((matcher) => normalized.includes(normalizeSourceForComparison(matcher))));
|
|
674
|
+
if (!match) return source;
|
|
675
|
+
return compactSource(match.i18nSource);
|
|
676
|
+
}
|
|
677
|
+
function upsertThemeInlineFont(source, fontVariable) {
|
|
678
|
+
const pattern = /@theme inline\s*\{[\s\S]*?\n\}/m;
|
|
679
|
+
if (!pattern.test(source)) {
|
|
680
|
+
const block = `@theme inline {\n${[fontVariable ? ` ${fontVariable}: var(${fontVariable});` : null, " --font-heading: var(--font-heading);"].filter(Boolean).join("\n")}\n}`;
|
|
681
|
+
const customVariantIndex = source.indexOf("@custom-variant");
|
|
682
|
+
if (customVariantIndex !== -1) {
|
|
683
|
+
const nextLineIndex = source.indexOf("\n", customVariantIndex);
|
|
684
|
+
return `${source.slice(0, nextLineIndex + 1).trimEnd()}\n\n${block}\n\n${source.slice(nextLineIndex + 1).trimStart()}`;
|
|
685
|
+
}
|
|
686
|
+
return `${block}\n\n${source.trimStart()}`;
|
|
687
|
+
}
|
|
688
|
+
return source.replace(pattern, (block) => {
|
|
689
|
+
const withoutManagedFonts = block.replace(/\n\s*--font-(sans|serif|mono|heading):[^\n]+/g, "").replace(/\n\s*--bejamas-font-family:[^\n]+/g, "").replace(/@theme inline\s*\{\n?/, "@theme inline {\n");
|
|
690
|
+
const declarations = [fontVariable ? ` ${fontVariable}: var(${fontVariable});` : null, " --font-heading: var(--font-heading);"].filter(Boolean).join("\n");
|
|
691
|
+
return withoutManagedFonts.replace("@theme inline {\n", `@theme inline {\n${declarations}\n`);
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
function upsertBaseLayerHtmlFont(source, fontClass) {
|
|
695
|
+
const pattern = /@layer base\s*\{[\s\S]*?\n\}/m;
|
|
696
|
+
if (!pattern.test(source)) return `${source.trimEnd()}\n\n@layer base {\n .cn-font-heading {\n @apply font-heading;\n }\n html {\n @apply ${fontClass};\n }\n}\n`;
|
|
697
|
+
return source.replace(pattern, (block) => {
|
|
698
|
+
return block.replace(/\n\s*html\s*\{[\s\S]*?\n\s*\}/m, "").replace(/\n\s*\.cn-font-heading\s*\{[\s\S]*?\n\s*\}/m, "").replace(/\n\}$/, `\n .cn-font-heading {\n @apply font-heading;\n }\n html {\n @apply ${fontClass};\n }\n}`);
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
function transformDesignSystemCss(source, config, themeVars) {
|
|
702
|
+
return transformDesignSystemFontCss(transformDesignSystemThemeCss(source, config, themeVars), config);
|
|
703
|
+
}
|
|
704
|
+
function transformDesignSystemThemeCss(source, config, themeVars) {
|
|
705
|
+
const effectiveThemeVars = themeVars ?? buildRegistryTheme(config).cssVars;
|
|
706
|
+
const rootVars = {
|
|
707
|
+
...Object.fromEntries(Object.entries(effectiveThemeVars.theme ?? {}).filter(([key]) => key !== "bejamas-font-family")),
|
|
708
|
+
...effectiveThemeVars.light ?? {}
|
|
709
|
+
};
|
|
710
|
+
const darkVars = { ...effectiveThemeVars.dark ?? {} };
|
|
711
|
+
const tailwindImport = resolveManagedTailwindImport(source);
|
|
712
|
+
let next = stripLegacyCreateBlock(source);
|
|
713
|
+
next = upsertImports(next, [tailwindImport]);
|
|
714
|
+
next = replaceTopLevelBlock(next, ":root", buildCssVarBlock(":root", rootVars));
|
|
715
|
+
next = replaceTopLevelBlock(next, ".dark", buildCssVarBlock(".dark", darkVars));
|
|
716
|
+
return compactCss(next);
|
|
717
|
+
}
|
|
718
|
+
function transformDesignSystemFontCss(source, config) {
|
|
719
|
+
const font = getFontValue(config.font);
|
|
720
|
+
const tailwindImport = resolveManagedTailwindImport(source);
|
|
721
|
+
let next = stripLegacyCreateBlock(source);
|
|
722
|
+
next = upsertImports(next, [tailwindImport]);
|
|
723
|
+
next = upsertThemeInlineFont(next, font?.font.variable);
|
|
724
|
+
if (font) next = upsertBaseLayerHtmlFont(next, fontClassFromCssVariable(font.font.variable));
|
|
725
|
+
return compactCss(next);
|
|
726
|
+
}
|
|
727
|
+
function transformAstroManagedFontCss(source, fontVariable) {
|
|
728
|
+
const tailwindImport = resolveManagedTailwindImport(source);
|
|
729
|
+
let next = stripLegacyCreateBlock(source);
|
|
730
|
+
next = upsertImports(next, [tailwindImport]);
|
|
731
|
+
next = upsertThemeInlineFont(next, fontVariable);
|
|
732
|
+
if (fontVariable) next = upsertBaseLayerHtmlFont(next, fontClassFromCssVariable(fontVariable));
|
|
733
|
+
return compactCss(next);
|
|
734
|
+
}
|
|
735
|
+
function transformManagedTailwindImportCss(source) {
|
|
736
|
+
return upsertManagedTailwindImport(source);
|
|
737
|
+
}
|
|
738
|
+
async function patchComponentsJson(filepath, config) {
|
|
739
|
+
const current = await fs.readJson(filepath);
|
|
740
|
+
const { rsc: _rsc, tsx: _tsx, ...normalizedCurrent } = current;
|
|
741
|
+
const next = {
|
|
742
|
+
...normalizedCurrent,
|
|
743
|
+
$schema: BEJAMAS_COMPONENTS_SCHEMA_URL,
|
|
744
|
+
style: getStyleId(config.style),
|
|
745
|
+
iconLibrary: config.iconLibrary,
|
|
746
|
+
rtl: config.rtl,
|
|
747
|
+
menuColor: config.menuColor,
|
|
748
|
+
menuAccent: config.menuAccent,
|
|
749
|
+
tailwind: {
|
|
750
|
+
...current.tailwind ?? {},
|
|
751
|
+
baseColor: config.baseColor,
|
|
752
|
+
cssVariables: true
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
await fs.writeJson(filepath, next, { spaces: 2 });
|
|
756
|
+
}
|
|
757
|
+
async function patchComponentsJsonWithTheme(filepath, config) {
|
|
758
|
+
await patchComponentsJson(filepath, config);
|
|
759
|
+
}
|
|
760
|
+
async function patchCssFileWithTheme(filepath, config, themeVars) {
|
|
761
|
+
const next = transformDesignSystemCss(await fs.readFile(filepath, "utf8"), config, themeVars);
|
|
762
|
+
await fs.writeFile(filepath, next, "utf8");
|
|
763
|
+
}
|
|
764
|
+
async function patchCssFileWithThemeOnly(filepath, config, themeVars) {
|
|
765
|
+
const next = transformDesignSystemThemeCss(await fs.readFile(filepath, "utf8"), config, themeVars);
|
|
766
|
+
await fs.writeFile(filepath, next, "utf8");
|
|
767
|
+
}
|
|
768
|
+
async function patchCssFileWithAstroFont(filepath, fontVariable) {
|
|
769
|
+
const next = transformAstroManagedFontCss(await fs.readFile(filepath, "utf8"), fontVariable);
|
|
770
|
+
await fs.writeFile(filepath, next, "utf8");
|
|
771
|
+
}
|
|
772
|
+
async function patchCssFileManagedTailwindImport(filepath) {
|
|
773
|
+
const next = transformManagedTailwindImportCss(await fs.readFile(filepath, "utf8"));
|
|
774
|
+
await fs.writeFile(filepath, next, "utf8");
|
|
775
|
+
}
|
|
776
|
+
function getSelectedIconPackageName(config) {
|
|
777
|
+
return ICON_LIBRARIES.find((library) => library.name === config.iconLibrary)?.packageName;
|
|
778
|
+
}
|
|
779
|
+
async function patchPackageJsonIconDependency(filepath, config) {
|
|
780
|
+
const selectedIconPackage = getSelectedIconPackageName(config);
|
|
781
|
+
if (!selectedIconPackage) return;
|
|
782
|
+
const packageJson = await fs.readJson(filepath);
|
|
783
|
+
let targetField = null;
|
|
784
|
+
let changed = false;
|
|
785
|
+
for (const field of ["dependencies", "devDependencies"]) {
|
|
786
|
+
const dependencies = packageJson[field];
|
|
787
|
+
if (!dependencies) continue;
|
|
788
|
+
for (const iconPackage of MANAGED_ICON_PACKAGES) {
|
|
789
|
+
if (!dependencies[iconPackage]) continue;
|
|
790
|
+
targetField ??= field;
|
|
791
|
+
if (iconPackage === selectedIconPackage) continue;
|
|
792
|
+
delete dependencies[iconPackage];
|
|
793
|
+
changed = true;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
if (!targetField) return;
|
|
797
|
+
if (!(packageJson.dependencies?.[selectedIconPackage] || packageJson.devDependencies?.[selectedIconPackage])) {
|
|
798
|
+
packageJson[targetField] ??= {};
|
|
799
|
+
packageJson[targetField][selectedIconPackage] = FALLBACK_ICON_PACKAGE_VERSION;
|
|
800
|
+
changed = true;
|
|
801
|
+
}
|
|
802
|
+
if (changed) await fs.writeJson(filepath, packageJson, { spaces: 2 });
|
|
803
|
+
}
|
|
804
|
+
async function syncPackageIconDependencies(projectPath, config) {
|
|
805
|
+
const packageJsonFiles = await fg("**/package.json", {
|
|
806
|
+
cwd: projectPath,
|
|
807
|
+
absolute: true,
|
|
808
|
+
ignore: [
|
|
809
|
+
"**/node_modules/**",
|
|
810
|
+
"**/dist/**",
|
|
811
|
+
"**/.astro/**"
|
|
812
|
+
]
|
|
813
|
+
});
|
|
814
|
+
await Promise.all(packageJsonFiles.map((filepath) => patchPackageJsonIconDependency(filepath, config)));
|
|
815
|
+
}
|
|
816
|
+
async function syncManagedTailwindCss(projectPath) {
|
|
817
|
+
const componentJsonFiles = await fg("**/components.json", {
|
|
818
|
+
cwd: projectPath,
|
|
819
|
+
absolute: true,
|
|
820
|
+
ignore: ["**/node_modules/**"]
|
|
821
|
+
});
|
|
822
|
+
await Promise.all(componentJsonFiles.map(async (componentJsonPath) => {
|
|
823
|
+
const cssRelativePath = (await fs.readJson(componentJsonPath))?.tailwind?.css;
|
|
824
|
+
if (typeof cssRelativePath !== "string" || cssRelativePath.length === 0) return;
|
|
825
|
+
const cssPath = path.resolve(path.dirname(componentJsonPath), cssRelativePath);
|
|
826
|
+
if (!await fs.pathExists(cssPath)) return;
|
|
827
|
+
await patchCssFileManagedTailwindImport(cssPath);
|
|
828
|
+
}));
|
|
829
|
+
}
|
|
830
|
+
async function syncAstroManagedFontCss(projectPath, fontVariable) {
|
|
831
|
+
const componentJsonFiles = await fg("**/components.json", {
|
|
832
|
+
cwd: projectPath,
|
|
833
|
+
ignore: [
|
|
834
|
+
"**/node_modules/**",
|
|
835
|
+
"**/dist/**",
|
|
836
|
+
"**/.astro/**"
|
|
837
|
+
]
|
|
838
|
+
});
|
|
839
|
+
const cssFiles = /* @__PURE__ */ new Set();
|
|
840
|
+
for (const relativePath of componentJsonFiles) {
|
|
841
|
+
const absolutePath = path.resolve(projectPath, relativePath);
|
|
842
|
+
const cssPath = (await fs.readJson(absolutePath))?.tailwind?.css;
|
|
843
|
+
if (typeof cssPath === "string" && cssPath.length > 0) cssFiles.add(path.resolve(path.dirname(absolutePath), cssPath));
|
|
844
|
+
}
|
|
845
|
+
await Promise.all(Array.from(cssFiles).map((filepath) => patchCssFileWithAstroFont(filepath, fontVariable)));
|
|
846
|
+
}
|
|
847
|
+
async function patchTemplateI18nFile(filepath, config) {
|
|
848
|
+
if (!config.rtl) return;
|
|
849
|
+
const nextLanguage = getDocumentLanguage(config);
|
|
850
|
+
const nextSource = buildTemplateI18nSource(getTemplateI18nVariant(filepath), nextLanguage);
|
|
851
|
+
if (!await fs.pathExists(filepath)) {
|
|
852
|
+
await fs.ensureDir(path.dirname(filepath));
|
|
853
|
+
await fs.writeFile(filepath, nextSource, "utf8");
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
const current = await fs.readFile(filepath, "utf8");
|
|
857
|
+
const next = current.includes("export const CURRENT_LANGUAGE") ? current.replace(/export const CURRENT_LANGUAGE: TemplateLanguage = "[^"]+";/, `export const CURRENT_LANGUAGE: TemplateLanguage = "${nextLanguage}";`) : nextSource;
|
|
858
|
+
if (next !== current) await fs.writeFile(filepath, next, "utf8");
|
|
859
|
+
}
|
|
860
|
+
async function patchLayoutFile(filepath, config) {
|
|
861
|
+
if (!await fs.pathExists(filepath)) return;
|
|
862
|
+
const current = await fs.readFile(filepath, "utf8");
|
|
863
|
+
if (current.includes("from \"@/i18n/ui\"")) return;
|
|
864
|
+
if (config.rtl) {
|
|
865
|
+
const next = transformTemplateLayoutToI18n(current);
|
|
866
|
+
if (next !== current) await fs.writeFile(filepath, next, "utf8");
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
const nextLanguage = getDocumentLanguage(config);
|
|
870
|
+
const nextDirection = getDocumentDirection(config);
|
|
871
|
+
let next = current.replace(/<html([^>]*?)lang="[^"]*"([^>]*)>/, "<html$1$2>").replace(/<html([^>]*?)dir="[^"]*"([^>]*)>/, "<html$1$2>");
|
|
872
|
+
next = next.replace(/<html([^>]*)>/, `<html$1 lang="${nextLanguage}"${config.rtl ? ` dir="${nextDirection}"` : ""}>`);
|
|
873
|
+
if (next !== current) await fs.writeFile(filepath, next, "utf8");
|
|
874
|
+
}
|
|
875
|
+
async function patchStarterPageFile(filepath, config) {
|
|
876
|
+
if (!config.rtl || !await fs.pathExists(filepath)) return;
|
|
877
|
+
const current = await fs.readFile(filepath, "utf8");
|
|
878
|
+
const next = transformStarterPageToI18n(current);
|
|
879
|
+
if (next !== current) await fs.writeFile(filepath, next, "utf8");
|
|
880
|
+
}
|
|
881
|
+
async function applyDesignSystemToProject(projectPath, config, options = {}) {
|
|
882
|
+
const componentJsonFiles = await fg("**/components.json", {
|
|
883
|
+
cwd: projectPath,
|
|
884
|
+
ignore: [
|
|
885
|
+
"**/node_modules/**",
|
|
886
|
+
"**/dist/**",
|
|
887
|
+
"**/.astro/**"
|
|
888
|
+
]
|
|
889
|
+
});
|
|
890
|
+
const cssFiles = /* @__PURE__ */ new Set();
|
|
891
|
+
for (const relativePath of componentJsonFiles) {
|
|
892
|
+
const absolutePath = path.resolve(projectPath, relativePath);
|
|
893
|
+
await patchComponentsJson(absolutePath, config);
|
|
894
|
+
const cssPath = (await fs.readJson(absolutePath))?.tailwind?.css;
|
|
895
|
+
if (typeof cssPath === "string" && cssPath.length > 0) {
|
|
896
|
+
const absoluteCssPath = path.resolve(path.dirname(absolutePath), cssPath);
|
|
897
|
+
cssFiles.add(absoluteCssPath);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
await Promise.all(Array.from(cssFiles).map((filepath) => patchCssFileWithTheme(filepath, config, options.themeVars)));
|
|
901
|
+
await syncPackageIconDependencies(projectPath, config);
|
|
902
|
+
await Promise.all([path.resolve(projectPath, "src/i18n/ui.ts"), path.resolve(projectPath, "apps/web/src/i18n/ui.ts")].map((filepath) => patchTemplateI18nFile(filepath, config)));
|
|
903
|
+
await Promise.all([path.resolve(projectPath, "src/layouts/Layout.astro"), path.resolve(projectPath, "apps/web/src/layouts/Layout.astro")].map((filepath) => patchLayoutFile(filepath, config)));
|
|
904
|
+
await Promise.all([path.resolve(projectPath, "src/pages/index.astro"), path.resolve(projectPath, "apps/web/src/pages/index.astro")].map((filepath) => patchStarterPageFile(filepath, config)));
|
|
905
|
+
await syncDesignSystemFontsInProject(projectPath, config);
|
|
906
|
+
}
|
|
907
|
+
async function applyDesignSystemThemeToProject(projectPath, config, options = {}) {
|
|
908
|
+
const componentJsonFiles = await fg("**/components.json", {
|
|
909
|
+
cwd: projectPath,
|
|
910
|
+
ignore: [
|
|
911
|
+
"**/node_modules/**",
|
|
912
|
+
"**/dist/**",
|
|
913
|
+
"**/.astro/**"
|
|
914
|
+
]
|
|
915
|
+
});
|
|
916
|
+
const cssFiles = /* @__PURE__ */ new Set();
|
|
917
|
+
for (const relativePath of componentJsonFiles) {
|
|
918
|
+
const absolutePath = path.resolve(projectPath, relativePath);
|
|
919
|
+
await patchComponentsJsonWithTheme(absolutePath, config);
|
|
920
|
+
const cssPath = (await fs.readJson(absolutePath))?.tailwind?.css;
|
|
921
|
+
if (typeof cssPath === "string" && cssPath.length > 0) cssFiles.add(path.resolve(path.dirname(absolutePath), cssPath));
|
|
922
|
+
}
|
|
923
|
+
await Promise.all(Array.from(cssFiles).map((filepath) => patchCssFileWithThemeOnly(filepath, config, options.themeVars)));
|
|
924
|
+
await syncPackageIconDependencies(projectPath, config);
|
|
925
|
+
}
|
|
926
|
+
async function applyDesignSystemFontToProject(projectPath, config) {
|
|
927
|
+
await syncDesignSystemFontsInProject(projectPath, config);
|
|
928
|
+
}
|
|
929
|
+
async function syncDesignSystemFontsInProject(projectPath, config) {
|
|
930
|
+
const managedFonts = [toManagedAstroFont(config.font), config.fontHeading !== "inherit" ? toManagedAstroFont(`font-heading-${config.fontHeading}`) : null].filter((font) => font !== null);
|
|
931
|
+
const managedFont = managedFonts.find((font) => font.cssVariable !== "--font-heading");
|
|
932
|
+
if (managedFonts.length > 0 && managedFont) {
|
|
933
|
+
await syncAstroFontsInProject(projectPath, managedFonts, managedFont.cssVariable);
|
|
934
|
+
await syncAstroManagedFontCss(projectPath, managedFont.cssVariable);
|
|
935
|
+
await cleanupAstroFontPackages(projectPath);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
//#endregion
|
|
940
|
+
//#region src/utils/icon-transform.ts
|
|
941
|
+
const FRONTMATTER_PATTERN = /^---\s*\n([\s\S]*?)\n---/;
|
|
942
|
+
function rewriteAstroIcons(content, iconLibrary) {
|
|
943
|
+
if (!(iconLibrary in ICON_LIBRARY_COLLECTIONS)) return content;
|
|
944
|
+
const library = iconLibrary;
|
|
945
|
+
const shouldRewriteLucideImports = library !== "lucide";
|
|
946
|
+
const frontmatterMatch = content.match(FRONTMATTER_PATTERN);
|
|
947
|
+
if (!frontmatterMatch) return content;
|
|
948
|
+
let frontmatter = frontmatterMatch[1];
|
|
949
|
+
let body = content.slice(frontmatterMatch[0].length);
|
|
950
|
+
const iconUsages = [];
|
|
951
|
+
const semanticIconImports = [];
|
|
952
|
+
if (shouldRewriteLucideImports) {
|
|
953
|
+
frontmatter = frontmatter.replace(/^\s*import\s+([A-Za-z0-9_$]+)\s+from\s+["']@lucide\/astro\/icons\/([^"']+)["'];?\s*$/gm, (full, localName, iconPath) => {
|
|
954
|
+
const semanticName = getSemanticIconNameFromLucidePath(iconPath);
|
|
955
|
+
if (!semanticName) return full;
|
|
956
|
+
iconUsages.push({
|
|
957
|
+
localName,
|
|
958
|
+
semanticName
|
|
959
|
+
});
|
|
960
|
+
return "";
|
|
961
|
+
});
|
|
962
|
+
frontmatter = frontmatter.replace(/import\s+\{([^}]*)\}\s+from\s+["']@lucide\/astro["'];?/g, (full, importsBlock) => {
|
|
963
|
+
const keptImports = [];
|
|
964
|
+
for (const rawImport of importsBlock.split(",")) {
|
|
965
|
+
const trimmedImport = rawImport.trim();
|
|
966
|
+
if (!trimmedImport) continue;
|
|
967
|
+
const aliasMatch = trimmedImport.match(/^([A-Za-z0-9_$]+)(?:\s+as\s+([A-Za-z0-9_$]+))?$/);
|
|
968
|
+
if (!aliasMatch) {
|
|
969
|
+
keptImports.push(trimmedImport);
|
|
970
|
+
continue;
|
|
971
|
+
}
|
|
972
|
+
const [, importedName, localAlias] = aliasMatch;
|
|
973
|
+
const semanticName = getSemanticIconNameFromLucideExport(importedName);
|
|
974
|
+
if (!semanticName) {
|
|
975
|
+
keptImports.push(trimmedImport);
|
|
976
|
+
continue;
|
|
977
|
+
}
|
|
978
|
+
iconUsages.push({
|
|
979
|
+
localName: localAlias ?? importedName,
|
|
980
|
+
semanticName
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
if (!keptImports.length) return "";
|
|
984
|
+
return `import { ${keptImports.join(", ")} } from "@lucide/astro";`;
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
frontmatter = frontmatter.replace(/^\s*import\s+([A-Za-z0-9_$]+)\s+from\s+["'][^"']*SemanticIcon\.astro["'];?\s*$/gm, (_full, localName) => {
|
|
988
|
+
semanticIconImports.push(localName);
|
|
989
|
+
return "";
|
|
990
|
+
});
|
|
991
|
+
if (!iconUsages.length && !semanticIconImports.length) return content;
|
|
992
|
+
for (const { localName, semanticName } of iconUsages.sort((left, right) => right.localName.length - left.localName.length)) {
|
|
993
|
+
const selfClosingPattern = new RegExp(`<${localName}(\\s[^>]*?)?\\s*/>`, "g");
|
|
994
|
+
const pairedPattern = new RegExp(`<${localName}(\\s[^>]*?)?>([\\s\\S]*?)<\\/${localName}>`, "g");
|
|
995
|
+
body = body.replace(selfClosingPattern, (_full, attributeString = "") => renderSemanticIconSvgWithAttributeString(library, semanticName, attributeString));
|
|
996
|
+
body = body.replace(pairedPattern, (_full, attributeString = "") => renderSemanticIconSvgWithAttributeString(library, semanticName, attributeString));
|
|
997
|
+
}
|
|
998
|
+
for (const localName of semanticIconImports.sort((left, right) => right.length - left.length)) {
|
|
999
|
+
const selfClosingPattern = new RegExp(`<${localName}(\\s[^>]*?)?\\s*/>`, "g");
|
|
1000
|
+
const pairedPattern = new RegExp(`<${localName}(\\s[^>]*?)?>([\\s\\S]*?)<\\/${localName}>`, "g");
|
|
1001
|
+
body = body.replace(selfClosingPattern, (full, attributeString = "") => renderSemanticIconUsage(full, library, attributeString));
|
|
1002
|
+
body = body.replace(pairedPattern, (full, attributeString = "") => renderSemanticIconUsage(full, library, attributeString));
|
|
1003
|
+
}
|
|
1004
|
+
return `---\n${frontmatter.split("\n").filter((line, index, lines) => {
|
|
1005
|
+
if (line.trim().length > 0) return true;
|
|
1006
|
+
return lines[index - 1]?.trim().length > 0;
|
|
1007
|
+
}).join("\n").trim()}\n---${body}`;
|
|
1008
|
+
}
|
|
1009
|
+
function renderSemanticIconUsage(original, library, attributeString = "") {
|
|
1010
|
+
const semanticName = attributeString.match(/\sname=(["'])([^"']+)\1/)?.[2];
|
|
1011
|
+
if (!semanticName || !SEMANTIC_ICON_NAMES.includes(semanticName)) return original;
|
|
1012
|
+
const normalizedAttributeString = attributeString.replace(/\sname=(["'])[^"']+\1/g, "").replace(/\slibrary=(["'])[^"']+\1/g, "").replace(/\sdata-icon-library=(["'])[^"']+\1/g, "").trim();
|
|
1013
|
+
return renderSemanticIconSvgWithAttributeString(library, semanticName, normalizedAttributeString);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
//#endregion
|
|
1017
|
+
//#region src/utils/menu-transform.ts
|
|
1018
|
+
const TRANSLUCENT_CLASSES = [
|
|
1019
|
+
"animate-none!",
|
|
1020
|
+
"relative",
|
|
1021
|
+
"bg-popover/70",
|
|
1022
|
+
"before:pointer-events-none",
|
|
1023
|
+
"before:absolute",
|
|
1024
|
+
"before:inset-0",
|
|
1025
|
+
"before:-z-1",
|
|
1026
|
+
"before:rounded-[inherit]",
|
|
1027
|
+
"before:backdrop-blur-2xl",
|
|
1028
|
+
"before:backdrop-saturate-150",
|
|
1029
|
+
"**:data-[slot$=-item]:focus:bg-foreground/10",
|
|
1030
|
+
"**:data-[slot$=-item]:data-highlighted:bg-foreground/10",
|
|
1031
|
+
"**:data-[slot$=-separator]:bg-foreground/5",
|
|
1032
|
+
"**:data-[slot$=-trigger]:focus:bg-foreground/10",
|
|
1033
|
+
"**:data-[slot$=-trigger]:aria-expanded:bg-foreground/10!",
|
|
1034
|
+
"**:data-[variant=destructive]:focus:bg-foreground/10!",
|
|
1035
|
+
"**:data-[variant=destructive]:text-accent-foreground!",
|
|
1036
|
+
"**:data-[variant=destructive]:**:text-accent-foreground!"
|
|
1037
|
+
];
|
|
1038
|
+
function transformMenuTokens(tokens, menuColor) {
|
|
1039
|
+
const transformed = [];
|
|
1040
|
+
for (const token of tokens) {
|
|
1041
|
+
if (token === "cn-menu-target") {
|
|
1042
|
+
if (menuColor === "inverted") transformed.push("dark");
|
|
1043
|
+
else if (menuColor === "default-translucent") transformed.push(...TRANSLUCENT_CLASSES);
|
|
1044
|
+
else if (menuColor === "inverted-translucent") transformed.push("dark", ...TRANSLUCENT_CLASSES);
|
|
1045
|
+
continue;
|
|
1046
|
+
}
|
|
1047
|
+
if (token === "cn-menu-translucent") {
|
|
1048
|
+
if (menuColor === "default-translucent" || menuColor === "inverted-translucent") transformed.push(...TRANSLUCENT_CLASSES);
|
|
1049
|
+
continue;
|
|
1050
|
+
}
|
|
1051
|
+
transformed.push(token);
|
|
1052
|
+
}
|
|
1053
|
+
return Array.from(new Set(transformed));
|
|
1054
|
+
}
|
|
1055
|
+
function rewriteAstroMenus(content, menuColor) {
|
|
1056
|
+
if (!content.includes("cn-menu-target") && !content.includes("cn-menu-translucent")) return content;
|
|
1057
|
+
return content.replace(/(["'])([^"']*\bcn-menu-(?:target|translucent)\b[^"']*)\1/g, (_match, quote, classes) => {
|
|
1058
|
+
return `${quote}${transformMenuTokens(String(classes).split(/\s+/).filter(Boolean), menuColor).join(" ")}${quote}`;
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
//#endregion
|
|
1063
|
+
//#region src/utils/astro-imports.ts
|
|
1064
|
+
function updateImportAliases(moduleSpecifier, config, isRemote = false) {
|
|
1065
|
+
const scope = config.aliases.components.split("/")[0];
|
|
1066
|
+
for (const [segment, alias] of [
|
|
1067
|
+
["lib/utils", config.aliases.utils],
|
|
1068
|
+
["lib", config.aliases.lib],
|
|
1069
|
+
["hooks", config.aliases.hooks]
|
|
1070
|
+
]) {
|
|
1071
|
+
if (!alias) continue;
|
|
1072
|
+
for (const prefix of [`@/${segment}`, `${scope}/${segment}`]) if (moduleSpecifier === prefix || moduleSpecifier.startsWith(`${prefix}/`)) return alias + moduleSpecifier.slice(prefix.length);
|
|
1073
|
+
}
|
|
1074
|
+
if (!moduleSpecifier.startsWith("@/") && !isRemote) return moduleSpecifier;
|
|
1075
|
+
let specifier = moduleSpecifier;
|
|
1076
|
+
if (isRemote && specifier.startsWith("@/")) specifier = specifier.replace(/^@\//, `@/registry/${config.style || "bejamas-juno"}/`);
|
|
1077
|
+
if (!specifier.startsWith("@/registry/")) {
|
|
1078
|
+
const alias = config.aliases.components.split("/")[0];
|
|
1079
|
+
return specifier.replace(/^@\//, `${alias}/`);
|
|
1080
|
+
}
|
|
1081
|
+
if (specifier.match(/^@\/registry\/(.+)\/ui/)) return specifier.replace(/^@\/registry\/(.+)\/ui/, config.aliases.ui ?? `${config.aliases.components}/ui`);
|
|
1082
|
+
if (config.aliases.components && specifier.match(/^@\/registry\/(.+)\/components/)) return specifier.replace(/^@\/registry\/(.+)\/components/, config.aliases.components);
|
|
1083
|
+
if (config.aliases.lib && specifier.match(/^@\/registry\/(.+)\/lib/)) return specifier.replace(/^@\/registry\/(.+)\/lib/, config.aliases.lib);
|
|
1084
|
+
if (config.aliases.hooks && specifier.match(/^@\/registry\/(.+)\/hooks/)) return specifier.replace(/^@\/registry\/(.+)\/hooks/, config.aliases.hooks);
|
|
1085
|
+
return specifier.replace(/^@\/registry\/[^/]+/, config.aliases.components);
|
|
1086
|
+
}
|
|
1087
|
+
function rewriteAstroImports(content, config) {
|
|
1088
|
+
let updated = content;
|
|
1089
|
+
const utilsAlias = config.aliases?.utils;
|
|
1090
|
+
const utilsImport = `${typeof utilsAlias === "string" && utilsAlias.includes("/") ? utilsAlias.split("/")[0] : "@"}/lib/utils`;
|
|
1091
|
+
updated = updated.replace(/import\s+([\s\S]*?)\s+from\s+["']([^"']+)["']/g, (full, importsPart, specifier) => {
|
|
1092
|
+
const next = updateImportAliases(specifier, config, false);
|
|
1093
|
+
let finalSpec = next;
|
|
1094
|
+
if (typeof importsPart === "string" && importsPart.split(/[{},\s]/).some((part) => part === "cn") && config.aliases.utils && (next === utilsImport || next === "@/lib/utils")) finalSpec = utilsImport === next ? next.replace(utilsImport, config.aliases.utils) : config.aliases.utils;
|
|
1095
|
+
if (finalSpec === specifier) return full;
|
|
1096
|
+
return full.replace(specifier, finalSpec);
|
|
1097
|
+
});
|
|
1098
|
+
updated = updated.replace(/(?:export\s+(?:type\s+)?(?:\{[^}]*\}|\*)\s+from\s*|import\s*\(\s*)["']([^"']+)["']/g, (full, specifier) => full.replace(specifier, updateImportAliases(specifier, config)));
|
|
1099
|
+
updated = updated.replace(/import\s+["']([^"']+)["']/g, (full, specifier) => {
|
|
1100
|
+
const next = updateImportAliases(specifier, config, false);
|
|
1101
|
+
if (next === specifier) return full;
|
|
1102
|
+
return full.replace(specifier, next);
|
|
1103
|
+
});
|
|
1104
|
+
updated = rewriteAstroMenus(updated, config.menuColor);
|
|
1105
|
+
return rewriteAstroIcons(updated, config.iconLibrary);
|
|
1106
|
+
}
|
|
1107
|
+
async function fixAstroImports(cwd, isVerbose, targetConfig) {
|
|
1108
|
+
const config = targetConfig ?? await getConfig(cwd);
|
|
1109
|
+
if (!config) return;
|
|
1110
|
+
const searchRoots = /* @__PURE__ */ new Set([
|
|
1111
|
+
config.resolvedPaths.components,
|
|
1112
|
+
config.resolvedPaths.ui,
|
|
1113
|
+
config.resolvedPaths.lib,
|
|
1114
|
+
config.resolvedPaths.hooks
|
|
1115
|
+
]);
|
|
1116
|
+
for (const root of Array.from(searchRoots)) {
|
|
1117
|
+
if (!root) continue;
|
|
1118
|
+
const astroFiles = await fg("**/*.{astro,ts,js}", {
|
|
1119
|
+
cwd: root,
|
|
1120
|
+
absolute: true,
|
|
1121
|
+
dot: false
|
|
1122
|
+
});
|
|
1123
|
+
for (const filePath of astroFiles) {
|
|
1124
|
+
const original = await fs$1.readFile(filePath, "utf8");
|
|
1125
|
+
const rewritten = rewriteAstroImports(original, config);
|
|
1126
|
+
if (rewritten === original) continue;
|
|
1127
|
+
await fs$1.writeFile(filePath, rewritten, "utf8");
|
|
1128
|
+
if (isVerbose) logger.info(`[bejamas-ui] fixed imports in ${path.relative(cwd, filePath)}`);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
//#endregion
|
|
1134
|
+
//#region src/utils/get-package-manager.ts
|
|
1135
|
+
async function getPackageManager(targetDir, { withFallback } = { withFallback: false }) {
|
|
1136
|
+
const packageManager = await detect({
|
|
1137
|
+
programmatic: true,
|
|
1138
|
+
cwd: targetDir
|
|
1139
|
+
});
|
|
1140
|
+
if (packageManager === "yarn@berry") return "yarn";
|
|
1141
|
+
if (packageManager === "pnpm@6") return "pnpm";
|
|
1142
|
+
if (packageManager === "bun") return "bun";
|
|
1143
|
+
if (packageManager === "deno") return "deno";
|
|
1144
|
+
if (!withFallback) return packageManager ?? "npm";
|
|
1145
|
+
const userAgent = process.env.npm_config_user_agent || "";
|
|
1146
|
+
if (userAgent.startsWith("yarn")) return "yarn";
|
|
1147
|
+
if (userAgent.startsWith("pnpm")) return "pnpm";
|
|
1148
|
+
if (userAgent.startsWith("bun")) return "bun";
|
|
1149
|
+
return "npm";
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
//#endregion
|
|
1153
|
+
//#region src/registry/errors.ts
|
|
1154
|
+
const RegistryErrorCode = {
|
|
1155
|
+
NETWORK_ERROR: "NETWORK_ERROR",
|
|
1156
|
+
NOT_FOUND: "NOT_FOUND",
|
|
1157
|
+
UNAUTHORIZED: "UNAUTHORIZED",
|
|
1158
|
+
FORBIDDEN: "FORBIDDEN",
|
|
1159
|
+
FETCH_ERROR: "FETCH_ERROR",
|
|
1160
|
+
NOT_CONFIGURED: "NOT_CONFIGURED",
|
|
1161
|
+
INVALID_CONFIG: "INVALID_CONFIG",
|
|
1162
|
+
MISSING_ENV_VARS: "MISSING_ENV_VARS",
|
|
1163
|
+
LOCAL_FILE_ERROR: "LOCAL_FILE_ERROR",
|
|
1164
|
+
PARSE_ERROR: "PARSE_ERROR",
|
|
1165
|
+
VALIDATION_ERROR: "VALIDATION_ERROR",
|
|
1166
|
+
UNKNOWN_ERROR: "UNKNOWN_ERROR"
|
|
1167
|
+
};
|
|
1168
|
+
var RegistryError = class extends Error {
|
|
1169
|
+
constructor(message, options = {}) {
|
|
1170
|
+
super(message);
|
|
1171
|
+
this.name = "RegistryError";
|
|
1172
|
+
this.code = options.code || RegistryErrorCode.UNKNOWN_ERROR;
|
|
1173
|
+
this.statusCode = options.statusCode;
|
|
1174
|
+
this.cause = options.cause;
|
|
1175
|
+
this.context = options.context;
|
|
1176
|
+
this.suggestion = options.suggestion;
|
|
1177
|
+
this.timestamp = /* @__PURE__ */ new Date();
|
|
1178
|
+
if (Error.captureStackTrace) Error.captureStackTrace(this, this.constructor);
|
|
1179
|
+
}
|
|
1180
|
+
toJSON() {
|
|
1181
|
+
return {
|
|
1182
|
+
name: this.name,
|
|
1183
|
+
message: this.message,
|
|
1184
|
+
code: this.code,
|
|
1185
|
+
statusCode: this.statusCode,
|
|
1186
|
+
context: this.context,
|
|
1187
|
+
suggestion: this.suggestion,
|
|
1188
|
+
timestamp: this.timestamp,
|
|
1189
|
+
stack: this.stack
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
};
|
|
1193
|
+
|
|
1194
|
+
//#endregion
|
|
1195
|
+
//#region src/utils/handle-error.ts
|
|
1196
|
+
function handleError(error) {
|
|
1197
|
+
logger.break();
|
|
1198
|
+
logger.error(`Something went wrong. Please check the error below for more details.`);
|
|
1199
|
+
logger.error(`If the problem persists, please open an issue on GitHub: https://github.com/bejamas/ui/issues`);
|
|
1200
|
+
logger.error("");
|
|
1201
|
+
if (typeof error === "string") {
|
|
1202
|
+
logger.error(error);
|
|
1203
|
+
logger.break();
|
|
1204
|
+
process.exit(1);
|
|
1205
|
+
}
|
|
1206
|
+
if (error instanceof RegistryError) {
|
|
1207
|
+
if (error.message) {
|
|
1208
|
+
logger.error(error.cause ? "Error:" : "Message:");
|
|
1209
|
+
logger.error(error.message);
|
|
1210
|
+
}
|
|
1211
|
+
if (error.cause) {
|
|
1212
|
+
logger.error("\nMessage:");
|
|
1213
|
+
logger.error(error.cause);
|
|
1214
|
+
}
|
|
1215
|
+
if (error.suggestion) {
|
|
1216
|
+
logger.error("\nSuggestion:");
|
|
1217
|
+
logger.error(error.suggestion);
|
|
1218
|
+
}
|
|
1219
|
+
logger.break();
|
|
1220
|
+
process.exit(1);
|
|
1221
|
+
}
|
|
1222
|
+
if (error instanceof z.ZodError) {
|
|
1223
|
+
logger.error("Validation failed:");
|
|
1224
|
+
for (const [key, value] of Object.entries(error.flatten().fieldErrors)) logger.error(`- ${highlighter.info(key)}: ${value}`);
|
|
1225
|
+
logger.break();
|
|
1226
|
+
process.exit(1);
|
|
1227
|
+
}
|
|
1228
|
+
if (error instanceof Error) {
|
|
1229
|
+
logger.error(error.message);
|
|
1230
|
+
logger.break();
|
|
1231
|
+
process.exit(1);
|
|
1232
|
+
}
|
|
1233
|
+
logger.break();
|
|
1234
|
+
process.exit(1);
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
//#endregion
|
|
1238
|
+
//#region src/utils/create-project.ts
|
|
1239
|
+
const TEMPLATES = {
|
|
1240
|
+
astro: "astro",
|
|
1241
|
+
"astro-monorepo": "astro-monorepo",
|
|
1242
|
+
"astro-with-component-docs-monorepo": "astro-with-component-docs-monorepo"
|
|
1243
|
+
};
|
|
1244
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
1245
|
+
const __dirname = path$1.dirname(__filename);
|
|
1246
|
+
const TEMPLATE_DIRNAME = {
|
|
1247
|
+
astro: "astro",
|
|
1248
|
+
"astro-monorepo": "monorepo-astro",
|
|
1249
|
+
"astro-with-component-docs-monorepo": "monorepo-astro-with-docs"
|
|
1250
|
+
};
|
|
1251
|
+
const TEMPLATE_EXCLUDED_BASENAMES = /* @__PURE__ */ new Set(["node_modules", ".astro"]);
|
|
1252
|
+
const DEFAULT_TEMPLATE_REPOSITORY = "bejamas/ui";
|
|
1253
|
+
const DEFAULT_TEMPLATE_REF = "main";
|
|
1254
|
+
function resolveLocalTemplatesDir(env = process.env) {
|
|
1255
|
+
const override = env.BEJAMAS_LOCAL_TEMPLATES_DIR?.trim();
|
|
1256
|
+
if (override) return path$1.resolve(override);
|
|
1257
|
+
for (const relativePath of [
|
|
1258
|
+
"../../../../templates",
|
|
1259
|
+
"../../../templates",
|
|
1260
|
+
"../../templates"
|
|
1261
|
+
]) {
|
|
1262
|
+
const candidate = path$1.resolve(__dirname, relativePath);
|
|
1263
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
1264
|
+
}
|
|
1265
|
+
return path$1.resolve(__dirname, "../../../../templates");
|
|
1266
|
+
}
|
|
1267
|
+
const LOCAL_TEMPLATES_DIR = resolveLocalTemplatesDir();
|
|
1268
|
+
function resolvePackageMetadataPath() {
|
|
1269
|
+
for (const relativePath of ["../package.json", "../../package.json"]) {
|
|
1270
|
+
const candidate = path$1.resolve(__dirname, relativePath);
|
|
1271
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
1272
|
+
}
|
|
1273
|
+
return path$1.resolve(__dirname, "../package.json");
|
|
1274
|
+
}
|
|
1275
|
+
function readPackageMetadata() {
|
|
1276
|
+
try {
|
|
1277
|
+
return fs.readJsonSync(resolvePackageMetadataPath());
|
|
1278
|
+
} catch {
|
|
1279
|
+
return {};
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
const BEJAMAS_PACKAGE_METADATA = readPackageMetadata();
|
|
1283
|
+
function shouldCopyTemplateEntry(source) {
|
|
1284
|
+
return !TEMPLATE_EXCLUDED_BASENAMES.has(path$1.basename(source));
|
|
1285
|
+
}
|
|
1286
|
+
async function copyTemplateIntoProject(templateSource, projectPath) {
|
|
1287
|
+
await fs.copy(templateSource, projectPath, { filter: shouldCopyTemplateEntry });
|
|
1288
|
+
}
|
|
1289
|
+
function resolveRemoteTemplateRefs(metadata = BEJAMAS_PACKAGE_METADATA, env = process.env) {
|
|
1290
|
+
const refs = [
|
|
1291
|
+
env.BEJAMAS_TEMPLATE_REF?.trim(),
|
|
1292
|
+
metadata.gitHead?.trim(),
|
|
1293
|
+
metadata.version && !metadata.version.includes("-") ? `bejamas@${metadata.version}` : void 0,
|
|
1294
|
+
DEFAULT_TEMPLATE_REF
|
|
1295
|
+
].filter((ref) => Boolean(ref));
|
|
1296
|
+
return [...new Set(refs)];
|
|
1297
|
+
}
|
|
1298
|
+
function buildTemplateArchiveUrl(ref, repository = DEFAULT_TEMPLATE_REPOSITORY) {
|
|
1299
|
+
return `https://api.github.com/repos/${repository}/tarball/${encodeURIComponent(ref)}`;
|
|
1300
|
+
}
|
|
1301
|
+
function getGitHubRequestHeaders(env = process.env) {
|
|
1302
|
+
const headers = { "User-Agent": `bejamas/${BEJAMAS_PACKAGE_METADATA.version ?? "dev"}` };
|
|
1303
|
+
const token = env.BEJAMAS_GITHUB_TOKEN?.trim() || env.GITHUB_TOKEN?.trim();
|
|
1304
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
1305
|
+
return headers;
|
|
1306
|
+
}
|
|
1307
|
+
async function downloadTemplateArchive(ref, targetDir) {
|
|
1308
|
+
const archiveUrl = buildTemplateArchiveUrl(ref);
|
|
1309
|
+
const archivePath = path$1.join(targetDir, "repo.tar.gz");
|
|
1310
|
+
const response = await fetch(archiveUrl, {
|
|
1311
|
+
headers: getGitHubRequestHeaders(),
|
|
1312
|
+
signal: AbortSignal.timeout(3e4)
|
|
1313
|
+
});
|
|
1314
|
+
if (!response.ok) throw new Error(`GitHub returned ${response.status} ${response.statusText} for ${ref}.`);
|
|
1315
|
+
await fs.ensureDir(targetDir);
|
|
1316
|
+
await fs.outputFile(archivePath, Buffer.from(await response.arrayBuffer()));
|
|
1317
|
+
await tar.x({
|
|
1318
|
+
cwd: targetDir,
|
|
1319
|
+
file: archivePath,
|
|
1320
|
+
strict: true
|
|
1321
|
+
});
|
|
1322
|
+
const extractedRoot = (await fs.readdir(targetDir)).filter((entry) => entry !== "repo.tar.gz").find((entry) => fs.statSync(path$1.join(targetDir, entry)).isDirectory());
|
|
1323
|
+
if (!extractedRoot) throw new Error(`Downloaded archive for ${ref} did not extract correctly.`);
|
|
1324
|
+
return path$1.join(targetDir, extractedRoot);
|
|
1325
|
+
}
|
|
1326
|
+
async function copyTemplateFromGitHub(templateDirName, projectPath) {
|
|
1327
|
+
const refs = resolveRemoteTemplateRefs();
|
|
1328
|
+
const failures = [];
|
|
1329
|
+
for (const ref of refs) {
|
|
1330
|
+
const tempDir = path$1.join(os.tmpdir(), `bejamas-template-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`);
|
|
1331
|
+
try {
|
|
1332
|
+
const extractedRoot = await downloadTemplateArchive(ref, tempDir);
|
|
1333
|
+
const templateSource = path$1.join(extractedRoot, "templates", templateDirName);
|
|
1334
|
+
if (!await fs.pathExists(templateSource)) throw new Error(`Template templates/${templateDirName} was not found.`);
|
|
1335
|
+
await copyTemplateIntoProject(templateSource, projectPath);
|
|
1336
|
+
return ref;
|
|
1337
|
+
} catch (error) {
|
|
1338
|
+
failures.push(`${ref}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1339
|
+
} finally {
|
|
1340
|
+
await fs.remove(tempDir);
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
throw new Error([
|
|
1344
|
+
`Unable to download templates/${templateDirName} from GitHub.`,
|
|
1345
|
+
`Tried refs: ${refs.join(", ")}.`,
|
|
1346
|
+
failures.join("\n")
|
|
1347
|
+
].join("\n"));
|
|
1348
|
+
}
|
|
1349
|
+
async function applyLocalPackageOverrides(projectPath) {
|
|
1350
|
+
const bejamasPackageOverride = process.env.BEJAMAS_PACKAGE_OVERRIDE;
|
|
1351
|
+
if (!bejamasPackageOverride) return;
|
|
1352
|
+
const packageJsonPaths = await fg("**/package.json", {
|
|
1353
|
+
cwd: projectPath,
|
|
1354
|
+
absolute: true,
|
|
1355
|
+
ignore: ["**/node_modules/**"]
|
|
1356
|
+
});
|
|
1357
|
+
const normalizedOverride = bejamasPackageOverride.replace(/\\/g, "/");
|
|
1358
|
+
await Promise.all(packageJsonPaths.map(async (packageJsonPath) => {
|
|
1359
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
1360
|
+
let changed = false;
|
|
1361
|
+
for (const field of ["dependencies", "devDependencies"]) if (packageJson[field]?.bejamas) {
|
|
1362
|
+
packageJson[field].bejamas = `file:${normalizedOverride}`;
|
|
1363
|
+
changed = true;
|
|
1364
|
+
}
|
|
1365
|
+
if (changed) await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
1366
|
+
}));
|
|
1367
|
+
}
|
|
1368
|
+
async function createProject(options) {
|
|
1369
|
+
options = {
|
|
1370
|
+
srcDir: false,
|
|
1371
|
+
...options
|
|
1372
|
+
};
|
|
1373
|
+
let template = options.template && TEMPLATES[options.template] ? options.template : "astro";
|
|
1374
|
+
let projectName = "my-app";
|
|
1375
|
+
const isRemoteComponent = options.components?.length === 1 && !!options.components[0].match(/\/chat\/b\//);
|
|
1376
|
+
if (!options.force) {
|
|
1377
|
+
const { type, name } = await prompts([{
|
|
1378
|
+
type: options.template || isRemoteComponent ? null : "select",
|
|
1379
|
+
name: "type",
|
|
1380
|
+
message: `The path ${highlighter.info(options.cwd)} does not contain a package.json file.\n Would you like to start a new project?`,
|
|
1381
|
+
choices: [
|
|
1382
|
+
{
|
|
1383
|
+
title: "Astro",
|
|
1384
|
+
value: "astro"
|
|
1385
|
+
},
|
|
1386
|
+
{
|
|
1387
|
+
title: "Astro (Monorepo)",
|
|
1388
|
+
value: "astro-monorepo"
|
|
1389
|
+
},
|
|
1390
|
+
{
|
|
1391
|
+
title: "Astro with Component Docs (Monorepo)",
|
|
1392
|
+
value: "astro-with-component-docs-monorepo"
|
|
1393
|
+
}
|
|
1394
|
+
],
|
|
1395
|
+
initial: 0
|
|
1396
|
+
}, {
|
|
1397
|
+
type: "text",
|
|
1398
|
+
name: "name",
|
|
1399
|
+
message: "What is your project named?",
|
|
1400
|
+
initial: (_prev, values) => {
|
|
1401
|
+
return (options.template && TEMPLATES[options.template] && options.template || values.type || template)?.endsWith("monorepo") ? "my-monorepo" : "my-app";
|
|
1402
|
+
},
|
|
1403
|
+
format: (value) => value.trim(),
|
|
1404
|
+
validate: (value) => value.length > 128 ? `Name should be less than 128 characters.` : true
|
|
1405
|
+
}]);
|
|
1406
|
+
template = type ?? template;
|
|
1407
|
+
projectName = name;
|
|
1408
|
+
}
|
|
1409
|
+
const packageManager = await getPackageManager(options.cwd, { withFallback: true });
|
|
1410
|
+
const projectPath = `${options.cwd}/${projectName}`;
|
|
1411
|
+
try {
|
|
1412
|
+
await fs.access(options.cwd, fs.constants.W_OK);
|
|
1413
|
+
} catch (error) {
|
|
1414
|
+
logger.break();
|
|
1415
|
+
logger.error(`The path ${highlighter.info(options.cwd)} is not writable.`);
|
|
1416
|
+
logger.error(`It is likely you do not have write permissions for this folder or the path ${highlighter.info(options.cwd)} does not exist.`);
|
|
1417
|
+
logger.break();
|
|
1418
|
+
process.exit(1);
|
|
1419
|
+
}
|
|
1420
|
+
if (fs.existsSync(path$1.resolve(options.cwd, projectName, "package.json"))) {
|
|
1421
|
+
logger.break();
|
|
1422
|
+
logger.error(`A project with the name ${highlighter.info(projectName)} already exists.`);
|
|
1423
|
+
logger.error(`Please choose a different name and try again.`);
|
|
1424
|
+
logger.break();
|
|
1425
|
+
process.exit(1);
|
|
1426
|
+
}
|
|
1427
|
+
await createProjectFromTemplate(projectPath, {
|
|
1428
|
+
templateKey: template,
|
|
1429
|
+
packageManager,
|
|
1430
|
+
cwd: options.cwd
|
|
1431
|
+
});
|
|
1432
|
+
return {
|
|
1433
|
+
projectPath,
|
|
1434
|
+
projectName,
|
|
1435
|
+
template
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1438
|
+
async function createProjectFromTemplate(projectPath, options) {
|
|
1439
|
+
const createSpinner = spinner(`Creating a new project from template. This may take a few minutes.`).start();
|
|
1440
|
+
try {
|
|
1441
|
+
dotenv.config({ quiet: true });
|
|
1442
|
+
const templateDirName = TEMPLATE_DIRNAME[options.templateKey];
|
|
1443
|
+
const templateSource = path$1.resolve(LOCAL_TEMPLATES_DIR, templateDirName);
|
|
1444
|
+
if (await fs.pathExists(templateSource)) await copyTemplateIntoProject(templateSource, projectPath);
|
|
1445
|
+
else {
|
|
1446
|
+
createSpinner.text = `Downloading the ${highlighter.info(options.templateKey)} template from GitHub.`;
|
|
1447
|
+
await copyTemplateFromGitHub(templateDirName, projectPath);
|
|
1448
|
+
}
|
|
1449
|
+
await removeEmptyTemplateI18nDirs(projectPath);
|
|
1450
|
+
await applyLocalPackageOverrides(projectPath);
|
|
1451
|
+
await execa(options.packageManager, ["install"], { cwd: projectPath });
|
|
1452
|
+
try {
|
|
1453
|
+
const { stdout } = await execa("git", ["rev-parse", "--is-inside-work-tree"], { cwd: projectPath });
|
|
1454
|
+
if (!(stdout.trim() === "true")) {
|
|
1455
|
+
await execa("git", ["init"], { cwd: projectPath });
|
|
1456
|
+
await execa("git", ["add", "-A"], { cwd: projectPath });
|
|
1457
|
+
await execa("git", [
|
|
1458
|
+
"commit",
|
|
1459
|
+
"-m",
|
|
1460
|
+
"Initial commit"
|
|
1461
|
+
], { cwd: projectPath });
|
|
1462
|
+
}
|
|
1463
|
+
} catch (_) {}
|
|
1464
|
+
createSpinner?.succeed("Creating a new project from template.");
|
|
1465
|
+
} catch (error) {
|
|
1466
|
+
createSpinner?.fail("Something went wrong creating a new project from template.");
|
|
1467
|
+
handleError(error);
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
async function removeEmptyTemplateI18nDirs(projectPath) {
|
|
1471
|
+
const candidates = [path$1.resolve(projectPath, "src/i18n"), path$1.resolve(projectPath, "apps/web/src/i18n")];
|
|
1472
|
+
await Promise.all(candidates.map(async (candidate) => {
|
|
1473
|
+
if (!await fs.pathExists(candidate)) return;
|
|
1474
|
+
if ((await fs.readdir(candidate)).length === 0) await fs.remove(candidate);
|
|
1475
|
+
}));
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
//#endregion
|
|
1479
|
+
//#region src/utils/installed-ui-components.ts
|
|
1480
|
+
function toRegistryName(filename) {
|
|
1481
|
+
return filename.replace(/\.[^.]+$/, "").replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
1482
|
+
}
|
|
1483
|
+
async function pathExists$1(filepath) {
|
|
1484
|
+
try {
|
|
1485
|
+
await promises.access(filepath);
|
|
1486
|
+
return true;
|
|
1487
|
+
} catch {
|
|
1488
|
+
return false;
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
async function detectInstalledUiComponents(uiDir) {
|
|
1492
|
+
if (!await pathExists$1(uiDir)) return [];
|
|
1493
|
+
const entries = await promises.readdir(uiDir, { withFileTypes: true });
|
|
1494
|
+
const components = /* @__PURE__ */ new Set();
|
|
1495
|
+
for (const entry of entries) {
|
|
1496
|
+
if (entry.name.startsWith(".")) continue;
|
|
1497
|
+
const entryPath = path.resolve(uiDir, entry.name);
|
|
1498
|
+
if (entry.isDirectory()) {
|
|
1499
|
+
if ((await promises.readdir(entryPath, { withFileTypes: true })).some((child) => child.isFile() && (/\.(astro|tsx|jsx)$/.test(child.name) || /^(index\.(ts|js|tsx|jsx))$/.test(child.name)))) components.add(entry.name);
|
|
1500
|
+
continue;
|
|
1501
|
+
}
|
|
1502
|
+
if (entry.isFile() && /\.(astro|tsx|jsx)$/.test(entry.name) && !entry.name.startsWith("index.")) components.add(toRegistryName(entry.name));
|
|
1503
|
+
}
|
|
1504
|
+
return Array.from(components).sort();
|
|
1505
|
+
}
|
|
1506
|
+
async function getInstalledUiComponents(cwd) {
|
|
1507
|
+
const config = await getConfig(cwd);
|
|
1508
|
+
if (!config) return [];
|
|
1509
|
+
return detectInstalledUiComponents(config.resolvedPaths.ui);
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
//#endregion
|
|
1513
|
+
//#region src/utils/reorganize-components.ts
|
|
1514
|
+
async function fetchRegistryItem(componentName, registryUrl, style = "bejamas-juno") {
|
|
1515
|
+
const url = `${registryUrl}/styles/${style}/${componentName}.json`;
|
|
1516
|
+
try {
|
|
1517
|
+
const response = await fetch(url);
|
|
1518
|
+
if (!response.ok) {
|
|
1519
|
+
const fallbackUrl = `${registryUrl}/${componentName}.json`;
|
|
1520
|
+
const fallbackResponse = await fetch(fallbackUrl);
|
|
1521
|
+
if (!fallbackResponse.ok) return null;
|
|
1522
|
+
return await fallbackResponse.json();
|
|
1523
|
+
}
|
|
1524
|
+
return await response.json();
|
|
1525
|
+
} catch {
|
|
1526
|
+
return null;
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
function getSubfolderFromPaths(files) {
|
|
1530
|
+
if (!files || files.length === 0) return null;
|
|
1531
|
+
const uiFiles = files.filter((file) => file.type === "registry:ui");
|
|
1532
|
+
if (uiFiles.length === 0) return null;
|
|
1533
|
+
const subfolders = /* @__PURE__ */ new Set();
|
|
1534
|
+
for (const file of uiFiles) {
|
|
1535
|
+
const parts = file.path.split("/");
|
|
1536
|
+
const uiIndex = parts.indexOf("ui");
|
|
1537
|
+
if (uiIndex !== -1 && parts.length > uiIndex + 2) subfolders.add(parts[uiIndex + 1]);
|
|
1538
|
+
}
|
|
1539
|
+
if (subfolders.size === 1) return Array.from(subfolders)[0];
|
|
1540
|
+
if (uiFiles.length > 0) {
|
|
1541
|
+
const dirname = path.dirname(uiFiles[0].path);
|
|
1542
|
+
const folderName = path.basename(dirname);
|
|
1543
|
+
if (folderName && folderName !== "ui") return folderName;
|
|
1544
|
+
}
|
|
1545
|
+
return null;
|
|
1546
|
+
}
|
|
1547
|
+
async function pathExists(filePath) {
|
|
1548
|
+
try {
|
|
1549
|
+
await fs$1.access(filePath);
|
|
1550
|
+
return true;
|
|
1551
|
+
} catch {
|
|
1552
|
+
return false;
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
function resolveShadcnUiRelativePath(filePath, uiDir) {
|
|
1556
|
+
const normalizedFilePath = filePath.replace(/^\/|\/$/g, "");
|
|
1557
|
+
const lastTargetSegment = path.basename(uiDir.replace(/^\/|\/$/g, ""));
|
|
1558
|
+
if (!lastTargetSegment) return path.basename(normalizedFilePath);
|
|
1559
|
+
const fileSegments = normalizedFilePath.split("/");
|
|
1560
|
+
const commonDirIndex = fileSegments.findIndex((segment) => segment === lastTargetSegment);
|
|
1561
|
+
if (commonDirIndex === -1) return fileSegments[fileSegments.length - 1];
|
|
1562
|
+
return fileSegments.slice(commonDirIndex + 1).join("/");
|
|
1563
|
+
}
|
|
1564
|
+
/**
|
|
1565
|
+
* Current upstream shadcn workspace installs flatten `ui/foo/Bar.astro` when
|
|
1566
|
+
* the resolved ui target ends in a segment like `components` instead of `ui`.
|
|
1567
|
+
* We keep reorganization only for that compatibility case.
|
|
1568
|
+
*/
|
|
1569
|
+
function shouldReorganizeRegistryUiFiles(files, uiDir) {
|
|
1570
|
+
if (!uiDir) return false;
|
|
1571
|
+
if (!getSubfolderFromPaths(files)) return false;
|
|
1572
|
+
return (files ?? []).filter((file) => file.type === "registry:ui").some((file) => {
|
|
1573
|
+
return !resolveShadcnUiRelativePath(file.path, uiDir).includes("/");
|
|
1574
|
+
});
|
|
1575
|
+
}
|
|
1576
|
+
/**
|
|
1577
|
+
* Reorganizes one multi-file registry:ui item from flat output into its
|
|
1578
|
+
* expected subfolder. This is the filesystem-level compatibility shim.
|
|
1579
|
+
*/
|
|
1580
|
+
async function reorganizeRegistryUiFiles(files, uiDir, verbose, overwriteExisting = false) {
|
|
1581
|
+
const result = {
|
|
1582
|
+
totalMoved: 0,
|
|
1583
|
+
movedFiles: [],
|
|
1584
|
+
skippedFiles: []
|
|
1585
|
+
};
|
|
1586
|
+
if (!uiDir || !files || files.length === 0) return result;
|
|
1587
|
+
const subfolder = getSubfolderFromPaths(files);
|
|
1588
|
+
if (!subfolder) return result;
|
|
1589
|
+
const uiFiles = files.filter((file) => file.type === "registry:ui");
|
|
1590
|
+
const targetDir = path.join(uiDir, subfolder);
|
|
1591
|
+
for (const file of uiFiles) {
|
|
1592
|
+
const filename = path.basename(file.path);
|
|
1593
|
+
if (filename === "index.ts") continue;
|
|
1594
|
+
const flatPath = path.join(uiDir, filename);
|
|
1595
|
+
const targetPath = path.join(targetDir, filename);
|
|
1596
|
+
if (!await pathExists(flatPath)) continue;
|
|
1597
|
+
if (await pathExists(targetPath)) {
|
|
1598
|
+
if (overwriteExisting) {
|
|
1599
|
+
await fs$1.mkdir(targetDir, { recursive: true });
|
|
1600
|
+
await fs$1.unlink(targetPath);
|
|
1601
|
+
await fs$1.rename(flatPath, targetPath);
|
|
1602
|
+
result.totalMoved++;
|
|
1603
|
+
result.movedFiles.push(`${subfolder}/${filename}`);
|
|
1604
|
+
if (verbose) logger.info(`[bejamas-ui] Replaced ${subfolder}/${filename} with the reinstalled version`);
|
|
1605
|
+
continue;
|
|
1606
|
+
}
|
|
1607
|
+
try {
|
|
1608
|
+
await fs$1.unlink(flatPath);
|
|
1609
|
+
result.skippedFiles.push(`${subfolder}/${filename}`);
|
|
1610
|
+
if (verbose) logger.info(`[bejamas-ui] Removed flat duplicate: ${filename} (${subfolder}/${filename} exists)`);
|
|
1611
|
+
} catch {
|
|
1612
|
+
result.skippedFiles.push(`${subfolder}/${filename}`);
|
|
1613
|
+
}
|
|
1614
|
+
continue;
|
|
1615
|
+
}
|
|
1616
|
+
await fs$1.mkdir(targetDir, { recursive: true });
|
|
1617
|
+
await fs$1.rename(flatPath, targetPath);
|
|
1618
|
+
result.totalMoved++;
|
|
1619
|
+
result.movedFiles.push(`${subfolder}/${filename}`);
|
|
1620
|
+
if (verbose) logger.info(`[bejamas-ui] Moved ${filename} -> ${subfolder}/${filename}`);
|
|
1621
|
+
}
|
|
1622
|
+
if (await pathExists(targetDir)) await repairComponentBarrel(targetDir, files, overwriteExisting);
|
|
1623
|
+
return result;
|
|
1624
|
+
}
|
|
1625
|
+
async function repairComponentBarrel(targetDir, files, overwriteExisting = false) {
|
|
1626
|
+
const localFiles = (await fs$1.readdir(targetDir)).filter((name) => name.endsWith(".astro")).sort();
|
|
1627
|
+
if (!localFiles.length) return;
|
|
1628
|
+
const indexPath = path.join(targetDir, "index.ts");
|
|
1629
|
+
const existing = await fs$1.readFile(indexPath, "utf8").catch(() => "");
|
|
1630
|
+
const registryBarrel = files.find((file) => path.basename(file.path) === "index.ts")?.content ?? "";
|
|
1631
|
+
const cleaned = (overwriteExisting || !existing ? registryBarrel : existing).replace(/export\s*\{[^}]*\}\s*from\s*["'](\.\/[^"']+\.astro)["'];?/g, (statement, specifier) => localFiles.includes(specifier.slice(2)) ? statement : "").trim();
|
|
1632
|
+
const next = [cleaned, ...localFiles.filter((file) => {
|
|
1633
|
+
const name = path.basename(file, ".astro");
|
|
1634
|
+
return !new RegExp(`default\\s+as\\s+${name}\\b`).test(cleaned);
|
|
1635
|
+
}).map((file) => `export { default as ${path.basename(file, ".astro")} } from "./${file}";`)].filter(Boolean).join("\n") + "\n";
|
|
1636
|
+
if (next !== existing) await fs$1.writeFile(indexPath, next);
|
|
1637
|
+
}
|
|
1638
|
+
/** Resolve dependencies as well as the requested item before repairing output. */
|
|
1639
|
+
async function fetchRegistryTree(components, registryUrl, style = "bejamas-juno") {
|
|
1640
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1641
|
+
const items = [];
|
|
1642
|
+
async function visit(name) {
|
|
1643
|
+
if (seen.has(name)) return;
|
|
1644
|
+
seen.add(name);
|
|
1645
|
+
if (!/^[a-z0-9-]+$/.test(name)) return;
|
|
1646
|
+
const item = await fetchRegistryItem(name, registryUrl, style);
|
|
1647
|
+
if (!item) throw new Error(`Unable to load registry item ${name} for installation validation.`);
|
|
1648
|
+
for (const dependency of item.registryDependencies ?? []) await visit(dependency);
|
|
1649
|
+
items.push(item);
|
|
1650
|
+
}
|
|
1651
|
+
for (const name of components) await visit(name);
|
|
1652
|
+
return items;
|
|
1653
|
+
}
|
|
1654
|
+
async function reorganizeComponents(components, uiDir, registryUrl, verbose, style = "bejamas-juno", overwriteExisting = false) {
|
|
1655
|
+
const result = {
|
|
1656
|
+
totalMoved: 0,
|
|
1657
|
+
movedFiles: [],
|
|
1658
|
+
skippedFiles: []
|
|
1659
|
+
};
|
|
1660
|
+
if (!uiDir || components.length === 0) return result;
|
|
1661
|
+
const items = await fetchRegistryTree(components, registryUrl, style);
|
|
1662
|
+
for (const item of items) {
|
|
1663
|
+
if (!shouldReorganizeRegistryUiFiles(item.files, uiDir)) continue;
|
|
1664
|
+
const componentResult = await reorganizeRegistryUiFiles(item.files, uiDir, verbose, overwriteExisting);
|
|
1665
|
+
result.totalMoved += componentResult.totalMoved;
|
|
1666
|
+
result.movedFiles.push(...componentResult.movedFiles);
|
|
1667
|
+
result.skippedFiles.push(...componentResult.skippedFiles);
|
|
1668
|
+
}
|
|
1669
|
+
const flatIndex = path.join(uiDir, "index.ts");
|
|
1670
|
+
const flatContent = await fs$1.readFile(flatIndex, "utf8").catch(() => null);
|
|
1671
|
+
if (flatContent && items.some((item) => item.files?.some((file) => file.type === "registry:ui" && path.basename(file.path) === "index.ts" && file.content.trim() === flatContent.trim()))) await fs$1.unlink(flatIndex);
|
|
1672
|
+
return result;
|
|
1673
|
+
}
|
|
1674
|
+
/** Update legacy template exports once the installed UI uses nested barrels. */
|
|
1675
|
+
async function repairUiPackageExports(uiDir, packageRoot) {
|
|
1676
|
+
const entries = await fs$1.readdir(uiDir, { withFileTypes: true }).catch(() => []);
|
|
1677
|
+
if (!entries.some((entry) => entry.isDirectory()) || entries.some((entry) => entry.name.endsWith(".astro"))) return;
|
|
1678
|
+
const packagePath = path.join(packageRoot, "package.json");
|
|
1679
|
+
const manifest = JSON.parse(await fs$1.readFile(packagePath, "utf8"));
|
|
1680
|
+
if (!manifest.exports || typeof manifest.exports !== "object") return;
|
|
1681
|
+
const relativeUi = path.relative(packageRoot, uiDir).split(path.sep).join("/");
|
|
1682
|
+
if (relativeUi.startsWith("../")) return;
|
|
1683
|
+
const prefix = `./${relativeUi}`;
|
|
1684
|
+
let changed = false;
|
|
1685
|
+
for (const [specifier, target] of Object.entries(manifest.exports)) if (target === `${prefix}/*.astro` || target === `${prefix}/*.ts`) {
|
|
1686
|
+
manifest.exports[specifier] = `${prefix}/*/index.ts`;
|
|
1687
|
+
changed = true;
|
|
1688
|
+
}
|
|
1689
|
+
if (changed) await fs$1.writeFile(packagePath, JSON.stringify(manifest, null, 2) + "\n");
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
//#endregion
|
|
1693
|
+
//#region src/utils/shadcn-cli.ts
|
|
1694
|
+
const PINNED_SHADCN_VERSION = "4.21.0";
|
|
1695
|
+
const PINNED_SHADCN_PACKAGE = `shadcn@${PINNED_SHADCN_VERSION}`;
|
|
1696
|
+
const PINNED_SHADCN_EXEC_PREFIX = path.join(os$1.tmpdir(), "bejamas-shadcn", PINNED_SHADCN_VERSION);
|
|
1697
|
+
async function ensurePinnedShadcnExecPrefix() {
|
|
1698
|
+
await fs$1.mkdir(PINNED_SHADCN_EXEC_PREFIX, { recursive: true });
|
|
1699
|
+
return PINNED_SHADCN_EXEC_PREFIX;
|
|
1700
|
+
}
|
|
1701
|
+
function getNpmExecutable() {
|
|
1702
|
+
return process.platform === "win32" ? "npm.cmd" : "npm";
|
|
1703
|
+
}
|
|
1704
|
+
function buildPinnedShadcnInvocation(shadcnArgs) {
|
|
1705
|
+
return {
|
|
1706
|
+
cmd: getNpmExecutable(),
|
|
1707
|
+
args: [
|
|
1708
|
+
"exec",
|
|
1709
|
+
"--yes",
|
|
1710
|
+
"--prefix",
|
|
1711
|
+
PINNED_SHADCN_EXEC_PREFIX,
|
|
1712
|
+
`--package=${PINNED_SHADCN_PACKAGE}`,
|
|
1713
|
+
"--",
|
|
1714
|
+
"shadcn",
|
|
1715
|
+
...shadcnArgs
|
|
1716
|
+
],
|
|
1717
|
+
source: "isolated"
|
|
1718
|
+
};
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
//#endregion
|
|
1722
|
+
//#region src/utils/shadcn-command.ts
|
|
1723
|
+
function extractPassthroughArgs(rawArgv, commandName) {
|
|
1724
|
+
const commandIndex = rawArgv.findIndex((arg) => arg === commandName);
|
|
1725
|
+
if (commandIndex === -1) return [];
|
|
1726
|
+
const rest = rawArgv.slice(commandIndex + 1);
|
|
1727
|
+
const doubleDashIndex = rest.indexOf("--");
|
|
1728
|
+
if (doubleDashIndex === -1) return [];
|
|
1729
|
+
return rest.slice(doubleDashIndex + 1);
|
|
1730
|
+
}
|
|
1731
|
+
async function runShadcnCommand({ cwd, args, env }) {
|
|
1732
|
+
await ensurePinnedShadcnExecPrefix();
|
|
1733
|
+
const invocation = buildPinnedShadcnInvocation(args);
|
|
1734
|
+
await execa(invocation.cmd, invocation.args, {
|
|
1735
|
+
cwd,
|
|
1736
|
+
env: {
|
|
1737
|
+
...process.env,
|
|
1738
|
+
...env
|
|
1739
|
+
},
|
|
1740
|
+
stdio: "inherit"
|
|
1741
|
+
});
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
//#endregion
|
|
1745
|
+
//#region src/commands/init.ts
|
|
1746
|
+
function resolveDesignSystemConfig(options) {
|
|
1747
|
+
const rtlLanguage = options.rtl && options.lang ? options.lang : DEFAULT_DESIGN_SYSTEM_CONFIG.rtlLanguage;
|
|
1748
|
+
if (options.preset && isPresetCode(options.preset)) {
|
|
1749
|
+
const decoded = decodePreset(options.preset);
|
|
1750
|
+
if (decoded) return normalizeDesignSystemConfig(designSystemConfigSchema.parse({
|
|
1751
|
+
...DEFAULT_DESIGN_SYSTEM_CONFIG,
|
|
1752
|
+
...decoded,
|
|
1753
|
+
template: options.template ?? DEFAULT_DESIGN_SYSTEM_CONFIG.template,
|
|
1754
|
+
rtl: options.rtl ?? false,
|
|
1755
|
+
rtlLanguage
|
|
1756
|
+
}));
|
|
1757
|
+
}
|
|
1758
|
+
const baseColor = options.baseColor ?? DEFAULT_DESIGN_SYSTEM_CONFIG.baseColor;
|
|
1759
|
+
return normalizeDesignSystemConfig(designSystemConfigSchema.parse({
|
|
1760
|
+
...DEFAULT_DESIGN_SYSTEM_CONFIG,
|
|
1761
|
+
template: options.template ?? DEFAULT_DESIGN_SYSTEM_CONFIG.template,
|
|
1762
|
+
rtl: options.rtl ?? false,
|
|
1763
|
+
rtlLanguage,
|
|
1764
|
+
baseColor,
|
|
1765
|
+
theme: baseColor === DEFAULT_DESIGN_SYSTEM_CONFIG.baseColor ? DEFAULT_DESIGN_SYSTEM_CONFIG.theme : baseColor
|
|
1766
|
+
}));
|
|
1767
|
+
}
|
|
1768
|
+
function buildInitUrl(config, themeRef, env = process.env) {
|
|
1769
|
+
const params = new URLSearchParams({
|
|
1770
|
+
preset: encodePreset(config),
|
|
1771
|
+
template: config.template
|
|
1772
|
+
});
|
|
1773
|
+
if (config.rtl) {
|
|
1774
|
+
params.set("rtl", "true");
|
|
1775
|
+
params.set("lang", config.rtlLanguage);
|
|
1776
|
+
}
|
|
1777
|
+
if (themeRef) params.set("themeRef", themeRef);
|
|
1778
|
+
return `${buildUiUrl("/init", env)}?${params.toString()}`;
|
|
1779
|
+
}
|
|
1780
|
+
function buildThemeVarsFromRegistryItem(item) {
|
|
1781
|
+
return item?.cssVars ?? null;
|
|
1782
|
+
}
|
|
1783
|
+
async function fetchInitThemeVars(initUrl) {
|
|
1784
|
+
const response = await fetch(initUrl);
|
|
1785
|
+
if (!response.ok) return null;
|
|
1786
|
+
return buildThemeVarsFromRegistryItem(await response.json());
|
|
1787
|
+
}
|
|
1788
|
+
const initOptionsSchema = z.object({
|
|
1789
|
+
cwd: z.string(),
|
|
1790
|
+
components: z.array(z.string()).optional(),
|
|
1791
|
+
yes: z.boolean(),
|
|
1792
|
+
reinstall: z.boolean().optional(),
|
|
1793
|
+
defaults: z.boolean(),
|
|
1794
|
+
force: z.boolean(),
|
|
1795
|
+
silent: z.boolean(),
|
|
1796
|
+
isNewProject: z.boolean(),
|
|
1797
|
+
srcDir: z.boolean().optional(),
|
|
1798
|
+
cssVariables: z.boolean(),
|
|
1799
|
+
template: z.string().optional().refine((val) => {
|
|
1800
|
+
if (val) return TEMPLATES[val];
|
|
1801
|
+
return true;
|
|
1802
|
+
}, { message: "Invalid template. Please use 'astro', 'astro-monorepo', or 'astro-with-component-docs-monorepo'." }),
|
|
1803
|
+
preset: z.string().optional(),
|
|
1804
|
+
baseColor: z.string().optional().refine((val) => {
|
|
1805
|
+
if (val) return BASE_COLORS.find((color) => color.name === val);
|
|
1806
|
+
return true;
|
|
1807
|
+
}, { message: `Invalid base color. Please use '${BASE_COLORS.map((color) => color.name).join("', '")}'` }),
|
|
1808
|
+
baseStyle: z.boolean(),
|
|
1809
|
+
rtl: z.boolean().default(false),
|
|
1810
|
+
lang: z.enum(RTL_LANGUAGE_VALUES).optional(),
|
|
1811
|
+
themeRef: z.string().optional()
|
|
1812
|
+
});
|
|
1813
|
+
function shouldReinstallExistingComponents(options) {
|
|
1814
|
+
return options.reinstall ?? Boolean(options.preset);
|
|
1815
|
+
}
|
|
1816
|
+
function ensureShadcnReinstallFlag(forwardedOptions, shouldReinstall) {
|
|
1817
|
+
if (!shouldReinstall || forwardedOptions.includes("--reinstall") || forwardedOptions.includes("--no-reinstall")) return forwardedOptions;
|
|
1818
|
+
const passthroughIndex = forwardedOptions.indexOf("--");
|
|
1819
|
+
if (passthroughIndex === -1) return [...forwardedOptions, "--reinstall"];
|
|
1820
|
+
return [
|
|
1821
|
+
...forwardedOptions.slice(0, passthroughIndex),
|
|
1822
|
+
"--reinstall",
|
|
1823
|
+
...forwardedOptions.slice(passthroughIndex)
|
|
1824
|
+
];
|
|
1825
|
+
}
|
|
1826
|
+
function extractOptionsForShadcnInit(rawArgv, cmd) {
|
|
1827
|
+
if (typeof cmd.getOptionValueSource === "function") {
|
|
1828
|
+
const opts = cmd.optsWithGlobals();
|
|
1829
|
+
const forwarded = [];
|
|
1830
|
+
const getSource = (key) => cmd.getOptionValueSource(key);
|
|
1831
|
+
const addBoolean = (key, flag, negateFlag) => {
|
|
1832
|
+
if (getSource(key) !== "cli") return;
|
|
1833
|
+
const value = opts[key];
|
|
1834
|
+
if (typeof value !== "boolean") return;
|
|
1835
|
+
if (value) forwarded.push(flag);
|
|
1836
|
+
else if (negateFlag) forwarded.push(negateFlag);
|
|
1837
|
+
};
|
|
1838
|
+
addBoolean("yes", "--yes");
|
|
1839
|
+
addBoolean("force", "--force");
|
|
1840
|
+
addBoolean("silent", "--silent");
|
|
1841
|
+
addBoolean("reinstall", "--reinstall", "--no-reinstall");
|
|
1842
|
+
const initIndex = rawArgv.findIndex((arg) => arg === "init");
|
|
1843
|
+
if (initIndex !== -1) {
|
|
1844
|
+
const rest = rawArgv.slice(initIndex + 1);
|
|
1845
|
+
const doubleDashIndex = rest.indexOf("--");
|
|
1846
|
+
if (doubleDashIndex !== -1) forwarded.push(...rest.slice(doubleDashIndex));
|
|
1847
|
+
}
|
|
1848
|
+
return forwarded;
|
|
1849
|
+
}
|
|
1850
|
+
const initIndex = rawArgv.findIndex((arg) => arg === "init");
|
|
1851
|
+
if (initIndex === -1) return [];
|
|
1852
|
+
const rest = rawArgv.slice(initIndex + 1);
|
|
1853
|
+
const forwarded = [];
|
|
1854
|
+
const filteredFlags = /* @__PURE__ */ new Set([
|
|
1855
|
+
"-v",
|
|
1856
|
+
"--verbose",
|
|
1857
|
+
"-t",
|
|
1858
|
+
"--template",
|
|
1859
|
+
"-b",
|
|
1860
|
+
"--base-color",
|
|
1861
|
+
"-p",
|
|
1862
|
+
"--preset",
|
|
1863
|
+
"--theme-ref",
|
|
1864
|
+
"-c",
|
|
1865
|
+
"--cwd",
|
|
1866
|
+
"-d",
|
|
1867
|
+
"--defaults",
|
|
1868
|
+
"--src-dir",
|
|
1869
|
+
"--no-src-dir",
|
|
1870
|
+
"--css-variables",
|
|
1871
|
+
"--no-css-variables",
|
|
1872
|
+
"--no-base-style",
|
|
1873
|
+
"--rtl",
|
|
1874
|
+
"--lang"
|
|
1875
|
+
]);
|
|
1876
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
1877
|
+
const token = rest[index];
|
|
1878
|
+
if (token === "--") {
|
|
1879
|
+
forwarded.push("--", ...rest.slice(index + 1));
|
|
1880
|
+
break;
|
|
1881
|
+
}
|
|
1882
|
+
if (!token.startsWith("-")) continue;
|
|
1883
|
+
if (filteredFlags.has(token)) continue;
|
|
1884
|
+
forwarded.push(token);
|
|
1885
|
+
}
|
|
1886
|
+
return forwarded;
|
|
1887
|
+
}
|
|
1888
|
+
const init = new Command().name("init").description("initialize your project and install dependencies").argument("[components...]", "names, url or local path to component").option("-t, --template <template>", "the template to use. (astro, astro-monorepo, astro-with-component-docs-monorepo)").option("-b, --base-color <base-color>", "the base color to use. (neutral, gray, zinc, stone, slate)", void 0).option("-p, --preset <preset>", "the encoded create preset to use").option("--theme-ref <theme-ref>", "the custom theme ref to use").option("-y, --yes", "skip confirmation prompt.", false).option("-d, --defaults", "use default configuration.", false).option("-f, --force", "force overwrite of existing configuration.", false).option("-c, --cwd <cwd>", "the working directory. defaults to the current directory.", process.cwd()).option("-s, --silent", "mute output.", false).option("--src-dir", "use the src directory when creating a new project.", false).option("--no-src-dir", "do not use the src directory when creating a new project.").option("--css-variables", "use css variables for theming.", true).option("--no-css-variables", "do not use css variables for theming.").option("--no-base-style", "do not install the base shadcn style.").option("--rtl", "enable right-to-left output", false).option("--lang <lang>", "set the RTL language. (ar, fa, he)").option("--reinstall", "re-install existing UI components. Enabled by default for preset switching.").option("--no-reinstall", "do not re-install existing UI components during preset switching.").action(async (_components, opts, cmd) => {
|
|
1889
|
+
try {
|
|
1890
|
+
await runInit({
|
|
1891
|
+
...opts,
|
|
1892
|
+
forwardedOptions: extractOptionsForShadcnInit(process.argv.slice(2), cmd)
|
|
1893
|
+
});
|
|
1894
|
+
} catch (error) {
|
|
1895
|
+
logger.break();
|
|
1896
|
+
handleError(error);
|
|
1897
|
+
} finally {
|
|
1898
|
+
clearRegistryContext();
|
|
1899
|
+
}
|
|
1900
|
+
});
|
|
1901
|
+
async function runInit(options) {
|
|
1902
|
+
const designConfig = resolveDesignSystemConfig(options);
|
|
1903
|
+
let newProjectTemplate;
|
|
1904
|
+
if (!options.skipPreflight) {
|
|
1905
|
+
if ((await preFlightInit(options)).errors["1"]) {
|
|
1906
|
+
const { projectPath, template } = await createProject(options);
|
|
1907
|
+
if (!projectPath) process.exit(1);
|
|
1908
|
+
options.cwd = projectPath;
|
|
1909
|
+
options.isNewProject = true;
|
|
1910
|
+
newProjectTemplate = template;
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
if (newProjectTemplate) {
|
|
1914
|
+
const projectPath = {
|
|
1915
|
+
"astro-monorepo": "apps/web",
|
|
1916
|
+
"astro-with-component-docs-monorepo": "apps/web",
|
|
1917
|
+
astro: ""
|
|
1918
|
+
};
|
|
1919
|
+
await applyDesignSystemToProject(options.cwd, {
|
|
1920
|
+
...designConfig,
|
|
1921
|
+
template: newProjectTemplate
|
|
1922
|
+
}, { themeVars: options.themeRef ? await fetchInitThemeVars(buildInitUrl({
|
|
1923
|
+
...designConfig,
|
|
1924
|
+
template: newProjectTemplate
|
|
1925
|
+
}, options.themeRef)) ?? void 0 : void 0 });
|
|
1926
|
+
options.cwd = path.resolve(options.cwd, projectPath[newProjectTemplate]);
|
|
1927
|
+
logger.log(`${highlighter.success("Success!")} Project initialization completed.\nYou may now add components.`);
|
|
1928
|
+
return await getConfig(options.cwd);
|
|
1929
|
+
}
|
|
1930
|
+
try {
|
|
1931
|
+
return await runExistingProjectInit(options, designConfig);
|
|
1932
|
+
} catch {
|
|
1933
|
+
process.exit(1);
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
async function runExistingProjectInit(options, designConfig = resolveDesignSystemConfig(options)) {
|
|
1937
|
+
const env = {
|
|
1938
|
+
...process.env,
|
|
1939
|
+
REGISTRY_URL: resolveRegistryUrl()
|
|
1940
|
+
};
|
|
1941
|
+
const initUrl = buildInitUrl(designConfig, options.themeRef);
|
|
1942
|
+
const themeVars = options.themeRef ? await fetchInitThemeVars(initUrl) ?? void 0 : void 0;
|
|
1943
|
+
const shouldReinstall = shouldReinstallExistingComponents(options);
|
|
1944
|
+
const reinstallComponents = shouldReinstall ? await getInstalledUiComponents(options.cwd) : [];
|
|
1945
|
+
const forwardedOptions = ensureShadcnReinstallFlag(options.forwardedOptions ?? [], shouldReinstall);
|
|
1946
|
+
await runShadcnCommand({
|
|
1947
|
+
cwd: options.cwd,
|
|
1948
|
+
args: [
|
|
1949
|
+
"init",
|
|
1950
|
+
initUrl,
|
|
1951
|
+
...reinstallComponents,
|
|
1952
|
+
...forwardedOptions
|
|
1953
|
+
],
|
|
1954
|
+
env
|
|
1955
|
+
});
|
|
1956
|
+
await syncManagedTailwindCss(options.cwd);
|
|
1957
|
+
await syncDesignSystemFontsInProject(options.cwd, designConfig);
|
|
1958
|
+
await applyDesignSystemToProject(options.cwd, designConfig, { themeVars });
|
|
1959
|
+
if (reinstallComponents.length > 0) {
|
|
1960
|
+
const config = await getConfig(options.cwd);
|
|
1961
|
+
const uiDir = config?.resolvedPaths.ui ?? "";
|
|
1962
|
+
const activeStyle = config?.style ?? "bejamas-juno";
|
|
1963
|
+
if (uiDir) await reorganizeComponents(reinstallComponents, uiDir, resolveRegistryUrl(), false, activeStyle, true);
|
|
1964
|
+
await fixAstroImports(options.cwd, false);
|
|
1965
|
+
}
|
|
1966
|
+
return await getConfig(options.cwd);
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
//#endregion
|
|
1970
|
+
//#region src/commands/docs.ts
|
|
1971
|
+
const docs = new Command().name("docs").description("proxy to shadcn docs").argument("<components...>", "component names").option("-c, --cwd <cwd>", "the working directory. defaults to the current directory.", process.cwd()).option("-b, --base <base>", "the base to use either 'base' or 'radix'. defaults to project base.").option("--json", "output as JSON.", false).action(async (components, opts) => {
|
|
1972
|
+
const cwd = path.resolve(opts.cwd ?? process.cwd());
|
|
1973
|
+
const rawArgv = process.argv.slice(2);
|
|
1974
|
+
const args = [
|
|
1975
|
+
"docs",
|
|
1976
|
+
...components,
|
|
1977
|
+
"--cwd",
|
|
1978
|
+
cwd
|
|
1979
|
+
];
|
|
1980
|
+
if (opts.base) args.push("--base", opts.base);
|
|
1981
|
+
if (opts.json) args.push("--json");
|
|
1982
|
+
const passthroughArgs = extractPassthroughArgs(rawArgv, "docs");
|
|
1983
|
+
if (passthroughArgs.length > 0) args.push(...passthroughArgs);
|
|
1984
|
+
try {
|
|
1985
|
+
await runShadcnCommand({
|
|
1986
|
+
cwd,
|
|
1987
|
+
args
|
|
1988
|
+
});
|
|
1989
|
+
} catch {
|
|
1990
|
+
process.exit(1);
|
|
1991
|
+
}
|
|
1992
|
+
});
|
|
1993
|
+
|
|
1994
|
+
//#endregion
|
|
1995
|
+
//#region src/utils/tsconfig-utils.ts
|
|
1996
|
+
/**
|
|
1997
|
+
* Read and parse tsconfig.json from the project root
|
|
1998
|
+
*/
|
|
1999
|
+
function readTsConfig(projectRoot) {
|
|
2000
|
+
try {
|
|
2001
|
+
const tsconfigPath = resolve(projectRoot, "tsconfig.json");
|
|
2002
|
+
if (!existsSync(tsconfigPath)) return null;
|
|
2003
|
+
const raw = readFileSync(tsconfigPath, "utf-8");
|
|
2004
|
+
return JSON.parse(raw);
|
|
2005
|
+
} catch {
|
|
2006
|
+
return null;
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
/**
|
|
2010
|
+
* Resolve an alias path (like @/components) using tsconfig.json paths
|
|
2011
|
+
*/
|
|
2012
|
+
function resolveAliasPathUsingTsConfig(inputPath, projectRoot) {
|
|
2013
|
+
const cfg = readTsConfig(projectRoot);
|
|
2014
|
+
if (!cfg || !cfg.compilerOptions) return null;
|
|
2015
|
+
const baseUrl = cfg.compilerOptions.baseUrl || ".";
|
|
2016
|
+
const paths = cfg.compilerOptions.paths || {};
|
|
2017
|
+
for (const [key, values] of Object.entries(paths)) {
|
|
2018
|
+
const pattern = key.replace(/\*/g, "(.*)");
|
|
2019
|
+
const re = new RegExp(`^${pattern}$`);
|
|
2020
|
+
const match = inputPath.match(re);
|
|
2021
|
+
if (!match) continue;
|
|
2022
|
+
const wildcard = match[1] || "";
|
|
2023
|
+
const first = Array.isArray(values) ? values[0] : values;
|
|
2024
|
+
if (!first) continue;
|
|
2025
|
+
const target = String(first).replace(/\*/g, wildcard);
|
|
2026
|
+
return resolve(projectRoot, baseUrl, target);
|
|
2027
|
+
}
|
|
2028
|
+
return null;
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
//#endregion
|
|
2032
|
+
//#region src/commands/docs-build.ts
|
|
2033
|
+
async function generateDocs({ cwd, outDir, verbose }) {
|
|
2034
|
+
const DEBUG = process.env.BEJAMAS_DEBUG === "1" || process.env.BEJAMAS_DEBUG === "true" || verbose;
|
|
2035
|
+
try {
|
|
2036
|
+
const shellCwd = process.cwd();
|
|
2037
|
+
if (cwd && cwd.length) process.env.BEJAMAS_UI_ROOT = resolve(shellCwd, cwd);
|
|
2038
|
+
if (outDir && outDir.length) process.env.BEJAMAS_DOCS_OUT_DIR = outDir;
|
|
2039
|
+
let projectRoot = shellCwd;
|
|
2040
|
+
let probe = shellCwd;
|
|
2041
|
+
for (let i = 0; i < 6 && probe; i += 1) {
|
|
2042
|
+
const candidate = resolve(probe, "components.json");
|
|
2043
|
+
if (existsSync(candidate)) {
|
|
2044
|
+
projectRoot = probe;
|
|
2045
|
+
try {
|
|
2046
|
+
const raw = readFileSync(candidate, "utf-8");
|
|
2047
|
+
const config = JSON.parse(raw);
|
|
2048
|
+
if (!cwd && !process.env.BEJAMAS_UI_ROOT && config?.aliases?.ui) {
|
|
2049
|
+
const mapped = String(config.aliases.ui);
|
|
2050
|
+
let uiAbs = null;
|
|
2051
|
+
if (mapped.startsWith("./") || mapped.startsWith("../") || isAbsolute(mapped)) uiAbs = resolve(projectRoot, mapped);
|
|
2052
|
+
else uiAbs = resolveAliasPathUsingTsConfig(mapped, projectRoot);
|
|
2053
|
+
if (!uiAbs && mapped.startsWith("@/")) uiAbs = resolve(projectRoot, "src", mapped.slice(2));
|
|
2054
|
+
if (uiAbs) process.env.BEJAMAS_UI_ROOT = uiAbs;
|
|
2055
|
+
}
|
|
2056
|
+
if (!cwd && !process.env.BEJAMAS_UI_ROOT && config?.tailwind?.css) {
|
|
2057
|
+
const cssRaw = String(config.tailwind.css);
|
|
2058
|
+
let cssAbs = null;
|
|
2059
|
+
if (cssRaw.startsWith("./") || cssRaw.startsWith("../") || isAbsolute(cssRaw)) cssAbs = resolve(projectRoot, cssRaw);
|
|
2060
|
+
else cssAbs = resolveAliasPathUsingTsConfig(cssRaw, projectRoot);
|
|
2061
|
+
if (!cssAbs && cssRaw.startsWith("@/")) cssAbs = resolve(projectRoot, "src", cssRaw.slice(2));
|
|
2062
|
+
if (cssAbs) {
|
|
2063
|
+
const uiRootFromCss = resolve(cssAbs, "..", "..", "..");
|
|
2064
|
+
process.env.BEJAMAS_UI_ROOT = uiRootFromCss;
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
if (!outDir && config?.aliases?.docs) {
|
|
2068
|
+
const mapped = String(config.aliases.docs);
|
|
2069
|
+
let outResolved = null;
|
|
2070
|
+
if (mapped.startsWith("./") || mapped.startsWith("../") || isAbsolute(mapped)) outResolved = mapped;
|
|
2071
|
+
else {
|
|
2072
|
+
const abs = resolveAliasPathUsingTsConfig(mapped, projectRoot);
|
|
2073
|
+
if (abs) outResolved = relative(projectRoot, abs);
|
|
2074
|
+
}
|
|
2075
|
+
if (!outResolved && mapped.startsWith("@/")) outResolved = mapped.replace(/^@\//, "src/");
|
|
2076
|
+
const finalOut = outResolved ?? mapped;
|
|
2077
|
+
if (finalOut && !process.env.BEJAMAS_DOCS_OUT_DIR) {
|
|
2078
|
+
process.env.BEJAMAS_DOCS_OUT_DIR = finalOut;
|
|
2079
|
+
process.env.BEJAMAS_DOCS_CWD = projectRoot;
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
} catch {}
|
|
2083
|
+
break;
|
|
2084
|
+
}
|
|
2085
|
+
const parent = resolve(probe, "..");
|
|
2086
|
+
probe = parent === probe ? null : parent;
|
|
2087
|
+
}
|
|
2088
|
+
if (!process.env.BEJAMAS_DOCS_CWD) process.env.BEJAMAS_DOCS_CWD = shellCwd;
|
|
2089
|
+
if (!process.env.BEJAMAS_UI_ROOT) {
|
|
2090
|
+
const defaultGuess = (() => {
|
|
2091
|
+
let current = shellCwd;
|
|
2092
|
+
for (let i = 0; i < 6; i += 1) {
|
|
2093
|
+
const cand = resolve(current, "packages/ui/package.json");
|
|
2094
|
+
if (existsSync(cand)) {
|
|
2095
|
+
const abs = resolve(current, "packages/ui");
|
|
2096
|
+
return relative(shellCwd, abs) || abs;
|
|
2097
|
+
}
|
|
2098
|
+
const parent = resolve(current, "..");
|
|
2099
|
+
if (parent === current) break;
|
|
2100
|
+
current = parent;
|
|
2101
|
+
}
|
|
2102
|
+
const nm = resolve(shellCwd, "node_modules/@bejamas/ui/package.json");
|
|
2103
|
+
if (existsSync(nm)) {
|
|
2104
|
+
const abs = resolve(shellCwd, "node_modules/@bejamas/ui");
|
|
2105
|
+
return relative(shellCwd, abs) || abs;
|
|
2106
|
+
}
|
|
2107
|
+
return "packages/ui";
|
|
2108
|
+
})();
|
|
2109
|
+
const { uiRoot } = await prompts({
|
|
2110
|
+
type: "text",
|
|
2111
|
+
name: "uiRoot",
|
|
2112
|
+
message: "Path to @bejamas/ui package root:",
|
|
2113
|
+
initial: defaultGuess,
|
|
2114
|
+
validate: (val) => {
|
|
2115
|
+
const p = resolve(shellCwd, val);
|
|
2116
|
+
return existsSync(resolve(p, "package.json")) ? true : `No package.json found in ${p}`;
|
|
2117
|
+
}
|
|
2118
|
+
});
|
|
2119
|
+
if (!uiRoot) {
|
|
2120
|
+
logger.error("@bejamas/ui root is required to generate docs.");
|
|
2121
|
+
process.exit(1);
|
|
2122
|
+
}
|
|
2123
|
+
process.env.BEJAMAS_UI_ROOT = resolve(shellCwd, uiRoot);
|
|
2124
|
+
}
|
|
2125
|
+
if (!process.env.BEJAMAS_DOCS_OUT_DIR) {
|
|
2126
|
+
const { out } = await prompts({
|
|
2127
|
+
type: "text",
|
|
2128
|
+
name: "out",
|
|
2129
|
+
message: "Where should we output docs (relative to project root)?",
|
|
2130
|
+
initial: "src/content/docs/components"
|
|
2131
|
+
});
|
|
2132
|
+
if (!out) {
|
|
2133
|
+
logger.error("An output directory is required to generate docs.");
|
|
2134
|
+
process.exit(1);
|
|
2135
|
+
}
|
|
2136
|
+
process.env.BEJAMAS_DOCS_OUT_DIR = out;
|
|
2137
|
+
process.env.BEJAMAS_DOCS_CWD = process.env.BEJAMAS_DOCS_CWD || shellCwd;
|
|
2138
|
+
}
|
|
2139
|
+
process.env.BEJAMAS_SKIP_AUTO_RUN = "1";
|
|
2140
|
+
logger.info(`Generating docs...`);
|
|
2141
|
+
if (DEBUG) {
|
|
2142
|
+
logger.info(`Generator entry: @/src/docs/generate-mdx/index`);
|
|
2143
|
+
if (process.env.BEJAMAS_UI_ROOT) logger.info(`UI root: ${process.env.BEJAMAS_UI_ROOT}`);
|
|
2144
|
+
if (process.env.BEJAMAS_DOCS_CWD) logger.info(`Docs CWD: ${process.env.BEJAMAS_DOCS_CWD}`);
|
|
2145
|
+
if (process.env.BEJAMAS_DOCS_OUT_DIR) logger.info(`Docs out: ${process.env.BEJAMAS_DOCS_OUT_DIR}`);
|
|
2146
|
+
}
|
|
2147
|
+
const mod = await import("./generate-mdx-BSRQJPrF.js");
|
|
2148
|
+
if (typeof mod.runDocsGenerator === "function") await mod.runDocsGenerator();
|
|
2149
|
+
else throw new Error("Failed to load docs generator. Export 'runDocsGenerator' not found.");
|
|
2150
|
+
} catch (err) {
|
|
2151
|
+
logger.error(err?.message || String(err));
|
|
2152
|
+
process.exit(1);
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
const docsBuild = new Command().name("docs:build").description("generate docs from @bejamas/ui components").option("-c, --cwd <cwd>", "path to UI working directory").option("-o, --out <outDir>", "output directory for generated MDX files").action(async (opts) => {
|
|
2156
|
+
await generateDocs({
|
|
2157
|
+
cwd: opts.cwd,
|
|
2158
|
+
outDir: opts.out,
|
|
2159
|
+
verbose: Boolean(opts.verbose)
|
|
2160
|
+
});
|
|
2161
|
+
});
|
|
2162
|
+
|
|
2163
|
+
//#endregion
|
|
2164
|
+
//#region src/commands/docs-check.ts
|
|
2165
|
+
const REQUIRED_FIELDS = [
|
|
2166
|
+
"name",
|
|
2167
|
+
"title",
|
|
2168
|
+
"description"
|
|
2169
|
+
];
|
|
2170
|
+
const RECOMMENDED_FIELDS = [
|
|
2171
|
+
"primaryExampleMDX",
|
|
2172
|
+
"usageMDX",
|
|
2173
|
+
"figmaUrl"
|
|
2174
|
+
];
|
|
2175
|
+
const FIELD_LABELS = {
|
|
2176
|
+
name: "@component",
|
|
2177
|
+
title: "@title",
|
|
2178
|
+
description: "@description",
|
|
2179
|
+
primaryExampleMDX: "@preview",
|
|
2180
|
+
usageMDX: "@usage",
|
|
2181
|
+
figmaUrl: "@figmaUrl"
|
|
2182
|
+
};
|
|
2183
|
+
function checkComponentDocs(filePath, fileName) {
|
|
2184
|
+
const content = readFileSync(filePath, "utf-8");
|
|
2185
|
+
const frontmatter = extractFrontmatter(content);
|
|
2186
|
+
const meta = parseJsDocMetadata(frontmatter);
|
|
2187
|
+
const componentName = fileName.replace(/\.astro$/i, "");
|
|
2188
|
+
const missingRequired = [];
|
|
2189
|
+
const missingRecommended = [];
|
|
2190
|
+
for (const field of REQUIRED_FIELDS) {
|
|
2191
|
+
const value = meta[field];
|
|
2192
|
+
if (!value || typeof value === "string" && !value.trim()) missingRequired.push(FIELD_LABELS[field] || field);
|
|
2193
|
+
}
|
|
2194
|
+
for (const field of RECOMMENDED_FIELDS) {
|
|
2195
|
+
const value = meta[field];
|
|
2196
|
+
if (!value || typeof value === "string" && !value.trim()) missingRecommended.push(FIELD_LABELS[field] || field);
|
|
2197
|
+
}
|
|
2198
|
+
let status;
|
|
2199
|
+
if (missingRequired.length > 0) status = "missing";
|
|
2200
|
+
else if (missingRecommended.length > 0) status = "incomplete";
|
|
2201
|
+
else status = "complete";
|
|
2202
|
+
return {
|
|
2203
|
+
name: componentName,
|
|
2204
|
+
file: fileName,
|
|
2205
|
+
status,
|
|
2206
|
+
missingRequired,
|
|
2207
|
+
missingRecommended
|
|
2208
|
+
};
|
|
2209
|
+
}
|
|
2210
|
+
async function checkDocs({ cwd, json }) {
|
|
2211
|
+
try {
|
|
2212
|
+
const shellCwd = process.cwd();
|
|
2213
|
+
let projectRoot = shellCwd;
|
|
2214
|
+
let probe = shellCwd;
|
|
2215
|
+
for (let i = 0; i < 6 && probe; i += 1) {
|
|
2216
|
+
const candidate = resolve(probe, "components.json");
|
|
2217
|
+
if (existsSync(candidate)) {
|
|
2218
|
+
projectRoot = probe;
|
|
2219
|
+
try {
|
|
2220
|
+
const raw = readFileSync(candidate, "utf-8");
|
|
2221
|
+
const config = JSON.parse(raw);
|
|
2222
|
+
if (!cwd && !process.env.BEJAMAS_UI_ROOT && config?.aliases?.ui) {
|
|
2223
|
+
const mapped = String(config.aliases.ui);
|
|
2224
|
+
let uiAbs = null;
|
|
2225
|
+
if (mapped.startsWith("./") || mapped.startsWith("../") || isAbsolute(mapped)) uiAbs = resolve(projectRoot, mapped);
|
|
2226
|
+
else uiAbs = resolveAliasPathUsingTsConfig(mapped, projectRoot);
|
|
2227
|
+
if (!uiAbs && mapped.startsWith("@/")) uiAbs = resolve(projectRoot, "src", mapped.slice(2));
|
|
2228
|
+
if (uiAbs) process.env.BEJAMAS_UI_ROOT = uiAbs;
|
|
2229
|
+
}
|
|
2230
|
+
if (!cwd && !process.env.BEJAMAS_UI_ROOT && config?.tailwind?.css) {
|
|
2231
|
+
const cssRaw = String(config.tailwind.css);
|
|
2232
|
+
let cssAbs = null;
|
|
2233
|
+
if (cssRaw.startsWith("./") || cssRaw.startsWith("../") || isAbsolute(cssRaw)) cssAbs = resolve(projectRoot, cssRaw);
|
|
2234
|
+
else cssAbs = resolveAliasPathUsingTsConfig(cssRaw, projectRoot);
|
|
2235
|
+
if (!cssAbs && cssRaw.startsWith("@/")) cssAbs = resolve(projectRoot, "src", cssRaw.slice(2));
|
|
2236
|
+
if (cssAbs) {
|
|
2237
|
+
const uiRootFromCss = resolve(cssAbs, "..", "..", "..");
|
|
2238
|
+
process.env.BEJAMAS_UI_ROOT = uiRootFromCss;
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
} catch {}
|
|
2242
|
+
break;
|
|
2243
|
+
}
|
|
2244
|
+
const parent = resolve(probe, "..");
|
|
2245
|
+
probe = parent === probe ? null : parent;
|
|
2246
|
+
}
|
|
2247
|
+
if (cwd && cwd.length) process.env.BEJAMAS_UI_ROOT = resolve(cwd);
|
|
2248
|
+
let uiRoot;
|
|
2249
|
+
try {
|
|
2250
|
+
uiRoot = resolveUiRoot(shellCwd);
|
|
2251
|
+
} catch {
|
|
2252
|
+
logger.error("Unable to locate @bejamas/ui. Use --cwd to specify the UI package path.");
|
|
2253
|
+
process.exit(1);
|
|
2254
|
+
}
|
|
2255
|
+
const componentsDir = join(uiRoot, "src", "components");
|
|
2256
|
+
if (!existsSync(componentsDir)) {
|
|
2257
|
+
logger.error(`Components directory not found: ${componentsDir}\n\nExpected structure: <uiRoot>/src/components/*.astro\nUse --cwd to specify a different UI package root.`);
|
|
2258
|
+
process.exit(1);
|
|
2259
|
+
}
|
|
2260
|
+
const files = readdirSync(componentsDir, { withFileTypes: true }).filter((e) => e.isFile() && extname(e.name).toLowerCase() === ".astro").map((e) => e.name).sort();
|
|
2261
|
+
if (files.length === 0) {
|
|
2262
|
+
logger.warn("No .astro component files found.");
|
|
2263
|
+
process.exit(0);
|
|
2264
|
+
}
|
|
2265
|
+
const results = [];
|
|
2266
|
+
for (const file of files) {
|
|
2267
|
+
const status = checkComponentDocs(join(componentsDir, file), file);
|
|
2268
|
+
results.push(status);
|
|
2269
|
+
}
|
|
2270
|
+
const complete = results.filter((r) => r.status === "complete");
|
|
2271
|
+
const incomplete = results.filter((r) => r.status === "incomplete");
|
|
2272
|
+
const missing = results.filter((r) => r.status === "missing");
|
|
2273
|
+
const checkResult = {
|
|
2274
|
+
total: results.length,
|
|
2275
|
+
complete,
|
|
2276
|
+
incomplete,
|
|
2277
|
+
missing
|
|
2278
|
+
};
|
|
2279
|
+
if (json) {
|
|
2280
|
+
console.log(JSON.stringify(checkResult, null, 2));
|
|
2281
|
+
if (missing.length > 0) process.exit(1);
|
|
2282
|
+
return;
|
|
2283
|
+
}
|
|
2284
|
+
const termWidth = Math.min(80, process.stdout.columns || 80);
|
|
2285
|
+
const headerLine = "━".repeat(termWidth);
|
|
2286
|
+
logger.break();
|
|
2287
|
+
console.log(dim("┌" + "─".repeat(termWidth - 2) + "┐"));
|
|
2288
|
+
console.log(dim("│") + bold(cyan(" docs:check")) + dim(" — Component Documentation Status") + " ".repeat(Math.max(0, termWidth - 47)) + dim("│"));
|
|
2289
|
+
console.log(dim("└" + "─".repeat(termWidth - 2) + "┘"));
|
|
2290
|
+
logger.break();
|
|
2291
|
+
const formatTag = (tag) => magenta(tag);
|
|
2292
|
+
const formatComponentName = (name) => bold(white(name));
|
|
2293
|
+
if (complete.length > 0) {
|
|
2294
|
+
console.log(green(`✓ Complete (${complete.length} component${complete.length === 1 ? "" : "s"}):`));
|
|
2295
|
+
const names = complete.map((c) => formatComponentName(c.name)).join(dim(", "));
|
|
2296
|
+
console.log(` ${names}`);
|
|
2297
|
+
logger.break();
|
|
2298
|
+
}
|
|
2299
|
+
if (incomplete.length > 0) {
|
|
2300
|
+
console.log(yellow(`⚠ Incomplete (${incomplete.length} component${incomplete.length === 1 ? "" : "s"}):`));
|
|
2301
|
+
for (const comp of incomplete) {
|
|
2302
|
+
const missingFields = comp.missingRecommended.map(formatTag).join(dim(", "));
|
|
2303
|
+
console.log(` ${formatComponentName(comp.name)} ${dim("-")} ${dim("missing:")} ${missingFields}`);
|
|
2304
|
+
}
|
|
2305
|
+
logger.break();
|
|
2306
|
+
}
|
|
2307
|
+
if (missing.length > 0) {
|
|
2308
|
+
console.log(red(`✗ Missing Docs (${missing.length} component${missing.length === 1 ? "" : "s"}):`));
|
|
2309
|
+
for (const comp of missing) {
|
|
2310
|
+
const missingFields = comp.missingRequired.map(formatTag).join(dim(", "));
|
|
2311
|
+
console.log(` ${formatComponentName(comp.name)} ${dim("-")} ${dim("missing:")} ${missingFields}`);
|
|
2312
|
+
}
|
|
2313
|
+
logger.break();
|
|
2314
|
+
}
|
|
2315
|
+
console.log(dim(headerLine));
|
|
2316
|
+
const completeText = green(`${complete.length}/${results.length} complete`);
|
|
2317
|
+
const incompleteText = incomplete.length > 0 ? yellow(`${incomplete.length} incomplete`) : dim(`${incomplete.length} incomplete`);
|
|
2318
|
+
const missingText = missing.length > 0 ? red(`${missing.length} missing docs`) : dim(`${missing.length} missing docs`);
|
|
2319
|
+
console.log(`${bold("Summary:")} ${completeText} ${dim("|")} ${incompleteText} ${dim("|")} ${missingText}`);
|
|
2320
|
+
logger.break();
|
|
2321
|
+
if (missing.length > 0) process.exit(1);
|
|
2322
|
+
} catch (err) {
|
|
2323
|
+
logger.error(err?.message || String(err));
|
|
2324
|
+
process.exit(1);
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
const docsCheck = new Command().name("docs:check").description("check documentation status for all components").option("-c, --cwd <cwd>", "path to UI working directory").option("--json", "output results as JSON").action(async (opts) => {
|
|
2328
|
+
await checkDocs({
|
|
2329
|
+
cwd: opts.cwd,
|
|
2330
|
+
json: Boolean(opts.json)
|
|
2331
|
+
});
|
|
2332
|
+
});
|
|
2333
|
+
|
|
2334
|
+
//#endregion
|
|
2335
|
+
//#region ../registry/src/lib/headless-dependencies.ts
|
|
2336
|
+
/** Headless behaviors imported by emitted frontmatter, client scripts or helpers. */
|
|
2337
|
+
function getHeadlessDependencies(files) {
|
|
2338
|
+
const dependencies = /* @__PURE__ */ new Set();
|
|
2339
|
+
for (const file of files) {
|
|
2340
|
+
const source = (file.content ?? "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
2341
|
+
for (const match of source.matchAll(/(?:\bfrom\s*|\bimport\s*\(?\s*)["'](@data-slot\/[^/"']+)(?:\/[^"']*)?["']/g)) dependencies.add(match[1]);
|
|
2342
|
+
}
|
|
2343
|
+
return [...dependencies].sort();
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
//#endregion
|
|
2347
|
+
//#region src/utils/registry-dependencies.ts
|
|
2348
|
+
function missingRegistryDependencies(manifest, items) {
|
|
2349
|
+
const declared = {
|
|
2350
|
+
...manifest.devDependencies,
|
|
2351
|
+
...manifest.dependencies
|
|
2352
|
+
};
|
|
2353
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
2354
|
+
for (const item of items) for (const specifier of item.dependencies ?? []) {
|
|
2355
|
+
const name = specifier.match(/^(@[^/]+\/[^@/]+|[^@/]+)/)?.[1];
|
|
2356
|
+
if (name && !declared[name]) candidates.set(name, specifier);
|
|
2357
|
+
}
|
|
2358
|
+
for (const item of items) for (const name of getHeadlessDependencies(item.files ?? [])) if (!declared[name] && !candidates.has(name)) candidates.set(name, name);
|
|
2359
|
+
return [...candidates.values()].sort();
|
|
2360
|
+
}
|
|
2361
|
+
async function ensureRegistryDependencies(cwd, items) {
|
|
2362
|
+
const missing = missingRegistryDependencies(JSON.parse(await fs$1.readFile(path.join(cwd, "package.json"), "utf8")), items);
|
|
2363
|
+
if (!missing.length) return;
|
|
2364
|
+
const manager = await getPackageManager(cwd);
|
|
2365
|
+
await execa(manager, [manager === "npm" ? "install" : "add", ...missing], {
|
|
2366
|
+
cwd,
|
|
2367
|
+
stdio: "inherit"
|
|
2368
|
+
});
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
//#endregion
|
|
2372
|
+
//#region src/commands/add.ts
|
|
2373
|
+
function extractOptionsForShadcn(rawArgv, cmd) {
|
|
2374
|
+
if (typeof cmd.getOptionValueSource === "function") {
|
|
2375
|
+
const opts = cmd.optsWithGlobals();
|
|
2376
|
+
const forwarded = [];
|
|
2377
|
+
const getSource = (key) => cmd.getOptionValueSource(key);
|
|
2378
|
+
const addBoolean = (key, flag, negateFlag) => {
|
|
2379
|
+
if (getSource(key) !== "cli") return;
|
|
2380
|
+
const value = opts[key];
|
|
2381
|
+
if (typeof value !== "boolean") return;
|
|
2382
|
+
if (value) forwarded.push(flag);
|
|
2383
|
+
else if (negateFlag) forwarded.push(negateFlag);
|
|
2384
|
+
};
|
|
2385
|
+
const addString = (key, flag) => {
|
|
2386
|
+
if (getSource(key) !== "cli") return;
|
|
2387
|
+
const value = opts[key];
|
|
2388
|
+
if (typeof value === "string") forwarded.push(flag, value);
|
|
2389
|
+
};
|
|
2390
|
+
const addOptionalString = (key, flag) => {
|
|
2391
|
+
if (getSource(key) !== "cli") return;
|
|
2392
|
+
const value = opts[key];
|
|
2393
|
+
if (value === true) {
|
|
2394
|
+
forwarded.push(flag);
|
|
2395
|
+
return;
|
|
2396
|
+
}
|
|
2397
|
+
if (typeof value === "string") forwarded.push(flag, value);
|
|
2398
|
+
};
|
|
2399
|
+
addBoolean("yes", "--yes");
|
|
2400
|
+
addBoolean("overwrite", "--overwrite");
|
|
2401
|
+
addBoolean("dryRun", "--dry-run");
|
|
2402
|
+
addString("cwd", "--cwd");
|
|
2403
|
+
addBoolean("all", "--all");
|
|
2404
|
+
addString("path", "--path");
|
|
2405
|
+
addBoolean("silent", "--silent");
|
|
2406
|
+
addBoolean("srcDir", "--src-dir", "--no-src-dir");
|
|
2407
|
+
addOptionalString("diff", "--diff");
|
|
2408
|
+
addOptionalString("view", "--view");
|
|
2409
|
+
const addIndex = rawArgv.findIndex((arg) => arg === "add");
|
|
2410
|
+
if (addIndex !== -1) {
|
|
2411
|
+
const rest = rawArgv.slice(addIndex + 1);
|
|
2412
|
+
const doubleDashIndex = rest.indexOf("--");
|
|
2413
|
+
if (doubleDashIndex !== -1) forwarded.push(...rest.slice(doubleDashIndex));
|
|
2414
|
+
}
|
|
2415
|
+
return forwarded;
|
|
2416
|
+
}
|
|
2417
|
+
const addIndex = rawArgv.findIndex((arg) => arg === "add");
|
|
2418
|
+
if (addIndex === -1) return [];
|
|
2419
|
+
const rest = rawArgv.slice(addIndex + 1);
|
|
2420
|
+
const forwarded = [];
|
|
2421
|
+
const optionsWithValues = /* @__PURE__ */ new Set([
|
|
2422
|
+
"-c",
|
|
2423
|
+
"--cwd",
|
|
2424
|
+
"-p",
|
|
2425
|
+
"--path",
|
|
2426
|
+
"--diff",
|
|
2427
|
+
"--view"
|
|
2428
|
+
]);
|
|
2429
|
+
const filteredFlags = /* @__PURE__ */ new Set(["-v", "--verbose"]);
|
|
2430
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
2431
|
+
const token = rest[index];
|
|
2432
|
+
if (token === "--") {
|
|
2433
|
+
forwarded.push("--", ...rest.slice(index + 1));
|
|
2434
|
+
break;
|
|
2435
|
+
}
|
|
2436
|
+
if (!token.startsWith("-")) continue;
|
|
2437
|
+
if (filteredFlags.has(token)) continue;
|
|
2438
|
+
forwarded.push(token);
|
|
2439
|
+
if (token.includes("=")) continue;
|
|
2440
|
+
if (optionsWithValues.has(token)) {
|
|
2441
|
+
const next = rest[index + 1];
|
|
2442
|
+
if (next) {
|
|
2443
|
+
forwarded.push(next);
|
|
2444
|
+
index += 1;
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
return forwarded;
|
|
2449
|
+
}
|
|
2450
|
+
function hasInspectionFlags(forwardedOptions) {
|
|
2451
|
+
return forwardedOptions.includes("--dry-run") || forwardedOptions.includes("--diff") || forwardedOptions.includes("--view");
|
|
2452
|
+
}
|
|
2453
|
+
function formatSkippedFilesHeading(count, overwriteUsed) {
|
|
2454
|
+
const noun = `file${count === 1 ? "" : "s"}`;
|
|
2455
|
+
if (overwriteUsed) return `Skipped ${count} ${noun}: (files might be identical)`;
|
|
2456
|
+
return `Skipped ${count} ${noun}: (files might be identical, use --overwrite to overwrite)`;
|
|
2457
|
+
}
|
|
2458
|
+
function ensureTrailingNewline(output) {
|
|
2459
|
+
return output.endsWith("\n") ? output : `${output}\n`;
|
|
2460
|
+
}
|
|
2461
|
+
function writeCapturedShadcnOutput(stdout, stderr) {
|
|
2462
|
+
if (stdout) process.stdout.write(ensureTrailingNewline(stdout));
|
|
2463
|
+
if (stderr) process.stderr.write(ensureTrailingNewline(stderr));
|
|
2464
|
+
}
|
|
2465
|
+
async function buildSubfolderMap(components, uiDir, registryUrl, style) {
|
|
2466
|
+
const filenameToSubfolders = /* @__PURE__ */ new Map();
|
|
2467
|
+
let requiresReorganization = false;
|
|
2468
|
+
for (const componentName of components) {
|
|
2469
|
+
const registryItems = await fetchRegistryTree([componentName], registryUrl, style);
|
|
2470
|
+
for (const registryItem of registryItems) {
|
|
2471
|
+
if (shouldReorganizeRegistryUiFiles(registryItem.files, uiDir)) requiresReorganization = true;
|
|
2472
|
+
const subfolder = getSubfolderFromPaths(registryItem.files);
|
|
2473
|
+
if (!subfolder) continue;
|
|
2474
|
+
for (const file of registryItem.files) if (file.type === "registry:ui") {
|
|
2475
|
+
const filename = path.basename(file.path);
|
|
2476
|
+
const subfolders = filenameToSubfolders.get(filename) || [];
|
|
2477
|
+
subfolders.push(subfolder);
|
|
2478
|
+
filenameToSubfolders.set(filename, subfolders);
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
const uniqueMap = /* @__PURE__ */ new Map();
|
|
2483
|
+
const sharedFilenames = /* @__PURE__ */ new Set();
|
|
2484
|
+
filenameToSubfolders.forEach((subfolders, filename) => {
|
|
2485
|
+
if (subfolders.length === 1) uniqueMap.set(filename, `${subfolders[0]}/${filename}`);
|
|
2486
|
+
else sharedFilenames.add(filename);
|
|
2487
|
+
});
|
|
2488
|
+
return {
|
|
2489
|
+
uniqueMap,
|
|
2490
|
+
sharedFilenames,
|
|
2491
|
+
requiresReorganization
|
|
2492
|
+
};
|
|
2493
|
+
}
|
|
2494
|
+
function rewritePaths(paths, mapResult) {
|
|
2495
|
+
const { uniqueMap, sharedFilenames } = mapResult;
|
|
2496
|
+
let currentSubfolder = null;
|
|
2497
|
+
return paths.map((filePath) => {
|
|
2498
|
+
const filename = path.basename(filePath);
|
|
2499
|
+
const parentDir = path.basename(path.dirname(filePath));
|
|
2500
|
+
const uniqueMapping = uniqueMap.get(filename);
|
|
2501
|
+
if (uniqueMapping) {
|
|
2502
|
+
const expectedSubfolder = path.dirname(uniqueMapping);
|
|
2503
|
+
currentSubfolder = expectedSubfolder;
|
|
2504
|
+
if (parentDir !== expectedSubfolder) return `${path.dirname(filePath)}/${uniqueMapping}`;
|
|
2505
|
+
return filePath;
|
|
2506
|
+
}
|
|
2507
|
+
if (sharedFilenames.has(filename) && currentSubfolder) {
|
|
2508
|
+
if (parentDir !== currentSubfolder) return `${path.dirname(filePath)}/${currentSubfolder}/${filename}`;
|
|
2509
|
+
}
|
|
2510
|
+
return filePath;
|
|
2511
|
+
});
|
|
2512
|
+
}
|
|
2513
|
+
async function fetchAvailableComponents(registryUrl) {
|
|
2514
|
+
const indexUrl = `${registryUrl}/index.json`;
|
|
2515
|
+
const response = await fetch(indexUrl);
|
|
2516
|
+
if (!response.ok) throw new Error(`Failed to fetch registry index: ${response.statusText}`);
|
|
2517
|
+
const data = await response.json();
|
|
2518
|
+
return Array.isArray(data) ? data : [];
|
|
2519
|
+
}
|
|
2520
|
+
async function promptForComponents(registryUrl) {
|
|
2521
|
+
const checkingSpinner = spinner("Checking registry.").start();
|
|
2522
|
+
let components;
|
|
2523
|
+
try {
|
|
2524
|
+
components = await fetchAvailableComponents(registryUrl);
|
|
2525
|
+
checkingSpinner.succeed();
|
|
2526
|
+
} catch {
|
|
2527
|
+
checkingSpinner.fail();
|
|
2528
|
+
logger.error("Failed to fetch available components from registry.");
|
|
2529
|
+
return null;
|
|
2530
|
+
}
|
|
2531
|
+
if (components.length === 0) {
|
|
2532
|
+
logger.warn("No components available in registry.");
|
|
2533
|
+
return null;
|
|
2534
|
+
}
|
|
2535
|
+
const choices = components.filter((component) => !component.type || component.type === "registry:ui").map((component) => ({
|
|
2536
|
+
title: component.name,
|
|
2537
|
+
value: component.name
|
|
2538
|
+
}));
|
|
2539
|
+
const { selected } = await prompts({
|
|
2540
|
+
type: "autocompleteMultiselect",
|
|
2541
|
+
name: "selected",
|
|
2542
|
+
message: "Which components would you like to add?",
|
|
2543
|
+
choices,
|
|
2544
|
+
hint: "- Space to select. Return to submit.",
|
|
2545
|
+
instructions: false
|
|
2546
|
+
});
|
|
2547
|
+
if (!selected) return null;
|
|
2548
|
+
return selected;
|
|
2549
|
+
}
|
|
2550
|
+
function parseShadcnOutput(stdout, stderr) {
|
|
2551
|
+
const result = {
|
|
2552
|
+
created: [],
|
|
2553
|
+
updated: [],
|
|
2554
|
+
skipped: []
|
|
2555
|
+
};
|
|
2556
|
+
const cleanStderr = stderr.replace(/\x1b\[[0-9;]*m/g, "");
|
|
2557
|
+
const cleanStdout = stdout.replace(/\x1b\[[0-9;]*m/g, "");
|
|
2558
|
+
const createdMatch = cleanStderr.match(/Created\s+(\d+)\s+file/i);
|
|
2559
|
+
const updatedMatch = cleanStderr.match(/Updated\s+(\d+)\s+file/i);
|
|
2560
|
+
const skippedMatch = cleanStderr.match(/Skipped\s+(\d+)\s+file/i);
|
|
2561
|
+
const createdCount = createdMatch ? parseInt(createdMatch[1], 10) : 0;
|
|
2562
|
+
const updatedCount = updatedMatch ? parseInt(updatedMatch[1], 10) : 0;
|
|
2563
|
+
const skippedCount = skippedMatch ? parseInt(skippedMatch[1], 10) : 0;
|
|
2564
|
+
const allPaths = [];
|
|
2565
|
+
for (const line of cleanStdout.split("\n")) {
|
|
2566
|
+
const match = line.match(/^\s+-\s+(.+)$/);
|
|
2567
|
+
if (match) allPaths.push(match[1].trim());
|
|
2568
|
+
}
|
|
2569
|
+
for (const line of cleanStderr.split("\n")) {
|
|
2570
|
+
const match = line.match(/^\s+-\s+(.+)$/);
|
|
2571
|
+
if (match) {
|
|
2572
|
+
const filePath = match[1].trim();
|
|
2573
|
+
if (!allPaths.includes(filePath)) allPaths.push(filePath);
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
let index = 0;
|
|
2577
|
+
for (let count = 0; count < createdCount && index < allPaths.length; count += 1) {
|
|
2578
|
+
result.created.push(allPaths[index]);
|
|
2579
|
+
index += 1;
|
|
2580
|
+
}
|
|
2581
|
+
for (let count = 0; count < updatedCount && index < allPaths.length; count += 1) {
|
|
2582
|
+
result.updated.push(allPaths[index]);
|
|
2583
|
+
index += 1;
|
|
2584
|
+
}
|
|
2585
|
+
for (let count = 0; count < skippedCount && index < allPaths.length; count += 1) {
|
|
2586
|
+
result.skipped.push(allPaths[index]);
|
|
2587
|
+
index += 1;
|
|
2588
|
+
}
|
|
2589
|
+
return result;
|
|
2590
|
+
}
|
|
2591
|
+
async function addComponents(cwd, packages, forwardedOptions, isVerbose, isSilent, inspectionMode) {
|
|
2592
|
+
const env = {
|
|
2593
|
+
...process.env,
|
|
2594
|
+
REGISTRY_URL: resolveRegistryUrl()
|
|
2595
|
+
};
|
|
2596
|
+
await ensurePinnedShadcnExecPrefix();
|
|
2597
|
+
const shadcnArgs = buildShadcnAddArgs(packages, forwardedOptions);
|
|
2598
|
+
const invocation = buildPinnedShadcnInvocation(shadcnArgs);
|
|
2599
|
+
if (isVerbose) logger.info(`[bejamas-ui] ${invocation.cmd} ${invocation.args.join(" ")}`);
|
|
2600
|
+
const registrySpinner = spinner("Checking registry.", { silent: isSilent });
|
|
2601
|
+
registrySpinner.start();
|
|
2602
|
+
try {
|
|
2603
|
+
const result = await execa(invocation.cmd, invocation.args, {
|
|
2604
|
+
cwd,
|
|
2605
|
+
env,
|
|
2606
|
+
input: "n\nn\nn\nn\nn\nn\nn\nn\nn\nn\n",
|
|
2607
|
+
stdout: "pipe",
|
|
2608
|
+
stderr: "pipe",
|
|
2609
|
+
reject: false
|
|
2610
|
+
});
|
|
2611
|
+
registrySpinner.succeed();
|
|
2612
|
+
spinner("Installing components.", { silent: isSilent }).succeed();
|
|
2613
|
+
const stdout = result.stdout || "";
|
|
2614
|
+
const stderr = result.stderr || "";
|
|
2615
|
+
if (isVerbose) {
|
|
2616
|
+
logger.info(`[bejamas-ui] Raw stdout: ${stdout}`);
|
|
2617
|
+
logger.info(`[bejamas-ui] Raw stderr: ${stderr}`);
|
|
2618
|
+
}
|
|
2619
|
+
if (inspectionMode) writeCapturedShadcnOutput(stdout, stderr);
|
|
2620
|
+
const parsed = inspectionMode ? {
|
|
2621
|
+
created: [],
|
|
2622
|
+
updated: [],
|
|
2623
|
+
skipped: []
|
|
2624
|
+
} : parseShadcnOutput(stdout, stderr);
|
|
2625
|
+
if (result.exitCode !== 0) {
|
|
2626
|
+
if (!inspectionMode) writeCapturedShadcnOutput(stdout, stderr);
|
|
2627
|
+
process.exit(result.exitCode);
|
|
2628
|
+
}
|
|
2629
|
+
return parsed;
|
|
2630
|
+
} catch {
|
|
2631
|
+
registrySpinner.fail();
|
|
2632
|
+
logger.error("Failed to add components");
|
|
2633
|
+
process.exit(1);
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
function buildShadcnAddArgs(packages, forwardedOptions) {
|
|
2637
|
+
const autoFlags = [];
|
|
2638
|
+
if (!forwardedOptions.includes("--yes")) autoFlags.push("--yes");
|
|
2639
|
+
return [
|
|
2640
|
+
"add",
|
|
2641
|
+
...packages,
|
|
2642
|
+
...autoFlags,
|
|
2643
|
+
...forwardedOptions
|
|
2644
|
+
];
|
|
2645
|
+
}
|
|
2646
|
+
const add = new Command().name("add").description("Add components via the Bejamas-managed shadcn registry flow").argument("[components...]", "Component package names to add").option("-y, --yes", "skip confirmation prompt.", false).option("-o, --overwrite", "overwrite existing files.", false).option("-c, --cwd <cwd>", "the working directory. defaults to the current directory.", process.cwd()).option("-a, --all", "add all available components", false).option("-p, --path <path>", "the path to add the component to.").option("-s, --silent", "mute output.", false).option("--dry-run", "preview changes without writing files.", false).option("--diff [path]", "show diff for a file.").option("--view [path]", "show file contents.").option("--src-dir", "use the src directory when creating a new project.", false).option("--no-src-dir", "do not use the src directory when creating a new project.").action(async function action(packages, _opts, cmd) {
|
|
2647
|
+
const root = cmd?.parent;
|
|
2648
|
+
const verbose = Boolean(root?.opts?.().verbose);
|
|
2649
|
+
const forwardedOptions = extractOptionsForShadcn(process.argv.slice(2), cmd);
|
|
2650
|
+
const opts = typeof cmd.optsWithGlobals === "function" ? cmd.optsWithGlobals() : cmd.opts?.() ?? {};
|
|
2651
|
+
const inspectionMode = hasInspectionFlags(forwardedOptions);
|
|
2652
|
+
const overwriteUsed = forwardedOptions.includes("--overwrite") || forwardedOptions.includes("-o");
|
|
2653
|
+
const cwd = opts.cwd || process.cwd();
|
|
2654
|
+
let componentsToAdd = packages || [];
|
|
2655
|
+
const wantsAll = Boolean(opts.all);
|
|
2656
|
+
const isSilent = opts.silent || false;
|
|
2657
|
+
const registryUrl = resolveRegistryUrl();
|
|
2658
|
+
if (wantsAll && componentsToAdd.length === 0) {
|
|
2659
|
+
const fetchingSpinner = spinner("Fetching available components.", { silent: isSilent }).start();
|
|
2660
|
+
try {
|
|
2661
|
+
componentsToAdd = (await fetchAvailableComponents(registryUrl)).filter((component) => !component.type || component.type === "registry:ui").map((component) => component.name);
|
|
2662
|
+
fetchingSpinner.succeed();
|
|
2663
|
+
} catch {
|
|
2664
|
+
fetchingSpinner.fail();
|
|
2665
|
+
logger.error("Failed to fetch available components from registry.");
|
|
2666
|
+
process.exit(1);
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
if (componentsToAdd.length === 0) {
|
|
2670
|
+
const selected = await promptForComponents(registryUrl);
|
|
2671
|
+
if (!selected || selected.length === 0) return;
|
|
2672
|
+
componentsToAdd = selected;
|
|
2673
|
+
}
|
|
2674
|
+
const config = await getConfig(cwd);
|
|
2675
|
+
let uiDir = config?.resolvedPaths?.ui || "";
|
|
2676
|
+
let uiConfig = config;
|
|
2677
|
+
if (config) {
|
|
2678
|
+
const workspaceConfig = await getWorkspaceConfig(config);
|
|
2679
|
+
if (workspaceConfig?.ui) {
|
|
2680
|
+
uiConfig = workspaceConfig.ui;
|
|
2681
|
+
uiDir = uiConfig.resolvedPaths?.ui || uiDir;
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
if (verbose) {
|
|
2685
|
+
logger.info(`[bejamas-ui] cwd: ${cwd}`);
|
|
2686
|
+
logger.info(`[bejamas-ui] uiDir: ${uiDir}`);
|
|
2687
|
+
logger.info(`[bejamas-ui] aliases.ui: ${uiConfig?.aliases?.ui || "not set"}`);
|
|
2688
|
+
}
|
|
2689
|
+
const activeStyle = uiConfig?.style || config?.style || "bejamas-juno";
|
|
2690
|
+
const totalComponents = componentsToAdd.length;
|
|
2691
|
+
for (let index = 0; index < componentsToAdd.length; index += 1) {
|
|
2692
|
+
const component = componentsToAdd[index];
|
|
2693
|
+
if (totalComponents > 1 && !isSilent) {
|
|
2694
|
+
logger.break();
|
|
2695
|
+
logger.info(highlighter.info(`[${index + 1}/${totalComponents}]`) + ` Adding ${highlighter.success(component)}...`);
|
|
2696
|
+
}
|
|
2697
|
+
const subfolderMapResult = inspectionMode ? {
|
|
2698
|
+
uniqueMap: /* @__PURE__ */ new Map(),
|
|
2699
|
+
sharedFilenames: /* @__PURE__ */ new Set(),
|
|
2700
|
+
requiresReorganization: false
|
|
2701
|
+
} : await buildSubfolderMap([component], uiDir, registryUrl, activeStyle);
|
|
2702
|
+
const parsed = await addComponents(cwd, [component], forwardedOptions, verbose, isSilent, inspectionMode);
|
|
2703
|
+
if (!inspectionMode) {
|
|
2704
|
+
await syncManagedTailwindCss(cwd);
|
|
2705
|
+
const registryItem = await fetchRegistryItem(component, registryUrl, activeStyle);
|
|
2706
|
+
if (registryItem?.type === "registry:font") {
|
|
2707
|
+
const nextFont = toManagedAstroFont(registryItem.name);
|
|
2708
|
+
if (nextFont) {
|
|
2709
|
+
const currentFonts = await readManagedAstroFontsFromProject(cwd);
|
|
2710
|
+
const nextFonts = mergeManagedAstroFonts(currentFonts, nextFont);
|
|
2711
|
+
await syncAstroFontsInProject(cwd, nextFonts, nextFont.cssVariable);
|
|
2712
|
+
await syncAstroManagedFontCss(cwd, nextFont.cssVariable);
|
|
2713
|
+
await cleanupAstroFontPackages(cwd);
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
let skippedCount = 0;
|
|
2718
|
+
if (!inspectionMode && uiDir && subfolderMapResult.requiresReorganization) skippedCount = (await reorganizeComponents([component], uiDir, registryUrl, verbose, activeStyle, overwriteUsed)).skippedFiles.length;
|
|
2719
|
+
if (!isSilent && !inspectionMode) {
|
|
2720
|
+
const actuallyCreated = Math.max(0, parsed.created.length - skippedCount);
|
|
2721
|
+
if (actuallyCreated > 0) {
|
|
2722
|
+
const createdPaths = rewritePaths(parsed.created.slice(0, actuallyCreated), subfolderMapResult);
|
|
2723
|
+
logger.success(`Created ${createdPaths.length} file${createdPaths.length > 1 ? "s" : ""}:`);
|
|
2724
|
+
for (const file of createdPaths) logger.log(` ${highlighter.info("-")} ${file}`);
|
|
2725
|
+
}
|
|
2726
|
+
if (parsed.updated.length > 0) {
|
|
2727
|
+
const updatedPaths = rewritePaths(Array.from(new Set(parsed.updated)), subfolderMapResult);
|
|
2728
|
+
logger.info(`Updated ${updatedPaths.length} file${updatedPaths.length > 1 ? "s" : ""}:`);
|
|
2729
|
+
for (const file of updatedPaths) logger.log(` ${highlighter.info("-")} ${file}`);
|
|
2730
|
+
}
|
|
2731
|
+
if (skippedCount > 0) logger.info(`Skipped ${skippedCount} file${skippedCount > 1 ? "s" : ""}: (already exists)`);
|
|
2732
|
+
if (parsed.skipped.length > 0) {
|
|
2733
|
+
const skippedPaths = rewritePaths(parsed.skipped, subfolderMapResult);
|
|
2734
|
+
logger.info(formatSkippedFilesHeading(skippedPaths.length, overwriteUsed));
|
|
2735
|
+
for (const file of skippedPaths) logger.log(` ${highlighter.info("-")} ${file}`);
|
|
2736
|
+
}
|
|
2737
|
+
if (actuallyCreated === 0 && parsed.updated.length === 0 && skippedCount === 0 && parsed.skipped.length === 0) logger.info("Already up to date.");
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
if (!inspectionMode) {
|
|
2741
|
+
await fixAstroImports(cwd, verbose, uiConfig);
|
|
2742
|
+
if (uiConfig) {
|
|
2743
|
+
await repairUiPackageExports(uiDir, uiConfig.resolvedPaths.cwd);
|
|
2744
|
+
const items = await fetchRegistryTree(componentsToAdd, registryUrl, activeStyle);
|
|
2745
|
+
await ensureRegistryDependencies(uiConfig.resolvedPaths.cwd, items);
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
});
|
|
2749
|
+
|
|
2750
|
+
//#endregion
|
|
2751
|
+
//#region src/utils/file-helper.ts
|
|
2752
|
+
const FILE_BACKUP_SUFFIX = ".bak";
|
|
2753
|
+
var FileBackupError = class extends Error {
|
|
2754
|
+
constructor(filePath) {
|
|
2755
|
+
super(`Could not back up ${filePath}.`);
|
|
2756
|
+
this.name = "FileBackupError";
|
|
2757
|
+
this.filePath = filePath;
|
|
2758
|
+
}
|
|
2759
|
+
};
|
|
2760
|
+
function createFileBackup(filePath) {
|
|
2761
|
+
if (!fs.existsSync(filePath)) return null;
|
|
2762
|
+
const backupPath = `${filePath}${FILE_BACKUP_SUFFIX}`;
|
|
2763
|
+
if (fs.existsSync(backupPath)) return null;
|
|
2764
|
+
try {
|
|
2765
|
+
fs.copyFileSync(filePath, backupPath);
|
|
2766
|
+
return backupPath;
|
|
2767
|
+
} catch {
|
|
2768
|
+
return null;
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
function restoreFileBackup(filePath) {
|
|
2772
|
+
const backupPath = `${filePath}${FILE_BACKUP_SUFFIX}`;
|
|
2773
|
+
if (!fs.existsSync(backupPath)) return false;
|
|
2774
|
+
try {
|
|
2775
|
+
fs.copyFileSync(backupPath, filePath);
|
|
2776
|
+
fs.unlinkSync(backupPath);
|
|
2777
|
+
return true;
|
|
2778
|
+
} catch (error) {
|
|
2779
|
+
console.error(`Warning: Could not restore backup file ${backupPath}: ${error}`);
|
|
2780
|
+
return false;
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
function deleteFileBackup(filePath) {
|
|
2784
|
+
const backupPath = `${filePath}${FILE_BACKUP_SUFFIX}`;
|
|
2785
|
+
if (!fs.existsSync(backupPath)) return false;
|
|
2786
|
+
try {
|
|
2787
|
+
fs.unlinkSync(backupPath);
|
|
2788
|
+
return true;
|
|
2789
|
+
} catch {
|
|
2790
|
+
return false;
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2794
|
+
//#endregion
|
|
2795
|
+
//#region src/utils/get-monorepo-info.ts
|
|
2796
|
+
const FRAMEWORK_CONFIG_FILES = [
|
|
2797
|
+
"next.config.*",
|
|
2798
|
+
"vite.config.*",
|
|
2799
|
+
"astro.config.*",
|
|
2800
|
+
"remix.config.*",
|
|
2801
|
+
"nuxt.config.*",
|
|
2802
|
+
"svelte.config.*",
|
|
2803
|
+
"gatsby-config.*",
|
|
2804
|
+
"angular.json"
|
|
2805
|
+
];
|
|
2806
|
+
async function isMonorepoRoot(cwd) {
|
|
2807
|
+
if (fs.existsSync(path.resolve(cwd, "pnpm-workspace.yaml"))) return true;
|
|
2808
|
+
const packageJsonPath = path.resolve(cwd, "package.json");
|
|
2809
|
+
if (fs.existsSync(packageJsonPath)) try {
|
|
2810
|
+
if ((await fs.readJson(packageJsonPath)).workspaces) return true;
|
|
2811
|
+
} catch {
|
|
2812
|
+
return false;
|
|
2813
|
+
}
|
|
2814
|
+
if (fs.existsSync(path.resolve(cwd, "lerna.json"))) return true;
|
|
2815
|
+
if (fs.existsSync(path.resolve(cwd, "nx.json"))) return true;
|
|
2816
|
+
return false;
|
|
2817
|
+
}
|
|
2818
|
+
async function getMonorepoTargets(cwd) {
|
|
2819
|
+
const patterns = await getWorkspacePatterns(cwd);
|
|
2820
|
+
if (!patterns.length) return [];
|
|
2821
|
+
const dirs = await fg(patterns, {
|
|
2822
|
+
cwd,
|
|
2823
|
+
onlyDirectories: true,
|
|
2824
|
+
ignore: ["**/node_modules/**"]
|
|
2825
|
+
});
|
|
2826
|
+
const targets = [];
|
|
2827
|
+
for (const dir of dirs) {
|
|
2828
|
+
const fullPath = path.resolve(cwd, dir);
|
|
2829
|
+
if (!fs.existsSync(path.resolve(fullPath, "package.json"))) continue;
|
|
2830
|
+
const hasComponentsJson = fs.existsSync(path.resolve(fullPath, "components.json"));
|
|
2831
|
+
const hasFrameworkConfig = FRAMEWORK_CONFIG_FILES.some((pattern) => fg.sync(pattern, {
|
|
2832
|
+
cwd: fullPath,
|
|
2833
|
+
dot: true
|
|
2834
|
+
}).length > 0);
|
|
2835
|
+
if (hasComponentsJson || hasFrameworkConfig) targets.push({
|
|
2836
|
+
name: dir,
|
|
2837
|
+
hasConfig: hasComponentsJson
|
|
2838
|
+
});
|
|
2839
|
+
}
|
|
2840
|
+
return targets;
|
|
2841
|
+
}
|
|
2842
|
+
function formatMonorepoMessage(command, targets, options = {}) {
|
|
2843
|
+
const binary = options.binary ?? "bejamas";
|
|
2844
|
+
const cwdFlag = options.cwdFlag ?? "-c";
|
|
2845
|
+
logger.break();
|
|
2846
|
+
logger.log(`It looks like you are running ${highlighter.info(command)} from a monorepo root.`);
|
|
2847
|
+
logger.log(`To use ${binary} in a specific workspace, use the ${highlighter.info(cwdFlag)} flag:`);
|
|
2848
|
+
logger.break();
|
|
2849
|
+
for (const target of targets) logger.log(` ${binary} ${command} ${cwdFlag} ${target.name}`);
|
|
2850
|
+
logger.break();
|
|
2851
|
+
}
|
|
2852
|
+
async function getWorkspacePatterns(cwd) {
|
|
2853
|
+
const patterns = [];
|
|
2854
|
+
const pnpmWorkspacePath = path.resolve(cwd, "pnpm-workspace.yaml");
|
|
2855
|
+
if (fs.existsSync(pnpmWorkspacePath)) {
|
|
2856
|
+
const content = await fs.readFile(pnpmWorkspacePath, "utf8");
|
|
2857
|
+
const matches = Array.from(content.matchAll(/^\s*-\s*["']?([^"'\n#]+)["']?\s*$/gm));
|
|
2858
|
+
for (const match of matches) patterns.push(match[1].trim());
|
|
2859
|
+
}
|
|
2860
|
+
const packageJsonPath = path.resolve(cwd, "package.json");
|
|
2861
|
+
if (fs.existsSync(packageJsonPath)) try {
|
|
2862
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
2863
|
+
const workspaces = Array.isArray(packageJson.workspaces) ? packageJson.workspaces : packageJson.workspaces?.packages;
|
|
2864
|
+
if (Array.isArray(workspaces)) patterns.push(...workspaces.filter((pattern) => !pattern.startsWith("!")));
|
|
2865
|
+
} catch {
|
|
2866
|
+
return Array.from(new Set(patterns));
|
|
2867
|
+
}
|
|
2868
|
+
return Array.from(new Set(patterns));
|
|
2869
|
+
}
|
|
2870
|
+
|
|
2871
|
+
//#endregion
|
|
2872
|
+
//#region src/commands/apply.ts
|
|
2873
|
+
const applyOptionsSchema = z.object({
|
|
2874
|
+
cwd: z.string(),
|
|
2875
|
+
positionalPreset: z.string().optional(),
|
|
2876
|
+
preset: z.string().optional(),
|
|
2877
|
+
only: z.union([z.boolean(), z.string()]).optional(),
|
|
2878
|
+
yes: z.boolean(),
|
|
2879
|
+
silent: z.boolean()
|
|
2880
|
+
});
|
|
2881
|
+
const APPLY_ONLY_VALUES = ["theme", "font"];
|
|
2882
|
+
var ApplyOnlyError = class extends Error {
|
|
2883
|
+
constructor(message) {
|
|
2884
|
+
super(message);
|
|
2885
|
+
this.name = "ApplyOnlyError";
|
|
2886
|
+
}
|
|
2887
|
+
};
|
|
2888
|
+
var ApplyPresetError = class extends Error {
|
|
2889
|
+
constructor(message) {
|
|
2890
|
+
super(message);
|
|
2891
|
+
this.name = "ApplyPresetError";
|
|
2892
|
+
}
|
|
2893
|
+
};
|
|
2894
|
+
var ApplyWorkspaceSyncError = class extends Error {
|
|
2895
|
+
constructor(message) {
|
|
2896
|
+
super(message);
|
|
2897
|
+
this.name = "ApplyWorkspaceSyncError";
|
|
2898
|
+
}
|
|
2899
|
+
};
|
|
2900
|
+
const apply = new Command().name("apply").description("apply a preset to an existing project").argument("[preset]", "the preset to apply").option("--preset <preset>", "preset configuration to apply").option("--only [parts]", "apply only parts of a preset: theme, font").option("-y, --yes", "skip confirmation prompt.", false).option("-c, --cwd <cwd>", "the working directory. defaults to the current directory.", process.cwd()).option("-s, --silent", "mute output.", false).action(async (positionalPreset, opts) => {
|
|
2901
|
+
try {
|
|
2902
|
+
const options = applyOptionsSchema.parse({
|
|
2903
|
+
...opts,
|
|
2904
|
+
cwd: path.resolve(opts.cwd),
|
|
2905
|
+
positionalPreset
|
|
2906
|
+
});
|
|
2907
|
+
const rawPreset = resolveApplyPreset(options);
|
|
2908
|
+
const explicitOnly = resolveApplyOnly(options.only);
|
|
2909
|
+
validateApplyOnlyPreset({
|
|
2910
|
+
preset: rawPreset,
|
|
2911
|
+
only: explicitOnly
|
|
2912
|
+
});
|
|
2913
|
+
const preflight = await preFlightApply(options);
|
|
2914
|
+
if (preflight.errors["1"]) {
|
|
2915
|
+
logger.break();
|
|
2916
|
+
logger.error(`The ${highlighter.info("apply")} command only works in an existing project.`);
|
|
2917
|
+
logger.error(`Run ${highlighter.info(getInitCommand(rawPreset))} first.`);
|
|
2918
|
+
logger.break();
|
|
2919
|
+
process.exit(1);
|
|
2920
|
+
}
|
|
2921
|
+
if (preflight.errors["3"]) {
|
|
2922
|
+
logger.break();
|
|
2923
|
+
logger.error(`No ${highlighter.info("components.json")} found at ${highlighter.info(options.cwd)}.`);
|
|
2924
|
+
logger.error(`Run ${highlighter.info(getInitCommand(rawPreset))} first.`);
|
|
2925
|
+
logger.break();
|
|
2926
|
+
process.exit(1);
|
|
2927
|
+
}
|
|
2928
|
+
const existingConfig = preflight.config;
|
|
2929
|
+
if (!existingConfig) process.exit(1);
|
|
2930
|
+
if (!rawPreset) {
|
|
2931
|
+
await printPresetBuilderGuidance(options);
|
|
2932
|
+
process.exit(0);
|
|
2933
|
+
}
|
|
2934
|
+
const resolvedPreset = resolveApplyPresetSource(rawPreset);
|
|
2935
|
+
const only = explicitOnly ?? resolveApplyOnly(resolvedPreset.only ?? getPresetUrlOnly(rawPreset));
|
|
2936
|
+
const designConfig = resolveApplyDesignSystemConfig(resolvedPreset, existingConfig);
|
|
2937
|
+
await confirmApply({
|
|
2938
|
+
cwd: options.cwd,
|
|
2939
|
+
only,
|
|
2940
|
+
silent: options.silent,
|
|
2941
|
+
yes: options.yes
|
|
2942
|
+
});
|
|
2943
|
+
await withFileBackups(await getApplyConfigPaths(existingConfig), async () => {
|
|
2944
|
+
if (only) await runPartialApply(options.cwd, designConfig, resolvedPreset, only);
|
|
2945
|
+
else await runExistingProjectInit({
|
|
2946
|
+
cwd: options.cwd,
|
|
2947
|
+
components: void 0,
|
|
2948
|
+
yes: true,
|
|
2949
|
+
reinstall: true,
|
|
2950
|
+
defaults: false,
|
|
2951
|
+
force: false,
|
|
2952
|
+
silent: options.silent,
|
|
2953
|
+
isNewProject: false,
|
|
2954
|
+
srcDir: false,
|
|
2955
|
+
cssVariables: true,
|
|
2956
|
+
template: "astro",
|
|
2957
|
+
preset: resolvedPreset.preset,
|
|
2958
|
+
baseColor: void 0,
|
|
2959
|
+
baseStyle: true,
|
|
2960
|
+
rtl: existingConfig.rtl ?? false,
|
|
2961
|
+
lang: void 0,
|
|
2962
|
+
themeRef: resolvedPreset.themeRef,
|
|
2963
|
+
forwardedOptions: ["--yes", "--force"]
|
|
2964
|
+
}, designConfig);
|
|
2965
|
+
const config = await getConfig(options.cwd);
|
|
2966
|
+
if (!config) throw new Error("Could not read components.json after applying preset.");
|
|
2967
|
+
await syncApplyWorkspaceConfigs(config, { only });
|
|
2968
|
+
});
|
|
2969
|
+
if (!options.silent) {
|
|
2970
|
+
logger.break();
|
|
2971
|
+
logger.log("Preset applied successfully.");
|
|
2972
|
+
logger.break();
|
|
2973
|
+
}
|
|
2974
|
+
} catch (error) {
|
|
2975
|
+
if (error instanceof ApplyOnlyError || error instanceof ApplyPresetError) {
|
|
2976
|
+
for (const line of error.message.split("\n")) logger.error(line);
|
|
2977
|
+
logger.break();
|
|
2978
|
+
process.exit(1);
|
|
2979
|
+
}
|
|
2980
|
+
if (error instanceof FileBackupError) {
|
|
2981
|
+
logger.error(`Could not back up ${highlighter.info("components.json")}. Aborting.`);
|
|
2982
|
+
logger.break();
|
|
2983
|
+
process.exit(1);
|
|
2984
|
+
}
|
|
2985
|
+
if (error instanceof ApplyWorkspaceSyncError) {
|
|
2986
|
+
logger.error(error.message);
|
|
2987
|
+
logger.break();
|
|
2988
|
+
process.exit(1);
|
|
2989
|
+
}
|
|
2990
|
+
logger.break();
|
|
2991
|
+
handleError(error);
|
|
2992
|
+
} finally {
|
|
2993
|
+
clearRegistryContext();
|
|
2994
|
+
}
|
|
2995
|
+
});
|
|
2996
|
+
function resolveApplyPreset(options) {
|
|
2997
|
+
const positionalPreset = options.positionalPreset?.trim();
|
|
2998
|
+
const flagPreset = options.preset?.trim();
|
|
2999
|
+
if (positionalPreset && flagPreset && positionalPreset !== flagPreset) throw new ApplyPresetError(`Received two different preset values. Use either the positional preset or ${highlighter.info("--preset")}, or pass the same value to both.`);
|
|
3000
|
+
return flagPreset ?? positionalPreset;
|
|
3001
|
+
}
|
|
3002
|
+
function getPresetUrlOnly(preset) {
|
|
3003
|
+
if (!isUrl(preset)) return;
|
|
3004
|
+
return new URL(preset).searchParams.get("only") ?? void 0;
|
|
3005
|
+
}
|
|
3006
|
+
function resolveApplyOnly(value) {
|
|
3007
|
+
if (value === void 0 || value === false) return;
|
|
3008
|
+
if (value === true) throw new ApplyOnlyError([
|
|
3009
|
+
"Missing value for --only.",
|
|
3010
|
+
`Use one or more of: ${APPLY_ONLY_VALUES.join(", ")}.`,
|
|
3011
|
+
"Example: bejamas apply <preset> --only theme,font."
|
|
3012
|
+
].join("\n"));
|
|
3013
|
+
return parseApplyOnlyParts(value);
|
|
3014
|
+
}
|
|
3015
|
+
function parseApplyOnlyParts(value) {
|
|
3016
|
+
const aliases = {
|
|
3017
|
+
theme: "theme",
|
|
3018
|
+
font: "font",
|
|
3019
|
+
fonts: "font"
|
|
3020
|
+
};
|
|
3021
|
+
const parts = value.split(",").map((part) => part.trim().toLowerCase()).filter(Boolean);
|
|
3022
|
+
const invalid = parts.filter((part) => !aliases[part]);
|
|
3023
|
+
if (!parts.length || invalid.length) throw new ApplyOnlyError([
|
|
3024
|
+
`Invalid value for --only: ${value}.`,
|
|
3025
|
+
`Use one or more of: ${APPLY_ONLY_VALUES.join(", ")}.`,
|
|
3026
|
+
"Example: bejamas apply <preset> --only theme,font."
|
|
3027
|
+
].join("\n"));
|
|
3028
|
+
return Array.from(new Set(parts.map((part) => aliases[part])));
|
|
3029
|
+
}
|
|
3030
|
+
function validateApplyOnlyPreset(options) {
|
|
3031
|
+
if (!options.only || options.preset) return;
|
|
3032
|
+
throw new ApplyOnlyError(["Missing preset for --only.", "Use: bejamas apply <preset> --only theme,font."].join("\n"));
|
|
3033
|
+
}
|
|
3034
|
+
function resolveApplyPresetSource(preset) {
|
|
3035
|
+
if (isPresetCode(preset)) {
|
|
3036
|
+
validatePresetCode(preset);
|
|
3037
|
+
return { preset };
|
|
3038
|
+
}
|
|
3039
|
+
if (!isUrl(preset)) throw new ApplyPresetError(`Invalid preset: ${highlighter.info(preset)}. Use a preset code or a Bejamas create/init URL containing a preset code.`);
|
|
3040
|
+
const url = new URL(preset);
|
|
3041
|
+
if (!isSupportedPresetUrl(url)) throw new ApplyPresetError(`Invalid preset URL: ${highlighter.info(preset)}. Use a Bejamas create or init URL containing a preset code.`);
|
|
3042
|
+
const code = url.searchParams.get("preset")?.trim();
|
|
3043
|
+
if (!code) throw new ApplyPresetError(`Invalid preset URL: ${highlighter.info(preset)}. Expected a URL containing a preset query parameter.`);
|
|
3044
|
+
validatePresetCode(code);
|
|
3045
|
+
return {
|
|
3046
|
+
preset: code,
|
|
3047
|
+
themeRef: url.searchParams.get("themeRef") ?? void 0,
|
|
3048
|
+
only: url.searchParams.get("only") ?? void 0
|
|
3049
|
+
};
|
|
3050
|
+
}
|
|
3051
|
+
async function preFlightApply(options) {
|
|
3052
|
+
const errors = {};
|
|
3053
|
+
if (!fs.existsSync(options.cwd) || !fs.existsSync(path.resolve(options.cwd, "package.json"))) {
|
|
3054
|
+
errors["1"] = true;
|
|
3055
|
+
return {
|
|
3056
|
+
errors,
|
|
3057
|
+
config: null
|
|
3058
|
+
};
|
|
3059
|
+
}
|
|
3060
|
+
if (!fs.existsSync(path.resolve(options.cwd, "components.json"))) {
|
|
3061
|
+
if (await isMonorepoRoot(options.cwd)) {
|
|
3062
|
+
const targets = await getMonorepoTargets(options.cwd);
|
|
3063
|
+
const applyTargets = [];
|
|
3064
|
+
for (const target of targets) {
|
|
3065
|
+
const projectInfo = await getProjectInfo(path.resolve(options.cwd, target.name));
|
|
3066
|
+
if (target.hasConfig || projectInfo?.isAstro) applyTargets.push(target);
|
|
3067
|
+
}
|
|
3068
|
+
if (applyTargets.length > 0) {
|
|
3069
|
+
formatMonorepoMessage("apply --preset <preset>", applyTargets, {
|
|
3070
|
+
binary: "bejamas",
|
|
3071
|
+
cwdFlag: "-c"
|
|
3072
|
+
});
|
|
3073
|
+
process.exit(1);
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
errors["3"] = true;
|
|
3077
|
+
return {
|
|
3078
|
+
errors,
|
|
3079
|
+
config: null
|
|
3080
|
+
};
|
|
3081
|
+
}
|
|
3082
|
+
try {
|
|
3083
|
+
return {
|
|
3084
|
+
errors,
|
|
3085
|
+
config: await getConfig(options.cwd)
|
|
3086
|
+
};
|
|
3087
|
+
} catch {
|
|
3088
|
+
logger.break();
|
|
3089
|
+
logger.error(`An invalid ${highlighter.info("components.json")} file was found at ${highlighter.info(options.cwd)}.\nBefore you can apply a preset, you must create a valid ${highlighter.info("components.json")} file by running the ${highlighter.info("init")} command.`);
|
|
3090
|
+
logger.break();
|
|
3091
|
+
process.exit(1);
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
async function printPresetBuilderGuidance(options) {
|
|
3095
|
+
if (options.silent) return;
|
|
3096
|
+
const createUrl = buildUiUrl("/create");
|
|
3097
|
+
logger.break();
|
|
3098
|
+
logger.log(`Build your custom preset on ${highlighter.info(createUrl)}.`);
|
|
3099
|
+
logger.log(`Then run ${highlighter.info("bejamas apply --preset <preset>")} with the preset code or preset URL from ui.bejamas.com.`);
|
|
3100
|
+
logger.break();
|
|
3101
|
+
}
|
|
3102
|
+
async function confirmApply(options) {
|
|
3103
|
+
if (options.yes) return;
|
|
3104
|
+
logger.break();
|
|
3105
|
+
if (!options.only) logger.warn(highlighter.warn("Applying a new preset will overwrite existing UI components, fonts, and CSS variables."));
|
|
3106
|
+
else logger.warn(highlighter.warn("Applying the selected preset parts will update your project configuration and styles."));
|
|
3107
|
+
logger.warn("Commit or stash your changes before continuing so you can easily go back.");
|
|
3108
|
+
if (!options.only) {
|
|
3109
|
+
const reinstallComponents = await getInstalledUiComponents(options.cwd);
|
|
3110
|
+
logger.break();
|
|
3111
|
+
logger.log(" The following components will be re-installed:");
|
|
3112
|
+
if (reinstallComponents.length) for (let index = 0; index < reinstallComponents.length; index += 8) logger.log(` - ${reinstallComponents.slice(index, index + 8).join(", ")}`);
|
|
3113
|
+
else logger.log(" - No installed UI components were detected.");
|
|
3114
|
+
}
|
|
3115
|
+
logger.break();
|
|
3116
|
+
const { proceed } = await prompts({
|
|
3117
|
+
type: "confirm",
|
|
3118
|
+
name: "proceed",
|
|
3119
|
+
message: "Would you like to continue?",
|
|
3120
|
+
initial: false
|
|
3121
|
+
});
|
|
3122
|
+
if (!proceed) {
|
|
3123
|
+
logger.break();
|
|
3124
|
+
process.exit(1);
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3127
|
+
async function runPartialApply(cwd, designConfig, preset, only) {
|
|
3128
|
+
if (only.includes("theme")) {
|
|
3129
|
+
const initUrl = buildInitUrl(designConfig, preset.themeRef);
|
|
3130
|
+
const themeVars = preset.themeRef ? await fetchInitThemeVars(initUrl) ?? void 0 : void 0;
|
|
3131
|
+
await applyDesignSystemThemeToProject(cwd, designConfig, { themeVars });
|
|
3132
|
+
await fixAstroImports(cwd, false);
|
|
3133
|
+
}
|
|
3134
|
+
if (only.includes("font")) await applyDesignSystemFontToProject(cwd, designConfig);
|
|
3135
|
+
}
|
|
3136
|
+
function resolveApplyDesignSystemConfig(preset, existingConfig) {
|
|
3137
|
+
return resolveDesignSystemConfig({
|
|
3138
|
+
preset: preset.preset,
|
|
3139
|
+
template: "astro",
|
|
3140
|
+
rtl: existingConfig.rtl ?? false,
|
|
3141
|
+
baseColor: void 0,
|
|
3142
|
+
lang: void 0
|
|
3143
|
+
});
|
|
3144
|
+
}
|
|
3145
|
+
function validatePresetCode(preset) {
|
|
3146
|
+
if (!isPresetCode(preset) || !decodePreset(preset)) throw new ApplyPresetError(`Invalid preset code: ${highlighter.info(preset)}.`);
|
|
3147
|
+
}
|
|
3148
|
+
async function getApplyConfigPaths(config) {
|
|
3149
|
+
const configPaths = /* @__PURE__ */ new Set([path.resolve(config.resolvedPaths.cwd, "components.json")]);
|
|
3150
|
+
const workspaceConfig = await getWorkspaceConfig(config);
|
|
3151
|
+
if (workspaceConfig) for (const linkedConfig of Object.values(workspaceConfig)) configPaths.add(path.resolve(linkedConfig.resolvedPaths.cwd, "components.json"));
|
|
3152
|
+
const existingPaths = [];
|
|
3153
|
+
for (const configPath of configPaths) if (await fs.pathExists(configPath)) existingPaths.push(configPath);
|
|
3154
|
+
return existingPaths.sort();
|
|
3155
|
+
}
|
|
3156
|
+
async function withFileBackups(filePaths, task) {
|
|
3157
|
+
const backedUpPaths = [];
|
|
3158
|
+
const restoreBackupOnExit = () => {
|
|
3159
|
+
for (const filePath of [...backedUpPaths].reverse()) restoreFileBackup(filePath);
|
|
3160
|
+
};
|
|
3161
|
+
try {
|
|
3162
|
+
for (const filePath of filePaths) {
|
|
3163
|
+
if (!createFileBackup(filePath)) throw new FileBackupError(filePath);
|
|
3164
|
+
backedUpPaths.push(filePath);
|
|
3165
|
+
}
|
|
3166
|
+
process.on("exit", restoreBackupOnExit);
|
|
3167
|
+
const result = await task();
|
|
3168
|
+
process.removeListener("exit", restoreBackupOnExit);
|
|
3169
|
+
for (const filePath of backedUpPaths) deleteFileBackup(filePath);
|
|
3170
|
+
return result;
|
|
3171
|
+
} catch (error) {
|
|
3172
|
+
process.removeListener("exit", restoreBackupOnExit);
|
|
3173
|
+
for (const filePath of [...backedUpPaths].reverse()) restoreFileBackup(filePath);
|
|
3174
|
+
throw error;
|
|
3175
|
+
}
|
|
3176
|
+
}
|
|
3177
|
+
async function syncApplyWorkspaceConfigs(config, options) {
|
|
3178
|
+
if (options.only && !options.only.includes("theme")) return;
|
|
3179
|
+
const linkedConfigs = await getApplyWorkspaceConfigs(config);
|
|
3180
|
+
if (!linkedConfigs.length) return;
|
|
3181
|
+
const patch = {
|
|
3182
|
+
style: config.style,
|
|
3183
|
+
tailwind: {
|
|
3184
|
+
baseColor: config.tailwind.baseColor,
|
|
3185
|
+
cssVariables: config.tailwind.cssVariables
|
|
3186
|
+
},
|
|
3187
|
+
...config.iconLibrary ? { iconLibrary: config.iconLibrary } : {},
|
|
3188
|
+
...config.rtl !== void 0 ? { rtl: config.rtl } : {},
|
|
3189
|
+
...config.menuColor ? { menuColor: config.menuColor } : {},
|
|
3190
|
+
...config.menuAccent ? { menuAccent: config.menuAccent } : {}
|
|
3191
|
+
};
|
|
3192
|
+
try {
|
|
3193
|
+
for (const linkedConfig of linkedConfigs) {
|
|
3194
|
+
const configPath = path.resolve(linkedConfig.resolvedPaths.cwd, "components.json");
|
|
3195
|
+
if (!await fs.pathExists(configPath)) continue;
|
|
3196
|
+
const existingConfig = await fs.readJson(configPath);
|
|
3197
|
+
await fs.writeJson(configPath, {
|
|
3198
|
+
...existingConfig,
|
|
3199
|
+
...patch,
|
|
3200
|
+
tailwind: {
|
|
3201
|
+
...existingConfig.tailwind,
|
|
3202
|
+
...patch.tailwind
|
|
3203
|
+
}
|
|
3204
|
+
}, { spaces: 2 });
|
|
3205
|
+
}
|
|
3206
|
+
} catch (error) {
|
|
3207
|
+
throw new ApplyWorkspaceSyncError(`Failed to sync linked workspace configs.${error instanceof Error ? ` ${error.message}` : ""}`);
|
|
3208
|
+
}
|
|
3209
|
+
}
|
|
3210
|
+
async function getApplyWorkspaceConfigs(config) {
|
|
3211
|
+
const workspaceConfig = await getWorkspaceConfig(config);
|
|
3212
|
+
if (!workspaceConfig) return [];
|
|
3213
|
+
const linkedConfigs = /* @__PURE__ */ new Map();
|
|
3214
|
+
for (const linkedConfig of Object.values(workspaceConfig)) {
|
|
3215
|
+
if (linkedConfig.resolvedPaths.cwd === config.resolvedPaths.cwd) continue;
|
|
3216
|
+
linkedConfigs.set(linkedConfig.resolvedPaths.cwd, linkedConfig);
|
|
3217
|
+
}
|
|
3218
|
+
return Array.from(linkedConfigs.values()).sort((a, b) => a.resolvedPaths.cwd.localeCompare(b.resolvedPaths.cwd));
|
|
3219
|
+
}
|
|
3220
|
+
function quoteShellArg(value) {
|
|
3221
|
+
return /[^A-Za-z0-9_./:-]/.test(value) ? JSON.stringify(value) : value;
|
|
3222
|
+
}
|
|
3223
|
+
function getInitCommand(preset) {
|
|
3224
|
+
if (!preset) return "bejamas init";
|
|
3225
|
+
return `bejamas init --preset ${quoteShellArg(preset)}`;
|
|
3226
|
+
}
|
|
3227
|
+
function isUrl(value) {
|
|
3228
|
+
try {
|
|
3229
|
+
new URL(value);
|
|
3230
|
+
return true;
|
|
3231
|
+
} catch {
|
|
3232
|
+
return false;
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
function isSupportedPresetUrl(url) {
|
|
3236
|
+
const pathname = url.pathname.replace(/\/+$/, "");
|
|
3237
|
+
return pathname === "/create" || pathname === "/init";
|
|
3238
|
+
}
|
|
3239
|
+
|
|
3240
|
+
//#endregion
|
|
3241
|
+
//#region src/commands/info.ts
|
|
3242
|
+
const info = new Command().name("info").description("proxy to shadcn info").option("-c, --cwd <cwd>", "the working directory. defaults to the current directory.", process.cwd()).option("--json", "output as JSON.", false).action(async (opts) => {
|
|
3243
|
+
const cwd = path.resolve(opts.cwd ?? process.cwd());
|
|
3244
|
+
const rawArgv = process.argv.slice(2);
|
|
3245
|
+
const args = [
|
|
3246
|
+
"info",
|
|
3247
|
+
"--cwd",
|
|
3248
|
+
cwd
|
|
3249
|
+
];
|
|
3250
|
+
if (opts.json) args.push("--json");
|
|
3251
|
+
const passthroughArgs = extractPassthroughArgs(rawArgv, "info");
|
|
3252
|
+
if (passthroughArgs.length > 0) args.push(...passthroughArgs);
|
|
3253
|
+
try {
|
|
3254
|
+
await runShadcnCommand({
|
|
3255
|
+
cwd,
|
|
3256
|
+
args
|
|
3257
|
+
});
|
|
3258
|
+
} catch {
|
|
3259
|
+
process.exit(1);
|
|
3260
|
+
}
|
|
3261
|
+
});
|
|
3262
|
+
|
|
3263
|
+
//#endregion
|
|
3264
|
+
//#region src/utils/preset-resolve.ts
|
|
3265
|
+
const PRESET_BASE_COLOR_SET = new Set(PRESET_BASE_COLORS);
|
|
3266
|
+
const PRESET_FONT_SET = new Set(PRESET_FONTS);
|
|
3267
|
+
const PRESET_FONT_HEADING_SET = new Set(PRESET_FONT_HEADINGS);
|
|
3268
|
+
const PRESET_ICON_LIBRARY_SET = new Set(PRESET_ICON_LIBRARIES);
|
|
3269
|
+
const PRESET_MENU_ACCENT_SET = new Set(PRESET_MENU_ACCENTS);
|
|
3270
|
+
const PRESET_MENU_COLOR_SET = new Set(PRESET_MENU_COLORS);
|
|
3271
|
+
const PRESET_STYLE_SET = new Set(PRESET_STYLES);
|
|
3272
|
+
new Set(PRESET_THEMES);
|
|
3273
|
+
const ROOT_FONT_VARIABLES = [
|
|
3274
|
+
"--font-sans",
|
|
3275
|
+
"--font-serif",
|
|
3276
|
+
"--font-mono"
|
|
3277
|
+
];
|
|
3278
|
+
const ROOT_FONT_VARIABLE_SET = new Set(ROOT_FONT_VARIABLES);
|
|
3279
|
+
const FONT_VARIABLE_SET = /* @__PURE__ */ new Set([...ROOT_FONT_VARIABLES, "--font-heading"]);
|
|
3280
|
+
async function resolveProjectPreset(config) {
|
|
3281
|
+
const style = normalizePresetStyle(config.style);
|
|
3282
|
+
if (!style) return {
|
|
3283
|
+
code: null,
|
|
3284
|
+
fallbacks: [],
|
|
3285
|
+
values: null
|
|
3286
|
+
};
|
|
3287
|
+
const cssState = await readCssState(config.resolvedPaths.tailwindCss);
|
|
3288
|
+
const baseColor = asPresetValue(PRESET_BASE_COLOR_SET, config.tailwind.baseColor) ?? DEFAULT_PRESET_CONFIG.baseColor;
|
|
3289
|
+
const iconLibrary = asPresetValue(PRESET_ICON_LIBRARY_SET, config.iconLibrary) ?? DEFAULT_PRESET_CONFIG.iconLibrary;
|
|
3290
|
+
const menuAccent = asPresetValue(PRESET_MENU_ACCENT_SET, config.menuAccent) ?? DEFAULT_PRESET_CONFIG.menuAccent;
|
|
3291
|
+
const menuColor = asPresetValue(PRESET_MENU_COLOR_SET, config.menuColor) ?? DEFAULT_PRESET_CONFIG.menuColor;
|
|
3292
|
+
const resolvedTheme = matchTheme(cssState, {
|
|
3293
|
+
baseColor,
|
|
3294
|
+
menuAccent,
|
|
3295
|
+
menuColor,
|
|
3296
|
+
style
|
|
3297
|
+
});
|
|
3298
|
+
const theme = resolvedTheme ?? getFallbackTheme(baseColor);
|
|
3299
|
+
const resolvedRadius = matchRadius(cssState.rootVars["--radius"], style);
|
|
3300
|
+
const radius = resolvedRadius ?? DEFAULT_PRESET_CONFIG.radius;
|
|
3301
|
+
const resolvedFont = await resolveBodyFont(config, cssState);
|
|
3302
|
+
const font = resolvedFont ?? DEFAULT_PRESET_CONFIG.font;
|
|
3303
|
+
const resolvedFontHeading = await resolveHeadingFont(config, cssState, font);
|
|
3304
|
+
const values = {
|
|
3305
|
+
style,
|
|
3306
|
+
baseColor,
|
|
3307
|
+
theme,
|
|
3308
|
+
iconLibrary,
|
|
3309
|
+
font,
|
|
3310
|
+
fontHeading: normalizeFontHeading(resolvedFontHeading ?? DEFAULT_PRESET_CONFIG.fontHeading, font),
|
|
3311
|
+
radius,
|
|
3312
|
+
menuAccent,
|
|
3313
|
+
menuColor
|
|
3314
|
+
};
|
|
3315
|
+
const fallbacks = [
|
|
3316
|
+
!asPresetValue(PRESET_BASE_COLOR_SET, config.tailwind.baseColor) && "baseColor",
|
|
3317
|
+
!resolvedTheme && "theme",
|
|
3318
|
+
!asPresetValue(PRESET_ICON_LIBRARY_SET, config.iconLibrary) && "iconLibrary",
|
|
3319
|
+
!resolvedFont && "font",
|
|
3320
|
+
!resolvedFontHeading && "fontHeading",
|
|
3321
|
+
!resolvedRadius && "radius",
|
|
3322
|
+
!asPresetValue(PRESET_MENU_ACCENT_SET, config.menuAccent) && "menuAccent",
|
|
3323
|
+
!asPresetValue(PRESET_MENU_COLOR_SET, config.menuColor) && "menuColor"
|
|
3324
|
+
].filter(Boolean);
|
|
3325
|
+
return {
|
|
3326
|
+
code: encodePreset(values),
|
|
3327
|
+
fallbacks,
|
|
3328
|
+
values
|
|
3329
|
+
};
|
|
3330
|
+
}
|
|
3331
|
+
async function readCssState(tailwindCssPath) {
|
|
3332
|
+
const fallback = {
|
|
3333
|
+
darkVars: {},
|
|
3334
|
+
imports: [],
|
|
3335
|
+
rootVars: {},
|
|
3336
|
+
source: "",
|
|
3337
|
+
themeVars: {}
|
|
3338
|
+
};
|
|
3339
|
+
if (!tailwindCssPath || !await fs.pathExists(tailwindCssPath)) return fallback;
|
|
3340
|
+
try {
|
|
3341
|
+
const source = await fs.readFile(tailwindCssPath, "utf8");
|
|
3342
|
+
return {
|
|
3343
|
+
darkVars: collectBlockDeclarations(source, ".dark"),
|
|
3344
|
+
imports: collectImports(source),
|
|
3345
|
+
rootVars: collectBlockDeclarations(source, ":root"),
|
|
3346
|
+
source,
|
|
3347
|
+
themeVars: collectAtRuleDeclarations(source, "@theme inline")
|
|
3348
|
+
};
|
|
3349
|
+
} catch {
|
|
3350
|
+
return fallback;
|
|
3351
|
+
}
|
|
3352
|
+
}
|
|
3353
|
+
function collectImports(source) {
|
|
3354
|
+
const imports = [];
|
|
3355
|
+
const pattern = /@import\s+(?:url\((['"]?)(.+?)\1\)|(['"])(.+?)\3)\s*;/g;
|
|
3356
|
+
let match;
|
|
3357
|
+
while (match = pattern.exec(source)) {
|
|
3358
|
+
const value = match[2] ?? match[4];
|
|
3359
|
+
if (value) imports.push(value);
|
|
3360
|
+
}
|
|
3361
|
+
return imports;
|
|
3362
|
+
}
|
|
3363
|
+
function collectBlockDeclarations(source, selector) {
|
|
3364
|
+
const pattern = new RegExp(`${escapeRegExp(selector)}\\s*\\{([\\s\\S]*?)\\}`, "m");
|
|
3365
|
+
const match = source.match(pattern);
|
|
3366
|
+
return match ? collectDeclarations(match[1]) : {};
|
|
3367
|
+
}
|
|
3368
|
+
function collectAtRuleDeclarations(source, atRule) {
|
|
3369
|
+
const pattern = new RegExp(`${escapeRegExp(atRule)}\\s*\\{([\\s\\S]*?)\\}`, "m");
|
|
3370
|
+
const match = source.match(pattern);
|
|
3371
|
+
return match ? collectDeclarations(match[1]) : {};
|
|
3372
|
+
}
|
|
3373
|
+
function collectDeclarations(source) {
|
|
3374
|
+
const declarations = {};
|
|
3375
|
+
const pattern = /(--[a-z0-9-]+)\s*:\s*([^;]+);/gi;
|
|
3376
|
+
let match;
|
|
3377
|
+
while (match = pattern.exec(source)) declarations[match[1]] = match[2].trim();
|
|
3378
|
+
return declarations;
|
|
3379
|
+
}
|
|
3380
|
+
function matchTheme(state, options) {
|
|
3381
|
+
const lightPrimary = state.rootVars["--primary"];
|
|
3382
|
+
if (!lightPrimary) return null;
|
|
3383
|
+
for (const theme of PRESET_THEMES) {
|
|
3384
|
+
let registryTheme;
|
|
3385
|
+
try {
|
|
3386
|
+
registryTheme = buildRegistryTheme({
|
|
3387
|
+
...DEFAULT_DESIGN_SYSTEM_FOR_MATCHING,
|
|
3388
|
+
...options,
|
|
3389
|
+
theme
|
|
3390
|
+
}).cssVars;
|
|
3391
|
+
} catch {
|
|
3392
|
+
continue;
|
|
3393
|
+
}
|
|
3394
|
+
const expectedLight = registryTheme.light?.primary;
|
|
3395
|
+
const expectedDark = registryTheme.dark?.primary;
|
|
3396
|
+
if (!valuesMatch(lightPrimary, expectedLight)) continue;
|
|
3397
|
+
const darkPrimary = state.darkVars["--primary"];
|
|
3398
|
+
if (darkPrimary && expectedDark && !valuesMatch(darkPrimary, expectedDark)) continue;
|
|
3399
|
+
return theme;
|
|
3400
|
+
}
|
|
3401
|
+
return null;
|
|
3402
|
+
}
|
|
3403
|
+
function getFallbackTheme(baseColor) {
|
|
3404
|
+
return baseColor === DEFAULT_PRESET_CONFIG.baseColor ? DEFAULT_PRESET_CONFIG.theme : baseColor;
|
|
3405
|
+
}
|
|
3406
|
+
function matchRadius(value, style) {
|
|
3407
|
+
if (!value) return null;
|
|
3408
|
+
const normalizedValue = normalizeCssValue(value);
|
|
3409
|
+
const styleDefaultRadius = getStyleDefaultRadius(style);
|
|
3410
|
+
if (styleDefaultRadius && normalizedValue === normalizeCssValue(getRadiusValue(styleDefaultRadius))) return "default";
|
|
3411
|
+
for (const radius of PRESET_RADII) if (normalizedValue === normalizeCssValue(getRadiusValue(radius))) return radius === "default" ? "medium" : radius;
|
|
3412
|
+
return null;
|
|
3413
|
+
}
|
|
3414
|
+
async function resolveBodyFont(config, state) {
|
|
3415
|
+
const activeVariable = resolveActiveBodyFontVariable(state);
|
|
3416
|
+
const managedFonts = await readManagedAstroFontsFromProject(config.resolvedPaths.cwd);
|
|
3417
|
+
const matchedManagedFont = matchManagedFont(activeVariable ? managedFonts.find((font) => font.cssVariable === activeVariable) : managedFonts.find((font) => ROOT_FONT_VARIABLE_SET.has(font.cssVariable)), "body");
|
|
3418
|
+
if (matchedManagedFont) return matchedManagedFont;
|
|
3419
|
+
const matchedImport = activeVariable ? matchFontByImports(state.imports, activeVariable) : null;
|
|
3420
|
+
if (matchedImport) return matchedImport;
|
|
3421
|
+
for (const variable of ROOT_FONT_VARIABLES) {
|
|
3422
|
+
const matched = matchFontFromVariable(state, variable);
|
|
3423
|
+
if (matched) return matched;
|
|
3424
|
+
}
|
|
3425
|
+
return null;
|
|
3426
|
+
}
|
|
3427
|
+
async function resolveHeadingFont(config, state, bodyFont) {
|
|
3428
|
+
const managedHeadingFont = matchManagedFont((await readManagedAstroFontsFromProject(config.resolvedPaths.cwd)).find((font) => font.cssVariable === "--font-heading"), "heading");
|
|
3429
|
+
if (managedHeadingFont) return managedHeadingFont === bodyFont ? "inherit" : managedHeadingFont;
|
|
3430
|
+
const resolved = resolveFontValue(state, "--font-heading");
|
|
3431
|
+
const matched = resolved ? parseFontFromFamily(resolved) : null;
|
|
3432
|
+
if (matched) return matched === bodyFont ? "inherit" : matched;
|
|
3433
|
+
const value = getCssVariableValue(state, "--font-heading");
|
|
3434
|
+
const reference = value ? getVarReference(value) : null;
|
|
3435
|
+
if (reference && ROOT_FONT_VARIABLE_SET.has(reference)) {
|
|
3436
|
+
const matchedRootFont = matchFontFromVariable(state, reference);
|
|
3437
|
+
if (!matchedRootFont || matchedRootFont === bodyFont) return "inherit";
|
|
3438
|
+
return matchedRootFont;
|
|
3439
|
+
}
|
|
3440
|
+
return "inherit";
|
|
3441
|
+
}
|
|
3442
|
+
function normalizePresetStyle(style) {
|
|
3443
|
+
if (!style) return null;
|
|
3444
|
+
const normalized = style.replace(/^(bejamas|base|radix)-/, "");
|
|
3445
|
+
if (!PRESET_STYLE_SET.has(normalized)) return null;
|
|
3446
|
+
return normalized;
|
|
3447
|
+
}
|
|
3448
|
+
function normalizeFontHeading(fontHeading, bodyFont) {
|
|
3449
|
+
const normalized = fontHeading === bodyFont ? "inherit" : fontHeading;
|
|
3450
|
+
return PRESET_FONT_HEADING_SET.has(normalized) ? normalized : DEFAULT_PRESET_CONFIG.fontHeading;
|
|
3451
|
+
}
|
|
3452
|
+
function resolveActiveBodyFontVariable(state) {
|
|
3453
|
+
const htmlOrBodyFont = state.source.match(/(?:html|body)\s*\{[\s\S]*?@apply[^;]*\bfont-(sans|serif|mono)\b[\s\S]*?\}/m)?.[1];
|
|
3454
|
+
if (htmlOrBodyFont) return `--font-${htmlOrBodyFont}`;
|
|
3455
|
+
for (const variable of ROOT_FONT_VARIABLES) if (state.themeVars[variable]) return variable;
|
|
3456
|
+
return null;
|
|
3457
|
+
}
|
|
3458
|
+
function matchManagedFont(managedFont, slot) {
|
|
3459
|
+
if (!managedFont) return null;
|
|
3460
|
+
for (const font of PRESET_FONTS) {
|
|
3461
|
+
const expected = slot === "heading" ? toManagedAstroFont(`font-heading-${font}`) : toManagedAstroFont(font);
|
|
3462
|
+
if (expected && expected.cssVariable === managedFont.cssVariable && normalizeFontName(expected.name) === normalizeFontName(managedFont.name)) return font;
|
|
3463
|
+
}
|
|
3464
|
+
return null;
|
|
3465
|
+
}
|
|
3466
|
+
function matchFontByImports(imports, variable) {
|
|
3467
|
+
const matches = imports.flatMap((input) => {
|
|
3468
|
+
const font = parseFontFromDependency(input);
|
|
3469
|
+
const fontValue = font ? getFontValue(font) : null;
|
|
3470
|
+
return font && fontValue?.font.variable === variable ? [font] : [];
|
|
3471
|
+
});
|
|
3472
|
+
return matches.length === 1 ? matches[0] : null;
|
|
3473
|
+
}
|
|
3474
|
+
function matchFontFromVariable(state, variable) {
|
|
3475
|
+
const resolved = resolveFontValue(state, variable);
|
|
3476
|
+
return resolved ? parseFontFromFamily(resolved) : null;
|
|
3477
|
+
}
|
|
3478
|
+
function resolveFontValue(state, variable, seen = /* @__PURE__ */ new Set()) {
|
|
3479
|
+
if (seen.has(variable)) return null;
|
|
3480
|
+
seen.add(variable);
|
|
3481
|
+
const value = getCssVariableValue(state, variable);
|
|
3482
|
+
if (!value) return null;
|
|
3483
|
+
const reference = getVarReference(value);
|
|
3484
|
+
if (!reference) return value;
|
|
3485
|
+
if (FONT_VARIABLE_SET.has(reference)) return resolveFontValue(state, reference, seen);
|
|
3486
|
+
return null;
|
|
3487
|
+
}
|
|
3488
|
+
function getCssVariableValue(state, variable) {
|
|
3489
|
+
const themeValue = state.themeVars[variable];
|
|
3490
|
+
if (themeValue && getVarReference(themeValue) !== variable) return themeValue;
|
|
3491
|
+
return state.rootVars[variable] ?? themeValue ?? null;
|
|
3492
|
+
}
|
|
3493
|
+
function getVarReference(value) {
|
|
3494
|
+
return normalizeCssValue(value).match(/^var\((--[a-z0-9-]+)\)$/)?.[1] ?? null;
|
|
3495
|
+
}
|
|
3496
|
+
function parseFontFromDependency(value) {
|
|
3497
|
+
if (!value) return null;
|
|
3498
|
+
const prefix = "@fontsource-variable/";
|
|
3499
|
+
const normalized = normalizeCssValue(value);
|
|
3500
|
+
return normalized.startsWith(prefix) ? toPresetFont(normalized.slice(21)) : null;
|
|
3501
|
+
}
|
|
3502
|
+
function parseFontFromFamily(value) {
|
|
3503
|
+
if (!value) return null;
|
|
3504
|
+
const primaryFamily = normalizeFontName(value.split(",")[0] ?? "");
|
|
3505
|
+
for (const font of PRESET_FONTS) {
|
|
3506
|
+
const bodyFont = getFontValue(font);
|
|
3507
|
+
const headingFont = getHeadingFontValue(font);
|
|
3508
|
+
if ([
|
|
3509
|
+
font,
|
|
3510
|
+
bodyFont?.title,
|
|
3511
|
+
bodyFont?.font.family.split(",")[0],
|
|
3512
|
+
bodyFont?.font.import,
|
|
3513
|
+
headingFont?.title?.replace(/\s+\(Heading\)$/, ""),
|
|
3514
|
+
headingFont?.font.family.split(",")[0],
|
|
3515
|
+
headingFont?.font.import
|
|
3516
|
+
].some((candidate) => candidate && normalizeFontName(candidate) === primaryFamily)) return font;
|
|
3517
|
+
}
|
|
3518
|
+
return null;
|
|
3519
|
+
}
|
|
3520
|
+
function toPresetFont(value) {
|
|
3521
|
+
const normalized = normalizeCssValue(value);
|
|
3522
|
+
return PRESET_FONT_SET.has(normalized) ? normalized : null;
|
|
3523
|
+
}
|
|
3524
|
+
function normalizeFontName(value) {
|
|
3525
|
+
return value.trim().replace(/^['"]|['"]$/g, "").replace(/\s+variable$/i, "").replace(/_/g, "-").replace(/\s+/g, "-").toLowerCase();
|
|
3526
|
+
}
|
|
3527
|
+
function normalizeCssValue(value) {
|
|
3528
|
+
if (!value) return "";
|
|
3529
|
+
return value.trim().replace(/-?\d*\.\d+/g, (match) => String(Number(match))).replace(/\s+/g, " ").replace(/\s*,\s*/g, ",").replace(/\s*\/\s*/g, "/").replace(/"/g, "'").toLowerCase();
|
|
3530
|
+
}
|
|
3531
|
+
function valuesMatch(actual, expected) {
|
|
3532
|
+
return normalizeCssValue(actual) === normalizeCssValue(expected);
|
|
3533
|
+
}
|
|
3534
|
+
function asPresetValue(set, value) {
|
|
3535
|
+
return value && set.has(value) ? value : null;
|
|
3536
|
+
}
|
|
3537
|
+
function escapeRegExp(value) {
|
|
3538
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3539
|
+
}
|
|
3540
|
+
const DEFAULT_DESIGN_SYSTEM_FOR_MATCHING = {
|
|
3541
|
+
...DEFAULT_PRESET_CONFIG,
|
|
3542
|
+
template: "astro",
|
|
3543
|
+
rtl: false,
|
|
3544
|
+
rtlLanguage: "ar"
|
|
3545
|
+
};
|
|
3546
|
+
|
|
3547
|
+
//#endregion
|
|
3548
|
+
//#region src/commands/preset.ts
|
|
3549
|
+
const PRESET_CHART_COLOR_SET = new Set(PRESET_CHART_COLORS);
|
|
3550
|
+
function getPresetUrl(code) {
|
|
3551
|
+
return buildUiUrl(`/create?preset=${code}`);
|
|
3552
|
+
}
|
|
3553
|
+
function decodePresetCode(code) {
|
|
3554
|
+
const decoded = decodePreset(code);
|
|
3555
|
+
if (!decoded) throw new Error(`Invalid preset code: ${code}`);
|
|
3556
|
+
const derived = [];
|
|
3557
|
+
const chartColor = resolveDecodedChartColor(decoded);
|
|
3558
|
+
if (!decoded.chartColor && chartColor) derived.push("chartColor");
|
|
3559
|
+
return {
|
|
3560
|
+
code,
|
|
3561
|
+
derived,
|
|
3562
|
+
url: getPresetUrl(code),
|
|
3563
|
+
values: {
|
|
3564
|
+
...decoded,
|
|
3565
|
+
...chartColor ? { chartColor } : {}
|
|
3566
|
+
},
|
|
3567
|
+
version: code[0]
|
|
3568
|
+
};
|
|
3569
|
+
}
|
|
3570
|
+
function printPresetDecode(result) {
|
|
3571
|
+
printPresetInfo({
|
|
3572
|
+
code: result.code,
|
|
3573
|
+
fallbacks: result.derived,
|
|
3574
|
+
values: result.values
|
|
3575
|
+
}, { fallbackNote: " * Compatibility value for older preset versions." });
|
|
3576
|
+
}
|
|
3577
|
+
function printPresetInfo(preset, options = {}) {
|
|
3578
|
+
logger.log(highlighter.info("Preset"));
|
|
3579
|
+
if (!preset.code || !preset.values) {
|
|
3580
|
+
printEntries({ code: "-" });
|
|
3581
|
+
return;
|
|
3582
|
+
}
|
|
3583
|
+
const fallbacks = preset.fallbacks ?? [];
|
|
3584
|
+
const formatPresetValue = (key, value) => {
|
|
3585
|
+
const suffix = fallbacks.includes(key) ? "*" : "";
|
|
3586
|
+
return `${value ?? "-"}${suffix}`;
|
|
3587
|
+
};
|
|
3588
|
+
printEntries({
|
|
3589
|
+
code: preset.code,
|
|
3590
|
+
version: preset.code[0],
|
|
3591
|
+
style: preset.values.style,
|
|
3592
|
+
baseColor: formatPresetValue("baseColor", preset.values.baseColor),
|
|
3593
|
+
theme: formatPresetValue("theme", preset.values.theme),
|
|
3594
|
+
chartColor: formatPresetValue("chartColor", preset.values.chartColor),
|
|
3595
|
+
iconLibrary: formatPresetValue("iconLibrary", preset.values.iconLibrary),
|
|
3596
|
+
font: formatPresetValue("font", preset.values.font),
|
|
3597
|
+
fontHeading: formatPresetValue("fontHeading", preset.values.fontHeading),
|
|
3598
|
+
radius: formatPresetValue("radius", preset.values.radius),
|
|
3599
|
+
menuAccent: formatPresetValue("menuAccent", preset.values.menuAccent),
|
|
3600
|
+
menuColor: formatPresetValue("menuColor", preset.values.menuColor),
|
|
3601
|
+
url: getPresetUrl(preset.code)
|
|
3602
|
+
});
|
|
3603
|
+
if (fallbacks.length > 0) {
|
|
3604
|
+
logger.log("");
|
|
3605
|
+
logger.log(options.fallbackNote ?? " * Uses preset defaults for values that could not be inferred from the project.");
|
|
3606
|
+
}
|
|
3607
|
+
}
|
|
3608
|
+
const decode = new Command().name("decode").description("decode a preset code").argument("<code>", "the preset code to decode").option("--json", "output as JSON.", false).action((code, opts) => {
|
|
3609
|
+
try {
|
|
3610
|
+
const result = decodePresetCode(code);
|
|
3611
|
+
if (opts.json) {
|
|
3612
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3613
|
+
return;
|
|
3614
|
+
}
|
|
3615
|
+
printPresetDecode(result);
|
|
3616
|
+
} catch (error) {
|
|
3617
|
+
handlePresetError(error);
|
|
3618
|
+
}
|
|
3619
|
+
});
|
|
3620
|
+
const url = new Command().name("url").description("get the create URL for a preset code").argument("<code>", "the preset code").action((code) => {
|
|
3621
|
+
try {
|
|
3622
|
+
logger.log(decodePresetCode(code).url);
|
|
3623
|
+
} catch (error) {
|
|
3624
|
+
handlePresetError(error);
|
|
3625
|
+
}
|
|
3626
|
+
});
|
|
3627
|
+
const openCommand = new Command().name("open").description("open a preset code in the browser").argument("<code>", "the preset code").action(async (code) => {
|
|
3628
|
+
let presetUrl;
|
|
3629
|
+
try {
|
|
3630
|
+
presetUrl = decodePresetCode(code).url;
|
|
3631
|
+
} catch (error) {
|
|
3632
|
+
handlePresetError(error);
|
|
3633
|
+
return;
|
|
3634
|
+
}
|
|
3635
|
+
logger.break();
|
|
3636
|
+
logger.log(` Opening ${presetUrl} in your browser.`);
|
|
3637
|
+
logger.break();
|
|
3638
|
+
try {
|
|
3639
|
+
const { default: open } = await import("open");
|
|
3640
|
+
await open(presetUrl);
|
|
3641
|
+
} catch (error) {
|
|
3642
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3643
|
+
handlePresetError(/* @__PURE__ */ new Error(`Failed to open preset URL: ${message}`));
|
|
3644
|
+
}
|
|
3645
|
+
});
|
|
3646
|
+
const resolve$1 = new Command().name("resolve").alias("info").description("resolve a preset from your project").option("-c, --cwd <cwd>", "the working directory. defaults to the current directory.", process.cwd()).option("--json", "output as JSON.", false).action(async (opts) => {
|
|
3647
|
+
try {
|
|
3648
|
+
const cwd = path.resolve(opts.cwd);
|
|
3649
|
+
if (!existsSync(path.resolve(cwd, "components.json")) && await isMonorepoRoot(cwd)) {
|
|
3650
|
+
const targets = await getMonorepoTargets(cwd);
|
|
3651
|
+
if (targets.length > 0) {
|
|
3652
|
+
if (opts.json) console.log(JSON.stringify({
|
|
3653
|
+
error: "monorepo_root",
|
|
3654
|
+
message: "You are running preset resolve from a monorepo root. Use the -c flag to specify a workspace.",
|
|
3655
|
+
targets: targets.map((target) => target.name)
|
|
3656
|
+
}, null, 2));
|
|
3657
|
+
else formatMonorepoMessage("preset resolve", targets, {
|
|
3658
|
+
binary: "bejamas",
|
|
3659
|
+
cwdFlag: "-c"
|
|
3660
|
+
});
|
|
3661
|
+
process.exit(1);
|
|
3662
|
+
}
|
|
3663
|
+
}
|
|
3664
|
+
const config = await getConfig(cwd);
|
|
3665
|
+
if (!config) {
|
|
3666
|
+
if (opts.json) {
|
|
3667
|
+
console.log(JSON.stringify(null, null, 2));
|
|
3668
|
+
return;
|
|
3669
|
+
}
|
|
3670
|
+
logger.log("No components.json found.");
|
|
3671
|
+
return;
|
|
3672
|
+
}
|
|
3673
|
+
const resolvedPreset = await resolveProjectPreset(config);
|
|
3674
|
+
if (opts.json) {
|
|
3675
|
+
console.log(JSON.stringify(resolvedPreset.code ? resolvedPreset : null, null, 2));
|
|
3676
|
+
return;
|
|
3677
|
+
}
|
|
3678
|
+
printPresetInfo(resolvedPreset);
|
|
3679
|
+
} catch (error) {
|
|
3680
|
+
handleError(error);
|
|
3681
|
+
}
|
|
3682
|
+
});
|
|
3683
|
+
const preset = new Command().name("preset").description("manage presets").addCommand(decode).addCommand(resolve$1).addCommand(url).addCommand(openCommand).action(() => {
|
|
3684
|
+
preset.outputHelp();
|
|
3685
|
+
});
|
|
3686
|
+
function resolveDecodedChartColor(decoded) {
|
|
3687
|
+
if (decoded.chartColor) return decoded.chartColor;
|
|
3688
|
+
const legacyChartColor = V1_CHART_COLOR_MAP[decoded.theme];
|
|
3689
|
+
if (legacyChartColor) return legacyChartColor;
|
|
3690
|
+
if (PRESET_CHART_COLOR_SET.has(decoded.theme)) return decoded.theme;
|
|
3691
|
+
}
|
|
3692
|
+
function printEntries(entries) {
|
|
3693
|
+
const maxKeyLength = Math.max(...Object.keys(entries).map((key) => key.length));
|
|
3694
|
+
for (const [key, value] of Object.entries(entries)) logger.log(` ${key.padEnd(maxKeyLength + 2)}${value}`);
|
|
3695
|
+
}
|
|
3696
|
+
function handlePresetError(error) {
|
|
3697
|
+
if (error instanceof Error) logger.error(error.message);
|
|
3698
|
+
process.exit(1);
|
|
3699
|
+
}
|
|
3700
|
+
|
|
3701
|
+
//#endregion
|
|
3702
|
+
//#region src/index.ts
|
|
3703
|
+
const pkg = createRequire(import.meta.url)("../package.json");
|
|
3704
|
+
const program = new Command().name("bejamas").description("bejamas/ui cli").configureHelp({ helpWidth: Math.min(100, process.stdout.columns || 100) }).version(pkg.version, "-v, --version", "output the version number");
|
|
3705
|
+
program.addCommand(init);
|
|
3706
|
+
program.addCommand(add);
|
|
3707
|
+
program.addCommand(apply);
|
|
3708
|
+
program.addCommand(preset);
|
|
3709
|
+
program.addCommand(info);
|
|
3710
|
+
program.addCommand(docs);
|
|
3711
|
+
program.addCommand(docsBuild);
|
|
3712
|
+
program.addCommand(docsCheck);
|
|
3713
|
+
program.parse(process.argv);
|
|
3714
|
+
|
|
3715
|
+
//#endregion
|
|
3716
|
+
export { program as default };
|
|
3717
|
+
//# sourceMappingURL=index.js.map
|