inai-react-components 1.2.1 → 1.4.1
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.
- package/dist/commands/add.d.ts +4 -7
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +60 -32
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +5 -7
- package/dist/commands/init.d.ts +12 -3
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +61 -35
- package/dist/commands/remove.d.ts.map +1 -1
- package/dist/commands/remove.js +5 -4
- package/dist/commands/status.d.ts +8 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +3 -2
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +2 -1
- package/dist/index.js +1 -1
- package/dist/utils/framework-detect.d.ts +9 -4
- package/dist/utils/framework-detect.d.ts.map +1 -1
- package/dist/utils/framework-detect.js +16 -9
- package/dist/utils/paths.d.ts +26 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +35 -0
- package/dist/utils/tsconfig-detect.d.ts +18 -0
- package/dist/utils/tsconfig-detect.d.ts.map +1 -0
- package/dist/utils/tsconfig-detect.js +121 -0
- package/package.json +1 -1
package/dist/commands/add.d.ts
CHANGED
|
@@ -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
|
|
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,
|
|
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,
|
|
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"}
|
package/dist/commands/add.js
CHANGED
|
@@ -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
|
|
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,
|
|
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
|
|
86
|
+
return resolveLocalDir(aliases.blocks, componentsJson);
|
|
84
87
|
}
|
|
85
88
|
if (type === "template") {
|
|
86
89
|
if (aliases.templates) {
|
|
87
|
-
return aliases.templates
|
|
90
|
+
return resolveLocalDir(aliases.templates, componentsJson);
|
|
88
91
|
}
|
|
89
92
|
// Fallback: derive templates dir from blocks dir
|
|
90
|
-
const blocksDir = aliases.blocks
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
601
|
+
// Regenerate CSS entry
|
|
593
602
|
const font = componentsJson.font ?? { body: "outfit", heading: "outfit" };
|
|
594
603
|
const radius = componentsJson.radius ?? 0.5;
|
|
595
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
622
|
+
console.log(chalk.dim(` - ${cssRelPath}`));
|
|
614
623
|
if (componentsJson.registrySource) {
|
|
615
|
-
console.log(chalk.dim(` -
|
|
624
|
+
console.log(chalk.dim(` - ${themesRelPath}/${newTheme}.css`));
|
|
616
625
|
}
|
|
617
626
|
}
|
|
618
627
|
catch (error) {
|
|
@@ -641,34 +650,53 @@ async function installAll(options) {
|
|
|
641
650
|
...registry.templates,
|
|
642
651
|
];
|
|
643
652
|
console.log(chalk.bold(`\nInstalling all ${allItems.length} components, blocks, and templates...\n`));
|
|
644
|
-
|
|
645
|
-
|
|
653
|
+
// Track what's been installed across the loop. Because `runAdd` resolves
|
|
654
|
+
// transitive dependencies (e.g. `alert` pulls in `button`), a component
|
|
655
|
+
// scheduled later in the iteration may already be on disk by the time we
|
|
656
|
+
// reach it. We refresh this set from `components.json` after every
|
|
657
|
+
// `runAdd` call so `isInstalled` stays accurate across the bulk install.
|
|
658
|
+
const installedNames = new Set((componentsJson.installedComponents ?? []).map((c) => c.name));
|
|
646
659
|
const allNpmDeps = new Set();
|
|
647
660
|
let addedCount = 0;
|
|
648
|
-
let
|
|
661
|
+
let skippedCount = 0;
|
|
649
662
|
for (const item of allItems) {
|
|
650
|
-
|
|
651
|
-
|
|
663
|
+
// Skip components that were already installed by a previous iteration
|
|
664
|
+
// as a transitive dep — avoids redundant file writes and prevents the
|
|
665
|
+
// overwrite prompt from hanging the bulk install.
|
|
666
|
+
if (installedNames.has(item.name)) {
|
|
667
|
+
skippedCount++;
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
652
670
|
const prefix = item.type === "block" ? "block/" : item.type === "template" ? "template/" : "";
|
|
653
|
-
const spinner = ora(
|
|
671
|
+
const spinner = ora(`Adding "${prefix}${item.name}"...`).start();
|
|
654
672
|
try {
|
|
655
673
|
const spec = prefix ? `${prefix}${item.name}` : item.name;
|
|
674
|
+
// Pause the spinner defensively — any stray `prompts()` call inside
|
|
675
|
+
// `runAdd` would otherwise deadlock because ora holds stdout while
|
|
676
|
+
// the prompt reads stdin.
|
|
677
|
+
spinner.stop();
|
|
656
678
|
const result = await runAdd(spec, rootDir, {
|
|
679
|
+
// During a bulk install, default to skipping files that already
|
|
680
|
+
// exist (from transitive deps or user edits) instead of prompting
|
|
681
|
+
// for overwrite. `--force` still overrides this if the user wants
|
|
682
|
+
// to refresh everything.
|
|
657
683
|
force: options?.force,
|
|
658
684
|
dryRun: options?.dryRun,
|
|
659
|
-
skipExisting: options?.skipExisting,
|
|
685
|
+
skipExisting: options?.skipExisting ?? !options?.force,
|
|
660
686
|
});
|
|
661
687
|
if (!result.added) {
|
|
662
688
|
spinner.fail(result.message);
|
|
663
689
|
continue;
|
|
664
690
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
691
|
+
spinner.succeed(`Added "${prefix}${item.name}"`);
|
|
692
|
+
addedCount++;
|
|
693
|
+
// Refresh the installed-names cache so the next iteration sees any
|
|
694
|
+
// transitive deps just installed by `runAdd`.
|
|
695
|
+
const freshJson = readComponentsJson(rootDir);
|
|
696
|
+
if (freshJson?.installedComponents) {
|
|
697
|
+
for (const entry of freshJson.installedComponents) {
|
|
698
|
+
installedNames.add(entry.name);
|
|
699
|
+
}
|
|
672
700
|
}
|
|
673
701
|
for (const dep of result.npmDeps) {
|
|
674
702
|
allNpmDeps.add(dep);
|
|
@@ -685,8 +713,8 @@ async function installAll(options) {
|
|
|
685
713
|
if (addedCount > 0) {
|
|
686
714
|
console.log(chalk.green(` ${addedCount} component${addedCount > 1 ? "s" : ""} added`));
|
|
687
715
|
}
|
|
688
|
-
if (
|
|
689
|
-
console.log(chalk.
|
|
716
|
+
if (skippedCount > 0) {
|
|
717
|
+
console.log(chalk.dim(` ${skippedCount} component${skippedCount > 1 ? "s" : ""} skipped (already installed as dependency)`));
|
|
690
718
|
}
|
|
691
719
|
if (allNpmDeps.size > 0) {
|
|
692
720
|
console.log(chalk.yellow("\nInstall required npm dependencies:"));
|
|
@@ -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;
|
|
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"}
|
package/dist/commands/diff.js
CHANGED
|
@@ -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
|
|
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
|
|
105
|
+
return resolveLocalDir(templatesAlias, componentsJson);
|
|
108
106
|
}
|
|
109
|
-
const blocksDir = componentsJson.aliases.blocks
|
|
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
|
|
111
|
+
return resolveLocalDir(componentsJson.aliases.components, componentsJson);
|
|
114
112
|
}
|
|
115
113
|
export async function runDiff(componentName, rootDir) {
|
|
116
114
|
const componentsJson = readComponentsJson(rootDir);
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -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":"AAOA,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/init.js
CHANGED
|
@@ -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
|
-
:
|
|
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:
|
|
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:
|
|
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 ??
|
|
190
|
+
css: config.cssEntryPoint ?? `${layout.sourceRoot}/index.css`,
|
|
194
191
|
},
|
|
195
192
|
aliases: {
|
|
196
|
-
components: config.
|
|
197
|
-
blocks: config.blockPath
|
|
198
|
-
|
|
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,
|
|
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(
|
|
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(
|
|
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(
|
|
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,
|
|
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(
|
|
389
|
+
filesCreated.push(`${sourceRoot}/lib/cn.ts`);
|
|
370
390
|
// 4. Copy tokens and create CSS
|
|
371
|
-
const cssRelPath = config.cssEntryPoint ??
|
|
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
|
|
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":"
|
|
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"}
|
package/dist/commands/remove.js
CHANGED
|
@@ -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
|
|
98
|
+
baseTargetDir = resolveLocalDir(aliases.blocks, componentsJson);
|
|
98
99
|
}
|
|
99
100
|
else if (type === "template") {
|
|
100
101
|
if (aliases.templates) {
|
|
101
|
-
baseTargetDir = aliases.templates
|
|
102
|
+
baseTargetDir = resolveLocalDir(aliases.templates, componentsJson);
|
|
102
103
|
}
|
|
103
104
|
else {
|
|
104
|
-
const blocksDir = aliases.blocks
|
|
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
|
|
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,
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/status.js
CHANGED
|
@@ -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
|
|
45
|
-
const localBlocksDir = componentsJson.aliases.blocks
|
|
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":"
|
|
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"}
|
package/dist/commands/update.js
CHANGED
|
@@ -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
|
|
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.
|
|
20
|
+
.version("1.4.1");
|
|
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
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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
|
|
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
|
|
52
|
-
*
|
|
53
|
-
*
|
|
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
|
|
69
|
+
return `${sourceRoot}/index.css`;
|
|
63
70
|
case "remix":
|
|
64
|
-
return
|
|
71
|
+
return `${sourceRoot}/styles/globals.css`;
|
|
65
72
|
case "astro":
|
|
66
|
-
return
|
|
73
|
+
return `${sourceRoot}/styles/globals.css`;
|
|
67
74
|
case "tanstack-start":
|
|
68
|
-
return
|
|
75
|
+
return `${sourceRoot}/styles/app.css`;
|
|
69
76
|
default:
|
|
70
|
-
return
|
|
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