wp-migrate-core 0.1.0-demo
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/LICENSE +21 -0
- package/README.md +74 -0
- package/dist/src/adapters.d.ts +25 -0
- package/dist/src/adapters.js +11 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +250 -0
- package/dist/src/core.d.ts +9 -0
- package/dist/src/core.js +603 -0
- package/dist/src/generate.d.ts +2 -0
- package/dist/src/generate.js +555 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.js +5 -0
- package/dist/src/report.d.ts +7 -0
- package/dist/src/report.js +761 -0
- package/dist/src/types.d.ts +83 -0
- package/dist/src/types.js +1 -0
- package/fixtures/demo-wordpress.xml +111 -0
- package/package.json +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 wp-migrate-core contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# wp-migrate-core
|
|
2
|
+
|
|
3
|
+
`wp-migrate-core` is an intentionally limited, local CLI prototype for inspecting a WordPress WXR export and creating an Astro migration handoff. Version `0.1.0-demo` is published under npm's `demo` tag.
|
|
4
|
+
|
|
5
|
+
It can help expose what must be rebuilt. It does not produce a finished or production-ready WordPress replacement.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
Requires Node.js 20 or later.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install --save-dev wp-migrate-core@demo
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Run the included fictional fixture:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx wp-migrate-core demo --out wp-migrate-core-demo
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The demo writes a migration plan, a local HTML repair report, and an Astro-shaped project to `wp-migrate-core-demo/`. Open `wp-migrate-core-demo/migration-plan/report.html` locally to review the detected work.
|
|
22
|
+
|
|
23
|
+
## Commands
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
wp-migrate-core inspect <export.xml> [--out migration-plan] [--target astro]
|
|
27
|
+
wp-migrate-core convert <export.xml> --out <new-site> [--target astro]
|
|
28
|
+
wp-migrate-core report <export.xml> [--out migration-report.html]
|
|
29
|
+
wp-migrate-core demo [--out wp-migrate-core-demo]
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`inspect` writes `migration-plan.json` and a local repair report. `convert` writes an Astro-shaped project plus its migration manifest and repair report. `report` writes only the repair report. `demo` runs both inspection and conversion against the bundled fixture.
|
|
33
|
+
|
|
34
|
+
The `astro` target is the only enabled target in `0.1.0-demo`. `next` and `nuxt` are registered as planned targets and deliberately fail when selected.
|
|
35
|
+
|
|
36
|
+
## What the demo handles
|
|
37
|
+
|
|
38
|
+
- Published WordPress pages and posts in a WXR export.
|
|
39
|
+
- Routes, authors, dates, taxonomies, source IDs, and post metadata in the migration model.
|
|
40
|
+
- A bounded subset of serialized Gutenberg core blocks.
|
|
41
|
+
- Elementor `_elementor_data` in WXR post metadata, including simple containers, headings, text, images, buttons, dividers, and spacers.
|
|
42
|
+
- An explicit repair queue for dynamic Gutenberg blocks, unsupported shortcodes, Elementor forms and queries, and unknown Elementor widgets.
|
|
43
|
+
|
|
44
|
+
Each detected construct is marked as `native`, `legacy-html`, `manual`, or `blocked`. Unsupported behavior stays visible in the generated output and repair queue; it is not counted as a successful conversion.
|
|
45
|
+
|
|
46
|
+
## Important limits
|
|
47
|
+
|
|
48
|
+
This package does not perform live-site extraction, authenticate with WordPress, download media, rewrite media URLs, reproduce themes or responsive layouts, compare the generated site visually or behaviorally with the source, or migrate plugin behavior such as forms, search, comments, memberships, or ecommerce.
|
|
49
|
+
|
|
50
|
+
Before treating an output as a handoff, review every blocker and warning; then verify routes, content, media, forms, dynamic behavior, metadata, accessibility, and visual behavior against the source site. Generated output is a starting point for deliberate engineering work, not a deployment artifact.
|
|
51
|
+
|
|
52
|
+
## Privacy and local handoff
|
|
53
|
+
|
|
54
|
+
The CLI reads the WXR file you provide and writes generated content to your local filesystem. It does not modify WordPress. A WXR export and the resulting migration plan, report, and generated project can contain private content, author names, source URLs, raw HTML, and post metadata.
|
|
55
|
+
|
|
56
|
+
Run it in a trusted local working directory. Treat the input and every generated output as potentially confidential: review and remove sensitive material before committing, uploading, sharing, or deploying it. The default generated directories are ignored by this repository, but that does not make their contents safe to share.
|
|
57
|
+
|
|
58
|
+
## Demo fixture
|
|
59
|
+
|
|
60
|
+
`fixtures/demo-wordpress.xml` contains fictional data only. Bright Path Plumbing, its author name, URLs under `brightpath.example`, and the fixture's page and post content exist solely to demonstrate the migration flow.
|
|
61
|
+
|
|
62
|
+
## Development
|
|
63
|
+
|
|
64
|
+
From a checkout:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npm install
|
|
68
|
+
npm test
|
|
69
|
+
npm run demo
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## License
|
|
73
|
+
|
|
74
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { MigrationProject, OutputTarget } from "./types.js";
|
|
2
|
+
export interface TargetAdapter {
|
|
3
|
+
readonly target: OutputTarget;
|
|
4
|
+
readonly enabled: boolean;
|
|
5
|
+
readonly label: string;
|
|
6
|
+
generate(project: MigrationProject, outputDirectory: string): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
export declare const targetAvailability: {
|
|
9
|
+
readonly astro: {
|
|
10
|
+
readonly target: "astro";
|
|
11
|
+
readonly label: "Astro";
|
|
12
|
+
readonly enabled: true;
|
|
13
|
+
};
|
|
14
|
+
readonly next: {
|
|
15
|
+
readonly target: "next";
|
|
16
|
+
readonly label: "Next.js";
|
|
17
|
+
readonly enabled: false;
|
|
18
|
+
};
|
|
19
|
+
readonly nuxt: {
|
|
20
|
+
readonly target: "nuxt";
|
|
21
|
+
readonly label: "Nuxt";
|
|
22
|
+
readonly enabled: false;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
export declare function assertTargetEnabled(target: OutputTarget): void;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const targetAvailability = {
|
|
2
|
+
astro: { target: "astro", label: "Astro", enabled: true },
|
|
3
|
+
next: { target: "next", label: "Next.js", enabled: false },
|
|
4
|
+
nuxt: { target: "nuxt", label: "Nuxt", enabled: false }
|
|
5
|
+
};
|
|
6
|
+
export function assertTargetEnabled(target) {
|
|
7
|
+
const availability = targetAvailability[target];
|
|
8
|
+
if (!availability.enabled) {
|
|
9
|
+
throw new Error(`${availability.label} is planned but disabled in 0.1.0-demo. Use --target astro.`);
|
|
10
|
+
}
|
|
11
|
+
}
|
package/dist/src/cli.js
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { lstat, mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { assertTargetEnabled, targetAvailability } from "./adapters.js";
|
|
6
|
+
import { parseWxr } from "./core.js";
|
|
7
|
+
import { generateAstroProject } from "./generate.js";
|
|
8
|
+
import { writeReport } from "./report.js";
|
|
9
|
+
function usage() {
|
|
10
|
+
return `WP Migrate Core 0.1.0-demo
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
wp-migrate-core inspect <export.xml> [--out migration-plan] [--target astro]
|
|
14
|
+
wp-migrate-core convert <export.xml> --out <new-site> [--target astro]
|
|
15
|
+
wp-migrate-core report <export.xml> [--out migration-report.html]
|
|
16
|
+
wp-migrate-core demo [--out wp-migrate-core-demo]
|
|
17
|
+
|
|
18
|
+
Targets:
|
|
19
|
+
astro implemented in 0.1.0-demo
|
|
20
|
+
next not implemented in 0.1.0-demo
|
|
21
|
+
nuxt not implemented in 0.1.0-demo
|
|
22
|
+
|
|
23
|
+
This is a deliberately incomplete demonstration. It does not modify WordPress.`;
|
|
24
|
+
}
|
|
25
|
+
function parseArguments(argv) {
|
|
26
|
+
const command = argv[0] ?? "help";
|
|
27
|
+
let input;
|
|
28
|
+
let output;
|
|
29
|
+
let target = "astro";
|
|
30
|
+
for (let index = 1; index < argv.length; index += 1) {
|
|
31
|
+
const value = argv[index];
|
|
32
|
+
if (value === "--out") {
|
|
33
|
+
output = argv[index + 1];
|
|
34
|
+
index += 1;
|
|
35
|
+
}
|
|
36
|
+
else if (value === "--target") {
|
|
37
|
+
const candidate = argv[index + 1];
|
|
38
|
+
if (candidate !== "astro" && candidate !== "next" && candidate !== "nuxt") {
|
|
39
|
+
throw new Error(`Unknown target: ${candidate ?? "missing"}. Use astro, next, or nuxt.`);
|
|
40
|
+
}
|
|
41
|
+
target = candidate;
|
|
42
|
+
index += 1;
|
|
43
|
+
}
|
|
44
|
+
else if (!value?.startsWith("--") && input === undefined) {
|
|
45
|
+
input = value;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
throw new Error(`Unknown argument: ${value ?? "missing"}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { command, ...(input === undefined ? {} : { input }), ...(output === undefined ? {} : { output }), target };
|
|
52
|
+
}
|
|
53
|
+
async function loadProject(inputPath) {
|
|
54
|
+
const xml = await readFile(resolve(inputPath), "utf8");
|
|
55
|
+
return parseWxr(xml);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Keep the default CLI plan useful for migration review without copying source
|
|
59
|
+
* content, post metadata, parser attributes, diagnostic snippets, or
|
|
60
|
+
* unsanitized source URLs into the default local review file.
|
|
61
|
+
*/
|
|
62
|
+
function createMigrationPlan(project) {
|
|
63
|
+
return {
|
|
64
|
+
site: { title: project.site.title },
|
|
65
|
+
source: project.source.title === undefined ? {} : { title: project.source.title },
|
|
66
|
+
records: project.records.map(createPlanRecord),
|
|
67
|
+
issues: project.issues.map(createPlanIssue),
|
|
68
|
+
summary: project.summary
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function createPlanRecord(record) {
|
|
72
|
+
const route = sanitizePlanRoute(record.route);
|
|
73
|
+
return {
|
|
74
|
+
sourceId: record.sourceId,
|
|
75
|
+
wordpressId: record.wordpressId,
|
|
76
|
+
type: record.type,
|
|
77
|
+
status: record.status,
|
|
78
|
+
title: record.title,
|
|
79
|
+
slug: record.slug,
|
|
80
|
+
...(route === undefined ? {} : { route }),
|
|
81
|
+
...(record.publishedAt === undefined ? {} : { publishedAt: record.publishedAt }),
|
|
82
|
+
...(record.modifiedAt === undefined ? {} : { modifiedAt: record.modifiedAt }),
|
|
83
|
+
editor: record.editor,
|
|
84
|
+
nodes: record.nodes.map(createPlanNode),
|
|
85
|
+
issues: record.issues.map(createPlanIssue)
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function createPlanNode(node) {
|
|
89
|
+
return {
|
|
90
|
+
id: node.id,
|
|
91
|
+
source: node.source,
|
|
92
|
+
sourceType: node.sourceType,
|
|
93
|
+
kind: node.kind,
|
|
94
|
+
conversion: node.conversion,
|
|
95
|
+
children: node.children.map(createPlanNode)
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function createPlanIssue(issue) {
|
|
99
|
+
const route = sanitizePlanRoute(issue.route);
|
|
100
|
+
return {
|
|
101
|
+
id: issue.id,
|
|
102
|
+
severity: issue.severity,
|
|
103
|
+
code: issue.code,
|
|
104
|
+
sourceId: issue.sourceId,
|
|
105
|
+
...(route === undefined ? {} : { route }),
|
|
106
|
+
...(issue.nodeId === undefined ? {} : { nodeId: issue.nodeId }),
|
|
107
|
+
title: issue.title,
|
|
108
|
+
message: issue.message,
|
|
109
|
+
requiredAction: issue.requiredAction
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function sanitizePlanRoute(value) {
|
|
113
|
+
if (value === undefined)
|
|
114
|
+
return undefined;
|
|
115
|
+
try {
|
|
116
|
+
const url = new URL(value);
|
|
117
|
+
if (url.protocol !== "http:" && url.protocol !== "https:")
|
|
118
|
+
return undefined;
|
|
119
|
+
url.username = "";
|
|
120
|
+
url.password = "";
|
|
121
|
+
url.search = "";
|
|
122
|
+
url.hash = "";
|
|
123
|
+
return url.href;
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
const pathname = value.trim().split(/[?#]/, 1)[0] ?? "";
|
|
127
|
+
return pathname.startsWith("/") ? pathname : undefined;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function isNodeErrorCode(error, code) {
|
|
131
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
132
|
+
}
|
|
133
|
+
function outputExistsError(outputPath) {
|
|
134
|
+
return new Error(`Refusing to overwrite existing output: ${outputPath}. Choose a different --out path or remove the existing file intentionally.`);
|
|
135
|
+
}
|
|
136
|
+
async function ensureInspectionDirectory(directory) {
|
|
137
|
+
try {
|
|
138
|
+
const details = await stat(directory);
|
|
139
|
+
if (!details.isDirectory()) {
|
|
140
|
+
throw new Error(`Inspect output path exists and is not a directory: ${directory}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
if (!isNodeErrorCode(error, "ENOENT"))
|
|
145
|
+
throw error;
|
|
146
|
+
await mkdir(directory, { recursive: true });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
async function assertOutputDoesNotExist(outputPath) {
|
|
150
|
+
try {
|
|
151
|
+
await lstat(outputPath);
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
if (isNodeErrorCode(error, "ENOENT"))
|
|
155
|
+
return;
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
throw outputExistsError(outputPath);
|
|
159
|
+
}
|
|
160
|
+
async function writeNewFile(outputPath, contents) {
|
|
161
|
+
try {
|
|
162
|
+
await writeFile(outputPath, contents, { encoding: "utf8", flag: "wx" });
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
if (isNodeErrorCode(error, "EEXIST"))
|
|
166
|
+
throw outputExistsError(outputPath);
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function printSummary(project) {
|
|
171
|
+
const { summary } = project;
|
|
172
|
+
console.log(`\n${project.source.title ?? "WordPress export"}`);
|
|
173
|
+
console.log(` ${summary.records} records`);
|
|
174
|
+
console.log(` ${summary.nodes} detected content constructs`);
|
|
175
|
+
console.log(` ${summary.nativeNodes} constructs marked as directly supported`);
|
|
176
|
+
console.log(` ${summary.manualNodes} constructs need manual handling`);
|
|
177
|
+
console.log(` ${summary.blockedNodes} blocked constructs`);
|
|
178
|
+
console.log(` ${summary.blockers} blockers; ${summary.warnings} items need review`);
|
|
179
|
+
if (project.issues.length > 0) {
|
|
180
|
+
console.log("\nRepair queue");
|
|
181
|
+
for (const issue of project.issues) {
|
|
182
|
+
const marker = issue.severity === "blocker" ? "BLOCKED" : issue.severity.toUpperCase();
|
|
183
|
+
console.log(` [${marker}] ${issue.route ?? "site-wide"}: ${issue.message}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async function inspect(project, outputDirectory) {
|
|
188
|
+
const directory = resolve(outputDirectory);
|
|
189
|
+
const planPath = resolve(directory, "migration-plan.json");
|
|
190
|
+
const reportPath = resolve(directory, "report.html");
|
|
191
|
+
await ensureInspectionDirectory(directory);
|
|
192
|
+
await assertOutputDoesNotExist(planPath);
|
|
193
|
+
await assertOutputDoesNotExist(reportPath);
|
|
194
|
+
await writeNewFile(planPath, `${JSON.stringify(createMigrationPlan(project), null, 2)}\n`);
|
|
195
|
+
await writeReport(project, reportPath, { noClobber: true });
|
|
196
|
+
printSummary(project);
|
|
197
|
+
console.log(`\nPlan: ${planPath}`);
|
|
198
|
+
console.log(`Report: ${reportPath}`);
|
|
199
|
+
}
|
|
200
|
+
async function run() {
|
|
201
|
+
const options = parseArguments(process.argv.slice(2));
|
|
202
|
+
if (options.command === "help" || options.command === "--help" || options.command === "-h") {
|
|
203
|
+
console.log(usage());
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (options.command === "demo") {
|
|
207
|
+
assertTargetEnabled(options.target);
|
|
208
|
+
const fixture = fileURLToPath(new URL("../../fixtures/demo-wordpress.xml", import.meta.url));
|
|
209
|
+
const project = await loadProject(fixture);
|
|
210
|
+
const output = resolve(options.output ?? "wp-migrate-core-demo");
|
|
211
|
+
await inspect(project, resolve(output, "migration-plan"));
|
|
212
|
+
await generateAstroProject(project, resolve(output, "astro-site"));
|
|
213
|
+
await writeReport(project, resolve(output, "astro-site", "migration", "report.html"));
|
|
214
|
+
console.log(`Astro demo: ${resolve(output, "astro-site")}`);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (!options.input) {
|
|
218
|
+
throw new Error(`${options.command} requires a WordPress WXR file.\n\n${usage()}`);
|
|
219
|
+
}
|
|
220
|
+
const project = await loadProject(options.input);
|
|
221
|
+
if (options.command === "inspect") {
|
|
222
|
+
assertTargetEnabled(options.target);
|
|
223
|
+
await inspect(project, options.output ?? "migration-plan");
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (options.command === "report") {
|
|
227
|
+
const output = resolve(options.output ?? "migration-report.html");
|
|
228
|
+
await writeReport(project, output, { noClobber: true });
|
|
229
|
+
printSummary(project);
|
|
230
|
+
console.log(`\nReport: ${output}`);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (options.command === "convert") {
|
|
234
|
+
assertTargetEnabled(options.target);
|
|
235
|
+
if (!options.output)
|
|
236
|
+
throw new Error("convert requires --out <new-site>.");
|
|
237
|
+
const output = resolve(options.output);
|
|
238
|
+
await generateAstroProject(project, output);
|
|
239
|
+
await writeReport(project, resolve(output, "migration", "report.html"));
|
|
240
|
+
printSummary(project);
|
|
241
|
+
console.log(`\nGenerated ${targetAvailability[options.target].label} project: ${output}`);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
throw new Error(`Unknown command: ${options.command}.\n\n${usage()}`);
|
|
245
|
+
}
|
|
246
|
+
run().catch((error) => {
|
|
247
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
248
|
+
console.error(`wp-migrate-core: ${message}`);
|
|
249
|
+
process.exitCode = 1;
|
|
250
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { InspectOptions, MigrationProject } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Parse a bounded WXR export into a target-neutral migration model.
|
|
4
|
+
*
|
|
5
|
+
* This deliberately does not claim to be a complete XML or WordPress parser. It
|
|
6
|
+
* is dependency-free so 0.1.0-demo can make the conversion boundary visible.
|
|
7
|
+
*/
|
|
8
|
+
export declare function parseWxr(xml: string, options?: InspectOptions): MigrationProject;
|
|
9
|
+
export declare const inspectWxr: typeof parseWxr;
|