loop-task 1.1.0 → 1.3.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 (117) hide show
  1. package/README.md +132 -50
  2. package/dist/board/App.js +202 -0
  3. package/dist/board/board-log.js +18 -0
  4. package/dist/board/components/ActionButtons.js +25 -0
  5. package/dist/board/components/BoardButton.js +58 -0
  6. package/dist/board/components/ConfirmModal.js +36 -0
  7. package/dist/board/components/CreateForm.js +155 -0
  8. package/dist/board/components/DetailView.js +25 -0
  9. package/dist/board/components/FilterBar.js +15 -0
  10. package/dist/board/components/Footer.js +39 -0
  11. package/dist/board/components/Header.js +16 -0
  12. package/dist/board/components/HelpModal.js +35 -0
  13. package/dist/board/components/Inspector.js +12 -0
  14. package/dist/board/components/LogModal.js +27 -0
  15. package/dist/board/components/Navigator.js +68 -0
  16. package/dist/board/components/RunHistory.js +44 -0
  17. package/dist/board/components/Timeline.js +5 -0
  18. package/dist/board/daemon.js +61 -0
  19. package/dist/board/format.js +95 -0
  20. package/dist/board/hooks/useBoardKeybindings.js +191 -0
  21. package/dist/board/hooks/useBreakpoint.js +6 -0
  22. package/dist/board/hooks/useHoverState.js +11 -0
  23. package/dist/board/hooks/useLogStream.js +32 -0
  24. package/dist/board/hooks/useLoopPolling.js +28 -0
  25. package/dist/board/index.js +32 -0
  26. package/dist/board/state.js +95 -0
  27. package/dist/board/toast.js +65 -0
  28. package/dist/board/types.js +1 -0
  29. package/dist/cli.js +80 -94
  30. package/dist/cli.js.map +1 -1
  31. package/dist/client/commands.d.ts +11 -0
  32. package/dist/client/commands.d.ts.map +1 -0
  33. package/dist/client/commands.js +161 -0
  34. package/dist/client/commands.js.map +1 -0
  35. package/dist/client/ipc.d.ts +5 -0
  36. package/dist/client/ipc.d.ts.map +1 -0
  37. package/dist/client/ipc.js +96 -0
  38. package/dist/client/ipc.js.map +1 -0
  39. package/dist/config/constants.js +14 -0
  40. package/dist/config/paths.js +43 -0
  41. package/dist/core/command-runner.js +80 -0
  42. package/dist/core/foreground-loop.js +78 -0
  43. package/dist/core/log-parser.js +29 -0
  44. package/dist/core/log-rotator.js +25 -0
  45. package/dist/core/loop-controller.js +251 -0
  46. package/dist/daemon/daemon-log.js +11 -0
  47. package/dist/daemon/index.d.ts +2 -0
  48. package/dist/daemon/index.d.ts.map +1 -0
  49. package/dist/daemon/index.js +40 -0
  50. package/dist/daemon/index.js.map +1 -0
  51. package/dist/daemon/manager.d.ts +17 -0
  52. package/dist/daemon/manager.d.ts.map +1 -0
  53. package/dist/daemon/manager.js +181 -0
  54. package/dist/daemon/manager.js.map +1 -0
  55. package/dist/daemon/server.d.ts +15 -0
  56. package/dist/daemon/server.d.ts.map +1 -0
  57. package/dist/daemon/server.js +174 -0
  58. package/dist/daemon/server.js.map +1 -0
  59. package/dist/daemon/spawner.d.ts +3 -0
  60. package/dist/daemon/spawner.d.ts.map +1 -0
  61. package/dist/daemon/spawner.js +82 -0
  62. package/dist/daemon/spawner.js.map +1 -0
  63. package/dist/daemon/state.d.ts +15 -0
  64. package/dist/daemon/state.d.ts.map +1 -0
  65. package/dist/daemon/state.js +120 -0
  66. package/dist/daemon/state.js.map +1 -0
  67. package/dist/duration.js +4 -4
  68. package/dist/entry.js +11 -0
  69. package/dist/esm-loader.js +36 -0
  70. package/dist/i18n/en.json +267 -0
  71. package/dist/i18n/index.js +11 -0
  72. package/dist/ipc/handlers/logs-stream.js +65 -0
  73. package/dist/ipc/send.js +5 -0
  74. package/dist/logger.js +0 -1
  75. package/dist/loop-config.d.ts +13 -0
  76. package/dist/loop-config.d.ts.map +1 -0
  77. package/dist/loop-config.js +74 -0
  78. package/dist/loop-config.js.map +1 -0
  79. package/dist/loop.d.ts +43 -2
  80. package/dist/loop.d.ts.map +1 -1
  81. package/dist/loop.js +247 -2
  82. package/dist/loop.js.map +1 -1
  83. package/dist/open-board.d.ts +2 -0
  84. package/dist/open-board.d.ts.map +1 -0
  85. package/dist/open-board.js +19 -0
  86. package/dist/open-board.js.map +1 -0
  87. package/dist/shared/fs-utils.js +11 -0
  88. package/dist/shared/sleep.js +17 -0
  89. package/dist/shared/tail.js +4 -0
  90. package/dist/tui/app.d.ts +57 -0
  91. package/dist/tui/app.d.ts.map +1 -0
  92. package/dist/tui/app.js +167 -0
  93. package/dist/tui/app.js.map +1 -0
  94. package/dist/tui/create-loop.d.ts +3 -0
  95. package/dist/tui/create-loop.d.ts.map +1 -0
  96. package/dist/tui/create-loop.js +90 -0
  97. package/dist/tui/create-loop.js.map +1 -0
  98. package/dist/tui/dashboard.d.ts +2 -0
  99. package/dist/tui/dashboard.d.ts.map +1 -0
  100. package/dist/tui/dashboard.js +45 -0
  101. package/dist/tui/dashboard.js.map +1 -0
  102. package/dist/tui/events.d.ts +3 -0
  103. package/dist/tui/events.d.ts.map +1 -0
  104. package/dist/tui/events.js +244 -0
  105. package/dist/tui/events.js.map +1 -0
  106. package/dist/tui/popup.d.ts +3 -0
  107. package/dist/tui/popup.d.ts.map +1 -0
  108. package/dist/tui/popup.js +58 -0
  109. package/dist/tui/popup.js.map +1 -0
  110. package/dist/tui/render.d.ts +9 -0
  111. package/dist/tui/render.d.ts.map +1 -0
  112. package/dist/tui/render.js +305 -0
  113. package/dist/tui/render.js.map +1 -0
  114. package/dist/types.d.ts +74 -0
  115. package/dist/types.d.ts.map +1 -1
  116. package/dist/types.js +0 -1
  117. package/package.json +64 -55
