stera-ui 0.3.2 → 0.4.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);
@@ -66,14 +70,22 @@ var TYPE_TO_ALIAS = {
66
70
  };
67
71
  function resolveAlias(alias, projectRoot) {
68
72
  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);
73
+ if (tsConfig.resultType === "success" && Object.keys(tsConfig.paths).length > 0) {
74
+ const hasMatchingPrefix = Object.keys(tsConfig.paths).some((key) => {
75
+ const prefix = key.replace(/\/\*$/, "");
76
+ return alias === prefix || alias.startsWith(`${prefix}/`);
77
+ });
78
+ if (hasMatchingPrefix) {
79
+ const match = createMatchPath(tsConfig.absoluteBaseUrl, tsConfig.paths);
80
+ const resolved = match(alias, void 0, () => true, [".ts", ".tsx"]);
81
+ if (resolved) {
82
+ return path.relative(projectRoot, resolved);
83
+ }
74
84
  }
75
85
  }
76
- return alias.replace(/^@\//, "");
86
+ throw new Error(
87
+ `Could not resolve alias "${alias}" from tsconfig.json. Ensure compilerOptions.paths defines a mapping for it (e.g. "${alias}/*"), then re-run. See https://ui.stera.sh/docs/installation.`
88
+ );
77
89
  }
