crewly 1.20.245 → 1.20.246
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/backend/backend/src/constants.d.ts +20 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +20 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts +2 -0
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +22 -0
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/services/system/scheduled-commands.service.d.ts +130 -0
- package/dist/backend/backend/src/services/system/scheduled-commands.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/system/scheduled-commands.service.js +286 -0
- package/dist/backend/backend/src/services/system/scheduled-commands.service.js.map +1 -0
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +20 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +20 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scheduled commands runner.
|
|
3
|
+
*
|
|
4
|
+
* Runs host commands listed in `<CREWLY_HOME>/scheduled-commands.json` on an
|
|
5
|
+
* interval. It exists because launchd jobs cannot read ~/Desktop (TCC), while
|
|
6
|
+
* the backend can. Design rules:
|
|
7
|
+
* - The list is read from disk only. There is deliberately no API route or
|
|
8
|
+
* trigger action that adds an entry: that would be arbitrary shell
|
|
9
|
+
* execution through the API.
|
|
10
|
+
* - No file = no jobs = feature off.
|
|
11
|
+
* - Each run is spawned DETACHED (own process group, unref'd, output to a log
|
|
12
|
+
* file) so a Crewly restart cannot kill it mid-way (a rollout cut in half
|
|
13
|
+
* leaves a mixed fleet).
|
|
14
|
+
* - A run never starts while the previous one is alive: checked by the pid we
|
|
15
|
+
* spawned and, when `lockFile` is set, by the pid inside the command's own
|
|
16
|
+
* lock file (a stale pid counts as dead).
|
|
17
|
+
* - Every skip is logged at debug; a run is logged at info only when spawned.
|
|
18
|
+
*
|
|
19
|
+
* Config shape: `[{name, cwd, command, args?, intervalMinutes, enabled?, lockFile?}]`.
|
|
20
|
+
*
|
|
21
|
+
* @module services/system/scheduled-commands
|
|
22
|
+
*/
|
|
23
|
+
import { type ChildProcess, type SpawnOptions } from 'child_process';
|
|
24
|
+
/** One validated entry from the config file. */
|
|
25
|
+
export interface ScheduledCommand {
|
|
26
|
+
/** Unique name (used in the log file name) */
|
|
27
|
+
name: string;
|
|
28
|
+
/** Absolute working directory */
|
|
29
|
+
cwd: string;
|
|
30
|
+
/** Executable, run without a shell */
|
|
31
|
+
command: string;
|
|
32
|
+
/** Arguments */
|
|
33
|
+
args: string[];
|
|
34
|
+
/** Minutes between runs */
|
|
35
|
+
intervalMinutes: number;
|
|
36
|
+
/** Absolute path of the command's own lock file (holds a pid), optional */
|
|
37
|
+
lockFile?: string;
|
|
38
|
+
}
|
|
39
|
+
/** Result of reading the config file. */
|
|
40
|
+
export interface LoadedScheduledCommands {
|
|
41
|
+
/** Enabled, valid entries */
|
|
42
|
+
entries: ScheduledCommand[];
|
|
43
|
+
/** Entries in the file that were valid but disabled */
|
|
44
|
+
disabled: number;
|
|
45
|
+
/** Why the file or an entry was not used (one line each) */
|
|
46
|
+
warnings: string[];
|
|
47
|
+
}
|
|
48
|
+
/** Minimal logger contract. */
|
|
49
|
+
export interface ScheduledCommandsLogger {
|
|
50
|
+
debug(message: string, meta?: Record<string, unknown>): void;
|
|
51
|
+
info(message: string, meta?: Record<string, unknown>): void;
|
|
52
|
+
warn(message: string, meta?: Record<string, unknown>): void;
|
|
53
|
+
error(message: string, meta?: Record<string, unknown>): void;
|
|
54
|
+
}
|
|
55
|
+
/** Injection points (tests replace the process and filesystem edges). */
|
|
56
|
+
export interface ScheduledCommandsDeps {
|
|
57
|
+
/** Path of the config file */
|
|
58
|
+
configPath: string;
|
|
59
|
+
/** Directory for per-command log files */
|
|
60
|
+
logDir: string;
|
|
61
|
+
logger: ScheduledCommandsLogger;
|
|
62
|
+
/** child_process.spawn */
|
|
63
|
+
spawn?: (command: string, args: string[], options: SpawnOptions) => ChildProcess;
|
|
64
|
+
/** True when a process with this pid exists */
|
|
65
|
+
isPidAlive?: (pid: number) => boolean;
|
|
66
|
+
/** Reads a text file; throws when missing */
|
|
67
|
+
readFile?: (file: string) => string;
|
|
68
|
+
/** True when the path exists */
|
|
69
|
+
pathExists?: (file: string) => boolean;
|
|
70
|
+
/** Opens the log file for appending and returns an fd */
|
|
71
|
+
openLog?: (file: string) => number;
|
|
72
|
+
/** Closes an fd */
|
|
73
|
+
closeLog?: (fd: number) => void;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* True when a process with this pid exists (signal 0 probe).
|
|
77
|
+
*
|
|
78
|
+
* @param pid - Process id
|
|
79
|
+
* @returns Whether it is alive
|
|
80
|
+
*/
|
|
81
|
+
export declare function defaultIsPidAlive(pid: number): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Parse and validate the config text.
|
|
84
|
+
*
|
|
85
|
+
* Invalid input never throws: it yields zero entries and a warning.
|
|
86
|
+
*
|
|
87
|
+
* @param text - File contents
|
|
88
|
+
* @returns Entries, disabled count and warnings
|
|
89
|
+
*/
|
|
90
|
+
export declare function parseScheduledCommands(text: string): LoadedScheduledCommands;
|
|
91
|
+
/** Outcome of one tick for one entry (also returned to tests). */
|
|
92
|
+
export type RunOutcome = 'spawned' | 'skipped-running' | 'skipped-lock' | 'skipped-no-cwd' | 'spawn-failed';
|
|
93
|
+
/**
|
|
94
|
+
* Interval runner for owner-configured host commands.
|
|
95
|
+
*/
|
|
96
|
+
export declare class ScheduledCommandsService {
|
|
97
|
+
private readonly deps;
|
|
98
|
+
private entries;
|
|
99
|
+
private timers;
|
|
100
|
+
/** Last spawned pid per entry name */
|
|
101
|
+
private readonly lastPid;
|
|
102
|
+
/**
|
|
103
|
+
* @param deps - Paths, logger and optional process/filesystem overrides
|
|
104
|
+
*/
|
|
105
|
+
constructor(deps: ScheduledCommandsDeps);
|
|
106
|
+
/**
|
|
107
|
+
* Load the config and schedule every enabled entry.
|
|
108
|
+
*
|
|
109
|
+
* Logs "N scheduled command(s) loaded" every time, including 0.
|
|
110
|
+
* A missing file is the normal off state (info); an unreadable or
|
|
111
|
+
* invalid file logs one warn line.
|
|
112
|
+
*
|
|
113
|
+
* @returns Number of jobs scheduled
|
|
114
|
+
*/
|
|
115
|
+
start(): number;
|
|
116
|
+
/** Cancel all timers. Spawned children are NOT touched (they are detached). */
|
|
117
|
+
stop(): void;
|
|
118
|
+
/**
|
|
119
|
+
* Try to run one entry now.
|
|
120
|
+
*
|
|
121
|
+
* Skips (debug log) when the previous run or the command's own lock holder
|
|
122
|
+
* is alive, or the cwd is missing (warn). Never claims a run happened
|
|
123
|
+
* when it was skipped.
|
|
124
|
+
*
|
|
125
|
+
* @param entry - Entry to run
|
|
126
|
+
* @returns What happened
|
|
127
|
+
*/
|
|
128
|
+
runEntry(entry: ScheduledCommand): RunOutcome;
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=scheduled-commands.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduled-commands.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/system/scheduled-commands.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scheduled commands runner.
|
|
3
|
+
*
|
|
4
|
+
* Runs host commands listed in `<CREWLY_HOME>/scheduled-commands.json` on an
|
|
5
|
+
* interval. It exists because launchd jobs cannot read ~/Desktop (TCC), while
|
|
6
|
+
* the backend can. Design rules:
|
|
7
|
+
* - The list is read from disk only. There is deliberately no API route or
|
|
8
|
+
* trigger action that adds an entry: that would be arbitrary shell
|
|
9
|
+
* execution through the API.
|
|
10
|
+
* - No file = no jobs = feature off.
|
|
11
|
+
* - Each run is spawned DETACHED (own process group, unref'd, output to a log
|
|
12
|
+
* file) so a Crewly restart cannot kill it mid-way (a rollout cut in half
|
|
13
|
+
* leaves a mixed fleet).
|
|
14
|
+
* - A run never starts while the previous one is alive: checked by the pid we
|
|
15
|
+
* spawned and, when `lockFile` is set, by the pid inside the command's own
|
|
16
|
+
* lock file (a stale pid counts as dead).
|
|
17
|
+
* - Every skip is logged at debug; a run is logged at info only when spawned.
|
|
18
|
+
*
|
|
19
|
+
* Config shape: `[{name, cwd, command, args?, intervalMinutes, enabled?, lockFile?}]`.
|
|
20
|
+
*
|
|
21
|
+
* @module services/system/scheduled-commands
|
|
22
|
+
*/
|
|
23
|
+
import { spawn as nodeSpawn } from 'child_process';
|
|
24
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync } from 'fs';
|
|
25
|
+
import * as os from 'os';
|
|
26
|
+
import * as path from 'path';
|
|
27
|
+
import { SCHEDULED_COMMANDS } from '../../constants.js';
|
|
28
|
+
/**
|
|
29
|
+
* Expand a leading `~` to the home directory.
|
|
30
|
+
*
|
|
31
|
+
* @param p - Path from the config file
|
|
32
|
+
* @returns Path with `~` expanded
|
|
33
|
+
*/
|
|
34
|
+
function expandHome(p) {
|
|
35
|
+
if (p === '~')
|
|
36
|
+
return os.homedir();
|
|
37
|
+
if (p.startsWith('~/'))
|
|
38
|
+
return path.join(os.homedir(), p.slice(2));
|
|
39
|
+
return p;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* True when a process with this pid exists (signal 0 probe).
|
|
43
|
+
*
|
|
44
|
+
* @param pid - Process id
|
|
45
|
+
* @returns Whether it is alive
|
|
46
|
+
*/
|
|
47
|
+
export function defaultIsPidAlive(pid) {
|
|
48
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
49
|
+
return false;
|
|
50
|
+
try {
|
|
51
|
+
process.kill(pid, 0);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
// EPERM = exists but not ours: still alive.
|
|
56
|
+
return err.code === 'EPERM';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Parse and validate the config text.
|
|
61
|
+
*
|
|
62
|
+
* Invalid input never throws: it yields zero entries and a warning.
|
|
63
|
+
*
|
|
64
|
+
* @param text - File contents
|
|
65
|
+
* @returns Entries, disabled count and warnings
|
|
66
|
+
*/
|
|
67
|
+
export function parseScheduledCommands(text) {
|
|
68
|
+
const out = { entries: [], disabled: 0, warnings: [] };
|
|
69
|
+
let raw;
|
|
70
|
+
try {
|
|
71
|
+
raw = JSON.parse(text);
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
out.warnings.push(`config is not valid JSON (${err instanceof Error ? err.message : String(err)}); 0 jobs`);
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
if (!Array.isArray(raw)) {
|
|
78
|
+
out.warnings.push('config must be a JSON array; 0 jobs');
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
const seen = new Set();
|
|
82
|
+
raw.forEach((item, i) => {
|
|
83
|
+
const label = `entry ${i}`;
|
|
84
|
+
if (!item || typeof item !== 'object') {
|
|
85
|
+
out.warnings.push(`${label}: not an object; skipped`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const e = item;
|
|
89
|
+
if (typeof e.name !== 'string' || !SCHEDULED_COMMANDS.NAME_PATTERN.test(e.name)) {
|
|
90
|
+
out.warnings.push(`${label}: name must match ${SCHEDULED_COMMANDS.NAME_PATTERN}; skipped`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (seen.has(e.name)) {
|
|
94
|
+
out.warnings.push(`${label} (${e.name}): duplicate name; skipped`);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (typeof e.command !== 'string' || e.command.trim() === '') {
|
|
98
|
+
out.warnings.push(`${label} (${e.name}): command is required; skipped`);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (typeof e.cwd !== 'string' || !path.isAbsolute(expandHome(e.cwd))) {
|
|
102
|
+
out.warnings.push(`${label} (${e.name}): cwd must be an absolute path; skipped`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (e.args !== undefined && (!Array.isArray(e.args) || e.args.some((a) => typeof a !== 'string'))) {
|
|
106
|
+
out.warnings.push(`${label} (${e.name}): args must be an array of strings; skipped`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (typeof e.intervalMinutes !== 'number' ||
|
|
110
|
+
!Number.isFinite(e.intervalMinutes) ||
|
|
111
|
+
e.intervalMinutes < SCHEDULED_COMMANDS.MIN_INTERVAL_MINUTES) {
|
|
112
|
+
out.warnings.push(`${label} (${e.name}): intervalMinutes must be a number >= ${SCHEDULED_COMMANDS.MIN_INTERVAL_MINUTES}; skipped`);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (e.lockFile !== undefined && (typeof e.lockFile !== 'string' || !path.isAbsolute(expandHome(e.lockFile)))) {
|
|
116
|
+
out.warnings.push(`${label} (${e.name}): lockFile must be an absolute path; skipped`);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
seen.add(e.name);
|
|
120
|
+
if (e.enabled === false) {
|
|
121
|
+
out.disabled++;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
out.entries.push({
|
|
125
|
+
name: e.name,
|
|
126
|
+
cwd: expandHome(e.cwd),
|
|
127
|
+
command: e.command,
|
|
128
|
+
args: e.args ?? [],
|
|
129
|
+
intervalMinutes: e.intervalMinutes,
|
|
130
|
+
lockFile: typeof e.lockFile === 'string' ? expandHome(e.lockFile) : undefined,
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Interval runner for owner-configured host commands.
|
|
137
|
+
*/
|
|
138
|
+
export class ScheduledCommandsService {
|
|
139
|
+
deps;
|
|
140
|
+
entries = [];
|
|
141
|
+
timers = [];
|
|
142
|
+
/** Last spawned pid per entry name */
|
|
143
|
+
lastPid = new Map();
|
|
144
|
+
/**
|
|
145
|
+
* @param deps - Paths, logger and optional process/filesystem overrides
|
|
146
|
+
*/
|
|
147
|
+
constructor(deps) {
|
|
148
|
+
this.deps = {
|
|
149
|
+
spawn: nodeSpawn,
|
|
150
|
+
isPidAlive: defaultIsPidAlive,
|
|
151
|
+
readFile: (f) => readFileSync(f, 'utf-8'),
|
|
152
|
+
pathExists: existsSync,
|
|
153
|
+
openLog: (f) => {
|
|
154
|
+
mkdirSync(path.dirname(f), { recursive: true });
|
|
155
|
+
return openSync(f, 'a');
|
|
156
|
+
},
|
|
157
|
+
closeLog: closeSync,
|
|
158
|
+
...deps,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Load the config and schedule every enabled entry.
|
|
163
|
+
*
|
|
164
|
+
* Logs "N scheduled command(s) loaded" every time, including 0.
|
|
165
|
+
* A missing file is the normal off state (info); an unreadable or
|
|
166
|
+
* invalid file logs one warn line.
|
|
167
|
+
*
|
|
168
|
+
* @returns Number of jobs scheduled
|
|
169
|
+
*/
|
|
170
|
+
start() {
|
|
171
|
+
this.stop();
|
|
172
|
+
let text;
|
|
173
|
+
try {
|
|
174
|
+
text = this.deps.readFile(this.deps.configPath);
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
if (err.code === 'ENOENT') {
|
|
178
|
+
this.deps.logger.info('0 scheduled command(s) loaded (no config file)', { path: this.deps.configPath });
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
this.deps.logger.warn('0 scheduled command(s) loaded: config unreadable', {
|
|
182
|
+
path: this.deps.configPath,
|
|
183
|
+
error: err instanceof Error ? err.message : String(err),
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
return 0;
|
|
187
|
+
}
|
|
188
|
+
const loaded = parseScheduledCommands(text);
|
|
189
|
+
loaded.warnings.forEach((w) => this.deps.logger.warn(`scheduled-commands: ${w}`));
|
|
190
|
+
this.entries = loaded.entries;
|
|
191
|
+
this.deps.logger.info(`${loaded.entries.length} scheduled command(s) loaded`, {
|
|
192
|
+
disabled: loaded.disabled,
|
|
193
|
+
invalid: loaded.warnings.length,
|
|
194
|
+
names: loaded.entries.map((e) => e.name),
|
|
195
|
+
});
|
|
196
|
+
for (const entry of this.entries) {
|
|
197
|
+
const every = entry.intervalMinutes * SCHEDULED_COMMANDS.MS_PER_MINUTE;
|
|
198
|
+
const first = setTimeout(() => this.runEntry(entry), SCHEDULED_COMMANDS.INITIAL_DELAY_MS);
|
|
199
|
+
const repeat = setInterval(() => this.runEntry(entry), every);
|
|
200
|
+
first.unref();
|
|
201
|
+
repeat.unref();
|
|
202
|
+
this.timers.push(first, repeat);
|
|
203
|
+
}
|
|
204
|
+
return this.entries.length;
|
|
205
|
+
}
|
|
206
|
+
/** Cancel all timers. Spawned children are NOT touched (they are detached). */
|
|
207
|
+
stop() {
|
|
208
|
+
for (const t of this.timers) {
|
|
209
|
+
clearTimeout(t);
|
|
210
|
+
clearInterval(t);
|
|
211
|
+
}
|
|
212
|
+
this.timers = [];
|
|
213
|
+
this.entries = [];
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Try to run one entry now.
|
|
217
|
+
*
|
|
218
|
+
* Skips (debug log) when the previous run or the command's own lock holder
|
|
219
|
+
* is alive, or the cwd is missing (warn). Never claims a run happened
|
|
220
|
+
* when it was skipped.
|
|
221
|
+
*
|
|
222
|
+
* @param entry - Entry to run
|
|
223
|
+
* @returns What happened
|
|
224
|
+
*/
|
|
225
|
+
runEntry(entry) {
|
|
226
|
+
const { logger, isPidAlive } = this.deps;
|
|
227
|
+
const prev = this.lastPid.get(entry.name);
|
|
228
|
+
if (prev !== undefined && isPidAlive(prev)) {
|
|
229
|
+
logger.debug(`scheduled-commands: ${entry.name} skipped, previous run pid ${prev} still alive`);
|
|
230
|
+
return 'skipped-running';
|
|
231
|
+
}
|
|
232
|
+
if (entry.lockFile) {
|
|
233
|
+
let holder = NaN;
|
|
234
|
+
try {
|
|
235
|
+
holder = parseInt(this.deps.readFile(entry.lockFile).trim(), 10);
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
// No lock file = nobody holds it.
|
|
239
|
+
}
|
|
240
|
+
if (Number.isInteger(holder) && isPidAlive(holder)) {
|
|
241
|
+
logger.debug(`scheduled-commands: ${entry.name} skipped, lock held by pid ${holder}`);
|
|
242
|
+
return 'skipped-lock';
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (!this.deps.pathExists(entry.cwd)) {
|
|
246
|
+
logger.warn(`scheduled-commands: ${entry.name} not run, cwd missing`, { cwd: entry.cwd });
|
|
247
|
+
return 'skipped-no-cwd';
|
|
248
|
+
}
|
|
249
|
+
let fd = -1;
|
|
250
|
+
try {
|
|
251
|
+
const logFile = path.join(this.deps.logDir, SCHEDULED_COMMANDS.LOG_FILE_TEMPLATE.replace('{name}', entry.name));
|
|
252
|
+
fd = this.deps.openLog(logFile);
|
|
253
|
+
const child = this.deps.spawn(entry.command, entry.args, {
|
|
254
|
+
cwd: entry.cwd,
|
|
255
|
+
detached: true,
|
|
256
|
+
stdio: ['ignore', fd, fd],
|
|
257
|
+
});
|
|
258
|
+
child.on('error', (err) => logger.error(`scheduled-commands: ${entry.name} failed to start`, { error: err.message }));
|
|
259
|
+
child.unref();
|
|
260
|
+
if (child.pid === undefined) {
|
|
261
|
+
logger.error(`scheduled-commands: ${entry.name} did not start (no pid)`);
|
|
262
|
+
return 'spawn-failed';
|
|
263
|
+
}
|
|
264
|
+
this.lastPid.set(entry.name, child.pid);
|
|
265
|
+
logger.info(`scheduled-commands: ${entry.name} started`, { pid: child.pid, cwd: entry.cwd });
|
|
266
|
+
return 'spawned';
|
|
267
|
+
}
|
|
268
|
+
catch (err) {
|
|
269
|
+
logger.error(`scheduled-commands: ${entry.name} spawn threw`, {
|
|
270
|
+
error: err instanceof Error ? err.message : String(err),
|
|
271
|
+
});
|
|
272
|
+
return 'spawn-failed';
|
|
273
|
+
}
|
|
274
|
+
finally {
|
|
275
|
+
if (fd >= 0) {
|
|
276
|
+
try {
|
|
277
|
+
this.deps.closeLog(fd);
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
// Already closed.
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
//# sourceMappingURL=scheduled-commands.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduled-commands.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/system/scheduled-commands.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -6989,4 +6989,24 @@ export declare const TEAM_PAUSE_CONSTANTS: {
|
|
|
6989
6989
|
/** Metadata key on a WorkItem unassigned because its team was paused */
|
|
6990
6990
|
readonly UNASSIGNED_METADATA_KEY: "pausedTeamUnassigned";
|
|
6991
6991
|
};
|
|
6992
|
+
/**
|
|
6993
|
+
* Scheduled commands: an owner-edited list of host commands the backend runs
|
|
6994
|
+
* on an interval (e.g. the crewly-web release script). Read from disk only.
|
|
6995
|
+
*/
|
|
6996
|
+
export declare const SCHEDULED_COMMANDS: {
|
|
6997
|
+
/** Config file under CREWLY_HOME; absent = feature off */
|
|
6998
|
+
readonly CONFIG_FILE: "scheduled-commands.json";
|
|
6999
|
+
/** Directory under CREWLY_HOME that receives each command's stdout/stderr */
|
|
7000
|
+
readonly LOG_DIR: "logs";
|
|
7001
|
+
/** Log file name pattern; `{name}` is the entry name */
|
|
7002
|
+
readonly LOG_FILE_TEMPLATE: "scheduled-{name}.log";
|
|
7003
|
+
/** Shortest interval accepted (minutes); smaller values are rejected */
|
|
7004
|
+
readonly MIN_INTERVAL_MINUTES: 1;
|
|
7005
|
+
/** Delay before the first run after boot (ms), so restarts do not starve a run */
|
|
7006
|
+
readonly INITIAL_DELAY_MS: 30000;
|
|
7007
|
+
/** Entry names: letters, digits, dot, dash, underscore */
|
|
7008
|
+
readonly NAME_PATTERN: RegExp;
|
|
7009
|
+
/** Milliseconds per minute */
|
|
7010
|
+
readonly MS_PER_MINUTE: 60000;
|
|
7011
|
+
};
|
|
6992
7012
|
//# sourceMappingURL=constants.d.ts.map
|