layoutbridge-spm 0.0.1 → 1.0.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/CHANGELOG.md +171 -0
- package/LICENSE +26 -0
- package/README.de.md +318 -0
- package/README.md +304 -4
- package/THIRD-PARTY-NOTICES.md +2878 -0
- package/dist/src/mcp/config.js +125 -0
- package/dist/src/mcp/dispatch.js +123 -0
- package/dist/src/mcp/license/free-tools.js +16 -0
- package/dist/src/mcp/license/gate.js +209 -0
- package/dist/src/mcp/license/index.js +96 -0
- package/dist/src/mcp/license/instance-name.js +56 -0
- package/dist/src/mcp/license/key-source.js +58 -0
- package/dist/src/mcp/license/lemonsqueezy-verifier.js +207 -0
- package/dist/src/mcp/license/placeholder-verifier.js +10 -0
- package/dist/src/mcp/license/polar-ids.js +63 -0
- package/dist/src/mcp/license/polar-verifier.js +265 -0
- package/dist/src/mcp/license/product-ids.js +72 -0
- package/dist/src/mcp/license/state-store.js +91 -0
- package/dist/src/mcp/license/types.js +11 -0
- package/dist/src/mcp/logging.js +270 -0
- package/dist/src/mcp/product-info.js +48 -0
- package/dist/src/mcp/server.js +250 -0
- package/dist/src/mcp/setup.js +608 -0
- package/dist/src/mcp/start.cjs +95 -0
- package/dist/src/mcp/tool-definition.js +2 -0
- package/dist/src/mcp/tools.js +2004 -0
- package/dist/src/mcp/typography-rules.js +70 -0
- package/dist/src/transport/com.js +250 -0
- package/dist/src/transport/osascript.js +171 -0
- package/dist/src/transport/queue.js +19 -0
- package/dist/src/transport/script-bundle.js +92 -0
- package/dist/src/transport/transport.js +30 -0
- package/indesign/commands/10-app.js +6 -0
- package/indesign/commands/20-documents.js +513 -0
- package/indesign/commands/22-document-setup.js +118 -0
- package/indesign/commands/23-save-package.js +255 -0
- package/indesign/commands/25-export.js +379 -0
- package/indesign/commands/26-export-image.js +197 -0
- package/indesign/commands/27-textdefaults.js +151 -0
- package/indesign/commands/28-selection.js +174 -0
- package/indesign/commands/30-pages.js +1032 -0
- package/indesign/commands/31-view.js +210 -0
- package/indesign/commands/32-sections.js +203 -0
- package/indesign/commands/35-parents.js +428 -0
- package/indesign/commands/37-layout.js +760 -0
- package/indesign/commands/40-frames.js +836 -0
- package/indesign/commands/41-shapes.js +505 -0
- package/indesign/commands/42-groups.js +117 -0
- package/indesign/commands/43-layers.js +291 -0
- package/indesign/commands/44-effects.js +274 -0
- package/indesign/commands/45-frame-options.js +247 -0
- package/indesign/commands/47-anchored.js +287 -0
- package/indesign/commands/50-modify.js +781 -0
- package/indesign/commands/52-align.js +139 -0
- package/indesign/commands/55-fit.js +106 -0
- package/indesign/commands/60-images.js +771 -0
- package/indesign/commands/62-embed.js +120 -0
- package/indesign/commands/69-placeholders.js +127 -0
- package/indesign/commands/70-text.js +567 -0
- package/indesign/commands/71-outlines.js +92 -0
- package/indesign/commands/72-place-text.js +366 -0
- package/indesign/commands/74-stories.js +116 -0
- package/indesign/commands/75-tables.js +676 -0
- package/indesign/commands/76-text-variables.js +260 -0
- package/indesign/commands/77-story-options.js +86 -0
- package/indesign/commands/78-frame-lines.js +304 -0
- package/indesign/commands/79-check-typography.js +303 -0
- package/indesign/commands/80-styles.js +1620 -0
- package/indesign/commands/81-composition.js +355 -0
- package/indesign/commands/82-object-styles.js +263 -0
- package/indesign/commands/83-indents-tabs.js +237 -0
- package/indesign/commands/84-import-styles.js +142 -0
- package/indesign/commands/85-swatches.js +112 -0
- package/indesign/commands/86-gradients.js +185 -0
- package/indesign/commands/87-replace-swatch.js +180 -0
- package/indesign/commands/88-replace-font.js +217 -0
- package/indesign/interpreter.js +758 -0
- package/package.json +55 -6
- package/rules/typography-rules.json +31 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regelkatalog fuer check_typography (A-7.6): eine Datentabelle im Repo
|
|
3
|
+
* (rules/typography-rules.json), kein Code je Regel. Der Server liest sie
|
|
4
|
+
* einmal, waehlt nach Sprache und Kennungen aus und reicht die Zeilen als
|
|
5
|
+
* Befehlsargument an den Interpreter; der kennt keine Regel von sich aus.
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync } from 'node:fs';
|
|
8
|
+
import { dirname, join } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
export const TYPOGRAPHY_LOCALES = ['de', 'de-CH', 'en'];
|
|
11
|
+
export const SEVERITY_RANK = { info: 0, warning: 1, error: 2 };
|
|
12
|
+
/* Diese Datei laeuft aus dist/src/mcp/ – drei Ebenen ueber ihr liegt das Repo bzw. Paket. */
|
|
13
|
+
export function typographyRulesPath() {
|
|
14
|
+
return join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..', 'rules', 'typography-rules.json');
|
|
15
|
+
}
|
|
16
|
+
function isRule(value) {
|
|
17
|
+
if (typeof value !== 'object' || value === null)
|
|
18
|
+
return false;
|
|
19
|
+
const r = value;
|
|
20
|
+
return (typeof r['id'] === 'string' &&
|
|
21
|
+
Array.isArray(r['locales']) &&
|
|
22
|
+
typeof r['category'] === 'string' &&
|
|
23
|
+
typeof r['severity'] === 'string' &&
|
|
24
|
+
(r['kind'] === 'grep' || r['kind'] === 'lines') &&
|
|
25
|
+
(r['kind'] === 'grep' ? typeof r['pattern'] === 'string' : typeof r['check'] === 'string') &&
|
|
26
|
+
typeof r['message'] === 'string' &&
|
|
27
|
+
typeof r['suggestion'] === 'string' &&
|
|
28
|
+
typeof r['source'] === 'string');
|
|
29
|
+
}
|
|
30
|
+
export function parseTypographyRules(text) {
|
|
31
|
+
const raw = JSON.parse(text);
|
|
32
|
+
if (!Array.isArray(raw.rules))
|
|
33
|
+
throw new Error('typography-rules.json: "rules" must be an array');
|
|
34
|
+
const out = [];
|
|
35
|
+
raw.rules.forEach((entry, index) => {
|
|
36
|
+
if (!isRule(entry))
|
|
37
|
+
throw new Error(`typography-rules.json: rule ${index} is malformed`);
|
|
38
|
+
out.push(entry);
|
|
39
|
+
});
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
let cache = null;
|
|
43
|
+
export function loadTypographyRules(path = typographyRulesPath()) {
|
|
44
|
+
if (cache === null)
|
|
45
|
+
cache = parseTypographyRules(readFileSync(path, 'utf8'));
|
|
46
|
+
return cache;
|
|
47
|
+
}
|
|
48
|
+
/** Alle Kennungen des Katalogs (ohne Doppelte, in Katalogreihenfolge). */
|
|
49
|
+
export function typographyRuleIds(rules = loadTypographyRules()) {
|
|
50
|
+
const out = [];
|
|
51
|
+
for (const rule of rules)
|
|
52
|
+
if (!out.includes(rule.id))
|
|
53
|
+
out.push(rule.id);
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Zeilen fuer einen Aufruf: Sprache, gewuenschte und ausgeschlossene
|
|
58
|
+
* Kennungen. Unbekannte Kennungen bleiben in unknown[] – der Interpreter
|
|
59
|
+
* meldet sie als invalid_argument, damit der Fehler wie alle anderen im
|
|
60
|
+
* Ergebnisumschlag steht.
|
|
61
|
+
*/
|
|
62
|
+
export function selectTypographyRules(locale, wanted, excluded, rules = loadTypographyRules()) {
|
|
63
|
+
const ids = typographyRuleIds(rules);
|
|
64
|
+
const unknown = [...(wanted ?? []), ...(excluded ?? [])].filter((id) => !ids.includes(id));
|
|
65
|
+
const chosen = rules.filter((rule) => rule.locales.includes(locale) &&
|
|
66
|
+
(wanted === undefined || wanted.length === 0 || wanted.includes(rule.id)) &&
|
|
67
|
+
!(excluded ?? []).includes(rule.id));
|
|
68
|
+
return { rules: chosen, unknown };
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=typography-rules.js.map
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport fuer Windows: PowerShell -> COM -> InDesign doScript.
|
|
3
|
+
*
|
|
4
|
+
* Gegenstueck zu osascript.ts. Nur diese Datei weiss, dass hier ein
|
|
5
|
+
* Windows-Rechner steht; der Interpreter und das Befehlsformat {cmd, args}
|
|
6
|
+
* bleiben unveraendert.
|
|
7
|
+
*
|
|
8
|
+
* Ablauf je Aufruf:
|
|
9
|
+
* 1. Interpreter und Handler zu einem Skript zusammensetzen, den Befehl als
|
|
10
|
+
* ASCII-JSON hineinschreiben und in eine temporaere .jsx-Datei legen.
|
|
11
|
+
* 2. Eine .ps1-Datei danebenlegen, die sich die Anwendung ueber COM holt und
|
|
12
|
+
* ihr das Skript per doScript uebergibt. Erst GetActiveObject (Running
|
|
13
|
+
* Object Table), dann CoCreateInstance (New-Object -ComObject): InDesign
|
|
14
|
+
* traegt sich nicht in die Running Object Table ein, CoCreateInstance
|
|
15
|
+
* verbindet aber mit der laufenden Instanz und startet sie sonst - wie
|
|
16
|
+
* `tell application` beim Mac-Transport (Vermerk E-70, 15.09.2026).
|
|
17
|
+
* 3. Das Skript schreibt sein Ergebnis in eine Datei; der Server liest sie.
|
|
18
|
+
* Umweg ueber die Datei wie beim Mac-Transport: die Rueckgabe ueber COM
|
|
19
|
+
* ist in Laenge und Zeichensatz unzuverlaessig.
|
|
20
|
+
*
|
|
21
|
+
* Gebaut ohne Windows-Rechner gegen eine Attrappe des Prozessstarts
|
|
22
|
+
* (Vermerk E-67). Transportweg am 15.09.2026 mit InDesign CS5 (7.0.0.355)
|
|
23
|
+
* auf Windows 11 ARM64 nachgewiesen (Vermerk E-70). Abgenommen am
|
|
24
|
+
* 15.09.2026 (Vermerk E-71): Integrationssuite 108/108 ueber diesen
|
|
25
|
+
* Transport mit InDesign 21.5.1.73 auf Windows 11 x64 (Build 26200,
|
|
26
|
+
* Windows 365 Cloud-PC, Node 24.21.0); scripts/com-test.ps1 Exit 0,
|
|
27
|
+
* scripts/win-smoke.mjs 7/7. Auch das aktuelle InDesign traegt sich nicht
|
|
28
|
+
* in die Running Object Table ein (GetActiveObject: MK_E_UNAVAILABLE),
|
|
29
|
+
* der Rueckfall auf CoCreateInstance traegt.
|
|
30
|
+
*/
|
|
31
|
+
import { spawn } from 'node:child_process';
|
|
32
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
33
|
+
import { tmpdir } from 'node:os';
|
|
34
|
+
import { join } from 'node:path';
|
|
35
|
+
import { buildScriptBundle, toAsciiJson } from './script-bundle.js';
|
|
36
|
+
import { transportError, } from './transport.js';
|
|
37
|
+
/** ScriptLanguage.javascript im Objektmodell – als Zahl, weil COM keine Aufzaehlung kennt. */
|
|
38
|
+
export const SCRIPT_LANGUAGE_JAVASCRIPT = 1246973031;
|
|
39
|
+
/** Zeilen, mit denen sich das PowerShell-Skript meldet, wenn es selbst abbricht. */
|
|
40
|
+
export const APP_NOT_AVAILABLE_MARKER = 'IDC-COM-ERROR app_not_available:';
|
|
41
|
+
export const SCRIPT_ERROR_MARKER = 'IDC-COM-ERROR script_error:';
|
|
42
|
+
/** Text in einem PowerShell-Literal mit einfachen Anfuehrungszeichen. */
|
|
43
|
+
export function escapeForPowerShell(text) {
|
|
44
|
+
return text.replace(/'/g, "''");
|
|
45
|
+
}
|
|
46
|
+
/** Text in einem JavaScript-Literal mit doppelten Anfuehrungszeichen. */
|
|
47
|
+
function escapeForJsString(text) {
|
|
48
|
+
return text.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Wertet den Ausgang eines Aufrufs aus. Reine Funktion ohne Dateisystem und
|
|
52
|
+
* ohne Prozess, damit die Einheitstests jede Fehlerart ohne Windows pruefen.
|
|
53
|
+
*/
|
|
54
|
+
export function interpretOutcome(outcome, raw, command, timeoutMs) {
|
|
55
|
+
if (outcome.spawnError !== null) {
|
|
56
|
+
return transportError('app_not_available', 'Could not run PowerShell – this transport needs Windows: ' + outcome.spawnError.message, null);
|
|
57
|
+
}
|
|
58
|
+
if (outcome.timedOut) {
|
|
59
|
+
return transportError('timeout', 'InDesign did not answer within ' + timeoutMs +
|
|
60
|
+
' ms. The script may still be running inside InDesign – check the document before repeating the call.', { command: command.cmd, timeout_ms: timeoutMs });
|
|
61
|
+
}
|
|
62
|
+
const stderr = outcome.stderr.trim();
|
|
63
|
+
const marked = markedMessage(stderr, APP_NOT_AVAILABLE_MARKER);
|
|
64
|
+
if (marked !== null) {
|
|
65
|
+
return transportError('app_not_available', marked, { exit_code: outcome.code });
|
|
66
|
+
}
|
|
67
|
+
const scriptMarked = markedMessage(stderr, SCRIPT_ERROR_MARKER);
|
|
68
|
+
if (scriptMarked !== null) {
|
|
69
|
+
return transportError('script_error', scriptMarked, { exit_code: outcome.code });
|
|
70
|
+
}
|
|
71
|
+
if (raw === null || raw.length === 0) {
|
|
72
|
+
return transportError('script_error', stderr.length > 0
|
|
73
|
+
? 'InDesign did not return a result: ' + stderr
|
|
74
|
+
: 'InDesign did not return a result and reported nothing.', { exit_code: outcome.code, stdout: outcome.stdout.trim() });
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const parsed = JSON.parse(raw);
|
|
78
|
+
if (!Array.isArray(parsed.warnings))
|
|
79
|
+
parsed.warnings = [];
|
|
80
|
+
return parsed;
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
return transportError('script_error', 'The result of the script is not valid JSON: ' + String(error), {
|
|
84
|
+
raw: raw.slice(0, 2000),
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** Sucht die Markerzeile und gibt den Text dahinter zurueck, sonst null. */
|
|
89
|
+
function markedMessage(stderr, marker) {
|
|
90
|
+
const at = stderr.indexOf(marker);
|
|
91
|
+
if (at < 0)
|
|
92
|
+
return null;
|
|
93
|
+
const rest = stderr.slice(at + marker.length);
|
|
94
|
+
const end = rest.indexOf('\n');
|
|
95
|
+
const line = (end < 0 ? rest : rest.slice(0, end)).trim();
|
|
96
|
+
return line.length > 0 ? line : marker.replace(':', '');
|
|
97
|
+
}
|
|
98
|
+
export class ComTransport {
|
|
99
|
+
name = 'com';
|
|
100
|
+
progIds;
|
|
101
|
+
powershellPath;
|
|
102
|
+
defaultTimeoutMs;
|
|
103
|
+
keepTempFiles;
|
|
104
|
+
runner;
|
|
105
|
+
constructor(options = {}) {
|
|
106
|
+
this.progIds = options.progIds ?? ['InDesign.Application'];
|
|
107
|
+
this.powershellPath = options.powershellPath ?? 'powershell.exe';
|
|
108
|
+
this.defaultTimeoutMs = options.defaultTimeoutMs ?? 120_000;
|
|
109
|
+
this.keepTempFiles = options.keepTempFiles ?? false;
|
|
110
|
+
this.runner = options.runner ?? runProcess;
|
|
111
|
+
}
|
|
112
|
+
/** Das vollstaendige Skript inklusive Rahmen, der das Ergebnis wegschreibt. */
|
|
113
|
+
buildScript(command, resultPath) {
|
|
114
|
+
const bundle = buildScriptBundle();
|
|
115
|
+
const commandLiteral = toAsciiJson(command);
|
|
116
|
+
const target = escapeForJsString(resultPath);
|
|
117
|
+
const frame = [
|
|
118
|
+
'',
|
|
119
|
+
'/* ---- Rahmen des com-Transports (ExtendScript, Windows) ---- */',
|
|
120
|
+
'(function () {',
|
|
121
|
+
' var __text = IDC.run(' + commandLiteral + ');',
|
|
122
|
+
' var __file = new File("' + target + '");',
|
|
123
|
+
' __file.encoding = "UTF-8";',
|
|
124
|
+
' __file.lineFeed = "Unix";',
|
|
125
|
+
' __file.open("w");',
|
|
126
|
+
' __file.write(__text);',
|
|
127
|
+
' __file.close();',
|
|
128
|
+
' return "ok";',
|
|
129
|
+
'})();',
|
|
130
|
+
'',
|
|
131
|
+
].join('\n');
|
|
132
|
+
return bundle + '\n' + frame;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Das PowerShell-Skript: Anwendung ueber COM holen (GetActiveObject, sonst
|
|
136
|
+
* New-Object -ComObject), Skripttext lesen, doScript aufrufen. Bricht es selbst ab, meldet es sich mit einer
|
|
137
|
+
* Markerzeile, damit die Fehlerart nicht aus Fremdtexten geraten wird.
|
|
138
|
+
*/
|
|
139
|
+
buildPowerShell(scriptPath) {
|
|
140
|
+
const ids = this.progIds.map((id) => "'" + escapeForPowerShell(id) + "'").join(', ');
|
|
141
|
+
return [
|
|
142
|
+
'$ErrorActionPreference = ' + "'Stop'",
|
|
143
|
+
'$progIds = @(' + ids + ')',
|
|
144
|
+
'$app = $null',
|
|
145
|
+
'$reasons = @()',
|
|
146
|
+
'foreach ($id in $progIds) {',
|
|
147
|
+
' try {',
|
|
148
|
+
' $app = [Runtime.InteropServices.Marshal]::GetActiveObject($id)',
|
|
149
|
+
' break',
|
|
150
|
+
' } catch {',
|
|
151
|
+
' $reasons += ($id + " (GetActiveObject): " + $_.Exception.Message)',
|
|
152
|
+
' }',
|
|
153
|
+
'}',
|
|
154
|
+
'if ($null -eq $app) {',
|
|
155
|
+
' foreach ($id in $progIds) {',
|
|
156
|
+
' try {',
|
|
157
|
+
' $app = New-Object -ComObject $id',
|
|
158
|
+
' break',
|
|
159
|
+
' } catch {',
|
|
160
|
+
' $reasons += ($id + " (New-Object): " + $_.Exception.Message)',
|
|
161
|
+
' }',
|
|
162
|
+
' }',
|
|
163
|
+
'}',
|
|
164
|
+
'if ($null -eq $app) {',
|
|
165
|
+
' [Console]::Error.WriteLine(' + "'" + APP_NOT_AVAILABLE_MARKER +
|
|
166
|
+
" ' + ($reasons -join '; '))",
|
|
167
|
+
' exit 3',
|
|
168
|
+
'}',
|
|
169
|
+
'$scriptText = [IO.File]::ReadAllText(' + "'" + escapeForPowerShell(scriptPath) +
|
|
170
|
+
"', [Text.Encoding]::UTF8)",
|
|
171
|
+
'try {',
|
|
172
|
+
' $null = $app.DoScript($scriptText, ' + SCRIPT_LANGUAGE_JAVASCRIPT + ')',
|
|
173
|
+
'} catch {',
|
|
174
|
+
' [Console]::Error.WriteLine(' + "'" + SCRIPT_ERROR_MARKER +
|
|
175
|
+
" ' + $_.Exception.Message)",
|
|
176
|
+
' exit 4',
|
|
177
|
+
'}',
|
|
178
|
+
'exit 0',
|
|
179
|
+
'',
|
|
180
|
+
].join('\n');
|
|
181
|
+
}
|
|
182
|
+
async send(command, options = {}) {
|
|
183
|
+
const timeoutMs = options.timeoutMs ?? this.defaultTimeoutMs;
|
|
184
|
+
let directory;
|
|
185
|
+
try {
|
|
186
|
+
directory = mkdtempSync(join(tmpdir(), 'idc-spm-'));
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
return transportError('io_error', 'Could not create a temporary directory.', String(error));
|
|
190
|
+
}
|
|
191
|
+
const scriptPath = join(directory, 'command.jsx');
|
|
192
|
+
const resultPath = join(directory, 'result.json');
|
|
193
|
+
const powerShellPath = join(directory, 'command.ps1');
|
|
194
|
+
try {
|
|
195
|
+
writeFileSync(scriptPath, this.buildScript(command, resultPath), 'utf8');
|
|
196
|
+
writeFileSync(powerShellPath, this.buildPowerShell(scriptPath), 'utf8');
|
|
197
|
+
const outcome = await this.runner(this.powershellPath, ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-File', powerShellPath], timeoutMs);
|
|
198
|
+
let raw = null;
|
|
199
|
+
try {
|
|
200
|
+
raw = readFileSync(resultPath, 'utf8');
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
raw = null;
|
|
204
|
+
}
|
|
205
|
+
return interpretOutcome(outcome, raw, command, timeoutMs);
|
|
206
|
+
}
|
|
207
|
+
finally {
|
|
208
|
+
if (!this.keepTempFiles) {
|
|
209
|
+
try {
|
|
210
|
+
rmSync(directory, { recursive: true, force: true });
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
/* Aufraeumen darf keinen Aufruf scheitern lassen. */
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
async close() {
|
|
219
|
+
/* Kein dauerhafter Zustand: nichts zu schliessen. */
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function runProcess(command, args, timeoutMs) {
|
|
223
|
+
return new Promise((resolve) => {
|
|
224
|
+
const child = spawn(command, args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
225
|
+
let stdout = '';
|
|
226
|
+
let stderr = '';
|
|
227
|
+
let timedOut = false;
|
|
228
|
+
let settled = false;
|
|
229
|
+
const timer = setTimeout(() => {
|
|
230
|
+
timedOut = true;
|
|
231
|
+
child.kill('SIGKILL');
|
|
232
|
+
}, timeoutMs);
|
|
233
|
+
child.stdout.on('data', (chunk) => {
|
|
234
|
+
stdout += chunk.toString('utf8');
|
|
235
|
+
});
|
|
236
|
+
child.stderr.on('data', (chunk) => {
|
|
237
|
+
stderr += chunk.toString('utf8');
|
|
238
|
+
});
|
|
239
|
+
const finish = (code, spawnError) => {
|
|
240
|
+
if (settled)
|
|
241
|
+
return;
|
|
242
|
+
settled = true;
|
|
243
|
+
clearTimeout(timer);
|
|
244
|
+
resolve({ code, stdout, stderr, timedOut, spawnError });
|
|
245
|
+
};
|
|
246
|
+
child.on('error', (error) => finish(null, error));
|
|
247
|
+
child.on('close', (code) => finish(code, null));
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
//# sourceMappingURL=com.js.map
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport fuer den Mac: osascript -> AppleScript -> InDesign do script.
|
|
3
|
+
*
|
|
4
|
+
* Nur diese Datei weiss, dass hier ein Mac steht. Der Interpreter bleibt frei
|
|
5
|
+
* von Plattformannahmen.
|
|
6
|
+
*
|
|
7
|
+
* Ablauf je Aufruf:
|
|
8
|
+
* 1. Interpreter und Handler zu einem Skript zusammensetzen, den Befehl als
|
|
9
|
+
* ASCII-JSON hineinschreiben und in eine temporaere .jsx-Datei legen.
|
|
10
|
+
* 2. AppleScript-Datei danebenlegen, die InDesign dieses Skript ausfuehren
|
|
11
|
+
* laesst.
|
|
12
|
+
* 3. Das Skript schreibt sein Ergebnis in eine Datei; der Server liest sie.
|
|
13
|
+
* Umweg ueber die Datei, weil die Rueckgabe ueber AppleScript in Laenge
|
|
14
|
+
* und Zeichensatz unzuverlaessig ist.
|
|
15
|
+
*
|
|
16
|
+
* Der kleine Rahmen in Schritt 1 benutzt das ExtendScript-Objekt File. Das ist
|
|
17
|
+
* bewusst hier und nicht im Interpreter: Dateizugriff ist Transportsache.
|
|
18
|
+
*/
|
|
19
|
+
import { spawn } from 'node:child_process';
|
|
20
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
21
|
+
import { tmpdir } from 'node:os';
|
|
22
|
+
import { join } from 'node:path';
|
|
23
|
+
import { buildScriptBundle, toAsciiJson } from './script-bundle.js';
|
|
24
|
+
import { transportError, } from './transport.js';
|
|
25
|
+
const APP_NOT_AVAILABLE_HINTS = [
|
|
26
|
+
'-1728',
|
|
27
|
+
'-600',
|
|
28
|
+
'-609',
|
|
29
|
+
'-10814',
|
|
30
|
+
"isn't running",
|
|
31
|
+
'is not running',
|
|
32
|
+
'Application isn',
|
|
33
|
+
];
|
|
34
|
+
function escapeForJsString(text) {
|
|
35
|
+
return text.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
36
|
+
}
|
|
37
|
+
export class OsascriptTransport {
|
|
38
|
+
name = 'osascript';
|
|
39
|
+
appBundleId;
|
|
40
|
+
defaultTimeoutMs;
|
|
41
|
+
keepTempFiles;
|
|
42
|
+
constructor(options = {}) {
|
|
43
|
+
this.appBundleId = options.appBundleId ?? 'com.adobe.InDesign';
|
|
44
|
+
this.defaultTimeoutMs = options.defaultTimeoutMs ?? 120_000;
|
|
45
|
+
this.keepTempFiles = options.keepTempFiles ?? false;
|
|
46
|
+
}
|
|
47
|
+
/** Das vollstaendige Skript inklusive Rahmen, der das Ergebnis wegschreibt. */
|
|
48
|
+
buildScript(command, resultPath) {
|
|
49
|
+
const bundle = buildScriptBundle();
|
|
50
|
+
const commandLiteral = toAsciiJson(command);
|
|
51
|
+
const target = escapeForJsString(resultPath);
|
|
52
|
+
const frame = [
|
|
53
|
+
'',
|
|
54
|
+
'/* ---- Rahmen des osascript-Transports (ExtendScript, Mac) ---- */',
|
|
55
|
+
'(function () {',
|
|
56
|
+
' var __text = IDC.run(' + commandLiteral + ');',
|
|
57
|
+
' var __file = new File("' + target + '");',
|
|
58
|
+
' __file.encoding = "UTF-8";',
|
|
59
|
+
' __file.lineFeed = "Unix";',
|
|
60
|
+
' __file.open("w");',
|
|
61
|
+
' __file.write(__text);',
|
|
62
|
+
' __file.close();',
|
|
63
|
+
' return "ok";',
|
|
64
|
+
'})();',
|
|
65
|
+
'',
|
|
66
|
+
].join('\n');
|
|
67
|
+
return bundle + '\n' + frame;
|
|
68
|
+
}
|
|
69
|
+
buildAppleScript(scriptPath, timeoutMs) {
|
|
70
|
+
const seconds = Math.max(5, Math.ceil(timeoutMs / 1000) + 5);
|
|
71
|
+
return [
|
|
72
|
+
'with timeout of ' + seconds + ' seconds',
|
|
73
|
+
'\ttell application id "' + this.appBundleId + '"',
|
|
74
|
+
'\t\tdo script (POSIX file "' + escapeForJsString(scriptPath) + '") language javascript',
|
|
75
|
+
'\tend tell',
|
|
76
|
+
'end timeout',
|
|
77
|
+
].join('\n');
|
|
78
|
+
}
|
|
79
|
+
async send(command, options = {}) {
|
|
80
|
+
const timeoutMs = options.timeoutMs ?? this.defaultTimeoutMs;
|
|
81
|
+
let directory;
|
|
82
|
+
try {
|
|
83
|
+
directory = mkdtempSync(join(tmpdir(), 'idc-spm-'));
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
return transportError('io_error', 'Could not create a temporary directory.', String(error));
|
|
87
|
+
}
|
|
88
|
+
const scriptPath = join(directory, 'command.jsx');
|
|
89
|
+
const resultPath = join(directory, 'result.json');
|
|
90
|
+
const appleScriptPath = join(directory, 'command.applescript');
|
|
91
|
+
try {
|
|
92
|
+
writeFileSync(scriptPath, this.buildScript(command, resultPath), 'utf8');
|
|
93
|
+
writeFileSync(appleScriptPath, this.buildAppleScript(scriptPath, timeoutMs), 'utf8');
|
|
94
|
+
const outcome = await runProcess('osascript', [appleScriptPath], timeoutMs);
|
|
95
|
+
if (outcome.spawnError !== null) {
|
|
96
|
+
return transportError('app_not_available', 'Could not run osascript – this transport needs macOS: ' + outcome.spawnError.message, null);
|
|
97
|
+
}
|
|
98
|
+
if (outcome.timedOut) {
|
|
99
|
+
return transportError('timeout', 'InDesign did not answer within ' + timeoutMs +
|
|
100
|
+
' ms. The script may still be running inside InDesign – check the document before repeating the call.', { command: command.cmd, timeout_ms: timeoutMs });
|
|
101
|
+
}
|
|
102
|
+
let raw = null;
|
|
103
|
+
try {
|
|
104
|
+
raw = readFileSync(resultPath, 'utf8');
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
raw = null;
|
|
108
|
+
}
|
|
109
|
+
if (raw === null || raw.length === 0) {
|
|
110
|
+
const stderr = outcome.stderr.trim();
|
|
111
|
+
const kind = APP_NOT_AVAILABLE_HINTS.some((hint) => stderr.includes(hint))
|
|
112
|
+
? 'app_not_available'
|
|
113
|
+
: 'script_error';
|
|
114
|
+
return transportError(kind, stderr.length > 0
|
|
115
|
+
? 'InDesign did not return a result: ' + stderr
|
|
116
|
+
: 'InDesign did not return a result and reported nothing.', { exit_code: outcome.code, stdout: outcome.stdout.trim() });
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
const parsed = JSON.parse(raw);
|
|
120
|
+
if (!Array.isArray(parsed.warnings))
|
|
121
|
+
parsed.warnings = [];
|
|
122
|
+
return parsed;
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
return transportError('script_error', 'The result of the script is not valid JSON: ' + String(error), { raw: raw.slice(0, 2000) });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
if (!this.keepTempFiles) {
|
|
130
|
+
try {
|
|
131
|
+
rmSync(directory, { recursive: true, force: true });
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
/* Aufraeumen darf keinen Aufruf scheitern lassen. */
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async close() {
|
|
140
|
+
/* Kein dauerhafter Zustand: nichts zu schliessen. */
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function runProcess(command, args, timeoutMs) {
|
|
144
|
+
return new Promise((resolve) => {
|
|
145
|
+
const child = spawn(command, args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
146
|
+
let stdout = '';
|
|
147
|
+
let stderr = '';
|
|
148
|
+
let timedOut = false;
|
|
149
|
+
let settled = false;
|
|
150
|
+
const timer = setTimeout(() => {
|
|
151
|
+
timedOut = true;
|
|
152
|
+
child.kill('SIGKILL');
|
|
153
|
+
}, timeoutMs);
|
|
154
|
+
child.stdout.on('data', (chunk) => {
|
|
155
|
+
stdout += chunk.toString('utf8');
|
|
156
|
+
});
|
|
157
|
+
child.stderr.on('data', (chunk) => {
|
|
158
|
+
stderr += chunk.toString('utf8');
|
|
159
|
+
});
|
|
160
|
+
const finish = (code, spawnError) => {
|
|
161
|
+
if (settled)
|
|
162
|
+
return;
|
|
163
|
+
settled = true;
|
|
164
|
+
clearTimeout(timer);
|
|
165
|
+
resolve({ code, stdout, stderr, timedOut, spawnError });
|
|
166
|
+
};
|
|
167
|
+
child.on('error', (error) => finish(null, error));
|
|
168
|
+
child.on('close', (code) => finish(code, null));
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=osascript.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export class SerialTransport {
|
|
2
|
+
inner;
|
|
3
|
+
chain = Promise.resolve();
|
|
4
|
+
constructor(inner) {
|
|
5
|
+
this.inner = inner;
|
|
6
|
+
}
|
|
7
|
+
get name() {
|
|
8
|
+
return this.inner.name;
|
|
9
|
+
}
|
|
10
|
+
send(command, options) {
|
|
11
|
+
const run = this.chain.then(() => this.inner.send(command, options), () => this.inner.send(command, options));
|
|
12
|
+
this.chain = run.then(() => undefined, () => undefined);
|
|
13
|
+
return run;
|
|
14
|
+
}
|
|
15
|
+
close() {
|
|
16
|
+
return this.inner.close();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=queue.js.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Setzt das Skript zusammen, das in InDesign laeuft: Interpreter-Kern plus
|
|
3
|
+
* alle Handler aus indesign/commands. Der Interpreter darf kein #include
|
|
4
|
+
* benutzen (ExtendScript-eigen, in UXP nicht vorhanden), deshalb haengt der
|
|
5
|
+
* Server die Dateien aneinander.
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs';
|
|
8
|
+
import { dirname, join } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
let cached = null;
|
|
11
|
+
/** Dateiliste mit Aenderungszeiten, mit der das Buendel gebaut wurde. */
|
|
12
|
+
let cachedSignature = '';
|
|
13
|
+
/** Handler-Dateien alphabetisch, wie sie ins Buendel kommen. */
|
|
14
|
+
function commandFiles(root) {
|
|
15
|
+
return readdirSync(join(root, 'commands'))
|
|
16
|
+
.filter((name) => name.endsWith('.js'))
|
|
17
|
+
.sort();
|
|
18
|
+
}
|
|
19
|
+
/*
|
|
20
|
+
* Signatur aus Pfaden und Aenderungszeiten. Aendert sich eine Handler-Datei
|
|
21
|
+
* (Bausitzung mit laufendem Server), wird das Buendel beim naechsten Aufruf
|
|
22
|
+
* neu gelesen – ohne Neustart von Claude Desktop. Ein Stat je Datei und
|
|
23
|
+
* Aufruf ist gegen die Laufzeit eines InDesign-Skripts vernachlaessigbar.
|
|
24
|
+
*/
|
|
25
|
+
export function bundleSignature(root = findScriptRoot()) {
|
|
26
|
+
return signature(root, commandFiles(root));
|
|
27
|
+
}
|
|
28
|
+
function signature(root, files) {
|
|
29
|
+
const parts = [root + ':' + statSync(join(root, 'interpreter.js')).mtimeMs];
|
|
30
|
+
for (const name of files) {
|
|
31
|
+
parts.push(name + ':' + statSync(join(root, 'commands', name)).mtimeMs);
|
|
32
|
+
}
|
|
33
|
+
return parts.join('|');
|
|
34
|
+
}
|
|
35
|
+
/** Sucht aufwaerts nach dem Repo-/Paketwurzelordner mit indesign/interpreter.js. */
|
|
36
|
+
export function findScriptRoot(startDir) {
|
|
37
|
+
let dir = startDir ?? dirname(fileURLToPath(import.meta.url));
|
|
38
|
+
for (let step = 0; step < 12; step += 1) {
|
|
39
|
+
if (existsSync(join(dir, 'indesign', 'interpreter.js'))) {
|
|
40
|
+
return join(dir, 'indesign');
|
|
41
|
+
}
|
|
42
|
+
const parent = dirname(dir);
|
|
43
|
+
if (parent === dir)
|
|
44
|
+
break;
|
|
45
|
+
dir = parent;
|
|
46
|
+
}
|
|
47
|
+
throw new Error('indesign/interpreter.js not found – package layout is broken.');
|
|
48
|
+
}
|
|
49
|
+
/** Interpreter und Handler in fester Reihenfolge, Dateinamen alphabetisch. */
|
|
50
|
+
export function buildScriptBundle(force = false) {
|
|
51
|
+
const root = findScriptRoot();
|
|
52
|
+
const files = commandFiles(root);
|
|
53
|
+
let current = '';
|
|
54
|
+
try {
|
|
55
|
+
current = signature(root, files);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
current = '';
|
|
59
|
+
}
|
|
60
|
+
if (cached !== null && !force && current !== '' && current === cachedSignature)
|
|
61
|
+
return cached;
|
|
62
|
+
const parts = [];
|
|
63
|
+
parts.push('/* layoutbridge-spm – zusammengesetztes Skript */');
|
|
64
|
+
parts.push(readFileSync(join(root, 'interpreter.js'), 'utf8'));
|
|
65
|
+
const commandDir = join(root, 'commands');
|
|
66
|
+
for (const name of files) {
|
|
67
|
+
parts.push('\n/* ---- commands/' + name + ' ---- */\n');
|
|
68
|
+
parts.push(readFileSync(join(commandDir, name), 'utf8'));
|
|
69
|
+
}
|
|
70
|
+
cached = parts.join('\n');
|
|
71
|
+
cachedSignature = current;
|
|
72
|
+
return cached;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* JSON, in dem jedes Zeichen ausserhalb von ASCII als \uXXXX steht. So ist das
|
|
76
|
+
* erzeugte Skript reines ASCII und die Zeichensatzfrage stellt sich nicht.
|
|
77
|
+
*/
|
|
78
|
+
export function toAsciiJson(value) {
|
|
79
|
+
const json = JSON.stringify(value ?? null);
|
|
80
|
+
let out = '';
|
|
81
|
+
for (let i = 0; i < json.length; i += 1) {
|
|
82
|
+
const code = json.charCodeAt(i);
|
|
83
|
+
if (code < 0x20 || code > 0x7e) {
|
|
84
|
+
out += '\\u' + code.toString(16).padStart(4, '0');
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
out += json.charAt(i);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=script-bundle.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transportschnittstelle.
|
|
3
|
+
*
|
|
4
|
+
* Der Transport ist die einzige Stelle mit Plattformwissen. Alles darueber
|
|
5
|
+
* kennt nur diese Schnittstelle: einen Befehl hinschicken, ein Ergebnis
|
|
6
|
+
* zurueckbekommen.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Fehlerarten nach Spezifikation, allgemeine Anforderung 5; dazu "license"
|
|
10
|
+
* fuer die Lizenzschranke (Auftrag A-6, E-60, Spezifikation S11) – sie
|
|
11
|
+
* greift vor dem Transport, InDesign ist nicht beteiligt.
|
|
12
|
+
*/
|
|
13
|
+
export const ERROR_KINDS = [
|
|
14
|
+
'app_not_available',
|
|
15
|
+
'invalid_argument',
|
|
16
|
+
'not_found',
|
|
17
|
+
'invalid_state',
|
|
18
|
+
'io_error',
|
|
19
|
+
'timeout',
|
|
20
|
+
'script_error',
|
|
21
|
+
'license',
|
|
22
|
+
];
|
|
23
|
+
/** Baut einen Fehlerumschlag, ohne dass InDesign beteiligt war. */
|
|
24
|
+
export function transportError(kind, message, details = null) {
|
|
25
|
+
return { ok: false, warnings: [], error: { kind, message, details } };
|
|
26
|
+
}
|
|
27
|
+
export function isErrorKind(value) {
|
|
28
|
+
return typeof value === 'string' && ERROR_KINDS.includes(value);
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=transport.js.map
|