@dawnlit/outlook-bridge 1.1.1 → 2.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.
Files changed (139) hide show
  1. package/README.md +150 -183
  2. package/dist/backend.d.ts +151 -0
  3. package/dist/backend.js +3 -0
  4. package/dist/backend.js.map +1 -0
  5. package/dist/bridge.d.ts +56 -0
  6. package/dist/bridge.js +507 -0
  7. package/dist/bridge.js.map +1 -0
  8. package/dist/errors.d.ts +81 -43
  9. package/dist/errors.js +98 -52
  10. package/dist/errors.js.map +1 -1
  11. package/dist/index.d.ts +10 -10
  12. package/dist/index.js +25 -52
  13. package/dist/index.js.map +1 -1
  14. package/dist/mac/accounts.d.ts +7 -2
  15. package/dist/mac/accounts.js +23 -6
  16. package/dist/mac/accounts.js.map +1 -1
  17. package/dist/mac/attachments.d.ts +5 -25
  18. package/dist/mac/attachments.js +40 -99
  19. package/dist/mac/attachments.js.map +1 -1
  20. package/dist/mac/bounces.d.ts +12 -27
  21. package/dist/mac/bounces.js +95 -112
  22. package/dist/mac/bounces.js.map +1 -1
  23. package/dist/mac/cleanup.d.ts +11 -25
  24. package/dist/mac/cleanup.js +50 -76
  25. package/dist/mac/cleanup.js.map +1 -1
  26. package/dist/mac/drafts.d.ts +13 -34
  27. package/dist/mac/drafts.js +95 -169
  28. package/dist/mac/drafts.js.map +1 -1
  29. package/dist/mac/folders.d.ts +10 -21
  30. package/dist/mac/folders.js +33 -47
  31. package/dist/mac/folders.js.map +1 -1
  32. package/dist/mac/index.d.ts +2 -22
  33. package/dist/mac/index.js +19 -81
  34. package/dist/mac/index.js.map +1 -1
  35. package/dist/mac/profile.d.ts +13 -0
  36. package/dist/mac/profile.js +149 -0
  37. package/dist/mac/profile.js.map +1 -0
  38. package/dist/mac/read.d.ts +18 -38
  39. package/dist/mac/read.js +154 -210
  40. package/dist/mac/read.js.map +1 -1
  41. package/dist/mac/run.d.ts +35 -39
  42. package/dist/mac/run.js +97 -102
  43. package/dist/mac/run.js.map +1 -1
  44. package/dist/mac/scripts.d.ts +78 -86
  45. package/dist/mac/scripts.js +125 -115
  46. package/dist/mac/scripts.js.map +1 -1
  47. package/dist/mac/send.d.ts +10 -20
  48. package/dist/mac/send.js +64 -86
  49. package/dist/mac/send.js.map +1 -1
  50. package/dist/mac/signatures.d.ts +2 -2
  51. package/dist/mac/signatures.js +9 -11
  52. package/dist/mac/signatures.js.map +1 -1
  53. package/dist/mac/templates.d.ts +14 -26
  54. package/dist/mac/templates.js +75 -113
  55. package/dist/mac/templates.js.map +1 -1
  56. package/dist/mail.d.ts +77 -45
  57. package/dist/mail.js +166 -73
  58. package/dist/mail.js.map +1 -1
  59. package/dist/runtime.d.ts +76 -43
  60. package/dist/runtime.js +103 -48
  61. package/dist/runtime.js.map +1 -1
  62. package/dist/shared/args.d.ts +48 -0
  63. package/dist/shared/args.js +216 -0
  64. package/dist/shared/args.js.map +1 -0
  65. package/dist/shared/attachmentMatch.d.ts +28 -16
  66. package/dist/shared/attachmentMatch.js +60 -22
  67. package/dist/shared/attachmentMatch.js.map +1 -1
  68. package/dist/shared/bounceReport.d.ts +11 -0
  69. package/dist/shared/bounceReport.js +45 -0
  70. package/dist/shared/bounceReport.js.map +1 -0
  71. package/dist/shared/bounceRules.d.ts +38 -10
  72. package/dist/shared/bounceRules.js +50 -32
  73. package/dist/shared/bounceRules.js.map +1 -1
  74. package/dist/shared/exec.d.ts +21 -0
  75. package/dist/shared/exec.js +47 -0
  76. package/dist/shared/exec.js.map +1 -0
  77. package/dist/shared/json.d.ts +12 -6
  78. package/dist/shared/json.js +29 -23
  79. package/dist/shared/json.js.map +1 -1
  80. package/dist/shared/locate.d.ts +39 -0
  81. package/dist/shared/locate.js +129 -0
  82. package/dist/shared/locate.js.map +1 -0
  83. package/dist/shared/replyBody.d.ts +42 -32
  84. package/dist/shared/replyBody.js +84 -71
  85. package/dist/shared/replyBody.js.map +1 -1
  86. package/dist/templateBody.d.ts +63 -0
  87. package/dist/{outlookTemplateSections.js → templateBody.js} +72 -72
  88. package/dist/templateBody.js.map +1 -0
  89. package/dist/types.d.ts +360 -167
  90. package/dist/types.js +8 -9
  91. package/dist/types.js.map +1 -1
  92. package/dist/windows/accounts.d.ts +7 -12
  93. package/dist/windows/accounts.js +19 -34
  94. package/dist/windows/accounts.js.map +1 -1
  95. package/dist/windows/attachments.d.ts +9 -24
  96. package/dist/windows/attachments.js +94 -119
  97. package/dist/windows/attachments.js.map +1 -1
  98. package/dist/windows/bounces.d.ts +13 -26
  99. package/dist/windows/bounces.js +168 -200
  100. package/dist/windows/bounces.js.map +1 -1
  101. package/dist/windows/cleanup.d.ts +19 -23
  102. package/dist/windows/cleanup.js +92 -111
  103. package/dist/windows/cleanup.js.map +1 -1
  104. package/dist/windows/drafts.d.ts +16 -35
  105. package/dist/windows/drafts.js +167 -253
  106. package/dist/windows/drafts.js.map +1 -1
  107. package/dist/windows/folders.d.ts +8 -13
  108. package/dist/windows/folders.js +44 -52
  109. package/dist/windows/folders.js.map +1 -1
  110. package/dist/windows/index.d.ts +2 -27
  111. package/dist/windows/index.js +11 -66
  112. package/dist/windows/index.js.map +1 -1
  113. package/dist/windows/read.d.ts +24 -60
  114. package/dist/windows/read.js +261 -306
  115. package/dist/windows/read.js.map +1 -1
  116. package/dist/windows/run.d.ts +39 -39
  117. package/dist/windows/run.js +151 -111
  118. package/dist/windows/run.js.map +1 -1
  119. package/dist/windows/scripts.d.ts +65 -55
  120. package/dist/windows/scripts.js +109 -85
  121. package/dist/windows/scripts.js.map +1 -1
  122. package/dist/windows/send.d.ts +6 -11
  123. package/dist/windows/send.js +68 -88
  124. package/dist/windows/send.js.map +1 -1
  125. package/dist/windows/signatures.d.ts +4 -10
  126. package/dist/windows/signatures.js +30 -37
  127. package/dist/windows/signatures.js.map +1 -1
  128. package/dist/windows/templates.d.ts +12 -18
  129. package/dist/windows/templates.js +122 -149
  130. package/dist/windows/templates.js.map +1 -1
  131. package/package.json +4 -3
  132. package/dist/OutlookService.d.ts +0 -43
  133. package/dist/OutlookService.js +0 -137
  134. package/dist/OutlookService.js.map +0 -1
  135. package/dist/outlookTemplateSections.d.ts +0 -62
  136. package/dist/outlookTemplateSections.js.map +0 -1
  137. package/dist/shared/capabilities.d.ts +0 -3
  138. package/dist/shared/capabilities.js +0 -9
  139. package/dist/shared/capabilities.js.map +0 -1
