@visulima/vis 1.0.0-alpha.11 → 1.0.0-alpha.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +101 -0
- package/LICENSE.md +559 -186
- package/README.md +18 -0
- package/dist/bin.js +1 -9
- package/dist/config/index.d.ts +477 -556
- package/dist/config/index.js +1 -2
- package/dist/generate/index.js +1 -3
- package/dist/packem_chunks/applyDefaults.js +2 -336
- package/dist/packem_chunks/bin.js +234 -9552
- package/dist/packem_chunks/doctor-probe.js +2 -112
- package/dist/packem_chunks/fix.js +11 -234
- package/dist/packem_chunks/handler.js +1 -99
- package/dist/packem_chunks/handler10.js +2 -53
- package/dist/packem_chunks/handler11.js +1 -32
- package/dist/packem_chunks/handler12.js +5 -100
- package/dist/packem_chunks/handler13.js +1 -25
- package/dist/packem_chunks/handler14.js +18 -916
- package/dist/packem_chunks/handler15.js +15 -201
- package/dist/packem_chunks/handler16.js +1 -124
- package/dist/packem_chunks/handler17.js +1 -13
- package/dist/packem_chunks/handler18.js +1 -106
- package/dist/packem_chunks/handler19.js +1 -19
- package/dist/packem_chunks/handler2.js +2 -75
- package/dist/packem_chunks/handler20.js +5 -29
- package/dist/packem_chunks/handler21.js +1 -222
- package/dist/packem_chunks/handler22.js +1 -237
- package/dist/packem_chunks/handler23.js +5 -101
- package/dist/packem_chunks/handler24.js +1 -110
- package/dist/packem_chunks/handler25.js +3 -402
- package/dist/packem_chunks/handler26.js +1 -13
- package/dist/packem_chunks/handler27.js +1 -63
- package/dist/packem_chunks/handler28.js +7 -34
- package/dist/packem_chunks/handler29.js +21 -456
- package/dist/packem_chunks/handler3.js +4 -95
- package/dist/packem_chunks/handler30.js +3 -170
- package/dist/packem_chunks/handler31.js +1 -530
- package/dist/packem_chunks/handler32.js +2 -214
- package/dist/packem_chunks/handler33.js +25 -119
- package/dist/packem_chunks/handler34.js +2 -630
- package/dist/packem_chunks/handler35.js +3 -283
- package/dist/packem_chunks/handler36.js +22 -542
- package/dist/packem_chunks/handler37.js +410 -744
- package/dist/packem_chunks/handler38.js +22 -989
- package/dist/packem_chunks/handler39.js +22 -574
- package/dist/packem_chunks/handler4.js +2 -90
- package/dist/packem_chunks/handler40.js +22 -1685
- package/dist/packem_chunks/handler41.js +6 -1088
- package/dist/packem_chunks/handler42.js +5 -797
- package/dist/packem_chunks/handler43.js +10 -2658
- package/dist/packem_chunks/handler44.js +51 -3784
- package/dist/packem_chunks/handler45.js +25 -2574
- package/dist/packem_chunks/handler46.js +3 -3769
- package/dist/packem_chunks/handler47.js +21 -1485
- package/dist/packem_chunks/handler48.js +42 -0
- package/dist/packem_chunks/handler5.js +8 -174
- package/dist/packem_chunks/handler6.js +1 -95
- package/dist/packem_chunks/handler7.js +1 -115
- package/dist/packem_chunks/handler8.js +1 -12
- package/dist/packem_chunks/handler9.js +1 -29
- package/dist/packem_chunks/heal-accept.js +10 -522
- package/dist/packem_chunks/heal.js +14 -673
- package/dist/packem_chunks/index.js +7 -873
- package/dist/packem_chunks/loader.js +1 -23
- package/dist/packem_chunks/tar.js +3 -0
- package/dist/packem_shared/ai-analysis-hm8d2W7z.js +67 -0
- package/dist/packem_shared/ai-cache-DoiF80AR.js +1 -0
- package/dist/packem_shared/ai-fix-nn4zOE95.js +43 -0
- package/dist/packem_shared/cache-directory-CwHlJhgx.js +1 -0
- package/dist/packem_shared/dependency-scan-COr5n63B.js +2 -0
- package/dist/packem_shared/docker-D6OGr5_S.js +2 -0
- package/dist/packem_shared/failure-log-iUVLf6ts.js +2 -0
- package/dist/packem_shared/flakiness-D9wf0t56.js +1 -0
- package/dist/packem_shared/giget-CcEy_Elm.js +2 -0
- package/dist/packem_shared/index-DH-5hsrC.js +1 -0
- package/dist/packem_shared/otel-DxDUPJJH.js +6 -0
- package/dist/packem_shared/otelPlugin-CQq6poq8.js +1 -0
- package/dist/packem_shared/registry-CkubDdiY.js +2 -0
- package/dist/packem_shared/run-summary-utils-BfBvjzhY.js +1 -0
- package/dist/packem_shared/runtime-check-BXZ43CBW.js +1 -0
- package/dist/packem_shared/selectors-BylODRiM.js +3 -0
- package/dist/packem_shared/symbols-CQmER5MT.js +1 -0
- package/dist/packem_shared/toolchain-BgBOUHII.js +5 -0
- package/dist/packem_shared/typosquats-CcZl99B1.js +1 -0
- package/dist/packem_shared/use-measured-height-DjYgUOKk.js +1 -0
- package/dist/packem_shared/utils-DrNg0XTR.js +1 -0
- package/dist/packem_shared/verify-Baj5mFJ7.js +1 -0
- package/dist/packem_shared/vis-update-app-D1jl0UZZ.js +1 -0
- package/dist/packem_shared/xxh3-DrAUNq4n.js +1 -0
- package/index.js +556 -727
- package/package.json +19 -29
- package/schemas/project.schema.json +739 -297
- package/schemas/vis-config.schema.json +3365 -384
- package/templates/buildkite-ci/template.yml +20 -20
- package/dist/packem_shared/VisUpdateApp-D-Yz_wvg.js +0 -1316
- package/dist/packem_shared/_commonjsHelpers-BqLXS_qQ.js +0 -5
- package/dist/packem_shared/ai-analysis-CHeB1joD.js +0 -367
- package/dist/packem_shared/ai-cache-Be_jexe4.js +0 -142
- package/dist/packem_shared/ai-fix-B9iQVcD2.js +0 -379
- package/dist/packem_shared/cache-directory-2qvs4goY.js +0 -98
- package/dist/packem_shared/catalog-BJTtyi-O.js +0 -1371
- package/dist/packem_shared/dependency-scan-A0KSklpG.js +0 -188
- package/dist/packem_shared/docker-2iZzc280.js +0 -181
- package/dist/packem_shared/failure-log-Cz3Z4SKL.js +0 -100
- package/dist/packem_shared/flakiness-goTxXuCX.js +0 -180
- package/dist/packem_shared/otel-DCvqCTz_.js +0 -158
- package/dist/packem_shared/otelPlugin-DFaLDvJf.js +0 -3
- package/dist/packem_shared/registry-CbqXI0rc.js +0 -272
- package/dist/packem_shared/run-summary-utils-PVMl4aIh.js +0 -130
- package/dist/packem_shared/runtime-check-Cobi3p6l.js +0 -127
- package/dist/packem_shared/selectors-SM69TfqC.js +0 -194
- package/dist/packem_shared/symbols-Ta7g2nU-.js +0 -14
- package/dist/packem_shared/toolchain-BdZd9eBi.js +0 -975
- package/dist/packem_shared/typosquats-C-bCh3PX.js +0 -1210
- package/dist/packem_shared/use-measured-height-CNP0vT4M.js +0 -20
- package/dist/packem_shared/utils-CthVdBPS.js +0 -40
- package/dist/packem_shared/xxh3-Ck8mXNg1.js +0 -239
|
@@ -1,3769 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
const __cjs_require = __cjs_createRequire(import.meta.url);
|
|
4
|
-
|
|
5
|
-
const __cjs_getProcess = typeof globalThis !== "undefined" && typeof globalThis.process !== "undefined" ? globalThis.process : process;
|
|
6
|
-
|
|
7
|
-
const __cjs_getBuiltinModule = (module) => {
|
|
8
|
-
// Check if we're in Node.js and version supports getBuiltinModule
|
|
9
|
-
if (typeof __cjs_getProcess !== "undefined" && __cjs_getProcess.versions && __cjs_getProcess.versions.node) {
|
|
10
|
-
const [major, minor] = __cjs_getProcess.versions.node.split(".").map(Number);
|
|
11
|
-
// Node.js 20.16.0+ and 22.3.0+
|
|
12
|
-
if (major > 22 || (major === 22 && minor >= 3) || (major === 20 && minor >= 16)) {
|
|
13
|
-
return __cjs_getProcess.getBuiltinModule(module);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
// Fallback to createRequire
|
|
17
|
-
return __cjs_require(module);
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
import { enforceProjectConstraints, parsePartition, TaskScheduler, createTaskGraph, CompositeLifeCycle, defaultTaskRunner, createLogReporter, readLastRunSummary, generateRunSummary, writeChromeTrace, runConcurrently, writeRunSummary, TerminalBuffer } from '@visulima/task-runner';
|
|
21
|
-
import { _ as QuitDialog, Y as loadVisTaskConfigsForWorkspace, d as discoverWorkspace, b as buildProjectGraph, a9 as detectCurrentOs, aa as shouldRunInCI, i as isInCi, Z as loadEnvFile, ab as resolveTargetShell } from './bin.js';
|
|
22
|
-
import { r as resolveSharedCacheDirectory, a as applyBranchScope } from '../packem_shared/cache-directory-2qvs4goY.js';
|
|
23
|
-
const {
|
|
24
|
-
statSync,
|
|
25
|
-
existsSync,
|
|
26
|
-
watch
|
|
27
|
-
} = __cjs_getBuiltinModule("node:fs");
|
|
28
|
-
import { join, basename, dirname, resolve, relative } from '@visulima/path';
|
|
29
|
-
import { F as FailureLogLifeCycle } from '../packem_shared/failure-log-Cz3Z4SKL.js';
|
|
30
|
-
import { a as analyzeFlakiness, f as formatFlakinessTable, b as formatTimingSummary, l as loadRunSummaries, c as compareDuration } from '../packem_shared/flakiness-goTxXuCX.js';
|
|
31
|
-
import { r as runToolchainPreflight } from '../packem_shared/toolchain-BdZd9eBi.js';
|
|
32
|
-
import { i as isAlive, c as readAllEntries, b as runReadiness } from '../packem_shared/registry-CbqXI0rc.js';
|
|
33
|
-
import { r as resolveSelector, f as filterProjectsByQuery } from '../packem_shared/selectors-SM69TfqC.js';
|
|
34
|
-
const {
|
|
35
|
-
createInterface,
|
|
36
|
-
emitKeypressEvents
|
|
37
|
-
} = __cjs_getBuiltinModule("node:readline");
|
|
38
|
-
import { Box, Text, StaticRender, renderToString, ScrollView, Spinner, useApp, useWindowSize, useInput, Dialog, render } from '@visulima/tui';
|
|
39
|
-
import React, { useSyncExternalStore, useState, useRef, useCallback, useEffect, useMemo } from 'react';
|
|
40
|
-
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
41
|
-
import { T as TICK, C as CROSS, D as DASH, E as ELLIPSIS } from '../packem_shared/symbols-Ta7g2nU-.js';
|
|
42
|
-
import { duration } from '@visulima/humanizer';
|
|
43
|
-
import { createHooks } from 'hookable';
|
|
44
|
-
const {
|
|
45
|
-
appendFile
|
|
46
|
-
} = __cjs_getBuiltinModule("node:fs/promises");
|
|
47
|
-
const {
|
|
48
|
-
platform,
|
|
49
|
-
homedir
|
|
50
|
-
} = __cjs_getBuiltinModule("node:os");
|
|
51
|
-
|
|
52
|
-
const LOCKFILE_FILES_BY_MANAGER = {
|
|
53
|
-
bun: ["bun.lock", "bun.lockb"],
|
|
54
|
-
npm: ["package-lock.json"],
|
|
55
|
-
pnpm: ["pnpm-lock.yaml"],
|
|
56
|
-
yarn: ["yarn.lock"]
|
|
57
|
-
};
|
|
58
|
-
const INSTALL_MARKERS = {
|
|
59
|
-
bun: ["node_modules/.bun-tag"],
|
|
60
|
-
npm: ["node_modules/.package-lock.json"],
|
|
61
|
-
pnpm: ["node_modules/.modules.yaml", "node_modules/.pnpm/lock.yaml"],
|
|
62
|
-
yarn: ["node_modules/.yarn-integrity", "node_modules/.yarn-state.yml", ".yarn/install-state.gz"]
|
|
63
|
-
};
|
|
64
|
-
const INSTALL_COMMAND = {
|
|
65
|
-
ci: {
|
|
66
|
-
bun: "bun install --frozen-lockfile",
|
|
67
|
-
npm: "npm ci",
|
|
68
|
-
pnpm: "pnpm install --frozen-lockfile",
|
|
69
|
-
yarn: "yarn install --immutable"
|
|
70
|
-
},
|
|
71
|
-
tty: {
|
|
72
|
-
bun: "bun install",
|
|
73
|
-
npm: "npm install",
|
|
74
|
-
pnpm: "pnpm install",
|
|
75
|
-
yarn: "yarn install"
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
const MTIME_SKEW_MS = 1e3;
|
|
79
|
-
const detectPackageManager = (workspaceRoot) => {
|
|
80
|
-
for (const [manager, files] of Object.entries(LOCKFILE_FILES_BY_MANAGER)) {
|
|
81
|
-
for (const file of files) {
|
|
82
|
-
if (existsSync(join(workspaceRoot, file))) {
|
|
83
|
-
return { lockfileFile: file, manager };
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return void 0;
|
|
88
|
-
};
|
|
89
|
-
const findFreshestMarker = (workspaceRoot, manager) => {
|
|
90
|
-
let freshest;
|
|
91
|
-
for (const relative of INSTALL_MARKERS[manager]) {
|
|
92
|
-
const absolute = join(workspaceRoot, relative);
|
|
93
|
-
if (!existsSync(absolute)) {
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
const { mtimeMs } = statSync(absolute);
|
|
97
|
-
if (!freshest || mtimeMs > freshest.mtimeMs) {
|
|
98
|
-
freshest = { mtimeMs, path: relative };
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return freshest;
|
|
102
|
-
};
|
|
103
|
-
const checkLockfileFreshness = (workspaceRoot, options = {}) => {
|
|
104
|
-
const detected = detectPackageManager(workspaceRoot);
|
|
105
|
-
if (!detected) {
|
|
106
|
-
return { checked: false };
|
|
107
|
-
}
|
|
108
|
-
const { lockfileFile, manager } = detected;
|
|
109
|
-
const lockfileMtimeMs = statSync(join(workspaceRoot, lockfileFile)).mtimeMs;
|
|
110
|
-
const marker = findFreshestMarker(workspaceRoot, manager);
|
|
111
|
-
const command = INSTALL_COMMAND[options.inCi ? "ci" : "tty"][manager];
|
|
112
|
-
const detail = {
|
|
113
|
-
installMarkerMtimeMs: marker?.mtimeMs,
|
|
114
|
-
lockfileMtimeMs,
|
|
115
|
-
lockfilePath: lockfileFile,
|
|
116
|
-
marker: marker?.path,
|
|
117
|
-
packageManager: manager
|
|
118
|
-
};
|
|
119
|
-
if (!marker) {
|
|
120
|
-
return {
|
|
121
|
-
checked: true,
|
|
122
|
-
detail,
|
|
123
|
-
failure: "missing-install",
|
|
124
|
-
message: `lockfile detected but node_modules looks uninitialised — run \`${command}\` before \`vis run\`.`
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
if (lockfileMtimeMs > marker.mtimeMs + MTIME_SKEW_MS) {
|
|
128
|
-
return {
|
|
129
|
-
checked: true,
|
|
130
|
-
detail,
|
|
131
|
-
failure: "stale-install",
|
|
132
|
-
message: `${lockfileFile} is newer than node_modules (${marker.path}) — run \`${command}\` to sync.`
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
return { checked: true, detail };
|
|
136
|
-
};
|
|
137
|
-
const runLockfilePreflight = (workspaceRoot, inCi, logger, options = {}) => {
|
|
138
|
-
if (options.skip) {
|
|
139
|
-
return { checked: false, shouldContinue: true };
|
|
140
|
-
}
|
|
141
|
-
const result = checkLockfileFreshness(workspaceRoot, { inCi });
|
|
142
|
-
if (!result.failure) {
|
|
143
|
-
return { ...result, shouldContinue: true };
|
|
144
|
-
}
|
|
145
|
-
const formattedMessage = `preflight: ${result.message ?? "lockfile drift detected"}`;
|
|
146
|
-
if (inCi && !options.ciAsWarning) {
|
|
147
|
-
return { ...result, formattedMessage, shouldContinue: false };
|
|
148
|
-
}
|
|
149
|
-
logger.warn(formattedMessage);
|
|
150
|
-
return { ...result, formattedMessage, shouldContinue: true };
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
const PLACEHOLDER_REGEX = /\$(\{([A-Z_][\w]*)(:-[^}]*)?\}|([A-Z_][\w]*))/gi;
|
|
154
|
-
const POSIX_SPECIALS = /* @__PURE__ */ new Set(["@", "*", "#", "?", "$", "!", "0", "_", "-"]);
|
|
155
|
-
const extractEnvReferences = (command) => {
|
|
156
|
-
const found = /* @__PURE__ */ new Map();
|
|
157
|
-
for (const match of command.matchAll(PLACEHOLDER_REGEX)) {
|
|
158
|
-
const braced = match[2];
|
|
159
|
-
const bareName = match[4];
|
|
160
|
-
const defaultClause = match[3];
|
|
161
|
-
const name = braced ?? bareName;
|
|
162
|
-
if (name === void 0) {
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
if (POSIX_SPECIALS.has(name)) {
|
|
166
|
-
continue;
|
|
167
|
-
}
|
|
168
|
-
const hasDefault = defaultClause !== void 0;
|
|
169
|
-
const previous = found.get(name);
|
|
170
|
-
if (previous === void 0) {
|
|
171
|
-
found.set(name, hasDefault);
|
|
172
|
-
} else if (previous && !hasDefault) {
|
|
173
|
-
found.set(name, false);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
return [...found.entries()].map(([name, hasDefault]) => ({ hasDefault, name }));
|
|
177
|
-
};
|
|
178
|
-
const checkStrictEnv = (options) => {
|
|
179
|
-
const { command, processEnv, taskEnv, taskId } = options;
|
|
180
|
-
const references = extractEnvReferences(command);
|
|
181
|
-
if (references.length === 0) {
|
|
182
|
-
return void 0;
|
|
183
|
-
}
|
|
184
|
-
const missing = [];
|
|
185
|
-
for (const { hasDefault, name } of references) {
|
|
186
|
-
if (hasDefault) {
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
189
|
-
const fromTask = taskEnv[name];
|
|
190
|
-
const fromProcess = processEnv[name];
|
|
191
|
-
if (fromTask === void 0 && fromProcess === void 0) {
|
|
192
|
-
missing.push(name);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
if (missing.length === 0) {
|
|
196
|
-
return void 0;
|
|
197
|
-
}
|
|
198
|
-
return { missing: missing.sort(), taskId };
|
|
199
|
-
};
|
|
200
|
-
const formatStrictEnvError = (violation) => {
|
|
201
|
-
const list = violation.missing.map((name) => `$${name}`).join(", ");
|
|
202
|
-
return `Strict env: ${violation.taskId} references unset variable${violation.missing.length === 1 ? "" : "s"} ${list}. Set ${violation.missing.length === 1 ? "it" : "them"} in the task env, an envFile, or the parent shell — or opt out with options.strictEnv: false.`;
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
const collectAvailableTargets = (workspace) => {
|
|
206
|
-
const targets = /* @__PURE__ */ new Set();
|
|
207
|
-
for (const project of Object.values(workspace.projects)) {
|
|
208
|
-
for (const name of Object.keys(project.targets ?? {})) {
|
|
209
|
-
targets.add(name);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
return [...targets].sort();
|
|
213
|
-
};
|
|
214
|
-
const buildAliasMap = (projectOptions) => {
|
|
215
|
-
const aliases = /* @__PURE__ */ new Map();
|
|
216
|
-
for (const visTargets of projectOptions.values()) {
|
|
217
|
-
for (const [canonical, config] of Object.entries(visTargets)) {
|
|
218
|
-
for (const alias of config.aliases ?? []) {
|
|
219
|
-
if (!aliases.has(alias)) {
|
|
220
|
-
aliases.set(alias, canonical);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
return aliases;
|
|
226
|
-
};
|
|
227
|
-
const resolveTargetAlias = (name, aliases) => aliases.get(name) ?? name;
|
|
228
|
-
const levenshtein = (a, b) => {
|
|
229
|
-
if (a.length === 0) {
|
|
230
|
-
return b.length;
|
|
231
|
-
}
|
|
232
|
-
if (b.length === 0) {
|
|
233
|
-
return a.length;
|
|
234
|
-
}
|
|
235
|
-
const matrix = [];
|
|
236
|
-
for (let i = 0; i <= b.length; i++) {
|
|
237
|
-
matrix[i] = [i];
|
|
238
|
-
}
|
|
239
|
-
for (let j = 0; j <= a.length; j++) {
|
|
240
|
-
matrix[0][j] = j;
|
|
241
|
-
}
|
|
242
|
-
for (let i = 1; i <= b.length; i++) {
|
|
243
|
-
for (let j = 1; j <= a.length; j++) {
|
|
244
|
-
const cost = b[i - 1] === a[j - 1] ? 0 : 1;
|
|
245
|
-
matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
return matrix[b.length][a.length];
|
|
249
|
-
};
|
|
250
|
-
const suggestTarget = (input, available, maxDistance = 3) => suggestTargets(input, available, 1, maxDistance)[0];
|
|
251
|
-
const suggestTargets = (input, available, limit = 3, maxDistance = 3) => {
|
|
252
|
-
const scored = [];
|
|
253
|
-
for (const candidate of available) {
|
|
254
|
-
const distance = levenshtein(input.toLowerCase(), candidate.toLowerCase());
|
|
255
|
-
if (distance <= maxDistance) {
|
|
256
|
-
scored.push({ distance, name: candidate });
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
scored.sort((a, b) => a.distance - b.distance || a.name.localeCompare(b.name));
|
|
260
|
-
return scored.slice(0, limit).map((s) => s.name);
|
|
261
|
-
};
|
|
262
|
-
const formatTargetList = (targets) => {
|
|
263
|
-
if (targets.length === 0) {
|
|
264
|
-
return " (no targets found)";
|
|
265
|
-
}
|
|
266
|
-
return targets.map((t) => ` - ${t}`).join("\n");
|
|
267
|
-
};
|
|
268
|
-
const promptTargetInteractively = async (targets) => {
|
|
269
|
-
if (targets.length === 0) {
|
|
270
|
-
return void 0;
|
|
271
|
-
}
|
|
272
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
273
|
-
return void 0;
|
|
274
|
-
}
|
|
275
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
276
|
-
try {
|
|
277
|
-
process.stdout.write("Available targets:\n");
|
|
278
|
-
for (const [index, name] of targets.entries()) {
|
|
279
|
-
process.stdout.write(` ${String(index + 1).padStart(2, " ")}. ${name}
|
|
280
|
-
`);
|
|
281
|
-
}
|
|
282
|
-
process.stdout.write("\n");
|
|
283
|
-
const answer = await new Promise((resolve) => {
|
|
284
|
-
rl.question("Select a target (number or name, blank to cancel): ", resolve);
|
|
285
|
-
});
|
|
286
|
-
const trimmed = answer.trim();
|
|
287
|
-
if (trimmed.length === 0) {
|
|
288
|
-
return void 0;
|
|
289
|
-
}
|
|
290
|
-
const asIndex = Number.parseInt(trimmed, 10);
|
|
291
|
-
if (Number.isFinite(asIndex) && asIndex >= 1 && asIndex <= targets.length) {
|
|
292
|
-
return targets[asIndex - 1];
|
|
293
|
-
}
|
|
294
|
-
if (targets.includes(trimmed)) {
|
|
295
|
-
return trimmed;
|
|
296
|
-
}
|
|
297
|
-
return suggestTarget(trimmed, targets);
|
|
298
|
-
} finally {
|
|
299
|
-
rl.close();
|
|
300
|
-
}
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
const formatValue = (value) => {
|
|
304
|
-
if (Array.isArray(value)) {
|
|
305
|
-
return `[${value.join(",")}]`;
|
|
306
|
-
}
|
|
307
|
-
if (typeof value === "object" && value !== null) {
|
|
308
|
-
return JSON.stringify(value);
|
|
309
|
-
}
|
|
310
|
-
return String(value);
|
|
311
|
-
};
|
|
312
|
-
const formatFlags = (leftPad, flag, value) => {
|
|
313
|
-
if (flag === "_") {
|
|
314
|
-
return `${leftPad}${Array.isArray(value) ? value.join(" ") : String(value)}`;
|
|
315
|
-
}
|
|
316
|
-
return `${leftPad}--${flag}=${formatValue(value)}`;
|
|
317
|
-
};
|
|
318
|
-
const formatTargetsAndProjects = (projectNames, targets, tasks) => {
|
|
319
|
-
const uniqueTargets = new Set(new Set(tasks.map((t) => t.target.target)));
|
|
320
|
-
const uniqueProjects = new Set(new Set(tasks.map((t) => t.target.project)));
|
|
321
|
-
const matchedTargets = targets.filter((t) => uniqueTargets.has(t));
|
|
322
|
-
const matchedProjects = projectNames.filter((p) => uniqueProjects.has(p));
|
|
323
|
-
const dependentTaskCount = tasks.length - matchedProjects.length * matchedTargets.length;
|
|
324
|
-
const targetLabel = matchedTargets.length === 1 ? "target" : "targets";
|
|
325
|
-
const targetList = matchedTargets.join(", ");
|
|
326
|
-
let projectLabel;
|
|
327
|
-
projectLabel = matchedProjects.length === 1 ? `project ${matchedProjects[0]}` : `${matchedProjects.length} projects`;
|
|
328
|
-
let result = `${targetLabel} ${targetList} for ${projectLabel}`;
|
|
329
|
-
if (dependentTaskCount > 0) {
|
|
330
|
-
const taskWord = dependentTaskCount === 1 ? "task" : "tasks";
|
|
331
|
-
const pronoun = dependentTaskCount === 1 ? "it depends" : "they depend";
|
|
332
|
-
result += ` and ${dependentTaskCount} ${taskWord} ${pronoun} on`;
|
|
333
|
-
}
|
|
334
|
-
return result;
|
|
335
|
-
};
|
|
336
|
-
|
|
337
|
-
const variantColors = {
|
|
338
|
-
error: "red",
|
|
339
|
-
info: "white",
|
|
340
|
-
success: "green"
|
|
341
|
-
};
|
|
342
|
-
const Header = ({ children, title, variant }) => {
|
|
343
|
-
const color = variantColors[variant];
|
|
344
|
-
return jsxs(Box, { flexDirection: "column", children: [
|
|
345
|
-
jsxs(Box, { gap: 1, children: [
|
|
346
|
-
jsx(Text, { bold: true, inverse: true, children: " VIS " }),
|
|
347
|
-
jsx(Text, { bold: true, color, children: "•" }),
|
|
348
|
-
jsx(Text, { children: title })
|
|
349
|
-
] }),
|
|
350
|
-
children
|
|
351
|
-
] });
|
|
352
|
-
};
|
|
353
|
-
|
|
354
|
-
const CommandSummary = ({ cached, failed, failedIds, projectNames, skippedIds, succeeded, targets, tasks, took }) => {
|
|
355
|
-
const description = formatTargetsAndProjects(projectNames, targets, tasks);
|
|
356
|
-
if (failed === 0 && (!skippedIds || skippedIds.length === 0)) {
|
|
357
|
-
const cacheNote = cached > 0 ? ` (${cached} read from cache)` : "";
|
|
358
|
-
return jsx(StaticRender, { children: () => jsx(Header, { title: `Successfully ran ${description}`, variant: "success", children: jsxs(Box, { flexDirection: "column", paddingLeft: 2, children: [
|
|
359
|
-
jsxs(Text, { children: [
|
|
360
|
-
jsx(Text, { color: "green", children: TICK }),
|
|
361
|
-
" ",
|
|
362
|
-
succeeded + cached,
|
|
363
|
-
" tasks completed",
|
|
364
|
-
cacheNote ? jsx(Text, { dimColor: true, children: cacheNote }) : null
|
|
365
|
-
] }),
|
|
366
|
-
jsxs(Text, { dimColor: true, children: [
|
|
367
|
-
" ",
|
|
368
|
-
"Took",
|
|
369
|
-
took
|
|
370
|
-
] })
|
|
371
|
-
] }) }) });
|
|
372
|
-
}
|
|
373
|
-
return jsx(StaticRender, { children: () => jsx(Header, { title: `Ran ${description}`, variant: "error", children: jsxs(Box, { flexDirection: "column", paddingLeft: 2, children: [
|
|
374
|
-
skippedIds && skippedIds.length > 0 && jsxs(Box, { flexDirection: "column", children: [
|
|
375
|
-
jsxs(Text, { dimColor: true, children: [
|
|
376
|
-
skippedIds.length,
|
|
377
|
-
" task",
|
|
378
|
-
skippedIds.length === 1 ? "" : "s",
|
|
379
|
-
" skipped (dependency failed or --bail)"
|
|
380
|
-
] }),
|
|
381
|
-
skippedIds.map((id) => jsxs(Text, { dimColor: true, children: [
|
|
382
|
-
" - ",
|
|
383
|
-
id
|
|
384
|
-
] }, id)),
|
|
385
|
-
jsx(Text, {})
|
|
386
|
-
] }),
|
|
387
|
-
failed > 0 && jsxs(Box, { flexDirection: "column", children: [
|
|
388
|
-
jsxs(Text, { children: [
|
|
389
|
-
jsx(Text, { color: "red", children: String(failed) }),
|
|
390
|
-
" task",
|
|
391
|
-
failed === 1 ? "" : "s",
|
|
392
|
-
" failed:"
|
|
393
|
-
] }),
|
|
394
|
-
failedIds.map((id) => jsxs(Text, { children: [
|
|
395
|
-
" ",
|
|
396
|
-
jsx(Text, { color: "red", children: CROSS }),
|
|
397
|
-
" ",
|
|
398
|
-
id
|
|
399
|
-
] }, id)),
|
|
400
|
-
jsx(Text, {})
|
|
401
|
-
] }),
|
|
402
|
-
jsxs(Text, { dimColor: true, children: [
|
|
403
|
-
" ",
|
|
404
|
-
"Took",
|
|
405
|
-
took
|
|
406
|
-
] })
|
|
407
|
-
] }) }) });
|
|
408
|
-
};
|
|
409
|
-
|
|
410
|
-
class TaskStore {
|
|
411
|
-
#state;
|
|
412
|
-
#listeners = /* @__PURE__ */ new Set();
|
|
413
|
-
#hrtimeStarts = /* @__PURE__ */ new Map();
|
|
414
|
-
constructor(tasks) {
|
|
415
|
-
this.#state = {
|
|
416
|
-
autoExitCountdown: null,
|
|
417
|
-
cached: 0,
|
|
418
|
-
completed: 0,
|
|
419
|
-
done: false,
|
|
420
|
-
endTime: null,
|
|
421
|
-
failed: 0,
|
|
422
|
-
filterActive: false,
|
|
423
|
-
filterText: "",
|
|
424
|
-
focusedPanel: "tasks",
|
|
425
|
-
interactiveMode: false,
|
|
426
|
-
outputs: /* @__PURE__ */ new Map(),
|
|
427
|
-
pinnedTaskIds: [null, null],
|
|
428
|
-
rerunRequested: false,
|
|
429
|
-
retryTaskId: null,
|
|
430
|
-
rows: tasks.map((t) => {
|
|
431
|
-
return { status: "pending", taskId: t.id };
|
|
432
|
-
}),
|
|
433
|
-
selectedIndex: 0,
|
|
434
|
-
startTime: Date.now(),
|
|
435
|
-
statusFilter: "all",
|
|
436
|
-
succeeded: 0,
|
|
437
|
-
viewMode: "list"
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
// ── React integration ───────────────────────────────────────────
|
|
441
|
-
getSnapshot = () => this.#state;
|
|
442
|
-
subscribe = (listener) => {
|
|
443
|
-
this.#listeners.add(listener);
|
|
444
|
-
return () => {
|
|
445
|
-
this.#listeners.delete(listener);
|
|
446
|
-
};
|
|
447
|
-
};
|
|
448
|
-
// ── Lifecycle methods (called by task runner) ───────────────────
|
|
449
|
-
startTasks(started) {
|
|
450
|
-
const rows = [...this.#state.rows];
|
|
451
|
-
for (const t of started) {
|
|
452
|
-
const index = rows.findIndex((r) => r.taskId === t.id);
|
|
453
|
-
if (index !== -1) {
|
|
454
|
-
rows[index] = { ...rows[index], elapsed: 0, status: "running" };
|
|
455
|
-
this.#hrtimeStarts.set(t.id, process.hrtime());
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
this.#emit({ ...this.#state, rows });
|
|
459
|
-
}
|
|
460
|
-
endTasks(results) {
|
|
461
|
-
const rows = [...this.#state.rows];
|
|
462
|
-
let { cached, completed, failed, succeeded } = this.#state;
|
|
463
|
-
const outputs = new Map(this.#state.outputs);
|
|
464
|
-
for (const res of results) {
|
|
465
|
-
const index = rows.findIndex((r) => r.taskId === res.task.id);
|
|
466
|
-
if (index !== -1) {
|
|
467
|
-
rows[index] = {
|
|
468
|
-
...rows[index],
|
|
469
|
-
duration: res.startTime && res.endTime ? res.endTime - res.startTime : void 0,
|
|
470
|
-
status: res.status
|
|
471
|
-
};
|
|
472
|
-
}
|
|
473
|
-
completed++;
|
|
474
|
-
switch (res.status) {
|
|
475
|
-
case "failure": {
|
|
476
|
-
failed++;
|
|
477
|
-
break;
|
|
478
|
-
}
|
|
479
|
-
case "local-cache":
|
|
480
|
-
case "local-cache-kept-existing":
|
|
481
|
-
case "remote-cache": {
|
|
482
|
-
cached++;
|
|
483
|
-
break;
|
|
484
|
-
}
|
|
485
|
-
case "success": {
|
|
486
|
-
succeeded++;
|
|
487
|
-
break;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
if (res.terminalOutput && !outputs.has(res.task.id)) {
|
|
491
|
-
outputs.set(res.task.id, res.terminalOutput);
|
|
492
|
-
}
|
|
493
|
-
this.#hrtimeStarts.delete(res.task.id);
|
|
494
|
-
}
|
|
495
|
-
let { selectedIndex } = this.#state;
|
|
496
|
-
if (failed > this.#state.failed) {
|
|
497
|
-
const firstFailureIndex = rows.findIndex((r) => r.status === "failure");
|
|
498
|
-
if (firstFailureIndex !== -1) {
|
|
499
|
-
selectedIndex = firstFailureIndex;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
this.#emit({ ...this.#state, cached, completed, failed, outputs, rows, selectedIndex, succeeded });
|
|
503
|
-
}
|
|
504
|
-
/** Maximum output stored per task (256 KB). Prevents OOM with long-running dev servers. */
|
|
505
|
-
static #MAX_OUTPUT_BYTES = 256 * 1024;
|
|
506
|
-
addOutput(taskId, output) {
|
|
507
|
-
if (!output.trim()) {
|
|
508
|
-
return;
|
|
509
|
-
}
|
|
510
|
-
let current = (this.#state.outputs.get(taskId) ?? "") + output;
|
|
511
|
-
if (current.length > TaskStore.#MAX_OUTPUT_BYTES) {
|
|
512
|
-
current = current.slice(-TaskStore.#MAX_OUTPUT_BYTES);
|
|
513
|
-
}
|
|
514
|
-
this.#state.outputs.set(taskId, current);
|
|
515
|
-
this.#emit({ ...this.#state });
|
|
516
|
-
}
|
|
517
|
-
/** Replace the full output for a task (used by PTY mode where ANSI sequences update in place). */
|
|
518
|
-
setOutput(taskId, content) {
|
|
519
|
-
this.#state.outputs.set(taskId, content);
|
|
520
|
-
this.#emit({ ...this.#state });
|
|
521
|
-
}
|
|
522
|
-
markDone() {
|
|
523
|
-
this.#emit({ ...this.#state, done: true, endTime: Date.now() });
|
|
524
|
-
}
|
|
525
|
-
/** Update elapsed times for running tasks. Called every 100ms. */
|
|
526
|
-
tick() {
|
|
527
|
-
if (this.#hrtimeStarts.size === 0) {
|
|
528
|
-
return;
|
|
529
|
-
}
|
|
530
|
-
let changed = false;
|
|
531
|
-
const rows = [...this.#state.rows];
|
|
532
|
-
for (let i = 0; i < rows.length; i++) {
|
|
533
|
-
const row = rows[i];
|
|
534
|
-
if (row.status === "running") {
|
|
535
|
-
const start = this.#hrtimeStarts.get(row.taskId);
|
|
536
|
-
if (start) {
|
|
537
|
-
const d = process.hrtime(start);
|
|
538
|
-
const ms = d[0] * 1e3 + d[1] / 1e6;
|
|
539
|
-
rows[i] = { ...row, elapsed: ms };
|
|
540
|
-
changed = true;
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
if (changed) {
|
|
545
|
-
this.#emit({ ...this.#state, rows });
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
// ── UI state methods (called by React components) ───────────────
|
|
549
|
-
setSelectedIndex(index) {
|
|
550
|
-
if (index !== this.#state.selectedIndex) {
|
|
551
|
-
this.#emit({ ...this.#state, selectedIndex: index });
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
setFocusedPanel(panel) {
|
|
555
|
-
if (panel !== this.#state.focusedPanel) {
|
|
556
|
-
this.#emit({ ...this.#state, focusedPanel: panel });
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
setFilter(text) {
|
|
560
|
-
this.#emit({ ...this.#state, filterText: text, selectedIndex: 0 });
|
|
561
|
-
}
|
|
562
|
-
setFilterActive(active) {
|
|
563
|
-
if (active !== this.#state.filterActive) {
|
|
564
|
-
this.#emit({
|
|
565
|
-
...this.#state,
|
|
566
|
-
filterActive: active,
|
|
567
|
-
filterText: active ? this.#state.filterText : "",
|
|
568
|
-
selectedIndex: 0
|
|
569
|
-
});
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
pinTask(slot, taskId) {
|
|
573
|
-
const pins = [...this.#state.pinnedTaskIds];
|
|
574
|
-
pins[slot] = taskId;
|
|
575
|
-
this.#emit({ ...this.#state, pinnedTaskIds: pins });
|
|
576
|
-
}
|
|
577
|
-
clearPins() {
|
|
578
|
-
this.#emit({ ...this.#state, pinnedTaskIds: [null, null] });
|
|
579
|
-
}
|
|
580
|
-
/** Request retry of a single failed task. */
|
|
581
|
-
requestRetry(taskId) {
|
|
582
|
-
const rows = [...this.#state.rows];
|
|
583
|
-
const index = rows.findIndex((r) => r.taskId === taskId);
|
|
584
|
-
let { completed, failed, succeeded } = this.#state;
|
|
585
|
-
if (index !== -1) {
|
|
586
|
-
const previousStatus = rows[index].status;
|
|
587
|
-
if (previousStatus === "failure") {
|
|
588
|
-
failed = Math.max(0, failed - 1);
|
|
589
|
-
completed = Math.max(0, completed - 1);
|
|
590
|
-
} else if (previousStatus === "success") {
|
|
591
|
-
succeeded = Math.max(0, succeeded - 1);
|
|
592
|
-
completed = Math.max(0, completed - 1);
|
|
593
|
-
}
|
|
594
|
-
rows[index] = { ...rows[index], elapsed: 0, status: "running" };
|
|
595
|
-
this.#hrtimeStarts.set(taskId, process.hrtime());
|
|
596
|
-
}
|
|
597
|
-
this.#emit({
|
|
598
|
-
...this.#state,
|
|
599
|
-
completed,
|
|
600
|
-
done: false,
|
|
601
|
-
endTime: null,
|
|
602
|
-
failed,
|
|
603
|
-
interactiveMode: false,
|
|
604
|
-
retryTaskId: taskId,
|
|
605
|
-
rows,
|
|
606
|
-
succeeded
|
|
607
|
-
});
|
|
608
|
-
}
|
|
609
|
-
/** Acknowledge the retry request (called by lifecycle after re-launching). */
|
|
610
|
-
acknowledgeRetry() {
|
|
611
|
-
const id = this.#state.retryTaskId;
|
|
612
|
-
if (id) {
|
|
613
|
-
this.#emit({ ...this.#state, retryTaskId: null });
|
|
614
|
-
}
|
|
615
|
-
return id;
|
|
616
|
-
}
|
|
617
|
-
/** Toggle interactive input mode for the current task. */
|
|
618
|
-
setInteractiveMode(active) {
|
|
619
|
-
if (active !== this.#state.interactiveMode) {
|
|
620
|
-
this.#emit({ ...this.#state, interactiveMode: active });
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
/** Set the current view mode. */
|
|
624
|
-
setViewMode(mode) {
|
|
625
|
-
if (mode !== this.#state.viewMode) {
|
|
626
|
-
this.#emit({ ...this.#state, viewMode: mode });
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
/** Set status filter for the task list. */
|
|
630
|
-
setStatusFilter(filter) {
|
|
631
|
-
this.#emit({ ...this.#state, selectedIndex: 0, statusFilter: filter });
|
|
632
|
-
}
|
|
633
|
-
/** Request a rerun — resets all task state back to pending. */
|
|
634
|
-
requestRerun() {
|
|
635
|
-
this.#hrtimeStarts.clear();
|
|
636
|
-
this.#emit({
|
|
637
|
-
...this.#state,
|
|
638
|
-
autoExitCountdown: null,
|
|
639
|
-
cached: 0,
|
|
640
|
-
completed: 0,
|
|
641
|
-
done: false,
|
|
642
|
-
endTime: null,
|
|
643
|
-
failed: 0,
|
|
644
|
-
interactiveMode: false,
|
|
645
|
-
outputs: /* @__PURE__ */ new Map(),
|
|
646
|
-
rerunRequested: true,
|
|
647
|
-
rows: this.#state.rows.map((r) => {
|
|
648
|
-
return { status: "pending", taskId: r.taskId };
|
|
649
|
-
}),
|
|
650
|
-
startTime: Date.now(),
|
|
651
|
-
succeeded: 0,
|
|
652
|
-
viewMode: "list"
|
|
653
|
-
});
|
|
654
|
-
}
|
|
655
|
-
/** Acknowledge the rerun request (called by lifecycle after re-launching tasks). */
|
|
656
|
-
acknowledgeRerun() {
|
|
657
|
-
if (this.#state.rerunRequested) {
|
|
658
|
-
this.#emit({ ...this.#state, rerunRequested: false });
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
// ── Internal ────────────────────────────────────────────────────
|
|
662
|
-
#emit(newState) {
|
|
663
|
-
this.#state = newState;
|
|
664
|
-
for (const listener of this.#listeners) {
|
|
665
|
-
try {
|
|
666
|
-
listener();
|
|
667
|
-
} catch {
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
const shortOptions = {
|
|
674
|
-
delimiter: " ",
|
|
675
|
-
language: {
|
|
676
|
-
d: () => " d",
|
|
677
|
-
future: "in %s",
|
|
678
|
-
h: () => " h",
|
|
679
|
-
m: () => " m",
|
|
680
|
-
mo: () => " mo",
|
|
681
|
-
ms: () => " ms",
|
|
682
|
-
past: "%s ago",
|
|
683
|
-
s: () => " s",
|
|
684
|
-
w: () => " w",
|
|
685
|
-
y: () => " y"
|
|
686
|
-
},
|
|
687
|
-
largest: 2,
|
|
688
|
-
round: true,
|
|
689
|
-
spacer: "",
|
|
690
|
-
units: ["h", "m", "s", "ms"]
|
|
691
|
-
};
|
|
692
|
-
const formatMs = (ms) => duration(ms, shortOptions);
|
|
693
|
-
|
|
694
|
-
const isCacheStatus$1 = (status) => status === "local-cache" || status === "local-cache-kept-existing" || status === "remote-cache";
|
|
695
|
-
const getStatusInfo = (status) => {
|
|
696
|
-
switch (status) {
|
|
697
|
-
case "failure": {
|
|
698
|
-
return { color: "red", icon: CROSS };
|
|
699
|
-
}
|
|
700
|
-
case "local-cache":
|
|
701
|
-
case "local-cache-kept-existing":
|
|
702
|
-
case "remote-cache":
|
|
703
|
-
case "success": {
|
|
704
|
-
return { color: "green", icon: TICK };
|
|
705
|
-
}
|
|
706
|
-
case "skipped": {
|
|
707
|
-
return { color: "gray", icon: DASH };
|
|
708
|
-
}
|
|
709
|
-
default: {
|
|
710
|
-
return { color: "gray", icon: "?" };
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
};
|
|
714
|
-
const getStatusIcon = (status) => {
|
|
715
|
-
const { color, icon } = getStatusInfo(status);
|
|
716
|
-
return renderToString(React.createElement(Text, { color }, icon), { columns: 10 }).trim();
|
|
717
|
-
};
|
|
718
|
-
const getStatusPrefix = (status) => {
|
|
719
|
-
const { color, icon } = getStatusInfo(status);
|
|
720
|
-
switch (status) {
|
|
721
|
-
case "local-cache":
|
|
722
|
-
case "local-cache-kept-existing":
|
|
723
|
-
case "remote-cache": {
|
|
724
|
-
return renderToString(
|
|
725
|
-
React.createElement(Text, null, React.createElement(Text, { color }, icon), " ", React.createElement(Text, { color: "cyan" }, "[cache]")),
|
|
726
|
-
{ columns: 30 }
|
|
727
|
-
).trim();
|
|
728
|
-
}
|
|
729
|
-
case "skipped": {
|
|
730
|
-
return renderToString(
|
|
731
|
-
React.createElement(
|
|
732
|
-
Text,
|
|
733
|
-
null,
|
|
734
|
-
React.createElement(Text, { dimColor: true }, icon),
|
|
735
|
-
" ",
|
|
736
|
-
React.createElement(Text, { dimColor: true }, "[skipped]")
|
|
737
|
-
),
|
|
738
|
-
{ columns: 30 }
|
|
739
|
-
).trim();
|
|
740
|
-
}
|
|
741
|
-
default: {
|
|
742
|
-
return renderToString(React.createElement(Text, { color }, icon), { columns: 10 }).trim();
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
};
|
|
746
|
-
const logCommandOutputCI = (taskId, status, output) => {
|
|
747
|
-
const trimmed = output.trim();
|
|
748
|
-
if (!trimmed) {
|
|
749
|
-
return;
|
|
750
|
-
}
|
|
751
|
-
const EOL = "\n";
|
|
752
|
-
const isGitHubActions = process.env["GITHUB_ACTIONS"] === "true";
|
|
753
|
-
if (isGitHubActions) {
|
|
754
|
-
process.stdout.write(`::group::${getStatusIcon(status)} ${taskId}${EOL}`);
|
|
755
|
-
process.stdout.write(trimmed + EOL);
|
|
756
|
-
process.stdout.write(`::endgroup::${EOL}`);
|
|
757
|
-
} else {
|
|
758
|
-
const width = process.stdout.columns || 80;
|
|
759
|
-
const separator = renderToString(React.createElement(Text, { dimColor: true }, DASH.repeat(width)), { columns: width }).trim();
|
|
760
|
-
const prefix = getStatusPrefix(status);
|
|
761
|
-
const boldTaskId = renderToString(React.createElement(Text, { bold: true }, taskId), { columns: width }).trim();
|
|
762
|
-
process.stdout.write(`${separator}${EOL}`);
|
|
763
|
-
process.stdout.write(`${prefix} ${boldTaskId}${EOL}`);
|
|
764
|
-
process.stdout.write(trimmed + EOL);
|
|
765
|
-
process.stdout.write(`${separator}${EOL}`);
|
|
766
|
-
}
|
|
767
|
-
};
|
|
768
|
-
|
|
769
|
-
const getDisplayInfo = (status) => {
|
|
770
|
-
if (status === "running") {
|
|
771
|
-
return { color: "white", icon: "•" };
|
|
772
|
-
}
|
|
773
|
-
if (status === "pending") {
|
|
774
|
-
return { color: "gray", icon: "·" };
|
|
775
|
-
}
|
|
776
|
-
return getStatusInfo(status);
|
|
777
|
-
};
|
|
778
|
-
const OutputPanel = ({ duration, focused, interactiveMode, output, scrollRef, showFullscreenHint, status, taskId }) => {
|
|
779
|
-
const statusValue = status ?? "pending";
|
|
780
|
-
const { icon: statusIcon } = getDisplayInfo(statusValue);
|
|
781
|
-
const borderStyle = focused ? "bold" : "single";
|
|
782
|
-
const borderColor = (() => {
|
|
783
|
-
if (statusValue === "failure") {
|
|
784
|
-
return "red";
|
|
785
|
-
}
|
|
786
|
-
if (statusValue === "success" || isCacheStatus$1(statusValue)) {
|
|
787
|
-
return focused ? "green" : "gray";
|
|
788
|
-
}
|
|
789
|
-
if (statusValue === "running") {
|
|
790
|
-
return focused ? "white" : "cyan";
|
|
791
|
-
}
|
|
792
|
-
return focused ? "white" : "gray";
|
|
793
|
-
})();
|
|
794
|
-
const topTitle = taskId ? `${statusIcon} ${taskId}` : void 0;
|
|
795
|
-
const topRightTitle = duration === void 0 ? void 0 : formatMs(duration);
|
|
796
|
-
const bottomTitle = taskId ? interactiveMode ? "Esc cancel | Enter send" : focused && statusValue === "running" && showFullscreenHint ? "⏎ FULLSCREEN i INPUT" : focused && statusValue === "running" ? "i INPUT" : focused && showFullscreenHint ? "<enter> full screen" : focused ? void 0 : "<tab> or <enter> to focus" : void 0;
|
|
797
|
-
if (!taskId) {
|
|
798
|
-
return jsxs(
|
|
799
|
-
Box,
|
|
800
|
-
{
|
|
801
|
-
alignItems: "center",
|
|
802
|
-
borderColor: "gray",
|
|
803
|
-
borderStyle: "single",
|
|
804
|
-
flexDirection: "column",
|
|
805
|
-
flexGrow: 1,
|
|
806
|
-
justifyContent: "center",
|
|
807
|
-
paddingX: 2,
|
|
808
|
-
paddingY: 1,
|
|
809
|
-
children: [
|
|
810
|
-
jsx(Text, { dimColor: true, children: "Select a task to view output" }),
|
|
811
|
-
jsx(Text, { dimColor: true, children: "Press Enter or 1/2 to pin" })
|
|
812
|
-
]
|
|
813
|
-
}
|
|
814
|
-
);
|
|
815
|
-
}
|
|
816
|
-
const lines = output ? output.split("\n").map((l) => l.replace(/\r$/, "")) : [];
|
|
817
|
-
if (!output && (statusValue === "running" || statusValue === "pending")) {
|
|
818
|
-
return jsx(
|
|
819
|
-
Box,
|
|
820
|
-
{
|
|
821
|
-
borderBottomTitle: bottomTitle,
|
|
822
|
-
borderColor,
|
|
823
|
-
borderStyle,
|
|
824
|
-
borderTopRightTitle: topRightTitle,
|
|
825
|
-
borderTopTitle: topTitle,
|
|
826
|
-
flexDirection: "column",
|
|
827
|
-
flexGrow: 1,
|
|
828
|
-
paddingX: 2,
|
|
829
|
-
paddingY: 1,
|
|
830
|
-
children: jsx(Box, { alignItems: "center", flexGrow: 1, justifyContent: "center", children: jsx(Text, { dimColor: true, children: "Waiting for task output..." }) })
|
|
831
|
-
}
|
|
832
|
-
);
|
|
833
|
-
}
|
|
834
|
-
return jsxs(
|
|
835
|
-
Box,
|
|
836
|
-
{
|
|
837
|
-
borderBottomTitle: bottomTitle,
|
|
838
|
-
borderColor: interactiveMode ? "yellow" : borderColor,
|
|
839
|
-
borderStyle,
|
|
840
|
-
borderTopRightTitle: topRightTitle,
|
|
841
|
-
borderTopTitle: topTitle,
|
|
842
|
-
flexDirection: "column",
|
|
843
|
-
flexGrow: 1,
|
|
844
|
-
children: [
|
|
845
|
-
jsx(Box, { flexGrow: 1, flexShrink: 1, paddingY: 1, children: jsx(ScrollView, { flexGrow: 1, followOutput: true, paddingX: 2, ref: scrollRef, scrollbar: true, scrollbarColor: "gray", scrollbarStyle: "block", children: lines.map((line, i) => jsx(Text, { children: line }, String(i))) }) }),
|
|
846
|
-
interactiveMode && jsx(Box, { flexShrink: 0, justifyContent: "center", paddingX: 1, children: jsx(Text, { bold: true, color: "yellow", children: "INTERACTIVE — keystrokes forwarded to task — Esc to exit" }) })
|
|
847
|
-
]
|
|
848
|
-
}
|
|
849
|
-
);
|
|
850
|
-
};
|
|
851
|
-
|
|
852
|
-
const STATUS_ICON_WIDTH = 6;
|
|
853
|
-
const CACHE_COLUMN_WIDTH = 8;
|
|
854
|
-
const DURATION_COLUMN_WIDTH = 10;
|
|
855
|
-
const getCacheLabel = (status) => {
|
|
856
|
-
if (status === "running" || status === "pending") {
|
|
857
|
-
return ELLIPSIS;
|
|
858
|
-
}
|
|
859
|
-
if (status === "local-cache" || status === "local-cache-kept-existing") {
|
|
860
|
-
return "Local";
|
|
861
|
-
}
|
|
862
|
-
if (status === "remote-cache") {
|
|
863
|
-
return "Remote";
|
|
864
|
-
}
|
|
865
|
-
return DASH;
|
|
866
|
-
};
|
|
867
|
-
const getPinLabel = (taskId, pinnedTaskIds) => pinnedTaskIds[0] === taskId ? "[1]" : pinnedTaskIds[1] === taskId ? "[2]" : "";
|
|
868
|
-
const TaskListRow = ({ compact, isSelected, pinLabel, row }) => {
|
|
869
|
-
const { status, taskId } = row;
|
|
870
|
-
const selectChar = isSelected ? ">" : " ";
|
|
871
|
-
let statusIcon;
|
|
872
|
-
if (status === "running") {
|
|
873
|
-
statusIcon = jsxs(Text, { bold: true, color: "white", children: [
|
|
874
|
-
" ",
|
|
875
|
-
jsx(Spinner, { type: "dots" }),
|
|
876
|
-
" "
|
|
877
|
-
] });
|
|
878
|
-
} else if (status === "pending") {
|
|
879
|
-
statusIcon = jsx(Text, { bold: true, color: "gray", children: " · " });
|
|
880
|
-
} else {
|
|
881
|
-
const { color, icon } = getStatusInfo(status);
|
|
882
|
-
statusIcon = jsxs(Text, { bold: true, color, children: [
|
|
883
|
-
" ",
|
|
884
|
-
icon,
|
|
885
|
-
" "
|
|
886
|
-
] });
|
|
887
|
-
}
|
|
888
|
-
let durationText = ELLIPSIS;
|
|
889
|
-
if (status !== "running" && status !== "pending") {
|
|
890
|
-
durationText = row.duration === void 0 ? DASH : formatMs(row.duration);
|
|
891
|
-
} else if (status === "running" && row.elapsed !== void 0) {
|
|
892
|
-
durationText = formatMs(row.elapsed);
|
|
893
|
-
}
|
|
894
|
-
return jsxs(Box, { children: [
|
|
895
|
-
jsx(Text, { children: selectChar }),
|
|
896
|
-
jsx(Box, { width: STATUS_ICON_WIDTH, children: statusIcon }),
|
|
897
|
-
jsxs(Box, { flexGrow: 1, children: [
|
|
898
|
-
jsx(Text, { bold: isSelected, inverse: isSelected, children: taskId }),
|
|
899
|
-
pinLabel ? jsx(Text, { dimColor: true, children: ` ${pinLabel}` }) : null
|
|
900
|
-
] }),
|
|
901
|
-
!compact && jsx(Box, { justifyContent: "flex-end", width: CACHE_COLUMN_WIDTH, children: jsx(Text, { dimColor: !isCacheStatus$1(status), children: getCacheLabel(status) }) }),
|
|
902
|
-
!compact && jsx(Box, { justifyContent: "flex-end", width: DURATION_COLUMN_WIDTH, children: jsx(Text, { dimColor: status === "pending", children: durationText }) })
|
|
903
|
-
] });
|
|
904
|
-
};
|
|
905
|
-
const TaskListPanel = ({
|
|
906
|
-
compact,
|
|
907
|
-
filterActive,
|
|
908
|
-
filterText,
|
|
909
|
-
focused,
|
|
910
|
-
headerStatus,
|
|
911
|
-
parallelSlots = 3,
|
|
912
|
-
pinnedTaskIds,
|
|
913
|
-
rows,
|
|
914
|
-
scrollRef,
|
|
915
|
-
selectedIndex,
|
|
916
|
-
title
|
|
917
|
-
}) => {
|
|
918
|
-
const borderColor = (() => {
|
|
919
|
-
if (headerStatus === "error") {
|
|
920
|
-
return "red";
|
|
921
|
-
}
|
|
922
|
-
if (headerStatus === "success") {
|
|
923
|
-
return "green";
|
|
924
|
-
}
|
|
925
|
-
return focused ? "white" : "gray";
|
|
926
|
-
})();
|
|
927
|
-
const selectedTaskId = rows[selectedIndex]?.taskId;
|
|
928
|
-
const runningRows = rows.filter((r) => r.status === "running");
|
|
929
|
-
const hasActiveWork = runningRows.length > 0 || rows.some((r) => r.status === "pending");
|
|
930
|
-
const parallelElements = [];
|
|
931
|
-
if (hasActiveWork) {
|
|
932
|
-
for (let i = 0; i < parallelSlots; i++) {
|
|
933
|
-
const row = runningRows[i];
|
|
934
|
-
if (row) {
|
|
935
|
-
parallelElements.push(
|
|
936
|
-
jsx(
|
|
937
|
-
TaskListRow,
|
|
938
|
-
{
|
|
939
|
-
compact,
|
|
940
|
-
isSelected: row.taskId === selectedTaskId,
|
|
941
|
-
pinLabel: getPinLabel(row.taskId, pinnedTaskIds),
|
|
942
|
-
row
|
|
943
|
-
},
|
|
944
|
-
`par-${row.taskId}`
|
|
945
|
-
)
|
|
946
|
-
);
|
|
947
|
-
} else {
|
|
948
|
-
parallelElements.push(
|
|
949
|
-
jsxs(Box, { children: [
|
|
950
|
-
jsx(Text, { children: " " }),
|
|
951
|
-
jsx(Box, { width: STATUS_ICON_WIDTH, children: jsx(Text, { bold: true, color: "gray", children: " · " }) }),
|
|
952
|
-
jsx(Text, { dimColor: true, children: "Waiting for task..." })
|
|
953
|
-
] }, `par-empty-${String(i)}`)
|
|
954
|
-
);
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
return jsxs(Box, { borderColor, borderStyle: "single", flexDirection: "column", flexGrow: 1, children: [
|
|
959
|
-
jsxs(Box, { flexShrink: 0, gap: 1, paddingX: 1, children: [
|
|
960
|
-
jsx(Text, { bold: true, inverse: true, children: " VIS " }),
|
|
961
|
-
jsx(Text, { children: title }),
|
|
962
|
-
!compact && jsxs(Box, { flexGrow: 1, gap: 0, justifyContent: "flex-end", children: [
|
|
963
|
-
jsx(Box, { justifyContent: "flex-end", width: CACHE_COLUMN_WIDTH, children: jsx(Text, { dimColor: true, children: "Cache" }) }),
|
|
964
|
-
jsx(Box, { justifyContent: "flex-end", width: DURATION_COLUMN_WIDTH, children: jsx(Text, { dimColor: true, children: "Duration" }) })
|
|
965
|
-
] })
|
|
966
|
-
] }),
|
|
967
|
-
jsx(ScrollView, { flexGrow: 1, flexShrink: 1, paddingLeft: 1, paddingY: 1, ref: scrollRef, scrollbar: true, scrollbarColor: "gray", scrollbarStyle: "block", children: rows.map((row) => jsx(
|
|
968
|
-
TaskListRow,
|
|
969
|
-
{
|
|
970
|
-
compact,
|
|
971
|
-
isSelected: row.taskId === selectedTaskId,
|
|
972
|
-
pinLabel: getPinLabel(row.taskId, pinnedTaskIds),
|
|
973
|
-
row
|
|
974
|
-
},
|
|
975
|
-
row.taskId
|
|
976
|
-
)) }),
|
|
977
|
-
hasActiveWork && jsx(
|
|
978
|
-
Box,
|
|
979
|
-
{
|
|
980
|
-
borderBottom: false,
|
|
981
|
-
borderColor: "gray",
|
|
982
|
-
borderLeft: false,
|
|
983
|
-
borderRight: false,
|
|
984
|
-
borderStyle: "single",
|
|
985
|
-
borderTop: true,
|
|
986
|
-
flexDirection: "column",
|
|
987
|
-
flexShrink: 0,
|
|
988
|
-
paddingLeft: 1,
|
|
989
|
-
paddingY: 1,
|
|
990
|
-
children: parallelElements
|
|
991
|
-
}
|
|
992
|
-
),
|
|
993
|
-
filterActive && jsxs(Box, { borderBottom: false, borderColor: "gray", borderLeft: false, borderRight: false, borderStyle: "single", borderTop: true, flexShrink: 0, paddingX: 1, children: [
|
|
994
|
-
jsx(Text, { bold: true, color: "white", children: "/ " }),
|
|
995
|
-
jsx(Text, { children: filterText }),
|
|
996
|
-
jsx(Text, { inverse: true, children: " " })
|
|
997
|
-
] })
|
|
998
|
-
] });
|
|
999
|
-
};
|
|
1000
|
-
|
|
1001
|
-
const MIN_VIEWPORT_WIDTH = 40;
|
|
1002
|
-
const MIN_VIEWPORT_HEIGHT = 10;
|
|
1003
|
-
const MIN_HORIZONTAL_WIDTH = 100;
|
|
1004
|
-
const VisTaskRunnerApp = ({ autoExitSeconds, parallelSlots, projectNames, stdinRegistry, store, targets, tasks }) => {
|
|
1005
|
-
const { exit } = useApp();
|
|
1006
|
-
const { columns, rows } = useWindowSize();
|
|
1007
|
-
const state = useSyncExternalStore(store.subscribe, store.getSnapshot);
|
|
1008
|
-
const [helpVisible, setHelpVisible] = useState(false);
|
|
1009
|
-
const helpScrollRef = useRef(null);
|
|
1010
|
-
const listScrollRef = useRef(null);
|
|
1011
|
-
const outputScrollRef = useRef(null);
|
|
1012
|
-
const [quitDialogVisible, setQuitDialogVisible] = useState(false);
|
|
1013
|
-
const savedScrollRef = useRef({
|
|
1014
|
-
list: 0,
|
|
1015
|
-
splitList: 0,
|
|
1016
|
-
splitOutput: 0
|
|
1017
|
-
});
|
|
1018
|
-
const saveScrollPositions = useCallback(() => {
|
|
1019
|
-
if (state.viewMode === "list") {
|
|
1020
|
-
savedScrollRef.current.list = listScrollRef.current?.getScrollOffset() ?? 0;
|
|
1021
|
-
} else if (state.viewMode === "split") {
|
|
1022
|
-
savedScrollRef.current.splitList = listScrollRef.current?.getScrollOffset() ?? 0;
|
|
1023
|
-
savedScrollRef.current.splitOutput = outputScrollRef.current?.getScrollOffset() ?? 0;
|
|
1024
|
-
}
|
|
1025
|
-
}, [state.viewMode]);
|
|
1026
|
-
const restoreScrollPositions = useCallback(
|
|
1027
|
-
(targetMode) => {
|
|
1028
|
-
setTimeout(() => {
|
|
1029
|
-
if (targetMode === "list") {
|
|
1030
|
-
const saved = savedScrollRef.current.list;
|
|
1031
|
-
listScrollRef.current?.scrollTo(saved);
|
|
1032
|
-
} else if (targetMode === "split") {
|
|
1033
|
-
const savedList = savedScrollRef.current.splitList;
|
|
1034
|
-
if (savedList > 0) {
|
|
1035
|
-
listScrollRef.current?.scrollTo(savedList);
|
|
1036
|
-
} else {
|
|
1037
|
-
listScrollRef.current?.scrollTo(Math.max(0, store.getSnapshot().selectedIndex - 2));
|
|
1038
|
-
}
|
|
1039
|
-
outputScrollRef.current?.scrollTo(savedScrollRef.current.splitOutput);
|
|
1040
|
-
}
|
|
1041
|
-
}, 0);
|
|
1042
|
-
},
|
|
1043
|
-
[store]
|
|
1044
|
-
);
|
|
1045
|
-
const previousDoneRef = useRef(false);
|
|
1046
|
-
useEffect(() => {
|
|
1047
|
-
if (state.done && !previousDoneRef.current) {
|
|
1048
|
-
previousDoneRef.current = true;
|
|
1049
|
-
if (autoExitSeconds > 0) {
|
|
1050
|
-
setQuitDialogVisible(true);
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
if (!state.done && previousDoneRef.current) {
|
|
1054
|
-
previousDoneRef.current = false;
|
|
1055
|
-
setQuitDialogVisible(false);
|
|
1056
|
-
}
|
|
1057
|
-
}, [state.done, autoExitSeconds]);
|
|
1058
|
-
const filteredRows = useMemo(() => {
|
|
1059
|
-
let filtered = state.rows;
|
|
1060
|
-
if (state.statusFilter !== "all") {
|
|
1061
|
-
filtered = filtered.filter((r) => {
|
|
1062
|
-
if (state.statusFilter === "failed") {
|
|
1063
|
-
return r.status === "failure";
|
|
1064
|
-
}
|
|
1065
|
-
if (state.statusFilter === "running") {
|
|
1066
|
-
return r.status === "running" || r.status === "pending";
|
|
1067
|
-
}
|
|
1068
|
-
if (state.statusFilter === "passed") {
|
|
1069
|
-
return r.status === "success" || isCacheStatus$1(r.status);
|
|
1070
|
-
}
|
|
1071
|
-
return true;
|
|
1072
|
-
});
|
|
1073
|
-
}
|
|
1074
|
-
if (state.filterText) {
|
|
1075
|
-
const lower = state.filterText.toLowerCase();
|
|
1076
|
-
filtered = filtered.filter((r) => r.taskId.toLowerCase().includes(lower));
|
|
1077
|
-
}
|
|
1078
|
-
return filtered;
|
|
1079
|
-
}, [state.rows, state.filterText, state.statusFilter]);
|
|
1080
|
-
const runningCount = useMemo(() => state.rows.filter((r) => r.status === "running").length, [state.rows]);
|
|
1081
|
-
const selectedRow = filteredRows[state.selectedIndex] ?? null;
|
|
1082
|
-
const selectedTaskId = selectedRow?.taskId ?? null;
|
|
1083
|
-
const outputTaskId = state.pinnedTaskIds[0] ?? selectedTaskId;
|
|
1084
|
-
const outputTask = outputTaskId ? state.rows.find((r) => r.taskId === outputTaskId) : null;
|
|
1085
|
-
const outputContent = outputTaskId ? state.outputs.get(outputTaskId) ?? "" : "";
|
|
1086
|
-
const description = formatTargetsAndProjects(projectNames, targets, tasks);
|
|
1087
|
-
const headerTitle = state.done ? `Completed ${description} (${formatMs((state.endTime ?? Date.now()) - state.startTime)})` : `Running ${description}...`;
|
|
1088
|
-
const headerStatus = state.done ? state.failed > 0 ? "error" : "success" : "running";
|
|
1089
|
-
const scrollToSelected = useCallback((index) => {
|
|
1090
|
-
listScrollRef.current?.scrollTo(Math.max(0, index - 2));
|
|
1091
|
-
}, []);
|
|
1092
|
-
useEffect(() => {
|
|
1093
|
-
if (state.interactiveMode && outputTask?.status !== "running") {
|
|
1094
|
-
store.setInteractiveMode(false);
|
|
1095
|
-
}
|
|
1096
|
-
}, [state.interactiveMode, outputTask?.status, store]);
|
|
1097
|
-
useEffect(() => {
|
|
1098
|
-
if (!outputTaskId) {
|
|
1099
|
-
return;
|
|
1100
|
-
}
|
|
1101
|
-
let panelCols = columns;
|
|
1102
|
-
if (state.viewMode === "split" && columns >= MIN_HORIZONTAL_WIDTH) {
|
|
1103
|
-
panelCols = columns - Math.floor(columns * 0.4) - 2;
|
|
1104
|
-
} else if (state.viewMode === "split") {
|
|
1105
|
-
panelCols = columns - 2;
|
|
1106
|
-
} else if (state.viewMode === "fullscreen") {
|
|
1107
|
-
panelCols = columns - 2;
|
|
1108
|
-
}
|
|
1109
|
-
const panelRows = Math.max(1, rows - 4);
|
|
1110
|
-
stdinRegistry.get(outputTaskId)?.resize?.(panelCols, panelRows);
|
|
1111
|
-
}, [columns, rows, state.viewMode, outputTaskId]);
|
|
1112
|
-
useInput(
|
|
1113
|
-
(input, key) => {
|
|
1114
|
-
if (key.escape) {
|
|
1115
|
-
store.setInteractiveMode(false);
|
|
1116
|
-
return;
|
|
1117
|
-
}
|
|
1118
|
-
if (!outputTaskId) {
|
|
1119
|
-
return;
|
|
1120
|
-
}
|
|
1121
|
-
const entry = stdinRegistry.get(outputTaskId);
|
|
1122
|
-
if (!entry) {
|
|
1123
|
-
store.setInteractiveMode(false);
|
|
1124
|
-
return;
|
|
1125
|
-
}
|
|
1126
|
-
if (key.return) {
|
|
1127
|
-
entry.write("\r");
|
|
1128
|
-
} else if (key.upArrow) {
|
|
1129
|
-
entry.write("\x1B[A");
|
|
1130
|
-
} else if (key.downArrow) {
|
|
1131
|
-
entry.write("\x1B[B");
|
|
1132
|
-
} else if (key.rightArrow) {
|
|
1133
|
-
entry.write("\x1B[C");
|
|
1134
|
-
} else if (key.leftArrow) {
|
|
1135
|
-
entry.write("\x1B[D");
|
|
1136
|
-
} else if (key.backspace) {
|
|
1137
|
-
entry.write("");
|
|
1138
|
-
} else if (key.delete) {
|
|
1139
|
-
entry.write("\x1B[3~");
|
|
1140
|
-
} else if (key.tab) {
|
|
1141
|
-
entry.write(" ");
|
|
1142
|
-
} else if (key.home) {
|
|
1143
|
-
entry.write("\x1B[H");
|
|
1144
|
-
} else if (key.end) {
|
|
1145
|
-
entry.write("\x1B[F");
|
|
1146
|
-
} else if (key.pageUp) {
|
|
1147
|
-
entry.write("\x1B[5~");
|
|
1148
|
-
} else if (key.pageDown) {
|
|
1149
|
-
entry.write("\x1B[6~");
|
|
1150
|
-
} else if (key.ctrl && input) {
|
|
1151
|
-
const code = input.toUpperCase().codePointAt(0);
|
|
1152
|
-
if (code !== void 0 && code >= 65 && code <= 90) {
|
|
1153
|
-
entry.write(String.fromCodePoint(code - 64));
|
|
1154
|
-
}
|
|
1155
|
-
} else if (input) {
|
|
1156
|
-
entry.write(input);
|
|
1157
|
-
}
|
|
1158
|
-
},
|
|
1159
|
-
{ isActive: state.interactiveMode }
|
|
1160
|
-
);
|
|
1161
|
-
useInput(
|
|
1162
|
-
(input, key) => {
|
|
1163
|
-
if (input === "c" && key.ctrl) {
|
|
1164
|
-
exit();
|
|
1165
|
-
return;
|
|
1166
|
-
}
|
|
1167
|
-
if (quitDialogVisible) {
|
|
1168
|
-
return;
|
|
1169
|
-
}
|
|
1170
|
-
if (helpVisible) {
|
|
1171
|
-
if (key.escape || input === "?") {
|
|
1172
|
-
setHelpVisible(false);
|
|
1173
|
-
} else if (input === "q") {
|
|
1174
|
-
setHelpVisible(false);
|
|
1175
|
-
setQuitDialogVisible(true);
|
|
1176
|
-
} else if (key.downArrow || input === "j") {
|
|
1177
|
-
helpScrollRef.current?.scrollBy(1);
|
|
1178
|
-
} else if (key.upArrow || input === "k") {
|
|
1179
|
-
helpScrollRef.current?.scrollBy(-1);
|
|
1180
|
-
} else if (key.pageDown) {
|
|
1181
|
-
helpScrollRef.current?.scrollBy(5);
|
|
1182
|
-
} else if (key.pageUp) {
|
|
1183
|
-
helpScrollRef.current?.scrollBy(-5);
|
|
1184
|
-
} else if (key.home) {
|
|
1185
|
-
helpScrollRef.current?.scrollToTop();
|
|
1186
|
-
} else if (key.end) {
|
|
1187
|
-
helpScrollRef.current?.scrollToBottom();
|
|
1188
|
-
}
|
|
1189
|
-
return;
|
|
1190
|
-
}
|
|
1191
|
-
if (input === "?") {
|
|
1192
|
-
setHelpVisible(true);
|
|
1193
|
-
return;
|
|
1194
|
-
}
|
|
1195
|
-
if (input === "q") {
|
|
1196
|
-
setQuitDialogVisible(true);
|
|
1197
|
-
return;
|
|
1198
|
-
}
|
|
1199
|
-
if (input === "r" && state.done) {
|
|
1200
|
-
store.requestRerun();
|
|
1201
|
-
return;
|
|
1202
|
-
}
|
|
1203
|
-
if (input === "R" && state.done) {
|
|
1204
|
-
const row = filteredRows[state.selectedIndex];
|
|
1205
|
-
if (row?.status === "failure") {
|
|
1206
|
-
store.requestRetry(row.taskId);
|
|
1207
|
-
}
|
|
1208
|
-
return;
|
|
1209
|
-
}
|
|
1210
|
-
if (input === "F" && !state.filterActive) {
|
|
1211
|
-
const filters = ["all", "failed", "running", "passed"];
|
|
1212
|
-
const currentIndex = filters.indexOf(state.statusFilter);
|
|
1213
|
-
store.setStatusFilter(filters[(currentIndex + 1) % filters.length]);
|
|
1214
|
-
return;
|
|
1215
|
-
}
|
|
1216
|
-
if (state.filterActive) {
|
|
1217
|
-
if (key.escape) {
|
|
1218
|
-
store.setFilterActive(false);
|
|
1219
|
-
return;
|
|
1220
|
-
}
|
|
1221
|
-
if (key.return) {
|
|
1222
|
-
store.setFilterActive(false);
|
|
1223
|
-
return;
|
|
1224
|
-
}
|
|
1225
|
-
if (key.backspace) {
|
|
1226
|
-
store.setFilter(state.filterText.slice(0, -1));
|
|
1227
|
-
return;
|
|
1228
|
-
}
|
|
1229
|
-
if (input && !key.ctrl && !key.meta) {
|
|
1230
|
-
store.setFilter(state.filterText + input);
|
|
1231
|
-
return;
|
|
1232
|
-
}
|
|
1233
|
-
return;
|
|
1234
|
-
}
|
|
1235
|
-
if (input === "i" && outputTask?.status === "running") {
|
|
1236
|
-
const isOutputView = state.viewMode === "fullscreen" || state.viewMode === "split" && state.focusedPanel === "output";
|
|
1237
|
-
if (isOutputView) {
|
|
1238
|
-
store.setInteractiveMode(true);
|
|
1239
|
-
return;
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
if (state.viewMode === "fullscreen") {
|
|
1243
|
-
if (key.escape) {
|
|
1244
|
-
store.setViewMode("split");
|
|
1245
|
-
restoreScrollPositions("split");
|
|
1246
|
-
return;
|
|
1247
|
-
}
|
|
1248
|
-
if (key.downArrow || input === "j") {
|
|
1249
|
-
outputScrollRef.current?.scrollBy(1);
|
|
1250
|
-
return;
|
|
1251
|
-
}
|
|
1252
|
-
if (key.upArrow || input === "k") {
|
|
1253
|
-
outputScrollRef.current?.scrollBy(-1);
|
|
1254
|
-
return;
|
|
1255
|
-
}
|
|
1256
|
-
if (key.pageDown || key.ctrl && input === "d") {
|
|
1257
|
-
outputScrollRef.current?.scrollBy(12);
|
|
1258
|
-
return;
|
|
1259
|
-
}
|
|
1260
|
-
if (key.pageUp || key.ctrl && input === "u") {
|
|
1261
|
-
outputScrollRef.current?.scrollBy(-12);
|
|
1262
|
-
return;
|
|
1263
|
-
}
|
|
1264
|
-
if (key.home) {
|
|
1265
|
-
outputScrollRef.current?.scrollToTop();
|
|
1266
|
-
return;
|
|
1267
|
-
}
|
|
1268
|
-
if (key.end) {
|
|
1269
|
-
outputScrollRef.current?.scrollToBottom();
|
|
1270
|
-
return;
|
|
1271
|
-
}
|
|
1272
|
-
return;
|
|
1273
|
-
}
|
|
1274
|
-
if (state.viewMode === "split") {
|
|
1275
|
-
if (key.tab) {
|
|
1276
|
-
const nextPanel = state.focusedPanel === "tasks" ? "output" : "tasks";
|
|
1277
|
-
store.setFocusedPanel(nextPanel);
|
|
1278
|
-
return;
|
|
1279
|
-
}
|
|
1280
|
-
if (state.focusedPanel === "output") {
|
|
1281
|
-
if (key.escape) {
|
|
1282
|
-
store.setFocusedPanel("tasks");
|
|
1283
|
-
return;
|
|
1284
|
-
}
|
|
1285
|
-
if (key.return) {
|
|
1286
|
-
saveScrollPositions();
|
|
1287
|
-
store.setViewMode("fullscreen");
|
|
1288
|
-
return;
|
|
1289
|
-
}
|
|
1290
|
-
if (key.downArrow || input === "j") {
|
|
1291
|
-
outputScrollRef.current?.scrollBy(1);
|
|
1292
|
-
return;
|
|
1293
|
-
}
|
|
1294
|
-
if (key.upArrow || input === "k") {
|
|
1295
|
-
outputScrollRef.current?.scrollBy(-1);
|
|
1296
|
-
return;
|
|
1297
|
-
}
|
|
1298
|
-
if (key.pageDown || key.ctrl && input === "d") {
|
|
1299
|
-
outputScrollRef.current?.scrollBy(12);
|
|
1300
|
-
return;
|
|
1301
|
-
}
|
|
1302
|
-
if (key.pageUp || key.ctrl && input === "u") {
|
|
1303
|
-
outputScrollRef.current?.scrollBy(-12);
|
|
1304
|
-
return;
|
|
1305
|
-
}
|
|
1306
|
-
if (key.home) {
|
|
1307
|
-
outputScrollRef.current?.scrollToTop();
|
|
1308
|
-
return;
|
|
1309
|
-
}
|
|
1310
|
-
if (key.end) {
|
|
1311
|
-
outputScrollRef.current?.scrollToBottom();
|
|
1312
|
-
return;
|
|
1313
|
-
}
|
|
1314
|
-
return;
|
|
1315
|
-
}
|
|
1316
|
-
if (key.escape) {
|
|
1317
|
-
store.setViewMode("list");
|
|
1318
|
-
restoreScrollPositions("list");
|
|
1319
|
-
return;
|
|
1320
|
-
}
|
|
1321
|
-
if (key.return) {
|
|
1322
|
-
store.setFocusedPanel("output");
|
|
1323
|
-
return;
|
|
1324
|
-
}
|
|
1325
|
-
}
|
|
1326
|
-
if (state.viewMode === "list" || state.viewMode === "split" && state.focusedPanel === "tasks") {
|
|
1327
|
-
if (key.downArrow || input === "j") {
|
|
1328
|
-
const next = Math.min(state.selectedIndex + 1, Math.max(0, filteredRows.length - 1));
|
|
1329
|
-
store.setSelectedIndex(next);
|
|
1330
|
-
scrollToSelected(next);
|
|
1331
|
-
return;
|
|
1332
|
-
}
|
|
1333
|
-
if (key.upArrow || input === "k") {
|
|
1334
|
-
const next = Math.max(state.selectedIndex - 1, 0);
|
|
1335
|
-
store.setSelectedIndex(next);
|
|
1336
|
-
scrollToSelected(next);
|
|
1337
|
-
return;
|
|
1338
|
-
}
|
|
1339
|
-
if (key.return && state.viewMode === "list") {
|
|
1340
|
-
saveScrollPositions();
|
|
1341
|
-
savedScrollRef.current.splitList = 0;
|
|
1342
|
-
savedScrollRef.current.splitOutput = 0;
|
|
1343
|
-
store.setViewMode("split");
|
|
1344
|
-
store.setFocusedPanel("output");
|
|
1345
|
-
restoreScrollPositions("split");
|
|
1346
|
-
return;
|
|
1347
|
-
}
|
|
1348
|
-
if (input === "/") {
|
|
1349
|
-
store.setFilterActive(true);
|
|
1350
|
-
return;
|
|
1351
|
-
}
|
|
1352
|
-
if (input === "1" && selectedTaskId) {
|
|
1353
|
-
store.pinTask(0, selectedTaskId);
|
|
1354
|
-
return;
|
|
1355
|
-
}
|
|
1356
|
-
if (input === "2" && selectedTaskId) {
|
|
1357
|
-
store.pinTask(1, selectedTaskId);
|
|
1358
|
-
return;
|
|
1359
|
-
}
|
|
1360
|
-
if (input === "0") {
|
|
1361
|
-
store.clearPins();
|
|
1362
|
-
return;
|
|
1363
|
-
}
|
|
1364
|
-
if (key.escape && state.filterText) {
|
|
1365
|
-
store.setFilter("");
|
|
1366
|
-
}
|
|
1367
|
-
}
|
|
1368
|
-
},
|
|
1369
|
-
{ isActive: !state.interactiveMode }
|
|
1370
|
-
);
|
|
1371
|
-
if (columns < MIN_VIEWPORT_WIDTH || rows < MIN_VIEWPORT_HEIGHT) {
|
|
1372
|
-
return jsx(Box, { alignItems: "center", height: rows, justifyContent: "center", width: columns, children: jsxs(Text, { color: "yellow", children: [
|
|
1373
|
-
"Terminal too small (",
|
|
1374
|
-
columns,
|
|
1375
|
-
"x",
|
|
1376
|
-
rows,
|
|
1377
|
-
"). Minimum: ",
|
|
1378
|
-
MIN_VIEWPORT_WIDTH,
|
|
1379
|
-
"x",
|
|
1380
|
-
MIN_VIEWPORT_HEIGHT
|
|
1381
|
-
] }) });
|
|
1382
|
-
}
|
|
1383
|
-
const statusSummary = jsxs(Box, { gap: 1, children: [
|
|
1384
|
-
state.succeeded > 0 && jsxs(Text, { bold: true, color: "green", children: [
|
|
1385
|
-
"✓",
|
|
1386
|
-
" ",
|
|
1387
|
-
state.succeeded
|
|
1388
|
-
] }),
|
|
1389
|
-
state.failed > 0 && jsxs(Text, { bold: true, color: "red", children: [
|
|
1390
|
-
"✗",
|
|
1391
|
-
" ",
|
|
1392
|
-
state.failed
|
|
1393
|
-
] }),
|
|
1394
|
-
runningCount > 0 && jsxs(Text, { color: "cyan", children: [
|
|
1395
|
-
"◷",
|
|
1396
|
-
" ",
|
|
1397
|
-
runningCount
|
|
1398
|
-
] }),
|
|
1399
|
-
jsxs(Text, { dimColor: true, children: [
|
|
1400
|
-
state.rows.length,
|
|
1401
|
-
" total"
|
|
1402
|
-
] }),
|
|
1403
|
-
state.statusFilter !== "all" && jsxs(Text, { color: "yellow", children: [
|
|
1404
|
-
"[",
|
|
1405
|
-
state.statusFilter,
|
|
1406
|
-
"]"
|
|
1407
|
-
] })
|
|
1408
|
-
] });
|
|
1409
|
-
let footerItems;
|
|
1410
|
-
if (state.viewMode === "fullscreen") {
|
|
1411
|
-
footerItems = [
|
|
1412
|
-
jsxs(Box, { gap: 1, children: [
|
|
1413
|
-
jsx(Text, { bold: true, color: "white", children: "Esc" }),
|
|
1414
|
-
jsx(Text, { dimColor: true, children: "BACK" })
|
|
1415
|
-
] }, "esc"),
|
|
1416
|
-
jsxs(Box, { gap: 1, children: [
|
|
1417
|
-
jsx(Text, { bold: true, color: "white", children: "↑↓" }),
|
|
1418
|
-
jsx(Text, { dimColor: true, children: "SCROLL" })
|
|
1419
|
-
] }, "scroll"),
|
|
1420
|
-
jsxs(Box, { gap: 1, children: [
|
|
1421
|
-
jsx(Text, { bold: true, color: "white", children: "^u ^d" }),
|
|
1422
|
-
jsx(Text, { dimColor: true, children: "PAGE" })
|
|
1423
|
-
] }, "page"),
|
|
1424
|
-
...outputTask?.status === "running" ? [
|
|
1425
|
-
jsxs(Box, { gap: 1, children: [
|
|
1426
|
-
jsx(Text, { bold: true, color: "white", children: "i" }),
|
|
1427
|
-
jsx(Text, { dimColor: true, children: "INPUT" })
|
|
1428
|
-
] }, "i")
|
|
1429
|
-
] : [],
|
|
1430
|
-
jsxs(Box, { gap: 1, children: [
|
|
1431
|
-
jsx(Text, { bold: true, color: "white", children: "q" }),
|
|
1432
|
-
jsx(Text, { dimColor: true, children: "QUIT" })
|
|
1433
|
-
] }, "q")
|
|
1434
|
-
];
|
|
1435
|
-
} else if (state.done) {
|
|
1436
|
-
const canRetry = filteredRows[state.selectedIndex]?.status === "failure";
|
|
1437
|
-
footerItems = [
|
|
1438
|
-
jsxs(Box, { gap: 1, children: [
|
|
1439
|
-
jsx(Text, { bold: true, color: "white", children: "q" }),
|
|
1440
|
-
jsx(Text, { dimColor: true, children: "QUIT" })
|
|
1441
|
-
] }, "q"),
|
|
1442
|
-
jsxs(Box, { gap: 1, children: [
|
|
1443
|
-
jsx(Text, { bold: true, color: "white", children: "r" }),
|
|
1444
|
-
jsx(Text, { dimColor: true, children: "RERUN" })
|
|
1445
|
-
] }, "r"),
|
|
1446
|
-
...canRetry ? [
|
|
1447
|
-
jsxs(Box, { gap: 1, children: [
|
|
1448
|
-
jsx(Text, { bold: true, color: "white", children: "R" }),
|
|
1449
|
-
jsx(Text, { dimColor: true, children: "RETRY" })
|
|
1450
|
-
] }, "R")
|
|
1451
|
-
] : [],
|
|
1452
|
-
jsxs(Box, { gap: 1, children: [
|
|
1453
|
-
jsx(Text, { bold: true, color: "white", children: "?" }),
|
|
1454
|
-
jsx(Text, { dimColor: true, children: "HELP" })
|
|
1455
|
-
] }, "?"),
|
|
1456
|
-
jsxs(Box, { gap: 1, children: [
|
|
1457
|
-
jsx(Text, { bold: true, color: "white", children: "↑↓" }),
|
|
1458
|
-
jsx(Text, { dimColor: true, children: "NAV" })
|
|
1459
|
-
] }, "nav"),
|
|
1460
|
-
jsxs(Box, { gap: 1, children: [
|
|
1461
|
-
jsx(Text, { bold: true, color: "white", children: "F" }),
|
|
1462
|
-
jsx(Text, { dimColor: true, children: "FILTER" })
|
|
1463
|
-
] }, "F"),
|
|
1464
|
-
jsxs(Box, { gap: 1, children: [
|
|
1465
|
-
jsx(Text, { bold: true, color: "white", children: "⏎" }),
|
|
1466
|
-
jsx(Text, { dimColor: true, children: state.viewMode === "list" ? "OUTPUT" : "FULLSCREEN" })
|
|
1467
|
-
] }, "enter")
|
|
1468
|
-
];
|
|
1469
|
-
} else if (state.viewMode === "split" && state.focusedPanel === "output") {
|
|
1470
|
-
footerItems = [
|
|
1471
|
-
jsxs(Box, { gap: 1, children: [
|
|
1472
|
-
jsx(Text, { bold: true, color: "white", children: "q" }),
|
|
1473
|
-
jsx(Text, { dimColor: true, children: "QUIT" })
|
|
1474
|
-
] }, "q"),
|
|
1475
|
-
jsxs(Box, { gap: 1, children: [
|
|
1476
|
-
jsx(Text, { bold: true, color: "white", children: "Esc" }),
|
|
1477
|
-
jsx(Text, { dimColor: true, children: "BACK" })
|
|
1478
|
-
] }, "esc"),
|
|
1479
|
-
jsxs(Box, { gap: 1, children: [
|
|
1480
|
-
jsx(Text, { bold: true, color: "white", children: "↑↓" }),
|
|
1481
|
-
jsx(Text, { dimColor: true, children: "SCROLL" })
|
|
1482
|
-
] }, "scroll"),
|
|
1483
|
-
...outputTask?.status === "running" ? [
|
|
1484
|
-
jsxs(Box, { gap: 1, children: [
|
|
1485
|
-
jsx(Text, { bold: true, color: "white", children: "i" }),
|
|
1486
|
-
jsx(Text, { dimColor: true, children: "INPUT" })
|
|
1487
|
-
] }, "i")
|
|
1488
|
-
] : [],
|
|
1489
|
-
jsxs(Box, { gap: 1, children: [
|
|
1490
|
-
jsx(Text, { bold: true, color: "white", children: "⏎" }),
|
|
1491
|
-
jsx(Text, { dimColor: true, children: "FULLSCREEN" })
|
|
1492
|
-
] }, "enter"),
|
|
1493
|
-
jsxs(Box, { gap: 1, children: [
|
|
1494
|
-
jsx(Text, { bold: true, color: "white", children: "Tab" }),
|
|
1495
|
-
jsx(Text, { dimColor: true, children: "PANEL" })
|
|
1496
|
-
] }, "tab"),
|
|
1497
|
-
jsxs(Box, { gap: 1, children: [
|
|
1498
|
-
jsx(Text, { bold: true, color: "white", children: "?" }),
|
|
1499
|
-
jsx(Text, { dimColor: true, children: "HELP" })
|
|
1500
|
-
] }, "?")
|
|
1501
|
-
];
|
|
1502
|
-
} else {
|
|
1503
|
-
footerItems = [
|
|
1504
|
-
jsxs(Box, { gap: 1, children: [
|
|
1505
|
-
jsx(Text, { bold: true, color: "white", children: "q" }),
|
|
1506
|
-
jsx(Text, { dimColor: true, children: "QUIT" })
|
|
1507
|
-
] }, "q"),
|
|
1508
|
-
jsxs(Box, { gap: 1, children: [
|
|
1509
|
-
jsx(Text, { bold: true, color: "white", children: "?" }),
|
|
1510
|
-
jsx(Text, { dimColor: true, children: "HELP" })
|
|
1511
|
-
] }, "?"),
|
|
1512
|
-
jsxs(Box, { gap: 1, children: [
|
|
1513
|
-
jsx(Text, { bold: true, color: "white", children: "↑↓" }),
|
|
1514
|
-
jsx(Text, { dimColor: true, children: "NAV" })
|
|
1515
|
-
] }, "nav"),
|
|
1516
|
-
jsxs(Box, { gap: 1, children: [
|
|
1517
|
-
jsx(Text, { bold: true, color: "white", children: "/" }),
|
|
1518
|
-
jsx(Text, { dimColor: true, children: "FILTER" })
|
|
1519
|
-
] }, "/"),
|
|
1520
|
-
jsxs(Box, { gap: 1, children: [
|
|
1521
|
-
jsx(Text, { bold: true, color: "white", children: "F" }),
|
|
1522
|
-
jsx(Text, { dimColor: true, children: "STATUS" })
|
|
1523
|
-
] }, "F"),
|
|
1524
|
-
jsxs(Box, { gap: 1, children: [
|
|
1525
|
-
jsx(Text, { bold: true, color: "white", children: "⏎" }),
|
|
1526
|
-
jsx(Text, { dimColor: true, children: state.viewMode === "list" ? "OUTPUT" : "FULLSCREEN" })
|
|
1527
|
-
] }, "enter"),
|
|
1528
|
-
...state.viewMode === "split" ? [
|
|
1529
|
-
jsxs(Box, { gap: 1, children: [
|
|
1530
|
-
jsx(Text, { bold: true, color: "white", children: "Tab" }),
|
|
1531
|
-
jsx(Text, { dimColor: true, children: "PANEL" })
|
|
1532
|
-
] }, "tab")
|
|
1533
|
-
] : []
|
|
1534
|
-
];
|
|
1535
|
-
}
|
|
1536
|
-
const footer = jsxs(Box, { borderBottom: false, borderColor: "gray", borderLeft: false, borderRight: false, borderStyle: "single", flexShrink: 0, justifyContent: "space-between", children: [
|
|
1537
|
-
jsx(Box, { flexGrow: 1, flexWrap: "wrap", gap: 2, paddingX: 1, children: footerItems }),
|
|
1538
|
-
jsx(Box, { flexShrink: 0, paddingX: 1, children: statusSummary })
|
|
1539
|
-
] });
|
|
1540
|
-
const helpPopup = jsxs(
|
|
1541
|
-
Dialog,
|
|
1542
|
-
{
|
|
1543
|
-
backgroundColor: "#1e1e1e",
|
|
1544
|
-
footer: jsxs(Text, { dimColor: true, children: [
|
|
1545
|
-
jsx(Text, { bold: true, color: "white", children: "↑↓" }),
|
|
1546
|
-
" ",
|
|
1547
|
-
"scroll",
|
|
1548
|
-
" ",
|
|
1549
|
-
jsx(Text, { bold: true, color: "white", children: "?" }),
|
|
1550
|
-
"/",
|
|
1551
|
-
jsx(Text, { bold: true, color: "white", children: "Esc" }),
|
|
1552
|
-
" ",
|
|
1553
|
-
"close"
|
|
1554
|
-
] }),
|
|
1555
|
-
scrollRef: helpScrollRef,
|
|
1556
|
-
title: "KEYBOARD SHORTCUTS",
|
|
1557
|
-
visible: helpVisible,
|
|
1558
|
-
width: 52,
|
|
1559
|
-
children: [
|
|
1560
|
-
jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1561
|
-
jsxs(Box, { marginBottom: 1, children: [
|
|
1562
|
-
jsx(Text, { dimColor: true, children: "── " }),
|
|
1563
|
-
jsx(Text, { bold: true, color: "white", children: "NAVIGATION" })
|
|
1564
|
-
] }),
|
|
1565
|
-
jsxs(Box, { children: [
|
|
1566
|
-
jsx(Box, { width: 24, children: jsxs(Text, { children: [
|
|
1567
|
-
jsxs(Text, { bold: true, color: "white", children: [
|
|
1568
|
-
" ",
|
|
1569
|
-
"↑",
|
|
1570
|
-
"/k"
|
|
1571
|
-
] }),
|
|
1572
|
-
jsx(Text, { dimColor: true, children: " Move up" })
|
|
1573
|
-
] }) }),
|
|
1574
|
-
jsxs(Text, { children: [
|
|
1575
|
-
jsxs(Text, { bold: true, color: "white", children: [
|
|
1576
|
-
" ",
|
|
1577
|
-
"↓",
|
|
1578
|
-
"/j"
|
|
1579
|
-
] }),
|
|
1580
|
-
jsx(Text, { dimColor: true, children: " Move down" })
|
|
1581
|
-
] })
|
|
1582
|
-
] }),
|
|
1583
|
-
jsxs(Text, { children: [
|
|
1584
|
-
jsxs(Text, { bold: true, color: "white", children: [
|
|
1585
|
-
" ",
|
|
1586
|
-
"Tab"
|
|
1587
|
-
] }),
|
|
1588
|
-
jsx(Text, { dimColor: true, children: " Switch panel (split)" })
|
|
1589
|
-
] }),
|
|
1590
|
-
jsxs(Text, { children: [
|
|
1591
|
-
jsxs(Text, { bold: true, color: "white", children: [
|
|
1592
|
-
" ",
|
|
1593
|
-
"Esc"
|
|
1594
|
-
] }),
|
|
1595
|
-
jsx(Text, { dimColor: true, children: " Back / close" })
|
|
1596
|
-
] }),
|
|
1597
|
-
jsxs(Text, { children: [
|
|
1598
|
-
jsxs(Text, { bold: true, color: "white", children: [
|
|
1599
|
-
" ",
|
|
1600
|
-
"Enter"
|
|
1601
|
-
] }),
|
|
1602
|
-
jsx(Text, { dimColor: true, children: " Show output / fullscreen" })
|
|
1603
|
-
] })
|
|
1604
|
-
] }),
|
|
1605
|
-
jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1606
|
-
jsxs(Box, { marginBottom: 1, children: [
|
|
1607
|
-
jsx(Text, { dimColor: true, children: "── " }),
|
|
1608
|
-
jsx(Text, { bold: true, color: "white", children: "VIEWS" })
|
|
1609
|
-
] }),
|
|
1610
|
-
jsxs(Text, { children: [
|
|
1611
|
-
jsxs(Text, { bold: true, color: "white", children: [
|
|
1612
|
-
" ",
|
|
1613
|
-
"Enter"
|
|
1614
|
-
] }),
|
|
1615
|
-
jsxs(Text, { dimColor: true, children: [
|
|
1616
|
-
" ",
|
|
1617
|
-
"List ",
|
|
1618
|
-
"→",
|
|
1619
|
-
" Split ",
|
|
1620
|
-
"→",
|
|
1621
|
-
" Fullscreen"
|
|
1622
|
-
] })
|
|
1623
|
-
] }),
|
|
1624
|
-
jsxs(Text, { children: [
|
|
1625
|
-
jsxs(Text, { bold: true, color: "white", children: [
|
|
1626
|
-
" ",
|
|
1627
|
-
"Esc"
|
|
1628
|
-
] }),
|
|
1629
|
-
jsxs(Text, { dimColor: true, children: [
|
|
1630
|
-
" ",
|
|
1631
|
-
"Fullscreen ",
|
|
1632
|
-
"→",
|
|
1633
|
-
" Split ",
|
|
1634
|
-
"→",
|
|
1635
|
-
" List"
|
|
1636
|
-
] })
|
|
1637
|
-
] })
|
|
1638
|
-
] }),
|
|
1639
|
-
jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1640
|
-
jsxs(Box, { marginBottom: 1, children: [
|
|
1641
|
-
jsx(Text, { dimColor: true, children: "── " }),
|
|
1642
|
-
jsx(Text, { bold: true, color: "white", children: "ACTIONS" })
|
|
1643
|
-
] }),
|
|
1644
|
-
jsxs(Box, { children: [
|
|
1645
|
-
jsx(Box, { width: 24, children: jsxs(Text, { children: [
|
|
1646
|
-
jsxs(Text, { bold: true, color: "white", children: [
|
|
1647
|
-
" ",
|
|
1648
|
-
"/"
|
|
1649
|
-
] }),
|
|
1650
|
-
jsx(Text, { dimColor: true, children: " Filter by text" })
|
|
1651
|
-
] }) }),
|
|
1652
|
-
jsxs(Text, { children: [
|
|
1653
|
-
jsxs(Text, { bold: true, color: "white", children: [
|
|
1654
|
-
" ",
|
|
1655
|
-
"F"
|
|
1656
|
-
] }),
|
|
1657
|
-
jsx(Text, { dimColor: true, children: " Filter by status" })
|
|
1658
|
-
] })
|
|
1659
|
-
] }),
|
|
1660
|
-
jsxs(Box, { children: [
|
|
1661
|
-
jsx(Box, { width: 24, children: jsxs(Text, { children: [
|
|
1662
|
-
jsxs(Text, { bold: true, color: "white", children: [
|
|
1663
|
-
" ",
|
|
1664
|
-
"1"
|
|
1665
|
-
] }),
|
|
1666
|
-
jsx(Text, { dimColor: true, children: "/" }),
|
|
1667
|
-
jsx(Text, { bold: true, color: "white", children: "2" }),
|
|
1668
|
-
jsx(Text, { dimColor: true, children: " Pin to output pane" })
|
|
1669
|
-
] }) }),
|
|
1670
|
-
jsxs(Text, { children: [
|
|
1671
|
-
jsxs(Text, { bold: true, color: "white", children: [
|
|
1672
|
-
" ",
|
|
1673
|
-
"0"
|
|
1674
|
-
] }),
|
|
1675
|
-
jsx(Text, { dimColor: true, children: " Clear pins" })
|
|
1676
|
-
] })
|
|
1677
|
-
] }),
|
|
1678
|
-
jsxs(Box, { children: [
|
|
1679
|
-
jsx(Box, { width: 24, children: jsxs(Text, { children: [
|
|
1680
|
-
jsxs(Text, { bold: true, color: "white", children: [
|
|
1681
|
-
" ",
|
|
1682
|
-
"r"
|
|
1683
|
-
] }),
|
|
1684
|
-
jsx(Text, { dimColor: true, children: " Rerun all (done)" })
|
|
1685
|
-
] }) }),
|
|
1686
|
-
jsxs(Text, { children: [
|
|
1687
|
-
jsxs(Text, { bold: true, color: "white", children: [
|
|
1688
|
-
" ",
|
|
1689
|
-
"R"
|
|
1690
|
-
] }),
|
|
1691
|
-
jsx(Text, { dimColor: true, children: " Retry failed task" })
|
|
1692
|
-
] })
|
|
1693
|
-
] }),
|
|
1694
|
-
jsxs(Text, { children: [
|
|
1695
|
-
jsxs(Text, { bold: true, color: "white", children: [
|
|
1696
|
-
" ",
|
|
1697
|
-
"i"
|
|
1698
|
-
] }),
|
|
1699
|
-
jsx(Text, { dimColor: true, children: " Interactive input (running task)" })
|
|
1700
|
-
] })
|
|
1701
|
-
] }),
|
|
1702
|
-
jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1703
|
-
jsxs(Box, { marginBottom: 1, children: [
|
|
1704
|
-
jsx(Text, { dimColor: true, children: "── " }),
|
|
1705
|
-
jsx(Text, { bold: true, color: "white", children: "SCROLLING" }),
|
|
1706
|
-
jsx(Text, { dimColor: true, children: " (output panel)" })
|
|
1707
|
-
] }),
|
|
1708
|
-
jsxs(Box, { children: [
|
|
1709
|
-
jsx(Box, { width: 24, children: jsxs(Text, { children: [
|
|
1710
|
-
jsxs(Text, { bold: true, color: "white", children: [
|
|
1711
|
-
" ",
|
|
1712
|
-
"↑",
|
|
1713
|
-
"/k"
|
|
1714
|
-
] }),
|
|
1715
|
-
jsx(Text, { dimColor: true, children: " Scroll up" })
|
|
1716
|
-
] }) }),
|
|
1717
|
-
jsxs(Text, { children: [
|
|
1718
|
-
jsxs(Text, { bold: true, color: "white", children: [
|
|
1719
|
-
" ",
|
|
1720
|
-
"↓",
|
|
1721
|
-
"/j"
|
|
1722
|
-
] }),
|
|
1723
|
-
jsx(Text, { dimColor: true, children: " Scroll down" })
|
|
1724
|
-
] })
|
|
1725
|
-
] }),
|
|
1726
|
-
jsxs(Box, { children: [
|
|
1727
|
-
jsx(Box, { width: 24, children: jsxs(Text, { children: [
|
|
1728
|
-
jsxs(Text, { bold: true, color: "white", children: [
|
|
1729
|
-
" ",
|
|
1730
|
-
"^u"
|
|
1731
|
-
] }),
|
|
1732
|
-
jsx(Text, { dimColor: true, children: " Page up" })
|
|
1733
|
-
] }) }),
|
|
1734
|
-
jsxs(Text, { children: [
|
|
1735
|
-
jsxs(Text, { bold: true, color: "white", children: [
|
|
1736
|
-
" ",
|
|
1737
|
-
"^d"
|
|
1738
|
-
] }),
|
|
1739
|
-
jsx(Text, { dimColor: true, children: " Page down" })
|
|
1740
|
-
] })
|
|
1741
|
-
] }),
|
|
1742
|
-
jsxs(Box, { children: [
|
|
1743
|
-
jsx(Box, { width: 24, children: jsxs(Text, { children: [
|
|
1744
|
-
jsxs(Text, { bold: true, color: "white", children: [
|
|
1745
|
-
" ",
|
|
1746
|
-
"Home"
|
|
1747
|
-
] }),
|
|
1748
|
-
jsx(Text, { dimColor: true, children: " Top" })
|
|
1749
|
-
] }) }),
|
|
1750
|
-
jsxs(Text, { children: [
|
|
1751
|
-
jsxs(Text, { bold: true, color: "white", children: [
|
|
1752
|
-
" ",
|
|
1753
|
-
"End"
|
|
1754
|
-
] }),
|
|
1755
|
-
jsx(Text, { dimColor: true, children: " Bottom" })
|
|
1756
|
-
] })
|
|
1757
|
-
] })
|
|
1758
|
-
] }),
|
|
1759
|
-
jsxs(Box, { flexDirection: "column", children: [
|
|
1760
|
-
jsxs(Box, { marginBottom: 1, children: [
|
|
1761
|
-
jsx(Text, { dimColor: true, children: "── " }),
|
|
1762
|
-
jsx(Text, { bold: true, color: "white", children: "GENERAL" })
|
|
1763
|
-
] }),
|
|
1764
|
-
jsxs(Box, { children: [
|
|
1765
|
-
jsx(Box, { width: 24, children: jsxs(Text, { children: [
|
|
1766
|
-
jsxs(Text, { bold: true, color: "white", children: [
|
|
1767
|
-
" ",
|
|
1768
|
-
"q"
|
|
1769
|
-
] }),
|
|
1770
|
-
jsx(Text, { dimColor: true, children: " Quit" })
|
|
1771
|
-
] }) }),
|
|
1772
|
-
jsxs(Text, { children: [
|
|
1773
|
-
jsxs(Text, { bold: true, color: "white", children: [
|
|
1774
|
-
" ",
|
|
1775
|
-
"?"
|
|
1776
|
-
] }),
|
|
1777
|
-
jsx(Text, { dimColor: true, children: " Toggle help" })
|
|
1778
|
-
] })
|
|
1779
|
-
] })
|
|
1780
|
-
] })
|
|
1781
|
-
]
|
|
1782
|
-
}
|
|
1783
|
-
);
|
|
1784
|
-
const quitDialog = jsx(
|
|
1785
|
-
QuitDialog,
|
|
1786
|
-
{
|
|
1787
|
-
autoExitSeconds: autoExitSeconds > 0 ? autoExitSeconds : 3,
|
|
1788
|
-
onCancel: () => {
|
|
1789
|
-
setQuitDialogVisible(false);
|
|
1790
|
-
},
|
|
1791
|
-
visible: quitDialogVisible
|
|
1792
|
-
}
|
|
1793
|
-
);
|
|
1794
|
-
if (state.viewMode === "fullscreen") {
|
|
1795
|
-
return jsxs(Box, { flexDirection: "column", height: rows, width: columns, children: [
|
|
1796
|
-
jsx(Box, { flexGrow: 1, children: jsx(
|
|
1797
|
-
OutputPanel,
|
|
1798
|
-
{
|
|
1799
|
-
duration: outputTask?.duration ?? outputTask?.elapsed,
|
|
1800
|
-
focused: true,
|
|
1801
|
-
interactiveMode: state.interactiveMode,
|
|
1802
|
-
output: outputContent,
|
|
1803
|
-
scrollRef: outputScrollRef,
|
|
1804
|
-
status: outputTask?.status,
|
|
1805
|
-
taskId: outputTaskId
|
|
1806
|
-
}
|
|
1807
|
-
) }),
|
|
1808
|
-
footer,
|
|
1809
|
-
quitDialog,
|
|
1810
|
-
helpPopup
|
|
1811
|
-
] });
|
|
1812
|
-
}
|
|
1813
|
-
if (state.viewMode === "split") {
|
|
1814
|
-
const isHorizontal = columns >= MIN_HORIZONTAL_WIDTH;
|
|
1815
|
-
const taskListPanel = jsx(
|
|
1816
|
-
TaskListPanel,
|
|
1817
|
-
{
|
|
1818
|
-
compact: true,
|
|
1819
|
-
filterActive: state.filterActive,
|
|
1820
|
-
filterText: state.filterText,
|
|
1821
|
-
focused: state.focusedPanel === "tasks",
|
|
1822
|
-
headerStatus,
|
|
1823
|
-
parallelSlots,
|
|
1824
|
-
pinnedTaskIds: state.pinnedTaskIds,
|
|
1825
|
-
rows: filteredRows,
|
|
1826
|
-
scrollRef: listScrollRef,
|
|
1827
|
-
selectedIndex: state.selectedIndex,
|
|
1828
|
-
title: headerTitle
|
|
1829
|
-
}
|
|
1830
|
-
);
|
|
1831
|
-
const outputPanel = jsx(
|
|
1832
|
-
OutputPanel,
|
|
1833
|
-
{
|
|
1834
|
-
duration: outputTask?.duration ?? outputTask?.elapsed,
|
|
1835
|
-
focused: state.focusedPanel === "output",
|
|
1836
|
-
interactiveMode: state.interactiveMode,
|
|
1837
|
-
output: outputContent,
|
|
1838
|
-
scrollRef: outputScrollRef,
|
|
1839
|
-
showFullscreenHint: true,
|
|
1840
|
-
status: outputTask?.status,
|
|
1841
|
-
taskId: outputTaskId
|
|
1842
|
-
}
|
|
1843
|
-
);
|
|
1844
|
-
if (isHorizontal) {
|
|
1845
|
-
const taskListWidth = Math.floor(columns * 0.4);
|
|
1846
|
-
return jsxs(Box, { flexDirection: "column", height: rows, width: columns, children: [
|
|
1847
|
-
jsxs(Box, { flexDirection: "row", flexGrow: 1, children: [
|
|
1848
|
-
jsx(Box, { width: taskListWidth, children: taskListPanel }),
|
|
1849
|
-
jsx(Box, { flexGrow: 1, children: outputPanel })
|
|
1850
|
-
] }),
|
|
1851
|
-
footer,
|
|
1852
|
-
quitDialog,
|
|
1853
|
-
helpPopup
|
|
1854
|
-
] });
|
|
1855
|
-
}
|
|
1856
|
-
const listHeight = Math.floor(rows * 0.45);
|
|
1857
|
-
return jsxs(Box, { flexDirection: "column", height: rows, width: columns, children: [
|
|
1858
|
-
jsx(Box, { height: listHeight, children: taskListPanel }),
|
|
1859
|
-
jsx(Box, { flexGrow: 1, children: outputPanel }),
|
|
1860
|
-
footer,
|
|
1861
|
-
quitDialog,
|
|
1862
|
-
helpPopup
|
|
1863
|
-
] });
|
|
1864
|
-
}
|
|
1865
|
-
return jsxs(Box, { flexDirection: "column", height: rows, width: columns, children: [
|
|
1866
|
-
jsx(Box, { flexGrow: 1, children: jsx(
|
|
1867
|
-
TaskListPanel,
|
|
1868
|
-
{
|
|
1869
|
-
filterActive: state.filterActive,
|
|
1870
|
-
filterText: state.filterText,
|
|
1871
|
-
focused: true,
|
|
1872
|
-
headerStatus,
|
|
1873
|
-
parallelSlots,
|
|
1874
|
-
pinnedTaskIds: state.pinnedTaskIds,
|
|
1875
|
-
rows: filteredRows,
|
|
1876
|
-
scrollRef: listScrollRef,
|
|
1877
|
-
selectedIndex: state.selectedIndex,
|
|
1878
|
-
title: headerTitle
|
|
1879
|
-
}
|
|
1880
|
-
) }),
|
|
1881
|
-
footer,
|
|
1882
|
-
quitDialog,
|
|
1883
|
-
helpPopup
|
|
1884
|
-
] });
|
|
1885
|
-
};
|
|
1886
|
-
|
|
1887
|
-
const createDynamicOutputRenderer = (options) => {
|
|
1888
|
-
const { args, autoExit = false, outputStyle = "normal", projectNames, stdinRegistry, tasks } = options;
|
|
1889
|
-
const store = new TaskStore(tasks);
|
|
1890
|
-
const parallelSlots = typeof args.parallel === "number" ? args.parallel : 3;
|
|
1891
|
-
const autoExitSeconds = autoExit === true ? 3 : typeof autoExit === "number" ? autoExit : 0;
|
|
1892
|
-
let instance;
|
|
1893
|
-
let resolveDone;
|
|
1894
|
-
const renderIsDone = new Promise((r) => {
|
|
1895
|
-
resolveDone = r;
|
|
1896
|
-
});
|
|
1897
|
-
let tickTimer;
|
|
1898
|
-
const cleanup = () => {
|
|
1899
|
-
if (tickTimer) {
|
|
1900
|
-
clearInterval(tickTimer);
|
|
1901
|
-
tickTimer = void 0;
|
|
1902
|
-
}
|
|
1903
|
-
};
|
|
1904
|
-
const killAllPtyProcesses = () => {
|
|
1905
|
-
if (stdinRegistry) {
|
|
1906
|
-
for (const entry of stdinRegistry.values()) {
|
|
1907
|
-
entry.kill?.();
|
|
1908
|
-
}
|
|
1909
|
-
stdinRegistry.clear();
|
|
1910
|
-
}
|
|
1911
|
-
};
|
|
1912
|
-
const onSignal = () => {
|
|
1913
|
-
cleanup();
|
|
1914
|
-
clearKeepAlive();
|
|
1915
|
-
killAllPtyProcesses();
|
|
1916
|
-
process.stdout.write("\x1B[?1049l\x1B[?25h");
|
|
1917
|
-
instance?.cleanup();
|
|
1918
|
-
process.exit(1);
|
|
1919
|
-
};
|
|
1920
|
-
const printExitSummary = () => {
|
|
1921
|
-
const state = store.getSnapshot();
|
|
1922
|
-
const took = formatMs(Date.now() - state.startTime);
|
|
1923
|
-
const failedIds = state.rows.filter((r) => r.status === "failure").map((r) => r.taskId);
|
|
1924
|
-
const columns = process.stdout.columns || 80;
|
|
1925
|
-
process.stdout.write("\n");
|
|
1926
|
-
for (const row of state.rows) {
|
|
1927
|
-
const { status, taskId } = row;
|
|
1928
|
-
const info = getStatusInfo(status);
|
|
1929
|
-
let cacheLabel = "";
|
|
1930
|
-
switch (status) {
|
|
1931
|
-
case "local-cache":
|
|
1932
|
-
case "local-cache-kept-existing": {
|
|
1933
|
-
cacheLabel = " [local cache]";
|
|
1934
|
-
break;
|
|
1935
|
-
}
|
|
1936
|
-
case "remote-cache": {
|
|
1937
|
-
cacheLabel = " [remote cache]";
|
|
1938
|
-
break;
|
|
1939
|
-
}
|
|
1940
|
-
case "skipped": {
|
|
1941
|
-
cacheLabel = " [skipped]";
|
|
1942
|
-
break;
|
|
1943
|
-
}
|
|
1944
|
-
}
|
|
1945
|
-
const line = renderToString(
|
|
1946
|
-
React.createElement(
|
|
1947
|
-
Text,
|
|
1948
|
-
null,
|
|
1949
|
-
" ",
|
|
1950
|
-
React.createElement(Text, { color: info.color }, info.icon),
|
|
1951
|
-
` vis run ${taskId}`,
|
|
1952
|
-
cacheLabel ? React.createElement(Text, { dimColor: true }, ` ${cacheLabel}`) : null
|
|
1953
|
-
),
|
|
1954
|
-
{ columns }
|
|
1955
|
-
);
|
|
1956
|
-
process.stdout.write(`${line}
|
|
1957
|
-
`);
|
|
1958
|
-
}
|
|
1959
|
-
process.stdout.write("\n");
|
|
1960
|
-
const summary = renderToString(
|
|
1961
|
-
React.createElement(CommandSummary, {
|
|
1962
|
-
cached: state.cached,
|
|
1963
|
-
failed: state.failed,
|
|
1964
|
-
failedIds,
|
|
1965
|
-
projectNames,
|
|
1966
|
-
succeeded: state.succeeded,
|
|
1967
|
-
targets: args.targets,
|
|
1968
|
-
tasks,
|
|
1969
|
-
took
|
|
1970
|
-
}),
|
|
1971
|
-
{ columns }
|
|
1972
|
-
);
|
|
1973
|
-
process.stdout.write(`${summary}
|
|
1974
|
-
`);
|
|
1975
|
-
if (failedIds.length > 0 && outputStyle !== "quiet") {
|
|
1976
|
-
for (const taskId of failedIds) {
|
|
1977
|
-
const output = state.outputs.get(taskId);
|
|
1978
|
-
if (output?.trim()) {
|
|
1979
|
-
const header = renderToString(
|
|
1980
|
-
React.createElement(Text, null, "\n", React.createElement(Text, { bold: true, color: "red" }, ` ${CROSS} vis run ${taskId}`)),
|
|
1981
|
-
{ columns }
|
|
1982
|
-
);
|
|
1983
|
-
process.stdout.write(`${header}
|
|
1984
|
-
|
|
1985
|
-
`);
|
|
1986
|
-
const indented = output.trim().split("\n").map((line) => ` ${line}`).join("\n");
|
|
1987
|
-
process.stdout.write(`${indented}
|
|
1988
|
-
`);
|
|
1989
|
-
}
|
|
1990
|
-
}
|
|
1991
|
-
}
|
|
1992
|
-
};
|
|
1993
|
-
let keepAliveTimer;
|
|
1994
|
-
const clearKeepAlive = () => {
|
|
1995
|
-
if (keepAliveTimer) {
|
|
1996
|
-
clearInterval(keepAliveTimer);
|
|
1997
|
-
keepAliveTimer = void 0;
|
|
1998
|
-
}
|
|
1999
|
-
};
|
|
2000
|
-
const lifeCycle = {
|
|
2001
|
-
endCommand() {
|
|
2002
|
-
cleanup();
|
|
2003
|
-
store.markDone();
|
|
2004
|
-
if (!keepAliveTimer) {
|
|
2005
|
-
keepAliveTimer = setInterval(() => {
|
|
2006
|
-
}, 1e3);
|
|
2007
|
-
}
|
|
2008
|
-
if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
|
|
2009
|
-
process.stdin.setRawMode(true);
|
|
2010
|
-
process.stdin.ref();
|
|
2011
|
-
process.stdin.resume();
|
|
2012
|
-
}
|
|
2013
|
-
},
|
|
2014
|
-
endTasks(results) {
|
|
2015
|
-
store.endTasks(results);
|
|
2016
|
-
},
|
|
2017
|
-
printTaskTerminalOutput(task, _status, output) {
|
|
2018
|
-
if (!store.getSnapshot().outputs.has(task.id)) {
|
|
2019
|
-
store.addOutput(task.id, output);
|
|
2020
|
-
}
|
|
2021
|
-
},
|
|
2022
|
-
startCommand() {
|
|
2023
|
-
process.on("SIGINT", onSignal);
|
|
2024
|
-
process.on("SIGTERM", onSignal);
|
|
2025
|
-
if (!keepAliveTimer) {
|
|
2026
|
-
keepAliveTimer = setInterval(() => {
|
|
2027
|
-
}, 1e3);
|
|
2028
|
-
}
|
|
2029
|
-
if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
|
|
2030
|
-
process.stdin.setRawMode(true);
|
|
2031
|
-
process.stdin.ref();
|
|
2032
|
-
process.stdin.resume();
|
|
2033
|
-
}
|
|
2034
|
-
instance = render(
|
|
2035
|
-
React.createElement(VisTaskRunnerApp, {
|
|
2036
|
-
autoExitSeconds,
|
|
2037
|
-
parallelSlots,
|
|
2038
|
-
projectNames,
|
|
2039
|
-
stdinRegistry: stdinRegistry ?? /* @__PURE__ */ new Map(),
|
|
2040
|
-
store,
|
|
2041
|
-
targets: args.targets,
|
|
2042
|
-
tasks
|
|
2043
|
-
}),
|
|
2044
|
-
{
|
|
2045
|
-
alternateScreen: true,
|
|
2046
|
-
exitOnCtrlC: false,
|
|
2047
|
-
interactive: true,
|
|
2048
|
-
patchConsole: true
|
|
2049
|
-
}
|
|
2050
|
-
);
|
|
2051
|
-
instance.waitUntilExit().then(
|
|
2052
|
-
() => {
|
|
2053
|
-
clearKeepAlive();
|
|
2054
|
-
killAllPtyProcesses();
|
|
2055
|
-
process.removeListener("SIGINT", onSignal);
|
|
2056
|
-
process.removeListener("SIGTERM", onSignal);
|
|
2057
|
-
printExitSummary();
|
|
2058
|
-
resolveDone();
|
|
2059
|
-
},
|
|
2060
|
-
() => {
|
|
2061
|
-
clearKeepAlive();
|
|
2062
|
-
killAllPtyProcesses();
|
|
2063
|
-
process.removeListener("SIGINT", onSignal);
|
|
2064
|
-
process.removeListener("SIGTERM", onSignal);
|
|
2065
|
-
resolveDone();
|
|
2066
|
-
}
|
|
2067
|
-
);
|
|
2068
|
-
},
|
|
2069
|
-
startTasks(started) {
|
|
2070
|
-
store.startTasks(started);
|
|
2071
|
-
if (!tickTimer) {
|
|
2072
|
-
tickTimer = setInterval(() => {
|
|
2073
|
-
store.tick();
|
|
2074
|
-
}, 100);
|
|
2075
|
-
}
|
|
2076
|
-
}
|
|
2077
|
-
};
|
|
2078
|
-
return { lifeCycle, renderIsDone, store };
|
|
2079
|
-
};
|
|
2080
|
-
|
|
2081
|
-
const parseOutputStyle = (value) => value === "quiet" ? "quiet" : "normal";
|
|
2082
|
-
const resolveTaskOutputStyle = (task, fallback) => {
|
|
2083
|
-
const visOptions = task.overrides["visOptions"];
|
|
2084
|
-
if (visOptions?.outputStyle === "normal" || visOptions?.outputStyle === "quiet") {
|
|
2085
|
-
return visOptions.outputStyle;
|
|
2086
|
-
}
|
|
2087
|
-
return fallback;
|
|
2088
|
-
};
|
|
2089
|
-
class StaticOutputLifeCycle {
|
|
2090
|
-
#projectNames;
|
|
2091
|
-
#targets;
|
|
2092
|
-
#tasks;
|
|
2093
|
-
#failedTasks = [];
|
|
2094
|
-
#cachedTasks = [];
|
|
2095
|
-
#allCompletedTasks = /* @__PURE__ */ new Map();
|
|
2096
|
-
#logReporter;
|
|
2097
|
-
#outputStyle;
|
|
2098
|
-
#commandStartTime = 0;
|
|
2099
|
-
constructor(options) {
|
|
2100
|
-
this.#projectNames = options.projectNames;
|
|
2101
|
-
this.#targets = options.args.targets;
|
|
2102
|
-
this.#tasks = options.tasks;
|
|
2103
|
-
this.#logReporter = options.logReporter;
|
|
2104
|
-
this.#outputStyle = options.outputStyle ?? "normal";
|
|
2105
|
-
}
|
|
2106
|
-
startCommand() {
|
|
2107
|
-
this.#commandStartTime = Date.now();
|
|
2108
|
-
const columns = process.stdout.columns || 80;
|
|
2109
|
-
const title = `Running ${formatTargetsAndProjects(this.#projectNames, this.#targets, this.#tasks)}`;
|
|
2110
|
-
const header = renderToString(React.createElement(Header, { title, variant: "info" }), { columns });
|
|
2111
|
-
process.stdout.write(header);
|
|
2112
|
-
const firstTask = this.#tasks[0];
|
|
2113
|
-
const overrideEntries = firstTask?.overrides ? Object.entries(firstTask.overrides).filter(([flag]) => flag !== "command") : [];
|
|
2114
|
-
if (overrideEntries.length > 0) {
|
|
2115
|
-
process.stdout.write(`
|
|
2116
|
-
With additional flags:
|
|
2117
|
-
`);
|
|
2118
|
-
for (const [flag, value] of overrideEntries) {
|
|
2119
|
-
process.stdout.write(`${formatFlags(" ", flag, value)}
|
|
2120
|
-
`);
|
|
2121
|
-
}
|
|
2122
|
-
}
|
|
2123
|
-
process.stdout.write("\n");
|
|
2124
|
-
}
|
|
2125
|
-
startTasks(tasks) {
|
|
2126
|
-
const columns = process.stdout.columns || 80;
|
|
2127
|
-
for (const task of tasks) {
|
|
2128
|
-
const line = renderToString(React.createElement(Text, null, React.createElement(Text, { dimColor: true }, ">"), ` ${task.id}`), { columns });
|
|
2129
|
-
process.stdout.write(`${line}
|
|
2130
|
-
`);
|
|
2131
|
-
}
|
|
2132
|
-
}
|
|
2133
|
-
endTasks(taskResults) {
|
|
2134
|
-
const columns = process.stdout.columns || 80;
|
|
2135
|
-
for (const result of taskResults) {
|
|
2136
|
-
this.#allCompletedTasks.set(result.task.id, result);
|
|
2137
|
-
if (result.status === "failure") {
|
|
2138
|
-
this.#failedTasks.push(result);
|
|
2139
|
-
} else if (isCacheStatus$1(result.status)) {
|
|
2140
|
-
this.#cachedTasks.push(result);
|
|
2141
|
-
}
|
|
2142
|
-
const icon = getStatusIcon(result.status);
|
|
2143
|
-
const elapsedString = result.startTime && result.endTime ? ` (${formatMs(result.endTime - result.startTime)})` : "";
|
|
2144
|
-
const cacheLabelString = isCacheStatus$1(result.status) ? " [cache]" : "";
|
|
2145
|
-
const line = renderToString(
|
|
2146
|
-
React.createElement(
|
|
2147
|
-
Text,
|
|
2148
|
-
null,
|
|
2149
|
-
icon,
|
|
2150
|
-
` ${result.task.id}`,
|
|
2151
|
-
cacheLabelString ? React.createElement(Text, { color: "cyan" }, cacheLabelString) : null,
|
|
2152
|
-
elapsedString ? React.createElement(Text, { dimColor: true }, elapsedString) : null
|
|
2153
|
-
),
|
|
2154
|
-
{ columns }
|
|
2155
|
-
);
|
|
2156
|
-
process.stdout.write(`${line}
|
|
2157
|
-
`);
|
|
2158
|
-
}
|
|
2159
|
-
}
|
|
2160
|
-
printTaskTerminalOutput(task, status, terminalOutput) {
|
|
2161
|
-
if (resolveTaskOutputStyle(task, this.#outputStyle) === "quiet" && (status === "success" || isCacheStatus$1(status))) {
|
|
2162
|
-
return;
|
|
2163
|
-
}
|
|
2164
|
-
if (this.#logReporter) {
|
|
2165
|
-
this.#logReporter.printTaskTerminalOutput(task, status, terminalOutput);
|
|
2166
|
-
return;
|
|
2167
|
-
}
|
|
2168
|
-
logCommandOutputCI(task.id, status, terminalOutput);
|
|
2169
|
-
}
|
|
2170
|
-
endCommand() {
|
|
2171
|
-
const totalTime = formatMs(Date.now() - this.#commandStartTime);
|
|
2172
|
-
const skippedIds = this.#tasks.filter((t) => !this.#allCompletedTasks.has(t.id)).map((t) => t.id);
|
|
2173
|
-
process.stdout.write("\n");
|
|
2174
|
-
const columns = process.stdout.columns || 80;
|
|
2175
|
-
const summary = renderToString(
|
|
2176
|
-
React.createElement(CommandSummary, {
|
|
2177
|
-
cached: this.#cachedTasks.length,
|
|
2178
|
-
failed: this.#failedTasks.length,
|
|
2179
|
-
failedIds: this.#failedTasks.map((r) => r.task.id),
|
|
2180
|
-
projectNames: this.#projectNames,
|
|
2181
|
-
skippedIds: skippedIds.length > 0 ? skippedIds : void 0,
|
|
2182
|
-
succeeded: this.#allCompletedTasks.size - this.#failedTasks.length - this.#cachedTasks.length,
|
|
2183
|
-
targets: this.#targets,
|
|
2184
|
-
tasks: this.#tasks,
|
|
2185
|
-
took: totalTime
|
|
2186
|
-
}),
|
|
2187
|
-
{ columns }
|
|
2188
|
-
);
|
|
2189
|
-
process.stdout.write(`${summary}
|
|
2190
|
-
`);
|
|
2191
|
-
}
|
|
2192
|
-
}
|
|
2193
|
-
|
|
2194
|
-
const createVisHooks = () => createHooks();
|
|
2195
|
-
const registerPlugins = async (hooks, plugins) => {
|
|
2196
|
-
if (!plugins || plugins.length === 0) {
|
|
2197
|
-
return;
|
|
2198
|
-
}
|
|
2199
|
-
for (const plugin of plugins) {
|
|
2200
|
-
if (plugin.hooks) {
|
|
2201
|
-
for (const [name, handler] of Object.entries(plugin.hooks)) {
|
|
2202
|
-
const handlers = Array.isArray(handler) ? handler : [handler];
|
|
2203
|
-
for (const fn of handlers) {
|
|
2204
|
-
hooks.hook(name, fn);
|
|
2205
|
-
}
|
|
2206
|
-
}
|
|
2207
|
-
}
|
|
2208
|
-
if (plugin.setup) {
|
|
2209
|
-
await plugin.setup(hooks);
|
|
2210
|
-
}
|
|
2211
|
-
}
|
|
2212
|
-
};
|
|
2213
|
-
class HookableLifeCycle {
|
|
2214
|
-
#hooks;
|
|
2215
|
-
#onError;
|
|
2216
|
-
/** Cached {task.id → Task} for the current run, filled on startTasks. */
|
|
2217
|
-
#inFlight = /* @__PURE__ */ new Map();
|
|
2218
|
-
constructor(hooks, onError) {
|
|
2219
|
-
this.#hooks = hooks;
|
|
2220
|
-
this.#onError = onError;
|
|
2221
|
-
}
|
|
2222
|
-
startTasks(tasks) {
|
|
2223
|
-
for (const task of tasks) {
|
|
2224
|
-
this.#inFlight.set(task.id, task);
|
|
2225
|
-
this.#fire("task:before", task);
|
|
2226
|
-
}
|
|
2227
|
-
}
|
|
2228
|
-
endTasks(results) {
|
|
2229
|
-
for (const result of results) {
|
|
2230
|
-
this.#inFlight.delete(result.task.id);
|
|
2231
|
-
this.#fire("task:after", result.task, result);
|
|
2232
|
-
if (result.status === "failure") {
|
|
2233
|
-
this.#fire("task:failure", result.task, result);
|
|
2234
|
-
} else if (isCacheStatus(result.status)) {
|
|
2235
|
-
this.#fire("task:cacheHit", result.task, result);
|
|
2236
|
-
}
|
|
2237
|
-
}
|
|
2238
|
-
}
|
|
2239
|
-
printCacheMiss(task, reasons) {
|
|
2240
|
-
this.#fire("task:cacheMiss", task, reasons);
|
|
2241
|
-
}
|
|
2242
|
-
onTaskStdout(task, chunk) {
|
|
2243
|
-
this.#fire("task:stdout", task, chunk);
|
|
2244
|
-
}
|
|
2245
|
-
onTaskStderr(task, chunk) {
|
|
2246
|
-
this.#fire("task:stderr", task, chunk);
|
|
2247
|
-
}
|
|
2248
|
-
#fire(name, ...args) {
|
|
2249
|
-
Promise.resolve(
|
|
2250
|
-
// `callHook` is typed as `(name, ...args: any[])` by
|
|
2251
|
-
// hookable, so the `Parameters<>` spread needs the cast.
|
|
2252
|
-
this.#hooks.callHook(name, ...args)
|
|
2253
|
-
).catch((error) => {
|
|
2254
|
-
if (this.#onError) {
|
|
2255
|
-
try {
|
|
2256
|
-
this.#onError(name, error);
|
|
2257
|
-
} catch {
|
|
2258
|
-
}
|
|
2259
|
-
}
|
|
2260
|
-
});
|
|
2261
|
-
}
|
|
2262
|
-
}
|
|
2263
|
-
const isCacheStatus = (status) => status === "local-cache" || status === "local-cache-kept-existing" || status === "remote-cache";
|
|
2264
|
-
|
|
2265
|
-
const appendToShellHistory = async (commandLine) => {
|
|
2266
|
-
if (process.env["VIS_NO_SHELL_HISTORY"]) {
|
|
2267
|
-
return;
|
|
2268
|
-
}
|
|
2269
|
-
if (platform() === "win32") {
|
|
2270
|
-
return;
|
|
2271
|
-
}
|
|
2272
|
-
const shellPath = process.env["SHELL"];
|
|
2273
|
-
if (!shellPath) {
|
|
2274
|
-
return;
|
|
2275
|
-
}
|
|
2276
|
-
const shell = basename(shellPath);
|
|
2277
|
-
try {
|
|
2278
|
-
if (shell === "zsh") {
|
|
2279
|
-
await writeZshHistory(commandLine);
|
|
2280
|
-
return;
|
|
2281
|
-
}
|
|
2282
|
-
if (shell === "bash") {
|
|
2283
|
-
await writeBashHistory(commandLine);
|
|
2284
|
-
return;
|
|
2285
|
-
}
|
|
2286
|
-
if (shell === "fish") {
|
|
2287
|
-
await writeFishHistory(commandLine);
|
|
2288
|
-
}
|
|
2289
|
-
} catch {
|
|
2290
|
-
}
|
|
2291
|
-
};
|
|
2292
|
-
const writeZshHistory = async (commandLine) => {
|
|
2293
|
-
const target = process.env["HISTFILE"] ?? join(process.env["ZDOTDIR"] ?? homedir(), ".zsh_history");
|
|
2294
|
-
const entry = `: ${Math.floor(Date.now() / 1e3)}:0;${commandLine}
|
|
2295
|
-
`;
|
|
2296
|
-
await appendFile(target, entry);
|
|
2297
|
-
};
|
|
2298
|
-
const writeBashHistory = async (commandLine) => {
|
|
2299
|
-
const target = process.env["HISTFILE"] ?? join(homedir(), ".bash_history");
|
|
2300
|
-
await appendFile(target, `${commandLine}
|
|
2301
|
-
`);
|
|
2302
|
-
};
|
|
2303
|
-
const writeFishHistory = async (commandLine) => {
|
|
2304
|
-
const target = join(homedir(), ".local", "share", "fish", "fish_history");
|
|
2305
|
-
const escaped = commandLine.replaceAll("\\", "\\\\").replaceAll("\n", String.raw`\n`);
|
|
2306
|
-
const entry = `- cmd: ${escaped}
|
|
2307
|
-
when: ${Math.floor(Date.now() / 1e3)}
|
|
2308
|
-
`;
|
|
2309
|
-
await appendFile(target, entry);
|
|
2310
|
-
};
|
|
2311
|
-
|
|
2312
|
-
const scheduleTimeoutKill = (options) => {
|
|
2313
|
-
const { killGracePeriodMs: rawKillGracePeriodMs = 5e3, onTimeout, sendSignal, timeoutMs } = options;
|
|
2314
|
-
if (timeoutMs <= 0) {
|
|
2315
|
-
return { cancel: () => {
|
|
2316
|
-
} };
|
|
2317
|
-
}
|
|
2318
|
-
const killGracePeriodMs = Math.max(rawKillGracePeriodMs, 0);
|
|
2319
|
-
let escalationHandle;
|
|
2320
|
-
const timeoutHandle = setTimeout(() => {
|
|
2321
|
-
onTimeout?.();
|
|
2322
|
-
sendSignal("SIGTERM");
|
|
2323
|
-
if (killGracePeriodMs > 0) {
|
|
2324
|
-
escalationHandle = setTimeout(() => {
|
|
2325
|
-
sendSignal("SIGKILL");
|
|
2326
|
-
}, killGracePeriodMs);
|
|
2327
|
-
}
|
|
2328
|
-
}, timeoutMs);
|
|
2329
|
-
return {
|
|
2330
|
-
cancel: () => {
|
|
2331
|
-
clearTimeout(timeoutHandle);
|
|
2332
|
-
if (escalationHandle) {
|
|
2333
|
-
clearTimeout(escalationHandle);
|
|
2334
|
-
}
|
|
2335
|
-
}
|
|
2336
|
-
};
|
|
2337
|
-
};
|
|
2338
|
-
|
|
2339
|
-
const IGNORE_PATTERNS = [/node_modules(?:\/|$)/, /\.git(?:\/|$)/, /\.vis(?:\/|$)/, /\.task-runner(?:\/|$)/];
|
|
2340
|
-
const collectTrackedWatchTargets = (results, workspaceRoot) => {
|
|
2341
|
-
const files = /* @__PURE__ */ new Set();
|
|
2342
|
-
const directories = /* @__PURE__ */ new Set();
|
|
2343
|
-
for (const [, result] of results) {
|
|
2344
|
-
const nodes = result.task.hashDetails?.nodes;
|
|
2345
|
-
if (!nodes) {
|
|
2346
|
-
continue;
|
|
2347
|
-
}
|
|
2348
|
-
for (const filePath of Object.keys(nodes)) {
|
|
2349
|
-
files.add(filePath);
|
|
2350
|
-
directories.add(dirname(resolve(workspaceRoot, filePath)));
|
|
2351
|
-
}
|
|
2352
|
-
}
|
|
2353
|
-
const sorted = [...directories].sort();
|
|
2354
|
-
const pruned = [];
|
|
2355
|
-
for (const directory of sorted) {
|
|
2356
|
-
if (pruned.some((parent) => directory === parent || directory.startsWith(`${parent}/`))) {
|
|
2357
|
-
continue;
|
|
2358
|
-
}
|
|
2359
|
-
pruned.push(directory);
|
|
2360
|
-
}
|
|
2361
|
-
return { directories: pruned, files };
|
|
2362
|
-
};
|
|
2363
|
-
const createTrackedFileFilter = (trackedFiles, workspaceRoot, watchedDirectories) => {
|
|
2364
|
-
const lookup = /* @__PURE__ */ new Set();
|
|
2365
|
-
for (const file of trackedFiles) {
|
|
2366
|
-
const absolute = resolve(workspaceRoot, file);
|
|
2367
|
-
for (const directory of watchedDirectories) {
|
|
2368
|
-
if (absolute === directory || absolute.startsWith(`${directory}/`)) {
|
|
2369
|
-
const rel = relative(directory, absolute);
|
|
2370
|
-
if (rel.length > 0) {
|
|
2371
|
-
lookup.add(rel);
|
|
2372
|
-
}
|
|
2373
|
-
}
|
|
2374
|
-
}
|
|
2375
|
-
}
|
|
2376
|
-
return (path) => {
|
|
2377
|
-
const normalized = path.replaceAll("\\", "/");
|
|
2378
|
-
return lookup.has(normalized);
|
|
2379
|
-
};
|
|
2380
|
-
};
|
|
2381
|
-
const shouldIgnore = (path) => {
|
|
2382
|
-
const normalized = path.replaceAll("\\", "/");
|
|
2383
|
-
return IGNORE_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
2384
|
-
};
|
|
2385
|
-
const startWatcher = (options) => {
|
|
2386
|
-
const { debounceMs = 150, filter, onChange, paths } = options;
|
|
2387
|
-
const watchers = [];
|
|
2388
|
-
let pendingChanges = /* @__PURE__ */ new Set();
|
|
2389
|
-
let timer;
|
|
2390
|
-
const flush = () => {
|
|
2391
|
-
timer = void 0;
|
|
2392
|
-
const changes = [...pendingChanges];
|
|
2393
|
-
pendingChanges = /* @__PURE__ */ new Set();
|
|
2394
|
-
if (changes.length === 0) {
|
|
2395
|
-
return;
|
|
2396
|
-
}
|
|
2397
|
-
Promise.resolve(onChange(changes)).catch((error) => {
|
|
2398
|
-
console.error("[vis watch] onChange handler failed:", error);
|
|
2399
|
-
});
|
|
2400
|
-
};
|
|
2401
|
-
for (const path of paths) {
|
|
2402
|
-
try {
|
|
2403
|
-
const watcher = watch(path, { recursive: true }, (_eventType, filename) => {
|
|
2404
|
-
if (!filename) {
|
|
2405
|
-
return;
|
|
2406
|
-
}
|
|
2407
|
-
if (shouldIgnore(filename)) {
|
|
2408
|
-
return;
|
|
2409
|
-
}
|
|
2410
|
-
if (filter && !filter(filename)) {
|
|
2411
|
-
return;
|
|
2412
|
-
}
|
|
2413
|
-
pendingChanges.add(filename);
|
|
2414
|
-
if (timer) {
|
|
2415
|
-
clearTimeout(timer);
|
|
2416
|
-
}
|
|
2417
|
-
timer = setTimeout(flush, debounceMs);
|
|
2418
|
-
});
|
|
2419
|
-
watchers.push(watcher);
|
|
2420
|
-
} catch (error) {
|
|
2421
|
-
console.warn(`[vis watch] unable to watch ${path}: ${error.message}`);
|
|
2422
|
-
}
|
|
2423
|
-
}
|
|
2424
|
-
return {
|
|
2425
|
-
close: () => {
|
|
2426
|
-
if (timer) {
|
|
2427
|
-
clearTimeout(timer);
|
|
2428
|
-
}
|
|
2429
|
-
for (const watcher of watchers) {
|
|
2430
|
-
try {
|
|
2431
|
-
watcher.close();
|
|
2432
|
-
} catch {
|
|
2433
|
-
}
|
|
2434
|
-
}
|
|
2435
|
-
}
|
|
2436
|
-
};
|
|
2437
|
-
};
|
|
2438
|
-
|
|
2439
|
-
const applyProjectFilter = (baseTasks, filter) => {
|
|
2440
|
-
const normalized = filter?.trim();
|
|
2441
|
-
if (!normalized) {
|
|
2442
|
-
return { filter: void 0, tasks: [...baseTasks] };
|
|
2443
|
-
}
|
|
2444
|
-
const needle = normalized.toLowerCase();
|
|
2445
|
-
return {
|
|
2446
|
-
filter: normalized,
|
|
2447
|
-
tasks: baseTasks.filter((task) => task.target.project.toLowerCase().includes(needle))
|
|
2448
|
-
};
|
|
2449
|
-
};
|
|
2450
|
-
|
|
2451
|
-
const KEYBIND_HELP = [
|
|
2452
|
-
"",
|
|
2453
|
-
" Watch keybinds:",
|
|
2454
|
-
" r, Enter rerun",
|
|
2455
|
-
" a rerun all (clear filter)",
|
|
2456
|
-
" p filter by project name",
|
|
2457
|
-
" q, Ctrl+C quit",
|
|
2458
|
-
" h, ? show this help",
|
|
2459
|
-
""
|
|
2460
|
-
].join("\n");
|
|
2461
|
-
const writeHelp = (output) => {
|
|
2462
|
-
output.write(`${KEYBIND_HELP}
|
|
2463
|
-
`);
|
|
2464
|
-
};
|
|
2465
|
-
const defaultPromptFilter = async (input, output) => {
|
|
2466
|
-
output.write("filter projects (empty to cancel) > ");
|
|
2467
|
-
const tty = input;
|
|
2468
|
-
const wasRaw = tty.isRaw === true;
|
|
2469
|
-
if (wasRaw) {
|
|
2470
|
-
try {
|
|
2471
|
-
tty.setRawMode?.(false);
|
|
2472
|
-
} catch {
|
|
2473
|
-
}
|
|
2474
|
-
}
|
|
2475
|
-
return await new Promise((resolve) => {
|
|
2476
|
-
let buffer = "";
|
|
2477
|
-
const onData = (chunk) => {
|
|
2478
|
-
buffer += typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
2479
|
-
const newlineIndex = buffer.indexOf("\n");
|
|
2480
|
-
if (newlineIndex === -1) {
|
|
2481
|
-
return;
|
|
2482
|
-
}
|
|
2483
|
-
const line = buffer.slice(0, newlineIndex).replace(/\r$/, "").trim();
|
|
2484
|
-
input.off("data", onData);
|
|
2485
|
-
if (wasRaw) {
|
|
2486
|
-
try {
|
|
2487
|
-
tty.setRawMode?.(true);
|
|
2488
|
-
} catch {
|
|
2489
|
-
}
|
|
2490
|
-
}
|
|
2491
|
-
resolve(line.length > 0 ? line : void 0);
|
|
2492
|
-
};
|
|
2493
|
-
input.on("data", onData);
|
|
2494
|
-
});
|
|
2495
|
-
};
|
|
2496
|
-
const installKeybinds = (options) => {
|
|
2497
|
-
const { handlers } = options;
|
|
2498
|
-
const input = options.input ?? process.stdin;
|
|
2499
|
-
const output = options.output ?? process.stdout;
|
|
2500
|
-
const promptFilter = options.promptFilter ?? defaultPromptFilter;
|
|
2501
|
-
if (!input || input.isTTY === false) {
|
|
2502
|
-
return { close: () => {
|
|
2503
|
-
} };
|
|
2504
|
-
}
|
|
2505
|
-
emitKeypressEvents(input);
|
|
2506
|
-
const previousIsRaw = input.isRaw === true;
|
|
2507
|
-
try {
|
|
2508
|
-
input.setRawMode?.(true);
|
|
2509
|
-
} catch {
|
|
2510
|
-
return { close: () => {
|
|
2511
|
-
} };
|
|
2512
|
-
}
|
|
2513
|
-
if (typeof input.resume === "function") {
|
|
2514
|
-
input.resume();
|
|
2515
|
-
}
|
|
2516
|
-
let prompting = false;
|
|
2517
|
-
const dispatch = async (key) => {
|
|
2518
|
-
if (key.ctrl === true && key.name === "c") {
|
|
2519
|
-
await handlers.onQuit();
|
|
2520
|
-
return;
|
|
2521
|
-
}
|
|
2522
|
-
if (prompting) {
|
|
2523
|
-
return;
|
|
2524
|
-
}
|
|
2525
|
-
switch (key.name) {
|
|
2526
|
-
case "?":
|
|
2527
|
-
case "h": {
|
|
2528
|
-
await handlers.onHelp();
|
|
2529
|
-
break;
|
|
2530
|
-
}
|
|
2531
|
-
case "a": {
|
|
2532
|
-
await handlers.onClearFilter();
|
|
2533
|
-
break;
|
|
2534
|
-
}
|
|
2535
|
-
case "p": {
|
|
2536
|
-
prompting = true;
|
|
2537
|
-
try {
|
|
2538
|
-
const pattern = await promptFilter(input, output);
|
|
2539
|
-
if (pattern === void 0) {
|
|
2540
|
-
output.write("filter cancelled.\n");
|
|
2541
|
-
} else {
|
|
2542
|
-
await handlers.onFilter(pattern);
|
|
2543
|
-
}
|
|
2544
|
-
} finally {
|
|
2545
|
-
prompting = false;
|
|
2546
|
-
}
|
|
2547
|
-
break;
|
|
2548
|
-
}
|
|
2549
|
-
case "q": {
|
|
2550
|
-
await handlers.onQuit();
|
|
2551
|
-
break;
|
|
2552
|
-
}
|
|
2553
|
-
case "r": {
|
|
2554
|
-
await handlers.onRerun();
|
|
2555
|
-
break;
|
|
2556
|
-
}
|
|
2557
|
-
case "return": {
|
|
2558
|
-
await handlers.onRerun();
|
|
2559
|
-
break;
|
|
2560
|
-
}
|
|
2561
|
-
}
|
|
2562
|
-
};
|
|
2563
|
-
const onKeypress = (_str, key) => {
|
|
2564
|
-
dispatch(key).catch((error) => {
|
|
2565
|
-
output.write(`[vis watch] keybind handler failed: ${error.message}
|
|
2566
|
-
`);
|
|
2567
|
-
});
|
|
2568
|
-
};
|
|
2569
|
-
input.on("keypress", onKeypress);
|
|
2570
|
-
return {
|
|
2571
|
-
close: () => {
|
|
2572
|
-
input.off("keypress", onKeypress);
|
|
2573
|
-
if (!previousIsRaw) {
|
|
2574
|
-
try {
|
|
2575
|
-
input.setRawMode?.(false);
|
|
2576
|
-
} catch {
|
|
2577
|
-
}
|
|
2578
|
-
}
|
|
2579
|
-
const pausable = input;
|
|
2580
|
-
if (typeof pausable.pause === "function") {
|
|
2581
|
-
try {
|
|
2582
|
-
pausable.pause();
|
|
2583
|
-
} catch {
|
|
2584
|
-
}
|
|
2585
|
-
}
|
|
2586
|
-
}
|
|
2587
|
-
};
|
|
2588
|
-
};
|
|
2589
|
-
|
|
2590
|
-
const getVisOptions = (task) => {
|
|
2591
|
-
const options = task.overrides["visOptions"];
|
|
2592
|
-
return options && typeof options === "object" ? options : void 0;
|
|
2593
|
-
};
|
|
2594
|
-
const applyServiceRegistry = async (input) => {
|
|
2595
|
-
const { initialTasks, probe, registeredEntries, taskGraph, visVersion } = input;
|
|
2596
|
-
const aliveById = /* @__PURE__ */ new Map();
|
|
2597
|
-
const staleById = /* @__PURE__ */ new Map();
|
|
2598
|
-
const probeFailedById = /* @__PURE__ */ new Map();
|
|
2599
|
-
for (const entry of registeredEntries) {
|
|
2600
|
-
if (!isAlive(entry.pid)) {
|
|
2601
|
-
continue;
|
|
2602
|
-
}
|
|
2603
|
-
if (entry.visVersion !== visVersion) {
|
|
2604
|
-
staleById.set(entry.id, entry);
|
|
2605
|
-
continue;
|
|
2606
|
-
}
|
|
2607
|
-
aliveById.set(entry.id, entry);
|
|
2608
|
-
}
|
|
2609
|
-
if (probe) {
|
|
2610
|
-
const aliveEntries = [...aliveById.values()];
|
|
2611
|
-
const probeResults = await Promise.all(
|
|
2612
|
-
aliveEntries.map(async (entry) => {
|
|
2613
|
-
try {
|
|
2614
|
-
return [entry.id, await probe(entry)];
|
|
2615
|
-
} catch {
|
|
2616
|
-
return [entry.id, false];
|
|
2617
|
-
}
|
|
2618
|
-
})
|
|
2619
|
-
);
|
|
2620
|
-
for (const [id, ok] of probeResults) {
|
|
2621
|
-
if (!ok) {
|
|
2622
|
-
const entry = aliveById.get(id);
|
|
2623
|
-
aliveById.delete(id);
|
|
2624
|
-
if (entry) {
|
|
2625
|
-
probeFailedById.set(id, entry);
|
|
2626
|
-
}
|
|
2627
|
-
}
|
|
2628
|
-
}
|
|
2629
|
-
}
|
|
2630
|
-
const initialTaskIds = new Set(initialTasks.map((task) => task.id));
|
|
2631
|
-
const satisfied = /* @__PURE__ */ new Set();
|
|
2632
|
-
const diagnostics = [];
|
|
2633
|
-
const satisfiedServices = [];
|
|
2634
|
-
const hasDependent = /* @__PURE__ */ new Set();
|
|
2635
|
-
for (const deps of Object.values(taskGraph.dependencies)) {
|
|
2636
|
-
for (const depId of deps) {
|
|
2637
|
-
hasDependent.add(depId);
|
|
2638
|
-
}
|
|
2639
|
-
}
|
|
2640
|
-
for (const [taskId, task] of Object.entries(taskGraph.tasks)) {
|
|
2641
|
-
const options = getVisOptions(task);
|
|
2642
|
-
if (!options?.service) {
|
|
2643
|
-
continue;
|
|
2644
|
-
}
|
|
2645
|
-
if (aliveById.has(taskId)) {
|
|
2646
|
-
satisfied.add(taskId);
|
|
2647
|
-
satisfiedServices.push(aliveById.get(taskId));
|
|
2648
|
-
continue;
|
|
2649
|
-
}
|
|
2650
|
-
const isUserInvoked = initialTaskIds.has(taskId);
|
|
2651
|
-
if (isUserInvoked) {
|
|
2652
|
-
continue;
|
|
2653
|
-
}
|
|
2654
|
-
if (!hasDependent.has(taskId)) {
|
|
2655
|
-
continue;
|
|
2656
|
-
}
|
|
2657
|
-
const stale = staleById.get(taskId);
|
|
2658
|
-
const probeFailed = probeFailedById.get(taskId);
|
|
2659
|
-
let reason;
|
|
2660
|
-
if (stale) {
|
|
2661
|
-
reason = `Service ${taskId} is registered with vis ${stale.visVersion}, but this invocation is vis ${visVersion}. Restart with \`vis service restart ${taskId}\` to pick up the new version.`;
|
|
2662
|
-
} else if (probeFailed) {
|
|
2663
|
-
reason = `Service ${taskId} is registered (PID ${String(probeFailed.pid)}) but failed its readiness probe — the wrapper process is alive but the underlying server is not responding. Run \`vis service restart ${taskId}\` to recover.`;
|
|
2664
|
-
} else {
|
|
2665
|
-
reason = `Target depends on the service ${taskId}, which is not running. Run \`vis service start ${taskId}\` first, or invoke \`${taskId}\` directly.`;
|
|
2666
|
-
}
|
|
2667
|
-
diagnostics.push({ message: reason, targetId: taskId });
|
|
2668
|
-
}
|
|
2669
|
-
if (satisfied.size === 0) {
|
|
2670
|
-
return {
|
|
2671
|
-
diagnostics,
|
|
2672
|
-
initialTasks,
|
|
2673
|
-
satisfiedServices,
|
|
2674
|
-
serviceEnvByTaskId: /* @__PURE__ */ new Map(),
|
|
2675
|
-
taskGraph
|
|
2676
|
-
};
|
|
2677
|
-
}
|
|
2678
|
-
const serviceEnvByTaskId = /* @__PURE__ */ new Map();
|
|
2679
|
-
const collectTransitiveServices = (start) => {
|
|
2680
|
-
const found = /* @__PURE__ */ new Set();
|
|
2681
|
-
const stack = [...taskGraph.dependencies[start] ?? []];
|
|
2682
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2683
|
-
while (stack.length > 0) {
|
|
2684
|
-
const next = stack.pop();
|
|
2685
|
-
if (seen.has(next)) {
|
|
2686
|
-
continue;
|
|
2687
|
-
}
|
|
2688
|
-
seen.add(next);
|
|
2689
|
-
if (satisfied.has(next)) {
|
|
2690
|
-
found.add(next);
|
|
2691
|
-
}
|
|
2692
|
-
for (const depOfNext of taskGraph.dependencies[next] ?? []) {
|
|
2693
|
-
if (!seen.has(depOfNext)) {
|
|
2694
|
-
stack.push(depOfNext);
|
|
2695
|
-
}
|
|
2696
|
-
}
|
|
2697
|
-
}
|
|
2698
|
-
return [...found].sort();
|
|
2699
|
-
};
|
|
2700
|
-
for (const dependentId of Object.keys(taskGraph.dependencies)) {
|
|
2701
|
-
if (satisfied.has(dependentId)) {
|
|
2702
|
-
continue;
|
|
2703
|
-
}
|
|
2704
|
-
const services = collectTransitiveServices(dependentId);
|
|
2705
|
-
if (services.length === 0) {
|
|
2706
|
-
continue;
|
|
2707
|
-
}
|
|
2708
|
-
const merged = {};
|
|
2709
|
-
for (const serviceId of services) {
|
|
2710
|
-
const entry = aliveById.get(serviceId);
|
|
2711
|
-
if (!entry) {
|
|
2712
|
-
continue;
|
|
2713
|
-
}
|
|
2714
|
-
Object.assign(merged, entry.env);
|
|
2715
|
-
}
|
|
2716
|
-
if (Object.keys(merged).length > 0) {
|
|
2717
|
-
serviceEnvByTaskId.set(dependentId, merged);
|
|
2718
|
-
}
|
|
2719
|
-
}
|
|
2720
|
-
const filteredTasks = {};
|
|
2721
|
-
for (const [id, task] of Object.entries(taskGraph.tasks)) {
|
|
2722
|
-
if (!satisfied.has(id)) {
|
|
2723
|
-
filteredTasks[id] = task;
|
|
2724
|
-
}
|
|
2725
|
-
}
|
|
2726
|
-
const filteredDependencies = {};
|
|
2727
|
-
for (const [id, deps] of Object.entries(taskGraph.dependencies)) {
|
|
2728
|
-
if (satisfied.has(id)) {
|
|
2729
|
-
continue;
|
|
2730
|
-
}
|
|
2731
|
-
filteredDependencies[id] = deps.filter((depId) => !satisfied.has(depId));
|
|
2732
|
-
}
|
|
2733
|
-
const reverseEdgeCount = /* @__PURE__ */ new Map();
|
|
2734
|
-
for (const id of Object.keys(filteredTasks)) {
|
|
2735
|
-
reverseEdgeCount.set(id, 0);
|
|
2736
|
-
}
|
|
2737
|
-
for (const deps of Object.values(filteredDependencies)) {
|
|
2738
|
-
for (const depId of deps) {
|
|
2739
|
-
if (reverseEdgeCount.has(depId)) {
|
|
2740
|
-
reverseEdgeCount.set(depId, (reverseEdgeCount.get(depId) ?? 0) + 1);
|
|
2741
|
-
}
|
|
2742
|
-
}
|
|
2743
|
-
}
|
|
2744
|
-
const filteredRoots = [];
|
|
2745
|
-
for (const root of taskGraph.roots) {
|
|
2746
|
-
if (!satisfied.has(root) && filteredTasks[root]) {
|
|
2747
|
-
filteredRoots.push(root);
|
|
2748
|
-
}
|
|
2749
|
-
}
|
|
2750
|
-
for (const [id, count] of reverseEdgeCount) {
|
|
2751
|
-
if (count === 0 && !filteredRoots.includes(id) && filteredTasks[id]) {
|
|
2752
|
-
filteredRoots.push(id);
|
|
2753
|
-
}
|
|
2754
|
-
}
|
|
2755
|
-
const filteredInitialTasks = initialTasks.filter((task) => !satisfied.has(task.id));
|
|
2756
|
-
return {
|
|
2757
|
-
diagnostics,
|
|
2758
|
-
initialTasks: filteredInitialTasks,
|
|
2759
|
-
satisfiedServices,
|
|
2760
|
-
serviceEnvByTaskId,
|
|
2761
|
-
taskGraph: {
|
|
2762
|
-
dependencies: filteredDependencies,
|
|
2763
|
-
roots: filteredRoots,
|
|
2764
|
-
tasks: filteredTasks
|
|
2765
|
-
}
|
|
2766
|
-
};
|
|
2767
|
-
};
|
|
2768
|
-
|
|
2769
|
-
const AFFECTED_FILES_ENV = "VIS_AFFECTED_FILES";
|
|
2770
|
-
const SERVICE_PROBE_TIMEOUT_MS = 2e3;
|
|
2771
|
-
const resolveCwd = (workspaceRoot, projectRoot, runFromWorkspaceRoot) => {
|
|
2772
|
-
if (runFromWorkspaceRoot) {
|
|
2773
|
-
return workspaceRoot;
|
|
2774
|
-
}
|
|
2775
|
-
if (!projectRoot) {
|
|
2776
|
-
return workspaceRoot;
|
|
2777
|
-
}
|
|
2778
|
-
return projectRoot.startsWith("/") ? projectRoot : `${workspaceRoot}/${projectRoot}`;
|
|
2779
|
-
};
|
|
2780
|
-
const runPersistentTasks = async (tasks, workspaceRoot, affectedFiles, initCwd) => {
|
|
2781
|
-
const commands = tasks.map((task) => {
|
|
2782
|
-
const command = task.overrides["command"];
|
|
2783
|
-
if (!command) {
|
|
2784
|
-
return void 0;
|
|
2785
|
-
}
|
|
2786
|
-
const visOptions = task.overrides["visOptions"];
|
|
2787
|
-
const cwd = resolveCwd(workspaceRoot, task.projectRoot, Boolean(visOptions?.runFromWorkspaceRoot));
|
|
2788
|
-
const envFileVars = visOptions?.envFile ? loadEnvFile(cwd, visOptions.envFile) : {};
|
|
2789
|
-
const affectedEnv = affectedFiles && (visOptions?.affectedFiles === "env" || visOptions?.affectedFiles === "both") ? { [AFFECTED_FILES_ENV]: affectedFiles.join("\n") } : {};
|
|
2790
|
-
return {
|
|
2791
|
-
command,
|
|
2792
|
-
cwd,
|
|
2793
|
-
env: { INIT_CWD: initCwd, ...envFileVars, ...affectedEnv },
|
|
2794
|
-
name: task.id
|
|
2795
|
-
};
|
|
2796
|
-
}).filter((c) => c !== void 0);
|
|
2797
|
-
if (commands.length === 0) {
|
|
2798
|
-
return;
|
|
2799
|
-
}
|
|
2800
|
-
await runConcurrently(commands, { killOthers: ["failure"] });
|
|
2801
|
-
};
|
|
2802
|
-
const MAX_OUTPUT_BYTES = 256 * 1024;
|
|
2803
|
-
class OutputRingBuffer {
|
|
2804
|
-
#maxBytes;
|
|
2805
|
-
#buffer = "";
|
|
2806
|
-
#truncated = false;
|
|
2807
|
-
constructor(maxBytes) {
|
|
2808
|
-
this.#maxBytes = maxBytes;
|
|
2809
|
-
}
|
|
2810
|
-
append(text) {
|
|
2811
|
-
this.#buffer += text;
|
|
2812
|
-
if (this.#buffer.length > this.#maxBytes) {
|
|
2813
|
-
this.#buffer = this.#buffer.slice(-this.#maxBytes);
|
|
2814
|
-
this.#truncated = true;
|
|
2815
|
-
}
|
|
2816
|
-
}
|
|
2817
|
-
toString() {
|
|
2818
|
-
if (this.#truncated) {
|
|
2819
|
-
return `[...output truncated, showing last ${Math.round(this.#maxBytes / 1024)}KB...]
|
|
2820
|
-
${this.#buffer}`;
|
|
2821
|
-
}
|
|
2822
|
-
return this.#buffer;
|
|
2823
|
-
}
|
|
2824
|
-
}
|
|
2825
|
-
const getTaskOptions = (task) => {
|
|
2826
|
-
const options = task.overrides["visOptions"];
|
|
2827
|
-
if (options && typeof options === "object") {
|
|
2828
|
-
return options;
|
|
2829
|
-
}
|
|
2830
|
-
return void 0;
|
|
2831
|
-
};
|
|
2832
|
-
const singleQuoteEscape = (value) => `'${value.replaceAll("'", String.raw`'\''`)}'`;
|
|
2833
|
-
const buildAffectedFilesArgs = (command, affectedFiles, mode) => {
|
|
2834
|
-
if (!affectedFiles || affectedFiles.length === 0 || mode === false || mode === void 0) {
|
|
2835
|
-
return command;
|
|
2836
|
-
}
|
|
2837
|
-
if (mode === "args" || mode === "both") {
|
|
2838
|
-
const quoted = affectedFiles.map(singleQuoteEscape).join(" ");
|
|
2839
|
-
return `${command} ${quoted}`;
|
|
2840
|
-
}
|
|
2841
|
-
return command;
|
|
2842
|
-
};
|
|
2843
|
-
const FORWARDED_ARGS_KEY = "visForwardedArgs";
|
|
2844
|
-
const appendForwardedArgs = (command, task) => {
|
|
2845
|
-
const args = task.overrides[FORWARDED_ARGS_KEY];
|
|
2846
|
-
if (!Array.isArray(args) || args.length === 0) {
|
|
2847
|
-
return command;
|
|
2848
|
-
}
|
|
2849
|
-
const quoted = args.map(singleQuoteEscape).join(" ");
|
|
2850
|
-
return `${command} ${quoted}`;
|
|
2851
|
-
};
|
|
2852
|
-
const withMutex = async (pool, name, run) => {
|
|
2853
|
-
if (!name) {
|
|
2854
|
-
return run();
|
|
2855
|
-
}
|
|
2856
|
-
const previous = pool.get(name) ?? Promise.resolve();
|
|
2857
|
-
let release;
|
|
2858
|
-
const next = new Promise((resolve) => {
|
|
2859
|
-
release = resolve;
|
|
2860
|
-
});
|
|
2861
|
-
const chained = previous.then(() => next);
|
|
2862
|
-
pool.set(name, chained);
|
|
2863
|
-
await previous;
|
|
2864
|
-
try {
|
|
2865
|
-
return await run();
|
|
2866
|
-
} finally {
|
|
2867
|
-
release();
|
|
2868
|
-
if (pool.get(name) === chained) {
|
|
2869
|
-
pool.delete(name);
|
|
2870
|
-
}
|
|
2871
|
-
}
|
|
2872
|
-
};
|
|
2873
|
-
const createRetryBudget = (limit) => {
|
|
2874
|
-
let remaining = Math.max(0, Math.floor(limit));
|
|
2875
|
-
return {
|
|
2876
|
-
claim(requested) {
|
|
2877
|
-
const granted = Math.max(0, Math.min(requested, remaining));
|
|
2878
|
-
remaining -= granted;
|
|
2879
|
-
return granted;
|
|
2880
|
-
},
|
|
2881
|
-
get remaining() {
|
|
2882
|
-
return remaining;
|
|
2883
|
-
}
|
|
2884
|
-
};
|
|
2885
|
-
};
|
|
2886
|
-
const loadEnvFileCached = (cache, cwd, envFileSpec) => {
|
|
2887
|
-
const key = `${cwd}\0${typeof envFileSpec === "string" ? envFileSpec : String(envFileSpec)}`;
|
|
2888
|
-
const hit = cache.get(key);
|
|
2889
|
-
if (hit) {
|
|
2890
|
-
return hit;
|
|
2891
|
-
}
|
|
2892
|
-
const parsed = loadEnvFile(cwd, envFileSpec);
|
|
2893
|
-
cache.set(key, parsed);
|
|
2894
|
-
return parsed;
|
|
2895
|
-
};
|
|
2896
|
-
const createConcurrentExecutor = (deps) => {
|
|
2897
|
-
const envFileCache = /* @__PURE__ */ new Map();
|
|
2898
|
-
return async (task, execOptions) => {
|
|
2899
|
-
const {
|
|
2900
|
-
affectedFiles,
|
|
2901
|
-
currentOs,
|
|
2902
|
-
initCwd,
|
|
2903
|
-
lifeCycle,
|
|
2904
|
-
mutexPool,
|
|
2905
|
-
onOutput,
|
|
2906
|
-
onOutputReplace,
|
|
2907
|
-
retryBudget,
|
|
2908
|
-
serviceEnvByTaskId,
|
|
2909
|
-
stdinRegistry,
|
|
2910
|
-
strictEnv: workspaceStrictEnv,
|
|
2911
|
-
workspaceRoot
|
|
2912
|
-
} = deps;
|
|
2913
|
-
const visOptions = getTaskOptions(task);
|
|
2914
|
-
const resolvedCwd = resolveCwd(workspaceRoot, execOptions.cwd ?? task.projectRoot, visOptions?.runFromWorkspaceRoot === true);
|
|
2915
|
-
const rawCommand = task.overrides["command"];
|
|
2916
|
-
if (!rawCommand) {
|
|
2917
|
-
return { code: 0, terminalOutput: `No command configured for ${task.target.project}:${task.target.target}` };
|
|
2918
|
-
}
|
|
2919
|
-
const commandWithArgs = appendForwardedArgs(rawCommand, task);
|
|
2920
|
-
const commandWithAffected = buildAffectedFilesArgs(commandWithArgs, affectedFiles, visOptions?.affectedFiles);
|
|
2921
|
-
const customShell = resolveTargetShell(visOptions, currentOs);
|
|
2922
|
-
const command = customShell ? `${customShell} -c ${singleQuoteEscape(commandWithAffected)}` : commandWithAffected;
|
|
2923
|
-
const envFileVars = visOptions?.envFile ? loadEnvFileCached(envFileCache, resolvedCwd, visOptions.envFile) : void 0;
|
|
2924
|
-
const affectedFilesEnv = {};
|
|
2925
|
-
if (affectedFiles && affectedFiles.length > 0 && (visOptions?.affectedFiles === "env" || visOptions?.affectedFiles === "both")) {
|
|
2926
|
-
affectedFilesEnv[AFFECTED_FILES_ENV] = affectedFiles.join("\n");
|
|
2927
|
-
}
|
|
2928
|
-
const serviceEnv = serviceEnvByTaskId?.get(task.id);
|
|
2929
|
-
const mergedEnv = {
|
|
2930
|
-
INIT_CWD: initCwd,
|
|
2931
|
-
...envFileVars,
|
|
2932
|
-
// Externally-registered service env merges below the
|
|
2933
|
-
// task's explicit env so users can override service-
|
|
2934
|
-
// provided defaults (e.g. point a test at a staging URL
|
|
2935
|
-
// without restarting the registered service).
|
|
2936
|
-
...serviceEnv,
|
|
2937
|
-
...execOptions.env,
|
|
2938
|
-
...affectedFilesEnv
|
|
2939
|
-
};
|
|
2940
|
-
const strictEnvActive = visOptions?.strictEnv ?? workspaceStrictEnv ?? false;
|
|
2941
|
-
if (strictEnvActive) {
|
|
2942
|
-
const violation = checkStrictEnv({
|
|
2943
|
-
command: commandWithAffected,
|
|
2944
|
-
processEnv: process.env,
|
|
2945
|
-
taskEnv: mergedEnv,
|
|
2946
|
-
taskId: task.id
|
|
2947
|
-
});
|
|
2948
|
-
if (violation) {
|
|
2949
|
-
const message = formatStrictEnvError(violation);
|
|
2950
|
-
lifeCycle?.onTaskStderr?.(task, message);
|
|
2951
|
-
return { code: 1, terminalOutput: message };
|
|
2952
|
-
}
|
|
2953
|
-
}
|
|
2954
|
-
const ptyOptIn = visOptions?.pty === true;
|
|
2955
|
-
const ptyInteractive = Boolean(stdinRegistry);
|
|
2956
|
-
const isPty = ptyInteractive || ptyOptIn;
|
|
2957
|
-
if (isPty) {
|
|
2958
|
-
task.cache = false;
|
|
2959
|
-
}
|
|
2960
|
-
const output = isPty ? void 0 : new OutputRingBuffer(MAX_OUTPUT_BYTES);
|
|
2961
|
-
const termBuf = isPty ? new TerminalBuffer(MAX_OUTPUT_BYTES) : void 0;
|
|
2962
|
-
let killCurrentProcess;
|
|
2963
|
-
const onEvent = (event) => {
|
|
2964
|
-
if (event.kind === "started") {
|
|
2965
|
-
killCurrentProcess = event.kill;
|
|
2966
|
-
if (event.write && stdinRegistry) {
|
|
2967
|
-
stdinRegistry.set(task.id, { kill: event.kill, resize: event.resize, write: event.write });
|
|
2968
|
-
}
|
|
2969
|
-
}
|
|
2970
|
-
if ((event.kind === "stdout" || event.kind === "stderr") && event.text !== void 0) {
|
|
2971
|
-
if (event.kind === "stdout") {
|
|
2972
|
-
lifeCycle?.onTaskStdout?.(task, event.text);
|
|
2973
|
-
} else {
|
|
2974
|
-
lifeCycle?.onTaskStderr?.(task, event.text);
|
|
2975
|
-
}
|
|
2976
|
-
if (termBuf) {
|
|
2977
|
-
termBuf.write(event.text);
|
|
2978
|
-
if (ptyInteractive) {
|
|
2979
|
-
onOutputReplace?.(task.id, termBuf.toString());
|
|
2980
|
-
}
|
|
2981
|
-
} else {
|
|
2982
|
-
const line = `${event.text}
|
|
2983
|
-
`;
|
|
2984
|
-
output.append(line);
|
|
2985
|
-
onOutput?.(task.id, line);
|
|
2986
|
-
}
|
|
2987
|
-
}
|
|
2988
|
-
if (event.kind === "close" && stdinRegistry) {
|
|
2989
|
-
stdinRegistry.delete(task.id);
|
|
2990
|
-
}
|
|
2991
|
-
};
|
|
2992
|
-
const runOnce = async () => {
|
|
2993
|
-
const requestedRetries = visOptions?.retryCount ?? 0;
|
|
2994
|
-
const retryDelay = visOptions?.retryDelay;
|
|
2995
|
-
const retryCount = retryBudget ? retryBudget.claim(requestedRetries) : requestedRetries;
|
|
2996
|
-
const timeoutMs = typeof visOptions?.timeout === "number" && visOptions.timeout > 0 ? visOptions.timeout : 0;
|
|
2997
|
-
const killGracePeriodMs = typeof visOptions?.killGracePeriodMs === "number" && visOptions.killGracePeriodMs >= 0 ? visOptions.killGracePeriodMs : 5e3;
|
|
2998
|
-
let timedOut = false;
|
|
2999
|
-
const timeoutKill = scheduleTimeoutKill({
|
|
3000
|
-
killGracePeriodMs,
|
|
3001
|
-
onTimeout: () => {
|
|
3002
|
-
timedOut = true;
|
|
3003
|
-
},
|
|
3004
|
-
sendSignal: (signal) => {
|
|
3005
|
-
killCurrentProcess?.(signal);
|
|
3006
|
-
},
|
|
3007
|
-
timeoutMs
|
|
3008
|
-
});
|
|
3009
|
-
let result;
|
|
3010
|
-
try {
|
|
3011
|
-
result = await runConcurrently(
|
|
3012
|
-
[
|
|
3013
|
-
{
|
|
3014
|
-
command,
|
|
3015
|
-
cwd: resolvedCwd,
|
|
3016
|
-
env: mergedEnv,
|
|
3017
|
-
name: task.id,
|
|
3018
|
-
...isPty ? {
|
|
3019
|
-
ptySize: { cols: process.stdout.columns ?? 80, rows: process.stdout.rows ?? 24 },
|
|
3020
|
-
stdin: "pty"
|
|
3021
|
-
} : {}
|
|
3022
|
-
}
|
|
3023
|
-
],
|
|
3024
|
-
{
|
|
3025
|
-
killOthers: ["failure"],
|
|
3026
|
-
onEvent,
|
|
3027
|
-
...retryCount > 0 ? { restart: { delay: retryDelay ?? "exponential", tries: retryCount } } : {}
|
|
3028
|
-
}
|
|
3029
|
-
);
|
|
3030
|
-
} finally {
|
|
3031
|
-
timeoutKill.cancel();
|
|
3032
|
-
}
|
|
3033
|
-
const closeEvent = result.closeEvents[0];
|
|
3034
|
-
const buffered = termBuf ? termBuf.toString() : output.toString();
|
|
3035
|
-
if (timedOut) {
|
|
3036
|
-
return {
|
|
3037
|
-
code: 124,
|
|
3038
|
-
terminalOutput: `${buffered}
|
|
3039
|
-
[timeout] Task "${task.id}" exceeded ${timeoutMs}ms budget.
|
|
3040
|
-
`
|
|
3041
|
-
};
|
|
3042
|
-
}
|
|
3043
|
-
return {
|
|
3044
|
-
code: closeEvent?.exitCode ?? 1,
|
|
3045
|
-
terminalOutput: buffered
|
|
3046
|
-
};
|
|
3047
|
-
};
|
|
3048
|
-
return mutexPool ? withMutex(mutexPool, visOptions?.mutex, runOnce) : runOnce();
|
|
3049
|
-
};
|
|
3050
|
-
};
|
|
3051
|
-
const parseEnvConcurrency = (raw) => {
|
|
3052
|
-
if (!raw || raw.trim().length === 0) {
|
|
3053
|
-
return void 0;
|
|
3054
|
-
}
|
|
3055
|
-
const value = Number.parseFloat(raw);
|
|
3056
|
-
if (!Number.isFinite(value) || value <= 0) {
|
|
3057
|
-
return { invalid: raw };
|
|
3058
|
-
}
|
|
3059
|
-
return { value: Math.floor(value) };
|
|
3060
|
-
};
|
|
3061
|
-
const renderLastRunSummary = async (workspaceRoot, logger) => {
|
|
3062
|
-
const summary = await readLastRunSummary(workspaceRoot);
|
|
3063
|
-
if (!summary) {
|
|
3064
|
-
logger.warn("No previous run recorded yet. Run a task at least once to populate .task-runner/last-summary.json.");
|
|
3065
|
-
return;
|
|
3066
|
-
}
|
|
3067
|
-
const seconds = (summary.duration / 1e3).toFixed(2);
|
|
3068
|
-
logger.info("");
|
|
3069
|
-
logger.info(`Last run — ${summary.startTime} (${seconds}s)`);
|
|
3070
|
-
logger.info("");
|
|
3071
|
-
logger.info(` Total: ${String(summary.stats.total)}`);
|
|
3072
|
-
logger.info(` Succeeded: ${String(summary.stats.succeeded)}`);
|
|
3073
|
-
logger.info(` Cached: ${String(summary.stats.cached)}`);
|
|
3074
|
-
logger.info(` Failed: ${String(summary.stats.failed)}`);
|
|
3075
|
-
logger.info(` Skipped: ${String(summary.stats.skipped)}`);
|
|
3076
|
-
logger.info("");
|
|
3077
|
-
if (summary.stats.failed > 0) {
|
|
3078
|
-
const failedTasks = summary.tasks.filter((t) => t.exitCode !== void 0 && t.exitCode !== 0);
|
|
3079
|
-
logger.info("Failed tasks:");
|
|
3080
|
-
for (const task of failedTasks) {
|
|
3081
|
-
const durationMs = task.duration ?? 0;
|
|
3082
|
-
logger.info(` × ${task.taskId} (exit ${String(task.exitCode ?? -1)}, ${durationMs}ms)`);
|
|
3083
|
-
}
|
|
3084
|
-
logger.info("");
|
|
3085
|
-
}
|
|
3086
|
-
const slowest = [...summary.tasks].filter((t) => typeof t.duration === "number").sort((a, b) => (b.duration ?? 0) - (a.duration ?? 0)).slice(0, 5);
|
|
3087
|
-
if (slowest.length > 0) {
|
|
3088
|
-
logger.info("Slowest tasks:");
|
|
3089
|
-
for (const task of slowest) {
|
|
3090
|
-
logger.info(` ${task.taskId.padEnd(40)} ${String(task.duration ?? 0).padStart(6)}ms [${task.cacheStatus}]`);
|
|
3091
|
-
}
|
|
3092
|
-
logger.info("");
|
|
3093
|
-
}
|
|
3094
|
-
};
|
|
3095
|
-
const parseCacheMode = (value) => {
|
|
3096
|
-
if (value === void 0 || value === "") {
|
|
3097
|
-
return void 0;
|
|
3098
|
-
}
|
|
3099
|
-
if (value !== "read" && value !== "write" && value !== "readwrite") {
|
|
3100
|
-
throw new Error(`--cache-mode must be one of: read, write, readwrite (received "${value}")`);
|
|
3101
|
-
}
|
|
3102
|
-
return value;
|
|
3103
|
-
};
|
|
3104
|
-
const parseCacheBackend = (value) => {
|
|
3105
|
-
if (value === void 0 || value === "") {
|
|
3106
|
-
return void 0;
|
|
3107
|
-
}
|
|
3108
|
-
if (value !== "http" && value !== "reapi") {
|
|
3109
|
-
throw new Error(`--cache-backend must be one of: http, reapi (received "${value}")`);
|
|
3110
|
-
}
|
|
3111
|
-
return value;
|
|
3112
|
-
};
|
|
3113
|
-
const execute = async ({ argument, logger, options, runtime, visConfig, workspaceRoot: wsRoot }) => {
|
|
3114
|
-
if (!wsRoot) {
|
|
3115
|
-
throw new Error("Could not determine workspace root. Run this command inside a monorepo.");
|
|
3116
|
-
}
|
|
3117
|
-
const workspaceRoot = wsRoot;
|
|
3118
|
-
if (options.lastDetails === true) {
|
|
3119
|
-
await renderLastRunSummary(workspaceRoot, logger);
|
|
3120
|
-
return;
|
|
3121
|
-
}
|
|
3122
|
-
const invocationCwd = process.cwd();
|
|
3123
|
-
const taskConfigs = await loadVisTaskConfigsForWorkspace(workspaceRoot);
|
|
3124
|
-
const { config, packageJsons, projectOptions, workspace } = discoverWorkspace(workspaceRoot, visConfig, taskConfigs);
|
|
3125
|
-
const projectGraph = buildProjectGraph(workspaceRoot, workspace, packageJsons);
|
|
3126
|
-
let rawSelector = argument[0];
|
|
3127
|
-
if (!rawSelector) {
|
|
3128
|
-
const available = collectAvailableTargets(workspace);
|
|
3129
|
-
if (process.stdout.isTTY && process.stdin.isTTY) {
|
|
3130
|
-
const picked = await promptTargetInteractively(available);
|
|
3131
|
-
if (!picked) {
|
|
3132
|
-
logger.info("No target selected.");
|
|
3133
|
-
return;
|
|
3134
|
-
}
|
|
3135
|
-
rawSelector = picked;
|
|
3136
|
-
await appendToShellHistory(`vis run ${picked}`);
|
|
3137
|
-
} else {
|
|
3138
|
-
logger.info("Available targets:");
|
|
3139
|
-
logger.info("");
|
|
3140
|
-
logger.info(formatTargetList(available));
|
|
3141
|
-
logger.info("");
|
|
3142
|
-
logger.info("Usage: vis run <target>");
|
|
3143
|
-
return;
|
|
3144
|
-
}
|
|
3145
|
-
}
|
|
3146
|
-
if (config.constraints && !options.skipConstraints) {
|
|
3147
|
-
const violations = enforceProjectConstraints(projectGraph, config.constraints);
|
|
3148
|
-
if (violations.length > 0) {
|
|
3149
|
-
for (const v of violations) {
|
|
3150
|
-
logger.error(`[${v.rule}] ${v.message}`);
|
|
3151
|
-
}
|
|
3152
|
-
throw new Error(`${violations.length} project constraint violation(s) found. Use --skip-constraints to bypass.`);
|
|
3153
|
-
}
|
|
3154
|
-
}
|
|
3155
|
-
if (options.affected) {
|
|
3156
|
-
const argv = [rawSelector];
|
|
3157
|
-
if (options.parallel !== void 0) {
|
|
3158
|
-
argv.push(`--parallel=${String(options.parallel)}`);
|
|
3159
|
-
}
|
|
3160
|
-
if (!options.cache) {
|
|
3161
|
-
argv.push("--no-cache");
|
|
3162
|
-
}
|
|
3163
|
-
if (options.query) {
|
|
3164
|
-
argv.push(`--query=${String(options.query)}`);
|
|
3165
|
-
}
|
|
3166
|
-
await runtime.runCommand("affected", { argv });
|
|
3167
|
-
return;
|
|
3168
|
-
}
|
|
3169
|
-
const selectorResult = await resolveSelector(rawSelector, workspace, process.cwd(), workspaceRoot);
|
|
3170
|
-
const aliasMap = buildAliasMap(projectOptions);
|
|
3171
|
-
const target = resolveTargetAlias(selectorResult.target, aliasMap);
|
|
3172
|
-
if (target !== selectorResult.target) {
|
|
3173
|
-
logger.debug?.(`Resolved alias "${selectorResult.target}" → "${target}"`);
|
|
3174
|
-
}
|
|
3175
|
-
let projectNames = selectorResult.projects;
|
|
3176
|
-
const forwardedArgs = argument.slice(1).map(String);
|
|
3177
|
-
if (options.projects) {
|
|
3178
|
-
const requested = new Set(options.projects.split(",").map((p) => p.trim()));
|
|
3179
|
-
projectNames = projectNames.filter((name) => requested.has(name));
|
|
3180
|
-
if (projectNames.length === 0) {
|
|
3181
|
-
throw new Error(`No matching projects found for: ${String(options.projects)}`);
|
|
3182
|
-
}
|
|
3183
|
-
}
|
|
3184
|
-
if (options.query) {
|
|
3185
|
-
projectNames = filterProjectsByQuery(projectNames, workspace, options.query);
|
|
3186
|
-
if (projectNames.length === 0) {
|
|
3187
|
-
logger.info(`Query "${String(options.query)}" matched no projects.`);
|
|
3188
|
-
return;
|
|
3189
|
-
}
|
|
3190
|
-
}
|
|
3191
|
-
const currentOs = detectCurrentOs();
|
|
3192
|
-
const affectedFilesRaw = process.env[AFFECTED_FILES_ENV];
|
|
3193
|
-
const affectedFiles = affectedFilesRaw ? affectedFilesRaw.split("\n").filter(Boolean) : void 0;
|
|
3194
|
-
const projectsWithTarget = [];
|
|
3195
|
-
const projectTargetIndex = /* @__PURE__ */ new Map();
|
|
3196
|
-
for (const name of projectNames) {
|
|
3197
|
-
const visTargets = projectOptions.get(name);
|
|
3198
|
-
const visTarget = visTargets?.[target];
|
|
3199
|
-
if (!visTarget) {
|
|
3200
|
-
continue;
|
|
3201
|
-
}
|
|
3202
|
-
const visOptions = visTarget.options;
|
|
3203
|
-
if (visOptions?.internal) {
|
|
3204
|
-
continue;
|
|
3205
|
-
}
|
|
3206
|
-
if (!shouldRunInCI(visOptions, Boolean(isInCi))) {
|
|
3207
|
-
logger.debug?.(`Skipping ${name}:${target} — runInCI filter`);
|
|
3208
|
-
continue;
|
|
3209
|
-
}
|
|
3210
|
-
projectsWithTarget.push(name);
|
|
3211
|
-
projectTargetIndex.set(name, visTarget);
|
|
3212
|
-
}
|
|
3213
|
-
if (projectsWithTarget.length === 0) {
|
|
3214
|
-
const available = collectAvailableTargets(workspace);
|
|
3215
|
-
const exactMatchProjects = Object.entries(workspace.projects).filter(([, proj]) => proj.targets?.[target] !== void 0).map(([name]) => name);
|
|
3216
|
-
logger.error(`No projects have the "${target}" target.`);
|
|
3217
|
-
if (exactMatchProjects.length > 0) {
|
|
3218
|
-
logger.info("");
|
|
3219
|
-
logger.info(`Target "${target}" exists in these projects but was filtered out:`);
|
|
3220
|
-
for (const name of exactMatchProjects.slice(0, 5)) {
|
|
3221
|
-
logger.info(` - ${name}`);
|
|
3222
|
-
}
|
|
3223
|
-
if (exactMatchProjects.length > 5) {
|
|
3224
|
-
logger.info(` …and ${exactMatchProjects.length - 5} more`);
|
|
3225
|
-
}
|
|
3226
|
-
} else {
|
|
3227
|
-
const suggestions = suggestTargets(target, available, 3);
|
|
3228
|
-
if (suggestions.length > 0) {
|
|
3229
|
-
logger.info("");
|
|
3230
|
-
logger.info(
|
|
3231
|
-
suggestions.length === 1 ? `Did you mean "${suggestions[0]}"?` : `Did you mean one of: ${suggestions.map((s) => `"${s}"`).join(", ")}?`
|
|
3232
|
-
);
|
|
3233
|
-
}
|
|
3234
|
-
logger.info("");
|
|
3235
|
-
logger.info("Run `vis run` without arguments to see all available targets.");
|
|
3236
|
-
}
|
|
3237
|
-
return;
|
|
3238
|
-
}
|
|
3239
|
-
const ptyFlag = options.pty === true;
|
|
3240
|
-
let initialTasks = projectsWithTarget.map((projectName) => {
|
|
3241
|
-
const project = workspace.projects[projectName];
|
|
3242
|
-
const visTarget = projectTargetIndex.get(projectName);
|
|
3243
|
-
const taskTarget = { project: projectName, target };
|
|
3244
|
-
const taskId = `${projectName}:${target}`;
|
|
3245
|
-
const mergedOptions = ptyFlag ? { ...visTarget.options, pty: visTarget.options?.pty ?? true } : visTarget.options;
|
|
3246
|
-
return {
|
|
3247
|
-
cache: visTarget.cache,
|
|
3248
|
-
id: taskId,
|
|
3249
|
-
outputs: visTarget.outputs ?? [],
|
|
3250
|
-
overrides: {
|
|
3251
|
-
command: visTarget.command,
|
|
3252
|
-
...forwardedArgs.length > 0 ? { [FORWARDED_ARGS_KEY]: forwardedArgs } : {},
|
|
3253
|
-
...mergedOptions ? { visOptions: mergedOptions } : {}
|
|
3254
|
-
},
|
|
3255
|
-
parallelism: visTarget.parallelism,
|
|
3256
|
-
projectRoot: project?.root,
|
|
3257
|
-
target: taskTarget
|
|
3258
|
-
};
|
|
3259
|
-
});
|
|
3260
|
-
const persistentTasks = [];
|
|
3261
|
-
const regularTasks = [];
|
|
3262
|
-
for (const task of initialTasks) {
|
|
3263
|
-
const opts = getTaskOptions(task);
|
|
3264
|
-
if (opts?.persistent) {
|
|
3265
|
-
task.cache = false;
|
|
3266
|
-
persistentTasks.push(task);
|
|
3267
|
-
} else {
|
|
3268
|
-
regularTasks.push(task);
|
|
3269
|
-
}
|
|
3270
|
-
}
|
|
3271
|
-
initialTasks = regularTasks;
|
|
3272
|
-
const partition = parsePartition(options.partition);
|
|
3273
|
-
if (partition) {
|
|
3274
|
-
initialTasks = TaskScheduler.partitionTasks(initialTasks, partition);
|
|
3275
|
-
logger.info(`Partition ${partition.index}/${partition.total}: running ${initialTasks.length} task(s)`);
|
|
3276
|
-
if (initialTasks.length === 0) {
|
|
3277
|
-
logger.info("No tasks assigned to this partition.");
|
|
3278
|
-
return;
|
|
3279
|
-
}
|
|
3280
|
-
}
|
|
3281
|
-
let taskGraph = createTaskGraph(initialTasks, {
|
|
3282
|
-
projectGraph,
|
|
3283
|
-
targetDefaults: config.targetDefaults,
|
|
3284
|
-
workspace
|
|
3285
|
-
});
|
|
3286
|
-
for (const [taskId, task] of Object.entries(taskGraph.tasks)) {
|
|
3287
|
-
if (task.overrides["visOptions"] !== void 0) {
|
|
3288
|
-
continue;
|
|
3289
|
-
}
|
|
3290
|
-
const projectName = task.target.project;
|
|
3291
|
-
const targetName = task.target.target;
|
|
3292
|
-
const visTarget = projectOptions.get(projectName)?.[targetName];
|
|
3293
|
-
if (visTarget?.options) {
|
|
3294
|
-
task.overrides = { ...task.overrides, visOptions: visTarget.options };
|
|
3295
|
-
taskGraph.tasks[taskId] = task;
|
|
3296
|
-
}
|
|
3297
|
-
}
|
|
3298
|
-
await runToolchainPreflight(
|
|
3299
|
-
workspaceRoot,
|
|
3300
|
-
config.toolchain,
|
|
3301
|
-
{
|
|
3302
|
-
error: (message) => {
|
|
3303
|
-
logger.error(message);
|
|
3304
|
-
},
|
|
3305
|
-
info: (message) => {
|
|
3306
|
-
logger.info(message);
|
|
3307
|
-
},
|
|
3308
|
-
warn: (message) => {
|
|
3309
|
-
logger.warn(message);
|
|
3310
|
-
}
|
|
3311
|
-
},
|
|
3312
|
-
Boolean(options.skipToolchain)
|
|
3313
|
-
);
|
|
3314
|
-
const preflightEnabled = options.preflight !== false && config.preflight?.lockfile !== false;
|
|
3315
|
-
const lockfilePreflight = runLockfilePreflight(
|
|
3316
|
-
workspaceRoot,
|
|
3317
|
-
isInCi,
|
|
3318
|
-
{
|
|
3319
|
-
warn: (message) => {
|
|
3320
|
-
logger.warn(message);
|
|
3321
|
-
}
|
|
3322
|
-
},
|
|
3323
|
-
{ skip: !preflightEnabled }
|
|
3324
|
-
);
|
|
3325
|
-
if (!lockfilePreflight.shouldContinue) {
|
|
3326
|
-
throw new Error(`${lockfilePreflight.formattedMessage ?? "preflight: lockfile drift detected"} (pass --no-preflight to bypass)`);
|
|
3327
|
-
}
|
|
3328
|
-
const registeredEntries = await readAllEntries(workspaceRoot);
|
|
3329
|
-
const serviceResult = await applyServiceRegistry({
|
|
3330
|
-
initialTasks,
|
|
3331
|
-
probe: options.dryRun ? void 0 : async (entry) => {
|
|
3332
|
-
try {
|
|
3333
|
-
await runReadiness(entry.config, { timeoutMs: SERVICE_PROBE_TIMEOUT_MS });
|
|
3334
|
-
return true;
|
|
3335
|
-
} catch {
|
|
3336
|
-
return false;
|
|
3337
|
-
}
|
|
3338
|
-
},
|
|
3339
|
-
registeredEntries,
|
|
3340
|
-
taskGraph,
|
|
3341
|
-
visVersion: process.env["VIS_VERSION"] ?? "0.0.0"
|
|
3342
|
-
});
|
|
3343
|
-
if (serviceResult.diagnostics.length > 0) {
|
|
3344
|
-
for (const diagnostic of serviceResult.diagnostics) {
|
|
3345
|
-
logger.error(diagnostic.message);
|
|
3346
|
-
}
|
|
3347
|
-
throw new Error(`${serviceResult.diagnostics.length} service dependency error(s) — start the missing services or invoke them directly.`);
|
|
3348
|
-
}
|
|
3349
|
-
initialTasks = serviceResult.initialTasks;
|
|
3350
|
-
taskGraph = serviceResult.taskGraph;
|
|
3351
|
-
const { serviceEnvByTaskId } = serviceResult;
|
|
3352
|
-
if (serviceResult.satisfiedServices.length > 0) {
|
|
3353
|
-
const names = serviceResult.satisfiedServices.map((s) => s.id).join(", ");
|
|
3354
|
-
logger.debug?.(`Auto-attached to running services: ${names}`);
|
|
3355
|
-
}
|
|
3356
|
-
if (options.dryRun) {
|
|
3357
|
-
const taskCount = Object.keys(taskGraph.tasks).length;
|
|
3358
|
-
const rootCount = taskGraph.roots.length;
|
|
3359
|
-
logger.info(`Execution plan (${String(taskCount)} task(s), ${String(rootCount)} root(s)):`);
|
|
3360
|
-
logger.info("");
|
|
3361
|
-
const visited = /* @__PURE__ */ new Set();
|
|
3362
|
-
const walkPlan = (id, depth) => {
|
|
3363
|
-
if (visited.has(id)) {
|
|
3364
|
-
return;
|
|
3365
|
-
}
|
|
3366
|
-
visited.add(id);
|
|
3367
|
-
for (const dep of taskGraph.dependencies[id] ?? []) {
|
|
3368
|
-
walkPlan(dep, depth + 1);
|
|
3369
|
-
}
|
|
3370
|
-
const task = taskGraph.tasks[id];
|
|
3371
|
-
const indent = " ".repeat(depth + 1);
|
|
3372
|
-
logger.info(`${indent}${id}${task?.cache === false ? " (no-cache)" : ""}`);
|
|
3373
|
-
};
|
|
3374
|
-
for (const root of taskGraph.roots) {
|
|
3375
|
-
walkPlan(root, 0);
|
|
3376
|
-
}
|
|
3377
|
-
if (persistentTasks.length > 0) {
|
|
3378
|
-
logger.info("");
|
|
3379
|
-
logger.info(` + ${String(persistentTasks.length)} persistent task(s) (run after graph completes)`);
|
|
3380
|
-
}
|
|
3381
|
-
logger.info("");
|
|
3382
|
-
return;
|
|
3383
|
-
}
|
|
3384
|
-
const startTime = Date.now();
|
|
3385
|
-
const hooks = createVisHooks();
|
|
3386
|
-
const onHookError = (hookName, error) => {
|
|
3387
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
3388
|
-
logger.warn(`Plugin error in ${hookName}: ${message}`);
|
|
3389
|
-
};
|
|
3390
|
-
await registerPlugins(hooks, config.plugins);
|
|
3391
|
-
const profilePath = typeof options.profile === "string" ? options.profile : void 0;
|
|
3392
|
-
const maybeWriteProfile = async (results) => {
|
|
3393
|
-
if (!profilePath) {
|
|
3394
|
-
return;
|
|
3395
|
-
}
|
|
3396
|
-
const summary = generateRunSummary(results, taskGraph, startTime);
|
|
3397
|
-
const resolvedProfilePath = profilePath.startsWith("/") ? profilePath : `${workspaceRoot}/${profilePath}`;
|
|
3398
|
-
await writeChromeTrace(summary, resolvedProfilePath);
|
|
3399
|
-
logger.info(`Profile written to ${profilePath}`);
|
|
3400
|
-
};
|
|
3401
|
-
const configTaskRunnerOptions = config.taskRunnerOptions ?? {};
|
|
3402
|
-
const baseCacheDirectory = resolveSharedCacheDirectory(workspaceRoot, options.cacheDir, configTaskRunnerOptions.cacheDirectory, config.sharedWorktreeCache);
|
|
3403
|
-
const resolvedCacheDirectory = applyBranchScope(baseCacheDirectory, workspaceRoot, config.branchScopedCache);
|
|
3404
|
-
const parallelEnvParsed = parseEnvConcurrency(process.env["VIS_RUN_CONCURRENCY_LIMIT"]);
|
|
3405
|
-
if (parallelEnvParsed && "invalid" in parallelEnvParsed) {
|
|
3406
|
-
logger.warn(`VIS_RUN_CONCURRENCY_LIMIT=${parallelEnvParsed.invalid} is not a positive number; falling back to default concurrency.`);
|
|
3407
|
-
}
|
|
3408
|
-
const parallelFromEnv = parallelEnvParsed && "value" in parallelEnvParsed ? parallelEnvParsed.value : void 0;
|
|
3409
|
-
const resolvedParallel = options.parallel ?? parallelFromEnv ?? 3;
|
|
3410
|
-
const resolvedStrictEnv = options.strictEnv ?? config.strictEnv ?? false;
|
|
3411
|
-
const runnerOptions = {
|
|
3412
|
-
dryRun: options.dryRun ?? false,
|
|
3413
|
-
parallel: resolvedParallel,
|
|
3414
|
-
skipNxCache: !options.cache,
|
|
3415
|
-
summarize: options.summarize ?? false,
|
|
3416
|
-
...configTaskRunnerOptions,
|
|
3417
|
-
// Applied after the config spread so the user's `--cache-dir` flag
|
|
3418
|
-
// wins over a config value and relative paths are normalized
|
|
3419
|
-
// against `workspaceRoot` via `resolveCacheDirectory()`.
|
|
3420
|
-
cacheDirectory: resolvedCacheDirectory
|
|
3421
|
-
};
|
|
3422
|
-
if (configTaskRunnerOptions.remoteCache) {
|
|
3423
|
-
const cliMode = parseCacheMode(options.cacheMode);
|
|
3424
|
-
const cliBackend = parseCacheBackend(options.cacheBackend);
|
|
3425
|
-
if (cliMode || cliBackend) {
|
|
3426
|
-
runnerOptions.remoteCache = {
|
|
3427
|
-
...configTaskRunnerOptions.remoteCache,
|
|
3428
|
-
...cliMode ? { mode: cliMode } : {},
|
|
3429
|
-
...cliBackend ? { backend: cliBackend } : {}
|
|
3430
|
-
};
|
|
3431
|
-
}
|
|
3432
|
-
} else if (options.cacheMode || options.cacheBackend) {
|
|
3433
|
-
logger.warn("[vis run] --cache-mode and --cache-backend require a `remoteCache` block in vis.config.ts; ignoring.");
|
|
3434
|
-
}
|
|
3435
|
-
const isTTY = process.stdout.isTTY && !isInCi;
|
|
3436
|
-
const autoExitConfig = config.tui?.autoExit ?? false;
|
|
3437
|
-
const lifecycleOptions = {
|
|
3438
|
-
args: { parallel: runnerOptions.parallel, targets: [target] },
|
|
3439
|
-
autoExit: autoExitConfig,
|
|
3440
|
-
projectNames: projectsWithTarget,
|
|
3441
|
-
tasks: initialTasks
|
|
3442
|
-
};
|
|
3443
|
-
const retryBudgetLimit = typeof options.retryBudget === "number" ? options.retryBudget : void 0;
|
|
3444
|
-
const sharedRetryBudget = retryBudgetLimit === void 0 ? void 0 : createRetryBudget(retryBudgetLimit);
|
|
3445
|
-
const hookLifeCycle = new HookableLifeCycle(hooks, onHookError);
|
|
3446
|
-
const failureLogLifeCycle = new FailureLogLifeCycle(workspaceRoot);
|
|
3447
|
-
const outputStyle = parseOutputStyle(typeof options.outputStyle === "string" ? options.outputStyle.toLowerCase() : void 0);
|
|
3448
|
-
await hooks.callHook("run:before", { tasks: initialTasks, workspaceRoot });
|
|
3449
|
-
if (isTTY) {
|
|
3450
|
-
const stdinRegistry = /* @__PURE__ */ new Map();
|
|
3451
|
-
const dynamic = createDynamicOutputRenderer({ ...lifecycleOptions, outputStyle, stdinRegistry });
|
|
3452
|
-
const { lifeCycle: uiLifeCycle, store } = dynamic;
|
|
3453
|
-
const lifeCycle = new CompositeLifeCycle([uiLifeCycle, hookLifeCycle, failureLogLifeCycle]);
|
|
3454
|
-
const mutexPool = /* @__PURE__ */ new Map();
|
|
3455
|
-
const taskExecutor = createConcurrentExecutor({
|
|
3456
|
-
affectedFiles,
|
|
3457
|
-
currentOs,
|
|
3458
|
-
initCwd: invocationCwd,
|
|
3459
|
-
lifeCycle,
|
|
3460
|
-
mutexPool,
|
|
3461
|
-
onOutput: (taskId, text) => {
|
|
3462
|
-
store.addOutput(taskId, text);
|
|
3463
|
-
},
|
|
3464
|
-
onOutputReplace: (taskId, fullContent) => {
|
|
3465
|
-
store.setOutput(taskId, fullContent);
|
|
3466
|
-
},
|
|
3467
|
-
retryBudget: sharedRetryBudget,
|
|
3468
|
-
serviceEnvByTaskId,
|
|
3469
|
-
stdinRegistry,
|
|
3470
|
-
strictEnv: resolvedStrictEnv,
|
|
3471
|
-
workspaceRoot
|
|
3472
|
-
});
|
|
3473
|
-
let loopAction = "rerun";
|
|
3474
|
-
let retryTaskId = null;
|
|
3475
|
-
let lastResults = /* @__PURE__ */ new Map();
|
|
3476
|
-
while (loopAction !== "quit") {
|
|
3477
|
-
if (loopAction === "rerun") {
|
|
3478
|
-
lastResults = await defaultTaskRunner(initialTasks, runnerOptions, {
|
|
3479
|
-
lifeCycle,
|
|
3480
|
-
projectGraph,
|
|
3481
|
-
taskExecutor,
|
|
3482
|
-
taskGraph,
|
|
3483
|
-
workspaceRoot
|
|
3484
|
-
});
|
|
3485
|
-
} else if (loopAction === "retry" && retryTaskId) {
|
|
3486
|
-
const task = initialTasks.find((t) => t.id === retryTaskId);
|
|
3487
|
-
const command = task?.overrides["command"];
|
|
3488
|
-
if (task && command) {
|
|
3489
|
-
const taskCwd = task.projectRoot ?? workspaceRoot;
|
|
3490
|
-
const resolvedCwd = taskCwd.startsWith("/") ? taskCwd : `${workspaceRoot}/${taskCwd}`;
|
|
3491
|
-
lifeCycle.startTasks?.([task]);
|
|
3492
|
-
const retryTermBuf = new TerminalBuffer(MAX_OUTPUT_BYTES);
|
|
3493
|
-
const retryResult = await runConcurrently(
|
|
3494
|
-
[
|
|
3495
|
-
{
|
|
3496
|
-
command,
|
|
3497
|
-
cwd: resolvedCwd,
|
|
3498
|
-
name: task.id,
|
|
3499
|
-
ptySize: { cols: process.stdout.columns ?? 80, rows: process.stdout.rows ?? 24 },
|
|
3500
|
-
stdin: "pty"
|
|
3501
|
-
}
|
|
3502
|
-
],
|
|
3503
|
-
{
|
|
3504
|
-
onEvent: (event) => {
|
|
3505
|
-
if (event.kind === "started" && event.write) {
|
|
3506
|
-
stdinRegistry.set(task.id, { kill: event.kill, resize: event.resize, write: event.write });
|
|
3507
|
-
}
|
|
3508
|
-
if ((event.kind === "stdout" || event.kind === "stderr") && event.text) {
|
|
3509
|
-
retryTermBuf.write(event.text);
|
|
3510
|
-
store.setOutput(task.id, retryTermBuf.toString());
|
|
3511
|
-
}
|
|
3512
|
-
if (event.kind === "close") {
|
|
3513
|
-
stdinRegistry.delete(task.id);
|
|
3514
|
-
}
|
|
3515
|
-
}
|
|
3516
|
-
}
|
|
3517
|
-
);
|
|
3518
|
-
const closeEvent = retryResult.closeEvents[0];
|
|
3519
|
-
lifeCycle.endTasks?.([
|
|
3520
|
-
{
|
|
3521
|
-
code: closeEvent?.exitCode ?? 1,
|
|
3522
|
-
status: closeEvent?.exitCode === 0 ? "success" : "failure",
|
|
3523
|
-
task,
|
|
3524
|
-
terminalOutput: store.getSnapshot().outputs.get(task.id)
|
|
3525
|
-
}
|
|
3526
|
-
]);
|
|
3527
|
-
} else if (task) {
|
|
3528
|
-
lifeCycle.endTasks?.([
|
|
3529
|
-
{
|
|
3530
|
-
code: 1,
|
|
3531
|
-
status: "failure",
|
|
3532
|
-
task,
|
|
3533
|
-
terminalOutput: `No command configured for ${task.id}`
|
|
3534
|
-
}
|
|
3535
|
-
]);
|
|
3536
|
-
}
|
|
3537
|
-
retryTaskId = null;
|
|
3538
|
-
store.markDone();
|
|
3539
|
-
}
|
|
3540
|
-
loopAction = await new Promise((resolve) => {
|
|
3541
|
-
const unsubscribe = store.subscribe(() => {
|
|
3542
|
-
const s = store.getSnapshot();
|
|
3543
|
-
if (s.rerunRequested) {
|
|
3544
|
-
store.acknowledgeRerun();
|
|
3545
|
-
unsubscribe();
|
|
3546
|
-
resolve("rerun");
|
|
3547
|
-
}
|
|
3548
|
-
if (s.retryTaskId) {
|
|
3549
|
-
retryTaskId = store.acknowledgeRetry();
|
|
3550
|
-
unsubscribe();
|
|
3551
|
-
resolve("retry");
|
|
3552
|
-
}
|
|
3553
|
-
});
|
|
3554
|
-
dynamic.renderIsDone.then(
|
|
3555
|
-
() => {
|
|
3556
|
-
unsubscribe();
|
|
3557
|
-
resolve("quit");
|
|
3558
|
-
},
|
|
3559
|
-
() => {
|
|
3560
|
-
unsubscribe();
|
|
3561
|
-
resolve("quit");
|
|
3562
|
-
}
|
|
3563
|
-
);
|
|
3564
|
-
});
|
|
3565
|
-
}
|
|
3566
|
-
await dynamic.renderIsDone;
|
|
3567
|
-
await hooks.callHook("run:after", lastResults);
|
|
3568
|
-
await maybeWriteProfile(lastResults);
|
|
3569
|
-
if (persistentTasks.length > 0 && !options.failFast) {
|
|
3570
|
-
await runPersistentTasks(persistentTasks, workspaceRoot, affectedFiles, invocationCwd);
|
|
3571
|
-
}
|
|
3572
|
-
} else {
|
|
3573
|
-
const mutexPool = /* @__PURE__ */ new Map();
|
|
3574
|
-
const logModeOption = typeof options.log === "string" ? options.log.toLowerCase() : "";
|
|
3575
|
-
const logMode = logModeOption === "labeled" || logModeOption === "grouped" || logModeOption === "interleaved" ? logModeOption : void 0;
|
|
3576
|
-
const logReporter = logMode ? createLogReporter(logMode) : void 0;
|
|
3577
|
-
const lifeCycle = new CompositeLifeCycle([
|
|
3578
|
-
new StaticOutputLifeCycle({ ...lifecycleOptions, logReporter, outputStyle }),
|
|
3579
|
-
hookLifeCycle,
|
|
3580
|
-
failureLogLifeCycle
|
|
3581
|
-
]);
|
|
3582
|
-
const taskExecutor = createConcurrentExecutor({
|
|
3583
|
-
affectedFiles,
|
|
3584
|
-
currentOs,
|
|
3585
|
-
initCwd: invocationCwd,
|
|
3586
|
-
lifeCycle,
|
|
3587
|
-
mutexPool,
|
|
3588
|
-
retryBudget: sharedRetryBudget,
|
|
3589
|
-
serviceEnvByTaskId,
|
|
3590
|
-
strictEnv: resolvedStrictEnv,
|
|
3591
|
-
workspaceRoot
|
|
3592
|
-
});
|
|
3593
|
-
const runOnce = async () => {
|
|
3594
|
-
const runStart = Date.now();
|
|
3595
|
-
const results = await defaultTaskRunner(initialTasks, runnerOptions, {
|
|
3596
|
-
lifeCycle,
|
|
3597
|
-
projectGraph,
|
|
3598
|
-
taskExecutor,
|
|
3599
|
-
taskGraph,
|
|
3600
|
-
workspaceRoot
|
|
3601
|
-
});
|
|
3602
|
-
const durationMs = Date.now() - runStart;
|
|
3603
|
-
if (options.summarize) {
|
|
3604
|
-
const summary = generateRunSummary(results, taskGraph, startTime);
|
|
3605
|
-
await writeRunSummary(summary, workspaceRoot);
|
|
3606
|
-
}
|
|
3607
|
-
let hasFailure2 = false;
|
|
3608
|
-
for (const [, result] of results) {
|
|
3609
|
-
if (result.status === "failure") {
|
|
3610
|
-
hasFailure2 = true;
|
|
3611
|
-
}
|
|
3612
|
-
}
|
|
3613
|
-
const timingLine = formatTimingSummary(results, durationMs);
|
|
3614
|
-
const runHistory = loadRunSummaries(workspaceRoot);
|
|
3615
|
-
const durationComparison = compareDuration(workspaceRoot, durationMs, runHistory);
|
|
3616
|
-
logger.info("");
|
|
3617
|
-
logger.info(` ${timingLine}${durationComparison ? ` ${durationComparison}` : ""}`);
|
|
3618
|
-
return { hasFailure: hasFailure2, results, runHistory };
|
|
3619
|
-
};
|
|
3620
|
-
const firstRun = await runOnce();
|
|
3621
|
-
await hooks.callHook("run:after", firstRun.results);
|
|
3622
|
-
await maybeWriteProfile(firstRun.results);
|
|
3623
|
-
const { hasFailure } = firstRun;
|
|
3624
|
-
if (options.watch) {
|
|
3625
|
-
const absoluteRoots = projectsWithTarget.map((name) => {
|
|
3626
|
-
const project = workspace.projects[name];
|
|
3627
|
-
const root = project?.root;
|
|
3628
|
-
if (!root) {
|
|
3629
|
-
return void 0;
|
|
3630
|
-
}
|
|
3631
|
-
return root.startsWith("/") ? root : `${workspaceRoot}/${root}`;
|
|
3632
|
-
}).filter((p) => p !== void 0);
|
|
3633
|
-
const baseTasks = initialTasks;
|
|
3634
|
-
let projectFilter;
|
|
3635
|
-
const applyFilter = (filter) => {
|
|
3636
|
-
const result = applyProjectFilter(baseTasks, filter);
|
|
3637
|
-
projectFilter = result.filter;
|
|
3638
|
-
initialTasks = result.tasks;
|
|
3639
|
-
return result.tasks.length;
|
|
3640
|
-
};
|
|
3641
|
-
let running = false;
|
|
3642
|
-
let lastResults = firstRun.results;
|
|
3643
|
-
const buildHandle = () => {
|
|
3644
|
-
const targets = collectTrackedWatchTargets(lastResults, workspaceRoot);
|
|
3645
|
-
if (targets.directories.length > 0 && targets.files.size > 0) {
|
|
3646
|
-
const filter = createTrackedFileFilter(targets.files, workspaceRoot, targets.directories);
|
|
3647
|
-
return {
|
|
3648
|
-
handle: startWatcher({
|
|
3649
|
-
filter,
|
|
3650
|
-
onChange: onChangeHandler,
|
|
3651
|
-
paths: targets.directories
|
|
3652
|
-
}),
|
|
3653
|
-
mode: "tracked"
|
|
3654
|
-
};
|
|
3655
|
-
}
|
|
3656
|
-
return {
|
|
3657
|
-
handle: startWatcher({ onChange: onChangeHandler, paths: absoluteRoots }),
|
|
3658
|
-
mode: "roots"
|
|
3659
|
-
};
|
|
3660
|
-
};
|
|
3661
|
-
let currentHandle;
|
|
3662
|
-
const onChangeHandler = async (paths) => {
|
|
3663
|
-
if (running) {
|
|
3664
|
-
return;
|
|
3665
|
-
}
|
|
3666
|
-
running = true;
|
|
3667
|
-
try {
|
|
3668
|
-
logger.info(`Change detected in ${paths.length} file(s), rerunning…`);
|
|
3669
|
-
const nextRun = await runOnce();
|
|
3670
|
-
lastResults = nextRun.results;
|
|
3671
|
-
currentHandle?.close();
|
|
3672
|
-
const rebuilt = buildHandle();
|
|
3673
|
-
currentHandle = rebuilt.handle;
|
|
3674
|
-
} finally {
|
|
3675
|
-
running = false;
|
|
3676
|
-
}
|
|
3677
|
-
};
|
|
3678
|
-
const initial = buildHandle();
|
|
3679
|
-
currentHandle = initial.handle;
|
|
3680
|
-
const scope = initial.mode === "tracked" ? `Watching ${String(collectTrackedWatchTargets(lastResults, workspaceRoot).files.size)} tracked file(s)` : `Watching ${String(absoluteRoots.length)} project root(s)`;
|
|
3681
|
-
logger.info(`${scope} — edit a file to rerun, press h for keybinds, q to quit.`);
|
|
3682
|
-
const triggerRerun = async () => {
|
|
3683
|
-
if (running) {
|
|
3684
|
-
return;
|
|
3685
|
-
}
|
|
3686
|
-
running = true;
|
|
3687
|
-
try {
|
|
3688
|
-
if (initialTasks.length === 0) {
|
|
3689
|
-
logger.info("No tasks match the active filter — press a to clear it.");
|
|
3690
|
-
return;
|
|
3691
|
-
}
|
|
3692
|
-
const nextRun = await runOnce();
|
|
3693
|
-
lastResults = nextRun.results;
|
|
3694
|
-
currentHandle?.close();
|
|
3695
|
-
const rebuilt = buildHandle();
|
|
3696
|
-
currentHandle = rebuilt.handle;
|
|
3697
|
-
} finally {
|
|
3698
|
-
running = false;
|
|
3699
|
-
}
|
|
3700
|
-
};
|
|
3701
|
-
await new Promise((resolveExit) => {
|
|
3702
|
-
let exited = false;
|
|
3703
|
-
let keybinds;
|
|
3704
|
-
const onSigint = () => {
|
|
3705
|
-
exit();
|
|
3706
|
-
};
|
|
3707
|
-
const exit = () => {
|
|
3708
|
-
if (exited) {
|
|
3709
|
-
return;
|
|
3710
|
-
}
|
|
3711
|
-
exited = true;
|
|
3712
|
-
keybinds?.close();
|
|
3713
|
-
process.off("SIGINT", onSigint);
|
|
3714
|
-
currentHandle?.close();
|
|
3715
|
-
resolveExit();
|
|
3716
|
-
};
|
|
3717
|
-
process.on("SIGINT", onSigint);
|
|
3718
|
-
keybinds = installKeybinds({
|
|
3719
|
-
handlers: {
|
|
3720
|
-
onClearFilter: async () => {
|
|
3721
|
-
const total = applyFilter(void 0);
|
|
3722
|
-
logger.info(`Filter cleared — running ${String(total)} task(s).`);
|
|
3723
|
-
await triggerRerun();
|
|
3724
|
-
},
|
|
3725
|
-
onFilter: async (pattern) => {
|
|
3726
|
-
const previousFilter = projectFilter;
|
|
3727
|
-
const matching = applyFilter(pattern);
|
|
3728
|
-
if (matching === 0) {
|
|
3729
|
-
logger.info(`Filter "${pattern}" matched no projects — keeping previous filter.`);
|
|
3730
|
-
applyFilter(previousFilter);
|
|
3731
|
-
return;
|
|
3732
|
-
}
|
|
3733
|
-
logger.info(`Filter "${pattern}" matched ${String(matching)} task(s).`);
|
|
3734
|
-
await triggerRerun();
|
|
3735
|
-
},
|
|
3736
|
-
onHelp: () => {
|
|
3737
|
-
writeHelp(process.stdout);
|
|
3738
|
-
},
|
|
3739
|
-
onQuit: () => {
|
|
3740
|
-
exit();
|
|
3741
|
-
},
|
|
3742
|
-
onRerun: triggerRerun
|
|
3743
|
-
}
|
|
3744
|
-
});
|
|
3745
|
-
});
|
|
3746
|
-
return;
|
|
3747
|
-
}
|
|
3748
|
-
if (hasFailure) {
|
|
3749
|
-
if (options.flaky !== false) {
|
|
3750
|
-
const flakyStats = analyzeFlakiness(workspaceRoot, { minRuns: 2 }, firstRun.runHistory);
|
|
3751
|
-
if (flakyStats.length > 0) {
|
|
3752
|
-
logger.info("");
|
|
3753
|
-
logger.info("Flaky tasks (based on historical runs):");
|
|
3754
|
-
logger.info("");
|
|
3755
|
-
for (const line of formatFlakinessTable(flakyStats)) {
|
|
3756
|
-
logger.info(` ${line}`);
|
|
3757
|
-
}
|
|
3758
|
-
logger.info("");
|
|
3759
|
-
}
|
|
3760
|
-
}
|
|
3761
|
-
throw new Error("Some tasks failed.");
|
|
3762
|
-
}
|
|
3763
|
-
if (persistentTasks.length > 0 && !options.failFast) {
|
|
3764
|
-
await runPersistentTasks(persistentTasks, workspaceRoot, affectedFiles, invocationCwd);
|
|
3765
|
-
}
|
|
3766
|
-
}
|
|
3767
|
-
};
|
|
3768
|
-
|
|
3769
|
-
export { createRetryBudget, execute as default };
|
|
1
|
+
var Ve=Object.defineProperty;var y=(n,e)=>Ve(n,"name",{value:e,configurable:!0});import{createRequire as He}from"node:module";import{Box as c,Text as r,TextInput as he,ScrollBar as Oe,ScrollView as Ye,useApp as Ke,useWindowSize as Qe,useInput as H,Dialog as Ze,Tabs as et,Tab as tt,render as rt}from"@visulima/tui";import{an as ot,A as nt,s as it}from"./bin.js";import ct,{useMemo as q,useSyncExternalStore as st,useState as C,useRef as Z,useEffect as le,useCallback as lt}from"react";import{isAccessibleSync as dt,readFileSync as ut,ensureDirSync as ht}from"@visulima/fs";import{stripJsonComments as mt}from"@visulima/fs/utils";import{join as de,dirname as gt}from"@visulima/path";import{jsxs as i,jsx as t}from"react/jsx-runtime";const Je=He(import.meta.url),V=typeof globalThis<"u"&&typeof globalThis.process<"u"?globalThis.process:process,qe=y(n=>{if(typeof V<"u"&&V.versions&&V.versions.node){const[e,s]=V.versions.node.split(".").map(Number);if(e>22||e===22&&s>=3||e===20&&s>=16)return V.getBuiltinModule(n)}return Je(n)},"__cjs_getBuiltinModule"),{writeFileSync:at}=qe("node:fs"),z=[{config:{customizations:{vscode:{extensions:["dbaeumer.vscode-eslint","esbenp.prettier-vscode"]}},features:{"ghcr.io/devcontainers/features/git:1":{},"ghcr.io/devcontainers/features/github-cli:1":{}},forwardPorts:[3e3],image:"mcr.microsoft.com/devcontainers/javascript-node:22",name:"Node.js",postCreateCommand:"npm install"},description:"Node.js 22 with Git and GitHub CLI",id:"node",name:"Node.js"},{config:{customizations:{vscode:{extensions:["dbaeumer.vscode-eslint","esbenp.prettier-vscode"]}},features:{"ghcr.io/devcontainers/features/git:1":{},"ghcr.io/devcontainers/features/github-cli:1":{}},forwardPorts:[3e3],image:"mcr.microsoft.com/devcontainers/javascript-node:22",mounts:[{source:"${localWorkspaceFolderBasename}-node_modules",target:"${containerWorkspaceFolder}/node_modules",type:"volume"},{source:"${localWorkspaceFolderBasename}-pnpm-store",target:"/home/node/.local/share/pnpm/store",type:"volume"}],name:"Node.js + pnpm Monorepo",postCreateCommand:"corepack enable && pnpm install",remoteUser:"node",workspaceFolder:"/workspaces/${localWorkspaceFolderBasename}"},description:"Node.js 22 with pnpm, corepack, and optimized volume mounts",id:"node-pnpm",name:"Node.js + pnpm"},{config:{customizations:{vscode:{extensions:["dbaeumer.vscode-eslint","esbenp.prettier-vscode","ms-azuretools.vscode-docker"]}},dockerComposeFile:"docker-compose.yml",forwardPorts:[3e3,5432],name:"Node.js + PostgreSQL",postCreateCommand:"npm install",service:"app",workspaceFolder:"/workspaces/${localWorkspaceFolderBasename}"},description:"Node.js with PostgreSQL via Docker Compose",id:"node-postgres",name:"Node.js + PostgreSQL"},{config:{customizations:{vscode:{extensions:["dbaeumer.vscode-eslint","esbenp.prettier-vscode","ms-azuretools.vscode-docker"]}},features:{"ghcr.io/devcontainers/features/docker-in-docker:2":{},"ghcr.io/devcontainers/features/git:1":{},"ghcr.io/devcontainers/features/github-cli:1":{}},forwardPorts:[3e3],image:"mcr.microsoft.com/devcontainers/javascript-node:22",name:"Node.js + Docker",postCreateCommand:"npm install"},description:"Node.js 22 with Docker-in-Docker for container workflows",id:"node-dind",name:"Node.js + Docker-in-Docker"},{config:{customizations:{vscode:{extensions:["dbaeumer.vscode-eslint","esbenp.prettier-vscode","ms-azuretools.vscode-docker"]}},dockerComposeFile:"docker-compose.yml",features:{"ghcr.io/devcontainers/features/docker-in-docker:2":{}},forwardPorts:[3e3,5432,6379],name:"Full Stack",postCreateCommand:"npm install",service:"app",workspaceFolder:"/workspaces/${localWorkspaceFolderBasename}"},description:"Node.js + PostgreSQL + Redis + Docker via Compose",id:"fullstack",name:"Full Stack"},{config:{customizations:{vscode:{extensions:["ms-python.python","ms-python.vscode-pylance"],settings:{"editor.formatOnSave":!0,"python.defaultInterpreterPath":"/usr/local/bin/python"}}},features:{"ghcr.io/devcontainers/features/git:1":{},"ghcr.io/devcontainers/features/github-cli:1":{},"ghcr.io/devcontainers/features/python:1":{version:"3.12"}},forwardPorts:[8e3],image:"mcr.microsoft.com/devcontainers/python:3.12",name:"Python",postCreateCommand:"pip install -r requirements.txt || true"},description:"Python 3.12 with pip and venv",id:"python",name:"Python"},{config:{customizations:{vscode:{extensions:["golang.go"],settings:{"editor.formatOnSave":!0,"go.toolsManagement.autoUpdate":!0}}},features:{"ghcr.io/devcontainers/features/git:1":{},"ghcr.io/devcontainers/features/go:1":{version:"1.22"}},forwardPorts:[8080],image:"mcr.microsoft.com/devcontainers/go:1.22",name:"Go",postCreateCommand:"go mod download || true"},description:"Go 1.22 development environment",id:"go",name:"Go"},{config:{customizations:{vscode:{extensions:["rust-lang.rust-analyzer","tamasfe.even-better-toml"],settings:{"editor.formatOnSave":!0}}},features:{"ghcr.io/devcontainers/features/git:1":{},"ghcr.io/devcontainers/features/rust:1":{}},image:"mcr.microsoft.com/devcontainers/rust:latest",name:"Rust",postCreateCommand:"cargo build || true"},description:"Rust development with cargo and rust-analyzer",id:"rust",name:"Rust"},{config:{customizations:{vscode:{extensions:["vscjava.vscode-java-pack","vscjava.vscode-maven"]}},features:{"ghcr.io/devcontainers/features/git:1":{},"ghcr.io/devcontainers/features/java:1":{version:"17"}},forwardPorts:[8080],image:"mcr.microsoft.com/devcontainers/java:17",name:"Java",postCreateCommand:"./mvnw install || ./gradlew build || true"},description:"Java 17 with Maven/Gradle support",id:"java",name:"Java"},{config:{customizations:{vscode:{extensions:["ms-azuretools.vscode-docker","ms-kubernetes-tools.vscode-kubernetes-tools","hashicorp.terraform"]}},features:{"ghcr.io/devcontainers/features/aws-cli:1":{},"ghcr.io/devcontainers/features/azure-cli:1":{},"ghcr.io/devcontainers/features/docker-in-docker:2":{},"ghcr.io/devcontainers/features/kubectl-helm-minikube:1":{},"ghcr.io/devcontainers/features/terraform:1":{}},image:"mcr.microsoft.com/devcontainers/base:ubuntu",name:"DevOps"},description:"Docker, Kubernetes, Terraform, AWS & Azure CLIs",id:"devops",name:"DevOps"},{config:{features:{"ghcr.io/devcontainers/features/common-utils:2":{}},image:"mcr.microsoft.com/devcontainers/base:ubuntu",name:"Minimal",remoteUser:"vscode"},description:"Bare Ubuntu with common utilities",id:"minimal",name:"Minimal"},{config:{image:"mcr.microsoft.com/devcontainers/base:ubuntu",name:"Custom"},description:"Minimal Ubuntu base - configure from scratch",id:"custom",name:"Custom (Blank)"}];var ft=Object.defineProperty,Fe=y((n,e)=>ft(n,"name",{value:e,configurable:!0}),"s$5");const pt=Fe(n=>{const e=de(n,".devcontainer","devcontainer.json");if(!dt(e))return null;const s=ut(e),a=mt(s),l=a!==s;let o;try{o=JSON.parse(a)}catch(d){const g=d instanceof Error?d.message:String(d);throw new Error(`Failed to parse ${e}: ${g}`,{cause:d})}return{config:o,hadComments:l}},"readDevcontainerJson"),vt=Fe((n,e,s)=>{const a=s?gt(s):de(n,".devcontainer"),l=s??de(a,"devcontainer.json");ht(a),at(l,`${JSON.stringify(e,null,2)}
|
|
2
|
+
`,"utf8")},"writeDevcontainerJson");var yt=Object.defineProperty,bt=y((n,e)=>yt(n,"name",{value:e,configurable:!0}),"s$4");const wt={bun:[{source:"${localWorkspaceFolderBasename}-node_modules",target:"${containerWorkspaceFolder}/node_modules",type:"volume"},{source:"${localWorkspaceFolderBasename}-bun-cache",target:"/home/node/.bun/install/cache",type:"volume"}],deno:[{source:"${localWorkspaceFolderBasename}-deno-cache",target:"/home/node/.cache/deno",type:"volume"}],npm:[{source:"${localWorkspaceFolderBasename}-node_modules",target:"${containerWorkspaceFolder}/node_modules",type:"volume"},{source:"${localWorkspaceFolderBasename}-npm-cache",target:"/home/node/.npm",type:"volume"}],pnpm:[{source:"${localWorkspaceFolderBasename}-node_modules",target:"${containerWorkspaceFolder}/node_modules",type:"volume"},{source:"${localWorkspaceFolderBasename}-pnpm-store",target:"/home/node/.local/share/pnpm/store",type:"volume"}],yarn:[{source:"${localWorkspaceFolderBasename}-node_modules",target:"${containerWorkspaceFolder}/node_modules",type:"volume"},{source:"${localWorkspaceFolderBasename}-yarn-cache",target:"/home/node/.yarn/cache",type:"volume"}]},xt=[{featureMatch:"docker-in-docker",mounts:[]},{featureMatch:"docker-outside-of-docker",mounts:[{source:"/var/run/docker.sock",target:"/var/run/docker.sock",type:"bind"}]},{featureMatch:"/features/git:",mounts:[{source:"${localWorkspaceFolderBasename}-git-config",target:"/home/node/.gitconfig",type:"volume"}]}],xe=bt((n,e,s)=>{const a=[],l=new Set(s.map(d=>typeof d=="string"?d:d.target));if(n)for(const d of wt[n])l.has(d.target)||a.push(d);const o=Object.keys(e);for(const{featureMatch:d,mounts:g}of xt)if(o.some(f=>f.includes(d)))for(const f of g)l.has(f.target)||a.push(f);return a},"getSuggestedMounts"),A=["general","features","ports","lifecycle","extensions","environment","mounts","compose"];var Ct=Object.defineProperty,ee=y((n,e)=>Ct(n,"name",{value:e,configurable:!0}),"c$1");const J=ee(n=>structuredClone(n),"deepClone");class kt{static{y(this,"DevcontainerStore")}static{ee(this,"DevcontainerStore")}#o=new Set;#e;constructor(e,s,a=null){const l=e===null,o=e??{name:""},d=J(o);this.#e={config:d,detectedPm:a,extensionSearch:"",featureSearch:"",fieldEditing:!1,fieldIndex:0,hadComments:s,isDirty:!1,mode:l?"create":"edit",originalConfig:l?null:J(o),section:"general",showTemplateSelector:l,suggestedMounts:xe(a,d.features??{},d.mounts??[]),templateIndex:0}}getSnapshot=ee(()=>this.#e,"getSnapshot");subscribe=ee(e=>(this.#o.add(e),()=>{this.#o.delete(e)}),"subscribe");setSection(e){e!==this.#e.section&&this.#t({...this.#e,fieldEditing:!1,fieldIndex:0,section:e})}nextSection(){const e=(A.indexOf(this.#e.section)+1)%A.length;this.setSection(A[e])}previousSection(){const e=(A.indexOf(this.#e.section)-1+A.length)%A.length;this.setSection(A[e])}setFieldIndex(e){e!==this.#e.fieldIndex&&this.#t({...this.#e,fieldIndex:Math.max(0,e)})}setFieldEditing(e){e!==this.#e.fieldEditing&&this.#t({...this.#e,fieldEditing:e})}setTemplateIndex(e){const s=Math.max(0,Math.min(e,z.length-1));s!==this.#e.templateIndex&&this.#t({...this.#e,templateIndex:s})}applyTemplate(e){const s=z.find(a=>a.id===e);s&&this.#t(this.#r({...this.#e,config:J(s.config),isDirty:!0,showTemplateSelector:!1}))}dismissTemplateSelector(){this.#t({...this.#e,showTemplateSelector:!1})}updateConfig(e){this.#t({...this.#e,config:{...this.#e.config,...e},isDirty:!0})}toggleFeature(e){const s={...this.#e.config.features};s[e]===void 0?s[e]={}:delete s[e],this.#t(this.#r({...this.#e,config:{...this.#e.config,features:s},isDirty:!0}))}setFeatureSearch(e){this.#t({...this.#e,featureSearch:e,fieldIndex:0})}addPort(e){const s=this.#e.config.forwardPorts??[];if(s.includes(e))return;const a=[...s,e];this.#t({...this.#e,config:{...this.#e.config,forwardPorts:a},isDirty:!0})}removePort(e){const s=[...this.#e.config.forwardPorts??[]];s.splice(e,1),this.#t({...this.#e,config:{...this.#e.config,forwardPorts:s.length>0?s:void 0},isDirty:!0})}toggleExtension(e){const s={...this.#e.config.customizations},a={...s.vscode},l=[...a.extensions??[]],o=l.indexOf(e);o===-1?l.push(e):l.splice(o,1),a.extensions=l.length>0?l:void 0,s.vscode=a.extensions||a.settings?a:void 0,this.#t({...this.#e,config:{...this.#e.config,customizations:s.vscode||s.jetbrains?s:void 0},isDirty:!0})}setExtensionSearch(e){this.#t({...this.#e,extensionSearch:e,fieldIndex:0})}addEnvVar(e,s,a){const l=e==="container"?"containerEnv":"remoteEnv",o={...this.#e.config[l],[s]:a};this.#t({...this.#e,config:{...this.#e.config,[l]:o},isDirty:!0})}removeEnvVar(e,s){const a=e==="container"?"containerEnv":"remoteEnv",l={...this.#e.config[a]};delete l[s],this.#t({...this.#e,config:{...this.#e.config,[a]:Object.keys(l).length>0?l:void 0},isDirty:!0})}addMount(e){const s=[...this.#e.config.mounts??[],e];this.#t(this.#r({...this.#e,config:{...this.#e.config,mounts:s},isDirty:!0}))}removeMount(e){const s=[...this.#e.config.mounts??[]];s.splice(e,1),this.#t(this.#r({...this.#e,config:{...this.#e.config,mounts:s.length>0?s:void 0},isDirty:!0}))}applySuggestedMounts(){if(this.#e.suggestedMounts.length===0)return;const e=[...this.#e.config.mounts??[],...this.#e.suggestedMounts];this.#t(this.#r({...this.#e,config:{...this.#e.config,mounts:e},isDirty:!0}))}setLifecycleCommand(e,s){this.#t({...this.#e,config:{...this.#e.config,[e]:s||void 0},isDirty:!0})}markClean(){this.#t({...this.#e,isDirty:!1,originalConfig:J(this.#e.config)})}getJsonPreview(){return JSON.stringify(this.#n(),null,2)}cleanConfig(){return this.#n()}#n(){const e=J(this.#e.config);for(const[s,a]of Object.entries(e))(a===""||a===void 0)&&delete e[s];return e.build&&(e.build.dockerfile===""&&delete e.build.dockerfile,e.build.context===""&&delete e.build.context,e.build.args&&Object.keys(e.build.args).length===0&&delete e.build.args,Object.keys(e.build).length===0&&delete e.build),e.forwardPorts?.length===0&&delete e.forwardPorts,e.mounts?.length===0&&delete e.mounts,e.runServices?.length===0&&delete e.runServices,e.capAdd?.length===0&&delete e.capAdd,e.securityOpt?.length===0&&delete e.securityOpt,e.features&&Object.keys(e.features).length===0&&delete e.features,e.customizations?.vscode?.extensions?.length===0&&delete e.customizations.vscode.extensions,e.customizations?.vscode&&Object.keys(e.customizations.vscode).length===0&&delete e.customizations.vscode,e.customizations&&Object.keys(e.customizations).length===0&&delete e.customizations,e.containerEnv&&Object.keys(e.containerEnv).length===0&&delete e.containerEnv,e.remoteEnv&&Object.keys(e.remoteEnv).length===0&&delete e.remoteEnv,e}#r(e){return{...e,suggestedMounts:xe(e.detectedPm,e.config.features??{},e.config.mounts??[])}}#t(e){this.#e=e;for(const s of this.#o)try{s()}catch{}}}const Ce=[{category:"linting",description:"Integrates ESLint into the editor",id:"dbaeumer.vscode-eslint",name:"ESLint"},{category:"linting",description:"Stylelint CSS/SCSS linting",id:"stylelint.vscode-stylelint",name:"Stylelint"},{category:"formatting",description:"Opinionated code formatter",id:"esbenp.prettier-vscode",name:"Prettier"},{category:"formatting",description:"EditorConfig file support",id:"editorconfig.editorconfig",name:"EditorConfig"},{category:"formatting",description:"Fast Rust-based formatter and linter",id:"biomejs.biome",name:"Biome"},{category:"language",description:"Rich TypeScript and JavaScript support",id:"ms-vscode.vscode-typescript-next",name:"TypeScript Nightly"},{category:"language",description:"Tailwind CSS IntelliSense",id:"bradlc.vscode-tailwindcss",name:"Tailwind CSS"},{category:"language",description:"YAML language support with schemas",id:"redhat.vscode-yaml",name:"YAML"},{category:"language",description:"TOML language support",id:"tamasfe.even-better-toml",name:"TOML"},{category:"language",description:"Dockerfile and Docker Compose support",id:"ms-azuretools.vscode-docker",name:"Docker"},{category:"language",description:"Python language support with Pylance",id:"ms-python.python",name:"Python"},{category:"language",description:"Go language support",id:"golang.go",name:"Go"},{category:"language",description:"Rust language support via rust-analyzer",id:"rust-lang.rust-analyzer",name:"rust-analyzer"},{category:"git",description:"Git supercharged: blame, history, stash, etc.",id:"eamodio.gitlens",name:"GitLens"},{category:"git",description:"GitHub Pull Requests and Issues",id:"github.vscode-pull-request-github",name:"GitHub PR"},{category:"testing",description:"Vitest test explorer integration",id:"vitest.explorer",name:"Vitest Explorer"},{category:"testing",description:"Jest test runner and assertions",id:"orta.vscode-jest",name:"Jest"},{category:"debugging",description:"REST client for testing APIs",id:"humao.rest-client",name:"REST Client"},{category:"debugging",description:"Error Lens: inline error highlighting",id:"usernamehw.errorlens",name:"Error Lens"},{category:"other",description:"Intelligent code completion with AI",id:"github.copilot",name:"GitHub Copilot"},{category:"other",description:"Path autocompletion for imports",id:"christian-kohler.path-intellisense",name:"Path Intellisense"},{category:"other",description:"Import cost display in editor",id:"wix.vscode-import-cost",name:"Import Cost"},{category:"other",description:"Todo Tree: highlight and list TODOs",id:"gruntfuggly.todo-tree",name:"Todo Tree"}],ke=[{category:"language",description:"Node.js runtime via nvm with optional pnpm/yarn",id:"ghcr.io/devcontainers/features/node:1",name:"Node.js"},{category:"language",description:"Python runtime with pip and optional tools",id:"ghcr.io/devcontainers/features/python:1",name:"Python"},{category:"language",description:"Go compiler and tools",id:"ghcr.io/devcontainers/features/go:1",name:"Go"},{category:"language",description:"Rust toolchain via rustup",id:"ghcr.io/devcontainers/features/rust:1",name:"Rust"},{category:"language",description:"Java runtime and JDK via SDKMAN",id:"ghcr.io/devcontainers/features/java:1",name:"Java"},{category:"language",description:".NET SDK and runtime",id:"ghcr.io/devcontainers/features/dotnet:2",name:".NET"},{category:"tool",description:"Common utilities: zsh, Oh My Zsh, git, curl, etc.",id:"ghcr.io/devcontainers/features/common-utils:2",name:"Common Utilities"},{category:"tool",description:"Git version control",id:"ghcr.io/devcontainers/features/git:1",name:"Git"},{category:"tool",description:"Git Large File Storage support",id:"ghcr.io/devcontainers/features/git-lfs:1",name:"Git LFS"},{category:"tool",description:"GitHub CLI for repository management",id:"ghcr.io/devcontainers/features/github-cli:1",name:"GitHub CLI"},{category:"tool",description:"Run Docker containers inside the dev container",id:"ghcr.io/devcontainers/features/docker-in-docker:2",name:"Docker-in-Docker"},{category:"tool",description:"Access host Docker daemon from inside the container",id:"ghcr.io/devcontainers/features/docker-outside-of-docker:1",name:"Docker-from-Docker"},{category:"tool",description:"kubectl, Helm, and Minikube for Kubernetes",id:"ghcr.io/devcontainers/features/kubectl-helm-minikube:1",name:"Kubernetes Tools"},{category:"tool",description:"Infrastructure as code with Terraform",id:"ghcr.io/devcontainers/features/terraform:1",name:"Terraform"},{category:"tool",description:"Nix package manager",id:"ghcr.io/devcontainers/features/nix:1",name:"Nix"},{category:"tool",description:"SSH server for remote connections to the container",id:"ghcr.io/devcontainers/features/sshd:1",name:"SSH Server"},{category:"cloud",description:"Amazon Web Services CLI v2",id:"ghcr.io/devcontainers/features/aws-cli:1",name:"AWS CLI"},{category:"cloud",description:"Microsoft Azure CLI",id:"ghcr.io/devcontainers/features/azure-cli:1",name:"Azure CLI"},{category:"cloud",description:"Google Cloud Platform CLI",id:"ghcr.io/devcontainers/features/gcloud:1",name:"Google Cloud CLI"},{category:"database",description:"PostgreSQL client tools",id:"ghcr.io/devcontainers-extra/features/postgres-client:1",name:"PostgreSQL Client"},{category:"database",description:"Redis client tools",id:"ghcr.io/devcontainers-extra/features/redis-client:1",name:"Redis Client"}];var St=Object.defineProperty,Ae=y((n,e)=>St(n,"name",{value:e,configurable:!0}),"r$1");const me=Ae(n=>{if(!n)return ke;const e=n.toLowerCase();return ke.filter(s=>s.name.toLowerCase().includes(e)||s.id.toLowerCase().includes(e)||s.description.toLowerCase().includes(e))},"filterFeatures"),ge=Ae(n=>{if(!n)return Ce;const e=n.toLowerCase();return Ce.filter(s=>s.name.toLowerCase().includes(e)||s.id.toLowerCase().includes(e)||s.description.toLowerCase().includes(e))},"filterExtensions");var Et=Object.defineProperty,It=y((n,e)=>Et(n,"name",{value:e,configurable:!0}),"l$1");const Le=["dockerComposeFile","service"],Tt={dockerComposeFile:"Compose File",service:"Service"},Se={dockerComposeFile:"docker-compose.yml",service:"app"},Dt={dockerComposeFile:"Path to Docker Compose file (relative to .devcontainer/)",service:"Which service in the compose file to connect the IDE to"},Pt=It(({config:n,fieldEditing:e,fieldIndex:s,onUpdate:a})=>{const l=!!n.dockerComposeFile,o=!!(n.image||n.build);return i(c,{flexDirection:"column",paddingX:1,children:[t(c,{marginBottom:1,children:t(r,{bold:!0,color:"cyan",children:"Docker Compose Integration"})}),o&&l&&t(c,{marginBottom:1,children:t(r,{color:"yellow",children:"Note: When using Docker Compose, the image/build settings in General are ignored."})}),Le.map((d,g)=>{const f=g===s,p=n[d]??"",x=Array.isArray(n[d])?n[d].join(", "):p;return i(c,{flexDirection:"column",marginBottom:1,children:[i(c,{children:[t(c,{width:20,children:i(r,{bold:f,color:f?"cyan":"white",children:[f?"❯ ":" ",Tt[d],":"]})}),t(c,{flexGrow:1,children:f&&e?t(he,{defaultValue:x,onChange:y(v=>{a({[d]:v||void 0})},"onChange"),placeholder:Se[d]}):t(r,{color:x?"white":"gray",children:x||Se[d]})})]}),t(c,{paddingLeft:4,children:t(r,{dimColor:!0,children:Dt[d]})})]},d)}),t(c,{marginTop:1,children:i(r,{dimColor:!0,children:[t(r,{bold:!0,color:"white",children:"Enter"})," ","edit field"," ",t(r,{bold:!0,color:"white",children:"↑↓"})," ","navigate"," ",t(r,{bold:!0,color:"white",children:"Esc"})," ","stop editing"]})})]})},"DockerComposeSection"),jt=Le.length;var Ot=Object.defineProperty,Me=y((n,e)=>Ot(n,"name",{value:e,configurable:!0}),"h$3");const Ft=Me(({config:n,fieldIndex:e})=>{const s=n.containerEnv??{},a=n.remoteEnv??{},l=Object.keys(s),o=Object.keys(a),d=l.length,g=l.length+1,f=g+o.length,p=e<=d,x=e>d;return i(c,{flexDirection:"column",paddingX:1,children:[i(c,{borderColor:p?"cyan":"gray",borderStyle:"single",flexDirection:"column",paddingX:1,paddingY:0,children:[i(c,{flexShrink:0,marginBottom:l.length>0?1:0,children:[t(r,{bold:!0,color:p?"cyan":"white",children:"containerEnv"}),t(r,{dimColor:!0,children:" — baked into the container image"})]}),l.map((v,w)=>{const k=w===e;return t(c,{flexShrink:0,children:i(r,{color:k?"cyan":void 0,inverse:k,wrap:"truncate",children:[k?" ❯ ":" ",t(r,{bold:!0,children:v}),t(r,{dimColor:!0,children:" = "}),t(r,{children:s[v]})]})},v)}),t(c,{flexShrink:0,marginTop:l.length>0?1:0,children:i(r,{color:e===d?"cyan":"gray",inverse:e===d,children:[" ","+ Add variable..."]})})]}),i(c,{borderColor:x?"cyan":"gray",borderStyle:"single",flexDirection:"column",marginTop:1,paddingX:1,paddingY:0,children:[i(c,{flexShrink:0,marginBottom:o.length>0?1:0,children:[t(r,{bold:!0,color:x?"cyan":"white",children:"remoteEnv"}),t(r,{dimColor:!0,children:" — set at runtime by the IDE"})]}),o.map((v,w)=>{const k=g+w===e;return t(c,{flexShrink:0,children:i(r,{color:k?"cyan":void 0,inverse:k,wrap:"truncate",children:[k?" ❯ ":" ",t(r,{bold:!0,children:v}),t(r,{dimColor:!0,children:" = "}),t(r,{children:a[v]})]})},v)}),t(c,{flexShrink:0,marginTop:o.length>0?1:0,children:i(r,{color:e===f?"cyan":"gray",inverse:e===f,children:[" ","+ Add variable..."]})})]}),t(c,{flexShrink:0,marginTop:1,children:i(r,{dimColor:!0,wrap:"truncate",children:[t(r,{bold:!0,color:"white",children:"a"}),"/",t(r,{bold:!0,color:"white",children:"Enter"})," ","add on + row"," ",t(r,{bold:!0,color:"white",children:"d"})," ","remove"," ",t(r,{bold:!0,color:"white",children:"↑↓"})," ","navigate"]})})]})},"EnvironmentSection"),At=Me(n=>{const e=Object.keys(n.containerEnv??{}).length,s=Object.keys(n.remoteEnv??{}).length;return e+1+s+1},"getEnvFieldCount");var Lt=Object.defineProperty,Mt=y((n,e)=>Lt(n,"name",{value:e,configurable:!0}),"h$2");const Bt=Mt(({config:n,fieldIndex:e,scrollOffset:s,searchText:a,viewportHeight:l})=>{const o=q(()=>new Set(n.customizations?.vscode?.extensions),[n.customizations?.vscode?.extensions]),d=q(()=>ge(a),[a]),g=d.length,f=g>l&&l>0;return i(c,{flexDirection:"column",flexGrow:1,children:[i(c,{flexShrink:0,gap:1,paddingX:1,children:[i(r,{bold:!0,color:"cyan",children:[o.size," ","selected"]}),a&&i(r,{dimColor:!0,children:["— filter:"," ",t(r,{color:"yellow",children:a})," ","(",d.length," ","results)"]})]}),i(c,{flexDirection:"row",flexGrow:1,overflow:"hidden",children:[t(c,{flexDirection:"column",flexGrow:1,overflow:"hidden",paddingLeft:1,children:t(c,{flexDirection:"column",marginTop:-s,children:d.map((p,x)=>{const v=x===e,w=o.has(p.id);return i(c,{flexShrink:0,height:1,children:[t(r,{children:v?">":" "}),i(r,{color:w?"white":"gray",children:[" ",w?"☑":"☐"," "]}),t(c,{flexGrow:1,children:i(r,{bold:v,inverse:v,wrap:"truncate",children:[p.name,i(r,{dimColor:!0,children:[" ","-",p.id]})]})})]},p.id)})})}),f&&t(c,{flexShrink:0,marginLeft:1,marginRight:1,children:t(Oe,{contentHeight:g,placement:"inset",scrollOffset:s,style:"block",viewportHeight:l})})]}),d.length===0&&t(c,{paddingX:1,children:t(r,{dimColor:!0,children:"No extensions match the search."})})]})},"ExtensionsSection");var Nt=Object.defineProperty,zt=y((n,e)=>Nt(n,"name",{value:e,configurable:!0}),"p$1");const Gt=zt(({config:n,fieldIndex:e,scrollOffset:s,searchText:a,viewportHeight:l})=>{const o=q(()=>new Set(Object.keys(n.features??{})),[n.features]),d=q(()=>me(a),[a]),g=d.length,f=g>l&&l>0;return i(c,{flexDirection:"column",flexGrow:1,children:[i(c,{flexShrink:0,gap:1,paddingX:1,children:[i(r,{bold:!0,color:"cyan",children:[o.size," ","selected"]}),a&&i(r,{dimColor:!0,children:["— filter:"," ",t(r,{color:"yellow",children:a})," ","(",d.length," ","results)"]})]}),i(c,{flexDirection:"row",flexGrow:1,overflow:"hidden",children:[t(c,{flexDirection:"column",flexGrow:1,overflow:"hidden",paddingLeft:1,children:t(c,{flexDirection:"column",marginTop:-s,children:d.map((p,x)=>{const v=x===e,w=o.has(p.id);return i(c,{flexShrink:0,height:1,children:[t(r,{children:v?">":" "}),i(r,{color:w?"white":"gray",children:[" ",w?"☑":"☐"," "]}),t(c,{flexGrow:1,children:i(r,{bold:v,inverse:v,wrap:"truncate",children:[p.name,i(r,{dimColor:!0,children:[" ","-",p.description]})]})})]},p.id)})})}),f&&t(c,{flexShrink:0,marginLeft:1,marginRight:1,children:t(Oe,{contentHeight:g,placement:"inset",scrollOffset:s,style:"block",viewportHeight:l})})]}),d.length===0&&t(c,{paddingX:1,children:t(r,{dimColor:!0,children:"No features match the search."})})]})},"FeaturesSection");var Rt=Object.defineProperty,_t=y((n,e)=>Rt(n,"name",{value:e,configurable:!0}),"u$1");const ue=["name","image","workspaceFolder","workspaceMount","remoteUser","containerUser","shutdownAction"],$t={containerUser:"Container User",image:"Image",name:"Name",remoteUser:"Remote User",shutdownAction:"Shutdown Action",workspaceFolder:"Workspace Folder",workspaceMount:"Workspace Mount"},Ee={containerUser:"root",image:"mcr.microsoft.com/devcontainers/javascript-node:22",name:"My Dev Container",remoteUser:"node",shutdownAction:"none | stopContainer",workspaceFolder:"/workspaces/${localWorkspaceFolderBasename}",workspaceMount:"source=${localWorkspaceFolder},target=...,type=bind"},te=["privileged","overrideCommand"],Ut={overrideCommand:"Override Command",privileged:"Privileged"},Wt=ue.length+te.length,Xt=_t(({config:n,fieldEditing:e,fieldIndex:s,onUpdate:a})=>i(c,{flexDirection:"column",paddingX:1,children:[t(c,{marginBottom:1,children:t(r,{bold:!0,color:"cyan",children:"General Configuration"})}),ue.map((l,o)=>{const d=o===s,g=n[l]??"";return i(c,{marginBottom:1,children:[t(c,{width:20,children:i(r,{bold:d,color:d?"cyan":"white",children:[d?"❯ ":" ",$t[l],":"]})}),t(c,{flexGrow:1,children:d&&e?t(he,{defaultValue:g,onChange:y(f=>{a({[l]:f})},"onChange"),placeholder:Ee[l]}):t(r,{color:g?"white":"gray",children:g||Ee[l]})})]},l)}),te.map((l,o)=>{const d=ue.length+o===s,g=n[l]??!1;return i(c,{marginBottom:o<te.length-1?1:0,children:[t(c,{width:20,children:i(r,{bold:d,color:d?"cyan":"white",children:[d?"❯ ":" ",Ut[l],":"]})}),t(c,{flexGrow:1,children:i(r,{color:g?"green":"gray",children:[g?"yes":"no",d&&t(r,{dimColor:!0,children:" (Space to toggle)"})]})})]},l)}),t(c,{marginTop:1,children:i(r,{dimColor:!0,children:[t(r,{bold:!0,color:"white",children:"Enter"})," ","edit field"," ",t(r,{bold:!0,color:"white",children:"Space"})," ","toggle"," ",t(r,{bold:!0,color:"white",children:"↑↓"})," ","navigate"," ",t(r,{bold:!0,color:"white",children:"Esc"})," ","stop editing"]})})]}),"GeneralSection"),Be=Wt,ae=te;var Vt=Object.defineProperty,Ht=y((n,e)=>Vt(n,"name",{value:e,configurable:!0}),"s$2");const Ne=["postCreateCommand","postStartCommand","postAttachCommand","onCreateCommand"],Jt={onCreateCommand:"On Create",postAttachCommand:"Post Attach",postCreateCommand:"Post Create",postStartCommand:"Post Start"},qt={onCreateCommand:"Runs once when the container is first created",postAttachCommand:"Runs each time the IDE attaches",postCreateCommand:"Runs after the container is created and workspace mounted",postStartCommand:"Runs each time the container starts"},Yt=Ht(({config:n,fieldEditing:e,fieldIndex:s,onSetCommand:a})=>i(c,{flexDirection:"column",paddingX:1,children:[t(c,{marginBottom:1,children:t(r,{bold:!0,color:"cyan",children:"Lifecycle Commands"})}),Ne.map((l,o)=>{const d=o===s,g=n[l],f=Array.isArray(g)?g.join(" && "):g??"";return i(c,{flexDirection:"column",marginBottom:1,children:[t(c,{children:i(r,{bold:d,color:d?"cyan":"white",children:[d?"❯ ":" ",Jt[l]]})}),t(c,{paddingLeft:4,children:t(r,{dimColor:!0,children:qt[l]})}),t(c,{paddingLeft:4,children:d&&e?t(he,{defaultValue:f,onChange:y(p=>{a(l,p)},"onChange"),placeholder:"e.g. npm install"}):t(r,{color:f?"green":"gray",children:f||"(not set)"})})]},l)}),t(c,{marginTop:1,children:i(r,{dimColor:!0,children:[t(r,{bold:!0,color:"white",children:"Enter"})," ","edit command"," ",t(r,{bold:!0,color:"white",children:"↑↓"})," ","navigate"," ",t(r,{bold:!0,color:"white",children:"Esc"})," ","stop editing"]})})]}),"LifecycleSection"),Kt=Ne.length;var Qt=Object.defineProperty,ze=y((n,e)=>Qt(n,"name",{value:e,configurable:!0}),"s$1");const Ie=ze(n=>typeof n=="string"?n:`[${n.type}] ${n.source} → ${n.target}`,"formatMount"),Zt=ze(({addingMount:n,config:e,detectedPm:s,fieldIndex:a,mountPhase:l,mountSource:o,mountTarget:d,mountType:g,suggestedMounts:f})=>{const p=e.mounts??[];return i(c,{flexDirection:"column",paddingX:1,children:[i(c,{flexShrink:0,gap:1,paddingX:1,children:[i(r,{bold:!0,color:"cyan",children:[p.length," ","mounts"]}),s&&i(r,{dimColor:!0,children:["— detected:"," ",t(r,{color:"white",children:s})]})]}),f.length>0&&!n&&i(c,{borderColor:"yellow",borderStyle:"single",flexDirection:"column",marginBottom:1,marginTop:1,paddingX:1,children:[i(c,{flexShrink:0,children:[t(r,{bold:!0,color:"yellow",children:"Suggested mounts"}),i(r,{dimColor:!0,children:[" ","— press"," ",t(r,{bold:!0,color:"white",children:"A"})," ","to add all"]})]}),f.map((x,v)=>t(c,{flexShrink:0,children:i(r,{dimColor:!0,wrap:"truncate",children:[" + ",Ie(x)]})},`suggestion-${String(v)}`))]}),p.length>0&&t(c,{flexDirection:"column",marginBottom:1,children:p.map((x,v)=>{const w=v===a;return i(c,{flexShrink:0,height:1,children:[t(r,{children:w?">":" "}),t(c,{flexGrow:1,children:i(r,{bold:w,inverse:w,wrap:"truncate",children:[" ",Ie(x)]})})]},`mount-${String(v)}`)})}),!n&&t(c,{flexShrink:0,children:i(r,{color:a===p.length?"cyan":"gray",inverse:a===p.length,children:[" ","+ Add mount..."]})}),n&&i(c,{borderColor:"cyan",borderStyle:"single",flexDirection:"column",marginTop:1,paddingX:1,children:[t(c,{flexShrink:0,marginBottom:1,children:t(r,{bold:!0,color:"cyan",children:"New Mount"})}),i(c,{flexShrink:0,children:[t(c,{width:12,children:i(r,{bold:l==="source",color:l==="source"?"cyan":"white",children:[l==="source"?"❯ ":" ","Source:"]})}),t(r,{color:o?"yellow":"gray",children:o||(l==="source"?"_":"(type source, Enter to continue)")})]}),i(c,{flexShrink:0,children:[t(c,{width:12,children:i(r,{bold:l==="target",color:l==="target"?"cyan":"white",children:[l==="target"?"❯ ":" ","Target:"]})}),t(r,{color:d?"yellow":"gray",children:d||(l==="target"?"_":"/container/path")})]}),i(c,{flexShrink:0,children:[t(c,{width:12,children:i(r,{bold:l==="type",color:l==="type"?"cyan":"white",children:[l==="type"?"❯ ":" ","Type:"]})}),l==="type"?i(r,{children:[t(r,{bold:g==="volume",color:g==="volume"?"cyan":"gray",children:"[1] volume"})," ",t(r,{bold:g==="bind",color:g==="bind"?"cyan":"gray",children:"[2] bind"})," ",t(r,{bold:g==="tmpfs",color:g==="tmpfs"?"cyan":"gray",children:"[3] tmpfs"})]}):t(r,{color:"gray",children:g})]}),t(c,{flexShrink:0,marginTop:1,children:t(r,{dimColor:!0,wrap:"truncate",children:l==="type"?"1/2/3 select type, Enter confirm, Esc cancel":"Type text, Enter next step, Esc cancel"})})]}),p.length===0&&!n&&f.length===0&&t(c,{marginTop:1,children:t(r,{dimColor:!0,children:"Tip: Use volume mounts for node_modules and caches to improve performance."})})]})},"MountsSection");var er=Object.defineProperty,tr=y((n,e)=>er(n,"name",{value:e,configurable:!0}),"m$1");const rr=tr(({addingPort:n,addPortValue:e,config:s,fieldIndex:a})=>{const l=s.forwardPorts??[],o=a===l.length;return i(c,{flexDirection:"column",paddingX:1,children:[i(c,{marginBottom:1,children:[t(r,{bold:!0,color:"cyan",children:"Forwarded Ports"}),i(r,{dimColor:!0,children:[" ","(",l.length," ","ports)"]})]}),l.map((d,g)=>{const f=g===a;return t(c,{children:i(r,{color:f?"cyan":void 0,inverse:f,children:[" ",String(d)]})},`port-${String(d)}`)}),t(c,{marginTop:l.length>0?1:0,children:i(r,{color:o?"cyan":"gray",inverse:o,children:[" ",o&&n?i(r,{children:["Enter port:"," ",t(r,{color:"yellow",children:e||"_"})]}):"+ Add port..."]})}),t(c,{marginTop:1,children:i(r,{dimColor:!0,children:[t(r,{bold:!0,color:"white",children:"Enter"})," ",o?"type port number, Enter to confirm":"select"," ",t(r,{bold:!0,color:"white",children:"d"})," ","remove selected"," ",t(r,{bold:!0,color:"white",children:"↑↓"})," ","navigate"]})})]})},"PortsSection");var or=Object.defineProperty,nr=y((n,e)=>or(n,"name",{value:e,configurable:!0}),"a");const ir=nr(({focused:n,hadComments:e,jsonPreview:s,mode:a,scrollRef:l})=>i(c,{borderColor:n?"cyan":"gray",borderStyle:"single",flexDirection:"column",flexGrow:1,children:[i(c,{flexShrink:0,paddingX:1,children:[t(r,{bold:!0,color:n?"cyan":"white",children:"Preview"}),i(r,{dimColor:!0,children:[" ","(",a==="create"?"new":"edit",")"]})]}),e&&a==="edit"&&t(c,{flexShrink:0,paddingX:1,children:t(r,{color:"yellow",children:"Comments will not be preserved."})}),t(Ye,{flexGrow:1,ref:l,scrollbar:!0,scrollbarColor:"gray",children:s.split(`
|
|
3
|
+
`).map((o,d)=>t(r,{color:"green",children:o},`line-${String(d)}`))})]}),"PreviewPanel");var cr=Object.defineProperty,sr=y((n,e)=>cr(n,"name",{value:e,configurable:!0}),"r");const lr=sr(n=>{const e=[],s=[],a=[];if(!n.image&&!n.build&&!n.dockerComposeFile&&e.push({field:"image",message:'One of "image", "build", or "dockerComposeFile" is required'}),n.build&&(n.image&&s.push({field:"image",message:'Both "image" and "build" are set; "build" takes precedence'}),n.build.dockerfile||e.push({field:"build.dockerfile",message:'"build" requires a "dockerfile" path'})),n.dockerComposeFile&&!n.service&&e.push({field:"service",message:'"service" is required when using "dockerComposeFile"'}),n.features!==void 0&&(typeof n.features!="object"||Array.isArray(n.features))&&e.push({field:"features",message:'"features" must be an object mapping feature IDs to options'}),n.forwardPorts)if(Array.isArray(n.forwardPorts))for(const[l,o]of n.forwardPorts.entries())typeof o=="number"&&(o<1||o>65535)&&e.push({field:"forwardPorts",message:`Invalid port ${String(o)} at index ${String(l)}`});else e.push({field:"forwardPorts",message:'"forwardPorts" must be an array'});return n.customizations?.vscode?.extensions&&!Array.isArray(n.customizations.vscode.extensions)&&e.push({field:"customizations.vscode.extensions",message:"Extensions must be an array"}),n.customizations?.vscode?.settings&&typeof n.customizations.vscode.settings!="object"&&e.push({field:"customizations.vscode.settings",message:"Settings must be an object"}),n.name||a.push({field:"name",message:"Consider adding a name for better identification"}),(!n.features||Object.keys(n.features).length===0)&&a.push({field:"features",message:"Consider adding features for common tools"}),(!n.customizations?.vscode?.extensions||n.customizations.vscode.extensions.length===0)&&a.push({field:"extensions",message:"Consider adding VS Code extensions for your stack"}),n.privileged&&s.push({field:"privileged",message:"Running in privileged mode is a security risk"}),{errors:e,suggestions:a,valid:e.length===0,warnings:s}},"validateConfig");var ar=Object.defineProperty,Ge=y((n,e)=>ar(n,"name",{value:e,configurable:!0}),"$");const Te=80,De=15,dr=120,Pe=[{description:"Container name, base image, workspace folder, and user",id:"general",label:"General"},{description:"Installable tools and runtimes (Node, Python, Docker, etc.)",id:"features",label:"Features"},{description:"Ports to forward from the container to your host",id:"ports",label:"Ports"},{description:"Commands to run at different stages of the container lifecycle",id:"lifecycle",label:"Lifecycle"},{description:"VS Code extensions to auto-install in the container",id:"extensions",label:"Extensions"},{description:"Environment variables for the container and IDE",id:"environment",label:"Env"},{description:"Volume and bind mounts for persistent data and caches",id:"mounts",label:"Mounts"},{description:"Docker Compose integration for multi-container setups",id:"compose",label:"Compose"}],ur=Ge((n,e,s,a)=>{switch(n){case"compose":return jt;case"environment":return At(e);case"extensions":return ge(a).length;case"features":return me(s).length;case"general":return Be;case"lifecycle":return Kt;case"mounts":return(e.mounts?.length??0)+1;case"ports":return(e.forwardPorts?.length??0)+1;default:return 0}},"getFieldCount"),hr=Ge(({onSave:n,store:e})=>{const{exit:s}=Ke(),{columns:a,rows:l}=Qe(),o=st(e.subscribe,e.getSnapshot),[d,g]=C(!1),[f,p]=C(!1),[x,v]=C(!1),[w,k]=C(null),[S,P]=C("editor"),[Y,fe]=C(0),[re,oe]=C(!1),[pe,G]=C(""),[R,_]=C(null),[$,j]=C(""),[ve,O]=C(""),[U,L]=C("key"),[ne,K]=C(!1),[ie,M]=C(""),[ce,B]=C(""),[ye,W]=C("volume"),[I,N]=C("source"),se=Z(null),F=Z(null),D=Z(null),Q=Z(!0);le(()=>(Q.current=!0,()=>{Q.current=!1,D.current&&clearTimeout(D.current)}),[]);const be=ur(o.section,o.config,o.featureSearch,o.extensionSearch),X=Math.max(1,l-9);le(()=>{o.section!=="features"&&o.section!=="extensions"||fe(h=>o.fieldIndex>=h+X?o.fieldIndex-X+1:o.fieldIndex<h?o.fieldIndex:h)},[o.fieldIndex,o.section,X]),le(()=>{fe(0)},[o.section,o.featureSearch,o.extensionSearch]);const Re=lt(()=>{const h=e.cleanConfig(),m=lr(h);if(!m.valid){const b=m.errors[0];k(b?`Error: ${b.message}`:"Validation failed"),D.current&&clearTimeout(D.current),D.current=setTimeout(()=>{Q.current&&k(null)},3e3);return}n(h),e.markClean();const u=m.warnings.length;k(u>0?`Saved! (${String(u)} warning${u>1?"s":""})`:"Saved!"),D.current&&clearTimeout(D.current),D.current=setTimeout(()=>{Q.current&&k(null)},2e3)},[n,e]);H((h,m)=>{if(m.downArrow||h==="j")e.setTemplateIndex(o.templateIndex+1);else if(m.upArrow||h==="k")e.setTemplateIndex(o.templateIndex-1);else if(m.return){const u=z[o.templateIndex];u&&e.applyTemplate(u.id)}else m.escape&&e.dismissTemplateSelector()},{isActive:o.showTemplateSelector}),H((h,m)=>{if(m.escape){oe(!1),G("");return}if(m.return){const u=Number.parseInt(pe,10);!Number.isNaN(u)&&u>0&&u<=65535&&e.addPort(u),oe(!1),G("");return}if(m.backspace){G(u=>u.slice(0,-1));return}h&&/^\d$/u.test(h)&&G(u=>u+h)},{isActive:re}),H((h,m)=>{if(m.escape){_(null),j(""),O(""),L("key");return}if(m.return){if(U==="key"&&$){L("value");return}if(U==="value"&&$){e.addEnvVar(R,$,ve),_(null),j(""),O(""),L("key");return}}if(m.backspace){U==="key"?j(u=>u.slice(0,-1)):O(u=>u.slice(0,-1));return}h&&!m.ctrl&&!m.meta&&(U==="key"?j(u=>u+h):O(u=>u+h))},{isActive:R!==null}),H((h,m)=>{if(m.escape){K(!1),M(""),B(""),N("source");return}if(m.return){if(I==="source"&&ie){N("target");return}if(I==="target"&&ce){N("type");return}if(I==="type"){e.addMount({source:ie,target:ce,type:ye}),K(!1),M(""),B(""),N("source");return}}if(I==="type"){switch(h){case"1":{W("volume");break}case"2":{W("bind");break}case"3":{W("tmpfs");break}}return}if(m.backspace){I==="source"?M(u=>u.slice(0,-1)):I==="target"&&B(u=>u.slice(0,-1));return}h&&!m.ctrl&&!m.meta&&(I==="source"?M(u=>u+h):I==="target"&&B(u=>u+h))},{isActive:ne}),H((h,m)=>{if(h==="c"&&m.ctrl){s();return}if(!f){if(d){m.escape||h==="?"?g(!1):m.downArrow||h==="j"?se.current?.scrollBy(1):m.upArrow||h==="k"?se.current?.scrollBy(-1):h==="q"&&(g(!1),p(!0));return}if(x){if(m.escape){v(!1),o.section==="features"?e.setFeatureSearch(""):e.setExtensionSearch("");return}if(m.return){v(!1);return}if(m.backspace){o.section==="features"?e.setFeatureSearch(o.featureSearch.slice(0,-1)):e.setExtensionSearch(o.extensionSearch.slice(0,-1));return}if(h&&!m.ctrl&&!m.meta){o.section==="features"?e.setFeatureSearch(o.featureSearch+h):e.setExtensionSearch(o.extensionSearch+h);return}return}if(o.fieldEditing){if(m.escape){e.setFieldEditing(!1);return}if(m.return){e.setFieldEditing(!1);return}return}if(h==="?"){g(!0);return}if(h==="q"){o.isDirty?p(!0):s();return}if(h==="s"){Re();return}if(m.tab){P(u=>u==="editor"?"preview":"editor");return}if(S==="preview"){if(m.downArrow||h==="j"){F.current?.scrollBy(1);return}if(m.upArrow||h==="k"){F.current?.scrollBy(-1);return}if(m.pageDown){F.current?.scrollBy(10);return}if(m.pageUp){F.current?.scrollBy(-10);return}if(m.home){F.current?.scrollToTop();return}if(m.end){F.current?.scrollToBottom();return}m.escape&&P("editor");return}if(m.downArrow||h==="j"){be>0&&e.setFieldIndex(Math.min(o.fieldIndex+1,be-1));return}if(m.upArrow||h==="k"){e.setFieldIndex(Math.max(o.fieldIndex-1,0));return}if(m.return){switch(o.section){case"compose":case"general":case"lifecycle":{e.setFieldEditing(!0);break}case"environment":{const u=Object.keys(o.config.containerEnv??{}).length,b=u,T=u+1+Object.keys(o.config.remoteEnv??{}).length;o.fieldIndex===b?(_("container"),j(""),O(""),L("key")):o.fieldIndex===T&&(_("remote"),j(""),O(""),L("key"));break}case"mounts":{const u=o.config.mounts??[];o.fieldIndex===u.length&&(K(!0),M(""),B(""),W("volume"),N("source"));break}case"ports":{const u=o.config.forwardPorts??[];o.fieldIndex===u.length&&(oe(!0),G(""));break}}return}if(h===" "){switch(o.section){case"extensions":{const u=ge(o.extensionSearch)[o.fieldIndex];u&&e.toggleExtension(u.id);break}case"features":{const u=me(o.featureSearch)[o.fieldIndex];u&&e.toggleFeature(u.id);break}case"general":{const u=Be-ae.length,b=o.fieldIndex-u;if(b>=0&&b<ae.length){const T=ae[b];e.updateConfig({[T]:!o.config[T]})}break}}return}if(h==="/"){(o.section==="features"||o.section==="extensions")&&v(!0);return}if(h==="A"&&o.section==="mounts"){e.applySuggestedMounts();return}if(h==="a"){if(o.section==="environment"){const u=Object.keys(o.config.containerEnv??{}).length,b=o.fieldIndex<=u?"container":"remote";_(b),j(""),O(""),L("key")}else o.section==="mounts"&&(K(!0),M(""),B(""),W("volume"),N("source"));return}if(h==="d")switch(o.section){case"environment":{const u=Object.keys(o.config.containerEnv??{}),b=Object.keys(o.config.remoteEnv??{});if(o.fieldIndex<u.length)e.removeEnvVar("container",u[o.fieldIndex]),u.length===1||o.fieldIndex>=u.length-1&&e.setFieldIndex(u.length-2);else{const T=o.fieldIndex-u.length-1;T>=0&&T<b.length&&(e.removeEnvVar("remote",b[T]),b.length===1||T>=b.length-1&&e.setFieldIndex(o.fieldIndex-1))}break}case"mounts":{const u=o.config.mounts??[];if(o.fieldIndex<u.length){e.removeMount(o.fieldIndex);const b=u.length-1;o.fieldIndex>=b&&b>0&&e.setFieldIndex(b-1)}break}case"ports":{const u=o.config.forwardPorts??[];if(o.fieldIndex<u.length){e.removePort(o.fieldIndex);const b=u.length-1;o.fieldIndex>=b&&b>0&&e.setFieldIndex(b-1)}break}}}},{isActive:!o.showTemplateSelector&&!re&&R===null&&!ne});const _e=q(()=>e.getJsonPreview(),[o.config]);if(a<Te||l<De)return t(c,{alignItems:"center",height:l,justifyContent:"center",width:a,children:i(r,{color:"yellow",children:["Terminal too small (",a,"x",l,"), need"," ",Te,"x",De]})});if(o.showTemplateSelector)return t(c,{alignItems:"center",flexDirection:"column",height:l,justifyContent:"center",width:a,children:i(c,{borderColor:"cyan",borderStyle:"round",flexDirection:"column",paddingX:2,paddingY:1,width:60,children:[t(c,{justifyContent:"center",marginBottom:1,children:t(r,{bold:!0,color:"cyan",children:"Select a Template"})}),z.map((h,m)=>{const u=m===o.templateIndex;return t(c,{children:i(r,{color:u?"cyan":void 0,inverse:u,children:[u?" ❯ ":" ",t(r,{bold:u,children:h.name}),i(r,{dimColor:!0,children:[" ","-",h.description]})]})},h.id)}),t(c,{justifyContent:"center",marginTop:1,children:i(r,{dimColor:!0,children:[t(r,{bold:!0,color:"white",children:"↑↓"})," ","navigate"," ",t(r,{bold:!0,color:"white",children:"Enter"})," ","select"," ",t(r,{bold:!0,color:"white",children:"Esc"})," ","blank"]})})]})});let E;switch(o.section){case"compose":{E=t(Pt,{config:o.config,fieldEditing:o.fieldEditing,fieldIndex:o.fieldIndex,onUpdate:y(h=>{e.updateConfig(h)},"onUpdate")});break}case"environment":{E=i(c,{flexDirection:"column",children:[t(Ft,{config:o.config,fieldIndex:o.fieldIndex}),R!==null&&t(c,{marginTop:1,paddingX:1,children:i(r,{color:"cyan",children:["Add"," ",R," ","env:"," ",U==="key"?i(r,{children:["key=",t(r,{color:"yellow",children:$||"_"})," ","(Enter to set value)"]}):i(r,{children:[$,"=",t(r,{color:"yellow",children:ve||"_"})," ","(Enter to confirm, Esc to cancel)"]})]})})]});break}case"extensions":{E=t(Bt,{config:o.config,fieldIndex:o.fieldIndex,scrollOffset:Y,searchText:o.extensionSearch,viewportHeight:X});break}case"features":{E=t(Gt,{config:o.config,fieldIndex:o.fieldIndex,scrollOffset:Y,searchText:o.featureSearch,viewportHeight:X});break}case"general":{E=t(Xt,{config:o.config,fieldEditing:o.fieldEditing,fieldIndex:o.fieldIndex,onUpdate:y(h=>{e.updateConfig(h)},"onUpdate")});break}case"lifecycle":{E=t(Yt,{config:o.config,fieldEditing:o.fieldEditing,fieldIndex:o.fieldIndex,onSetCommand:y((h,m)=>{e.setLifecycleCommand(h,m)},"onSetCommand")});break}case"mounts":{E=t(Zt,{addingMount:ne,config:o.config,detectedPm:o.detectedPm,fieldIndex:o.fieldIndex,mountPhase:I,mountSource:ie,mountTarget:ce,mountType:ye,suggestedMounts:o.suggestedMounts});break}case"ports":{E=t(rr,{addingPort:re,addPortValue:pe,config:o.config,fieldIndex:o.fieldIndex});break}default:E=t(r,{children:"Unknown section"})}const $e=i(c,{borderBottom:!1,borderColor:"gray",borderLeft:!1,borderRight:!1,borderStyle:"single",flexShrink:0,children:[i(c,{flexGrow:1,flexWrap:"wrap",gap:2,paddingX:1,children:[i(c,{gap:1,children:[t(r,{bold:!0,color:"white",children:"q"}),t(r,{dimColor:!0,children:"QUIT"})]}),i(c,{gap:1,children:[t(r,{bold:!0,color:"white",children:"?"}),t(r,{dimColor:!0,children:"HELP"})]}),i(c,{gap:1,children:[t(r,{bold:!0,color:"white",children:"↑↓"}),t(r,{dimColor:!0,children:"NAV"})]}),(o.section==="features"||o.section==="extensions")&&i(c,{gap:1,children:[t(r,{bold:!0,color:"white",children:"Space"}),t(r,{dimColor:!0,children:"CHECK"})]}),i(c,{gap:1,children:[t(r,{bold:!0,color:"white",children:"←→"}),t(r,{dimColor:!0,children:"TABS"})]}),i(c,{gap:1,children:[t(r,{bold:!0,color:"white",children:"Tab"}),t(r,{dimColor:!0,children:"PANEL"})]}),(o.section==="features"||o.section==="extensions")&&i(c,{gap:1,children:[t(r,{bold:!0,color:"white",children:"/"}),t(r,{dimColor:!0,children:"FILTER"})]}),i(c,{gap:1,children:[t(r,{bold:!0,color:"white",children:"s"}),t(r,{dimColor:!0,children:"SAVE"})]})]}),i(c,{paddingX:1,children:[w&&i(r,{color:w.startsWith("Error")?"red":"green",children:[w," "]}),o.isDirty&&t(r,{color:"yellow",children:"[modified]"}),!o.isDirty&&!w&&t(r,{dimColor:!0,children:"[saved]"})]})]}),Ue=i(Ze,{footer:i(r,{dimColor:!0,children:[t(r,{bold:!0,color:"white",children:"↑↓"})," ","scroll"," ",t(r,{bold:!0,color:"white",children:"?"}),"/",t(r,{bold:!0,color:"white",children:"Esc"})," ","close"]}),scrollRef:se,title:"KEYBOARD SHORTCUTS",visible:d,width:56,children:[i(c,{flexDirection:"column",marginBottom:1,children:[i(c,{marginBottom:1,children:[t(r,{dimColor:!0,children:"── "}),t(r,{bold:!0,color:"white",children:"NAVIGATION"})]}),i(r,{children:[" ",t(r,{bold:!0,color:"white",children:"←→"}),t(r,{dimColor:!0,children:" Switch tabs"})]}),i(r,{children:[" ",t(r,{bold:!0,color:"white",children:"↑↓"}),"/",t(r,{bold:!0,color:"white",children:"j/k"}),t(r,{dimColor:!0,children:" Navigate within section"})]}),i(r,{children:[" ",t(r,{bold:!0,color:"white",children:"Tab"}),t(r,{dimColor:!0,children:" Switch editor/preview panel"})]}),i(r,{children:[" ",t(r,{bold:!0,color:"white",children:"Enter"}),t(r,{dimColor:!0,children:" Edit selected field"})]}),i(r,{children:[" ",t(r,{bold:!0,color:"white",children:"Esc"}),t(r,{dimColor:!0,children:" Stop editing / cancel"})]})]}),i(c,{flexDirection:"column",marginBottom:1,children:[i(c,{marginBottom:1,children:[t(r,{dimColor:!0,children:"── "}),t(r,{bold:!0,color:"white",children:"FEATURES / EXTENSIONS"})]}),i(r,{children:[" ",t(r,{bold:!0,color:"white",children:"Space"}),t(r,{dimColor:!0,children:" Toggle selection"})]}),i(r,{children:[" ",t(r,{bold:!0,color:"white",children:"/"}),t(r,{dimColor:!0,children:" Search / filter"})]})]}),i(c,{flexDirection:"column",marginBottom:1,children:[i(c,{marginBottom:1,children:[t(r,{dimColor:!0,children:"── "}),t(r,{bold:!0,color:"white",children:"LISTS (Ports, Mounts, Env)"})]}),i(r,{children:[" ",t(r,{bold:!0,color:"white",children:"a"}),t(r,{dimColor:!0,children:" Add new entry"})]}),i(r,{children:[" ",t(r,{bold:!0,color:"white",children:"d"}),t(r,{dimColor:!0,children:" Delete selected entry"})]})]}),i(c,{flexDirection:"column",children:[i(c,{marginBottom:1,children:[t(r,{dimColor:!0,children:"── "}),t(r,{bold:!0,color:"white",children:"ACTIONS"})]}),i(r,{children:[" ",t(r,{bold:!0,color:"white",children:"s"}),t(r,{dimColor:!0,children:" Save configuration"})]}),i(r,{children:[" ",t(r,{bold:!0,color:"white",children:"q"}),t(r,{dimColor:!0,children:" Quit"})]}),i(r,{children:[" ",t(r,{bold:!0,color:"white",children:"?"}),t(r,{dimColor:!0,children:" Toggle help"})]})]})]}),We=t(ir,{focused:S==="preview",hadComments:o.hadComments,jsonPreview:_e,mode:o.mode,scrollRef:F}),we=a>=dr,Xe=we?Math.floor(a*.38):0;return i(c,{flexDirection:"column",height:l,width:a,children:[i(c,{flexShrink:0,gap:1,paddingX:1,children:[t(r,{bold:!0,inverse:!0,children:" VIS "}),i(r,{wrap:"truncate",children:[o.mode==="create"?"Create":"Edit"," ","devcontainer"]})]}),t(c,{flexShrink:0,paddingX:1,paddingY:1,children:t(et,{defaultValue:o.section,keyMap:{useNumbers:!1,useTab:!1},onChange:y(h=>{e.setSection(h),P("editor")},"onChange"),showIndex:!1,children:Pe.map(({id:h,label:m})=>t(tt,{name:h,children:m},h))})}),t(c,{flexShrink:0,paddingRight:2,children:t(r,{dimColor:!0,wrap:"truncate",children:Pe.find(h=>h.id===o.section)?.description??""})}),i(c,{flexDirection:"row",flexGrow:1,overflow:"hidden",children:[t(c,{borderColor:S==="editor"?"white":"gray",borderStyle:"single",flexDirection:"column",flexGrow:1,overflow:"hidden",children:E}),we&&t(c,{flexShrink:0,width:Xe,children:We})]}),$e,t(ot,{autoExitSeconds:3,onCancel:y(()=>{p(!1)},"onCancel"),visible:f}),Ue]})},"VisDevcontainerApp");var mr=Object.defineProperty,je=y((n,e)=>mr(n,"name",{value:e,configurable:!0}),"c");const kr=je(async({logger:n,options:e,workspaceRoot:s})=>{if(!s)throw new Error("Could not determine workspace root. Run this command inside a monorepo or project directory.");const a=s,l=e.template,o=e.output,d=!!process.stdout.isTTY&&!it;let g=null;try{g=nt(a).name}catch{}const f=pt(a);let p=f?.config??null;const x=f?.hadComments??!1;if(l&&!f){const S=z.find(P=>P.id===l);if(!S){const P=z.map(Y=>Y.id).join(", ");throw new Error(`Unknown template "${l}". Valid templates: ${P}`)}p=S.config}if(!d){p?n.info(JSON.stringify(p,null,2)):(n.error("No existing devcontainer.json found. Use --template to generate one in non-TTY mode."),process.exitCode=1);return}process.stdin.isTTY&&typeof process.stdin.setRawMode=="function"&&(process.stdin.setRawMode(!0),process.stdin.ref(),process.stdin.resume());const v=setInterval(()=>{},1e3),w=new kt(p,x,g);l&&!f&&w.dismissTemplateSelector();let k=null;if(await rt(ct.createElement(hr,{onSave:je(S=>{vt(a,S,o),k=S},"onSave"),store:w}),{alternateScreen:!0,exitOnCtrlC:!1,interactive:!0,patchConsole:!0}).waitUntilExit(),clearInterval(v),k){const S=o??".devcontainer/devcontainer.json";n.info(`DevContainer config saved to ${S}`)}},"execute");export{kr as default};
|