stera-ui 0.4.0 → 0.6.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.
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ CHECK,
4
+ CROSS,
5
+ brand
6
+ } from "./chunk-KGMF6UW4.js";
7
+
8
+ // src/schema/index.ts
9
+ import { z } from "zod";
10
+ var registryItemTypeSchema = z.enum([
11
+ "registry:ui",
12
+ "registry:component",
13
+ "registry:hook",
14
+ "registry:lib",
15
+ "registry:style",
16
+ "registry:font"
17
+ ]);
18
+ var registryItemFileSchema = z.object({
19
+ path: z.string(),
20
+ type: registryItemTypeSchema,
21
+ content: z.string().optional(),
22
+ target: z.string().optional()
23
+ });
24
+ var registryItemFontSchema = z.object({
25
+ family: z.string(),
26
+ provider: z.literal("google"),
27
+ import: z.string(),
28
+ variable: z.string(),
29
+ weight: z.array(z.string()).optional(),
30
+ subsets: z.array(z.string()).optional(),
31
+ selector: z.string().optional(),
32
+ dependency: z.string().optional()
33
+ });
34
+ var registryItemCommonSchema = z.object({
35
+ name: z.string(),
36
+ dependencies: z.array(z.string()).optional(),
37
+ registryDependencies: z.array(z.string()).optional(),
38
+ files: z.array(registryItemFileSchema).optional()
39
+ });
40
+ var registryItemSchema = z.discriminatedUnion("type", [
41
+ registryItemCommonSchema.extend({
42
+ type: z.literal("registry:font"),
43
+ font: registryItemFontSchema,
44
+ files: z.array(registryItemFileSchema).optional()
45
+ }),
46
+ registryItemCommonSchema.extend({
47
+ type: registryItemTypeSchema.exclude(["registry:font"]),
48
+ files: z.array(registryItemFileSchema)
49
+ })
50
+ ]);
51
+ var registryIndexEntrySchema = z.object({
52
+ name: z.string(),
53
+ type: z.string(),
54
+ dependencies: z.array(z.string()).optional(),
55
+ registryDependencies: z.array(z.string()).optional()
56
+ });
57
+ var registryIndexSchema = z.object({
58
+ name: z.string(),
59
+ items: z.array(registryIndexEntrySchema)
60
+ });
61
+ var configSchema = z.object({
62
+ $schema: z.string().optional(),
63
+ version: z.literal(1),
64
+ css: z.string(),
65
+ aliases: z.object({
66
+ components: z.string(),
67
+ utils: z.string(),
68
+ ui: z.string(),
69
+ lib: z.string(),
70
+ hooks: z.string()
71
+ }),
72
+ fonts: z.object({
73
+ strategy: z.enum(["stera-default", "keep-existing", "skip"]),
74
+ nextFont: z.boolean().optional()
75
+ }).optional()
76
+ });
77
+
78
+ // src/registry/index.ts
79
+ var REGISTRY_URL = process.env.REGISTRY_URL ?? "https://ui.stera.sh/r";
80
+ var FETCH_TIMEOUT_MS = 15e3;
81
+ var ComponentNotFoundError = class extends Error {
82
+ components;
83
+ constructor(components) {
84
+ super(
85
+ components.length === 1 ? `Component "${components[0]}" not found in registry.` : `Components not found in registry: ${components.map((c) => `"${c}"`).join(", ")}.`
86
+ );
87
+ this.name = "ComponentNotFoundError";
88
+ this.components = components;
89
+ }
90
+ };
91
+ var _indexCache = null;
92
+ async function fetchJSON(url) {
93
+ let res;
94
+ try {
95
+ res = await fetch(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
96
+ } catch (err) {
97
+ if (err instanceof Error && err.name === "TimeoutError") {
98
+ throw new Error(
99
+ `Timed out fetching ${url} after ${FETCH_TIMEOUT_MS / 1e3}s. Check your network connection.`
100
+ );
101
+ }
102
+ throw new Error(
103
+ `Failed to fetch ${url}: ${err instanceof Error ? err.message : String(err)}`
104
+ );
105
+ }
106
+ if (!res.ok) {
107
+ throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`);
108
+ }
109
+ return res.json();
110
+ }
111
+ async function fetchRegistryIndex() {
112
+ if (_indexCache) return _indexCache;
113
+ const data = await fetchJSON(`${REGISTRY_URL}/index.json`);
114
+ const parsed = registryIndexSchema.safeParse(data);
115
+ if (!parsed.success) {
116
+ throw new Error(
117
+ `Registry index at ${REGISTRY_URL}/index.json returned unexpected data.`
118
+ );
119
+ }
120
+ _indexCache = parsed.data.items;
121
+ return _indexCache;
122
+ }
123
+ async function fetchRegistryItem(name) {
124
+ const data = await fetchJSON(`${REGISTRY_URL}/${name}.json`);
125
+ const parsed = registryItemSchema.safeParse(data);
126
+ if (!parsed.success) {
127
+ throw new Error(
128
+ `Registry item "${name}" returned unexpected data from ${REGISTRY_URL}/${name}.json.`
129
+ );
130
+ }
131
+ return parsed.data;
132
+ }
133
+ async function getAllComponents() {
134
+ return fetchRegistryIndex();
135
+ }
136
+ async function resolveDependencies(names) {
137
+ const index = await fetchRegistryIndex();
138
+ const byName = new Map(index.map((entry) => [entry.name, entry]));
139
+ const needed = [];
140
+ const seen = /* @__PURE__ */ new Set();
141
+ const missing = [];
142
+ const queue = [...names];
143
+ while (queue.length > 0) {
144
+ const name = queue.shift();
145
+ if (seen.has(name)) continue;
146
+ seen.add(name);
147
+ const entry = byName.get(name);
148
+ if (!entry) {
149
+ missing.push(name);
150
+ continue;
151
+ }
152
+ needed.push(name);
153
+ queue.push(...entry.registryDependencies ?? []);
154
+ }
155
+ if (missing.length > 0) {
156
+ throw new ComponentNotFoundError(missing);
157
+ }
158
+ return Promise.all(needed.map((name) => fetchRegistryItem(name)));
159
+ }
160
+
161
+ // src/utils/spinner.ts
162
+ var FRAMES = ["\u2022", "\u2726", "\u2736", "\u273B"].map(brand);
163
+ var INTERVAL_MS = 150;
164
+ function createSpinner(text) {
165
+ const isTTY = process.stdout.isTTY;
166
+ if (!isTTY) {
167
+ process.stdout.write(` ${text}...
168
+ `);
169
+ return {
170
+ succeed(msg) {
171
+ process.stdout.write(` ${CHECK} ${msg}
172
+ `);
173
+ },
174
+ fail(msg) {
175
+ process.stdout.write(` ${CROSS} ${msg}
176
+ `);
177
+ }
178
+ };
179
+ }
180
+ let frameIndex = 0;
181
+ const interval = setInterval(() => {
182
+ process.stdout.write(`\r ${FRAMES[frameIndex % FRAMES.length]} ${text}`);
183
+ frameIndex++;
184
+ }, INTERVAL_MS);
185
+ const clear = () => {
186
+ clearInterval(interval);
187
+ process.stdout.write("\r" + " ".repeat(text.length + 6) + "\r");
188
+ };
189
+ return {
190
+ succeed(msg) {
191
+ clear();
192
+ process.stdout.write(` ${CHECK} ${msg}
193
+ `);
194
+ },
195
+ fail(msg) {
196
+ clear();
197
+ process.stdout.write(` ${CROSS} ${msg}
198
+ `);
199
+ }
200
+ };
201
+ }
202
+
203
+ export {
204
+ ComponentNotFoundError,
205
+ fetchRegistryItem,
206
+ getAllComponents,
207
+ resolveDependencies,
208
+ createSpinner
209
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  fetchRegistryItem
3
- } from "./chunk-T3QWOVJR.js";
3
+ } from "./chunk-BJNLGEZJ.js";
4
4
 
5
5
  // src/utils/transform.ts
6
6
  function applyTransforms(content, ctx, transforms) {
@@ -22,19 +22,31 @@ var transformImports = (content, ctx) => {
22
22
  const { aliases } = ctx.config;
23
23
  const rules = [];
24
24
  if (aliases.utils !== DEFAULT_ALIASES.utils) {
25
- rules.push({ type: "exact", from: DEFAULT_ALIASES.utils, to: aliases.utils });
25
+ rules.push({
26
+ type: "exact",
27
+ from: DEFAULT_ALIASES.utils,
28
+ to: aliases.utils
29
+ });
26
30
  }
27
31
  if (aliases.ui !== DEFAULT_ALIASES.ui) {
28
32
  rules.push({ type: "prefix", from: DEFAULT_ALIASES.ui, to: aliases.ui });
29
33
  }
30
34
  if (aliases.components !== DEFAULT_ALIASES.components) {
31
- rules.push({ type: "prefix", from: DEFAULT_ALIASES.components, to: aliases.components });
35
+ rules.push({
36
+ type: "prefix",
37
+ from: DEFAULT_ALIASES.components,
38
+ to: aliases.components
39
+ });
32
40
  }
33
41
  if (aliases.lib !== DEFAULT_ALIASES.lib) {
34
42
  rules.push({ type: "prefix", from: DEFAULT_ALIASES.lib, to: aliases.lib });
35
43
  }
36
44
  if (aliases.hooks !== DEFAULT_ALIASES.hooks) {
37
- rules.push({ type: "prefix", from: DEFAULT_ALIASES.hooks, to: aliases.hooks });
45
+ rules.push({
46
+ type: "prefix",
47
+ from: DEFAULT_ALIASES.hooks,
48
+ to: aliases.hooks
49
+ });
38
50
  }
39
51
  if (rules.length === 0) {
40
52
  return content;
@@ -68,16 +80,30 @@ var TYPE_TO_ALIAS = {
68
80
  "registry:hook": "hooks",
69
81
  "registry:component": "components"
70
82
  };
71
- function resolveAlias(alias, projectRoot) {
83
+ var matcherCache = /* @__PURE__ */ new Map();
84
+ function getTsConfigMatcher(projectRoot) {
85
+ let matcher = matcherCache.get(projectRoot);
86
+ if (matcher !== void 0) return matcher;
72
87
  const tsConfig = loadConfig(projectRoot);
73
- if (tsConfig.resultType === "success" && Object.keys(tsConfig.paths).length > 0) {
74
- const hasMatchingPrefix = Object.keys(tsConfig.paths).some((key) => {
88
+ matcher = tsConfig.resultType === "success" && Object.keys(tsConfig.paths).length > 0 ? {
89
+ paths: tsConfig.paths,
90
+ match: createMatchPath(tsConfig.absoluteBaseUrl, tsConfig.paths)
91
+ } : null;
92
+ matcherCache.set(projectRoot, matcher);
93
+ return matcher;
94
+ }
95
+ function resolveAlias(alias, projectRoot) {
96
+ const matcher = getTsConfigMatcher(projectRoot);
97
+ if (matcher) {
98
+ const hasMatchingPrefix = Object.keys(matcher.paths).some((key) => {
75
99
  const prefix = key.replace(/\/\*$/, "");
76
100
  return alias === prefix || alias.startsWith(`${prefix}/`);
77
101
  });
78
102
  if (hasMatchingPrefix) {
79
- const match = createMatchPath(tsConfig.absoluteBaseUrl, tsConfig.paths);
80
- const resolved = match(alias, void 0, () => true, [".ts", ".tsx"]);
103
+ const resolved = matcher.match(alias, void 0, () => true, [
104
+ ".ts",
105
+ ".tsx"
106
+ ]);
81
107
  if (resolved) {
82
108
  return path.relative(projectRoot, resolved);
83
109
  }
@@ -105,7 +131,8 @@ function resolveOutputPath(config, projectRoot, file) {
105
131
  // src/utils/resolve-config.ts
106
132
  import fs from "fs";
107
133
  import path2 from "path";
108
- var CONFIG_FILE = "components.json";
134
+ var CONFIG_FILE = "stera.config.json";
135
+ var LEGACY_CONFIG_FILE = "components.json";
109
136
  function migrateConfig(raw) {
110
137
  if (!("version" in raw) && "tailwind" in raw) {
111
138
  const legacy = raw;
@@ -117,11 +144,22 @@ function migrateConfig(raw) {
117
144
  }
118
145
  return raw;
119
146
  }
147
+ var warnedLegacyFilename = false;
148
+ function warnLegacyFilenameOnce(legacyPath) {
149
+ if (warnedLegacyFilename) return;
150
+ warnedLegacyFilename = true;
151
+ console.warn(
152
+ ` \u26A0 Found ${LEGACY_CONFIG_FILE}. Rename to ${CONFIG_FILE} \u2014 legacy filename support will be removed in a future release.
153
+ (${legacyPath})`
154
+ );
155
+ }
120
156
  function findConfigPath(cwd) {
121
157
  let dir = path2.resolve(cwd);
122
158
  while (true) {
123
159
  const configPath = path2.join(dir, CONFIG_FILE);
124
160
  if (fs.existsSync(configPath)) return configPath;
161
+ const legacyPath = path2.join(dir, LEGACY_CONFIG_FILE);
162
+ if (fs.existsSync(legacyPath)) return legacyPath;
125
163
  const parent = path2.dirname(dir);
126
164
  if (parent === dir) return null;
127
165
  dir = parent;
@@ -130,6 +168,9 @@ function findConfigPath(cwd) {
130
168
  function loadConfig2(cwd) {
131
169
  const configPath = findConfigPath(cwd);
132
170
  if (!configPath) return null;
171
+ if (path2.basename(configPath) === LEGACY_CONFIG_FILE) {
172
+ warnLegacyFilenameOnce(configPath);
173
+ }
133
174
  try {
134
175
  const raw = JSON.parse(fs.readFileSync(configPath, "utf-8"));
135
176
  return migrateConfig(raw);
@@ -364,7 +405,9 @@ async function installDependencies(deps, cwd) {
364
405
  if (fs5.existsSync(pkgPath)) {
365
406
  try {
366
407
  const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
367
- existing = new Set(Object.keys({ ...pkg.dependencies, ...pkg.devDependencies }));
408
+ existing = new Set(
409
+ Object.keys({ ...pkg.dependencies, ...pkg.devDependencies })
410
+ );
368
411
  } catch {
369
412
  }
370
413
  }
@@ -392,19 +435,18 @@ or use "pnpm add -w <package>" manually to install at the root.`
392
435
  // src/utils/write-files.ts
393
436
  import fs6 from "fs";
394
437
  import path6 from "path";
395
- import readline from "readline/promises";
438
+ import { confirm } from "@inquirer/prompts";
396
439
  async function confirmOverwrite(relativePath) {
397
440
  if (!process.stdin.isTTY) {
398
- console.log(` Skipping ${relativePath} (already exists, use --overwrite to replace)`);
441
+ console.log(
442
+ ` Skipping ${relativePath} (already exists, use --overwrite to replace)`
443
+ );
399
444
  return false;
400
445
  }
401
- const rl = readline.createInterface({
402
- input: process.stdin,
403
- output: process.stdout
446
+ return confirm({
447
+ message: `${relativePath} already exists. Overwrite?`,
448
+ default: false
404
449
  });
405
- const answer = await rl.question(` ${relativePath} already exists. Overwrite? (y/N) `);
406
- rl.close();
407
- return answer.toLowerCase() === "y";
408
450
  }
409
451
  async function writeComponentFiles(items, config, projectRoot, options = {}) {
410
452
  const written = [];
@@ -416,13 +458,13 @@ async function writeComponentFiles(items, config, projectRoot, options = {}) {
416
458
  if (fs6.existsSync(outputPath)) {
417
459
  const existing = fs6.readFileSync(outputPath, "utf-8");
418
460
  if (existing === file.content) {
419
- skipped.push(relativePath);
461
+ skipped.push({ path: relativePath, reason: "unchanged" });
420
462
  continue;
421
463
  }
422
464
  if (!options.overwrite) {
423
465
  const confirmed = await confirmOverwrite(relativePath);
424
466
  if (!confirmed) {
425
- skipped.push(relativePath);
467
+ skipped.push({ path: relativePath, reason: "declined" });
426
468
  continue;
427
469
  }
428
470
  }
@@ -752,6 +794,7 @@ export {
752
794
  transformImports,
753
795
  resolveOutputPath,
754
796
  CONFIG_FILE,
797
+ LEGACY_CONFIG_FILE,
755
798
  findConfigPath,
756
799
  loadConfig2 as loadConfig,
757
800
  detectProject,