@usestatemachines/cli 0.0.0-stage → 0.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.
Files changed (169) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +34 -2
  3. package/dist/agents/bundled.d.ts +5 -0
  4. package/dist/agents/bundled.js +6 -0
  5. package/dist/agents/destinations.d.ts +13 -0
  6. package/dist/agents/destinations.js +16 -0
  7. package/dist/api-internal.d.ts +62 -0
  8. package/dist/api-internal.js +348 -0
  9. package/dist/bin.d.ts +2 -0
  10. package/dist/bin.js +4 -0
  11. package/dist/command.d.ts +119 -0
  12. package/dist/command.js +195 -0
  13. package/dist/commands/api/command.d.ts +15 -0
  14. package/dist/commands/api/command.js +38 -0
  15. package/dist/commands/api/run.d.ts +4 -0
  16. package/dist/commands/api/run.js +24 -0
  17. package/dist/commands/apps/command.d.ts +19 -0
  18. package/dist/commands/apps/command.js +29 -0
  19. package/dist/commands/apps/list.d.ts +8 -0
  20. package/dist/commands/apps/list.js +10 -0
  21. package/dist/commands/connect/command.d.ts +34 -0
  22. package/dist/commands/connect/command.js +70 -0
  23. package/dist/commands/connect/run.d.ts +10 -0
  24. package/dist/commands/connect/run.js +71 -0
  25. package/dist/commands/doctor/command.d.ts +29 -0
  26. package/dist/commands/doctor/command.js +36 -0
  27. package/dist/commands/doctor/run.d.ts +13 -0
  28. package/dist/commands/doctor/run.js +12 -0
  29. package/dist/commands/env/command.d.ts +5 -0
  30. package/dist/commands/env/command.js +19 -0
  31. package/dist/commands/env/pull.d.ts +6 -0
  32. package/dist/commands/env/pull.js +6 -0
  33. package/dist/commands/environments/command.d.ts +121 -0
  34. package/dist/commands/environments/command.js +170 -0
  35. package/dist/commands/environments/create.d.ts +9 -0
  36. package/dist/commands/environments/create.js +42 -0
  37. package/dist/commands/environments/delete.d.ts +7 -0
  38. package/dist/commands/environments/delete.js +48 -0
  39. package/dist/commands/environments/get.d.ts +39 -0
  40. package/dist/commands/environments/get.js +5 -0
  41. package/dist/commands/environments/list.d.ts +41 -0
  42. package/dist/commands/environments/list.js +18 -0
  43. package/dist/commands/environments/wait.d.ts +39 -0
  44. package/dist/commands/environments/wait.js +21 -0
  45. package/dist/commands/init/command.d.ts +68 -0
  46. package/dist/commands/init/command.js +90 -0
  47. package/dist/commands/init/run.d.ts +8 -0
  48. package/dist/commands/init/run.js +422 -0
  49. package/dist/commands/link/command.d.ts +13 -0
  50. package/dist/commands/link/command.js +32 -0
  51. package/dist/commands/link/run.d.ts +12 -0
  52. package/dist/commands/link/run.js +7 -0
  53. package/dist/commands/login/command.d.ts +1 -0
  54. package/dist/commands/login/command.js +43 -0
  55. package/dist/commands/login/run.d.ts +30 -0
  56. package/dist/commands/login/run.js +43 -0
  57. package/dist/commands/login/schema.d.ts +33 -0
  58. package/dist/commands/login/schema.js +33 -0
  59. package/dist/commands/logout/command.d.ts +1 -0
  60. package/dist/commands/logout/command.js +24 -0
  61. package/dist/commands/logout/run.d.ts +10 -0
  62. package/dist/commands/logout/run.js +19 -0
  63. package/dist/commands/logout/schema.d.ts +10 -0
  64. package/dist/commands/logout/schema.js +10 -0
  65. package/dist/commands/profiles/command.d.ts +2 -0
  66. package/dist/commands/profiles/command.js +29 -0
  67. package/dist/commands/profiles/list.d.ts +20 -0
  68. package/dist/commands/profiles/list.js +10 -0
  69. package/dist/commands/profiles/schema.d.ts +24 -0
  70. package/dist/commands/profiles/schema.js +19 -0
  71. package/dist/commands/profiles/use.d.ts +7 -0
  72. package/dist/commands/profiles/use.js +7 -0
  73. package/dist/commands/requests/command.d.ts +39 -0
  74. package/dist/commands/requests/command.js +65 -0
  75. package/dist/commands/requests/get.d.ts +32 -0
  76. package/dist/commands/requests/get.js +15 -0
  77. package/dist/commands/requests/list.d.ts +27 -0
  78. package/dist/commands/requests/list.js +25 -0
  79. package/dist/commands/snapshots/command.d.ts +48 -0
  80. package/dist/commands/snapshots/command.js +80 -0
  81. package/dist/commands/snapshots/create.d.ts +7 -0
  82. package/dist/commands/snapshots/create.js +26 -0
  83. package/dist/commands/snapshots/delete.d.ts +34 -0
  84. package/dist/commands/snapshots/delete.js +8 -0
  85. package/dist/commands/snapshots/get.d.ts +35 -0
  86. package/dist/commands/snapshots/get.js +5 -0
  87. package/dist/commands/snapshots/list.d.ts +37 -0
  88. package/dist/commands/snapshots/list.js +10 -0
  89. package/dist/commands/unlink/command.d.ts +1 -0
  90. package/dist/commands/unlink/command.js +15 -0
  91. package/dist/commands/unlink/run.d.ts +7 -0
  92. package/dist/commands/unlink/run.js +8 -0
  93. package/dist/commands/whoami/command.d.ts +28 -0
  94. package/dist/commands/whoami/command.js +44 -0
  95. package/dist/commands/whoami/run.d.ts +5 -0
  96. package/dist/commands/whoami/run.js +39 -0
  97. package/dist/commands/workspaces/command.d.ts +33 -0
  98. package/dist/commands/workspaces/command.js +73 -0
  99. package/dist/commands/workspaces/list.d.ts +5 -0
  100. package/dist/commands/workspaces/list.js +42 -0
  101. package/dist/commands/workspaces/use.d.ts +5 -0
  102. package/dist/commands/workspaces/use.js +18 -0
  103. package/dist/config/project.d.ts +21 -0
  104. package/dist/config/project.js +105 -0
  105. package/dist/config/schema.d.ts +616 -0
  106. package/dist/config/schema.js +282 -0
  107. package/dist/context-types.d.ts +89 -0
  108. package/dist/context-types.js +56 -0
  109. package/dist/context.d.ts +13 -0
  110. package/dist/context.js +290 -0
  111. package/dist/credentials/sealing.d.ts +10 -0
  112. package/dist/credentials/sealing.js +72 -0
  113. package/dist/credentials/store.d.ts +17 -0
  114. package/dist/credentials/store.js +156 -0
  115. package/dist/doctor.d.ts +4 -0
  116. package/dist/doctor.js +239 -0
  117. package/dist/env-file.d.ts +10 -0
  118. package/dist/env-file.js +254 -0
  119. package/dist/errors.d.ts +784 -0
  120. package/dist/errors.js +459 -0
  121. package/dist/flags.d.ts +22 -0
  122. package/dist/flags.js +56 -0
  123. package/dist/generated/internal.d.ts +138 -0
  124. package/dist/generated/internal.js +2627 -0
  125. package/dist/generated/retry.d.ts +1 -0
  126. package/dist/generated/retry.js +11 -0
  127. package/dist/help.d.ts +408 -0
  128. package/dist/help.js +220 -0
  129. package/dist/io-types.d.ts +127 -0
  130. package/dist/io-types.js +1 -0
  131. package/dist/io.d.ts +13 -0
  132. package/dist/io.js +1017 -0
  133. package/dist/keys.d.ts +50 -0
  134. package/dist/keys.js +691 -0
  135. package/dist/link.d.ts +6 -0
  136. package/dist/link.js +58 -0
  137. package/dist/login.d.ts +92 -0
  138. package/dist/login.js +965 -0
  139. package/dist/main.d.ts +2 -0
  140. package/dist/main.js +126 -0
  141. package/dist/native-helper.d.ts +1 -0
  142. package/dist/native-helper.js +2 -0
  143. package/dist/output.d.ts +8 -0
  144. package/dist/output.js +135 -0
  145. package/dist/parser.d.ts +28 -0
  146. package/dist/parser.js +250 -0
  147. package/dist/program-types.d.ts +67 -0
  148. package/dist/program-types.js +14 -0
  149. package/dist/programs.d.ts +2 -0
  150. package/dist/programs.js +44 -0
  151. package/dist/project-roots.d.ts +3 -0
  152. package/dist/project-roots.js +26 -0
  153. package/dist/prompt.d.ts +3 -0
  154. package/dist/prompt.js +61 -0
  155. package/dist/registry.d.ts +2 -0
  156. package/dist/registry.js +44 -0
  157. package/dist/resources/create.d.ts +9 -0
  158. package/dist/resources/create.js +55 -0
  159. package/dist/resources/read.d.ts +15 -0
  160. package/dist/resources/read.js +69 -0
  161. package/dist/resources/schema.d.ts +177 -0
  162. package/dist/resources/schema.js +158 -0
  163. package/dist/text.d.ts +3 -0
  164. package/dist/text.js +19 -0
  165. package/dist/update.d.ts +22 -0
  166. package/dist/update.js +126 -0
  167. package/dist/version.d.ts +2 -0
  168. package/dist/version.js +2 -0
  169. package/package.json +52 -3
