diff --git a/build/adonis-typings/container.d.ts b/build/adonis-typings/container.d.ts new file mode 100644 index 0000000..52c44ac --- /dev/null +++ b/build/adonis-typings/container.d.ts @@ -0,0 +1,7 @@ +/// +declare module '@ioc:Adonis/Core/Application' { + import { SchedulerContract } from '@ioc:Verful/Scheduler'; + interface ContainerBindings { + 'Verful/Scheduler': SchedulerContract; + } +} diff --git a/build/adonis-typings/container.js b/build/adonis-typings/container.js new file mode 100644 index 0000000..e69de29 diff --git a/build/adonis-typings/index.d.ts b/build/adonis-typings/index.d.ts new file mode 100644 index 0000000..8d1ad49 --- /dev/null +++ b/build/adonis-typings/index.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/build/adonis-typings/index.js b/build/adonis-typings/index.js new file mode 100644 index 0000000..e69de29 diff --git a/build/adonis-typings/scheduler.d.ts b/build/adonis-typings/scheduler.d.ts new file mode 100644 index 0000000..28f27da --- /dev/null +++ b/build/adonis-typings/scheduler.d.ts @@ -0,0 +1,62 @@ +declare module '@ioc:Verful/Scheduler' { + export type ScheduleHandler = () => void; + export type Time = `${string}:${string}`; + export type Condition = () => boolean | Promise; + export interface ManagesFrequenciesContract { + expression: string; + cron(expression: string): this; + everyMinute(): this; + everyTwoMinutes(): this; + everyThreeMinutes(): this; + everyFourMinutes(): this; + everyFiveMinutes(): this; + everyTenMinutes(): this; + everyFifteenMinutes(): this; + everyThirtyMinutes(): this; + hourly(): this; + hourlyAt(offset: number | number[]): this; + everyTwoHours(): this; + everyFourHours(): this; + everySixHours(): this; + daily(): this; + dailyAt(time: Time): this; + twiceDaily(): this; + twiceDailyAt(first: number, second: number, offset: number): this; + weekly(): this; + weeklyOn(daysOfWeek: number | number[] | string, time: Time): this; + monthly(): this; + monthlyOn(dayOfMonth: number, time: Time): this; + twiceMonthly(first: number, second: number, time: Time): this; + lastDayOfMonth(time: Time): this; + yearly(): this; + yearlyOn(month: number, dayOfMonth?: string | number, time?: Time): this; + days(days: number | number[] | string): this; + weekdays(): this; + weekends(): this; + sundays(): this; + mondays(): this; + tuesdays(): this; + wednesdays(): this; + thursdays(): this; + fridays(): this; + saturdays(): this; + } + export interface ScheduleContract extends ManagesFrequenciesContract { + command: ScheduleHandler; + filters: Condition[]; + rejects: Condition[]; + skip(condition: Condition): this; + when(condition: Condition): this; + between(start: Time, end: Time): this; + unlessBetween(start: Time, end: Time): this; + environments(environments: Array<'production' | 'development' | 'staging' | 'test'>): this; + } + export interface SchedulerContract { + call(handler: ScheduleHandler): ScheduleContract; + command(command: string | string[]): ScheduleContract; + exec(command: string): ScheduleContract; + start(): void; + } + const Scheduler: SchedulerContract; + export default Scheduler; +} diff --git a/build/adonis-typings/scheduler.js b/build/adonis-typings/scheduler.js new file mode 100644 index 0000000..e69de29 diff --git a/build/bin/japa_types.d.ts b/build/bin/japa_types.d.ts new file mode 100644 index 0000000..64490c7 --- /dev/null +++ b/build/bin/japa_types.d.ts @@ -0,0 +1,19 @@ +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +import { ApplicationContract } from '@ioc:Adonis/Core/Application'; +import '@japa/runner'; +declare module '@japa/runner' { + interface TestContext { + app: ApplicationContract; + } + interface Test { + } +} diff --git a/build/bin/japa_types.js b/build/bin/japa_types.js new file mode 100644 index 0000000..c6112f4 --- /dev/null +++ b/build/bin/japa_types.js @@ -0,0 +1,3 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +require("@japa/runner"); diff --git a/build/bin/test.d.ts b/build/bin/test.d.ts new file mode 100644 index 0000000..d2c9bc6 --- /dev/null +++ b/build/bin/test.d.ts @@ -0,0 +1 @@ +import 'reflect-metadata'; diff --git a/build/bin/test.js b/build/bin/test.js new file mode 100644 index 0000000..5b675d2 --- /dev/null +++ b/build/bin/test.js @@ -0,0 +1,66 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +require("reflect-metadata"); +const node_path_1 = require("node:path"); +const standalone_1 = require("@adonisjs/core/build/standalone"); +const assert_1 = require("@japa/assert"); +const runner_1 = require("@japa/runner"); +const spec_reporter_1 = require("@japa/spec-reporter"); +const dev_utils_1 = require("@poppinss/dev-utils"); +const fs = new dev_utils_1.Filesystem((0, node_path_1.resolve)(__dirname, '__app')); +(0, runner_1.configure)({ + ...(0, runner_1.processCliArgs)(process.argv.slice(2)), + files: ['tests/**/*.spec.ts'], + plugins: [(0, assert_1.assert)()], + reporters: [(0, spec_reporter_1.specReporter)()], + importer: (filePath) => Promise.resolve(`${filePath}`).then(s => __importStar(require(s))), + forceExit: true, + setup: [ + async () => { + await fs.add('.env', ''); + await fs.add('config/app.ts', ` + export const profiler = { enabled: true } + export const appKey = 'averylong32charsrandomsecretkey', + export const http = { + cookie: {}, + trustProxy: () => true, + } + `); + const app = new standalone_1.Application(fs.basePath, 'test', { + providers: ['@adonisjs/core', '../../providers/scheduler_provider'], + }); + await app.setup(); + await app.registerProviders(); + await app.bootProviders(); + return async () => { + await app.shutdown(); + await fs.cleanup(); + }; + }, + ], +}); +runner_1.TestContext.getter('app', () => require('@adonisjs/core/build/services/app.js').default); +(0, runner_1.run)(); diff --git a/build/commands/index.d.ts b/build/commands/index.d.ts new file mode 100644 index 0000000..d451d2b --- /dev/null +++ b/build/commands/index.d.ts @@ -0,0 +1,2 @@ +declare const _default: string[]; +export default _default; diff --git a/build/commands/index.js b/build/commands/index.js new file mode 100644 index 0000000..4af0d83 --- /dev/null +++ b/build/commands/index.js @@ -0,0 +1,3 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.default = ['@verful/scheduler/build/commands/scheduler_work']; diff --git a/build/commands/scheduler_work.d.ts b/build/commands/scheduler_work.d.ts new file mode 100644 index 0000000..003e047 --- /dev/null +++ b/build/commands/scheduler_work.d.ts @@ -0,0 +1,10 @@ +import { BaseCommand } from '@adonisjs/core/build/standalone'; +export default class ProcessSchedule extends BaseCommand { + static commandName: string; + static description: string; + static settings: { + loadApp: boolean; + stayAlive: boolean; + }; + run(): Promise; +} diff --git a/build/commands/scheduler_work.js b/build/commands/scheduler_work.js new file mode 100644 index 0000000..7eb9ee4 --- /dev/null +++ b/build/commands/scheduler_work.js @@ -0,0 +1,15 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const standalone_1 = require("@adonisjs/core/build/standalone"); +class ProcessSchedule extends standalone_1.BaseCommand { + static commandName = 'scheduler:work'; + static description = 'Process the scheduled tasks'; + static settings = { + loadApp: true, + stayAlive: true, + }; + async run() { + this.application.container.use('Verful/Scheduler').start(); + } +} +exports.default = ProcessSchedule; diff --git a/build/instructions.md b/build/instructions.md new file mode 100644 index 0000000..5c3a8c7 --- /dev/null +++ b/build/instructions.md @@ -0,0 +1 @@ +The package has been configured successfully. diff --git a/build/providers/scheduler_provider.d.ts b/build/providers/scheduler_provider.d.ts new file mode 100644 index 0000000..572c3ac --- /dev/null +++ b/build/providers/scheduler_provider.d.ts @@ -0,0 +1,16 @@ +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +import type { ApplicationContract } from '@ioc:Adonis/Core/Application'; +export default class ScheduleProvider { + protected app: ApplicationContract; + constructor(app: ApplicationContract); + register(): void; +} diff --git a/build/providers/scheduler_provider.js b/build/providers/scheduler_provider.js new file mode 100644 index 0000000..0d957e5 --- /dev/null +++ b/build/providers/scheduler_provider.js @@ -0,0 +1,15 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +class ScheduleProvider { + app; + constructor(app) { + this.app = app; + } + register() { + this.app.container.singleton('Verful/Scheduler', () => { + const { default: Scheduler } = require('../src/scheduler'); + return new Scheduler(this.app); + }); + } +} +exports.default = ScheduleProvider; diff --git a/build/src/manages_frequencies.d.ts b/build/src/manages_frequencies.d.ts new file mode 100644 index 0000000..3015d66 --- /dev/null +++ b/build/src/manages_frequencies.d.ts @@ -0,0 +1,46 @@ +/// +import { ManagesFrequenciesContract, Time } from '@ioc:Verful/Scheduler'; +export default class ManagesFrequencies implements ManagesFrequenciesContract { + expression: string; + protected currentTimezone: string; + timezone(timezone: string): this; + protected spliceIntoPosition(position: number, value: string | number): this; + everyMinute(): this; + everyTwoMinutes(): this; + everyThreeMinutes(): this; + everyFourMinutes(): this; + everyFiveMinutes(): this; + everyTenMinutes(): this; + everyFifteenMinutes(): this; + everyThirtyMinutes(): this; + hourly(): this; + hourlyAt(offset: number | number[]): this; + everyTwoHours(): this; + everyThreeHours(): this; + everyFourHours(): this; + everySixHours(): this; + daily(): this; + dailyAt(time: Time): this; + twiceDaily(): this; + twiceDailyAt(first: number, second: number, offset?: number): this; + weekly(): this; + weeklyOn(daysOfWeek: number | number[] | string, time: Time): this; + monthly(): this; + monthlyOn(dayOfMonth: number, time?: Time): this; + twiceMonthly(first?: number, second?: number, time?: Time): this; + lastDayOfMonth(time?: Time): this; + quarterly(): this; + yearly(): this; + yearlyOn(month: number, dayOfMonth?: string | number, time?: Time): this; + days(days: number | number[] | string): this; + weekdays(): this; + weekends(): this; + sundays(): this; + mondays(): this; + tuesdays(): this; + wednesdays(): this; + thursdays(): this; + fridays(): this; + saturdays(): this; + cron(expression: string): this; +} diff --git a/build/src/manages_frequencies.js b/build/src/manages_frequencies.js new file mode 100644 index 0000000..226b7b4 --- /dev/null +++ b/build/src/manages_frequencies.js @@ -0,0 +1,152 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const luxon_1 = require("luxon"); +const Weekdays = Object.freeze({ + MONDAY: 1, + TUESDAY: 2, + WEDNESDAY: 3, + THURSDAY: 4, + FRIDAY: 5, + SATURDAY: 6, + SUNDAY: 7, +}); +class ManagesFrequencies { + expression = '* * * * *'; + currentTimezone = 'UTC'; + timezone(timezone) { + this.currentTimezone = timezone; + return this; + } + spliceIntoPosition(position, value) { + const segments = this.expression.split(' '); + segments[position] = String(value); + return this.cron(segments.join(' ')); + } + everyMinute() { + return this.spliceIntoPosition(0, '*'); + } + everyTwoMinutes() { + return this.spliceIntoPosition(0, '*/2'); + } + everyThreeMinutes() { + return this.spliceIntoPosition(0, '*/3'); + } + everyFourMinutes() { + return this.spliceIntoPosition(0, '*/4'); + } + everyFiveMinutes() { + return this.spliceIntoPosition(0, '*/5'); + } + everyTenMinutes() { + return this.spliceIntoPosition(0, '*/10'); + } + everyFifteenMinutes() { + return this.spliceIntoPosition(0, '*/15'); + } + everyThirtyMinutes() { + return this.spliceIntoPosition(0, '0,30'); + } + hourly() { + return this.spliceIntoPosition(0, 0); + } + hourlyAt(offset) { + const offsetString = Array.isArray(offset) ? offset.join(',') : offset; + return this.spliceIntoPosition(0, offsetString); + } + everyTwoHours() { + return this.spliceIntoPosition(0, 0).spliceIntoPosition(1, '*/2'); + } + everyThreeHours() { + return this.spliceIntoPosition(0, 0).spliceIntoPosition(1, '*/3'); + } + everyFourHours() { + return this.spliceIntoPosition(0, 0).spliceIntoPosition(1, '*/4'); + } + everySixHours() { + return this.spliceIntoPosition(0, 0).spliceIntoPosition(1, '*/6'); + } + daily() { + return this.spliceIntoPosition(0, 0).spliceIntoPosition(1, 0); + } + dailyAt(time) { + const [hour, minute] = time.split(':').map((value) => String(Number(value))); + return this.spliceIntoPosition(0, minute).spliceIntoPosition(1, hour); + } + twiceDaily() { + return this.twiceDailyAt(0, 12, 0); + } + twiceDailyAt(first, second, offset = 0) { + const hours = `${first},${second}`; + return this.spliceIntoPosition(0, offset).spliceIntoPosition(1, hours); + } + weekly() { + return this.spliceIntoPosition(0, 0).spliceIntoPosition(1, 0).spliceIntoPosition(4, 0); + } + weeklyOn(daysOfWeek, time) { + return this.days(daysOfWeek).dailyAt(time); + } + monthly() { + return this.spliceIntoPosition(0, 0).spliceIntoPosition(1, 0).spliceIntoPosition(2, 1); + } + monthlyOn(dayOfMonth, time = '00:00') { + return this.dailyAt(time).spliceIntoPosition(2, dayOfMonth); + } + twiceMonthly(first = 1, second = 16, time = '00:00') { + const daysOfMonth = `${first},${second}`; + return this.spliceIntoPosition(2, daysOfMonth).dailyAt(time); + } + lastDayOfMonth(time = '00:00') { + return this.spliceIntoPosition(2, luxon_1.DateTime.now().setZone(this.currentTimezone).endOf('month').day).dailyAt(time); + } + quarterly() { + return this.spliceIntoPosition(0, 0) + .spliceIntoPosition(1, 0) + .spliceIntoPosition(2, 1) + .spliceIntoPosition(3, '1-12/3'); + } + yearly() { + return this.spliceIntoPosition(0, 0) + .spliceIntoPosition(1, 0) + .spliceIntoPosition(2, 1) + .spliceIntoPosition(3, 1); + } + yearlyOn(month, dayOfMonth = 1, time = '00:00') { + return this.spliceIntoPosition(2, dayOfMonth).spliceIntoPosition(3, month).dailyAt(time); + } + days(days) { + const daysString = Array.isArray(days) ? days.join(',') : days; + return this.spliceIntoPosition(4, daysString); + } + weekdays() { + return this.days(`${Weekdays.MONDAY}-${Weekdays.FRIDAY}`); + } + weekends() { + return this.days(`${Weekdays.SATURDAY},${Weekdays.SUNDAY}`); + } + sundays() { + return this.days(Weekdays.SUNDAY); + } + mondays() { + return this.days(Weekdays.MONDAY); + } + tuesdays() { + return this.days(Weekdays.TUESDAY); + } + wednesdays() { + return this.days(Weekdays.WEDNESDAY); + } + thursdays() { + return this.days(Weekdays.THURSDAY); + } + fridays() { + return this.days(Weekdays.FRIDAY); + } + saturdays() { + return this.days(Weekdays.SATURDAY); + } + cron(expression) { + this.expression = expression; + return this; + } +} +exports.default = ManagesFrequencies; diff --git a/build/src/schedule.d.ts b/build/src/schedule.d.ts new file mode 100644 index 0000000..6485df0 --- /dev/null +++ b/build/src/schedule.d.ts @@ -0,0 +1,27 @@ +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +import { ApplicationContract } from '@ioc:Adonis/Core/Application'; +import { Condition, ScheduleContract, ScheduleHandler, Time } from '@ioc:Verful/Scheduler'; +import ManagesFrequencies from './manages_frequencies'; +export default class Schedule extends ManagesFrequencies implements ScheduleContract { + private app; + command: ScheduleHandler; + constructor(app: ApplicationContract, command: ScheduleHandler); + protected inTimeInterval(startTime: Time, endTime: Time): () => boolean; + between(start: Time, end: Time): this; + unlessBetween(start: Time, end: Time): this; + filters: Condition[]; + rejects: Condition[]; + skip(condition: Condition): this; + when(condition: Condition): this; + environments(environments: Array<'production' | 'development' | 'staging' | 'test'>): this; +} diff --git a/build/src/schedule.js b/build/src/schedule.js new file mode 100644 index 0000000..aa22d20 --- /dev/null +++ b/build/src/schedule.js @@ -0,0 +1,59 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const luxon_1 = require("luxon"); +const manages_frequencies_1 = __importDefault(require("./manages_frequencies")); +class Schedule extends manages_frequencies_1.default { + app; + command; + constructor(app, command) { + super(); + this.app = app; + this.command = command; + } + inTimeInterval(startTime, endTime) { + const [startHours, startMinutes] = startTime.split(':').map(Number); + const [endHours, endMinutes] = endTime.split(':').map(Number); + let [now, start, end] = [ + luxon_1.DateTime.now().setZone(this.currentTimezone), + luxon_1.DateTime.now() + .set({ minute: startMinutes, hour: startHours, second: 0, millisecond: 0 }) + .setZone(this.currentTimezone), + luxon_1.DateTime.now() + .set({ minute: endMinutes, hour: endHours, second: 0, millisecond: 0 }) + .setZone(this.currentTimezone), + ]; + if (end < start) { + if (start > now) { + start = start.minus({ days: 1 }); + } + else { + end = end.plus({ days: 1 }); + } + } + return () => now > start && now < end; + } + between(start, end) { + return this.when(this.inTimeInterval(start, end)); + } + unlessBetween(start, end) { + return this.skip(this.inTimeInterval(start, end)); + } + filters = []; + rejects = []; + skip(condition) { + this.rejects.push(condition); + return this; + } + when(condition) { + this.filters.push(condition); + return this; + } + environments(environments) { + this.when(() => environments.includes(this.app.nodeEnvironment)); + return this; + } +} +exports.default = Schedule; diff --git a/build/src/scheduler.d.ts b/build/src/scheduler.d.ts new file mode 100644 index 0000000..502c560 --- /dev/null +++ b/build/src/scheduler.d.ts @@ -0,0 +1,22 @@ +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +import { ApplicationContract } from '@ioc:Adonis/Core/Application'; +import { ScheduleContract, SchedulerContract } from '@ioc:Verful/Scheduler'; +export default class Scheduler implements SchedulerContract { + private app; + private events; + constructor(app: ApplicationContract); + call(callback: () => void): ScheduleContract; + command(command: string | string[]): ScheduleContract; + exec(command: string): ScheduleContract; + start(): void; +} diff --git a/build/src/scheduler.js b/build/src/scheduler.js new file mode 100644 index 0000000..46a618a --- /dev/null +++ b/build/src/scheduler.js @@ -0,0 +1,61 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const execa_1 = __importDefault(require("execa")); +const node_cron_1 = __importDefault(require("node-cron")); +const schedule_1 = __importDefault(require("./schedule")); +class Scheduler { + app; + events = []; + constructor(app) { + this.app = app; + } + call(callback) { + const schedule = new schedule_1.default(this.app, callback); + this.events.push(schedule); + return schedule; + } + command(command) { + const commandArguments = Array.isArray(command) ? command : command.split(' '); + const callback = async () => { + try { + const result = await execa_1.default.node('ace', commandArguments); + return result.stdout; + } + catch (error) { + return error; + } + }; + return this.call(callback); + } + exec(command) { + const callback = async () => { + try { + const result = await execa_1.default.command(command); + return result.stdout; + } + catch (error) { + return error; + } + }; + return this.call(callback); + } + start() { + this.app.logger.info('Schedule processing started'); + for (const event of this.events) + node_cron_1.default.schedule(event.expression, async () => { + for (const filter of event.filters) { + if (!(await filter())) + return; + } + for (const reject of event.rejects) { + if (await reject()) + return; + } + return await event.command(); + }); + } +} +exports.default = Scheduler; diff --git a/build/templates/tasks.txt b/build/templates/tasks.txt new file mode 100644 index 0000000..906bd18 --- /dev/null +++ b/build/templates/tasks.txt @@ -0,0 +1,41 @@ +import Scheduler from '@ioc:Verful/Scheduler' + +/* +|-------------------------------------------------------------------------- +| Scheduled tasks +|-------------------------------------------------------------------------- +| +| Scheduled tasks allow you to run recurrent tasks in the background of your +| application. Here you can define all your scheduled tasks. +| +| You can define a scheduled task using the `.call` method on the Scheduler object +| as shown in the following example +| +| ``` +| Scheduler.call(() => { +| console.log('I am a scheduled task') +| }).everyMinute() +| ``` +| +| The example above will print the message `I am a scheduled task` every minute. +| +| You can also schedule ace commands using the `.command` method on the Scheduler +| object as shown in the following example +| +| ``` +| Scheduler.command('greet').everyMinute() +| ``` +| +| The example above will run the `greet` command every minute. +| +| You can also schedule shell commands with arguments using the `.exec` method on the Scheduler +| object as shown in the following example +| +| ``` +| Scheduler.exec('node ace greet').everyMinute() +| ``` +| +| The example above will run the `node ace greet` command every minute. +| +| Happy scheduling! +*/ diff --git a/build/tests/manages_frequencies.spec.d.ts b/build/tests/manages_frequencies.spec.d.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/build/tests/manages_frequencies.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/build/tests/manages_frequencies.spec.js b/build/tests/manages_frequencies.spec.js new file mode 100644 index 0000000..962e205 --- /dev/null +++ b/build/tests/manages_frequencies.spec.js @@ -0,0 +1,232 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const runner_1 = require("@japa/runner"); +const sinon_1 = __importDefault(require("sinon")); +const manages_frequencies_1 = __importDefault(require("../src/manages_frequencies")); +runner_1.test.group('ManagesFrequencies', (group) => { + group.each.setup(() => { + return () => sinon_1.default.restore(); + }); + (0, runner_1.test)('can create instance', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + assert.instanceOf(managesFrequencies, manages_frequencies_1.default); + }); + (0, runner_1.test)('can set timezone', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + const timezone = 'America/New_York'; + managesFrequencies.timezone(timezone); + assert.equal(managesFrequencies['currentTimezone'], timezone); + }); + (0, runner_1.test)('can set expression with cron', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + const expression = '0 0 * * *'; + managesFrequencies.cron(expression); + assert.equal(managesFrequencies['expression'], expression); + }); + (0, runner_1.test)('can set expression for every minute', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.everyMinute(); + assert.equal(managesFrequencies['expression'], '* * * * *'); + }); + (0, runner_1.test)('can set expression for every two minutes', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.everyTwoMinutes(); + assert.equal(managesFrequencies['expression'], '*/2 * * * *'); + }); + (0, runner_1.test)('can set expression for every three minutes', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.everyThreeMinutes(); + assert.equal(managesFrequencies['expression'], '*/3 * * * *'); + }); + (0, runner_1.test)('can set expression for every four minutes', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.everyFourMinutes(); + assert.equal(managesFrequencies['expression'], '*/4 * * * *'); + }); + (0, runner_1.test)('can set expression for every five minutes', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.everyFiveMinutes(); + assert.equal(managesFrequencies['expression'], '*/5 * * * *'); + }); + (0, runner_1.test)('can set expression for every ten minutes', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.everyTenMinutes(); + assert.equal(managesFrequencies['expression'], '*/10 * * * *'); + }); + (0, runner_1.test)('can set expression for every fifteen minutes', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.everyFifteenMinutes(); + assert.equal(managesFrequencies['expression'], '*/15 * * * *'); + }); + (0, runner_1.test)('can set expression for every thirty minutes', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.everyThirtyMinutes(); + assert.equal(managesFrequencies['expression'], '0,30 * * * *'); + }); + (0, runner_1.test)('can set expression for hourly', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.hourly(); + assert.equal(managesFrequencies['expression'], '0 * * * *'); + }); + (0, runner_1.test)('can set expression for hourly at offset', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.hourlyAt(3); + assert.equal(managesFrequencies['expression'], '3 * * * *'); + }); + (0, runner_1.test)('can set expression for hourly at multiple offsets', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.hourlyAt([3, 9]); + assert.equal(managesFrequencies['expression'], '3,9 * * * *'); + }); + (0, runner_1.test)('can set expression for every two hours', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.everyTwoHours(); + assert.equal(managesFrequencies['expression'], '0 */2 * * *'); + }); + (0, runner_1.test)('can set expression for every three hours', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.everyThreeHours(); + assert.equal(managesFrequencies['expression'], '0 */3 * * *'); + }); + (0, runner_1.test)('can set expression for every four hours', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.everyFourHours(); + assert.equal(managesFrequencies['expression'], '0 */4 * * *'); + }); + (0, runner_1.test)('can set expression for every six hours', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.everySixHours(); + assert.equal(managesFrequencies['expression'], '0 */6 * * *'); + }); + (0, runner_1.test)('can set expression for daily', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.daily(); + assert.equal(managesFrequencies['expression'], '0 0 * * *'); + }); + (0, runner_1.test)('can set expression for daily at specific time', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.dailyAt('12:30'); + assert.equal(managesFrequencies['expression'], '30 12 * * *'); + }); + (0, runner_1.test)('can set expression for twice daily', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.twiceDaily(); + assert.equal(managesFrequencies['expression'], '0 0,12 * * *'); + }); + (0, runner_1.test)('can set expression for twice daily at specific times', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.twiceDailyAt(1, 13); + assert.equal(managesFrequencies['expression'], '0 1,13 * * *'); + }); + (0, runner_1.test)('can set expression for weekly', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.weekly(); + assert.equal(managesFrequencies['expression'], '0 0 * * 0'); + }); + (0, runner_1.test)('can set expression for weekly on specific days', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.weeklyOn([2, 5], '15:00'); + assert.equal(managesFrequencies['expression'], '0 15 * * 2,5'); + }); + (0, runner_1.test)('can set days of the week', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + const daysOfWeek = [1, 2, 3]; + managesFrequencies.days(daysOfWeek); + assert.equal(managesFrequencies['expression'], '* * * * 1,2,3'); + }); + (0, runner_1.test)('can set weekdays', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.weekdays(); + assert.equal(managesFrequencies['expression'], '* * * * 1-5'); + }); + (0, runner_1.test)('can set weekends', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.weekends(); + assert.equal(managesFrequencies['expression'], '* * * * 6,7'); + }); + (0, runner_1.test)('can set specific day of the week', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.sundays(); + assert.equal(managesFrequencies['expression'], '* * * * 7'); + }); + (0, runner_1.test)('can set expression for mondays', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.mondays(); + assert.equal(managesFrequencies['expression'], '* * * * 1'); + }); + (0, runner_1.test)('can set expression for tuesdays', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.tuesdays(); + assert.equal(managesFrequencies['expression'], '* * * * 2'); + }); + (0, runner_1.test)('can set expression for wednesdays', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.wednesdays(); + assert.equal(managesFrequencies['expression'], '* * * * 3'); + }); + (0, runner_1.test)('can set expression for thursdays', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.thursdays(); + assert.equal(managesFrequencies['expression'], '* * * * 4'); + }); + (0, runner_1.test)('can set expression for fridays', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.fridays(); + assert.equal(managesFrequencies['expression'], '* * * * 5'); + }); + (0, runner_1.test)('can set expression for saturdays', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.saturdays(); + assert.equal(managesFrequencies['expression'], '* * * * 6'); + }); + (0, runner_1.test)('can combine frequency methods', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.everyTwoHours().weekdays(); + assert.equal(managesFrequencies['expression'], '0 */2 * * 1-5'); + }); + (0, runner_1.test)('can combine frequency methods for specific days', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.weeklyOn([2, 4], '12:00'); + assert.equal(managesFrequencies['expression'], '0 12 * * 2,4'); + }); + (0, runner_1.test)('can set expression for monthly', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.monthly(); + assert.equal(managesFrequencies['expression'], '0 0 1 * *'); + }); + (0, runner_1.test)('can set expression for monthly on specific day and time', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.monthlyOn(15, '14:30'); + assert.equal(managesFrequencies['expression'], '30 14 15 * *'); + }); + (0, runner_1.test)('can set expression for twice monthly', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.twiceMonthly(); + assert.equal(managesFrequencies['expression'], '0 0 1,16 * *'); + }); + (0, runner_1.test)('can set expression for last day of month', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.lastDayOfMonth(); + const lastDayOfMonth = new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0); + const expectedExpression = `0 0 ${lastDayOfMonth.getDate()} * *`; + assert.equal(managesFrequencies['expression'], expectedExpression); + }); + (0, runner_1.test)('can set expression for quarterly', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.quarterly(); + assert.equal(managesFrequencies['expression'], '0 0 1 1-12/3 *'); + }); + (0, runner_1.test)('can set expression for yearly', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.yearly(); + assert.equal(managesFrequencies['expression'], '0 0 1 1 *'); + }); + (0, runner_1.test)('can set expression for yearly on specific month and day', ({ assert }) => { + const managesFrequencies = new manages_frequencies_1.default(); + managesFrequencies.yearlyOn(6, 20, '10:45'); + assert.equal(managesFrequencies['expression'], '45 10 20 6 *'); + }); +}); diff --git a/build/tests/schedule.spec.d.ts b/build/tests/schedule.spec.d.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/build/tests/schedule.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/build/tests/schedule.spec.js b/build/tests/schedule.spec.js new file mode 100644 index 0000000..0ae183f --- /dev/null +++ b/build/tests/schedule.spec.js @@ -0,0 +1,80 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const runner_1 = require("@japa/runner"); +const luxon_1 = require("luxon"); +const sinon_1 = __importDefault(require("sinon")); +const schedule_1 = __importDefault(require("../src/schedule")); +runner_1.test.group('Schedule', (group) => { + group.each.setup(() => { + sinon_1.default.stub(luxon_1.DateTime, 'now').returns(luxon_1.DateTime.fromObject({ + year: 2021, + month: 7, + day: 31, + hour: 7, + minute: 30, + })); + return () => { + sinon_1.default.restore(); + }; + }); + (0, runner_1.test)('can set between condition', ({ assert }) => { + const schedule = new schedule_1.default({}, () => { }); + const start = '7:00'; + const end = '8:00'; + schedule.between(start, end); + assert.lengthOf(schedule.filters, 1); + assert.isFunction(schedule.filters[0]); + }); + (0, runner_1.test)('can set unlessBetween condition', ({ assert }) => { + const schedule = new schedule_1.default({}, () => { }); + const start = '7:00'; + const end = '8:00'; + schedule.unlessBetween(start, end); + assert.lengthOf(schedule.rejects, 1); + assert.isFunction(schedule.rejects[0]); + }); + (0, runner_1.test)('can set between condition that wraps midnight', ({ assert }) => { + const schedule = new schedule_1.default({}, () => { }); + const start = '23:00'; + const end = '1:00'; + schedule.between(start, end); + assert.lengthOf(schedule.filters, 1); + assert.isFunction(schedule.filters[0]); + }); + (0, runner_1.test)('can set unlessBetween condition that wraps midnight', ({ assert }) => { + const schedule = new schedule_1.default({}, () => { }); + const start = '23:00'; + const end = '1:00'; + schedule.unlessBetween(start, end); + assert.lengthOf(schedule.rejects, 1); + assert.isFunction(schedule.rejects[0]); + }); + (0, runner_1.test)('time interval check is correct', ({ assert }) => { + const schedule = new schedule_1.default({}, () => { }); + assert.isTrue(schedule['inTimeInterval']('7:00', '8:00')()); + assert.isTrue(schedule['inTimeInterval']('23:00', '8:00')()); + assert.isFalse(schedule['inTimeInterval']('6:00', '7:00')()); + assert.isFalse(schedule['inTimeInterval']('23:00', '1:00')()); + }); + (0, runner_1.test)('can set skip condition', ({ assert }) => { + const schedule = new schedule_1.default({}, () => { }); + schedule.skip(() => true); + assert.lengthOf(schedule.rejects, 1); + assert.isFunction(schedule.rejects[0]); + }); + (0, runner_1.test)('can set when condition', ({ assert }) => { + const schedule = new schedule_1.default({}, () => { }); + schedule.when(() => true); + assert.lengthOf(schedule.filters, 1); + assert.isFunction(schedule.filters[0]); + }); + (0, runner_1.test)('can set environments condition', ({ assert }) => { + const schedule = new schedule_1.default({ nodeEnvironment: 'test' }, () => { }); + schedule.environments(['test']); + assert.lengthOf(schedule.filters, 1); + assert.isFunction(schedule.filters[0]); + }); +}); diff --git a/build/tests/scheduler_provider.spec.d.ts b/build/tests/scheduler_provider.spec.d.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/build/tests/scheduler_provider.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/build/tests/scheduler_provider.spec.js b/build/tests/scheduler_provider.spec.js new file mode 100644 index 0000000..693bbe3 --- /dev/null +++ b/build/tests/scheduler_provider.spec.js @@ -0,0 +1,12 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const runner_1 = require("@japa/runner"); +const scheduler_1 = __importDefault(require("../src/scheduler")); +runner_1.test.group('SchedulerProvider', () => { + (0, runner_1.test)('Bindings registered correctly', ({ assert, app }) => { + assert.instanceOf(app.container.resolveBinding('Verful/Scheduler'), scheduler_1.default); + }); +}); diff --git a/build/tests/sheduler.spec.d.ts b/build/tests/sheduler.spec.d.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/build/tests/sheduler.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/build/tests/sheduler.spec.js b/build/tests/sheduler.spec.js new file mode 100644 index 0000000..152c1f4 --- /dev/null +++ b/build/tests/sheduler.spec.js @@ -0,0 +1,83 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const runner_1 = require("@japa/runner"); +const execa_1 = __importDefault(require("execa")); +const node_cron_1 = __importDefault(require("node-cron")); +const sinon_1 = __importDefault(require("sinon")); +const scheduler_1 = __importDefault(require("../src/scheduler")); +runner_1.test.group('Scheduler', (group) => { + group.each.setup(() => { + return () => sinon_1.default.restore(); + }); + (0, runner_1.test)('calls a callback on scheduling', ({ app, assert }) => { + const scheduler = new scheduler_1.default(app); + const callback = sinon_1.default.stub(); + scheduler.call(callback); + assert.lengthOf(scheduler['events'], 1); + }); + (0, runner_1.test)('schedules a command to be executed', ({ app, assert }) => { + sinon_1.default.stub(execa_1.default, 'node'); + const scheduler = new scheduler_1.default(app); + const command = 'some:command'; + scheduler.command(command); + assert.lengthOf(scheduler['events'], 1); + }); + (0, runner_1.test)('schedules an execution of a command', ({ app, assert }) => { + sinon_1.default.stub(execa_1.default, 'node'); + const scheduler = new scheduler_1.default(app); + const command = 'some:command'; + scheduler.exec(command); + assert.lengthOf(scheduler['events'], 1); + }); + (0, runner_1.test)('the scheduler executes scheduled functions', async ({ app, assert }) => { + assert.plan(3); + const scheduler = new scheduler_1.default(app); + const mockFilter = sinon_1.default.stub().resolves(true); + const mockReject = sinon_1.default.stub().resolves(false); + const mockCommand = sinon_1.default.stub().resolves('Command executed'); + scheduler.call(mockCommand).when(mockFilter).skip(mockReject).everyMinute(); + sinon_1.default.stub(node_cron_1.default, 'schedule').callsFake((expression, cronCallback) => { + cronCallback().then(() => { + assert.isTrue(mockFilter.called); + assert.isTrue(mockReject.called); + assert.isTrue(mockCommand.called); + }); + }); + await scheduler.start(); + }); + (0, runner_1.test)('the scheduler executes scheduled ace commands', async ({ app, assert }) => { + assert.plan(3); + const scheduler = new scheduler_1.default(app); + const mockFilter = sinon_1.default.stub().resolves(true); + const mockReject = sinon_1.default.stub().resolves(false); + scheduler.command('make:user').when(mockFilter).skip(mockReject).everyMinute(); + const execaStub = sinon_1.default.stub(execa_1.default, 'node').resolves({ stdout: 'Command executed' }); + sinon_1.default.stub(node_cron_1.default, 'schedule').callsFake((expression, cronCallback) => { + cronCallback().then(() => { + assert.isTrue(mockFilter.called); + assert.isTrue(mockReject.called); + assert.isTrue(execaStub.called); + }); + }); + await scheduler.start(); + }); + (0, runner_1.test)('the scheduler executes scheduled shell commands', async ({ app, assert }) => { + assert.plan(3); + const scheduler = new scheduler_1.default(app); + const mockFilter = sinon_1.default.stub().resolves(true); + const mockReject = sinon_1.default.stub().resolves(false); + scheduler.exec('node ace make:user').when(mockFilter).skip(mockReject).everyMinute(); + const execaStub = sinon_1.default.stub(execa_1.default, 'command').resolves({ stdout: 'Command executed' }); + sinon_1.default.stub(node_cron_1.default, 'schedule').callsFake((expression, cronCallback) => { + cronCallback().then(() => { + assert.isTrue(mockFilter.called); + assert.isTrue(mockReject.called); + assert.isTrue(execaStub.called); + }); + }); + await scheduler.start(); + }); +});