bejamas 0.2.12 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{generate-mdx-BejBMCLk.js → generate-mdx-PPNpe9_J.js} +2 -7
- package/dist/{generate-mdx-BejBMCLk.js.map → generate-mdx-PPNpe9_J.js.map} +1 -1
- package/dist/index.js +1603 -320
- package/dist/index.js.map +1 -1
- package/dist/utils-BCEkpKMO.js +9277 -0
- package/dist/utils-BCEkpKMO.js.map +1 -0
- package/package.json +16 -3
- package/src/tailwind.css +104 -0
- package/dist/utils-BfJTJvcy.js +0 -431
- package/dist/utils-BfJTJvcy.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { _ as
|
|
2
|
+
import { A as renderSemanticIconSvgWithAttributeString, B as normalizeDesignSystemConfig, C as buildUiUrl, D as SEMANTIC_ICON_NAMES, E as ICON_LIBRARY_COLLECTIONS, F as getDocumentLanguage, H as encodePreset, I as getFontPackageName, L as getFontValue, M as RTL_LANGUAGE_VALUES, N as designSystemConfigSchema, O as getSemanticIconNameFromLucideExport, P as getDocumentDirection, R as getHeadingFontValue, S as BASE_COLORS, T as buildRegistryTheme, U as isPresetCode, V as decodePreset, W as fonts, _ as getConfig, b as highlighter, d as parseJsDocMetadata, g as spinner, j as DEFAULT_DESIGN_SYSTEM_CONFIG, k as getSemanticIconNameFromLucidePath, o as extractFrontmatter, p as resolveUiRoot, v as getWorkspaceConfig, w as resolveRegistryUrl, x as BEJAMAS_COMPONENTS_SCHEMA_URL, y as logger, z as getStyleId } from "./utils-BCEkpKMO.js";
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import { createRequire } from "module";
|
|
5
|
-
import path from "path";
|
|
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";
|
|
6
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";
|
|
7
14
|
import os from "os";
|
|
8
15
|
import dotenv from "dotenv";
|
|
9
16
|
import { detect } from "@antfu/ni";
|
|
10
|
-
import { z } from "zod";
|
|
11
|
-
import { bold, cyan, dim, green, magenta, red, white, yellow } from "kleur/colors";
|
|
12
17
|
import { execa } from "execa";
|
|
13
18
|
import prompts from "prompts";
|
|
14
|
-
import
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
import os$1 from "node:os";
|
|
20
|
+
|
|
21
|
+
//#region src/registry/context.ts
|
|
22
|
+
let context = { headers: {} };
|
|
23
|
+
function clearRegistryContext() {
|
|
24
|
+
context.headers = {};
|
|
25
|
+
}
|
|
18
26
|
|
|
27
|
+
//#endregion
|
|
19
28
|
//#region src/utils/errors.ts
|
|
20
29
|
const MISSING_DIR_OR_EMPTY_PROJECT = "1";
|
|
21
30
|
|
|
@@ -23,7 +32,7 @@ const MISSING_DIR_OR_EMPTY_PROJECT = "1";
|
|
|
23
32
|
//#region src/preflights/preflight-init.ts
|
|
24
33
|
async function preFlightInit(options) {
|
|
25
34
|
const errors = {};
|
|
26
|
-
if (!fs.existsSync(options.cwd) || !fs.existsSync(path.resolve(options.cwd, "package.json"))) {
|
|
35
|
+
if (!fs.existsSync(options.cwd) || !fs.existsSync(path$1.resolve(options.cwd, "package.json"))) {
|
|
27
36
|
errors[MISSING_DIR_OR_EMPTY_PROJECT] = true;
|
|
28
37
|
return {
|
|
29
38
|
errors,
|
|
@@ -37,10 +46,986 @@ async function preFlightInit(options) {
|
|
|
37
46
|
}
|
|
38
47
|
|
|
39
48
|
//#endregion
|
|
40
|
-
//#region src/
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
//#region src/utils/astro-fonts.ts
|
|
50
|
+
const ASTRO_CONFIG_BLOCK_START = "// bejamas:astro-fonts:start";
|
|
51
|
+
const ASTRO_CONFIG_BLOCK_END = "// bejamas:astro-fonts:end";
|
|
52
|
+
const ASTRO_LAYOUT_BLOCK_START = "<!-- bejamas:astro-fonts:start -->";
|
|
53
|
+
const ASTRO_LAYOUT_BLOCK_END = "<!-- bejamas:astro-fonts:end -->";
|
|
54
|
+
const ASTRO_FONT_CONSTANT = "BEJAMAS_ASTRO_FONTS";
|
|
55
|
+
const ASTRO_CONFIG_CANDIDATES = [
|
|
56
|
+
"astro.config.mjs",
|
|
57
|
+
"apps/web/astro.config.mjs",
|
|
58
|
+
"apps/docs/astro.config.mjs"
|
|
59
|
+
];
|
|
60
|
+
const ASTRO_LAYOUT_CANDIDATES = ["src/layouts/Layout.astro", "apps/web/src/layouts/Layout.astro"];
|
|
61
|
+
const STARLIGHT_HEAD_RELATIVE_PATH = "apps/docs/src/components/Head.astro";
|
|
62
|
+
const ASTRO_FONT_PROVIDERS = ["fontsource", "google"];
|
|
63
|
+
const ASTRO_FONTSOURCE_FONT_NAMES = new Set(["geist", "geist-mono"]);
|
|
64
|
+
const MANAGED_BODY_FONT_CLASSES = new Set(["font-mono", "font-serif"]);
|
|
65
|
+
function compactSource$1(source) {
|
|
66
|
+
return source.replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
|
|
67
|
+
}
|
|
68
|
+
function escapeRegExp$1(value) {
|
|
69
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
70
|
+
}
|
|
71
|
+
function upsertImport(source, moduleName, importedName, fallbackNames = []) {
|
|
72
|
+
const importPattern = /* @__PURE__ */ new RegExp(`import\\s*\\{([^}]+)\\}\\s*from\\s*["']${escapeRegExp$1(moduleName)}["'];?`);
|
|
73
|
+
if (importPattern.test(source)) return source.replace(importPattern, (_match, importsSource) => {
|
|
74
|
+
const imports = importsSource.split(",").map((value) => value.trim()).filter(Boolean);
|
|
75
|
+
if (!imports.includes(importedName)) imports.push(importedName);
|
|
76
|
+
for (const name of fallbackNames) if (!imports.includes(name)) imports.unshift(name);
|
|
77
|
+
return `import { ${Array.from(new Set(imports)).join(", ")} } from "${moduleName}";`;
|
|
78
|
+
});
|
|
79
|
+
const importLine = `import { ${Array.from(new Set([...fallbackNames, importedName])).join(", ")} } from "${moduleName}";`;
|
|
80
|
+
const frontmatterEnd = source.indexOf("---", 3);
|
|
81
|
+
if (source.startsWith("---") && frontmatterEnd !== -1) return `${source.slice(0, frontmatterEnd).trimEnd()}\n${importLine}\n${source.slice(frontmatterEnd)}`;
|
|
82
|
+
return `${importLine}\n${source}`;
|
|
83
|
+
}
|
|
84
|
+
function buildAstroConfigFontBlock(fontsToSerialize) {
|
|
85
|
+
return `${ASTRO_CONFIG_BLOCK_START}\n/** @type {NonNullable<import("astro/config").AstroUserConfig["fonts"]>} */\nconst ${ASTRO_FONT_CONSTANT} = [${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`}];\n${ASTRO_CONFIG_BLOCK_END}`;
|
|
86
|
+
}
|
|
87
|
+
function buildAstroLayoutFontBlock(fontsToSerialize) {
|
|
88
|
+
return `${ASTRO_LAYOUT_BLOCK_START}\n${fontsToSerialize.map((font) => `<Font cssVariable="${font.cssVariable}" />`).join("\n")}\n${ASTRO_LAYOUT_BLOCK_END}`;
|
|
89
|
+
}
|
|
90
|
+
function bodyFontClassFromCssVariable(cssVariable) {
|
|
91
|
+
if (cssVariable === "--font-mono" || cssVariable === "--font-serif") return fontClassFromCssVariable(cssVariable);
|
|
92
|
+
return "";
|
|
93
|
+
}
|
|
94
|
+
function upsertManagedBodyFontClass(source, cssVariable) {
|
|
95
|
+
const bodyClass = bodyFontClassFromCssVariable(cssVariable);
|
|
96
|
+
const bodyTagPattern = /<body\b([^>]*?)(\/?)>/m;
|
|
97
|
+
if (!bodyTagPattern.test(source)) return source;
|
|
98
|
+
return source.replace(bodyTagPattern, (_match, attrs, selfClosing) => {
|
|
99
|
+
const classPattern = /\sclass=(["'])(.*?)\1/m;
|
|
100
|
+
const classMatch = attrs.match(classPattern);
|
|
101
|
+
const quote = classMatch?.[1] ?? "\"";
|
|
102
|
+
const existingClasses = (classMatch?.[2] ?? "").split(/\s+/).filter(Boolean).filter((className) => !MANAGED_BODY_FONT_CLASSES.has(className));
|
|
103
|
+
const nextClasses = bodyClass ? Array.from(new Set([...existingClasses, bodyClass])) : existingClasses;
|
|
104
|
+
let nextAttrs = attrs.replace(classPattern, "");
|
|
105
|
+
if (nextClasses.length > 0) nextAttrs = `${nextAttrs} class=${quote}${nextClasses.join(" ")}${quote}`;
|
|
106
|
+
return `<body${nextAttrs}${selfClosing}>`;
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
function parseManagedAstroFonts(source) {
|
|
110
|
+
const markerPattern = /* @__PURE__ */ new RegExp(`${escapeRegExp$1(ASTRO_CONFIG_BLOCK_START)}[\\s\\S]*?const\\s+${ASTRO_FONT_CONSTANT}\\s*=\\s*\\[([\\s\\S]*?)\\];[\\s\\S]*?${escapeRegExp$1(ASTRO_CONFIG_BLOCK_END)}`);
|
|
111
|
+
const match = source.match(markerPattern);
|
|
112
|
+
if (!match) return [];
|
|
113
|
+
return (match[1].match(/\{[\s\S]*?\}/g) ?? []).map((entry) => {
|
|
114
|
+
const name = entry.match(/name:\s*"([^"]+)"/)?.[1];
|
|
115
|
+
const cssVariable = entry.match(/cssVariable:\s*"([^"]+)"/)?.[1];
|
|
116
|
+
const provider = entry.match(/provider:\s*fontProviders\.([a-z]+)\(\)/)?.[1];
|
|
117
|
+
const subsetsSource = entry.match(/subsets:\s*(\[[^\]]*\])/s)?.[1];
|
|
118
|
+
if (!name || !cssVariable || !provider || !ASTRO_FONT_PROVIDERS.includes(provider)) return null;
|
|
119
|
+
let subsets = ["latin"];
|
|
120
|
+
if (subsetsSource) try {
|
|
121
|
+
const parsed = JSON.parse(subsetsSource);
|
|
122
|
+
if (Array.isArray(parsed) && parsed.length > 0 && parsed.every((value) => typeof value === "string")) subsets = parsed;
|
|
123
|
+
} catch {
|
|
124
|
+
subsets = ["latin"];
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
name,
|
|
128
|
+
cssVariable,
|
|
129
|
+
provider,
|
|
130
|
+
subsets
|
|
131
|
+
};
|
|
132
|
+
}).filter((value) => value !== null);
|
|
133
|
+
}
|
|
134
|
+
function normalizeManagedAstroFontSubsets(subsets) {
|
|
135
|
+
if (!subsets?.length) return ["latin"];
|
|
136
|
+
return [subsets[0], ...subsets.slice(1)];
|
|
137
|
+
}
|
|
138
|
+
function replaceManagedBlock(source, nextBlock, start, end) {
|
|
139
|
+
const blockPattern = new RegExp(`${escapeRegExp$1(start)}[\\s\\S]*?${escapeRegExp$1(end)}`, "m");
|
|
140
|
+
if (blockPattern.test(source)) return source.replace(blockPattern, nextBlock);
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
function stripExperimentalFontsProperty(source) {
|
|
144
|
+
const experimentalMatch = source.match(/experimental\s*:\s*\{/);
|
|
145
|
+
if (!experimentalMatch || experimentalMatch.index === void 0) return source;
|
|
146
|
+
const propertyStart = experimentalMatch.index;
|
|
147
|
+
const openingBraceIndex = propertyStart + experimentalMatch[0].lastIndexOf("{");
|
|
148
|
+
let depth = 0;
|
|
149
|
+
let closingBraceIndex = -1;
|
|
150
|
+
let quote = null;
|
|
151
|
+
for (let index = openingBraceIndex; index < source.length; index += 1) {
|
|
152
|
+
const character = source[index];
|
|
153
|
+
const previousCharacter = index > 0 ? source[index - 1] : "";
|
|
154
|
+
if (quote) {
|
|
155
|
+
if (character === quote && previousCharacter !== "\\") quote = null;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (character === "\"" || character === "'" || character === "`") {
|
|
159
|
+
quote = character;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (character === "{") {
|
|
163
|
+
depth += 1;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (character === "}") {
|
|
167
|
+
depth -= 1;
|
|
168
|
+
if (depth === 0) {
|
|
169
|
+
closingBraceIndex = index;
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (closingBraceIndex === -1) return source;
|
|
175
|
+
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");
|
|
176
|
+
let propertyEnd = closingBraceIndex + 1;
|
|
177
|
+
while (propertyEnd < source.length && /\s/.test(source[propertyEnd])) propertyEnd += 1;
|
|
178
|
+
if (source[propertyEnd] === ",") propertyEnd += 1;
|
|
179
|
+
if (nextBlockContent.trim().length === 0) return `${source.slice(0, propertyStart)}${source.slice(propertyEnd)}`;
|
|
180
|
+
const rebuiltProperty = `experimental: {${nextBlockContent}\n }`;
|
|
181
|
+
return `${source.slice(0, propertyStart)}${rebuiltProperty}${source.slice(closingBraceIndex + 1)}`;
|
|
182
|
+
}
|
|
183
|
+
function patchAstroConfigSource(source, fontsToSerialize, options = {}) {
|
|
184
|
+
let next = upsertImport(source, "astro/config", "fontProviders", ["defineConfig"]);
|
|
185
|
+
const fontBlock = buildAstroConfigFontBlock(fontsToSerialize);
|
|
186
|
+
const replacedBlock = replaceManagedBlock(next, fontBlock, ASTRO_CONFIG_BLOCK_START, ASTRO_CONFIG_BLOCK_END);
|
|
187
|
+
if (replacedBlock) next = replacedBlock;
|
|
188
|
+
else {
|
|
189
|
+
const lastImport = Array.from(next.matchAll(/^import .*$/gm)).at(-1);
|
|
190
|
+
if (lastImport?.index !== void 0) {
|
|
191
|
+
const insertAt = lastImport.index + lastImport[0].length;
|
|
192
|
+
next = `${next.slice(0, insertAt)}\n\n${fontBlock}${next.slice(insertAt)}`;
|
|
193
|
+
} else next = `${fontBlock}\n\n${next}`;
|
|
194
|
+
}
|
|
195
|
+
next = stripExperimentalFontsProperty(next);
|
|
196
|
+
if (!/^\s*fonts:\s*BEJAMAS_ASTRO_FONTS\b/m.test(next)) next = next.replace(/defineConfig\(\s*\{/, `defineConfig({\n fonts: ${ASTRO_FONT_CONSTANT},`);
|
|
197
|
+
if (options.starlightHeadOverride) {
|
|
198
|
+
if (next.includes("Head: \"./src/components/Head.astro\"")) return compactSource$1(next);
|
|
199
|
+
const starlightComponentsPattern = /starlight\(\{\s*([\s\S]*?)components:\s*\{([\s\S]*?)\}/m;
|
|
200
|
+
if (starlightComponentsPattern.test(next)) next = next.replace(starlightComponentsPattern, (_match, before, inner) => `starlight({${before}components: {\n Head: "./src/components/Head.astro",${inner ? `${inner}` : ""}\n }`);
|
|
201
|
+
else next = next.replace(/starlight\(\s*\{/, `starlight({\n components: {\n Head: "./src/components/Head.astro",\n },`);
|
|
202
|
+
}
|
|
203
|
+
return compactSource$1(next);
|
|
204
|
+
}
|
|
205
|
+
function patchAstroLayoutSource(source, fontsToSerialize, activeFontCssVariable) {
|
|
206
|
+
let next = upsertImport(source, "astro:assets", "Font");
|
|
207
|
+
const fontBlock = buildAstroLayoutFontBlock(fontsToSerialize);
|
|
208
|
+
const replacedBlock = replaceManagedBlock(next, fontBlock, ASTRO_LAYOUT_BLOCK_START, ASTRO_LAYOUT_BLOCK_END);
|
|
209
|
+
if (replacedBlock) next = replacedBlock;
|
|
210
|
+
else if (next.includes("<head>")) next = next.replace("<head>", `<head>\n ${fontBlock.replace(/\n/g, "\n ")}`);
|
|
211
|
+
else if (next.includes("</head>")) next = next.replace("</head>", ` ${fontBlock.replace(/\n/g, "\n ")}\n</head>`);
|
|
212
|
+
else next = `${next}\n${fontBlock}\n`;
|
|
213
|
+
next = upsertManagedBodyFontClass(next, activeFontCssVariable);
|
|
214
|
+
return compactSource$1(next);
|
|
215
|
+
}
|
|
216
|
+
function patchStarlightHeadSource(source, fontsToSerialize) {
|
|
217
|
+
let next = upsertImport(source, "@astrojs/starlight/components/Head.astro", "DefaultHead");
|
|
218
|
+
next = upsertImport(next, "astro:assets", "Font");
|
|
219
|
+
const fontBlock = buildAstroLayoutFontBlock(fontsToSerialize);
|
|
220
|
+
const replacedBlock = replaceManagedBlock(next, fontBlock, ASTRO_LAYOUT_BLOCK_START, ASTRO_LAYOUT_BLOCK_END);
|
|
221
|
+
if (replacedBlock) next = replacedBlock;
|
|
222
|
+
else if (next.includes("<DefaultHead")) next = next.replace(/<DefaultHead[^>]*\/>/, (match) => `${match}\n${fontBlock}`);
|
|
223
|
+
else next = `${next.trimEnd()}\n\n<DefaultHead />\n${fontBlock}\n`;
|
|
224
|
+
return compactSource$1(next);
|
|
225
|
+
}
|
|
226
|
+
function toManagedAstroFont(fontName) {
|
|
227
|
+
const normalized = fontName.replace(/^font-heading-/, "").replace(/^font-/, "");
|
|
228
|
+
const font = fontName.startsWith("font-heading-") ? getHeadingFontValue(normalized) : getFontValue(normalized);
|
|
229
|
+
if (!font || font.type !== "registry:font" || !ASTRO_FONT_PROVIDERS.includes(font.font.provider)) return null;
|
|
230
|
+
return {
|
|
231
|
+
name: font.title?.replace(/\s+\(Heading\)$/, "") ?? font.font.import.replace(/_/g, " "),
|
|
232
|
+
cssVariable: font.font.variable,
|
|
233
|
+
provider: ASTRO_FONTSOURCE_FONT_NAMES.has(normalized) ? "fontsource" : "google",
|
|
234
|
+
subsets: normalizeManagedAstroFontSubsets(font.font.subsets)
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function mergeManagedAstroFonts(currentFonts, nextFont) {
|
|
238
|
+
return [...currentFonts.filter((font) => font.cssVariable !== nextFont.cssVariable), nextFont].sort((left, right) => left.cssVariable.localeCompare(right.cssVariable));
|
|
239
|
+
}
|
|
240
|
+
function fontClassFromCssVariable(cssVariable) {
|
|
241
|
+
return `font-${cssVariable.replace(/^--font-/, "")}`;
|
|
242
|
+
}
|
|
243
|
+
async function patchFileIfExists(filePath, transformer) {
|
|
244
|
+
if (!await fs.pathExists(filePath)) return;
|
|
245
|
+
const current = await fs.readFile(filePath, "utf8");
|
|
246
|
+
const next = transformer(current);
|
|
247
|
+
if (next !== current) await fs.writeFile(filePath, next, "utf8");
|
|
248
|
+
}
|
|
249
|
+
async function collectPackageJsonsFromComponents(projectPath) {
|
|
250
|
+
const componentJsonFiles = await fg("**/components.json", {
|
|
251
|
+
cwd: projectPath,
|
|
252
|
+
ignore: [
|
|
253
|
+
"**/node_modules/**",
|
|
254
|
+
"**/dist/**",
|
|
255
|
+
"**/.astro/**"
|
|
256
|
+
]
|
|
257
|
+
});
|
|
258
|
+
const packageJsonFiles = /* @__PURE__ */ new Set();
|
|
259
|
+
for (const relativePath of componentJsonFiles) {
|
|
260
|
+
const absolutePath = path.resolve(projectPath, relativePath);
|
|
261
|
+
const cssPath = (await fs.readJson(absolutePath))?.tailwind?.css;
|
|
262
|
+
if (typeof cssPath !== "string" || cssPath.length === 0) continue;
|
|
263
|
+
let currentDir = path.dirname(path.resolve(path.dirname(absolutePath), cssPath));
|
|
264
|
+
while (true) {
|
|
265
|
+
const packageJsonPath = path.resolve(currentDir, "package.json");
|
|
266
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
267
|
+
packageJsonFiles.add(packageJsonPath);
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
const parentDir = path.dirname(currentDir);
|
|
271
|
+
if (parentDir === currentDir) break;
|
|
272
|
+
currentDir = parentDir;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return packageJsonFiles;
|
|
276
|
+
}
|
|
277
|
+
async function upsertStarlightHeadFile(projectPath, fontsToSerialize) {
|
|
278
|
+
const headPath = path.resolve(projectPath, STARLIGHT_HEAD_RELATIVE_PATH);
|
|
279
|
+
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);
|
|
280
|
+
await fs.ensureDir(path.dirname(headPath));
|
|
281
|
+
await fs.writeFile(headPath, next, "utf8");
|
|
282
|
+
}
|
|
283
|
+
async function readManagedAstroFontsFromProject(projectPath) {
|
|
284
|
+
for (const relativePath of ASTRO_CONFIG_CANDIDATES) {
|
|
285
|
+
const absolutePath = path.resolve(projectPath, relativePath);
|
|
286
|
+
if (!await fs.pathExists(absolutePath)) continue;
|
|
287
|
+
const parsed = parseManagedAstroFonts(await fs.readFile(absolutePath, "utf8"));
|
|
288
|
+
if (parsed.length > 0) return parsed;
|
|
289
|
+
}
|
|
290
|
+
return [];
|
|
291
|
+
}
|
|
292
|
+
async function cleanupAstroFontPackages(projectPath) {
|
|
293
|
+
const managedPackages = new Set(fonts.filter((font) => font.type === "registry:font").map((font) => getFontPackageName(font.name.replace(/^font-heading-/, "").replace(/^font-/, ""))));
|
|
294
|
+
const packageJsonFiles = await collectPackageJsonsFromComponents(projectPath);
|
|
295
|
+
await Promise.all(Array.from(packageJsonFiles).map(async (absolutePath) => {
|
|
296
|
+
const current = await fs.readJson(absolutePath);
|
|
297
|
+
let changed = false;
|
|
298
|
+
for (const bucket of ["dependencies", "devDependencies"]) {
|
|
299
|
+
const currentBucket = current[bucket];
|
|
300
|
+
if (!currentBucket || typeof currentBucket !== "object") continue;
|
|
301
|
+
for (const packageName of managedPackages) if (packageName in currentBucket) {
|
|
302
|
+
delete currentBucket[packageName];
|
|
303
|
+
changed = true;
|
|
304
|
+
}
|
|
305
|
+
if (Object.keys(currentBucket).length === 0) {
|
|
306
|
+
delete current[bucket];
|
|
307
|
+
changed = true;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (changed) await fs.writeJson(absolutePath, current, { spaces: 2 });
|
|
311
|
+
}));
|
|
312
|
+
}
|
|
313
|
+
async function syncAstroFontsInProject(projectPath, fontsToSerialize, activeFontCssVariable) {
|
|
314
|
+
await Promise.all(ASTRO_CONFIG_CANDIDATES.map((relativePath) => patchFileIfExists(path.resolve(projectPath, relativePath), (source) => patchAstroConfigSource(source, fontsToSerialize, { starlightHeadOverride: relativePath === "apps/docs/astro.config.mjs" }))));
|
|
315
|
+
await Promise.all(ASTRO_LAYOUT_CANDIDATES.map((relativePath) => patchFileIfExists(path.resolve(projectPath, relativePath), (source) => patchAstroLayoutSource(source, fontsToSerialize, activeFontCssVariable))));
|
|
316
|
+
const docsConfigPath = path.resolve(projectPath, "apps/docs/astro.config.mjs");
|
|
317
|
+
if (await fs.pathExists(docsConfigPath)) await upsertStarlightHeadFile(projectPath, fontsToSerialize);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
//#endregion
|
|
321
|
+
//#region src/utils/apply-design-system.ts
|
|
322
|
+
const CREATE_BLOCK_START = "/* bejamas:create:start */";
|
|
323
|
+
const CREATE_BLOCK_END = "/* bejamas:create:end */";
|
|
324
|
+
const SHADCN_TAILWIND_IMPORT = "@import \"shadcn/tailwind.css\";";
|
|
325
|
+
const BEJAMAS_TAILWIND_IMPORT = "@import \"bejamas/tailwind.css\";";
|
|
326
|
+
const MANAGED_TAILWIND_IMPORTS = new Set([SHADCN_TAILWIND_IMPORT, BEJAMAS_TAILWIND_IMPORT]);
|
|
327
|
+
const TEMPLATE_APP_UI_IMPORT = "import { appUi } from \"@/i18n/ui\";";
|
|
328
|
+
const TEMPLATE_I18N_SOURCES = {
|
|
329
|
+
astro: `const RTL_LANGUAGES = ["ar", "fa", "he"] as const;
|
|
330
|
+
|
|
331
|
+
export type TemplateLanguage = "en" | (typeof RTL_LANGUAGES)[number];
|
|
332
|
+
|
|
333
|
+
export const CURRENT_LANGUAGE: TemplateLanguage = "en";
|
|
334
|
+
|
|
335
|
+
const ui = {
|
|
336
|
+
en: {
|
|
337
|
+
metadataTitle: "bejamas/ui Astro project",
|
|
338
|
+
welcomeMessage: "Welcome to {project} Astro project.",
|
|
339
|
+
getStartedMessage: "Get started by editing src/pages/index.astro.",
|
|
340
|
+
readDocs: "Read docs",
|
|
341
|
+
getCustomDemo: "Get custom demo",
|
|
342
|
+
},
|
|
343
|
+
ar: {
|
|
344
|
+
metadataTitle: "مشروع Astro من bejamas/ui",
|
|
345
|
+
welcomeMessage: "مرحبًا بك في مشروع Astro الخاص بـ {project}.",
|
|
346
|
+
getStartedMessage: "ابدأ بتحرير src/pages/index.astro.",
|
|
347
|
+
readDocs: "اقرأ التوثيق",
|
|
348
|
+
getCustomDemo: "احصل على عرض توضيحي مخصص",
|
|
349
|
+
},
|
|
350
|
+
fa: {
|
|
351
|
+
metadataTitle: "پروژه Astro با bejamas/ui",
|
|
352
|
+
welcomeMessage: "به پروژه Astro با {project} خوش آمدید.",
|
|
353
|
+
getStartedMessage: "برای شروع src/pages/index.astro را ویرایش کنید.",
|
|
354
|
+
readDocs: "مطالعه مستندات",
|
|
355
|
+
getCustomDemo: "درخواست دموی سفارشی",
|
|
356
|
+
},
|
|
357
|
+
he: {
|
|
358
|
+
metadataTitle: "פרויקט Astro עם bejamas/ui",
|
|
359
|
+
welcomeMessage: "ברוכים הבאים לפרויקט Astro של {project}.",
|
|
360
|
+
getStartedMessage: "התחילו בעריכת src/pages/index.astro.",
|
|
361
|
+
readDocs: "קריאת התיעוד",
|
|
362
|
+
getCustomDemo: "קבלת דמו מותאם",
|
|
363
|
+
},
|
|
364
|
+
} as const satisfies Record<TemplateLanguage, Record<string, string>>;
|
|
365
|
+
|
|
366
|
+
export const appUi = {
|
|
367
|
+
lang: CURRENT_LANGUAGE,
|
|
368
|
+
dir: RTL_LANGUAGES.includes(
|
|
369
|
+
CURRENT_LANGUAGE as (typeof RTL_LANGUAGES)[number],
|
|
370
|
+
)
|
|
371
|
+
? "rtl"
|
|
372
|
+
: "ltr",
|
|
373
|
+
...ui[CURRENT_LANGUAGE],
|
|
374
|
+
};
|
|
375
|
+
`,
|
|
376
|
+
monorepo: `const RTL_LANGUAGES = ["ar", "fa", "he"] as const;
|
|
377
|
+
|
|
378
|
+
export type TemplateLanguage = "en" | (typeof RTL_LANGUAGES)[number];
|
|
379
|
+
|
|
380
|
+
export const CURRENT_LANGUAGE: TemplateLanguage = "en";
|
|
381
|
+
|
|
382
|
+
const ui = {
|
|
383
|
+
en: {
|
|
384
|
+
metadataTitle: "bejamas/ui Astro project",
|
|
385
|
+
welcomeMessage: "Welcome to {project} Astro monorepo.",
|
|
386
|
+
getStartedMessage: "Get started by editing apps/web/src/pages/index.astro.",
|
|
387
|
+
readDocs: "Read docs",
|
|
388
|
+
getCustomDemo: "Get custom demo",
|
|
389
|
+
},
|
|
390
|
+
ar: {
|
|
391
|
+
metadataTitle: "مشروع Astro من bejamas/ui",
|
|
392
|
+
welcomeMessage: "مرحبًا بك في مشروع Astro الأحادي من {project}.",
|
|
393
|
+
getStartedMessage: "ابدأ بتحرير apps/web/src/pages/index.astro.",
|
|
394
|
+
readDocs: "اقرأ التوثيق",
|
|
395
|
+
getCustomDemo: "احصل على عرض توضيحي مخصص",
|
|
396
|
+
},
|
|
397
|
+
fa: {
|
|
398
|
+
metadataTitle: "پروژه Astro با bejamas/ui",
|
|
399
|
+
welcomeMessage: "به مونوریپوی Astro با {project} خوش آمدید.",
|
|
400
|
+
getStartedMessage:
|
|
401
|
+
"برای شروع apps/web/src/pages/index.astro را ویرایش کنید.",
|
|
402
|
+
readDocs: "مطالعه مستندات",
|
|
403
|
+
getCustomDemo: "درخواست دموی سفارشی",
|
|
404
|
+
},
|
|
405
|
+
he: {
|
|
406
|
+
metadataTitle: "פרויקט Astro עם bejamas/ui",
|
|
407
|
+
welcomeMessage: "ברוכים הבאים למונורפו Astro של {project}.",
|
|
408
|
+
getStartedMessage: "התחילו בעריכת apps/web/src/pages/index.astro.",
|
|
409
|
+
readDocs: "קריאת התיעוד",
|
|
410
|
+
getCustomDemo: "קבלת דמו מותאם",
|
|
411
|
+
},
|
|
412
|
+
} as const satisfies Record<TemplateLanguage, Record<string, string>>;
|
|
413
|
+
|
|
414
|
+
export const appUi = {
|
|
415
|
+
lang: CURRENT_LANGUAGE,
|
|
416
|
+
dir: RTL_LANGUAGES.includes(
|
|
417
|
+
CURRENT_LANGUAGE as (typeof RTL_LANGUAGES)[number],
|
|
418
|
+
)
|
|
419
|
+
? "rtl"
|
|
420
|
+
: "ltr",
|
|
421
|
+
...ui[CURRENT_LANGUAGE],
|
|
422
|
+
};
|
|
423
|
+
`
|
|
424
|
+
};
|
|
425
|
+
const TEMPLATE_PAGE_VARIANTS = [
|
|
426
|
+
{
|
|
427
|
+
matchers: [
|
|
428
|
+
"import { Button } from \"@/ui/button\";",
|
|
429
|
+
"Astro project.",
|
|
430
|
+
"Get started by editing src/pages/index.astro."
|
|
431
|
+
],
|
|
432
|
+
i18nSource: `---
|
|
433
|
+
import Layout from "@/layouts/Layout.astro";
|
|
434
|
+
import { appUi } from "@/i18n/ui";
|
|
435
|
+
import { Button } from "@/ui/button";
|
|
436
|
+
|
|
437
|
+
const [welcomePrefix, welcomeSuffix = ""] =
|
|
438
|
+
appUi.welcomeMessage.split("{project}");
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
<Layout>
|
|
442
|
+
<div
|
|
443
|
+
class="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20"
|
|
444
|
+
>
|
|
445
|
+
<div class="flex flex-col gap-6 row-start-2 items-center sm:items-start">
|
|
446
|
+
<img src="/bejamas.svg" alt="bejamas/ui" class="w-8 dark:invert" />
|
|
447
|
+
<div class="space-y-2">
|
|
448
|
+
<p class="text-sm">
|
|
449
|
+
{welcomePrefix}<code
|
|
450
|
+
class="font-bold relative bg-muted rounded p-1"
|
|
451
|
+
>@bejamas/ui</code
|
|
452
|
+
>{welcomeSuffix}
|
|
453
|
+
</p>
|
|
454
|
+
<p class="text-sm">
|
|
455
|
+
{appUi.getStartedMessage}
|
|
456
|
+
</p>
|
|
457
|
+
</div>
|
|
458
|
+
<div class="flex gap-2">
|
|
459
|
+
<Button as="a" href="https://ui.bejamas.com/docs"
|
|
460
|
+
>{appUi.readDocs}</Button
|
|
461
|
+
>
|
|
462
|
+
<Button as="a" href="https://bejamas.com/get-in-touch" variant="outline"
|
|
463
|
+
>{appUi.getCustomDemo}</Button
|
|
464
|
+
>
|
|
465
|
+
</div>
|
|
466
|
+
</div>
|
|
467
|
+
</div>
|
|
468
|
+
</Layout>
|
|
469
|
+
`
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
matchers: [
|
|
473
|
+
"import { Button } from \"@repo/ui/components/button\";",
|
|
474
|
+
"width=\"32\"",
|
|
475
|
+
"Astro monorepo.",
|
|
476
|
+
"Get started by editing apps/web/src/pages/index.astro."
|
|
477
|
+
],
|
|
478
|
+
i18nSource: `---
|
|
479
|
+
import Layout from "@/layouts/Layout.astro";
|
|
480
|
+
import { appUi } from "@/i18n/ui";
|
|
481
|
+
import { Button } from "@repo/ui/components/button";
|
|
482
|
+
|
|
483
|
+
const [welcomePrefix, welcomeSuffix = ""] =
|
|
484
|
+
appUi.welcomeMessage.split("{project}");
|
|
485
|
+
---
|
|
486
|
+
|
|
487
|
+
<Layout>
|
|
488
|
+
<div
|
|
489
|
+
class="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20"
|
|
490
|
+
>
|
|
491
|
+
<div class="flex flex-col gap-6 row-start-2 items-center sm:items-start">
|
|
492
|
+
<img
|
|
493
|
+
src="/bejamas.svg"
|
|
494
|
+
alt="bejamas/ui"
|
|
495
|
+
class="w-8 dark:invert"
|
|
496
|
+
width="32"
|
|
497
|
+
height="32"
|
|
498
|
+
alt="bejamas/ui logo"
|
|
499
|
+
/>
|
|
500
|
+
<div class="space-y-2">
|
|
501
|
+
<p class="text-sm">
|
|
502
|
+
{welcomePrefix}<code
|
|
503
|
+
class="font-bold relative bg-muted rounded p-1"
|
|
504
|
+
>@bejamas/ui</code
|
|
505
|
+
>{welcomeSuffix}
|
|
506
|
+
</p>
|
|
507
|
+
<p class="text-sm">
|
|
508
|
+
{appUi.getStartedMessage}
|
|
509
|
+
</p>
|
|
510
|
+
</div>
|
|
511
|
+
<div class="flex gap-2">
|
|
512
|
+
<Button as="a" href="https://ui.bejamas.com/docs"
|
|
513
|
+
>{appUi.readDocs}</Button
|
|
514
|
+
>
|
|
515
|
+
<Button as="a" href="https://bejamas.com/get-in-touch" variant="outline"
|
|
516
|
+
>{appUi.getCustomDemo}</Button
|
|
517
|
+
>
|
|
518
|
+
</div>
|
|
519
|
+
</div>
|
|
520
|
+
</div>
|
|
521
|
+
</Layout>
|
|
522
|
+
`
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
matchers: [
|
|
526
|
+
"import { Button } from \"@repo/ui/components/button\";",
|
|
527
|
+
"<img src=\"/bejamas.svg\" alt=\"bejamas/ui\" class=\"w-8 dark:invert\" />",
|
|
528
|
+
"Astro monorepo.",
|
|
529
|
+
"Get started by editing apps/web/src/pages/index.astro."
|
|
530
|
+
],
|
|
531
|
+
i18nSource: `---
|
|
532
|
+
import Layout from "@/layouts/Layout.astro";
|
|
533
|
+
import { appUi } from "@/i18n/ui";
|
|
534
|
+
import { Button } from "@repo/ui/components/button";
|
|
535
|
+
|
|
536
|
+
const [welcomePrefix, welcomeSuffix = ""] =
|
|
537
|
+
appUi.welcomeMessage.split("{project}");
|
|
538
|
+
---
|
|
539
|
+
|
|
540
|
+
<Layout>
|
|
541
|
+
<div
|
|
542
|
+
class="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20"
|
|
543
|
+
>
|
|
544
|
+
<div class="flex flex-col gap-6 row-start-2 items-center sm:items-start">
|
|
545
|
+
<img src="/bejamas.svg" alt="bejamas/ui" class="w-8 dark:invert" />
|
|
546
|
+
<div class="space-y-2">
|
|
547
|
+
<p class="text-sm">
|
|
548
|
+
{welcomePrefix}<code
|
|
549
|
+
class="font-bold relative bg-muted rounded p-1"
|
|
550
|
+
>@bejamas/ui</code
|
|
551
|
+
>{welcomeSuffix}
|
|
552
|
+
</p>
|
|
553
|
+
<p class="text-sm">
|
|
554
|
+
{appUi.getStartedMessage}
|
|
555
|
+
</p>
|
|
556
|
+
</div>
|
|
557
|
+
<div class="flex gap-2">
|
|
558
|
+
<Button as="a" href="https://ui.bejamas.com/docs"
|
|
559
|
+
>{appUi.readDocs}</Button
|
|
560
|
+
>
|
|
561
|
+
<Button as="a" href="https://bejamas.com/get-in-touch" variant="outline"
|
|
562
|
+
>{appUi.getCustomDemo}</Button
|
|
563
|
+
>
|
|
564
|
+
</div>
|
|
565
|
+
</div>
|
|
566
|
+
</div>
|
|
567
|
+
</Layout>
|
|
568
|
+
`
|
|
569
|
+
}
|
|
570
|
+
];
|
|
571
|
+
function escapeRegExp(value) {
|
|
572
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
573
|
+
}
|
|
574
|
+
function compactCss(source) {
|
|
575
|
+
return source.replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
|
|
576
|
+
}
|
|
577
|
+
function compactSource(source) {
|
|
578
|
+
return source.replace(/\r\n/g, "\n").replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
|
|
579
|
+
}
|
|
580
|
+
function stripLegacyCreateBlock(source) {
|
|
581
|
+
const fullBlockPattern = new RegExp(`\\n*${escapeRegExp(CREATE_BLOCK_START)}[\\s\\S]*?${escapeRegExp(CREATE_BLOCK_END)}\\n*`, "m");
|
|
582
|
+
const danglingBlockPattern = new RegExp(`\\n*${escapeRegExp(CREATE_BLOCK_START)}[\\s\\S]*$`, "m");
|
|
583
|
+
return compactCss(source.replace(fullBlockPattern, "\n\n").replace(danglingBlockPattern, "\n\n"));
|
|
584
|
+
}
|
|
585
|
+
function buildCssVarBlock(selector, vars) {
|
|
586
|
+
return [
|
|
587
|
+
`${selector} {`,
|
|
588
|
+
...Object.entries(vars).map(([key, value]) => ` --${key}: ${value};`),
|
|
589
|
+
"}"
|
|
590
|
+
].join("\n");
|
|
591
|
+
}
|
|
592
|
+
function replaceTopLevelBlock(source, selector, nextBlock) {
|
|
593
|
+
const pattern = new RegExp(`^${escapeRegExp(selector)}\\s*\\{[\\s\\S]*?^\\}`, "m");
|
|
594
|
+
if (pattern.test(source)) return source.replace(pattern, nextBlock);
|
|
595
|
+
const layerIndex = source.indexOf("@layer base");
|
|
596
|
+
if (layerIndex !== -1) return `${source.slice(0, layerIndex).trimEnd()}\n\n${nextBlock}\n\n${source.slice(layerIndex).trimStart()}`;
|
|
597
|
+
return `${source.trimEnd()}\n\n${nextBlock}\n`;
|
|
598
|
+
}
|
|
599
|
+
function upsertImports(source, imports) {
|
|
600
|
+
const cleanedLines = source.split("\n").filter((line) => {
|
|
601
|
+
const trimmed = line.trim();
|
|
602
|
+
if (MANAGED_TAILWIND_IMPORTS.has(trimmed)) return false;
|
|
603
|
+
return !trimmed.startsWith("@import \"@fontsource-variable/");
|
|
604
|
+
});
|
|
605
|
+
let insertAt = -1;
|
|
606
|
+
for (let index = 0; index < cleanedLines.length; index += 1) {
|
|
607
|
+
if (cleanedLines[index].trim().startsWith("@import ")) {
|
|
608
|
+
insertAt = index;
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
if (insertAt !== -1 && cleanedLines[index].trim() !== "") break;
|
|
612
|
+
}
|
|
613
|
+
const uniqueImports = imports.filter((line, index) => imports.indexOf(line) === index);
|
|
614
|
+
if (insertAt === -1) return compactCss([
|
|
615
|
+
...uniqueImports,
|
|
616
|
+
"",
|
|
617
|
+
...cleanedLines
|
|
618
|
+
].join("\n"));
|
|
619
|
+
cleanedLines.splice(insertAt + 1, 0, ...uniqueImports);
|
|
620
|
+
return compactCss(cleanedLines.join("\n"));
|
|
621
|
+
}
|
|
622
|
+
function upsertManagedTailwindImport(source) {
|
|
623
|
+
const nextImport = resolveManagedTailwindImport(source);
|
|
624
|
+
const cleanedLines = source.split("\n").filter((line) => !MANAGED_TAILWIND_IMPORTS.has(line.trim()));
|
|
625
|
+
let insertAt = -1;
|
|
626
|
+
for (let index = 0; index < cleanedLines.length; index += 1) {
|
|
627
|
+
if (cleanedLines[index].trim().startsWith("@import ")) {
|
|
628
|
+
insertAt = index;
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
if (insertAt !== -1 && cleanedLines[index].trim() !== "") break;
|
|
632
|
+
}
|
|
633
|
+
if (insertAt === -1) return compactCss([
|
|
634
|
+
nextImport,
|
|
635
|
+
"",
|
|
636
|
+
...cleanedLines
|
|
637
|
+
].join("\n"));
|
|
638
|
+
cleanedLines.splice(insertAt + 1, 0, nextImport);
|
|
639
|
+
return compactCss(cleanedLines.join("\n"));
|
|
640
|
+
}
|
|
641
|
+
function resolveManagedTailwindImport(source) {
|
|
642
|
+
if (source.includes(SHADCN_TAILWIND_IMPORT)) return SHADCN_TAILWIND_IMPORT;
|
|
643
|
+
if (source.includes(BEJAMAS_TAILWIND_IMPORT)) return BEJAMAS_TAILWIND_IMPORT;
|
|
644
|
+
return BEJAMAS_TAILWIND_IMPORT;
|
|
645
|
+
}
|
|
646
|
+
function normalizeSourceForComparison(source) {
|
|
647
|
+
return source.replace(/\r\n/g, "\n").replace(/\s+/g, " ").trim();
|
|
648
|
+
}
|
|
649
|
+
function upsertFrontmatterImport(source, importLine) {
|
|
650
|
+
if (source.includes(importLine)) return source;
|
|
651
|
+
const frontmatterEnd = source.indexOf("\n---", 3);
|
|
652
|
+
if (source.startsWith("---\n") && frontmatterEnd !== -1) return `${source.slice(0, frontmatterEnd).trimEnd()}\n${importLine}${source.slice(frontmatterEnd)}`;
|
|
653
|
+
return `${importLine}\n${source}`;
|
|
654
|
+
}
|
|
655
|
+
function getTemplateI18nVariant(filepath) {
|
|
656
|
+
return filepath.includes(`${path.sep}apps${path.sep}web${path.sep}`) ? "monorepo" : "astro";
|
|
657
|
+
}
|
|
658
|
+
function buildTemplateI18nSource(variant, language) {
|
|
659
|
+
return compactSource(TEMPLATE_I18N_SOURCES[variant].replace("export const CURRENT_LANGUAGE: TemplateLanguage = \"en\";", `export const CURRENT_LANGUAGE: TemplateLanguage = "${language}";`));
|
|
660
|
+
}
|
|
661
|
+
function transformTemplateLayoutToI18n(source) {
|
|
662
|
+
if (source.includes(TEMPLATE_APP_UI_IMPORT)) return source;
|
|
663
|
+
let next = upsertFrontmatterImport(source, TEMPLATE_APP_UI_IMPORT);
|
|
664
|
+
next = next.replace(/<html([^>]*?)\slang=(\"[^\"]*\"|\{[^}]+\})([^>]*)>/, "<html$1$3>").replace(/<html([^>]*?)\sdir=(\"[^\"]*\"|\{[^}]+\})([^>]*)>/, "<html$1$3>");
|
|
665
|
+
next = next.replace(/<html([^>]*)>/, "<html$1 lang={appUi.lang} dir={appUi.dir}>");
|
|
666
|
+
next = next.replace(/<title>[\s\S]*?<\/title>/, "<title>{appUi.metadataTitle}</title>");
|
|
667
|
+
return compactSource(next);
|
|
668
|
+
}
|
|
669
|
+
function transformStarterPageToI18n(source) {
|
|
670
|
+
if (source.includes(TEMPLATE_APP_UI_IMPORT)) return source;
|
|
671
|
+
const normalized = normalizeSourceForComparison(source);
|
|
672
|
+
const match = TEMPLATE_PAGE_VARIANTS.find((variant) => variant.matchers.every((matcher) => normalized.includes(normalizeSourceForComparison(matcher))));
|
|
673
|
+
if (!match) return source;
|
|
674
|
+
return compactSource(match.i18nSource);
|
|
675
|
+
}
|
|
676
|
+
function upsertThemeInlineFont(source, fontVariable) {
|
|
677
|
+
const pattern = /@theme inline\s*\{[\s\S]*?\n\}/m;
|
|
678
|
+
if (!pattern.test(source)) {
|
|
679
|
+
const block = `@theme inline {\n${[fontVariable ? ` ${fontVariable}: var(${fontVariable});` : null, " --font-heading: var(--font-heading);"].filter(Boolean).join("\n")}\n}`;
|
|
680
|
+
const customVariantIndex = source.indexOf("@custom-variant");
|
|
681
|
+
if (customVariantIndex !== -1) {
|
|
682
|
+
const nextLineIndex = source.indexOf("\n", customVariantIndex);
|
|
683
|
+
return `${source.slice(0, nextLineIndex + 1).trimEnd()}\n\n${block}\n\n${source.slice(nextLineIndex + 1).trimStart()}`;
|
|
684
|
+
}
|
|
685
|
+
return `${block}\n\n${source.trimStart()}`;
|
|
686
|
+
}
|
|
687
|
+
return source.replace(pattern, (block) => {
|
|
688
|
+
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");
|
|
689
|
+
const declarations = [fontVariable ? ` ${fontVariable}: var(${fontVariable});` : null, " --font-heading: var(--font-heading);"].filter(Boolean).join("\n");
|
|
690
|
+
return withoutManagedFonts.replace("@theme inline {\n", `@theme inline {\n${declarations}\n`);
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
function upsertBaseLayerHtmlFont(source, fontClass) {
|
|
694
|
+
const pattern = /@layer base\s*\{[\s\S]*?\n\}/m;
|
|
695
|
+
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`;
|
|
696
|
+
return source.replace(pattern, (block) => {
|
|
697
|
+
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}`);
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
function transformDesignSystemCss(source, config, themeVars) {
|
|
701
|
+
const effectiveThemeVars = themeVars ?? buildRegistryTheme(config).cssVars;
|
|
702
|
+
const rootVars = {
|
|
703
|
+
...Object.fromEntries(Object.entries(effectiveThemeVars.theme ?? {}).filter(([key]) => key !== "bejamas-font-family")),
|
|
704
|
+
...effectiveThemeVars.light ?? {}
|
|
705
|
+
};
|
|
706
|
+
const darkVars = { ...effectiveThemeVars.dark ?? {} };
|
|
707
|
+
const font = getFontValue(config.font);
|
|
708
|
+
const tailwindImport = resolveManagedTailwindImport(source);
|
|
709
|
+
let next = stripLegacyCreateBlock(source);
|
|
710
|
+
next = upsertImports(next, [tailwindImport]);
|
|
711
|
+
next = replaceTopLevelBlock(next, ":root", buildCssVarBlock(":root", rootVars));
|
|
712
|
+
next = replaceTopLevelBlock(next, ".dark", buildCssVarBlock(".dark", darkVars));
|
|
713
|
+
next = upsertThemeInlineFont(next, font?.font.variable);
|
|
714
|
+
if (font) next = upsertBaseLayerHtmlFont(next, fontClassFromCssVariable(font.font.variable));
|
|
715
|
+
return compactCss(next);
|
|
716
|
+
}
|
|
717
|
+
function transformAstroManagedFontCss(source, fontVariable) {
|
|
718
|
+
const tailwindImport = resolveManagedTailwindImport(source);
|
|
719
|
+
let next = stripLegacyCreateBlock(source);
|
|
720
|
+
next = upsertImports(next, [tailwindImport]);
|
|
721
|
+
next = upsertThemeInlineFont(next, fontVariable);
|
|
722
|
+
if (fontVariable) next = upsertBaseLayerHtmlFont(next, fontClassFromCssVariable(fontVariable));
|
|
723
|
+
return compactCss(next);
|
|
724
|
+
}
|
|
725
|
+
function transformManagedTailwindImportCss(source) {
|
|
726
|
+
return upsertManagedTailwindImport(source);
|
|
727
|
+
}
|
|
728
|
+
async function patchComponentsJson(filepath, config) {
|
|
729
|
+
const current = await fs.readJson(filepath);
|
|
730
|
+
const { rsc: _rsc, tsx: _tsx,...normalizedCurrent } = current;
|
|
731
|
+
const next = {
|
|
732
|
+
...normalizedCurrent,
|
|
733
|
+
$schema: BEJAMAS_COMPONENTS_SCHEMA_URL,
|
|
734
|
+
style: getStyleId(config.style),
|
|
735
|
+
iconLibrary: config.iconLibrary,
|
|
736
|
+
rtl: config.rtl,
|
|
737
|
+
tailwind: {
|
|
738
|
+
...current.tailwind ?? {},
|
|
739
|
+
baseColor: config.baseColor,
|
|
740
|
+
cssVariables: true
|
|
741
|
+
}
|
|
742
|
+
};
|
|
743
|
+
await fs.writeJson(filepath, next, { spaces: 2 });
|
|
744
|
+
}
|
|
745
|
+
async function patchCssFileWithTheme(filepath, config, themeVars) {
|
|
746
|
+
const next = transformDesignSystemCss(await fs.readFile(filepath, "utf8"), config, themeVars);
|
|
747
|
+
await fs.writeFile(filepath, next, "utf8");
|
|
748
|
+
}
|
|
749
|
+
async function patchCssFileWithAstroFont(filepath, fontVariable) {
|
|
750
|
+
const next = transformAstroManagedFontCss(await fs.readFile(filepath, "utf8"), fontVariable);
|
|
751
|
+
await fs.writeFile(filepath, next, "utf8");
|
|
752
|
+
}
|
|
753
|
+
async function patchCssFileManagedTailwindImport(filepath) {
|
|
754
|
+
const next = transformManagedTailwindImportCss(await fs.readFile(filepath, "utf8"));
|
|
755
|
+
await fs.writeFile(filepath, next, "utf8");
|
|
756
|
+
}
|
|
757
|
+
async function syncManagedTailwindCss(projectPath) {
|
|
758
|
+
const componentJsonFiles = await fg("**/components.json", {
|
|
759
|
+
cwd: projectPath,
|
|
760
|
+
absolute: true,
|
|
761
|
+
ignore: ["**/node_modules/**"]
|
|
762
|
+
});
|
|
763
|
+
await Promise.all(componentJsonFiles.map(async (componentJsonPath) => {
|
|
764
|
+
const cssRelativePath = (await fs.readJson(componentJsonPath))?.tailwind?.css;
|
|
765
|
+
if (typeof cssRelativePath !== "string" || cssRelativePath.length === 0) return;
|
|
766
|
+
const cssPath = path.resolve(path.dirname(componentJsonPath), cssRelativePath);
|
|
767
|
+
if (!await fs.pathExists(cssPath)) return;
|
|
768
|
+
await patchCssFileManagedTailwindImport(cssPath);
|
|
769
|
+
}));
|
|
770
|
+
}
|
|
771
|
+
async function syncAstroManagedFontCss(projectPath, fontVariable) {
|
|
772
|
+
const componentJsonFiles = await fg("**/components.json", {
|
|
773
|
+
cwd: projectPath,
|
|
774
|
+
ignore: [
|
|
775
|
+
"**/node_modules/**",
|
|
776
|
+
"**/dist/**",
|
|
777
|
+
"**/.astro/**"
|
|
778
|
+
]
|
|
779
|
+
});
|
|
780
|
+
const cssFiles = /* @__PURE__ */ new Set();
|
|
781
|
+
for (const relativePath of componentJsonFiles) {
|
|
782
|
+
const absolutePath = path.resolve(projectPath, relativePath);
|
|
783
|
+
const cssPath = (await fs.readJson(absolutePath))?.tailwind?.css;
|
|
784
|
+
if (typeof cssPath === "string" && cssPath.length > 0) cssFiles.add(path.resolve(path.dirname(absolutePath), cssPath));
|
|
785
|
+
}
|
|
786
|
+
await Promise.all(Array.from(cssFiles).map((filepath) => patchCssFileWithAstroFont(filepath, fontVariable)));
|
|
787
|
+
}
|
|
788
|
+
async function patchTemplateI18nFile(filepath, config) {
|
|
789
|
+
if (!config.rtl) return;
|
|
790
|
+
const nextLanguage = getDocumentLanguage(config);
|
|
791
|
+
const nextSource = buildTemplateI18nSource(getTemplateI18nVariant(filepath), nextLanguage);
|
|
792
|
+
if (!await fs.pathExists(filepath)) {
|
|
793
|
+
await fs.ensureDir(path.dirname(filepath));
|
|
794
|
+
await fs.writeFile(filepath, nextSource, "utf8");
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
const current = await fs.readFile(filepath, "utf8");
|
|
798
|
+
const next = current.includes("export const CURRENT_LANGUAGE") ? current.replace(/export const CURRENT_LANGUAGE: TemplateLanguage = "[^"]+";/, `export const CURRENT_LANGUAGE: TemplateLanguage = "${nextLanguage}";`) : nextSource;
|
|
799
|
+
if (next !== current) await fs.writeFile(filepath, next, "utf8");
|
|
800
|
+
}
|
|
801
|
+
async function patchLayoutFile(filepath, config) {
|
|
802
|
+
if (!await fs.pathExists(filepath)) return;
|
|
803
|
+
const current = await fs.readFile(filepath, "utf8");
|
|
804
|
+
if (current.includes("from \"@/i18n/ui\"")) return;
|
|
805
|
+
if (config.rtl) {
|
|
806
|
+
const next$1 = transformTemplateLayoutToI18n(current);
|
|
807
|
+
if (next$1 !== current) await fs.writeFile(filepath, next$1, "utf8");
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
const nextLanguage = getDocumentLanguage(config);
|
|
811
|
+
const nextDirection = getDocumentDirection(config);
|
|
812
|
+
let next = current.replace(/<html([^>]*?)lang="[^"]*"([^>]*)>/, "<html$1$2>").replace(/<html([^>]*?)dir="[^"]*"([^>]*)>/, "<html$1$2>");
|
|
813
|
+
next = next.replace(/<html([^>]*)>/, `<html$1 lang="${nextLanguage}"${config.rtl ? ` dir="${nextDirection}"` : ""}>`);
|
|
814
|
+
if (next !== current) await fs.writeFile(filepath, next, "utf8");
|
|
815
|
+
}
|
|
816
|
+
async function patchStarterPageFile(filepath, config) {
|
|
817
|
+
if (!config.rtl || !await fs.pathExists(filepath)) return;
|
|
818
|
+
const current = await fs.readFile(filepath, "utf8");
|
|
819
|
+
const next = transformStarterPageToI18n(current);
|
|
820
|
+
if (next !== current) await fs.writeFile(filepath, next, "utf8");
|
|
821
|
+
}
|
|
822
|
+
async function applyDesignSystemToProject(projectPath, config, options = {}) {
|
|
823
|
+
const componentJsonFiles = await fg("**/components.json", {
|
|
824
|
+
cwd: projectPath,
|
|
825
|
+
ignore: [
|
|
826
|
+
"**/node_modules/**",
|
|
827
|
+
"**/dist/**",
|
|
828
|
+
"**/.astro/**"
|
|
829
|
+
]
|
|
830
|
+
});
|
|
831
|
+
const cssFiles = /* @__PURE__ */ new Set();
|
|
832
|
+
for (const relativePath of componentJsonFiles) {
|
|
833
|
+
const absolutePath = path.resolve(projectPath, relativePath);
|
|
834
|
+
await patchComponentsJson(absolutePath, config);
|
|
835
|
+
const cssPath = (await fs.readJson(absolutePath))?.tailwind?.css;
|
|
836
|
+
if (typeof cssPath === "string" && cssPath.length > 0) {
|
|
837
|
+
const absoluteCssPath = path.resolve(path.dirname(absolutePath), cssPath);
|
|
838
|
+
cssFiles.add(absoluteCssPath);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
await Promise.all(Array.from(cssFiles).map((filepath) => patchCssFileWithTheme(filepath, config, options.themeVars)));
|
|
842
|
+
await Promise.all([path.resolve(projectPath, "src/i18n/ui.ts"), path.resolve(projectPath, "apps/web/src/i18n/ui.ts")].map((filepath) => patchTemplateI18nFile(filepath, config)));
|
|
843
|
+
await Promise.all([path.resolve(projectPath, "src/layouts/Layout.astro"), path.resolve(projectPath, "apps/web/src/layouts/Layout.astro")].map((filepath) => patchLayoutFile(filepath, config)));
|
|
844
|
+
await Promise.all([path.resolve(projectPath, "src/pages/index.astro"), path.resolve(projectPath, "apps/web/src/pages/index.astro")].map((filepath) => patchStarterPageFile(filepath, config)));
|
|
845
|
+
const managedFonts = [toManagedAstroFont(config.font), config.fontHeading !== "inherit" ? toManagedAstroFont(`font-heading-${config.fontHeading}`) : null].filter((font) => font !== null);
|
|
846
|
+
const managedFont = managedFonts.find((font) => font.cssVariable !== "--font-heading");
|
|
847
|
+
if (managedFonts.length > 0 && managedFont) {
|
|
848
|
+
await syncAstroFontsInProject(projectPath, managedFonts, managedFont.cssVariable);
|
|
849
|
+
await cleanupAstroFontPackages(projectPath);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
//#endregion
|
|
854
|
+
//#region src/utils/icon-transform.ts
|
|
855
|
+
const FRONTMATTER_PATTERN = /^---\s*\n([\s\S]*?)\n---/;
|
|
856
|
+
function rewriteAstroIcons(content, iconLibrary) {
|
|
857
|
+
if (!(iconLibrary in ICON_LIBRARY_COLLECTIONS)) return content;
|
|
858
|
+
const library = iconLibrary;
|
|
859
|
+
const shouldRewriteLucideImports = library !== "lucide";
|
|
860
|
+
const frontmatterMatch = content.match(FRONTMATTER_PATTERN);
|
|
861
|
+
if (!frontmatterMatch) return content;
|
|
862
|
+
let frontmatter = frontmatterMatch[1];
|
|
863
|
+
let body = content.slice(frontmatterMatch[0].length);
|
|
864
|
+
const iconUsages = [];
|
|
865
|
+
const semanticIconImports = [];
|
|
866
|
+
if (shouldRewriteLucideImports) {
|
|
867
|
+
frontmatter = frontmatter.replace(/^\s*import\s+([A-Za-z0-9_$]+)\s+from\s+["']@lucide\/astro\/icons\/([^"']+)["'];?\s*$/gm, (full, localName, iconPath) => {
|
|
868
|
+
const semanticName = getSemanticIconNameFromLucidePath(iconPath);
|
|
869
|
+
if (!semanticName) return full;
|
|
870
|
+
iconUsages.push({
|
|
871
|
+
localName,
|
|
872
|
+
semanticName
|
|
873
|
+
});
|
|
874
|
+
return "";
|
|
875
|
+
});
|
|
876
|
+
frontmatter = frontmatter.replace(/import\s+\{([^}]*)\}\s+from\s+["']@lucide\/astro["'];?/g, (full, importsBlock) => {
|
|
877
|
+
const keptImports = [];
|
|
878
|
+
for (const rawImport of importsBlock.split(",")) {
|
|
879
|
+
const trimmedImport = rawImport.trim();
|
|
880
|
+
if (!trimmedImport) continue;
|
|
881
|
+
const aliasMatch = trimmedImport.match(/^([A-Za-z0-9_$]+)(?:\s+as\s+([A-Za-z0-9_$]+))?$/);
|
|
882
|
+
if (!aliasMatch) {
|
|
883
|
+
keptImports.push(trimmedImport);
|
|
884
|
+
continue;
|
|
885
|
+
}
|
|
886
|
+
const [, importedName, localAlias] = aliasMatch;
|
|
887
|
+
const semanticName = getSemanticIconNameFromLucideExport(importedName);
|
|
888
|
+
if (!semanticName) {
|
|
889
|
+
keptImports.push(trimmedImport);
|
|
890
|
+
continue;
|
|
891
|
+
}
|
|
892
|
+
iconUsages.push({
|
|
893
|
+
localName: localAlias ?? importedName,
|
|
894
|
+
semanticName
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
if (!keptImports.length) return "";
|
|
898
|
+
return `import { ${keptImports.join(", ")} } from "@lucide/astro";`;
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
frontmatter = frontmatter.replace(/^\s*import\s+([A-Za-z0-9_$]+)\s+from\s+["'][^"']*SemanticIcon\.astro["'];?\s*$/gm, (_full, localName) => {
|
|
902
|
+
semanticIconImports.push(localName);
|
|
903
|
+
return "";
|
|
904
|
+
});
|
|
905
|
+
if (!iconUsages.length && !semanticIconImports.length) return content;
|
|
906
|
+
for (const { localName, semanticName } of iconUsages.sort((left, right) => right.localName.length - left.localName.length)) {
|
|
907
|
+
const selfClosingPattern = new RegExp(`<${localName}(\\s[^>]*?)?\\s*/>`, "g");
|
|
908
|
+
const pairedPattern = new RegExp(`<${localName}(\\s[^>]*?)?>([\\s\\S]*?)<\\/${localName}>`, "g");
|
|
909
|
+
body = body.replace(selfClosingPattern, (_full, attributeString = "") => renderSemanticIconSvgWithAttributeString(library, semanticName, attributeString));
|
|
910
|
+
body = body.replace(pairedPattern, (_full, attributeString = "") => renderSemanticIconSvgWithAttributeString(library, semanticName, attributeString));
|
|
911
|
+
}
|
|
912
|
+
for (const localName of semanticIconImports.sort((left, right) => right.length - left.length)) {
|
|
913
|
+
const selfClosingPattern = new RegExp(`<${localName}(\\s[^>]*?)?\\s*/>`, "g");
|
|
914
|
+
const pairedPattern = new RegExp(`<${localName}(\\s[^>]*?)?>([\\s\\S]*?)<\\/${localName}>`, "g");
|
|
915
|
+
body = body.replace(selfClosingPattern, (full, attributeString = "") => renderSemanticIconUsage(full, library, attributeString));
|
|
916
|
+
body = body.replace(pairedPattern, (full, attributeString = "") => renderSemanticIconUsage(full, library, attributeString));
|
|
917
|
+
}
|
|
918
|
+
return `---\n${frontmatter.split("\n").filter((line, index, lines) => {
|
|
919
|
+
if (line.trim().length > 0) return true;
|
|
920
|
+
return lines[index - 1]?.trim().length > 0;
|
|
921
|
+
}).join("\n").trim()}\n---${body}`;
|
|
922
|
+
}
|
|
923
|
+
function renderSemanticIconUsage(original, library, attributeString = "") {
|
|
924
|
+
const semanticName = attributeString.match(/\sname=(["'])([^"']+)\1/)?.[2];
|
|
925
|
+
if (!semanticName || !SEMANTIC_ICON_NAMES.includes(semanticName)) return original;
|
|
926
|
+
return renderSemanticIconSvgWithAttributeString(library, semanticName, attributeString.replace(/\sname=(["'])[^"']+\1/g, "").replace(/\slibrary=(["'])[^"']+\1/g, "").replace(/\sdata-icon-library=(["'])[^"']+\1/g, "").trim());
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
//#endregion
|
|
930
|
+
//#region src/utils/menu-transform.ts
|
|
931
|
+
const TRANSLUCENT_CLASSES = [
|
|
932
|
+
"animate-none!",
|
|
933
|
+
"relative",
|
|
934
|
+
"bg-popover/70",
|
|
935
|
+
"before:pointer-events-none",
|
|
936
|
+
"before:absolute",
|
|
937
|
+
"before:inset-0",
|
|
938
|
+
"before:-z-1",
|
|
939
|
+
"before:rounded-[inherit]",
|
|
940
|
+
"before:backdrop-blur-2xl",
|
|
941
|
+
"before:backdrop-saturate-150",
|
|
942
|
+
"**:data-[slot$=-item]:focus:bg-foreground/10",
|
|
943
|
+
"**:data-[slot$=-item]:data-highlighted:bg-foreground/10",
|
|
944
|
+
"**:data-[slot$=-separator]:bg-foreground/5",
|
|
945
|
+
"**:data-[slot$=-trigger]:focus:bg-foreground/10",
|
|
946
|
+
"**:data-[slot$=-trigger]:aria-expanded:bg-foreground/10!",
|
|
947
|
+
"**:data-[variant=destructive]:focus:bg-foreground/10!",
|
|
948
|
+
"**:data-[variant=destructive]:text-accent-foreground!",
|
|
949
|
+
"**:data-[variant=destructive]:**:text-accent-foreground!"
|
|
950
|
+
];
|
|
951
|
+
function transformMenuTokens(tokens, menuColor) {
|
|
952
|
+
const transformed = [];
|
|
953
|
+
for (const token of tokens) {
|
|
954
|
+
if (token === "cn-menu-target") {
|
|
955
|
+
if (menuColor === "inverted") transformed.push("dark");
|
|
956
|
+
else if (menuColor === "default-translucent") transformed.push(...TRANSLUCENT_CLASSES);
|
|
957
|
+
else if (menuColor === "inverted-translucent") transformed.push("dark", ...TRANSLUCENT_CLASSES);
|
|
958
|
+
continue;
|
|
959
|
+
}
|
|
960
|
+
if (token === "cn-menu-translucent") {
|
|
961
|
+
if (menuColor === "default-translucent" || menuColor === "inverted-translucent") transformed.push(...TRANSLUCENT_CLASSES);
|
|
962
|
+
continue;
|
|
963
|
+
}
|
|
964
|
+
transformed.push(token);
|
|
965
|
+
}
|
|
966
|
+
return Array.from(new Set(transformed));
|
|
967
|
+
}
|
|
968
|
+
function rewriteAstroMenus(content, menuColor) {
|
|
969
|
+
if (!content.includes("cn-menu-target") && !content.includes("cn-menu-translucent")) return content;
|
|
970
|
+
return content.replace(/(["'])([^"']*\bcn-menu-(?:target|translucent)\b[^"']*)\1/g, (_match, quote, classes) => {
|
|
971
|
+
return `${quote}${transformMenuTokens(String(classes).split(/\s+/).filter(Boolean), menuColor).join(" ")}${quote}`;
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
//#endregion
|
|
976
|
+
//#region src/utils/astro-imports.ts
|
|
977
|
+
function updateImportAliases(moduleSpecifier, config, isRemote = false) {
|
|
978
|
+
if (!moduleSpecifier.startsWith("@/") && !isRemote) return moduleSpecifier;
|
|
979
|
+
let specifier = moduleSpecifier;
|
|
980
|
+
if (isRemote && specifier.startsWith("@/")) specifier = specifier.replace(/^@\//, `@/registry/${config.style || "bejamas-juno"}/`);
|
|
981
|
+
if (!specifier.startsWith("@/registry/")) {
|
|
982
|
+
const alias = config.aliases.components.split("/")[0];
|
|
983
|
+
return specifier.replace(/^@\//, `${alias}/`);
|
|
984
|
+
}
|
|
985
|
+
if (specifier.match(/^@\/registry\/(.+)\/ui/)) return specifier.replace(/^@\/registry\/(.+)\/ui/, config.aliases.ui ?? `${config.aliases.components}/ui`);
|
|
986
|
+
if (config.aliases.components && specifier.match(/^@\/registry\/(.+)\/components/)) return specifier.replace(/^@\/registry\/(.+)\/components/, config.aliases.components);
|
|
987
|
+
if (config.aliases.lib && specifier.match(/^@\/registry\/(.+)\/lib/)) return specifier.replace(/^@\/registry\/(.+)\/lib/, config.aliases.lib);
|
|
988
|
+
if (config.aliases.hooks && specifier.match(/^@\/registry\/(.+)\/hooks/)) return specifier.replace(/^@\/registry\/(.+)\/hooks/, config.aliases.hooks);
|
|
989
|
+
return specifier.replace(/^@\/registry\/[^/]+/, config.aliases.components);
|
|
990
|
+
}
|
|
991
|
+
function rewriteAstroImports(content, config) {
|
|
992
|
+
let updated = content;
|
|
993
|
+
const utilsAlias = config.aliases?.utils;
|
|
994
|
+
const utilsImport = `${typeof utilsAlias === "string" && utilsAlias.includes("/") ? utilsAlias.split("/")[0] : "@"}/lib/utils`;
|
|
995
|
+
updated = updated.replace(/import\s+([\s\S]*?)\s+from\s+["']([^"']+)["']/g, (full, importsPart, specifier) => {
|
|
996
|
+
const next = updateImportAliases(specifier, config, false);
|
|
997
|
+
let finalSpec = next;
|
|
998
|
+
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;
|
|
999
|
+
if (finalSpec === specifier) return full;
|
|
1000
|
+
return full.replace(specifier, finalSpec);
|
|
1001
|
+
});
|
|
1002
|
+
updated = updated.replace(/import\s+["']([^"']+)["']/g, (full, specifier) => {
|
|
1003
|
+
const next = updateImportAliases(specifier, config, false);
|
|
1004
|
+
if (next === specifier) return full;
|
|
1005
|
+
return full.replace(specifier, next);
|
|
1006
|
+
});
|
|
1007
|
+
updated = rewriteAstroMenus(updated, config.menuColor);
|
|
1008
|
+
return rewriteAstroIcons(updated, config.iconLibrary);
|
|
1009
|
+
}
|
|
1010
|
+
async function fixAstroImports(cwd, isVerbose) {
|
|
1011
|
+
const config = await getConfig(cwd);
|
|
1012
|
+
if (!config) return;
|
|
1013
|
+
const searchRoots = new Set([config.resolvedPaths.components, config.resolvedPaths.ui]);
|
|
1014
|
+
for (const root of Array.from(searchRoots)) {
|
|
1015
|
+
if (!root) continue;
|
|
1016
|
+
const astroFiles = await fg("**/*.astro", {
|
|
1017
|
+
cwd: root,
|
|
1018
|
+
absolute: true,
|
|
1019
|
+
dot: false
|
|
1020
|
+
});
|
|
1021
|
+
for (const filePath of astroFiles) {
|
|
1022
|
+
const original = await fs$1.readFile(filePath, "utf8");
|
|
1023
|
+
const rewritten = rewriteAstroImports(original, config);
|
|
1024
|
+
if (rewritten === original) continue;
|
|
1025
|
+
await fs$1.writeFile(filePath, rewritten, "utf8");
|
|
1026
|
+
if (isVerbose) logger.info(`[bejamas-ui] fixed imports in ${path.relative(cwd, filePath)}`);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
44
1029
|
}
|
|
45
1030
|
|
|
46
1031
|
//#endregion
|
|
@@ -154,7 +1139,39 @@ const TEMPLATES = {
|
|
|
154
1139
|
"astro-monorepo": "astro-monorepo",
|
|
155
1140
|
"astro-with-component-docs-monorepo": "astro-with-component-docs-monorepo"
|
|
156
1141
|
};
|
|
157
|
-
const
|
|
1142
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
1143
|
+
const __dirname = path$1.dirname(__filename);
|
|
1144
|
+
function resolveLocalTemplatesDir() {
|
|
1145
|
+
for (const relativePath of [
|
|
1146
|
+
"../../../../templates",
|
|
1147
|
+
"../../../templates",
|
|
1148
|
+
"../../templates"
|
|
1149
|
+
]) {
|
|
1150
|
+
const candidate = path$1.resolve(__dirname, relativePath);
|
|
1151
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
1152
|
+
}
|
|
1153
|
+
return path$1.resolve(__dirname, "../../../../templates");
|
|
1154
|
+
}
|
|
1155
|
+
const LOCAL_TEMPLATES_DIR = resolveLocalTemplatesDir();
|
|
1156
|
+
async function applyLocalPackageOverrides(projectPath) {
|
|
1157
|
+
const bejamasPackageOverride = process.env.BEJAMAS_PACKAGE_OVERRIDE;
|
|
1158
|
+
if (!bejamasPackageOverride) return;
|
|
1159
|
+
const packageJsonPaths = await fg("**/package.json", {
|
|
1160
|
+
cwd: projectPath,
|
|
1161
|
+
absolute: true,
|
|
1162
|
+
ignore: ["**/node_modules/**"]
|
|
1163
|
+
});
|
|
1164
|
+
const normalizedOverride = bejamasPackageOverride.replace(/\\/g, "/");
|
|
1165
|
+
await Promise.all(packageJsonPaths.map(async (packageJsonPath) => {
|
|
1166
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
1167
|
+
let changed = false;
|
|
1168
|
+
for (const field of ["dependencies", "devDependencies"]) if (packageJson[field]?.bejamas) {
|
|
1169
|
+
packageJson[field].bejamas = `file:${normalizedOverride}`;
|
|
1170
|
+
changed = true;
|
|
1171
|
+
}
|
|
1172
|
+
if (changed) await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
1173
|
+
}));
|
|
1174
|
+
}
|
|
158
1175
|
async function createProject(options) {
|
|
159
1176
|
options = {
|
|
160
1177
|
srcDir: false,
|
|
@@ -207,7 +1224,7 @@ async function createProject(options) {
|
|
|
207
1224
|
logger.break();
|
|
208
1225
|
process.exit(1);
|
|
209
1226
|
}
|
|
210
|
-
if (fs.existsSync(path.resolve(options.cwd, projectName, "package.json"))) {
|
|
1227
|
+
if (fs.existsSync(path$1.resolve(options.cwd, projectName, "package.json"))) {
|
|
211
1228
|
logger.break();
|
|
212
1229
|
logger.error(`A project with the name ${highlighter.info(projectName)} already exists.`);
|
|
213
1230
|
logger.error(`Please choose a different name and try again.`);
|
|
@@ -227,39 +1244,22 @@ async function createProject(options) {
|
|
|
227
1244
|
}
|
|
228
1245
|
async function createProjectFromTemplate(projectPath, options) {
|
|
229
1246
|
const createSpinner = spinner(`Creating a new project from template. This may take a few minutes.`).start();
|
|
230
|
-
const
|
|
231
|
-
astro: "
|
|
232
|
-
"astro-monorepo": "
|
|
233
|
-
"astro-with-component-docs-monorepo": "
|
|
1247
|
+
const TEMPLATE_DIRNAME = {
|
|
1248
|
+
astro: "astro",
|
|
1249
|
+
"astro-monorepo": "monorepo-astro",
|
|
1250
|
+
"astro-with-component-docs-monorepo": "monorepo-astro-with-docs"
|
|
234
1251
|
};
|
|
235
1252
|
try {
|
|
236
1253
|
dotenv.config({ quiet: true });
|
|
237
|
-
const templatePath = path.join(os.tmpdir(), `bejamas-template-${Date.now()}`);
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if (response.status === 404) throw new Error("Failed to download template: not found.");
|
|
247
|
-
throw new Error(`Failed to download template: ${response.status} ${response.statusText}`);
|
|
248
|
-
}
|
|
249
|
-
const tarPath = path.resolve(templatePath, "template.tar.gz");
|
|
250
|
-
await fs.writeFile(tarPath, Buffer.from(await response.arrayBuffer()));
|
|
251
|
-
const tarSubpath = TEMPLATE_TAR_SUBPATH[options.templateKey];
|
|
252
|
-
const leafName = tarSubpath.split("/").pop();
|
|
253
|
-
await execa("tar", [
|
|
254
|
-
"-xzf",
|
|
255
|
-
tarPath,
|
|
256
|
-
"-C",
|
|
257
|
-
templatePath,
|
|
258
|
-
"--strip-components=2",
|
|
259
|
-
tarSubpath
|
|
260
|
-
]);
|
|
261
|
-
const extractedPath = path.resolve(templatePath, leafName);
|
|
262
|
-
await fs.move(extractedPath, projectPath);
|
|
1254
|
+
const templatePath = path$1.join(os.tmpdir(), `bejamas-template-${Date.now()}`);
|
|
1255
|
+
const templateSource = path$1.resolve(LOCAL_TEMPLATES_DIR, TEMPLATE_DIRNAME[options.templateKey]);
|
|
1256
|
+
if (!await fs.pathExists(templateSource)) throw new Error(`Local template not found: ${templateSource}`);
|
|
1257
|
+
await fs.copy(templateSource, projectPath, { filter: (source) => {
|
|
1258
|
+
const basename = path$1.basename(source);
|
|
1259
|
+
return basename !== "node_modules" && basename !== ".astro";
|
|
1260
|
+
} });
|
|
1261
|
+
await removeEmptyTemplateI18nDirs(projectPath);
|
|
1262
|
+
await applyLocalPackageOverrides(projectPath);
|
|
263
1263
|
await fs.remove(templatePath);
|
|
264
1264
|
await execa(options.packageManager, ["install"], { cwd: projectPath });
|
|
265
1265
|
try {
|
|
@@ -273,51 +1273,295 @@ async function createProjectFromTemplate(projectPath, options) {
|
|
|
273
1273
|
"Initial commit"
|
|
274
1274
|
], { cwd: projectPath });
|
|
275
1275
|
}
|
|
276
|
-
} catch (_) {}
|
|
277
|
-
createSpinner?.succeed("Creating a new project from template.");
|
|
278
|
-
} catch (error) {
|
|
279
|
-
createSpinner?.fail("Something went wrong creating a new project from template.");
|
|
280
|
-
handleError(error);
|
|
1276
|
+
} catch (_) {}
|
|
1277
|
+
createSpinner?.succeed("Creating a new project from template.");
|
|
1278
|
+
} catch (error) {
|
|
1279
|
+
createSpinner?.fail("Something went wrong creating a new project from template.");
|
|
1280
|
+
handleError(error);
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
async function removeEmptyTemplateI18nDirs(projectPath) {
|
|
1284
|
+
const candidates = [path$1.resolve(projectPath, "src/i18n"), path$1.resolve(projectPath, "apps/web/src/i18n")];
|
|
1285
|
+
await Promise.all(candidates.map(async (candidate) => {
|
|
1286
|
+
if (!await fs.pathExists(candidate)) return;
|
|
1287
|
+
if ((await fs.readdir(candidate)).length === 0) await fs.remove(candidate);
|
|
1288
|
+
}));
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
//#endregion
|
|
1292
|
+
//#region src/utils/installed-ui-components.ts
|
|
1293
|
+
function toRegistryName(filename) {
|
|
1294
|
+
return filename.replace(/\.[^.]+$/, "").replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
1295
|
+
}
|
|
1296
|
+
async function pathExists$1(filepath) {
|
|
1297
|
+
try {
|
|
1298
|
+
await promises.access(filepath);
|
|
1299
|
+
return true;
|
|
1300
|
+
} catch {
|
|
1301
|
+
return false;
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
async function detectInstalledUiComponents(uiDir) {
|
|
1305
|
+
if (!await pathExists$1(uiDir)) return [];
|
|
1306
|
+
const entries = await promises.readdir(uiDir, { withFileTypes: true });
|
|
1307
|
+
const components = /* @__PURE__ */ new Set();
|
|
1308
|
+
for (const entry of entries) {
|
|
1309
|
+
if (entry.name.startsWith(".")) continue;
|
|
1310
|
+
const entryPath = path.resolve(uiDir, entry.name);
|
|
1311
|
+
if (entry.isDirectory()) {
|
|
1312
|
+
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);
|
|
1313
|
+
continue;
|
|
1314
|
+
}
|
|
1315
|
+
if (entry.isFile() && /\.(astro|tsx|jsx)$/.test(entry.name) && !entry.name.startsWith("index.")) components.add(toRegistryName(entry.name));
|
|
1316
|
+
}
|
|
1317
|
+
return Array.from(components).sort();
|
|
1318
|
+
}
|
|
1319
|
+
async function getInstalledUiComponents(cwd) {
|
|
1320
|
+
const config = await getConfig(cwd);
|
|
1321
|
+
if (!config) return [];
|
|
1322
|
+
return detectInstalledUiComponents(config.resolvedPaths.ui);
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
//#endregion
|
|
1326
|
+
//#region src/utils/reorganize-components.ts
|
|
1327
|
+
async function fetchRegistryItem(componentName, registryUrl, style = "bejamas-juno") {
|
|
1328
|
+
const url = `${registryUrl}/styles/${style}/${componentName}.json`;
|
|
1329
|
+
try {
|
|
1330
|
+
const response = await fetch(url);
|
|
1331
|
+
if (!response.ok) {
|
|
1332
|
+
const fallbackUrl = `${registryUrl}/${componentName}.json`;
|
|
1333
|
+
const fallbackResponse = await fetch(fallbackUrl);
|
|
1334
|
+
if (!fallbackResponse.ok) return null;
|
|
1335
|
+
return await fallbackResponse.json();
|
|
1336
|
+
}
|
|
1337
|
+
return await response.json();
|
|
1338
|
+
} catch {
|
|
1339
|
+
return null;
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
function getSubfolderFromPaths(files) {
|
|
1343
|
+
if (!files || files.length === 0) return null;
|
|
1344
|
+
const uiFiles = files.filter((file) => file.type === "registry:ui");
|
|
1345
|
+
if (uiFiles.length < 2) return null;
|
|
1346
|
+
const subfolders = /* @__PURE__ */ new Set();
|
|
1347
|
+
for (const file of uiFiles) {
|
|
1348
|
+
const parts = file.path.split("/");
|
|
1349
|
+
const uiIndex = parts.indexOf("ui");
|
|
1350
|
+
if (uiIndex !== -1 && parts.length > uiIndex + 2) subfolders.add(parts[uiIndex + 1]);
|
|
1351
|
+
}
|
|
1352
|
+
if (subfolders.size === 1) return Array.from(subfolders)[0];
|
|
1353
|
+
if (uiFiles.length > 0) {
|
|
1354
|
+
const dirname$1 = path.dirname(uiFiles[0].path);
|
|
1355
|
+
const folderName = path.basename(dirname$1);
|
|
1356
|
+
if (folderName && folderName !== "ui") return folderName;
|
|
1357
|
+
}
|
|
1358
|
+
return null;
|
|
1359
|
+
}
|
|
1360
|
+
async function pathExists(filePath) {
|
|
1361
|
+
try {
|
|
1362
|
+
await fs$1.access(filePath);
|
|
1363
|
+
return true;
|
|
1364
|
+
} catch {
|
|
1365
|
+
return false;
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
function resolveShadcnUiRelativePath(filePath, uiDir) {
|
|
1369
|
+
const normalizedFilePath = filePath.replace(/^\/|\/$/g, "");
|
|
1370
|
+
const lastTargetSegment = path.basename(uiDir.replace(/^\/|\/$/g, ""));
|
|
1371
|
+
if (!lastTargetSegment) return path.basename(normalizedFilePath);
|
|
1372
|
+
const fileSegments = normalizedFilePath.split("/");
|
|
1373
|
+
const commonDirIndex = fileSegments.findIndex((segment) => segment === lastTargetSegment);
|
|
1374
|
+
if (commonDirIndex === -1) return fileSegments[fileSegments.length - 1];
|
|
1375
|
+
return fileSegments.slice(commonDirIndex + 1).join("/");
|
|
1376
|
+
}
|
|
1377
|
+
/**
|
|
1378
|
+
* Current upstream shadcn workspace installs flatten `ui/foo/Bar.astro` when
|
|
1379
|
+
* the resolved ui target ends in a segment like `components` instead of `ui`.
|
|
1380
|
+
* We keep reorganization only for that compatibility case.
|
|
1381
|
+
*/
|
|
1382
|
+
function shouldReorganizeRegistryUiFiles(files, uiDir) {
|
|
1383
|
+
if (!uiDir) return false;
|
|
1384
|
+
if (!getSubfolderFromPaths(files)) return false;
|
|
1385
|
+
return files.filter((file) => file.type === "registry:ui").some((file) => {
|
|
1386
|
+
return !resolveShadcnUiRelativePath(file.path, uiDir).includes("/");
|
|
1387
|
+
});
|
|
1388
|
+
}
|
|
1389
|
+
/**
|
|
1390
|
+
* Reorganizes one multi-file registry:ui item from flat output into its
|
|
1391
|
+
* expected subfolder. This is the filesystem-level compatibility shim.
|
|
1392
|
+
*/
|
|
1393
|
+
async function reorganizeRegistryUiFiles(files, uiDir, verbose, overwriteExisting = false) {
|
|
1394
|
+
const result = {
|
|
1395
|
+
totalMoved: 0,
|
|
1396
|
+
movedFiles: [],
|
|
1397
|
+
skippedFiles: []
|
|
1398
|
+
};
|
|
1399
|
+
if (!uiDir || !files || files.length === 0) return result;
|
|
1400
|
+
const subfolder = getSubfolderFromPaths(files);
|
|
1401
|
+
if (!subfolder) return result;
|
|
1402
|
+
const uiFiles = files.filter((file) => file.type === "registry:ui");
|
|
1403
|
+
const targetDir = path.join(uiDir, subfolder);
|
|
1404
|
+
for (const file of uiFiles) {
|
|
1405
|
+
const filename = path.basename(file.path);
|
|
1406
|
+
const flatPath = path.join(uiDir, filename);
|
|
1407
|
+
const targetPath = path.join(targetDir, filename);
|
|
1408
|
+
if (!await pathExists(flatPath)) continue;
|
|
1409
|
+
if (await pathExists(targetPath)) {
|
|
1410
|
+
if (overwriteExisting) {
|
|
1411
|
+
await fs$1.mkdir(targetDir, { recursive: true });
|
|
1412
|
+
await fs$1.unlink(targetPath);
|
|
1413
|
+
await fs$1.rename(flatPath, targetPath);
|
|
1414
|
+
result.totalMoved++;
|
|
1415
|
+
result.movedFiles.push(`${subfolder}/${filename}`);
|
|
1416
|
+
if (verbose) logger.info(`[bejamas-ui] Replaced ${subfolder}/${filename} with the reinstalled version`);
|
|
1417
|
+
continue;
|
|
1418
|
+
}
|
|
1419
|
+
try {
|
|
1420
|
+
await fs$1.unlink(flatPath);
|
|
1421
|
+
result.skippedFiles.push(`${subfolder}/${filename}`);
|
|
1422
|
+
if (verbose) logger.info(`[bejamas-ui] Removed flat duplicate: ${filename} (${subfolder}/${filename} exists)`);
|
|
1423
|
+
} catch {
|
|
1424
|
+
result.skippedFiles.push(`${subfolder}/${filename}`);
|
|
1425
|
+
}
|
|
1426
|
+
continue;
|
|
1427
|
+
}
|
|
1428
|
+
await fs$1.mkdir(targetDir, { recursive: true });
|
|
1429
|
+
await fs$1.rename(flatPath, targetPath);
|
|
1430
|
+
result.totalMoved++;
|
|
1431
|
+
result.movedFiles.push(`${subfolder}/${filename}`);
|
|
1432
|
+
if (verbose) logger.info(`[bejamas-ui] Moved ${filename} -> ${subfolder}/${filename}`);
|
|
1433
|
+
}
|
|
1434
|
+
return result;
|
|
1435
|
+
}
|
|
1436
|
+
async function reorganizeComponents(components, uiDir, registryUrl, verbose, style = "bejamas-juno", overwriteExisting = false) {
|
|
1437
|
+
const result = {
|
|
1438
|
+
totalMoved: 0,
|
|
1439
|
+
movedFiles: [],
|
|
1440
|
+
skippedFiles: []
|
|
1441
|
+
};
|
|
1442
|
+
if (!uiDir || components.length === 0) return result;
|
|
1443
|
+
for (const componentName of components) try {
|
|
1444
|
+
const registryItem = await fetchRegistryItem(componentName, registryUrl, style);
|
|
1445
|
+
if (!registryItem) {
|
|
1446
|
+
if (verbose) logger.info(`[bejamas-ui] Could not fetch registry for ${componentName}, skipping reorganization`);
|
|
1447
|
+
continue;
|
|
1448
|
+
}
|
|
1449
|
+
if (!shouldReorganizeRegistryUiFiles(registryItem.files, uiDir)) continue;
|
|
1450
|
+
const componentResult = await reorganizeRegistryUiFiles(registryItem.files, uiDir, verbose, overwriteExisting);
|
|
1451
|
+
result.totalMoved += componentResult.totalMoved;
|
|
1452
|
+
result.movedFiles.push(...componentResult.movedFiles);
|
|
1453
|
+
result.skippedFiles.push(...componentResult.skippedFiles);
|
|
1454
|
+
if (componentResult.totalMoved > 0 && verbose) {
|
|
1455
|
+
const subfolder = getSubfolderFromPaths(registryItem.files);
|
|
1456
|
+
logger.info(`[bejamas-ui] Reorganized ${componentName} into ${subfolder}/`);
|
|
1457
|
+
}
|
|
1458
|
+
} catch (err) {
|
|
1459
|
+
if (verbose) logger.warn(`[bejamas-ui] Failed to reorganize ${componentName}: ${err}`);
|
|
281
1460
|
}
|
|
1461
|
+
return result;
|
|
282
1462
|
}
|
|
283
1463
|
|
|
284
1464
|
//#endregion
|
|
285
1465
|
//#region src/utils/shadcn-cli.ts
|
|
286
|
-
const
|
|
287
|
-
const PINNED_SHADCN_VERSION = "3.8.5";
|
|
1466
|
+
const PINNED_SHADCN_VERSION = "4.1.1";
|
|
288
1467
|
const PINNED_SHADCN_PACKAGE = `shadcn@${PINNED_SHADCN_VERSION}`;
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
return null;
|
|
294
|
-
}
|
|
1468
|
+
const PINNED_SHADCN_EXEC_PREFIX = path.join(os$1.tmpdir(), "bejamas-shadcn", PINNED_SHADCN_VERSION);
|
|
1469
|
+
async function ensurePinnedShadcnExecPrefix() {
|
|
1470
|
+
await fs$1.mkdir(PINNED_SHADCN_EXEC_PREFIX, { recursive: true });
|
|
1471
|
+
return PINNED_SHADCN_EXEC_PREFIX;
|
|
295
1472
|
}
|
|
296
|
-
function
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
source: "bundled"
|
|
301
|
-
};
|
|
1473
|
+
function getNpmExecutable() {
|
|
1474
|
+
return process.platform === "win32" ? "npm.cmd" : "npm";
|
|
1475
|
+
}
|
|
1476
|
+
function buildPinnedShadcnInvocation(shadcnArgs) {
|
|
302
1477
|
return {
|
|
303
|
-
cmd:
|
|
1478
|
+
cmd: getNpmExecutable(),
|
|
304
1479
|
args: [
|
|
305
|
-
"
|
|
306
|
-
|
|
1480
|
+
"exec",
|
|
1481
|
+
"--yes",
|
|
1482
|
+
"--prefix",
|
|
1483
|
+
PINNED_SHADCN_EXEC_PREFIX,
|
|
1484
|
+
`--package=${PINNED_SHADCN_PACKAGE}`,
|
|
1485
|
+
"--",
|
|
1486
|
+
"shadcn",
|
|
307
1487
|
...shadcnArgs
|
|
308
1488
|
],
|
|
309
|
-
source: "
|
|
1489
|
+
source: "isolated"
|
|
310
1490
|
};
|
|
311
1491
|
}
|
|
312
1492
|
|
|
1493
|
+
//#endregion
|
|
1494
|
+
//#region src/utils/shadcn-command.ts
|
|
1495
|
+
function extractPassthroughArgs(rawArgv, commandName) {
|
|
1496
|
+
const commandIndex = rawArgv.findIndex((arg) => arg === commandName);
|
|
1497
|
+
if (commandIndex === -1) return [];
|
|
1498
|
+
const rest = rawArgv.slice(commandIndex + 1);
|
|
1499
|
+
const doubleDashIndex = rest.indexOf("--");
|
|
1500
|
+
if (doubleDashIndex === -1) return [];
|
|
1501
|
+
return rest.slice(doubleDashIndex + 1);
|
|
1502
|
+
}
|
|
1503
|
+
async function runShadcnCommand({ cwd, args, env }) {
|
|
1504
|
+
await ensurePinnedShadcnExecPrefix();
|
|
1505
|
+
const invocation = buildPinnedShadcnInvocation(args);
|
|
1506
|
+
await execa(invocation.cmd, invocation.args, {
|
|
1507
|
+
cwd,
|
|
1508
|
+
env: {
|
|
1509
|
+
...process.env,
|
|
1510
|
+
...env
|
|
1511
|
+
},
|
|
1512
|
+
stdio: "inherit"
|
|
1513
|
+
});
|
|
1514
|
+
}
|
|
1515
|
+
|
|
313
1516
|
//#endregion
|
|
314
1517
|
//#region src/commands/init.ts
|
|
315
|
-
|
|
316
|
-
const
|
|
1518
|
+
function resolveDesignSystemConfig(options) {
|
|
1519
|
+
const rtlLanguage = options.rtl && options.lang ? options.lang : DEFAULT_DESIGN_SYSTEM_CONFIG.rtlLanguage;
|
|
1520
|
+
if (options.preset && isPresetCode(options.preset)) {
|
|
1521
|
+
const decoded = decodePreset(options.preset);
|
|
1522
|
+
if (decoded) return normalizeDesignSystemConfig(designSystemConfigSchema.parse({
|
|
1523
|
+
...DEFAULT_DESIGN_SYSTEM_CONFIG,
|
|
1524
|
+
...decoded,
|
|
1525
|
+
template: options.template ?? DEFAULT_DESIGN_SYSTEM_CONFIG.template,
|
|
1526
|
+
rtl: options.rtl ?? false,
|
|
1527
|
+
rtlLanguage
|
|
1528
|
+
}));
|
|
1529
|
+
}
|
|
1530
|
+
const baseColor = options.baseColor ?? DEFAULT_DESIGN_SYSTEM_CONFIG.baseColor;
|
|
1531
|
+
return normalizeDesignSystemConfig(designSystemConfigSchema.parse({
|
|
1532
|
+
...DEFAULT_DESIGN_SYSTEM_CONFIG,
|
|
1533
|
+
template: options.template ?? DEFAULT_DESIGN_SYSTEM_CONFIG.template,
|
|
1534
|
+
rtl: options.rtl ?? false,
|
|
1535
|
+
rtlLanguage,
|
|
1536
|
+
baseColor,
|
|
1537
|
+
theme: baseColor === DEFAULT_DESIGN_SYSTEM_CONFIG.baseColor ? DEFAULT_DESIGN_SYSTEM_CONFIG.theme : baseColor
|
|
1538
|
+
}));
|
|
1539
|
+
}
|
|
1540
|
+
function buildInitUrl(config, themeRef, env = process.env) {
|
|
1541
|
+
const params = new URLSearchParams({
|
|
1542
|
+
preset: encodePreset(config),
|
|
1543
|
+
template: config.template
|
|
1544
|
+
});
|
|
1545
|
+
if (config.rtl) {
|
|
1546
|
+
params.set("rtl", "true");
|
|
1547
|
+
params.set("lang", config.rtlLanguage);
|
|
1548
|
+
}
|
|
1549
|
+
if (themeRef) params.set("themeRef", themeRef);
|
|
1550
|
+
return `${buildUiUrl("/init", env)}?${params.toString()}`;
|
|
1551
|
+
}
|
|
1552
|
+
function buildThemeVarsFromRegistryItem(item) {
|
|
1553
|
+
return item?.cssVars ?? null;
|
|
1554
|
+
}
|
|
1555
|
+
async function fetchInitThemeVars(initUrl) {
|
|
1556
|
+
const response = await fetch(initUrl);
|
|
1557
|
+
if (!response.ok) return null;
|
|
1558
|
+
return buildThemeVarsFromRegistryItem(await response.json());
|
|
1559
|
+
}
|
|
317
1560
|
const initOptionsSchema = z.object({
|
|
318
1561
|
cwd: z.string(),
|
|
319
1562
|
components: z.array(z.string()).optional(),
|
|
320
1563
|
yes: z.boolean(),
|
|
1564
|
+
reinstall: z.boolean().optional(),
|
|
321
1565
|
defaults: z.boolean(),
|
|
322
1566
|
force: z.boolean(),
|
|
323
1567
|
silent: z.boolean(),
|
|
@@ -327,20 +1571,98 @@ const initOptionsSchema = z.object({
|
|
|
327
1571
|
template: z.string().optional().refine((val) => {
|
|
328
1572
|
if (val) return TEMPLATES[val];
|
|
329
1573
|
return true;
|
|
330
|
-
}, { message: "Invalid template. Please use '
|
|
331
|
-
|
|
332
|
-
|
|
1574
|
+
}, { message: "Invalid template. Please use 'astro', 'astro-monorepo', or 'astro-with-component-docs-monorepo'." }),
|
|
1575
|
+
preset: z.string().optional(),
|
|
1576
|
+
baseColor: z.string().optional().refine((val) => {
|
|
1577
|
+
if (val) return BASE_COLORS.find((color) => color.name === val);
|
|
1578
|
+
return true;
|
|
1579
|
+
}, { message: `Invalid base color. Please use '${BASE_COLORS.map((color) => color.name).join("', '")}'` }),
|
|
1580
|
+
baseStyle: z.boolean(),
|
|
1581
|
+
rtl: z.boolean().default(false),
|
|
1582
|
+
lang: z.enum(RTL_LANGUAGE_VALUES).optional(),
|
|
1583
|
+
themeRef: z.string().optional()
|
|
333
1584
|
});
|
|
334
|
-
function
|
|
1585
|
+
function shouldReinstallExistingComponents(options) {
|
|
1586
|
+
return options.reinstall ?? Boolean(options.preset);
|
|
1587
|
+
}
|
|
1588
|
+
function ensureShadcnReinstallFlag(forwardedOptions, shouldReinstall) {
|
|
1589
|
+
if (!shouldReinstall || forwardedOptions.includes("--reinstall") || forwardedOptions.includes("--no-reinstall")) return forwardedOptions;
|
|
1590
|
+
const passthroughIndex = forwardedOptions.indexOf("--");
|
|
1591
|
+
if (passthroughIndex === -1) return [...forwardedOptions, "--reinstall"];
|
|
335
1592
|
return [
|
|
336
|
-
|
|
337
|
-
"--
|
|
338
|
-
|
|
1593
|
+
...forwardedOptions.slice(0, passthroughIndex),
|
|
1594
|
+
"--reinstall",
|
|
1595
|
+
...forwardedOptions.slice(passthroughIndex)
|
|
339
1596
|
];
|
|
340
1597
|
}
|
|
341
|
-
|
|
1598
|
+
function extractOptionsForShadcnInit(rawArgv, cmd) {
|
|
1599
|
+
if (typeof cmd.getOptionValueSource === "function") {
|
|
1600
|
+
const opts = cmd.optsWithGlobals();
|
|
1601
|
+
const forwarded$1 = [];
|
|
1602
|
+
const getSource = (key) => cmd.getOptionValueSource(key);
|
|
1603
|
+
const addBoolean = (key, flag, negateFlag) => {
|
|
1604
|
+
if (getSource(key) !== "cli") return;
|
|
1605
|
+
const value = opts[key];
|
|
1606
|
+
if (typeof value !== "boolean") return;
|
|
1607
|
+
if (value) forwarded$1.push(flag);
|
|
1608
|
+
else if (negateFlag) forwarded$1.push(negateFlag);
|
|
1609
|
+
};
|
|
1610
|
+
addBoolean("yes", "--yes");
|
|
1611
|
+
addBoolean("force", "--force");
|
|
1612
|
+
addBoolean("silent", "--silent");
|
|
1613
|
+
addBoolean("reinstall", "--reinstall", "--no-reinstall");
|
|
1614
|
+
const initIndex$1 = rawArgv.findIndex((arg) => arg === "init");
|
|
1615
|
+
if (initIndex$1 !== -1) {
|
|
1616
|
+
const rest$1 = rawArgv.slice(initIndex$1 + 1);
|
|
1617
|
+
const doubleDashIndex = rest$1.indexOf("--");
|
|
1618
|
+
if (doubleDashIndex !== -1) forwarded$1.push(...rest$1.slice(doubleDashIndex));
|
|
1619
|
+
}
|
|
1620
|
+
return forwarded$1;
|
|
1621
|
+
}
|
|
1622
|
+
const initIndex = rawArgv.findIndex((arg) => arg === "init");
|
|
1623
|
+
if (initIndex === -1) return [];
|
|
1624
|
+
const rest = rawArgv.slice(initIndex + 1);
|
|
1625
|
+
const forwarded = [];
|
|
1626
|
+
const filteredFlags = new Set([
|
|
1627
|
+
"-v",
|
|
1628
|
+
"--verbose",
|
|
1629
|
+
"-t",
|
|
1630
|
+
"--template",
|
|
1631
|
+
"-b",
|
|
1632
|
+
"--base-color",
|
|
1633
|
+
"-p",
|
|
1634
|
+
"--preset",
|
|
1635
|
+
"--theme-ref",
|
|
1636
|
+
"-c",
|
|
1637
|
+
"--cwd",
|
|
1638
|
+
"-d",
|
|
1639
|
+
"--defaults",
|
|
1640
|
+
"--src-dir",
|
|
1641
|
+
"--no-src-dir",
|
|
1642
|
+
"--css-variables",
|
|
1643
|
+
"--no-css-variables",
|
|
1644
|
+
"--no-base-style",
|
|
1645
|
+
"--rtl",
|
|
1646
|
+
"--lang"
|
|
1647
|
+
]);
|
|
1648
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
1649
|
+
const token = rest[index];
|
|
1650
|
+
if (token === "--") {
|
|
1651
|
+
forwarded.push("--", ...rest.slice(index + 1));
|
|
1652
|
+
break;
|
|
1653
|
+
}
|
|
1654
|
+
if (!token.startsWith("-")) continue;
|
|
1655
|
+
if (filteredFlags.has(token)) continue;
|
|
1656
|
+
forwarded.push(token);
|
|
1657
|
+
}
|
|
1658
|
+
return forwarded;
|
|
1659
|
+
}
|
|
1660
|
+
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) => {
|
|
342
1661
|
try {
|
|
343
|
-
await runInit(
|
|
1662
|
+
await runInit({
|
|
1663
|
+
...opts,
|
|
1664
|
+
forwardedOptions: extractOptionsForShadcnInit(process.argv.slice(2), cmd)
|
|
1665
|
+
});
|
|
344
1666
|
} catch (error) {
|
|
345
1667
|
logger.break();
|
|
346
1668
|
handleError(error);
|
|
@@ -349,6 +1671,7 @@ const init = new Command().name("init").description("initialize your project and
|
|
|
349
1671
|
}
|
|
350
1672
|
});
|
|
351
1673
|
async function runInit(options) {
|
|
1674
|
+
const designConfig = resolveDesignSystemConfig(options);
|
|
352
1675
|
let newProjectTemplate;
|
|
353
1676
|
if (!options.skipPreflight) {
|
|
354
1677
|
if ((await preFlightInit(options)).errors[MISSING_DIR_OR_EMPTY_PROJECT]) {
|
|
@@ -360,30 +1683,86 @@ async function runInit(options) {
|
|
|
360
1683
|
}
|
|
361
1684
|
}
|
|
362
1685
|
if (newProjectTemplate) {
|
|
363
|
-
|
|
1686
|
+
const projectPath = {
|
|
364
1687
|
"astro-monorepo": "apps/web",
|
|
365
1688
|
"astro-with-component-docs-monorepo": "apps/web",
|
|
366
1689
|
astro: ""
|
|
367
|
-
}
|
|
1690
|
+
};
|
|
1691
|
+
await applyDesignSystemToProject(options.cwd, {
|
|
1692
|
+
...designConfig,
|
|
1693
|
+
template: newProjectTemplate
|
|
1694
|
+
}, { themeVars: options.themeRef ? await fetchInitThemeVars(buildInitUrl({
|
|
1695
|
+
...designConfig,
|
|
1696
|
+
template: newProjectTemplate
|
|
1697
|
+
}, options.themeRef)) ?? void 0 : void 0 });
|
|
1698
|
+
options.cwd = path.resolve(options.cwd, projectPath[newProjectTemplate]);
|
|
368
1699
|
logger.log(`${highlighter.success("Success!")} Project initialization completed.\nYou may now add components.`);
|
|
369
1700
|
return await getConfig(options.cwd);
|
|
370
1701
|
}
|
|
371
1702
|
try {
|
|
372
1703
|
const env = {
|
|
373
1704
|
...process.env,
|
|
374
|
-
REGISTRY_URL:
|
|
1705
|
+
REGISTRY_URL: resolveRegistryUrl()
|
|
375
1706
|
};
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
|
|
1707
|
+
const initUrl = buildInitUrl(designConfig, options.themeRef);
|
|
1708
|
+
const shouldReinstall = shouldReinstallExistingComponents(options);
|
|
1709
|
+
const reinstallComponents = shouldReinstall ? await getInstalledUiComponents(options.cwd) : [];
|
|
1710
|
+
const forwardedOptions = ensureShadcnReinstallFlag(options.forwardedOptions ?? [], shouldReinstall);
|
|
1711
|
+
await runShadcnCommand({
|
|
379
1712
|
cwd: options.cwd,
|
|
1713
|
+
args: [
|
|
1714
|
+
"init",
|
|
1715
|
+
initUrl,
|
|
1716
|
+
...reinstallComponents,
|
|
1717
|
+
...forwardedOptions
|
|
1718
|
+
],
|
|
380
1719
|
env
|
|
381
1720
|
});
|
|
382
|
-
|
|
1721
|
+
await syncManagedTailwindCss(options.cwd);
|
|
1722
|
+
const managedFonts = [toManagedAstroFont(designConfig.font), designConfig.fontHeading !== "inherit" ? toManagedAstroFont(`font-heading-${designConfig.fontHeading}`) : null].filter((font) => font !== null);
|
|
1723
|
+
const managedFont = managedFonts.find((font) => font.cssVariable !== "--font-heading");
|
|
1724
|
+
if (managedFonts.length > 0 && managedFont) {
|
|
1725
|
+
await syncAstroFontsInProject(options.cwd, managedFonts, managedFont.cssVariable);
|
|
1726
|
+
await syncAstroManagedFontCss(options.cwd, managedFont.cssVariable);
|
|
1727
|
+
await cleanupAstroFontPackages(options.cwd);
|
|
1728
|
+
}
|
|
1729
|
+
if (reinstallComponents.length > 0) {
|
|
1730
|
+
const config = await getConfig(options.cwd);
|
|
1731
|
+
const uiDir = config?.resolvedPaths.ui ?? "";
|
|
1732
|
+
const activeStyle = config?.style ?? "bejamas-juno";
|
|
1733
|
+
if (uiDir) await reorganizeComponents(reinstallComponents, uiDir, resolveRegistryUrl(), false, activeStyle, true);
|
|
1734
|
+
await fixAstroImports(options.cwd, false);
|
|
1735
|
+
}
|
|
1736
|
+
} catch {
|
|
383
1737
|
process.exit(1);
|
|
384
1738
|
}
|
|
385
1739
|
}
|
|
386
1740
|
|
|
1741
|
+
//#endregion
|
|
1742
|
+
//#region src/commands/docs.ts
|
|
1743
|
+
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) => {
|
|
1744
|
+
const cwd = path.resolve(opts.cwd ?? process.cwd());
|
|
1745
|
+
const rawArgv = process.argv.slice(2);
|
|
1746
|
+
const args = [
|
|
1747
|
+
"docs",
|
|
1748
|
+
...components,
|
|
1749
|
+
"--cwd",
|
|
1750
|
+
cwd
|
|
1751
|
+
];
|
|
1752
|
+
if (opts.base) args.push("--base", opts.base);
|
|
1753
|
+
if (opts.json) args.push("--json");
|
|
1754
|
+
const passthroughArgs = extractPassthroughArgs(rawArgv, "docs");
|
|
1755
|
+
if (passthroughArgs.length > 0) args.push(...passthroughArgs);
|
|
1756
|
+
try {
|
|
1757
|
+
await runShadcnCommand({
|
|
1758
|
+
cwd,
|
|
1759
|
+
args
|
|
1760
|
+
});
|
|
1761
|
+
} catch {
|
|
1762
|
+
process.exit(1);
|
|
1763
|
+
}
|
|
1764
|
+
});
|
|
1765
|
+
|
|
387
1766
|
//#endregion
|
|
388
1767
|
//#region src/utils/tsconfig-utils.ts
|
|
389
1768
|
/**
|
|
@@ -421,7 +1800,7 @@ function resolveAliasPathUsingTsConfig(inputPath, projectRoot) {
|
|
|
421
1800
|
}
|
|
422
1801
|
|
|
423
1802
|
//#endregion
|
|
424
|
-
//#region src/commands/docs.ts
|
|
1803
|
+
//#region src/commands/docs-build.ts
|
|
425
1804
|
async function generateDocs({ cwd, outDir, verbose }) {
|
|
426
1805
|
const DEBUG = process.env.BEJAMAS_DEBUG === "1" || process.env.BEJAMAS_DEBUG === "true" || verbose;
|
|
427
1806
|
try {
|
|
@@ -460,7 +1839,7 @@ async function generateDocs({ cwd, outDir, verbose }) {
|
|
|
460
1839
|
if (mapped.startsWith("./") || mapped.startsWith("../") || isAbsolute(mapped)) outResolved = mapped;
|
|
461
1840
|
else {
|
|
462
1841
|
const abs = resolveAliasPathUsingTsConfig(mapped, projectRoot);
|
|
463
|
-
if (abs) outResolved = relative
|
|
1842
|
+
if (abs) outResolved = relative(projectRoot, abs);
|
|
464
1843
|
}
|
|
465
1844
|
if (!outResolved && mapped.startsWith("@/")) outResolved = mapped.replace(/^@\//, "src/");
|
|
466
1845
|
const finalOut = outResolved ?? mapped;
|
|
@@ -486,7 +1865,7 @@ async function generateDocs({ cwd, outDir, verbose }) {
|
|
|
486
1865
|
for (let i = 0; i < 6; i += 1) {
|
|
487
1866
|
if (existsSync(resolve(current, "packages/ui/package.json"))) {
|
|
488
1867
|
const abs = resolve(current, "packages/ui");
|
|
489
|
-
return relative
|
|
1868
|
+
return relative(shellCwd, abs) || abs;
|
|
490
1869
|
}
|
|
491
1870
|
const parent = resolve(current, "..");
|
|
492
1871
|
if (parent === current) break;
|
|
@@ -494,7 +1873,7 @@ async function generateDocs({ cwd, outDir, verbose }) {
|
|
|
494
1873
|
}
|
|
495
1874
|
if (existsSync(resolve(shellCwd, "node_modules/@bejamas/ui/package.json"))) {
|
|
496
1875
|
const abs = resolve(shellCwd, "node_modules/@bejamas/ui");
|
|
497
|
-
return relative
|
|
1876
|
+
return relative(shellCwd, abs) || abs;
|
|
498
1877
|
}
|
|
499
1878
|
return "packages/ui";
|
|
500
1879
|
})(),
|
|
@@ -533,7 +1912,7 @@ async function generateDocs({ cwd, outDir, verbose }) {
|
|
|
533
1912
|
if (process.env.BEJAMAS_DOCS_CWD) logger.info(`Docs CWD: ${process.env.BEJAMAS_DOCS_CWD}`);
|
|
534
1913
|
if (process.env.BEJAMAS_DOCS_OUT_DIR) logger.info(`Docs out: ${process.env.BEJAMAS_DOCS_OUT_DIR}`);
|
|
535
1914
|
}
|
|
536
|
-
const mod = await import("./generate-mdx-
|
|
1915
|
+
const mod = await import("./generate-mdx-PPNpe9_J.js");
|
|
537
1916
|
if (typeof mod.runDocsGenerator === "function") await mod.runDocsGenerator();
|
|
538
1917
|
else throw new Error("Failed to load docs generator. Export 'runDocsGenerator' not found.");
|
|
539
1918
|
} catch (err) {
|
|
@@ -541,7 +1920,7 @@ async function generateDocs({ cwd, outDir, verbose }) {
|
|
|
541
1920
|
process.exit(1);
|
|
542
1921
|
}
|
|
543
1922
|
}
|
|
544
|
-
const
|
|
1923
|
+
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) => {
|
|
545
1924
|
await generateDocs({
|
|
546
1925
|
cwd: opts.cwd,
|
|
547
1926
|
outDir: opts.out,
|
|
@@ -639,19 +2018,19 @@ async function checkDocs({ cwd, json }) {
|
|
|
639
2018
|
logger.error("Unable to locate @bejamas/ui. Use --cwd to specify the UI package path.");
|
|
640
2019
|
process.exit(1);
|
|
641
2020
|
}
|
|
642
|
-
const componentsDir = join
|
|
2021
|
+
const componentsDir = join(uiRoot, "src", "components");
|
|
643
2022
|
if (!existsSync(componentsDir)) {
|
|
644
2023
|
logger.error(`Components directory not found: ${componentsDir}\n\nExpected structure: <uiRoot>/src/components/*.astro\nUse --cwd to specify a different UI package root.`);
|
|
645
2024
|
process.exit(1);
|
|
646
2025
|
}
|
|
647
|
-
const files = readdirSync(componentsDir, { withFileTypes: true }).filter((e) => e.isFile() && extname
|
|
2026
|
+
const files = readdirSync(componentsDir, { withFileTypes: true }).filter((e) => e.isFile() && extname(e.name).toLowerCase() === ".astro").map((e) => e.name).sort();
|
|
648
2027
|
if (files.length === 0) {
|
|
649
2028
|
logger.warn("No .astro component files found.");
|
|
650
2029
|
process.exit(0);
|
|
651
2030
|
}
|
|
652
2031
|
const results = [];
|
|
653
2032
|
for (const file of files) {
|
|
654
|
-
const status = checkComponentDocs(join
|
|
2033
|
+
const status = checkComponentDocs(join(componentsDir, file), file);
|
|
655
2034
|
results.push(status);
|
|
656
2035
|
}
|
|
657
2036
|
const complete = results.filter((r) => r.status === "complete");
|
|
@@ -718,174 +2097,8 @@ const docsCheck = new Command().name("docs:check").description("check documentat
|
|
|
718
2097
|
});
|
|
719
2098
|
});
|
|
720
2099
|
|
|
721
|
-
//#endregion
|
|
722
|
-
//#region src/utils/astro-imports.ts
|
|
723
|
-
function updateImportAliases(moduleSpecifier, config, isRemote = false) {
|
|
724
|
-
if (!moduleSpecifier.startsWith("@/") && !isRemote) return moduleSpecifier;
|
|
725
|
-
let specifier = moduleSpecifier;
|
|
726
|
-
if (isRemote && specifier.startsWith("@/")) specifier = specifier.replace(/^@\//, "@/registry/new-york/");
|
|
727
|
-
if (!specifier.startsWith("@/registry/")) {
|
|
728
|
-
const alias = config.aliases.components.split("/")[0];
|
|
729
|
-
return specifier.replace(/^@\//, `${alias}/`);
|
|
730
|
-
}
|
|
731
|
-
if (specifier.match(/^@\/registry\/(.+)\/ui/)) return specifier.replace(/^@\/registry\/(.+)\/ui/, config.aliases.ui ?? `${config.aliases.components}/ui`);
|
|
732
|
-
if (config.aliases.components && specifier.match(/^@\/registry\/(.+)\/components/)) return specifier.replace(/^@\/registry\/(.+)\/components/, config.aliases.components);
|
|
733
|
-
if (config.aliases.lib && specifier.match(/^@\/registry\/(.+)\/lib/)) return specifier.replace(/^@\/registry\/(.+)\/lib/, config.aliases.lib);
|
|
734
|
-
if (config.aliases.hooks && specifier.match(/^@\/registry\/(.+)\/hooks/)) return specifier.replace(/^@\/registry\/(.+)\/hooks/, config.aliases.hooks);
|
|
735
|
-
return specifier.replace(/^@\/registry\/[^/]+/, config.aliases.components);
|
|
736
|
-
}
|
|
737
|
-
function rewriteAstroImports(content, config) {
|
|
738
|
-
let updated = content;
|
|
739
|
-
const utilsAlias = config.aliases?.utils;
|
|
740
|
-
const utilsImport = `${typeof utilsAlias === "string" && utilsAlias.includes("/") ? utilsAlias.split("/")[0] : "@"}/lib/utils`;
|
|
741
|
-
updated = updated.replace(/import\s+([\s\S]*?)\s+from\s+["']([^"']+)["']/g, (full, importsPart, specifier) => {
|
|
742
|
-
const next = updateImportAliases(specifier, config, false);
|
|
743
|
-
let finalSpec = next;
|
|
744
|
-
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;
|
|
745
|
-
if (finalSpec === specifier) return full;
|
|
746
|
-
return full.replace(specifier, finalSpec);
|
|
747
|
-
});
|
|
748
|
-
updated = updated.replace(/import\s+["']([^"']+)["']/g, (full, specifier) => {
|
|
749
|
-
const next = updateImportAliases(specifier, config, false);
|
|
750
|
-
if (next === specifier) return full;
|
|
751
|
-
return full.replace(specifier, next);
|
|
752
|
-
});
|
|
753
|
-
return updated;
|
|
754
|
-
}
|
|
755
|
-
async function fixAstroImports(cwd, isVerbose) {
|
|
756
|
-
const config = await getConfig(cwd);
|
|
757
|
-
if (!config) return;
|
|
758
|
-
const searchRoots = new Set([config.resolvedPaths.components, config.resolvedPaths.ui]);
|
|
759
|
-
for (const root of Array.from(searchRoots)) {
|
|
760
|
-
if (!root) continue;
|
|
761
|
-
const astroFiles = await fg("**/*.astro", {
|
|
762
|
-
cwd: root,
|
|
763
|
-
absolute: true,
|
|
764
|
-
dot: false
|
|
765
|
-
});
|
|
766
|
-
for (const filePath of astroFiles) {
|
|
767
|
-
const original = await fs$1.readFile(filePath, "utf8");
|
|
768
|
-
const rewritten = rewriteAstroImports(original, config);
|
|
769
|
-
if (rewritten === original) continue;
|
|
770
|
-
await fs$1.writeFile(filePath, rewritten, "utf8");
|
|
771
|
-
if (isVerbose) logger.info(`[bejamas-ui] fixed imports in ${path$1.relative(cwd, filePath)}`);
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
//#endregion
|
|
777
|
-
//#region src/utils/reorganize-components.ts
|
|
778
|
-
/**
|
|
779
|
-
* Fetches a registry item JSON from the registry URL.
|
|
780
|
-
*/
|
|
781
|
-
async function fetchRegistryItem(componentName, registryUrl) {
|
|
782
|
-
const url = `${registryUrl}/styles/new-york-v4/${componentName}.json`;
|
|
783
|
-
try {
|
|
784
|
-
const response = await fetch(url);
|
|
785
|
-
if (!response.ok) {
|
|
786
|
-
const fallbackUrl = `${registryUrl}/${componentName}.json`;
|
|
787
|
-
const fallbackResponse = await fetch(fallbackUrl);
|
|
788
|
-
if (!fallbackResponse.ok) return null;
|
|
789
|
-
return await fallbackResponse.json();
|
|
790
|
-
}
|
|
791
|
-
return await response.json();
|
|
792
|
-
} catch {
|
|
793
|
-
return null;
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
/**
|
|
797
|
-
* Extracts the subfolder name from registry file paths.
|
|
798
|
-
* E.g., "components/ui/avatar/Avatar.astro" → "avatar"
|
|
799
|
-
*/
|
|
800
|
-
function getSubfolderFromPaths(files) {
|
|
801
|
-
const uiFiles = files.filter((f) => f.type === "registry:ui");
|
|
802
|
-
if (uiFiles.length < 2) return null;
|
|
803
|
-
const subfolders = /* @__PURE__ */ new Set();
|
|
804
|
-
for (const file of uiFiles) {
|
|
805
|
-
const parts = file.path.split("/");
|
|
806
|
-
const uiIndex = parts.indexOf("ui");
|
|
807
|
-
if (uiIndex !== -1 && parts.length > uiIndex + 2) subfolders.add(parts[uiIndex + 1]);
|
|
808
|
-
}
|
|
809
|
-
if (subfolders.size === 1) return Array.from(subfolders)[0];
|
|
810
|
-
if (uiFiles.length > 0) {
|
|
811
|
-
const firstPath = uiFiles[0].path;
|
|
812
|
-
const dirname$1 = path$1.dirname(firstPath);
|
|
813
|
-
const folderName = path$1.basename(dirname$1);
|
|
814
|
-
if (folderName && folderName !== "ui") return folderName;
|
|
815
|
-
}
|
|
816
|
-
return null;
|
|
817
|
-
}
|
|
818
|
-
/**
|
|
819
|
-
* Checks if a path exists.
|
|
820
|
-
*/
|
|
821
|
-
async function pathExists(filePath) {
|
|
822
|
-
try {
|
|
823
|
-
await fs$1.access(filePath);
|
|
824
|
-
return true;
|
|
825
|
-
} catch {
|
|
826
|
-
return false;
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
/**
|
|
830
|
-
* Reorganizes multi-file components into correct subfolders.
|
|
831
|
-
* Only moves files from FLAT location to subfolder.
|
|
832
|
-
* Does NOT touch files already in subfolders.
|
|
833
|
-
* E.g., moves `uiDir/Avatar.astro` to `uiDir/avatar/Avatar.astro`.
|
|
834
|
-
* Returns info about moved files for display purposes.
|
|
835
|
-
*/
|
|
836
|
-
async function reorganizeComponents(components, uiDir, registryUrl, verbose) {
|
|
837
|
-
const result = {
|
|
838
|
-
totalMoved: 0,
|
|
839
|
-
movedFiles: [],
|
|
840
|
-
skippedFiles: []
|
|
841
|
-
};
|
|
842
|
-
if (!uiDir || components.length === 0) return result;
|
|
843
|
-
for (const componentName of components) try {
|
|
844
|
-
const registryItem = await fetchRegistryItem(componentName, registryUrl);
|
|
845
|
-
if (!registryItem) {
|
|
846
|
-
if (verbose) logger.info(`[bejamas-ui] Could not fetch registry for ${componentName}, skipping reorganization`);
|
|
847
|
-
continue;
|
|
848
|
-
}
|
|
849
|
-
const subfolder = getSubfolderFromPaths(registryItem.files);
|
|
850
|
-
if (!subfolder) {
|
|
851
|
-
if (verbose) logger.info(`[bejamas-ui] ${componentName} is single-file or has no subfolder, skipping`);
|
|
852
|
-
continue;
|
|
853
|
-
}
|
|
854
|
-
const uiFiles = registryItem.files.filter((f) => f.type === "registry:ui");
|
|
855
|
-
const targetDir = path$1.join(uiDir, subfolder);
|
|
856
|
-
let movedCount = 0;
|
|
857
|
-
for (const file of uiFiles) {
|
|
858
|
-
const filename = path$1.basename(file.path);
|
|
859
|
-
const flatPath = path$1.join(uiDir, filename);
|
|
860
|
-
const targetPath = path$1.join(targetDir, filename);
|
|
861
|
-
if (!await pathExists(flatPath)) continue;
|
|
862
|
-
if (await pathExists(targetPath)) {
|
|
863
|
-
try {
|
|
864
|
-
await fs$1.unlink(flatPath);
|
|
865
|
-
result.skippedFiles.push(`${subfolder}/${filename}`);
|
|
866
|
-
if (verbose) logger.info(`[bejamas-ui] Removed flat duplicate: ${filename} (${subfolder}/${filename} exists)`);
|
|
867
|
-
} catch {
|
|
868
|
-
result.skippedFiles.push(`${subfolder}/${filename}`);
|
|
869
|
-
}
|
|
870
|
-
continue;
|
|
871
|
-
}
|
|
872
|
-
await fs$1.mkdir(targetDir, { recursive: true });
|
|
873
|
-
await fs$1.rename(flatPath, targetPath);
|
|
874
|
-
movedCount++;
|
|
875
|
-
result.totalMoved++;
|
|
876
|
-
result.movedFiles.push(`${subfolder}/${filename}`);
|
|
877
|
-
if (verbose) logger.info(`[bejamas-ui] Moved ${filename} → ${subfolder}/${filename}`);
|
|
878
|
-
}
|
|
879
|
-
if (movedCount > 0 && verbose) logger.info(`[bejamas-ui] Reorganized ${componentName} into ${subfolder}/`);
|
|
880
|
-
} catch (err) {
|
|
881
|
-
if (verbose) logger.warn(`[bejamas-ui] Failed to reorganize ${componentName}: ${err}`);
|
|
882
|
-
}
|
|
883
|
-
return result;
|
|
884
|
-
}
|
|
885
|
-
|
|
886
2100
|
//#endregion
|
|
887
2101
|
//#region src/commands/add.ts
|
|
888
|
-
const DEFAULT_REGISTRY_URL = "https://ui.bejamas.com/r";
|
|
889
2102
|
function extractOptionsForShadcn(rawArgv, cmd) {
|
|
890
2103
|
if (typeof cmd.getOptionValueSource === "function") {
|
|
891
2104
|
const opts = cmd.optsWithGlobals();
|
|
@@ -903,13 +2116,25 @@ function extractOptionsForShadcn(rawArgv, cmd) {
|
|
|
903
2116
|
const value = opts[key];
|
|
904
2117
|
if (typeof value === "string") forwarded$1.push(flag, value);
|
|
905
2118
|
};
|
|
2119
|
+
const addOptionalString = (key, flag) => {
|
|
2120
|
+
if (getSource(key) !== "cli") return;
|
|
2121
|
+
const value = opts[key];
|
|
2122
|
+
if (value === true) {
|
|
2123
|
+
forwarded$1.push(flag);
|
|
2124
|
+
return;
|
|
2125
|
+
}
|
|
2126
|
+
if (typeof value === "string") forwarded$1.push(flag, value);
|
|
2127
|
+
};
|
|
906
2128
|
addBoolean("yes", "--yes");
|
|
907
2129
|
addBoolean("overwrite", "--overwrite");
|
|
2130
|
+
addBoolean("dryRun", "--dry-run");
|
|
908
2131
|
addString("cwd", "--cwd");
|
|
909
2132
|
addBoolean("all", "--all");
|
|
910
2133
|
addString("path", "--path");
|
|
911
2134
|
addBoolean("silent", "--silent");
|
|
912
2135
|
addBoolean("srcDir", "--src-dir", "--no-src-dir");
|
|
2136
|
+
addOptionalString("diff", "--diff");
|
|
2137
|
+
addOptionalString("view", "--view");
|
|
913
2138
|
const addIndex$1 = rawArgv.findIndex((arg) => arg === "add");
|
|
914
2139
|
if (addIndex$1 !== -1) {
|
|
915
2140
|
const rest$1 = rawArgv.slice(addIndex$1 + 1);
|
|
@@ -926,13 +2151,15 @@ function extractOptionsForShadcn(rawArgv, cmd) {
|
|
|
926
2151
|
"-c",
|
|
927
2152
|
"--cwd",
|
|
928
2153
|
"-p",
|
|
929
|
-
"--path"
|
|
2154
|
+
"--path",
|
|
2155
|
+
"--diff",
|
|
2156
|
+
"--view"
|
|
930
2157
|
]);
|
|
931
2158
|
const filteredFlags = new Set(["-v", "--verbose"]);
|
|
932
|
-
for (let
|
|
933
|
-
const token = rest[
|
|
2159
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
2160
|
+
const token = rest[index];
|
|
934
2161
|
if (token === "--") {
|
|
935
|
-
forwarded.push("--", ...rest.slice(
|
|
2162
|
+
forwarded.push("--", ...rest.slice(index + 1));
|
|
936
2163
|
break;
|
|
937
2164
|
}
|
|
938
2165
|
if (!token.startsWith("-")) continue;
|
|
@@ -940,36 +2167,38 @@ function extractOptionsForShadcn(rawArgv, cmd) {
|
|
|
940
2167
|
forwarded.push(token);
|
|
941
2168
|
if (token.includes("=")) continue;
|
|
942
2169
|
if (optionsWithValues.has(token)) {
|
|
943
|
-
const next = rest[
|
|
2170
|
+
const next = rest[index + 1];
|
|
944
2171
|
if (next) {
|
|
945
2172
|
forwarded.push(next);
|
|
946
|
-
|
|
2173
|
+
index += 1;
|
|
947
2174
|
}
|
|
948
2175
|
}
|
|
949
2176
|
}
|
|
950
2177
|
return forwarded;
|
|
951
2178
|
}
|
|
952
|
-
|
|
953
|
-
|
|
2179
|
+
function hasInspectionFlags(forwardedOptions) {
|
|
2180
|
+
return forwardedOptions.includes("--dry-run") || forwardedOptions.includes("--diff") || forwardedOptions.includes("--view");
|
|
2181
|
+
}
|
|
2182
|
+
function formatSkippedFilesHeading(count, overwriteUsed) {
|
|
2183
|
+
const noun = `file${count === 1 ? "" : "s"}`;
|
|
2184
|
+
if (overwriteUsed) return `Skipped ${count} ${noun}: (files might be identical)`;
|
|
2185
|
+
return `Skipped ${count} ${noun}: (files might be identical, use --overwrite to overwrite)`;
|
|
2186
|
+
}
|
|
2187
|
+
async function buildSubfolderMap(components, uiDir, registryUrl, style) {
|
|
954
2188
|
const filenameToSubfolders = /* @__PURE__ */ new Map();
|
|
955
|
-
|
|
2189
|
+
let requiresReorganization = false;
|
|
956
2190
|
for (const componentName of components) {
|
|
957
|
-
const registryItem = await fetchRegistryItem(componentName, registryUrl);
|
|
2191
|
+
const registryItem = await fetchRegistryItem(componentName, registryUrl, style);
|
|
958
2192
|
if (!registryItem) continue;
|
|
2193
|
+
if (shouldReorganizeRegistryUiFiles(registryItem.files, uiDir)) requiresReorganization = true;
|
|
959
2194
|
const subfolder = getSubfolderFromPaths(registryItem.files);
|
|
960
2195
|
if (!subfolder) continue;
|
|
961
|
-
const files = [];
|
|
962
2196
|
for (const file of registryItem.files) if (file.type === "registry:ui") {
|
|
963
|
-
const filename = path
|
|
964
|
-
files.push(filename);
|
|
2197
|
+
const filename = path.basename(file.path);
|
|
965
2198
|
const subfolders = filenameToSubfolders.get(filename) || [];
|
|
966
2199
|
subfolders.push(subfolder);
|
|
967
2200
|
filenameToSubfolders.set(filename, subfolders);
|
|
968
2201
|
}
|
|
969
|
-
componentInfo.set(subfolder, {
|
|
970
|
-
subfolder,
|
|
971
|
-
files
|
|
972
|
-
});
|
|
973
2202
|
}
|
|
974
2203
|
const uniqueMap = /* @__PURE__ */ new Map();
|
|
975
2204
|
const sharedFilenames = /* @__PURE__ */ new Set();
|
|
@@ -979,35 +2208,29 @@ async function buildSubfolderMap(components, registryUrl) {
|
|
|
979
2208
|
});
|
|
980
2209
|
return {
|
|
981
2210
|
uniqueMap,
|
|
982
|
-
|
|
983
|
-
|
|
2211
|
+
sharedFilenames,
|
|
2212
|
+
requiresReorganization
|
|
984
2213
|
};
|
|
985
2214
|
}
|
|
986
|
-
/**
|
|
987
|
-
* Rewrite file paths to include correct subfolders.
|
|
988
|
-
* Handles shared filenames (like index.ts) by tracking current component context.
|
|
989
|
-
*/
|
|
990
2215
|
function rewritePaths(paths, mapResult) {
|
|
991
|
-
const { uniqueMap,
|
|
2216
|
+
const { uniqueMap, sharedFilenames } = mapResult;
|
|
992
2217
|
let currentSubfolder = null;
|
|
993
2218
|
return paths.map((filePath) => {
|
|
994
|
-
const filename = path
|
|
995
|
-
const parentDir = path
|
|
2219
|
+
const filename = path.basename(filePath);
|
|
2220
|
+
const parentDir = path.basename(path.dirname(filePath));
|
|
996
2221
|
const uniqueMapping = uniqueMap.get(filename);
|
|
997
2222
|
if (uniqueMapping) {
|
|
998
|
-
const expectedSubfolder = path
|
|
2223
|
+
const expectedSubfolder = path.dirname(uniqueMapping);
|
|
999
2224
|
currentSubfolder = expectedSubfolder;
|
|
1000
|
-
if (parentDir !== expectedSubfolder) return `${path
|
|
2225
|
+
if (parentDir !== expectedSubfolder) return `${path.dirname(filePath)}/${uniqueMapping}`;
|
|
1001
2226
|
return filePath;
|
|
1002
2227
|
}
|
|
1003
2228
|
if (sharedFilenames.has(filename) && currentSubfolder) {
|
|
1004
|
-
|
|
1005
|
-
if (parentDir !== expectedSubfolder) return `${path$1.dirname(filePath)}/${expectedSubfolder}/${filename}`;
|
|
2229
|
+
if (parentDir !== currentSubfolder) return `${path.dirname(filePath)}/${currentSubfolder}/${filename}`;
|
|
1006
2230
|
}
|
|
1007
2231
|
return filePath;
|
|
1008
2232
|
});
|
|
1009
2233
|
}
|
|
1010
|
-
/** Fetch available components from the registry */
|
|
1011
2234
|
async function fetchAvailableComponents(registryUrl) {
|
|
1012
2235
|
const indexUrl = `${registryUrl}/index.json`;
|
|
1013
2236
|
const response = await fetch(indexUrl);
|
|
@@ -1015,14 +2238,13 @@ async function fetchAvailableComponents(registryUrl) {
|
|
|
1015
2238
|
const data = await response.json();
|
|
1016
2239
|
return Array.isArray(data) ? data : [];
|
|
1017
2240
|
}
|
|
1018
|
-
/** Prompt user to select components interactively */
|
|
1019
2241
|
async function promptForComponents(registryUrl) {
|
|
1020
2242
|
const checkingSpinner = spinner("Checking registry.").start();
|
|
1021
2243
|
let components;
|
|
1022
2244
|
try {
|
|
1023
2245
|
components = await fetchAvailableComponents(registryUrl);
|
|
1024
2246
|
checkingSpinner.succeed();
|
|
1025
|
-
} catch
|
|
2247
|
+
} catch {
|
|
1026
2248
|
checkingSpinner.fail();
|
|
1027
2249
|
logger.error("Failed to fetch available components from registry.");
|
|
1028
2250
|
return null;
|
|
@@ -1035,9 +2257,9 @@ async function promptForComponents(registryUrl) {
|
|
|
1035
2257
|
type: "autocompleteMultiselect",
|
|
1036
2258
|
name: "selected",
|
|
1037
2259
|
message: "Which components would you like to add?",
|
|
1038
|
-
choices: components.filter((
|
|
1039
|
-
title:
|
|
1040
|
-
value:
|
|
2260
|
+
choices: components.filter((component) => !component.type || component.type === "registry:ui").map((component) => ({
|
|
2261
|
+
title: component.name,
|
|
2262
|
+
value: component.name
|
|
1041
2263
|
})),
|
|
1042
2264
|
hint: "- Space to select. Return to submit.",
|
|
1043
2265
|
instructions: false
|
|
@@ -1045,7 +2267,6 @@ async function promptForComponents(registryUrl) {
|
|
|
1045
2267
|
if (!selected) return null;
|
|
1046
2268
|
return selected;
|
|
1047
2269
|
}
|
|
1048
|
-
/** Parse shadcn output to extract file lists (stdout has paths, stderr has headers) */
|
|
1049
2270
|
function parseShadcnOutput(stdout, stderr) {
|
|
1050
2271
|
const result = {
|
|
1051
2272
|
created: [],
|
|
@@ -1072,23 +2293,34 @@ function parseShadcnOutput(stdout, stderr) {
|
|
|
1072
2293
|
if (!allPaths.includes(filePath)) allPaths.push(filePath);
|
|
1073
2294
|
}
|
|
1074
2295
|
}
|
|
1075
|
-
let
|
|
1076
|
-
for (let
|
|
1077
|
-
|
|
1078
|
-
|
|
2296
|
+
let index = 0;
|
|
2297
|
+
for (let count = 0; count < createdCount && index < allPaths.length; count += 1) {
|
|
2298
|
+
result.created.push(allPaths[index]);
|
|
2299
|
+
index += 1;
|
|
2300
|
+
}
|
|
2301
|
+
for (let count = 0; count < updatedCount && index < allPaths.length; count += 1) {
|
|
2302
|
+
result.updated.push(allPaths[index]);
|
|
2303
|
+
index += 1;
|
|
2304
|
+
}
|
|
2305
|
+
for (let count = 0; count < skippedCount && index < allPaths.length; count += 1) {
|
|
2306
|
+
result.skipped.push(allPaths[index]);
|
|
2307
|
+
index += 1;
|
|
2308
|
+
}
|
|
1079
2309
|
return result;
|
|
1080
2310
|
}
|
|
1081
|
-
async function addComponents(packages, forwardedOptions, isVerbose, isSilent,
|
|
2311
|
+
async function addComponents(cwd, packages, forwardedOptions, isVerbose, isSilent, inspectionMode) {
|
|
1082
2312
|
const env = {
|
|
1083
2313
|
...process.env,
|
|
1084
|
-
REGISTRY_URL:
|
|
2314
|
+
REGISTRY_URL: resolveRegistryUrl()
|
|
1085
2315
|
};
|
|
2316
|
+
await ensurePinnedShadcnExecPrefix();
|
|
1086
2317
|
const invocation = buildPinnedShadcnInvocation(buildShadcnAddArgs(packages, forwardedOptions));
|
|
1087
2318
|
if (isVerbose) logger.info(`[bejamas-ui] ${invocation.cmd} ${invocation.args.join(" ")}`);
|
|
1088
2319
|
const registrySpinner = spinner("Checking registry.", { silent: isSilent });
|
|
1089
2320
|
registrySpinner.start();
|
|
1090
2321
|
try {
|
|
1091
2322
|
const result = await execa(invocation.cmd, invocation.args, {
|
|
2323
|
+
cwd,
|
|
1092
2324
|
env,
|
|
1093
2325
|
input: "n\nn\nn\nn\nn\nn\nn\nn\nn\nn\n",
|
|
1094
2326
|
stdout: "pipe",
|
|
@@ -1103,13 +2335,21 @@ async function addComponents(packages, forwardedOptions, isVerbose, isSilent, su
|
|
|
1103
2335
|
logger.info(`[bejamas-ui] Raw stdout: ${stdout}`);
|
|
1104
2336
|
logger.info(`[bejamas-ui] Raw stderr: ${stderr}`);
|
|
1105
2337
|
}
|
|
1106
|
-
|
|
2338
|
+
if (inspectionMode) {
|
|
2339
|
+
if (stdout) process.stdout.write(stdout.endsWith("\n") ? stdout : `${stdout}\n`);
|
|
2340
|
+
if (stderr) process.stderr.write(stderr.endsWith("\n") ? stderr : `${stderr}\n`);
|
|
2341
|
+
}
|
|
2342
|
+
const parsed = inspectionMode ? {
|
|
2343
|
+
created: [],
|
|
2344
|
+
updated: [],
|
|
2345
|
+
skipped: []
|
|
2346
|
+
} : parseShadcnOutput(stdout, stderr);
|
|
1107
2347
|
if (result.exitCode !== 0) {
|
|
1108
2348
|
if (result.stderr) logger.error(result.stderr);
|
|
1109
2349
|
process.exit(result.exitCode);
|
|
1110
2350
|
}
|
|
1111
2351
|
return parsed;
|
|
1112
|
-
} catch
|
|
2352
|
+
} catch {
|
|
1113
2353
|
registrySpinner.fail();
|
|
1114
2354
|
logger.error("Failed to add components");
|
|
1115
2355
|
process.exit(1);
|
|
@@ -1125,22 +2365,24 @@ function buildShadcnAddArgs(packages, forwardedOptions) {
|
|
|
1125
2365
|
...forwardedOptions
|
|
1126
2366
|
];
|
|
1127
2367
|
}
|
|
1128
|
-
const add = new Command().name("add").description("Add components via the
|
|
2368
|
+
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) {
|
|
1129
2369
|
const root = cmd?.parent;
|
|
1130
2370
|
const verbose = Boolean(root?.opts?.().verbose);
|
|
1131
2371
|
const forwardedOptions = extractOptionsForShadcn(process.argv.slice(2), cmd);
|
|
1132
2372
|
const opts = typeof cmd.optsWithGlobals === "function" ? cmd.optsWithGlobals() : cmd.opts?.() ?? {};
|
|
2373
|
+
const inspectionMode = hasInspectionFlags(forwardedOptions);
|
|
2374
|
+
const overwriteUsed = forwardedOptions.includes("--overwrite") || forwardedOptions.includes("-o");
|
|
1133
2375
|
const cwd = opts.cwd || process.cwd();
|
|
1134
2376
|
let componentsToAdd = packages || [];
|
|
1135
2377
|
const wantsAll = Boolean(opts.all);
|
|
1136
2378
|
const isSilent = opts.silent || false;
|
|
1137
|
-
const registryUrl =
|
|
2379
|
+
const registryUrl = resolveRegistryUrl();
|
|
1138
2380
|
if (wantsAll && componentsToAdd.length === 0) {
|
|
1139
2381
|
const fetchingSpinner = spinner("Fetching available components.", { silent: isSilent }).start();
|
|
1140
2382
|
try {
|
|
1141
|
-
componentsToAdd = (await fetchAvailableComponents(registryUrl)).filter((
|
|
2383
|
+
componentsToAdd = (await fetchAvailableComponents(registryUrl)).filter((component) => !component.type || component.type === "registry:ui").map((component) => component.name);
|
|
1142
2384
|
fetchingSpinner.succeed();
|
|
1143
|
-
} catch
|
|
2385
|
+
} catch {
|
|
1144
2386
|
fetchingSpinner.fail();
|
|
1145
2387
|
logger.error("Failed to fetch available components from registry.");
|
|
1146
2388
|
process.exit(1);
|
|
@@ -1166,19 +2408,35 @@ const add = new Command().name("add").description("Add components via the pinned
|
|
|
1166
2408
|
logger.info(`[bejamas-ui] uiDir: ${uiDir}`);
|
|
1167
2409
|
logger.info(`[bejamas-ui] aliases.ui: ${uiConfig?.aliases?.ui || "not set"}`);
|
|
1168
2410
|
}
|
|
2411
|
+
const activeStyle = uiConfig?.style || config?.style || "bejamas-juno";
|
|
1169
2412
|
const totalComponents = componentsToAdd.length;
|
|
1170
|
-
for (let
|
|
1171
|
-
const component = componentsToAdd[
|
|
2413
|
+
for (let index = 0; index < componentsToAdd.length; index += 1) {
|
|
2414
|
+
const component = componentsToAdd[index];
|
|
1172
2415
|
if (totalComponents > 1 && !isSilent) {
|
|
1173
2416
|
logger.break();
|
|
1174
|
-
logger.info(highlighter.info(`[${
|
|
2417
|
+
logger.info(highlighter.info(`[${index + 1}/${totalComponents}]`) + ` Adding ${highlighter.success(component)}...`);
|
|
2418
|
+
}
|
|
2419
|
+
const subfolderMapResult = inspectionMode ? {
|
|
2420
|
+
uniqueMap: /* @__PURE__ */ new Map(),
|
|
2421
|
+
sharedFilenames: /* @__PURE__ */ new Set(),
|
|
2422
|
+
requiresReorganization: false
|
|
2423
|
+
} : await buildSubfolderMap([component], uiDir, registryUrl, activeStyle);
|
|
2424
|
+
const parsed = await addComponents(cwd, [component], forwardedOptions, verbose, isSilent, inspectionMode);
|
|
2425
|
+
if (!inspectionMode) {
|
|
2426
|
+
await syncManagedTailwindCss(cwd);
|
|
2427
|
+
const registryItem = await fetchRegistryItem(component, registryUrl, activeStyle);
|
|
2428
|
+
if (registryItem?.type === "registry:font") {
|
|
2429
|
+
const nextFont = toManagedAstroFont(registryItem.name);
|
|
2430
|
+
if (nextFont) {
|
|
2431
|
+
await syncAstroFontsInProject(cwd, mergeManagedAstroFonts(await readManagedAstroFontsFromProject(cwd), nextFont), nextFont.cssVariable);
|
|
2432
|
+
await syncAstroManagedFontCss(cwd, nextFont.cssVariable);
|
|
2433
|
+
await cleanupAstroFontPackages(cwd);
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
1175
2436
|
}
|
|
1176
|
-
const subfolderMapResult = await buildSubfolderMap([component], registryUrl);
|
|
1177
|
-
const parsed = await addComponents([component], forwardedOptions, verbose, isSilent, subfolderMapResult);
|
|
1178
2437
|
let skippedCount = 0;
|
|
1179
|
-
if (uiDir) skippedCount = (await reorganizeComponents([component], uiDir, registryUrl, verbose)).skippedFiles.length;
|
|
1180
|
-
if (!isSilent) {
|
|
1181
|
-
uiDir && path$1.relative(cwd, uiDir);
|
|
2438
|
+
if (!inspectionMode && uiDir && subfolderMapResult.requiresReorganization) skippedCount = (await reorganizeComponents([component], uiDir, registryUrl, verbose, activeStyle)).skippedFiles.length;
|
|
2439
|
+
if (!isSilent && !inspectionMode) {
|
|
1182
2440
|
const actuallyCreated = Math.max(0, parsed.created.length - skippedCount);
|
|
1183
2441
|
if (actuallyCreated > 0) {
|
|
1184
2442
|
const createdPaths = rewritePaths(parsed.created.slice(0, actuallyCreated), subfolderMapResult);
|
|
@@ -1193,13 +2451,36 @@ const add = new Command().name("add").description("Add components via the pinned
|
|
|
1193
2451
|
if (skippedCount > 0) logger.info(`Skipped ${skippedCount} file${skippedCount > 1 ? "s" : ""}: (already exists)`);
|
|
1194
2452
|
if (parsed.skipped.length > 0) {
|
|
1195
2453
|
const skippedPaths = rewritePaths(parsed.skipped, subfolderMapResult);
|
|
1196
|
-
logger.info(
|
|
2454
|
+
logger.info(formatSkippedFilesHeading(skippedPaths.length, overwriteUsed));
|
|
1197
2455
|
for (const file of skippedPaths) logger.log(` ${highlighter.info("-")} ${file}`);
|
|
1198
2456
|
}
|
|
1199
2457
|
if (actuallyCreated === 0 && parsed.updated.length === 0 && skippedCount === 0 && parsed.skipped.length === 0) logger.info("Already up to date.");
|
|
1200
2458
|
}
|
|
1201
2459
|
}
|
|
1202
|
-
await fixAstroImports(cwd, verbose);
|
|
2460
|
+
if (!inspectionMode) await fixAstroImports(cwd, verbose);
|
|
2461
|
+
});
|
|
2462
|
+
|
|
2463
|
+
//#endregion
|
|
2464
|
+
//#region src/commands/info.ts
|
|
2465
|
+
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) => {
|
|
2466
|
+
const cwd = path.resolve(opts.cwd ?? process.cwd());
|
|
2467
|
+
const rawArgv = process.argv.slice(2);
|
|
2468
|
+
const args = [
|
|
2469
|
+
"info",
|
|
2470
|
+
"--cwd",
|
|
2471
|
+
cwd
|
|
2472
|
+
];
|
|
2473
|
+
if (opts.json) args.push("--json");
|
|
2474
|
+
const passthroughArgs = extractPassthroughArgs(rawArgv, "info");
|
|
2475
|
+
if (passthroughArgs.length > 0) args.push(...passthroughArgs);
|
|
2476
|
+
try {
|
|
2477
|
+
await runShadcnCommand({
|
|
2478
|
+
cwd,
|
|
2479
|
+
args
|
|
2480
|
+
});
|
|
2481
|
+
} catch {
|
|
2482
|
+
process.exit(1);
|
|
2483
|
+
}
|
|
1203
2484
|
});
|
|
1204
2485
|
|
|
1205
2486
|
//#endregion
|
|
@@ -1208,7 +2489,9 @@ const pkg = createRequire(import.meta.url)("../package.json");
|
|
|
1208
2489
|
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");
|
|
1209
2490
|
program.addCommand(init);
|
|
1210
2491
|
program.addCommand(add);
|
|
2492
|
+
program.addCommand(info);
|
|
1211
2493
|
program.addCommand(docs);
|
|
2494
|
+
program.addCommand(docsBuild);
|
|
1212
2495
|
program.addCommand(docsCheck);
|
|
1213
2496
|
program.parse(process.argv);
|
|
1214
2497
|
var src_default = program;
|