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