@notedge/panduck 0.0.0 → 0.0.2
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/bin/{panduck.mjs → panduck} +11 -11
- package/locales/en-US/cli.json +46 -46
- package/locales/locales.json +7 -7
- package/locales/zh-CN/cli.json +46 -46
- package/package.json +30 -34
- package/readme.md +89 -5
- package/src/cli/cli.ts +28 -28
- package/src/cli/commands/check-cmd.ts +174 -174
- package/src/cli/commands/convert-cmd.ts +300 -300
- package/src/cli/commands/doctor-cmd.ts +84 -84
- package/src/cli/commands/formats-cmd.ts +50 -50
- package/src/cli/commands/inspect-cmd.ts +231 -231
- package/src/cli/commands/plan-cmd.ts +188 -188
- package/src/cli/console-backend.ts +16 -16
- package/src/cli/context.ts +98 -98
- package/src/cli/diagnostics.ts +24 -24
- package/src/cli/exit-codes.ts +14 -14
- package/src/cli/format-registry.ts +170 -170
- package/src/cli/index.ts +7 -7
- package/src/cli/main.ts +10 -10
- package/src/cli/options.ts +127 -127
- package/src/cli/paths.ts +57 -56
- package/src/cli/pipeline.ts +225 -225
- package/src/cli/report.ts +110 -110
- package/src/index.ts +8 -8
- package/src/node/index.ts +14 -14
- package/src/node/load.ts +120 -120
- package/src/types.ts +49 -49
- package/src/wasm/index.ts +14 -2
- package/src/wasm/load.ts +25 -23
- package/src/wasm/panduck_wasm.d.ts +0 -6
package/src/cli/exit-codes.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
/** Stable Panduck CLI exit codes (see Living CLI contract). */
|
|
2
|
-
export const ExitCode = {
|
|
3
|
-
Success: 0,
|
|
4
|
-
LossPolicyViolation: 1,
|
|
5
|
-
InvalidArgs: 2,
|
|
6
|
-
InputUnsupported: 3,
|
|
7
|
-
ParseFailure: 4,
|
|
8
|
-
IrOrWriterFailure: 5,
|
|
9
|
-
OutputFailure: 6,
|
|
10
|
-
BudgetExceeded: 7,
|
|
11
|
-
BatchFailure: 8,
|
|
12
|
-
} as const;
|
|
13
|
-
|
|
14
|
-
export type ExitCodeValue = (typeof ExitCode)[keyof typeof ExitCode];
|
|
1
|
+
/** Stable Panduck CLI exit codes (see Living CLI contract). */
|
|
2
|
+
export const ExitCode = {
|
|
3
|
+
Success: 0,
|
|
4
|
+
LossPolicyViolation: 1,
|
|
5
|
+
InvalidArgs: 2,
|
|
6
|
+
InputUnsupported: 3,
|
|
7
|
+
ParseFailure: 4,
|
|
8
|
+
IrOrWriterFailure: 5,
|
|
9
|
+
OutputFailure: 6,
|
|
10
|
+
BudgetExceeded: 7,
|
|
11
|
+
BatchFailure: 8,
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
export type ExitCodeValue = (typeof ExitCode)[keyof typeof ExitCode];
|
|
@@ -1,170 +1,170 @@
|
|
|
1
|
-
import type { PanduckBindings } from "../types.js";
|
|
2
|
-
|
|
3
|
-
export type CapabilityState =
|
|
4
|
-
| "registered"
|
|
5
|
-
| "planned"
|
|
6
|
-
| "partial"
|
|
7
|
-
| "ready"
|
|
8
|
-
| "unavailable"
|
|
9
|
-
| "blocked";
|
|
10
|
-
|
|
11
|
-
export type OperationKind = "read" | "write";
|
|
12
|
-
|
|
13
|
-
export type OperationCapability = {
|
|
14
|
-
state: CapabilityState;
|
|
15
|
-
stages: string[];
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export type FormatRecord = {
|
|
19
|
-
format: string;
|
|
20
|
-
container: string[];
|
|
21
|
-
aliases?: string[];
|
|
22
|
-
reader: OperationCapability;
|
|
23
|
-
writer: OperationCapability;
|
|
24
|
-
embeddedText?: string[];
|
|
25
|
-
coverage: {
|
|
26
|
-
state: CapabilityState;
|
|
27
|
-
known: string[];
|
|
28
|
-
unknown: string[];
|
|
29
|
-
};
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const TEXT_READ_STAGES = ["probe", "decode", "validate"];
|
|
33
|
-
const TEXT_WRITE_STAGES = ["ir", "validate"];
|
|
34
|
-
const CONTAINER_READ_STAGES = ["probe", "index", "decode", "validate"];
|
|
35
|
-
const CONTAINER_WRITE_STAGES = ["ir", "package", "validate"];
|
|
36
|
-
|
|
37
|
-
function textFormat(
|
|
38
|
-
format: string,
|
|
39
|
-
reader: CapabilityState,
|
|
40
|
-
writer: CapabilityState,
|
|
41
|
-
known: string[],
|
|
42
|
-
unknown: string[],
|
|
43
|
-
aliases?: string[],
|
|
44
|
-
): FormatRecord {
|
|
45
|
-
return {
|
|
46
|
-
format,
|
|
47
|
-
container: ["text"],
|
|
48
|
-
aliases,
|
|
49
|
-
reader: { state: reader, stages: TEXT_READ_STAGES },
|
|
50
|
-
writer: { state: writer, stages: TEXT_WRITE_STAGES },
|
|
51
|
-
coverage: { state: reader === "ready" && writer === "ready" ? "ready" : "partial", known, unknown },
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function containerFormat(
|
|
56
|
-
format: string,
|
|
57
|
-
container: string[],
|
|
58
|
-
reader: CapabilityState,
|
|
59
|
-
writer: CapabilityState,
|
|
60
|
-
embeddedText: string[],
|
|
61
|
-
known: string[],
|
|
62
|
-
unknown: string[],
|
|
63
|
-
): FormatRecord {
|
|
64
|
-
return {
|
|
65
|
-
format,
|
|
66
|
-
container,
|
|
67
|
-
reader: { state: reader, stages: CONTAINER_READ_STAGES },
|
|
68
|
-
writer: { state: writer, stages: CONTAINER_WRITE_STAGES },
|
|
69
|
-
embeddedText,
|
|
70
|
-
coverage: {
|
|
71
|
-
state:
|
|
72
|
-
reader === "ready" && writer === "ready"
|
|
73
|
-
? "ready"
|
|
74
|
-
: reader === "planned" && writer === "planned"
|
|
75
|
-
? "planned"
|
|
76
|
-
: "partial",
|
|
77
|
-
known,
|
|
78
|
-
unknown,
|
|
79
|
-
},
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** Static capability registry aligned with the Panduck Living CLI matrix. */
|
|
84
|
-
export function baseFormatRegistry(): FormatRecord[] {
|
|
85
|
-
return [
|
|
86
|
-
textFormat("markdown", "partial", "ready", ["paragraph", "heading", "code_block", "link"], ["table", "footnote"]),
|
|
87
|
-
textFormat("notedown", "partial", "planned", ["paragraph", "heading", "list", "link"], ["macro", "table"]),
|
|
88
|
-
textFormat("org", "partial", "partial", ["headline", "paragraph"], ["drawer", "babel"]),
|
|
89
|
-
textFormat("rst", "partial", "partial", ["paragraph", "directive"], ["table"]),
|
|
90
|
-
textFormat("tex", "partial", "partial", ["paragraph", "command"], ["environment"]),
|
|
91
|
-
textFormat("html", "planned", "planned", ["paragraph", "heading"], ["script", "style"]),
|
|
92
|
-
containerFormat(
|
|
93
|
-
"docx",
|
|
94
|
-
["zip", "opc"],
|
|
95
|
-
"partial",
|
|
96
|
-
"partial",
|
|
97
|
-
["oak-xml"],
|
|
98
|
-
["paragraph", "heading", "list", "table", "style", "hyperlink", "image", "bold", "italic", "footnote_reference", "footnote_body"],
|
|
99
|
-
["tracked_changes", "comment"],
|
|
100
|
-
),
|
|
101
|
-
containerFormat(
|
|
102
|
-
"epub",
|
|
103
|
-
["zip", "ocf"],
|
|
104
|
-
"planned",
|
|
105
|
-
"planned",
|
|
106
|
-
["oak-xml", "oak-xhtml"],
|
|
107
|
-
["spine", "nav", "image"],
|
|
108
|
-
["fixed_layout"],
|
|
109
|
-
),
|
|
110
|
-
containerFormat(
|
|
111
|
-
"pdf",
|
|
112
|
-
["pdf"],
|
|
113
|
-
"planned",
|
|
114
|
-
"planned",
|
|
115
|
-
[],
|
|
116
|
-
["page", "text_run", "image"],
|
|
117
|
-
["annotation", "form", "encryption"],
|
|
118
|
-
),
|
|
119
|
-
containerFormat(
|
|
120
|
-
"doc",
|
|
121
|
-
["ole"],
|
|
122
|
-
"planned",
|
|
123
|
-
"unavailable",
|
|
124
|
-
["oak-xml"],
|
|
125
|
-
["paragraph"],
|
|
126
|
-
["field", "revision"],
|
|
127
|
-
),
|
|
128
|
-
];
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/** Merges N-API `supportedFormats()` names into the static registry as `registered` floors. */
|
|
132
|
-
export function getFormatRegistry(bindings?: PanduckBindings | null): FormatRecord[] {
|
|
133
|
-
const registered = new Set((bindings?.supportedFormats() ?? []).map((name) => name.toLowerCase()));
|
|
134
|
-
return baseFormatRegistry().map((record) => {
|
|
135
|
-
if (!registered.has(record.format)) {
|
|
136
|
-
return record;
|
|
137
|
-
}
|
|
138
|
-
const readerState = bumpRegistered(record.reader.state);
|
|
139
|
-
const writerState = bumpRegistered(record.writer.state);
|
|
140
|
-
return {
|
|
141
|
-
...record,
|
|
142
|
-
reader: { ...record.reader, state: readerState },
|
|
143
|
-
writer: { ...record.writer, state: writerState },
|
|
144
|
-
};
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function bumpRegistered(state: CapabilityState): CapabilityState {
|
|
149
|
-
if (state === "planned" || state === "registered") {
|
|
150
|
-
return "registered";
|
|
151
|
-
}
|
|
152
|
-
return state;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
export function findFormat(registry: FormatRecord[], id: string): FormatRecord | undefined {
|
|
156
|
-
const needle = id.toLowerCase();
|
|
157
|
-
return registry.find(
|
|
158
|
-
(record) =>
|
|
159
|
-
record.format === needle || record.aliases?.some((alias) => alias.toLowerCase() === needle),
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export function isOperationReady(record: FormatRecord, operation: OperationKind): boolean {
|
|
164
|
-
const capability = operation === "read" ? record.reader : record.writer;
|
|
165
|
-
return capability.state === "ready";
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
export function ebookAliasTarget(): string {
|
|
169
|
-
return "epub";
|
|
170
|
-
}
|
|
1
|
+
import type { PanduckBindings } from "../types.js";
|
|
2
|
+
|
|
3
|
+
export type CapabilityState =
|
|
4
|
+
| "registered"
|
|
5
|
+
| "planned"
|
|
6
|
+
| "partial"
|
|
7
|
+
| "ready"
|
|
8
|
+
| "unavailable"
|
|
9
|
+
| "blocked";
|
|
10
|
+
|
|
11
|
+
export type OperationKind = "read" | "write";
|
|
12
|
+
|
|
13
|
+
export type OperationCapability = {
|
|
14
|
+
state: CapabilityState;
|
|
15
|
+
stages: string[];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type FormatRecord = {
|
|
19
|
+
format: string;
|
|
20
|
+
container: string[];
|
|
21
|
+
aliases?: string[];
|
|
22
|
+
reader: OperationCapability;
|
|
23
|
+
writer: OperationCapability;
|
|
24
|
+
embeddedText?: string[];
|
|
25
|
+
coverage: {
|
|
26
|
+
state: CapabilityState;
|
|
27
|
+
known: string[];
|
|
28
|
+
unknown: string[];
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const TEXT_READ_STAGES = ["probe", "decode", "validate"];
|
|
33
|
+
const TEXT_WRITE_STAGES = ["ir", "validate"];
|
|
34
|
+
const CONTAINER_READ_STAGES = ["probe", "index", "decode", "validate"];
|
|
35
|
+
const CONTAINER_WRITE_STAGES = ["ir", "package", "validate"];
|
|
36
|
+
|
|
37
|
+
function textFormat(
|
|
38
|
+
format: string,
|
|
39
|
+
reader: CapabilityState,
|
|
40
|
+
writer: CapabilityState,
|
|
41
|
+
known: string[],
|
|
42
|
+
unknown: string[],
|
|
43
|
+
aliases?: string[],
|
|
44
|
+
): FormatRecord {
|
|
45
|
+
return {
|
|
46
|
+
format,
|
|
47
|
+
container: ["text"],
|
|
48
|
+
aliases,
|
|
49
|
+
reader: { state: reader, stages: TEXT_READ_STAGES },
|
|
50
|
+
writer: { state: writer, stages: TEXT_WRITE_STAGES },
|
|
51
|
+
coverage: { state: reader === "ready" && writer === "ready" ? "ready" : "partial", known, unknown },
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function containerFormat(
|
|
56
|
+
format: string,
|
|
57
|
+
container: string[],
|
|
58
|
+
reader: CapabilityState,
|
|
59
|
+
writer: CapabilityState,
|
|
60
|
+
embeddedText: string[],
|
|
61
|
+
known: string[],
|
|
62
|
+
unknown: string[],
|
|
63
|
+
): FormatRecord {
|
|
64
|
+
return {
|
|
65
|
+
format,
|
|
66
|
+
container,
|
|
67
|
+
reader: { state: reader, stages: CONTAINER_READ_STAGES },
|
|
68
|
+
writer: { state: writer, stages: CONTAINER_WRITE_STAGES },
|
|
69
|
+
embeddedText,
|
|
70
|
+
coverage: {
|
|
71
|
+
state:
|
|
72
|
+
reader === "ready" && writer === "ready"
|
|
73
|
+
? "ready"
|
|
74
|
+
: reader === "planned" && writer === "planned"
|
|
75
|
+
? "planned"
|
|
76
|
+
: "partial",
|
|
77
|
+
known,
|
|
78
|
+
unknown,
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Static capability registry aligned with the Panduck Living CLI matrix. */
|
|
84
|
+
export function baseFormatRegistry(): FormatRecord[] {
|
|
85
|
+
return [
|
|
86
|
+
textFormat("markdown", "partial", "ready", ["paragraph", "heading", "code_block", "link"], ["table", "footnote"]),
|
|
87
|
+
textFormat("notedown", "partial", "planned", ["paragraph", "heading", "list", "link"], ["macro", "table"]),
|
|
88
|
+
textFormat("org", "partial", "partial", ["headline", "paragraph"], ["drawer", "babel"]),
|
|
89
|
+
textFormat("rst", "partial", "partial", ["paragraph", "directive"], ["table"]),
|
|
90
|
+
textFormat("tex", "partial", "partial", ["paragraph", "command"], ["environment"]),
|
|
91
|
+
textFormat("html", "planned", "planned", ["paragraph", "heading"], ["script", "style"]),
|
|
92
|
+
containerFormat(
|
|
93
|
+
"docx",
|
|
94
|
+
["zip", "opc"],
|
|
95
|
+
"partial",
|
|
96
|
+
"partial",
|
|
97
|
+
["oak-xml"],
|
|
98
|
+
["paragraph", "heading", "list", "table", "style", "hyperlink", "image", "bold", "italic", "footnote_reference", "footnote_body"],
|
|
99
|
+
["tracked_changes", "comment"],
|
|
100
|
+
),
|
|
101
|
+
containerFormat(
|
|
102
|
+
"epub",
|
|
103
|
+
["zip", "ocf"],
|
|
104
|
+
"planned",
|
|
105
|
+
"planned",
|
|
106
|
+
["oak-xml", "oak-xhtml"],
|
|
107
|
+
["spine", "nav", "image"],
|
|
108
|
+
["fixed_layout"],
|
|
109
|
+
),
|
|
110
|
+
containerFormat(
|
|
111
|
+
"pdf",
|
|
112
|
+
["pdf"],
|
|
113
|
+
"planned",
|
|
114
|
+
"planned",
|
|
115
|
+
[],
|
|
116
|
+
["page", "text_run", "image"],
|
|
117
|
+
["annotation", "form", "encryption"],
|
|
118
|
+
),
|
|
119
|
+
containerFormat(
|
|
120
|
+
"doc",
|
|
121
|
+
["ole"],
|
|
122
|
+
"planned",
|
|
123
|
+
"unavailable",
|
|
124
|
+
["oak-xml"],
|
|
125
|
+
["paragraph"],
|
|
126
|
+
["field", "revision"],
|
|
127
|
+
),
|
|
128
|
+
];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Merges N-API `supportedFormats()` names into the static registry as `registered` floors. */
|
|
132
|
+
export function getFormatRegistry(bindings?: PanduckBindings | null): FormatRecord[] {
|
|
133
|
+
const registered = new Set((bindings?.supportedFormats() ?? []).map((name) => name.toLowerCase()));
|
|
134
|
+
return baseFormatRegistry().map((record) => {
|
|
135
|
+
if (!registered.has(record.format)) {
|
|
136
|
+
return record;
|
|
137
|
+
}
|
|
138
|
+
const readerState = bumpRegistered(record.reader.state);
|
|
139
|
+
const writerState = bumpRegistered(record.writer.state);
|
|
140
|
+
return {
|
|
141
|
+
...record,
|
|
142
|
+
reader: { ...record.reader, state: readerState },
|
|
143
|
+
writer: { ...record.writer, state: writerState },
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function bumpRegistered(state: CapabilityState): CapabilityState {
|
|
149
|
+
if (state === "planned" || state === "registered") {
|
|
150
|
+
return "registered";
|
|
151
|
+
}
|
|
152
|
+
return state;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function findFormat(registry: FormatRecord[], id: string): FormatRecord | undefined {
|
|
156
|
+
const needle = id.toLowerCase();
|
|
157
|
+
return registry.find(
|
|
158
|
+
(record) =>
|
|
159
|
+
record.format === needle || record.aliases?.some((alias) => alias.toLowerCase() === needle),
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function isOperationReady(record: FormatRecord, operation: OperationKind): boolean {
|
|
164
|
+
const capability = operation === "read" ? record.reader : record.writer;
|
|
165
|
+
return capability.state === "ready";
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function ebookAliasTarget(): string {
|
|
169
|
+
return "epub";
|
|
170
|
+
}
|
package/src/cli/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export { buildPanduckCli } from "./cli.js";
|
|
2
|
-
export { runCli } from "./main.js";
|
|
3
|
-
export { ExitCode } from "./exit-codes.js";
|
|
4
|
-
export { getFormatRegistry, baseFormatRegistry } from "./format-registry.js";
|
|
5
|
-
export type { FormatRecord, CapabilityState, OperationKind } from "./format-registry.js";
|
|
6
|
-
export { REPORT_SCHEMA_VERSION } from "./report.js";
|
|
7
|
-
export type { PanduckReport } from "./report.js";
|
|
1
|
+
export { buildPanduckCli } from "./cli.js";
|
|
2
|
+
export { runCli } from "./main.js";
|
|
3
|
+
export { ExitCode } from "./exit-codes.js";
|
|
4
|
+
export { getFormatRegistry, baseFormatRegistry } from "./format-registry.js";
|
|
5
|
+
export type { FormatRecord, CapabilityState, OperationKind } from "./format-registry.js";
|
|
6
|
+
export { REPORT_SCHEMA_VERSION } from "./report.js";
|
|
7
|
+
export type { PanduckReport } from "./report.js";
|
package/src/cli/main.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { buildPanduckCli } from "./cli.js";
|
|
2
|
-
|
|
3
|
-
export async function runCli(argv: string[]): Promise<number> {
|
|
4
|
-
try {
|
|
5
|
-
return await buildPanduckCli().parse(argv);
|
|
6
|
-
} catch (error) {
|
|
7
|
-
console.error(`error: ${error instanceof Error ? error.message : String(error)}`);
|
|
8
|
-
return 2;
|
|
9
|
-
}
|
|
10
|
-
}
|
|
1
|
+
import { buildPanduckCli } from "./cli.js";
|
|
2
|
+
|
|
3
|
+
export async function runCli(argv: string[]): Promise<number> {
|
|
4
|
+
try {
|
|
5
|
+
return await buildPanduckCli().parse(argv);
|
|
6
|
+
} catch (error) {
|
|
7
|
+
console.error(`error: ${error instanceof Error ? error.message : String(error)}`);
|
|
8
|
+
return 2;
|
|
9
|
+
}
|
|
10
|
+
}
|
package/src/cli/options.ts
CHANGED
|
@@ -1,127 +1,127 @@
|
|
|
1
|
-
import type { ParsedOptions } from "@vmz/commander";
|
|
2
|
-
|
|
3
|
-
import { ExitCode } from "./exit-codes.js";
|
|
4
|
-
import type { LossPolicy } from "./report.js";
|
|
5
|
-
|
|
6
|
-
export type AssetPolicy = "preserve" | "copy" | "embed" | "drop";
|
|
7
|
-
export type LinkPolicy = "preserve" | "rewrite" | "drop";
|
|
8
|
-
export type DiagnosticsMode = "silent" | "human" | "json";
|
|
9
|
-
export type InspectStage = "probe" | "index" | "decode" | "validate";
|
|
10
|
-
|
|
11
|
-
export type SharedCliOptions = {
|
|
12
|
-
from?: string;
|
|
13
|
-
to?: string;
|
|
14
|
-
profile?: string;
|
|
15
|
-
config?: string;
|
|
16
|
-
json: boolean;
|
|
17
|
-
budgets: Record<string, number>;
|
|
18
|
-
loss: LossPolicy;
|
|
19
|
-
strict: boolean;
|
|
20
|
-
allowPartial: boolean;
|
|
21
|
-
diagnostics: DiagnosticsMode;
|
|
22
|
-
overwrite: boolean;
|
|
23
|
-
logFile?: string;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const LOSS_VALUES = new Set<LossPolicy>(["allow", "warn", "deny"]);
|
|
27
|
-
const ASSET_VALUES = new Set<AssetPolicy>(["preserve", "copy", "embed", "drop"]);
|
|
28
|
-
const LINK_VALUES = new Set<LinkPolicy>(["preserve", "rewrite", "drop"]);
|
|
29
|
-
const DIAG_VALUES = new Set<DiagnosticsMode>(["silent", "human", "json"]);
|
|
30
|
-
const STAGE_VALUES = new Set<InspectStage>(["probe", "index", "decode", "validate"]);
|
|
31
|
-
|
|
32
|
-
export function readFlag(options: ParsedOptions, key: string): boolean {
|
|
33
|
-
return options[key] === true;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function readString(options: ParsedOptions, key: string): string | undefined {
|
|
37
|
-
const value = options[key];
|
|
38
|
-
if (typeof value === "string" && value.length > 0) {
|
|
39
|
-
return value;
|
|
40
|
-
}
|
|
41
|
-
return undefined;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function readSharedOptions(options: ParsedOptions): SharedCliOptions {
|
|
45
|
-
const loss = parseEnum(readString(options, "loss") ?? "warn", LOSS_VALUES, "loss");
|
|
46
|
-
const diagnostics = parseEnum(
|
|
47
|
-
readString(options, "diagnostics") ?? "human",
|
|
48
|
-
DIAG_VALUES,
|
|
49
|
-
"diagnostics",
|
|
50
|
-
);
|
|
51
|
-
return {
|
|
52
|
-
from: readString(options, "from"),
|
|
53
|
-
to: readString(options, "to"),
|
|
54
|
-
profile: readString(options, "profile"),
|
|
55
|
-
config: readString(options, "config"),
|
|
56
|
-
json: readFlag(options, "json"),
|
|
57
|
-
budgets: parseBudgets(options.budget),
|
|
58
|
-
loss,
|
|
59
|
-
strict: readFlag(options, "strict"),
|
|
60
|
-
allowPartial: readFlag(options, "allow-partial"),
|
|
61
|
-
diagnostics,
|
|
62
|
-
overwrite: readFlag(options, "overwrite"),
|
|
63
|
-
logFile: readString(options, "log-file"),
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function parseAssetPolicy(value: string | undefined): AssetPolicy {
|
|
68
|
-
return parseEnum(value ?? "preserve", ASSET_VALUES, "asset-policy");
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function parseLinkPolicy(value: string | undefined): LinkPolicy {
|
|
72
|
-
return parseEnum(value ?? "preserve", LINK_VALUES, "link-policy");
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export function parseInspectStage(value: string | undefined): InspectStage {
|
|
76
|
-
return parseEnum(value ?? "probe", STAGE_VALUES, "stage");
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function parseEnum<T extends string>(value: string, allowed: Set<T>, name: string): T {
|
|
80
|
-
if (!allowed.has(value as T)) {
|
|
81
|
-
throw new CliOptionError(name, value);
|
|
82
|
-
}
|
|
83
|
-
return value as T;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export function parseBudgets(raw: string | boolean | string[] | undefined): Record<string, number> {
|
|
87
|
-
const entries = normalizeRepeatable(raw);
|
|
88
|
-
const budgets: Record<string, number> = {};
|
|
89
|
-
for (const item of entries) {
|
|
90
|
-
const eq = item.indexOf("=");
|
|
91
|
-
if (eq <= 0) {
|
|
92
|
-
throw new CliOptionError("budget", item);
|
|
93
|
-
}
|
|
94
|
-
const key = item.slice(0, eq).trim();
|
|
95
|
-
const value = Number(item.slice(eq + 1).trim());
|
|
96
|
-
if (!Number.isFinite(value) || value < 0) {
|
|
97
|
-
throw new CliOptionError("budget", item);
|
|
98
|
-
}
|
|
99
|
-
budgets[key] = value;
|
|
100
|
-
}
|
|
101
|
-
return budgets;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function normalizeRepeatable(raw: string | boolean | string[] | undefined): string[] {
|
|
105
|
-
if (raw === undefined || raw === false) {
|
|
106
|
-
return [];
|
|
107
|
-
}
|
|
108
|
-
if (Array.isArray(raw)) {
|
|
109
|
-
return raw;
|
|
110
|
-
}
|
|
111
|
-
if (typeof raw === "string") {
|
|
112
|
-
return [raw];
|
|
113
|
-
}
|
|
114
|
-
return [];
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export class CliOptionError extends Error {
|
|
118
|
-
readonly field: string;
|
|
119
|
-
readonly value: string;
|
|
120
|
-
readonly exitCode = ExitCode.InvalidArgs;
|
|
121
|
-
|
|
122
|
-
constructor(field: string, value: string) {
|
|
123
|
-
super(`invalid ${field}: ${value}`);
|
|
124
|
-
this.field = field;
|
|
125
|
-
this.value = value;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
1
|
+
import type { ParsedOptions } from "@vmz/commander";
|
|
2
|
+
|
|
3
|
+
import { ExitCode } from "./exit-codes.js";
|
|
4
|
+
import type { LossPolicy } from "./report.js";
|
|
5
|
+
|
|
6
|
+
export type AssetPolicy = "preserve" | "copy" | "embed" | "drop";
|
|
7
|
+
export type LinkPolicy = "preserve" | "rewrite" | "drop";
|
|
8
|
+
export type DiagnosticsMode = "silent" | "human" | "json";
|
|
9
|
+
export type InspectStage = "probe" | "index" | "decode" | "validate";
|
|
10
|
+
|
|
11
|
+
export type SharedCliOptions = {
|
|
12
|
+
from?: string;
|
|
13
|
+
to?: string;
|
|
14
|
+
profile?: string;
|
|
15
|
+
config?: string;
|
|
16
|
+
json: boolean;
|
|
17
|
+
budgets: Record<string, number>;
|
|
18
|
+
loss: LossPolicy;
|
|
19
|
+
strict: boolean;
|
|
20
|
+
allowPartial: boolean;
|
|
21
|
+
diagnostics: DiagnosticsMode;
|
|
22
|
+
overwrite: boolean;
|
|
23
|
+
logFile?: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const LOSS_VALUES = new Set<LossPolicy>(["allow", "warn", "deny"]);
|
|
27
|
+
const ASSET_VALUES = new Set<AssetPolicy>(["preserve", "copy", "embed", "drop"]);
|
|
28
|
+
const LINK_VALUES = new Set<LinkPolicy>(["preserve", "rewrite", "drop"]);
|
|
29
|
+
const DIAG_VALUES = new Set<DiagnosticsMode>(["silent", "human", "json"]);
|
|
30
|
+
const STAGE_VALUES = new Set<InspectStage>(["probe", "index", "decode", "validate"]);
|
|
31
|
+
|
|
32
|
+
export function readFlag(options: ParsedOptions, key: string): boolean {
|
|
33
|
+
return options[key] === true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function readString(options: ParsedOptions, key: string): string | undefined {
|
|
37
|
+
const value = options[key];
|
|
38
|
+
if (typeof value === "string" && value.length > 0) {
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function readSharedOptions(options: ParsedOptions): SharedCliOptions {
|
|
45
|
+
const loss = parseEnum(readString(options, "loss") ?? "warn", LOSS_VALUES, "loss");
|
|
46
|
+
const diagnostics = parseEnum(
|
|
47
|
+
readString(options, "diagnostics") ?? "human",
|
|
48
|
+
DIAG_VALUES,
|
|
49
|
+
"diagnostics",
|
|
50
|
+
);
|
|
51
|
+
return {
|
|
52
|
+
from: readString(options, "from"),
|
|
53
|
+
to: readString(options, "to"),
|
|
54
|
+
profile: readString(options, "profile"),
|
|
55
|
+
config: readString(options, "config"),
|
|
56
|
+
json: readFlag(options, "json"),
|
|
57
|
+
budgets: parseBudgets(options.budget),
|
|
58
|
+
loss,
|
|
59
|
+
strict: readFlag(options, "strict"),
|
|
60
|
+
allowPartial: readFlag(options, "allow-partial"),
|
|
61
|
+
diagnostics,
|
|
62
|
+
overwrite: readFlag(options, "overwrite"),
|
|
63
|
+
logFile: readString(options, "log-file"),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function parseAssetPolicy(value: string | undefined): AssetPolicy {
|
|
68
|
+
return parseEnum(value ?? "preserve", ASSET_VALUES, "asset-policy");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function parseLinkPolicy(value: string | undefined): LinkPolicy {
|
|
72
|
+
return parseEnum(value ?? "preserve", LINK_VALUES, "link-policy");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function parseInspectStage(value: string | undefined): InspectStage {
|
|
76
|
+
return parseEnum(value ?? "probe", STAGE_VALUES, "stage");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function parseEnum<T extends string>(value: string, allowed: Set<T>, name: string): T {
|
|
80
|
+
if (!allowed.has(value as T)) {
|
|
81
|
+
throw new CliOptionError(name, value);
|
|
82
|
+
}
|
|
83
|
+
return value as T;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function parseBudgets(raw: string | boolean | string[] | undefined): Record<string, number> {
|
|
87
|
+
const entries = normalizeRepeatable(raw);
|
|
88
|
+
const budgets: Record<string, number> = {};
|
|
89
|
+
for (const item of entries) {
|
|
90
|
+
const eq = item.indexOf("=");
|
|
91
|
+
if (eq <= 0) {
|
|
92
|
+
throw new CliOptionError("budget", item);
|
|
93
|
+
}
|
|
94
|
+
const key = item.slice(0, eq).trim();
|
|
95
|
+
const value = Number(item.slice(eq + 1).trim());
|
|
96
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
97
|
+
throw new CliOptionError("budget", item);
|
|
98
|
+
}
|
|
99
|
+
budgets[key] = value;
|
|
100
|
+
}
|
|
101
|
+
return budgets;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function normalizeRepeatable(raw: string | boolean | string[] | undefined): string[] {
|
|
105
|
+
if (raw === undefined || raw === false) {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
if (Array.isArray(raw)) {
|
|
109
|
+
return raw;
|
|
110
|
+
}
|
|
111
|
+
if (typeof raw === "string") {
|
|
112
|
+
return [raw];
|
|
113
|
+
}
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export class CliOptionError extends Error {
|
|
118
|
+
readonly field: string;
|
|
119
|
+
readonly value: string;
|
|
120
|
+
readonly exitCode = ExitCode.InvalidArgs;
|
|
121
|
+
|
|
122
|
+
constructor(field: string, value: string) {
|
|
123
|
+
super(`invalid ${field}: ${value}`);
|
|
124
|
+
this.field = field;
|
|
125
|
+
this.value = value;
|
|
126
|
+
}
|
|
127
|
+
}
|