package/dist/mac/run.d.ts CHANGED
@@ -1,20 +1,27 @@
1
+ import { type RunBudget } from '../runtime';
1
2
  /**
2
- * Run an AppleScript via a temp file (avoids arg-length and quoting limits).
3
+ * Run a script from a temp file (no argument-length or quoting limits) and
4
+ * return what it printed, minus the newline osascript appends.
3
5
  *
4
- * `AS_HANDLERS` is prepended here rather than by each caller — the Windows runner
5
- * already owns its UTF-8 prelude the same way. It matters more on this side: a
6
- * script missing a handler fails at COMPILE time for the WHOLE script, with a
7
- * message pointing nowhere near the omission, so "remember to paste the handlers"
8
- * was a footgun every new script had to survive.
9
- *
10
- * `timeout` overrides the configured default for this one call; see `configure()`
11
- * for that and for the stdout cap.
6
+ * `AS_HANDLERS` is prepended here rather than by each caller: a script missing
7
+ * a handler fails to compile as a WHOLE, with an error pointing nowhere near the
8
+ * omission, so it can't be left to each script to remember.
9
+ */
10
+ export declare function runOsaScript(source: string, budget: RunBudget): Promise<string>;
11
+ /**
12
+ * Escape text for the inside of an AppleScript double-quoted literal.
13
+ * AppleScript strings never interpolate, so escaping the quote, the backslash
14
+ * and line breaks is the whole job.
12
15
  */
13
- export declare function runOsaScript(source: string, timeout?: number): Promise<string>;
14
- /** Escape a string for embedding inside an AppleScript double-quoted literal. */
15
- export declare function asEscape(s: string): string;
16
+ export declare function asEscape(text: string): string;
17
+ /** Text as an AppleScript string literal. */
18
+ export declare function asString(text: string): string;
16
19
  /** AppleScript's boolean literals. */
17
20
  export declare function asBool(value: boolean): string;
21
+ /** A whole number for a script; anything else is refused before it becomes code. */
22
+ export declare function asInt(value: number): string;
23
+ /** Message ids (already validated as integers) as an AppleScript list literal. */
24
+ export declare function asIdList(ids: readonly string[]): string;
18
25
  export declare const FIELD_SEP = "\u001F";
19
26
  export declare const RECORD_SEP = "\u001E";
20
27
  export declare const LIST_SEP = "\u001D";
@@ -23,42 +30,31 @@ export declare const AS_FIELD_SEP = "(character id 31)";
23
30
  export declare const AS_RECORD_SEP = "(character id 30)";
24
31
  export declare const AS_LIST_SEP = "(character id 29)";
25
32
  /**
26
- * Emit one record: the AppleScript expressions in `fields`, separated and
27
- * terminated so `splitRecords`/`splitFields` can take them apart again.
28
- *
29
- * Every field goes through `sanitize`, which is what makes the framing safe
30
- * rather than merely unlikely to break. A field holding a LIST must be built
31
- * with `my sanitizeList(...)`, which cleans the elements instead — `sanitize`
32
- * deliberately leaves the list separator alone.
33
+ * One record: the AppleScript expressions in `fields`, separated and terminated
34
+ * so `splitRecords`/`splitFields` can take them apart again. A field holding a
35
+ * LIST must be built with `my sanitizeList(...)`, which cleans the elements —
36
+ * `sanitize` deliberately leaves the list separator alone.
33
37
  */
