重构 lib 为扁平模块并支持 Windows schtasks;新增 JS/Bash/PowerShell 模板、WebUI 调度编辑,以及 Cursor Skill 与 MCP 工具供 Agent 管理定时任务。 Co-authored-by: Cursor <cursoragent@cursor.com>
92 lines
2.7 KiB
Python
92 lines
2.7 KiB
Python
"""非 Python 任务:由 Python 编排 disable / 锁 / 日志,subprocess 执行业务脚本。"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import os
|
||
import shutil
|
||
import subprocess
|
||
|
||
from runner import (
|
||
TaskContext,
|
||
acquire_cron_lock,
|
||
append_raw_to_log,
|
||
task_is_disabled,
|
||
task_logging,
|
||
)
|
||
from task_manifest import TaskManifest
|
||
|
||
|
||
def _resolve_interpreter(runtime: str) -> list[str] | None:
|
||
if runtime == "javascript":
|
||
node = shutil.which("node")
|
||
return [node] if node else None
|
||
if runtime == "bash":
|
||
bash = shutil.which("bash")
|
||
return [bash] if bash else None
|
||
if runtime == "powershell":
|
||
pwsh = shutil.which("pwsh")
|
||
if pwsh:
|
||
return [pwsh, "-File"]
|
||
ps = shutil.which("powershell")
|
||
return [ps, "-File"] if ps else None
|
||
return None
|
||
|
||
|
||
def build_command(manifest: TaskManifest, ctx: TaskContext) -> list[str]:
|
||
interpreter = _resolve_interpreter(manifest.runtime)
|
||
if interpreter is None:
|
||
raise FileNotFoundError(f"未找到 {manifest.runtime} 解释器,请确认已安装并在 PATH 中")
|
||
entry = ctx.task_dir / manifest.entry
|
||
if manifest.runtime == "powershell":
|
||
return [*interpreter, str(entry)]
|
||
if manifest.runtime == "bash":
|
||
return [*interpreter, str(entry)]
|
||
return [*interpreter, str(entry)]
|
||
|
||
|
||
def _task_env(ctx: TaskContext) -> dict[str, str]:
|
||
env = os.environ.copy()
|
||
env["CRON_TASK_ID"] = ctx.task_id
|
||
env["CRON_TASK_DIR"] = str(ctx.task_dir)
|
||
env["CRON_ROOT"] = str(ctx.cron_root)
|
||
return env
|
||
|
||
|
||
def run_external_task(ctx: TaskContext, manifest: TaskManifest) -> int:
|
||
if task_is_disabled(ctx):
|
||
return 0
|
||
|
||
with task_logging(ctx) as log:
|
||
with acquire_cron_lock(ctx.lock_file, log=log) as locked:
|
||
if not locked:
|
||
return 0
|
||
|
||
log("start")
|
||
log(f"runtime={manifest.runtime} entry={manifest.entry}")
|
||
|
||
try:
|
||
cmd = build_command(manifest, ctx)
|
||
except FileNotFoundError as exc:
|
||
log(str(exc))
|
||
log("end")
|
||
return 1
|
||
|
||
proc = subprocess.run(
|
||
cmd,
|
||
cwd=str(ctx.task_dir),
|
||
env=_task_env(ctx),
|
||
capture_output=True,
|
||
text=True,
|
||
encoding="utf-8",
|
||
errors="replace",
|
||
)
|
||
output = ((proc.stdout or "") + (proc.stderr or "")).strip()
|
||
if output:
|
||
append_raw_to_log(ctx.log_file, output)
|
||
|
||
if proc.returncode != 0:
|
||
log(f"exit={proc.returncode}")
|
||
|
||
log("end")
|
||
return proc.returncode
|