inai-react-components 0.1.7 → 1.2.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.
Files changed (62) hide show
  1. package/README.md +80 -0
  2. package/dist/commands/add.d.ts +38 -5
  3. package/dist/commands/add.d.ts.map +1 -1
  4. package/dist/commands/add.js +391 -80
  5. package/dist/commands/diff.d.ts +6 -0
  6. package/dist/commands/diff.d.ts.map +1 -1
  7. package/dist/commands/diff.js +92 -20
  8. package/dist/commands/init.d.ts +6 -3
  9. package/dist/commands/init.d.ts.map +1 -1
  10. package/dist/commands/init.js +33 -36
  11. package/dist/commands/mcp.d.ts +123 -0
  12. package/dist/commands/mcp.d.ts.map +1 -0
  13. package/dist/commands/mcp.js +289 -0
  14. package/dist/commands/migrate.d.ts +41 -0
  15. package/dist/commands/migrate.d.ts.map +1 -0
  16. package/dist/commands/migrate.js +139 -0
  17. package/dist/commands/registries.d.ts +46 -0
  18. package/dist/commands/registries.d.ts.map +1 -0
  19. package/dist/commands/registries.js +152 -0
  20. package/dist/commands/remove.d.ts +11 -0
  21. package/dist/commands/remove.d.ts.map +1 -0
  22. package/dist/commands/remove.js +170 -0
  23. package/dist/commands/schema.d.ts +16 -0
  24. package/dist/commands/schema.d.ts.map +1 -0
  25. package/dist/commands/schema.js +32 -0
  26. package/dist/commands/status.d.ts +32 -3
  27. package/dist/commands/status.d.ts.map +1 -1
  28. package/dist/commands/status.js +148 -25
  29. package/dist/commands/theme.d.ts.map +1 -1
  30. package/dist/commands/theme.js +3 -37
  31. package/dist/commands/update.d.ts +18 -1
  32. package/dist/commands/update.d.ts.map +1 -1
  33. package/dist/commands/update.js +195 -5
  34. package/dist/index.js +76 -5
  35. package/dist/schemas/registry-config.schema.json +62 -0
  36. package/dist/schemas/registry-item.schema.json +63 -0
  37. package/dist/schemas/registry.schema.json +15 -0
  38. package/dist/types/registry.d.ts +50 -0
  39. package/dist/types/registry.d.ts.map +1 -0
  40. package/dist/types/registry.js +10 -0
  41. package/dist/utils/auto-install.d.ts +11 -0
  42. package/dist/utils/auto-install.d.ts.map +1 -0
  43. package/dist/utils/auto-install.js +29 -0
  44. package/dist/utils/framework-detect.d.ts +15 -0
  45. package/dist/utils/framework-detect.d.ts.map +1 -0
  46. package/dist/utils/framework-detect.js +90 -0
  47. package/dist/utils/fuzzy-search.d.ts +16 -0
  48. package/dist/utils/fuzzy-search.d.ts.map +1 -0
  49. package/dist/utils/fuzzy-search.js +67 -0
  50. package/dist/utils/registry-config.d.ts +27 -0
  51. package/dist/utils/registry-config.d.ts.map +1 -0
  52. package/dist/utils/registry-config.js +94 -0
  53. package/dist/utils/registry-resolver.d.ts +26 -0
  54. package/dist/utils/registry-resolver.d.ts.map +1 -1
  55. package/dist/utils/registry-resolver.js +134 -10
  56. package/dist/utils/snapshot.d.ts +20 -0
  57. package/dist/utils/snapshot.d.ts.map +1 -0
  58. package/dist/utils/snapshot.js +32 -0
  59. package/dist/utils/themes.d.ts +17 -0
  60. package/dist/utils/themes.d.ts.map +1 -0
  61. package/dist/utils/themes.js +49 -0
  62. package/package.json +9 -3
