@staticduo/opencode-scheduler 1.3.3 → 1.3.5
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 +1 -1
- package/dist/index.js +239 -60
- package/dist/systemd.d.ts +17 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -96,7 +96,7 @@ Jobs run from the working directory where you created them, picking up your `ope
|
|
|
96
96
|
- **No overlap**: if the previous run is still active, the next scheduled tick is skipped.
|
|
97
97
|
- **Non-interactive by default**: scheduled runs force `OPENCODE_PERMISSION` to deny "question" prompts, so jobs don't hang waiting for approvals.
|
|
98
98
|
- **Optional timeout**: set `timeoutSeconds` to hard-stop long runs (SIGTERM, then SIGKILL).
|
|
99
|
-
- **Transactional systemd updates**: Linux
|
|
99
|
+
- **Transactional systemd updates**: Linux installation preserves regular files or exact symlink targets (including masked units), permissions, unit-file state, and active state on failure. A bounded cross-process lock serializes updates to the same scoped timer; live locks are never broken and dead stale locks are reclaimed.
|
|
100
100
|
|
|
101
101
|
### Platform Support
|
|
102
102
|
|
package/dist/index.js
CHANGED
|
@@ -12335,7 +12335,7 @@ function tool(input) {
|
|
|
12335
12335
|
}
|
|
12336
12336
|
tool.schema = exports_external;
|
|
12337
12337
|
// src/index.ts
|
|
12338
|
-
import { createWriteStream, existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
12338
|
+
import { createWriteStream, existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
12339
12339
|
import { basename, dirname, join as join2, resolve as resolvePath } from "path";
|
|
12340
12340
|
import { homedir, platform } from "os";
|
|
12341
12341
|
import { execFileSync, execSync, spawn } from "child_process";
|
|
@@ -12435,10 +12435,14 @@ function cronToSystemdCalendars(cron) {
|
|
|
12435
12435
|
import {
|
|
12436
12436
|
chmodSync,
|
|
12437
12437
|
existsSync,
|
|
12438
|
+
lstatSync,
|
|
12438
12439
|
mkdirSync,
|
|
12439
12440
|
readFileSync,
|
|
12441
|
+
readlinkSync,
|
|
12440
12442
|
renameSync,
|
|
12443
|
+
rmSync,
|
|
12441
12444
|
statSync,
|
|
12445
|
+
symlinkSync,
|
|
12442
12446
|
unlinkSync,
|
|
12443
12447
|
writeFileSync
|
|
12444
12448
|
} from "fs";
|
|
@@ -12467,36 +12471,97 @@ function withSystemdRuntimeEnv(env, dependencies = defaultRuntimeEnvDependencies
|
|
|
12467
12471
|
var defaultFileSystem = {
|
|
12468
12472
|
chmod: chmodSync,
|
|
12469
12473
|
exists: existsSync,
|
|
12474
|
+
lstat: lstatSync,
|
|
12470
12475
|
mkdir: mkdirSync,
|
|
12471
12476
|
readFile: readFileSync,
|
|
12477
|
+
readlink: readlinkSync,
|
|
12472
12478
|
rename: renameSync,
|
|
12479
|
+
rm: rmSync,
|
|
12473
12480
|
stat: statSync,
|
|
12481
|
+
symlink: symlinkSync,
|
|
12474
12482
|
unlink: unlinkSync,
|
|
12475
12483
|
writeFile: writeFileSync
|
|
12476
12484
|
};
|
|
12485
|
+
var sleepArray = new Int32Array(new SharedArrayBuffer(4));
|
|
12486
|
+
var defaultLockOptions = {
|
|
12487
|
+
timeoutMs: 1e4,
|
|
12488
|
+
staleAfterMs: 60000,
|
|
12489
|
+
pollMs: 25,
|
|
12490
|
+
now: Date.now,
|
|
12491
|
+
pid: process.pid,
|
|
12492
|
+
isPidAlive(pid) {
|
|
12493
|
+
try {
|
|
12494
|
+
process.kill(pid, 0);
|
|
12495
|
+
return true;
|
|
12496
|
+
} catch {
|
|
12497
|
+
return false;
|
|
12498
|
+
}
|
|
12499
|
+
},
|
|
12500
|
+
sleep(milliseconds) {
|
|
12501
|
+
Atomics.wait(sleepArray, 0, 0, milliseconds);
|
|
12502
|
+
}
|
|
12503
|
+
};
|
|
12477
12504
|
function snapshotFile(path, fileSystem) {
|
|
12478
|
-
|
|
12479
|
-
|
|
12480
|
-
|
|
12481
|
-
|
|
12482
|
-
|
|
12483
|
-
|
|
12484
|
-
|
|
12485
|
-
}
|
|
12505
|
+
let stats;
|
|
12506
|
+
try {
|
|
12507
|
+
stats = fileSystem.lstat(path);
|
|
12508
|
+
} catch (error45) {
|
|
12509
|
+
if (isErrorCode(error45, "ENOENT"))
|
|
12510
|
+
return { path, type: "missing" };
|
|
12511
|
+
throw error45;
|
|
12512
|
+
}
|
|
12513
|
+
if (stats.isSymbolicLink())
|
|
12514
|
+
return { path, type: "symlink", target: fileSystem.readlink(path) };
|
|
12515
|
+
if (stats.isFile()) {
|
|
12516
|
+
return { path, type: "regular", content: fileSystem.readFile(path), mode: stats.mode & 511 };
|
|
12517
|
+
}
|
|
12518
|
+
throw new Error(`Unsupported systemd unit node type at ${path}; refusing to mutate it`);
|
|
12486
12519
|
}
|
|
12487
12520
|
var temporaryFileSequence = 0;
|
|
12488
|
-
function
|
|
12521
|
+
function temporaryPath(path) {
|
|
12489
12522
|
temporaryFileSequence += 1;
|
|
12490
|
-
|
|
12523
|
+
return `${path}.tmp-${process.pid}-${temporaryFileSequence}`;
|
|
12524
|
+
}
|
|
12525
|
+
function atomicReplace(path, content, mode, fileSystem) {
|
|
12526
|
+
const temporary = temporaryPath(path);
|
|
12527
|
+
let primaryError;
|
|
12491
12528
|
try {
|
|
12492
|
-
fileSystem.writeFile(
|
|
12493
|
-
fileSystem.chmod(
|
|
12494
|
-
fileSystem.rename(
|
|
12529
|
+
fileSystem.writeFile(temporary, content, { mode });
|
|
12530
|
+
fileSystem.chmod(temporary, mode);
|
|
12531
|
+
fileSystem.rename(temporary, path);
|
|
12495
12532
|
fileSystem.chmod(path, mode);
|
|
12533
|
+
} catch (error45) {
|
|
12534
|
+
primaryError = error45;
|
|
12535
|
+
throw error45;
|
|
12496
12536
|
} finally {
|
|
12497
|
-
|
|
12498
|
-
|
|
12499
|
-
|
|
12537
|
+
if (primaryError)
|
|
12538
|
+
bestEffort(() => removeNode(temporary, fileSystem));
|
|
12539
|
+
else
|
|
12540
|
+
removeNode(temporary, fileSystem);
|
|
12541
|
+
}
|
|
12542
|
+
}
|
|
12543
|
+
function atomicSymlink(path, target, fileSystem) {
|
|
12544
|
+
const temporary = temporaryPath(path);
|
|
12545
|
+
let primaryError;
|
|
12546
|
+
try {
|
|
12547
|
+
fileSystem.symlink(target, temporary);
|
|
12548
|
+
fileSystem.rename(temporary, path);
|
|
12549
|
+
} catch (error45) {
|
|
12550
|
+
primaryError = error45;
|
|
12551
|
+
throw error45;
|
|
12552
|
+
} finally {
|
|
12553
|
+
if (primaryError)
|
|
12554
|
+
bestEffort(() => removeNode(temporary, fileSystem));
|
|
12555
|
+
else
|
|
12556
|
+
removeNode(temporary, fileSystem);
|
|
12557
|
+
}
|
|
12558
|
+
}
|
|
12559
|
+
function removeNode(path, fileSystem) {
|
|
12560
|
+
try {
|
|
12561
|
+
fileSystem.unlink(path);
|
|
12562
|
+
} catch (error45) {
|
|
12563
|
+
if (!isErrorCode(error45, "ENOENT"))
|
|
12564
|
+
throw error45;
|
|
12500
12565
|
}
|
|
12501
12566
|
}
|
|
12502
12567
|
function commandOutput(value) {
|
|
@@ -12513,69 +12578,182 @@ function commandOutput(value) {
|
|
|
12513
12578
|
}
|
|
12514
12579
|
return "";
|
|
12515
12580
|
}
|
|
12516
|
-
function
|
|
12581
|
+
function queryUnitFileState(run, timerUnit) {
|
|
12517
12582
|
let output = "";
|
|
12518
12583
|
try {
|
|
12519
|
-
output = commandOutput(run(`systemctl --user
|
|
12584
|
+
output = commandOutput(run(`systemctl --user is-enabled ${timerUnit}`, { stdio: ["ignore", "pipe", "ignore"] }));
|
|
12520
12585
|
} catch (error45) {
|
|
12521
12586
|
output = commandOutput(error45);
|
|
12522
12587
|
}
|
|
12523
|
-
const
|
|
12524
|
-
|
|
12525
|
-
|
|
12526
|
-
|
|
12527
|
-
|
|
12528
|
-
|
|
12529
|
-
|
|
12530
|
-
|
|
12531
|
-
|
|
12588
|
+
const supported = [
|
|
12589
|
+
"enabled",
|
|
12590
|
+
"enabled-runtime",
|
|
12591
|
+
"disabled",
|
|
12592
|
+
"static",
|
|
12593
|
+
"indirect",
|
|
12594
|
+
"masked",
|
|
12595
|
+
"masked-runtime",
|
|
12596
|
+
"linked",
|
|
12597
|
+
"linked-runtime",
|
|
12598
|
+
"alias",
|
|
12599
|
+
"not-found"
|
|
12600
|
+
];
|
|
12601
|
+
if (supported.includes(output))
|
|
12602
|
+
return output;
|
|
12603
|
+
const unrecoverable = ["generated", "transient", "bad"];
|
|
12604
|
+
if (unrecoverable.includes(output)) {
|
|
12605
|
+
throw new Error(`Cannot safely restore ${timerUnit} from systemd unit-file state ${output}; refusing to mutate it`);
|
|
12606
|
+
}
|
|
12607
|
+
throw new Error(`Unable to determine ${timerUnit} unit-file state: ${output || "no status returned"}`);
|
|
12608
|
+
}
|
|
12609
|
+
function queryActiveState(run, timerUnit) {
|
|
12610
|
+
let output = "";
|
|
12611
|
+
try {
|
|
12612
|
+
output = commandOutput(run(`systemctl --user is-active ${timerUnit}`, { stdio: ["ignore", "pipe", "ignore"] }));
|
|
12613
|
+
} catch (error45) {
|
|
12614
|
+
output = commandOutput(error45);
|
|
12615
|
+
}
|
|
12616
|
+
if (["active", "activating", "reloading"].includes(output))
|
|
12532
12617
|
return true;
|
|
12533
|
-
if (
|
|
12618
|
+
if (["inactive", "failed", "deactivating", "unknown"].includes(output))
|
|
12534
12619
|
return false;
|
|
12535
|
-
throw new Error(`Unable to determine whether ${timerUnit}
|
|
12620
|
+
throw new Error(`Unable to determine whether ${timerUnit} is active: ${output || "no status returned"}`);
|
|
12536
12621
|
}
|
|
12537
12622
|
function restoreFile(snapshot, fileSystem) {
|
|
12538
|
-
if (
|
|
12539
|
-
|
|
12540
|
-
|
|
12541
|
-
|
|
12542
|
-
|
|
12623
|
+
if (snapshot.type === "missing") {
|
|
12624
|
+
removeNode(snapshot.path, fileSystem);
|
|
12625
|
+
} else if (snapshot.type === "symlink") {
|
|
12626
|
+
atomicSymlink(snapshot.path, snapshot.target, fileSystem);
|
|
12627
|
+
} else {
|
|
12628
|
+
atomicReplace(snapshot.path, snapshot.content, snapshot.mode, fileSystem);
|
|
12543
12629
|
}
|
|
12544
|
-
atomicReplace(snapshot.path, snapshot.content ?? Buffer.alloc(0), snapshot.mode ?? 420, fileSystem);
|
|
12545
12630
|
}
|
|
12546
12631
|
function bestEffort(action) {
|
|
12547
12632
|
try {
|
|
12548
12633
|
action();
|
|
12549
12634
|
} catch {}
|
|
12550
12635
|
}
|
|
12636
|
+
function restoreUnitFileState(run, timerUnit, state) {
|
|
12637
|
+
if (state === "enabled")
|
|
12638
|
+
run(`systemctl --user enable ${timerUnit}`, { stdio: "ignore" });
|
|
12639
|
+
if (state === "enabled-runtime")
|
|
12640
|
+
run(`systemctl --user enable --runtime ${timerUnit}`, { stdio: "ignore" });
|
|
12641
|
+
if (state === "disabled")
|
|
12642
|
+
run(`systemctl --user disable ${timerUnit}`, { stdio: "ignore" });
|
|
12643
|
+
}
|
|
12644
|
+
function isErrorCode(error45, code) {
|
|
12645
|
+
return typeof error45 === "object" && error45 !== null && "code" in error45 && error45.code === code;
|
|
12646
|
+
}
|
|
12647
|
+
function readLockMetadata(lockPath, fileSystem) {
|
|
12648
|
+
try {
|
|
12649
|
+
const parsed = JSON.parse(fileSystem.readFile(join(lockPath, "owner.json"), "utf8"));
|
|
12650
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
12651
|
+
return {};
|
|
12652
|
+
const record2 = parsed;
|
|
12653
|
+
return {
|
|
12654
|
+
pid: typeof record2.pid === "number" ? record2.pid : undefined,
|
|
12655
|
+
timestamp: typeof record2.timestamp === "number" ? record2.timestamp : undefined
|
|
12656
|
+
};
|
|
12657
|
+
} catch {
|
|
12658
|
+
return {};
|
|
12659
|
+
}
|
|
12660
|
+
}
|
|
12661
|
+
function acquireLock(lockRoot, timerUnit, fileSystem, overrides) {
|
|
12662
|
+
const options = { ...defaultLockOptions, ...overrides };
|
|
12663
|
+
fileSystem.mkdir(lockRoot, { recursive: true });
|
|
12664
|
+
const lockPath = join(lockRoot, `${timerUnit}.lock`);
|
|
12665
|
+
const startedAt = options.now();
|
|
12666
|
+
while (true) {
|
|
12667
|
+
try {
|
|
12668
|
+
fileSystem.mkdir(lockPath);
|
|
12669
|
+
try {
|
|
12670
|
+
fileSystem.writeFile(join(lockPath, "owner.json"), JSON.stringify({ pid: options.pid, timestamp: options.now() }));
|
|
12671
|
+
} catch (error45) {
|
|
12672
|
+
fileSystem.rm(lockPath, { recursive: true, force: true });
|
|
12673
|
+
throw error45;
|
|
12674
|
+
}
|
|
12675
|
+
return () => fileSystem.rm(lockPath, { recursive: true, force: true });
|
|
12676
|
+
} catch (error45) {
|
|
12677
|
+
if (!isErrorCode(error45, "EEXIST"))
|
|
12678
|
+
throw error45;
|
|
12679
|
+
let lockStats;
|
|
12680
|
+
try {
|
|
12681
|
+
lockStats = fileSystem.lstat(lockPath);
|
|
12682
|
+
} catch (inspectError) {
|
|
12683
|
+
if (isErrorCode(inspectError, "ENOENT"))
|
|
12684
|
+
continue;
|
|
12685
|
+
throw inspectError;
|
|
12686
|
+
}
|
|
12687
|
+
if (lockStats.isSymbolicLink()) {
|
|
12688
|
+
throw new Error(`Refusing systemd install lock symlink at ${lockPath}`);
|
|
12689
|
+
}
|
|
12690
|
+
if (!lockStats.isDirectory()) {
|
|
12691
|
+
throw new Error(`Refusing non-directory systemd install lock at ${lockPath}`);
|
|
12692
|
+
}
|
|
12693
|
+
const metadata = readLockMetadata(lockPath, fileSystem);
|
|
12694
|
+
const timestamp = metadata.timestamp ?? lockStats.mtimeMs;
|
|
12695
|
+
const oldEnough = options.now() - timestamp >= options.staleAfterMs;
|
|
12696
|
+
const ownerAlive = metadata.pid !== undefined && options.isPidAlive(metadata.pid);
|
|
12697
|
+
if (oldEnough && !ownerAlive) {
|
|
12698
|
+
const quarantinePath = join(lockRoot, `${timerUnit}.stale-${options.pid}-${options.now()}-${temporaryFileSequence += 1}`);
|
|
12699
|
+
try {
|
|
12700
|
+
fileSystem.rename(lockPath, quarantinePath);
|
|
12701
|
+
} catch (claimError) {
|
|
12702
|
+
if (isErrorCode(claimError, "ENOENT") || isErrorCode(claimError, "EEXIST"))
|
|
12703
|
+
continue;
|
|
12704
|
+
throw claimError;
|
|
12705
|
+
}
|
|
12706
|
+
fileSystem.rm(quarantinePath, { recursive: true, force: true });
|
|
12707
|
+
continue;
|
|
12708
|
+
}
|
|
12709
|
+
if (options.now() - startedAt >= options.timeoutMs) {
|
|
12710
|
+
throw new Error(`Timed out waiting ${options.timeoutMs}ms for systemd install lock ${lockPath}`);
|
|
12711
|
+
}
|
|
12712
|
+
options.sleep(options.pollMs);
|
|
12713
|
+
}
|
|
12714
|
+
}
|
|
12715
|
+
}
|
|
12551
12716
|
function installSystemdUnits(request) {
|
|
12552
12717
|
const fileSystem = request.fileSystem ?? defaultFileSystem;
|
|
12553
|
-
|
|
12554
|
-
|
|
12555
|
-
const timerPath = join(request.unitDir, request.timerUnit);
|
|
12556
|
-
const serviceSnapshot = snapshotFile(servicePath, fileSystem);
|
|
12557
|
-
const timerSnapshot = snapshotFile(timerPath, fileSystem);
|
|
12558
|
-
const wasEnabled = queryTimerState(request.run, request.timerUnit, "is-enabled");
|
|
12559
|
-
const wasActive = queryTimerState(request.run, request.timerUnit, "is-active");
|
|
12718
|
+
const releaseLock = acquireLock(request.lockDir ?? join(request.unitDir, ".opencode-scheduler-locks"), request.timerUnit, fileSystem, request.lock);
|
|
12719
|
+
let primaryError;
|
|
12560
12720
|
try {
|
|
12561
|
-
|
|
12562
|
-
|
|
12563
|
-
request.
|
|
12564
|
-
|
|
12565
|
-
|
|
12721
|
+
fileSystem.mkdir(request.unitDir, { recursive: true });
|
|
12722
|
+
const servicePath = join(request.unitDir, request.serviceUnit);
|
|
12723
|
+
const timerPath = join(request.unitDir, request.timerUnit);
|
|
12724
|
+
const serviceSnapshot = snapshotFile(servicePath, fileSystem);
|
|
12725
|
+
const timerSnapshot = snapshotFile(timerPath, fileSystem);
|
|
12726
|
+
const unitFileState = queryUnitFileState(request.run, request.timerUnit);
|
|
12727
|
+
const wasActive = queryActiveState(request.run, request.timerUnit);
|
|
12728
|
+
let enabledByAttempt = false;
|
|
12729
|
+
try {
|
|
12730
|
+
atomicReplace(servicePath, request.serviceContent, 420, fileSystem);
|
|
12731
|
+
atomicReplace(timerPath, request.timerContent, 420, fileSystem);
|
|
12732
|
+
request.run("systemctl --user daemon-reload");
|
|
12733
|
+
request.run(`systemctl --user enable ${request.timerUnit}`);
|
|
12734
|
+
enabledByAttempt = true;
|
|
12735
|
+
request.run(`systemctl --user start ${request.timerUnit}`);
|
|
12736
|
+
} catch (error45) {
|
|
12737
|
+
if (!wasActive)
|
|
12738
|
+
bestEffort(() => request.run(`systemctl --user stop ${request.timerUnit}`, { stdio: "ignore" }));
|
|
12739
|
+
if (enabledByAttempt)
|
|
12740
|
+
bestEffort(() => request.run(`systemctl --user disable ${request.timerUnit}`, { stdio: "ignore" }));
|
|
12741
|
+
bestEffort(() => restoreFile(serviceSnapshot, fileSystem));
|
|
12742
|
+
bestEffort(() => restoreFile(timerSnapshot, fileSystem));
|
|
12743
|
+
bestEffort(() => request.run("systemctl --user daemon-reload", { stdio: "ignore" }));
|
|
12744
|
+
bestEffort(() => restoreUnitFileState(request.run, request.timerUnit, unitFileState));
|
|
12745
|
+
if (wasActive)
|
|
12746
|
+
bestEffort(() => request.run(`systemctl --user start ${request.timerUnit}`, { stdio: "ignore" }));
|
|
12747
|
+
throw error45;
|
|
12748
|
+
}
|
|
12566
12749
|
} catch (error45) {
|
|
12567
|
-
|
|
12568
|
-
bestEffort(() => request.run(`systemctl --user stop ${request.timerUnit}`, { stdio: "ignore" }));
|
|
12569
|
-
if (!wasEnabled)
|
|
12570
|
-
bestEffort(() => request.run(`systemctl --user disable ${request.timerUnit}`, { stdio: "ignore" }));
|
|
12571
|
-
bestEffort(() => restoreFile(serviceSnapshot, fileSystem));
|
|
12572
|
-
bestEffort(() => restoreFile(timerSnapshot, fileSystem));
|
|
12573
|
-
bestEffort(() => request.run("systemctl --user daemon-reload", { stdio: "ignore" }));
|
|
12574
|
-
if (wasEnabled)
|
|
12575
|
-
bestEffort(() => request.run(`systemctl --user enable ${request.timerUnit}`, { stdio: "ignore" }));
|
|
12576
|
-
if (wasActive)
|
|
12577
|
-
bestEffort(() => request.run(`systemctl --user start ${request.timerUnit}`, { stdio: "ignore" }));
|
|
12750
|
+
primaryError = error45;
|
|
12578
12751
|
throw error45;
|
|
12752
|
+
} finally {
|
|
12753
|
+
if (primaryError)
|
|
12754
|
+
bestEffort(releaseLock);
|
|
12755
|
+
else
|
|
12756
|
+
releaseLock();
|
|
12579
12757
|
}
|
|
12580
12758
|
}
|
|
12581
12759
|
|
|
@@ -13436,6 +13614,7 @@ function installSystemdJob(job, run = defaultSystemdCommandRunner) {
|
|
|
13436
13614
|
const timerUnit = timerPath.slice(SYSTEMD_USER_DIR.length + 1);
|
|
13437
13615
|
installSystemdUnits({
|
|
13438
13616
|
unitDir: SYSTEMD_USER_DIR,
|
|
13617
|
+
lockDir: join2(SCHEDULER_DIR, "systemd-install-locks"),
|
|
13439
13618
|
serviceUnit,
|
|
13440
13619
|
timerUnit,
|
|
13441
13620
|
serviceContent: createSystemdService(job),
|
|
@@ -13823,7 +14002,7 @@ function removePaths(paths, errors3) {
|
|
|
13823
14002
|
if (!existsSync2(path))
|
|
13824
14003
|
continue;
|
|
13825
14004
|
try {
|
|
13826
|
-
|
|
14005
|
+
rmSync2(path, { recursive: true, force: true });
|
|
13827
14006
|
removed.push(path);
|
|
13828
14007
|
} catch (error45) {
|
|
13829
14008
|
const msg = error45 instanceof Error ? error45.message : String(error45);
|
package/dist/systemd.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
1
|
+
import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync, renameSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "fs";
|
|
2
2
|
import type { ExecSyncOptions } from "child_process";
|
|
3
3
|
export type SystemdCommandRunner = (command: string, options?: ExecSyncOptions) => Buffer | string;
|
|
4
4
|
export interface RuntimeEnvDependencies {
|
|
@@ -8,21 +8,37 @@ export interface RuntimeEnvDependencies {
|
|
|
8
8
|
export declare function withSystemdRuntimeEnv(env: NodeJS.ProcessEnv, dependencies?: RuntimeEnvDependencies): NodeJS.ProcessEnv;
|
|
9
9
|
export interface SystemdInstallRequest {
|
|
10
10
|
unitDir: string;
|
|
11
|
+
lockDir?: string;
|
|
11
12
|
serviceUnit: string;
|
|
12
13
|
timerUnit: string;
|
|
13
14
|
serviceContent: string;
|
|
14
15
|
timerContent: string;
|
|
15
16
|
run: SystemdCommandRunner;
|
|
16
17
|
fileSystem?: SystemdFileSystem;
|
|
18
|
+
lock?: Partial<SystemdLockOptions>;
|
|
17
19
|
}
|
|
18
20
|
export interface SystemdFileSystem {
|
|
19
21
|
chmod: typeof chmodSync;
|
|
20
22
|
exists: typeof existsSync;
|
|
23
|
+
lstat: typeof lstatSync;
|
|
21
24
|
mkdir: typeof mkdirSync;
|
|
22
25
|
readFile: typeof readFileSync;
|
|
26
|
+
readlink: typeof readlinkSync;
|
|
23
27
|
rename: typeof renameSync;
|
|
28
|
+
rm: typeof rmSync;
|
|
24
29
|
stat: typeof statSync;
|
|
30
|
+
symlink: typeof symlinkSync;
|
|
25
31
|
unlink: typeof unlinkSync;
|
|
26
32
|
writeFile: typeof writeFileSync;
|
|
27
33
|
}
|
|
34
|
+
interface SystemdLockOptions {
|
|
35
|
+
timeoutMs: number;
|
|
36
|
+
staleAfterMs: number;
|
|
37
|
+
pollMs: number;
|
|
38
|
+
now: () => number;
|
|
39
|
+
pid: number;
|
|
40
|
+
isPidAlive: (pid: number) => boolean;
|
|
41
|
+
sleep: (milliseconds: number) => void;
|
|
42
|
+
}
|
|
28
43
|
export declare function installSystemdUnits(request: SystemdInstallRequest): void;
|
|
44
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@staticduo/opencode-scheduler",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.5",
|
|
4
4
|
"description": "OpenCode plugin for scheduling recurring jobs using launchd, systemd, Task Scheduler, or cron fallback",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|