34
38
  export declare function asRow(fields: readonly string[]): string;
35
39
  /**
36
- * The AppleScript handlers every script here relies on. Called with `my` from
37
- * inside a `tell` block.
40
+ * The handlers every script relies on, called with `my` from inside a `tell`.
38
41
  *
39
- * `isoDate` produces the zero-padded 'yyyy-MM-dd HH:mm' both platforms' readers
40
- * return; `sanitize` strips the framing characters; `joinList` and `replaceText`
41
- * are the text-item-delimiter dance nobody should write twice.
42
+ * `isoDate` produces the zero-padded 'yyyy-MM-dd HH:mm' both platforms return;
43
+ * `sanitize` strips the framing characters; `joinList` and `replaceText` are the
44
+ * text-item-delimiter dance nobody should write twice.
42
45
  */
43
- export declare const AS_HANDLERS = "\non pad2(n)\n set s to (n as integer) as string\n if (length of s) < 2 then set s to \"0\" & s\n return s\nend pad2\n\non isoDate(d)\n if d is missing value then return \"\"\n return (year of d as string) & \"-\" & pad2(month of d as integer) & \"-\" & pad2(day of d) & \" \" & pad2(hours of d) & \":\" & pad2(minutes of d)\nend isoDate\n\non joinList(lst, sep)\n set saved to AppleScript's text item delimiters\n set AppleScript's text item delimiters to sep\n set out to lst as string\n set AppleScript's text item delimiters to saved\n return out\nend joinList\n\non replaceText(t, findWhat, replaceWith)\n set saved to AppleScript's text item delimiters\n set AppleScript's text item delimiters to findWhat\n set parts to text items of t\n set AppleScript's text item delimiters to replaceWith\n set out to parts as string\n set AppleScript's text item delimiters to saved\n return out\nend replaceText\n\n-- Strips the FIELD and RECORD separators only. The LIST separator is left\n-- alone on purpose: a field may legitimately BE a list, and stripping 29 here\n-- collapsed every such field into one run-on string.\non sanitize(v)\n if v is missing value then return \"\"\n set t to v as string\n repeat with codePoint in {30, 31}\n set marker to (character id (codePoint as integer))\n if t contains marker then set t to my replaceText(t, marker, \" \")\n end repeat\n return t\nend sanitize\n\n-- A list field: each element loses all three separators, then they are joined\n-- with the LIST separator that splitList() takes them apart on.\non sanitizeList(lst)\n set cleaned to {}\n repeat with item_ in lst\n set t to my sanitize(item_)\n repeat with codePoint in {29}\n set marker to (character id (codePoint as integer))\n if t contains marker then set t to my replaceText(t, marker, \" \")\n end repeat\n set end of cleaned to t\n end repeat\n return my joinList(cleaned, (character id 29))\nend sanitizeList\n\non insertAboveQuoted(c, ins)\n if c is missing value then return ins\n set c to c as string\n if (length of c) is 0 then return ins\n -- Outlook builds a reply as a document whose own empty paragraph sits inside\n -- WordSection1; inserting straight after that tag puts the new text where the\n -- user's cursor would have been. Anything else falls back to just inside <body>.\n -- 'at' is a reserved AppleScript parameter name, so the cut point cannot be\n -- called that however naturally it reads.\n set cutPoint to my tagEnd(c, \"WordSection1>\")\n if cutPoint is 0 then set cutPoint to my tagEnd(c, \"<body\")\n if cutPoint is 0 then return ins & c\n if cutPoint is (length of c) then return c & ins\n return (text 1 thru cutPoint of c) & ins & (text (cutPoint + 1) thru -1 of c)\nend insertAboveQuoted\n\n-- 'rest' is an AppleScript term (rest of list), so the remainder cannot be\n-- called that: assigning to it compiles and then fails at run time.\non tagEnd(c, marker)\n set ix to offset of marker in c\n if ix is 0 then return 0\n if marker ends with \">\" then return ix + (length of marker) - 1\n set tailText to text ix thru -1 of c\n set gt to offset of \">\" in tailText\n if gt is 0 then return 0\n return ix + gt - 1\nend tagEnd\n";
44
- /** Split a script's output into records, dropping the trailing empty one. */
46
+ export declare const AS_HANDLERS = "\non pad2(n)\n set s to (n as integer) as string\n if (length of s) < 2 then set s to \"0\" & s\n return s\nend pad2\n\non isoDate(d)\n if d is missing value then return \"\"\n return (year of d as string) & \"-\" & pad2(month of d as integer) & \"-\" & pad2(day of d) & \" \" & pad2(hours of d) & \":\" & pad2(minutes of d)\nend isoDate\n\non joinList(lst, sep)\n set saved to AppleScript's text item delimiters\n set AppleScript's text item delimiters to sep\n set out to lst as string\n set AppleScript's text item delimiters to saved\n return out\nend joinList\n\non replaceText(t, findWhat, replaceWith)\n set saved to AppleScript's text item delimiters\n set AppleScript's text item delimiters to findWhat\n set parts to text items of t\n set AppleScript's text item delimiters to replaceWith\n set out to parts as string\n set AppleScript's text item delimiters to saved\n return out\nend replaceText\n\n-- Strips the FIELD and RECORD separators only. The LIST separator is left\n-- alone on purpose: a field may legitimately BE a list.\non sanitize(v)\n if v is missing value then return \"\"\n set t to v as string\n repeat with codePoint in {30, 31}\n set marker to (character id (codePoint as integer))\n if t contains marker then set t to my replaceText(t, marker, \" \")\n end repeat\n return t\nend sanitize\n\n-- A list field: each element loses all three separators, then they are joined\n-- with the LIST separator that splitList() takes them apart on.\non sanitizeList(lst)\n set cleaned to {}\n repeat with item_ in lst\n set t to my sanitize(item_)\n set marker to (character id 29)\n if t contains marker then set t to my replaceText(t, marker, \" \")\n set end of cleaned to t\n end repeat\n return my joinList(cleaned, (character id 29))\nend sanitizeList\n\non insertAboveQuoted(c, ins)\n if c is missing value then return ins\n set c to c as string\n if (length of c) is 0 then return ins\n -- A reply's own empty paragraph sits inside WordSection1; inserting straight\n -- after that tag puts the new text where the cursor would be. Anything else\n -- falls back to just inside <body>. ('at' is a reserved parameter name, so\n -- the cut point can't be called that.)\n set cutPoint to my tagEnd(c, \"WordSection1>\")\n if cutPoint is 0 then set cutPoint to my tagEnd(c, \"<body\")\n if cutPoint is 0 then return ins & c\n if cutPoint is (length of c) then return c & ins\n return (text 1 thru cutPoint of c) & ins & (text (cutPoint + 1) thru -1 of c)\nend insertAboveQuoted\n\n-- 'rest' is an AppleScript term, so the remainder can't be called that:\n-- assigning to it compiles and then fails at run time.\non tagEnd(c, marker)\n set ix to offset of marker in c\n if ix is 0 then return 0\n if marker ends with \">\" then return ix + (length of marker) - 1\n set tailText to text ix thru -1 of c\n set gt to offset of \">\" in tailText\n if gt is 0 then return 0\n return ix + gt - 1\nend tagEnd\n";
47
+ /** A script's output as records, without the trailing empty one. */
45
48
  export declare function splitRecords(raw: string): string[];
