@microbuild/cli 0.1.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 +555 -0
- package/dist/chunk-6YA3DSAE.js +362 -0
- package/dist/chunk-6YA3DSAE.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2279 -0
- package/dist/index.js.map +1 -0
- package/dist/outdated-TV5ERBNC.js +110 -0
- package/dist/outdated-TV5ERBNC.js.map +1 -0
- package/dist/templates/api/auth-callback-route.ts +36 -0
- package/dist/templates/api/auth-headers.ts +72 -0
- package/dist/templates/api/auth-login-route.ts +63 -0
- package/dist/templates/api/auth-logout-route.ts +41 -0
- package/dist/templates/api/auth-user-route.ts +71 -0
- package/dist/templates/api/fields-route.ts +44 -0
- package/dist/templates/api/files-id-route.ts +116 -0
- package/dist/templates/api/files-route.ts +83 -0
- package/dist/templates/api/items-id-route.ts +120 -0
- package/dist/templates/api/items-route.ts +88 -0
- package/dist/templates/api/login-page.tsx +142 -0
- package/dist/templates/api/relations-route.ts +46 -0
- package/dist/templates/app/design-tokens.css +183 -0
- package/dist/templates/app/globals.css +58 -0
- package/dist/templates/app/layout.tsx +49 -0
- package/dist/templates/app/page.tsx +23 -0
- package/dist/templates/components/ColorSchemeToggle.tsx +35 -0
- package/dist/templates/lib/common-utils.ts +156 -0
- package/dist/templates/lib/hooks/index.ts +98 -0
- package/dist/templates/lib/services/index.ts +26 -0
- package/dist/templates/lib/theme.ts +241 -0
- package/dist/templates/lib/types/index.ts +10 -0
- package/dist/templates/lib/utils-index.ts +32 -0
- package/dist/templates/lib/utils.ts +14 -0
- package/dist/templates/lib/vform/index.ts +24 -0
- package/dist/templates/middleware/middleware.ts +29 -0
- package/dist/templates/supabase/client.ts +25 -0
- package/dist/templates/supabase/middleware.ts +66 -0
- package/dist/templates/supabase/server.ts +45 -0
- package/package.json +61 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2279 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getRegistry,
|
|
4
|
+
init,
|
|
5
|
+
loadConfig,
|
|
6
|
+
resolveSourceFile,
|
|
7
|
+
saveConfig,
|
|
8
|
+
sourceFileExists
|
|
9
|
+
} from "./chunk-6YA3DSAE.js";
|
|
10
|
+
|
|
11
|
+
// src/index.ts
|
|
12
|
+
import { Command } from "commander";
|
|
13
|
+
|
|
14
|
+
// src/commands/add.ts
|
|
15
|
+
import fs2 from "fs-extra";
|
|
16
|
+
import path2 from "path";
|
|
17
|
+
import chalk2 from "chalk";
|
|
18
|
+
import ora2 from "ora";
|
|
19
|
+
import prompts from "prompts";
|
|
20
|
+
|
|
21
|
+
// src/commands/transformer.ts
|
|
22
|
+
function getImportMappings(config) {
|
|
23
|
+
const libAlias = config.aliases.lib;
|
|
24
|
+
const componentsAlias = config.aliases.components;
|
|
25
|
+
return [
|
|
26
|
+
// Types
|
|
27
|
+
{
|
|
28
|
+
from: /from ['"]@microbuild\/types['"]/g,
|
|
29
|
+
to: `from '${libAlias}/types'`
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
from: /from ['"]@microbuild\/types\/([^'"]+)['"]/g,
|
|
33
|
+
to: `from '${libAlias}/types/$1'`
|
|
34
|
+
},
|
|
35
|
+
// Services
|
|
36
|
+
{
|
|
37
|
+
from: /from ['"]@microbuild\/services['"]/g,
|
|
38
|
+
to: `from '${libAlias}/services'`
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
from: /from ['"]@microbuild\/services\/([^'"]+)['"]/g,
|
|
42
|
+
to: `from '${libAlias}/services/$1'`
|
|
43
|
+
},
|
|
44
|
+
// Hooks
|
|
45
|
+
{
|
|
46
|
+
from: /from ['"]@microbuild\/hooks['"]/g,
|
|
47
|
+
to: `from '${libAlias}/hooks'`
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
from: /from ['"]@microbuild\/hooks\/([^'"]+)['"]/g,
|
|
51
|
+
to: `from '${libAlias}/hooks/$1'`
|
|
52
|
+
},
|
|
53
|
+
// UI Interfaces (component to component imports)
|
|
54
|
+
{
|
|
55
|
+
from: /from ['"]@microbuild\/ui-interfaces['"]/g,
|
|
56
|
+
to: `from '${componentsAlias}'`
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
from: /from ['"]@microbuild\/ui-interfaces\/([^'"]+)['"]/g,
|
|
60
|
+
to: `from '${componentsAlias}/$1'`
|
|
61
|
+
},
|
|
62
|
+
// UI Collections (component to component imports)
|
|
63
|
+
{
|
|
64
|
+
from: /from ['"]@microbuild\/ui-collections['"]/g,
|
|
65
|
+
to: `from '${componentsAlias}'`
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
from: /from ['"]@microbuild\/ui-collections\/([^'"]+)['"]/g,
|
|
69
|
+
to: `from '${componentsAlias}/$1'`
|
|
70
|
+
},
|
|
71
|
+
// Utils
|
|
72
|
+
{
|
|
73
|
+
from: /from ['"]@microbuild\/utils['"]/g,
|
|
74
|
+
to: `from '${libAlias}/utils'`
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
from: /from ['"]@microbuild\/utils\/([^'"]+)['"]/g,
|
|
78
|
+
to: `from '${libAlias}/utils/$1'`
|
|
79
|
+
},
|
|
80
|
+
// UI Form (VForm and related components)
|
|
81
|
+
{
|
|
82
|
+
from: /from ['"]@microbuild\/ui-form['"]/g,
|
|
83
|
+
to: `from '${componentsAlias}/vform'`
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
from: /from ['"]@microbuild\/ui-form\/([^'"]+)['"]/g,
|
|
87
|
+
to: `from '${componentsAlias}/vform/$1'`
|
|
88
|
+
},
|
|
89
|
+
// Import type statements
|
|
90
|
+
{
|
|
91
|
+
from: /import type \{([^}]+)\} from ['"]@microbuild\/types['"]/g,
|
|
92
|
+
to: `import type {$1} from '${libAlias}/types'`
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
from: /import type \{([^}]+)\} from ['"]@microbuild\/hooks['"]/g,
|
|
96
|
+
to: `import type {$1} from '${libAlias}/hooks'`
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
from: /import type \{([^}]+)\} from ['"]@microbuild\/services['"]/g,
|
|
100
|
+
to: `import type {$1} from '${libAlias}/services'`
|
|
101
|
+
},
|
|
102
|
+
// Import type for utils
|
|
103
|
+
{
|
|
104
|
+
from: /import type \{([^}]+)\} from ['"]@microbuild\/utils['"]/g,
|
|
105
|
+
to: `import type {$1} from '${libAlias}/utils'`
|
|
106
|
+
},
|
|
107
|
+
// Import type for ui-form
|
|
108
|
+
{
|
|
109
|
+
from: /import type \{([^}]+)\} from ['"]@microbuild\/ui-form['"]/g,
|
|
110
|
+
to: `import type {$1} from '${componentsAlias}/vform'`
|
|
111
|
+
},
|
|
112
|
+
// Dynamic imports - import('@microbuild/services') etc.
|
|
113
|
+
{
|
|
114
|
+
from: /import\s*\(\s*['"]@microbuild\/services['"]\s*\)/g,
|
|
115
|
+
to: `import('${libAlias}/services')`
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
from: /import\s*\(\s*['"]@microbuild\/hooks['"]\s*\)/g,
|
|
119
|
+
to: `import('${libAlias}/hooks')`
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
from: /import\s*\(\s*['"]@microbuild\/types['"]\s*\)/g,
|
|
123
|
+
to: `import('${libAlias}/types')`
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
from: /import\s*\(\s*['"]@microbuild\/utils['"]\s*\)/g,
|
|
127
|
+
to: `import('${libAlias}/utils')`
|
|
128
|
+
}
|
|
129
|
+
];
|
|
130
|
+
}
|
|
131
|
+
function transformImports(content, config, targetPath) {
|
|
132
|
+
const mappings = getImportMappings(config);
|
|
133
|
+
let result = content;
|
|
134
|
+
for (const mapping of mappings) {
|
|
135
|
+
result = result.replace(mapping.from, mapping.to);
|
|
136
|
+
}
|
|
137
|
+
result = normalizeImportPaths(result, targetPath);
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
function toKebabCase(str) {
|
|
141
|
+
return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
|
|
142
|
+
}
|
|
143
|
+
function normalizeImportPaths(content, targetPath) {
|
|
144
|
+
if (content.includes("@microbuild-preserve-casing")) {
|
|
145
|
+
return content;
|
|
146
|
+
}
|
|
147
|
+
if (targetPath && (targetPath.includes("/vform/") || targetPath.includes("/ui-form/") || targetPath.includes("VForm") || targetPath.includes("FormField"))) {
|
|
148
|
+
return content;
|
|
149
|
+
}
|
|
150
|
+
const pascalCaseImportPattern = /from\s+['"](\.\.\/?|\.\/)([A-Z][a-zA-Z0-9]*(?:\/[A-Z][a-zA-Z0-9]*)?)['"]/g;
|
|
151
|
+
let result = content.replace(pascalCaseImportPattern, (_match, prefix, importPath) => {
|
|
152
|
+
const parts = importPath.split("/");
|
|
153
|
+
const fileName = parts[parts.length - 1];
|
|
154
|
+
const kebabFileName = toKebabCase(fileName);
|
|
155
|
+
if (prefix === "../" && parts.length >= 1) {
|
|
156
|
+
return `from './${kebabFileName}'`;
|
|
157
|
+
}
|
|
158
|
+
return `from '${prefix}${kebabFileName}'`;
|
|
159
|
+
});
|
|
160
|
+
const dynamicImportPattern = /import\s*\(\s*['"](\.\/)([A-Z][a-zA-Z0-9]*)['"]\s*\)/g;
|
|
161
|
+
result = result.replace(dynamicImportPattern, (_match, prefix, componentName) => {
|
|
162
|
+
const kebabName = toKebabCase(componentName);
|
|
163
|
+
return `import('${prefix}${kebabName}')`;
|
|
164
|
+
});
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
var RELATIVE_IMPORT_MAPPINGS = {
|
|
168
|
+
// file-image/FileImage.tsx imports from ../upload → ./upload
|
|
169
|
+
"../upload": "./upload"
|
|
170
|
+
// file/File.tsx imports from ../upload → ./upload
|
|
171
|
+
// files/Files.tsx imports from ../upload → ./upload
|
|
172
|
+
// list-o2m imports from ../upload → ./upload
|
|
173
|
+
};
|
|
174
|
+
var VFORM_IMPORT_MAPPINGS = {
|
|
175
|
+
// Files in vform/components/ folder
|
|
176
|
+
"components": {
|
|
177
|
+
"../types": "../types",
|
|
178
|
+
"./types": "../types"
|
|
179
|
+
},
|
|
180
|
+
// Files in vform/utils/ folder
|
|
181
|
+
"utils": {
|
|
182
|
+
"../types": "../types",
|
|
183
|
+
"./types": "../types"
|
|
184
|
+
},
|
|
185
|
+
// Files in vform/ root folder
|
|
186
|
+
"root": {
|
|
187
|
+
"./types": "./types"
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
function transformVFormImports(content, sourceFile, _targetFile) {
|
|
191
|
+
let result = content;
|
|
192
|
+
let folder = "root";
|
|
193
|
+
if (sourceFile.includes("/components/")) {
|
|
194
|
+
folder = "components";
|
|
195
|
+
} else if (sourceFile.includes("/utils/")) {
|
|
196
|
+
folder = "utils";
|
|
197
|
+
}
|
|
198
|
+
const mappings = VFORM_IMPORT_MAPPINGS[folder] || {};
|
|
199
|
+
for (const [from, to] of Object.entries(mappings)) {
|
|
200
|
+
const importPattern = new RegExp(
|
|
201
|
+
`(from\\s+['"])${from.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(['"])`,
|
|
202
|
+
"g"
|
|
203
|
+
);
|
|
204
|
+
result = result.replace(importPattern, `$1${to}$2`);
|
|
205
|
+
}
|
|
206
|
+
return result;
|
|
207
|
+
}
|
|
208
|
+
function transformRelativeImports(content, _sourceFile, _targetFile, _componentsAlias) {
|
|
209
|
+
let result = content;
|
|
210
|
+
for (const [from, to] of Object.entries(RELATIVE_IMPORT_MAPPINGS)) {
|
|
211
|
+
const importPattern = new RegExp(
|
|
212
|
+
`(from\\s+['"])${from.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(['"])`,
|
|
213
|
+
"g"
|
|
214
|
+
);
|
|
215
|
+
result = result.replace(importPattern, `$1${to}$2`);
|
|
216
|
+
}
|
|
217
|
+
const siblingImportPattern = /from\s+['"](\.\.\/([a-z][-a-z0-9]*)(?:\/[A-Z][a-zA-Z]*)?)['"]/g;
|
|
218
|
+
result = result.replace(siblingImportPattern, (_match, _fullPath, componentFolder) => {
|
|
219
|
+
const kebabName = toKebabCase(componentFolder);
|
|
220
|
+
return `from './${kebabName}'`;
|
|
221
|
+
});
|
|
222
|
+
return result;
|
|
223
|
+
}
|
|
224
|
+
function generateOriginHeader(componentName, sourcePackage, version = "1.0.0") {
|
|
225
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
226
|
+
return `/**
|
|
227
|
+
* @microbuild-origin ${sourcePackage}/${componentName}
|
|
228
|
+
* @microbuild-version ${version}
|
|
229
|
+
* @microbuild-date ${timestamp}
|
|
230
|
+
*
|
|
231
|
+
* This file was copied from Microbuild UI Packages.
|
|
232
|
+
* To update, run: npx @microbuild/cli add ${componentName} --overwrite
|
|
233
|
+
*
|
|
234
|
+
* Docs: https://microbuild.dev/components/${componentName}
|
|
235
|
+
*/
|
|
236
|
+
|
|
237
|
+
`;
|
|
238
|
+
}
|
|
239
|
+
function addOriginHeader(content, componentName, sourcePackage, version = "1.0.0") {
|
|
240
|
+
const header = generateOriginHeader(componentName, sourcePackage, version);
|
|
241
|
+
const useClientMatch = content.match(/^(["']use client["'];?\s*\n)/);
|
|
242
|
+
if (useClientMatch) {
|
|
243
|
+
return useClientMatch[1] + header + content.slice(useClientMatch[0].length);
|
|
244
|
+
}
|
|
245
|
+
return header + content;
|
|
246
|
+
}
|
|
247
|
+
function hasMicrobuildOrigin(content) {
|
|
248
|
+
return content.includes("@microbuild-origin");
|
|
249
|
+
}
|
|
250
|
+
function extractOriginInfo(content) {
|
|
251
|
+
const originMatch = content.match(/@microbuild-origin\s+([^\n*]+)/);
|
|
252
|
+
const versionMatch = content.match(/@microbuild-version\s+([^\n*]+)/);
|
|
253
|
+
const dateMatch = content.match(/@microbuild-date\s+([^\n*]+)/);
|
|
254
|
+
if (!originMatch) return null;
|
|
255
|
+
return {
|
|
256
|
+
origin: originMatch[1].trim(),
|
|
257
|
+
version: versionMatch?.[1].trim(),
|
|
258
|
+
date: dateMatch?.[1].trim()
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// src/commands/validate.ts
|
|
263
|
+
import fs from "fs-extra";
|
|
264
|
+
import path from "path";
|
|
265
|
+
import chalk from "chalk";
|
|
266
|
+
import ora from "ora";
|
|
267
|
+
import fg from "fast-glob";
|
|
268
|
+
import { execSync } from "child_process";
|
|
269
|
+
async function checkUntransformedImports(cwd, config) {
|
|
270
|
+
const errors = [];
|
|
271
|
+
const srcDir = config.srcDir ? path.join(cwd, "src") : cwd;
|
|
272
|
+
const patterns = [
|
|
273
|
+
path.join(srcDir, "components/**/*.{ts,tsx,js,jsx}"),
|
|
274
|
+
path.join(srcDir, "lib/microbuild/**/*.{ts,tsx,js,jsx}")
|
|
275
|
+
];
|
|
276
|
+
for (const pattern of patterns) {
|
|
277
|
+
const files = await fg(pattern, { ignore: ["**/node_modules/**"] });
|
|
278
|
+
for (const file of files) {
|
|
279
|
+
const content = await fs.readFile(file, "utf-8");
|
|
280
|
+
const lines = content.split("\n");
|
|
281
|
+
lines.forEach((line, index) => {
|
|
282
|
+
if ((line.includes("from '@microbuild/") || line.includes('from "@microbuild/')) && !line.trim().startsWith("//") && !line.trim().startsWith("*")) {
|
|
283
|
+
errors.push({
|
|
284
|
+
file: path.relative(cwd, file),
|
|
285
|
+
line: index + 1,
|
|
286
|
+
message: `Untransformed import: ${line.trim()}`,
|
|
287
|
+
code: "UNTRANSFORMED_IMPORT"
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return errors;
|
|
294
|
+
}
|
|
295
|
+
async function checkMissingCssFiles(cwd, config) {
|
|
296
|
+
const warnings = [];
|
|
297
|
+
const srcDir = config.srcDir ? path.join(cwd, "src") : cwd;
|
|
298
|
+
const componentsDir = path.join(srcDir, "components/ui");
|
|
299
|
+
const cssRequirements = {
|
|
300
|
+
"input-block-editor.tsx": "InputBlockEditor.css",
|
|
301
|
+
"rich-text-html.tsx": "RichTextHTML.css",
|
|
302
|
+
"rich-text-markdown.tsx": "RichTextMarkdown.css"
|
|
303
|
+
};
|
|
304
|
+
for (const [component, cssFile] of Object.entries(cssRequirements)) {
|
|
305
|
+
const componentPath = path.join(componentsDir, component);
|
|
306
|
+
const cssPath = path.join(componentsDir, cssFile);
|
|
307
|
+
if (fs.existsSync(componentPath) && !fs.existsSync(cssPath)) {
|
|
308
|
+
warnings.push({
|
|
309
|
+
file: cssFile,
|
|
310
|
+
message: `Missing CSS file for ${component}`,
|
|
311
|
+
code: "MISSING_CSS"
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return warnings;
|
|
316
|
+
}
|
|
317
|
+
async function checkLibModules(cwd, config) {
|
|
318
|
+
const errors = [];
|
|
319
|
+
const srcDir = config.srcDir ? path.join(cwd, "src") : cwd;
|
|
320
|
+
const libDir = path.join(srcDir, "lib/microbuild");
|
|
321
|
+
const requiredModules = {
|
|
322
|
+
types: ["types/index.ts", "types/core.ts"],
|
|
323
|
+
services: ["services/index.ts", "services/api-request.ts"],
|
|
324
|
+
hooks: ["hooks/index.ts"],
|
|
325
|
+
utils: ["utils.ts", "field-interface-mapper.ts"]
|
|
326
|
+
};
|
|
327
|
+
for (const [module, files] of Object.entries(requiredModules)) {
|
|
328
|
+
if (config.installedLib.includes(module)) {
|
|
329
|
+
for (const file of files) {
|
|
330
|
+
const filePath = path.join(libDir, file);
|
|
331
|
+
if (!fs.existsSync(filePath)) {
|
|
332
|
+
errors.push({
|
|
333
|
+
file: `lib/microbuild/${file}`,
|
|
334
|
+
message: `Missing required file for ${module} module`,
|
|
335
|
+
code: "MISSING_LIB_FILE"
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
const defineInterfacePath = path.join(libDir, "define-interface.ts");
|
|
342
|
+
const interfaceRegistryPath = path.join(libDir, "interface-registry.ts");
|
|
343
|
+
if (fs.existsSync(defineInterfacePath) && !fs.existsSync(interfaceRegistryPath)) {
|
|
344
|
+
errors.push({
|
|
345
|
+
file: "lib/microbuild/interface-registry.ts",
|
|
346
|
+
message: "Missing interface-registry.ts (required by define-interface.ts)",
|
|
347
|
+
code: "MISSING_INTERFACE_REGISTRY"
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
return errors;
|
|
351
|
+
}
|
|
352
|
+
async function checkSsrIssues(cwd, config) {
|
|
353
|
+
const warnings = [];
|
|
354
|
+
const srcDir = config.srcDir ? path.join(cwd, "src") : cwd;
|
|
355
|
+
const componentsDir = path.join(srcDir, "components/ui");
|
|
356
|
+
const indexPath = path.join(componentsDir, "index.ts");
|
|
357
|
+
if (fs.existsSync(indexPath)) {
|
|
358
|
+
const content = await fs.readFile(indexPath, "utf-8");
|
|
359
|
+
if (content.includes("InputBlockEditor") && !content.includes("input-block-editor-wrapper") && content.includes("from './input-block-editor'")) {
|
|
360
|
+
warnings.push({
|
|
361
|
+
file: "components/ui/index.ts",
|
|
362
|
+
message: "InputBlockEditor exported directly may cause SSR errors. Use input-block-editor-wrapper instead.",
|
|
363
|
+
code: "SSR_UNSAFE_EXPORT"
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return warnings;
|
|
368
|
+
}
|
|
369
|
+
async function checkApiRoutes(cwd) {
|
|
370
|
+
const warnings = [];
|
|
371
|
+
const srcDir = fs.existsSync(path.join(cwd, "src/app")) ? path.join(cwd, "src") : cwd;
|
|
372
|
+
const appDir = path.join(srcDir, "app");
|
|
373
|
+
const apiDir = path.join(appDir, "api");
|
|
374
|
+
const requiredRoutes = [
|
|
375
|
+
{ path: "fields/[collection]/route.ts", description: "Fetch collection field schemas" },
|
|
376
|
+
{ path: "items/[collection]/route.ts", description: "List/Create items" },
|
|
377
|
+
{ path: "items/[collection]/[id]/route.ts", description: "Get/Update/Delete item" }
|
|
378
|
+
];
|
|
379
|
+
const recommendedRoutes = [
|
|
380
|
+
{ path: "relations/route.ts", description: "Relation definitions (for M2M/M2O/O2M)" },
|
|
381
|
+
{ path: "files/route.ts", description: "File operations (for file components)" }
|
|
382
|
+
];
|
|
383
|
+
if (!fs.existsSync(apiDir)) {
|
|
384
|
+
warnings.push({
|
|
385
|
+
file: "app/api/",
|
|
386
|
+
message: "Missing API directory. Forms require API routes to fetch data from DaaS.",
|
|
387
|
+
code: "MISSING_API_DIR"
|
|
388
|
+
});
|
|
389
|
+
return warnings;
|
|
390
|
+
}
|
|
391
|
+
for (const route of requiredRoutes) {
|
|
392
|
+
const routePath = path.join(apiDir, route.path);
|
|
393
|
+
if (!fs.existsSync(routePath)) {
|
|
394
|
+
warnings.push({
|
|
395
|
+
file: `app/api/${route.path}`,
|
|
396
|
+
message: `Missing required API route: ${route.description}`,
|
|
397
|
+
code: "MISSING_API_ROUTE"
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
for (const route of recommendedRoutes) {
|
|
402
|
+
const routePath = path.join(apiDir, route.path);
|
|
403
|
+
if (!fs.existsSync(routePath)) {
|
|
404
|
+
warnings.push({
|
|
405
|
+
file: `app/api/${route.path}`,
|
|
406
|
+
message: `Missing recommended API route: ${route.description}`,
|
|
407
|
+
code: "MISSING_OPTIONAL_API_ROUTE"
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
const authHeadersPath = path.join(srcDir, "lib/api/auth-headers.ts");
|
|
412
|
+
if (!fs.existsSync(authHeadersPath)) {
|
|
413
|
+
warnings.push({
|
|
414
|
+
file: "lib/api/auth-headers.ts",
|
|
415
|
+
message: "Missing auth-headers helper. API routes need this to forward auth tokens.",
|
|
416
|
+
code: "MISSING_AUTH_HELPER"
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
return warnings;
|
|
420
|
+
}
|
|
421
|
+
async function checkBrokenRelativeImports(cwd, config) {
|
|
422
|
+
const errors = [];
|
|
423
|
+
const srcDir = config.srcDir ? path.join(cwd, "src") : cwd;
|
|
424
|
+
const patterns = [
|
|
425
|
+
path.join(srcDir, "components/**/*.{ts,tsx,js,jsx}"),
|
|
426
|
+
path.join(srcDir, "lib/microbuild/**/*.{ts,tsx,js,jsx}")
|
|
427
|
+
];
|
|
428
|
+
const relativeImportPattern = /from\s+['"](\.\.?\/[^'"]+)['"]/g;
|
|
429
|
+
for (const pattern of patterns) {
|
|
430
|
+
const files = await fg(pattern, { ignore: ["**/node_modules/**"] });
|
|
431
|
+
for (const file of files) {
|
|
432
|
+
const content = await fs.readFile(file, "utf-8");
|
|
433
|
+
const lines = content.split("\n");
|
|
434
|
+
const fileDir = path.dirname(file);
|
|
435
|
+
lines.forEach((line, index) => {
|
|
436
|
+
if (line.trim().startsWith("//") || line.trim().startsWith("*")) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
let match;
|
|
440
|
+
relativeImportPattern.lastIndex = 0;
|
|
441
|
+
while ((match = relativeImportPattern.exec(line)) !== null) {
|
|
442
|
+
const importPath = match[1];
|
|
443
|
+
const possibleExtensions = [".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js", "/index.jsx", ""];
|
|
444
|
+
const absolutePath = path.resolve(fileDir, importPath);
|
|
445
|
+
const exists = possibleExtensions.some(
|
|
446
|
+
(ext) => fs.existsSync(absolutePath + ext)
|
|
447
|
+
);
|
|
448
|
+
if (!exists) {
|
|
449
|
+
errors.push({
|
|
450
|
+
file: path.relative(cwd, file),
|
|
451
|
+
line: index + 1,
|
|
452
|
+
message: `Cannot find module '${importPath}'`,
|
|
453
|
+
code: "BROKEN_IMPORT"
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return errors;
|
|
461
|
+
}
|
|
462
|
+
async function checkReact19Compatibility(cwd, config) {
|
|
463
|
+
const warnings = [];
|
|
464
|
+
const srcDir = config.srcDir ? path.join(cwd, "src") : cwd;
|
|
465
|
+
const appDir = path.join(srcDir, "app");
|
|
466
|
+
if (!fs.existsSync(appDir)) {
|
|
467
|
+
return warnings;
|
|
468
|
+
}
|
|
469
|
+
const serverComponentPattern = path.join(appDir, "**/page.tsx");
|
|
470
|
+
const files = await fg(serverComponentPattern, { ignore: ["**/node_modules/**"] });
|
|
471
|
+
const componentPropPattern = /component=\{[A-Z][a-zA-Z]*\}/;
|
|
472
|
+
for (const file of files) {
|
|
473
|
+
const content = await fs.readFile(file, "utf-8");
|
|
474
|
+
if (content.includes('"use client"') || content.includes("'use client'")) {
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
const lines = content.split("\n");
|
|
478
|
+
lines.forEach((line, index) => {
|
|
479
|
+
if (componentPropPattern.test(line)) {
|
|
480
|
+
warnings.push({
|
|
481
|
+
file: path.relative(cwd, file),
|
|
482
|
+
line: index + 1,
|
|
483
|
+
message: 'Passing component as prop in Server Component may cause React 19 errors. Add "use client" or use wrapper pattern.',
|
|
484
|
+
code: "REACT19_COMPONENT_PROP"
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
return warnings;
|
|
490
|
+
}
|
|
491
|
+
async function checkDuplicateExports(cwd, config) {
|
|
492
|
+
const warnings = [];
|
|
493
|
+
const srcDir = config.srcDir ? path.join(cwd, "src") : cwd;
|
|
494
|
+
const indexPath = path.join(srcDir, "components/ui/index.ts");
|
|
495
|
+
if (!fs.existsSync(indexPath)) {
|
|
496
|
+
return warnings;
|
|
497
|
+
}
|
|
498
|
+
const content = await fs.readFile(indexPath, "utf-8");
|
|
499
|
+
const exportPattern = /export \* from ['"]\.\/([^'"]+)['"]/g;
|
|
500
|
+
const exports = /* @__PURE__ */ new Map();
|
|
501
|
+
let match;
|
|
502
|
+
let lineNum = 0;
|
|
503
|
+
const lines = content.split("\n");
|
|
504
|
+
for (const line of lines) {
|
|
505
|
+
lineNum++;
|
|
506
|
+
exportPattern.lastIndex = 0;
|
|
507
|
+
while ((match = exportPattern.exec(line)) !== null) {
|
|
508
|
+
const exportPath = match[1];
|
|
509
|
+
if (exports.has(exportPath)) {
|
|
510
|
+
warnings.push({
|
|
511
|
+
file: "components/ui/index.ts",
|
|
512
|
+
line: lineNum,
|
|
513
|
+
message: `Duplicate export from './${exportPath}' (first at line ${exports.get(exportPath)})`,
|
|
514
|
+
code: "DUPLICATE_EXPORT"
|
|
515
|
+
});
|
|
516
|
+
} else {
|
|
517
|
+
exports.set(exportPath, lineNum);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
const componentsDir = path.join(srcDir, "components/ui");
|
|
522
|
+
const namedExports = /* @__PURE__ */ new Map();
|
|
523
|
+
for (const [exportFile] of exports) {
|
|
524
|
+
const filePath = path.join(componentsDir, exportFile + ".tsx");
|
|
525
|
+
if (!fs.existsSync(filePath)) continue;
|
|
526
|
+
const fileContent = await fs.readFile(filePath, "utf-8");
|
|
527
|
+
const namedExportPattern = /export\s+(?:const|function|class|type|interface|enum)\s+(\w+)/g;
|
|
528
|
+
let namedMatch;
|
|
529
|
+
while ((namedMatch = namedExportPattern.exec(fileContent)) !== null) {
|
|
530
|
+
const exportName = namedMatch[1];
|
|
531
|
+
if (namedExports.has(exportName)) {
|
|
532
|
+
const firstExport = namedExports.get(exportName);
|
|
533
|
+
warnings.push({
|
|
534
|
+
file: `components/ui/${exportFile}.tsx`,
|
|
535
|
+
message: `Conflicting export '${exportName}' also exported from '${firstExport.file}'. This will cause 'export *' conflicts in index.ts`,
|
|
536
|
+
code: "CONFLICTING_NAMED_EXPORT"
|
|
537
|
+
});
|
|
538
|
+
} else {
|
|
539
|
+
namedExports.set(exportName, { file: exportFile, line: 0 });
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return warnings;
|
|
544
|
+
}
|
|
545
|
+
async function checkTypeScriptErrors(cwd, config) {
|
|
546
|
+
const errors = [];
|
|
547
|
+
const tsconfigPath = path.join(cwd, "tsconfig.json");
|
|
548
|
+
if (!fs.existsSync(tsconfigPath)) {
|
|
549
|
+
return errors;
|
|
550
|
+
}
|
|
551
|
+
try {
|
|
552
|
+
const srcDir = config.srcDir ? path.join(cwd, "src") : cwd;
|
|
553
|
+
const componentsDir = path.join(srcDir, "components/ui");
|
|
554
|
+
if (!fs.existsSync(componentsDir)) {
|
|
555
|
+
return errors;
|
|
556
|
+
}
|
|
557
|
+
const result = execSync(
|
|
558
|
+
`npx tsc --noEmit --skipLibCheck --pretty false 2>&1 || true`,
|
|
559
|
+
{ cwd, encoding: "utf-8", maxBuffer: 10 * 1024 * 1024 }
|
|
560
|
+
);
|
|
561
|
+
const tsErrorPattern = /^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+(TS\d+):\s+(.+)$/gm;
|
|
562
|
+
let match;
|
|
563
|
+
while ((match = tsErrorPattern.exec(result)) !== null) {
|
|
564
|
+
const [, file, line, , severity, tsCode, message] = match;
|
|
565
|
+
const relativePath = path.relative(cwd, file);
|
|
566
|
+
if (relativePath.includes("components/ui") || relativePath.includes("lib/microbuild")) {
|
|
567
|
+
if (severity === "error") {
|
|
568
|
+
errors.push({
|
|
569
|
+
file: relativePath,
|
|
570
|
+
line: parseInt(line, 10),
|
|
571
|
+
message: `${tsCode}: ${message}`,
|
|
572
|
+
code: "TYPESCRIPT_ERROR"
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
} catch {
|
|
578
|
+
}
|
|
579
|
+
return errors;
|
|
580
|
+
}
|
|
581
|
+
function generateSuggestions(errors, warnings) {
|
|
582
|
+
const suggestions = [];
|
|
583
|
+
const tsErrorCount = errors.filter((e) => e.code === "TYPESCRIPT_ERROR").length;
|
|
584
|
+
if (tsErrorCount > 0) {
|
|
585
|
+
suggestions.push(
|
|
586
|
+
`Fix ${tsErrorCount} TypeScript error(s) in component files`
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
const untransformedCount = errors.filter((e) => e.code === "UNTRANSFORMED_IMPORT").length;
|
|
590
|
+
if (untransformedCount > 0) {
|
|
591
|
+
suggestions.push(
|
|
592
|
+
`Fix ${untransformedCount} untransformed import(s) by running: pnpm cli add --all --overwrite --cwd .`
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
const brokenImportCount = errors.filter((e) => e.code === "BROKEN_IMPORT").length;
|
|
596
|
+
if (brokenImportCount > 0) {
|
|
597
|
+
suggestions.push(
|
|
598
|
+
`Fix ${brokenImportCount} broken import(s) by checking file paths or reinstalling components`
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
const missingLibCount = errors.filter((e) => e.code === "MISSING_LIB_FILE").length;
|
|
602
|
+
if (missingLibCount > 0) {
|
|
603
|
+
suggestions.push(
|
|
604
|
+
`Reinstall lib modules with: pnpm cli add vform --overwrite --cwd .`
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
if (errors.some((e) => e.code === "MISSING_INTERFACE_REGISTRY")) {
|
|
608
|
+
suggestions.push(
|
|
609
|
+
`Add missing interface-registry.ts by reinstalling utils module`
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
const missingCssCount = warnings.filter((w) => w.code === "MISSING_CSS").length;
|
|
613
|
+
if (missingCssCount > 0) {
|
|
614
|
+
suggestions.push(
|
|
615
|
+
`Copy missing CSS files from microbuild-ui-packages/packages/ui-interfaces/src/`
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
if (warnings.some((w) => w.code === "SSR_UNSAFE_EXPORT")) {
|
|
619
|
+
suggestions.push(
|
|
620
|
+
`Update components/ui/index.ts to export InputBlockEditor from './input-block-editor-wrapper'`
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
const missingRouteCount = warnings.filter((w) => w.code === "MISSING_API_ROUTE").length;
|
|
624
|
+
const missingApiDir = warnings.some((w) => w.code === "MISSING_API_DIR");
|
|
625
|
+
const missingAuthHelper = warnings.some((w) => w.code === "MISSING_AUTH_HELPER");
|
|
626
|
+
if (missingApiDir || missingRouteCount > 0 || missingAuthHelper) {
|
|
627
|
+
suggestions.push(
|
|
628
|
+
`Install API routes and auth helpers: pnpm cli add api-routes --cwd .`
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
const react19Count = warnings.filter((w) => w.code === "REACT19_COMPONENT_PROP").length;
|
|
632
|
+
if (react19Count > 0) {
|
|
633
|
+
suggestions.push(
|
|
634
|
+
`Fix ${react19Count} React 19 compatibility issue(s): wrap component with "use client" or use Link directly`
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
const duplicateCount = warnings.filter((w) => w.code === "DUPLICATE_EXPORT").length;
|
|
638
|
+
if (duplicateCount > 0) {
|
|
639
|
+
suggestions.push(
|
|
640
|
+
`Remove ${duplicateCount} duplicate export(s) from components/ui/index.ts`
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
return suggestions;
|
|
644
|
+
}
|
|
645
|
+
async function validate(options) {
|
|
646
|
+
const { cwd, json = false, noExit = false } = options;
|
|
647
|
+
const config = await loadConfig(cwd);
|
|
648
|
+
if (!config) {
|
|
649
|
+
if (json) {
|
|
650
|
+
console.log(JSON.stringify({
|
|
651
|
+
valid: false,
|
|
652
|
+
errors: [{ file: "microbuild.json", message: "Not found", code: "NO_CONFIG" }],
|
|
653
|
+
warnings: [],
|
|
654
|
+
suggestions: ['Run "npx microbuild init" first']
|
|
655
|
+
}));
|
|
656
|
+
} else {
|
|
657
|
+
console.log(chalk.red('\n\u2717 microbuild.json not found. Run "npx microbuild init" first.\n'));
|
|
658
|
+
}
|
|
659
|
+
if (noExit) {
|
|
660
|
+
return { valid: false, errors: [{ file: "microbuild.json", message: "Not found", code: "NO_CONFIG" }], warnings: [], suggestions: ['Run "npx microbuild init" first'] };
|
|
661
|
+
}
|
|
662
|
+
process.exit(1);
|
|
663
|
+
}
|
|
664
|
+
const spinner = json ? null : ora("Validating Microbuild installation...").start();
|
|
665
|
+
try {
|
|
666
|
+
const [
|
|
667
|
+
untransformedErrors,
|
|
668
|
+
brokenImportErrors,
|
|
669
|
+
missingCssWarnings,
|
|
670
|
+
libModuleErrors,
|
|
671
|
+
ssrWarnings,
|
|
672
|
+
apiRouteWarnings,
|
|
673
|
+
react19Warnings,
|
|
674
|
+
duplicateExportWarnings
|
|
675
|
+
] = await Promise.all([
|
|
676
|
+
checkUntransformedImports(cwd, config),
|
|
677
|
+
checkBrokenRelativeImports(cwd, config),
|
|
678
|
+
checkMissingCssFiles(cwd, config),
|
|
679
|
+
checkLibModules(cwd, config),
|
|
680
|
+
checkSsrIssues(cwd, config),
|
|
681
|
+
checkApiRoutes(cwd),
|
|
682
|
+
checkReact19Compatibility(cwd, config),
|
|
683
|
+
checkDuplicateExports(cwd, config)
|
|
684
|
+
]);
|
|
685
|
+
if (spinner) {
|
|
686
|
+
spinner.text = "Running TypeScript check...";
|
|
687
|
+
}
|
|
688
|
+
const tsErrors = await checkTypeScriptErrors(cwd, config);
|
|
689
|
+
const errors = [...untransformedErrors, ...brokenImportErrors, ...libModuleErrors, ...tsErrors];
|
|
690
|
+
const warnings = [...missingCssWarnings, ...ssrWarnings, ...apiRouteWarnings, ...react19Warnings, ...duplicateExportWarnings];
|
|
691
|
+
const suggestions = generateSuggestions(errors, warnings);
|
|
692
|
+
const result = {
|
|
693
|
+
valid: errors.length === 0,
|
|
694
|
+
errors,
|
|
695
|
+
warnings,
|
|
696
|
+
suggestions
|
|
697
|
+
};
|
|
698
|
+
if (noExit) {
|
|
699
|
+
return result;
|
|
700
|
+
}
|
|
701
|
+
if (json) {
|
|
702
|
+
console.log(JSON.stringify(result, null, 2));
|
|
703
|
+
return result;
|
|
704
|
+
}
|
|
705
|
+
if (errors.length === 0 && warnings.length === 0) {
|
|
706
|
+
spinner?.succeed(chalk.green("Microbuild installation is valid! \u2713"));
|
|
707
|
+
console.log(chalk.dim(`
|
|
708
|
+
${config.installedComponents.length} components installed`));
|
|
709
|
+
console.log(chalk.dim(` ${config.installedLib.length} lib modules installed
|
|
710
|
+
`));
|
|
711
|
+
return result;
|
|
712
|
+
}
|
|
713
|
+
spinner?.stop();
|
|
714
|
+
if (errors.length > 0) {
|
|
715
|
+
console.log(chalk.red(`
|
|
716
|
+
\u2717 Found ${errors.length} error(s):
|
|
717
|
+
`));
|
|
718
|
+
errors.forEach((error) => {
|
|
719
|
+
const location = error.line ? `:${error.line}` : "";
|
|
720
|
+
console.log(chalk.red(` \u2717 ${error.file}${location}`));
|
|
721
|
+
console.log(chalk.dim(` ${error.message}`));
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
if (warnings.length > 0) {
|
|
725
|
+
console.log(chalk.yellow(`
|
|
726
|
+
\u26A0 Found ${warnings.length} warning(s):
|
|
727
|
+
`));
|
|
728
|
+
warnings.forEach((warning) => {
|
|
729
|
+
console.log(chalk.yellow(` \u26A0 ${warning.file}`));
|
|
730
|
+
console.log(chalk.dim(` ${warning.message}`));
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
if (suggestions.length > 0) {
|
|
734
|
+
console.log(chalk.cyan("\n\u{1F4A1} Suggestions:\n"));
|
|
735
|
+
suggestions.forEach((suggestion, i) => {
|
|
736
|
+
console.log(chalk.cyan(` ${i + 1}. ${suggestion}`));
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
console.log();
|
|
740
|
+
if (errors.length > 0) {
|
|
741
|
+
process.exit(1);
|
|
742
|
+
}
|
|
743
|
+
return result;
|
|
744
|
+
} catch (error) {
|
|
745
|
+
spinner?.fail("Validation failed");
|
|
746
|
+
console.error(error);
|
|
747
|
+
process.exit(1);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// src/commands/add.ts
|
|
752
|
+
async function getRegistry2() {
|
|
753
|
+
try {
|
|
754
|
+
return await getRegistry();
|
|
755
|
+
} catch (err) {
|
|
756
|
+
console.error(chalk2.red("Failed to load registry:", err.message));
|
|
757
|
+
process.exit(1);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
var COMPONENT_ALIASES = {
|
|
761
|
+
"form": "vform",
|
|
762
|
+
"dynamicform": "vform",
|
|
763
|
+
"v-form": "vform",
|
|
764
|
+
"select": "select-dropdown",
|
|
765
|
+
"dropdown": "select-dropdown",
|
|
766
|
+
"checkbox": "boolean",
|
|
767
|
+
"switch": "toggle",
|
|
768
|
+
"date": "datetime",
|
|
769
|
+
"time": "datetime",
|
|
770
|
+
"datepicker": "datetime",
|
|
771
|
+
"text": "input",
|
|
772
|
+
"textinput": "input",
|
|
773
|
+
"textfield": "input",
|
|
774
|
+
"image": "file-image",
|
|
775
|
+
"imageupload": "file-image",
|
|
776
|
+
"wysiwyg": "rich-text-html",
|
|
777
|
+
"richtext": "rich-text-html",
|
|
778
|
+
"markdown": "rich-text-markdown",
|
|
779
|
+
"md": "rich-text-markdown",
|
|
780
|
+
"m2m": "list-m2m",
|
|
781
|
+
"m2o": "list-m2o",
|
|
782
|
+
"o2m": "list-o2m",
|
|
783
|
+
"m2a": "list-m2a",
|
|
784
|
+
"manytomany": "list-m2m",
|
|
785
|
+
"manytoone": "list-m2o",
|
|
786
|
+
"onetomany": "list-o2m",
|
|
787
|
+
"manytoany": "list-m2a",
|
|
788
|
+
"relation": "list-m2o",
|
|
789
|
+
"multiselect": "select-multiple-dropdown",
|
|
790
|
+
"checkboxes": "select-multiple-checkbox",
|
|
791
|
+
"radio": "select-radio",
|
|
792
|
+
"icon": "select-icon",
|
|
793
|
+
"colorpicker": "color",
|
|
794
|
+
"fileupload": "file",
|
|
795
|
+
"code": "input-code",
|
|
796
|
+
"blockeditor": "input-block-editor",
|
|
797
|
+
"editor": "input-block-editor"
|
|
798
|
+
};
|
|
799
|
+
function findComponentWithSuggestions(name, registry) {
|
|
800
|
+
const normalized = name.toLowerCase().replace(/-/g, "");
|
|
801
|
+
const directMatch = registry.components.find(
|
|
802
|
+
(c) => c.name.toLowerCase() === name.toLowerCase()
|
|
803
|
+
);
|
|
804
|
+
if (directMatch) return directMatch;
|
|
805
|
+
const titleMatch = registry.components.find(
|
|
806
|
+
(c) => c.title.toLowerCase() === name.toLowerCase()
|
|
807
|
+
);
|
|
808
|
+
if (titleMatch) return titleMatch;
|
|
809
|
+
const fuzzyMatch = registry.components.find(
|
|
810
|
+
(c) => c.name.toLowerCase().replace(/-/g, "") === normalized || c.title.toLowerCase().replace(/-/g, "") === normalized
|
|
811
|
+
);
|
|
812
|
+
if (fuzzyMatch) return fuzzyMatch;
|
|
813
|
+
const aliasedName = COMPONENT_ALIASES[normalized];
|
|
814
|
+
if (aliasedName) {
|
|
815
|
+
const aliasMatch = registry.components.find((c) => c.name === aliasedName);
|
|
816
|
+
if (aliasMatch) {
|
|
817
|
+
console.log(chalk2.yellow(`
|
|
818
|
+
\u{1F4A1} "${name}" matched alias \u2192 using "${aliasMatch.name}"
|
|
819
|
+
`));
|
|
820
|
+
return aliasMatch;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
console.log(chalk2.red(`
|
|
824
|
+
\u2717 Component not found: ${name}
|
|
825
|
+
`));
|
|
826
|
+
const suggestions = registry.components.map((c) => ({
|
|
827
|
+
component: c,
|
|
828
|
+
score: calculateSimilarity(normalized, c.name.replace(/-/g, "")) + calculateSimilarity(normalized, c.title.toLowerCase().replace(/-/g, "")) + (c.description.toLowerCase().includes(name.toLowerCase()) ? 0.3 : 0)
|
|
829
|
+
})).filter((s) => s.score > 0.2).sort((a, b) => b.score - a.score).slice(0, 5);
|
|
830
|
+
if (suggestions.length > 0) {
|
|
831
|
+
console.log(chalk2.yellow("Did you mean one of these?\n"));
|
|
832
|
+
suggestions.forEach((s) => {
|
|
833
|
+
console.log(` ${chalk2.green(s.component.name.padEnd(28))} ${chalk2.dim(s.component.description)}`);
|
|
834
|
+
});
|
|
835
|
+
console.log();
|
|
836
|
+
}
|
|
837
|
+
const categoryHint = registry.categories.find(
|
|
838
|
+
(cat) => name.toLowerCase().includes(cat.name.toLowerCase())
|
|
839
|
+
);
|
|
840
|
+
if (categoryHint) {
|
|
841
|
+
console.log(chalk2.dim(`Try: microbuild add --category ${categoryHint.name}
|
|
842
|
+
`));
|
|
843
|
+
}
|
|
844
|
+
console.log(chalk2.dim("Commands to help you find components:"));
|
|
845
|
+
console.log(chalk2.dim(" microbuild list List all components"));
|
|
846
|
+
console.log(chalk2.dim(" microbuild list --category Filter by category"));
|
|
847
|
+
console.log(chalk2.dim(" microbuild info <name> Get component details\n"));
|
|
848
|
+
return null;
|
|
849
|
+
}
|
|
850
|
+
function calculateSimilarity(a, b) {
|
|
851
|
+
if (a === b) return 1;
|
|
852
|
+
if (a.includes(b) || b.includes(a)) return 0.7;
|
|
853
|
+
let matches = 0;
|
|
854
|
+
const shorter = a.length < b.length ? a : b;
|
|
855
|
+
const longer = a.length < b.length ? b : a;
|
|
856
|
+
for (let i = 0; i < shorter.length; i++) {
|
|
857
|
+
if (longer.includes(shorter[i])) matches++;
|
|
858
|
+
}
|
|
859
|
+
return matches / longer.length;
|
|
860
|
+
}
|
|
861
|
+
async function copyLibModule(moduleName, registry, config, cwd, spinner) {
|
|
862
|
+
const libModule = registry.lib[moduleName];
|
|
863
|
+
if (!libModule) {
|
|
864
|
+
spinner.warn(`Lib module not found: ${moduleName}`);
|
|
865
|
+
return false;
|
|
866
|
+
}
|
|
867
|
+
if (config.installedLib.includes(moduleName)) {
|
|
868
|
+
return true;
|
|
869
|
+
}
|
|
870
|
+
if (libModule.internalDependencies) {
|
|
871
|
+
for (const dep of libModule.internalDependencies) {
|
|
872
|
+
if (!config.installedLib.includes(dep)) {
|
|
873
|
+
await copyLibModule(dep, registry, config, cwd, spinner);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
if (libModule.path && libModule.target) {
|
|
878
|
+
const targetPath = path2.join(
|
|
879
|
+
config.srcDir ? path2.join(cwd, "src") : cwd,
|
|
880
|
+
libModule.target
|
|
881
|
+
);
|
|
882
|
+
if (await sourceFileExists(libModule.path)) {
|
|
883
|
+
let content = await resolveSourceFile(libModule.path);
|
|
884
|
+
content = transformImports(content, config);
|
|
885
|
+
content = addOriginHeader(content, moduleName, "@microbuild/lib", registry.version);
|
|
886
|
+
await fs2.ensureDir(path2.dirname(targetPath));
|
|
887
|
+
await fs2.writeFile(targetPath, content);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
if (libModule.files) {
|
|
891
|
+
for (const file of libModule.files) {
|
|
892
|
+
const targetPath = path2.join(
|
|
893
|
+
config.srcDir ? path2.join(cwd, "src") : cwd,
|
|
894
|
+
file.target
|
|
895
|
+
);
|
|
896
|
+
if (await sourceFileExists(file.source)) {
|
|
897
|
+
let content = await resolveSourceFile(file.source);
|
|
898
|
+
content = transformImports(content, config);
|
|
899
|
+
const fileName = path2.basename(file.source, path2.extname(file.source));
|
|
900
|
+
content = addOriginHeader(content, `${moduleName}/${fileName}`, "@microbuild/lib", registry.version);
|
|
901
|
+
await fs2.ensureDir(path2.dirname(targetPath));
|
|
902
|
+
await fs2.writeFile(targetPath, content);
|
|
903
|
+
} else {
|
|
904
|
+
spinner.warn(`Source file not found: ${file.source}`);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
config.installedLib.push(moduleName);
|
|
909
|
+
spinner.succeed(`Installed lib: ${moduleName}`);
|
|
910
|
+
return true;
|
|
911
|
+
}
|
|
912
|
+
async function copyComponent(component, registry, config, cwd, overwrite, spinner, installing = /* @__PURE__ */ new Set(), dryRun = false, dryRunInfo) {
|
|
913
|
+
if (installing.has(component.name)) {
|
|
914
|
+
return true;
|
|
915
|
+
}
|
|
916
|
+
if (config.installedComponents.includes(component.name) && !overwrite && !dryRun) {
|
|
917
|
+
if (installing.has("__nonInteractive__")) {
|
|
918
|
+
return true;
|
|
919
|
+
}
|
|
920
|
+
const { shouldOverwrite } = await prompts({
|
|
921
|
+
type: "confirm",
|
|
922
|
+
name: "shouldOverwrite",
|
|
923
|
+
message: `${component.title} already installed. Overwrite?`,
|
|
924
|
+
initial: false
|
|
925
|
+
});
|
|
926
|
+
if (!shouldOverwrite) {
|
|
927
|
+
spinner.info(`Skipped ${component.title}`);
|
|
928
|
+
return false;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
installing.add(component.name);
|
|
932
|
+
const info2 = {
|
|
933
|
+
component: component.name,
|
|
934
|
+
files: component.files.map((f) => ({ source: f.source, target: f.target })),
|
|
935
|
+
dependencies: component.dependencies,
|
|
936
|
+
libDependencies: component.internalDependencies
|
|
937
|
+
};
|
|
938
|
+
for (const dep of component.internalDependencies) {
|
|
939
|
+
if (!config.installedLib.includes(dep)) {
|
|
940
|
+
spinner.text = `Installing dependency: ${dep}...`;
|
|
941
|
+
if (!dryRun) {
|
|
942
|
+
await copyLibModule(dep, registry, config, cwd, spinner);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
if (component.registryDependencies) {
|
|
947
|
+
for (const depName of component.registryDependencies) {
|
|
948
|
+
if (!config.installedComponents.includes(depName) && !installing.has(depName)) {
|
|
949
|
+
const depComponent = registry.components.find((c) => c.name === depName);
|
|
950
|
+
if (depComponent) {
|
|
951
|
+
spinner.text = `Installing component dependency: ${depComponent.title}...`;
|
|
952
|
+
await copyComponent(depComponent, registry, config, cwd, overwrite, spinner, installing, dryRun, dryRunInfo);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
if (dryRun) {
|
|
958
|
+
if (dryRunInfo) {
|
|
959
|
+
dryRunInfo.push(info2);
|
|
960
|
+
}
|
|
961
|
+
spinner.info(`Would add ${component.title}`);
|
|
962
|
+
return true;
|
|
963
|
+
}
|
|
964
|
+
for (const file of component.files) {
|
|
965
|
+
const targetPath = path2.join(
|
|
966
|
+
config.srcDir ? path2.join(cwd, "src") : cwd,
|
|
967
|
+
file.target
|
|
968
|
+
);
|
|
969
|
+
if (!await sourceFileExists(file.source)) {
|
|
970
|
+
spinner.warn(`Source not found: ${file.source}`);
|
|
971
|
+
continue;
|
|
972
|
+
}
|
|
973
|
+
let content = await resolveSourceFile(file.source);
|
|
974
|
+
content = transformImports(content, config, file.target);
|
|
975
|
+
content = transformRelativeImports(content, file.source, file.target, config.aliases.components);
|
|
976
|
+
if (component.name === "vform" || file.target.includes("/vform/")) {
|
|
977
|
+
content = transformVFormImports(content, file.source, file.target);
|
|
978
|
+
}
|
|
979
|
+
content = addOriginHeader(content, component.name, "@microbuild/ui-interfaces", registry.version);
|
|
980
|
+
await fs2.ensureDir(path2.dirname(targetPath));
|
|
981
|
+
const ext = config.tsx ? ".tsx" : ".jsx";
|
|
982
|
+
const finalPath = targetPath.replace(/\.tsx?$/, ext);
|
|
983
|
+
await fs2.writeFile(finalPath, content);
|
|
984
|
+
}
|
|
985
|
+
if (!config.installedComponents.includes(component.name)) {
|
|
986
|
+
config.installedComponents.push(component.name);
|
|
987
|
+
}
|
|
988
|
+
if (!config.componentVersions) {
|
|
989
|
+
config.componentVersions = {};
|
|
990
|
+
}
|
|
991
|
+
config.componentVersions[component.name] = {
|
|
992
|
+
version: registry.version,
|
|
993
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
994
|
+
source: "@microbuild/ui-interfaces"
|
|
995
|
+
};
|
|
996
|
+
spinner.succeed(`Added ${component.title}`);
|
|
997
|
+
return true;
|
|
998
|
+
}
|
|
999
|
+
async function generateComponentsIndex(config, cwd, registry, spinner) {
|
|
1000
|
+
const srcDir = config.srcDir ? path2.join(cwd, "src") : cwd;
|
|
1001
|
+
const componentsDir = path2.join(srcDir, "components/ui");
|
|
1002
|
+
const indexPath = path2.join(componentsDir, "index.ts");
|
|
1003
|
+
spinner.text = "Generating components/ui/index.ts...";
|
|
1004
|
+
const exportLines = [
|
|
1005
|
+
"/**",
|
|
1006
|
+
" * Microbuild UI Components Index",
|
|
1007
|
+
" * ",
|
|
1008
|
+
" * Auto-generated by Microbuild CLI.",
|
|
1009
|
+
' * Re-run "microbuild add" to update after adding new components.',
|
|
1010
|
+
" */",
|
|
1011
|
+
""
|
|
1012
|
+
];
|
|
1013
|
+
const exportedPaths = /* @__PURE__ */ new Set();
|
|
1014
|
+
const namedExportMap = /* @__PURE__ */ new Map();
|
|
1015
|
+
const sortedComponents = [...config.installedComponents].sort();
|
|
1016
|
+
const ssrUnsafeComponents = {
|
|
1017
|
+
"input-block-editor": "input-block-editor-wrapper"
|
|
1018
|
+
};
|
|
1019
|
+
const skippedForWrapper = [];
|
|
1020
|
+
for (const componentName of sortedComponents) {
|
|
1021
|
+
const component = registry.components.find((c) => c.name === componentName);
|
|
1022
|
+
if (!component) continue;
|
|
1023
|
+
const mainFile = component.files[0];
|
|
1024
|
+
if (!mainFile) continue;
|
|
1025
|
+
const targetPath = mainFile.target;
|
|
1026
|
+
let exportPath;
|
|
1027
|
+
if (targetPath.includes("/vform/")) {
|
|
1028
|
+
exportPath = "./vform";
|
|
1029
|
+
} else {
|
|
1030
|
+
const fileName = path2.basename(targetPath, path2.extname(targetPath));
|
|
1031
|
+
if (ssrUnsafeComponents[fileName]) {
|
|
1032
|
+
const wrapperPath = path2.join(componentsDir, `${ssrUnsafeComponents[fileName]}.tsx`);
|
|
1033
|
+
if (fs2.existsSync(wrapperPath)) {
|
|
1034
|
+
exportPath = `./${ssrUnsafeComponents[fileName]}`;
|
|
1035
|
+
skippedForWrapper.push(fileName);
|
|
1036
|
+
} else {
|
|
1037
|
+
exportPath = `./${fileName}`;
|
|
1038
|
+
}
|
|
1039
|
+
} else {
|
|
1040
|
+
exportPath = `./${fileName}`;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
if (!exportedPaths.has(exportPath)) {
|
|
1044
|
+
exportedPaths.add(exportPath);
|
|
1045
|
+
exportLines.push(`export * from '${exportPath}';`);
|
|
1046
|
+
const filePath = path2.join(componentsDir, exportPath.slice(2) + ".tsx");
|
|
1047
|
+
if (fs2.existsSync(filePath)) {
|
|
1048
|
+
try {
|
|
1049
|
+
const content = await fs2.readFile(filePath, "utf-8");
|
|
1050
|
+
const namedExportPattern = /export\s+(?:const|function|class)\s+(\w+)/g;
|
|
1051
|
+
let match;
|
|
1052
|
+
while ((match = namedExportPattern.exec(content)) !== null) {
|
|
1053
|
+
const exportName = match[1];
|
|
1054
|
+
if (!namedExportMap.has(exportName)) {
|
|
1055
|
+
namedExportMap.set(exportName, []);
|
|
1056
|
+
}
|
|
1057
|
+
namedExportMap.get(exportName).push(exportPath);
|
|
1058
|
+
}
|
|
1059
|
+
} catch {
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
await fs2.writeFile(indexPath, exportLines.join("\n") + "\n");
|
|
1065
|
+
const duplicates = Array.from(namedExportMap.entries()).filter(([_, files]) => files.length > 1);
|
|
1066
|
+
if (duplicates.length > 0) {
|
|
1067
|
+
spinner.warn("Generated components/ui/index.ts (with duplicate warnings)");
|
|
1068
|
+
console.log(chalk2.yellow("\n\u26A0 Duplicate export names detected:"));
|
|
1069
|
+
for (const [exportName, files] of duplicates) {
|
|
1070
|
+
console.log(chalk2.dim(` "${exportName}" exported from: ${files.join(", ")}`));
|
|
1071
|
+
}
|
|
1072
|
+
console.log(chalk2.dim(" Consider using named imports or renaming exports.\n"));
|
|
1073
|
+
} else {
|
|
1074
|
+
spinner.info("Generated components/ui/index.ts");
|
|
1075
|
+
}
|
|
1076
|
+
if (skippedForWrapper.length > 0) {
|
|
1077
|
+
console.log(chalk2.dim(` \u2139 Using SSR-safe wrappers for: ${skippedForWrapper.join(", ")}`));
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
async function add(components, options) {
|
|
1081
|
+
const { cwd, all, withApi, category, overwrite = false, dryRun = false, nonInteractive = false } = options;
|
|
1082
|
+
if (dryRun) {
|
|
1083
|
+
console.log(chalk2.yellow("\n\u{1F50D} Dry Run Mode - No files will be modified\n"));
|
|
1084
|
+
}
|
|
1085
|
+
const config = await loadConfig(cwd);
|
|
1086
|
+
if (!config) {
|
|
1087
|
+
console.log(chalk2.red('\n\u2717 microbuild.json not found. Run "npx microbuild init" first.\n'));
|
|
1088
|
+
process.exit(1);
|
|
1089
|
+
}
|
|
1090
|
+
if (!config.componentVersions) {
|
|
1091
|
+
config.componentVersions = {};
|
|
1092
|
+
}
|
|
1093
|
+
const registry = await getRegistry2();
|
|
1094
|
+
if (withApi || all) {
|
|
1095
|
+
console.log(chalk2.bold("\n\u{1F50C} Installing API routes and Supabase auth...\n"));
|
|
1096
|
+
const spinner2 = ora2("Processing lib modules...").start();
|
|
1097
|
+
if (registry.lib["supabase-auth"] && !config.installedLib.includes("supabase-auth")) {
|
|
1098
|
+
await copyLibModule("supabase-auth", registry, config, cwd, spinner2);
|
|
1099
|
+
}
|
|
1100
|
+
if (registry.lib["api-routes"] && !config.installedLib.includes("api-routes")) {
|
|
1101
|
+
await copyLibModule("api-routes", registry, config, cwd, spinner2);
|
|
1102
|
+
}
|
|
1103
|
+
spinner2.succeed("API routes and auth installed!");
|
|
1104
|
+
await saveConfig(cwd, config);
|
|
1105
|
+
}
|
|
1106
|
+
let componentsToAdd = [];
|
|
1107
|
+
if (all) {
|
|
1108
|
+
componentsToAdd = registry.components;
|
|
1109
|
+
} else if (category) {
|
|
1110
|
+
componentsToAdd = registry.components.filter((c) => c.category === category);
|
|
1111
|
+
if (componentsToAdd.length === 0) {
|
|
1112
|
+
console.log(chalk2.red(`
|
|
1113
|
+
\u2717 No components found in category: ${category}
|
|
1114
|
+
`));
|
|
1115
|
+
const categories = registry.categories.map((c) => c.name).join(", ");
|
|
1116
|
+
console.log(chalk2.dim(`Available categories: ${categories}
|
|
1117
|
+
`));
|
|
1118
|
+
process.exit(1);
|
|
1119
|
+
}
|
|
1120
|
+
} else if (components.length > 0) {
|
|
1121
|
+
for (const name of components) {
|
|
1122
|
+
const component = findComponentWithSuggestions(name, registry);
|
|
1123
|
+
if (!component) {
|
|
1124
|
+
process.exit(1);
|
|
1125
|
+
}
|
|
1126
|
+
componentsToAdd.push(component);
|
|
1127
|
+
}
|
|
1128
|
+
} else {
|
|
1129
|
+
const choices = registry.categories.map((cat) => ({
|
|
1130
|
+
title: chalk2.bold(cat.title),
|
|
1131
|
+
value: cat.name,
|
|
1132
|
+
description: cat.description
|
|
1133
|
+
}));
|
|
1134
|
+
const { selectedCategory } = await prompts({
|
|
1135
|
+
type: "select",
|
|
1136
|
+
name: "selectedCategory",
|
|
1137
|
+
message: "Select a category",
|
|
1138
|
+
choices
|
|
1139
|
+
});
|
|
1140
|
+
if (!selectedCategory) {
|
|
1141
|
+
console.log(chalk2.yellow("\n\u2713 No category selected\n"));
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
const categoryComponents = registry.components.filter(
|
|
1145
|
+
(c) => c.category === selectedCategory
|
|
1146
|
+
);
|
|
1147
|
+
const { selected } = await prompts({
|
|
1148
|
+
type: "multiselect",
|
|
1149
|
+
name: "selected",
|
|
1150
|
+
message: "Select components to add",
|
|
1151
|
+
choices: categoryComponents.map((c) => ({
|
|
1152
|
+
title: `${c.title} - ${c.description}`,
|
|
1153
|
+
value: c.name,
|
|
1154
|
+
selected: false
|
|
1155
|
+
})),
|
|
1156
|
+
hint: "- Space to select. Return to submit"
|
|
1157
|
+
});
|
|
1158
|
+
componentsToAdd = registry.components.filter((c) => selected?.includes(c.name));
|
|
1159
|
+
}
|
|
1160
|
+
if (componentsToAdd.length === 0) {
|
|
1161
|
+
console.log(chalk2.yellow("\n\u2713 No components selected\n"));
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
if (dryRun) {
|
|
1165
|
+
console.log(chalk2.bold(`
|
|
1166
|
+
\u{1F50D} Dry Run: Would add ${componentsToAdd.length} component(s)
|
|
1167
|
+
`));
|
|
1168
|
+
const dryRunInfo = [];
|
|
1169
|
+
const spinner2 = ora2("Analyzing...").start();
|
|
1170
|
+
for (const component of componentsToAdd) {
|
|
1171
|
+
spinner2.text = `Analyzing ${component.title}...`;
|
|
1172
|
+
await copyComponent(component, registry, config, cwd, overwrite, spinner2, /* @__PURE__ */ new Set(), true, dryRunInfo);
|
|
1173
|
+
}
|
|
1174
|
+
spinner2.stop();
|
|
1175
|
+
console.log(chalk2.bold("\n\u{1F4CB} Files that would be created:\n"));
|
|
1176
|
+
for (const info2 of dryRunInfo) {
|
|
1177
|
+
console.log(chalk2.cyan(` ${info2.component}:`));
|
|
1178
|
+
for (const file of info2.files) {
|
|
1179
|
+
console.log(chalk2.dim(` \u2192 ${file.target}`));
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
const allDryRunDeps = /* @__PURE__ */ new Set();
|
|
1183
|
+
dryRunInfo.forEach((info2) => info2.dependencies.forEach((dep) => allDryRunDeps.add(dep)));
|
|
1184
|
+
if (allDryRunDeps.size > 0) {
|
|
1185
|
+
console.log(chalk2.bold("\n\u{1F4E6} External dependencies needed:\n"));
|
|
1186
|
+
Array.from(allDryRunDeps).forEach((dep) => console.log(chalk2.dim(` ${dep}`)));
|
|
1187
|
+
}
|
|
1188
|
+
console.log(chalk2.dim("\n Run without --dry-run to install components.\n"));
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
console.log(chalk2.bold(`
|
|
1192
|
+
\u{1F4E6} Adding ${componentsToAdd.length} component(s)...
|
|
1193
|
+
`));
|
|
1194
|
+
const spinner = ora2("Processing...").start();
|
|
1195
|
+
const allDeps = /* @__PURE__ */ new Set();
|
|
1196
|
+
try {
|
|
1197
|
+
const sharedInstalling = /* @__PURE__ */ new Set();
|
|
1198
|
+
if (nonInteractive || all) {
|
|
1199
|
+
sharedInstalling.add("__nonInteractive__");
|
|
1200
|
+
}
|
|
1201
|
+
for (const component of componentsToAdd) {
|
|
1202
|
+
spinner.text = `Adding ${component.title}...`;
|
|
1203
|
+
await copyComponent(component, registry, config, cwd, overwrite, spinner, sharedInstalling, false);
|
|
1204
|
+
component.dependencies.forEach((dep) => allDeps.add(dep));
|
|
1205
|
+
}
|
|
1206
|
+
config.registryVersion = registry.version;
|
|
1207
|
+
await saveConfig(cwd, config);
|
|
1208
|
+
await generateComponentsIndex(config, cwd, registry, spinner);
|
|
1209
|
+
spinner.succeed("All components added!");
|
|
1210
|
+
if (!nonInteractive) {
|
|
1211
|
+
console.log(chalk2.bold("\n\u{1F50D} Running post-install validation...\n"));
|
|
1212
|
+
try {
|
|
1213
|
+
await validate({ cwd, json: false });
|
|
1214
|
+
} catch {
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
console.log(chalk2.bold("\n\u{1F4E6} External dependencies...\n"));
|
|
1218
|
+
const packageJsonPath = path2.join(cwd, "package.json");
|
|
1219
|
+
let missingDeps = [];
|
|
1220
|
+
if (fs2.existsSync(packageJsonPath)) {
|
|
1221
|
+
const packageJson = await fs2.readJSON(packageJsonPath);
|
|
1222
|
+
const installed = {
|
|
1223
|
+
...packageJson.dependencies,
|
|
1224
|
+
...packageJson.devDependencies
|
|
1225
|
+
};
|
|
1226
|
+
missingDeps = Array.from(allDeps).filter((dep) => !installed[dep]);
|
|
1227
|
+
} else {
|
|
1228
|
+
missingDeps = Array.from(allDeps);
|
|
1229
|
+
}
|
|
1230
|
+
if (missingDeps.length > 0) {
|
|
1231
|
+
console.log(chalk2.yellow("\u26A0 Missing dependencies:"));
|
|
1232
|
+
missingDeps.forEach((dep) => console.log(chalk2.dim(` - ${dep}`)));
|
|
1233
|
+
let autoInstall = nonInteractive;
|
|
1234
|
+
if (!nonInteractive) {
|
|
1235
|
+
const answer = await prompts({
|
|
1236
|
+
type: "confirm",
|
|
1237
|
+
name: "autoInstall",
|
|
1238
|
+
message: "Install missing dependencies automatically?",
|
|
1239
|
+
initial: true
|
|
1240
|
+
});
|
|
1241
|
+
autoInstall = answer.autoInstall;
|
|
1242
|
+
}
|
|
1243
|
+
if (autoInstall) {
|
|
1244
|
+
const installSpinner = ora2("Installing dependencies...").start();
|
|
1245
|
+
try {
|
|
1246
|
+
const hasYarnLock = fs2.existsSync(path2.join(cwd, "yarn.lock"));
|
|
1247
|
+
const hasPnpmLock = fs2.existsSync(path2.join(cwd, "pnpm-lock.yaml"));
|
|
1248
|
+
const hasBunLock = fs2.existsSync(path2.join(cwd, "bun.lockb"));
|
|
1249
|
+
let installCmd;
|
|
1250
|
+
if (hasPnpmLock) {
|
|
1251
|
+
installCmd = `pnpm add ${missingDeps.join(" ")}`;
|
|
1252
|
+
} else if (hasYarnLock) {
|
|
1253
|
+
installCmd = `yarn add ${missingDeps.join(" ")}`;
|
|
1254
|
+
} else if (hasBunLock) {
|
|
1255
|
+
installCmd = `bun add ${missingDeps.join(" ")}`;
|
|
1256
|
+
} else {
|
|
1257
|
+
installCmd = `npm install ${missingDeps.join(" ")}`;
|
|
1258
|
+
}
|
|
1259
|
+
const { execSync: execSync2 } = await import("child_process");
|
|
1260
|
+
execSync2(installCmd, { cwd, stdio: "pipe" });
|
|
1261
|
+
installSpinner.succeed("Dependencies installed!");
|
|
1262
|
+
} catch (error) {
|
|
1263
|
+
installSpinner.fail("Failed to install dependencies");
|
|
1264
|
+
console.log(chalk2.dim("\nInstall manually with:"));
|
|
1265
|
+
console.log(chalk2.cyan(` pnpm add ${missingDeps.join(" ")}
|
|
1266
|
+
`));
|
|
1267
|
+
}
|
|
1268
|
+
} else {
|
|
1269
|
+
console.log(chalk2.dim("\nInstall manually with:"));
|
|
1270
|
+
console.log(chalk2.cyan(` pnpm add ${missingDeps.join(" ")}
|
|
1271
|
+
`));
|
|
1272
|
+
}
|
|
1273
|
+
} else {
|
|
1274
|
+
console.log(chalk2.green("\u2713 All external dependencies installed\n"));
|
|
1275
|
+
}
|
|
1276
|
+
console.log(chalk2.bold.blue("\u{1F4CB} Summary:\n"));
|
|
1277
|
+
console.log(chalk2.dim("Components installed:"));
|
|
1278
|
+
config.installedComponents.forEach((name) => {
|
|
1279
|
+
console.log(chalk2.green(` \u2713 ${name}`));
|
|
1280
|
+
});
|
|
1281
|
+
if (config.installedLib.length > 0) {
|
|
1282
|
+
console.log(chalk2.dim("\nLib modules installed:"));
|
|
1283
|
+
config.installedLib.forEach((name) => {
|
|
1284
|
+
console.log(chalk2.green(` \u2713 ${name}`));
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1287
|
+
console.log(chalk2.bold.green("\n\u2728 Done!\n"));
|
|
1288
|
+
console.log(chalk2.dim("Components are now part of your codebase. Customize freely!"));
|
|
1289
|
+
console.log(chalk2.dim(`Location: ${config.aliases.components}
|
|
1290
|
+
`));
|
|
1291
|
+
} catch (error) {
|
|
1292
|
+
spinner.fail("Failed to add components");
|
|
1293
|
+
console.error(chalk2.red(error));
|
|
1294
|
+
process.exit(1);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
// src/commands/list.ts
|
|
1299
|
+
import chalk3 from "chalk";
|
|
1300
|
+
async function getRegistry3() {
|
|
1301
|
+
try {
|
|
1302
|
+
return await getRegistry();
|
|
1303
|
+
} catch (err) {
|
|
1304
|
+
console.error(chalk3.red("Failed to load registry:", err.message));
|
|
1305
|
+
process.exit(1);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
async function list(options) {
|
|
1309
|
+
const { category, json, cwd = process.cwd() } = options;
|
|
1310
|
+
const registry = await getRegistry3();
|
|
1311
|
+
const config = await loadConfig(cwd);
|
|
1312
|
+
let components = category ? registry.components.filter((c) => c.category === category) : registry.components;
|
|
1313
|
+
if (json) {
|
|
1314
|
+
console.log(JSON.stringify(components, null, 2));
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
console.log(chalk3.bold("\n\u{1F4E6} Microbuild Components (Copy & Own)\n"));
|
|
1318
|
+
console.log(chalk3.dim("Components are copied to your project as source files.\n"));
|
|
1319
|
+
const byCategory = components.reduce((acc, c) => {
|
|
1320
|
+
if (!acc[c.category]) acc[c.category] = [];
|
|
1321
|
+
acc[c.category].push(c);
|
|
1322
|
+
return acc;
|
|
1323
|
+
}, {});
|
|
1324
|
+
const categoryTitles = registry.categories.reduce((acc, cat) => {
|
|
1325
|
+
acc[cat.name] = cat.title;
|
|
1326
|
+
return acc;
|
|
1327
|
+
}, {});
|
|
1328
|
+
for (const [cat, items] of Object.entries(byCategory)) {
|
|
1329
|
+
const catTitle = categoryTitles[cat] || cat.toUpperCase();
|
|
1330
|
+
console.log(chalk3.bold.cyan(`
|
|
1331
|
+
${catTitle}`));
|
|
1332
|
+
items.forEach((item) => {
|
|
1333
|
+
const installed = config?.installedComponents.includes(item.name);
|
|
1334
|
+
const status2 = installed ? chalk3.green("\u2713") : " ";
|
|
1335
|
+
const name = item.name.padEnd(28);
|
|
1336
|
+
console.log(
|
|
1337
|
+
` ${status2} ${chalk3.green(name)} ${chalk3.dim(item.description)}`
|
|
1338
|
+
);
|
|
1339
|
+
if (item.internalDependencies.length > 0) {
|
|
1340
|
+
console.log(
|
|
1341
|
+
` ${chalk3.dim("requires:")} ${chalk3.yellow(item.internalDependencies.join(", "))}`
|
|
1342
|
+
);
|
|
1343
|
+
}
|
|
1344
|
+
});
|
|
1345
|
+
}
|
|
1346
|
+
console.log(chalk3.bold("\n\u{1F4C2} Categories"));
|
|
1347
|
+
registry.categories.forEach((cat) => {
|
|
1348
|
+
const count = registry.components.filter((c) => c.category === cat.name).length;
|
|
1349
|
+
console.log(` ${chalk3.cyan(cat.name.padEnd(15))} ${count} components - ${chalk3.dim(cat.description)}`);
|
|
1350
|
+
});
|
|
1351
|
+
if (config) {
|
|
1352
|
+
console.log(chalk3.bold("\n\u{1F4CB} Installed"));
|
|
1353
|
+
console.log(` Components: ${chalk3.green(config.installedComponents.length)}`);
|
|
1354
|
+
console.log(` Lib modules: ${chalk3.green(config.installedLib.length)} ${chalk3.dim(`(${config.installedLib.join(", ") || "none"})`)}`);
|
|
1355
|
+
}
|
|
1356
|
+
console.log(chalk3.bold("\n\u{1F4A1} Usage"));
|
|
1357
|
+
console.log(chalk3.dim(" npx microbuild add input"));
|
|
1358
|
+
console.log(chalk3.dim(" npx microbuild add input select-dropdown datetime"));
|
|
1359
|
+
console.log(chalk3.dim(" npx microbuild add --category selection"));
|
|
1360
|
+
console.log(chalk3.dim(" npx microbuild add --all\n"));
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
// src/commands/diff.ts
|
|
1364
|
+
import fs3 from "fs-extra";
|
|
1365
|
+
import path3 from "path";
|
|
1366
|
+
import chalk4 from "chalk";
|
|
1367
|
+
async function getRegistry4() {
|
|
1368
|
+
try {
|
|
1369
|
+
return await getRegistry();
|
|
1370
|
+
} catch (err) {
|
|
1371
|
+
console.error(chalk4.red("Failed to load registry:", err.message));
|
|
1372
|
+
process.exit(1);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
async function diff(component, options) {
|
|
1376
|
+
const { cwd } = options;
|
|
1377
|
+
console.log(chalk4.bold(`
|
|
1378
|
+
\u{1F4CB} Preview: ${component}
|
|
1379
|
+
`));
|
|
1380
|
+
const config = await loadConfig(cwd);
|
|
1381
|
+
if (!config) {
|
|
1382
|
+
console.log(chalk4.red('\u2717 microbuild.json not found. Run "npx microbuild init" first.\n'));
|
|
1383
|
+
process.exit(1);
|
|
1384
|
+
}
|
|
1385
|
+
const registry = await getRegistry4();
|
|
1386
|
+
const comp = registry.components.find(
|
|
1387
|
+
(c) => c.name.toLowerCase() === component.toLowerCase() || c.title.toLowerCase() === component.toLowerCase()
|
|
1388
|
+
);
|
|
1389
|
+
if (!comp) {
|
|
1390
|
+
console.log(chalk4.red(`\u2717 Component not found: ${component}
|
|
1391
|
+
`));
|
|
1392
|
+
console.log(chalk4.dim('Run "npx microbuild list" to see available components.\n'));
|
|
1393
|
+
process.exit(1);
|
|
1394
|
+
}
|
|
1395
|
+
console.log(chalk4.bold.cyan(`${comp.title}`));
|
|
1396
|
+
console.log(chalk4.dim(comp.description));
|
|
1397
|
+
console.log();
|
|
1398
|
+
console.log(chalk4.bold("Files to be created:"));
|
|
1399
|
+
for (const file of comp.files) {
|
|
1400
|
+
const targetPath = path3.join(
|
|
1401
|
+
config.srcDir ? path3.join(cwd, "src") : cwd,
|
|
1402
|
+
file.target
|
|
1403
|
+
);
|
|
1404
|
+
const relativePath = path3.relative(cwd, targetPath);
|
|
1405
|
+
const exists = fs3.existsSync(targetPath);
|
|
1406
|
+
if (exists) {
|
|
1407
|
+
console.log(chalk4.yellow(` \u26A0 ${relativePath} (exists, will be overwritten)`));
|
|
1408
|
+
} else {
|
|
1409
|
+
console.log(chalk4.green(` + ${relativePath}`));
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
if (comp.internalDependencies.length > 0) {
|
|
1413
|
+
console.log();
|
|
1414
|
+
console.log(chalk4.bold("Lib modules required:"));
|
|
1415
|
+
for (const dep of comp.internalDependencies) {
|
|
1416
|
+
const installed = config.installedLib.includes(dep);
|
|
1417
|
+
if (installed) {
|
|
1418
|
+
console.log(chalk4.dim(` \u2713 ${dep} (already installed)`));
|
|
1419
|
+
} else {
|
|
1420
|
+
console.log(chalk4.cyan(` \u2192 ${dep}`));
|
|
1421
|
+
const libModule = registry.lib[dep];
|
|
1422
|
+
if (libModule?.files) {
|
|
1423
|
+
for (const file of libModule.files) {
|
|
1424
|
+
console.log(chalk4.dim(` + ${file.target}`));
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
if (comp.dependencies.length > 0) {
|
|
1431
|
+
console.log();
|
|
1432
|
+
console.log(chalk4.bold("External dependencies:"));
|
|
1433
|
+
const packageJsonPath = path3.join(cwd, "package.json");
|
|
1434
|
+
let installed = {};
|
|
1435
|
+
if (fs3.existsSync(packageJsonPath)) {
|
|
1436
|
+
const pkg = await fs3.readJSON(packageJsonPath);
|
|
1437
|
+
installed = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1438
|
+
}
|
|
1439
|
+
for (const dep of comp.dependencies) {
|
|
1440
|
+
if (installed[dep]) {
|
|
1441
|
+
console.log(chalk4.dim(` \u2713 ${dep} (v${installed[dep]})`));
|
|
1442
|
+
} else {
|
|
1443
|
+
console.log(chalk4.yellow(` \u26A0 ${dep} (not installed)`));
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
console.log();
|
|
1448
|
+
console.log(chalk4.bold("Import transformation preview:"));
|
|
1449
|
+
const sampleSource = comp.files[0]?.source;
|
|
1450
|
+
if (sampleSource) {
|
|
1451
|
+
try {
|
|
1452
|
+
const content = await resolveSourceFile(sampleSource);
|
|
1453
|
+
const lines = content.split("\n").slice(0, 15);
|
|
1454
|
+
console.log(chalk4.dim("\nOriginal imports:"));
|
|
1455
|
+
lines.filter((l) => l.startsWith("import")).forEach((line) => {
|
|
1456
|
+
if (line.includes("@microbuild/")) {
|
|
1457
|
+
console.log(chalk4.red(` ${line}`));
|
|
1458
|
+
}
|
|
1459
|
+
});
|
|
1460
|
+
const transformed = transformImports(content, config);
|
|
1461
|
+
const transformedLines = transformed.split("\n").slice(0, 15);
|
|
1462
|
+
console.log(chalk4.dim("\nTransformed imports:"));
|
|
1463
|
+
transformedLines.filter((l) => l.startsWith("import")).forEach((line) => {
|
|
1464
|
+
if (line.includes(config.aliases.lib) || line.includes(config.aliases.components)) {
|
|
1465
|
+
console.log(chalk4.green(` ${line}`));
|
|
1466
|
+
}
|
|
1467
|
+
});
|
|
1468
|
+
} catch {
|
|
1469
|
+
console.log(chalk4.dim("\n (source preview not available in remote mode)"));
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
console.log();
|
|
1473
|
+
console.log(chalk4.dim("Run the following to add this component:"));
|
|
1474
|
+
console.log(chalk4.cyan(` npx microbuild add ${comp.name}
|
|
1475
|
+
`));
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
// src/commands/status.ts
|
|
1479
|
+
import fs4 from "fs-extra";
|
|
1480
|
+
import path4 from "path";
|
|
1481
|
+
import chalk5 from "chalk";
|
|
1482
|
+
async function findMicrobuildFiles(dir) {
|
|
1483
|
+
const files = [];
|
|
1484
|
+
if (!fs4.existsSync(dir)) {
|
|
1485
|
+
return files;
|
|
1486
|
+
}
|
|
1487
|
+
const entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
1488
|
+
for (const entry of entries) {
|
|
1489
|
+
const fullPath = path4.join(dir, entry.name);
|
|
1490
|
+
if (entry.isDirectory()) {
|
|
1491
|
+
if (entry.name !== "node_modules" && !entry.name.startsWith(".")) {
|
|
1492
|
+
const subFiles = await findMicrobuildFiles(fullPath);
|
|
1493
|
+
files.push(...subFiles);
|
|
1494
|
+
}
|
|
1495
|
+
} else if (entry.isFile() && /\.(tsx?|jsx?)$/.test(entry.name)) {
|
|
1496
|
+
const content = await fs4.readFile(fullPath, "utf-8");
|
|
1497
|
+
if (hasMicrobuildOrigin(content)) {
|
|
1498
|
+
const info2 = extractOriginInfo(content);
|
|
1499
|
+
if (info2 && info2.origin) {
|
|
1500
|
+
files.push({
|
|
1501
|
+
path: fullPath,
|
|
1502
|
+
origin: info2.origin,
|
|
1503
|
+
version: info2.version || "unknown",
|
|
1504
|
+
date: info2.date || "unknown",
|
|
1505
|
+
modified: false
|
|
1506
|
+
// TODO: Compare with registry hash
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
return files;
|
|
1513
|
+
}
|
|
1514
|
+
function groupByOrigin(files) {
|
|
1515
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1516
|
+
for (const file of files) {
|
|
1517
|
+
const [pkg, component] = file.origin.split("/").slice(0, 2);
|
|
1518
|
+
const key = `${pkg}/${component || "root"}`;
|
|
1519
|
+
if (!groups.has(key)) {
|
|
1520
|
+
groups.set(key, []);
|
|
1521
|
+
}
|
|
1522
|
+
groups.get(key).push(file);
|
|
1523
|
+
}
|
|
1524
|
+
return groups;
|
|
1525
|
+
}
|
|
1526
|
+
async function status(options) {
|
|
1527
|
+
const { cwd, json = false } = options;
|
|
1528
|
+
const config = await loadConfig(cwd);
|
|
1529
|
+
if (!config) {
|
|
1530
|
+
console.log(chalk5.red("\n\u2717 microbuild.json not found.\n"));
|
|
1531
|
+
console.log("This project may not be initialized with Microbuild.");
|
|
1532
|
+
console.log('Run "npx @microbuild/cli init" to initialize.\n');
|
|
1533
|
+
return;
|
|
1534
|
+
}
|
|
1535
|
+
const srcDir = config.srcDir ? path4.join(cwd, "src") : cwd;
|
|
1536
|
+
const files = await findMicrobuildFiles(srcDir);
|
|
1537
|
+
if (json) {
|
|
1538
|
+
console.log(JSON.stringify({
|
|
1539
|
+
config: {
|
|
1540
|
+
installedLib: config.installedLib,
|
|
1541
|
+
installedComponents: config.installedComponents
|
|
1542
|
+
},
|
|
1543
|
+
files: files.map((f) => ({
|
|
1544
|
+
...f,
|
|
1545
|
+
path: path4.relative(cwd, f.path)
|
|
1546
|
+
}))
|
|
1547
|
+
}, null, 2));
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1550
|
+
console.log("\n" + chalk5.bold("\u{1F4E6} Microbuild Status\n"));
|
|
1551
|
+
console.log(chalk5.gray("Config file: ") + "microbuild.json");
|
|
1552
|
+
console.log(chalk5.gray("Lib modules: ") + (config.installedLib.join(", ") || "none"));
|
|
1553
|
+
console.log(chalk5.gray("Components: ") + (config.installedComponents.join(", ") || "none"));
|
|
1554
|
+
if (files.length === 0) {
|
|
1555
|
+
console.log("\n" + chalk5.yellow("No files with @microbuild-origin headers found."));
|
|
1556
|
+
console.log(chalk5.gray("This may mean components were installed before origin tracking was added."));
|
|
1557
|
+
return;
|
|
1558
|
+
}
|
|
1559
|
+
const groups = groupByOrigin(files);
|
|
1560
|
+
console.log("\n" + chalk5.bold("Installed Files:\n"));
|
|
1561
|
+
for (const [origin, groupFiles] of groups) {
|
|
1562
|
+
console.log(chalk5.cyan(` ${origin}`));
|
|
1563
|
+
for (const file of groupFiles) {
|
|
1564
|
+
const relativePath = path4.relative(cwd, file.path);
|
|
1565
|
+
console.log(chalk5.gray(` \u2514\u2500 ${relativePath}`));
|
|
1566
|
+
console.log(chalk5.gray(` v${file.version} (${file.date})`));
|
|
1567
|
+
}
|
|
1568
|
+
console.log();
|
|
1569
|
+
}
|
|
1570
|
+
console.log(chalk5.gray(`
|
|
1571
|
+
Total: ${files.length} files from Microbuild
|
|
1572
|
+
`));
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
// src/commands/info.ts
|
|
1576
|
+
import chalk6 from "chalk";
|
|
1577
|
+
async function getRegistry5() {
|
|
1578
|
+
try {
|
|
1579
|
+
return await getRegistry();
|
|
1580
|
+
} catch (err) {
|
|
1581
|
+
console.error(chalk6.red("Failed to load registry:", err.message));
|
|
1582
|
+
process.exit(1);
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
function findComponent(name, registry) {
|
|
1586
|
+
const normalized = name.toLowerCase().replace(/-/g, "");
|
|
1587
|
+
const direct = registry.components.find(
|
|
1588
|
+
(c) => c.name.toLowerCase() === name.toLowerCase() || c.title.toLowerCase() === name.toLowerCase()
|
|
1589
|
+
);
|
|
1590
|
+
if (direct) return direct;
|
|
1591
|
+
const fuzzy = registry.components.find(
|
|
1592
|
+
(c) => c.name.toLowerCase().replace(/-/g, "") === normalized || c.title.toLowerCase().replace(/-/g, "") === normalized
|
|
1593
|
+
);
|
|
1594
|
+
if (fuzzy) return fuzzy;
|
|
1595
|
+
const aliases = {
|
|
1596
|
+
"form": "vform",
|
|
1597
|
+
"dynamicform": "vform",
|
|
1598
|
+
"select": "select-dropdown",
|
|
1599
|
+
"dropdown": "select-dropdown",
|
|
1600
|
+
"checkbox": "boolean",
|
|
1601
|
+
"switch": "toggle",
|
|
1602
|
+
"date": "datetime",
|
|
1603
|
+
"time": "datetime",
|
|
1604
|
+
"text": "input",
|
|
1605
|
+
"textinput": "input",
|
|
1606
|
+
"image": "file-image",
|
|
1607
|
+
"wysiwyg": "rich-text-html",
|
|
1608
|
+
"markdown": "rich-text-markdown",
|
|
1609
|
+
"m2m": "list-m2m",
|
|
1610
|
+
"m2o": "list-m2o",
|
|
1611
|
+
"o2m": "list-o2m",
|
|
1612
|
+
"m2a": "list-m2a",
|
|
1613
|
+
"manytomany": "list-m2m",
|
|
1614
|
+
"manytoone": "list-m2o",
|
|
1615
|
+
"onetomany": "list-o2m",
|
|
1616
|
+
"manytoany": "list-m2a"
|
|
1617
|
+
};
|
|
1618
|
+
const aliased = aliases[normalized];
|
|
1619
|
+
if (aliased) {
|
|
1620
|
+
return registry.components.find((c) => c.name === aliased);
|
|
1621
|
+
}
|
|
1622
|
+
return void 0;
|
|
1623
|
+
}
|
|
1624
|
+
function calculateTotalDependencies(component, registry, visited = /* @__PURE__ */ new Set()) {
|
|
1625
|
+
if (visited.has(component.name)) {
|
|
1626
|
+
return { components: [], libs: [], npm: [] };
|
|
1627
|
+
}
|
|
1628
|
+
visited.add(component.name);
|
|
1629
|
+
const result = {
|
|
1630
|
+
components: [component.name],
|
|
1631
|
+
libs: [...component.internalDependencies],
|
|
1632
|
+
npm: [...component.dependencies]
|
|
1633
|
+
};
|
|
1634
|
+
if (component.registryDependencies) {
|
|
1635
|
+
for (const depName of component.registryDependencies) {
|
|
1636
|
+
const dep = registry.components.find((c) => c.name === depName);
|
|
1637
|
+
if (dep) {
|
|
1638
|
+
const subDeps = calculateTotalDependencies(dep, registry, visited);
|
|
1639
|
+
result.components.push(...subDeps.components);
|
|
1640
|
+
result.libs.push(...subDeps.libs);
|
|
1641
|
+
result.npm.push(...subDeps.npm);
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
return {
|
|
1646
|
+
components: [...new Set(result.components)],
|
|
1647
|
+
libs: [...new Set(result.libs)],
|
|
1648
|
+
npm: [...new Set(result.npm)]
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
async function info(componentName, options) {
|
|
1652
|
+
const { json } = options;
|
|
1653
|
+
const registry = await getRegistry5();
|
|
1654
|
+
const component = findComponent(componentName, registry);
|
|
1655
|
+
if (!component) {
|
|
1656
|
+
console.log(chalk6.red(`
|
|
1657
|
+
\u2717 Component not found: ${componentName}
|
|
1658
|
+
`));
|
|
1659
|
+
const suggestions = registry.components.filter(
|
|
1660
|
+
(c) => c.name.includes(componentName.toLowerCase()) || c.title.toLowerCase().includes(componentName.toLowerCase()) || c.description.toLowerCase().includes(componentName.toLowerCase())
|
|
1661
|
+
).slice(0, 5);
|
|
1662
|
+
if (suggestions.length > 0) {
|
|
1663
|
+
console.log(chalk6.yellow("Did you mean one of these?\n"));
|
|
1664
|
+
suggestions.forEach((s) => {
|
|
1665
|
+
console.log(` ${chalk6.green(s.name.padEnd(25))} ${chalk6.dim(s.description)}`);
|
|
1666
|
+
});
|
|
1667
|
+
console.log();
|
|
1668
|
+
}
|
|
1669
|
+
console.log(chalk6.dim('Run "microbuild list" to see all available components.\n'));
|
|
1670
|
+
process.exit(1);
|
|
1671
|
+
}
|
|
1672
|
+
const totals = calculateTotalDependencies(component, registry);
|
|
1673
|
+
const category = registry.categories.find((c) => c.name === component.category);
|
|
1674
|
+
if (json) {
|
|
1675
|
+
console.log(JSON.stringify({
|
|
1676
|
+
...component,
|
|
1677
|
+
categoryTitle: category?.title,
|
|
1678
|
+
totalDependencies: totals
|
|
1679
|
+
}, null, 2));
|
|
1680
|
+
return;
|
|
1681
|
+
}
|
|
1682
|
+
console.log(chalk6.bold.blue(`
|
|
1683
|
+
\u{1F4E6} ${component.title}`));
|
|
1684
|
+
console.log(chalk6.dim(` ${component.name} \u2022 ${category?.title || component.category}
|
|
1685
|
+
`));
|
|
1686
|
+
console.log(`${component.description}
|
|
1687
|
+
`);
|
|
1688
|
+
console.log(chalk6.bold("\u{1F4C1} Source Files"));
|
|
1689
|
+
component.files.forEach((file) => {
|
|
1690
|
+
console.log(` ${chalk6.green(file.source)}`);
|
|
1691
|
+
console.log(` ${chalk6.dim("\u2192")} ${chalk6.cyan(file.target)}`);
|
|
1692
|
+
});
|
|
1693
|
+
if (component.dependencies.length > 0) {
|
|
1694
|
+
console.log(chalk6.bold("\n\u{1F4E6} NPM Dependencies"));
|
|
1695
|
+
console.log(` ${chalk6.yellow(component.dependencies.join(", "))}`);
|
|
1696
|
+
}
|
|
1697
|
+
if (component.internalDependencies.length > 0) {
|
|
1698
|
+
console.log(chalk6.bold("\n\u{1F527} Lib Modules"));
|
|
1699
|
+
component.internalDependencies.forEach((lib) => {
|
|
1700
|
+
const libModule = registry.lib[lib];
|
|
1701
|
+
console.log(` ${chalk6.magenta(lib)} ${chalk6.dim(`- ${libModule?.description || ""}`)}`);
|
|
1702
|
+
});
|
|
1703
|
+
}
|
|
1704
|
+
if (component.registryDependencies && component.registryDependencies.length > 0) {
|
|
1705
|
+
console.log(chalk6.bold("\n\u{1F517} Component Dependencies"));
|
|
1706
|
+
console.log(` ${chalk6.cyan(component.registryDependencies.length)} components will be installed:`);
|
|
1707
|
+
const chunks = [];
|
|
1708
|
+
for (let i = 0; i < component.registryDependencies.length; i += 6) {
|
|
1709
|
+
chunks.push(component.registryDependencies.slice(i, i + 6).join(", "));
|
|
1710
|
+
}
|
|
1711
|
+
chunks.forEach((chunk) => console.log(` ${chalk6.dim(chunk)}`));
|
|
1712
|
+
}
|
|
1713
|
+
if (component.interface) {
|
|
1714
|
+
console.log(chalk6.bold("\n\u{1F3A8} Interface Metadata"));
|
|
1715
|
+
console.log(` ID: ${chalk6.green(component.interface.id)}`);
|
|
1716
|
+
console.log(` Icon: ${chalk6.cyan(component.interface.icon)}`);
|
|
1717
|
+
console.log(` Field Types: ${chalk6.yellow(component.interface.types.join(", "))}`);
|
|
1718
|
+
if (component.interface.recommended) {
|
|
1719
|
+
console.log(` ${chalk6.green("\u2605")} Recommended interface for its field types`);
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
console.log(chalk6.bold("\n\u{1F4CA} Installation Summary"));
|
|
1723
|
+
console.log(` Components: ${chalk6.green(totals.components.length)}`);
|
|
1724
|
+
console.log(` Lib modules: ${chalk6.green(totals.libs.length)} ${chalk6.dim(`(${totals.libs.join(", ") || "none"})`)}`);
|
|
1725
|
+
console.log(` NPM packages: ${chalk6.yellow(totals.npm.length)}`);
|
|
1726
|
+
console.log(chalk6.bold("\n\u{1F4A1} Usage"));
|
|
1727
|
+
console.log(chalk6.dim(` microbuild add ${component.name}`));
|
|
1728
|
+
console.log(chalk6.dim(` microbuild tree ${component.name}
|
|
1729
|
+
`));
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
// src/commands/tree.ts
|
|
1733
|
+
import chalk7 from "chalk";
|
|
1734
|
+
async function getRegistry6() {
|
|
1735
|
+
try {
|
|
1736
|
+
return await getRegistry();
|
|
1737
|
+
} catch (err) {
|
|
1738
|
+
console.error(chalk7.red("Failed to load registry:", err.message));
|
|
1739
|
+
process.exit(1);
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
function findComponent2(name, registry) {
|
|
1743
|
+
return registry.components.find(
|
|
1744
|
+
(c) => c.name.toLowerCase() === name.toLowerCase() || c.title.toLowerCase() === name.toLowerCase()
|
|
1745
|
+
);
|
|
1746
|
+
}
|
|
1747
|
+
function buildTree(component, registry, visited = /* @__PURE__ */ new Set(), depth = 0, maxDepth = 3) {
|
|
1748
|
+
const node = {
|
|
1749
|
+
name: component.name,
|
|
1750
|
+
type: "component",
|
|
1751
|
+
description: component.description,
|
|
1752
|
+
children: []
|
|
1753
|
+
};
|
|
1754
|
+
if (visited.has(component.name) || depth >= maxDepth) {
|
|
1755
|
+
return node;
|
|
1756
|
+
}
|
|
1757
|
+
visited.add(component.name);
|
|
1758
|
+
for (const libName of component.internalDependencies) {
|
|
1759
|
+
const libModule = registry.lib[libName];
|
|
1760
|
+
const libNode = {
|
|
1761
|
+
name: libName,
|
|
1762
|
+
type: "lib",
|
|
1763
|
+
description: libModule?.description,
|
|
1764
|
+
children: []
|
|
1765
|
+
};
|
|
1766
|
+
if (libModule?.internalDependencies) {
|
|
1767
|
+
for (const subLib of libModule.internalDependencies) {
|
|
1768
|
+
if (!visited.has(`lib:${subLib}`)) {
|
|
1769
|
+
visited.add(`lib:${subLib}`);
|
|
1770
|
+
const subLibModule = registry.lib[subLib];
|
|
1771
|
+
libNode.children.push({
|
|
1772
|
+
name: subLib,
|
|
1773
|
+
type: "lib",
|
|
1774
|
+
description: subLibModule?.description,
|
|
1775
|
+
children: []
|
|
1776
|
+
});
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
node.children.push(libNode);
|
|
1781
|
+
}
|
|
1782
|
+
if (component.registryDependencies) {
|
|
1783
|
+
for (const depName of component.registryDependencies) {
|
|
1784
|
+
const dep = findComponent2(depName, registry);
|
|
1785
|
+
if (dep) {
|
|
1786
|
+
node.children.push(buildTree(dep, registry, visited, depth + 1, maxDepth));
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
for (const npmDep of component.dependencies) {
|
|
1791
|
+
node.children.push({
|
|
1792
|
+
name: npmDep,
|
|
1793
|
+
type: "npm",
|
|
1794
|
+
children: []
|
|
1795
|
+
});
|
|
1796
|
+
}
|
|
1797
|
+
return node;
|
|
1798
|
+
}
|
|
1799
|
+
function renderTree(node, prefix = "", isLast = true, isRoot = true) {
|
|
1800
|
+
const connector = isRoot ? "" : isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
1801
|
+
const childPrefix = isRoot ? "" : isLast ? " " : "\u2502 ";
|
|
1802
|
+
let icon = "";
|
|
1803
|
+
let color = chalk7.white;
|
|
1804
|
+
switch (node.type) {
|
|
1805
|
+
case "component":
|
|
1806
|
+
icon = "\u{1F4E6}";
|
|
1807
|
+
color = chalk7.green;
|
|
1808
|
+
break;
|
|
1809
|
+
case "lib":
|
|
1810
|
+
icon = "\u{1F527}";
|
|
1811
|
+
color = chalk7.magenta;
|
|
1812
|
+
break;
|
|
1813
|
+
case "npm":
|
|
1814
|
+
icon = "\u{1F4DA}";
|
|
1815
|
+
color = chalk7.yellow;
|
|
1816
|
+
break;
|
|
1817
|
+
}
|
|
1818
|
+
console.log(
|
|
1819
|
+
prefix + connector + icon + " " + color(node.name) + (node.description && isRoot ? chalk7.dim(` - ${node.description}`) : "")
|
|
1820
|
+
);
|
|
1821
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
1822
|
+
const child = node.children[i];
|
|
1823
|
+
const isChildLast = i === node.children.length - 1;
|
|
1824
|
+
renderTree(child, prefix + childPrefix, isChildLast, false);
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
function flattenTree(node) {
|
|
1828
|
+
const result = {
|
|
1829
|
+
components: /* @__PURE__ */ new Set(),
|
|
1830
|
+
libs: /* @__PURE__ */ new Set(),
|
|
1831
|
+
npm: /* @__PURE__ */ new Set()
|
|
1832
|
+
};
|
|
1833
|
+
function traverse(n) {
|
|
1834
|
+
switch (n.type) {
|
|
1835
|
+
case "component":
|
|
1836
|
+
result.components.add(n.name);
|
|
1837
|
+
break;
|
|
1838
|
+
case "lib":
|
|
1839
|
+
result.libs.add(n.name);
|
|
1840
|
+
break;
|
|
1841
|
+
case "npm":
|
|
1842
|
+
result.npm.add(n.name);
|
|
1843
|
+
break;
|
|
1844
|
+
}
|
|
1845
|
+
n.children.forEach(traverse);
|
|
1846
|
+
}
|
|
1847
|
+
traverse(node);
|
|
1848
|
+
return {
|
|
1849
|
+
components: Array.from(result.components),
|
|
1850
|
+
libs: Array.from(result.libs),
|
|
1851
|
+
npm: Array.from(result.npm)
|
|
1852
|
+
};
|
|
1853
|
+
}
|
|
1854
|
+
async function tree(componentName, options) {
|
|
1855
|
+
const { json, depth = 2 } = options;
|
|
1856
|
+
const registry = await getRegistry6();
|
|
1857
|
+
const component = findComponent2(componentName, registry);
|
|
1858
|
+
if (!component) {
|
|
1859
|
+
console.log(chalk7.red(`
|
|
1860
|
+
\u2717 Component not found: ${componentName}
|
|
1861
|
+
`));
|
|
1862
|
+
const suggestions = registry.components.filter(
|
|
1863
|
+
(c) => c.name.includes(componentName.toLowerCase()) || c.title.toLowerCase().includes(componentName.toLowerCase())
|
|
1864
|
+
).slice(0, 5);
|
|
1865
|
+
if (suggestions.length > 0) {
|
|
1866
|
+
console.log(chalk7.yellow("Did you mean one of these?\n"));
|
|
1867
|
+
suggestions.forEach((s) => {
|
|
1868
|
+
console.log(` ${chalk7.green(s.name.padEnd(25))} ${chalk7.dim(s.description)}`);
|
|
1869
|
+
});
|
|
1870
|
+
console.log();
|
|
1871
|
+
}
|
|
1872
|
+
console.log(chalk7.dim('Run "microbuild list" to see all available components.\n'));
|
|
1873
|
+
process.exit(1);
|
|
1874
|
+
}
|
|
1875
|
+
const treeData = buildTree(component, registry, /* @__PURE__ */ new Set(), 0, depth);
|
|
1876
|
+
const flattened = flattenTree(treeData);
|
|
1877
|
+
if (json) {
|
|
1878
|
+
console.log(JSON.stringify({
|
|
1879
|
+
tree: treeData,
|
|
1880
|
+
summary: flattened
|
|
1881
|
+
}, null, 2));
|
|
1882
|
+
return;
|
|
1883
|
+
}
|
|
1884
|
+
console.log(chalk7.bold.blue(`
|
|
1885
|
+
\u{1F333} Dependency Tree: ${component.title}
|
|
1886
|
+
`));
|
|
1887
|
+
renderTree(treeData);
|
|
1888
|
+
console.log(chalk7.bold("\n\u{1F4CA} Summary"));
|
|
1889
|
+
console.log(` ${chalk7.green("\u{1F4E6} Components:")} ${flattened.components.length}`);
|
|
1890
|
+
console.log(` ${chalk7.magenta("\u{1F527} Lib modules:")} ${flattened.libs.length} ${chalk7.dim(`(${flattened.libs.join(", ") || "none"})`)}`);
|
|
1891
|
+
console.log(` ${chalk7.yellow("\u{1F4DA} NPM packages:")} ${flattened.npm.length}`);
|
|
1892
|
+
if (flattened.npm.length > 0) {
|
|
1893
|
+
console.log(chalk7.bold("\n\u{1F4E6} Install NPM Dependencies"));
|
|
1894
|
+
console.log(chalk7.dim(` pnpm add ${flattened.npm.join(" ")}
|
|
1895
|
+
`));
|
|
1896
|
+
}
|
|
1897
|
+
console.log(chalk7.bold("\u{1F4A1} Add this component"));
|
|
1898
|
+
console.log(chalk7.dim(` microbuild add ${component.name}
|
|
1899
|
+
`));
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
// src/commands/fix.ts
|
|
1903
|
+
import fs5 from "fs-extra";
|
|
1904
|
+
import path5 from "path";
|
|
1905
|
+
import chalk8 from "chalk";
|
|
1906
|
+
import ora3 from "ora";
|
|
1907
|
+
import fg2 from "fast-glob";
|
|
1908
|
+
import prompts2 from "prompts";
|
|
1909
|
+
async function fixUntransformedImports(cwd, config, dryRun) {
|
|
1910
|
+
const result = { fixed: 0, skipped: 0, errors: [] };
|
|
1911
|
+
const srcDir = config.srcDir ? path5.join(cwd, "src") : cwd;
|
|
1912
|
+
const patterns = [
|
|
1913
|
+
path5.join(srcDir, "components/**/*.{ts,tsx,js,jsx}"),
|
|
1914
|
+
path5.join(srcDir, "lib/microbuild/**/*.{ts,tsx,js,jsx}")
|
|
1915
|
+
];
|
|
1916
|
+
for (const pattern of patterns) {
|
|
1917
|
+
const files = await fg2(pattern, { ignore: ["**/node_modules/**"] });
|
|
1918
|
+
for (const file of files) {
|
|
1919
|
+
const content = await fs5.readFile(file, "utf-8");
|
|
1920
|
+
if (content.includes("from '@microbuild/") || content.includes('from "@microbuild/')) {
|
|
1921
|
+
const transformed = transformImports(content, config);
|
|
1922
|
+
if (transformed !== content) {
|
|
1923
|
+
if (dryRun) {
|
|
1924
|
+
console.log(chalk8.dim(` Would fix: ${path5.relative(cwd, file)}`));
|
|
1925
|
+
} else {
|
|
1926
|
+
await fs5.writeFile(file, transformed);
|
|
1927
|
+
}
|
|
1928
|
+
result.fixed++;
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
return result;
|
|
1934
|
+
}
|
|
1935
|
+
async function fixSsrUnsafeExports(cwd, config, dryRun) {
|
|
1936
|
+
const result = { fixed: 0, skipped: 0, errors: [] };
|
|
1937
|
+
const srcDir = config.srcDir ? path5.join(cwd, "src") : cwd;
|
|
1938
|
+
const indexPath = path5.join(srcDir, "components/ui/index.ts");
|
|
1939
|
+
if (!fs5.existsSync(indexPath)) {
|
|
1940
|
+
return result;
|
|
1941
|
+
}
|
|
1942
|
+
let content = await fs5.readFile(indexPath, "utf-8");
|
|
1943
|
+
let modified = false;
|
|
1944
|
+
if (content.includes("from './input-block-editor'") && !content.includes("from './input-block-editor-wrapper'")) {
|
|
1945
|
+
const wrapperPath = path5.join(srcDir, "components/ui/input-block-editor-wrapper.tsx");
|
|
1946
|
+
if (fs5.existsSync(wrapperPath)) {
|
|
1947
|
+
content = content.replace(
|
|
1948
|
+
/export \* from ['"]\.\/input-block-editor['"]/g,
|
|
1949
|
+
"export * from './input-block-editor-wrapper'"
|
|
1950
|
+
);
|
|
1951
|
+
modified = true;
|
|
1952
|
+
result.fixed++;
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
if (modified && !dryRun) {
|
|
1956
|
+
await fs5.writeFile(indexPath, content);
|
|
1957
|
+
} else if (modified && dryRun) {
|
|
1958
|
+
console.log(chalk8.dim(` Would fix SSR exports in: components/ui/index.ts`));
|
|
1959
|
+
}
|
|
1960
|
+
return result;
|
|
1961
|
+
}
|
|
1962
|
+
async function fixDuplicateExports(cwd, config, dryRun) {
|
|
1963
|
+
const result = { fixed: 0, skipped: 0, errors: [] };
|
|
1964
|
+
const srcDir = config.srcDir ? path5.join(cwd, "src") : cwd;
|
|
1965
|
+
const indexPath = path5.join(srcDir, "components/ui/index.ts");
|
|
1966
|
+
if (!fs5.existsSync(indexPath)) {
|
|
1967
|
+
return result;
|
|
1968
|
+
}
|
|
1969
|
+
const content = await fs5.readFile(indexPath, "utf-8");
|
|
1970
|
+
const componentsDir = path5.join(srcDir, "components/ui");
|
|
1971
|
+
const exportPattern = /export \* from ['"]\.\/([^'"]+)['"]/g;
|
|
1972
|
+
const namedExports = /* @__PURE__ */ new Map();
|
|
1973
|
+
const namedExportPattern = /export\s+(?:const|function|class|type|interface|enum)\s+(\w+)/g;
|
|
1974
|
+
let match;
|
|
1975
|
+
while ((match = exportPattern.exec(content)) !== null) {
|
|
1976
|
+
const exportFile = match[1];
|
|
1977
|
+
const filePath = path5.join(componentsDir, exportFile + ".tsx");
|
|
1978
|
+
if (!fs5.existsSync(filePath)) continue;
|
|
1979
|
+
const fileContent = await fs5.readFile(filePath, "utf-8");
|
|
1980
|
+
let namedMatch;
|
|
1981
|
+
while ((namedMatch = namedExportPattern.exec(fileContent)) !== null) {
|
|
1982
|
+
const exportName = namedMatch[1];
|
|
1983
|
+
if (!namedExports.has(exportName)) {
|
|
1984
|
+
namedExports.set(exportName, { files: [exportFile], count: 1 });
|
|
1985
|
+
} else {
|
|
1986
|
+
const existing = namedExports.get(exportName);
|
|
1987
|
+
existing.files.push(exportFile);
|
|
1988
|
+
existing.count++;
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
const conflicts = Array.from(namedExports.entries()).filter(([_, info2]) => info2.count > 1);
|
|
1993
|
+
if (conflicts.length > 0 && !dryRun) {
|
|
1994
|
+
console.log(chalk8.yellow(`
|
|
1995
|
+
\u26A0 Found ${conflicts.length} conflicting export(s):`));
|
|
1996
|
+
conflicts.forEach(([name, info2]) => {
|
|
1997
|
+
console.log(chalk8.dim(` ${name}: exported from ${info2.files.join(", ")}`));
|
|
1998
|
+
});
|
|
1999
|
+
console.log(chalk8.dim("\nTo fix, manually update components/ui/index.ts to use explicit named exports."));
|
|
2000
|
+
console.log(chalk8.dim('Example: export { ComponentA } from "./file-a";'));
|
|
2001
|
+
result.skipped = conflicts.length;
|
|
2002
|
+
} else if (conflicts.length > 0 && dryRun) {
|
|
2003
|
+
console.log(chalk8.dim(` Would report ${conflicts.length} conflicting exports`));
|
|
2004
|
+
}
|
|
2005
|
+
return result;
|
|
2006
|
+
}
|
|
2007
|
+
async function fixBrokenImports(cwd, config, dryRun) {
|
|
2008
|
+
const result = { fixed: 0, skipped: 0, errors: [] };
|
|
2009
|
+
const srcDir = config.srcDir ? path5.join(cwd, "src") : cwd;
|
|
2010
|
+
const componentsDir = path5.join(srcDir, "components/ui");
|
|
2011
|
+
const files = await fg2(path5.join(componentsDir, "**/*.{ts,tsx}"), {
|
|
2012
|
+
ignore: ["**/node_modules/**"]
|
|
2013
|
+
});
|
|
2014
|
+
for (const file of files) {
|
|
2015
|
+
const content = await fs5.readFile(file, "utf-8");
|
|
2016
|
+
const fileDir = path5.dirname(file);
|
|
2017
|
+
let modified = false;
|
|
2018
|
+
let newContent = content;
|
|
2019
|
+
const relativeImportPattern = /from\s+['"](\.\.?\/[^'"]+)['"]/g;
|
|
2020
|
+
let match;
|
|
2021
|
+
while ((match = relativeImportPattern.exec(content)) !== null) {
|
|
2022
|
+
const importPath = match[1];
|
|
2023
|
+
const absolutePath = path5.resolve(fileDir, importPath);
|
|
2024
|
+
const extensions = [".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", ""];
|
|
2025
|
+
const exists = extensions.some((ext) => fs5.existsSync(absolutePath + ext));
|
|
2026
|
+
if (!exists) {
|
|
2027
|
+
const baseName = path5.basename(importPath);
|
|
2028
|
+
const kebabName = toKebabCase(baseName);
|
|
2029
|
+
const dirName = path5.dirname(importPath);
|
|
2030
|
+
const newImportPath = dirName === "." ? `./${kebabName}` : `${dirName}/${kebabName}`;
|
|
2031
|
+
const newAbsolutePath = path5.resolve(fileDir, newImportPath);
|
|
2032
|
+
const newExists = extensions.some((ext) => fs5.existsSync(newAbsolutePath + ext));
|
|
2033
|
+
if (newExists) {
|
|
2034
|
+
newContent = newContent.replace(
|
|
2035
|
+
new RegExp(`from\\s+['"]${importPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}['"]`),
|
|
2036
|
+
`from '${newImportPath}'`
|
|
2037
|
+
);
|
|
2038
|
+
modified = true;
|
|
2039
|
+
result.fixed++;
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
if (modified && !dryRun) {
|
|
2044
|
+
await fs5.writeFile(file, newContent);
|
|
2045
|
+
} else if (modified && dryRun) {
|
|
2046
|
+
console.log(chalk8.dim(` Would fix imports in: ${path5.relative(cwd, file)}`));
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
return result;
|
|
2050
|
+
}
|
|
2051
|
+
async function fixMissingCss(cwd, config, dryRun) {
|
|
2052
|
+
const result = { fixed: 0, skipped: 0, errors: [] };
|
|
2053
|
+
const srcDir = config.srcDir ? path5.join(cwd, "src") : cwd;
|
|
2054
|
+
const componentsDir = path5.join(srcDir, "components/ui");
|
|
2055
|
+
const cssRequirements = {
|
|
2056
|
+
"input-block-editor.tsx": {
|
|
2057
|
+
component: "input-block-editor",
|
|
2058
|
+
cssFile: "InputBlockEditor.css"
|
|
2059
|
+
},
|
|
2060
|
+
"rich-text-html.tsx": {
|
|
2061
|
+
component: "rich-text-html",
|
|
2062
|
+
cssFile: "RichTextHTML.css"
|
|
2063
|
+
},
|
|
2064
|
+
"rich-text-markdown.tsx": {
|
|
2065
|
+
component: "rich-text-markdown",
|
|
2066
|
+
cssFile: "RichTextMarkdown.css"
|
|
2067
|
+
}
|
|
2068
|
+
};
|
|
2069
|
+
for (const [componentFile, info2] of Object.entries(cssRequirements)) {
|
|
2070
|
+
const componentPath = path5.join(componentsDir, componentFile);
|
|
2071
|
+
const cssPath = path5.join(componentsDir, info2.cssFile);
|
|
2072
|
+
if (fs5.existsSync(componentPath) && !fs5.existsSync(cssPath)) {
|
|
2073
|
+
if (dryRun) {
|
|
2074
|
+
console.log(chalk8.dim(` Would copy missing CSS: ${info2.cssFile}`));
|
|
2075
|
+
result.fixed++;
|
|
2076
|
+
} else {
|
|
2077
|
+
console.log(chalk8.yellow(` Missing CSS: ${info2.cssFile}`));
|
|
2078
|
+
console.log(chalk8.dim(` Reinstall component: npx microbuild add ${info2.component} --overwrite`));
|
|
2079
|
+
result.skipped++;
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
return result;
|
|
2084
|
+
}
|
|
2085
|
+
async function fix(options) {
|
|
2086
|
+
const { cwd, dryRun = false, yes = false } = options;
|
|
2087
|
+
console.log(chalk8.bold("\n\u{1F527} Microbuild Fix\n"));
|
|
2088
|
+
if (dryRun) {
|
|
2089
|
+
console.log(chalk8.yellow("Dry run mode - no changes will be made\n"));
|
|
2090
|
+
}
|
|
2091
|
+
const config = await loadConfig(cwd);
|
|
2092
|
+
if (!config) {
|
|
2093
|
+
console.log(chalk8.red('\u2717 microbuild.json not found. Run "npx microbuild init" first.\n'));
|
|
2094
|
+
process.exit(1);
|
|
2095
|
+
}
|
|
2096
|
+
if (!yes && !dryRun) {
|
|
2097
|
+
const { confirm } = await prompts2({
|
|
2098
|
+
type: "confirm",
|
|
2099
|
+
name: "confirm",
|
|
2100
|
+
message: "This will modify files in your project. Continue?",
|
|
2101
|
+
initial: true
|
|
2102
|
+
});
|
|
2103
|
+
if (!confirm) {
|
|
2104
|
+
console.log(chalk8.yellow("\n\u2713 Cancelled\n"));
|
|
2105
|
+
return;
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
const spinner = ora3("Scanning for issues...").start();
|
|
2109
|
+
try {
|
|
2110
|
+
spinner.text = "Fixing untransformed imports...";
|
|
2111
|
+
const importResult = await fixUntransformedImports(cwd, config, dryRun);
|
|
2112
|
+
spinner.text = "Fixing broken relative imports...";
|
|
2113
|
+
const brokenResult = await fixBrokenImports(cwd, config, dryRun);
|
|
2114
|
+
spinner.text = "Fixing SSR-unsafe exports...";
|
|
2115
|
+
const ssrResult = await fixSsrUnsafeExports(cwd, config, dryRun);
|
|
2116
|
+
spinner.text = "Checking duplicate exports...";
|
|
2117
|
+
const duplicateResult = await fixDuplicateExports(cwd, config, dryRun);
|
|
2118
|
+
spinner.text = "Checking missing CSS files...";
|
|
2119
|
+
const cssResult = await fixMissingCss(cwd, config, dryRun);
|
|
2120
|
+
spinner.stop();
|
|
2121
|
+
const totalFixed = importResult.fixed + brokenResult.fixed + ssrResult.fixed + cssResult.fixed;
|
|
2122
|
+
const totalSkipped = importResult.skipped + brokenResult.skipped + ssrResult.skipped + duplicateResult.skipped + cssResult.skipped;
|
|
2123
|
+
console.log(chalk8.bold("\n\u{1F4CB} Fix Summary:\n"));
|
|
2124
|
+
if (importResult.fixed > 0) {
|
|
2125
|
+
console.log(chalk8.green(` \u2713 Fixed ${importResult.fixed} untransformed import(s)`));
|
|
2126
|
+
}
|
|
2127
|
+
if (brokenResult.fixed > 0) {
|
|
2128
|
+
console.log(chalk8.green(` \u2713 Fixed ${brokenResult.fixed} broken import(s)`));
|
|
2129
|
+
}
|
|
2130
|
+
if (ssrResult.fixed > 0) {
|
|
2131
|
+
console.log(chalk8.green(` \u2713 Fixed ${ssrResult.fixed} SSR-unsafe export(s)`));
|
|
2132
|
+
}
|
|
2133
|
+
if (cssResult.fixed > 0) {
|
|
2134
|
+
console.log(chalk8.green(` \u2713 Fixed ${cssResult.fixed} missing CSS file(s)`));
|
|
2135
|
+
}
|
|
2136
|
+
if (totalSkipped > 0) {
|
|
2137
|
+
console.log(chalk8.yellow(`
|
|
2138
|
+
\u26A0 Skipped ${totalSkipped} issue(s) requiring manual fix`));
|
|
2139
|
+
}
|
|
2140
|
+
if (totalFixed === 0 && totalSkipped === 0) {
|
|
2141
|
+
console.log(chalk8.green(" \u2713 No issues found!\n"));
|
|
2142
|
+
} else if (dryRun) {
|
|
2143
|
+
console.log(chalk8.dim("\n Run without --dry-run to apply fixes\n"));
|
|
2144
|
+
} else {
|
|
2145
|
+
console.log(chalk8.green("\n\u2728 Fixes applied!\n"));
|
|
2146
|
+
console.log(chalk8.dim('Run "npx microbuild validate" to verify.\n'));
|
|
2147
|
+
}
|
|
2148
|
+
} catch (error) {
|
|
2149
|
+
spinner.fail("Fix failed");
|
|
2150
|
+
console.error(chalk8.red(error));
|
|
2151
|
+
process.exit(1);
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
// src/commands/bootstrap.ts
|
|
2156
|
+
import fs6 from "fs-extra";
|
|
2157
|
+
import path6 from "path";
|
|
2158
|
+
import chalk9 from "chalk";
|
|
2159
|
+
async function bootstrap(options) {
|
|
2160
|
+
const { cwd, skipDeps = false, skipValidate = false } = options;
|
|
2161
|
+
const startTime = Date.now();
|
|
2162
|
+
console.log(chalk9.bold.blue("\n\u{1F680} Microbuild Bootstrap - Full Project Setup\n"));
|
|
2163
|
+
console.log(chalk9.dim(`Target: ${cwd}
|
|
2164
|
+
`));
|
|
2165
|
+
console.log(chalk9.bold("\u2501".repeat(60)));
|
|
2166
|
+
console.log(chalk9.bold("Step 1/3: Initializing project...\n"));
|
|
2167
|
+
try {
|
|
2168
|
+
await init({ yes: true, cwd });
|
|
2169
|
+
} catch (error) {
|
|
2170
|
+
console.error(chalk9.red("\n\u2717 Init failed:"), error);
|
|
2171
|
+
process.exit(1);
|
|
2172
|
+
}
|
|
2173
|
+
console.log(chalk9.bold("\n" + "\u2501".repeat(60)));
|
|
2174
|
+
console.log(chalk9.bold("Step 2/3: Adding all components...\n"));
|
|
2175
|
+
try {
|
|
2176
|
+
await add([], {
|
|
2177
|
+
all: true,
|
|
2178
|
+
withApi: true,
|
|
2179
|
+
cwd,
|
|
2180
|
+
nonInteractive: true
|
|
2181
|
+
});
|
|
2182
|
+
} catch (error) {
|
|
2183
|
+
console.error(chalk9.red("\n\u2717 Component installation failed:"), error);
|
|
2184
|
+
process.exit(1);
|
|
2185
|
+
}
|
|
2186
|
+
if (!skipDeps) {
|
|
2187
|
+
console.log(chalk9.bold("\n" + "\u2501".repeat(60)));
|
|
2188
|
+
console.log(chalk9.bold("Step 3/3: Installing npm dependencies...\n"));
|
|
2189
|
+
try {
|
|
2190
|
+
const { execSync: execSync2 } = await import("child_process");
|
|
2191
|
+
const hasPnpmLock = fs6.existsSync(path6.join(cwd, "pnpm-lock.yaml"));
|
|
2192
|
+
const hasYarnLock = fs6.existsSync(path6.join(cwd, "yarn.lock"));
|
|
2193
|
+
const hasBunLock = fs6.existsSync(path6.join(cwd, "bun.lockb"));
|
|
2194
|
+
let installCmd;
|
|
2195
|
+
if (hasPnpmLock || !hasYarnLock && !hasBunLock) {
|
|
2196
|
+
installCmd = "pnpm install";
|
|
2197
|
+
} else if (hasYarnLock) {
|
|
2198
|
+
installCmd = "yarn install";
|
|
2199
|
+
} else {
|
|
2200
|
+
installCmd = "bun install";
|
|
2201
|
+
}
|
|
2202
|
+
console.log(chalk9.dim(` Running: ${installCmd}
|
|
2203
|
+
`));
|
|
2204
|
+
execSync2(installCmd, { cwd, stdio: "inherit" });
|
|
2205
|
+
console.log(chalk9.green("\n\u2713 Dependencies installed!"));
|
|
2206
|
+
} catch (error) {
|
|
2207
|
+
console.log(chalk9.yellow('\n\u26A0 Dependency installation had issues. Run "pnpm install" manually.'));
|
|
2208
|
+
}
|
|
2209
|
+
} else {
|
|
2210
|
+
console.log(chalk9.dim("\nSkipped dependency installation (--skip-deps)\n"));
|
|
2211
|
+
}
|
|
2212
|
+
if (!skipValidate) {
|
|
2213
|
+
console.log(chalk9.bold("\n" + "\u2501".repeat(60)));
|
|
2214
|
+
console.log(chalk9.bold("Validating installation...\n"));
|
|
2215
|
+
try {
|
|
2216
|
+
const result = await validate({ cwd, json: false, noExit: true });
|
|
2217
|
+
if (result && !result.valid) {
|
|
2218
|
+
const tsErrors = result.errors.filter((e) => e.code === "TYPESCRIPT_ERROR");
|
|
2219
|
+
const structuralErrors = result.errors.filter((e) => e.code !== "TYPESCRIPT_ERROR");
|
|
2220
|
+
if (tsErrors.length > 0) {
|
|
2221
|
+
console.log(chalk9.yellow(`
|
|
2222
|
+
\u26A0 ${tsErrors.length} TypeScript type error(s) detected in component files.`));
|
|
2223
|
+
console.log(chalk9.yellow(' These may cause build failures. Run "pnpm cli fix --cwd ." to attempt auto-fix,'));
|
|
2224
|
+
console.log(chalk9.yellow(" or ensure all component dependencies are installed.\n"));
|
|
2225
|
+
}
|
|
2226
|
+
if (structuralErrors.length > 0) {
|
|
2227
|
+
console.log(chalk9.red(`
|
|
2228
|
+
\u2717 ${structuralErrors.length} structural error(s) found that need fixing.`));
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
} catch {
|
|
2232
|
+
console.log(chalk9.yellow("\n\u26A0 Validation encountered issues (see above). Bootstrap still completed.\n"));
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
|
|
2236
|
+
console.log(chalk9.bold("\n" + "\u2501".repeat(60)));
|
|
2237
|
+
console.log(chalk9.bold.green(`
|
|
2238
|
+
\u2728 Bootstrap complete! (${elapsed}s)
|
|
2239
|
+
`));
|
|
2240
|
+
console.log(chalk9.dim("Your project is ready with:"));
|
|
2241
|
+
console.log(chalk9.dim(" \u2022 microbuild.json configuration"));
|
|
2242
|
+
console.log(chalk9.dim(" \u2022 40+ UI components in components/ui/"));
|
|
2243
|
+
console.log(chalk9.dim(" \u2022 Types, services, hooks in lib/microbuild/"));
|
|
2244
|
+
console.log(chalk9.dim(" \u2022 API proxy routes in app/api/"));
|
|
2245
|
+
console.log(chalk9.dim(" \u2022 Supabase auth utilities"));
|
|
2246
|
+
console.log(chalk9.dim(" \u2022 Next.js skeleton (layout, page, config)"));
|
|
2247
|
+
console.log(chalk9.bold("\nNext steps:"));
|
|
2248
|
+
console.log(chalk9.cyan(" 1. ") + chalk9.dim("Configure .env.local with your DaaS URL"));
|
|
2249
|
+
console.log(chalk9.cyan(" 2. ") + chalk9.dim("pnpm dev"));
|
|
2250
|
+
console.log(chalk9.cyan(" 3. ") + chalk9.dim("Create pages that import from @/components/ui/\n"));
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
// src/index.ts
|
|
2254
|
+
var program = new Command();
|
|
2255
|
+
program.name("microbuild").description("Copy & Own CLI - Add Microbuild components to your project").version("1.0.0");
|
|
2256
|
+
program.command("init").description("Initialize Microbuild in your project (creates microbuild.json)").option("-y, --yes", "Skip prompts and use defaults").option("-c, --cwd <path>", "Project directory", process.cwd()).action(init);
|
|
2257
|
+
program.command("add").description("Copy components to your project (with transformed imports)").argument("[components...]", "Component names to add").option("-a, --all", "Add all components").option("--with-api", "Also add API routes and Supabase auth templates").option("--category <name>", "Add all components from a category").option("-o, --overwrite", "Overwrite existing components").option("-n, --dry-run", "Preview changes without modifying files").option("--cwd <path>", "Project directory", process.cwd()).action(add);
|
|
2258
|
+
program.command("list").description("List all available components").option("--category <name>", "Filter by category").option("--json", "Output as JSON").option("--cwd <path>", "Project directory", process.cwd()).action(list);
|
|
2259
|
+
program.command("diff").description("Preview changes before adding a component").argument("<component>", "Component name").option("--cwd <path>", "Project directory", process.cwd()).action(diff);
|
|
2260
|
+
program.command("status").description("Show installed Microbuild components and their origins").option("--json", "Output as JSON").option("--cwd <path>", "Project directory", process.cwd()).action(status);
|
|
2261
|
+
program.command("info").description("Show detailed information about a component (sources, dependencies, interface)").argument("<component>", "Component name").option("--json", "Output as JSON").action(info);
|
|
2262
|
+
program.command("tree").description("Display dependency tree for a component").argument("<component>", "Component name").option("--json", "Output as JSON").option("-d, --depth <number>", "Max depth to display", "2").action((component, options) => tree(component, { ...options, depth: parseInt(options.depth) }));
|
|
2263
|
+
program.command("validate").description("Validate Microbuild installation (check imports, missing files, SSR issues)").option("--json", "Output as JSON").option("--cwd <path>", "Project directory", process.cwd()).action(async (options) => {
|
|
2264
|
+
await validate(options);
|
|
2265
|
+
});
|
|
2266
|
+
program.command("bootstrap").description("Full project setup: init + add --all + install deps + validate (single command for AI agents)").option("--cwd <path>", "Project directory", process.cwd()).option("--skip-deps", "Skip npm dependency installation").option("--skip-validate", "Skip post-install validation").action(async (options) => {
|
|
2267
|
+
await bootstrap({
|
|
2268
|
+
cwd: options.cwd,
|
|
2269
|
+
skipDeps: options.skipDeps,
|
|
2270
|
+
skipValidate: options.skipValidate
|
|
2271
|
+
});
|
|
2272
|
+
});
|
|
2273
|
+
program.command("fix").description("Automatically fix common issues (untransformed imports, broken paths, SSR exports)").option("-n, --dry-run", "Preview fixes without modifying files").option("-y, --yes", "Skip confirmation prompts").option("--cwd <path>", "Project directory", process.cwd()).action(fix);
|
|
2274
|
+
program.command("outdated").description("Check for component updates (compares installed versions to registry)").option("--json", "Output as JSON").option("--cwd <path>", "Project directory", process.cwd()).action(async (options) => {
|
|
2275
|
+
const { outdated } = await import("./outdated-TV5ERBNC.js");
|
|
2276
|
+
await outdated(options);
|
|
2277
|
+
});
|
|
2278
|
+
program.parse();
|
|
2279
|
+
//# sourceMappingURL=index.js.map
|