litmus-cli 0.2.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.
Files changed (54) hide show
  1. package/dist/commands/init.d.ts +2 -0
  2. package/dist/commands/init.d.ts.map +1 -0
  3. package/dist/commands/init.js +153 -0
  4. package/dist/commands/init.js.map +1 -0
  5. package/dist/commands/submit.d.ts +4 -0
  6. package/dist/commands/submit.d.ts.map +1 -0
  7. package/dist/commands/submit.js +124 -0
  8. package/dist/commands/submit.js.map +1 -0
  9. package/dist/index.d.ts +3 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +36 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/lib/api.d.ts +30 -0
  14. package/dist/lib/api.d.ts.map +1 -0
  15. package/dist/lib/api.js +54 -0
  16. package/dist/lib/api.js.map +1 -0
  17. package/dist/lib/config.d.ts +19 -0
  18. package/dist/lib/config.d.ts.map +1 -0
  19. package/dist/lib/config.js +40 -0
  20. package/dist/lib/config.js.map +1 -0
  21. package/dist/lib/extract.d.ts +6 -0
  22. package/dist/lib/extract.d.ts.map +1 -0
  23. package/dist/lib/extract.js +12 -0
  24. package/dist/lib/extract.js.map +1 -0
  25. package/dist/lib/tracker.d.ts +7 -0
  26. package/dist/lib/tracker.d.ts.map +1 -0
  27. package/dist/lib/tracker.js +36 -0
  28. package/dist/lib/tracker.js.map +1 -0
  29. package/dist/lib/zip.d.ts +10 -0
  30. package/dist/lib/zip.d.ts.map +1 -0
  31. package/dist/lib/zip.js +96 -0
  32. package/dist/lib/zip.js.map +1 -0
  33. package/dist/utils/detect-project.d.ts +11 -0
  34. package/dist/utils/detect-project.d.ts.map +1 -0
  35. package/dist/utils/detect-project.js +55 -0
  36. package/dist/utils/detect-project.js.map +1 -0
  37. package/dist/utils/errors.d.ts +19 -0
  38. package/dist/utils/errors.d.ts.map +1 -0
  39. package/dist/utils/errors.js +35 -0
  40. package/dist/utils/errors.js.map +1 -0
  41. package/dist/watcher.cjs +58 -0
  42. package/package.json +37 -0
  43. package/src/commands/init.ts +171 -0
  44. package/src/commands/submit.ts +141 -0
  45. package/src/index.ts +38 -0
  46. package/src/lib/api.ts +93 -0
  47. package/src/lib/config.ts +59 -0
  48. package/src/lib/extract.ts +15 -0
  49. package/src/lib/tracker.ts +38 -0
  50. package/src/lib/watcher.cjs +58 -0
  51. package/src/lib/zip.ts +104 -0
  52. package/src/utils/detect-project.ts +83 -0
  53. package/src/utils/errors.ts +41 -0
  54. package/tsconfig.json +19 -0
