schrun 0.2.2 → 0.3.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 +38 -29
- package/dist/daemon.mjs +31 -0
- package/dist/index.mjs +62 -47
- package/dist/readJSON-BIHboQ76.mjs +18 -0
- package/package.json +5 -2
- package/CLAUDE.md +0 -106
- package/CONTRIBUTING.md +0 -40
- package/src/commands/default/index.ts +0 -55
- package/src/commands/run/index.ts +0 -31
- package/src/constant/app.ts +0 -6
- package/src/index.ts +0 -5
- package/src/readJSON.ts +0 -18
- package/src/run/background.ts +0 -26
- package/src/run/daemon.ts +0 -39
- package/src/run/startscheduler.ts +0 -29
- package/src/run/stopscheduler.ts +0 -29
- package/src/run/surface.ts +0 -27
- package/src/type.ts +0 -11
- package/src/version.ts +0 -15
- package/tsconfig.json +0 -30
- package/tsdown.config.ts +0 -8
package/README.md
CHANGED
|
@@ -1,61 +1,70 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="./assets/schrun-logo-small.jpg" alt="schrun logo">
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<h1 align="center">schrun</h1>
|
|
6
|
+
|
|
7
|
+
`schrun` is a small task runner. It reads task definitions from JSON files and can run them in the foreground, start them in the background, or schedule enabled tasks with cron expressions.
|
|
8
|
+
|
|
9
|
+
> [!TIP]
|
|
10
|
+
> `schrun` is a completely independent package and is not related to any other tools.
|
|
4
11
|
|
|
5
12
|
## Requirements
|
|
6
13
|
|
|
7
14
|
- [Bun](https://bun.sh/)
|
|
8
15
|
|
|
9
|
-
##
|
|
10
|
-
|
|
11
|
-
From the repository root, install the workspace dependencies:
|
|
16
|
+
## Install and run from this repository
|
|
12
17
|
|
|
13
|
-
```
|
|
18
|
+
```sh
|
|
14
19
|
bun install
|
|
15
20
|
```
|
|
16
21
|
|
|
17
|
-
|
|
22
|
+
Until a published package is available, run the CLI from the repository root with Bun:
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
mkdir -p ~/.config/schrun
|
|
24
|
+
```sh
|
|
25
|
+
bunx schrun --surface hello
|
|
26
|
+
bunx schrun --background hello
|
|
23
27
|
```
|
|
24
28
|
|
|
25
|
-
|
|
29
|
+
The task name is passed without the `.json` extension. schrun reads `~/.config/schrun/<name>.json`.
|
|
30
|
+
|
|
31
|
+
## Define a task
|
|
32
|
+
|
|
33
|
+
Create `~/.config/schrun/hello.json`:
|
|
26
34
|
|
|
27
35
|
```json
|
|
28
36
|
{
|
|
29
37
|
"id": 1,
|
|
30
38
|
"description": "Print a greeting",
|
|
31
|
-
"
|
|
39
|
+
"cron": "* * * * *",
|
|
40
|
+
"run": "echo hello",
|
|
41
|
+
"enabled": true
|
|
32
42
|
}
|
|
33
43
|
```
|
|
34
44
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
- `id`: a number
|
|
38
|
-
- `description`: a string
|
|
39
|
-
- `run`: a command and its arguments, separated by whitespace
|
|
45
|
+
Every field is required: `id` (number), `description` (string), `cron` (cron expression string), `run` (command string), and `enabled` (boolean). The scheduler considers only enabled tasks.
|
|
40
46
|
|
|
41
|
-
## Run
|
|
47
|
+
## Run tasks
|
|
42
48
|
|
|
43
|
-
|
|
49
|
+
Foreground mode inherits standard input/output and waits for the command to finish:
|
|
44
50
|
|
|
45
|
-
```
|
|
46
|
-
|
|
51
|
+
```sh
|
|
52
|
+
bunx schrun --surface hello
|
|
47
53
|
```
|
|
48
54
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
To start it detached in the background:
|
|
55
|
+
Detached background mode ignores standard input/output and returns after starting the process:
|
|
52
56
|
|
|
53
|
-
```
|
|
54
|
-
|
|
57
|
+
```sh
|
|
58
|
+
bunx schrun --background hello
|
|
55
59
|
```
|
|
56
60
|
|
|
57
|
-
|
|
61
|
+
Start or stop the scheduler:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
bunx schrun run --start
|
|
65
|
+
bunx schrun run --stop
|
|
66
|
+
```
|
|
58
67
|
|
|
59
68
|
## Limitations
|
|
60
69
|
|
|
61
|
-
|
|
70
|
+
Commands are split on whitespace and launched directly, without a shell. Pipes, redirects, variable expansion, and quoted arguments are not interpreted. Use a script file when a task needs shell features.
|
package/dist/daemon.mjs
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { r as CONFIG_DIR, t as readJSON } from "./readJSON-BIHboQ76.mjs";
|
|
2
|
+
import { readdir } from "node:fs/promises";
|
|
3
|
+
import { consola } from "consola";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import cron from "node-cron";
|
|
6
|
+
// src/run/daemon.ts
|
|
7
|
+
const startschedule = async () => {
|
|
8
|
+
const readConfig = await readdir(CONFIG_DIR);
|
|
9
|
+
for (const filename of readConfig) {
|
|
10
|
+
if (!filename.endsWith(".json")) continue;
|
|
11
|
+
if (filename.match("pid.json")) continue;
|
|
12
|
+
const [filenamesplit] = filename.split(".");
|
|
13
|
+
const read = await readJSON(filenamesplit);
|
|
14
|
+
if (!read.enabled) continue;
|
|
15
|
+
const [command, ...args] = read.run.trim().split(/\s+/);
|
|
16
|
+
if (!command) continue;
|
|
17
|
+
cron.schedule(read.cron, () => {
|
|
18
|
+
spawn(command, args, {
|
|
19
|
+
detached: true,
|
|
20
|
+
stdio: "inherit"
|
|
21
|
+
}).on("error", (error) => {
|
|
22
|
+
consola.error(`コマンドの起動に失敗: ${command}`, error);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
startschedule().catch((error) => {
|
|
28
|
+
consola.error("failed to up scheduler", error);
|
|
29
|
+
process.exitCode = 1;
|
|
30
|
+
});
|
|
31
|
+
export { startschedule };
|
package/dist/index.mjs
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { i as PID_FILE, n as CLI_COMMAND_NAME, t as readJSON } from "./readJSON-
|
|
2
|
+
import { i as PID_FILE, n as CLI_COMMAND_NAME, r as CONFIG_DIR, t as readJSON } from "./readJSON-BIHboQ76.mjs";
|
|
3
3
|
import { cli, define } from "gunshi";
|
|
4
|
-
import {
|
|
4
|
+
import { readFile, readdir, unlink, writeFile } from "node:fs/promises";
|
|
5
5
|
import { consola } from "consola";
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
|
-
|
|
8
|
+
import Table from "cli-table3";
|
|
9
|
+
// src/version.ts
|
|
9
10
|
const version = async () => {
|
|
10
11
|
const packageJsonText = await readFile(new URL("../package.json", import.meta.url), "utf-8");
|
|
11
12
|
return JSON.parse(packageJsonText).version;
|
|
12
13
|
};
|
|
13
|
-
|
|
14
|
-
//#region src/run/background.ts
|
|
14
|
+
// src/run/background.ts
|
|
15
15
|
const runbackground = async (FileName) => {
|
|
16
16
|
const [command, ...args] = (await readJSON(FileName)).run.split(/\s+/);
|
|
17
17
|
if (!command) {
|
|
@@ -27,8 +27,7 @@ const runbackground = async (FileName) => {
|
|
|
27
27
|
});
|
|
28
28
|
spawncli.unref();
|
|
29
29
|
};
|
|
30
|
-
|
|
31
|
-
//#region src/run/surface.ts
|
|
30
|
+
// src/run/surface.ts
|
|
32
31
|
const runsurface = async (FileName) => {
|
|
33
32
|
const [command, ...args] = (await readJSON(FileName)).run.split(/\s+/);
|
|
34
33
|
if (!command) {
|
|
@@ -43,55 +42,43 @@ const runsurface = async (FileName) => {
|
|
|
43
42
|
consola.success(`done code: ${code}`);
|
|
44
43
|
});
|
|
45
44
|
};
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const startschedule = async () => {
|
|
45
|
+
// src/run/startScheduler.ts
|
|
46
|
+
const startSchedule = async () => {
|
|
49
47
|
const daemonPath = fileURLToPath(new URL("./daemon.mjs", import.meta.url));
|
|
50
|
-
const logFile = await open("/tmp/schrun-daemon.log", "a");
|
|
51
48
|
const child = spawn(process.execPath, [daemonPath], {
|
|
52
49
|
detached: true,
|
|
53
|
-
stdio:
|
|
54
|
-
"ignore",
|
|
55
|
-
logFile.fd,
|
|
56
|
-
logFile.fd
|
|
57
|
-
]
|
|
50
|
+
stdio: "ignore"
|
|
58
51
|
});
|
|
59
|
-
if (child.pid === void 0)
|
|
60
|
-
await logFile.close();
|
|
61
|
-
throw new Error("Failed to start daemon");
|
|
62
|
-
}
|
|
52
|
+
if (child.pid === void 0) throw new Error("Failed to start daemon");
|
|
63
53
|
child.on("error", (error) => {
|
|
64
|
-
|
|
54
|
+
throw new Error("Failed to start daemon:", error);
|
|
65
55
|
});
|
|
66
|
-
await writeFile(PID_FILE, String(child.pid));
|
|
56
|
+
await writeFile(PID_FILE, String(child.pid), "utf-8");
|
|
67
57
|
child.unref();
|
|
68
|
-
await logFile.close();
|
|
69
58
|
};
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const stopschedule = async () => {
|
|
59
|
+
// src/run/stopScheduler.ts
|
|
60
|
+
const stopSchedule = async () => {
|
|
73
61
|
let pid;
|
|
74
62
|
try {
|
|
75
63
|
pid = Number(await readFile(PID_FILE, "utf-8"));
|
|
76
|
-
} catch {
|
|
77
|
-
consola.warn("
|
|
64
|
+
} catch (error) {
|
|
65
|
+
consola.warn("No look schedule pid file");
|
|
78
66
|
return;
|
|
79
67
|
}
|
|
80
68
|
if (!Number.isSafeInteger(pid) || pid <= 0) {
|
|
81
|
-
consola.error("
|
|
69
|
+
consola.error("Unsafe pid files");
|
|
82
70
|
return;
|
|
83
71
|
}
|
|
84
72
|
try {
|
|
85
73
|
process.kill(pid, "SIGINT");
|
|
86
|
-
consola.success("
|
|
74
|
+
consola.success("Success Send stop scheduler");
|
|
87
75
|
} catch (error) {
|
|
88
|
-
consola.error("
|
|
76
|
+
consola.error("Failed Send stop scheduler", error);
|
|
89
77
|
return;
|
|
90
78
|
}
|
|
91
79
|
await unlink(PID_FILE).catch(() => {});
|
|
92
80
|
};
|
|
93
|
-
|
|
94
|
-
//#region src/commands/run/index.ts
|
|
81
|
+
// src/commands/run/index.ts
|
|
95
82
|
const runCommand = {
|
|
96
83
|
name: "run",
|
|
97
84
|
args: {
|
|
@@ -108,20 +95,47 @@ const runCommand = {
|
|
|
108
95
|
},
|
|
109
96
|
run: (ctx) => {
|
|
110
97
|
const { start, stop } = ctx.values;
|
|
111
|
-
if (start == true)
|
|
112
|
-
if (stop == true)
|
|
98
|
+
if (start == true) startSchedule();
|
|
99
|
+
if (stop == true) stopSchedule();
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
// src/table.ts
|
|
103
|
+
const list = async () => {
|
|
104
|
+
const read = await readdir(CONFIG_DIR);
|
|
105
|
+
if (read.length === 0) consola.error("No schedule found");
|
|
106
|
+
const table = new Table({ head: [
|
|
107
|
+
"description",
|
|
108
|
+
"cron",
|
|
109
|
+
"run",
|
|
110
|
+
"enabled"
|
|
111
|
+
] });
|
|
112
|
+
for (const readdir of read) {
|
|
113
|
+
if (readdir.match("pid.json")) continue;
|
|
114
|
+
const [splits] = readdir.split(".");
|
|
115
|
+
const readfunc = await readJSON(splits);
|
|
116
|
+
table.push([
|
|
117
|
+
readfunc.description,
|
|
118
|
+
readfunc.cron,
|
|
119
|
+
readfunc.run,
|
|
120
|
+
readfunc.enabled
|
|
121
|
+
]);
|
|
113
122
|
}
|
|
123
|
+
console.log(table.toString());
|
|
114
124
|
};
|
|
115
|
-
|
|
116
|
-
|
|
125
|
+
// src/commands/list/index.ts
|
|
126
|
+
const listCommand = {
|
|
127
|
+
name: "list",
|
|
128
|
+
run: list
|
|
129
|
+
};
|
|
130
|
+
// src/commands/default/index.ts
|
|
117
131
|
const mainCommand = async () => {
|
|
118
132
|
const RunmainCommand = define({
|
|
119
133
|
name: CLI_COMMAND_NAME,
|
|
120
134
|
description: "agent and other task runner",
|
|
121
135
|
version: await version(),
|
|
122
136
|
args: {
|
|
123
|
-
|
|
124
|
-
type: "
|
|
137
|
+
surface: {
|
|
138
|
+
type: "string",
|
|
125
139
|
required: false,
|
|
126
140
|
description: "Input value"
|
|
127
141
|
},
|
|
@@ -132,15 +146,15 @@ const mainCommand = async () => {
|
|
|
132
146
|
}
|
|
133
147
|
},
|
|
134
148
|
run: async (ctx) => {
|
|
135
|
-
const {
|
|
149
|
+
const { surface, background } = ctx.values;
|
|
136
150
|
if (background !== void 0) {
|
|
137
151
|
console.log("Background mode:", background);
|
|
138
152
|
await runbackground(background);
|
|
139
153
|
return;
|
|
140
154
|
}
|
|
141
|
-
if (
|
|
142
|
-
console.log("Normal mode:",
|
|
143
|
-
await runsurface(
|
|
155
|
+
if (surface !== void 0) {
|
|
156
|
+
console.log("Normal mode:", surface);
|
|
157
|
+
await runsurface(surface);
|
|
144
158
|
return;
|
|
145
159
|
}
|
|
146
160
|
consola.error("No Input String");
|
|
@@ -148,11 +162,12 @@ const mainCommand = async () => {
|
|
|
148
162
|
});
|
|
149
163
|
await cli(process.argv.slice(2), RunmainCommand, {
|
|
150
164
|
name: CLI_COMMAND_NAME,
|
|
151
|
-
subCommands: {
|
|
165
|
+
subCommands: {
|
|
166
|
+
run: runCommand,
|
|
167
|
+
list: listCommand
|
|
168
|
+
}
|
|
152
169
|
});
|
|
153
170
|
};
|
|
154
|
-
|
|
155
|
-
//#region src/index.ts
|
|
171
|
+
// src/index.ts
|
|
156
172
|
await mainCommand();
|
|
157
|
-
//#endregion
|
|
158
173
|
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import "zod";
|
|
5
|
+
import typia from "typia";
|
|
6
|
+
// src/constant/app.ts
|
|
7
|
+
const CLI_COMMAND_NAME = "schrun";
|
|
8
|
+
const CONFIG_DIR = join(`${homedir()}`, ".config", "schrun");
|
|
9
|
+
const PID_FILE = join(`${homedir()}`, ".config", "schrun", "pid.json");
|
|
10
|
+
// src/readJSON.ts
|
|
11
|
+
const readJSON = async (FileName) => {
|
|
12
|
+
const file = join(CONFIG_DIR, `${FileName}.json`);
|
|
13
|
+
const reads = await readFile(file, "utf-8");
|
|
14
|
+
const readparse = JSON.parse(reads);
|
|
15
|
+
if (typia.is(readparse)) return readparse;
|
|
16
|
+
else throw "The configuration format is invalid";
|
|
17
|
+
};
|
|
18
|
+
export { PID_FILE as i, CLI_COMMAND_NAME as n, CONFIG_DIR as r, readJSON as t };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "schrun",
|
|
3
3
|
"module": "src/index.ts",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
"name": "nazozokc",
|
|
13
13
|
"url": "https://github.com/nazozokc"
|
|
14
14
|
},
|
|
15
|
-
"
|
|
15
|
+
"files": [
|
|
16
16
|
"dist",
|
|
17
17
|
"package.json",
|
|
18
|
+
"bun.lock",
|
|
18
19
|
"README.md"
|
|
19
20
|
],
|
|
20
21
|
"devDependencies": {
|
|
@@ -25,10 +26,12 @@
|
|
|
25
26
|
"typescript": "^7"
|
|
26
27
|
},
|
|
27
28
|
"dependencies": {
|
|
29
|
+
"cli-table3": "^0.6.5",
|
|
28
30
|
"consola": "^3.4.2",
|
|
29
31
|
"execa": "^10.1.0",
|
|
30
32
|
"gunshi": "^0.37.3",
|
|
31
33
|
"node-cron": "^4.6.0",
|
|
34
|
+
"typia": "^15.2.1",
|
|
32
35
|
"zod": "^4.6.7"
|
|
33
36
|
}
|
|
34
37
|
}
|
package/CLAUDE.md
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
Default to using Bun instead of Node.js.
|
|
3
|
-
|
|
4
|
-
- Use `bun <file>` instead of `node <file>` or `ts-node <file>`
|
|
5
|
-
- Use `bun test` instead of `jest` or `vitest`
|
|
6
|
-
- Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild`
|
|
7
|
-
- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install`
|
|
8
|
-
- Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>`
|
|
9
|
-
- Use `bunx <package> <command>` instead of `npx <package> <command>`
|
|
10
|
-
- Bun automatically loads .env, so don't use dotenv.
|
|
11
|
-
|
|
12
|
-
## APIs
|
|
13
|
-
|
|
14
|
-
- `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`.
|
|
15
|
-
- `bun:sqlite` for SQLite. Don't use `better-sqlite3`.
|
|
16
|
-
- `Bun.redis` for Redis. Don't use `ioredis`.
|
|
17
|
-
- `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`.
|
|
18
|
-
- `WebSocket` is built-in. Don't use `ws`.
|
|
19
|
-
- Prefer `Bun.file` over `node:fs`'s readFile/writeFile
|
|
20
|
-
- Bun.$`ls` instead of execa.
|
|
21
|
-
|
|
22
|
-
## Testing
|
|
23
|
-
|
|
24
|
-
Use `bun test` to run tests.
|
|
25
|
-
|
|
26
|
-
```ts#index.test.ts
|
|
27
|
-
import { test, expect } from "bun:test";
|
|
28
|
-
|
|
29
|
-
test("hello world", () => {
|
|
30
|
-
expect(1).toBe(1);
|
|
31
|
-
});
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
## Frontend
|
|
35
|
-
|
|
36
|
-
Use HTML imports with `Bun.serve()`. Don't use `vite`. HTML imports fully support React, CSS, Tailwind.
|
|
37
|
-
|
|
38
|
-
Server:
|
|
39
|
-
|
|
40
|
-
```ts#index.ts
|
|
41
|
-
import index from "./index.html"
|
|
42
|
-
|
|
43
|
-
Bun.serve({
|
|
44
|
-
routes: {
|
|
45
|
-
"/": index,
|
|
46
|
-
"/api/users/:id": {
|
|
47
|
-
GET: (req) => {
|
|
48
|
-
return new Response(JSON.stringify({ id: req.params.id }));
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
// optional websocket support
|
|
53
|
-
websocket: {
|
|
54
|
-
open: (ws) => {
|
|
55
|
-
ws.send("Hello, world!");
|
|
56
|
-
},
|
|
57
|
-
message: (ws, message) => {
|
|
58
|
-
ws.send(message);
|
|
59
|
-
},
|
|
60
|
-
close: (ws) => {
|
|
61
|
-
// handle close
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
development: {
|
|
65
|
-
hmr: true,
|
|
66
|
-
console: true,
|
|
67
|
-
}
|
|
68
|
-
})
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. `<link>` tags can point to stylesheets and Bun's CSS bundler will bundle.
|
|
72
|
-
|
|
73
|
-
```html#index.html
|
|
74
|
-
<html>
|
|
75
|
-
<body>
|
|
76
|
-
<h1>Hello, world!</h1>
|
|
77
|
-
<script type="module" src="./frontend.tsx"></script>
|
|
78
|
-
</body>
|
|
79
|
-
</html>
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
With the following `frontend.tsx`:
|
|
83
|
-
|
|
84
|
-
```tsx#frontend.tsx
|
|
85
|
-
import React from "react";
|
|
86
|
-
import { createRoot } from "react-dom/client";
|
|
87
|
-
|
|
88
|
-
// import .css files directly and it works
|
|
89
|
-
import './index.css';
|
|
90
|
-
|
|
91
|
-
const root = createRoot(document.body);
|
|
92
|
-
|
|
93
|
-
export default function Frontend() {
|
|
94
|
-
return <h1>Hello, world!</h1>;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
root.render(<Frontend />);
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
Then, run index.ts
|
|
101
|
-
|
|
102
|
-
```sh
|
|
103
|
-
bun --hot ./index.ts
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
For more information, read the Bun API docs in `node_modules/bun-types/docs/**.mdx`.
|
package/CONTRIBUTING.md
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# Contributing to schrun
|
|
2
|
-
|
|
3
|
-
Thanks for your interest in contributing to `schrun`! Contributions such as bug reports, documentation improvements, and focused code changes are welcome.
|
|
4
|
-
|
|
5
|
-
## Getting started
|
|
6
|
-
|
|
7
|
-
- Install [Bun](https://bun.sh/).
|
|
8
|
-
- Clone the repository and work from its root directory.
|
|
9
|
-
- Install workspace dependencies:
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
bun install
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## Making changes
|
|
16
|
-
|
|
17
|
-
1. Create a branch for your change.
|
|
18
|
-
2. Keep changes focused and follow the existing TypeScript style and project conventions.
|
|
19
|
-
3. Update the documentation when behavior or usage changes.
|
|
20
|
-
4. Run the relevant checks before opening a pull request.
|
|
21
|
-
|
|
22
|
-
The CLI entry point is `apps/schrun/src/index.ts`. You can run it from the repository root with:
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
bun run apps/schrun/src/index.ts --help
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
## Testing
|
|
29
|
-
|
|
30
|
-
There is currently no test script configured for this app. For changes that affect CLI behavior, manually verify the relevant command using a temporary task file under `~/.config/schrun/`. Avoid committing personal configuration or credentials.
|
|
31
|
-
|
|
32
|
-
## Pull requests
|
|
33
|
-
|
|
34
|
-
When opening a pull request, please include:
|
|
35
|
-
|
|
36
|
-
- A concise description of the problem and the proposed change.
|
|
37
|
-
- Any relevant usage or behavior changes.
|
|
38
|
-
- The checks or manual verification you performed.
|
|
39
|
-
|
|
40
|
-
Keep pull requests focused and be open to feedback during review.
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { cli, define } from "gunshi";
|
|
2
|
-
import { version } from "../../version.ts";
|
|
3
|
-
import { runbackground } from "../../run/background.ts";
|
|
4
|
-
import { runsurface } from "../../run/surface.ts";
|
|
5
|
-
import { consola } from "consola";
|
|
6
|
-
import { CLI_COMMAND_NAME } from "../../constant/app.ts";
|
|
7
|
-
import { runCommand } from "../run/index.ts";
|
|
8
|
-
|
|
9
|
-
export const mainCommand = async (): Promise<void> => {
|
|
10
|
-
const RunmainCommand = define({
|
|
11
|
-
name: CLI_COMMAND_NAME,
|
|
12
|
-
description: "agent and other task runner",
|
|
13
|
-
version: await version(),
|
|
14
|
-
|
|
15
|
-
args: {
|
|
16
|
-
surface: {
|
|
17
|
-
type: "string",
|
|
18
|
-
required: false,
|
|
19
|
-
description: "Input value",
|
|
20
|
-
},
|
|
21
|
-
|
|
22
|
-
background: {
|
|
23
|
-
type: "string",
|
|
24
|
-
required: false,
|
|
25
|
-
description: "Run a task in the background",
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
|
|
29
|
-
run: async (ctx) => {
|
|
30
|
-
const { surface, background } = ctx.values;
|
|
31
|
-
|
|
32
|
-
if (background !== undefined) {
|
|
33
|
-
console.log("Background mode:", background);
|
|
34
|
-
await runbackground(background);
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (surface !== undefined) {
|
|
39
|
-
console.log("Normal mode:", surface);
|
|
40
|
-
await runsurface(surface);
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
consola.error("No Input String");
|
|
45
|
-
return;
|
|
46
|
-
},
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
await cli(process.argv.slice(2), RunmainCommand, {
|
|
50
|
-
name: CLI_COMMAND_NAME,
|
|
51
|
-
subCommands: {
|
|
52
|
-
run: runCommand,
|
|
53
|
-
},
|
|
54
|
-
});
|
|
55
|
-
};
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { Command } from "gunshi";
|
|
2
|
-
import { startschedule } from "../../run/startscheduler.ts";
|
|
3
|
-
import { stopschedule } from "../../run/stopscheduler.ts";
|
|
4
|
-
|
|
5
|
-
export const runCommand: Command = {
|
|
6
|
-
name: "run",
|
|
7
|
-
args: {
|
|
8
|
-
start: {
|
|
9
|
-
type: "boolean",
|
|
10
|
-
required: false,
|
|
11
|
-
description: "Start scheduler",
|
|
12
|
-
},
|
|
13
|
-
stop: {
|
|
14
|
-
type: "boolean",
|
|
15
|
-
required: false,
|
|
16
|
-
description: "Stop scheduler",
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
|
|
20
|
-
run: (ctx) => {
|
|
21
|
-
const { start, stop } = ctx.values;
|
|
22
|
-
|
|
23
|
-
if (start == true) {
|
|
24
|
-
startschedule();
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (stop == true) {
|
|
28
|
-
stopschedule();
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
};
|
package/src/constant/app.ts
DELETED
package/src/index.ts
DELETED
package/src/readJSON.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { CONFIG_DIR } from "./constant/app";
|
|
4
|
-
import { fileSchema, type FileType } from "./type";
|
|
5
|
-
import { consola } from "consola";
|
|
6
|
-
|
|
7
|
-
export const readJSON = async (FileName: string): Promise<FileType> => {
|
|
8
|
-
const file = join(CONFIG_DIR, `${FileName}.json`);
|
|
9
|
-
const reads = await readFile(file, "utf-8");
|
|
10
|
-
|
|
11
|
-
const safeParseSchema = fileSchema.safeParse(JSON.parse(reads));
|
|
12
|
-
|
|
13
|
-
if (!safeParseSchema.success) {
|
|
14
|
-
throw consola.error(safeParseSchema.error);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return safeParseSchema.data as FileType;
|
|
18
|
-
};
|
package/src/run/background.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { consola } from "consola";
|
|
2
|
-
import { readJSON } from "../readJSON.ts";
|
|
3
|
-
import { spawn } from "node:child_process";
|
|
4
|
-
|
|
5
|
-
export const runbackground = async (FileName: string): Promise<void> => {
|
|
6
|
-
const reads = await readJSON(FileName);
|
|
7
|
-
const commandsplit = reads.run.split(/\s+/);
|
|
8
|
-
|
|
9
|
-
const [command, ...args] = commandsplit;
|
|
10
|
-
|
|
11
|
-
if (!command) {
|
|
12
|
-
consola.error("do not run commands");
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const spawncli = spawn(command, args, {
|
|
17
|
-
stdio: "ignore",
|
|
18
|
-
detached: true,
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
spawncli.on("error", (error) => {
|
|
22
|
-
consola.error("failed to start processing", error);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
spawncli.unref();
|
|
26
|
-
};
|
package/src/run/daemon.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import cron from "node-cron";
|
|
2
|
-
import { spawn } from "node:child_process";
|
|
3
|
-
import { readJSON } from "../readJSON.ts";
|
|
4
|
-
import { readdir } from "node:fs/promises";
|
|
5
|
-
import { CONFIG_DIR } from "../constant/app.ts";
|
|
6
|
-
import { consola } from "consola";
|
|
7
|
-
|
|
8
|
-
export const startschedule = async (): Promise<void> => {
|
|
9
|
-
const readConfig = await readdir(CONFIG_DIR);
|
|
10
|
-
|
|
11
|
-
for (const filename of readConfig) {
|
|
12
|
-
if (!filename.endsWith(".json")) continue;
|
|
13
|
-
if (!filename.match("foo.json")) continue;
|
|
14
|
-
const [filenamesplit] = filename.split(".");
|
|
15
|
-
const read = await readJSON(filenamesplit!);
|
|
16
|
-
|
|
17
|
-
if (!read.enabled) continue;
|
|
18
|
-
|
|
19
|
-
const [command, ...args] = read.run.trim().split(/\s+/);
|
|
20
|
-
|
|
21
|
-
if (!command) continue;
|
|
22
|
-
|
|
23
|
-
cron.schedule(read.cron, () => {
|
|
24
|
-
const cronspawn = spawn(command!, args!, {
|
|
25
|
-
detached: true,
|
|
26
|
-
stdio: "inherit",
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
cronspawn.on("error", (error) => {
|
|
30
|
-
consola.error(`コマンドの起動に失敗: ${command}`, error);
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
startschedule().catch((error) => {
|
|
37
|
-
consola.error("failed to up scheduler", error);
|
|
38
|
-
process.exitCode = 1;
|
|
39
|
-
});
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import { open, writeFile } from "node:fs/promises";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { PID_FILE } from "../constant/app.ts";
|
|
5
|
-
|
|
6
|
-
export const startschedule = async (): Promise<void> => {
|
|
7
|
-
const daemonPath = fileURLToPath(new URL("./daemon.mjs", import.meta.url));
|
|
8
|
-
|
|
9
|
-
const logFile = await open("/tmp/schrun-daemon.log", "a");
|
|
10
|
-
|
|
11
|
-
const child = spawn(process.execPath, [daemonPath], {
|
|
12
|
-
detached: true,
|
|
13
|
-
stdio: ["ignore", logFile.fd, logFile.fd],
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
if (child.pid === undefined) {
|
|
17
|
-
await logFile.close();
|
|
18
|
-
throw new Error("Failed to start daemon");
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
child.on("error", (error) => {
|
|
22
|
-
console.error("Failed to start daemon:", error);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
await writeFile(PID_FILE, String(child.pid));
|
|
26
|
-
child.unref();
|
|
27
|
-
|
|
28
|
-
await logFile.close();
|
|
29
|
-
};
|
package/src/run/stopscheduler.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { readFile, unlink } from "node:fs/promises";
|
|
2
|
-
import { PID_FILE } from "../constant/app.ts";
|
|
3
|
-
import { consola } from "consola";
|
|
4
|
-
|
|
5
|
-
export const stopschedule = async (): Promise<void> => {
|
|
6
|
-
let pid: number;
|
|
7
|
-
|
|
8
|
-
try {
|
|
9
|
-
pid = Number(await readFile(PID_FILE, "utf-8"));
|
|
10
|
-
} catch {
|
|
11
|
-
consola.warn("no look schedule pid file");
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
if (!Number.isSafeInteger(pid) || pid <= 0) {
|
|
16
|
-
consola.error("warning pid files");
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
process.kill(pid, "SIGINT");
|
|
22
|
-
consola.success("send stop to scheduler");
|
|
23
|
-
} catch (error) {
|
|
24
|
-
consola.error("failed stop to scheduler", error);
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
await unlink(PID_FILE).catch(() => {});
|
|
29
|
-
};
|
package/src/run/surface.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { consola } from "consola";
|
|
2
|
-
import { readJSON } from "../readJSON.ts";
|
|
3
|
-
import { spawn } from "node:child_process";
|
|
4
|
-
|
|
5
|
-
export const runsurface = async (FileName: string): Promise<void> => {
|
|
6
|
-
const reads = await readJSON(FileName);
|
|
7
|
-
const commandsplit = reads.run.split(/\s+/);
|
|
8
|
-
|
|
9
|
-
const [command, ...args] = commandsplit;
|
|
10
|
-
|
|
11
|
-
if (!command) {
|
|
12
|
-
consola.error("do not run commands");
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const spawncli = spawn(command, args, {
|
|
17
|
-
stdio: "inherit",
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
spawncli.on("error", (error) => {
|
|
21
|
-
consola.error("failed run command", error);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
spawncli.on("close", (code) => {
|
|
25
|
-
consola.success(`done code: ${code}`);
|
|
26
|
-
});
|
|
27
|
-
};
|
package/src/type.ts
DELETED
package/src/version.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
|
|
3
|
-
type packagejson = {
|
|
4
|
-
version: string;
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
export const version = async (): Promise<string> => {
|
|
8
|
-
const packageJsonText = await readFile(
|
|
9
|
-
new URL("../package.json", import.meta.url),
|
|
10
|
-
"utf-8",
|
|
11
|
-
);
|
|
12
|
-
const packageJson: packagejson = JSON.parse(packageJsonText);
|
|
13
|
-
|
|
14
|
-
return packageJson.version;
|
|
15
|
-
};
|
package/tsconfig.json
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
// Environment setup & latest features
|
|
4
|
-
"lib": ["ESNext"],
|
|
5
|
-
"target": "ESNext",
|
|
6
|
-
"module": "Preserve",
|
|
7
|
-
"moduleDetection": "force",
|
|
8
|
-
"jsx": "react-jsx",
|
|
9
|
-
"allowJs": true,
|
|
10
|
-
"types": ["bun"],
|
|
11
|
-
|
|
12
|
-
// Bundler mode
|
|
13
|
-
"moduleResolution": "bundler",
|
|
14
|
-
"allowImportingTsExtensions": true,
|
|
15
|
-
"verbatimModuleSyntax": true,
|
|
16
|
-
"noEmit": true,
|
|
17
|
-
|
|
18
|
-
// Best practices
|
|
19
|
-
"strict": true,
|
|
20
|
-
"skipLibCheck": true,
|
|
21
|
-
"noFallthroughCasesInSwitch": true,
|
|
22
|
-
"noUncheckedIndexedAccess": true,
|
|
23
|
-
"noImplicitOverride": true,
|
|
24
|
-
|
|
25
|
-
// Some stricter flags (disabled by default)
|
|
26
|
-
"noUnusedLocals": false,
|
|
27
|
-
"noUnusedParameters": false,
|
|
28
|
-
"noPropertyAccessFromIndexSignature": false
|
|
29
|
-
}
|
|
30
|
-
}
|