@@ -0,0 +1,11 @@
1
+ import type { LoopOptions } from "../types.js";
2
+ export declare function createBackgroundLoop(options: LoopOptions, intervalHuman: string): Promise<string>;
3
+ export declare function startLoop(options: LoopOptions, intervalHuman: string): Promise<void>;
4
+ export declare function listLoops(): Promise<void>;
5
+ export declare function showStatus(id: string): Promise<void>;
6
+ export declare function pauseLoop(id: string): Promise<void>;
7
+ export declare function resumeLoop(id: string): Promise<void>;
8
+ export declare function deleteLoop(id: string): Promise<void>;
9
+ export declare function viewLogs(id: string, follow: boolean, tail: number): Promise<void>;
10
+ export declare function attachLoop(id: string): Promise<void>;
11
+ //# sourceMappingURL=commands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/client/commands.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAY,WAAW,EAAE,MAAM,aAAa,CAAC;AAIzD,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,WAAW,EACpB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,MAAM,CAAC,CAWjB;AAED,wBAAsB,SAAS,CAC7B,OAAO,EAAE,WAAW,EACpB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC,CAiBf;AAED,wBAAsB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CA4C/C;AAED,wBAAsB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA6B1D;AAED,wBAAsB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQzD;AAED,wBAAsB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ1D;AAED,wBAAsB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ1D;AAED,wBAAsB,QAAQ,CAC5B,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,OAAO,EACf,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAgCf;AAED,wBAAsB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAkB1D"}
@@ -0,0 +1,161 @@
1
+ import { formatDuration } from "../duration.js";
2
+ import { t } from "../i18n/index.js";
3
+ import { sendRequest, streamRequest } from "./ipc.js";
4
+ export async function createBackgroundLoop(options, intervalHuman) {
5
+ const response = await sendRequest({
6
+ type: "start",
7
+ payload: { ...options, intervalHuman },
8
+ });
9
+ if (response.type !== "ok") {
10
+ throw new Error(response.message);
11
+ }
12
+ return response.data.id;
13
+ }
14
+ export async function startLoop(options, intervalHuman) {
15
+ const id = await createBackgroundLoop(options, intervalHuman).catch((error) => {
16
+ const message = error instanceof Error ? error.message : String(error);
17
+ console.error(t("cli.error", { message }));
18
+ process.exit(1);
19
+ });
20
+ console.log(t("cli.startedTitle"));
21
+ console.log(t("cli.startedId", { id }));
22
+ console.log(t("cli.startedCommand", { command: `${options.command} ${options.commandArgs.join(" ")}` }));
23
+ console.log(t("cli.startedInterval", { interval: intervalHuman }));
24
+ console.log(t("cli.startedStatus"));
25
+ console.log();
26
+ console.log(t("cli.startedHint"));
27
+ process.exit(0);
28
+ }
29
+ export async function listLoops() {
30
+ const response = await sendRequest({ type: "list" });
31
+ if (response.type !== "ok") {
32
+ console.error(t("cli.error", { message: response.message }));
33
+ process.exit(1);
34
+ }
35
+ const loops = response.data;
36
+ if (loops.length === 0) {
37
+ console.log(t("cli.noLoops"));
38
+ return;
39
+ }
40
+ const idW = Math.max(4, ...loops.map((l) => l.id.length)) + 2;
41
+ const cmdW = Math.max(7, ...loops.map((l) => formatCmd(l.command, l.commandArgs).length)) + 2;
42
+ const intW = 10;
43
+ const statusW = 10;
44
+ const runsW = 6;
45
+ const header = pad(t("cli.headerId"), idW) +
46
+ pad(t("cli.headerCommand"), cmdW) +
47
+ pad(t("cli.headerInterval"), intW) +
48
+ pad(t("cli.headerStatus"), statusW) +
49
+ pad(t("cli.headerRuns"), runsW) +
50
+ t("cli.headerLastExit");
51
+ console.log(header);
52
+ console.log("-".repeat(header.length));
53
+ for (const loop of loops) {
54
+ const exit = loop.lastExitCode !== null ? String(loop.lastExitCode) : "-";
55
+ console.log(pad(loop.id, idW) +
56
+ pad(formatCmd(loop.command, loop.commandArgs), cmdW) +
57
+ pad(loop.intervalHuman, intW) +
58
+ pad(loop.status, statusW) +
59
+ pad(String(loop.runCount), runsW) +
60
+ exit);
61
+ }
62
+ process.exit(0);
63
+ }
64
+ export async function showStatus(id) {
65
+ const response = await sendRequest({ type: "status", payload: { id } });
66
+ if (response.type !== "ok") {
67
+ console.error(t("cli.error", { message: response.message }));
68
+ process.exit(1);
69
+ }
70
+ const loop = response.data;
71
+ const cmd = `${loop.command} ${loop.commandArgs.join(" ")}`.trim();
72
+ const maxRuns = loop.maxRuns !== null ? String(loop.maxRuns) : t("cli.unlimited");
73
+ console.log(t("cli.statusTitle", { id: loop.id }));
74
+ console.log(t("cli.statusCommand", { command: cmd }));
75
+ console.log(t("cli.statusInterval", { interval: loop.intervalHuman, duration: formatDuration(loop.interval) }));
76
+ console.log(t("cli.statusStatus", { status: loop.status }));
77
+ console.log(t("cli.statusRuns", { runs: loop.runCount, maxRuns }));
78
+ console.log(t("cli.statusCreated", { created: loop.createdAt }));
79
+ if (loop.lastRunAt) {
80
+ const exitInfo = loop.lastExitCode !== null ? t("cli.exitInfo", { code: loop.lastExitCode }) : "";
81
+ const durInfo = loop.lastDuration !== null ? formatDuration(loop.lastDuration) : "";
82
+ console.log(t("cli.statusLastRun", { lastRun: loop.lastRunAt, exit: exitInfo, duration: durInfo }).trim());
83
+ }
84
+ if (loop.nextRunAt) {
85
+ console.log(t("cli.statusNextRun", { nextRun: loop.nextRunAt }));
86
+ }
87
+ process.exit(0);
88
+ }
89
+ export async function pauseLoop(id) {
90
+ const response = await sendRequest({ type: "pause", payload: { id } });
91
+ if (response.type !== "ok") {
92
+ console.error(t("cli.error", { message: response.message }));
93
+ process.exit(1);
94
+ }
95
+ console.log(t("cli.paused", { id }));
96
+ process.exit(0);
97
+ }
98
+ export async function resumeLoop(id) {
99
+ const response = await sendRequest({ type: "resume", payload: { id } });
100
+ if (response.type !== "ok") {
101
+ console.error(t("cli.error", { message: response.message }));
102
+ process.exit(1);
103
+ }
104
+ console.log(t("cli.resumed", { id }));
105
+ process.exit(0);
106
+ }
107
+ export async function deleteLoop(id) {
108
+ const response = await sendRequest({ type: "delete", payload: { id } });
109
+ if (response.type !== "ok") {
110
+ console.error(t("cli.error", { message: response.message }));
111
+ process.exit(1);
112
+ }
113
+ console.log(t("cli.deleted", { id }));
114
+ process.exit(0);
115
+ }
116
+ export async function viewLogs(id, follow, tail) {
117
+ if (!follow) {
118
+ const response = await sendRequest({
119
+ type: "logs",
120
+ payload: { id, follow: false, tail },
121
+ });
122
+ if (response.type !== "ok") {
123
+ console.error(t("cli.error", { message: response.message }));
124
+ process.exit(1);
125
+ }
126
+ const content = response.data;
127
+ if (content) {
128
+ process.stdout.write(content);
129
+ if (!content.endsWith("\n"))
130
+ process.stdout.write("\n");
131
+ }
132
+ process.exit(0);
133
+ }
134
+ const socket = streamRequest({ type: "logs", payload: { id, follow: true, tail } }, (line) => console.log(line), () => process.exit(0), (err) => {
135
+ console.error(t("cli.error", { message: err.message }));
136
+ process.exit(1);
137
+ });
138
+ process.on("SIGINT", () => {
139
+ socket.destroy();
140
+ process.exit(0);
141
+ });
142
+ }
143
+ export async function attachLoop(id) {
144
+ console.log(t("cli.attaching", { id }));
145
+ const socket = streamRequest({ type: "attach", payload: { id } }, (line) => console.log(line), () => process.exit(0), (err) => {
146
+ console.error(t("cli.error", { message: err.message }));
147
+ process.exit(1);
148
+ });
149
+ process.on("SIGINT", () => {
150
+ console.log(t("cli.detached"));
151
+ socket.destroy();
152
+ process.exit(0);
153
+ });
154
+ }
155
+ function formatCmd(command, args) {
156
+ const full = `${command} ${args.join(" ")}`.trim();
157
+ return full.length > 30 ? full.slice(0, 27) + "..." : full;
158
+ }
159
+ function pad(str, width) {
160
+ return str.length >= width ? str + " " : str + " ".repeat(width - str.length);
161
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.js","sourceRoot":"","sources":["../../src/client/commands.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import net from "node:net";
2
+ import type { IpcRequest, IpcResponse } from "../types.js";
3
+ export declare function sendRequest(request: IpcRequest): Promise<IpcResponse>;
4
+ export declare function streamRequest(request: IpcRequest, onLine: (line: string) => void, onEnd: () => void, onError: (err: Error) => void): net.Socket;
5
+ //# sourceMappingURL=ipc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ipc.d.ts","sourceRoot":"","sources":["../../src/client/ipc.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,UAAU,CAAC;AAC3B,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG3D,wBAAsB,WAAW,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAuD3E;AAED,wBAAgB,aAAa,CAC3B,OAAO,EAAE,UAAU,EACnB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAC9B,KAAK,EAAE,MAAM,IAAI,EACjB,OAAO,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,GAC5B,GAAG,CAAC,MAAM,CA6CZ"}
@@ -0,0 +1,96 @@
1
+ import net from "node:net";
2
+ import { ensureDaemon, getSocket } from "../daemon/spawner.js";
3
+ import { t } from "../i18n/index.js";
4
+ import { IPC_TIMEOUT_MS } from "../config/constants.js";
5
+ export async function sendRequest(request) {
6
+ ensureDaemon();
7
+ const socketPath = getSocket();
8
+ return new Promise((resolve, reject) => {
9
+ let resolved = false;
10
+ const socket = net.createConnection(socketPath, () => {
11
+ socket.write(JSON.stringify(request) + "\n");
12
+ });
13
+ let buffer = "";
14
+ socket.on("data", (chunk) => {
15
+ buffer += chunk.toString();
16
+ const lines = buffer.split("\n");
17
+ buffer = lines.pop() ?? "";
18
+ for (const line of lines) {
19
+ if (line.trim() && !resolved) {
20
+ try {
21
+ const response = JSON.parse(line);
22
+ resolved = true;
23
+ socket.destroy();
24
+ resolve(response);
25
+ }
26
+ catch {
27
+ resolved = true;
28
+ socket.destroy();
29
+ reject(new Error(t("errors.invalidResponse")));
30
+ }
31
+ }
32
+ }
33
+ });
34
+ socket.on("close", () => {
35
+ if (!resolved) {
36
+ resolved = true;
37
+ reject(new Error(t("errors.connectionClosed")));
38
+ }
39
+ });
40
+ socket.on("error", (err) => {
41
+ if (!resolved) {
42
+ resolved = true;
43
+ reject(new Error(t("errors.cannotConnect", { message: err.message })));
44
+ }
45
+ });
46
+ socket.setTimeout(IPC_TIMEOUT_MS, () => {
47
+ if (!resolved) {
48
+ resolved = true;
49
+ socket.destroy();
50
+ reject(new Error(t("errors.requestTimedOut")));
51
+ }
52
+ });
53
+ });
54
+ }
55
+ export function streamRequest(request, onLine, onEnd, onError) {
56
+ ensureDaemon();
57
+ const socketPath = getSocket();
58
+ const socket = net.createConnection(socketPath, () => {
59
+ socket.write(JSON.stringify(request) + "\n");
60
+ });
61
+ let buffer = "";
62
+ socket.on("data", (chunk) => {
63
+ buffer += chunk.toString();
64
+ const lines = buffer.split("\n");
65
+ buffer = lines.pop() ?? "";
66
+ for (const line of lines) {
67
+ if (line.trim()) {
68
+ try {
69
+ const response = JSON.parse(line);
70
+ if (response.type === "data") {
71
+ onLine(response.line);
72
+ }
73
+ else if (response.type === "error") {
74
+ onError(new Error(response.message));
75
+ socket.destroy();
76
+ }
77
+ else if (response.type === "end") {
78
+ onEnd();
79
+ socket.destroy();
80
+ }
81
+ }
82
+ catch {
83
+ onError(new Error(t("errors.invalidResponse")));
84
+ socket.destroy();
85
+ }
86
+ }
87
+ }
88
+ });
89
+ socket.on("error", (err) => {
90
+ onError(new Error(t("errors.cannotConnect", { message: err.message })));
91
+ });
92
+ socket.on("close", () => {
93
+ onEnd();
94
+ });
95
+ return socket;
96
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ipc.js","sourceRoot":"","sources":["../../src/client/ipc.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ export const POLL_MS = 1000;
2
+ export const TOAST_TIMEOUT_MS = 3500;
3
+ export const TOAST_MAX = 4;
4
+ export const LOG_LINES_MAX = 500;
5
+ export const MAX_LOG_BYTES = 1024 * 1024;
6
+ export const MAX_LOG_GENERATIONS = 3;
7
+ export const SLEEP_CHUNK_MS = 200;
8
+ export const DAEMON_SPAWN_TIMEOUT_MS = 5000;
9
+ export const DAEMON_POLL_MS = 100;
10
+ export const IPC_TIMEOUT_MS = 10000;
11
+ export const LOG_TAIL_DEFAULT = 50;
12
+ export const BOARD_BREAKPOINT_WIDTH = 80;
13
+ export const HEADER_COMPACT_WIDTH = 60;
14
+ export const HOVER_BG = "#1e3a5f";
@@ -0,0 +1,43 @@
1
+ import path from "node:path";
2
+ import os from "node:os";
3
+ import crypto from "node:crypto";
4
+ export function getDataDir() {
5
+ const override = process.env.LOOP_CLI_HOME;
6
+ const base = override && override.trim() ? override : os.homedir();
7
+ return path.join(base, ".loop-cli");
8
+ }
9
+ export function getLoopsDir() {
10
+ return path.join(getDataDir(), "loops");
11
+ }
12
+ export function getLogsDir() {
13
+ return path.join(getDataDir(), "logs");
14
+ }
15
+ export function loopFile(id) {
16
+ return path.join(getLoopsDir(), `${id}.json`);
17
+ }
18
+ export function logFile(id) {
19
+ return path.join(getLogsDir(), `${id}.log`);
20
+ }
21
+ export function getPidFile() {
22
+ return path.join(getDataDir(), "daemon.pid");
23
+ }
24
+ export function getSignatureFile() {
25
+ return path.join(getDataDir(), "daemon.sig");
26
+ }
27
+ export function getDaemonLogFile() {
28
+ return path.join(getDataDir(), "daemon.log");
29
+ }
30
+ export function getBoardLogFile() {
31
+ return path.join(getDataDir(), "board.log");
32
+ }
33
+ export function getSocketPath() {
34
+ const suffix = crypto
35
+ .createHash("sha1")
36
+ .update(getDataDir())
37
+ .digest("hex")
38
+ .slice(0, 12);
39
+ if (process.platform === "win32") {
40
+ return `\\\\.\\pipe\\loop-cli-${os.userInfo().username}-${suffix}`;
41
+ }
42
+ return path.join(getDataDir(), `daemon-${suffix}.sock`);
43
+ }
@@ -0,0 +1,80 @@
1
+ import fs from "node:fs";
2
+ import { execa } from "execa";
3
+ import { formatDuration } from "../duration.js";
4
+ import { t } from "../i18n/index.js";
5
+ export function extractExitCode(error) {
6
+ return error && typeof error === "object" && "exitCode" in error
7
+ ? error.exitCode
8
+ : 1;
9
+ }
10
+ export async function executeCommand(command, commandArgs, cwd, logStream, signal, runNumber) {
11
+ const startedAt = new Date();
12
+ const header = t("loop.runHeader", { timestamp: startedAt.toLocaleString(), runNumber: runNumber ?? 0 });
13
+ logStream.write(header);
14
+ if (cwd && !fs.existsSync(cwd)) {
15
+ const endedAt = new Date();
16
+ logStream.write(t("loop.cwdMissingLog", { cwd }));
17
+ logStream.write(t("loop.exitMarker", { code: 1, duration: formatDuration(0) }));
18
+ return { exitCode: 1, duration: 0, startedAt, endedAt };
19
+ }
20
+ const child = execa(command, commandArgs, {
21
+ stdout: "pipe",
22
+ stderr: "pipe",
23
+ stdin: "ignore",
24
+ cwd: cwd || undefined,
25
+ cancelSignal: signal,
26
+ });
27
+ child.stdout.on("data", (chunk) => {
28
+ logStream.write(chunk);
29
+ });
30
+ child.stderr.on("data", (chunk) => {
31
+ logStream.write(chunk);
32
+ });
33
+ try {
34
+ const result = await child;
35
+ const endedAt = new Date();
36
+ const duration = endedAt.getTime() - startedAt.getTime();
37
+ logStream.write(t("loop.exitMarker", { code: String(result.exitCode), duration: formatDuration(duration) }));
38
+ return { exitCode: result.exitCode ?? 0, duration, startedAt, endedAt };
39
+ }
40
+ catch (error) {
41
+ const endedAt = new Date();
42
+ const duration = endedAt.getTime() - startedAt.getTime();
43
+ const exitCode = extractExitCode(error);
44
+ logStream.write(t("loop.exitMarker", { code: exitCode, duration: formatDuration(duration) }));
45
+ return { exitCode, duration, startedAt, endedAt };
46
+ }
47
+ }
48
+ export async function executeCommandForeground(command, commandArgs, logger, cwd = "") {
49
+ const startedAt = new Date();
50
+ logger.debug(t("loop.executing", { command: `${command} ${commandArgs.join(" ")}` }));
51
+ if (cwd && !fs.existsSync(cwd)) {
52
+ logger.error(t("loop.cwdMissing", { cwd }));
53
+ const endedAt = new Date();
54
+ return { exitCode: 1, duration: 0, startedAt, endedAt };
55
+ }
56
+ try {
57
+ const result = await execa(command, commandArgs, {
58
+ stdout: "inherit",
59
+ stderr: "inherit",
60
+ stdin: "inherit",
61
+ cwd: cwd || undefined,
62
+ });
63
+ const endedAt = new Date();
64
+ return {
65
+ exitCode: result.exitCode ?? 0,
66
+ duration: endedAt.getTime() - startedAt.getTime(),
67
+ startedAt,
68
+ endedAt,
69
+ };
70
+ }
71
+ catch (error) {
72
+ const endedAt = new Date();
73
+ return {
74
+ exitCode: extractExitCode(error),
75
+ duration: endedAt.getTime() - startedAt.getTime(),
76
+ startedAt,
77
+ endedAt,
78
+ };
79
+ }
80
+ }
@@ -0,0 +1,78 @@
1
+ import { formatDuration } from "../duration.js";
2
+ import { t } from "../i18n/index.js";
3
+ import { sleep } from "../shared/sleep.js";
4
+ import { executeCommandForeground } from "./command-runner.js";
5
+ export async function runLoop(options, logger, signal) {
6
+ const state = {
7
+ running: false,
8
+ runCount: 0,
9
+ shuttingDown: false,
10
+ };
11
+ const onSignal = () => {
12
+ state.shuttingDown = true;
13
+ logger.info(t("loop.shuttingDown"));
14
+ };
15
+ process.on("SIGINT", onSignal);
16
+ process.on("SIGTERM", onSignal);
17
+ try {
18
+ let isFirstRun = true;
19
+ while (!state.shuttingDown) {
20
+ if (options.maxRuns !== null &&
21
+ state.runCount >= options.maxRuns) {
22
+ logger.info(t("loop.completed", { runs: state.runCount }));
23
+ break;
24
+ }
25
+ if (isFirstRun && !options.immediate) {
26
+ logger.info(t("loop.waitingFirst", { duration: formatDuration(options.interval) }));
27
+ try {
28
+ await sleep(options.interval, signal);
29
+ }
30
+ catch {
31
+ break;
32
+ }
33
+ if (state.shuttingDown)
34
+ break;
35
+ }
36
+ isFirstRun = false;
37
+ state.running = true;
38
+ state.runCount++;
39
+ logger.info(t("loop.runSeparator", {
40
+ run: state.runCount,
41
+ maxRuns: options.maxRuns !== null ? `/${options.maxRuns}` : "",
42
+ }));
43
+ const result = await executeCommandForeground(options.command, options.commandArgs, logger, options.cwd);
44
+ state.running = false;
45
+ if (result.exitCode !== 0) {
46
+ logger.error(t("loop.commandFailed", { code: result.exitCode }));
47
+ }
48
+ if (options.verbose) {
49
+ logger.debug(t("loop.debugStartedAt", { timestamp: result.startedAt.toISOString() }));
50
+ logger.debug(t("loop.debugEndedAt", { timestamp: result.endedAt.toISOString() }));
51
+ logger.debug(t("loop.debugExitCode", { code: result.exitCode }));
52
+ logger.debug(t("loop.debugDuration", { duration: formatDuration(result.duration) }));
53
+ }
54
+ if (state.shuttingDown)
55
+ break;
56
+ if (options.maxRuns !== null &&
57
+ state.runCount >= options.maxRuns) {
58
+ logger.info(t("loop.completed", { runs: state.runCount }));
59
+ break;
60
+ }
61
+ const nextRun = new Date(Date.now() + options.interval);
62
+ logger.info(t("loop.nextRun", {
63
+ duration: formatDuration(options.interval),
64
+ time: nextRun.toLocaleTimeString(),
65
+ }));
66
+ try {
67
+ await sleep(options.interval, signal);
68
+ }
69
+ catch {
70
+ break;
71
+ }
72
+ }
73
+ }
74
+ finally {
75
+ process.removeListener("SIGINT", onSignal);
76
+ process.removeListener("SIGTERM", onSignal);
77
+ }
78
+ }
@@ -0,0 +1,29 @@
1
+ const RUN_HEADER_RE = /^\[Run #(\d+)/;
2
+ const EXIT_MARKER_RE = /^\[exit /;
3
+ export function splitLogByRuns(content) {
4
+ const lines = content.split("\n");
5
+ const runs = [];
6
+ let currentRun = null;
7
+ for (const line of lines) {
8
+ const headerMatch = line.match(RUN_HEADER_RE);
9
+ if (headerMatch) {
10
+ if (currentRun) {
11
+ runs.push(currentRun);
12
+ }
13
+ currentRun = { runNumber: parseInt(headerMatch[1], 10), lines: [] };
14
+ continue;
15
+ }
16
+ if (currentRun && EXIT_MARKER_RE.test(line)) {
17
+ runs.push(currentRun);
18
+ currentRun = null;
19
+ continue;
20
+ }
21
+ if (currentRun) {
22
+ currentRun.lines.push(line);
23
+ }
24
+ }
25
+ if (currentRun) {
26
+ runs.push(currentRun);
27
+ }
28
+ return runs;
29
+ }
@@ -0,0 +1,25 @@
1
+ import fs from "node:fs";
2
+ import { MAX_LOG_BYTES, MAX_LOG_GENERATIONS } from "../config/constants.js";
3
+ export function rotateLogIfNeeded(logPath, currentStream) {
4
+ if (!fs.existsSync(logPath)) {
5
+ return currentStream;
6
+ }
7
+ const size = fs.statSync(logPath).size;
8
+ if (size < MAX_LOG_BYTES) {
9
+ return currentStream;
10
+ }
11
+ currentStream?.end();
12
+ for (let index = MAX_LOG_GENERATIONS; index >= 1; index--) {
13
+ const currentPath = `${logPath}.${index}`;
14
+ if (!fs.existsSync(currentPath)) {
15
+ continue;
16
+ }
17
+ if (index === MAX_LOG_GENERATIONS) {
18
+ fs.unlinkSync(currentPath);
19
+ continue;
20
+ }
21
+ fs.renameSync(currentPath, `${logPath}.${index + 1}`);
22
+ }
23
+ fs.renameSync(logPath, `${logPath}.1`);
24
+ return fs.createWriteStream(logPath, { flags: "a" });
25
+ }