@rendotdev/rig 0.0.17 → 0.0.19
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 +4 -0
- package/dist/{run-y66hvyxg.js → cli-113n7c3t.js} +158 -16
- package/dist/{cli-ne7ed594.js → cli-13q90bjr.js} +12 -0
- package/dist/{cli-vx11bmzr.js → cli-1hm5dxfw.js} +2 -2
- package/dist/{cli-dshh1cp9.js → cli-884hpkjc.js} +1 -1
- package/dist/cli-es8j6mma.js +226 -0
- package/dist/{cli-7zqnqrah.js → cli-f7p31fzm.js} +106 -42
- package/dist/{cli-xv4m20sx.js → cli-nk194xv0.js} +198 -71
- package/dist/{cli-mswdavqx.js → cli-zgw1jbeg.js} +3 -3
- package/dist/{config-24gwe08t.js → config-20f90shh.js} +2 -3
- package/dist/{create-59myk91d.js → create-pvym2abv.js} +7 -8
- package/dist/cron-aw908dzn.js +18 -0
- package/dist/{dev-link-jpte1gq9.js → dev-link-e1mj8w17.js} +7 -4
- package/dist/{discover-fd9e6j9j.js → discover-95bpzy46.js} +3 -4
- package/dist/{help-6k8m07yv.js → help-qj75r0x3.js} +5 -6
- package/dist/{inspect-ks3x7s4n.js → inspect-0ckw7bs5.js} +5 -6
- package/dist/list-reekhy3q.js +13 -0
- package/dist/{paths-hq1vy0wh.js → paths-3vxw7dek.js} +1 -1
- package/dist/{registry-gng9br0x.js → registry-fzc3aeb8.js} +2 -3
- package/dist/rig.js +101 -32
- package/dist/run-thx15p83.js +13 -0
- package/dist/{runtime-comment-gvmkkcyh.js → runtime-comment-07cpchsr.js} +15 -9
- package/dist/{sync-zsc35m3f.js → sync-kkqh3axx.js} +33 -16
- package/dist/{typecheck-c2k87ppw.js → typecheck-971tmqbp.js} +5 -7
- package/dist/{update-check-87p71vrc.js → update-check-dpkh7hc6.js} +8 -5
- package/package.json +2 -2
- package/dist/cli-aj56a1ja.js +0 -48
- package/dist/list-f7r354tq.js +0 -14
package/README.md
CHANGED
|
@@ -68,11 +68,15 @@ The `rig` CLI is installed on this machine. It allows you to write, run and own
|
|
|
68
68
|
- To discover available tools, run `rig list`.
|
|
69
69
|
- To learn about a tool's usage, run `rig help <tool>`.
|
|
70
70
|
- To run a tool, use `rig run <tool>.<command> [args]`.
|
|
71
|
+
- Tools run under Bun with fallback auto-install enabled, so tool files can import npm packages; add explicit package versions when reproducibility matters.
|
|
72
|
+
- To schedule a tool command, use `rig cron add <name> <tool>.<command> <schedule> --input '<json>'`; use `rig cron list`, `rig cron run <name>`, and `rig cron remove <name>` to manage scheduled runs.
|
|
73
|
+
- If a tool needs local secrets or settings, put them in the tool folder's `.env`, add an `env` Zod schema to the tool definition, and read validated values from `context.env`.
|
|
71
74
|
- To create a new tool, run `rig create <tool>`.
|
|
72
75
|
- To edit an existing tool, run `rig edit <tool>` and open the printed file path.
|
|
73
76
|
- To remove an existing tool, run `rig remove <tool>`.
|
|
74
77
|
- To list tool registries, run `rig registry list`.
|
|
75
78
|
- To add a registry, run `rig registry create [path]` (defaults to current directory).
|
|
79
|
+
- If a tool needs persistent state, define `setupDb` and use `context.db`; Rig stores that SQLite database beside the tool entry file as `index.sqlite`.
|
|
76
80
|
|
|
77
81
|
When rig runs, it keeps detected `AGENTS.md` and `CLAUDE.md` files updated with these instructions and the current `rig list` output.
|
|
78
82
|
|
|
@@ -1,20 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SchemaRenderer
|
|
3
|
-
} from "./cli-
|
|
3
|
+
} from "./cli-884hpkjc.js";
|
|
4
4
|
import {
|
|
5
5
|
CommandIds,
|
|
6
6
|
ToolLoader,
|
|
7
7
|
createRigToolKit
|
|
8
|
-
} from "./cli-
|
|
9
|
-
import"./cli-vx11bmzr.js";
|
|
10
|
-
import"./cli-xv4m20sx.js";
|
|
11
|
-
import"./cli-aj56a1ja.js";
|
|
8
|
+
} from "./cli-f7p31fzm.js";
|
|
12
9
|
import {
|
|
13
10
|
RigError,
|
|
14
11
|
RigErrors
|
|
15
12
|
} from "./cli-1c7te5cg.js";
|
|
16
|
-
import"./cli-ne7ed594.js";
|
|
17
|
-
import"./cli-b7jgjgy7.js";
|
|
18
13
|
|
|
19
14
|
// src/tools/run.ts
|
|
20
15
|
import { readFile } from "node:fs/promises";
|
|
@@ -94,7 +89,11 @@ class OutputTempFileStore {
|
|
|
94
89
|
async writeJson(content) {
|
|
95
90
|
const dir = await mkdtemp(join(tmpdir(), "rig-output-"));
|
|
96
91
|
const path = join(dir, "data.json");
|
|
97
|
-
|
|
92
|
+
if (typeof Bun !== "undefined")
|
|
93
|
+
await Bun.write(path, `${content}
|
|
94
|
+
`);
|
|
95
|
+
else
|
|
96
|
+
await writeFile(path, `${content}
|
|
98
97
|
`, "utf8");
|
|
99
98
|
return path;
|
|
100
99
|
}
|
|
@@ -156,6 +155,138 @@ class RigOutputTruncator {
|
|
|
156
155
|
}
|
|
157
156
|
}
|
|
158
157
|
|
|
158
|
+
// src/tools/db.ts
|
|
159
|
+
import { createHash } from "node:crypto";
|
|
160
|
+
import { mkdir } from "node:fs/promises";
|
|
161
|
+
import { dirname, join as join2 } from "node:path";
|
|
162
|
+
class MigrationChecksum {
|
|
163
|
+
digest(version, name, sql) {
|
|
164
|
+
return createHash("sha256").update(`${version}\x00${name}\x00${sql}`).digest("hex");
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
class RigDatabaseMigrator {
|
|
169
|
+
db;
|
|
170
|
+
lastVersion = 0;
|
|
171
|
+
checksums = new MigrationChecksum;
|
|
172
|
+
constructor(db) {
|
|
173
|
+
this.db = db;
|
|
174
|
+
}
|
|
175
|
+
ensureMetadata() {
|
|
176
|
+
this.db.run(`
|
|
177
|
+
create table if not exists _rig_migrations (
|
|
178
|
+
version integer primary key,
|
|
179
|
+
name text not null,
|
|
180
|
+
checksum text not null,
|
|
181
|
+
applied_at text not null
|
|
182
|
+
);
|
|
183
|
+
`);
|
|
184
|
+
}
|
|
185
|
+
migrate(version, name, sql) {
|
|
186
|
+
this.validate(version, name, sql);
|
|
187
|
+
const checksum = this.checksums.digest(version, name, sql);
|
|
188
|
+
const existing = this.existingMigration(version);
|
|
189
|
+
if (existing) {
|
|
190
|
+
this.assertExistingMigrationMatches(version, name, checksum, existing);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const apply = this.db.transaction(() => {
|
|
194
|
+
this.db.run(sql);
|
|
195
|
+
this.db.query(`insert into _rig_migrations (version, name, checksum, applied_at)
|
|
196
|
+
values ($version, $name, $checksum, $appliedAt)`).run({ version, name, checksum, appliedAt: new Date().toISOString() });
|
|
197
|
+
});
|
|
198
|
+
apply();
|
|
199
|
+
}
|
|
200
|
+
validate(version, name, sql) {
|
|
201
|
+
if (!Number.isInteger(version) || version <= 0) {
|
|
202
|
+
throw new RigError("TOOL_INVALID", `Migration version must be a positive integer: ${version}`);
|
|
203
|
+
}
|
|
204
|
+
if (version <= this.lastVersion) {
|
|
205
|
+
throw new RigError("TOOL_INVALID", `Migration versions must be declared in ascending order: ${version} after ${this.lastVersion}`);
|
|
206
|
+
}
|
|
207
|
+
if (!name.trim())
|
|
208
|
+
throw new RigError("TOOL_INVALID", "Migration name must not be empty.");
|
|
209
|
+
if (!sql.trim())
|
|
210
|
+
throw new RigError("TOOL_INVALID", `Migration ${version} SQL must not be empty.`);
|
|
211
|
+
this.lastVersion = version;
|
|
212
|
+
}
|
|
213
|
+
existingMigration(version) {
|
|
214
|
+
const row = this.db.query("select name, checksum from _rig_migrations where version = ?").get(version);
|
|
215
|
+
return row ?? undefined;
|
|
216
|
+
}
|
|
217
|
+
assertExistingMigrationMatches(version, name, checksum, existing) {
|
|
218
|
+
if (existing.name === name && existing.checksum === checksum)
|
|
219
|
+
return;
|
|
220
|
+
throw new RigError("TOOL_INVALID", `Migration ${version} has changed since it was applied.`, {
|
|
221
|
+
version,
|
|
222
|
+
expected: existing,
|
|
223
|
+
actual: { name, checksum }
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
class BunSqliteModuleLoader {
|
|
229
|
+
async database() {
|
|
230
|
+
const database = globalThis.rigSqliteDatabaseForTests;
|
|
231
|
+
if (!database)
|
|
232
|
+
return this.nativeDatabase();
|
|
233
|
+
return database;
|
|
234
|
+
}
|
|
235
|
+
async nativeDatabase() {
|
|
236
|
+
const specifier = "bun:sqlite";
|
|
237
|
+
const moduleValue = await import(specifier);
|
|
238
|
+
return moduleValue.Database;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
class RigToolDatabaseFactory {
|
|
243
|
+
sqlite = new BunSqliteModuleLoader;
|
|
244
|
+
async create(toolPath) {
|
|
245
|
+
const dbPath = this.dbPathForToolPath(toolPath);
|
|
246
|
+
await mkdir(dirname(dbPath), { recursive: true });
|
|
247
|
+
const Database = await this.sqlite.database();
|
|
248
|
+
const db = new Database(dbPath, { create: true, strict: true });
|
|
249
|
+
const migrator = new RigDatabaseMigrator(db);
|
|
250
|
+
Object.defineProperty(db, "path", { value: dbPath, enumerable: true });
|
|
251
|
+
Object.defineProperty(db, "migrate", {
|
|
252
|
+
value: (version, name, sql) => migrator.migrate(version, name, sql),
|
|
253
|
+
enumerable: true
|
|
254
|
+
});
|
|
255
|
+
db.run("PRAGMA journal_mode = WAL;");
|
|
256
|
+
migrator.ensureMetadata();
|
|
257
|
+
return db;
|
|
258
|
+
}
|
|
259
|
+
dbPathForToolPath(toolPath) {
|
|
260
|
+
return join2(dirname(toolPath), "index.sqlite");
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
class UnavailableToolDatabaseFactory {
|
|
265
|
+
create(toolName) {
|
|
266
|
+
return new Proxy({}, {
|
|
267
|
+
get() {
|
|
268
|
+
throw new RigError("TOOL_INVALID", `Tool ${toolName} must define setupDb before using context.db.`, {
|
|
269
|
+
tool: toolName
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
class ToolDatabaseService {
|
|
277
|
+
factory = new RigToolDatabaseFactory;
|
|
278
|
+
async setup(tool) {
|
|
279
|
+
if (!tool.definition.setupDb)
|
|
280
|
+
return;
|
|
281
|
+
const db = await this.factory.create(tool.path);
|
|
282
|
+
await tool.definition.setupDb(db);
|
|
283
|
+
return db;
|
|
284
|
+
}
|
|
285
|
+
dbPathForToolPath(toolPath) {
|
|
286
|
+
return this.factory.dbPathForToolPath(toolPath);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
159
290
|
// src/tools/run.ts
|
|
160
291
|
class RunInputReader {
|
|
161
292
|
async read(command, options) {
|
|
@@ -169,8 +300,8 @@ class RunInputReader {
|
|
|
169
300
|
throw new RigError("INPUT_ERROR", "Use args, --input, or --input-file, not more than one.");
|
|
170
301
|
}
|
|
171
302
|
if (options.inputFile) {
|
|
172
|
-
const
|
|
173
|
-
return { value
|
|
303
|
+
const value = typeof Bun !== "undefined" ? await Bun.file(options.inputFile).json() : JSON.parse(await readFile(options.inputFile, "utf8"));
|
|
304
|
+
return { value, source: `--input-file ${options.inputFile}` };
|
|
174
305
|
}
|
|
175
306
|
if (options.input) {
|
|
176
307
|
return { value: JSON.parse(options.input), source: `--input '${options.input}'` };
|
|
@@ -300,16 +431,19 @@ class DryRunPresenter {
|
|
|
300
431
|
|
|
301
432
|
class ToolRunner {
|
|
302
433
|
options;
|
|
434
|
+
databases = new ToolDatabaseService;
|
|
303
435
|
loader;
|
|
304
436
|
inputReader = new RunInputReader;
|
|
305
437
|
outputTruncator = new RigOutputTruncator;
|
|
438
|
+
unavailableDatabases = new UnavailableToolDatabaseFactory;
|
|
306
439
|
constructor(options = {}) {
|
|
307
440
|
this.options = options;
|
|
308
441
|
this.loader = new ToolLoader(options);
|
|
309
442
|
}
|
|
310
443
|
async run(toolName, commandName, options = {}) {
|
|
444
|
+
let db;
|
|
311
445
|
try {
|
|
312
|
-
const { command } = await this.loader.loadCommand(toolName, commandName);
|
|
446
|
+
const { tool, command } = await this.loader.loadCommand(toolName, commandName);
|
|
313
447
|
const input = await this.inputReader.read(command, options);
|
|
314
448
|
const inputResult = command.input.safeParse(input.value);
|
|
315
449
|
if (!inputResult.success) {
|
|
@@ -330,11 +464,13 @@ class ToolRunner {
|
|
|
330
464
|
};
|
|
331
465
|
}
|
|
332
466
|
const rig = createRigToolKit(this.options);
|
|
467
|
+
db = await this.databases.setup(tool);
|
|
333
468
|
const data = await command.run({
|
|
334
469
|
input: inputResult.data,
|
|
335
|
-
env:
|
|
470
|
+
env: tool.env,
|
|
471
|
+
processEnv: process.env,
|
|
336
472
|
cwd: process.cwd(),
|
|
337
|
-
|
|
473
|
+
db: db ?? this.unavailableDatabases.create(toolName),
|
|
338
474
|
rig
|
|
339
475
|
});
|
|
340
476
|
const outputResult = command.output.safeParse(data);
|
|
@@ -357,8 +493,15 @@ class ToolRunner {
|
|
|
357
493
|
}),
|
|
358
494
|
exitCode: 1
|
|
359
495
|
};
|
|
496
|
+
} finally {
|
|
497
|
+
this.closeDatabase(db);
|
|
360
498
|
}
|
|
361
499
|
}
|
|
500
|
+
closeDatabase(db) {
|
|
501
|
+
try {
|
|
502
|
+
db?.close(false);
|
|
503
|
+
} catch {}
|
|
504
|
+
}
|
|
362
505
|
asInputAwareRigError(error) {
|
|
363
506
|
if (error instanceof SyntaxError) {
|
|
364
507
|
return new RigError("INPUT_ERROR", "Input JSON is invalid.", { message: error.message });
|
|
@@ -366,6 +509,5 @@ class ToolRunner {
|
|
|
366
509
|
return RigErrors.from(error);
|
|
367
510
|
}
|
|
368
511
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
};
|
|
512
|
+
|
|
513
|
+
export { ToolRunner };
|
|
@@ -22,6 +22,9 @@ class RigPaths {
|
|
|
22
22
|
return isAbsolute(expanded) ? resolve(expanded) : resolve(process.cwd(), expanded);
|
|
23
23
|
}
|
|
24
24
|
get rigDir() {
|
|
25
|
+
return join(this.homeDir, "rig");
|
|
26
|
+
}
|
|
27
|
+
get legacyRigDir() {
|
|
25
28
|
return join(this.homeDir, ".rig");
|
|
26
29
|
}
|
|
27
30
|
get configPath() {
|
|
@@ -42,10 +45,19 @@ class RigPaths {
|
|
|
42
45
|
get runtimeToolTsconfigPath() {
|
|
43
46
|
return join(this.runtimeDir, "tsconfig.tools.json");
|
|
44
47
|
}
|
|
48
|
+
get cronDir() {
|
|
49
|
+
return join(this.rigDir, "cron");
|
|
50
|
+
}
|
|
51
|
+
cronWorkerPath(name) {
|
|
52
|
+
return join(this.cronDir, `${name}.ts`);
|
|
53
|
+
}
|
|
45
54
|
get updateCheckCachePath() {
|
|
46
55
|
return join(this.rigDir, "update-check.json");
|
|
47
56
|
}
|
|
48
57
|
get defaultBaseRegistryDir() {
|
|
58
|
+
return "~/rig/tools";
|
|
59
|
+
}
|
|
60
|
+
get legacyDefaultBaseRegistryDir() {
|
|
49
61
|
return "~/.rig/tools";
|
|
50
62
|
}
|
|
51
63
|
parentDir(pathValue) {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
RigConfigStore
|
|
3
|
-
} from "./cli-
|
|
3
|
+
} from "./cli-nk194xv0.js";
|
|
4
4
|
import {
|
|
5
5
|
RigError
|
|
6
6
|
} from "./cli-1c7te5cg.js";
|
|
7
7
|
import {
|
|
8
8
|
RigPaths
|
|
9
|
-
} from "./cli-
|
|
9
|
+
} from "./cli-13q90bjr.js";
|
|
10
10
|
|
|
11
11
|
// src/registry/discover.ts
|
|
12
12
|
import { existsSync, statSync } from "node:fs";
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ToolRunner
|
|
3
|
+
} from "./cli-113n7c3t.js";
|
|
4
|
+
import {
|
|
5
|
+
RigConfigStore
|
|
6
|
+
} from "./cli-nk194xv0.js";
|
|
7
|
+
import {
|
|
8
|
+
RigError
|
|
9
|
+
} from "./cli-1c7te5cg.js";
|
|
10
|
+
import {
|
|
11
|
+
RigPaths
|
|
12
|
+
} from "./cli-13q90bjr.js";
|
|
13
|
+
|
|
14
|
+
// src/tools/cron.ts
|
|
15
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
16
|
+
import { dirname } from "node:path";
|
|
17
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
18
|
+
class BunCronRegistrar {
|
|
19
|
+
register(path, schedule, title) {
|
|
20
|
+
return this.cron()(path, schedule, title);
|
|
21
|
+
}
|
|
22
|
+
remove(title) {
|
|
23
|
+
return this.cron().remove(title);
|
|
24
|
+
}
|
|
25
|
+
validate(schedule) {
|
|
26
|
+
const next = this.cron().parse(schedule);
|
|
27
|
+
if (!next)
|
|
28
|
+
throw new RigError("CRON_ERROR", `Cron schedule has no future runs: ${schedule}`);
|
|
29
|
+
}
|
|
30
|
+
cron() {
|
|
31
|
+
const cron = globalThis.Bun?.cron;
|
|
32
|
+
if (typeof cron !== "function") {
|
|
33
|
+
throw new RigError("CRON_ERROR", "Bun cron is unavailable. Run rig with Bun.");
|
|
34
|
+
}
|
|
35
|
+
return cron;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
class CronJobName {
|
|
40
|
+
value;
|
|
41
|
+
constructor(value) {
|
|
42
|
+
this.value = value;
|
|
43
|
+
if (!/^[A-Za-z0-9_-]+$/.test(value)) {
|
|
44
|
+
throw new RigError("INPUT_ERROR", "Cron job names may only contain letters, numbers, hyphens, and underscores.", { name: value });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
class CronInputReader {
|
|
50
|
+
async read(options) {
|
|
51
|
+
if (options.input && options.inputFile) {
|
|
52
|
+
throw new RigError("INPUT_ERROR", "Use --input or --input-file, not both.");
|
|
53
|
+
}
|
|
54
|
+
if (options.inputFile) {
|
|
55
|
+
return typeof Bun !== "undefined" ? Bun.file(options.inputFile).json() : JSON.parse(await readFile(options.inputFile, "utf8"));
|
|
56
|
+
}
|
|
57
|
+
return options.input === undefined ? undefined : JSON.parse(options.input);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
class CronCommandTarget {
|
|
62
|
+
id;
|
|
63
|
+
tool;
|
|
64
|
+
command;
|
|
65
|
+
constructor(id) {
|
|
66
|
+
this.id = id;
|
|
67
|
+
const parts = id.split(".");
|
|
68
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
69
|
+
throw new RigError("INPUT_ERROR", `Command id must use <tool>.<command>: ${id}`);
|
|
70
|
+
}
|
|
71
|
+
this.tool = parts[0];
|
|
72
|
+
this.command = parts[1];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
class CronWorkerScript {
|
|
77
|
+
render(params) {
|
|
78
|
+
const entrypoint = fileURLToPath(params.moduleUrl);
|
|
79
|
+
const env = params.homeDir ? { RIG_HOME: params.homeDir } : {};
|
|
80
|
+
return `export default {
|
|
81
|
+
async scheduled() {
|
|
82
|
+
const proc = Bun.spawn([
|
|
83
|
+
process.execPath,
|
|
84
|
+
"--install=fallback",
|
|
85
|
+
${JSON.stringify(entrypoint)},
|
|
86
|
+
"cron",
|
|
87
|
+
"run",
|
|
88
|
+
${JSON.stringify(params.name)},
|
|
89
|
+
], {
|
|
90
|
+
stdout: "pipe",
|
|
91
|
+
stderr: "pipe",
|
|
92
|
+
env: { ...process.env, ...${JSON.stringify(env)} },
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
96
|
+
new Response(proc.stdout).text(),
|
|
97
|
+
new Response(proc.stderr).text(),
|
|
98
|
+
proc.exited,
|
|
99
|
+
]);
|
|
100
|
+
if (stdout) console.log(stdout.trimEnd());
|
|
101
|
+
if (stderr) console.error(stderr.trimEnd());
|
|
102
|
+
if (exitCode !== 0) throw new Error("Rig cron job failed: " + ${JSON.stringify(params.name)});
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
`;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
class RigCronService {
|
|
110
|
+
options;
|
|
111
|
+
registrar;
|
|
112
|
+
paths;
|
|
113
|
+
configStore;
|
|
114
|
+
inputReader = new CronInputReader;
|
|
115
|
+
workerScript = new CronWorkerScript;
|
|
116
|
+
constructor(options = {}, registrar = new BunCronRegistrar) {
|
|
117
|
+
this.options = options;
|
|
118
|
+
this.registrar = registrar;
|
|
119
|
+
this.paths = new RigPaths(options);
|
|
120
|
+
this.configStore = new RigConfigStore(options);
|
|
121
|
+
}
|
|
122
|
+
async list() {
|
|
123
|
+
const config = await this.configStore.ensure();
|
|
124
|
+
return {
|
|
125
|
+
cronJobs: [...config.cronJobs].toSorted((left, right) => left.name.localeCompare(right.name))
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
async add(options) {
|
|
129
|
+
const name = new CronJobName(options.name);
|
|
130
|
+
const target = new CronCommandTarget(options.command);
|
|
131
|
+
const input = await this.inputReader.read(options);
|
|
132
|
+
this.registrar.validate(options.schedule);
|
|
133
|
+
await this.validateCommand(target, input);
|
|
134
|
+
const config = await this.configStore.ensure();
|
|
135
|
+
const job = {
|
|
136
|
+
name: name.value,
|
|
137
|
+
command: target.id,
|
|
138
|
+
schedule: options.schedule,
|
|
139
|
+
...input === undefined ? {} : { input }
|
|
140
|
+
};
|
|
141
|
+
const workerPath = this.paths.cronWorkerPath(name.value);
|
|
142
|
+
await this.writeWorker(workerPath, {
|
|
143
|
+
name: name.value,
|
|
144
|
+
homeDir: this.options.homeDir,
|
|
145
|
+
moduleUrl: options.moduleUrl
|
|
146
|
+
});
|
|
147
|
+
await this.configStore.write({
|
|
148
|
+
...config,
|
|
149
|
+
cronJobs: [...config.cronJobs.filter((existing) => existing.name !== name.value), job]
|
|
150
|
+
});
|
|
151
|
+
try {
|
|
152
|
+
await this.registrar.register(workerPath, options.schedule, name.value);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
await this.configStore.write(config);
|
|
155
|
+
await rm(workerPath, { force: true });
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
return { job, workerPath };
|
|
159
|
+
}
|
|
160
|
+
async remove(nameValue) {
|
|
161
|
+
const name = new CronJobName(nameValue);
|
|
162
|
+
const config = await this.configStore.ensure();
|
|
163
|
+
const workerPath = this.paths.cronWorkerPath(name.value);
|
|
164
|
+
const nextJobs = config.cronJobs.filter((job) => job.name !== name.value);
|
|
165
|
+
await this.configStore.write({ ...config, cronJobs: nextJobs });
|
|
166
|
+
await this.registrar.remove(name.value);
|
|
167
|
+
await rm(workerPath, { force: true });
|
|
168
|
+
return { name: name.value, removed: nextJobs.length !== config.cronJobs.length, workerPath };
|
|
169
|
+
}
|
|
170
|
+
async run(nameValue) {
|
|
171
|
+
const name = new CronJobName(nameValue);
|
|
172
|
+
const config = await this.configStore.ensure();
|
|
173
|
+
const job = config.cronJobs.find((candidate) => candidate.name === name.value);
|
|
174
|
+
if (!job)
|
|
175
|
+
throw new RigError("CRON_ERROR", `Cron job not found: ${name.value}`, { name });
|
|
176
|
+
const target = new CronCommandTarget(job.command);
|
|
177
|
+
const result = await new ToolRunner(this.options).run(target.tool, target.command, {
|
|
178
|
+
...this.options,
|
|
179
|
+
input: job.input === undefined ? undefined : JSON.stringify(job.input)
|
|
180
|
+
});
|
|
181
|
+
return { job, envelope: result.envelope, exitCode: result.exitCode };
|
|
182
|
+
}
|
|
183
|
+
async validateCommand(target, input) {
|
|
184
|
+
const result = await new ToolRunner(this.options).run(target.tool, target.command, {
|
|
185
|
+
...this.options,
|
|
186
|
+
input: input === undefined ? undefined : JSON.stringify(input),
|
|
187
|
+
dryRun: true
|
|
188
|
+
});
|
|
189
|
+
if (result.exitCode !== 0) {
|
|
190
|
+
throw new RigError("CRON_ERROR", `Cron command validation failed: ${target.id}`, {
|
|
191
|
+
envelope: result.envelope
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async writeWorker(workerPath, params) {
|
|
196
|
+
await mkdir(dirname(workerPath), { recursive: true });
|
|
197
|
+
await writeFile(workerPath, this.workerScript.render(params), "utf8");
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
class RigCronWorker {
|
|
202
|
+
options;
|
|
203
|
+
constructor(options = {}) {
|
|
204
|
+
this.options = options;
|
|
205
|
+
}
|
|
206
|
+
async scheduled(name, controller) {
|
|
207
|
+
const result = await new RigCronService(this.options).run(name);
|
|
208
|
+
const envelope = result.envelope;
|
|
209
|
+
console.log(JSON.stringify({
|
|
210
|
+
cron: {
|
|
211
|
+
name: result.job.name,
|
|
212
|
+
command: result.job.command,
|
|
213
|
+
schedule: controller?.cron ?? result.job.schedule,
|
|
214
|
+
scheduledTime: controller?.scheduledTime,
|
|
215
|
+
type: controller?.type
|
|
216
|
+
},
|
|
217
|
+
result: envelope
|
|
218
|
+
}, null, 2));
|
|
219
|
+
process.exitCode = result.exitCode;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function cronModuleUrl(metaUrl) {
|
|
223
|
+
return pathToFileURL(fileURLToPath(metaUrl)).href;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export { RigCronService, RigCronWorker, cronModuleUrl };
|