inai-react-components 0.1.7 → 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.
Files changed (68) hide show
  1. package/README.md +80 -0
  2. package/dist/commands/add.d.ts +42 -12
  3. package/dist/commands/add.d.ts.map +1 -1
  4. package/dist/commands/add.js +413 -93
  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 +90 -20
  8. package/dist/commands/init.d.ts +17 -5
  9. package/dist/commands/init.d.ts.map +1 -1
  10. package/dist/commands/init.js +87 -64
  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 +171 -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 +40 -4
  27. package/dist/commands/status.d.ts.map +1 -1
  28. package/dist/commands/status.js +149 -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 +197 -6
  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 +20 -0
  45. package/dist/utils/framework-detect.d.ts.map +1 -0
  46. package/dist/utils/framework-detect.js +97 -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/paths.d.ts +26 -0
  51. package/dist/utils/paths.d.ts.map +1 -0
  52. package/dist/utils/paths.js +35 -0
  53. package/dist/utils/registry-config.d.ts +27 -0
  54. package/dist/utils/registry-config.d.ts.map +1 -0
  55. package/dist/utils/registry-config.js +94 -0
  56. package/dist/utils/registry-resolver.d.ts +26 -0
  57. package/dist/utils/registry-resolver.d.ts.map +1 -1
  58. package/dist/utils/registry-resolver.js +134 -10
  59. package/dist/utils/snapshot.d.ts +20 -0
  60. package/dist/utils/snapshot.d.ts.map +1 -0
  61. package/dist/utils/snapshot.js +32 -0
  62. package/dist/utils/themes.d.ts +17 -0
  63. package/dist/utils/themes.d.ts.map +1 -0
  64. package/dist/utils/themes.js +49 -0
  65. package/dist/utils/tsconfig-detect.d.ts +18 -0
  66. package/dist/utils/tsconfig-detect.d.ts.map +1 -0
  67. package/dist/utils/tsconfig-detect.js +121 -0
  68. package/package.json +9 -3
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,171 @@
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
+ import { resolveLocalDir } from "../utils/paths.js";
10
+ /**
11
+ * Find components that depend on the given component name via
12
+ * `registryDependencies`.
13
+ */
14
+ function findDependents(componentName, installedNames, registryPath) {
15
+ const registry = readRegistryJson(registryPath);
16
+ if (!registry)
17
+ return [];
18
+ const allItems = [
19
+ ...registry.components,
20
+ ...registry.blocks,
21
+ ...registry.templates,
22
+ ];
23
+ const dependents = [];
24
+ for (const item of allItems) {
25
+ if (!installedNames.includes(item.name))
26
+ continue;
27
+ if (item.name === componentName)
28
+ continue;
29
+ const deps = item.registryDependencies ?? [];
30
+ if (deps.includes(componentName)) {
31
+ dependents.push(item.name);
32
+ }
33
+ }
34
+ return dependents;
35
+ }
36
+ export async function runRemove(componentName, rootDir, options = {}) {
37
+ const componentsJson = readComponentsJson(rootDir);
38
+ if (!componentsJson) {
39
+ return {
40
+ removed: false,
41
+ message: "No components.json found. Run `inai-ui init` first to initialize your project.",
42
+ deletedFiles: [],
43
+ };
44
+ }
45
+ const installed = componentsJson.installedComponents ?? [];
46
+ const entry = installed.find((c) => c.name === componentName);
47
+ if (!entry) {
48
+ return {
49
+ removed: false,
50
+ message: `Component "${componentName}" is not installed.`,
51
+ deletedFiles: [],
52
+ };
53
+ }
54
+ // Check for dependents
55
+ const registryDir = resolveRegistryDir(rootDir);
56
+ const registryPath = path.join(registryDir, "registry.json");
57
+ const installedNames = installed.map((c) => c.name);
58
+ const dependents = findDependents(componentName, installedNames, registryPath);
59
+ if (dependents.length > 0 && !options.force) {
60
+ const depList = dependents.join(", ");
61
+ console.log(chalk.yellow(`\nWarning: The following installed components depend on "${componentName}": ${depList}`));
62
+ if (process.stdin.isTTY) {
63
+ const res = await prompts({
64
+ type: "confirm",
65
+ name: "proceed",
66
+ message: `Remove "${componentName}" anyway?`,
67
+ initial: false,
68
+ });
69
+ if (!res.proceed) {
70
+ return {
71
+ removed: false,
72
+ message: "Removal cancelled.",
73
+ deletedFiles: [],
74
+ };
75
+ }
76
+ }
77
+ else {
78
+ return {
79
+ removed: false,
80
+ message: `Component "${componentName}" has dependents (${depList}). Use --force to remove anyway.`,
81
+ deletedFiles: [],
82
+ };
83
+ }
84
+ }
85
+ // Determine the files to delete
86
+ const registry = readRegistryJson(registryPath);
87
+ const allItems = registry
88
+ ? [...registry.components, ...registry.blocks, ...registry.templates]
89
+ : [];
90
+ const regEntry = allItems.find((c) => c.name === componentName);
91
+ const deletedFiles = [];
92
+ if (regEntry) {
93
+ const type = regEntry.type.toLowerCase();
94
+ const isMultiFile = type === "block" || type === "template";
95
+ const aliases = componentsJson.aliases;
96
+ let baseTargetDir;
97
+ if (type === "block") {
98
+ baseTargetDir = resolveLocalDir(aliases.blocks, componentsJson);
99
+ }
100
+ else if (type === "template") {
101
+ if (aliases.templates) {
102
+ baseTargetDir = resolveLocalDir(aliases.templates, componentsJson);
103
+ }
104
+ else {
105
+ const blocksDir = resolveLocalDir(aliases.blocks, componentsJson);
106
+ baseTargetDir = path.join(path.dirname(blocksDir), "templates");
107
+ }
108
+ }
109
+ else {
110
+ baseTargetDir = resolveLocalDir(aliases.components, componentsJson);
111
+ }
112
+ if (isMultiFile) {
113
+ // Delete the entire component directory
114
+ const componentDir = path.join(rootDir, baseTargetDir, componentName);
115
+ if (fs.existsSync(componentDir)) {
116
+ fs.rmSync(componentDir, { recursive: true, force: true });
117
+ deletedFiles.push(path.relative(rootDir, componentDir) + "/");
118
+ }
119
+ }
120
+ else {
121
+ // Delete individual files
122
+ for (const filePath of regEntry.files) {
123
+ const fileName = path.basename(filePath);
124
+ const localPath = path.join(rootDir, baseTargetDir, fileName);
125
+ if (fs.existsSync(localPath)) {
126
+ fs.unlinkSync(localPath);
127
+ deletedFiles.push(path.relative(rootDir, localPath));
128
+ }
129
+ }
130
+ }
131
+ }
132
+ // Remove snapshot
133
+ deleteSnapshot(rootDir, componentName);
134
+ // Update components.json
135
+ componentsJson.installedComponents = installed.filter((c) => c.name !== componentName);
136
+ const componentsJsonPath = path.join(rootDir, "components.json");
137
+ fs.writeFileSync(componentsJsonPath, JSON.stringify(componentsJson, null, 2) + "\n");
138
+ return {
139
+ removed: true,
140
+ message: `Component "${componentName}" removed successfully.`,
141
+ deletedFiles,
142
+ };
143
+ }
144
+ export async function removeCommand(componentName, options) {
145
+ const spinner = ora(`Removing "${componentName}"...`).start();
146
+ try {
147
+ const rootDir = process.cwd();
148
+ spinner.stop();
149
+ const result = await runRemove(componentName, rootDir, {
150
+ force: options?.force,
151
+ });
152
+ if (!result.removed) {
153
+ console.log(chalk.yellow(result.message));
154
+ return;
155
+ }
156
+ console.log(chalk.green(result.message));
157
+ if (result.deletedFiles.length > 0) {
158
+ console.log(chalk.green("\nDeleted files:"));
159
+ for (const file of result.deletedFiles) {
160
+ console.log(chalk.dim(` - ${file}`));
161
+ }
162
+ }
163
+ console.log(chalk.dim("\nUpdated components.json"));
164
+ }
165
+ catch (error) {
166
+ spinner.fail("Removal failed.");
167
+ const message = error instanceof Error ? error.message : String(error);
168
+ console.error(chalk.red(`\nError: ${message}`));
169
+ process.exit(1);
170
+ }
171
+ }
@@ -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
+ }
@@ -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;
@@ -46,10 +53,39 @@ export interface RegistryComponent {
46
53
  tokenUsage: string[];
47
54
  tanstackCompatibility: Record<string, boolean>;
48
55
  fieldWrappers?: string[];
56
+ /**
57
+ * Names of other registry components this component depends on. Used by
58
+ * `inai-ui add` to install transitive deps automatically. Defaults to [].
59
+ */
60
+ registryDependencies?: string[];
49
61
  }
