litmus-cli 1.4.5 → 1.4.7-eng1502.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/README.md +15 -0
- package/dist/commands/init.d.ts +3 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +74 -10
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/submit.d.ts.map +1 -1
- package/dist/commands/submit.js +49 -5
- package/dist/commands/submit.js.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/config.d.ts +33 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +43 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/deadline.d.ts +92 -14
- package/dist/lib/deadline.d.ts.map +1 -1
- package/dist/lib/deadline.js +112 -17
- package/dist/lib/deadline.js.map +1 -1
- package/dist/lib/detect-project.d.ts.map +1 -1
- package/dist/lib/detect-project.js +48 -2
- package/dist/lib/detect-project.js.map +1 -1
- package/dist/lib/editor.d.ts +73 -0
- package/dist/lib/editor.d.ts.map +1 -0
- package/dist/lib/editor.js +222 -0
- package/dist/lib/editor.js.map +1 -0
- package/dist/lib/schemas.d.ts +6 -0
- package/dist/lib/schemas.d.ts.map +1 -1
- package/dist/lib/schemas.js +8 -0
- package/dist/lib/schemas.js.map +1 -1
- package/dist/lib/select.d.ts +64 -0
- package/dist/lib/select.d.ts.map +1 -0
- package/dist/lib/select.js +170 -0
- package/dist/lib/select.js.map +1 -0
- package/dist/lib/session.d.ts +65 -0
- package/dist/lib/session.d.ts.map +1 -0
- package/dist/lib/session.js +212 -0
- package/dist/lib/session.js.map +1 -0
- package/dist/lib/watcher.js +166 -63
- package/dist/lib/watcher.js.map +1 -1
- package/package.json +1 -1
package/dist/lib/deadline.d.ts
CHANGED
|
@@ -9,29 +9,107 @@
|
|
|
9
9
|
* wake: the interval also pauses while suspended, but the first tick after
|
|
10
10
|
* resume sees that wall-clock has passed the deadline and fires immediately.
|
|
11
11
|
* Polling additionally sidesteps the ~24.8-day `setTimeout` ceiling.
|
|
12
|
+
*
|
|
13
|
+
* Two pieces (ENG-1489 / ENG-1490):
|
|
14
|
+
*
|
|
15
|
+
* - `armDeadlinePoll` — the dumb wall-clock poll. It reads the CURRENT
|
|
16
|
+
* deadline through a getter on every tick, because the deadline can move
|
|
17
|
+
* mid-session (server-side extensions and corrections arrive via the
|
|
18
|
+
* heartbeat response). It never latches and never clears itself on fire —
|
|
19
|
+
* dedupe, retries, and the fire decision live in the controller.
|
|
20
|
+
*
|
|
21
|
+
* - `createAutoSubmitController` — the fire decision. It never submits on
|
|
22
|
+
* local state alone: every attempt re-verifies the deadline against the
|
|
23
|
+
* server first. A stale-early local deadline that fires "successfully" is
|
|
24
|
+
* strictly worse than one that fails — the server, which believes the
|
|
25
|
+
* deadline is still hours or days away, accepts the upload as an ordinary
|
|
26
|
+
* early submission and the candidate's session ends prematurely. The
|
|
27
|
+
* verification is what makes retrying safe at all.
|
|
12
28
|
*/
|
|
13
29
|
export interface DeadlinePollHandle {
|
|
14
30
|
stop(): void;
|
|
15
31
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
32
|
+
/** Tick gaps beyond this are reported as a suspend/resume (wake) even when the
|
|
33
|
+
* poll cadence is long. */
|
|
34
|
+
export declare const WAKE_GAP_MIN_MS: number;
|
|
35
|
+
export interface DeadlinePollOptions {
|
|
36
|
+
/** Read the current effective deadline (epoch ms), or null when none is
|
|
37
|
+
* configured. Re-read every tick so mid-session changes take effect. */
|
|
38
|
+
getDeadline: () => number | null;
|
|
19
39
|
/** Poll cadence in ms. */
|
|
20
40
|
pollMs: number;
|
|
21
|
-
/**
|
|
22
|
-
|
|
41
|
+
/** Called on EVERY tick at/after the deadline. The caller owns dedupe. */
|
|
42
|
+
onDue: () => void;
|
|
23
43
|
/** Injectable clock (defaults to Date.now) — used by tests. */
|
|
24
44
|
now?: () => number;
|
|
25
|
-
/** Called when the poll is armed, with ms until the deadline
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
45
|
+
/** Called once when the poll is armed, with ms until the deadline (null when
|
|
46
|
+
* no deadline is configured yet). */
|
|
47
|
+
onArmed?: (msUntilDeadline: number | null) => void;
|
|
48
|
+
/** Called when consecutive ticks are separated by far more than pollMs — the
|
|
49
|
+
* process was suspended (lid close / sleep) and just resumed. */
|
|
50
|
+
onWake?: (gapMs: number) => void;
|
|
29
51
|
}
|
|
30
52
|
/**
|
|
31
|
-
* Arm a poll that
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
53
|
+
* Arm a poll that calls `onDue` on every tick where `now() >= getDeadline()`.
|
|
54
|
+
* Always arms, even when the deadline is currently null or already past: a
|
|
55
|
+
* null deadline can become real via server sync, and a watcher restarted
|
|
56
|
+
* after the deadline should still hand the (server-verified) recovery
|
|
57
|
+
* decision to the controller instead of silently skipping.
|
|
35
58
|
*/
|
|
36
|
-
export declare function
|
|
59
|
+
export declare function armDeadlinePoll(opts: DeadlinePollOptions): DeadlinePollHandle;
|
|
60
|
+
/** Outcome of asking the server for the authoritative times. */
|
|
61
|
+
export type VerifyTimesResult =
|
|
62
|
+
/** Server answered; times were synced by the caller. `effectiveDeadline` is
|
|
63
|
+
* the post-sync effective deadline (epoch ms) or null. */
|
|
64
|
+
{
|
|
65
|
+
status: "ok";
|
|
66
|
+
effectiveDeadline: number | null;
|
|
67
|
+
}
|
|
68
|
+
/** Network error / 5xx — transient; try again shortly. */
|
|
69
|
+
| {
|
|
70
|
+
status: "unavailable";
|
|
71
|
+
}
|
|
72
|
+
/** 4xx — the token was refused (session likely over). Do not submit; the
|
|
73
|
+
* activity-upload path independently shuts the watcher down on
|
|
74
|
+
* "Session over". */
|
|
75
|
+
| {
|
|
76
|
+
status: "rejected";
|
|
77
|
+
};
|
|
78
|
+
export interface AutoSubmitOutcome {
|
|
79
|
+
kind: "submitted" | "failed" | "averted" | "deferred" | "exhausted";
|
|
80
|
+
/** 1-based attempt number for submitted/failed. */
|
|
81
|
+
attempt?: number;
|
|
82
|
+
detail?: string;
|
|
83
|
+
}
|
|
84
|
+
export interface AutoSubmitControllerOptions {
|
|
85
|
+
/** Fetch authoritative times from the server AND apply them locally
|
|
86
|
+
* (config + poll getter). Must not throw. */
|
|
87
|
+
verifyTimes: () => Promise<VerifyTimesResult>;
|
|
88
|
+
/** Run `litmus submit --yes`. Must not throw. */
|
|
89
|
+
runSubmit: () => Promise<{
|
|
90
|
+
ok: boolean;
|
|
91
|
+
error?: string;
|
|
92
|
+
}>;
|
|
93
|
+
onOutcome?: (outcome: AutoSubmitOutcome) => void;
|
|
94
|
+
now?: () => number;
|
|
95
|
+
/** Verified-due submit attempts before giving up (server cron owns the
|
|
96
|
+
* session after that). */
|
|
97
|
+
maxAttempts?: number;
|
|
98
|
+
/** Backoff after a failed submit attempt; last entry repeats. */
|
|
99
|
+
retryBackoffMs?: number[];
|
|
100
|
+
/** Re-check cadence while the server is unreachable at the deadline. */
|
|
101
|
+
verifyRetryMs?: number;
|
|
102
|
+
}
|
|
103
|
+
export interface AutoSubmitController {
|
|
104
|
+
/** Called by the poll on every due tick. Serialized internally. */
|
|
105
|
+
handleDue(): Promise<void>;
|
|
106
|
+
/** Called when a server sync moved the deadline. A deadline now in the
|
|
107
|
+
* future resets the attempt budget — the earlier failures belonged to a
|
|
108
|
+
* cutoff that no longer exists. */
|
|
109
|
+
noteDeadlineChanged(newDeadlineMs: number | null): void;
|
|
110
|
+
/** External signal that the session is over (e.g. upload path saw
|
|
111
|
+
* "Session over") — stop attempting. */
|
|
112
|
+
markDone(): void;
|
|
113
|
+
}
|
|
114
|
+
export declare function createAutoSubmitController(opts: AutoSubmitControllerOptions): AutoSubmitController;
|
|
37
115
|
//# sourceMappingURL=deadline.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deadline.d.ts","sourceRoot":"","sources":["../../src/lib/deadline.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"deadline.d.ts","sourceRoot":"","sources":["../../src/lib/deadline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,MAAM,WAAW,kBAAkB;IACjC,IAAI,IAAI,IAAI,CAAA;CACb;AAED;2BAC2B;AAC3B,eAAO,MAAM,eAAe,QAAa,CAAA;AAEzC,MAAM,WAAW,mBAAmB;IAClC;4EACwE;IACxE,WAAW,EAAE,MAAM,MAAM,GAAG,IAAI,CAAA;IAChC,0BAA0B;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,0EAA0E;IAC1E,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,+DAA+D;IAC/D,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB;yCACqC;IACrC,OAAO,CAAC,EAAE,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IAClD;qEACiE;IACjE,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CACjC;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,kBAAkB,CAuB7E;AAED,gEAAgE;AAChE,MAAM,MAAM,iBAAiB;AAC3B;0DAC0D;AACxD;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE;AACpD,0DAA0D;GACxD;IAAE,MAAM,EAAE,aAAa,CAAA;CAAE;AAC3B;;qBAEqB;GACnB;IAAE,MAAM,EAAE,UAAU,CAAA;CAAE,CAAA;AAE1B,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,WAAW,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,GAAG,WAAW,CAAA;IACnE,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,2BAA2B;IAC1C;iDAC6C;IAC7C,WAAW,EAAE,MAAM,OAAO,CAAC,iBAAiB,CAAC,CAAA;IAC7C,iDAAiD;IACjD,SAAS,EAAE,MAAM,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACzD,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,IAAI,CAAA;IAChD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB;8BAC0B;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,iEAAiE;IACjE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;IACzB,wEAAwE;IACxE,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,oBAAoB;IACnC,mEAAmE;IACnE,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1B;;uCAEmC;IACnC,mBAAmB,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAA;IACvD;4CACwC;IACxC,QAAQ,IAAI,IAAI,CAAA;CACjB;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,2BAA2B,GAAG,oBAAoB,CA6ElG"}
|
package/dist/lib/deadline.js
CHANGED
|
@@ -9,32 +9,127 @@
|
|
|
9
9
|
* wake: the interval also pauses while suspended, but the first tick after
|
|
10
10
|
* resume sees that wall-clock has passed the deadline and fires immediately.
|
|
11
11
|
* Polling additionally sidesteps the ~24.8-day `setTimeout` ceiling.
|
|
12
|
+
*
|
|
13
|
+
* Two pieces (ENG-1489 / ENG-1490):
|
|
14
|
+
*
|
|
15
|
+
* - `armDeadlinePoll` — the dumb wall-clock poll. It reads the CURRENT
|
|
16
|
+
* deadline through a getter on every tick, because the deadline can move
|
|
17
|
+
* mid-session (server-side extensions and corrections arrive via the
|
|
18
|
+
* heartbeat response). It never latches and never clears itself on fire —
|
|
19
|
+
* dedupe, retries, and the fire decision live in the controller.
|
|
20
|
+
*
|
|
21
|
+
* - `createAutoSubmitController` — the fire decision. It never submits on
|
|
22
|
+
* local state alone: every attempt re-verifies the deadline against the
|
|
23
|
+
* server first. A stale-early local deadline that fires "successfully" is
|
|
24
|
+
* strictly worse than one that fails — the server, which believes the
|
|
25
|
+
* deadline is still hours or days away, accepts the upload as an ordinary
|
|
26
|
+
* early submission and the candidate's session ends prematurely. The
|
|
27
|
+
* verification is what makes retrying safe at all.
|
|
12
28
|
*/
|
|
29
|
+
/** Tick gaps beyond this are reported as a suspend/resume (wake) even when the
|
|
30
|
+
* poll cadence is long. */
|
|
31
|
+
export const WAKE_GAP_MIN_MS = 2 * 60000;
|
|
13
32
|
/**
|
|
14
|
-
* Arm a poll that
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
33
|
+
* Arm a poll that calls `onDue` on every tick where `now() >= getDeadline()`.
|
|
34
|
+
* Always arms, even when the deadline is currently null or already past: a
|
|
35
|
+
* null deadline can become real via server sync, and a watcher restarted
|
|
36
|
+
* after the deadline should still hand the (server-verified) recovery
|
|
37
|
+
* decision to the controller instead of silently skipping.
|
|
18
38
|
*/
|
|
19
|
-
export function
|
|
39
|
+
export function armDeadlinePoll(opts) {
|
|
20
40
|
const now = opts.now ?? Date.now;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
opts.onArmed?.(opts.deadline - now());
|
|
26
|
-
let fired = false;
|
|
41
|
+
const initial = opts.getDeadline();
|
|
42
|
+
opts.onArmed?.(initial === null ? null : initial - now());
|
|
43
|
+
let lastTick = now();
|
|
27
44
|
const timer = setInterval(() => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
45
|
+
const t = now();
|
|
46
|
+
const gap = t - lastTick;
|
|
47
|
+
lastTick = t;
|
|
48
|
+
if (gap > Math.max(opts.pollMs * 4, WAKE_GAP_MIN_MS)) {
|
|
49
|
+
opts.onWake?.(gap);
|
|
50
|
+
}
|
|
51
|
+
const deadline = opts.getDeadline();
|
|
52
|
+
if (deadline !== null && t >= deadline) {
|
|
53
|
+
opts.onDue();
|
|
34
54
|
}
|
|
35
55
|
}, opts.pollMs);
|
|
36
56
|
// Intentionally NOT unref()'d: the watcher should stay alive to fire the
|
|
37
57
|
// auto-submit even if every other keep-alive source went quiet.
|
|
38
58
|
return { stop: () => clearInterval(timer) };
|
|
39
59
|
}
|
|
60
|
+
export function createAutoSubmitController(opts) {
|
|
61
|
+
const now = opts.now ?? Date.now;
|
|
62
|
+
const maxAttempts = opts.maxAttempts ?? 3;
|
|
63
|
+
const backoffs = opts.retryBackoffMs ?? [60000, 5 * 60000];
|
|
64
|
+
const verifyRetryMs = opts.verifyRetryMs ?? 2 * 60000;
|
|
65
|
+
let state = "idle";
|
|
66
|
+
let attempts = 0;
|
|
67
|
+
let gateUntil = 0;
|
|
68
|
+
let exhaustedReported = false;
|
|
69
|
+
async function handleDue() {
|
|
70
|
+
if (state !== "idle")
|
|
71
|
+
return;
|
|
72
|
+
if (now() < gateUntil)
|
|
73
|
+
return;
|
|
74
|
+
if (attempts >= maxAttempts) {
|
|
75
|
+
if (!exhaustedReported) {
|
|
76
|
+
exhaustedReported = true;
|
|
77
|
+
opts.onOutcome?.({ kind: "exhausted", attempt: attempts });
|
|
78
|
+
}
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
state = "in_flight";
|
|
82
|
+
try {
|
|
83
|
+
const verdict = await opts.verifyTimes();
|
|
84
|
+
if (verdict.status === "unavailable") {
|
|
85
|
+
// Can't verify ⇒ can't safely submit (and an upload would almost
|
|
86
|
+
// certainly fail on the same dead network). Doesn't burn an attempt:
|
|
87
|
+
// a machine that wakes offline must still submit when the network
|
|
88
|
+
// returns, however long that takes.
|
|
89
|
+
gateUntil = now() + verifyRetryMs;
|
|
90
|
+
opts.onOutcome?.({ kind: "deferred", detail: "server unreachable for deadline verification" });
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (verdict.status === "rejected") {
|
|
94
|
+
gateUntil = now() + verifyRetryMs * 5;
|
|
95
|
+
opts.onOutcome?.({ kind: "deferred", detail: "server rejected deadline verification" });
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (verdict.effectiveDeadline === null || now() < verdict.effectiveDeadline) {
|
|
99
|
+
// The local deadline was stale-early — the sync inside verifyTimes
|
|
100
|
+
// already re-armed the poll with the server's value. This is the
|
|
101
|
+
// protective path: without it, a submit here would have been
|
|
102
|
+
// ACCEPTED by the server as an ordinary early submission.
|
|
103
|
+
opts.onOutcome?.({ kind: "averted" });
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
attempts += 1;
|
|
107
|
+
const res = await opts.runSubmit();
|
|
108
|
+
if (res.ok) {
|
|
109
|
+
state = "done";
|
|
110
|
+
opts.onOutcome?.({ kind: "submitted", attempt: attempts });
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
gateUntil = now() + backoffs[Math.min(attempts - 1, backoffs.length - 1)];
|
|
114
|
+
opts.onOutcome?.({ kind: "failed", attempt: attempts, detail: res.error });
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
if (state === "in_flight")
|
|
118
|
+
state = "idle";
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function noteDeadlineChanged(newDeadlineMs) {
|
|
122
|
+
if (state === "done")
|
|
123
|
+
return;
|
|
124
|
+
if (newDeadlineMs !== null && newDeadlineMs > now()) {
|
|
125
|
+
attempts = 0;
|
|
126
|
+
gateUntil = 0;
|
|
127
|
+
exhaustedReported = false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function markDone() {
|
|
131
|
+
state = "done";
|
|
132
|
+
}
|
|
133
|
+
return { handleDue, noteDeadlineChanged, markDone };
|
|
134
|
+
}
|
|
40
135
|
//# sourceMappingURL=deadline.js.map
|
package/dist/lib/deadline.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deadline.js","sourceRoot":"","sources":["../../src/lib/deadline.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"deadline.js","sourceRoot":"","sources":["../../src/lib/deadline.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detect-project.d.ts","sourceRoot":"","sources":["../../src/lib/detect-project.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,WAAW,GACnB,MAAM,GACN,QAAQ,GACR,IAAI,GACJ,MAAM,GACN,QAAQ,GACR,MAAM,GACN,MAAM,GACN,KAAK,GACL,SAAS,CAAA;AAEb,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,WAAW,CAAA;IACjB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,sBAAsB,SAAS,CAAA;AA0D5C;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,IAAI;IAC1C,OAAO,EAAE,MAAM,CAAA;IACf,aAAa,EAAE,OAAO,CAAA;IACtB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;CAC/B,CAkBA;
|
|
1
|
+
{"version":3,"file":"detect-project.d.ts","sourceRoot":"","sources":["../../src/lib/detect-project.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,WAAW,GACnB,MAAM,GACN,QAAQ,GACR,IAAI,GACJ,MAAM,GACN,QAAQ,GACR,MAAM,GACN,MAAM,GACN,KAAK,GACL,SAAS,CAAA;AAEb,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,WAAW,CAAA;IACjB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,sBAAsB,SAAS,CAAA;AA0D5C;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,IAAI;IAC1C,OAAO,EAAE,MAAM,CAAA;IACf,aAAa,EAAE,OAAO,CAAA;IACtB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;CAC/B,CAkBA;AAyBD;;GAEG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAmGtD"}
|
|
@@ -92,6 +92,27 @@ export function resolvePythonInterpreter() {
|
|
|
92
92
|
const last = candidates[candidates.length - 1];
|
|
93
93
|
return { command: last.command, matchesGrader: false, detectedVersion: null };
|
|
94
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Is `bin` runnable from PATH?
|
|
97
|
+
*
|
|
98
|
+
* `--version` rather than `which`/`where`: one probe that works on all three
|
|
99
|
+
* platforms, and it proves the binary actually EXECUTES. A PATH entry pointing at
|
|
100
|
+
* a broken install or the wrong architecture resolves fine and then fails on use,
|
|
101
|
+
* which is the case a warning most needs to catch. `shell: true` on Windows so
|
|
102
|
+
* `.cmd`/`.ps1` shims resolve, matching the Python probe above.
|
|
103
|
+
*/
|
|
104
|
+
function isOnPath(bin) {
|
|
105
|
+
try {
|
|
106
|
+
return (spawnSync(bin, ["--version"], {
|
|
107
|
+
encoding: "utf8",
|
|
108
|
+
shell: process.platform === "win32",
|
|
109
|
+
}).status === 0);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// spawnSync throws (rather than returning a status) on some ENOENT paths.
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
95
116
|
/**
|
|
96
117
|
* Detect project type and suggest an install command.
|
|
97
118
|
*/
|
|
@@ -99,12 +120,37 @@ export function detectProject(dir) {
|
|
|
99
120
|
if (existsSync(path.join(dir, "package.json"))) {
|
|
100
121
|
const hasYarnLock = existsSync(path.join(dir, "yarn.lock"));
|
|
101
122
|
const hasPnpmLock = existsSync(path.join(dir, "pnpm-lock.yaml"));
|
|
123
|
+
// A bun lockfile has to steer this, not just be tolerated. `npm install` on a
|
|
124
|
+
// Bun project does populate node_modules, but it ignores bun.lock and writes a
|
|
125
|
+
// COMPETING package-lock.json next to it — so the candidate ends up with a tree
|
|
126
|
+
// resolved fresh from the caret ranges, which is the drift the lockfile exists
|
|
127
|
+
// to prevent, plus a stray lockfile in their submission.
|
|
128
|
+
const hasBunLock = existsSync(path.join(dir, "bun.lock")) || existsSync(path.join(dir, "bun.lockb"));
|
|
102
129
|
const installCommand = hasPnpmLock
|
|
103
130
|
? "pnpm install"
|
|
104
131
|
: hasYarnLock
|
|
105
132
|
? "yarn"
|
|
106
|
-
:
|
|
107
|
-
|
|
133
|
+
: hasBunLock
|
|
134
|
+
? "bun install"
|
|
135
|
+
: "npm install";
|
|
136
|
+
// Same disclosure contract as the Python skew below: a missing toolchain is
|
|
137
|
+
// ours to state here, not theirs to infer. Without this, `init`'s install
|
|
138
|
+
// failure prints "run `bun install` manually", which is the command that just
|
|
139
|
+
// failed — and the candidate is left guessing that a runtime they may never
|
|
140
|
+
// have heard of is the missing piece.
|
|
141
|
+
//
|
|
142
|
+
// Gated on the SELECTED command, not on `hasBunLock`. A repo carrying both a
|
|
143
|
+
// bun lockfile and a pnpm/yarn one resolves to pnpm/yarn above, and telling
|
|
144
|
+
// that candidate to install Bun and run `bun install` would send them to
|
|
145
|
+
// install a second, competing dependency tree over the one we just built.
|
|
146
|
+
// Also means npm/yarn/pnpm repos never pay for the PATH probe.
|
|
147
|
+
const toolchainWarning = installCommand === "bun install" && !isOnPath("bun")
|
|
148
|
+
? "This assessment's scaffold is a Bun project (bun.lock), and Bun isn't on " +
|
|
149
|
+
"your PATH. Install it from https://bun.sh, then re-run `bun install` in " +
|
|
150
|
+
"this folder. npm can install the dependencies but cannot run the " +
|
|
151
|
+
"scaffold's server — it uses Bun-only APIs."
|
|
152
|
+
: undefined;
|
|
153
|
+
return { type: "node", installCommand, toolchainWarning };
|
|
108
154
|
}
|
|
109
155
|
if (existsSync(path.join(dir, "requirements.txt")) ||
|
|
110
156
|
existsSync(path.join(dir, "pyproject.toml")) ||
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detect-project.js","sourceRoot":"","sources":["../../src/lib/detect-project.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"detect-project.js","sourceRoot":"","sources":["../../src/lib/detect-project.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opening — and later closing — the candidate's editor on their own machine.
|
|
3
|
+
*
|
|
4
|
+
* `litmus init` offers the editors it can actually launch and opens the
|
|
5
|
+
* assessment in the chosen one; `litmus submit` closes that window again once
|
|
6
|
+
* the work is in, so the agent chat session that was open against the
|
|
7
|
+
* assessment ends with the assessment instead of outliving it.
|
|
8
|
+
*
|
|
9
|
+
* Closing is best-effort and SILENT. Neither VS Code nor Cursor exposes a
|
|
10
|
+
* "close this window" command, so every route here is an OS window-manager
|
|
11
|
+
* trick that can legitimately be unavailable: macOS gates UI scripting behind
|
|
12
|
+
* Accessibility permission, and a Linux box may have neither wmctrl nor
|
|
13
|
+
* xdotool. A candidate who has just submitted should not be handed a warning
|
|
14
|
+
* about a window they can close themselves, so a failure produces no output at
|
|
15
|
+
* all.
|
|
16
|
+
*/
|
|
17
|
+
export type EditorId = "vscode" | "cursor";
|
|
18
|
+
interface EditorSpec {
|
|
19
|
+
id: EditorId;
|
|
20
|
+
label: string;
|
|
21
|
+
/** Command the editor installs on PATH ("Shell Command: Install ... in PATH"). */
|
|
22
|
+
cli: string;
|
|
23
|
+
/** Bundle name under /Applications on macOS. */
|
|
24
|
+
macApp: string;
|
|
25
|
+
/** Process name as System Events sees it (NOT the bundle name for VS Code). */
|
|
26
|
+
macProcess: string;
|
|
27
|
+
/** Process name for Get-Process on Windows, and the WM_CLASS prefix on Linux. */
|
|
28
|
+
processName: string;
|
|
29
|
+
}
|
|
30
|
+
export interface DetectedEditor {
|
|
31
|
+
spec: EditorSpec;
|
|
32
|
+
/** "cli" launches through the editor's own PATH command; "app" is the macOS
|
|
33
|
+
* bundle fallback for candidates who never ran "Install 'code' in PATH". */
|
|
34
|
+
how: "cli" | "app";
|
|
35
|
+
appPath?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The editors we can actually open on this machine, in menu order. An editor
|
|
39
|
+
* the candidate has installed but never added to PATH still shows up on macOS
|
|
40
|
+
* (we open the bundle directly) — that gap is common enough with Cursor that
|
|
41
|
+
* omitting it would hide the option most candidates want.
|
|
42
|
+
*/
|
|
43
|
+
export declare function detectEditors(): DetectedEditor[];
|
|
44
|
+
/**
|
|
45
|
+
* Open `dir` in the chosen editor. Resolves false only when the launcher itself
|
|
46
|
+
* is missing — spawn reports that asynchronously via 'error', so we give it a
|
|
47
|
+
* short grace period rather than reporting success and printing a line that
|
|
48
|
+
* turns out to be a lie. The editor is detached: it outlives the CLI.
|
|
49
|
+
*/
|
|
50
|
+
export declare function openInEditor(editor: DetectedEditor, dir: string): Promise<boolean>;
|
|
51
|
+
/**
|
|
52
|
+
* Close every window of `process` whose title mentions `folderName`, by
|
|
53
|
+
* pressing its close button through the Accessibility API. Editor window titles
|
|
54
|
+
* carry the workspace folder ("index.ts — acme-backend"), so this hits the
|
|
55
|
+
* assessment window and leaves the candidate's other projects alone.
|
|
56
|
+
*
|
|
57
|
+
* Exported for tests: the escaping is the part worth pinning.
|
|
58
|
+
*/
|
|
59
|
+
export declare function macCloseScript(processName: string, folderName: string): string;
|
|
60
|
+
/**
|
|
61
|
+
* Windows equivalent. `.Contains` rather than `-like '*x*'` so a folder name
|
|
62
|
+
* with a wildcard character in it can't widen the match. CloseMainWindow is the
|
|
63
|
+
* polite close — an editor with unsaved work still gets to prompt.
|
|
64
|
+
*/
|
|
65
|
+
export declare function winCloseScript(processName: string, folderName: string): string;
|
|
66
|
+
/**
|
|
67
|
+
* Best-effort, silent close of the assessment's editor window. Every failure
|
|
68
|
+
* mode — no permission, no window manager, editor already quit, window renamed
|
|
69
|
+
* — is a no-op with no output.
|
|
70
|
+
*/
|
|
71
|
+
export declare function closeEditorWindow(editorId: EditorId, folderName: string): void;
|
|
72
|
+
export {};
|
|
73
|
+
//# sourceMappingURL=editor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../../src/lib/editor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAOH,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAE1C,UAAU,UAAU;IAClB,EAAE,EAAE,QAAQ,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,kFAAkF;IAClF,GAAG,EAAE,MAAM,CAAA;IACX,gDAAgD;IAChD,MAAM,EAAE,MAAM,CAAA;IACd,+EAA+E;IAC/E,UAAU,EAAE,MAAM,CAAA;IAClB,iFAAiF;IACjF,WAAW,EAAE,MAAM,CAAA;CACpB;AAOD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,UAAU,CAAA;IAChB;iFAC6E;IAC7E,GAAG,EAAE,KAAK,GAAG,KAAK,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AA0BD;;;;;GAKG;AACH,wBAAgB,aAAa,IAAI,cAAc,EAAE,CAWhD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAqBlF;AAYD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAe9E;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAQ9E;AA8CD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAwB9E"}
|