@parall/daemon 1.44.0 → 1.45.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.
@@ -1799,7 +1799,10 @@ export class DaemonSupervisor {
1799
1799
  child.once('error', (err) => {
1800
1800
  if (err.code === 'ENOENT') {
1801
1801
  if (adapter.args.length > 0) {
1802
- this.log.error(`Node runtime "${adapter.bin}" not found is the Desktop app installed?`);
1802
+ // Overlay / entry-sibling resolution: bin is the node executable
1803
+ // and args[0] is the bridge bundle js that just resolved on disk.
1804
+ this.log.error(`Node runtime "${adapter.bin}" not found while launching ${adapter.args[0]} — ` +
1805
+ `reinstall the daemon (npm install -g @parall/daemon) or the Desktop app`);
1803
1806
  }
1804
1807
  else {
1805
1808
  const pkg = RUNTIME_PACKAGES[state.runtimeType] ?? `@parall/${state.runtimeType}-agent`;
@@ -0,0 +1,96 @@
1
+ import { type WinServicePaths } from './win-service.js';
2
+ /**
3
+ * Windows service lifecycle operations (stop / uninstall / probes) with
4
+ * injected command execution and filesystem, so every failure mode —
5
+ * CIM query failure, access denied, kill failure, task-delete failure —
6
+ * unit-tests on any platform. cli.ts binds the real execFileSync/fs.
7
+ *
8
+ * Fail-closed contract (PR #1876 round 2): tracking state (pidfile,
9
+ * daemon-running marker) and service artifacts are removed ONLY after the
10
+ * relevant facts are POSITIVELY confirmed — task disabled/deleted, process
11
+ * exited. Any indeterminate probe (query failed, unreadable identity)
12
+ * keeps the state on disk and surfaces as a problem + non-zero exit;
13
+ * "could not check" is never treated as "gone".
14
+ */
15
+ /** One external command execution; implementations must never throw. */
16
+ export interface WinCmdResult {
17
+ /** Process exit code; null = the command could not be spawned at all. */
18
+ code: number | null;
19
+ stdout: string;
20
+ spawnError?: string;
21
+ }
22
+ export type WinCmdRunner = (file: string, args: string[]) => WinCmdResult;
23
+ export interface WinFsOps {
24
+ /** null on any read error (missing file included). */
25
+ readFile(p: string): string | null;
26
+ /** true when the file is gone afterwards (missing counts); false on failure. */
27
+ unlink(p: string): boolean;
28
+ }
29
+ export interface WinLifecycleDeps {
30
+ run: WinCmdRunner;
31
+ fs: WinFsOps;
32
+ paths: WinServicePaths;
33
+ sleep(ms: number): void;
34
+ now(): number;
35
+ }
36
+ export interface WinOpResult {
37
+ ok: boolean;
38
+ problems: string[];
39
+ }
40
+ /**
41
+ * Three-state process identity:
42
+ * - 'daemon' — pid exists AND is a Node process running our launcher
43
+ * - 'not-daemon' — pid does not exist, or verifiably belongs to something
44
+ * else (our tracked process has therefore exited)
45
+ * - 'indeterminate' — the question could not be answered (query failed, or
46
+ * the process exists but its identity is unreadable) —
47
+ * callers MUST fail closed on this
48
+ */
49
+ export type PidIdentity = 'daemon' | 'not-daemon' | 'indeterminate';
50
+ export declare function probePidIdentity(deps: WinLifecycleDeps, pid: number): PidIdentity;
51
+ export declare function readTrackedPid(deps: WinLifecycleDeps): number | null;
52
+ /**
53
+ * Task registration state, three-state and locale-safe.
54
+ *
55
+ * NOT schtasks: `schtasks /Query` exits non-zero for "no such task" AND for
56
+ * access-denied / RPC-unavailable alike, and its error text is localized — so
57
+ * an exit-code probe silently reads a permissions failure as "not installed",
58
+ * which would let stop skip the disable guard and let uninstall wipe tracking
59
+ * state for a task that is still very much registered. That is a fail-open
60
+ * hole in a module whose whole contract is fail-closed.
61
+ *
62
+ * Get-ScheduledTask raises a typed ObjectNotFound error for a missing task and
63
+ * a different category for everything else, so the three states are actually
64
+ * distinguishable. Emitted as an ASCII token, never parsed from a message.
65
+ */
66
+ export declare function queryTaskExists(deps: WinLifecycleDeps): boolean | 'indeterminate';
67
+ /**
68
+ * Disabled-state readback from /Query /XML — tag names are English on every
69
+ * locale. Our generated triggers are always <Enabled>true</Enabled>, so any
70
+ * false Enabled means the task itself is disabled.
71
+ */
72
+ export declare function queryTaskDisabled(deps: WinLifecycleDeps): boolean | 'indeterminate';
73
+ /**
74
+ * Stop: disable the task (so no trigger resurrects it), confirm the disabled
75
+ * readback, best-effort /End, then positively confirm the tracked process is
76
+ * gone. Tracking files are removed only when every fact is confirmed.
77
+ *
78
+ * Disabling is a GUARD, not a step — so every one of its failure modes aborts
79
+ * immediately, before anything is terminated (same contract as uninstall). If
80
+ * the task state cannot be read, the /Change fails (access denied), or the
81
+ * readback is not `disabled`, then a trigger — the hourly revival one, or the
82
+ * logon one — is still free to relaunch the daemon. Killing the process in
83
+ * that state would make a failed stop destructive: it could interrupt the
84
+ * daemon while leaving the task armed to relaunch it. Fail closed and leave
85
+ * tracking state untouched instead.
86
+ */
87
+ export declare function stopDaemonWindows(deps: WinLifecycleDeps): WinOpResult;
88
+ /**
89
+ * Uninstall: full stop semantics first — DISABLE (so no trigger can relaunch
90
+ * the daemon between the process-exit confirmation and /Delete, which would
91
+ * strand an orphan with no task left to manage it), /End, confirmed process
92
+ * exit — then /Delete with a confirmed disappearance readback, then artifact
93
+ * cleanup. Any unconfirmed step aborts before state is destroyed.
94
+ */
95
+ export declare function uninstallDaemonWindows(deps: WinLifecycleDeps): WinOpResult;
96
+ //# sourceMappingURL=win-lifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"win-lifecycle.d.ts","sourceRoot":"","sources":["../src/win-lifecycle.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEvE;;;;;;;;;;;;GAYG;AAEH,wEAAwE;AACxE,MAAM,WAAW,YAAY;IAC3B,yEAAyE;IACzE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AACD,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,YAAY,CAAC;AAE1E,MAAM,WAAW,QAAQ;IACvB,sDAAsD;IACtD,QAAQ,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACnC,gFAAgF;IAChF,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,YAAY,CAAC;IAClB,EAAE,EAAE,QAAQ,CAAC;IACb,KAAK,EAAE,eAAe,CAAC;IACvB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,GAAG,IAAI,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,YAAY,GAAG,eAAe,CAAC;AAEpE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,GAAG,WAAW,CAwBjF;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,GAAG,IAAI,CAKpE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,GAAG,eAAe,CAUjF;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,GAAG,eAAe,CAInF;AAkDD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,gBAAgB,GAAG,WAAW,CAoCrE;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,gBAAgB,GAAG,WAAW,CA+D1E"}
@@ -0,0 +1,229 @@
1
+ import * as path from 'node:path';
2
+ import { WIN_TASK_NAME } from './win-service.js';
3
+ export function probePidIdentity(deps, pid) {
4
+ // pid comes from readPidFile (validated positive integer) — safe to
5
+ // interpolate. [char]9 = TAB without backtick-escape pitfalls.
6
+ const script = `$p = Get-CimInstance Win32_Process -Filter 'ProcessId=${pid}'; ` +
7
+ `if ($null -eq $p) { 'ABSENT' } else { 'PRESENT' + [char]9 + $p.Name + [char]9 + "$($p.CommandLine)" }`;
8
+ const r = deps.run('powershell.exe', ['-NoProfile', '-Command', script]);
9
+ if (r.code !== 0 || r.spawnError)
10
+ return 'indeterminate';
11
+ const out = r.stdout.trim();
12
+ if (out === 'ABSENT')
13
+ return 'not-daemon';
14
+ if (!out.startsWith('PRESENT\t'))
15
+ return 'indeterminate';
16
+ const [, name = '', ...rest] = out.split('\t');
17
+ const commandLine = rest.join('\t').trim();
18
+ // Identity = expected image name AND our launcher path on the command
19
+ // line. The extension-less needle covers the current .cjs launcher and the
20
+ // legacy .js one (pre-.cjs installs may still be running).
21
+ if (!/node/i.test(name))
22
+ return 'not-daemon';
23
+ if (!commandLine) {
24
+ // Node-named process whose command line we cannot read (e.g. access
25
+ // denied on an elevated process): could be ours — fail closed.
26
+ return 'indeterminate';
27
+ }
28
+ const launcherNeedle = path.join(deps.paths.serviceDir, 'parall-daemon-launcher').toLowerCase();
29
+ return commandLine.toLowerCase().includes(launcherNeedle) ? 'daemon' : 'not-daemon';
30
+ }
31
+ export function readTrackedPid(deps) {
32
+ const raw = deps.fs.readFile(deps.paths.pidFile);
33
+ if (raw === null)
34
+ return null;
35
+ const pid = Number(raw.trim());
36
+ return Number.isInteger(pid) && pid > 0 ? pid : null;
37
+ }
38
+ /**
39
+ * Task registration state, three-state and locale-safe.
40
+ *
41
+ * NOT schtasks: `schtasks /Query` exits non-zero for "no such task" AND for
42
+ * access-denied / RPC-unavailable alike, and its error text is localized — so
43
+ * an exit-code probe silently reads a permissions failure as "not installed",
44
+ * which would let stop skip the disable guard and let uninstall wipe tracking
45
+ * state for a task that is still very much registered. That is a fail-open
46
+ * hole in a module whose whole contract is fail-closed.
47
+ *
48
+ * Get-ScheduledTask raises a typed ObjectNotFound error for a missing task and
49
+ * a different category for everything else, so the three states are actually
50
+ * distinguishable. Emitted as an ASCII token, never parsed from a message.
51
+ */
52
+ export function queryTaskExists(deps) {
53
+ const script = `try { Get-ScheduledTask -TaskName '${WIN_TASK_NAME}' -ErrorAction Stop | Out-Null; 'PRESENT' } ` +
54
+ `catch { if ($_.CategoryInfo.Category -eq 'ObjectNotFound') { 'ABSENT' } else { 'ERROR' } }`;
55
+ const r = deps.run('powershell.exe', ['-NoProfile', '-Command', script]);
56
+ if (r.code !== 0 || r.spawnError)
57
+ return 'indeterminate';
58
+ const out = r.stdout.trim();
59
+ if (out === 'PRESENT')
60
+ return true;
61
+ if (out === 'ABSENT')
62
+ return false;
63
+ return 'indeterminate';
64
+ }
65
+ /**
66
+ * Disabled-state readback from /Query /XML — tag names are English on every
67
+ * locale. Our generated triggers are always <Enabled>true</Enabled>, so any
68
+ * false Enabled means the task itself is disabled.
69
+ */
70
+ export function queryTaskDisabled(deps) {
71
+ const r = deps.run('schtasks', ['/Query', '/TN', WIN_TASK_NAME, '/XML']);
72
+ if (r.code !== 0 || r.spawnError)
73
+ return 'indeterminate';
74
+ return /<Enabled>\s*false\s*<\/Enabled>/i.test(r.stdout);
75
+ }
76
+ const PROCESS_EXIT_TIMEOUT_MS = 5_000;
77
+ const PROCESS_EXIT_POLL_MS = 200;
78
+ /**
79
+ * Confirm the tracked process is gone, killing it first when it is
80
+ * positively ours. Appends to `problems` and returns false on anything
81
+ * indeterminate — the caller must then keep the pidfile.
82
+ */
83
+ function confirmProcessGone(deps, problems) {
84
+ const pid = readTrackedPid(deps);
85
+ if (pid === null)
86
+ return true; // nothing tracked
87
+ let identity = probePidIdentity(deps, pid);
88
+ if (identity === 'not-daemon')
89
+ return true; // stale pidfile — daemon already exited
90
+ if (identity === 'indeterminate') {
91
+ problems.push(`cannot verify ownership of pid ${pid} (process query failed) — leaving the pidfile in place`);
92
+ return false;
93
+ }
94
+ const kill = deps.run('taskkill', ['/T', '/F', '/PID', String(pid)]);
95
+ const deadline = deps.now() + PROCESS_EXIT_TIMEOUT_MS;
96
+ for (;;) {
97
+ identity = probePidIdentity(deps, pid);
98
+ if (identity === 'not-daemon')
99
+ return true;
100
+ if (identity === 'indeterminate') {
101
+ problems.push(`process query failed while confirming pid ${pid} exited`);
102
+ return false;
103
+ }
104
+ if (deps.now() >= deadline) {
105
+ problems.push(`daemon process ${pid} is still running after taskkill` +
106
+ (kill.code !== 0 ? ` (taskkill exit ${kill.code ?? 'spawn-failed'})` : ''));
107
+ return false;
108
+ }
109
+ deps.sleep(PROCESS_EXIT_POLL_MS);
110
+ }
111
+ }
112
+ function cleanupTracking(deps, problems) {
113
+ if (!deps.fs.unlink(deps.paths.pidFile)) {
114
+ problems.push(`could not remove pidfile ${deps.paths.pidFile}`);
115
+ }
116
+ if (!deps.fs.unlink(deps.paths.runningMarker)) {
117
+ problems.push(`could not remove crash marker ${deps.paths.runningMarker}`);
118
+ }
119
+ }
120
+ /**
121
+ * Stop: disable the task (so no trigger resurrects it), confirm the disabled
122
+ * readback, best-effort /End, then positively confirm the tracked process is
123
+ * gone. Tracking files are removed only when every fact is confirmed.
124
+ *
125
+ * Disabling is a GUARD, not a step — so every one of its failure modes aborts
126
+ * immediately, before anything is terminated (same contract as uninstall). If
127
+ * the task state cannot be read, the /Change fails (access denied), or the
128
+ * readback is not `disabled`, then a trigger — the hourly revival one, or the
129
+ * logon one — is still free to relaunch the daemon. Killing the process in
130
+ * that state would make a failed stop destructive: it could interrupt the
131
+ * daemon while leaving the task armed to relaunch it. Fail closed and leave
132
+ * tracking state untouched instead.
133
+ */
134
+ export function stopDaemonWindows(deps) {
135
+ const problems = [];
136
+ const exists = queryTaskExists(deps);
137
+ if (exists === 'indeterminate') {
138
+ problems.push('schtasks could not be executed to query the task');
139
+ return { ok: false, problems };
140
+ }
141
+ if (exists) {
142
+ const disable = deps.run('schtasks', ['/Change', '/TN', WIN_TASK_NAME, '/DISABLE']);
143
+ if (disable.code !== 0) {
144
+ problems.push(`could not disable the task (schtasks /Change exit ${disable.code ?? 'spawn-failed'})`);
145
+ return { ok: false, problems };
146
+ }
147
+ const disabled = queryTaskDisabled(deps);
148
+ if (disabled !== true) {
149
+ problems.push(disabled === 'indeterminate'
150
+ ? 'could not read back the task state after disabling'
151
+ : 'task did not read back as disabled');
152
+ return { ok: false, problems };
153
+ }
154
+ // Best-effort accelerator only: its failure modes ("no running
155
+ // instance" vs anything else) are locale-dependent text, and process
156
+ // exit is independently confirmed below — so the result is ignored.
157
+ deps.run('schtasks', ['/End', '/TN', WIN_TASK_NAME]);
158
+ }
159
+ // Task confirmed absent or confirmed disabled: nothing can relaunch the
160
+ // daemon behind us, so terminating the tracked process is safe.
161
+ if (!confirmProcessGone(deps, problems)) {
162
+ return { ok: false, problems };
163
+ }
164
+ cleanupTracking(deps, problems);
165
+ return { ok: problems.length === 0, problems };
166
+ }
167
+ /**
168
+ * Uninstall: full stop semantics first — DISABLE (so no trigger can relaunch
169
+ * the daemon between the process-exit confirmation and /Delete, which would
170
+ * strand an orphan with no task left to manage it), /End, confirmed process
171
+ * exit — then /Delete with a confirmed disappearance readback, then artifact
172
+ * cleanup. Any unconfirmed step aborts before state is destroyed.
173
+ */
174
+ export function uninstallDaemonWindows(deps) {
175
+ const problems = [];
176
+ const exists = queryTaskExists(deps);
177
+ if (exists === 'indeterminate') {
178
+ problems.push('schtasks could not be executed to query the task');
179
+ return { ok: false, problems };
180
+ }
181
+ if (exists) {
182
+ // Disable BEFORE terminating: the hourly revival TimeTrigger (and the
183
+ // logon trigger) would otherwise be free to start a fresh instance in
184
+ // the gap between the exit confirmation below and /Delete — a daemon the
185
+ // deleted task can no longer stop.
186
+ const disable = deps.run('schtasks', ['/Change', '/TN', WIN_TASK_NAME, '/DISABLE']);
187
+ if (disable.code !== 0) {
188
+ problems.push(`could not disable the task before uninstalling (schtasks /Change exit ${disable.code ?? 'spawn-failed'})`);
189
+ return { ok: false, problems };
190
+ }
191
+ const disabled = queryTaskDisabled(deps);
192
+ if (disabled !== true) {
193
+ problems.push(disabled === 'indeterminate'
194
+ ? 'could not read back the task state after disabling'
195
+ : 'task did not read back as disabled before uninstall');
196
+ return { ok: false, problems };
197
+ }
198
+ deps.run('schtasks', ['/End', '/TN', WIN_TASK_NAME]); // best-effort, see stop
199
+ }
200
+ if (!confirmProcessGone(deps, problems)) {
201
+ return { ok: false, problems };
202
+ }
203
+ if (exists) {
204
+ const del = deps.run('schtasks', ['/Delete', '/TN', WIN_TASK_NAME, '/F']);
205
+ if (del.code !== 0) {
206
+ problems.push(`could not delete the task (schtasks /Delete exit ${del.code ?? 'spawn-failed'})`);
207
+ return { ok: false, problems };
208
+ }
209
+ const stillThere = queryTaskExists(deps);
210
+ if (stillThere !== false) {
211
+ problems.push(stillThere === 'indeterminate'
212
+ ? 'could not confirm the task was deleted'
213
+ : 'task is still registered after /Delete');
214
+ return { ok: false, problems };
215
+ }
216
+ }
217
+ cleanupTracking(deps, problems);
218
+ for (const artifact of [
219
+ deps.paths.launcherCjs,
220
+ deps.paths.legacyLauncherJs,
221
+ deps.paths.launcherVbs,
222
+ deps.paths.taskXml,
223
+ ]) {
224
+ if (!deps.fs.unlink(artifact)) {
225
+ problems.push(`could not remove ${artifact}`);
226
+ }
227
+ }
228
+ return { ok: problems.length === 0, problems };
229
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Windows service (Task Scheduler) artifact generation. Everything here is a
3
+ * pure function from paths to strings so the whole surface unit-tests on any
4
+ * platform; cli.ts owns filesystem writes and schtasks invocations.
5
+ *
6
+ * The chain at run time:
7
+ * Task Scheduler → wscript.exe //B launcher.vbs
8
+ * → vbs Run("node.exe launcher.js", 0, True) — window hidden, waits,
9
+ * propagates the exit code via WScript.Quit
10
+ * → launcher.js sets PRLL_DAEMON_MANAGED=1, writes the pidfile,
11
+ * redirects stdio to the log file, then imports the daemon bundle
12
+ * IN-PROCESS (overlay-first) — so the pid in the pidfile IS the
13
+ * daemon and a self-update exit(42) is the task's own exit code,
14
+ * which RestartOnFailure turns into a restart on the new overlay.
15
+ */
16
+ export declare const WIN_TASK_NAME = "ParallDaemon";
17
+ export interface WinServicePaths {
18
+ serviceDir: string;
19
+ /**
20
+ * .cjs, deliberately: the launcher is CommonJS (require) and a package.json
21
+ * with "type": "module" anywhere up the tree (e.g. a user's ~/package.json)
22
+ * would flip a .js file into ESM scope and break require() at startup.
23
+ * .cjs is CommonJS regardless of ancestor package.json files.
24
+ */
25
+ launcherCjs: string;
26
+ /** Pre-.cjs installs wrote this name; install/uninstall clean it up. */
27
+ legacyLauncherJs: string;
28
+ launcherVbs: string;
29
+ taskXml: string;
30
+ pidFile: string;
31
+ logDir: string;
32
+ logFile: string;
33
+ /**
34
+ * Canonical single-env overlay entry (~/.parall-daemon/bundle/current/...).
35
+ * Deliberately NOT PRLL_DAEMON_CONFIG_DIR-aware — the CLI service installer
36
+ * is single-env by design, mirroring the launchd/systemd wrappers (see
37
+ * ts/daemon/AGENTS.md, config-dir scope caveat).
38
+ */
39
+ overlayEntry: string;
40
+ /** Crash marker the bootstrap writes; intentional stops must clear it. */
41
+ runningMarker: string;
42
+ }
43
+ export declare function winServicePaths(home: string): WinServicePaths;
44
+ /**
45
+ * VBScript string literal: wrap in quotes, escape embedded quotes by
46
+ * doubling. NTFS forbids `"` in file names, so real paths never exercise
47
+ * the escape — it exists as defense-in-depth for arbitrary strings.
48
+ */
49
+ export declare function vbsQuote(value: string): string;
50
+ /**
51
+ * Encode a generated artifact as UTF-16LE with a BOM. wscript reads BOM-less
52
+ * files in the ANSI codepage (mangling non-ASCII profile paths) and schtasks'
53
+ * canonical XML form is UTF-16 — both .vbs and task.xml go through this.
54
+ */
55
+ export declare function encodeUtf16LeBom(content: string): Buffer;
56
+ export declare function xmlEscape(value: string): string;
57
+ /**
58
+ * The hidden-window launcher. `Run(cmd, 0, True)`: 0 hides the console
59
+ * window (a console app started by an interactive scheduled task would
60
+ * otherwise flash/persist one), True waits and yields the exit code, and
61
+ * WScript.Quit hands that code to Task Scheduler so RestartOnFailure sees
62
+ * daemon crashes and self-update exit(42)s.
63
+ *
64
+ * Caller must write this file as UTF-16LE with a BOM: wscript reads BOM-less
65
+ * files in the ANSI codepage, which mangles non-ASCII (e.g. Chinese
66
+ * user-profile) paths.
67
+ */
68
+ export declare function buildLauncherVbs(p: {
69
+ nodeExe: string;
70
+ launcherCjs: string;
71
+ }): string;
72
+ /**
73
+ * The in-process daemon launcher. Embedded paths go through JSON.stringify,
74
+ * which handles backslashes and quotes; the file itself is plain UTF-8 (Node
75
+ * always reads JS as UTF-8, so non-ASCII paths round-trip).
76
+ *
77
+ * The content is CommonJS and the artifact is written as `.cjs` so an
78
+ * ancestor package.json with "type": "module" (users do npm-init their home
79
+ * directories) can never flip it into ESM scope.
80
+ */
81
+ export declare function buildLauncherCjs(p: {
82
+ npmEntry: string;
83
+ overlayEntry: string;
84
+ pidFile: string;
85
+ logFile: string;
86
+ }): string;
87
+ /**
88
+ * Task Scheduler task definition.
89
+ *
90
+ * Element-order ground rules (Microsoft Task Scheduler schema docs):
91
+ * - trigger children (triggerBaseType) are an xs:sequence and MUST appear as
92
+ * StartBoundary → EndBoundary → Enabled → Repetition → ExecutionTimeLimit
93
+ * (learn.microsoft.com .../taskschedulerschema-timetrigger-triggergroup-element);
94
+ * - Settings children (settingsType) are an xs:all — order-insensitive;
95
+ * - RestartOnFailure Count is xs:unsignedByte (1–255)
96
+ * (.../taskschedulerschema-count-restarttype-element).
97
+ *
98
+ * - LogonTrigger: start at user logon (no admin, no stored password:
99
+ * InteractiveToken + omitted UserId registers for the calling user).
100
+ * - TimeTrigger PT1H: revival tick (StartBoundary is a fixed date safely in
101
+ * the past — a boundary in the future would silently disarm the trigger on
102
+ * a machine whose clock is behind) — if RestartOnFailure's count is ever
103
+ * exhausted the daemon still comes back within the hour; IgnoreNew makes
104
+ * it a no-op while an instance is running. `stop` disables the whole task,
105
+ * so this trigger never resurrects an intentionally stopped daemon.
106
+ * - ExecutionTimeLimit PT0S: no 72h default kill.
107
+ * - RestartOnFailure PT1M/99: crash or self-update exit(42) restarts in
108
+ * ~1 minute (PT1M is the schema minimum interval; 99 stays well inside the
109
+ * unsignedByte cap). Note this makes Windows self-update restarts ~1min,
110
+ * vs ~5s under launchd.
111
+ *
112
+ * Caller must write this as UTF-16LE with BOM to match the declared
113
+ * encoding — the canonical form schtasks itself produces on export.
114
+ */
115
+ export declare function buildTaskXml(p: {
116
+ wscriptExe: string;
117
+ launcherVbs: string;
118
+ }): string;
119
+ //# sourceMappingURL=win-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"win-service.d.ts","sourceRoot":"","sources":["../src/win-service.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AAEH,eAAO,MAAM,aAAa,iBAAiB,CAAC;AAE5C,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,wEAAwE;IACxE,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAgB7D;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAO/C;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CASpF;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,MAAM,CA6DT;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CA+CnF"}