50
62
  export declare function readComponentsJson(rootDir: string): ComponentsJsonFile | null;
51
63
  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>;
64
+ export type ComponentHealth = "HEALTHY" | "DRIFT" | "OUTDATED" | "MISSING";
65
+ export interface ComponentStatusEntry {
66
+ name: string;
67
+ installedVersion: string;
68
+ registryVersion: string;
69
+ status: ComponentHealth;
70
+ /** Files that drifted vs the snapshot (populated only for DRIFT). */
71
+ driftedFiles?: string[];
72
+ /** Files missing from disk (populated for MISSING). */
73
+ missingFiles?: string[];
74
+ }
75
+ export interface StatusReport {
76
+ registryVersion: string;
77
+ components: ComponentStatusEntry[];
78
+ }
79
+ /**
80
+ * Inspect every installed component, comparing disk state against the
81
+ * saved snapshot and the registry version. This is the machine-readable
82
+ * side of the `status` command — `formatStatusTable` renders it.
83
+ */
84
+ export declare function inspectStatus(rootDir: string, componentsJson: ComponentsJsonFile, registry: RegistryJson | null): StatusReport;
85
+ export declare function formatStatusTable(report: StatusReport): string;
86
+ export interface RunStatusOptions {
87
+ json?: boolean;
88
+ }
89
+ export declare function runStatus(rootDir: string, options?: RunStatusOptions): Promise<string>;
90
+ export declare function statusCommand(options?: RunStatusOptions): Promise<void>;
55
91
  //# 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,188 @@ 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";
