loop-task 1.4.5 → 1.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/board/App.js +12 -6
- package/dist/board/components/Navigator.js +3 -3
- package/dist/board/hooks/useInputShortcuts.js +29 -10
- package/dist/board/toast.js +1 -1
- package/dist/cli.js +47 -0
- package/dist/core/loop-controller.js +14 -2
- package/dist/core/scheduling.js +12 -0
- package/dist/daemon/manager.js +12 -1
- package/dist/daemon/server.js +5 -0
- package/dist/daemon/spawner.js +1 -1
- package/dist/entry.js +0 -0
- package/dist/loop-config.js +1 -0
- package/package.json +73 -74
package/README.md
CHANGED
|
@@ -50,6 +50,8 @@ loop-task # open the board (requires Bun)
|
|
|
50
50
|
loop-task start # start the daemon, restore persisted loops
|
|
51
51
|
loop-task new 30m -- npm test # create a background loop
|
|
52
52
|
loop-task run --now 10s -- echo hi # run a loop in the foreground
|
|
53
|
+
loop-task stop <id> # stop a frozen loop and kill its child process
|
|
54
|
+
loop-task restart # kill daemon + all loops, restart fresh
|
|
53
55
|
```
|
|
54
56
|
|
|
55
57
|
Or run it directly:
|
|
@@ -137,6 +139,8 @@ Colors can be a name (`white`, `cyan`, `green`, `yellow`, `orange`, `pink`) or a
|
|
|
137
139
|
| `loop-task new <interval> -- <command>` | Create a background loop (creates an inline task) |
|
|
138
140
|
| `loop-task new <interval> --project <name> -- <command>` | Create a loop assigned to a project |
|
|
139
141
|
| `loop-task run <interval> -- <command>` | Run a loop in the foreground |
|
|
142
|
+
| `loop-task stop <id>` | Stop a loop and interrupt its running child process |
|
|
143
|
+
| `loop-task restart` | Kill the daemon and all running loops, then restart fresh |
|
|
140
144
|
| `loop-task project list` | List all projects |
|
|
141
145
|
| `loop-task project new <name> [--color <color>]` | Create a project |
|
|
142
146
|
| `loop-task project rename <id\|name> <new-name>` | Rename a project |
|
package/dist/board/App.js
CHANGED
|
@@ -26,7 +26,7 @@ import { ProjectsPage } from "./components/ProjectsPage.js";
|
|
|
26
26
|
import { fetchRunLog, deleteLoop, pauseLoop, resumeLoop, stopLoop, playLoop, triggerLoop, listTasks, deleteTask, listProjects } from "./daemon.js";
|
|
27
27
|
import { useBreakpoint } from "./hooks/useBreakpoint.js";
|
|
28
28
|
import { useRouter } from "./router.js";
|
|
29
|
-
|
|
29
|
+
import { POLL_MS } from "../config/constants.js";
|
|
30
30
|
const VIEW_TO_MODE = {
|
|
31
31
|
create: "create",
|
|
32
32
|
"task-create": "task",
|
|
@@ -117,14 +117,20 @@ export function App(props) {
|
|
|
117
117
|
}
|
|
118
118
|
catch { /* ignore */ }
|
|
119
119
|
}
|
|
120
|
-
useState(() => { void refreshTasks(); });
|
|
121
120
|
async function refreshProjects() {
|
|
122
121
|
try {
|
|
123
122
|
setProjects(await listProjects());
|
|
124
123
|
}
|
|
125
124
|
catch { /* ignore */ }
|
|
126
125
|
}
|
|
127
|
-
|
|
126
|
+
useEffect(() => { void refreshTasks(); void refreshProjects(); }, []);
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
const timer = setInterval(() => {
|
|
129
|
+
void refreshTasks();
|
|
130
|
+
void refreshProjects();
|
|
131
|
+
}, POLL_MS);
|
|
132
|
+
return () => clearInterval(timer);
|
|
133
|
+
}, []);
|
|
128
134
|
useEffect(() => {
|
|
129
135
|
try {
|
|
130
136
|
localStorage.setItem("loop-current-project", currentProjectId);
|
|
@@ -135,7 +141,7 @@ export function App(props) {
|
|
|
135
141
|
return async () => {
|
|
136
142
|
try {
|
|
137
143
|
await action();
|
|
138
|
-
|
|
144
|
+
void refresh();
|
|
139
145
|
pushToast("success", label);
|
|
140
146
|
}
|
|
141
147
|
catch (error) {
|
|
@@ -266,7 +272,7 @@ export function App(props) {
|
|
|
266
272
|
setPendingTaskSelection(null);
|
|
267
273
|
pop();
|
|
268
274
|
pushToast("success", updated ? t("board.toastUpdated", { desc }) : t("board.toastStarted", { desc }));
|
|
269
|
-
|
|
275
|
+
void refresh();
|
|
270
276
|
};
|
|
271
277
|
const onTaskDone = (updated, id) => {
|
|
272
278
|
setEditTask(null);
|
|
@@ -309,7 +315,7 @@ export function App(props) {
|
|
|
309
315
|
onOpenRunLog: handleOpenRunLog,
|
|
310
316
|
refreshTasks,
|
|
311
317
|
onViewTasks: () => { void refreshTasks(); push("task-list"); },
|
|
312
|
-
onViewProjects: () => push("projects"),
|
|
318
|
+
onViewProjects: () => { void refreshProjects(); push("projects"); },
|
|
313
319
|
onViewLoops: () => replace("board"),
|
|
314
320
|
onAddLoop: () => { setEditTarget(null); push("create"); },
|
|
315
321
|
onAddTask: () => { setEditTask(null); push("task-create"); },
|
|
@@ -26,9 +26,9 @@ export function Navigator(props) {
|
|
|
26
26
|
const bulletW = 2;
|
|
27
27
|
const nonVar = 2 + 1 + statusW + 1 + 1 + 1 + exitW + 1 + 1 + runsW + 1 + skpW;
|
|
28
28
|
const avail = Math.floor(panelWidth) - nonVar - bulletW;
|
|
29
|
-
const descW = Math.min(
|
|
30
|
-
const
|
|
31
|
-
const
|
|
29
|
+
const descW = Math.min(32, Math.max(10, Math.round(avail * 0.38)));
|
|
30
|
+
const timingW = Math.min(16, Math.max(6, Math.round(avail * 0.25)));
|
|
31
|
+
const sinceW = Math.max(8, avail - descW - timingW);
|
|
32
32
|
const header = " " +
|
|
33
33
|
fit("", bulletW) +
|
|
34
34
|
fit(t("board.headerDescription"), descW) +
|
|
@@ -9,33 +9,52 @@ export function useInputShortcuts(getActiveInput) {
|
|
|
9
9
|
return;
|
|
10
10
|
const name = key.name;
|
|
11
11
|
if (name === "c") {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
try {
|
|
13
|
+
const text = input.getSelectedText();
|
|
14
|
+
if (text) {
|
|
15
|
+
copyToClipboard(text);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch (e) {
|
|
19
|
+
console.error(`[loop-task] copy failed: ${e}`);
|
|
15
20
|
}
|
|
16
21
|
key.preventDefault();
|
|
22
|
+
key.stopPropagation();
|
|
17
23
|
return;
|
|
18
24
|
}
|
|
19
25
|
if (name === "x") {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
try {
|
|
27
|
+
const text = input.getSelectedText();
|
|
28
|
+
if (text) {
|
|
29
|
+
copyToClipboard(text);
|
|
30
|
+
input.deleteSelection();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
console.error(`[loop-task] cut failed: ${e}`);
|
|
24
35
|
}
|
|
25
36
|
key.preventDefault();
|
|
37
|
+
key.stopPropagation();
|
|
26
38
|
return;
|
|
27
39
|
}
|
|
28
40
|
if (name === "v") {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
41
|
+
try {
|
|
42
|
+
const text = readFromClipboard();
|
|
43
|
+
if (text) {
|
|
44
|
+
input.insertText(text);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
console.error(`[loop-task] paste failed: ${e}`);
|
|
32
49
|
}
|
|
33
50
|
key.preventDefault();
|
|
51
|
+
key.stopPropagation();
|
|
34
52
|
return;
|
|
35
53
|
}
|
|
36
54
|
if (name === "a") {
|
|
37
55
|
input.selectAll();
|
|
38
56
|
key.preventDefault();
|
|
57
|
+
key.stopPropagation();
|
|
39
58
|
return;
|
|
40
59
|
}
|
|
41
60
|
});
|
package/dist/board/toast.js
CHANGED
package/dist/cli.js
CHANGED
|
@@ -4,6 +4,7 @@ import { createRequire } from "node:module";
|
|
|
4
4
|
import { Logger } from "./logger.js";
|
|
5
5
|
import { runLoop } from "./core/foreground-loop.js";
|
|
6
6
|
import { buildLoopOptions } from "./loop-config.js";
|
|
7
|
+
import { parseDuration } from "./duration.js";
|
|
7
8
|
import { startLoop } from "./client/commands.js";
|
|
8
9
|
import { listProjectsCli, createProjectCli, renameProjectCli, setProjectColorCli, deleteProjectCli, resolveProjectId, } from "./client/commands.js";
|
|
9
10
|
import { t } from "./i18n/index.js";
|
|
@@ -22,6 +23,49 @@ program
|
|
|
22
23
|
ensureDaemon();
|
|
23
24
|
console.log(t("cli.daemonStarted"));
|
|
24
25
|
});
|
|
26
|
+
program
|
|
27
|
+
.command("stop")
|
|
28
|
+
.description("Stop a running loop and interrupt its current execution")
|
|
29
|
+
.argument("<id>", "Loop ID")
|
|
30
|
+
.action(async (id) => {
|
|
31
|
+
try {
|
|
32
|
+
const { sendRequest } = await import("./client/ipc.js");
|
|
33
|
+
const res = await sendRequest({ type: "stop-loop", payload: { id } });
|
|
34
|
+
if (res.type === "ok" && res.data) {
|
|
35
|
+
console.log(`Stopped loop ${id}`);
|
|
36
|
+
}
|
|
37
|
+
else if (res.type === "error") {
|
|
38
|
+
console.error(res.message);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
console.error(`Loop ${id} not found`);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
48
|
+
console.error(t("cli.error", { message }));
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
program
|
|
53
|
+
.command("restart")
|
|
54
|
+
.description("Kill the daemon and all running loops, then restart fresh")
|
|
55
|
+
.action(async () => {
|
|
56
|
+
const { stopDaemon, ensureDaemon } = await import("./daemon/spawner.js");
|
|
57
|
+
const { readDaemonPid, removeDaemonPid, removeDaemonSignature } = await import("./daemon/state.js");
|
|
58
|
+
const pid = readDaemonPid();
|
|
59
|
+
if (pid !== null) {
|
|
60
|
+
console.log("Stopping daemon...");
|
|
61
|
+
stopDaemon(pid);
|
|
62
|
+
removeDaemonPid();
|
|
63
|
+
removeDaemonSignature();
|
|
64
|
+
}
|
|
65
|
+
console.log("Starting fresh daemon...");
|
|
66
|
+
ensureDaemon();
|
|
67
|
+
console.log("Daemon restarted.");
|
|
68
|
+
});
|
|
25
69
|
program
|
|
26
70
|
.command("new")
|
|
27
71
|
.description(t("cli.newDescription"))
|
|
@@ -32,17 +76,20 @@ program
|
|
|
32
76
|
.option("--verbose", t("cli.optVerbose"), false)
|
|
33
77
|
.option("--cwd <dir>", t("cli.optCwd"))
|
|
34
78
|
.option("--project <name>", t("cli.optProject"))
|
|
79
|
+
.option("--offset <duration>", "Phase offset (e.g. 5m, 15m)")
|
|
35
80
|
.action(async (intervalStr, cmdArgs, opts) => {
|
|
36
81
|
try {
|
|
37
82
|
const projectId = opts.project
|
|
38
83
|
? await resolveProjectId(opts.project)
|
|
39
84
|
: undefined;
|
|
85
|
+
const offsetMs = opts.offset ? parseDuration(opts.offset) : null;
|
|
40
86
|
const built = buildLoopOptions(intervalStr, {
|
|
41
87
|
...opts,
|
|
42
88
|
command: cmdArgs[0],
|
|
43
89
|
commandArgs: cmdArgs.slice(1),
|
|
44
90
|
cwd: opts.cwd ?? process.cwd(),
|
|
45
91
|
projectId,
|
|
92
|
+
offset: offsetMs,
|
|
46
93
|
});
|
|
47
94
|
await startLoop(built.options, built.intervalHuman);
|
|
48
95
|
}
|
|
@@ -5,6 +5,7 @@ import { sleep } from "../shared/sleep.js";
|
|
|
5
5
|
import { SLEEP_CHUNK_MS } from "../config/constants.js";
|
|
6
6
|
import { executeCommand } from "./command-runner.js";
|
|
7
7
|
import { rotateLogIfNeeded } from "./log-rotator.js";
|
|
8
|
+
import { computePhase, alignToPhase } from "./scheduling.js";
|
|
8
9
|
import { t } from "../i18n/index.js";
|
|
9
10
|
export class LoopController extends EventEmitter {
|
|
10
11
|
abortController;
|
|
@@ -116,7 +117,11 @@ export class LoopController extends EventEmitter {
|
|
|
116
117
|
this._resetSchedule = true;
|
|
117
118
|
this._paused = false;
|
|
118
119
|
this._status = "waiting";
|
|
119
|
-
|
|
120
|
+
const phase = this.options.offset !== null
|
|
121
|
+
? this.options.offset
|
|
122
|
+
: computePhase(this.id, this.options.interval);
|
|
123
|
+
const delay = alignToPhase(Date.now(), this.options.interval, phase);
|
|
124
|
+
this.nextRunAt = new Date(Date.now() + delay).toISOString();
|
|
120
125
|
if (this.resumeResolve) {
|
|
121
126
|
this.resumeResolve();
|
|
122
127
|
this.resumeResolve = null;
|
|
@@ -274,7 +279,14 @@ export class LoopController extends EventEmitter {
|
|
|
274
279
|
}
|
|
275
280
|
}
|
|
276
281
|
else if (isFirstRun && !this.options.immediate) {
|
|
277
|
-
const
|
|
282
|
+
const phase = this.options.offset !== null
|
|
283
|
+
? this.options.offset
|
|
284
|
+
: computePhase(this.id, this.options.interval);
|
|
285
|
+
const delay = alignToPhase(Date.now(), this.options.interval, phase);
|
|
286
|
+
if (delay > 0) {
|
|
287
|
+
this.nextRunAt = new Date(Date.now() + delay).toISOString();
|
|
288
|
+
}
|
|
289
|
+
const completed = await this.waitForDelay(delay, signal);
|
|
278
290
|
if (!completed) {
|
|
279
291
|
break;
|
|
280
292
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function computePhase(loopId, intervalMs) {
|
|
2
|
+
let hash = 0;
|
|
3
|
+
for (let i = 0; i < loopId.length; i++) {
|
|
4
|
+
hash = ((hash << 5) - hash + loopId.charCodeAt(i)) | 0;
|
|
5
|
+
}
|
|
6
|
+
return Math.abs(hash) % intervalMs;
|
|
7
|
+
}
|
|
8
|
+
export function alignToPhase(now, intervalMs, phaseMs) {
|
|
9
|
+
const elapsed = now % intervalMs;
|
|
10
|
+
const delay = (phaseMs - elapsed + intervalMs) % intervalMs;
|
|
11
|
+
return delay;
|
|
12
|
+
}
|
package/dist/daemon/manager.js
CHANGED
|
@@ -44,6 +44,7 @@ export class LoopManager {
|
|
|
44
44
|
verbose: meta.verbose,
|
|
45
45
|
description: meta.description ?? "",
|
|
46
46
|
projectId: meta.projectId ?? "default",
|
|
47
|
+
offset: meta.offset ?? null,
|
|
47
48
|
};
|
|
48
49
|
const logPath = getLogPath(meta.id);
|
|
49
50
|
const controller = new LoopController(meta.id, options, logPath, this.taskResolver, {
|
|
@@ -161,10 +162,19 @@ export class LoopManager {
|
|
|
161
162
|
const entry = this.loops.get(id);
|
|
162
163
|
if (!entry)
|
|
163
164
|
return false;
|
|
164
|
-
entry.controller.stopLoop();
|
|
165
|
+
entry.controller.stopLoop(true);
|
|
165
166
|
this.persist(id, entry.controller, entry.options, entry.intervalHuman);
|
|
166
167
|
return true;
|
|
167
168
|
}
|
|
169
|
+
stopAllLoops() {
|
|
170
|
+
let count = 0;
|
|
171
|
+
for (const [id, entry] of this.loops) {
|
|
172
|
+
entry.controller.stopLoop(true);
|
|
173
|
+
this.persist(id, entry.controller, entry.options, entry.intervalHuman);
|
|
174
|
+
count++;
|
|
175
|
+
}
|
|
176
|
+
return count;
|
|
177
|
+
}
|
|
168
178
|
playLoop(id) {
|
|
169
179
|
const entry = this.loops.get(id);
|
|
170
180
|
if (!entry)
|
|
@@ -255,6 +265,7 @@ export class LoopManager {
|
|
|
255
265
|
remainingDelayMs: runtime.remainingDelayMs,
|
|
256
266
|
pid: process.pid,
|
|
257
267
|
projectId: options.projectId ?? "default",
|
|
268
|
+
offset: options.offset,
|
|
258
269
|
};
|
|
259
270
|
}
|
|
260
271
|
}
|
package/dist/daemon/server.js
CHANGED
|
@@ -112,6 +112,11 @@ export class IpcServer {
|
|
|
112
112
|
this.respondOk(socket, this.manager.stopLoop(request.payload.id), request.payload.id);
|
|
113
113
|
break;
|
|
114
114
|
}
|
|
115
|
+
case "stop-all": {
|
|
116
|
+
const count = this.manager.stopAllLoops();
|
|
117
|
+
send(socket, { type: "ok", data: count });
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
115
120
|
case "play-loop": {
|
|
116
121
|
if (this.manager.isMaxRunsBlocked(request.payload.id)) {
|
|
117
122
|
send(socket, { type: "error", message: t("errors.maxRunsReached") });
|
package/dist/daemon/spawner.js
CHANGED
package/dist/entry.js
CHANGED
|
File without changes
|
package/dist/loop-config.js
CHANGED
package/package.json
CHANGED
|
@@ -1,75 +1,74 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "loop-task",
|
|
3
|
-
"version": "1.4.
|
|
4
|
-
"description": "Loop engineering toolkit. Run any command on a cadence, in the background, managed from a terminal board. Schedule tests, builds, syncs, or agent prompts.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"bin": {
|
|
7
|
-
"loop-task": "dist/entry.js"
|
|
8
|
-
},
|
|
9
|
-
"main": "dist/entry.js",
|
|
10
|
-
"files": [
|
|
11
|
-
"dist"
|
|
12
|
-
],
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
"
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
|
|
74
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "loop-task",
|
|
3
|
+
"version": "1.4.7",
|
|
4
|
+
"description": "Loop engineering toolkit. Run any command on a cadence, in the background, managed from a terminal board. Schedule tests, builds, syncs, or agent prompts.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"loop-task": "dist/entry.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/entry.js",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [
|
|
14
|
+
"cli",
|
|
15
|
+
"loop",
|
|
16
|
+
"loop-engineering",
|
|
17
|
+
"repeat",
|
|
18
|
+
"interval",
|
|
19
|
+
"schedule",
|
|
20
|
+
"timer",
|
|
21
|
+
"cron",
|
|
22
|
+
"automation",
|
|
23
|
+
"automations",
|
|
24
|
+
"devops",
|
|
25
|
+
"agent",
|
|
26
|
+
"ai-agent",
|
|
27
|
+
"coding-agent",
|
|
28
|
+
"agent-automation",
|
|
29
|
+
"claude-code",
|
|
30
|
+
"codex",
|
|
31
|
+
"opencode",
|
|
32
|
+
"background-tasks",
|
|
33
|
+
"scheduler"
|
|
34
|
+
],
|
|
35
|
+
"author": "Quique Fdez Guerra",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=20.0.0"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@opentui/core": "^0.4.1",
|
|
42
|
+
"@opentui/react": "^0.4.1",
|
|
43
|
+
"commander": "^13.1.0",
|
|
44
|
+
"execa": "^9.6.0",
|
|
45
|
+
"ms": "^2.1.3",
|
|
46
|
+
"react": "^19.2.7"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/ms": "^2.1.0",
|
|
50
|
+
"@types/node": "^22.15.0",
|
|
51
|
+
"@types/react": "^19.2.17",
|
|
52
|
+
"@vitest/coverage-v8": "^3.1.0",
|
|
53
|
+
"eslint": "^9.25.0",
|
|
54
|
+
"tsx": "^4.19.0",
|
|
55
|
+
"typescript": "^5.8.0",
|
|
56
|
+
"typescript-eslint": "^8.30.0",
|
|
57
|
+
"vitest": "^3.1.0"
|
|
58
|
+
},
|
|
59
|
+
"scripts": {
|
|
60
|
+
"build": "tsc -p tsconfig.build.json && node -e \"require('fs').copyFileSync('src/entry.js','dist/entry.js'); require('fs').copyFileSync('src/esm-loader.js','dist/esm-loader.js')\"",
|
|
61
|
+
"start": "node dist/entry.js",
|
|
62
|
+
"dev": "bun src/cli.ts",
|
|
63
|
+
"dev:watch": "bun --watch src/cli.ts",
|
|
64
|
+
"board": "node dist/entry.js",
|
|
65
|
+
"test": "vitest run --exclude '**/background-cli.test.ts'",
|
|
66
|
+
"test:all": "vitest run",
|
|
67
|
+
"test:watch": "vitest",
|
|
68
|
+
"test:coverage": "vitest run --coverage --exclude '**/background-cli.test.ts'",
|
|
69
|
+
"lint": "eslint src/ tests/",
|
|
70
|
+
"typecheck": "tsc --noEmit",
|
|
71
|
+
"release:dry": "npm publish --dry-run",
|
|
72
|
+
"release": "npm publish"
|
|
73
|
+
}
|
|
75
74
|
}
|