@qodalis/cli-cron 2.0.0-beta.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/package.json +34 -0
- package/public-api.d.mts +26 -0
- package/public-api.d.ts +26 -0
- package/public-api.js +237 -0
- package/public-api.js.map +1 -0
- package/public-api.mjs +230 -0
- package/public-api.mjs.map +1 -0
- package/umd/cli-entrypoint.global.js +3020 -0
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@qodalis/cli-cron",
|
|
3
|
+
"version": "2.0.0-beta.1",
|
|
4
|
+
"description": "Schedule commands to run on a repeating interval",
|
|
5
|
+
"author": "Qodalis Solutions",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/qodalis-solutions/web-cli"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://qodalis.com",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"cli",
|
|
14
|
+
"qodalis",
|
|
15
|
+
"terminal",
|
|
16
|
+
"cron"
|
|
17
|
+
],
|
|
18
|
+
"umd": "./umd/index.js",
|
|
19
|
+
"unpkg": "./umd/index.js",
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@qodalis/cli-core": "2.0.0-beta.1"
|
|
22
|
+
},
|
|
23
|
+
"sideEffects": false,
|
|
24
|
+
"main": "./public-api.js",
|
|
25
|
+
"module": "./public-api.mjs",
|
|
26
|
+
"types": "./public-api.d.ts",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./public-api.d.ts",
|
|
30
|
+
"import": "./public-api.mjs",
|
|
31
|
+
"require": "./public-api.js"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
package/public-api.d.mts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ICliCommandProcessor, ICliCommandAuthor, CliProcessorMetadata, CliStateConfiguration, ICliCommandChildProcessor, ICliExecutionContext, CliProcessCommand, ICliModule } from '@qodalis/cli-core';
|
|
2
|
+
|
|
3
|
+
declare class CliCronCommandProcessor implements ICliCommandProcessor {
|
|
4
|
+
command: string;
|
|
5
|
+
description: string;
|
|
6
|
+
author: ICliCommandAuthor;
|
|
7
|
+
metadata: CliProcessorMetadata;
|
|
8
|
+
stateConfiguration: CliStateConfiguration;
|
|
9
|
+
private timers;
|
|
10
|
+
processors: ICliCommandChildProcessor[];
|
|
11
|
+
initialize(context: ICliExecutionContext): Promise<void>;
|
|
12
|
+
processCommand(_: CliProcessCommand, context: ICliExecutionContext): Promise<void>;
|
|
13
|
+
private startJob;
|
|
14
|
+
private stopJob;
|
|
15
|
+
private setEnabled;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
declare function parseInterval(input: string): number | null;
|
|
19
|
+
declare function formatInterval(ms: number): string;
|
|
20
|
+
|
|
21
|
+
declare const LIBRARY_VERSION = "2.0.0-beta.1";
|
|
22
|
+
declare const API_VERSION = 2;
|
|
23
|
+
|
|
24
|
+
declare const cronModule: ICliModule;
|
|
25
|
+
|
|
26
|
+
export { API_VERSION, CliCronCommandProcessor, LIBRARY_VERSION, cronModule, formatInterval, parseInterval };
|
package/public-api.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ICliCommandProcessor, ICliCommandAuthor, CliProcessorMetadata, CliStateConfiguration, ICliCommandChildProcessor, ICliExecutionContext, CliProcessCommand, ICliModule } from '@qodalis/cli-core';
|
|
2
|
+
|
|
3
|
+
declare class CliCronCommandProcessor implements ICliCommandProcessor {
|
|
4
|
+
command: string;
|
|
5
|
+
description: string;
|
|
6
|
+
author: ICliCommandAuthor;
|
|
7
|
+
metadata: CliProcessorMetadata;
|
|
8
|
+
stateConfiguration: CliStateConfiguration;
|
|
9
|
+
private timers;
|
|
10
|
+
processors: ICliCommandChildProcessor[];
|
|
11
|
+
initialize(context: ICliExecutionContext): Promise<void>;
|
|
12
|
+
processCommand(_: CliProcessCommand, context: ICliExecutionContext): Promise<void>;
|
|
13
|
+
private startJob;
|
|
14
|
+
private stopJob;
|
|
15
|
+
private setEnabled;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
declare function parseInterval(input: string): number | null;
|
|
19
|
+
declare function formatInterval(ms: number): string;
|
|
20
|
+
|
|
21
|
+
declare const LIBRARY_VERSION = "2.0.0-beta.1";
|
|
22
|
+
declare const API_VERSION = 2;
|
|
23
|
+
|
|
24
|
+
declare const cronModule: ICliModule;
|
|
25
|
+
|
|
26
|
+
export { API_VERSION, CliCronCommandProcessor, LIBRARY_VERSION, cronModule, formatInterval, parseInterval };
|
package/public-api.js
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var cliCore = require('@qodalis/cli-core');
|
|
4
|
+
|
|
5
|
+
// src/lib/processors/cli-cron-command-processor.ts
|
|
6
|
+
|
|
7
|
+
// src/lib/cron-utils.ts
|
|
8
|
+
var MIN_INTERVAL_MS = 1e4;
|
|
9
|
+
function parseInterval(input) {
|
|
10
|
+
if (!input.trim()) return null;
|
|
11
|
+
if (/^\d+$/.test(input.trim())) {
|
|
12
|
+
const ms = parseInt(input.trim(), 10) * 1e3;
|
|
13
|
+
return ms >= MIN_INTERVAL_MS ? ms : null;
|
|
14
|
+
}
|
|
15
|
+
const pattern = /^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$/;
|
|
16
|
+
const match = input.trim().match(pattern);
|
|
17
|
+
if (!match || match[0] === "") return null;
|
|
18
|
+
const h = parseInt(match[1] ?? "0", 10);
|
|
19
|
+
const m = parseInt(match[2] ?? "0", 10);
|
|
20
|
+
const s = parseInt(match[3] ?? "0", 10);
|
|
21
|
+
const total = h * 36e5 + m * 6e4 + s * 1e3;
|
|
22
|
+
return total >= MIN_INTERVAL_MS ? total : null;
|
|
23
|
+
}
|
|
24
|
+
function formatInterval(ms) {
|
|
25
|
+
const h = Math.floor(ms / 36e5);
|
|
26
|
+
const m = Math.floor(ms % 36e5 / 6e4);
|
|
27
|
+
const s = Math.floor(ms % 6e4 / 1e3);
|
|
28
|
+
const parts = [];
|
|
29
|
+
if (h > 0) parts.push(`${h}h`);
|
|
30
|
+
if (m > 0) parts.push(`${m}m`);
|
|
31
|
+
if (s > 0) parts.push(`${s}s`);
|
|
32
|
+
return parts.join(" ") || "0s";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/lib/processors/cli-cron-command-processor.ts
|
|
36
|
+
var CliCronCommandProcessor = class {
|
|
37
|
+
constructor() {
|
|
38
|
+
this.command = "cron";
|
|
39
|
+
this.description = "Schedule commands to run on a repeating interval";
|
|
40
|
+
this.author = cliCore.DefaultLibraryAuthor;
|
|
41
|
+
this.metadata = { icon: cliCore.CliIcon.Clock };
|
|
42
|
+
this.stateConfiguration = {
|
|
43
|
+
storeName: "cron",
|
|
44
|
+
initialState: { jobs: [] }
|
|
45
|
+
};
|
|
46
|
+
this.timers = /* @__PURE__ */ new Map();
|
|
47
|
+
this.processors = [
|
|
48
|
+
{
|
|
49
|
+
command: "add",
|
|
50
|
+
description: "Add a cron job: cron add <name> <interval> <command>",
|
|
51
|
+
acceptsRawInput: true,
|
|
52
|
+
valueRequired: true,
|
|
53
|
+
processCommand: async (cmd, context) => {
|
|
54
|
+
const parts = (cmd.value ?? "").trim().split(/\s+/);
|
|
55
|
+
if (parts.length < 3) {
|
|
56
|
+
context.writer.writeError("Usage: cron add <name> <interval> <command>");
|
|
57
|
+
context.writer.writeln(" Example: cron add ping 5m echo heartbeat");
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const [name, intervalStr, ...cmdParts] = parts;
|
|
61
|
+
const command = cmdParts.join(" ");
|
|
62
|
+
const intervalMs = parseInterval(intervalStr);
|
|
63
|
+
if (!intervalMs) {
|
|
64
|
+
context.writer.writeError(`Invalid interval "${intervalStr}". Min 10s. Examples: 10s, 5m, 1h`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const state = context.state.getState();
|
|
68
|
+
if (state.jobs.find((j) => j.name === name)) {
|
|
69
|
+
context.writer.writeError(`Job "${name}" already exists. Remove it first: cron remove ${name}`);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const job = {
|
|
73
|
+
id: `cron_${Date.now()}`,
|
|
74
|
+
name,
|
|
75
|
+
command,
|
|
76
|
+
intervalMs,
|
|
77
|
+
enabled: true,
|
|
78
|
+
runCount: 0
|
|
79
|
+
};
|
|
80
|
+
const updated = [...state.jobs, job];
|
|
81
|
+
context.state.updateState({ jobs: updated });
|
|
82
|
+
await context.state.persist();
|
|
83
|
+
this.startJob(job, context);
|
|
84
|
+
context.writer.writeSuccess(
|
|
85
|
+
`Job "${name}" added \u2014 runs every ${formatInterval(intervalMs)}: ${command}`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
command: "list",
|
|
91
|
+
description: "List all cron jobs",
|
|
92
|
+
aliases: ["ls"],
|
|
93
|
+
processCommand: async (_, context) => {
|
|
94
|
+
const { jobs } = context.state.getState();
|
|
95
|
+
if (!jobs.length) {
|
|
96
|
+
context.writer.writeInfo("No cron jobs. Use: cron add <name> <interval> <command>");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
context.writer.writeln(context.writer.wrapInColor("Cron Jobs:", cliCore.CliForegroundColor.Yellow));
|
|
100
|
+
for (const job of jobs) {
|
|
101
|
+
const status = job.enabled ? context.writer.wrapInColor("enabled ", cliCore.CliForegroundColor.Green) : context.writer.wrapInColor("disabled", cliCore.CliForegroundColor.Red);
|
|
102
|
+
context.writer.writeln(
|
|
103
|
+
` ${context.writer.wrapInColor(job.name.padEnd(12), cliCore.CliForegroundColor.Cyan)}[${status}] every ${formatInterval(job.intervalMs)} \u2192 ${job.command} (ran ${job.runCount}x)`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
command: "remove",
|
|
110
|
+
description: "Remove a cron job: cron remove <name>",
|
|
111
|
+
aliases: ["rm", "delete"],
|
|
112
|
+
acceptsRawInput: true,
|
|
113
|
+
valueRequired: true,
|
|
114
|
+
processCommand: async (cmd, context) => {
|
|
115
|
+
const name = (cmd.value ?? "").trim();
|
|
116
|
+
const state = context.state.getState();
|
|
117
|
+
const idx = state.jobs.findIndex((j) => j.name === name);
|
|
118
|
+
if (idx === -1) {
|
|
119
|
+
context.writer.writeError(`Job "${name}" not found`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const job = state.jobs[idx];
|
|
123
|
+
this.stopJob(job.id);
|
|
124
|
+
const updated = state.jobs.filter((j) => j.name !== name);
|
|
125
|
+
context.state.updateState({ jobs: updated });
|
|
126
|
+
await context.state.persist();
|
|
127
|
+
context.writer.writeSuccess(`Job "${name}" removed`);
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
command: "enable",
|
|
132
|
+
description: "Enable a paused cron job",
|
|
133
|
+
acceptsRawInput: true,
|
|
134
|
+
valueRequired: true,
|
|
135
|
+
processCommand: async (cmd, context) => {
|
|
136
|
+
await this.setEnabled((cmd.value ?? "").trim(), true, context);
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
command: "disable",
|
|
141
|
+
description: "Pause a cron job without removing it",
|
|
142
|
+
acceptsRawInput: true,
|
|
143
|
+
valueRequired: true,
|
|
144
|
+
processCommand: async (cmd, context) => {
|
|
145
|
+
await this.setEnabled((cmd.value ?? "").trim(), false, context);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
];
|
|
149
|
+
}
|
|
150
|
+
async initialize(context) {
|
|
151
|
+
const { jobs } = context.state.getState();
|
|
152
|
+
for (const job of jobs.filter((j) => j.enabled)) {
|
|
153
|
+
this.startJob(job, context);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async processCommand(_, context) {
|
|
157
|
+
const { jobs } = context.state.getState();
|
|
158
|
+
context.writer.writeln(`${jobs.length} job(s). Sub-commands: add, list, remove, enable, disable`);
|
|
159
|
+
}
|
|
160
|
+
startJob(job, context) {
|
|
161
|
+
if (this.timers.has(job.id)) return;
|
|
162
|
+
const timer = setInterval(async () => {
|
|
163
|
+
if (!job.enabled) return;
|
|
164
|
+
try {
|
|
165
|
+
await context.executor.executeCommand(job.command, context);
|
|
166
|
+
job.runCount++;
|
|
167
|
+
job.lastRun = Date.now();
|
|
168
|
+
const state = context.state.getState();
|
|
169
|
+
const updated = state.jobs.map(
|
|
170
|
+
(j) => j.id === job.id ? { ...j, runCount: job.runCount, lastRun: job.lastRun } : j
|
|
171
|
+
);
|
|
172
|
+
context.state.updateState({ jobs: updated });
|
|
173
|
+
} catch (e) {
|
|
174
|
+
console.error(`Cron job "${job.name}" failed:`, e);
|
|
175
|
+
}
|
|
176
|
+
}, job.intervalMs);
|
|
177
|
+
this.timers.set(job.id, timer);
|
|
178
|
+
}
|
|
179
|
+
stopJob(id) {
|
|
180
|
+
const timer = this.timers.get(id);
|
|
181
|
+
if (timer) {
|
|
182
|
+
clearInterval(timer);
|
|
183
|
+
this.timers.delete(id);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
async setEnabled(name, enabled, context) {
|
|
187
|
+
const state = context.state.getState();
|
|
188
|
+
const jobIdx = state.jobs.findIndex((j) => j.name === name);
|
|
189
|
+
if (jobIdx === -1) {
|
|
190
|
+
context.writer.writeError(`Job "${name}" not found`);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const job = state.jobs[jobIdx];
|
|
194
|
+
job.enabled = enabled;
|
|
195
|
+
if (enabled) {
|
|
196
|
+
this.startJob(job, context);
|
|
197
|
+
} else {
|
|
198
|
+
this.stopJob(job.id);
|
|
199
|
+
}
|
|
200
|
+
const updated = state.jobs.map((j) => j.name === name ? { ...j, enabled } : j);
|
|
201
|
+
context.state.updateState({ jobs: updated });
|
|
202
|
+
await context.state.persist();
|
|
203
|
+
context.writer.writeSuccess(`Job "${name}" ${enabled ? "enabled" : "disabled"}`);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// src/lib/version.ts
|
|
208
|
+
var LIBRARY_VERSION = "2.0.0-beta.1";
|
|
209
|
+
var API_VERSION = 2;
|
|
210
|
+
|
|
211
|
+
// src/public-api.ts
|
|
212
|
+
var cronModule = {
|
|
213
|
+
apiVersion: API_VERSION,
|
|
214
|
+
name: "@qodalis/cli-cron",
|
|
215
|
+
processors: [new CliCronCommandProcessor()],
|
|
216
|
+
translations: {
|
|
217
|
+
es: { "cli.cron.description": "Programar comandos para ejecuci\xF3n peri\xF3dica" },
|
|
218
|
+
fr: { "cli.cron.description": "Planifier des commandes \xE0 ex\xE9cution p\xE9riodique" },
|
|
219
|
+
de: { "cli.cron.description": "Befehle f\xFCr regelm\xE4\xDFige Ausf\xFChrung planen" },
|
|
220
|
+
pt: { "cli.cron.description": "Agendar comandos para execu\xE7\xE3o peri\xF3dica" },
|
|
221
|
+
it: { "cli.cron.description": "Pianificare comandi a esecuzione periodica" },
|
|
222
|
+
ja: { "cli.cron.description": "\u30B3\u30DE\u30F3\u30C9\u306E\u5B9A\u671F\u5B9F\u884C\u3092\u30B9\u30B1\u30B8\u30E5\u30FC\u30EB" },
|
|
223
|
+
ko: { "cli.cron.description": "\uBA85\uB839\uC5B4\uB97C \uC8FC\uAE30\uC801\uC73C\uB85C \uC2E4\uD589\uD558\uB3C4\uB85D \uC608\uC57D" },
|
|
224
|
+
zh: { "cli.cron.description": "\u5B9A\u65F6\u91CD\u590D\u6267\u884C\u547D\u4EE4" },
|
|
225
|
+
ru: { "cli.cron.description": "\u041F\u043B\u0430\u043D\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0435 \u043F\u0435\u0440\u0438\u043E\u0434\u0438\u0447\u0435\u0441\u043A\u043E\u0433\u043E \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u044F \u043A\u043E\u043C\u0430\u043D\u0434" },
|
|
226
|
+
ro: { "cli.cron.description": "Programeaz\u0103 comenzi pentru execu\u021Bie periodic\u0103" }
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
exports.API_VERSION = API_VERSION;
|
|
231
|
+
exports.CliCronCommandProcessor = CliCronCommandProcessor;
|
|
232
|
+
exports.LIBRARY_VERSION = LIBRARY_VERSION;
|
|
233
|
+
exports.cronModule = cronModule;
|
|
234
|
+
exports.formatInterval = formatInterval;
|
|
235
|
+
exports.parseInterval = parseInterval;
|
|
236
|
+
//# sourceMappingURL=public-api.js.map
|
|
237
|
+
//# sourceMappingURL=public-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../packages/plugins/cron/src/lib/cron-utils.ts","../../packages/plugins/cron/src/lib/processors/cli-cron-command-processor.ts","../../packages/plugins/cron/src/lib/version.ts","../../packages/plugins/cron/src/public-api.ts"],"names":["DefaultLibraryAuthor","CliIcon","CliForegroundColor"],"mappings":"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","file":"public-api.js","sourcesContent":["const MIN_INTERVAL_MS = 10_000; // 10 seconds minimum\n\nexport function parseInterval(input: string): number | null {\n if (!input.trim()) return null;\n if (/^\\d+$/.test(input.trim())) {\n const ms = parseInt(input.trim(), 10) * 1000;\n return ms >= MIN_INTERVAL_MS ? ms : null;\n }\n const pattern = /^(?:(\\d+)h)?(?:(\\d+)m)?(?:(\\d+)s)?$/;\n const match = input.trim().match(pattern);\n if (!match || match[0] === '') return null;\n const h = parseInt(match[1] ?? '0', 10);\n const m = parseInt(match[2] ?? '0', 10);\n const s = parseInt(match[3] ?? '0', 10);\n const total = h * 3_600_000 + m * 60_000 + s * 1_000;\n return total >= MIN_INTERVAL_MS ? total : null;\n}\n\nexport function formatInterval(ms: number): string {\n const h = Math.floor(ms / 3_600_000);\n const m = Math.floor((ms % 3_600_000) / 60_000);\n const s = Math.floor((ms % 60_000) / 1_000);\n const parts: string[] = [];\n if (h > 0) parts.push(`${h}h`);\n if (m > 0) parts.push(`${m}m`);\n if (s > 0) parts.push(`${s}s`);\n return parts.join(' ') || '0s';\n}\n","import {\n ICliCommandProcessor,\n ICliExecutionContext,\n CliProcessCommand,\n ICliCommandAuthor,\n CliProcessorMetadata,\n CliIcon,\n CliStateConfiguration,\n DefaultLibraryAuthor,\n CliForegroundColor,\n ICliCommandChildProcessor,\n} from '@qodalis/cli-core';\nimport { parseInterval, formatInterval } from '../cron-utils';\n\ninterface CronJob {\n id: string;\n name: string;\n command: string;\n intervalMs: number;\n enabled: boolean;\n runCount: number;\n lastRun?: number;\n}\n\ninterface CronState {\n jobs: CronJob[];\n}\n\nexport class CliCronCommandProcessor implements ICliCommandProcessor {\n command = 'cron';\n description = 'Schedule commands to run on a repeating interval';\n author: ICliCommandAuthor = DefaultLibraryAuthor;\n metadata: CliProcessorMetadata = { icon: CliIcon.Clock };\n\n stateConfiguration: CliStateConfiguration = {\n storeName: 'cron',\n initialState: { jobs: [] } as CronState,\n };\n\n private timers = new Map<string, ReturnType<typeof setInterval>>();\n\n processors: ICliCommandChildProcessor[] = [\n {\n command: 'add',\n description: 'Add a cron job: cron add <name> <interval> <command>',\n acceptsRawInput: true,\n valueRequired: true,\n processCommand: async (cmd: CliProcessCommand, context: ICliExecutionContext) => {\n const parts = (cmd.value ?? '').trim().split(/\\s+/);\n if (parts.length < 3) {\n context.writer.writeError('Usage: cron add <name> <interval> <command>');\n context.writer.writeln(' Example: cron add ping 5m echo heartbeat');\n return;\n }\n const [name, intervalStr, ...cmdParts] = parts;\n const command = cmdParts.join(' ');\n const intervalMs = parseInterval(intervalStr);\n if (!intervalMs) {\n context.writer.writeError(`Invalid interval \"${intervalStr}\". Min 10s. Examples: 10s, 5m, 1h`);\n return;\n }\n const state = context.state.getState<CronState>();\n if (state.jobs.find((j) => j.name === name)) {\n context.writer.writeError(`Job \"${name}\" already exists. Remove it first: cron remove ${name}`);\n return;\n }\n const job: CronJob = {\n id: `cron_${Date.now()}`,\n name,\n command,\n intervalMs,\n enabled: true,\n runCount: 0,\n };\n const updated = [...state.jobs, job];\n context.state.updateState({ jobs: updated });\n await context.state.persist();\n this.startJob(job, context);\n context.writer.writeSuccess(\n `Job \"${name}\" added — runs every ${formatInterval(intervalMs)}: ${command}`,\n );\n },\n },\n {\n command: 'list',\n description: 'List all cron jobs',\n aliases: ['ls'],\n processCommand: async (_: CliProcessCommand, context: ICliExecutionContext) => {\n const { jobs } = context.state.getState<CronState>();\n if (!jobs.length) {\n context.writer.writeInfo('No cron jobs. Use: cron add <name> <interval> <command>');\n return;\n }\n context.writer.writeln(context.writer.wrapInColor('Cron Jobs:', CliForegroundColor.Yellow));\n for (const job of jobs) {\n const status = job.enabled\n ? context.writer.wrapInColor('enabled ', CliForegroundColor.Green)\n : context.writer.wrapInColor('disabled', CliForegroundColor.Red);\n context.writer.writeln(\n ` ${context.writer.wrapInColor(job.name.padEnd(12), CliForegroundColor.Cyan)}` +\n `[${status}] every ${formatInterval(job.intervalMs)} \\u2192 ${job.command} (ran ${job.runCount}x)`,\n );\n }\n },\n },\n {\n command: 'remove',\n description: 'Remove a cron job: cron remove <name>',\n aliases: ['rm', 'delete'],\n acceptsRawInput: true,\n valueRequired: true,\n processCommand: async (cmd: CliProcessCommand, context: ICliExecutionContext) => {\n const name = (cmd.value ?? '').trim();\n const state = context.state.getState<CronState>();\n const idx = state.jobs.findIndex((j) => j.name === name);\n if (idx === -1) {\n context.writer.writeError(`Job \"${name}\" not found`);\n return;\n }\n const job = state.jobs[idx];\n this.stopJob(job.id);\n const updated = state.jobs.filter((j) => j.name !== name);\n context.state.updateState({ jobs: updated });\n await context.state.persist();\n context.writer.writeSuccess(`Job \"${name}\" removed`);\n },\n },\n {\n command: 'enable',\n description: 'Enable a paused cron job',\n acceptsRawInput: true,\n valueRequired: true,\n processCommand: async (cmd: CliProcessCommand, context: ICliExecutionContext) => {\n await this.setEnabled((cmd.value ?? '').trim(), true, context);\n },\n },\n {\n command: 'disable',\n description: 'Pause a cron job without removing it',\n acceptsRawInput: true,\n valueRequired: true,\n processCommand: async (cmd: CliProcessCommand, context: ICliExecutionContext) => {\n await this.setEnabled((cmd.value ?? '').trim(), false, context);\n },\n },\n ];\n\n async initialize(context: ICliExecutionContext): Promise<void> {\n const { jobs } = context.state.getState<CronState>();\n for (const job of jobs.filter((j) => j.enabled)) {\n this.startJob(job, context);\n }\n }\n\n async processCommand(_: CliProcessCommand, context: ICliExecutionContext): Promise<void> {\n const { jobs } = context.state.getState<CronState>();\n context.writer.writeln(`${jobs.length} job(s). Sub-commands: add, list, remove, enable, disable`);\n }\n\n private startJob(job: CronJob, context: ICliExecutionContext): void {\n if (this.timers.has(job.id)) return; // already running\n const timer = setInterval(async () => {\n if (!job.enabled) return;\n try {\n await context.executor.executeCommand(job.command, context);\n job.runCount++;\n job.lastRun = Date.now();\n const state = context.state.getState<CronState>();\n const updated = state.jobs.map((j) =>\n j.id === job.id ? { ...j, runCount: job.runCount, lastRun: job.lastRun } : j,\n );\n context.state.updateState({ jobs: updated });\n } catch (e) {\n console.error(`Cron job \"${job.name}\" failed:`, e);\n }\n }, job.intervalMs);\n this.timers.set(job.id, timer);\n }\n\n private stopJob(id: string): void {\n const timer = this.timers.get(id);\n if (timer) {\n clearInterval(timer);\n this.timers.delete(id);\n }\n }\n\n private async setEnabled(name: string, enabled: boolean, context: ICliExecutionContext): Promise<void> {\n const state = context.state.getState<CronState>();\n const jobIdx = state.jobs.findIndex((j) => j.name === name);\n if (jobIdx === -1) {\n context.writer.writeError(`Job \"${name}\" not found`);\n return;\n }\n const job = state.jobs[jobIdx];\n job.enabled = enabled;\n if (enabled) {\n this.startJob(job, context);\n } else {\n this.stopJob(job.id);\n }\n const updated = state.jobs.map((j) => j.name === name ? { ...j, enabled } : j);\n context.state.updateState({ jobs: updated });\n await context.state.persist();\n context.writer.writeSuccess(`Job \"${name}\" ${enabled ? 'enabled' : 'disabled'}`);\n }\n}\n","\n// Automatically generated during build\nexport const LIBRARY_VERSION = '2.0.0-beta.1';\nexport const API_VERSION = 2;\n ","/*\n * Public API Surface of cron\n */\n\nexport * from './lib/processors/cli-cron-command-processor';\nexport * from './lib/cron-utils';\nexport * from './lib/version';\n\nimport { ICliModule } from '@qodalis/cli-core';\nimport { CliCronCommandProcessor } from './lib/processors/cli-cron-command-processor';\nimport { API_VERSION } from './lib/version';\n\nexport const cronModule: ICliModule = {\n apiVersion: API_VERSION,\n name: '@qodalis/cli-cron',\n processors: [new CliCronCommandProcessor()],\n translations: {\n es: { 'cli.cron.description': 'Programar comandos para ejecución periódica' },\n fr: { 'cli.cron.description': 'Planifier des commandes à exécution périodique' },\n de: { 'cli.cron.description': 'Befehle für regelmäßige Ausführung planen' },\n pt: { 'cli.cron.description': 'Agendar comandos para execução periódica' },\n it: { 'cli.cron.description': 'Pianificare comandi a esecuzione periodica' },\n ja: { 'cli.cron.description': 'コマンドの定期実行をスケジュール' },\n ko: { 'cli.cron.description': '명령어를 주기적으로 실행하도록 예약' },\n zh: { 'cli.cron.description': '定时重复执行命令' },\n ru: { 'cli.cron.description': 'Планирование периодического выполнения команд' },\n ro: { 'cli.cron.description': 'Programează comenzi pentru execuție periodică' },\n },\n};\n"]}
|
package/public-api.mjs
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { DefaultLibraryAuthor, CliIcon, CliForegroundColor } from '@qodalis/cli-core';
|
|
2
|
+
|
|
3
|
+
// src/lib/processors/cli-cron-command-processor.ts
|
|
4
|
+
|
|
5
|
+
// src/lib/cron-utils.ts
|
|
6
|
+
var MIN_INTERVAL_MS = 1e4;
|
|
7
|
+
function parseInterval(input) {
|
|
8
|
+
if (!input.trim()) return null;
|
|
9
|
+
if (/^\d+$/.test(input.trim())) {
|
|
10
|
+
const ms = parseInt(input.trim(), 10) * 1e3;
|
|
11
|
+
return ms >= MIN_INTERVAL_MS ? ms : null;
|
|
12
|
+
}
|
|
13
|
+
const pattern = /^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$/;
|
|
14
|
+
const match = input.trim().match(pattern);
|
|
15
|
+
if (!match || match[0] === "") return null;
|
|
16
|
+
const h = parseInt(match[1] ?? "0", 10);
|
|
17
|
+
const m = parseInt(match[2] ?? "0", 10);
|
|
18
|
+
const s = parseInt(match[3] ?? "0", 10);
|
|
19
|
+
const total = h * 36e5 + m * 6e4 + s * 1e3;
|
|
20
|
+
return total >= MIN_INTERVAL_MS ? total : null;
|
|
21
|
+
}
|
|
22
|
+
function formatInterval(ms) {
|
|
23
|
+
const h = Math.floor(ms / 36e5);
|
|
24
|
+
const m = Math.floor(ms % 36e5 / 6e4);
|
|
25
|
+
const s = Math.floor(ms % 6e4 / 1e3);
|
|
26
|
+
const parts = [];
|
|
27
|
+
if (h > 0) parts.push(`${h}h`);
|
|
28
|
+
if (m > 0) parts.push(`${m}m`);
|
|
29
|
+
if (s > 0) parts.push(`${s}s`);
|
|
30
|
+
return parts.join(" ") || "0s";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/lib/processors/cli-cron-command-processor.ts
|
|
34
|
+
var CliCronCommandProcessor = class {
|
|
35
|
+
constructor() {
|
|
36
|
+
this.command = "cron";
|
|
37
|
+
this.description = "Schedule commands to run on a repeating interval";
|
|
38
|
+
this.author = DefaultLibraryAuthor;
|
|
39
|
+
this.metadata = { icon: CliIcon.Clock };
|
|
40
|
+
this.stateConfiguration = {
|
|
41
|
+
storeName: "cron",
|
|
42
|
+
initialState: { jobs: [] }
|
|
43
|
+
};
|
|
44
|
+
this.timers = /* @__PURE__ */ new Map();
|
|
45
|
+
this.processors = [
|
|
46
|
+
{
|
|
47
|
+
command: "add",
|
|
48
|
+
description: "Add a cron job: cron add <name> <interval> <command>",
|
|
49
|
+
acceptsRawInput: true,
|
|
50
|
+
valueRequired: true,
|
|
51
|
+
processCommand: async (cmd, context) => {
|
|
52
|
+
const parts = (cmd.value ?? "").trim().split(/\s+/);
|
|
53
|
+
if (parts.length < 3) {
|
|
54
|
+
context.writer.writeError("Usage: cron add <name> <interval> <command>");
|
|
55
|
+
context.writer.writeln(" Example: cron add ping 5m echo heartbeat");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const [name, intervalStr, ...cmdParts] = parts;
|
|
59
|
+
const command = cmdParts.join(" ");
|
|
60
|
+
const intervalMs = parseInterval(intervalStr);
|
|
61
|
+
if (!intervalMs) {
|
|
62
|
+
context.writer.writeError(`Invalid interval "${intervalStr}". Min 10s. Examples: 10s, 5m, 1h`);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const state = context.state.getState();
|
|
66
|
+
if (state.jobs.find((j) => j.name === name)) {
|
|
67
|
+
context.writer.writeError(`Job "${name}" already exists. Remove it first: cron remove ${name}`);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const job = {
|
|
71
|
+
id: `cron_${Date.now()}`,
|
|
72
|
+
name,
|
|
73
|
+
command,
|
|
74
|
+
intervalMs,
|
|
75
|
+
enabled: true,
|
|
76
|
+
runCount: 0
|
|
77
|
+
};
|
|
78
|
+
const updated = [...state.jobs, job];
|
|
79
|
+
context.state.updateState({ jobs: updated });
|
|
80
|
+
await context.state.persist();
|
|
81
|
+
this.startJob(job, context);
|
|
82
|
+
context.writer.writeSuccess(
|
|
83
|
+
`Job "${name}" added \u2014 runs every ${formatInterval(intervalMs)}: ${command}`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
command: "list",
|
|
89
|
+
description: "List all cron jobs",
|
|
90
|
+
aliases: ["ls"],
|
|
91
|
+
processCommand: async (_, context) => {
|
|
92
|
+
const { jobs } = context.state.getState();
|
|
93
|
+
if (!jobs.length) {
|
|
94
|
+
context.writer.writeInfo("No cron jobs. Use: cron add <name> <interval> <command>");
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
context.writer.writeln(context.writer.wrapInColor("Cron Jobs:", CliForegroundColor.Yellow));
|
|
98
|
+
for (const job of jobs) {
|
|
99
|
+
const status = job.enabled ? context.writer.wrapInColor("enabled ", CliForegroundColor.Green) : context.writer.wrapInColor("disabled", CliForegroundColor.Red);
|
|
100
|
+
context.writer.writeln(
|
|
101
|
+
` ${context.writer.wrapInColor(job.name.padEnd(12), CliForegroundColor.Cyan)}[${status}] every ${formatInterval(job.intervalMs)} \u2192 ${job.command} (ran ${job.runCount}x)`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
command: "remove",
|
|
108
|
+
description: "Remove a cron job: cron remove <name>",
|
|
109
|
+
aliases: ["rm", "delete"],
|
|
110
|
+
acceptsRawInput: true,
|
|
111
|
+
valueRequired: true,
|
|
112
|
+
processCommand: async (cmd, context) => {
|
|
113
|
+
const name = (cmd.value ?? "").trim();
|
|
114
|
+
const state = context.state.getState();
|
|
115
|
+
const idx = state.jobs.findIndex((j) => j.name === name);
|
|
116
|
+
if (idx === -1) {
|
|
117
|
+
context.writer.writeError(`Job "${name}" not found`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const job = state.jobs[idx];
|
|
121
|
+
this.stopJob(job.id);
|
|
122
|
+
const updated = state.jobs.filter((j) => j.name !== name);
|
|
123
|
+
context.state.updateState({ jobs: updated });
|
|
124
|
+
await context.state.persist();
|
|
125
|
+
context.writer.writeSuccess(`Job "${name}" removed`);
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
command: "enable",
|
|
130
|
+
description: "Enable a paused cron job",
|
|
131
|
+
acceptsRawInput: true,
|
|
132
|
+
valueRequired: true,
|
|
133
|
+
processCommand: async (cmd, context) => {
|
|
134
|
+
await this.setEnabled((cmd.value ?? "").trim(), true, context);
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
command: "disable",
|
|
139
|
+
description: "Pause a cron job without removing it",
|
|
140
|
+
acceptsRawInput: true,
|
|
141
|
+
valueRequired: true,
|
|
142
|
+
processCommand: async (cmd, context) => {
|
|
143
|
+
await this.setEnabled((cmd.value ?? "").trim(), false, context);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
];
|
|
147
|
+
}
|
|
148
|
+
async initialize(context) {
|
|
149
|
+
const { jobs } = context.state.getState();
|
|
150
|
+
for (const job of jobs.filter((j) => j.enabled)) {
|
|
151
|
+
this.startJob(job, context);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
async processCommand(_, context) {
|
|
155
|
+
const { jobs } = context.state.getState();
|
|
156
|
+
context.writer.writeln(`${jobs.length} job(s). Sub-commands: add, list, remove, enable, disable`);
|
|
157
|
+
}
|
|
158
|
+
startJob(job, context) {
|
|
159
|
+
if (this.timers.has(job.id)) return;
|
|
160
|
+
const timer = setInterval(async () => {
|
|
161
|
+
if (!job.enabled) return;
|
|
162
|
+
try {
|
|
163
|
+
await context.executor.executeCommand(job.command, context);
|
|
164
|
+
job.runCount++;
|
|
165
|
+
job.lastRun = Date.now();
|
|
166
|
+
const state = context.state.getState();
|
|
167
|
+
const updated = state.jobs.map(
|
|
168
|
+
(j) => j.id === job.id ? { ...j, runCount: job.runCount, lastRun: job.lastRun } : j
|
|
169
|
+
);
|
|
170
|
+
context.state.updateState({ jobs: updated });
|
|
171
|
+
} catch (e) {
|
|
172
|
+
console.error(`Cron job "${job.name}" failed:`, e);
|
|
173
|
+
}
|
|
174
|
+
}, job.intervalMs);
|
|
175
|
+
this.timers.set(job.id, timer);
|
|
176
|
+
}
|
|
177
|
+
stopJob(id) {
|
|
178
|
+
const timer = this.timers.get(id);
|
|
179
|
+
if (timer) {
|
|
180
|
+
clearInterval(timer);
|
|
181
|
+
this.timers.delete(id);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
async setEnabled(name, enabled, context) {
|
|
185
|
+
const state = context.state.getState();
|
|
186
|
+
const jobIdx = state.jobs.findIndex((j) => j.name === name);
|
|
187
|
+
if (jobIdx === -1) {
|
|
188
|
+
context.writer.writeError(`Job "${name}" not found`);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const job = state.jobs[jobIdx];
|
|
192
|
+
job.enabled = enabled;
|
|
193
|
+
if (enabled) {
|
|
194
|
+
this.startJob(job, context);
|
|
195
|
+
} else {
|
|
196
|
+
this.stopJob(job.id);
|
|
197
|
+
}
|
|
198
|
+
const updated = state.jobs.map((j) => j.name === name ? { ...j, enabled } : j);
|
|
199
|
+
context.state.updateState({ jobs: updated });
|
|
200
|
+
await context.state.persist();
|
|
201
|
+
context.writer.writeSuccess(`Job "${name}" ${enabled ? "enabled" : "disabled"}`);
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// src/lib/version.ts
|
|
206
|
+
var LIBRARY_VERSION = "2.0.0-beta.1";
|
|
207
|
+
var API_VERSION = 2;
|
|
208
|
+
|
|
209
|
+
// src/public-api.ts
|
|
210
|
+
var cronModule = {
|
|
211
|
+
apiVersion: API_VERSION,
|
|
212
|
+
name: "@qodalis/cli-cron",
|
|
213
|
+
processors: [new CliCronCommandProcessor()],
|
|
214
|
+
translations: {
|
|
215
|
+
es: { "cli.cron.description": "Programar comandos para ejecuci\xF3n peri\xF3dica" },
|
|
216
|
+
fr: { "cli.cron.description": "Planifier des commandes \xE0 ex\xE9cution p\xE9riodique" },
|
|
217
|
+
de: { "cli.cron.description": "Befehle f\xFCr regelm\xE4\xDFige Ausf\xFChrung planen" },
|
|
218
|
+
pt: { "cli.cron.description": "Agendar comandos para execu\xE7\xE3o peri\xF3dica" },
|
|
219
|
+
it: { "cli.cron.description": "Pianificare comandi a esecuzione periodica" },
|
|
220
|
+
ja: { "cli.cron.description": "\u30B3\u30DE\u30F3\u30C9\u306E\u5B9A\u671F\u5B9F\u884C\u3092\u30B9\u30B1\u30B8\u30E5\u30FC\u30EB" },
|
|
221
|
+
ko: { "cli.cron.description": "\uBA85\uB839\uC5B4\uB97C \uC8FC\uAE30\uC801\uC73C\uB85C \uC2E4\uD589\uD558\uB3C4\uB85D \uC608\uC57D" },
|
|
222
|
+
zh: { "cli.cron.description": "\u5B9A\u65F6\u91CD\u590D\u6267\u884C\u547D\u4EE4" },
|
|
223
|
+
ru: { "cli.cron.description": "\u041F\u043B\u0430\u043D\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0435 \u043F\u0435\u0440\u0438\u043E\u0434\u0438\u0447\u0435\u0441\u043A\u043E\u0433\u043E \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u044F \u043A\u043E\u043C\u0430\u043D\u0434" },
|
|
224
|
+
ro: { "cli.cron.description": "Programeaz\u0103 comenzi pentru execu\u021Bie periodic\u0103" }
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
export { API_VERSION, CliCronCommandProcessor, LIBRARY_VERSION, cronModule, formatInterval, parseInterval };
|
|
229
|
+
//# sourceMappingURL=public-api.mjs.map
|
|
230
|
+
//# sourceMappingURL=public-api.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../packages/plugins/cron/src/lib/cron-utils.ts","../../packages/plugins/cron/src/lib/processors/cli-cron-command-processor.ts","../../packages/plugins/cron/src/lib/version.ts","../../packages/plugins/cron/src/public-api.ts"],"names":[],"mappings":"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","file":"public-api.mjs","sourcesContent":["const MIN_INTERVAL_MS = 10_000; // 10 seconds minimum\n\nexport function parseInterval(input: string): number | null {\n if (!input.trim()) return null;\n if (/^\\d+$/.test(input.trim())) {\n const ms = parseInt(input.trim(), 10) * 1000;\n return ms >= MIN_INTERVAL_MS ? ms : null;\n }\n const pattern = /^(?:(\\d+)h)?(?:(\\d+)m)?(?:(\\d+)s)?$/;\n const match = input.trim().match(pattern);\n if (!match || match[0] === '') return null;\n const h = parseInt(match[1] ?? '0', 10);\n const m = parseInt(match[2] ?? '0', 10);\n const s = parseInt(match[3] ?? '0', 10);\n const total = h * 3_600_000 + m * 60_000 + s * 1_000;\n return total >= MIN_INTERVAL_MS ? total : null;\n}\n\nexport function formatInterval(ms: number): string {\n const h = Math.floor(ms / 3_600_000);\n const m = Math.floor((ms % 3_600_000) / 60_000);\n const s = Math.floor((ms % 60_000) / 1_000);\n const parts: string[] = [];\n if (h > 0) parts.push(`${h}h`);\n if (m > 0) parts.push(`${m}m`);\n if (s > 0) parts.push(`${s}s`);\n return parts.join(' ') || '0s';\n}\n","import {\n ICliCommandProcessor,\n ICliExecutionContext,\n CliProcessCommand,\n ICliCommandAuthor,\n CliProcessorMetadata,\n CliIcon,\n CliStateConfiguration,\n DefaultLibraryAuthor,\n CliForegroundColor,\n ICliCommandChildProcessor,\n} from '@qodalis/cli-core';\nimport { parseInterval, formatInterval } from '../cron-utils';\n\ninterface CronJob {\n id: string;\n name: string;\n command: string;\n intervalMs: number;\n enabled: boolean;\n runCount: number;\n lastRun?: number;\n}\n\ninterface CronState {\n jobs: CronJob[];\n}\n\nexport class CliCronCommandProcessor implements ICliCommandProcessor {\n command = 'cron';\n description = 'Schedule commands to run on a repeating interval';\n author: ICliCommandAuthor = DefaultLibraryAuthor;\n metadata: CliProcessorMetadata = { icon: CliIcon.Clock };\n\n stateConfiguration: CliStateConfiguration = {\n storeName: 'cron',\n initialState: { jobs: [] } as CronState,\n };\n\n private timers = new Map<string, ReturnType<typeof setInterval>>();\n\n processors: ICliCommandChildProcessor[] = [\n {\n command: 'add',\n description: 'Add a cron job: cron add <name> <interval> <command>',\n acceptsRawInput: true,\n valueRequired: true,\n processCommand: async (cmd: CliProcessCommand, context: ICliExecutionContext) => {\n const parts = (cmd.value ?? '').trim().split(/\\s+/);\n if (parts.length < 3) {\n context.writer.writeError('Usage: cron add <name> <interval> <command>');\n context.writer.writeln(' Example: cron add ping 5m echo heartbeat');\n return;\n }\n const [name, intervalStr, ...cmdParts] = parts;\n const command = cmdParts.join(' ');\n const intervalMs = parseInterval(intervalStr);\n if (!intervalMs) {\n context.writer.writeError(`Invalid interval \"${intervalStr}\". Min 10s. Examples: 10s, 5m, 1h`);\n return;\n }\n const state = context.state.getState<CronState>();\n if (state.jobs.find((j) => j.name === name)) {\n context.writer.writeError(`Job \"${name}\" already exists. Remove it first: cron remove ${name}`);\n return;\n }\n const job: CronJob = {\n id: `cron_${Date.now()}`,\n name,\n command,\n intervalMs,\n enabled: true,\n runCount: 0,\n };\n const updated = [...state.jobs, job];\n context.state.updateState({ jobs: updated });\n await context.state.persist();\n this.startJob(job, context);\n context.writer.writeSuccess(\n `Job \"${name}\" added — runs every ${formatInterval(intervalMs)}: ${command}`,\n );\n },\n },\n {\n command: 'list',\n description: 'List all cron jobs',\n aliases: ['ls'],\n processCommand: async (_: CliProcessCommand, context: ICliExecutionContext) => {\n const { jobs } = context.state.getState<CronState>();\n if (!jobs.length) {\n context.writer.writeInfo('No cron jobs. Use: cron add <name> <interval> <command>');\n return;\n }\n context.writer.writeln(context.writer.wrapInColor('Cron Jobs:', CliForegroundColor.Yellow));\n for (const job of jobs) {\n const status = job.enabled\n ? context.writer.wrapInColor('enabled ', CliForegroundColor.Green)\n : context.writer.wrapInColor('disabled', CliForegroundColor.Red);\n context.writer.writeln(\n ` ${context.writer.wrapInColor(job.name.padEnd(12), CliForegroundColor.Cyan)}` +\n `[${status}] every ${formatInterval(job.intervalMs)} \\u2192 ${job.command} (ran ${job.runCount}x)`,\n );\n }\n },\n },\n {\n command: 'remove',\n description: 'Remove a cron job: cron remove <name>',\n aliases: ['rm', 'delete'],\n acceptsRawInput: true,\n valueRequired: true,\n processCommand: async (cmd: CliProcessCommand, context: ICliExecutionContext) => {\n const name = (cmd.value ?? '').trim();\n const state = context.state.getState<CronState>();\n const idx = state.jobs.findIndex((j) => j.name === name);\n if (idx === -1) {\n context.writer.writeError(`Job \"${name}\" not found`);\n return;\n }\n const job = state.jobs[idx];\n this.stopJob(job.id);\n const updated = state.jobs.filter((j) => j.name !== name);\n context.state.updateState({ jobs: updated });\n await context.state.persist();\n context.writer.writeSuccess(`Job \"${name}\" removed`);\n },\n },\n {\n command: 'enable',\n description: 'Enable a paused cron job',\n acceptsRawInput: true,\n valueRequired: true,\n processCommand: async (cmd: CliProcessCommand, context: ICliExecutionContext) => {\n await this.setEnabled((cmd.value ?? '').trim(), true, context);\n },\n },\n {\n command: 'disable',\n description: 'Pause a cron job without removing it',\n acceptsRawInput: true,\n valueRequired: true,\n processCommand: async (cmd: CliProcessCommand, context: ICliExecutionContext) => {\n await this.setEnabled((cmd.value ?? '').trim(), false, context);\n },\n },\n ];\n\n async initialize(context: ICliExecutionContext): Promise<void> {\n const { jobs } = context.state.getState<CronState>();\n for (const job of jobs.filter((j) => j.enabled)) {\n this.startJob(job, context);\n }\n }\n\n async processCommand(_: CliProcessCommand, context: ICliExecutionContext): Promise<void> {\n const { jobs } = context.state.getState<CronState>();\n context.writer.writeln(`${jobs.length} job(s). Sub-commands: add, list, remove, enable, disable`);\n }\n\n private startJob(job: CronJob, context: ICliExecutionContext): void {\n if (this.timers.has(job.id)) return; // already running\n const timer = setInterval(async () => {\n if (!job.enabled) return;\n try {\n await context.executor.executeCommand(job.command, context);\n job.runCount++;\n job.lastRun = Date.now();\n const state = context.state.getState<CronState>();\n const updated = state.jobs.map((j) =>\n j.id === job.id ? { ...j, runCount: job.runCount, lastRun: job.lastRun } : j,\n );\n context.state.updateState({ jobs: updated });\n } catch (e) {\n console.error(`Cron job \"${job.name}\" failed:`, e);\n }\n }, job.intervalMs);\n this.timers.set(job.id, timer);\n }\n\n private stopJob(id: string): void {\n const timer = this.timers.get(id);\n if (timer) {\n clearInterval(timer);\n this.timers.delete(id);\n }\n }\n\n private async setEnabled(name: string, enabled: boolean, context: ICliExecutionContext): Promise<void> {\n const state = context.state.getState<CronState>();\n const jobIdx = state.jobs.findIndex((j) => j.name === name);\n if (jobIdx === -1) {\n context.writer.writeError(`Job \"${name}\" not found`);\n return;\n }\n const job = state.jobs[jobIdx];\n job.enabled = enabled;\n if (enabled) {\n this.startJob(job, context);\n } else {\n this.stopJob(job.id);\n }\n const updated = state.jobs.map((j) => j.name === name ? { ...j, enabled } : j);\n context.state.updateState({ jobs: updated });\n await context.state.persist();\n context.writer.writeSuccess(`Job \"${name}\" ${enabled ? 'enabled' : 'disabled'}`);\n }\n}\n","\n// Automatically generated during build\nexport const LIBRARY_VERSION = '2.0.0-beta.1';\nexport const API_VERSION = 2;\n ","/*\n * Public API Surface of cron\n */\n\nexport * from './lib/processors/cli-cron-command-processor';\nexport * from './lib/cron-utils';\nexport * from './lib/version';\n\nimport { ICliModule } from '@qodalis/cli-core';\nimport { CliCronCommandProcessor } from './lib/processors/cli-cron-command-processor';\nimport { API_VERSION } from './lib/version';\n\nexport const cronModule: ICliModule = {\n apiVersion: API_VERSION,\n name: '@qodalis/cli-cron',\n processors: [new CliCronCommandProcessor()],\n translations: {\n es: { 'cli.cron.description': 'Programar comandos para ejecución periódica' },\n fr: { 'cli.cron.description': 'Planifier des commandes à exécution périodique' },\n de: { 'cli.cron.description': 'Befehle für regelmäßige Ausführung planen' },\n pt: { 'cli.cron.description': 'Agendar comandos para execução periódica' },\n it: { 'cli.cron.description': 'Pianificare comandi a esecuzione periodica' },\n ja: { 'cli.cron.description': 'コマンドの定期実行をスケジュール' },\n ko: { 'cli.cron.description': '명령어를 주기적으로 실행하도록 예약' },\n zh: { 'cli.cron.description': '定时重复执行命令' },\n ru: { 'cli.cron.description': 'Планирование периодического выполнения команд' },\n ro: { 'cli.cron.description': 'Programează comenzi pentru execuție periodică' },\n },\n};\n"]}
|