@h-rig/init-plugin 0.0.6-alpha.156
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 +1 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +528 -0
- package/dist/src/plugin.d.ts +5 -0
- package/dist/src/plugin.js +506 -0
- package/dist/src/rig-init-builder.d.ts +30 -0
- package/dist/src/rig-init-builder.js +61 -0
- package/dist/src/setup.d.ts +121 -0
- package/dist/src/setup.js +458 -0
- package/package.json +37 -0
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# @h-rig/init-plugin
|
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __returnValue = (v) => v;
|
|
4
|
+
function __exportSetter(name, newValue) {
|
|
5
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
+
}
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, {
|
|
10
|
+
get: all[name],
|
|
11
|
+
enumerable: true,
|
|
12
|
+
configurable: true,
|
|
13
|
+
set: __exportSetter.bind(all, name)
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
|
+
|
|
18
|
+
// packages/init-plugin/src/rig-init-builder.ts
|
|
19
|
+
function buildRigInitConfigSource(input) {
|
|
20
|
+
const lines = [`import { defineConfig } from "@rig/core/config";`];
|
|
21
|
+
if (input.useStandardPlugin) {
|
|
22
|
+
lines.push(`import { standardPlugins } from "@rig/standard-plugin/bundle";`);
|
|
23
|
+
if (input.taskSource.kind === "github-issues") {
|
|
24
|
+
lines.push(`import { createStateGitHubCredentialProvider } from "@rig/standard-plugin";`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
lines.push(``, `export default defineConfig({`);
|
|
28
|
+
const projectRepo = input.projectRepo ?? (input.taskSource.kind === "github-issues" ? `${input.taskSource.owner}/${input.taskSource.repo}` : undefined);
|
|
29
|
+
lines.push(projectRepo ? ` project: { name: ${JSON.stringify(input.projectName)}, repo: ${JSON.stringify(projectRepo)} },` : ` project: { name: ${JSON.stringify(input.projectName)} },`);
|
|
30
|
+
if (input.useStandardPlugin && input.taskSource.kind === "github-issues") {
|
|
31
|
+
lines.push(` plugins: [...standardPlugins({`);
|
|
32
|
+
lines.push(` taskSources: {`);
|
|
33
|
+
lines.push(` githubCredentialProvider: createStateGitHubCredentialProvider(),`);
|
|
34
|
+
lines.push(` githubWorkspaceId: ${JSON.stringify(`${input.taskSource.owner}/${input.taskSource.repo}`)},`);
|
|
35
|
+
lines.push(` githubUserId: process.env.RIG_GITHUB_USER_ID ?? "server-selected-user",`);
|
|
36
|
+
lines.push(` },`);
|
|
37
|
+
lines.push(` })],`);
|
|
38
|
+
} else {
|
|
39
|
+
lines.push(` plugins: [${input.useStandardPlugin ? "...standardPlugins()" : ""}],`);
|
|
40
|
+
}
|
|
41
|
+
if (input.taskSource.kind === "github-issues") {
|
|
42
|
+
lines.push(` taskSource: {`);
|
|
43
|
+
lines.push(` kind: "github-issues",`);
|
|
44
|
+
lines.push(` owner: ${JSON.stringify(input.taskSource.owner)},`);
|
|
45
|
+
lines.push(` repo: ${JSON.stringify(input.taskSource.repo)},`);
|
|
46
|
+
lines.push(` // labels: ["task"], // uncomment to filter by labels`);
|
|
47
|
+
lines.push(` state: "open",`);
|
|
48
|
+
if (input.taskSource.assignee?.trim()) {
|
|
49
|
+
lines.push(` options: { assignee: ${JSON.stringify(input.taskSource.assignee.trim())} },`);
|
|
50
|
+
}
|
|
51
|
+
lines.push(` },`);
|
|
52
|
+
} else {
|
|
53
|
+
lines.push(` taskSource: {`);
|
|
54
|
+
lines.push(` kind: "files",`);
|
|
55
|
+
lines.push(` path: ${JSON.stringify(input.taskSource.path)},`);
|
|
56
|
+
lines.push(` },`);
|
|
57
|
+
}
|
|
58
|
+
lines.push(` workspace: { mainRepo: ".", isolation: "worktree" },`);
|
|
59
|
+
const sshTarget = input.sshTarget?.trim();
|
|
60
|
+
lines.push(sshTarget ? ` runtime: { harness: "pi", mode: "yolo", server: { sshTarget: ${JSON.stringify(sshTarget)} } },` : ` runtime: { harness: "pi", mode: "yolo" }, // server.sshTarget unset = local placement`);
|
|
61
|
+
lines.push(` planning: { mode: "auto" },`);
|
|
62
|
+
lines.push(` github: {`);
|
|
63
|
+
lines.push(` issueUpdates: "lifecycle",`);
|
|
64
|
+
lines.push(` projects: { enabled: false },`);
|
|
65
|
+
lines.push(` },`);
|
|
66
|
+
lines.push(` automation: { maxValidationAttempts: 30, maxPrFixIterations: 100500 },`);
|
|
67
|
+
lines.push(` pr: { mode: "auto", watchChecks: true, autoFixChecks: true, autoFixReview: true },`);
|
|
68
|
+
lines.push(` merge: { mode: "auto", method: "repo-default", deleteBranch: "repo-default", bypass: false },`);
|
|
69
|
+
lines.push(` issueAnalysis: { enabled: true, harness: "pi", mode: "continuous" },`);
|
|
70
|
+
lines.push(`});`);
|
|
71
|
+
lines.push(``);
|
|
72
|
+
return lines.join(`
|
|
73
|
+
`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// packages/init-plugin/src/setup.ts
|
|
77
|
+
var exports_setup = {};
|
|
78
|
+
__export(exports_setup, {
|
|
79
|
+
writeRigConnectionState: () => writeRigConnectionState,
|
|
80
|
+
writeRigConfig: () => writeRigConfig,
|
|
81
|
+
validateGitHubAuth: () => validateGitHubAuth,
|
|
82
|
+
saveGitHubTokenLocally: () => saveGitHubTokenLocally,
|
|
83
|
+
runSetup: () => runSetup,
|
|
84
|
+
readRigConnectionStatus: () => readRigConnectionStatus,
|
|
85
|
+
readRigConfigStatus: () => readRigConfigStatus,
|
|
86
|
+
readGhAuthToken: () => readGhAuthToken,
|
|
87
|
+
parseRepoSlugFromRemote: () => parseRepoSlugFromRemote,
|
|
88
|
+
parseRepoSlug: () => parseRepoSlug,
|
|
89
|
+
ensureRigPrivateDirs: () => ensureRigPrivateDirs,
|
|
90
|
+
ensurePiRigInstalledForSetup: () => ensurePiRigInstalledForSetup,
|
|
91
|
+
ensureGitignoreEntries: () => ensureGitignoreEntries,
|
|
92
|
+
ensureGitHubLabels: () => ensureGitHubLabels,
|
|
93
|
+
ensureGitHubAuth: () => ensureGitHubAuth,
|
|
94
|
+
detectStartupStatus: () => detectStartupStatus,
|
|
95
|
+
detectRigStartupStatus: () => detectRigStartupStatus,
|
|
96
|
+
detectOriginRepoSlug: () => detectOriginRepoSlug,
|
|
97
|
+
detectGhAuth: () => detectGhAuth,
|
|
98
|
+
applyRigSetupProject: () => applyRigSetupProject
|
|
99
|
+
});
|
|
100
|
+
import { spawnSync } from "child_process";
|
|
101
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
102
|
+
import { dirname, resolve } from "path";
|
|
103
|
+
import {
|
|
104
|
+
createGitHubAuthStore,
|
|
105
|
+
probeGitHubRepository,
|
|
106
|
+
resolveGitHubAuthStatus,
|
|
107
|
+
saveGitHubTokenForProject
|
|
108
|
+
} from "@rig/github-provider-plugin";
|
|
109
|
+
import { addPlacement, selectPlacement } from "@rig/runtime/control-plane/placement";
|
|
110
|
+
function cleanString(value) {
|
|
111
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
112
|
+
}
|
|
113
|
+
function parseRepoSlugFromRemote(remoteUrl) {
|
|
114
|
+
const trimmed = remoteUrl.trim();
|
|
115
|
+
const match = trimmed.match(/github\.com[:/]([^/\s]+)\/([^/\s.]+)(?:\.git)?$/i);
|
|
116
|
+
return match ? `${match[1]}/${match[2]}` : null;
|
|
117
|
+
}
|
|
118
|
+
function parseRepoSlug(value) {
|
|
119
|
+
const match = value.trim().match(/^([^/\s]+)\/([^/\s]+)$/);
|
|
120
|
+
if (!match)
|
|
121
|
+
throw new Error(`Invalid GitHub repo slug "${value}". Expected owner/repo.`);
|
|
122
|
+
return { owner: match[1], repo: match[2], slug: `${match[1]}/${match[2]}` };
|
|
123
|
+
}
|
|
124
|
+
function runSyncCommand(command, input = {}) {
|
|
125
|
+
const executable = command[0];
|
|
126
|
+
if (!executable)
|
|
127
|
+
throw new Error("command is required");
|
|
128
|
+
return (input.spawn ?? spawnSync)(executable, [...command.slice(1)], {
|
|
129
|
+
cwd: input.cwd,
|
|
130
|
+
encoding: "utf8",
|
|
131
|
+
timeout: input.timeoutMs ?? 1e4,
|
|
132
|
+
env: input.env ?? process.env
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
function detectOriginRepoSlug(projectRoot, deps = {}) {
|
|
136
|
+
const result = runSyncCommand(["git", "-C", projectRoot, "remote", "get-url", "origin"], { timeoutMs: 5000, spawn: deps.spawn });
|
|
137
|
+
if (result.status !== 0 || result.error)
|
|
138
|
+
return null;
|
|
139
|
+
return parseRepoSlugFromRemote(result.stdout.trim());
|
|
140
|
+
}
|
|
141
|
+
function connectionStatePath(projectRoot) {
|
|
142
|
+
return resolve(projectRoot, ".rig", "state", "connection.json");
|
|
143
|
+
}
|
|
144
|
+
function projectLinkStatePath(projectRoot) {
|
|
145
|
+
return resolve(projectRoot, ".rig", "state", "project-link.json");
|
|
146
|
+
}
|
|
147
|
+
function readJsonRecord(path) {
|
|
148
|
+
if (!existsSync(path))
|
|
149
|
+
return null;
|
|
150
|
+
try {
|
|
151
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
152
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
153
|
+
} catch {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function writeJsonFile(path, value) {
|
|
158
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
159
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}
|
|
160
|
+
`, "utf-8");
|
|
161
|
+
}
|
|
162
|
+
function ensureRigPrivateDirs(projectRoot) {
|
|
163
|
+
mkdirSync(resolve(projectRoot, ".rig", "state"), { recursive: true });
|
|
164
|
+
mkdirSync(resolve(projectRoot, ".rig", "logs"), { recursive: true });
|
|
165
|
+
mkdirSync(resolve(projectRoot, ".rig", "runs"), { recursive: true });
|
|
166
|
+
mkdirSync(resolve(projectRoot, ".rig", "tmp"), { recursive: true });
|
|
167
|
+
mkdirSync(resolve(projectRoot, "artifacts"), { recursive: true });
|
|
168
|
+
const taskConfigPath = resolve(projectRoot, ".rig", "task-config.json");
|
|
169
|
+
if (!existsSync(taskConfigPath))
|
|
170
|
+
writeFileSync(taskConfigPath, `{}
|
|
171
|
+
`, "utf-8");
|
|
172
|
+
}
|
|
173
|
+
function ensureGitignoreEntries(projectRoot) {
|
|
174
|
+
const path = resolve(projectRoot, ".gitignore");
|
|
175
|
+
const existing = existsSync(path) ? readFileSync(path, "utf-8") : "";
|
|
176
|
+
const lines = new Set(existing.split(/\r?\n/));
|
|
177
|
+
const missing = [".rig/state/", ".rig/logs/", ".rig/runs/", ".rig/tmp/"].filter((entry) => !lines.has(entry));
|
|
178
|
+
if (missing.length === 0)
|
|
179
|
+
return;
|
|
180
|
+
const prefix = existing.length > 0 && !existing.endsWith(`
|
|
181
|
+
`) ? `
|
|
182
|
+
` : "";
|
|
183
|
+
appendFileSync(path, `${prefix}${missing.join(`
|
|
184
|
+
`)}
|
|
185
|
+
`, "utf-8");
|
|
186
|
+
}
|
|
187
|
+
function writeRigConnectionState(projectRoot, slug, placement) {
|
|
188
|
+
const previous = readJsonRecord(connectionStatePath(projectRoot)) ?? {};
|
|
189
|
+
writeJsonFile(connectionStatePath(projectRoot), {
|
|
190
|
+
...previous,
|
|
191
|
+
selected: placement.alias,
|
|
192
|
+
project: slug,
|
|
193
|
+
linkedAt: new Date().toISOString()
|
|
194
|
+
});
|
|
195
|
+
writeJsonFile(projectLinkStatePath(projectRoot), {
|
|
196
|
+
repoSlug: slug,
|
|
197
|
+
connection: placement.alias,
|
|
198
|
+
linkedAt: new Date().toISOString()
|
|
199
|
+
});
|
|
200
|
+
if (placement.alias === "local")
|
|
201
|
+
delete process.env.RIG_REMOTE_ALIAS;
|
|
202
|
+
else
|
|
203
|
+
process.env.RIG_REMOTE_ALIAS = placement.alias;
|
|
204
|
+
}
|
|
205
|
+
function writeRigConfig(projectRoot, slug) {
|
|
206
|
+
const repo = parseRepoSlug(slug);
|
|
207
|
+
writeFileSync(resolve(projectRoot, "rig.config.ts"), buildRigInitConfigSource({
|
|
208
|
+
projectName: repo.slug,
|
|
209
|
+
projectRepo: repo.slug,
|
|
210
|
+
taskSource: { kind: "github-issues", owner: repo.owner, repo: repo.repo },
|
|
211
|
+
useStandardPlugin: true
|
|
212
|
+
}), "utf-8");
|
|
213
|
+
}
|
|
214
|
+
function readRigConfigStatus(projectRoot) {
|
|
215
|
+
const path = resolve(projectRoot, "rig.config.ts");
|
|
216
|
+
if (!existsSync(path))
|
|
217
|
+
return { exists: false, valid: false, path, slug: null, reason: "missing rig.config.ts" };
|
|
218
|
+
try {
|
|
219
|
+
const source = readFileSync(path, "utf-8");
|
|
220
|
+
const owner = source.match(/\bowner:\s*["']([^"']+)["']/)?.[1] ?? null;
|
|
221
|
+
const repoValues = [...source.matchAll(/\brepo:\s*["']([^"']+)["']/g)].map((match) => match[1]).filter(Boolean);
|
|
222
|
+
const taskRepo = repoValues.find((value) => !value.includes("/")) ?? null;
|
|
223
|
+
const projectRepo = repoValues.find((value) => value.includes("/")) ?? null;
|
|
224
|
+
const githubIssues = /\bkind:\s*["']github-issues["']/.test(source);
|
|
225
|
+
const slug = owner && taskRepo ? `${owner}/${taskRepo}` : projectRepo;
|
|
226
|
+
if (!githubIssues || !slug)
|
|
227
|
+
return { exists: true, valid: false, path, slug: slug ?? null, reason: "rig.config.ts is not a GitHub Issues Rig config" };
|
|
228
|
+
parseRepoSlug(slug);
|
|
229
|
+
return { exists: true, valid: true, path, slug };
|
|
230
|
+
} catch (error) {
|
|
231
|
+
return { exists: true, valid: false, path, slug: null, reason: error instanceof Error ? error.message : String(error) };
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function readRigConnectionStatus(projectRoot) {
|
|
235
|
+
const stateDir = resolve(projectRoot, ".rig", "state");
|
|
236
|
+
if (!existsSync(stateDir))
|
|
237
|
+
return { valid: false, selected: null, project: null, reason: "missing .rig/state" };
|
|
238
|
+
const connection = readJsonRecord(connectionStatePath(projectRoot));
|
|
239
|
+
if (!connection)
|
|
240
|
+
return { valid: false, selected: null, project: null, reason: "missing or invalid .rig/state/connection.json" };
|
|
241
|
+
const selected = cleanString(connection.selected);
|
|
242
|
+
const project = cleanString(connection.project);
|
|
243
|
+
if (!selected)
|
|
244
|
+
return { valid: false, selected: null, project, reason: "connection.json is missing selected placement" };
|
|
245
|
+
if (!project)
|
|
246
|
+
return { valid: false, selected, project: null, reason: "connection.json is missing project slug" };
|
|
247
|
+
try {
|
|
248
|
+
parseRepoSlug(project);
|
|
249
|
+
} catch (error) {
|
|
250
|
+
return { valid: false, selected, project, reason: error instanceof Error ? error.message : String(error) };
|
|
251
|
+
}
|
|
252
|
+
return { valid: true, selected, project };
|
|
253
|
+
}
|
|
254
|
+
function detectGhAuth(projectRoot, slug, deps = {}) {
|
|
255
|
+
const user = runSyncCommand(["gh", "api", "user", "--jq", ".login"], { cwd: projectRoot, timeoutMs: 5000, spawn: deps.spawn });
|
|
256
|
+
if (user.status !== 0 || user.error || !user.stdout.trim())
|
|
257
|
+
return null;
|
|
258
|
+
const repo = runSyncCommand(["gh", "repo", "view", slug, "--json", "nameWithOwner", "--jq", ".nameWithOwner"], { cwd: projectRoot, timeoutMs: 5000, spawn: deps.spawn });
|
|
259
|
+
if (repo.status !== 0 || repo.error)
|
|
260
|
+
return { ok: false, source: "gh", login: user.stdout.trim(), detail: (repo.stderr || repo.stdout || "gh cannot access the selected repository").trim() };
|
|
261
|
+
return { ok: true, source: "gh", login: user.stdout.trim(), detail: "gh CLI authentication can access the selected repository" };
|
|
262
|
+
}
|
|
263
|
+
async function validateGitHubAuth(projectRoot, slug, deps = {}) {
|
|
264
|
+
if (!slug)
|
|
265
|
+
return { ok: false, source: "missing", detail: "GitHub repo slug is unknown" };
|
|
266
|
+
const status = resolveGitHubAuthStatus({ projectRoot, oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
|
|
267
|
+
if (status.signedIn) {
|
|
268
|
+
const store = createGitHubAuthStore(projectRoot);
|
|
269
|
+
if (!status.selectedRepo) {
|
|
270
|
+
store.saveSelectedRepo(slug);
|
|
271
|
+
return { ok: true, source: "stored-token", login: status.login, detail: "stored Rig GitHub token selected for this repo", status: store.status({ oauthConfigured: status.oauthConfigured }) };
|
|
272
|
+
}
|
|
273
|
+
if (status.selectedRepo !== slug)
|
|
274
|
+
return { ok: false, source: "stored-token", login: status.login, detail: `stored GitHub token is scoped to ${status.selectedRepo}, not ${slug}`, status };
|
|
275
|
+
return { ok: true, source: "stored-token", login: status.login, detail: "stored Rig GitHub token is present", status };
|
|
276
|
+
}
|
|
277
|
+
const gh = detectGhAuth(projectRoot, slug, deps);
|
|
278
|
+
if (gh)
|
|
279
|
+
return gh;
|
|
280
|
+
return { ok: false, source: "missing", detail: "Sign in with `gh auth login`, choose Setup \u2192 GitHub auth, or paste a token." };
|
|
281
|
+
}
|
|
282
|
+
async function saveGitHubTokenLocally(projectRoot, token, slug, deps = {}) {
|
|
283
|
+
ensureRigPrivateDirs(projectRoot);
|
|
284
|
+
await saveGitHubTokenForProject({
|
|
285
|
+
projectRoot,
|
|
286
|
+
token,
|
|
287
|
+
tokenSource: "manual-token",
|
|
288
|
+
selectedRepo: slug,
|
|
289
|
+
...deps.fetchUser ? { fetchUser: deps.fetchUser } : {}
|
|
290
|
+
});
|
|
291
|
+
createGitHubAuthStore(projectRoot).copyToLocalProjectRoot(projectRoot);
|
|
292
|
+
}
|
|
293
|
+
function readGhAuthToken(projectRoot, deps = {}) {
|
|
294
|
+
const result = runSyncCommand(["gh", "auth", "token"], { cwd: projectRoot, timeoutMs: 1e4, spawn: deps.spawn });
|
|
295
|
+
if (result.status !== 0 || result.error || !result.stdout.trim())
|
|
296
|
+
throw new Error((result.stderr || result.stdout || "Could not read GitHub token from `gh auth token`.").trim());
|
|
297
|
+
return result.stdout.trim();
|
|
298
|
+
}
|
|
299
|
+
async function ensureGitHubAuth(input) {
|
|
300
|
+
const current = await validateGitHubAuth(input.projectRoot, input.slug, input.deps);
|
|
301
|
+
if (current.ok && !input.token && !input.importGhToken)
|
|
302
|
+
return current;
|
|
303
|
+
if (input.token?.trim())
|
|
304
|
+
await saveGitHubTokenLocally(input.projectRoot, input.token.trim(), input.slug, input.deps);
|
|
305
|
+
else if (input.importGhToken)
|
|
306
|
+
await saveGitHubTokenLocally(input.projectRoot, readGhAuthToken(input.projectRoot, input.deps), input.slug, input.deps);
|
|
307
|
+
return validateGitHubAuth(input.projectRoot, input.slug, input.deps);
|
|
308
|
+
}
|
|
309
|
+
async function ensureGitHubLabels(input) {
|
|
310
|
+
const repo = parseRepoSlug(input.slug);
|
|
311
|
+
const token = input.token?.trim() || createGitHubAuthStore(input.projectRoot).readToken();
|
|
312
|
+
if (token) {
|
|
313
|
+
const fetchLabels = input.deps?.fetch ?? fetch;
|
|
314
|
+
for (const name of RIG_LABELS_TO_ENSURE) {
|
|
315
|
+
const metadata = RIG_LABEL_METADATA[name];
|
|
316
|
+
const response = await fetchLabels(`https://api.github.com/repos/${encodeURIComponent(repo.owner)}/${encodeURIComponent(repo.repo)}/labels`, {
|
|
317
|
+
method: "POST",
|
|
318
|
+
headers: {
|
|
319
|
+
accept: "application/vnd.github+json",
|
|
320
|
+
authorization: `Bearer ${token}`,
|
|
321
|
+
"content-type": "application/json",
|
|
322
|
+
"user-agent": "rig"
|
|
323
|
+
},
|
|
324
|
+
body: JSON.stringify({ name, color: metadata.color, description: metadata.description })
|
|
325
|
+
});
|
|
326
|
+
if (response.ok)
|
|
327
|
+
continue;
|
|
328
|
+
const text = await response.text().catch(() => "");
|
|
329
|
+
if (response.status === 422 && /already_exists|already exists|exists/i.test(text))
|
|
330
|
+
continue;
|
|
331
|
+
throw new Error(`Could not create GitHub label ${name}: ${response.status} ${text || response.statusText}`);
|
|
332
|
+
}
|
|
333
|
+
return { ok: true, method: "api", labels: RIG_LABELS_TO_ENSURE };
|
|
334
|
+
}
|
|
335
|
+
const gh = detectGhAuth(input.projectRoot, input.slug, input.deps);
|
|
336
|
+
if (!gh?.ok)
|
|
337
|
+
throw new Error("GitHub labels require a stored Rig token or gh auth.");
|
|
338
|
+
for (const name of RIG_LABELS_TO_ENSURE) {
|
|
339
|
+
const metadata = RIG_LABEL_METADATA[name];
|
|
340
|
+
const result = runSyncCommand(["gh", "label", "create", name, "--repo", input.slug, "--color", metadata.color, "--description", metadata.description, "--force"], { cwd: input.projectRoot, timeoutMs: 1e4, spawn: input.deps?.spawn });
|
|
341
|
+
if (result.status !== 0 || result.error)
|
|
342
|
+
throw new Error(`gh label create ${name} failed: ${(result.stderr || result.stdout || result.error?.message || "unknown error").trim()}`);
|
|
343
|
+
}
|
|
344
|
+
return { ok: true, method: "gh", labels: RIG_LABELS_TO_ENSURE };
|
|
345
|
+
}
|
|
346
|
+
function piListContainsRigExtension(output) {
|
|
347
|
+
return output.split(/\r?\n/).some((line) => line.includes("@h-rig/pi-rig") || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(line));
|
|
348
|
+
}
|
|
349
|
+
function splitInstallCommand(value) {
|
|
350
|
+
return value.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g)?.map((part) => part.replace(/^["']|["']$/g, "")) ?? [];
|
|
351
|
+
}
|
|
352
|
+
function ensurePiRigInstalledForSetup(projectRoot, deps = {}) {
|
|
353
|
+
if (process.env.RIG_TEST_FAKE_PI_INSTALL === "1")
|
|
354
|
+
return { ok: true, detail: "fake-pi" };
|
|
355
|
+
let version = runSyncCommand(["pi", "--version"], { cwd: projectRoot, timeoutMs: 1e4, spawn: deps.spawn });
|
|
356
|
+
if (version.status !== 0 || version.error) {
|
|
357
|
+
const installCommand = process.env.RIG_PI_INSTALL_COMMAND?.trim();
|
|
358
|
+
if (!installCommand)
|
|
359
|
+
throw new Error(`Pi/OMP is not available: ${(version.stderr || version.stdout || version.error?.message || "pi --version failed").trim()}. Install Pi/OMP or set RIG_PI_INSTALL_COMMAND.`);
|
|
360
|
+
const parts = splitInstallCommand(installCommand);
|
|
361
|
+
if (parts.length === 0)
|
|
362
|
+
throw new Error("RIG_PI_INSTALL_COMMAND is empty.");
|
|
363
|
+
const install = runSyncCommand(parts, { cwd: projectRoot, timeoutMs: 120000, spawn: deps.spawn });
|
|
364
|
+
if (install.status !== 0 || install.error)
|
|
365
|
+
throw new Error(`Pi/OMP install command failed: ${(install.stderr || install.stdout || install.error?.message || "unknown error").trim()}`);
|
|
366
|
+
version = runSyncCommand(["pi", "--version"], { cwd: projectRoot, timeoutMs: 1e4, spawn: deps.spawn });
|
|
367
|
+
if (version.status !== 0 || version.error)
|
|
368
|
+
throw new Error(`Pi/OMP is still unavailable after install: ${(version.stderr || version.stdout || version.error?.message || "pi --version failed").trim()}`);
|
|
369
|
+
}
|
|
370
|
+
let list = runSyncCommand(["pi", "list"], { cwd: projectRoot, timeoutMs: 1e4, spawn: deps.spawn });
|
|
371
|
+
if (!piListContainsRigExtension(`${list.stdout}
|
|
372
|
+
${list.stderr}`)) {
|
|
373
|
+
const packageSource = existsSync(resolve(projectRoot, "packages", "pi-rig", "package.json")) ? resolve(projectRoot, "packages", "pi-rig") : "npm:@h-rig/pi-rig";
|
|
374
|
+
const install = runSyncCommand(["pi", "install", packageSource], { cwd: projectRoot, timeoutMs: 120000, spawn: deps.spawn });
|
|
375
|
+
if (install.status !== 0 || install.error)
|
|
376
|
+
throw new Error(`Could not install/register the Rig OMP extension: ${(install.stderr || install.stdout || install.error?.message || "pi install failed").trim()}`);
|
|
377
|
+
list = runSyncCommand(["pi", "list"], { cwd: projectRoot, timeoutMs: 1e4, spawn: deps.spawn });
|
|
378
|
+
if (!piListContainsRigExtension(`${list.stdout}
|
|
379
|
+
${list.stderr}`))
|
|
380
|
+
throw new Error("Pi/OMP is installed, but `pi list` does not show the Rig extension.");
|
|
381
|
+
}
|
|
382
|
+
return { ok: true, detail: (version.stdout || version.stderr).trim() || "pi available; rig extension registered" };
|
|
383
|
+
}
|
|
384
|
+
async function detectRigStartupStatus(input) {
|
|
385
|
+
const projectRoot = input.projectRoot;
|
|
386
|
+
const config = readRigConfigStatus(projectRoot);
|
|
387
|
+
const state = readRigConnectionStatus(projectRoot);
|
|
388
|
+
const detectedSlug = detectOriginRepoSlug(projectRoot, input.deps);
|
|
389
|
+
const slug = config.slug ?? state.project ?? detectedSlug;
|
|
390
|
+
const reasons = [];
|
|
391
|
+
if (!detectedSlug)
|
|
392
|
+
reasons.push("git origin does not point at a GitHub owner/repo remote");
|
|
393
|
+
if (!config.exists || !config.valid)
|
|
394
|
+
reasons.push(config.reason ?? "rig.config.ts is invalid");
|
|
395
|
+
if (!state.valid)
|
|
396
|
+
reasons.push(state.reason ?? ".rig/state/connection.json is invalid");
|
|
397
|
+
if (config.slug && state.project && config.slug !== state.project)
|
|
398
|
+
reasons.push(`rig.config.ts repo ${config.slug} does not match connection project ${state.project}`);
|
|
399
|
+
if (slug && detectedSlug && slug !== detectedSlug)
|
|
400
|
+
reasons.push(`configured repo ${slug} does not match git origin ${detectedSlug}`);
|
|
401
|
+
const auth = await validateGitHubAuth(projectRoot, slug, input.deps);
|
|
402
|
+
if (!auth.ok)
|
|
403
|
+
reasons.push(auth.detail);
|
|
404
|
+
return { configured: reasons.length === 0, projectRoot, slug, config, state, auth, reasons };
|
|
405
|
+
}
|
|
406
|
+
async function applyRigSetupProject(input) {
|
|
407
|
+
const repo = parseRepoSlug(input.slug);
|
|
408
|
+
ensureRigPrivateDirs(input.projectRoot);
|
|
409
|
+
ensureGitignoreEntries(input.projectRoot);
|
|
410
|
+
if (input.placement.alias !== "local" && input.placement.host) {
|
|
411
|
+
addPlacement(input.projectRoot, { alias: input.placement.alias, host: input.placement.host, port: input.placement.port, token: input.placement.token, select: true });
|
|
412
|
+
} else {
|
|
413
|
+
selectPlacement(input.projectRoot, input.placement.alias);
|
|
414
|
+
}
|
|
415
|
+
writeRigConnectionState(input.projectRoot, repo.slug, input.placement);
|
|
416
|
+
if (input.rewriteConfig)
|
|
417
|
+
writeRigConfig(input.projectRoot, repo.slug);
|
|
418
|
+
const labels = input.ensureLabels === false ? { skipped: true } : await ensureGitHubLabels({ projectRoot: input.projectRoot, slug: repo.slug, deps: input.deps });
|
|
419
|
+
const pi = input.ensurePi === false ? { skipped: true } : ensurePiRigInstalledForSetup(input.projectRoot, input.deps);
|
|
420
|
+
return { repoSlug: repo.slug, placement: input.placement.alias, configWritten: input.rewriteConfig, labels, pi };
|
|
421
|
+
}
|
|
422
|
+
async function runSetup(input) {
|
|
423
|
+
const repo = parseRepoSlug(input.slug);
|
|
424
|
+
const auth = await ensureGitHubAuth({ projectRoot: input.projectRoot, slug: repo.slug, token: input.githubToken, importGhToken: input.importGhToken, deps: input.deps });
|
|
425
|
+
if (!auth.ok)
|
|
426
|
+
throw new Error(auth.detail);
|
|
427
|
+
const token = createGitHubAuthStore(input.projectRoot).readToken();
|
|
428
|
+
const probe = await probeGitHubRepository({
|
|
429
|
+
owner: repo.owner,
|
|
430
|
+
repo: repo.repo,
|
|
431
|
+
token,
|
|
432
|
+
scopes: auth.status?.scopes ?? [],
|
|
433
|
+
...input.deps?.fetch ? { fetchRepository: input.deps.fetch } : {}
|
|
434
|
+
});
|
|
435
|
+
if (!probe.ok)
|
|
436
|
+
throw new Error(probe.message);
|
|
437
|
+
const result = await applyRigSetupProject({
|
|
438
|
+
projectRoot: input.projectRoot,
|
|
439
|
+
slug: repo.slug,
|
|
440
|
+
placement: input.placement,
|
|
441
|
+
rewriteConfig: input.rewriteConfig ?? true,
|
|
442
|
+
ensurePi: input.ensurePi,
|
|
443
|
+
ensureLabels: input.ensureLabels,
|
|
444
|
+
deps: input.deps
|
|
445
|
+
});
|
|
446
|
+
const status = await detectRigStartupStatus({ projectRoot: input.projectRoot, deps: input.deps });
|
|
447
|
+
if (!status.configured)
|
|
448
|
+
throw new Error(`Setup wrote state but doctor still reports: ${status.reasons.join("; ")}`);
|
|
449
|
+
return { ...result, status };
|
|
450
|
+
}
|
|
451
|
+
var RIG_LABELS_TO_ENSURE, RIG_LABEL_METADATA, detectStartupStatus;
|
|
452
|
+
var init_setup = __esm(() => {
|
|
453
|
+
RIG_LABELS_TO_ENSURE = [
|
|
454
|
+
"rig:running",
|
|
455
|
+
"rig:pr-open",
|
|
456
|
+
"rig:ci-fixing",
|
|
457
|
+
"rig:merging",
|
|
458
|
+
"rig:done",
|
|
459
|
+
"rig:needs-attention",
|
|
460
|
+
"rig:ready",
|
|
461
|
+
"rig:blocked",
|
|
462
|
+
"rig:generated"
|
|
463
|
+
];
|
|
464
|
+
RIG_LABEL_METADATA = {
|
|
465
|
+
"rig:running": { color: "1d76db", description: "Rig is actively working on this issue." },
|
|
466
|
+
"rig:pr-open": { color: "5319e7", description: "Rig opened a pull request for this issue." },
|
|
467
|
+
"rig:ci-fixing": { color: "fbca04", description: "Rig is fixing CI or review feedback for this issue." },
|
|
468
|
+
"rig:merging": { color: "0052cc", description: "Rig is merging the completed change for this issue." },
|
|
469
|
+
"rig:done": { color: "0e8a16", description: "Rig completed this issue." },
|
|
470
|
+
"rig:needs-attention": { color: "d93f0b", description: "Rig needs operator attention for this issue." },
|
|
471
|
+
"rig:ready": { color: "0e8a16", description: "Rig issue analysis marked this issue ready." },
|
|
472
|
+
"rig:blocked": { color: "d93f0b", description: "Rig issue analysis found blockers for this issue." },
|
|
473
|
+
"rig:generated": { color: "c5def5", description: "Rig generated this follow-up issue." }
|
|
474
|
+
};
|
|
475
|
+
detectStartupStatus = detectRigStartupStatus;
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
// packages/init-plugin/src/index.ts
|
|
479
|
+
init_setup();
|
|
480
|
+
|
|
481
|
+
// packages/init-plugin/src/plugin.ts
|
|
482
|
+
import { definePlugin } from "@rig/core/config";
|
|
483
|
+
var INIT_PLUGIN_NAME = "@rig/init-plugin";
|
|
484
|
+
var SETUP_CAPABILITY_ID = "workspace.setup";
|
|
485
|
+
var initPlugin = definePlugin({
|
|
486
|
+
name: INIT_PLUGIN_NAME,
|
|
487
|
+
version: "0.0.0-alpha.1",
|
|
488
|
+
contributes: {
|
|
489
|
+
capabilities: [
|
|
490
|
+
{
|
|
491
|
+
id: SETUP_CAPABILITY_ID,
|
|
492
|
+
title: "Project setup",
|
|
493
|
+
description: "Scaffold rig.config.ts, ensure rig:* labels, install the Pi/OMP extension, and verify GitHub auth.",
|
|
494
|
+
run: async (input) => (await Promise.resolve().then(() => (init_setup(), exports_setup))).runSetup(input)
|
|
495
|
+
}
|
|
496
|
+
]
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
function createInitPlugin() {
|
|
500
|
+
return initPlugin;
|
|
501
|
+
}
|
|
502
|
+
export {
|
|
503
|
+
writeRigConnectionState,
|
|
504
|
+
writeRigConfig,
|
|
505
|
+
validateGitHubAuth,
|
|
506
|
+
saveGitHubTokenLocally,
|
|
507
|
+
runSetup,
|
|
508
|
+
readRigConnectionStatus,
|
|
509
|
+
readRigConfigStatus,
|
|
510
|
+
readGhAuthToken,
|
|
511
|
+
parseRepoSlugFromRemote,
|
|
512
|
+
parseRepoSlug,
|
|
513
|
+
initPlugin,
|
|
514
|
+
ensureRigPrivateDirs,
|
|
515
|
+
ensurePiRigInstalledForSetup,
|
|
516
|
+
ensureGitignoreEntries,
|
|
517
|
+
ensureGitHubLabels,
|
|
518
|
+
ensureGitHubAuth,
|
|
519
|
+
detectStartupStatus,
|
|
520
|
+
detectRigStartupStatus,
|
|
521
|
+
detectOriginRepoSlug,
|
|
522
|
+
detectGhAuth,
|
|
523
|
+
createInitPlugin,
|
|
524
|
+
buildRigInitConfigSource,
|
|
525
|
+
applyRigSetupProject,
|
|
526
|
+
SETUP_CAPABILITY_ID,
|
|
527
|
+
INIT_PLUGIN_NAME
|
|
528
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const INIT_PLUGIN_NAME = "@rig/init-plugin";
|
|
2
|
+
export declare const SETUP_CAPABILITY_ID = "workspace.setup";
|
|
3
|
+
export declare const initPlugin: import("@rig/core").RigPlugin;
|
|
4
|
+
export declare function createInitPlugin(): import("@rig/core").RigPlugin;
|
|
5
|
+
export default initPlugin;
|