Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions docs/resources/(resources)/cron.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
title: cron
description: A reference page for the cron resource
---

The cron resource manages scheduled jobs in the current user's crontab using the built-in `crontab` command. It works the same way on macOS and Linux.

Each managed job is written to the crontab as a comment marker followed by the schedule and command:

```sh title="crontab -l"
# Codify managed: <name>
<schedule> <command>
```

The marker comment is how Codify identifies, updates, and removes jobs without disturbing any other entries already in your crontab.

## Parameters

- **jobs**: *(array[object], optional)* An array of cron job definitions. Each job object contains:
- **name**: *(string, required)* A unique name identifying this cron job. Used to track the job across runs.
- **schedule**: *(string, required)* A cron schedule expression (e.g. `"0 5 * * *"`) or a special schedule string (`@reboot`, `@yearly`, `@monthly`, `@weekly`, `@daily`, `@hourly`).
- **command**: *(string, required)* The command to run on the configured schedule.

- **declarationsOnly**: *(boolean, optional)* Controls whether the resource operates in declarative or stateful mode. Defaults to `true`.
- `true` (default): Declarative mode — only manages the jobs explicitly listed in the configuration. Other Codify-managed jobs in the crontab (e.g. ones declared elsewhere) are left untouched.
- `false`: Stateful mode — Codify tracks and manages all Codify-managed jobs found in the crontab.

## Example usage

### Nightly backup job

```json title="codify.jsonc"
[
{
"type": "cron",
"jobs": [
{ "name": "nightly-backup", "schedule": "30 2 * * *", "command": "/usr/local/bin/backup.sh" }
]
}
]
```

### Multiple maintenance jobs

```json title="codify.jsonc"
[
{
"type": "cron",
"jobs": [
{ "name": "health-check", "schedule": "*/5 * * * *", "command": "curl -fsS https://example.com/health" },
{ "name": "weekly-cleanup", "schedule": "0 3 * * 0", "command": "rm -rf /tmp/myapp-cache/*" }
]
}
]
```

## Notes

- No installation is required — `cron`/`crontab` ships with both macOS and Linux.
- This resource manages the **current user's** crontab (`crontab -l` / `crontab <file>`), not system-wide crontabs (e.g. `/etc/crontab` or `/etc/cron.d`).
- Only entries with the `# Codify managed: <name>` marker are considered — manually added crontab entries are never modified or removed.
- When a job's `schedule` or `command` changes, Codify removes the old entry and writes a new one in its place.
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { AsdfPluginResource } from './resources/asdf/asdf-plugin.js';
import { AwsCliResource } from './resources/aws-cli/cli/aws-cli.js';
import { AwsProfileResource } from './resources/aws-cli/profile/aws-profile.js';
import { DnfResource } from './resources/dnf/dnf.js';
import { CronResource } from './resources/cron/cron-resource.js';
import { GoenvResource } from './resources/go/goenv/goenv.js';
import { DockerResource } from './resources/docker/docker.js';
import { EnvFileResource } from './resources/file/env-file/env-file-resource.js';
Expand Down Expand Up @@ -67,6 +68,7 @@ runPlugin(Plugin.create(
'default',
[
new GitResource(),
new CronResource(),
new XcodeToolsResource(),
new PathResource(),
new AliasResource(),
Expand Down
61 changes: 61 additions & 0 deletions src/resources/cron/cron-resource.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { describe, expect, it } from 'vitest';
import { parseManagedJobs, removeJobBlock } from './cron-resource.js';

describe('CronResource unit tests', () => {
it('parses a single managed job', () => {
const result = parseManagedJobs('# Codify managed: my-job\n*/5 * * * * echo hello\n');
expect(result).toMatchObject([{ name: 'my-job', schedule: '*/5 * * * *', command: 'echo hello' }]);
});

it('parses a job with an @special schedule', () => {
const result = parseManagedJobs('# Codify managed: daily-job\n@daily echo hello\n');
expect(result).toMatchObject([{ name: 'daily-job', schedule: '@daily', command: 'echo hello' }]);
});

it('parses multiple managed jobs', () => {
const result = parseManagedJobs(`
# Codify managed: job-one
0 5 * * * /usr/local/bin/backup.sh

# Codify managed: job-two
*/10 * * * * /usr/local/bin/healthcheck.sh
`);
expect(result).toMatchObject([
{ name: 'job-one', schedule: '0 5 * * *', command: '/usr/local/bin/backup.sh' },
{ name: 'job-two', schedule: '*/10 * * * *', command: '/usr/local/bin/healthcheck.sh' },
]);
});

it('ignores unmanaged crontab entries', () => {
const result = parseManagedJobs(`
# A manual job, not managed by Codify
0 0 * * * /usr/local/bin/manual.sh

# Codify managed: managed-job
0 1 * * * /usr/local/bin/managed.sh
`);
expect(result).toMatchObject([{ name: 'managed-job', schedule: '0 1 * * *', command: '/usr/local/bin/managed.sh' }]);
});

it('removes a managed job block by name', () => {
const content = `0 0 * * * /usr/local/bin/manual.sh
# Codify managed: job-one
0 5 * * * /usr/local/bin/backup.sh
# Codify managed: job-two
*/10 * * * * /usr/local/bin/healthcheck.sh`;

const result = removeJobBlock(content, 'job-one');

expect(result).to.not.include('job-one');
expect(result).to.not.include('/usr/local/bin/backup.sh');
expect(result).to.include('# Codify managed: job-two');
expect(result).to.include('/usr/local/bin/healthcheck.sh');
expect(result).to.include('/usr/local/bin/manual.sh');
});

it('removeJobBlock is a no-op when the job is not present', () => {
const content = '# Codify managed: job-two\n*/10 * * * * /usr/local/bin/healthcheck.sh';
const result = removeJobBlock(content, 'job-one');
expect(result).toBe(content);
});
});
Loading