@provos/ironcurtain 0.6.0 → 0.7.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/README.md +29 -86
- package/dist/cli-help.d.ts +63 -0
- package/dist/cli-help.js +102 -0
- package/dist/cli-help.js.map +1 -0
- package/dist/cli.js +79 -56
- package/dist/cli.js.map +1 -1
- package/dist/config/config-command.js +136 -2
- package/dist/config/config-command.js.map +1 -1
- package/dist/config/constitution-readonly.md +21 -0
- package/dist/config/generated/tool-annotations.json +487 -338
- package/dist/config/generated-readonly/compiled-policy.json +468 -0
- package/dist/config/index.d.ts +21 -1
- package/dist/config/index.js +12 -4
- package/dist/config/index.js.map +1 -1
- package/dist/config/mcp-servers.json +2 -8
- package/dist/config/paths.d.ts +47 -0
- package/dist/config/paths.js +81 -0
- package/dist/config/paths.js.map +1 -1
- package/dist/config/tool-description-hints.json +15 -0
- package/dist/config/types.d.ts +7 -0
- package/dist/config/user-config.d.ts +21 -0
- package/dist/config/user-config.js +8 -0
- package/dist/config/user-config.js.map +1 -1
- package/dist/cron/compile-task-policy.d.ts +17 -0
- package/dist/cron/compile-task-policy.js +39 -0
- package/dist/cron/compile-task-policy.js.map +1 -0
- package/dist/cron/constitution-generator.d.ts +60 -0
- package/dist/cron/constitution-generator.js +217 -0
- package/dist/cron/constitution-generator.js.map +1 -0
- package/dist/cron/cron-scheduler.d.ts +65 -0
- package/dist/cron/cron-scheduler.js +184 -0
- package/dist/cron/cron-scheduler.js.map +1 -0
- package/dist/cron/file-lock.d.ts +41 -0
- package/dist/cron/file-lock.js +156 -0
- package/dist/cron/file-lock.js.map +1 -0
- package/dist/cron/format-utils.d.ts +18 -0
- package/dist/cron/format-utils.js +145 -0
- package/dist/cron/format-utils.js.map +1 -0
- package/dist/cron/git-sync.d.ts +22 -0
- package/dist/cron/git-sync.js +121 -0
- package/dist/cron/git-sync.js.map +1 -0
- package/dist/cron/headless-transport.d.ts +27 -0
- package/dist/cron/headless-transport.js +43 -0
- package/dist/cron/headless-transport.js.map +1 -0
- package/dist/cron/job-commands.d.ts +39 -0
- package/dist/cron/job-commands.js +585 -0
- package/dist/cron/job-commands.js.map +1 -0
- package/dist/cron/job-customizer.d.ts +22 -0
- package/dist/cron/job-customizer.js +47 -0
- package/dist/cron/job-customizer.js.map +1 -0
- package/dist/cron/job-store.d.ts +35 -0
- package/dist/cron/job-store.js +111 -0
- package/dist/cron/job-store.js.map +1 -0
- package/dist/cron/types.d.ts +135 -0
- package/dist/cron/types.js +22 -0
- package/dist/cron/types.js.map +1 -0
- package/dist/daemon/control-socket.d.ts +127 -0
- package/dist/daemon/control-socket.js +261 -0
- package/dist/daemon/control-socket.js.map +1 -0
- package/dist/daemon/daemon-command.d.ts +13 -0
- package/dist/daemon/daemon-command.js +230 -0
- package/dist/daemon/daemon-command.js.map +1 -0
- package/dist/daemon/ironcurtain-daemon.d.ts +90 -0
- package/dist/daemon/ironcurtain-daemon.js +550 -0
- package/dist/daemon/ironcurtain-daemon.js.map +1 -0
- package/dist/docker/adapters/claude-code.js +7 -47
- package/dist/docker/adapters/claude-code.js.map +1 -1
- package/dist/docker/adapters/goose.d.ts +58 -0
- package/dist/docker/adapters/goose.js +250 -0
- package/dist/docker/adapters/goose.js.map +1 -0
- package/dist/docker/adapters/shared-scripts.d.ts +29 -0
- package/dist/docker/adapters/shared-scripts.js +80 -0
- package/dist/docker/adapters/shared-scripts.js.map +1 -0
- package/dist/docker/agent-adapter.d.ts +12 -0
- package/dist/docker/agent-adapter.js.map +1 -1
- package/dist/docker/agent-registry.d.ts +6 -1
- package/dist/docker/agent-registry.js +10 -1
- package/dist/docker/agent-registry.js.map +1 -1
- package/dist/docker/docker-agent-session.d.ts +2 -25
- package/dist/docker/docker-agent-session.js +7 -106
- package/dist/docker/docker-agent-session.js.map +1 -1
- package/dist/docker/docker-infrastructure.d.ts +8 -0
- package/dist/docker/docker-infrastructure.js +12 -11
- package/dist/docker/docker-infrastructure.js.map +1 -1
- package/dist/docker/mitm-proxy.js +10 -5
- package/dist/docker/mitm-proxy.js.map +1 -1
- package/dist/docker/oauth-credentials.js +2 -2
- package/dist/docker/oauth-credentials.js.map +1 -1
- package/dist/docker/pty-session.js +146 -39
- package/dist/docker/pty-session.js.map +1 -1
- package/dist/escalation/escalation-watcher.d.ts +2 -0
- package/dist/escalation/escalation-watcher.js +3 -0
- package/dist/escalation/escalation-watcher.js.map +1 -1
- package/dist/escalation/session-registry.js +1 -14
- package/dist/escalation/session-registry.js.map +1 -1
- package/dist/index.js +43 -5
- package/dist/index.js.map +1 -1
- package/dist/mux/mux-app.js +11 -6
- package/dist/mux/mux-app.js.map +1 -1
- package/dist/mux/mux-command.js +49 -10
- package/dist/mux/mux-command.js.map +1 -1
- package/dist/mux/mux-input-handler.d.ts +5 -0
- package/dist/mux/mux-input-handler.js +64 -9
- package/dist/mux/mux-input-handler.js.map +1 -1
- package/dist/mux/mux-renderer.d.ts +20 -0
- package/dist/mux/mux-renderer.js +127 -35
- package/dist/mux/mux-renderer.js.map +1 -1
- package/dist/mux/mux-splash.js +2 -1
- package/dist/mux/mux-splash.js.map +1 -1
- package/dist/mux/paste-interceptor.d.ts +22 -0
- package/dist/mux/paste-interceptor.js +143 -0
- package/dist/mux/paste-interceptor.js.map +1 -0
- package/dist/mux/pty-bridge.js +1 -1
- package/dist/mux/pty-bridge.js.map +1 -1
- package/dist/mux/types.d.ts +8 -1
- package/dist/mux/types.js +17 -5
- package/dist/mux/types.js.map +1 -1
- package/dist/persona/compile-persona-policy.d.ts +21 -0
- package/dist/persona/compile-persona-policy.js +50 -0
- package/dist/persona/compile-persona-policy.js.map +1 -0
- package/dist/persona/persona-command.d.ts +9 -0
- package/dist/persona/persona-command.js +479 -0
- package/dist/persona/persona-command.js.map +1 -0
- package/dist/persona/persona-customizer.d.ts +23 -0
- package/dist/persona/persona-customizer.js +52 -0
- package/dist/persona/persona-customizer.js.map +1 -0
- package/dist/persona/persona-prompt.d.ts +19 -0
- package/dist/persona/persona-prompt.js +42 -0
- package/dist/persona/persona-prompt.js.map +1 -0
- package/dist/persona/resolve.d.ts +46 -0
- package/dist/persona/resolve.js +108 -0
- package/dist/persona/resolve.js.map +1 -0
- package/dist/persona/types.d.ts +40 -0
- package/dist/persona/types.js +20 -0
- package/dist/persona/types.js.map +1 -0
- package/dist/pipeline/annotate.js +10 -4
- package/dist/pipeline/annotate.js.map +1 -1
- package/dist/pipeline/compile.d.ts +14 -31
- package/dist/pipeline/compile.js +50 -563
- package/dist/pipeline/compile.js.map +1 -1
- package/dist/pipeline/constitution-compiler.d.ts +6 -0
- package/dist/pipeline/constitution-compiler.js +42 -6
- package/dist/pipeline/constitution-compiler.js.map +1 -1
- package/dist/pipeline/constitution-customizer.d.ts +3 -0
- package/dist/pipeline/constitution-customizer.js +19 -5
- package/dist/pipeline/constitution-customizer.js.map +1 -1
- package/dist/pipeline/customizer-loop.d.ts +34 -0
- package/dist/pipeline/customizer-loop.js +167 -0
- package/dist/pipeline/customizer-loop.js.map +1 -0
- package/dist/pipeline/mcp-connections.d.ts +16 -0
- package/dist/pipeline/mcp-connections.js +61 -0
- package/dist/pipeline/mcp-connections.js.map +1 -0
- package/dist/pipeline/pipeline-runner.d.ts +86 -0
- package/dist/pipeline/pipeline-runner.js +547 -0
- package/dist/pipeline/pipeline-runner.js.map +1 -0
- package/dist/pipeline/pipeline-shared.d.ts +37 -3
- package/dist/pipeline/pipeline-shared.js +77 -16
- package/dist/pipeline/pipeline-shared.js.map +1 -1
- package/dist/pipeline/refresh-lists.js +38 -12
- package/dist/pipeline/refresh-lists.js.map +1 -1
- package/dist/pipeline/scenario-generator.js +2 -1
- package/dist/pipeline/scenario-generator.js.map +1 -1
- package/dist/pipeline/tool-annotator.d.ts +3 -3
- package/dist/pipeline/tool-annotator.js +114 -4
- package/dist/pipeline/tool-annotator.js.map +1 -1
- package/dist/pipeline/types.d.ts +52 -0
- package/dist/sandbox/index.js +36 -1
- package/dist/sandbox/index.js.map +1 -1
- package/dist/session/agent-session.d.ts +3 -0
- package/dist/session/agent-session.js +10 -1
- package/dist/session/agent-session.js.map +1 -1
- package/dist/session/index.d.ts +10 -0
- package/dist/session/index.js +134 -12
- package/dist/session/index.js.map +1 -1
- package/dist/session/preflight.js +26 -6
- package/dist/session/preflight.js.map +1 -1
- package/dist/session/prompts.d.ts +14 -0
- package/dist/session/prompts.js +39 -2
- package/dist/session/prompts.js.map +1 -1
- package/dist/session/session-manager.d.ts +105 -0
- package/dist/session/session-manager.js +134 -0
- package/dist/session/session-manager.js.map +1 -0
- package/dist/session/session-metadata.d.ts +18 -0
- package/dist/session/session-metadata.js +34 -0
- package/dist/session/session-metadata.js.map +1 -0
- package/dist/session/types.d.ts +47 -0
- package/dist/session/workspace-validation.d.ts +2 -0
- package/dist/session/workspace-validation.js +6 -5
- package/dist/session/workspace-validation.js.map +1 -1
- package/dist/signal/bot-command.d.ts +2 -0
- package/dist/signal/bot-command.js +8 -0
- package/dist/signal/bot-command.js.map +1 -1
- package/dist/signal/format.d.ts +27 -0
- package/dist/signal/format.js +59 -0
- package/dist/signal/format.js.map +1 -1
- package/dist/signal/signal-bot-daemon.d.ts +27 -25
- package/dist/signal/signal-bot-daemon.js +153 -135
- package/dist/signal/signal-bot-daemon.js.map +1 -1
- package/dist/signal/signal-container.js +63 -19
- package/dist/signal/signal-container.js.map +1 -1
- package/dist/signal/signal-transport.js +3 -8
- package/dist/signal/signal-transport.js.map +1 -1
- package/dist/trusted-process/domain-utils.d.ts +37 -4
- package/dist/trusted-process/domain-utils.js +130 -11
- package/dist/trusted-process/domain-utils.js.map +1 -1
- package/dist/trusted-process/index.js +6 -2
- package/dist/trusted-process/index.js.map +1 -1
- package/dist/trusted-process/mcp-client-manager.js +11 -5
- package/dist/trusted-process/mcp-client-manager.js.map +1 -1
- package/dist/trusted-process/mcp-proxy-server.d.ts +21 -0
- package/dist/trusted-process/mcp-proxy-server.js +91 -28
- package/dist/trusted-process/mcp-proxy-server.js.map +1 -1
- package/dist/trusted-process/permissive-output-validator.d.ts +23 -0
- package/dist/trusted-process/permissive-output-validator.js +10 -0
- package/dist/trusted-process/permissive-output-validator.js.map +1 -0
- package/dist/trusted-process/policy-engine.d.ts +29 -7
- package/dist/trusted-process/policy-engine.js +149 -92
- package/dist/trusted-process/policy-engine.js.map +1 -1
- package/dist/trusted-process/tool-description-hints.d.ts +19 -0
- package/dist/trusted-process/tool-description-hints.js +53 -0
- package/dist/trusted-process/tool-description-hints.js.map +1 -0
- package/dist/types/argument-roles.d.ts +33 -1
- package/dist/types/argument-roles.js +129 -1
- package/dist/types/argument-roles.js.map +1 -1
- package/dist/types/slug.d.ts +7 -0
- package/dist/types/slug.js +14 -0
- package/dist/types/slug.js.map +1 -0
- package/dist/utils/editor.d.ts +24 -0
- package/dist/utils/editor.js +66 -0
- package/dist/utils/editor.js.map +1 -0
- package/docker/Dockerfile.goose +34 -0
- package/docker/entrypoint-goose.sh +30 -0
- package/package.json +1 -1
- package/src/config/generated/tool-annotations.json +487 -338
- package/src/config/mcp-servers.json +2 -8
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constitution-generator.js","sourceRoot":"","sources":["../../src/cron/constitution-generator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CronScheduler -- manages cron schedules for all enabled jobs.
|
|
3
|
+
*
|
|
4
|
+
* Uses a polling approach with 60-second intervals to check
|
|
5
|
+
* whether each job's cron expression matches the current time.
|
|
6
|
+
* When node-cron is available, the implementation can be swapped
|
|
7
|
+
* to use it for more efficient scheduling.
|
|
8
|
+
*/
|
|
9
|
+
import type { JobDefinition, JobId } from './types.js';
|
|
10
|
+
/**
|
|
11
|
+
* Callback invoked when a scheduled job fires.
|
|
12
|
+
* The daemon implements this to create a session.
|
|
13
|
+
*/
|
|
14
|
+
export type JobTriggerCallback = (job: JobDefinition) => Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Manages cron schedules for all enabled jobs.
|
|
17
|
+
*
|
|
18
|
+
* Invariant: at most one schedule exists per jobId at any time.
|
|
19
|
+
* Calling schedule() for an already-scheduled job replaces the
|
|
20
|
+
* previous schedule.
|
|
21
|
+
*/
|
|
22
|
+
export interface CronScheduler {
|
|
23
|
+
/**
|
|
24
|
+
* Registers a job's schedule. The callback fires at each
|
|
25
|
+
* scheduled time. Does not fire immediately.
|
|
26
|
+
*
|
|
27
|
+
* @throws InvalidCronExpressionError if the cron expression
|
|
28
|
+
* is syntactically invalid.
|
|
29
|
+
*/
|
|
30
|
+
schedule(job: JobDefinition, onTrigger: JobTriggerCallback): void;
|
|
31
|
+
/** Removes a job's schedule. No-op if not scheduled. */
|
|
32
|
+
unschedule(jobId: JobId): void;
|
|
33
|
+
/** Removes all schedules. Called during shutdown. */
|
|
34
|
+
unscheduleAll(): void;
|
|
35
|
+
/**
|
|
36
|
+
* Returns the next scheduled fire time for a job, or undefined
|
|
37
|
+
* if the job is not scheduled.
|
|
38
|
+
*/
|
|
39
|
+
getNextRun(jobId: JobId): Date | undefined;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Thrown when a cron expression cannot be parsed.
|
|
43
|
+
*/
|
|
44
|
+
export declare class InvalidCronExpressionError extends Error {
|
|
45
|
+
readonly expression: string;
|
|
46
|
+
constructor(expression: string, cause: unknown);
|
|
47
|
+
}
|
|
48
|
+
interface CronFields {
|
|
49
|
+
minutes: Set<number>;
|
|
50
|
+
hours: Set<number>;
|
|
51
|
+
daysOfMonth: Set<number>;
|
|
52
|
+
months: Set<number>;
|
|
53
|
+
daysOfWeek: Set<number>;
|
|
54
|
+
}
|
|
55
|
+
declare function parseCronExpression(expression: string): CronFields;
|
|
56
|
+
declare function cronMatchesTime(fields: CronFields, date: Date): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Calculates the next time a cron expression would fire after the given date.
|
|
59
|
+
* Scans forward minute-by-minute (up to 366 days).
|
|
60
|
+
*/
|
|
61
|
+
declare function getNextFireTime(fields: CronFields, after: Date): Date;
|
|
62
|
+
/** Creates a CronScheduler with built-in cron expression parsing. */
|
|
63
|
+
export declare function createCronScheduler(): CronScheduler;
|
|
64
|
+
export { parseCronExpression, cronMatchesTime, getNextFireTime };
|
|
65
|
+
export type { CronFields };
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CronScheduler -- manages cron schedules for all enabled jobs.
|
|
3
|
+
*
|
|
4
|
+
* Uses a polling approach with 60-second intervals to check
|
|
5
|
+
* whether each job's cron expression matches the current time.
|
|
6
|
+
* When node-cron is available, the implementation can be swapped
|
|
7
|
+
* to use it for more efficient scheduling.
|
|
8
|
+
*/
|
|
9
|
+
import * as logger from '../logger.js';
|
|
10
|
+
/**
|
|
11
|
+
* Thrown when a cron expression cannot be parsed.
|
|
12
|
+
*/
|
|
13
|
+
export class InvalidCronExpressionError extends Error {
|
|
14
|
+
expression;
|
|
15
|
+
constructor(expression, cause) {
|
|
16
|
+
super(`Invalid cron expression "${expression}": ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
17
|
+
this.expression = expression;
|
|
18
|
+
this.name = 'InvalidCronExpressionError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function parseField(field, min, max) {
|
|
22
|
+
const values = new Set();
|
|
23
|
+
for (const part of field.split(',')) {
|
|
24
|
+
const stepMatch = part.match(/^(.+)\/(\d+)$/);
|
|
25
|
+
const step = stepMatch ? parseInt(stepMatch[2], 10) : 1;
|
|
26
|
+
if (stepMatch && (isNaN(step) || step < 1)) {
|
|
27
|
+
throw new Error(`Invalid step "${stepMatch[2]}" in "${part}" (expected integer >= 1)`);
|
|
28
|
+
}
|
|
29
|
+
const range = stepMatch ? stepMatch[1] : part;
|
|
30
|
+
if (range === '*') {
|
|
31
|
+
for (let i = min; i <= max; i += step)
|
|
32
|
+
values.add(i);
|
|
33
|
+
}
|
|
34
|
+
else if (range.includes('-')) {
|
|
35
|
+
const [startStr, endStr] = range.split('-');
|
|
36
|
+
const start = parseInt(startStr, 10);
|
|
37
|
+
const end = parseInt(endStr, 10);
|
|
38
|
+
if (isNaN(start) || isNaN(end) || start < min || end > max || start > end) {
|
|
39
|
+
throw new Error(`Invalid range "${range}" (expected ${min}-${max})`);
|
|
40
|
+
}
|
|
41
|
+
for (let i = start; i <= end; i += step)
|
|
42
|
+
values.add(i);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
const val = parseInt(range, 10);
|
|
46
|
+
if (isNaN(val) || val < min || val > max) {
|
|
47
|
+
throw new Error(`Invalid value "${range}" (expected ${min}-${max})`);
|
|
48
|
+
}
|
|
49
|
+
values.add(val);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return values;
|
|
53
|
+
}
|
|
54
|
+
function parseCronExpression(expression) {
|
|
55
|
+
const parts = expression.trim().split(/\s+/);
|
|
56
|
+
if (parts.length !== 5) {
|
|
57
|
+
throw new Error(`Expected 5 fields (min hour dom mon dow), got ${parts.length}`);
|
|
58
|
+
}
|
|
59
|
+
const daysOfWeek = parseField(parts[4], 0, 7); // 0 and 7 both mean Sunday
|
|
60
|
+
if (daysOfWeek.has(7)) {
|
|
61
|
+
daysOfWeek.add(0);
|
|
62
|
+
daysOfWeek.delete(7);
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
minutes: parseField(parts[0], 0, 59),
|
|
66
|
+
hours: parseField(parts[1], 0, 23),
|
|
67
|
+
daysOfMonth: parseField(parts[2], 1, 31),
|
|
68
|
+
months: parseField(parts[3], 1, 12),
|
|
69
|
+
daysOfWeek,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function cronMatchesTime(fields, date) {
|
|
73
|
+
const minute = date.getMinutes();
|
|
74
|
+
const hour = date.getHours();
|
|
75
|
+
const dayOfMonth = date.getDate();
|
|
76
|
+
const month = date.getMonth() + 1; // 0-indexed -> 1-indexed
|
|
77
|
+
const dayOfWeek = date.getDay(); // 0 = Sunday
|
|
78
|
+
if (!fields.minutes.has(minute))
|
|
79
|
+
return false;
|
|
80
|
+
if (!fields.hours.has(hour))
|
|
81
|
+
return false;
|
|
82
|
+
if (!fields.months.has(month))
|
|
83
|
+
return false;
|
|
84
|
+
// Standard cron: if both dom and dow are restricted (not *), match either
|
|
85
|
+
const domIsWildcard = fields.daysOfMonth.size === 31;
|
|
86
|
+
const dowIsWildcard = fields.daysOfWeek.size === 7;
|
|
87
|
+
if (domIsWildcard && dowIsWildcard)
|
|
88
|
+
return true;
|
|
89
|
+
if (domIsWildcard)
|
|
90
|
+
return fields.daysOfWeek.has(dayOfWeek);
|
|
91
|
+
if (dowIsWildcard)
|
|
92
|
+
return fields.daysOfMonth.has(dayOfMonth);
|
|
93
|
+
// Both restricted: OR logic (standard cron behavior)
|
|
94
|
+
return fields.daysOfMonth.has(dayOfMonth) || fields.daysOfWeek.has(dayOfWeek);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Calculates the next time a cron expression would fire after the given date.
|
|
98
|
+
* Scans forward minute-by-minute (up to 366 days).
|
|
99
|
+
*/
|
|
100
|
+
function getNextFireTime(fields, after) {
|
|
101
|
+
const candidate = new Date(after);
|
|
102
|
+
// Start from the next minute
|
|
103
|
+
candidate.setSeconds(0, 0);
|
|
104
|
+
candidate.setMinutes(candidate.getMinutes() + 1);
|
|
105
|
+
const maxIterations = 366 * 24 * 60; // One year of minutes
|
|
106
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
107
|
+
if (cronMatchesTime(fields, candidate)) {
|
|
108
|
+
return candidate;
|
|
109
|
+
}
|
|
110
|
+
candidate.setMinutes(candidate.getMinutes() + 1);
|
|
111
|
+
}
|
|
112
|
+
// Should not happen for valid expressions, but return a far-future date
|
|
113
|
+
return new Date(after.getTime() + 366 * 24 * 60 * 60 * 1000);
|
|
114
|
+
}
|
|
115
|
+
/** Creates a CronScheduler with built-in cron expression parsing. */
|
|
116
|
+
export function createCronScheduler() {
|
|
117
|
+
const jobs = new Map();
|
|
118
|
+
let pollInterval = null;
|
|
119
|
+
function startPolling() {
|
|
120
|
+
if (pollInterval)
|
|
121
|
+
return;
|
|
122
|
+
// Poll every 30 seconds (within the 60-second cron granularity)
|
|
123
|
+
pollInterval = setInterval(() => tick(), 30_000);
|
|
124
|
+
pollInterval.unref(); // Don't keep the process alive just for polling
|
|
125
|
+
}
|
|
126
|
+
function stopPolling() {
|
|
127
|
+
if (pollInterval) {
|
|
128
|
+
clearInterval(pollInterval);
|
|
129
|
+
pollInterval = null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function tick() {
|
|
133
|
+
const now = new Date();
|
|
134
|
+
// Truncate to the current minute for comparison
|
|
135
|
+
now.setSeconds(0, 0);
|
|
136
|
+
for (const [jobId, scheduled] of jobs.entries()) {
|
|
137
|
+
if (scheduled.lastFired && scheduled.lastFired.getTime() === now.getTime()) {
|
|
138
|
+
continue; // Already fired this minute
|
|
139
|
+
}
|
|
140
|
+
if (cronMatchesTime(scheduled.fields, now)) {
|
|
141
|
+
scheduled.lastFired = now;
|
|
142
|
+
logger.info(`[CronScheduler] Firing job ${jobId} at ${now.toISOString()}`);
|
|
143
|
+
scheduled.callback(scheduled.job).catch((err) => {
|
|
144
|
+
logger.warn(`[CronScheduler] Job ${jobId} callback failed: ${String(err)}`);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
schedule(job, onTrigger) {
|
|
151
|
+
let fields;
|
|
152
|
+
try {
|
|
153
|
+
fields = parseCronExpression(job.schedule);
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
throw new InvalidCronExpressionError(job.schedule, err);
|
|
157
|
+
}
|
|
158
|
+
// Replace any existing schedule for this job
|
|
159
|
+
jobs.set(job.id, { job, fields, callback: onTrigger, lastFired: null });
|
|
160
|
+
if (jobs.size === 1) {
|
|
161
|
+
startPolling();
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
unschedule(jobId) {
|
|
165
|
+
jobs.delete(jobId);
|
|
166
|
+
if (jobs.size === 0) {
|
|
167
|
+
stopPolling();
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
unscheduleAll() {
|
|
171
|
+
jobs.clear();
|
|
172
|
+
stopPolling();
|
|
173
|
+
},
|
|
174
|
+
getNextRun(jobId) {
|
|
175
|
+
const scheduled = jobs.get(jobId);
|
|
176
|
+
if (!scheduled)
|
|
177
|
+
return undefined;
|
|
178
|
+
return getNextFireTime(scheduled.fields, new Date());
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
// Export for testing
|
|
183
|
+
export { parseCronExpression, cronMatchesTime, getNextFireTime };
|
|
184
|
+
//# sourceMappingURL=cron-scheduler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cron-scheduler.js","sourceRoot":"","sources":["../../src/cron/cron-scheduler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advisory file locking for the job store.
|
|
3
|
+
*
|
|
4
|
+
* Uses mkdir (atomic on POSIX) to create a lock directory next to the
|
|
5
|
+
* target file. The lock directory contains a metadata file with the
|
|
6
|
+
* owning PID and timestamp, enabling stale lock detection when a
|
|
7
|
+
* process crashes without releasing.
|
|
8
|
+
*
|
|
9
|
+
* This is designed for short-lived critical sections (read-modify-write),
|
|
10
|
+
* not long-held process-level locks. For process singleton enforcement,
|
|
11
|
+
* see src/escalation/listener-lock.ts.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Releases an advisory lock. Safe to call even if the lock is not held.
|
|
15
|
+
*/
|
|
16
|
+
export declare function releaseLock(lockDir: string): void;
|
|
17
|
+
/**
|
|
18
|
+
* Acquires an advisory lock, retrying until timeout.
|
|
19
|
+
*
|
|
20
|
+
* @param lockDir Path to the lock directory (typically `{target}.lock`)
|
|
21
|
+
* @param staleMs Time in ms after which an unreleased lock is considered stale
|
|
22
|
+
* @returns true if acquired, false if timed out
|
|
23
|
+
*/
|
|
24
|
+
export declare function acquireLock(lockDir: string, staleMs?: number): Promise<boolean>;
|
|
25
|
+
/**
|
|
26
|
+
* Executes a function while holding an advisory file lock.
|
|
27
|
+
*
|
|
28
|
+
* Acquires the lock, runs the function, and releases the lock in a
|
|
29
|
+
* finally block. Throws if the lock cannot be acquired within the
|
|
30
|
+
* timeout period.
|
|
31
|
+
*
|
|
32
|
+
* @param lockDir Path to the lock directory
|
|
33
|
+
* @param fn The function to execute under lock
|
|
34
|
+
* @param staleMs Stale lock timeout (default 30s)
|
|
35
|
+
*/
|
|
36
|
+
export declare function withFileLock<T>(lockDir: string, fn: () => T | Promise<T>, staleMs?: number): Promise<T>;
|
|
37
|
+
/**
|
|
38
|
+
* Returns the conventional lock directory path for a job directory.
|
|
39
|
+
* The lock is placed at `{jobDir}/job.json.lock/`.
|
|
40
|
+
*/
|
|
41
|
+
export declare function getJobLockDir(jobDir: string): string;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advisory file locking for the job store.
|
|
3
|
+
*
|
|
4
|
+
* Uses mkdir (atomic on POSIX) to create a lock directory next to the
|
|
5
|
+
* target file. The lock directory contains a metadata file with the
|
|
6
|
+
* owning PID and timestamp, enabling stale lock detection when a
|
|
7
|
+
* process crashes without releasing.
|
|
8
|
+
*
|
|
9
|
+
* This is designed for short-lived critical sections (read-modify-write),
|
|
10
|
+
* not long-held process-level locks. For process singleton enforcement,
|
|
11
|
+
* see src/escalation/listener-lock.ts.
|
|
12
|
+
*/
|
|
13
|
+
import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
14
|
+
import { resolve } from 'node:path';
|
|
15
|
+
import { isPidAlive } from '../escalation/listener-lock.js';
|
|
16
|
+
/** Default stale timeout: 30 seconds. */
|
|
17
|
+
const DEFAULT_STALE_MS = 30_000;
|
|
18
|
+
/** Retry interval when waiting for a lock. */
|
|
19
|
+
const RETRY_INTERVAL_MS = 50;
|
|
20
|
+
/** Maximum time to wait for a lock before giving up. */
|
|
21
|
+
const MAX_WAIT_MS = 10_000;
|
|
22
|
+
/**
|
|
23
|
+
* Attempts to acquire an advisory lock for the given path.
|
|
24
|
+
* Returns true if the lock was acquired, false otherwise.
|
|
25
|
+
*
|
|
26
|
+
* The lock is a directory at `{path}.lock/` containing a `meta.json`
|
|
27
|
+
* with the owning PID and creation timestamp.
|
|
28
|
+
*/
|
|
29
|
+
function tryAcquire(lockDir, staleMs) {
|
|
30
|
+
// Attempt atomic mkdir first — this is the authoritative lock acquisition.
|
|
31
|
+
// Only check for stale locks after EEXIST, avoiding a TOCTOU race.
|
|
32
|
+
try {
|
|
33
|
+
mkdirSync(lockDir);
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
if (err.code !== 'EEXIST') {
|
|
37
|
+
throw err;
|
|
38
|
+
}
|
|
39
|
+
// Lock dir exists — check if it's stale
|
|
40
|
+
if (isLockStale(lockDir, staleMs)) {
|
|
41
|
+
forceRelease(lockDir);
|
|
42
|
+
// Retry mkdir after releasing stale lock
|
|
43
|
+
try {
|
|
44
|
+
mkdirSync(lockDir);
|
|
45
|
+
}
|
|
46
|
+
catch (retryErr) {
|
|
47
|
+
if (retryErr.code === 'EEXIST') {
|
|
48
|
+
return false; // Another process grabbed it
|
|
49
|
+
}
|
|
50
|
+
throw retryErr;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Write metadata for stale detection
|
|
58
|
+
const meta = { pid: process.pid, timestamp: Date.now() };
|
|
59
|
+
try {
|
|
60
|
+
writeFileSync(resolve(lockDir, 'meta.json'), JSON.stringify(meta));
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// If we can't write metadata, release and fail
|
|
64
|
+
forceRelease(lockDir);
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Checks whether an existing lock is stale (owner dead or timed out).
|
|
71
|
+
*/
|
|
72
|
+
function isLockStale(lockDir, staleMs) {
|
|
73
|
+
const metaPath = resolve(lockDir, 'meta.json');
|
|
74
|
+
try {
|
|
75
|
+
const raw = readFileSync(metaPath, 'utf-8');
|
|
76
|
+
const meta = JSON.parse(raw);
|
|
77
|
+
// Check if the owning process is still alive
|
|
78
|
+
if (!isPidAlive(meta.pid)) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
// Check if the lock has exceeded the stale timeout
|
|
82
|
+
if (Date.now() - meta.timestamp > staleMs) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// No metadata or unreadable -- treat as stale
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Force-removes a lock directory. Best-effort.
|
|
94
|
+
*/
|
|
95
|
+
function forceRelease(lockDir) {
|
|
96
|
+
try {
|
|
97
|
+
rmSync(lockDir, { recursive: true, force: true });
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
/* best effort */
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Releases an advisory lock. Safe to call even if the lock is not held.
|
|
105
|
+
*/
|
|
106
|
+
export function releaseLock(lockDir) {
|
|
107
|
+
forceRelease(lockDir);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Acquires an advisory lock, retrying until timeout.
|
|
111
|
+
*
|
|
112
|
+
* @param lockDir Path to the lock directory (typically `{target}.lock`)
|
|
113
|
+
* @param staleMs Time in ms after which an unreleased lock is considered stale
|
|
114
|
+
* @returns true if acquired, false if timed out
|
|
115
|
+
*/
|
|
116
|
+
export async function acquireLock(lockDir, staleMs = DEFAULT_STALE_MS) {
|
|
117
|
+
const deadline = Date.now() + MAX_WAIT_MS;
|
|
118
|
+
while (Date.now() < deadline) {
|
|
119
|
+
if (tryAcquire(lockDir, staleMs)) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
await new Promise((resolve) => setTimeout(resolve, RETRY_INTERVAL_MS));
|
|
123
|
+
}
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Executes a function while holding an advisory file lock.
|
|
128
|
+
*
|
|
129
|
+
* Acquires the lock, runs the function, and releases the lock in a
|
|
130
|
+
* finally block. Throws if the lock cannot be acquired within the
|
|
131
|
+
* timeout period.
|
|
132
|
+
*
|
|
133
|
+
* @param lockDir Path to the lock directory
|
|
134
|
+
* @param fn The function to execute under lock
|
|
135
|
+
* @param staleMs Stale lock timeout (default 30s)
|
|
136
|
+
*/
|
|
137
|
+
export async function withFileLock(lockDir, fn, staleMs = DEFAULT_STALE_MS) {
|
|
138
|
+
const acquired = await acquireLock(lockDir, staleMs);
|
|
139
|
+
if (!acquired) {
|
|
140
|
+
throw new Error(`Failed to acquire file lock: ${lockDir} (timed out after ${MAX_WAIT_MS}ms)`);
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
return await fn();
|
|
144
|
+
}
|
|
145
|
+
finally {
|
|
146
|
+
releaseLock(lockDir);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Returns the conventional lock directory path for a job directory.
|
|
151
|
+
* The lock is placed at `{jobDir}/job.json.lock/`.
|
|
152
|
+
*/
|
|
153
|
+
export function getJobLockDir(jobDir) {
|
|
154
|
+
return resolve(jobDir, 'job.json.lock');
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=file-lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-lock.js","sourceRoot":"","sources":["../../src/cron/file-lock.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formatting utilities for cron job CLI output.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Formats a duration in seconds as a compact string like "1d 2h 30m".
|
|
6
|
+
* Shows up to two units for brevity (e.g., "2h 15m" not "2h 15m 30s").
|
|
7
|
+
*/
|
|
8
|
+
export declare function formatDuration(seconds: number): string;
|
|
9
|
+
/**
|
|
10
|
+
* Formats a date as a relative time string with an absolute time suffix.
|
|
11
|
+
* Examples: "in 2h 15m (Mar 5, 09:00)", "3h ago (Mar 5, 06:00)", "just now (Mar 5, 09:00)"
|
|
12
|
+
*/
|
|
13
|
+
export declare function formatRelativeTime(date: Date, now?: Date): string;
|
|
14
|
+
/**
|
|
15
|
+
* Converts a 5-field cron expression into a human-readable English description.
|
|
16
|
+
* Covers common patterns well; exotic expressions get a best-effort description.
|
|
17
|
+
*/
|
|
18
|
+
export declare function describeCronExpression(expr: string): string;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formatting utilities for cron job CLI output.
|
|
3
|
+
*/
|
|
4
|
+
import { parseCronExpression } from './cron-scheduler.js';
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Shared constants
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
const MONTH_NAMES = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Duration and relative time display
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
/**
|
|
13
|
+
* Formats a duration in seconds as a compact string like "1d 2h 30m".
|
|
14
|
+
* Shows up to two units for brevity (e.g., "2h 15m" not "2h 15m 30s").
|
|
15
|
+
*/
|
|
16
|
+
export function formatDuration(seconds) {
|
|
17
|
+
const minutes = Math.floor(seconds / 60);
|
|
18
|
+
const hours = Math.floor(minutes / 60);
|
|
19
|
+
const days = Math.floor(hours / 24);
|
|
20
|
+
const parts = [];
|
|
21
|
+
if (days > 0)
|
|
22
|
+
parts.push(`${days}d`);
|
|
23
|
+
if (hours % 24 > 0)
|
|
24
|
+
parts.push(`${hours % 24}h`);
|
|
25
|
+
if (minutes % 60 > 0 && days === 0)
|
|
26
|
+
parts.push(`${minutes % 60}m`);
|
|
27
|
+
if (parts.length === 0)
|
|
28
|
+
parts.push('0m');
|
|
29
|
+
return parts.join(' ');
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Formats a date as a relative time string with an absolute time suffix.
|
|
33
|
+
* Examples: "in 2h 15m (Mar 5, 09:00)", "3h ago (Mar 5, 06:00)", "just now (Mar 5, 09:00)"
|
|
34
|
+
*/
|
|
35
|
+
export function formatRelativeTime(date, now = new Date()) {
|
|
36
|
+
const diffMs = date.getTime() - now.getTime();
|
|
37
|
+
const absDiffSec = Math.floor(Math.abs(diffMs) / 1000);
|
|
38
|
+
const absolute = formatAbsoluteTime(date);
|
|
39
|
+
if (absDiffSec < 60)
|
|
40
|
+
return `just now (${absolute})`;
|
|
41
|
+
const dur = formatDuration(absDiffSec);
|
|
42
|
+
const relative = diffMs >= 0 ? `in ${dur}` : `${dur} ago`;
|
|
43
|
+
return `${relative} (${absolute})`;
|
|
44
|
+
}
|
|
45
|
+
function formatAbsoluteTime(date) {
|
|
46
|
+
const month = MONTH_NAMES[date.getMonth()];
|
|
47
|
+
const day = date.getDate();
|
|
48
|
+
const hours = date.getHours().toString().padStart(2, '0');
|
|
49
|
+
const minutes = date.getMinutes().toString().padStart(2, '0');
|
|
50
|
+
return `${month} ${day}, ${hours}:${minutes}`;
|
|
51
|
+
}
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Cron expression description
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
const DAY_NAMES = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
56
|
+
/**
|
|
57
|
+
* Converts a 5-field cron expression into a human-readable English description.
|
|
58
|
+
* Covers common patterns well; exotic expressions get a best-effort description.
|
|
59
|
+
*/
|
|
60
|
+
export function describeCronExpression(expr) {
|
|
61
|
+
let fields;
|
|
62
|
+
try {
|
|
63
|
+
fields = parseCronExpression(expr);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return expr; // fallback to raw expression if unparseable
|
|
67
|
+
}
|
|
68
|
+
const parts = [];
|
|
69
|
+
// Time part
|
|
70
|
+
parts.push(describeTimePart(expr, fields));
|
|
71
|
+
// Day-of-week part
|
|
72
|
+
const dowPart = describeDaysOfWeek(fields);
|
|
73
|
+
if (dowPart)
|
|
74
|
+
parts.push(dowPart);
|
|
75
|
+
// Day-of-month part
|
|
76
|
+
const domPart = describeDaysOfMonth(fields);
|
|
77
|
+
if (domPart)
|
|
78
|
+
parts.push(domPart);
|
|
79
|
+
// Month part
|
|
80
|
+
const monthPart = describeMonths(fields);
|
|
81
|
+
if (monthPart)
|
|
82
|
+
parts.push(monthPart);
|
|
83
|
+
return parts.join(', ');
|
|
84
|
+
}
|
|
85
|
+
function describeTimePart(expr, fields) {
|
|
86
|
+
const rawParts = expr.trim().split(/\s+/);
|
|
87
|
+
const minField = rawParts[0];
|
|
88
|
+
const hourField = rawParts[1];
|
|
89
|
+
// Every N minutes
|
|
90
|
+
const minStep = minField.match(/^\*\/(\d+)$/);
|
|
91
|
+
if (minStep && hourField === '*') {
|
|
92
|
+
return `Every ${minStep[1]} minutes`;
|
|
93
|
+
}
|
|
94
|
+
// Every hour at specific minute
|
|
95
|
+
if (fields.minutes.size === 1 && hourField === '*') {
|
|
96
|
+
const min = [...fields.minutes][0];
|
|
97
|
+
return min === 0 ? 'Every hour' : `Every hour at :${min.toString().padStart(2, '0')}`;
|
|
98
|
+
}
|
|
99
|
+
// Every N hours
|
|
100
|
+
const hourStep = hourField.match(/^\*\/(\d+)$/);
|
|
101
|
+
if (hourStep && fields.minutes.size === 1) {
|
|
102
|
+
const min = [...fields.minutes][0];
|
|
103
|
+
return `Every ${hourStep[1]} hours at :${min.toString().padStart(2, '0')}`;
|
|
104
|
+
}
|
|
105
|
+
// Specific time(s)
|
|
106
|
+
if (fields.hours.size <= 3 && fields.minutes.size === 1) {
|
|
107
|
+
const min = [...fields.minutes][0];
|
|
108
|
+
const times = [...fields.hours]
|
|
109
|
+
.sort((a, b) => a - b)
|
|
110
|
+
.map((h) => `${h.toString().padStart(2, '0')}:${min.toString().padStart(2, '0')}`);
|
|
111
|
+
return `At ${times.join(', ')}`;
|
|
112
|
+
}
|
|
113
|
+
// Fallback: describe minutes and hours separately
|
|
114
|
+
return `At minute ${[...fields.minutes].sort((a, b) => a - b).join(',')} of hour ${[...fields.hours].sort((a, b) => a - b).join(',')}`;
|
|
115
|
+
}
|
|
116
|
+
function describeDaysOfWeek(fields) {
|
|
117
|
+
if (fields.daysOfWeek.size === 7)
|
|
118
|
+
return null; // wildcard
|
|
119
|
+
const days = [...fields.daysOfWeek].sort((a, b) => a - b);
|
|
120
|
+
// Check for contiguous range
|
|
121
|
+
if (days.length >= 2 && isContiguous(days)) {
|
|
122
|
+
return `${DAY_NAMES[days[0]]}--${DAY_NAMES[days[days.length - 1]]}`;
|
|
123
|
+
}
|
|
124
|
+
return days.map((d) => DAY_NAMES[d]).join(', ');
|
|
125
|
+
}
|
|
126
|
+
function describeDaysOfMonth(fields) {
|
|
127
|
+
if (fields.daysOfMonth.size === 31)
|
|
128
|
+
return null; // wildcard
|
|
129
|
+
const days = [...fields.daysOfMonth].sort((a, b) => a - b);
|
|
130
|
+
return `on day ${days.join(', ')} of the month`;
|
|
131
|
+
}
|
|
132
|
+
function describeMonths(fields) {
|
|
133
|
+
if (fields.months.size === 12)
|
|
134
|
+
return null; // wildcard
|
|
135
|
+
const months = [...fields.months].sort((a, b) => a - b);
|
|
136
|
+
return `in ${months.map((m) => MONTH_NAMES[m - 1]).join(', ')}`;
|
|
137
|
+
}
|
|
138
|
+
function isContiguous(sorted) {
|
|
139
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
140
|
+
if (sorted[i] !== sorted[i - 1] + 1)
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=format-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-utils.js","sourceRoot":"","sources":["../../src/cron/format-utils.ts"],"names":[],"mappings":"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"}
|