@wonsukchoi/crondex 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/bin/crondex.js +13 -3
- package/lib/diff.js +66 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,8 +35,10 @@ npx @wonsukchoi/crondex deploy ssl-cert-expiry-check --var host=example.com
|
|
|
35
35
|
- `show <id>` — print a job's full YAML
|
|
36
36
|
- `add <id> [--dest path]` — copy it into your project to edit
|
|
37
37
|
- `init <id> [--category x]` — scaffold a brand-new job from the template
|
|
38
|
-
- `update <path
|
|
39
|
-
its `id` field) against the current catalog
|
|
38
|
+
- `update <path> [--dry-run]` — re-pull a job you already `add`ed/`init`ed
|
|
39
|
+
(matched by its `id` field) against the current catalog, print a diff of
|
|
40
|
+
what changed, and overwrite it in place. `--dry-run` shows the diff
|
|
41
|
+
without applying it.
|
|
40
42
|
- `deploy <id> [--target crontab|github-actions|systemd|docker|k8s-cronjob|eventbridge|cloud-scheduler] [--var name=value ...]`
|
|
41
43
|
— turn a job into something that actually runs: prints a ready crontab
|
|
42
44
|
line (or installs it into your own crontab with `--install`), writes a
|
|
@@ -170,7 +172,7 @@ tags, variables) use `crondex list`, `crondex recommend`, or browse
|
|
|
170
172
|
crondex/
|
|
171
173
|
├── llms.txt agent-discovery manifest (llms.txt convention)
|
|
172
174
|
├── bin/crondex.js CLI: list / categories / show / add / recommend / init / update / deploy / uninstall
|
|
173
|
-
├── lib/ recommend, deploy, and catalog-building logic (unit tested in test/)
|
|
175
|
+
├── lib/ recommend, deploy, diff, and catalog-building logic (unit tested in test/)
|
|
174
176
|
├── catalog.json generated index of every job — read this first
|
|
175
177
|
├── schema/job.schema.json spec every job file follows
|
|
176
178
|
├── jobs/ one YAML per job, grouped by category subdirectory
|
package/bin/crondex.js
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
buildEventBridgeCommand,
|
|
19
19
|
buildCloudSchedulerCommand,
|
|
20
20
|
} from "../lib/deploy.js";
|
|
21
|
+
import { formatDiff } from "../lib/diff.js";
|
|
21
22
|
|
|
22
23
|
const ROOT = new URL("..", import.meta.url).pathname;
|
|
23
24
|
const CATALOG = JSON.parse(readFileSync(join(ROOT, "catalog.json"), "utf8"));
|
|
@@ -70,7 +71,7 @@ Usage:
|
|
|
70
71
|
crondex add <id> [--dest <path>]
|
|
71
72
|
crondex recommend "<what you want done>" [--limit <n>] [--json]
|
|
72
73
|
crondex init <id> [--category <name>] [--dest <path>]
|
|
73
|
-
crondex update <path>
|
|
74
|
+
crondex update <path> [--dry-run]
|
|
74
75
|
crondex deploy <id> [--target crontab|github-actions|systemd|docker|k8s-cronjob|
|
|
75
76
|
eventbridge|cloud-scheduler] [--mode script|prompt]
|
|
76
77
|
[--var name=value ...] [--dest <path>] [--install]
|
|
@@ -85,6 +86,7 @@ Examples:
|
|
|
85
86
|
crondex recommend "warn me before my SSL cert expires"
|
|
86
87
|
crondex init ssl-cert-expiry-check --category security
|
|
87
88
|
crondex update ./cron/backup-reminder.yaml
|
|
89
|
+
crondex update ./cron/backup-reminder.yaml --dry-run
|
|
88
90
|
crondex deploy ssl-cert-expiry-check --var host=example.com --var port=443
|
|
89
91
|
crondex deploy repo-health-check --target github-actions
|
|
90
92
|
crondex deploy repo-health-check --target systemd --dest ./systemd
|
|
@@ -120,7 +122,8 @@ deploy --list-installed shows every crondex-managed line in your crontab
|
|
|
120
122
|
removes one of those by id.
|
|
121
123
|
|
|
122
124
|
update re-pulls a job you already added/inited (matched by its "id" field)
|
|
123
|
-
against the current catalog
|
|
125
|
+
against the current catalog, prints a diff of what changed, and overwrites
|
|
126
|
+
the local file in place. Pass --dry-run to see the diff without applying it.
|
|
124
127
|
`);
|
|
125
128
|
}
|
|
126
129
|
|
|
@@ -297,10 +300,17 @@ function update(path) {
|
|
|
297
300
|
}
|
|
298
301
|
const meta = findJob(localDoc.id);
|
|
299
302
|
const latestRaw = readFileSync(join(ROOT, meta.path), "utf8");
|
|
300
|
-
|
|
303
|
+
const diff = formatDiff(localRaw, latestRaw);
|
|
304
|
+
if (!diff) {
|
|
301
305
|
console.log(`${path} is already up to date with "${localDoc.id}".`);
|
|
302
306
|
return;
|
|
303
307
|
}
|
|
308
|
+
console.log(diff);
|
|
309
|
+
console.log();
|
|
310
|
+
if (hasFlag("dry-run")) {
|
|
311
|
+
console.log(`${path} differs from the catalog (shown above) — rerun without --dry-run to apply.`);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
304
314
|
writeFileSync(path, latestRaw);
|
|
305
315
|
console.log(`updated ${path} to the latest "${localDoc.id}" from the catalog.`);
|
|
306
316
|
}
|
package/lib/diff.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Dependency-free line diff — used by `crondex update` to show what actually changed
|
|
2
|
+
// in a catalog job instead of just "it changed". Job files are small (tens of lines),
|
|
3
|
+
// so a plain O(n*m) LCS table is plenty fast; no need for a real Myers-diff package.
|
|
4
|
+
|
|
5
|
+
// Returns the line-by-line edit script as {type: "context"|"add"|"remove", line}.
|
|
6
|
+
export function diffLines(oldText, newText) {
|
|
7
|
+
const a = oldText.split("\n");
|
|
8
|
+
const b = newText.split("\n");
|
|
9
|
+
const n = a.length;
|
|
10
|
+
const m = b.length;
|
|
11
|
+
const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
|
|
12
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
13
|
+
for (let j = m - 1; j >= 0; j--) {
|
|
14
|
+
dp[i][j] = a[i] === b[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const ops = [];
|
|
18
|
+
let i = 0;
|
|
19
|
+
let j = 0;
|
|
20
|
+
while (i < n && j < m) {
|
|
21
|
+
if (a[i] === b[j]) {
|
|
22
|
+
ops.push({ type: "context", line: a[i] });
|
|
23
|
+
i++;
|
|
24
|
+
j++;
|
|
25
|
+
} else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
|
26
|
+
ops.push({ type: "remove", line: a[i] });
|
|
27
|
+
i++;
|
|
28
|
+
} else {
|
|
29
|
+
ops.push({ type: "add", line: b[j] });
|
|
30
|
+
j++;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
while (i < n) ops.push({ type: "remove", line: a[i++] });
|
|
34
|
+
while (j < m) ops.push({ type: "add", line: b[j++] });
|
|
35
|
+
return ops;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Renders the edit script as a compact +/-/context diff, collapsing unchanged runs
|
|
39
|
+
// longer than 2*context into a single "..." marker so a one-line change in a
|
|
40
|
+
// 100-line job doesn't dump the whole file. Returns "" when the texts are identical.
|
|
41
|
+
export function formatDiff(oldText, newText, { context = 2 } = {}) {
|
|
42
|
+
const ops = diffLines(oldText, newText);
|
|
43
|
+
const added = ops.filter((o) => o.type === "add").length;
|
|
44
|
+
const removed = ops.filter((o) => o.type === "remove").length;
|
|
45
|
+
if (!added && !removed) return "";
|
|
46
|
+
|
|
47
|
+
const keep = new Array(ops.length).fill(false);
|
|
48
|
+
ops.forEach((op, idx) => {
|
|
49
|
+
if (op.type === "context") return;
|
|
50
|
+
for (let k = Math.max(0, idx - context); k <= Math.min(ops.length - 1, idx + context); k++) keep[k] = true;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const lines = [`${added} added, ${removed} removed`];
|
|
54
|
+
let skipping = false;
|
|
55
|
+
ops.forEach((op, idx) => {
|
|
56
|
+
if (!keep[idx]) {
|
|
57
|
+
if (!skipping) lines.push(" ...");
|
|
58
|
+
skipping = true;
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
skipping = false;
|
|
62
|
+
const prefix = op.type === "add" ? "+ " : op.type === "remove" ? "- " : " ";
|
|
63
|
+
lines.push(prefix + op.line);
|
|
64
|
+
});
|
|
65
|
+
return lines.join("\n");
|
|
66
|
+
}
|