@frontic/ui 0.3.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-DmrBFC4C.d.ts +313 -0
- package/dist/api-DmrBFC4C.d.ts.map +1 -0
- package/dist/index-Cd5RBhtD.d.ts +1480 -0
- package/dist/index-Cd5RBhtD.d.ts.map +1 -0
- package/dist/index.d.ts +4 -6
- package/dist/index.js +2411 -555
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.d.ts +46 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +5 -0
- package/dist/mcp-CxUIP4Qu.js +304 -0
- package/dist/mcp-CxUIP4Qu.js.map +1 -0
- package/dist/registry/index.d.ts +113 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +4 -0
- package/dist/registry-CgE-Q6HO.js +2638 -0
- package/dist/registry-CgE-Q6HO.js.map +1 -0
- package/dist/schema/index.d.ts +2 -0
- package/dist/schema/index.js +3 -0
- package/dist/schema-DgHN-d0T.js +155 -0
- package/dist/schema-DgHN-d0T.js.map +1 -0
- package/package.json +68 -14
- package/dist/bin/frontic-ui.js +0 -590
- package/dist/bin/frontic-ui.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,589 +1,2445 @@
|
|
|
1
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { A as mergeEnvContent, B as DEFAULT_TAILWIND_CSS, C as configWithDefaults, D as ICON_LIBRARIES, E as transform, F as getProjectInfo, G as getConfig, H as createConfig, I as getProjectTailwindVersionFromConfig, K as getWorkspaceConfig, L as getPackageInfo, M as spinner, N as logger, O as findExistingEnvFile, P as getProjectConfig, R as DEFAULT_COMPONENTS, S as clearRegistryContext, T as updateFiles, U as findCommonRoot, V as DEFAULT_UTILS, W as findPackageRoot, _ as _createSourceFile, a as getRegistriesIndex, at as highlighter, b as handleError, c as getRegistryBaseColors, ct as DEPRECATED_COMPONENTS, d as getRegistryStyles, f as getShadcnRegistryIndex, g as resolveRegistryTree, h as fetchRegistryItems, i as getRegistriesConfig, j as parseRegistryAndItemFromString, k as getNewEnvKeys, l as getRegistryIcons, lt as REGISTRY_URL, m as resolveTree, n as fetchTree, o as getRegistry, ot as BASE_COLORS, p as resolveRegistryItems, q as resolveConfigPaths, r as getItemTargetPath, s as getRegistryBaseColor, st as BUILTIN_REGISTRIES, tt as RegistryNotConfiguredError, u as getRegistryItems, v as _getQuoteChar, w as isUniversalRegistryItem, x as buildUrlAndHeadersForRegistryItem, y as updateTailwindConfig, z as DEFAULT_TAILWIND_CONFIG } from "./registry-CgE-Q6HO.js";
|
|
3
|
+
import { _ as registrySchema, p as registryItemSchema, r as rawConfigSchema } from "./schema-DgHN-d0T.js";
|
|
4
|
+
import { t as server } from "./mcp-CxUIP4Qu.js";
|
|
2
5
|
import { Command } from "commander";
|
|
3
|
-
|
|
4
|
-
// src/commands/init.ts
|
|
5
|
-
import chalk from "chalk";
|
|
6
|
-
import ora from "ora";
|
|
6
|
+
import path, { isAbsolute, join, normalize, resolve, sep } from "pathe";
|
|
7
7
|
import prompts from "prompts";
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
proc.stderr?.on("data", (data) => {
|
|
26
|
-
process.stderr.write(
|
|
27
|
-
data.toString().replace(/shadcn-vue/gi, "frontic-ui")
|
|
28
|
-
);
|
|
29
|
-
});
|
|
30
|
-
await proc;
|
|
31
|
-
} else {
|
|
32
|
-
await execa("npx", ["shadcn-vue@latest", ...args], {
|
|
33
|
-
stdio: silent ? "pipe" : "inherit",
|
|
34
|
-
shell: true
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
} catch (error) {
|
|
38
|
-
if (!silent) {
|
|
39
|
-
throw error;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
8
|
+
import z$1, { z } from "zod";
|
|
9
|
+
import { existsSync, promises } from "fs";
|
|
10
|
+
import deepmerge from "deepmerge";
|
|
11
|
+
import fsExtra from "fs-extra";
|
|
12
|
+
import { glob } from "tinyglobby";
|
|
13
|
+
import consola from "consola";
|
|
14
|
+
import * as fs from "fs/promises";
|
|
15
|
+
import { tmpdir } from "os";
|
|
16
|
+
import { Project, ScriptKind, SyntaxKind } from "ts-morph";
|
|
17
|
+
import { randomBytes } from "crypto";
|
|
18
|
+
import postcss from "postcss";
|
|
19
|
+
import AtRule from "postcss/lib/at-rule";
|
|
20
|
+
import { addDependency, detectPackageManager } from "nypm";
|
|
21
|
+
import * as path$1 from "path";
|
|
22
|
+
import { diffLines } from "diff";
|
|
23
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
24
|
+
import { x } from "tinyexec";
|
|
43
25
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
26
|
+
//#region src/utils/errors.ts
|
|
27
|
+
const MISSING_DIR_OR_EMPTY_PROJECT = "1";
|
|
28
|
+
const MISSING_CONFIG = "3";
|
|
29
|
+
const TAILWIND_NOT_CONFIGURED = "5";
|
|
30
|
+
const IMPORT_ALIAS_MISSING = "6";
|
|
31
|
+
const UNSUPPORTED_FRAMEWORK = "7";
|
|
32
|
+
const BUILD_MISSING_REGISTRY_FILE = "13";
|
|
47
33
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
result[key] = deepMerge(
|
|
118
|
-
targetValue,
|
|
119
|
-
sourceValue
|
|
120
|
-
);
|
|
121
|
-
} else {
|
|
122
|
-
result[key] = sourceValue;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
return result;
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/preflights/preflight-init.ts
|
|
36
|
+
async function preFlightInit(options) {
|
|
37
|
+
const errors = {};
|
|
38
|
+
if (!fsExtra.existsSync(options.cwd) || !fsExtra.existsSync(path.resolve(options.cwd, "package.json"))) {
|
|
39
|
+
errors[MISSING_DIR_OR_EMPTY_PROJECT] = true;
|
|
40
|
+
return {
|
|
41
|
+
errors,
|
|
42
|
+
projectInfo: null
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const projectSpinner = spinner(`Preflight checks.`, { silent: options.silent }).start();
|
|
46
|
+
if (fsExtra.existsSync(path.resolve(options.cwd, "components.json")) && !options.force) {
|
|
47
|
+
projectSpinner?.fail();
|
|
48
|
+
logger.break();
|
|
49
|
+
logger.error(`A ${highlighter.info("components.json")} file already exists at ${highlighter.info(options.cwd)}.\nTo start over, remove the ${highlighter.info("components.json")} file and run ${highlighter.info("init")} again.`);
|
|
50
|
+
logger.break();
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
projectSpinner?.succeed();
|
|
54
|
+
const frameworkSpinner = spinner(`Verifying framework.`, { silent: options.silent }).start();
|
|
55
|
+
const projectInfo = await getProjectInfo(options.cwd);
|
|
56
|
+
if (!projectInfo || projectInfo?.framework.name === "manual") {
|
|
57
|
+
errors[UNSUPPORTED_FRAMEWORK] = true;
|
|
58
|
+
frameworkSpinner?.fail();
|
|
59
|
+
logger.break();
|
|
60
|
+
if (projectInfo?.framework.links.installation) logger.error(`We could not detect a supported framework at ${highlighter.info(options.cwd)}.\nVisit ${highlighter.info(projectInfo?.framework.links.installation)} to manually configure your project.\nOnce configured, you can use the cli to add components.`);
|
|
61
|
+
logger.break();
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
frameworkSpinner?.succeed(`Verifying framework. Found ${highlighter.info(projectInfo.framework.label)}.`);
|
|
65
|
+
let tailwindSpinnerMessage = "Validating Tailwind CSS.";
|
|
66
|
+
if (projectInfo.tailwindVersion === "v4") tailwindSpinnerMessage = `Validating Tailwind CSS config. Found ${highlighter.info("v4")}.`;
|
|
67
|
+
const tailwindSpinner = spinner(tailwindSpinnerMessage, { silent: options.silent }).start();
|
|
68
|
+
if (projectInfo.tailwindVersion === "v3" && (!projectInfo?.tailwindConfigFile || !projectInfo?.tailwindCssFile)) {
|
|
69
|
+
errors[TAILWIND_NOT_CONFIGURED] = true;
|
|
70
|
+
tailwindSpinner?.fail();
|
|
71
|
+
} else if (projectInfo.tailwindVersion === "v4" && !projectInfo?.tailwindCssFile) {
|
|
72
|
+
errors[TAILWIND_NOT_CONFIGURED] = true;
|
|
73
|
+
tailwindSpinner?.fail();
|
|
74
|
+
} else if (!projectInfo.tailwindVersion) {
|
|
75
|
+
errors[TAILWIND_NOT_CONFIGURED] = true;
|
|
76
|
+
tailwindSpinner?.fail();
|
|
77
|
+
} else tailwindSpinner?.succeed();
|
|
78
|
+
const tsConfigSpinner = spinner(`Validating import alias.`, { silent: options.silent }).start();
|
|
79
|
+
if (!projectInfo?.aliasPrefix) {
|
|
80
|
+
errors[IMPORT_ALIAS_MISSING] = true;
|
|
81
|
+
tsConfigSpinner?.fail();
|
|
82
|
+
} else tsConfigSpinner?.succeed();
|
|
83
|
+
if (Object.keys(errors).length > 0) {
|
|
84
|
+
if (errors[TAILWIND_NOT_CONFIGURED]) {
|
|
85
|
+
logger.break();
|
|
86
|
+
logger.error(`No Tailwind CSS configuration found at ${highlighter.info(options.cwd)}.`);
|
|
87
|
+
logger.error(`It is likely you do not have Tailwind CSS installed or have an invalid configuration.`);
|
|
88
|
+
logger.error(`Install Tailwind CSS then try again.`);
|
|
89
|
+
if (projectInfo?.framework.links.tailwind) logger.error(`Visit ${highlighter.info(projectInfo?.framework.links.tailwind)} to get started.`);
|
|
90
|
+
}
|
|
91
|
+
if (errors[IMPORT_ALIAS_MISSING]) {
|
|
92
|
+
logger.break();
|
|
93
|
+
logger.error(`No import alias found in your tsconfig.json file.`);
|
|
94
|
+
if (projectInfo?.framework.links.installation) logger.error(`Visit ${highlighter.info(projectInfo?.framework.links.installation)} to learn how to set an import alias.`);
|
|
95
|
+
}
|
|
96
|
+
logger.break();
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
errors,
|
|
101
|
+
projectInfo
|
|
102
|
+
};
|
|
126
103
|
}
|
|
127
104
|
|
|
128
|
-
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region src/utils/frontic-colors.ts
|
|
107
|
+
/**
|
|
108
|
+
* Color conversion utilities for Frontic UI CLI
|
|
109
|
+
* Converts hex colors to OKLCH format for CSS variables
|
|
110
|
+
*/
|
|
111
|
+
/**
|
|
112
|
+
* Validates a hex color string
|
|
113
|
+
* Accepts formats: #RGB, #RRGGBB, RGB, RRGGBB
|
|
114
|
+
*/
|
|
129
115
|
function isValidHex(hex) {
|
|
130
|
-
|
|
131
|
-
|
|
116
|
+
const cleanHex = hex.replace(/^#/, "");
|
|
117
|
+
return /^([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$/.test(cleanHex);
|
|
132
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Normalizes a hex color to 6-character format without #
|
|
121
|
+
*/
|
|
133
122
|
function normalizeHex(hex) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
return cleanHex;
|
|
123
|
+
const cleanHex = hex.replace(/^#/, "");
|
|
124
|
+
if (cleanHex.length === 3) return cleanHex.split("").map((c) => c + c).join("");
|
|
125
|
+
return cleanHex;
|
|
139
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Converts hex color to RGB values (0-255)
|
|
129
|
+
*/
|
|
140
130
|
function hexToRgb(hex) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
131
|
+
const normalizedHex = normalizeHex(hex);
|
|
132
|
+
return {
|
|
133
|
+
r: parseInt(normalizedHex.slice(0, 2), 16),
|
|
134
|
+
g: parseInt(normalizedHex.slice(2, 4), 16),
|
|
135
|
+
b: parseInt(normalizedHex.slice(4, 6), 16)
|
|
136
|
+
};
|
|
147
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Converts sRGB to linear RGB
|
|
140
|
+
*/
|
|
148
141
|
function srgbToLinear(value) {
|
|
149
|
-
|
|
150
|
-
|
|
142
|
+
const normalized = value / 255;
|
|
143
|
+
return normalized <= .04045 ? normalized / 12.92 : Math.pow((normalized + .055) / 1.055, 2.4);
|
|
151
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Converts linear RGB to XYZ color space
|
|
147
|
+
*/
|
|
152
148
|
function linearRgbToXyz(r, g, b) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
149
|
+
return {
|
|
150
|
+
x: .4124564 * r + .3575761 * g + .1804375 * b,
|
|
151
|
+
y: .2126729 * r + .7151522 * g + .072175 * b,
|
|
152
|
+
z: .0193339 * r + .119192 * g + .9503041 * b
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Converts XYZ to OKLAB color space
|
|
157
|
+
*/
|
|
158
|
+
function xyzToOklab(x$1, y, z$2) {
|
|
159
|
+
const l_ = Math.cbrt(.8189330101 * x$1 + .3618667424 * y - .1288597137 * z$2);
|
|
160
|
+
const m_ = Math.cbrt(.0329845436 * x$1 + .9293118715 * y + .0361456387 * z$2);
|
|
161
|
+
const s_ = Math.cbrt(-.0482003018 * x$1 + .2643662691 * y + .633851707 * z$2);
|
|
162
|
+
return {
|
|
163
|
+
l: .2104542553 * l_ + .793617785 * m_ - .0040720468 * s_,
|
|
164
|
+
a: 1.9779984951 * l_ - 2.428592205 * m_ + .4505937099 * s_,
|
|
165
|
+
b: .0259040371 * l_ + .7827717662 * m_ - .808675766 * s_
|
|
166
|
+
};
|
|
168
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Converts OKLAB to OKLCH color space
|
|
170
|
+
*/
|
|
169
171
|
function oklabToOklch(l, a, b) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
172
|
+
const c = Math.sqrt(a * a + b * b);
|
|
173
|
+
let h = Math.atan2(b, a) * (180 / Math.PI);
|
|
174
|
+
if (h < 0) h += 360;
|
|
175
|
+
return {
|
|
176
|
+
l,
|
|
177
|
+
c,
|
|
178
|
+
h
|
|
179
|
+
};
|
|
174
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* Converts a hex color to OKLCH format string
|
|
183
|
+
* @param hex - Hex color (e.g., "#6366f1" or "6366f1")
|
|
184
|
+
* @returns OKLCH string (e.g., "oklch(55% 0.24 265)")
|
|
185
|
+
*/
|
|
175
186
|
function hexToOklch(hex) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const linearB = srgbToLinear(b);
|
|
183
|
-
const { x, y, z } = linearRgbToXyz(linearR, linearG, linearB);
|
|
184
|
-
const oklab = xyzToOklab(x, y, z);
|
|
185
|
-
const { l, c, h } = oklabToOklch(oklab.l, oklab.a, oklab.b);
|
|
186
|
-
const lightness = Math.round(l * 100);
|
|
187
|
-
const chroma = Math.round(c * 100) / 100;
|
|
188
|
-
const hue = Math.round(h);
|
|
189
|
-
return `oklch(${lightness}% ${chroma} ${hue})`;
|
|
187
|
+
if (!isValidHex(hex)) throw new Error(`Invalid hex color: ${hex}`);
|
|
188
|
+
const { r, g, b } = hexToRgb(hex);
|
|
189
|
+
const { x: x$1, y, z: z$2 } = linearRgbToXyz(srgbToLinear(r), srgbToLinear(g), srgbToLinear(b));
|
|
190
|
+
const oklab = xyzToOklab(x$1, y, z$2);
|
|
191
|
+
const { l, c, h } = oklabToOklch(oklab.l, oklab.a, oklab.b);
|
|
192
|
+
return `oklch(${Math.round(l * 100)}% ${Math.round(c * 100) / 100} ${Math.round(h)})`;
|
|
190
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* Calculates appropriate foreground color (black or white) based on background lightness
|
|
196
|
+
* @param oklchOrHex - Either an OKLCH string or hex color
|
|
197
|
+
* @returns OKLCH string for foreground (white or dark)
|
|
198
|
+
*/
|
|
191
199
|
function getForegroundColor(oklchOrHex) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
200
|
+
let lightness;
|
|
201
|
+
if (oklchOrHex.startsWith("oklch")) {
|
|
202
|
+
const match = oklchOrHex.match(/oklch\((\d+)%/);
|
|
203
|
+
lightness = match?.[1] ? parseInt(match[1], 10) / 100 : .5;
|
|
204
|
+
} else {
|
|
205
|
+
const match = hexToOklch(oklchOrHex).match(/oklch\((\d+)%/);
|
|
206
|
+
lightness = match?.[1] ? parseInt(match[1], 10) / 100 : .5;
|
|
207
|
+
}
|
|
208
|
+
return lightness > .6 ? "oklch(20% 0 0)" : "oklch(100% 0 0)";
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
//#endregion
|
|
212
|
+
//#region src/utils/is-safe-target.ts
|
|
213
|
+
function isSafeTarget(targetPath, cwd) {
|
|
214
|
+
if (targetPath.includes("\0")) return false;
|
|
215
|
+
let decodedPath;
|
|
216
|
+
try {
|
|
217
|
+
decodedPath = targetPath;
|
|
218
|
+
let prevPath = "";
|
|
219
|
+
while (decodedPath !== prevPath && decodedPath.includes("%")) {
|
|
220
|
+
prevPath = decodedPath;
|
|
221
|
+
decodedPath = decodeURIComponent(decodedPath);
|
|
222
|
+
}
|
|
223
|
+
} catch {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
const normalizedTarget = normalize(decodedPath);
|
|
227
|
+
const normalizedRoot = normalize(cwd);
|
|
228
|
+
const hasPathTraversal = (path$2) => {
|
|
229
|
+
return path$2.replace(/\[\.\.\..*?\]/g, "").includes("..");
|
|
230
|
+
};
|
|
231
|
+
if (hasPathTraversal(normalizedTarget) || hasPathTraversal(decodedPath) || hasPathTraversal(targetPath)) return false;
|
|
232
|
+
const cleanPath = (path$2) => path$2.replace(/\[\.\.\..*?\]/g, "");
|
|
233
|
+
const cleanTarget = cleanPath(targetPath);
|
|
234
|
+
const cleanDecoded = cleanPath(decodedPath);
|
|
235
|
+
if ([
|
|
236
|
+
/\.\.[/\\]/,
|
|
237
|
+
/[/\\]\.\./,
|
|
238
|
+
/\.\./,
|
|
239
|
+
/\.\.%/,
|
|
240
|
+
/\0/,
|
|
241
|
+
/[\x01-\x1F]/
|
|
242
|
+
].some((pattern) => pattern.test(cleanTarget) || pattern.test(cleanDecoded))) return false;
|
|
243
|
+
if ((targetPath.includes("~") || decodedPath.includes("~")) && (targetPath.includes("../") || decodedPath.includes("../"))) return false;
|
|
244
|
+
if (/^[a-z]:[/\\]/i.test(decodedPath)) {
|
|
245
|
+
if (process.platform === "win32") return decodedPath.toLowerCase().startsWith(cwd.toLowerCase());
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
const absoluteTarget = isAbsolute(normalizedTarget) ? normalizedTarget : resolve(normalizedRoot, normalizedTarget);
|
|
249
|
+
const safeRoot = normalizedRoot.endsWith(sep) ? normalizedRoot : normalizedRoot + sep;
|
|
250
|
+
return absoluteTarget === normalizedRoot || absoluteTarget.startsWith(safeRoot);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
//#endregion
|
|
254
|
+
//#region src/utils/updaters/update-css.ts
|
|
255
|
+
async function updateCss(css, config, options) {
|
|
256
|
+
if (!config.resolvedPaths.tailwindCss || !css || Object.keys(css).length === 0) return;
|
|
257
|
+
options = {
|
|
258
|
+
silent: false,
|
|
259
|
+
...options
|
|
260
|
+
};
|
|
261
|
+
const cssFilepath = config.resolvedPaths.tailwindCss;
|
|
262
|
+
const cssFilepathRelative = path.relative(config.resolvedPaths.cwd, cssFilepath);
|
|
263
|
+
const cssSpinner = spinner(`Updating ${highlighter.info(cssFilepathRelative)}`, { silent: options.silent }).start();
|
|
264
|
+
let output = await transformCss(await promises.readFile(cssFilepath, "utf8"), css);
|
|
265
|
+
await promises.writeFile(cssFilepath, output, "utf8");
|
|
266
|
+
cssSpinner.succeed();
|
|
267
|
+
}
|
|
268
|
+
async function transformCss(input, css) {
|
|
269
|
+
const result = await postcss([updateCssPlugin(css)]).process(input, { from: void 0 });
|
|
270
|
+
let output = result.css;
|
|
271
|
+
const root = result.root;
|
|
272
|
+
if (root.nodes && root.nodes.length > 0) {
|
|
273
|
+
const lastNode = root.nodes[root.nodes.length - 1];
|
|
274
|
+
if (lastNode.type === "atrule" && !lastNode.nodes && !output.trimEnd().endsWith(";")) output = `${output.trimEnd()};`;
|
|
275
|
+
}
|
|
276
|
+
output = output.replace(/\/\* ---break--- \*\//g, "");
|
|
277
|
+
output = output.replace(/(\n\s*\n)+/g, "\n\n");
|
|
278
|
+
output = output.trimEnd();
|
|
279
|
+
return output;
|
|
280
|
+
}
|
|
281
|
+
function updateCssPlugin(css) {
|
|
282
|
+
return {
|
|
283
|
+
postcssPlugin: "update-css",
|
|
284
|
+
Once(root) {
|
|
285
|
+
for (const [selector, properties] of Object.entries(css)) if (selector.startsWith("@")) {
|
|
286
|
+
const atRuleMatch = selector.match(/@([a-z-]+)\s*(.*)/i);
|
|
287
|
+
if (!atRuleMatch) continue;
|
|
288
|
+
const [, name, params] = atRuleMatch;
|
|
289
|
+
if (name === "import") {
|
|
290
|
+
if (!root.nodes?.find((node) => node.type === "atrule" && node.name === "import" && node.params === params)) {
|
|
291
|
+
const importRule = postcss.atRule({
|
|
292
|
+
name: "import",
|
|
293
|
+
params,
|
|
294
|
+
raws: { semicolon: true }
|
|
295
|
+
});
|
|
296
|
+
const importNodes = root.nodes?.filter((node) => node.type === "atrule" && node.name === "import");
|
|
297
|
+
if (importNodes && importNodes.length > 0) {
|
|
298
|
+
const lastImport = importNodes[importNodes.length - 1];
|
|
299
|
+
importRule.raws.before = "\n";
|
|
300
|
+
root.insertAfter(lastImport, importRule);
|
|
301
|
+
} else {
|
|
302
|
+
if (!root.nodes || root.nodes.length === 0) importRule.raws.before = "";
|
|
303
|
+
else importRule.raws.before = "";
|
|
304
|
+
root.prepend(importRule);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
} else if (name === "plugin") {
|
|
308
|
+
let quotedParams = params;
|
|
309
|
+
if (params && !params.startsWith("\"") && !params.startsWith("'")) quotedParams = `"${params}"`;
|
|
310
|
+
const normalizeParams = (p) => {
|
|
311
|
+
if (p.startsWith("\"") && p.endsWith("\"")) return p.slice(1, -1);
|
|
312
|
+
if (p.startsWith("'") && p.endsWith("'")) return p.slice(1, -1);
|
|
313
|
+
return p;
|
|
314
|
+
};
|
|
315
|
+
if (!root.nodes?.find((node) => {
|
|
316
|
+
if (node.type !== "atrule" || node.name !== "plugin") return false;
|
|
317
|
+
return normalizeParams(node.params) === normalizeParams(params);
|
|
318
|
+
})) {
|
|
319
|
+
const pluginRule = postcss.atRule({
|
|
320
|
+
name: "plugin",
|
|
321
|
+
params: quotedParams,
|
|
322
|
+
raws: {
|
|
323
|
+
semicolon: true,
|
|
324
|
+
before: "\n"
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
const importNodes = root.nodes?.filter((node) => node.type === "atrule" && node.name === "import");
|
|
328
|
+
const pluginNodes = root.nodes?.filter((node) => node.type === "atrule" && node.name === "plugin");
|
|
329
|
+
if (pluginNodes && pluginNodes.length > 0) {
|
|
330
|
+
const lastPlugin = pluginNodes[pluginNodes.length - 1];
|
|
331
|
+
root.insertAfter(lastPlugin, pluginRule);
|
|
332
|
+
} else if (importNodes && importNodes.length > 0) {
|
|
333
|
+
const lastImport = importNodes[importNodes.length - 1];
|
|
334
|
+
root.insertAfter(lastImport, pluginRule);
|
|
335
|
+
root.insertBefore(pluginRule, postcss.comment({ text: "---break---" }));
|
|
336
|
+
root.insertAfter(pluginRule, postcss.comment({ text: "---break---" }));
|
|
337
|
+
} else {
|
|
338
|
+
root.prepend(pluginRule);
|
|
339
|
+
root.insertBefore(pluginRule, postcss.comment({ text: "---break---" }));
|
|
340
|
+
root.insertAfter(pluginRule, postcss.comment({ text: "---break---" }));
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
} else if (typeof properties === "object" && Object.keys(properties).length === 0) {
|
|
344
|
+
if (!root.nodes?.find((node) => node.type === "atrule" && node.name === name && node.params === params)) {
|
|
345
|
+
const newAtRule = postcss.atRule({
|
|
346
|
+
name,
|
|
347
|
+
params,
|
|
348
|
+
raws: { semicolon: true }
|
|
349
|
+
});
|
|
350
|
+
root.append(newAtRule);
|
|
351
|
+
root.insertBefore(newAtRule, postcss.comment({ text: "---break---" }));
|
|
352
|
+
}
|
|
353
|
+
} else if (name === "keyframes") {
|
|
354
|
+
let themeInline = root.nodes?.find((node) => node.type === "atrule" && node.name === "theme" && node.params === "inline");
|
|
355
|
+
if (!themeInline) {
|
|
356
|
+
themeInline = postcss.atRule({
|
|
357
|
+
name: "theme",
|
|
358
|
+
params: "inline",
|
|
359
|
+
raws: {
|
|
360
|
+
semicolon: true,
|
|
361
|
+
between: " ",
|
|
362
|
+
before: "\n"
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
root.append(themeInline);
|
|
366
|
+
root.insertBefore(themeInline, postcss.comment({ text: "---break---" }));
|
|
367
|
+
}
|
|
368
|
+
const existingKeyframesRule = themeInline.nodes?.find((node) => node.type === "atrule" && node.name === "keyframes" && node.params === params);
|
|
369
|
+
let keyframesRule;
|
|
370
|
+
if (existingKeyframesRule) {
|
|
371
|
+
keyframesRule = postcss.atRule({
|
|
372
|
+
name: "keyframes",
|
|
373
|
+
params,
|
|
374
|
+
raws: {
|
|
375
|
+
semicolon: true,
|
|
376
|
+
between: " ",
|
|
377
|
+
before: "\n "
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
existingKeyframesRule.replaceWith(keyframesRule);
|
|
381
|
+
} else {
|
|
382
|
+
keyframesRule = postcss.atRule({
|
|
383
|
+
name: "keyframes",
|
|
384
|
+
params,
|
|
385
|
+
raws: {
|
|
386
|
+
semicolon: true,
|
|
387
|
+
between: " ",
|
|
388
|
+
before: "\n "
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
themeInline.append(keyframesRule);
|
|
392
|
+
}
|
|
393
|
+
if (typeof properties === "object") for (const [step, stepProps] of Object.entries(properties)) processRule(keyframesRule, step, stepProps);
|
|
394
|
+
} else if (name === "utility") {
|
|
395
|
+
const utilityAtRule = root.nodes?.find((node) => node.type === "atrule" && node.name === name && node.params === params);
|
|
396
|
+
if (!utilityAtRule) {
|
|
397
|
+
const atRule = postcss.atRule({
|
|
398
|
+
name,
|
|
399
|
+
params,
|
|
400
|
+
raws: {
|
|
401
|
+
semicolon: true,
|
|
402
|
+
between: " ",
|
|
403
|
+
before: "\n"
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
root.append(atRule);
|
|
407
|
+
root.insertBefore(atRule, postcss.comment({ text: "---break---" }));
|
|
408
|
+
if (typeof properties === "object") {
|
|
409
|
+
for (const [prop, value] of Object.entries(properties)) if (typeof value === "string") {
|
|
410
|
+
const decl = postcss.decl({
|
|
411
|
+
prop,
|
|
412
|
+
value,
|
|
413
|
+
raws: {
|
|
414
|
+
semicolon: true,
|
|
415
|
+
before: "\n "
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
atRule.append(decl);
|
|
419
|
+
} else if (typeof value === "object") processRule(atRule, prop, value);
|
|
420
|
+
}
|
|
421
|
+
} else if (typeof properties === "object") {
|
|
422
|
+
for (const [prop, value] of Object.entries(properties)) if (typeof value === "string") {
|
|
423
|
+
const existingDecl = utilityAtRule.nodes?.find((node) => node.type === "decl" && node.prop === prop);
|
|
424
|
+
const decl = postcss.decl({
|
|
425
|
+
prop,
|
|
426
|
+
value,
|
|
427
|
+
raws: {
|
|
428
|
+
semicolon: true,
|
|
429
|
+
before: "\n "
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
if (existingDecl) existingDecl.replaceWith(decl);
|
|
433
|
+
else utilityAtRule.append(decl);
|
|
434
|
+
} else if (typeof value === "object") processRule(utilityAtRule, prop, value);
|
|
435
|
+
}
|
|
436
|
+
} else if (name === "property") processRule(root, selector, properties);
|
|
437
|
+
else processAtRule(root, name, params, properties);
|
|
438
|
+
} else processRule(root, selector, properties);
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
function processAtRule(root, name, params, properties) {
|
|
443
|
+
let atRule = root.nodes?.find((node) => node.type === "atrule" && node.name === name && node.params === params);
|
|
444
|
+
if (!atRule) {
|
|
445
|
+
atRule = postcss.atRule({
|
|
446
|
+
name,
|
|
447
|
+
params,
|
|
448
|
+
raws: {
|
|
449
|
+
semicolon: true,
|
|
450
|
+
between: " ",
|
|
451
|
+
before: "\n"
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
root.append(atRule);
|
|
455
|
+
root.insertBefore(atRule, postcss.comment({ text: "---break---" }));
|
|
456
|
+
}
|
|
457
|
+
if (typeof properties === "object") for (const [childSelector, childProps] of Object.entries(properties)) if (childSelector.startsWith("@")) {
|
|
458
|
+
const nestedMatch = childSelector.match(/@([a-z-]+)\s*(.*)/i);
|
|
459
|
+
if (nestedMatch) {
|
|
460
|
+
const [, nestedName, nestedParams] = nestedMatch;
|
|
461
|
+
processAtRule(atRule, nestedName, nestedParams, childProps);
|
|
462
|
+
}
|
|
463
|
+
} else processRule(atRule, childSelector, childProps);
|
|
464
|
+
else if (typeof properties === "string") try {
|
|
465
|
+
const tempRule = postcss.parse(`.temp{${properties}}`).first;
|
|
466
|
+
if (tempRule && tempRule.nodes) {
|
|
467
|
+
const rule = postcss.rule({
|
|
468
|
+
selector: "temp",
|
|
469
|
+
raws: {
|
|
470
|
+
semicolon: true,
|
|
471
|
+
between: " ",
|
|
472
|
+
before: "\n "
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
tempRule.nodes.forEach((node) => {
|
|
476
|
+
if (node.type === "decl") {
|
|
477
|
+
const clone = node.clone();
|
|
478
|
+
clone.raws.before = "\n ";
|
|
479
|
+
rule.append(clone);
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
if (rule.nodes?.length) atRule.append(rule);
|
|
483
|
+
}
|
|
484
|
+
} catch (error) {
|
|
485
|
+
console.error("Error parsing at-rule content:", properties, error);
|
|
486
|
+
throw error;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
function processRule(parent, selector, properties) {
|
|
490
|
+
let rule = parent.nodes?.find((node) => node.type === "rule" && node.selector === selector);
|
|
491
|
+
if (!rule) {
|
|
492
|
+
rule = postcss.rule({
|
|
493
|
+
selector,
|
|
494
|
+
raws: {
|
|
495
|
+
semicolon: true,
|
|
496
|
+
between: " ",
|
|
497
|
+
before: "\n "
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
parent.append(rule);
|
|
501
|
+
}
|
|
502
|
+
if (typeof properties === "object") {
|
|
503
|
+
for (const [prop, value] of Object.entries(properties)) if (prop.startsWith("@") && typeof value === "object" && value !== null && Object.keys(value).length === 0) {
|
|
504
|
+
const atRuleMatch = prop.match(/@([a-z-]+)\s*(.*)/i);
|
|
505
|
+
if (atRuleMatch) {
|
|
506
|
+
const [, atRuleName, atRuleParams] = atRuleMatch;
|
|
507
|
+
const atRule = postcss.atRule({
|
|
508
|
+
name: atRuleName,
|
|
509
|
+
params: atRuleParams,
|
|
510
|
+
raws: {
|
|
511
|
+
semicolon: true,
|
|
512
|
+
before: "\n "
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
rule.append(atRule);
|
|
516
|
+
}
|
|
517
|
+
} else if (typeof value === "string") {
|
|
518
|
+
const decl = postcss.decl({
|
|
519
|
+
prop,
|
|
520
|
+
value,
|
|
521
|
+
raws: {
|
|
522
|
+
semicolon: true,
|
|
523
|
+
before: "\n "
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
const existingDecl = rule.nodes?.find((node) => node.type === "decl" && node.prop === prop);
|
|
527
|
+
if (existingDecl) existingDecl.replaceWith(decl);
|
|
528
|
+
else rule.append(decl);
|
|
529
|
+
} else if (typeof value === "object") processRule(parent, prop.startsWith("&") ? selector.replace(/^([^:]+)/, `$1${prop.substring(1)}`) : prop, value);
|
|
530
|
+
} else if (typeof properties === "string") try {
|
|
531
|
+
const tempRule = postcss.parse(`.temp{${properties}}`).first;
|
|
532
|
+
if (tempRule && tempRule.nodes) tempRule.nodes.forEach((node) => {
|
|
533
|
+
if (node.type === "decl") {
|
|
534
|
+
const clone = node.clone();
|
|
535
|
+
clone.raws.before = "\n ";
|
|
536
|
+
rule?.append(clone);
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
} catch (error) {
|
|
540
|
+
console.error("Error parsing rule content:", selector, properties, error);
|
|
541
|
+
throw error;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
//#endregion
|
|
546
|
+
//#region src/utils/updaters/update-css-vars.ts
|
|
547
|
+
async function updateCssVars(cssVars, config, options) {
|
|
548
|
+
if (!config.resolvedPaths.tailwindCss || !Object.keys(cssVars ?? {}).length) return;
|
|
549
|
+
options = {
|
|
550
|
+
cleanupDefaultNextStyles: false,
|
|
551
|
+
silent: false,
|
|
552
|
+
tailwindVersion: "v3",
|
|
553
|
+
overwriteCssVars: false,
|
|
554
|
+
initIndex: true,
|
|
555
|
+
...options
|
|
556
|
+
};
|
|
557
|
+
const cssFilepath = config.resolvedPaths.tailwindCss;
|
|
558
|
+
const cssFilepathRelative = path.relative(config.resolvedPaths.cwd, cssFilepath);
|
|
559
|
+
const cssVarsSpinner = spinner(`Updating CSS variables in ${highlighter.info(cssFilepathRelative)}`, { silent: options.silent }).start();
|
|
560
|
+
const output = await transformCssVars(await promises.readFile(cssFilepath, "utf8"), cssVars ?? {}, config, {
|
|
561
|
+
cleanupDefaultNextStyles: options.cleanupDefaultNextStyles,
|
|
562
|
+
tailwindVersion: options.tailwindVersion,
|
|
563
|
+
tailwindConfig: options.tailwindConfig,
|
|
564
|
+
overwriteCssVars: options.overwriteCssVars,
|
|
565
|
+
initIndex: options.initIndex
|
|
566
|
+
});
|
|
567
|
+
await promises.writeFile(cssFilepath, output, "utf8");
|
|
568
|
+
cssVarsSpinner.succeed();
|
|
569
|
+
}
|
|
570
|
+
async function transformCssVars(input, cssVars, config, options = {
|
|
571
|
+
cleanupDefaultNextStyles: false,
|
|
572
|
+
tailwindVersion: "v3",
|
|
573
|
+
tailwindConfig: void 0,
|
|
574
|
+
overwriteCssVars: false,
|
|
575
|
+
initIndex: true
|
|
576
|
+
}) {
|
|
577
|
+
options = {
|
|
578
|
+
cleanupDefaultNextStyles: false,
|
|
579
|
+
tailwindVersion: "v3",
|
|
580
|
+
tailwindConfig: void 0,
|
|
581
|
+
overwriteCssVars: false,
|
|
582
|
+
initIndex: true,
|
|
583
|
+
...options
|
|
584
|
+
};
|
|
585
|
+
let plugins = [updateCssVarsPlugin(cssVars)];
|
|
586
|
+
if (options.cleanupDefaultNextStyles) plugins.push(cleanupDefaultNextStylesPlugin());
|
|
587
|
+
if (options.tailwindVersion === "v4") {
|
|
588
|
+
plugins = [];
|
|
589
|
+
if (config.resolvedPaths?.cwd) {
|
|
590
|
+
const packageInfo = getPackageInfo(config.resolvedPaths.cwd);
|
|
591
|
+
if (!packageInfo?.dependencies?.["tailwindcss-animate"] && !packageInfo?.devDependencies?.["tailwindcss-animate"] && options.initIndex) plugins.push(addCustomImport({ params: "tw-animate-css" }));
|
|
592
|
+
}
|
|
593
|
+
plugins.push(addCustomVariant({ params: "dark (&:is(.dark *))" }));
|
|
594
|
+
if (options.cleanupDefaultNextStyles) plugins.push(cleanupDefaultNextStylesPlugin());
|
|
595
|
+
plugins.push(updateCssVarsPluginV4(cssVars, { overwriteCssVars: options.overwriteCssVars }));
|
|
596
|
+
plugins.push(updateThemePlugin(cssVars));
|
|
597
|
+
if (options.tailwindConfig) {
|
|
598
|
+
plugins.push(updateTailwindConfigPlugin(options.tailwindConfig));
|
|
599
|
+
plugins.push(updateTailwindConfigAnimationPlugin(options.tailwindConfig));
|
|
600
|
+
plugins.push(updateTailwindConfigKeyframesPlugin(options.tailwindConfig));
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
if (config.tailwind.cssVariables && options.initIndex) plugins.push(updateBaseLayerPlugin({ tailwindVersion: options.tailwindVersion }));
|
|
604
|
+
let output = (await postcss(plugins).process(input, { from: void 0 })).css;
|
|
605
|
+
output = output.replace(/\/\* ---break--- \*\//g, "");
|
|
606
|
+
if (options.tailwindVersion === "v4") output = output.replace(/(\n\s*\n)+/g, "\n\n");
|
|
607
|
+
return output;
|
|
608
|
+
}
|
|
609
|
+
function updateBaseLayerPlugin({ tailwindVersion }) {
|
|
610
|
+
return {
|
|
611
|
+
postcssPlugin: "update-base-layer",
|
|
612
|
+
Once(root) {
|
|
613
|
+
const requiredRules = [{
|
|
614
|
+
selector: "*",
|
|
615
|
+
apply: tailwindVersion === "v4" ? "border-border outline-ring/50" : "border-border"
|
|
616
|
+
}, {
|
|
617
|
+
selector: "body",
|
|
618
|
+
apply: "bg-background text-foreground"
|
|
619
|
+
}];
|
|
620
|
+
let baseLayer = root.nodes.find((node) => node.type === "atrule" && node.name === "layer" && node.params === "base" && requiredRules.every(({ selector, apply }) => node.nodes?.some((rule) => rule.type === "rule" && rule.selector === selector && rule.nodes.some((applyRule) => applyRule.type === "atrule" && applyRule.name === "apply" && applyRule.params === apply))));
|
|
621
|
+
if (!baseLayer) {
|
|
622
|
+
baseLayer = postcss.atRule({
|
|
623
|
+
name: "layer",
|
|
624
|
+
params: "base",
|
|
625
|
+
raws: {
|
|
626
|
+
semicolon: true,
|
|
627
|
+
between: " ",
|
|
628
|
+
before: "\n"
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
|
+
root.append(baseLayer);
|
|
632
|
+
root.insertBefore(baseLayer, postcss.comment({ text: "---break---" }));
|
|
633
|
+
}
|
|
634
|
+
requiredRules.forEach(({ selector, apply }) => {
|
|
635
|
+
if (!baseLayer?.nodes?.find((node) => node.type === "rule" && node.selector === selector)) baseLayer?.append(postcss.rule({
|
|
636
|
+
selector,
|
|
637
|
+
nodes: [postcss.atRule({
|
|
638
|
+
name: "apply",
|
|
639
|
+
params: apply,
|
|
640
|
+
raws: {
|
|
641
|
+
semicolon: true,
|
|
642
|
+
before: "\n "
|
|
643
|
+
}
|
|
644
|
+
})],
|
|
645
|
+
raws: {
|
|
646
|
+
semicolon: true,
|
|
647
|
+
between: " ",
|
|
648
|
+
before: "\n "
|
|
649
|
+
}
|
|
650
|
+
}));
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
function updateCssVarsPlugin(cssVars) {
|
|
656
|
+
return {
|
|
657
|
+
postcssPlugin: "update-css-vars",
|
|
658
|
+
Once(root) {
|
|
659
|
+
let baseLayer = root.nodes.find((node) => node.type === "atrule" && node.name === "layer" && node.params === "base");
|
|
660
|
+
if (!(baseLayer instanceof AtRule)) {
|
|
661
|
+
baseLayer = postcss.atRule({
|
|
662
|
+
name: "layer",
|
|
663
|
+
params: "base",
|
|
664
|
+
nodes: [],
|
|
665
|
+
raws: {
|
|
666
|
+
semicolon: true,
|
|
667
|
+
before: "\n",
|
|
668
|
+
between: " "
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
root.append(baseLayer);
|
|
672
|
+
root.insertBefore(baseLayer, postcss.comment({ text: "---break---" }));
|
|
673
|
+
}
|
|
674
|
+
if (baseLayer !== void 0) Object.entries(cssVars).forEach(([key, vars]) => {
|
|
675
|
+
const selector = key === "light" ? ":root" : `.${key}`;
|
|
676
|
+
addOrUpdateVars(baseLayer, selector, vars);
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
function removeConflictVars(root) {
|
|
682
|
+
const rootRule = root.nodes.find((node) => node.type === "rule" && node.selector === ":root");
|
|
683
|
+
if (rootRule) {
|
|
684
|
+
const propsToRemove = ["--background", "--foreground"];
|
|
685
|
+
rootRule.nodes.filter((node) => node.type === "decl" && propsToRemove.includes(node.prop)).forEach((node) => node.remove());
|
|
686
|
+
if (rootRule.nodes.length === 0) rootRule.remove();
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
function cleanupDefaultNextStylesPlugin() {
|
|
690
|
+
return {
|
|
691
|
+
postcssPlugin: "cleanup-default-next-styles",
|
|
692
|
+
Once(root) {
|
|
693
|
+
const bodyRule = root.nodes.find((node) => node.type === "rule" && node.selector === "body");
|
|
694
|
+
if (bodyRule) {
|
|
695
|
+
bodyRule.nodes.find((node) => node.type === "decl" && node.prop === "color" && ["rgb(var(--foreground-rgb))", "var(--foreground)"].includes(node.value))?.remove();
|
|
696
|
+
bodyRule.nodes.find((node) => {
|
|
697
|
+
return node.type === "decl" && node.prop === "background" && (node.value.startsWith("linear-gradient") || node.value === "var(--background)");
|
|
698
|
+
})?.remove();
|
|
699
|
+
bodyRule.nodes.find((node) => node.type === "decl" && node.prop === "font-family" && node.value === "Arial, Helvetica, sans-serif")?.remove();
|
|
700
|
+
if (bodyRule.nodes.length === 0) bodyRule.remove();
|
|
701
|
+
}
|
|
702
|
+
removeConflictVars(root);
|
|
703
|
+
const darkRootRule = root.nodes.find((node) => node.type === "atrule" && node.params === "(prefers-color-scheme: dark)");
|
|
704
|
+
if (darkRootRule) {
|
|
705
|
+
removeConflictVars(darkRootRule);
|
|
706
|
+
if (darkRootRule.nodes.length === 0) darkRootRule.remove();
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
function addOrUpdateVars(baseLayer, selector, vars) {
|
|
712
|
+
let ruleNode = baseLayer.nodes?.find((node) => node.type === "rule" && node.selector === selector);
|
|
713
|
+
if (!ruleNode) {
|
|
714
|
+
if (Object.keys(vars).length > 0) {
|
|
715
|
+
ruleNode = postcss.rule({
|
|
716
|
+
selector,
|
|
717
|
+
raws: {
|
|
718
|
+
between: " ",
|
|
719
|
+
before: "\n "
|
|
720
|
+
}
|
|
721
|
+
});
|
|
722
|
+
baseLayer.append(ruleNode);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
Object.entries(vars).forEach(([key, value]) => {
|
|
726
|
+
const prop = `--${key.replace(/^--/, "")}`;
|
|
727
|
+
const newDecl = postcss.decl({
|
|
728
|
+
prop,
|
|
729
|
+
value,
|
|
730
|
+
raws: { semicolon: true }
|
|
731
|
+
});
|
|
732
|
+
const existingDecl = ruleNode?.nodes.find((node) => node.type === "decl" && node.prop === prop);
|
|
733
|
+
if (existingDecl) existingDecl.replaceWith(newDecl);
|
|
734
|
+
else ruleNode?.append(newDecl);
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
function updateCssVarsPluginV4(cssVars, options) {
|
|
738
|
+
return {
|
|
739
|
+
postcssPlugin: "update-css-vars-v4",
|
|
740
|
+
Once(root) {
|
|
741
|
+
Object.entries(cssVars).forEach(([key, vars]) => {
|
|
742
|
+
let selector = key === "light" ? ":root" : `.${key}`;
|
|
743
|
+
if (key === "theme") {
|
|
744
|
+
selector = "@theme";
|
|
745
|
+
const themeNode = upsertThemeNode(root);
|
|
746
|
+
Object.entries(vars).forEach(([key$1, value]) => {
|
|
747
|
+
const prop = `--${key$1.replace(/^--/, "")}`;
|
|
748
|
+
const newDecl = postcss.decl({
|
|
749
|
+
prop,
|
|
750
|
+
value,
|
|
751
|
+
raws: { semicolon: true }
|
|
752
|
+
});
|
|
753
|
+
const existingDecl = themeNode?.nodes?.find((node) => node.type === "decl" && node.prop === prop);
|
|
754
|
+
if (options.overwriteCssVars) if (existingDecl) existingDecl.replaceWith(newDecl);
|
|
755
|
+
else themeNode?.append(newDecl);
|
|
756
|
+
else if (!existingDecl) themeNode?.append(newDecl);
|
|
757
|
+
});
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
let ruleNode = root.nodes?.find((node) => node.type === "rule" && node.selector === selector);
|
|
761
|
+
if (!ruleNode && Object.keys(vars).length > 0) {
|
|
762
|
+
ruleNode = postcss.rule({
|
|
763
|
+
selector,
|
|
764
|
+
nodes: [],
|
|
765
|
+
raws: {
|
|
766
|
+
semicolon: true,
|
|
767
|
+
between: " ",
|
|
768
|
+
before: "\n"
|
|
769
|
+
}
|
|
770
|
+
});
|
|
771
|
+
root.append(ruleNode);
|
|
772
|
+
root.insertBefore(ruleNode, postcss.comment({ text: "---break---" }));
|
|
773
|
+
}
|
|
774
|
+
Object.entries(vars).forEach(([key$1, value]) => {
|
|
775
|
+
let prop = `--${key$1.replace(/^--/, "")}`;
|
|
776
|
+
if (prop === "--sidebar-background") prop = "--sidebar";
|
|
777
|
+
if (isLocalHSLValue(value)) value = `hsl(${value})`;
|
|
778
|
+
const newDecl = postcss.decl({
|
|
779
|
+
prop,
|
|
780
|
+
value,
|
|
781
|
+
raws: { semicolon: true }
|
|
782
|
+
});
|
|
783
|
+
const existingDecl = ruleNode?.nodes.find((node) => node.type === "decl" && node.prop === prop);
|
|
784
|
+
if (options.overwriteCssVars) if (existingDecl) existingDecl.replaceWith(newDecl);
|
|
785
|
+
else ruleNode?.append(newDecl);
|
|
786
|
+
else if (!existingDecl) ruleNode?.append(newDecl);
|
|
787
|
+
});
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
function updateThemePlugin(cssVars) {
|
|
793
|
+
return {
|
|
794
|
+
postcssPlugin: "update-theme",
|
|
795
|
+
Once(root) {
|
|
796
|
+
const variables = Array.from(new Set(Object.keys(cssVars).flatMap((key) => Object.keys(cssVars[key] || {}))));
|
|
797
|
+
if (!variables.length) return;
|
|
798
|
+
const themeNode = upsertThemeNode(root);
|
|
799
|
+
const themeVarNodes = themeNode.nodes?.filter((node) => node.type === "decl" && node.prop.startsWith("--"));
|
|
800
|
+
for (const variable of variables) {
|
|
801
|
+
const value = Object.values(cssVars).find((vars) => vars[variable])?.[variable];
|
|
802
|
+
if (!value) continue;
|
|
803
|
+
if (variable === "radius") {
|
|
804
|
+
for (const [key, value$1] of Object.entries({
|
|
805
|
+
sm: "calc(var(--radius) - 4px)",
|
|
806
|
+
md: "calc(var(--radius) - 2px)",
|
|
807
|
+
lg: "var(--radius)",
|
|
808
|
+
xl: "calc(var(--radius) + 4px)"
|
|
809
|
+
})) {
|
|
810
|
+
const cssVarNode$1 = postcss.decl({
|
|
811
|
+
prop: `--radius-${key}`,
|
|
812
|
+
value: value$1,
|
|
813
|
+
raws: { semicolon: true }
|
|
814
|
+
});
|
|
815
|
+
if (themeNode?.nodes?.find((node) => node.type === "decl" && node.prop === cssVarNode$1.prop)) continue;
|
|
816
|
+
themeNode?.append(cssVarNode$1);
|
|
817
|
+
}
|
|
818
|
+
continue;
|
|
819
|
+
}
|
|
820
|
+
let prop = isLocalHSLValue(value) || isColorValue(value) ? `--color-${variable.replace(/^--/, "")}` : `--${variable.replace(/^--/, "")}`;
|
|
821
|
+
if (prop === "--color-sidebar-background") prop = "--color-sidebar";
|
|
822
|
+
let propValue = `var(--${variable})`;
|
|
823
|
+
if (prop === "--color-sidebar") propValue = "var(--sidebar)";
|
|
824
|
+
const cssVarNode = postcss.decl({
|
|
825
|
+
prop,
|
|
826
|
+
value: propValue,
|
|
827
|
+
raws: { semicolon: true }
|
|
828
|
+
});
|
|
829
|
+
if (!themeNode?.nodes?.find((node) => node.type === "decl" && node.prop === cssVarNode.prop)) if (themeVarNodes?.length) themeNode?.insertAfter(themeVarNodes[themeVarNodes.length - 1], cssVarNode);
|
|
830
|
+
else themeNode?.append(cssVarNode);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
function upsertThemeNode(root) {
|
|
836
|
+
let themeNode = root.nodes.find((node) => node.type === "atrule" && node.name === "theme" && node.params === "inline");
|
|
837
|
+
if (!themeNode) {
|
|
838
|
+
themeNode = postcss.atRule({
|
|
839
|
+
name: "theme",
|
|
840
|
+
params: "inline",
|
|
841
|
+
nodes: [],
|
|
842
|
+
raws: {
|
|
843
|
+
semicolon: true,
|
|
844
|
+
between: " ",
|
|
845
|
+
before: "\n"
|
|
846
|
+
}
|
|
847
|
+
});
|
|
848
|
+
root.append(themeNode);
|
|
849
|
+
root.insertBefore(themeNode, postcss.comment({ text: "---break---" }));
|
|
850
|
+
}
|
|
851
|
+
return themeNode;
|
|
852
|
+
}
|
|
853
|
+
function addCustomVariant({ params }) {
|
|
854
|
+
return {
|
|
855
|
+
postcssPlugin: "add-custom-variant",
|
|
856
|
+
Once(root) {
|
|
857
|
+
if (!root.nodes.find((node) => node.type === "atrule" && node.name === "custom-variant")) {
|
|
858
|
+
const importNodes = root.nodes.filter((node) => node.type === "atrule" && node.name === "import");
|
|
859
|
+
const variantNode = postcss.atRule({
|
|
860
|
+
name: "custom-variant",
|
|
861
|
+
params,
|
|
862
|
+
raws: {
|
|
863
|
+
semicolon: true,
|
|
864
|
+
before: "\n"
|
|
865
|
+
}
|
|
866
|
+
});
|
|
867
|
+
if (importNodes.length > 0) {
|
|
868
|
+
const lastImport = importNodes[importNodes.length - 1];
|
|
869
|
+
root.insertAfter(lastImport, variantNode);
|
|
870
|
+
} else root.insertAfter(root.nodes[0], variantNode);
|
|
871
|
+
root.insertBefore(variantNode, postcss.comment({ text: "---break---" }));
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
function addCustomImport({ params }) {
|
|
877
|
+
return {
|
|
878
|
+
postcssPlugin: "add-custom-import",
|
|
879
|
+
Once(root) {
|
|
880
|
+
const importNodes = root.nodes.filter((node) => node.type === "atrule" && node.name === "import");
|
|
881
|
+
const customVariantNode = root.nodes.find((node) => node.type === "atrule" && node.name === "custom-variant");
|
|
882
|
+
if (!importNodes.some((node) => node.params.replace(/["']/g, "") === params)) {
|
|
883
|
+
const importNode = postcss.atRule({
|
|
884
|
+
name: "import",
|
|
885
|
+
params: `"${params}"`,
|
|
886
|
+
raws: {
|
|
887
|
+
semicolon: true,
|
|
888
|
+
before: "\n"
|
|
889
|
+
}
|
|
890
|
+
});
|
|
891
|
+
if (importNodes.length > 0) {
|
|
892
|
+
const lastImport = importNodes[importNodes.length - 1];
|
|
893
|
+
root.insertAfter(lastImport, importNode);
|
|
894
|
+
} else if (customVariantNode) {
|
|
895
|
+
root.insertBefore(customVariantNode, importNode);
|
|
896
|
+
root.insertBefore(customVariantNode, postcss.comment({ text: "---break---" }));
|
|
897
|
+
} else {
|
|
898
|
+
root.prepend(importNode);
|
|
899
|
+
root.insertAfter(importNode, postcss.comment({ text: "---break---" }));
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
function updateTailwindConfigPlugin(tailwindConfig) {
|
|
906
|
+
return {
|
|
907
|
+
postcssPlugin: "update-tailwind-config",
|
|
908
|
+
Once(root) {
|
|
909
|
+
if (!tailwindConfig?.plugins) return;
|
|
910
|
+
const quote = getQuoteType(root) === "single" ? "'" : "\"";
|
|
911
|
+
const pluginNodes = root.nodes.filter((node) => node.type === "atrule" && node.name === "plugin");
|
|
912
|
+
const lastPluginNode = pluginNodes[pluginNodes.length - 1] || root.nodes[0];
|
|
913
|
+
for (const plugin of tailwindConfig.plugins) {
|
|
914
|
+
const pluginName = plugin.replace(/^require\(["']|["']\)$/g, "");
|
|
915
|
+
if (pluginNodes.some((node) => {
|
|
916
|
+
return node.params.replace(/["']/g, "") === pluginName;
|
|
917
|
+
})) continue;
|
|
918
|
+
const pluginNode = postcss.atRule({
|
|
919
|
+
name: "plugin",
|
|
920
|
+
params: `${quote}${pluginName}${quote}`,
|
|
921
|
+
raws: {
|
|
922
|
+
semicolon: true,
|
|
923
|
+
before: "\n"
|
|
924
|
+
}
|
|
925
|
+
});
|
|
926
|
+
root.insertAfter(lastPluginNode, pluginNode);
|
|
927
|
+
root.insertBefore(pluginNode, postcss.comment({ text: "---break---" }));
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
function updateTailwindConfigKeyframesPlugin(tailwindConfig) {
|
|
933
|
+
return {
|
|
934
|
+
postcssPlugin: "update-tailwind-config-keyframes",
|
|
935
|
+
Once(root) {
|
|
936
|
+
if (!tailwindConfig?.theme?.extend?.keyframes) return;
|
|
937
|
+
const themeNode = upsertThemeNode(root);
|
|
938
|
+
const existingKeyFrameNodes = themeNode.nodes?.filter((node) => node.type === "atrule" && node.name === "keyframes");
|
|
939
|
+
const keyframeValueSchema = z.record(z.string(), z.record(z.string(), z.string()));
|
|
940
|
+
for (const [keyframeName, keyframeValue] of Object.entries(tailwindConfig.theme.extend.keyframes)) {
|
|
941
|
+
if (typeof keyframeName !== "string") continue;
|
|
942
|
+
const parsedKeyframeValue = keyframeValueSchema.safeParse(keyframeValue);
|
|
943
|
+
if (!parsedKeyframeValue.success) continue;
|
|
944
|
+
if (existingKeyFrameNodes?.find((node) => node.type === "atrule" && node.name === "keyframes" && node.params === keyframeName)) continue;
|
|
945
|
+
const keyframeNode = postcss.atRule({
|
|
946
|
+
name: "keyframes",
|
|
947
|
+
params: keyframeName,
|
|
948
|
+
nodes: [],
|
|
949
|
+
raws: {
|
|
950
|
+
semicolon: true,
|
|
951
|
+
between: " ",
|
|
952
|
+
before: "\n "
|
|
953
|
+
}
|
|
954
|
+
});
|
|
955
|
+
for (const [key, values] of Object.entries(parsedKeyframeValue.data)) {
|
|
956
|
+
const rule = postcss.rule({
|
|
957
|
+
selector: key,
|
|
958
|
+
nodes: Object.entries(values).map(([key$1, value]) => postcss.decl({
|
|
959
|
+
prop: key$1,
|
|
960
|
+
value,
|
|
961
|
+
raws: {
|
|
962
|
+
semicolon: true,
|
|
963
|
+
before: "\n ",
|
|
964
|
+
between: ": "
|
|
965
|
+
}
|
|
966
|
+
})),
|
|
967
|
+
raws: {
|
|
968
|
+
semicolon: true,
|
|
969
|
+
between: " ",
|
|
970
|
+
before: "\n "
|
|
971
|
+
}
|
|
972
|
+
});
|
|
973
|
+
keyframeNode.append(rule);
|
|
974
|
+
}
|
|
975
|
+
themeNode.append(keyframeNode);
|
|
976
|
+
themeNode.insertBefore(keyframeNode, postcss.comment({ text: "---break---" }));
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
function updateTailwindConfigAnimationPlugin(tailwindConfig) {
|
|
982
|
+
return {
|
|
983
|
+
postcssPlugin: "update-tailwind-config-animation",
|
|
984
|
+
Once(root) {
|
|
985
|
+
if (!tailwindConfig?.theme?.extend?.animation) return;
|
|
986
|
+
const themeNode = upsertThemeNode(root);
|
|
987
|
+
const existingAnimationNodes = themeNode.nodes?.filter((node) => node.type === "decl" && node.prop.startsWith("--animate-"));
|
|
988
|
+
const parsedAnimationValue = z.record(z.string(), z.string()).safeParse(tailwindConfig.theme.extend.animation);
|
|
989
|
+
if (!parsedAnimationValue.success) return;
|
|
990
|
+
for (const [key, value] of Object.entries(parsedAnimationValue.data)) {
|
|
991
|
+
const prop = `--animate-${key}`;
|
|
992
|
+
if (existingAnimationNodes?.find((node) => node.prop === prop)) continue;
|
|
993
|
+
const animationNode = postcss.decl({
|
|
994
|
+
prop,
|
|
995
|
+
value,
|
|
996
|
+
raws: {
|
|
997
|
+
semicolon: true,
|
|
998
|
+
between: ": ",
|
|
999
|
+
before: "\n "
|
|
1000
|
+
}
|
|
1001
|
+
});
|
|
1002
|
+
themeNode.append(animationNode);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
function getQuoteType(root) {
|
|
1008
|
+
if (root.nodes[0].toString().includes("'")) return "single";
|
|
1009
|
+
return "double";
|
|
1010
|
+
}
|
|
1011
|
+
function isLocalHSLValue(value) {
|
|
1012
|
+
if (value.startsWith("hsl") || value.startsWith("rgb") || value.startsWith("#") || value.startsWith("oklch")) return false;
|
|
1013
|
+
const chunks = value.split(" ");
|
|
1014
|
+
return chunks.length === 3 && chunks.slice(1, 3).every((chunk) => chunk.includes("%"));
|
|
1015
|
+
}
|
|
1016
|
+
function isColorValue(value) {
|
|
1017
|
+
return value.startsWith("hsl") || value.startsWith("rgb") || value.startsWith("#") || value.startsWith("oklch") || value.includes("var(--color-");
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
//#endregion
|
|
1021
|
+
//#region src/utils/updaters/update-dependencies.ts
|
|
1022
|
+
async function updateDependencies(dependencies, devDependencies, config, options) {
|
|
1023
|
+
dependencies = Array.from(new Set(dependencies));
|
|
1024
|
+
devDependencies = Array.from(new Set(devDependencies));
|
|
1025
|
+
if (!dependencies?.length && !devDependencies?.length) return;
|
|
1026
|
+
options = {
|
|
1027
|
+
silent: false,
|
|
1028
|
+
...options
|
|
1029
|
+
};
|
|
1030
|
+
const dependenciesSpinner = spinner(`Installing dependencies.`, { silent: options.silent })?.start();
|
|
1031
|
+
dependenciesSpinner?.start();
|
|
1032
|
+
if (dependencies?.length) await addDependency(dependencies, {
|
|
1033
|
+
cwd: config.resolvedPaths.cwd,
|
|
1034
|
+
silent: true,
|
|
1035
|
+
dev: false
|
|
1036
|
+
});
|
|
1037
|
+
if (devDependencies?.length) await addDependency(devDependencies, {
|
|
1038
|
+
cwd: config.resolvedPaths.cwd,
|
|
1039
|
+
silent: true,
|
|
1040
|
+
dev: true
|
|
1041
|
+
});
|
|
1042
|
+
dependenciesSpinner?.succeed();
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
//#endregion
|
|
1046
|
+
//#region src/utils/updaters/update-env-vars.ts
|
|
1047
|
+
async function updateEnvVars(envVars, config, options) {
|
|
1048
|
+
if (!envVars || Object.keys(envVars).length === 0) return {
|
|
1049
|
+
envVarsAdded: [],
|
|
1050
|
+
envFileUpdated: null,
|
|
1051
|
+
envFileCreated: null
|
|
1052
|
+
};
|
|
1053
|
+
options = {
|
|
1054
|
+
silent: false,
|
|
1055
|
+
...options
|
|
1056
|
+
};
|
|
1057
|
+
const envSpinner = spinner(`Adding environment variables.`, { silent: options.silent })?.start();
|
|
1058
|
+
const projectRoot = config.resolvedPaths.cwd;
|
|
1059
|
+
let envFilePath = path.join(projectRoot, ".env.local");
|
|
1060
|
+
const existingEnvFile = findExistingEnvFile(projectRoot);
|
|
1061
|
+
if (existingEnvFile) envFilePath = existingEnvFile;
|
|
1062
|
+
const envFileExists = existsSync(envFilePath);
|
|
1063
|
+
const envFileName = path.basename(envFilePath);
|
|
1064
|
+
const newEnvContent = Object.entries(envVars).map(([key, value]) => `${key}=${value}`).join("\n");
|
|
1065
|
+
let envVarsAdded = [];
|
|
1066
|
+
let envFileUpdated = null;
|
|
1067
|
+
let envFileCreated = null;
|
|
1068
|
+
if (envFileExists) {
|
|
1069
|
+
const existingContent = await promises.readFile(envFilePath, "utf-8");
|
|
1070
|
+
const mergedContent = mergeEnvContent(existingContent, newEnvContent);
|
|
1071
|
+
envVarsAdded = getNewEnvKeys(existingContent, newEnvContent);
|
|
1072
|
+
if (envVarsAdded.length > 0) {
|
|
1073
|
+
await promises.writeFile(envFilePath, mergedContent, "utf-8");
|
|
1074
|
+
envFileUpdated = path.relative(projectRoot, envFilePath);
|
|
1075
|
+
envSpinner?.succeed(`Added the following variables to ${highlighter.info(envFileName)}:`);
|
|
1076
|
+
if (!options.silent) for (const key of envVarsAdded) logger.log(` ${highlighter.success("+")} ${key}`);
|
|
1077
|
+
} else envSpinner?.stop();
|
|
1078
|
+
} else {
|
|
1079
|
+
await promises.writeFile(envFilePath, `${newEnvContent}\n`, "utf-8");
|
|
1080
|
+
envFileCreated = path.relative(projectRoot, envFilePath);
|
|
1081
|
+
envVarsAdded = Object.keys(envVars);
|
|
1082
|
+
envSpinner?.succeed(`Added the following variables to ${highlighter.info(envFileName)}:`);
|
|
1083
|
+
if (!options.silent) for (const key of envVarsAdded) logger.log(` ${highlighter.success("+")} ${key}`);
|
|
1084
|
+
}
|
|
1085
|
+
if (!options.silent && envVarsAdded.length > 0) logger.break();
|
|
1086
|
+
return {
|
|
1087
|
+
envVarsAdded,
|
|
1088
|
+
envFileUpdated,
|
|
1089
|
+
envFileCreated
|
|
1090
|
+
};
|
|
202
1091
|
}
|
|
203
1092
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
function
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
1093
|
+
//#endregion
|
|
1094
|
+
//#region src/utils/add-components.ts
|
|
1095
|
+
async function addComponents(components, config, options) {
|
|
1096
|
+
options = {
|
|
1097
|
+
overwrite: false,
|
|
1098
|
+
silent: false,
|
|
1099
|
+
isNewProject: false,
|
|
1100
|
+
baseStyle: true,
|
|
1101
|
+
...options
|
|
1102
|
+
};
|
|
1103
|
+
const workspaceConfig = await getWorkspaceConfig(config);
|
|
1104
|
+
if (workspaceConfig && workspaceConfig.ui && workspaceConfig.ui.resolvedPaths.cwd !== config.resolvedPaths.cwd) return await addWorkspaceComponents(components, config, workspaceConfig, {
|
|
1105
|
+
...options,
|
|
1106
|
+
isRemote: components?.length === 1 && !!components[0].match(/\/chat\/b\//)
|
|
1107
|
+
});
|
|
1108
|
+
return await addProjectComponents(components, config, options);
|
|
1109
|
+
}
|
|
1110
|
+
async function addProjectComponents(components, config, options) {
|
|
1111
|
+
if (!options.baseStyle && !components.length) return;
|
|
1112
|
+
const registrySpinner = spinner(`Checking registry.`, { silent: options.silent })?.start();
|
|
1113
|
+
const tree = await resolveRegistryTree(components, configWithDefaults(config));
|
|
1114
|
+
if (!tree) {
|
|
1115
|
+
registrySpinner?.fail();
|
|
1116
|
+
return handleError(/* @__PURE__ */ new Error("Failed to fetch components from registry."));
|
|
1117
|
+
}
|
|
1118
|
+
try {
|
|
1119
|
+
validateFilesTarget(tree.files ?? [], config.resolvedPaths.cwd);
|
|
1120
|
+
} catch (error) {
|
|
1121
|
+
registrySpinner?.fail();
|
|
1122
|
+
return handleError(error);
|
|
1123
|
+
}
|
|
1124
|
+
registrySpinner?.succeed();
|
|
1125
|
+
const tailwindVersion = await getProjectTailwindVersionFromConfig(config);
|
|
1126
|
+
await updateTailwindConfig(tree.tailwind?.config, config, {
|
|
1127
|
+
silent: options.silent,
|
|
1128
|
+
tailwindVersion
|
|
1129
|
+
});
|
|
1130
|
+
const overwriteCssVars = await shouldOverwriteCssVars(components, config);
|
|
1131
|
+
await updateCssVars(tree.cssVars, config, {
|
|
1132
|
+
cleanupDefaultNextStyles: options.isNewProject,
|
|
1133
|
+
silent: options.silent,
|
|
1134
|
+
tailwindVersion,
|
|
1135
|
+
tailwindConfig: tree.tailwind?.config,
|
|
1136
|
+
overwriteCssVars,
|
|
1137
|
+
initIndex: options.baseStyle
|
|
1138
|
+
});
|
|
1139
|
+
await updateCss(tree.css, config, { silent: options.silent });
|
|
1140
|
+
await updateEnvVars(tree.envVars, config, { silent: options.silent });
|
|
1141
|
+
await updateDependencies(tree.dependencies, tree.devDependencies, config, { silent: options.silent });
|
|
1142
|
+
await updateFiles(tree.files, config, {
|
|
1143
|
+
overwrite: options.overwrite,
|
|
1144
|
+
silent: options.silent,
|
|
1145
|
+
path: options.path
|
|
1146
|
+
});
|
|
1147
|
+
if (tree.docs) logger.info(tree.docs);
|
|
1148
|
+
}
|
|
1149
|
+
async function addWorkspaceComponents(components, config, workspaceConfig, options) {
|
|
1150
|
+
if (!options.baseStyle && !components.length) return;
|
|
1151
|
+
const registrySpinner = spinner(`Checking registry.`, { silent: options.silent })?.start();
|
|
1152
|
+
const tree = await resolveRegistryTree(components, configWithDefaults(config));
|
|
1153
|
+
if (!tree) {
|
|
1154
|
+
registrySpinner?.fail();
|
|
1155
|
+
return handleError(/* @__PURE__ */ new Error("Failed to fetch components from registry."));
|
|
1156
|
+
}
|
|
1157
|
+
try {
|
|
1158
|
+
validateFilesTarget(tree.files ?? [], config.resolvedPaths.cwd);
|
|
1159
|
+
} catch (error) {
|
|
1160
|
+
registrySpinner?.fail();
|
|
1161
|
+
return handleError(error);
|
|
1162
|
+
}
|
|
1163
|
+
registrySpinner?.succeed();
|
|
1164
|
+
const filesCreated = [];
|
|
1165
|
+
const filesUpdated = [];
|
|
1166
|
+
const filesSkipped = [];
|
|
1167
|
+
const rootSpinner = spinner(`Installing components.`)?.start();
|
|
1168
|
+
const mainTargetConfig = workspaceConfig.ui;
|
|
1169
|
+
const tailwindVersion = await getProjectTailwindVersionFromConfig(mainTargetConfig);
|
|
1170
|
+
const workspaceRoot = findCommonRoot(config.resolvedPaths.cwd, mainTargetConfig.resolvedPaths.ui);
|
|
1171
|
+
if (tree.tailwind?.config) {
|
|
1172
|
+
await updateTailwindConfig(tree.tailwind?.config, mainTargetConfig, {
|
|
1173
|
+
silent: true,
|
|
1174
|
+
tailwindVersion
|
|
1175
|
+
});
|
|
1176
|
+
filesUpdated.push(path.relative(workspaceRoot, mainTargetConfig.resolvedPaths.tailwindConfig));
|
|
1177
|
+
}
|
|
1178
|
+
if (tree.cssVars) {
|
|
1179
|
+
const overwriteCssVars = await shouldOverwriteCssVars(components, config);
|
|
1180
|
+
await updateCssVars(tree.cssVars, mainTargetConfig, {
|
|
1181
|
+
silent: true,
|
|
1182
|
+
tailwindVersion,
|
|
1183
|
+
tailwindConfig: tree.tailwind?.config,
|
|
1184
|
+
overwriteCssVars
|
|
1185
|
+
});
|
|
1186
|
+
filesUpdated.push(path.relative(workspaceRoot, mainTargetConfig.resolvedPaths.tailwindCss));
|
|
1187
|
+
}
|
|
1188
|
+
if (tree.css) {
|
|
1189
|
+
await updateCss(tree.css, mainTargetConfig, { silent: true });
|
|
1190
|
+
filesUpdated.push(path.relative(workspaceRoot, mainTargetConfig.resolvedPaths.tailwindCss));
|
|
1191
|
+
}
|
|
1192
|
+
if (tree.envVars) await updateEnvVars(tree.envVars, mainTargetConfig, { silent: true });
|
|
1193
|
+
await updateDependencies(tree.dependencies, tree.devDependencies, mainTargetConfig, { silent: true });
|
|
1194
|
+
const filesByType = /* @__PURE__ */ new Map();
|
|
1195
|
+
for (const file of tree.files ?? []) {
|
|
1196
|
+
const type = file.type || "registry:ui";
|
|
1197
|
+
if (!filesByType.has(type)) filesByType.set(type, []);
|
|
1198
|
+
filesByType.get(type).push(file);
|
|
1199
|
+
}
|
|
1200
|
+
for (const type of Array.from(filesByType.keys())) {
|
|
1201
|
+
const typeFiles = filesByType.get(type);
|
|
1202
|
+
let targetConfig = type === "registry:ui" ? workspaceConfig.ui : config;
|
|
1203
|
+
const typeWorkspaceRoot = findCommonRoot(config.resolvedPaths.cwd, targetConfig.resolvedPaths.ui || targetConfig.resolvedPaths.cwd);
|
|
1204
|
+
const packageRoot = await findPackageRoot(typeWorkspaceRoot, targetConfig.resolvedPaths.cwd) ?? targetConfig.resolvedPaths.cwd;
|
|
1205
|
+
const files = await updateFiles(typeFiles, targetConfig, {
|
|
1206
|
+
overwrite: options.overwrite,
|
|
1207
|
+
silent: true,
|
|
1208
|
+
rootSpinner,
|
|
1209
|
+
isRemote: options.isRemote,
|
|
1210
|
+
isWorkspace: true,
|
|
1211
|
+
path: options.path
|
|
1212
|
+
});
|
|
1213
|
+
filesCreated.push(...files.filesCreated.map((file) => path.relative(typeWorkspaceRoot, path.join(packageRoot, file))));
|
|
1214
|
+
filesUpdated.push(...files.filesUpdated.map((file) => path.relative(typeWorkspaceRoot, path.join(packageRoot, file))));
|
|
1215
|
+
filesSkipped.push(...files.filesSkipped.map((file) => path.relative(typeWorkspaceRoot, path.join(packageRoot, file))));
|
|
1216
|
+
}
|
|
1217
|
+
rootSpinner?.succeed();
|
|
1218
|
+
filesCreated.sort();
|
|
1219
|
+
filesUpdated.sort();
|
|
1220
|
+
filesSkipped.sort();
|
|
1221
|
+
if (!(filesCreated.length || filesUpdated.length) && !filesSkipped.length) spinner(`No files updated.`, { silent: options.silent })?.info();
|
|
1222
|
+
if (filesCreated.length) {
|
|
1223
|
+
spinner(`Created ${filesCreated.length} ${filesCreated.length === 1 ? "file" : "files"}:`, { silent: options.silent })?.succeed();
|
|
1224
|
+
for (const file of filesCreated) logger.log(` - ${file}`);
|
|
1225
|
+
}
|
|
1226
|
+
if (filesUpdated.length) {
|
|
1227
|
+
spinner(`Updated ${filesUpdated.length} ${filesUpdated.length === 1 ? "file" : "files"}:`, { silent: options.silent })?.info();
|
|
1228
|
+
for (const file of filesUpdated) logger.log(` - ${file}`);
|
|
1229
|
+
}
|
|
1230
|
+
if (filesSkipped.length) {
|
|
1231
|
+
spinner(`Skipped ${filesSkipped.length} ${filesUpdated.length === 1 ? "file" : "files"}: (use --overwrite to overwrite)`, { silent: options.silent })?.info();
|
|
1232
|
+
for (const file of filesSkipped) logger.log(` - ${file}`);
|
|
1233
|
+
}
|
|
1234
|
+
if (tree.docs) logger.info(tree.docs);
|
|
1235
|
+
}
|
|
1236
|
+
async function shouldOverwriteCssVars(components, config) {
|
|
1237
|
+
const result = await getRegistryItems(components, { config });
|
|
1238
|
+
return z.array(registryItemSchema).parse(result).some((component) => component.type === "registry:theme" || component.type === "registry:style");
|
|
1239
|
+
}
|
|
1240
|
+
function validateFilesTarget(files, cwd) {
|
|
1241
|
+
for (const file of files) {
|
|
1242
|
+
if (!file?.target) continue;
|
|
1243
|
+
if (!isSafeTarget(file.target, cwd)) throw new Error(`We found an unsafe file path "${file.target} in the registry item. Installation aborted.`);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
//#endregion
|
|
1248
|
+
//#region src/utils/env-loader.ts
|
|
1249
|
+
async function loadEnvFiles(cwd = process.cwd()) {
|
|
1250
|
+
try {
|
|
1251
|
+
const { config } = await import("@dotenvx/dotenvx");
|
|
1252
|
+
for (const envFile of [
|
|
1253
|
+
".env.local",
|
|
1254
|
+
".env.development.local",
|
|
1255
|
+
".env.development",
|
|
1256
|
+
".env"
|
|
1257
|
+
]) {
|
|
1258
|
+
const envPath = join(cwd, envFile);
|
|
1259
|
+
if (existsSync(envPath)) config({
|
|
1260
|
+
path: envPath,
|
|
1261
|
+
overload: false,
|
|
1262
|
+
quiet: true
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
} catch (error) {
|
|
1266
|
+
logger.warn("Failed to load env files:", error);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
//#endregion
|
|
1271
|
+
//#region src/utils/file-helper.ts
|
|
1272
|
+
const FILE_BACKUP_SUFFIX = ".bak";
|
|
1273
|
+
function createFileBackup(filePath) {
|
|
1274
|
+
if (!fsExtra.existsSync(filePath)) return null;
|
|
1275
|
+
const backupPath = `${filePath}${FILE_BACKUP_SUFFIX}`;
|
|
1276
|
+
try {
|
|
1277
|
+
fsExtra.renameSync(filePath, backupPath);
|
|
1278
|
+
return backupPath;
|
|
1279
|
+
} catch (error) {
|
|
1280
|
+
console.error(`Failed to create backup of ${filePath}: ${error}`);
|
|
1281
|
+
return null;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
function restoreFileBackup(filePath) {
|
|
1285
|
+
const backupPath = `${filePath}${FILE_BACKUP_SUFFIX}`;
|
|
1286
|
+
if (!fsExtra.existsSync(backupPath)) return false;
|
|
1287
|
+
try {
|
|
1288
|
+
fsExtra.renameSync(backupPath, filePath);
|
|
1289
|
+
return true;
|
|
1290
|
+
} catch (error) {
|
|
1291
|
+
console.error(`Warning: Could not restore backup file ${backupPath}: ${error}`);
|
|
1292
|
+
return false;
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
function deleteFileBackup(filePath) {
|
|
1296
|
+
const backupPath = `${filePath}${FILE_BACKUP_SUFFIX}`;
|
|
1297
|
+
if (!fsExtra.existsSync(backupPath)) return false;
|
|
1298
|
+
try {
|
|
1299
|
+
fsExtra.unlinkSync(backupPath);
|
|
1300
|
+
return true;
|
|
1301
|
+
} catch (_error) {
|
|
1302
|
+
return false;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
//#endregion
|
|
1307
|
+
//#region src/registry/namespaces.ts
|
|
1308
|
+
async function resolveRegistryNamespaces(components, config) {
|
|
1309
|
+
const discoveredNamespaces = /* @__PURE__ */ new Set();
|
|
1310
|
+
const visitedItems = /* @__PURE__ */ new Set();
|
|
1311
|
+
const itemsToProcess = [...components];
|
|
1312
|
+
while (itemsToProcess.length > 0) {
|
|
1313
|
+
const currentItem = itemsToProcess.shift();
|
|
1314
|
+
if (visitedItems.has(currentItem)) continue;
|
|
1315
|
+
visitedItems.add(currentItem);
|
|
1316
|
+
const { registry } = parseRegistryAndItemFromString(currentItem);
|
|
1317
|
+
if (registry && !BUILTIN_REGISTRIES[registry]) discoveredNamespaces.add(registry);
|
|
1318
|
+
try {
|
|
1319
|
+
const [item] = await fetchRegistryItems([currentItem], config, { useCache: true });
|
|
1320
|
+
if (item?.registryDependencies) for (const dep of item.registryDependencies) {
|
|
1321
|
+
const { registry: depRegistry } = parseRegistryAndItemFromString(dep);
|
|
1322
|
+
if (depRegistry && !BUILTIN_REGISTRIES[depRegistry]) discoveredNamespaces.add(depRegistry);
|
|
1323
|
+
if (!visitedItems.has(dep)) itemsToProcess.push(dep);
|
|
1324
|
+
}
|
|
1325
|
+
} catch (error) {
|
|
1326
|
+
if (error instanceof RegistryNotConfiguredError) {
|
|
1327
|
+
const { registry: registry$1 } = parseRegistryAndItemFromString(currentItem);
|
|
1328
|
+
if (registry$1 && !BUILTIN_REGISTRIES[registry$1]) discoveredNamespaces.add(registry$1);
|
|
1329
|
+
continue;
|
|
1330
|
+
}
|
|
1331
|
+
continue;
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
return Array.from(discoveredNamespaces);
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
//#endregion
|
|
1338
|
+
//#region src/utils/registries.ts
|
|
1339
|
+
async function ensureRegistriesInConfig(components, config, options = {}) {
|
|
1340
|
+
options = {
|
|
1341
|
+
silent: false,
|
|
1342
|
+
writeFile: true,
|
|
1343
|
+
...options
|
|
1344
|
+
};
|
|
1345
|
+
const missingRegistries = (await resolveRegistryNamespaces(components, config)).filter((registry) => !config.registries?.[registry] && !Object.keys(BUILTIN_REGISTRIES).includes(registry));
|
|
1346
|
+
if (missingRegistries.length === 0) return {
|
|
1347
|
+
config,
|
|
1348
|
+
newRegistries: []
|
|
1349
|
+
};
|
|
1350
|
+
const registryIndex = await getRegistriesIndex({ useCache: process.env.NODE_ENV !== "development" });
|
|
1351
|
+
if (!registryIndex) return {
|
|
1352
|
+
config,
|
|
1353
|
+
newRegistries: []
|
|
1354
|
+
};
|
|
1355
|
+
const foundRegistries = {};
|
|
1356
|
+
for (const registry of missingRegistries) if (registryIndex[registry]) foundRegistries[registry] = registryIndex[registry];
|
|
1357
|
+
if (Object.keys(foundRegistries).length === 0) return {
|
|
1358
|
+
config,
|
|
1359
|
+
newRegistries: []
|
|
1360
|
+
};
|
|
1361
|
+
const existingRegistries = Object.fromEntries(Object.entries(config.registries || {}).filter(([key]) => !Object.keys(BUILTIN_REGISTRIES).includes(key)));
|
|
1362
|
+
const newConfigWithRegistries = {
|
|
1363
|
+
...config,
|
|
1364
|
+
registries: {
|
|
1365
|
+
...existingRegistries,
|
|
1366
|
+
...foundRegistries
|
|
1367
|
+
}
|
|
1368
|
+
};
|
|
1369
|
+
if (options.writeFile) {
|
|
1370
|
+
const { resolvedPaths: _resolvedPaths, ...configWithoutResolvedPaths } = newConfigWithRegistries;
|
|
1371
|
+
const configSpinner = spinner("Updating components.json.", { silent: options.silent }).start();
|
|
1372
|
+
const updatedConfig = rawConfigSchema.parse(configWithoutResolvedPaths);
|
|
1373
|
+
await fsExtra.writeFile(path.resolve(config.resolvedPaths.cwd, "components.json"), `${JSON.stringify(updatedConfig, null, 2)}\n`, "utf-8");
|
|
1374
|
+
configSpinner.succeed();
|
|
1375
|
+
}
|
|
1376
|
+
return {
|
|
1377
|
+
config: newConfigWithRegistries,
|
|
1378
|
+
newRegistries: Object.keys(foundRegistries)
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
//#endregion
|
|
1383
|
+
//#region src/utils/updaters/update-tailwind-content.ts
|
|
1384
|
+
async function updateTailwindContent(content, config, options) {
|
|
1385
|
+
if (!content) return;
|
|
1386
|
+
options = {
|
|
1387
|
+
silent: false,
|
|
1388
|
+
...options
|
|
1389
|
+
};
|
|
1390
|
+
const tailwindFileRelativePath = path.relative(config.resolvedPaths.cwd, config.resolvedPaths.tailwindConfig);
|
|
1391
|
+
const tailwindSpinner = spinner(`Updating ${highlighter.info(tailwindFileRelativePath)}`, { silent: options.silent }).start();
|
|
1392
|
+
const output = await transformTailwindContent(await promises.readFile(config.resolvedPaths.tailwindConfig, "utf8"), content, config);
|
|
1393
|
+
await promises.writeFile(config.resolvedPaths.tailwindConfig, output, "utf8");
|
|
1394
|
+
tailwindSpinner?.succeed();
|
|
1395
|
+
}
|
|
1396
|
+
async function transformTailwindContent(input, content, config) {
|
|
1397
|
+
const sourceFile = await _createSourceFile(input, config);
|
|
1398
|
+
const configObject = sourceFile.getDescendantsOfKind(SyntaxKind.ObjectLiteralExpression).find((node) => node.getProperties().some((property) => property.isKind(SyntaxKind.PropertyAssignment) && property.getName() === "content"));
|
|
1399
|
+
if (!configObject) return input;
|
|
1400
|
+
addTailwindConfigContent(configObject, content);
|
|
1401
|
+
return sourceFile.getFullText();
|
|
1402
|
+
}
|
|
1403
|
+
async function addTailwindConfigContent(configObject, content) {
|
|
1404
|
+
const quoteChar = _getQuoteChar(configObject);
|
|
1405
|
+
const existingProperty = configObject.getProperty("content");
|
|
1406
|
+
if (!existingProperty) {
|
|
1407
|
+
const newProperty = {
|
|
1408
|
+
name: "content",
|
|
1409
|
+
initializer: `[${quoteChar}${content.join(`${quoteChar}, ${quoteChar}`)}${quoteChar}]`
|
|
1410
|
+
};
|
|
1411
|
+
configObject.addPropertyAssignment(newProperty);
|
|
1412
|
+
return configObject;
|
|
1413
|
+
}
|
|
1414
|
+
if (existingProperty.isKind(SyntaxKind.PropertyAssignment)) {
|
|
1415
|
+
const initializer = existingProperty.getInitializer();
|
|
1416
|
+
if (initializer?.isKind(SyntaxKind.ArrayLiteralExpression)) for (const contentItem of content) {
|
|
1417
|
+
const newValue = `${quoteChar}${contentItem}${quoteChar}`;
|
|
1418
|
+
if (initializer.getElements().map((element) => element.getText()).includes(newValue)) continue;
|
|
1419
|
+
initializer.addElement(newValue);
|
|
1420
|
+
}
|
|
1421
|
+
return configObject;
|
|
1422
|
+
}
|
|
1423
|
+
return configObject;
|
|
236
1424
|
}
|
|
237
1425
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
1426
|
+
//#endregion
|
|
1427
|
+
//#region src/commands/init.ts
|
|
1428
|
+
process.on("exit", (code) => {
|
|
1429
|
+
const filePath = path.resolve(process.cwd(), "components.json");
|
|
1430
|
+
if (code === 0) return deleteFileBackup(filePath);
|
|
1431
|
+
return restoreFileBackup(filePath);
|
|
1432
|
+
});
|
|
1433
|
+
const initOptionsSchema = z.object({
|
|
1434
|
+
cwd: z.string(),
|
|
1435
|
+
components: z.array(z.string()).optional(),
|
|
1436
|
+
yes: z.boolean(),
|
|
1437
|
+
defaults: z.boolean(),
|
|
1438
|
+
force: z.boolean(),
|
|
1439
|
+
silent: z.boolean(),
|
|
1440
|
+
isNewProject: z.boolean(),
|
|
1441
|
+
srcDir: z.boolean().optional(),
|
|
1442
|
+
cssVariables: z.boolean(),
|
|
1443
|
+
brand: z.string().optional(),
|
|
1444
|
+
baseColor: z.string().optional().refine((val) => {
|
|
1445
|
+
if (val) return BASE_COLORS.find((color) => color.name === val);
|
|
1446
|
+
return true;
|
|
1447
|
+
}, { message: `Invalid base color. Please use '${BASE_COLORS.map((color) => color.name).join("', '")}'` }),
|
|
1448
|
+
baseStyle: z.boolean()
|
|
1449
|
+
});
|
|
1450
|
+
const init = new Command().name("init").description("Initialize Frontic UI in your Vue/Nuxt project").argument("[components...]", "names, url or local path to component").option("-b, --base-color <base-color>", "the base color to use. (neutral, gray, zinc, stone, slate)", void 0).option("--brand <color>", "brand color in hex format (e.g., #6366f1)", void 0).option("-y, --yes", "skip confirmation prompt.", true).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("--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.").action(async (components, opts) => {
|
|
1451
|
+
try {
|
|
1452
|
+
if (opts.defaults) {
|
|
1453
|
+
opts.template = opts.template || "next";
|
|
1454
|
+
opts.baseColor = opts.baseColor || "neutral";
|
|
1455
|
+
}
|
|
1456
|
+
const options = initOptionsSchema.parse({
|
|
1457
|
+
cwd: path.resolve(opts.cwd),
|
|
1458
|
+
isNewProject: false,
|
|
1459
|
+
components,
|
|
1460
|
+
...opts
|
|
1461
|
+
});
|
|
1462
|
+
await loadEnvFiles(options.cwd);
|
|
1463
|
+
if (components.length > 0) {
|
|
1464
|
+
let shadowConfig = configWithDefaults(createConfig({ resolvedPaths: { cwd: options.cwd } }));
|
|
1465
|
+
const componentsJsonPath = path.resolve(options.cwd, "components.json");
|
|
1466
|
+
if (fsExtra.existsSync(componentsJsonPath)) {
|
|
1467
|
+
const existingConfig = await fsExtra.readJson(componentsJsonPath);
|
|
1468
|
+
const config = rawConfigSchema.partial().parse(existingConfig);
|
|
1469
|
+
const baseConfig = createConfig({ resolvedPaths: { cwd: options.cwd } });
|
|
1470
|
+
shadowConfig = configWithDefaults({
|
|
1471
|
+
...config,
|
|
1472
|
+
resolvedPaths: {
|
|
1473
|
+
...baseConfig.resolvedPaths,
|
|
1474
|
+
cwd: options.cwd
|
|
1475
|
+
}
|
|
1476
|
+
});
|
|
1477
|
+
createFileBackup(componentsJsonPath);
|
|
1478
|
+
}
|
|
1479
|
+
const { config: updatedConfig } = await ensureRegistriesInConfig(components, shadowConfig, {
|
|
1480
|
+
silent: true,
|
|
1481
|
+
writeFile: false
|
|
1482
|
+
});
|
|
1483
|
+
shadowConfig = updatedConfig;
|
|
1484
|
+
buildUrlAndHeadersForRegistryItem(components[0], shadowConfig);
|
|
1485
|
+
const [item] = await getRegistryItems([components[0]], { config: shadowConfig });
|
|
1486
|
+
if (item?.type === "registry:style") {
|
|
1487
|
+
options.baseColor = "neutral";
|
|
1488
|
+
options.baseStyle = item.extends === "none" ? false : options.baseStyle;
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
if (!options.baseStyle) options.baseColor = "neutral";
|
|
1492
|
+
await runInit(options);
|
|
1493
|
+
logger.log(`${highlighter.success("Success!")} Frontic UI initialized.\nYou may now add components with: ${highlighter.info("npx @frontic/ui add button")}`);
|
|
1494
|
+
deleteFileBackup(path.resolve(options.cwd, "components.json"));
|
|
1495
|
+
logger.break();
|
|
1496
|
+
} catch (error) {
|
|
1497
|
+
logger.break();
|
|
1498
|
+
handleError(error);
|
|
1499
|
+
} finally {
|
|
1500
|
+
clearRegistryContext();
|
|
1501
|
+
}
|
|
1502
|
+
});
|
|
1503
|
+
async function runInit(options) {
|
|
1504
|
+
let projectInfo;
|
|
1505
|
+
if (!options.skipPreflight) {
|
|
1506
|
+
const preflight = await preFlightInit(options);
|
|
1507
|
+
if (preflight.errors[MISSING_DIR_OR_EMPTY_PROJECT]) process.exit(1);
|
|
1508
|
+
projectInfo = preflight.projectInfo;
|
|
1509
|
+
} else projectInfo = await getProjectInfo(options.cwd);
|
|
1510
|
+
const projectConfig = await getProjectConfig(options.cwd, projectInfo);
|
|
1511
|
+
let config = projectConfig ? await promptForMinimalConfig(projectConfig, options) : await promptForConfig(await getConfig(options.cwd));
|
|
1512
|
+
if (!options.yes) {
|
|
1513
|
+
const { proceed } = await prompts({
|
|
1514
|
+
type: "confirm",
|
|
1515
|
+
name: "proceed",
|
|
1516
|
+
message: `Write configuration to ${highlighter.info("components.json")}. Proceed?`,
|
|
1517
|
+
initial: true
|
|
1518
|
+
});
|
|
1519
|
+
if (!proceed) process.exit(0);
|
|
1520
|
+
}
|
|
1521
|
+
const components = [...options.baseStyle ? ["index"] : [], ...options.components ?? []];
|
|
1522
|
+
const { config: configWithRegistries } = await ensureRegistriesInConfig(components, await resolveConfigPaths(options.cwd, config), { silent: true });
|
|
1523
|
+
if (configWithRegistries.registries) config.registries = configWithRegistries.registries;
|
|
1524
|
+
const componentSpinner = spinner(`Writing components.json.`).start();
|
|
1525
|
+
const targetPath = path.resolve(options.cwd, "components.json");
|
|
1526
|
+
const backupPath = `${targetPath}${FILE_BACKUP_SUFFIX}`;
|
|
1527
|
+
if (!options.force && fsExtra.existsSync(backupPath)) {
|
|
1528
|
+
const { registries, ...merged } = deepmerge(await fsExtra.readJson(backupPath), config);
|
|
1529
|
+
config = {
|
|
1530
|
+
...merged,
|
|
1531
|
+
registries
|
|
1532
|
+
};
|
|
1533
|
+
}
|
|
1534
|
+
config.registries = Object.fromEntries(Object.entries(config.registries || {}).filter(([key]) => !Object.keys(BUILTIN_REGISTRIES).includes(key)));
|
|
1535
|
+
await promises.writeFile(targetPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
1536
|
+
componentSpinner.succeed();
|
|
1537
|
+
const fullConfig = await resolveConfigPaths(options.cwd, config);
|
|
1538
|
+
await addComponents(components, fullConfig, {
|
|
1539
|
+
overwrite: true,
|
|
1540
|
+
silent: options.silent,
|
|
1541
|
+
baseStyle: options.baseStyle,
|
|
1542
|
+
isNewProject: options.isNewProject || projectInfo?.framework.name === "nuxt4"
|
|
1543
|
+
});
|
|
1544
|
+
if (options.isNewProject && options.srcDir) await updateTailwindContent(["./src/**/*.{js,ts,jsx,tsx,mdx}"], fullConfig, { silent: options.silent });
|
|
1545
|
+
const configPath = path.resolve(options.cwd, "components.json");
|
|
1546
|
+
try {
|
|
1547
|
+
const existingConfig = await fsExtra.readJson(configPath);
|
|
1548
|
+
existingConfig.registries = existingConfig.registries || {};
|
|
1549
|
+
existingConfig.registries["@frontic"] = { url: `${REGISTRY_URL}/styles/{style}/{name}.json` };
|
|
1550
|
+
await promises.writeFile(configPath, `${JSON.stringify(existingConfig, null, 2)}\n`, "utf8");
|
|
1551
|
+
if (!options.silent) logger.success("Frontic registry configured");
|
|
1552
|
+
} catch {
|
|
1553
|
+
logger.warn("Could not add Frontic registry to components.json");
|
|
1554
|
+
}
|
|
1555
|
+
const { brand, brandForeground } = fullConfig.tailwind;
|
|
1556
|
+
if (!options.silent && brand) {
|
|
1557
|
+
logger.break();
|
|
1558
|
+
logger.info("Your Frontic theme settings:");
|
|
1559
|
+
logger.info(` --brand: ${highlighter.info(brand)}`);
|
|
1560
|
+
if (brandForeground) logger.info(` --brand-foreground: ${highlighter.info(brandForeground)}`);
|
|
1561
|
+
}
|
|
1562
|
+
return fullConfig;
|
|
241
1563
|
}
|
|
1564
|
+
async function promptForConfig(defaultConfig = null) {
|
|
1565
|
+
const [styles, baseColors] = await Promise.all([getRegistryStyles(), getRegistryBaseColors()]);
|
|
1566
|
+
logger.info("");
|
|
1567
|
+
const options = await prompts([
|
|
1568
|
+
{
|
|
1569
|
+
type: "toggle",
|
|
1570
|
+
name: "typescript",
|
|
1571
|
+
message: `Would you like to use ${highlighter.info("TypeScript")} (recommended)?`,
|
|
1572
|
+
initial: defaultConfig?.typescript ?? true,
|
|
1573
|
+
active: "yes",
|
|
1574
|
+
inactive: "no"
|
|
1575
|
+
},
|
|
1576
|
+
{
|
|
1577
|
+
type: "select",
|
|
1578
|
+
name: "style",
|
|
1579
|
+
message: `Which ${highlighter.info("style")} would you like to use?`,
|
|
1580
|
+
choices: styles.map((style) => ({
|
|
1581
|
+
title: style.label,
|
|
1582
|
+
value: style.name
|
|
1583
|
+
}))
|
|
1584
|
+
},
|
|
1585
|
+
{
|
|
1586
|
+
type: "select",
|
|
1587
|
+
name: "tailwindBaseColor",
|
|
1588
|
+
message: `Which color would you like to use as the ${highlighter.info("base color")}?`,
|
|
1589
|
+
choices: baseColors.map((color) => ({
|
|
1590
|
+
title: color.label,
|
|
1591
|
+
value: color.name
|
|
1592
|
+
}))
|
|
1593
|
+
},
|
|
1594
|
+
{
|
|
1595
|
+
type: "text",
|
|
1596
|
+
name: "brandColor",
|
|
1597
|
+
message: `What ${highlighter.info("brand color")} would you like to use? (hex)`,
|
|
1598
|
+
initial: "#6366f1",
|
|
1599
|
+
validate: (value) => {
|
|
1600
|
+
if (!value) return true;
|
|
1601
|
+
return isValidHex(value) || "Please enter a valid hex color (e.g., #6366f1)";
|
|
1602
|
+
}
|
|
1603
|
+
},
|
|
1604
|
+
{
|
|
1605
|
+
type: "text",
|
|
1606
|
+
name: "tailwindCss",
|
|
1607
|
+
message: `Where is your ${highlighter.info("global CSS")} file?`,
|
|
1608
|
+
initial: defaultConfig?.tailwind.css ?? DEFAULT_TAILWIND_CSS
|
|
1609
|
+
},
|
|
1610
|
+
{
|
|
1611
|
+
type: "toggle",
|
|
1612
|
+
name: "tailwindCssVariables",
|
|
1613
|
+
message: `Would you like to use ${highlighter.info("CSS variables")} for theming?`,
|
|
1614
|
+
initial: defaultConfig?.tailwind.cssVariables ?? true,
|
|
1615
|
+
active: "yes",
|
|
1616
|
+
inactive: "no"
|
|
1617
|
+
},
|
|
1618
|
+
{
|
|
1619
|
+
type: "text",
|
|
1620
|
+
name: "tailwindPrefix",
|
|
1621
|
+
message: `Are you using a custom ${highlighter.info("tailwind prefix eg. tw-")}? (Leave blank if not)`,
|
|
1622
|
+
initial: ""
|
|
1623
|
+
},
|
|
1624
|
+
{
|
|
1625
|
+
type: "text",
|
|
1626
|
+
name: "tailwindConfig",
|
|
1627
|
+
message: `Where is your ${highlighter.info("tailwind.config.js")} located?`,
|
|
1628
|
+
initial: defaultConfig?.tailwind.config ?? DEFAULT_TAILWIND_CONFIG
|
|
1629
|
+
},
|
|
1630
|
+
{
|
|
1631
|
+
type: "text",
|
|
1632
|
+
name: "components",
|
|
1633
|
+
message: `Configure the import alias for ${highlighter.info("components")}:`,
|
|
1634
|
+
initial: defaultConfig?.aliases.components ?? DEFAULT_COMPONENTS
|
|
1635
|
+
},
|
|
1636
|
+
{
|
|
1637
|
+
type: "text",
|
|
1638
|
+
name: "utils",
|
|
1639
|
+
message: `Configure the import alias for ${highlighter.info("utils")}:`,
|
|
1640
|
+
initial: defaultConfig?.aliases.utils ?? DEFAULT_UTILS
|
|
1641
|
+
}
|
|
1642
|
+
]);
|
|
1643
|
+
const brandOklch = options.brandColor && isValidHex(options.brandColor) ? hexToOklch(options.brandColor) : void 0;
|
|
1644
|
+
const brandForeground = brandOklch ? getForegroundColor(brandOklch) : void 0;
|
|
1645
|
+
return rawConfigSchema.parse({
|
|
1646
|
+
$schema: "https://shadcn-vue.com/schema.json",
|
|
1647
|
+
style: options.style,
|
|
1648
|
+
tailwind: {
|
|
1649
|
+
config: options.tailwindConfig,
|
|
1650
|
+
css: options.tailwindCss,
|
|
1651
|
+
baseColor: options.tailwindBaseColor,
|
|
1652
|
+
cssVariables: options.tailwindCssVariables,
|
|
1653
|
+
prefix: options.tailwindPrefix,
|
|
1654
|
+
...brandOklch && { brand: brandOklch },
|
|
1655
|
+
...brandForeground && { brandForeground }
|
|
1656
|
+
},
|
|
1657
|
+
typescript: options.typescript,
|
|
1658
|
+
aliases: {
|
|
1659
|
+
utils: options.utils,
|
|
1660
|
+
components: options.components,
|
|
1661
|
+
lib: options.components.replace(/\/components$/, "/lib"),
|
|
1662
|
+
composables: options.components.replace(/\/components$/, "/composables")
|
|
1663
|
+
}
|
|
1664
|
+
});
|
|
1665
|
+
}
|
|
1666
|
+
async function promptForMinimalConfig(defaultConfig, opts) {
|
|
1667
|
+
let style = defaultConfig.style;
|
|
1668
|
+
let baseColor = opts.baseColor;
|
|
1669
|
+
let cssVariables = defaultConfig.tailwind.cssVariables;
|
|
1670
|
+
let brandOklch;
|
|
1671
|
+
let brandForeground;
|
|
1672
|
+
if (opts.brand && isValidHex(opts.brand)) {
|
|
1673
|
+
brandOklch = hexToOklch(opts.brand);
|
|
1674
|
+
brandForeground = getForegroundColor(brandOklch);
|
|
1675
|
+
}
|
|
1676
|
+
if (!opts.defaults) {
|
|
1677
|
+
const [styles, baseColors, tailwindVersion] = await Promise.all([
|
|
1678
|
+
getRegistryStyles(),
|
|
1679
|
+
getRegistryBaseColors(),
|
|
1680
|
+
getProjectTailwindVersionFromConfig(defaultConfig)
|
|
1681
|
+
]);
|
|
1682
|
+
const options = await prompts([
|
|
1683
|
+
{
|
|
1684
|
+
type: tailwindVersion === "v4" ? null : "select",
|
|
1685
|
+
name: "style",
|
|
1686
|
+
message: `Which ${highlighter.info("style")} would you like to use?`,
|
|
1687
|
+
choices: styles.map((style$1) => ({
|
|
1688
|
+
title: style$1.name === "new-york" ? "New York (Recommended)" : style$1.label,
|
|
1689
|
+
value: style$1.name
|
|
1690
|
+
})),
|
|
1691
|
+
initial: 0
|
|
1692
|
+
},
|
|
1693
|
+
{
|
|
1694
|
+
type: opts.baseColor ? null : "select",
|
|
1695
|
+
name: "tailwindBaseColor",
|
|
1696
|
+
message: `Which color would you like to use as the ${highlighter.info("base color")}?`,
|
|
1697
|
+
choices: baseColors.map((color) => ({
|
|
1698
|
+
title: color.label,
|
|
1699
|
+
value: color.name
|
|
1700
|
+
}))
|
|
1701
|
+
},
|
|
1702
|
+
{
|
|
1703
|
+
type: opts.brand ? null : "text",
|
|
1704
|
+
name: "brandColor",
|
|
1705
|
+
message: `What ${highlighter.info("brand color")} would you like to use? (hex)`,
|
|
1706
|
+
initial: "#6366f1",
|
|
1707
|
+
validate: (value) => {
|
|
1708
|
+
if (!value) return true;
|
|
1709
|
+
return isValidHex(value) || "Please enter a valid hex color (e.g., #6366f1)";
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
]);
|
|
1713
|
+
style = options.style ?? "new-york";
|
|
1714
|
+
baseColor = options.tailwindBaseColor ?? baseColor;
|
|
1715
|
+
cssVariables = opts.cssVariables;
|
|
1716
|
+
if (!opts.brand && options.brandColor && isValidHex(options.brandColor)) {
|
|
1717
|
+
brandOklch = hexToOklch(options.brandColor);
|
|
1718
|
+
brandForeground = getForegroundColor(brandOklch);
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
return rawConfigSchema.parse({
|
|
1722
|
+
$schema: defaultConfig?.$schema,
|
|
1723
|
+
style,
|
|
1724
|
+
tailwind: {
|
|
1725
|
+
...defaultConfig?.tailwind,
|
|
1726
|
+
baseColor,
|
|
1727
|
+
cssVariables,
|
|
1728
|
+
...brandOklch && { brand: brandOklch },
|
|
1729
|
+
...brandForeground && { brandForeground }
|
|
1730
|
+
},
|
|
1731
|
+
typescript: defaultConfig.typescript,
|
|
1732
|
+
aliases: defaultConfig?.aliases,
|
|
1733
|
+
iconLibrary: defaultConfig?.iconLibrary
|
|
1734
|
+
});
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
//#endregion
|
|
1738
|
+
//#region src/preflights/preflight-add.ts
|
|
1739
|
+
async function preFlightAdd(options) {
|
|
1740
|
+
const errors = {};
|
|
1741
|
+
if (!fsExtra.existsSync(options.cwd) || !fsExtra.existsSync(path.resolve(options.cwd, "package.json"))) {
|
|
1742
|
+
errors[MISSING_DIR_OR_EMPTY_PROJECT] = true;
|
|
1743
|
+
return {
|
|
1744
|
+
errors,
|
|
1745
|
+
config: null
|
|
1746
|
+
};
|
|
1747
|
+
}
|
|
1748
|
+
if (!fsExtra.existsSync(path.resolve(options.cwd, "components.json"))) {
|
|
1749
|
+
errors[MISSING_CONFIG] = true;
|
|
1750
|
+
return {
|
|
1751
|
+
errors,
|
|
1752
|
+
config: null
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
try {
|
|
1756
|
+
return {
|
|
1757
|
+
errors,
|
|
1758
|
+
config: await getConfig(options.cwd)
|
|
1759
|
+
};
|
|
1760
|
+
} catch (_error) {
|
|
1761
|
+
logger.break();
|
|
1762
|
+
logger.error(`An invalid ${highlighter.info("components.json")} file was found at ${highlighter.info(options.cwd)}.\nBefore you can add components, you must create a valid ${highlighter.info("components.json")} file by running the ${highlighter.info("init")} command.`);
|
|
1763
|
+
logger.error(`Learn more at ${highlighter.info("https://shadcn-vue.com/docs/components-json")}.`);
|
|
1764
|
+
logger.break();
|
|
1765
|
+
process.exit(1);
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
//#endregion
|
|
1770
|
+
//#region src/commands/add.ts
|
|
1771
|
+
const addOptionsSchema = z.object({
|
|
1772
|
+
components: z.array(z.string()).optional(),
|
|
1773
|
+
yes: z.boolean(),
|
|
1774
|
+
overwrite: z.boolean(),
|
|
1775
|
+
cwd: z.string(),
|
|
1776
|
+
all: z.boolean(),
|
|
1777
|
+
path: z.string().optional(),
|
|
1778
|
+
silent: z.boolean(),
|
|
1779
|
+
srcDir: z.boolean().optional(),
|
|
1780
|
+
cssVariables: z.boolean()
|
|
1781
|
+
});
|
|
1782
|
+
const add = new Command().name("add").description("add a component to your project").argument("[components...]", "names, url or local path to component").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("--css-variables", "use css variables for theming.", true).option("--no-css-variables", "do not use css variables for theming.").action(async (components, opts) => {
|
|
1783
|
+
try {
|
|
1784
|
+
const options = addOptionsSchema.parse({
|
|
1785
|
+
components,
|
|
1786
|
+
cwd: path.resolve(opts.cwd),
|
|
1787
|
+
...opts
|
|
1788
|
+
});
|
|
1789
|
+
await loadEnvFiles(options.cwd);
|
|
1790
|
+
let initialConfig = await getConfig(options.cwd);
|
|
1791
|
+
if (!initialConfig) initialConfig = createConfig({
|
|
1792
|
+
style: "new-york",
|
|
1793
|
+
resolvedPaths: { cwd: options.cwd }
|
|
1794
|
+
});
|
|
1795
|
+
let hasNewRegistries = false;
|
|
1796
|
+
if (components.length > 0) {
|
|
1797
|
+
const { config: updatedConfig$1, newRegistries } = await ensureRegistriesInConfig(components, initialConfig, {
|
|
1798
|
+
silent: options.silent,
|
|
1799
|
+
writeFile: false
|
|
1800
|
+
});
|
|
1801
|
+
initialConfig = updatedConfig$1;
|
|
1802
|
+
hasNewRegistries = newRegistries.length > 0;
|
|
1803
|
+
}
|
|
1804
|
+
let itemType;
|
|
1805
|
+
let shouldInstallBaseStyle = true;
|
|
1806
|
+
if (components.length > 0) {
|
|
1807
|
+
const [registryItem] = await getRegistryItems([components[0]], { config: initialConfig });
|
|
1808
|
+
itemType = registryItem?.type;
|
|
1809
|
+
shouldInstallBaseStyle = itemType !== "registry:theme" && itemType !== "registry:style";
|
|
1810
|
+
if (isUniversalRegistryItem(registryItem)) {
|
|
1811
|
+
await addComponents(components, initialConfig, {
|
|
1812
|
+
...options,
|
|
1813
|
+
baseStyle: shouldInstallBaseStyle
|
|
1814
|
+
});
|
|
1815
|
+
return;
|
|
1816
|
+
}
|
|
1817
|
+
if (!options.yes && (itemType === "registry:style" || itemType === "registry:theme")) {
|
|
1818
|
+
logger.break();
|
|
1819
|
+
const { confirm } = await prompts({
|
|
1820
|
+
type: "confirm",
|
|
1821
|
+
name: "confirm",
|
|
1822
|
+
message: highlighter.warn(`You are about to install a new ${itemType.replace("registry:", "")}. \nExisting CSS variables and components will be overwritten. Continue?`)
|
|
1823
|
+
});
|
|
1824
|
+
if (!confirm) {
|
|
1825
|
+
logger.break();
|
|
1826
|
+
logger.log(`Installation cancelled.`);
|
|
1827
|
+
logger.break();
|
|
1828
|
+
process.exit(1);
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
if (!options.components?.length) options.components = await promptForRegistryComponents(options);
|
|
1833
|
+
if ((await getProjectInfo(options.cwd))?.tailwindVersion === "v4") {
|
|
1834
|
+
const deprecatedComponents = DEPRECATED_COMPONENTS.filter((component) => options.components?.includes(component.name));
|
|
1835
|
+
if (deprecatedComponents?.length) {
|
|
1836
|
+
logger.break();
|
|
1837
|
+
deprecatedComponents.forEach((component) => {
|
|
1838
|
+
logger.warn(highlighter.warn(component.message));
|
|
1839
|
+
});
|
|
1840
|
+
logger.break();
|
|
1841
|
+
process.exit(1);
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
let { errors, config } = await preFlightAdd(options);
|
|
1845
|
+
if (errors[MISSING_CONFIG]) {
|
|
1846
|
+
const { proceed } = await prompts({
|
|
1847
|
+
type: "confirm",
|
|
1848
|
+
name: "proceed",
|
|
1849
|
+
message: `You need to create a ${highlighter.info("components.json")} file to add components. Proceed?`,
|
|
1850
|
+
initial: true
|
|
1851
|
+
});
|
|
1852
|
+
if (!proceed) {
|
|
1853
|
+
logger.break();
|
|
1854
|
+
process.exit(1);
|
|
1855
|
+
}
|
|
1856
|
+
config = await runInit({
|
|
1857
|
+
cwd: options.cwd,
|
|
1858
|
+
yes: true,
|
|
1859
|
+
force: true,
|
|
1860
|
+
defaults: false,
|
|
1861
|
+
skipPreflight: false,
|
|
1862
|
+
silent: options.silent && !hasNewRegistries,
|
|
1863
|
+
isNewProject: false,
|
|
1864
|
+
srcDir: options.srcDir,
|
|
1865
|
+
cssVariables: options.cssVariables,
|
|
1866
|
+
baseStyle: shouldInstallBaseStyle,
|
|
1867
|
+
baseColor: shouldInstallBaseStyle ? void 0 : "neutral",
|
|
1868
|
+
components: options.components
|
|
1869
|
+
});
|
|
1870
|
+
}
|
|
1871
|
+
if (!config) throw new Error(`Failed to read config at ${highlighter.info(options.cwd)}.`);
|
|
1872
|
+
const { config: updatedConfig } = await ensureRegistriesInConfig(options.components, config, { silent: options.silent || hasNewRegistries });
|
|
1873
|
+
config = updatedConfig;
|
|
1874
|
+
await addComponents(options.components, config, {
|
|
1875
|
+
...options,
|
|
1876
|
+
baseStyle: shouldInstallBaseStyle
|
|
1877
|
+
});
|
|
1878
|
+
} catch (error) {
|
|
1879
|
+
logger.break();
|
|
1880
|
+
handleError(error);
|
|
1881
|
+
} finally {
|
|
1882
|
+
clearRegistryContext();
|
|
1883
|
+
}
|
|
1884
|
+
});
|
|
1885
|
+
async function promptForRegistryComponents(options) {
|
|
1886
|
+
const registryIndex = await getShadcnRegistryIndex();
|
|
1887
|
+
if (!registryIndex) {
|
|
1888
|
+
logger.break();
|
|
1889
|
+
handleError(/* @__PURE__ */ new Error("Failed to fetch registry index."));
|
|
1890
|
+
return [];
|
|
1891
|
+
}
|
|
1892
|
+
if (options.all) return registryIndex.map((entry) => entry.name).filter((component) => !DEPRECATED_COMPONENTS.some((c) => c.name === component));
|
|
1893
|
+
if (options.components?.length) return options.components;
|
|
1894
|
+
const { components } = await prompts({
|
|
1895
|
+
type: "multiselect",
|
|
1896
|
+
name: "components",
|
|
1897
|
+
message: "Which components would you like to add?",
|
|
1898
|
+
hint: "Space to select. A to toggle all. Enter to submit.",
|
|
1899
|
+
instructions: false,
|
|
1900
|
+
choices: registryIndex.filter((entry) => entry.type === "registry:ui" && !DEPRECATED_COMPONENTS.some((component) => component.name === entry.name)).map((entry) => ({
|
|
1901
|
+
title: entry.name,
|
|
1902
|
+
value: entry.name,
|
|
1903
|
+
selected: options.all ? true : options.components?.includes(entry.name)
|
|
1904
|
+
}))
|
|
1905
|
+
});
|
|
1906
|
+
if (!components?.length) {
|
|
1907
|
+
logger.warn("No components selected. Exiting.");
|
|
1908
|
+
logger.info("");
|
|
1909
|
+
process.exit(1);
|
|
1910
|
+
}
|
|
1911
|
+
const result = z.array(z.string()).safeParse(components);
|
|
1912
|
+
if (!result.success) {
|
|
1913
|
+
logger.error("");
|
|
1914
|
+
handleError(/* @__PURE__ */ new Error("Something went wrong. Please try again."));
|
|
1915
|
+
return [];
|
|
1916
|
+
}
|
|
1917
|
+
return result.data;
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
//#endregion
|
|
1921
|
+
//#region src/preflights/preflight-build.ts
|
|
1922
|
+
async function preFlightBuild(options) {
|
|
1923
|
+
const errors = {};
|
|
1924
|
+
const resolvePaths = {
|
|
1925
|
+
cwd: options.cwd,
|
|
1926
|
+
registryFile: path.resolve(options.cwd, options.registryFile),
|
|
1927
|
+
outputDir: path.resolve(options.cwd, options.outputDir)
|
|
1928
|
+
};
|
|
1929
|
+
if (!fsExtra.existsSync(resolvePaths.registryFile)) errors[BUILD_MISSING_REGISTRY_FILE] = true;
|
|
1930
|
+
await fsExtra.mkdir(resolvePaths.outputDir, { recursive: true });
|
|
1931
|
+
if (Object.keys(errors).length > 0) {
|
|
1932
|
+
if (errors[BUILD_MISSING_REGISTRY_FILE]) {
|
|
1933
|
+
logger.break();
|
|
1934
|
+
logger.error(`The path ${highlighter.info(resolvePaths.registryFile)} does not exist.`);
|
|
1935
|
+
}
|
|
1936
|
+
logger.break();
|
|
1937
|
+
process.exit(1);
|
|
1938
|
+
}
|
|
1939
|
+
return {
|
|
1940
|
+
errors,
|
|
1941
|
+
resolvePaths
|
|
1942
|
+
};
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
//#endregion
|
|
1946
|
+
//#region src/commands/build.ts
|
|
1947
|
+
const buildOptionsSchema = z.object({
|
|
1948
|
+
cwd: z.string(),
|
|
1949
|
+
registryFile: z.string(),
|
|
1950
|
+
outputDir: z.string()
|
|
1951
|
+
});
|
|
1952
|
+
const build = new Command().name("build").description("Build components for a Frontic UI registry").argument("[registry]", "path to registry.json file", "./registry.json").option("-o, --output <path>", "destination directory for json files", "./public/r").option("-c, --cwd <cwd>", "the working directory. defaults to the current directory.", process.cwd()).action(async (registry, opts) => {
|
|
1953
|
+
try {
|
|
1954
|
+
const { resolvePaths } = await preFlightBuild(buildOptionsSchema.parse({
|
|
1955
|
+
cwd: path$1.resolve(opts.cwd),
|
|
1956
|
+
registryFile: registry,
|
|
1957
|
+
outputDir: opts.output
|
|
1958
|
+
}));
|
|
1959
|
+
const content = await fs.readFile(resolvePaths.registryFile, "utf-8");
|
|
1960
|
+
const result = registrySchema.safeParse(JSON.parse(content));
|
|
1961
|
+
if (!result.success) {
|
|
1962
|
+
logger.error(`Invalid registry file found at ${highlighter.info(resolvePaths.registryFile)}.`);
|
|
1963
|
+
process.exit(1);
|
|
1964
|
+
}
|
|
1965
|
+
const buildSpinner = spinner("Building registry...");
|
|
1966
|
+
for (const registryItem of result.data.items) {
|
|
1967
|
+
buildSpinner.start(`Building ${registryItem.name}...`);
|
|
1968
|
+
registryItem.$schema = "https://shadcn-vue.com/schema/registry-item.json";
|
|
1969
|
+
for (const file of registryItem.files ?? []) file.content = (await fs.readFile(path$1.resolve(resolvePaths.cwd, file.path), "utf-8")).replace(/\r\n/g, "\n");
|
|
1970
|
+
const result$1 = registryItemSchema.safeParse(registryItem);
|
|
1971
|
+
if (!result$1.success) {
|
|
1972
|
+
logger.error(`Invalid registry item found for ${highlighter.info(registryItem.name)}.`);
|
|
1973
|
+
continue;
|
|
1974
|
+
}
|
|
1975
|
+
await fs.writeFile(path$1.resolve(resolvePaths.outputDir, `${result$1.data.name}.json`), JSON.stringify(result$1.data, null, 2));
|
|
1976
|
+
}
|
|
1977
|
+
await fs.copyFile(resolvePaths.registryFile, path$1.resolve(resolvePaths.outputDir, "registry.json"));
|
|
1978
|
+
buildSpinner.succeed("Building registry.");
|
|
1979
|
+
} catch (error) {
|
|
1980
|
+
logger.break();
|
|
1981
|
+
handleError(error);
|
|
1982
|
+
}
|
|
1983
|
+
});
|
|
242
1984
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
);
|
|
345
|
-
console.log();
|
|
346
|
-
process.exit(1);
|
|
347
|
-
}
|
|
348
|
-
console.log();
|
|
349
|
-
}
|
|
350
|
-
const brandForeground = getForegroundColor(brandColor);
|
|
351
|
-
const config = await readConfig();
|
|
352
|
-
const cssPath = config?.tailwind?.css;
|
|
353
|
-
if (cssPath) {
|
|
354
|
-
const cssSpinner = ora("Injecting theme variables...").start();
|
|
355
|
-
try {
|
|
356
|
-
const fullCssPath = join2(process.cwd(), cssPath);
|
|
357
|
-
await injectCssVars(fullCssPath, {
|
|
358
|
-
brand: brandColor,
|
|
359
|
-
brandForeground,
|
|
360
|
-
radius: radiusValue
|
|
361
|
-
});
|
|
362
|
-
cssSpinner.succeed("Theme variables injected");
|
|
363
|
-
} catch {
|
|
364
|
-
cssSpinner.warn(
|
|
365
|
-
"Could not inject theme variables (you may need to add them manually)"
|
|
366
|
-
);
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
const spinner = ora("Configuring Frontic registry...").start();
|
|
370
|
-
try {
|
|
371
|
-
await updateConfig({
|
|
372
|
-
registries: {
|
|
373
|
-
"@frontic": {
|
|
374
|
-
url: FRONTIC_REGISTRY_URL
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
});
|
|
378
|
-
spinner.succeed("Frontic registry configured");
|
|
379
|
-
} catch (error) {
|
|
380
|
-
spinner.fail("Failed to configure registry");
|
|
381
|
-
throw error;
|
|
382
|
-
}
|
|
383
|
-
const themeSpinner = ora("Installing Frontic theme...").start();
|
|
384
|
-
try {
|
|
385
|
-
await spawnShadcn(["add", "@frontic/theme", "--yes"], { silent: true });
|
|
386
|
-
themeSpinner.succeed("Frontic theme installed");
|
|
387
|
-
} catch {
|
|
388
|
-
themeSpinner.warn(
|
|
389
|
-
"Theme installation skipped (may already exist or registry not available)"
|
|
390
|
-
);
|
|
391
|
-
}
|
|
392
|
-
console.log();
|
|
393
|
-
console.log(chalk.green("\u2713 Frontic UI initialized successfully!"));
|
|
394
|
-
console.log();
|
|
395
|
-
console.log(chalk.dim("Your theme settings:"));
|
|
396
|
-
console.log(` --brand: ${chalk.cyan(brandColor)}`);
|
|
397
|
-
console.log(` --brand-foreground: ${chalk.cyan(brandForeground)}`);
|
|
398
|
-
console.log(` --radius: ${chalk.cyan(radiusValue)}`);
|
|
399
|
-
console.log();
|
|
400
|
-
console.log("Next steps:");
|
|
401
|
-
console.log(chalk.dim(" 1. Add components:"));
|
|
402
|
-
console.log(` ${chalk.cyan("npx @frontic/ui add button")}`);
|
|
403
|
-
console.log();
|
|
404
|
-
console.log(chalk.dim(" 2. Set up MCP for AI assistants (optional):"));
|
|
405
|
-
console.log(` ${chalk.cyan("npx @frontic/ui mcp init")}`);
|
|
406
|
-
console.log();
|
|
1985
|
+
//#endregion
|
|
1986
|
+
//#region src/commands/diff.ts
|
|
1987
|
+
const updateOptionsSchema = z.object({
|
|
1988
|
+
component: z.string().optional(),
|
|
1989
|
+
yes: z.boolean(),
|
|
1990
|
+
cwd: z.string(),
|
|
1991
|
+
path: z.string().optional()
|
|
1992
|
+
});
|
|
1993
|
+
const diff = new Command().name("diff").description("check for updates against the registry").argument("[component]", "the component name").option("-y, --yes", "skip confirmation prompt.", false).option("-c, --cwd <cwd>", "the working directory. defaults to the current directory.", process.cwd()).action(async (name, opts) => {
|
|
1994
|
+
try {
|
|
1995
|
+
const options = updateOptionsSchema.parse({
|
|
1996
|
+
component: name,
|
|
1997
|
+
...opts
|
|
1998
|
+
});
|
|
1999
|
+
const cwd = path.resolve(options.cwd);
|
|
2000
|
+
if (!existsSync(cwd)) {
|
|
2001
|
+
logger.error(`The path ${cwd} does not exist. Please try again.`);
|
|
2002
|
+
process.exit(1);
|
|
2003
|
+
}
|
|
2004
|
+
const config = await getConfig(cwd);
|
|
2005
|
+
if (!config) {
|
|
2006
|
+
logger.warn(`Configuration is missing. Please run ${highlighter.success(`init`)} to create a components.json file.`);
|
|
2007
|
+
process.exit(1);
|
|
2008
|
+
}
|
|
2009
|
+
const registryIndex = await getShadcnRegistryIndex();
|
|
2010
|
+
if (!registryIndex) {
|
|
2011
|
+
handleError(/* @__PURE__ */ new Error("Failed to fetch registry index."));
|
|
2012
|
+
process.exit(1);
|
|
2013
|
+
}
|
|
2014
|
+
if (!options.component) {
|
|
2015
|
+
const targetDir = config.resolvedPaths.components;
|
|
2016
|
+
const projectComponents = registryIndex.filter((item) => {
|
|
2017
|
+
for (const file of item.files ?? []) if (existsSync(path.resolve(targetDir, typeof file === "string" ? file : file.path))) return true;
|
|
2018
|
+
return false;
|
|
2019
|
+
});
|
|
2020
|
+
const componentsWithUpdates = [];
|
|
2021
|
+
for (const component$1 of projectComponents) {
|
|
2022
|
+
const changes$1 = await diffComponent(component$1, config);
|
|
2023
|
+
if (changes$1.length) componentsWithUpdates.push({
|
|
2024
|
+
name: component$1.name,
|
|
2025
|
+
changes: changes$1
|
|
2026
|
+
});
|
|
2027
|
+
}
|
|
2028
|
+
if (!componentsWithUpdates.length) {
|
|
2029
|
+
logger.info("No updates found.");
|
|
2030
|
+
process.exit(0);
|
|
2031
|
+
}
|
|
2032
|
+
logger.info("The following components have updates available:");
|
|
2033
|
+
for (const component$1 of componentsWithUpdates) {
|
|
2034
|
+
logger.info(`- ${component$1.name}`);
|
|
2035
|
+
for (const change of component$1.changes) logger.info(` - ${change.filePath}`);
|
|
2036
|
+
}
|
|
2037
|
+
logger.break();
|
|
2038
|
+
logger.info(`Run ${highlighter.success(`diff <component>`)} to see the changes.`);
|
|
2039
|
+
process.exit(0);
|
|
2040
|
+
}
|
|
2041
|
+
const component = registryIndex.find((item) => item.name === options.component);
|
|
2042
|
+
if (!component) {
|
|
2043
|
+
logger.error(`The component ${highlighter.success(options.component)} does not exist.`);
|
|
2044
|
+
process.exit(1);
|
|
2045
|
+
}
|
|
2046
|
+
const changes = await diffComponent(component, config);
|
|
2047
|
+
if (!changes.length) {
|
|
2048
|
+
logger.info(`No updates found for ${options.component}.`);
|
|
2049
|
+
process.exit(0);
|
|
2050
|
+
}
|
|
2051
|
+
for (const change of changes) {
|
|
2052
|
+
logger.info(`- ${change.filePath}`);
|
|
2053
|
+
await printDiff(change.patch);
|
|
2054
|
+
logger.info("");
|
|
2055
|
+
}
|
|
2056
|
+
} catch (error) {
|
|
2057
|
+
handleError(error);
|
|
2058
|
+
}
|
|
2059
|
+
});
|
|
2060
|
+
async function diffComponent(component, config) {
|
|
2061
|
+
const payload = await fetchTree(config.style, [component]);
|
|
2062
|
+
const baseColor = await getRegistryBaseColor(config.tailwind.baseColor);
|
|
2063
|
+
if (!payload) return [];
|
|
2064
|
+
const changes = [];
|
|
2065
|
+
for (const item of payload) {
|
|
2066
|
+
const targetDir = await getItemTargetPath(config, item);
|
|
2067
|
+
if (!targetDir) continue;
|
|
2068
|
+
for (const file of item.files ?? []) {
|
|
2069
|
+
const filePath = path.resolve(targetDir, typeof file === "string" ? file : file.path);
|
|
2070
|
+
if (!existsSync(filePath)) continue;
|
|
2071
|
+
const fileContent = await promises.readFile(filePath, "utf8");
|
|
2072
|
+
if (typeof file === "string" || !file.content) continue;
|
|
2073
|
+
const patch = diffLines(await transform({
|
|
2074
|
+
filename: file.path,
|
|
2075
|
+
raw: file.content,
|
|
2076
|
+
config,
|
|
2077
|
+
baseColor
|
|
2078
|
+
}), fileContent);
|
|
2079
|
+
if (patch.length > 1) changes.push({
|
|
2080
|
+
filePath,
|
|
2081
|
+
patch
|
|
2082
|
+
});
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
return changes;
|
|
407
2086
|
}
|
|
2087
|
+
async function printDiff(diff$1) {
|
|
2088
|
+
diff$1.forEach((part) => {
|
|
2089
|
+
if (part) {
|
|
2090
|
+
if (part.added) return process.stdout.write(highlighter.success(part.value));
|
|
2091
|
+
if (part.removed) return process.stdout.write(highlighter.error(part.value));
|
|
2092
|
+
return process.stdout.write(part.value);
|
|
2093
|
+
}
|
|
2094
|
+
});
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
//#endregion
|
|
2098
|
+
//#region src/commands/info.ts
|
|
2099
|
+
const info = new Command().name("info").description("get information about your project").option("-c, --cwd <cwd>", "the working directory. defaults to the current directory.", process.cwd()).action(async (opts) => {
|
|
2100
|
+
try {
|
|
2101
|
+
logger.info("> project info");
|
|
2102
|
+
consola.log(await getProjectInfo(opts.cwd));
|
|
2103
|
+
logger.break();
|
|
2104
|
+
logger.info("> components.json");
|
|
2105
|
+
consola.log(await getConfig(opts.cwd));
|
|
2106
|
+
} catch (error) {
|
|
2107
|
+
handleError(error);
|
|
2108
|
+
}
|
|
2109
|
+
});
|
|
408
2110
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
2111
|
+
//#endregion
|
|
2112
|
+
//#region src/commands/mcp.ts
|
|
2113
|
+
const FRONTIC_MCP_VERSION = "latest";
|
|
2114
|
+
const CLIENTS = [
|
|
2115
|
+
{
|
|
2116
|
+
name: "claude",
|
|
2117
|
+
label: "Claude Code",
|
|
2118
|
+
configPath: ".mcp.json",
|
|
2119
|
+
config: { mcpServers: { "frontic-ui": {
|
|
2120
|
+
command: "npx",
|
|
2121
|
+
args: [`@frontic/ui@${FRONTIC_MCP_VERSION}`, "mcp"],
|
|
2122
|
+
env: { REGISTRY_URL }
|
|
2123
|
+
} } }
|
|
2124
|
+
},
|
|
2125
|
+
{
|
|
2126
|
+
name: "cursor",
|
|
2127
|
+
label: "Cursor",
|
|
2128
|
+
configPath: ".cursor/mcp.json",
|
|
2129
|
+
config: { mcpServers: { "frontic-ui": {
|
|
2130
|
+
command: "npx",
|
|
2131
|
+
args: [`@frontic/ui@${FRONTIC_MCP_VERSION}`, "mcp"],
|
|
2132
|
+
env: { REGISTRY_URL }
|
|
2133
|
+
} } }
|
|
2134
|
+
},
|
|
2135
|
+
{
|
|
2136
|
+
name: "vscode",
|
|
2137
|
+
label: "VS Code",
|
|
2138
|
+
configPath: ".vscode/mcp.json",
|
|
2139
|
+
config: { servers: { "frontic-ui": {
|
|
2140
|
+
command: "npx",
|
|
2141
|
+
args: [`@frontic/ui@${FRONTIC_MCP_VERSION}`, "mcp"],
|
|
2142
|
+
env: { REGISTRY_URL }
|
|
2143
|
+
} } }
|
|
2144
|
+
},
|
|
2145
|
+
{
|
|
2146
|
+
name: "codex",
|
|
2147
|
+
label: "Codex",
|
|
2148
|
+
configPath: ".codex/config.toml",
|
|
2149
|
+
config: `[mcp_servers.frontic_ui]
|
|
2150
|
+
command = "npx"
|
|
2151
|
+
args = ["@frontic/ui@${FRONTIC_MCP_VERSION}", "mcp"]
|
|
2152
|
+
`
|
|
2153
|
+
},
|
|
2154
|
+
{
|
|
2155
|
+
name: "opencode",
|
|
2156
|
+
label: "Opencode",
|
|
2157
|
+
configPath: "opencode.json",
|
|
2158
|
+
config: {
|
|
2159
|
+
$schema: "https://opencode.ai/config.json",
|
|
2160
|
+
mcp: { "frontic-ui": {
|
|
2161
|
+
type: "local",
|
|
2162
|
+
enabled: true,
|
|
2163
|
+
command: [
|
|
2164
|
+
"npx",
|
|
2165
|
+
`@frontic/ui@${FRONTIC_MCP_VERSION}`,
|
|
2166
|
+
"mcp"
|
|
2167
|
+
]
|
|
2168
|
+
} }
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
];
|
|
2172
|
+
const DEPENDENCIES = [`@frontic/ui@${FRONTIC_MCP_VERSION}`];
|
|
2173
|
+
const mcp = new Command().name("mcp").description("MCP server and configuration commands").option("-c, --cwd <cwd>", "the working directory. defaults to the current directory.", process.cwd()).action(async (options) => {
|
|
2174
|
+
try {
|
|
2175
|
+
await loadEnvFiles(options.cwd);
|
|
2176
|
+
const transport = new StdioServerTransport();
|
|
2177
|
+
await server.connect(transport);
|
|
2178
|
+
} catch (error) {
|
|
2179
|
+
logger.break();
|
|
2180
|
+
handleError(error);
|
|
2181
|
+
}
|
|
2182
|
+
});
|
|
2183
|
+
const mcpInitOptionsSchema = z$1.object({
|
|
2184
|
+
client: z$1.enum([
|
|
2185
|
+
"claude",
|
|
2186
|
+
"cursor",
|
|
2187
|
+
"vscode",
|
|
2188
|
+
"codex",
|
|
2189
|
+
"opencode"
|
|
2190
|
+
]),
|
|
2191
|
+
cwd: z$1.string()
|
|
2192
|
+
});
|
|
2193
|
+
mcp.command("init").description("Initialize MCP configuration for your client").option("--client <client>", `MCP client (${CLIENTS.map((c) => c.name).join(", ")})`).action(async (opts, command) => {
|
|
2194
|
+
try {
|
|
2195
|
+
const cwd = (command.parent?.opts() || {}).cwd || process.cwd();
|
|
2196
|
+
let client = opts.client;
|
|
2197
|
+
if (!client) {
|
|
2198
|
+
const response = await prompts({
|
|
2199
|
+
type: "select",
|
|
2200
|
+
name: "client",
|
|
2201
|
+
message: "Which MCP client are you using?",
|
|
2202
|
+
choices: CLIENTS.map((c) => ({
|
|
2203
|
+
title: c.label,
|
|
2204
|
+
value: c.name
|
|
2205
|
+
}))
|
|
2206
|
+
});
|
|
2207
|
+
if (!response.client) {
|
|
2208
|
+
logger.break();
|
|
2209
|
+
process.exit(1);
|
|
2210
|
+
}
|
|
2211
|
+
client = response.client;
|
|
2212
|
+
}
|
|
2213
|
+
const options = mcpInitOptionsSchema.parse({
|
|
2214
|
+
client,
|
|
2215
|
+
cwd
|
|
2216
|
+
});
|
|
2217
|
+
const config = await getConfig(options.cwd);
|
|
2218
|
+
if (options.client === "codex") {
|
|
2219
|
+
if (config) await updateDependencies([], DEPENDENCIES, config, { silent: false });
|
|
2220
|
+
else {
|
|
2221
|
+
const packageManager = await detectPackageManager(options.cwd);
|
|
2222
|
+
const installCommand = packageManager?.name === "npm" ? "install" : "add";
|
|
2223
|
+
const devFlag = packageManager?.name === "npm" ? "--save-dev" : "-D";
|
|
2224
|
+
const installSpinner = spinner("Installing dependencies...").start();
|
|
2225
|
+
await x(packageManager?.name || "npm", [
|
|
2226
|
+
installCommand,
|
|
2227
|
+
devFlag,
|
|
2228
|
+
...DEPENDENCIES
|
|
2229
|
+
], { nodeOptions: { cwd: options.cwd } });
|
|
2230
|
+
installSpinner.succeed("Installing dependencies.");
|
|
2231
|
+
}
|
|
2232
|
+
logger.break();
|
|
2233
|
+
logger.log("To configure the Frontic UI MCP server in Codex:");
|
|
2234
|
+
logger.break();
|
|
2235
|
+
logger.log(`1. Open or create the file ${highlighter.info("~/.codex/config.toml")}`);
|
|
2236
|
+
logger.log("2. Add the following configuration:");
|
|
2237
|
+
logger.log();
|
|
2238
|
+
logger.info(`[mcp_servers.frontic_ui]
|
|
2239
|
+
command = "npx"
|
|
2240
|
+
args = ["@frontic/ui@${FRONTIC_MCP_VERSION}", "mcp"]`);
|
|
2241
|
+
logger.break();
|
|
2242
|
+
logger.info("3. Restart Codex to load the MCP server");
|
|
2243
|
+
logger.break();
|
|
2244
|
+
process.exit(0);
|
|
2245
|
+
}
|
|
2246
|
+
const configSpinner = spinner("Configuring MCP server...").start();
|
|
2247
|
+
const configPath = await runMcpInit(options);
|
|
2248
|
+
configSpinner.succeed("Configuring MCP server.");
|
|
2249
|
+
if (config) await updateDependencies([], DEPENDENCIES, config, { silent: false });
|
|
2250
|
+
else {
|
|
2251
|
+
const packageManager = await detectPackageManager(options.cwd);
|
|
2252
|
+
const installCommand = packageManager?.name === "npm" ? "install" : "add";
|
|
2253
|
+
const devFlag = packageManager?.name === "npm" ? "--save-dev" : "-D";
|
|
2254
|
+
const installSpinner = spinner("Installing dependencies...").start();
|
|
2255
|
+
await x(packageManager?.name || "npm", [
|
|
2256
|
+
installCommand,
|
|
2257
|
+
devFlag,
|
|
2258
|
+
...DEPENDENCIES
|
|
2259
|
+
], { nodeOptions: { cwd: options.cwd } });
|
|
2260
|
+
installSpinner.succeed("Installing dependencies.");
|
|
2261
|
+
}
|
|
2262
|
+
logger.break();
|
|
2263
|
+
logger.success(`Configuration saved to ${configPath}.`);
|
|
2264
|
+
logger.break();
|
|
2265
|
+
} catch (error) {
|
|
2266
|
+
handleError(error);
|
|
2267
|
+
}
|
|
2268
|
+
});
|
|
2269
|
+
const overwriteMerge = (_destArray, sourceArray) => sourceArray;
|
|
2270
|
+
async function runMcpInit(options) {
|
|
2271
|
+
const { client, cwd } = options;
|
|
2272
|
+
const clientInfo = CLIENTS.find((c) => c.name === client);
|
|
2273
|
+
if (!clientInfo) throw new Error(`Unknown client: ${client}. Available clients: ${CLIENTS.map((c) => c.name).join(", ")}`);
|
|
2274
|
+
const configPath = path.join(cwd, clientInfo.configPath);
|
|
2275
|
+
const dir = path.dirname(configPath);
|
|
2276
|
+
await fsExtra.ensureDir(dir);
|
|
2277
|
+
let existingConfig = {};
|
|
2278
|
+
try {
|
|
2279
|
+
const content = await promises.readFile(configPath, "utf-8");
|
|
2280
|
+
existingConfig = JSON.parse(content);
|
|
2281
|
+
} catch {}
|
|
2282
|
+
const mergedConfig = deepmerge(existingConfig, clientInfo.config, { arrayMerge: overwriteMerge });
|
|
2283
|
+
await promises.writeFile(configPath, `${JSON.stringify(mergedConfig, null, 2)}\n`, "utf-8");
|
|
2284
|
+
return clientInfo.configPath;
|
|
2285
|
+
}
|
|
2286
|
+
|
|
2287
|
+
//#endregion
|
|
2288
|
+
//#region src/migrations/migrate-icons.ts
|
|
2289
|
+
async function migrateIcons(config) {
|
|
2290
|
+
if (!config.resolvedPaths.ui) throw new Error("We could not find a valid `ui` path in your `components.json` file. Please ensure you have a valid `ui` path in your `components.json` file.");
|
|
2291
|
+
const uiPath = config.resolvedPaths.ui;
|
|
2292
|
+
const [files, registryIcons] = await Promise.all([glob("**/*.{js,ts,jsx,tsx}", { cwd: uiPath }), getRegistryIcons()]);
|
|
2293
|
+
if (Object.keys(registryIcons).length === 0) throw new Error("Something went wrong fetching the registry icons.");
|
|
2294
|
+
const libraryChoices = Object.entries(ICON_LIBRARIES).map(([name, iconLibrary]) => ({
|
|
2295
|
+
title: iconLibrary.name,
|
|
2296
|
+
value: name
|
|
2297
|
+
}));
|
|
2298
|
+
const migrateOptions = await prompts([{
|
|
2299
|
+
type: "select",
|
|
2300
|
+
name: "sourceLibrary",
|
|
2301
|
+
message: `Which icon library would you like to ${highlighter.info("migrate from")}?`,
|
|
2302
|
+
choices: libraryChoices
|
|
2303
|
+
}, {
|
|
2304
|
+
type: "select",
|
|
2305
|
+
name: "targetLibrary",
|
|
2306
|
+
message: `Which icon library would you like to ${highlighter.info("migrate to")}?`,
|
|
2307
|
+
choices: libraryChoices
|
|
2308
|
+
}]);
|
|
2309
|
+
if (migrateOptions.sourceLibrary === migrateOptions.targetLibrary) throw new Error("You cannot migrate to the same icon library. Please choose a different icon library.");
|
|
2310
|
+
if (!(migrateOptions.sourceLibrary in ICON_LIBRARIES && migrateOptions.targetLibrary in ICON_LIBRARIES)) throw new Error("Invalid icon library. Please choose a valid icon library.");
|
|
2311
|
+
const sourceLibrary = ICON_LIBRARIES[migrateOptions.sourceLibrary];
|
|
2312
|
+
const targetLibrary = ICON_LIBRARIES[migrateOptions.targetLibrary];
|
|
2313
|
+
const { confirm } = await prompts({
|
|
2314
|
+
type: "confirm",
|
|
2315
|
+
name: "confirm",
|
|
2316
|
+
initial: true,
|
|
2317
|
+
message: `We will migrate ${highlighter.info(files.length)} files in ${highlighter.info(`./${path.relative(config.resolvedPaths.cwd, uiPath)}`)} from ${highlighter.info(sourceLibrary.name)} to ${highlighter.info(targetLibrary.name)}. Continue?`
|
|
2318
|
+
});
|
|
2319
|
+
if (!confirm) {
|
|
2320
|
+
logger.info("Migration cancelled.");
|
|
2321
|
+
process.exit(0);
|
|
2322
|
+
}
|
|
2323
|
+
if (targetLibrary.package) await updateDependencies([targetLibrary.package], [], config, { silent: false });
|
|
2324
|
+
const migrationSpinner = spinner(`Migrating icons...`)?.start();
|
|
2325
|
+
await Promise.all(files.map(async (file) => {
|
|
2326
|
+
migrationSpinner.text = `Migrating ${file}...`;
|
|
2327
|
+
const filePath = path.join(uiPath, file);
|
|
2328
|
+
const content = await migrateIconsFile(await promises.readFile(filePath, "utf-8"), migrateOptions.sourceLibrary, migrateOptions.targetLibrary, registryIcons);
|
|
2329
|
+
await promises.writeFile(filePath, content);
|
|
2330
|
+
}));
|
|
2331
|
+
migrationSpinner.succeed("Migration complete.");
|
|
2332
|
+
}
|
|
2333
|
+
async function migrateIconsFile(content, sourceLibrary, targetLibrary, iconsMapping) {
|
|
2334
|
+
const sourceLibraryImport = ICON_LIBRARIES[sourceLibrary]?.import;
|
|
2335
|
+
const targetLibraryImport = ICON_LIBRARIES[targetLibrary]?.import;
|
|
2336
|
+
const dir = await promises.mkdtemp(path.join(tmpdir(), "shadcn-"));
|
|
2337
|
+
const project = new Project({ compilerOptions: {} });
|
|
2338
|
+
const tempFile = path.join(dir, `shadcn-icons-${randomBytes(4).toString("hex")}.tsx`);
|
|
2339
|
+
const sourceFile = project.createSourceFile(tempFile, content, { scriptKind: ScriptKind.TSX });
|
|
2340
|
+
const targetedIcons = [];
|
|
2341
|
+
for (const importDeclaration of sourceFile.getImportDeclarations() ?? []) {
|
|
2342
|
+
if (importDeclaration.getModuleSpecifier()?.getText() !== `"${sourceLibraryImport}"`) continue;
|
|
2343
|
+
for (const specifier of importDeclaration.getNamedImports() ?? []) {
|
|
2344
|
+
const iconName = specifier.getName();
|
|
2345
|
+
const targetedIcon = Object.values(iconsMapping).find((icon) => icon[sourceLibrary] === iconName)?.[targetLibrary];
|
|
2346
|
+
if (!targetedIcon || targetedIcons.includes(targetedIcon)) continue;
|
|
2347
|
+
targetedIcons.push(targetedIcon);
|
|
2348
|
+
specifier.remove();
|
|
2349
|
+
sourceFile.getDescendantsOfKind(SyntaxKind.JsxSelfClosingElement).filter((node) => node.getTagNameNode()?.getText() === iconName).forEach((node) => node.getTagNameNode()?.replaceWithText(targetedIcon));
|
|
2350
|
+
}
|
|
2351
|
+
if (importDeclaration.getNamedImports()?.length === 0) importDeclaration.remove();
|
|
2352
|
+
}
|
|
2353
|
+
if (targetedIcons.length > 0) sourceFile.addImportDeclaration({
|
|
2354
|
+
moduleSpecifier: targetLibraryImport,
|
|
2355
|
+
namedImports: targetedIcons.map((icon) => ({ name: icon }))
|
|
2356
|
+
});
|
|
2357
|
+
return await sourceFile.getText();
|
|
473
2358
|
}
|
|
474
2359
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
return ".mcp.json";
|
|
556
|
-
case "cursor":
|
|
557
|
-
return ".cursor/mcp.json";
|
|
558
|
-
case "vscode":
|
|
559
|
-
return ".vscode/mcp.json";
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
function getMcpConfig() {
|
|
563
|
-
return {
|
|
564
|
-
mcpServers: {
|
|
565
|
-
"frontic-ui": {
|
|
566
|
-
command: "npx",
|
|
567
|
-
args: ["shadcn-vue@latest", "mcp"],
|
|
568
|
-
env: {
|
|
569
|
-
SHADCN_REGISTRY_URL: FRONTIC_REGISTRY_URL
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
};
|
|
2360
|
+
//#endregion
|
|
2361
|
+
//#region src/preflights/preflight-migrate.ts
|
|
2362
|
+
async function preFlightMigrate(options) {
|
|
2363
|
+
const errors = {};
|
|
2364
|
+
if (!fsExtra.existsSync(options.cwd) || !fsExtra.existsSync(path.resolve(options.cwd, "package.json"))) {
|
|
2365
|
+
errors[MISSING_DIR_OR_EMPTY_PROJECT] = true;
|
|
2366
|
+
return {
|
|
2367
|
+
errors,
|
|
2368
|
+
config: null
|
|
2369
|
+
};
|
|
2370
|
+
}
|
|
2371
|
+
if (!fsExtra.existsSync(path.resolve(options.cwd, "components.json"))) {
|
|
2372
|
+
errors[MISSING_CONFIG] = true;
|
|
2373
|
+
return {
|
|
2374
|
+
errors,
|
|
2375
|
+
config: null
|
|
2376
|
+
};
|
|
2377
|
+
}
|
|
2378
|
+
try {
|
|
2379
|
+
return {
|
|
2380
|
+
errors,
|
|
2381
|
+
config: await getConfig(options.cwd)
|
|
2382
|
+
};
|
|
2383
|
+
} catch (_error) {
|
|
2384
|
+
logger.break();
|
|
2385
|
+
logger.error(`An invalid ${highlighter.info("components.json")} file was found at ${highlighter.info(options.cwd)}.\nBefore you can run a migration, you must create a valid ${highlighter.info("components.json")} file by running the ${highlighter.info("init")} command.`);
|
|
2386
|
+
logger.error(`Learn more at ${highlighter.info("https://shadcn-vue.com/docs/components-json")}.`);
|
|
2387
|
+
logger.break();
|
|
2388
|
+
process.exit(1);
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
//#endregion
|
|
2393
|
+
//#region src/commands/migrate.ts
|
|
2394
|
+
const migrations = [{
|
|
2395
|
+
name: "icons",
|
|
2396
|
+
description: "migrate your ui components to a different icon library."
|
|
2397
|
+
}];
|
|
2398
|
+
const migrateOptionsSchema = z.object({
|
|
2399
|
+
cwd: z.string(),
|
|
2400
|
+
list: z.boolean(),
|
|
2401
|
+
yes: z.boolean(),
|
|
2402
|
+
migration: z.string().refine((value) => value && migrations.some((migration) => migration.name === value), { message: "You must specify a valid migration. Run `shadcn migrate --list` to see available migrations." }).optional()
|
|
2403
|
+
});
|
|
2404
|
+
const migrate = new Command().name("migrate").description("run a migration.").argument("[migration]", "the migration to run.").option("-c, --cwd <cwd>", "the working directory. defaults to the current directory.", process.cwd()).option("-l, --list", "list all migrations.", false).option("-y, --yes", "skip confirmation prompt.", false).action(async (migration, opts) => {
|
|
2405
|
+
try {
|
|
2406
|
+
const options = migrateOptionsSchema.parse({
|
|
2407
|
+
cwd: path.resolve(opts.cwd),
|
|
2408
|
+
migration,
|
|
2409
|
+
list: opts.list,
|
|
2410
|
+
yes: opts.yes
|
|
2411
|
+
});
|
|
2412
|
+
if (options.list || !options.migration) {
|
|
2413
|
+
logger.info("Available migrations:");
|
|
2414
|
+
for (const migration$1 of migrations) logger.info(`- ${migration$1.name}: ${migration$1.description}`);
|
|
2415
|
+
return;
|
|
2416
|
+
}
|
|
2417
|
+
if (!options.migration) throw new Error("You must specify a migration. Run `shadcn migrate --list` to see available migrations.");
|
|
2418
|
+
const { errors, config } = await preFlightMigrate(options);
|
|
2419
|
+
if (errors[MISSING_DIR_OR_EMPTY_PROJECT] || errors[MISSING_CONFIG]) throw new Error("No `components.json` file found. Ensure you are at the root of your project.");
|
|
2420
|
+
if (!config) throw new Error("Something went wrong reading your `components.json` file. Please ensure you have a valid `components.json` file.");
|
|
2421
|
+
if (options.migration === "icons") await migrateIcons(config);
|
|
2422
|
+
} catch (error) {
|
|
2423
|
+
logger.break();
|
|
2424
|
+
handleError(error);
|
|
2425
|
+
}
|
|
2426
|
+
});
|
|
2427
|
+
|
|
2428
|
+
//#endregion
|
|
2429
|
+
//#region package.json
|
|
2430
|
+
var version = "0.5.0";
|
|
2431
|
+
|
|
2432
|
+
//#endregion
|
|
2433
|
+
//#region src/index.ts
|
|
2434
|
+
process.on("SIGINT", () => process.exit(0));
|
|
2435
|
+
process.on("SIGTERM", () => process.exit(0));
|
|
2436
|
+
async function main() {
|
|
2437
|
+
const program = new Command().name("frontic-ui").description("Add Frontic UI components to your Vue/Nuxt project").version(version || "1.0.0", "-v, --version", "display the version number");
|
|
2438
|
+
program.addCommand(init).addCommand(add).addCommand(diff).addCommand(migrate).addCommand(info).addCommand(build).addCommand(mcp);
|
|
2439
|
+
program.parse();
|
|
574
2440
|
}
|
|
2441
|
+
main();
|
|
575
2442
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
program.name("frontic-ui").description("CLI for adding Frontic UI components to your Nuxt project").version("0.3.1");
|
|
579
|
-
registerInitCommand(program);
|
|
580
|
-
registerAddCommand(program);
|
|
581
|
-
registerMcpCommand(program);
|
|
582
|
-
function run() {
|
|
583
|
-
program.parse();
|
|
584
|
-
}
|
|
585
|
-
export {
|
|
586
|
-
program,
|
|
587
|
-
run
|
|
588
|
-
};
|
|
2443
|
+
//#endregion
|
|
2444
|
+
export { fetchTree, getItemTargetPath, getRegistriesConfig, getRegistriesIndex, getRegistry, getRegistryBaseColor, getRegistryBaseColors, getRegistryIcons, getRegistryItems, getRegistryStyles, getShadcnRegistryIndex, resolveRegistryItems, resolveTree };
|
|
589
2445
|
//# sourceMappingURL=index.js.map
|