carrick 0.3.61 → 0.3.63
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 +32 -12
- package/bin/carrick.mjs +7 -6
- package/dist/auth/read.d.ts +6 -6
- package/dist/contract.d.ts +102 -0
- package/dist/contract.js +22 -0
- package/dist/contract.js.map +1 -1
- package/dist/init/connect.d.ts +31 -2
- package/dist/init/connect.js +153 -29
- package/dist/init/connect.js.map +1 -1
- package/dist/init/projects.d.ts +110 -0
- package/dist/init/projects.js +162 -3
- package/dist/init/projects.js.map +1 -1
- package/dist/init/repos.d.ts +61 -7
- package/dist/init/repos.js +125 -31
- package/dist/init/repos.js.map +1 -1
- package/dist/init/run.d.ts +33 -2
- package/dist/init/run.js +227 -135
- package/dist/init/run.js.map +1 -1
- package/dist/render.d.ts +20 -8
- package/dist/render.js +65 -16
- package/dist/render.js.map +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -27,14 +27,28 @@ carrick init --project payments
|
|
|
27
27
|
The command shows the current project assignment for every proposed GitHub
|
|
28
28
|
repo. When the project is not in the workspace yet, it lists the workspace's
|
|
29
29
|
projects and offers to create the named one from the terminal; where the API
|
|
30
|
-
has no such action it prints the project link instead
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
the
|
|
30
|
+
has no such action it prints the project link instead. Connected repos are then
|
|
31
|
+
put in the project from the terminal too, so the GitHub App grant is the only
|
|
32
|
+
browser step; where the API has no assignment action, the command prints the
|
|
33
|
+
repo-assignment link and, in an interactive terminal, opens the page. Either
|
|
34
|
+
way the claim comes from a read. The CLI reads `resolve-repos` until every
|
|
35
|
+
proposed repo reports `project_slug: "payments"`, and a repo connected to
|
|
36
|
+
another project remains pending. A target it cannot meet does not stop the run.
|
|
37
|
+
The hooks, the MCP connection and the proposal are written, and the command
|
|
38
|
+
says which browser steps are left.
|
|
39
|
+
|
|
40
|
+
Without `--project` the project step still runs: the command takes the project
|
|
41
|
+
the repos are already in, and otherwise lists the workspace's projects for you
|
|
42
|
+
to name one, which it creates from the terminal where the API allows that. An
|
|
43
|
+
empty answer leaves the step to the browser.
|
|
44
|
+
|
|
45
|
+
Each repository is identified by its `origin` remote. A remote written through
|
|
46
|
+
a per-account SSH host alias (`git@github.com-work:owner/repo.git`) is resolved
|
|
47
|
+
with `ssh -G`, so an alias whose `HostName` is `github.com` is an ordinary
|
|
48
|
+
GitHub repository here. When a repository still names none, `init` says which
|
|
49
|
+
one it was and what it read, and leaves that repository out of the project and
|
|
50
|
+
connection steps rather than dropping it quietly. `carrick init --repo
|
|
51
|
+
owner/repo` names the repository in that case.
|
|
38
52
|
|
|
39
53
|
Credentials live in `$XDG_CONFIG_HOME/carrick/credentials.json`, falling back
|
|
40
54
|
to `~/.config/carrick/` on macOS and Linux or `%APPDATA%\carrick\` on Windows.
|
|
@@ -44,9 +58,15 @@ The file is written with mode 0600. `carrick logout` removes it; unset
|
|
|
44
58
|
|
|
45
59
|
Rust derives the same services for CI, local indexing and `init`. An existing
|
|
46
60
|
`carrick.json` is authoritative. When it is missing, `init` presents workspace
|
|
47
|
-
packages and their compiler configuration
|
|
48
|
-
|
|
49
|
-
|
|
61
|
+
packages and their compiler configuration and writes that proposal to
|
|
62
|
+
`.carrick/proposal.json`, which is ignored. Each member in it also carries what
|
|
63
|
+
its own manifest says about it. That is `private`, `bin`, `main`, `exports`,
|
|
64
|
+
any deployment descriptor in its directory, and the members that depend on it,
|
|
65
|
+
so the application-versus-library decision is read rather than re-derived. It
|
|
66
|
+
writes no `carrick.json`: your agent turns the proposal into one, so the config
|
|
67
|
+
you commit is one somebody has read, and the first scan runs against it. `carrick index` is free and states
|
|
68
|
+
what it can; `carrick index --infer` is the scan that asks Carrick to classify
|
|
69
|
+
the rest, and it refuses to run until a `carrick.json` exists.
|
|
50
70
|
|
|
51
71
|
Workspace detection handles a repo root or immediate sibling repositories.
|
|
52
72
|
An optional `carrick-workspace.json` adds paths through `repos` and removes
|
|
@@ -85,7 +105,7 @@ types in a Node process.
|
|
|
85
105
|
|---|---|
|
|
86
106
|
| `carrick login` | Authorise a Carrick workspace in the browser, or verify `CARRICK_TOKEN` |
|
|
87
107
|
| `carrick logout` | Remove the saved local credential |
|
|
88
|
-
| `carrick init [--project SLUG]` | The repo list,
|
|
108
|
+
| `carrick init [--project SLUG] [--repo OWNER/REPO]` | The repo list, the project, the service proposal in `.carrick/`, the agent hooks, the MCP connection, and the prompt that writes `carrick.json` |
|
|
89
109
|
| `carrick index` | Derive the workspace, apply optional repo overrides and write `.carrick/` |
|
|
90
110
|
| `carrick refresh [--service X]` | Re-scan one repo, or all of them, and re-join |
|
|
91
111
|
| `carrick check <file>` | What the index knows about that file, verdicts included |
|
package/bin/carrick.mjs
CHANGED
|
@@ -100,16 +100,16 @@ async function printVersion() {
|
|
|
100
100
|
* Printed after the scanner's help rather than woven into it: the binary is
|
|
101
101
|
* spawned for that text and answers none of these names, so its groups are
|
|
102
102
|
* repeated here as headings instead of being interleaved (carrick#976).
|
|
103
|
+
*
|
|
104
|
+
* `init` is named in the binary's own WORKSPACE block instead of here: it is
|
|
105
|
+
* where the first run starts, and a help text a reader stops part-way through
|
|
106
|
+
* must not be the reason they never find it (carrick#997 item 5). Its
|
|
107
|
+
* arguments are still this package's.
|
|
103
108
|
*/
|
|
104
109
|
function extraHelp() {
|
|
105
110
|
return [
|
|
106
111
|
"This package carries the commands that put those answers where you work.",
|
|
107
112
|
"",
|
|
108
|
-
"WORKSPACE:",
|
|
109
|
-
" init [--project SLUG] set up this folder: the repos to index, the",
|
|
110
|
-
" first index, and the editor and agent wiring;",
|
|
111
|
-
" with a project, verify every repo is in it",
|
|
112
|
-
"",
|
|
113
113
|
"ACCOUNT:",
|
|
114
114
|
" login sign in to a Carrick workspace",
|
|
115
115
|
" logout remove the saved local credential",
|
|
@@ -124,7 +124,8 @@ function extraHelp() {
|
|
|
124
124
|
" carrick.json",
|
|
125
125
|
" --version the version of this package",
|
|
126
126
|
"",
|
|
127
|
-
"`carrick init --
|
|
127
|
+
"`carrick init [--project SLUG]` sets this folder up; `carrick init --help`",
|
|
128
|
+
"prints its own arguments.",
|
|
128
129
|
"",
|
|
129
130
|
].join("\n");
|
|
130
131
|
}
|
package/dist/auth/read.d.ts
CHANGED
|
@@ -72,11 +72,11 @@ declare const response: z.ZodObject<{
|
|
|
72
72
|
project_slug: z.ZodString;
|
|
73
73
|
repos: z.ZodArray<z.ZodString, "many">;
|
|
74
74
|
}, "strip", z.ZodTypeAny, {
|
|
75
|
-
project_slug: string;
|
|
76
75
|
repos: string[];
|
|
77
|
-
}, {
|
|
78
76
|
project_slug: string;
|
|
77
|
+
}, {
|
|
79
78
|
repos: string[];
|
|
79
|
+
project_slug: string;
|
|
80
80
|
}>, "many">;
|
|
81
81
|
}, "strip", z.ZodTypeAny, {
|
|
82
82
|
schema: "carrick.resolve-repos/0";
|
|
@@ -85,7 +85,6 @@ declare const response: z.ZodObject<{
|
|
|
85
85
|
billing_tier: "free" | "cross_repo" | "paid";
|
|
86
86
|
installed: boolean;
|
|
87
87
|
};
|
|
88
|
-
allowance_sentence: string | null;
|
|
89
88
|
repos: ({
|
|
90
89
|
connected: false;
|
|
91
90
|
full_name: string;
|
|
@@ -101,9 +100,10 @@ declare const response: z.ZodObject<{
|
|
|
101
100
|
project_id: string;
|
|
102
101
|
project_slug: string;
|
|
103
102
|
})[];
|
|
103
|
+
allowance_sentence: string | null;
|
|
104
104
|
project_repos: {
|
|
105
|
-
project_slug: string;
|
|
106
105
|
repos: string[];
|
|
106
|
+
project_slug: string;
|
|
107
107
|
}[];
|
|
108
108
|
}, {
|
|
109
109
|
schema: "carrick.resolve-repos/0";
|
|
@@ -112,7 +112,6 @@ declare const response: z.ZodObject<{
|
|
|
112
112
|
billing_tier: "free" | "cross_repo" | "paid";
|
|
113
113
|
installed: boolean;
|
|
114
114
|
};
|
|
115
|
-
allowance_sentence: string | null;
|
|
116
115
|
repos: ({
|
|
117
116
|
connected: false;
|
|
118
117
|
full_name: string;
|
|
@@ -128,9 +127,10 @@ declare const response: z.ZodObject<{
|
|
|
128
127
|
project_id: string;
|
|
129
128
|
project_slug: string;
|
|
130
129
|
})[];
|
|
130
|
+
allowance_sentence: string | null;
|
|
131
131
|
project_repos: {
|
|
132
|
-
project_slug: string;
|
|
133
132
|
repos: string[];
|
|
133
|
+
project_slug: string;
|
|
134
134
|
}[];
|
|
135
135
|
}>;
|
|
136
136
|
export type ResolvedRepos = z.infer<typeof response>;
|
package/dist/contract.d.ts
CHANGED
|
@@ -10,6 +10,12 @@ export type Boundary = {
|
|
|
10
10
|
files_attempted?: number;
|
|
11
11
|
files_lost?: Counted;
|
|
12
12
|
unemitted_literal_candidates?: number;
|
|
13
|
+
/** `file:line` for up to 200 of those sites, so the count can be checked. */
|
|
14
|
+
unemitted_literal_sites?: string[];
|
|
15
|
+
/** Candidates no model has been asked about: what `--infer` would classify. */
|
|
16
|
+
candidates_awaiting_model?: number;
|
|
17
|
+
/** Why this service's types are thin, when the scan recorded a reason. */
|
|
18
|
+
type_extraction_status?: string;
|
|
13
19
|
consumers_not_resolved?: Counted;
|
|
14
20
|
sdk_unresolved?: Counted;
|
|
15
21
|
unknown_call_paths?: Counted;
|
|
@@ -104,6 +110,10 @@ export type StatusService = {
|
|
|
104
110
|
indexed_at?: string;
|
|
105
111
|
routes: number;
|
|
106
112
|
calls: number;
|
|
113
|
+
/**
|
|
114
|
+
* Files THIS SERVICE reads that changed: its directory and its `include`
|
|
115
|
+
* roots. What belongs to no service is on the repo's own entry.
|
|
116
|
+
*/
|
|
107
117
|
changed_since_index: number;
|
|
108
118
|
/** Up to 50, repo-relative and sorted. */
|
|
109
119
|
stale_files?: string[];
|
|
@@ -120,6 +130,88 @@ export type StatusService = {
|
|
|
120
130
|
*/
|
|
121
131
|
hosted_state?: "enriched" | "no_index_yet" | "not_connected" | "not_signed_in" | "version_mismatch" | "commit_missing" | "read_failed";
|
|
122
132
|
};
|
|
133
|
+
/**
|
|
134
|
+
* One repo of the workspace, for what moved in it that belongs to no service:
|
|
135
|
+
* a workflow file, a lockfile, an editor's settings. Stated once here rather
|
|
136
|
+
* than under every service that never reads it (carrick#997 item 4).
|
|
137
|
+
*/
|
|
138
|
+
export type StatusRepo = {
|
|
139
|
+
repo: string;
|
|
140
|
+
name: string;
|
|
141
|
+
/** The whole repo, services included. */
|
|
142
|
+
changed_since_index: number;
|
|
143
|
+
outside_every_service: number;
|
|
144
|
+
/** Up to 50 of the ones outside every service, repo-relative. */
|
|
145
|
+
stale_files?: string[];
|
|
146
|
+
stale_files_truncated?: boolean;
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* A scan running on this machine right now, as `carrick status` reports it.
|
|
150
|
+
*
|
|
151
|
+
* The whole point of `carrick index --detach` is that the scan outlives the
|
|
152
|
+
* shell that started it, so something has to be able to say it is still going
|
|
153
|
+
* (carrick#992). `line` is not sent — the CLI renders the sentence and this
|
|
154
|
+
* package prints what it needs from the fields.
|
|
155
|
+
*/
|
|
156
|
+
export type RunningScan = {
|
|
157
|
+
scan_id: string;
|
|
158
|
+
pid: number;
|
|
159
|
+
started_at: string;
|
|
160
|
+
updated_at?: string;
|
|
161
|
+
infer?: boolean;
|
|
162
|
+
workspace?: string;
|
|
163
|
+
status: "running" | "failed";
|
|
164
|
+
phase?: string;
|
|
165
|
+
progress?: {
|
|
166
|
+
service?: string;
|
|
167
|
+
service_index?: number;
|
|
168
|
+
service_total?: number;
|
|
169
|
+
phase?: "files" | "intents";
|
|
170
|
+
done?: number;
|
|
171
|
+
total?: number;
|
|
172
|
+
};
|
|
173
|
+
error?: string;
|
|
174
|
+
/** What this scan has paid so far, once an upload has come back with a figure. */
|
|
175
|
+
spend?: RunSpend;
|
|
176
|
+
};
|
|
177
|
+
/**
|
|
178
|
+
* One scan's Vertex spend, as Carrick Cloud reports it on the write action
|
|
179
|
+
* that closed the run (`carrick.scan-spend/0`).
|
|
180
|
+
*
|
|
181
|
+
* `priced` is false when any model in the month has no price on file, and it
|
|
182
|
+
* covers all three dollar figures at once: with one of them the run's own cost
|
|
183
|
+
* and both remainings are under-counts. A null amount is "not set", never
|
|
184
|
+
* "unlimited". Both rules are the CLI's to apply — this package carries the
|
|
185
|
+
* fields and prints none of them.
|
|
186
|
+
*/
|
|
187
|
+
export type ScanSpend = {
|
|
188
|
+
schema: string;
|
|
189
|
+
scan_id?: string;
|
|
190
|
+
first_index?: boolean;
|
|
191
|
+
priced?: boolean;
|
|
192
|
+
unpriced_models?: string[];
|
|
193
|
+
/** Null when `priced` is false. */
|
|
194
|
+
usd?: number | null;
|
|
195
|
+
input_tokens?: number;
|
|
196
|
+
output_tokens?: number;
|
|
197
|
+
cached_tokens?: number;
|
|
198
|
+
calls?: number;
|
|
199
|
+
first_index_ceiling_usd?: number | null;
|
|
200
|
+
first_index_remaining_usd?: number | null;
|
|
201
|
+
monthly_allowance_usd?: number | null;
|
|
202
|
+
monthly_remaining_usd?: number | null;
|
|
203
|
+
/** `YYYY-MM`, the month the monthly figure is for. */
|
|
204
|
+
period?: string;
|
|
205
|
+
};
|
|
206
|
+
/** What one run of `carrick index --infer` paid: one entry per repo it scanned. */
|
|
207
|
+
export type RunSpend = {
|
|
208
|
+
/** RFC 3339, when the last figure in it landed. */
|
|
209
|
+
updated_at?: string;
|
|
210
|
+
scans: {
|
|
211
|
+
repo: string;
|
|
212
|
+
spend: ScanSpend;
|
|
213
|
+
}[];
|
|
214
|
+
};
|
|
123
215
|
/** `carrick status --json`: the workspace, with no file in the question. */
|
|
124
216
|
export type StatusResult = {
|
|
125
217
|
schema: string;
|
|
@@ -129,6 +221,16 @@ export type StatusResult = {
|
|
|
129
221
|
scanner_version?: string;
|
|
130
222
|
/** RFC 3339, when this index last read the hosted side. */
|
|
131
223
|
hosted_checked_at?: string;
|
|
224
|
+
/** Absent on an answer written before this field existed. */
|
|
225
|
+
repos?: StatusRepo[];
|
|
226
|
+
/** Scans running (or stopped part-way) on this machine. Usually absent. */
|
|
227
|
+
running_scans?: RunningScan[];
|
|
228
|
+
/**
|
|
229
|
+
* What the last paid scan of this workspace cost. Absent until one has run.
|
|
230
|
+
* Carried on the error body too: a first paid run killed before it wrote an
|
|
231
|
+
* index still spent the money.
|
|
232
|
+
*/
|
|
233
|
+
last_scan?: RunSpend;
|
|
132
234
|
services: StatusService[];
|
|
133
235
|
};
|
|
134
236
|
/**
|
package/dist/contract.js
CHANGED
|
@@ -112,6 +112,28 @@ export function parseStatusResult(stdout) {
|
|
|
112
112
|
continue;
|
|
113
113
|
result.services.push(entry);
|
|
114
114
|
}
|
|
115
|
+
const scans = Array.isArray(parsed["running_scans"]) ? parsed["running_scans"] : [];
|
|
116
|
+
for (const entry of scans) {
|
|
117
|
+
if (!isRecord(entry))
|
|
118
|
+
continue;
|
|
119
|
+
if (typeof entry["scan_id"] !== "string" || typeof entry["status"] !== "string")
|
|
120
|
+
continue;
|
|
121
|
+
(result.running_scans ??= []).push(entry);
|
|
122
|
+
}
|
|
123
|
+
const lastScan = parsed["last_scan"];
|
|
124
|
+
if (isRecord(lastScan) && Array.isArray(lastScan["scans"])) {
|
|
125
|
+
result.last_scan = lastScan;
|
|
126
|
+
}
|
|
127
|
+
const repos = Array.isArray(parsed["repos"]) ? parsed["repos"] : [];
|
|
128
|
+
for (const entry of repos) {
|
|
129
|
+
if (!isRecord(entry))
|
|
130
|
+
continue;
|
|
131
|
+
if (typeof entry["repo"] !== "string" || typeof entry["name"] !== "string")
|
|
132
|
+
continue;
|
|
133
|
+
if (typeof entry["outside_every_service"] !== "number")
|
|
134
|
+
continue;
|
|
135
|
+
(result.repos ??= []).push(entry);
|
|
136
|
+
}
|
|
115
137
|
return result;
|
|
116
138
|
}
|
|
117
139
|
/** True when the row is the model's reading alone (R1). */
|
package/dist/contract.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,EAAE;AACF,yDAAyD;AACzD,+EAA+E;AAC/E,4DAA4D;AAC5D,6EAA6E;AAC7E,6EAA6E;AAC7E,+EAA+E;AAC/E,0CAA0C;
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"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"}
|
package/dist/init/connect.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ResolvedRepos } from "../auth/read.ts";
|
|
2
|
+
import { type AssignOutcome } from "./projects.ts";
|
|
2
3
|
type ConnectOptions = {
|
|
3
4
|
interactive: boolean;
|
|
4
5
|
say: (message: string) => void;
|
|
@@ -6,12 +7,40 @@ type ConnectOptions = {
|
|
|
6
7
|
/** True when this run has already seen or created the project (carrick#955). */
|
|
7
8
|
projectExists?: boolean;
|
|
8
9
|
open?: (url: string) => Promise<boolean>;
|
|
9
|
-
poll?: (signal
|
|
10
|
+
poll?: (signal?: AbortSignal) => Promise<ResolvedRepos>;
|
|
11
|
+
/** Placing repos in the project, injected so tests state the server (carrick#999). */
|
|
12
|
+
assign?: (repos: string[], signal?: AbortSignal) => Promise<AssignOutcome>;
|
|
10
13
|
wait?: (signal: AbortSignal) => Promise<void>;
|
|
11
14
|
signal?: AbortSignal;
|
|
12
15
|
};
|
|
16
|
+
/**
|
|
17
|
+
* The line before every wait.
|
|
18
|
+
*
|
|
19
|
+
* What is waited on is a page in the Carrick dashboard, and both of them —
|
|
20
|
+
* the App grant and the Repos page — refuse anyone who is not an owner or an
|
|
21
|
+
* admin of the workspace. Waiting thirty minutes on a page you may not use is
|
|
22
|
+
* the friction this says out loud (carrick#993).
|
|
23
|
+
*/
|
|
24
|
+
export declare const ADMIN_WAIT = "A workspace owner or admin must do this; Ctrl-C and run init again once they have.";
|
|
13
25
|
/** Every requested repo must be present, connected, and assigned to this project. */
|
|
14
26
|
export declare function reposAreInProject(identity: ResolvedRepos, repos: string[], project: string): boolean;
|
|
15
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* The project each requested repo is in, in the order they were requested,
|
|
29
|
+
* with null for one this workspace does not hold.
|
|
30
|
+
*
|
|
31
|
+
* The project step reads this before it asks anything: a repo that is already
|
|
32
|
+
* in a project answers the question on its own (carrick#987).
|
|
33
|
+
*/
|
|
34
|
+
export declare function projectAssignments(identity: ResolvedRepos, repos: string[]): Array<string | null>;
|
|
35
|
+
/**
|
|
36
|
+
* The GitHub App grant is the only browser step; the CLI places the repos.
|
|
37
|
+
*
|
|
38
|
+
* Assignment is a server action on this credential (carrick#999), tried for
|
|
39
|
+
* every connected repo the moment the poll sees it, so nobody has to open the
|
|
40
|
+
* Repos page and move it by hand. An API that does not serve the action yet —
|
|
41
|
+
* which is every API until the deploy lands — answers an absence, and the
|
|
42
|
+
* browser instruction this command printed before is what it falls back to.
|
|
43
|
+
* Ctrl-C stops verification.
|
|
44
|
+
*/
|
|
16
45
|
export declare function connectRepos(token: string, repos: string[], initial: ResolvedRepos, options: ConnectOptions): Promise<ResolvedRepos>;
|
|
17
46
|
export {};
|
package/dist/init/connect.js
CHANGED
|
@@ -2,6 +2,16 @@ import { setTimeout } from "node:timers/promises";
|
|
|
2
2
|
import { APP_BASE } from "../auth/credentials.js";
|
|
3
3
|
import { openBrowser } from "../auth/oauth.js";
|
|
4
4
|
import { resolveRepos } from "../auth/read.js";
|
|
5
|
+
import { assignRepos } from "./projects.js";
|
|
6
|
+
/**
|
|
7
|
+
* The line before every wait.
|
|
8
|
+
*
|
|
9
|
+
* What is waited on is a page in the Carrick dashboard, and both of them —
|
|
10
|
+
* the App grant and the Repos page — refuse anyone who is not an owner or an
|
|
11
|
+
* admin of the workspace. Waiting thirty minutes on a page you may not use is
|
|
12
|
+
* the friction this says out loud (carrick#993).
|
|
13
|
+
*/
|
|
14
|
+
export const ADMIN_WAIT = "A workspace owner or admin must do this; Ctrl-C and run init again once they have.";
|
|
5
15
|
/** Every requested repo must be present, connected, and assigned to this project. */
|
|
6
16
|
export function reposAreInProject(identity, repos, project) {
|
|
7
17
|
if (!identity.workspace.installed || repos.length === 0)
|
|
@@ -15,6 +25,19 @@ function assignment(identity, name) {
|
|
|
15
25
|
const repo = identity.repos.find((candidate) => candidate.full_name.toLowerCase() === name.toLowerCase());
|
|
16
26
|
return repo?.connected === true ? repo.project_slug : "not connected";
|
|
17
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* The project each requested repo is in, in the order they were requested,
|
|
30
|
+
* with null for one this workspace does not hold.
|
|
31
|
+
*
|
|
32
|
+
* The project step reads this before it asks anything: a repo that is already
|
|
33
|
+
* in a project answers the question on its own (carrick#987).
|
|
34
|
+
*/
|
|
35
|
+
export function projectAssignments(identity, repos) {
|
|
36
|
+
return repos.map((name) => {
|
|
37
|
+
const repo = identity.repos.find((candidate) => candidate.full_name.toLowerCase() === name.toLowerCase());
|
|
38
|
+
return repo?.connected === true ? repo.project_slug : null;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
18
41
|
function reportAssignments(identity, repos, previous, say) {
|
|
19
42
|
for (const name of repos) {
|
|
20
43
|
const current = assignment(identity, name);
|
|
@@ -33,65 +56,166 @@ function verifiedLine(repos, project) {
|
|
|
33
56
|
? `Verified 1 repo in project "${project}".`
|
|
34
57
|
: `Verified ${repos.length} repos in project "${project}".`;
|
|
35
58
|
}
|
|
36
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* The repos this run may still place: connected to the workspace, and in some
|
|
61
|
+
* other project than the one asked for.
|
|
62
|
+
*/
|
|
63
|
+
function misplaced(identity, repos, project) {
|
|
64
|
+
return repos.filter((name) => {
|
|
65
|
+
const repo = identity.repos.find((candidate) => candidate.full_name.toLowerCase() === name.toLowerCase());
|
|
66
|
+
return repo?.connected === true && repo.project_slug !== project;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The GitHub App grant is the only browser step; the CLI places the repos.
|
|
71
|
+
*
|
|
72
|
+
* Assignment is a server action on this credential (carrick#999), tried for
|
|
73
|
+
* every connected repo the moment the poll sees it, so nobody has to open the
|
|
74
|
+
* Repos page and move it by hand. An API that does not serve the action yet —
|
|
75
|
+
* which is every API until the deploy lands — answers an absence, and the
|
|
76
|
+
* browser instruction this command printed before is what it falls back to.
|
|
77
|
+
* Ctrl-C stops verification.
|
|
78
|
+
*/
|
|
37
79
|
export async function connectRepos(token, repos, initial, options) {
|
|
38
80
|
const workspaceUrl = `${APP_BASE}/w/${encodeURIComponent(initial.workspace.slug)}`;
|
|
39
81
|
const connectUrl = `${workspaceUrl}/connect`;
|
|
40
82
|
const projectUrl = `${workspaceUrl}/projects`;
|
|
41
83
|
const reposUrl = `${workspaceUrl}/repos`;
|
|
42
84
|
const assignments = new Map();
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
85
|
+
const poll = options.poll ?? ((signal) => resolveRepos(token, repos, fetch, signal));
|
|
86
|
+
const project = options.project;
|
|
87
|
+
const assign = options.assign ??
|
|
88
|
+
((names, signal) => assignRepos(token, project ?? "", names, fetch, signal));
|
|
89
|
+
// A project this run has not seen in the workspace may not exist at all, so
|
|
90
|
+
// there is nothing to place repos into and the browser owns both steps.
|
|
91
|
+
let placeable = project !== undefined && options.projectExists === true;
|
|
92
|
+
let saidBrowserAssign = false;
|
|
93
|
+
// One line per distinct thing the server said, so a write that keeps failing
|
|
94
|
+
// does not print the same sentence every five seconds.
|
|
95
|
+
const said = new Set();
|
|
96
|
+
const sayOnce = (line) => {
|
|
97
|
+
if (said.has(line))
|
|
98
|
+
return;
|
|
99
|
+
said.add(line);
|
|
100
|
+
options.say(line);
|
|
101
|
+
};
|
|
102
|
+
const browserAssign = () => {
|
|
103
|
+
if (saidBrowserAssign)
|
|
104
|
+
return;
|
|
105
|
+
saidBrowserAssign = true;
|
|
106
|
+
options.say(`Assign the requested repos: ${reposUrl}`);
|
|
107
|
+
options.say(` Select ${repos.join(", ")}, choose the target project in "Move selected to", then choose "Move selected".`);
|
|
108
|
+
};
|
|
109
|
+
/** Place what can be placed; true when something moved and is worth re-reading. */
|
|
110
|
+
const place = async (identity, signal) => {
|
|
111
|
+
if (!placeable || project === undefined)
|
|
112
|
+
return false;
|
|
113
|
+
const pending = misplaced(identity, repos, project);
|
|
114
|
+
if (pending.length === 0)
|
|
115
|
+
return false;
|
|
116
|
+
const outcome = await assign(pending, signal);
|
|
117
|
+
if (outcome.kind !== "placed") {
|
|
118
|
+
// Neither a refusal nor an absence is worth retrying every five seconds,
|
|
119
|
+
// and both leave the assignment to the browser. The instruction itself
|
|
120
|
+
// is printed by the caller, after the steps that come before it.
|
|
121
|
+
placeable = false;
|
|
122
|
+
if (outcome.kind === "refused") {
|
|
123
|
+
options.say(`Carrick did not assign the requested repos to "${project}": ${outcome.message}`);
|
|
124
|
+
}
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
let moved = false;
|
|
128
|
+
for (const repo of outcome.repos) {
|
|
129
|
+
if (repo.assigned) {
|
|
130
|
+
moved = moved || repo.moved;
|
|
131
|
+
if (repo.moved)
|
|
132
|
+
sayOnce(`Moved ${repo.full_name} into project "${project}".`);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
sayOnce(`${repo.full_name} was not moved: ${repo.reason ?? "Carrick gave no reason."}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return moved;
|
|
139
|
+
};
|
|
140
|
+
/** Place, then read the assignment back: a claim is only made on the read. */
|
|
141
|
+
const settle = async (identity, signal) => {
|
|
142
|
+
if (!(await place(identity, signal)))
|
|
143
|
+
return identity;
|
|
144
|
+
const latest = await poll(signal);
|
|
145
|
+
reportAssignments(latest, repos, assignments, options.say);
|
|
146
|
+
return latest;
|
|
147
|
+
};
|
|
148
|
+
let latest = initial;
|
|
149
|
+
if (project !== undefined) {
|
|
150
|
+
reportAssignments(latest, repos, assignments, options.say);
|
|
151
|
+
if (reposAreInProject(latest, repos, project)) {
|
|
152
|
+
options.say(verifiedLine(repos, project));
|
|
153
|
+
return latest;
|
|
154
|
+
}
|
|
155
|
+
latest = await settle(latest, options.signal);
|
|
156
|
+
if (reposAreInProject(latest, repos, project)) {
|
|
157
|
+
options.say(verifiedLine(repos, project));
|
|
158
|
+
return latest;
|
|
48
159
|
}
|
|
49
160
|
// The create step is skipped only when this run has just seen the project
|
|
50
|
-
// in the workspace or created it
|
|
51
|
-
// assigned to it.
|
|
161
|
+
// in the workspace or created it.
|
|
52
162
|
if (!options.projectExists) {
|
|
53
|
-
options.say(`Create project "${
|
|
54
|
-
options.say(` Choose Create project, enter a name, and set the slug to "${
|
|
163
|
+
options.say(`Create project "${project}" if needed: ${projectUrl}`);
|
|
164
|
+
options.say(` Choose Create project, enter a name, and set the slug to "${project}".`);
|
|
55
165
|
}
|
|
56
|
-
if (!
|
|
166
|
+
if (!latest.workspace.installed || latest.repos.some((repo) => !repo.connected)) {
|
|
57
167
|
options.say(`Connect any missing repos: ${connectUrl}`);
|
|
58
168
|
}
|
|
59
|
-
|
|
60
|
-
|
|
169
|
+
// Only where this run cannot do the placing itself. With repos still to
|
|
170
|
+
// connect it does not know yet, and says so when the poll finds out.
|
|
171
|
+
if (!placeable)
|
|
172
|
+
browserAssign();
|
|
61
173
|
}
|
|
62
174
|
else {
|
|
63
|
-
if (
|
|
64
|
-
return
|
|
175
|
+
if (latest.workspace.installed && latest.repos.every((repo) => repo.connected))
|
|
176
|
+
return latest;
|
|
65
177
|
options.say(`Connect repositories in your browser: ${connectUrl}`);
|
|
66
178
|
}
|
|
67
179
|
if (!options.interactive) {
|
|
68
|
-
if (
|
|
69
|
-
options.say(`Project "${
|
|
180
|
+
if (project !== undefined) {
|
|
181
|
+
options.say(`Project "${project}" is not verified for every requested repo.`);
|
|
70
182
|
}
|
|
71
|
-
return
|
|
183
|
+
return latest;
|
|
72
184
|
}
|
|
73
|
-
options.say(
|
|
74
|
-
|
|
185
|
+
options.say(ADMIN_WAIT);
|
|
186
|
+
options.say(project !== undefined
|
|
187
|
+
? `Waiting for every requested repo to reach project "${project}". Press Ctrl-C to stop.`
|
|
75
188
|
: "Waiting for repository connections. Press Ctrl-C to continue setup without waiting.");
|
|
76
189
|
const controller = new AbortController();
|
|
77
190
|
const cancel = () => controller.abort();
|
|
78
191
|
process.once("SIGINT", cancel);
|
|
79
192
|
const signal = AbortSignal.any([controller.signal, AbortSignal.timeout(30 * 60_000), ...(options.signal ? [options.signal] : [])]);
|
|
80
|
-
|
|
81
|
-
const seen = new Set(initial.repos.filter((repo) => repo.connected).map((repo) => repo.full_name));
|
|
193
|
+
const seen = new Set(latest.repos.filter((repo) => repo.connected).map((repo) => repo.full_name));
|
|
82
194
|
try {
|
|
83
|
-
|
|
195
|
+
// Where the browser is pointed: at the project it must create, else at the
|
|
196
|
+
// grant that connects the repos, else — only where this run cannot place
|
|
197
|
+
// them itself — at the page that moves them.
|
|
198
|
+
const target = project === undefined
|
|
199
|
+
? connectUrl
|
|
200
|
+
: !options.projectExists
|
|
201
|
+
? projectUrl
|
|
202
|
+
: !latest.workspace.installed || latest.repos.some((repo) => !repo.connected)
|
|
203
|
+
? connectUrl
|
|
204
|
+
: reposUrl;
|
|
84
205
|
void (options.open ?? openBrowser)(target).catch(() => false);
|
|
85
206
|
while (!signal.aborted) {
|
|
86
207
|
await (options.wait ?? (async (signal) => { await setTimeout(5000, undefined, { signal }); }))(signal);
|
|
87
208
|
signal.throwIfAborted();
|
|
88
|
-
latest = await
|
|
89
|
-
if (
|
|
209
|
+
latest = await poll(signal);
|
|
210
|
+
if (project !== undefined) {
|
|
90
211
|
reportAssignments(latest, repos, assignments, options.say);
|
|
91
|
-
|
|
92
|
-
|
|
212
|
+
latest = await settle(latest, signal);
|
|
213
|
+
if (reposAreInProject(latest, repos, project)) {
|
|
214
|
+
options.say(verifiedLine(repos, project));
|
|
93
215
|
return latest;
|
|
94
216
|
}
|
|
217
|
+
if (!placeable)
|
|
218
|
+
browserAssign();
|
|
95
219
|
}
|
|
96
220
|
else {
|
|
97
221
|
for (const repo of latest.repos) {
|
|
@@ -112,8 +236,8 @@ export async function connectRepos(token, repos, initial, options) {
|
|
|
112
236
|
finally {
|
|
113
237
|
process.removeListener("SIGINT", cancel);
|
|
114
238
|
}
|
|
115
|
-
options.say(
|
|
116
|
-
? `Stopped waiting; project "${
|
|
239
|
+
options.say(project !== undefined
|
|
240
|
+
? `Stopped waiting; project "${project}" is not verified for every requested repo.`
|
|
117
241
|
: "Stopped waiting for repository connections; continuing local setup.");
|
|
118
242
|
return latest;
|
|
119
243
|
}
|