stera-ui 0.1.0 → 0.1.2

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.
@@ -0,0 +1,46 @@
1
+ // src/schema/index.ts
2
+ import { z } from "zod";
3
+ var registryItemTypeSchema = z.enum([
4
+ "registry:ui",
5
+ "registry:component",
6
+ "registry:hook",
7
+ "registry:lib",
8
+ "registry:style"
9
+ ]);
10
+ var registryItemFileSchema = z.object({
11
+ path: z.string(),
12
+ type: registryItemTypeSchema,
13
+ content: z.string().optional(),
14
+ target: z.string().optional()
15
+ });
16
+ var registryItemSchema = z.object({
17
+ name: z.string(),
18
+ type: registryItemTypeSchema,
19
+ dependencies: z.array(z.string()).optional(),
20
+ registryDependencies: z.array(z.string()).optional(),
21
+ files: z.array(registryItemFileSchema)
22
+ });
23
+ var registryIndexSchema = z.object({
24
+ name: z.string(),
25
+ items: z.array(registryItemSchema)
26
+ });
27
+ var configSchema = z.object({
28
+ $schema: z.string().optional(),
29
+ version: z.literal(1),
30
+ css: z.string(),
31
+ aliases: z.object({
32
+ components: z.string(),
33
+ utils: z.string(),
34
+ ui: z.string(),
35
+ lib: z.string(),
36
+ hooks: z.string()
37
+ })
38
+ });
39
+
40
+ export {
41
+ registryItemTypeSchema,
42
+ registryItemFileSchema,
43
+ registryItemSchema,
44
+ registryIndexSchema,
45
+ configSchema
46
+ };
@@ -0,0 +1,362 @@
1
+ // src/utils/transform.ts
2
+ function applyTransforms(content, ctx, transforms) {
3
+ return transforms.reduce((acc, fn) => fn(acc, ctx), content);
4
+ }
5
+
6
+ // src/utils/transform-imports.ts
7
+ var DEFAULT_ALIASES = {
8
+ utils: "@/lib/utils",
9
+ ui: "@/components/ui",
10
+ components: "@/components",
11
+ lib: "@/lib",
12
+ hooks: "@/hooks"
13
+ };
14
+ var transformImports = (content, ctx) => {
15
+ if (!ctx.filename.endsWith(".ts") && !ctx.filename.endsWith(".tsx")) {
16
+ return content;
17
+ }
18
+ const { aliases } = ctx.config;
19
+ const rules = [];
20
+ if (aliases.utils !== DEFAULT_ALIASES.utils) {
21
+ rules.push({ type: "exact", from: DEFAULT_ALIASES.utils, to: aliases.utils });
22
+ }
23
+ if (aliases.ui !== DEFAULT_ALIASES.ui) {
24
+ rules.push({ type: "prefix", from: DEFAULT_ALIASES.ui, to: aliases.ui });
25
+ }
26
+ if (aliases.components !== DEFAULT_ALIASES.components) {
27
+ rules.push({ type: "prefix", from: DEFAULT_ALIASES.components, to: aliases.components });
28
+ }
29
+ if (aliases.lib !== DEFAULT_ALIASES.lib) {
30
+ rules.push({ type: "prefix", from: DEFAULT_ALIASES.lib, to: aliases.lib });
31
+ }
32
+ if (aliases.hooks !== DEFAULT_ALIASES.hooks) {
33
+ rules.push({ type: "prefix", from: DEFAULT_ALIASES.hooks, to: aliases.hooks });
34
+ }
35
+ if (rules.length === 0) {
36
+ return content;
37
+ }
38
+ return content.replace(
39
+ /from\s+(["'])([^"']+)\1/g,
40
+ (match, quote, importPath) => {
41
+ for (const rule of rules) {
42
+ if (rule.type === "exact" && importPath === rule.from) {
43
+ return `from ${quote}${rule.to}${quote}`;
44
+ }
45
+ if (rule.type === "prefix" && importPath.startsWith(rule.from + "/")) {
46
+ const rest = importPath.slice(rule.from.length);
47
+ return `from ${quote}${rule.to}${rest}${quote}`;
48
+ }
49
+ if (rule.type === "prefix" && importPath === rule.from) {
50
+ return `from ${quote}${rule.to}${quote}`;
51
+ }
52
+ }
53
+ return match;
54
+ }
55
+ );
56
+ };
57
+
58
+ // src/utils/resolve-paths.ts
59
+ import path from "path";
60
+ import { loadConfig, createMatchPath } from "tsconfig-paths";
61
+ var TYPE_TO_ALIAS = {
62
+ "registry:ui": "ui",
63
+ "registry:lib": "lib",
64
+ "registry:hook": "hooks",
65
+ "registry:component": "components"
66
+ };
67
+ function resolveAlias(alias, projectRoot) {
68
+ const tsConfig = loadConfig(projectRoot);
69
+ if (tsConfig.resultType === "success") {
70
+ const match = createMatchPath(tsConfig.absoluteBaseUrl, tsConfig.paths);
71
+ const resolved = match(alias, void 0, () => true, [".ts", ".tsx"]);
72
+ if (resolved) {
73
+ return path.relative(projectRoot, resolved);
74
+ }
75
+ }
76
+ return alias.replace(/^@\//, "");
77
+ }
78
+ function resolveOutputPath(config, projectRoot, file) {
79
+ const alias = TYPE_TO_ALIAS[file.type];
80
+ if (alias) {
81
+ const dir = resolveAlias(config.aliases[alias], projectRoot);
82
+ const filename = path.basename(file.path);
83
+ return path.join(projectRoot, dir, filename);
84
+ }
85
+ if (file.type === "registry:style") {
86
+ const cssDir = path.dirname(config.css);
87
+ const filename = path.basename(file.path);
88
+ return path.join(projectRoot, cssDir, filename);
89
+ }
90
+ return path.join(projectRoot, file.path);
91
+ }
92
+
93
+ // src/utils/resolve-config.ts
94
+ import fs from "fs";
95
+ import path2 from "path";
96
+ var CONFIG_FILE = "components.json";
97
+ function migrateConfig(raw) {
98
+ if (!("version" in raw) && "tailwind" in raw) {
99
+ const legacy = raw;
100
+ return {
101
+ version: 1,
102
+ css: legacy.tailwind.css || "src/styles/globals.css",
103
+ aliases: legacy.aliases
104
+ };
105
+ }
106
+ return raw;
107
+ }
108
+ function findConfigPath(cwd) {
109
+ let dir = path2.resolve(cwd);
110
+ while (true) {
111
+ const configPath = path2.join(dir, CONFIG_FILE);
112
+ if (fs.existsSync(configPath)) return configPath;
113
+ const parent = path2.dirname(dir);
114
+ if (parent === dir) return null;
115
+ dir = parent;
116
+ }
117
+ }
118
+ function loadConfig2(cwd) {
119
+ const configPath = findConfigPath(cwd);
120
+ if (!configPath) return null;
121
+ try {
122
+ const raw = JSON.parse(fs.readFileSync(configPath, "utf-8"));
123
+ return migrateConfig(raw);
124
+ } catch {
125
+ return null;
126
+ }
127
+ }
128
+
129
+ // src/utils/detect-project.ts
130
+ import fs2 from "fs";
131
+ import path3 from "path";
132
+ function hasFile(cwd, filenames) {
133
+ return filenames.some((f) => fs2.existsSync(path3.join(cwd, f)));
134
+ }
135
+ function findTailwindCssFile(cwd) {
136
+ const candidates = [
137
+ "src/app/globals.css",
138
+ "src/styles/globals.css",
139
+ "src/index.css",
140
+ "app/globals.css",
141
+ "styles/globals.css",
142
+ "index.css"
143
+ ];
144
+ for (const candidate of candidates) {
145
+ const fullPath = path3.join(cwd, candidate);
146
+ if (!fs2.existsSync(fullPath)) continue;
147
+ const content = fs2.readFileSync(fullPath, "utf-8");
148
+ if (content.includes('@import "tailwindcss"') || content.includes("@import 'tailwindcss'") || content.includes("@tailwind base")) {
149
+ return candidate;
150
+ }
151
+ }
152
+ return null;
153
+ }
154
+ function detectProject(cwd) {
155
+ const hasSrc = fs2.existsSync(path3.join(cwd, "src"));
156
+ let framework = "other";
157
+ if (hasFile(cwd, ["next.config.ts", "next.config.js", "next.config.mjs"])) {
158
+ framework = "next";
159
+ } else if (hasFile(cwd, ["vite.config.ts", "vite.config.js", "vite.config.mjs"])) {
160
+ framework = "vite";
161
+ }
162
+ let rsc = false;
163
+ if (framework === "next") {
164
+ const appDir = hasSrc ? "src/app" : "app";
165
+ rsc = fs2.existsSync(path3.join(cwd, appDir));
166
+ }
167
+ const existingCssFile = findTailwindCssFile(cwd);
168
+ return { hasSrc, framework, rsc, existingCssFile };
169
+ }
170
+
171
+ // src/utils/detect-globals.ts
172
+ import fs3 from "fs";
173
+ import path4 from "path";
174
+ function hasGlobalsCss(config, projectRoot) {
175
+ const cssPath = path4.resolve(projectRoot, config.css);
176
+ if (!fs3.existsSync(cssPath)) {
177
+ return false;
178
+ }
179
+ const steraPath = path4.join(path4.dirname(cssPath), "stera-ui.css");
180
+ return fs3.existsSync(steraPath);
181
+ }
182
+
183
+ // src/utils/install-deps.ts
184
+ import fs4 from "fs";
185
+ import path5 from "path";
186
+ import { spawn } from "child_process";
187
+
188
+ // src/utils/format.ts
189
+ var BRAND = "\x1B[38;2;166;244;48m";
190
+ var DIM = "\x1B[2m";
191
+ var RESET = "\x1B[0m";
192
+ var enabled = process.stdout.isTTY && !process.env.NO_COLOR;
193
+ var wrap = (code, text) => enabled ? `${code}${text}${RESET}` : text;
194
+ var brand = (text) => wrap(BRAND, text);
195
+ var dim = (text) => wrap(DIM, text);
196
+ var LOGO = brand("\u273B");
197
+ var CHECK = brand("\u2713");
198
+ var CROSS = "\u2717";
199
+ var WARN = "!";
200
+
201
+ // src/utils/spinner.ts
202
+ var FRAMES = ["\u2022", "\u2726", "\u2736", "\u273B"].map(brand);
203
+ var INTERVAL_MS = 150;
204
+ function createSpinner(text) {
205
+ const isTTY = process.stdout.isTTY;
206
+ if (!isTTY) {
207
+ process.stdout.write(` ${text}...
208
+ `);
209
+ return {
210
+ succeed(msg) {
211
+ process.stdout.write(` ${CHECK} ${msg}
212
+ `);
213
+ },
214
+ fail(msg) {
215
+ process.stdout.write(` ${CROSS} ${msg}
216
+ `);
217
+ }
218
+ };
219
+ }
220
+ let frameIndex = 0;
221
+ const interval = setInterval(() => {
222
+ process.stdout.write(`\r ${FRAMES[frameIndex % FRAMES.length]} ${text}`);
223
+ frameIndex++;
224
+ }, INTERVAL_MS);
225
+ const clear = () => {
226
+ clearInterval(interval);
227
+ process.stdout.write("\r" + " ".repeat(text.length + 6) + "\r");
228
+ };
229
+ return {
230
+ succeed(msg) {
231
+ clear();
232
+ process.stdout.write(` ${CHECK} ${msg}
233
+ `);
234
+ },
235
+ fail(msg) {
236
+ clear();
237
+ process.stdout.write(` ${CROSS} ${msg}
238
+ `);
239
+ }
240
+ };
241
+ }
242
+
243
+ // src/utils/install-deps.ts
244
+ function detectPackageManager(cwd) {
245
+ if (fs4.existsSync(path5.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
246
+ if (fs4.existsSync(path5.join(cwd, "bun.lockb"))) return "bun";
247
+ if (fs4.existsSync(path5.join(cwd, "yarn.lock"))) return "yarn";
248
+ return "npm";
249
+ }
250
+ function getInstallArgs(pm, deps) {
251
+ switch (pm) {
252
+ case "pnpm":
253
+ return ["pnpm", ["add", ...deps]];
254
+ case "yarn":
255
+ return ["yarn", ["add", ...deps]];
256
+ case "bun":
257
+ return ["bun", ["add", ...deps]];
258
+ default:
259
+ return ["npm", ["install", ...deps]];
260
+ }
261
+ }
262
+ async function installDependencies(deps, cwd) {
263
+ if (deps.length === 0) return;
264
+ const pkgPath = path5.join(cwd, "package.json");
265
+ let existing = /* @__PURE__ */ new Set();
266
+ if (fs4.existsSync(pkgPath)) {
267
+ try {
268
+ const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf-8"));
269
+ existing = new Set(Object.keys({ ...pkg.dependencies, ...pkg.devDependencies }));
270
+ } catch {
271
+ }
272
+ }
273
+ const toInstall = deps.filter((dep) => !existing.has(dep));
274
+ if (toInstall.length === 0) return;
275
+ const pm = detectPackageManager(cwd);
276
+ const [cmd, args] = getInstallArgs(pm, toInstall);
277
+ const spinner = createSpinner(`Installing with ${pm}`);
278
+ await new Promise((resolve, reject) => {
279
+ const proc = spawn(cmd, args, { cwd, stdio: "pipe" });
280
+ proc.on("close", (code) => {
281
+ if (code === 0) {
282
+ spinner.succeed(`Installed with ${pm}`);
283
+ resolve();
284
+ } else {
285
+ spinner.fail(`Install failed (exit ${code})`);
286
+ reject(new Error(`${pm} exited with code ${code}`));
287
+ }
288
+ });
289
+ proc.on("error", (err) => {
290
+ spinner.fail(`Install failed`);
291
+ reject(err);
292
+ });
293
+ });
294
+ }
295
+
296
+ // src/utils/write-files.ts
297
+ import fs5 from "fs";
298
+ import path6 from "path";
299
+ import readline from "readline/promises";
300
+ async function confirmOverwrite(relativePath) {
301
+ if (!process.stdin.isTTY) {
302
+ console.log(` Skipping ${relativePath} (already exists, use --overwrite to replace)`);
303
+ return false;
304
+ }
305
+ const rl = readline.createInterface({
306
+ input: process.stdin,
307
+ output: process.stdout
308
+ });
309
+ const answer = await rl.question(` ${relativePath} already exists. Overwrite? (y/N) `);
310
+ rl.close();
311
+ return answer.toLowerCase() === "y";
312
+ }
313
+ async function writeComponentFiles(items, config, projectRoot, options = {}) {
314
+ const written = [];
315
+ const skipped = [];
316
+ for (const item of items) {
317
+ for (const file of item.files) {
318
+ const outputPath = resolveOutputPath(config, projectRoot, file);
319
+ const relativePath = path6.relative(projectRoot, outputPath);
320
+ if (fs5.existsSync(outputPath)) {
321
+ const existing = fs5.readFileSync(outputPath, "utf-8");
322
+ if (existing === file.content) {
323
+ skipped.push(relativePath);
324
+ continue;
325
+ }
326
+ if (!options.overwrite) {
327
+ const confirmed = await confirmOverwrite(relativePath);
328
+ if (!confirmed) {
329
+ skipped.push(relativePath);
330
+ continue;
331
+ }
332
+ }
333
+ }
334
+ const dir = path6.dirname(outputPath);
335
+ fs5.mkdirSync(dir, { recursive: true });
336
+ fs5.writeFileSync(outputPath, file.content ?? "", "utf-8");
337
+ written.push(relativePath);
338
+ }
339
+ }
340
+ return { written, skipped };
341
+ }
342
+
343
+ export {
344
+ applyTransforms,
345
+ transformImports,
346
+ resolveOutputPath,
347
+ CONFIG_FILE,
348
+ findConfigPath,
349
+ loadConfig2 as loadConfig,
350
+ detectProject,
351
+ hasGlobalsCss,
352
+ brand,
353
+ dim,
354
+ LOGO,
355
+ CHECK,
356
+ CROSS,
357
+ WARN,
358
+ createSpinner,
359
+ detectPackageManager,
360
+ installDependencies,
361
+ writeComponentFiles
362
+ };
@@ -0,0 +1,60 @@
1
+ // src/registry/index.ts
2
+ var REGISTRY_URL = process.env.REGISTRY_URL ?? "https://ui.stera.sh/r";
3
+ var _indexCache = null;
4
+ async function fetchJSON(url) {
5
+ const res = await fetch(url);
6
+ if (!res.ok) {
7
+ throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`);
8
+ }
9
+ return res.json();
10
+ }
11
+ async function fetchRegistryIndex() {
12
+ if (_indexCache) return _indexCache;
13
+ const data = await fetchJSON(
14
+ `${REGISTRY_URL}/index.json`
15
+ );
16
+ _indexCache = data.items;
17
+ return _indexCache;
18
+ }
19
+ async function fetchRegistryItem(name) {
20
+ return fetchJSON(`${REGISTRY_URL}/${name}.json`);
21
+ }
22
+ async function getComponent(name) {
23
+ const index = await fetchRegistryIndex();
24
+ return index.find((item) => item.name === name);
25
+ }
26
+ async function getAllComponents() {
27
+ return fetchRegistryIndex();
28
+ }
29
+ async function resolveDependencies(names) {
30
+ const resolved = /* @__PURE__ */ new Map();
31
+ const queue = [...names];
32
+ while (queue.length > 0) {
33
+ const name = queue.shift();
34
+ if (resolved.has(name)) continue;
35
+ let item;
36
+ try {
37
+ item = await fetchRegistryItem(name);
38
+ } catch {
39
+ console.error(`Component "${name}" not found in registry.`);
40
+ process.exit(1);
41
+ }
42
+ resolved.set(name, item);
43
+ if (item.registryDependencies) {
44
+ for (const dep of item.registryDependencies) {
45
+ if (!resolved.has(dep)) {
46
+ queue.push(dep);
47
+ }
48
+ }
49
+ }
50
+ }
51
+ return Array.from(resolved.values());
52
+ }
53
+
54
+ export {
55
+ fetchRegistryIndex,
56
+ fetchRegistryItem,
57
+ getComponent,
58
+ getAllComponents,
59
+ resolveDependencies
60
+ };