bejamas 0.2.11 → 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-C8gXBOHp.js → generate-mdx-PPNpe9_J.js} +3 -8
- package/dist/{generate-mdx-C8gXBOHp.js.map → generate-mdx-PPNpe9_J.js.map} +1 -1
- package/dist/index.js +1630 -408
- 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-Dr0kJLcx.js +0 -431
- package/dist/utils-Dr0kJLcx.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,22 +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 {
|
|
6
|
-
import
|
|
7
|
-
import
|
|
5
|
+
import path, { extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import path$1 from "path";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
import { existsSync, promises, readFileSync, readdirSync } from "node:fs";
|
|
10
|
+
import fs from "fs-extra";
|
|
11
|
+
import fg from "fast-glob";
|
|
12
|
+
import fs$1 from "node:fs/promises";
|
|
13
|
+
import { bold, cyan, dim, green, magenta, red, white, yellow } from "kleur/colors";
|
|
8
14
|
import os from "os";
|
|
9
15
|
import dotenv from "dotenv";
|
|
10
16
|
import { detect } from "@antfu/ni";
|
|
11
|
-
import { z } from "zod";
|
|
12
|
-
import { bold, cyan, dim, green, magenta, red, white, yellow } from "kleur/colors";
|
|
13
17
|
import { execa } from "execa";
|
|
14
18
|
import prompts from "prompts";
|
|
15
|
-
import
|
|
16
|
-
import path$1, { extname as extname$1, isAbsolute, join as join$1, relative as relative$1, resolve } from "node:path";
|
|
17
|
-
import { existsSync as existsSync$1, readFileSync, readdirSync } from "node:fs";
|
|
18
|
-
import fs from "node:fs/promises";
|
|
19
|
+
import os$1 from "node:os";
|
|
19
20
|
|
|
21
|
+
//#region src/registry/context.ts
|
|
22
|
+
let context = { headers: {} };
|
|
23
|
+
function clearRegistryContext() {
|
|
24
|
+
context.headers = {};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
20
28
|
//#region src/utils/errors.ts
|
|
21
29
|
const MISSING_DIR_OR_EMPTY_PROJECT = "1";
|
|
22
30
|
|
|
@@ -24,7 +32,7 @@ const MISSING_DIR_OR_EMPTY_PROJECT = "1";
|
|
|
24
32
|
//#region src/preflights/preflight-init.ts
|
|
25
33
|
async function preFlightInit(options) {
|
|
26
34
|
const errors = {};
|
|
27
|
-
if (!
|
|
35
|
+
if (!fs.existsSync(options.cwd) || !fs.existsSync(path$1.resolve(options.cwd, "package.json"))) {
|
|
28
36
|
errors[MISSING_DIR_OR_EMPTY_PROJECT] = true;
|
|
29
37
|
return {
|
|
30
38
|
errors,
|
|
@@ -38,10 +46,986 @@ async function preFlightInit(options) {
|
|
|
38
46
|
}
|
|
39
47
|
|
|
40
48
|
//#endregion
|
|
41
|
-
//#region src/
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
+
}
|
|
45
1029
|
}
|
|
46
1030
|
|
|
47
1031
|
//#endregion
|
|
@@ -62,12 +1046,6 @@ async function getPackageManager(targetDir, { withFallback } = { withFallback: f
|
|
|
62
1046
|
if (userAgent.startsWith("bun")) return "bun";
|
|
63
1047
|
return "npm";
|
|
64
1048
|
}
|
|
65
|
-
async function getPackageRunner(cwd) {
|
|
66
|
-
const packageManager = await getPackageManager(cwd);
|
|
67
|
-
if (packageManager === "pnpm") return "pnpm dlx";
|
|
68
|
-
if (packageManager === "bun") return "bunx";
|
|
69
|
-
return "npx";
|
|
70
|
-
}
|
|
71
1049
|
|
|
72
1050
|
//#endregion
|
|
73
1051
|
//#region src/registry/errors.ts
|
|
@@ -161,7 +1139,39 @@ const TEMPLATES = {
|
|
|
161
1139
|
"astro-monorepo": "astro-monorepo",
|
|
162
1140
|
"astro-with-component-docs-monorepo": "astro-with-component-docs-monorepo"
|
|
163
1141
|
};
|
|
164
|
-
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
|
+
}
|
|
165
1175
|
async function createProject(options) {
|
|
166
1176
|
options = {
|
|
167
1177
|
srcDir: false,
|
|
@@ -206,7 +1216,7 @@ async function createProject(options) {
|
|
|
206
1216
|
const packageManager = await getPackageManager(options.cwd, { withFallback: true });
|
|
207
1217
|
const projectPath = `${options.cwd}/${projectName}`;
|
|
208
1218
|
try {
|
|
209
|
-
await
|
|
1219
|
+
await fs.access(options.cwd, fs.constants.W_OK);
|
|
210
1220
|
} catch (error) {
|
|
211
1221
|
logger.break();
|
|
212
1222
|
logger.error(`The path ${highlighter.info(options.cwd)} is not writable.`);
|
|
@@ -214,7 +1224,7 @@ async function createProject(options) {
|
|
|
214
1224
|
logger.break();
|
|
215
1225
|
process.exit(1);
|
|
216
1226
|
}
|
|
217
|
-
if (
|
|
1227
|
+
if (fs.existsSync(path$1.resolve(options.cwd, projectName, "package.json"))) {
|
|
218
1228
|
logger.break();
|
|
219
1229
|
logger.error(`A project with the name ${highlighter.info(projectName)} already exists.`);
|
|
220
1230
|
logger.error(`Please choose a different name and try again.`);
|
|
@@ -234,40 +1244,23 @@ async function createProject(options) {
|
|
|
234
1244
|
}
|
|
235
1245
|
async function createProjectFromTemplate(projectPath, options) {
|
|
236
1246
|
const createSpinner = spinner(`Creating a new project from template. This may take a few minutes.`).start();
|
|
237
|
-
const
|
|
238
|
-
astro: "
|
|
239
|
-
"astro-monorepo": "
|
|
240
|
-
"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"
|
|
241
1251
|
};
|
|
242
1252
|
try {
|
|
243
1253
|
dotenv.config({ quiet: true });
|
|
244
|
-
const templatePath = path.join(os.tmpdir(), `bejamas-template-${Date.now()}`);
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
throw new Error(`Failed to download template: ${response.status} ${response.statusText}`);
|
|
255
|
-
}
|
|
256
|
-
const tarPath = path.resolve(templatePath, "template.tar.gz");
|
|
257
|
-
await fsExtra.writeFile(tarPath, Buffer.from(await response.arrayBuffer()));
|
|
258
|
-
const tarSubpath = TEMPLATE_TAR_SUBPATH[options.templateKey];
|
|
259
|
-
const leafName = tarSubpath.split("/").pop();
|
|
260
|
-
await execa("tar", [
|
|
261
|
-
"-xzf",
|
|
262
|
-
tarPath,
|
|
263
|
-
"-C",
|
|
264
|
-
templatePath,
|
|
265
|
-
"--strip-components=2",
|
|
266
|
-
tarSubpath
|
|
267
|
-
]);
|
|
268
|
-
const extractedPath = path.resolve(templatePath, leafName);
|
|
269
|
-
await fsExtra.move(extractedPath, projectPath);
|
|
270
|
-
await fsExtra.remove(templatePath);
|
|
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);
|
|
1263
|
+
await fs.remove(templatePath);
|
|
271
1264
|
await execa(options.packageManager, ["install"], { cwd: projectPath });
|
|
272
1265
|
try {
|
|
273
1266
|
const { stdout } = await execa("git", ["rev-parse", "--is-inside-work-tree"], { cwd: projectPath });
|
|
@@ -287,16 +1280,288 @@ async function createProjectFromTemplate(projectPath, options) {
|
|
|
287
1280
|
handleError(error);
|
|
288
1281
|
}
|
|
289
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}`);
|
|
1460
|
+
}
|
|
1461
|
+
return result;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
//#endregion
|
|
1465
|
+
//#region src/utils/shadcn-cli.ts
|
|
1466
|
+
const PINNED_SHADCN_VERSION = "4.1.1";
|
|
1467
|
+
const PINNED_SHADCN_PACKAGE = `shadcn@${PINNED_SHADCN_VERSION}`;
|
|
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;
|
|
1472
|
+
}
|
|
1473
|
+
function getNpmExecutable() {
|
|
1474
|
+
return process.platform === "win32" ? "npm.cmd" : "npm";
|
|
1475
|
+
}
|
|
1476
|
+
function buildPinnedShadcnInvocation(shadcnArgs) {
|
|
1477
|
+
return {
|
|
1478
|
+
cmd: getNpmExecutable(),
|
|
1479
|
+
args: [
|
|
1480
|
+
"exec",
|
|
1481
|
+
"--yes",
|
|
1482
|
+
"--prefix",
|
|
1483
|
+
PINNED_SHADCN_EXEC_PREFIX,
|
|
1484
|
+
`--package=${PINNED_SHADCN_PACKAGE}`,
|
|
1485
|
+
"--",
|
|
1486
|
+
"shadcn",
|
|
1487
|
+
...shadcnArgs
|
|
1488
|
+
],
|
|
1489
|
+
source: "isolated"
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
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
|
+
}
|
|
290
1515
|
|
|
291
1516
|
//#endregion
|
|
292
1517
|
//#region src/commands/init.ts
|
|
293
|
-
|
|
294
|
-
const
|
|
295
|
-
|
|
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
|
+
}
|
|
296
1560
|
const initOptionsSchema = z.object({
|
|
297
1561
|
cwd: z.string(),
|
|
298
1562
|
components: z.array(z.string()).optional(),
|
|
299
1563
|
yes: z.boolean(),
|
|
1564
|
+
reinstall: z.boolean().optional(),
|
|
300
1565
|
defaults: z.boolean(),
|
|
301
1566
|
force: z.boolean(),
|
|
302
1567
|
silent: z.boolean(),
|
|
@@ -306,85 +1571,98 @@ const initOptionsSchema = z.object({
|
|
|
306
1571
|
template: z.string().optional().refine((val) => {
|
|
307
1572
|
if (val) return TEMPLATES[val];
|
|
308
1573
|
return true;
|
|
309
|
-
}, { message: "Invalid template. Please use '
|
|
310
|
-
|
|
311
|
-
|
|
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()
|
|
312
1584
|
});
|
|
313
|
-
function
|
|
314
|
-
|
|
315
|
-
const args = [...SHADCN_INIT_ARGS];
|
|
316
|
-
if (usesLegacyBaseColorFlag(localShadcnVersion)) args.push("--base-color", DEFAULT_COMPONENTS_BASE_COLOR);
|
|
317
|
-
return {
|
|
318
|
-
cmd: localShadcnPath,
|
|
319
|
-
args
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
return {
|
|
323
|
-
cmd: "npx",
|
|
324
|
-
args: [
|
|
325
|
-
"-y",
|
|
326
|
-
"shadcn@latest",
|
|
327
|
-
...SHADCN_INIT_ARGS
|
|
328
|
-
]
|
|
329
|
-
};
|
|
1585
|
+
function shouldReinstallExistingComponents(options) {
|
|
1586
|
+
return options.reinstall ?? Boolean(options.preset);
|
|
330
1587
|
}
|
|
331
|
-
function
|
|
332
|
-
if (!
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
}
|
|
336
|
-
async function getLocalShadcnVersion(cwd) {
|
|
337
|
-
const filePath = path.resolve(cwd, "node_modules", "shadcn", "package.json");
|
|
338
|
-
try {
|
|
339
|
-
const contents = await promises.readFile(filePath, "utf8");
|
|
340
|
-
const parsed = JSON.parse(contents);
|
|
341
|
-
return typeof parsed.version === "string" ? parsed.version : null;
|
|
342
|
-
} catch {
|
|
343
|
-
return null;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
function getDeprecatedBaseColorWarning(baseColor) {
|
|
347
|
-
if (!baseColor) return null;
|
|
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"];
|
|
348
1592
|
return [
|
|
349
|
-
|
|
350
|
-
"
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
1593
|
+
...forwardedOptions.slice(0, passthroughIndex),
|
|
1594
|
+
"--reinstall",
|
|
1595
|
+
...forwardedOptions.slice(passthroughIndex)
|
|
1596
|
+
];
|
|
1597
|
+
}
|
|
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;
|
|
370
1621
|
}
|
|
371
|
-
const
|
|
372
|
-
if (
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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;
|
|
378
1653
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
return
|
|
1654
|
+
if (!token.startsWith("-")) continue;
|
|
1655
|
+
if (filteredFlags.has(token)) continue;
|
|
1656
|
+
forwarded.push(token);
|
|
1657
|
+
}
|
|
1658
|
+
return forwarded;
|
|
384
1659
|
}
|
|
385
|
-
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. (
|
|
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) => {
|
|
386
1661
|
try {
|
|
387
|
-
await runInit(
|
|
1662
|
+
await runInit({
|
|
1663
|
+
...opts,
|
|
1664
|
+
forwardedOptions: extractOptionsForShadcnInit(process.argv.slice(2), cmd)
|
|
1665
|
+
});
|
|
388
1666
|
} catch (error) {
|
|
389
1667
|
logger.break();
|
|
390
1668
|
handleError(error);
|
|
@@ -393,8 +1671,7 @@ const init = new Command().name("init").description("initialize your project and
|
|
|
393
1671
|
}
|
|
394
1672
|
});
|
|
395
1673
|
async function runInit(options) {
|
|
396
|
-
const
|
|
397
|
-
if (baseColorWarning && !options.silent) logger.warn(baseColorWarning);
|
|
1674
|
+
const designConfig = resolveDesignSystemConfig(options);
|
|
398
1675
|
let newProjectTemplate;
|
|
399
1676
|
if (!options.skipPreflight) {
|
|
400
1677
|
if ((await preFlightInit(options)).errors[MISSING_DIR_OR_EMPTY_PROJECT]) {
|
|
@@ -406,34 +1683,86 @@ async function runInit(options) {
|
|
|
406
1683
|
}
|
|
407
1684
|
}
|
|
408
1685
|
if (newProjectTemplate) {
|
|
409
|
-
|
|
1686
|
+
const projectPath = {
|
|
410
1687
|
"astro-monorepo": "apps/web",
|
|
411
1688
|
"astro-with-component-docs-monorepo": "apps/web",
|
|
412
1689
|
astro: ""
|
|
413
|
-
}
|
|
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]);
|
|
414
1699
|
logger.log(`${highlighter.success("Success!")} Project initialization completed.\nYou may now add components.`);
|
|
415
1700
|
return await getConfig(options.cwd);
|
|
416
1701
|
}
|
|
417
|
-
const shadcnBin = process.platform === "win32" ? "shadcn.cmd" : "shadcn";
|
|
418
|
-
const localShadcnPath = path.resolve(options.cwd, "node_modules", ".bin", shadcnBin);
|
|
419
1702
|
try {
|
|
420
1703
|
const env = {
|
|
421
1704
|
...process.env,
|
|
422
|
-
REGISTRY_URL:
|
|
1705
|
+
REGISTRY_URL: resolveRegistryUrl()
|
|
423
1706
|
};
|
|
424
|
-
const
|
|
425
|
-
const
|
|
426
|
-
await
|
|
427
|
-
|
|
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({
|
|
428
1712
|
cwd: options.cwd,
|
|
1713
|
+
args: [
|
|
1714
|
+
"init",
|
|
1715
|
+
initUrl,
|
|
1716
|
+
...reinstallComponents,
|
|
1717
|
+
...forwardedOptions
|
|
1718
|
+
],
|
|
429
1719
|
env
|
|
430
1720
|
});
|
|
431
|
-
|
|
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 {
|
|
432
1737
|
process.exit(1);
|
|
433
1738
|
}
|
|
434
|
-
await normalizeComponentsBaseColor(options.cwd);
|
|
435
1739
|
}
|
|
436
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
|
+
|
|
437
1766
|
//#endregion
|
|
438
1767
|
//#region src/utils/tsconfig-utils.ts
|
|
439
1768
|
/**
|
|
@@ -442,7 +1771,7 @@ async function runInit(options) {
|
|
|
442
1771
|
function readTsConfig(projectRoot) {
|
|
443
1772
|
try {
|
|
444
1773
|
const tsconfigPath = resolve(projectRoot, "tsconfig.json");
|
|
445
|
-
if (!existsSync
|
|
1774
|
+
if (!existsSync(tsconfigPath)) return null;
|
|
446
1775
|
const raw = readFileSync(tsconfigPath, "utf-8");
|
|
447
1776
|
return JSON.parse(raw);
|
|
448
1777
|
} catch {
|
|
@@ -471,7 +1800,7 @@ function resolveAliasPathUsingTsConfig(inputPath, projectRoot) {
|
|
|
471
1800
|
}
|
|
472
1801
|
|
|
473
1802
|
//#endregion
|
|
474
|
-
//#region src/commands/docs.ts
|
|
1803
|
+
//#region src/commands/docs-build.ts
|
|
475
1804
|
async function generateDocs({ cwd, outDir, verbose }) {
|
|
476
1805
|
const DEBUG = process.env.BEJAMAS_DEBUG === "1" || process.env.BEJAMAS_DEBUG === "true" || verbose;
|
|
477
1806
|
try {
|
|
@@ -480,7 +1809,7 @@ async function generateDocs({ cwd, outDir, verbose }) {
|
|
|
480
1809
|
let probe = shellCwd;
|
|
481
1810
|
for (let i = 0; i < 6 && probe; i += 1) {
|
|
482
1811
|
const candidate = resolve(probe, "components.json");
|
|
483
|
-
if (existsSync
|
|
1812
|
+
if (existsSync(candidate)) {
|
|
484
1813
|
projectRoot = probe;
|
|
485
1814
|
try {
|
|
486
1815
|
const raw = readFileSync(candidate, "utf-8");
|
|
@@ -510,7 +1839,7 @@ async function generateDocs({ cwd, outDir, verbose }) {
|
|
|
510
1839
|
if (mapped.startsWith("./") || mapped.startsWith("../") || isAbsolute(mapped)) outResolved = mapped;
|
|
511
1840
|
else {
|
|
512
1841
|
const abs = resolveAliasPathUsingTsConfig(mapped, projectRoot);
|
|
513
|
-
if (abs) outResolved = relative
|
|
1842
|
+
if (abs) outResolved = relative(projectRoot, abs);
|
|
514
1843
|
}
|
|
515
1844
|
if (!outResolved && mapped.startsWith("@/")) outResolved = mapped.replace(/^@\//, "src/");
|
|
516
1845
|
const finalOut = outResolved ?? mapped;
|
|
@@ -534,23 +1863,23 @@ async function generateDocs({ cwd, outDir, verbose }) {
|
|
|
534
1863
|
initial: (() => {
|
|
535
1864
|
let current = shellCwd;
|
|
536
1865
|
for (let i = 0; i < 6; i += 1) {
|
|
537
|
-
if (existsSync
|
|
1866
|
+
if (existsSync(resolve(current, "packages/ui/package.json"))) {
|
|
538
1867
|
const abs = resolve(current, "packages/ui");
|
|
539
|
-
return relative
|
|
1868
|
+
return relative(shellCwd, abs) || abs;
|
|
540
1869
|
}
|
|
541
1870
|
const parent = resolve(current, "..");
|
|
542
1871
|
if (parent === current) break;
|
|
543
1872
|
current = parent;
|
|
544
1873
|
}
|
|
545
|
-
if (existsSync
|
|
1874
|
+
if (existsSync(resolve(shellCwd, "node_modules/@bejamas/ui/package.json"))) {
|
|
546
1875
|
const abs = resolve(shellCwd, "node_modules/@bejamas/ui");
|
|
547
|
-
return relative
|
|
1876
|
+
return relative(shellCwd, abs) || abs;
|
|
548
1877
|
}
|
|
549
1878
|
return "packages/ui";
|
|
550
1879
|
})(),
|
|
551
1880
|
validate: (val) => {
|
|
552
1881
|
const p = resolve(shellCwd, val);
|
|
553
|
-
return existsSync
|
|
1882
|
+
return existsSync(resolve(p, "package.json")) ? true : `No package.json found in ${p}`;
|
|
554
1883
|
}
|
|
555
1884
|
});
|
|
556
1885
|
if (!uiRoot) {
|
|
@@ -583,7 +1912,7 @@ async function generateDocs({ cwd, outDir, verbose }) {
|
|
|
583
1912
|
if (process.env.BEJAMAS_DOCS_CWD) logger.info(`Docs CWD: ${process.env.BEJAMAS_DOCS_CWD}`);
|
|
584
1913
|
if (process.env.BEJAMAS_DOCS_OUT_DIR) logger.info(`Docs out: ${process.env.BEJAMAS_DOCS_OUT_DIR}`);
|
|
585
1914
|
}
|
|
586
|
-
const mod = await import("./generate-mdx-
|
|
1915
|
+
const mod = await import("./generate-mdx-PPNpe9_J.js");
|
|
587
1916
|
if (typeof mod.runDocsGenerator === "function") await mod.runDocsGenerator();
|
|
588
1917
|
else throw new Error("Failed to load docs generator. Export 'runDocsGenerator' not found.");
|
|
589
1918
|
} catch (err) {
|
|
@@ -591,7 +1920,7 @@ async function generateDocs({ cwd, outDir, verbose }) {
|
|
|
591
1920
|
process.exit(1);
|
|
592
1921
|
}
|
|
593
1922
|
}
|
|
594
|
-
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) => {
|
|
595
1924
|
await generateDocs({
|
|
596
1925
|
cwd: opts.cwd,
|
|
597
1926
|
outDir: opts.out,
|
|
@@ -651,7 +1980,7 @@ async function checkDocs({ cwd, json }) {
|
|
|
651
1980
|
let probe = shellCwd;
|
|
652
1981
|
for (let i = 0; i < 6 && probe; i += 1) {
|
|
653
1982
|
const candidate = resolve(probe, "components.json");
|
|
654
|
-
if (existsSync
|
|
1983
|
+
if (existsSync(candidate)) {
|
|
655
1984
|
projectRoot = probe;
|
|
656
1985
|
try {
|
|
657
1986
|
const raw = readFileSync(candidate, "utf-8");
|
|
@@ -689,19 +2018,19 @@ async function checkDocs({ cwd, json }) {
|
|
|
689
2018
|
logger.error("Unable to locate @bejamas/ui. Use --cwd to specify the UI package path.");
|
|
690
2019
|
process.exit(1);
|
|
691
2020
|
}
|
|
692
|
-
const componentsDir = join
|
|
693
|
-
if (!existsSync
|
|
2021
|
+
const componentsDir = join(uiRoot, "src", "components");
|
|
2022
|
+
if (!existsSync(componentsDir)) {
|
|
694
2023
|
logger.error(`Components directory not found: ${componentsDir}\n\nExpected structure: <uiRoot>/src/components/*.astro\nUse --cwd to specify a different UI package root.`);
|
|
695
2024
|
process.exit(1);
|
|
696
2025
|
}
|
|
697
|
-
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();
|
|
698
2027
|
if (files.length === 0) {
|
|
699
2028
|
logger.warn("No .astro component files found.");
|
|
700
2029
|
process.exit(0);
|
|
701
2030
|
}
|
|
702
2031
|
const results = [];
|
|
703
2032
|
for (const file of files) {
|
|
704
|
-
const status = checkComponentDocs(join
|
|
2033
|
+
const status = checkComponentDocs(join(componentsDir, file), file);
|
|
705
2034
|
results.push(status);
|
|
706
2035
|
}
|
|
707
2036
|
const complete = results.filter((r) => r.status === "complete");
|
|
@@ -768,174 +2097,8 @@ const docsCheck = new Command().name("docs:check").description("check documentat
|
|
|
768
2097
|
});
|
|
769
2098
|
});
|
|
770
2099
|
|
|
771
|
-
//#endregion
|
|
772
|
-
//#region src/utils/astro-imports.ts
|
|
773
|
-
function updateImportAliases(moduleSpecifier, config, isRemote = false) {
|
|
774
|
-
if (!moduleSpecifier.startsWith("@/") && !isRemote) return moduleSpecifier;
|
|
775
|
-
let specifier = moduleSpecifier;
|
|
776
|
-
if (isRemote && specifier.startsWith("@/")) specifier = specifier.replace(/^@\//, "@/registry/new-york/");
|
|
777
|
-
if (!specifier.startsWith("@/registry/")) {
|
|
778
|
-
const alias = config.aliases.components.split("/")[0];
|
|
779
|
-
return specifier.replace(/^@\//, `${alias}/`);
|
|
780
|
-
}
|
|
781
|
-
if (specifier.match(/^@\/registry\/(.+)\/ui/)) return specifier.replace(/^@\/registry\/(.+)\/ui/, config.aliases.ui ?? `${config.aliases.components}/ui`);
|
|
782
|
-
if (config.aliases.components && specifier.match(/^@\/registry\/(.+)\/components/)) return specifier.replace(/^@\/registry\/(.+)\/components/, config.aliases.components);
|
|
783
|
-
if (config.aliases.lib && specifier.match(/^@\/registry\/(.+)\/lib/)) return specifier.replace(/^@\/registry\/(.+)\/lib/, config.aliases.lib);
|
|
784
|
-
if (config.aliases.hooks && specifier.match(/^@\/registry\/(.+)\/hooks/)) return specifier.replace(/^@\/registry\/(.+)\/hooks/, config.aliases.hooks);
|
|
785
|
-
return specifier.replace(/^@\/registry\/[^/]+/, config.aliases.components);
|
|
786
|
-
}
|
|
787
|
-
function rewriteAstroImports(content, config) {
|
|
788
|
-
let updated = content;
|
|
789
|
-
const utilsAlias = config.aliases?.utils;
|
|
790
|
-
const utilsImport = `${typeof utilsAlias === "string" && utilsAlias.includes("/") ? utilsAlias.split("/")[0] : "@"}/lib/utils`;
|
|
791
|
-
updated = updated.replace(/import\s+([\s\S]*?)\s+from\s+["']([^"']+)["']/g, (full, importsPart, specifier) => {
|
|
792
|
-
const next = updateImportAliases(specifier, config, false);
|
|
793
|
-
let finalSpec = next;
|
|
794
|
-
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;
|
|
795
|
-
if (finalSpec === specifier) return full;
|
|
796
|
-
return full.replace(specifier, finalSpec);
|
|
797
|
-
});
|
|
798
|
-
updated = updated.replace(/import\s+["']([^"']+)["']/g, (full, specifier) => {
|
|
799
|
-
const next = updateImportAliases(specifier, config, false);
|
|
800
|
-
if (next === specifier) return full;
|
|
801
|
-
return full.replace(specifier, next);
|
|
802
|
-
});
|
|
803
|
-
return updated;
|
|
804
|
-
}
|
|
805
|
-
async function fixAstroImports(cwd, isVerbose) {
|
|
806
|
-
const config = await getConfig(cwd);
|
|
807
|
-
if (!config) return;
|
|
808
|
-
const searchRoots = new Set([config.resolvedPaths.components, config.resolvedPaths.ui]);
|
|
809
|
-
for (const root of Array.from(searchRoots)) {
|
|
810
|
-
if (!root) continue;
|
|
811
|
-
const astroFiles = await fg("**/*.astro", {
|
|
812
|
-
cwd: root,
|
|
813
|
-
absolute: true,
|
|
814
|
-
dot: false
|
|
815
|
-
});
|
|
816
|
-
for (const filePath of astroFiles) {
|
|
817
|
-
const original = await fs.readFile(filePath, "utf8");
|
|
818
|
-
const rewritten = rewriteAstroImports(original, config);
|
|
819
|
-
if (rewritten === original) continue;
|
|
820
|
-
await fs.writeFile(filePath, rewritten, "utf8");
|
|
821
|
-
if (isVerbose) logger.info(`[bejamas-ui] fixed imports in ${path$1.relative(cwd, filePath)}`);
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
//#endregion
|
|
827
|
-
//#region src/utils/reorganize-components.ts
|
|
828
|
-
/**
|
|
829
|
-
* Fetches a registry item JSON from the registry URL.
|
|
830
|
-
*/
|
|
831
|
-
async function fetchRegistryItem(componentName, registryUrl) {
|
|
832
|
-
const url = `${registryUrl}/styles/new-york-v4/${componentName}.json`;
|
|
833
|
-
try {
|
|
834
|
-
const response = await fetch(url);
|
|
835
|
-
if (!response.ok) {
|
|
836
|
-
const fallbackUrl = `${registryUrl}/${componentName}.json`;
|
|
837
|
-
const fallbackResponse = await fetch(fallbackUrl);
|
|
838
|
-
if (!fallbackResponse.ok) return null;
|
|
839
|
-
return await fallbackResponse.json();
|
|
840
|
-
}
|
|
841
|
-
return await response.json();
|
|
842
|
-
} catch {
|
|
843
|
-
return null;
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
/**
|
|
847
|
-
* Extracts the subfolder name from registry file paths.
|
|
848
|
-
* E.g., "components/ui/avatar/Avatar.astro" → "avatar"
|
|
849
|
-
*/
|
|
850
|
-
function getSubfolderFromPaths(files) {
|
|
851
|
-
const uiFiles = files.filter((f) => f.type === "registry:ui");
|
|
852
|
-
if (uiFiles.length < 2) return null;
|
|
853
|
-
const subfolders = /* @__PURE__ */ new Set();
|
|
854
|
-
for (const file of uiFiles) {
|
|
855
|
-
const parts = file.path.split("/");
|
|
856
|
-
const uiIndex = parts.indexOf("ui");
|
|
857
|
-
if (uiIndex !== -1 && parts.length > uiIndex + 2) subfolders.add(parts[uiIndex + 1]);
|
|
858
|
-
}
|
|
859
|
-
if (subfolders.size === 1) return Array.from(subfolders)[0];
|
|
860
|
-
if (uiFiles.length > 0) {
|
|
861
|
-
const firstPath = uiFiles[0].path;
|
|
862
|
-
const dirname$1 = path$1.dirname(firstPath);
|
|
863
|
-
const folderName = path$1.basename(dirname$1);
|
|
864
|
-
if (folderName && folderName !== "ui") return folderName;
|
|
865
|
-
}
|
|
866
|
-
return null;
|
|
867
|
-
}
|
|
868
|
-
/**
|
|
869
|
-
* Checks if a path exists.
|
|
870
|
-
*/
|
|
871
|
-
async function pathExists(filePath) {
|
|
872
|
-
try {
|
|
873
|
-
await fs.access(filePath);
|
|
874
|
-
return true;
|
|
875
|
-
} catch {
|
|
876
|
-
return false;
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
/**
|
|
880
|
-
* Reorganizes multi-file components into correct subfolders.
|
|
881
|
-
* Only moves files from FLAT location to subfolder.
|
|
882
|
-
* Does NOT touch files already in subfolders.
|
|
883
|
-
* E.g., moves `uiDir/Avatar.astro` to `uiDir/avatar/Avatar.astro`.
|
|
884
|
-
* Returns info about moved files for display purposes.
|
|
885
|
-
*/
|
|
886
|
-
async function reorganizeComponents(components, uiDir, registryUrl, verbose) {
|
|
887
|
-
const result = {
|
|
888
|
-
totalMoved: 0,
|
|
889
|
-
movedFiles: [],
|
|
890
|
-
skippedFiles: []
|
|
891
|
-
};
|
|
892
|
-
if (!uiDir || components.length === 0) return result;
|
|
893
|
-
for (const componentName of components) try {
|
|
894
|
-
const registryItem = await fetchRegistryItem(componentName, registryUrl);
|
|
895
|
-
if (!registryItem) {
|
|
896
|
-
if (verbose) logger.info(`[bejamas-ui] Could not fetch registry for ${componentName}, skipping reorganization`);
|
|
897
|
-
continue;
|
|
898
|
-
}
|
|
899
|
-
const subfolder = getSubfolderFromPaths(registryItem.files);
|
|
900
|
-
if (!subfolder) {
|
|
901
|
-
if (verbose) logger.info(`[bejamas-ui] ${componentName} is single-file or has no subfolder, skipping`);
|
|
902
|
-
continue;
|
|
903
|
-
}
|
|
904
|
-
const uiFiles = registryItem.files.filter((f) => f.type === "registry:ui");
|
|
905
|
-
const targetDir = path$1.join(uiDir, subfolder);
|
|
906
|
-
let movedCount = 0;
|
|
907
|
-
for (const file of uiFiles) {
|
|
908
|
-
const filename = path$1.basename(file.path);
|
|
909
|
-
const flatPath = path$1.join(uiDir, filename);
|
|
910
|
-
const targetPath = path$1.join(targetDir, filename);
|
|
911
|
-
if (!await pathExists(flatPath)) continue;
|
|
912
|
-
if (await pathExists(targetPath)) {
|
|
913
|
-
try {
|
|
914
|
-
await fs.unlink(flatPath);
|
|
915
|
-
result.skippedFiles.push(`${subfolder}/${filename}`);
|
|
916
|
-
if (verbose) logger.info(`[bejamas-ui] Removed flat duplicate: ${filename} (${subfolder}/${filename} exists)`);
|
|
917
|
-
} catch {
|
|
918
|
-
result.skippedFiles.push(`${subfolder}/${filename}`);
|
|
919
|
-
}
|
|
920
|
-
continue;
|
|
921
|
-
}
|
|
922
|
-
await fs.mkdir(targetDir, { recursive: true });
|
|
923
|
-
await fs.rename(flatPath, targetPath);
|
|
924
|
-
movedCount++;
|
|
925
|
-
result.totalMoved++;
|
|
926
|
-
result.movedFiles.push(`${subfolder}/${filename}`);
|
|
927
|
-
if (verbose) logger.info(`[bejamas-ui] Moved ${filename} → ${subfolder}/${filename}`);
|
|
928
|
-
}
|
|
929
|
-
if (movedCount > 0 && verbose) logger.info(`[bejamas-ui] Reorganized ${componentName} into ${subfolder}/`);
|
|
930
|
-
} catch (err) {
|
|
931
|
-
if (verbose) logger.warn(`[bejamas-ui] Failed to reorganize ${componentName}: ${err}`);
|
|
932
|
-
}
|
|
933
|
-
return result;
|
|
934
|
-
}
|
|
935
|
-
|
|
936
2100
|
//#endregion
|
|
937
2101
|
//#region src/commands/add.ts
|
|
938
|
-
const DEFAULT_REGISTRY_URL = "https://ui.bejamas.com/r";
|
|
939
2102
|
function extractOptionsForShadcn(rawArgv, cmd) {
|
|
940
2103
|
if (typeof cmd.getOptionValueSource === "function") {
|
|
941
2104
|
const opts = cmd.optsWithGlobals();
|
|
@@ -953,13 +2116,25 @@ function extractOptionsForShadcn(rawArgv, cmd) {
|
|
|
953
2116
|
const value = opts[key];
|
|
954
2117
|
if (typeof value === "string") forwarded$1.push(flag, value);
|
|
955
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
|
+
};
|
|
956
2128
|
addBoolean("yes", "--yes");
|
|
957
2129
|
addBoolean("overwrite", "--overwrite");
|
|
2130
|
+
addBoolean("dryRun", "--dry-run");
|
|
958
2131
|
addString("cwd", "--cwd");
|
|
959
2132
|
addBoolean("all", "--all");
|
|
960
2133
|
addString("path", "--path");
|
|
961
2134
|
addBoolean("silent", "--silent");
|
|
962
2135
|
addBoolean("srcDir", "--src-dir", "--no-src-dir");
|
|
2136
|
+
addOptionalString("diff", "--diff");
|
|
2137
|
+
addOptionalString("view", "--view");
|
|
963
2138
|
const addIndex$1 = rawArgv.findIndex((arg) => arg === "add");
|
|
964
2139
|
if (addIndex$1 !== -1) {
|
|
965
2140
|
const rest$1 = rawArgv.slice(addIndex$1 + 1);
|
|
@@ -976,13 +2151,15 @@ function extractOptionsForShadcn(rawArgv, cmd) {
|
|
|
976
2151
|
"-c",
|
|
977
2152
|
"--cwd",
|
|
978
2153
|
"-p",
|
|
979
|
-
"--path"
|
|
2154
|
+
"--path",
|
|
2155
|
+
"--diff",
|
|
2156
|
+
"--view"
|
|
980
2157
|
]);
|
|
981
2158
|
const filteredFlags = new Set(["-v", "--verbose"]);
|
|
982
|
-
for (let
|
|
983
|
-
const token = rest[
|
|
2159
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
2160
|
+
const token = rest[index];
|
|
984
2161
|
if (token === "--") {
|
|
985
|
-
forwarded.push("--", ...rest.slice(
|
|
2162
|
+
forwarded.push("--", ...rest.slice(index + 1));
|
|
986
2163
|
break;
|
|
987
2164
|
}
|
|
988
2165
|
if (!token.startsWith("-")) continue;
|
|
@@ -990,36 +2167,38 @@ function extractOptionsForShadcn(rawArgv, cmd) {
|
|
|
990
2167
|
forwarded.push(token);
|
|
991
2168
|
if (token.includes("=")) continue;
|
|
992
2169
|
if (optionsWithValues.has(token)) {
|
|
993
|
-
const next = rest[
|
|
2170
|
+
const next = rest[index + 1];
|
|
994
2171
|
if (next) {
|
|
995
2172
|
forwarded.push(next);
|
|
996
|
-
|
|
2173
|
+
index += 1;
|
|
997
2174
|
}
|
|
998
2175
|
}
|
|
999
2176
|
}
|
|
1000
2177
|
return forwarded;
|
|
1001
2178
|
}
|
|
1002
|
-
|
|
1003
|
-
|
|
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) {
|
|
1004
2188
|
const filenameToSubfolders = /* @__PURE__ */ new Map();
|
|
1005
|
-
|
|
2189
|
+
let requiresReorganization = false;
|
|
1006
2190
|
for (const componentName of components) {
|
|
1007
|
-
const registryItem = await fetchRegistryItem(componentName, registryUrl);
|
|
2191
|
+
const registryItem = await fetchRegistryItem(componentName, registryUrl, style);
|
|
1008
2192
|
if (!registryItem) continue;
|
|
2193
|
+
if (shouldReorganizeRegistryUiFiles(registryItem.files, uiDir)) requiresReorganization = true;
|
|
1009
2194
|
const subfolder = getSubfolderFromPaths(registryItem.files);
|
|
1010
2195
|
if (!subfolder) continue;
|
|
1011
|
-
const files = [];
|
|
1012
2196
|
for (const file of registryItem.files) if (file.type === "registry:ui") {
|
|
1013
|
-
const filename = path
|
|
1014
|
-
files.push(filename);
|
|
2197
|
+
const filename = path.basename(file.path);
|
|
1015
2198
|
const subfolders = filenameToSubfolders.get(filename) || [];
|
|
1016
2199
|
subfolders.push(subfolder);
|
|
1017
2200
|
filenameToSubfolders.set(filename, subfolders);
|
|
1018
2201
|
}
|
|
1019
|
-
componentInfo.set(subfolder, {
|
|
1020
|
-
subfolder,
|
|
1021
|
-
files
|
|
1022
|
-
});
|
|
1023
2202
|
}
|
|
1024
2203
|
const uniqueMap = /* @__PURE__ */ new Map();
|
|
1025
2204
|
const sharedFilenames = /* @__PURE__ */ new Set();
|
|
@@ -1029,35 +2208,29 @@ async function buildSubfolderMap(components, registryUrl) {
|
|
|
1029
2208
|
});
|
|
1030
2209
|
return {
|
|
1031
2210
|
uniqueMap,
|
|
1032
|
-
|
|
1033
|
-
|
|
2211
|
+
sharedFilenames,
|
|
2212
|
+
requiresReorganization
|
|
1034
2213
|
};
|
|
1035
2214
|
}
|
|
1036
|
-
/**
|
|
1037
|
-
* Rewrite file paths to include correct subfolders.
|
|
1038
|
-
* Handles shared filenames (like index.ts) by tracking current component context.
|
|
1039
|
-
*/
|
|
1040
2215
|
function rewritePaths(paths, mapResult) {
|
|
1041
|
-
const { uniqueMap,
|
|
2216
|
+
const { uniqueMap, sharedFilenames } = mapResult;
|
|
1042
2217
|
let currentSubfolder = null;
|
|
1043
2218
|
return paths.map((filePath) => {
|
|
1044
|
-
const filename = path
|
|
1045
|
-
const parentDir = path
|
|
2219
|
+
const filename = path.basename(filePath);
|
|
2220
|
+
const parentDir = path.basename(path.dirname(filePath));
|
|
1046
2221
|
const uniqueMapping = uniqueMap.get(filename);
|
|
1047
2222
|
if (uniqueMapping) {
|
|
1048
|
-
const expectedSubfolder = path
|
|
2223
|
+
const expectedSubfolder = path.dirname(uniqueMapping);
|
|
1049
2224
|
currentSubfolder = expectedSubfolder;
|
|
1050
|
-
if (parentDir !== expectedSubfolder) return `${path
|
|
2225
|
+
if (parentDir !== expectedSubfolder) return `${path.dirname(filePath)}/${uniqueMapping}`;
|
|
1051
2226
|
return filePath;
|
|
1052
2227
|
}
|
|
1053
2228
|
if (sharedFilenames.has(filename) && currentSubfolder) {
|
|
1054
|
-
|
|
1055
|
-
if (parentDir !== expectedSubfolder) return `${path$1.dirname(filePath)}/${expectedSubfolder}/${filename}`;
|
|
2229
|
+
if (parentDir !== currentSubfolder) return `${path.dirname(filePath)}/${currentSubfolder}/${filename}`;
|
|
1056
2230
|
}
|
|
1057
2231
|
return filePath;
|
|
1058
2232
|
});
|
|
1059
2233
|
}
|
|
1060
|
-
/** Fetch available components from the registry */
|
|
1061
2234
|
async function fetchAvailableComponents(registryUrl) {
|
|
1062
2235
|
const indexUrl = `${registryUrl}/index.json`;
|
|
1063
2236
|
const response = await fetch(indexUrl);
|
|
@@ -1065,14 +2238,13 @@ async function fetchAvailableComponents(registryUrl) {
|
|
|
1065
2238
|
const data = await response.json();
|
|
1066
2239
|
return Array.isArray(data) ? data : [];
|
|
1067
2240
|
}
|
|
1068
|
-
/** Prompt user to select components interactively */
|
|
1069
2241
|
async function promptForComponents(registryUrl) {
|
|
1070
2242
|
const checkingSpinner = spinner("Checking registry.").start();
|
|
1071
2243
|
let components;
|
|
1072
2244
|
try {
|
|
1073
2245
|
components = await fetchAvailableComponents(registryUrl);
|
|
1074
2246
|
checkingSpinner.succeed();
|
|
1075
|
-
} catch
|
|
2247
|
+
} catch {
|
|
1076
2248
|
checkingSpinner.fail();
|
|
1077
2249
|
logger.error("Failed to fetch available components from registry.");
|
|
1078
2250
|
return null;
|
|
@@ -1085,9 +2257,9 @@ async function promptForComponents(registryUrl) {
|
|
|
1085
2257
|
type: "autocompleteMultiselect",
|
|
1086
2258
|
name: "selected",
|
|
1087
2259
|
message: "Which components would you like to add?",
|
|
1088
|
-
choices: components.filter((
|
|
1089
|
-
title:
|
|
1090
|
-
value:
|
|
2260
|
+
choices: components.filter((component) => !component.type || component.type === "registry:ui").map((component) => ({
|
|
2261
|
+
title: component.name,
|
|
2262
|
+
value: component.name
|
|
1091
2263
|
})),
|
|
1092
2264
|
hint: "- Space to select. Return to submit.",
|
|
1093
2265
|
instructions: false
|
|
@@ -1095,7 +2267,6 @@ async function promptForComponents(registryUrl) {
|
|
|
1095
2267
|
if (!selected) return null;
|
|
1096
2268
|
return selected;
|
|
1097
2269
|
}
|
|
1098
|
-
/** Parse shadcn output to extract file lists (stdout has paths, stderr has headers) */
|
|
1099
2270
|
function parseShadcnOutput(stdout, stderr) {
|
|
1100
2271
|
const result = {
|
|
1101
2272
|
created: [],
|
|
@@ -1122,44 +2293,34 @@ function parseShadcnOutput(stdout, stderr) {
|
|
|
1122
2293
|
if (!allPaths.includes(filePath)) allPaths.push(filePath);
|
|
1123
2294
|
}
|
|
1124
2295
|
}
|
|
1125
|
-
let
|
|
1126
|
-
for (let
|
|
1127
|
-
|
|
1128
|
-
|
|
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
|
+
}
|
|
1129
2309
|
return result;
|
|
1130
2310
|
}
|
|
1131
|
-
async function addComponents(packages, forwardedOptions, isVerbose, isSilent,
|
|
1132
|
-
const runner = await getPackageRunner(process.cwd());
|
|
2311
|
+
async function addComponents(cwd, packages, forwardedOptions, isVerbose, isSilent, inspectionMode) {
|
|
1133
2312
|
const env = {
|
|
1134
2313
|
...process.env,
|
|
1135
|
-
REGISTRY_URL:
|
|
2314
|
+
REGISTRY_URL: resolveRegistryUrl()
|
|
1136
2315
|
};
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
"shadcn@latest",
|
|
1141
|
-
"add",
|
|
1142
|
-
...packages,
|
|
1143
|
-
...autoFlags,
|
|
1144
|
-
...forwardedOptions
|
|
1145
|
-
];
|
|
1146
|
-
let cmd = "npx";
|
|
1147
|
-
let args = ["-y", ...baseArgs];
|
|
1148
|
-
if (runner === "bunx") {
|
|
1149
|
-
cmd = "bunx";
|
|
1150
|
-
args = baseArgs;
|
|
1151
|
-
} else if (runner === "pnpm dlx") {
|
|
1152
|
-
cmd = "pnpm";
|
|
1153
|
-
args = ["dlx", ...baseArgs];
|
|
1154
|
-
} else if (runner === "npx") {
|
|
1155
|
-
cmd = "npx";
|
|
1156
|
-
args = ["-y", ...baseArgs];
|
|
1157
|
-
}
|
|
1158
|
-
if (isVerbose) logger.info(`[bejamas-ui] ${cmd} ${args.join(" ")}`);
|
|
2316
|
+
await ensurePinnedShadcnExecPrefix();
|
|
2317
|
+
const invocation = buildPinnedShadcnInvocation(buildShadcnAddArgs(packages, forwardedOptions));
|
|
2318
|
+
if (isVerbose) logger.info(`[bejamas-ui] ${invocation.cmd} ${invocation.args.join(" ")}`);
|
|
1159
2319
|
const registrySpinner = spinner("Checking registry.", { silent: isSilent });
|
|
1160
2320
|
registrySpinner.start();
|
|
1161
2321
|
try {
|
|
1162
|
-
const result = await execa(cmd, args, {
|
|
2322
|
+
const result = await execa(invocation.cmd, invocation.args, {
|
|
2323
|
+
cwd,
|
|
1163
2324
|
env,
|
|
1164
2325
|
input: "n\nn\nn\nn\nn\nn\nn\nn\nn\nn\n",
|
|
1165
2326
|
stdout: "pipe",
|
|
@@ -1174,34 +2335,54 @@ async function addComponents(packages, forwardedOptions, isVerbose, isSilent, su
|
|
|
1174
2335
|
logger.info(`[bejamas-ui] Raw stdout: ${stdout}`);
|
|
1175
2336
|
logger.info(`[bejamas-ui] Raw stderr: ${stderr}`);
|
|
1176
2337
|
}
|
|
1177
|
-
|
|
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);
|
|
1178
2347
|
if (result.exitCode !== 0) {
|
|
1179
2348
|
if (result.stderr) logger.error(result.stderr);
|
|
1180
2349
|
process.exit(result.exitCode);
|
|
1181
2350
|
}
|
|
1182
2351
|
return parsed;
|
|
1183
|
-
} catch
|
|
2352
|
+
} catch {
|
|
1184
2353
|
registrySpinner.fail();
|
|
1185
2354
|
logger.error("Failed to add components");
|
|
1186
2355
|
process.exit(1);
|
|
1187
2356
|
}
|
|
1188
2357
|
}
|
|
1189
|
-
|
|
2358
|
+
function buildShadcnAddArgs(packages, forwardedOptions) {
|
|
2359
|
+
const autoFlags = [];
|
|
2360
|
+
if (!forwardedOptions.includes("--yes")) autoFlags.push("--yes");
|
|
2361
|
+
return [
|
|
2362
|
+
"add",
|
|
2363
|
+
...packages,
|
|
2364
|
+
...autoFlags,
|
|
2365
|
+
...forwardedOptions
|
|
2366
|
+
];
|
|
2367
|
+
}
|
|
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) {
|
|
1190
2369
|
const root = cmd?.parent;
|
|
1191
2370
|
const verbose = Boolean(root?.opts?.().verbose);
|
|
1192
2371
|
const forwardedOptions = extractOptionsForShadcn(process.argv.slice(2), cmd);
|
|
1193
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");
|
|
1194
2375
|
const cwd = opts.cwd || process.cwd();
|
|
1195
2376
|
let componentsToAdd = packages || [];
|
|
1196
2377
|
const wantsAll = Boolean(opts.all);
|
|
1197
2378
|
const isSilent = opts.silent || false;
|
|
1198
|
-
const registryUrl =
|
|
2379
|
+
const registryUrl = resolveRegistryUrl();
|
|
1199
2380
|
if (wantsAll && componentsToAdd.length === 0) {
|
|
1200
2381
|
const fetchingSpinner = spinner("Fetching available components.", { silent: isSilent }).start();
|
|
1201
2382
|
try {
|
|
1202
|
-
componentsToAdd = (await fetchAvailableComponents(registryUrl)).filter((
|
|
2383
|
+
componentsToAdd = (await fetchAvailableComponents(registryUrl)).filter((component) => !component.type || component.type === "registry:ui").map((component) => component.name);
|
|
1203
2384
|
fetchingSpinner.succeed();
|
|
1204
|
-
} catch
|
|
2385
|
+
} catch {
|
|
1205
2386
|
fetchingSpinner.fail();
|
|
1206
2387
|
logger.error("Failed to fetch available components from registry.");
|
|
1207
2388
|
process.exit(1);
|
|
@@ -1227,19 +2408,35 @@ const add = new Command().name("add").description("Add components via shadcn@lat
|
|
|
1227
2408
|
logger.info(`[bejamas-ui] uiDir: ${uiDir}`);
|
|
1228
2409
|
logger.info(`[bejamas-ui] aliases.ui: ${uiConfig?.aliases?.ui || "not set"}`);
|
|
1229
2410
|
}
|
|
2411
|
+
const activeStyle = uiConfig?.style || config?.style || "bejamas-juno";
|
|
1230
2412
|
const totalComponents = componentsToAdd.length;
|
|
1231
|
-
for (let
|
|
1232
|
-
const component = componentsToAdd[
|
|
2413
|
+
for (let index = 0; index < componentsToAdd.length; index += 1) {
|
|
2414
|
+
const component = componentsToAdd[index];
|
|
1233
2415
|
if (totalComponents > 1 && !isSilent) {
|
|
1234
2416
|
logger.break();
|
|
1235
|
-
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
|
+
}
|
|
1236
2436
|
}
|
|
1237
|
-
const subfolderMapResult = await buildSubfolderMap([component], registryUrl);
|
|
1238
|
-
const parsed = await addComponents([component], forwardedOptions, verbose, isSilent, subfolderMapResult);
|
|
1239
2437
|
let skippedCount = 0;
|
|
1240
|
-
if (uiDir) skippedCount = (await reorganizeComponents([component], uiDir, registryUrl, verbose)).skippedFiles.length;
|
|
1241
|
-
if (!isSilent) {
|
|
1242
|
-
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) {
|
|
1243
2440
|
const actuallyCreated = Math.max(0, parsed.created.length - skippedCount);
|
|
1244
2441
|
if (actuallyCreated > 0) {
|
|
1245
2442
|
const createdPaths = rewritePaths(parsed.created.slice(0, actuallyCreated), subfolderMapResult);
|
|
@@ -1254,13 +2451,36 @@ const add = new Command().name("add").description("Add components via shadcn@lat
|
|
|
1254
2451
|
if (skippedCount > 0) logger.info(`Skipped ${skippedCount} file${skippedCount > 1 ? "s" : ""}: (already exists)`);
|
|
1255
2452
|
if (parsed.skipped.length > 0) {
|
|
1256
2453
|
const skippedPaths = rewritePaths(parsed.skipped, subfolderMapResult);
|
|
1257
|
-
logger.info(
|
|
2454
|
+
logger.info(formatSkippedFilesHeading(skippedPaths.length, overwriteUsed));
|
|
1258
2455
|
for (const file of skippedPaths) logger.log(` ${highlighter.info("-")} ${file}`);
|
|
1259
2456
|
}
|
|
1260
2457
|
if (actuallyCreated === 0 && parsed.updated.length === 0 && skippedCount === 0 && parsed.skipped.length === 0) logger.info("Already up to date.");
|
|
1261
2458
|
}
|
|
1262
2459
|
}
|
|
1263
|
-
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
|
+
}
|
|
1264
2484
|
});
|
|
1265
2485
|
|
|
1266
2486
|
//#endregion
|
|
@@ -1269,7 +2489,9 @@ const pkg = createRequire(import.meta.url)("../package.json");
|
|
|
1269
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");
|
|
1270
2490
|
program.addCommand(init);
|
|
1271
2491
|
program.addCommand(add);
|
|
2492
|
+
program.addCommand(info);
|
|
1272
2493
|
program.addCommand(docs);
|
|
2494
|
+
program.addCommand(docsBuild);
|
|
1273
2495
|
program.addCommand(docsCheck);
|
|
1274
2496
|
program.parse(process.argv);
|
|
1275
2497
|
var src_default = program;
|