@deckflow/deckuse 1.0.2 → 1.2.0
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 +662 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/bin.js +938 -225
- package/dist/bin.js.map +1 -1
- package/dist/cli-text.d.ts +12 -0
- package/dist/cli-text.d.ts.map +1 -0
- package/dist/cli-text.js +49 -0
- package/dist/cli-text.js.map +1 -0
- package/dist/edition.d.ts +4 -0
- package/dist/edition.d.ts.map +1 -0
- package/dist/edition.js +2 -0
- package/dist/edition.js.map +1 -0
- package/dist/help.d.ts +6 -0
- package/dist/help.d.ts.map +1 -0
- package/dist/help.js +689 -0
- package/dist/help.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/monitor-daemon.d.ts +38 -0
- package/dist/monitor-daemon.d.ts.map +1 -0
- package/dist/monitor-daemon.js +182 -0
- package/dist/monitor-daemon.js.map +1 -0
- package/dist/monitor.d.ts +24 -0
- package/dist/monitor.d.ts.map +1 -0
- package/dist/monitor.js +894 -0
- package/dist/monitor.js.map +1 -0
- package/dist/office2html-exec.d.ts +3 -0
- package/dist/office2html-exec.d.ts.map +1 -0
- package/dist/office2html-exec.js +22 -0
- package/dist/office2html-exec.js.map +1 -0
- package/dist/render.d.ts +28 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +124 -0
- package/dist/render.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +19 -8
package/dist/bin.js
CHANGED
|
@@ -1,122 +1,59 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { readFile } from 'node:fs/promises';
|
|
3
|
-
import { extname, resolve } from 'node:path';
|
|
2
|
+
import { access, readFile } from 'node:fs/promises';
|
|
3
|
+
import { dirname, extname, join, resolve } from 'node:path';
|
|
4
|
+
import { PROTOCOL_VERSION, commandJsonSchema, measureText, parseLength, revisionAsNumber, } from '@deckflow/deckuse-core';
|
|
5
|
+
import { resolveCliText } from './cli-text.js';
|
|
6
|
+
import { helpTopicFromArgs, resolveHelp } from './help.js';
|
|
4
7
|
import { runCommand } from './index.js';
|
|
8
|
+
import { monitorStart, monitorStatus, monitorStop, runMonitorForeground, } from './monitor-daemon.js';
|
|
9
|
+
import { renderPage } from './render.js';
|
|
10
|
+
import { EDITION } from './edition.js';
|
|
5
11
|
import { version } from './version.js';
|
|
6
12
|
const args = process.argv.slice(2);
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
Common commands:
|
|
15
|
-
init <input> <workspace> Create a workspace from an Office document
|
|
16
|
-
inspect <workspace> Inspect the document structure
|
|
17
|
-
query <workspace> [selector] Query document elements
|
|
18
|
-
apply <workspace> Apply JSON command objects
|
|
19
|
-
validate <workspace> Validate workspace changes
|
|
20
|
-
commit <workspace> Export the workspace to a document
|
|
21
|
-
|
|
22
|
-
Run 'deckuse <command> --help' for details about a command.
|
|
23
|
-
Run 'deckuse help <command>' for the same command reference.
|
|
24
|
-
|
|
25
|
-
Options:
|
|
26
|
-
-h, --help Show this help message
|
|
27
|
-
-v, -V, --version
|
|
28
|
-
Show the deckuse version
|
|
29
|
-
--json Format command results as compact JSON
|
|
30
|
-
`,
|
|
31
|
-
init: `usage: deckuse init <input> <workspace> [--json]
|
|
32
|
-
|
|
33
|
-
Create a deckuse workspace from an Office document.
|
|
34
|
-
|
|
35
|
-
Arguments:
|
|
36
|
-
<input> Source document (.pptx, .docx, .xlsx, .key, or .numbers)
|
|
37
|
-
<workspace> Directory in which to create the workspace
|
|
38
|
-
|
|
39
|
-
Example:
|
|
40
|
-
deckuse init presentation.pptx ./presentation
|
|
41
|
-
`,
|
|
42
|
-
inspect: `usage: deckuse inspect <workspace> [--depth <n>] [--json]
|
|
43
|
-
|
|
44
|
-
Inspect the indexed structure of a workspace.
|
|
45
|
-
|
|
46
|
-
Arguments:
|
|
47
|
-
<workspace> Existing deckuse workspace
|
|
48
|
-
|
|
49
|
-
Options:
|
|
50
|
-
--depth <n> Traversal depth (default: 2)
|
|
51
|
-
|
|
52
|
-
Example:
|
|
53
|
-
deckuse inspect ./presentation --depth 3
|
|
54
|
-
`,
|
|
55
|
-
query: `usage: deckuse query <workspace> [selector] [--limit <n>] [--json]
|
|
56
|
-
|
|
57
|
-
Query indexed elements in a workspace.
|
|
58
|
-
|
|
59
|
-
Arguments:
|
|
60
|
-
<workspace> Existing deckuse workspace
|
|
61
|
-
[selector] Element selector (default: *)
|
|
62
|
-
|
|
63
|
-
Options:
|
|
64
|
-
--limit <n> Maximum results to return (default: 100)
|
|
65
|
-
|
|
66
|
-
Example:
|
|
67
|
-
deckuse query ./presentation 'slide[*] text' --limit 20
|
|
68
|
-
`,
|
|
69
|
-
apply: `usage: deckuse apply <workspace> [--input <file|->] [--json]
|
|
70
|
-
|
|
71
|
-
Apply one or more JSON command objects to a workspace. Input may be a JSON
|
|
72
|
-
object, a JSON array, or newline-delimited JSON. Commands read from standard
|
|
73
|
-
input by default.
|
|
74
|
-
|
|
75
|
-
Arguments:
|
|
76
|
-
<workspace> Existing deckuse workspace
|
|
77
|
-
|
|
78
|
-
Options:
|
|
79
|
-
--input <file|-> Command input file, or - for standard input (default: -)
|
|
80
|
-
|
|
81
|
-
Examples:
|
|
82
|
-
deckuse apply ./presentation --input changes.json
|
|
83
|
-
printf '%s\n' '{"type":"add", ...}' | deckuse apply ./presentation
|
|
84
|
-
`,
|
|
85
|
-
validate: `usage: deckuse validate <workspace> [--level <level>] [--json]
|
|
86
|
-
|
|
87
|
-
Validate workspace changes before export.
|
|
88
|
-
|
|
89
|
-
Arguments:
|
|
90
|
-
<workspace> Existing deckuse workspace
|
|
91
|
-
|
|
92
|
-
Options:
|
|
93
|
-
--level <level> Validation level (default: full)
|
|
94
|
-
|
|
95
|
-
Example:
|
|
96
|
-
deckuse validate ./presentation --level full
|
|
97
|
-
`,
|
|
98
|
-
commit: `usage: deckuse commit <workspace> [-o <output>] [--force] [--json]
|
|
99
|
-
|
|
100
|
-
Export workspace changes to an Office document.
|
|
101
|
-
|
|
102
|
-
Arguments:
|
|
103
|
-
<workspace> Existing deckuse workspace
|
|
104
|
-
|
|
105
|
-
Options:
|
|
106
|
-
-o, --output <output>
|
|
107
|
-
Destination document path
|
|
108
|
-
-f, --force Overwrite an existing destination
|
|
109
|
-
|
|
110
|
-
Example:
|
|
111
|
-
deckuse commit ./presentation -o updated.pptx
|
|
112
|
-
`,
|
|
13
|
+
const takeFlag = (list, name) => {
|
|
14
|
+
const i = list.indexOf(name);
|
|
15
|
+
if (i < 0)
|
|
16
|
+
return false;
|
|
17
|
+
list.splice(i, 1);
|
|
18
|
+
return true;
|
|
113
19
|
};
|
|
114
|
-
const
|
|
115
|
-
|
|
20
|
+
const takeOption = (list, name) => {
|
|
21
|
+
const i = list.indexOf(name);
|
|
22
|
+
if (i < 0)
|
|
23
|
+
return undefined;
|
|
24
|
+
const value = list[i + 1];
|
|
25
|
+
list.splice(i, 2);
|
|
26
|
+
return value;
|
|
27
|
+
};
|
|
28
|
+
const json = takeFlag(args, '--json');
|
|
29
|
+
const quiet = takeFlag(args, '--quiet');
|
|
30
|
+
const dryRun = takeFlag(args, '--dry-run');
|
|
31
|
+
const workspaceOpt = takeOption(args, '--workspace');
|
|
32
|
+
const revisionOpt = takeOption(args, '--revision');
|
|
33
|
+
const expectRevisionOpt = takeOption(args, '--expect-revision');
|
|
34
|
+
const reasonOpt = takeOption(args, '--reason');
|
|
35
|
+
const clean = args;
|
|
36
|
+
const findWorkspace = async (start) => {
|
|
37
|
+
if (start)
|
|
38
|
+
return resolve(start);
|
|
39
|
+
let dir = resolve(process.cwd());
|
|
40
|
+
for (;;) {
|
|
41
|
+
try {
|
|
42
|
+
await access(join(dir, '.deckuse', 'manifest.json'));
|
|
43
|
+
return dir;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
const parent = dirname(dir);
|
|
47
|
+
if (parent === dir)
|
|
48
|
+
throw new Error('No deckuse workspace found; pass --workspace <path>');
|
|
49
|
+
dir = parent;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const revisionFor = async (workspace) => {
|
|
54
|
+
const manifest = JSON.parse(await readFile(resolve(workspace, '.deckuse', 'manifest.json'), 'utf8'));
|
|
55
|
+
return manifest.revision;
|
|
116
56
|
};
|
|
117
|
-
const isHelpFlag = (value) => value === '--help' || value === '-h';
|
|
118
|
-
const isVersionFlag = (value) => value === '--version' || value === '-v' || value === '-V';
|
|
119
|
-
const isHelpCommand = (value) => value in HELP && value !== 'main';
|
|
120
57
|
const readStdin = async () => {
|
|
121
58
|
let value = '';
|
|
122
59
|
process.stdin.setEncoding('utf8');
|
|
@@ -124,137 +61,913 @@ const readStdin = async () => {
|
|
|
124
61
|
value += String(chunk);
|
|
125
62
|
return value;
|
|
126
63
|
};
|
|
127
|
-
const
|
|
128
|
-
const
|
|
129
|
-
return
|
|
64
|
+
const mutationExtras = async (workspace) => {
|
|
65
|
+
const transactionId = expectRevisionOpt ?? (await revisionFor(workspace));
|
|
66
|
+
return {
|
|
67
|
+
version: PROTOCOL_VERSION,
|
|
68
|
+
workspaceId: workspace,
|
|
69
|
+
transactionId,
|
|
70
|
+
...(dryRun ? { dryRun: true } : {}),
|
|
71
|
+
...(expectRevisionOpt ? { expectRevision: expectRevisionOpt } : {}),
|
|
72
|
+
...(reasonOpt ? { reason: reasonOpt } : {}),
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
const toEnvelope = (commandLabel, result, extras = {}) => {
|
|
76
|
+
if (!result.ok) {
|
|
77
|
+
return {
|
|
78
|
+
ok: false,
|
|
79
|
+
command: commandLabel,
|
|
80
|
+
warnings: result.diagnostics
|
|
81
|
+
.filter((d) => d.severity === 'warning')
|
|
82
|
+
.map((d) => d.message),
|
|
83
|
+
error: {
|
|
84
|
+
code: result.error.code,
|
|
85
|
+
message: result.error.message,
|
|
86
|
+
...(result.error.target ? { target: result.error.target } : {}),
|
|
87
|
+
...(result.error.hint ? { hint: result.error.hint } : {}),
|
|
88
|
+
...(result.error.diagnostics?.length
|
|
89
|
+
? { diagnostics: result.error.diagnostics }
|
|
90
|
+
: result.diagnostics.length
|
|
91
|
+
? { diagnostics: result.diagnostics }
|
|
92
|
+
: {}),
|
|
93
|
+
},
|
|
94
|
+
...extras,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const value = result.value;
|
|
98
|
+
const revision = revisionAsNumber(value['revision']) ??
|
|
99
|
+
(typeof value['revision'] === 'number' ? value['revision'] : undefined);
|
|
100
|
+
return {
|
|
101
|
+
ok: true,
|
|
102
|
+
command: commandLabel,
|
|
103
|
+
...(revision !== undefined ? { revision } : {}),
|
|
104
|
+
branch: typeof value['branch'] === 'string' ? value['branch'] : 'main',
|
|
105
|
+
...(Array.isArray(value['affectedSlides'])
|
|
106
|
+
? { affectedSlides: value['affectedSlides'] }
|
|
107
|
+
: {}),
|
|
108
|
+
...(Array.isArray(value['changedTargets'])
|
|
109
|
+
? { changedTargets: value['changedTargets'] }
|
|
110
|
+
: {}),
|
|
111
|
+
...(Array.isArray(value['changedParts'])
|
|
112
|
+
? { changedParts: value['changedParts'] }
|
|
113
|
+
: {}),
|
|
114
|
+
warnings: [
|
|
115
|
+
...result.diagnostics.filter((d) => d.severity === 'warning').map((d) => d.message),
|
|
116
|
+
...(Array.isArray(value['warnings']) ? value['warnings'] : []),
|
|
117
|
+
],
|
|
118
|
+
data: value,
|
|
119
|
+
...extras,
|
|
120
|
+
};
|
|
130
121
|
};
|
|
131
|
-
const
|
|
132
|
-
|
|
122
|
+
const outputEnvelope = (envelope) => {
|
|
123
|
+
if (json) {
|
|
124
|
+
process.stdout.write(`${JSON.stringify(envelope)}\n`);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (quiet)
|
|
128
|
+
return;
|
|
129
|
+
if (!envelope.ok) {
|
|
130
|
+
process.stderr.write(`deckuse: ${envelope.error?.code ?? 'ERROR'}: ${envelope.error?.message ?? 'failed'}\n`);
|
|
131
|
+
if (envelope.error?.hint)
|
|
132
|
+
process.stderr.write(`hint: ${envelope.error.hint}\n`);
|
|
133
|
+
const diagnostics = envelope.error?.diagnostics;
|
|
134
|
+
if (Array.isArray(diagnostics)) {
|
|
135
|
+
for (const d of diagnostics) {
|
|
136
|
+
const path = Array.isArray(d.path) && d.path.length > 0 ? `${d.path.map(String).join('.')}: ` : '';
|
|
137
|
+
process.stderr.write(` - ${path}${d.message}\n`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
process.stdout.write(`${JSON.stringify(envelope, null, 2)}\n`);
|
|
133
143
|
};
|
|
134
|
-
const execute = async (command) => {
|
|
144
|
+
const execute = async (commandLabel, command) => {
|
|
145
|
+
if (revisionOpt && command && typeof command === 'object' && command !== null) {
|
|
146
|
+
const type = command.type;
|
|
147
|
+
if (type &&
|
|
148
|
+
![
|
|
149
|
+
'status',
|
|
150
|
+
'list',
|
|
151
|
+
'get',
|
|
152
|
+
'inspect',
|
|
153
|
+
'search',
|
|
154
|
+
'query',
|
|
155
|
+
'getText',
|
|
156
|
+
'validate',
|
|
157
|
+
'history',
|
|
158
|
+
'export',
|
|
159
|
+
].includes(type)) {
|
|
160
|
+
outputEnvelope({
|
|
161
|
+
ok: false,
|
|
162
|
+
command: commandLabel,
|
|
163
|
+
error: {
|
|
164
|
+
code: 'INVALID_COMMAND',
|
|
165
|
+
message: 'Write commands reject --revision; omit it or use a read command',
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
135
171
|
const result = await runCommand(command);
|
|
136
|
-
|
|
172
|
+
outputEnvelope(toEnvelope(commandLabel, result));
|
|
137
173
|
return result.ok;
|
|
138
174
|
};
|
|
139
|
-
const
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
175
|
+
const parseProps = (list) => {
|
|
176
|
+
const props = {};
|
|
177
|
+
for (let i = 0; i < list.length;) {
|
|
178
|
+
const token = list[i];
|
|
179
|
+
if (!token.startsWith('--')) {
|
|
180
|
+
i += 1;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
const key = token.slice(2);
|
|
184
|
+
const next = list[i + 1];
|
|
185
|
+
if (next === undefined || next.startsWith('--')) {
|
|
186
|
+
props[key] = true;
|
|
187
|
+
i += 1;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
let value = next;
|
|
191
|
+
if (next === 'true')
|
|
192
|
+
value = true;
|
|
193
|
+
else if (next === 'false')
|
|
194
|
+
value = false;
|
|
195
|
+
else if (/^-?\d+(\.\d+)?$/.test(next))
|
|
196
|
+
value = Number(next);
|
|
197
|
+
else if ((next.startsWith('{') && next.endsWith('}')) ||
|
|
198
|
+
(next.startsWith('[') && next.endsWith(']'))) {
|
|
199
|
+
try {
|
|
200
|
+
value = JSON.parse(next);
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
value = next;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
props[key] = value;
|
|
207
|
+
i += 2;
|
|
152
208
|
}
|
|
153
|
-
|
|
154
|
-
|
|
209
|
+
return props;
|
|
210
|
+
};
|
|
211
|
+
const optionFrom = (list, name) => {
|
|
212
|
+
const i = list.indexOf(name);
|
|
213
|
+
return i >= 0 ? list[i + 1] : undefined;
|
|
214
|
+
};
|
|
215
|
+
/** Pass through bare numbers as number; keep unit strings for protocol parseLength. */
|
|
216
|
+
const lengthArg = (raw) => {
|
|
217
|
+
if (raw === undefined)
|
|
218
|
+
return undefined;
|
|
219
|
+
if (raw === 'auto')
|
|
220
|
+
return 'auto';
|
|
221
|
+
if (/^-?\d+(\.\d+)?$/.test(raw))
|
|
222
|
+
return Number(raw);
|
|
223
|
+
return raw;
|
|
224
|
+
};
|
|
225
|
+
const APPLY_WRITE_TYPES = new Set([
|
|
226
|
+
'setText',
|
|
227
|
+
'replaceText',
|
|
228
|
+
'setTransform',
|
|
229
|
+
'setProperties',
|
|
230
|
+
'set',
|
|
231
|
+
'xfrmSet',
|
|
232
|
+
'zMove',
|
|
233
|
+
'add',
|
|
234
|
+
'addSlide',
|
|
235
|
+
'addShape',
|
|
236
|
+
'remove',
|
|
237
|
+
'replacePicture',
|
|
238
|
+
'duplicate',
|
|
239
|
+
'alignElements',
|
|
240
|
+
'batch',
|
|
241
|
+
]);
|
|
242
|
+
const isRecord = (value) => typeof value === 'object' && value !== null;
|
|
243
|
+
const isTransactionOp = (value) => isRecord(value) && 'op' in value;
|
|
244
|
+
const isHighLevelWrite = (value) => isRecord(value) && APPLY_WRITE_TYPES.has(String(value['type']));
|
|
245
|
+
/** Classify apply payload items: high-level batch vs low-level transaction. */
|
|
246
|
+
const classifyApplyOps = (ops) => {
|
|
247
|
+
if (ops.length === 0)
|
|
248
|
+
return { kind: 'invalid', message: 'apply input operations array is empty' };
|
|
249
|
+
if (ops.every(isTransactionOp))
|
|
250
|
+
return { kind: 'transaction' };
|
|
251
|
+
if (ops.every(isHighLevelWrite))
|
|
252
|
+
return { kind: 'batch' };
|
|
253
|
+
if (ops.some(isHighLevelWrite) && ops.some(isTransactionOp)) {
|
|
254
|
+
return {
|
|
255
|
+
kind: 'invalid',
|
|
256
|
+
message: 'apply input mixes high-level write commands (type) with transaction ops (op); use one format',
|
|
257
|
+
};
|
|
155
258
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
throw new Error(`Unknown command: ${command}`);
|
|
259
|
+
if (ops.some(isHighLevelWrite))
|
|
260
|
+
return { kind: 'batch' };
|
|
261
|
+
if (ops.some(isTransactionOp)) {
|
|
262
|
+
return {
|
|
263
|
+
kind: 'invalid',
|
|
264
|
+
message: 'apply transaction operations must all include "op"; high-level writes must use "type"',
|
|
265
|
+
};
|
|
164
266
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
267
|
+
return {
|
|
268
|
+
kind: 'invalid',
|
|
269
|
+
message: 'apply input must be high-level write commands ({ "type": "addShape"|... }) or transaction ops ({ "op": ... })',
|
|
270
|
+
};
|
|
271
|
+
};
|
|
272
|
+
const main = async () => {
|
|
273
|
+
try {
|
|
274
|
+
if (clean.length === 0) {
|
|
275
|
+
const payload = await readStdin();
|
|
276
|
+
const parsed = JSON.parse(payload);
|
|
277
|
+
const ok = await execute('stdin', parsed);
|
|
278
|
+
if (!ok)
|
|
279
|
+
process.exitCode = 1;
|
|
172
280
|
}
|
|
173
|
-
else if (
|
|
174
|
-
|
|
175
|
-
if (!source || !workspace)
|
|
176
|
-
throw new Error('Usage: deckuse init input.pptx workspace/');
|
|
177
|
-
ok = await execute({
|
|
178
|
-
version: '1.0',
|
|
179
|
-
type: 'init',
|
|
180
|
-
workspaceId: workspace,
|
|
181
|
-
format: extname(source).slice(1).toLowerCase(),
|
|
182
|
-
source,
|
|
183
|
-
});
|
|
281
|
+
else if (clean[0] === '--version' || clean[0] === '-v' || clean[0] === '-V') {
|
|
282
|
+
process.stdout.write(`deckuse ${version} (edition=${EDITION})\n`);
|
|
184
283
|
}
|
|
185
|
-
else
|
|
186
|
-
const
|
|
187
|
-
if (
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
284
|
+
else {
|
|
285
|
+
const helpTopic = helpTopicFromArgs(clean);
|
|
286
|
+
if (helpTopic !== null) {
|
|
287
|
+
process.stdout.write(resolveHelp(helpTopic));
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
const action = clean[0];
|
|
291
|
+
let ok = true;
|
|
292
|
+
if (action === 'schema') {
|
|
293
|
+
const typeOpt = optionFrom(clean, '--type') ??
|
|
294
|
+
(clean[1] && !clean[1].startsWith('--') ? clean[1] : undefined);
|
|
295
|
+
const full = commandJsonSchema;
|
|
296
|
+
const defs = full.$defs ?? {};
|
|
297
|
+
const resolveRef = (entry) => {
|
|
298
|
+
if (!entry || typeof entry !== 'object')
|
|
299
|
+
return undefined;
|
|
300
|
+
const record = entry;
|
|
301
|
+
if (typeof record['$ref'] === 'string') {
|
|
302
|
+
const ref = record['$ref'];
|
|
303
|
+
const name = ref.replace(/^#\/\$defs\//, '');
|
|
304
|
+
const resolved = defs[name];
|
|
305
|
+
return resolved && typeof resolved === 'object'
|
|
306
|
+
? resolved
|
|
307
|
+
: undefined;
|
|
308
|
+
}
|
|
309
|
+
return record;
|
|
310
|
+
};
|
|
311
|
+
const typeOf = (entry) => {
|
|
312
|
+
const resolved = resolveRef(entry);
|
|
313
|
+
const typeProp = resolved?.['properties']
|
|
314
|
+
? resolved['properties']['type']
|
|
315
|
+
: undefined;
|
|
316
|
+
if (!typeProp || typeof typeProp !== 'object')
|
|
317
|
+
return undefined;
|
|
318
|
+
const typeRecord = typeProp;
|
|
319
|
+
if (typeof typeRecord['const'] === 'string')
|
|
320
|
+
return typeRecord['const'];
|
|
321
|
+
const nested = resolveRef(typeProp);
|
|
322
|
+
return typeof nested?.['const'] === 'string' ? nested['const'] : undefined;
|
|
323
|
+
};
|
|
324
|
+
let schema = full;
|
|
325
|
+
if (typeOpt) {
|
|
326
|
+
const variants = full.oneOf ?? full.anyOf ?? [];
|
|
327
|
+
const match = variants.find((entry) => typeOf(entry) === typeOpt);
|
|
328
|
+
if (!match) {
|
|
329
|
+
const known = variants.map(typeOf).filter(Boolean).join(', ');
|
|
330
|
+
throw new Error(`Unknown command type for schema: ${typeOpt}. Known: ${known || '(none)'}`);
|
|
331
|
+
}
|
|
332
|
+
schema = { ...(resolveRef(match) ?? match), $defs: defs };
|
|
333
|
+
}
|
|
334
|
+
const payload = {
|
|
335
|
+
ok: true,
|
|
336
|
+
command: 'deckuse schema',
|
|
337
|
+
data: {
|
|
338
|
+
cliVersion: version,
|
|
339
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
340
|
+
edition: EDITION,
|
|
341
|
+
...(typeOpt ? { type: typeOpt } : {}),
|
|
342
|
+
schema,
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
process.stdout.write(`${JSON.stringify(payload, null, json ? undefined : 2)}\n`);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (action === 'measure') {
|
|
349
|
+
const text = optionFrom(clean, '--text');
|
|
350
|
+
if (text === undefined)
|
|
351
|
+
throw new Error('Usage: deckuse measure --text <string> --font-size <pt>');
|
|
352
|
+
const fontSize = Number(optionFrom(clean, '--font-size') ?? optionFrom(clean, '--fontSize') ?? 12);
|
|
353
|
+
if (!(fontSize > 0))
|
|
354
|
+
throw new Error('--font-size must be a positive number (pt)');
|
|
355
|
+
const bold = clean.includes('--bold');
|
|
356
|
+
const fontFamily = optionFrom(clean, '--font-family') ?? optionFrom(clean, '--fontFamily');
|
|
357
|
+
const maxWidthRaw = optionFrom(clean, '--max-width') ?? optionFrom(clean, '--maxWidth');
|
|
358
|
+
const maxWidthEmu = maxWidthRaw !== undefined
|
|
359
|
+
? parseLength(lengthArg(maxWidthRaw) ?? maxWidthRaw, { axis: 'x' })
|
|
360
|
+
: undefined;
|
|
361
|
+
const measured = measureText({
|
|
362
|
+
text,
|
|
363
|
+
fontSize,
|
|
364
|
+
...(bold ? { bold: true } : {}),
|
|
365
|
+
...(fontFamily ? { fontFamily } : {}),
|
|
366
|
+
...(maxWidthEmu !== undefined ? { maxWidthEmu } : {}),
|
|
367
|
+
});
|
|
368
|
+
outputEnvelope({
|
|
369
|
+
ok: true,
|
|
370
|
+
command: 'deckuse measure',
|
|
371
|
+
data: measured,
|
|
372
|
+
});
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
if (action === 'init') {
|
|
376
|
+
const source = clean[1];
|
|
377
|
+
const workspace = workspaceOpt ?? clean[2];
|
|
378
|
+
if (!source || !workspace)
|
|
379
|
+
throw new Error('Usage: deckuse init <input.pptx> <workspace/>');
|
|
380
|
+
ok = await execute('deckuse init', {
|
|
381
|
+
version: PROTOCOL_VERSION,
|
|
382
|
+
type: 'init',
|
|
383
|
+
workspaceId: resolve(workspace),
|
|
384
|
+
format: extname(source).slice(1).toLowerCase(),
|
|
385
|
+
source: resolve(source),
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
else if (action === 'status') {
|
|
389
|
+
const workspace = await findWorkspace(workspaceOpt ?? clean[1]);
|
|
390
|
+
ok = await execute('deckuse status', {
|
|
391
|
+
version: PROTOCOL_VERSION,
|
|
392
|
+
type: 'status',
|
|
193
393
|
workspaceId: workspace,
|
|
194
|
-
|
|
195
|
-
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
else if (action === 'list') {
|
|
397
|
+
const resource = clean[1];
|
|
398
|
+
if (!resource)
|
|
399
|
+
throw new Error('Usage: deckuse list <slides|shapes|layouts|masters|theme>');
|
|
400
|
+
const workspace = await findWorkspace(workspaceOpt);
|
|
401
|
+
const slide = optionFrom(clean, '--slide');
|
|
402
|
+
ok = await execute(`deckuse list ${resource}`, {
|
|
403
|
+
version: PROTOCOL_VERSION,
|
|
404
|
+
type: 'list',
|
|
405
|
+
workspaceId: workspace,
|
|
406
|
+
resource,
|
|
407
|
+
...(slide ? { slide: Number(slide) } : {}),
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
else if (action === 'get') {
|
|
411
|
+
const target = clean[1];
|
|
412
|
+
if (!target)
|
|
413
|
+
throw new Error('Usage: deckuse get <target> [--resolve both] [--json]');
|
|
414
|
+
const workspace = await findWorkspace(workspaceOpt);
|
|
415
|
+
const resolveMode = optionFrom(clean, '--resolve') ?? 'both';
|
|
416
|
+
const propsRaw = optionFrom(clean, '--props');
|
|
417
|
+
ok = await execute('deckuse get', {
|
|
418
|
+
version: PROTOCOL_VERSION,
|
|
419
|
+
type: 'get',
|
|
420
|
+
workspaceId: workspace,
|
|
421
|
+
target,
|
|
422
|
+
resolve: resolveMode,
|
|
423
|
+
provenance: !clean.includes('--no-provenance'),
|
|
424
|
+
...(propsRaw ? { props: propsRaw.split(',').map((p) => p.trim()) } : {}),
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
else if (action === 'inspect') {
|
|
428
|
+
const target = clean[1];
|
|
429
|
+
const workspace = await findWorkspace(workspaceOpt);
|
|
430
|
+
ok = await execute('deckuse inspect', {
|
|
431
|
+
version: PROTOCOL_VERSION,
|
|
432
|
+
type: 'inspect',
|
|
433
|
+
workspaceId: workspace,
|
|
434
|
+
...(target && !target.startsWith('--') ? { target } : {}),
|
|
435
|
+
visualTree: clean.includes('--visual-tree'),
|
|
436
|
+
depth: Number(optionFrom(clean, '--depth') ?? 2),
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
else if (action === 'search') {
|
|
440
|
+
const kind = clean[1];
|
|
441
|
+
if (kind !== 'text' && kind !== 'shape')
|
|
442
|
+
throw new Error('Usage: deckuse search <text|shape> ...');
|
|
443
|
+
const workspace = await findWorkspace(workspaceOpt);
|
|
444
|
+
const query = kind === 'text' ? clean[2] : optionFrom(clean, '--query');
|
|
445
|
+
ok = await execute('deckuse search', {
|
|
446
|
+
version: PROTOCOL_VERSION,
|
|
447
|
+
type: 'search',
|
|
448
|
+
workspaceId: workspace,
|
|
449
|
+
kind,
|
|
450
|
+
...(query ? { query } : {}),
|
|
451
|
+
...(optionFrom(clean, '--name') ? { name: optionFrom(clean, '--name') } : {}),
|
|
452
|
+
limit: Number(optionFrom(clean, '--limit') ?? 100),
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
else if (action === 'add') {
|
|
456
|
+
const what = clean[1];
|
|
457
|
+
const workspace = await findWorkspace(workspaceOpt);
|
|
458
|
+
const base = await mutationExtras(workspace);
|
|
459
|
+
if (what === 'slide') {
|
|
460
|
+
ok = await execute('deckuse add slide', {
|
|
461
|
+
...base,
|
|
462
|
+
type: 'addSlide',
|
|
463
|
+
...(optionFrom(clean, '--after') ? { after: Number(optionFrom(clean, '--after')) } : {}),
|
|
464
|
+
...(optionFrom(clean, '--layout') ? { layout: optionFrom(clean, '--layout') } : {}),
|
|
465
|
+
...(optionFrom(clean, '--name') ? { name: optionFrom(clean, '--name') } : {}),
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
else if (what === 'shape') {
|
|
469
|
+
const slide = optionFrom(clean, '--slide');
|
|
470
|
+
const shapeType = optionFrom(clean, '--type');
|
|
471
|
+
if (!slide || !shapeType)
|
|
472
|
+
throw new Error('Usage: deckuse add shape --slide <n> --type <text|rect|rounded-rect|ellipse|line|connector|elbow|arrow|chevron|circular-arrow|image|group|table|chart|…>');
|
|
473
|
+
const rowsRaw = optionFrom(clean, '--rows');
|
|
474
|
+
let rows;
|
|
475
|
+
if (rowsRaw !== undefined) {
|
|
476
|
+
try {
|
|
477
|
+
const parsed = JSON.parse(rowsRaw);
|
|
478
|
+
if (!Array.isArray(parsed) ||
|
|
479
|
+
!parsed.every((row) => Array.isArray(row) && row.every((cell) => typeof cell === 'string'))) {
|
|
480
|
+
throw new Error('expected string[][]');
|
|
481
|
+
}
|
|
482
|
+
rows = parsed;
|
|
483
|
+
}
|
|
484
|
+
catch {
|
|
485
|
+
throw new Error(`--rows must be JSON string[][] (example: --rows '[["A","B"],["1","2"]]')`);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
const dataRaw = optionFrom(clean, '--data');
|
|
489
|
+
let data;
|
|
490
|
+
if (dataRaw !== undefined) {
|
|
491
|
+
try {
|
|
492
|
+
data = JSON.parse(dataRaw);
|
|
493
|
+
}
|
|
494
|
+
catch {
|
|
495
|
+
throw new Error(`--data must be JSON (example: --data '{"categories":["Q1"],"series":[{"name":"S1","values":[1]}]}')`);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
const chartType = optionFrom(clean, '--chart-type');
|
|
499
|
+
const textRaw = takeFlag(clean, '--text-raw');
|
|
500
|
+
const textFile = optionFrom(clean, '--text-file');
|
|
501
|
+
const textOpt = optionFrom(clean, '--text');
|
|
502
|
+
const initialText = await resolveCliText({
|
|
503
|
+
...(textOpt !== undefined ? { value: textOpt } : {}),
|
|
504
|
+
...(textFile !== undefined ? { textFile } : {}),
|
|
505
|
+
raw: textRaw,
|
|
506
|
+
});
|
|
507
|
+
const theme = optionFrom(clean, '--theme');
|
|
508
|
+
const alignColumnsRaw = optionFrom(clean, '--align-columns');
|
|
509
|
+
let alignColumns;
|
|
510
|
+
if (alignColumnsRaw) {
|
|
511
|
+
try {
|
|
512
|
+
alignColumns = JSON.parse(alignColumnsRaw);
|
|
513
|
+
}
|
|
514
|
+
catch {
|
|
515
|
+
alignColumns = alignColumnsRaw.split(',').map((s) => s.trim());
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
ok = await execute('deckuse add shape', {
|
|
519
|
+
...base,
|
|
520
|
+
type: 'addShape',
|
|
521
|
+
slide: Number(slide),
|
|
522
|
+
shapeType,
|
|
523
|
+
...(optionFrom(clean, '--name') ? { name: optionFrom(clean, '--name') } : {}),
|
|
524
|
+
...(optionFrom(clean, '--role') ? { role: optionFrom(clean, '--role') } : {}),
|
|
525
|
+
...(lengthArg(optionFrom(clean, '--x')) !== undefined
|
|
526
|
+
? { x: lengthArg(optionFrom(clean, '--x')) }
|
|
527
|
+
: {}),
|
|
528
|
+
...(lengthArg(optionFrom(clean, '--y')) !== undefined
|
|
529
|
+
? { y: lengthArg(optionFrom(clean, '--y')) }
|
|
530
|
+
: {}),
|
|
531
|
+
...(lengthArg(optionFrom(clean, '--width')) !== undefined
|
|
532
|
+
? { width: lengthArg(optionFrom(clean, '--width')) }
|
|
533
|
+
: {}),
|
|
534
|
+
...(lengthArg(optionFrom(clean, '--height')) !== undefined
|
|
535
|
+
? { height: lengthArg(optionFrom(clean, '--height')) }
|
|
536
|
+
: {}),
|
|
537
|
+
...(optionFrom(clean, '--file') ? { file: optionFrom(clean, '--file') } : {}),
|
|
538
|
+
...(initialText !== undefined ? { text: initialText } : {}),
|
|
539
|
+
...(rows !== undefined ? { rows } : {}),
|
|
540
|
+
...(theme ? { theme } : {}),
|
|
541
|
+
...(alignColumns ? { alignColumns } : {}),
|
|
542
|
+
...(chartType ? { chartType } : {}),
|
|
543
|
+
...(data !== undefined ? { data } : {}),
|
|
544
|
+
...(takeFlag(clean, '--show-data-labels') ? { showDataLabels: true } : {}),
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
else
|
|
548
|
+
throw new Error('Usage: deckuse add <slide|shape> ...');
|
|
549
|
+
}
|
|
550
|
+
else if (action === 'remove') {
|
|
551
|
+
const target = clean[1];
|
|
552
|
+
if (!target)
|
|
553
|
+
throw new Error('Usage: deckuse remove <target>');
|
|
554
|
+
const workspace = await findWorkspace(workspaceOpt);
|
|
555
|
+
ok = await execute('deckuse remove', {
|
|
556
|
+
...(await mutationExtras(workspace)),
|
|
557
|
+
type: 'remove',
|
|
558
|
+
target,
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
else if (action === 'set') {
|
|
562
|
+
const workspace = await findWorkspace(workspaceOpt);
|
|
563
|
+
const base = await mutationExtras(workspace);
|
|
564
|
+
if (clean[1] === 'text') {
|
|
565
|
+
const target = clean[2];
|
|
566
|
+
const textRaw = takeFlag(clean, '--text-raw');
|
|
567
|
+
const textFile = optionFrom(clean, '--text-file');
|
|
568
|
+
const valueOpt = optionFrom(clean, '--value');
|
|
569
|
+
const blocksRaw = optionFrom(clean, '--blocks');
|
|
570
|
+
if (!target)
|
|
571
|
+
throw new Error('Usage: deckuse set text <target> --value <text> | --text-file <path> | --blocks <json>');
|
|
572
|
+
if (blocksRaw !== undefined) {
|
|
573
|
+
let blocks;
|
|
574
|
+
try {
|
|
575
|
+
blocks = JSON.parse(blocksRaw);
|
|
576
|
+
}
|
|
577
|
+
catch {
|
|
578
|
+
throw new Error('--blocks must be JSON array of {text, fontSize?, textColor?, ...}');
|
|
579
|
+
}
|
|
580
|
+
ok = await execute('deckuse set text', {
|
|
581
|
+
...base,
|
|
582
|
+
type: 'setText',
|
|
583
|
+
target,
|
|
584
|
+
blocks,
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
const value = await resolveCliText({
|
|
589
|
+
...(valueOpt !== undefined ? { value: valueOpt } : {}),
|
|
590
|
+
...(textFile !== undefined ? { textFile } : {}),
|
|
591
|
+
raw: textRaw,
|
|
592
|
+
});
|
|
593
|
+
if (value === undefined)
|
|
594
|
+
throw new Error('Usage: deckuse set text <target> --value <text> | --text-file <path> | --blocks <json>');
|
|
595
|
+
ok = await execute('deckuse set text', {
|
|
596
|
+
...base,
|
|
597
|
+
type: 'setText',
|
|
598
|
+
target,
|
|
599
|
+
value,
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
else {
|
|
604
|
+
const target = clean[1];
|
|
605
|
+
if (!target)
|
|
606
|
+
throw new Error('Usage: deckuse set <target> --font.size 42 ...');
|
|
607
|
+
const props = parseProps(clean.slice(2));
|
|
608
|
+
delete props['scope'];
|
|
609
|
+
const scope = optionFrom(clean, '--scope') ?? 'local';
|
|
610
|
+
// remove flag keys that are not properties
|
|
611
|
+
for (const key of Object.keys(props)) {
|
|
612
|
+
if (['expect-revision', 'reason', 'dry-run', 'workspace', 'json', 'quiet'].includes(key))
|
|
613
|
+
delete props[key];
|
|
614
|
+
}
|
|
615
|
+
ok = await execute('deckuse set', {
|
|
616
|
+
...base,
|
|
617
|
+
type: 'set',
|
|
618
|
+
target,
|
|
619
|
+
properties: props,
|
|
620
|
+
scope,
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
else if (action === 'replace-text') {
|
|
625
|
+
const source = optionFrom(clean, '--source');
|
|
626
|
+
const target = optionFrom(clean, '--target');
|
|
627
|
+
if (source === undefined || target === undefined)
|
|
628
|
+
throw new Error('Usage: deckuse replace-text --source <text> --target <text> [--regex] [--limit <n>] [--selector <sel>]');
|
|
629
|
+
if (!source)
|
|
630
|
+
throw new Error('replace-text --source must be a non-empty string');
|
|
631
|
+
const workspace = await findWorkspace(workspaceOpt);
|
|
632
|
+
ok = await execute('deckuse replace-text', {
|
|
633
|
+
...(await mutationExtras(workspace)),
|
|
634
|
+
type: 'replaceText',
|
|
635
|
+
find: source,
|
|
636
|
+
replace: target,
|
|
637
|
+
...(clean.includes('--regex') ? { regex: true } : {}),
|
|
638
|
+
...(optionFrom(clean, '--limit')
|
|
639
|
+
? { limit: Number(optionFrom(clean, '--limit')) }
|
|
640
|
+
: {}),
|
|
641
|
+
...(optionFrom(clean, '--selector')
|
|
642
|
+
? { selector: optionFrom(clean, '--selector') }
|
|
643
|
+
: {}),
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
else if (action === 'xfrm') {
|
|
647
|
+
if (clean[1] !== 'set')
|
|
648
|
+
throw new Error('Usage: deckuse xfrm set --slide <n> --shape <id> ...');
|
|
649
|
+
const workspace = await findWorkspace(workspaceOpt);
|
|
650
|
+
const slide = optionFrom(clean, '--slide');
|
|
651
|
+
const shape = optionFrom(clean, '--shape');
|
|
652
|
+
const target = optionFrom(clean, '--target') ??
|
|
653
|
+
(slide && shape ? `slide:${slide}/shape:${shape}` : undefined);
|
|
654
|
+
if (!target)
|
|
655
|
+
throw new Error('xfrm set requires --target or --slide and --shape');
|
|
656
|
+
ok = await execute('deckuse xfrm set', {
|
|
657
|
+
...(await mutationExtras(workspace)),
|
|
658
|
+
type: 'xfrmSet',
|
|
659
|
+
target,
|
|
660
|
+
...(lengthArg(optionFrom(clean, '--x')) !== undefined
|
|
661
|
+
? { x: lengthArg(optionFrom(clean, '--x')) }
|
|
662
|
+
: {}),
|
|
663
|
+
...(lengthArg(optionFrom(clean, '--y')) !== undefined
|
|
664
|
+
? { y: lengthArg(optionFrom(clean, '--y')) }
|
|
665
|
+
: {}),
|
|
666
|
+
...(lengthArg(optionFrom(clean, '--width') ?? optionFrom(clean, '--cx')) !== undefined
|
|
667
|
+
? { width: lengthArg(optionFrom(clean, '--width') ?? optionFrom(clean, '--cx')) }
|
|
668
|
+
: {}),
|
|
669
|
+
...(lengthArg(optionFrom(clean, '--height') ?? optionFrom(clean, '--cy')) !== undefined
|
|
670
|
+
? { height: lengthArg(optionFrom(clean, '--height') ?? optionFrom(clean, '--cy')) }
|
|
671
|
+
: {}),
|
|
672
|
+
...(optionFrom(clean, '--rotation')
|
|
673
|
+
? { rotation: Number(optionFrom(clean, '--rotation')) }
|
|
674
|
+
: {}),
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
else if (action === 'align') {
|
|
678
|
+
const workspace = await findWorkspace(workspaceOpt);
|
|
679
|
+
const slide = optionFrom(clean, '--slide');
|
|
680
|
+
const targetsRaw = optionFrom(clean, '--targets');
|
|
681
|
+
const mode = optionFrom(clean, '--mode');
|
|
682
|
+
if (!slide || !targetsRaw || !mode)
|
|
683
|
+
throw new Error('Usage: deckuse align --slide <n> --targets <t1,t2,...> --mode <left|right|top|bottom|center-h|center-v|distribute-h|distribute-v> [--gap <length>]');
|
|
684
|
+
const targets = targetsRaw.split(',').map((t) => t.trim()).filter(Boolean);
|
|
685
|
+
ok = await execute('deckuse align', {
|
|
686
|
+
...(await mutationExtras(workspace)),
|
|
687
|
+
type: 'alignElements',
|
|
688
|
+
slide: Number(slide),
|
|
689
|
+
targets,
|
|
690
|
+
mode,
|
|
691
|
+
...(lengthArg(optionFrom(clean, '--gap')) !== undefined
|
|
692
|
+
? { gap: lengthArg(optionFrom(clean, '--gap')) }
|
|
693
|
+
: {}),
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
else if (action === 'z') {
|
|
697
|
+
if (clean[1] !== 'move')
|
|
698
|
+
throw new Error('Usage: deckuse z move <target> --above <target>');
|
|
699
|
+
const target = clean[2];
|
|
700
|
+
if (!target)
|
|
701
|
+
throw new Error('Usage: deckuse z move <target> ...');
|
|
702
|
+
const workspace = await findWorkspace(workspaceOpt);
|
|
703
|
+
ok = await execute('deckuse z move', {
|
|
704
|
+
...(await mutationExtras(workspace)),
|
|
705
|
+
type: 'zMove',
|
|
706
|
+
target,
|
|
707
|
+
...(optionFrom(clean, '--above') ? { above: optionFrom(clean, '--above') } : {}),
|
|
708
|
+
...(optionFrom(clean, '--below') ? { below: optionFrom(clean, '--below') } : {}),
|
|
709
|
+
...(clean.includes('--to-front') ? { toFront: true } : {}),
|
|
710
|
+
...(clean.includes('--to-back') ? { toBack: true } : {}),
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
else if (action === 'apply') {
|
|
714
|
+
const input = optionFrom(clean, '--input') ?? '-';
|
|
715
|
+
const workspace = await findWorkspace(workspaceOpt ?? clean[1]);
|
|
716
|
+
const raw = input === '-' ? await readStdin() : await readFile(resolve(input), 'utf8');
|
|
717
|
+
let values;
|
|
718
|
+
let wrappedOperations;
|
|
719
|
+
try {
|
|
720
|
+
const parsed = JSON.parse(raw);
|
|
721
|
+
if (typeof parsed === 'object' &&
|
|
722
|
+
parsed !== null &&
|
|
723
|
+
Array.isArray(parsed.operations)) {
|
|
724
|
+
wrappedOperations = parsed.operations;
|
|
725
|
+
}
|
|
726
|
+
else {
|
|
727
|
+
values = Array.isArray(parsed) ? parsed : [parsed];
|
|
728
|
+
}
|
|
196
729
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
730
|
+
catch {
|
|
731
|
+
values = raw
|
|
732
|
+
.split(/\r?\n/)
|
|
733
|
+
.filter(Boolean)
|
|
734
|
+
.map((line) => JSON.parse(line));
|
|
735
|
+
}
|
|
736
|
+
const extras = await mutationExtras(workspace);
|
|
737
|
+
const runHighLevelBatch = async (list) => {
|
|
738
|
+
const commands = list.map((value) => {
|
|
739
|
+
if (!isRecord(value))
|
|
740
|
+
throw new Error('apply input must contain command objects');
|
|
741
|
+
if (value['type'] === 'batch')
|
|
742
|
+
return value;
|
|
743
|
+
if (!APPLY_WRITE_TYPES.has(String(value['type'])))
|
|
744
|
+
throw new Error(`apply input contains non-write command: ${String(value['type'])}`);
|
|
745
|
+
return { ...extras, ...value };
|
|
746
|
+
});
|
|
747
|
+
const payload = commands.length === 1 && commands[0]?.['type'] !== 'batch'
|
|
748
|
+
? commands[0]
|
|
749
|
+
: {
|
|
750
|
+
...extras,
|
|
751
|
+
type: 'batch',
|
|
752
|
+
atomic: true,
|
|
753
|
+
commands: commands.flatMap((command) => command['type'] === 'batch'
|
|
754
|
+
? (command.commands ?? [])
|
|
755
|
+
: [command]),
|
|
756
|
+
};
|
|
757
|
+
return execute('deckuse apply', payload);
|
|
758
|
+
};
|
|
759
|
+
if (wrappedOperations) {
|
|
760
|
+
const classified = classifyApplyOps(wrappedOperations);
|
|
761
|
+
if (classified.kind === 'invalid')
|
|
762
|
+
throw new Error(classified.message);
|
|
763
|
+
if (classified.kind === 'transaction') {
|
|
764
|
+
ok = await execute('deckuse apply', {
|
|
765
|
+
...extras,
|
|
766
|
+
type: 'applyTransaction',
|
|
767
|
+
operations: wrappedOperations,
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
else {
|
|
771
|
+
ok = await runHighLevelBatch(wrappedOperations);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
else {
|
|
775
|
+
const list = values ?? [];
|
|
776
|
+
const classified = classifyApplyOps(list);
|
|
777
|
+
if (classified.kind === 'invalid')
|
|
778
|
+
throw new Error(classified.message);
|
|
779
|
+
if (classified.kind === 'transaction') {
|
|
780
|
+
ok = await execute('deckuse apply', {
|
|
781
|
+
...extras,
|
|
782
|
+
type: 'applyTransaction',
|
|
783
|
+
operations: list,
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
else {
|
|
787
|
+
ok = await runHighLevelBatch(list);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
else if (action === 'validate') {
|
|
792
|
+
const workspace = await findWorkspace(workspaceOpt ?? clean[1]);
|
|
793
|
+
ok = await execute('deckuse validate', {
|
|
794
|
+
version: PROTOCOL_VERSION,
|
|
795
|
+
type: 'validate',
|
|
200
796
|
workspaceId: workspace,
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
797
|
+
level: optionFrom(clean, '--level') ?? 'full',
|
|
798
|
+
package: clean.includes('--package'),
|
|
799
|
+
relationships: clean.includes('--relationships'),
|
|
800
|
+
...(optionFrom(clean, '--slide') ? { slide: Number(optionFrom(clean, '--slide')) } : {}),
|
|
204
801
|
});
|
|
205
|
-
}
|
|
206
|
-
else if (action === 'commit') {
|
|
207
|
-
const workspace = clean[1];
|
|
208
|
-
if (!workspace)
|
|
209
|
-
throw new Error('Usage: deckuse commit workspace/ -o output.pptx [--force]');
|
|
210
|
-
const force = clean.includes('--force') || clean.includes('-f');
|
|
211
|
-
ok = await execute({
|
|
212
|
-
version: '1.0',
|
|
213
|
-
type: 'commit',
|
|
214
|
-
workspaceId: workspace,
|
|
215
|
-
transactionId: await revisionFor(workspace),
|
|
216
|
-
...(option('-o') || option('--output')
|
|
217
|
-
? { destination: option('-o') ?? option('--output') }
|
|
218
|
-
: {}),
|
|
219
|
-
...(force ? { overwrite: true } : {}),
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
else {
|
|
223
|
-
const workspace = clean[1], input = option('--input') ?? '-';
|
|
224
|
-
if (!workspace)
|
|
225
|
-
throw new Error('Usage: deckuse apply workspace/ --input file|-');
|
|
226
|
-
const raw = input === '-' ? await readStdin() : await readFile(resolve(input), 'utf8');
|
|
227
|
-
let values;
|
|
228
|
-
try {
|
|
229
|
-
const parsed = JSON.parse(raw);
|
|
230
|
-
values = Array.isArray(parsed) ? parsed : [parsed];
|
|
231
802
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
803
|
+
else if (action === 'history') {
|
|
804
|
+
const workspace = await findWorkspace(workspaceOpt ?? clean[1]);
|
|
805
|
+
ok = await execute('deckuse history', {
|
|
806
|
+
version: PROTOCOL_VERSION,
|
|
807
|
+
type: 'history',
|
|
808
|
+
workspaceId: workspace,
|
|
809
|
+
limit: Number(optionFrom(clean, '--limit') ?? 100),
|
|
810
|
+
offset: Number(optionFrom(clean, '--offset') ?? 0),
|
|
811
|
+
...(optionFrom(clean, '--slide') ? { slide: Number(optionFrom(clean, '--slide')) } : {}),
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
else if (action === 'undo') {
|
|
815
|
+
const workspace = await findWorkspace(workspaceOpt ?? clean[1]);
|
|
816
|
+
ok = await execute('deckuse undo', {
|
|
817
|
+
version: PROTOCOL_VERSION,
|
|
818
|
+
type: 'undo',
|
|
819
|
+
workspaceId: workspace,
|
|
820
|
+
steps: Number(optionFrom(clean, '--steps') ?? 1),
|
|
821
|
+
});
|
|
237
822
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
823
|
+
else if (action === 'export') {
|
|
824
|
+
const output = clean[1];
|
|
825
|
+
if (!output)
|
|
826
|
+
throw new Error('Usage: deckuse export <output.pptx>');
|
|
827
|
+
const workspace = await findWorkspace(workspaceOpt);
|
|
828
|
+
const fromPackage = takeFlag(clean, '--from-package');
|
|
829
|
+
ok = await execute('deckuse export', {
|
|
830
|
+
version: PROTOCOL_VERSION,
|
|
831
|
+
type: 'export',
|
|
243
832
|
workspaceId: workspace,
|
|
244
|
-
|
|
245
|
-
...
|
|
833
|
+
output: resolve(output),
|
|
834
|
+
...(revisionOpt ? { revision: revisionOpt } : {}),
|
|
835
|
+
...(fromPackage ? { fromPackage: true } : {}),
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
else if (action === 'monitor') {
|
|
839
|
+
const sub = clean[1] === 'start' || clean[1] === 'stop' || clean[1] === 'status' ? clean[1] : undefined;
|
|
840
|
+
const daemonWorker = takeFlag(clean, '--daemon-worker');
|
|
841
|
+
const positionalWorkspace = sub
|
|
842
|
+
? clean[2] && !clean[2].startsWith('--')
|
|
843
|
+
? clean[2]
|
|
844
|
+
: undefined
|
|
845
|
+
: clean[1] && !clean[1].startsWith('--')
|
|
846
|
+
? clean[1]
|
|
847
|
+
: undefined;
|
|
848
|
+
const workspace = await findWorkspace(workspaceOpt ?? positionalWorkspace);
|
|
849
|
+
const port = Number(optionFrom(clean, '--port') ?? 4173);
|
|
850
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535)
|
|
851
|
+
throw new Error('--port must be an integer between 0 and 65535');
|
|
852
|
+
const host = optionFrom(clean, '--host') ?? '0.0.0.0';
|
|
853
|
+
if (sub === 'start') {
|
|
854
|
+
try {
|
|
855
|
+
const meta = await monitorStart(workspace, { host, port });
|
|
856
|
+
outputEnvelope({
|
|
857
|
+
ok: true,
|
|
858
|
+
command: 'deckuse monitor start',
|
|
859
|
+
data: meta,
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
catch (error) {
|
|
863
|
+
outputEnvelope({
|
|
864
|
+
ok: false,
|
|
865
|
+
command: 'deckuse monitor start',
|
|
866
|
+
error: {
|
|
867
|
+
code: 'IO_ERROR',
|
|
868
|
+
message: error instanceof Error ? error.message : 'Monitor start failed',
|
|
869
|
+
},
|
|
870
|
+
});
|
|
871
|
+
process.exitCode = 1;
|
|
872
|
+
}
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
if (sub === 'stop') {
|
|
876
|
+
const result = await monitorStop(workspace);
|
|
877
|
+
outputEnvelope({
|
|
878
|
+
ok: true,
|
|
879
|
+
command: 'deckuse monitor stop',
|
|
880
|
+
data: result,
|
|
881
|
+
});
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
if (sub === 'status') {
|
|
885
|
+
const result = await monitorStatus(workspace);
|
|
886
|
+
outputEnvelope({
|
|
887
|
+
ok: true,
|
|
888
|
+
command: 'deckuse monitor status',
|
|
889
|
+
data: result,
|
|
890
|
+
});
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
const monitor = await runMonitorForeground(workspace, {
|
|
894
|
+
host,
|
|
895
|
+
port,
|
|
896
|
+
asDaemonWorker: daemonWorker,
|
|
897
|
+
});
|
|
898
|
+
if (!daemonWorker)
|
|
899
|
+
process.stdout.write(`Deckuse monitor: ${monitor.url}\n`);
|
|
900
|
+
const shutdown = async (signal) => {
|
|
901
|
+
process.stderr.write(`deckuse monitor: received ${signal}, shutting down\n`);
|
|
902
|
+
await monitor.close();
|
|
903
|
+
process.exit(0);
|
|
246
904
|
};
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
905
|
+
process.once('SIGINT', () => void shutdown('SIGINT'));
|
|
906
|
+
process.once('SIGTERM', () => void shutdown('SIGTERM'));
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
else if (action === 'render') {
|
|
910
|
+
const pageRaw = optionFrom(clean, '--page');
|
|
911
|
+
if (!pageRaw)
|
|
912
|
+
throw new Error('Usage: deckuse render --page <n> [--output <file.png>] [--scale <n>]');
|
|
913
|
+
if (/\s|,|-/.test(pageRaw))
|
|
914
|
+
throw new Error('--page accepts exactly one positive integer (not a range or list)');
|
|
915
|
+
const page = Number(pageRaw);
|
|
916
|
+
if (!Number.isInteger(page) || page < 1)
|
|
917
|
+
throw new Error('--page must be a positive integer');
|
|
918
|
+
const scaleRaw = optionFrom(clean, '--scale');
|
|
919
|
+
const scale = scaleRaw !== undefined ? Number(scaleRaw) : undefined;
|
|
920
|
+
if (scale !== undefined && !(scale > 0))
|
|
921
|
+
throw new Error('--scale must be a positive number');
|
|
922
|
+
const workspace = await findWorkspace(workspaceOpt ?? clean[1]);
|
|
923
|
+
const outputOpt = optionFrom(clean, '--output');
|
|
924
|
+
try {
|
|
925
|
+
const rendered = await renderPage(workspace, {
|
|
926
|
+
page,
|
|
927
|
+
...(outputOpt ? { output: resolve(outputOpt) } : {}),
|
|
928
|
+
...(scale !== undefined ? { scale } : {}),
|
|
929
|
+
});
|
|
930
|
+
outputEnvelope({
|
|
931
|
+
ok: true,
|
|
932
|
+
command: 'deckuse render',
|
|
933
|
+
warnings: [...rendered.warnings],
|
|
934
|
+
data: { page: rendered.page, output: rendered.output, warnings: rendered.warnings },
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
catch (error) {
|
|
938
|
+
outputEnvelope({
|
|
939
|
+
ok: false,
|
|
940
|
+
command: 'deckuse render',
|
|
941
|
+
error: {
|
|
942
|
+
code: 'RENDER_FAILED',
|
|
943
|
+
message: error instanceof Error ? error.message : 'Render failed',
|
|
944
|
+
},
|
|
945
|
+
});
|
|
946
|
+
ok = false;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
else if (action === 'query') {
|
|
950
|
+
// Back-compat shim
|
|
951
|
+
const workspace = await findWorkspace(workspaceOpt ?? clean[1]);
|
|
952
|
+
ok = await execute('deckuse query', {
|
|
953
|
+
version: PROTOCOL_VERSION,
|
|
954
|
+
type: 'query',
|
|
955
|
+
workspaceId: workspace,
|
|
956
|
+
selector: clean[2] ?? '*',
|
|
957
|
+
limit: Number(optionFrom(clean, '--limit') ?? 100),
|
|
958
|
+
});
|
|
250
959
|
}
|
|
960
|
+
else {
|
|
961
|
+
throw new Error(`Unknown command: ${action}`);
|
|
962
|
+
}
|
|
963
|
+
if (!ok)
|
|
964
|
+
process.exitCode = 1;
|
|
251
965
|
}
|
|
252
|
-
if (!ok)
|
|
253
|
-
process.exitCode = 1;
|
|
254
966
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
967
|
+
catch (cause) {
|
|
968
|
+
process.stderr.write(`deckuse: ${cause instanceof Error ? cause.message : 'Invalid input'}\n`);
|
|
969
|
+
process.exitCode = 2;
|
|
970
|
+
}
|
|
971
|
+
};
|
|
972
|
+
void main();
|
|
260
973
|
//# sourceMappingURL=bin.js.map
|