diff --git a/docs/runtime/cron.mdx b/docs/runtime/cron.mdx index ca9f32a242..40b40bb447 100644 --- a/docs/runtime/cron.mdx +++ b/docs/runtime/cron.mdx @@ -34,9 +34,9 @@ console.log(next); // => next quarter-hour boundary ### Parameters -| Parameter | Type | Description | -|-----------|------|-------------| -| `expression` | `string` | A 5-field cron expression or predefined nickname | +| Parameter | Type | Description | +| -------------- | ---------------- | -------------------------------------------------------- | +| `expression` | `string` | A 5-field cron expression or predefined nickname | | `relativeDate` | `Date \| number` | Starting point for the search (defaults to `Date.now()`) | ### Returns @@ -63,22 +63,22 @@ console.log(second); // => 2025-01-15T12:00:00.000Z Standard 5-field format: `minute hour day-of-month month day-of-week` -| Field | Values | Special characters | -|-------|--------|--------------------| -| Minute | `0`–`59` | `*` `,` `-` `/` | -| Hour | `0`–`23` | `*` `,` `-` `/` | -| Day of month | `1`–`31` | `*` `,` `-` `/` | -| Month | `1`–`12` or `JAN`–`DEC` | `*` `,` `-` `/` | -| Day of week | `0`–`7` or `SUN`–`SAT` | `*` `,` `-` `/` | +| Field | Values | Special characters | +| ------------ | ----------------------- | ------------------ | +| Minute | `0`–`59` | `*` `,` `-` `/` | +| Hour | `0`–`23` | `*` `,` `-` `/` | +| Day of month | `1`–`31` | `*` `,` `-` `/` | +| Month | `1`–`12` or `JAN`–`DEC` | `*` `,` `-` `/` | +| Day of week | `0`–`7` or `SUN`–`SAT` | `*` `,` `-` `/` | ### Special characters -| Character | Description | Example | -|-----------|-------------|---------| -| `*` | All values | `* * * * *` — every minute | -| `,` | List | `1,15 * * * *` — minute 1 and 15 | -| `-` | Range | `9-17 * * * *` — minutes 9 through 17 | -| `/` | Step | `*/15 * * * *` — every 15 minutes | +| Character | Description | Example | +| --------- | ----------- | ------------------------------------- | +| `*` | All values | `* * * * *` — every minute | +| `,` | List | `1,15 * * * *` — minute 1 and 15 | +| `-` | Range | `9-17 * * * *` — minutes 9 through 17 | +| `/` | Step | `*/15 * * * *` — every 15 minutes | ### Named values @@ -98,13 +98,13 @@ Both `0` and `7` mean Sunday in the weekday field. ### Predefined nicknames -| Nickname | Equivalent | Description | -|----------|------------|-------------| +| Nickname | Equivalent | Description | +| ----------------------- | ----------- | ------------------------- | | `@yearly` / `@annually` | `0 0 1 1 *` | Once a year (January 1st) | -| `@monthly` | `0 0 1 * *` | Once a month (1st day) | -| `@weekly` | `0 0 * * 0` | Once a week (Sunday) | -| `@daily` / `@midnight` | `0 0 * * *` | Once a day (midnight) | -| `@hourly` | `0 * * * *` | Once an hour | +| `@monthly` | `0 0 1 * *` | Once a month (1st day) | +| `@weekly` | `0 0 * * 0` | Once a week (Sunday) | +| `@daily` / `@midnight` | `0 0 * * *` | Once a day (midnight) | +| `@hourly` | `0 * * * *` | Once an hour | ```ts const next = Bun.cron.parse("@daily"); @@ -134,16 +134,16 @@ await Bun.cron("./worker.ts", "30 2 * * MON", "weekly-report"); ### Parameters -| Parameter | Type | Description | -|-----------|------|-------------| -| `path` | `string` | Path to the script (resolved relative to caller) | -| `schedule` | `string` | Cron expression or nickname | -| `title` | `string` | Unique job identifier (alphanumeric, hyphens, underscores) | +| Parameter | Type | Description | +| ---------- | -------- | ---------------------------------------------------------- | +| `path` | `string` | Path to the script (resolved relative to caller) | +| `schedule` | `string` | Cron expression or nickname | +| `title` | `string` | Unique job identifier (alphanumeric, hyphens, underscores) | Re-registering with the same `title` overwrites the existing job in-place — the old schedule is replaced, not duplicated. ```ts -await Bun.cron("./worker.ts", "0 * * * *", "my-job"); // every hour +await Bun.cron("./worker.ts", "0 * * * *", "my-job"); // every hour await Bun.cron("./worker.ts", "*/15 * * * *", "my-job"); // replaces: every 15 min ``` @@ -154,8 +154,8 @@ The registered script must export a default object with a `scheduled()` method, ```ts worker.ts export default { scheduled(controller) { - console.log(controller.cron); // "30 2 * * 1" - console.log(controller.type); // "scheduled" + console.log(controller.cron); // "30 2 * * 1" + console.log(controller.type); // "scheduled" console.log(controller.scheduledTime); // 1737340200000 }, }; @@ -302,10 +302,10 @@ await Bun.cron.remove("weekly-report"); This reverses what `Bun.cron()` did: -| Platform | What `remove()` does | -|----------|---------------------| -| Linux | Edits crontab to remove the entry and its marker comment | -| macOS | Runs `launchctl bootout` and deletes the plist file | -| Windows | Runs `schtasks /delete` to remove the scheduled task | +| Platform | What `remove()` does | +| -------- | -------------------------------------------------------- | +| Linux | Edits crontab to remove the entry and its marker comment | +| macOS | Runs `launchctl bootout` and deletes the plist file | +| Windows | Runs `schtasks /delete` to remove the scheduled task | Removing a job that doesn't exist resolves without error. diff --git a/src/bun.js.zig b/src/bun.js.zig index 1945c07d00..c48879c086 100644 --- a/src/bun.js.zig +++ b/src/bun.js.zig @@ -596,13 +596,6 @@ const VirtualMachine = jsc.VirtualMachine; const string = []const u8; -const CPUProfiler = @import("./bun.js/bindings/BunCPUProfiler.zig"); -const HeapProfiler = @import("./bun.js/bindings/BunHeapProfiler.zig"); -const options = @import("./options.zig"); -const std = @import("std"); -const Command = @import("./cli.zig").Command; -const which = @import("./which.zig").which; - /// Escape a string for safe embedding in a JS double-quoted string literal. /// Escapes backslashes, double quotes, newlines, etc. fn escapeForJSString(allocator: std.mem.Allocator, input: []const u8) ![]const u8 { @@ -629,6 +622,13 @@ fn escapeForJSString(allocator: std.mem.Allocator, input: []const u8) ![]const u return result.toOwnedSlice(); } +const CPUProfiler = @import("./bun.js/bindings/BunCPUProfiler.zig"); +const HeapProfiler = @import("./bun.js/bindings/BunHeapProfiler.zig"); +const options = @import("./options.zig"); +const std = @import("std"); +const Command = @import("./cli.zig").Command; +const which = @import("./which.zig").which; + const bun = @import("bun"); const Global = bun.Global; const Output = bun.Output; diff --git a/src/bun.js/api/BunObject.zig b/src/bun.js/api/BunObject.zig index 32b509ef08..af1dc2770f 100644 --- a/src/bun.js/api/BunObject.zig +++ b/src/bun.js/api/BunObject.zig @@ -69,7 +69,7 @@ pub const BunObject = struct { pub const YAML = toJSLazyPropertyCallback(Bun.getYAMLObject); pub const Transpiler = toJSLazyPropertyCallback(Bun.getTranspilerConstructor); pub const argv = toJSLazyPropertyCallback(Bun.getArgv); - pub const cron = toJSLazyPropertyCallback(@import("cron.zig").getCronObject); + pub const cron = toJSLazyPropertyCallback(@import("./cron.zig").getCronObject); pub const cwd = toJSLazyPropertyCallback(Bun.getCWD); pub const embeddedFiles = toJSLazyPropertyCallback(Bun.getEmbeddedFiles); pub const enableANSIColors = toJSLazyPropertyCallback(Bun.enableANSIColors); diff --git a/src/bun.js/api/bun/process.zig b/src/bun.js/api/bun/process.zig index 4365e9e609..c976e15d09 100644 --- a/src/bun.js/api/bun/process.zig +++ b/src/bun.js/api/bun/process.zig @@ -2269,6 +2269,9 @@ const std = @import("std"); const MultiRunProcessHandle = @import("../../../cli/multi_run.zig").ProcessHandle; const ProcessHandle = @import("../../../cli/filter_run.zig").ProcessHandle; +const CronRegisterJob = @import("../cron.zig").CronRegisterJob; +const CronRemoveJob = @import("../cron.zig").CronRemoveJob; + const bun = @import("bun"); const Environment = bun.Environment; const Output = bun.Output; @@ -2279,8 +2282,6 @@ const uv = bun.windows.libuv; const LifecycleScriptSubprocess = bun.install.LifecycleScriptSubprocess; const SecurityScanSubprocess = bun.install.SecurityScanSubprocess; -const CronRegisterJob = @import("../cron.zig").CronRegisterJob; -const CronRemoveJob = @import("../cron.zig").CronRemoveJob; const jsc = bun.jsc; const Subprocess = jsc.Subprocess; diff --git a/src/bun.js/api/cron.zig b/src/bun.js/api/cron.zig index fca54d75fb..18e0e0e8c3 100644 --- a/src/bun.js/api/cron.zig +++ b/src/bun.js/api/cron.zig @@ -41,9 +41,6 @@ fn CronJobBase(comptime Self: type) type { }; } -const OutputReader = bun.io.BufferedReader; -const Process = bun.spawn.Process; - // ============================================================================ // CronRegisterJob // ============================================================================ @@ -492,17 +489,14 @@ pub const CronRegisterJob = struct { }; var argv = [_:null]?[*:0]const u8{ - "schtasks", "/create", - "/tn", task_name.ptr, - "/tr", tr_cmd.ptr, - "/sc", schtasks_params.sc, - "/mo", schtasks_params.mo, - if (schtasks_params.start_time != null) "/st" else null, - schtasks_params.start_time, - if (schtasks_params.day_spec != null) "/d" else null, - schtasks_params.day_spec, - "/f", - null, + "schtasks", "/create", + "/tn", task_name.ptr, + "/tr", tr_cmd.ptr, + "/sc", schtasks_params.sc, + "/mo", schtasks_params.mo, + if (schtasks_params.start_time != null) "/st" else null, schtasks_params.start_time, + if (schtasks_params.day_spec != null) "/d" else null, schtasks_params.day_spec, + "/f", null, }; this.spawnCmd(&argv, .ignore, .ignore); } @@ -1130,9 +1124,10 @@ fn makeTempPath(comptime prefix: []const u8, title: []const u8) ![:0]const u8 { return allocPrintZ(bun.default_allocator, "/tmp/" ++ prefix ++ "{s}-{x}.tmp", .{ title, rand }); } -const CronExpression = @import("cron_parser.zig").CronExpression; - const std = @import("std"); +const CronExpression = @import("./cron_parser.zig").CronExpression; + const bun = @import("bun"); const jsc = bun.jsc; -const Output = bun.Output; +const OutputReader = bun.io.BufferedReader; +const Process = bun.spawn.Process; diff --git a/src/bun.js/api/cron_parser.zig b/src/bun.js/api/cron_parser.zig index 39cd486bdd..05c764d6b0 100644 --- a/src/bun.js/api/cron_parser.zig +++ b/src/bun.js/api/cron_parser.zig @@ -179,22 +179,22 @@ fn parseNickname(expr: []const u8) ?CronExpression { } const weekday_map = bun.ComptimeStringMap(u7, .{ - .{ "sun", 0 }, .{ "mon", 1 }, .{ "tue", 2 }, - .{ "wed", 3 }, .{ "thu", 4 }, .{ "fri", 5 }, - .{ "sat", 6 }, .{ "sunday", 0 }, .{ "monday", 1 }, + .{ "sun", 0 }, .{ "mon", 1 }, .{ "tue", 2 }, + .{ "wed", 3 }, .{ "thu", 4 }, .{ "fri", 5 }, + .{ "sat", 6 }, .{ "sunday", 0 }, .{ "monday", 1 }, .{ "tuesday", 2 }, .{ "wednesday", 3 }, .{ "thursday", 4 }, .{ "friday", 5 }, .{ "saturday", 6 }, }); const month_map = bun.ComptimeStringMap(u7, .{ - .{ "jan", 1 }, .{ "feb", 2 }, .{ "mar", 3 }, - .{ "apr", 4 }, .{ "may", 5 }, .{ "jun", 6 }, - .{ "jul", 7 }, .{ "aug", 8 }, .{ "sep", 9 }, - .{ "oct", 10 }, .{ "nov", 11 }, .{ "dec", 12 }, - .{ "january", 1 }, .{ "february", 2 }, .{ "march", 3 }, - .{ "april", 4 }, .{ "may", 5 }, .{ "june", 6 }, - .{ "july", 7 }, .{ "august", 8 }, .{ "september", 9 }, - .{ "october", 10 }, .{ "november", 11 }, .{ "december", 12 }, + .{ "jan", 1 }, .{ "feb", 2 }, .{ "mar", 3 }, + .{ "apr", 4 }, .{ "may", 5 }, .{ "jun", 6 }, + .{ "jul", 7 }, .{ "aug", 8 }, .{ "sep", 9 }, + .{ "oct", 10 }, .{ "nov", 11 }, .{ "dec", 12 }, + .{ "january", 1 }, .{ "february", 2 }, .{ "march", 3 }, + .{ "april", 4 }, .{ "may", 5 }, .{ "june", 6 }, + .{ "july", 7 }, .{ "august", 8 }, .{ "september", 9 }, + .{ "october", 10 }, .{ "november", 11 }, .{ "december", 12 }, }); // ============================================================================ @@ -309,5 +309,6 @@ fn formatBitfield(w: anytype, comptime T: type, bits: T, min: u8, max: u8) void } const std = @import("std"); + const bun = @import("bun"); const jsc = bun.jsc; diff --git a/test/js/bun/cron/cron.test.ts b/test/js/bun/cron/cron.test.ts index e04d98896b..61a2e0d4bd 100644 --- a/test/js/bun/cron/cron.test.ts +++ b/test/js/bun/cron/cron.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test"; import { bunEnv, bunExe, isLinux, tempDir } from "harness"; -import { writeFileSync, unlinkSync } from "node:fs"; +import { unlinkSync, writeFileSync } from "node:fs"; function readCrontab(): string { const result = Bun.spawnSync({ @@ -17,7 +17,9 @@ function writeCrontab(content: string) { try { Bun.spawnSync({ cmd: ["/usr/bin/crontab", tmpFile] }); } finally { - try { unlinkSync(tmpFile); } catch {} + try { + unlinkSync(tmpFile); + } catch {} } }