@h-rig/standard-plugin 0.0.6-alpha.1 → 0.0.6-alpha.100
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/src/files-source.d.ts +18 -0
- package/dist/src/files-source.js +4 -3
- package/dist/src/github-issues-source.d.ts +78 -0
- package/dist/src/github-issues-source.js +263 -24
- package/dist/src/index.d.ts +14 -0
- package/dist/src/index.js +327 -32
- package/package.json +6 -4
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { RegisteredTaskSource } from "@rig/contracts";
|
|
2
|
+
export interface FilesTaskSourceOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Directory containing one JSON file per task. Use either `path` (matches
|
|
5
|
+
* the `taskSource.path` field in rig.config.ts) or `dir` (back-compat).
|
|
6
|
+
*/
|
|
7
|
+
path?: string;
|
|
8
|
+
dir?: string;
|
|
9
|
+
pattern?: RegExp;
|
|
10
|
+
/**
|
|
11
|
+
* Root a relative `path`/`dir` resolves against. The serving process's cwd
|
|
12
|
+
* is NOT the project (workspace-spawned servers run from the engine
|
|
13
|
+
* checkout), so without this a relative path silently reads the WRONG
|
|
14
|
+
* repo's tasks. Defaults to cwd for direct programmatic use.
|
|
15
|
+
*/
|
|
16
|
+
projectRoot?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function createFilesTaskSource(opts: FilesTaskSourceOptions): RegisteredTaskSource;
|
package/dist/src/files-source.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/standard-plugin/src/files-source.ts
|
|
3
3
|
import { readFileSync, readdirSync, existsSync, statSync, writeFileSync } from "fs";
|
|
4
|
-
import { join, basename } from "path";
|
|
4
|
+
import { join, basename, isAbsolute, resolve } from "path";
|
|
5
5
|
var DEFAULT_PATTERN = /\.(task\.)?json$/;
|
|
6
6
|
function readTaskFile(file, pattern) {
|
|
7
7
|
const raw = JSON.parse(readFileSync(file, "utf-8"));
|
|
@@ -22,10 +22,11 @@ function readTaskFile(file, pattern) {
|
|
|
22
22
|
}
|
|
23
23
|
function createFilesTaskSource(opts) {
|
|
24
24
|
const pattern = opts.pattern ?? DEFAULT_PATTERN;
|
|
25
|
-
const
|
|
26
|
-
if (!
|
|
25
|
+
const configured = opts.path ?? opts.dir;
|
|
26
|
+
if (!configured) {
|
|
27
27
|
throw new Error("createFilesTaskSource: either `path` or `dir` must be provided");
|
|
28
28
|
}
|
|
29
|
+
const directory = isAbsolute(configured) ? configured : resolve(opts.projectRoot ?? process.cwd(), configured);
|
|
29
30
|
const findTaskFile = (id) => {
|
|
30
31
|
if (!existsSync(directory))
|
|
31
32
|
return;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import type { RegisteredTaskSource, TaskRecord } from "@rig/contracts";
|
|
3
|
+
export type GitHubCredentialPurpose = "selected-repo" | "admin-fallback";
|
|
4
|
+
export type GitHubIssueUpdatesMode = "lifecycle" | "minimal" | "off";
|
|
5
|
+
export interface GitHubCredentialProvider {
|
|
6
|
+
resolveGitHubToken(input: {
|
|
7
|
+
owner: string;
|
|
8
|
+
repo: string;
|
|
9
|
+
workspaceId: string;
|
|
10
|
+
userId?: string;
|
|
11
|
+
purpose: GitHubCredentialPurpose;
|
|
12
|
+
}): Promise<{
|
|
13
|
+
token: string;
|
|
14
|
+
source: "signed-in-user" | "host-admin-fallback";
|
|
15
|
+
}>;
|
|
16
|
+
}
|
|
17
|
+
export type GitHubProjectLifecycleStatus = "todo" | "running" | "prOpen" | "ciFixing" | "merging" | "done" | "needsAttention";
|
|
18
|
+
export interface GitHubProjectsOptions {
|
|
19
|
+
enabled?: boolean;
|
|
20
|
+
projectId?: string;
|
|
21
|
+
statusFieldId?: string;
|
|
22
|
+
statuses?: Partial<Record<GitHubProjectLifecycleStatus, string>>;
|
|
23
|
+
}
|
|
24
|
+
export interface GitHubIssuesOptions {
|
|
25
|
+
owner: string;
|
|
26
|
+
repo: string;
|
|
27
|
+
labels?: readonly string[];
|
|
28
|
+
state?: "open" | "closed" | "all";
|
|
29
|
+
assignee?: string;
|
|
30
|
+
ghBinary?: string;
|
|
31
|
+
workspaceId?: string;
|
|
32
|
+
userId?: string;
|
|
33
|
+
credentialProvider?: GitHubCredentialProvider;
|
|
34
|
+
issueUpdates?: GitHubIssueUpdatesMode;
|
|
35
|
+
/** Timeout for every gh CLI call. Defaults to 15 seconds. */
|
|
36
|
+
timeoutMs?: number;
|
|
37
|
+
/** Maximum issue-list rows before Rig fails loudly instead of silently truncating. Defaults to 1,000. */
|
|
38
|
+
listLimit?: number;
|
|
39
|
+
/** @internal — for testing. Override the spawnSync used by the adapter. */
|
|
40
|
+
spawn?: typeof spawnSync;
|
|
41
|
+
/** Notify the host that issue-backed task state changed and snapshots should refresh. */
|
|
42
|
+
onTaskChanged?: (event: {
|
|
43
|
+
repo: string;
|
|
44
|
+
id: string;
|
|
45
|
+
status?: string;
|
|
46
|
+
reason: "github-issue-updated";
|
|
47
|
+
}) => void;
|
|
48
|
+
/** Optional GitHub Projects (v2) status-field sync mapped from Rig task status. */
|
|
49
|
+
projects?: GitHubProjectsOptions;
|
|
50
|
+
}
|
|
51
|
+
export interface GitHubIssueCreateInput {
|
|
52
|
+
title: string;
|
|
53
|
+
body?: string;
|
|
54
|
+
labels?: readonly string[];
|
|
55
|
+
}
|
|
56
|
+
export interface GitHubIssuesTaskSource extends RegisteredTaskSource {
|
|
57
|
+
addLabels(id: string, labels: readonly string[]): Promise<void>;
|
|
58
|
+
removeLabels(id: string, labels: readonly string[]): Promise<void>;
|
|
59
|
+
createIssue(input: GitHubIssueCreateInput): Promise<TaskRecord>;
|
|
60
|
+
getIssueBody(id: string): Promise<string | undefined>;
|
|
61
|
+
}
|
|
62
|
+
export declare function createEnvGitHubCredentialProvider(): GitHubCredentialProvider;
|
|
63
|
+
export declare function createStateGitHubCredentialProvider(options?: {
|
|
64
|
+
stateFile?: string;
|
|
65
|
+
stateDir?: string;
|
|
66
|
+
}): GitHubCredentialProvider;
|
|
67
|
+
export declare const RIG_STATUS_COMMENT_MARKER = "<!-- rig:status-comment -->";
|
|
68
|
+
export declare const RIG_METADATA_START = "<!-- rig:metadata:start -->";
|
|
69
|
+
export declare const RIG_METADATA_END = "<!-- rig:metadata:end -->";
|
|
70
|
+
export declare function updateRigOwnedMetadataBlock(body: string, metadata: Record<string, unknown>): string;
|
|
71
|
+
export declare function buildRigStickyStatusComment(input: {
|
|
72
|
+
status: "running" | "prOpen" | "ciFixing" | "done" | "needsAttention" | string;
|
|
73
|
+
summary: string;
|
|
74
|
+
runId?: string;
|
|
75
|
+
prUrl?: string;
|
|
76
|
+
details?: readonly string[];
|
|
77
|
+
}): string;
|
|
78
|
+
export declare function createGitHubIssuesTaskSource(opts: GitHubIssuesOptions): GitHubIssuesTaskSource;
|
|
@@ -11,9 +11,9 @@ function createEnvGitHubCredentialProvider() {
|
|
|
11
11
|
return {
|
|
12
12
|
async resolveGitHubToken(input) {
|
|
13
13
|
if (input.purpose === "selected-repo") {
|
|
14
|
-
return { token: cleanToken(process.env.RIG_GITHUB_SELECTED_TOKEN ?? null) ?? "", source: "signed-in-user" };
|
|
14
|
+
return { token: cleanToken(process.env.RIG_GITHUB_SELECTED_TOKEN ?? process.env.RIG_GITHUB_TOKEN ?? null) ?? "", source: "signed-in-user" };
|
|
15
15
|
}
|
|
16
|
-
const token = cleanToken(process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN ?? null);
|
|
16
|
+
const token = cleanToken(process.env.RIG_GITHUB_TOKEN ?? process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN ?? null);
|
|
17
17
|
if (!token) {
|
|
18
18
|
throw new Error("No host GitHub token is configured for admin fallback.");
|
|
19
19
|
}
|
|
@@ -44,12 +44,12 @@ function createStateGitHubCredentialProvider(options = {}) {
|
|
|
44
44
|
async resolveGitHubToken(input) {
|
|
45
45
|
const token = readToken();
|
|
46
46
|
if (input.purpose === "selected-repo") {
|
|
47
|
-
return { token: token ?? "", source: "signed-in-user" };
|
|
47
|
+
return { token: token ?? cleanToken(process.env.RIG_GITHUB_SELECTED_TOKEN ?? process.env.RIG_GITHUB_TOKEN ?? null) ?? "", source: "signed-in-user" };
|
|
48
48
|
}
|
|
49
49
|
if (token) {
|
|
50
50
|
return { token, source: "signed-in-user" };
|
|
51
51
|
}
|
|
52
|
-
const fallback = cleanToken(process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN ?? null);
|
|
52
|
+
const fallback = cleanToken(process.env.RIG_GITHUB_TOKEN ?? process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN ?? null);
|
|
53
53
|
if (!fallback) {
|
|
54
54
|
throw new Error("No signed-in GitHub token is stored for Rig and no host admin fallback token is configured.");
|
|
55
55
|
}
|
|
@@ -111,8 +111,10 @@ function issueToTask(issue, repo) {
|
|
|
111
111
|
const role = roleLabel ? roleLabel.slice("role:".length) : undefined;
|
|
112
112
|
const validators = labelNames.filter((l) => l.startsWith("validator:")).map((l) => l.slice("validator:".length));
|
|
113
113
|
const body = issue.body ?? "";
|
|
114
|
+
const issueNodeId = issue.id ?? issue.nodeId ?? issue.node_id;
|
|
114
115
|
return {
|
|
115
116
|
id: String(issue.number),
|
|
117
|
+
...typeof issueNodeId === "string" && issueNodeId.trim() ? { issueNodeId: issueNodeId.trim() } : {},
|
|
116
118
|
deps: parseDeps(body),
|
|
117
119
|
status: statusFor(issue),
|
|
118
120
|
title: issue.title,
|
|
@@ -181,13 +183,21 @@ function isRigStickyStatusComment(body) {
|
|
|
181
183
|
return body.includes(RIG_STATUS_COMMENT_MARKER);
|
|
182
184
|
}
|
|
183
185
|
function ghSpawnOptions(extraEnv, timeoutMs) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
186
|
+
return {
|
|
187
|
+
encoding: "utf-8",
|
|
188
|
+
timeout: timeoutMs,
|
|
189
|
+
env: {
|
|
190
|
+
...process.env,
|
|
191
|
+
...process.env.GH_TOKEN !== undefined ? { GH_TOKEN: process.env.GH_TOKEN } : {},
|
|
192
|
+
...process.env.GITHUB_TOKEN !== undefined ? { GITHUB_TOKEN: process.env.GITHUB_TOKEN } : {},
|
|
193
|
+
...process.env.RIG_GITHUB_TOKEN !== undefined ? { RIG_GITHUB_TOKEN: process.env.RIG_GITHUB_TOKEN } : {},
|
|
194
|
+
...extraEnv ?? {}
|
|
195
|
+
}
|
|
196
|
+
};
|
|
187
197
|
}
|
|
188
198
|
function credentialEnv(token) {
|
|
189
199
|
const clean = token?.trim() ?? "";
|
|
190
|
-
return { GH_TOKEN: clean, GITHUB_TOKEN: clean };
|
|
200
|
+
return { GH_TOKEN: clean, GITHUB_TOKEN: clean, RIG_GITHUB_TOKEN: clean };
|
|
191
201
|
}
|
|
192
202
|
async function resolveCredentialEnv(opts, purpose) {
|
|
193
203
|
if (!opts.credentialProvider)
|
|
@@ -202,28 +212,239 @@ async function resolveCredentialEnv(opts, purpose) {
|
|
|
202
212
|
const resolved = await opts.credentialProvider.resolveGitHubToken(input);
|
|
203
213
|
return credentialEnv(resolved.token);
|
|
204
214
|
}
|
|
215
|
+
function tokenDiagnostic(value) {
|
|
216
|
+
const clean = value?.trim() ?? "";
|
|
217
|
+
return clean ? `present(len=${clean.length})` : "missing";
|
|
218
|
+
}
|
|
205
219
|
function runGh(bin, args, spawn, extraEnv, timeoutMs) {
|
|
206
|
-
const
|
|
207
|
-
|
|
220
|
+
const options = ghSpawnOptions(extraEnv, timeoutMs);
|
|
221
|
+
const res = spawn(bin, [...args], options);
|
|
222
|
+
assertGhSuccess(args, res, options.env);
|
|
208
223
|
if (!res.stdout || res.stdout.trim() === "")
|
|
209
224
|
return [];
|
|
210
225
|
return JSON.parse(res.stdout);
|
|
211
226
|
}
|
|
212
227
|
function runGhVoid(bin, args, spawn, extraEnv, timeoutMs) {
|
|
213
|
-
const
|
|
214
|
-
|
|
228
|
+
const options = ghSpawnOptions(extraEnv, timeoutMs);
|
|
229
|
+
const res = spawn(bin, [...args], options);
|
|
230
|
+
assertGhSuccess(args, res, options.env);
|
|
215
231
|
}
|
|
216
|
-
function assertGhSuccess(args, res) {
|
|
232
|
+
function assertGhSuccess(args, res, env) {
|
|
217
233
|
if (res.error) {
|
|
218
234
|
const msg = res.error.message ?? String(res.error);
|
|
219
235
|
throw new Error(`gh CLI not available \u2014 install gh (brew install gh / apt install gh): ${msg}`);
|
|
220
236
|
}
|
|
221
237
|
if (res.status !== 0) {
|
|
222
|
-
throw new Error(`gh ${args.join(" ")} failed (exit ${res.status}): ${res.stderr}
|
|
238
|
+
throw new Error(`gh ${args.join(" ")} failed (exit ${res.status}): ${res.stderr}
|
|
239
|
+
[rig gh env:standard-plugin] GH_TOKEN=${tokenDiagnostic(env.GH_TOKEN)} GITHUB_TOKEN=${tokenDiagnostic(env.GITHUB_TOKEN)} RIG_GITHUB_TOKEN=${tokenDiagnostic(env.RIG_GITHUB_TOKEN)}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
var DEFAULT_PROJECT_STATUSES = {
|
|
243
|
+
todo: "Todo",
|
|
244
|
+
running: "In Progress",
|
|
245
|
+
prOpen: "In Review",
|
|
246
|
+
ciFixing: "In Review",
|
|
247
|
+
merging: "In Review",
|
|
248
|
+
done: "Done",
|
|
249
|
+
needsAttention: "Needs Attention"
|
|
250
|
+
};
|
|
251
|
+
function asProjectRecord(value) {
|
|
252
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
253
|
+
}
|
|
254
|
+
function projectString(value) {
|
|
255
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
256
|
+
}
|
|
257
|
+
function projectLifecycleStatusForTaskStatus(status) {
|
|
258
|
+
const normalized = status?.trim().toLowerCase().replace(/[-\s]+/g, "_");
|
|
259
|
+
switch (normalized) {
|
|
260
|
+
case "draft":
|
|
261
|
+
case "open":
|
|
262
|
+
case "queued":
|
|
263
|
+
case "ready":
|
|
264
|
+
return "todo";
|
|
265
|
+
case "running":
|
|
266
|
+
case "in_progress":
|
|
267
|
+
return "running";
|
|
268
|
+
case "under_review":
|
|
269
|
+
case "review":
|
|
270
|
+
case "pr_open":
|
|
271
|
+
return "prOpen";
|
|
272
|
+
case "ci_fixing":
|
|
273
|
+
case "fixing":
|
|
274
|
+
return "ciFixing";
|
|
275
|
+
case "merging":
|
|
276
|
+
case "merge":
|
|
277
|
+
return "merging";
|
|
278
|
+
case "closed":
|
|
279
|
+
case "completed":
|
|
280
|
+
case "done":
|
|
281
|
+
return "done";
|
|
282
|
+
case "blocked":
|
|
283
|
+
case "cancelled":
|
|
284
|
+
case "failed":
|
|
285
|
+
case "needs_attention":
|
|
286
|
+
return "needsAttention";
|
|
287
|
+
default:
|
|
288
|
+
return null;
|
|
223
289
|
}
|
|
224
290
|
}
|
|
291
|
+
function ghGraphQLFetch(bin, spawnFn, extraEnv, timeoutMs) {
|
|
292
|
+
return async (query, variables) => {
|
|
293
|
+
const args = ["api", "graphql", "-f", `query=${query}`];
|
|
294
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
295
|
+
if (value === undefined || value === null)
|
|
296
|
+
continue;
|
|
297
|
+
args.push("-f", `${key}=${String(value)}`);
|
|
298
|
+
}
|
|
299
|
+
const response = runGh(bin, args, spawnFn, extraEnv, timeoutMs);
|
|
300
|
+
return asProjectRecord(response)?.data ?? response;
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
function projectStatusFieldFrom(data, projectId) {
|
|
304
|
+
const fields = asProjectRecord(asProjectRecord(asProjectRecord(data)?.node)?.fields)?.nodes;
|
|
305
|
+
for (const node of Array.isArray(fields) ? fields : []) {
|
|
306
|
+
const record = asProjectRecord(node);
|
|
307
|
+
if (projectString(record?.name)?.toLowerCase() !== "status")
|
|
308
|
+
continue;
|
|
309
|
+
const id = projectString(record?.id);
|
|
310
|
+
if (!id)
|
|
311
|
+
continue;
|
|
312
|
+
const options = Array.isArray(record?.options) ? record.options.flatMap((option) => {
|
|
313
|
+
const optionRecord = asProjectRecord(option);
|
|
314
|
+
const optionId = projectString(optionRecord?.id);
|
|
315
|
+
const name = projectString(optionRecord?.name);
|
|
316
|
+
return optionId && name ? [{ id: optionId, name }] : [];
|
|
317
|
+
}) : [];
|
|
318
|
+
return { id, name: "Status", options };
|
|
319
|
+
}
|
|
320
|
+
throw new Error(`GitHub Project ${projectId} does not expose a Status single-select field.`);
|
|
321
|
+
}
|
|
322
|
+
async function resolveProjectStatusField(input) {
|
|
323
|
+
const query = `
|
|
324
|
+
query RigProjectStatusField($projectId: ID!) {
|
|
325
|
+
node(id: $projectId) {
|
|
326
|
+
... on ProjectV2 {
|
|
327
|
+
fields(first: 50) {
|
|
328
|
+
nodes {
|
|
329
|
+
... on ProjectV2FieldCommon { id name }
|
|
330
|
+
... on ProjectV2SingleSelectField { id name options { id name } }
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
`;
|
|
337
|
+
return projectStatusFieldFrom(await input.fetchGraphQL(query, { projectId: input.projectId }, input.token), input.projectId);
|
|
338
|
+
}
|
|
339
|
+
async function ensureIssueProjectItem(input) {
|
|
340
|
+
const query = `
|
|
341
|
+
query RigFindProjectIssueItem($projectId: ID!) {
|
|
342
|
+
node(id: $projectId) {
|
|
343
|
+
... on ProjectV2 {
|
|
344
|
+
items(first: 100) { nodes { id content { ... on Issue { id } } } }
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
`;
|
|
349
|
+
const data = await input.fetchGraphQL(query, { projectId: input.projectId }, input.token);
|
|
350
|
+
const nodes = asProjectRecord(asProjectRecord(asProjectRecord(data)?.node)?.items)?.nodes;
|
|
351
|
+
for (const node of Array.isArray(nodes) ? nodes : []) {
|
|
352
|
+
const record = asProjectRecord(node);
|
|
353
|
+
const content = asProjectRecord(record?.content);
|
|
354
|
+
if (projectString(content?.id) === input.issueNodeId) {
|
|
355
|
+
const id2 = projectString(record?.id);
|
|
356
|
+
if (id2)
|
|
357
|
+
return { id: id2, created: false };
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
const mutation = `
|
|
361
|
+
mutation RigAddIssueToProject($projectId: ID!, $contentId: ID!) {
|
|
362
|
+
addProjectV2ItemById(input: { projectId: $projectId, contentId: $contentId }) { item { id } }
|
|
363
|
+
}
|
|
364
|
+
`;
|
|
365
|
+
const created = await input.fetchGraphQL(mutation, { projectId: input.projectId, contentId: input.issueNodeId }, input.token);
|
|
366
|
+
const addResult = asProjectRecord(asProjectRecord(created)?.addProjectV2ItemById);
|
|
367
|
+
const id = projectString(asProjectRecord(addResult?.item)?.id);
|
|
368
|
+
if (!id)
|
|
369
|
+
throw new Error("GitHub Project item creation did not return an item id.");
|
|
370
|
+
return { id, created: true };
|
|
371
|
+
}
|
|
372
|
+
async function updateIssueProjectStatus(input) {
|
|
373
|
+
const mutation = `
|
|
374
|
+
mutation RigUpdateProjectStatus($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
|
|
375
|
+
updateProjectV2ItemFieldValue(input: {
|
|
376
|
+
projectId: $projectId,
|
|
377
|
+
itemId: $itemId,
|
|
378
|
+
fieldId: $fieldId,
|
|
379
|
+
value: { singleSelectOptionId: $optionId }
|
|
380
|
+
}) { projectV2Item { id } }
|
|
381
|
+
}
|
|
382
|
+
`;
|
|
383
|
+
await input.fetchGraphQL(mutation, {
|
|
384
|
+
projectId: input.projectId,
|
|
385
|
+
itemId: input.itemId,
|
|
386
|
+
fieldId: input.fieldId,
|
|
387
|
+
optionId: input.optionId
|
|
388
|
+
}, input.token);
|
|
389
|
+
}
|
|
390
|
+
function fetchIssueNodeId(bin, repo, spawnFn, id, extraEnv, timeoutMs) {
|
|
391
|
+
const issue = runGh(bin, ["issue", "view", String(id), "--repo", repo, "--json", "id"], spawnFn, extraEnv, timeoutMs);
|
|
392
|
+
return projectString(issue.id) ?? projectString(issue.nodeId) ?? projectString(issue.node_id);
|
|
393
|
+
}
|
|
394
|
+
async function syncGitHubProjectStatus(bin, repo, spawnFn, id, status, projects, extraEnv, timeoutMs) {
|
|
395
|
+
if (!projects?.enabled)
|
|
396
|
+
return;
|
|
397
|
+
const projectId = projectString(projects.projectId);
|
|
398
|
+
if (!projectId)
|
|
399
|
+
throw new Error("GitHub Projects status sync is enabled but projectId is missing.");
|
|
400
|
+
const lifecycleStatus = projectLifecycleStatusForTaskStatus(status);
|
|
401
|
+
if (!lifecycleStatus)
|
|
402
|
+
return;
|
|
403
|
+
const issueNodeId = fetchIssueNodeId(bin, repo, spawnFn, id, extraEnv, timeoutMs);
|
|
404
|
+
if (!issueNodeId)
|
|
405
|
+
throw new Error(`GitHub issue ${repo}#${id} did not expose a node id for Projects status sync.`);
|
|
406
|
+
const projectStatus = projectString(projects.statuses?.[lifecycleStatus]) ?? DEFAULT_PROJECT_STATUSES[lifecycleStatus];
|
|
407
|
+
const fetchGraphQL = ghGraphQLFetch(bin, spawnFn, extraEnv, timeoutMs);
|
|
408
|
+
const field = await resolveProjectStatusField({ projectId, token: "gh-cli", fetchGraphQL });
|
|
409
|
+
const option = field.options.find((candidate) => candidate.name.toLowerCase() === projectStatus.toLowerCase() || candidate.id === projectStatus);
|
|
410
|
+
if (!option)
|
|
411
|
+
throw new Error(`GitHub Project ${projectId} Status field does not contain option "${projectStatus}".`);
|
|
412
|
+
const item = await ensureIssueProjectItem({ projectId, issueNodeId, token: "gh-cli", fetchGraphQL });
|
|
413
|
+
await updateIssueProjectStatus({
|
|
414
|
+
projectId,
|
|
415
|
+
itemId: item.id,
|
|
416
|
+
fieldId: projectString(projects.statusFieldId) ?? field.id,
|
|
417
|
+
optionId: option.id,
|
|
418
|
+
token: "gh-cli",
|
|
419
|
+
fetchGraphQL
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
var TERMINAL_TASK_STATUSES = new Set(["closed", "completed", "merged", "cancelled", "resolved", "done"]);
|
|
423
|
+
function normalizeTaskStatusToken(status) {
|
|
424
|
+
return status?.trim().toLowerCase().replace(/[-\s]+/g, "_") ?? "";
|
|
425
|
+
}
|
|
426
|
+
function issueUpdatesMode(value) {
|
|
427
|
+
return value === "off" || value === "minimal" || value === "lifecycle" ? value : "lifecycle";
|
|
428
|
+
}
|
|
429
|
+
function isTerminalTaskStatus(status) {
|
|
430
|
+
return TERMINAL_TASK_STATUSES.has(normalizeTaskStatusToken(status));
|
|
431
|
+
}
|
|
432
|
+
function shouldWriteIssueUpdate(mode, status) {
|
|
433
|
+
if (mode === "off")
|
|
434
|
+
return false;
|
|
435
|
+
if (mode === "lifecycle")
|
|
436
|
+
return true;
|
|
437
|
+
return isTerminalTaskStatus(status);
|
|
438
|
+
}
|
|
439
|
+
function isRunningStatus(status) {
|
|
440
|
+
return normalizeTaskStatusToken(status) === "running";
|
|
441
|
+
}
|
|
442
|
+
function assignRunningIssue(bin, repo, spawnFn, id, assignee, extraEnv, timeoutMs) {
|
|
443
|
+
runGhVoid(bin, ["issue", "edit", String(id), "--repo", repo, "--add-assignee", assignee?.trim() || "@me"], spawnFn, extraEnv, timeoutMs);
|
|
444
|
+
}
|
|
225
445
|
function statusLabelFor(status) {
|
|
226
446
|
switch (status) {
|
|
447
|
+
case "running":
|
|
227
448
|
case "in_progress":
|
|
228
449
|
return "in-progress";
|
|
229
450
|
case "blocked":
|
|
@@ -242,6 +463,8 @@ function statusLabelFor(status) {
|
|
|
242
463
|
return "under-review";
|
|
243
464
|
case "needs_attention":
|
|
244
465
|
return "blocked";
|
|
466
|
+
case "closed":
|
|
467
|
+
case "completed":
|
|
245
468
|
case "open":
|
|
246
469
|
return null;
|
|
247
470
|
default:
|
|
@@ -250,11 +473,13 @@ function statusLabelFor(status) {
|
|
|
250
473
|
}
|
|
251
474
|
function rigStatusLabelFor(status) {
|
|
252
475
|
switch (status) {
|
|
476
|
+
case "running":
|
|
253
477
|
case "in_progress":
|
|
254
478
|
return "rig:running";
|
|
255
479
|
case "under_review":
|
|
256
480
|
return "rig:pr-open";
|
|
257
481
|
case "closed":
|
|
482
|
+
case "completed":
|
|
258
483
|
return "rig:done";
|
|
259
484
|
case "ci_fixing":
|
|
260
485
|
return "rig:ci-fixing";
|
|
@@ -272,9 +497,10 @@ function rigStatusLabelFor(status) {
|
|
|
272
497
|
return null;
|
|
273
498
|
}
|
|
274
499
|
}
|
|
275
|
-
function applyIssueStatus(bin, repo, spawnFn, id, status, extraEnv, timeoutMs) {
|
|
276
|
-
const targetLabel = status === "closed" ? null : statusLabelFor(status);
|
|
500
|
+
async function applyIssueStatus(bin, repo, spawnFn, id, status, projects, runningAssignee, issueUpdates, extraEnv, timeoutMs) {
|
|
501
|
+
const targetLabel = status === "closed" || status === "completed" ? null : statusLabelFor(status);
|
|
277
502
|
const targetRigLabel = rigStatusLabelFor(status);
|
|
503
|
+
const shouldSyncLifecycle = shouldWriteIssueUpdate(issueUpdates, status);
|
|
278
504
|
for (const l of [...STATUS_LABELS, ...RIG_STATUS_LABELS]) {
|
|
279
505
|
if (targetLabel !== null && l === targetLabel)
|
|
280
506
|
continue;
|
|
@@ -296,7 +522,19 @@ function applyIssueStatus(bin, repo, spawnFn, id, status, extraEnv, timeoutMs) {
|
|
|
296
522
|
runGhVoid(bin, ["issue", "edit", String(id), "--repo", repo, "--add-label", label], spawnFn, extraEnv, timeoutMs);
|
|
297
523
|
}
|
|
298
524
|
}
|
|
299
|
-
if (status
|
|
525
|
+
if (isRunningStatus(status)) {
|
|
526
|
+
assignRunningIssue(bin, repo, spawnFn, id, runningAssignee, extraEnv, timeoutMs);
|
|
527
|
+
if (shouldSyncLifecycle) {
|
|
528
|
+
upsertRigStickyComment(bin, repo, spawnFn, String(id), buildRigStickyStatusComment({
|
|
529
|
+
status: "running",
|
|
530
|
+
summary: "Rig run started."
|
|
531
|
+
}), extraEnv, timeoutMs);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
if (shouldSyncLifecycle) {
|
|
535
|
+
await syncGitHubProjectStatus(bin, repo, spawnFn, id, status, projects, extraEnv, timeoutMs);
|
|
536
|
+
}
|
|
537
|
+
if (status === "closed" || status === "completed") {
|
|
300
538
|
runGhVoid(bin, ["issue", "close", String(id), "--repo", repo], spawnFn, extraEnv, timeoutMs);
|
|
301
539
|
}
|
|
302
540
|
}
|
|
@@ -366,11 +604,11 @@ function applyLabels(bin, repo, spawnFn, id, labels, action, extraEnv, timeoutMs
|
|
|
366
604
|
} catch {}
|
|
367
605
|
}
|
|
368
606
|
}
|
|
369
|
-
function applyIssueUpdate(bin, repo, spawnFn, id, update, extraEnv, timeoutMs) {
|
|
607
|
+
async function applyIssueUpdate(bin, repo, spawnFn, id, update, projects, runningAssignee, issueUpdates, extraEnv, timeoutMs) {
|
|
370
608
|
if (update.status) {
|
|
371
|
-
applyIssueStatus(bin, repo, spawnFn, id, update.status, extraEnv, timeoutMs);
|
|
609
|
+
await applyIssueStatus(bin, repo, spawnFn, id, update.status, projects, runningAssignee, issueUpdates, extraEnv, timeoutMs);
|
|
372
610
|
}
|
|
373
|
-
if (update.comment?.trim()) {
|
|
611
|
+
if (update.comment?.trim() && shouldWriteIssueUpdate(issueUpdates, update.status)) {
|
|
374
612
|
if (isRigStickyStatusComment(update.comment)) {
|
|
375
613
|
upsertRigStickyComment(bin, repo, spawnFn, String(id), update.comment, extraEnv, timeoutMs);
|
|
376
614
|
} else {
|
|
@@ -396,6 +634,7 @@ function createGitHubIssuesTaskSource(opts) {
|
|
|
396
634
|
const spawnFn = opts.spawn ?? spawnSync;
|
|
397
635
|
const timeoutMs = Math.max(1000, Math.trunc(opts.timeoutMs ?? DEFAULT_GH_TIMEOUT_MS));
|
|
398
636
|
const listLimit = Math.max(1, Math.trunc(opts.listLimit ?? DEFAULT_GITHUB_ISSUE_LIST_LIMIT));
|
|
637
|
+
const issueUpdates = issueUpdatesMode(opts.issueUpdates);
|
|
399
638
|
return {
|
|
400
639
|
id: "std:github-issues",
|
|
401
640
|
kind: "github-issues",
|
|
@@ -414,7 +653,7 @@ function createGitHubIssuesTaskSource(opts) {
|
|
|
414
653
|
"--limit",
|
|
415
654
|
String(listLimit),
|
|
416
655
|
"--json",
|
|
417
|
-
"number,title,body,labels,state,url,assignees"
|
|
656
|
+
"number,title,body,labels,state,url,assignees,id"
|
|
418
657
|
];
|
|
419
658
|
const env = await resolveCredentialEnv(opts, "selected-repo");
|
|
420
659
|
const rawIssues = runGh(bin, args, spawnFn, env, timeoutMs);
|
|
@@ -434,7 +673,7 @@ function createGitHubIssuesTaskSource(opts) {
|
|
|
434
673
|
"--repo",
|
|
435
674
|
repo,
|
|
436
675
|
"--json",
|
|
437
|
-
"number,title,body,labels,state,url,assignees"
|
|
676
|
+
"number,title,body,labels,state,url,assignees,id"
|
|
438
677
|
], spawnFn, env, timeoutMs);
|
|
439
678
|
return issueToTask(issue, repo);
|
|
440
679
|
} catch {
|
|
@@ -443,12 +682,12 @@ function createGitHubIssuesTaskSource(opts) {
|
|
|
443
682
|
},
|
|
444
683
|
async updateStatus(id, status) {
|
|
445
684
|
const env = await resolveCredentialEnv(opts, "selected-repo");
|
|
446
|
-
applyIssueStatus(bin, repo, spawnFn, id, status, env, timeoutMs);
|
|
685
|
+
await applyIssueStatus(bin, repo, spawnFn, id, status, opts.projects, opts.assignee, issueUpdates, env, timeoutMs);
|
|
447
686
|
notifyTaskChanged(opts.onTaskChanged, repo, id, status);
|
|
448
687
|
},
|
|
449
688
|
async updateTask(id, update) {
|
|
450
689
|
const env = await resolveCredentialEnv(opts, "selected-repo");
|
|
451
|
-
applyIssueUpdate(bin, repo, spawnFn, id, update, env, timeoutMs);
|
|
690
|
+
await applyIssueUpdate(bin, repo, spawnFn, id, update, opts.projects, opts.assignee, issueUpdates, env, timeoutMs);
|
|
452
691
|
notifyTaskChanged(opts.onTaskChanged, repo, id, update.status);
|
|
453
692
|
},
|
|
454
693
|
async addLabels(id, labels) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type RigPluginWithRuntime } from "@rig/core";
|
|
2
|
+
import { createEnvGitHubCredentialProvider, createStateGitHubCredentialProvider, createGitHubIssuesTaskSource, type GitHubCredentialProvider, type GitHubIssuesOptions } from "./github-issues-source";
|
|
3
|
+
import { createFilesTaskSource } from "./files-source";
|
|
4
|
+
export { createGitHubIssuesTaskSource, createEnvGitHubCredentialProvider, createStateGitHubCredentialProvider, createFilesTaskSource };
|
|
5
|
+
export type { GitHubIssuesOptions } from "./github-issues-source";
|
|
6
|
+
export type { FilesTaskSourceOptions } from "./files-source";
|
|
7
|
+
export interface StandardPluginOptions {
|
|
8
|
+
githubCredentialProvider?: GitHubCredentialProvider;
|
|
9
|
+
githubWorkspaceId?: string;
|
|
10
|
+
githubUserId?: string;
|
|
11
|
+
githubSpawn?: GitHubIssuesOptions["spawn"];
|
|
12
|
+
onGitHubTaskChanged?: GitHubIssuesOptions["onTaskChanged"];
|
|
13
|
+
}
|
|
14
|
+
export default function standardPlugin(opts?: StandardPluginOptions): RigPluginWithRuntime;
|
package/dist/src/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/standard-plugin/src/index.ts
|
|
3
|
+
import { resolve as resolve3 } from "path";
|
|
3
4
|
import { definePlugin } from "@rig/core";
|
|
4
5
|
|
|
5
6
|
// packages/standard-plugin/src/github-issues-source.ts
|
|
@@ -14,9 +15,9 @@ function createEnvGitHubCredentialProvider() {
|
|
|
14
15
|
return {
|
|
15
16
|
async resolveGitHubToken(input) {
|
|
16
17
|
if (input.purpose === "selected-repo") {
|
|
17
|
-
return { token: cleanToken(process.env.RIG_GITHUB_SELECTED_TOKEN ?? null) ?? "", source: "signed-in-user" };
|
|
18
|
+
return { token: cleanToken(process.env.RIG_GITHUB_SELECTED_TOKEN ?? process.env.RIG_GITHUB_TOKEN ?? null) ?? "", source: "signed-in-user" };
|
|
18
19
|
}
|
|
19
|
-
const token = cleanToken(process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN ?? null);
|
|
20
|
+
const token = cleanToken(process.env.RIG_GITHUB_TOKEN ?? process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN ?? null);
|
|
20
21
|
if (!token) {
|
|
21
22
|
throw new Error("No host GitHub token is configured for admin fallback.");
|
|
22
23
|
}
|
|
@@ -47,12 +48,12 @@ function createStateGitHubCredentialProvider(options = {}) {
|
|
|
47
48
|
async resolveGitHubToken(input) {
|
|
48
49
|
const token = readToken();
|
|
49
50
|
if (input.purpose === "selected-repo") {
|
|
50
|
-
return { token: token ?? "", source: "signed-in-user" };
|
|
51
|
+
return { token: token ?? cleanToken(process.env.RIG_GITHUB_SELECTED_TOKEN ?? process.env.RIG_GITHUB_TOKEN ?? null) ?? "", source: "signed-in-user" };
|
|
51
52
|
}
|
|
52
53
|
if (token) {
|
|
53
54
|
return { token, source: "signed-in-user" };
|
|
54
55
|
}
|
|
55
|
-
const fallback = cleanToken(process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN ?? null);
|
|
56
|
+
const fallback = cleanToken(process.env.RIG_GITHUB_TOKEN ?? process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN ?? null);
|
|
56
57
|
if (!fallback) {
|
|
57
58
|
throw new Error("No signed-in GitHub token is stored for Rig and no host admin fallback token is configured.");
|
|
58
59
|
}
|
|
@@ -114,8 +115,10 @@ function issueToTask(issue, repo) {
|
|
|
114
115
|
const role = roleLabel ? roleLabel.slice("role:".length) : undefined;
|
|
115
116
|
const validators = labelNames.filter((l) => l.startsWith("validator:")).map((l) => l.slice("validator:".length));
|
|
116
117
|
const body = issue.body ?? "";
|
|
118
|
+
const issueNodeId = issue.id ?? issue.nodeId ?? issue.node_id;
|
|
117
119
|
return {
|
|
118
120
|
id: String(issue.number),
|
|
121
|
+
...typeof issueNodeId === "string" && issueNodeId.trim() ? { issueNodeId: issueNodeId.trim() } : {},
|
|
119
122
|
deps: parseDeps(body),
|
|
120
123
|
status: statusFor(issue),
|
|
121
124
|
title: issue.title,
|
|
@@ -164,17 +167,41 @@ ${rendered}
|
|
|
164
167
|
` : `${rendered}
|
|
165
168
|
`;
|
|
166
169
|
}
|
|
170
|
+
function buildRigStickyStatusComment(input) {
|
|
171
|
+
const lines = [
|
|
172
|
+
RIG_STATUS_COMMENT_MARKER,
|
|
173
|
+
`### Rig status: ${input.status}`,
|
|
174
|
+
"",
|
|
175
|
+
input.summary
|
|
176
|
+
];
|
|
177
|
+
if (input.runId)
|
|
178
|
+
lines.push("", `- Run: ${input.runId}`);
|
|
179
|
+
if (input.prUrl)
|
|
180
|
+
lines.push(`- PR: ${input.prUrl}`);
|
|
181
|
+
for (const detail of input.details ?? [])
|
|
182
|
+
lines.push(`- ${detail}`);
|
|
183
|
+
return lines.join(`
|
|
184
|
+
`);
|
|
185
|
+
}
|
|
167
186
|
function isRigStickyStatusComment(body) {
|
|
168
187
|
return body.includes(RIG_STATUS_COMMENT_MARKER);
|
|
169
188
|
}
|
|
170
189
|
function ghSpawnOptions(extraEnv, timeoutMs) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
190
|
+
return {
|
|
191
|
+
encoding: "utf-8",
|
|
192
|
+
timeout: timeoutMs,
|
|
193
|
+
env: {
|
|
194
|
+
...process.env,
|
|
195
|
+
...process.env.GH_TOKEN !== undefined ? { GH_TOKEN: process.env.GH_TOKEN } : {},
|
|
196
|
+
...process.env.GITHUB_TOKEN !== undefined ? { GITHUB_TOKEN: process.env.GITHUB_TOKEN } : {},
|
|
197
|
+
...process.env.RIG_GITHUB_TOKEN !== undefined ? { RIG_GITHUB_TOKEN: process.env.RIG_GITHUB_TOKEN } : {},
|
|
198
|
+
...extraEnv ?? {}
|
|
199
|
+
}
|
|
200
|
+
};
|
|
174
201
|
}
|
|
175
202
|
function credentialEnv(token) {
|
|
176
203
|
const clean = token?.trim() ?? "";
|
|
177
|
-
return { GH_TOKEN: clean, GITHUB_TOKEN: clean };
|
|
204
|
+
return { GH_TOKEN: clean, GITHUB_TOKEN: clean, RIG_GITHUB_TOKEN: clean };
|
|
178
205
|
}
|
|
179
206
|
async function resolveCredentialEnv(opts, purpose) {
|
|
180
207
|
if (!opts.credentialProvider)
|
|
@@ -189,28 +216,239 @@ async function resolveCredentialEnv(opts, purpose) {
|
|
|
189
216
|
const resolved = await opts.credentialProvider.resolveGitHubToken(input);
|
|
190
217
|
return credentialEnv(resolved.token);
|
|
191
218
|
}
|
|
219
|
+
function tokenDiagnostic(value) {
|
|
220
|
+
const clean = value?.trim() ?? "";
|
|
221
|
+
return clean ? `present(len=${clean.length})` : "missing";
|
|
222
|
+
}
|
|
192
223
|
function runGh(bin, args, spawn, extraEnv, timeoutMs) {
|
|
193
|
-
const
|
|
194
|
-
|
|
224
|
+
const options = ghSpawnOptions(extraEnv, timeoutMs);
|
|
225
|
+
const res = spawn(bin, [...args], options);
|
|
226
|
+
assertGhSuccess(args, res, options.env);
|
|
195
227
|
if (!res.stdout || res.stdout.trim() === "")
|
|
196
228
|
return [];
|
|
197
229
|
return JSON.parse(res.stdout);
|
|
198
230
|
}
|
|
199
231
|
function runGhVoid(bin, args, spawn, extraEnv, timeoutMs) {
|
|
200
|
-
const
|
|
201
|
-
|
|
232
|
+
const options = ghSpawnOptions(extraEnv, timeoutMs);
|
|
233
|
+
const res = spawn(bin, [...args], options);
|
|
234
|
+
assertGhSuccess(args, res, options.env);
|
|
202
235
|
}
|
|
203
|
-
function assertGhSuccess(args, res) {
|
|
236
|
+
function assertGhSuccess(args, res, env) {
|
|
204
237
|
if (res.error) {
|
|
205
238
|
const msg = res.error.message ?? String(res.error);
|
|
206
239
|
throw new Error(`gh CLI not available \u2014 install gh (brew install gh / apt install gh): ${msg}`);
|
|
207
240
|
}
|
|
208
241
|
if (res.status !== 0) {
|
|
209
|
-
throw new Error(`gh ${args.join(" ")} failed (exit ${res.status}): ${res.stderr}
|
|
242
|
+
throw new Error(`gh ${args.join(" ")} failed (exit ${res.status}): ${res.stderr}
|
|
243
|
+
[rig gh env:standard-plugin] GH_TOKEN=${tokenDiagnostic(env.GH_TOKEN)} GITHUB_TOKEN=${tokenDiagnostic(env.GITHUB_TOKEN)} RIG_GITHUB_TOKEN=${tokenDiagnostic(env.RIG_GITHUB_TOKEN)}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
var DEFAULT_PROJECT_STATUSES = {
|
|
247
|
+
todo: "Todo",
|
|
248
|
+
running: "In Progress",
|
|
249
|
+
prOpen: "In Review",
|
|
250
|
+
ciFixing: "In Review",
|
|
251
|
+
merging: "In Review",
|
|
252
|
+
done: "Done",
|
|
253
|
+
needsAttention: "Needs Attention"
|
|
254
|
+
};
|
|
255
|
+
function asProjectRecord(value) {
|
|
256
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
257
|
+
}
|
|
258
|
+
function projectString(value) {
|
|
259
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
260
|
+
}
|
|
261
|
+
function projectLifecycleStatusForTaskStatus(status) {
|
|
262
|
+
const normalized = status?.trim().toLowerCase().replace(/[-\s]+/g, "_");
|
|
263
|
+
switch (normalized) {
|
|
264
|
+
case "draft":
|
|
265
|
+
case "open":
|
|
266
|
+
case "queued":
|
|
267
|
+
case "ready":
|
|
268
|
+
return "todo";
|
|
269
|
+
case "running":
|
|
270
|
+
case "in_progress":
|
|
271
|
+
return "running";
|
|
272
|
+
case "under_review":
|
|
273
|
+
case "review":
|
|
274
|
+
case "pr_open":
|
|
275
|
+
return "prOpen";
|
|
276
|
+
case "ci_fixing":
|
|
277
|
+
case "fixing":
|
|
278
|
+
return "ciFixing";
|
|
279
|
+
case "merging":
|
|
280
|
+
case "merge":
|
|
281
|
+
return "merging";
|
|
282
|
+
case "closed":
|
|
283
|
+
case "completed":
|
|
284
|
+
case "done":
|
|
285
|
+
return "done";
|
|
286
|
+
case "blocked":
|
|
287
|
+
case "cancelled":
|
|
288
|
+
case "failed":
|
|
289
|
+
case "needs_attention":
|
|
290
|
+
return "needsAttention";
|
|
291
|
+
default:
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function ghGraphQLFetch(bin, spawnFn, extraEnv, timeoutMs) {
|
|
296
|
+
return async (query, variables) => {
|
|
297
|
+
const args = ["api", "graphql", "-f", `query=${query}`];
|
|
298
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
299
|
+
if (value === undefined || value === null)
|
|
300
|
+
continue;
|
|
301
|
+
args.push("-f", `${key}=${String(value)}`);
|
|
302
|
+
}
|
|
303
|
+
const response = runGh(bin, args, spawnFn, extraEnv, timeoutMs);
|
|
304
|
+
return asProjectRecord(response)?.data ?? response;
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
function projectStatusFieldFrom(data, projectId) {
|
|
308
|
+
const fields = asProjectRecord(asProjectRecord(asProjectRecord(data)?.node)?.fields)?.nodes;
|
|
309
|
+
for (const node of Array.isArray(fields) ? fields : []) {
|
|
310
|
+
const record = asProjectRecord(node);
|
|
311
|
+
if (projectString(record?.name)?.toLowerCase() !== "status")
|
|
312
|
+
continue;
|
|
313
|
+
const id = projectString(record?.id);
|
|
314
|
+
if (!id)
|
|
315
|
+
continue;
|
|
316
|
+
const options = Array.isArray(record?.options) ? record.options.flatMap((option) => {
|
|
317
|
+
const optionRecord = asProjectRecord(option);
|
|
318
|
+
const optionId = projectString(optionRecord?.id);
|
|
319
|
+
const name = projectString(optionRecord?.name);
|
|
320
|
+
return optionId && name ? [{ id: optionId, name }] : [];
|
|
321
|
+
}) : [];
|
|
322
|
+
return { id, name: "Status", options };
|
|
210
323
|
}
|
|
324
|
+
throw new Error(`GitHub Project ${projectId} does not expose a Status single-select field.`);
|
|
325
|
+
}
|
|
326
|
+
async function resolveProjectStatusField(input) {
|
|
327
|
+
const query = `
|
|
328
|
+
query RigProjectStatusField($projectId: ID!) {
|
|
329
|
+
node(id: $projectId) {
|
|
330
|
+
... on ProjectV2 {
|
|
331
|
+
fields(first: 50) {
|
|
332
|
+
nodes {
|
|
333
|
+
... on ProjectV2FieldCommon { id name }
|
|
334
|
+
... on ProjectV2SingleSelectField { id name options { id name } }
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
`;
|
|
341
|
+
return projectStatusFieldFrom(await input.fetchGraphQL(query, { projectId: input.projectId }, input.token), input.projectId);
|
|
342
|
+
}
|
|
343
|
+
async function ensureIssueProjectItem(input) {
|
|
344
|
+
const query = `
|
|
345
|
+
query RigFindProjectIssueItem($projectId: ID!) {
|
|
346
|
+
node(id: $projectId) {
|
|
347
|
+
... on ProjectV2 {
|
|
348
|
+
items(first: 100) { nodes { id content { ... on Issue { id } } } }
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
`;
|
|
353
|
+
const data = await input.fetchGraphQL(query, { projectId: input.projectId }, input.token);
|
|
354
|
+
const nodes = asProjectRecord(asProjectRecord(asProjectRecord(data)?.node)?.items)?.nodes;
|
|
355
|
+
for (const node of Array.isArray(nodes) ? nodes : []) {
|
|
356
|
+
const record = asProjectRecord(node);
|
|
357
|
+
const content = asProjectRecord(record?.content);
|
|
358
|
+
if (projectString(content?.id) === input.issueNodeId) {
|
|
359
|
+
const id2 = projectString(record?.id);
|
|
360
|
+
if (id2)
|
|
361
|
+
return { id: id2, created: false };
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
const mutation = `
|
|
365
|
+
mutation RigAddIssueToProject($projectId: ID!, $contentId: ID!) {
|
|
366
|
+
addProjectV2ItemById(input: { projectId: $projectId, contentId: $contentId }) { item { id } }
|
|
367
|
+
}
|
|
368
|
+
`;
|
|
369
|
+
const created = await input.fetchGraphQL(mutation, { projectId: input.projectId, contentId: input.issueNodeId }, input.token);
|
|
370
|
+
const addResult = asProjectRecord(asProjectRecord(created)?.addProjectV2ItemById);
|
|
371
|
+
const id = projectString(asProjectRecord(addResult?.item)?.id);
|
|
372
|
+
if (!id)
|
|
373
|
+
throw new Error("GitHub Project item creation did not return an item id.");
|
|
374
|
+
return { id, created: true };
|
|
375
|
+
}
|
|
376
|
+
async function updateIssueProjectStatus(input) {
|
|
377
|
+
const mutation = `
|
|
378
|
+
mutation RigUpdateProjectStatus($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
|
|
379
|
+
updateProjectV2ItemFieldValue(input: {
|
|
380
|
+
projectId: $projectId,
|
|
381
|
+
itemId: $itemId,
|
|
382
|
+
fieldId: $fieldId,
|
|
383
|
+
value: { singleSelectOptionId: $optionId }
|
|
384
|
+
}) { projectV2Item { id } }
|
|
385
|
+
}
|
|
386
|
+
`;
|
|
387
|
+
await input.fetchGraphQL(mutation, {
|
|
388
|
+
projectId: input.projectId,
|
|
389
|
+
itemId: input.itemId,
|
|
390
|
+
fieldId: input.fieldId,
|
|
391
|
+
optionId: input.optionId
|
|
392
|
+
}, input.token);
|
|
393
|
+
}
|
|
394
|
+
function fetchIssueNodeId(bin, repo, spawnFn, id, extraEnv, timeoutMs) {
|
|
395
|
+
const issue = runGh(bin, ["issue", "view", String(id), "--repo", repo, "--json", "id"], spawnFn, extraEnv, timeoutMs);
|
|
396
|
+
return projectString(issue.id) ?? projectString(issue.nodeId) ?? projectString(issue.node_id);
|
|
397
|
+
}
|
|
398
|
+
async function syncGitHubProjectStatus(bin, repo, spawnFn, id, status, projects, extraEnv, timeoutMs) {
|
|
399
|
+
if (!projects?.enabled)
|
|
400
|
+
return;
|
|
401
|
+
const projectId = projectString(projects.projectId);
|
|
402
|
+
if (!projectId)
|
|
403
|
+
throw new Error("GitHub Projects status sync is enabled but projectId is missing.");
|
|
404
|
+
const lifecycleStatus = projectLifecycleStatusForTaskStatus(status);
|
|
405
|
+
if (!lifecycleStatus)
|
|
406
|
+
return;
|
|
407
|
+
const issueNodeId = fetchIssueNodeId(bin, repo, spawnFn, id, extraEnv, timeoutMs);
|
|
408
|
+
if (!issueNodeId)
|
|
409
|
+
throw new Error(`GitHub issue ${repo}#${id} did not expose a node id for Projects status sync.`);
|
|
410
|
+
const projectStatus = projectString(projects.statuses?.[lifecycleStatus]) ?? DEFAULT_PROJECT_STATUSES[lifecycleStatus];
|
|
411
|
+
const fetchGraphQL = ghGraphQLFetch(bin, spawnFn, extraEnv, timeoutMs);
|
|
412
|
+
const field = await resolveProjectStatusField({ projectId, token: "gh-cli", fetchGraphQL });
|
|
413
|
+
const option = field.options.find((candidate) => candidate.name.toLowerCase() === projectStatus.toLowerCase() || candidate.id === projectStatus);
|
|
414
|
+
if (!option)
|
|
415
|
+
throw new Error(`GitHub Project ${projectId} Status field does not contain option "${projectStatus}".`);
|
|
416
|
+
const item = await ensureIssueProjectItem({ projectId, issueNodeId, token: "gh-cli", fetchGraphQL });
|
|
417
|
+
await updateIssueProjectStatus({
|
|
418
|
+
projectId,
|
|
419
|
+
itemId: item.id,
|
|
420
|
+
fieldId: projectString(projects.statusFieldId) ?? field.id,
|
|
421
|
+
optionId: option.id,
|
|
422
|
+
token: "gh-cli",
|
|
423
|
+
fetchGraphQL
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
var TERMINAL_TASK_STATUSES = new Set(["closed", "completed", "merged", "cancelled", "resolved", "done"]);
|
|
427
|
+
function normalizeTaskStatusToken(status) {
|
|
428
|
+
return status?.trim().toLowerCase().replace(/[-\s]+/g, "_") ?? "";
|
|
429
|
+
}
|
|
430
|
+
function issueUpdatesMode(value) {
|
|
431
|
+
return value === "off" || value === "minimal" || value === "lifecycle" ? value : "lifecycle";
|
|
432
|
+
}
|
|
433
|
+
function isTerminalTaskStatus(status) {
|
|
434
|
+
return TERMINAL_TASK_STATUSES.has(normalizeTaskStatusToken(status));
|
|
435
|
+
}
|
|
436
|
+
function shouldWriteIssueUpdate(mode, status) {
|
|
437
|
+
if (mode === "off")
|
|
438
|
+
return false;
|
|
439
|
+
if (mode === "lifecycle")
|
|
440
|
+
return true;
|
|
441
|
+
return isTerminalTaskStatus(status);
|
|
442
|
+
}
|
|
443
|
+
function isRunningStatus(status) {
|
|
444
|
+
return normalizeTaskStatusToken(status) === "running";
|
|
445
|
+
}
|
|
446
|
+
function assignRunningIssue(bin, repo, spawnFn, id, assignee, extraEnv, timeoutMs) {
|
|
447
|
+
runGhVoid(bin, ["issue", "edit", String(id), "--repo", repo, "--add-assignee", assignee?.trim() || "@me"], spawnFn, extraEnv, timeoutMs);
|
|
211
448
|
}
|
|
212
449
|
function statusLabelFor(status) {
|
|
213
450
|
switch (status) {
|
|
451
|
+
case "running":
|
|
214
452
|
case "in_progress":
|
|
215
453
|
return "in-progress";
|
|
216
454
|
case "blocked":
|
|
@@ -229,6 +467,8 @@ function statusLabelFor(status) {
|
|
|
229
467
|
return "under-review";
|
|
230
468
|
case "needs_attention":
|
|
231
469
|
return "blocked";
|
|
470
|
+
case "closed":
|
|
471
|
+
case "completed":
|
|
232
472
|
case "open":
|
|
233
473
|
return null;
|
|
234
474
|
default:
|
|
@@ -237,11 +477,13 @@ function statusLabelFor(status) {
|
|
|
237
477
|
}
|
|
238
478
|
function rigStatusLabelFor(status) {
|
|
239
479
|
switch (status) {
|
|
480
|
+
case "running":
|
|
240
481
|
case "in_progress":
|
|
241
482
|
return "rig:running";
|
|
242
483
|
case "under_review":
|
|
243
484
|
return "rig:pr-open";
|
|
244
485
|
case "closed":
|
|
486
|
+
case "completed":
|
|
245
487
|
return "rig:done";
|
|
246
488
|
case "ci_fixing":
|
|
247
489
|
return "rig:ci-fixing";
|
|
@@ -259,9 +501,10 @@ function rigStatusLabelFor(status) {
|
|
|
259
501
|
return null;
|
|
260
502
|
}
|
|
261
503
|
}
|
|
262
|
-
function applyIssueStatus(bin, repo, spawnFn, id, status, extraEnv, timeoutMs) {
|
|
263
|
-
const targetLabel = status === "closed" ? null : statusLabelFor(status);
|
|
504
|
+
async function applyIssueStatus(bin, repo, spawnFn, id, status, projects, runningAssignee, issueUpdates, extraEnv, timeoutMs) {
|
|
505
|
+
const targetLabel = status === "closed" || status === "completed" ? null : statusLabelFor(status);
|
|
264
506
|
const targetRigLabel = rigStatusLabelFor(status);
|
|
507
|
+
const shouldSyncLifecycle = shouldWriteIssueUpdate(issueUpdates, status);
|
|
265
508
|
for (const l of [...STATUS_LABELS, ...RIG_STATUS_LABELS]) {
|
|
266
509
|
if (targetLabel !== null && l === targetLabel)
|
|
267
510
|
continue;
|
|
@@ -283,7 +526,19 @@ function applyIssueStatus(bin, repo, spawnFn, id, status, extraEnv, timeoutMs) {
|
|
|
283
526
|
runGhVoid(bin, ["issue", "edit", String(id), "--repo", repo, "--add-label", label], spawnFn, extraEnv, timeoutMs);
|
|
284
527
|
}
|
|
285
528
|
}
|
|
286
|
-
if (status
|
|
529
|
+
if (isRunningStatus(status)) {
|
|
530
|
+
assignRunningIssue(bin, repo, spawnFn, id, runningAssignee, extraEnv, timeoutMs);
|
|
531
|
+
if (shouldSyncLifecycle) {
|
|
532
|
+
upsertRigStickyComment(bin, repo, spawnFn, String(id), buildRigStickyStatusComment({
|
|
533
|
+
status: "running",
|
|
534
|
+
summary: "Rig run started."
|
|
535
|
+
}), extraEnv, timeoutMs);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
if (shouldSyncLifecycle) {
|
|
539
|
+
await syncGitHubProjectStatus(bin, repo, spawnFn, id, status, projects, extraEnv, timeoutMs);
|
|
540
|
+
}
|
|
541
|
+
if (status === "closed" || status === "completed") {
|
|
287
542
|
runGhVoid(bin, ["issue", "close", String(id), "--repo", repo], spawnFn, extraEnv, timeoutMs);
|
|
288
543
|
}
|
|
289
544
|
}
|
|
@@ -353,11 +608,11 @@ function applyLabels(bin, repo, spawnFn, id, labels, action, extraEnv, timeoutMs
|
|
|
353
608
|
} catch {}
|
|
354
609
|
}
|
|
355
610
|
}
|
|
356
|
-
function applyIssueUpdate(bin, repo, spawnFn, id, update, extraEnv, timeoutMs) {
|
|
611
|
+
async function applyIssueUpdate(bin, repo, spawnFn, id, update, projects, runningAssignee, issueUpdates, extraEnv, timeoutMs) {
|
|
357
612
|
if (update.status) {
|
|
358
|
-
applyIssueStatus(bin, repo, spawnFn, id, update.status, extraEnv, timeoutMs);
|
|
613
|
+
await applyIssueStatus(bin, repo, spawnFn, id, update.status, projects, runningAssignee, issueUpdates, extraEnv, timeoutMs);
|
|
359
614
|
}
|
|
360
|
-
if (update.comment?.trim()) {
|
|
615
|
+
if (update.comment?.trim() && shouldWriteIssueUpdate(issueUpdates, update.status)) {
|
|
361
616
|
if (isRigStickyStatusComment(update.comment)) {
|
|
362
617
|
upsertRigStickyComment(bin, repo, spawnFn, String(id), update.comment, extraEnv, timeoutMs);
|
|
363
618
|
} else {
|
|
@@ -383,6 +638,7 @@ function createGitHubIssuesTaskSource(opts) {
|
|
|
383
638
|
const spawnFn = opts.spawn ?? spawnSync;
|
|
384
639
|
const timeoutMs = Math.max(1000, Math.trunc(opts.timeoutMs ?? DEFAULT_GH_TIMEOUT_MS));
|
|
385
640
|
const listLimit = Math.max(1, Math.trunc(opts.listLimit ?? DEFAULT_GITHUB_ISSUE_LIST_LIMIT));
|
|
641
|
+
const issueUpdates = issueUpdatesMode(opts.issueUpdates);
|
|
386
642
|
return {
|
|
387
643
|
id: "std:github-issues",
|
|
388
644
|
kind: "github-issues",
|
|
@@ -401,7 +657,7 @@ function createGitHubIssuesTaskSource(opts) {
|
|
|
401
657
|
"--limit",
|
|
402
658
|
String(listLimit),
|
|
403
659
|
"--json",
|
|
404
|
-
"number,title,body,labels,state,url,assignees"
|
|
660
|
+
"number,title,body,labels,state,url,assignees,id"
|
|
405
661
|
];
|
|
406
662
|
const env = await resolveCredentialEnv(opts, "selected-repo");
|
|
407
663
|
const rawIssues = runGh(bin, args, spawnFn, env, timeoutMs);
|
|
@@ -421,7 +677,7 @@ function createGitHubIssuesTaskSource(opts) {
|
|
|
421
677
|
"--repo",
|
|
422
678
|
repo,
|
|
423
679
|
"--json",
|
|
424
|
-
"number,title,body,labels,state,url,assignees"
|
|
680
|
+
"number,title,body,labels,state,url,assignees,id"
|
|
425
681
|
], spawnFn, env, timeoutMs);
|
|
426
682
|
return issueToTask(issue, repo);
|
|
427
683
|
} catch {
|
|
@@ -430,12 +686,12 @@ function createGitHubIssuesTaskSource(opts) {
|
|
|
430
686
|
},
|
|
431
687
|
async updateStatus(id, status) {
|
|
432
688
|
const env = await resolveCredentialEnv(opts, "selected-repo");
|
|
433
|
-
applyIssueStatus(bin, repo, spawnFn, id, status, env, timeoutMs);
|
|
689
|
+
await applyIssueStatus(bin, repo, spawnFn, id, status, opts.projects, opts.assignee, issueUpdates, env, timeoutMs);
|
|
434
690
|
notifyTaskChanged(opts.onTaskChanged, repo, id, status);
|
|
435
691
|
},
|
|
436
692
|
async updateTask(id, update) {
|
|
437
693
|
const env = await resolveCredentialEnv(opts, "selected-repo");
|
|
438
|
-
applyIssueUpdate(bin, repo, spawnFn, id, update, env, timeoutMs);
|
|
694
|
+
await applyIssueUpdate(bin, repo, spawnFn, id, update, opts.projects, opts.assignee, issueUpdates, env, timeoutMs);
|
|
439
695
|
notifyTaskChanged(opts.onTaskChanged, repo, id, update.status);
|
|
440
696
|
},
|
|
441
697
|
async addLabels(id, labels) {
|
|
@@ -474,7 +730,7 @@ function createGitHubIssuesTaskSource(opts) {
|
|
|
474
730
|
|
|
475
731
|
// packages/standard-plugin/src/files-source.ts
|
|
476
732
|
import { readFileSync as readFileSync2, readdirSync, existsSync as existsSync2, statSync, writeFileSync } from "fs";
|
|
477
|
-
import { join, basename } from "path";
|
|
733
|
+
import { join, basename, isAbsolute, resolve as resolve2 } from "path";
|
|
478
734
|
var DEFAULT_PATTERN = /\.(task\.)?json$/;
|
|
479
735
|
function readTaskFile(file, pattern) {
|
|
480
736
|
const raw = JSON.parse(readFileSync2(file, "utf-8"));
|
|
@@ -495,10 +751,11 @@ function readTaskFile(file, pattern) {
|
|
|
495
751
|
}
|
|
496
752
|
function createFilesTaskSource(opts) {
|
|
497
753
|
const pattern = opts.pattern ?? DEFAULT_PATTERN;
|
|
498
|
-
const
|
|
499
|
-
if (!
|
|
754
|
+
const configured = opts.path ?? opts.dir;
|
|
755
|
+
if (!configured) {
|
|
500
756
|
throw new Error("createFilesTaskSource: either `path` or `dir` must be provided");
|
|
501
757
|
}
|
|
758
|
+
const directory = isAbsolute(configured) ? configured : resolve2(opts.projectRoot ?? process.cwd(), configured);
|
|
502
759
|
const findTaskFile = (id) => {
|
|
503
760
|
if (!existsSync2(directory))
|
|
504
761
|
return;
|
|
@@ -583,6 +840,40 @@ function requireStringField(config, field, kind) {
|
|
|
583
840
|
}
|
|
584
841
|
return value;
|
|
585
842
|
}
|
|
843
|
+
function isRecord(value) {
|
|
844
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
845
|
+
}
|
|
846
|
+
function optionalString(value) {
|
|
847
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
848
|
+
}
|
|
849
|
+
function parseGitHubProjectsOptions(value) {
|
|
850
|
+
if (!isRecord(value))
|
|
851
|
+
return;
|
|
852
|
+
const statusesSource = isRecord(value.statuses) ? value.statuses : undefined;
|
|
853
|
+
const statuses = {};
|
|
854
|
+
for (const key of ["todo", "running", "prOpen", "ciFixing", "merging", "done", "needsAttention"]) {
|
|
855
|
+
const status = optionalString(statusesSource?.[key]);
|
|
856
|
+
if (status)
|
|
857
|
+
statuses[key] = status;
|
|
858
|
+
}
|
|
859
|
+
const parsed = {};
|
|
860
|
+
if (typeof value.enabled === "boolean")
|
|
861
|
+
parsed.enabled = value.enabled;
|
|
862
|
+
const projectId = optionalString(value.projectId);
|
|
863
|
+
if (projectId)
|
|
864
|
+
parsed.projectId = projectId;
|
|
865
|
+
const statusFieldId = optionalString(value.statusFieldId);
|
|
866
|
+
if (statusFieldId)
|
|
867
|
+
parsed.statusFieldId = statusFieldId;
|
|
868
|
+
if (Object.keys(statuses).length > 0)
|
|
869
|
+
parsed.statuses = statuses;
|
|
870
|
+
return parsed;
|
|
871
|
+
}
|
|
872
|
+
function githubProjectsOptionsFromConfig(config, context) {
|
|
873
|
+
const rigConfig = isRecord(context?.rigConfig) ? context.rigConfig : undefined;
|
|
874
|
+
const github = isRecord(rigConfig?.github) ? rigConfig.github : undefined;
|
|
875
|
+
return parseGitHubProjectsOptions(config.options?.projects) ?? parseGitHubProjectsOptions(github?.projects);
|
|
876
|
+
}
|
|
586
877
|
function standardPlugin(opts = {}) {
|
|
587
878
|
return definePlugin({
|
|
588
879
|
name: "rig-standard",
|
|
@@ -607,13 +898,13 @@ function standardPlugin(opts = {}) {
|
|
|
607
898
|
id: "std:github-issues",
|
|
608
899
|
kind: "github-issues",
|
|
609
900
|
description: "GitHub Issues via gh CLI",
|
|
610
|
-
factory(config) {
|
|
901
|
+
factory(config, context) {
|
|
611
902
|
const options = {
|
|
612
903
|
owner: requireStringField(config, "owner", "github-issues"),
|
|
613
904
|
repo: requireStringField(config, "repo", "github-issues")
|
|
614
905
|
};
|
|
615
|
-
|
|
616
|
-
|
|
906
|
+
const credentialProviderOptions = context?.projectRoot ? { stateDir: resolve3(context.projectRoot, ".rig", "state") } : {};
|
|
907
|
+
options.credentialProvider = opts.githubCredentialProvider ?? createStateGitHubCredentialProvider(credentialProviderOptions);
|
|
617
908
|
if (opts.githubWorkspaceId)
|
|
618
909
|
options.workspaceId = opts.githubWorkspaceId;
|
|
619
910
|
if (opts.githubUserId)
|
|
@@ -635,6 +926,9 @@ function standardPlugin(opts = {}) {
|
|
|
635
926
|
const listLimit = typeof config.options?.listLimit === "number" ? config.options.listLimit : undefined;
|
|
636
927
|
if (listLimit !== undefined)
|
|
637
928
|
options.listLimit = listLimit;
|
|
929
|
+
const projects = githubProjectsOptionsFromConfig(config, context);
|
|
930
|
+
if (projects)
|
|
931
|
+
options.projects = projects;
|
|
638
932
|
return createGitHubIssuesTaskSource(options);
|
|
639
933
|
}
|
|
640
934
|
},
|
|
@@ -642,9 +936,10 @@ function standardPlugin(opts = {}) {
|
|
|
642
936
|
id: "std:files",
|
|
643
937
|
kind: "files",
|
|
644
938
|
description: "JSON files in a local directory",
|
|
645
|
-
factory(config) {
|
|
939
|
+
factory(config, context) {
|
|
646
940
|
return createFilesTaskSource({
|
|
647
|
-
path: requireStringField(config, "path", "files")
|
|
941
|
+
path: requireStringField(config, "path", "files"),
|
|
942
|
+
...context?.projectRoot ? { projectRoot: context.projectRoot } : {}
|
|
648
943
|
});
|
|
649
944
|
}
|
|
650
945
|
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@h-rig/standard-plugin",
|
|
3
|
-
"version": "0.0.6-alpha.
|
|
3
|
+
"version": "0.0.6-alpha.100",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "Rig
|
|
5
|
+
"description": "First-party contribution bundle for Rig's OMP extension plugin graph; not a standalone plugin runtime.",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
7
|
"files": [
|
|
8
8
|
"dist",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
],
|
|
11
11
|
"exports": {
|
|
12
12
|
".": {
|
|
13
|
+
"types": "./dist/src/index.d.ts",
|
|
13
14
|
"import": "./dist/src/index.js"
|
|
14
15
|
}
|
|
15
16
|
},
|
|
@@ -18,9 +19,10 @@
|
|
|
18
19
|
},
|
|
19
20
|
"main": "./dist/src/index.js",
|
|
20
21
|
"module": "./dist/src/index.js",
|
|
22
|
+
"types": "./dist/src/index.d.ts",
|
|
21
23
|
"dependencies": {
|
|
22
|
-
"@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.
|
|
23
|
-
"@rig/core": "npm:@h-rig/core@0.0.6-alpha.
|
|
24
|
+
"@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.100",
|
|
25
|
+
"@rig/core": "npm:@h-rig/core@0.0.6-alpha.100",
|
|
24
26
|
"effect": "4.0.0-beta.78"
|
|
25
27
|
}
|
|
26
28
|
}
|