@aloth/olcli 0.11.0 → 0.13.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.
@@ -0,0 +1,165 @@
1
+ /**
2
+ * `latexdiff` integration for `olcli diff`.
3
+ *
4
+ * `diff` already computes both sides of the comparison - the project as it is
5
+ * on Overleaf right now, and the working directory - and prints them as
6
+ * unified patches. That is the right artifact for a developer and the wrong
7
+ * one for a thesis advisor, who expects the revision marked up in the document
8
+ * itself. `latexdiff` produces that, and the only parts a user cannot do by
9
+ * hand are getting the remote side onto disk and compiling the result without
10
+ * a local TeX installation.
11
+ *
12
+ * Everything decidable from data is decided here as a pure function so it can
13
+ * be unit-tested with no Overleaf account and no `latexdiff` binary; the spawn
14
+ * and the upload/compile/delete sequence stay in `cli.ts` with the rest of the
15
+ * IO. Same split as `diff.ts`.
16
+ *
17
+ * Orientation matches `diff.ts` and is not negotiable: **old is the remote,
18
+ * new is local**. Struck-through text in the markup is content a subsequent
19
+ * push would overwrite, underlined text is content it would upload.
20
+ */
21
+ /** Binary looked up on PATH. Not configurable; `latexdiff` is its only name. */
22
+ export declare const LATEXDIFF_BIN = "latexdiff";
23
+ /**
24
+ * Directory the marked-up files are written to, relative to the target
25
+ * directory.
26
+ *
27
+ * Dotted on purpose. `scanLocalFiles` skips dotted entries unconditionally,
28
+ * before any ignore rule is consulted, so this is the one location whose
29
+ * contents cannot leak into a later `push`. A plain `main-diff.tex` next to
30
+ * the document would be uploaded to Overleaf on the next sync.
31
+ */
32
+ export declare const DIFF_OUTPUT_DIR = ".olcli-diff";
33
+ /**
34
+ * Name of the file `--pdf` uploads to the project for the duration of one
35
+ * compile.
36
+ *
37
+ * Fixed rather than randomized: a name a user can recognize and delete by hand
38
+ * is worth more than one that never collides, and a collision is refused
39
+ * rather than resolved anyway.
40
+ */
41
+ export declare const REMOTE_SCRATCH_NAME = "olcli-latexdiff.tex";
42
+ /**
43
+ * True when the file declares a document class outside of a comment.
44
+ *
45
+ * This is what separates a root document from the chapters it inputs. A
46
+ * commented-out `\documentclass` is common in subfiles - people leave the
47
+ * standalone preamble behind when they split a document up - and counting it
48
+ * would report every chapter as a candidate root.
49
+ */
50
+ export declare function declaresDocumentClass(content: Buffer): boolean;
51
+ export declare function isTexPath(path: string): boolean;
52
+ /**
53
+ * Every `.tex` file in the tree that declares a document class, sorted by path
54
+ * so the list a user is shown is stable across runs.
55
+ */
56
+ export declare function findRootCandidates(files: Map<string, Buffer>): string[];
57
+ /** Raised when the root document cannot be determined; carries the advice. */
58
+ export declare class RootDocumentError extends Error {
59
+ readonly candidates: string[];
60
+ constructor(message: string, candidates?: string[]);
61
+ }
62
+ /**
63
+ * Decide which document to mark up.
64
+ *
65
+ * An explicit `--main` always wins, including for layouts this heuristic
66
+ * cannot read. Otherwise the document class is the signal, and ambiguity is
67
+ * reported rather than guessed at: a project holding both `paper.tex` and
68
+ * `poster.tex` has no correct default, and marking up the wrong one produces a
69
+ * plausible PDF describing the wrong revision - the failure that is hardest to
70
+ * notice.
71
+ *
72
+ * @param files The local tree, already filtered the same way `diff` filters it.
73
+ */
74
+ export declare function resolveRootDocument(files: Map<string, Buffer>, explicit?: string): string;
75
+ export interface LatexdiffArgsOptions {
76
+ /**
77
+ * Inline `\input` and `\include` before comparing. On by default; see
78
+ * `buildLatexdiffArgs`.
79
+ */
80
+ flatten?: boolean;
81
+ /** Verbatim `latexdiff` options from `--latexdiff-opt`, passed through. */
82
+ extra?: string[];
83
+ }
84
+ /**
85
+ * Build the `latexdiff` argument list.
86
+ *
87
+ * `--flatten` is the default, and the reason is the remote compile rather than
88
+ * taste. Without it the marked-up file still contains `\input{sections/intro}`,
89
+ * and compiling it on Overleaf resolves those against the files sitting in the
90
+ * project - the old content - so a multi-file thesis would produce a PDF that
91
+ * marks up the root document and silently shows every input file as unchanged.
92
+ * `--no-flatten` is there for anyone who wants the raw single-file markup.
93
+ *
94
+ * Extra options come before the file arguments because `latexdiff` reads the
95
+ * last two positional arguments as old and new.
96
+ */
97
+ export declare function buildLatexdiffArgs(oldPath: string, newPath: string, options?: LatexdiffArgsOptions): string[];
98
+ /**
99
+ * Where a marked-up artifact is written, relative to the target directory.
100
+ *
101
+ * Flat rather than mirroring the root document's folder: the output of a
102
+ * flattened run is a single self-contained file, and burying it under
103
+ * `.olcli-diff/chapters/` would only make it harder to find.
104
+ */
105
+ export declare function diffOutputPath(rootPath: string, extension: string): string;
106
+ /**
107
+ * Project path `--pdf` uploads the marked-up file to.
108
+ *
109
+ * Placed next to the root document rather than at the project root, so that
110
+ * relative `\includegraphics` and `\bibliography` paths in the flattened file
111
+ * resolve exactly as they do for the document it was built from.
112
+ */
113
+ export declare function remoteScratchPath(rootPath: string): string;
114
+ /**
115
+ * Write a file tree to a directory, for use as `latexdiff`'s old side.
116
+ *
117
+ * `latexdiff --flatten` resolves `\input` relative to each side's own file, so
118
+ * the remote tree has to exist on disk with its folder structure intact -
119
+ * handing it just the root document would break every input in a multi-file
120
+ * project.
121
+ *
122
+ * Entry names are re-checked against the destination even though `diff` has
123
+ * already filtered them against the target directory: this writes files, and a
124
+ * path that was safe relative to one base is not automatically safe relative
125
+ * to another. See #44.
126
+ */
127
+ export declare function materializeTree(destination: string, files: Map<string, Buffer>): number;
128
+ export interface LatexdiffResult {
129
+ /** The marked-up document, as printed to stdout. */
130
+ markup: string;
131
+ /** Warnings `latexdiff` printed while succeeding. */
132
+ stderr: string;
133
+ }
134
+ /**
135
+ * Raised when `latexdiff` could not be run or exited non-zero. `missing`
136
+ * separates "not installed" - a setup problem with a known fix - from a real
137
+ * failure on the documents, which needs the tool's own message.
138
+ */
139
+ export declare class LatexdiffError extends Error {
140
+ readonly missing: boolean;
141
+ readonly stderr: string;
142
+ constructor(message: string, missing: boolean, stderr?: string);
143
+ }
144
+ /**
145
+ * Run `latexdiff`, returning its stdout.
146
+ *
147
+ * `execFile`, never a shell: the arguments include user-supplied paths and
148
+ * pass-through options, and an argv array cannot be talked into running
149
+ * something else.
150
+ *
151
+ * The default `maxBuffer` is 1 MB, which a flattened thesis passes without
152
+ * trying. Exceeding it truncates stdout and reports it as an error, which
153
+ * would read as "latexdiff failed" on exactly the documents this feature
154
+ * exists for.
155
+ */
156
+ export declare function runLatexdiff(args: string[]): Promise<LatexdiffResult>;
157
+ /**
158
+ * Install advice for a missing binary, by platform.
159
+ *
160
+ * `latexdiff` ships with TeX Live and MacTeX, so on most machines the answer
161
+ * is that the whole distribution is missing rather than this one tool - which
162
+ * is also the case where `--pdf` is most useful, since it needs no local TeX.
163
+ */
164
+ export declare function latexdiffInstallHint(platform?: string): string;
165
+ //# sourceMappingURL=latexdiff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"latexdiff.d.ts","sourceRoot":"","sources":["../src/latexdiff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAUH,gFAAgF;AAChF,eAAO,MAAM,aAAa,cAAc,CAAC;AAEzC;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,gBAAgB,CAAC;AAE7C;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,wBAAwB,CAAC;AAsBzD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAK9D;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAG/C;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,EAAE,CAKvE;AAED,8EAA8E;AAC9E,qBAAa,iBAAkB,SAAQ,KAAK;IACb,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE;gBAA9C,OAAO,EAAE,MAAM,EAAW,UAAU,GAAE,MAAM,EAAO;CAIhE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CA4BzF;AAED,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,2EAA2E;IAC3E,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,MAAM,EAAE,CAMV;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAG1E;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAUvF;AAED,MAAM,WAAW,eAAe;IAC9B,oDAAoD;IACpD,MAAM,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,qBAAa,cAAe,SAAQ,KAAK;IACV,QAAQ,CAAC,OAAO,EAAE,OAAO;IAAE,QAAQ,CAAC,MAAM;gBAA3D,OAAO,EAAE,MAAM,EAAW,OAAO,EAAE,OAAO,EAAW,MAAM,SAAK;CAI7E;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,eAAe,CAAC,CAsB3E;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,GAAE,MAAyB,GAAG,MAAM,CAShF"}
@@ -0,0 +1,269 @@
1
+ /**
2
+ * `latexdiff` integration for `olcli diff`.
3
+ *
4
+ * `diff` already computes both sides of the comparison - the project as it is
5
+ * on Overleaf right now, and the working directory - and prints them as
6
+ * unified patches. That is the right artifact for a developer and the wrong
7
+ * one for a thesis advisor, who expects the revision marked up in the document
8
+ * itself. `latexdiff` produces that, and the only parts a user cannot do by
9
+ * hand are getting the remote side onto disk and compiling the result without
10
+ * a local TeX installation.
11
+ *
12
+ * Everything decidable from data is decided here as a pure function so it can
13
+ * be unit-tested with no Overleaf account and no `latexdiff` binary; the spawn
14
+ * and the upload/compile/delete sequence stay in `cli.ts` with the rest of the
15
+ * IO. Same split as `diff.ts`.
16
+ *
17
+ * Orientation matches `diff.ts` and is not negotiable: **old is the remote,
18
+ * new is local**. Struck-through text in the markup is content a subsequent
19
+ * push would overwrite, underlined text is content it would upload.
20
+ */
21
+ import { execFile } from 'node:child_process';
22
+ import { promisify } from 'node:util';
23
+ import { mkdirSync, writeFileSync } from 'node:fs';
24
+ import { dirname } from 'node:path';
25
+ import { resolveWithin } from './paths.js';
26
+ const execFileAsync = promisify(execFile);
27
+ /** Binary looked up on PATH. Not configurable; `latexdiff` is its only name. */
28
+ export const LATEXDIFF_BIN = 'latexdiff';
29
+ /**
30
+ * Directory the marked-up files are written to, relative to the target
31
+ * directory.
32
+ *
33
+ * Dotted on purpose. `scanLocalFiles` skips dotted entries unconditionally,
34
+ * before any ignore rule is consulted, so this is the one location whose
35
+ * contents cannot leak into a later `push`. A plain `main-diff.tex` next to
36
+ * the document would be uploaded to Overleaf on the next sync.
37
+ */
38
+ export const DIFF_OUTPUT_DIR = '.olcli-diff';
39
+ /**
40
+ * Name of the file `--pdf` uploads to the project for the duration of one
41
+ * compile.
42
+ *
43
+ * Fixed rather than randomized: a name a user can recognize and delete by hand
44
+ * is worth more than one that never collides, and a collision is refused
45
+ * rather than resolved anyway.
46
+ */
47
+ export const REMOTE_SCRATCH_NAME = 'olcli-latexdiff.tex';
48
+ /** Extensions treated as LaTeX source when looking for the root document. */
49
+ const TEX_EXTENSIONS = ['.tex', '.ltx'];
50
+ /**
51
+ * Drop the comment part of a single line of TeX.
52
+ *
53
+ * A `%` starts a comment unless it is escaped, and `\\%` is an escaped
54
+ * backslash followed by a comment, so the run of backslashes before the `%`
55
+ * has to be counted rather than just the character in front of it.
56
+ */
57
+ function stripTexComment(line) {
58
+ for (let i = 0; i < line.length; i++) {
59
+ if (line[i] !== '%')
60
+ continue;
61
+ let backslashes = 0;
62
+ for (let j = i - 1; j >= 0 && line[j] === '\\'; j--)
63
+ backslashes++;
64
+ if (backslashes % 2 === 0)
65
+ return line.slice(0, i);
66
+ }
67
+ return line;
68
+ }
69
+ /**
70
+ * True when the file declares a document class outside of a comment.
71
+ *
72
+ * This is what separates a root document from the chapters it inputs. A
73
+ * commented-out `\documentclass` is common in subfiles - people leave the
74
+ * standalone preamble behind when they split a document up - and counting it
75
+ * would report every chapter as a candidate root.
76
+ */
77
+ export function declaresDocumentClass(content) {
78
+ const text = content.toString('utf-8');
79
+ return text
80
+ .split('\n')
81
+ .some((line) => /\\documentclass\s*[[{]/.test(stripTexComment(line)));
82
+ }
83
+ export function isTexPath(path) {
84
+ const lower = path.toLowerCase();
85
+ return TEX_EXTENSIONS.some((ext) => lower.endsWith(ext));
86
+ }
87
+ /**
88
+ * Every `.tex` file in the tree that declares a document class, sorted by path
89
+ * so the list a user is shown is stable across runs.
90
+ */
91
+ export function findRootCandidates(files) {
92
+ return [...files.keys()]
93
+ .filter((path) => isTexPath(path))
94
+ .filter((path) => declaresDocumentClass(files.get(path)))
95
+ .sort();
96
+ }
97
+ /** Raised when the root document cannot be determined; carries the advice. */
98
+ export class RootDocumentError extends Error {
99
+ candidates;
100
+ constructor(message, candidates = []) {
101
+ super(message);
102
+ this.candidates = candidates;
103
+ this.name = 'RootDocumentError';
104
+ }
105
+ }
106
+ /**
107
+ * Decide which document to mark up.
108
+ *
109
+ * An explicit `--main` always wins, including for layouts this heuristic
110
+ * cannot read. Otherwise the document class is the signal, and ambiguity is
111
+ * reported rather than guessed at: a project holding both `paper.tex` and
112
+ * `poster.tex` has no correct default, and marking up the wrong one produces a
113
+ * plausible PDF describing the wrong revision - the failure that is hardest to
114
+ * notice.
115
+ *
116
+ * @param files The local tree, already filtered the same way `diff` filters it.
117
+ */
118
+ export function resolveRootDocument(files, explicit) {
119
+ if (explicit) {
120
+ if (!files.has(explicit)) {
121
+ throw new RootDocumentError(`Root document not found locally: ${explicit}\n` +
122
+ ' It must be a file in the target directory that diff also reports ' +
123
+ '(an ignore rule can hide it).');
124
+ }
125
+ return explicit;
126
+ }
127
+ const candidates = findRootCandidates(files);
128
+ if (candidates.length === 1)
129
+ return candidates[0];
130
+ if (candidates.length === 0) {
131
+ throw new RootDocumentError('No .tex file declaring \\documentclass was found, so there is no root document to mark up.\n' +
132
+ ' Pass --main <path> to name one.');
133
+ }
134
+ throw new RootDocumentError(`${candidates.length} files declare \\documentclass, so the root document is ambiguous.\n` +
135
+ ' Pass --main <path> to choose one.', candidates);
136
+ }
137
+ /**
138
+ * Build the `latexdiff` argument list.
139
+ *
140
+ * `--flatten` is the default, and the reason is the remote compile rather than
141
+ * taste. Without it the marked-up file still contains `\input{sections/intro}`,
142
+ * and compiling it on Overleaf resolves those against the files sitting in the
143
+ * project - the old content - so a multi-file thesis would produce a PDF that
144
+ * marks up the root document and silently shows every input file as unchanged.
145
+ * `--no-flatten` is there for anyone who wants the raw single-file markup.
146
+ *
147
+ * Extra options come before the file arguments because `latexdiff` reads the
148
+ * last two positional arguments as old and new.
149
+ */
150
+ export function buildLatexdiffArgs(oldPath, newPath, options = {}) {
151
+ const args = [];
152
+ if (options.flatten !== false)
153
+ args.push('--flatten');
154
+ args.push(...(options.extra ?? []));
155
+ args.push(oldPath, newPath);
156
+ return args;
157
+ }
158
+ /**
159
+ * Where a marked-up artifact is written, relative to the target directory.
160
+ *
161
+ * Flat rather than mirroring the root document's folder: the output of a
162
+ * flattened run is a single self-contained file, and burying it under
163
+ * `.olcli-diff/chapters/` would only make it harder to find.
164
+ */
165
+ export function diffOutputPath(rootPath, extension) {
166
+ const base = rootPath.split('/').pop().replace(/\.(tex|ltx)$/i, '');
167
+ return `${DIFF_OUTPUT_DIR}/${base}-diff.${extension}`;
168
+ }
169
+ /**
170
+ * Project path `--pdf` uploads the marked-up file to.
171
+ *
172
+ * Placed next to the root document rather than at the project root, so that
173
+ * relative `\includegraphics` and `\bibliography` paths in the flattened file
174
+ * resolve exactly as they do for the document it was built from.
175
+ */
176
+ export function remoteScratchPath(rootPath) {
177
+ const folder = rootPath.includes('/') ? rootPath.slice(0, rootPath.lastIndexOf('/')) : '';
178
+ return folder ? `${folder}/${REMOTE_SCRATCH_NAME}` : REMOTE_SCRATCH_NAME;
179
+ }
180
+ /**
181
+ * Write a file tree to a directory, for use as `latexdiff`'s old side.
182
+ *
183
+ * `latexdiff --flatten` resolves `\input` relative to each side's own file, so
184
+ * the remote tree has to exist on disk with its folder structure intact -
185
+ * handing it just the root document would break every input in a multi-file
186
+ * project.
187
+ *
188
+ * Entry names are re-checked against the destination even though `diff` has
189
+ * already filtered them against the target directory: this writes files, and a
190
+ * path that was safe relative to one base is not automatically safe relative
191
+ * to another. See #44.
192
+ */
193
+ export function materializeTree(destination, files) {
194
+ let written = 0;
195
+ for (const [path, data] of files) {
196
+ const target = resolveWithin(destination, path);
197
+ if (!target)
198
+ continue;
199
+ mkdirSync(dirname(target), { recursive: true });
200
+ writeFileSync(target, data);
201
+ written++;
202
+ }
203
+ return written;
204
+ }
205
+ /**
206
+ * Raised when `latexdiff` could not be run or exited non-zero. `missing`
207
+ * separates "not installed" - a setup problem with a known fix - from a real
208
+ * failure on the documents, which needs the tool's own message.
209
+ */
210
+ export class LatexdiffError extends Error {
211
+ missing;
212
+ stderr;
213
+ constructor(message, missing, stderr = '') {
214
+ super(message);
215
+ this.missing = missing;
216
+ this.stderr = stderr;
217
+ this.name = 'LatexdiffError';
218
+ }
219
+ }
220
+ /**
221
+ * Run `latexdiff`, returning its stdout.
222
+ *
223
+ * `execFile`, never a shell: the arguments include user-supplied paths and
224
+ * pass-through options, and an argv array cannot be talked into running
225
+ * something else.
226
+ *
227
+ * The default `maxBuffer` is 1 MB, which a flattened thesis passes without
228
+ * trying. Exceeding it truncates stdout and reports it as an error, which
229
+ * would read as "latexdiff failed" on exactly the documents this feature
230
+ * exists for.
231
+ */
232
+ export async function runLatexdiff(args) {
233
+ try {
234
+ const { stdout, stderr } = await execFileAsync(LATEXDIFF_BIN, args, {
235
+ encoding: 'utf-8',
236
+ maxBuffer: 256 * 1024 * 1024,
237
+ });
238
+ return { markup: stdout, stderr };
239
+ }
240
+ catch (error) {
241
+ // execFile rejects with the spawn error for a missing binary and with the
242
+ // captured streams for a non-zero exit; `code` carries the errno in the
243
+ // first case and the exit status in the second.
244
+ const failure = error;
245
+ if (failure.code === 'ENOENT') {
246
+ throw new LatexdiffError(`${LATEXDIFF_BIN} was not found on PATH`, true);
247
+ }
248
+ const stderr = typeof failure.stderr === 'string' ? failure.stderr.trim() : '';
249
+ throw new LatexdiffError(`${LATEXDIFF_BIN} exited with status ${failure.code ?? 'unknown'}`, false, stderr);
250
+ }
251
+ }
252
+ /**
253
+ * Install advice for a missing binary, by platform.
254
+ *
255
+ * `latexdiff` ships with TeX Live and MacTeX, so on most machines the answer
256
+ * is that the whole distribution is missing rather than this one tool - which
257
+ * is also the case where `--pdf` is most useful, since it needs no local TeX.
258
+ */
259
+ export function latexdiffInstallHint(platform = process.platform) {
260
+ switch (platform) {
261
+ case 'darwin':
262
+ return 'Install MacTeX (brew install --cask mactex-no-gui) or brew install latexdiff';
263
+ case 'win32':
264
+ return 'Install MiKTeX or TeX Live; both ship latexdiff';
265
+ default:
266
+ return 'Install it from your TeX distribution (apt install latexdiff, or TeX Live)';
267
+ }
268
+ }
269
+ //# sourceMappingURL=latexdiff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"latexdiff.js","sourceRoot":"","sources":["../src/latexdiff.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Interactive terminal prompts.
3
+ *
4
+ * Exists for one reason: reading a password without it appearing on the
5
+ * command line, and therefore in shell history. See issue #50.
6
+ *
7
+ * No dependency for this. A masked read is a raw-mode loop over stdin, and
8
+ * pulling in a prompt library to avoid writing it would cost more than it
9
+ * saves - the bar in this repo is low dependency count, not zero.
10
+ *
11
+ * The keystroke handling is a pure reducer (`applyChunk`) with the terminal
12
+ * wiring wrapped around it, for the same reason `diff.ts` is pure: a raw-mode
13
+ * loop cannot be exercised without a pty, and the part that can silently
14
+ * corrupt a password is the character handling, not the plumbing.
15
+ */
16
+ /** Raised when the user aborts the prompt (Ctrl+C / Ctrl+D). */
17
+ export declare class PromptCancelled extends Error {
18
+ constructor();
19
+ }
20
+ /** Raised when there is no terminal to prompt on. */
21
+ export declare class NotATerminal extends Error {
22
+ constructor(message: string);
23
+ }
24
+ export interface KeyState {
25
+ /** What the user has typed so far. */
26
+ value: string;
27
+ /**
28
+ * Where we are in an ANSI escape sequence.
29
+ *
30
+ * 'none' - ordinary input
31
+ * 'esc' - saw ESC, waiting to see whether a sequence follows
32
+ * 'csi' - inside `ESC [` or `ESC O`, swallowing until the final byte
33
+ */
34
+ escape: 'none' | 'esc' | 'csi';
35
+ outcome: 'pending' | 'submit' | 'cancel';
36
+ }
37
+ export declare function initialKeyState(): KeyState;
38
+ /**
39
+ * Fold one chunk of terminal input into the state.
40
+ *
41
+ * A chunk is not one keystroke: a paste arrives whole, and a single arrow key
42
+ * arrives as the three characters `ESC [ A`. Both have to be handled here.
43
+ *
44
+ * Escape sequences are swallowed rather than filtered character by character.
45
+ * Dropping only the ESC leaves `[` and `A` behind, and both are printable - so
46
+ * an arrow key pressed mid-entry would append "[A" to the password and the
47
+ * user would never see it. That is the failure this state machine exists to
48
+ * prevent; it was found by driving the prompt over a pty.
49
+ */
50
+ export declare function applyChunk(state: KeyState, chunk: string): KeyState;
51
+ /**
52
+ * Read a line from stdin without echoing it.
53
+ *
54
+ * Nothing is written back as the user types - not even asterisks. `sudo`
55
+ * behaves the same way, and echoing one character per keystroke leaks the
56
+ * length of the secret to anyone looking at the screen.
57
+ *
58
+ * Throws `NotATerminal` when stdin is piped or redirected. A masked read is
59
+ * impossible there, and silently falling back to an unmasked one would defeat
60
+ * the point; the caller is expected to name the scripted alternative instead.
61
+ */
62
+ export declare function promptHidden(label: string): Promise<string>;
63
+ //# sourceMappingURL=prompt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AASH,gEAAgE;AAChE,qBAAa,eAAgB,SAAQ,KAAK;;CAKzC;AAED,qDAAqD;AACrD,qBAAa,YAAa,SAAQ,KAAK;gBACzB,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,QAAQ;IACvB,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;OAMG;IACH,MAAM,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,CAAC;IAC/B,OAAO,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC;CAC1C;AAED,wBAAgB,eAAe,IAAI,QAAQ,CAE1C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,QAAQ,CA4CnE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA4C3D"}
package/dist/prompt.js ADDED
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Interactive terminal prompts.
3
+ *
4
+ * Exists for one reason: reading a password without it appearing on the
5
+ * command line, and therefore in shell history. See issue #50.
6
+ *
7
+ * No dependency for this. A masked read is a raw-mode loop over stdin, and
8
+ * pulling in a prompt library to avoid writing it would cost more than it
9
+ * saves - the bar in this repo is low dependency count, not zero.
10
+ *
11
+ * The keystroke handling is a pure reducer (`applyChunk`) with the terminal
12
+ * wiring wrapped around it, for the same reason `diff.ts` is pure: a raw-mode
13
+ * loop cannot be exercised without a pty, and the part that can silently
14
+ * corrupt a password is the character handling, not the plumbing.
15
+ */
16
+ import { stdin, stdout } from 'node:process';
17
+ const CTRL_C = '\u0003';
18
+ const CTRL_D = '\u0004';
19
+ const DEL = '\u007f';
20
+ const ESC = '\u001b';
21
+ /** Raised when the user aborts the prompt (Ctrl+C / Ctrl+D). */
22
+ export class PromptCancelled extends Error {
23
+ constructor() {
24
+ super('Cancelled');
25
+ this.name = 'PromptCancelled';
26
+ }
27
+ }
28
+ /** Raised when there is no terminal to prompt on. */
29
+ export class NotATerminal extends Error {
30
+ constructor(message) {
31
+ super(message);
32
+ this.name = 'NotATerminal';
33
+ }
34
+ }
35
+ export function initialKeyState() {
36
+ return { value: '', escape: 'none', outcome: 'pending' };
37
+ }
38
+ /**
39
+ * Fold one chunk of terminal input into the state.
40
+ *
41
+ * A chunk is not one keystroke: a paste arrives whole, and a single arrow key
42
+ * arrives as the three characters `ESC [ A`. Both have to be handled here.
43
+ *
44
+ * Escape sequences are swallowed rather than filtered character by character.
45
+ * Dropping only the ESC leaves `[` and `A` behind, and both are printable - so
46
+ * an arrow key pressed mid-entry would append "[A" to the password and the
47
+ * user would never see it. That is the failure this state machine exists to
48
+ * prevent; it was found by driving the prompt over a pty.
49
+ */
50
+ export function applyChunk(state, chunk) {
51
+ let { value, escape, outcome } = state;
52
+ for (const ch of chunk) {
53
+ if (outcome !== 'pending')
54
+ break;
55
+ if (escape === 'csi') {
56
+ // Parameter and intermediate bytes continue the sequence; a final byte
57
+ // in @..~ ends it. `ESC [ 3 ~` (Delete) needs the parameter handling.
58
+ if (ch >= '@' && ch <= '~')
59
+ escape = 'none';
60
+ continue;
61
+ }
62
+ if (escape === 'esc') {
63
+ // `ESC [` is CSI, `ESC O` is the alternate cursor mode some terminals
64
+ // use for arrows. Anything else was a lone ESC plus a real character.
65
+ escape = ch === '[' || ch === 'O' ? 'csi' : 'none';
66
+ continue;
67
+ }
68
+ switch (ch) {
69
+ case '\r':
70
+ case '\n':
71
+ outcome = 'submit';
72
+ break;
73
+ case CTRL_C:
74
+ case CTRL_D:
75
+ outcome = 'cancel';
76
+ break;
77
+ case DEL:
78
+ case '\b':
79
+ value = value.slice(0, -1);
80
+ break;
81
+ case ESC:
82
+ escape = 'esc';
83
+ break;
84
+ default:
85
+ // Remaining control characters are not password material. Appending
86
+ // them would corrupt the value invisibly.
87
+ if (ch >= ' ')
88
+ value += ch;
89
+ }
90
+ }
91
+ return { value, escape, outcome };
92
+ }
93
+ /**
94
+ * Read a line from stdin without echoing it.
95
+ *
96
+ * Nothing is written back as the user types - not even asterisks. `sudo`
97
+ * behaves the same way, and echoing one character per keystroke leaks the
98
+ * length of the secret to anyone looking at the screen.
99
+ *
100
+ * Throws `NotATerminal` when stdin is piped or redirected. A masked read is
101
+ * impossible there, and silently falling back to an unmasked one would defeat
102
+ * the point; the caller is expected to name the scripted alternative instead.
103
+ */
104
+ export function promptHidden(label) {
105
+ return new Promise((resolve, reject) => {
106
+ if (!stdin.isTTY) {
107
+ reject(new NotATerminal('stdin is not a terminal, so a password cannot be prompted for'));
108
+ return;
109
+ }
110
+ // Restore whatever mode the caller was in rather than assuming cooked:
111
+ // olcli may be driven from a wrapper that set raw mode itself.
112
+ const wasRaw = stdin.isRaw;
113
+ let state = initialKeyState();
114
+ let settled = false;
115
+ const cleanup = () => {
116
+ if (settled)
117
+ return;
118
+ settled = true;
119
+ stdin.removeListener('data', onData);
120
+ stdin.setRawMode(wasRaw);
121
+ stdin.pause();
122
+ stdout.write('\n');
123
+ };
124
+ const onData = (chunk) => {
125
+ state = applyChunk(state, chunk);
126
+ if (state.outcome === 'submit') {
127
+ cleanup();
128
+ resolve(state.value);
129
+ }
130
+ else if (state.outcome === 'cancel') {
131
+ cleanup();
132
+ reject(new PromptCancelled());
133
+ }
134
+ };
135
+ // Raw mode goes on before the label is written. The terminal's line
136
+ // discipline echoes whatever arrives while ECHO is still set, so a caller
137
+ // that answers the instant the prompt appears - anything automated - would
138
+ // otherwise have its first keystrokes echoed to the screen.
139
+ stdin.setRawMode(true);
140
+ stdin.resume();
141
+ stdin.setEncoding('utf8');
142
+ stdin.on('data', onData);
143
+ stdout.write(label);
144
+ });
145
+ }
146
+ //# sourceMappingURL=prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.js","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"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"}