inai-react-components 1.2.1 → 1.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,4 +1,4 @@
1
- import { type RegistryComponent } from "./status.js";
1
+ import { type RegistryComponent, type ComponentsJsonFile } from "./status.js";
2
2
  export interface AddOptions {
3
3
  /** Overwrite existing files without prompting. */
4
4
  force?: boolean;
@@ -54,13 +54,10 @@ export declare function findInRegistry(registry: {
54
54
  templates: RegistryComponent[];
55
55
  }, name: string, prefix: string | null): RegistryComponent | null;
56
56
  /**
57
- * Determine the target directory for a component based on its type and aliases.
57
+ * Determine the target directory for a component based on its type and the
58
+ * project's aliases + layout from `components.json`.
58
59
  */
59
- export declare function getTargetDir(component: RegistryComponent, aliases: {
60
- components: string;
61
- blocks: string;
62
- templates?: string;
63
- }): string;
60
+ export declare function getTargetDir(component: RegistryComponent, componentsJson: ComponentsJsonFile): string;
64
61
  export declare function runAdd(componentSpec: string, rootDir: string, options?: AddOptions): Promise<AddResult>;
65
62
  export interface AddCommandOptions extends AddOptions {
66
63
  all?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":"AAKA,OAAO,EAGL,KAAK,iBAAiB,EAIvB,MAAM,aAAa,CAAC;AAmBrB,MAAM,WAAW,UAAU;IACzB,kDAAkD;IAClD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,2CAA2C;IAC3C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,kDAAkD;IAClD,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,oDAAoD;IACpD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kDAAkD;IAClD,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,2DAA2D;IAC3D,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,kEAAkE;IAClE,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACjD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG;IAChD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;CACd,CAsCA;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE;IAAE,UAAU,EAAE,iBAAiB,EAAE,CAAC;IAAC,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAAC,SAAS,EAAE,iBAAiB,EAAE,CAAA;CAAE,EAC1G,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GAAG,IAAI,GACpB,iBAAiB,GAAG,IAAI,CAc1B;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,SAAS,EAAE,iBAAiB,EAC5B,OAAO,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAClE,MAAM,CAgBR;AA0JD,wBAAsB,MAAM,CAC1B,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,UAAe,GACvB,OAAO,CAAC,SAAS,CAAC,CAmMpB;AAqCD,MAAM,WAAW,iBAAkB,SAAQ,UAAU;IACnD,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAsB,UAAU,CAC9B,aAAa,CAAC,EAAE,MAAM,EACtB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAqIf"}
1
+ {"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":"AAKA,OAAO,EAGL,KAAK,iBAAiB,EAEtB,KAAK,kBAAkB,EAExB,MAAM,aAAa,CAAC;AAoBrB,MAAM,WAAW,UAAU;IACzB,kDAAkD;IAClD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,2CAA2C;IAC3C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,kDAAkD;IAClD,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,oDAAoD;IACpD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kDAAkD;IAClD,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,2DAA2D;IAC3D,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,kEAAkE;IAClE,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACjD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG;IAChD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;CACd,CAsCA;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE;IAAE,UAAU,EAAE,iBAAiB,EAAE,CAAC;IAAC,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAAC,SAAS,EAAE,iBAAiB,EAAE,CAAA;CAAE,EAC1G,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GAAG,IAAI,GACpB,iBAAiB,GAAG,IAAI,CAc1B;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,SAAS,EAAE,iBAAiB,EAC5B,cAAc,EAAE,kBAAkB,GACjC,MAAM,CAiBR;AA4JD,wBAAsB,MAAM,CAC1B,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,UAAe,GACvB,OAAO,CAAC,SAAS,CAAC,CAmMpB;AAqCD,MAAM,WAAW,iBAAkB,SAAQ,UAAU;IACnD,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAsB,UAAU,CAC9B,aAAa,CAAC,EAAE,MAAM,EACtB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAqIf"}
@@ -10,6 +10,7 @@ import { saveSnapshot, sha256, } from "../utils/snapshot.js";
10
10
  import { fuzzySearchNames } from "../utils/fuzzy-search.js";
11
11
  import { normalizeThemeCss, getLocalTailwindCssTemplate, getLegacyTailwindCssTemplate, } from "./init.js";
12
12
  import { autoInstallDeps } from "../utils/auto-install.js";
13
+ import { resolveLocalDir, getSourceRoot } from "../utils/paths.js";
13
14
  /**
14
15
  * Parse a component spec into its registry namespace, type prefix and name.
15
16
  *
@@ -75,24 +76,26 @@ export function findInRegistry(registry, name, prefix) {
75
76
  return allItems.find((c) => c.name === name) ?? null;
76
77
  }
77
78
  /**
78
- * Determine the target directory for a component based on its type and aliases.
79
+ * Determine the target directory for a component based on its type and the
80
+ * project's aliases + layout from `components.json`.
79
81
  */
80
- export function getTargetDir(component, aliases) {
82
+ export function getTargetDir(component, componentsJson) {
83
+ const { aliases } = componentsJson;
81
84
  const type = component.type.toLowerCase();
82
85
  if (type === "block") {
83
- return aliases.blocks.replace(/^@\//, "src/");
86
+ return resolveLocalDir(aliases.blocks, componentsJson);
84
87
  }
85
88
  if (type === "template") {
86
89
  if (aliases.templates) {
87
- return aliases.templates.replace(/^@\//, "src/");
90
+ return resolveLocalDir(aliases.templates, componentsJson);
88
91
  }
89
92
  // Fallback: derive templates dir from blocks dir
90
- const blocksDir = aliases.blocks.replace(/^@\//, "src/");
93
+ const blocksDir = resolveLocalDir(aliases.blocks, componentsJson);
91
94
  const parent = path.dirname(blocksDir);
92
95
  return path.join(parent, "templates");
93
96
  }
94
97
  // Components, forms, and other types go to the components directory
95
- return aliases.components.replace(/^@\//, "src/");
98
+ return resolveLocalDir(aliases.components, componentsJson);
96
99
  }
97
100
  /**
98
101
  * Install a single component (no dep resolution). Internal helper used by
@@ -101,7 +104,7 @@ export function getTargetDir(component, aliases) {
101
104
  */
102
105
  async function installOne(component, registry, componentsJson, registryDir, rootDir, options) {
103
106
  const name = component.name;
104
- const baseTargetDir = getTargetDir(component, componentsJson.aliases);
107
+ const baseTargetDir = getTargetDir(component, componentsJson);
105
108
  const isMultiFile = component.type === "block" || component.type === "template";
106
109
  const targetDir = isMultiFile ? path.join(baseTargetDir, name) : baseTargetDir;
107
110
  const copiedFiles = [];
@@ -157,12 +160,14 @@ async function installOne(component, registry, componentsJson, registryDir, root
157
160
  }
158
161
  copiedFiles.push(isMultiFile ? `${name}/${fileName}` : fileName);
159
162
  }
160
- // Internal deps (lib/cn, lib/animations, ...)
163
+ // Internal deps (lib/cn, lib/animations, ...) — installed under the
164
+ // project's detected sourceRoot so they match where `init` placed cn.ts.
165
+ const sourceRoot = getSourceRoot(componentsJson);
161
166
  const copiedInternalDeps = [];
162
167
  for (const dep of component.internalDeps) {
163
168
  const depFileName = dep.split("/").pop() + ".ts";
164
169
  const depSourcePath = path.join(registryDir, "packages", "ui", "src", dep + ".ts");
165
- const depTargetPath = path.join(rootDir, "src", "lib", depFileName);
170
+ const depTargetPath = path.join(rootDir, sourceRoot, "lib", depFileName);
166
171
  if (!fs.existsSync(depSourcePath)) {
167
172
  continue;
168
173
  }
@@ -572,10 +577,14 @@ async function changeTheme(rootDir, registryDir) {
572
577
  try {
573
578
  const tokensSrc = path.join(registryDir, "packages", "tokens", "src");
574
579
  const isRemote = !!componentsJson.registrySource;
580
+ const sourceRoot = getSourceRoot(componentsJson);
581
+ const cssRelPath = componentsJson.tailwind?.css ?? `${sourceRoot}/index.css`;
582
+ const cssPath = path.join(rootDir, cssRelPath);
583
+ const themesRelPath = `${sourceRoot}/styles/tokens/themes`;
575
584
  if (isRemote) {
576
585
  // Remote mode: copy and normalize theme file
577
586
  const themesSrc = path.join(tokensSrc, "themes");
578
- const themesDest = path.join(rootDir, "src", "styles", "tokens", "themes");
587
+ const themesDest = path.join(rootDir, sourceRoot, "styles", "tokens", "themes");
579
588
  // Remove old theme file
580
589
  const oldThemeFile = path.join(themesDest, `${currentTheme}.css`);
581
590
  if (fs.existsSync(oldThemeFile)) {
@@ -589,17 +598,17 @@ async function changeTheme(rootDir, registryDir) {
589
598
  const normalizedCss = normalizeThemeCss(rawCss, newTheme);
590
599
  fs.writeFileSync(path.join(themesDest, `${newTheme}.css`), normalizedCss);
591
600
  }
592
- // Regenerate index.css
601
+ // Regenerate CSS entry
593
602
  const font = componentsJson.font ?? { body: "outfit", heading: "outfit" };
594
603
  const radius = componentsJson.radius ?? 0.5;
595
- const cssPath = path.join(rootDir, "src", "index.css");
604
+ fs.mkdirSync(path.dirname(cssPath), { recursive: true });
596
605
  fs.writeFileSync(cssPath, getLocalTailwindCssTemplate(newTheme, font.body, font.heading, radius));
597
606
  }
598
607
  else {
599
- // Local mode: just regenerate index.css with new import
608
+ // Local mode: just regenerate CSS entry with new import
600
609
  const font = componentsJson.font ?? { body: "outfit", heading: "outfit" };
601
610
  const radius = componentsJson.radius ?? 0.5;
602
- const cssPath = path.join(rootDir, "src", "index.css");
611
+ fs.mkdirSync(path.dirname(cssPath), { recursive: true });
603
612
  fs.writeFileSync(cssPath, getLegacyTailwindCssTemplate(newTheme, font.body, font.heading, radius));
604
613
  }
605
614
  // Update components.json
@@ -610,9 +619,9 @@ async function changeTheme(rootDir, registryDir) {
610
619
  spinner.succeed(`Theme switched to "${newTheme}"`);
611
620
  console.log(chalk.green("\nUpdated files:"));
612
621
  console.log(chalk.dim(" - components.json"));
613
- console.log(chalk.dim(" - src/index.css"));
622
+ console.log(chalk.dim(` - ${cssRelPath}`));
614
623
  if (componentsJson.registrySource) {
615
- console.log(chalk.dim(` - src/styles/tokens/themes/${newTheme}.css`));
624
+ console.log(chalk.dim(` - ${themesRelPath}/${newTheme}.css`));
616
625
  }
617
626
  }
618
627
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/commands/diff.ts"],"names":[],"mappings":"AAKA,OAAO,EAGL,KAAK,iBAAiB,EAEvB,MAAM,aAAa,CAAC;AAKrB;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,MAAM,CAqEjF;AAED,wBAAgB,uBAAuB,CACrC,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM,GACpB,iBAAiB,GAAG,IAAI,CAW1B;AA+BD,wBAAsB,OAAO,CAC3B,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC,CAgEjB;AAED,wBAAsB,WAAW,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CActE"}
1
+ {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/commands/diff.ts"],"names":[],"mappings":"AAKA,OAAO,EAGL,KAAK,iBAAiB,EAEvB,MAAM,aAAa,CAAC;AAMrB;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,MAAM,CAqEjF;AAED,wBAAgB,uBAAuB,CACrC,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM,GACpB,iBAAiB,GAAG,IAAI,CAW1B;AA6BD,wBAAsB,OAAO,CAC3B,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC,CAgEjB;AAED,wBAAsB,WAAW,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CActE"}
@@ -5,6 +5,7 @@ import ora from "ora";
5
5
  import { diffLines } from "diff";
6
6
  import { readComponentsJson, readRegistryJson, } from "./status.js";
7
7
  import { resolveRegistryDir } from "../utils/registry-resolver.js";
8
+ import { resolveLocalDir } from "../utils/paths.js";
8
9
  const CONTEXT_LINES = 2;
9
10
  /**
10
11
  * Render a unified-style colored diff between two strings using a real
@@ -96,21 +97,18 @@ export function findComponentInRegistry(registryPath, componentName) {
96
97
  function getLocalAliasDir(component, componentsJson) {
97
98
  const type = component.type.toLowerCase();
98
99
  if (type === "block") {
99
- return componentsJson.aliases.blocks.replace(/^@\//, "src/");
100
+ return resolveLocalDir(componentsJson.aliases.blocks, componentsJson);
100
101
  }
101
102
  if (type === "template") {
102
- // ComponentsJsonFile doesn't officially type `aliases.templates`, but
103
- // it may be present in user config — fall back to blocks/templates.
104
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
105
103
  const templatesAlias = componentsJson.aliases.templates;
106
104
  if (templatesAlias) {
107
- return templatesAlias.replace(/^@\//, "src/");
105
+ return resolveLocalDir(templatesAlias, componentsJson);
108
106
  }
109
- const blocksDir = componentsJson.aliases.blocks.replace(/^@\//, "src/");
107
+ const blocksDir = resolveLocalDir(componentsJson.aliases.blocks, componentsJson);
110
108
  const parent = path.dirname(blocksDir);
111
109
  return path.join(parent, "templates");
112
110
  }
113
- return componentsJson.aliases.components.replace(/^@\//, "src/");
111
+ return resolveLocalDir(componentsJson.aliases.components, componentsJson);
114
112
  }
115
113
  export async function runDiff(componentName, rootDir) {
116
114
  const componentsJson = readComponentsJson(rootDir);
@@ -1,4 +1,5 @@
1
1
  import { type Framework } from "../utils/framework-detect.js";
2
+ import { type ProjectLayout } from "../utils/tsconfig-detect.js";
2
3
  type Theme = string;
3
4
  declare const FONT_PRESETS: readonly [{
4
5
  readonly name: "outfit";
@@ -221,20 +222,21 @@ export interface InitConfig {
221
222
  fontBody: FontPreset;
222
223
  fontHeading: FontPreset;
223
224
  radius: number;
224
- importAlias: string;
225
- utilsAlias: string;
226
225
  tanstackRouter: boolean;
227
226
  tanstackQuery: boolean;
228
227
  tanstackForm: boolean;
229
228
  tanstackTable: boolean;
230
229
  framework?: Framework;
231
230
  cssEntryPoint?: string;
231
+ /** Detected project layout — drives file destinations in `runInit`. */
232
+ layout?: ProjectLayout;
232
233
  }
233
234
  export interface ComponentsJson {
234
235
  $schema: string;
235
236
  registrySource?: string;
236
237
  style: string;
237
238
  framework?: Framework;
239
+ layout?: ProjectLayout;
238
240
  tailwind: {
239
241
  config: string;
240
242
  css: string;
@@ -257,7 +259,7 @@ export interface ComponentsJson {
257
259
  table: boolean;
258
260
  };
259
261
  }
260
- export declare function promptInitConfig(registryDir?: string, framework?: Framework): Promise<InitConfig | null>;
262
+ export declare function promptInitConfig(registryDir?: string, framework?: Framework, layout?: ProjectLayout): Promise<InitConfig | null>;
261
263
  export declare function buildComponentsJson(config: InitConfig, repoUrl?: string): ComponentsJson;
262
264
  export declare function getCnTemplate(): string;
263
265
  export declare function getLocalTailwindCssTemplate(theme: Theme, fontBody: FontPreset, fontHeading: FontPreset, radius: number): string;
@@ -269,6 +271,13 @@ export declare function getLegacyTailwindCssTemplate(theme: Theme, fontBody: Fon
269
271
  * `data-theme` attribute on the HTML element.
270
272
  */
271
273
  export declare function normalizeThemeCss(css: string, theme: string): string;
274
+ /**
275
+ * Copies the i18n base files (context, types, defaults) from the registry
276
+ * to the project's <sourceRoot>/lib/. Required because ~75 components import
277
+ * `useUIMessages` from `./lib/i18n-context.js` — without these files, every
278
+ * subsequent `add` of an i18n-aware component would produce a broken import.
279
+ */
280
+ export declare function copyI18nFiles(registryDir: string | undefined, targetDir: string, sourceRoot: string): string[];
272
281
  export declare function runInit(config: InitConfig, targetDir: string, repoUrl?: string): Promise<{
273
282
  success: boolean;
274
283
  filesCreated: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
@@ -6,6 +6,7 @@ import prompts from "prompts";
6
6
  import { cloneRegistry, getCacheDir } from "../utils/registry-resolver.js";
7
7
  import { THEME_NAMES } from "../utils/themes.js";
8
8
  import { detectFramework, frameworkLabel, getCssEntryPoint, } from "../utils/framework-detect.js";
9
+ import { detectProjectLayout, } from "../utils/tsconfig-detect.js";
9
10
  const AVAILABLE_THEMES = THEME_NAMES;
10
11
  const FONT_PRESETS = [
11
12
  // Sans-serif — geometric & modern
@@ -88,23 +89,24 @@ function discoverThemes(registryDir) {
88
89
  }
89
90
  return [...AVAILABLE_THEMES];
90
91
  }
91
- export async function promptInitConfig(registryDir, framework) {
92
+ export async function promptInitConfig(registryDir, framework, layout) {
92
93
  const themes = discoverThemes(registryDir);
94
+ const sourceRoot = layout?.sourceRoot ?? "src";
93
95
  const defaultCssEntry = framework
94
- ? getCssEntryPoint(framework)
95
- : "src/index.css";
96
+ ? getCssEntryPoint(framework, sourceRoot)
97
+ : `${sourceRoot}/index.css`;
96
98
  const response = await prompts([
97
99
  {
98
100
  type: "text",
99
101
  name: "componentPath",
100
102
  message: "Where would you like to install components?",
101
- initial: "src/components/ui",
103
+ initial: `${sourceRoot}/components/ui`,
102
104
  },
103
105
  {
104
106
  type: "text",
105
107
  name: "blockPath",
106
108
  message: "Where would you like to install blocks?",
107
- initial: "src/components/blocks",
109
+ initial: `${sourceRoot}/components/blocks`,
108
110
  },
109
111
  {
110
112
  type: "select",
@@ -134,18 +136,6 @@ export async function promptInitConfig(registryDir, framework) {
134
136
  choices: RADIUS_OPTIONS.map((r) => ({ title: r.label, value: r.value })),
135
137
  initial: 2,
136
138
  },
137
- {
138
- type: "text",
139
- name: "importAlias",
140
- message: "Configure the import alias for components:",
141
- initial: "@/components/ui",
142
- },
143
- {
144
- type: "text",
145
- name: "utilsAlias",
146
- message: "Configure the import alias for utils:",
147
- initial: "@/lib/utils",
148
- },
149
139
  {
150
140
  type: "confirm",
151
141
  name: "tanstackRouter",
@@ -181,23 +171,28 @@ export async function promptInitConfig(registryDir, framework) {
181
171
  const config = response;
182
172
  config.framework = framework;
183
173
  config.cssEntryPoint = defaultCssEntry;
174
+ config.layout = layout;
184
175
  return config;
185
176
  }
186
177
  export function buildComponentsJson(config, repoUrl) {
178
+ const layout = config.layout ?? {
179
+ sourceRoot: "src",
180
+ aliasPrefix: "@/",
181
+ };
182
+ const utilsAlias = `${layout.aliasPrefix}lib/utils`;
187
183
  const json = {
188
184
  $schema: "https://inai-ui.dev/schema.json",
189
185
  style: "default",
190
186
  framework: config.framework,
187
+ layout,
191
188
  tailwind: {
192
189
  config: "tailwind.config.ts",
193
- css: config.cssEntryPoint ?? "src/index.css",
190
+ css: config.cssEntryPoint ?? `${layout.sourceRoot}/index.css`,
194
191
  },
195
192
  aliases: {
196
- components: config.importAlias,
197
- blocks: config.blockPath.startsWith("src/")
198
- ? `@/${config.blockPath.slice(4)}`
199
- : config.blockPath,
200
- utils: config.utilsAlias,
193
+ components: config.componentPath,
194
+ blocks: config.blockPath,
195
+ utils: utilsAlias,
201
196
  },
202
197
  theme: config.theme,
203
198
  font: {
@@ -295,9 +290,10 @@ function copyDirRecursive(src, dest) {
295
290
  }
296
291
  return copied;
297
292
  }
298
- function copyTokensToProject(registryDir, targetDir, theme) {
293
+ function copyTokensToProject(registryDir, targetDir, theme, sourceRoot) {
299
294
  const tokensSrc = path.join(registryDir, "packages", "tokens", "src");
300
- const tokensDest = path.join(targetDir, "src", "styles", "tokens");
295
+ const tokensDest = path.join(targetDir, sourceRoot, "styles", "tokens");
296
+ const tokensRelBase = `${sourceRoot}/styles/tokens`;
301
297
  const copied = [];
302
298
  if (!fs.existsSync(tokensSrc))
303
299
  return copied;
@@ -307,7 +303,7 @@ function copyTokensToProject(registryDir, targetDir, theme) {
307
303
  const src = path.join(tokensSrc, file);
308
304
  if (fs.existsSync(src)) {
309
305
  fs.copyFileSync(src, path.join(tokensDest, file));
310
- copied.push(`src/styles/tokens/${file}`);
306
+ copied.push(`${tokensRelBase}/${file}`);
311
307
  }
312
308
  }
313
309
  // Copy primitives (color scales referenced by all themes)
@@ -331,7 +327,7 @@ function copyTokensToProject(registryDir, targetDir, theme) {
331
327
  const rawCss = fs.readFileSync(themeSrc, "utf-8");
332
328
  const normalizedCss = normalizeThemeCss(rawCss, theme);
333
329
  fs.writeFileSync(path.join(themesDest, themeFile), normalizedCss);
334
- copied.push(`src/styles/tokens/themes/${themeFile}`);
330
+ copied.push(`${tokensRelBase}/themes/${themeFile}`);
335
331
  }
336
332
  }
337
333
  // Copy presets
@@ -343,14 +339,38 @@ function copyTokensToProject(registryDir, targetDir, theme) {
343
339
  const src = path.join(presetsSrc, file);
344
340
  if (fs.existsSync(src)) {
345
341
  fs.copyFileSync(src, path.join(presetsDest, file));
346
- copied.push(`src/styles/tokens/presets/${file}`);
342
+ copied.push(`${tokensRelBase}/presets/${file}`);
347
343
  }
348
344
  }
349
345
  }
350
346
  return copied;
351
347
  }
348
+ /**
349
+ * Copies the i18n base files (context, types, defaults) from the registry
350
+ * to the project's <sourceRoot>/lib/. Required because ~75 components import
351
+ * `useUIMessages` from `./lib/i18n-context.js` — without these files, every
352
+ * subsequent `add` of an i18n-aware component would produce a broken import.
353
+ */
354
+ export function copyI18nFiles(registryDir, targetDir, sourceRoot) {
355
+ if (!registryDir)
356
+ return [];
357
+ const files = ["i18n-context.tsx", "i18n-types.ts", "i18n-defaults.ts"];
358
+ const srcBase = path.join(registryDir, "packages", "ui", "src", "lib");
359
+ const destBase = path.join(targetDir, sourceRoot, "lib");
360
+ fs.mkdirSync(destBase, { recursive: true });
361
+ const copied = [];
362
+ for (const file of files) {
363
+ const src = path.join(srcBase, file);
364
+ if (!fs.existsSync(src))
365
+ continue;
366
+ fs.copyFileSync(src, path.join(destBase, file));
367
+ copied.push(`${sourceRoot}/lib/${file}`);
368
+ }
369
+ return copied;
370
+ }
352
371
  export async function runInit(config, targetDir, repoUrl) {
353
372
  const filesCreated = [];
373
+ const sourceRoot = config.layout?.sourceRoot ?? "src";
354
374
  // 1. Create components.json
355
375
  const componentsJsonPath = path.join(targetDir, "components.json");
356
376
  const componentsJson = buildComponentsJson(config, repoUrl);
@@ -361,21 +381,24 @@ export async function runInit(config, targetDir, repoUrl) {
361
381
  fs.mkdirSync(componentDir, { recursive: true });
362
382
  const blockDir = path.join(targetDir, config.blockPath);
363
383
  fs.mkdirSync(blockDir, { recursive: true });
364
- // 3. Create cn.ts utility
365
- const utilsDir = path.join(targetDir, "src", "lib");
384
+ // 3. Create cn.ts utility under the detected source root
385
+ const utilsDir = path.join(targetDir, sourceRoot, "lib");
366
386
  fs.mkdirSync(utilsDir, { recursive: true });
367
387
  const cnPath = path.join(utilsDir, "cn.ts");
368
388
  fs.writeFileSync(cnPath, getCnTemplate());
369
- filesCreated.push("src/lib/cn.ts");
389
+ filesCreated.push(`${sourceRoot}/lib/cn.ts`);
370
390
  // 4. Copy tokens and create CSS
371
- const cssRelPath = config.cssEntryPoint ?? "src/index.css";
391
+ const cssRelPath = config.cssEntryPoint ?? `${sourceRoot}/index.css`;
372
392
  const cssPath = path.join(targetDir, cssRelPath);
373
393
  fs.mkdirSync(path.dirname(cssPath), { recursive: true });
374
394
  if (repoUrl) {
375
395
  // Remote mode: copy tokens from cached registry
376
396
  const registryDir = getCacheDir();
377
- const tokenFiles = copyTokensToProject(registryDir, targetDir, config.theme);
397
+ const tokenFiles = copyTokensToProject(registryDir, targetDir, config.theme, sourceRoot);
378
398
  filesCreated.push(...tokenFiles);
399
+ // Copy i18n base files so components importing useUIMessages resolve
400
+ const i18nFiles = copyI18nFiles(registryDir, targetDir, sourceRoot);
401
+ filesCreated.push(...i18nFiles);
379
402
  fs.writeFileSync(cssPath, getLocalTailwindCssTemplate(config.theme, config.fontBody, config.fontHeading, config.radius));
380
403
  }
381
404
  else {
@@ -387,8 +410,11 @@ export async function runInit(config, targetDir, repoUrl) {
387
410
  }
388
411
  export async function initCommand(repoUrl) {
389
412
  console.log(chalk.bold("\nInAI UI - Project Initialization\n"));
390
- const detectedFramework = detectFramework(process.cwd());
413
+ const cwd = process.cwd();
414
+ const detectedFramework = detectFramework(cwd);
415
+ const detectedLayout = detectProjectLayout(cwd);
391
416
  console.log(chalk.dim(`Detected framework: ${frameworkLabel(detectedFramework)}`));
417
+ console.log(chalk.dim(`Detected layout: ${detectedLayout.sourceRoot}/ with alias ${detectedLayout.aliasPrefix}`));
392
418
  if (repoUrl) {
393
419
  const spinner = ora("Cloning component registry...").start();
394
420
  try {
@@ -404,7 +430,7 @@ export async function initCommand(repoUrl) {
404
430
  }
405
431
  }
406
432
  const registryDir = repoUrl ? getCacheDir() : undefined;
407
- const config = await promptInitConfig(registryDir, detectedFramework);
433
+ const config = await promptInitConfig(registryDir, detectedFramework, detectedLayout);
408
434
  if (!config) {
409
435
  console.log(chalk.yellow("\nInitialization cancelled."));
410
436
  process.exit(0);
@@ -1 +1 @@
1
- {"version":3,"file":"remove.d.ts","sourceRoot":"","sources":["../../src/commands/remove.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAgCD,wBAAsB,SAAS,CAC7B,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAgIvB;AAED,wBAAsB,aAAa,CACjC,aAAa,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE,aAAa,GACtB,OAAO,CAAC,IAAI,CAAC,CA+Bf"}
1
+ {"version":3,"file":"remove.d.ts","sourceRoot":"","sources":["../../src/commands/remove.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAgCD,wBAAsB,SAAS,CAC7B,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAgIvB;AAED,wBAAsB,aAAa,CACjC,aAAa,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE,aAAa,GACtB,OAAO,CAAC,IAAI,CAAC,CA+Bf"}
@@ -6,6 +6,7 @@ import prompts from "prompts";
6
6
  import { readComponentsJson, readRegistryJson, } from "./status.js";
7
7
  import { resolveRegistryDir } from "../utils/registry-resolver.js";
8
8
  import { deleteSnapshot } from "../utils/snapshot.js";
9
+ import { resolveLocalDir } from "../utils/paths.js";
9
10
  /**
10
11
  * Find components that depend on the given component name via
11
12
  * `registryDependencies`.
@@ -94,19 +95,19 @@ export async function runRemove(componentName, rootDir, options = {}) {
94
95
  const aliases = componentsJson.aliases;
95
96
  let baseTargetDir;
96
97
  if (type === "block") {
97
- baseTargetDir = aliases.blocks.replace(/^@\//, "src/");
98
+ baseTargetDir = resolveLocalDir(aliases.blocks, componentsJson);
98
99
  }
99
100
  else if (type === "template") {
100
101
  if (aliases.templates) {
101
- baseTargetDir = aliases.templates.replace(/^@\//, "src/");
102
+ baseTargetDir = resolveLocalDir(aliases.templates, componentsJson);
102
103
  }
103
104
  else {
104
- const blocksDir = aliases.blocks.replace(/^@\//, "src/");
105
+ const blocksDir = resolveLocalDir(aliases.blocks, componentsJson);
105
106
  baseTargetDir = path.join(path.dirname(blocksDir), "templates");
106
107
  }
107
108
  }
108
109
  else {
109
- baseTargetDir = aliases.components.replace(/^@\//, "src/");
110
+ baseTargetDir = resolveLocalDir(aliases.components, componentsJson);
110
111
  }
111
112
  if (isMultiFile) {
112
113
  // Delete the entire component directory
@@ -1,6 +1,13 @@
1
+ import type { Framework } from "../utils/framework-detect.js";
1
2
  export interface ComponentsJsonFile {
2
3
  $schema: string;
3
4
  style: string;
5
+ framework?: Framework;
6
+ registrySource?: string;
7
+ layout?: {
8
+ sourceRoot: string;
9
+ aliasPrefix: string;
10
+ };
4
11
  tailwind: {
5
12
  config: string;
6
13
  css: string;
@@ -8,9 +15,9 @@ export interface ComponentsJsonFile {
8
15
  aliases: {
9
16
  components: string;
10
17
  blocks: string;
18
+ templates?: string;
11
19
  utils: string;
12
20
  };
13
- registrySource?: string;
14
21
  theme: string;
15
22
  font?: {
16
23
  body: string;
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE;QACR,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;KACb,CAAC;IACF,OAAO,EAAE;QACP,UAAU,EAAE,MAAM,CAAC;QACnB,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE;QACR,MAAM,EAAE,OAAO,CAAC;QAChB,KAAK,EAAE,OAAO,CAAC;QACf,IAAI,EAAE,OAAO,CAAC;QACd,KAAK,EAAE,OAAO,CAAC;KAChB,CAAC;IACF,mBAAmB,CAAC,EAAE,kBAAkB,EAAE,CAAC;CAC5C;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,iBAAiB,EAAE,CAAC;IAChC,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAC5B,SAAS,EAAE,iBAAiB,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,qBAAqB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/C,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;CACjC;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAW7E;AAED,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAU1E;AAED,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,CAAC;AAE3E,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,eAAe,CAAC;IACxB,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,uDAAuD;IACvD,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,oBAAoB,EAAE,CAAC;CACpC;AAOD;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,kBAAkB,EAClC,QAAQ,EAAE,YAAY,GAAG,IAAI,GAC5B,YAAY,CAmFd;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAkD9D;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,SAAS,CAC7B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,MAAM,CAAC,CA4BjB;AAED,wBAAsB,aAAa,CACjC,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAcf"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"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"}
@@ -3,6 +3,7 @@ import path from "node:path";
3
3
  import chalk from "chalk";
4
4
  import ora from "ora";
5
5
  import { loadSnapshot, sha256 } from "../utils/snapshot.js";
6
+ import { resolveLocalDir } from "../utils/paths.js";
6
7
  export function readComponentsJson(rootDir) {
7
8
  const filePath = path.join(rootDir, "components.json");
8
9
  if (!fs.existsSync(filePath)) {
@@ -41,8 +42,8 @@ function padRight(str, len) {
41
42
  export function inspectStatus(rootDir, componentsJson, registry) {
42
43
  const installed = componentsJson.installedComponents ?? [];
43
44
  const registryVersion = registry?.version ?? "unknown";
44
- const localComponentDir = componentsJson.aliases.components.replace(/^@\//, "src/");
45
- const localBlocksDir = componentsJson.aliases.blocks.replace(/^@\//, "src/");
45
+ const localComponentDir = resolveLocalDir(componentsJson.aliases.components, componentsJson);
46
+ const localBlocksDir = resolveLocalDir(componentsJson.aliases.blocks, componentsJson);
46
47
  const allRegistryItems = registry
47
48
  ? [...registry.components, ...registry.blocks, ...registry.templates]
48
49
  : [];
@@ -1 +1 @@
1
- {"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../src/commands/update.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,2EAA2E;IAC3E,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,OAAO,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb,YAAY,CA4Cd;AAED,wBAAsB,SAAS,CAC7B,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACf,WAAW,UAAQ,GAClB,OAAO,CAAC,YAAY,CAAC,CA+MvB;AAED,wBAAsB,aAAa,CACjC,aAAa,CAAC,EAAE,MAAM,EACtB,OAAO,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,GAC1B,OAAO,CAAC,IAAI,CAAC,CAiDf"}
1
+ {"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../src/commands/update.ts"],"names":[],"mappings":"AAsBA,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,2EAA2E;IAC3E,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,OAAO,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb,YAAY,CA4Cd;AAED,wBAAsB,SAAS,CAC7B,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACf,WAAW,UAAQ,GAClB,OAAO,CAAC,YAAY,CAAC,CA+MvB;AAED,wBAAsB,aAAa,CACjC,aAAa,CAAC,EAAE,MAAM,EACtB,OAAO,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,GAC1B,OAAO,CAAC,IAAI,CAAC,CAiDf"}
@@ -7,6 +7,7 @@ import diff3Merge from "diff3";
7
7
  import { readComponentsJson, readRegistryJson, } from "./status.js";
8
8
  import { findComponentInRegistry, computeDiff } from "./diff.js";
9
9
  import { resolveRegistryDir } from "../utils/registry-resolver.js";
10
+ import { resolveLocalDir } from "../utils/paths.js";
10
11
  import { loadSnapshot, saveSnapshot, sha256, } from "../utils/snapshot.js";
11
12
  /**
12
13
  * Run a 3-way line merge between base (snapshot), mine (local) and theirs
@@ -73,7 +74,7 @@ export async function runUpdate(componentName, rootDir, skipPrompts = false) {
73
74
  }
74
75
  const registry = readRegistryJson(registryPath);
75
76
  const registryVersion = registry?.version ?? "unknown";
76
- const localComponentDir = componentsJson.aliases.components.replace(/^@\//, "src/");
77
+ const localComponentDir = resolveLocalDir(componentsJson.aliases.components, componentsJson);
77
78
  const updatedFiles = [];
78
79
  const cleanMerges = [];
79
80
  const conflictedFiles = [];
package/dist/index.js CHANGED
@@ -17,7 +17,7 @@ const program = new Command();
17
17
  program
18
18
  .name("inai-ui")
19
19
  .description("CLI for InAI UI component library")
20
- .version("1.2.1");
20
+ .version("1.4.0");
21
21
  program
22
22
  .command("init [repo-url]")
23
23
  .description("Initialize InAI UI in your project. Optionally pass a Git repo URL to fetch components remotely.")
@@ -6,10 +6,15 @@ export type Framework = "next-app" | "next-pages" | "vite" | "remix" | "astro" |
6
6
  */
7
7
  export declare function detectFramework(cwd: string): Framework;
8
8
  /**
9
- * Default CSS entry point for a given framework. Used by `inai-ui init`
10
- * to pre-populate the prompt that asks where tailwind/tokens should be
11
- * imported.
9
+ * Default CSS entry point for a given framework, relative to the project
10
+ * root. `sourceRoot` is the project's source directory (usually "src" or
11
+ * "app") as detected from `tsconfig.json`. Paths are derived from it so
12
+ * projects that use `app/` instead of `src/` get correct defaults without
13
+ * manual overrides.
14
+ *
15
+ * Next.js Pages Router and Next.js App Router are special: their CSS
16
+ * entry point is dictated by the framework, not the user's source root.
12
17
  */
13
- export declare function getCssEntryPoint(framework: Framework): string;
18
+ export declare function getCssEntryPoint(framework: Framework, sourceRoot: string): string;
14
19
  export declare function frameworkLabel(framework: Framework): string;
15
20
  //# sourceMappingURL=framework-detect.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"framework-detect.d.ts","sourceRoot":"","sources":["../../src/utils/framework-detect.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,SAAS,GACjB,UAAU,GACV,YAAY,GACZ,MAAM,GACN,OAAO,GACP,OAAO,GACP,gBAAgB,GAChB,SAAS,CAAC;AAEd;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAkDtD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,SAAS,GAAG,MAAM,CAiB7D;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,SAAS,GAAG,MAAM,CAiB3D"}
1
+ {"version":3,"file":"framework-detect.d.ts","sourceRoot":"","sources":["../../src/utils/framework-detect.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,SAAS,GACjB,UAAU,GACV,YAAY,GACZ,MAAM,GACN,OAAO,GACP,OAAO,GACP,gBAAgB,GAChB,SAAS,CAAC;AAEd;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAkDtD;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAmBjF;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,SAAS,GAAG,MAAM,CAiB3D"}
@@ -48,26 +48,33 @@ export function detectFramework(cwd) {
48
48
  return "unknown";
49
49
  }
50
50
  /**
51
- * Default CSS entry point for a given framework. Used by `inai-ui init`
52
- * to pre-populate the prompt that asks where tailwind/tokens should be
53
- * imported.
51
+ * Default CSS entry point for a given framework, relative to the project
52
+ * root. `sourceRoot` is the project's source directory (usually "src" or
53
+ * "app") as detected from `tsconfig.json`. Paths are derived from it so
54
+ * projects that use `app/` instead of `src/` get correct defaults without
55
+ * manual overrides.
56
+ *
57
+ * Next.js Pages Router and Next.js App Router are special: their CSS
58
+ * entry point is dictated by the framework, not the user's source root.
54
59
  */
55
- export function getCssEntryPoint(framework) {
60
+ export function getCssEntryPoint(framework, sourceRoot) {
56
61
  switch (framework) {
57
62
  case "next-app":
63
+ // Next App Router always uses `app/globals.css` regardless of sourceRoot.
58
64
  return "app/globals.css";
59
65
  case "next-pages":
66
+ // Next Pages Router uses top-level `styles/globals.css`.
60
67
  return "styles/globals.css";
61
68
  case "vite":
62
- return "src/index.css";
69
+ return `${sourceRoot}/index.css`;
63
70
  case "remix":
64
- return "app/styles/globals.css";
71
+ return `${sourceRoot}/styles/globals.css`;
65
72
  case "astro":
66
- return "src/styles/globals.css";
73
+ return `${sourceRoot}/styles/globals.css`;
67
74
  case "tanstack-start":
68
- return "src/styles/app.css";
75
+ return `${sourceRoot}/styles/app.css`;
69
76
  default:
70
- return "src/index.css";
77
+ return `${sourceRoot}/index.css`;
71
78
  }
72
79
  }
73
80
  export function frameworkLabel(framework) {
@@ -0,0 +1,26 @@
1
+ import type { ComponentsJsonFile } from "../commands/status.js";
2
+ /**
3
+ * Resolve an alias stored in `components.json` (e.g. "@/components/ui" or
4
+ * "app/components/ui") to an actual directory relative to the project root.
5
+ *
6
+ * Rules:
7
+ * 1. If the value starts with a known alias prefix (`@/`, `~/`, `$/`),
8
+ * substitute it with `layout.sourceRoot` from `components.json`.
9
+ * This preserves backwards compatibility with legacy configs that
10
+ * stored `@/...` aliases and assumed `src/` as the source root.
11
+ * 2. If the value does not start with a known prefix, it's already a
12
+ * filesystem-relative path and is returned unchanged. This is the
13
+ * preferred format going forward — `init` now writes raw paths.
14
+ *
15
+ * When `layout` is missing (pre-1.3.0 `components.json` files), the
16
+ * fallback is `sourceRoot = "src"`, which matches the hardcoded behaviour
17
+ * the CLI shipped with before this refactor.
18
+ */
19
+ export declare function resolveLocalDir(alias: string, componentsJson: Pick<ComponentsJsonFile, "layout">): string;
20
+ /**
21
+ * Get the source root directory to use for project-level files like
22
+ * `lib/cn.ts` and `styles/tokens/`. Falls back to `"src"` for legacy
23
+ * `components.json` files without a `layout` field.
24
+ */
25
+ export declare function getSourceRoot(componentsJson: Pick<ComponentsJsonFile, "layout">): string;
26
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/utils/paths.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAIhE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,EACb,cAAc,EAAE,IAAI,CAAC,kBAAkB,EAAE,QAAQ,CAAC,GACjD,MAAM,CAQR;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,cAAc,EAAE,IAAI,CAAC,kBAAkB,EAAE,QAAQ,CAAC,GACjD,MAAM,CAER"}
@@ -0,0 +1,35 @@
1
+ const KNOWN_PREFIXES = ["@/", "~/", "$/"];
2
+ /**
3
+ * Resolve an alias stored in `components.json` (e.g. "@/components/ui" or
4
+ * "app/components/ui") to an actual directory relative to the project root.
5
+ *
6
+ * Rules:
7
+ * 1. If the value starts with a known alias prefix (`@/`, `~/`, `$/`),
8
+ * substitute it with `layout.sourceRoot` from `components.json`.
9
+ * This preserves backwards compatibility with legacy configs that
10
+ * stored `@/...` aliases and assumed `src/` as the source root.
11
+ * 2. If the value does not start with a known prefix, it's already a
12
+ * filesystem-relative path and is returned unchanged. This is the
13
+ * preferred format going forward — `init` now writes raw paths.
14
+ *
15
+ * When `layout` is missing (pre-1.3.0 `components.json` files), the
16
+ * fallback is `sourceRoot = "src"`, which matches the hardcoded behaviour
17
+ * the CLI shipped with before this refactor.
18
+ */
19
+ export function resolveLocalDir(alias, componentsJson) {
20
+ for (const prefix of KNOWN_PREFIXES) {
21
+ if (alias.startsWith(prefix)) {
22
+ const sourceRoot = componentsJson.layout?.sourceRoot ?? "src";
23
+ return `${sourceRoot}/${alias.slice(prefix.length)}`;
24
+ }
25
+ }
26
+ return alias;
27
+ }
28
+ /**
29
+ * Get the source root directory to use for project-level files like
30
+ * `lib/cn.ts` and `styles/tokens/`. Falls back to `"src"` for legacy
31
+ * `components.json` files without a `layout` field.
32
+ */
33
+ export function getSourceRoot(componentsJson) {
34
+ return componentsJson.layout?.sourceRoot ?? "src";
35
+ }
@@ -0,0 +1,18 @@
1
+ export interface ProjectLayout {
2
+ /** Directory where source code lives, relative to project root. e.g. "app" | "src". */
3
+ sourceRoot: string;
4
+ /** Import alias prefix including trailing slash. e.g. "~/" | "@/" | "$/". */
5
+ aliasPrefix: string;
6
+ }
7
+ /**
8
+ * Detect the project layout (source root directory and alias prefix) by
9
+ * reading `tsconfig.json` in `cwd`. Falls back to filesystem heuristics
10
+ * (looking for `app/` or `src/`) if tsconfig is missing or doesn't declare
11
+ * path aliases.
12
+ *
13
+ * The returned layout drives where the CLI writes component files, cn.ts,
14
+ * tokens, and the CSS entry point so projects using `app/` (Remix,
15
+ * TanStack Start, Next App Router, custom) work without manual config.
16
+ */
17
+ export declare function detectProjectLayout(cwd: string): ProjectLayout;
18
+ //# sourceMappingURL=tsconfig-detect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tsconfig-detect.d.ts","sourceRoot":"","sources":["../../src/utils/tsconfig-detect.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,aAAa;IAC5B,uFAAuF;IACvF,UAAU,EAAE,MAAM,CAAC;IACnB,6EAA6E;IAC7E,WAAW,EAAE,MAAM,CAAC;CACrB;AA0FD;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CA0B9D"}
@@ -0,0 +1,121 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ /**
4
+ * Strip JSONC comments and trailing commas so `JSON.parse` can handle
5
+ * typical `tsconfig.json` files without an extra dependency.
6
+ */
7
+ function parseJsonc(raw) {
8
+ // Remove block comments /* ... */
9
+ let stripped = raw.replace(/\/\*[\s\S]*?\*\//g, "");
10
+ // Remove line comments //
11
+ stripped = stripped.replace(/(^|[^:])\/\/.*$/gm, "$1");
12
+ // Remove trailing commas in objects/arrays
13
+ stripped = stripped.replace(/,(\s*[}\]])/g, "$1");
14
+ return JSON.parse(stripped);
15
+ }
16
+ /**
17
+ * Load a tsconfig file and merge its `extends` chain (up to 3 levels).
18
+ * Only `compilerOptions.paths` and `compilerOptions.baseUrl` are merged —
19
+ * that is the minimum we need to resolve the source root.
20
+ */
21
+ function loadTsconfigChain(tsconfigPath, depth = 0) {
22
+ if (depth > 3)
23
+ return null;
24
+ if (!fs.existsSync(tsconfigPath))
25
+ return null;
26
+ let parsed;
27
+ try {
28
+ const raw = fs.readFileSync(tsconfigPath, "utf-8");
29
+ parsed = parseJsonc(raw);
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ if (parsed.extends) {
35
+ let parentPath = parsed.extends;
36
+ if (!parentPath.endsWith(".json"))
37
+ parentPath += ".json";
38
+ const resolved = path.isAbsolute(parentPath)
39
+ ? parentPath
40
+ : path.resolve(path.dirname(tsconfigPath), parentPath);
41
+ const parent = loadTsconfigChain(resolved, depth + 1);
42
+ if (parent) {
43
+ return {
44
+ compilerOptions: {
45
+ baseUrl: parsed.compilerOptions?.baseUrl ?? parent.compilerOptions?.baseUrl,
46
+ paths: {
47
+ ...(parent.compilerOptions?.paths ?? {}),
48
+ ...(parsed.compilerOptions?.paths ?? {}),
49
+ },
50
+ },
51
+ };
52
+ }
53
+ }
54
+ return parsed;
55
+ }
56
+ /**
57
+ * Parse a tsconfig `paths` entry to extract the alias prefix and target
58
+ * directory. Returns null if the entry is not a simple wildcard-to-directory
59
+ * mapping.
60
+ *
61
+ * Examples:
62
+ * - `"~/*": ["./app/*"]` → { prefix: "~/", sourceRoot: "app" }
63
+ * - `"@/*": ["./src/*"]` → { prefix: "@/", sourceRoot: "src" }
64
+ * - `"@foo/*": ["./libs/foo/*"]` → { prefix: "@foo/", sourceRoot: "libs/foo" }
65
+ */
66
+ function parsePathEntry(key, value) {
67
+ if (!key.endsWith("/*"))
68
+ return null;
69
+ const target = value[0];
70
+ if (!target || !target.endsWith("/*"))
71
+ return null;
72
+ const aliasPrefix = key.slice(0, -1); // "~/*" → "~/"
73
+ let sourceRoot = target.slice(0, -2); // "./app/*" → "./app"
74
+ // Strip leading "./"
75
+ if (sourceRoot.startsWith("./"))
76
+ sourceRoot = sourceRoot.slice(2);
77
+ // Reject absolute paths and paths that escape the project
78
+ if (path.isAbsolute(sourceRoot) || sourceRoot.startsWith(".."))
79
+ return null;
80
+ if (!sourceRoot)
81
+ return null;
82
+ return { sourceRoot, aliasPrefix };
83
+ }
84
+ /**
85
+ * Detect the project layout (source root directory and alias prefix) by
86
+ * reading `tsconfig.json` in `cwd`. Falls back to filesystem heuristics
87
+ * (looking for `app/` or `src/`) if tsconfig is missing or doesn't declare
88
+ * path aliases.
89
+ *
90
+ * The returned layout drives where the CLI writes component files, cn.ts,
91
+ * tokens, and the CSS entry point so projects using `app/` (Remix,
92
+ * TanStack Start, Next App Router, custom) work without manual config.
93
+ */
94
+ export function detectProjectLayout(cwd) {
95
+ const tsconfigPath = path.join(cwd, "tsconfig.json");
96
+ const tsconfig = loadTsconfigChain(tsconfigPath);
97
+ const paths = tsconfig?.compilerOptions?.paths ?? {};
98
+ // Prefer an entry whose source root directory actually exists on disk.
99
+ // This handles tsconfigs that declare multiple aliases.
100
+ let firstParsed = null;
101
+ for (const [key, value] of Object.entries(paths)) {
102
+ const parsed = parsePathEntry(key, value);
103
+ if (!parsed)
104
+ continue;
105
+ if (!firstParsed)
106
+ firstParsed = parsed;
107
+ if (fs.existsSync(path.join(cwd, parsed.sourceRoot))) {
108
+ return parsed;
109
+ }
110
+ }
111
+ if (firstParsed)
112
+ return firstParsed;
113
+ // No usable alias in tsconfig — fall back to directory heuristics.
114
+ if (fs.existsSync(path.join(cwd, "app"))) {
115
+ return { sourceRoot: "app", aliasPrefix: "~/" };
116
+ }
117
+ if (fs.existsSync(path.join(cwd, "src"))) {
118
+ return { sourceRoot: "src", aliasPrefix: "@/" };
119
+ }
120
+ return { sourceRoot: "src", aliasPrefix: "@/" };
121
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inai-react-components",
3
- "version": "1.2.1",
3
+ "version": "1.4.0",
4
4
  "description": "CLI for InAI UI component library — install components from a private registry into your React project",
5
5
  "private": false,
6
6
  "type": "module",