46
- /** Split one record into its fields. */
49
+ /** One record's fields. */
47
50
  export declare function splitFields(record: string): string[];
48
- /** Split one field that carries a list. */
51
+ /** A field that carries a list. */
49
52
  export declare function splitList(value: string | undefined): string[];
50
- /** A field by position — a missing field reads as ''. */
53
+ /** A field by position; a missing field reads as ''. */
51
54
  export declare function field(fields: readonly string[], index: number): string;
52
- /**
53
- * The fields of a script's ONE summary record — the shape every mutation here
54
- * returns (counts first, then any per-item failures as further records).
55
- */
55
+ /** The fields of a script's first record — the summary a mutation returns ahead of any per-item rows. */
56
56
  export declare function summaryFields(raw: string): string[];
57
- /** A numeric field, defaulting to `fallback` when absent or unparsable. */
57
+ /** A numeric field, `fallback` when absent or unparsable. */
58
58
  export declare function intField(fields: readonly string[], index: number, fallback?: number): number;
59
- /**
60
- * A boolean field. AppleScript renders booleans as the bare words `true`/`false`,
61
- * so this is the counterpart of `shared/json.ts`'s coercions for the framed
62
- * output — the Windows side gets those free from ConvertFrom-Json.
63
- */
59
+ /** A boolean field; AppleScript renders booleans as the bare words true/false. */
64
60
  export declare function boolField(fields: readonly string[], index: number): boolean;
package/dist/mac/run.js CHANGED
@@ -6,7 +6,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.AS_HANDLERS = exports.AS_LIST_SEP = exports.AS_RECORD_SEP = exports.AS_FIELD_SEP = exports.LIST_SEP = exports.RECORD_SEP = exports.FIELD_SEP = void 0;
7
7
  exports.runOsaScript = runOsaScript;
8
8
  exports.asEscape = asEscape;
9
+ exports.asString = asString;
9
10
  exports.asBool = asBool;
11
+ exports.asInt = asInt;
12
+ exports.asIdList = asIdList;
10
13
  exports.asRow = asRow;
11
14
  exports.splitRecords = splitRecords;
12
15
  exports.splitFields = splitFields;
@@ -17,100 +20,104 @@ exports.intField = intField;
17
20
  exports.boolField = boolField;
18
21
  // Getting a generated AppleScript to osascript and its output back.
19
22
  //
20
- // The macOS counterpart of windows/run.ts: the run itself, the escaping rules,
21
- // and the record framing every reader here shares.
22
- const child_process_1 = require("child_process");
23
+ // The macOS counterpart of windows/run.ts: running a script, the escaping
24
+ // rules, and the record framing every reader here shares.
23
25
  const fs_1 = __importDefault(require("fs"));
24
26
  const runtime_1 = require("../runtime");
27
+ const exec_1 = require("../shared/exec");
25
28
  const errors_1 = require("../errors");
26
29
  const replyBody_1 = require("../shared/replyBody");
27
30
  const RUNNER = 'osascript';
28
31
  /**
29
- * Run an AppleScript via a temp file (avoids arg-length and quoting limits).
30
- *
31
- * `AS_HANDLERS` is prepended here rather than by each caller — the Windows runner
32
- * already owns its UTF-8 prelude the same way. It matters more on this side: a
33
- * script missing a handler fails at COMPILE time for the WHOLE script, with a
34
- * message pointing nowhere near the omission, so "remember to paste the handlers"
35
- * was a footgun every new script had to survive.
32
+ * osascript's report of a script error: "<file>:<start>:<end>: execution error:
33
+ * <message> (<number>)". Only the message is worth showing — these reach people
34
+ * in per-item failure lists — and -2700 is the number AppleScript gives every
35
+ * `error "…"` a script raises itself.
36
+ */
37
+ function parseFailure(stderr) {
38
+ const message = stderr
39
+ .replace(/^(?:.*?:)?\d+:\d+:\s*execution error:\s*/m, '')
40
+ .replace(/\s*\(-2700\)\s*$/, '')
41
+ .trim();
42
+ return { message };
43
+ }
44
+ /**
45
+ * Run a script from a temp file (no argument-length or quoting limits) and
46
+ * return what it printed, minus the newline osascript appends.
36
47
  *
37
- * `timeout` overrides the configured default for this one call; see `configure()`
38
- * for that and for the stdout cap.
48
+ * `AS_HANDLERS` is prepended here rather than by each caller: a script missing
49
+ * a handler fails to compile as a WHOLE, with an error pointing nowhere near the
50
+ * omission, so it can't be left to each script to remember.
39
51
  */
