@theford/runly 0.0.0-stage → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +157 -2
  3. package/dist/cli/client.d.ts +80 -0
  4. package/dist/cli/client.js +294 -0
  5. package/dist/cli/client.js.map +1 -0
  6. package/dist/cli/daemon-unit.d.ts +58 -0
  7. package/dist/cli/daemon-unit.js +289 -0
  8. package/dist/cli/daemon-unit.js.map +1 -0
  9. package/dist/cli/index.d.ts +2 -0
  10. package/dist/cli/index.js +1482 -0
  11. package/dist/cli/index.js.map +1 -0
  12. package/dist/cli/install.d.ts +9 -0
  13. package/dist/cli/install.js +53 -0
  14. package/dist/cli/install.js.map +1 -0
  15. package/dist/cli/logs.d.ts +43 -0
  16. package/dist/cli/logs.js +232 -0
  17. package/dist/cli/logs.js.map +1 -0
  18. package/dist/cli/tether.d.ts +3 -0
  19. package/dist/cli/tether.js +65 -0
  20. package/dist/cli/tether.js.map +1 -0
  21. package/dist/cli/watchdog.d.ts +1 -0
  22. package/dist/cli/watchdog.js +81 -0
  23. package/dist/cli/watchdog.js.map +1 -0
  24. package/dist/core/builds.d.ts +20 -0
  25. package/dist/core/builds.js +93 -0
  26. package/dist/core/builds.js.map +1 -0
  27. package/dist/core/caller-env.d.ts +17 -0
  28. package/dist/core/caller-env.js +77 -0
  29. package/dist/core/caller-env.js.map +1 -0
  30. package/dist/core/diagnostics.d.ts +68 -0
  31. package/dist/core/diagnostics.js +34 -0
  32. package/dist/core/diagnostics.js.map +1 -0
  33. package/dist/core/env-vars.d.ts +35 -0
  34. package/dist/core/env-vars.js +32 -0
  35. package/dist/core/env-vars.js.map +1 -0
  36. package/dist/core/events.d.ts +10 -0
  37. package/dist/core/events.js +42 -0
  38. package/dist/core/events.js.map +1 -0
  39. package/dist/core/exec.d.ts +22 -0
  40. package/dist/core/exec.js +84 -0
  41. package/dist/core/exec.js.map +1 -0
  42. package/dist/core/journal.d.ts +227 -0
  43. package/dist/core/journal.js +395 -0
  44. package/dist/core/journal.js.map +1 -0
  45. package/dist/core/logs.d.ts +56 -0
  46. package/dist/core/logs.js +259 -0
  47. package/dist/core/logs.js.map +1 -0
  48. package/dist/core/manifest.d.ts +292 -0
  49. package/dist/core/manifest.js +179 -0
  50. package/dist/core/manifest.js.map +1 -0
  51. package/dist/core/namespaces.d.ts +7 -0
  52. package/dist/core/namespaces.js +59 -0
  53. package/dist/core/namespaces.js.map +1 -0
  54. package/dist/core/paths.d.ts +31 -0
  55. package/dist/core/paths.js +65 -0
  56. package/dist/core/paths.js.map +1 -0
  57. package/dist/core/policy.d.ts +94 -0
  58. package/dist/core/policy.js +122 -0
  59. package/dist/core/policy.js.map +1 -0
  60. package/dist/core/ports.d.ts +52 -0
  61. package/dist/core/ports.js +94 -0
  62. package/dist/core/ports.js.map +1 -0
  63. package/dist/core/presets.d.ts +25 -0
  64. package/dist/core/presets.js +86 -0
  65. package/dist/core/presets.js.map +1 -0
  66. package/dist/core/procscan.d.ts +111 -0
  67. package/dist/core/procscan.js +342 -0
  68. package/dist/core/procscan.js.map +1 -0
  69. package/dist/core/retention.d.ts +97 -0
  70. package/dist/core/retention.js +322 -0
  71. package/dist/core/retention.js.map +1 -0
  72. package/dist/core/sleep.d.ts +51 -0
  73. package/dist/core/sleep.js +99 -0
  74. package/dist/core/sleep.js.map +1 -0
  75. package/dist/core/tree-ledger.d.ts +39 -0
  76. package/dist/core/tree-ledger.js +92 -0
  77. package/dist/core/tree-ledger.js.map +1 -0
  78. package/dist/core/types.d.ts +21 -0
  79. package/dist/core/types.js +6 -0
  80. package/dist/core/types.js.map +1 -0
  81. package/dist/core/units.d.ts +20 -0
  82. package/dist/core/units.js +69 -0
  83. package/dist/core/units.js.map +1 -0
  84. package/dist/core/upkeep.d.ts +90 -0
  85. package/dist/core/upkeep.js +176 -0
  86. package/dist/core/upkeep.js.map +1 -0
  87. package/dist/core/util.d.ts +46 -0
  88. package/dist/core/util.js +168 -0
  89. package/dist/core/util.js.map +1 -0
  90. package/dist/core/version.d.ts +39 -0
  91. package/dist/core/version.js +144 -0
  92. package/dist/core/version.js.map +1 -0
  93. package/dist/core/worktree.d.ts +51 -0
  94. package/dist/core/worktree.js +309 -0
  95. package/dist/core/worktree.js.map +1 -0
  96. package/dist/daemon/budget.d.ts +155 -0
  97. package/dist/daemon/budget.js +288 -0
  98. package/dist/daemon/budget.js.map +1 -0
  99. package/dist/daemon/election.d.ts +9 -0
  100. package/dist/daemon/election.js +140 -0
  101. package/dist/daemon/election.js.map +1 -0
  102. package/dist/daemon/engine.d.ts +1703 -0
  103. package/dist/daemon/engine.js +5305 -0
  104. package/dist/daemon/engine.js.map +1 -0
  105. package/dist/daemon/index.d.ts +2 -0
  106. package/dist/daemon/index.js +412 -0
  107. package/dist/daemon/index.js.map +1 -0
  108. package/dist/daemon/proxy.d.ts +108 -0
  109. package/dist/daemon/proxy.js +615 -0
  110. package/dist/daemon/proxy.js.map +1 -0
  111. package/dist/daemon/supervisor.d.ts +101 -0
  112. package/dist/daemon/supervisor.js +493 -0
  113. package/dist/daemon/supervisor.js.map +1 -0
  114. package/dist/drivers/appliances.d.ts +11 -0
  115. package/dist/drivers/appliances.js +174 -0
  116. package/dist/drivers/appliances.js.map +1 -0
  117. package/dist/drivers/datastores.d.ts +144 -0
  118. package/dist/drivers/datastores.js +775 -0
  119. package/dist/drivers/datastores.js.map +1 -0
  120. package/dist/drivers/preview.d.ts +69 -0
  121. package/dist/drivers/preview.js +605 -0
  122. package/dist/drivers/preview.js.map +1 -0
  123. package/dist/drivers/types.d.ts +70 -0
  124. package/dist/drivers/types.js +13 -0
  125. package/dist/drivers/types.js.map +1 -0
  126. package/package.json +63 -4
  127. package/schema/backlot.schema.json +549 -0
  128. package/schema/runly.schema.json +556 -0
