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 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 readFiles(FileName)).run.split(/\s+/);
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 readFiles(FileName)).run.split(/\s+/);
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/index.ts
69
- const mainCommand = define({
70
- name: CLI_COMMAND_NAME,
71
- description: "agent and other task runner",
72
- version: await version(),
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
- defaultInput: {
75
- type: "positional",
98
+ start: {
99
+ type: "boolean",
76
100
  required: false,
77
- description: "Input value"
101
+ description: "Start scheduler"
78
102
  },
79
- background: {
80
- type: "string",
103
+ stop: {
104
+ type: "boolean",
81
105
  required: false,
82
- description: "Run a task in the background"
106
+ description: "Stop scheduler"
83
107
  }
84
108
  },
85
- run: async (ctx) => {
86
- const { defaultInput, background } = ctx.values;
87
- if (background !== void 0) {
88
- console.log("Background mode:", background);
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
- await cli(process.argv.slice(2), mainCommand, {});
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.1.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
+ };
@@ -3,3 +3,4 @@ import { homedir } from "node:os";
3
3
 
4
4
  export const CLI_COMMAND_NAME = "schrun";
5
5
  export const CONFIG_DIR = join(`${homedir()}`, ".config", "schrun");
6
+ export const PID_FILE = join(`${homedir()}`, ".config", "schrun", "pid.json");
package/src/index.ts CHANGED
@@ -1,49 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { cli, define } from "gunshi";
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
- const mainCommand = 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), 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 readFiles = async (FileName: string): Promise<FileType> => {
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
 
@@ -1,9 +1,9 @@
1
1
  import { consola } from "consola";
2
- import { readFiles } from "../read.ts";
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 readFiles(FileName);
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
+ };
@@ -1,9 +1,9 @@
1
1
  import { consola } from "consola";
2
- import { readFiles } from "../read.ts";
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 readFiles(FileName);
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
@@ -3,7 +3,9 @@ import { z } from "zod";
3
3
  export const fileSchema = z.object({
4
4
  id: z.number(),
5
5
  description: z.string(),
6
+ cron: z.string(),
6
7
  run: z.string(),
8
+ enabled: z.boolean(),
7
9
  });
8
10
 
9
11
  export type FileType = z.infer<typeof fileSchema>;
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from "tsdown";
2
+
3
+ export default defineConfig({
4
+ entry: {
5
+ index: "src/index.ts",
6
+ daemon: "src/run/daemon.ts",
7
+ },
8
+ });