@yagni-app/code-staging 1.2.13-staging.2190.1 → 1.2.13-staging.2196.1
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/dist/cli.js +8 -0
- package/dist/env.d.ts +116 -0
- package/dist/env.js +332 -0
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -32,6 +32,7 @@ import { buildLaunch } from "./launch.js";
|
|
|
32
32
|
import { resolveOtelLaunchWithWorkspace } from "./otel.js";
|
|
33
33
|
import { parseOutputFormat, parseJsonEvents, buildResultObject, readGuardianEvents, } from "./outputFormat.js";
|
|
34
34
|
import { feedbackCommand } from "./feedback.js";
|
|
35
|
+
import { envCommand } from "./env.js";
|
|
35
36
|
import { runDoctor } from "./doctor.js";
|
|
36
37
|
import { installProcessCrashHandlers } from "./crashReport.js";
|
|
37
38
|
import { currentCliVersion, maybeNudgeAndRefresh, upgradeCommand } from "./upgrade.js";
|
|
@@ -705,6 +706,10 @@ export const HELP_TEXT = [
|
|
|
705
706
|
" add, install, uninstall, update, list — run",
|
|
706
707
|
" `yagni plugin` for details. Skills, commands,",
|
|
707
708
|
" agents, and bundled MCP servers load on launch.",
|
|
709
|
+
" yagni env check [--repo owner/name] Validate the repository's environment",
|
|
710
|
+
" as it stands, without repair, printing each step.",
|
|
711
|
+
" The repository defaults to the origin remote.",
|
|
712
|
+
" Exits 0 ready, 1 needs attention, 2 request error.",
|
|
708
713
|
" yagni token Output the active environment's API token (for helpers).",
|
|
709
714
|
" yagni use <name> Switch the active environment (sticky).",
|
|
710
715
|
" Presets: prod, local. Others need --base-url <url>.",
|
|
@@ -866,6 +871,9 @@ export async function main(argv) {
|
|
|
866
871
|
if (command === "feedback") {
|
|
867
872
|
return feedbackCommand(rest, {}, cliVersion());
|
|
868
873
|
}
|
|
874
|
+
if (command === "env") {
|
|
875
|
+
return envCommand(rest, {}, cliVersion());
|
|
876
|
+
}
|
|
869
877
|
if (command === "token") {
|
|
870
878
|
return tokenCommand({}, rest);
|
|
871
879
|
}
|
package/dist/env.d.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `yagni env check [--repo owner/name]` (YAG-1109): validate a repository's
|
|
3
|
+
* environment as it stands, without repair, and print each step as it runs.
|
|
4
|
+
*
|
|
5
|
+
* The repository defaults to the current directory's `origin` remote. The
|
|
6
|
+
* command starts the check (`POST /api/yagni-code/environment/check`), then
|
|
7
|
+
* polls the run (`GET /api/yagni-code/environment/runs/:revisionId`) every
|
|
8
|
+
* 3 s. Exit codes: 0 ready, 1 needs attention, 2 a request or usage error.
|
|
9
|
+
*
|
|
10
|
+
* The remote parser, the step formatter and the poll loop are pure, with
|
|
11
|
+
* `fetch` and `sleep` injected, so they test without a network.
|
|
12
|
+
*/
|
|
13
|
+
export type EnvRunStatus = "validating" | "ready" | "needs_attention";
|
|
14
|
+
export type EnvStepState = "pending" | "running" | "done" | "failed" | "skipped";
|
|
15
|
+
/** One step of the run, as the server returns it (`QaRunStep`). */
|
|
16
|
+
export interface EnvRunStep {
|
|
17
|
+
index: number;
|
|
18
|
+
kind?: string;
|
|
19
|
+
label: string;
|
|
20
|
+
path?: string;
|
|
21
|
+
line?: number;
|
|
22
|
+
state: EnvStepState;
|
|
23
|
+
durationMs?: number;
|
|
24
|
+
exitCode?: number;
|
|
25
|
+
outputTail?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface EnvRun {
|
|
28
|
+
status: EnvRunStatus;
|
|
29
|
+
message: string;
|
|
30
|
+
steps: EnvRunStep[];
|
|
31
|
+
notes: string[];
|
|
32
|
+
outputTail?: string;
|
|
33
|
+
/** The declared system the run validated in, or why it ran alone. */
|
|
34
|
+
system?: EnvRunSystem;
|
|
35
|
+
}
|
|
36
|
+
export interface EnvRunSystem {
|
|
37
|
+
name: string;
|
|
38
|
+
applied?: boolean;
|
|
39
|
+
reason?: "not_member" | "unreadable" | "missing";
|
|
40
|
+
}
|
|
41
|
+
/** `in system gpx`, or `ran alone: <why>` when the system was not applied. */
|
|
42
|
+
export declare function formatSystem(system: EnvRunSystem): string;
|
|
43
|
+
export declare const ENV_POLL_INTERVAL_MS = 3000;
|
|
44
|
+
export declare const ENV_USAGE: string;
|
|
45
|
+
/**
|
|
46
|
+
* `owner/name` from a git remote URL: https (`https://github.com/o/n.git`),
|
|
47
|
+
* ssh (`ssh://git@github.com/o/n`) or scp-like (`git@github.com:o/n.git`).
|
|
48
|
+
* The `.git` suffix and a trailing slash are optional. Undefined otherwise.
|
|
49
|
+
*/
|
|
50
|
+
export declare function parseRemoteRepo(url: string): string | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* A remote URL safe to print: the username and password (a token, often)
|
|
53
|
+
* are dropped from a URL remote, and the user from an scp-like one.
|
|
54
|
+
*/
|
|
55
|
+
export declare function redactRemote(url: string): string;
|
|
56
|
+
/** A validated `owner/name` given on the command line, `.git` allowed. */
|
|
57
|
+
export declare function parseRepoArg(raw: string): string | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* A step's time as a person reads it: `41 s`, `11 m 20 s`, `2 h 5 m`. Mirrors
|
|
60
|
+
* `qaStepDuration` in `@yagni/shared` (`packages/shared/src/qa.ts`), which this
|
|
61
|
+
* self-contained package cannot import; keep the two in step.
|
|
62
|
+
*/
|
|
63
|
+
export declare function formatDuration(ms: number): string;
|
|
64
|
+
/**
|
|
65
|
+
* The lines one step prints when it reaches a state. `pending` prints
|
|
66
|
+
* nothing; a failed step prints its output tail, indented.
|
|
67
|
+
*/
|
|
68
|
+
export declare function formatStep(step: EnvRunStep): string[];
|
|
69
|
+
/**
|
|
70
|
+
* The lines this read adds: every step whose state moved since the last
|
|
71
|
+
* read. `seen` is updated in place.
|
|
72
|
+
*/
|
|
73
|
+
export declare function stepTransitions(steps: EnvRunStep[], seen: Map<number, EnvStepState>): string[];
|
|
74
|
+
export interface EnvRequest {
|
|
75
|
+
baseUrl: string;
|
|
76
|
+
token: string;
|
|
77
|
+
fetchImpl: typeof fetch;
|
|
78
|
+
}
|
|
79
|
+
/** Start the check: the run's revision id, or the reason it did not start. */
|
|
80
|
+
export declare function startCheck(req: EnvRequest, repo: string): Promise<{
|
|
81
|
+
revisionId: string;
|
|
82
|
+
} | {
|
|
83
|
+
error: string;
|
|
84
|
+
}>;
|
|
85
|
+
export interface PollOptions extends EnvRequest {
|
|
86
|
+
revisionId: string;
|
|
87
|
+
sleep: (ms: number) => Promise<void>;
|
|
88
|
+
writeOut: (line: string) => void;
|
|
89
|
+
writeErr: (line: string) => void;
|
|
90
|
+
intervalMs?: number;
|
|
91
|
+
}
|
|
92
|
+
/** Poll the run until it settles; the command's exit code. */
|
|
93
|
+
export declare function pollRun(opts: PollOptions): Promise<number>;
|
|
94
|
+
export interface EnvDeps {
|
|
95
|
+
loadCredentials?: () => Promise<{
|
|
96
|
+
token?: string;
|
|
97
|
+
baseUrl: string;
|
|
98
|
+
name: string;
|
|
99
|
+
}>;
|
|
100
|
+
fetchImpl?: typeof fetch;
|
|
101
|
+
sleep?: (ms: number) => Promise<void>;
|
|
102
|
+
/** The current directory's `origin` remote URL, or undefined. */
|
|
103
|
+
originUrl?: (cwd: string) => Promise<string | undefined>;
|
|
104
|
+
cwd?: string;
|
|
105
|
+
env?: NodeJS.ProcessEnv;
|
|
106
|
+
writeOut?: (line: string) => void;
|
|
107
|
+
writeErr?: (line: string) => void;
|
|
108
|
+
}
|
|
109
|
+
/** `--repo owner/name` or `--repo=owner/name`; `error` names a bad argument. */
|
|
110
|
+
export declare function parseEnvCheckArgs(args: string[]): {
|
|
111
|
+
repo?: string;
|
|
112
|
+
error?: string;
|
|
113
|
+
};
|
|
114
|
+
/** `yagni env <subcommand>`; only `check` exists. */
|
|
115
|
+
export declare function envCommand(args: string[], deps?: EnvDeps, _cliVersion?: string): Promise<number>;
|
|
116
|
+
//# sourceMappingURL=env.d.ts.map
|
package/dist/env.js
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `yagni env check [--repo owner/name]` (YAG-1109): validate a repository's
|
|
3
|
+
* environment as it stands, without repair, and print each step as it runs.
|
|
4
|
+
*
|
|
5
|
+
* The repository defaults to the current directory's `origin` remote. The
|
|
6
|
+
* command starts the check (`POST /api/yagni-code/environment/check`), then
|
|
7
|
+
* polls the run (`GET /api/yagni-code/environment/runs/:revisionId`) every
|
|
8
|
+
* 3 s. Exit codes: 0 ready, 1 needs attention, 2 a request or usage error.
|
|
9
|
+
*
|
|
10
|
+
* The remote parser, the step formatter and the poll loop are pure, with
|
|
11
|
+
* `fetch` and `sleep` injected, so they test without a network.
|
|
12
|
+
*/
|
|
13
|
+
import { execFile } from "node:child_process";
|
|
14
|
+
import { credentialsFromProfile, readActiveProfile } from "./profiles.js";
|
|
15
|
+
/** `in system gpx`, or `ran alone: <why>` when the system was not applied. */
|
|
16
|
+
export function formatSystem(system) {
|
|
17
|
+
if (system.applied !== false)
|
|
18
|
+
return `in system ${system.name}`;
|
|
19
|
+
const why = system.reason === "not_member"
|
|
20
|
+
? `not a member of system ${system.name}`
|
|
21
|
+
: system.reason === "unreadable"
|
|
22
|
+
? `system ${system.name} could not be read`
|
|
23
|
+
: system.reason === "missing"
|
|
24
|
+
? `system ${system.name} no longer exists`
|
|
25
|
+
: `system ${system.name} was not applied`;
|
|
26
|
+
return `ran alone: ${why}`;
|
|
27
|
+
}
|
|
28
|
+
export const ENV_POLL_INTERVAL_MS = 3_000;
|
|
29
|
+
/** Consecutive failed reads of the run before the command gives up. */
|
|
30
|
+
const MAX_READ_FAILURES = 3;
|
|
31
|
+
export const ENV_USAGE = [
|
|
32
|
+
"Usage: yagni env check [--repo owner/name]",
|
|
33
|
+
"",
|
|
34
|
+
"Validates the repository's environment as it stands, without repair.",
|
|
35
|
+
"The repository defaults to this directory's origin remote.",
|
|
36
|
+
"Exits 0 when ready, 1 when it needs attention, 2 on a request error.",
|
|
37
|
+
].join("\n");
|
|
38
|
+
/**
|
|
39
|
+
* `owner/name` from a git remote URL: https (`https://github.com/o/n.git`),
|
|
40
|
+
* ssh (`ssh://git@github.com/o/n`) or scp-like (`git@github.com:o/n.git`).
|
|
41
|
+
* The `.git` suffix and a trailing slash are optional. Undefined otherwise.
|
|
42
|
+
*/
|
|
43
|
+
export function parseRemoteRepo(url) {
|
|
44
|
+
const trimmed = url
|
|
45
|
+
.trim()
|
|
46
|
+
.replace(/\/+$/, "")
|
|
47
|
+
.replace(/\.git$/, "");
|
|
48
|
+
let path;
|
|
49
|
+
if (trimmed.includes("://")) {
|
|
50
|
+
try {
|
|
51
|
+
path = new URL(trimmed).pathname;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
const scp = /^(?:[^@/\s]+@)?[^:/\s]+:(.+)$/.exec(trimmed);
|
|
59
|
+
if (!scp)
|
|
60
|
+
return undefined;
|
|
61
|
+
path = scp[1];
|
|
62
|
+
}
|
|
63
|
+
// Exactly owner and name: a longer path (an Azure DevOps `_git` path, a
|
|
64
|
+
// nested group) is never guessed at.
|
|
65
|
+
const segments = path.split("/").filter(Boolean);
|
|
66
|
+
if (segments.length !== 2)
|
|
67
|
+
return undefined;
|
|
68
|
+
const [owner, name] = segments;
|
|
69
|
+
const part = /^[A-Za-z0-9._-]+$/;
|
|
70
|
+
return part.test(owner) && part.test(name) ? `${owner}/${name}` : undefined;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* A remote URL safe to print: the username and password (a token, often)
|
|
74
|
+
* are dropped from a URL remote, and the user from an scp-like one.
|
|
75
|
+
*/
|
|
76
|
+
export function redactRemote(url) {
|
|
77
|
+
const trimmed = url.trim();
|
|
78
|
+
if (trimmed.includes("://")) {
|
|
79
|
+
try {
|
|
80
|
+
const parsed = new URL(trimmed);
|
|
81
|
+
parsed.username = "";
|
|
82
|
+
parsed.password = "";
|
|
83
|
+
return parsed.toString();
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return trimmed.replace(/^([^:/]+:\/\/)[^/]*@/, "$1");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return trimmed.replace(/^[^/\s]*@/, "");
|
|
90
|
+
}
|
|
91
|
+
/** A validated `owner/name` given on the command line, `.git` allowed. */
|
|
92
|
+
export function parseRepoArg(raw) {
|
|
93
|
+
const repo = raw.trim().replace(/\.git$/, "");
|
|
94
|
+
return /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/.test(repo) ? repo : undefined;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* A step's time as a person reads it: `41 s`, `11 m 20 s`, `2 h 5 m`. Mirrors
|
|
98
|
+
* `qaStepDuration` in `@yagni/shared` (`packages/shared/src/qa.ts`), which this
|
|
99
|
+
* self-contained package cannot import; keep the two in step.
|
|
100
|
+
*/
|
|
101
|
+
export function formatDuration(ms) {
|
|
102
|
+
const seconds = Math.max(0, Math.round(ms / 1000));
|
|
103
|
+
if (seconds < 60)
|
|
104
|
+
return `${seconds} s`;
|
|
105
|
+
const minutes = Math.floor(seconds / 60);
|
|
106
|
+
if (minutes < 60)
|
|
107
|
+
return seconds % 60 ? `${minutes} m ${seconds % 60} s` : `${minutes} m`;
|
|
108
|
+
const hours = Math.floor(minutes / 60);
|
|
109
|
+
return minutes % 60 ? `${hours} h ${minutes % 60} m` : `${hours} h`;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* The lines one step prints when it reaches a state. `pending` prints
|
|
113
|
+
* nothing; a failed step prints its output tail, indented.
|
|
114
|
+
*/
|
|
115
|
+
export function formatStep(step) {
|
|
116
|
+
const line = step.line !== undefined ? ` (line ${step.line})` : "";
|
|
117
|
+
const took = step.durationMs !== undefined ? ` ${formatDuration(step.durationMs)}` : "";
|
|
118
|
+
switch (step.state) {
|
|
119
|
+
case "running":
|
|
120
|
+
return [`… ${step.label}`];
|
|
121
|
+
case "done":
|
|
122
|
+
return [`✓ ${step.label}${took}`];
|
|
123
|
+
case "skipped":
|
|
124
|
+
return [`- ${step.label} skipped`];
|
|
125
|
+
case "failed": {
|
|
126
|
+
const tail = (step.outputTail ?? "")
|
|
127
|
+
.split("\n")
|
|
128
|
+
.filter((l) => l.trim().length > 0)
|
|
129
|
+
.map((l) => ` ${l}`);
|
|
130
|
+
return [`✗ ${step.label}${line}${took}`, ...tail];
|
|
131
|
+
}
|
|
132
|
+
default:
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* The lines this read adds: every step whose state moved since the last
|
|
138
|
+
* read. `seen` is updated in place.
|
|
139
|
+
*/
|
|
140
|
+
export function stepTransitions(steps, seen) {
|
|
141
|
+
const out = [];
|
|
142
|
+
for (const step of [...steps].sort((a, b) => a.index - b.index)) {
|
|
143
|
+
if (seen.get(step.index) === step.state)
|
|
144
|
+
continue;
|
|
145
|
+
seen.set(step.index, step.state);
|
|
146
|
+
out.push(...formatStep(step));
|
|
147
|
+
}
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
150
|
+
function apiUrl(baseUrl, path) {
|
|
151
|
+
return `${baseUrl.replace(/\/$/, "")}/api/yagni-code${path}`;
|
|
152
|
+
}
|
|
153
|
+
async function errorText(res) {
|
|
154
|
+
const body = (await res.json().catch(() => ({})));
|
|
155
|
+
const detail = typeof body.error === "string" ? body.error : "";
|
|
156
|
+
if (res.status === 401)
|
|
157
|
+
return "Your login has expired. Run `yagni login` and try again.";
|
|
158
|
+
return detail ? `${detail} (HTTP ${res.status})` : `The server answered HTTP ${res.status}.`;
|
|
159
|
+
}
|
|
160
|
+
/** Start the check: the run's revision id, or the reason it did not start. */
|
|
161
|
+
export async function startCheck(req, repo) {
|
|
162
|
+
try {
|
|
163
|
+
const res = await req.fetchImpl(apiUrl(req.baseUrl, "/environment/check"), {
|
|
164
|
+
method: "POST",
|
|
165
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${req.token}` },
|
|
166
|
+
body: JSON.stringify({ repo }),
|
|
167
|
+
signal: AbortSignal.timeout(60_000),
|
|
168
|
+
});
|
|
169
|
+
if (!res.ok)
|
|
170
|
+
return { error: await errorText(res) };
|
|
171
|
+
const body = (await res.json());
|
|
172
|
+
return typeof body.revisionId === "string"
|
|
173
|
+
? { revisionId: body.revisionId }
|
|
174
|
+
: { error: "The server did not say which run started." };
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
async function readRun(req, revisionId) {
|
|
181
|
+
try {
|
|
182
|
+
const res = await req.fetchImpl(apiUrl(req.baseUrl, `/environment/runs/${encodeURIComponent(revisionId)}`), {
|
|
183
|
+
headers: { authorization: `Bearer ${req.token}` },
|
|
184
|
+
signal: AbortSignal.timeout(30_000),
|
|
185
|
+
});
|
|
186
|
+
if (!res.ok)
|
|
187
|
+
return { error: await errorText(res) };
|
|
188
|
+
const body = (await res.json());
|
|
189
|
+
return {
|
|
190
|
+
status: body.status ?? "validating",
|
|
191
|
+
message: body.message ?? "",
|
|
192
|
+
steps: Array.isArray(body.steps) ? body.steps : [],
|
|
193
|
+
notes: Array.isArray(body.notes) ? body.notes : [],
|
|
194
|
+
...(body.outputTail ? { outputTail: body.outputTail } : {}),
|
|
195
|
+
...(body.system && typeof body.system.name === "string" ? { system: body.system } : {}),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/** Poll the run until it settles; the command's exit code. */
|
|
203
|
+
export async function pollRun(opts) {
|
|
204
|
+
const seen = new Map();
|
|
205
|
+
let failures = 0;
|
|
206
|
+
let systemSaid = false;
|
|
207
|
+
for (;;) {
|
|
208
|
+
const run = await readRun(opts, opts.revisionId);
|
|
209
|
+
if ("error" in run) {
|
|
210
|
+
failures += 1;
|
|
211
|
+
if (failures >= MAX_READ_FAILURES) {
|
|
212
|
+
opts.writeErr(`Could not read the run: ${run.error}`);
|
|
213
|
+
return 2;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
failures = 0;
|
|
218
|
+
if (run.system && !systemSaid) {
|
|
219
|
+
systemSaid = true;
|
|
220
|
+
opts.writeOut(formatSystem(run.system));
|
|
221
|
+
}
|
|
222
|
+
for (const line of stepTransitions(run.steps, seen))
|
|
223
|
+
opts.writeOut(line);
|
|
224
|
+
if (run.status !== "validating") {
|
|
225
|
+
const failedTail = run.steps.some((s) => s.state === "failed" && s.outputTail);
|
|
226
|
+
if (run.status === "needs_attention" && run.outputTail && !failedTail) {
|
|
227
|
+
for (const line of run.outputTail.split("\n").filter((l) => l.trim()))
|
|
228
|
+
opts.writeOut(` ${line}`);
|
|
229
|
+
}
|
|
230
|
+
for (const note of run.notes)
|
|
231
|
+
opts.writeOut(`note: ${note}`);
|
|
232
|
+
if (run.status === "ready") {
|
|
233
|
+
opts.writeOut(run.message ? `Ready. ${run.message}` : "Ready.");
|
|
234
|
+
return 0;
|
|
235
|
+
}
|
|
236
|
+
opts.writeOut(run.message ? `Needs attention: ${run.message}` : "Needs attention.");
|
|
237
|
+
return 1;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
await opts.sleep(opts.intervalMs ?? ENV_POLL_INTERVAL_MS);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function gitOriginUrl(cwd) {
|
|
244
|
+
return new Promise((resolve) => {
|
|
245
|
+
execFile("git", ["remote", "get-url", "origin"], { cwd, timeout: 10_000 }, (err, stdout) => resolve(err ? undefined : stdout.trim() || undefined));
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
/** `--repo owner/name` or `--repo=owner/name`; `error` names a bad argument. */
|
|
249
|
+
export function parseEnvCheckArgs(args) {
|
|
250
|
+
let repo;
|
|
251
|
+
for (let i = 0; i < args.length; i++) {
|
|
252
|
+
const a = args[i];
|
|
253
|
+
if (a === "--repo") {
|
|
254
|
+
const value = args[++i];
|
|
255
|
+
if (!value)
|
|
256
|
+
return { error: "--repo needs owner/name" };
|
|
257
|
+
repo = value;
|
|
258
|
+
}
|
|
259
|
+
else if (a.startsWith("--repo=")) {
|
|
260
|
+
repo = a.slice("--repo=".length);
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
return { error: `Unknown argument: ${a}` };
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (repo === undefined)
|
|
267
|
+
return {};
|
|
268
|
+
const parsed = parseRepoArg(repo);
|
|
269
|
+
return parsed ? { repo: parsed } : { error: `Not a repository: ${repo} (expected owner/name)` };
|
|
270
|
+
}
|
|
271
|
+
/** `yagni env <subcommand>`; only `check` exists. */
|
|
272
|
+
export async function envCommand(args, deps = {}, _cliVersion) {
|
|
273
|
+
const writeOut = deps.writeOut ?? ((line) => void process.stdout.write(`${line}\n`));
|
|
274
|
+
const writeErr = deps.writeErr ?? ((line) => void process.stderr.write(`${line}\n`));
|
|
275
|
+
const [sub, ...rest] = args;
|
|
276
|
+
if (sub !== "check") {
|
|
277
|
+
(sub === undefined ? writeOut : writeErr)(ENV_USAGE);
|
|
278
|
+
return sub === undefined ? 0 : 2;
|
|
279
|
+
}
|
|
280
|
+
const parsed = parseEnvCheckArgs(rest);
|
|
281
|
+
if (parsed.error) {
|
|
282
|
+
writeErr(parsed.error);
|
|
283
|
+
writeErr(ENV_USAGE);
|
|
284
|
+
return 2;
|
|
285
|
+
}
|
|
286
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
287
|
+
let repo = parsed.repo;
|
|
288
|
+
if (!repo) {
|
|
289
|
+
const url = await (deps.originUrl ?? gitOriginUrl)(cwd);
|
|
290
|
+
repo = url ? parseRemoteRepo(url) : undefined;
|
|
291
|
+
if (!repo) {
|
|
292
|
+
writeErr(url
|
|
293
|
+
? `Could not read the repository from the origin remote (${redactRemote(url)}). Pass --repo owner/name.`
|
|
294
|
+
: "This directory has no origin remote. Pass --repo owner/name.");
|
|
295
|
+
return 2;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const env = deps.env ?? process.env;
|
|
299
|
+
const profile = await (deps.loadCredentials ??
|
|
300
|
+
(async () => {
|
|
301
|
+
const active = await readActiveProfile(env);
|
|
302
|
+
const creds = credentialsFromProfile(active);
|
|
303
|
+
return {
|
|
304
|
+
name: active.name,
|
|
305
|
+
baseUrl: active.baseUrl,
|
|
306
|
+
...(creds?.token ? { token: creds.token } : {}),
|
|
307
|
+
};
|
|
308
|
+
}))();
|
|
309
|
+
if (!profile.token) {
|
|
310
|
+
writeErr(`Not logged in to environment "${profile.name}" (${profile.baseUrl}). Run \`yagni login\` first.`);
|
|
311
|
+
return 2;
|
|
312
|
+
}
|
|
313
|
+
const req = {
|
|
314
|
+
baseUrl: profile.baseUrl,
|
|
315
|
+
token: profile.token,
|
|
316
|
+
fetchImpl: deps.fetchImpl ?? fetch,
|
|
317
|
+
};
|
|
318
|
+
const started = await startCheck(req, repo);
|
|
319
|
+
if ("error" in started) {
|
|
320
|
+
writeErr(`Could not start the check for ${repo}: ${started.error}`);
|
|
321
|
+
return 2;
|
|
322
|
+
}
|
|
323
|
+
writeOut(`Validating ${repo} as it stands, without repair.`);
|
|
324
|
+
return pollRun({
|
|
325
|
+
...req,
|
|
326
|
+
revisionId: started.revisionId,
|
|
327
|
+
sleep: deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
|
|
328
|
+
writeOut,
|
|
329
|
+
writeErr,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
//# sourceMappingURL=env.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yagni-app/code-staging",
|
|
3
|
-
"version": "1.2.13-staging.
|
|
3
|
+
"version": "1.2.13-staging.2196.1",
|
|
4
4
|
"description": "YAGNI Code: a terminal coding agent that already knows your company. One YAGNI login routes the model and grounds the agent in your team's context.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"author": "YAGNI, Inc. <jack@yagni.app> (https://yagni.app)",
|
|
@@ -58,5 +58,5 @@
|
|
|
58
58
|
"turndown": "^7.2.4",
|
|
59
59
|
"typebox": "^1.3.15"
|
|
60
60
|
},
|
|
61
|
-
"yagniSourceSha": "
|
|
61
|
+
"yagniSourceSha": "c0c70b535de32727cd574cd8c4eedc460601ff76"
|
|
62
62
|
}
|