@primeuicom/mcp 0.1.25 → 0.1.26
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 +32 -10
- package/dist/service.js +1477 -539
- package/dist/service.js.map +1 -1
- package/package.json +1 -1
package/dist/service.js
CHANGED
|
@@ -3,14 +3,212 @@
|
|
|
3
3
|
// src/service.ts
|
|
4
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
5
|
|
|
6
|
-
// src/
|
|
6
|
+
// src/cli.ts
|
|
7
|
+
import path2 from "path";
|
|
8
|
+
|
|
9
|
+
// src/package-metadata.ts
|
|
10
|
+
import { readFileSync } from "fs";
|
|
11
|
+
import path from "path";
|
|
12
|
+
import { fileURLToPath } from "url";
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
var packageMetadataSchema = z.object({
|
|
15
|
+
name: z.string().trim().min(1),
|
|
16
|
+
version: z.string().trim().min(1),
|
|
17
|
+
description: z.string().trim().min(1)
|
|
18
|
+
});
|
|
19
|
+
function loadPackageMetadata() {
|
|
20
|
+
const currentFilePath = fileURLToPath(import.meta.url);
|
|
21
|
+
const currentDir = path.dirname(currentFilePath);
|
|
22
|
+
const packageJsonPath = path.resolve(currentDir, "../package.json");
|
|
23
|
+
let rawPackageJson = "";
|
|
24
|
+
try {
|
|
25
|
+
rawPackageJson = readFileSync(packageJsonPath, "utf-8");
|
|
26
|
+
} catch (error) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
`[primeui-mcp] failed to read package.json at ${packageJsonPath}: ${error instanceof Error ? error.message : String(error)}`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
let parsedPackageJson;
|
|
32
|
+
try {
|
|
33
|
+
parsedPackageJson = JSON.parse(rawPackageJson);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`[primeui-mcp] invalid package.json at ${packageJsonPath}: ${error instanceof Error ? error.message : String(error)}`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
const parsed = packageMetadataSchema.safeParse(parsedPackageJson);
|
|
40
|
+
if (!parsed.success) {
|
|
41
|
+
const details = parsed.error.issues.map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`).join("; ");
|
|
42
|
+
throw new Error(
|
|
43
|
+
`[primeui-mcp] invalid package metadata in ${packageJsonPath}: ${details}`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
return parsed.data;
|
|
47
|
+
}
|
|
48
|
+
var packageMetadata = loadPackageMetadata();
|
|
49
|
+
|
|
50
|
+
// src/cli.ts
|
|
51
|
+
function buildCliCommand(metadata) {
|
|
52
|
+
return `npx ${metadata.name}@latest`;
|
|
53
|
+
}
|
|
54
|
+
function formatUsageText(metadata = packageMetadata) {
|
|
55
|
+
const command = buildCliCommand(metadata);
|
|
56
|
+
return [
|
|
57
|
+
"Usage:",
|
|
58
|
+
` ${command}`,
|
|
59
|
+
` ${command} --help`,
|
|
60
|
+
` ${command} --version`,
|
|
61
|
+
` ${command} --health`,
|
|
62
|
+
` ${command} --health /absolute/project/path`
|
|
63
|
+
].join("\n");
|
|
64
|
+
}
|
|
65
|
+
function formatHelpText(metadata = packageMetadata) {
|
|
66
|
+
return [
|
|
67
|
+
"PrimeUI MCP",
|
|
68
|
+
"MCP stdio server for importing PrimeUI pages into your local project.",
|
|
69
|
+
"",
|
|
70
|
+
formatUsageText(metadata),
|
|
71
|
+
"",
|
|
72
|
+
"Options:",
|
|
73
|
+
" -h, --help Show this help message and exit.",
|
|
74
|
+
" -v, --version Show package version and exit.",
|
|
75
|
+
" --health Run runtime diagnostics without starting the MCP server.",
|
|
76
|
+
"",
|
|
77
|
+
"Behavior:",
|
|
78
|
+
" Running without arguments starts the PrimeUI MCP stdio server.",
|
|
79
|
+
" This mode is intended to be launched by an MCP-compatible client.",
|
|
80
|
+
" `--health` prints runtime diagnostics and exits.",
|
|
81
|
+
"",
|
|
82
|
+
"Configuration:",
|
|
83
|
+
" PRIMEUI_API_KEY PrimeUI API key. Falls back to .primeui/project.json.",
|
|
84
|
+
" PRIMEUI_API_BASE_URL Override PrimeUI API base URL for local development.",
|
|
85
|
+
" PRIMEUI_PROJECT_ROOT Override project root used to resolve .primeui/project.json.",
|
|
86
|
+
"",
|
|
87
|
+
"Project config:",
|
|
88
|
+
" .primeui/project.json must include projectId, apiKey, and targetProjectPath."
|
|
89
|
+
].join("\n");
|
|
90
|
+
}
|
|
91
|
+
function formatInvalidHealthPathError(projectRoot, metadata = packageMetadata) {
|
|
92
|
+
return [
|
|
93
|
+
`Invalid --health project path: ${projectRoot}`,
|
|
94
|
+
"",
|
|
95
|
+
"The optional `--health` project path must be an absolute path.",
|
|
96
|
+
"",
|
|
97
|
+
formatUsageText(metadata)
|
|
98
|
+
].join("\n");
|
|
99
|
+
}
|
|
100
|
+
function formatUnknownArgError(arg, metadata = packageMetadata) {
|
|
101
|
+
return [`Unknown argument: ${arg}`, "", formatUsageText(metadata)].join("\n");
|
|
102
|
+
}
|
|
103
|
+
function formatInvalidArgsError(args, metadata = packageMetadata) {
|
|
104
|
+
return [
|
|
105
|
+
`Invalid arguments: ${args.join(" ")}`,
|
|
106
|
+
"",
|
|
107
|
+
formatUsageText(metadata)
|
|
108
|
+
].join("\n");
|
|
109
|
+
}
|
|
110
|
+
function parseCliArgs(argv) {
|
|
111
|
+
if (argv.length === 0) {
|
|
112
|
+
return {
|
|
113
|
+
kind: "start-server"
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const [arg, maybeProjectRoot] = argv;
|
|
117
|
+
if (arg === "--health") {
|
|
118
|
+
if (argv.length === 1) {
|
|
119
|
+
return {
|
|
120
|
+
kind: "run-health-check"
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
if (argv.length !== 2) {
|
|
124
|
+
return {
|
|
125
|
+
kind: "error",
|
|
126
|
+
reason: "invalid-arguments",
|
|
127
|
+
args: argv
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (maybeProjectRoot === "--help" || maybeProjectRoot === "-h" || maybeProjectRoot === "--version" || maybeProjectRoot === "-v") {
|
|
131
|
+
return {
|
|
132
|
+
kind: "error",
|
|
133
|
+
reason: "invalid-arguments",
|
|
134
|
+
args: argv
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
if (!path2.isAbsolute(maybeProjectRoot ?? "")) {
|
|
138
|
+
return {
|
|
139
|
+
kind: "error",
|
|
140
|
+
reason: "invalid-health-path",
|
|
141
|
+
args: argv
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
kind: "run-health-check",
|
|
146
|
+
projectRoot: maybeProjectRoot
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (argv.length !== 1) {
|
|
150
|
+
return {
|
|
151
|
+
kind: "error",
|
|
152
|
+
reason: "invalid-arguments",
|
|
153
|
+
args: argv
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
if (arg === "--help" || arg === "-h") {
|
|
157
|
+
return {
|
|
158
|
+
kind: "print-help"
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
if (arg === "--version" || arg === "-v") {
|
|
162
|
+
return {
|
|
163
|
+
kind: "print-version"
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
kind: "error",
|
|
168
|
+
reason: "unknown-argument",
|
|
169
|
+
args: argv
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function writeLine(stream, text) {
|
|
173
|
+
stream.write(`${text}
|
|
174
|
+
`);
|
|
175
|
+
}
|
|
176
|
+
async function runCli(argv, dependencies) {
|
|
177
|
+
const metadata = dependencies.metadata ?? packageMetadata;
|
|
178
|
+
const parsed = parseCliArgs(argv);
|
|
179
|
+
switch (parsed.kind) {
|
|
180
|
+
case "start-server":
|
|
181
|
+
await dependencies.startServer();
|
|
182
|
+
return 0;
|
|
183
|
+
case "run-health-check":
|
|
184
|
+
return dependencies.runHealthCheck({
|
|
185
|
+
projectRoot: parsed.projectRoot
|
|
186
|
+
});
|
|
187
|
+
case "print-help":
|
|
188
|
+
writeLine(dependencies.stdout, formatHelpText(metadata));
|
|
189
|
+
return 0;
|
|
190
|
+
case "print-version":
|
|
191
|
+
writeLine(dependencies.stdout, metadata.version);
|
|
192
|
+
return 0;
|
|
193
|
+
case "error":
|
|
194
|
+
writeLine(
|
|
195
|
+
dependencies.stderr,
|
|
196
|
+
parsed.reason === "unknown-argument" ? formatUnknownArgError(parsed.args[0] ?? "", metadata) : parsed.reason === "invalid-health-path" ? formatInvalidHealthPathError(parsed.args[1] ?? "", metadata) : formatInvalidArgsError(parsed.args, metadata)
|
|
197
|
+
);
|
|
198
|
+
return 1;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// src/health/run-health-check.ts
|
|
203
|
+
import { stat as stat3 } from "fs/promises";
|
|
7
204
|
import path7 from "path";
|
|
8
205
|
|
|
9
206
|
// src/lib/project-link-config.ts
|
|
10
207
|
import { readFile, stat } from "fs/promises";
|
|
11
|
-
import
|
|
12
|
-
import { z } from "zod";
|
|
13
|
-
var
|
|
208
|
+
import path3 from "path";
|
|
209
|
+
import { z as z2 } from "zod";
|
|
210
|
+
var PRIMEUI_PROJECT_DIR_NAME = ".primeui";
|
|
211
|
+
var PRIMEUI_PROJECT_CONFIG_RELATIVE_PATH = `${PRIMEUI_PROJECT_DIR_NAME}/project.json`;
|
|
14
212
|
var PrimeUiProjectConfigError = class extends Error {
|
|
15
213
|
code;
|
|
16
214
|
hint;
|
|
@@ -26,10 +224,10 @@ var PrimeUiProjectConfigError = class extends Error {
|
|
|
26
224
|
function buildProjectRootHint() {
|
|
27
225
|
return "Likely MCP project-path bug: when Codex VSCode runs MCP from global config, server can start in HOME without project context. Fix: set tool input parameter `projectRoot` to the ABSOLUTE path of the current project root (for example: /path/to/current/project).";
|
|
28
226
|
}
|
|
29
|
-
var primeUiProjectConfigSchema =
|
|
30
|
-
projectId:
|
|
31
|
-
apiKey:
|
|
32
|
-
targetProjectPath:
|
|
227
|
+
var primeUiProjectConfigSchema = z2.object({
|
|
228
|
+
projectId: z2.string().trim().min(1),
|
|
229
|
+
apiKey: z2.string().trim().min(1),
|
|
230
|
+
targetProjectPath: z2.string().trim().regex(/^\.\/.*$/)
|
|
33
231
|
}).passthrough();
|
|
34
232
|
async function fileExists(filePath) {
|
|
35
233
|
try {
|
|
@@ -40,12 +238,12 @@ async function fileExists(filePath) {
|
|
|
40
238
|
}
|
|
41
239
|
}
|
|
42
240
|
function ancestors(startPath) {
|
|
43
|
-
const resolvedStartPath =
|
|
241
|
+
const resolvedStartPath = path3.resolve(startPath);
|
|
44
242
|
const directories = [];
|
|
45
243
|
let currentPath = resolvedStartPath;
|
|
46
244
|
while (true) {
|
|
47
245
|
directories.push(currentPath);
|
|
48
|
-
const parentPath =
|
|
246
|
+
const parentPath = path3.dirname(currentPath);
|
|
49
247
|
if (parentPath === currentPath) {
|
|
50
248
|
break;
|
|
51
249
|
}
|
|
@@ -55,7 +253,7 @@ function ancestors(startPath) {
|
|
|
55
253
|
}
|
|
56
254
|
async function findPrimeUiProjectConfigPath(startPath) {
|
|
57
255
|
for (const currentPath of ancestors(startPath)) {
|
|
58
|
-
const candidatePath =
|
|
256
|
+
const candidatePath = path3.join(
|
|
59
257
|
currentPath,
|
|
60
258
|
PRIMEUI_PROJECT_CONFIG_RELATIVE_PATH
|
|
61
259
|
);
|
|
@@ -106,6 +304,37 @@ async function readPrimeUiProjectConfig(projectConfigPath) {
|
|
|
106
304
|
}
|
|
107
305
|
return parsedConfig.data;
|
|
108
306
|
}
|
|
307
|
+
function withConfigSourceDetails(error, context) {
|
|
308
|
+
if (!(error instanceof PrimeUiProjectConfigError)) {
|
|
309
|
+
throw error;
|
|
310
|
+
}
|
|
311
|
+
throw new PrimeUiProjectConfigError({
|
|
312
|
+
code: error.code,
|
|
313
|
+
message: error.message,
|
|
314
|
+
hint: error.hint,
|
|
315
|
+
details: {
|
|
316
|
+
...error.details,
|
|
317
|
+
source: context.source,
|
|
318
|
+
projectRoot: context.projectRoot,
|
|
319
|
+
expectedConfigPath: context.projectConfigPath
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
async function loadResolvedPrimeUiProjectConfig(context) {
|
|
324
|
+
try {
|
|
325
|
+
const projectConfig = await readPrimeUiProjectConfig(
|
|
326
|
+
context.projectConfigPath
|
|
327
|
+
);
|
|
328
|
+
return {
|
|
329
|
+
projectRoot: context.projectRoot,
|
|
330
|
+
projectConfigPath: context.projectConfigPath,
|
|
331
|
+
projectConfig,
|
|
332
|
+
source: context.source
|
|
333
|
+
};
|
|
334
|
+
} catch (error) {
|
|
335
|
+
withConfigSourceDetails(error, context);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
109
338
|
function toUniqueResolvedDirs(directories) {
|
|
110
339
|
const resolved = /* @__PURE__ */ new Set();
|
|
111
340
|
for (const directory of directories) {
|
|
@@ -113,7 +342,7 @@ function toUniqueResolvedDirs(directories) {
|
|
|
113
342
|
if (!trimmed) {
|
|
114
343
|
continue;
|
|
115
344
|
}
|
|
116
|
-
resolved.add(
|
|
345
|
+
resolved.add(path3.resolve(trimmed));
|
|
117
346
|
}
|
|
118
347
|
return [...resolved];
|
|
119
348
|
}
|
|
@@ -123,8 +352,8 @@ function buildPrimeUiProjectSearchRoots(options) {
|
|
|
123
352
|
async function resolvePrimeUiProjectConfig(options) {
|
|
124
353
|
const toolProjectRoot = options.projectRootFromTool?.trim();
|
|
125
354
|
if (toolProjectRoot) {
|
|
126
|
-
const resolvedToolRoot =
|
|
127
|
-
const toolProjectConfigPath =
|
|
355
|
+
const resolvedToolRoot = path3.resolve(toolProjectRoot);
|
|
356
|
+
const toolProjectConfigPath = path3.join(
|
|
128
357
|
resolvedToolRoot,
|
|
129
358
|
PRIMEUI_PROJECT_CONFIG_RELATIVE_PATH
|
|
130
359
|
);
|
|
@@ -141,35 +370,31 @@ async function resolvePrimeUiProjectConfig(options) {
|
|
|
141
370
|
hint: buildProjectRootHint()
|
|
142
371
|
});
|
|
143
372
|
}
|
|
144
|
-
|
|
145
|
-
|
|
373
|
+
return loadResolvedPrimeUiProjectConfig({
|
|
374
|
+
source: "tool",
|
|
146
375
|
projectRoot: resolvedToolRoot,
|
|
147
|
-
projectConfigPath: toolProjectConfigPath
|
|
148
|
-
|
|
149
|
-
};
|
|
376
|
+
projectConfigPath: toolProjectConfigPath
|
|
377
|
+
});
|
|
150
378
|
}
|
|
151
379
|
const stickyProjectRoot = options.projectRootFromSticky?.trim();
|
|
152
380
|
if (stickyProjectRoot) {
|
|
153
|
-
const resolvedStickyRoot =
|
|
154
|
-
const stickyProjectConfigPath =
|
|
381
|
+
const resolvedStickyRoot = path3.resolve(stickyProjectRoot);
|
|
382
|
+
const stickyProjectConfigPath = path3.join(
|
|
155
383
|
resolvedStickyRoot,
|
|
156
384
|
PRIMEUI_PROJECT_CONFIG_RELATIVE_PATH
|
|
157
385
|
);
|
|
158
386
|
if (await fileExists(stickyProjectConfigPath)) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
);
|
|
162
|
-
return {
|
|
387
|
+
return loadResolvedPrimeUiProjectConfig({
|
|
388
|
+
source: "sticky",
|
|
163
389
|
projectRoot: resolvedStickyRoot,
|
|
164
|
-
projectConfigPath: stickyProjectConfigPath
|
|
165
|
-
|
|
166
|
-
};
|
|
390
|
+
projectConfigPath: stickyProjectConfigPath
|
|
391
|
+
});
|
|
167
392
|
}
|
|
168
393
|
}
|
|
169
394
|
const envProjectRoot = options.projectRootFromEnv?.trim();
|
|
170
395
|
if (envProjectRoot) {
|
|
171
|
-
const resolvedEnvRoot =
|
|
172
|
-
const envProjectConfigPath =
|
|
396
|
+
const resolvedEnvRoot = path3.resolve(envProjectRoot);
|
|
397
|
+
const envProjectConfigPath = path3.join(
|
|
173
398
|
resolvedEnvRoot,
|
|
174
399
|
PRIMEUI_PROJECT_CONFIG_RELATIVE_PATH
|
|
175
400
|
);
|
|
@@ -187,12 +412,11 @@ async function resolvePrimeUiProjectConfig(options) {
|
|
|
187
412
|
hint: buildProjectRootHint()
|
|
188
413
|
});
|
|
189
414
|
}
|
|
190
|
-
|
|
191
|
-
|
|
415
|
+
return loadResolvedPrimeUiProjectConfig({
|
|
416
|
+
source: "env",
|
|
192
417
|
projectRoot: resolvedEnvRoot,
|
|
193
|
-
projectConfigPath: envProjectConfigPath
|
|
194
|
-
|
|
195
|
-
};
|
|
418
|
+
projectConfigPath: envProjectConfigPath
|
|
419
|
+
});
|
|
196
420
|
}
|
|
197
421
|
const searchRoots = buildPrimeUiProjectSearchRoots({
|
|
198
422
|
cwd: options.cwd,
|
|
@@ -220,37 +444,52 @@ async function resolvePrimeUiProjectConfig(options) {
|
|
|
220
444
|
hint: buildProjectRootHint()
|
|
221
445
|
});
|
|
222
446
|
}
|
|
223
|
-
const projectRoot =
|
|
224
|
-
|
|
225
|
-
|
|
447
|
+
const projectRoot = path3.dirname(path3.dirname(projectConfigPath));
|
|
448
|
+
return loadResolvedPrimeUiProjectConfig({
|
|
449
|
+
source: "search",
|
|
226
450
|
projectRoot,
|
|
227
|
-
projectConfigPath
|
|
228
|
-
|
|
451
|
+
projectConfigPath
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
function resolvePrimeUiApiKeyDetails(options) {
|
|
455
|
+
const envApiKey = options.apiKeyFromEnv?.trim();
|
|
456
|
+
const configApiKey = options.projectConfig?.apiKey.trim();
|
|
457
|
+
const envProvided = Boolean(envApiKey);
|
|
458
|
+
const configProvided = Boolean(configApiKey);
|
|
459
|
+
return {
|
|
460
|
+
apiKey: envApiKey ?? configApiKey,
|
|
461
|
+
source: envProvided ? "env" : configProvided ? "config" : "missing",
|
|
462
|
+
envProvided,
|
|
463
|
+
configProvided,
|
|
464
|
+
valuesMatch: envProvided && configProvided ? envApiKey === configApiKey : void 0
|
|
229
465
|
};
|
|
230
466
|
}
|
|
467
|
+
function resolvePrimeUiTargetProjectRoot(projectRoot, projectConfig) {
|
|
468
|
+
return path3.resolve(projectRoot, projectConfig.targetProjectPath);
|
|
469
|
+
}
|
|
231
470
|
async function resolvePrimeUiApiKey(options) {
|
|
232
|
-
const
|
|
233
|
-
if (
|
|
234
|
-
return
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
hint: "Set PRIMEUI_API_KEY or ensure apiKey is present in .primeui/project.json."
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
return projectConfigApiKey;
|
|
471
|
+
const resolved = resolvePrimeUiApiKeyDetails(options);
|
|
472
|
+
if (resolved.apiKey) {
|
|
473
|
+
return resolved.apiKey;
|
|
474
|
+
}
|
|
475
|
+
throw new PrimeUiProjectConfigError({
|
|
476
|
+
code: "PROJECT_API_KEY_MISSING",
|
|
477
|
+
message: "[primeui-mcp] PRIMEUI_API_KEY is missing in env and .primeui/project.json",
|
|
478
|
+
hint: "Set PRIMEUI_API_KEY or ensure apiKey is present in .primeui/project.json."
|
|
479
|
+
});
|
|
245
480
|
}
|
|
246
481
|
|
|
247
|
-
// src/
|
|
482
|
+
// src/sources/api-provider.ts
|
|
483
|
+
import { createWriteStream } from "fs";
|
|
484
|
+
import { unlink } from "fs/promises";
|
|
248
485
|
import path5 from "path";
|
|
249
|
-
import {
|
|
486
|
+
import { Readable, Transform } from "stream";
|
|
487
|
+
import { pipeline } from "stream/promises";
|
|
488
|
+
import { z as z3 } from "zod";
|
|
250
489
|
|
|
251
490
|
// src/lib/fs.ts
|
|
252
491
|
import { mkdir, rm, writeFile } from "fs/promises";
|
|
253
|
-
import
|
|
492
|
+
import path4 from "path";
|
|
254
493
|
import extractZipArchive from "extract-zip";
|
|
255
494
|
async function ensureDir(dirPath) {
|
|
256
495
|
await mkdir(dirPath, { recursive: true });
|
|
@@ -260,7 +499,7 @@ async function resetDir(dirPath) {
|
|
|
260
499
|
await mkdir(dirPath, { recursive: true });
|
|
261
500
|
}
|
|
262
501
|
async function writeUtf8(filePath, content) {
|
|
263
|
-
await ensureDir(
|
|
502
|
+
await ensureDir(path4.dirname(filePath));
|
|
264
503
|
await writeFile(filePath, content, "utf-8");
|
|
265
504
|
}
|
|
266
505
|
async function extractZip(zipPath, targetDir) {
|
|
@@ -273,14 +512,1010 @@ async function extractZip(zipPath, targetDir) {
|
|
|
273
512
|
}
|
|
274
513
|
}
|
|
275
514
|
|
|
515
|
+
// src/sources/api-provider.ts
|
|
516
|
+
var DEFAULT_API_BASE_URL = "https://app.primeui.com/";
|
|
517
|
+
var ZIP_CONTENT_TYPES = [
|
|
518
|
+
"application/zip",
|
|
519
|
+
"application/octet-stream",
|
|
520
|
+
"application/x-zip-compressed"
|
|
521
|
+
];
|
|
522
|
+
var exportStatusSchema = z3.enum(["in_progress", "completed", "failed"]);
|
|
523
|
+
var projectPageObjectSchema = z3.object({
|
|
524
|
+
id: z3.string(),
|
|
525
|
+
title: z3.string(),
|
|
526
|
+
slug: z3.string(),
|
|
527
|
+
pageType: z3.string(),
|
|
528
|
+
isReadyToExport: z3.boolean(),
|
|
529
|
+
pagePath: z3.string(),
|
|
530
|
+
componentsPath: z3.string()
|
|
531
|
+
});
|
|
532
|
+
var projectPageSchema = projectPageObjectSchema;
|
|
533
|
+
var exportSummarySchema = z3.object({
|
|
534
|
+
total: z3.number(),
|
|
535
|
+
successful: z3.number(),
|
|
536
|
+
failed: z3.number()
|
|
537
|
+
});
|
|
538
|
+
var exportedComponentSchema = z3.object({
|
|
539
|
+
componentKey: z3.string(),
|
|
540
|
+
enabled: z3.boolean(),
|
|
541
|
+
files: z3.array(z3.string()),
|
|
542
|
+
message: z3.string()
|
|
543
|
+
});
|
|
544
|
+
var exportPageManifestSchema = z3.object({
|
|
545
|
+
success: z3.boolean(),
|
|
546
|
+
message: z3.string(),
|
|
547
|
+
files: z3.array(z3.string())
|
|
548
|
+
});
|
|
549
|
+
var exportPageSchema = z3.object({
|
|
550
|
+
id: z3.string(),
|
|
551
|
+
title: z3.string().optional(),
|
|
552
|
+
slug: z3.string(),
|
|
553
|
+
pageType: z3.string(),
|
|
554
|
+
isReadyToExport: z3.literal(true),
|
|
555
|
+
pagePath: z3.string(),
|
|
556
|
+
componentsPath: z3.string(),
|
|
557
|
+
manifest: exportPageManifestSchema
|
|
558
|
+
});
|
|
559
|
+
var projectInfoSchema = z3.object({
|
|
560
|
+
projectId: z3.string(),
|
|
561
|
+
projectName: z3.string(),
|
|
562
|
+
metadata: z3.record(z3.unknown()),
|
|
563
|
+
pages: z3.array(projectPageSchema)
|
|
564
|
+
});
|
|
565
|
+
var projectPageComponentSchema = z3.object({
|
|
566
|
+
blockId: z3.string(),
|
|
567
|
+
componentId: z3.string(),
|
|
568
|
+
componentGroup: z3.string(),
|
|
569
|
+
slot: z3.string().nullable(),
|
|
570
|
+
props: z3.record(z3.unknown()).nullable()
|
|
571
|
+
});
|
|
572
|
+
var projectPageDetailsSchema = z3.object({
|
|
573
|
+
page: projectPageObjectSchema.extend({
|
|
574
|
+
pageInstruction: z3.string().nullable()
|
|
575
|
+
}),
|
|
576
|
+
variant: z3.object({
|
|
577
|
+
id: z3.string(),
|
|
578
|
+
name: z3.string()
|
|
579
|
+
}).nullable(),
|
|
580
|
+
components: z3.array(projectPageComponentSchema).nullable()
|
|
581
|
+
});
|
|
582
|
+
var exportsResponseSchema = z3.object({
|
|
583
|
+
exports: z3.array(
|
|
584
|
+
z3.object({
|
|
585
|
+
id: z3.string(),
|
|
586
|
+
status: exportStatusSchema,
|
|
587
|
+
createdAt: z3.string().datetime({ offset: true })
|
|
588
|
+
})
|
|
589
|
+
)
|
|
590
|
+
});
|
|
591
|
+
var createExportResponseSchema = z3.object({
|
|
592
|
+
export: z3.object({
|
|
593
|
+
id: z3.string(),
|
|
594
|
+
status: exportStatusSchema,
|
|
595
|
+
createdAt: z3.string().datetime({ offset: true }),
|
|
596
|
+
expiresAt: z3.string().datetime({ offset: true }).nullable(),
|
|
597
|
+
summary: exportSummarySchema,
|
|
598
|
+
components: z3.array(exportedComponentSchema)
|
|
599
|
+
}),
|
|
600
|
+
pages: z3.array(exportPageSchema)
|
|
601
|
+
});
|
|
602
|
+
var PrimeUiApiContractError = class extends Error {
|
|
603
|
+
constructor(endpoint, details) {
|
|
604
|
+
super(`PrimeUI API contract mismatch for "${endpoint}": ${details}`);
|
|
605
|
+
this.name = "PrimeUiApiContractError";
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
var normalizePrimeUiApiRoot = (rawBaseUrl) => {
|
|
609
|
+
const normalizedBase = rawBaseUrl?.trim() || DEFAULT_API_BASE_URL;
|
|
610
|
+
const parsed = new URL(normalizedBase);
|
|
611
|
+
const normalizedPath = parsed.pathname.replace(/\/+$/, "");
|
|
612
|
+
if (normalizedPath.endsWith("/api/v1")) {
|
|
613
|
+
parsed.pathname = `${normalizedPath}/`;
|
|
614
|
+
return parsed.toString();
|
|
615
|
+
}
|
|
616
|
+
parsed.pathname = `${normalizedPath}/api/v1/`.replace(
|
|
617
|
+
"//api/v1/",
|
|
618
|
+
"/api/v1/"
|
|
619
|
+
);
|
|
620
|
+
return parsed.toString();
|
|
621
|
+
};
|
|
622
|
+
var buildPrimeUiApiUrl = (apiRoot, endpoint) => new URL(endpoint, apiRoot).toString();
|
|
623
|
+
var isJsonContentType = (contentType) => contentType.includes("application/json") || contentType.includes("+json");
|
|
624
|
+
var isZipContentType = (contentType) => ZIP_CONTENT_TYPES.some((allowedType) => contentType.includes(allowedType));
|
|
625
|
+
var looksLikeZipArchive = (buffer) => {
|
|
626
|
+
if (buffer.length < 4) {
|
|
627
|
+
return false;
|
|
628
|
+
}
|
|
629
|
+
if (buffer[0] !== 80 || buffer[1] !== 75) {
|
|
630
|
+
return false;
|
|
631
|
+
}
|
|
632
|
+
const signature = `${buffer[2]}:${buffer[3]}`;
|
|
633
|
+
return signature === "3:4" || signature === "5:6" || signature === "7:8";
|
|
634
|
+
};
|
|
635
|
+
var createZipSignatureGuard = (endpoint) => {
|
|
636
|
+
let signature = Buffer.alloc(0);
|
|
637
|
+
let validated = false;
|
|
638
|
+
return new Transform({
|
|
639
|
+
transform(chunk, _encoding, callback) {
|
|
640
|
+
const chunkBuffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
641
|
+
if (!validated) {
|
|
642
|
+
const requiredBytes = Math.max(0, 4 - signature.length);
|
|
643
|
+
if (requiredBytes > 0) {
|
|
644
|
+
signature = Buffer.concat([
|
|
645
|
+
signature,
|
|
646
|
+
chunkBuffer.subarray(0, requiredBytes)
|
|
647
|
+
]);
|
|
648
|
+
}
|
|
649
|
+
if (signature.length >= 4) {
|
|
650
|
+
if (!looksLikeZipArchive(signature)) {
|
|
651
|
+
callback(
|
|
652
|
+
new PrimeUiApiContractError(
|
|
653
|
+
endpoint,
|
|
654
|
+
"response body is not a valid zip archive"
|
|
655
|
+
)
|
|
656
|
+
);
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
validated = true;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
callback(null, chunkBuffer);
|
|
663
|
+
},
|
|
664
|
+
flush(callback) {
|
|
665
|
+
if (!validated) {
|
|
666
|
+
callback(
|
|
667
|
+
new PrimeUiApiContractError(
|
|
668
|
+
endpoint,
|
|
669
|
+
"response body is not a valid zip archive"
|
|
670
|
+
)
|
|
671
|
+
);
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
callback();
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
};
|
|
678
|
+
var ApiProjectDataProvider = class {
|
|
679
|
+
apiKey;
|
|
680
|
+
apiRoot;
|
|
681
|
+
constructor(options) {
|
|
682
|
+
this.apiKey = options.apiKey;
|
|
683
|
+
this.apiRoot = normalizePrimeUiApiRoot(options.baseUrl);
|
|
684
|
+
}
|
|
685
|
+
async getProjectInfo() {
|
|
686
|
+
return this.requestJson("project", projectInfoSchema);
|
|
687
|
+
}
|
|
688
|
+
async getProjectPageBySlug(slug) {
|
|
689
|
+
const encodedSlug = encodeURIComponent(slug);
|
|
690
|
+
return this.requestJson(
|
|
691
|
+
`project/page?slug=${encodedSlug}`,
|
|
692
|
+
projectPageDetailsSchema
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
async listExports() {
|
|
696
|
+
const response = await this.requestJson(
|
|
697
|
+
"project/exports",
|
|
698
|
+
exportsResponseSchema
|
|
699
|
+
);
|
|
700
|
+
return response.exports;
|
|
701
|
+
}
|
|
702
|
+
async createExport() {
|
|
703
|
+
return this.requestJson("project/exports", createExportResponseSchema, {
|
|
704
|
+
method: "POST"
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* Consumer-side runtime contract for GET /api/v1/project/exports/:exportId/download.
|
|
709
|
+
* Producer source of truth: apps/studio/src/app/api/v1/project/exports/[exportId]/download/route.ts
|
|
710
|
+
* Keep content-type and archive format checks synchronized with the producer response.
|
|
711
|
+
*/
|
|
712
|
+
async downloadExportArchive(exportId, destinationPath) {
|
|
713
|
+
const endpoint = `project/exports/${encodeURIComponent(exportId)}/download`;
|
|
714
|
+
const apiKey = this.requireApiKey();
|
|
715
|
+
const response = await fetch(this.buildUrl(endpoint), {
|
|
716
|
+
method: "GET",
|
|
717
|
+
headers: {
|
|
718
|
+
Authorization: `Bearer ${apiKey}`
|
|
719
|
+
}
|
|
720
|
+
});
|
|
721
|
+
if (!response.ok) {
|
|
722
|
+
const details = await this.readError(response);
|
|
723
|
+
throw new Error(
|
|
724
|
+
`PrimeUI API request failed (${response.status}) while downloading export "${exportId}": ${details}`
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
728
|
+
if (!isZipContentType(contentType)) {
|
|
729
|
+
throw new PrimeUiApiContractError(
|
|
730
|
+
endpoint,
|
|
731
|
+
`expected zip content-type but got "${contentType || "unknown"}"`
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
if (!response.body) {
|
|
735
|
+
throw new PrimeUiApiContractError(endpoint, "response body is empty");
|
|
736
|
+
}
|
|
737
|
+
await ensureDir(path5.dirname(destinationPath));
|
|
738
|
+
const zipStream = Readable.fromWeb(
|
|
739
|
+
response.body
|
|
740
|
+
);
|
|
741
|
+
const signatureGuard = createZipSignatureGuard(endpoint);
|
|
742
|
+
const fileStream = createWriteStream(destinationPath);
|
|
743
|
+
try {
|
|
744
|
+
await pipeline(zipStream, signatureGuard, fileStream);
|
|
745
|
+
} catch (error) {
|
|
746
|
+
await unlink(destinationPath).catch(() => void 0);
|
|
747
|
+
throw error;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
buildUrl(endpoint) {
|
|
751
|
+
return buildPrimeUiApiUrl(this.apiRoot, endpoint);
|
|
752
|
+
}
|
|
753
|
+
async requestJson(endpoint, schema, requestInit = {}) {
|
|
754
|
+
const apiKey = this.requireApiKey();
|
|
755
|
+
const method = requestInit.method ?? "GET";
|
|
756
|
+
const response = await fetch(this.buildUrl(endpoint), {
|
|
757
|
+
...requestInit,
|
|
758
|
+
method,
|
|
759
|
+
headers: {
|
|
760
|
+
...requestInit.headers ?? {},
|
|
761
|
+
Authorization: `Bearer ${apiKey}`
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
if (!response.ok) {
|
|
765
|
+
const details = await this.readError(response);
|
|
766
|
+
throw new Error(
|
|
767
|
+
`PrimeUI API request failed (${response.status}) for "${endpoint}": ${details}`
|
|
768
|
+
);
|
|
769
|
+
}
|
|
770
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
771
|
+
if (!isJsonContentType(contentType)) {
|
|
772
|
+
throw new PrimeUiApiContractError(
|
|
773
|
+
endpoint,
|
|
774
|
+
`expected JSON content-type but got "${contentType || "unknown"}"`
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
let payload;
|
|
778
|
+
try {
|
|
779
|
+
payload = await response.json();
|
|
780
|
+
} catch {
|
|
781
|
+
throw new PrimeUiApiContractError(
|
|
782
|
+
endpoint,
|
|
783
|
+
"response body is not valid JSON"
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
const parsed = schema.safeParse(payload);
|
|
787
|
+
if (!parsed.success) {
|
|
788
|
+
const details = parsed.error.issues.map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`).join("; ");
|
|
789
|
+
throw new PrimeUiApiContractError(endpoint, details);
|
|
790
|
+
}
|
|
791
|
+
return parsed.data;
|
|
792
|
+
}
|
|
793
|
+
async readError(response) {
|
|
794
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
795
|
+
try {
|
|
796
|
+
if (isJsonContentType(contentType)) {
|
|
797
|
+
const body = await response.json();
|
|
798
|
+
if (body?.message && body?.code) {
|
|
799
|
+
return `${body.code}: ${body.message}`;
|
|
800
|
+
}
|
|
801
|
+
if (body?.message) {
|
|
802
|
+
return body.message;
|
|
803
|
+
}
|
|
804
|
+
} else {
|
|
805
|
+
const bodyText = await response.text();
|
|
806
|
+
if (bodyText.trim()) {
|
|
807
|
+
return bodyText.trim();
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
} catch {
|
|
811
|
+
}
|
|
812
|
+
return response.statusText || "Unknown error";
|
|
813
|
+
}
|
|
814
|
+
requireApiKey() {
|
|
815
|
+
const apiKey = this.apiKey?.trim();
|
|
816
|
+
if (!apiKey) {
|
|
817
|
+
throw new Error("PRIMEUI_API_KEY is required to call PrimeUI API tools.");
|
|
818
|
+
}
|
|
819
|
+
return apiKey;
|
|
820
|
+
}
|
|
821
|
+
};
|
|
822
|
+
|
|
823
|
+
// src/health/hints.ts
|
|
824
|
+
function addHint(target, seen, hint) {
|
|
825
|
+
if (!seen.has(hint)) {
|
|
826
|
+
seen.add(hint);
|
|
827
|
+
target.push(hint);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
function buildHealthHints(options) {
|
|
831
|
+
const { report, connectApiKey } = options;
|
|
832
|
+
const hints = [];
|
|
833
|
+
const seen = /* @__PURE__ */ new Set();
|
|
834
|
+
const validNestedRoots = report.nestedSearch.candidates.filter((candidate) => candidate.configStatus === "valid").map((candidate) => candidate.projectRoot);
|
|
835
|
+
if (!report.runtimeContext.inputProjectRoot && report.projectResolution.configStatus === "unresolved" && report.projectResolution.errorSource === "search") {
|
|
836
|
+
addHint(
|
|
837
|
+
hints,
|
|
838
|
+
seen,
|
|
839
|
+
"Config was not found from the current working directory. Rerun `--health /absolute/project/path`, pass the same absolute path via MCP tool `projectRoot`, or set `PRIMEUI_PROJECT_ROOT` if your MCP client starts outside the project."
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
if (report.runtimeContext.inputProjectRoot && report.projectResolution.primeUiDirStatus === "missing") {
|
|
843
|
+
if (report.nestedSearch.candidates.length > 0) {
|
|
844
|
+
addHint(
|
|
845
|
+
hints,
|
|
846
|
+
seen,
|
|
847
|
+
validNestedRoots.length > 0 ? `No direct \`.primeui\` directory was found under the provided path. Rerun health with one of the exact nested project roots that passed config validation, for example: ${validNestedRoots.join(", ")}.` : "No direct `.primeui` directory was found under the provided path. Nested `.primeui` directories were found below it, so rerun health with the exact intended project root before using MCP tools."
|
|
848
|
+
);
|
|
849
|
+
if (validNestedRoots.length > 0) {
|
|
850
|
+
addHint(
|
|
851
|
+
hints,
|
|
852
|
+
seen,
|
|
853
|
+
`When calling MCP tools, pass \`projectRoot\` only for a candidate whose nested health check passes, for example: ${validNestedRoots[0]}.`
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
} else {
|
|
857
|
+
const connectCommand = `npx @primeuicom/cli ${connectApiKey ?? "<api-key>"} connect`;
|
|
858
|
+
addHint(
|
|
859
|
+
hints,
|
|
860
|
+
seen,
|
|
861
|
+
`No \`.primeui\` directory was found under the provided path. From the intended target folder, run \`${connectCommand}\` to connect the project to PrimeUI.`
|
|
862
|
+
);
|
|
863
|
+
if (!connectApiKey) {
|
|
864
|
+
addHint(
|
|
865
|
+
hints,
|
|
866
|
+
seen,
|
|
867
|
+
"If you do not already have a valid PrimeUI API key, get it from the appropriate PrimeUI project on `primeui.com`."
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
if (report.projectResolution.primeUiDirStatus === "found" && report.projectResolution.configStatus === "missing") {
|
|
873
|
+
addHint(
|
|
874
|
+
hints,
|
|
875
|
+
seen,
|
|
876
|
+
"`.primeui/project.json` is missing. It was likely corrupted or deleted; restore it by reconnecting the project or by making a fresh export via the PrimeUI CLI tool."
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
if (report.projectResolution.primeUiDirStatus === "found" && (report.projectResolution.configStatus === "invalid-json" || report.projectResolution.configStatus === "invalid-format" || report.projectResolution.configStatus === "read-failed")) {
|
|
880
|
+
addHint(
|
|
881
|
+
hints,
|
|
882
|
+
seen,
|
|
883
|
+
"`.primeui/project.json` looks corrupted or incomplete. Restore it by reconnecting the project or by making a fresh export via the PrimeUI CLI tool."
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
if (report.runtimeContext.envProjectRoot && report.sourceResolution.projectRoot.comparison === "different") {
|
|
887
|
+
addHint(
|
|
888
|
+
hints,
|
|
889
|
+
seen,
|
|
890
|
+
report.projectResolution.inputProjectRootValidated ? `\`PRIMEUI_PROJECT_ROOT\` points to a different location than the validated explicit input path. Fix or unset \`PRIMEUI_PROJECT_ROOT\`, and use the successful path via MCP tool \`projectRoot\`: ${report.runtimeContext.inputProjectRoot}.` : "`PRIMEUI_PROJECT_ROOT` points to a different location than the explicit input path. Correct the project root first, then rerun health before using MCP tool `projectRoot`."
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
if (report.sourceResolution.apiKey.effectiveSource === "missing" && !(report.projectResolution.primeUiDirStatus === "found" && report.projectResolution.configStatus !== "valid")) {
|
|
894
|
+
addHint(
|
|
895
|
+
hints,
|
|
896
|
+
seen,
|
|
897
|
+
"No usable API key was found. Set `PRIMEUI_API_KEY`, restore `apiKey` in `.primeui/project.json`, or get a valid key from the appropriate PrimeUI project on `primeui.com`."
|
|
898
|
+
);
|
|
899
|
+
}
|
|
900
|
+
if (report.apiCheck.status === "skipped" && report.apiCheck.errorKind === "configuration" && report.runtimeContext.envApiBaseUrl) {
|
|
901
|
+
addHint(
|
|
902
|
+
hints,
|
|
903
|
+
seen,
|
|
904
|
+
"Set `PRIMEUI_API_BASE_URL` to a valid absolute URL such as `http://localhost:3020`, or remove it to fall back to the standard production API."
|
|
905
|
+
);
|
|
906
|
+
}
|
|
907
|
+
if (report.apiCheck.status === "failed" && report.apiCheck.errorKind === "auth") {
|
|
908
|
+
if (report.sourceResolution.apiKey.envState === "set" && report.sourceResolution.apiKey.configState === "set") {
|
|
909
|
+
addHint(
|
|
910
|
+
hints,
|
|
911
|
+
seen,
|
|
912
|
+
"Both env and config API keys are present. Verify that `PRIMEUI_API_KEY` is not unintentionally overriding a valid config key."
|
|
913
|
+
);
|
|
914
|
+
}
|
|
915
|
+
if (report.apiCheck.nonStandardBaseUrl) {
|
|
916
|
+
addHint(
|
|
917
|
+
hints,
|
|
918
|
+
seen,
|
|
919
|
+
"Authentication failed while using a non-standard `PRIMEUI_API_BASE_URL`. If you did not intend to target a custom API host, remove that env var and retry."
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
addHint(
|
|
923
|
+
hints,
|
|
924
|
+
seen,
|
|
925
|
+
"The current API key was rejected by `project info`. Rotate or regenerate the key, then rerun health."
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
if (report.apiCheck.status === "failed" && report.apiCheck.errorKind === "network") {
|
|
929
|
+
addHint(
|
|
930
|
+
hints,
|
|
931
|
+
seen,
|
|
932
|
+
report.apiCheck.nonStandardBaseUrl ? "The `project info` request failed while using a non-standard `PRIMEUI_API_BASE_URL`. Verify that host is reachable, or remove the env var if you did not intend to target a custom API." : "The `project info` request could not reach the API successfully. Verify network reachability and retry."
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
return hints;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// src/health/nested-primeui-search.ts
|
|
939
|
+
import { readdir, stat as stat2 } from "fs/promises";
|
|
940
|
+
import path6 from "path";
|
|
941
|
+
var SCAN_EXCLUDED_DIRS = /* @__PURE__ */ new Set([
|
|
942
|
+
".git",
|
|
943
|
+
"node_modules",
|
|
944
|
+
".next",
|
|
945
|
+
"dist",
|
|
946
|
+
"build",
|
|
947
|
+
"coverage"
|
|
948
|
+
]);
|
|
949
|
+
async function isFile(targetPath) {
|
|
950
|
+
try {
|
|
951
|
+
const targetStat = await stat2(targetPath);
|
|
952
|
+
return targetStat.isFile();
|
|
953
|
+
} catch {
|
|
954
|
+
return false;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
function mapConfigStatus(error) {
|
|
958
|
+
if (!(error instanceof PrimeUiProjectConfigError)) {
|
|
959
|
+
return "read-failed";
|
|
960
|
+
}
|
|
961
|
+
switch (error.code) {
|
|
962
|
+
case "PROJECT_CONFIG_INVALID_JSON":
|
|
963
|
+
return "invalid-json";
|
|
964
|
+
case "PROJECT_CONFIG_INVALID_FORMAT":
|
|
965
|
+
return "invalid-format";
|
|
966
|
+
case "PROJECT_CONFIG_READ_FAILED":
|
|
967
|
+
return "read-failed";
|
|
968
|
+
case "PROJECT_CONFIG_NOT_FOUND":
|
|
969
|
+
return "missing";
|
|
970
|
+
default:
|
|
971
|
+
return "read-failed";
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
async function inspectCandidate(projectRoot, primeUiPath) {
|
|
975
|
+
const configPath = path6.join(primeUiPath, "project.json");
|
|
976
|
+
if (!await isFile(configPath)) {
|
|
977
|
+
return {
|
|
978
|
+
projectRoot,
|
|
979
|
+
primeUiPath,
|
|
980
|
+
configPath,
|
|
981
|
+
hasProjectConfig: false,
|
|
982
|
+
configStatus: "missing"
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
try {
|
|
986
|
+
const projectConfig = await readPrimeUiProjectConfig(configPath);
|
|
987
|
+
return {
|
|
988
|
+
projectRoot,
|
|
989
|
+
primeUiPath,
|
|
990
|
+
configPath,
|
|
991
|
+
hasProjectConfig: true,
|
|
992
|
+
configStatus: "valid",
|
|
993
|
+
targetProjectPath: projectConfig.targetProjectPath,
|
|
994
|
+
targetProjectRoot: resolvePrimeUiTargetProjectRoot(
|
|
995
|
+
projectRoot,
|
|
996
|
+
projectConfig
|
|
997
|
+
)
|
|
998
|
+
};
|
|
999
|
+
} catch (error) {
|
|
1000
|
+
return {
|
|
1001
|
+
projectRoot,
|
|
1002
|
+
primeUiPath,
|
|
1003
|
+
configPath,
|
|
1004
|
+
hasProjectConfig: true,
|
|
1005
|
+
configStatus: mapConfigStatus(error)
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
async function findNestedPrimeUiCandidates(rootPath) {
|
|
1010
|
+
const queue = [path6.resolve(rootPath)];
|
|
1011
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1012
|
+
const candidates = [];
|
|
1013
|
+
while (queue.length > 0) {
|
|
1014
|
+
const currentDir = queue.shift();
|
|
1015
|
+
if (!currentDir || seen.has(currentDir)) {
|
|
1016
|
+
continue;
|
|
1017
|
+
}
|
|
1018
|
+
seen.add(currentDir);
|
|
1019
|
+
let entries;
|
|
1020
|
+
try {
|
|
1021
|
+
entries = await readdir(currentDir, { withFileTypes: true });
|
|
1022
|
+
} catch {
|
|
1023
|
+
continue;
|
|
1024
|
+
}
|
|
1025
|
+
for (const entry of entries) {
|
|
1026
|
+
if (!entry.isDirectory()) {
|
|
1027
|
+
continue;
|
|
1028
|
+
}
|
|
1029
|
+
const entryPath = path6.join(currentDir, entry.name);
|
|
1030
|
+
if (entry.name === PRIMEUI_PROJECT_DIR_NAME) {
|
|
1031
|
+
candidates.push(await inspectCandidate(currentDir, entryPath));
|
|
1032
|
+
continue;
|
|
1033
|
+
}
|
|
1034
|
+
if (SCAN_EXCLUDED_DIRS.has(entry.name)) {
|
|
1035
|
+
continue;
|
|
1036
|
+
}
|
|
1037
|
+
queue.push(entryPath);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
return candidates.sort(
|
|
1041
|
+
(left, right) => left.primeUiPath.localeCompare(right.primeUiPath)
|
|
1042
|
+
);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// src/health/report.ts
|
|
1046
|
+
function renderValue(value, fallback = "missing") {
|
|
1047
|
+
return value?.trim() ? value : fallback;
|
|
1048
|
+
}
|
|
1049
|
+
function renderProjectRootComparison(comparison) {
|
|
1050
|
+
switch (comparison) {
|
|
1051
|
+
case "same":
|
|
1052
|
+
return "same";
|
|
1053
|
+
case "different":
|
|
1054
|
+
return "different";
|
|
1055
|
+
case "input-only":
|
|
1056
|
+
return "only explicit input path is set";
|
|
1057
|
+
case "env-only":
|
|
1058
|
+
return "only PRIMEUI_PROJECT_ROOT is set";
|
|
1059
|
+
case "neither":
|
|
1060
|
+
return "neither explicit input path nor PRIMEUI_PROJECT_ROOT is set";
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
function renderApiKeyComparison(comparison) {
|
|
1064
|
+
switch (comparison) {
|
|
1065
|
+
case "same":
|
|
1066
|
+
return "same";
|
|
1067
|
+
case "different":
|
|
1068
|
+
return "different";
|
|
1069
|
+
case "not-comparable":
|
|
1070
|
+
return "not comparable";
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
function renderConfigStatus(status) {
|
|
1074
|
+
switch (status) {
|
|
1075
|
+
case "valid":
|
|
1076
|
+
return "valid";
|
|
1077
|
+
case "missing":
|
|
1078
|
+
return "missing";
|
|
1079
|
+
case "invalid-json":
|
|
1080
|
+
return "invalid JSON";
|
|
1081
|
+
case "invalid-format":
|
|
1082
|
+
return "invalid format";
|
|
1083
|
+
case "read-failed":
|
|
1084
|
+
return "read failed";
|
|
1085
|
+
case "unresolved":
|
|
1086
|
+
return "unresolved";
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
function formatSection(title, lines) {
|
|
1090
|
+
return [title, ...lines.map((line) => `- ${line}`)].join("\n");
|
|
1091
|
+
}
|
|
1092
|
+
function formatHealthReport(report) {
|
|
1093
|
+
const sections = [
|
|
1094
|
+
"PrimeUI MCP Health",
|
|
1095
|
+
"",
|
|
1096
|
+
formatSection("Version", [
|
|
1097
|
+
`current: ${report.version.currentVersion}`,
|
|
1098
|
+
`latest: ${renderValue(report.version.latestVersion, "unavailable")}`,
|
|
1099
|
+
`update status: ${report.version.updateStatus}${report.version.detail ? ` (${report.version.detail})` : ""}`
|
|
1100
|
+
]),
|
|
1101
|
+
"",
|
|
1102
|
+
formatSection("Runtime Context", [
|
|
1103
|
+
`cwd: ${report.runtimeContext.cwd}`,
|
|
1104
|
+
`health input path: ${renderValue(report.runtimeContext.inputProjectRoot)}`,
|
|
1105
|
+
`PRIMEUI_PROJECT_ROOT: ${renderValue(report.runtimeContext.envProjectRoot)}`,
|
|
1106
|
+
`PRIMEUI_API_BASE_URL: ${renderValue(report.runtimeContext.envApiBaseUrl)}`,
|
|
1107
|
+
`PRIMEUI_API_KEY: ${report.runtimeContext.envApiKeySet ? "set" : "missing"}`
|
|
1108
|
+
]),
|
|
1109
|
+
"",
|
|
1110
|
+
formatSection("Source Resolution", [
|
|
1111
|
+
`input project root: ${renderValue(report.sourceResolution.projectRoot.inputPath)}`,
|
|
1112
|
+
`env project root: ${renderValue(report.sourceResolution.projectRoot.envPath)}`,
|
|
1113
|
+
`project root comparison: ${renderProjectRootComparison(
|
|
1114
|
+
report.sourceResolution.projectRoot.comparison
|
|
1115
|
+
)}`,
|
|
1116
|
+
`effective project root source: ${report.sourceResolution.projectRoot.effectiveSource}`,
|
|
1117
|
+
`env API key: ${report.sourceResolution.apiKey.envState}`,
|
|
1118
|
+
`config API key: ${report.sourceResolution.apiKey.configState}`,
|
|
1119
|
+
`API key comparison: ${renderApiKeyComparison(
|
|
1120
|
+
report.sourceResolution.apiKey.comparison
|
|
1121
|
+
)}`,
|
|
1122
|
+
`effective API key source: ${report.sourceResolution.apiKey.effectiveSource}`
|
|
1123
|
+
]),
|
|
1124
|
+
"",
|
|
1125
|
+
formatSection("Project Resolution", [
|
|
1126
|
+
`resolved project root: ${renderValue(
|
|
1127
|
+
report.projectResolution.resolvedProjectRoot,
|
|
1128
|
+
"unresolved"
|
|
1129
|
+
)}`,
|
|
1130
|
+
`effective project root source: ${report.projectResolution.effectiveSource}`,
|
|
1131
|
+
`project root validation via explicit input: ${report.projectResolution.inputProjectRootValidated ? "passed" : "not used or failed"}`,
|
|
1132
|
+
`.primeui directory: ${report.projectResolution.primeUiDirStatus === "found" ? `found at ${report.projectResolution.primeUiDirPath}` : report.projectResolution.primeUiDirStatus}`,
|
|
1133
|
+
`config path: ${report.projectResolution.configPath ? `${report.projectResolution.configPath} (${renderConfigStatus(
|
|
1134
|
+
report.projectResolution.configStatus
|
|
1135
|
+
)})` : renderConfigStatus(report.projectResolution.configStatus)}`,
|
|
1136
|
+
`targetProjectPath: ${report.projectResolution.targetProjectPath ?? "unresolved"}`,
|
|
1137
|
+
`resolved target project root: ${renderValue(
|
|
1138
|
+
report.projectResolution.targetProjectRoot,
|
|
1139
|
+
"unresolved"
|
|
1140
|
+
)}`,
|
|
1141
|
+
`project resolution detail: ${renderValue(
|
|
1142
|
+
report.projectResolution.errorMessage,
|
|
1143
|
+
"none"
|
|
1144
|
+
)}`
|
|
1145
|
+
]),
|
|
1146
|
+
"",
|
|
1147
|
+
formatSection("API Check", [
|
|
1148
|
+
`API root: ${renderValue(report.apiCheck.apiRoot, "unresolved")}`,
|
|
1149
|
+
`project info URL: ${renderValue(report.apiCheck.projectInfoUrl, "unresolved")}`,
|
|
1150
|
+
`PRIMEUI_API_BASE_URL source: ${report.apiCheck.apiBaseUrlSource}`,
|
|
1151
|
+
`non-standard API base URL: ${report.apiCheck.nonStandardBaseUrl ? "yes" : "no"}`,
|
|
1152
|
+
`project info request: ${report.apiCheck.status}`,
|
|
1153
|
+
`request detail: ${report.apiCheck.message}`
|
|
1154
|
+
])
|
|
1155
|
+
];
|
|
1156
|
+
if (report.nestedSearch.performed) {
|
|
1157
|
+
const candidateLines = report.nestedSearch.candidates.length > 0 ? report.nestedSearch.candidates.map((candidate) => {
|
|
1158
|
+
const parts = [
|
|
1159
|
+
candidate.primeUiPath,
|
|
1160
|
+
`project root: ${candidate.projectRoot}`,
|
|
1161
|
+
`config: ${renderConfigStatus(candidate.configStatus)}`
|
|
1162
|
+
];
|
|
1163
|
+
if (candidate.targetProjectPath) {
|
|
1164
|
+
parts.push(`targetProjectPath: ${candidate.targetProjectPath}`);
|
|
1165
|
+
}
|
|
1166
|
+
return parts.join("; ");
|
|
1167
|
+
}) : ["none found"];
|
|
1168
|
+
sections.splice(
|
|
1169
|
+
sections.length - 2,
|
|
1170
|
+
0,
|
|
1171
|
+
"",
|
|
1172
|
+
formatSection("Nested PrimeUI Candidates", [
|
|
1173
|
+
`search root: ${renderValue(report.nestedSearch.rootPath, "unresolved")}`,
|
|
1174
|
+
...candidateLines
|
|
1175
|
+
])
|
|
1176
|
+
);
|
|
1177
|
+
}
|
|
1178
|
+
sections.push("");
|
|
1179
|
+
if (report.hints.length === 0) {
|
|
1180
|
+
sections.push("Hints\n- none");
|
|
1181
|
+
} else {
|
|
1182
|
+
sections.push(
|
|
1183
|
+
[
|
|
1184
|
+
"Hints",
|
|
1185
|
+
...report.hints.map((hint, index) => `${index + 1}. ${hint}`)
|
|
1186
|
+
].join("\n")
|
|
1187
|
+
);
|
|
1188
|
+
}
|
|
1189
|
+
return sections.join("\n");
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
// src/health/update-check.ts
|
|
1193
|
+
function parseVersion(input) {
|
|
1194
|
+
const [coreVersion, prerelease] = input.split("-", 2);
|
|
1195
|
+
return {
|
|
1196
|
+
parts: coreVersion.split(".").map((segment) => {
|
|
1197
|
+
const parsed = Number.parseInt(segment, 10);
|
|
1198
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
1199
|
+
}),
|
|
1200
|
+
prerelease
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
function compareVersions(left, right) {
|
|
1204
|
+
const leftVersion = parseVersion(left);
|
|
1205
|
+
const rightVersion = parseVersion(right);
|
|
1206
|
+
const length = Math.max(leftVersion.parts.length, rightVersion.parts.length);
|
|
1207
|
+
for (let index = 0; index < length; index += 1) {
|
|
1208
|
+
const leftPart = leftVersion.parts[index] ?? 0;
|
|
1209
|
+
const rightPart = rightVersion.parts[index] ?? 0;
|
|
1210
|
+
if (leftPart !== rightPart) {
|
|
1211
|
+
return leftPart > rightPart ? 1 : -1;
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
if (!leftVersion.prerelease && !rightVersion.prerelease) {
|
|
1215
|
+
return 0;
|
|
1216
|
+
}
|
|
1217
|
+
if (!leftVersion.prerelease) {
|
|
1218
|
+
return 1;
|
|
1219
|
+
}
|
|
1220
|
+
if (!rightVersion.prerelease) {
|
|
1221
|
+
return -1;
|
|
1222
|
+
}
|
|
1223
|
+
return leftVersion.prerelease.localeCompare(rightVersion.prerelease);
|
|
1224
|
+
}
|
|
1225
|
+
async function checkLatestPackageVersion(packageName, currentVersion) {
|
|
1226
|
+
const controller = new AbortController();
|
|
1227
|
+
const timeoutHandle = setTimeout(() => controller.abort(), 3e3);
|
|
1228
|
+
try {
|
|
1229
|
+
const response = await fetch(
|
|
1230
|
+
`https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`,
|
|
1231
|
+
{
|
|
1232
|
+
signal: controller.signal
|
|
1233
|
+
}
|
|
1234
|
+
);
|
|
1235
|
+
if (!response.ok) {
|
|
1236
|
+
return {
|
|
1237
|
+
status: "unavailable",
|
|
1238
|
+
detail: `npm registry request failed with status ${response.status}`
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
const payload = await response.json();
|
|
1242
|
+
const latestVersion = payload.version?.trim();
|
|
1243
|
+
if (!latestVersion) {
|
|
1244
|
+
return {
|
|
1245
|
+
status: "unavailable",
|
|
1246
|
+
detail: "npm registry response did not include a latest version"
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
const comparison = compareVersions(currentVersion, latestVersion);
|
|
1250
|
+
return {
|
|
1251
|
+
latestVersion,
|
|
1252
|
+
status: comparison < 0 ? "update-available" : "up-to-date",
|
|
1253
|
+
detail: comparison > 0 ? "installed version is newer than the npm latest tag" : void 0
|
|
1254
|
+
};
|
|
1255
|
+
} catch (error) {
|
|
1256
|
+
return {
|
|
1257
|
+
status: "unavailable",
|
|
1258
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
1259
|
+
};
|
|
1260
|
+
} finally {
|
|
1261
|
+
clearTimeout(timeoutHandle);
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
// src/health/run-health-check.ts
|
|
1266
|
+
function mapProjectSource(source) {
|
|
1267
|
+
switch (source) {
|
|
1268
|
+
case "tool":
|
|
1269
|
+
return "input";
|
|
1270
|
+
case "env":
|
|
1271
|
+
return "env";
|
|
1272
|
+
case "search":
|
|
1273
|
+
return "search";
|
|
1274
|
+
default:
|
|
1275
|
+
return "unresolved";
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
function mapProjectErrorSource(source) {
|
|
1279
|
+
switch (source) {
|
|
1280
|
+
case "tool":
|
|
1281
|
+
return "input";
|
|
1282
|
+
case "env":
|
|
1283
|
+
return "env";
|
|
1284
|
+
case "search":
|
|
1285
|
+
return "search";
|
|
1286
|
+
default:
|
|
1287
|
+
return void 0;
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
function compareProjectRoots(inputPath, envPath) {
|
|
1291
|
+
if (inputPath && envPath) {
|
|
1292
|
+
return inputPath === envPath ? "same" : "different";
|
|
1293
|
+
}
|
|
1294
|
+
if (inputPath) {
|
|
1295
|
+
return "input-only";
|
|
1296
|
+
}
|
|
1297
|
+
if (envPath) {
|
|
1298
|
+
return "env-only";
|
|
1299
|
+
}
|
|
1300
|
+
return "neither";
|
|
1301
|
+
}
|
|
1302
|
+
function compareApiKeys(valuesMatch, envProvided, configProvided) {
|
|
1303
|
+
if (envProvided && configProvided) {
|
|
1304
|
+
return valuesMatch ? "same" : "different";
|
|
1305
|
+
}
|
|
1306
|
+
return "not-comparable";
|
|
1307
|
+
}
|
|
1308
|
+
function mapConfigStatus2(error) {
|
|
1309
|
+
if (!error) {
|
|
1310
|
+
return "valid";
|
|
1311
|
+
}
|
|
1312
|
+
switch (error.code) {
|
|
1313
|
+
case "PROJECT_CONFIG_NOT_FOUND":
|
|
1314
|
+
return typeof error.details.expectedConfigPath === "string" ? "missing" : "unresolved";
|
|
1315
|
+
case "PROJECT_CONFIG_INVALID_JSON":
|
|
1316
|
+
return "invalid-json";
|
|
1317
|
+
case "PROJECT_CONFIG_INVALID_FORMAT":
|
|
1318
|
+
return "invalid-format";
|
|
1319
|
+
case "PROJECT_CONFIG_READ_FAILED":
|
|
1320
|
+
return "read-failed";
|
|
1321
|
+
default:
|
|
1322
|
+
return "unresolved";
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
async function directoryExists(targetPath) {
|
|
1326
|
+
try {
|
|
1327
|
+
const targetStat = await stat3(targetPath);
|
|
1328
|
+
return targetStat.isDirectory();
|
|
1329
|
+
} catch {
|
|
1330
|
+
return false;
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
function classifyApiError(message) {
|
|
1334
|
+
return /PrimeUI API request failed \((401|403)\)/.test(message) ? "auth" : "network";
|
|
1335
|
+
}
|
|
1336
|
+
async function runHealthCheck(options) {
|
|
1337
|
+
const metadata = options.metadata ?? packageMetadata;
|
|
1338
|
+
const env = options.env ?? process.env;
|
|
1339
|
+
const cwd = path7.resolve(options.cwd ?? process.cwd());
|
|
1340
|
+
const inputProjectRoot = options.projectRoot?.trim() ? path7.resolve(options.projectRoot.trim()) : void 0;
|
|
1341
|
+
const envProjectRoot = env.PRIMEUI_PROJECT_ROOT?.trim() ? path7.resolve(env.PRIMEUI_PROJECT_ROOT.trim()) : void 0;
|
|
1342
|
+
const versionResult = await (options.checkLatestVersion ?? checkLatestPackageVersion)(metadata.name, metadata.version);
|
|
1343
|
+
let resolvedProjectConfig;
|
|
1344
|
+
let projectConfigError;
|
|
1345
|
+
try {
|
|
1346
|
+
resolvedProjectConfig = await resolvePrimeUiProjectConfig({
|
|
1347
|
+
cwd,
|
|
1348
|
+
projectRootFromTool: inputProjectRoot,
|
|
1349
|
+
projectRootFromEnv: envProjectRoot
|
|
1350
|
+
});
|
|
1351
|
+
} catch (error) {
|
|
1352
|
+
if (!(error instanceof PrimeUiProjectConfigError)) {
|
|
1353
|
+
throw error;
|
|
1354
|
+
}
|
|
1355
|
+
projectConfigError = error;
|
|
1356
|
+
}
|
|
1357
|
+
const resolvedProjectRoot = resolvedProjectConfig?.projectRoot ?? (typeof projectConfigError?.details.projectRoot === "string" ? path7.resolve(projectConfigError.details.projectRoot) : void 0);
|
|
1358
|
+
const primeUiDirPath = resolvedProjectRoot ? path7.join(resolvedProjectRoot, PRIMEUI_PROJECT_DIR_NAME) : inputProjectRoot ? path7.join(inputProjectRoot, PRIMEUI_PROJECT_DIR_NAME) : envProjectRoot && mapProjectErrorSource(
|
|
1359
|
+
projectConfigError?.details.source
|
|
1360
|
+
) === "env" ? path7.join(envProjectRoot, PRIMEUI_PROJECT_DIR_NAME) : void 0;
|
|
1361
|
+
const primeUiDirStatus = primeUiDirPath ? await directoryExists(primeUiDirPath) ? "found" : "missing" : "unresolved";
|
|
1362
|
+
const configStatus = resolvedProjectConfig ? "valid" : mapConfigStatus2(projectConfigError);
|
|
1363
|
+
const configPath = resolvedProjectConfig?.projectConfigPath ?? (typeof projectConfigError?.details.expectedConfigPath === "string" ? path7.resolve(projectConfigError.details.expectedConfigPath) : primeUiDirPath ? path7.join(primeUiDirPath, "project.json") : void 0);
|
|
1364
|
+
const nestedSearchPerformed = Boolean(inputProjectRoot) && primeUiDirStatus === "missing";
|
|
1365
|
+
const nestedCandidates = nestedSearchPerformed ? await findNestedPrimeUiCandidates(inputProjectRoot) : [];
|
|
1366
|
+
const apiKeyDetails = resolvePrimeUiApiKeyDetails({
|
|
1367
|
+
projectConfig: resolvedProjectConfig?.projectConfig,
|
|
1368
|
+
apiKeyFromEnv: env.PRIMEUI_API_KEY
|
|
1369
|
+
});
|
|
1370
|
+
const configApiKeyState = resolvedProjectConfig ? "set" : configStatus === "missing" || configStatus === "unresolved" ? "missing" : "unknown";
|
|
1371
|
+
const apiBaseUrlSource = env.PRIMEUI_API_BASE_URL?.trim() ? "env" : "default";
|
|
1372
|
+
const defaultApiRoot = normalizePrimeUiApiRoot(DEFAULT_API_BASE_URL);
|
|
1373
|
+
let apiRoot;
|
|
1374
|
+
let projectInfoUrl;
|
|
1375
|
+
let apiCheck;
|
|
1376
|
+
try {
|
|
1377
|
+
apiRoot = normalizePrimeUiApiRoot(env.PRIMEUI_API_BASE_URL);
|
|
1378
|
+
projectInfoUrl = buildPrimeUiApiUrl(apiRoot, "project");
|
|
1379
|
+
} catch (error) {
|
|
1380
|
+
apiCheck = {
|
|
1381
|
+
status: "skipped",
|
|
1382
|
+
apiBaseUrlSource,
|
|
1383
|
+
nonStandardBaseUrl: Boolean(env.PRIMEUI_API_BASE_URL?.trim()),
|
|
1384
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1385
|
+
errorKind: "configuration"
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
if (!apiCheck) {
|
|
1389
|
+
if (!apiKeyDetails.apiKey) {
|
|
1390
|
+
apiCheck = {
|
|
1391
|
+
status: "skipped",
|
|
1392
|
+
apiRoot,
|
|
1393
|
+
projectInfoUrl,
|
|
1394
|
+
apiBaseUrlSource,
|
|
1395
|
+
nonStandardBaseUrl: apiRoot !== defaultApiRoot,
|
|
1396
|
+
message: "No usable API key was available, so project info was not requested."
|
|
1397
|
+
};
|
|
1398
|
+
} else {
|
|
1399
|
+
const provider = options.createProvider?.({
|
|
1400
|
+
apiKey: apiKeyDetails.apiKey,
|
|
1401
|
+
baseUrl: env.PRIMEUI_API_BASE_URL
|
|
1402
|
+
}) ?? new ApiProjectDataProvider({
|
|
1403
|
+
apiKey: apiKeyDetails.apiKey,
|
|
1404
|
+
baseUrl: env.PRIMEUI_API_BASE_URL
|
|
1405
|
+
});
|
|
1406
|
+
try {
|
|
1407
|
+
await provider.getProjectInfo();
|
|
1408
|
+
apiCheck = {
|
|
1409
|
+
status: "passed",
|
|
1410
|
+
apiRoot,
|
|
1411
|
+
projectInfoUrl,
|
|
1412
|
+
apiBaseUrlSource,
|
|
1413
|
+
nonStandardBaseUrl: apiRoot !== defaultApiRoot,
|
|
1414
|
+
message: "Project info request succeeded with the current API key."
|
|
1415
|
+
};
|
|
1416
|
+
} catch (error) {
|
|
1417
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1418
|
+
apiCheck = {
|
|
1419
|
+
status: "failed",
|
|
1420
|
+
apiRoot,
|
|
1421
|
+
projectInfoUrl,
|
|
1422
|
+
apiBaseUrlSource,
|
|
1423
|
+
nonStandardBaseUrl: apiRoot !== defaultApiRoot,
|
|
1424
|
+
message,
|
|
1425
|
+
errorKind: classifyApiError(message)
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
if (!apiCheck) {
|
|
1431
|
+
throw new Error(
|
|
1432
|
+
"PrimeUI health check did not produce an API check result."
|
|
1433
|
+
);
|
|
1434
|
+
}
|
|
1435
|
+
const report = {
|
|
1436
|
+
version: {
|
|
1437
|
+
currentVersion: metadata.version,
|
|
1438
|
+
latestVersion: versionResult.latestVersion,
|
|
1439
|
+
updateStatus: versionResult.status,
|
|
1440
|
+
detail: versionResult.detail
|
|
1441
|
+
},
|
|
1442
|
+
runtimeContext: {
|
|
1443
|
+
cwd,
|
|
1444
|
+
inputProjectRoot,
|
|
1445
|
+
envProjectRoot,
|
|
1446
|
+
envApiBaseUrl: env.PRIMEUI_API_BASE_URL?.trim() || void 0,
|
|
1447
|
+
envApiKeySet: Boolean(env.PRIMEUI_API_KEY?.trim())
|
|
1448
|
+
},
|
|
1449
|
+
sourceResolution: {
|
|
1450
|
+
projectRoot: {
|
|
1451
|
+
inputPath: inputProjectRoot,
|
|
1452
|
+
envPath: envProjectRoot,
|
|
1453
|
+
comparison: compareProjectRoots(inputProjectRoot, envProjectRoot),
|
|
1454
|
+
effectiveSource: mapProjectSource(
|
|
1455
|
+
resolvedProjectConfig?.source ?? projectConfigError?.details.source
|
|
1456
|
+
)
|
|
1457
|
+
},
|
|
1458
|
+
apiKey: {
|
|
1459
|
+
envState: apiKeyDetails.envProvided ? "set" : "missing",
|
|
1460
|
+
configState: configApiKeyState,
|
|
1461
|
+
comparison: compareApiKeys(
|
|
1462
|
+
apiKeyDetails.valuesMatch,
|
|
1463
|
+
apiKeyDetails.envProvided,
|
|
1464
|
+
apiKeyDetails.configProvided
|
|
1465
|
+
),
|
|
1466
|
+
effectiveSource: apiKeyDetails.source
|
|
1467
|
+
}
|
|
1468
|
+
},
|
|
1469
|
+
projectResolution: {
|
|
1470
|
+
resolvedProjectRoot,
|
|
1471
|
+
effectiveSource: mapProjectSource(
|
|
1472
|
+
resolvedProjectConfig?.source ?? projectConfigError?.details.source
|
|
1473
|
+
),
|
|
1474
|
+
errorSource: mapProjectErrorSource(
|
|
1475
|
+
projectConfigError?.details.source
|
|
1476
|
+
),
|
|
1477
|
+
primeUiDirPath,
|
|
1478
|
+
primeUiDirStatus,
|
|
1479
|
+
configPath,
|
|
1480
|
+
configStatus,
|
|
1481
|
+
targetProjectPath: resolvedProjectConfig?.projectConfig.targetProjectPath,
|
|
1482
|
+
targetProjectRoot: resolvedProjectConfig ? resolvePrimeUiTargetProjectRoot(
|
|
1483
|
+
resolvedProjectConfig.projectRoot,
|
|
1484
|
+
resolvedProjectConfig.projectConfig
|
|
1485
|
+
) : void 0,
|
|
1486
|
+
errorCode: projectConfigError?.code,
|
|
1487
|
+
errorMessage: projectConfigError?.message,
|
|
1488
|
+
inputProjectRootValidated: Boolean(inputProjectRoot) && resolvedProjectConfig?.source === "tool"
|
|
1489
|
+
},
|
|
1490
|
+
nestedSearch: {
|
|
1491
|
+
performed: nestedSearchPerformed,
|
|
1492
|
+
rootPath: nestedSearchPerformed ? inputProjectRoot : void 0,
|
|
1493
|
+
candidates: nestedCandidates
|
|
1494
|
+
},
|
|
1495
|
+
apiCheck,
|
|
1496
|
+
hints: []
|
|
1497
|
+
};
|
|
1498
|
+
report.hints = buildHealthHints({
|
|
1499
|
+
report,
|
|
1500
|
+
connectApiKey: nestedSearchPerformed && nestedCandidates.length === 0 && apiCheck.status === "passed" ? apiKeyDetails.apiKey : void 0
|
|
1501
|
+
});
|
|
1502
|
+
options.stdout.write(`${formatHealthReport(report)}
|
|
1503
|
+
`);
|
|
1504
|
+
return 0;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
// src/services/project-sync-service.ts
|
|
1508
|
+
import path10 from "path";
|
|
1509
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
1510
|
+
|
|
276
1511
|
// src/services/page-copy-service.ts
|
|
277
|
-
import { readFile as readFile3, readdir, stat as
|
|
278
|
-
import
|
|
1512
|
+
import { readFile as readFile3, readdir as readdir2, stat as stat5, writeFile as writeFile2 } from "fs/promises";
|
|
1513
|
+
import path9 from "path";
|
|
279
1514
|
|
|
280
1515
|
// src/lib/import-graph.ts
|
|
281
|
-
import { readFile as readFile2, stat as
|
|
1516
|
+
import { readFile as readFile2, stat as stat4 } from "fs/promises";
|
|
282
1517
|
import { builtinModules } from "module";
|
|
283
|
-
import
|
|
1518
|
+
import path8 from "path";
|
|
284
1519
|
var INTERNAL_EXTENSIONS = [
|
|
285
1520
|
".ts",
|
|
286
1521
|
".tsx",
|
|
@@ -349,14 +1584,14 @@ function getExternalPackageName(specifier) {
|
|
|
349
1584
|
}
|
|
350
1585
|
async function pathExists(filePath) {
|
|
351
1586
|
try {
|
|
352
|
-
await
|
|
1587
|
+
await stat4(filePath);
|
|
353
1588
|
return true;
|
|
354
1589
|
} catch {
|
|
355
1590
|
return false;
|
|
356
1591
|
}
|
|
357
1592
|
}
|
|
358
1593
|
async function resolveFileCandidate(candidateBase) {
|
|
359
|
-
const ext =
|
|
1594
|
+
const ext = path8.extname(candidateBase);
|
|
360
1595
|
if (ext) {
|
|
361
1596
|
if (await pathExists(candidateBase)) {
|
|
362
1597
|
return candidateBase;
|
|
@@ -370,13 +1605,13 @@ async function resolveFileCandidate(candidateBase) {
|
|
|
370
1605
|
}
|
|
371
1606
|
}
|
|
372
1607
|
if (await pathExists(candidateBase)) {
|
|
373
|
-
const stats = await
|
|
1608
|
+
const stats = await stat4(candidateBase);
|
|
374
1609
|
if (stats.isFile()) {
|
|
375
1610
|
return candidateBase;
|
|
376
1611
|
}
|
|
377
1612
|
if (stats.isDirectory()) {
|
|
378
1613
|
for (const extension of INTERNAL_EXTENSIONS) {
|
|
379
|
-
const indexCandidate =
|
|
1614
|
+
const indexCandidate = path8.join(candidateBase, `index${extension}`);
|
|
380
1615
|
if (await pathExists(indexCandidate)) {
|
|
381
1616
|
return indexCandidate;
|
|
382
1617
|
}
|
|
@@ -398,13 +1633,13 @@ async function resolveImportToFile(projectRoot, importerFilePath, specifier) {
|
|
|
398
1633
|
}
|
|
399
1634
|
let candidateBase = null;
|
|
400
1635
|
if (specifier.startsWith("@/")) {
|
|
401
|
-
candidateBase =
|
|
1636
|
+
candidateBase = path8.join(projectRoot, "src", specifier.slice(2));
|
|
402
1637
|
} else if (specifier.startsWith("@root/")) {
|
|
403
|
-
candidateBase =
|
|
1638
|
+
candidateBase = path8.join(projectRoot, specifier.slice(6));
|
|
404
1639
|
} else if (specifier.startsWith(".")) {
|
|
405
|
-
candidateBase =
|
|
1640
|
+
candidateBase = path8.resolve(path8.dirname(importerFilePath), specifier);
|
|
406
1641
|
} else if (specifier.startsWith("/")) {
|
|
407
|
-
candidateBase =
|
|
1642
|
+
candidateBase = path8.join(projectRoot, specifier.slice(1));
|
|
408
1643
|
} else {
|
|
409
1644
|
return { kind: "unknown" };
|
|
410
1645
|
}
|
|
@@ -418,12 +1653,12 @@ async function resolveImportToFile(projectRoot, importerFilePath, specifier) {
|
|
|
418
1653
|
};
|
|
419
1654
|
}
|
|
420
1655
|
function shouldParseFile(filePath) {
|
|
421
|
-
return PARSEABLE_EXTENSIONS.has(
|
|
1656
|
+
return PARSEABLE_EXTENSIONS.has(path8.extname(filePath).toLowerCase());
|
|
422
1657
|
}
|
|
423
1658
|
async function buildImportGraph(input) {
|
|
424
1659
|
const shouldFollowInternalImports = input.followInternalImports ?? true;
|
|
425
|
-
const projectRoot =
|
|
426
|
-
const queue = input.entryFiles.map((filePath) =>
|
|
1660
|
+
const projectRoot = path8.resolve(input.projectRoot);
|
|
1661
|
+
const queue = input.entryFiles.map((filePath) => path8.resolve(filePath));
|
|
427
1662
|
const visited = /* @__PURE__ */ new Set();
|
|
428
1663
|
const internalFiles = /* @__PURE__ */ new Set();
|
|
429
1664
|
const externalPackages = /* @__PURE__ */ new Set();
|
|
@@ -676,10 +1911,10 @@ function normalizeSlug2(slug) {
|
|
|
676
1911
|
return withLeadingSlash.replace(/\/+$/, "") || "/";
|
|
677
1912
|
}
|
|
678
1913
|
function toPosixPath(value) {
|
|
679
|
-
return value.split(
|
|
1914
|
+
return value.split(path9.sep).join("/");
|
|
680
1915
|
}
|
|
681
1916
|
function toProjectRelative(rootPath, absolutePath) {
|
|
682
|
-
return toPosixPath(
|
|
1917
|
+
return toPosixPath(path9.relative(rootPath, absolutePath));
|
|
683
1918
|
}
|
|
684
1919
|
function escapeRegExp(value) {
|
|
685
1920
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -736,7 +1971,7 @@ function buildPlannedSourceBuffer(sourceBuffer, sourceFilePath, importRewritePla
|
|
|
736
1971
|
if (!importRewritePlan) {
|
|
737
1972
|
return sourceBuffer;
|
|
738
1973
|
}
|
|
739
|
-
const extension =
|
|
1974
|
+
const extension = path9.extname(sourceFilePath).toLowerCase();
|
|
740
1975
|
if (!REWRITABLE_IMPORT_EXTENSIONS.has(extension)) {
|
|
741
1976
|
return sourceBuffer;
|
|
742
1977
|
}
|
|
@@ -759,7 +1994,7 @@ async function readJsonFile(filePath) {
|
|
|
759
1994
|
}
|
|
760
1995
|
async function fileExists2(filePath) {
|
|
761
1996
|
try {
|
|
762
|
-
await
|
|
1997
|
+
await stat5(filePath);
|
|
763
1998
|
return true;
|
|
764
1999
|
} catch {
|
|
765
2000
|
return false;
|
|
@@ -838,14 +2073,14 @@ async function resolveManifestCandidateFiles(input) {
|
|
|
838
2073
|
);
|
|
839
2074
|
}
|
|
840
2075
|
const normalizedRelative = toPosixPath(trimmed).replace(/^\.\/+/, "");
|
|
841
|
-
const absolutePath =
|
|
842
|
-
const relative =
|
|
843
|
-
if (relative.startsWith("..") ||
|
|
2076
|
+
const absolutePath = path9.resolve(input.exportPath, normalizedRelative);
|
|
2077
|
+
const relative = path9.relative(input.exportPath, absolutePath);
|
|
2078
|
+
if (relative.startsWith("..") || path9.isAbsolute(relative)) {
|
|
844
2079
|
throw new Error(
|
|
845
2080
|
`Export manifest for page "${input.pageSlug}" contains path outside export root: ${trimmed}`
|
|
846
2081
|
);
|
|
847
2082
|
}
|
|
848
|
-
const stats = await
|
|
2083
|
+
const stats = await stat5(absolutePath).catch(() => null);
|
|
849
2084
|
if (!stats?.isFile()) {
|
|
850
2085
|
throw new Error(
|
|
851
2086
|
`Export manifest file is missing for page "${input.pageSlug}": ${normalizedRelative}`
|
|
@@ -856,7 +2091,7 @@ async function resolveManifestCandidateFiles(input) {
|
|
|
856
2091
|
return [...result].sort((a, b) => a.localeCompare(b));
|
|
857
2092
|
}
|
|
858
2093
|
async function resolveSingleExportDirectory(exportsRoot) {
|
|
859
|
-
const entries = await
|
|
2094
|
+
const entries = await readdir2(exportsRoot, { withFileTypes: true });
|
|
860
2095
|
const exportDirectories = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort((a, b) => a.localeCompare(b));
|
|
861
2096
|
if (exportDirectories.length === 0) {
|
|
862
2097
|
throw new Error(
|
|
@@ -871,12 +2106,12 @@ async function resolveSingleExportDirectory(exportsRoot) {
|
|
|
871
2106
|
const exportId = exportDirectories[0] ?? "";
|
|
872
2107
|
return {
|
|
873
2108
|
exportId,
|
|
874
|
-
exportPath:
|
|
2109
|
+
exportPath: path9.join(exportsRoot, exportId)
|
|
875
2110
|
};
|
|
876
2111
|
}
|
|
877
2112
|
function ensureSafeTargetPath(projectRoot, targetPath) {
|
|
878
|
-
const relative =
|
|
879
|
-
if (relative.startsWith("..") ||
|
|
2113
|
+
const relative = path9.relative(projectRoot, targetPath);
|
|
2114
|
+
if (relative.startsWith("..") || path9.isAbsolute(relative)) {
|
|
880
2115
|
throw new Error(
|
|
881
2116
|
`Refusing to write outside project root. Computed target: ${targetPath}`
|
|
882
2117
|
);
|
|
@@ -934,19 +2169,19 @@ function resolveTargetFilePath(input) {
|
|
|
934
2169
|
if (sourceFilePath === sourcePagePath) {
|
|
935
2170
|
return targetPagePath;
|
|
936
2171
|
}
|
|
937
|
-
const componentsPrefix = `${sourceComponentsPath}${
|
|
2172
|
+
const componentsPrefix = `${sourceComponentsPath}${path9.sep}`;
|
|
938
2173
|
if (sourceFilePath === sourceComponentsPath || sourceFilePath.startsWith(componentsPrefix)) {
|
|
939
|
-
const relativeToComponents =
|
|
2174
|
+
const relativeToComponents = path9.relative(
|
|
940
2175
|
sourceComponentsPath,
|
|
941
2176
|
sourceFilePath
|
|
942
2177
|
);
|
|
943
|
-
return
|
|
2178
|
+
return path9.join(targetComponentsPath, relativeToComponents);
|
|
944
2179
|
}
|
|
945
|
-
const relativeToExport =
|
|
946
|
-
if (relativeToExport.startsWith("..") ||
|
|
2180
|
+
const relativeToExport = path9.relative(exportRoot, sourceFilePath);
|
|
2181
|
+
if (relativeToExport.startsWith("..") || path9.isAbsolute(relativeToExport)) {
|
|
947
2182
|
throw new Error(`Source file is outside export root: ${sourceFilePath}`);
|
|
948
2183
|
}
|
|
949
|
-
return
|
|
2184
|
+
return path9.join(projectRoot, relativeToExport);
|
|
950
2185
|
}
|
|
951
2186
|
async function copyPageFromExport(input) {
|
|
952
2187
|
const normalizedOriginSlug = normalizeSlug2(input.originPageSlug);
|
|
@@ -957,7 +2192,7 @@ async function copyPageFromExport(input) {
|
|
|
957
2192
|
const { exportId, exportPath } = await resolveSingleExportDirectory(
|
|
958
2193
|
input.exportsRoot
|
|
959
2194
|
);
|
|
960
|
-
const manifestPath =
|
|
2195
|
+
const manifestPath = path9.join(
|
|
961
2196
|
input.exportsRoot,
|
|
962
2197
|
`${exportId}.manifest.json`
|
|
963
2198
|
);
|
|
@@ -980,13 +2215,13 @@ async function copyPageFromExport(input) {
|
|
|
980
2215
|
`Page not found in manifest for slug: ${normalizedOriginSlug}`
|
|
981
2216
|
);
|
|
982
2217
|
}
|
|
983
|
-
const sourcePagePath =
|
|
984
|
-
const sourceComponentsPath =
|
|
985
|
-
const sourcePageStats = await
|
|
2218
|
+
const sourcePagePath = path9.join(exportPath, page.pagePath);
|
|
2219
|
+
const sourceComponentsPath = path9.join(exportPath, page.componentsPath);
|
|
2220
|
+
const sourcePageStats = await stat5(sourcePagePath).catch(() => null);
|
|
986
2221
|
if (!sourcePageStats?.isFile()) {
|
|
987
2222
|
throw new Error(`Source page file not found: ${sourcePagePath}`);
|
|
988
2223
|
}
|
|
989
|
-
const sourceComponentsStats = await
|
|
2224
|
+
const sourceComponentsStats = await stat5(sourceComponentsPath).catch(
|
|
990
2225
|
() => null
|
|
991
2226
|
);
|
|
992
2227
|
if (!sourceComponentsStats?.isDirectory()) {
|
|
@@ -1001,8 +2236,8 @@ async function copyPageFromExport(input) {
|
|
|
1001
2236
|
pageType: page.pageType,
|
|
1002
2237
|
slug: normalizedActualSlug
|
|
1003
2238
|
});
|
|
1004
|
-
const targetPagePath =
|
|
1005
|
-
const targetComponentsPath =
|
|
2239
|
+
const targetPagePath = path9.join(input.projectRoot, targetPaths.pagePath);
|
|
2240
|
+
const targetComponentsPath = path9.join(
|
|
1006
2241
|
input.projectRoot,
|
|
1007
2242
|
targetPaths.componentsPath
|
|
1008
2243
|
);
|
|
@@ -1048,7 +2283,7 @@ async function copyPageFromExport(input) {
|
|
|
1048
2283
|
const sourceRelative = toProjectRelative(exportPath, sourceFilePath);
|
|
1049
2284
|
const targetRelative = toProjectRelative(input.projectRoot, targetFilePath);
|
|
1050
2285
|
if (!targetBuffer) {
|
|
1051
|
-
await ensureDir(
|
|
2286
|
+
await ensureDir(path9.dirname(targetFilePath));
|
|
1052
2287
|
await writeFile2(targetFilePath, plannedSourceBuffer);
|
|
1053
2288
|
newFiles.push({
|
|
1054
2289
|
sourcePath: sourceRelative,
|
|
@@ -1077,8 +2312,8 @@ async function copyPageFromExport(input) {
|
|
|
1077
2312
|
isBinary
|
|
1078
2313
|
});
|
|
1079
2314
|
}
|
|
1080
|
-
const exportPackageJsonPath =
|
|
1081
|
-
const userPackageJsonPath =
|
|
2315
|
+
const exportPackageJsonPath = path9.join(exportPath, "package.json");
|
|
2316
|
+
const userPackageJsonPath = path9.join(input.projectRoot, "package.json");
|
|
1082
2317
|
if (!await fileExists2(exportPackageJsonPath)) {
|
|
1083
2318
|
throw new Error(`Export package.json not found: ${exportPackageJsonPath}`);
|
|
1084
2319
|
}
|
|
@@ -1284,7 +2519,7 @@ function isExportPage(value) {
|
|
|
1284
2519
|
return typeof maybe.id === "string" && (typeof title === "string" || typeof title === "undefined") && typeof maybe.slug === "string" && typeof maybe.pageType === "string" && maybe.isReadyToExport === true && typeof maybe.pagePath === "string" && typeof maybe.componentsPath === "string" && isExportPageManifest(maybe.manifest);
|
|
1285
2520
|
}
|
|
1286
2521
|
function buildManifestPath(exportsRoot, exportId) {
|
|
1287
|
-
return
|
|
2522
|
+
return path10.join(exportsRoot, `${exportId}.manifest.json`);
|
|
1288
2523
|
}
|
|
1289
2524
|
function parseExportManifest(value) {
|
|
1290
2525
|
if (!value || typeof value !== "object") {
|
|
@@ -1328,9 +2563,9 @@ var ProjectSyncService = class {
|
|
|
1328
2563
|
this.projectRoot = options.projectRoot;
|
|
1329
2564
|
this.targetProjectRoot = options.targetProjectRoot;
|
|
1330
2565
|
this.provider = options.provider;
|
|
1331
|
-
this.primeUiRoot =
|
|
1332
|
-
this.tempRoot =
|
|
1333
|
-
this.exportsRoot =
|
|
2566
|
+
this.primeUiRoot = path10.join(this.projectRoot, ".primeui");
|
|
2567
|
+
this.tempRoot = path10.join(this.primeUiRoot, "temp");
|
|
2568
|
+
this.exportsRoot = path10.join(this.tempRoot, "exports");
|
|
1334
2569
|
}
|
|
1335
2570
|
async getProjectInfo(_context) {
|
|
1336
2571
|
await this.ensureTempLayout();
|
|
@@ -1355,8 +2590,8 @@ var ProjectSyncService = class {
|
|
|
1355
2590
|
if (!selected) {
|
|
1356
2591
|
throw new Error(`Export not found: ${id}`);
|
|
1357
2592
|
}
|
|
1358
|
-
const targetZipPath =
|
|
1359
|
-
const targetProjectPath =
|
|
2593
|
+
const targetZipPath = path10.join(this.exportsRoot, `${id}.zip`);
|
|
2594
|
+
const targetProjectPath = path10.join(this.exportsRoot, id);
|
|
1360
2595
|
const manifestPath = buildManifestPath(this.exportsRoot, id);
|
|
1361
2596
|
let manifest;
|
|
1362
2597
|
try {
|
|
@@ -1416,319 +2651,6 @@ var ProjectSyncService = class {
|
|
|
1416
2651
|
}
|
|
1417
2652
|
};
|
|
1418
2653
|
|
|
1419
|
-
// src/sources/api-provider.ts
|
|
1420
|
-
import { createWriteStream } from "fs";
|
|
1421
|
-
import { unlink } from "fs/promises";
|
|
1422
|
-
import path6 from "path";
|
|
1423
|
-
import { Readable, Transform } from "stream";
|
|
1424
|
-
import { pipeline } from "stream/promises";
|
|
1425
|
-
import { z as z2 } from "zod";
|
|
1426
|
-
var DEFAULT_API_BASE_URL = "https://app.primeui.com/";
|
|
1427
|
-
var ZIP_CONTENT_TYPES = [
|
|
1428
|
-
"application/zip",
|
|
1429
|
-
"application/octet-stream",
|
|
1430
|
-
"application/x-zip-compressed"
|
|
1431
|
-
];
|
|
1432
|
-
var exportStatusSchema = z2.enum(["in_progress", "completed", "failed"]);
|
|
1433
|
-
var projectPageObjectSchema = z2.object({
|
|
1434
|
-
id: z2.string(),
|
|
1435
|
-
title: z2.string(),
|
|
1436
|
-
slug: z2.string(),
|
|
1437
|
-
pageType: z2.string(),
|
|
1438
|
-
isReadyToExport: z2.boolean(),
|
|
1439
|
-
pagePath: z2.string(),
|
|
1440
|
-
componentsPath: z2.string()
|
|
1441
|
-
});
|
|
1442
|
-
var projectPageSchema = projectPageObjectSchema;
|
|
1443
|
-
var exportSummarySchema = z2.object({
|
|
1444
|
-
total: z2.number(),
|
|
1445
|
-
successful: z2.number(),
|
|
1446
|
-
failed: z2.number()
|
|
1447
|
-
});
|
|
1448
|
-
var exportedComponentSchema = z2.object({
|
|
1449
|
-
componentKey: z2.string(),
|
|
1450
|
-
enabled: z2.boolean(),
|
|
1451
|
-
files: z2.array(z2.string()),
|
|
1452
|
-
message: z2.string()
|
|
1453
|
-
});
|
|
1454
|
-
var exportPageManifestSchema = z2.object({
|
|
1455
|
-
success: z2.boolean(),
|
|
1456
|
-
message: z2.string(),
|
|
1457
|
-
files: z2.array(z2.string())
|
|
1458
|
-
});
|
|
1459
|
-
var exportPageSchema = z2.object({
|
|
1460
|
-
id: z2.string(),
|
|
1461
|
-
title: z2.string().optional(),
|
|
1462
|
-
slug: z2.string(),
|
|
1463
|
-
pageType: z2.string(),
|
|
1464
|
-
isReadyToExport: z2.literal(true),
|
|
1465
|
-
pagePath: z2.string(),
|
|
1466
|
-
componentsPath: z2.string(),
|
|
1467
|
-
manifest: exportPageManifestSchema
|
|
1468
|
-
});
|
|
1469
|
-
var projectInfoSchema = z2.object({
|
|
1470
|
-
projectId: z2.string(),
|
|
1471
|
-
projectName: z2.string(),
|
|
1472
|
-
metadata: z2.record(z2.unknown()),
|
|
1473
|
-
pages: z2.array(projectPageSchema)
|
|
1474
|
-
});
|
|
1475
|
-
var projectPageComponentSchema = z2.object({
|
|
1476
|
-
blockId: z2.string(),
|
|
1477
|
-
componentId: z2.string(),
|
|
1478
|
-
componentGroup: z2.string(),
|
|
1479
|
-
slot: z2.string().nullable(),
|
|
1480
|
-
props: z2.record(z2.unknown()).nullable()
|
|
1481
|
-
});
|
|
1482
|
-
var projectPageDetailsSchema = z2.object({
|
|
1483
|
-
page: projectPageObjectSchema.extend({
|
|
1484
|
-
pageInstruction: z2.string().nullable()
|
|
1485
|
-
}),
|
|
1486
|
-
variant: z2.object({
|
|
1487
|
-
id: z2.string(),
|
|
1488
|
-
name: z2.string()
|
|
1489
|
-
}).nullable(),
|
|
1490
|
-
components: z2.array(projectPageComponentSchema).nullable()
|
|
1491
|
-
});
|
|
1492
|
-
var exportsResponseSchema = z2.object({
|
|
1493
|
-
exports: z2.array(
|
|
1494
|
-
z2.object({
|
|
1495
|
-
id: z2.string(),
|
|
1496
|
-
status: exportStatusSchema,
|
|
1497
|
-
createdAt: z2.string().datetime({ offset: true })
|
|
1498
|
-
})
|
|
1499
|
-
)
|
|
1500
|
-
});
|
|
1501
|
-
var createExportResponseSchema = z2.object({
|
|
1502
|
-
export: z2.object({
|
|
1503
|
-
id: z2.string(),
|
|
1504
|
-
status: exportStatusSchema,
|
|
1505
|
-
createdAt: z2.string().datetime({ offset: true }),
|
|
1506
|
-
expiresAt: z2.string().datetime({ offset: true }).nullable(),
|
|
1507
|
-
summary: exportSummarySchema,
|
|
1508
|
-
components: z2.array(exportedComponentSchema)
|
|
1509
|
-
}),
|
|
1510
|
-
pages: z2.array(exportPageSchema)
|
|
1511
|
-
});
|
|
1512
|
-
var PrimeUiApiContractError = class extends Error {
|
|
1513
|
-
constructor(endpoint, details) {
|
|
1514
|
-
super(`PrimeUI API contract mismatch for "${endpoint}": ${details}`);
|
|
1515
|
-
this.name = "PrimeUiApiContractError";
|
|
1516
|
-
}
|
|
1517
|
-
};
|
|
1518
|
-
var normalizeApiRoot = (rawBaseUrl) => {
|
|
1519
|
-
const normalizedBase = rawBaseUrl.trim() || DEFAULT_API_BASE_URL;
|
|
1520
|
-
const parsed = new URL(normalizedBase);
|
|
1521
|
-
const normalizedPath = parsed.pathname.replace(/\/+$/, "");
|
|
1522
|
-
if (normalizedPath.endsWith("/api/v1")) {
|
|
1523
|
-
parsed.pathname = `${normalizedPath}/`;
|
|
1524
|
-
return parsed.toString();
|
|
1525
|
-
}
|
|
1526
|
-
parsed.pathname = `${normalizedPath}/api/v1/`.replace(
|
|
1527
|
-
"//api/v1/",
|
|
1528
|
-
"/api/v1/"
|
|
1529
|
-
);
|
|
1530
|
-
return parsed.toString();
|
|
1531
|
-
};
|
|
1532
|
-
var isJsonContentType = (contentType) => contentType.includes("application/json") || contentType.includes("+json");
|
|
1533
|
-
var isZipContentType = (contentType) => ZIP_CONTENT_TYPES.some((allowedType) => contentType.includes(allowedType));
|
|
1534
|
-
var looksLikeZipArchive = (buffer) => {
|
|
1535
|
-
if (buffer.length < 4) {
|
|
1536
|
-
return false;
|
|
1537
|
-
}
|
|
1538
|
-
if (buffer[0] !== 80 || buffer[1] !== 75) {
|
|
1539
|
-
return false;
|
|
1540
|
-
}
|
|
1541
|
-
const signature = `${buffer[2]}:${buffer[3]}`;
|
|
1542
|
-
return signature === "3:4" || signature === "5:6" || signature === "7:8";
|
|
1543
|
-
};
|
|
1544
|
-
var createZipSignatureGuard = (endpoint) => {
|
|
1545
|
-
let signature = Buffer.alloc(0);
|
|
1546
|
-
let validated = false;
|
|
1547
|
-
return new Transform({
|
|
1548
|
-
transform(chunk, _encoding, callback) {
|
|
1549
|
-
const chunkBuffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1550
|
-
if (!validated) {
|
|
1551
|
-
const requiredBytes = Math.max(0, 4 - signature.length);
|
|
1552
|
-
if (requiredBytes > 0) {
|
|
1553
|
-
signature = Buffer.concat([
|
|
1554
|
-
signature,
|
|
1555
|
-
chunkBuffer.subarray(0, requiredBytes)
|
|
1556
|
-
]);
|
|
1557
|
-
}
|
|
1558
|
-
if (signature.length >= 4) {
|
|
1559
|
-
if (!looksLikeZipArchive(signature)) {
|
|
1560
|
-
callback(
|
|
1561
|
-
new PrimeUiApiContractError(
|
|
1562
|
-
endpoint,
|
|
1563
|
-
"response body is not a valid zip archive"
|
|
1564
|
-
)
|
|
1565
|
-
);
|
|
1566
|
-
return;
|
|
1567
|
-
}
|
|
1568
|
-
validated = true;
|
|
1569
|
-
}
|
|
1570
|
-
}
|
|
1571
|
-
callback(null, chunkBuffer);
|
|
1572
|
-
},
|
|
1573
|
-
flush(callback) {
|
|
1574
|
-
if (!validated) {
|
|
1575
|
-
callback(
|
|
1576
|
-
new PrimeUiApiContractError(
|
|
1577
|
-
endpoint,
|
|
1578
|
-
"response body is not a valid zip archive"
|
|
1579
|
-
)
|
|
1580
|
-
);
|
|
1581
|
-
return;
|
|
1582
|
-
}
|
|
1583
|
-
callback();
|
|
1584
|
-
}
|
|
1585
|
-
});
|
|
1586
|
-
};
|
|
1587
|
-
var ApiProjectDataProvider = class {
|
|
1588
|
-
apiKey;
|
|
1589
|
-
apiRoot;
|
|
1590
|
-
constructor(options) {
|
|
1591
|
-
this.apiKey = options.apiKey;
|
|
1592
|
-
this.apiRoot = normalizeApiRoot(options.baseUrl ?? DEFAULT_API_BASE_URL);
|
|
1593
|
-
}
|
|
1594
|
-
async getProjectInfo() {
|
|
1595
|
-
return this.requestJson("project", projectInfoSchema);
|
|
1596
|
-
}
|
|
1597
|
-
async getProjectPageBySlug(slug) {
|
|
1598
|
-
const encodedSlug = encodeURIComponent(slug);
|
|
1599
|
-
return this.requestJson(
|
|
1600
|
-
`project/page?slug=${encodedSlug}`,
|
|
1601
|
-
projectPageDetailsSchema
|
|
1602
|
-
);
|
|
1603
|
-
}
|
|
1604
|
-
async listExports() {
|
|
1605
|
-
const response = await this.requestJson(
|
|
1606
|
-
"project/exports",
|
|
1607
|
-
exportsResponseSchema
|
|
1608
|
-
);
|
|
1609
|
-
return response.exports;
|
|
1610
|
-
}
|
|
1611
|
-
async createExport() {
|
|
1612
|
-
return this.requestJson("project/exports", createExportResponseSchema, {
|
|
1613
|
-
method: "POST"
|
|
1614
|
-
});
|
|
1615
|
-
}
|
|
1616
|
-
/**
|
|
1617
|
-
* Consumer-side runtime contract for GET /api/v1/project/exports/:exportId/download.
|
|
1618
|
-
* Producer source of truth: apps/studio/src/app/api/v1/project/exports/[exportId]/download/route.ts
|
|
1619
|
-
* Keep content-type and archive format checks synchronized with the producer response.
|
|
1620
|
-
*/
|
|
1621
|
-
async downloadExportArchive(exportId, destinationPath) {
|
|
1622
|
-
const endpoint = `project/exports/${encodeURIComponent(exportId)}/download`;
|
|
1623
|
-
const apiKey = this.requireApiKey();
|
|
1624
|
-
const response = await fetch(this.buildUrl(endpoint), {
|
|
1625
|
-
method: "GET",
|
|
1626
|
-
headers: {
|
|
1627
|
-
Authorization: `Bearer ${apiKey}`
|
|
1628
|
-
}
|
|
1629
|
-
});
|
|
1630
|
-
if (!response.ok) {
|
|
1631
|
-
const details = await this.readError(response);
|
|
1632
|
-
throw new Error(
|
|
1633
|
-
`PrimeUI API request failed (${response.status}) while downloading export "${exportId}": ${details}`
|
|
1634
|
-
);
|
|
1635
|
-
}
|
|
1636
|
-
const contentType = response.headers.get("content-type") ?? "";
|
|
1637
|
-
if (!isZipContentType(contentType)) {
|
|
1638
|
-
throw new PrimeUiApiContractError(
|
|
1639
|
-
endpoint,
|
|
1640
|
-
`expected zip content-type but got "${contentType || "unknown"}"`
|
|
1641
|
-
);
|
|
1642
|
-
}
|
|
1643
|
-
if (!response.body) {
|
|
1644
|
-
throw new PrimeUiApiContractError(endpoint, "response body is empty");
|
|
1645
|
-
}
|
|
1646
|
-
await ensureDir(path6.dirname(destinationPath));
|
|
1647
|
-
const zipStream = Readable.fromWeb(
|
|
1648
|
-
response.body
|
|
1649
|
-
);
|
|
1650
|
-
const signatureGuard = createZipSignatureGuard(endpoint);
|
|
1651
|
-
const fileStream = createWriteStream(destinationPath);
|
|
1652
|
-
try {
|
|
1653
|
-
await pipeline(zipStream, signatureGuard, fileStream);
|
|
1654
|
-
} catch (error) {
|
|
1655
|
-
await unlink(destinationPath).catch(() => void 0);
|
|
1656
|
-
throw error;
|
|
1657
|
-
}
|
|
1658
|
-
}
|
|
1659
|
-
buildUrl(endpoint) {
|
|
1660
|
-
return new URL(endpoint, this.apiRoot).toString();
|
|
1661
|
-
}
|
|
1662
|
-
async requestJson(endpoint, schema, requestInit = {}) {
|
|
1663
|
-
const apiKey = this.requireApiKey();
|
|
1664
|
-
const method = requestInit.method ?? "GET";
|
|
1665
|
-
const response = await fetch(this.buildUrl(endpoint), {
|
|
1666
|
-
...requestInit,
|
|
1667
|
-
method,
|
|
1668
|
-
headers: {
|
|
1669
|
-
...requestInit.headers ?? {},
|
|
1670
|
-
Authorization: `Bearer ${apiKey}`
|
|
1671
|
-
}
|
|
1672
|
-
});
|
|
1673
|
-
if (!response.ok) {
|
|
1674
|
-
const details = await this.readError(response);
|
|
1675
|
-
throw new Error(
|
|
1676
|
-
`PrimeUI API request failed (${response.status}) for "${endpoint}": ${details}`
|
|
1677
|
-
);
|
|
1678
|
-
}
|
|
1679
|
-
const contentType = response.headers.get("content-type") ?? "";
|
|
1680
|
-
if (!isJsonContentType(contentType)) {
|
|
1681
|
-
throw new PrimeUiApiContractError(
|
|
1682
|
-
endpoint,
|
|
1683
|
-
`expected JSON content-type but got "${contentType || "unknown"}"`
|
|
1684
|
-
);
|
|
1685
|
-
}
|
|
1686
|
-
let payload;
|
|
1687
|
-
try {
|
|
1688
|
-
payload = await response.json();
|
|
1689
|
-
} catch {
|
|
1690
|
-
throw new PrimeUiApiContractError(
|
|
1691
|
-
endpoint,
|
|
1692
|
-
"response body is not valid JSON"
|
|
1693
|
-
);
|
|
1694
|
-
}
|
|
1695
|
-
const parsed = schema.safeParse(payload);
|
|
1696
|
-
if (!parsed.success) {
|
|
1697
|
-
const details = parsed.error.issues.map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`).join("; ");
|
|
1698
|
-
throw new PrimeUiApiContractError(endpoint, details);
|
|
1699
|
-
}
|
|
1700
|
-
return parsed.data;
|
|
1701
|
-
}
|
|
1702
|
-
async readError(response) {
|
|
1703
|
-
const contentType = response.headers.get("content-type") ?? "";
|
|
1704
|
-
try {
|
|
1705
|
-
if (isJsonContentType(contentType)) {
|
|
1706
|
-
const body = await response.json();
|
|
1707
|
-
if (body?.message && body?.code) {
|
|
1708
|
-
return `${body.code}: ${body.message}`;
|
|
1709
|
-
}
|
|
1710
|
-
if (body?.message) {
|
|
1711
|
-
return body.message;
|
|
1712
|
-
}
|
|
1713
|
-
} else {
|
|
1714
|
-
const bodyText = await response.text();
|
|
1715
|
-
if (bodyText.trim()) {
|
|
1716
|
-
return bodyText.trim();
|
|
1717
|
-
}
|
|
1718
|
-
}
|
|
1719
|
-
} catch {
|
|
1720
|
-
}
|
|
1721
|
-
return response.statusText || "Unknown error";
|
|
1722
|
-
}
|
|
1723
|
-
requireApiKey() {
|
|
1724
|
-
const apiKey = this.apiKey?.trim();
|
|
1725
|
-
if (!apiKey) {
|
|
1726
|
-
throw new Error("PRIMEUI_API_KEY is required to call PrimeUI API tools.");
|
|
1727
|
-
}
|
|
1728
|
-
return apiKey;
|
|
1729
|
-
}
|
|
1730
|
-
};
|
|
1731
|
-
|
|
1732
2654
|
// src/runtime.ts
|
|
1733
2655
|
function defaultCreateProvider(options) {
|
|
1734
2656
|
return new ApiProjectDataProvider(options);
|
|
@@ -1759,9 +2681,9 @@ var LazyProjectSyncSource = class {
|
|
|
1759
2681
|
projectRootFromEnv: this.env.PRIMEUI_PROJECT_ROOT
|
|
1760
2682
|
});
|
|
1761
2683
|
const projectRoot = resolvedProjectConfig.projectRoot;
|
|
1762
|
-
const targetProjectRoot =
|
|
2684
|
+
const targetProjectRoot = resolvePrimeUiTargetProjectRoot(
|
|
1763
2685
|
projectRoot,
|
|
1764
|
-
resolvedProjectConfig.projectConfig
|
|
2686
|
+
resolvedProjectConfig.projectConfig
|
|
1765
2687
|
);
|
|
1766
2688
|
const apiKey = await resolvePrimeUiApiKey({
|
|
1767
2689
|
projectConfig: resolvedProjectConfig.projectConfig,
|
|
@@ -1823,132 +2745,132 @@ var LazyProjectSyncSource = class {
|
|
|
1823
2745
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
1824
2746
|
|
|
1825
2747
|
// src/instructions.ts
|
|
1826
|
-
import { z as
|
|
1827
|
-
var pageSchema =
|
|
1828
|
-
id:
|
|
2748
|
+
import { z as z4 } from "zod/v3";
|
|
2749
|
+
var pageSchema = z4.object({
|
|
2750
|
+
id: z4.string().describe(
|
|
1829
2751
|
"Stable PrimeUI page identifier. Use this to match the same page across tool responses."
|
|
1830
2752
|
),
|
|
1831
|
-
title:
|
|
2753
|
+
title: z4.string().describe(
|
|
1832
2754
|
"Human-readable page title. Show this to the user when confirming which pages to import."
|
|
1833
2755
|
),
|
|
1834
|
-
slug:
|
|
2756
|
+
slug: z4.string().describe(
|
|
1835
2757
|
"PrimeUI route slug, for example '/', '/pricing', '/docs/getting-started'. Use to match user intent."
|
|
1836
2758
|
),
|
|
1837
|
-
pageType:
|
|
2759
|
+
pageType: z4.string().describe(
|
|
1838
2760
|
"PrimeUI page type classification (for example landing, docs, pricing, blogIndex, legal)."
|
|
1839
2761
|
),
|
|
1840
|
-
isReadyToExport:
|
|
2762
|
+
isReadyToExport: z4.boolean().describe(
|
|
1841
2763
|
"True when this page has an active export-ready variant. Only ready pages are expected in export output."
|
|
1842
2764
|
),
|
|
1843
|
-
pagePath:
|
|
2765
|
+
pagePath: z4.string().describe(
|
|
1844
2766
|
"Relative file path to the page source inside downloaded export root. Copy page code from this exact path."
|
|
1845
2767
|
),
|
|
1846
|
-
componentsPath:
|
|
2768
|
+
componentsPath: z4.string().describe(
|
|
1847
2769
|
"Relative directory path inside downloaded export root where page-level components live. Use as dependency copy start."
|
|
1848
2770
|
)
|
|
1849
2771
|
}).describe(
|
|
1850
2772
|
"PrimeUI page descriptor used by project and export tools. Paths are always relative to export project root."
|
|
1851
2773
|
);
|
|
1852
|
-
var exportStatusSchema2 =
|
|
2774
|
+
var exportStatusSchema2 = z4.enum(["in_progress", "completed", "failed"]).describe(
|
|
1853
2775
|
"Export lifecycle state. Use 'completed' before calling download_export."
|
|
1854
2776
|
);
|
|
1855
|
-
var exportItemSchema =
|
|
1856
|
-
id:
|
|
2777
|
+
var exportItemSchema = z4.object({
|
|
2778
|
+
id: z4.string().describe(
|
|
1857
2779
|
"Export identifier. Pass this value to download_export input.id."
|
|
1858
2780
|
),
|
|
1859
2781
|
status: exportStatusSchema2,
|
|
1860
|
-
createdAt:
|
|
2782
|
+
createdAt: z4.string().describe("Export creation timestamp in ISO-8601 UTC format.")
|
|
1861
2783
|
}).describe("Single export record from PrimeUI export history.");
|
|
1862
|
-
var exportSummarySchema2 =
|
|
1863
|
-
total:
|
|
1864
|
-
successful:
|
|
1865
|
-
failed:
|
|
2784
|
+
var exportSummarySchema2 = z4.object({
|
|
2785
|
+
total: z4.number().describe("Total pages processed in this export run."),
|
|
2786
|
+
successful: z4.number().describe("Number of pages exported successfully."),
|
|
2787
|
+
failed: z4.number().describe("Number of pages that failed export.")
|
|
1866
2788
|
});
|
|
1867
|
-
var exportComponentSchema =
|
|
1868
|
-
componentKey:
|
|
1869
|
-
enabled:
|
|
1870
|
-
files:
|
|
1871
|
-
message:
|
|
2789
|
+
var exportComponentSchema = z4.object({
|
|
2790
|
+
componentKey: z4.string().describe("Global component key from export manifest."),
|
|
2791
|
+
enabled: z4.boolean().describe("True when this global component was exported."),
|
|
2792
|
+
files: z4.array(z4.string()).describe("Files associated with this global component."),
|
|
2793
|
+
message: z4.string().describe("Export status message for this global component.")
|
|
1872
2794
|
});
|
|
1873
|
-
var exportPageManifestSchema2 =
|
|
1874
|
-
success:
|
|
1875
|
-
message:
|
|
1876
|
-
files:
|
|
2795
|
+
var exportPageManifestSchema2 = z4.object({
|
|
2796
|
+
success: z4.boolean().describe("True when page export succeeded in this export run."),
|
|
2797
|
+
message: z4.string().describe("Export status message for this page."),
|
|
2798
|
+
files: z4.array(z4.string()).describe(
|
|
1877
2799
|
"Authoritative list of page-related files from PrimeUI export manifest, including shared project files required by this page."
|
|
1878
2800
|
)
|
|
1879
2801
|
});
|
|
1880
|
-
var exportPageSchema2 =
|
|
1881
|
-
id:
|
|
1882
|
-
title:
|
|
2802
|
+
var exportPageSchema2 = z4.object({
|
|
2803
|
+
id: z4.string().describe("Stable PrimeUI page identifier for this export entry."),
|
|
2804
|
+
title: z4.string().optional().describe(
|
|
1883
2805
|
"Optional page title from export manifest. Can be undefined for some legacy records."
|
|
1884
2806
|
),
|
|
1885
|
-
slug:
|
|
1886
|
-
pageType:
|
|
1887
|
-
isReadyToExport:
|
|
1888
|
-
pagePath:
|
|
1889
|
-
componentsPath:
|
|
2807
|
+
slug: z4.string().describe("PrimeUI page slug included in this export."),
|
|
2808
|
+
pageType: z4.string().describe("PrimeUI page type for routing/path resolution."),
|
|
2809
|
+
isReadyToExport: z4.literal(true).describe("Always true for pages included in export payload."),
|
|
2810
|
+
pagePath: z4.string().describe("Page source file path relative to export root."),
|
|
2811
|
+
componentsPath: z4.string().describe("Page components directory path relative to export root."),
|
|
1890
2812
|
manifest: exportPageManifestSchema2
|
|
1891
2813
|
});
|
|
1892
|
-
var fileTransferSchema =
|
|
1893
|
-
sourcePath:
|
|
1894
|
-
targetPath:
|
|
2814
|
+
var fileTransferSchema = z4.object({
|
|
2815
|
+
sourcePath: z4.string().describe("Relative source file path inside downloaded export root."),
|
|
2816
|
+
targetPath: z4.string().describe("Relative target file path inside user project root.")
|
|
1895
2817
|
});
|
|
1896
2818
|
var conflictFileSchema = fileTransferSchema.extend({
|
|
1897
|
-
diff:
|
|
2819
|
+
diff: z4.string().describe(
|
|
1898
2820
|
"Unified diff between user file and export file. For binary files the diff contains a plain message."
|
|
1899
2821
|
),
|
|
1900
|
-
isBinary:
|
|
2822
|
+
isBinary: z4.boolean().describe("True if conflict comparison was treated as binary data.")
|
|
1901
2823
|
});
|
|
1902
|
-
var dependencySectionSchema =
|
|
2824
|
+
var dependencySectionSchema = z4.enum([
|
|
1903
2825
|
"dependencies",
|
|
1904
2826
|
"devDependencies",
|
|
1905
2827
|
"peerDependencies"
|
|
1906
2828
|
]);
|
|
1907
|
-
var dependencyToAddSchema =
|
|
1908
|
-
packageName:
|
|
1909
|
-
version:
|
|
2829
|
+
var dependencyToAddSchema = z4.object({
|
|
2830
|
+
packageName: z4.string().describe("Dependency package name that was missing in user package.json."),
|
|
2831
|
+
version: z4.string().describe("Version from exported project's package.json."),
|
|
1910
2832
|
section: dependencySectionSchema.describe(
|
|
1911
2833
|
"package.json section where dependency should be added."
|
|
1912
2834
|
)
|
|
1913
2835
|
});
|
|
1914
|
-
var dependencyVersionConflictSchema =
|
|
1915
|
-
packageName:
|
|
2836
|
+
var dependencyVersionConflictSchema = z4.object({
|
|
2837
|
+
packageName: z4.string().describe("Dependency package name with version mismatch."),
|
|
1916
2838
|
section: dependencySectionSchema.describe(
|
|
1917
2839
|
"Section where export expects this dependency."
|
|
1918
2840
|
),
|
|
1919
|
-
exportVersion:
|
|
1920
|
-
userVersion:
|
|
2841
|
+
exportVersion: z4.string().describe("Version found in exported project's package.json."),
|
|
2842
|
+
userVersion: z4.string().describe("Version currently present in user's package.json.")
|
|
1921
2843
|
});
|
|
1922
2844
|
var pageDetailsSchema = pageSchema.extend({
|
|
1923
|
-
pageInstruction:
|
|
2845
|
+
pageInstruction: z4.string().nullable().describe(
|
|
1924
2846
|
"Current instruction text for the active page variant. Null when no active variant is set."
|
|
1925
2847
|
)
|
|
1926
2848
|
});
|
|
1927
|
-
var pageVariantSchema =
|
|
1928
|
-
id:
|
|
1929
|
-
name:
|
|
2849
|
+
var pageVariantSchema = z4.object({
|
|
2850
|
+
id: z4.string().describe("Active variant identifier."),
|
|
2851
|
+
name: z4.string().describe("Active variant display name.")
|
|
1930
2852
|
}).nullable().describe(
|
|
1931
2853
|
"Active page variant. Null when page has no active variant and components are unavailable."
|
|
1932
2854
|
);
|
|
1933
|
-
var pageComponentSchema =
|
|
1934
|
-
blockId:
|
|
1935
|
-
componentId:
|
|
1936
|
-
componentGroup:
|
|
1937
|
-
slot:
|
|
1938
|
-
props:
|
|
2855
|
+
var pageComponentSchema = z4.object({
|
|
2856
|
+
blockId: z4.string().describe("Stable block identifier. Use as primary component instance ID."),
|
|
2857
|
+
componentId: z4.string().describe("Component key from variant blocks payload (block.key)."),
|
|
2858
|
+
componentGroup: z4.string().describe("Component family/group normalized from component key."),
|
|
2859
|
+
slot: z4.string().nullable().describe("Optional slot value from block payload."),
|
|
2860
|
+
props: z4.record(z4.unknown()).nullable().describe("Raw block props payload from PrimeUI.")
|
|
1939
2861
|
});
|
|
1940
|
-
var inspectPageReportRowSchema =
|
|
1941
|
-
number:
|
|
1942
|
-
componentGroup:
|
|
1943
|
-
componentId:
|
|
1944
|
-
blockId:
|
|
1945
|
-
title:
|
|
1946
|
-
description:
|
|
2862
|
+
var inspectPageReportRowSchema = z4.object({
|
|
2863
|
+
number: z4.number().describe("1-based row number matching report order."),
|
|
2864
|
+
componentGroup: z4.string().describe("Component family/group label."),
|
|
2865
|
+
componentId: z4.string().describe("Component key (block key)."),
|
|
2866
|
+
blockId: z4.string().describe("Primary component instance identifier for future operations."),
|
|
2867
|
+
title: z4.string().nullable().describe("Title extracted by MCP from component props."),
|
|
2868
|
+
description: z4.string().nullable().describe("Description extracted by MCP from component props.")
|
|
1947
2869
|
});
|
|
1948
|
-
var projectRootInputSchema =
|
|
2870
|
+
var projectRootInputSchema = z4.string().optional().describe(
|
|
1949
2871
|
"Optional absolute project root path. Use when .primeui/project.json cannot be auto-resolved from current working directory."
|
|
1950
2872
|
);
|
|
1951
|
-
var optionalProjectRootInputObjectSchema =
|
|
2873
|
+
var optionalProjectRootInputObjectSchema = z4.object({
|
|
1952
2874
|
projectRoot: projectRootInputSchema
|
|
1953
2875
|
}).optional();
|
|
1954
2876
|
var TRIGGER_PHRASES = [
|
|
@@ -2032,17 +2954,17 @@ AFTER CALLING:
|
|
|
2032
2954
|
|
|
2033
2955
|
${WORKFLOW_SUMMARY}`,
|
|
2034
2956
|
inputSchema: optionalProjectRootInputObjectSchema,
|
|
2035
|
-
outputSchema:
|
|
2036
|
-
projectId:
|
|
2957
|
+
outputSchema: z4.object({
|
|
2958
|
+
projectId: z4.string().describe(
|
|
2037
2959
|
"PrimeUI project identifier associated with the API key and current import session context."
|
|
2038
2960
|
),
|
|
2039
|
-
projectName:
|
|
2961
|
+
projectName: z4.string().describe(
|
|
2040
2962
|
"Human-readable PrimeUI project name for confirmations in chat."
|
|
2041
2963
|
),
|
|
2042
|
-
metadata:
|
|
2964
|
+
metadata: z4.record(z4.unknown()).describe(
|
|
2043
2965
|
"Additional project metadata from PrimeUI. May include project-description and other context fields."
|
|
2044
2966
|
),
|
|
2045
|
-
pages:
|
|
2967
|
+
pages: z4.array(pageSchema).describe(
|
|
2046
2968
|
"All pages visible for this project context. Filter by user request and isReadyToExport before creating export."
|
|
2047
2969
|
)
|
|
2048
2970
|
}),
|
|
@@ -2073,22 +2995,22 @@ If page has no active variant, components is null (not empty array), and report
|
|
|
2073
2995
|
${WORKFLOW_SUMMARY}`,
|
|
2074
2996
|
inputSchema: {
|
|
2075
2997
|
projectRoot: projectRootInputSchema,
|
|
2076
|
-
pageSlug:
|
|
2998
|
+
pageSlug: z4.string().describe(
|
|
2077
2999
|
"PrimeUI page slug to inspect, for example '/', '/pricing', '/docs/getting-started'."
|
|
2078
3000
|
)
|
|
2079
3001
|
},
|
|
2080
|
-
outputSchema:
|
|
3002
|
+
outputSchema: z4.object({
|
|
2081
3003
|
page: pageDetailsSchema.describe(
|
|
2082
3004
|
"Single PrimeUI page details resolved by slug with pageInstruction."
|
|
2083
3005
|
),
|
|
2084
3006
|
variant: pageVariantSchema,
|
|
2085
|
-
components:
|
|
3007
|
+
components: z4.array(pageComponentSchema).nullable().describe(
|
|
2086
3008
|
"Raw component instances from active variant. Null when page has no active variant."
|
|
2087
3009
|
),
|
|
2088
|
-
report:
|
|
3010
|
+
report: z4.string().describe(
|
|
2089
3011
|
"Formatted report table generated by MCP from component payload (includes extracted title/description summary)."
|
|
2090
3012
|
),
|
|
2091
|
-
reportRows:
|
|
3013
|
+
reportRows: z4.array(inspectPageReportRowSchema).describe(
|
|
2092
3014
|
"Structured report rows with 1-based numbering and extracted title/description fields."
|
|
2093
3015
|
)
|
|
2094
3016
|
}),
|
|
@@ -2114,8 +3036,8 @@ Do NOT use this tool as a substitute for create_export. Always create a fresh ex
|
|
|
2114
3036
|
|
|
2115
3037
|
${WORKFLOW_SUMMARY}`,
|
|
2116
3038
|
inputSchema: optionalProjectRootInputObjectSchema,
|
|
2117
|
-
outputSchema:
|
|
2118
|
-
exports:
|
|
3039
|
+
outputSchema: z4.object({
|
|
3040
|
+
exports: z4.array(exportItemSchema).describe(
|
|
2119
3041
|
"Available exports sorted by API policy. Use item.id to download a specific prior export if user asks."
|
|
2120
3042
|
)
|
|
2121
3043
|
}),
|
|
@@ -2142,24 +3064,24 @@ AFTER CALLING:
|
|
|
2142
3064
|
|
|
2143
3065
|
${WORKFLOW_SUMMARY}`,
|
|
2144
3066
|
inputSchema: optionalProjectRootInputObjectSchema,
|
|
2145
|
-
outputSchema:
|
|
2146
|
-
export:
|
|
2147
|
-
id:
|
|
3067
|
+
outputSchema: z4.object({
|
|
3068
|
+
export: z4.object({
|
|
3069
|
+
id: z4.string().describe(
|
|
2148
3070
|
"Freshly created export identifier. Use this as download_export input.id."
|
|
2149
3071
|
),
|
|
2150
3072
|
status: exportStatusSchema2,
|
|
2151
|
-
createdAt:
|
|
2152
|
-
expiresAt:
|
|
3073
|
+
createdAt: z4.string().describe("Export creation timestamp in ISO-8601 UTC format."),
|
|
3074
|
+
expiresAt: z4.string().nullable().describe(
|
|
2153
3075
|
"Export expiration timestamp in ISO-8601 UTC format, or null when export has no explicit expiration."
|
|
2154
3076
|
),
|
|
2155
3077
|
summary: exportSummarySchema2.describe(
|
|
2156
3078
|
"Export run summary with total/successful/failed counters."
|
|
2157
3079
|
),
|
|
2158
|
-
components:
|
|
3080
|
+
components: z4.array(exportComponentSchema).describe(
|
|
2159
3081
|
"Global component export manifest. These components are independent from page-level manifests."
|
|
2160
3082
|
)
|
|
2161
3083
|
}).describe("Created export summary."),
|
|
2162
|
-
pages:
|
|
3084
|
+
pages: z4.array(exportPageSchema2).describe(
|
|
2163
3085
|
"Strict page payload associated with this export, including per-page manifest files list."
|
|
2164
3086
|
)
|
|
2165
3087
|
}),
|
|
@@ -2193,21 +3115,21 @@ AFTER DOWNLOAD:
|
|
|
2193
3115
|
${WORKFLOW_SUMMARY}`,
|
|
2194
3116
|
inputSchema: {
|
|
2195
3117
|
projectRoot: projectRootInputSchema,
|
|
2196
|
-
id:
|
|
3118
|
+
id: z4.string().describe(
|
|
2197
3119
|
"Export identifier to download. Prefer export.id from create_export; use list_exports only on explicit user request."
|
|
2198
3120
|
)
|
|
2199
3121
|
},
|
|
2200
|
-
outputSchema:
|
|
2201
|
-
exportId:
|
|
3122
|
+
outputSchema: z4.object({
|
|
3123
|
+
exportId: z4.string().describe(
|
|
2202
3124
|
"Downloaded export identifier. Should match the requested input.id for traceability."
|
|
2203
3125
|
),
|
|
2204
|
-
projectPath:
|
|
3126
|
+
projectPath: z4.string().describe(
|
|
2205
3127
|
"Absolute local path to extracted export root in .primeui/temp/exports/[exportId]. Read files from here only."
|
|
2206
3128
|
),
|
|
2207
|
-
manifestPath:
|
|
3129
|
+
manifestPath: z4.string().describe(
|
|
2208
3130
|
"Absolute path to sidecar export manifest file (.primeui/temp/exports/[exportId].manifest.json) saved by create_export and required by copy_page."
|
|
2209
3131
|
),
|
|
2210
|
-
pages:
|
|
3132
|
+
pages: z4.array(exportPageSchema2).describe(
|
|
2211
3133
|
"Page descriptors loaded from local sidecar export manifest for import decisions."
|
|
2212
3134
|
)
|
|
2213
3135
|
}),
|
|
@@ -2243,46 +3165,46 @@ BEHAVIOR:
|
|
|
2243
3165
|
${WORKFLOW_SUMMARY}`,
|
|
2244
3166
|
inputSchema: {
|
|
2245
3167
|
projectRoot: projectRootInputSchema,
|
|
2246
|
-
originPageSlug:
|
|
3168
|
+
originPageSlug: z4.string().describe(
|
|
2247
3169
|
"Source page slug from PrimeUI project pages list, for example '/', '/pricing', '/docs'."
|
|
2248
3170
|
),
|
|
2249
|
-
actualPageSlug:
|
|
3171
|
+
actualPageSlug: z4.string().optional().describe(
|
|
2250
3172
|
"Optional destination slug in user's project. If omitted, originPageSlug is used."
|
|
2251
3173
|
)
|
|
2252
3174
|
},
|
|
2253
|
-
outputSchema:
|
|
2254
|
-
exportId:
|
|
2255
|
-
exportPath:
|
|
2256
|
-
originPageSlug:
|
|
2257
|
-
actualPageSlug:
|
|
2258
|
-
sourcePagePath:
|
|
2259
|
-
targetPagePath:
|
|
2260
|
-
sourceComponentsPath:
|
|
2261
|
-
targetComponentsPath:
|
|
3175
|
+
outputSchema: z4.object({
|
|
3176
|
+
exportId: z4.string().describe("Resolved local export identifier used for copy operation."),
|
|
3177
|
+
exportPath: z4.string().describe("Absolute path to local extracted export project root."),
|
|
3178
|
+
originPageSlug: z4.string().describe("Normalized source page slug requested for copy."),
|
|
3179
|
+
actualPageSlug: z4.string().describe("Normalized destination slug used for target route paths."),
|
|
3180
|
+
sourcePagePath: z4.string().describe("Source page path relative to export root."),
|
|
3181
|
+
targetPagePath: z4.string().describe("Destination page path relative to user project root."),
|
|
3182
|
+
sourceComponentsPath: z4.string().describe("Source components directory relative to export root."),
|
|
3183
|
+
targetComponentsPath: z4.string().describe(
|
|
2262
3184
|
"Destination components directory relative to user project root."
|
|
2263
3185
|
),
|
|
2264
|
-
newFiles:
|
|
2265
|
-
identicalFiles:
|
|
3186
|
+
newFiles: z4.array(fileTransferSchema).describe("New files copied into user project without conflicts."),
|
|
3187
|
+
identicalFiles: z4.array(fileTransferSchema).describe(
|
|
2266
3188
|
"Existing files in user project that are byte-identical to export files."
|
|
2267
3189
|
),
|
|
2268
|
-
conflictFiles:
|
|
3190
|
+
conflictFiles: z4.array(conflictFileSchema).describe(
|
|
2269
3191
|
"Files that already exist and differ from export version. Never overwritten."
|
|
2270
3192
|
),
|
|
2271
|
-
addedDependencies:
|
|
3193
|
+
addedDependencies: z4.array(dependencyToAddSchema).describe(
|
|
2272
3194
|
"Dependencies that were missing and have been added to user's package.json."
|
|
2273
3195
|
),
|
|
2274
|
-
dependenciesVersionConflicts:
|
|
3196
|
+
dependenciesVersionConflicts: z4.array(dependencyVersionConflictSchema).describe(
|
|
2275
3197
|
"Dependencies with version mismatch between export and user project. Report only."
|
|
2276
3198
|
),
|
|
2277
|
-
summary:
|
|
2278
|
-
totalCandidateFiles:
|
|
3199
|
+
summary: z4.object({
|
|
3200
|
+
totalCandidateFiles: z4.number().describe(
|
|
2279
3201
|
"Total number of candidate files considered for page copy from pages[].manifest.files."
|
|
2280
3202
|
),
|
|
2281
|
-
copiedFiles:
|
|
2282
|
-
identicalFiles:
|
|
2283
|
-
conflictFiles:
|
|
2284
|
-
addedDependencies:
|
|
2285
|
-
dependenciesVersionConflicts:
|
|
3203
|
+
copiedFiles: z4.number().describe("Number of files copied as new."),
|
|
3204
|
+
identicalFiles: z4.number().describe("Number of files detected as identical."),
|
|
3205
|
+
conflictFiles: z4.number().describe("Number of conflicting files not copied."),
|
|
3206
|
+
addedDependencies: z4.number().describe("Count of dependencies added to package.json."),
|
|
3207
|
+
dependenciesVersionConflicts: z4.number().describe("Count of dependency version mismatches reported.")
|
|
2286
3208
|
})
|
|
2287
3209
|
}),
|
|
2288
3210
|
annotations: {
|
|
@@ -2304,8 +3226,8 @@ Safe to call multiple times. Only affects .primeui/temp/ directory - never touch
|
|
|
2304
3226
|
|
|
2305
3227
|
${WORKFLOW_SUMMARY}`,
|
|
2306
3228
|
inputSchema: optionalProjectRootInputObjectSchema,
|
|
2307
|
-
outputSchema:
|
|
2308
|
-
success:
|
|
3229
|
+
outputSchema: z4.object({
|
|
3230
|
+
success: z4.boolean().describe(
|
|
2309
3231
|
"True when temp cleanup completed and baseline temp structure was recreated."
|
|
2310
3232
|
)
|
|
2311
3233
|
}),
|
|
@@ -2364,7 +3286,7 @@ function createPrimeUiMcpServer(source) {
|
|
|
2364
3286
|
const server = new McpServer(
|
|
2365
3287
|
{
|
|
2366
3288
|
name: "primeui-mcp-server",
|
|
2367
|
-
version:
|
|
3289
|
+
version: packageMetadata.version
|
|
2368
3290
|
},
|
|
2369
3291
|
{
|
|
2370
3292
|
instructions: initialInstructions
|
|
@@ -2482,12 +3404,28 @@ function createPrimeUiMcpServer(source) {
|
|
|
2482
3404
|
}
|
|
2483
3405
|
|
|
2484
3406
|
// src/service.ts
|
|
2485
|
-
async function
|
|
3407
|
+
async function startPrimeUiMcpServer() {
|
|
2486
3408
|
const source = new LazyProjectSyncSource();
|
|
2487
3409
|
const server = createPrimeUiMcpServer(source);
|
|
2488
3410
|
const transport = new StdioServerTransport();
|
|
2489
3411
|
await server.connect(transport);
|
|
2490
3412
|
}
|
|
3413
|
+
async function main() {
|
|
3414
|
+
const exitCode = await runCli(process.argv.slice(2), {
|
|
3415
|
+
stdout: process.stdout,
|
|
3416
|
+
stderr: process.stderr,
|
|
3417
|
+
startServer: startPrimeUiMcpServer,
|
|
3418
|
+
runHealthCheck: ({ projectRoot }) => runHealthCheck({
|
|
3419
|
+
projectRoot,
|
|
3420
|
+
stdout: process.stdout,
|
|
3421
|
+
env: process.env,
|
|
3422
|
+
cwd: process.cwd()
|
|
3423
|
+
})
|
|
3424
|
+
});
|
|
3425
|
+
if (exitCode !== 0) {
|
|
3426
|
+
process.exitCode = exitCode;
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
2491
3429
|
main().catch((error) => {
|
|
2492
3430
|
console.error("[primeui-mcp] failed to start", error);
|
|
2493
3431
|
process.exit(1);
|