@clipboard-health/groundcrew 4.2.0 → 4.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -25
- package/dist/commands/cleaner.d.ts +1 -1
- package/dist/commands/cleaner.d.ts.map +1 -1
- package/dist/commands/cleaner.js +4 -2
- package/dist/commands/dispatcher.d.ts +7 -6
- package/dist/commands/dispatcher.d.ts.map +1 -1
- package/dist/commands/dispatcher.js +56 -28
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +18 -22
- package/dist/commands/eligibility.d.ts +1 -1
- package/dist/commands/eligibility.d.ts.map +1 -1
- package/dist/commands/eligibility.js +7 -6
- package/dist/commands/orchestrator.d.ts.map +1 -1
- package/dist/commands/orchestrator.js +18 -14
- package/dist/commands/resumeWorkspace.d.ts.map +1 -1
- package/dist/commands/resumeWorkspace.js +3 -2
- package/dist/commands/setupWorkspace.d.ts +2 -4
- package/dist/commands/setupWorkspace.d.ts.map +1 -1
- package/dist/commands/setupWorkspace.js +27 -27
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +6 -3
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/lib/adapters/linear/client.d.ts +22 -0
- package/dist/lib/adapters/linear/client.d.ts.map +1 -0
- package/dist/lib/adapters/linear/client.js +36 -0
- package/dist/lib/adapters/linear/factory.d.ts +24 -14
- package/dist/lib/adapters/linear/factory.d.ts.map +1 -1
- package/dist/lib/adapters/linear/factory.js +113 -46
- package/dist/lib/{boardSource.d.ts → adapters/linear/fetch.d.ts} +22 -74
- package/dist/lib/adapters/linear/fetch.d.ts.map +1 -0
- package/dist/lib/{boardSource.js → adapters/linear/fetch.js} +28 -136
- package/dist/lib/adapters/linear/index.d.ts +1 -0
- package/dist/lib/adapters/linear/index.d.ts.map +1 -1
- package/dist/lib/adapters/linear/parsing.d.ts +44 -0
- package/dist/lib/adapters/linear/parsing.d.ts.map +1 -0
- package/dist/lib/adapters/linear/parsing.js +144 -0
- package/dist/lib/{linearIssueStatus.d.ts → adapters/linear/writeback.d.ts} +1 -2
- package/dist/lib/adapters/linear/writeback.d.ts.map +1 -0
- package/dist/lib/{linearIssueStatus.js → adapters/linear/writeback.js} +16 -17
- package/dist/lib/adapters/shell/factory.d.ts +1 -1
- package/dist/lib/adapters/shell/factory.d.ts.map +1 -1
- package/dist/lib/adapters/shell/factory.js +8 -4
- package/dist/lib/adapters/shell/invoke.d.ts +4 -7
- package/dist/lib/adapters/shell/invoke.d.ts.map +1 -1
- package/dist/lib/adapters/shell/invoke.js +46 -75
- package/dist/lib/adapters/shell/schema.d.ts +10 -0
- package/dist/lib/adapters/shell/schema.d.ts.map +1 -1
- package/dist/lib/adapters/shell/schema.js +9 -5
- package/dist/lib/board.d.ts.map +1 -1
- package/dist/lib/board.js +43 -4
- package/dist/lib/buildSources.d.ts +11 -0
- package/dist/lib/buildSources.d.ts.map +1 -1
- package/dist/lib/buildSources.js +41 -0
- package/dist/lib/repositoryValidation.d.ts +13 -0
- package/dist/lib/repositoryValidation.d.ts.map +1 -0
- package/dist/lib/repositoryValidation.js +20 -0
- package/dist/lib/testing/canonicalFixtures.d.ts +19 -0
- package/dist/lib/testing/canonicalFixtures.d.ts.map +1 -0
- package/dist/lib/testing/canonicalFixtures.js +62 -0
- package/dist/lib/ticketSource.d.ts +73 -3
- package/dist/lib/ticketSource.d.ts.map +1 -1
- package/dist/lib/ticketSource.js +31 -0
- package/dist/lib/util.d.ts +0 -20
- package/dist/lib/util.d.ts.map +1 -1
- package/dist/lib/util.js +0 -35
- package/package.json +1 -1
- package/dist/lib/boardSource.d.ts.map +0 -1
- package/dist/lib/linearIssueStatus.d.ts.map +0 -1
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
import { log } from "
|
|
1
|
+
import { log } from "../../util.js";
|
|
2
2
|
export function createLinearIssueStatusUpdater(arguments_) {
|
|
3
3
|
const { client } = arguments_;
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
4
|
+
// Positive cache only. Keyed by teamId because the workflow `state.type ===
|
|
5
|
+
// "started"` lookup yields a single stateId per team — independent of which
|
|
6
|
+
// project the ticket belongs to. State ids don't change for misconfig
|
|
7
|
+
// reasons, so caching successful resolutions is safe across the process.
|
|
8
|
+
//
|
|
9
|
+
// No negative cache: a missing "started" workflow state is a Linear-side
|
|
10
|
+
// config issue the operator can correct mid-session, and a negative cache
|
|
11
|
+
// would mask that recovery until process restart. Slot count caps
|
|
12
|
+
// markInProgress calls per tick at 1-5, so re-fetching team states on
|
|
13
|
+
// every failing attempt costs at most a handful of extra Linear API calls
|
|
14
|
+
// per tick.
|
|
8
15
|
const inProgressStateByTeam = new Map();
|
|
9
|
-
let teamsMissingInProgress = new Set();
|
|
10
16
|
async function getInProgressStateId(teamId) {
|
|
11
17
|
if (teamId.length === 0) {
|
|
12
18
|
return undefined;
|
|
@@ -15,17 +21,13 @@ export function createLinearIssueStatusUpdater(arguments_) {
|
|
|
15
21
|
if (cached !== undefined) {
|
|
16
22
|
return cached;
|
|
17
23
|
}
|
|
18
|
-
if (teamsMissingInProgress.has(teamId)) {
|
|
19
|
-
return undefined;
|
|
20
|
-
}
|
|
21
24
|
const team = await client.team(teamId);
|
|
22
25
|
const states = await team.states();
|
|
23
|
-
// Use the workflow state's
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
+
// Use the workflow state's `type` — Linear standardises on `started` for
|
|
27
|
+
// in-progress columns regardless of how the user renames them, so this
|
|
28
|
+
// works without any per-team status-name configuration.
|
|
26
29
|
const inProgress = states.nodes.find((state) => state.type === "started");
|
|
27
30
|
if (inProgress?.id === undefined) {
|
|
28
|
-
teamsMissingInProgress.add(teamId);
|
|
29
31
|
return undefined;
|
|
30
32
|
}
|
|
31
33
|
inProgressStateByTeam.set(teamId, inProgress.id);
|
|
@@ -39,8 +41,5 @@ export function createLinearIssueStatusUpdater(arguments_) {
|
|
|
39
41
|
await client.updateIssue(issue.uuid, { stateId });
|
|
40
42
|
log(`Marked ${issue.id} as in progress`);
|
|
41
43
|
}
|
|
42
|
-
|
|
43
|
-
teamsMissingInProgress = new Set();
|
|
44
|
-
}
|
|
45
|
-
return { markInProgress, resetMissingInProgressCache };
|
|
44
|
+
return { markInProgress };
|
|
46
45
|
}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* - `fetch` is required by the Zod schema.
|
|
15
15
|
*/
|
|
16
16
|
import type { AdapterContext } from "../../adapterDefinition.ts";
|
|
17
|
-
import type
|
|
17
|
+
import { type Issue as CanonicalIssue, type TicketSource } from "../../ticketSource.ts";
|
|
18
18
|
import { type ShellAdapterConfig, type ShellIssue } from "./schema.ts";
|
|
19
19
|
export declare function toCanonicalIssue(shellIssue: ShellIssue, sourceName: string): CanonicalIssue;
|
|
20
20
|
export declare function createShellTicketSource(config: ShellAdapterConfig, _context: AdapterContext): TicketSource;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/shell/factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/shell/factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAGL,KAAK,KAAK,IAAI,cAAc,EAC5B,KAAK,YAAY,EAClB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EACL,KAAK,kBAAkB,EAEvB,KAAK,UAAU,EAEhB,MAAM,aAAa,CAAC;AAyBrB,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,cAAc,CAsB3F;AAED,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,kBAAkB,EAC1B,QAAQ,EAAE,cAAc,GACvB,YAAY,CAgFd"}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* - `markInProgress` absent → silent no-op.
|
|
14
14
|
* - `fetch` is required by the Zod schema.
|
|
15
15
|
*/
|
|
16
|
+
import { toCanonicalId, } from "../../ticketSource.js";
|
|
16
17
|
import { invokeShellCommand } from "./invoke.js";
|
|
17
18
|
import { shellFetchOutputSchema, shellIssueSchema, } from "./schema.js";
|
|
18
19
|
const DEFAULT_TIMEOUTS = {
|
|
@@ -31,12 +32,14 @@ function mergeTimeouts(overrides) {
|
|
|
31
32
|
}
|
|
32
33
|
export function toCanonicalIssue(shellIssue, sourceName) {
|
|
33
34
|
const blockers = shellIssue.blockers.map((b) => ({
|
|
34
|
-
id:
|
|
35
|
+
id: toCanonicalId(sourceName, b.id),
|
|
35
36
|
title: b.title,
|
|
36
37
|
status: b.status,
|
|
38
|
+
...(b.statusReason !== undefined && { statusReason: b.statusReason }),
|
|
39
|
+
...(b.nativeStatus !== undefined && { nativeStatus: b.nativeStatus }),
|
|
37
40
|
}));
|
|
38
41
|
return {
|
|
39
|
-
id:
|
|
42
|
+
id: toCanonicalId(sourceName, shellIssue.id),
|
|
40
43
|
source: sourceName,
|
|
41
44
|
title: shellIssue.title,
|
|
42
45
|
description: shellIssue.description,
|
|
@@ -81,10 +84,11 @@ export function createShellTicketSource(config, _context) {
|
|
|
81
84
|
},
|
|
82
85
|
fetch: runFetch,
|
|
83
86
|
async resolveOne(naturalId) {
|
|
87
|
+
const canonicalId = toCanonicalId(sourceName, naturalId);
|
|
84
88
|
const resolveCommand = config.commands.resolveOne;
|
|
85
89
|
if (resolveCommand === undefined) {
|
|
86
90
|
const all = await runFetch();
|
|
87
|
-
return all.find((i) => i.id ===
|
|
91
|
+
return all.find((i) => i.id === canonicalId);
|
|
88
92
|
}
|
|
89
93
|
const result = await invokeShellCommand({
|
|
90
94
|
command: resolveCommand,
|
|
@@ -93,7 +97,7 @@ export function createShellTicketSource(config, _context) {
|
|
|
93
97
|
env: config.env,
|
|
94
98
|
substitutions: {
|
|
95
99
|
id: naturalId,
|
|
96
|
-
canonicalId
|
|
100
|
+
canonicalId,
|
|
97
101
|
name: sourceName,
|
|
98
102
|
},
|
|
99
103
|
sourceName,
|
|
@@ -11,19 +11,12 @@
|
|
|
11
11
|
* Exit code 0 = success; exit code 3 = "not found" (caller decides how to
|
|
12
12
|
* interpret); any other nonzero exit throws.
|
|
13
13
|
*/
|
|
14
|
-
export declare const SHELL_COMMAND_MAX_BUFFER_BYTES: number;
|
|
15
14
|
export declare class ShellAdapterTimeoutError extends Error {
|
|
16
15
|
constructor(arguments_: {
|
|
17
16
|
command: string;
|
|
18
17
|
timeoutMs: number;
|
|
19
18
|
});
|
|
20
19
|
}
|
|
21
|
-
export declare class ShellAdapterOutputLimitError extends Error {
|
|
22
|
-
constructor(arguments_: {
|
|
23
|
-
command: string;
|
|
24
|
-
maxBytes: number;
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
20
|
interface InvokeArgs {
|
|
28
21
|
command: string;
|
|
29
22
|
timeoutMs: number;
|
|
@@ -33,11 +26,15 @@ interface InvokeArgs {
|
|
|
33
26
|
substitutions?: Record<string, string> | undefined;
|
|
34
27
|
/** Source name for log prefixing. */
|
|
35
28
|
sourceName: string;
|
|
29
|
+
/** Override the default per-stream stdout/stderr cap (10 MB). Used by tests. */
|
|
30
|
+
maxOutputBytes?: number;
|
|
36
31
|
}
|
|
37
32
|
interface InvokeResult {
|
|
38
33
|
stdout: string;
|
|
39
34
|
stderr: string;
|
|
40
35
|
exitCode: number;
|
|
36
|
+
/** True if either stream hit the byte cap and the rest was discarded. */
|
|
37
|
+
truncated: boolean;
|
|
41
38
|
}
|
|
42
39
|
export declare function applySubstitutions(command: string, subs: Record<string, string>): string;
|
|
43
40
|
export declare function invokeShellCommand(args: InvokeArgs): Promise<InvokeResult>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invoke.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/shell/invoke.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;
|
|
1
|
+
{"version":3,"file":"invoke.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/shell/invoke.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAcH,qBAAa,wBAAyB,SAAQ,KAAK;IACjD,YAAmB,UAAU,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,EAGpE;CACF;AAED,UAAU,UAAU;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IACzC,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IACnD,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,UAAU,YAAY;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,SAAS,EAAE,OAAO,CAAC;CACpB;AAMD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAMxF;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC,CAkGhF"}
|
|
@@ -13,31 +13,22 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { spawn } from "node:child_process";
|
|
15
15
|
import { log } from "../../util.js";
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Hard cap on captured stdout/stderr per stream. Misbehaving scripts that
|
|
18
|
+
* `yes | head -c <huge>` would otherwise exhaust memory. 10 MB is enough for
|
|
19
|
+
* any realistic JSON ticket payload; tests can override via InvokeArgs.maxOutputBytes
|
|
20
|
+
* to exercise the truncation path with a smaller fixture.
|
|
21
|
+
*/
|
|
22
|
+
const DEFAULT_MAX_OUTPUT_BYTES = 10 * 1024 * 1024;
|
|
18
23
|
export class ShellAdapterTimeoutError extends Error {
|
|
19
24
|
constructor(arguments_) {
|
|
20
25
|
super(`Shell command timed out after ${arguments_.timeoutMs}ms: ${arguments_.command}`);
|
|
21
26
|
this.name = "ShellAdapterTimeoutError";
|
|
22
27
|
}
|
|
23
28
|
}
|
|
24
|
-
export class ShellAdapterOutputLimitError extends Error {
|
|
25
|
-
constructor(arguments_) {
|
|
26
|
-
super(`Shell command exceeded combined stdout/stderr maxBuffer of ${arguments_.maxBytes} bytes: ${arguments_.command}`);
|
|
27
|
-
this.name = "ShellAdapterOutputLimitError";
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
29
|
function shellQuote(value) {
|
|
31
30
|
return `'${value.replaceAll("'", String.raw `'\''`)}'`;
|
|
32
31
|
}
|
|
33
|
-
function killChildProcess(child, signal, shouldUseProcessGroup) {
|
|
34
|
-
/* v8 ignore next 4 @preserve -- fallback path is for Windows or a spawn failure before pid assignment */
|
|
35
|
-
if (!shouldUseProcessGroup || child.pid === undefined) {
|
|
36
|
-
child.kill(signal);
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
process.kill(-child.pid, signal);
|
|
40
|
-
}
|
|
41
32
|
export function applySubstitutions(command, subs) {
|
|
42
33
|
let result = command;
|
|
43
34
|
for (const [key, value] of Object.entries(subs)) {
|
|
@@ -49,92 +40,72 @@ export async function invokeShellCommand(args) {
|
|
|
49
40
|
const command = args.substitutions === undefined
|
|
50
41
|
? args.command
|
|
51
42
|
: applySubstitutions(args.command, args.substitutions);
|
|
43
|
+
const maxBytes = args.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
|
|
52
44
|
return await new Promise((resolve, reject) => {
|
|
53
|
-
const shouldUseProcessGroup = process.platform !== "win32";
|
|
54
45
|
const child = spawn("sh", ["-c", command], {
|
|
55
46
|
cwd: args.cwd,
|
|
56
|
-
detached: shouldUseProcessGroup,
|
|
57
47
|
// oxlint-disable-next-line node/no-process-env -- subprocess inherits the parent's full env by design; user-supplied vars layer on top
|
|
58
48
|
env: { ...process.env, ...args.env },
|
|
59
49
|
stdio: ["pipe", "pipe", "pipe"],
|
|
60
50
|
});
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
let
|
|
64
|
-
let stderrLength = 0;
|
|
51
|
+
let stdout = Buffer.alloc(0);
|
|
52
|
+
let stderr = Buffer.alloc(0);
|
|
53
|
+
let truncated = false;
|
|
65
54
|
let settled = false;
|
|
66
|
-
|
|
67
|
-
clearTimeout
|
|
68
|
-
}
|
|
69
|
-
function killChild(signal) {
|
|
70
|
-
try {
|
|
71
|
-
killChildProcess(child, signal, shouldUseProcessGroup);
|
|
72
|
-
}
|
|
73
|
-
catch {
|
|
74
|
-
// The child may have exited between timeout/output-limit handling and the kill request.
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
function failAndKill(error) {
|
|
78
|
-
/* v8 ignore next 3 @preserve -- timeout/output-limit races can call this after another terminal event; deterministic tests cover the first terminal path */
|
|
55
|
+
const timer = setTimeout(() => {
|
|
56
|
+
/* v8 ignore next 3 @preserve -- timer/close race: clearTimeout in the close handler should prevent this branch, but the guard is kept as defense-in-depth */
|
|
79
57
|
if (settled) {
|
|
80
58
|
return;
|
|
81
59
|
}
|
|
82
60
|
settled = true;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
61
|
+
child.kill("SIGKILL");
|
|
62
|
+
reject(new ShellAdapterTimeoutError({ command, timeoutMs: args.timeoutMs }));
|
|
63
|
+
}, args.timeoutMs);
|
|
64
|
+
// Buffer accumulators so the byte cap matches its name. A string-based
|
|
65
|
+
// comparison would measure UTF-16 code units against a byte budget,
|
|
66
|
+
// letting multibyte UTF-8 sneak past the cap and risking a mid-
|
|
67
|
+
// surrogate-pair slice on truncation.
|
|
68
|
+
const appendCapped = (current, chunk) => {
|
|
69
|
+
if (current.byteLength >= maxBytes) {
|
|
70
|
+
truncated = true;
|
|
71
|
+
return current;
|
|
91
72
|
}
|
|
92
|
-
const
|
|
93
|
-
if (
|
|
94
|
-
|
|
95
|
-
command,
|
|
96
|
-
maxBytes: SHELL_COMMAND_MAX_BUFFER_BYTES,
|
|
97
|
-
}));
|
|
98
|
-
return input.currentLength;
|
|
73
|
+
const next = Buffer.concat([current, chunk]);
|
|
74
|
+
if (next.byteLength <= maxBytes) {
|
|
75
|
+
return next;
|
|
99
76
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
77
|
+
truncated = true;
|
|
78
|
+
const clipped = next.subarray(0, maxBytes);
|
|
79
|
+
return Buffer.concat([
|
|
80
|
+
clipped,
|
|
81
|
+
Buffer.from(`\n[truncated: stream exceeded ${maxBytes} bytes]`),
|
|
82
|
+
]);
|
|
83
|
+
};
|
|
106
84
|
child.stdout.on("data", (chunk) => {
|
|
107
|
-
|
|
108
|
-
chunks: stdoutChunks,
|
|
109
|
-
currentLength: stdoutLength,
|
|
110
|
-
chunk,
|
|
111
|
-
});
|
|
85
|
+
stdout = appendCapped(stdout, chunk);
|
|
112
86
|
});
|
|
113
87
|
child.stderr.on("data", (chunk) => {
|
|
114
|
-
|
|
115
|
-
chunks: stderrChunks,
|
|
116
|
-
currentLength: stderrLength,
|
|
117
|
-
chunk,
|
|
118
|
-
});
|
|
88
|
+
stderr = appendCapped(stderr, chunk);
|
|
119
89
|
});
|
|
120
90
|
child.on("close", (code) => {
|
|
91
|
+
/* v8 ignore next 3 @preserve -- timer/close race: when the timeout fires first it SIGKILLs and sets settled=true; the 'close' event still arrives and must no-op. No deterministic test exists — an orphaned grandchild (`sh -c "sleep N; ..."`) keeps the stdout pipe open, so 'close' doesn't arrive until the real timeout elapses; mirrors the ignored timer/error settle guards above. */
|
|
121
92
|
if (settled) {
|
|
122
93
|
return;
|
|
123
94
|
}
|
|
124
95
|
settled = true;
|
|
125
|
-
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
log(`[shell:${args.sourceName}] ${command}\n${stderr.trimEnd()}`);
|
|
96
|
+
clearTimeout(timer);
|
|
97
|
+
const stderrText = stderr.toString("utf8");
|
|
98
|
+
if (stderrText.length > 0) {
|
|
99
|
+
log(`[shell:${args.sourceName}] ${command}\n${stderrText.trimEnd()}`);
|
|
130
100
|
}
|
|
131
|
-
/* v8 ignore next @preserve -- `code` is null only when the process was killed by signal; timeout
|
|
101
|
+
/* v8 ignore next @preserve -- `code` is null only when the process was killed by signal; the timeout path SIGKILLs but settles via the timer rather than 'close' */
|
|
132
102
|
const exitCode = code ?? 1;
|
|
103
|
+
const stdoutText = stdout.toString("utf8");
|
|
133
104
|
if (exitCode === 0 || exitCode === 3) {
|
|
134
|
-
resolve({ stdout, stderr, exitCode });
|
|
105
|
+
resolve({ stdout: stdoutText, stderr: stderrText, exitCode, truncated });
|
|
135
106
|
return;
|
|
136
107
|
}
|
|
137
|
-
reject(new Error(`Shell command for source "${args.sourceName}" failed with exit ${exitCode}: ${
|
|
108
|
+
reject(new Error(`Shell command for source "${args.sourceName}" failed with exit ${exitCode}: ${stderrText.trim().length > 0 ? stderrText.trim() : command}`));
|
|
138
109
|
});
|
|
139
110
|
/* v8 ignore next 8 @preserve -- spawn 'error' event fires only on exec failures (PATH miss, EACCES) which are hard to simulate in tests without polluting host PATH */
|
|
140
111
|
child.on("error", (error) => {
|
|
@@ -142,7 +113,7 @@ export async function invokeShellCommand(args) {
|
|
|
142
113
|
return;
|
|
143
114
|
}
|
|
144
115
|
settled = true;
|
|
145
|
-
|
|
116
|
+
clearTimeout(timer);
|
|
146
117
|
reject(error);
|
|
147
118
|
});
|
|
148
119
|
if (args.stdin !== undefined) {
|
|
@@ -34,6 +34,11 @@ export declare const shellIssueSchema: z.ZodObject<{
|
|
|
34
34
|
other: "other";
|
|
35
35
|
todo: "todo";
|
|
36
36
|
}>;
|
|
37
|
+
statusReason: z.ZodOptional<z.ZodEnum<{
|
|
38
|
+
missing: "missing";
|
|
39
|
+
unmapped: "unmapped";
|
|
40
|
+
}>>;
|
|
41
|
+
nativeStatus: z.ZodOptional<z.ZodString>;
|
|
37
42
|
}, z.core.$strip>>;
|
|
38
43
|
hasMoreBlockers: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
39
44
|
sourceRef: z.ZodUnknown;
|
|
@@ -64,6 +69,11 @@ export declare const shellFetchOutputSchema: z.ZodArray<z.ZodObject<{
|
|
|
64
69
|
other: "other";
|
|
65
70
|
todo: "todo";
|
|
66
71
|
}>;
|
|
72
|
+
statusReason: z.ZodOptional<z.ZodEnum<{
|
|
73
|
+
missing: "missing";
|
|
74
|
+
unmapped: "unmapped";
|
|
75
|
+
}>>;
|
|
76
|
+
nativeStatus: z.ZodOptional<z.ZodString>;
|
|
67
77
|
}, z.core.$strip>>;
|
|
68
78
|
hasMoreBlockers: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
69
79
|
sourceRef: z.ZodUnknown;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/shell/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/shell/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAYxB,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAY3B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAA4B,CAAC;AAEhE,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;iBAwBnC,CAAC;AAEH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
|
|
@@ -10,11 +10,12 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { z } from "zod";
|
|
12
12
|
const canonicalStatusSchema = z.enum(["todo", "in-progress", "in-review", "done", "other"]);
|
|
13
|
-
const timeoutSchema = z.number().int().positive();
|
|
14
13
|
const shellBlockerSchema = z.object({
|
|
15
14
|
id: z.string(),
|
|
16
15
|
title: z.string(),
|
|
17
16
|
status: canonicalStatusSchema,
|
|
17
|
+
statusReason: z.enum(["missing", "unmapped"]).optional(),
|
|
18
|
+
nativeStatus: z.string().optional(),
|
|
18
19
|
});
|
|
19
20
|
export const shellIssueSchema = z.object({
|
|
20
21
|
id: z.string(),
|
|
@@ -44,10 +45,13 @@ export const shellAdapterConfigSchema = z.object({
|
|
|
44
45
|
cwd: z.string().optional(),
|
|
45
46
|
timeouts: z
|
|
46
47
|
.object({
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
// Per-method timeout in milliseconds. Must be a positive integer —
|
|
49
|
+
// zero, negative, and fractional values would either deadlock or
|
|
50
|
+
// misbehave inside setTimeout.
|
|
51
|
+
verify: z.number().int().positive().optional(),
|
|
52
|
+
fetch: z.number().int().positive().optional(),
|
|
53
|
+
resolveOne: z.number().int().positive().optional(),
|
|
54
|
+
markInProgress: z.number().int().positive().optional(),
|
|
51
55
|
})
|
|
52
56
|
.optional(),
|
|
53
57
|
env: z.record(z.string(), z.string()).optional(),
|
package/dist/lib/board.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"board.d.ts","sourceRoot":"","sources":["../../src/lib/board.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,KAAK,
|
|
1
|
+
{"version":3,"file":"board.d.ts","sourceRoot":"","sources":["../../src/lib/board.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,KAAK,EAEV,KAAK,YAAY,EAClB,MAAM,mBAAmB,CAAC;AAE3B,MAAM,WAAW,KAAK;IACpB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;IAC7B;;;OAGG;IACH,UAAU,CAAC,oBAAoB,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;IACrE,wFAAwF;IACxF,cAAc,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7C;AAqBD,wBAAgB,WAAW,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,GAAG,KAAK,CAwGnE"}
|
package/dist/lib/board.js
CHANGED
|
@@ -11,6 +11,12 @@ async function callVerify(source) {
|
|
|
11
11
|
async function callFetch(source) {
|
|
12
12
|
return await source.fetch();
|
|
13
13
|
}
|
|
14
|
+
async function callFetchParentSkips(source) {
|
|
15
|
+
if (source.fetchParentSkips !== undefined) {
|
|
16
|
+
return await source.fetchParentSkips();
|
|
17
|
+
}
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
14
20
|
async function callResolveOne(source, naturalId) {
|
|
15
21
|
return await source.resolveOne(naturalId);
|
|
16
22
|
}
|
|
@@ -38,8 +44,21 @@ export function createBoard(sources) {
|
|
|
38
44
|
}
|
|
39
45
|
},
|
|
40
46
|
async fetch() {
|
|
41
|
-
|
|
42
|
-
|
|
47
|
+
// Per-source serialization: each source's callFetch must complete
|
|
48
|
+
// before its callFetchParentSkips so adapters that cache parent skips
|
|
49
|
+
// as a side effect of fetch() (e.g. Linear, which stores them on
|
|
50
|
+
// `lastParentSkips`) don't serve stale or empty data. Outer Promise.all
|
|
51
|
+
// keeps cross-source fan-out concurrent.
|
|
52
|
+
const perSource = await Promise.all(sources.map(async (source) => {
|
|
53
|
+
const issues = await callFetch(source);
|
|
54
|
+
const parentSkips = await callFetchParentSkips(source);
|
|
55
|
+
return { issues, parentSkips };
|
|
56
|
+
}));
|
|
57
|
+
return {
|
|
58
|
+
timestamp: new Date().toISOString(),
|
|
59
|
+
issues: perSource.flatMap((entry) => entry.issues),
|
|
60
|
+
parentSkips: perSource.flatMap((entry) => entry.parentSkips),
|
|
61
|
+
};
|
|
43
62
|
},
|
|
44
63
|
async resolveOne(idArgument) {
|
|
45
64
|
const colonIndex = idArgument.indexOf(":");
|
|
@@ -52,9 +71,29 @@ export function createBoard(sources) {
|
|
|
52
71
|
}
|
|
53
72
|
return await callResolveOne(source, naturalId);
|
|
54
73
|
}
|
|
55
|
-
|
|
56
|
-
|
|
74
|
+
// Per-source resolveOne errors must not poison sibling resolutions.
|
|
75
|
+
// A source that rejects on a natural-id lookup is treated as "I don't
|
|
76
|
+
// have this ticket" (or "I can't say"). If any source resolved we use
|
|
77
|
+
// it; only when none resolved AND at least one rejected do we surface
|
|
78
|
+
// the rejection — so the user sees a real Linear/network error when
|
|
79
|
+
// there's no fallback, but a stray "not found" from one source doesn't
|
|
80
|
+
// mask a successful match from another.
|
|
81
|
+
const results = await Promise.allSettled(sources.map(async (s) => await callResolveOne(s, idArgument)));
|
|
82
|
+
const matches = [];
|
|
83
|
+
const rejections = [];
|
|
84
|
+
for (const result of results) {
|
|
85
|
+
if (result.status === "rejected") {
|
|
86
|
+
rejections.push(result.reason);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (result.value !== undefined) {
|
|
90
|
+
matches.push(result.value);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
57
93
|
if (matches.length === 0) {
|
|
94
|
+
if (rejections.length > 0) {
|
|
95
|
+
throw rejections[0];
|
|
96
|
+
}
|
|
58
97
|
return undefined;
|
|
59
98
|
}
|
|
60
99
|
if (matches.length === 1) {
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* directory-scanned `adapterRegistry`.
|
|
7
7
|
*/
|
|
8
8
|
import type { AdapterContext, AdapterDefinition } from "./adapterDefinition.ts";
|
|
9
|
+
import type { ResolvedConfig } from "./config.ts";
|
|
9
10
|
import type { TicketSource } from "./ticketSource.ts";
|
|
10
11
|
/**
|
|
11
12
|
* Production entry point. Awaits the directory-scanned registry, then dispatches.
|
|
@@ -16,4 +17,14 @@ export declare function buildSources(rawConfigs: readonly unknown[], context: Ad
|
|
|
16
17
|
* import side effects.
|
|
17
18
|
*/
|
|
18
19
|
export declare function buildSourcesWith(registry: Record<string, AdapterDefinition>, rawConfigs: readonly unknown[], context: AdapterContext): TicketSource[];
|
|
20
|
+
/**
|
|
21
|
+
* Build the runtime source list from a ResolvedConfig: synthesizes the
|
|
22
|
+
* implicit Linear source (Linear is always active under the post-#110
|
|
23
|
+
* model — viewer + agent-* label filtering happens at the GraphQL layer)
|
|
24
|
+
* and appends any user-declared `sources`. The implicit source is omitted
|
|
25
|
+
* when the user already declared a Linear source (by `kind` or by runtime
|
|
26
|
+
* name "linear") so they can override its `name` / construction without
|
|
27
|
+
* spawning a duplicate adapter.
|
|
28
|
+
*/
|
|
29
|
+
export declare function sourcesFromConfig(config: ResolvedConfig): readonly unknown[];
|
|
19
30
|
//# sourceMappingURL=buildSources.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildSources.d.ts","sourceRoot":"","sources":["../../src/lib/buildSources.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAItD;;GAEG;AACH,wBAAsB,YAAY,CAChC,UAAU,EAAE,SAAS,OAAO,EAAE,EAC9B,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,YAAY,EAAE,CAAC,CAGzB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,EAC3C,UAAU,EAAE,SAAS,OAAO,EAAE,EAC9B,OAAO,EAAE,cAAc,GACtB,YAAY,EAAE,CAchB"}
|
|
1
|
+
{"version":3,"file":"buildSources.d.ts","sourceRoot":"","sources":["../../src/lib/buildSources.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAItD;;GAEG;AACH,wBAAsB,YAAY,CAChC,UAAU,EAAE,SAAS,OAAO,EAAE,EAC9B,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,YAAY,EAAE,CAAC,CAGzB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,EAC3C,UAAU,EAAE,SAAS,OAAO,EAAE,EAC9B,OAAO,EAAE,cAAc,GACtB,YAAY,EAAE,CAchB;AA6BD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,cAAc,GAAG,SAAS,OAAO,EAAE,CAM5E"}
|
package/dist/lib/buildSources.js
CHANGED
|
@@ -32,3 +32,44 @@ export function buildSourcesWith(registry, rawConfigs, context) {
|
|
|
32
32
|
return adapter.create(config, context);
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
|
+
const sourceShape = z.looseObject({
|
|
36
|
+
name: z.string().optional(),
|
|
37
|
+
kind: z.string().optional(),
|
|
38
|
+
});
|
|
39
|
+
/**
|
|
40
|
+
* True when `raw` is an explicitly-declared Linear source. Matches either a
|
|
41
|
+
* `kind: "linear"` entry — regardless of any `name` override — or any entry
|
|
42
|
+
* whose resolved runtime name (explicit `name`, else `kind`) is "linear".
|
|
43
|
+
* The latter catches a non-Linear adapter the user named "linear", which
|
|
44
|
+
* would otherwise collide with the implicit Linear source.
|
|
45
|
+
*
|
|
46
|
+
* Used to suppress the synthesized implicit Linear source so a renamed Linear
|
|
47
|
+
* entry like `{ kind: "linear", name: "custom" }` doesn't spawn a duplicate
|
|
48
|
+
* adapter pointed at the same viewer. Returns false for malformed entries
|
|
49
|
+
* (no `kind`/`name`) — those get rejected by the per-adapter Zod schema
|
|
50
|
+
* downstream.
|
|
51
|
+
*/
|
|
52
|
+
function isExplicitLinearSource(raw) {
|
|
53
|
+
const parsed = sourceShape.safeParse(raw);
|
|
54
|
+
/* v8 ignore next 3 @preserve -- looseObject() with all-optional fields only fails to parse non-object inputs (null, primitives); the same input would be rejected by the per-adapter Zod schema in buildSourcesWith, so this guard never fires in practice. */
|
|
55
|
+
if (!parsed.success) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
return parsed.data.kind === "linear" || (parsed.data.name ?? parsed.data.kind) === "linear";
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Build the runtime source list from a ResolvedConfig: synthesizes the
|
|
62
|
+
* implicit Linear source (Linear is always active under the post-#110
|
|
63
|
+
* model — viewer + agent-* label filtering happens at the GraphQL layer)
|
|
64
|
+
* and appends any user-declared `sources`. The implicit source is omitted
|
|
65
|
+
* when the user already declared a Linear source (by `kind` or by runtime
|
|
66
|
+
* name "linear") so they can override its `name` / construction without
|
|
67
|
+
* spawning a duplicate adapter.
|
|
68
|
+
*/
|
|
69
|
+
export function sourcesFromConfig(config) {
|
|
70
|
+
const hasExplicitLinear = config.sources.some(isExplicitLinearSource);
|
|
71
|
+
if (hasExplicitLinear) {
|
|
72
|
+
return [...config.sources];
|
|
73
|
+
}
|
|
74
|
+
return [{ kind: "linear" }, ...config.sources];
|
|
75
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-level repository validation for canonical Issues. Adapters produce
|
|
3
|
+
* Issue.repository based on their own signal (Linear: agent-* label parse;
|
|
4
|
+
* shell: script JSON output). The host (dispatcher) decides whether that
|
|
5
|
+
* repository is configured for this crew via workspace.knownRepositories.
|
|
6
|
+
*
|
|
7
|
+
* WARN+skip on unknown repo is a deliberate behavior choice (P-refined in
|
|
8
|
+
* the MVP-2 plan): one badly-labelled ticket should not throw and abort
|
|
9
|
+
* the tick across N sources.
|
|
10
|
+
*/
|
|
11
|
+
import type { Issue } from "./ticketSource.ts";
|
|
12
|
+
export declare function dispatchableRepository(issue: Issue, knownRepositories: readonly string[], log: (message: string) => void): string | undefined;
|
|
13
|
+
//# sourceMappingURL=repositoryValidation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repositoryValidation.d.ts","sourceRoot":"","sources":["../../src/lib/repositoryValidation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE/C,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,KAAK,EACZ,iBAAiB,EAAE,SAAS,MAAM,EAAE,EACpC,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC7B,MAAM,GAAG,SAAS,CAWpB"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-level repository validation for canonical Issues. Adapters produce
|
|
3
|
+
* Issue.repository based on their own signal (Linear: agent-* label parse;
|
|
4
|
+
* shell: script JSON output). The host (dispatcher) decides whether that
|
|
5
|
+
* repository is configured for this crew via workspace.knownRepositories.
|
|
6
|
+
*
|
|
7
|
+
* WARN+skip on unknown repo is a deliberate behavior choice (P-refined in
|
|
8
|
+
* the MVP-2 plan): one badly-labelled ticket should not throw and abort
|
|
9
|
+
* the tick across N sources.
|
|
10
|
+
*/
|
|
11
|
+
export function dispatchableRepository(issue, knownRepositories, log) {
|
|
12
|
+
if (issue.repository === undefined) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
if (!knownRepositories.includes(issue.repository)) {
|
|
16
|
+
log(`issue ${issue.id} references unknown repository ${issue.repository}; configured workspace.knownRepositories: ${knownRepositories.join(", ") || "(none)"}`);
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
return issue.repository;
|
|
20
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type Blocker, type Issue } from "../ticketSource.ts";
|
|
2
|
+
export declare function canonicalLinearIssue(overrides: Partial<Issue> & {
|
|
3
|
+
naturalId: string;
|
|
4
|
+
}): Issue;
|
|
5
|
+
export declare function canonicalBlocker(overrides: Partial<Blocker> & {
|
|
6
|
+
naturalId: string;
|
|
7
|
+
}): Blocker;
|
|
8
|
+
/**
|
|
9
|
+
* Canonical Issue fixture for a non-Linear source. Default source name is
|
|
10
|
+
* "shell-test"; override via `sourceName`. Mirrors `canonicalLinearIssue`'s
|
|
11
|
+
* defaults except `sourceRef` is an empty opaque object (no LinearSourceRef
|
|
12
|
+
* shape, since this is meant to stand in for any non-Linear adapter — the
|
|
13
|
+
* shell adapter, future Jira adapter, etc.).
|
|
14
|
+
*/
|
|
15
|
+
export declare function canonicalShellIssue(overrides: Partial<Issue> & {
|
|
16
|
+
naturalId: string;
|
|
17
|
+
sourceName?: string;
|
|
18
|
+
}): Issue;
|
|
19
|
+
//# sourceMappingURL=canonicalFixtures.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonicalFixtures.d.ts","sourceRoot":"","sources":["../../../src/lib/testing/canonicalFixtures.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,KAAK,EAAiB,MAAM,oBAAoB,CAAC;AAE7E,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,KAAK,CA0B7F;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAQ7F;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GACrE,KAAK,CAiBP"}
|