stera-ui 0.3.3 → 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.
@@ -1,3 +1,7 @@
1
+ import {
2
+ fetchRegistryItem
3
+ } from "./chunk-T3QWOVJR.js";
4
+
1
5
  // src/utils/transform.ts
2
6
  function applyTransforms(content, ctx, transforms) {
3
7
  return transforms.reduce((acc, fn) => fn(acc, ctx), content);
@@ -101,7 +105,8 @@ function resolveOutputPath(config, projectRoot, file) {
101
105
  // src/utils/resolve-config.ts
102
106
  import fs from "fs";
103
107
  import path2 from "path";
104
- var CONFIG_FILE = "components.json";
108
+ var CONFIG_FILE = "stera.config.json";
109
+ var LEGACY_CONFIG_FILE = "components.json";
105
110
  function migrateConfig(raw) {
106
111
  if (!("version" in raw) && "tailwind" in raw) {
107
112
  const legacy = raw;
@@ -113,11 +118,22 @@ function migrateConfig(raw) {
113
118
  }
114
119
  return raw;
115
120
  }
121
+ var warnedLegacyFilename = false;
122
+ function warnLegacyFilenameOnce(legacyPath) {
123
+ if (warnedLegacyFilename) return;
124
+ warnedLegacyFilename = true;
125
+ console.warn(
126
+ ` \u26A0 Found ${LEGACY_CONFIG_FILE}. Rename to ${CONFIG_FILE} \u2014 legacy filename support will be removed in a future release.
127
+ (${legacyPath})`
128
+ );
129
+ }
116
130
  function findConfigPath(cwd) {
117
131
  let dir = path2.resolve(cwd);
118
132
  while (true) {
119
133
  const configPath = path2.join(dir, CONFIG_FILE);
120
134
  if (fs.existsSync(configPath)) return configPath;
135
+ const legacyPath = path2.join(dir, LEGACY_CONFIG_FILE);
136
+ if (fs.existsSync(legacyPath)) return legacyPath;
121
137
  const parent = path2.dirname(dir);
122
138
  if (parent === dir) return null;
123
139
  dir = parent;
@@ -126,6 +142,9 @@ function findConfigPath(cwd) {
126
142
  function loadConfig2(cwd) {
127
143
  const configPath = findConfigPath(cwd);
128
144
  if (!configPath) return null;
145
+ if (path2.basename(configPath) === LEGACY_CONFIG_FILE) {
146
+ warnLegacyFilenameOnce(configPath);
147
+ }
129
148
  try {
130
149
  const raw = JSON.parse(fs.readFileSync(configPath, "utf-8"));
131
150
  return migrateConfig(raw);
@@ -210,28 +229,65 @@ function detectAliasPrefix(cwd) {
210
229
  return first ? first.replace(/\/\*$/, "") : null;
211
230
  }
212
231
 
213
- // src/utils/detect-globals.ts
214
- import fs3 from "fs";
232
+ // src/utils/write-stera-css.ts
233
+ import fs3 from "fs/promises";
215
234
  import path4 from "path";
216
- function hasGlobalsCss(config, projectRoot) {
217
- const cssPath = path4.resolve(projectRoot, config.css);
218
- if (!fs3.existsSync(cssPath)) {
219
- return false;
235
+ function stripImportLines(content) {
236
+ const lines = content.split("\n");
237
+ const imports = [];
238
+ const kept = [];
239
+ for (const line of lines) {
240
+ if (line.trim().match(/^@import\s+["'].+["']/)) {
241
+ if (!line.trim().match(/^@import\s+["']tailwindcss["']/)) {
242
+ imports.push(line.trim());
243
+ }
244
+ } else {
245
+ kept.push(line);
246
+ }
220
247
  }
221
- const steraPath = path4.join(path4.dirname(cssPath), "stera-ui.css");
222
- if (fs3.existsSync(steraPath)) {
223
- return true;
248
+ return { content: kept.join("\n"), imports };
249
+ }
250
+ async function writeSteraUiCss(config, cwd) {
251
+ const item = await fetchRegistryItem("globals");
252
+ const rawContent = item.files?.[0]?.content ?? "";
253
+ const { content, imports } = stripImportLines(rawContent);
254
+ const cssDir = path4.dirname(path4.resolve(cwd, config.css));
255
+ const steraUiPath = path4.join(cssDir, "stera-ui.css");
256
+ await fs3.mkdir(cssDir, { recursive: true });
257
+ await fs3.writeFile(steraUiPath, content, "utf-8");
258
+ return {
259
+ steraUiPath,
260
+ extraImports: imports,
261
+ dependencies: item.dependencies ?? []
262
+ };
263
+ }
264
+
265
+ // src/utils/css-imports.ts
266
+ import fs4 from "fs/promises";
267
+ async function insertImportLine(filePath, importStatement) {
268
+ const content = await fs4.readFile(filePath, "utf-8");
269
+ const importPath = importStatement.match(/["']([^"']+)["']/)?.[1];
270
+ if (importPath) {
271
+ const escaped = importPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
272
+ if (new RegExp(`@import\\s+["']${escaped}["']`).test(content)) return;
273
+ }
274
+ const lines = content.split("\n");
275
+ let lastImportIndex = -1;
276
+ for (let i = 0; i < lines.length; i++) {
277
+ if (lines[i].trim().startsWith("@import")) {
278
+ lastImportIndex = i;
279
+ }
224
280
  }
225
- try {
226
- const contents = fs3.readFileSync(cssPath, "utf-8");
227
- return contents.includes("--color-bg-surface");
228
- } catch {
229
- return false;
281
+ if (lastImportIndex >= 0) {
282
+ lines.splice(lastImportIndex + 1, 0, importStatement);
283
+ } else {
284
+ lines.unshift(importStatement);
230
285
  }
286
+ await fs4.writeFile(filePath, lines.join("\n"), "utf-8");
231
287
  }
232
288
 
233
289
  // src/utils/install-deps.ts
234
- import fs4 from "fs";
290
+ import fs5 from "fs";
235
291
  import path5 from "path";
236
292
  import { detect } from "@antfu/ni";
237
293
  import { execa } from "execa";
@@ -320,9 +376,9 @@ async function installDependencies(deps, cwd) {
320
376
  if (deps.length === 0) return;
321
377
  const pkgPath = path5.join(cwd, "package.json");
322
378
  let existing = /* @__PURE__ */ new Set();
323
- if (fs4.existsSync(pkgPath)) {
379
+ if (fs5.existsSync(pkgPath)) {
324
380
  try {
325
- const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf-8"));
381
+ const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
326
382
  existing = new Set(Object.keys({ ...pkg.dependencies, ...pkg.devDependencies }));
327
383
  } catch {
328
384
  }
@@ -349,7 +405,7 @@ or use "pnpm add -w <package>" manually to install at the root.`
349
405
  }
350
406
 
351
407
  // src/utils/write-files.ts
352
- import fs5 from "fs";
408
+ import fs6 from "fs";
353
409
  import path6 from "path";
354
410
  import readline from "readline/promises";
355
411
  async function confirmOverwrite(relativePath) {
@@ -372,8 +428,8 @@ async function writeComponentFiles(items, config, projectRoot, options = {}) {
372
428
  for (const file of item.files ?? []) {
373
429
  const outputPath = resolveOutputPath(config, projectRoot, file);
374
430
  const relativePath = path6.relative(projectRoot, outputPath);
375
- if (fs5.existsSync(outputPath)) {
376
- const existing = fs5.readFileSync(outputPath, "utf-8");
431
+ if (fs6.existsSync(outputPath)) {
432
+ const existing = fs6.readFileSync(outputPath, "utf-8");
377
433
  if (existing === file.content) {
378
434
  skipped.push(relativePath);
379
435
  continue;
@@ -387,8 +443,8 @@ async function writeComponentFiles(items, config, projectRoot, options = {}) {
387
443
  }
388
444
  }
389
445
  const dir = path6.dirname(outputPath);
390
- fs5.mkdirSync(dir, { recursive: true });
391
- fs5.writeFileSync(outputPath, file.content ?? "", "utf-8");
446
+ fs6.mkdirSync(dir, { recursive: true });
447
+ fs6.writeFileSync(outputPath, file.content ?? "", "utf-8");
392
448
  written.push(relativePath);
393
449
  }
394
450
  }
@@ -396,7 +452,7 @@ async function writeComponentFiles(items, config, projectRoot, options = {}) {
396
452
  }
397
453
 
398
454
  // src/utils/update-fonts.ts
399
- import { existsSync, promises as fs6 } from "fs";
455
+ import { existsSync, promises as fs7 } from "fs";
400
456
  import path7 from "path";
401
457
  import {
402
458
  Project,
@@ -437,10 +493,10 @@ async function updateFonts(fonts, project, cwd, options = {}) {
437
493
  if (!layoutPath) return;
438
494
  if (options.silent) {
439
495
  try {
440
- const layoutContent = await fs6.readFile(layoutPath, "utf-8");
496
+ const layoutContent = await fs7.readFile(layoutPath, "utf-8");
441
497
  const updatedContent = transformLayoutFonts(layoutContent, fonts);
442
498
  if (updatedContent !== layoutContent) {
443
- await fs6.writeFile(layoutPath, updatedContent, "utf-8");
499
+ await fs7.writeFile(layoutPath, updatedContent, "utf-8");
444
500
  }
445
501
  } catch {
446
502
  }
@@ -448,10 +504,10 @@ async function updateFonts(fonts, project, cwd, options = {}) {
448
504
  }
449
505
  const spinner = createSpinner("Updating fonts");
450
506
  try {
451
- const layoutContent = await fs6.readFile(layoutPath, "utf-8");
507
+ const layoutContent = await fs7.readFile(layoutPath, "utf-8");
452
508
  const updatedContent = transformLayoutFonts(layoutContent, fonts);
453
509
  if (updatedContent !== layoutContent) {
454
- await fs6.writeFile(layoutPath, updatedContent, "utf-8");
510
+ await fs7.writeFile(layoutPath, updatedContent, "utf-8");
455
511
  }
456
512
  spinner.succeed("Updated layout with font imports");
457
513
  } catch {
@@ -711,11 +767,13 @@ export {
711
767
  transformImports,
712
768
  resolveOutputPath,
713
769
  CONFIG_FILE,
770
+ LEGACY_CONFIG_FILE,
714
771
  findConfigPath,
715
772
  loadConfig2 as loadConfig,
716
773
  detectProject,
717
774
  detectAliasPrefix,
718
- hasGlobalsCss,
775
+ writeSteraUiCss,
776
+ insertImportLine,
719
777
  brand,
720
778
  dim,
721
779
  LOGO,
package/dist/cli.js CHANGED
@@ -9,7 +9,8 @@ import path6 from "path";
9
9
  // src/utils/resolve-config.ts
10
10
  import fs from "fs";
11
11
  import path from "path";
12
- var CONFIG_FILE = "components.json";
12
+ var CONFIG_FILE = "stera.config.json";
13
+ var LEGACY_CONFIG_FILE = "components.json";
13
14
  function migrateConfig(raw) {
14
15
  if (!("version" in raw) && "tailwind" in raw) {
15
16
  const legacy = raw;
@@ -21,11 +22,22 @@ function migrateConfig(raw) {
21
22
  }
22
23
  return raw;
23
24
  }
25
+ var warnedLegacyFilename = false;
26
+ function warnLegacyFilenameOnce(legacyPath) {
27
+ if (warnedLegacyFilename) return;
28
+ warnedLegacyFilename = true;
29
+ console.warn(
30
+ ` \u26A0 Found ${LEGACY_CONFIG_FILE}. Rename to ${CONFIG_FILE} \u2014 legacy filename support will be removed in a future release.
31
+ (${legacyPath})`
32
+ );
33
+ }
24
34
  function findConfigPath(cwd) {
25
35
  let dir = path.resolve(cwd);
26
36
  while (true) {
27
37
  const configPath = path.join(dir, CONFIG_FILE);
28
38
  if (fs.existsSync(configPath)) return configPath;
39
+ const legacyPath = path.join(dir, LEGACY_CONFIG_FILE);
40
+ if (fs.existsSync(legacyPath)) return legacyPath;
29
41
  const parent = path.dirname(dir);
30
42
  if (parent === dir) return null;
31
43
  dir = parent;
@@ -34,6 +46,9 @@ function findConfigPath(cwd) {
34
46
  function loadConfig(cwd) {
35
47
  const configPath = findConfigPath(cwd);
36
48
  if (!configPath) return null;
49
+ if (path.basename(configPath) === LEGACY_CONFIG_FILE) {
50
+ warnLegacyFilenameOnce(configPath);
51
+ }
37
52
  try {
38
53
  const raw = JSON.parse(fs.readFileSync(configPath, "utf-8"));
39
54
  return migrateConfig(raw);
@@ -204,7 +219,6 @@ var dim = (text) => wrap(DIM, text);
204
219
  var LOGO = brand("\u273B");
205
220
  var CHECK = brand("\u2713");
206
221
  var CROSS = "\u2717";
207
- var WARN = "!";
208
222
 
209
223
  // src/utils/spinner.ts
210
224
  var FRAMES = ["\u2022", "\u2726", "\u2736", "\u273B"].map(brand);
@@ -362,30 +376,78 @@ var transformImports = (content, ctx) => {
362
376
  );
363
377
  };
364
378
 
365
- // src/utils/detect-globals.ts
366
- import fs4 from "fs";
379
+ // src/utils/write-stera-css.ts
380
+ import fs4 from "fs/promises";
367
381
  import path5 from "path";
368
- function hasGlobalsCss(config, projectRoot) {
369
- const cssPath = path5.resolve(projectRoot, config.css);
370
- if (!fs4.existsSync(cssPath)) {
371
- return false;
372
- }
373
- const steraPath = path5.join(path5.dirname(cssPath), "stera-ui.css");
374
- if (fs4.existsSync(steraPath)) {
375
- return true;
376
- }
377
- try {
378
- const contents = fs4.readFileSync(cssPath, "utf-8");
379
- return contents.includes("--color-bg-surface");
380
- } catch {
381
- return false;
382
+ function stripImportLines(content) {
383
+ const lines = content.split("\n");
384
+ const imports = [];
385
+ const kept = [];
386
+ for (const line of lines) {
387
+ if (line.trim().match(/^@import\s+["'].+["']/)) {
388
+ if (!line.trim().match(/^@import\s+["']tailwindcss["']/)) {
389
+ imports.push(line.trim());
390
+ }
391
+ } else {
392
+ kept.push(line);
393
+ }
382
394
  }
395
+ return { content: kept.join("\n"), imports };
396
+ }
397
+ async function writeSteraUiCss(config, cwd) {
398
+ const item = await fetchRegistryItem("globals");
399
+ const rawContent = item.files?.[0]?.content ?? "";
400
+ const { content, imports } = stripImportLines(rawContent);
401
+ const cssDir = path5.dirname(path5.resolve(cwd, config.css));
402
+ const steraUiPath = path5.join(cssDir, "stera-ui.css");
403
+ await fs4.mkdir(cssDir, { recursive: true });
404
+ await fs4.writeFile(steraUiPath, content, "utf-8");
405
+ return {
406
+ steraUiPath,
407
+ extraImports: imports,
408
+ dependencies: item.dependencies ?? []
409
+ };
383
410
  }
384
411
 
385
412
  // src/commands/add.ts
386
413
  var transforms = [transformImports];
387
414
  async function add(components, options) {
388
415
  const cwd = options.cwd ? path6.resolve(options.cwd) : process.cwd();
416
+ const configPath = findConfigPath(cwd);
417
+ if (!configPath) {
418
+ console.error(` ${CROSS} ${CONFIG_FILE} not found. Run "stera-ui init" first.`);
419
+ process.exit(1);
420
+ }
421
+ const config = loadConfig(cwd);
422
+ if (!config) {
423
+ console.error(` ${CROSS} Failed to parse ${CONFIG_FILE}.`);
424
+ process.exit(1);
425
+ }
426
+ const projectRoot = path6.dirname(configPath);
427
+ if (components.includes("globals")) {
428
+ if (components.length > 1) {
429
+ console.error(
430
+ ` ${CROSS} "stera-ui add globals" refreshes design tokens and cannot be combined with other components.`
431
+ );
432
+ console.error(
433
+ ` Run "stera-ui add globals" and "stera-ui add ${components.filter((c) => c !== "globals").join(" ")}" separately.`
434
+ );
435
+ process.exit(1);
436
+ }
437
+ const spinner2 = createSpinner("Refreshing base styles");
438
+ let result;
439
+ try {
440
+ result = await writeSteraUiCss(config, projectRoot);
441
+ } catch (err) {
442
+ spinner2.fail("Failed to refresh base styles");
443
+ throw err;
444
+ }
445
+ spinner2.succeed(
446
+ `Refreshed ${path6.relative(projectRoot, result.steraUiPath)}`
447
+ );
448
+ console.log("");
449
+ return;
450
+ }
389
451
  const spinner = createSpinner("Fetching registry");
390
452
  try {
391
453
  for (const name of components) {
@@ -400,17 +462,6 @@ async function add(components, options) {
400
462
  spinner.fail("Failed to fetch registry");
401
463
  throw err;
402
464
  }
403
- const configPath = findConfigPath(cwd);
404
- if (!configPath) {
405
- spinner.fail(`${CONFIG_FILE} not found. Run "stera-ui init" first.`);
406
- process.exit(1);
407
- }
408
- const config = loadConfig(cwd);
409
- if (!config) {
410
- spinner.fail(`Failed to parse ${CONFIG_FILE}.`);
411
- process.exit(1);
412
- }
413
- const projectRoot = path6.dirname(configPath);
414
465
  let resolved;
415
466
  try {
416
467
  resolved = await resolveDependencies(components);
@@ -419,14 +470,6 @@ async function add(components, options) {
419
470
  throw err;
420
471
  }
421
472
  spinner.succeed("Registry resolved");
422
- const isAddingGlobals = components.includes("globals");
423
- if (!isAddingGlobals && !hasGlobalsCss(config, projectRoot)) {
424
- console.warn(`
425
- ${WARN} Stera design tokens not found in ${config.css}`);
426
- console.warn(` Components may not render correctly.`);
427
- console.warn(` Run "stera-ui add globals" to install base styles.
428
- `);
429
- }
430
473
  for (const item of resolved) {
431
474
  for (const file of item.files ?? []) {
432
475
  if (file.content) {
@@ -478,9 +521,10 @@ async function add(components, options) {
478
521
  }
479
522
 
480
523
  // src/commands/init.ts
481
- import fs9 from "fs";
524
+ import fs10 from "fs";
525
+ import fsp from "fs/promises";
482
526
  import path10 from "path";
483
- import { select } from "@inquirer/prompts";
527
+ import { confirm, select } from "@inquirer/prompts";
484
528
 
485
529
  // src/utils/detect-project.ts
486
530
  import fs5 from "fs";
@@ -987,6 +1031,30 @@ function parseTemplateLiteralToCnArgs(templateLiteral) {
987
1031
  return [...staticArgs, ...variableArgs];
988
1032
  }
989
1033
 
1034
+ // src/utils/css-imports.ts
1035
+ import fs9 from "fs/promises";
1036
+ async function insertImportLine(filePath, importStatement) {
1037
+ const content = await fs9.readFile(filePath, "utf-8");
1038
+ const importPath = importStatement.match(/["']([^"']+)["']/)?.[1];
1039
+ if (importPath) {
1040
+ const escaped = importPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1041
+ if (new RegExp(`@import\\s+["']${escaped}["']`).test(content)) return;
1042
+ }
1043
+ const lines = content.split("\n");
1044
+ let lastImportIndex = -1;
1045
+ for (let i = 0; i < lines.length; i++) {
1046
+ if (lines[i].trim().startsWith("@import")) {
1047
+ lastImportIndex = i;
1048
+ }
1049
+ }
1050
+ if (lastImportIndex >= 0) {
1051
+ lines.splice(lastImportIndex + 1, 0, importStatement);
1052
+ } else {
1053
+ lines.unshift(importStatement);
1054
+ }
1055
+ await fs9.writeFile(filePath, lines.join("\n"), "utf-8");
1056
+ }
1057
+
990
1058
  // src/commands/init.ts
991
1059
  var DEFAULT_FONT_NAMES = ["font-geist", "font-geist-mono"];
992
1060
  function buildConfig(project, strategy, aliasPrefix) {
@@ -1014,42 +1082,6 @@ function buildConfig(project, strategy, aliasPrefix) {
1014
1082
  }
1015
1083
  };
1016
1084
  }
1017
- function stripImportLines(content) {
1018
- const lines = content.split("\n");
1019
- const imports = [];
1020
- const kept = [];
1021
- for (const line of lines) {
1022
- if (line.trim().match(/^@import\s+["'].+["']/)) {
1023
- if (!line.trim().match(/^@import\s+["']tailwindcss["']/)) {
1024
- imports.push(line.trim());
1025
- }
1026
- } else {
1027
- kept.push(line);
1028
- }
1029
- }
1030
- return { content: kept.join("\n"), imports };
1031
- }
1032
- function insertImportLine(filePath, importStatement) {
1033
- const content = fs9.readFileSync(filePath, "utf-8");
1034
- const importPath = importStatement.match(/["']([^"']+)["']/)?.[1];
1035
- if (importPath) {
1036
- const escaped = importPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1037
- if (new RegExp(`@import\\s+["']${escaped}["']`).test(content)) return;
1038
- }
1039
- const lines = content.split("\n");
1040
- let lastImportIndex = -1;
1041
- for (let i = 0; i < lines.length; i++) {
1042
- if (lines[i].trim().startsWith("@import")) {
1043
- lastImportIndex = i;
1044
- }
1045
- }
1046
- if (lastImportIndex >= 0) {
1047
- lines.splice(lastImportIndex + 1, 0, importStatement);
1048
- } else {
1049
- lines.unshift(importStatement);
1050
- }
1051
- fs9.writeFileSync(filePath, lines.join("\n"), "utf-8");
1052
- }
1053
1085
  async function promptFontStrategy(detected, skipPrompts) {
1054
1086
  const hasExistingFonts = detected.hasNextFont || detected.hasFontFace || detected.hasGoogleFontsLink;
1055
1087
  const defaultStrategy = hasExistingFonts ? "keep-existing" : "stera-default";
@@ -1112,26 +1144,17 @@ async function fetchDefaultFonts() {
1112
1144
  }
1113
1145
  async function executeFontStrategy(strategy, project, detected, config, cwd) {
1114
1146
  if (strategy === "skip") return;
1147
+ const cssDir = path10.dirname(path10.resolve(cwd, config.css));
1148
+ const steraUiPath = path10.join(cssDir, "stera-ui.css");
1115
1149
  if (strategy === "keep-existing") {
1116
- const cssPath = path10.resolve(cwd, config.css);
1117
- const cssDir = path10.dirname(cssPath);
1118
- if (fs9.existsSync(cssPath)) {
1119
- const fontFamily = detected.fontFamilies.length > 0 ? detected.fontFamilies[0] : null;
1120
- const fontValue = detected.nextFontVariable ? `var(${detected.nextFontVariable})` : fontFamily ?? "Geist";
1121
- const patched = patchCssVariables(cssPath, {
1122
- "--font-heading": fontValue,
1123
- "--font-sans": fontValue
1124
- });
1125
- const steraUiPath = path10.join(cssDir, "stera-ui.css");
1126
- if (fs9.existsSync(steraUiPath)) {
1127
- patchCssVariables(steraUiPath, {
1128
- "--font-heading": fontValue,
1129
- "--font-sans": fontValue
1130
- });
1131
- }
1132
- if (patched.length > 0) {
1133
- console.log(` ${CHECK} Updated font variables to use ${fontValue}`);
1134
- }
1150
+ const fontFamily = detected.fontFamilies.length > 0 ? detected.fontFamilies[0] : null;
1151
+ const fontValue = detected.nextFontVariable ? `var(${detected.nextFontVariable})` : fontFamily ?? "Geist";
1152
+ const patched = patchCssVariables(steraUiPath, {
1153
+ "--font-heading": fontValue,
1154
+ "--font-sans": fontValue
1155
+ });
1156
+ if (patched.length > 0) {
1157
+ console.log(` ${CHECK} Updated font variables to use ${fontValue}`);
1135
1158
  }
1136
1159
  return;
1137
1160
  }
@@ -1142,25 +1165,15 @@ async function executeFontStrategy(strategy, project, detected, config, cwd) {
1142
1165
  await installDependencies(fontTree.dependencies, cwd);
1143
1166
  }
1144
1167
  if (fontTree.cssImports.length > 0) {
1145
- const cssPath = path10.resolve(cwd, config.css);
1146
- const cssDir = path10.dirname(cssPath);
1147
- const targetCss = fs9.existsSync(path10.join(cssDir, "stera-ui.css")) ? path10.join(cssDir, "stera-ui.css") : cssPath;
1148
- if (fs9.existsSync(targetCss)) {
1149
- for (const importLine of fontTree.cssImports) {
1150
- insertImportLine(targetCss, importLine);
1151
- }
1152
- console.log(` ${CHECK} Added font imports to ${path10.relative(cwd, targetCss)}`);
1168
+ for (const importLine of fontTree.cssImports) {
1169
+ await insertImportLine(steraUiPath, importLine);
1153
1170
  }
1171
+ console.log(` ${CHECK} Added font imports to ${path10.relative(cwd, steraUiPath)}`);
1154
1172
  }
1155
1173
  if (Object.keys(fontTree.cssVars).length > 0) {
1156
- const cssPath = path10.resolve(cwd, config.css);
1157
- const cssDir = path10.dirname(cssPath);
1158
- const targetCss = fs9.existsSync(path10.join(cssDir, "stera-ui.css")) ? path10.join(cssDir, "stera-ui.css") : cssPath;
1159
- if (fs9.existsSync(targetCss)) {
1160
- const patched = patchCssVariables(targetCss, fontTree.cssVars);
1161
- if (patched.length > 0) {
1162
- console.log(` ${CHECK} Updated font variables in ${path10.relative(cwd, targetCss)}`);
1163
- }
1174
+ const patched = patchCssVariables(steraUiPath, fontTree.cssVars);
1175
+ if (patched.length > 0) {
1176
+ console.log(` ${CHECK} Updated font variables in ${path10.relative(cwd, steraUiPath)}`);
1164
1177
  }
1165
1178
  }
1166
1179
  await updateFonts(fonts, project, cwd);
@@ -1172,12 +1185,34 @@ async function init(options) {
1172
1185
  `);
1173
1186
  const existing = findConfigPath(cwd);
1174
1187
  if (existing) {
1175
- console.log(` components.json already exists at ${existing}`);
1188
+ const existingName = path10.basename(existing);
1189
+ if (existingName === LEGACY_CONFIG_FILE) {
1190
+ console.log(
1191
+ ` Found legacy ${LEGACY_CONFIG_FILE} at ${existing}`
1192
+ );
1193
+ const shouldRename = options.yes ? true : await confirm({
1194
+ message: `Rename it to ${CONFIG_FILE}?`,
1195
+ default: true
1196
+ });
1197
+ if (shouldRename) {
1198
+ const renamed = path10.join(path10.dirname(existing), CONFIG_FILE);
1199
+ fs10.renameSync(existing, renamed);
1200
+ console.log(` ${CHECK} Renamed to ${CONFIG_FILE}`);
1201
+ console.log(' Run "stera-ui add <component>" to add components.\n');
1202
+ } else {
1203
+ console.log(
1204
+ ` Keeping ${LEGACY_CONFIG_FILE} for now. Legacy filename support will be removed in a future release.
1205
+ `
1206
+ );
1207
+ }
1208
+ return;
1209
+ }
1210
+ console.log(` ${CONFIG_FILE} already exists at ${existing}`);
1176
1211
  console.log(' Run "stera-ui add <component>" to add components.\n');
1177
1212
  return;
1178
1213
  }
1179
1214
  const pkgPath = path10.join(cwd, "package.json");
1180
- if (!fs9.existsSync(pkgPath)) {
1215
+ if (!fs10.existsSync(pkgPath)) {
1181
1216
  console.error(
1182
1217
  " Error: No package.json found. Please run this in a project directory."
1183
1218
  );
@@ -1212,58 +1247,42 @@ async function init(options) {
1212
1247
  );
1213
1248
  const config = buildConfig(project, strategy, aliasPrefix);
1214
1249
  const configPath = path10.join(cwd, CONFIG_FILE);
1215
- fs9.writeFileSync(
1250
+ fs10.writeFileSync(
1216
1251
  configPath,
1217
1252
  JSON.stringify(config, null, 2) + "\n",
1218
1253
  "utf-8"
1219
1254
  );
1220
- console.log(` ${CHECK} Created components.json`);
1221
- const cssDir = path10.dirname(path10.resolve(cwd, config.css));
1222
- if (project.existingCssFile) {
1223
- if (hasGlobalsCss(config, cwd)) {
1224
- console.log(
1225
- ` ${CHECK} Stera already initialised ${dim(`(${path10.join(path10.dirname(project.existingCssFile), "stera-ui.css")} exists)`)}`
1226
- );
1227
- } else {
1228
- const fetchSpinner = createSpinner("Fetching base styles");
1229
- let globalsItem;
1230
- try {
1231
- globalsItem = await fetchRegistryItem("globals");
1232
- } catch {
1233
- fetchSpinner.fail("Base styles not found \u2014 skipping");
1234
- globalsItem = null;
1235
- }
1236
- if (globalsItem) {
1237
- fetchSpinner.succeed("Base styles resolved");
1238
- const globalsContent = globalsItem.files?.[0]?.content ?? "";
1239
- const { content: steraUiContent, imports } = stripImportLines(globalsContent);
1240
- const steraUiPath = path10.join(cssDir, "stera-ui.css");
1241
- fs9.mkdirSync(cssDir, { recursive: true });
1242
- fs9.writeFileSync(steraUiPath, steraUiContent, "utf-8");
1243
- console.log(` ${CHECK} Created ${path10.relative(cwd, steraUiPath)}`);
1244
- const existingCssPath = path10.resolve(cwd, project.existingCssFile);
1245
- for (const imp of imports) {
1246
- insertImportLine(existingCssPath, imp);
1247
- }
1248
- insertImportLine(existingCssPath, "@import './stera-ui.css';");
1249
- console.log(` ${CHECK} Added @import to ${project.existingCssFile}`);
1250
- }
1255
+ console.log(` ${CHECK} Created ${CONFIG_FILE}`);
1256
+ const fetchSpinner = createSpinner("Fetching base styles");
1257
+ let steraCssResult;
1258
+ try {
1259
+ steraCssResult = await writeSteraUiCss(config, cwd);
1260
+ } catch (err) {
1261
+ fetchSpinner.fail("Failed to fetch base styles");
1262
+ throw err;
1263
+ }
1264
+ fetchSpinner.succeed("Base styles resolved");
1265
+ console.log(
1266
+ ` ${CHECK} Created ${path10.relative(cwd, steraCssResult.steraUiPath)}`
1267
+ );
1268
+ const globalsPath = project.existingCssFile ? path10.resolve(cwd, project.existingCssFile) : path10.resolve(cwd, config.css);
1269
+ if (fs10.existsSync(globalsPath)) {
1270
+ for (const imp of steraCssResult.extraImports) {
1271
+ await insertImportLine(globalsPath, imp);
1251
1272
  }
1273
+ await insertImportLine(globalsPath, '@import "./stera-ui.css";');
1274
+ console.log(
1275
+ ` ${CHECK} Added @import to ${path10.relative(cwd, globalsPath)}`
1276
+ );
1252
1277
  } else {
1253
- const fetchSpinner = createSpinner("Fetching base styles");
1254
- let resolved;
1255
- try {
1256
- resolved = await resolveDependencies(["globals"]);
1257
- } catch (err) {
1258
- fetchSpinner.fail("Failed to fetch base styles");
1259
- throw err;
1260
- }
1261
- fetchSpinner.succeed("Base styles resolved");
1262
- const filtered = resolved.filter((item) => item.name !== "fonts");
1263
- const { written } = await writeComponentFiles(filtered, config, cwd);
1264
- for (const file of written) {
1265
- console.log(` ${CHECK} Created ${file}`);
1266
- }
1278
+ await fsp.mkdir(path10.dirname(globalsPath), { recursive: true });
1279
+ const scaffold = [
1280
+ '@import "tailwindcss";',
1281
+ ...steraCssResult.extraImports,
1282
+ '@import "./stera-ui.css";'
1283
+ ].join("\n") + "\n";
1284
+ await fsp.writeFile(globalsPath, scaffold, "utf-8");
1285
+ console.log(` ${CHECK} Created ${path10.relative(cwd, globalsPath)}`);
1267
1286
  }
1268
1287
  await executeFontStrategy(
1269
1288
  strategy,
@@ -1272,21 +1291,10 @@ async function init(options) {
1272
1291
  config,
1273
1292
  cwd
1274
1293
  );
1275
- const depsSpinner = createSpinner("Fetching package list");
1276
- let globalsForDeps;
1277
- try {
1278
- globalsForDeps = await fetchRegistryItem("globals");
1279
- } catch {
1280
- depsSpinner.fail("Package list unavailable \u2014 skipping");
1281
- globalsForDeps = null;
1282
- }
1283
- if (globalsForDeps) {
1284
- depsSpinner.succeed("Package list resolved");
1285
- if (globalsForDeps.dependencies) {
1286
- const deps = [...new Set(globalsForDeps.dependencies)].sort();
1287
- console.log("");
1288
- await installDependencies(deps, cwd);
1289
- }
1294
+ if (steraCssResult.dependencies.length > 0) {
1295
+ const deps = [...new Set(steraCssResult.dependencies)].sort();
1296
+ console.log("");
1297
+ await installDependencies(deps, cwd);
1290
1298
  }
1291
1299
  console.log("");
1292
1300
  console.log(" Next steps:");
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { fetchRegistryIndex, fetchRegistryItem, getAllComponents, getComponent, resolveDependencies } from './registry/index.js';
2
2
  export { RegistryFontItem, RegistryIndex, RegistryItem, RegistryItemFile, RegistryItemFont, RegistryItemType, SteraConfig, configSchema, registryIndexSchema, registryItemFileSchema, registryItemFontSchema, registryItemSchema, registryItemTypeSchema } from './schema/index.js';
3
- export { CHECK, CONFIG_FILE, CROSS, LOGO, ProjectInfo, TransformContext, TransformFn, WARN, WriteOptions, WriteResult, applyTransforms, brand, createSpinner, detectAliasPrefix, detectPackageManager, detectProject, dim, findConfigPath, hasGlobalsCss, installDependencies, loadConfig, massageTreeForFonts, resolveOutputPath, transformImports, updateFonts, writeComponentFiles } from './utils/index.js';
3
+ export { CHECK, CONFIG_FILE, CROSS, LEGACY_CONFIG_FILE, LOGO, ProjectInfo, TransformContext, TransformFn, WARN, WriteOptions, WriteResult, WriteSteraCssResult, applyTransforms, brand, createSpinner, detectAliasPrefix, detectPackageManager, detectProject, dim, findConfigPath, insertImportLine, installDependencies, loadConfig, massageTreeForFonts, resolveOutputPath, transformImports, updateFonts, writeComponentFiles, writeSteraUiCss } from './utils/index.js';
4
4
  import 'zod';
package/dist/index.js CHANGED
@@ -1,10 +1,3 @@
1
- import {
2
- fetchRegistryIndex,
3
- fetchRegistryItem,
4
- getAllComponents,
5
- getComponent,
6
- resolveDependencies
7
- } from "./chunk-T3QWOVJR.js";
8
1
  import {
9
2
  configSchema,
10
3
  registryIndexSchema,
@@ -17,6 +10,7 @@ import {
17
10
  CHECK,
18
11
  CONFIG_FILE,
19
12
  CROSS,
13
+ LEGACY_CONFIG_FILE,
20
14
  LOGO,
21
15
  WARN,
22
16
  applyTransforms,
@@ -27,19 +21,28 @@ import {
27
21
  detectProject,
28
22
  dim,
29
23
  findConfigPath,
30
- hasGlobalsCss,
24
+ insertImportLine,
31
25
  installDependencies,
32
26
  loadConfig,
33
27
  massageTreeForFonts,
34
28
  resolveOutputPath,
35
29
  transformImports,
36
30
  updateFonts,
37
- writeComponentFiles
38
- } from "./chunk-QD7FOI3A.js";
31
+ writeComponentFiles,
32
+ writeSteraUiCss
33
+ } from "./chunk-F4NT3JC6.js";
34
+ import {
35
+ fetchRegistryIndex,
36
+ fetchRegistryItem,
37
+ getAllComponents,
38
+ getComponent,
39
+ resolveDependencies
40
+ } from "./chunk-T3QWOVJR.js";
39
41
  export {
40
42
  CHECK,
41
43
  CONFIG_FILE,
42
44
  CROSS,
45
+ LEGACY_CONFIG_FILE,
43
46
  LOGO,
44
47
  WARN,
45
48
  applyTransforms,
@@ -55,7 +58,7 @@ export {
55
58
  findConfigPath,
56
59
  getAllComponents,
57
60
  getComponent,
58
- hasGlobalsCss,
61
+ insertImportLine,
59
62
  installDependencies,
60
63
  loadConfig,
61
64
  massageTreeForFonts,
@@ -68,5 +71,6 @@ export {
68
71
  resolveOutputPath,
69
72
  transformImports,
70
73
  updateFonts,
71
- writeComponentFiles
74
+ writeComponentFiles,
75
+ writeSteraUiCss
72
76
  };
@@ -17,7 +17,8 @@ interface SteraConfig {
17
17
  nextFont?: boolean;
18
18
  };
19
19
  }
20
- declare const CONFIG_FILE = "components.json";
20
+ declare const CONFIG_FILE = "stera.config.json";
21
+ declare const LEGACY_CONFIG_FILE = "components.json";
21
22
  declare function findConfigPath(cwd: string): string | null;
22
23
  declare function loadConfig(cwd: string): SteraConfig | null;
23
24
 
@@ -77,13 +78,36 @@ declare function detectProject(cwd: string): ProjectInfo;
77
78
  */
78
79
  declare function detectAliasPrefix(cwd: string): string | null;
79
80
 
81
+ interface WriteSteraCssResult {
82
+ /** Absolute path to the written `stera-ui.css`. */
83
+ steraUiPath: string;
84
+ /**
85
+ * Non-tailwindcss `@import` statements stripped from the registry content
86
+ * (e.g. `@import "tw-animate-css";`). Callers are expected to ensure these
87
+ * are present in the user's main globals.css.
88
+ */
89
+ extraImports: string[];
90
+ /** npm packages declared by the globals registry item. */
91
+ dependencies: string[];
92
+ }
80
93
  /**
81
- * Check if Stera has already been initialised in the user's project.
82
- * Returns true if either a sibling `stera-ui.css` exists (existing-CSS init
83
- * path) or the user's CSS file itself contains Stera tokens inline
84
- * (no-existing-CSS init path, or a hand-rolled setup).
94
+ * Fetch the `globals` registry item and write `stera-ui.css` into the
95
+ * directory that holds the user's configured globals.css. Overwrites
96
+ * unconditionally — the caller decides when this is appropriate (init or
97
+ * an explicit `add globals` refresh).
98
+ *
99
+ * Does NOT touch the user's globals.css.
100
+ */
101
+ declare function writeSteraUiCss(config: SteraConfig, cwd: string): Promise<WriteSteraCssResult>;
102
+
103
+ /**
104
+ * Insert an `@import` statement into an existing CSS file, placed after the
105
+ * last existing `@import` line. Prepends to the file if none exist.
106
+ *
107
+ * Idempotent: if an `@import` for the same path already exists, no change
108
+ * is made.
85
109
  */
86
- declare function hasGlobalsCss(config: SteraConfig, projectRoot: string): boolean;
110
+ declare function insertImportLine(filePath: string, importStatement: string): Promise<void>;
87
111
 
88
112
  type PackageManager = "pnpm" | "npm" | "yarn" | "bun";
89
113
  declare function detectPackageManager(cwd: string): Promise<PackageManager>;
@@ -143,4 +167,4 @@ declare function updateFonts(fonts: RegistryFontItem[], project: ProjectInfo, cw
143
167
  silent?: boolean;
144
168
  }): Promise<void>;
145
169
 
146
- export { CHECK, CONFIG_FILE, CROSS, LOGO, type ProjectInfo, type TransformContext, type TransformFn, WARN, type WriteOptions, type WriteResult, applyTransforms, brand, createSpinner, detectAliasPrefix, detectPackageManager, detectProject, dim, findConfigPath, hasGlobalsCss, installDependencies, loadConfig, massageTreeForFonts, resolveOutputPath, transformImports, updateFonts, writeComponentFiles };
170
+ export { CHECK, CONFIG_FILE, CROSS, LEGACY_CONFIG_FILE, LOGO, type ProjectInfo, type TransformContext, type TransformFn, WARN, type WriteOptions, type WriteResult, type WriteSteraCssResult, applyTransforms, brand, createSpinner, detectAliasPrefix, detectPackageManager, detectProject, dim, findConfigPath, insertImportLine, installDependencies, loadConfig, massageTreeForFonts, resolveOutputPath, transformImports, updateFonts, writeComponentFiles, writeSteraUiCss };
@@ -2,6 +2,7 @@ import {
2
2
  CHECK,
3
3
  CONFIG_FILE,
4
4
  CROSS,
5
+ LEGACY_CONFIG_FILE,
5
6
  LOGO,
6
7
  WARN,
7
8
  applyTransforms,
@@ -12,19 +13,22 @@ import {
12
13
  detectProject,
13
14
  dim,
14
15
  findConfigPath,
15
- hasGlobalsCss,
16
+ insertImportLine,
16
17
  installDependencies,
17
18
  loadConfig,
18
19
  massageTreeForFonts,
19
20
  resolveOutputPath,
20
21
  transformImports,
21
22
  updateFonts,
22
- writeComponentFiles
23
- } from "../chunk-QD7FOI3A.js";
23
+ writeComponentFiles,
24
+ writeSteraUiCss
25
+ } from "../chunk-F4NT3JC6.js";
26
+ import "../chunk-T3QWOVJR.js";
24
27
  export {
25
28
  CHECK,
26
29
  CONFIG_FILE,
27
30
  CROSS,
31
+ LEGACY_CONFIG_FILE,
28
32
  LOGO,
29
33
  WARN,
30
34
  applyTransforms,
@@ -35,12 +39,13 @@ export {
35
39
  detectProject,
36
40
  dim,
37
41
  findConfigPath,
38
- hasGlobalsCss,
42
+ insertImportLine,
39
43
  installDependencies,
40
44
  loadConfig,
41
45
  massageTreeForFonts,
42
46
  resolveOutputPath,
43
47
  transformImports,
44
48
  updateFonts,
45
- writeComponentFiles
49
+ writeComponentFiles,
50
+ writeSteraUiCss
46
51
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stera-ui",
3
- "version": "0.3.3",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "description": "Add Stera UI components to your project.",
6
6
  "license": "MIT",