layoutbridge-light 1.0.0 → 1.0.1
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/CHANGELOG.md +83 -0
- package/README.de.md +31 -2
- package/README.md +31 -3
- package/THIRD-PARTY-NOTICES.md +2 -2
- package/dist/src/mcp/catalog.js +9 -4
- package/dist/src/mcp/content-credentials.js +65 -0
- package/dist/src/mcp/dispatch.js +8 -1
- package/dist/src/mcp/license/polar-ids.js +1 -1
- package/dist/src/mcp/pdf-postprocess.js +798 -0
- package/dist/src/mcp/server.js +1 -1
- package/dist/src/mcp/target-folder.js +25 -0
- package/dist/src/mcp/tools.js +275 -20
- package/dist/src/mcp/typography-rules.js +1 -1
- package/dist/src/transport/com.js +224 -38
- package/dist/src/transport/transport.js +22 -0
- package/indesign/commands/25-export.js +258 -11
- package/indesign/commands/26-export-image.js +1 -1
- package/indesign/commands/31-view.js +100 -0
- package/indesign/commands/35-parents.js +44 -24
- package/indesign/commands/40-frames.js +35 -0
- package/indesign/commands/47-anchored.js +142 -0
- package/indesign/commands/50-modify.js +25 -3
- package/indesign/commands/60-images.js +28 -2
- package/indesign/commands/69-placeholders.js +7 -2
- package/indesign/commands/70-text.js +39 -4
- package/indesign/commands/75-tables.js +27 -1
- package/indesign/commands/78-frame-lines.js +59 -0
- package/indesign/commands/79-check-typography.js +143 -4
- package/indesign/commands/80-styles.js +105 -10
- package/indesign/commands/89-toc-index.js +61 -4
- package/indesign/commands/91-epub.js +92 -9
- package/indesign/commands/92-accessibility.js +1131 -0
- package/indesign/interpreter.js +51 -1
- package/package.json +1 -1
- package/rules/typography-rules.json +3 -2
|
@@ -5,11 +5,20 @@
|
|
|
5
5
|
* Windows-Rechner steht; der Interpreter und das Befehlsformat {cmd, args}
|
|
6
6
|
* bleiben unveraendert.
|
|
7
7
|
*
|
|
8
|
-
* Ablauf je Aufruf:
|
|
9
|
-
* 1. Interpreter und Handler
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* Ablauf je Aufruf (seit Sitzung 80, A-74):
|
|
9
|
+
* 1. Interpreter und Handler samt festem Rahmen liegen als Skriptdatei in
|
|
10
|
+
* einem festen Arbeitsordner (`%LOCALAPPDATA%\layoutbridge-spm\com`),
|
|
11
|
+
* einmal je Buendelstand geschrieben (Pruefsumme im Namen). Daneben ein
|
|
12
|
+
* festes PowerShell-Skript. Je Aufruf entstehen nur Datendateien: der
|
|
13
|
+
* Befehl als ASCII-JSON und das Ergebnis. Vorher entstanden je Aufruf
|
|
14
|
+
* zwei neue Skriptdateien (.jsx mit ~0,9 MB, .ps1) in einem neuen
|
|
15
|
+
* Temp-Ordner; ein Virenschutz mit Echtzeitpruefung (beim Kunden Avira,
|
|
16
|
+
* per Test bestaetigt 10.11.2026) hielt jede davon an – Aufrufe 12 s bis
|
|
17
|
+
* 122 s statt unter einer Sekunde. Ein fester Ordner laesst sich zudem
|
|
18
|
+
* gezielt als Ausnahme eintragen.
|
|
19
|
+
* 2. PowerShell holt sich die Anwendung ueber COM und uebergibt ihr das
|
|
20
|
+
* Skript per doScript, den Befehl und den Ergebnispfad ueber
|
|
21
|
+
* withArguments (im Skript `arguments`). Erst GetActiveObject (Running
|
|
13
22
|
* Object Table), dann CoCreateInstance (New-Object -ComObject): InDesign
|
|
14
23
|
* traegt sich nicht in die Running Object Table ein, CoCreateInstance
|
|
15
24
|
* verbindet aber mit der laufenden Instanz und startet sie sonst - wie
|
|
@@ -29,24 +38,34 @@
|
|
|
29
38
|
* der Rueckfall auf CoCreateInstance traegt.
|
|
30
39
|
*/
|
|
31
40
|
import { spawn } from 'node:child_process';
|
|
32
|
-
import {
|
|
41
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
42
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from 'node:fs';
|
|
33
43
|
import { tmpdir } from 'node:os';
|
|
34
44
|
import { join } from 'node:path';
|
|
35
45
|
import { buildScriptBundle, toAsciiJson } from './script-bundle.js';
|
|
36
|
-
import { transportError, } from './transport.js';
|
|
46
|
+
import { attachTiming, transportError, } from './transport.js';
|
|
37
47
|
/** ScriptLanguage.javascript im Objektmodell – als Zahl, weil COM keine Aufzaehlung kennt. */
|
|
38
48
|
export const SCRIPT_LANGUAGE_JAVASCRIPT = 1246973031;
|
|
39
49
|
/** Zeilen, mit denen sich das PowerShell-Skript meldet, wenn es selbst abbricht. */
|
|
40
50
|
export const APP_NOT_AVAILABLE_MARKER = 'IDC-COM-ERROR app_not_available:';
|
|
41
51
|
export const SCRIPT_ERROR_MARKER = 'IDC-COM-ERROR script_error:';
|
|
52
|
+
/** Zeile mit den Zeitanteilen aus PowerShell (nur Protokoll, A-74). */
|
|
53
|
+
export const TIMING_MARKER = 'IDC-COM-TIMING';
|
|
54
|
+
/** Aufgeraeumt wird, was laenger als einen Tag im Arbeitsordner liegt. */
|
|
55
|
+
const STALE_MS = 24 * 60 * 60 * 1000;
|
|
56
|
+
/**
|
|
57
|
+
* Fester Arbeitsordner des Windows-Transports:
|
|
58
|
+
* `%LOCALAPPDATA%\<produkt>\com`, ohne LOCALAPPDATA im Temp-Ordner.
|
|
59
|
+
*/
|
|
60
|
+
export function defaultComWorkDir(dataDirName = 'layoutbridge-spm', env = process.env) {
|
|
61
|
+
const local = env['LOCALAPPDATA'];
|
|
62
|
+
const base = local !== undefined && local.length > 0 ? local : tmpdir();
|
|
63
|
+
return join(base, dataDirName, 'com');
|
|
64
|
+
}
|
|
42
65
|
/** Text in einem PowerShell-Literal mit einfachen Anfuehrungszeichen. */
|
|
43
66
|
export function escapeForPowerShell(text) {
|
|
44
67
|
return text.replace(/'/g, "''");
|
|
45
68
|
}
|
|
46
|
-
/** Text in einem JavaScript-Literal mit doppelten Anfuehrungszeichen. */
|
|
47
|
-
function escapeForJsString(text) {
|
|
48
|
-
return text.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
49
|
-
}
|
|
50
69
|
/**
|
|
51
70
|
* Wertet den Ausgang eines Aufrufs aus. Reine Funktion ohne Dateisystem und
|
|
52
71
|
* ohne Prozess, damit die Einheitstests jede Fehlerart ohne Windows pruefen.
|
|
@@ -95,6 +114,60 @@ function markedMessage(stderr, marker) {
|
|
|
95
114
|
const line = (end < 0 ? rest : rest.slice(0, end)).trim();
|
|
96
115
|
return line.length > 0 ? line : marker.replace(':', '');
|
|
97
116
|
}
|
|
117
|
+
/** Zieht die Zeitzeile aus stderr: Zeitanteile und der uebrige Text. */
|
|
118
|
+
export function splitTiming(stderr) {
|
|
119
|
+
const timing = {};
|
|
120
|
+
const kept = [];
|
|
121
|
+
for (const line of stderr.split(/\r?\n/)) {
|
|
122
|
+
const at = line.indexOf(TIMING_MARKER);
|
|
123
|
+
if (at < 0) {
|
|
124
|
+
kept.push(line);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
for (const pair of line.slice(at + TIMING_MARKER.length).trim().split(/\s+/)) {
|
|
128
|
+
const eq = pair.indexOf('=');
|
|
129
|
+
if (eq <= 0)
|
|
130
|
+
continue;
|
|
131
|
+
const value = Number(pair.slice(eq + 1));
|
|
132
|
+
if (Number.isFinite(value))
|
|
133
|
+
timing['ps_' + pair.slice(0, eq)] = value;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return { timing, rest: kept.join('\n') };
|
|
137
|
+
}
|
|
138
|
+
/** Kurze Pruefsumme eines Textes fuer Dateinamen. */
|
|
139
|
+
function shortHash(text) {
|
|
140
|
+
return createHash('sha256').update(text, 'utf8').digest('hex').slice(0, 16);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Schreibt eine Datei nur, wenn sie mit diesem Inhalt noch nicht da ist
|
|
144
|
+
* (Pruefsumme im Namen, Groesse als Vollstaendigkeitsprobe). Erst unter
|
|
145
|
+
* eigenem Namen, dann umbenennen: Mehrere Serverinstanzen koennen
|
|
146
|
+
* gleichzeitig starten, keine liest eine halb geschriebene Datei.
|
|
147
|
+
* Liefert true, wenn geschrieben wurde.
|
|
148
|
+
*/
|
|
149
|
+
export function ensureFile(path, content) {
|
|
150
|
+
const size = Buffer.byteLength(content, 'utf8');
|
|
151
|
+
try {
|
|
152
|
+
if (existsSync(path) && statSync(path).size === size)
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
/* dann neu schreiben */
|
|
157
|
+
}
|
|
158
|
+
const temporary = path + '.' + process.pid + '.' + randomBytes(4).toString('hex') + '.tmp';
|
|
159
|
+
writeFileSync(temporary, content, 'utf8');
|
|
160
|
+
try {
|
|
161
|
+
renameSync(temporary, path);
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
rmSync(temporary, { force: true });
|
|
165
|
+
/* Eine andere Instanz war schneller und haelt die Datei offen. */
|
|
166
|
+
if (!(existsSync(path) && statSync(path).size === size))
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
98
171
|
export class ComTransport {
|
|
99
172
|
name = 'com';
|
|
100
173
|
progIds;
|
|
@@ -102,44 +175,69 @@ export class ComTransport {
|
|
|
102
175
|
defaultTimeoutMs;
|
|
103
176
|
keepTempFiles;
|
|
104
177
|
runner;
|
|
178
|
+
workDir;
|
|
179
|
+
sequence = 0;
|
|
180
|
+
cleaned = false;
|
|
181
|
+
/** Zuletzt bereitgelegtes Buendel (gleiches Objekt = gleicher Stand). */
|
|
182
|
+
preparedBundle = null;
|
|
183
|
+
preparedScriptPath = '';
|
|
184
|
+
preparedPowerShellPath = '';
|
|
105
185
|
constructor(options = {}) {
|
|
106
186
|
this.progIds = options.progIds ?? ['InDesign.Application'];
|
|
107
187
|
this.powershellPath = options.powershellPath ?? 'powershell.exe';
|
|
108
188
|
this.defaultTimeoutMs = options.defaultTimeoutMs ?? 120_000;
|
|
109
189
|
this.keepTempFiles = options.keepTempFiles ?? false;
|
|
110
190
|
this.runner = options.runner ?? runProcess;
|
|
191
|
+
this.workDir = options.workDir ?? defaultComWorkDir(options.dataDirName);
|
|
111
192
|
}
|
|
112
|
-
/**
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
193
|
+
/**
|
|
194
|
+
* Das vollstaendige Skript: Buendel plus fester Rahmen. Der Befehl steht
|
|
195
|
+
* nicht darin – er kommt ueber withArguments (arguments[0] Befehl als
|
|
196
|
+
* ASCII-JSON, arguments[1] Ergebnispfad). So bleibt die Datei je
|
|
197
|
+
* Buendelstand gleich.
|
|
198
|
+
*/
|
|
199
|
+
buildScript(bundle = buildScriptBundle()) {
|
|
117
200
|
const frame = [
|
|
118
201
|
'',
|
|
119
202
|
'/* ---- Rahmen des com-Transports (ExtendScript, Windows) ---- */',
|
|
120
|
-
'(
|
|
121
|
-
'
|
|
122
|
-
' var __file
|
|
203
|
+
'var __idcArgs = (typeof arguments !== "undefined" && arguments !== null) ? arguments : [];',
|
|
204
|
+
'(function (commandText, resultPath) {',
|
|
205
|
+
' var __text, __file;',
|
|
206
|
+
' if (commandText === undefined || commandText === null || resultPath === undefined || resultPath === null) {',
|
|
207
|
+
' return "missing arguments";',
|
|
208
|
+
' }',
|
|
209
|
+
' __text = IDC.run(eval("(" + String(commandText) + ")"));',
|
|
210
|
+
' __file = new File(String(resultPath));',
|
|
123
211
|
' __file.encoding = "UTF-8";',
|
|
124
212
|
' __file.lineFeed = "Unix";',
|
|
125
213
|
' __file.open("w");',
|
|
126
214
|
' __file.write(__text);',
|
|
127
215
|
' __file.close();',
|
|
128
216
|
' return "ok";',
|
|
129
|
-
'})();',
|
|
217
|
+
'})(__idcArgs[0], __idcArgs[1]);',
|
|
130
218
|
'',
|
|
131
219
|
].join('\n');
|
|
132
220
|
return bundle + '\n' + frame;
|
|
133
221
|
}
|
|
134
222
|
/**
|
|
135
|
-
* Das PowerShell-Skript: Anwendung ueber COM holen (GetActiveObject,
|
|
136
|
-
* New-Object -ComObject),
|
|
137
|
-
*
|
|
223
|
+
* Das feste PowerShell-Skript: Anwendung ueber COM holen (GetActiveObject,
|
|
224
|
+
* sonst New-Object -ComObject), Skript- und Befehlsdatei lesen, doScript
|
|
225
|
+
* mit withArguments aufrufen. Pfade kommen als Parameter. Bricht es selbst
|
|
226
|
+
* ab, meldet es sich mit einer Markerzeile, damit die Fehlerart nicht aus
|
|
227
|
+
* Fremdtexten geraten wird. Zeitanteile stehen in einer eigenen Zeile.
|
|
138
228
|
*/
|
|
139
|
-
buildPowerShell(
|
|
229
|
+
buildPowerShell() {
|
|
140
230
|
const ids = this.progIds.map((id) => "'" + escapeForPowerShell(id) + "'").join(', ');
|
|
141
231
|
return [
|
|
232
|
+
'param([string]$ScriptPath, [string]$CommandPath, [string]$ResultPath)',
|
|
142
233
|
'$ErrorActionPreference = ' + "'Stop'",
|
|
234
|
+
'$watch = [Diagnostics.Stopwatch]::StartNew()',
|
|
235
|
+
'$marks = @{}',
|
|
236
|
+
'function Write-IdcTiming {',
|
|
237
|
+
" $line = '" + TIMING_MARKER + "'",
|
|
238
|
+
' foreach ($key in $marks.Keys) { $line += (" " + $key + "=" + $marks[$key]) }',
|
|
239
|
+
' [Console]::Error.WriteLine($line)',
|
|
240
|
+
'}',
|
|
143
241
|
'$progIds = @(' + ids + ')',
|
|
144
242
|
'$app = $null',
|
|
145
243
|
'$reasons = @()',
|
|
@@ -161,40 +259,125 @@ export class ComTransport {
|
|
|
161
259
|
' }',
|
|
162
260
|
' }',
|
|
163
261
|
'}',
|
|
262
|
+
"$marks['connect'] = $watch.ElapsedMilliseconds",
|
|
164
263
|
'if ($null -eq $app) {',
|
|
264
|
+
' Write-IdcTiming',
|
|
165
265
|
' [Console]::Error.WriteLine(' + "'" + APP_NOT_AVAILABLE_MARKER +
|
|
166
266
|
" ' + ($reasons -join '; '))",
|
|
167
267
|
' exit 3',
|
|
168
268
|
'}',
|
|
169
|
-
'$scriptText = [IO.File]::ReadAllText(
|
|
170
|
-
|
|
269
|
+
'$scriptText = [IO.File]::ReadAllText($ScriptPath, [Text.Encoding]::UTF8)',
|
|
270
|
+
'$commandText = [IO.File]::ReadAllText($CommandPath, [Text.Encoding]::UTF8)',
|
|
271
|
+
"$marks['read'] = $watch.ElapsedMilliseconds - $marks['connect']",
|
|
171
272
|
'try {',
|
|
172
|
-
' $null = $app.DoScript($scriptText, ' + SCRIPT_LANGUAGE_JAVASCRIPT +
|
|
273
|
+
' $null = $app.DoScript($scriptText, ' + SCRIPT_LANGUAGE_JAVASCRIPT +
|
|
274
|
+
', [object[]]@($commandText, $ResultPath))',
|
|
173
275
|
'} catch {',
|
|
276
|
+
" $marks['doscript'] = $watch.ElapsedMilliseconds - $marks['connect'] - $marks['read']",
|
|
277
|
+
' Write-IdcTiming',
|
|
174
278
|
' [Console]::Error.WriteLine(' + "'" + SCRIPT_ERROR_MARKER +
|
|
175
279
|
" ' + $_.Exception.Message)",
|
|
176
280
|
' exit 4',
|
|
177
281
|
'}',
|
|
282
|
+
"$marks['doscript'] = $watch.ElapsedMilliseconds - $marks['connect'] - $marks['read']",
|
|
283
|
+
"$marks['total'] = $watch.ElapsedMilliseconds",
|
|
284
|
+
'Write-IdcTiming',
|
|
178
285
|
'exit 0',
|
|
179
286
|
'',
|
|
180
287
|
].join('\n');
|
|
181
288
|
}
|
|
289
|
+
/**
|
|
290
|
+
* Legt Skript- und PowerShell-Datei in den Arbeitsordner, falls noetig.
|
|
291
|
+
* Liefert true, wenn in diesem Aufruf eine davon geschrieben wurde.
|
|
292
|
+
*/
|
|
293
|
+
prepare() {
|
|
294
|
+
const bundle = buildScriptBundle();
|
|
295
|
+
if (bundle === this.preparedBundle && existsSync(this.preparedScriptPath) && existsSync(this.preparedPowerShellPath)) {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
mkdirSync(this.workDir, { recursive: true });
|
|
299
|
+
const script = this.buildScript(bundle);
|
|
300
|
+
const powerShell = this.buildPowerShell();
|
|
301
|
+
const scriptPath = join(this.workDir, 'script-' + shortHash(script) + '.jsx');
|
|
302
|
+
const powerShellPath = join(this.workDir, 'run-' + shortHash(powerShell) + '.ps1');
|
|
303
|
+
let written = ensureFile(scriptPath, script);
|
|
304
|
+
written = ensureFile(powerShellPath, powerShell) || written;
|
|
305
|
+
this.preparedBundle = bundle;
|
|
306
|
+
this.preparedScriptPath = scriptPath;
|
|
307
|
+
this.preparedPowerShellPath = powerShellPath;
|
|
308
|
+
if (!this.cleaned) {
|
|
309
|
+
this.cleaned = true;
|
|
310
|
+
this.removeStale([scriptPath, powerShellPath]);
|
|
311
|
+
}
|
|
312
|
+
return written;
|
|
313
|
+
}
|
|
314
|
+
/** Alte Dateien frueherer Staende und liegengebliebener Aufrufe entfernen. */
|
|
315
|
+
removeStale(keep) {
|
|
316
|
+
const now = Date.now();
|
|
317
|
+
let names = [];
|
|
318
|
+
try {
|
|
319
|
+
names = readdirSync(this.workDir);
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
for (const name of names) {
|
|
325
|
+
const path = join(this.workDir, name);
|
|
326
|
+
if (keep.includes(path))
|
|
327
|
+
continue;
|
|
328
|
+
if (!/^(script-.*\.jsx|run-.*\.ps1|call-.*\.json|.*\.tmp)$/.test(name))
|
|
329
|
+
continue;
|
|
330
|
+
try {
|
|
331
|
+
if (now - statSync(path).mtimeMs > STALE_MS)
|
|
332
|
+
rmSync(path, { force: true });
|
|
333
|
+
}
|
|
334
|
+
catch {
|
|
335
|
+
/* Aufraeumen darf keinen Aufruf scheitern lassen. */
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
182
339
|
async send(command, options = {}) {
|
|
183
340
|
const timeoutMs = options.timeoutMs ?? this.defaultTimeoutMs;
|
|
184
|
-
|
|
341
|
+
const timing = { begin: Date.now() };
|
|
342
|
+
let mark = Date.now();
|
|
343
|
+
const lap = (key) => {
|
|
344
|
+
const now = Date.now();
|
|
345
|
+
timing[key] = now - mark;
|
|
346
|
+
mark = now;
|
|
347
|
+
};
|
|
348
|
+
this.sequence += 1;
|
|
349
|
+
const stem = 'call-' + process.pid + '-' + this.sequence + '-' + randomBytes(4).toString('hex');
|
|
350
|
+
const commandPath = join(this.workDir, stem + '.command.json');
|
|
351
|
+
const resultPath = join(this.workDir, stem + '.result.json');
|
|
185
352
|
try {
|
|
186
|
-
|
|
353
|
+
timing['script_written'] = this.prepare() ? 1 : 0;
|
|
354
|
+
writeFileSync(commandPath, toAsciiJson(command), 'utf8');
|
|
187
355
|
}
|
|
188
356
|
catch (error) {
|
|
189
|
-
return transportError('io_error', 'Could not
|
|
357
|
+
return transportError('io_error', 'Could not prepare the script files of the Windows transport.', String(error));
|
|
190
358
|
}
|
|
191
|
-
|
|
192
|
-
const resultPath = join(directory, 'result.json');
|
|
193
|
-
const powerShellPath = join(directory, 'command.ps1');
|
|
359
|
+
lap('prepare');
|
|
194
360
|
try {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
361
|
+
const outcome = await this.runner(this.powershellPath, [
|
|
362
|
+
'-NoProfile',
|
|
363
|
+
'-NonInteractive',
|
|
364
|
+
'-ExecutionPolicy',
|
|
365
|
+
'Bypass',
|
|
366
|
+
'-File',
|
|
367
|
+
this.preparedPowerShellPath,
|
|
368
|
+
'-ScriptPath',
|
|
369
|
+
this.preparedScriptPath,
|
|
370
|
+
'-CommandPath',
|
|
371
|
+
commandPath,
|
|
372
|
+
'-ResultPath',
|
|
373
|
+
resultPath,
|
|
374
|
+
], timeoutMs);
|
|
375
|
+
lap('process');
|
|
376
|
+
const split = splitTiming(outcome.stderr);
|
|
377
|
+
Object.assign(timing, split.timing);
|
|
378
|
+
if (split.timing['ps_total'] !== undefined && timing['process'] !== undefined) {
|
|
379
|
+
timing['ps_start'] = Math.max(0, timing['process'] - split.timing['ps_total']);
|
|
380
|
+
}
|
|
198
381
|
let raw = null;
|
|
199
382
|
try {
|
|
200
383
|
raw = readFileSync(resultPath, 'utf8');
|
|
@@ -202,17 +385,20 @@ export class ComTransport {
|
|
|
202
385
|
catch {
|
|
203
386
|
raw = null;
|
|
204
387
|
}
|
|
205
|
-
|
|
388
|
+
lap('read_result');
|
|
389
|
+
return attachTiming(interpretOutcome({ ...outcome, stderr: split.rest }, raw, command, timeoutMs), timing);
|
|
206
390
|
}
|
|
207
391
|
finally {
|
|
208
392
|
if (!this.keepTempFiles) {
|
|
209
393
|
try {
|
|
210
|
-
rmSync(
|
|
394
|
+
rmSync(commandPath, { force: true });
|
|
395
|
+
rmSync(resultPath, { force: true });
|
|
211
396
|
}
|
|
212
397
|
catch {
|
|
213
398
|
/* Aufraeumen darf keinen Aufruf scheitern lassen. */
|
|
214
399
|
}
|
|
215
400
|
}
|
|
401
|
+
lap('cleanup');
|
|
216
402
|
}
|
|
217
403
|
}
|
|
218
404
|
async close() {
|
|
@@ -20,6 +20,28 @@ export const ERROR_KINDS = [
|
|
|
20
20
|
'script_error',
|
|
21
21
|
'license',
|
|
22
22
|
];
|
|
23
|
+
/**
|
|
24
|
+
* Zeitanteile eines Aufrufs fuer das Protokoll (A-74, Sitzung 80). Haengt
|
|
25
|
+
* unter einem Symbol am Ergebnis: JSON.stringify und Object.keys sehen es
|
|
26
|
+
* nicht, die Antwort an den Client bleibt also unveraendert. Der Aufrufer
|
|
27
|
+
* nimmt es mit takeTiming() ab.
|
|
28
|
+
*/
|
|
29
|
+
export const TIMING = Symbol('idc.timing');
|
|
30
|
+
export function attachTiming(result, timing) {
|
|
31
|
+
Object.defineProperty(result, TIMING, { value: timing, enumerable: false, configurable: true });
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
/** Liest die Zeitanteile und entfernt sie vom Ergebnis; null, wenn keine da sind. */
|
|
35
|
+
export function takeTiming(result) {
|
|
36
|
+
if (result === null || typeof result !== 'object')
|
|
37
|
+
return null;
|
|
38
|
+
const holder = result;
|
|
39
|
+
const timing = holder[TIMING];
|
|
40
|
+
if (timing === undefined)
|
|
41
|
+
return null;
|
|
42
|
+
delete holder[TIMING];
|
|
43
|
+
return timing;
|
|
44
|
+
}
|
|
23
45
|
/** Baut einen Fehlerumschlag, ohne dass InDesign beteiligt war. */
|
|
24
46
|
export function transportError(kind, message, details = null) {
|
|
25
47
|
return { ok: false, warnings: [], error: { kind, message, details } };
|