@cronvello/sdk 0.1.1 → 0.1.2
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/CHANGELOG.md +44 -0
- package/README.md +162 -11
- package/dist/cli.cjs +877 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.js +874 -0
- package/dist/cli.js.map +1 -0
- package/dist/{dispatch-handler-Bnda1Ekq.d.cts → dispatch-handler-BNy-H5Nr.d.cts} +41 -2
- package/dist/{dispatch-handler-Bnda1Ekq.d.ts → dispatch-handler-BNy-H5Nr.d.ts} +41 -2
- package/dist/express.d.cts +1 -1
- package/dist/express.d.ts +1 -1
- package/dist/index.cjs +378 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +108 -3
- package/dist/index.d.ts +108 -3
- package/dist/index.js +365 -40
- package/dist/index.js.map +1 -1
- package/dist/next.d.cts +1 -1
- package/dist/next.d.ts +1 -1
- package/package.json +15 -3
package/dist/index.cjs
CHANGED
|
@@ -372,6 +372,109 @@ function enc(segment) {
|
|
|
372
372
|
return encodeURIComponent(segment);
|
|
373
373
|
}
|
|
374
374
|
|
|
375
|
+
// src/internal/cron.ts
|
|
376
|
+
var MACROS = /* @__PURE__ */ new Set([
|
|
377
|
+
"@yearly",
|
|
378
|
+
"@annually",
|
|
379
|
+
"@monthly",
|
|
380
|
+
"@weekly",
|
|
381
|
+
"@daily",
|
|
382
|
+
"@midnight",
|
|
383
|
+
"@hourly",
|
|
384
|
+
"@reboot"
|
|
385
|
+
]);
|
|
386
|
+
var MONTHS = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"];
|
|
387
|
+
var DOWS = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
|
|
388
|
+
var FIVE = [
|
|
389
|
+
{ min: 0, max: 59, label: "minute" },
|
|
390
|
+
{ min: 0, max: 23, label: "hour" },
|
|
391
|
+
{ min: 1, max: 31, label: "day-of-month" },
|
|
392
|
+
{ min: 1, max: 12, names: MONTHS, label: "month" },
|
|
393
|
+
{ min: 0, max: 7, names: DOWS, label: "day-of-week" }
|
|
394
|
+
// 0 and 7 are both Sunday
|
|
395
|
+
];
|
|
396
|
+
var SECONDS = { min: 0, max: 59, label: "second" };
|
|
397
|
+
function validateCron(expr) {
|
|
398
|
+
if (typeof expr !== "string" || !expr.trim()) {
|
|
399
|
+
return { valid: false, error: "schedule is empty" };
|
|
400
|
+
}
|
|
401
|
+
const trimmed = expr.trim();
|
|
402
|
+
if (trimmed.startsWith("@")) {
|
|
403
|
+
return MACROS.has(trimmed.toLowerCase()) ? { valid: true } : { valid: false, error: `unknown cron macro "${trimmed}" (try @daily, @hourly, \u2026)` };
|
|
404
|
+
}
|
|
405
|
+
const parts = trimmed.split(/\s+/);
|
|
406
|
+
if (parts.length !== 5 && parts.length !== 6) {
|
|
407
|
+
return {
|
|
408
|
+
valid: false,
|
|
409
|
+
error: `expected 5 fields (min hour dom month dow) or 6 with seconds, got ${parts.length}: "${trimmed}"`
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
const specs = parts.length === 6 ? [SECONDS, ...FIVE] : FIVE;
|
|
413
|
+
for (let i = 0; i < parts.length; i++) {
|
|
414
|
+
const err = validateField(parts[i], specs[i]);
|
|
415
|
+
if (err) return { valid: false, error: `invalid ${specs[i].label} "${parts[i]}": ${err}` };
|
|
416
|
+
}
|
|
417
|
+
return { valid: true };
|
|
418
|
+
}
|
|
419
|
+
function validateField(field2, spec) {
|
|
420
|
+
for (const term of field2.split(",")) {
|
|
421
|
+
const err = validateTerm(term, spec);
|
|
422
|
+
if (err) return err;
|
|
423
|
+
}
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
function validateTerm(term, spec) {
|
|
427
|
+
if (term === "") return "empty list item";
|
|
428
|
+
let step;
|
|
429
|
+
let base = term;
|
|
430
|
+
const slash = term.indexOf("/");
|
|
431
|
+
if (slash >= 0) {
|
|
432
|
+
base = term.slice(0, slash);
|
|
433
|
+
step = term.slice(slash + 1);
|
|
434
|
+
if (!/^\d+$/.test(step) || Number(step) === 0) return `step must be a positive integer`;
|
|
435
|
+
}
|
|
436
|
+
if (base === "*") return null;
|
|
437
|
+
const dash = base.indexOf("-");
|
|
438
|
+
if (dash > 0) {
|
|
439
|
+
const lo = resolveValue(base.slice(0, dash), spec);
|
|
440
|
+
const hi = resolveValue(base.slice(dash + 1), spec);
|
|
441
|
+
if (lo === null) return `"${base.slice(0, dash)}" is out of range ${spec.min}-${spec.max}`;
|
|
442
|
+
if (hi === null) return `"${base.slice(dash + 1)}" is out of range ${spec.min}-${spec.max}`;
|
|
443
|
+
if (lo > hi) return `range start ${lo} is greater than end ${hi}`;
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
if (step !== void 0 && base !== "*") {
|
|
447
|
+
return resolveValue(base, spec) === null ? `"${base}" is out of range ${spec.min}-${spec.max}` : null;
|
|
448
|
+
}
|
|
449
|
+
return resolveValue(base, spec) === null ? `"${base}" is out of range ${spec.min}-${spec.max}` : null;
|
|
450
|
+
}
|
|
451
|
+
function resolveValue(token, spec) {
|
|
452
|
+
if (/^\d+$/.test(token)) {
|
|
453
|
+
const n = Number(token);
|
|
454
|
+
return n >= spec.min && n <= spec.max ? n : null;
|
|
455
|
+
}
|
|
456
|
+
if (spec.names) {
|
|
457
|
+
const idx = spec.names.indexOf(token.toLowerCase());
|
|
458
|
+
if (idx >= 0) return idx + (spec.label === "month" ? 1 : 0);
|
|
459
|
+
}
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
function assertValidCron(expr, context = "") {
|
|
463
|
+
const res = validateCron(expr);
|
|
464
|
+
if (!res.valid) {
|
|
465
|
+
throw new Error(`${context ? `${context}: ` : ""}invalid cron schedule \u2014 ${res.error}`);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
function isValidTimeZone(tz) {
|
|
469
|
+
if (!tz || typeof tz !== "string") return false;
|
|
470
|
+
try {
|
|
471
|
+
new Intl.DateTimeFormat("en-US", { timeZone: tz });
|
|
472
|
+
return true;
|
|
473
|
+
} catch {
|
|
474
|
+
return false;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
375
478
|
// src/registry/verify.ts
|
|
376
479
|
var encoder = new TextEncoder();
|
|
377
480
|
function parseBearer(headerValue2) {
|
|
@@ -403,17 +506,54 @@ async function signHmacSha256(payload, secret) {
|
|
|
403
506
|
}
|
|
404
507
|
|
|
405
508
|
// src/registry/dispatch.ts
|
|
509
|
+
var NOOP_LOGGER = {};
|
|
406
510
|
function createDispatcher(state) {
|
|
407
|
-
const log = state.logger;
|
|
511
|
+
const log = state.logger ?? NOOP_LOGGER;
|
|
512
|
+
const hooks = state.hooks;
|
|
513
|
+
const maxBodyBytes = state.maxBodyBytes ?? 1048576;
|
|
514
|
+
async function invokeHandler(job, ctx) {
|
|
515
|
+
await safeHook(() => hooks?.onJobStart?.({ key: job.key, source: ctx.source, isAsync: ctx.isAsync }), log);
|
|
516
|
+
const startedAt = Date.now();
|
|
517
|
+
try {
|
|
518
|
+
const result = await job.config.handler(ctx);
|
|
519
|
+
const durationMs = Date.now() - startedAt;
|
|
520
|
+
log.debug?.(`[cronvello] job '${job.key}' completed in ${durationMs}ms`);
|
|
521
|
+
await safeHook(() => hooks?.onJobSuccess?.({ key: job.key, source: ctx.source, durationMs, result }), log);
|
|
522
|
+
return { durationMs, result };
|
|
523
|
+
} catch (err) {
|
|
524
|
+
const durationMs = Date.now() - startedAt;
|
|
525
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
526
|
+
log.error?.(`[cronvello] job '${job.key}' failed: ${error.message}`, { error: error.message });
|
|
527
|
+
await safeHook(() => hooks?.onJobError?.({ key: job.key, source: ctx.source, durationMs, error }), log);
|
|
528
|
+
throw error;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
function buildContext(key, job, req, body, isAsync, source) {
|
|
532
|
+
return {
|
|
533
|
+
key,
|
|
534
|
+
schedule: typeof body["schedule"] === "string" ? body["schedule"] : job.config.schedule ?? "",
|
|
535
|
+
payload: job.config.payload ?? {},
|
|
536
|
+
body,
|
|
537
|
+
headers: req?.headers ?? {},
|
|
538
|
+
isAsync,
|
|
539
|
+
source,
|
|
540
|
+
logger: log,
|
|
541
|
+
signal: req?.signal
|
|
542
|
+
};
|
|
543
|
+
}
|
|
408
544
|
async function handle(req) {
|
|
409
545
|
if (req.method.toUpperCase() !== "POST") {
|
|
410
546
|
return resp(405, { ok: false, error: "Method not allowed" });
|
|
411
547
|
}
|
|
412
548
|
const token = parseBearer(req.authorization);
|
|
413
549
|
if (!token || !timingSafeEqual(token, state.dispatchSecret)) {
|
|
414
|
-
log
|
|
550
|
+
log.warn?.("[cronvello] dispatch rejected: bad or missing bearer token");
|
|
415
551
|
return resp(401, { ok: false, error: "Unauthorized" });
|
|
416
552
|
}
|
|
553
|
+
if (byteLength(req.rawBody) > maxBodyBytes) {
|
|
554
|
+
log.warn?.(`[cronvello] dispatch rejected: body exceeds ${maxBodyBytes} bytes`);
|
|
555
|
+
return resp(413, { ok: false, error: "Payload too large" });
|
|
556
|
+
}
|
|
417
557
|
let body;
|
|
418
558
|
try {
|
|
419
559
|
const parsed = req.rawBody ? JSON.parse(req.rawBody) : {};
|
|
@@ -430,54 +570,44 @@ function createDispatcher(state) {
|
|
|
430
570
|
}
|
|
431
571
|
const job = state.jobs.get(key);
|
|
432
572
|
if (!job) {
|
|
433
|
-
log
|
|
573
|
+
log.warn?.(`[cronvello] dispatch for unknown job '${key}'`);
|
|
434
574
|
return resp(404, { ok: false, error: `Unknown job: ${key}` });
|
|
435
575
|
}
|
|
436
576
|
const callback = readCallback(body["_callback"]);
|
|
437
577
|
const isAsync = callback !== null;
|
|
438
|
-
const ctx =
|
|
439
|
-
key,
|
|
440
|
-
schedule: typeof body["schedule"] === "string" ? body["schedule"] : "",
|
|
441
|
-
payload: job.config.payload ?? {},
|
|
442
|
-
body,
|
|
443
|
-
headers: req.headers,
|
|
444
|
-
isAsync,
|
|
445
|
-
signal: req.signal
|
|
446
|
-
};
|
|
578
|
+
const ctx = buildContext(key, job, req, body, isAsync, "dispatch");
|
|
447
579
|
if (callback) {
|
|
448
|
-
const work = runAndReportCallback(job, ctx, callback, state.dispatchSecret, log);
|
|
580
|
+
const work = runAndReportCallback(job, ctx, callback, state.dispatchSecret, log, invokeHandler);
|
|
449
581
|
if (req.waitUntil) req.waitUntil(work);
|
|
450
582
|
return resp(202, { ok: true, job: key, accepted: true });
|
|
451
583
|
}
|
|
452
|
-
const startedAt = Date.now();
|
|
453
584
|
try {
|
|
454
|
-
const result = await job
|
|
455
|
-
log?.debug?.(`[cronvello] job '${key}' completed in ${Date.now() - startedAt}ms`);
|
|
585
|
+
const { result } = await invokeHandler(job, ctx);
|
|
456
586
|
return resp(200, { ok: true, job: key, result: result ?? null });
|
|
457
587
|
} catch (err) {
|
|
458
|
-
|
|
459
|
-
log?.error?.(`[cronvello] job '${key}' failed: ${message}`, { error: message });
|
|
460
|
-
return resp(500, { ok: false, job: key, error: message });
|
|
588
|
+
return resp(500, { ok: false, job: key, error: err instanceof Error ? err.message : String(err) });
|
|
461
589
|
}
|
|
462
590
|
}
|
|
463
|
-
|
|
591
|
+
async function runLocal(key, payload) {
|
|
592
|
+
const job = state.jobs.get(key);
|
|
593
|
+
if (!job) throw new Error(`Unknown job '${key}'. Known: ${[...state.jobs.keys()].join(", ") || "(none)"}`);
|
|
594
|
+
const body = { job: key, schedule: job.config.schedule, ...payload ?? {} };
|
|
595
|
+
const ctx = buildContext(key, job, null, body, false, "local");
|
|
596
|
+
const { result } = await invokeHandler(job, ctx);
|
|
597
|
+
return result;
|
|
598
|
+
}
|
|
599
|
+
return { handle, runLocal };
|
|
464
600
|
}
|
|
465
|
-
async function runAndReportCallback(job, ctx, callback, secret, log) {
|
|
466
|
-
const startedAt = Date.now();
|
|
601
|
+
async function runAndReportCallback(job, ctx, callback, secret, log, invokeHandler) {
|
|
467
602
|
let payload;
|
|
468
603
|
try {
|
|
469
|
-
const result = await job
|
|
470
|
-
payload = {
|
|
471
|
-
runId: callback.runId,
|
|
472
|
-
success: true,
|
|
473
|
-
durationMs: Date.now() - startedAt,
|
|
474
|
-
result: result ?? null
|
|
475
|
-
};
|
|
604
|
+
const { durationMs, result } = await invokeHandler(job, ctx);
|
|
605
|
+
payload = { runId: callback.runId, success: true, durationMs, result: result ?? null };
|
|
476
606
|
} catch (err) {
|
|
477
607
|
payload = {
|
|
478
608
|
runId: callback.runId,
|
|
479
609
|
success: false,
|
|
480
|
-
durationMs:
|
|
610
|
+
durationMs: 0,
|
|
481
611
|
error: err instanceof Error ? err.message : String(err)
|
|
482
612
|
};
|
|
483
613
|
}
|
|
@@ -491,14 +621,26 @@ async function runAndReportCallback(job, ctx, callback, secret, log) {
|
|
|
491
621
|
body: bodyStr
|
|
492
622
|
});
|
|
493
623
|
if (!res.ok) {
|
|
494
|
-
log
|
|
624
|
+
log.warn?.(`[cronvello] callback POST for job '${job.key}' returned ${res.status}`);
|
|
495
625
|
}
|
|
496
626
|
} catch (err) {
|
|
497
|
-
log
|
|
627
|
+
log.error?.(`[cronvello] failed to deliver callback for job '${job.key}'`, {
|
|
498
628
|
error: err instanceof Error ? err.message : String(err)
|
|
499
629
|
});
|
|
500
630
|
}
|
|
501
631
|
}
|
|
632
|
+
async function safeHook(fn, log) {
|
|
633
|
+
try {
|
|
634
|
+
await fn();
|
|
635
|
+
} catch (err) {
|
|
636
|
+
log.warn?.(`[cronvello] a lifecycle hook threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
function byteLength(s) {
|
|
640
|
+
if (!s) return 0;
|
|
641
|
+
if (typeof TextEncoder !== "undefined") return new TextEncoder().encode(s).length;
|
|
642
|
+
return s.length;
|
|
643
|
+
}
|
|
502
644
|
function readCallback(value) {
|
|
503
645
|
if (!value || typeof value !== "object") return null;
|
|
504
646
|
const v = value;
|
|
@@ -608,9 +750,9 @@ function buildDesiredTask(args) {
|
|
|
608
750
|
function diffTask(existing, desired, opts) {
|
|
609
751
|
const patch = {};
|
|
610
752
|
const changed = [];
|
|
611
|
-
const set = (
|
|
612
|
-
patch[
|
|
613
|
-
changed.push(
|
|
753
|
+
const set = (field2, value) => {
|
|
754
|
+
patch[field2] = value;
|
|
755
|
+
changed.push(field2);
|
|
614
756
|
};
|
|
615
757
|
if (existing.schedule !== desired.schedule) set("schedule", desired.schedule);
|
|
616
758
|
if (desired.timeZone !== void 0 && existing.timeZone !== desired.timeZone) set("timeZone", desired.timeZone);
|
|
@@ -742,10 +884,14 @@ var DEFAULT_DISPATCH_PATH = "/cronvello/dispatch";
|
|
|
742
884
|
var DEFAULT_TIME_ZONE = "Europe/Berlin";
|
|
743
885
|
function defineCronvello(config) {
|
|
744
886
|
validateConfig(config);
|
|
745
|
-
const
|
|
887
|
+
const validate = config.validateSchedules ?? true;
|
|
888
|
+
const defaultTimeZone = config.timeZone ?? DEFAULT_TIME_ZONE;
|
|
889
|
+
if (validate && !isValidTimeZone(defaultTimeZone)) {
|
|
890
|
+
throw new CronvelloConfigError(`Invalid \`timeZone\` "${defaultTimeZone}" \u2014 expected an IANA name like "Europe/Berlin" or "UTC".`);
|
|
891
|
+
}
|
|
892
|
+
const jobs = normalizeJobs(config.jobs, validate);
|
|
746
893
|
const dispatchPath = normalizePath(config.dispatchPath ?? DEFAULT_DISPATCH_PATH);
|
|
747
894
|
const dispatchUrl = joinUrl(config.appUrl, dispatchPath);
|
|
748
|
-
const defaultTimeZone = config.timeZone ?? DEFAULT_TIME_ZONE;
|
|
749
895
|
const client = new CronvelloClient({
|
|
750
896
|
apiKey: config.apiKey,
|
|
751
897
|
baseUrl: config.baseUrl ?? CRONVELLO_DEFAULT_BASE_URL,
|
|
@@ -756,7 +902,9 @@ function defineCronvello(config) {
|
|
|
756
902
|
const dispatcher = createDispatcher({
|
|
757
903
|
jobs,
|
|
758
904
|
dispatchSecret: config.dispatchSecret,
|
|
759
|
-
logger: config.logger
|
|
905
|
+
logger: config.logger,
|
|
906
|
+
...config.hooks ? { hooks: config.hooks } : {},
|
|
907
|
+
...config.maxBodyBytes !== void 0 ? { maxBodyBytes: config.maxBodyBytes } : {}
|
|
760
908
|
});
|
|
761
909
|
const app = {
|
|
762
910
|
client,
|
|
@@ -820,6 +968,7 @@ function defineCronvello(config) {
|
|
|
820
968
|
}
|
|
821
969
|
return client.tasks.runNow(task.id);
|
|
822
970
|
},
|
|
971
|
+
trigger: (key, payload) => dispatcher.runLocal(key, payload),
|
|
823
972
|
handle: dispatcher.handle,
|
|
824
973
|
expressHandler: () => expressHandler(app),
|
|
825
974
|
nextHandler: () => nextHandler(app),
|
|
@@ -827,6 +976,30 @@ function defineCronvello(config) {
|
|
|
827
976
|
};
|
|
828
977
|
return app;
|
|
829
978
|
}
|
|
979
|
+
((defineCronvello2) => {
|
|
980
|
+
function fromEnv(config, env = typeof process !== "undefined" ? process.env : {}) {
|
|
981
|
+
const apiKey = config.apiKey ?? env["CRONVELLO_API_KEY"];
|
|
982
|
+
const dispatchSecret = config.dispatchSecret ?? env["CRONVELLO_DISPATCH_SECRET"];
|
|
983
|
+
const appUrl = config.appUrl ?? env["CRONVELLO_APP_URL"] ?? env["PUBLIC_URL"];
|
|
984
|
+
const baseUrl = config.baseUrl ?? env["CRONVELLO_API_URL"];
|
|
985
|
+
const missing = [
|
|
986
|
+
!apiKey && "CRONVELLO_API_KEY",
|
|
987
|
+
!dispatchSecret && "CRONVELLO_DISPATCH_SECRET",
|
|
988
|
+
!appUrl && "CRONVELLO_APP_URL (or PUBLIC_URL)"
|
|
989
|
+
].filter(Boolean);
|
|
990
|
+
if (missing.length) {
|
|
991
|
+
throw new CronvelloConfigError(`defineCronvello.fromEnv() is missing required env: ${missing.join(", ")}.`);
|
|
992
|
+
}
|
|
993
|
+
return defineCronvello2({
|
|
994
|
+
...config,
|
|
995
|
+
apiKey,
|
|
996
|
+
dispatchSecret,
|
|
997
|
+
appUrl,
|
|
998
|
+
...baseUrl ? { baseUrl } : {}
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
defineCronvello2.fromEnv = fromEnv;
|
|
1002
|
+
})(defineCronvello || (defineCronvello = {}));
|
|
830
1003
|
function validateConfig(config) {
|
|
831
1004
|
if (!config) throw new CronvelloConfigError("defineCronvello requires a config object.");
|
|
832
1005
|
if (!config.appName || !config.appName.trim()) throw new CronvelloConfigError("`appName` is required.");
|
|
@@ -839,7 +1012,7 @@ function validateConfig(config) {
|
|
|
839
1012
|
}
|
|
840
1013
|
if (!config.jobs) throw new CronvelloConfigError("`jobs` is required.");
|
|
841
1014
|
}
|
|
842
|
-
function normalizeJobs(input) {
|
|
1015
|
+
function normalizeJobs(input, validate) {
|
|
843
1016
|
const map = /* @__PURE__ */ new Map();
|
|
844
1017
|
const entries = Array.isArray(input) ? input.map(({ key, ...rest }) => ({ key, config: rest })) : Object.entries(input).map(([key, config]) => ({ key, config }));
|
|
845
1018
|
for (const { key, config } of entries) {
|
|
@@ -853,6 +1026,15 @@ function normalizeJobs(input) {
|
|
|
853
1026
|
if (!config.schedule || !config.schedule.trim()) {
|
|
854
1027
|
throw new CronvelloConfigError(`Job '${trimmed}' is missing a schedule.`);
|
|
855
1028
|
}
|
|
1029
|
+
if (validate) {
|
|
1030
|
+
const cronCheck = validateCron(config.schedule);
|
|
1031
|
+
if (!cronCheck.valid) {
|
|
1032
|
+
throw new CronvelloConfigError(`Job '${trimmed}' has an invalid schedule "${config.schedule}" \u2014 ${cronCheck.error}.`);
|
|
1033
|
+
}
|
|
1034
|
+
if (config.timeZone !== void 0 && !isValidTimeZone(config.timeZone)) {
|
|
1035
|
+
throw new CronvelloConfigError(`Job '${trimmed}' has an invalid timeZone "${config.timeZone}" \u2014 expected an IANA name.`);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
856
1038
|
map.set(trimmed, { key: trimmed, config });
|
|
857
1039
|
}
|
|
858
1040
|
if (map.size === 0) throw new CronvelloConfigError("At least one job is required.");
|
|
@@ -895,6 +1077,149 @@ function buildRegistryRequest(config, dispatchUrl, defaultTimeZone, jobs, opts)
|
|
|
895
1077
|
};
|
|
896
1078
|
}
|
|
897
1079
|
|
|
1080
|
+
// src/registry/format.ts
|
|
1081
|
+
var ESC = String.fromCharCode(27);
|
|
1082
|
+
var ANSI = {
|
|
1083
|
+
reset: `${ESC}[0m`,
|
|
1084
|
+
dim: `${ESC}[2m`,
|
|
1085
|
+
bold: `${ESC}[1m`,
|
|
1086
|
+
green: `${ESC}[32m`,
|
|
1087
|
+
yellow: `${ESC}[33m`,
|
|
1088
|
+
red: `${ESC}[31m`,
|
|
1089
|
+
cyan: `${ESC}[36m`,
|
|
1090
|
+
gray: `${ESC}[90m`
|
|
1091
|
+
};
|
|
1092
|
+
var GLYPH = {
|
|
1093
|
+
created: { sign: "+", color: "green" },
|
|
1094
|
+
updated: { sign: "~", color: "yellow" },
|
|
1095
|
+
unchanged: { sign: "=", color: "gray" },
|
|
1096
|
+
deleted: { sign: "-", color: "red" },
|
|
1097
|
+
skipped: { sign: "\xB7", color: "cyan" }
|
|
1098
|
+
};
|
|
1099
|
+
function formatSyncResult(result, options = {}) {
|
|
1100
|
+
const paint = (s, c) => options.color ? `${ANSI[c]}${s}${ANSI.reset}` : s;
|
|
1101
|
+
const verb = options.dryRun ? "would sync" : "synced";
|
|
1102
|
+
const lines = [];
|
|
1103
|
+
lines.push(
|
|
1104
|
+
`${paint("Cronvello", "cyan")} ${verb} ${paint(`"${result.jobName}"`, "bold")} ${paint(`(${result.jobId})`, "gray")}`
|
|
1105
|
+
);
|
|
1106
|
+
const order = ["created", "updated", "deleted", "skipped", "unchanged"];
|
|
1107
|
+
const sorted = [...result.changes].sort((a, b) => order.indexOf(a.action) - order.indexOf(b.action));
|
|
1108
|
+
for (const c of sorted) {
|
|
1109
|
+
const g = GLYPH[c.action];
|
|
1110
|
+
const detail = c.changedFields && c.changedFields.length ? paint(` (${c.changedFields.join(", ")})`, "gray") : c.reason ? paint(` (${c.reason})`, "gray") : "";
|
|
1111
|
+
lines.push(` ${paint(g.sign, g.color)} ${paint(c.action.padEnd(9), g.color)} ${c.key}${detail}`);
|
|
1112
|
+
}
|
|
1113
|
+
const tally = [
|
|
1114
|
+
result.created && `${result.created} created`,
|
|
1115
|
+
result.updated && `${result.updated} updated`,
|
|
1116
|
+
result.unchanged && `${result.unchanged} unchanged`,
|
|
1117
|
+
result.deleted && `${result.deleted} deleted`,
|
|
1118
|
+
result.skipped && `${result.skipped} skipped`
|
|
1119
|
+
].filter(Boolean);
|
|
1120
|
+
const summary = tally.length ? tally.join(", ") : "no changes";
|
|
1121
|
+
const containerNote = result.jobCreated ? paint(" \xB7 container created", "gray") : "";
|
|
1122
|
+
lines.push(` ${paint(summary, "bold")}${containerNote}`);
|
|
1123
|
+
return lines.join("\n");
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
// src/schedule/index.ts
|
|
1127
|
+
var DOW = { sun: 0, mon: 1, tue: 2, wed: 3, thu: 4, fri: 5, sat: 6 };
|
|
1128
|
+
function cron(expression) {
|
|
1129
|
+
assertValidCron(expression, "cron()");
|
|
1130
|
+
return expression;
|
|
1131
|
+
}
|
|
1132
|
+
function every(interval) {
|
|
1133
|
+
const m = /^(\d+)\s*(s|sec|secs|m|min|mins|h|hr|hrs|hour|hours|d|day|days)$/i.exec(interval.trim());
|
|
1134
|
+
if (!m) throw new Error(`every(): cannot parse interval "${interval}" \u2014 use forms like "30s", "15m", "2h", "1d"`);
|
|
1135
|
+
const n = Number(m[1]);
|
|
1136
|
+
const unit = m[2].toLowerCase()[0];
|
|
1137
|
+
if (n < 1) throw new Error(`every(): interval must be at least 1, got ${n}`);
|
|
1138
|
+
let expr;
|
|
1139
|
+
if (unit === "s") {
|
|
1140
|
+
if (n > 59) throw new Error(`every(): seconds must be 1\u201359, got ${n}`);
|
|
1141
|
+
expr = `*/${n} * * * * *`;
|
|
1142
|
+
} else if (unit === "m") {
|
|
1143
|
+
if (n > 59) throw new Error(`every(): minutes must be 1\u201359 (use "1h" for 60), got ${n}`);
|
|
1144
|
+
expr = `*/${n} * * * *`;
|
|
1145
|
+
} else if (unit === "h") {
|
|
1146
|
+
if (n > 23) throw new Error(`every(): hours must be 1\u201323 (use "1d" for 24), got ${n}`);
|
|
1147
|
+
expr = `0 */${n} * * *`;
|
|
1148
|
+
} else {
|
|
1149
|
+
if (n > 31) throw new Error(`every(): days must be 1\u201331, got ${n}`);
|
|
1150
|
+
expr = n === 1 ? `0 0 * * *` : `0 0 */${n} * *`;
|
|
1151
|
+
}
|
|
1152
|
+
assertValidCron(expr, `every("${interval}")`);
|
|
1153
|
+
return expr;
|
|
1154
|
+
}
|
|
1155
|
+
function everyMinutes(n) {
|
|
1156
|
+
return every(`${n}m`);
|
|
1157
|
+
}
|
|
1158
|
+
function everyHours(n) {
|
|
1159
|
+
return every(`${n}h`);
|
|
1160
|
+
}
|
|
1161
|
+
function hourly(minute = 0) {
|
|
1162
|
+
const mm = field(minute, 0, 59, "minute");
|
|
1163
|
+
return cron(`${mm} * * * *`);
|
|
1164
|
+
}
|
|
1165
|
+
function daily(time = "00:00") {
|
|
1166
|
+
const { hh, mm } = parseTime(time);
|
|
1167
|
+
return cron(`${mm} ${hh} * * *`);
|
|
1168
|
+
}
|
|
1169
|
+
function weekly(day, time = "00:00") {
|
|
1170
|
+
const { hh, mm } = parseTime(time);
|
|
1171
|
+
return cron(`${mm} ${hh} * * ${weekday(day)}`);
|
|
1172
|
+
}
|
|
1173
|
+
function monthly(dayOfMonth, time = "00:00") {
|
|
1174
|
+
const { hh, mm } = parseTime(time);
|
|
1175
|
+
const dom = field(dayOfMonth, 1, 31, "day-of-month");
|
|
1176
|
+
return cron(`${mm} ${hh} ${dom} * *`);
|
|
1177
|
+
}
|
|
1178
|
+
function weekdays(time = "00:00") {
|
|
1179
|
+
const { hh, mm } = parseTime(time);
|
|
1180
|
+
return cron(`${mm} ${hh} * * 1-5`);
|
|
1181
|
+
}
|
|
1182
|
+
function weekends(time = "00:00") {
|
|
1183
|
+
const { hh, mm } = parseTime(time);
|
|
1184
|
+
return cron(`${mm} ${hh} * * 0,6`);
|
|
1185
|
+
}
|
|
1186
|
+
var schedule = {
|
|
1187
|
+
cron,
|
|
1188
|
+
every,
|
|
1189
|
+
everyMinutes,
|
|
1190
|
+
everyHours,
|
|
1191
|
+
hourly,
|
|
1192
|
+
daily,
|
|
1193
|
+
weekly,
|
|
1194
|
+
monthly,
|
|
1195
|
+
weekdays,
|
|
1196
|
+
weekends
|
|
1197
|
+
};
|
|
1198
|
+
function parseTime(time) {
|
|
1199
|
+
const m = /^(\d{1,2}):(\d{2})$/.exec(time.trim());
|
|
1200
|
+
if (!m) throw new Error(`Expected a time like "08:00" or "23:30", got "${time}"`);
|
|
1201
|
+
const hh = Number(m[1]);
|
|
1202
|
+
const mm = Number(m[2]);
|
|
1203
|
+
if (hh > 23) throw new Error(`Hour must be 0\u201323, got ${hh}`);
|
|
1204
|
+
if (mm > 59) throw new Error(`Minute must be 0\u201359, got ${mm}`);
|
|
1205
|
+
return { hh, mm };
|
|
1206
|
+
}
|
|
1207
|
+
function weekday(day) {
|
|
1208
|
+
if (typeof day === "number") {
|
|
1209
|
+
if (day < 0 || day > 6) throw new Error(`Weekday number must be 0 (Sun) \u2013 6 (Sat), got ${day}`);
|
|
1210
|
+
return day;
|
|
1211
|
+
}
|
|
1212
|
+
const n = DOW[day.toLowerCase()];
|
|
1213
|
+
if (n === void 0) throw new Error(`Unknown weekday "${day}" \u2014 use sun\u2026sat or 0\u20266`);
|
|
1214
|
+
return n;
|
|
1215
|
+
}
|
|
1216
|
+
function field(value, min, max, label) {
|
|
1217
|
+
if (!Number.isInteger(value) || value < min || value > max) {
|
|
1218
|
+
throw new Error(`${label} must be an integer ${min}\u2013${max}, got ${value}`);
|
|
1219
|
+
}
|
|
1220
|
+
return value;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
898
1223
|
// src/index.ts
|
|
899
1224
|
function generateDispatchSecret(bytes = 32) {
|
|
900
1225
|
const buf = new Uint8Array(bytes);
|
|
@@ -908,7 +1233,21 @@ exports.CronvelloClient = CronvelloClient;
|
|
|
908
1233
|
exports.CronvelloConfigError = CronvelloConfigError;
|
|
909
1234
|
exports.CronvelloError = CronvelloError;
|
|
910
1235
|
exports.CronvelloNetworkError = CronvelloNetworkError;
|
|
1236
|
+
exports.cron = cron;
|
|
1237
|
+
exports.daily = daily;
|
|
911
1238
|
exports.defineCronvello = defineCronvello;
|
|
1239
|
+
exports.every = every;
|
|
1240
|
+
exports.everyHours = everyHours;
|
|
1241
|
+
exports.everyMinutes = everyMinutes;
|
|
1242
|
+
exports.formatSyncResult = formatSyncResult;
|
|
912
1243
|
exports.generateDispatchSecret = generateDispatchSecret;
|
|
1244
|
+
exports.hourly = hourly;
|
|
1245
|
+
exports.isValidTimeZone = isValidTimeZone;
|
|
1246
|
+
exports.monthly = monthly;
|
|
1247
|
+
exports.schedule = schedule;
|
|
1248
|
+
exports.validateCron = validateCron;
|
|
1249
|
+
exports.weekdays = weekdays;
|
|
1250
|
+
exports.weekends = weekends;
|
|
1251
|
+
exports.weekly = weekly;
|
|
913
1252
|
//# sourceMappingURL=index.cjs.map
|
|
914
1253
|
//# sourceMappingURL=index.cjs.map
|