@misterhuydo/sentinel 1.3.1 → 1.3.3
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/add.js +66 -10
- package/lib/generate.js +10 -1
- package/package.json +1 -1
package/.cairn/.hint-lock
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2026-03-
|
|
1
|
+
2026-03-23T18:20:09.151Z
|
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-23T18:08:05.159Z",
|
|
3
|
+
"checkpoint_at": "2026-03-23T18:08:05.160Z",
|
|
4
4
|
"active_files": [],
|
|
5
5
|
"notes": [],
|
|
6
6
|
"mtime_snapshot": {}
|
package/lib/add.js
CHANGED
|
@@ -129,7 +129,7 @@ function ensureKnownHosts() {
|
|
|
129
129
|
const content = fs.existsSync(knownHosts) ? fs.readFileSync(knownHosts, 'utf8') : '';
|
|
130
130
|
if (content.includes('github.com')) return;
|
|
131
131
|
info('Adding GitHub to known_hosts…');
|
|
132
|
-
const r = spawnSync('ssh-keyscan', ['github.com'], { encoding: 'utf8', timeout: 10000 });
|
|
132
|
+
const r = spawnSync('ssh-keyscan', ['github.com'], { encoding: 'utf8', timeout: 10000, env: gitEnv() });
|
|
133
133
|
if (r.stdout) fs.appendFileSync(knownHosts, r.stdout);
|
|
134
134
|
}
|
|
135
135
|
|
|
@@ -175,6 +175,15 @@ function printDeployKeyInstructions(orgRepo, keyFile) {
|
|
|
175
175
|
console.log('');
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
// Ensure git/ssh are findable regardless of how Node was launched
|
|
179
|
+
function gitEnv(extra = {}) {
|
|
180
|
+
const PATH = [
|
|
181
|
+
process.env.PATH || '',
|
|
182
|
+
'/usr/bin', '/usr/local/bin', '/bin', '/usr/sbin',
|
|
183
|
+
].filter(Boolean).join(':');
|
|
184
|
+
return { ...process.env, PATH, GIT_TERMINAL_PROMPT: '0', ...extra };
|
|
185
|
+
}
|
|
186
|
+
|
|
178
187
|
// ── repo discovery helpers ────────────────────────────────────────────────────
|
|
179
188
|
|
|
180
189
|
function gitUrlToOrgRepo(gitUrl) {
|
|
@@ -191,16 +200,18 @@ function toHttpsUrl(gitUrl) {
|
|
|
191
200
|
function isPublicRepo(gitUrl) {
|
|
192
201
|
const r = spawnSync('git', ['ls-remote', '--heads', toHttpsUrl(gitUrl)], {
|
|
193
202
|
encoding: 'utf8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'],
|
|
194
|
-
env:
|
|
203
|
+
env: gitEnv(),
|
|
195
204
|
});
|
|
196
205
|
return r.status === 0;
|
|
197
206
|
}
|
|
198
207
|
|
|
199
208
|
function validateAccess(repoUrl, keyFile) {
|
|
200
|
-
const
|
|
201
|
-
|
|
209
|
+
const extra = keyFile
|
|
210
|
+
? { GIT_SSH_COMMAND: `ssh -i ${keyFile} -o StrictHostKeyChecking=no -o BatchMode=yes` }
|
|
211
|
+
: {};
|
|
202
212
|
const r = spawnSync('git', ['ls-remote', '--heads', repoUrl], {
|
|
203
|
-
encoding: 'utf8', timeout: 15000, stdio: ['pipe', 'pipe', 'pipe'],
|
|
213
|
+
encoding: 'utf8', timeout: 15000, stdio: ['pipe', 'pipe', 'pipe'],
|
|
214
|
+
env: gitEnv(extra),
|
|
204
215
|
});
|
|
205
216
|
return { ok: r.status === 0, stderr: (r.stderr || r.error?.message || '').trim() };
|
|
206
217
|
}
|
|
@@ -258,10 +269,10 @@ async function addFromGit(gitUrl, workspace) {
|
|
|
258
269
|
step(`[2/3] Scanning repo-configs in ${repoSlug}…`);
|
|
259
270
|
|
|
260
271
|
if (!fs.existsSync(localPath)) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
272
|
+
spawnSync('git', ['clone', '--depth', '1', gitUrl, localPath], {
|
|
273
|
+
stdio: 'inherit',
|
|
274
|
+
env: gitEnv({ GIT_SSH_COMMAND: `ssh -i ${keyFile} -o StrictHostKeyChecking=no -o BatchMode=yes` }),
|
|
275
|
+
});
|
|
265
276
|
}
|
|
266
277
|
|
|
267
278
|
const discovered = discoverReposFromClone(localPath);
|
|
@@ -364,6 +375,51 @@ async function addFromGit(gitUrl, workspace) {
|
|
|
364
375
|
warn('AUTO_PUBLISH=true: fixes push directly to main. Ensure CI blocks bad pushes.');
|
|
365
376
|
}
|
|
366
377
|
|
|
378
|
+
// ── GitHub token ───────────────────────────────────────────────────────────
|
|
379
|
+
// AUTO_PUBLISH=false → required (open PRs via API)
|
|
380
|
+
// AUTO_PUBLISH=true → optional (CI/CD triggers only)
|
|
381
|
+
const workspaceProps = path.join(workspace, 'sentinel.properties');
|
|
382
|
+
const existingToken = fs.existsSync(workspaceProps)
|
|
383
|
+
? (fs.readFileSync(workspaceProps, 'utf8').match(/^GITHUB_TOKEN\s*=\s*(.+)$/m) || [])[1]?.trim()
|
|
384
|
+
: '';
|
|
385
|
+
|
|
386
|
+
if (!autoPublish) {
|
|
387
|
+
// PR mode — token is required
|
|
388
|
+
console.log('');
|
|
389
|
+
console.log(chalk.bold(' GitHub Personal Access Token (classic) — required for opening PRs'));
|
|
390
|
+
console.log(chalk.cyan(' github.com/settings/tokens/new → Tokens (classic)'));
|
|
391
|
+
console.log(chalk.cyan(' Note: "Expiration → No expiration" Scope: ✓ repo'));
|
|
392
|
+
console.log('');
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const { githubToken } = await prompts({
|
|
396
|
+
type: (!autoPublish || !existingToken) ? 'password' : null,
|
|
397
|
+
name: 'githubToken',
|
|
398
|
+
message: existingToken
|
|
399
|
+
? 'GitHub token (press Enter to keep current)'
|
|
400
|
+
: autoPublish
|
|
401
|
+
? 'GitHub token (classic, repo scope) — optional, press Enter to skip'
|
|
402
|
+
: 'GitHub token (classic, repo scope)',
|
|
403
|
+
validate: v => {
|
|
404
|
+
if (!autoPublish && !v && !existingToken) return 'Token is required for PR mode';
|
|
405
|
+
if (v && !v.startsWith('ghp_') && !v.startsWith('github_pat_')) return 'Should start with ghp_ or github_pat_';
|
|
406
|
+
return true;
|
|
407
|
+
},
|
|
408
|
+
}, { onCancel: () => process.exit(0) });
|
|
409
|
+
|
|
410
|
+
const effectiveToken = githubToken || existingToken || '';
|
|
411
|
+
if (effectiveToken && fs.existsSync(workspaceProps)) {
|
|
412
|
+
let props = fs.readFileSync(workspaceProps, 'utf8');
|
|
413
|
+
if (/^#?\s*GITHUB_TOKEN\s*=/m.test(props))
|
|
414
|
+
props = props.replace(/^#?\s*GITHUB_TOKEN\s*=.*/m, `GITHUB_TOKEN=${effectiveToken}`);
|
|
415
|
+
else
|
|
416
|
+
props = props.trimEnd() + `\nGITHUB_TOKEN=${effectiveToken}\n`;
|
|
417
|
+
fs.writeFileSync(workspaceProps, props);
|
|
418
|
+
ok('GITHUB_TOKEN saved to workspace sentinel.properties');
|
|
419
|
+
} else if (effectiveToken) {
|
|
420
|
+
info('GITHUB_TOKEN will be written when project files are created');
|
|
421
|
+
}
|
|
422
|
+
|
|
367
423
|
// ── Preview + confirm ──────────────────────────────────────────────────────
|
|
368
424
|
const projectDir = path.join(workspace, name);
|
|
369
425
|
|
|
@@ -426,7 +482,7 @@ async function addFromGit(gitUrl, workspace) {
|
|
|
426
482
|
});
|
|
427
483
|
const example = path.join(repoDir, '_example.properties');
|
|
428
484
|
if (fs.existsSync(example)) fs.removeSync(example);
|
|
429
|
-
generateWorkspaceScripts(workspace);
|
|
485
|
+
generateWorkspaceScripts(workspace, {}, {}, {}, effectiveToken);
|
|
430
486
|
ok(`Project "${name}" created at ${projectDir}`);
|
|
431
487
|
printNextSteps(projectDir, autoPublish);
|
|
432
488
|
}
|
package/lib/generate.js
CHANGED
|
@@ -96,7 +96,7 @@ rm -f "$PID_FILE"
|
|
|
96
96
|
|
|
97
97
|
// ── Workspace-level startAll / stopAll ────────────────────────────────────────
|
|
98
98
|
|
|
99
|
-
function generateWorkspaceScripts(workspace, smtpConfig = {}, slackConfig = {}, authConfig = {}) {
|
|
99
|
+
function generateWorkspaceScripts(workspace, smtpConfig = {}, slackConfig = {}, authConfig = {}, githubToken = '') {
|
|
100
100
|
// Write shared sentinel.properties once (never overwrite existing)
|
|
101
101
|
const workspaceProps = path.join(workspace, 'sentinel.properties');
|
|
102
102
|
if (!fs.existsSync(workspaceProps)) {
|
|
@@ -126,6 +126,15 @@ function generateWorkspaceScripts(workspace, smtpConfig = {}, slackConfig = {},
|
|
|
126
126
|
}
|
|
127
127
|
fs.writeFileSync(workspaceProps, props);
|
|
128
128
|
}
|
|
129
|
+
// Always upsert GitHub token so re-runs persist it
|
|
130
|
+
if (githubToken) {
|
|
131
|
+
let props = fs.readFileSync(workspaceProps, 'utf8');
|
|
132
|
+
if (/^#?\s*GITHUB_TOKEN=/m.test(props))
|
|
133
|
+
props = props.replace(/^#?\s*GITHUB_TOKEN=.*/mg, 'GITHUB_TOKEN=' + githubToken);
|
|
134
|
+
else
|
|
135
|
+
props = props.trimEnd() + '\nGITHUB_TOKEN=' + githubToken + '\n';
|
|
136
|
+
fs.writeFileSync(workspaceProps, props);
|
|
137
|
+
}
|
|
129
138
|
// Always upsert Slack tokens so re-runs persist them
|
|
130
139
|
if (slackConfig.botToken || slackConfig.appToken) {
|
|
131
140
|
let props = fs.readFileSync(workspaceProps, 'utf8');
|