@@ -0,0 +1,2 @@
1
+ export declare function runInit(token: string): Promise<void>;
2
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +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,CAyJ1D"}
@@ -0,0 +1,153 @@
1
+ import { createWriteStream, unlinkSync } from "fs";
2
+ import { pipeline } from "stream/promises";
3
+ import { Readable } from "stream";
4
+ import path from "path";
5
+ import os from "os";
6
+ import chalk from "chalk";
7
+ import ora from "ora";
8
+ import { fetchInitMetadata, downloadZip } from "../lib/api.js";
9
+ import { writeConfig } from "../lib/config.js";
10
+ import { extractZip } from "../lib/extract.js";
11
+ import { startTracker } from "../lib/tracker.js";
12
+ import { detectProject } from "../utils/detect-project.js";
13
+ import { fatal, success, info, warn, FALLBACK_URL } from "../utils/errors.js";
14
+ import { execSync } from "child_process";
15
+ const DEFAULT_API_BASE = process.env.LITMUS_API_URL || "https://app.litmus.sh";
16
+ export async function runInit(token) {
17
+ const apiBase = DEFAULT_API_BASE;
18
+ // Step 1: Fetch metadata
19
+ const spinner = ora("Connecting to Litmus...").start();
20
+ let metadata;
21
+ try {
22
+ metadata = await fetchInitMetadata(apiBase, token);
23
+ spinner.succeed("Connected");
24
+ }
25
+ catch (err) {
26
+ spinner.fail("Connection failed");
27
+ const msg = err instanceof Error ? err.message : String(err);
28
+ fatal(msg, `Check your token or visit ${FALLBACK_URL} to download manually.`);
29
+ }
30
+ // Check for naming collision
31
+ const targetDir = path.resolve(process.cwd(), metadata.folderName);
32
+ const { existsSync } = await import("fs");
33
+ if (existsSync(targetDir)) {
34
+ fatal(`Folder "${metadata.folderName}" already exists in this directory.`, `Delete or rename it, then run: litmus init ${token}`);
35
+ }
36
+ // Print what we're setting up
37
+ console.log();
38
+ console.log(chalk.bold(` ${metadata.assessmentName}`));
39
+ if (metadata.deadline) {
40
+ const deadline = new Date(metadata.deadline);
41
+ const hoursLeft = Math.max(0, Math.floor((deadline.getTime() - Date.now()) / (1000 * 60 * 60)));
42
+ const deadlineStr = deadline.toLocaleString();
43
+ if (hoursLeft < 24) {
44
+ warn(`Deadline in ${hoursLeft} hours (${deadlineStr})`);
45
+ }
46
+ else {
47
+ info(`Deadline: ${deadlineStr}`);
48
+ }
49
+ }
50
+ if (metadata.timeLimit) {
51
+ info(`Time limit: ${metadata.timeLimit} minutes`);
52
+ }
53
+ console.log();
54
+ // Step 2: Download ZIP to a temp file
55
+ const downloadSpinner = ora("Downloading assessment...").start();
56
+ const tmpZip = path.join(os.tmpdir(), `litmus-${Date.now()}.zip`);
57
+ try {
58
+ const zipResponse = await downloadZip(apiBase, token);
59
+ if (!zipResponse.body) {
60
+ throw new Error("Empty response from server");
61
+ }
62
+ const nodeStream = Readable.fromWeb(zipResponse.body);
63
+ await pipeline(nodeStream, createWriteStream(tmpZip));
64
+ downloadSpinner.succeed("Downloaded");
65
+ }
66
+ catch (err) {
67
+ downloadSpinner.fail("Download failed");
68
+ const msg = err instanceof Error ? err.message : String(err);
69
+ fatal(msg, `Visit ${FALLBACK_URL} to download manually.`);
70
+ }
71
+ // Step 3: Extract from temp file to target directory
72
+ const extractSpinner = ora(`Setting up ${metadata.folderName}/...`).start();
73
+ try {
74
+ await extractZip(tmpZip, targetDir);
75
+ extractSpinner.succeed(`Created ${chalk.bold(metadata.folderName)}/`);
76
+ }
77
+ catch (err) {
78
+ extractSpinner.fail("Extraction failed");
79
+ const msg = err instanceof Error ? err.message : String(err);
80
+ fatal(msg, `Visit ${FALLBACK_URL} to download manually.`);
81
+ }
82
+ finally {
83
+ try {
84
+ unlinkSync(tmpZip);
85
+ }
86
+ catch { /* cleanup */ }
87
+ }
88
+ // Step 4: Initialize git repo with initial commit
89
+ try {
90
+ execSync("git init", { cwd: targetDir, stdio: "pipe" });
91
+ execSync("git add -A", { cwd: targetDir, stdio: "pipe" });
92
+ execSync("git commit -m \"Assessment: initial state\"", { cwd: targetDir, stdio: "pipe" });
93
+ }
94
+ catch {
95
+ // Non-critical — git may not be installed; analysis degrades gracefully
96
+ }
97
+ // Step 5: Write .litmus/config.json
98
+ await writeConfig(targetDir, {
99
+ assessmentId: metadata.assessmentId,
100
+ assessmentName: metadata.assessmentName,
101
+ candidateEmail: metadata.candidateEmail,
102
+ candidateName: metadata.candidateName,
103
+ token,
104
+ startedAt: metadata.startedAt,
105
+ deadline: metadata.deadline,
106
+ timeLimit: metadata.timeLimit,
107
+ apiBase,
108
+ });
109
+ // Step 5: Detect project type and optionally install
110
+ const project = detectProject(targetDir);
111
+ if (project.installCommand) {
112
+ console.log();
113
+ const installSpinner = ora(`Running ${chalk.cyan(project.installCommand)}... (this may take a minute)`).start();
114
+ try {
115
+ execSync(project.installCommand, {
116
+ cwd: targetDir,
117
+ stdio: "pipe",
118
+ });
119
+ installSpinner.succeed("Dependencies installed");
120
+ }
121
+ catch {
122
+ installSpinner.warn(`Couldn't run "${project.installCommand}" automatically. Run it manually inside ${metadata.folderName}/.`);
123
+ }
124
+ }
125
+ // Step 6: Start tracker in background
126
+ try {
127
+ startTracker(targetDir);
128
+ }
129
+ catch {
130
+ // Non-critical
131
+ }
132
+ // Done — print next steps
133
+ console.log();
134
+ success("Assessment ready");
135
+ console.log();
136
+ console.log(chalk.bold(" Next steps:"));
137
+ console.log();
138
+ console.log(` ${chalk.cyan(`cd ${metadata.folderName}`)}`);
139
+ if (project.runHint) {
140
+ console.log(` ${chalk.dim(`# ${project.runHint}`)}`);
141
+ }
142
+ console.log();
143
+ console.log(chalk.dim(" Your work is being tracked. When you're done:"));
144
+ console.log();
145
+ console.log(` ${chalk.cyan("litmus submit")}`);
146
+ console.log();
147
+ if (metadata.deadline) {
148
+ const deadline = new Date(metadata.deadline);
149
+ console.log(chalk.dim(` Deadline: ${deadline.toLocaleString()}`));
150
+ console.log();
151
+ }
152
+ }
153
+ //# sourceMappingURL=init.js.map
@@ -0,0 +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,uBAAuB,CAAA;AAE9E,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,6BAA6B,YAAY,wBAAwB,CAAC,CAAA;IAC/E,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,8CAA8C,KAAK,EAAE,CACtD,CAAA;IACH,CAAC;IAED,8BAA8B;IAC9B,OAAO,CAAC,GAAG,EAAE,CAAA;IACb,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,QAAQ,CAAC,cAAc,EAAE,CAAC,CAAC,CAAA;IACvD,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;QACtB,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;QAC5C,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CACxB,CAAC,EACD,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,CACjE,CAAA;QACD,MAAM,WAAW,GAAG,QAAQ,CAAC,cAAc,EAAE,CAAA;QAC7C,IAAI,SAAS,GAAG,EAAE,EAAE,CAAC;YACnB,IAAI,CAAC,eAAe,SAAS,WAAW,WAAW,GAAG,CAAC,CAAA;QACzD,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,aAAa,WAAW,EAAE,CAAC,CAAA;QAClC,CAAC;IACH,CAAC;IACD,IAAI,QAAQ,CAAC,SAAS,EAAE,CAAC;QACvB,IAAI,CAAC,eAAe,QAAQ,CAAC,SAAS,UAAU,CAAC,CAAA;IACnD,CAAC;IACD,OAAO,CAAC,GAAG,EAAE,CAAA;IAEb,sCAAsC;IACtC,MAAM,eAAe,GAAG,GAAG,CAAC,2BAA2B,CAAC,CAAC,KAAK,EAAE,CAAA;IAChE,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,UAAU,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;IAEjE,IAAI,CAAC;QACH,MAAM,WAAW,GAAG,MAAM,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;QACrD,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAA;QAC/C,CAAC;QACD,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CACjC,WAAW,CAAC,IAA8C,CAC3D,CAAA;QACD,MAAM,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAA;QACrD,eAAe,CAAC,OAAO,CAAC,YAAY,CAAC,CAAA;IACvC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,eAAe,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAA;QACvC,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,SAAS,YAAY,wBAAwB,CAAC,CAAA;IAC3D,CAAC;IAED,qDAAqD;IACrD,MAAM,cAAc,GAAG,GAAG,CAAC,cAAc,QAAQ,CAAC,UAAU,MAAM,CAAC,CAAC,KAAK,EAAE,CAAA;IAE3E,IAAI,CAAC;QACH,MAAM,UAAU,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;QACnC,cAAc,CAAC,OAAO,CAAC,WAAW,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,CAAA;IACvE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,cAAc,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAA;QACxC,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,SAAS,YAAY,wBAAwB,CAAC,CAAA;IAC3D,CAAC;YAAS,CAAC;QACT,IAAI,CAAC;YAAC,UAAU,CAAC,MAAM,CAAC,CAAA;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,aAAa,CAAC,CAAC;IACpD,CAAC;IAED,kDAAkD;IAClD,IAAI,CAAC;QACH,QAAQ,CAAC,UAAU,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;QACvD,QAAQ,CAAC,YAAY,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;QACzD,QAAQ,CACN,6CAA6C,EAC7C,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,CAClC,CAAA;IACH,CAAC;IAAC,MAAM,CAAC;QACP,wEAAwE;IAC1E,CAAC;IAED,oCAAoC;IACpC,MAAM,WAAW,CAAC,SAAS,EAAE;QAC3B,YAAY,EAAE,QAAQ,CAAC,YAAY;QACnC,cAAc,EAAE,QAAQ,CAAC,cAAc;QACvC,cAAc,EAAE,QAAQ,CAAC,cAAc;QACvC,aAAa,EAAE,QAAQ,CAAC,aAAa;QACrC,KAAK;QACL,SAAS,EAAE,QAAQ,CAAC,SAAS;QAC7B,QAAQ,EAAE,QAAQ,CAAC,QAAQ;QAC3B,SAAS,EAAE,QAAQ,CAAC,SAAS;QAC7B,OAAO;KACR,CAAC,CAAA;IAEF,qDAAqD;IACrD,MAAM,OAAO,GAAG,aAAa,CAAC,SAAS,CAAC,CAAA;IACxC,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;QAC3B,OAAO,CAAC,GAAG,EAAE,CAAA;QACb,MAAM,cAAc,GAAG,GAAG,CACxB,WAAW,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,8BAA8B,CAC5E,CAAC,KAAK,EAAE,CAAA;QACT,IAAI,CAAC;YACH,QAAQ,CAAC,OAAO,CAAC,cAAc,EAAE;gBAC/B,GAAG,EAAE,SAAS;gBACd,KAAK,EAAE,MAAM;aACd,CAAC,CAAA;YACF,cAAc,CAAC,OAAO,CAAC,wBAAwB,CAAC,CAAA;QAClD,CAAC;QAAC,MAAM,CAAC;YACP,cAAc,CAAC,IAAI,CACjB,iBAAiB,OAAO,CAAC,cAAc,2CAA2C,QAAQ,CAAC,UAAU,IAAI,CAC1G,CAAA;QACH,CAAC;IACH,CAAC;IAED,sCAAsC;IACtC,IAAI,CAAC;QACH,YAAY,CAAC,SAAS,CAAC,CAAA;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,eAAe;IACjB,CAAC;IAED,0BAA0B;IAC1B,OAAO,CAAC,GAAG,EAAE,CAAA;IACb,OAAO,CAAC,kBAAkB,CAAC,CAAA;IAC3B,OAAO,CAAC,GAAG,EAAE,CAAA;IACb,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAA;IACxC,OAAO,CAAC,GAAG,EAAE,CAAA;IACb,OAAO,CAAC,GAAG,CAAC,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,QAAQ,CAAC,UAAU,EAAE,CAAC,EAAE,CAAC,CAAA;IAC7D,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,CAAC,GAAG,CAAC,OAAO,KAAK,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAA;IACzD,CAAC;IACD,OAAO,CAAC,GAAG,EAAE,CAAA;IACb,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,iDAAiD,CAAC,CAAC,CAAA;IACzE,OAAO,CAAC,GAAG,EAAE,CAAA;IACb,OAAO,CAAC,GAAG,CAAC,OAAO,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC,CAAA;IACjD,OAAO,CAAC,GAAG,EAAE,CAAA;IACb,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;QACtB,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;QAC5C,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,eAAe,QAAQ,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,CAAA;QAClE,OAAO,CAAC,GAAG,EAAE,CAAA;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,4 @@
1
+ export declare function runSubmit(opts: {
2
+ yes?: boolean;
3
+ }): Promise<void>;
4
+ //# sourceMappingURL=submit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"submit.d.ts","sourceRoot":"","sources":["../../src/commands/submit.ts"],"names":[],"mappings":"AAWA,wBAAsB,SAAS,CAAC,IAAI,EAAE;IAAE,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAiItE"}
@@ -0,0 +1,124 @@
1
+ import { statSync, unlinkSync } from "fs";
2
+ import path from "path";
3
+ import chalk from "chalk";
4
+ import ora from "ora";
5
+ import * as readline from "readline/promises";
6
+ import { stdin as input, stdout as output } from "process";
7
+ import { readConfig } from "../lib/config.js";
8
+ import { submitZip } from "../lib/api.js";
9
+ import { createSubmissionZip } from "../lib/zip.js";
10
+ import { fatal, success, warn, info, FALLBACK_URL } from "../utils/errors.js";
11
+ export async function runSubmit(opts) {
12
+ // Step 1: Read config
13
+ const config = await readConfig();
14
+ if (!config) {
15
+ fatal("No Litmus assessment found in this directory.", "Run this command from inside your assessment folder, or run 'litmus init <token>' first.");
16
+ }
17
+ // Step 2: Check deadline
18
+ if (config.deadline) {
19
+ const deadline = new Date(config.deadline);
20
+ const now = new Date();
21
+ if (now > deadline) {
22
+ fatal(`The submission deadline passed at ${deadline.toLocaleString()}.`, `Contact the company if you need an extension. Visit ${FALLBACK_URL} for help.`);
23
+ }
24
+ const minsLeft = Math.floor((deadline.getTime() - now.getTime()) / (1000 * 60));
25
+ if (minsLeft < 30) {
26
+ warn(`Only ${minsLeft} minutes left before the deadline!`);
27
+ }
28
+ }
29
+ // Step 3: Find the project root (where .litmus/config.json lives)
30
+ let projectRoot = process.cwd();
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
+ }
49
+ // Step 4: Create the submission ZIP
50
+ const zipSpinner = ora("Preparing submission...").start();
51
+ let zipPath;
52
+ try {
53
+ zipPath = await createSubmissionZip(projectRoot);
54
+ zipSpinner.succeed("Submission prepared");
55
+ }
56
+ catch (err) {
57
+ zipSpinner.fail("Failed to create submission archive");
58
+ const msg = err instanceof Error ? err.message : String(err);
59
+ fatal(msg, `Visit ${FALLBACK_URL} to submit manually.`);
60
+ }
61
+ // Step 5: Show what's being submitted
62
+ const zipSizeMb = (statSync(zipPath).size / (1024 * 1024)).toFixed(1);
63
+ const folderName = path.basename(projectRoot);
64
+ console.log();
65
+ console.log(chalk.bold(` Submitting: ${config.assessmentName}`));
66
+ info(` Archive size: ${zipSizeMb} MB`);
67
+ info(` Tracking log included`);
68
+ if (config.deadline) {
69
+ info(` Deadline: ${new Date(config.deadline).toLocaleString()}`);
70
+ }
71
+ console.log();
72
+ // Step 6: Confirm (unless --yes flag)
73
+ if (!opts.yes) {
74
+ const rl = readline.createInterface({ input, output });
75
+ let answer;
76
+ try {
77
+ answer = await rl.question(chalk.bold(" Confirm submission? ") + chalk.dim("[Y/n] "));
78
+ }
79
+ finally {
80
+ rl.close();
81
+ }
82
+ const confirmed = answer.trim().toLowerCase();
83
+ if (confirmed !== "" && confirmed !== "y" && confirmed !== "yes") {
84
+ console.log();
85
+ console.log(chalk.dim(" Submission cancelled. Your work is saved."));
86
+ console.log();
87
+ try {
88
+ unlinkSync(zipPath);
89
+ }
90
+ catch { /* cleanup */ }
91
+ process.exit(0);
92
+ }
93
+ }
94
+ console.log();
95
+ // Step 7: Upload
96
+ const uploadSpinner = ora("Uploading submission...").start();
97
+ try {
98
+ const fileName = `${folderName}_submission.zip`;
99
+ const result = await submitZip(config.apiBase, config.token, zipPath, fileName);
100
+ uploadSpinner.succeed("Submission received");
101
+ console.log();
102
+ success("You're done!");
103
+ console.log();
104
+ console.log(chalk.dim(" The company will review your work and be in touch."));
105
+ if (result.submissionId) {
106
+ console.log(chalk.dim(` Submission ID: ${result.submissionId}`));
107
+ }
108
+ console.log();
109
+ }
110
+ catch (err) {
111
+ uploadSpinner.fail("Upload failed");
112
+ const msg = err instanceof Error ? err.message : String(err);
113
+ console.log();
114
+ fatal(msg, `To submit manually, visit ${FALLBACK_URL} and upload your ZIP from there.`);
115
+ }
116
+ finally {
117
+ // Clean up temp ZIP
118
+ try {
119
+ unlinkSync(zipPath);
120
+ }
121
+ catch { /* cleanup */ }
122
+ }
123
+ }
124
+ //# sourceMappingURL=submit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"submit.js","sourceRoot":"","sources":["../../src/commands/submit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ import { program } from "commander";
3
+ import { runInit } from "./commands/init.js";
4
+ import { runSubmit } from "./commands/submit.js";
5
+ const VERSION = "0.1.0";
6
+ program
7
+ .name("litmus")
8
+ .description("Litmus engineering assessment CLI")
9
+ .version(VERSION);
10
+ program
11
+ .command("init <token>")
12
+ .description("Download and set up a Litmus assessment")
13
+ .action(async (token) => {
14
+ try {
15
+ await runInit(token);
16
+ }
17
+ catch (err) {
18
+ console.error(err instanceof Error ? err.message : String(err));
19
+ process.exit(1);
20
+ }
21
+ });
22
+ program
23
+ .command("submit")
24
+ .description("Submit your completed assessment")
25
+ .option("-y, --yes", "Skip confirmation prompt")
26
+ .action(async (opts) => {
27
+ try {
28
+ await runSubmit(opts);
29
+ }
30
+ catch (err) {
31
+ console.error(err instanceof Error ? err.message : String(err));
32
+ process.exit(1);
33
+ }
34
+ });
35
+ program.parse();
36
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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"}
@@ -0,0 +1,30 @@
1
+ export interface InitMetadata {
2
+ assessmentId: string;
3
+ assessmentName: string;
4
+ folderName: string;
5
+ candidateEmail: string;
6
+ candidateName: string;
7
+ deadline: string | null;
8
+ timeLimit: number | null;
9
+ startedAt: string;
10
+ }
11
+ export interface SubmitResult {
12
+ success: boolean;
13
+ submissionId: string;
14
+ message: string;
15
+ }
16
+ /**
17
+ * Fetch assessment metadata by token.
18
+ * Marks the candidate as in_progress if they were pending.
19
+ */
20
+ export declare function fetchInitMetadata(apiBase: string, token: string): Promise<InitMetadata>;
21
+ /**
22
+ * Download the candidate ZIP by token.
23
+ * Returns a ReadableStream of the ZIP bytes.
24
+ */
25
+ export declare function downloadZip(apiBase: string, token: string): Promise<Response>;
26
+ /**
27
+ * Upload a ZIP file for submission.
28
+ */
29
+ export declare function submitZip(apiBase: string, token: string, zipPath: string, fileName: string): Promise<SubmitResult>;
30
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/lib/api.ts"],"names":[],"mappings":"AAGA,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;AAED;;;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;;GAEG;AACH,wBAAsB,SAAS,CAC7B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,YAAY,CAAC,CAwBvB"}
@@ -0,0 +1,54 @@
1
+ import FormData from "form-data";
2
+ import { createReadStream } from "fs";
3
+ /**
4
+ * Fetch assessment metadata by token.
5
+ * Marks the candidate as in_progress if they were pending.
6
+ */
7
+ export async function fetchInitMetadata(apiBase, token) {
8
+ const url = `${apiBase}/api/cli/init?token=${encodeURIComponent(token)}`;
9
+ const res = await fetch(url);
10
+ const body = await res.json().catch(() => ({}));
11
+ if (!res.ok) {
12
+ const message = body.message || body.error || `Server returned ${res.status}`;
13
+ throw new Error(message);
14
+ }
15
+ return body;
16
+ }
17
+ /**
18
+ * Download the candidate ZIP by token.
19
+ * Returns a ReadableStream of the ZIP bytes.
20
+ */
21
+ export async function downloadZip(apiBase, token) {
22
+ const url = `${apiBase}/api/cli/download?token=${encodeURIComponent(token)}`;
23
+ const res = await fetch(url);
24
+ if (!res.ok) {
25
+ const body = await res.json().catch(() => ({}));
26
+ const message = body.message || body.error || `Server returned ${res.status}`;
27
+ throw new Error(message);
28
+ }
29
+ return res;
30
+ }
31
+ /**
32
+ * Upload a ZIP file for submission.
33
+ */
34
+ export async function submitZip(apiBase, token, zipPath, fileName) {
35
+ const url = `${apiBase}/api/cli/submit?token=${encodeURIComponent(token)}`;
36
+ const form = new FormData();
37
+ form.append("file", createReadStream(zipPath), {
38
+ filename: fileName,
39
+ contentType: "application/zip",
40
+ });
41
+ const res = await fetch(url, {
42
+ method: "POST",
43
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
44
+ body: form,
45
+ headers: form.getHeaders(),
46
+ });
47
+ const body = await res.json().catch(() => ({}));
48
+ if (!res.ok) {
49
+ const message = body.message || body.error || `Server returned ${res.status}`;
50
+ throw new Error(message);
51
+ }
52
+ return body;
53
+ }
54
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/lib/api.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,gBAAgB,EAAE,MAAM,IAAI,CAAA;AAmBrC;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,OAAe,EACf,KAAa;IAEb,MAAM,GAAG,GAAG,GAAG,OAAO,uBAAuB,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAA;IACxE,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAA;IAC5B,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAA4B,CAAA;IAE1E,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,OAAO,GAAI,IAAI,CAAC,OAAkB,IAAK,IAAI,CAAC,KAAgB,IAAI,mBAAmB,GAAG,CAAC,MAAM,EAAE,CAAA;QACrG,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAA;IAC1B,CAAC;IAED,OAAO,IAA+B,CAAA;AACxC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,OAAe,EACf,KAAa;IAEb,MAAM,GAAG,GAAG,GAAG,OAAO,2BAA2B,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAA;IAC5E,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAA;IAE5B,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAA4B,CAAA;QAC1E,MAAM,OAAO,GAAI,IAAI,CAAC,OAAkB,IAAK,IAAI,CAAC,KAAgB,IAAI,mBAAmB,GAAG,CAAC,MAAM,EAAE,CAAA;QACrG,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAA;IAC1B,CAAC;IAED,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,OAAe,EACf,KAAa,EACb,OAAe,EACf,QAAgB;IAEhB,MAAM,GAAG,GAAG,GAAG,OAAO,yBAAyB,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAA;IAE1E,MAAM,IAAI,GAAG,IAAI,QAAQ,EAAE,CAAA;IAC3B,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,OAAO,CAAC,EAAE;QAC7C,QAAQ,EAAE,QAAQ;QAClB,WAAW,EAAE,iBAAiB;KAC/B,CAAC,CAAA;IAEF,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAC3B,MAAM,EAAE,MAAM;QACd,8DAA8D;QAC9D,IAAI,EAAE,IAAW;QACjB,OAAO,EAAE,IAAI,CAAC,UAAU,EAAE;KAC3B,CAAC,CAAA;IAEF,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAA4B,CAAA;IAE1E,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,OAAO,GAAI,IAAI,CAAC,OAAkB,IAAK,IAAI,CAAC,KAAgB,IAAI,mBAAmB,GAAG,CAAC,MAAM,EAAE,CAAA;QACrG,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAA;IAC1B,CAAC;IAED,OAAO,IAA+B,CAAA;AACxC,CAAC"}
@@ -0,0 +1,19 @@
1
+ export interface LitmusConfig {
2
+ assessmentId: string;
3
+ assessmentName: string;
4
+ candidateEmail: string;
5
+ candidateName: string;
6
+ token: string;
7
+ startedAt: string;
8
+ deadline: string | null;
9
+ timeLimit: number | null;
10
+ apiBase: string;
11
+ }
12
+ export declare function getConfigPath(projectRoot: string): string;
13
+ export declare function writeConfig(projectRoot: string, config: LitmusConfig): Promise<void>;
14
+ /**
15
+ * Read .litmus/config.json from the current directory or a parent directory.
16
+ * Returns null if no config is found.
17
+ */
18
+ export declare function readConfig(): Promise<LitmusConfig | null>;
19
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +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,CAqB/D"}
@@ -0,0 +1,40 @@
1
+ import { readFile, writeFile, mkdir } from "fs/promises";
2
+ import { existsSync } from "fs";
3
+ import path from "path";
4
+ const CONFIG_DIR = ".litmus";
5
+ const CONFIG_FILE = "config.json";
6
+ export function getConfigPath(projectRoot) {
7
+ return path.join(projectRoot, CONFIG_DIR, CONFIG_FILE);
8
+ }
9
+ export async function writeConfig(projectRoot, config) {
10
+ const configDir = path.join(projectRoot, CONFIG_DIR);
11
+ await mkdir(configDir, { recursive: true });
12
+ const configPath = path.join(configDir, CONFIG_FILE);
13
+ await writeFile(configPath, JSON.stringify(config, null, 2), "utf8");
14
+ }
15
+ /**
16
+ * Read .litmus/config.json from the current directory or a parent directory.
17
+ * Returns null if no config is found.
18
+ */
19
+ export async function readConfig() {
20
+ let dir = process.cwd();
21
+ // Walk up the directory tree looking for .litmus/config.json
22
+ while (true) {
23
+ const configPath = path.join(dir, CONFIG_DIR, CONFIG_FILE);
24
+ if (existsSync(configPath)) {
25
+ try {
26
+ const raw = await readFile(configPath, "utf8");
27
+ return JSON.parse(raw);
28
+ }
29
+ catch {
30
+ return null;
31
+ }
32
+ }
33
+ const parent = path.dirname(dir);
34
+ if (parent === dir)
35
+ break; // Reached filesystem root
36
+ dir = parent;
37
+ }
38
+ return null;
39
+ }
40
+ //# sourceMappingURL=config.js.map
@@ -0,0 +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,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,CAAA;gBAC9C,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAiB,CAAA;YACxC,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,IAAI,CAAA;YACb,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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Extract a ZIP file from disk to a target directory.
3
+ * Using Open.file + extract() is the most reliable unzipper API.
4
+ */
5
+ export declare function extractZip(zipPath: string, targetDir: string): Promise<void>;
6
+ //# sourceMappingURL=extract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../../src/lib/extract.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAIf"}
@@ -0,0 +1,12 @@
1
+ import { mkdir } from "fs/promises";
2
+ import unzipper from "unzipper";
3
+ /**
4
+ * Extract a ZIP file from disk to a target directory.
5
+ * Using Open.file + extract() is the most reliable unzipper API.
6
+ */
7
+ export async function extractZip(zipPath, targetDir) {
8
+ await mkdir(targetDir, { recursive: true });
9
+ const directory = await unzipper.Open.file(zipPath);
10
+ await directory.extract({ path: targetDir });
11
+ }
12
+ //# sourceMappingURL=extract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.js","sourceRoot":"","sources":["../../src/lib/extract.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AACnC,OAAO,QAAQ,MAAM,UAAU,CAAA;AAE/B;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,OAAe,EACf,SAAiB;IAEjB,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAC3C,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACnD,MAAM,SAAS,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAA;AAC9C,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Start the chokidar file watcher in the background.
3
+ * Spawns dist/watcher.cjs as a detached process; logs file events to
4
+ * <projectDir>/.litmus/activity.jsonl for later inclusion in the submission ZIP.
5
+ */
6
+ export declare function startTracker(projectDir: string): void;
7
+ //# sourceMappingURL=tracker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracker.d.ts","sourceRoot":"","sources":["../../src/lib/tracker.ts"],"names":[],"mappings":"AAOA;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAyBrD"}
@@ -0,0 +1,36 @@
1
+ import { spawn } from "child_process";
2
+ import { mkdirSync, writeFileSync } from "fs";
3
+ import path from "path";
4
+ import { fileURLToPath } from "url";
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+ /**
7
+ * Start the chokidar file watcher in the background.
8
+ * Spawns dist/watcher.cjs as a detached process; logs file events to
9
+ * <projectDir>/.litmus/activity.jsonl for later inclusion in the submission ZIP.
10
+ */
11
+ export function startTracker(projectDir) {
12
+ const litmusDir = path.join(projectDir, ".litmus");
13
+ const activityLog = path.join(litmusDir, "activity.jsonl");
14
+ const pidFile = path.join(litmusDir, "tracker.pid");
15
+ const watcherScript = path.join(__dirname, "watcher.cjs");
16
+ try {
17
+ mkdirSync(litmusDir, { recursive: true });
18
+ }
19
+ catch {
20
+ // Non-critical
21
+ }
22
+ const child = spawn("node", [watcherScript, projectDir, activityLog], {
23
+ detached: true,
24
+ stdio: "ignore",
25
+ });
26
+ if (child.pid) {
27
+ child.unref();
28
+ try {
29
+ writeFileSync(pidFile, String(child.pid), "utf8");
30
+ }
31
+ catch {
32
+ // Non-critical
33
+ }
34
+ }
35
+ }
36
+ //# sourceMappingURL=tracker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracker.js","sourceRoot":"","sources":["../../src/lib/tracker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAA;AACrC,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,IAAI,CAAA;AAC7C,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAA;AAEnC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;AAE9D;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,UAAkB;IAC7C,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,CAAA;IAClD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAA;IAC1D,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,CAAA;IACnD,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,CAAA;IAEzD,IAAI,CAAC;QACH,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,eAAe;IACjB,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,aAAa,EAAE,UAAU,EAAE,WAAW,CAAC,EAAE;QACpE,QAAQ,EAAE,IAAI;QACd,KAAK,EAAE,QAAQ;KAChB,CAAC,CAAA;IAEF,IAAI,KAAK,CAAC,GAAG,EAAE,CAAC;QACd,KAAK,CAAC,KAAK,EAAE,CAAA;QACb,IAAI,CAAC;YACH,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAA;QACnD,CAAC;QAAC,MAAM,CAAC;YACP,eAAe;QACjB,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Create a ZIP of the project directory for submission.
3
+ * Respects .gitignore patterns (basic support) and excludes common large dirs.
4
+ * Always includes .litmus/activity.jsonl (the tracking log).
5
+ *
6
+ * @param projectDir - Root of the project to zip
7
+ * @returns Path to the temporary ZIP file
8
+ */
9
+ export declare function createSubmissionZip(projectDir: string): Promise<string>;
10
+ //# sourceMappingURL=zip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zip.d.ts","sourceRoot":"","sources":["../../src/lib/zip.ts"],"names":[],"mappings":"AA+BA;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAuB7E"}