package/dist/io.js ADDED
@@ -0,0 +1,1017 @@
1
+ import { execFile, fork, spawn } from 'node:child_process';
2
+ import { randomBytes } from 'node:crypto';
3
+ import { constants } from 'node:fs';
4
+ import * as fs from 'node:fs/promises';
5
+ import { homedir, hostname, constants as osConstants } from 'node:os';
6
+ import path from 'node:path';
7
+ import process from 'node:process';
8
+ import { fileURLToPath } from 'node:url';
9
+ import { promisify } from 'node:util';
10
+ import { CliError } from './errors.js';
11
+ function errno(error) {
12
+ return typeof error === 'object' &&
13
+ error !== null &&
14
+ 'code' in error &&
15
+ typeof error.code === 'string'
16
+ ? error.code
17
+ : null;
18
+ }
19
+ function check(options, now = Date.now()) {
20
+ if (options.signal.aborted) {
21
+ throw new CliError('cli.cancelled');
22
+ }
23
+ if (options.deadline !== null && options.deadline <= now) {
24
+ throw new CliError('cli.wait_timeout');
25
+ }
26
+ }
27
+ function budget(options, maximum) {
28
+ return Math.max(0, Math.min(maximum, options.deadline === null ? maximum : options.deadline - Date.now()));
29
+ }
30
+ function effectSignal(options) {
31
+ return options.deadline === null
32
+ ? options.signal
33
+ : AbortSignal.any([
34
+ options.signal,
35
+ AbortSignal.timeout(budget(options, 2_147_483_647)),
36
+ ]);
37
+ }
38
+ async function sleep(milliseconds, options) {
39
+ check(options);
40
+ await new Promise((resolve, reject) => {
41
+ const finish = () => {
42
+ options.signal.removeEventListener('abort', abort);
43
+ resolve();
44
+ };
45
+ const timer = setTimeout(finish, budget(options, milliseconds));
46
+ const abort = () => {
47
+ clearTimeout(timer);
48
+ reject(new CliError('cli.cancelled'));
49
+ };
50
+ options.signal.addEventListener('abort', abort, { once: true });
51
+ });
52
+ check(options);
53
+ }
54
+ function owner() {
55
+ return { hostname: hostname(), pid: process.pid };
56
+ }
57
+ function liveness(candidate, local) {
58
+ if (candidate.hostname !== local.hostname) {
59
+ return 'unknown';
60
+ }
61
+ try {
62
+ process.kill(candidate.pid, 0);
63
+ return 'alive';
64
+ }
65
+ catch (error) {
66
+ return errno(error) === 'ESRCH' ? 'dead' : 'unknown';
67
+ }
68
+ }
69
+ async function inspect(file) {
70
+ try {
71
+ const stat = await fs.lstat(file);
72
+ return {
73
+ kind: stat.isSymbolicLink()
74
+ ? 'symlink'
75
+ : stat.isDirectory()
76
+ ? 'directory'
77
+ : stat.isFile()
78
+ ? 'file'
79
+ : 'other',
80
+ mode: stat.mode & 0o777,
81
+ };
82
+ }
83
+ catch (error) {
84
+ if (errno(error) === 'ENOENT') {
85
+ return { kind: 'missing' };
86
+ }
87
+ throw error;
88
+ }
89
+ }
90
+ async function realDirectory(directory, create, privateLeaf) {
91
+ const parent = path.dirname(directory);
92
+ if (create) {
93
+ await fs.mkdir(parent, { recursive: true, mode: 0o700 });
94
+ }
95
+ const resolved = path.join(await fs.realpath(parent), path.basename(directory));
96
+ let entry = await inspect(resolved);
97
+ if (entry.kind === 'missing' && create) {
98
+ try {
99
+ await fs.mkdir(resolved, { mode: 0o700 });
100
+ }
101
+ catch (error) {
102
+ if (errno(error) !== 'EEXIST') {
103
+ throw error;
104
+ }
105
+ }
106
+ entry = await inspect(resolved);
107
+ }
108
+ if (entry.kind !== 'directory') {
109
+ throw new CliError('cli.config_io');
110
+ }
111
+ if (privateLeaf && process.platform !== 'win32') {
112
+ await fs.chmod(resolved, 0o700);
113
+ }
114
+ return resolved;
115
+ }
116
+ async function realFile(file, create, privateLeaf = false) {
117
+ const absolute = path.resolve(file);
118
+ return path.join(await realDirectory(path.dirname(absolute), create, privateLeaf), path.basename(absolute));
119
+ }
120
+ const utf8 = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });
121
+ async function safeReadBytes(file) {
122
+ const entry = await inspect(file);
123
+ if (entry.kind === 'missing') {
124
+ return null;
125
+ }
126
+ if (entry.kind !== 'file') {
127
+ throw new CliError('cli.config_io');
128
+ }
129
+ const resolved = await realFile(file, false);
130
+ let handle;
131
+ try {
132
+ handle = await fs.open(resolved, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
133
+ }
134
+ catch (error) {
135
+ if (errno(error) === 'ENOENT') {
136
+ return null;
137
+ }
138
+ throw error;
139
+ }
140
+ try {
141
+ if (!(await handle.stat()).isFile()) {
142
+ throw new CliError('cli.config_io');
143
+ }
144
+ return await handle.readFile();
145
+ }
146
+ finally {
147
+ await handle.close();
148
+ }
149
+ }
150
+ async function safeRead(file) {
151
+ const bytes = await safeReadBytes(file);
152
+ if (bytes === null) {
153
+ return null;
154
+ }
155
+ try {
156
+ return utf8.decode(bytes);
157
+ }
158
+ catch {
159
+ throw new CliError('cli.file_not_utf8', {
160
+ message: `${file} is not valid UTF-8. Save it as UTF-8 and try again.`,
161
+ });
162
+ }
163
+ }
164
+ async function syncDirectory(directory) {
165
+ let handle;
166
+ try {
167
+ handle = await fs.open(directory, constants.O_RDONLY);
168
+ await handle.sync();
169
+ }
170
+ catch (error) {
171
+ const code = errno(error);
172
+ if (code !== 'EINVAL' &&
173
+ code !== 'ENOTSUP' &&
174
+ !(process.platform === 'win32' && (code === 'EPERM' || code === 'EISDIR'))) {
175
+ throw error;
176
+ }
177
+ }
178
+ finally {
179
+ await handle?.close();
180
+ }
181
+ }
182
+ async function writeCandidate(file, text, mode) {
183
+ const handle = await fs.open(file, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, mode);
184
+ try {
185
+ await handle.writeFile(text, 'utf8');
186
+ await handle.sync();
187
+ }
188
+ finally {
189
+ await handle.close();
190
+ }
191
+ }
192
+ async function removeOwned(file) {
193
+ try {
194
+ await fs.unlink(file);
195
+ }
196
+ catch (error) {
197
+ if (errno(error) !== 'ENOENT') {
198
+ throw error;
199
+ }
200
+ }
201
+ }
202
+ // Windows refuses to open, link over or delete a file that another process
203
+ // is deleting or holds without delete sharing. Lock contention causes both.
204
+ function windowsSharing(error) {
205
+ const code = errno(error);
206
+ return (process.platform === 'win32' &&
207
+ (code === 'EPERM' || code === 'EACCES' || code === 'EBUSY'));
208
+ }
209
+ function busyAsMissing(error) {
210
+ if (windowsSharing(error)) {
211
+ return null;
212
+ }
213
+ throw error;
214
+ }
215
+ function parseLock(text) {
216
+ let value;
217
+ try {
218
+ value = JSON.parse(text);
219
+ }
220
+ catch {
221
+ return null;
222
+ }
223
+ if (typeof value !== 'object' ||
224
+ value === null ||
225
+ !('hostname' in value) ||
226
+ typeof value.hostname !== 'string' ||
227
+ !('pid' in value) ||
228
+ typeof value.pid !== 'number' ||
229
+ !Number.isSafeInteger(value.pid) ||
230
+ value.pid <= 0 ||
231
+ !('nonce' in value) ||
232
+ typeof value.nonce !== 'string' ||
233
+ !/^[0-9a-f]{16,64}$/.test(value.nonce)) {
234
+ return null;
235
+ }
236
+ return { hostname: value.hostname, pid: value.pid, nonce: value.nonce };
237
+ }
238
+ async function staleLock(file, holder) {
239
+ if (holder === null) {
240
+ return new CliError('cli.config_lock_invalid', {
241
+ message: `The configuration lock ${file} is unreadable. If no other statemachines command is running, delete ${file} and try again.`,
242
+ });
243
+ }
244
+ const taken = await fs.stat(file).then((stat) => Math.round((Date.now() - stat.mtimeMs) / 60_000), () => 0);
245
+ return new CliError('cli.config_lock_invalid', {
246
+ message: `The configuration lock ${file} belongs to process ${holder.pid} on ${holder.hostname}, taken ${taken} minutes ago. If that process is not running, delete ${file} and try again.`,
247
+ });
248
+ }
249
+ export function createSystemFiles(local = owner()) {
250
+ const protect = async (action) => {
251
+ try {
252
+ return await action();
253
+ }
254
+ catch (error) {
255
+ if (error instanceof CliError) {
256
+ throw error;
257
+ }
258
+ throw new CliError('cli.config_io');
259
+ }
260
+ };
261
+ const write = (file, content, privacy, options, replace) => protect(async () => {
262
+ check(options);
263
+ const destination = await realFile(file, true, privacy === 'private');
264
+ const directory = path.dirname(destination);
265
+ const entry = await inspect(destination);
266
+ if (entry.kind !== 'file' && entry.kind !== 'missing') {
267
+ throw new CliError('cli.config_io');
268
+ }
269
+ if (!replace && entry.kind === 'file') {
270
+ return false;
271
+ }
272
+ const temporary = path.join(directory, `.state-${randomBytes(16).toString('hex')}.tmp`);
273
+ try {
274
+ await writeCandidate(temporary, content, privacy === 'public' ? 0o644 : 0o600);
275
+ check(options);
276
+ const current = await inspect(destination);
277
+ if (current.kind !== 'file' && current.kind !== 'missing') {
278
+ throw new CliError('cli.config_io');
279
+ }
280
+ if (replace) {
281
+ await fs.rename(temporary, destination);
282
+ }
283
+ else {
284
+ try {
285
+ await fs.link(temporary, destination);
286
+ }
287
+ catch (error) {
288
+ if (errno(error) === 'EEXIST') {
289
+ return false;
290
+ }
291
+ throw error;
292
+ }
293
+ }
294
+ await syncDirectory(directory);
295
+ return true;
296
+ }
297
+ finally {
298
+ await removeOwned(temporary);
299
+ }
300
+ });
301
+ return {
302
+ read: (file, options) => protect(async () => {
303
+ check(options);
304
+ return safeRead(file);
305
+ }),
306
+ readBytes: (file, options) => protect(async () => {
307
+ check(options);
308
+ return safeReadBytes(file);
309
+ }),
310
+ inspect: (file, options) => protect(async () => {
311
+ check(options);
312
+ return inspect(file);
313
+ }),
314
+ async atomicWrite(file, content, privacy, options) {
315
+ await write(file, content, privacy, options, true);
316
+ },
317
+ createIfMissing: (file, content, privacy, options) => write(file, content, privacy, options, false),
318
+ remove: (file, options) => protect(async () => {
319
+ check(options);
320
+ const entry = await inspect(file);
321
+ if (entry.kind === 'missing') {
322
+ return;
323
+ }
324
+ if (entry.kind !== 'file') {
325
+ throw new CliError('cli.config_io');
326
+ }
327
+ const resolved = await realFile(file, false);
328
+ await fs.unlink(resolved);
329
+ await syncDirectory(path.dirname(resolved));
330
+ }),
331
+ withLock: async (requested, options, action) => {
332
+ check(options);
333
+ const file = await protect(() => realFile(requested, true, true));
334
+ const directory = path.dirname(file);
335
+ const identity = JSON.stringify({
336
+ ...local,
337
+ nonce: randomBytes(16).toString('hex'),
338
+ });
339
+ const candidate = path.join(directory, `.lock-${randomBytes(16).toString('hex')}.tmp`);
340
+ const deadline = Date.now() + budget(options, 10_000);
341
+ let acquired = false;
342
+ const claim = async (target) => {
343
+ try {
344
+ await fs.link(candidate, target);
345
+ return true;
346
+ }
347
+ catch (error) {
348
+ if (errno(error) !== 'EEXIST' && !windowsSharing(error)) {
349
+ throw error;
350
+ }
351
+ }
352
+ const existing = await safeRead(target).catch(busyAsMissing);
353
+ if (existing === null) {
354
+ return false;
355
+ }
356
+ const holder = parseLock(existing);
357
+ const status = holder === null ? 'unknown' : liveness(holder, local);
358
+ if (holder === null || status === 'unknown') {
359
+ throw await staleLock(target, holder);
360
+ }
361
+ if (status === 'dead') {
362
+ const marker = `${file}.takeover-${holder.nonce}`;
363
+ if (await claim(marker)) {
364
+ try {
365
+ if ((await safeRead(target).catch(busyAsMissing)) === existing) {
366
+ await fs.unlink(target).catch(busyAsMissing);
367
+ }
368
+ }
369
+ finally {
370
+ await fs.unlink(marker);
371
+ }
372
+ }
373
+ }
374
+ return false;
375
+ };
376
+ try {
377
+ await protect(async () => {
378
+ await writeCandidate(candidate, identity, 0o600);
379
+ for (;;) {
380
+ acquired = await claim(file);
381
+ if (acquired) {
382
+ break;
383
+ }
384
+ check(options);
385
+ if (Date.now() >= deadline) {
386
+ throw new CliError('cli.config_locked');
387
+ }
388
+ await sleep(Math.min(25, Math.max(1, deadline - Date.now())), options);
389
+ }
390
+ await syncDirectory(directory);
391
+ check(options);
392
+ });
393
+ return await action();
394
+ }
395
+ finally {
396
+ await protect(async () => {
397
+ if (acquired && (await safeRead(file)) === identity) {
398
+ await fs.unlink(file);
399
+ await syncDirectory(directory);
400
+ }
401
+ await removeOwned(candidate);
402
+ });
403
+ }
404
+ },
405
+ findRepositoryRoot: (from, options) => protect(async () => {
406
+ let current = path.resolve(from);
407
+ for (;;) {
408
+ check(options);
409
+ const git = await inspect(path.join(current, '.git'));
410
+ if (git.kind === 'directory' || git.kind === 'file') {
411
+ return current;
412
+ }
413
+ const parent = path.dirname(current);
414
+ if (parent === current) {
415
+ return null;
416
+ }
417
+ current = parent;
418
+ }
419
+ }),
420
+ };
421
+ }
422
+ export function createSystemGit() {
423
+ const run = async (root, args, options) => {
424
+ check(options);
425
+ const environment = Object.fromEntries(Object.entries(process.env).filter(([key]) => !key.startsWith('GIT_')));
426
+ return new Promise((resolve, reject) => {
427
+ const child = spawn('git', ['-C', root, ...args], {
428
+ env: environment,
429
+ stdio: ['ignore', 'pipe', 'ignore'],
430
+ shell: false,
431
+ });
432
+ const chunks = [];
433
+ let failed = false;
434
+ child.stdout.on('data', (chunk) => chunks.push(chunk));
435
+ child.once('error', () => {
436
+ failed = true;
437
+ });
438
+ const cancel = () => {
439
+ child.kill('SIGKILL');
440
+ };
441
+ const timer = options.deadline === null
442
+ ? null
443
+ : setTimeout(cancel, budget(options, 2_147_483_647));
444
+ options.signal.addEventListener('abort', cancel, { once: true });
445
+ child.once('close', (code) => {
446
+ if (timer !== null) {
447
+ clearTimeout(timer);
448
+ }
449
+ options.signal.removeEventListener('abort', cancel);
450
+ try {
451
+ check(options);
452
+ if (failed || code === null) {
453
+ throw new CliError('cli.git');
454
+ }
455
+ resolve({ code, output: Buffer.concat(chunks).toString('utf8') });
456
+ }
457
+ catch (error) {
458
+ reject(error);
459
+ }
460
+ });
461
+ if (options.signal.aborted) {
462
+ cancel();
463
+ }
464
+ });
465
+ };
466
+ return {
467
+ async fileStatus(target, options) {
468
+ const tracked = await run(target.repositoryRoot, [
469
+ '--literal-pathspecs',
470
+ 'ls-files',
471
+ '--cached',
472
+ '-z',
473
+ '--',
474
+ target.path,
475
+ ], options);
476
+ if (tracked.code !== 0) {
477
+ throw new CliError('cli.git');
478
+ }
479
+ const ignored = await run(target.repositoryRoot, ['check-ignore', '--quiet', '--no-index', '--', target.path], options);
480
+ if (ignored.code !== 0 && ignored.code !== 1) {
481
+ throw new CliError('cli.git');
482
+ }
483
+ return {
484
+ tracked: tracked.output.length > 0,
485
+ ignored: ignored.code === 0,
486
+ };
487
+ },
488
+ };
489
+ }
490
+ function parseKeychainRequest(value) {
491
+ if (typeof value !== 'object' || value === null || !('kind' in value)) {
492
+ return null;
493
+ }
494
+ if (value.kind === 'probe') {
495
+ return { kind: 'probe' };
496
+ }
497
+ if (!('service' in value) ||
498
+ typeof value.service !== 'string' ||
499
+ !('account' in value) ||
500
+ typeof value.account !== 'string') {
501
+ return null;
502
+ }
503
+ if (value.kind === 'get' || value.kind === 'delete') {
504
+ return { kind: value.kind, service: value.service, account: value.account };
505
+ }
506
+ if (value.kind === 'set' &&
507
+ 'value' in value &&
508
+ typeof value.value === 'string') {
509
+ return {
510
+ kind: 'set',
511
+ service: value.service,
512
+ account: value.account,
513
+ value: value.value,
514
+ };
515
+ }
516
+ return null;
517
+ }
518
+ function parseKeychainResult(value) {
519
+ if (typeof value !== 'object' || value === null || !('kind' in value)) {
520
+ return { kind: 'invalid-helper-response' };
521
+ }
522
+ if (value.kind === 'value' &&
523
+ 'value' in value &&
524
+ typeof value.value === 'string') {
525
+ return { kind: 'value', value: value.value };
526
+ }
527
+ switch (value.kind) {
528
+ case 'available':
529
+ case 'missing':
530
+ case 'written':
531
+ case 'deleted':
532
+ case 'module-unavailable':
533
+ case 'operation-failed':
534
+ return { kind: value.kind };
535
+ default:
536
+ return { kind: 'invalid-helper-response' };
537
+ }
538
+ }
539
+ export function createNativeExecutor(helper = new URL('./native-helper.js', import.meta.url)) {
540
+ return async (request, options) => {
541
+ if (options.signal.aborted) {
542
+ throw new CliError('cli.cancelled');
543
+ }
544
+ const remaining = budget(options, 10_000);
545
+ if (remaining === 0) {
546
+ return { kind: 'timed-out' };
547
+ }
548
+ return new Promise((resolve, reject) => {
549
+ const environment = {};
550
+ for (const key of [
551
+ 'HOME',
552
+ 'USER',
553
+ 'LOGNAME',
554
+ 'PATH',
555
+ 'SystemRoot',
556
+ 'WINDIR',
557
+ 'APPDATA',
558
+ 'LOCALAPPDATA',
559
+ 'USERPROFILE',
560
+ 'DBUS_SESSION_BUS_ADDRESS',
561
+ 'XDG_RUNTIME_DIR',
562
+ 'DISPLAY',
563
+ ]) {
564
+ const value = process.env[key];
565
+ if (value !== undefined) {
566
+ environment[key] = value;
567
+ }
568
+ }
569
+ const child = fork(fileURLToPath(helper), [], {
570
+ env: environment,
571
+ execArgv: [],
572
+ stdio: ['ignore', 'ignore', 'ignore', 'ipc'],
573
+ });
574
+ let outcome = null;
575
+ let cancelled = false;
576
+ let finished = false;
577
+ const kill = () => {
578
+ if (child.exitCode === null && child.signalCode === null) {
579
+ child.kill('SIGKILL');
580
+ }
581
+ };
582
+ const abort = () => {
583
+ cancelled = true;
584
+ kill();
585
+ };
586
+ const timer = setTimeout(() => {
587
+ outcome = { kind: 'timed-out' };
588
+ kill();
589
+ }, remaining);
590
+ options.signal.addEventListener('abort', abort, { once: true });
591
+ child.on('message', (message) => {
592
+ if (outcome !== null || cancelled) {
593
+ return;
594
+ }
595
+ const result = parseKeychainResult(message);
596
+ const compatible = result.kind === 'module-unavailable' ||
597
+ result.kind === 'operation-failed' ||
598
+ result.kind === 'invalid-helper-response' ||
599
+ (request.kind === 'probe' && result.kind === 'available') ||
600
+ (request.kind === 'get' &&
601
+ (result.kind === 'value' || result.kind === 'missing')) ||
602
+ (request.kind === 'set' && result.kind === 'written') ||
603
+ (request.kind === 'delete' &&
604
+ (result.kind === 'deleted' || result.kind === 'missing'));
605
+ outcome = compatible ? result : { kind: 'invalid-helper-response' };
606
+ kill();
607
+ });
608
+ child.on('error', () => {
609
+ outcome ??= { kind: 'operation-failed' };
610
+ kill();
611
+ });
612
+ child.once('close', () => {
613
+ if (finished) {
614
+ return;
615
+ }
616
+ finished = true;
617
+ clearTimeout(timer);
618
+ options.signal.removeEventListener('abort', abort);
619
+ if (cancelled) {
620
+ reject(new CliError('cli.cancelled'));
621
+ }
622
+ else {
623
+ resolve(outcome ?? { kind: 'invalid-helper-response' });
624
+ }
625
+ });
626
+ child.send(request, (error) => {
627
+ if (error !== null) {
628
+ outcome ??= { kind: 'operation-failed' };
629
+ kill();
630
+ }
631
+ });
632
+ if (options.signal.aborted) {
633
+ abort();
634
+ }
635
+ });
636
+ };
637
+ }
638
+ export async function nativeOperation(request, load = () => import('@napi-rs/keyring')) {
639
+ let module;
640
+ try {
641
+ module = await load();
642
+ }
643
+ catch {
644
+ return { kind: 'module-unavailable' };
645
+ }
646
+ if (request.kind === 'probe') {
647
+ return { kind: 'available' };
648
+ }
649
+ try {
650
+ const entry = new module.Entry(request.service, request.account, {
651
+ linux: { store: 'secret-service' },
652
+ });
653
+ switch (request.kind) {
654
+ case 'get': {
655
+ const value = entry.getPassword();
656
+ return value === null ? { kind: 'missing' } : { kind: 'value', value };
657
+ }
658
+ case 'set':
659
+ entry.setPassword(request.value);
660
+ return { kind: 'written' };
661
+ case 'delete':
662
+ return { kind: entry.deleteCredential() ? 'deleted' : 'missing' };
663
+ }
664
+ }
665
+ catch {
666
+ return { kind: 'operation-failed' };
667
+ }
668
+ }
669
+ export function runNativeHelper() {
670
+ if (process.send === undefined) {
671
+ process.exitCode = 1;
672
+ return;
673
+ }
674
+ process.once('message', (message) => {
675
+ const request = parseKeychainRequest(message);
676
+ const operation = request === null
677
+ ? Promise.resolve({ kind: 'invalid-helper-response' })
678
+ : nativeOperation(request);
679
+ void operation.then((result) => {
680
+ process.send?.(result, () => {
681
+ process.disconnect();
682
+ });
683
+ });
684
+ });
685
+ }
686
+ export async function createHttpLease(proxy, options) {
687
+ check(options);
688
+ const { EnvHttpProxyAgent, fetch: request } = await import('undici');
689
+ const dispatcher = new EnvHttpProxyAgent({
690
+ httpProxy: proxy.http ?? '',
691
+ httpsProxy: proxy.https ?? proxy.http ?? '',
692
+ noProxy: proxy.noProxy ?? '',
693
+ });
694
+ const lifetime = new AbortController();
695
+ let closed = false;
696
+ return {
697
+ async fetch(input, init) {
698
+ check(options);
699
+ if (closed) {
700
+ throw new CliError('cli.network');
701
+ }
702
+ const source = new Request(input, init);
703
+ const signal = AbortSignal.any([
704
+ options.signal,
705
+ lifetime.signal,
706
+ source.signal,
707
+ ...(options.deadline === null
708
+ ? []
709
+ : [AbortSignal.timeout(budget(options, 2_147_483_647))]),
710
+ ]);
711
+ const body = source.method === 'GET' || source.method === 'HEAD'
712
+ ? undefined
713
+ : await source.arrayBuffer();
714
+ const response = await request(source.url, {
715
+ method: source.method,
716
+ headers: [...source.headers.entries()],
717
+ body,
718
+ signal,
719
+ redirect: source.redirect,
720
+ dispatcher,
721
+ });
722
+ const responseHeaders = new Headers();
723
+ response.headers.forEach((value, key) => {
724
+ responseHeaders.append(key, value);
725
+ });
726
+ const reader = response.body?.getReader();
727
+ const stream = reader
728
+ ? new ReadableStream({
729
+ async pull(controller) {
730
+ const result = await reader.read();
731
+ if (result.done) {
732
+ controller.close();
733
+ return;
734
+ }
735
+ const chunk = result.value;
736
+ if (!(chunk instanceof Uint8Array)) {
737
+ throw new CliError('cli.invalid_response');
738
+ }
739
+ controller.enqueue(chunk);
740
+ },
741
+ async cancel(reason) {
742
+ await reader.cancel(reason);
743
+ },
744
+ })
745
+ : null;
746
+ return new Response(stream, {
747
+ status: response.status,
748
+ statusText: response.statusText,
749
+ headers: responseHeaders,
750
+ });
751
+ },
752
+ async close() {
753
+ if (!closed) {
754
+ closed = true;
755
+ lifetime.abort();
756
+ await dispatcher.close();
757
+ }
758
+ },
759
+ };
760
+ }
761
+ const execFileAsync = promisify(execFile);
762
+ async function inTerminalForeground(platform) {
763
+ try {
764
+ if (platform === 'linux') {
765
+ const stat = await fs.readFile('/proc/self/stat', 'utf8');
766
+ const fields = stat.slice(stat.lastIndexOf(')') + 2).split(' ');
767
+ return fields[2] === fields[5];
768
+ }
769
+ const { stdout } = await execFileAsync('ps', [
770
+ '-o',
771
+ 'pgid=,tpgid=',
772
+ '-p',
773
+ String(process.pid),
774
+ ]);
775
+ const [group, foreground] = stdout.trim().split(/\s+/);
776
+ return group === foreground;
777
+ }
778
+ catch {
779
+ return false;
780
+ }
781
+ }
782
+ export function createSystemIo() {
783
+ const controller = new AbortController();
784
+ let forwarding = false;
785
+ let activeChild = null;
786
+ const interrupt = (signal) => {
787
+ activeChild?.(signal);
788
+ forwarding = true;
789
+ controller.abort(signal);
790
+ forwarding = false;
791
+ };
792
+ const platform = process.platform;
793
+ const relayed = platform === 'win32'
794
+ ? ['SIGINT', 'SIGTERM', 'SIGHUP']
795
+ : ['SIGINT', 'SIGQUIT', 'SIGTERM', 'SIGHUP'];
796
+ for (const signal of relayed) {
797
+ process.on(signal, interrupt);
798
+ }
799
+ if (platform !== 'linux' && platform !== 'darwin' && platform !== 'win32') {
800
+ throw new CliError('cli.internal');
801
+ }
802
+ const home = homedir();
803
+ const environment = { ...process.env };
804
+ const platformPath = platform === 'win32' ? path.win32 : path.posix;
805
+ const configuredDirectory = platform === 'win32' ? environment.APPDATA : environment.XDG_CONFIG_HOME;
806
+ const configDirectory = configuredDirectory && platformPath.isAbsolute(configuredDirectory)
807
+ ? configuredDirectory
808
+ : platformPath.join(home, ...(platform === 'win32' ? ['AppData', 'Roaming'] : ['.config']));
809
+ const local = owner();
810
+ const leases = new Set();
811
+ const write = (stream) => async (value) => {
812
+ await new Promise((resolve, reject) => {
813
+ stream.write(value, (error) => (error ? reject(error) : resolve()));
814
+ });
815
+ };
816
+ const host = {
817
+ environment,
818
+ platform,
819
+ home,
820
+ cwd: process.cwd(),
821
+ nodeVersion: process.versions.node,
822
+ owner: local,
823
+ terminal: process.stdin.isTTY
824
+ ? process.stdout.isTTY
825
+ ? 'both'
826
+ : 'stdin'
827
+ : process.stdout.isTTY
828
+ ? 'stdout'
829
+ : 'none',
830
+ configHome: platformPath.join(configDirectory, 'statemachines'),
831
+ };
832
+ return {
833
+ host,
834
+ signal: controller.signal,
835
+ stdout: { write: write(process.stdout) },
836
+ stderr: { write: write(process.stderr) },
837
+ stdin: process.stdin,
838
+ files: createSystemFiles(local),
839
+ git: createSystemGit(),
840
+ now: () => Date.now(),
841
+ sleep,
842
+ randomBytes: (length) => randomBytes(length),
843
+ nativeKeychain: { executeInChild: createNativeExecutor() },
844
+ prompt: {
845
+ async multiselect(question, choices, initialValues, options) {
846
+ check(options);
847
+ const { createPrompter } = await import('./prompt.js');
848
+ try {
849
+ return await createPrompter(process.stdin, process.stderr).multiselect(question, choices, initialValues, {
850
+ ...options,
851
+ signal: effectSignal(options),
852
+ });
853
+ }
854
+ catch (error) {
855
+ check(options);
856
+ throw error;
857
+ }
858
+ },
859
+ async confirm(question, options) {
860
+ check(options);
861
+ const { createPrompter } = await import('./prompt.js');
862
+ try {
863
+ return await createPrompter(process.stdin, process.stderr).confirm(question, { ...options, signal: effectSignal(options) });
864
+ }
865
+ catch (error) {
866
+ check(options);
867
+ throw error;
868
+ }
869
+ },
870
+ async select(question, choices, options) {
871
+ check(options);
872
+ const { createPrompter } = await import('./prompt.js');
873
+ try {
874
+ return await createPrompter(process.stdin, process.stderr).select(question, choices, { ...options, signal: effectSignal(options) });
875
+ }
876
+ catch (error) {
877
+ check(options);
878
+ throw error;
879
+ }
880
+ },
881
+ },
882
+ child: {
883
+ async liveness(candidate, options) {
884
+ check(options);
885
+ return liveness(candidate, local);
886
+ },
887
+ async run(spec) {
888
+ check(spec.options);
889
+ const [{ default: crossSpawn }, foreground] = await Promise.all([
890
+ import('cross-spawn'),
891
+ platform !== 'win32' && inTerminalForeground(platform),
892
+ ]);
893
+ check(spec.options);
894
+ return new Promise((resolve, reject) => {
895
+ const child = crossSpawn(spec.command, [...spec.args], {
896
+ cwd: spec.cwd,
897
+ env: { ...spec.environment },
898
+ stdio: [
899
+ spec.stdin,
900
+ spec.output === 'stderr' ? process.stderr : 'inherit',
901
+ 'inherit',
902
+ ],
903
+ shell: false,
904
+ });
905
+ let started = false;
906
+ let startFailed = false;
907
+ const forward = (signal) => {
908
+ child.kill(signal);
909
+ };
910
+ const relay = (signal) => {
911
+ if (platform !== 'win32' &&
912
+ !(foreground && (signal === 'SIGINT' || signal === 'SIGQUIT'))) {
913
+ forward(signal);
914
+ }
915
+ };
916
+ activeChild = relay;
917
+ const abort = () => {
918
+ if (!forwarding) {
919
+ const reason = spec.options.signal.reason;
920
+ forward(relayed.find((signal) => signal === reason) ?? 'SIGTERM');
921
+ }
922
+ };
923
+ const timer = spec.options.deadline === null
924
+ ? null
925
+ : setTimeout(() => forward('SIGKILL'), budget(spec.options, 2_147_483_647));
926
+ spec.options.signal.addEventListener('abort', abort, { once: true });
927
+ child.once('spawn', () => {
928
+ started = true;
929
+ });
930
+ child.on('error', (error) => {
931
+ startFailed ||= !started || errno(error) === 'ENOENT';
932
+ });
933
+ child.once('close', (code, signal) => {
934
+ if (timer !== null) {
935
+ clearTimeout(timer);
936
+ }
937
+ spec.options.signal.removeEventListener('abort', abort);
938
+ if (activeChild === relay) {
939
+ activeChild = null;
940
+ }
941
+ if (startFailed) {
942
+ reject(new CliError('cli.child_start'));
943
+ }
944
+ else {
945
+ resolve(signal === null
946
+ ? { kind: 'exit', code: code ?? 1 }
947
+ : { kind: 'signal', signal });
948
+ }
949
+ });
950
+ if (spec.options.signal.aborted) {
951
+ abort();
952
+ }
953
+ });
954
+ },
955
+ },
956
+ async http(proxy, options) {
957
+ const lease = await createHttpLease(proxy, options);
958
+ leases.add(lease);
959
+ return lease;
960
+ },
961
+ async detectAgent() {
962
+ const { determineAgent } = await import('@vercel/detect-agent');
963
+ const detected = await determineAgent();
964
+ return detected.isAgent ? { name: detected.agent.name } : null;
965
+ },
966
+ async openBrowser(url, options) {
967
+ check(options);
968
+ try {
969
+ const { default: open } = await import('open');
970
+ await open(url, { wait: false });
971
+ check(options);
972
+ return 'opened';
973
+ }
974
+ catch (error) {
975
+ if (error instanceof CliError) {
976
+ throw error;
977
+ }
978
+ return 'failed';
979
+ }
980
+ },
981
+ async close() {
982
+ for (const signal of relayed) {
983
+ process.off(signal, interrupt);
984
+ }
985
+ await Promise.all([...leases].map((lease) => lease.close()));
986
+ },
987
+ };
988
+ }
989
+ export async function runProcess(main) {
990
+ const io = createSystemIo();
991
+ let result;
992
+ try {
993
+ result = await main(process.argv.slice(2), io);
994
+ }
995
+ catch (error) {
996
+ await io.close();
997
+ throw error;
998
+ }
999
+ if (typeof result === 'number') {
1000
+ process.exitCode = result;
1001
+ await io.close();
1002
+ return;
1003
+ }
1004
+ try {
1005
+ await io.close();
1006
+ }
1007
+ catch { }
1008
+ if (result.kind === 'exit') {
1009
+ process.exitCode = result.code;
1010
+ }
1011
+ else {
1012
+ process.exitCode = 128 + osConstants.signals[result.signal];
1013
+ if (process.platform !== 'win32') {
1014
+ process.kill(process.pid, result.signal);
1015
+ }
1016
+ }
1017
+ }