@primeuicom/mcp 0.1.14 → 0.1.16
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 +4 -3
- package/dist/service.js +331 -128
- package/dist/service.js.map +1 -1
- package/package.json +1 -1
package/dist/service.js
CHANGED
|
@@ -74,15 +74,46 @@ async function readPrimeUiProjectConfig(projectConfigPath) {
|
|
|
74
74
|
}
|
|
75
75
|
return parsedConfig.data;
|
|
76
76
|
}
|
|
77
|
+
function toUniqueResolvedDirs(directories) {
|
|
78
|
+
const resolved = /* @__PURE__ */ new Set();
|
|
79
|
+
for (const directory of directories) {
|
|
80
|
+
const trimmed = directory?.trim();
|
|
81
|
+
if (!trimmed) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
resolved.add(path.resolve(trimmed));
|
|
85
|
+
}
|
|
86
|
+
return [...resolved];
|
|
87
|
+
}
|
|
77
88
|
async function resolvePrimeUiProjectConfig(options) {
|
|
78
89
|
const envProjectRoot = options.projectRootFromEnv?.trim();
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
90
|
+
let projectConfigPath;
|
|
91
|
+
if (envProjectRoot) {
|
|
92
|
+
projectConfigPath = path.join(
|
|
93
|
+
path.resolve(envProjectRoot),
|
|
94
|
+
PRIMEUI_PROJECT_CONFIG_RELATIVE_PATH
|
|
95
|
+
);
|
|
96
|
+
} else {
|
|
97
|
+
const searchRoots = toUniqueResolvedDirs([
|
|
98
|
+
options.cwd,
|
|
99
|
+
...options.fallbackCwds ?? []
|
|
100
|
+
]);
|
|
101
|
+
for (const searchRoot of searchRoots) {
|
|
102
|
+
const foundProjectConfigPath = await findPrimeUiProjectConfigPath(searchRoot);
|
|
103
|
+
if (foundProjectConfigPath) {
|
|
104
|
+
projectConfigPath = foundProjectConfigPath;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (!projectConfigPath) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
`[primeui-mcp] ${PRIMEUI_PROJECT_CONFIG_RELATIVE_PATH} not found. Searched from: ${searchRoots.join(", ")}. Set PRIMEUI_PROJECT_ROOT.`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
83
114
|
if (!projectConfigPath) {
|
|
84
115
|
throw new Error(
|
|
85
|
-
`[primeui-mcp] ${PRIMEUI_PROJECT_CONFIG_RELATIVE_PATH} not found
|
|
116
|
+
`[primeui-mcp] ${PRIMEUI_PROJECT_CONFIG_RELATIVE_PATH} not found. Set PRIMEUI_PROJECT_ROOT.`
|
|
86
117
|
);
|
|
87
118
|
}
|
|
88
119
|
const projectRoot = path.dirname(path.dirname(projectConfigPath));
|
|
@@ -147,6 +178,36 @@ var exportItemSchema = z2.object({
|
|
|
147
178
|
status: exportStatusSchema,
|
|
148
179
|
createdAt: z2.string().describe("Export creation timestamp in ISO-8601 UTC format.")
|
|
149
180
|
}).describe("Single export record from PrimeUI export history.");
|
|
181
|
+
var exportSummarySchema = z2.object({
|
|
182
|
+
total: z2.number().describe("Total pages processed in this export run."),
|
|
183
|
+
successful: z2.number().describe("Number of pages exported successfully."),
|
|
184
|
+
failed: z2.number().describe("Number of pages that failed export.")
|
|
185
|
+
});
|
|
186
|
+
var exportComponentSchema = z2.object({
|
|
187
|
+
componentKey: z2.string().describe("Global component key from export manifest."),
|
|
188
|
+
enabled: z2.boolean().describe("True when this global component was exported."),
|
|
189
|
+
files: z2.array(z2.string()).describe("Files associated with this global component."),
|
|
190
|
+
message: z2.string().describe("Export status message for this global component.")
|
|
191
|
+
});
|
|
192
|
+
var exportPageManifestSchema = z2.object({
|
|
193
|
+
success: z2.boolean().describe("True when page export succeeded in this export run."),
|
|
194
|
+
message: z2.string().describe("Export status message for this page."),
|
|
195
|
+
files: z2.array(z2.string()).describe(
|
|
196
|
+
"Authoritative list of page-related files from PrimeUI export manifest, including shared project files required by this page."
|
|
197
|
+
)
|
|
198
|
+
});
|
|
199
|
+
var exportPageSchema = z2.object({
|
|
200
|
+
id: z2.string().describe("Stable PrimeUI page identifier for this export entry."),
|
|
201
|
+
title: z2.string().optional().describe(
|
|
202
|
+
"Optional page title from export manifest. Can be undefined for some legacy records."
|
|
203
|
+
),
|
|
204
|
+
slug: z2.string().describe("PrimeUI page slug included in this export."),
|
|
205
|
+
pageType: z2.string().describe("PrimeUI page type for routing/path resolution."),
|
|
206
|
+
isReadyToExport: z2.literal(true).describe("Always true for pages included in export payload."),
|
|
207
|
+
pagePath: z2.string().describe("Page source file path relative to export root."),
|
|
208
|
+
componentsPath: z2.string().describe("Page components directory path relative to export root."),
|
|
209
|
+
manifest: exportPageManifestSchema
|
|
210
|
+
});
|
|
150
211
|
var fileTransferSchema = z2.object({
|
|
151
212
|
sourcePath: z2.string().describe("Relative source file path inside downloaded export root."),
|
|
152
213
|
targetPath: z2.string().describe("Relative target file path inside user project root.")
|
|
@@ -215,14 +276,14 @@ var TRIGGER_PHRASES = [
|
|
|
215
276
|
].join(", ");
|
|
216
277
|
var WORKFLOW_SUMMARY = `
|
|
217
278
|
WORKFLOW ORDER (always follow this sequence):
|
|
218
|
-
1.
|
|
219
|
-
2.
|
|
220
|
-
3.
|
|
221
|
-
4.
|
|
222
|
-
5.
|
|
223
|
-
6.
|
|
224
|
-
7.
|
|
225
|
-
8.
|
|
279
|
+
1. clear_temp -> cleanup stale temp files from previous runs before starting new import flow
|
|
280
|
+
2. get_project_info -> discover PrimeUI pages
|
|
281
|
+
3. Reconcile PrimeUI pages with local project pages/routes/paths, then confirm target pages with user
|
|
282
|
+
4. inspect_page -> inspect selected page components and produce a structured comparison table
|
|
283
|
+
5. create_export -> generate export snapshot and local manifest with page file lists + global components
|
|
284
|
+
6. download_export -> download to temp directory
|
|
285
|
+
7. copy_page -> copy one page safely (repeat for each selected page)
|
|
286
|
+
8. Reconcile integration points and resolve reported conflicts, if any
|
|
226
287
|
`.trim();
|
|
227
288
|
var initialInstructions = `
|
|
228
289
|
PrimeUI MCP enables importing pages from PrimeUI Studio into your local project.
|
|
@@ -233,6 +294,7 @@ ${WORKFLOW_SUMMARY}
|
|
|
233
294
|
|
|
234
295
|
CRITICAL RULES FOR PAGE IMPORT:
|
|
235
296
|
- Import is PAGE-BY-PAGE only. Never import the entire project at once.
|
|
297
|
+
- Always call clear_temp at the very beginning of a new import flow to avoid stale export state.
|
|
236
298
|
- After get_project_info, always compare PrimeUI pages against the user's existing local project pages before proposing imports.
|
|
237
299
|
- Reconciliation report MUST include: page presence match, slug/pagePath/componentsPath path match, and proposed action per page.
|
|
238
300
|
- Decision matrix:
|
|
@@ -244,6 +306,10 @@ CRITICAL RULES FOR PAGE IMPORT:
|
|
|
244
306
|
- After inspect_page returns report table, supplement that table with local project page state:
|
|
245
307
|
mark what already exists, what is missing, and what differs.
|
|
246
308
|
- Before creating export, ask user which pages to add/update (specific pages or all missing pages) and keep that choice in thread context.
|
|
309
|
+
- create_export response includes:
|
|
310
|
+
- page-level manifest files per page ('pages[].manifest.files'),
|
|
311
|
+
- global components list ('export.components').
|
|
312
|
+
Mention both to user after create_export, because this is the source of truth for copy decisions.
|
|
247
313
|
- The downloaded export in .primeui/temp/ contains a full standalone Next.js project.
|
|
248
314
|
Do NOT copy it wholesale.
|
|
249
315
|
- Always call copy_page for each confirmed page instead of manual file copy.
|
|
@@ -251,6 +317,7 @@ CRITICAL RULES FOR PAGE IMPORT:
|
|
|
251
317
|
- new files are copied,
|
|
252
318
|
- identical files are reported,
|
|
253
319
|
- conflicting files are NEVER overwritten and are returned with diff.
|
|
320
|
+
- copy_page reads and validates files only from 'pages[].manifest.files' in local sidecar manifest.
|
|
254
321
|
- copy_page may add only missing dependencies to user's package.json.
|
|
255
322
|
It never upgrades existing dependency versions and never runs dependency installation.
|
|
256
323
|
- After page copy operations, inspect integration points (navigation configs, link menus, route registries, page indexes).
|
|
@@ -373,7 +440,7 @@ ${WORKFLOW_SUMMARY}`,
|
|
|
373
440
|
};
|
|
374
441
|
var toolCreateExport = {
|
|
375
442
|
title: "PrimeUI Create Export",
|
|
376
|
-
description: `Create a new export snapshot from the current PrimeUI project state and wait for completion. Returns export
|
|
443
|
+
description: `Create a new export snapshot from the current PrimeUI project state and wait for completion. Returns strict export manifest payload from API, including page-level file manifests and global components.
|
|
377
444
|
|
|
378
445
|
WHEN TO USE: Call this AFTER the user has confirmed which pages they want to import via get_project_info. This tool is REQUIRED before download_export, unless the user explicitly and directly asked to download a specific previous export.
|
|
379
446
|
|
|
@@ -381,6 +448,8 @@ AFTER CALLING:
|
|
|
381
448
|
- Verify that all user-selected pages are present in the export result AND have isReadyToExport: true.
|
|
382
449
|
- If path mismatch pages were flagged during project-info reconciliation, ensure they are included for deeper comparison after download.
|
|
383
450
|
- If any required page is missing or not ready, report and pause for user decision before proceeding.
|
|
451
|
+
- Mention that global components are available in export.components and can be transferred separately if needed.
|
|
452
|
+
- Local sidecar manifest is saved to .primeui/temp/exports/[exportId].manifest.json from this API payload.
|
|
384
453
|
- Use the returned export ID to call download_export.
|
|
385
454
|
|
|
386
455
|
${WORKFLOW_SUMMARY}`,
|
|
@@ -390,10 +459,20 @@ ${WORKFLOW_SUMMARY}`,
|
|
|
390
459
|
id: z2.string().describe(
|
|
391
460
|
"Freshly created export identifier. Use this as download_export input.id."
|
|
392
461
|
),
|
|
393
|
-
status: exportStatusSchema
|
|
462
|
+
status: exportStatusSchema,
|
|
463
|
+
createdAt: z2.string().describe("Export creation timestamp in ISO-8601 UTC format."),
|
|
464
|
+
expiresAt: z2.string().nullable().describe(
|
|
465
|
+
"Export expiration timestamp in ISO-8601 UTC format, or null when export has no explicit expiration."
|
|
466
|
+
),
|
|
467
|
+
summary: exportSummarySchema.describe(
|
|
468
|
+
"Export run summary with total/successful/failed counters."
|
|
469
|
+
),
|
|
470
|
+
components: z2.array(exportComponentSchema).describe(
|
|
471
|
+
"Global component export manifest. These components are independent from page-level manifests."
|
|
472
|
+
)
|
|
394
473
|
}).describe("Created export summary."),
|
|
395
|
-
pages: z2.array(
|
|
396
|
-
"
|
|
474
|
+
pages: z2.array(exportPageSchema).describe(
|
|
475
|
+
"Strict page payload associated with this export, including per-page manifest files list."
|
|
397
476
|
)
|
|
398
477
|
},
|
|
399
478
|
annotations: {
|
|
@@ -409,19 +488,19 @@ var toolDownloadExport = {
|
|
|
409
488
|
|
|
410
489
|
Do NOT call this as the first step. Requires export ID from create_export. Start with get_project_info instead.
|
|
411
490
|
|
|
412
|
-
WHEN TO USE: Call this AFTER create_export has returned a completed export. Requires export ID from create_export so MCP can use the matching local
|
|
491
|
+
WHEN TO USE: Call this AFTER create_export has returned a completed export. Requires export ID from create_export so MCP can use the matching local sidecar manifest.
|
|
413
492
|
|
|
414
493
|
NOTE:
|
|
415
|
-
- Download of an export ID without local
|
|
494
|
+
- Download of an export ID without local sidecar manifest will fail.
|
|
416
495
|
- For previous exports, create a fresh export first when possible.
|
|
417
496
|
|
|
418
497
|
AFTER DOWNLOAD:
|
|
419
|
-
- Use manifestPath from this response as the contract for page slug/path mapping in copy operations.
|
|
498
|
+
- Use manifestPath from this response as the contract for page slug/path mapping and manifest file lists in copy operations.
|
|
420
499
|
- For each selected page, call copy_page with:
|
|
421
500
|
- originPageSlug (required),
|
|
422
501
|
- actualPageSlug (optional, if route remap is needed).
|
|
423
502
|
- If there are unresolved conflicts in copy report, stop and ask the user.
|
|
424
|
-
-
|
|
503
|
+
- Do not force cleanup at flow end; keep downloaded export files available for validation and follow-up checks.
|
|
425
504
|
|
|
426
505
|
${WORKFLOW_SUMMARY}`,
|
|
427
506
|
inputSchema: {
|
|
@@ -437,10 +516,10 @@ AFTER DOWNLOAD:
|
|
|
437
516
|
"Absolute local path to extracted export root in .primeui/temp/exports/[exportId]. Read files from here only."
|
|
438
517
|
),
|
|
439
518
|
manifestPath: z2.string().describe(
|
|
440
|
-
"Absolute path to sidecar export manifest file (.primeui/temp/exports/[exportId].manifest.json)
|
|
519
|
+
"Absolute path to sidecar export manifest file (.primeui/temp/exports/[exportId].manifest.json) saved by create_export and required by copy_page."
|
|
441
520
|
),
|
|
442
|
-
pages: z2.array(
|
|
443
|
-
"Page descriptors
|
|
521
|
+
pages: z2.array(exportPageSchema).describe(
|
|
522
|
+
"Page descriptors loaded from local sidecar export manifest for import decisions."
|
|
444
523
|
)
|
|
445
524
|
},
|
|
446
525
|
annotations: {
|
|
@@ -461,7 +540,7 @@ WHEN TO USE:
|
|
|
461
540
|
|
|
462
541
|
BEHAVIOR:
|
|
463
542
|
- Reads export sidecar manifest to resolve originPageSlug -> pagePath/componentsPath.
|
|
464
|
-
- Copies
|
|
543
|
+
- Copies and validates files strictly from pages[].manifest.files for the selected page.
|
|
465
544
|
- Never overwrites existing conflicting files.
|
|
466
545
|
- Reports:
|
|
467
546
|
a) copied new files,
|
|
@@ -506,7 +585,9 @@ ${WORKFLOW_SUMMARY}`,
|
|
|
506
585
|
"Dependencies with version mismatch between export and user project. Report only."
|
|
507
586
|
),
|
|
508
587
|
summary: z2.object({
|
|
509
|
-
totalCandidateFiles: z2.number().describe(
|
|
588
|
+
totalCandidateFiles: z2.number().describe(
|
|
589
|
+
"Total number of candidate files considered for page copy from pages[].manifest.files."
|
|
590
|
+
),
|
|
510
591
|
copiedFiles: z2.number().describe("Number of files copied as new."),
|
|
511
592
|
identicalFiles: z2.number().describe("Number of files detected as identical."),
|
|
512
593
|
conflictFiles: z2.number().describe("Number of conflicting files not copied."),
|
|
@@ -525,9 +606,9 @@ var toolClearTemp = {
|
|
|
525
606
|
title: "PrimeUI Clear Temp",
|
|
526
607
|
description: `Delete all files in .primeui/temp/ and recreate empty temp structure.
|
|
527
608
|
|
|
528
|
-
|
|
609
|
+
Call this at the START of a new import flow, before get_project_info/create_export, to reset stale temp state from previous runs.
|
|
529
610
|
|
|
530
|
-
WHEN TO USE: Call
|
|
611
|
+
WHEN TO USE: Call once at the beginning of each new import flow. Do not call this between page imports in the same flow.
|
|
531
612
|
|
|
532
613
|
Safe to call multiple times. Only affects .primeui/temp/ directory - never touches the user's project files.
|
|
533
614
|
|
|
@@ -837,6 +918,7 @@ function shouldParseFile(filePath) {
|
|
|
837
918
|
return PARSEABLE_EXTENSIONS.has(path3.extname(filePath).toLowerCase());
|
|
838
919
|
}
|
|
839
920
|
async function buildImportGraph(input) {
|
|
921
|
+
const shouldFollowInternalImports = input.followInternalImports ?? true;
|
|
840
922
|
const projectRoot = path3.resolve(input.projectRoot);
|
|
841
923
|
const queue = input.entryFiles.map((filePath) => path3.resolve(filePath));
|
|
842
924
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -866,7 +948,7 @@ async function buildImportGraph(input) {
|
|
|
866
948
|
specifier
|
|
867
949
|
);
|
|
868
950
|
if (resolved.kind === "internal") {
|
|
869
|
-
if (!visited.has(resolved.filePath)) {
|
|
951
|
+
if (shouldFollowInternalImports && !visited.has(resolved.filePath)) {
|
|
870
952
|
queue.push(resolved.filePath);
|
|
871
953
|
}
|
|
872
954
|
continue;
|
|
@@ -1180,53 +1262,95 @@ async function fileExists2(filePath) {
|
|
|
1180
1262
|
return false;
|
|
1181
1263
|
}
|
|
1182
1264
|
}
|
|
1183
|
-
function
|
|
1265
|
+
function isExportPageManifestRecord(value) {
|
|
1266
|
+
if (!value || typeof value !== "object") {
|
|
1267
|
+
return false;
|
|
1268
|
+
}
|
|
1269
|
+
const maybe = value;
|
|
1270
|
+
return typeof maybe.success === "boolean" && typeof maybe.message === "string" && Array.isArray(maybe.files) && maybe.files.every((item) => typeof item === "string");
|
|
1271
|
+
}
|
|
1272
|
+
function isExportStatus(value) {
|
|
1273
|
+
return value === "in_progress" || value === "completed" || value === "failed";
|
|
1274
|
+
}
|
|
1275
|
+
function isExportSummaryRecord(value) {
|
|
1276
|
+
if (!value || typeof value !== "object") {
|
|
1277
|
+
return false;
|
|
1278
|
+
}
|
|
1279
|
+
const maybe = value;
|
|
1280
|
+
return typeof maybe.total === "number" && typeof maybe.successful === "number" && typeof maybe.failed === "number";
|
|
1281
|
+
}
|
|
1282
|
+
function isExportedComponentRecord(value) {
|
|
1283
|
+
if (!value || typeof value !== "object") {
|
|
1284
|
+
return false;
|
|
1285
|
+
}
|
|
1286
|
+
const maybe = value;
|
|
1287
|
+
return typeof maybe.componentKey === "string" && typeof maybe.enabled === "boolean" && Array.isArray(maybe.files) && maybe.files.every((item) => typeof item === "string") && typeof maybe.message === "string";
|
|
1288
|
+
}
|
|
1289
|
+
function isExportPageRecord(value) {
|
|
1184
1290
|
if (!value || typeof value !== "object") {
|
|
1185
1291
|
return false;
|
|
1186
1292
|
}
|
|
1187
1293
|
const maybe = value;
|
|
1188
|
-
|
|
1294
|
+
const title = maybe.title;
|
|
1295
|
+
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" && isExportPageManifestRecord(maybe.manifest);
|
|
1189
1296
|
}
|
|
1190
1297
|
function parseManifest(value) {
|
|
1191
1298
|
if (!value || typeof value !== "object") {
|
|
1192
1299
|
throw new Error("Invalid export manifest format.");
|
|
1193
1300
|
}
|
|
1194
1301
|
const maybe = value;
|
|
1195
|
-
|
|
1302
|
+
const exportPayload = maybe.export;
|
|
1303
|
+
if (!exportPayload || typeof exportPayload !== "object") {
|
|
1304
|
+
throw new Error("Export manifest export payload is invalid.");
|
|
1305
|
+
}
|
|
1306
|
+
const exportObject = exportPayload;
|
|
1307
|
+
const status = exportObject.status;
|
|
1308
|
+
const summary = exportObject.summary;
|
|
1309
|
+
const components = exportObject.components;
|
|
1310
|
+
if (typeof exportObject.id !== "string" || !isExportStatus(status) || typeof exportObject.createdAt !== "string" || !("expiresAt" in exportObject) || !(typeof exportObject.expiresAt === "string" || exportObject.expiresAt === null) || !isExportSummaryRecord(summary) || !Array.isArray(components) || !components.every(isExportedComponentRecord) || !Array.isArray(maybe.pages)) {
|
|
1196
1311
|
throw new Error("Export manifest does not match expected schema.");
|
|
1197
1312
|
}
|
|
1198
|
-
if (!maybe.pages.every(
|
|
1313
|
+
if (!maybe.pages.every(isExportPageRecord)) {
|
|
1199
1314
|
throw new Error("Export manifest pages payload is invalid.");
|
|
1200
1315
|
}
|
|
1201
1316
|
return {
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1317
|
+
export: {
|
|
1318
|
+
id: exportObject.id,
|
|
1319
|
+
status,
|
|
1320
|
+
createdAt: exportObject.createdAt,
|
|
1321
|
+
expiresAt: exportObject.expiresAt,
|
|
1322
|
+
summary,
|
|
1323
|
+
components
|
|
1324
|
+
},
|
|
1206
1325
|
pages: maybe.pages
|
|
1207
1326
|
};
|
|
1208
1327
|
}
|
|
1209
|
-
async function
|
|
1210
|
-
const
|
|
1211
|
-
const
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1328
|
+
async function resolveManifestCandidateFiles(input) {
|
|
1329
|
+
const result = /* @__PURE__ */ new Set();
|
|
1330
|
+
for (const rawPath of input.files) {
|
|
1331
|
+
const trimmed = rawPath.trim();
|
|
1332
|
+
if (!trimmed) {
|
|
1333
|
+
throw new Error(
|
|
1334
|
+
`Export manifest for page "${input.pageSlug}" contains an empty file path.`
|
|
1335
|
+
);
|
|
1216
1336
|
}
|
|
1217
|
-
const
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1337
|
+
const normalizedRelative = toPosixPath(trimmed).replace(/^\.\/+/, "");
|
|
1338
|
+
const absolutePath = path4.resolve(input.exportPath, normalizedRelative);
|
|
1339
|
+
const relative = path4.relative(input.exportPath, absolutePath);
|
|
1340
|
+
if (relative.startsWith("..") || path4.isAbsolute(relative)) {
|
|
1341
|
+
throw new Error(
|
|
1342
|
+
`Export manifest for page "${input.pageSlug}" contains path outside export root: ${trimmed}`
|
|
1343
|
+
);
|
|
1344
|
+
}
|
|
1345
|
+
const stats = await stat3(absolutePath).catch(() => null);
|
|
1346
|
+
if (!stats?.isFile()) {
|
|
1347
|
+
throw new Error(
|
|
1348
|
+
`Export manifest file is missing for page "${input.pageSlug}": ${normalizedRelative}`
|
|
1349
|
+
);
|
|
1227
1350
|
}
|
|
1351
|
+
result.add(absolutePath);
|
|
1228
1352
|
}
|
|
1229
|
-
return
|
|
1353
|
+
return [...result].sort((a, b) => a.localeCompare(b));
|
|
1230
1354
|
}
|
|
1231
1355
|
async function resolveSingleExportDirectory(exportsRoot) {
|
|
1232
1356
|
const entries = await readdir(exportsRoot, { withFileTypes: true });
|
|
@@ -1336,13 +1460,13 @@ async function copyPageFromExport(input) {
|
|
|
1336
1460
|
);
|
|
1337
1461
|
if (!await fileExists2(manifestPath)) {
|
|
1338
1462
|
throw new Error(
|
|
1339
|
-
`Export manifest is missing at ${manifestPath}. Run download_export to regenerate it.`
|
|
1463
|
+
`Export manifest is missing at ${manifestPath}. Run create_export and download_export to regenerate it.`
|
|
1340
1464
|
);
|
|
1341
1465
|
}
|
|
1342
1466
|
const manifest = parseManifest(await readJsonFile(manifestPath));
|
|
1343
|
-
if (manifest.
|
|
1467
|
+
if (manifest.export.id !== exportId) {
|
|
1344
1468
|
throw new Error(
|
|
1345
|
-
`Manifest
|
|
1469
|
+
`Manifest export id mismatch: expected ${exportId}, got ${manifest.export.id}. Re-run create_export and download_export.`
|
|
1346
1470
|
);
|
|
1347
1471
|
}
|
|
1348
1472
|
const page = manifest.pages.find(
|
|
@@ -1382,15 +1506,16 @@ async function copyPageFromExport(input) {
|
|
|
1382
1506
|
const importRewritePlan = isSlugRemapped ? buildImportRewritePlan(page.componentsPath, targetPaths.componentsPath) : null;
|
|
1383
1507
|
ensureSafeTargetPath(input.projectRoot, targetPagePath);
|
|
1384
1508
|
ensureSafeTargetPath(input.projectRoot, targetComponentsPath);
|
|
1385
|
-
const
|
|
1386
|
-
|
|
1387
|
-
|
|
1509
|
+
const candidateFiles = await resolveManifestCandidateFiles({
|
|
1510
|
+
exportPath,
|
|
1511
|
+
pageSlug: normalizedOriginSlug,
|
|
1512
|
+
files: page.manifest.files
|
|
1513
|
+
});
|
|
1514
|
+
const dependencyGraph = await buildImportGraph({
|
|
1388
1515
|
projectRoot: exportPath,
|
|
1389
|
-
entryFiles:
|
|
1516
|
+
entryFiles: candidateFiles,
|
|
1517
|
+
followInternalImports: false
|
|
1390
1518
|
});
|
|
1391
|
-
const candidateFiles = [...new Set(importGraph.internalFiles)].sort(
|
|
1392
|
-
(a, b) => a.localeCompare(b)
|
|
1393
|
-
);
|
|
1394
1519
|
const newFiles = [];
|
|
1395
1520
|
const identicalFiles = [];
|
|
1396
1521
|
const conflictFiles = [];
|
|
@@ -1463,7 +1588,7 @@ async function copyPageFromExport(input) {
|
|
|
1463
1588
|
const userPackageJson = await readJsonFile(userPackageJsonPath);
|
|
1464
1589
|
const addedDependencies = [];
|
|
1465
1590
|
const dependenciesVersionConflicts = [];
|
|
1466
|
-
for (const packageName of
|
|
1591
|
+
for (const packageName of dependencyGraph.externalPackages) {
|
|
1467
1592
|
const exportDependency = getDependencyVersion(
|
|
1468
1593
|
exportPackageJson,
|
|
1469
1594
|
packageName
|
|
@@ -1623,31 +1748,69 @@ function buildInspectPageReport(input) {
|
|
|
1623
1748
|
}
|
|
1624
1749
|
|
|
1625
1750
|
// src/services/project-sync-service.ts
|
|
1626
|
-
function
|
|
1751
|
+
function isExportStatus2(value) {
|
|
1752
|
+
return value === "in_progress" || value === "completed" || value === "failed";
|
|
1753
|
+
}
|
|
1754
|
+
function isExportSummary(value) {
|
|
1627
1755
|
if (!value || typeof value !== "object") {
|
|
1628
1756
|
return false;
|
|
1629
1757
|
}
|
|
1630
1758
|
const maybe = value;
|
|
1631
|
-
return typeof maybe.
|
|
1759
|
+
return typeof maybe.total === "number" && typeof maybe.successful === "number" && typeof maybe.failed === "number";
|
|
1632
1760
|
}
|
|
1633
|
-
function
|
|
1634
|
-
|
|
1761
|
+
function isExportedComponent(value) {
|
|
1762
|
+
if (!value || typeof value !== "object") {
|
|
1763
|
+
return false;
|
|
1764
|
+
}
|
|
1765
|
+
const maybe = value;
|
|
1766
|
+
return typeof maybe.componentKey === "string" && typeof maybe.enabled === "boolean" && Array.isArray(maybe.files) && maybe.files.every((item) => typeof item === "string") && typeof maybe.message === "string";
|
|
1635
1767
|
}
|
|
1636
|
-
function
|
|
1768
|
+
function isExportPageManifest(value) {
|
|
1637
1769
|
if (!value || typeof value !== "object") {
|
|
1638
|
-
|
|
1770
|
+
return false;
|
|
1639
1771
|
}
|
|
1640
1772
|
const maybe = value;
|
|
1641
|
-
|
|
1642
|
-
|
|
1773
|
+
return typeof maybe.success === "boolean" && typeof maybe.message === "string" && Array.isArray(maybe.files) && maybe.files.every((item) => typeof item === "string");
|
|
1774
|
+
}
|
|
1775
|
+
function isExportPage(value) {
|
|
1776
|
+
if (!value || typeof value !== "object") {
|
|
1777
|
+
return false;
|
|
1778
|
+
}
|
|
1779
|
+
const maybe = value;
|
|
1780
|
+
const title = maybe.title;
|
|
1781
|
+
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);
|
|
1782
|
+
}
|
|
1783
|
+
function buildManifestPath(exportsRoot, exportId) {
|
|
1784
|
+
return path5.join(exportsRoot, `${exportId}.manifest.json`);
|
|
1785
|
+
}
|
|
1786
|
+
function parseExportManifest(value) {
|
|
1787
|
+
if (!value || typeof value !== "object") {
|
|
1788
|
+
throw new Error("Export manifest is invalid.");
|
|
1643
1789
|
}
|
|
1644
|
-
|
|
1645
|
-
|
|
1790
|
+
const maybe = value;
|
|
1791
|
+
const exportPayload = maybe.export;
|
|
1792
|
+
if (!exportPayload || typeof exportPayload !== "object") {
|
|
1793
|
+
throw new Error("Export manifest export payload is invalid.");
|
|
1794
|
+
}
|
|
1795
|
+
const exportObject = exportPayload;
|
|
1796
|
+
const status = exportObject.status;
|
|
1797
|
+
const summary = exportObject.summary;
|
|
1798
|
+
const components = exportObject.components;
|
|
1799
|
+
if (typeof exportObject.id !== "string" || !isExportStatus2(status) || typeof exportObject.createdAt !== "string" || !("expiresAt" in exportObject) || !(typeof exportObject.expiresAt === "string" || exportObject.expiresAt === null) || !isExportSummary(summary) || !Array.isArray(components) || !components.every(isExportedComponent) || !Array.isArray(maybe.pages)) {
|
|
1800
|
+
throw new Error("Export manifest does not match expected schema.");
|
|
1801
|
+
}
|
|
1802
|
+
if (!maybe.pages.every(isExportPage)) {
|
|
1803
|
+
throw new Error("Export manifest pages payload is invalid.");
|
|
1646
1804
|
}
|
|
1647
1805
|
return {
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1806
|
+
export: {
|
|
1807
|
+
id: exportObject.id,
|
|
1808
|
+
status,
|
|
1809
|
+
createdAt: exportObject.createdAt,
|
|
1810
|
+
expiresAt: exportObject.expiresAt,
|
|
1811
|
+
summary,
|
|
1812
|
+
components
|
|
1813
|
+
},
|
|
1651
1814
|
pages: maybe.pages
|
|
1652
1815
|
};
|
|
1653
1816
|
}
|
|
@@ -1677,21 +1840,9 @@ var ProjectSyncService = class {
|
|
|
1677
1840
|
async createExport() {
|
|
1678
1841
|
await this.ensureTempLayout();
|
|
1679
1842
|
const result = await this.provider.createExport();
|
|
1680
|
-
const
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
);
|
|
1684
|
-
const snapshot = {
|
|
1685
|
-
schemaVersion: 1,
|
|
1686
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1687
|
-
exportId: result.export.id,
|
|
1688
|
-
pages: result.pages
|
|
1689
|
-
};
|
|
1690
|
-
await writeUtf8(
|
|
1691
|
-
pagesSnapshotPath,
|
|
1692
|
-
`${JSON.stringify(snapshot, null, 2)}
|
|
1693
|
-
`
|
|
1694
|
-
);
|
|
1843
|
+
const manifestPath = buildManifestPath(this.exportsRoot, result.export.id);
|
|
1844
|
+
await writeUtf8(manifestPath, `${JSON.stringify(result, null, 2)}
|
|
1845
|
+
`);
|
|
1695
1846
|
return result;
|
|
1696
1847
|
}
|
|
1697
1848
|
async downloadExportById(id) {
|
|
@@ -1703,39 +1854,28 @@ var ProjectSyncService = class {
|
|
|
1703
1854
|
}
|
|
1704
1855
|
const targetZipPath = path5.join(this.exportsRoot, `${id}.zip`);
|
|
1705
1856
|
const targetProjectPath = path5.join(this.exportsRoot, id);
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
await resetDir(targetProjectPath);
|
|
1709
|
-
await extractZip(targetZipPath, targetProjectPath);
|
|
1710
|
-
const pagesSnapshotPath = buildPagesSnapshotPath(this.exportsRoot, id);
|
|
1711
|
-
let pagesSnapshot;
|
|
1857
|
+
const manifestPath = buildManifestPath(this.exportsRoot, id);
|
|
1858
|
+
let manifest;
|
|
1712
1859
|
try {
|
|
1713
|
-
|
|
1714
|
-
await readFile4(
|
|
1860
|
+
manifest = parseExportManifest(
|
|
1861
|
+
JSON.parse(await readFile4(manifestPath, "utf-8"))
|
|
1715
1862
|
);
|
|
1716
|
-
pagesSnapshot = parsePagesSnapshot(snapshotRaw);
|
|
1717
1863
|
} catch (error) {
|
|
1718
1864
|
const reason = error instanceof Error ? error.message : String(error);
|
|
1719
1865
|
throw new Error(
|
|
1720
|
-
`Export
|
|
1866
|
+
`Export manifest is required for download id "${id}". Run create_export for this export before downloading. Details: ${reason}`
|
|
1721
1867
|
);
|
|
1722
1868
|
}
|
|
1723
|
-
if (
|
|
1869
|
+
if (manifest.export.id !== id) {
|
|
1724
1870
|
throw new Error(
|
|
1725
|
-
`Export
|
|
1871
|
+
`Export manifest mismatch for id "${id}". Run create_export and retry download.`
|
|
1726
1872
|
);
|
|
1727
1873
|
}
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
exportId: id,
|
|
1734
|
-
projectPath: targetProjectPath,
|
|
1735
|
-
pages
|
|
1736
|
-
};
|
|
1737
|
-
await writeUtf8(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
1738
|
-
`);
|
|
1874
|
+
await ensureDir(this.exportsRoot);
|
|
1875
|
+
await this.provider.downloadExportArchive(id, targetZipPath);
|
|
1876
|
+
await resetDir(targetProjectPath);
|
|
1877
|
+
await extractZip(targetZipPath, targetProjectPath);
|
|
1878
|
+
const pages = manifest.pages;
|
|
1739
1879
|
return {
|
|
1740
1880
|
exportId: id,
|
|
1741
1881
|
projectPath: targetProjectPath,
|
|
@@ -1764,14 +1904,12 @@ var ProjectSyncService = class {
|
|
|
1764
1904
|
}
|
|
1765
1905
|
async clearTemp() {
|
|
1766
1906
|
await resetDir(this.tempRoot);
|
|
1767
|
-
await writeUtf8(path5.join(this.tempRoot, ".gitkeep"), "");
|
|
1768
1907
|
await ensureDir(this.exportsRoot);
|
|
1769
1908
|
}
|
|
1770
1909
|
async ensureTempLayout() {
|
|
1771
1910
|
await ensureDir(this.primeUiRoot);
|
|
1772
1911
|
await ensureDir(this.tempRoot);
|
|
1773
1912
|
await ensureDir(this.exportsRoot);
|
|
1774
|
-
await writeUtf8(path5.join(this.tempRoot, ".gitkeep"), "");
|
|
1775
1913
|
}
|
|
1776
1914
|
};
|
|
1777
1915
|
|
|
@@ -1799,6 +1937,32 @@ var projectPageObjectSchema = z3.object({
|
|
|
1799
1937
|
componentsPath: z3.string()
|
|
1800
1938
|
});
|
|
1801
1939
|
var projectPageSchema = projectPageObjectSchema;
|
|
1940
|
+
var exportSummarySchema2 = z3.object({
|
|
1941
|
+
total: z3.number(),
|
|
1942
|
+
successful: z3.number(),
|
|
1943
|
+
failed: z3.number()
|
|
1944
|
+
});
|
|
1945
|
+
var exportedComponentSchema = z3.object({
|
|
1946
|
+
componentKey: z3.string(),
|
|
1947
|
+
enabled: z3.boolean(),
|
|
1948
|
+
files: z3.array(z3.string()),
|
|
1949
|
+
message: z3.string()
|
|
1950
|
+
});
|
|
1951
|
+
var exportPageManifestSchema2 = z3.object({
|
|
1952
|
+
success: z3.boolean(),
|
|
1953
|
+
message: z3.string(),
|
|
1954
|
+
files: z3.array(z3.string())
|
|
1955
|
+
});
|
|
1956
|
+
var exportPageSchema2 = z3.object({
|
|
1957
|
+
id: z3.string(),
|
|
1958
|
+
title: z3.string().optional(),
|
|
1959
|
+
slug: z3.string(),
|
|
1960
|
+
pageType: z3.string(),
|
|
1961
|
+
isReadyToExport: z3.literal(true),
|
|
1962
|
+
pagePath: z3.string(),
|
|
1963
|
+
componentsPath: z3.string(),
|
|
1964
|
+
manifest: exportPageManifestSchema2
|
|
1965
|
+
});
|
|
1802
1966
|
var projectInfoSchema = z3.object({
|
|
1803
1967
|
projectId: z3.string(),
|
|
1804
1968
|
projectName: z3.string(),
|
|
@@ -1834,9 +1998,13 @@ var exportsResponseSchema = z3.object({
|
|
|
1834
1998
|
var createExportResponseSchema = z3.object({
|
|
1835
1999
|
export: z3.object({
|
|
1836
2000
|
id: z3.string(),
|
|
1837
|
-
status: exportStatusSchema2
|
|
2001
|
+
status: exportStatusSchema2,
|
|
2002
|
+
createdAt: z3.string().datetime({ offset: true }),
|
|
2003
|
+
expiresAt: z3.string().datetime({ offset: true }).nullable(),
|
|
2004
|
+
summary: exportSummarySchema2,
|
|
2005
|
+
components: z3.array(exportedComponentSchema)
|
|
1838
2006
|
}),
|
|
1839
|
-
pages: z3.array(
|
|
2007
|
+
pages: z3.array(exportPageSchema2)
|
|
1840
2008
|
});
|
|
1841
2009
|
var PrimeUiApiContractError = class extends Error {
|
|
1842
2010
|
constructor(endpoint, details) {
|
|
@@ -2059,10 +2227,14 @@ var ApiProjectDataProvider = class {
|
|
|
2059
2227
|
};
|
|
2060
2228
|
|
|
2061
2229
|
// src/service.ts
|
|
2062
|
-
|
|
2230
|
+
function getProjectConfigFallbackCwds() {
|
|
2231
|
+
return [process.env.INIT_CWD, process.env.PWD].map((value) => value?.trim()).filter((value) => Boolean(value));
|
|
2232
|
+
}
|
|
2233
|
+
async function createProjectSyncService() {
|
|
2063
2234
|
const resolvedProjectConfig = await resolvePrimeUiProjectConfig({
|
|
2064
2235
|
cwd: process.cwd(),
|
|
2065
|
-
projectRootFromEnv: process.env.PRIMEUI_PROJECT_ROOT
|
|
2236
|
+
projectRootFromEnv: process.env.PRIMEUI_PROJECT_ROOT,
|
|
2237
|
+
fallbackCwds: getProjectConfigFallbackCwds()
|
|
2066
2238
|
});
|
|
2067
2239
|
const projectRoot = resolvedProjectConfig.projectRoot;
|
|
2068
2240
|
const targetProjectRoot = path7.resolve(
|
|
@@ -2077,17 +2249,48 @@ async function main() {
|
|
|
2077
2249
|
apiKey,
|
|
2078
2250
|
baseUrl: process.env.PRIMEUI_API_BASE_URL
|
|
2079
2251
|
});
|
|
2080
|
-
|
|
2252
|
+
return new ProjectSyncService({
|
|
2081
2253
|
projectRoot,
|
|
2082
2254
|
targetProjectRoot,
|
|
2083
2255
|
provider
|
|
2084
2256
|
});
|
|
2085
|
-
|
|
2257
|
+
}
|
|
2258
|
+
var LazyProjectSyncSource = class {
|
|
2259
|
+
async withService(operation) {
|
|
2260
|
+
const service = await createProjectSyncService();
|
|
2261
|
+
return operation(service);
|
|
2262
|
+
}
|
|
2263
|
+
async getProjectInfo() {
|
|
2264
|
+
return this.withService((service) => service.getProjectInfo());
|
|
2265
|
+
}
|
|
2266
|
+
async listExports() {
|
|
2267
|
+
return this.withService((service) => service.listExports());
|
|
2268
|
+
}
|
|
2269
|
+
async createExport() {
|
|
2270
|
+
return this.withService((service) => service.createExport());
|
|
2271
|
+
}
|
|
2272
|
+
async downloadExportById(id) {
|
|
2273
|
+
return this.withService((service) => service.downloadExportById(id));
|
|
2274
|
+
}
|
|
2275
|
+
async inspectPage(slug) {
|
|
2276
|
+
return this.withService((service) => service.inspectPage(slug));
|
|
2277
|
+
}
|
|
2278
|
+
async copyPage(originPageSlug, actualPageSlug) {
|
|
2279
|
+
return this.withService(
|
|
2280
|
+
(service) => service.copyPage(originPageSlug, actualPageSlug)
|
|
2281
|
+
);
|
|
2282
|
+
}
|
|
2283
|
+
async clearTemp() {
|
|
2284
|
+
return this.withService((service) => service.clearTemp());
|
|
2285
|
+
}
|
|
2286
|
+
};
|
|
2287
|
+
async function main() {
|
|
2288
|
+
console.error("CWD:", process.cwd());
|
|
2289
|
+
console.error("INIT_CWD:", process.env.INIT_CWD);
|
|
2290
|
+
console.error("PWD:", process.env.PWD);
|
|
2291
|
+
const server = createPrimeUiMcpServer(new LazyProjectSyncSource());
|
|
2086
2292
|
const transport = new StdioServerTransport();
|
|
2087
2293
|
await server.connect(transport);
|
|
2088
|
-
console.error(
|
|
2089
|
-
`[primeui-mcp] running for root: ${projectRoot}; target project: ${targetProjectRoot}`
|
|
2090
|
-
);
|
|
2091
2294
|
}
|
|
2092
2295
|
main().catch((error) => {
|
|
2093
2296
|
console.error("[primeui-mcp] failed to start", error);
|