@dawnlit/outlook-bridge 1.1.1 → 2.1.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/README.md +152 -183
- package/dist/backend.d.ts +151 -0
- package/dist/backend.js +3 -0
- package/dist/backend.js.map +1 -0
- package/dist/bridge.d.ts +56 -0
- package/dist/bridge.js +507 -0
- package/dist/bridge.js.map +1 -0
- package/dist/errors.d.ts +79 -43
- package/dist/errors.js +93 -52
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +10 -10
- package/dist/index.js +25 -52
- package/dist/index.js.map +1 -1
- package/dist/mac/accounts.d.ts +7 -2
- package/dist/mac/accounts.js +23 -6
- package/dist/mac/accounts.js.map +1 -1
- package/dist/mac/attachments.d.ts +5 -25
- package/dist/mac/attachments.js +40 -99
- package/dist/mac/attachments.js.map +1 -1
- package/dist/mac/bounces.d.ts +12 -27
- package/dist/mac/bounces.js +96 -117
- package/dist/mac/bounces.js.map +1 -1
- package/dist/mac/cleanup.d.ts +11 -25
- package/dist/mac/cleanup.js +44 -107
- package/dist/mac/cleanup.js.map +1 -1
- package/dist/mac/drafts.d.ts +15 -34
- package/dist/mac/drafts.js +113 -186
- package/dist/mac/drafts.js.map +1 -1
- package/dist/mac/folders.d.ts +12 -21
- package/dist/mac/folders.js +44 -59
- package/dist/mac/folders.js.map +1 -1
- package/dist/mac/index.d.ts +2 -22
- package/dist/mac/index.js +19 -81
- package/dist/mac/index.js.map +1 -1
- package/dist/mac/profile.d.ts +13 -0
- package/dist/mac/profile.js +149 -0
- package/dist/mac/profile.js.map +1 -0
- package/dist/mac/read.d.ts +21 -37
- package/dist/mac/read.js +198 -216
- package/dist/mac/read.js.map +1 -1
- package/dist/mac/run.d.ts +51 -38
- package/dist/mac/run.js +138 -103
- package/dist/mac/run.js.map +1 -1
- package/dist/mac/scripts.d.ts +90 -91
- package/dist/mac/scripts.js +167 -126
- package/dist/mac/scripts.js.map +1 -1
- package/dist/mac/send.d.ts +10 -20
- package/dist/mac/send.js +99 -89
- package/dist/mac/send.js.map +1 -1
- package/dist/mac/signatures.d.ts +2 -2
- package/dist/mac/signatures.js +9 -11
- package/dist/mac/signatures.js.map +1 -1
- package/dist/mac/templates.d.ts +14 -26
- package/dist/mac/templates.js +79 -120
- package/dist/mac/templates.js.map +1 -1
- package/dist/mail.d.ts +80 -46
- package/dist/mail.js +179 -73
- package/dist/mail.js.map +1 -1
- package/dist/runtime.d.ts +76 -43
- package/dist/runtime.js +103 -48
- package/dist/runtime.js.map +1 -1
- package/dist/shared/args.d.ts +48 -0
- package/dist/shared/args.js +216 -0
- package/dist/shared/args.js.map +1 -0
- package/dist/shared/attachmentMatch.d.ts +26 -20
- package/dist/shared/attachmentMatch.js +60 -23
- package/dist/shared/attachmentMatch.js.map +1 -1
- package/dist/shared/bounceReport.d.ts +11 -0
- package/dist/shared/bounceReport.js +45 -0
- package/dist/shared/bounceReport.js.map +1 -0
- package/dist/shared/bounceRules.d.ts +38 -10
- package/dist/shared/bounceRules.js +50 -32
- package/dist/shared/bounceRules.js.map +1 -1
- package/dist/shared/exec.d.ts +21 -0
- package/dist/shared/exec.js +47 -0
- package/dist/shared/exec.js.map +1 -0
- package/dist/shared/json.d.ts +17 -6
- package/dist/shared/json.js +35 -24
- package/dist/shared/json.js.map +1 -1
- package/dist/shared/locate.d.ts +31 -0
- package/dist/shared/locate.js +128 -0
- package/dist/shared/locate.js.map +1 -0
- package/dist/shared/replyBody.d.ts +31 -36
- package/dist/shared/replyBody.js +83 -72
- package/dist/shared/replyBody.js.map +1 -1
- package/dist/shared/templateLimits.d.ts +14 -0
- package/dist/shared/templateLimits.js +21 -0
- package/dist/shared/templateLimits.js.map +1 -0
- package/dist/templateBody.d.ts +69 -0
- package/dist/{outlookTemplateSections.js → templateBody.js} +80 -73
- package/dist/templateBody.js.map +1 -0
- package/dist/types.d.ts +381 -168
- package/dist/types.js +8 -9
- package/dist/types.js.map +1 -1
- package/dist/windows/accounts.d.ts +7 -12
- package/dist/windows/accounts.js +19 -34
- package/dist/windows/accounts.js.map +1 -1
- package/dist/windows/attachments.d.ts +9 -24
- package/dist/windows/attachments.js +94 -119
- package/dist/windows/attachments.js.map +1 -1
- package/dist/windows/bounces.d.ts +13 -26
- package/dist/windows/bounces.js +168 -200
- package/dist/windows/bounces.js.map +1 -1
- package/dist/windows/cleanup.d.ts +23 -23
- package/dist/windows/cleanup.js +99 -113
- package/dist/windows/cleanup.js.map +1 -1
- package/dist/windows/drafts.d.ts +16 -35
- package/dist/windows/drafts.js +156 -253
- package/dist/windows/drafts.js.map +1 -1
- package/dist/windows/folders.d.ts +8 -13
- package/dist/windows/folders.js +44 -52
- package/dist/windows/folders.js.map +1 -1
- package/dist/windows/index.d.ts +2 -27
- package/dist/windows/index.js +11 -66
- package/dist/windows/index.js.map +1 -1
- package/dist/windows/read.d.ts +24 -60
- package/dist/windows/read.js +265 -306
- package/dist/windows/read.js.map +1 -1
- package/dist/windows/run.d.ts +38 -40
- package/dist/windows/run.js +151 -112
- package/dist/windows/run.js.map +1 -1
- package/dist/windows/scripts.d.ts +65 -55
- package/dist/windows/scripts.js +109 -85
- package/dist/windows/scripts.js.map +1 -1
- package/dist/windows/send.d.ts +9 -9
- package/dist/windows/send.js +76 -88
- package/dist/windows/send.js.map +1 -1
- package/dist/windows/signatures.d.ts +4 -10
- package/dist/windows/signatures.js +30 -37
- package/dist/windows/signatures.js.map +1 -1
- package/dist/windows/templates.d.ts +12 -18
- package/dist/windows/templates.js +117 -150
- package/dist/windows/templates.js.map +1 -1
- package/package.json +5 -4
- package/dist/OutlookService.d.ts +0 -43
- package/dist/OutlookService.js +0 -137
- package/dist/OutlookService.js.map +0 -1
- package/dist/outlookTemplateSections.d.ts +0 -62
- package/dist/outlookTemplateSections.js.map +0 -1
- package/dist/shared/capabilities.d.ts +0 -3
- package/dist/shared/capabilities.js +0 -9
- package/dist/shared/capabilities.js.map +0 -1
package/dist/runtime.js
CHANGED
|
@@ -3,54 +3,57 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.RUN_BUDGETS = void 0;
|
|
6
7
|
exports.mergeOptions = mergeOptions;
|
|
7
8
|
exports.configure = configure;
|
|
8
9
|
exports.getConfig = getConfig;
|
|
9
10
|
exports.getGlobalConfig = getGlobalConfig;
|
|
10
11
|
exports.withConfig = withConfig;
|
|
12
|
+
exports.timeoutFor = timeoutFor;
|
|
11
13
|
exports.reportRun = reportRun;
|
|
12
14
|
exports.tempFile = tempFile;
|
|
13
|
-
exports.
|
|
14
|
-
exports.
|
|
15
|
-
//
|
|
16
|
-
// PowerShell / AppleScript runs.
|
|
15
|
+
exports.removeTempFile = removeTempFile;
|
|
16
|
+
exports.attachmentDestination = attachmentDestination;
|
|
17
|
+
// Settings, and the scratch files every run needs.
|
|
17
18
|
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
19
|
+
// Each operation generates a script and runs it under an interpreter, which
|
|
20
|
+
// leaves a caller four things they cannot otherwise reach: how long a run may
|
|
21
|
+
// take, how much it may print, how to call it off, and what the script actually
|
|
22
|
+
// said. Those are the settings here — process-wide through `configure()`, or per
|
|
23
|
+
// bridge through `createOutlookBridge()`.
|
|
22
24
|
const async_hooks_1 = require("async_hooks");
|
|
23
25
|
const fs_1 = __importDefault(require("fs"));
|
|
24
26
|
const os_1 = __importDefault(require("os"));
|
|
25
27
|
const path_1 = __importDefault(require("path"));
|
|
28
|
+
const errors_1 = require("./errors");
|
|
26
29
|
const envDebug = process.env.OUTLOOK_BRIDGE_DEBUG;
|
|
27
|
-
/** The process-wide
|
|
28
|
-
|
|
29
|
-
timeoutMs:
|
|
30
|
+
/** The process-wide settings, as `configure()` leaves them. */
|
|
31
|
+
let globalConfig = {
|
|
32
|
+
timeoutMs: undefined,
|
|
30
33
|
maxBufferBytes: 8 * 1024 * 1024,
|
|
31
34
|
tempDir: os_1.default.tmpdir(),
|
|
35
|
+
signal: undefined,
|
|
32
36
|
debug: !!envDebug && envDebug !== '0',
|
|
33
37
|
};
|
|
34
38
|
/**
|
|
35
|
-
* Per-
|
|
39
|
+
* Per-bridge settings, scoped to an async call tree.
|
|
36
40
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* bridges with different timeouts can then run concurrently in one process,
|
|
43
|
-
* which is the thing a second consumer actually needs and the global could never
|
|
44
|
-
* give them.
|
|
41
|
+
* The platform code reads its settings through `getConfig()` wherever it needs
|
|
42
|
+
* them rather than having a config threaded through every function. A bridge
|
|
43
|
+
* instance runs each call inside `withConfig`, and the store follows every
|
|
44
|
+
* `await` beneath it — so two bridges with different timeouts can run at once
|
|
45
|
+
* in one process without seeing each other's.
|
|
45
46
|
*/
|
|
46
47
|
const scoped = new async_hooks_1.AsyncLocalStorage();
|
|
47
|
-
/**
|
|
48
|
+
/** Caller options merged over a base; keys left out keep the base's value. */
|
|
48
49
|
function mergeOptions(base, options = {}) {
|
|
49
50
|
const next = { ...base };
|
|
50
|
-
if (options.timeoutMs
|
|
51
|
-
next.timeoutMs =
|
|
51
|
+
if (options.timeoutMs === null)
|
|
52
|
+
next.timeoutMs = undefined;
|
|
53
|
+
else if (options.timeoutMs !== undefined)
|
|
54
|
+
next.timeoutMs = nonNegative(options.timeoutMs, 'timeoutMs');
|
|
52
55
|
if (options.maxBufferBytes !== undefined)
|
|
53
|
-
next.maxBufferBytes = Math.max(1, options.maxBufferBytes);
|
|
56
|
+
next.maxBufferBytes = Math.max(1, nonNegative(options.maxBufferBytes, 'maxBufferBytes'));
|
|
54
57
|
if (options.tempDir !== undefined)
|
|
55
58
|
next.tempDir = options.tempDir;
|
|
56
59
|
if (options.signal !== undefined)
|
|
@@ -59,21 +62,27 @@ function mergeOptions(base, options = {}) {
|
|
|
59
62
|
next.debug = options.debug;
|
|
60
63
|
return next;
|
|
61
64
|
}
|
|
65
|
+
function nonNegative(value, name) {
|
|
66
|
+
if (typeof value !== 'number' || Number.isNaN(value)) {
|
|
67
|
+
throw new errors_1.InvalidRequestError(`${name} must be a number (got ${String(value)}).`);
|
|
68
|
+
}
|
|
69
|
+
return Math.max(0, Math.floor(value));
|
|
70
|
+
}
|
|
62
71
|
/**
|
|
63
|
-
*
|
|
64
|
-
* rest alone.
|
|
72
|
+
* Change the process-wide settings. Merges, so naming one option leaves the
|
|
73
|
+
* rest alone.
|
|
65
74
|
*
|
|
66
|
-
* Affects every caller in the process. Prefer
|
|
67
|
-
*
|
|
75
|
+
* Affects every caller in the process that uses the exported functions. Prefer
|
|
76
|
+
* `createOutlookBridge(options)` in a process shared with code you don't own.
|
|
68
77
|
*/
|
|
69
78
|
function configure(options) {
|
|
70
|
-
|
|
79
|
+
globalConfig = mergeOptions(globalConfig, options);
|
|
71
80
|
}
|
|
72
|
-
/** The settings in force for the current call
|
|
81
|
+
/** The settings in force for the current call: the enclosing bridge's, else the process-wide ones. */
|
|
73
82
|
function getConfig() {
|
|
74
83
|
return scoped.getStore() ?? globalConfig;
|
|
75
84
|
}
|
|
76
|
-
/** The process-wide
|
|
85
|
+
/** The process-wide settings, ignoring any bridge scope. */
|
|
77
86
|
function getGlobalConfig() {
|
|
78
87
|
return globalConfig;
|
|
79
88
|
}
|
|
@@ -81,7 +90,30 @@ function getGlobalConfig() {
|
|
|
81
90
|
function withConfig(config, fn) {
|
|
82
91
|
return scoped.run(config, fn);
|
|
83
92
|
}
|
|
84
|
-
/**
|
|
93
|
+
/**
|
|
94
|
+
* How long each kind of run may take when no `timeoutMs` is set.
|
|
95
|
+
*
|
|
96
|
+
* Generous on purpose: a budget is a guard against a hung interpreter, not a
|
|
97
|
+
* performance target, and the first call against an Outlook that isn't running
|
|
98
|
+
* yet spends much of its budget starting it.
|
|
99
|
+
*/
|
|
100
|
+
exports.RUN_BUDGETS = {
|
|
101
|
+
/** One lookup: accounts, signatures, opening or reading a single email. */
|
|
102
|
+
quick: 60_000,
|
|
103
|
+
/** Composing, filing, saving attachments, or reading one folder. */
|
|
104
|
+
standard: 120_000,
|
|
105
|
+
/** Walking a mailbox tree, or acting on a batch of items. */
|
|
106
|
+
scan: 300_000,
|
|
107
|
+
/** Emptying Deleted Items. */
|
|
108
|
+
purge: 600_000,
|
|
109
|
+
};
|
|
110
|
+
/** The timeout one run gets: the configured ceiling if there is one, else its budget. */
|
|
111
|
+
function timeoutFor(budget) {
|
|
112
|
+
if (budget === 'interactive')
|
|
113
|
+
return 0;
|
|
114
|
+
return getConfig().timeoutMs ?? exports.RUN_BUDGETS[budget];
|
|
115
|
+
}
|
|
116
|
+
/** Report one finished run to the configured debug hook. */
|
|
85
117
|
function reportRun(event) {
|
|
86
118
|
const { debug } = getConfig();
|
|
87
119
|
if (!debug)
|
|
@@ -94,30 +126,53 @@ function reportRun(event) {
|
|
|
94
126
|
process.stderr.write(`[outlook-bridge] ${event.runner} ${event.durationMs}ms ${outcome}\n${event.script}\n`);
|
|
95
127
|
}
|
|
96
128
|
/**
|
|
97
|
-
*
|
|
98
|
-
* matters: two concurrent calls
|
|
99
|
-
*
|
|
129
|
+
* A scratch-file path in the configured temp directory. The random part
|
|
130
|
+
* matters: two concurrent calls in the same millisecond would otherwise pick the
|
|
131
|
+
* same name and overwrite each other's script mid-run.
|
|
100
132
|
*/
|
|
101
133
|
function tempFile(kind, extension) {
|
|
102
134
|
const unique = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
103
135
|
return path_1.default.join(getConfig().tempDir, `outlook-bridge-${kind}-${unique}.${extension}`);
|
|
104
136
|
}
|
|
137
|
+
/** Delete a scratch file, ignoring one that is already gone. */
|
|
138
|
+
function removeTempFile(file) {
|
|
139
|
+
try {
|
|
140
|
+
fs_1.default.unlinkSync(file);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
// The run is over either way; a leftover scratch file isn't worth failing it.
|
|
144
|
+
}
|
|
145
|
+
}
|
|
105
146
|
/**
|
|
106
|
-
*
|
|
147
|
+
* Where one call saves its attachments: the caller's directory (created if
|
|
148
|
+
* absent), or a fresh private directory of its own.
|
|
107
149
|
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
* unrelated programs using this package on the same machine.
|
|
150
|
+
* Private by default because attachments keep the names senders gave them, so
|
|
151
|
+
* one shared folder means two emails carrying 'invoice.pdf' overwrite each
|
|
152
|
+
* other — and so do two programs using this package on one machine.
|
|
112
153
|
*/
|
|
113
|
-
function
|
|
114
|
-
|
|
154
|
+
function attachmentDestination(destDir) {
|
|
155
|
+
if (destDir) {
|
|
156
|
+
const existed = fs_1.default.existsSync(destDir);
|
|
157
|
+
fs_1.default.mkdirSync(destDir, { recursive: true });
|
|
158
|
+
return {
|
|
159
|
+
dir: destDir,
|
|
160
|
+
discardIfUnused() {
|
|
161
|
+
if (!existed)
|
|
162
|
+
removeIfEmpty(destDir);
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
const dir = fs_1.default.mkdtempSync(path_1.default.join(getConfig().tempDir, 'outlook-bridge-attachments-'));
|
|
167
|
+
return { dir, discardIfUnused: () => removeIfEmpty(dir) };
|
|
115
168
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
169
|
+
function removeIfEmpty(dir) {
|
|
170
|
+
try {
|
|
171
|
+
if (fs_1.default.readdirSync(dir).length === 0)
|
|
172
|
+
fs_1.default.rmdirSync(dir);
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
// Nothing to undo if it can't be read.
|
|
176
|
+
}
|
|
122
177
|
}
|
|
123
178
|
//# sourceMappingURL=runtime.js.map
|
package/dist/runtime.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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","sourcesContent":["// Settings, and the scratch files every run needs.\n//\n// Each operation generates a script and runs it under an interpreter, which\n// leaves a caller four things they cannot otherwise reach: how long a run may\n// take, how much it may print, how to call it off, and what the script actually\n// said. Those are the settings here — process-wide through `configure()`, or per\n// bridge through `createOutlookBridge()`.\nimport { AsyncLocalStorage } from 'async_hooks';\nimport fs from 'fs';\nimport os from 'os';\nimport path from 'path';\nimport { InvalidRequestError, type ScriptRunner } from './errors';\n\n/** One completed script run, handed to the `debug` hook. */\nexport interface BridgeDebugEvent {\n runner: ScriptRunner;\n /** The script exactly as it ran. */\n script: string;\n durationMs: number;\n /** Present only when the run failed. */\n error?: string;\n}\n\nexport interface BridgeOptions {\n /**\n * Ceiling on every script run, in milliseconds, replacing each operation's\n * own budget; 0 disables timeouts. Leave unset (or pass null to go back) to\n * let each operation use a budget sized for its work — a minute for a\n * lookup, up to ten for purging Deleted Items. `editEmailTemplate` waits on\n * a person and is never timed; cancel it with `signal`.\n */\n timeoutMs?: number | null;\n /**\n * Max bytes a script may print before the run is killed with\n * OUTPUT_TOO_LARGE. Searches that return whole bodies push against this.\n * Default 8 MiB.\n */\n maxBufferBytes?: number;\n /** Directory for generated scripts, and for saved attachments when a call\n * names no destination. Default: the OS temp directory. */\n tempDir?: string;\n /**\n * Cancels in-flight runs: the interpreter is killed and the call rejects\n * with ABORTED. Most useful scoped to the calls it should cancel:\n * `bridge.withOptions({ signal }).searchInboxByFilter(...)`.\n */\n signal?: AbortSignal;\n /** `true` logs every script to stderr; a function receives each run\n * instead. Default: on when OUTLOOK_BRIDGE_DEBUG is set to anything but '' or '0'. */\n debug?: boolean | ((event: BridgeDebugEvent) => void);\n}\n\n/** The settings a call runs with, every default applied. */\nexport interface ResolvedConfig {\n /** undefined = each operation's own budget. */\n readonly timeoutMs: number | undefined;\n readonly maxBufferBytes: number;\n readonly tempDir: string;\n readonly signal: AbortSignal | undefined;\n readonly debug: boolean | ((event: BridgeDebugEvent) => void);\n}\n\nconst envDebug = process.env.OUTLOOK_BRIDGE_DEBUG;\n\n/** The process-wide settings, as `configure()` leaves them. */\nlet globalConfig: ResolvedConfig = {\n timeoutMs: undefined,\n maxBufferBytes: 8 * 1024 * 1024,\n tempDir: os.tmpdir(),\n signal: undefined,\n debug: !!envDebug && envDebug !== '0',\n};\n\n/**\n * Per-bridge settings, scoped to an async call tree.\n *\n * The platform code reads its settings through `getConfig()` wherever it needs\n * them rather than having a config threaded through every function. A bridge\n * instance runs each call inside `withConfig`, and the store follows every\n * `await` beneath it — so two bridges with different timeouts can run at once\n * in one process without seeing each other's.\n */\nconst scoped = new AsyncLocalStorage<ResolvedConfig>();\n\n/** Caller options merged over a base; keys left out keep the base's value. */\nexport function mergeOptions(base: ResolvedConfig, options: BridgeOptions = {}): ResolvedConfig {\n const next = { ...base };\n if (options.timeoutMs === null) next.timeoutMs = undefined;\n else if (options.timeoutMs !== undefined) next.timeoutMs = nonNegative(options.timeoutMs, 'timeoutMs');\n if (options.maxBufferBytes !== undefined) next.maxBufferBytes = Math.max(1, nonNegative(options.maxBufferBytes, 'maxBufferBytes'));\n if (options.tempDir !== undefined) next.tempDir = options.tempDir;\n if (options.signal !== undefined) next.signal = options.signal;\n if (options.debug !== undefined) next.debug = options.debug;\n return next;\n}\n\nfunction nonNegative(value: number, name: string): number {\n if (typeof value !== 'number' || Number.isNaN(value)) {\n throw new InvalidRequestError(`${name} must be a number (got ${String(value)}).`);\n }\n return Math.max(0, Math.floor(value));\n}\n\n/**\n * Change the process-wide settings. Merges, so naming one option leaves the\n * rest alone.\n *\n * Affects every caller in the process that uses the exported functions. Prefer\n * `createOutlookBridge(options)` in a process shared with code you don't own.\n */\nexport function configure(options: BridgeOptions): void {\n globalConfig = mergeOptions(globalConfig, options);\n}\n\n/** The settings in force for the current call: the enclosing bridge's, else the process-wide ones. */\nexport function getConfig(): ResolvedConfig {\n return scoped.getStore() ?? globalConfig;\n}\n\n/** The process-wide settings, ignoring any bridge scope. */\nexport function getGlobalConfig(): ResolvedConfig {\n return globalConfig;\n}\n\n/** Run `fn` with `config` in force for it and everything it awaits. */\nexport function withConfig<T>(config: ResolvedConfig, fn: () => T): T {\n return scoped.run(config, fn);\n}\n\n/**\n * How long each kind of run may take when no `timeoutMs` is set.\n *\n * Generous on purpose: a budget is a guard against a hung interpreter, not a\n * performance target, and the first call against an Outlook that isn't running\n * yet spends much of its budget starting it.\n */\nexport const RUN_BUDGETS = {\n /** One lookup: accounts, signatures, opening or reading a single email. */\n quick: 60_000,\n /** Composing, filing, saving attachments, or reading one folder. */\n standard: 120_000,\n /** Walking a mailbox tree, or acting on a batch of items. */\n scan: 300_000,\n /** Emptying Deleted Items. */\n purge: 600_000,\n} as const;\n\n/** A budget name, or 'interactive' for a run that waits on a person and is never timed. */\nexport type RunBudget = keyof typeof RUN_BUDGETS | 'interactive';\n\n/** The timeout one run gets: the configured ceiling if there is one, else its budget. */\nexport function timeoutFor(budget: RunBudget): number {\n if (budget === 'interactive') return 0;\n return getConfig().timeoutMs ?? RUN_BUDGETS[budget];\n}\n\n/** Report one finished run to the configured debug hook. */\nexport function reportRun(event: BridgeDebugEvent): void {\n const { debug } = getConfig();\n if (!debug) return;\n if (typeof debug === 'function') {\n debug(event);\n return;\n }\n const outcome = event.error ? `FAILED: ${event.error}` : 'ok';\n process.stderr.write(`[outlook-bridge] ${event.runner} ${event.durationMs}ms ${outcome}\\n${event.script}\\n`);\n}\n\n/**\n * A scratch-file path in the configured temp directory. The random part\n * matters: two concurrent calls in the same millisecond would otherwise pick the\n * same name and overwrite each other's script mid-run.\n */\nexport function tempFile(kind: string, extension: string): string {\n const unique = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;\n return path.join(getConfig().tempDir, `outlook-bridge-${kind}-${unique}.${extension}`);\n}\n\n/** Delete a scratch file, ignoring one that is already gone. */\nexport function removeTempFile(file: string): void {\n try {\n fs.unlinkSync(file);\n } catch {\n // The run is over either way; a leftover scratch file isn't worth failing it.\n }\n}\n\n/** A destination for saved attachments, and how to undo creating it. */\nexport interface AttachmentDestination {\n dir: string;\n\n /** Remove the directory if this call created it and left it empty. */\n discardIfUnused(): void;\n}\n\n/**\n * Where one call saves its attachments: the caller's directory (created if\n * absent), or a fresh private directory of its own.\n *\n * Private by default because attachments keep the names senders gave them, so\n * one shared folder means two emails carrying 'invoice.pdf' overwrite each\n * other — and so do two programs using this package on one machine.\n */\nexport function attachmentDestination(destDir?: string): AttachmentDestination {\n if (destDir) {\n const existed = fs.existsSync(destDir);\n fs.mkdirSync(destDir, { recursive: true });\n return {\n dir: destDir,\n discardIfUnused() {\n if (!existed) removeIfEmpty(destDir);\n },\n };\n }\n const dir = fs.mkdtempSync(path.join(getConfig().tempDir, 'outlook-bridge-attachments-'));\n return { dir, discardIfUnused: () => removeIfEmpty(dir) };\n}\n\nfunction removeIfEmpty(dir: string): void {\n try {\n if (fs.readdirSync(dir).length === 0) fs.rmdirSync(dir);\n } catch {\n // Nothing to undo if it can't be read.\n }\n}\n"]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { EmailLocator, EmailRef, Recipients } from '../types';
|
|
2
|
+
/** A required parameters object. */
|
|
3
|
+
export declare function requireObject<T extends object>(value: T | undefined | null, name: string): Partial<T>;
|
|
4
|
+
/** An optional options object; undefined and null are an empty one. */
|
|
5
|
+
export declare function optionsObject<T extends object>(value: T | undefined | null, name?: string): Partial<T>;
|
|
6
|
+
/** A required string, trimmed. Blank is an error. */
|
|
7
|
+
export declare function requiredText(value: unknown, name: string): string;
|
|
8
|
+
/** A required string kept exactly as given — content, not an identifier. May be empty. */
|
|
9
|
+
export declare function requiredContent(value: unknown, name: string): string;
|
|
10
|
+
/** Optional content kept exactly as given; undefined and null mean "not given". */
|
|
11
|
+
export declare function optionalContent(value: unknown, name: string): string | undefined;
|
|
12
|
+
/** An optional regular expression. */
|
|
13
|
+
export declare function optionalRegExp(value: unknown, name: string): RegExp | undefined;
|
|
14
|
+
/** An optional string, trimmed; undefined, null and blank all mean "not given". */
|
|
15
|
+
export declare function optionalText(value: unknown, name: string): string | undefined;
|
|
16
|
+
/** An optional flag; undefined and null take the fallback. */
|
|
17
|
+
export declare function flag(value: unknown, name: string, fallback: boolean): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* An optional count: undefined and null take the fallback, a fraction is
|
|
20
|
+
* rounded down, and the result is clamped into [min, max] — the documented
|
|
21
|
+
* range is a guard, not a trap. Anything that isn't a finite number is an error.
|
|
22
|
+
*/
|
|
23
|
+
export declare function count(value: unknown, name: string, fallback: number, min: number, max: number): number;
|
|
24
|
+
/** A list of strings, each non-blank and trimmed. Omitted is an empty list. */
|
|
25
|
+
export declare function textList(value: unknown, name: string): string[];
|
|
26
|
+
/** A list of names matched exactly as given — attachment file names, say. */
|
|
27
|
+
export declare function nameList(value: unknown, name: string): string[];
|
|
28
|
+
/** The entry ids a batch operation acts on. Duplicates are dropped; order is kept. */
|
|
29
|
+
export declare function entryIdList(value: unknown, name?: string): string[];
|
|
30
|
+
/** Where an email lives, from an id or anything carrying a locator. */
|
|
31
|
+
export declare function emailLocator(email: EmailRef | undefined | null, name?: string): EmailLocator;
|
|
32
|
+
/** A string→string map, e.g. template placeholders. */
|
|
33
|
+
export declare function stringRecord(value: unknown, name: string): Record<string, string> | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* Split a recipient list into its entries.
|
|
36
|
+
*
|
|
37
|
+
* Separators are commas and semicolons — but not inside a quoted display name
|
|
38
|
+
* or between angle brackets, where `"Doe, Jo" <jo@example.com>` has a comma
|
|
39
|
+
* that belongs to the name.
|
|
40
|
+
*/
|
|
41
|
+
export declare function recipientList(value: Recipients | undefined | null, name: string): string[];
|
|
42
|
+
/** One recipient entry as a display name and an address. */
|
|
43
|
+
export interface ParsedRecipient {
|
|
44
|
+
name: string;
|
|
45
|
+
address: string;
|
|
46
|
+
}
|
|
47
|
+
/** `"Doe, Jo" <jo@example.com>` → name + address; a bare address has no name. */
|
|
48
|
+
export declare function parseRecipient(entry: string): ParsedRecipient;
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.requireObject = requireObject;
|
|
4
|
+
exports.optionsObject = optionsObject;
|
|
5
|
+
exports.requiredText = requiredText;
|
|
6
|
+
exports.requiredContent = requiredContent;
|
|
7
|
+
exports.optionalContent = optionalContent;
|
|
8
|
+
exports.optionalRegExp = optionalRegExp;
|
|
9
|
+
exports.optionalText = optionalText;
|
|
10
|
+
exports.flag = flag;
|
|
11
|
+
exports.count = count;
|
|
12
|
+
exports.textList = textList;
|
|
13
|
+
exports.nameList = nameList;
|
|
14
|
+
exports.entryIdList = entryIdList;
|
|
15
|
+
exports.emailLocator = emailLocator;
|
|
16
|
+
exports.stringRecord = stringRecord;
|
|
17
|
+
exports.recipientList = recipientList;
|
|
18
|
+
exports.parseRecipient = parseRecipient;
|
|
19
|
+
// Checking and normalizing what a caller passes, once, before either platform
|
|
20
|
+
// sees it.
|
|
21
|
+
//
|
|
22
|
+
// This is a security boundary as much as a convenience. Arguments end up
|
|
23
|
+
// inside generated scripts, and TypeScript's types vanish at run time: a
|
|
24
|
+
// JavaScript caller, or a tool layer passing JSON through, can hand a string
|
|
25
|
+
// where a count belongs. So every value is checked for its type here, every
|
|
26
|
+
// count is made a whole number within its documented range, and the platform
|
|
27
|
+
// code only ever receives values of the shape its script templates assume.
|
|
28
|
+
const errors_1 = require("../errors");
|
|
29
|
+
function describe(value) {
|
|
30
|
+
if (value === null)
|
|
31
|
+
return 'null';
|
|
32
|
+
if (Array.isArray(value))
|
|
33
|
+
return 'an array';
|
|
34
|
+
return typeof value === 'string' ? `'${value}'` : typeof value;
|
|
35
|
+
}
|
|
36
|
+
/** A required parameters object. */
|
|
37
|
+
function requireObject(value, name) {
|
|
38
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
39
|
+
throw new errors_1.InvalidRequestError(`${name} must be an object (got ${describe(value)}).`);
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
/** An optional options object; undefined and null are an empty one. */
|
|
44
|
+
function optionsObject(value, name = 'options') {
|
|
45
|
+
if (value === undefined || value === null)
|
|
46
|
+
return {};
|
|
47
|
+
return requireObject(value, name);
|
|
48
|
+
}
|
|
49
|
+
/** A required string, trimmed. Blank is an error. */
|
|
50
|
+
function requiredText(value, name) {
|
|
51
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
52
|
+
throw new errors_1.InvalidRequestError(`${name} is required and must be a non-empty string (got ${describe(value)}).`);
|
|
53
|
+
}
|
|
54
|
+
return value.trim();
|
|
55
|
+
}
|
|
56
|
+
/** A required string kept exactly as given — content, not an identifier. May be empty. */
|
|
57
|
+
function requiredContent(value, name) {
|
|
58
|
+
if (typeof value !== 'string') {
|
|
59
|
+
throw new errors_1.InvalidRequestError(`${name} must be a string (got ${describe(value)}).`);
|
|
60
|
+
}
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
/** Optional content kept exactly as given; undefined and null mean "not given". */
|
|
64
|
+
function optionalContent(value, name) {
|
|
65
|
+
if (value === undefined || value === null)
|
|
66
|
+
return undefined;
|
|
67
|
+
return requiredContent(value, name);
|
|
68
|
+
}
|
|
69
|
+
/** An optional regular expression. */
|
|
70
|
+
function optionalRegExp(value, name) {
|
|
71
|
+
if (value === undefined || value === null)
|
|
72
|
+
return undefined;
|
|
73
|
+
// Checked by tag rather than instanceof, so a RegExp from another realm passes.
|
|
74
|
+
if (Object.prototype.toString.call(value) !== '[object RegExp]') {
|
|
75
|
+
throw new errors_1.InvalidRequestError(`${name} must be a RegExp (got ${describe(value)}).`);
|
|
76
|
+
}
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
/** An optional string, trimmed; undefined, null and blank all mean "not given". */
|
|
80
|
+
function optionalText(value, name) {
|
|
81
|
+
if (value === undefined || value === null)
|
|
82
|
+
return undefined;
|
|
83
|
+
if (typeof value !== 'string') {
|
|
84
|
+
throw new errors_1.InvalidRequestError(`${name} must be a string (got ${describe(value)}).`);
|
|
85
|
+
}
|
|
86
|
+
return value.trim() || undefined;
|
|
87
|
+
}
|
|
88
|
+
/** An optional flag; undefined and null take the fallback. */
|
|
89
|
+
function flag(value, name, fallback) {
|
|
90
|
+
if (value === undefined || value === null)
|
|
91
|
+
return fallback;
|
|
92
|
+
if (typeof value !== 'boolean') {
|
|
93
|
+
throw new errors_1.InvalidRequestError(`${name} must be true or false (got ${describe(value)}).`);
|
|
94
|
+
}
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* An optional count: undefined and null take the fallback, a fraction is
|
|
99
|
+
* rounded down, and the result is clamped into [min, max] — the documented
|
|
100
|
+
* range is a guard, not a trap. Anything that isn't a finite number is an error.
|
|
101
|
+
*/
|
|
102
|
+
function count(value, name, fallback, min, max) {
|
|
103
|
+
if (value === undefined || value === null)
|
|
104
|
+
return fallback;
|
|
105
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
106
|
+
throw new errors_1.InvalidRequestError(`${name} must be a finite number (got ${describe(value)}).`);
|
|
107
|
+
}
|
|
108
|
+
return Math.min(max, Math.max(min, Math.floor(value)));
|
|
109
|
+
}
|
|
110
|
+
/** A list of strings, each non-blank and trimmed. Omitted is an empty list. */
|
|
111
|
+
function textList(value, name) {
|
|
112
|
+
if (value === undefined || value === null)
|
|
113
|
+
return [];
|
|
114
|
+
if (!Array.isArray(value)) {
|
|
115
|
+
throw new errors_1.InvalidRequestError(`${name} must be an array of strings (got ${describe(value)}).`);
|
|
116
|
+
}
|
|
117
|
+
return value.map((item, index) => requiredText(item, `${name}[${index}]`));
|
|
118
|
+
}
|
|
119
|
+
/** A list of names matched exactly as given — attachment file names, say. */
|
|
120
|
+
function nameList(value, name) {
|
|
121
|
+
if (!Array.isArray(value)) {
|
|
122
|
+
throw new errors_1.InvalidRequestError(`${name} must be an array of strings (got ${describe(value)}).`);
|
|
123
|
+
}
|
|
124
|
+
return value.map((item, index) => {
|
|
125
|
+
if (typeof item !== 'string' || item.trim() === '') {
|
|
126
|
+
throw new errors_1.InvalidRequestError(`${name}[${index}] must be a non-empty string (got ${describe(item)}).`);
|
|
127
|
+
}
|
|
128
|
+
return item;
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
/** The entry ids a batch operation acts on. Duplicates are dropped; order is kept. */
|
|
132
|
+
function entryIdList(value, name = 'entryIds') {
|
|
133
|
+
if (!Array.isArray(value)) {
|
|
134
|
+
throw new errors_1.InvalidRequestError(`${name} must be an array of entry ids (got ${describe(value)}).`);
|
|
135
|
+
}
|
|
136
|
+
return [...new Set(value.map((id, index) => requiredText(id, `${name}[${index}]`)))];
|
|
137
|
+
}
|
|
138
|
+
/** Where an email lives, from an id or anything carrying a locator. */
|
|
139
|
+
function emailLocator(email, name = 'email') {
|
|
140
|
+
if (typeof email === 'string')
|
|
141
|
+
return { entryId: requiredText(email, name) };
|
|
142
|
+
if (email && typeof email === 'object') {
|
|
143
|
+
const entryId = requiredText(email.entryId, `${name}.entryId`);
|
|
144
|
+
const storeId = optionalText(email.storeId, `${name}.storeId`);
|
|
145
|
+
return storeId ? { entryId, storeId } : { entryId };
|
|
146
|
+
}
|
|
147
|
+
throw new errors_1.InvalidRequestError(`${name} must be an entry id or an object with an entryId (got ${describe(email)}).`);
|
|
148
|
+
}
|
|
149
|
+
/** A string→string map, e.g. template placeholders. */
|
|
150
|
+
function stringRecord(value, name) {
|
|
151
|
+
if (value === undefined || value === null)
|
|
152
|
+
return undefined;
|
|
153
|
+
if (typeof value !== 'object' || Array.isArray(value)) {
|
|
154
|
+
throw new errors_1.InvalidRequestError(`${name} must be an object of strings (got ${describe(value)}).`);
|
|
155
|
+
}
|
|
156
|
+
const out = {};
|
|
157
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
158
|
+
if (typeof entry !== 'string') {
|
|
159
|
+
throw new errors_1.InvalidRequestError(`${name}.${key} must be a string (got ${describe(entry)}).`);
|
|
160
|
+
}
|
|
161
|
+
out[key] = entry;
|
|
162
|
+
}
|
|
163
|
+
return out;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Split a recipient list into its entries.
|
|
167
|
+
*
|
|
168
|
+
* Separators are commas and semicolons — but not inside a quoted display name
|
|
169
|
+
* or between angle brackets, where `"Doe, Jo" <jo@example.com>` has a comma
|
|
170
|
+
* that belongs to the name.
|
|
171
|
+
*/
|
|
172
|
+
function recipientList(value, name) {
|
|
173
|
+
if (value === undefined || value === null)
|
|
174
|
+
return [];
|
|
175
|
+
const parts = Array.isArray(value) ? value : [value];
|
|
176
|
+
const out = [];
|
|
177
|
+
parts.forEach((part, index) => {
|
|
178
|
+
if (typeof part !== 'string') {
|
|
179
|
+
throw new errors_1.InvalidRequestError(`${name}[${index}] must be a string (got ${describe(part)}).`);
|
|
180
|
+
}
|
|
181
|
+
out.push(...splitAddressList(part));
|
|
182
|
+
});
|
|
183
|
+
return out;
|
|
184
|
+
}
|
|
185
|
+
function splitAddressList(text) {
|
|
186
|
+
const entries = [];
|
|
187
|
+
let current = '';
|
|
188
|
+
let quoted = false;
|
|
189
|
+
let angled = false;
|
|
190
|
+
for (const ch of text) {
|
|
191
|
+
if (ch === '"' && !angled)
|
|
192
|
+
quoted = !quoted;
|
|
193
|
+
else if (ch === '<' && !quoted)
|
|
194
|
+
angled = true;
|
|
195
|
+
else if (ch === '>' && !quoted)
|
|
196
|
+
angled = false;
|
|
197
|
+
if ((ch === ',' || ch === ';') && !quoted && !angled) {
|
|
198
|
+
entries.push(current);
|
|
199
|
+
current = '';
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
current += ch;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
entries.push(current);
|
|
206
|
+
return entries.map(entry => entry.trim()).filter(Boolean);
|
|
207
|
+
}
|
|
208
|
+
/** `"Doe, Jo" <jo@example.com>` → name + address; a bare address has no name. */
|
|
209
|
+
function parseRecipient(entry) {
|
|
210
|
+
const angled = /^(.*)<([^<>]+)>\s*$/.exec(entry);
|
|
211
|
+
if (!angled)
|
|
212
|
+
return { name: '', address: entry.trim() };
|
|
213
|
+
const name = angled[1].trim().replace(/^"(.*)"$/, '$1').trim();
|
|
214
|
+
return { name, address: angled[2].trim() };
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=args.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"args.js","sourceRoot":"","sources":["../../src/shared/args.ts"],"names":[],"mappings":"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","sourcesContent":["// Checking and normalizing what a caller passes, once, before either platform\n// sees it.\n//\n// This is a security boundary as much as a convenience. Arguments end up\n// inside generated scripts, and TypeScript's types vanish at run time: a\n// JavaScript caller, or a tool layer passing JSON through, can hand a string\n// where a count belongs. So every value is checked for its type here, every\n// count is made a whole number within its documented range, and the platform\n// code only ever receives values of the shape its script templates assume.\nimport { InvalidRequestError } from '../errors';\nimport type { EmailLocator, EmailRef, Recipients } from '../types';\n\nfunction describe(value: unknown): string {\n if (value === null) return 'null';\n if (Array.isArray(value)) return 'an array';\n return typeof value === 'string' ? `'${value}'` : typeof value;\n}\n\n/** A required parameters object. */\nexport function requireObject<T extends object>(value: T | undefined | null, name: string): Partial<T> {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n throw new InvalidRequestError(`${name} must be an object (got ${describe(value)}).`);\n }\n return value;\n}\n\n/** An optional options object; undefined and null are an empty one. */\nexport function optionsObject<T extends object>(value: T | undefined | null, name = 'options'): Partial<T> {\n if (value === undefined || value === null) return {};\n return requireObject(value, name);\n}\n\n/** A required string, trimmed. Blank is an error. */\nexport function requiredText(value: unknown, name: string): string {\n if (typeof value !== 'string' || value.trim() === '') {\n throw new InvalidRequestError(`${name} is required and must be a non-empty string (got ${describe(value)}).`);\n }\n return value.trim();\n}\n\n/** A required string kept exactly as given — content, not an identifier. May be empty. */\nexport function requiredContent(value: unknown, name: string): string {\n if (typeof value !== 'string') {\n throw new InvalidRequestError(`${name} must be a string (got ${describe(value)}).`);\n }\n return value;\n}\n\n/** Optional content kept exactly as given; undefined and null mean \"not given\". */\nexport function optionalContent(value: unknown, name: string): string | undefined {\n if (value === undefined || value === null) return undefined;\n return requiredContent(value, name);\n}\n\n/** An optional regular expression. */\nexport function optionalRegExp(value: unknown, name: string): RegExp | undefined {\n if (value === undefined || value === null) return undefined;\n // Checked by tag rather than instanceof, so a RegExp from another realm passes.\n if (Object.prototype.toString.call(value) !== '[object RegExp]') {\n throw new InvalidRequestError(`${name} must be a RegExp (got ${describe(value)}).`);\n }\n return value as RegExp;\n}\n\n/** An optional string, trimmed; undefined, null and blank all mean \"not given\". */\nexport function optionalText(value: unknown, name: string): string | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value !== 'string') {\n throw new InvalidRequestError(`${name} must be a string (got ${describe(value)}).`);\n }\n return value.trim() || undefined;\n}\n\n/** An optional flag; undefined and null take the fallback. */\nexport function flag(value: unknown, name: string, fallback: boolean): boolean {\n if (value === undefined || value === null) return fallback;\n if (typeof value !== 'boolean') {\n throw new InvalidRequestError(`${name} must be true or false (got ${describe(value)}).`);\n }\n return value;\n}\n\n/**\n * An optional count: undefined and null take the fallback, a fraction is\n * rounded down, and the result is clamped into [min, max] — the documented\n * range is a guard, not a trap. Anything that isn't a finite number is an error.\n */\nexport function count(value: unknown, name: string, fallback: number, min: number, max: number): number {\n if (value === undefined || value === null) return fallback;\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw new InvalidRequestError(`${name} must be a finite number (got ${describe(value)}).`);\n }\n return Math.min(max, Math.max(min, Math.floor(value)));\n}\n\n/** A list of strings, each non-blank and trimmed. Omitted is an empty list. */\nexport function textList(value: unknown, name: string): string[] {\n if (value === undefined || value === null) return [];\n if (!Array.isArray(value)) {\n throw new InvalidRequestError(`${name} must be an array of strings (got ${describe(value)}).`);\n }\n return value.map((item, index) => requiredText(item, `${name}[${index}]`));\n}\n\n/** A list of names matched exactly as given — attachment file names, say. */\nexport function nameList(value: unknown, name: string): string[] {\n if (!Array.isArray(value)) {\n throw new InvalidRequestError(`${name} must be an array of strings (got ${describe(value)}).`);\n }\n return value.map((item, index) => {\n if (typeof item !== 'string' || item.trim() === '') {\n throw new InvalidRequestError(`${name}[${index}] must be a non-empty string (got ${describe(item)}).`);\n }\n return item;\n });\n}\n\n/** The entry ids a batch operation acts on. Duplicates are dropped; order is kept. */\nexport function entryIdList(value: unknown, name = 'entryIds'): string[] {\n if (!Array.isArray(value)) {\n throw new InvalidRequestError(`${name} must be an array of entry ids (got ${describe(value)}).`);\n }\n return [...new Set(value.map((id, index) => requiredText(id, `${name}[${index}]`)))];\n}\n\n/** Where an email lives, from an id or anything carrying a locator. */\nexport function emailLocator(email: EmailRef | undefined | null, name = 'email'): EmailLocator {\n if (typeof email === 'string') return { entryId: requiredText(email, name) };\n if (email && typeof email === 'object') {\n const entryId = requiredText(email.entryId, `${name}.entryId`);\n const storeId = optionalText(email.storeId, `${name}.storeId`);\n return storeId ? { entryId, storeId } : { entryId };\n }\n throw new InvalidRequestError(`${name} must be an entry id or an object with an entryId (got ${describe(email)}).`);\n}\n\n/** A string→string map, e.g. template placeholders. */\nexport function stringRecord(value: unknown, name: string): Record<string, string> | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value !== 'object' || Array.isArray(value)) {\n throw new InvalidRequestError(`${name} must be an object of strings (got ${describe(value)}).`);\n }\n const out: Record<string, string> = {};\n for (const [key, entry] of Object.entries(value)) {\n if (typeof entry !== 'string') {\n throw new InvalidRequestError(`${name}.${key} must be a string (got ${describe(entry)}).`);\n }\n out[key] = entry;\n }\n return out;\n}\n\n/**\n * Split a recipient list into its entries.\n *\n * Separators are commas and semicolons — but not inside a quoted display name\n * or between angle brackets, where `\"Doe, Jo\" <jo@example.com>` has a comma\n * that belongs to the name.\n */\nexport function recipientList(value: Recipients | undefined | null, name: string): string[] {\n if (value === undefined || value === null) return [];\n const parts = Array.isArray(value) ? value : [value];\n const out: string[] = [];\n parts.forEach((part, index) => {\n if (typeof part !== 'string') {\n throw new InvalidRequestError(`${name}[${index}] must be a string (got ${describe(part)}).`);\n }\n out.push(...splitAddressList(part));\n });\n return out;\n}\n\nfunction splitAddressList(text: string): string[] {\n const entries: string[] = [];\n let current = '';\n let quoted = false;\n let angled = false;\n for (const ch of text) {\n if (ch === '\"' && !angled) quoted = !quoted;\n else if (ch === '<' && !quoted) angled = true;\n else if (ch === '>' && !quoted) angled = false;\n if ((ch === ',' || ch === ';') && !quoted && !angled) {\n entries.push(current);\n current = '';\n } else {\n current += ch;\n }\n }\n entries.push(current);\n return entries.map(entry => entry.trim()).filter(Boolean);\n}\n\n/** One recipient entry as a display name and an address. */\nexport interface ParsedRecipient {\n name: string;\n address: string;\n}\n\n/** `\"Doe, Jo\" <jo@example.com>` → name + address; a bare address has no name. */\nexport function parseRecipient(entry: string): ParsedRecipient {\n const angled = /^(.*)<([^<>]+)>\\s*$/.exec(entry);\n if (!angled) return { name: '', address: entry.trim() };\n const name = angled[1].trim().replace(/^\"(.*)\"$/, '$1').trim();\n return { name, address: angled[2].trim() };\n}\n"]}
|
|
@@ -1,32 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* likely to have substituted.
|
|
6
|
-
*/
|
|
7
|
-
export declare function normalizeAttachmentName(name: string): string;
|
|
8
|
-
/**
|
|
9
|
-
* Index of the attachment matching `wanted`, or -1.
|
|
10
|
-
*
|
|
11
|
-
* Exact first, then whitespace-normalized and case-insensitive — in that order,
|
|
12
|
-
* so an email carrying two names differing only in case still resolves the one
|
|
13
|
-
* that was actually asked for.
|
|
2
|
+
* Index of the attachment matching `wanted`, or -1: exact first, then
|
|
3
|
+
* whitespace-normalized and case-insensitive — in that order, so two names that
|
|
4
|
+
* differ only in case still resolve the one asked for.
|
|
14
5
|
*/
|
|
15
6
|
export declare function findAttachmentIndex(names: readonly string[], wanted: string): number;
|
|
16
7
|
/**
|
|
17
|
-
* The "attachment not found" message, worded once for both platforms.
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* build the identical sentence: it passes PowerShell interpolations
|
|
21
|
-
* (`$($item.Subject)`) where macOS passes the values it read.
|
|
8
|
+
* The "attachment not found" message, worded once for both platforms. Fields
|
|
9
|
+
* arrive as strings so the Windows script can build the same sentence from
|
|
10
|
+
* PowerShell variables where macOS passes the values it read.
|
|
22
11
|
*/
|
|
23
12
|
export declare function attachmentNotFoundMessage(init: {
|
|
24
13
|
fileName: string;
|
|
25
14
|
senderEmail: string;
|
|
26
15
|
subject: string;
|
|
27
16
|
receivedTime: string;
|
|
28
|
-
/** The attachments the email does carry,
|
|
17
|
+
/** The attachments the email does carry, joined for display. */
|
|
29
18
|
present: string;
|
|
30
|
-
/** What this platform calls the id, for the closing advice. */
|
|
31
|
-
idLabel: string;
|
|
32
19
|
}): string;
|
|
20
|
+
/** Characters no file name may carry on Windows (a superset of macOS's). */
|
|
21
|
+
export declare const UNSAFE_FILE_NAME_CHARS = "[<>:\"/\\\\|?*\\x00-\\x1F]";
|
|
22
|
+
/** Names Windows reserves for devices, with or without an extension. */
|
|
23
|
+
export declare const RESERVED_FILE_NAME = "^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\\..*)?$";
|
|
24
|
+
/** How long a saved name's stem may run before it is cut. */
|
|
25
|
+
export declare const MAX_FILE_STEM = 150;
|
|
26
|
+
/**
|
|
27
|
+
* A sender-chosen attachment name made safe to write: no path separators or
|
|
28
|
+
* characters a file system rejects, no trailing dots or spaces, no reserved
|
|
29
|
+
* device name, and a stem short enough to stay clear of path-length limits. A
|
|
30
|
+
* name like `..\..\x.exe` therefore lands inside the destination as
|
|
31
|
+
* `.._.._x.exe`, never outside it.
|
|
32
|
+
*/
|
|
33
|
+
export declare function safeFileName(name: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* A path in `dir` for `name` that `exists` reports free: the name itself, else
|
|
36
|
+
* `name (1).ext`, `name (2).ext`, … Existing files are never overwritten.
|
|
37
|
+
*/
|
|
38
|
+
export declare function uniqueSavePath(dir: string, name: string, exists: (file: string) => boolean): string;
|