40
- function runOsaScript(source, timeout) {
41
- // The full text is what osascript compiled, so it is also what the error's
42
- // line numbers refer to and what the debug hook and ScriptError must carry.
52
+ async function runOsaScript(source, budget) {
53
+ // The full text is what osascript compiles, so it is also what error offsets
54
+ // refer to and what the debug hook and errors carry.
43
55
  const script = exports.AS_HANDLERS + source;
44
- const scriptFile = (0, runtime_1.tempFile)('osa', 'applescript');
45
- fs_1.default.writeFileSync(scriptFile, script, 'utf-8');
46
- const { timeoutMs, maxBufferBytes, signal } = (0, runtime_1.getConfig)();
47
- const effectiveTimeout = timeout ?? timeoutMs;
48
- const startedAt = Date.now();
49
- return new Promise((resolve, reject) => {
50
- (0, child_process_1.execFile)('osascript', [scriptFile], { maxBuffer: maxBufferBytes, timeout: effectiveTimeout, signal }, (error, stdout, stderr) => {
51
- const durationMs = Date.now() - startedAt;
52
- try {
53
- fs_1.default.unlinkSync(scriptFile);
54
- }
55
- catch { /* ignore */
56
- }
57
- if (error) {
58
- // Running from a file, osascript prefixes the script path:
59
- // "/tmp/outlook-bridge-osa-1.applescript:12:34: execution error:
60
- // Microsoft Outlook got an error: … (-1728)". Keep just the human
61
- // part — these strings reach the user in per-email failure lists.
62
- const msg = (stderr || error.message)
63
- .replace(/^(?:.*?:)?\d+:\d+:\s*execution error:\s*/m, '')
64
- .trim();
65
- const failure = (0, errors_1.classifyRunFailure)({
66
- runner: RUNNER,
67
- script,
68
- stderr: msg,
69
- durationMs,
70
- nodeError: error,
71
- timeoutMs: effectiveTimeout,
72
- signal,
73
- });
74
- (0, runtime_1.reportRun)({ runner: RUNNER, script, durationMs, error: failure.message });
75
- reject(failure);
76
- }
77
- else {
78
- (0, runtime_1.reportRun)({ runner: RUNNER, script, durationMs });
79
- resolve(stdout.replace(/\n$/, ''));
80
- }
56
+ const file = (0, runtime_1.tempFile)('osa', 'applescript');
57
+ fs_1.default.writeFileSync(file, script, 'utf8');
58
+ try {
59
+ const { stdout } = await (0, exec_1.runScriptProcess)({
60
+ runner: RUNNER,
61
+ command: 'osascript',
62
+ args: [file],
63
+ script,
64
+ budget,
65
+ parseFailure,
81
66
  });
82
- });
67
+ return stdout.replace(/\n$/, '');
68
+ }
69
+ finally {
70
+ (0, runtime_1.removeTempFile)(file);
71
+ }
83
72
  }
84
- /** Escape a string for embedding inside an AppleScript double-quoted literal. */
85
- function asEscape(s) {
86
- return s
73
+ /**
74
+ * Escape text for the inside of an AppleScript double-quoted literal.
75
+ * AppleScript strings never interpolate, so escaping the quote, the backslash
76
+ * and line breaks is the whole job.
77
+ */
78
+ function asEscape(text) {
79
+ return text
87
80
  .replace(/\\/g, '\\\\')
88
81
  .replace(/"/g, '\\"')
89
82
  .replace(/\r\n|\r|\n/g, '\\n');
90
83
  }
84
+ /** Text as an AppleScript string literal. */
85
+ function asString(text) {
86
+ return `"${asEscape(text)}"`;
87
+ }
91
88
  /** AppleScript's boolean literals. */
92
89
  function asBool(value) {
93
90
  return value ? 'true' : 'false';
94
91
  }
95
- // Control characters as separators: subjects and bodies contain tabs, newlines
96
- // and commas, but never these. Every emitted field also passes through the
97
- // `sanitize` handler below, which is what makes the framing safe rather than
98
- // merely unlikely to break.
99
- exports.FIELD_SEP = '\u001f';
100
- exports.RECORD_SEP = '\u001e';
101
- exports.LIST_SEP = '\u001d';
92
+ /** A whole number for a script; anything else is refused before it becomes code. */
93
+ function asInt(value) {
94
+ if (!Number.isSafeInteger(value)) {
95
+ throw new errors_1.InvalidRequestError(`Expected a whole number, got ${String(value)}.`);
96
+ }
97
+ return String(value);
98
+ }
99
+ /** Message ids (already validated as integers) as an AppleScript list literal. */
100
+ function asIdList(ids) {
101
+ return `{${ids.map(id => asInt(Number(id))).join(', ')}}`;
102
+ }
103
+ // ── Record framing ───────────────────────────────────────────────────────
104
+ //
105
+ // Control characters separate fields, records and list items: subjects and
106
+ // bodies contain tabs, newlines and commas, but never these — and every emitted
107
+ // field passes through the `sanitize` handler, which makes the framing safe
108
+ // rather than merely unlikely to break.
109
+ exports.FIELD_SEP = '';
110
+ exports.RECORD_SEP = '';
111
+ exports.LIST_SEP = '';
102
112
  /** The same three, as AppleScript expressions. */
103
113
  exports.AS_FIELD_SEP = '(character id 31)';
104
114
  exports.AS_RECORD_SEP = '(character id 30)';
105
115
  exports.AS_LIST_SEP = '(character id 29)';
106
116
  /**
107
- * Emit one record: the AppleScript expressions in `fields`, separated and
108
- * terminated so `splitRecords`/`splitFields` can take them apart again.
109
- *
110
- * Every field goes through `sanitize`, which is what makes the framing safe
111
- * rather than merely unlikely to break. A field holding a LIST must be built
112
- * with `my sanitizeList(...)`, which cleans the elements instead — `sanitize`
113
- * deliberately leaves the list separator alone.
117
+ * One record: the AppleScript expressions in `fields`, separated and terminated
118
+ * so `splitRecords`/`splitFields` can take them apart again. A field holding a
119
+ * LIST must be built with `my sanitizeList(...)`, which cleans the elements —
120
+ * `sanitize` deliberately leaves the list separator alone.
114
121
  */
115
122
  function asRow(fields) {
116
123
  return fields
@@ -118,12 +125,11 @@ function asRow(fields) {
118
125
  .join(` & ${exports.AS_FIELD_SEP} & `) + ` & ${exports.AS_RECORD_SEP}`;
119
126
  }
120
127
  /**
121
- * The AppleScript handlers every script here relies on. Called with `my` from
122
- * inside a `tell` block.
128
+ * The handlers every script relies on, called with `my` from inside a `tell`.
123
129
  *
124
- * `isoDate` produces the zero-padded 'yyyy-MM-dd HH:mm' both platforms' readers
125
- * return; `sanitize` strips the framing characters; `joinList` and `replaceText`
126
- * are the text-item-delimiter dance nobody should write twice.
130
+ * `isoDate` produces the zero-padded 'yyyy-MM-dd HH:mm' both platforms return;
131
+ * `sanitize` strips the framing characters; `joinList` and `replaceText` are the
132
+ * text-item-delimiter dance nobody should write twice.
127
133
  */
128
134
  exports.AS_HANDLERS = `
129
135
  on pad2(n)
@@ -156,8 +162,7 @@ on replaceText(t, findWhat, replaceWith)
156
162
  end replaceText
157
163
 
158
164
  -- Strips the FIELD and RECORD separators only. The LIST separator is left
159
- -- alone on purpose: a field may legitimately BE a list, and stripping 29 here
160
- -- collapsed every such field into one run-on string.
165
+ -- alone on purpose: a field may legitimately BE a list.
161
166
  on sanitize(v)
162
167
  if v is missing value then return ""
163
168
  set t to v as string
@@ -174,10 +179,8 @@ on sanitizeList(lst)
174
179
  set cleaned to {}
175
180
  repeat with item_ in lst
176
181
  set t to my sanitize(item_)
177
- repeat with codePoint in {29}
178
- set marker to (character id (codePoint as integer))
179
- if t contains marker then set t to my replaceText(t, marker, " ")
180
- end repeat
182
+ set marker to (character id 29)
183
+ if t contains marker then set t to my replaceText(t, marker, " ")
181
184
  set end of cleaned to t
182
185
  end repeat
183
186
  return my joinList(cleaned, (character id 29))
@@ -187,11 +190,10 @@ on insertAboveQuoted(c, ins)
187
190
  if c is missing value then return ins
188
191
  set c to c as string
189
192
  if (length of c) is 0 then return ins
190
- -- Outlook builds a reply as a document whose own empty paragraph sits inside
191
- -- WordSection1; inserting straight after that tag puts the new text where the
192
- -- user's cursor would have been. Anything else falls back to just inside <body>.
193
- -- 'at' is a reserved AppleScript parameter name, so the cut point cannot be
194
- -- called that however naturally it reads.
193
+ -- A reply's own empty paragraph sits inside WordSection1; inserting straight
194
+ -- after that tag puts the new text where the cursor would be. Anything else
195
+ -- falls back to just inside <body>. ('at' is a reserved parameter name, so
196
+ -- the cut point can't be called that.)
195
197
  set cutPoint to my tagEnd(c, "${replyBody_1.WORD_SECTION_ANCHOR}")
196
198
  if cutPoint is 0 then set cutPoint to my tagEnd(c, "<body")
197
199
  if cutPoint is 0 then return ins & c
@@ -199,8 +201,8 @@ on insertAboveQuoted(c, ins)
199
201
  return (text 1 thru cutPoint of c) & ins & (text (cutPoint + 1) thru -1 of c)
200
202
  end insertAboveQuoted
201
203
 
202
- -- 'rest' is an AppleScript term (rest of list), so the remainder cannot be
203
- -- called that: assigning to it compiles and then fails at run time.
204
+ -- 'rest' is an AppleScript term, so the remainder can't be called that:
205
+ -- assigning to it compiles and then fails at run time.
204
206
  on tagEnd(c, marker)
205
207
  set ix to offset of marker in c
206
208
  if ix is 0 then return 0
@@ -211,39 +213,32 @@ on tagEnd(c, marker)
211
213
  return ix + gt - 1
212
214
  end tagEnd
213
215
  `;
214
- /** Split a script's output into records, dropping the trailing empty one. */
216
+ /** A script's output as records, without the trailing empty one. */
215
217
  function splitRecords(raw) {
216
218
  return raw.split(exports.RECORD_SEP).filter(record => record.trim() !== '');
217
219
  }
218
- /** Split one record into its fields. */
220
+ /** One record's fields. */
219
221
  function splitFields(record) {
220
222
  return record.split(exports.FIELD_SEP);
221
223
  }
222
- /** Split one field that carries a list. */
224
+ /** A field that carries a list. */
223
225
  function splitList(value) {
224
226
  return (value || '').split(exports.LIST_SEP).filter(Boolean);
225
227
  }
226
- /** A field by position — a missing field reads as ''. */
228
+ /** A field by position; a missing field reads as ''. */
227
229
  function field(fields, index) {
228
230
  return fields[index] ?? '';
229
231
  }
230
- /**
231
- * The fields of a script's ONE summary record — the shape every mutation here
232
- * returns (counts first, then any per-item failures as further records).
233
- */
232
+ /** The fields of a script's first record — the summary a mutation returns ahead of any per-item rows. */
234
233
  function summaryFields(raw) {
235
234
  return splitFields(splitRecords(raw)[0] ?? '');
236
235
  }
237
- /** A numeric field, defaulting to `fallback` when absent or unparsable. */
236
+ /** A numeric field, `fallback` when absent or unparsable. */
238
237
  function intField(fields, index, fallback = 0) {
239
238
  const parsed = Number.parseInt(field(fields, index), 10);
240
239
  return Number.isNaN(parsed) ? fallback : parsed;
241
240
  }
242
- /**
243
- * A boolean field. AppleScript renders booleans as the bare words `true`/`false`,
244
- * so this is the counterpart of `shared/json.ts`'s coercions for the framed
245
- * output — the Windows side gets those free from ConvertFrom-Json.
246
- */
241
+ /** A boolean field; AppleScript renders booleans as the bare words true/false. */
247
242
  function boolField(fields, index) {
248
243
  return field(fields, index).trim() === 'true';
249
244
  }
@@ -1 +1 @@
1
- {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/mac/run.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/mac/run.ts"],"names":[],"mappings":"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","sourcesContent":["// Getting a generated AppleScript to osascript and its output back.\n//\n// The macOS counterpart of windows/run.ts: running a script, the escaping\n// rules, and the record framing every reader here shares.\nimport fs from 'fs';\nimport { removeTempFile, type RunBudget, tempFile } from '../runtime';\nimport { runScriptProcess } from '../shared/exec';\nimport { InvalidRequestError } from '../errors';\nimport { WORD_SECTION_ANCHOR } from '../shared/replyBody';\n\nconst RUNNER = 'osascript' as const;\n\n/**\n * osascript's report of a script error: \"<file>:<start>:<end>: execution error:\n * <message> (<number>)\". Only the message is worth showing — these reach people\n * in per-item failure lists — and -2700 is the number AppleScript gives every\n * `error \"…\"` a script raises itself.\n */\nfunction parseFailure(stderr: string): { message: string } {\n const message = stderr\n .replace(/^(?:.*?:)?\\d+:\\d+:\\s*execution error:\\s*/m, '')\n .replace(/\\s*\\(-2700\\)\\s*$/, '')\n .trim();\n return {message};\n}\n\n/**\n * Run a script from a temp file (no argument-length or quoting limits) and\n * return what it printed, minus the newline osascript appends.\n *\n * `AS_HANDLERS` is prepended here rather than by each caller: a script missing\n * a handler fails to compile as a WHOLE, with an error pointing nowhere near the\n * omission, so it can't be left to each script to remember.\n */\nexport async function runOsaScript(source: string, budget: RunBudget): Promise<string> {\n // The full text is what osascript compiles, so it is also what error offsets\n // refer to and what the debug hook and errors carry.\n const script = AS_HANDLERS + source;\n const file = tempFile('osa', 'applescript');\n fs.writeFileSync(file, script, 'utf8');\n try {\n const {stdout} = await runScriptProcess({\n runner: RUNNER,\n command: 'osascript',\n args: [file],\n script,\n budget,\n parseFailure,\n });\n return stdout.replace(/\\n$/, '');\n } finally {\n removeTempFile(file);\n }\n}\n\n/**\n * Escape text for the inside of an AppleScript double-quoted literal.\n * AppleScript strings never interpolate, so escaping the quote, the backslash\n * and line breaks is the whole job.\n */\nexport function asEscape(text: string): string {\n return text\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n .replace(/\\r\\n|\\r|\\n/g, '\\\\n');\n}\n\n/** Text as an AppleScript string literal. */\nexport function asString(text: string): string {\n return `\"${asEscape(text)}\"`;\n}\n\n/** AppleScript's boolean literals. */\nexport function asBool(value: boolean): string {\n return value ? 'true' : 'false';\n}\n\n/** A whole number for a script; anything else is refused before it becomes code. */\nexport function asInt(value: number): string {\n if (!Number.isSafeInteger(value)) {\n throw new InvalidRequestError(`Expected a whole number, got ${String(value)}.`);\n }\n return String(value);\n}\n\n/** Message ids (already validated as integers) as an AppleScript list literal. */\nexport function asIdList(ids: readonly string[]): string {\n return `{${ids.map(id => asInt(Number(id))).join(', ')}}`;\n}\n\n// ── Record framing ───────────────────────────────────────────────────────\n//\n// Control characters separate fields, records and list items: subjects and\n// bodies contain tabs, newlines and commas, but never these — and every emitted\n// field passes through the `sanitize` handler, which makes the framing safe\n// rather than merely unlikely to break.\nexport const FIELD_SEP = '\u001f';\nexport const RECORD_SEP = '\u001e';\nexport const LIST_SEP = '\u001d';\n\n/** The same three, as AppleScript expressions. */\nexport const AS_FIELD_SEP = '(character id 31)';\nexport const AS_RECORD_SEP = '(character id 30)';\nexport const AS_LIST_SEP = '(character id 29)';\n\n/**\n * One record: the AppleScript expressions in `fields`, separated and terminated\n * so `splitRecords`/`splitFields` can take them apart again. A field holding a\n * LIST must be built with `my sanitizeList(...)`, which cleans the elements —\n * `sanitize` deliberately leaves the list separator alone.\n */\nexport function asRow(fields: readonly string[]): string {\n return fields\n .map(f => `my sanitize(${f})`)\n .join(` & ${AS_FIELD_SEP} & `) + ` & ${AS_RECORD_SEP}`;\n}\n\n/**\n * The handlers every script relies on, called with `my` from inside a `tell`.\n *\n * `isoDate` produces the zero-padded 'yyyy-MM-dd HH:mm' both platforms return;\n * `sanitize` strips the framing characters; `joinList` and `replaceText` are the\n * text-item-delimiter dance nobody should write twice.\n */\nexport const AS_HANDLERS = `\non pad2(n)\n set s to (n as integer) as string\n if (length of s) < 2 then set s to \"0\" & s\n return s\nend pad2\n\non isoDate(d)\n if d is missing value then return \"\"\n return (year of d as string) & \"-\" & pad2(month of d as integer) & \"-\" & pad2(day of d) & \" \" & pad2(hours of d) & \":\" & pad2(minutes of d)\nend isoDate\n\non joinList(lst, sep)\n set saved to AppleScript's text item delimiters\n set AppleScript's text item delimiters to sep\n set out to lst as string\n set AppleScript's text item delimiters to saved\n return out\nend joinList\n\non replaceText(t, findWhat, replaceWith)\n set saved to AppleScript's text item delimiters\n set AppleScript's text item delimiters to findWhat\n set parts to text items of t\n set AppleScript's text item delimiters to replaceWith\n set out to parts as string\n set AppleScript's text item delimiters to saved\n return out\nend replaceText\n\n-- Strips the FIELD and RECORD separators only. The LIST separator is left\n-- alone on purpose: a field may legitimately BE a list.\non sanitize(v)\n if v is missing value then return \"\"\n set t to v as string\n repeat with codePoint in {30, 31}\n set marker to (character id (codePoint as integer))\n if t contains marker then set t to my replaceText(t, marker, \" \")\n end repeat\n return t\nend sanitize\n\n-- A list field: each element loses all three separators, then they are joined\n-- with the LIST separator that splitList() takes them apart on.\non sanitizeList(lst)\n set cleaned to {}\n repeat with item_ in lst\n set t to my sanitize(item_)\n set marker to (character id 29)\n if t contains marker then set t to my replaceText(t, marker, \" \")\n set end of cleaned to t\n end repeat\n return my joinList(cleaned, (character id 29))\nend sanitizeList\n\non insertAboveQuoted(c, ins)\n if c is missing value then return ins\n set c to c as string\n if (length of c) is 0 then return ins\n -- A reply's own empty paragraph sits inside WordSection1; inserting straight\n -- after that tag puts the new text where the cursor would be. Anything else\n -- falls back to just inside <body>. ('at' is a reserved parameter name, so\n -- the cut point can't be called that.)\n set cutPoint to my tagEnd(c, \"${WORD_SECTION_ANCHOR}\")\n if cutPoint is 0 then set cutPoint to my tagEnd(c, \"<body\")\n if cutPoint is 0 then return ins & c\n if cutPoint is (length of c) then return c & ins\n return (text 1 thru cutPoint of c) & ins & (text (cutPoint + 1) thru -1 of c)\nend insertAboveQuoted\n\n-- 'rest' is an AppleScript term, so the remainder can't be called that:\n-- assigning to it compiles and then fails at run time.\non tagEnd(c, marker)\n set ix to offset of marker in c\n if ix is 0 then return 0\n if marker ends with \">\" then return ix + (length of marker) - 1\n set tailText to text ix thru -1 of c\n set gt to offset of \">\" in tailText\n if gt is 0 then return 0\n return ix + gt - 1\nend tagEnd\n`;\n\n/** A script's output as records, without the trailing empty one. */\nexport function splitRecords(raw: string): string[] {\n return raw.split(RECORD_SEP).filter(record => record.trim() !== '');\n}\n\n/** One record's fields. */\nexport function splitFields(record: string): string[] {\n return record.split(FIELD_SEP);\n}\n\n/** A field that carries a list. */\nexport function splitList(value: string | undefined): string[] {\n return (value || '').split(LIST_SEP).filter(Boolean);\n}\n\n/** A field by position; a missing field reads as ''. */\nexport function field(fields: readonly string[], index: number): string {\n return fields[index] ?? '';\n}\n\n/** The fields of a script's first record — the summary a mutation returns ahead of any per-item rows. */\nexport function summaryFields(raw: string): string[] {\n return splitFields(splitRecords(raw)[0] ?? '');\n}\n\n/** A numeric field, `fallback` when absent or unparsable. */\nexport function intField(fields: readonly string[], index: number, fallback = 0): number {\n const parsed = Number.parseInt(field(fields, index), 10);\n return Number.isNaN(parsed) ? fallback : parsed;\n}\n\n/** A boolean field; AppleScript renders booleans as the bare words true/false. */\nexport function boolField(fields: readonly string[], index: number): boolean {\n return field(fields, index).trim() === 'true';\n}\n"]}