litmus-cli 1.4.6 → 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 +38 -4
- 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 +2 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.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/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/package.json +1 -1
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The assessment shell — how `litmus init` puts the candidate *inside* their
|
|
3
|
+
* assessment folder, and how `litmus submit` takes them back out.
|
|
4
|
+
*
|
|
5
|
+
* A process cannot change its parent shell's working directory, so "cd into the
|
|
6
|
+
* folder for them" has exactly one honest implementation: init spawns an
|
|
7
|
+
* interactive shell rooted in the assessment directory and waits on it. The
|
|
8
|
+
* candidate works in that shell. When submit succeeds it hangs that shell up,
|
|
9
|
+
* the shell exits, init's wait returns, and the candidate is back at the prompt
|
|
10
|
+
* they started from, in the directory they started in — the `cd ..` they never
|
|
11
|
+
* had to type.
|
|
12
|
+
*
|
|
13
|
+
* Two independent facts have to line up before we signal anything:
|
|
14
|
+
* - LITMUS_ASSESSMENT_SHELL=1 in our own environment, which only a shell we
|
|
15
|
+
* spawned can have set, and
|
|
16
|
+
* - a .litmus/shell.pid written by that same init, for this same folder,
|
|
17
|
+
* naming a process that is genuinely one of our ancestors.
|
|
18
|
+
* Either alone could be stale; together they can only describe the shell we are
|
|
19
|
+
* actually standing in.
|
|
20
|
+
*/
|
|
21
|
+
import path from "path";
|
|
22
|
+
import { existsSync, readFileSync, writeFileSync, unlinkSync } from "fs";
|
|
23
|
+
import { spawn, execFileSync } from "child_process";
|
|
24
|
+
import chalk from "chalk";
|
|
25
|
+
/** Set in the spawned shell's environment; read back by `litmus submit`. */
|
|
26
|
+
export const SHELL_ENV_MARKER = "LITMUS_ASSESSMENT_SHELL";
|
|
27
|
+
const SHELL_PID_FILE = "shell.pid";
|
|
28
|
+
function shellPidPath(projectRoot) {
|
|
29
|
+
return path.join(projectRoot, ".litmus", SHELL_PID_FILE);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Whether init should hand the candidate a shell at all. Piped stdin (the e2e
|
|
33
|
+
* suite, a CI script, `litmus init | tee`) must never get one — it would hang
|
|
34
|
+
* forever on a shell nobody can type into.
|
|
35
|
+
*/
|
|
36
|
+
export function shouldEnterShell(optIn) {
|
|
37
|
+
if (optIn === false)
|
|
38
|
+
return false;
|
|
39
|
+
if (process.env.LITMUS_NO_SHELL === "1")
|
|
40
|
+
return false;
|
|
41
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
42
|
+
}
|
|
43
|
+
/** The candidate's own shell, falling back to something that certainly exists. */
|
|
44
|
+
export function resolveShell() {
|
|
45
|
+
if (process.platform === "win32") {
|
|
46
|
+
return { command: process.env.ComSpec || "cmd.exe", args: [] };
|
|
47
|
+
}
|
|
48
|
+
// -i because stdio is inherited but the shell is not the session leader; zsh
|
|
49
|
+
// and bash both need it to read their interactive rc files and print a prompt.
|
|
50
|
+
return { command: process.env.SHELL || "/bin/sh", args: ["-i"] };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Drop the candidate into `projectRoot` in their own shell and block until they
|
|
54
|
+
* leave it — by typing exit, or by `litmus submit` hanging it up.
|
|
55
|
+
*
|
|
56
|
+
* Resolves to "submitted" when the folder is gone by the time the shell exits
|
|
57
|
+
* (submit deleted it), "exited" when the candidate just left.
|
|
58
|
+
*
|
|
59
|
+
* Closing the shell has two triggers, because the two cases know different
|
|
60
|
+
* things: an in-shell `litmus submit` signals it directly (instant, and ordered
|
|
61
|
+
* against its own output), while a submit that happens elsewhere — the deadline
|
|
62
|
+
* auto-submit in the watcher, or a second terminal — is noticed here by the
|
|
63
|
+
* assessment folder disappearing.
|
|
64
|
+
*/
|
|
65
|
+
export async function enterAssessmentShell(projectRoot, folderName) {
|
|
66
|
+
const { command, args } = resolveShell();
|
|
67
|
+
console.log(chalk.dim(` You're in ${folderName}/ — a shell opened here for you.`));
|
|
68
|
+
console.log(chalk.dim(` Type ${chalk.reset(chalk.cyan("exit"))}${chalk.dim(" to leave it; your assessment keeps running either way.")}`));
|
|
69
|
+
console.log();
|
|
70
|
+
const child = spawn(command, args, {
|
|
71
|
+
cwd: projectRoot,
|
|
72
|
+
stdio: "inherit",
|
|
73
|
+
env: {
|
|
74
|
+
...process.env,
|
|
75
|
+
[SHELL_ENV_MARKER]: "1",
|
|
76
|
+
LITMUS_ASSESSMENT_DIR: projectRoot,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
if (child.pid) {
|
|
80
|
+
try {
|
|
81
|
+
writeFileSync(shellPidPath(projectRoot), String(child.pid), "utf8");
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// Without the pid file submit just skips the hang-up and tells the
|
|
85
|
+
// candidate to cd out themselves. Not worth failing init over.
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Not every submit can hang this shell up itself. The deadline auto-submit
|
|
89
|
+
// runs in the detached watcher, and a candidate can run `litmus submit` from
|
|
90
|
+
// a second terminal; neither is standing in this shell, so neither is allowed
|
|
91
|
+
// to signal it — and both delete the folder, which would leave the candidate
|
|
92
|
+
// sitting in a shell whose working directory no longer exists.
|
|
93
|
+
//
|
|
94
|
+
// This process is the one that can always fix that: it spawned the shell, so
|
|
95
|
+
// it knows the pid for certain, with no pid file and no ancestry to infer.
|
|
96
|
+
// The folder going away IS the signal, whoever caused it.
|
|
97
|
+
const folderWatch = setInterval(() => {
|
|
98
|
+
if (existsSync(projectRoot))
|
|
99
|
+
return;
|
|
100
|
+
clearInterval(folderWatch);
|
|
101
|
+
try {
|
|
102
|
+
child.kill("SIGHUP");
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// Already gone — the in-shell submit path beat us to it.
|
|
106
|
+
}
|
|
107
|
+
}, 2000);
|
|
108
|
+
// The shell is the foreground job; Ctrl-C at its prompt reaches this process
|
|
109
|
+
// too. Ignoring it here means the candidate's ^C interrupts what they're
|
|
110
|
+
// running, not the init that is holding their session open.
|
|
111
|
+
const swallowSigint = () => { };
|
|
112
|
+
process.on("SIGINT", swallowSigint);
|
|
113
|
+
try {
|
|
114
|
+
await new Promise((resolve) => {
|
|
115
|
+
child.on("exit", () => resolve());
|
|
116
|
+
child.on("error", () => resolve());
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
clearInterval(folderWatch);
|
|
121
|
+
process.off("SIGINT", swallowSigint);
|
|
122
|
+
}
|
|
123
|
+
if (!existsSync(projectRoot))
|
|
124
|
+
return "submitted";
|
|
125
|
+
try {
|
|
126
|
+
unlinkSync(shellPidPath(projectRoot));
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// already gone
|
|
130
|
+
}
|
|
131
|
+
return "exited";
|
|
132
|
+
}
|
|
133
|
+
/** The pid init recorded for this folder's shell, if there is one. */
|
|
134
|
+
export function readAssessmentShellPid(projectRoot) {
|
|
135
|
+
try {
|
|
136
|
+
const pid = parseInt(readFileSync(shellPidPath(projectRoot), "utf8").trim(), 10);
|
|
137
|
+
return Number.isFinite(pid) && pid > 1 ? pid : null;
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Is `target` one of our ancestors? Guards against a recycled pid: the pid file
|
|
145
|
+
* outlives the shell that wrote it if init was killed outright.
|
|
146
|
+
*/
|
|
147
|
+
function isAncestorPid(target) {
|
|
148
|
+
if (process.platform === "win32") {
|
|
149
|
+
// No cheap ancestor walk here; the env marker plus a per-folder pid file is
|
|
150
|
+
// the gate on Windows.
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
let pid = process.ppid;
|
|
154
|
+
for (let depth = 0; depth < 8 && pid > 1; depth++) {
|
|
155
|
+
if (pid === target)
|
|
156
|
+
return true;
|
|
157
|
+
try {
|
|
158
|
+
const out = execFileSync("ps", ["-o", "ppid=", "-p", String(pid)], {
|
|
159
|
+
encoding: "utf8",
|
|
160
|
+
timeout: 3000,
|
|
161
|
+
});
|
|
162
|
+
pid = parseInt(out.trim(), 10);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
if (!Number.isFinite(pid))
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Are we running inside the shell init opened for this folder? Both the
|
|
174
|
+
* environment marker and an ancestry check have to agree — see the file header.
|
|
175
|
+
*
|
|
176
|
+
* Callers use this before printing anything about where the candidate will end
|
|
177
|
+
* up, since leaving the shell moves them out of the folder for free.
|
|
178
|
+
*/
|
|
179
|
+
export function inAssessmentShell(shellPid) {
|
|
180
|
+
if (process.env[SHELL_ENV_MARKER] !== "1")
|
|
181
|
+
return false;
|
|
182
|
+
if (shellPid === null)
|
|
183
|
+
return false;
|
|
184
|
+
return isAncestorPid(shellPid);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* End the assessment shell. Returns true when the hang-up went out.
|
|
188
|
+
*
|
|
189
|
+
* Call this LAST and print nothing after it: the shell hangs up its jobs on the
|
|
190
|
+
* way out, and this process is one of them.
|
|
191
|
+
*/
|
|
192
|
+
export function hangUpAssessmentShell(shellPid) {
|
|
193
|
+
if (!inAssessmentShell(shellPid))
|
|
194
|
+
return false;
|
|
195
|
+
// zsh forwards SIGHUP to its jobs, and we are one. Ignore it so this process
|
|
196
|
+
// finishes on its own terms instead of dying between two writes.
|
|
197
|
+
process.on("SIGHUP", () => { });
|
|
198
|
+
try {
|
|
199
|
+
if (process.platform === "win32") {
|
|
200
|
+
// No /T: that would take this process down with the shell.
|
|
201
|
+
execFileSync("taskkill", ["/PID", String(shellPid), "/F"], { stdio: "ignore", timeout: 5000 });
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
process.kill(shellPid, "SIGHUP");
|
|
205
|
+
}
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/lib/session.ts"],"names":[],"mappings":"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"}
|