@clinebot/core 0.0.36 → 0.0.37
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/dist/ClineCore.d.ts +312 -3
- package/dist/ClineCore.d.ts.map +1 -1
- package/dist/account/cline-account-service.d.ts.map +1 -1
- package/dist/cron/cron-event-ingress.d.ts +38 -0
- package/dist/cron/cron-event-ingress.d.ts.map +1 -0
- package/dist/cron/cron-materializer.d.ts +36 -0
- package/dist/cron/cron-materializer.d.ts.map +1 -0
- package/dist/cron/cron-reconciler.d.ts +62 -0
- package/dist/cron/cron-reconciler.d.ts.map +1 -0
- package/dist/cron/cron-report-writer.d.ts +41 -0
- package/dist/cron/cron-report-writer.d.ts.map +1 -0
- package/dist/cron/cron-runner.d.ts +43 -0
- package/dist/cron/cron-runner.d.ts.map +1 -0
- package/dist/cron/cron-schema.d.ts +3 -0
- package/dist/cron/cron-schema.d.ts.map +1 -0
- package/dist/cron/cron-service.d.ts +57 -0
- package/dist/cron/cron-service.d.ts.map +1 -0
- package/dist/cron/cron-spec-parser.d.ts +27 -0
- package/dist/cron/cron-spec-parser.d.ts.map +1 -0
- package/dist/cron/cron-watcher.d.ts +23 -0
- package/dist/cron/cron-watcher.d.ts.map +1 -0
- package/dist/cron/scheduler.d.ts +3 -1
- package/dist/cron/scheduler.d.ts.map +1 -1
- package/dist/cron/sqlite-cron-store.d.ts +230 -0
- package/dist/cron/sqlite-cron-store.d.ts.map +1 -0
- package/dist/extensions/plugin/plugin-config-loader.d.ts +7 -1
- package/dist/extensions/plugin/plugin-config-loader.d.ts.map +1 -1
- package/dist/extensions/plugin/plugin-loader.d.ts +10 -6
- package/dist/extensions/plugin/plugin-loader.d.ts.map +1 -1
- package/dist/extensions/plugin/plugin-sandbox.d.ts +7 -1
- package/dist/extensions/plugin/plugin-sandbox.d.ts.map +1 -1
- package/dist/extensions/plugin-sandbox-bootstrap.js +236 -275
- package/dist/extensions/tools/constants.d.ts +1 -0
- package/dist/extensions/tools/constants.d.ts.map +1 -1
- package/dist/extensions/tools/definitions.d.ts +2 -3
- package/dist/extensions/tools/definitions.d.ts.map +1 -1
- package/dist/extensions/tools/executors/editor.d.ts.map +1 -1
- package/dist/extensions/tools/helpers.d.ts +1 -0
- package/dist/extensions/tools/helpers.d.ts.map +1 -1
- package/dist/extensions/tools/index.d.ts +1 -2
- package/dist/extensions/tools/index.d.ts.map +1 -1
- package/dist/extensions/tools/presets.d.ts +1 -1
- package/dist/extensions/tools/schemas.d.ts +25 -3
- package/dist/extensions/tools/schemas.d.ts.map +1 -1
- package/dist/extensions/tools/team/delegated-agent.d.ts +2 -2
- package/dist/extensions/tools/team/delegated-agent.d.ts.map +1 -1
- package/dist/extensions/tools/team/multi-agent.d.ts +7 -3
- package/dist/extensions/tools/team/multi-agent.d.ts.map +1 -1
- package/dist/extensions/tools/team/team-tools.d.ts.map +1 -1
- package/dist/extensions/tools/types.d.ts +0 -5
- package/dist/extensions/tools/types.d.ts.map +1 -1
- package/dist/hooks/hook-bridge.d.ts +118 -0
- package/dist/hooks/hook-bridge.d.ts.map +1 -0
- package/dist/hooks/hook-file-hooks.d.ts +2 -1
- package/dist/hooks/hook-file-hooks.d.ts.map +1 -1
- package/dist/hooks/hook-registry.d.ts +16 -0
- package/dist/hooks/hook-registry.d.ts.map +1 -0
- package/dist/hub/browser-websocket.d.ts.map +1 -1
- package/dist/hub/client.d.ts +7 -1
- package/dist/hub/client.d.ts.map +1 -1
- package/dist/hub/daemon-entry.js +721 -461
- package/dist/hub/daemon.d.ts.map +1 -1
- package/dist/hub/defaults.d.ts +8 -4
- package/dist/hub/defaults.d.ts.map +1 -1
- package/dist/hub/index.js +665 -415
- package/dist/hub/runtime-handlers.d.ts.map +1 -1
- package/dist/hub/server.d.ts +18 -0
- package/dist/hub/server.d.ts.map +1 -1
- package/dist/hub/session-client.d.ts +3 -0
- package/dist/hub/session-client.d.ts.map +1 -1
- package/dist/hub/start-shared-server.d.ts.map +1 -1
- package/dist/hub/ui-client.d.ts +1 -0
- package/dist/hub/ui-client.d.ts.map +1 -1
- package/dist/index.d.ts +9 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +756 -467
- package/dist/llms/cline-recommended-models.d.ts +20 -0
- package/dist/llms/cline-recommended-models.d.ts.map +1 -0
- package/dist/llms/handler-factory.d.ts +16 -0
- package/dist/llms/handler-factory.d.ts.map +1 -0
- package/dist/llms/provider-defaults.d.ts.map +1 -1
- package/dist/llms/provider-settings.d.ts +45 -2
- package/dist/llms/provider-settings.d.ts.map +1 -1
- package/dist/llms/runtime-registry.d.ts.map +1 -1
- package/dist/runtime/agent-config-adapter.d.ts +148 -0
- package/dist/runtime/agent-config-adapter.d.ts.map +1 -0
- package/dist/runtime/agent-runtime-config-builder.d.ts +96 -0
- package/dist/runtime/agent-runtime-config-builder.d.ts.map +1 -0
- package/dist/runtime/history.d.ts +6 -0
- package/dist/runtime/history.d.ts.map +1 -1
- package/dist/runtime/host.d.ts.map +1 -1
- package/dist/runtime/loop-detection.d.ts +59 -0
- package/dist/runtime/loop-detection.d.ts.map +1 -0
- package/dist/runtime/mistake-tracker.d.ts +69 -0
- package/dist/runtime/mistake-tracker.d.ts.map +1 -0
- package/dist/runtime/runtime-builder.d.ts.map +1 -1
- package/dist/runtime/runtime-event-adapter.d.ts +102 -0
- package/dist/runtime/runtime-event-adapter.d.ts.map +1 -0
- package/dist/runtime/runtime-host.d.ts +28 -3
- package/dist/runtime/runtime-host.d.ts.map +1 -1
- package/dist/runtime/session-runtime-orchestrator.d.ts +261 -0
- package/dist/runtime/session-runtime-orchestrator.d.ts.map +1 -0
- package/dist/runtime/session-runtime.d.ts +16 -3
- package/dist/runtime/session-runtime.d.ts.map +1 -1
- package/dist/runtime/user-input-builder.d.ts +24 -0
- package/dist/runtime/user-input-builder.d.ts.map +1 -0
- package/dist/services/index.js +28 -0
- package/dist/services/local-runtime-bootstrap.d.ts.map +1 -1
- package/dist/services/plugin-tools.d.ts.map +1 -1
- package/dist/services/providers/local-provider-registry.d.ts +197 -21
- package/dist/services/providers/local-provider-registry.d.ts.map +1 -1
- package/dist/services/providers/local-provider-service.d.ts +3 -1
- package/dist/services/providers/local-provider-service.d.ts.map +1 -1
- package/dist/services/session-data.d.ts.map +1 -1
- package/dist/services/session-telemetry.d.ts +7 -2
- package/dist/services/session-telemetry.d.ts.map +1 -1
- package/dist/services/storage/file-team-store.d.ts.map +1 -1
- package/dist/services/storage/provider-settings-legacy-migration.d.ts.map +1 -1
- package/dist/services/storage/provider-settings-manager.d.ts +1 -0
- package/dist/services/storage/provider-settings-manager.d.ts.map +1 -1
- package/dist/services/storage/sqlite-team-store.d.ts.map +1 -1
- package/dist/session/conversation-store.d.ts +30 -0
- package/dist/session/conversation-store.d.ts.map +1 -0
- package/dist/session/message-builder.d.ts +65 -0
- package/dist/session/message-builder.d.ts.map +1 -0
- package/dist/session/session-manifest.d.ts +1 -1
- package/dist/transports/hub.d.ts +14 -3
- package/dist/transports/hub.d.ts.map +1 -1
- package/dist/transports/local.d.ts +14 -4
- package/dist/transports/local.d.ts.map +1 -1
- package/dist/transports/remote.d.ts.map +1 -1
- package/dist/types/chat-schema.d.ts +5 -5
- package/dist/types/config.d.ts +9 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/events.d.ts +7 -6
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/provider-settings.d.ts +2 -2
- package/dist/types/provider-settings.d.ts.map +1 -1
- package/dist/types/session.d.ts +5 -2
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types.d.ts +4 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/ClineCore.ts +691 -6
- package/src/account/cline-account-service.ts +44 -6
- package/src/cron/cron-event-ingress.ts +357 -0
- package/src/cron/cron-materializer.ts +97 -0
- package/src/cron/cron-reconciler.ts +241 -0
- package/src/cron/cron-report-writer.ts +153 -0
- package/src/cron/cron-runner.ts +495 -0
- package/src/cron/cron-schema.ts +127 -0
- package/src/cron/cron-service.ts +163 -0
- package/src/cron/cron-spec-parser.ts +489 -0
- package/src/cron/cron-watcher.ts +102 -0
- package/src/cron/index.ts +10 -0
- package/src/cron/scheduler.ts +141 -6
- package/src/cron/sqlite-cron-store.ts +1286 -0
- package/src/extensions/plugin/plugin-config-loader.ts +21 -1
- package/src/extensions/plugin/plugin-loader.ts +25 -9
- package/src/extensions/plugin/plugin-sandbox-bootstrap.ts +151 -1
- package/src/extensions/plugin/plugin-sandbox.ts +131 -7
- package/src/extensions/tools/constants.ts +2 -0
- package/src/extensions/tools/definitions.ts +31 -22
- package/src/extensions/tools/executors/editor.ts +4 -3
- package/src/extensions/tools/helpers.ts +24 -0
- package/src/extensions/tools/index.ts +1 -2
- package/src/extensions/tools/presets.ts +1 -1
- package/src/extensions/tools/schemas.ts +13 -18
- package/src/extensions/tools/team/delegated-agent.ts +8 -3
- package/src/extensions/tools/team/multi-agent.ts +135 -19
- package/src/extensions/tools/team/team-tools.ts +151 -91
- package/src/extensions/tools/types.ts +0 -6
- package/src/hooks/hook-bridge.ts +489 -0
- package/src/hooks/hook-file-hooks.ts +58 -3
- package/src/hooks/hook-registry.ts +257 -0
- package/src/hub/browser-websocket.ts +26 -4
- package/src/hub/client.ts +72 -13
- package/src/hub/daemon-entry.ts +35 -0
- package/src/hub/daemon.ts +117 -14
- package/src/hub/defaults.ts +39 -12
- package/src/hub/runtime-handlers.ts +4 -3
- package/src/hub/server.ts +506 -77
- package/src/hub/session-client.ts +43 -1
- package/src/hub/start-shared-server.ts +3 -0
- package/src/hub/ui-client.ts +4 -0
- package/src/index.ts +46 -1
- package/src/llms/cline-recommended-models.ts +167 -0
- package/src/llms/handler-factory.ts +56 -0
- package/src/llms/provider-defaults.ts +17 -1
- package/src/llms/provider-settings.ts +48 -1
- package/src/llms/runtime-registry.ts +1 -0
- package/src/runtime/agent-config-adapter.ts +636 -0
- package/src/runtime/agent-runtime-config-builder.ts +205 -0
- package/src/runtime/error-feedback.ts +142 -0
- package/src/runtime/history.ts +137 -0
- package/src/runtime/host.ts +22 -0
- package/src/runtime/loop-detection.ts +162 -0
- package/src/runtime/mistake-tracker.ts +221 -0
- package/src/runtime/runtime-builder.ts +61 -5
- package/src/runtime/runtime-event-adapter.ts +412 -0
- package/src/runtime/runtime-host.ts +45 -1
- package/src/runtime/session-runtime-orchestrator.ts +1253 -0
- package/src/runtime/session-runtime.ts +16 -2
- package/src/runtime/user-input-builder.ts +167 -0
- package/src/services/local-runtime-bootstrap.ts +128 -22
- package/src/services/plugin-tools.ts +1 -0
- package/src/services/providers/local-provider-registry.ts +273 -57
- package/src/services/providers/local-provider-service.ts +67 -7
- package/src/services/session-data.ts +16 -14
- package/src/services/session-telemetry.ts +6 -15
- package/src/services/storage/file-team-store.ts +1 -5
- package/src/services/storage/provider-settings-legacy-migration.ts +8 -47
- package/src/services/storage/provider-settings-manager.ts +16 -1
- package/src/services/storage/sqlite-team-store.ts +1 -5
- package/src/session/conversation-store.ts +77 -0
- package/src/session/message-builder.ts +941 -0
- package/src/transports/hub.ts +458 -33
- package/src/transports/local.ts +296 -65
- package/src/transports/remote.ts +1 -0
- package/src/types/config.ts +9 -0
- package/src/types/events.ts +8 -6
- package/src/types/index.ts +3 -0
- package/src/types/provider-settings.ts +8 -1
- package/src/types/session.ts +5 -2
- package/src/types.ts +15 -1
- package/dist/cron/index.d.ts +0 -6
- package/dist/cron/index.d.ts.map +0 -1
- package/dist/services/telemetry/index.js +0 -28
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { FSWatcher } from "node:fs";
|
|
2
|
+
import { existsSync, mkdirSync, watch } from "node:fs";
|
|
3
|
+
import { relative, resolve } from "node:path";
|
|
4
|
+
import type { CronReconciler } from "./cron-reconciler";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Filesystem watcher for the configured cron specs directory.
|
|
8
|
+
*
|
|
9
|
+
* Uses `node:fs` `watch` with `{ recursive: true }`. Emits a re-reconcile
|
|
10
|
+
* request for each change, debounced per path, so rapid save bursts from
|
|
11
|
+
* editors don't cause redundant DB churn.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const DEFAULT_DEBOUNCE_MS = 250;
|
|
15
|
+
|
|
16
|
+
export interface CronWatcherOptions {
|
|
17
|
+
reconciler: CronReconciler;
|
|
18
|
+
debounceMs?: number;
|
|
19
|
+
onError?: (error: unknown) => void;
|
|
20
|
+
onReconciled?: () => void | Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class CronWatcher {
|
|
24
|
+
private readonly reconciler: CronReconciler;
|
|
25
|
+
private readonly debounceMs: number;
|
|
26
|
+
private readonly onError: (error: unknown) => void;
|
|
27
|
+
private readonly onReconciled: () => void | Promise<void>;
|
|
28
|
+
private watcher?: FSWatcher;
|
|
29
|
+
private readonly pending = new Map<string, ReturnType<typeof setTimeout>>();
|
|
30
|
+
private disposed = false;
|
|
31
|
+
|
|
32
|
+
constructor(options: CronWatcherOptions) {
|
|
33
|
+
this.reconciler = options.reconciler;
|
|
34
|
+
this.debounceMs = Math.max(0, options.debounceMs ?? DEFAULT_DEBOUNCE_MS);
|
|
35
|
+
this.onError = options.onError ?? (() => {});
|
|
36
|
+
this.onReconciled = options.onReconciled ?? (() => {});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public start(): void {
|
|
40
|
+
if (this.disposed) throw new Error("CronWatcher disposed");
|
|
41
|
+
if (this.watcher) return;
|
|
42
|
+
const dir = this.reconciler.getCronDir();
|
|
43
|
+
try {
|
|
44
|
+
mkdirSync(dir, { recursive: true });
|
|
45
|
+
this.watcher = watch(dir, { recursive: true }, (_eventType, filename) => {
|
|
46
|
+
if (!filename) return;
|
|
47
|
+
const rel = String(filename).replace(/\\/g, "/");
|
|
48
|
+
if (!rel.endsWith(".md")) return;
|
|
49
|
+
if (rel.startsWith("reports/")) return;
|
|
50
|
+
this.scheduleReconcile(rel);
|
|
51
|
+
});
|
|
52
|
+
this.watcher.on("error", this.onError);
|
|
53
|
+
} catch (err) {
|
|
54
|
+
this.onError(err);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public stop(): void {
|
|
59
|
+
for (const timer of this.pending.values()) clearTimeout(timer);
|
|
60
|
+
this.pending.clear();
|
|
61
|
+
this.watcher?.close();
|
|
62
|
+
this.watcher = undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public dispose(): void {
|
|
66
|
+
this.disposed = true;
|
|
67
|
+
this.stop();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private scheduleReconcile(relativePath: string): void {
|
|
71
|
+
const existing = this.pending.get(relativePath);
|
|
72
|
+
if (existing) clearTimeout(existing);
|
|
73
|
+
const timer = setTimeout(() => {
|
|
74
|
+
this.pending.delete(relativePath);
|
|
75
|
+
void this.reconcileNow(relativePath);
|
|
76
|
+
}, this.debounceMs);
|
|
77
|
+
this.pending.set(relativePath, timer);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private async reconcileNow(relativePath: string): Promise<void> {
|
|
81
|
+
try {
|
|
82
|
+
const abs = resolve(this.reconciler.getCronDir(), relativePath);
|
|
83
|
+
if (!existsSync(abs)) {
|
|
84
|
+
// File was deleted — force a full reconcile to catch the
|
|
85
|
+
// missing source and mark the spec removed.
|
|
86
|
+
await this.reconciler.reconcileAll();
|
|
87
|
+
await this.onReconciled();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
// Normalize relative path through the reconciler dir to defend
|
|
91
|
+
// against a watcher emitting an unexpected format.
|
|
92
|
+
const normalizedRel = relative(this.reconciler.getCronDir(), abs).replace(
|
|
93
|
+
/\\/g,
|
|
94
|
+
"/",
|
|
95
|
+
);
|
|
96
|
+
await this.reconciler.reconcileFile(normalizedRel, abs);
|
|
97
|
+
await this.onReconciled();
|
|
98
|
+
} catch (err) {
|
|
99
|
+
this.onError(err);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
package/src/cron/index.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
|
+
export * from "./cron-event-ingress";
|
|
2
|
+
export * from "./cron-materializer";
|
|
3
|
+
export * from "./cron-reconciler";
|
|
4
|
+
export * from "./cron-report-writer";
|
|
5
|
+
export * from "./cron-runner";
|
|
6
|
+
export * from "./cron-schema";
|
|
7
|
+
export * from "./cron-service";
|
|
8
|
+
export * from "./cron-spec-parser";
|
|
9
|
+
export * from "./cron-watcher";
|
|
1
10
|
export * from "./resource-limiter";
|
|
2
11
|
export * from "./schedule-command-service";
|
|
3
12
|
export * from "./schedule-service";
|
|
4
13
|
export * from "./scheduler";
|
|
14
|
+
export * from "./sqlite-cron-store";
|
|
5
15
|
export * from "./sqlite-schedule-store";
|
package/src/cron/scheduler.ts
CHANGED
|
@@ -60,6 +60,9 @@ function parseCronField(
|
|
|
60
60
|
from = resolveValue(rangePart);
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
+
if (from > to) {
|
|
64
|
+
throw new Error(`Invalid cron range "${rangePart}"`);
|
|
65
|
+
}
|
|
63
66
|
for (let value = from; value <= to; value += step) {
|
|
64
67
|
results.add(value);
|
|
65
68
|
}
|
|
@@ -70,6 +73,9 @@ function parseCronField(
|
|
|
70
73
|
if (dashIndex !== -1) {
|
|
71
74
|
const from = resolveValue(part.slice(0, dashIndex));
|
|
72
75
|
const to = resolveValue(part.slice(dashIndex + 1));
|
|
76
|
+
if (from > to) {
|
|
77
|
+
throw new Error(`Invalid cron range "${part}"`);
|
|
78
|
+
}
|
|
73
79
|
for (let value = from; value <= to; value += 1) {
|
|
74
80
|
results.add(value);
|
|
75
81
|
}
|
|
@@ -107,6 +113,14 @@ export interface ParsedCron {
|
|
|
107
113
|
daysOfWeek: number[];
|
|
108
114
|
}
|
|
109
115
|
|
|
116
|
+
interface CronDateParts {
|
|
117
|
+
month: number;
|
|
118
|
+
dayOfMonth: number;
|
|
119
|
+
dayOfWeek: number;
|
|
120
|
+
hour: number;
|
|
121
|
+
minute: number;
|
|
122
|
+
}
|
|
123
|
+
|
|
110
124
|
function getRequiredField(
|
|
111
125
|
fields: readonly string[],
|
|
112
126
|
index: number,
|
|
@@ -159,7 +173,131 @@ export function validateCronPattern(pattern: string): void {
|
|
|
159
173
|
parseCron(pattern);
|
|
160
174
|
}
|
|
161
175
|
|
|
162
|
-
export function
|
|
176
|
+
export function validateCronSchedule(
|
|
177
|
+
pattern: string,
|
|
178
|
+
timezone?: string,
|
|
179
|
+
after: number = Date.now(),
|
|
180
|
+
): void {
|
|
181
|
+
getNextCronTime(pattern, after, timezone);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const TIMEZONE_FORMATTERS = new Map<string, Intl.DateTimeFormat>();
|
|
185
|
+
const DOW_BY_SHORT_NAME = new Map(
|
|
186
|
+
DOW_NAMES.map((name, index) => [name, index] as const),
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
function normalizeTimezone(timezone: string | undefined): string | undefined {
|
|
190
|
+
const trimmed = timezone?.trim();
|
|
191
|
+
return trimmed ? trimmed : undefined;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function getTimezoneFormatter(timezone: string): Intl.DateTimeFormat {
|
|
195
|
+
const existing = TIMEZONE_FORMATTERS.get(timezone);
|
|
196
|
+
if (existing) return existing;
|
|
197
|
+
const formatter = new Intl.DateTimeFormat("en-US", {
|
|
198
|
+
timeZone: timezone,
|
|
199
|
+
calendar: "gregory",
|
|
200
|
+
numberingSystem: "latn",
|
|
201
|
+
year: "numeric",
|
|
202
|
+
month: "2-digit",
|
|
203
|
+
day: "2-digit",
|
|
204
|
+
weekday: "short",
|
|
205
|
+
hour: "2-digit",
|
|
206
|
+
minute: "2-digit",
|
|
207
|
+
hourCycle: "h23",
|
|
208
|
+
});
|
|
209
|
+
TIMEZONE_FORMATTERS.set(timezone, formatter);
|
|
210
|
+
return formatter;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function validateTimezone(timezone: string | undefined): void {
|
|
214
|
+
const normalized = normalizeTimezone(timezone);
|
|
215
|
+
if (!normalized) return;
|
|
216
|
+
getTimezoneFormatter(normalized).format(new Date());
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function getZonedCronDateParts(
|
|
220
|
+
timestampMs: number,
|
|
221
|
+
timezone: string,
|
|
222
|
+
): CronDateParts {
|
|
223
|
+
const parts = getTimezoneFormatter(timezone).formatToParts(
|
|
224
|
+
new Date(timestampMs),
|
|
225
|
+
);
|
|
226
|
+
const byType = new Map(parts.map((part) => [part.type, part.value]));
|
|
227
|
+
const weekdayRaw = byType.get("weekday")?.toLowerCase().slice(0, 3) ?? "";
|
|
228
|
+
const dayOfWeek = DOW_BY_SHORT_NAME.get(
|
|
229
|
+
weekdayRaw as (typeof DOW_NAMES)[number],
|
|
230
|
+
);
|
|
231
|
+
if (dayOfWeek === undefined) {
|
|
232
|
+
throw new Error(`Unable to resolve weekday for timezone "${timezone}"`);
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
month: Number(byType.get("month")),
|
|
236
|
+
dayOfMonth: Number(byType.get("day")),
|
|
237
|
+
dayOfWeek,
|
|
238
|
+
hour: Number(byType.get("hour")),
|
|
239
|
+
minute: Number(byType.get("minute")),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function getLocalCronDateParts(timestampMs: number): CronDateParts {
|
|
244
|
+
const date = new Date(timestampMs);
|
|
245
|
+
return {
|
|
246
|
+
month: date.getMonth() + 1,
|
|
247
|
+
dayOfMonth: date.getDate(),
|
|
248
|
+
dayOfWeek: date.getDay(),
|
|
249
|
+
hour: date.getHours(),
|
|
250
|
+
minute: date.getMinutes(),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function cronMatchesParts(cron: ParsedCron, parts: CronDateParts): boolean {
|
|
255
|
+
return (
|
|
256
|
+
cron.months.includes(parts.month) &&
|
|
257
|
+
cron.daysOfMonth.includes(parts.dayOfMonth) &&
|
|
258
|
+
cron.daysOfWeek.includes(parts.dayOfWeek) &&
|
|
259
|
+
cron.hours.includes(parts.hour) &&
|
|
260
|
+
cron.minutes.includes(parts.minute)
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function getNextCronTimeByMinuteScan(
|
|
265
|
+
pattern: string,
|
|
266
|
+
after: number,
|
|
267
|
+
timezone: string,
|
|
268
|
+
): number {
|
|
269
|
+
const cron = parseCron(pattern);
|
|
270
|
+
const next = new Date(after);
|
|
271
|
+
next.setSeconds(0, 0);
|
|
272
|
+
let nextMs = next.getTime() + 60_000;
|
|
273
|
+
|
|
274
|
+
const limit = new Date(after);
|
|
275
|
+
limit.setFullYear(limit.getFullYear() + 4);
|
|
276
|
+
const limitMs = limit.getTime();
|
|
277
|
+
|
|
278
|
+
while (nextMs <= limitMs) {
|
|
279
|
+
if (cronMatchesParts(cron, getZonedCronDateParts(nextMs, timezone))) {
|
|
280
|
+
return nextMs;
|
|
281
|
+
}
|
|
282
|
+
nextMs += 60_000;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
throw new Error(
|
|
286
|
+
`No cron occurrence found within 4 years for pattern "${pattern}" in timezone "${timezone}"`,
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export function getNextCronTime(
|
|
291
|
+
pattern: string,
|
|
292
|
+
after: number,
|
|
293
|
+
timezone?: string,
|
|
294
|
+
): number {
|
|
295
|
+
const normalizedTimezone = normalizeTimezone(timezone);
|
|
296
|
+
if (normalizedTimezone) {
|
|
297
|
+
validateTimezone(normalizedTimezone);
|
|
298
|
+
return getNextCronTimeByMinuteScan(pattern, after, normalizedTimezone);
|
|
299
|
+
}
|
|
300
|
+
|
|
163
301
|
const cron = parseCron(pattern);
|
|
164
302
|
let next = new Date(after);
|
|
165
303
|
next.setSeconds(0, 0);
|
|
@@ -169,11 +307,8 @@ export function getNextCronTime(pattern: string, after: number): number {
|
|
|
169
307
|
limit.setFullYear(limit.getFullYear() + 4);
|
|
170
308
|
|
|
171
309
|
while (next <= limit) {
|
|
172
|
-
const month
|
|
173
|
-
|
|
174
|
-
const dayOfWeek = next.getDay();
|
|
175
|
-
const hour = next.getHours();
|
|
176
|
-
const minute = next.getMinutes();
|
|
310
|
+
const { month, dayOfMonth, dayOfWeek, hour, minute } =
|
|
311
|
+
getLocalCronDateParts(next.getTime());
|
|
177
312
|
|
|
178
313
|
if (!cron.months.includes(month)) {
|
|
179
314
|
const targetMonth =
|