@misterhuydo/sentinel 1.4.69 → 1.4.70
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/.cairn/.hint-lock +1 -1
- package/.cairn/session.json +2 -2
- package/lib/.cairn/minify-map.json +0 -12
- package/lib/add.js +30 -16
- package/package.json +1 -1
- package/lib/.cairn/views/244a09_generate.js +0 -274
package/.cairn/.hint-lock
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2026-03-
|
|
1
|
+
2026-03-26T18:21:14.192Z
|
package/.cairn/session.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"message": "Auto-checkpoint at 2026-03-
|
|
3
|
-
"checkpoint_at": "2026-03-
|
|
2
|
+
"message": "Auto-checkpoint at 2026-03-26T18:22:50.768Z",
|
|
3
|
+
"checkpoint_at": "2026-03-26T18:22:50.770Z",
|
|
4
4
|
"active_files": [],
|
|
5
5
|
"notes": [],
|
|
6
6
|
"mtime_snapshot": {}
|
|
@@ -1,20 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"J:\\Projects\\Sentinel\\cli\\lib\\add.js": {
|
|
3
|
-
"tempPath": "J:\\Projects\\Sentinel\\cli\\lib\\.cairn\\views\\fc4a1a_add.js",
|
|
4
|
-
"state": "edit-ready",
|
|
5
|
-
"minifiedAt": 1774403831187.837,
|
|
6
|
-
"readCount": 1
|
|
7
|
-
},
|
|
8
2
|
"J:\\Projects\\Sentinel\\cli\\lib\\upgrade.js": {
|
|
9
3
|
"tempPath": "J:\\Projects\\Sentinel\\cli\\lib\\.cairn\\views\\fb78ac_upgrade.js",
|
|
10
4
|
"state": "edit-ready",
|
|
11
5
|
"minifiedAt": 1774409075884.3267,
|
|
12
6
|
"readCount": 1
|
|
13
|
-
},
|
|
14
|
-
"J:\\Projects\\Sentinel\\cli\\lib\\generate.js": {
|
|
15
|
-
"tempPath": "J:\\Projects\\Sentinel\\cli\\lib\\.cairn\\views\\244a09_generate.js",
|
|
16
|
-
"state": "compressed",
|
|
17
|
-
"minifiedAt": 1774454098784.183,
|
|
18
|
-
"readCount": 1
|
|
19
7
|
}
|
|
20
8
|
}
|
package/lib/add.js
CHANGED
|
@@ -277,35 +277,49 @@ async function addFromGit(gitUrl, workspace) {
|
|
|
277
277
|
validate: v => VALID_NAME.test(v) || 'Use letters, numbers, hyphens only',
|
|
278
278
|
}], { onCancel: () => process.exit(0) });
|
|
279
279
|
|
|
280
|
-
// ── 1.
|
|
280
|
+
// ── 1. Set up SSH access to the primary (config) repo ─────────────────────
|
|
281
281
|
step(`[1/3] Setting up SSH access to ${repoSlug}`);
|
|
282
282
|
ensureKnownHosts();
|
|
283
|
-
const { keyFile } = generateDeployKey(repoSlug);
|
|
284
|
-
printDeployKeyInstructions(orgRepo, keyFile);
|
|
285
283
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
284
|
+
// Check if the repo is already accessible via the existing SSH key/agent.
|
|
285
|
+
// This is the case when GIT_ACCESS=ssh_user_key is configured — no deploy key needed.
|
|
286
|
+
const existingAccess = validateAccess(gitUrl, null);
|
|
287
|
+
let keyFile = null;
|
|
290
288
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
289
|
+
if (existingAccess.ok) {
|
|
290
|
+
ok(`${repoSlug}: reachable via existing SSH key — skipping deploy key generation`);
|
|
291
|
+
} else {
|
|
292
|
+
const { keyFile: generatedKey } = generateDeployKey(repoSlug);
|
|
293
|
+
keyFile = generatedKey;
|
|
294
|
+
printDeployKeyInstructions(orgRepo, keyFile);
|
|
295
|
+
|
|
296
|
+
await prompts({
|
|
297
|
+
type: 'text', name: '_', format: () => '',
|
|
298
|
+
message: chalk.bold(`Press Enter once you've added the deploy key to GitHub…`),
|
|
299
|
+
}, { onCancel: () => process.exit(0) });
|
|
300
|
+
|
|
301
|
+
// Validate primary repo with deploy key
|
|
302
|
+
const primary = validateAccess(gitUrl, keyFile);
|
|
303
|
+
if (!primary.ok) {
|
|
304
|
+
console.error(chalk.red(' ✖ Cannot reach ' + gitUrl));
|
|
305
|
+
if (primary.stderr) console.error(chalk.red(' ' + primary.stderr));
|
|
306
|
+
console.error(chalk.yellow(' Check the deploy key has write access, then re-run.'));
|
|
307
|
+
process.exit(1);
|
|
308
|
+
}
|
|
309
|
+
ok(`${repoSlug}: reachable`);
|
|
298
310
|
}
|
|
299
|
-
ok(`${repoSlug}: reachable`);
|
|
300
311
|
|
|
301
312
|
// ── 2. Clone primary repo and discover additional repos ────────────────────
|
|
302
313
|
const projectDir = path.join(workspace, name);
|
|
303
314
|
step(`[2/3] Scanning repo-configs in ${repoSlug}…`);
|
|
304
315
|
|
|
305
316
|
if (!fs.existsSync(projectDir)) {
|
|
317
|
+
const cloneEnv = keyFile
|
|
318
|
+
? gitEnv({ GIT_SSH_COMMAND: `ssh -i ${keyFile} -o StrictHostKeyChecking=no -o BatchMode=yes` })
|
|
319
|
+
: gitEnv({});
|
|
306
320
|
spawnSync(gitBin(), ['clone', '--depth', '1', gitUrl, projectDir], {
|
|
307
321
|
stdio: 'inherit',
|
|
308
|
-
env:
|
|
322
|
+
env: cloneEnv,
|
|
309
323
|
});
|
|
310
324
|
}
|
|
311
325
|
|
package/package.json
CHANGED
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
const fs = require('fs-extra');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
function writeExampleProject(projectDir, codeDir, pythonBin, anthropicKey = '', slackTokens = {}) {
|
|
5
|
-
const configDir = path.join(projectDir, 'config', 'log-configs');
|
|
6
|
-
const repoDir = path.join(projectDir, 'config', 'repo-configs');
|
|
7
|
-
fs.ensureDirSync(configDir);
|
|
8
|
-
fs.ensureDirSync(repoDir);
|
|
9
|
-
const tplDir = path.join(__dirname, '..', 'templates');
|
|
10
|
-
let sentinelProps = fs.readFileSync(path.join(tplDir, 'sentinel.properties'), 'utf8');
|
|
11
|
-
if (anthropicKey) {
|
|
12
|
-
sentinelProps = sentinelProps.replace(/^# ANTHROPIC_API_KEY=.*/m, `ANTHROPIC_API_KEY=${anthropicKey}`);
|
|
13
|
-
}
|
|
14
|
-
if (slackTokens.botToken) {
|
|
15
|
-
sentinelProps = sentinelProps.replace(/^# SLACK_BOT_TOKEN=.*/m, `SLACK_BOT_TOKEN=${slackTokens.botToken}`);
|
|
16
|
-
}
|
|
17
|
-
if (slackTokens.appToken) {
|
|
18
|
-
sentinelProps = sentinelProps.replace(/^# SLACK_APP_TOKEN=.*/m, `SLACK_APP_TOKEN=${slackTokens.appToken}`);
|
|
19
|
-
}
|
|
20
|
-
fs.writeFileSync(path.join(projectDir, 'config', 'sentinel.properties'), sentinelProps);
|
|
21
|
-
fs.copySync(path.join(tplDir, 'log-configs', '_example.properties'), path.join(configDir, '_example.properties'));
|
|
22
|
-
fs.copySync(path.join(tplDir, 'repo-configs', '_example.properties'), path.join(repoDir, '_example.properties'));
|
|
23
|
-
generateProjectScripts(projectDir, codeDir, pythonBin);
|
|
24
|
-
}
|
|
25
|
-
function generateProjectScripts(projectDir, codeDir, pythonBin) {
|
|
26
|
-
const name = path.basename(projectDir);
|
|
27
|
-
fs.writeFileSync(path.join(projectDir, 'start.sh'), `#!/usr/bin/env bash
|
|
28
|
-
# Start this Sentinel instance
|
|
29
|
-
set -euo pipefail
|
|
30
|
-
DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
31
|
-
PID_FILE="$DIR/sentinel.pid"
|
|
32
|
-
if [[ -f "$PID_FILE" ]] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
|
|
33
|
-
echo "[sentinel] ${name} already running (PID $(cat "$PID_FILE"))"
|
|
34
|
-
exit 0
|
|
35
|
-
fi
|
|
36
|
-
# Kill any orphaned sentinel processes for this project (stale PIDs not in PID file)
|
|
37
|
-
pkill -f "sentinel.main --config $DIR/config" 2>/dev/null || true
|
|
38
|
-
rm -f "$PID_FILE"
|
|
39
|
-
WORKSPACE="$(dirname "$DIR")"
|
|
40
|
-
# Check Claude Code authentication — skip if CLAUDE_PRO_FOR_TASKS=false in either config
|
|
41
|
-
_claude_pro=true
|
|
42
|
-
for _conf in "$WORKSPACE/sentinel.properties" "$DIR/config/sentinel.properties"; do
|
|
43
|
-
if [[ -f "$_conf" ]]; then
|
|
44
|
-
_val=$(grep -iE "^CLAUDE_PRO_FOR_TASKS[[:space:]]*=" "$_conf" 2>/dev/null | tail -1 | cut -d= -f2- | tr -d ' ' | tr '[:upper:]' '[:lower:]' || true)
|
|
45
|
-
[[ -n "$_val" ]] && _claude_pro="$_val"
|
|
46
|
-
fi
|
|
47
|
-
done
|
|
48
|
-
if [[ "$_claude_pro" != "false" ]]; then
|
|
49
|
-
AUTH_OUT=$(claude --print \"hi\" 2>&1 || true)
|
|
50
|
-
if echo "$AUTH_OUT" | grep -Eqi "not logged in|/login"; then
|
|
51
|
-
echo ""
|
|
52
|
-
echo "[sentinel] Claude Code is not authenticated."
|
|
53
|
-
echo " 1. Open a new terminal and run: claude"
|
|
54
|
-
echo " 2. Type /login at the prompt"
|
|
55
|
-
echo " 3. Open the URL in any browser and log in"
|
|
56
|
-
echo " 4. Type /exit when done"
|
|
57
|
-
echo " 5. Re-run this script"
|
|
58
|
-
echo ""
|
|
59
|
-
exit 1
|
|
60
|
-
fi
|
|
61
|
-
fi
|
|
62
|
-
mkdir -p "$DIR/logs" "$WORKSPACE/logs" "$DIR/workspace/fetched" "$DIR/workspace/patches" "$DIR/issues"
|
|
63
|
-
cd "$DIR"
|
|
64
|
-
# Ensure npm-global bin (cairn-mcp, claude) and ~/.local/bin (auto-installed tools) are on PATH
|
|
65
|
-
export PATH="$HOME/.npm-global/bin:$HOME/.local/bin:$PATH"
|
|
66
|
-
PYTHONPATH="${codeDir}" "${codeDir}/.venv/bin/python3" -m sentinel.main --config ./config \\
|
|
67
|
-
>> "$DIR/logs/sentinel.log" 2>&1 &
|
|
68
|
-
echo $! > "$PID_FILE"
|
|
69
|
-
echo "[sentinel] ${name} started (PID $!)"
|
|
70
|
-
echo " project log : $DIR/logs/sentinel.log"
|
|
71
|
-
echo " workspace log: $WORKSPACE/logs/sentinel.log"
|
|
72
|
-
`, { mode: 0o755 });
|
|
73
|
-
fs.writeFileSync(path.join(projectDir, 'stop.sh'), `#!/usr/bin/env bash
|
|
74
|
-
# Stop this Sentinel instance
|
|
75
|
-
set -euo pipefail
|
|
76
|
-
DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
77
|
-
PID_FILE="$DIR/sentinel.pid"
|
|
78
|
-
if [[ ! -f "$PID_FILE" ]]; then
|
|
79
|
-
echo "[sentinel] ${name} — no PID file, not running"
|
|
80
|
-
exit 0
|
|
81
|
-
fi
|
|
82
|
-
PID=$(cat "$PID_FILE")
|
|
83
|
-
if kill -0 "$PID" 2>/dev/null; then
|
|
84
|
-
kill "$PID"
|
|
85
|
-
echo "[sentinel] ${name} stopped (PID $PID)"
|
|
86
|
-
else
|
|
87
|
-
echo "[sentinel] ${name} — PID $PID not running"
|
|
88
|
-
fi
|
|
89
|
-
rm -f "$PID_FILE"
|
|
90
|
-
`, { mode: 0o755 });
|
|
91
|
-
}
|
|
92
|
-
function generateWorkspaceScripts(workspace, smtpConfig = {}, slackConfig = {}, authConfig = {}, githubToken = '') {
|
|
93
|
-
const workspaceProps = path.join(workspace, 'sentinel.properties');
|
|
94
|
-
if (!fs.existsSync(workspaceProps)) {
|
|
95
|
-
const tplDir = path.join(__dirname, '..', 'templates');
|
|
96
|
-
let tpl = fs.readFileSync(path.join(tplDir, 'workspace-sentinel.properties'), 'utf8');
|
|
97
|
-
if (smtpConfig.host) tpl = tpl.replace('SMTP_HOST=smtp.gmail.com', 'SMTP_HOST=' + smtpConfig.host);
|
|
98
|
-
if (smtpConfig.port) tpl = tpl.replace('SMTP_PORT=587', 'SMTP_PORT=' + smtpConfig.port);
|
|
99
|
-
if (smtpConfig.user) tpl = tpl.replace('SMTP_USER=sentinel@yourdomain.com', 'SMTP_USER=' + smtpConfig.user);
|
|
100
|
-
if (smtpConfig.password) tpl = tpl.replace('SMTP_PASSWORD=<app-password>', 'SMTP_PASSWORD=' + smtpConfig.password);
|
|
101
|
-
fs.writeFileSync(workspaceProps, tpl);
|
|
102
|
-
}
|
|
103
|
-
if (authConfig.apiKey || authConfig.claudeProForTasks !== undefined) {
|
|
104
|
-
let props = fs.readFileSync(workspaceProps, 'utf8');
|
|
105
|
-
if (authConfig.apiKey) {
|
|
106
|
-
if (/^#?\s*ANTHROPIC_API_KEY=/m.test(props))
|
|
107
|
-
props = props.replace(/^#?\s*ANTHROPIC_API_KEY=.*/mg, 'ANTHROPIC_API_KEY=' + authConfig.apiKey);
|
|
108
|
-
else
|
|
109
|
-
props = props.trimEnd() + '\nANTHROPIC_API_KEY=' + authConfig.apiKey + '\n';
|
|
110
|
-
}
|
|
111
|
-
if (authConfig.claudeProForTasks !== undefined) {
|
|
112
|
-
const val = authConfig.claudeProForTasks ? 'true' : 'false';
|
|
113
|
-
if (/^#?\s*CLAUDE_PRO_FOR_TASKS=/m.test(props))
|
|
114
|
-
props = props.replace(/^#?\s*CLAUDE_PRO_FOR_TASKS=.*/mg, 'CLAUDE_PRO_FOR_TASKS=' + val);
|
|
115
|
-
else
|
|
116
|
-
props = props.trimEnd() + '\nCLAUDE_PRO_FOR_TASKS=' + val + '\n';
|
|
117
|
-
}
|
|
118
|
-
fs.writeFileSync(workspaceProps, props);
|
|
119
|
-
}
|
|
120
|
-
if (githubToken) {
|
|
121
|
-
let props = fs.readFileSync(workspaceProps, 'utf8');
|
|
122
|
-
if (/^#?\s*GITHUB_TOKEN=/m.test(props))
|
|
123
|
-
props = props.replace(/^#?\s*GITHUB_TOKEN=.*/mg, 'GITHUB_TOKEN=' + githubToken);
|
|
124
|
-
else
|
|
125
|
-
props = props.trimEnd() + '\nGITHUB_TOKEN=' + githubToken + '\n';
|
|
126
|
-
fs.writeFileSync(workspaceProps, props);
|
|
127
|
-
}
|
|
128
|
-
if (slackConfig.botToken || slackConfig.appToken) {
|
|
129
|
-
let props = fs.readFileSync(workspaceProps, 'utf8');
|
|
130
|
-
if (slackConfig.botToken) {
|
|
131
|
-
if (/^#?\s*SLACK_BOT_TOKEN=/m.test(props))
|
|
132
|
-
props = props.replace(/^#?\s*SLACK_BOT_TOKEN=.*/mg, 'SLACK_BOT_TOKEN=' + slackConfig.botToken);
|
|
133
|
-
else
|
|
134
|
-
props = props.trimEnd() + '\nSLACK_BOT_TOKEN=' + slackConfig.botToken + '\n';
|
|
135
|
-
}
|
|
136
|
-
if (slackConfig.appToken) {
|
|
137
|
-
if (/^#?\s*SLACK_APP_TOKEN=/m.test(props))
|
|
138
|
-
props = props.replace(/^#?\s*SLACK_APP_TOKEN=.*/mg, 'SLACK_APP_TOKEN=' + slackConfig.appToken);
|
|
139
|
-
else
|
|
140
|
-
props = props.trimEnd() + '\nSLACK_APP_TOKEN=' + slackConfig.appToken + '\n';
|
|
141
|
-
}
|
|
142
|
-
fs.writeFileSync(workspaceProps, props);
|
|
143
|
-
}
|
|
144
|
-
fs.writeFileSync(path.join(workspace, 'startAll.sh'), `#!/usr/bin/env bash
|
|
145
|
-
# Start all valid Sentinel project instances.
|
|
146
|
-
# A valid project must have config/repo-configs; do
|
|
147
|
-
[[ -d "$project_dir" ]] || continue
|
|
148
|
-
name=$(basename "$project_dir")
|
|
149
|
-
echo " $NON_PROJECT " | grep -qw "$name" && continue
|
|
150
|
-
# Auto-generate start.sh / stop.sh if missing (codeDir = $WORKSPACE/code)
|
|
151
|
-
if [[ ! -f "$project_dir/start.sh" ]]; then
|
|
152
|
-
code_dir="$WORKSPACE/code"
|
|
153
|
-
python_bin="$code_dir/.venv/bin/python3"
|
|
154
|
-
sed -e "s|__NAME__|$name|g" -e "s|__CODE_DIR__|$code_dir|g" -e "s|__PYTHON_BIN__|$python_bin|g" << 'STARTSH' > "$project_dir/start.sh"
|
|
155
|
-
#!/usr/bin/env bash
|
|
156
|
-
set -euo pipefail
|
|
157
|
-
DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
158
|
-
PID_FILE="$DIR/sentinel.pid"
|
|
159
|
-
if [[ -f "$PID_FILE" ]] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
|
|
160
|
-
echo "[sentinel] __NAME__ already running (PID $(cat "$PID_FILE"))"
|
|
161
|
-
exit 0
|
|
162
|
-
fi
|
|
163
|
-
pkill -f "sentinel.main --config $DIR/config" 2>/dev/null || true
|
|
164
|
-
rm -f "$PID_FILE"
|
|
165
|
-
WORKSPACE="$(dirname "$DIR")"
|
|
166
|
-
_claude_pro=true
|
|
167
|
-
for _conf in "$WORKSPACE/sentinel.properties" "$DIR/config/sentinel.properties"; do
|
|
168
|
-
if [[ -f "$_conf" ]]; then
|
|
169
|
-
_val=$(grep -iE "^CLAUDE_PRO_FOR_TASKS[[:space:]]*=" "$_conf" 2>/dev/null | tail -1 | cut -d= -f2- | tr -d ' ' | tr '[:upper:]' '[:lower:]' || true)
|
|
170
|
-
[[ -n "$_val" ]] && _claude_pro="$_val"
|
|
171
|
-
fi
|
|
172
|
-
done
|
|
173
|
-
if [[ "$_claude_pro" != "false" ]]; then
|
|
174
|
-
AUTH_OUT=$(claude --print \"hi\" 2>&1 || true)
|
|
175
|
-
if echo "$AUTH_OUT" | grep -Eqi "not logged in|/login"; then
|
|
176
|
-
echo "[sentinel] Claude Code is not authenticated. Run: claude then /login"
|
|
177
|
-
exit 1
|
|
178
|
-
fi
|
|
179
|
-
fi
|
|
180
|
-
mkdir -p "$DIR/logs" "$WORKSPACE/logs" "$DIR/workspace/fetched" "$DIR/workspace/patches" "$DIR/issues"
|
|
181
|
-
cd "$DIR"
|
|
182
|
-
PYTHONPATH="__CODE_DIR__" "__PYTHON_BIN__" -m sentinel.main --config ./config \
|
|
183
|
-
> "$DIR/logs/sentinel.log" 2>&1 &
|
|
184
|
-
echo $! > "$PID_FILE"
|
|
185
|
-
echo "[sentinel] __NAME__ started (PID $!)"
|
|
186
|
-
echo " project log : $DIR/logs/sentinel.log"
|
|
187
|
-
echo " workspace log: $WORKSPACE/logs/sentinel.log"
|
|
188
|
-
STARTSH
|
|
189
|
-
chmod +x "$project_dir/start.sh"
|
|
190
|
-
echo "[sentinel] Auto-generated start.sh for $name"
|
|
191
|
-
fi
|
|
192
|
-
if [[ ! -f "$project_dir/stop.sh" ]]; then
|
|
193
|
-
sed -e "s|__NAME__|$name|g" << 'STOPSH' > "$project_dir/stop.sh"
|
|
194
|
-
#!/usr/bin/env bash
|
|
195
|
-
set -euo pipefail
|
|
196
|
-
DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
197
|
-
PID_FILE="$DIR/sentinel.pid"
|
|
198
|
-
if [[ ! -f "$PID_FILE" ]]; then
|
|
199
|
-
echo "[sentinel] __NAME__ — no PID file, not running"
|
|
200
|
-
exit 0
|
|
201
|
-
fi
|
|
202
|
-
PID=$(cat "$PID_FILE")
|
|
203
|
-
if kill -0 "$PID" 2>/dev/null; then
|
|
204
|
-
kill "$PID"
|
|
205
|
-
echo "[sentinel] __NAME__ stopped (PID $PID)"
|
|
206
|
-
else
|
|
207
|
-
echo "[sentinel] __NAME__ — PID $PID not running"
|
|
208
|
-
fi
|
|
209
|
-
rm -f "$PID_FILE"
|
|
210
|
-
STOPSH
|
|
211
|
-
chmod +x "$project_dir/stop.sh"
|
|
212
|
-
echo "[sentinel] Auto-generated stop.sh for $name"
|
|
213
|
-
fi
|
|
214
|
-
# Must have at least one repo-config with a valid GitHub REPO_URL
|
|
215
|
-
repo_configs_dir="$project_dir/config/repo-configs"
|
|
216
|
-
if [[ ! -d "$repo_configs_dir" ]]; then
|
|
217
|
-
echo "[sentinel] Skipping $name — config/repo-configs/ directory not found"
|
|
218
|
-
skipped=$((skipped + 1))
|
|
219
|
-
continue
|
|
220
|
-
fi
|
|
221
|
-
has_config=false
|
|
222
|
-
valid_repo=false
|
|
223
|
-
for props in "$repo_configs_dir/"*.properties; do
|
|
224
|
-
[[ -f "$props" ]] || continue
|
|
225
|
-
[[ "$(basename "$props")" == _* ]] && continue
|
|
226
|
-
has_config=true
|
|
227
|
-
if grep -qE "^REPO_URL[[:space:]]*=[[:space:]]*(git@github\.com:|https://github\.com/)" "$props"; then
|
|
228
|
-
valid_repo=true
|
|
229
|
-
break
|
|
230
|
-
else
|
|
231
|
-
repo_url=$(grep -E "^REPO_URL[[:space:]]*=" "$props" | head -1 | cut -d= -f2- | xargs 2>/dev/null || true)
|
|
232
|
-
if [[ -z "$repo_url" ]]; then
|
|
233
|
-
echo "[sentinel] Skipping $name — REPO_URL not set in $(basename \"$props\")"
|
|
234
|
-
else
|
|
235
|
-
echo "[sentinel] Skipping $name — REPO_URL in $(basename \"$props\") is not a GitHub URL: $repo_url"
|
|
236
|
-
fi
|
|
237
|
-
fi
|
|
238
|
-
done
|
|
239
|
-
if [[ "$has_config" == "false" ]]; then
|
|
240
|
-
echo "[sentinel] Skipping $name — no .properties files in config/repo-configs/ (only _example?)"
|
|
241
|
-
skipped=$((skipped + 1))
|
|
242
|
-
continue
|
|
243
|
-
fi
|
|
244
|
-
if [[ "$valid_repo" == "false" ]]; then
|
|
245
|
-
skipped=$((skipped + 1))
|
|
246
|
-
continue
|
|
247
|
-
fi
|
|
248
|
-
if bash "$project_dir/start.sh"; then
|
|
249
|
-
started=$((started + 1))
|
|
250
|
-
else
|
|
251
|
-
echo "[sentinel] Failed to start $name"
|
|
252
|
-
skipped=$((skipped + 1))
|
|
253
|
-
fi
|
|
254
|
-
done
|
|
255
|
-
echo "[sentinel] $started project(s) started, $skipped skipped"
|
|
256
|
-
`, { mode: 0o755 });
|
|
257
|
-
// stopAll.sh
|
|
258
|
-
fs.writeFileSync(path.join(workspace, 'stopAll.sh'), `#!/usr/bin/env bash
|
|
259
|
-
# Stop all Sentinel project instances
|
|
260
|
-
WORKSPACE="$(cd "$(dirname "$0")" && pwd)"
|
|
261
|
-
stopped=0
|
|
262
|
-
NON_PROJECT="code repos logs issues workspace"
|
|
263
|
-
for project_dir in "$WORKSPACE"/*/; do
|
|
264
|
-
[[ -d "$project_dir" ]] || continue
|
|
265
|
-
name=$(basename "$project_dir")
|
|
266
|
-
echo " $NON_PROJECT " | grep -qw "$name" && continue
|
|
267
|
-
[[ -f "$project_dir/stop.sh" ]] || continue
|
|
268
|
-
bash "$project_dir/stop.sh"
|
|
269
|
-
stopped=$((stopped + 1))
|
|
270
|
-
done
|
|
271
|
-
echo "[sentinel] $stopped project(s) stopped"
|
|
272
|
-
`, { mode: 0o755 });
|
|
273
|
-
}
|
|
274
|
-
module.exports = { writeExampleProject, generateProjectScripts, generateWorkspaceScripts };
|