@oss-autopilot/core 0.42.6 → 0.43.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.bundle.cjs +263 -16828
- package/dist/commands/config.js +2 -1
- package/dist/commands/daily.js +57 -36
- package/dist/commands/dashboard-lifecycle.d.ts +22 -0
- package/dist/commands/dashboard-lifecycle.js +87 -0
- package/dist/commands/dashboard-server.d.ts +14 -0
- package/dist/commands/dashboard-server.js +88 -2
- package/dist/commands/dashboard.d.ts +5 -0
- package/dist/commands/dashboard.js +1 -1
- package/dist/commands/startup.d.ts +1 -1
- package/dist/commands/startup.js +72 -13
- package/dist/commands/vet.js +2 -1
- package/dist/core/concurrency.d.ts +2 -1
- package/dist/core/concurrency.js +12 -2
- package/dist/core/pr-monitor.js +12 -12
- package/dist/core/utils.d.ts +1 -2
- package/dist/core/utils.js +4 -2
- package/dist/formatters/json.d.ts +3 -1
- package/package.json +2 -2
package/dist/commands/startup.js
CHANGED
|
@@ -8,10 +8,12 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import * as fs from 'fs';
|
|
10
10
|
import { execFile } from 'child_process';
|
|
11
|
-
import { getStateManager, getGitHubToken, getCLIVersion } from '../core/index.js';
|
|
11
|
+
import { getStateManager, getGitHubToken, getCLIVersion, getStatePath, getDashboardPath } from '../core/index.js';
|
|
12
12
|
import { errorMessage } from '../core/errors.js';
|
|
13
|
+
import { warn } from '../core/logger.js';
|
|
13
14
|
import { executeDailyCheck } from './daily.js';
|
|
14
15
|
import { writeDashboardFromState } from './dashboard.js';
|
|
16
|
+
import { launchDashboardServer } from './dashboard-lifecycle.js';
|
|
15
17
|
/**
|
|
16
18
|
* Parse issueListPath from a config file's YAML frontmatter.
|
|
17
19
|
* Returns the path string or undefined if not found.
|
|
@@ -93,21 +95,53 @@ export function detectIssueList() {
|
|
|
93
95
|
}
|
|
94
96
|
}
|
|
95
97
|
function openInBrowser(filePath) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
98
|
+
let openCmd;
|
|
99
|
+
let args;
|
|
100
|
+
switch (process.platform) {
|
|
101
|
+
case 'darwin':
|
|
102
|
+
openCmd = 'open';
|
|
103
|
+
args = [filePath];
|
|
104
|
+
break;
|
|
105
|
+
case 'win32':
|
|
106
|
+
openCmd = 'cmd';
|
|
107
|
+
args = ['/c', 'start', '', filePath];
|
|
108
|
+
break;
|
|
109
|
+
default:
|
|
110
|
+
openCmd = 'xdg-open';
|
|
111
|
+
args = [filePath];
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
99
114
|
execFile(openCmd, args, (error) => {
|
|
100
115
|
if (error) {
|
|
101
116
|
console.error(`[STARTUP] Failed to open dashboard in browser: ${error.message}`);
|
|
102
117
|
}
|
|
103
118
|
});
|
|
104
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Check whether the dashboard HTML file is at least as recent as state.json.
|
|
122
|
+
* Returns true when the dashboard exists and its mtime >= state mtime,
|
|
123
|
+
* meaning there is no need to regenerate it.
|
|
124
|
+
*/
|
|
125
|
+
function isDashboardFresh() {
|
|
126
|
+
try {
|
|
127
|
+
const dashPath = getDashboardPath();
|
|
128
|
+
if (!fs.existsSync(dashPath))
|
|
129
|
+
return false;
|
|
130
|
+
const dashMtime = fs.statSync(dashPath).mtimeMs;
|
|
131
|
+
const stateMtime = fs.statSync(getStatePath()).mtimeMs;
|
|
132
|
+
return dashMtime >= stateMtime;
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
warn('startup', `Failed to check dashboard freshness, will regenerate: ${errorMessage(error)}`);
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
105
139
|
/**
|
|
106
140
|
* Run startup checks and return structured output.
|
|
107
141
|
* Returns StartupOutput with one of three shapes:
|
|
108
142
|
* 1. Setup incomplete: { version, setupComplete: false }
|
|
109
143
|
* 2. Auth failure: { version, setupComplete: true, authError: "..." }
|
|
110
|
-
* 3. Success: { version, setupComplete: true, daily, dashboardPath?, issueList? }
|
|
144
|
+
* 3. Success: { version, setupComplete: true, daily, dashboardPath?, dashboardUrl?, issueList? }
|
|
111
145
|
*
|
|
112
146
|
* Errors from the daily check propagate to the caller.
|
|
113
147
|
*/
|
|
@@ -129,31 +163,56 @@ export async function runStartup() {
|
|
|
129
163
|
}
|
|
130
164
|
// 3. Run daily check
|
|
131
165
|
const daily = await executeDailyCheck(token);
|
|
132
|
-
// 4. Generate
|
|
133
|
-
//
|
|
166
|
+
// 4. Generate static HTML dashboard (serves as fallback + snapshot).
|
|
167
|
+
// Skip regeneration if the dashboard HTML is already newer than state.json.
|
|
134
168
|
let dashboardPath;
|
|
135
|
-
let dashboardOpened = false;
|
|
136
169
|
try {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
170
|
+
if (isDashboardFresh()) {
|
|
171
|
+
dashboardPath = getDashboardPath();
|
|
172
|
+
console.error('[STARTUP] Dashboard HTML is fresh, skipping regeneration');
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
dashboardPath = writeDashboardFromState();
|
|
141
176
|
}
|
|
142
177
|
}
|
|
143
178
|
catch (error) {
|
|
144
179
|
console.error('[STARTUP] Dashboard generation failed:', errorMessage(error));
|
|
145
180
|
}
|
|
181
|
+
// 5. Launch interactive SPA dashboard (preferred) with static HTML fallback
|
|
182
|
+
// Skip opening on first run (0 PRs) — the welcome flow handles onboarding
|
|
183
|
+
let dashboardUrl;
|
|
184
|
+
let dashboardOpened = false;
|
|
185
|
+
if (daily.digest.summary.totalActivePRs > 0) {
|
|
186
|
+
let spaResult = null;
|
|
187
|
+
try {
|
|
188
|
+
spaResult = await launchDashboardServer();
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
console.error('[STARTUP] SPA dashboard launch failed:', errorMessage(error));
|
|
192
|
+
}
|
|
193
|
+
if (spaResult) {
|
|
194
|
+
dashboardUrl = spaResult.url;
|
|
195
|
+
openInBrowser(spaResult.url);
|
|
196
|
+
dashboardOpened = true;
|
|
197
|
+
}
|
|
198
|
+
else if (dashboardPath) {
|
|
199
|
+
// SPA unavailable (assets not built) — fall back to static HTML
|
|
200
|
+
openInBrowser(dashboardPath);
|
|
201
|
+
dashboardOpened = true;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
146
204
|
// Append dashboard status to brief summary (only startup opens the browser, not daily)
|
|
147
205
|
if (dashboardOpened) {
|
|
148
206
|
daily.briefSummary += ' | Dashboard opened in browser';
|
|
149
207
|
}
|
|
150
|
-
//
|
|
208
|
+
// 6. Detect issue list
|
|
151
209
|
const issueList = detectIssueList();
|
|
152
210
|
return {
|
|
153
211
|
version,
|
|
154
212
|
setupComplete: true,
|
|
155
213
|
daily,
|
|
156
214
|
dashboardPath,
|
|
215
|
+
dashboardUrl,
|
|
157
216
|
issueList,
|
|
158
217
|
};
|
|
159
218
|
}
|
package/dist/commands/vet.js
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
* Vets a specific issue before working on it
|
|
4
4
|
*/
|
|
5
5
|
import { IssueDiscovery, requireGitHubToken } from '../core/index.js';
|
|
6
|
-
import { validateUrl } from './validation.js';
|
|
6
|
+
import { ISSUE_URL_PATTERN, validateGitHubUrl, validateUrl } from './validation.js';
|
|
7
7
|
export async function runVet(options) {
|
|
8
8
|
validateUrl(options.issueUrl);
|
|
9
|
+
validateGitHubUrl(options.issueUrl, ISSUE_URL_PATTERN, 'issue');
|
|
9
10
|
const token = requireGitHubToken();
|
|
10
11
|
const discovery = new IssueDiscovery(token);
|
|
11
12
|
const candidate = await discovery.vetIssue(options.issueUrl);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Runs a worker pool that processes items with bounded concurrency.
|
|
3
|
-
* N workers consume from a shared index
|
|
3
|
+
* N workers consume from a shared index. On any worker error, remaining
|
|
4
|
+
* workers are aborted via a shared flag and the error is propagated.
|
|
4
5
|
*/
|
|
5
6
|
export declare function runWorkerPool<T>(items: T[], worker: (item: T) => Promise<void>, concurrency: number): Promise<void>;
|
package/dist/core/concurrency.js
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Runs a worker pool that processes items with bounded concurrency.
|
|
3
|
-
* N workers consume from a shared index
|
|
3
|
+
* N workers consume from a shared index. On any worker error, remaining
|
|
4
|
+
* workers are aborted via a shared flag and the error is propagated.
|
|
4
5
|
*/
|
|
5
6
|
export async function runWorkerPool(items, worker, concurrency) {
|
|
6
7
|
let index = 0;
|
|
8
|
+
let aborted = false;
|
|
7
9
|
const poolWorker = async () => {
|
|
8
10
|
while (index < items.length) {
|
|
11
|
+
if (aborted)
|
|
12
|
+
break;
|
|
9
13
|
const item = items[index++];
|
|
10
|
-
|
|
14
|
+
try {
|
|
15
|
+
await worker(item);
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
aborted = true;
|
|
19
|
+
throw err;
|
|
20
|
+
}
|
|
11
21
|
}
|
|
12
22
|
};
|
|
13
23
|
const workerCount = Math.min(concurrency, items.length);
|
package/dist/core/pr-monitor.js
CHANGED
|
@@ -350,11 +350,7 @@ export class PRMonitor {
|
|
|
350
350
|
* Check if PR has merge conflict
|
|
351
351
|
*/
|
|
352
352
|
hasMergeConflict(mergeable, mergeableState) {
|
|
353
|
-
|
|
354
|
-
return true;
|
|
355
|
-
if (mergeableState === 'dirty')
|
|
356
|
-
return true;
|
|
357
|
-
return false;
|
|
353
|
+
return mergeable === false || mergeableState === 'dirty';
|
|
358
354
|
}
|
|
359
355
|
/**
|
|
360
356
|
* Get CI status from combined status API and check runs.
|
|
@@ -371,6 +367,14 @@ export class PRMonitor {
|
|
|
371
367
|
// 404 is expected for repos without check runs configured; log other errors for debugging
|
|
372
368
|
this.octokit.checks.listForRef({ owner, repo, ref: sha }).catch((err) => {
|
|
373
369
|
const status = getHttpStatusCode(err);
|
|
370
|
+
// Rate limit errors must propagate — matches listReviewComments pattern (#481)
|
|
371
|
+
if (status === 429)
|
|
372
|
+
throw err;
|
|
373
|
+
if (status === 403) {
|
|
374
|
+
const msg = errorMessage(err).toLowerCase();
|
|
375
|
+
if (msg.includes('rate limit') || msg.includes('abuse detection'))
|
|
376
|
+
throw err;
|
|
377
|
+
}
|
|
374
378
|
if (status === 404) {
|
|
375
379
|
debug('pr-monitor', `Check runs 404 for ${owner}/${repo}@${sha.slice(0, 7)} (no checks configured)`);
|
|
376
380
|
}
|
|
@@ -400,12 +404,8 @@ export class PRMonitor {
|
|
|
400
404
|
}
|
|
401
405
|
catch (error) {
|
|
402
406
|
const statusCode = getHttpStatusCode(error);
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
warn('pr-monitor', `CI check failed for ${owner}/${repo}: Invalid token`);
|
|
406
|
-
}
|
|
407
|
-
else if (statusCode === 403) {
|
|
408
|
-
warn('pr-monitor', `CI check failed for ${owner}/${repo}: Rate limit exceeded`);
|
|
407
|
+
if (statusCode === 401 || statusCode === 403 || statusCode === 429) {
|
|
408
|
+
throw error;
|
|
409
409
|
}
|
|
410
410
|
else if (statusCode === 404) {
|
|
411
411
|
// Repo might not have CI configured, this is normal
|
|
@@ -413,7 +413,7 @@ export class PRMonitor {
|
|
|
413
413
|
return { status: 'unknown', failingCheckNames: [], failingCheckConclusions: new Map() };
|
|
414
414
|
}
|
|
415
415
|
else {
|
|
416
|
-
warn('pr-monitor', `Failed to check CI for ${owner}/${repo}@${sha.slice(0, 7)}: ${
|
|
416
|
+
warn('pr-monitor', `Failed to check CI for ${owner}/${repo}@${sha.slice(0, 7)}: ${errorMessage(error)}`);
|
|
417
417
|
}
|
|
418
418
|
return { status: 'unknown', failingCheckNames: [], failingCheckConclusions: new Map() };
|
|
419
419
|
}
|
package/dist/core/utils.d.ts
CHANGED
|
@@ -144,10 +144,9 @@ export declare function daysBetween(from: Date, to?: Date): number;
|
|
|
144
144
|
/**
|
|
145
145
|
* Splits an `"owner/repo"` string into its owner and repo components.
|
|
146
146
|
*
|
|
147
|
-
* Does not validate the input format; if no `/` is present, `repo` will be `undefined`.
|
|
148
|
-
*
|
|
149
147
|
* @param repoFullName - Full repository name in `"owner/repo"` format
|
|
150
148
|
* @returns Object with `owner` and `repo` string properties
|
|
149
|
+
* @throws {Error} If the input does not contain both an owner and repo separated by `/`
|
|
151
150
|
*
|
|
152
151
|
* @example
|
|
153
152
|
* splitRepo('facebook/react')
|
package/dist/core/utils.js
CHANGED
|
@@ -215,10 +215,9 @@ export function daysBetween(from, to = new Date()) {
|
|
|
215
215
|
/**
|
|
216
216
|
* Splits an `"owner/repo"` string into its owner and repo components.
|
|
217
217
|
*
|
|
218
|
-
* Does not validate the input format; if no `/` is present, `repo` will be `undefined`.
|
|
219
|
-
*
|
|
220
218
|
* @param repoFullName - Full repository name in `"owner/repo"` format
|
|
221
219
|
* @returns Object with `owner` and `repo` string properties
|
|
220
|
+
* @throws {Error} If the input does not contain both an owner and repo separated by `/`
|
|
222
221
|
*
|
|
223
222
|
* @example
|
|
224
223
|
* splitRepo('facebook/react')
|
|
@@ -226,6 +225,9 @@ export function daysBetween(from, to = new Date()) {
|
|
|
226
225
|
*/
|
|
227
226
|
export function splitRepo(repoFullName) {
|
|
228
227
|
const [owner, repo] = repoFullName.split('/');
|
|
228
|
+
if (!owner || !repo) {
|
|
229
|
+
throw new Error(`Invalid repo format: expected "owner/repo", got "${repoFullName}"`);
|
|
230
|
+
}
|
|
229
231
|
return { owner, repo };
|
|
230
232
|
}
|
|
231
233
|
/**
|
|
@@ -200,7 +200,7 @@ export interface IssueListInfo {
|
|
|
200
200
|
* Three valid shapes:
|
|
201
201
|
* 1. Setup incomplete: { version, setupComplete: false }
|
|
202
202
|
* 2. Auth failure: { version, setupComplete: true, authError: "..." }
|
|
203
|
-
* 3. Success: { version, setupComplete: true, daily, dashboardPath?, issueList? }
|
|
203
|
+
* 3. Success: { version, setupComplete: true, daily, dashboardPath?, dashboardUrl?, issueList? }
|
|
204
204
|
*/
|
|
205
205
|
export interface StartupOutput {
|
|
206
206
|
version: string;
|
|
@@ -208,6 +208,8 @@ export interface StartupOutput {
|
|
|
208
208
|
authError?: string;
|
|
209
209
|
daily?: DailyOutput;
|
|
210
210
|
dashboardPath?: string;
|
|
211
|
+
/** URL of the interactive SPA dashboard server, when running (e.g., "http://localhost:3000") */
|
|
212
|
+
dashboardUrl?: string;
|
|
211
213
|
issueList?: IssueListInfo;
|
|
212
214
|
}
|
|
213
215
|
/** A single parsed issue from a markdown list (#82) */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oss-autopilot/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.43.1",
|
|
4
4
|
"description": "CLI and core library for managing open source contributions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
},
|
|
61
61
|
"scripts": {
|
|
62
62
|
"build": "tsc",
|
|
63
|
-
"bundle": "esbuild src/cli.ts --bundle --platform=node --target=node20 --format=cjs --outfile=dist/cli.bundle.cjs",
|
|
63
|
+
"bundle": "esbuild src/cli.ts --bundle --platform=node --target=node20 --format=cjs --minify --outfile=dist/cli.bundle.cjs",
|
|
64
64
|
"start": "tsx src/cli.ts",
|
|
65
65
|
"dev": "tsx watch src/cli.ts",
|
|
66
66
|
"typecheck": "tsc --noEmit",
|