6
+ import { resolveLocalDir } from "../utils/paths.js";
5
7
  export function readComponentsJson(rootDir) {
6
8
  const filePath = path.join(rootDir, "components.json");
7
9
  if (!fs.existsSync(filePath)) {
8
10
  return null;
9
11
  }
10
- const raw = fs.readFileSync(filePath, "utf-8");
11
- return JSON.parse(raw);
12
+ try {
13
+ const raw = fs.readFileSync(filePath, "utf-8");
14
+ return JSON.parse(raw);
15
+ }
16
+ catch {
17
+ return null;
18
+ }
12
19
  }
13
20
  export function readRegistryJson(registryPath) {
14
21
  if (!fs.existsSync(registryPath)) {
15
22
  return null;
16
23
  }
17
- const raw = fs.readFileSync(registryPath, "utf-8");
18
- return JSON.parse(raw);
24
+ try {
25
+ const raw = fs.readFileSync(registryPath, "utf-8");
26
+ return JSON.parse(raw);
27
+ }
28
+ catch {
29
+ return null;
30
+ }
31
+ }
32
+ function padRight(str, len) {
33
+ if (str.length >= len)
34
+ return str;
35
+ return str + " ".repeat(len - str.length);
36
+ }
37
+ /**
38
+ * Inspect every installed component, comparing disk state against the
39
+ * saved snapshot and the registry version. This is the machine-readable
40
+ * side of the `status` command — `formatStatusTable` renders it.
41
+ */
42
+ export function inspectStatus(rootDir, componentsJson, registry) {
43
+ const installed = componentsJson.installedComponents ?? [];
44
+ const registryVersion = registry?.version ?? "unknown";
45
+ const localComponentDir = resolveLocalDir(componentsJson.aliases.components, componentsJson);
46
+ const localBlocksDir = resolveLocalDir(componentsJson.aliases.blocks, componentsJson);
47
+ const allRegistryItems = registry
48
+ ? [...registry.components, ...registry.blocks, ...registry.templates]
49
+ : [];
50
+ const registryByName = new Map(allRegistryItems.map((c) => [c.name, c]));
51
+ const components = installed.map((inst) => {
52
+ const snapshot = loadSnapshot(rootDir, inst.name);
53
+ const regItem = registryByName.get(inst.name);
54
+ const isMultiFile = regItem?.type === "block" || regItem?.type === "template";
55
+ // Compute expected local file paths from the snapshot (preferred) or
56
+ // fall back to registry files if the snapshot is missing.
57
+ const expectedFiles = [];
58
+ if (snapshot) {
59
+ for (const f of Object.values(snapshot.files)) {
60
+ expectedFiles.push(f.localPath);
61
+ }
62
+ }
63
+ else if (regItem) {
64
+ const targetDirRaw = regItem.type === "block"
65
+ ? path.join(localBlocksDir, regItem.name)
66
+ : regItem.type === "template"
67
+ ? path.join(path.dirname(localBlocksDir), "templates", regItem.name)
68
+ : localComponentDir;
69
+ for (const rf of regItem.files) {
70
+ expectedFiles.push(path.join(targetDirRaw, path.basename(rf)));
71
+ }
72
+ }
73
+ const missingFiles = [];
74
+ const driftedFiles = [];
75
+ for (const rel of expectedFiles) {
76
+ const abs = path.join(rootDir, rel);
77
+ if (!fs.existsSync(abs)) {
78
+ missingFiles.push(rel);
79
+ continue;
80
+ }
81
+ if (snapshot) {
82
+ const snap = snapshot.files[rel];
83
+ if (snap) {
84
+ const current = fs.readFileSync(abs, "utf-8");
85
+ if (sha256(current) !== snap.hash) {
86
+ driftedFiles.push(rel);
87
+ }
88
+ }
89
+ }
90
+ }
91
+ let status;
92
+ if (missingFiles.length > 0 && missingFiles.length === expectedFiles.length) {
93
+ status = "MISSING";
94
+ }
95
+ else if (driftedFiles.length > 0 || missingFiles.length > 0) {
96
+ status = "DRIFT";
97
+ }
98
+ else if (registryVersion !== "unknown" &&
99
+ inst.version !== registryVersion) {
100
+ status = "OUTDATED";
101
+ }
102
+ else {
103
+ status = "HEALTHY";
104
+ }
105
+ // Silence unused param warning when block dir isn't used.
106
+ void isMultiFile;
107
+ return {
108
+ name: inst.name,
109
+ installedVersion: inst.version,
110
+ registryVersion,
111
+ status,
112
+ driftedFiles: driftedFiles.length > 0 ? driftedFiles : undefined,
113
+ missingFiles: missingFiles.length > 0 ? missingFiles : undefined,
114
+ };
115
+ });
116
+ return { registryVersion, components };
19
117
  }
