@westbayberry/dg 2.3.2 → 2.3.4
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/README.md +95 -208
- package/dist/agents/copilot-cli.js +7 -2
- package/dist/agents/persistence.js +83 -8
- package/dist/agents/registry.js +5 -2
- package/dist/agents/routing.js +31 -14
- package/dist/api/analyze.js +138 -41
- package/dist/audit/deep.js +2 -1
- package/dist/audit/detectors.js +23 -2
- package/dist/audit/rules.js +4 -1
- package/dist/audit-ui/export.js +5 -4
- package/dist/auth/device-login.js +33 -11
- package/dist/auth/login-app.js +17 -12
- package/dist/auth/store.js +65 -9
- package/dist/bin/dg.js +1 -1
- package/dist/commands/audit.js +12 -20
- package/dist/commands/{guard-commit.js → commit-guard.js} +12 -12
- package/dist/commands/cooldown.js +2 -1
- package/dist/commands/decisions.js +8 -7
- package/dist/commands/help.js +11 -3
- package/dist/commands/licenses.js +17 -8
- package/dist/commands/login.js +58 -21
- package/dist/commands/router.js +12 -2
- package/dist/commands/sbom.js +20 -5
- package/dist/commands/scan.js +5 -4
- package/dist/commands/service.js +1 -1
- package/dist/commands/setup.js +12 -12
- package/dist/commands/status.js +4 -4
- package/dist/commands/uninstall.js +1 -1
- package/dist/commands/update.js +20 -0
- package/dist/commands/verify.js +4 -4
- package/dist/config/settings.js +81 -12
- package/dist/export-ui/ExportDialog.js +1 -1
- package/dist/gate/cooldown-request.js +22 -0
- package/dist/gate/egress-guard.js +185 -0
- package/dist/gate/host-match.js +48 -0
- package/dist/gate/verdict-core.js +101 -0
- package/dist/gate/verdict-fetch.js +214 -0
- package/dist/launcher/agent-check.js +1096 -42
- package/dist/launcher/agent-hook-exec.js +8 -1
- package/dist/launcher/agent-hook-io.js +5 -3
- package/dist/launcher/classify.js +110 -26
- package/dist/launcher/env.js +84 -18
- package/dist/launcher/install-preflight.js +26 -3
- package/dist/launcher/live-install.js +4 -2
- package/dist/launcher/manifest-screen.js +88 -7
- package/dist/launcher/output-redaction.js +153 -15
- package/dist/launcher/preflight-prompt.js +1 -1
- package/dist/launcher/resolve-real-binary.js +8 -3
- package/dist/launcher/run.js +237 -40
- package/dist/origin/artifact-cache.js +55 -0
- package/dist/origin/cargo-config.js +12 -0
- package/dist/origin/gate-mode.js +7 -0
- package/dist/origin/platform-paths.js +14 -0
- package/dist/origin/precedence.js +345 -0
- package/dist/origin/server.js +611 -0
- package/dist/origin/spike-worker.js +35 -0
- package/dist/origin/worker.js +116 -0
- package/dist/presentation/package-page.js +21 -1
- package/dist/presentation/safe-version.js +11 -0
- package/dist/presentation/sarif.js +109 -0
- package/dist/project/dgfile.js +54 -17
- package/dist/project/override-trust.js +0 -0
- package/dist/proxy/enforcement.js +27 -7
- package/dist/proxy/metadata-map.js +33 -7
- package/dist/proxy/preverified.js +0 -3
- package/dist/proxy/server.js +104 -446
- package/dist/proxy/upstream-proxy.js +23 -4
- package/dist/proxy/worker.js +3 -2
- package/dist/publish-set/collect.js +6 -0
- package/dist/runtime/fatal.js +2 -1
- package/dist/runtime/first-run.js +5 -1
- package/dist/runtime/node-version.js +51 -0
- package/dist/runtime/nudges.js +27 -1
- package/dist/sbom/cyclonedx.js +120 -33
- package/dist/sbom/graph.js +236 -0
- package/dist/sbom/spdx.js +53 -0
- package/dist/sbom-ui/SbomApp.js +5 -3
- package/dist/sbom-ui/inventory.js +8 -10
- package/dist/scan/analyze-worker.js +3 -1
- package/dist/scan/collect.js +38 -8
- package/dist/scan/command.js +77 -27
- package/dist/scan/discovery.js +18 -7
- package/dist/scan/installed.js +202 -0
- package/dist/scan/manifest-coverage.js +128 -0
- package/dist/scan/render.js +125 -40
- package/dist/scan/scanner-report.js +92 -22
- package/dist/scan/staged.js +68 -15
- package/dist/scan/types.js +7 -1
- package/dist/scan-ui/LegacyApp.js +3 -3
- package/dist/scan-ui/components/InteractiveResultsView.js +17 -14
- package/dist/scan-ui/hooks/useScan.js +37 -11
- package/dist/scan-ui/launch.js +2 -2
- package/dist/scan-ui/logo.js +6 -2
- package/dist/scan-ui/shims.js +6 -6
- package/dist/scripts/gate.js +53 -35
- package/dist/security/csv.js +9 -0
- package/dist/security/sanitize.js +11 -2
- package/dist/service/state.js +120 -37
- package/dist/service/trust-refresh.js +12 -1
- package/dist/service/trust-store.js +36 -5
- package/dist/service/worker.js +5 -7
- package/dist/setup/git-hook.js +2 -2
- package/dist/setup/install-method.js +36 -0
- package/dist/setup/plan.js +119 -57
- package/dist/setup/uninstall-standalone.js +7 -2
- package/dist/setup-ui/gate.js +1 -8
- package/dist/setup-ui/offer.js +14 -7
- package/dist/setup-ui/selector.js +1 -1
- package/dist/setup-ui/wizard.js +73 -78
- package/dist/standalone/uninstall.mjs +1114 -386
- package/dist/state/cleanup-registry.js +14 -3
- package/dist/state/cooldown-held.js +36 -13
- package/dist/state/locks.js +239 -34
- package/dist/state/sessions.js +2 -2
- package/dist/util/sh-escape.js +6 -0
- package/dist/util/tty-prompt.js +13 -4
- package/dist/verify/local.js +60 -10
- package/dist/verify/package-check.js +35 -49
- package/dist/verify/preflight.js +59 -33
- package/dist/verify/render.js +11 -12
- package/npm-shrinkwrap.json +714 -213
- package/package.json +4 -2
package/dist/setup-ui/wizard.js
CHANGED
|
@@ -16,7 +16,7 @@ import { createTheme } from "../presentation/theme.js";
|
|
|
16
16
|
import { activateShell, activationOffer } from "../setup/activate-shell.js";
|
|
17
17
|
import { commitGuardOffer } from "../setup/git-hook.js";
|
|
18
18
|
import { activationCommand, tildifyPath } from "../setup/plan.js";
|
|
19
|
-
import { markSecurityNotesShown,
|
|
19
|
+
import { markSecurityNotesShown, securityNotesShown } from "./gate.js";
|
|
20
20
|
import { Selector } from "./selector.js";
|
|
21
21
|
import { buildCommitGuardTask, buildWizardTasks } from "./tasks.js";
|
|
22
22
|
export function protectsSentence(agentLabels) {
|
|
@@ -30,31 +30,30 @@ export function protectsSentence(agentLabels) {
|
|
|
30
30
|
}
|
|
31
31
|
const Header = ({ version }) => (_jsxs(Box, { flexDirection: "column", children: [renderLogo("pass").map((line, i) => (_jsx(Text, { children: line }, i))), _jsx(Text, { children: " " }), _jsxs(Text, { bold: true, children: ["Dependency Guardian v", version] }), _jsx(Text, { children: " " })] }));
|
|
32
32
|
const LoginFlow = ({ webBase, env, onDone }) => {
|
|
33
|
-
const { state,
|
|
34
|
-
useEffect(() => {
|
|
35
|
-
if (state.phase === "ready") {
|
|
36
|
-
openAndPoll();
|
|
37
|
-
}
|
|
38
|
-
}, [state.phase, openAndPoll]);
|
|
33
|
+
const { state, openInBrowser } = useLogin(webBase, env);
|
|
39
34
|
useInput((_input, key) => {
|
|
40
|
-
if (key.
|
|
41
|
-
onDone(state.phase === "success" ? state.email : "");
|
|
42
|
-
}
|
|
43
|
-
else if (key.escape) {
|
|
35
|
+
if (key.escape) {
|
|
44
36
|
onDone("");
|
|
45
37
|
}
|
|
38
|
+
else if (key.return) {
|
|
39
|
+
if (state.phase === "ready") {
|
|
40
|
+
openInBrowser();
|
|
41
|
+
}
|
|
42
|
+
else if (state.phase === "success" || state.phase === "expired" || state.phase === "error") {
|
|
43
|
+
onDone(state.phase === "success" ? state.email : "");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
46
|
});
|
|
47
47
|
switch (state.phase) {
|
|
48
48
|
case "creating":
|
|
49
49
|
return _jsx(Spinner, { label: "Creating login session\u2026" });
|
|
50
50
|
case "ready":
|
|
51
|
-
|
|
52
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: "Sign in at:" }), _jsx(Text, { color: "cyan", children: state.verifyUrl }), _jsx(Text, { children: " " }), _jsx(Spinner, { label: "Waiting for you to approve in the browser\u2026" }), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "Esc to skip" })] }));
|
|
51
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: "Sign in at:" }), _jsx(Text, { color: "cyan", children: state.verifyUrl }), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "Press Enter to open it in your browser, or open the link yourself." }), _jsx(Text, { children: " " }), _jsx(Spinner, { label: "Waiting for you to approve\u2026" }), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "Esc to cancel setup" })] }));
|
|
53
52
|
case "success":
|
|
54
53
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "green", bold: true, children: ["\u2713 Logged in", state.email ? ` as ${state.email}` : "", state.plan ? ` (${displayTier(state.plan)} plan)` : ""] }), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "Login successful. Press Enter to continue\u2026" })] }));
|
|
55
54
|
case "expired":
|
|
56
55
|
case "error":
|
|
57
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "yellow", children: state.phase === "expired" ? "That login link expired." : `Login failed: ${state.message}.` }), _jsx(Text, { dimColor: true, children: "
|
|
56
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "yellow", children: state.phase === "expired" ? "That login link expired." : `Login failed: ${state.message}.` }), _jsx(Text, { dimColor: true, children: "Run dg login any time. Press Enter to exit setup\u2026" })] }));
|
|
58
57
|
}
|
|
59
58
|
};
|
|
60
59
|
function hyperlink(label, url) {
|
|
@@ -70,7 +69,7 @@ const NotesStep = ({ termsUrl, privacyUrl, onAgree }) => {
|
|
|
70
69
|
};
|
|
71
70
|
export const SetupWizardApp = (props) => {
|
|
72
71
|
const { exit } = useApp();
|
|
73
|
-
const [step, setStep] = useState(props.
|
|
72
|
+
const [step, setStep] = useState(props.notesNeeded ? "notes" : props.loginNeeded ? "login" : "choose");
|
|
74
73
|
const [results, setResults] = useState([]);
|
|
75
74
|
const [running, setRunning] = useState(null);
|
|
76
75
|
const [fatalDetail, setFatalDetail] = useState("");
|
|
@@ -79,7 +78,7 @@ export const SetupWizardApp = (props) => {
|
|
|
79
78
|
const applyStarted = useRef(false);
|
|
80
79
|
const Login = props.LoginFlowComponent ?? LoginFlow;
|
|
81
80
|
const afterLogin = () => {
|
|
82
|
-
setStep(
|
|
81
|
+
setStep("choose");
|
|
83
82
|
};
|
|
84
83
|
useInput((_input, key) => {
|
|
85
84
|
if (key.return) {
|
|
@@ -136,30 +135,25 @@ export const SetupWizardApp = (props) => {
|
|
|
136
135
|
cancelled = true;
|
|
137
136
|
};
|
|
138
137
|
}, [step, props]);
|
|
139
|
-
return (_jsxs(Box, { flexDirection: "column", paddingLeft: 1, paddingTop: 1, children: [_jsx(Header, { version: props.version }), step === "login" && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Connect your account" }), _jsx(Text, { children: " " }), _jsx(Selector, { options: [{ label: "Connect in your browser" },
|
|
140
|
-
if (index === 0) {
|
|
141
|
-
setStep("login-flow");
|
|
142
|
-
}
|
|
143
|
-
else {
|
|
144
|
-
props.onLoginSkipped();
|
|
145
|
-
afterLogin();
|
|
146
|
-
}
|
|
147
|
-
}, onCancel: () => {
|
|
138
|
+
return (_jsxs(Box, { flexDirection: "column", paddingLeft: 1, paddingTop: 1, children: [_jsx(Header, { version: props.version }), step === "login" && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Connect your account" }), _jsx(Text, { dimColor: true, children: "Sign in to use dg. Esc cancels setup." }), _jsx(Text, { children: " " }), _jsx(Selector, { options: [{ label: "Connect in your browser" }], onSelect: () => setStep("login-flow"), onCancel: () => {
|
|
148
139
|
props.onLoginSkipped();
|
|
149
|
-
|
|
140
|
+
props.onOutcome({ kind: "declined" });
|
|
141
|
+
setStep("declined");
|
|
150
142
|
} })] })), step === "login-flow" && (_jsx(Login, { webBase: props.webBase, env: props.env, onDone: (loggedInEmail) => {
|
|
151
143
|
if (loggedInEmail) {
|
|
152
144
|
setEmail(loggedInEmail);
|
|
153
145
|
emailRef.current = loggedInEmail;
|
|
146
|
+
afterLogin();
|
|
154
147
|
}
|
|
155
148
|
else {
|
|
156
149
|
props.onLoginSkipped();
|
|
150
|
+
props.onOutcome({ kind: "declined" });
|
|
151
|
+
setStep("declined");
|
|
157
152
|
}
|
|
158
|
-
afterLogin();
|
|
159
153
|
} })), step === "notes" && (_jsx(NotesStep, { termsUrl: `${props.webBase}/terms`, privacyUrl: `${props.webBase}/privacy`, onAgree: () => {
|
|
160
154
|
props.onNotesAgreed();
|
|
161
|
-
setStep("choose");
|
|
162
|
-
} })), step === "choose" && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Use recommended settings?" }), _jsx(Text, { children: " " }), _jsx(Text, { children: props.sentence }), props.guardsRepo && _jsx(Text, { children: "Also scans this repo's commits before they land." }), props.skips.map((skip) => (_jsxs(Text, { dimColor: true, children: [skip.label, " found but skipped \u2014 ", skip.detail] }, skip.label))), _jsx(Text, { children: " " }), _jsx(Selector, { options: [{ label: "Yes, use recommended settings" }, { label: "
|
|
155
|
+
setStep(props.loginNeeded ? "login" : "choose");
|
|
156
|
+
} })), step === "choose" && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Use recommended settings?" }), _jsx(Text, { children: " " }), _jsx(Text, { children: props.sentence }), props.guardsRepo && _jsx(Text, { children: "Also scans this repo's commits before they land." }), props.skips.map((skip) => (_jsxs(Text, { dimColor: true, children: [skip.label, " found but skipped \u2014 ", skip.detail] }, skip.label))), _jsx(Text, { children: " " }), _jsx(Selector, { options: [{ label: "Yes, use recommended settings" }, { label: "Not now — exit setup" }], onSelect: (index) => {
|
|
163
157
|
if (index === 0) {
|
|
164
158
|
setStep("applying");
|
|
165
159
|
}
|
|
@@ -170,7 +164,7 @@ export const SetupWizardApp = (props) => {
|
|
|
170
164
|
}, onCancel: () => {
|
|
171
165
|
props.onOutcome({ kind: "declined" });
|
|
172
166
|
setStep("declined");
|
|
173
|
-
} })] })), (step === "applying" || step === "done" || step === "fatal") && (_jsxs(Box, { flexDirection: "column", children: [results.map((line) => (_jsxs(Text, { color: line.ok ? "green" : "yellow", children: [line.ok ? "✓" : "✗", " ", line.label, !line.ok && line.detail ? ` — ${line.detail}` : ""] }, line.label))), running !== null && _jsx(Spinner, { label: running }), step === "fatal" && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: " " }), _jsx(Text, { color: "red", children: fatalDetail })] })), step === "done" && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: " " }), _jsxs(Text, { children: [_jsx(Text, { color: "green", bold: true, children: "\u2713 Setup complete." }), " ", "Activate now: ", _jsx(Text, { bold: true, children: props.activateHint })] }), email ? _jsxs(Text, { dimColor: true, children: ["Logged in as ", email] }) : null, _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "Press Enter to continue\u2026" })] }))] })), step === "declined" && _jsx(Text, { dimColor: true, children: "
|
|
167
|
+
} })] })), (step === "applying" || step === "done" || step === "fatal") && (_jsxs(Box, { flexDirection: "column", children: [results.map((line) => (_jsxs(Text, { color: line.ok ? "green" : "yellow", children: [line.ok ? "✓" : "✗", " ", line.label, !line.ok && line.detail ? ` — ${line.detail}` : ""] }, line.label))), running !== null && _jsx(Spinner, { label: running }), step === "fatal" && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: " " }), _jsx(Text, { color: "red", children: fatalDetail })] })), step === "done" && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: " " }), _jsxs(Text, { children: [_jsx(Text, { color: "green", bold: true, children: "\u2713 Setup complete." }), " ", "Activate now: ", _jsx(Text, { bold: true, children: props.activateHint })] }), email ? _jsxs(Text, { dimColor: true, children: ["Logged in as ", email] }) : null, _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "Press Enter to continue\u2026" })] }))] })), step === "declined" && _jsx(Text, { dimColor: true, children: "Setup not finished \u2014 dg is unavailable until setup completes." })] }));
|
|
174
168
|
};
|
|
175
169
|
export async function runSetupWizard(plan, options = {}) {
|
|
176
170
|
const env = options.env ?? process.env;
|
|
@@ -179,55 +173,56 @@ export async function runSetupWizard(plan, options = {}) {
|
|
|
179
173
|
if (restoreCi) {
|
|
180
174
|
delete process.env.CI;
|
|
181
175
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
markWizardSkipped(env);
|
|
176
|
+
try {
|
|
177
|
+
const agents = collectAgentOffers();
|
|
178
|
+
const repo = commitGuardOffer();
|
|
179
|
+
const auth = authStatus(env);
|
|
180
|
+
const state = { outcome: null };
|
|
181
|
+
const tasks = buildWizardTasks(plan, agents);
|
|
182
|
+
if (repo) {
|
|
183
|
+
tasks.push(buildCommitGuardTask(repo));
|
|
184
|
+
}
|
|
185
|
+
const { render } = await import("ink");
|
|
186
|
+
enterTui();
|
|
187
|
+
const instance = render(_jsx(SetupWizardApp, { version: dgVersion(), notesNeeded: !securityNotesShown(env), loginNeeded: !auth.authenticated, authedEmail: auth.email ?? "", sentence: protectsSentence(agents.map((offer) => offer.label)), guardsRepo: repo !== null, skips: collectAgentSkips(), tasks: tasks, webBase: resolveWebBase(env), env: env, activateHint: activationCommand(plan.shell, tildifyPath(plan.rcPath)), onNotesAgreed: () => markSecurityNotesShown(env), onLoginSkipped: () => recordLoginNudge(env), onOutcome: (outcome) => {
|
|
188
|
+
state.outcome = outcome;
|
|
189
|
+
} }), { exitOnCtrlC: true });
|
|
190
|
+
await instance.waitUntilExit();
|
|
191
|
+
leaveTui();
|
|
192
|
+
const theme = createTheme(resolvePresentation().color);
|
|
193
|
+
const accent = (text) => theme.paint("accent", text);
|
|
194
|
+
const muted = (text) => theme.paint("muted", text);
|
|
195
|
+
const outcome = state.outcome;
|
|
196
|
+
if (outcome === null || outcome.kind === "declined") {
|
|
197
|
+
return {
|
|
198
|
+
exitCode: 1,
|
|
199
|
+
stdout: "",
|
|
200
|
+
stderr: ` ${muted("Setup not finished — dg is unavailable until setup completes. Run")} ${accent("dg setup")} ${muted("to finish.")}\n`
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
if (outcome.kind === "fatal") {
|
|
204
|
+
return { exitCode: 1, stdout: "", stderr: ` ${outcome.detail}\n` };
|
|
205
|
+
}
|
|
213
206
|
markFirstRunShown(env);
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
207
|
+
const lines = outcome.results.map((line) => line.ok
|
|
208
|
+
? ` ${theme.paint("pass", `✓ ${line.label}`)}`
|
|
209
|
+
: ` ${theme.paint("warn", `✗ ${line.label}`)}${line.detail ? ` ${muted(`— ${line.detail}`)}` : ""}`);
|
|
210
|
+
lines.push(` ${theme.paint("pass", "✓ dg setup complete — installs are protected in new terminals.")}`);
|
|
211
|
+
if (outcome.email) {
|
|
212
|
+
lines.push(` ${muted(`Logged in as ${outcome.email}`)}`);
|
|
213
|
+
}
|
|
214
|
+
lines.push(` ${muted("Next: run")} ${accent("dg scan")} ${muted("in a project for a dependency-risk overview")}`);
|
|
215
|
+
lines.push(` ${muted("Docs:")} ${accent(`${resolveWebBase(env)}/docs`)}`);
|
|
216
|
+
if (options.autoActivate && activationOffer() === "prompt") {
|
|
217
|
+
process.stderr.write(`${lines.join("\n")}\n ${muted("Starting a protected shell — type")} ${accent("exit")} ${muted("to return to your previous one.")}\n`);
|
|
218
|
+
return { exitCode: activateShell(), stdout: "", stderr: "" };
|
|
219
|
+
}
|
|
220
|
+
lines.push(` ${muted("Activate now:")} ${accent(activationCommand(plan.shell, tildifyPath(plan.rcPath)))}`);
|
|
221
|
+
return { exitCode: 0, stdout: "", stderr: `${lines.join("\n")}\n` };
|
|
226
222
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
223
|
+
finally {
|
|
224
|
+
if (restoreCi) {
|
|
225
|
+
process.env.CI = ci;
|
|
226
|
+
}
|
|
230
227
|
}
|
|
231
|
-
lines.push(` ${muted("Activate now:")} ${accent(activationCommand(plan.shell, tildifyPath(plan.rcPath)))}`);
|
|
232
|
-
return { exitCode: 0, stdout: "", stderr: `${lines.join("\n")}\n` };
|
|
233
228
|
}
|