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.
- package/README.md +80 -0
- package/dist/commands/add.d.ts +38 -5
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +391 -80
- package/dist/commands/diff.d.ts +6 -0
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +92 -20
- package/dist/commands/init.d.ts +6 -3
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +33 -36
- package/dist/commands/mcp.d.ts +123 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +289 -0
- package/dist/commands/migrate.d.ts +41 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +139 -0
- package/dist/commands/registries.d.ts +46 -0
- package/dist/commands/registries.d.ts.map +1 -0
- package/dist/commands/registries.js +152 -0
- package/dist/commands/remove.d.ts +11 -0
- package/dist/commands/remove.d.ts.map +1 -0
- package/dist/commands/remove.js +170 -0
- package/dist/commands/schema.d.ts +16 -0
- package/dist/commands/schema.d.ts.map +1 -0
- package/dist/commands/schema.js +32 -0
- package/dist/commands/status.d.ts +32 -3
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +148 -25
- package/dist/commands/theme.d.ts.map +1 -1
- package/dist/commands/theme.js +3 -37
- package/dist/commands/update.d.ts +18 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +195 -5
- package/dist/index.js +76 -5
- package/dist/schemas/registry-config.schema.json +62 -0
- package/dist/schemas/registry-item.schema.json +63 -0
- package/dist/schemas/registry.schema.json +15 -0
- package/dist/types/registry.d.ts +50 -0
- package/dist/types/registry.d.ts.map +1 -0
- package/dist/types/registry.js +10 -0
- package/dist/utils/auto-install.d.ts +11 -0
- package/dist/utils/auto-install.d.ts.map +1 -0
- package/dist/utils/auto-install.js +29 -0
- package/dist/utils/framework-detect.d.ts +15 -0
- package/dist/utils/framework-detect.d.ts.map +1 -0
- package/dist/utils/framework-detect.js +90 -0
- package/dist/utils/fuzzy-search.d.ts +16 -0
- package/dist/utils/fuzzy-search.d.ts.map +1 -0
- package/dist/utils/fuzzy-search.js +67 -0
- package/dist/utils/registry-config.d.ts +27 -0
- package/dist/utils/registry-config.d.ts.map +1 -0
- package/dist/utils/registry-config.js +94 -0
- package/dist/utils/registry-resolver.d.ts +26 -0
- package/dist/utils/registry-resolver.d.ts.map +1 -1
- package/dist/utils/registry-resolver.js +134 -10
- package/dist/utils/snapshot.d.ts +20 -0
- package/dist/utils/snapshot.d.ts.map +1 -0
- package/dist/utils/snapshot.js +32 -0
- package/dist/utils/themes.d.ts +17 -0
- package/dist/utils/themes.d.ts.map +1 -0
- package/dist/utils/themes.js +49 -0
- package/package.json +9 -3
package/dist/commands/update.js
CHANGED
|
@@ -3,9 +3,55 @@ import path from "node:path";
|
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import ora from "ora";
|
|
5
5
|
import prompts from "prompts";
|
|
6
|
+
import diff3Merge from "diff3";
|
|
6
7
|
import { readComponentsJson, readRegistryJson, } from "./status.js";
|
|
7
8
|
import { findComponentInRegistry, computeDiff } from "./diff.js";
|
|
8
9
|
import { resolveRegistryDir } from "../utils/registry-resolver.js";
|
|
10
|
+
import { loadSnapshot, saveSnapshot, sha256, } from "../utils/snapshot.js";
|
|
11
|
+
/**
|
|
12
|
+
* Run a 3-way line merge between base (snapshot), mine (local) and theirs
|
|
13
|
+
* (registry). When a conflict is encountered we emit the standard git-style
|
|
14
|
+
* markers so the user can resolve them in their editor.
|
|
15
|
+
*/
|
|
16
|
+
export function threeWayMerge(mine, base, theirs) {
|
|
17
|
+
// Fast path — nothing to do.
|
|
18
|
+
if (mine === theirs) {
|
|
19
|
+
return { merged: mine, hasConflicts: false, conflictCount: 0 };
|
|
20
|
+
}
|
|
21
|
+
// If mine === base the user never touched the file — take theirs wholesale.
|
|
22
|
+
if (mine === base) {
|
|
23
|
+
return { merged: theirs, hasConflicts: false, conflictCount: 0 };
|
|
24
|
+
}
|
|
25
|
+
// If theirs === base nothing changed upstream — keep mine.
|
|
26
|
+
if (theirs === base) {
|
|
27
|
+
return { merged: mine, hasConflicts: false, conflictCount: 0 };
|
|
28
|
+
}
|
|
29
|
+
const mineLines = mine.split("\n");
|
|
30
|
+
const baseLines = base.split("\n");
|
|
31
|
+
const theirsLines = theirs.split("\n");
|
|
32
|
+
const regions = diff3Merge(mineLines, baseLines, theirsLines);
|
|
33
|
+
const output = [];
|
|
34
|
+
let conflictCount = 0;
|
|
35
|
+
for (const region of regions) {
|
|
36
|
+
if ("ok" in region) {
|
|
37
|
+
output.push(...region.ok);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
conflictCount++;
|
|
41
|
+
const { a, b } = region.conflict;
|
|
42
|
+
output.push("<<<<<<< local");
|
|
43
|
+
output.push(...a);
|
|
44
|
+
output.push("=======");
|
|
45
|
+
output.push(...b);
|
|
46
|
+
output.push(">>>>>>> registry");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
merged: output.join("\n"),
|
|
51
|
+
hasConflicts: conflictCount > 0,
|
|
52
|
+
conflictCount,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
9
55
|
export async function runUpdate(componentName, rootDir, skipPrompts = false) {
|
|
10
56
|
const componentsJson = readComponentsJson(rootDir);
|
|
11
57
|
if (!componentsJson) {
|
|
@@ -29,7 +75,11 @@ export async function runUpdate(componentName, rootDir, skipPrompts = false) {
|
|
|
29
75
|
const registryVersion = registry?.version ?? "unknown";
|
|
30
76
|
const localComponentDir = componentsJson.aliases.components.replace(/^@\//, "src/");
|
|
31
77
|
const updatedFiles = [];
|
|
78
|
+
const cleanMerges = [];
|
|
79
|
+
const conflictedFiles = [];
|
|
32
80
|
let needsUpdate = false;
|
|
81
|
+
const snapshot = loadSnapshot(rootDir, componentName);
|
|
82
|
+
const newSnapshotFiles = {};
|
|
33
83
|
for (const filePath of registryComponent.files) {
|
|
34
84
|
const registryFilePath = path.join(registryDir, filePath);
|
|
35
85
|
const fileName = path.basename(filePath);
|
|
@@ -44,15 +94,64 @@ export async function runUpdate(componentName, rootDir, skipPrompts = false) {
|
|
|
44
94
|
fs.mkdirSync(dir, { recursive: true });
|
|
45
95
|
fs.writeFileSync(localFilePath, registryContent);
|
|
46
96
|
updatedFiles.push(fileName);
|
|
97
|
+
cleanMerges.push(fileName);
|
|
47
98
|
needsUpdate = true;
|
|
99
|
+
const rel = path.relative(rootDir, localFilePath);
|
|
100
|
+
newSnapshotFiles[rel] = {
|
|
101
|
+
localPath: rel,
|
|
102
|
+
hash: sha256(registryContent),
|
|
103
|
+
content: registryContent,
|
|
104
|
+
};
|
|
48
105
|
continue;
|
|
49
106
|
}
|
|
50
107
|
const localContent = fs.readFileSync(localFilePath, "utf-8");
|
|
51
108
|
if (localContent === registryContent) {
|
|
109
|
+
// No-op: retain the previous snapshot entry if one exists.
|
|
110
|
+
const rel = path.relative(rootDir, localFilePath);
|
|
111
|
+
const prev = snapshot?.files[rel];
|
|
112
|
+
if (prev) {
|
|
113
|
+
newSnapshotFiles[rel] = {
|
|
114
|
+
...prev,
|
|
115
|
+
hash: sha256(registryContent),
|
|
116
|
+
content: registryContent,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
newSnapshotFiles[rel] = {
|
|
121
|
+
localPath: rel,
|
|
122
|
+
hash: sha256(registryContent),
|
|
123
|
+
content: registryContent,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
52
126
|
continue;
|
|
53
127
|
}
|
|
54
128
|
// There is a difference
|
|
55
129
|
needsUpdate = true;
|
|
130
|
+
const rel = path.relative(rootDir, localFilePath);
|
|
131
|
+
const baseContent = snapshot?.files[rel]?.content;
|
|
132
|
+
if (baseContent !== undefined) {
|
|
133
|
+
// 3-way merge path — we know what the user started from, so we can
|
|
134
|
+
// reconcile their local edits with the new registry version.
|
|
135
|
+
const outcome = threeWayMerge(localContent, baseContent, registryContent);
|
|
136
|
+
fs.writeFileSync(localFilePath, outcome.merged);
|
|
137
|
+
updatedFiles.push(fileName);
|
|
138
|
+
if (outcome.hasConflicts) {
|
|
139
|
+
conflictedFiles.push(fileName);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
cleanMerges.push(fileName);
|
|
143
|
+
}
|
|
144
|
+
// Snapshot now records the new registry content as the base for
|
|
145
|
+
// future updates (regardless of conflict state — the user will
|
|
146
|
+
// resolve markers on disk).
|
|
147
|
+
newSnapshotFiles[rel] = {
|
|
148
|
+
localPath: rel,
|
|
149
|
+
hash: sha256(registryContent),
|
|
150
|
+
content: registryContent,
|
|
151
|
+
};
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
// Legacy path — no snapshot, fall back to the pre-S2.5 overwrite prompt.
|
|
56
155
|
if (!skipPrompts) {
|
|
57
156
|
const diff = computeDiff(localContent, registryContent);
|
|
58
157
|
console.log(chalk.bold(`\nChanges for ${fileName}:`));
|
|
@@ -72,6 +171,12 @@ export async function runUpdate(componentName, rootDir, skipPrompts = false) {
|
|
|
72
171
|
if (response.action === "overwrite") {
|
|
73
172
|
fs.writeFileSync(localFilePath, registryContent);
|
|
74
173
|
updatedFiles.push(fileName);
|
|
174
|
+
cleanMerges.push(fileName);
|
|
175
|
+
newSnapshotFiles[rel] = {
|
|
176
|
+
localPath: rel,
|
|
177
|
+
hash: sha256(registryContent),
|
|
178
|
+
content: registryContent,
|
|
179
|
+
};
|
|
75
180
|
}
|
|
76
181
|
// "keep" and "skip" both leave the file as-is
|
|
77
182
|
}
|
|
@@ -79,6 +184,12 @@ export async function runUpdate(componentName, rootDir, skipPrompts = false) {
|
|
|
79
184
|
// Non-interactive mode: overwrite
|
|
80
185
|
fs.writeFileSync(localFilePath, registryContent);
|
|
81
186
|
updatedFiles.push(fileName);
|
|
187
|
+
cleanMerges.push(fileName);
|
|
188
|
+
newSnapshotFiles[rel] = {
|
|
189
|
+
localPath: rel,
|
|
190
|
+
hash: sha256(registryContent),
|
|
191
|
+
content: registryContent,
|
|
192
|
+
};
|
|
82
193
|
}
|
|
83
194
|
}
|
|
84
195
|
if (!needsUpdate) {
|
|
@@ -105,13 +216,33 @@ export async function runUpdate(componentName, rootDir, skipPrompts = false) {
|
|
|
105
216
|
componentsJson.installedComponents = installed;
|
|
106
217
|
const componentsJsonPath = path.join(rootDir, "components.json");
|
|
107
218
|
fs.writeFileSync(componentsJsonPath, JSON.stringify(componentsJson, null, 2) + "\n");
|
|
219
|
+
// Refresh the snapshot so future updates merge from the new baseline.
|
|
220
|
+
if (Object.keys(newSnapshotFiles).length > 0) {
|
|
221
|
+
const refreshed = {
|
|
222
|
+
name: componentName,
|
|
223
|
+
version: registryVersion,
|
|
224
|
+
files: newSnapshotFiles,
|
|
225
|
+
capturedAt: new Date().toISOString(),
|
|
226
|
+
};
|
|
227
|
+
saveSnapshot(rootDir, refreshed);
|
|
228
|
+
}
|
|
108
229
|
return {
|
|
109
230
|
updated: true,
|
|
110
231
|
message: `Component "${componentName}" updated to registry v${registryVersion}.`,
|
|
111
232
|
files: updatedFiles,
|
|
233
|
+
cleanMerges,
|
|
234
|
+
conflictedFiles,
|
|
112
235
|
};
|
|
113
236
|
}
|
|
114
|
-
export async function updateCommand(componentName) {
|
|
237
|
+
export async function updateCommand(componentName, options) {
|
|
238
|
+
if (options?.all) {
|
|
239
|
+
await updateAll();
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (!componentName) {
|
|
243
|
+
console.error(chalk.red("Please specify a component name or use --all to update all installed components."));
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
115
246
|
const spinner = ora(`Checking "${componentName}" for updates...`).start();
|
|
116
247
|
try {
|
|
117
248
|
const rootDir = process.cwd();
|
|
@@ -119,10 +250,16 @@ export async function updateCommand(componentName) {
|
|
|
119
250
|
const result = await runUpdate(componentName, rootDir);
|
|
120
251
|
if (result.updated) {
|
|
121
252
|
console.log(chalk.green(`\n${result.message}`));
|
|
122
|
-
if (result.
|
|
123
|
-
console.log(chalk.
|
|
124
|
-
for (const file of result.
|
|
125
|
-
console.log(chalk.dim(`
|
|
253
|
+
if (result.cleanMerges && result.cleanMerges.length > 0) {
|
|
254
|
+
console.log(chalk.green("\nMerged cleanly:"));
|
|
255
|
+
for (const file of result.cleanMerges) {
|
|
256
|
+
console.log(chalk.dim(` ✓ ${file}`));
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (result.conflictedFiles && result.conflictedFiles.length > 0) {
|
|
260
|
+
console.log(chalk.yellow(`\nMerged with conflicts — please resolve the <<<<<<< markers manually:`));
|
|
261
|
+
for (const file of result.conflictedFiles) {
|
|
262
|
+
console.log(chalk.yellow(` ! ${file}`));
|
|
126
263
|
}
|
|
127
264
|
}
|
|
128
265
|
}
|
|
@@ -137,3 +274,56 @@ export async function updateCommand(componentName) {
|
|
|
137
274
|
process.exit(1);
|
|
138
275
|
}
|
|
139
276
|
}
|
|
277
|
+
async function updateAll() {
|
|
278
|
+
const rootDir = process.cwd();
|
|
279
|
+
const componentsJson = readComponentsJson(rootDir);
|
|
280
|
+
if (!componentsJson) {
|
|
281
|
+
console.error(chalk.red("No components.json found. Run `inai-ui init` first to initialize your project."));
|
|
282
|
+
process.exit(1);
|
|
283
|
+
}
|
|
284
|
+
const installed = componentsJson.installedComponents ?? [];
|
|
285
|
+
if (installed.length === 0) {
|
|
286
|
+
console.log(chalk.yellow("No installed components found."));
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
console.log(chalk.bold(`\nChecking ${installed.length} installed component${installed.length > 1 ? "s" : ""} for updates...\n`));
|
|
290
|
+
let updatedCount = 0;
|
|
291
|
+
let skippedCount = 0;
|
|
292
|
+
let conflictCount = 0;
|
|
293
|
+
for (const entry of installed) {
|
|
294
|
+
const spinner = ora(`Checking "${entry.name}"...`).start();
|
|
295
|
+
try {
|
|
296
|
+
const result = await runUpdate(entry.name, rootDir, true);
|
|
297
|
+
if (result.updated) {
|
|
298
|
+
const hasConflicts = result.conflictedFiles && result.conflictedFiles.length > 0;
|
|
299
|
+
if (hasConflicts) {
|
|
300
|
+
spinner.warn(`Updated "${entry.name}" with ${result.conflictedFiles.length} conflict(s)`);
|
|
301
|
+
conflictCount++;
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
spinner.succeed(`Updated "${entry.name}"`);
|
|
305
|
+
}
|
|
306
|
+
updatedCount++;
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
spinner.info(chalk.dim(`"${entry.name}" is up to date`));
|
|
310
|
+
skippedCount++;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
spinner.fail(`Failed to update "${entry.name}"`);
|
|
315
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
316
|
+
console.error(chalk.red(` Error: ${message}`));
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
console.log(chalk.bold("\nSummary:"));
|
|
320
|
+
if (updatedCount > 0) {
|
|
321
|
+
console.log(chalk.green(` ${updatedCount} component${updatedCount > 1 ? "s" : ""} updated`));
|
|
322
|
+
}
|
|
323
|
+
if (skippedCount > 0) {
|
|
324
|
+
console.log(chalk.dim(` ${skippedCount} component${skippedCount > 1 ? "s" : ""} already up to date`));
|
|
325
|
+
}
|
|
326
|
+
if (conflictCount > 0) {
|
|
327
|
+
console.log(chalk.yellow(` ${conflictCount} component${conflictCount > 1 ? "s" : ""} with merge conflicts — resolve <<<<<<< markers manually`));
|
|
328
|
+
}
|
|
329
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -8,11 +8,16 @@ import { listCommand } from "./commands/list.js";
|
|
|
8
8
|
import { updateCommand } from "./commands/update.js";
|
|
9
9
|
import { syncCommand } from "./commands/sync.js";
|
|
10
10
|
import { themeCreateCommand } from "./commands/theme.js";
|
|
11
|
+
import { mcpCommand } from "./commands/mcp.js";
|
|
12
|
+
import { migrateCommand } from "./commands/migrate.js";
|
|
13
|
+
import { runSchema } from "./commands/schema.js";
|
|
14
|
+
import { registriesListCommand, registriesAddCommand, registriesRemoveCommand, } from "./commands/registries.js";
|
|
15
|
+
import { removeCommand } from "./commands/remove.js";
|
|
11
16
|
const program = new Command();
|
|
12
17
|
program
|
|
13
18
|
.name("inai-ui")
|
|
14
19
|
.description("CLI for InAI UI component library")
|
|
15
|
-
.version("
|
|
20
|
+
.version("1.2.1");
|
|
16
21
|
program
|
|
17
22
|
.command("init [repo-url]")
|
|
18
23
|
.description("Initialize InAI UI in your project. Optionally pass a Git repo URL to fetch components remotely.")
|
|
@@ -21,6 +26,11 @@ program
|
|
|
21
26
|
.command("add [component]")
|
|
22
27
|
.description("Add a component to your project. Without arguments, opens an interactive picker to select multiple components.")
|
|
23
28
|
.option("-a, --all", "Install all available components, blocks, and templates")
|
|
29
|
+
.option("-f, --force", "Overwrite existing files without prompting")
|
|
30
|
+
.option("--dry-run", "Print what would be written without touching the filesystem")
|
|
31
|
+
.option("--skip-existing", "Skip files that already exist instead of prompting")
|
|
32
|
+
.option("--offline", "Use cached registries without network access")
|
|
33
|
+
.option("--skip-install", "Skip automatic npm dependency installation")
|
|
24
34
|
.action((component, options) => addCommand(component, options));
|
|
25
35
|
program
|
|
26
36
|
.command("sync")
|
|
@@ -28,8 +38,17 @@ program
|
|
|
28
38
|
.action(syncCommand);
|
|
29
39
|
program
|
|
30
40
|
.command("status")
|
|
31
|
-
.description("Show installed components
|
|
32
|
-
.
|
|
41
|
+
.description("Show installed components with drift / outdated / missing state")
|
|
42
|
+
.option("--json", "Emit the status report as JSON (machine-readable)")
|
|
43
|
+
.action((options) => statusCommand(options));
|
|
44
|
+
const mcpCmd = program
|
|
45
|
+
.command("mcp")
|
|
46
|
+
.description("Start the MCP (Model Context Protocol) server over stdio so AI assistants can drive the CLI.")
|
|
47
|
+
.action(() => mcpCommand());
|
|
48
|
+
mcpCmd
|
|
49
|
+
.command("init")
|
|
50
|
+
.description("Print the JSON config fragment for Claude Desktop / Cursor mcpServers.")
|
|
51
|
+
.action(() => mcpCommand("init"));
|
|
33
52
|
program
|
|
34
53
|
.command("diff <component>")
|
|
35
54
|
.description("Show differences between local component and registry version")
|
|
@@ -40,9 +59,15 @@ program
|
|
|
40
59
|
.option("-c, --category <category>", "Filter by component category")
|
|
41
60
|
.action((options) => listCommand(options));
|
|
42
61
|
program
|
|
43
|
-
.command("update
|
|
62
|
+
.command("update [component]")
|
|
44
63
|
.description("Update a component to the latest registry version")
|
|
45
|
-
.
|
|
64
|
+
.option("-a, --all", "Update all installed components that are outdated")
|
|
65
|
+
.action((component, options) => updateCommand(component, options));
|
|
66
|
+
program
|
|
67
|
+
.command("remove <component>")
|
|
68
|
+
.description("Remove an installed component from your project")
|
|
69
|
+
.option("-f, --force", "Skip dependency warning and remove anyway")
|
|
70
|
+
.action((component, options) => removeCommand(component, options));
|
|
46
71
|
const themeCmd = program
|
|
47
72
|
.command("theme")
|
|
48
73
|
.description("Theme management commands");
|
|
@@ -52,4 +77,50 @@ themeCmd
|
|
|
52
77
|
.requiredOption("-n, --name <name>", "Name for the new theme")
|
|
53
78
|
.requiredOption("-b, --base <base>", "Base theme to extend (monday, linear, notion, vercel)")
|
|
54
79
|
.action((options) => themeCreateCommand(options));
|
|
80
|
+
program
|
|
81
|
+
.command("migrate")
|
|
82
|
+
.description("Apply deprecated-prop rename codemods to your project (inputSize→size, selectSize→size, comboBoxSize→size, checkboxState→state).")
|
|
83
|
+
.option("--dry-run", "Show what would change without writing files")
|
|
84
|
+
.action((options) => migrateCommand({ dryRun: !!options.dryRun }));
|
|
85
|
+
program
|
|
86
|
+
.command("schema")
|
|
87
|
+
.description("Print or write the InAI registry JSON Schema")
|
|
88
|
+
.option("--kind <kind>", "Schema kind: 'item', 'registry', or 'registry-config'", "item")
|
|
89
|
+
.option("--output <path>", "Write to file instead of stdout", "stdout")
|
|
90
|
+
.action(async (options) => {
|
|
91
|
+
await runSchema({
|
|
92
|
+
kind: options.kind,
|
|
93
|
+
output: options.output,
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
const registriesCmd = program
|
|
97
|
+
.command("registries")
|
|
98
|
+
.description("Manage federated component registries (M3)");
|
|
99
|
+
registriesCmd
|
|
100
|
+
.command("list")
|
|
101
|
+
.alias("ls")
|
|
102
|
+
.description("List configured registries")
|
|
103
|
+
.action(async () => {
|
|
104
|
+
await registriesListCommand();
|
|
105
|
+
});
|
|
106
|
+
registriesCmd
|
|
107
|
+
.command("add <name>")
|
|
108
|
+
.description("Add a new registry to components.json")
|
|
109
|
+
.option("--source <source>", "Source type: local | git | https", "git")
|
|
110
|
+
.option("--url <url>", "Registry URL (for source=git or source=https)")
|
|
111
|
+
.option("--path <path>", "Local path (for source=local)")
|
|
112
|
+
.option("--branch <branch>", "Git branch", "main")
|
|
113
|
+
.option("--token-env <env>", "Environment variable holding the bearer token")
|
|
114
|
+
.option("--token-file <file>", "Path to a file containing the bearer token")
|
|
115
|
+
.option("--default", "Mark this registry as the default (clears any prior default)")
|
|
116
|
+
.action(async (name, opts) => {
|
|
117
|
+
await registriesAddCommand(name, opts);
|
|
118
|
+
});
|
|
119
|
+
registriesCmd
|
|
120
|
+
.command("remove <name>")
|
|
121
|
+
.alias("rm")
|
|
122
|
+
.description("Remove a registry from components.json")
|
|
123
|
+
.action(async (name) => {
|
|
124
|
+
await registriesRemoveCommand(name);
|
|
125
|
+
});
|
|
55
126
|
program.parse();
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://inai.dev/schemas/registry-config.json",
|
|
4
|
+
"title": "InAI components.json — federated registry configuration",
|
|
5
|
+
"description": "Shape of the `registries` array in components.json for multi-source component federation (M3). Consumer projects may configure multiple registries simultaneously with bearer-token auth and offline mirroring.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"registries": {
|
|
9
|
+
"type": "array",
|
|
10
|
+
"description": "Ordered list of registries. The first with `default: true` (or the first entry) is used when a component spec has no `@namespace/` prefix.",
|
|
11
|
+
"items": {
|
|
12
|
+
"type": "object",
|
|
13
|
+
"required": ["name", "source"],
|
|
14
|
+
"additionalProperties": false,
|
|
15
|
+
"properties": {
|
|
16
|
+
"name": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"pattern": "^[a-z0-9][a-z0-9-]*$",
|
|
19
|
+
"description": "Stable alias used in `@namespace/name` syntax."
|
|
20
|
+
},
|
|
21
|
+
"source": {
|
|
22
|
+
"enum": ["local", "git", "https"],
|
|
23
|
+
"description": "How the registry is fetched."
|
|
24
|
+
},
|
|
25
|
+
"path": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"description": "Filesystem path relative to the project root (for source=local)."
|
|
28
|
+
},
|
|
29
|
+
"url": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"format": "uri",
|
|
32
|
+
"description": "Git clone URL (source=git) or registry.json URL (source=https)."
|
|
33
|
+
},
|
|
34
|
+
"branch": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"description": "Git branch. Defaults to 'main'."
|
|
37
|
+
},
|
|
38
|
+
"default": {
|
|
39
|
+
"type": "boolean",
|
|
40
|
+
"description": "Use this registry when a component has no @namespace/."
|
|
41
|
+
},
|
|
42
|
+
"auth": {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"required": ["type"],
|
|
45
|
+
"additionalProperties": false,
|
|
46
|
+
"properties": {
|
|
47
|
+
"type": { "const": "bearer" },
|
|
48
|
+
"tokenEnv": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"description": "Environment variable holding the bearer token."
|
|
51
|
+
},
|
|
52
|
+
"tokenFile": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"description": "Absolute path to a file containing the token."
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://inai.dev/schemas/registry-item.json",
|
|
4
|
+
"title": "InAI Registry Item",
|
|
5
|
+
"description": "Shape of a component, block, or template entry in the InAI UI registry.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["name", "type", "files"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"name": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "Unique name used by `inai-ui add <name>`"
|
|
12
|
+
},
|
|
13
|
+
"type": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"enum": ["component", "form", "block", "template"],
|
|
16
|
+
"description": "Registry classification"
|
|
17
|
+
},
|
|
18
|
+
"description": {
|
|
19
|
+
"type": "string"
|
|
20
|
+
},
|
|
21
|
+
"files": {
|
|
22
|
+
"type": "array",
|
|
23
|
+
"items": { "type": "string" },
|
|
24
|
+
"description": "Relative paths from the registry root to the files copied when this item is installed"
|
|
25
|
+
},
|
|
26
|
+
"npmDeps": {
|
|
27
|
+
"type": "array",
|
|
28
|
+
"items": { "type": "string" },
|
|
29
|
+
"description": "NPM packages required by this item (installed automatically by the CLI)"
|
|
30
|
+
},
|
|
31
|
+
"internalDeps": {
|
|
32
|
+
"type": "array",
|
|
33
|
+
"items": { "type": "string" },
|
|
34
|
+
"description": "Internal library helpers (`lib/cn`, `lib/animations`) this item imports"
|
|
35
|
+
},
|
|
36
|
+
"registryDependencies": {
|
|
37
|
+
"type": "array",
|
|
38
|
+
"items": { "type": "string" },
|
|
39
|
+
"description": "Other registry items this one depends on. Resolved transitively by `inai-ui add`."
|
|
40
|
+
},
|
|
41
|
+
"tokenUsage": {
|
|
42
|
+
"type": "array",
|
|
43
|
+
"items": { "type": "string" },
|
|
44
|
+
"description": "CSS custom properties this item consumes"
|
|
45
|
+
},
|
|
46
|
+
"tanstackCompatibility": {
|
|
47
|
+
"type": "object",
|
|
48
|
+
"description": "Which TanStack adapters this item ships with",
|
|
49
|
+
"additionalProperties": { "type": "boolean" }
|
|
50
|
+
},
|
|
51
|
+
"classNames": {
|
|
52
|
+
"type": "array",
|
|
53
|
+
"items": { "type": "string" },
|
|
54
|
+
"description": "Slot keys exposed by the item's `classNames` prop"
|
|
55
|
+
},
|
|
56
|
+
"fieldWrappers": {
|
|
57
|
+
"type": "array",
|
|
58
|
+
"items": { "type": "string" },
|
|
59
|
+
"description": "TanStack Form field wrapper components this item exports"
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"additionalProperties": false
|
|
63
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://inai.dev/schemas/registry.json",
|
|
4
|
+
"title": "InAI Registry",
|
|
5
|
+
"description": "Top-level registry document listing components, blocks, and templates.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["version", "components", "blocks", "templates"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"version": { "type": "string" },
|
|
10
|
+
"components": { "type": "array", "items": { "$ref": "./registry-item.schema.json" } },
|
|
11
|
+
"blocks": { "type": "array", "items": { "$ref": "./registry-item.schema.json" } },
|
|
12
|
+
"templates": { "type": "array", "items": { "$ref": "./registry-item.schema.json" } }
|
|
13
|
+
},
|
|
14
|
+
"additionalProperties": false
|
|
15
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-registry federation types for InAI UI CLI (M3).
|
|
3
|
+
*
|
|
4
|
+
* A consumer project's `components.json` may declare multiple component
|
|
5
|
+
* sources ("registries") — the default `inai` one plus any number of
|
|
6
|
+
* private, git-hosted, or HTTPS registries. Components are resolved per
|
|
7
|
+
* registry, with optional `@namespace/` syntax in the `add` command to
|
|
8
|
+
* disambiguate between registries that expose the same component name.
|
|
9
|
+
*/
|
|
10
|
+
/** How a registry is physically sourced. */
|
|
11
|
+
export type RegistrySource = "local" | "git" | "https";
|
|
12
|
+
/** Bearer-token authentication for a private registry. */
|
|
13
|
+
export interface RegistryAuth {
|
|
14
|
+
type: "bearer";
|
|
15
|
+
/** Environment variable holding the token (preferred). */
|
|
16
|
+
tokenEnv?: string;
|
|
17
|
+
/** Absolute path to a file containing the token (fallback). */
|
|
18
|
+
tokenFile?: string;
|
|
19
|
+
}
|
|
20
|
+
/** A single registry entry in the federated `components.json`. */
|
|
21
|
+
export interface Registry {
|
|
22
|
+
/** Stable alias used in `@namespace/name` syntax (lowercase kebab-case). */
|
|
23
|
+
name: string;
|
|
24
|
+
source: RegistrySource;
|
|
25
|
+
/** Local filesystem path (for `source: "local"`). */
|
|
26
|
+
path?: string;
|
|
27
|
+
/** Git clone URL or HTTPS registry.json URL. */
|
|
28
|
+
url?: string;
|
|
29
|
+
/** Git branch (defaults to "main"). */
|
|
30
|
+
branch?: string;
|
|
31
|
+
/** Bearer-token auth config for private registries. */
|
|
32
|
+
auth?: RegistryAuth;
|
|
33
|
+
/** If true, this registry is used when a component name has no namespace. */
|
|
34
|
+
default?: boolean;
|
|
35
|
+
}
|
|
36
|
+
/** Parsed result of `parseComponentSpec` — namespace + type + name. */
|
|
37
|
+
export interface ResolvedComponentSpec {
|
|
38
|
+
/** Registry name from `@namespace/name` syntax, or null for the default. */
|
|
39
|
+
registry: string | null;
|
|
40
|
+
/** Component name (no prefixes). */
|
|
41
|
+
name: string;
|
|
42
|
+
/** Type derived from `block/` or `template/` prefix. */
|
|
43
|
+
type: "component" | "block" | "template";
|
|
44
|
+
}
|
|
45
|
+
/** Options passed to registry resolution. */
|
|
46
|
+
export interface ResolveRegistryOptions {
|
|
47
|
+
/** Use cached registry without network access. */
|
|
48
|
+
offline?: boolean;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/types/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,4CAA4C;AAC5C,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,KAAK,GAAG,OAAO,CAAC;AAEvD,0DAA0D;AAC1D,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,QAAQ,CAAC;IACf,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,kEAAkE;AAClE,MAAM,WAAW,QAAQ;IACvB,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,cAAc,CAAC;IACvB,qDAAqD;IACrD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,uEAAuE;AACvE,MAAM,WAAW,qBAAqB;IACpC,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,IAAI,EAAE,WAAW,GAAG,OAAO,GAAG,UAAU,CAAC;CAC1C;AAED,6CAA6C;AAC7C,MAAM,WAAW,sBAAsB;IACrC,kDAAkD;IAClD,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-registry federation types for InAI UI CLI (M3).
|
|
3
|
+
*
|
|
4
|
+
* A consumer project's `components.json` may declare multiple component
|
|
5
|
+
* sources ("registries") — the default `inai` one plus any number of
|
|
6
|
+
* private, git-hosted, or HTTPS registries. Components are resolved per
|
|
7
|
+
* registry, with optional `@namespace/` syntax in the `add` command to
|
|
8
|
+
* disambiguate between registries that expose the same component name.
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type PackageManager = "pnpm" | "npm" | "yarn" | "bun";
|
|
2
|
+
/**
|
|
3
|
+
* Detect which package manager a project uses by checking for lockfiles.
|
|
4
|
+
*/
|
|
5
|
+
export declare function detectPackageManager(projectDir: string): PackageManager;
|
|
6
|
+
/**
|
|
7
|
+
* Run the package manager's install command for the given dependencies.
|
|
8
|
+
* Returns the command that was executed for display purposes.
|
|
9
|
+
*/
|
|
10
|
+
export declare function autoInstallDeps(projectDir: string, deps: string[]): string | null;
|
|
11
|
+
//# sourceMappingURL=auto-install.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-install.d.ts","sourceRoot":"","sources":["../../src/utils/auto-install.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,CAAC;AAE7D;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,CAKvE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EAAE,GACb,MAAM,GAAG,IAAI,CAWf"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* Detect which package manager a project uses by checking for lockfiles.
|
|
6
|
+
*/
|
|
7
|
+
export function detectPackageManager(projectDir) {
|
|
8
|
+
if (fs.existsSync(path.join(projectDir, "pnpm-lock.yaml")))
|
|
9
|
+
return "pnpm";
|
|
10
|
+
if (fs.existsSync(path.join(projectDir, "bun.lockb")))
|
|
11
|
+
return "bun";
|
|
12
|
+
if (fs.existsSync(path.join(projectDir, "yarn.lock")))
|
|
13
|
+
return "yarn";
|
|
14
|
+
return "npm";
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Run the package manager's install command for the given dependencies.
|
|
18
|
+
* Returns the command that was executed for display purposes.
|
|
19
|
+
*/
|
|
20
|
+
export function autoInstallDeps(projectDir, deps) {
|
|
21
|
+
if (deps.length === 0)
|
|
22
|
+
return null;
|
|
23
|
+
const pm = detectPackageManager(projectDir);
|
|
24
|
+
const cmd = pm === "npm"
|
|
25
|
+
? `npm install ${deps.join(" ")}`
|
|
26
|
+
: `${pm} add ${deps.join(" ")}`;
|
|
27
|
+
execSync(cmd, { cwd: projectDir, stdio: "inherit" });
|
|
28
|
+
return cmd;
|
|
29
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type Framework = "next-app" | "next-pages" | "vite" | "remix" | "astro" | "tanstack-start" | "unknown";
|
|
2
|
+
/**
|
|
3
|
+
* Detect the React meta-framework in use for a given project directory.
|
|
4
|
+
* Order matters: Next / Remix / Astro win over a bare vite.config because
|
|
5
|
+
* those frameworks often *also* ship a vite config under the hood.
|
|
6
|
+
*/
|
|
7
|
+
export declare function detectFramework(cwd: string): Framework;
|
|
8
|
+
/**
|
|
9
|
+
* Default CSS entry point for a given framework. Used by `inai-ui init`
|
|
10
|
+
* to pre-populate the prompt that asks where tailwind/tokens should be
|
|
11
|
+
* imported.
|
|
12
|
+
*/
|
|
13
|
+
export declare function getCssEntryPoint(framework: Framework): string;
|
|
14
|
+
export declare function frameworkLabel(framework: Framework): string;
|
|
15
|
+
//# sourceMappingURL=framework-detect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"framework-detect.d.ts","sourceRoot":"","sources":["../../src/utils/framework-detect.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,SAAS,GACjB,UAAU,GACV,YAAY,GACZ,MAAM,GACN,OAAO,GACP,OAAO,GACP,gBAAgB,GAChB,SAAS,CAAC;AAEd;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAkDtD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,SAAS,GAAG,MAAM,CAiB7D;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,SAAS,GAAG,MAAM,CAiB3D"}
|