20
- export function formatStatusTable(installedComponents, registryVersion) {
21
- if (installedComponents.length === 0) {
118
+ export function formatStatusTable(report) {
119
+ if (report.components.length === 0) {
22
120
  return chalk.yellow("No components installed yet. Run `inai-ui add` to get started.");
23
121
  }
24
- const header = `${chalk.bold("Installed Components")} (registry v${registryVersion})\n`;
122
+ const header = `${chalk.bold("Installed Components")} (registry v${report.registryVersion})\n`;
25
123
  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") +
124
+ const columnHeader = chalk.bold(padRight("NAME", 22)) +
125
+ chalk.bold(padRight("STATUS", 12)) +
126
+ chalk.bold("VERSION") +
29
127
  "\n";
128
+ const colorize = (status, text) => {
129
+ switch (status) {
130
+ case "HEALTHY":
131
+ return chalk.green(text);
132
+ case "DRIFT":
133
+ return chalk.yellow(text);
134
+ case "OUTDATED":
135
+ return chalk.blue(text);
136
+ case "MISSING":
137
+ return chalk.red(text);
138
+ }
139
+ };
30
140
  let rows = "";
31
- for (const comp of installedComponents) {
141
+ for (const comp of report.components) {
142
+ let versionCell;
143
+ switch (comp.status) {
144
+ case "OUTDATED":
145
+ versionCell = `${comp.installedVersion} → ${comp.registryVersion}`;
146
+ break;
147
+ case "DRIFT":
148
+ versionCell = `${comp.installedVersion} (local modified)`;
149
+ break;
150
+ case "MISSING":
151
+ versionCell = `(file deleted)`;
152
+ break;
153
+ default:
154
+ versionCell = comp.installedVersion;
155
+ }
32
156
  rows +=
33
- chalk.cyan(padRight(comp.name, 20)) +
34
- padRight(comp.version, 15) +
35
- chalk.dim(comp.installedAt) +
157
+ chalk.cyan(padRight(comp.name, 22)) +
158
+ colorize(comp.status, padRight(comp.status, 12)) +
159
+ chalk.dim(versionCell) +
36
160
  "\n";
37
161
  }
38
162
  return header + separator + columnHeader + separator + rows;
39
163
  }
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) {
164
+ export async function runStatus(rootDir, options = {}) {
46
165
  const componentsJson = readComponentsJson(rootDir);
47
166
  if (!componentsJson) {
167
+ if (options.json) {
168
+ return JSON.stringify({ error: "No components.json found. Run `inai-ui init` first." }, null, 2);
169
+ }
48
170
  return chalk.red("No components.json found. Run `inai-ui init` first to initialize your project.");
49
171
  }
50
172
  const { resolveRegistryDir } = await import("../utils/registry-resolver.js");
51
173
  const registryDir = resolveRegistryDir(rootDir);
52
174
  const registryPath = path.join(registryDir, "registry.json");
53
175
  const registry = readRegistryJson(registryPath);
54
- const registryVersion = registry?.version ?? "unknown";
55
- const installed = componentsJson.installedComponents ?? [];
56
- return formatStatusTable(installed, registryVersion);
176
+ const report = inspectStatus(rootDir, componentsJson, registry);
177
+ if (options.json) {
178
+ return JSON.stringify(report, null, 2);
179
+ }
180
+ return formatStatusTable(report);
57
181
  }
58
- export async function statusCommand() {
182
+ export async function statusCommand(options = {}) {
59
183
  const spinner = ora("Reading project configuration...").start();
60
184
  try {
61
185
  const rootDir = process.cwd();
62
- const output = await runStatus(rootDir);
186
+ const output = await runStatus(rootDir, options);
63
187
  spinner.stop();
64
188
  console.log(output);
65
189
  }
@@ -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":"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"}