litmus-cli 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +11 -5
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +134 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/submit.d.ts.map +1 -1
- package/dist/commands/submit.js +46 -23
- package/dist/commands/submit.js.map +1 -1
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/api.d.ts +3 -1
- package/dist/lib/api.d.ts.map +1 -1
- package/dist/lib/api.js +67 -17
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/config.d.ts +5 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +20 -2
- package/dist/lib/config.js.map +1 -1
- package/dist/utils/detect-project.d.ts.map +1 -1
- package/dist/utils/detect-project.js +3 -2
- package/dist/utils/detect-project.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/init.ts +13 -8
- package/src/commands/status.ts +145 -0
- package/src/commands/submit.ts +46 -22
- package/src/index.ts +13 -0
- package/src/lib/api.ts +70 -18
- package/src/lib/config.ts +22 -2
- package/src/utils/detect-project.ts +3 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAiBA,wBAAsB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAiBA,wBAAsB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA+J1D"}
|
package/dist/commands/init.js
CHANGED
|
@@ -31,7 +31,7 @@ export async function runInit(token) {
|
|
|
31
31
|
const targetDir = path.resolve(process.cwd(), metadata.folderName);
|
|
32
32
|
const { existsSync } = await import("fs");
|
|
33
33
|
if (existsSync(targetDir)) {
|
|
34
|
-
fatal(`Folder "${metadata.folderName}" already exists in this directory.`, `Delete or rename it, then run
|
|
34
|
+
fatal(`Folder "${metadata.folderName}" already exists in this directory.`, `Delete or rename it, then re-run litmus init with your token.`);
|
|
35
35
|
}
|
|
36
36
|
// Print what we're setting up
|
|
37
37
|
console.log();
|
|
@@ -87,12 +87,18 @@ export async function runInit(token) {
|
|
|
87
87
|
}
|
|
88
88
|
// Step 4: Initialize git repo with initial commit
|
|
89
89
|
try {
|
|
90
|
-
execSync("git
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
execSync("git --version", { stdio: "pipe" });
|
|
91
|
+
try {
|
|
92
|
+
execSync("git init", { cwd: targetDir, stdio: "pipe" });
|
|
93
|
+
execSync("git add -A", { cwd: targetDir, stdio: "pipe" });
|
|
94
|
+
execSync("git commit -m \"Assessment: initial state\"", { cwd: targetDir, stdio: "pipe" });
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
warn("Git is installed but failed to initialize. Check your git config (user.name / user.email).");
|
|
98
|
+
}
|
|
93
99
|
}
|
|
94
100
|
catch {
|
|
95
|
-
//
|
|
101
|
+
// git not installed — non-critical, analysis degrades gracefully
|
|
96
102
|
}
|
|
97
103
|
// Step 5: Write .litmus/config.json
|
|
98
104
|
await writeConfig(targetDir, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,IAAI,CAAA;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAA;AACjC,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,EAAE,MAAM,IAAI,CAAA;AACnB,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,GAAG,MAAM,KAAK,CAAA;AACrB,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAC1D,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAC7E,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAExC,MAAM,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,0BAA0B,CAAA;AAEjF,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,KAAa;IACzC,MAAM,OAAO,GAAG,gBAAgB,CAAA;IAEhC,yBAAyB;IACzB,MAAM,OAAO,GAAG,GAAG,CAAC,yBAAyB,CAAC,CAAC,KAAK,EAAE,CAAA;IACtD,IAAI,QAAuD,CAAA;IAE3D,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,iBAAiB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;QAClD,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;IAC9B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAA;QACjC,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QAC5D,KAAK,CAAC,GAAG,EAAE,kEAAkE,CAAC,CAAA;IAChF,CAAC;IAED,6BAA6B;IAC7B,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAA;IAClE,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,CAAA;IACzC,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC1B,KAAK,CACH,WAAW,QAAQ,CAAC,UAAU,qCAAqC,EACnE,
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAOA,wBAAsB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAsG/C"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync, readdirSync } from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { readConfig, findProjectRoot } from "../lib/config.js";
|
|
5
|
+
import { fatal, info } from "../utils/errors.js";
|
|
6
|
+
import { execSync } from "child_process";
|
|
7
|
+
export async function runStatus() {
|
|
8
|
+
const config = await readConfig();
|
|
9
|
+
if (!config) {
|
|
10
|
+
fatal("No Litmus assessment found in this directory.", "Run this command from inside your assessment folder, or run 'litmus init <token>' first.");
|
|
11
|
+
}
|
|
12
|
+
const projectRoot = findProjectRoot() ?? process.cwd();
|
|
13
|
+
const folderName = path.basename(projectRoot);
|
|
14
|
+
console.log();
|
|
15
|
+
console.log(chalk.bold(` ${config.assessmentName}`));
|
|
16
|
+
console.log();
|
|
17
|
+
// Candidate info
|
|
18
|
+
info(` Candidate: ${config.candidateName} (${config.candidateEmail})`);
|
|
19
|
+
info(` Folder: ${folderName}/`);
|
|
20
|
+
// Time elapsed since init
|
|
21
|
+
const startedAt = new Date(config.startedAt);
|
|
22
|
+
const elapsed = Date.now() - startedAt.getTime();
|
|
23
|
+
const elapsedHours = Math.floor(elapsed / (1000 * 60 * 60));
|
|
24
|
+
const elapsedMins = Math.floor((elapsed % (1000 * 60 * 60)) / (1000 * 60));
|
|
25
|
+
info(` Started: ${startedAt.toLocaleString()} (${elapsedHours}h ${elapsedMins}m ago)`);
|
|
26
|
+
// Deadline / time remaining
|
|
27
|
+
if (config.deadline) {
|
|
28
|
+
const deadline = new Date(config.deadline);
|
|
29
|
+
const remaining = deadline.getTime() - Date.now();
|
|
30
|
+
if (remaining <= 0) {
|
|
31
|
+
console.log(chalk.red(` Deadline: PASSED (${deadline.toLocaleString()})`));
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
const remHours = Math.floor(remaining / (1000 * 60 * 60));
|
|
35
|
+
const remMins = Math.floor((remaining % (1000 * 60 * 60)) / (1000 * 60));
|
|
36
|
+
info(` Deadline: ${deadline.toLocaleString()} (${remHours}h ${remMins}m remaining)`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (config.timeLimit) {
|
|
40
|
+
const timeLimitMs = config.timeLimit * 60 * 1000;
|
|
41
|
+
const used = Date.now() - startedAt.getTime();
|
|
42
|
+
const remaining = timeLimitMs - used;
|
|
43
|
+
if (remaining <= 0) {
|
|
44
|
+
console.log(chalk.red(` Time limit: EXCEEDED (${config.timeLimit} minutes)`));
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
const remMins = Math.floor(remaining / (1000 * 60));
|
|
48
|
+
info(` Time limit: ${config.timeLimit} min (${remMins} min remaining)`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
console.log();
|
|
52
|
+
// Git stats
|
|
53
|
+
try {
|
|
54
|
+
execSync("git --version", { stdio: "pipe" });
|
|
55
|
+
try {
|
|
56
|
+
const commitCount = execSync("git rev-list --count HEAD", { cwd: projectRoot, stdio: "pipe" }).toString().trim();
|
|
57
|
+
const lastCommit = execSync("git log -1 --format=%s", { cwd: projectRoot, stdio: "pipe" }).toString().trim();
|
|
58
|
+
info(` Commits: ${commitCount}`);
|
|
59
|
+
info(` Last commit: "${lastCommit}"`);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
info(` Commits: no commits yet`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
info(` Git: not installed`);
|
|
67
|
+
}
|
|
68
|
+
// Estimated submission size (count files, approximate size)
|
|
69
|
+
const sizeInfo = estimateSubmissionSize(projectRoot);
|
|
70
|
+
info(` Files: ~${sizeInfo.fileCount}`);
|
|
71
|
+
info(` Estimated submission size: ~${sizeInfo.sizeMb} MB`);
|
|
72
|
+
// Tracker status
|
|
73
|
+
const pidFile = path.join(projectRoot, ".litmus", "tracker.pid");
|
|
74
|
+
let trackerRunning = false;
|
|
75
|
+
try {
|
|
76
|
+
const pid = parseInt(readFileSync(pidFile, "utf8").trim(), 10);
|
|
77
|
+
if (!isNaN(pid)) {
|
|
78
|
+
process.kill(pid, 0); // check if process exists (signal 0)
|
|
79
|
+
trackerRunning = true;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch { /* not running */ }
|
|
83
|
+
info(` Tracker: ${trackerRunning ? "running" : "not running"}`);
|
|
84
|
+
// Activity log stats
|
|
85
|
+
const activityPath = path.join(projectRoot, ".litmus", "activity.jsonl");
|
|
86
|
+
if (existsSync(activityPath)) {
|
|
87
|
+
try {
|
|
88
|
+
const content = readFileSync(activityPath, "utf8");
|
|
89
|
+
const lines = content.split("\n").filter(l => l.trim());
|
|
90
|
+
info(` Activity events: ${lines.length}`);
|
|
91
|
+
}
|
|
92
|
+
catch { /* ignore */ }
|
|
93
|
+
}
|
|
94
|
+
console.log();
|
|
95
|
+
console.log(chalk.dim(" When you're ready to submit:"));
|
|
96
|
+
console.log(` ${chalk.cyan("litmus submit")}`);
|
|
97
|
+
console.log();
|
|
98
|
+
}
|
|
99
|
+
function estimateSubmissionSize(projectRoot) {
|
|
100
|
+
let fileCount = 0;
|
|
101
|
+
let totalBytes = 0;
|
|
102
|
+
const SKIP_DIRS = new Set([
|
|
103
|
+
"node_modules", "__pycache__", "venv", ".venv", "env", ".env",
|
|
104
|
+
"target", "build", "dist", ".gradle", ".idea",
|
|
105
|
+
]);
|
|
106
|
+
function walk(dir) {
|
|
107
|
+
let entries;
|
|
108
|
+
try {
|
|
109
|
+
entries = readdirSync(dir);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
for (const entry of entries) {
|
|
115
|
+
if (SKIP_DIRS.has(entry))
|
|
116
|
+
continue;
|
|
117
|
+
const fullPath = path.join(dir, entry);
|
|
118
|
+
try {
|
|
119
|
+
const stat = statSync(fullPath);
|
|
120
|
+
if (stat.isDirectory()) {
|
|
121
|
+
walk(fullPath);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
fileCount++;
|
|
125
|
+
totalBytes += stat.size;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch { /* permission errors, etc. */ }
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
walk(projectRoot);
|
|
132
|
+
return { fileCount, sizeMb: (totalBytes / (1024 * 1024)).toFixed(1) };
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"submit.d.ts","sourceRoot":"","sources":["../../src/commands/submit.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"submit.d.ts","sourceRoot":"","sources":["../../src/commands/submit.ts"],"names":[],"mappings":"AAYA,wBAAsB,SAAS,CAAC,IAAI,EAAE;IAAE,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAwHtE"}
|
package/dist/commands/submit.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { statSync, unlinkSync } from "fs";
|
|
1
|
+
import { statSync, unlinkSync, readFileSync } from "fs";
|
|
2
|
+
import { rm } from "fs/promises";
|
|
2
3
|
import path from "path";
|
|
3
4
|
import chalk from "chalk";
|
|
4
5
|
import ora from "ora";
|
|
5
6
|
import * as readline from "readline/promises";
|
|
6
7
|
import { stdin as input, stdout as output } from "process";
|
|
7
|
-
import { readConfig } from "../lib/config.js";
|
|
8
|
+
import { readConfig, findProjectRoot } from "../lib/config.js";
|
|
8
9
|
import { submitZip } from "../lib/api.js";
|
|
9
10
|
import { createSubmissionZip } from "../lib/zip.js";
|
|
10
11
|
import { fatal, success, warn, info, FALLBACK_URL } from "../utils/errors.js";
|
|
@@ -27,25 +28,7 @@ export async function runSubmit(opts) {
|
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
30
|
// Step 3: Find the project root (where .litmus/config.json lives)
|
|
30
|
-
|
|
31
|
-
// readConfig walks up the tree, so we use the config's assessed location
|
|
32
|
-
// The config file is at .litmus/config.json relative to the project root
|
|
33
|
-
// We find it by searching from cwd up, same as readConfig does
|
|
34
|
-
{
|
|
35
|
-
let dir = process.cwd();
|
|
36
|
-
while (true) {
|
|
37
|
-
const configPath = path.join(dir, ".litmus", "config.json");
|
|
38
|
-
const { existsSync } = await import("fs");
|
|
39
|
-
if (existsSync(configPath)) {
|
|
40
|
-
projectRoot = dir;
|
|
41
|
-
break;
|
|
42
|
-
}
|
|
43
|
-
const parent = path.dirname(dir);
|
|
44
|
-
if (parent === dir)
|
|
45
|
-
break;
|
|
46
|
-
dir = parent;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
31
|
+
const projectRoot = findProjectRoot() ?? process.cwd();
|
|
49
32
|
// Step 4: Create the submission ZIP
|
|
50
33
|
const zipSpinner = ora("Preparing submission...").start();
|
|
51
34
|
let zipPath;
|
|
@@ -92,11 +75,16 @@ export async function runSubmit(opts) {
|
|
|
92
75
|
}
|
|
93
76
|
}
|
|
94
77
|
console.log();
|
|
95
|
-
// Step 7: Upload
|
|
78
|
+
// Step 7: Upload with progress
|
|
96
79
|
const uploadSpinner = ora("Uploading submission...").start();
|
|
97
80
|
try {
|
|
98
81
|
const fileName = `${folderName}_submission.zip`;
|
|
99
|
-
const result = await submitZip(config.apiBase, config.token, zipPath, fileName)
|
|
82
|
+
const result = await submitZip(config.apiBase, config.token, zipPath, fileName, (uploaded, total) => {
|
|
83
|
+
const pct = Math.round((uploaded / total) * 100);
|
|
84
|
+
const uploadedMb = (uploaded / (1024 * 1024)).toFixed(1);
|
|
85
|
+
const totalMb = (total / (1024 * 1024)).toFixed(1);
|
|
86
|
+
uploadSpinner.text = `Uploading submission... ${uploadedMb}/${totalMb} MB (${pct}%)`;
|
|
87
|
+
});
|
|
100
88
|
uploadSpinner.succeed("Submission received");
|
|
101
89
|
console.log();
|
|
102
90
|
success("You're done!");
|
|
@@ -106,6 +94,8 @@ export async function runSubmit(opts) {
|
|
|
106
94
|
console.log(chalk.dim(` Submission ID: ${result.submissionId}`));
|
|
107
95
|
}
|
|
108
96
|
console.log();
|
|
97
|
+
// Step 8: Delete assessment folder after successful submission
|
|
98
|
+
await deleteAssessmentFolder(projectRoot, config.token, folderName);
|
|
109
99
|
}
|
|
110
100
|
catch (err) {
|
|
111
101
|
uploadSpinner.fail("Upload failed");
|
|
@@ -121,4 +111,37 @@ export async function runSubmit(opts) {
|
|
|
121
111
|
catch { /* cleanup */ }
|
|
122
112
|
}
|
|
123
113
|
}
|
|
114
|
+
async function deleteAssessmentFolder(projectRoot, token, folderName) {
|
|
115
|
+
// Safety check: verify this is actually a litmus assessment directory with matching token
|
|
116
|
+
try {
|
|
117
|
+
const configPath = path.join(projectRoot, ".litmus", "config.json");
|
|
118
|
+
const configData = JSON.parse(readFileSync(configPath, "utf8"));
|
|
119
|
+
if (configData.token !== token) {
|
|
120
|
+
warn(`Could not verify assessment folder. You can manually remove: ${projectRoot}`);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
warn(`Could not verify assessment folder. You can manually remove: ${projectRoot}`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
// Verified — proceed with deletion
|
|
129
|
+
try {
|
|
130
|
+
// Stop the background tracker process
|
|
131
|
+
const pidFile = path.join(projectRoot, ".litmus", "tracker.pid");
|
|
132
|
+
try {
|
|
133
|
+
const pid = parseInt(readFileSync(pidFile, "utf8").trim(), 10);
|
|
134
|
+
if (!isNaN(pid))
|
|
135
|
+
process.kill(pid);
|
|
136
|
+
}
|
|
137
|
+
catch { /* tracker may not be running */ }
|
|
138
|
+
await rm(projectRoot, { recursive: true, force: true });
|
|
139
|
+
console.log(chalk.dim(` Assessment folder deleted: ${folderName}`));
|
|
140
|
+
console.log(chalk.dim(` You may need to \`cd\` to another directory.`));
|
|
141
|
+
console.log();
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
warn(`Could not delete assessment folder. You can manually remove: ${projectRoot}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
124
147
|
//# sourceMappingURL=submit.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"submit.js","sourceRoot":"","sources":["../../src/commands/submit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"submit.js","sourceRoot":"","sources":["../../src/commands/submit.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { program } from "commander";
|
|
3
3
|
import { runInit } from "./commands/init.js";
|
|
4
4
|
import { runSubmit } from "./commands/submit.js";
|
|
5
|
+
import { runStatus } from "./commands/status.js";
|
|
5
6
|
const VERSION = "0.1.0";
|
|
6
7
|
program
|
|
7
8
|
.name("litmus")
|
|
@@ -32,5 +33,17 @@ program
|
|
|
32
33
|
process.exit(1);
|
|
33
34
|
}
|
|
34
35
|
});
|
|
36
|
+
program
|
|
37
|
+
.command("status")
|
|
38
|
+
.description("Show assessment status and stats")
|
|
39
|
+
.action(async () => {
|
|
40
|
+
try {
|
|
41
|
+
await runStatus();
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
35
48
|
program.parse();
|
|
36
49
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAEhD,MAAM,OAAO,GAAG,OAAO,CAAA;AAEvB,OAAO;KACJ,IAAI,CAAC,QAAQ,CAAC;KACd,WAAW,CAAC,mCAAmC,CAAC;KAChD,OAAO,CAAC,OAAO,CAAC,CAAA;AAEnB,OAAO;KACJ,OAAO,CAAC,cAAc,CAAC;KACvB,WAAW,CAAC,yCAAyC,CAAC;KACtD,MAAM,CAAC,KAAK,EAAE,KAAa,EAAE,EAAE;IAC9B,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,KAAK,CAAC,CAAA;IACtB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;QAC/D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;AACH,CAAC,CAAC,CAAA;AAEJ,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,kCAAkC,CAAC;KAC/C,MAAM,CAAC,WAAW,EAAE,0BAA0B,CAAC;KAC/C,MAAM,CAAC,KAAK,EAAE,IAAuB,EAAE,EAAE;IACxC,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,IAAI,CAAC,CAAA;IACvB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;QAC/D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;AACH,CAAC,CAAC,CAAA;AAEJ,OAAO,CAAC,KAAK,EAAE,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAEhD,MAAM,OAAO,GAAG,OAAO,CAAA;AAEvB,OAAO;KACJ,IAAI,CAAC,QAAQ,CAAC;KACd,WAAW,CAAC,mCAAmC,CAAC;KAChD,OAAO,CAAC,OAAO,CAAC,CAAA;AAEnB,OAAO;KACJ,OAAO,CAAC,cAAc,CAAC;KACvB,WAAW,CAAC,yCAAyC,CAAC;KACtD,MAAM,CAAC,KAAK,EAAE,KAAa,EAAE,EAAE;IAC9B,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,KAAK,CAAC,CAAA;IACtB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;QAC/D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;AACH,CAAC,CAAC,CAAA;AAEJ,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,kCAAkC,CAAC;KAC/C,MAAM,CAAC,WAAW,EAAE,0BAA0B,CAAC;KAC/C,MAAM,CAAC,KAAK,EAAE,IAAuB,EAAE,EAAE;IACxC,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,IAAI,CAAC,CAAA;IACvB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;QAC/D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;AACH,CAAC,CAAC,CAAA;AAEJ,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,kCAAkC,CAAC;KAC/C,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,IAAI,CAAC;QACH,MAAM,SAAS,EAAE,CAAA;IACnB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;QAC/D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;AACH,CAAC,CAAC,CAAA;AAEJ,OAAO,CAAC,KAAK,EAAE,CAAA"}
|
package/dist/lib/api.d.ts
CHANGED
|
@@ -25,6 +25,8 @@ export declare function fetchInitMetadata(apiBase: string, token: string): Promi
|
|
|
25
25
|
export declare function downloadZip(apiBase: string, token: string): Promise<Response>;
|
|
26
26
|
/**
|
|
27
27
|
* Upload a ZIP file for submission.
|
|
28
|
+
* Streams the file to avoid loading it entirely into memory.
|
|
29
|
+
* Calls onProgress with (bytesUploaded, totalBytes) during upload.
|
|
28
30
|
*/
|
|
29
|
-
export declare function submitZip(apiBase: string, token: string, zipPath: string, fileName: string): Promise<SubmitResult>;
|
|
31
|
+
export declare function submitZip(apiBase: string, token: string, zipPath: string, fileName: string, onProgress?: (uploaded: number, total: number) => void): Promise<SubmitResult>;
|
|
30
32
|
//# sourceMappingURL=api.d.ts.map
|
package/dist/lib/api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/lib/api.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/lib/api.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,MAAM,CAAA;IACtB,UAAU,EAAE,MAAM,CAAA;IAClB,cAAc,EAAE,MAAM,CAAA;IACtB,aAAa,EAAE,MAAM,CAAA;IACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;CAChB;AAID;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,YAAY,CAAC,CAWvB;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,QAAQ,CAAC,CAWnB;AAED;;;;GAIG;AACH,wBAAsB,SAAS,CAC7B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GACrD,OAAO,CAAC,YAAY,CAAC,CAiEvB"}
|
package/dist/lib/api.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createReadStream, statSync } from "fs";
|
|
2
|
+
import { request as httpsRequest } from "https";
|
|
3
|
+
import { request as httpRequest } from "http";
|
|
4
|
+
const TIMEOUT_MS = 30000; // 30s for metadata/download requests
|
|
2
5
|
/**
|
|
3
6
|
* Fetch assessment metadata by token.
|
|
4
7
|
* Marks the candidate as in_progress if they were pending.
|
|
5
8
|
*/
|
|
6
9
|
export async function fetchInitMetadata(apiBase, token) {
|
|
7
10
|
const url = `${apiBase}/api/cli/init?token=${encodeURIComponent(token)}`;
|
|
8
|
-
const res = await fetch(url);
|
|
11
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(TIMEOUT_MS) });
|
|
9
12
|
const body = await res.json().catch(() => ({}));
|
|
10
13
|
if (!res.ok) {
|
|
11
14
|
const message = body.message || body.error || `Server returned ${res.status}`;
|
|
@@ -19,7 +22,7 @@ export async function fetchInitMetadata(apiBase, token) {
|
|
|
19
22
|
*/
|
|
20
23
|
export async function downloadZip(apiBase, token) {
|
|
21
24
|
const url = `${apiBase}/api/cli/download?token=${encodeURIComponent(token)}`;
|
|
22
|
-
const res = await fetch(url);
|
|
25
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(TIMEOUT_MS) });
|
|
23
26
|
if (!res.ok) {
|
|
24
27
|
const body = await res.json().catch(() => ({}));
|
|
25
28
|
const message = body.message || body.error || `Server returned ${res.status}`;
|
|
@@ -29,21 +32,68 @@ export async function downloadZip(apiBase, token) {
|
|
|
29
32
|
}
|
|
30
33
|
/**
|
|
31
34
|
* Upload a ZIP file for submission.
|
|
35
|
+
* Streams the file to avoid loading it entirely into memory.
|
|
36
|
+
* Calls onProgress with (bytesUploaded, totalBytes) during upload.
|
|
32
37
|
*/
|
|
33
|
-
export async function submitZip(apiBase, token, zipPath, fileName) {
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
export async function submitZip(apiBase, token, zipPath, fileName, onProgress) {
|
|
39
|
+
const parsedUrl = new URL(`/api/cli/submit?token=${encodeURIComponent(token)}`, apiBase);
|
|
40
|
+
const fileSize = statSync(zipPath).size;
|
|
41
|
+
const boundary = `----litmus${Date.now()}`;
|
|
42
|
+
const headerBuf = Buffer.from(`--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="${fileName}"\r\nContent-Type: application/zip\r\n\r\n`);
|
|
43
|
+
const footerBuf = Buffer.from(`\r\n--${boundary}--\r\n`);
|
|
44
|
+
const totalSize = headerBuf.length + fileSize + footerBuf.length;
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
const reqFn = parsedUrl.protocol === "https:" ? httpsRequest : httpRequest;
|
|
47
|
+
const req = reqFn(parsedUrl, {
|
|
48
|
+
method: "POST",
|
|
49
|
+
headers: {
|
|
50
|
+
"Content-Type": `multipart/form-data; boundary=${boundary}`,
|
|
51
|
+
"Content-Length": totalSize.toString(),
|
|
52
|
+
},
|
|
53
|
+
timeout: 300000, // 5 min for upload
|
|
54
|
+
}, (res) => {
|
|
55
|
+
let data = "";
|
|
56
|
+
res.on("data", (chunk) => { data += chunk.toString(); });
|
|
57
|
+
res.on("end", () => {
|
|
58
|
+
try {
|
|
59
|
+
const body = JSON.parse(data);
|
|
60
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
61
|
+
const message = body.message || body.error || `Server returned ${res.statusCode}`;
|
|
62
|
+
reject(new Error(message));
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
resolve(body);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
reject(new Error(`Server returned invalid response (${res.statusCode})`));
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
req.on("error", reject);
|
|
74
|
+
req.on("timeout", () => {
|
|
75
|
+
req.destroy(new Error("Upload timed out"));
|
|
76
|
+
});
|
|
77
|
+
// Write multipart header
|
|
78
|
+
req.write(headerBuf);
|
|
79
|
+
let uploaded = headerBuf.length;
|
|
80
|
+
// Stream the file in chunks with backpressure handling
|
|
81
|
+
const fileStream = createReadStream(zipPath, { highWaterMark: 64 * 1024 });
|
|
82
|
+
fileStream.on("data", (chunk) => {
|
|
83
|
+
uploaded += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length;
|
|
84
|
+
onProgress?.(uploaded, totalSize);
|
|
85
|
+
if (!req.write(chunk)) {
|
|
86
|
+
fileStream.pause();
|
|
87
|
+
req.once("drain", () => fileStream.resume());
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
fileStream.on("end", () => {
|
|
91
|
+
req.end(footerBuf);
|
|
92
|
+
});
|
|
93
|
+
fileStream.on("error", (err) => {
|
|
94
|
+
req.destroy();
|
|
95
|
+
reject(err);
|
|
96
|
+
});
|
|
41
97
|
});
|
|
42
|
-
const body = await res.json().catch(() => ({}));
|
|
43
|
-
if (!res.ok) {
|
|
44
|
-
const message = body.message || body.error || `Server returned ${res.status}`;
|
|
45
|
-
throw new Error(message);
|
|
46
|
-
}
|
|
47
|
-
return body;
|
|
48
98
|
}
|
|
49
99
|
//# sourceMappingURL=api.js.map
|
package/dist/lib/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/lib/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/lib/api.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -11,6 +11,11 @@ export interface LitmusConfig {
|
|
|
11
11
|
}
|
|
12
12
|
export declare function getConfigPath(projectRoot: string): string;
|
|
13
13
|
export declare function writeConfig(projectRoot: string, config: LitmusConfig): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Walk up from cwd to find the directory containing .litmus/config.json.
|
|
16
|
+
* Returns null if not found.
|
|
17
|
+
*/
|
|
18
|
+
export declare function findProjectRoot(): string | null;
|
|
14
19
|
/**
|
|
15
20
|
* Read .litmus/config.json from the current directory or a parent directory.
|
|
16
21
|
* Returns null if no config is found.
|
package/dist/lib/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,MAAM,CAAA;IACtB,cAAc,EAAE,MAAM,CAAA;IACtB,aAAa,EAAE,MAAM,CAAA;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,OAAO,EAAE,MAAM,CAAA;CAChB;AAKD,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAsB,WAAW,CAC/B,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,IAAI,CAAC,CAKf;AAED;;;GAGG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,MAAM,CAAA;IACtB,cAAc,EAAE,MAAM,CAAA;IACtB,aAAa,EAAE,MAAM,CAAA;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,OAAO,EAAE,MAAM,CAAA;CAChB;AAKD,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAsB,WAAW,CAC/B,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,IAAI,CAAC,CAKf;AAED;;;GAGG;AACH,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CAY/C;AAED;;;GAGG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAuB/D"}
|
package/dist/lib/config.js
CHANGED
|
@@ -12,6 +12,24 @@ export async function writeConfig(projectRoot, config) {
|
|
|
12
12
|
const configPath = path.join(configDir, CONFIG_FILE);
|
|
13
13
|
await writeFile(configPath, JSON.stringify(config, null, 2), "utf8");
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Walk up from cwd to find the directory containing .litmus/config.json.
|
|
17
|
+
* Returns null if not found.
|
|
18
|
+
*/
|
|
19
|
+
export function findProjectRoot() {
|
|
20
|
+
let dir = process.cwd();
|
|
21
|
+
while (true) {
|
|
22
|
+
const configPath = path.join(dir, CONFIG_DIR, CONFIG_FILE);
|
|
23
|
+
if (existsSync(configPath)) {
|
|
24
|
+
return dir;
|
|
25
|
+
}
|
|
26
|
+
const parent = path.dirname(dir);
|
|
27
|
+
if (parent === dir)
|
|
28
|
+
break;
|
|
29
|
+
dir = parent;
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
15
33
|
/**
|
|
16
34
|
* Read .litmus/config.json from the current directory or a parent directory.
|
|
17
35
|
* Returns null if no config is found.
|
|
@@ -22,12 +40,12 @@ export async function readConfig() {
|
|
|
22
40
|
while (true) {
|
|
23
41
|
const configPath = path.join(dir, CONFIG_DIR, CONFIG_FILE);
|
|
24
42
|
if (existsSync(configPath)) {
|
|
43
|
+
const raw = await readFile(configPath, "utf8");
|
|
25
44
|
try {
|
|
26
|
-
const raw = await readFile(configPath, "utf8");
|
|
27
45
|
return JSON.parse(raw);
|
|
28
46
|
}
|
|
29
47
|
catch {
|
|
30
|
-
|
|
48
|
+
throw new Error(`Your .litmus/config.json is corrupted. Delete the .litmus folder and re-run litmus init with your token.`);
|
|
31
49
|
}
|
|
32
50
|
}
|
|
33
51
|
const parent = path.dirname(dir);
|
package/dist/lib/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAA;AAC/B,OAAO,IAAI,MAAM,MAAM,CAAA;AAcvB,MAAM,UAAU,GAAG,SAAS,CAAA;AAC5B,MAAM,WAAW,GAAG,aAAa,CAAA;AAEjC,MAAM,UAAU,aAAa,CAAC,WAAmB;IAC/C,OAAO,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,EAAE,WAAW,CAAC,CAAA;AACxD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,WAAmB,EACnB,MAAoB;IAEpB,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,CAAA;IACpD,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAC3C,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAA;IACpD,MAAM,SAAS,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAA;AACtE,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,IAAI,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAA;IAEvB,6DAA6D;IAC7D,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,EAAE,WAAW,CAAC,CAAA;QAC1D,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC3B,
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAA;AAC/B,OAAO,IAAI,MAAM,MAAM,CAAA;AAcvB,MAAM,UAAU,GAAG,SAAS,CAAA;AAC5B,MAAM,WAAW,GAAG,aAAa,CAAA;AAEjC,MAAM,UAAU,aAAa,CAAC,WAAmB;IAC/C,OAAO,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,EAAE,WAAW,CAAC,CAAA;AACxD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,WAAmB,EACnB,MAAoB;IAEpB,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,CAAA;IACpD,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAC3C,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAA;IACpD,MAAM,SAAS,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAA;AACtE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe;IAC7B,IAAI,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAA;IACvB,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,EAAE,WAAW,CAAC,CAAA;QAC1D,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC3B,OAAO,GAAG,CAAA;QACZ,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAChC,IAAI,MAAM,KAAK,GAAG;YAAE,MAAK;QACzB,GAAG,GAAG,MAAM,CAAA;IACd,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,IAAI,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAA;IAEvB,6DAA6D;IAC7D,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,EAAE,WAAW,CAAC,CAAA;QAC1D,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC3B,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,CAAA;YAC9C,IAAI,CAAC;gBACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAiB,CAAA;YACxC,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,IAAI,KAAK,CACb,0GAA0G,CAC3G,CAAA;YACH,CAAC;QACH,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAChC,IAAI,MAAM,KAAK,GAAG;YAAE,MAAK,CAAC,0BAA0B;QACpD,GAAG,GAAG,MAAM,CAAA;IACd,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detect-project.d.ts","sourceRoot":"","sources":["../../src/utils/detect-project.ts"],"names":[],"mappings":"AAGA,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,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,
|
|
1
|
+
{"version":3,"file":"detect-project.d.ts","sourceRoot":"","sources":["../../src/utils/detect-project.ts"],"names":[],"mappings":"AAGA,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,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CA4DtD"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync } from "fs";
|
|
1
|
+
import { existsSync, readdirSync } from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
/**
|
|
4
4
|
* Detect project type and suggest an install command.
|
|
@@ -38,7 +38,8 @@ export function detectProject(dir) {
|
|
|
38
38
|
if (existsSync(path.join(dir, "build.gradle")) || existsSync(path.join(dir, "build.gradle.kts"))) {
|
|
39
39
|
return { type: "java", installCommand: "gradle build", runHint: "gradle test" };
|
|
40
40
|
}
|
|
41
|
-
|
|
41
|
+
const files = readdirSync(dir);
|
|
42
|
+
if (files.some(f => f.endsWith(".csproj") || f.endsWith(".sln"))) {
|
|
42
43
|
return { type: "dotnet", installCommand: "dotnet restore", runHint: "dotnet run / dotnet test" };
|
|
43
44
|
}
|
|
44
45
|
if (existsSync(path.join(dir, "Cargo.toml"))) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detect-project.js","sourceRoot":"","sources":["../../src/utils/detect-project.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"detect-project.js","sourceRoot":"","sources":["../../src/utils/detect-project.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/commands/init.ts
CHANGED
|
@@ -37,7 +37,7 @@ export async function runInit(token: string): Promise<void> {
|
|
|
37
37
|
if (existsSync(targetDir)) {
|
|
38
38
|
fatal(
|
|
39
39
|
`Folder "${metadata.folderName}" already exists in this directory.`,
|
|
40
|
-
`Delete or rename it, then run
|
|
40
|
+
`Delete or rename it, then re-run litmus init with your token.`
|
|
41
41
|
)
|
|
42
42
|
}
|
|
43
43
|
|
|
@@ -98,14 +98,19 @@ export async function runInit(token: string): Promise<void> {
|
|
|
98
98
|
|
|
99
99
|
// Step 4: Initialize git repo with initial commit
|
|
100
100
|
try {
|
|
101
|
-
execSync("git
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
"git
|
|
105
|
-
|
|
106
|
-
|
|
101
|
+
execSync("git --version", { stdio: "pipe" })
|
|
102
|
+
try {
|
|
103
|
+
execSync("git init", { cwd: targetDir, stdio: "pipe" })
|
|
104
|
+
execSync("git add -A", { cwd: targetDir, stdio: "pipe" })
|
|
105
|
+
execSync(
|
|
106
|
+
"git commit -m \"Assessment: initial state\"",
|
|
107
|
+
{ cwd: targetDir, stdio: "pipe" }
|
|
108
|
+
)
|
|
109
|
+
} catch {
|
|
110
|
+
warn("Git is installed but failed to initialize. Check your git config (user.name / user.email).")
|
|
111
|
+
}
|
|
107
112
|
} catch {
|
|
108
|
-
//
|
|
113
|
+
// git not installed — non-critical, analysis degrades gracefully
|
|
109
114
|
}
|
|
110
115
|
|
|
111
116
|
// Step 5: Write .litmus/config.json
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync, readdirSync } from "fs"
|
|
2
|
+
import path from "path"
|
|
3
|
+
import chalk from "chalk"
|
|
4
|
+
import { readConfig, findProjectRoot } from "../lib/config.js"
|
|
5
|
+
import { fatal, info } from "../utils/errors.js"
|
|
6
|
+
import { execSync } from "child_process"
|
|
7
|
+
|
|
8
|
+
export async function runStatus(): Promise<void> {
|
|
9
|
+
const config = await readConfig()
|
|
10
|
+
|
|
11
|
+
if (!config) {
|
|
12
|
+
fatal(
|
|
13
|
+
"No Litmus assessment found in this directory.",
|
|
14
|
+
"Run this command from inside your assessment folder, or run 'litmus init <token>' first."
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const projectRoot = findProjectRoot() ?? process.cwd()
|
|
19
|
+
const folderName = path.basename(projectRoot)
|
|
20
|
+
|
|
21
|
+
console.log()
|
|
22
|
+
console.log(chalk.bold(` ${config.assessmentName}`))
|
|
23
|
+
console.log()
|
|
24
|
+
|
|
25
|
+
// Candidate info
|
|
26
|
+
info(` Candidate: ${config.candidateName} (${config.candidateEmail})`)
|
|
27
|
+
info(` Folder: ${folderName}/`)
|
|
28
|
+
|
|
29
|
+
// Time elapsed since init
|
|
30
|
+
const startedAt = new Date(config.startedAt)
|
|
31
|
+
const elapsed = Date.now() - startedAt.getTime()
|
|
32
|
+
const elapsedHours = Math.floor(elapsed / (1000 * 60 * 60))
|
|
33
|
+
const elapsedMins = Math.floor((elapsed % (1000 * 60 * 60)) / (1000 * 60))
|
|
34
|
+
info(` Started: ${startedAt.toLocaleString()} (${elapsedHours}h ${elapsedMins}m ago)`)
|
|
35
|
+
|
|
36
|
+
// Deadline / time remaining
|
|
37
|
+
if (config.deadline) {
|
|
38
|
+
const deadline = new Date(config.deadline)
|
|
39
|
+
const remaining = deadline.getTime() - Date.now()
|
|
40
|
+
|
|
41
|
+
if (remaining <= 0) {
|
|
42
|
+
console.log(chalk.red(` Deadline: PASSED (${deadline.toLocaleString()})`))
|
|
43
|
+
} else {
|
|
44
|
+
const remHours = Math.floor(remaining / (1000 * 60 * 60))
|
|
45
|
+
const remMins = Math.floor((remaining % (1000 * 60 * 60)) / (1000 * 60))
|
|
46
|
+
info(` Deadline: ${deadline.toLocaleString()} (${remHours}h ${remMins}m remaining)`)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (config.timeLimit) {
|
|
51
|
+
const timeLimitMs = config.timeLimit * 60 * 1000
|
|
52
|
+
const used = Date.now() - startedAt.getTime()
|
|
53
|
+
const remaining = timeLimitMs - used
|
|
54
|
+
if (remaining <= 0) {
|
|
55
|
+
console.log(chalk.red(` Time limit: EXCEEDED (${config.timeLimit} minutes)`))
|
|
56
|
+
} else {
|
|
57
|
+
const remMins = Math.floor(remaining / (1000 * 60))
|
|
58
|
+
info(` Time limit: ${config.timeLimit} min (${remMins} min remaining)`)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
console.log()
|
|
63
|
+
|
|
64
|
+
// Git stats
|
|
65
|
+
try {
|
|
66
|
+
execSync("git --version", { stdio: "pipe" })
|
|
67
|
+
try {
|
|
68
|
+
const commitCount = execSync("git rev-list --count HEAD", { cwd: projectRoot, stdio: "pipe" }).toString().trim()
|
|
69
|
+
const lastCommit = execSync("git log -1 --format=%s", { cwd: projectRoot, stdio: "pipe" }).toString().trim()
|
|
70
|
+
info(` Commits: ${commitCount}`)
|
|
71
|
+
info(` Last commit: "${lastCommit}"`)
|
|
72
|
+
} catch {
|
|
73
|
+
info(` Commits: no commits yet`)
|
|
74
|
+
}
|
|
75
|
+
} catch {
|
|
76
|
+
info(` Git: not installed`)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Estimated submission size (count files, approximate size)
|
|
80
|
+
const sizeInfo = estimateSubmissionSize(projectRoot)
|
|
81
|
+
info(` Files: ~${sizeInfo.fileCount}`)
|
|
82
|
+
info(` Estimated submission size: ~${sizeInfo.sizeMb} MB`)
|
|
83
|
+
|
|
84
|
+
// Tracker status
|
|
85
|
+
const pidFile = path.join(projectRoot, ".litmus", "tracker.pid")
|
|
86
|
+
let trackerRunning = false
|
|
87
|
+
try {
|
|
88
|
+
const pid = parseInt(readFileSync(pidFile, "utf8").trim(), 10)
|
|
89
|
+
if (!isNaN(pid)) {
|
|
90
|
+
process.kill(pid, 0) // check if process exists (signal 0)
|
|
91
|
+
trackerRunning = true
|
|
92
|
+
}
|
|
93
|
+
} catch { /* not running */ }
|
|
94
|
+
info(` Tracker: ${trackerRunning ? "running" : "not running"}`)
|
|
95
|
+
|
|
96
|
+
// Activity log stats
|
|
97
|
+
const activityPath = path.join(projectRoot, ".litmus", "activity.jsonl")
|
|
98
|
+
if (existsSync(activityPath)) {
|
|
99
|
+
try {
|
|
100
|
+
const content = readFileSync(activityPath, "utf8")
|
|
101
|
+
const lines = content.split("\n").filter(l => l.trim())
|
|
102
|
+
info(` Activity events: ${lines.length}`)
|
|
103
|
+
} catch { /* ignore */ }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
console.log()
|
|
107
|
+
console.log(chalk.dim(" When you're ready to submit:"))
|
|
108
|
+
console.log(` ${chalk.cyan("litmus submit")}`)
|
|
109
|
+
console.log()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function estimateSubmissionSize(projectRoot: string): { fileCount: number; sizeMb: string } {
|
|
113
|
+
let fileCount = 0
|
|
114
|
+
let totalBytes = 0
|
|
115
|
+
|
|
116
|
+
const SKIP_DIRS = new Set([
|
|
117
|
+
"node_modules", "__pycache__", "venv", ".venv", "env", ".env",
|
|
118
|
+
"target", "build", "dist", ".gradle", ".idea",
|
|
119
|
+
])
|
|
120
|
+
|
|
121
|
+
function walk(dir: string): void {
|
|
122
|
+
let entries: string[]
|
|
123
|
+
try {
|
|
124
|
+
entries = readdirSync(dir)
|
|
125
|
+
} catch {
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
for (const entry of entries) {
|
|
129
|
+
if (SKIP_DIRS.has(entry)) continue
|
|
130
|
+
const fullPath = path.join(dir, entry)
|
|
131
|
+
try {
|
|
132
|
+
const stat = statSync(fullPath)
|
|
133
|
+
if (stat.isDirectory()) {
|
|
134
|
+
walk(fullPath)
|
|
135
|
+
} else {
|
|
136
|
+
fileCount++
|
|
137
|
+
totalBytes += stat.size
|
|
138
|
+
}
|
|
139
|
+
} catch { /* permission errors, etc. */ }
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
walk(projectRoot)
|
|
144
|
+
return { fileCount, sizeMb: (totalBytes / (1024 * 1024)).toFixed(1) }
|
|
145
|
+
}
|
package/src/commands/submit.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { statSync, unlinkSync } from "fs"
|
|
1
|
+
import { statSync, unlinkSync, readFileSync } from "fs"
|
|
2
|
+
import { rm } from "fs/promises"
|
|
2
3
|
import path from "path"
|
|
3
4
|
import chalk from "chalk"
|
|
4
5
|
import ora from "ora"
|
|
5
6
|
import * as readline from "readline/promises"
|
|
6
7
|
import { stdin as input, stdout as output } from "process"
|
|
7
|
-
import { readConfig } from "../lib/config.js"
|
|
8
|
+
import { readConfig, findProjectRoot } from "../lib/config.js"
|
|
8
9
|
import { submitZip } from "../lib/api.js"
|
|
9
10
|
import { createSubmissionZip } from "../lib/zip.js"
|
|
10
11
|
import { fatal, success, warn, info, FALLBACK_URL } from "../utils/errors.js"
|
|
@@ -39,24 +40,7 @@ export async function runSubmit(opts: { yes?: boolean }): Promise<void> {
|
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
// Step 3: Find the project root (where .litmus/config.json lives)
|
|
42
|
-
|
|
43
|
-
// readConfig walks up the tree, so we use the config's assessed location
|
|
44
|
-
// The config file is at .litmus/config.json relative to the project root
|
|
45
|
-
// We find it by searching from cwd up, same as readConfig does
|
|
46
|
-
{
|
|
47
|
-
let dir = process.cwd()
|
|
48
|
-
while (true) {
|
|
49
|
-
const configPath = path.join(dir, ".litmus", "config.json")
|
|
50
|
-
const { existsSync } = await import("fs")
|
|
51
|
-
if (existsSync(configPath)) {
|
|
52
|
-
projectRoot = dir
|
|
53
|
-
break
|
|
54
|
-
}
|
|
55
|
-
const parent = path.dirname(dir)
|
|
56
|
-
if (parent === dir) break
|
|
57
|
-
dir = parent
|
|
58
|
-
}
|
|
59
|
-
}
|
|
43
|
+
const projectRoot = findProjectRoot() ?? process.cwd()
|
|
60
44
|
|
|
61
45
|
// Step 4: Create the submission ZIP
|
|
62
46
|
const zipSpinner = ora("Preparing submission...").start()
|
|
@@ -108,12 +92,17 @@ export async function runSubmit(opts: { yes?: boolean }): Promise<void> {
|
|
|
108
92
|
|
|
109
93
|
console.log()
|
|
110
94
|
|
|
111
|
-
// Step 7: Upload
|
|
95
|
+
// Step 7: Upload with progress
|
|
112
96
|
const uploadSpinner = ora("Uploading submission...").start()
|
|
113
97
|
|
|
114
98
|
try {
|
|
115
99
|
const fileName = `${folderName}_submission.zip`
|
|
116
|
-
const result = await submitZip(config.apiBase, config.token, zipPath, fileName)
|
|
100
|
+
const result = await submitZip(config.apiBase, config.token, zipPath, fileName, (uploaded, total) => {
|
|
101
|
+
const pct = Math.round((uploaded / total) * 100)
|
|
102
|
+
const uploadedMb = (uploaded / (1024 * 1024)).toFixed(1)
|
|
103
|
+
const totalMb = (total / (1024 * 1024)).toFixed(1)
|
|
104
|
+
uploadSpinner.text = `Uploading submission... ${uploadedMb}/${totalMb} MB (${pct}%)`
|
|
105
|
+
})
|
|
117
106
|
uploadSpinner.succeed("Submission received")
|
|
118
107
|
|
|
119
108
|
console.log()
|
|
@@ -126,6 +115,9 @@ export async function runSubmit(opts: { yes?: boolean }): Promise<void> {
|
|
|
126
115
|
console.log(chalk.dim(` Submission ID: ${result.submissionId}`))
|
|
127
116
|
}
|
|
128
117
|
console.log()
|
|
118
|
+
|
|
119
|
+
// Step 8: Delete assessment folder after successful submission
|
|
120
|
+
await deleteAssessmentFolder(projectRoot, config.token, folderName)
|
|
129
121
|
} catch (err) {
|
|
130
122
|
uploadSpinner.fail("Upload failed")
|
|
131
123
|
const msg = err instanceof Error ? err.message : String(err)
|
|
@@ -139,3 +131,35 @@ export async function runSubmit(opts: { yes?: boolean }): Promise<void> {
|
|
|
139
131
|
try { unlinkSync(zipPath) } catch { /* cleanup */ }
|
|
140
132
|
}
|
|
141
133
|
}
|
|
134
|
+
|
|
135
|
+
async function deleteAssessmentFolder(projectRoot: string, token: string, folderName: string): Promise<void> {
|
|
136
|
+
// Safety check: verify this is actually a litmus assessment directory with matching token
|
|
137
|
+
try {
|
|
138
|
+
const configPath = path.join(projectRoot, ".litmus", "config.json")
|
|
139
|
+
const configData = JSON.parse(readFileSync(configPath, "utf8"))
|
|
140
|
+
if (configData.token !== token) {
|
|
141
|
+
warn(`Could not verify assessment folder. You can manually remove: ${projectRoot}`)
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
} catch {
|
|
145
|
+
warn(`Could not verify assessment folder. You can manually remove: ${projectRoot}`)
|
|
146
|
+
return
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Verified — proceed with deletion
|
|
150
|
+
try {
|
|
151
|
+
// Stop the background tracker process
|
|
152
|
+
const pidFile = path.join(projectRoot, ".litmus", "tracker.pid")
|
|
153
|
+
try {
|
|
154
|
+
const pid = parseInt(readFileSync(pidFile, "utf8").trim(), 10)
|
|
155
|
+
if (!isNaN(pid)) process.kill(pid)
|
|
156
|
+
} catch { /* tracker may not be running */ }
|
|
157
|
+
|
|
158
|
+
await rm(projectRoot, { recursive: true, force: true })
|
|
159
|
+
console.log(chalk.dim(` Assessment folder deleted: ${folderName}`))
|
|
160
|
+
console.log(chalk.dim(` You may need to \`cd\` to another directory.`))
|
|
161
|
+
console.log()
|
|
162
|
+
} catch {
|
|
163
|
+
warn(`Could not delete assessment folder. You can manually remove: ${projectRoot}`)
|
|
164
|
+
}
|
|
165
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { program } from "commander"
|
|
3
3
|
import { runInit } from "./commands/init.js"
|
|
4
4
|
import { runSubmit } from "./commands/submit.js"
|
|
5
|
+
import { runStatus } from "./commands/status.js"
|
|
5
6
|
|
|
6
7
|
const VERSION = "0.1.0"
|
|
7
8
|
|
|
@@ -35,4 +36,16 @@ program
|
|
|
35
36
|
}
|
|
36
37
|
})
|
|
37
38
|
|
|
39
|
+
program
|
|
40
|
+
.command("status")
|
|
41
|
+
.description("Show assessment status and stats")
|
|
42
|
+
.action(async () => {
|
|
43
|
+
try {
|
|
44
|
+
await runStatus()
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.error(err instanceof Error ? err.message : String(err))
|
|
47
|
+
process.exit(1)
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
38
51
|
program.parse()
|
package/src/lib/api.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createReadStream, statSync } from "fs"
|
|
2
|
+
import { request as httpsRequest } from "https"
|
|
3
|
+
import { request as httpRequest } from "http"
|
|
2
4
|
|
|
3
5
|
export interface InitMetadata {
|
|
4
6
|
assessmentId: string
|
|
@@ -17,6 +19,8 @@ export interface SubmitResult {
|
|
|
17
19
|
message: string
|
|
18
20
|
}
|
|
19
21
|
|
|
22
|
+
const TIMEOUT_MS = 30_000 // 30s for metadata/download requests
|
|
23
|
+
|
|
20
24
|
/**
|
|
21
25
|
* Fetch assessment metadata by token.
|
|
22
26
|
* Marks the candidate as in_progress if they were pending.
|
|
@@ -26,7 +30,7 @@ export async function fetchInitMetadata(
|
|
|
26
30
|
token: string
|
|
27
31
|
): Promise<InitMetadata> {
|
|
28
32
|
const url = `${apiBase}/api/cli/init?token=${encodeURIComponent(token)}`
|
|
29
|
-
const res = await fetch(url)
|
|
33
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(TIMEOUT_MS) })
|
|
30
34
|
const body = await res.json().catch(() => ({})) as Record<string, unknown>
|
|
31
35
|
|
|
32
36
|
if (!res.ok) {
|
|
@@ -46,7 +50,7 @@ export async function downloadZip(
|
|
|
46
50
|
token: string
|
|
47
51
|
): Promise<Response> {
|
|
48
52
|
const url = `${apiBase}/api/cli/download?token=${encodeURIComponent(token)}`
|
|
49
|
-
const res = await fetch(url)
|
|
53
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(TIMEOUT_MS) })
|
|
50
54
|
|
|
51
55
|
if (!res.ok) {
|
|
52
56
|
const body = await res.json().catch(() => ({})) as Record<string, unknown>
|
|
@@ -59,30 +63,78 @@ export async function downloadZip(
|
|
|
59
63
|
|
|
60
64
|
/**
|
|
61
65
|
* Upload a ZIP file for submission.
|
|
66
|
+
* Streams the file to avoid loading it entirely into memory.
|
|
67
|
+
* Calls onProgress with (bytesUploaded, totalBytes) during upload.
|
|
62
68
|
*/
|
|
63
69
|
export async function submitZip(
|
|
64
70
|
apiBase: string,
|
|
65
71
|
token: string,
|
|
66
72
|
zipPath: string,
|
|
67
|
-
fileName: string
|
|
73
|
+
fileName: string,
|
|
74
|
+
onProgress?: (uploaded: number, total: number) => void
|
|
68
75
|
): Promise<SubmitResult> {
|
|
69
|
-
const
|
|
76
|
+
const parsedUrl = new URL(`/api/cli/submit?token=${encodeURIComponent(token)}`, apiBase)
|
|
77
|
+
const fileSize = statSync(zipPath).size
|
|
70
78
|
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
|
|
79
|
+
const boundary = `----litmus${Date.now()}`
|
|
80
|
+
const headerBuf = Buffer.from(
|
|
81
|
+
`--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="${fileName}"\r\nContent-Type: application/zip\r\n\r\n`
|
|
82
|
+
)
|
|
83
|
+
const footerBuf = Buffer.from(`\r\n--${boundary}--\r\n`)
|
|
84
|
+
const totalSize = headerBuf.length + fileSize + footerBuf.length
|
|
74
85
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
86
|
+
return new Promise((resolve, reject) => {
|
|
87
|
+
const reqFn = parsedUrl.protocol === "https:" ? httpsRequest : httpRequest
|
|
88
|
+
const req = reqFn(parsedUrl, {
|
|
89
|
+
method: "POST",
|
|
90
|
+
headers: {
|
|
91
|
+
"Content-Type": `multipart/form-data; boundary=${boundary}`,
|
|
92
|
+
"Content-Length": totalSize.toString(),
|
|
93
|
+
},
|
|
94
|
+
timeout: 300_000, // 5 min for upload
|
|
95
|
+
}, (res) => {
|
|
96
|
+
let data = ""
|
|
97
|
+
res.on("data", (chunk: Buffer) => { data += chunk.toString() })
|
|
98
|
+
res.on("end", () => {
|
|
99
|
+
try {
|
|
100
|
+
const body = JSON.parse(data) as Record<string, unknown>
|
|
101
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
102
|
+
const message = (body.message as string) || (body.error as string) || `Server returned ${res.statusCode}`
|
|
103
|
+
reject(new Error(message))
|
|
104
|
+
} else {
|
|
105
|
+
resolve(body as unknown as SubmitResult)
|
|
106
|
+
}
|
|
107
|
+
} catch {
|
|
108
|
+
reject(new Error(`Server returned invalid response (${res.statusCode})`))
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
})
|
|
79
112
|
|
|
80
|
-
|
|
113
|
+
req.on("error", reject)
|
|
114
|
+
req.on("timeout", () => {
|
|
115
|
+
req.destroy(new Error("Upload timed out"))
|
|
116
|
+
})
|
|
81
117
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
118
|
+
// Write multipart header
|
|
119
|
+
req.write(headerBuf)
|
|
120
|
+
let uploaded = headerBuf.length
|
|
86
121
|
|
|
87
|
-
|
|
122
|
+
// Stream the file in chunks with backpressure handling
|
|
123
|
+
const fileStream = createReadStream(zipPath, { highWaterMark: 64 * 1024 })
|
|
124
|
+
fileStream.on("data", (chunk: string | Buffer) => {
|
|
125
|
+
uploaded += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length
|
|
126
|
+
onProgress?.(uploaded, totalSize)
|
|
127
|
+
if (!req.write(chunk)) {
|
|
128
|
+
fileStream.pause()
|
|
129
|
+
req.once("drain", () => fileStream.resume())
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
fileStream.on("end", () => {
|
|
133
|
+
req.end(footerBuf)
|
|
134
|
+
})
|
|
135
|
+
fileStream.on("error", (err) => {
|
|
136
|
+
req.destroy()
|
|
137
|
+
reject(err)
|
|
138
|
+
})
|
|
139
|
+
})
|
|
88
140
|
}
|
package/src/lib/config.ts
CHANGED
|
@@ -31,6 +31,24 @@ export async function writeConfig(
|
|
|
31
31
|
await writeFile(configPath, JSON.stringify(config, null, 2), "utf8")
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Walk up from cwd to find the directory containing .litmus/config.json.
|
|
36
|
+
* Returns null if not found.
|
|
37
|
+
*/
|
|
38
|
+
export function findProjectRoot(): string | null {
|
|
39
|
+
let dir = process.cwd()
|
|
40
|
+
while (true) {
|
|
41
|
+
const configPath = path.join(dir, CONFIG_DIR, CONFIG_FILE)
|
|
42
|
+
if (existsSync(configPath)) {
|
|
43
|
+
return dir
|
|
44
|
+
}
|
|
45
|
+
const parent = path.dirname(dir)
|
|
46
|
+
if (parent === dir) break
|
|
47
|
+
dir = parent
|
|
48
|
+
}
|
|
49
|
+
return null
|
|
50
|
+
}
|
|
51
|
+
|
|
34
52
|
/**
|
|
35
53
|
* Read .litmus/config.json from the current directory or a parent directory.
|
|
36
54
|
* Returns null if no config is found.
|
|
@@ -42,11 +60,13 @@ export async function readConfig(): Promise<LitmusConfig | null> {
|
|
|
42
60
|
while (true) {
|
|
43
61
|
const configPath = path.join(dir, CONFIG_DIR, CONFIG_FILE)
|
|
44
62
|
if (existsSync(configPath)) {
|
|
63
|
+
const raw = await readFile(configPath, "utf8")
|
|
45
64
|
try {
|
|
46
|
-
const raw = await readFile(configPath, "utf8")
|
|
47
65
|
return JSON.parse(raw) as LitmusConfig
|
|
48
66
|
} catch {
|
|
49
|
-
|
|
67
|
+
throw new Error(
|
|
68
|
+
`Your .litmus/config.json is corrupted. Delete the .litmus folder and re-run litmus init with your token.`
|
|
69
|
+
)
|
|
50
70
|
}
|
|
51
71
|
}
|
|
52
72
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync } from "fs"
|
|
1
|
+
import { existsSync, readdirSync } from "fs"
|
|
2
2
|
import path from "path"
|
|
3
3
|
|
|
4
4
|
export type ProjectType =
|
|
@@ -63,7 +63,8 @@ export function detectProject(dir: string): ProjectInfo {
|
|
|
63
63
|
return { type: "java", installCommand: "gradle build", runHint: "gradle test" }
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
const files = readdirSync(dir)
|
|
67
|
+
if (files.some(f => f.endsWith(".csproj") || f.endsWith(".sln"))) {
|
|
67
68
|
return { type: "dotnet", installCommand: "dotnet restore", runHint: "dotnet run / dotnet test" }
|
|
68
69
|
}
|
|
69
70
|
|