@oss-autopilot/core 1.15.2 → 1.16.0
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-registry.js +413 -690
- package/dist/cli.bundle.cjs +86 -86
- package/dist/cli.js +4 -16
- package/dist/commands/dashboard-data.d.ts +9 -0
- package/dist/commands/dashboard-server.js +3 -1
- package/dist/commands/index.d.ts +2 -1
- package/dist/commands/index.js +1 -1
- package/dist/commands/search.d.ts +6 -0
- package/dist/commands/search.js +6 -0
- package/dist/commands/startup.js +5 -2
- package/dist/commands/track.d.ts +23 -7
- package/dist/commands/track.js +23 -7
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/state.d.ts +19 -0
- package/dist/core/state.js +38 -2
- package/dist/core/utils.d.ts +5 -5
- package/dist/core/utils.js +5 -5
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -50,22 +50,10 @@ program.hook('preAction', async (thisCommand, actionCommand) => {
|
|
|
50
50
|
process.exit(1);
|
|
51
51
|
}
|
|
52
52
|
// Activate Gist persistence if configured, before any command runs.
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const { getStatePath } = await import('./core/index.js');
|
|
58
|
-
const fs = await import('fs');
|
|
59
|
-
const raw = fs.readFileSync(getStatePath(), 'utf-8');
|
|
60
|
-
persistence = JSON.parse(raw)?.config?.persistence;
|
|
61
|
-
}
|
|
62
|
-
catch {
|
|
63
|
-
// No state file or unreadable — local mode, lazy init
|
|
64
|
-
}
|
|
65
|
-
if (persistence === 'gist' && token) {
|
|
66
|
-
const { getStateManagerAsync } = await import('./core/index.js');
|
|
67
|
-
await getStateManagerAsync(token);
|
|
68
|
-
}
|
|
53
|
+
// Shared helper peeks at the state file and only pre-sets the singleton
|
|
54
|
+
// when Gist mode is the configured persistence (#1000).
|
|
55
|
+
const { ensureGistPersistence } = await import('./core/index.js');
|
|
56
|
+
await ensureGistPersistence(token);
|
|
69
57
|
}
|
|
70
58
|
});
|
|
71
59
|
// First-run detection: if no subcommand was provided and no state file exists,
|
|
@@ -49,6 +49,15 @@ export interface DashboardJsonData {
|
|
|
49
49
|
offline?: boolean;
|
|
50
50
|
lastUpdated?: string;
|
|
51
51
|
}
|
|
52
|
+
/** Action types the dashboard can request via POST /api/action. */
|
|
53
|
+
export type DashboardActionType = 'move' | 'dismiss_issue_response';
|
|
54
|
+
/** Body shape for POST /api/action — single source for both server validation and SPA client (#998). */
|
|
55
|
+
export interface ActionRequest {
|
|
56
|
+
action: DashboardActionType;
|
|
57
|
+
url: string;
|
|
58
|
+
/** Target state for move action. */
|
|
59
|
+
target?: 'attention' | 'waiting' | 'shelved' | 'auto';
|
|
60
|
+
}
|
|
52
61
|
export declare function buildDashboardStats(digest: DailyDigest, state: Readonly<AgentState>, storedMergedCount?: number, storedClosedCount?: number): DashboardStats;
|
|
53
62
|
/**
|
|
54
63
|
* Merge fresh API counts into existing stored counts.
|
|
@@ -251,7 +251,9 @@ export async function startDashboardServer(options) {
|
|
|
251
251
|
}
|
|
252
252
|
catch (error) {
|
|
253
253
|
warn(MODULE, `Failed to rebuild dashboard data after state reload: ${errorMessage(error)}`);
|
|
254
|
-
//
|
|
254
|
+
// Serve previous cachedJsonData rather than returning 500.
|
|
255
|
+
// Signal staleness via response header so clients can detect the degraded mode (#994).
|
|
256
|
+
res.setHeader('X-Dashboard-Stale', '1');
|
|
255
257
|
}
|
|
256
258
|
}
|
|
257
259
|
sendJson(res, 200, cachedJsonData);
|
package/dist/commands/index.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ export { runStartup } from './startup.js';
|
|
|
22
22
|
/** Return contribution statistics (merge rate, PR counts, repo breakdown) from local state. */
|
|
23
23
|
export { runStatus } from './status.js';
|
|
24
24
|
/** Search GitHub for contributable issues using multi-strategy discovery. */
|
|
25
|
-
export { runSearch } from './search.js';
|
|
25
|
+
export { runSearch, MAX_SEARCH_RESULTS } from './search.js';
|
|
26
26
|
/** Vet a single GitHub issue for claimability (open, unassigned, no linked PRs, repo health). */
|
|
27
27
|
export { runVet } from './vet.js';
|
|
28
28
|
/** Re-vet all available issues in a curated issue list for freshness. */
|
|
@@ -71,6 +71,7 @@ export { runCheckIntegration } from './check-integration.js';
|
|
|
71
71
|
export { runDetectFormatters } from './detect-formatters.js';
|
|
72
72
|
/** Scan for locally cloned repos. */
|
|
73
73
|
export { runLocalRepos } from './local-repos.js';
|
|
74
|
+
export type { DashboardJsonData, DashboardStats, DashboardActionType, ActionRequest } from './dashboard-data.js';
|
|
74
75
|
export type { ErrorCode } from '../formatters/json.js';
|
|
75
76
|
export type { DailyOutput, SearchOutput, StartupOutput, StatusOutput, TrackOutput } from '../formatters/json.js';
|
|
76
77
|
export type { VetOutput, CommentsOutput, PostOutput, ClaimOutput, VetListOutput, VetListItemStatus, } from '../formatters/json.js';
|
package/dist/commands/index.js
CHANGED
|
@@ -23,7 +23,7 @@ export { runStartup } from './startup.js';
|
|
|
23
23
|
/** Return contribution statistics (merge rate, PR counts, repo breakdown) from local state. */
|
|
24
24
|
export { runStatus } from './status.js';
|
|
25
25
|
/** Search GitHub for contributable issues using multi-strategy discovery. */
|
|
26
|
-
export { runSearch } from './search.js';
|
|
26
|
+
export { runSearch, MAX_SEARCH_RESULTS } from './search.js';
|
|
27
27
|
/** Vet a single GitHub issue for claimability (open, unassigned, no linked PRs, repo health). */
|
|
28
28
|
export { runVet } from './vet.js';
|
|
29
29
|
/** Re-vet all available issues in a curated issue list for freshness. */
|
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { type SearchOutput } from '../formatters/json.js';
|
|
6
6
|
export { type SearchOutput } from '../formatters/json.js';
|
|
7
|
+
/**
|
|
8
|
+
* Hard cap on issue-search result count. Shared between CLI (`cli-registry.ts`),
|
|
9
|
+
* MCP tool (`tools.ts`), and MCP prompt (`prompts.ts`) so a future adjustment
|
|
10
|
+
* lands in one place instead of three (#1002).
|
|
11
|
+
*/
|
|
12
|
+
export declare const MAX_SEARCH_RESULTS = 100;
|
|
7
13
|
interface SearchOptions {
|
|
8
14
|
maxResults: number;
|
|
9
15
|
}
|
package/dist/commands/search.js
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { createAutopilotScout } from './scout-bridge.js';
|
|
6
6
|
import { getStateManager } from '../core/index.js';
|
|
7
|
+
/**
|
|
8
|
+
* Hard cap on issue-search result count. Shared between CLI (`cli-registry.ts`),
|
|
9
|
+
* MCP tool (`tools.ts`), and MCP prompt (`prompts.ts`) so a future adjustment
|
|
10
|
+
* lands in one place instead of three (#1002).
|
|
11
|
+
*/
|
|
12
|
+
export const MAX_SEARCH_RESULTS = 100;
|
|
7
13
|
/**
|
|
8
14
|
* Search GitHub for contributable issues using multi-phase discovery.
|
|
9
15
|
*
|
package/dist/commands/startup.js
CHANGED
|
@@ -109,8 +109,11 @@ export function detectIssueList() {
|
|
|
109
109
|
skippedIssuesPath = configuredSkipPath;
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
|
-
catch {
|
|
113
|
-
|
|
112
|
+
catch (err) {
|
|
113
|
+
// State access can fail on a degraded state file (corrupt JSON, EACCES).
|
|
114
|
+
// Default-path probe below still runs; warn so the underlying cause is visible (#994).
|
|
115
|
+
// Matches the sibling warn at line 64 for `issueListPath` read failures.
|
|
116
|
+
warn('startup', `Could not read skippedIssuesPath from state: ${errorMessage(err)}`);
|
|
114
117
|
}
|
|
115
118
|
// Probe default path: same directory as issue list, named skipped-issues.md
|
|
116
119
|
if (!skippedIssuesPath && issueListPath) {
|
package/dist/commands/track.d.ts
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Track/Untrack commands
|
|
3
|
-
*
|
|
4
|
-
* These commands
|
|
2
|
+
* Track/Untrack commands (v2 semantics — see #1001)
|
|
3
|
+
*
|
|
4
|
+
* **These commands do not mutate state.** In v2, PRs are discovered and
|
|
5
|
+
* enriched automatically on every `daily` run — there is no local tracking
|
|
6
|
+
* list to add to or remove from. The commands are preserved for backwards
|
|
7
|
+
* compatibility with v1 callers, but:
|
|
8
|
+
*
|
|
9
|
+
* - `runTrack` is an **informational lookup** that fetches PR metadata from
|
|
10
|
+
* GitHub and returns it. Useful for inspecting a specific PR's shape
|
|
11
|
+
* without waiting for the next `daily` run. Nothing is persisted.
|
|
12
|
+
* - `runUntrack` is **deprecated** and always a no-op. Use `shelve` to hide
|
|
13
|
+
* a PR from the daily digest.
|
|
5
14
|
*/
|
|
6
15
|
import type { TrackOutput } from '../formatters/json.js';
|
|
7
16
|
export interface UntrackOutput {
|
|
@@ -10,7 +19,11 @@ export interface UntrackOutput {
|
|
|
10
19
|
message: string;
|
|
11
20
|
}
|
|
12
21
|
/**
|
|
13
|
-
*
|
|
22
|
+
* Fetch metadata for a PR URL (informational — does not persist).
|
|
23
|
+
*
|
|
24
|
+
* In v2 this is a read-only lookup. PRs are discovered automatically on each
|
|
25
|
+
* `daily` run; this command exists for one-off inspection of a specific PR's
|
|
26
|
+
* shape (title, repo, number).
|
|
14
27
|
*
|
|
15
28
|
* @param options - Track options
|
|
16
29
|
* @param options.prUrl - Full GitHub PR URL
|
|
@@ -21,11 +34,14 @@ export declare function runTrack(options: {
|
|
|
21
34
|
prUrl: string;
|
|
22
35
|
}): Promise<TrackOutput>;
|
|
23
36
|
/**
|
|
24
|
-
* No-op in v2
|
|
37
|
+
* @deprecated No-op in v2. Use `runShelve` to hide a PR from the daily digest.
|
|
38
|
+
*
|
|
39
|
+
* Kept for backwards compatibility with v1 callers. PRs are fetched fresh
|
|
40
|
+
* on each `daily` run, so there is no local tracking list to remove from.
|
|
25
41
|
*
|
|
26
42
|
* @param options - Untrack options
|
|
27
|
-
* @param options.prUrl - Full GitHub PR URL
|
|
28
|
-
* @returns
|
|
43
|
+
* @param options.prUrl - Full GitHub PR URL (validated but not used)
|
|
44
|
+
* @returns Output object with `removed: false` and a message explaining v2 behavior
|
|
29
45
|
* @throws {ValidationError} If the URL is not a valid GitHub PR URL
|
|
30
46
|
*/
|
|
31
47
|
export declare function runUntrack(options: {
|
package/dist/commands/track.js
CHANGED
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Track/Untrack commands
|
|
3
|
-
*
|
|
4
|
-
* These commands
|
|
2
|
+
* Track/Untrack commands (v2 semantics — see #1001)
|
|
3
|
+
*
|
|
4
|
+
* **These commands do not mutate state.** In v2, PRs are discovered and
|
|
5
|
+
* enriched automatically on every `daily` run — there is no local tracking
|
|
6
|
+
* list to add to or remove from. The commands are preserved for backwards
|
|
7
|
+
* compatibility with v1 callers, but:
|
|
8
|
+
*
|
|
9
|
+
* - `runTrack` is an **informational lookup** that fetches PR metadata from
|
|
10
|
+
* GitHub and returns it. Useful for inspecting a specific PR's shape
|
|
11
|
+
* without waiting for the next `daily` run. Nothing is persisted.
|
|
12
|
+
* - `runUntrack` is **deprecated** and always a no-op. Use `shelve` to hide
|
|
13
|
+
* a PR from the daily digest.
|
|
5
14
|
*/
|
|
6
15
|
import { getOctokit, requireGitHubToken } from '../core/index.js';
|
|
7
16
|
import { validateUrl, PR_URL_PATTERN, validateGitHubUrl } from './validation.js';
|
|
8
17
|
import { parseGitHubUrl } from '../core/utils.js';
|
|
9
18
|
/**
|
|
10
|
-
*
|
|
19
|
+
* Fetch metadata for a PR URL (informational — does not persist).
|
|
20
|
+
*
|
|
21
|
+
* In v2 this is a read-only lookup. PRs are discovered automatically on each
|
|
22
|
+
* `daily` run; this command exists for one-off inspection of a specific PR's
|
|
23
|
+
* shape (title, repo, number).
|
|
11
24
|
*
|
|
12
25
|
* @param options - Track options
|
|
13
26
|
* @param options.prUrl - Full GitHub PR URL
|
|
@@ -35,11 +48,14 @@ export async function runTrack(options) {
|
|
|
35
48
|
};
|
|
36
49
|
}
|
|
37
50
|
/**
|
|
38
|
-
* No-op in v2
|
|
51
|
+
* @deprecated No-op in v2. Use `runShelve` to hide a PR from the daily digest.
|
|
52
|
+
*
|
|
53
|
+
* Kept for backwards compatibility with v1 callers. PRs are fetched fresh
|
|
54
|
+
* on each `daily` run, so there is no local tracking list to remove from.
|
|
39
55
|
*
|
|
40
56
|
* @param options - Untrack options
|
|
41
|
-
* @param options.prUrl - Full GitHub PR URL
|
|
42
|
-
* @returns
|
|
57
|
+
* @param options.prUrl - Full GitHub PR URL (validated but not used)
|
|
58
|
+
* @returns Output object with `removed: false` and a message explaining v2 behavior
|
|
43
59
|
* @throws {ValidationError} If the URL is not a valid GitHub PR URL
|
|
44
60
|
*/
|
|
45
61
|
export async function runUntrack(options) {
|
package/dist/core/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Core module exports
|
|
3
3
|
* Re-exports all core functionality for convenient imports
|
|
4
4
|
*/
|
|
5
|
-
export { StateManager, getStateManager, getStateManagerAsync, resetStateManager, type Stats } from './state.js';
|
|
5
|
+
export { StateManager, getStateManager, getStateManagerAsync, ensureGistPersistence, resetStateManager, type Stats, } from './state.js';
|
|
6
6
|
export { GistStateStore } from './gist-state-store.js';
|
|
7
7
|
export { PRMonitor, type PRCheckFailure, type FetchPRsResult, computeDisplayLabel, classifyCICheck, classifyFailingChecks, } from './pr-monitor.js';
|
|
8
8
|
export { IssueConversationMonitor } from './issue-conversation.js';
|
package/dist/core/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Core module exports
|
|
3
3
|
* Re-exports all core functionality for convenient imports
|
|
4
4
|
*/
|
|
5
|
-
export { StateManager, getStateManager, getStateManagerAsync, resetStateManager } from './state.js';
|
|
5
|
+
export { StateManager, getStateManager, getStateManagerAsync, ensureGistPersistence, resetStateManager, } from './state.js';
|
|
6
6
|
export { GistStateStore } from './gist-state-store.js';
|
|
7
7
|
export { PRMonitor, computeDisplayLabel, classifyCICheck, classifyFailingChecks, } from './pr-monitor.js';
|
|
8
8
|
// Search/vetting now delegated to @oss-scout/core via commands/scout-bridge.ts
|
package/dist/core/state.d.ts
CHANGED
|
@@ -301,6 +301,25 @@ export declare function getStateManager(): StateManager;
|
|
|
301
301
|
* StateManager and Gist checkpoints will be no-ops.
|
|
302
302
|
*/
|
|
303
303
|
export declare function getStateManagerAsync(token?: string): Promise<StateManager>;
|
|
304
|
+
/**
|
|
305
|
+
* Bootstrap helper for processes that may run in Gist persistence mode.
|
|
306
|
+
*
|
|
307
|
+
* Peeks at the state file to check if Gist mode is configured. If so and a
|
|
308
|
+
* valid token is provided, pre-sets the singleton via {@link getStateManagerAsync}
|
|
309
|
+
* so subsequent synchronous {@link getStateManager} calls return the Gist-backed
|
|
310
|
+
* instance. No-op when the state file is absent, unparseable, or not in Gist mode.
|
|
311
|
+
*
|
|
312
|
+
* Consolidates identical filesystem-peek + getStateManagerAsync logic that
|
|
313
|
+
* was duplicated between the CLI bootstrap (`cli.ts`) and MCP tool bootstrap
|
|
314
|
+
* (`mcp-server/src/tools.ts`) — #1000.
|
|
315
|
+
*
|
|
316
|
+
* @param token - GitHub token with `gist` scope, or `null` to skip activation
|
|
317
|
+
*
|
|
318
|
+
* @example
|
|
319
|
+
* // CLI bootstrap
|
|
320
|
+
* await ensureGistPersistence(token);
|
|
321
|
+
*/
|
|
322
|
+
export declare function ensureGistPersistence(token: string | null): Promise<void>;
|
|
304
323
|
/**
|
|
305
324
|
* Reset the singleton StateManager instance to null. Intended for test isolation.
|
|
306
325
|
*/
|
package/dist/core/state.js
CHANGED
|
@@ -185,8 +185,10 @@ export class StateManager {
|
|
|
185
185
|
try {
|
|
186
186
|
atomicWriteFileSync(getStateCachePath(), JSON.stringify(this.state, null, 2), 0o600);
|
|
187
187
|
}
|
|
188
|
-
catch {
|
|
189
|
-
//
|
|
188
|
+
catch (err) {
|
|
189
|
+
// Cache write failure is non-fatal (Gist remains source of truth), but
|
|
190
|
+
// silent failure masks degraded-mode risk on next offline bootstrap (#994).
|
|
191
|
+
warn(MODULE, `Failed to write Gist local cache: ${errorMessage(err)}`);
|
|
190
192
|
}
|
|
191
193
|
return;
|
|
192
194
|
}
|
|
@@ -705,6 +707,40 @@ export async function getStateManagerAsync(token) {
|
|
|
705
707
|
}
|
|
706
708
|
return getStateManager();
|
|
707
709
|
}
|
|
710
|
+
/**
|
|
711
|
+
* Bootstrap helper for processes that may run in Gist persistence mode.
|
|
712
|
+
*
|
|
713
|
+
* Peeks at the state file to check if Gist mode is configured. If so and a
|
|
714
|
+
* valid token is provided, pre-sets the singleton via {@link getStateManagerAsync}
|
|
715
|
+
* so subsequent synchronous {@link getStateManager} calls return the Gist-backed
|
|
716
|
+
* instance. No-op when the state file is absent, unparseable, or not in Gist mode.
|
|
717
|
+
*
|
|
718
|
+
* Consolidates identical filesystem-peek + getStateManagerAsync logic that
|
|
719
|
+
* was duplicated between the CLI bootstrap (`cli.ts`) and MCP tool bootstrap
|
|
720
|
+
* (`mcp-server/src/tools.ts`) — #1000.
|
|
721
|
+
*
|
|
722
|
+
* @param token - GitHub token with `gist` scope, or `null` to skip activation
|
|
723
|
+
*
|
|
724
|
+
* @example
|
|
725
|
+
* // CLI bootstrap
|
|
726
|
+
* await ensureGistPersistence(token);
|
|
727
|
+
*/
|
|
728
|
+
export async function ensureGistPersistence(token) {
|
|
729
|
+
if (!token)
|
|
730
|
+
return;
|
|
731
|
+
let persistence;
|
|
732
|
+
try {
|
|
733
|
+
const raw = fs.readFileSync(getStatePath(), 'utf-8');
|
|
734
|
+
persistence = JSON.parse(raw)?.config?.persistence;
|
|
735
|
+
}
|
|
736
|
+
catch {
|
|
737
|
+
// No state file or unreadable — stay in local mode
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
if (persistence === 'gist') {
|
|
741
|
+
await getStateManagerAsync(token);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
708
744
|
/**
|
|
709
745
|
* Reset the singleton StateManager instance to null. Intended for test isolation.
|
|
710
746
|
*/
|
package/dist/core/utils.d.ts
CHANGED
|
@@ -145,14 +145,14 @@ export declare function extractOwnerRepo(url: string): {
|
|
|
145
145
|
repo: string;
|
|
146
146
|
} | null;
|
|
147
147
|
/**
|
|
148
|
-
* Calculates the number of whole days between two dates,
|
|
148
|
+
* Calculates the number of whole days between two dates, clamped to zero.
|
|
149
149
|
*
|
|
150
|
-
*
|
|
151
|
-
* (e.g., 1.9 days
|
|
150
|
+
* Returns `0` if `from` is after `to` — reversed ranges and clock-skew do not
|
|
151
|
+
* produce negative values. Partial days are truncated (e.g., 1.9 days -> 1).
|
|
152
152
|
*
|
|
153
153
|
* @param from - The start date
|
|
154
154
|
* @param to - The end date (defaults to the current date/time)
|
|
155
|
-
* @returns Number of whole days between the two dates
|
|
155
|
+
* @returns Number of whole days between the two dates, minimum `0`
|
|
156
156
|
*
|
|
157
157
|
* @example
|
|
158
158
|
* daysBetween(new Date('2024-01-01'), new Date('2024-01-10'))
|
|
@@ -160,7 +160,7 @@ export declare function extractOwnerRepo(url: string): {
|
|
|
160
160
|
*
|
|
161
161
|
* @example
|
|
162
162
|
* daysBetween(new Date('2024-01-10'), new Date('2024-01-01'))
|
|
163
|
-
* //
|
|
163
|
+
* // 0 (clamped; reversed ranges are not signed)
|
|
164
164
|
*/
|
|
165
165
|
export declare function daysBetween(from: Date, to?: Date): number;
|
|
166
166
|
/**
|
package/dist/core/utils.js
CHANGED
|
@@ -220,14 +220,14 @@ export function extractOwnerRepo(url) {
|
|
|
220
220
|
return { owner, repo };
|
|
221
221
|
}
|
|
222
222
|
/**
|
|
223
|
-
* Calculates the number of whole days between two dates,
|
|
223
|
+
* Calculates the number of whole days between two dates, clamped to zero.
|
|
224
224
|
*
|
|
225
|
-
*
|
|
226
|
-
* (e.g., 1.9 days
|
|
225
|
+
* Returns `0` if `from` is after `to` — reversed ranges and clock-skew do not
|
|
226
|
+
* produce negative values. Partial days are truncated (e.g., 1.9 days -> 1).
|
|
227
227
|
*
|
|
228
228
|
* @param from - The start date
|
|
229
229
|
* @param to - The end date (defaults to the current date/time)
|
|
230
|
-
* @returns Number of whole days between the two dates
|
|
230
|
+
* @returns Number of whole days between the two dates, minimum `0`
|
|
231
231
|
*
|
|
232
232
|
* @example
|
|
233
233
|
* daysBetween(new Date('2024-01-01'), new Date('2024-01-10'))
|
|
@@ -235,7 +235,7 @@ export function extractOwnerRepo(url) {
|
|
|
235
235
|
*
|
|
236
236
|
* @example
|
|
237
237
|
* daysBetween(new Date('2024-01-10'), new Date('2024-01-01'))
|
|
238
|
-
* //
|
|
238
|
+
* // 0 (clamped; reversed ranges are not signed)
|
|
239
239
|
*/
|
|
240
240
|
export function daysBetween(from, to = new Date()) {
|
|
241
241
|
return Math.max(0, Math.floor((to.getTime() - from.getTime()) / (1000 * 60 * 60 * 24)));
|