diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index f46ba12..271410c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -26,7 +26,8 @@ A clear and concise description of what you expected to happen. **System:** - OS: [e.g. iOS, Linux, Windows] - Runtime [Node.js, Deno, Bun, Chrome, Safari, Firefox] - - Version [e.g. 22] + - Runtime Version [e.g. 22] + - Croner Version [e.g. 9.0.0-dev.11] **Additional context** Add any other context about the problem here. diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 909d063..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,6 +0,0 @@ -version: 2 -updates: - - package-ecosystem: "npm" # See documentation for possible values - directory: "/" # Location of package manifests - schedule: - interval: "monthly" diff --git a/.github/workflows/npm-prerelease.yml b/.github/workflows/npm-prerelease.yml index f4318b4..317a544 100644 --- a/.github/workflows/npm-prerelease.yml +++ b/.github/workflows/npm-prerelease.yml @@ -9,11 +9,19 @@ jobs: - uses: actions/checkout@v3 with: ref: dev + + - name: Setup Deno + uses: denoland/setup-deno@v2 + with: + deno-version: "2.x" + - uses: actions/setup-node@v3.5.1 with: node-version: '16.x' registry-url: 'https://registry.npmjs.org' - - run: npm install + + - run: deno task build + - run: npm publish --tag dev env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index 2b170ef..4e152ce 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -9,11 +9,19 @@ jobs: - uses: actions/checkout@v3 with: ref: master + + - name: Setup Deno + uses: denoland/setup-deno@v2 + with: + deno-version: "2.x" + - uses: actions/setup-node@v3.5.1 with: - node-version: '16.x' + node-version: '18.x' registry-url: 'https://registry.npmjs.org' - - run: npm install + + - run: deno task build + - run: npm publish env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/build/build.ts b/build/build.ts index 87eee76..699914e 100644 --- a/build/build.ts +++ b/build/build.ts @@ -1,20 +1,15 @@ import esbuild from "esbuild"; -import { cp, readFile, rm, rmdir, writeFile } from "@cross/fs"; -import { expandGlob } from "@std/fs"; import { dtsPlugin } from "esbuild-plugin-d.ts"; +import { cp, readFile, writeFile } from "@cross/fs"; /** - * Helper: Builds one or more esbuild configurations. - * @param baseConfig - The base esbuild configuration. - * @param configs - An optional array of configurations extending the base configuration. - * @returns A promise that resolves when all builds are complete. + * Build helpers */ export async function build( baseConfig: esbuild.BuildOptions, configs?: esbuild.BuildOptions[], ): Promise { const buildConfigs = configs?.map((config) => ({ ...baseConfig, ...config })) || [baseConfig]; - try { await Promise.all(buildConfigs.map((config) => esbuild.build(config))); console.log("All builds completed successfully."); @@ -22,46 +17,6 @@ export async function build( console.error("Build failed:", error); } } - -/** - * Helper: Recursively removes files and folders based on the provided glob patterns. - * - * @param files - An array of glob patterns for files to be removed. - * @param folders - An array of glob patterns for folders to be removed. - * @returns A promise that resolves when all specified files and folders have been removed. - */ -async function rimraf(files: string[]): Promise { - // Handle files - for (const pattern of files) { - const filePaths = await Array.fromAsync(expandGlob(pattern)); - for (const filePath of filePaths) { - if (filePath.isFile) { - await rm(filePath.name); - } else if (filePath.isDirectory) { - await rmdir(filePath.name, { - recursive: true, - }); - } - } - } -} - -/** - * Helper: Writes a JavaScript object to a JSON file. - * @param filePath - The path to the file where the JSON will be written. - * @param data - The JavaScript object to write to the file. - * @returns A promise that resolves when the file has been written. - */ -async function writeJson(filePath: string, data: object): Promise { - const jsonData = JSON.stringify(data, null, 2); - await writeFile(filePath, new TextEncoder().encode(jsonData)); -} - -/** - * Helper: Reads a JSON file and parses its content to a JavaScript object. - * @param filePath - The path to the JSON file to read. - * @returns A promise that resolves to the parsed JavaScript object. - */ async function readJson(filePath: string): Promise { const jsonData = await readFile(filePath); return JSON.parse(new TextDecoder().decode(jsonData)) as T; @@ -70,7 +25,6 @@ async function readJson(filePath: string): Promise { /** * Now the actual build script */ - import { dirname, fromFileUrl, resolve } from "@std/path"; /* Preparations - Work out paths */ @@ -81,12 +35,18 @@ const resolvedDistPath = resolve(relativeProjectRoot, "dist"); /* Handle argument `clean`: Rimraf build artifacts */ if (Deno.args[1] === "clean") { - await rimraf([ - "package.json", - "tsconfig.json", - "node_modules", - "dist", - ]); + for ( + const filePath of [ + "package.json", + "tsconfig.json", + "node_modules", + "dist", + ] + ) { + try { + await Deno.remove(filePath, { recursive: true }); + } catch (_e) { /* No-op */ } + } /* Handle argument `build`: Transpile and generate typings */ } else if (Deno.args[1] === "build") { @@ -143,8 +103,15 @@ if (Deno.args[1] === "clean") { const denoConfig = await readJson<{ version: string }>(resolve(relativeProjectRoot, "deno.json")); // Write package.json - await writeJson(resolve(relativeProjectRoot, "package.json"), { - ...await readJson(resolve(relativeProjectRoot, "build/package.template.json")), - version: denoConfig.version, - }); + await writeFile( + resolve(relativeProjectRoot, "package.json"), + new TextEncoder().encode(JSON.stringify( + { + ...await readJson(resolve(relativeProjectRoot, "build/package.template.json")), + version: denoConfig.version, + }, + null, + 2, + )), + ); } diff --git a/deno.json b/deno.json index 9d6aa31..3d4665f 100644 --- a/deno.json +++ b/deno.json @@ -23,7 +23,6 @@ "@cross/fs": "jsr:@cross/fs@~0.1.11", "@cross/test": "jsr:@cross/test@~0.0.9", "@std/assert": "jsr:@std/assert@~1.0.6", - "@std/fs": "jsr:@std/fs@^1.0.4", "@std/path": "jsr:@std/path@~1.0.6", "esbuild": "npm:esbuild@~0.24.0", "esbuild-plugin-d.ts": "npm:esbuild-plugin-d.ts@~1.3.1" diff --git a/src/croner.ts b/src/croner.ts index 86620cc..db918e2 100644 --- a/src/croner.ts +++ b/src/croner.ts @@ -125,7 +125,7 @@ class Cron { ); } - this.name = options ? options.name : void 0; + this.name = options?.name; this.options = CronOptionsHandler(options); this._states = { @@ -175,8 +175,8 @@ class Cron { /** * Find next runtime, based on supplied date. Strips milliseconds. * - * @param prev - Date to start from - * @returns Next run time + * @param prev - Optional. Date to start from. Can be a CronDate, Date object, or a string representing a date. + * @returns The next run time as a Date object, or null if there is no next run. */ public nextRun(prev?: CronDate | Date | string | null): Date | null { const next = this._next(prev); diff --git a/src/date.ts b/src/date.ts index 8b057bb..eecfe21 100644 --- a/src/date.ts +++ b/src/date.ts @@ -338,14 +338,26 @@ class CronDate { } /** - * Increment to next run time recursively + * Increment to next run time recursively. * - * This function is currently capped at year 3000. Do you have a reason to go further? Open an issue on GitHub! + * This function traverses the date components (year, month, day, hour, minute, second) + * to find the next date and time that matches the cron pattern. It uses a recursive + * approach to handle the dependencies between different components. For example, + * if the day changes, the hour, minute, and second need to be reset. * - * @param pattern The pattern used to increment current state - * @param options Cron options used for incrementing - * @param doing Which part to increment, 0 represent first item of RecursionSteps-array etc. - * @return Returns itthis for chaining, or null if increment wasnt possible + * The recursion is currently limited to the year 3000 to prevent potential + * infinite loops or excessive stack depth. If you need to schedule beyond + * the year 3000, please open an issue on GitHub to discuss possible solutions. + * + * @param pattern The cron pattern used to determine the next run time. + * @param options The cron options that influence the incrementing behavior. + * @param doing The index of the `RecursionSteps` array indicating the current + * date component being processed. 0 represents "month", 1 represents "day", etc. + * + * @returns This `CronDate` instance for chaining, or null if incrementing + * was not possible (e.g., reached year 3000 limit or no matching date). + * + * @private */ private recurse(pattern: CronPattern, options: CronOptions, doing: number): CronDate | null { // Find next month (or whichever part we're at) @@ -393,10 +405,10 @@ class CronDate { /** * Increment to next run time * - * @param pattern The pattern used to increment current state - * @param options Cron options used for incrementing - * @param hasPreviousRun If this run should adhere to minimum interval - * @return Returns itthis for chaining, or null if increment wasnt possible + * @param pattern The pattern used to increment the current date. + * @param options Cron options used for incrementing. + * @param hasPreviousRun True if there was a previous run, false otherwise. This is used to determine whether to apply the minimum interval. + * @returns This CronDate instance for chaining, or null if incrementing was not possible (e.g., reached year 3000 limit). */ public increment( pattern: CronPattern, diff --git a/src/options.ts b/src/options.ts index d879932..cd80d21 100644 --- a/src/options.ts +++ b/src/options.ts @@ -4,23 +4,102 @@ import type { Cron } from "./croner.ts"; type CatchCallbackFn = (e: unknown, job: Cron) => void; type ProtectCallbackFn = (job: Cron) => void; +/** + * Options for configuring cron jobs. + * + * @interface + */ interface CronOptions { + /** + * The name of the cron job. If provided, the job will be added to the + * `scheduledJobs` array, allowing it to be accessed by name. + */ name?: string; + + /** + * If true, the job will be paused initially. + * @default false + */ paused?: boolean; + + /** + * If true, the job will be stopped permanently. + * @default false + */ kill?: boolean; + + /** + * If true, errors thrown by the job function will be caught. + * If a function is provided, it will be called with the error and the job instance. + * @default false + */ catch?: boolean | CatchCallbackFn; + + /** + * If true, the underlying timer will be unreferenced, allowing the Node.js + * process to exit even if the job is still running. + * @default false + */ unref?: boolean; + + /** + * The maximum number of times the job will run. + * @default Infinity + */ maxRuns?: number; + + /** + * The minimum interval between job executions, in seconds. + * @default 1 + */ interval?: number; + + /** + * If true, prevents the job from running if the previous execution is still in progress. + * If a function is provided, it will be called if the job is blocked. + * @default false + */ protect?: boolean | ProtectCallbackFn; + + /** + * The date and time at which the job should start running. + */ startAt?: string | Date | CronDate; + + /** + * The date and time at which the job should stop running. + */ stopAt?: string | Date | CronDate; + + /** + * The timezone for the cron job. + */ timezone?: string; + + /** + * The UTC offset for the cron job, in minutes. + */ utcOffset?: number; + + /** + * If true, enables legacy mode for compatibility with older cron implementations. + * @default true + */ legacyMode?: boolean; + + /** + * An optional context object that will be passed to the job function. + */ context?: unknown; } +/** + * Processes and validates cron options. + * + * @param options The cron options to handle. + * @returns The processed and validated cron options. + * @throws {Error} If any of the options are invalid. + */ function CronOptionsHandler(options?: CronOptions): CronOptions { if (options === void 0) { options = {}; diff --git a/src/utils.ts b/src/utils.ts index ff40dbb..84c8de6 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,9 +1,9 @@ /** - * Helper function to check if a variable is a function - * @private + * Helper function to check if a variable is a function. * - * @param {?} [v] - Variable to check - * @returns {boolean} + * @param v The variable to check. + * @returns True if the variable is a function, false otherwise. + * @private */ function isFunction(v: unknown) { return ( @@ -14,9 +14,10 @@ function isFunction(v: unknown) { } /** - * Helper function to unref a timer in both Deno and Node + * Helper function to unref a timer in both Deno and Node.js. + * + * @param timer The timer to unref. * @private - * @param {unknown} [timer] - Timer to unref */ //@ts-ignore Cross Runtime function unrefTimer(timer: NodeJS.Timeout | number) {