@@ -0,0 +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"}
@@ -0,0 +1,170 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import chalk from "chalk";
4
+ import ora from "ora";
5
+ import prompts from "prompts";
6
+ import { readComponentsJson, readRegistryJson, } from "./status.js";
7
+ import { resolveRegistryDir } from "../utils/registry-resolver.js";
8
+ import { deleteSnapshot } from "../utils/snapshot.js";
9
+ /**
10
+ * Find components that depend on the given component name via
11
+ * `registryDependencies`.
12
+ */
13
+ function findDependents(componentName, installedNames, registryPath) {
14
+ const registry = readRegistryJson(registryPath);
15
+ if (!registry)
16
+ return [];
17
+ const allItems = [
18
+ ...registry.components,
19
+ ...registry.blocks,
20
+ ...registry.templates,
21
+ ];
22
+ const dependents = [];
23
+ for (const item of allItems) {
24
+ if (!installedNames.includes(item.name))
25
+ continue;
26
+ if (item.name === componentName)
27
+ continue;
28
+ const deps = item.registryDependencies ?? [];
29
+ if (deps.includes(componentName)) {
30
+ dependents.push(item.name);
31
+ }
32
+ }
33
+ return dependents;
34
+ }
35
+ export async function runRemove(componentName, rootDir, options = {}) {
36
+ const componentsJson = readComponentsJson(rootDir);
37
+ if (!componentsJson) {
38
+ return {
39
+ removed: false,
40
+ message: "No components.json found. Run `inai-ui init` first to initialize your project.",
41
+ deletedFiles: [],
42
+ };
43
+ }
44
+ const installed = componentsJson.installedComponents ?? [];
45
+ const entry = installed.find((c) => c.name === componentName);
46
+ if (!entry) {
47
+ return {
48
+ removed: false,
49
+ message: `Component "${componentName}" is not installed.`,
50
+ deletedFiles: [],
51
+ };
52
+ }
53
+ // Check for dependents
54
+ const registryDir = resolveRegistryDir(rootDir);
55
+ const registryPath = path.join(registryDir, "registry.json");
56
+ const installedNames = installed.map((c) => c.name);
57
+ const dependents = findDependents(componentName, installedNames, registryPath);
58
+ if (dependents.length > 0 && !options.force) {
59
+ const depList = dependents.join(", ");
60
+ console.log(chalk.yellow(`\nWarning: The following installed components depend on "${componentName}": ${depList}`));
61
+ if (process.stdin.isTTY) {
62
+ const res = await prompts({
63
+ type: "confirm",
64
+ name: "proceed",
65
+ message: `Remove "${componentName}" anyway?`,
66
+ initial: false,
67
+ });
68
+ if (!res.proceed) {
69
+ return {
70
+ removed: false,
71
+ message: "Removal cancelled.",
72
+ deletedFiles: [],
73
+ };
74
+ }
75
+ }
76
+ else {
77
+ return {
78
+ removed: false,
79
+ message: `Component "${componentName}" has dependents (${depList}). Use --force to remove anyway.`,
80
+ deletedFiles: [],
81
+ };
82
+ }
83
+ }
84
+ // Determine the files to delete
85
+ const registry = readRegistryJson(registryPath);
86
+ const allItems = registry
87
+ ? [...registry.components, ...registry.blocks, ...registry.templates]
88
+ : [];
89
+ const regEntry = allItems.find((c) => c.name === componentName);
90
+ const deletedFiles = [];
91
+ if (regEntry) {
92
+ const type = regEntry.type.toLowerCase();
93
+ const isMultiFile = type === "block" || type === "template";
94
+ const aliases = componentsJson.aliases;
95
+ let baseTargetDir;
96
+ if (type === "block") {
97
+ baseTargetDir = aliases.blocks.replace(/^@\//, "src/");
98
+ }
99
+ else if (type === "template") {
100
+ if (aliases.templates) {
101
+ baseTargetDir = aliases.templates.replace(/^@\//, "src/");
102
+ }
103
+ else {
104
+ const blocksDir = aliases.blocks.replace(/^@\//, "src/");
105
+ baseTargetDir = path.join(path.dirname(blocksDir), "templates");
106
+ }
107
+ }
108
+ else {
109
+ baseTargetDir = aliases.components.replace(/^@\//, "src/");
110
+ }
111
+ if (isMultiFile) {
112
+ // Delete the entire component directory
113
+ const componentDir = path.join(rootDir, baseTargetDir, componentName);
114
+ if (fs.existsSync(componentDir)) {
115
+ fs.rmSync(componentDir, { recursive: true, force: true });
116
+ deletedFiles.push(path.relative(rootDir, componentDir) + "/");
117
+ }
118
+ }
119
+ else {
120
+ // Delete individual files
121
+ for (const filePath of regEntry.files) {
122
+ const fileName = path.basename(filePath);
123
+ const localPath = path.join(rootDir, baseTargetDir, fileName);
124
+ if (fs.existsSync(localPath)) {
125
+ fs.unlinkSync(localPath);
126
+ deletedFiles.push(path.relative(rootDir, localPath));
127
+ }
128
+ }
129
+ }
130
+ }
131
+ // Remove snapshot
132
+ deleteSnapshot(rootDir, componentName);
133
+ // Update components.json
134
+ componentsJson.installedComponents = installed.filter((c) => c.name !== componentName);
135
+ const componentsJsonPath = path.join(rootDir, "components.json");
136
+ fs.writeFileSync(componentsJsonPath, JSON.stringify(componentsJson, null, 2) + "\n");
137
+ return {
138
+ removed: true,
139
+ message: `Component "${componentName}" removed successfully.`,
140
+ deletedFiles,
141
+ };
142
+ }
143
+ export async function removeCommand(componentName, options) {
144
+ const spinner = ora(`Removing "${componentName}"...`).start();
145
+ try {
146
+ const rootDir = process.cwd();
147
+ spinner.stop();
148
+ const result = await runRemove(componentName, rootDir, {
149
+ force: options?.force,
150
+ });
151
+ if (!result.removed) {
152
+ console.log(chalk.yellow(result.message));
153
+ return;
154
+ }
155
+ console.log(chalk.green(result.message));
156
+ if (result.deletedFiles.length > 0) {
157
+ console.log(chalk.green("\nDeleted files:"));
158
+ for (const file of result.deletedFiles) {
159
+ console.log(chalk.dim(` - ${file}`));
160
+ }
161
+ }
162
+ console.log(chalk.dim("\nUpdated components.json"));
163
+ }
164
+ catch (error) {
165
+ spinner.fail("Removal failed.");
166
+ const message = error instanceof Error ? error.message : String(error);
167
+ console.error(chalk.red(`\nError: ${message}`));
168
+ process.exit(1);
169
+ }
170
+ }
@@ -0,0 +1,16 @@
1
+ export type SchemaKind = "item" | "registry" | "registry-config";
2
+ export interface SchemaOptions {
3
+ kind?: SchemaKind;
4
+ output?: "stdout" | string;
5
+ }
6
+ /**
7
+ * Print or write the JSON Schema for the InAI registry.
8
+ *
9
+ * @example
10
+ * inai-ui schema # prints registry-item.schema.json
11
+ * inai-ui schema --kind registry # prints registry.schema.json
12
+ * inai-ui schema --kind registry-config # prints registry-config.schema.json (M3 federated registries)
13
+ * inai-ui schema --output ./my-schema.json
14
+ */
15
+ export declare function runSchema(options?: SchemaOptions): Promise<void>;
16
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/commands/schema.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,UAAU,GAAG,iBAAiB,CAAC;AAEjE,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,MAAM,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;CAC5B;AAED;;;;;;;;GAQG;AACH,wBAAsB,SAAS,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmB1E"}
@@ -0,0 +1,32 @@
1
+ import { readFile, writeFile } from "node:fs/promises";
2
+ import { fileURLToPath } from "node:url";
3
+ import { dirname, join } from "node:path";
4
+ import chalk from "chalk";
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = dirname(__filename);
7
+ /**
8
+ * Print or write the JSON Schema for the InAI registry.
9
+ *
10
+ * @example
11
+ * inai-ui schema # prints registry-item.schema.json
12
+ * inai-ui schema --kind registry # prints registry.schema.json
13
+ * inai-ui schema --kind registry-config # prints registry-config.schema.json (M3 federated registries)
14
+ * inai-ui schema --output ./my-schema.json
15
+ */
16
+ export async function runSchema(options = {}) {
17
+ const { kind = "item", output = "stdout" } = options;
18
+ const filename = kind === "registry"
19
+ ? "registry.schema.json"
20
+ : kind === "registry-config"
21
+ ? "registry-config.schema.json"
22
+ : "registry-item.schema.json";
23
+ // dist/commands/schema.js → ../schemas/<file>
24
+ const schemaPath = join(__dirname, "..", "schemas", filename);
25
+ const content = await readFile(schemaPath, "utf8");
26
+ if (output === "stdout") {
27
+ console.log(content);
28
+ return;
29
+ }
30
+ await writeFile(output, content, "utf8");
31
+ console.log(chalk.green(`✓ Schema written to ${output}`));
32
+ }
@@ -46,10 +46,39 @@ export interface RegistryComponent {
46
46
  tokenUsage: string[];
47
47
  tanstackCompatibility: Record<string, boolean>;
48
48
  fieldWrappers?: string[];
49
+ /**
50
+ * Names of other registry components this component depends on. Used by
51
+ * `inai-ui add` to install transitive deps automatically. Defaults to [].
52
+ */
53
+ registryDependencies?: string[];
49
54
  }
50
55
  export declare function readComponentsJson(rootDir: string): ComponentsJsonFile | null;
51
56
  export declare function readRegistryJson(registryPath: string): RegistryJson | null;
52
- export declare function formatStatusTable(installedComponents: InstalledComponent[], registryVersion: string): string;
53
- export declare function runStatus(rootDir: string): Promise<string>;
54
- export declare function statusCommand(): Promise<void>;
57
+ export type ComponentHealth = "HEALTHY" | "DRIFT" | "OUTDATED" | "MISSING";
58
+ export interface ComponentStatusEntry {
59
+ name: string;
60
+ installedVersion: string;
61
+ registryVersion: string;
62
+ status: ComponentHealth;
63
+ /** Files that drifted vs the snapshot (populated only for DRIFT). */
64
+ driftedFiles?: string[];
65
+ /** Files missing from disk (populated for MISSING). */
66
+ missingFiles?: string[];
67
+ }
68
+ export interface StatusReport {
69
+ registryVersion: string;
70
+ components: ComponentStatusEntry[];
71
+ }
72
+ /**
73
+ * Inspect every installed component, comparing disk state against the
74
+ * saved snapshot and the registry version. This is the machine-readable
75
+ * side of the `status` command — `formatStatusTable` renders it.
76
+ */
77
+ export declare function inspectStatus(rootDir: string, componentsJson: ComponentsJsonFile, registry: RegistryJson | null): StatusReport;
78
+ export declare function formatStatusTable(report: StatusReport): string;
79
+ export interface RunStatusOptions {
80
+ json?: boolean;
81
+ }
82
+ export declare function runStatus(rootDir: string, options?: RunStatusOptions): Promise<string>;
83
+ export declare function statusCommand(options?: RunStatusOptions): Promise<void>;
55
84
  //# sourceMappingURL=status.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAKA,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;CAC1B;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAO7E;AAED,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAM1E;AAED,wBAAgB,iBAAiB,CAC/B,mBAAmB,EAAE,kBAAkB,EAAE,EACzC,eAAe,EAAE,MAAM,GACtB,MAAM,CAuBR;AAOD,wBAAsB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAkBhE;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAcnD"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"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"}
@@ -2,64 +2,187 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import chalk from "chalk";
4
4
  import ora from "ora";
5
+ import { loadSnapshot, sha256 } from "../utils/snapshot.js";
5
6
  export function readComponentsJson(rootDir) {
6
7
  const filePath = path.join(rootDir, "components.json");
7
8
  if (!fs.existsSync(filePath)) {
8
9
  return null;
9
10
  }
10
- const raw = fs.readFileSync(filePath, "utf-8");
11
- return JSON.parse(raw);
11
+ try {
12
+ const raw = fs.readFileSync(filePath, "utf-8");
13
+ return JSON.parse(raw);
14
+ }
15
+ catch {
16
+ return null;
17
+ }
12
18
  }
13
19
  export function readRegistryJson(registryPath) {
14
20
  if (!fs.existsSync(registryPath)) {
15
21
  return null;
16
22
  }
17
- const raw = fs.readFileSync(registryPath, "utf-8");
18
- return JSON.parse(raw);
23
+ try {
24
+ const raw = fs.readFileSync(registryPath, "utf-8");
25
+ return JSON.parse(raw);
26
+ }
27
+ catch {
28
+ return null;
29
+ }
30
+ }
31
+ function padRight(str, len) {
32
+ if (str.length >= len)
33
+ return str;
34
+ return str + " ".repeat(len - str.length);
35
+ }
36
+ /**
37
+ * Inspect every installed component, comparing disk state against the
38
+ * saved snapshot and the registry version. This is the machine-readable
39
+ * side of the `status` command — `formatStatusTable` renders it.
40
+ */
41
+ export function inspectStatus(rootDir, componentsJson, registry) {
42
+ const installed = componentsJson.installedComponents ?? [];
43
+ const registryVersion = registry?.version ?? "unknown";
44
+ const localComponentDir = componentsJson.aliases.components.replace(/^@\//, "src/");
45
+ const localBlocksDir = componentsJson.aliases.blocks.replace(/^@\//, "src/");
46
+ const allRegistryItems = registry
47
+ ? [...registry.components, ...registry.blocks, ...registry.templates]
48
+ : [];
49
+ const registryByName = new Map(allRegistryItems.map((c) => [c.name, c]));
50
+ const components = installed.map((inst) => {
51
+ const snapshot = loadSnapshot(rootDir, inst.name);
52
+ const regItem = registryByName.get(inst.name);
53
+ const isMultiFile = regItem?.type === "block" || regItem?.type === "template";
54
+ // Compute expected local file paths from the snapshot (preferred) or
55
+ // fall back to registry files if the snapshot is missing.
56
+ const expectedFiles = [];
57
+ if (snapshot) {
58
+ for (const f of Object.values(snapshot.files)) {
59
+ expectedFiles.push(f.localPath);
60
+ }
61
+ }
62
+ else if (regItem) {
63
+ const targetDirRaw = regItem.type === "block"
64
+ ? path.join(localBlocksDir, regItem.name)
65
+ : regItem.type === "template"
66
+ ? path.join(path.dirname(localBlocksDir), "templates", regItem.name)
67
+ : localComponentDir;
68
+ for (const rf of regItem.files) {
69
+ expectedFiles.push(path.join(targetDirRaw, path.basename(rf)));
70
+ }
71
+ }
72
+ const missingFiles = [];
73
+ const driftedFiles = [];
74
+ for (const rel of expectedFiles) {
75
+ const abs = path.join(rootDir, rel);
76
+ if (!fs.existsSync(abs)) {
77
+ missingFiles.push(rel);
78
+ continue;
79
+ }
80
+ if (snapshot) {
81
+ const snap = snapshot.files[rel];
82
+ if (snap) {
83
+ const current = fs.readFileSync(abs, "utf-8");
84
+ if (sha256(current) !== snap.hash) {
85
+ driftedFiles.push(rel);
86
+ }
87
+ }
88
+ }
89
+ }
90
+ let status;
91
+ if (missingFiles.length > 0 && missingFiles.length === expectedFiles.length) {
92
+ status = "MISSING";
93
+ }
94
+ else if (driftedFiles.length > 0 || missingFiles.length > 0) {
95
+ status = "DRIFT";
96
+ }
97
+ else if (registryVersion !== "unknown" &&
98
+ inst.version !== registryVersion) {
99
+ status = "OUTDATED";
100
+ }
101
+ else {
102
+ status = "HEALTHY";
103
+ }
104
+ // Silence unused param warning when block dir isn't used.
105
+ void isMultiFile;
106
+ return {
107
+ name: inst.name,
108
+ installedVersion: inst.version,
109
+ registryVersion,
110
+ status,
111
+ driftedFiles: driftedFiles.length > 0 ? driftedFiles : undefined,
112
+ missingFiles: missingFiles.length > 0 ? missingFiles : undefined,
113
+ };
114
+ });
115
+ return { registryVersion, components };
19
116
  }
20
- export function formatStatusTable(installedComponents, registryVersion) {
21
- if (installedComponents.length === 0) {
117
+ export function formatStatusTable(report) {
118
+ if (report.components.length === 0) {
22
119
  return chalk.yellow("No components installed yet. Run `inai-ui add` to get started.");
23
120
  }
24
- const header = `${chalk.bold("Installed Components")} (registry v${registryVersion})\n`;
121
+ const header = `${chalk.bold("Installed Components")} (registry v${report.registryVersion})\n`;
25
122
  const separator = chalk.dim("─".repeat(60)) + "\n";
26
- const columnHeader = chalk.bold(padRight("Component", 20)) +
27
- chalk.bold(padRight("Version", 15)) +
28
- chalk.bold("Installed At") +
123
+ const columnHeader = chalk.bold(padRight("NAME", 22)) +
124
+ chalk.bold(padRight("STATUS", 12)) +
125
+ chalk.bold("VERSION") +
29
126
  "\n";
127
+ const colorize = (status, text) => {
128
+ switch (status) {
129
+ case "HEALTHY":
130
+ return chalk.green(text);
131
+ case "DRIFT":
132
+ return chalk.yellow(text);
133
+ case "OUTDATED":
134
+ return chalk.blue(text);
135
+ case "MISSING":
136
+ return chalk.red(text);
137
+ }
138
+ };
30
139
  let rows = "";
31
- for (const comp of installedComponents) {
140
+ for (const comp of report.components) {
141
+ let versionCell;
142
+ switch (comp.status) {
143
+ case "OUTDATED":
144
+ versionCell = `${comp.installedVersion} → ${comp.registryVersion}`;
145
+ break;
146
+ case "DRIFT":
147
+ versionCell = `${comp.installedVersion} (local modified)`;
148
+ break;
149
+ case "MISSING":
150
+ versionCell = `(file deleted)`;
151
+ break;
152
+ default:
153
+ versionCell = comp.installedVersion;
154
+ }
32
155
  rows +=
33
- chalk.cyan(padRight(comp.name, 20)) +
34
- padRight(comp.version, 15) +
35
- chalk.dim(comp.installedAt) +
156
+ chalk.cyan(padRight(comp.name, 22)) +
157
+ colorize(comp.status, padRight(comp.status, 12)) +
158
+ chalk.dim(versionCell) +
36
159
  "\n";
37
160
  }
38
161
  return header + separator + columnHeader + separator + rows;
39
162
  }
40
- function padRight(str, len) {
41
- if (str.length >= len)
42
- return str;
43
- return str + " ".repeat(len - str.length);
44
- }
45
- export async function runStatus(rootDir) {
163
+ export async function runStatus(rootDir, options = {}) {
46
164
  const componentsJson = readComponentsJson(rootDir);
47
165
  if (!componentsJson) {
166
+ if (options.json) {
167
+ return JSON.stringify({ error: "No components.json found. Run `inai-ui init` first." }, null, 2);
168
+ }
48
169
  return chalk.red("No components.json found. Run `inai-ui init` first to initialize your project.");
49
170
  }
50
171
  const { resolveRegistryDir } = await import("../utils/registry-resolver.js");
51
172
  const registryDir = resolveRegistryDir(rootDir);
52
173
  const registryPath = path.join(registryDir, "registry.json");
53
174
  const registry = readRegistryJson(registryPath);
54
- const registryVersion = registry?.version ?? "unknown";
55
- const installed = componentsJson.installedComponents ?? [];
56
- return formatStatusTable(installed, registryVersion);
175
+ const report = inspectStatus(rootDir, componentsJson, registry);
176
+ if (options.json) {
177
+ return JSON.stringify(report, null, 2);
178
+ }
179
+ return formatStatusTable(report);
57
180
  }
58
- export async function statusCommand() {
181
+ export async function statusCommand(options = {}) {
59
182
  const spinner = ora("Reading project configuration...").start();
60
183
  try {
61
184
  const rootDir = process.cwd();
62
- const output = await runStatus(rootDir);
185
+ const output = await runStatus(rootDir, options);
63
186
  spinner.stop();
64
187
  console.log(output);
65
188
  }
@@ -1 +1 @@
1
- {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../src/commands/theme.ts"],"names":[],"mappings":"AA0CA,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMhF;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAgB3F;AAED,wBAAsB,cAAc,CAClC,OAAO,EAAE,kBAAkB,EAC3B,OAAO,EAAE,MAAM,GACd,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAoDlE;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAwBnF"}
1
+ {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../src/commands/theme.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMhF;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAgB3F;AAED,wBAAsB,cAAc,CAClC,OAAO,EAAE,kBAAkB,EAC3B,OAAO,EAAE,MAAM,GACd,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAoDlE;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAwBnF"}
@@ -2,42 +2,8 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import chalk from "chalk";
4
4
  import ora from "ora";
5
- const AVAILABLE_THEMES = [
6
- "inai",
7
- "monday",
8
- "linear",
9
- "notion",
10
- "vercel",
11
- "anthropic",
12
- "shadcn-default",
13
- "shadcn-blue",
14
- "shadcn-green",
15
- "shadcn-orange",
16
- "shadcn-red",
17
- "shadcn-rose",
18
- "shadcn-violet",
19
- "shadcn-yellow",
20
- "shadcn-amber",
21
- "shadcn-lime",
22
- "shadcn-emerald",
23
- "shadcn-teal",
24
- "shadcn-cyan",
25
- "shadcn-sky",
26
- "shadcn-indigo",
27
- "shadcn-purple",
28
- "shadcn-fuchsia",
29
- "shadcn-pink",
30
- "cyberpunk",
31
- "aurora",
32
- "sunset",
33
- "midnight",
34
- "acid",
35
- "sakura",
36
- "ocean",
37
- "cosmic",
38
- "matcha",
39
- "volcanic",
40
- ];
5
+ import { THEME_NAMES } from "../utils/themes.js";
6
+ const AVAILABLE_THEMES = THEME_NAMES;
41
7
  export function resolveThemesDir(rootDir) {
42
8
  return path.join(rootDir, "packages", "tokens", "src", "themes");
43
9
  }
@@ -71,7 +37,7 @@ export async function runThemeCreate(options, rootDir) {
71
37
  message: "Theme name is required.",
72
38
  };
73
39
  }
74
- const validBases = [...AVAILABLE_THEMES];
40
+ const validBases = AVAILABLE_THEMES;
75
41
  if (!validBases.includes(base)) {
76
42
  return {
77
43
  success: false,
@@ -2,7 +2,24 @@ export interface UpdateResult {
2
2
  updated: boolean;
3
3
  message: string;
4
4
  files: string[];
5
+ /** Files that merged cleanly (no conflict markers written). */
6
+ cleanMerges?: string[];
7
+ /** Files that were written with conflict markers the user must resolve. */
8
+ conflictedFiles?: string[];
5
9
  }
10
+ export interface MergeOutcome {
11
+ merged: string;
12
+ hasConflicts: boolean;
13
+ conflictCount: number;
14
+ }
15
+ /**
16
+ * Run a 3-way line merge between base (snapshot), mine (local) and theirs
17
+ * (registry). When a conflict is encountered we emit the standard git-style
18
+ * markers so the user can resolve them in their editor.
19
+ */
20
+ export declare function threeWayMerge(mine: string, base: string, theirs: string): MergeOutcome;
6
21
  export declare function runUpdate(componentName: string, rootDir: string, skipPrompts?: boolean): Promise<UpdateResult>;
7
- export declare function updateCommand(componentName: string): Promise<void>;
22
+ export declare function updateCommand(componentName?: string, options?: {
23
+ all?: boolean;
24
+ }): Promise<void>;
8
25
  //# sourceMappingURL=update.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../src/commands/update.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,wBAAsB,SAAS,CAC7B,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACf,WAAW,UAAQ,GAClB,OAAO,CAAC,YAAY,CAAC,CAgIvB;AAED,wBAAsB,aAAa,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBxE"}
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"}