@primeuicom/mcp 0.1.10 → 0.1.11
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 +11 -9
- package/dist/service.js +1075 -44
- package/dist/service.js.map +1 -1
- package/package.json +1 -1
package/dist/service.js
CHANGED
|
@@ -43,6 +43,36 @@ var exportItemSchema = z.object({
|
|
|
43
43
|
status: exportStatusSchema,
|
|
44
44
|
createdAt: z.string().describe("Export creation timestamp in ISO-8601 UTC format.")
|
|
45
45
|
}).describe("Single export record from PrimeUI export history.");
|
|
46
|
+
var fileTransferSchema = z.object({
|
|
47
|
+
sourcePath: z.string().describe("Relative source file path inside downloaded export root."),
|
|
48
|
+
targetPath: z.string().describe("Relative target file path inside user project root.")
|
|
49
|
+
});
|
|
50
|
+
var conflictFileSchema = fileTransferSchema.extend({
|
|
51
|
+
diff: z.string().describe(
|
|
52
|
+
"Unified diff between user file and export file. For binary files the diff contains a plain message."
|
|
53
|
+
),
|
|
54
|
+
isBinary: z.boolean().describe("True if conflict comparison was treated as binary data.")
|
|
55
|
+
});
|
|
56
|
+
var dependencySectionSchema = z.enum([
|
|
57
|
+
"dependencies",
|
|
58
|
+
"devDependencies",
|
|
59
|
+
"peerDependencies"
|
|
60
|
+
]);
|
|
61
|
+
var dependencyToAddSchema = z.object({
|
|
62
|
+
packageName: z.string().describe("Dependency package name that was missing in user package.json."),
|
|
63
|
+
version: z.string().describe("Version from exported project's package.json."),
|
|
64
|
+
section: dependencySectionSchema.describe(
|
|
65
|
+
"package.json section where dependency should be added."
|
|
66
|
+
)
|
|
67
|
+
});
|
|
68
|
+
var dependencyVersionConflictSchema = z.object({
|
|
69
|
+
packageName: z.string().describe("Dependency package name with version mismatch."),
|
|
70
|
+
section: dependencySectionSchema.describe(
|
|
71
|
+
"Section where export expects this dependency."
|
|
72
|
+
),
|
|
73
|
+
exportVersion: z.string().describe("Version found in exported project's package.json."),
|
|
74
|
+
userVersion: z.string().describe("Version currently present in user's package.json.")
|
|
75
|
+
});
|
|
46
76
|
var TRIGGER_PHRASES = [
|
|
47
77
|
"import page from PrimeUI",
|
|
48
78
|
"add page from PrimeUI",
|
|
@@ -59,8 +89,8 @@ WORKFLOW ORDER (always follow this sequence):
|
|
|
59
89
|
2. Reconcile PrimeUI pages with local project pages/routes/paths, then confirm target pages with user
|
|
60
90
|
3. primeui_create_export -> generate export snapshot for agreed pages/analysis scope
|
|
61
91
|
4. primeui_download_export -> download to temp directory
|
|
62
|
-
5.
|
|
63
|
-
6.
|
|
92
|
+
5. primeui_copy_page -> copy one page safely (repeat for each selected page)
|
|
93
|
+
6. Reconcile integration points and resolve reported conflicts, if any
|
|
64
94
|
7. primeui_clear_temp -> cleanup temp files
|
|
65
95
|
`.trim();
|
|
66
96
|
var initialInstructions = `
|
|
@@ -82,16 +112,16 @@ CRITICAL RULES FOR PAGE IMPORT:
|
|
|
82
112
|
- Before creating export, ask user which pages to add/update (specific pages or all missing pages) and keep that choice in thread context.
|
|
83
113
|
- The downloaded export in .primeui/temp/ contains a full standalone Next.js project.
|
|
84
114
|
Do NOT copy it wholesale.
|
|
85
|
-
-
|
|
86
|
-
-
|
|
87
|
-
-
|
|
88
|
-
-
|
|
89
|
-
-
|
|
90
|
-
-
|
|
115
|
+
- Always call primeui_copy_page for each confirmed page instead of manual file copy.
|
|
116
|
+
- primeui_copy_page performs safe copy only:
|
|
117
|
+
- new files are copied,
|
|
118
|
+
- identical files are reported,
|
|
119
|
+
- conflicting files are NEVER overwritten and are returned with diff.
|
|
120
|
+
- primeui_copy_page may add only missing dependencies to user's package.json.
|
|
121
|
+
It never upgrades existing dependency versions and never runs dependency installation.
|
|
122
|
+
- After page copy operations, inspect integration points (navigation configs, link menus, route registries, page indexes).
|
|
91
123
|
- If integration pattern is obvious, apply it and explicitly mark it in the report with "\u26A0 integration update".
|
|
92
124
|
- If integration pattern is unclear, ask the user before editing integration files.
|
|
93
|
-
- Do NOT copy: package.json, next.config, tailwind.config, layout files, or any project-level config.
|
|
94
|
-
- If merge conflicts arise and cannot be resolved unambiguously, STOP and ask the user to decide.
|
|
95
125
|
`.trim();
|
|
96
126
|
var toolGetProjectInfo = {
|
|
97
127
|
title: "PrimeUI Project Info",
|
|
@@ -200,30 +230,19 @@ var toolDownloadExport = {
|
|
|
200
230
|
|
|
201
231
|
Do NOT call this as the first step. Requires export ID from primeui_create_export. Start with primeui_get_project_info instead.
|
|
202
232
|
|
|
203
|
-
WHEN TO USE: Call this AFTER primeui_create_export has returned a completed export. Requires
|
|
233
|
+
WHEN TO USE: Call this AFTER primeui_create_export has returned a completed export. Requires export ID from primeui_create_export so MCP can use the matching local pages snapshot for sidecar manifest generation.
|
|
234
|
+
|
|
235
|
+
NOTE:
|
|
236
|
+
- Download of an export ID without local pages snapshot may fail.
|
|
237
|
+
- For previous exports, create a fresh export first when possible.
|
|
204
238
|
|
|
205
239
|
AFTER DOWNLOAD:
|
|
206
|
-
-
|
|
207
|
-
-
|
|
208
|
-
-
|
|
209
|
-
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
1. Read the page file at the pagePath from the response.
|
|
213
|
-
2. Read the component files at the componentsPath from the response. Trace the page's imports to identify which specific component folders are needed.
|
|
214
|
-
3. Copy ONLY the page file and its component dependencies into the user's project.
|
|
215
|
-
4. Adjust all import paths to match the user's project structure.
|
|
216
|
-
5. Check for existing components in user's project:
|
|
217
|
-
- If code overlaps or was modified on the user's side, carefully determine the nature of changes on both sides.
|
|
218
|
-
- If the merge conflict can be resolved unambiguously - resolve it.
|
|
219
|
-
- If NOT - STOP, explain the conflict to the user, and ask for their decision before continuing.
|
|
220
|
-
6. Inspect integration points used by existing pages (navigation configs, route registries, link components, page indexes).
|
|
221
|
-
7. If integration pattern is obvious and consistent with project conventions, apply integration updates and mark them in the report as "\u26A0 integration update".
|
|
222
|
-
8. If integration pattern is not obvious, provide concrete integration suggestions and ask user confirmation before changing integration files.
|
|
223
|
-
9. Do NOT copy project config files (package.json, next.config, tailwind.config, layout, etc.).
|
|
224
|
-
10. REPEAT steps 1-9 for each page the user wants to import.
|
|
225
|
-
11. Report exactly which files/areas were changed and which items require user follow-up.
|
|
226
|
-
12. Only after ALL pages are successfully imported, call primeui_clear_temp to clean up.
|
|
240
|
+
- Use manifestPath from this response as the contract for page slug/path mapping in copy operations.
|
|
241
|
+
- For each selected page, call primeui_copy_page with:
|
|
242
|
+
- originPageSlug (required),
|
|
243
|
+
- actualPageSlug (optional, if route remap is needed).
|
|
244
|
+
- If there are unresolved conflicts in copy report, stop and ask the user.
|
|
245
|
+
- Only after all requested pages are copied and reconciled, call primeui_clear_temp.
|
|
227
246
|
|
|
228
247
|
${WORKFLOW_SUMMARY}`,
|
|
229
248
|
inputSchema: {
|
|
@@ -238,6 +257,9 @@ MANUAL IMPORT STEPS per page (do not skip):
|
|
|
238
257
|
projectPath: z.string().describe(
|
|
239
258
|
"Absolute local path to extracted export root in .primeui/temp/exports/[exportId]. Read files from here only."
|
|
240
259
|
),
|
|
260
|
+
manifestPath: z.string().describe(
|
|
261
|
+
"Absolute path to sidecar export manifest file (.primeui/temp/exports/[exportId].manifest.json). primeui_copy_page depends on this file."
|
|
262
|
+
),
|
|
241
263
|
pages: z.array(pageSchema).describe(
|
|
242
264
|
"Page descriptors for import decisions. Use pagePath/componentsPath from each item when copying code into user project."
|
|
243
265
|
)
|
|
@@ -249,6 +271,77 @@ MANUAL IMPORT STEPS per page (do not skip):
|
|
|
249
271
|
openWorldHint: true
|
|
250
272
|
}
|
|
251
273
|
};
|
|
274
|
+
var toolCopyPage = {
|
|
275
|
+
title: "PrimeUI Copy Page",
|
|
276
|
+
description: `Safely copy one page from downloaded export into the user's local project. Works only with local files in .primeui/temp/exports and does NOT call PrimeUI API.
|
|
277
|
+
|
|
278
|
+
WHEN TO USE:
|
|
279
|
+
- Call this AFTER primeui_download_export.
|
|
280
|
+
- Call once per selected page.
|
|
281
|
+
- Use actualPageSlug only when user wants to remap route during import.
|
|
282
|
+
|
|
283
|
+
BEHAVIOR:
|
|
284
|
+
- Reads export sidecar manifest to resolve originPageSlug -> pagePath/componentsPath.
|
|
285
|
+
- Copies page file + full page components folder + recursively discovered internal dependencies.
|
|
286
|
+
- Never overwrites existing conflicting files.
|
|
287
|
+
- Reports:
|
|
288
|
+
a) copied new files,
|
|
289
|
+
b) identical existing files,
|
|
290
|
+
c) conflicting files with diff,
|
|
291
|
+
d) missing dependencies added to package.json,
|
|
292
|
+
e) dependency version conflicts (report-only, no auto upgrade).
|
|
293
|
+
- Adds only missing dependencies to package.json (dependencies/devDependencies/peerDependencies).
|
|
294
|
+
- Never updates existing dependency versions and never runs install commands.
|
|
295
|
+
|
|
296
|
+
${WORKFLOW_SUMMARY}`,
|
|
297
|
+
inputSchema: {
|
|
298
|
+
originPageSlug: z.string().describe(
|
|
299
|
+
"Source page slug from PrimeUI project pages list, for example '/', '/pricing', '/docs'."
|
|
300
|
+
),
|
|
301
|
+
actualPageSlug: z.string().optional().describe(
|
|
302
|
+
"Optional destination slug in user's project. If omitted, originPageSlug is used."
|
|
303
|
+
)
|
|
304
|
+
},
|
|
305
|
+
outputSchema: {
|
|
306
|
+
exportId: z.string().describe("Resolved local export identifier used for copy operation."),
|
|
307
|
+
exportPath: z.string().describe("Absolute path to local extracted export project root."),
|
|
308
|
+
originPageSlug: z.string().describe("Normalized source page slug requested for copy."),
|
|
309
|
+
actualPageSlug: z.string().describe("Normalized destination slug used for target route paths."),
|
|
310
|
+
sourcePagePath: z.string().describe("Source page path relative to export root."),
|
|
311
|
+
targetPagePath: z.string().describe("Destination page path relative to user project root."),
|
|
312
|
+
sourceComponentsPath: z.string().describe("Source components directory relative to export root."),
|
|
313
|
+
targetComponentsPath: z.string().describe(
|
|
314
|
+
"Destination components directory relative to user project root."
|
|
315
|
+
),
|
|
316
|
+
newFiles: z.array(fileTransferSchema).describe("New files copied into user project without conflicts."),
|
|
317
|
+
identicalFiles: z.array(fileTransferSchema).describe(
|
|
318
|
+
"Existing files in user project that are byte-identical to export files."
|
|
319
|
+
),
|
|
320
|
+
conflictFiles: z.array(conflictFileSchema).describe(
|
|
321
|
+
"Files that already exist and differ from export version. Never overwritten."
|
|
322
|
+
),
|
|
323
|
+
addedDependencies: z.array(dependencyToAddSchema).describe(
|
|
324
|
+
"Dependencies that were missing and have been added to user's package.json."
|
|
325
|
+
),
|
|
326
|
+
dependenciesVersionConflicts: z.array(dependencyVersionConflictSchema).describe(
|
|
327
|
+
"Dependencies with version mismatch between export and user project. Report only."
|
|
328
|
+
),
|
|
329
|
+
summary: z.object({
|
|
330
|
+
totalCandidateFiles: z.number().describe("Total number of candidate files considered for page copy."),
|
|
331
|
+
copiedFiles: z.number().describe("Number of files copied as new."),
|
|
332
|
+
identicalFiles: z.number().describe("Number of files detected as identical."),
|
|
333
|
+
conflictFiles: z.number().describe("Number of conflicting files not copied."),
|
|
334
|
+
addedDependencies: z.number().describe("Count of dependencies added to package.json."),
|
|
335
|
+
dependenciesVersionConflicts: z.number().describe("Count of dependency version mismatches reported.")
|
|
336
|
+
})
|
|
337
|
+
},
|
|
338
|
+
annotations: {
|
|
339
|
+
readOnlyHint: false,
|
|
340
|
+
destructiveHint: false,
|
|
341
|
+
idempotentHint: false,
|
|
342
|
+
openWorldHint: false
|
|
343
|
+
}
|
|
344
|
+
};
|
|
252
345
|
var toolClearTemp = {
|
|
253
346
|
title: "PrimeUI Clear Temp",
|
|
254
347
|
description: `Delete all files in .primeui/temp/ and recreate empty temp structure.
|
|
@@ -358,11 +451,27 @@ function createPrimeUiMcpServer(source) {
|
|
|
358
451
|
}
|
|
359
452
|
}
|
|
360
453
|
);
|
|
454
|
+
server.registerTool(
|
|
455
|
+
"primeui_copy_page",
|
|
456
|
+
toolCopyPage,
|
|
457
|
+
async ({ originPageSlug, actualPageSlug }) => {
|
|
458
|
+
try {
|
|
459
|
+
const result = await source.copyPage(
|
|
460
|
+
originPageSlug,
|
|
461
|
+
actualPageSlug
|
|
462
|
+
);
|
|
463
|
+
return okResult("primeui_copy_page", result);
|
|
464
|
+
} catch (error) {
|
|
465
|
+
return errorResult(error);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
);
|
|
361
469
|
return server;
|
|
362
470
|
}
|
|
363
471
|
|
|
364
472
|
// src/services/project-sync-service.ts
|
|
365
|
-
import
|
|
473
|
+
import path4 from "path";
|
|
474
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
366
475
|
|
|
367
476
|
// src/lib/fs.ts
|
|
368
477
|
import { mkdir, rm, writeFile } from "fs/promises";
|
|
@@ -389,7 +498,875 @@ async function extractZip(zipPath, targetDir) {
|
|
|
389
498
|
}
|
|
390
499
|
}
|
|
391
500
|
|
|
501
|
+
// src/services/page-copy-service.ts
|
|
502
|
+
import { readFile as readFile2, readdir, stat as stat2, writeFile as writeFile2 } from "fs/promises";
|
|
503
|
+
import path3 from "path";
|
|
504
|
+
|
|
505
|
+
// src/lib/import-graph.ts
|
|
506
|
+
import { readFile, stat } from "fs/promises";
|
|
507
|
+
import { builtinModules } from "module";
|
|
508
|
+
import path2 from "path";
|
|
509
|
+
var INTERNAL_EXTENSIONS = [
|
|
510
|
+
".ts",
|
|
511
|
+
".tsx",
|
|
512
|
+
".js",
|
|
513
|
+
".jsx",
|
|
514
|
+
".mjs",
|
|
515
|
+
".cjs",
|
|
516
|
+
".json"
|
|
517
|
+
];
|
|
518
|
+
var PARSEABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
519
|
+
".ts",
|
|
520
|
+
".tsx",
|
|
521
|
+
".js",
|
|
522
|
+
".jsx",
|
|
523
|
+
".mjs",
|
|
524
|
+
".cjs"
|
|
525
|
+
]);
|
|
526
|
+
var BUILTIN_MODULES = /* @__PURE__ */ new Set([
|
|
527
|
+
...builtinModules,
|
|
528
|
+
...builtinModules.map((item) => `node:${item}`)
|
|
529
|
+
]);
|
|
530
|
+
var STATIC_IMPORT_RE = /\bimport\s+(?:type\s+)?(?:[\s\S]*?\sfrom\s+)?["']([^"']+)["']/g;
|
|
531
|
+
var EXPORT_FROM_RE = /\bexport\s+(?:[\s\S]*?\sfrom\s+)["']([^"']+)["']/g;
|
|
532
|
+
var DYNAMIC_IMPORT_RE = /\bimport\s*\(\s*["']([^"']+)["']\s*\)/g;
|
|
533
|
+
var REQUIRE_RE = /\brequire\s*\(\s*["']([^"']+)["']\s*\)/g;
|
|
534
|
+
function collectSpecifiers(content) {
|
|
535
|
+
const results = /* @__PURE__ */ new Set();
|
|
536
|
+
let match = null;
|
|
537
|
+
for (const re of [
|
|
538
|
+
STATIC_IMPORT_RE,
|
|
539
|
+
EXPORT_FROM_RE,
|
|
540
|
+
DYNAMIC_IMPORT_RE,
|
|
541
|
+
REQUIRE_RE
|
|
542
|
+
]) {
|
|
543
|
+
re.lastIndex = 0;
|
|
544
|
+
while ((match = re.exec(content)) !== null) {
|
|
545
|
+
const specifier = match[1]?.trim();
|
|
546
|
+
if (specifier) {
|
|
547
|
+
results.add(specifier);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
return [...results];
|
|
552
|
+
}
|
|
553
|
+
function getExternalPackageName(specifier) {
|
|
554
|
+
if (!specifier || specifier.startsWith(".") || specifier.startsWith("/")) {
|
|
555
|
+
return null;
|
|
556
|
+
}
|
|
557
|
+
if (specifier.startsWith("node:")) {
|
|
558
|
+
return null;
|
|
559
|
+
}
|
|
560
|
+
if (specifier.startsWith("@/") || specifier.startsWith("@root/")) {
|
|
561
|
+
return null;
|
|
562
|
+
}
|
|
563
|
+
const parts = specifier.split("/");
|
|
564
|
+
if (parts.length === 0) {
|
|
565
|
+
return null;
|
|
566
|
+
}
|
|
567
|
+
if (specifier.startsWith("@")) {
|
|
568
|
+
if (parts.length < 2) {
|
|
569
|
+
return null;
|
|
570
|
+
}
|
|
571
|
+
return `${parts[0]}/${parts[1]}`;
|
|
572
|
+
}
|
|
573
|
+
return parts[0] ?? null;
|
|
574
|
+
}
|
|
575
|
+
async function pathExists(filePath) {
|
|
576
|
+
try {
|
|
577
|
+
await stat(filePath);
|
|
578
|
+
return true;
|
|
579
|
+
} catch {
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
async function resolveFileCandidate(candidateBase) {
|
|
584
|
+
const ext = path2.extname(candidateBase);
|
|
585
|
+
if (ext) {
|
|
586
|
+
if (await pathExists(candidateBase)) {
|
|
587
|
+
return candidateBase;
|
|
588
|
+
}
|
|
589
|
+
return null;
|
|
590
|
+
}
|
|
591
|
+
for (const extension of INTERNAL_EXTENSIONS) {
|
|
592
|
+
const withExtension = `${candidateBase}${extension}`;
|
|
593
|
+
if (await pathExists(withExtension)) {
|
|
594
|
+
return withExtension;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
if (await pathExists(candidateBase)) {
|
|
598
|
+
const stats = await stat(candidateBase);
|
|
599
|
+
if (stats.isFile()) {
|
|
600
|
+
return candidateBase;
|
|
601
|
+
}
|
|
602
|
+
if (stats.isDirectory()) {
|
|
603
|
+
for (const extension of INTERNAL_EXTENSIONS) {
|
|
604
|
+
const indexCandidate = path2.join(candidateBase, `index${extension}`);
|
|
605
|
+
if (await pathExists(indexCandidate)) {
|
|
606
|
+
return indexCandidate;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
async function resolveImportToFile(projectRoot, importerFilePath, specifier) {
|
|
614
|
+
const externalPackageName = getExternalPackageName(specifier);
|
|
615
|
+
if (externalPackageName) {
|
|
616
|
+
if (!BUILTIN_MODULES.has(externalPackageName)) {
|
|
617
|
+
return {
|
|
618
|
+
kind: "external",
|
|
619
|
+
packageName: externalPackageName
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
return { kind: "unknown" };
|
|
623
|
+
}
|
|
624
|
+
let candidateBase = null;
|
|
625
|
+
if (specifier.startsWith("@/")) {
|
|
626
|
+
candidateBase = path2.join(projectRoot, "src", specifier.slice(2));
|
|
627
|
+
} else if (specifier.startsWith("@root/")) {
|
|
628
|
+
candidateBase = path2.join(projectRoot, specifier.slice(6));
|
|
629
|
+
} else if (specifier.startsWith(".")) {
|
|
630
|
+
candidateBase = path2.resolve(path2.dirname(importerFilePath), specifier);
|
|
631
|
+
} else if (specifier.startsWith("/")) {
|
|
632
|
+
candidateBase = path2.join(projectRoot, specifier.slice(1));
|
|
633
|
+
} else {
|
|
634
|
+
return { kind: "unknown" };
|
|
635
|
+
}
|
|
636
|
+
const resolved = await resolveFileCandidate(candidateBase);
|
|
637
|
+
if (!resolved) {
|
|
638
|
+
return { kind: "unknown" };
|
|
639
|
+
}
|
|
640
|
+
return {
|
|
641
|
+
kind: "internal",
|
|
642
|
+
filePath: resolved
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
function shouldParseFile(filePath) {
|
|
646
|
+
return PARSEABLE_EXTENSIONS.has(path2.extname(filePath).toLowerCase());
|
|
647
|
+
}
|
|
648
|
+
async function buildImportGraph(input) {
|
|
649
|
+
const projectRoot = path2.resolve(input.projectRoot);
|
|
650
|
+
const queue = input.entryFiles.map((filePath) => path2.resolve(filePath));
|
|
651
|
+
const visited = /* @__PURE__ */ new Set();
|
|
652
|
+
const internalFiles = /* @__PURE__ */ new Set();
|
|
653
|
+
const externalPackages = /* @__PURE__ */ new Set();
|
|
654
|
+
while (queue.length > 0) {
|
|
655
|
+
const currentFilePath = queue.shift();
|
|
656
|
+
if (!currentFilePath || visited.has(currentFilePath)) {
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
visited.add(currentFilePath);
|
|
660
|
+
internalFiles.add(currentFilePath);
|
|
661
|
+
if (!shouldParseFile(currentFilePath)) {
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
let content = "";
|
|
665
|
+
try {
|
|
666
|
+
content = await readFile(currentFilePath, "utf-8");
|
|
667
|
+
} catch {
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
const specifiers = collectSpecifiers(content);
|
|
671
|
+
for (const specifier of specifiers) {
|
|
672
|
+
const resolved = await resolveImportToFile(
|
|
673
|
+
projectRoot,
|
|
674
|
+
currentFilePath,
|
|
675
|
+
specifier
|
|
676
|
+
);
|
|
677
|
+
if (resolved.kind === "internal") {
|
|
678
|
+
if (!visited.has(resolved.filePath)) {
|
|
679
|
+
queue.push(resolved.filePath);
|
|
680
|
+
}
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
if (resolved.kind === "external") {
|
|
684
|
+
externalPackages.add(resolved.packageName);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
return {
|
|
689
|
+
internalFiles: [...internalFiles].sort(),
|
|
690
|
+
externalPackages: [...externalPackages].sort()
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// src/lib/page-paths.ts
|
|
695
|
+
var WEBSITE_APP_ROOT = "src/app/(website)";
|
|
696
|
+
var PAGE_COMPONENTS_ROOT = "src/components/pages";
|
|
697
|
+
var normalizeSlug = (slug) => {
|
|
698
|
+
const trimmed = slug.trim();
|
|
699
|
+
if (!trimmed || trimmed === "/") {
|
|
700
|
+
return "/";
|
|
701
|
+
}
|
|
702
|
+
const withLeadingSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
703
|
+
const withoutTrailingSlash = withLeadingSlash.replace(/\/+$/, "");
|
|
704
|
+
return withoutTrailingSlash || "/";
|
|
705
|
+
};
|
|
706
|
+
var slugToFolderPath = (normalizedSlug) => {
|
|
707
|
+
if (normalizedSlug === "/") {
|
|
708
|
+
return "";
|
|
709
|
+
}
|
|
710
|
+
return normalizedSlug.slice(1);
|
|
711
|
+
};
|
|
712
|
+
var toPathSegment = (value) => {
|
|
713
|
+
const normalized = value.trim().toLowerCase().replace(/[^a-z0-9/-]+/g, "-");
|
|
714
|
+
const compact = normalized.replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
715
|
+
return compact || "page";
|
|
716
|
+
};
|
|
717
|
+
var buildWebsitePagePath = (normalizedSlug) => {
|
|
718
|
+
if (normalizedSlug === "/") {
|
|
719
|
+
return `${WEBSITE_APP_ROOT}/page.tsx`;
|
|
720
|
+
}
|
|
721
|
+
return `${WEBSITE_APP_ROOT}${normalizedSlug}/page.tsx`;
|
|
722
|
+
};
|
|
723
|
+
var buildComponentsPath = (folderPath) => `${PAGE_COMPONENTS_ROOT}/${folderPath}`;
|
|
724
|
+
var resolveBlogSectionRoot = (slugFolderPath) => {
|
|
725
|
+
if (!slugFolderPath) {
|
|
726
|
+
return "blog";
|
|
727
|
+
}
|
|
728
|
+
const segments = slugFolderPath.split("/").filter(Boolean);
|
|
729
|
+
if (segments.length <= 1) {
|
|
730
|
+
return "blog";
|
|
731
|
+
}
|
|
732
|
+
return segments.slice(0, -1).join("/");
|
|
733
|
+
};
|
|
734
|
+
function resolvePrimeUiPageExportPaths({
|
|
735
|
+
pageType,
|
|
736
|
+
slug
|
|
737
|
+
}) {
|
|
738
|
+
const normalizedSlug = normalizeSlug(slug);
|
|
739
|
+
const slugFolderPath = slugToFolderPath(normalizedSlug);
|
|
740
|
+
switch (pageType) {
|
|
741
|
+
case "landing": {
|
|
742
|
+
const componentsFolder = slugFolderPath || "home";
|
|
743
|
+
return {
|
|
744
|
+
pagePath: buildWebsitePagePath(normalizedSlug),
|
|
745
|
+
componentsPath: buildComponentsPath(componentsFolder)
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
case "pricing":
|
|
749
|
+
case "contact-us": {
|
|
750
|
+
const componentsFolder = slugFolderPath || pageType;
|
|
751
|
+
return {
|
|
752
|
+
pagePath: buildWebsitePagePath(normalizedSlug),
|
|
753
|
+
componentsPath: buildComponentsPath(componentsFolder)
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
case "docs": {
|
|
757
|
+
const docsFolder = slugFolderPath || "docs";
|
|
758
|
+
return {
|
|
759
|
+
pagePath: `${WEBSITE_APP_ROOT}/${docsFolder}/[[...slug]]/page.tsx`,
|
|
760
|
+
componentsPath: buildComponentsPath("docs")
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
case "blogIndex": {
|
|
764
|
+
const blogFolder = slugFolderPath || "blog";
|
|
765
|
+
return {
|
|
766
|
+
pagePath: `${WEBSITE_APP_ROOT}/${blogFolder}/page.tsx`,
|
|
767
|
+
componentsPath: buildComponentsPath("blog")
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
case "blogPost": {
|
|
771
|
+
const blogSectionRoot = resolveBlogSectionRoot(slugFolderPath);
|
|
772
|
+
return {
|
|
773
|
+
pagePath: `${WEBSITE_APP_ROOT}/${blogSectionRoot}/[slug]/page.tsx`,
|
|
774
|
+
componentsPath: buildComponentsPath("blog")
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
case "legal":
|
|
778
|
+
return {
|
|
779
|
+
pagePath: buildWebsitePagePath(normalizedSlug),
|
|
780
|
+
componentsPath: buildComponentsPath("legal")
|
|
781
|
+
};
|
|
782
|
+
default: {
|
|
783
|
+
const fallbackFolder = slugFolderPath || toPathSegment(pageType);
|
|
784
|
+
return {
|
|
785
|
+
pagePath: buildWebsitePagePath(normalizedSlug),
|
|
786
|
+
componentsPath: buildComponentsPath(fallbackFolder)
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// src/lib/text-diff.ts
|
|
793
|
+
var DEFAULT_MAX_DIFF_CHARS = 16e3;
|
|
794
|
+
var MAX_LCS_CELLS = 2e6;
|
|
795
|
+
function computeOps(oldLines, newLines) {
|
|
796
|
+
const oldLength = oldLines.length;
|
|
797
|
+
const newLength = newLines.length;
|
|
798
|
+
const table = Array.from(
|
|
799
|
+
{ length: oldLength + 1 },
|
|
800
|
+
() => Array(newLength + 1).fill(0)
|
|
801
|
+
);
|
|
802
|
+
for (let i2 = oldLength - 1; i2 >= 0; i2 -= 1) {
|
|
803
|
+
for (let j2 = newLength - 1; j2 >= 0; j2 -= 1) {
|
|
804
|
+
if (oldLines[i2] === newLines[j2]) {
|
|
805
|
+
table[i2][j2] = table[i2 + 1][j2 + 1] + 1;
|
|
806
|
+
} else {
|
|
807
|
+
table[i2][j2] = Math.max(table[i2 + 1][j2], table[i2][j2 + 1]);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
const ops = [];
|
|
812
|
+
let i = 0;
|
|
813
|
+
let j = 0;
|
|
814
|
+
while (i < oldLength && j < newLength) {
|
|
815
|
+
if (oldLines[i] === newLines[j]) {
|
|
816
|
+
ops.push({ kind: "equal", value: oldLines[i] ?? "" });
|
|
817
|
+
i += 1;
|
|
818
|
+
j += 1;
|
|
819
|
+
continue;
|
|
820
|
+
}
|
|
821
|
+
if (table[i + 1][j] >= table[i][j + 1]) {
|
|
822
|
+
ops.push({ kind: "delete", value: oldLines[i] ?? "" });
|
|
823
|
+
i += 1;
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
ops.push({ kind: "insert", value: newLines[j] ?? "" });
|
|
827
|
+
j += 1;
|
|
828
|
+
}
|
|
829
|
+
while (i < oldLength) {
|
|
830
|
+
ops.push({ kind: "delete", value: oldLines[i] ?? "" });
|
|
831
|
+
i += 1;
|
|
832
|
+
}
|
|
833
|
+
while (j < newLength) {
|
|
834
|
+
ops.push({ kind: "insert", value: newLines[j] ?? "" });
|
|
835
|
+
j += 1;
|
|
836
|
+
}
|
|
837
|
+
return ops;
|
|
838
|
+
}
|
|
839
|
+
function buildUnifiedDiff(input) {
|
|
840
|
+
const maxChars = input.maxChars ?? DEFAULT_MAX_DIFF_CHARS;
|
|
841
|
+
if (input.oldText === input.newText) {
|
|
842
|
+
return "";
|
|
843
|
+
}
|
|
844
|
+
const oldLines = input.oldText.split(/\r?\n/);
|
|
845
|
+
const newLines = input.newText.split(/\r?\n/);
|
|
846
|
+
const cells = (oldLines.length + 1) * (newLines.length + 1);
|
|
847
|
+
if (cells > MAX_LCS_CELLS) {
|
|
848
|
+
return [
|
|
849
|
+
`--- ${input.oldLabel}`,
|
|
850
|
+
`+++ ${input.newLabel}`,
|
|
851
|
+
"@@ diff omitted @@",
|
|
852
|
+
"Diff is too large to render safely."
|
|
853
|
+
].join("\n");
|
|
854
|
+
}
|
|
855
|
+
const ops = computeOps(oldLines, newLines);
|
|
856
|
+
const output = [
|
|
857
|
+
`--- ${input.oldLabel}`,
|
|
858
|
+
`+++ ${input.newLabel}`,
|
|
859
|
+
`@@ -1,${oldLines.length} +1,${newLines.length} @@`
|
|
860
|
+
];
|
|
861
|
+
for (const op of ops) {
|
|
862
|
+
if (op.kind === "equal") {
|
|
863
|
+
output.push(` ${op.value}`);
|
|
864
|
+
continue;
|
|
865
|
+
}
|
|
866
|
+
if (op.kind === "delete") {
|
|
867
|
+
output.push(`-${op.value}`);
|
|
868
|
+
continue;
|
|
869
|
+
}
|
|
870
|
+
output.push(`+${op.value}`);
|
|
871
|
+
}
|
|
872
|
+
const diff = output.join("\n");
|
|
873
|
+
if (diff.length <= maxChars) {
|
|
874
|
+
return diff;
|
|
875
|
+
}
|
|
876
|
+
return `${diff.slice(0, maxChars)}
|
|
877
|
+
... [diff truncated]`;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// src/services/page-copy-service.ts
|
|
881
|
+
var DEPENDENCY_SECTIONS = [
|
|
882
|
+
"dependencies",
|
|
883
|
+
"devDependencies",
|
|
884
|
+
"peerDependencies"
|
|
885
|
+
];
|
|
886
|
+
var REWRITABLE_IMPORT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
887
|
+
".ts",
|
|
888
|
+
".tsx",
|
|
889
|
+
".js",
|
|
890
|
+
".jsx",
|
|
891
|
+
".mjs",
|
|
892
|
+
".cjs"
|
|
893
|
+
]);
|
|
894
|
+
function normalizeSlug2(slug) {
|
|
895
|
+
const trimmed = slug.trim();
|
|
896
|
+
if (!trimmed || trimmed === "/") {
|
|
897
|
+
return "/";
|
|
898
|
+
}
|
|
899
|
+
const withLeadingSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
900
|
+
return withLeadingSlash.replace(/\/+$/, "") || "/";
|
|
901
|
+
}
|
|
902
|
+
function toPosixPath(value) {
|
|
903
|
+
return value.split(path3.sep).join("/");
|
|
904
|
+
}
|
|
905
|
+
function toProjectRelative(rootPath, absolutePath) {
|
|
906
|
+
return toPosixPath(path3.relative(rootPath, absolutePath));
|
|
907
|
+
}
|
|
908
|
+
function escapeRegExp(value) {
|
|
909
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
910
|
+
}
|
|
911
|
+
function isBinaryBuffer(buffer) {
|
|
912
|
+
const limit = Math.min(buffer.length, 8e3);
|
|
913
|
+
for (let index = 0; index < limit; index += 1) {
|
|
914
|
+
if (buffer[index] === 0) {
|
|
915
|
+
return true;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
return false;
|
|
919
|
+
}
|
|
920
|
+
function stripSrcPrefix(relativePath) {
|
|
921
|
+
return relativePath.replace(/^src\//, "");
|
|
922
|
+
}
|
|
923
|
+
function buildImportRewritePlan(sourceComponentsPath, targetComponentsPath) {
|
|
924
|
+
const normalizedSource = toPosixPath(sourceComponentsPath);
|
|
925
|
+
const normalizedTarget = toPosixPath(targetComponentsPath);
|
|
926
|
+
if (normalizedSource === normalizedTarget) {
|
|
927
|
+
return null;
|
|
928
|
+
}
|
|
929
|
+
const sourceAtAliasPrefix = `@/${stripSrcPrefix(normalizedSource)}`;
|
|
930
|
+
const targetAtAliasPrefix = `@/${stripSrcPrefix(normalizedTarget)}`;
|
|
931
|
+
const sourceRootAliasPrefix = `@root/${normalizedSource}`;
|
|
932
|
+
const targetRootAliasPrefix = `@root/${normalizedTarget}`;
|
|
933
|
+
return {
|
|
934
|
+
sourceAtAliasPrefix,
|
|
935
|
+
targetAtAliasPrefix,
|
|
936
|
+
sourceRootAliasPrefix,
|
|
937
|
+
targetRootAliasPrefix
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
function replaceAliasPrefixInImportStrings(content, sourcePrefix, targetPrefix) {
|
|
941
|
+
const pattern = new RegExp(
|
|
942
|
+
`(["'\`])${escapeRegExp(sourcePrefix)}(?=\\/|\\1)`,
|
|
943
|
+
"g"
|
|
944
|
+
);
|
|
945
|
+
return content.replace(pattern, `$1${targetPrefix}`);
|
|
946
|
+
}
|
|
947
|
+
function rewriteImportsForRemappedSlug(content, plan) {
|
|
948
|
+
const afterAtAlias = replaceAliasPrefixInImportStrings(
|
|
949
|
+
content,
|
|
950
|
+
plan.sourceAtAliasPrefix,
|
|
951
|
+
plan.targetAtAliasPrefix
|
|
952
|
+
);
|
|
953
|
+
return replaceAliasPrefixInImportStrings(
|
|
954
|
+
afterAtAlias,
|
|
955
|
+
plan.sourceRootAliasPrefix,
|
|
956
|
+
plan.targetRootAliasPrefix
|
|
957
|
+
);
|
|
958
|
+
}
|
|
959
|
+
function buildPlannedSourceBuffer(sourceBuffer, sourceFilePath, importRewritePlan) {
|
|
960
|
+
if (!importRewritePlan) {
|
|
961
|
+
return sourceBuffer;
|
|
962
|
+
}
|
|
963
|
+
const extension = path3.extname(sourceFilePath).toLowerCase();
|
|
964
|
+
if (!REWRITABLE_IMPORT_EXTENSIONS.has(extension)) {
|
|
965
|
+
return sourceBuffer;
|
|
966
|
+
}
|
|
967
|
+
if (isBinaryBuffer(sourceBuffer)) {
|
|
968
|
+
return sourceBuffer;
|
|
969
|
+
}
|
|
970
|
+
const sourceText = sourceBuffer.toString("utf-8");
|
|
971
|
+
const rewritten = rewriteImportsForRemappedSlug(sourceText, importRewritePlan);
|
|
972
|
+
if (rewritten === sourceText) {
|
|
973
|
+
return sourceBuffer;
|
|
974
|
+
}
|
|
975
|
+
return Buffer.from(rewritten, "utf-8");
|
|
976
|
+
}
|
|
977
|
+
async function readJsonFile(filePath) {
|
|
978
|
+
const content = await readFile2(filePath, "utf-8");
|
|
979
|
+
return JSON.parse(content);
|
|
980
|
+
}
|
|
981
|
+
async function fileExists(filePath) {
|
|
982
|
+
try {
|
|
983
|
+
await stat2(filePath);
|
|
984
|
+
return true;
|
|
985
|
+
} catch {
|
|
986
|
+
return false;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
function isPageRecord(value) {
|
|
990
|
+
if (!value || typeof value !== "object") {
|
|
991
|
+
return false;
|
|
992
|
+
}
|
|
993
|
+
const maybe = value;
|
|
994
|
+
return typeof maybe.id === "string" && typeof maybe.title === "string" && typeof maybe.slug === "string" && typeof maybe.pageType === "string" && typeof maybe.isReadyToExport === "boolean" && typeof maybe.pagePath === "string" && typeof maybe.componentsPath === "string";
|
|
995
|
+
}
|
|
996
|
+
function parseManifest(value) {
|
|
997
|
+
if (!value || typeof value !== "object") {
|
|
998
|
+
throw new Error("Invalid export manifest format.");
|
|
999
|
+
}
|
|
1000
|
+
const maybe = value;
|
|
1001
|
+
if (maybe.schemaVersion !== 1 || typeof maybe.generatedAt !== "string" || typeof maybe.exportId !== "string" || typeof maybe.projectPath !== "string" || !Array.isArray(maybe.pages)) {
|
|
1002
|
+
throw new Error("Export manifest does not match expected schema.");
|
|
1003
|
+
}
|
|
1004
|
+
if (!maybe.pages.every(isPageRecord)) {
|
|
1005
|
+
throw new Error("Export manifest pages payload is invalid.");
|
|
1006
|
+
}
|
|
1007
|
+
return {
|
|
1008
|
+
schemaVersion: 1,
|
|
1009
|
+
generatedAt: maybe.generatedAt,
|
|
1010
|
+
exportId: maybe.exportId,
|
|
1011
|
+
projectPath: maybe.projectPath,
|
|
1012
|
+
pages: maybe.pages
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
async function listFilesRecursively(dirPath) {
|
|
1016
|
+
const files = [];
|
|
1017
|
+
const stack = [dirPath];
|
|
1018
|
+
while (stack.length > 0) {
|
|
1019
|
+
const current = stack.pop();
|
|
1020
|
+
if (!current) {
|
|
1021
|
+
continue;
|
|
1022
|
+
}
|
|
1023
|
+
const entries = await readdir(current, { withFileTypes: true });
|
|
1024
|
+
for (const entry of entries) {
|
|
1025
|
+
const absolutePath = path3.join(current, entry.name);
|
|
1026
|
+
if (entry.isDirectory()) {
|
|
1027
|
+
stack.push(absolutePath);
|
|
1028
|
+
continue;
|
|
1029
|
+
}
|
|
1030
|
+
if (entry.isFile()) {
|
|
1031
|
+
files.push(absolutePath);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
return files;
|
|
1036
|
+
}
|
|
1037
|
+
async function resolveSingleExportDirectory(exportsRoot) {
|
|
1038
|
+
const entries = await readdir(exportsRoot, { withFileTypes: true });
|
|
1039
|
+
const exportDirectories = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort((a, b) => a.localeCompare(b));
|
|
1040
|
+
if (exportDirectories.length === 0) {
|
|
1041
|
+
throw new Error(
|
|
1042
|
+
"No downloaded exports found in .primeui/temp/exports. Run primeui_download_export first."
|
|
1043
|
+
);
|
|
1044
|
+
}
|
|
1045
|
+
if (exportDirectories.length > 1) {
|
|
1046
|
+
throw new Error(
|
|
1047
|
+
"Multiple export folders found in .primeui/temp/exports. Clear temp with primeui_clear_temp and download one export."
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
const exportId = exportDirectories[0] ?? "";
|
|
1051
|
+
return {
|
|
1052
|
+
exportId,
|
|
1053
|
+
exportPath: path3.join(exportsRoot, exportId)
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
function ensureSafeTargetPath(projectRoot, targetPath) {
|
|
1057
|
+
const relative = path3.relative(projectRoot, targetPath);
|
|
1058
|
+
if (relative.startsWith("..") || path3.isAbsolute(relative)) {
|
|
1059
|
+
throw new Error(
|
|
1060
|
+
`Refusing to write outside project root. Computed target: ${targetPath}`
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
function getDependencyVersion(packageJson, packageName) {
|
|
1065
|
+
for (const section of DEPENDENCY_SECTIONS) {
|
|
1066
|
+
const sectionData = packageJson[section];
|
|
1067
|
+
if (!sectionData || typeof sectionData !== "object") {
|
|
1068
|
+
continue;
|
|
1069
|
+
}
|
|
1070
|
+
const value = sectionData[packageName];
|
|
1071
|
+
if (typeof value === "string") {
|
|
1072
|
+
return {
|
|
1073
|
+
section,
|
|
1074
|
+
version: value
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
return null;
|
|
1079
|
+
}
|
|
1080
|
+
function getOrCreateDependencySection(packageJson, section) {
|
|
1081
|
+
const existing = packageJson[section];
|
|
1082
|
+
if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
|
|
1083
|
+
const created = {};
|
|
1084
|
+
packageJson[section] = created;
|
|
1085
|
+
return created;
|
|
1086
|
+
}
|
|
1087
|
+
return existing;
|
|
1088
|
+
}
|
|
1089
|
+
function sortDependencySections(packageJson) {
|
|
1090
|
+
for (const section of DEPENDENCY_SECTIONS) {
|
|
1091
|
+
const sectionData = packageJson[section];
|
|
1092
|
+
if (!sectionData || typeof sectionData !== "object" || Array.isArray(sectionData)) {
|
|
1093
|
+
continue;
|
|
1094
|
+
}
|
|
1095
|
+
const sorted = Object.fromEntries(
|
|
1096
|
+
Object.entries(sectionData).sort(
|
|
1097
|
+
([a], [b]) => a.localeCompare(b)
|
|
1098
|
+
)
|
|
1099
|
+
);
|
|
1100
|
+
packageJson[section] = sorted;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
function resolveTargetFilePath(input) {
|
|
1104
|
+
const {
|
|
1105
|
+
sourceFilePath,
|
|
1106
|
+
sourcePagePath,
|
|
1107
|
+
sourceComponentsPath,
|
|
1108
|
+
targetPagePath,
|
|
1109
|
+
targetComponentsPath,
|
|
1110
|
+
exportRoot,
|
|
1111
|
+
projectRoot
|
|
1112
|
+
} = input;
|
|
1113
|
+
if (sourceFilePath === sourcePagePath) {
|
|
1114
|
+
return targetPagePath;
|
|
1115
|
+
}
|
|
1116
|
+
const componentsPrefix = `${sourceComponentsPath}${path3.sep}`;
|
|
1117
|
+
if (sourceFilePath === sourceComponentsPath || sourceFilePath.startsWith(componentsPrefix)) {
|
|
1118
|
+
const relativeToComponents = path3.relative(
|
|
1119
|
+
sourceComponentsPath,
|
|
1120
|
+
sourceFilePath
|
|
1121
|
+
);
|
|
1122
|
+
return path3.join(targetComponentsPath, relativeToComponents);
|
|
1123
|
+
}
|
|
1124
|
+
const relativeToExport = path3.relative(exportRoot, sourceFilePath);
|
|
1125
|
+
if (relativeToExport.startsWith("..") || path3.isAbsolute(relativeToExport)) {
|
|
1126
|
+
throw new Error(`Source file is outside export root: ${sourceFilePath}`);
|
|
1127
|
+
}
|
|
1128
|
+
return path3.join(projectRoot, relativeToExport);
|
|
1129
|
+
}
|
|
1130
|
+
async function copyPageFromExport(input) {
|
|
1131
|
+
const normalizedOriginSlug = normalizeSlug2(input.originPageSlug);
|
|
1132
|
+
const normalizedActualSlug = normalizeSlug2(
|
|
1133
|
+
input.actualPageSlug ?? input.originPageSlug
|
|
1134
|
+
);
|
|
1135
|
+
const isSlugRemapped = normalizedActualSlug !== normalizedOriginSlug;
|
|
1136
|
+
const { exportId, exportPath } = await resolveSingleExportDirectory(
|
|
1137
|
+
input.exportsRoot
|
|
1138
|
+
);
|
|
1139
|
+
const manifestPath = path3.join(
|
|
1140
|
+
input.exportsRoot,
|
|
1141
|
+
`${exportId}.manifest.json`
|
|
1142
|
+
);
|
|
1143
|
+
if (!await fileExists(manifestPath)) {
|
|
1144
|
+
throw new Error(
|
|
1145
|
+
`Export manifest is missing at ${manifestPath}. Run primeui_download_export to regenerate it.`
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1148
|
+
const manifest = parseManifest(await readJsonFile(manifestPath));
|
|
1149
|
+
if (manifest.exportId !== exportId) {
|
|
1150
|
+
throw new Error(
|
|
1151
|
+
`Manifest exportId mismatch: expected ${exportId}, got ${manifest.exportId}. Re-download export.`
|
|
1152
|
+
);
|
|
1153
|
+
}
|
|
1154
|
+
const page = manifest.pages.find(
|
|
1155
|
+
(item) => normalizeSlug2(item.slug) === normalizedOriginSlug
|
|
1156
|
+
);
|
|
1157
|
+
if (!page) {
|
|
1158
|
+
throw new Error(
|
|
1159
|
+
`Page not found in manifest for slug: ${normalizedOriginSlug}`
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
const sourcePagePath = path3.join(exportPath, page.pagePath);
|
|
1163
|
+
const sourceComponentsPath = path3.join(exportPath, page.componentsPath);
|
|
1164
|
+
const sourcePageStats = await stat2(sourcePagePath).catch(() => null);
|
|
1165
|
+
if (!sourcePageStats?.isFile()) {
|
|
1166
|
+
throw new Error(`Source page file not found: ${sourcePagePath}`);
|
|
1167
|
+
}
|
|
1168
|
+
const sourceComponentsStats = await stat2(sourceComponentsPath).catch(
|
|
1169
|
+
() => null
|
|
1170
|
+
);
|
|
1171
|
+
if (!sourceComponentsStats?.isDirectory()) {
|
|
1172
|
+
throw new Error(
|
|
1173
|
+
`Source components folder not found: ${sourceComponentsPath}`
|
|
1174
|
+
);
|
|
1175
|
+
}
|
|
1176
|
+
const targetPaths = normalizedActualSlug === normalizedOriginSlug ? {
|
|
1177
|
+
pagePath: page.pagePath,
|
|
1178
|
+
componentsPath: page.componentsPath
|
|
1179
|
+
} : resolvePrimeUiPageExportPaths({
|
|
1180
|
+
pageType: page.pageType,
|
|
1181
|
+
slug: normalizedActualSlug
|
|
1182
|
+
});
|
|
1183
|
+
const targetPagePath = path3.join(input.projectRoot, targetPaths.pagePath);
|
|
1184
|
+
const targetComponentsPath = path3.join(
|
|
1185
|
+
input.projectRoot,
|
|
1186
|
+
targetPaths.componentsPath
|
|
1187
|
+
);
|
|
1188
|
+
const importRewritePlan = isSlugRemapped ? buildImportRewritePlan(page.componentsPath, targetPaths.componentsPath) : null;
|
|
1189
|
+
ensureSafeTargetPath(input.projectRoot, targetPagePath);
|
|
1190
|
+
ensureSafeTargetPath(input.projectRoot, targetComponentsPath);
|
|
1191
|
+
const componentFiles = await listFilesRecursively(sourceComponentsPath);
|
|
1192
|
+
const seedFiles = [sourcePagePath, ...componentFiles];
|
|
1193
|
+
const importGraph = await buildImportGraph({
|
|
1194
|
+
projectRoot: exportPath,
|
|
1195
|
+
entryFiles: seedFiles
|
|
1196
|
+
});
|
|
1197
|
+
const candidateFiles = [...new Set(importGraph.internalFiles)].sort(
|
|
1198
|
+
(a, b) => a.localeCompare(b)
|
|
1199
|
+
);
|
|
1200
|
+
const newFiles = [];
|
|
1201
|
+
const identicalFiles = [];
|
|
1202
|
+
const conflictFiles = [];
|
|
1203
|
+
for (const sourceFilePath of candidateFiles) {
|
|
1204
|
+
const targetFilePath = resolveTargetFilePath({
|
|
1205
|
+
sourceFilePath,
|
|
1206
|
+
sourcePagePath,
|
|
1207
|
+
sourceComponentsPath,
|
|
1208
|
+
targetPagePath,
|
|
1209
|
+
targetComponentsPath,
|
|
1210
|
+
exportRoot: exportPath,
|
|
1211
|
+
projectRoot: input.projectRoot
|
|
1212
|
+
});
|
|
1213
|
+
ensureSafeTargetPath(input.projectRoot, targetFilePath);
|
|
1214
|
+
const sourceBuffer = await readFile2(sourceFilePath);
|
|
1215
|
+
const plannedSourceBuffer = buildPlannedSourceBuffer(
|
|
1216
|
+
sourceBuffer,
|
|
1217
|
+
sourceFilePath,
|
|
1218
|
+
importRewritePlan
|
|
1219
|
+
);
|
|
1220
|
+
let targetBuffer = null;
|
|
1221
|
+
try {
|
|
1222
|
+
targetBuffer = await readFile2(targetFilePath);
|
|
1223
|
+
} catch {
|
|
1224
|
+
targetBuffer = null;
|
|
1225
|
+
}
|
|
1226
|
+
const sourceRelative = toProjectRelative(exportPath, sourceFilePath);
|
|
1227
|
+
const targetRelative = toProjectRelative(input.projectRoot, targetFilePath);
|
|
1228
|
+
if (!targetBuffer) {
|
|
1229
|
+
await ensureDir(path3.dirname(targetFilePath));
|
|
1230
|
+
await writeFile2(targetFilePath, plannedSourceBuffer);
|
|
1231
|
+
newFiles.push({
|
|
1232
|
+
sourcePath: sourceRelative,
|
|
1233
|
+
targetPath: targetRelative
|
|
1234
|
+
});
|
|
1235
|
+
continue;
|
|
1236
|
+
}
|
|
1237
|
+
if (plannedSourceBuffer.equals(targetBuffer)) {
|
|
1238
|
+
identicalFiles.push({
|
|
1239
|
+
sourcePath: sourceRelative,
|
|
1240
|
+
targetPath: targetRelative
|
|
1241
|
+
});
|
|
1242
|
+
continue;
|
|
1243
|
+
}
|
|
1244
|
+
const isBinary = isBinaryBuffer(plannedSourceBuffer) || isBinaryBuffer(targetBuffer);
|
|
1245
|
+
const diff = isBinary ? "Binary files differ." : buildUnifiedDiff({
|
|
1246
|
+
oldText: targetBuffer.toString("utf-8"),
|
|
1247
|
+
newText: plannedSourceBuffer.toString("utf-8"),
|
|
1248
|
+
oldLabel: `user/${targetRelative}`,
|
|
1249
|
+
newLabel: `export/${sourceRelative}`
|
|
1250
|
+
});
|
|
1251
|
+
conflictFiles.push({
|
|
1252
|
+
sourcePath: sourceRelative,
|
|
1253
|
+
targetPath: targetRelative,
|
|
1254
|
+
diff,
|
|
1255
|
+
isBinary
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
const exportPackageJsonPath = path3.join(exportPath, "package.json");
|
|
1259
|
+
const userPackageJsonPath = path3.join(input.projectRoot, "package.json");
|
|
1260
|
+
if (!await fileExists(exportPackageJsonPath)) {
|
|
1261
|
+
throw new Error(`Export package.json not found: ${exportPackageJsonPath}`);
|
|
1262
|
+
}
|
|
1263
|
+
if (!await fileExists(userPackageJsonPath)) {
|
|
1264
|
+
throw new Error(`User package.json not found: ${userPackageJsonPath}`);
|
|
1265
|
+
}
|
|
1266
|
+
const exportPackageJson = await readJsonFile(
|
|
1267
|
+
exportPackageJsonPath
|
|
1268
|
+
);
|
|
1269
|
+
const userPackageJson = await readJsonFile(userPackageJsonPath);
|
|
1270
|
+
const addedDependencies = [];
|
|
1271
|
+
const dependenciesVersionConflicts = [];
|
|
1272
|
+
for (const packageName of importGraph.externalPackages) {
|
|
1273
|
+
const exportDependency = getDependencyVersion(
|
|
1274
|
+
exportPackageJson,
|
|
1275
|
+
packageName
|
|
1276
|
+
);
|
|
1277
|
+
if (!exportDependency) {
|
|
1278
|
+
continue;
|
|
1279
|
+
}
|
|
1280
|
+
const userDependency = getDependencyVersion(userPackageJson, packageName);
|
|
1281
|
+
if (!userDependency) {
|
|
1282
|
+
const sectionData = getOrCreateDependencySection(
|
|
1283
|
+
userPackageJson,
|
|
1284
|
+
exportDependency.section
|
|
1285
|
+
);
|
|
1286
|
+
sectionData[packageName] = exportDependency.version;
|
|
1287
|
+
addedDependencies.push({
|
|
1288
|
+
packageName,
|
|
1289
|
+
version: exportDependency.version,
|
|
1290
|
+
section: exportDependency.section
|
|
1291
|
+
});
|
|
1292
|
+
continue;
|
|
1293
|
+
}
|
|
1294
|
+
if (userDependency.version !== exportDependency.version) {
|
|
1295
|
+
dependenciesVersionConflicts.push({
|
|
1296
|
+
packageName,
|
|
1297
|
+
section: exportDependency.section,
|
|
1298
|
+
exportVersion: exportDependency.version,
|
|
1299
|
+
userVersion: userDependency.version
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
if (addedDependencies.length > 0) {
|
|
1304
|
+
sortDependencySections(userPackageJson);
|
|
1305
|
+
await writeFile2(
|
|
1306
|
+
userPackageJsonPath,
|
|
1307
|
+
`${JSON.stringify(userPackageJson, null, 2)}
|
|
1308
|
+
`,
|
|
1309
|
+
"utf-8"
|
|
1310
|
+
);
|
|
1311
|
+
}
|
|
1312
|
+
return {
|
|
1313
|
+
exportId,
|
|
1314
|
+
exportPath,
|
|
1315
|
+
originPageSlug: normalizedOriginSlug,
|
|
1316
|
+
actualPageSlug: normalizedActualSlug,
|
|
1317
|
+
sourcePagePath: page.pagePath,
|
|
1318
|
+
targetPagePath: targetPaths.pagePath,
|
|
1319
|
+
sourceComponentsPath: page.componentsPath,
|
|
1320
|
+
targetComponentsPath: targetPaths.componentsPath,
|
|
1321
|
+
newFiles,
|
|
1322
|
+
identicalFiles,
|
|
1323
|
+
conflictFiles,
|
|
1324
|
+
addedDependencies: addedDependencies.sort(
|
|
1325
|
+
(a, b) => a.packageName.localeCompare(b.packageName)
|
|
1326
|
+
),
|
|
1327
|
+
dependenciesVersionConflicts: dependenciesVersionConflicts.sort(
|
|
1328
|
+
(a, b) => a.packageName.localeCompare(b.packageName)
|
|
1329
|
+
),
|
|
1330
|
+
summary: {
|
|
1331
|
+
totalCandidateFiles: candidateFiles.length,
|
|
1332
|
+
copiedFiles: newFiles.length,
|
|
1333
|
+
identicalFiles: identicalFiles.length,
|
|
1334
|
+
conflictFiles: conflictFiles.length,
|
|
1335
|
+
addedDependencies: addedDependencies.length,
|
|
1336
|
+
dependenciesVersionConflicts: dependenciesVersionConflicts.length
|
|
1337
|
+
}
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
|
|
392
1341
|
// src/services/project-sync-service.ts
|
|
1342
|
+
function isProjectPage(value) {
|
|
1343
|
+
if (!value || typeof value !== "object") {
|
|
1344
|
+
return false;
|
|
1345
|
+
}
|
|
1346
|
+
const maybe = value;
|
|
1347
|
+
return typeof maybe.id === "string" && typeof maybe.title === "string" && typeof maybe.slug === "string" && typeof maybe.pageType === "string" && typeof maybe.isReadyToExport === "boolean" && typeof maybe.pagePath === "string" && typeof maybe.componentsPath === "string";
|
|
1348
|
+
}
|
|
1349
|
+
function buildPagesSnapshotPath(exportsRoot, exportId) {
|
|
1350
|
+
return path4.join(exportsRoot, `${exportId}.pages.snapshot.json`);
|
|
1351
|
+
}
|
|
1352
|
+
function parsePagesSnapshot(value) {
|
|
1353
|
+
if (!value || typeof value !== "object") {
|
|
1354
|
+
throw new Error("Export pages snapshot is invalid.");
|
|
1355
|
+
}
|
|
1356
|
+
const maybe = value;
|
|
1357
|
+
if (maybe.schemaVersion !== 1 || typeof maybe.generatedAt !== "string" || typeof maybe.exportId !== "string" || !Array.isArray(maybe.pages)) {
|
|
1358
|
+
throw new Error("Export pages snapshot does not match expected schema.");
|
|
1359
|
+
}
|
|
1360
|
+
if (!maybe.pages.every(isProjectPage)) {
|
|
1361
|
+
throw new Error("Export pages snapshot has invalid pages payload.");
|
|
1362
|
+
}
|
|
1363
|
+
return {
|
|
1364
|
+
schemaVersion: 1,
|
|
1365
|
+
generatedAt: maybe.generatedAt,
|
|
1366
|
+
exportId: maybe.exportId,
|
|
1367
|
+
pages: maybe.pages
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
393
1370
|
var ProjectSyncService = class {
|
|
394
1371
|
provider;
|
|
395
1372
|
projectRoot;
|
|
@@ -399,9 +1376,9 @@ var ProjectSyncService = class {
|
|
|
399
1376
|
constructor(options) {
|
|
400
1377
|
this.projectRoot = options.projectRoot;
|
|
401
1378
|
this.provider = options.provider;
|
|
402
|
-
this.primeUiRoot =
|
|
403
|
-
this.tempRoot =
|
|
404
|
-
this.exportsRoot =
|
|
1379
|
+
this.primeUiRoot = path4.join(this.projectRoot, ".primeui");
|
|
1380
|
+
this.tempRoot = path4.join(this.primeUiRoot, "temp");
|
|
1381
|
+
this.exportsRoot = path4.join(this.tempRoot, "exports");
|
|
405
1382
|
}
|
|
406
1383
|
async getProjectInfo() {
|
|
407
1384
|
await this.ensureTempLayout();
|
|
@@ -413,7 +1390,23 @@ var ProjectSyncService = class {
|
|
|
413
1390
|
}
|
|
414
1391
|
async createExport() {
|
|
415
1392
|
await this.ensureTempLayout();
|
|
416
|
-
|
|
1393
|
+
const result = await this.provider.createExport();
|
|
1394
|
+
const pagesSnapshotPath = buildPagesSnapshotPath(
|
|
1395
|
+
this.exportsRoot,
|
|
1396
|
+
result.export.id
|
|
1397
|
+
);
|
|
1398
|
+
const snapshot = {
|
|
1399
|
+
schemaVersion: 1,
|
|
1400
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1401
|
+
exportId: result.export.id,
|
|
1402
|
+
pages: result.pages
|
|
1403
|
+
};
|
|
1404
|
+
await writeUtf8(
|
|
1405
|
+
pagesSnapshotPath,
|
|
1406
|
+
`${JSON.stringify(snapshot, null, 2)}
|
|
1407
|
+
`
|
|
1408
|
+
);
|
|
1409
|
+
return result;
|
|
417
1410
|
}
|
|
418
1411
|
async downloadExportById(id) {
|
|
419
1412
|
await this.ensureTempLayout();
|
|
@@ -422,36 +1415,74 @@ var ProjectSyncService = class {
|
|
|
422
1415
|
if (!selected) {
|
|
423
1416
|
throw new Error(`Export not found: ${id}`);
|
|
424
1417
|
}
|
|
425
|
-
const targetZipPath =
|
|
426
|
-
const targetProjectPath =
|
|
1418
|
+
const targetZipPath = path4.join(this.exportsRoot, `${id}.zip`);
|
|
1419
|
+
const targetProjectPath = path4.join(this.exportsRoot, id);
|
|
427
1420
|
await ensureDir(this.exportsRoot);
|
|
428
1421
|
await this.provider.downloadExportArchive(id, targetZipPath);
|
|
429
1422
|
await resetDir(targetProjectPath);
|
|
430
1423
|
await extractZip(targetZipPath, targetProjectPath);
|
|
431
|
-
const
|
|
1424
|
+
const pagesSnapshotPath = buildPagesSnapshotPath(this.exportsRoot, id);
|
|
1425
|
+
let pagesSnapshot;
|
|
1426
|
+
try {
|
|
1427
|
+
const snapshotRaw = JSON.parse(
|
|
1428
|
+
await readFile3(pagesSnapshotPath, "utf-8")
|
|
1429
|
+
);
|
|
1430
|
+
pagesSnapshot = parsePagesSnapshot(snapshotRaw);
|
|
1431
|
+
} catch (error) {
|
|
1432
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
1433
|
+
throw new Error(
|
|
1434
|
+
`Export pages snapshot is required for download id "${id}". Run primeui_create_export for this export before downloading. Details: ${reason}`
|
|
1435
|
+
);
|
|
1436
|
+
}
|
|
1437
|
+
if (pagesSnapshot.exportId !== id) {
|
|
1438
|
+
throw new Error(
|
|
1439
|
+
`Export pages snapshot mismatch for id "${id}". Run primeui_create_export and retry download.`
|
|
1440
|
+
);
|
|
1441
|
+
}
|
|
1442
|
+
const pages = pagesSnapshot.pages;
|
|
1443
|
+
const manifestPath = path4.join(this.exportsRoot, `${id}.manifest.json`);
|
|
1444
|
+
const manifest = {
|
|
1445
|
+
schemaVersion: 1,
|
|
1446
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1447
|
+
exportId: id,
|
|
1448
|
+
projectPath: targetProjectPath,
|
|
1449
|
+
pages
|
|
1450
|
+
};
|
|
1451
|
+
await writeUtf8(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
1452
|
+
`);
|
|
432
1453
|
return {
|
|
433
1454
|
exportId: id,
|
|
434
1455
|
projectPath: targetProjectPath,
|
|
1456
|
+
manifestPath,
|
|
435
1457
|
pages
|
|
436
1458
|
};
|
|
437
1459
|
}
|
|
1460
|
+
async copyPage(originPageSlug, actualPageSlug) {
|
|
1461
|
+
await this.ensureTempLayout();
|
|
1462
|
+
return copyPageFromExport({
|
|
1463
|
+
projectRoot: this.projectRoot,
|
|
1464
|
+
exportsRoot: this.exportsRoot,
|
|
1465
|
+
originPageSlug,
|
|
1466
|
+
actualPageSlug
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
438
1469
|
async clearTemp() {
|
|
439
1470
|
await resetDir(this.tempRoot);
|
|
440
|
-
await writeUtf8(
|
|
1471
|
+
await writeUtf8(path4.join(this.tempRoot, ".gitkeep"), "");
|
|
441
1472
|
await ensureDir(this.exportsRoot);
|
|
442
1473
|
}
|
|
443
1474
|
async ensureTempLayout() {
|
|
444
1475
|
await ensureDir(this.primeUiRoot);
|
|
445
1476
|
await ensureDir(this.tempRoot);
|
|
446
1477
|
await ensureDir(this.exportsRoot);
|
|
447
|
-
await writeUtf8(
|
|
1478
|
+
await writeUtf8(path4.join(this.tempRoot, ".gitkeep"), "");
|
|
448
1479
|
}
|
|
449
1480
|
};
|
|
450
1481
|
|
|
451
1482
|
// src/sources/api-provider.ts
|
|
452
1483
|
import { createWriteStream } from "fs";
|
|
453
1484
|
import { unlink } from "fs/promises";
|
|
454
|
-
import
|
|
1485
|
+
import path5 from "path";
|
|
455
1486
|
import { Readable, Transform } from "stream";
|
|
456
1487
|
import { pipeline } from "stream/promises";
|
|
457
1488
|
import { z as z2 } from "zod";
|
|
@@ -620,7 +1651,7 @@ var ApiProjectDataProvider = class {
|
|
|
620
1651
|
if (!response.body) {
|
|
621
1652
|
throw new PrimeUiApiContractError(endpoint, "response body is empty");
|
|
622
1653
|
}
|
|
623
|
-
await ensureDir(
|
|
1654
|
+
await ensureDir(path5.dirname(destinationPath));
|
|
624
1655
|
const zipStream = Readable.fromWeb(
|
|
625
1656
|
response.body
|
|
626
1657
|
);
|