schrun 0.1.0 → 0.2.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/dist/index.mjs +108 -52
- package/package.json +2 -1
- package/src/commands/default/index.ts +55 -0
- package/src/commands/run/index.ts +31 -0
- package/src/constant/app.ts +1 -0
- package/src/index.ts +2 -46
- package/src/{read.ts → readJSON.ts} +1 -1
- package/src/run/background.ts +2 -2
- package/src/run/daemon.ts +39 -0
- package/src/run/startscheduler.ts +29 -0
- package/src/run/stopscheduler.ts +29 -0
- package/src/run/surface.ts +2 -2
- package/src/type.ts +2 -0
- package/tsdown.config.ts +8 -0
package/dist/index.mjs
CHANGED
|
@@ -1,40 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { i as PID_FILE, n as CLI_COMMAND_NAME, t as readJSON } from "./readJSON-p8OldMa9.mjs";
|
|
2
3
|
import { cli, define } from "gunshi";
|
|
3
|
-
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { open, readFile, unlink, writeFile } from "node:fs/promises";
|
|
4
5
|
import { consola } from "consola";
|
|
5
|
-
import { join } from "node:path";
|
|
6
|
-
import { homedir } from "node:os";
|
|
7
|
-
import { z } from "zod";
|
|
8
6
|
import { spawn } from "node:child_process";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
9
8
|
//#region src/version.ts
|
|
10
9
|
const version = async () => {
|
|
11
10
|
const packageJsonText = await readFile(new URL("../package.json", import.meta.url), "utf-8");
|
|
12
11
|
return JSON.parse(packageJsonText).version;
|
|
13
12
|
};
|
|
14
13
|
//#endregion
|
|
15
|
-
//#region src/constant/app.ts
|
|
16
|
-
const CLI_COMMAND_NAME = "schrun";
|
|
17
|
-
const CONFIG_DIR = join(`${homedir()}`, ".config", "schrun");
|
|
18
|
-
//#endregion
|
|
19
|
-
//#region src/type.ts
|
|
20
|
-
const fileSchema = z.object({
|
|
21
|
-
id: z.number(),
|
|
22
|
-
description: z.string(),
|
|
23
|
-
run: z.string()
|
|
24
|
-
});
|
|
25
|
-
//#endregion
|
|
26
|
-
//#region src/read.ts
|
|
27
|
-
const readFiles = async (FileName) => {
|
|
28
|
-
const file = join(CONFIG_DIR, `${FileName}.json`);
|
|
29
|
-
const reads = await readFile(file, "utf-8");
|
|
30
|
-
const safeParseSchema = fileSchema.safeParse(JSON.parse(reads));
|
|
31
|
-
if (!safeParseSchema.success) throw consola.error(safeParseSchema.error);
|
|
32
|
-
return safeParseSchema.data;
|
|
33
|
-
};
|
|
34
|
-
//#endregion
|
|
35
14
|
//#region src/run/background.ts
|
|
36
15
|
const runbackground = async (FileName) => {
|
|
37
|
-
const [command, ...args] = (await
|
|
16
|
+
const [command, ...args] = (await readJSON(FileName)).run.split(/\s+/);
|
|
38
17
|
if (!command) {
|
|
39
18
|
consola.error("do not run commands");
|
|
40
19
|
return;
|
|
@@ -51,7 +30,7 @@ const runbackground = async (FileName) => {
|
|
|
51
30
|
//#endregion
|
|
52
31
|
//#region src/run/surface.ts
|
|
53
32
|
const runsurface = async (FileName) => {
|
|
54
|
-
const [command, ...args] = (await
|
|
33
|
+
const [command, ...args] = (await readJSON(FileName)).run.split(/\s+/);
|
|
55
34
|
if (!command) {
|
|
56
35
|
consola.error("do not run commands");
|
|
57
36
|
return;
|
|
@@ -65,38 +44,115 @@ const runsurface = async (FileName) => {
|
|
|
65
44
|
});
|
|
66
45
|
};
|
|
67
46
|
//#endregion
|
|
68
|
-
//#region src/
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
47
|
+
//#region src/run/startscheduler.ts
|
|
48
|
+
const startschedule = async () => {
|
|
49
|
+
const daemonPath = fileURLToPath(new URL("./daemon.mjs", import.meta.url));
|
|
50
|
+
const logFile = await open("/tmp/schrun-daemon.log", "a");
|
|
51
|
+
const child = spawn(process.execPath, [daemonPath], {
|
|
52
|
+
detached: true,
|
|
53
|
+
stdio: [
|
|
54
|
+
"ignore",
|
|
55
|
+
logFile.fd,
|
|
56
|
+
logFile.fd
|
|
57
|
+
]
|
|
58
|
+
});
|
|
59
|
+
if (child.pid === void 0) {
|
|
60
|
+
await logFile.close();
|
|
61
|
+
throw new Error("Failed to start daemon");
|
|
62
|
+
}
|
|
63
|
+
child.on("error", (error) => {
|
|
64
|
+
console.error("Failed to start daemon:", error);
|
|
65
|
+
});
|
|
66
|
+
await writeFile(PID_FILE, String(child.pid));
|
|
67
|
+
child.unref();
|
|
68
|
+
await logFile.close();
|
|
69
|
+
};
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/run/stopscheduler.ts
|
|
72
|
+
const stopschedule = async () => {
|
|
73
|
+
let pid;
|
|
74
|
+
try {
|
|
75
|
+
pid = Number(await readFile(PID_FILE, "utf-8"));
|
|
76
|
+
} catch {
|
|
77
|
+
consola.warn("no look schedule pid file");
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) {
|
|
81
|
+
consola.error("warning pid files");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
process.kill(pid, "SIGINT");
|
|
86
|
+
consola.success("send stop to scheduler");
|
|
87
|
+
} catch (error) {
|
|
88
|
+
consola.error("failed stop to scheduler", error);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
await unlink(PID_FILE).catch(() => {});
|
|
92
|
+
};
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/commands/run/index.ts
|
|
95
|
+
const runCommand = {
|
|
96
|
+
name: "run",
|
|
73
97
|
args: {
|
|
74
|
-
|
|
75
|
-
type: "
|
|
98
|
+
start: {
|
|
99
|
+
type: "boolean",
|
|
76
100
|
required: false,
|
|
77
|
-
description: "
|
|
101
|
+
description: "Start scheduler"
|
|
78
102
|
},
|
|
79
|
-
|
|
80
|
-
type: "
|
|
103
|
+
stop: {
|
|
104
|
+
type: "boolean",
|
|
81
105
|
required: false,
|
|
82
|
-
description: "
|
|
106
|
+
description: "Stop scheduler"
|
|
83
107
|
}
|
|
84
108
|
},
|
|
85
|
-
run:
|
|
86
|
-
const {
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
-
await runbackground(background);
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
if (defaultInput !== void 0) {
|
|
93
|
-
console.log("Normal mode:", defaultInput);
|
|
94
|
-
await runsurface(defaultInput);
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
consola.error("No Input String");
|
|
109
|
+
run: (ctx) => {
|
|
110
|
+
const { start, stop } = ctx.values;
|
|
111
|
+
if (start == true) startschedule();
|
|
112
|
+
if (stop == true) stopschedule();
|
|
98
113
|
}
|
|
99
|
-
}
|
|
100
|
-
|
|
114
|
+
};
|
|
115
|
+
//#endregion
|
|
116
|
+
//#region src/commands/default/index.ts
|
|
117
|
+
const mainCommand = async () => {
|
|
118
|
+
const RunmainCommand = define({
|
|
119
|
+
name: CLI_COMMAND_NAME,
|
|
120
|
+
description: "agent and other task runner",
|
|
121
|
+
version: await version(),
|
|
122
|
+
args: {
|
|
123
|
+
defaultInput: {
|
|
124
|
+
type: "positional",
|
|
125
|
+
required: false,
|
|
126
|
+
description: "Input value"
|
|
127
|
+
},
|
|
128
|
+
background: {
|
|
129
|
+
type: "string",
|
|
130
|
+
required: false,
|
|
131
|
+
description: "Run a task in the background"
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
run: async (ctx) => {
|
|
135
|
+
const { defaultInput, background } = ctx.values;
|
|
136
|
+
if (background !== void 0) {
|
|
137
|
+
console.log("Background mode:", background);
|
|
138
|
+
await runbackground(background);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (defaultInput !== void 0) {
|
|
142
|
+
console.log("Normal mode:", defaultInput);
|
|
143
|
+
await runsurface(defaultInput);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
consola.error("No Input String");
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
await cli(process.argv.slice(2), RunmainCommand, {
|
|
150
|
+
name: CLI_COMMAND_NAME,
|
|
151
|
+
subCommands: { run: runCommand }
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region src/index.ts
|
|
156
|
+
await mainCommand();
|
|
101
157
|
//#endregion
|
|
102
158
|
export {};
|
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.2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"consola": "^3.4.2",
|
|
24
24
|
"execa": "^10.1.0",
|
|
25
25
|
"gunshi": "^0.37.3",
|
|
26
|
+
"node-cron": "^4.6.0",
|
|
26
27
|
"zod": "^4.6.7"
|
|
27
28
|
}
|
|
28
29
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
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
|
+
defaultInput: {
|
|
17
|
+
type: "positional",
|
|
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 { defaultInput, background } = ctx.values;
|
|
31
|
+
|
|
32
|
+
if (background !== undefined) {
|
|
33
|
+
console.log("Background mode:", background);
|
|
34
|
+
await runbackground(background);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (defaultInput !== undefined) {
|
|
39
|
+
console.log("Normal mode:", defaultInput);
|
|
40
|
+
await runsurface(defaultInput);
|
|
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
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
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
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,49 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { version } from "./version.ts";
|
|
5
|
-
import { runbackground } from "./run/background.ts";
|
|
6
|
-
import { runsurface } from "./run/surface.ts";
|
|
7
|
-
import { consola } from "consola";
|
|
8
|
-
import { CLI_COMMAND_NAME } from "./constant/app.ts";
|
|
3
|
+
import { mainCommand } from "./commands/default";
|
|
9
4
|
|
|
10
|
-
|
|
11
|
-
name: CLI_COMMAND_NAME,
|
|
12
|
-
description: "agent and other task runner",
|
|
13
|
-
version: await version(),
|
|
14
|
-
|
|
15
|
-
args: {
|
|
16
|
-
defaultInput: {
|
|
17
|
-
type: "positional",
|
|
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 { defaultInput, background } = ctx.values;
|
|
31
|
-
|
|
32
|
-
if (background !== undefined) {
|
|
33
|
-
console.log("Background mode:", background);
|
|
34
|
-
await runbackground(background);
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (defaultInput !== undefined) {
|
|
39
|
-
console.log("Normal mode:", defaultInput);
|
|
40
|
-
await runsurface(defaultInput);
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
consola.error("No Input String");
|
|
45
|
-
return;
|
|
46
|
-
},
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
await cli(process.argv.slice(2), mainCommand, {});
|
|
5
|
+
await mainCommand();
|
|
@@ -4,7 +4,7 @@ import { CONFIG_DIR } from "./constant/app";
|
|
|
4
4
|
import { fileSchema, type FileType } from "./type";
|
|
5
5
|
import { consola } from "consola";
|
|
6
6
|
|
|
7
|
-
export const
|
|
7
|
+
export const readJSON = async (FileName: string): Promise<FileType> => {
|
|
8
8
|
const file = join(CONFIG_DIR, `${FileName}.json`);
|
|
9
9
|
const reads = await readFile(file, "utf-8");
|
|
10
10
|
|
package/src/run/background.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { consola } from "consola";
|
|
2
|
-
import {
|
|
2
|
+
import { readJSON } from "../readJSON.ts";
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
4
|
|
|
5
5
|
export const runbackground = async (FileName: string): Promise<void> => {
|
|
6
|
-
const reads = await
|
|
6
|
+
const reads = await readJSON(FileName);
|
|
7
7
|
const commandsplit = reads.run.split(/\s+/);
|
|
8
8
|
|
|
9
9
|
const [command, ...args] = commandsplit;
|
|
@@ -0,0 +1,39 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
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
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { consola } from "consola";
|
|
2
|
-
import {
|
|
2
|
+
import { readJSON } from "../readJSON.ts";
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
4
|
|
|
5
5
|
export const runsurface = async (FileName: string): Promise<void> => {
|
|
6
|
-
const reads = await
|
|
6
|
+
const reads = await readJSON(FileName);
|
|
7
7
|
const commandsplit = reads.run.split(/\s+/);
|
|
8
8
|
|
|
9
9
|
const [command, ...args] = commandsplit;
|
package/src/type.ts
CHANGED