78
90
  function resolveOutputPath(config, projectRoot, file) {
79
91
  const alias = TYPE_TO_ALIAS[file.type];
@@ -129,6 +141,7 @@ function loadConfig2(cwd) {
129
141
  // src/utils/detect-project.ts
130
142
  import fs2 from "fs";
131
143
  import path3 from "path";
144
+ import { loadConfig as loadConfig3 } from "tsconfig-paths";
132
145
  function hasFile(cwd, filenames) {
133
146
  return filenames.some((f) => fs2.existsSync(path3.join(cwd, f)));
134
147
  }
@@ -182,21 +195,84 @@ function detectProject(cwd) {
182
195
  }
183
196
  return { hasSrc, framework, rsc, existingCssFile, layoutFile };
184
197
  }
198
+ function detectAliasPrefix(cwd) {
199
+ let tsConfig;
200
+ try {
201
+ tsConfig = loadConfig3(cwd);
202
+ } catch {
203
+ return null;
204
+ }
205
+ if (tsConfig.resultType !== "success" || Object.keys(tsConfig.paths).length === 0) {
206
+ return null;
207
+ }
208
+ for (const [alias, targets] of Object.entries(tsConfig.paths)) {
209
+ if (targets.includes("./*") || targets.includes("./src/*") || targets.includes("./app/*")) {
210
+ return alias.replace(/\/\*$/, "");
211
+ }
212
+ }
213
+ const first = Object.keys(tsConfig.paths)[0];
214
+ return first ? first.replace(/\/\*$/, "") : null;
215
+ }
185
216
 
186
- // src/utils/detect-globals.ts
187
- import fs3 from "fs";
217
+ // src/utils/write-stera-css.ts
218
+ import fs3 from "fs/promises";
188
219
  import path4 from "path";
189
- function hasGlobalsCss(config, projectRoot) {
190
- const cssPath = path4.resolve(projectRoot, config.css);
191
- if (!fs3.existsSync(cssPath)) {
192
- return false;
220
+ function stripImportLines(content) {
221
+ const lines = content.split("\n");
222
+ const imports = [];
223
+ const kept = [];
224
+ for (const line of lines) {
225
+ if (line.trim().match(/^@import\s+["'].+["']/)) {
226
+ if (!line.trim().match(/^@import\s+["']tailwindcss["']/)) {
227
+ imports.push(line.trim());
228
+ }
229
+ } else {
230
+ kept.push(line);
231
+ }
193
232
  }
194
- const steraPath = path4.join(path4.dirname(cssPath), "stera-ui.css");
195
- return fs3.existsSync(steraPath);
233
+ return { content: kept.join("\n"), imports };
234
+ }
235
+ async function writeSteraUiCss(config, cwd) {
236
+ const item = await fetchRegistryItem("globals");
237
+ const rawContent = item.files?.[0]?.content ?? "";
238
+ const { content, imports } = stripImportLines(rawContent);
239
+ const cssDir = path4.dirname(path4.resolve(cwd, config.css));
240
+ const steraUiPath = path4.join(cssDir, "stera-ui.css");
241
+ await fs3.mkdir(cssDir, { recursive: true });
242
+ await fs3.writeFile(steraUiPath, content, "utf-8");
243
+ return {
244
+ steraUiPath,
245
+ extraImports: imports,
246
+ dependencies: item.dependencies ?? []
247
+ };
248
+ }
249
+
250
+ // src/utils/css-imports.ts
251
+ import fs4 from "fs/promises";
252
+ async function insertImportLine(filePath, importStatement) {
253
+ const content = await fs4.readFile(filePath, "utf-8");
254
+ const importPath = importStatement.match(/["']([^"']+)["']/)?.[1];
255
+ if (importPath) {
256
+ const escaped = importPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
257
+ if (new RegExp(`@import\\s+["']${escaped}["']`).test(content)) return;
258
+ }
259
+ const lines = content.split("\n");
260
+ let lastImportIndex = -1;
261
+ for (let i = 0; i < lines.length; i++) {
262
+ if (lines[i].trim().startsWith("@import")) {
263
+ lastImportIndex = i;
264
+ }
265
+ }
266
+ if (lastImportIndex >= 0) {
267
+ lines.splice(lastImportIndex + 1, 0, importStatement);
268
+ } else {
269
+ lines.unshift(importStatement);
270
+ }
271
+ await fs4.writeFile(filePath, lines.join("\n"), "utf-8");
196
272
  }
197
273
 
198
274
  // src/utils/install-deps.ts
199
- import fs4 from "fs";
275
+ import fs5 from "fs";
200
276
  import path5 from "path";
201
277
  import { detect } from "@antfu/ni";
202
278
  import { execa } from "execa";
@@ -285,9 +361,9 @@ async function installDependencies(deps, cwd) {
285
361
  if (deps.length === 0) return;
286
362
  const pkgPath = path5.join(cwd, "package.json");
287
363
  let existing = /* @__PURE__ */ new Set();
288
- if (fs4.existsSync(pkgPath)) {
364
+ if (fs5.existsSync(pkgPath)) {
289
365
  try {
290
- const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf-8"));
366
+ const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
291
367
  existing = new Set(Object.keys({ ...pkg.dependencies, ...pkg.devDependencies }));
292
368
  } catch {
293
369
  }
@@ -314,7 +390,7 @@ or use "pnpm add -w <package>" manually to install at the root.`
314
390
  }
315
391
 
316
392
  // src/utils/write-files.ts
317
- import fs5 from "fs";
393
+ import fs6 from "fs";
318
394
  import path6 from "path";
319
395
  import readline from "readline/promises";
320
396
  async function confirmOverwrite(relativePath) {
@@ -337,8 +413,8 @@ async function writeComponentFiles(items, config, projectRoot, options = {}) {
337
413
  for (const file of item.files ?? []) {
338
414
  const outputPath = resolveOutputPath(config, projectRoot, file);
339
415
  const relativePath = path6.relative(projectRoot, outputPath);
340
- if (fs5.existsSync(outputPath)) {
341
- const existing = fs5.readFileSync(outputPath, "utf-8");
416
+ if (fs6.existsSync(outputPath)) {
417
+ const existing = fs6.readFileSync(outputPath, "utf-8");
342
418
  if (existing === file.content) {
343
419
  skipped.push(relativePath);
344
420
  continue;
@@ -352,8 +428,8 @@ async function writeComponentFiles(items, config, projectRoot, options = {}) {
352
428
  }
353
429
  }
354
430
  const dir = path6.dirname(outputPath);
355
- fs5.mkdirSync(dir, { recursive: true });
356
- fs5.writeFileSync(outputPath, file.content ?? "", "utf-8");
431
+ fs6.mkdirSync(dir, { recursive: true });
432
+ fs6.writeFileSync(outputPath, file.content ?? "", "utf-8");
357
433
  written.push(relativePath);
358
434
  }
359
435
  }
@@ -361,7 +437,7 @@ async function writeComponentFiles(items, config, projectRoot, options = {}) {
361
437
  }
362
438
 
363
439
  // src/utils/update-fonts.ts
364
- import { existsSync, promises as fs6 } from "fs";
440
+ import { existsSync, promises as fs7 } from "fs";
365
441
  import path7 from "path";
366
442
  import {
367
443
  Project,
@@ -402,10 +478,10 @@ async function updateFonts(fonts, project, cwd, options = {}) {
402
478
  if (!layoutPath) return;
403
479
  if (options.silent) {
404
480
  try {
405
- const layoutContent = await fs6.readFile(layoutPath, "utf-8");
481
+ const layoutContent = await fs7.readFile(layoutPath, "utf-8");
406
482
  const updatedContent = transformLayoutFonts(layoutContent, fonts);
407
483
  if (updatedContent !== layoutContent) {
408
- await fs6.writeFile(layoutPath, updatedContent, "utf-8");
484
+ await fs7.writeFile(layoutPath, updatedContent, "utf-8");
409
485
  }
410
486
  } catch {
411
487
  }
@@ -413,10 +489,10 @@ async function updateFonts(fonts, project, cwd, options = {}) {
413
489
  }
414
490
  const spinner = createSpinner("Updating fonts");
415
491
  try {
416
- const layoutContent = await fs6.readFile(layoutPath, "utf-8");
492
+ const layoutContent = await fs7.readFile(layoutPath, "utf-8");
417
493
  const updatedContent = transformLayoutFonts(layoutContent, fonts);
418
494
  if (updatedContent !== layoutContent) {
419
- await fs6.writeFile(layoutPath, updatedContent, "utf-8");
495
+ await fs7.writeFile(layoutPath, updatedContent, "utf-8");
420
496
  }
421
497
  spinner.succeed("Updated layout with font imports");
422
498
  } catch {
@@ -679,7 +755,9 @@ export {
679
755
  findConfigPath,
680
756
  loadConfig2 as loadConfig,
681
757
  detectProject,
682
- hasGlobalsCss,
758
+ detectAliasPrefix,
759
+ writeSteraUiCss,
760
+ insertImportLine,
683
761
  brand,
684
762
  dim,
685
763
  LOGO,
package/dist/cli.js CHANGED
@@ -111,14 +111,22 @@ var TYPE_TO_ALIAS = {
111
111
  };
112
112
  function resolveAlias(alias, projectRoot) {
113
113
  const tsConfig = loadConfig2(projectRoot);
114
- if (tsConfig.resultType === "success") {
115
- const match = createMatchPath(tsConfig.absoluteBaseUrl, tsConfig.paths);
116
- const resolved = match(alias, void 0, () => true, [".ts", ".tsx"]);
117
- if (resolved) {
118
- return path2.relative(projectRoot, resolved);
114
+ if (tsConfig.resultType === "success" && Object.keys(tsConfig.paths).length > 0) {
115
+ const hasMatchingPrefix = Object.keys(tsConfig.paths).some((key) => {
116
+ const prefix = key.replace(/\/\*$/, "");
117
+ return alias === prefix || alias.startsWith(`${prefix}/`);
118
+ });
119
+ if (hasMatchingPrefix) {
120
+ const match = createMatchPath(tsConfig.absoluteBaseUrl, tsConfig.paths);
121
+ const resolved = match(alias, void 0, () => true, [".ts", ".tsx"]);
122
+ if (resolved) {
123
+ return path2.relative(projectRoot, resolved);
124
+ }
119
125
  }
120
126
  }
121
- return alias.replace(/^@\//, "");
127
+ throw new Error(
128
+ `Could not resolve alias "${alias}" from tsconfig.json. Ensure compilerOptions.paths defines a mapping for it (e.g. "${alias}/*"), then re-run. See https://ui.stera.sh/docs/installation.`
129
+ );
122
130
  }
123
131
  function resolveOutputPath(config, projectRoot, file) {
124
132
  const alias = TYPE_TO_ALIAS[file.type];
@@ -196,7 +204,6 @@ var dim = (text) => wrap(DIM, text);
196
204
  var LOGO = brand("\u273B");
197
205
  var CHECK = brand("\u2713");
198
206
  var CROSS = "\u2717";
199
- var WARN = "!";
200
207
 
201
208
  // src/utils/spinner.ts
202
209
  var FRAMES = ["\u2022", "\u2726", "\u2736", "\u273B"].map(brand);
@@ -354,22 +361,78 @@ var transformImports = (content, ctx) => {
354
361
  );
355
362
  };
356
363
 
357
- // src/utils/detect-globals.ts
358
- import fs4 from "fs";
364
+ // src/utils/write-stera-css.ts
365
+ import fs4 from "fs/promises";
359
366
  import path5 from "path";
360
- function hasGlobalsCss(config, projectRoot) {
361
- const cssPath = path5.resolve(projectRoot, config.css);
362
- if (!fs4.existsSync(cssPath)) {
363
- return false;
367
+ function stripImportLines(content) {
368
+ const lines = content.split("\n");
369
+ const imports = [];
370
+ const kept = [];
371
+ for (const line of lines) {
372
+ if (line.trim().match(/^@import\s+["'].+["']/)) {
373
+ if (!line.trim().match(/^@import\s+["']tailwindcss["']/)) {
374
+ imports.push(line.trim());
375
+ }
376
+ } else {
377
+ kept.push(line);
378
+ }
364
379
  }
365
- const steraPath = path5.join(path5.dirname(cssPath), "stera-ui.css");
366
- return fs4.existsSync(steraPath);
380
+ return { content: kept.join("\n"), imports };
381
+ }
382
+ async function writeSteraUiCss(config, cwd) {
383
+ const item = await fetchRegistryItem("globals");
384
+ const rawContent = item.files?.[0]?.content ?? "";
385
+ const { content, imports } = stripImportLines(rawContent);
386
+ const cssDir = path5.dirname(path5.resolve(cwd, config.css));
387
+ const steraUiPath = path5.join(cssDir, "stera-ui.css");
388
+ await fs4.mkdir(cssDir, { recursive: true });
389
+ await fs4.writeFile(steraUiPath, content, "utf-8");
390
+ return {
391
+ steraUiPath,
392
+ extraImports: imports,
393
+ dependencies: item.dependencies ?? []
394
+ };
367
395
  }
368
396
 
369
397
  // src/commands/add.ts
370
398
  var transforms = [transformImports];
371
399
  async function add(components, options) {
372
400
  const cwd = options.cwd ? path6.resolve(options.cwd) : process.cwd();
401
+ const configPath = findConfigPath(cwd);
402
+ if (!configPath) {
403
+ console.error(` ${CROSS} ${CONFIG_FILE} not found. Run "stera-ui init" first.`);
404
+ process.exit(1);
405
+ }
406
+ const config = loadConfig(cwd);
407
+ if (!config) {
408
+ console.error(` ${CROSS} Failed to parse ${CONFIG_FILE}.`);
409
+ process.exit(1);
410
+ }
411
+ const projectRoot = path6.dirname(configPath);
412
+ if (components.includes("globals")) {
413
+ if (components.length > 1) {
414
+ console.error(
415
+ ` ${CROSS} "stera-ui add globals" refreshes design tokens and cannot be combined with other components.`
416
+ );
417
+ console.error(
418
+ ` Run "stera-ui add globals" and "stera-ui add ${components.filter((c) => c !== "globals").join(" ")}" separately.`
419
+ );
420
+ process.exit(1);
421
+ }
422
+ const spinner2 = createSpinner("Refreshing base styles");
423
+ let result;
424
+ try {
425
+ result = await writeSteraUiCss(config, projectRoot);
426
+ } catch (err) {
427
+ spinner2.fail("Failed to refresh base styles");
428
+ throw err;
429
+ }
430
+ spinner2.succeed(
431
+ `Refreshed ${path6.relative(projectRoot, result.steraUiPath)}`
432
+ );
433
+ console.log("");
434
+ return;
435
+ }
373
436
  const spinner = createSpinner("Fetching registry");
374
437
  try {
375
438
  for (const name of components) {
@@ -384,17 +447,6 @@ async function add(components, options) {
384
447
  spinner.fail("Failed to fetch registry");
385
448
  throw err;
386
449
  }
387
- const configPath = findConfigPath(cwd);
388
- if (!configPath) {
389
- spinner.fail(`${CONFIG_FILE} not found. Run "stera-ui init" first.`);
390
- process.exit(1);
391
- }
392
- const config = loadConfig(cwd);
393
- if (!config) {
394
- spinner.fail(`Failed to parse ${CONFIG_FILE}.`);
395
- process.exit(1);
396
- }
397
- const projectRoot = path6.dirname(configPath);
398
450
  let resolved;
399
451
  try {
400
452
  resolved = await resolveDependencies(components);
@@ -403,14 +455,6 @@ async function add(components, options) {
403
455
  throw err;
404
456
  }
405
457
  spinner.succeed("Registry resolved");
406
- const isAddingGlobals = components.includes("globals");
407
- if (!isAddingGlobals && !hasGlobalsCss(config, projectRoot)) {
408
- console.warn(`
409
- ${WARN} Stera design tokens not found in ${config.css}`);
410
- console.warn(` Components may not render correctly.`);
411
- console.warn(` Run "stera-ui add globals" to install base styles.
412
- `);
413
- }
414
458
  for (const item of resolved) {
415
459
  for (const file of item.files ?? []) {
416
460
  if (file.content) {
@@ -462,13 +506,15 @@ async function add(components, options) {
462
506
  }
463
507
 
464
508
  // src/commands/init.ts
465
- import fs9 from "fs";
509
+ import fs10 from "fs";
510
+ import fsp from "fs/promises";
466
511
  import path10 from "path";
467
512
  import { select } from "@inquirer/prompts";
468
513
 
469
514
  // src/utils/detect-project.ts
470
515
  import fs5 from "fs";
471
516
  import path7 from "path";
517
+ import { loadConfig as loadConfig3 } from "tsconfig-paths";
472
518
  function hasFile(cwd, filenames) {
473
519
  return filenames.some((f) => fs5.existsSync(path7.join(cwd, f)));
474
520
  }
@@ -522,6 +568,24 @@ function detectProject(cwd) {
522
568
  }
523
569
  return { hasSrc, framework, rsc, existingCssFile, layoutFile };
524
570
  }
571
+ function detectAliasPrefix(cwd) {
572
+ let tsConfig;
573
+ try {
574
+ tsConfig = loadConfig3(cwd);
575
+ } catch {
576
+ return null;
577
+ }
578
+ if (tsConfig.resultType !== "success" || Object.keys(tsConfig.paths).length === 0) {
579
+ return null;
580
+ }
581
+ for (const [alias, targets] of Object.entries(tsConfig.paths)) {
582
+ if (targets.includes("./*") || targets.includes("./src/*") || targets.includes("./app/*")) {
583
+ return alias.replace(/\/\*$/, "");
584
+ }
585
+ }
586
+ const first = Object.keys(tsConfig.paths)[0];
587
+ return first ? first.replace(/\/\*$/, "") : null;
588
+ }
525
589
 
526
590
  // src/utils/detect-fonts.ts
527
591
  import fs6 from "fs";
@@ -952,9 +1016,33 @@ function parseTemplateLiteralToCnArgs(templateLiteral) {
952
1016
  return [...staticArgs, ...variableArgs];
953
1017
  }
954
1018
 
1019
+ // src/utils/css-imports.ts
1020
+ import fs9 from "fs/promises";
1021
+ async function insertImportLine(filePath, importStatement) {
1022
+ const content = await fs9.readFile(filePath, "utf-8");
1023
+ const importPath = importStatement.match(/["']([^"']+)["']/)?.[1];
1024
+ if (importPath) {
1025
+ const escaped = importPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1026
+ if (new RegExp(`@import\\s+["']${escaped}["']`).test(content)) return;
1027
+ }
1028
+ const lines = content.split("\n");
1029
+ let lastImportIndex = -1;
1030
+ for (let i = 0; i < lines.length; i++) {
1031
+ if (lines[i].trim().startsWith("@import")) {
1032
+ lastImportIndex = i;
1033
+ }
1034
+ }
1035
+ if (lastImportIndex >= 0) {
1036
+ lines.splice(lastImportIndex + 1, 0, importStatement);
1037
+ } else {
1038
+ lines.unshift(importStatement);
1039
+ }
1040
+ await fs9.writeFile(filePath, lines.join("\n"), "utf-8");
1041
+ }
1042
+
955
1043
  // src/commands/init.ts
956
1044
  var DEFAULT_FONT_NAMES = ["font-geist", "font-geist-mono"];
957
- function buildConfig(project, strategy) {
1045
+ function buildConfig(project, strategy, aliasPrefix) {
958
1046
  let cssPath;
959
1047
  if (project.existingCssFile) {
960
1048
  cssPath = project.existingCssFile;
@@ -968,53 +1056,17 @@ function buildConfig(project, strategy) {
968
1056
  version: 1,
969
1057
  css: cssPath,
970
1058
  aliases: {
971
- components: "@/components",
972
- utils: "@/lib/utils",
973
- ui: "@/components/ui",
974
- lib: "@/lib",
975
- hooks: "@/hooks"
1059
+ components: `${aliasPrefix}/components`,
1060
+ utils: `${aliasPrefix}/lib/utils`,
1061
+ ui: `${aliasPrefix}/components/ui`,
1062
+ lib: `${aliasPrefix}/lib`,
1063
+ hooks: `${aliasPrefix}/hooks`
976
1064
  },
977
1065
  fonts: {
978
1066
  strategy
979
1067
  }
980
1068
  };
981
1069
  }
982
- function stripImportLines(content) {
983
- const lines = content.split("\n");
984
- const imports = [];
985
- const kept = [];
986
- for (const line of lines) {
987
- if (line.trim().match(/^@import\s+["'].+["']/)) {
988
- if (!line.trim().match(/^@import\s+["']tailwindcss["']/)) {
989
- imports.push(line.trim());
990
- }
991
- } else {
992
- kept.push(line);
993
- }
994
- }
995
- return { content: kept.join("\n"), imports };
996
- }
997
- function insertImportLine(filePath, importStatement) {
998
- const content = fs9.readFileSync(filePath, "utf-8");
999
- const importPath = importStatement.match(/["']([^"']+)["']/)?.[1];
1000
- if (importPath) {
1001
- const escaped = importPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1002
- if (new RegExp(`@import\\s+["']${escaped}["']`).test(content)) return;
1003
- }
1004
- const lines = content.split("\n");
1005
- let lastImportIndex = -1;
1006
- for (let i = 0; i < lines.length; i++) {
1007
- if (lines[i].trim().startsWith("@import")) {
1008
- lastImportIndex = i;
1009
- }
1010
- }
1011
- if (lastImportIndex >= 0) {
1012
- lines.splice(lastImportIndex + 1, 0, importStatement);
1013
- } else {
1014
- lines.unshift(importStatement);
1015
- }
1016
- fs9.writeFileSync(filePath, lines.join("\n"), "utf-8");
1017
- }
1018
1070
  async function promptFontStrategy(detected, skipPrompts) {
1019
1071
  const hasExistingFonts = detected.hasNextFont || detected.hasFontFace || detected.hasGoogleFontsLink;
1020
1072
  const defaultStrategy = hasExistingFonts ? "keep-existing" : "stera-default";
@@ -1077,26 +1129,17 @@ async function fetchDefaultFonts() {
1077
1129
  }
1078
1130
  async function executeFontStrategy(strategy, project, detected, config, cwd) {
1079
1131
  if (strategy === "skip") return;
1132
+ const cssDir = path10.dirname(path10.resolve(cwd, config.css));
1133
+ const steraUiPath = path10.join(cssDir, "stera-ui.css");
1080
1134
  if (strategy === "keep-existing") {
1081
- const cssPath = path10.resolve(cwd, config.css);
1082
- const cssDir = path10.dirname(cssPath);
1083
- if (fs9.existsSync(cssPath)) {
1084
- const fontFamily = detected.fontFamilies.length > 0 ? detected.fontFamilies[0] : null;
1085
- const fontValue = detected.nextFontVariable ? `var(${detected.nextFontVariable})` : fontFamily ?? "Geist";
1086
- const patched = patchCssVariables(cssPath, {
1087
- "--font-heading": fontValue,
1088
- "--font-sans": fontValue
1089
- });
1090
- const steraUiPath = path10.join(cssDir, "stera-ui.css");
1091
- if (fs9.existsSync(steraUiPath)) {
1092
- patchCssVariables(steraUiPath, {
1093
- "--font-heading": fontValue,
1094
- "--font-sans": fontValue
1095
- });
1096
- }
1097
- if (patched.length > 0) {
1098
- console.log(` ${CHECK} Updated font variables to use ${fontValue}`);
1099
- }
1135
+ const fontFamily = detected.fontFamilies.length > 0 ? detected.fontFamilies[0] : null;
1136
+ const fontValue = detected.nextFontVariable ? `var(${detected.nextFontVariable})` : fontFamily ?? "Geist";
1137
+ const patched = patchCssVariables(steraUiPath, {
1138
+ "--font-heading": fontValue,
1139
+ "--font-sans": fontValue
1140
+ });
1141
+ if (patched.length > 0) {
1142
+ console.log(` ${CHECK} Updated font variables to use ${fontValue}`);
1100
1143
  }
1101
1144
  return;
1102
1145
  }
@@ -1107,25 +1150,15 @@ async function executeFontStrategy(strategy, project, detected, config, cwd) {
1107
1150
  await installDependencies(fontTree.dependencies, cwd);
1108
1151
  }
1109
1152
  if (fontTree.cssImports.length > 0) {
1110
- const cssPath = path10.resolve(cwd, config.css);
1111
- const cssDir = path10.dirname(cssPath);
1112
- const targetCss = fs9.existsSync(path10.join(cssDir, "stera-ui.css")) ? path10.join(cssDir, "stera-ui.css") : cssPath;
1113
- if (fs9.existsSync(targetCss)) {
1114
- for (const importLine of fontTree.cssImports) {
1115
- insertImportLine(targetCss, importLine);
1116
- }
1117
- console.log(` ${CHECK} Added font imports to ${path10.relative(cwd, targetCss)}`);
1153
+ for (const importLine of fontTree.cssImports) {
1154
+ await insertImportLine(steraUiPath, importLine);
1118
1155
  }
1156
+ console.log(` ${CHECK} Added font imports to ${path10.relative(cwd, steraUiPath)}`);
1119
1157
  }
1120
1158
  if (Object.keys(fontTree.cssVars).length > 0) {
1121
- const cssPath = path10.resolve(cwd, config.css);
1122
- const cssDir = path10.dirname(cssPath);
1123
- const targetCss = fs9.existsSync(path10.join(cssDir, "stera-ui.css")) ? path10.join(cssDir, "stera-ui.css") : cssPath;
1124
- if (fs9.existsSync(targetCss)) {
1125
- const patched = patchCssVariables(targetCss, fontTree.cssVars);
1126
- if (patched.length > 0) {
1127
- console.log(` ${CHECK} Updated font variables in ${path10.relative(cwd, targetCss)}`);
1128
- }
1159
+ const patched = patchCssVariables(steraUiPath, fontTree.cssVars);
1160
+ if (patched.length > 0) {
1161
+ console.log(` ${CHECK} Updated font variables in ${path10.relative(cwd, steraUiPath)}`);
1129
1162
  }
1130
1163
  }
1131
1164
  await updateFonts(fonts, project, cwd);
@@ -1142,13 +1175,26 @@ async function init(options) {
1142
1175
  return;
1143
1176
  }
1144
1177
  const pkgPath = path10.join(cwd, "package.json");
1145
- if (!fs9.existsSync(pkgPath)) {
1178
+ if (!fs10.existsSync(pkgPath)) {
1146
1179
  console.error(
1147
1180
  " Error: No package.json found. Please run this in a project directory."
1148
1181
  );
1149
1182
  process.exit(1);
1150
1183
  }
1151
1184
  const project = detectProject(cwd);
1185
+ const aliasPrefix = detectAliasPrefix(cwd);
1186
+ if (!aliasPrefix) {
1187
+ console.error("");
1188
+ console.error(" Error: No import alias found in your tsconfig.json file.");
1189
+ console.error(
1190
+ ' Stera UI requires a path alias (e.g. "@/*": ["./src/*"]) in compilerOptions.paths.'
1191
+ );
1192
+ console.error(
1193
+ " See https://ui.stera.sh/docs/installation for setup instructions per framework."
1194
+ );
1195
+ console.error("");
1196
+ process.exit(1);
1197
+ }
1152
1198
  const frameworkName = project.framework === "next" ? "Next.js" : project.framework === "vite" ? "Vite" : null;
1153
1199
  const parts = [];
1154
1200
  if (frameworkName) parts.push(frameworkName);
@@ -1162,60 +1208,44 @@ async function init(options) {
1162
1208
  detectedFonts,
1163
1209
  options.yes ?? false
1164
1210
  );
1165
- const config = buildConfig(project, strategy);
1211
+ const config = buildConfig(project, strategy, aliasPrefix);
1166
1212
  const configPath = path10.join(cwd, CONFIG_FILE);
1167
- fs9.writeFileSync(
1213
+ fs10.writeFileSync(
1168
1214
  configPath,
1169
1215
  JSON.stringify(config, null, 2) + "\n",
1170
1216
  "utf-8"
1171
1217
  );
1172
1218
  console.log(` ${CHECK} Created components.json`);
1173
- const cssDir = path10.dirname(path10.resolve(cwd, config.css));
1174
- if (project.existingCssFile) {
1175
- if (hasGlobalsCss(config, cwd)) {
1176
- console.log(
1177
- ` ${CHECK} Stera already initialised ${dim(`(${path10.join(path10.dirname(project.existingCssFile), "stera-ui.css")} exists)`)}`
1178
- );
1179
- } else {
1180
- const fetchSpinner = createSpinner("Fetching base styles");
1181
- let globalsItem;
1182
- try {
1183
- globalsItem = await fetchRegistryItem("globals");
1184
- } catch {
1185
- fetchSpinner.fail("Base styles not found \u2014 skipping");
1186
- globalsItem = null;
1187
- }
1188
- if (globalsItem) {
1189
- fetchSpinner.succeed("Base styles resolved");
1190
- const globalsContent = globalsItem.files?.[0]?.content ?? "";
1191
- const { content: steraUiContent, imports } = stripImportLines(globalsContent);
1192
- const steraUiPath = path10.join(cssDir, "stera-ui.css");
1193
- fs9.mkdirSync(cssDir, { recursive: true });
1194
- fs9.writeFileSync(steraUiPath, steraUiContent, "utf-8");
1195
- console.log(` ${CHECK} Created ${path10.relative(cwd, steraUiPath)}`);
1196
- const existingCssPath = path10.resolve(cwd, project.existingCssFile);
1197
- for (const imp of imports) {
1198
- insertImportLine(existingCssPath, imp);
1199
- }
1200
- insertImportLine(existingCssPath, "@import './stera-ui.css';");
1201
- console.log(` ${CHECK} Added @import to ${project.existingCssFile}`);
1202
- }
1219
+ const fetchSpinner = createSpinner("Fetching base styles");
1220
+ let steraCssResult;
1221
+ try {
1222
+ steraCssResult = await writeSteraUiCss(config, cwd);
1223
+ } catch (err) {
1224
+ fetchSpinner.fail("Failed to fetch base styles");
1225
+ throw err;
1226
+ }
1227
+ fetchSpinner.succeed("Base styles resolved");
1228
+ console.log(
1229
+ ` ${CHECK} Created ${path10.relative(cwd, steraCssResult.steraUiPath)}`
1230
+ );
1231
+ const globalsPath = project.existingCssFile ? path10.resolve(cwd, project.existingCssFile) : path10.resolve(cwd, config.css);
1232
+ if (fs10.existsSync(globalsPath)) {
1233
+ for (const imp of steraCssResult.extraImports) {
1234
+ await insertImportLine(globalsPath, imp);
1203
1235
  }
1236
+ await insertImportLine(globalsPath, '@import "./stera-ui.css";');
1237
+ console.log(
1238
+ ` ${CHECK} Added @import to ${path10.relative(cwd, globalsPath)}`
1239
+ );
1204
1240
  } else {
1205
- const fetchSpinner = createSpinner("Fetching base styles");
1206
- let resolved;
1207
- try {
1208
- resolved = await resolveDependencies(["globals"]);
1209
- } catch (err) {
1210
- fetchSpinner.fail("Failed to fetch base styles");
1211
- throw err;
1212
- }
1213
- fetchSpinner.succeed("Base styles resolved");
1214
- const filtered = resolved.filter((item) => item.name !== "fonts");
1215
- const { written } = await writeComponentFiles(filtered, config, cwd);
1216
- for (const file of written) {
1217
- console.log(` ${CHECK} Created ${file}`);
1218
- }
1241
+ await fsp.mkdir(path10.dirname(globalsPath), { recursive: true });
1242
+ const scaffold = [
1243
+ '@import "tailwindcss";',
1244
+ ...steraCssResult.extraImports,
1245
+ '@import "./stera-ui.css";'
1246
+ ].join("\n") + "\n";
1247
+ await fsp.writeFile(globalsPath, scaffold, "utf-8");
1248
+ console.log(` ${CHECK} Created ${path10.relative(cwd, globalsPath)}`);
1219
1249
  }
1220
1250
  await executeFontStrategy(
1221
1251
  strategy,
@@ -1224,21 +1254,10 @@ async function init(options) {
1224
1254
  config,
1225
1255
  cwd
1226
1256
  );
1227
- const depsSpinner = createSpinner("Fetching package list");
1228
- let globalsForDeps;
1229
- try {
1230
- globalsForDeps = await fetchRegistryItem("globals");
1231
- } catch {
1232
- depsSpinner.fail("Package list unavailable \u2014 skipping");
1233
- globalsForDeps = null;
1234
- }
1235
- if (globalsForDeps) {
1236
- depsSpinner.succeed("Package list resolved");
1237
- if (globalsForDeps.dependencies) {
1238
- const deps = [...new Set(globalsForDeps.dependencies)].sort();
1239
- console.log("");
1240
- await installDependencies(deps, cwd);
1241
- }
1257
+ if (steraCssResult.dependencies.length > 0) {
1258
+ const deps = [...new Set(steraCssResult.dependencies)].sort();
1259
+ console.log("");
1260
+ await installDependencies(deps, cwd);
1242
1261
  }
1243
1262
  console.log("");
1244
1263
  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, detectPackageManager, detectProject, dim, findConfigPath, hasGlobalsCss, installDependencies, loadConfig, massageTreeForFonts, resolveOutputPath, transformImports, updateFonts, writeComponentFiles } from './utils/index.js';
3
+ export { CHECK, CONFIG_FILE, CROSS, 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,
@@ -22,19 +15,28 @@ import {
22
15
  applyTransforms,
23
16
  brand,
24
17
  createSpinner,
18
+ detectAliasPrefix,
25
19
  detectPackageManager,
26
20
  detectProject,
27
21
  dim,
28
22
  findConfigPath,
29
- hasGlobalsCss,
23
+ insertImportLine,
30
24
  installDependencies,
31
25
  loadConfig,
32
26
  massageTreeForFonts,
33
27
  resolveOutputPath,
34
28
  transformImports,
35
29
  updateFonts,
36
- writeComponentFiles
37
- } from "./chunk-26HP67ZM.js";
30
+ writeComponentFiles,
31
+ writeSteraUiCss
32
+ } from "./chunk-67ILD3JO.js";
33
+ import {
34
+ fetchRegistryIndex,
35
+ fetchRegistryItem,
36
+ getAllComponents,
37
+ getComponent,
38
+ resolveDependencies
39
+ } from "./chunk-T3QWOVJR.js";
38
40
  export {
39
41
  CHECK,
40
42
  CONFIG_FILE,
@@ -45,6 +47,7 @@ export {
45
47
  brand,
46
48
  configSchema,
47
49
  createSpinner,
50
+ detectAliasPrefix,
48
51
  detectPackageManager,
49
52
  detectProject,
50
53
  dim,
@@ -53,7 +56,7 @@ export {
53
56
  findConfigPath,
54
57
  getAllComponents,
55
58
  getComponent,
56
- hasGlobalsCss,
59
+ insertImportLine,
57
60
  installDependencies,
58
61
  loadConfig,
59
62
  massageTreeForFonts,
@@ -66,5 +69,6 @@ export {
66
69
  resolveOutputPath,
67
70
  transformImports,
68
71
  updateFonts,
69
- writeComponentFiles
72
+ writeComponentFiles,
73
+ writeSteraUiCss
70
74
  };
@@ -67,12 +67,46 @@ interface ProjectInfo {
67
67
  * - Existing Tailwind CSS entry file
68
68
  */
69
69
  declare function detectProject(cwd: string): ProjectInfo;
70
+ /**
71
+ * Detect the alias prefix (e.g. "@" or "~") from the project's tsconfig.json
72
+ * `compilerOptions.paths`. Mirrors shadcn's approach.
73
+ *
74
+ * Returns null when tsconfig can't be loaded or defines no path mappings —
75
+ * `init` should surface a clear error in that case rather than templating
76
+ * aliases that won't resolve.
77
+ */
78
+ declare function detectAliasPrefix(cwd: string): string | null;
79
+
80
+ interface WriteSteraCssResult {
81
+ /** Absolute path to the written `stera-ui.css`. */
82
+ steraUiPath: string;
83
+ /**
84
+ * Non-tailwindcss `@import` statements stripped from the registry content
85
+ * (e.g. `@import "tw-animate-css";`). Callers are expected to ensure these
86
+ * are present in the user's main globals.css.
87
+ */
88
+ extraImports: string[];
89
+ /** npm packages declared by the globals registry item. */
90
+ dependencies: string[];
91
+ }
92
+ /**
93
+ * Fetch the `globals` registry item and write `stera-ui.css` into the
94
+ * directory that holds the user's configured globals.css. Overwrites
95
+ * unconditionally — the caller decides when this is appropriate (init or
96
+ * an explicit `add globals` refresh).
97
+ *
98
+ * Does NOT touch the user's globals.css.
99
+ */
100
+ declare function writeSteraUiCss(config: SteraConfig, cwd: string): Promise<WriteSteraCssResult>;
70
101
 
71
102
  /**
72
- * Check if Stera has already been initialised in the user's project.
73
- * Looks for the presence of `stera-ui.css` alongside the user's CSS file.
103
+ * Insert an `@import` statement into an existing CSS file, placed after the
104
+ * last existing `@import` line. Prepends to the file if none exist.
105
+ *
106
+ * Idempotent: if an `@import` for the same path already exists, no change
107
+ * is made.
74
108
  */
75
- declare function hasGlobalsCss(config: SteraConfig, projectRoot: string): boolean;
109
+ declare function insertImportLine(filePath: string, importStatement: string): Promise<void>;
76
110
 
77
111
  type PackageManager = "pnpm" | "npm" | "yarn" | "bun";
78
112
  declare function detectPackageManager(cwd: string): Promise<PackageManager>;
@@ -132,4 +166,4 @@ declare function updateFonts(fonts: RegistryFontItem[], project: ProjectInfo, cw
132
166
  silent?: boolean;
133
167
  }): Promise<void>;
134
168
 
135
- export { CHECK, CONFIG_FILE, CROSS, LOGO, type ProjectInfo, type TransformContext, type TransformFn, WARN, type WriteOptions, type WriteResult, applyTransforms, brand, createSpinner, detectPackageManager, detectProject, dim, findConfigPath, hasGlobalsCss, installDependencies, loadConfig, massageTreeForFonts, resolveOutputPath, transformImports, updateFonts, writeComponentFiles };
169
+ export { CHECK, CONFIG_FILE, CROSS, 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 };
@@ -7,19 +7,22 @@ import {
7
7
  applyTransforms,
8
8
  brand,
9
9
  createSpinner,
10
+ detectAliasPrefix,
10
11
  detectPackageManager,
11
12
  detectProject,
12
13
  dim,
13
14
  findConfigPath,
14
- hasGlobalsCss,
15
+ insertImportLine,
15
16
  installDependencies,
16
17
  loadConfig,
17
18
  massageTreeForFonts,
18
19
  resolveOutputPath,
19
20
  transformImports,
20
21
  updateFonts,
21
- writeComponentFiles
22
- } from "../chunk-26HP67ZM.js";
22
+ writeComponentFiles,
23
+ writeSteraUiCss
24
+ } from "../chunk-67ILD3JO.js";
25
+ import "../chunk-T3QWOVJR.js";
23
26
  export {
24
27
  CHECK,
25
28
  CONFIG_FILE,
@@ -29,16 +32,18 @@ export {
29
32
  applyTransforms,
30
33
  brand,
31
34
  createSpinner,
35
+ detectAliasPrefix,
32
36
  detectPackageManager,
33
37
  detectProject,
34
38
  dim,
35
39
  findConfigPath,
36
- hasGlobalsCss,
40
+ insertImportLine,
37
41
  installDependencies,
38
42
  loadConfig,
39
43
  massageTreeForFonts,
40
44
  resolveOutputPath,
41
45
  transformImports,
42
46
  updateFonts,
43
- writeComponentFiles
47
+ writeComponentFiles,
48
+ writeSteraUiCss
44
49
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stera-ui",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "description": "Add Stera UI components to your project.",
6
6
  "license": "MIT",