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.
- package/README.md +80 -0
- package/dist/commands/add.d.ts +42 -12
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +413 -93
- package/dist/commands/diff.d.ts +6 -0
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +90 -20
- package/dist/commands/init.d.ts +17 -5
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +87 -64
- 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 +171 -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 +40 -4
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +149 -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 +197 -6
- 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 +20 -0
- package/dist/utils/framework-detect.d.ts.map +1 -0
- package/dist/utils/framework-detect.js +97 -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/paths.d.ts +26 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +35 -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/dist/utils/tsconfig-detect.d.ts +18 -0
- package/dist/utils/tsconfig-detect.d.ts.map +1 -0
- package/dist/utils/tsconfig-detect.js +121 -0
- package/package.json +9 -3
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
6
|
+
import { runList } from "./list.js";
|
|
7
|
+
import { runDiff } from "./diff.js";
|
|
8
|
+
import { runAdd } from "./add.js";
|
|
9
|
+
import { runUpdate } from "./update.js";
|
|
10
|
+
import { runStatus } from "./status.js";
|
|
11
|
+
import { THEMES } from "../utils/themes.js";
|
|
12
|
+
import { resolveRegistryDir } from "../utils/registry-resolver.js";
|
|
13
|
+
import { readRegistryJson } from "./status.js";
|
|
14
|
+
// eslint-disable-next-line no-control-regex
|
|
15
|
+
const ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
16
|
+
function stripAnsi(s) {
|
|
17
|
+
return s.replace(ANSI_RE, "");
|
|
18
|
+
}
|
|
19
|
+
const MCP_CONFIG_SNIPPET = {
|
|
20
|
+
mcpServers: {
|
|
21
|
+
"inai-ui": {
|
|
22
|
+
command: "inai-ui",
|
|
23
|
+
args: ["mcp"],
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Print the JSON config fragment that users must drop into their Claude
|
|
29
|
+
* Desktop or Cursor `mcpServers` block. Invoked from `inai-ui mcp init`.
|
|
30
|
+
*/
|
|
31
|
+
export function printMcpConfig() {
|
|
32
|
+
console.log(JSON.stringify(MCP_CONFIG_SNIPPET, null, 2));
|
|
33
|
+
}
|
|
34
|
+
function textResult(text, isError = false) {
|
|
35
|
+
return {
|
|
36
|
+
content: [{ type: "text", text: stripAnsi(text) }],
|
|
37
|
+
...(isError ? { isError: true } : {}),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export async function handleListComponents(args, cwd) {
|
|
41
|
+
const registryDir = resolveRegistryDir(cwd);
|
|
42
|
+
const registryPath = path.join(registryDir, "registry.json");
|
|
43
|
+
const output = await runList(registryPath, { category: args?.category });
|
|
44
|
+
return textResult(output);
|
|
45
|
+
}
|
|
46
|
+
export async function handleViewComponent(args, cwd) {
|
|
47
|
+
const name = args?.name;
|
|
48
|
+
if (!name)
|
|
49
|
+
return textResult("Error: 'name' argument is required.", true);
|
|
50
|
+
const registryDir = resolveRegistryDir(cwd);
|
|
51
|
+
const registryPath = path.join(registryDir, "registry.json");
|
|
52
|
+
const registry = readRegistryJson(registryPath);
|
|
53
|
+
if (!registry)
|
|
54
|
+
return textResult("Error: Registry not found.", true);
|
|
55
|
+
const all = [
|
|
56
|
+
...registry.components,
|
|
57
|
+
...registry.blocks,
|
|
58
|
+
...registry.templates,
|
|
59
|
+
];
|
|
60
|
+
const component = all.find((c) => c.name === name);
|
|
61
|
+
if (!component) {
|
|
62
|
+
return textResult(`Error: Component "${name}" not found in registry.`, true);
|
|
63
|
+
}
|
|
64
|
+
const sections = [
|
|
65
|
+
`# ${component.name} (${component.type})`,
|
|
66
|
+
component.description,
|
|
67
|
+
"",
|
|
68
|
+
];
|
|
69
|
+
for (const rel of component.files) {
|
|
70
|
+
const abs = path.join(registryDir, rel);
|
|
71
|
+
if (!fs.existsSync(abs))
|
|
72
|
+
continue;
|
|
73
|
+
const src = fs.readFileSync(abs, "utf-8");
|
|
74
|
+
sections.push(`## ${rel}`, "```tsx", src, "```", "");
|
|
75
|
+
}
|
|
76
|
+
return textResult(sections.join("\n"));
|
|
77
|
+
}
|
|
78
|
+
export async function handleAddComponent(args, cwd) {
|
|
79
|
+
const name = args?.name;
|
|
80
|
+
if (!name)
|
|
81
|
+
return textResult("Error: 'name' argument is required.", true);
|
|
82
|
+
const result = await runAdd(name, cwd, { force: args?.force });
|
|
83
|
+
if (!result.added)
|
|
84
|
+
return textResult(result.message, true);
|
|
85
|
+
const lines = [result.message];
|
|
86
|
+
if (result.files.length > 0) {
|
|
87
|
+
lines.push("Files:");
|
|
88
|
+
for (const f of result.files)
|
|
89
|
+
lines.push(` - ${f}`);
|
|
90
|
+
}
|
|
91
|
+
if (result.resolvedDeps && result.resolvedDeps.length > 0) {
|
|
92
|
+
lines.push("Resolved registry dependencies:");
|
|
93
|
+
for (const d of result.resolvedDeps)
|
|
94
|
+
lines.push(` - ${d}`);
|
|
95
|
+
}
|
|
96
|
+
if (result.npmDeps.length > 0) {
|
|
97
|
+
lines.push(`npm deps: ${result.npmDeps.join(", ")}`);
|
|
98
|
+
}
|
|
99
|
+
return textResult(lines.join("\n"));
|
|
100
|
+
}
|
|
101
|
+
export async function handleDiffComponent(args, cwd) {
|
|
102
|
+
const name = args?.name;
|
|
103
|
+
if (!name)
|
|
104
|
+
return textResult("Error: 'name' argument is required.", true);
|
|
105
|
+
const output = stripAnsi(await runDiff(name, cwd));
|
|
106
|
+
// Count +/- lines so Claude Desktop / Cursor can see the magnitude of the
|
|
107
|
+
// change at a glance without re-parsing the diff body.
|
|
108
|
+
let added = 0;
|
|
109
|
+
let removed = 0;
|
|
110
|
+
for (const line of output.split("\n")) {
|
|
111
|
+
if (/^\+(?!\+\+)/.test(line))
|
|
112
|
+
added++;
|
|
113
|
+
else if (/^-(?!--)/.test(line))
|
|
114
|
+
removed++;
|
|
115
|
+
}
|
|
116
|
+
const lower = output.toLowerCase();
|
|
117
|
+
const hasNoChanges = lower.includes("no differences") || lower.includes("no changes");
|
|
118
|
+
const summary = hasNoChanges
|
|
119
|
+
? "_No differences between local and registry versions._"
|
|
120
|
+
: `**${added} line${added === 1 ? "" : "s"} added, ${removed} line${removed === 1 ? "" : "s"} removed.**`;
|
|
121
|
+
const markdown = [
|
|
122
|
+
`### Diff: \`${name}\``,
|
|
123
|
+
"",
|
|
124
|
+
summary,
|
|
125
|
+
"",
|
|
126
|
+
"```diff",
|
|
127
|
+
output,
|
|
128
|
+
"```",
|
|
129
|
+
].join("\n");
|
|
130
|
+
return {
|
|
131
|
+
content: [{ type: "text", text: markdown }],
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
export async function handleUpdateComponent(args, cwd) {
|
|
135
|
+
const name = args?.name;
|
|
136
|
+
if (!name)
|
|
137
|
+
return textResult("Error: 'name' argument is required.", true);
|
|
138
|
+
// Non-interactive: the MCP transport cannot drive prompts. The update
|
|
139
|
+
// path will do a 3-way merge via the saved snapshot when available.
|
|
140
|
+
const result = await runUpdate(name, cwd, true);
|
|
141
|
+
const lines = [result.message];
|
|
142
|
+
if (result.cleanMerges && result.cleanMerges.length > 0) {
|
|
143
|
+
lines.push("Merged cleanly:");
|
|
144
|
+
for (const f of result.cleanMerges)
|
|
145
|
+
lines.push(` - ${f}`);
|
|
146
|
+
}
|
|
147
|
+
if (result.conflictedFiles && result.conflictedFiles.length > 0) {
|
|
148
|
+
lines.push("Merged with conflicts (manual resolution required):");
|
|
149
|
+
for (const f of result.conflictedFiles)
|
|
150
|
+
lines.push(` ! ${f}`);
|
|
151
|
+
}
|
|
152
|
+
return textResult(lines.join("\n"), !result.updated);
|
|
153
|
+
}
|
|
154
|
+
export async function handleListThemes() {
|
|
155
|
+
return textResult(JSON.stringify(THEMES, null, 2));
|
|
156
|
+
}
|
|
157
|
+
export async function handleStatus(args, cwd) {
|
|
158
|
+
const output = await runStatus(cwd, { json: args?.json ?? true });
|
|
159
|
+
return textResult(output);
|
|
160
|
+
}
|
|
161
|
+
// ─── MCP server bootstrap ────────────────────────────────────────────
|
|
162
|
+
export const MCP_TOOLS = [
|
|
163
|
+
{
|
|
164
|
+
name: "list_components",
|
|
165
|
+
description: "List all available components, blocks and templates in the InAI UI registry.",
|
|
166
|
+
inputSchema: {
|
|
167
|
+
type: "object",
|
|
168
|
+
properties: {
|
|
169
|
+
category: {
|
|
170
|
+
type: "string",
|
|
171
|
+
description: "Optional type filter (e.g. 'component', 'form', 'block').",
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: "view_component",
|
|
178
|
+
description: "View the source code of a component from the registry.",
|
|
179
|
+
inputSchema: {
|
|
180
|
+
type: "object",
|
|
181
|
+
properties: {
|
|
182
|
+
name: { type: "string", description: "Component name" },
|
|
183
|
+
},
|
|
184
|
+
required: ["name"],
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
name: "add_component",
|
|
189
|
+
description: "Install a component (and its transitive registry dependencies) into the current project.",
|
|
190
|
+
inputSchema: {
|
|
191
|
+
type: "object",
|
|
192
|
+
properties: {
|
|
193
|
+
name: { type: "string", description: "Component name" },
|
|
194
|
+
force: {
|
|
195
|
+
type: "boolean",
|
|
196
|
+
description: "Overwrite existing files without prompting.",
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
required: ["name"],
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
name: "diff_component",
|
|
204
|
+
description: "Show a unified diff between the locally-installed component and the registry version.",
|
|
205
|
+
inputSchema: {
|
|
206
|
+
type: "object",
|
|
207
|
+
properties: {
|
|
208
|
+
name: { type: "string", description: "Component name" },
|
|
209
|
+
},
|
|
210
|
+
required: ["name"],
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
name: "update_component",
|
|
215
|
+
description: "Update an installed component to the latest registry version using a 3-way merge against the snapshot captured at install time. Emits conflict markers when local and registry edits overlap.",
|
|
216
|
+
inputSchema: {
|
|
217
|
+
type: "object",
|
|
218
|
+
properties: {
|
|
219
|
+
name: { type: "string", description: "Component name" },
|
|
220
|
+
},
|
|
221
|
+
required: ["name"],
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
name: "list_themes",
|
|
226
|
+
description: "List the available InAI UI themes with their categories and descriptions.",
|
|
227
|
+
inputSchema: { type: "object", properties: {} },
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
name: "project_status",
|
|
231
|
+
description: "Return the InAI UI project status (HEALTHY / DRIFT / OUTDATED / MISSING per component).",
|
|
232
|
+
inputSchema: {
|
|
233
|
+
type: "object",
|
|
234
|
+
properties: {
|
|
235
|
+
json: { type: "boolean", description: "Return JSON payload (default true)." },
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
];
|
|
240
|
+
export async function runMcp(cwd = process.cwd()) {
|
|
241
|
+
const server = new Server({ name: "inai-ui-mcp", version: "0.1.0" }, { capabilities: { tools: {} } });
|
|
242
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
243
|
+
tools: MCP_TOOLS.map((t) => ({ ...t })),
|
|
244
|
+
}));
|
|
245
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
246
|
+
server.setRequestHandler(CallToolRequestSchema, (async (req) => {
|
|
247
|
+
const { name, arguments: args } = req.params;
|
|
248
|
+
try {
|
|
249
|
+
switch (name) {
|
|
250
|
+
case "list_components":
|
|
251
|
+
return await handleListComponents(args, cwd);
|
|
252
|
+
case "view_component":
|
|
253
|
+
return await handleViewComponent(args, cwd);
|
|
254
|
+
case "add_component":
|
|
255
|
+
return await handleAddComponent(args, cwd);
|
|
256
|
+
case "diff_component":
|
|
257
|
+
return await handleDiffComponent(args, cwd);
|
|
258
|
+
case "update_component":
|
|
259
|
+
return await handleUpdateComponent(args, cwd);
|
|
260
|
+
case "list_themes":
|
|
261
|
+
return await handleListThemes();
|
|
262
|
+
case "project_status":
|
|
263
|
+
return await handleStatus(args, cwd);
|
|
264
|
+
default:
|
|
265
|
+
return {
|
|
266
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
267
|
+
isError: true,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
273
|
+
return {
|
|
274
|
+
content: [{ type: "text", text: `Tool "${name}" failed: ${msg}` }],
|
|
275
|
+
isError: true,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
279
|
+
}));
|
|
280
|
+
const transport = new StdioServerTransport();
|
|
281
|
+
await server.connect(transport);
|
|
282
|
+
}
|
|
283
|
+
export async function mcpCommand(subcommand) {
|
|
284
|
+
if (subcommand === "init") {
|
|
285
|
+
printMcpConfig();
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
await runMcp();
|
|
289
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A single rename rule applied by the `inai-ui migrate` codemod.
|
|
3
|
+
*
|
|
4
|
+
* The patterns are anchored on a word boundary and an `=` or whitespace
|
|
5
|
+
* followed by `=` so they match JSX props (`inputSize="md"`, `inputSize={x}`)
|
|
6
|
+
* without touching unrelated identifiers that happen to share the prefix.
|
|
7
|
+
*/
|
|
8
|
+
export interface MigrationRule {
|
|
9
|
+
name: string;
|
|
10
|
+
pattern: RegExp;
|
|
11
|
+
replacement: string;
|
|
12
|
+
description: string;
|
|
13
|
+
}
|
|
14
|
+
export declare const MIGRATION_RULES: MigrationRule[];
|
|
15
|
+
export interface MigrateOptions {
|
|
16
|
+
dryRun?: boolean;
|
|
17
|
+
files?: string[];
|
|
18
|
+
cwd?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface MigrateResult {
|
|
21
|
+
filesAffected: number;
|
|
22
|
+
totalReplacements: number;
|
|
23
|
+
perRule: Record<string, number>;
|
|
24
|
+
touchedFiles: string[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Recursively walk `root` and collect every `.ts`/`.tsx`/`.js`/`.jsx` file
|
|
28
|
+
* outside of the standard ignore list. Exported so tests can drive discovery
|
|
29
|
+
* deterministically against a tmpdir fixture.
|
|
30
|
+
*/
|
|
31
|
+
export declare function discoverSourceFiles(root: string): Promise<string[]>;
|
|
32
|
+
export declare function runMigrate(options?: MigrateOptions): Promise<MigrateResult>;
|
|
33
|
+
/**
|
|
34
|
+
* CLI entry point — wraps `runMigrate` with console output. Kept separate
|
|
35
|
+
* from `runMigrate` so tests can assert on the structured result without
|
|
36
|
+
* parsing stdout.
|
|
37
|
+
*/
|
|
38
|
+
export declare function migrateCommand(options?: {
|
|
39
|
+
dryRun?: boolean;
|
|
40
|
+
}): Promise<void>;
|
|
41
|
+
//# sourceMappingURL=migrate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/commands/migrate.ts"],"names":[],"mappings":"AAIA;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,eAAe,EAAE,aAAa,EAyB1C,CAAC;AAEF,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAeD;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAqBzE;AAED,wBAAsB,UAAU,CAC9B,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,aAAa,CAAC,CA8CxB;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC,OAAO,CAAC,IAAI,CAAC,CAwBf"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { readFile, writeFile, readdir } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
export const MIGRATION_RULES = [
|
|
5
|
+
{
|
|
6
|
+
name: "inputSize-to-size",
|
|
7
|
+
pattern: /\binputSize(\s*=)/g,
|
|
8
|
+
replacement: "size$1",
|
|
9
|
+
description: "Input: inputSize prop renamed to size",
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
name: "selectSize-to-size",
|
|
13
|
+
pattern: /\bselectSize(\s*=)/g,
|
|
14
|
+
replacement: "size$1",
|
|
15
|
+
description: "Select: selectSize prop renamed to size",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: "comboBoxSize-to-size",
|
|
19
|
+
pattern: /\bcomboBoxSize(\s*=)/g,
|
|
20
|
+
replacement: "size$1",
|
|
21
|
+
description: "ComboBox: comboBoxSize prop renamed to size",
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: "checkboxState-to-state",
|
|
25
|
+
pattern: /\bcheckboxState(\s*=)/g,
|
|
26
|
+
replacement: "state$1",
|
|
27
|
+
description: "Checkbox: checkboxState prop renamed to state",
|
|
28
|
+
},
|
|
29
|
+
];
|
|
30
|
+
const IGNORE_DIRS = new Set([
|
|
31
|
+
"node_modules",
|
|
32
|
+
".git",
|
|
33
|
+
"dist",
|
|
34
|
+
"build",
|
|
35
|
+
".next",
|
|
36
|
+
".turbo",
|
|
37
|
+
".inai-ui",
|
|
38
|
+
"coverage",
|
|
39
|
+
".vercel",
|
|
40
|
+
".cache",
|
|
41
|
+
]);
|
|
42
|
+
/**
|
|
43
|
+
* Recursively walk `root` and collect every `.ts`/`.tsx`/`.js`/`.jsx` file
|
|
44
|
+
* outside of the standard ignore list. Exported so tests can drive discovery
|
|
45
|
+
* deterministically against a tmpdir fixture.
|
|
46
|
+
*/
|
|
47
|
+
export async function discoverSourceFiles(root) {
|
|
48
|
+
const out = [];
|
|
49
|
+
async function walk(dir) {
|
|
50
|
+
let entries;
|
|
51
|
+
try {
|
|
52
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
for (const entry of entries) {
|
|
58
|
+
if (IGNORE_DIRS.has(entry.name))
|
|
59
|
+
continue;
|
|
60
|
+
const full = path.join(dir, entry.name);
|
|
61
|
+
if (entry.isDirectory()) {
|
|
62
|
+
await walk(full);
|
|
63
|
+
}
|
|
64
|
+
else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
|
|
65
|
+
out.push(full);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
await walk(root);
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
export async function runMigrate(options = {}) {
|
|
73
|
+
const { dryRun = false, files, cwd = process.cwd() } = options;
|
|
74
|
+
const targets = files && files.length > 0 ? files : await discoverSourceFiles(cwd);
|
|
75
|
+
const perRule = {};
|
|
76
|
+
const touchedFiles = [];
|
|
77
|
+
let totalReplacements = 0;
|
|
78
|
+
for (const file of targets) {
|
|
79
|
+
let content;
|
|
80
|
+
try {
|
|
81
|
+
content = await readFile(file, "utf-8");
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
let modified = content;
|
|
87
|
+
let fileHadChanges = false;
|
|
88
|
+
for (const rule of MIGRATION_RULES) {
|
|
89
|
+
// Reset lastIndex is unnecessary for a new `match()` call, but using
|
|
90
|
+
// `exec` in a loop would require it — stick with a single `match`.
|
|
91
|
+
const matches = modified.match(rule.pattern);
|
|
92
|
+
if (matches && matches.length > 0) {
|
|
93
|
+
modified = modified.replace(rule.pattern, rule.replacement);
|
|
94
|
+
perRule[rule.name] = (perRule[rule.name] ?? 0) + matches.length;
|
|
95
|
+
totalReplacements += matches.length;
|
|
96
|
+
fileHadChanges = true;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (fileHadChanges) {
|
|
100
|
+
touchedFiles.push(file);
|
|
101
|
+
if (!dryRun) {
|
|
102
|
+
await writeFile(file, modified, "utf-8");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
filesAffected: touchedFiles.length,
|
|
108
|
+
totalReplacements,
|
|
109
|
+
perRule,
|
|
110
|
+
touchedFiles,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* CLI entry point — wraps `runMigrate` with console output. Kept separate
|
|
115
|
+
* from `runMigrate` so tests can assert on the structured result without
|
|
116
|
+
* parsing stdout.
|
|
117
|
+
*/
|
|
118
|
+
export async function migrateCommand(options = {}) {
|
|
119
|
+
const result = await runMigrate({ dryRun: !!options.dryRun });
|
|
120
|
+
for (const file of result.touchedFiles) {
|
|
121
|
+
if (options.dryRun) {
|
|
122
|
+
console.log(chalk.dim(`[dry-run] would update ${file}`));
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
console.log(chalk.green(`\u2713 ${file}`));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
console.log("");
|
|
129
|
+
console.log(chalk.bold(`Migration complete${options.dryRun ? " (dry run)" : ""}:`));
|
|
130
|
+
console.log(` files affected: ${result.filesAffected}`);
|
|
131
|
+
console.log(` replacements: ${result.totalReplacements}`);
|
|
132
|
+
for (const [name, count] of Object.entries(result.perRule)) {
|
|
133
|
+
console.log(` ${name}: ${count}`);
|
|
134
|
+
}
|
|
135
|
+
if (options.dryRun && result.totalReplacements > 0) {
|
|
136
|
+
console.log("");
|
|
137
|
+
console.log(chalk.yellow("Run without --dry-run to apply changes."));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { RegistrySource } from "../types/registry.js";
|
|
2
|
+
export interface RegistriesListResult {
|
|
3
|
+
output: string;
|
|
4
|
+
count: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Render the list of configured registries as a human-readable block.
|
|
8
|
+
* Tests call `runRegistriesList` for the returned string; the CLI
|
|
9
|
+
* action prints it.
|
|
10
|
+
*/
|
|
11
|
+
export declare function runRegistriesList(cwd?: string): Promise<RegistriesListResult>;
|
|
12
|
+
export interface RegistriesAddOptions {
|
|
13
|
+
cwd?: string;
|
|
14
|
+
name: string;
|
|
15
|
+
source?: RegistrySource;
|
|
16
|
+
url?: string;
|
|
17
|
+
path?: string;
|
|
18
|
+
branch?: string;
|
|
19
|
+
tokenEnv?: string;
|
|
20
|
+
tokenFile?: string;
|
|
21
|
+
default?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface RegistriesMutationResult {
|
|
24
|
+
ok: boolean;
|
|
25
|
+
message: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Append a new registry to `components.json`. Enforces unique names
|
|
29
|
+
* and basic source-specific field validation so the resulting
|
|
30
|
+
* configuration is resolvable.
|
|
31
|
+
*/
|
|
32
|
+
export declare function runRegistriesAdd(options: RegistriesAddOptions): Promise<RegistriesMutationResult>;
|
|
33
|
+
/** Remove a registry by name. Refuses to remove the last registry. */
|
|
34
|
+
export declare function runRegistriesRemove(name: string, cwd?: string): Promise<RegistriesMutationResult>;
|
|
35
|
+
export declare function registriesListCommand(): Promise<void>;
|
|
36
|
+
export declare function registriesAddCommand(name: string, opts: {
|
|
37
|
+
source?: string;
|
|
38
|
+
url?: string;
|
|
39
|
+
path?: string;
|
|
40
|
+
branch?: string;
|
|
41
|
+
tokenEnv?: string;
|
|
42
|
+
tokenFile?: string;
|
|
43
|
+
default?: boolean;
|
|
44
|
+
}): Promise<void>;
|
|
45
|
+
export declare function registriesRemoveCommand(name: string): Promise<void>;
|
|
46
|
+
//# sourceMappingURL=registries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registries.d.ts","sourceRoot":"","sources":["../../src/commands/registries.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAY,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAErE,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,GAAE,MAAsB,GAC1B,OAAO,CAAC,oBAAoB,CAAC,CA0B/B;AAED,MAAM,WAAW,oBAAoB;IACnC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,wBAAwB;IACvC,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,wBAAwB,CAAC,CA8DnC;AAED,sEAAsE;AACtE,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,MAAM,EACZ,GAAG,GAAE,MAAsB,GAC1B,OAAO,CAAC,wBAAwB,CAAC,CAmBnC;AAED,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC,CAG3D;AAED,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE;IACJ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,GACA,OAAO,CAAC,IAAI,CAAC,CAiBf;AAED,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQzE"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { loadRegistries, saveRegistries, getRegistryByName, } from "../utils/registry-config.js";
|
|
3
|
+
/**
|
|
4
|
+
* Render the list of configured registries as a human-readable block.
|
|
5
|
+
* Tests call `runRegistriesList` for the returned string; the CLI
|
|
6
|
+
* action prints it.
|
|
7
|
+
*/
|
|
8
|
+
export async function runRegistriesList(cwd = process.cwd()) {
|
|
9
|
+
const registries = await loadRegistries(cwd);
|
|
10
|
+
if (registries.length === 0) {
|
|
11
|
+
return {
|
|
12
|
+
output: chalk.yellow("No registries configured. Run `inai-ui init` first."),
|
|
13
|
+
count: 0,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
const lines = [];
|
|
17
|
+
lines.push(chalk.bold(`\nConfigured registries (${registries.length}):\n`));
|
|
18
|
+
for (const r of registries) {
|
|
19
|
+
const defaultMark = r.default ? chalk.cyan(" [default]") : "";
|
|
20
|
+
const lock = r.auth ? chalk.dim(" [auth]") : "";
|
|
21
|
+
lines.push(` ${chalk.bold(r.name)}${defaultMark}${lock}`);
|
|
22
|
+
lines.push(` source: ${r.source}`);
|
|
23
|
+
if (r.path)
|
|
24
|
+
lines.push(` path: ${r.path}`);
|
|
25
|
+
if (r.url)
|
|
26
|
+
lines.push(` url: ${r.url}`);
|
|
27
|
+
if (r.branch)
|
|
28
|
+
lines.push(` branch: ${r.branch}`);
|
|
29
|
+
if (r.auth?.tokenEnv)
|
|
30
|
+
lines.push(` token: env:${r.auth.tokenEnv}`);
|
|
31
|
+
else if (r.auth?.tokenFile)
|
|
32
|
+
lines.push(` token: file:${r.auth.tokenFile}`);
|
|
33
|
+
lines.push("");
|
|
34
|
+
}
|
|
35
|
+
return { output: lines.join("\n"), count: registries.length };
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Append a new registry to `components.json`. Enforces unique names
|
|
39
|
+
* and basic source-specific field validation so the resulting
|
|
40
|
+
* configuration is resolvable.
|
|
41
|
+
*/
|
|
42
|
+
export async function runRegistriesAdd(options) {
|
|
43
|
+
const cwd = options.cwd ?? process.cwd();
|
|
44
|
+
const source = options.source ?? "git";
|
|
45
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(options.name)) {
|
|
46
|
+
return {
|
|
47
|
+
ok: false,
|
|
48
|
+
message: `Invalid registry name "${options.name}" — use lowercase letters, numbers, and hyphens.`,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const registries = await loadRegistries(cwd);
|
|
52
|
+
if (getRegistryByName(registries, options.name)) {
|
|
53
|
+
return {
|
|
54
|
+
ok: false,
|
|
55
|
+
message: `Registry "${options.name}" already exists.`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if ((source === "git" || source === "https") && !options.url) {
|
|
59
|
+
return {
|
|
60
|
+
ok: false,
|
|
61
|
+
message: `Registry source "${source}" requires --url.`,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (source === "local" && !options.path) {
|
|
65
|
+
return {
|
|
66
|
+
ok: false,
|
|
67
|
+
message: `Registry source "local" requires --path.`,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const next = {
|
|
71
|
+
name: options.name,
|
|
72
|
+
source,
|
|
73
|
+
...(options.path ? { path: options.path } : {}),
|
|
74
|
+
...(options.url ? { url: options.url } : {}),
|
|
75
|
+
...(source === "git" ? { branch: options.branch ?? "main" } : {}),
|
|
76
|
+
...(options.tokenEnv || options.tokenFile
|
|
77
|
+
? {
|
|
78
|
+
auth: {
|
|
79
|
+
type: "bearer",
|
|
80
|
+
...(options.tokenEnv ? { tokenEnv: options.tokenEnv } : {}),
|
|
81
|
+
...(options.tokenFile ? { tokenFile: options.tokenFile } : {}),
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
: {}),
|
|
85
|
+
...(options.default ? { default: true } : {}),
|
|
86
|
+
};
|
|
87
|
+
// If caller marked this as default, clear any prior default.
|
|
88
|
+
let updated = registries;
|
|
89
|
+
if (next.default) {
|
|
90
|
+
updated = registries.map((r) => ({ ...r, default: false }));
|
|
91
|
+
}
|
|
92
|
+
updated = [...updated, next];
|
|
93
|
+
saveRegistries(cwd, updated);
|
|
94
|
+
return {
|
|
95
|
+
ok: true,
|
|
96
|
+
message: `Registry "${options.name}" added.`,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/** Remove a registry by name. Refuses to remove the last registry. */
|
|
100
|
+
export async function runRegistriesRemove(name, cwd = process.cwd()) {
|
|
101
|
+
const registries = await loadRegistries(cwd);
|
|
102
|
+
if (!getRegistryByName(registries, name)) {
|
|
103
|
+
return { ok: false, message: `Registry "${name}" not found.` };
|
|
104
|
+
}
|
|
105
|
+
const filtered = registries.filter((r) => r.name !== name);
|
|
106
|
+
if (filtered.length === 0) {
|
|
107
|
+
return {
|
|
108
|
+
ok: false,
|
|
109
|
+
message: `Cannot remove the last registry — add another one first.`,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
// If we removed the default, promote the first remaining to default.
|
|
113
|
+
const removed = registries.find((r) => r.name === name);
|
|
114
|
+
if (removed.default && !filtered.some((r) => r.default)) {
|
|
115
|
+
filtered[0] = { ...filtered[0], default: true };
|
|
116
|
+
}
|
|
117
|
+
saveRegistries(cwd, filtered);
|
|
118
|
+
return { ok: true, message: `Registry "${name}" removed.` };
|
|
119
|
+
}
|
|
120
|
+
export async function registriesListCommand() {
|
|
121
|
+
const { output } = await runRegistriesList();
|
|
122
|
+
console.log(output);
|
|
123
|
+
}
|
|
124
|
+
export async function registriesAddCommand(name, opts) {
|
|
125
|
+
const result = await runRegistriesAdd({
|
|
126
|
+
name,
|
|
127
|
+
source: opts.source ?? "git",
|
|
128
|
+
url: opts.url,
|
|
129
|
+
path: opts.path,
|
|
130
|
+
branch: opts.branch,
|
|
131
|
+
tokenEnv: opts.tokenEnv,
|
|
132
|
+
tokenFile: opts.tokenFile,
|
|
133
|
+
default: opts.default,
|
|
134
|
+
});
|
|
135
|
+
if (result.ok) {
|
|
136
|
+
console.log(chalk.green(result.message));
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
console.error(chalk.red(result.message));
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
export async function registriesRemoveCommand(name) {
|
|
144
|
+
const result = await runRegistriesRemove(name);
|
|
145
|
+
if (result.ok) {
|
|
146
|
+
console.log(chalk.green(result.message));
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
console.error(chalk.red(result.message));
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface RemoveOptions {
|
|
2
|
+
force?: boolean;
|
|
3
|
+
}
|
|
4
|
+
export interface RemoveResult {
|
|
5
|
+
removed: boolean;
|
|
6
|
+
message: string;
|
|
7
|
+
deletedFiles: string[];
|
|
8
|
+
}
|
|
9
|
+
export declare function runRemove(componentName: string, rootDir: string, options?: RemoveOptions): Promise<RemoveResult>;
|
|
10
|
+
export declare function removeCommand(componentName: string, options?: RemoveOptions): Promise<void>;
|
|
11
|
+
//# sourceMappingURL=remove.d.ts.map
|