Skip to content

Commit

Permalink
Fix npm release ci. Improve tsdoc. Refactor build script.
Browse files Browse the repository at this point in the history
  • Loading branch information
Hexagon committed Oct 14, 2024
1 parent c45e868 commit 40dabf4
Show file tree
Hide file tree
Showing 10 changed files with 157 additions and 88 deletions.
3 changes: 2 additions & 1 deletion .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
6 changes: 0 additions & 6 deletions .github/dependabot.yml

This file was deleted.

10 changes: 9 additions & 1 deletion .github/workflows/npm-prerelease.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
12 changes: 10 additions & 2 deletions .github/workflows/npm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
83 changes: 25 additions & 58 deletions build/build.ts
Original file line number Diff line number Diff line change
@@ -1,67 +1,22 @@
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<void> {
const buildConfigs = configs?.map((config) => ({ ...baseConfig, ...config })) || [baseConfig];

try {
await Promise.all(buildConfigs.map((config) => esbuild.build(config)));
console.log("All builds completed successfully.");
} catch (error) {
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<void> {
// 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<void> {
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<T>(filePath: string): Promise<T> {
const jsonData = await readFile(filePath);
return JSON.parse(new TextDecoder().decode(jsonData)) as T;
Expand All @@ -70,7 +25,6 @@ async function readJson<T>(filePath: string): Promise<T> {
/**
* Now the actual build script
*/

import { dirname, fromFileUrl, resolve } from "@std/path";

/* Preparations - Work out paths */
Expand All @@ -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") {
Expand Down Expand Up @@ -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<object>(resolve(relativeProjectRoot, "build/package.template.json")),
version: denoConfig.version,
},
null,
2,
)),
);
}
1 change: 0 additions & 1 deletion deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
6 changes: 3 additions & 3 deletions src/croner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ class Cron {
);
}

this.name = options ? options.name : void 0;
this.name = options?.name;
this.options = CronOptionsHandler(options);

this._states = {
Expand Down Expand Up @@ -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);
Expand Down
32 changes: 22 additions & 10 deletions src/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down
79 changes: 79 additions & 0 deletions src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {};
Expand Down
13 changes: 7 additions & 6 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -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 (
Expand All @@ -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) {
Expand Down

0 comments on commit 40dabf4

Please sign in to comment.