@@ -0,0 +1,94 @@
1
+ import { createServer } from 'node:net';
2
+ import { readFileSync } from 'node:fs';
3
+ /**
4
+ * Is `port` genuinely free?
5
+ *
6
+ * Binding only 127.0.0.1 misses a foreign process listening on the WILDCARD
7
+ * address: on Linux that bind collides and reports busy, but on macOS it
8
+ * succeeds, so the "port occupied by a foreign process" guard never fired
9
+ * there and the service started into a port someone else already held.
10
+ * Probing the wildcard as well is what makes the two platforms agree.
11
+ */
12
+ export function probeFree(port) {
13
+ const bind = (host) => new Promise((resolve) => {
14
+ const srv = createServer();
15
+ srv.once('error', () => resolve(false));
16
+ srv.listen(port, host, () => srv.close(() => resolve(true)));
17
+ });
18
+ return bind('127.0.0.1').then((loopback) => (loopback ? bind('0.0.0.0') : false));
19
+ }
20
+ export const DEFAULT_PUBLIC_BLOCK = { lo: 20000, hi: 29999 };
21
+ export const DEFAULT_INTERNAL_BLOCK = { lo: 30000, hi: 31999 };
22
+ export const DEFAULT_TUNNEL_BLOCK = { lo: 32000, hi: 32767 };
23
+ function blockFromEnv(name, fallback) {
24
+ const raw = process.env[name];
25
+ if (!raw)
26
+ return fallback;
27
+ const m = /^\s*(\d+)\s*-\s*(\d+)\s*$/.exec(raw);
28
+ if (!m)
29
+ return fallback;
30
+ const lo = Number(m[1]);
31
+ const hi = Number(m[2]);
32
+ if (!(lo >= 1024 && hi <= 65535 && lo <= hi))
33
+ return fallback;
34
+ return { lo, hi };
35
+ }
36
+ export const publicBlock = () => blockFromEnv('BACKLOT_PORT_RANGE', DEFAULT_PUBLIC_BLOCK);
37
+ export const internalBlock = () => blockFromEnv('BACKLOT_INTERNAL_PORT_RANGE', DEFAULT_INTERNAL_BLOCK);
38
+ export const tunnelBlock = () => blockFromEnv('BACKLOT_TUNNEL_PORT_RANGE', DEFAULT_TUNNEL_BLOCK);
39
+ export const inBlock = (port, block) => port >= block.lo && port <= block.hi;
40
+ /**
41
+ * The OS ephemeral (outbound source-port) range. Linux publishes it; other
42
+ * platforms get the IANA range their kernels use by default.
43
+ */
44
+ export function ephemeralRange() {
45
+ try {
46
+ const [lo, hi] = readFileSync('/proc/sys/net/ipv4/ip_local_port_range', 'utf8').trim().split(/\s+/).map(Number);
47
+ if (lo && hi && lo <= hi)
48
+ return { lo, hi };
49
+ }
50
+ catch {
51
+ /* not Linux */
52
+ }
53
+ return { lo: 49152, hi: 65535 };
54
+ }
55
+ /** Human-readable overlaps of the configured blocks with each other and the ephemeral range. */
56
+ export function blockConflicts() {
57
+ const eph = ephemeralRange();
58
+ const blocks = [['public', publicBlock()], ['internal', internalBlock()], ['tunnel', tunnelBlock()]];
59
+ const overlap = (a, b) => a.lo <= b.hi && b.lo <= a.hi;
60
+ const out = [];
61
+ for (const [name, b] of blocks) {
62
+ if (overlap(b, eph))
63
+ out.push(`the ${name} port block ${b.lo}-${b.hi} overlaps the OS ephemeral range ${eph.lo}-${eph.hi}; ports in the overlap are skipped`);
64
+ }
65
+ for (let i = 0; i < blocks.length; i++) {
66
+ for (let j = i + 1; j < blocks.length; j++) {
67
+ const [an, a] = blocks[i];
68
+ const [bn, b] = blocks[j];
69
+ if (overlap(a, b))
70
+ out.push(`the ${an} port block ${a.lo}-${a.hi} overlaps the ${bn} block ${b.lo}-${b.hi}`);
71
+ }
72
+ }
73
+ return out;
74
+ }
75
+ /**
76
+ * The first port of `block` that is not in `taken`, not ephemeral, and probes
77
+ * free, scanning from a random offset so concurrent daemons (and a daemon
78
+ * restarted with an empty memory) do not all start at the bottom. Returns
79
+ * undefined when the block has nothing left — the caller names the block.
80
+ */
81
+ export async function allocateInBlock(block, taken, probe = probeFree) {
82
+ const eph = ephemeralRange();
83
+ const span = block.hi - block.lo + 1;
84
+ const start = Math.floor(Math.random() * span);
85
+ for (let i = 0; i < span; i++) {
86
+ const port = block.lo + ((start + i) % span);
87
+ if (taken.has(port) || inBlock(port, eph))
88
+ continue;
89
+ if (await probe(port))
90
+ return port;
91
+ }
92
+ return undefined;
93
+ }
94
+ //# sourceMappingURL=ports.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ports.js","sourceRoot":"","sources":["../../src/core/ports.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import { type DatastoreSpec, type Manifest } from './manifest.js';
2
+ /** The presets a datastore offers. A manifest without a catalog keeps its historical default choices. */
3
+ export declare function presetCatalog(spec: DatastoreSpec): string[];
4
+ /** A datastore's default preset, validated against its catalog. */
5
+ export declare function defaultPresetFor(name: string, spec: DatastoreSpec): string;
6
+ /**
7
+ * Validate an explicit `--preset` request (decision 0034) and every declared
8
+ * default, before anything is claimed or changed. Returns ONLY the datastores
9
+ * the caller named: an explicit preset reloads that datastore from its
10
+ * template, and a datastore nobody named keeps whatever it holds.
11
+ */
12
+ export declare function validatePresetRequest(manifest: Manifest, requested?: unknown, file?: string): Record<string, string>;
13
+ /**
14
+ * The preset a datastore is (re)created with when the caller named none: the
15
+ * one it holds, while the catalog still offers it, else the default. A store
16
+ * that is merely KEPT is never touched — this only decides what a restore that
17
+ * happens anyway (a reset, a first creation) restores.
18
+ */
19
+ export declare function presetToRestore(name: string, spec: DatastoreSpec, held: string | undefined): string;
20
+ /**
21
+ * CLI shorthand is only unambiguous for a single datastore — of the ones an
22
+ * environment has: a `copies_only` datastore (decision 0039) is never reloaded
23
+ * by `up`/`reset-data`, so it does not make `--preset NAME` ambiguous.
24
+ */
25
+ export declare function parsePresetArgs(manifest: Manifest, values: string[]): Record<string, string> | undefined;
@@ -0,0 +1,86 @@
1
+ import { declaredDefaultPreset, defaultPreset, envDatastoreNames } from './manifest.js';
2
+ import { BrokerError } from './util.js';
3
+ /** The presets a datastore offers. A manifest without a catalog keeps its historical default choices. */
4
+ export function presetCatalog(spec) {
5
+ const declared = declaredDefaultPreset(spec);
6
+ return spec.presets?.length ? spec.presets : [...new Set(['default', ...(declared === undefined ? [] : [declared])])];
7
+ }
8
+ /** A datastore's default preset, validated against its catalog. */
9
+ export function defaultPresetFor(name, spec) {
10
+ const catalog = presetCatalog(spec);
11
+ const declared = declaredDefaultPreset(spec);
12
+ if (declared !== undefined && !catalog.includes(declared))
13
+ throw new BrokerError('work-error', `default preset '${declared}' for '${name}' is not declared in presets`, 'manifest');
14
+ const value = defaultPreset(spec);
15
+ if (!catalog.includes(value)) {
16
+ throw new BrokerError('work-error', `no preset '${value}' for datastore '${name}' (have: ${catalog.join(', ') || 'none'})`, 'manifest');
17
+ }
18
+ return value;
19
+ }
20
+ /**
21
+ * Validate an explicit `--preset` request (decision 0034) and every declared
22
+ * default, before anything is claimed or changed. Returns ONLY the datastores
23
+ * the caller named: an explicit preset reloads that datastore from its
24
+ * template, and a datastore nobody named keeps whatever it holds.
25
+ */
26
+ export function validatePresetRequest(manifest, requested, file = 'runly.yml') {
27
+ const stores = manifest.datastores ?? {};
28
+ if (requested !== undefined && (requested === null || typeof requested !== 'object' || Array.isArray(requested))) {
29
+ throw new BrokerError('work-error', 'presets must map datastore names to preset names', 'manifest');
30
+ }
31
+ const choices = (requested ?? {});
32
+ for (const [name, value] of Object.entries(choices)) {
33
+ if (!Object.hasOwn(stores, name))
34
+ throw new BrokerError('work-error', `no datastore '${name}' in ${file}`, 'manifest');
35
+ if (typeof value !== 'string' || !value)
36
+ throw new BrokerError('work-error', `preset for '${name}' must be a nonempty name`, 'manifest');
37
+ }
38
+ const out = {};
39
+ for (const [name, spec] of Object.entries(stores)) {
40
+ defaultPresetFor(name, spec);
41
+ if (!Object.hasOwn(choices, name))
42
+ continue;
43
+ const value = choices[name];
44
+ const catalog = presetCatalog(spec);
45
+ if (!catalog.includes(value)) {
46
+ throw new BrokerError('work-error', `no preset '${value}' for datastore '${name}' (have: ${catalog.join(', ') || 'none'})`, 'manifest');
47
+ }
48
+ out[name] = value;
49
+ }
50
+ return out;
51
+ }
52
+ /**
53
+ * The preset a datastore is (re)created with when the caller named none: the
54
+ * one it holds, while the catalog still offers it, else the default. A store
55
+ * that is merely KEPT is never touched — this only decides what a restore that
56
+ * happens anyway (a reset, a first creation) restores.
57
+ */
58
+ export function presetToRestore(name, spec, held) {
59
+ if (held !== undefined && presetCatalog(spec).includes(held))
60
+ return held;
61
+ return defaultPresetFor(name, spec);
62
+ }
63
+ /**
64
+ * CLI shorthand is only unambiguous for a single datastore — of the ones an
65
+ * environment has: a `copies_only` datastore (decision 0039) is never reloaded
66
+ * by `up`/`reset-data`, so it does not make `--preset NAME` ambiguous.
67
+ */
68
+ export function parsePresetArgs(manifest, values) {
69
+ if (values.length === 0)
70
+ return undefined;
71
+ const stores = envDatastoreNames(manifest);
72
+ const choices = Object.create(null);
73
+ for (const value of values) {
74
+ const at = value.indexOf('=');
75
+ if (at < 0 && stores.length !== 1) {
76
+ throw new BrokerError('work-error', `--preset NAME requires exactly one datastore an environment has (here: ${stores.join(', ') || 'none'}); use --preset DATASTORE=NAME`, 'manifest');
77
+ }
78
+ const name = at < 0 ? stores[0] : value.slice(0, at);
79
+ const preset = at < 0 ? value : value.slice(at + 1);
80
+ if (Object.hasOwn(choices, name))
81
+ throw new BrokerError('work-error', `preset for '${name}' was selected more than once`, 'manifest');
82
+ choices[name] = preset;
83
+ }
84
+ return choices;
85
+ }
86
+ //# sourceMappingURL=presets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presets.js","sourceRoot":"","sources":["../../src/core/presets.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,111 @@
1
+ export declare const ENV_TAG = "BACKLOT_ENV_ID";
2
+ export declare const SERVICE_TAG = "BACKLOT_SERVICE";
3
+ /** Scopes a tag to one state root so parallel installs never reap each other. */
4
+ export declare const ROOT_TAG = "BACKLOT_STATE_ROOT";
5
+ export interface TaggedProc {
6
+ pid: number;
7
+ envId: string;
8
+ service: string;
9
+ startTime: number;
10
+ }
11
+ /** The environment every supervised service is spawned with. */
12
+ /** The service name `runly exec`'s command is tagged with; never reaped (see scanTagged). */
13
+ export declare const EXEC_SERVICE = "exec";
14
+ export declare function serviceTag(envId: string, service: string, stateRoot: string): Record<string, string>;
15
+ export declare const procScanSupported: () => boolean;
16
+ /**
17
+ * Field 22 of /proc/<pid>/stat: the process start time in clock ticks since
18
+ * boot. Unique per pid *life*, so (pid, startTime) survives pid reuse.
19
+ *
20
+ * comm (field 2) is an arbitrary string wrapped in parens and may itself
21
+ * contain spaces or parens, so parse after the LAST ')' rather than splitting
22
+ * the whole line.
23
+ */
24
+ export declare function startTime(pid: number): number | undefined;
25
+ /**
26
+ * Field 5 of /proc/<pid>/stat: the process group id.
27
+ *
28
+ * `sh -c <cmd>` does NOT reliably exec: dash forks for anything non-trivial,
29
+ * so the pid runly records is often only the wrapper, and the real server is
30
+ * a sibling in the same group. Killing "the leader" and then checking the
31
+ * leader's liveness therefore reports success while the server keeps running —
32
+ * group membership is the only honest liveness question.
33
+ */
34
+ export declare function processGroup(pid: number): number | undefined;
35
+ /**
36
+ * Is ANY process still in this group?
37
+ *
38
+ * Without /proc, `kill(-pgid, 0)` is still a genuine GROUP query — ESRCH means
39
+ * the group is empty. Checking the leader instead would report "gone" the
40
+ * moment an `sh -c` wrapper exited, while the real server kept running, which
41
+ * is the whole failure this function exists to detect.
42
+ */
43
+ export declare function groupAlive(pgid: number): boolean;
44
+ /** Is this pid alive at all? Says nothing about whose it is. */
45
+ export declare function isAlive(pid: number): boolean;
46
+ /**
47
+ * Is pid N still the same process that was recorded with `recordedStart`?
48
+ *
49
+ * With no recorded start time (a journal written by an older runly, or a
50
+ * non-Linux host) this can only fall back to liveness, which is exactly the
51
+ * pid-reuse hazard this exists to close — so callers should treat an
52
+ * un-verifiable pid as un-signallable where a mistake would be costly.
53
+ */
54
+ export declare function sameProcess(pid: number, recordedStart?: number): boolean;
55
+ /**
56
+ * Every live process tagged as belonging to this state root.
57
+ *
58
+ * Note this finds *descendants* too — an `ng serve` inherits the tag from the
59
+ * `sh -c` wrapper runly spawned, which is the whole point: after the wrapper
60
+ * dies the child is still identifiable.
61
+ */
62
+ export declare function scanTagged(stateRoot: string): TaggedProc[];
63
+ /** Carried by `runly db with`'s command and its descendants: the copy it runs against (decision 0039). */
64
+ export declare const DB_COPY_TAG = "BACKLOT_DB_COPY";
65
+ /** The environment `runly db with` runs its command with, so the daemon can find it again. */
66
+ export declare function dbCopyTag(copy: string, stateRoot: string): Record<string, string>;
67
+ /**
68
+ * Every live process of this state root running against database copy
69
+ * `copy` — `runly db with`'s command and whatever it started (the tag is
70
+ * inherited). Linux only, like `scanTagged`; elsewhere the recorded pid is
71
+ * all there is.
72
+ */
73
+ export declare function scanDbCopy(copy: string, stateRoot: string): Array<{
74
+ pid: number;
75
+ startTime: number;
76
+ }>;
77
+ /**
78
+ * Every live process whose working directory sits inside `prefix` AND which has
79
+ * no controlling terminal.
80
+ *
81
+ * The tag scan above misses a descendant that scrubbed its environment (a
82
+ * process launched through `env -i`, a re-exec that rebuilds environ, some
83
+ * language runtimes' worker pools). Those still have to live SOMEWHERE. Under
84
+ * the old projection that somewhere was the environment's own tree; since
85
+ * decision 0032 services run in the caller's worktree, where cwd proves
86
+ * nothing, so the engine only ever points this at an environment's PRIVATE
87
+ * directory (see Engine.reapEnvTree).
88
+ *
89
+ * Linux keeps the cwd link readable after the directory is unlinked, appending
90
+ * " (deleted)" to the target — which is precisely the shape of the leak this
91
+ * exists to catch: hundreds of service children still running out of an
92
+ * environment tree that was removed a day earlier. The suffix is stripped so a
93
+ * deleted tree still matches its prefix.
94
+ *
95
+ * The tty exclusion is not a nicety. cwd is NOT proof of ownership — a developer
96
+ * who runs `cd <env-tree>` to look around matches this scan exactly, and callers
97
+ * signal a matched process's whole GROUP, so without the exclusion a teardown
98
+ * would kill that person's shell and every job in it. That is the one outcome
99
+ * this module exists to prevent (see the header): skipping a sweep is safe,
100
+ * signalling a stranger's process is not. A runly service can never be
101
+ * excluded by it, because it never has a terminal to begin with.
102
+ *
103
+ * Even so, deliberately NOT used on the quiesce path, and never on a worktree:
104
+ * only teardown, which is about to delete the environment's private directory
105
+ * anyway, may reap by cwd — and only inside that directory.
106
+ */
107
+ export declare function scanByCwd(prefix: string): Array<{
108
+ pid: number;
109
+ startTime: number;
110
+ cwd: string;
111
+ }>;