@matthesketh/fleet 1.8.1 → 1.11.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/README.md +186 -16
- package/dist/bin/fleet-agent.d.ts +2 -0
- package/dist/bin/fleet-agent.js +7 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +73 -31
- package/dist/commands/add.d.ts +2 -1
- package/dist/commands/add.js +66 -59
- package/dist/commands/audit.d.ts +1 -0
- package/dist/commands/audit.js +144 -0
- package/dist/commands/backup.d.ts +1 -0
- package/dist/commands/backup.js +510 -0
- package/dist/commands/boot-start.d.ts +3 -1
- package/dist/commands/boot-start.js +39 -47
- package/dist/commands/completions.d.ts +6 -0
- package/dist/commands/completions.js +83 -0
- package/dist/commands/config.d.ts +16 -0
- package/dist/commands/config.js +96 -0
- package/dist/commands/deploy.js +3 -2
- package/dist/commands/deps.js +5 -1
- package/dist/commands/doctor.d.ts +32 -0
- package/dist/commands/doctor.js +186 -0
- package/dist/commands/egress.d.ts +1 -1
- package/dist/commands/egress.js +13 -10
- package/dist/commands/freeze.d.ts +8 -4
- package/dist/commands/freeze.js +77 -59
- package/dist/commands/git.js +2 -2
- package/dist/commands/health.d.ts +2 -1
- package/dist/commands/health.js +38 -56
- package/dist/commands/init.d.ts +2 -1
- package/dist/commands/init.js +83 -73
- package/dist/commands/install-mcp.d.ts +3 -1
- package/dist/commands/install-mcp.js +53 -34
- package/dist/commands/list.d.ts +2 -1
- package/dist/commands/list.js +22 -19
- package/dist/commands/logs.js +1 -1
- package/dist/commands/notify.d.ts +1 -0
- package/dist/commands/notify.js +51 -0
- package/dist/commands/patch-systemd.d.ts +7 -1
- package/dist/commands/patch-systemd.js +71 -31
- package/dist/commands/remove.d.ts +3 -1
- package/dist/commands/remove.js +37 -26
- package/dist/commands/restart.d.ts +4 -1
- package/dist/commands/restart.js +17 -20
- package/dist/commands/rollback.d.ts +4 -1
- package/dist/commands/rollback.js +33 -42
- package/dist/commands/secrets.js +157 -9
- package/dist/commands/start.d.ts +4 -1
- package/dist/commands/start.js +17 -20
- package/dist/commands/status.d.ts +1 -1
- package/dist/commands/status.js +21 -26
- package/dist/commands/stop.d.ts +4 -1
- package/dist/commands/stop.js +17 -20
- package/dist/commands/testflight.d.ts +1 -0
- package/dist/commands/testflight.js +193 -0
- package/dist/commands/update.d.ts +16 -0
- package/dist/commands/update.js +95 -0
- package/dist/core/audit/cache.d.ts +4 -0
- package/dist/core/audit/cache.js +37 -0
- package/dist/core/audit/config.d.ts +5 -0
- package/dist/core/audit/config.js +35 -0
- package/dist/core/audit/greenlight.d.ts +11 -0
- package/dist/core/audit/greenlight.js +81 -0
- package/dist/core/audit/reporters/cli.d.ts +3 -0
- package/dist/core/audit/reporters/cli.js +68 -0
- package/dist/core/audit/suppress.d.ts +6 -0
- package/dist/core/audit/suppress.js +37 -0
- package/dist/core/audit/target.d.ts +5 -0
- package/dist/core/audit/target.js +26 -0
- package/dist/core/audit/types.d.ts +54 -0
- package/dist/core/audit/types.js +5 -0
- package/dist/core/backup/browser-api.d.ts +66 -0
- package/dist/core/backup/browser-api.js +197 -0
- package/dist/core/backup/browser-server.d.ts +11 -0
- package/dist/core/backup/browser-server.js +241 -0
- package/dist/core/backup/browser-ui.d.ts +5 -0
- package/dist/core/backup/browser-ui.js +268 -0
- package/dist/core/backup/cloudflare.d.ts +7 -0
- package/dist/core/backup/cloudflare.js +82 -0
- package/dist/core/backup/config.d.ts +9 -0
- package/dist/core/backup/config.js +80 -0
- package/dist/core/backup/detect.d.ts +11 -0
- package/dist/core/backup/detect.js +71 -0
- package/dist/core/backup/dump.d.ts +11 -0
- package/dist/core/backup/dump.js +82 -0
- package/dist/core/backup/index.d.ts +9 -0
- package/dist/core/backup/index.js +9 -0
- package/dist/core/backup/repo.d.ts +71 -0
- package/dist/core/backup/repo.js +256 -0
- package/dist/core/backup/schedule.d.ts +17 -0
- package/dist/core/backup/schedule.js +90 -0
- package/dist/core/backup/sensitive.d.ts +5 -0
- package/dist/core/backup/sensitive.js +37 -0
- package/dist/core/backup/status.d.ts +3 -0
- package/dist/core/backup/status.js +29 -0
- package/dist/core/backup/statuspage.d.ts +23 -0
- package/dist/core/backup/statuspage.js +145 -0
- package/dist/core/backup/system.d.ts +24 -0
- package/dist/core/backup/system.js +209 -0
- package/dist/core/backup/totp.d.ts +16 -0
- package/dist/core/backup/totp.js +116 -0
- package/dist/core/backup/types.d.ts +70 -0
- package/dist/core/backup/types.js +7 -0
- package/dist/core/backup/unlock.d.ts +19 -0
- package/dist/core/backup/unlock.js +69 -0
- package/dist/core/boot-refresh.d.ts +1 -1
- package/dist/core/boot-refresh.js +10 -9
- package/dist/core/deps/actors/pr-creator.d.ts +5 -3
- package/dist/core/deps/actors/pr-creator.js +71 -18
- package/dist/core/deps/collectors/fetch-with-timeout.d.ts +7 -0
- package/dist/core/deps/collectors/fetch-with-timeout.js +16 -0
- package/dist/core/deps/collectors/npm.js +3 -1
- package/dist/core/deps/collectors/vulnerability.d.ts +8 -0
- package/dist/core/deps/collectors/vulnerability.js +31 -2
- package/dist/core/deps/config.js +6 -0
- package/dist/core/deps/scanner.js +1 -1
- package/dist/core/deps/types.d.ts +8 -0
- package/dist/core/env.d.ts +3 -0
- package/dist/core/env.js +11 -0
- package/dist/core/exec.d.ts +1 -0
- package/dist/core/exec.js +4 -0
- package/dist/core/file-lock.d.ts +18 -0
- package/dist/core/file-lock.js +44 -0
- package/dist/core/git-onboard.js +10 -13
- package/dist/core/github.d.ts +3 -1
- package/dist/core/github.js +10 -7
- package/dist/core/logs-policy.d.ts +5 -0
- package/dist/core/logs-policy.js +20 -1
- package/dist/core/operator.d.ts +21 -0
- package/dist/core/operator.js +54 -0
- package/dist/core/registry.d.ts +18 -0
- package/dist/core/registry.js +26 -0
- package/dist/core/routines/schema.d.ts +11 -11
- package/dist/core/routines/schema.js +14 -3
- package/dist/core/routines/store.d.ts +8 -8
- package/dist/core/secrets-ops.d.ts +31 -6
- package/dist/core/secrets-ops.js +208 -102
- package/dist/core/secrets-providers.js +2 -2
- package/dist/core/secrets-rotation.d.ts +1 -1
- package/dist/core/secrets-rotation.js +58 -52
- package/dist/core/secrets-v2-cleanup.d.ts +19 -0
- package/dist/core/secrets-v2-cleanup.js +94 -0
- package/dist/core/secrets-v2-creds.d.ts +9 -0
- package/dist/core/secrets-v2-creds.js +44 -0
- package/dist/core/secrets-v2-install.d.ts +13 -0
- package/dist/core/secrets-v2-install.js +76 -0
- package/dist/core/secrets-v2-keypair.d.ts +10 -0
- package/dist/core/secrets-v2-keypair.js +31 -0
- package/dist/core/secrets-v2-migrate.d.ts +29 -0
- package/dist/core/secrets-v2-migrate.js +395 -0
- package/dist/core/secrets-v2-ops.d.ts +36 -0
- package/dist/core/secrets-v2-ops.js +184 -0
- package/dist/core/secrets-v2-protocol.d.ts +19 -0
- package/dist/core/secrets-v2-protocol.js +60 -0
- package/dist/core/secrets-v2-snapshot.d.ts +36 -0
- package/dist/core/secrets-v2-snapshot.js +115 -0
- package/dist/core/secrets-v2.d.ts +21 -0
- package/dist/core/secrets-v2.js +249 -0
- package/dist/core/secrets.d.ts +39 -4
- package/dist/core/secrets.js +91 -11
- package/dist/core/self-update.d.ts +32 -11
- package/dist/core/self-update.js +52 -14
- package/dist/core/testflight/asc.d.ts +12 -0
- package/dist/core/testflight/asc.js +101 -0
- package/dist/core/testflight/credentials.d.ts +3 -0
- package/dist/core/testflight/credentials.js +35 -0
- package/dist/core/testflight/eas.d.ts +4 -0
- package/dist/core/testflight/eas.js +38 -0
- package/dist/core/testflight/resolve.d.ts +6 -0
- package/dist/core/testflight/resolve.js +44 -0
- package/dist/core/testflight/types.d.ts +13 -0
- package/dist/core/testflight/types.js +3 -0
- package/dist/core/testflight/workflow.d.ts +17 -0
- package/dist/core/testflight/workflow.js +65 -0
- package/dist/core/validate.d.ts +1 -0
- package/dist/core/validate.js +8 -0
- package/dist/mcp/audit-tools.d.ts +2 -0
- package/dist/mcp/audit-tools.js +94 -0
- package/dist/mcp/git-tools.js +1 -1
- package/dist/mcp/registry-bridge.d.ts +10 -0
- package/dist/mcp/registry-bridge.js +65 -0
- package/dist/mcp/secrets-tools.js +2 -2
- package/dist/mcp/server.js +16 -82
- package/dist/mcp/testflight-tools.d.ts +2 -0
- package/dist/mcp/testflight-tools.js +52 -0
- package/dist/registry/context.d.ts +7 -0
- package/dist/registry/context.js +37 -0
- package/dist/registry/index.d.ts +5 -0
- package/dist/registry/index.js +44 -0
- package/dist/registry/parse-args.d.ts +13 -0
- package/dist/registry/parse-args.js +74 -0
- package/dist/registry/registry.d.ts +24 -0
- package/dist/registry/registry.js +26 -0
- package/dist/registry/render.d.ts +3 -0
- package/dist/registry/render.js +29 -0
- package/dist/registry/types.d.ts +50 -0
- package/dist/registry/types.js +1 -0
- package/dist/templates/agent-unit.d.ts +5 -0
- package/dist/templates/agent-unit.js +40 -0
- package/dist/templates/app-unit-edit.d.ts +2 -0
- package/dist/templates/app-unit-edit.js +46 -0
- package/dist/templates/compose-edit.d.ts +2 -0
- package/dist/templates/compose-edit.js +156 -0
- package/dist/templates/nginx.js +11 -0
- package/dist/templates/systemd.js +6 -0
- package/dist/tui/components/ArgForm.d.ts +7 -0
- package/dist/tui/components/ArgForm.js +64 -0
- package/dist/tui/components/ArgForm.test.d.ts +1 -0
- package/dist/tui/components/ArgForm.test.js +19 -0
- package/dist/tui/components/KeyHint.js +5 -0
- package/dist/tui/hooks/use-secrets.d.ts +8 -8
- package/dist/tui/hooks/use-secrets.js +7 -7
- package/dist/tui/router.d.ts +1 -0
- package/dist/tui/router.js +26 -9
- package/dist/tui/router.test.d.ts +1 -0
- package/dist/tui/router.test.js +13 -0
- package/dist/tui/routines/components/SignalsGrid.test.js +2 -2
- package/dist/tui/routines/tabs/ScaffoldTab.js +1 -1
- package/dist/tui/tests/redaction-rerender.test.d.ts +1 -0
- package/dist/tui/tests/redaction-rerender.test.js +53 -0
- package/dist/tui/tests/scroll-flicker-proof.test.d.ts +1 -0
- package/dist/tui/tests/scroll-flicker-proof.test.js +145 -0
- package/dist/tui/types.d.ts +1 -1
- package/dist/tui/views/CommandPalette.d.ts +5 -0
- package/dist/tui/views/CommandPalette.js +90 -0
- package/dist/tui/views/CommandPalette.test.d.ts +1 -0
- package/dist/tui/views/CommandPalette.test.js +117 -0
- package/dist/tui/views/Dashboard.js +9 -6
- package/dist/tui/views/HealthView.js +9 -4
- package/dist/tui/views/SecretEdit.js +15 -16
- package/dist/tui/views/SecretEdit.test.d.ts +1 -0
- package/dist/tui/views/SecretEdit.test.js +82 -0
- package/dist/tui/views/SecretsView.js +26 -16
- package/package.json +8 -5
|
@@ -33,7 +33,7 @@ export type BuildResult = {
|
|
|
33
33
|
reason: 'build-failed';
|
|
34
34
|
};
|
|
35
35
|
export declare function buildIfStale(app: AppEntry, currentHead: string): BuildResult;
|
|
36
|
-
export declare function recordBuiltCommit(appName: string, commit: string): void
|
|
36
|
+
export declare function recordBuiltCommit(appName: string, commit: string): Promise<void>;
|
|
37
37
|
export declare const KILL_SWITCH = "/etc/fleet/no-auto-refresh";
|
|
38
38
|
export declare const DEFAULT_WALL_CLOCK_MS = 900000;
|
|
39
39
|
export type RefreshResult = {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { isGitRepo, getGitStatus } from './git.js';
|
|
3
3
|
import { execGit } from './exec.js';
|
|
4
|
-
import {
|
|
4
|
+
import { withRegistry } from './registry.js';
|
|
5
5
|
import { composeBuild } from './docker.js';
|
|
6
6
|
export function preflight(projectRoot) {
|
|
7
7
|
if (!isGitRepo(projectRoot))
|
|
@@ -53,13 +53,14 @@ export function buildIfStale(app, currentHead) {
|
|
|
53
53
|
return { ok: false, reason: 'build-failed' };
|
|
54
54
|
return { ok: true, built: true };
|
|
55
55
|
}
|
|
56
|
-
export function recordBuiltCommit(appName, commit) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
export async function recordBuiltCommit(appName, commit) {
|
|
57
|
+
await withRegistry(reg => {
|
|
58
|
+
const i = reg.apps.findIndex(a => a.name === appName);
|
|
59
|
+
if (i < 0)
|
|
60
|
+
return reg;
|
|
61
|
+
reg.apps[i] = { ...reg.apps[i], lastBuiltCommit: commit };
|
|
62
|
+
return reg;
|
|
63
|
+
});
|
|
63
64
|
}
|
|
64
65
|
export const KILL_SWITCH = '/etc/fleet/no-auto-refresh';
|
|
65
66
|
function killSwitchPath() {
|
|
@@ -80,7 +81,7 @@ async function doRefresh(app) {
|
|
|
80
81
|
if (!build.ok)
|
|
81
82
|
return { kind: 'failed-safe', step: 'build', detail: build.reason };
|
|
82
83
|
if (build.built)
|
|
83
|
-
recordBuiltCommit(app.name, ff.newHead);
|
|
84
|
+
await recordBuiltCommit(app.name, ff.newHead);
|
|
84
85
|
if (!ff.changed && !build.built)
|
|
85
86
|
return { kind: 'no-change', head: ff.newHead };
|
|
86
87
|
return { kind: 'refreshed', head: ff.newHead, built: build.built };
|
|
@@ -2,13 +2,15 @@ import type { AppEntry } from '../../registry.js';
|
|
|
2
2
|
import type { Finding } from '../types.js';
|
|
3
3
|
export interface VersionBump {
|
|
4
4
|
file: string;
|
|
5
|
-
|
|
5
|
+
searchRegex: RegExp;
|
|
6
6
|
replace: string;
|
|
7
7
|
}
|
|
8
8
|
export declare function generateVersionBump(finding: Finding): VersionBump | null;
|
|
9
9
|
export declare function buildPrBody(findings: Finding[]): string;
|
|
10
|
-
export
|
|
10
|
+
export interface CreateDepsPrResult {
|
|
11
11
|
branch: string;
|
|
12
12
|
bumps: VersionBump[];
|
|
13
13
|
prUrl?: string;
|
|
14
|
-
|
|
14
|
+
error?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function createDepsPr(app: AppEntry, findings: Finding[], dryRun: boolean): CreateDepsPrResult;
|
|
@@ -1,34 +1,41 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { execSafe } from '../../exec.js';
|
|
4
|
+
import { getGitStatus } from '../../git.js';
|
|
5
|
+
function escapeRegex(s) {
|
|
6
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
7
|
+
}
|
|
4
8
|
export function generateVersionBump(finding) {
|
|
5
9
|
if (!finding.fixable || !finding.package || !finding.currentVersion || !finding.latestVersion) {
|
|
6
10
|
return null;
|
|
7
11
|
}
|
|
12
|
+
const escName = escapeRegex(finding.package);
|
|
13
|
+
const escCurrent = escapeRegex(finding.currentVersion);
|
|
8
14
|
switch (finding.source) {
|
|
9
15
|
case 'npm':
|
|
10
16
|
return {
|
|
11
17
|
file: 'package.json',
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
// capture optional leading range operator (^, ~, >=, <=, >, <, =) so it survives the rewrite
|
|
19
|
+
searchRegex: new RegExp(`("${escName}"\\s*:\\s*")([\\^~>=<]*)${escCurrent}(")`),
|
|
20
|
+
replace: `$1$2${finding.latestVersion}$3`,
|
|
14
21
|
};
|
|
15
22
|
case 'composer':
|
|
16
23
|
return {
|
|
17
24
|
file: 'composer.json',
|
|
18
|
-
|
|
19
|
-
replace:
|
|
25
|
+
searchRegex: new RegExp(`("${escName}"\\s*:\\s*")([\\^~>=<]*)${escCurrent}(")`),
|
|
26
|
+
replace: `$1$2${finding.latestVersion}$3`,
|
|
20
27
|
};
|
|
21
28
|
case 'pip':
|
|
22
29
|
return {
|
|
23
30
|
file: 'requirements.txt',
|
|
24
|
-
|
|
25
|
-
replace: `${finding.
|
|
31
|
+
searchRegex: new RegExp(`(${escName}==)${escCurrent}\\b`),
|
|
32
|
+
replace: `$1${finding.latestVersion}`,
|
|
26
33
|
};
|
|
27
34
|
case 'docker-image':
|
|
28
35
|
return {
|
|
29
36
|
file: 'Dockerfile',
|
|
30
|
-
|
|
31
|
-
replace: `${finding.
|
|
37
|
+
searchRegex: new RegExp(`(${escName}:)${escCurrent}\\b`),
|
|
38
|
+
replace: `$1${finding.latestVersion}`,
|
|
32
39
|
};
|
|
33
40
|
default:
|
|
34
41
|
return null;
|
|
@@ -74,25 +81,71 @@ export function createDepsPr(app, findings, dryRun) {
|
|
|
74
81
|
if (dryRun) {
|
|
75
82
|
return { branch, bumps };
|
|
76
83
|
}
|
|
84
|
+
// Working-tree precheck: refuse to operate on a dirty repo. Otherwise the
|
|
85
|
+
// checkout/pull below can fail mid-way and leave the repo in a partial state,
|
|
86
|
+
// or worse, run on stale content.
|
|
87
|
+
const status = getGitStatus(app.composePath);
|
|
88
|
+
if (!status.initialised) {
|
|
89
|
+
return { branch: '', bumps: [], error: `${app.composePath} is not a git repo` };
|
|
90
|
+
}
|
|
91
|
+
if (!status.clean) {
|
|
92
|
+
return {
|
|
93
|
+
branch: '',
|
|
94
|
+
bumps: [],
|
|
95
|
+
error: `working tree at ${app.composePath} is dirty (${status.staged} staged, ${status.modified} modified, ${status.untracked} untracked) — commit or stash before running deps fix`,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
77
98
|
const sshEnv = process.env.SSH_AUTH_SOCK ? { SSH_AUTH_SOCK: process.env.SSH_AUTH_SOCK } : {};
|
|
78
|
-
execSafe('git', ['checkout', 'develop'], { cwd: app.composePath });
|
|
79
|
-
|
|
80
|
-
|
|
99
|
+
const checkoutDevelop = execSafe('git', ['checkout', 'develop'], { cwd: app.composePath });
|
|
100
|
+
if (!checkoutDevelop.ok) {
|
|
101
|
+
return { branch: '', bumps: [], error: `git checkout develop failed: ${checkoutDevelop.stderr}` };
|
|
102
|
+
}
|
|
103
|
+
const pull = execSafe('git', ['pull'], { cwd: app.composePath, env: sshEnv });
|
|
104
|
+
if (!pull.ok) {
|
|
105
|
+
return { branch: '', bumps: [], error: `git pull failed: ${pull.stderr}` };
|
|
106
|
+
}
|
|
107
|
+
const checkoutBranch = execSafe('git', ['checkout', '-b', branch], { cwd: app.composePath });
|
|
108
|
+
if (!checkoutBranch.ok) {
|
|
109
|
+
return { branch: '', bumps: [], error: `git checkout -b ${branch} failed: ${checkoutBranch.stderr}` };
|
|
110
|
+
}
|
|
111
|
+
// Apply each bump and track which files actually changed.
|
|
112
|
+
const changedFiles = new Set();
|
|
81
113
|
for (const bump of bumps) {
|
|
82
114
|
const filePath = join(app.composePath, bump.file);
|
|
83
115
|
if (!existsSync(filePath))
|
|
84
116
|
continue;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
117
|
+
const before = readFileSync(filePath, 'utf-8');
|
|
118
|
+
const after = before.replace(bump.searchRegex, bump.replace);
|
|
119
|
+
if (after !== before) {
|
|
120
|
+
writeFileSync(filePath, after);
|
|
121
|
+
changedFiles.add(bump.file);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Range mismatch / unknown file content / nothing to update — abort before
|
|
125
|
+
// committing or pushing so we never open an empty PR.
|
|
126
|
+
if (changedFiles.size === 0) {
|
|
127
|
+
return {
|
|
128
|
+
branch: '',
|
|
129
|
+
bumps: [],
|
|
130
|
+
error: `no files changed: regex did not match any of ${[...new Set(bumps.map(b => b.file))].join(', ')}; the manifest may already be at the target version, or the version range syntax is unsupported`,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const files = [...changedFiles];
|
|
134
|
+
const add = execSafe('git', ['add', ...files], { cwd: app.composePath });
|
|
135
|
+
if (!add.ok) {
|
|
136
|
+
return { branch: '', bumps: [], error: `git add failed: ${add.stderr}` };
|
|
88
137
|
}
|
|
89
|
-
const files = [...new Set(bumps.map(b => b.file))];
|
|
90
|
-
execSafe('git', ['add', ...files], { cwd: app.composePath });
|
|
91
138
|
const commitMsg = bumps.length === 1
|
|
92
139
|
? `chore(deps): update ${fixable[0].package} from ${fixable[0].currentVersion} to ${fixable[0].latestVersion}`
|
|
93
140
|
: `chore(deps): update ${bumps.length} dependencies`;
|
|
94
|
-
execSafe('git', ['commit', '-m', commitMsg], { cwd: app.composePath });
|
|
95
|
-
|
|
141
|
+
const commit = execSafe('git', ['commit', '-m', commitMsg], { cwd: app.composePath });
|
|
142
|
+
if (!commit.ok) {
|
|
143
|
+
return { branch: '', bumps: [], error: `git commit failed: ${commit.stderr}` };
|
|
144
|
+
}
|
|
145
|
+
const push = execSafe('git', ['push', '-u', 'origin', branch], { cwd: app.composePath, env: sshEnv });
|
|
146
|
+
if (!push.ok) {
|
|
147
|
+
return { branch: '', bumps: [], error: `git push failed: ${push.stderr}` };
|
|
148
|
+
}
|
|
96
149
|
if (!app.gitRepo)
|
|
97
150
|
return { branch, bumps };
|
|
98
151
|
const prBody = buildPrBody(fixable);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fetch wrapper that aborts after `timeoutMs`.
|
|
3
|
+
*
|
|
4
|
+
* Used by collectors that hit third-party HTTP services (OSV, npm registry).
|
|
5
|
+
* Without this, a hung peer would stall an entire scan indefinitely.
|
|
6
|
+
*/
|
|
7
|
+
export declare function fetchWithTimeout(url: string, init?: RequestInit, timeoutMs?: number): Promise<Response>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fetch wrapper that aborts after `timeoutMs`.
|
|
3
|
+
*
|
|
4
|
+
* Used by collectors that hit third-party HTTP services (OSV, npm registry).
|
|
5
|
+
* Without this, a hung peer would stall an entire scan indefinitely.
|
|
6
|
+
*/
|
|
7
|
+
export async function fetchWithTimeout(url, init = {}, timeoutMs = 10_000) {
|
|
8
|
+
const controller = new AbortController();
|
|
9
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
10
|
+
try {
|
|
11
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
12
|
+
}
|
|
13
|
+
finally {
|
|
14
|
+
clearTimeout(timer);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { readFileSync, existsSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { severityFromVersionDelta } from '../severity.js';
|
|
4
|
+
import { fetchWithTimeout } from './fetch-with-timeout.js';
|
|
5
|
+
const NPM_REGISTRY_TIMEOUT_MS = 10_000;
|
|
4
6
|
export class NpmCollector {
|
|
5
7
|
overrides;
|
|
6
8
|
type = 'npm';
|
|
@@ -37,7 +39,7 @@ export class NpmCollector {
|
|
|
37
39
|
async checkPackage(appName, name, currentRaw) {
|
|
38
40
|
const current = currentRaw.replace(/^[^\d]*/, '');
|
|
39
41
|
try {
|
|
40
|
-
const res = await
|
|
42
|
+
const res = await fetchWithTimeout(`https://registry.npmjs.org/${encodeURIComponent(name)}/latest`, {}, NPM_REGISTRY_TIMEOUT_MS);
|
|
41
43
|
if (!res.ok)
|
|
42
44
|
return null;
|
|
43
45
|
const data = await res.json();
|
|
@@ -2,6 +2,14 @@ import type { AppEntry } from '../../registry.js';
|
|
|
2
2
|
import type { Collector, Finding } from '../types.js';
|
|
3
3
|
export declare class VulnerabilityCollector implements Collector {
|
|
4
4
|
type: "vulnerability";
|
|
5
|
+
private skipRegexes;
|
|
6
|
+
/**
|
|
7
|
+
* @param osvSkipPatterns regex strings matched against bare package names.
|
|
8
|
+
* Matching packages are NOT sent to OSV (https://api.osv.dev), which is a
|
|
9
|
+
* third-party service. This prevents leaking private/internal package
|
|
10
|
+
* manifests to a third party.
|
|
11
|
+
*/
|
|
12
|
+
constructor(osvSkipPatterns?: string[]);
|
|
5
13
|
detect(appPath: string): boolean;
|
|
6
14
|
collect(app: AppEntry): Promise<Finding[]>;
|
|
7
15
|
private extractPackages;
|
|
@@ -1,8 +1,29 @@
|
|
|
1
1
|
import { readFileSync, existsSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { severityFromCvss } from '../severity.js';
|
|
4
|
+
import { fetchWithTimeout } from './fetch-with-timeout.js';
|
|
5
|
+
const OSV_TIMEOUT_MS = 10_000;
|
|
4
6
|
export class VulnerabilityCollector {
|
|
5
7
|
type = 'vulnerability';
|
|
8
|
+
skipRegexes;
|
|
9
|
+
/**
|
|
10
|
+
* @param osvSkipPatterns regex strings matched against bare package names.
|
|
11
|
+
* Matching packages are NOT sent to OSV (https://api.osv.dev), which is a
|
|
12
|
+
* third-party service. This prevents leaking private/internal package
|
|
13
|
+
* manifests to a third party.
|
|
14
|
+
*/
|
|
15
|
+
constructor(osvSkipPatterns = []) {
|
|
16
|
+
this.skipRegexes = osvSkipPatterns
|
|
17
|
+
.map(p => {
|
|
18
|
+
try {
|
|
19
|
+
return new RegExp(p);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
.filter((r) => r !== null);
|
|
26
|
+
}
|
|
6
27
|
detect(appPath) {
|
|
7
28
|
return (existsSync(join(appPath, 'package.json')) ||
|
|
8
29
|
existsSync(join(appPath, 'composer.json')) ||
|
|
@@ -63,18 +84,26 @@ export class VulnerabilityCollector {
|
|
|
63
84
|
}
|
|
64
85
|
catch { /* skip */ }
|
|
65
86
|
}
|
|
87
|
+
// Filter out packages that match any configured skip pattern. OSV is a
|
|
88
|
+
// third-party service (api.osv.dev, operated by Google), so the request
|
|
89
|
+
// payload is the package name + version. Internal/private package names
|
|
90
|
+
// would otherwise be exfiltrated. The default config skips the user's
|
|
91
|
+
// own npm scope.
|
|
92
|
+
if (this.skipRegexes.length > 0) {
|
|
93
|
+
return packages.filter(p => !this.skipRegexes.some(rx => rx.test(p.name)));
|
|
94
|
+
}
|
|
66
95
|
return packages;
|
|
67
96
|
}
|
|
68
97
|
async queryOsv(appName, pkg) {
|
|
69
98
|
try {
|
|
70
|
-
const res = await
|
|
99
|
+
const res = await fetchWithTimeout('https://api.osv.dev/v1/query', {
|
|
71
100
|
method: 'POST',
|
|
72
101
|
headers: { 'Content-Type': 'application/json' },
|
|
73
102
|
body: JSON.stringify({
|
|
74
103
|
version: pkg.version,
|
|
75
104
|
package: { name: pkg.name, ecosystem: pkg.ecosystem },
|
|
76
105
|
}),
|
|
77
|
-
});
|
|
106
|
+
}, OSV_TIMEOUT_MS);
|
|
78
107
|
if (!res.ok)
|
|
79
108
|
return [];
|
|
80
109
|
const data = await res.json();
|
package/dist/core/deps/config.js
CHANGED
|
@@ -21,6 +21,12 @@ export function defaultConfig() {
|
|
|
21
21
|
minorVersionBehind: 'medium',
|
|
22
22
|
patchVersionBehind: 'low',
|
|
23
23
|
},
|
|
24
|
+
// Skip OSV lookups for the user's own npm scope by default — OSV is a
|
|
25
|
+
// third-party service (Google) and sending internal package names there
|
|
26
|
+
// leaks the proprietary dependency manifest. Backwards compat: if an
|
|
27
|
+
// existing deps-config.json is missing this field, mergeConfig fills it
|
|
28
|
+
// in from these defaults.
|
|
29
|
+
osvSkipPatterns: ['^@matthesketh/'],
|
|
24
30
|
};
|
|
25
31
|
}
|
|
26
32
|
export function mergeConfig(base, overrides) {
|
|
@@ -14,7 +14,7 @@ export function createCollectors(config) {
|
|
|
14
14
|
new DockerImageCollector(config.severityOverrides),
|
|
15
15
|
new DockerRunningCollector(config.severityOverrides),
|
|
16
16
|
new EolCollector(config.severityOverrides.eolDaysWarning),
|
|
17
|
-
new VulnerabilityCollector(),
|
|
17
|
+
new VulnerabilityCollector(config.osvSkipPatterns),
|
|
18
18
|
new GitHubPrCollector(),
|
|
19
19
|
];
|
|
20
20
|
}
|
|
@@ -48,6 +48,14 @@ export interface DepsConfig {
|
|
|
48
48
|
minorVersionBehind: Severity;
|
|
49
49
|
patchVersionBehind: Severity;
|
|
50
50
|
};
|
|
51
|
+
/**
|
|
52
|
+
* Regex strings (matched against bare package name) of packages that must
|
|
53
|
+
* NOT be queried against OSV (https://api.osv.dev). OSV is a third-party
|
|
54
|
+
* service operated by Google; sending private/internal package names there
|
|
55
|
+
* leaks the proprietary dependency manifest. Default skips the user's own
|
|
56
|
+
* npm scope.
|
|
57
|
+
*/
|
|
58
|
+
osvSkipPatterns: string[];
|
|
51
59
|
}
|
|
52
60
|
export interface DepsCache {
|
|
53
61
|
version: 1;
|
package/dist/core/env.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { FleetError } from './errors.js';
|
|
2
|
+
/** returns a required env var, or throws a clear error. use for secrets,
|
|
3
|
+
* keys and credential paths where a silent default would be dangerous. */
|
|
4
|
+
export function requireEnv(name) {
|
|
5
|
+
const value = process.env[name];
|
|
6
|
+
if (value === undefined || value === '') {
|
|
7
|
+
throw new FleetError(`required environment variable ${name} is not set — ` +
|
|
8
|
+
`it has no safe default and must be provided explicitly`);
|
|
9
|
+
}
|
|
10
|
+
return value;
|
|
11
|
+
}
|
package/dist/core/exec.d.ts
CHANGED
package/dist/core/exec.js
CHANGED
|
@@ -7,6 +7,10 @@ export function execSafe(cmd, args, opts = {}) {
|
|
|
7
7
|
encoding: 'utf-8',
|
|
8
8
|
stdio: 'pipe',
|
|
9
9
|
input: opts.input,
|
|
10
|
+
// node's default is 1mb. restic --json on a long-running snapshot emits
|
|
11
|
+
// hundreds of progress lines that easily blow past that; bump to 256mb
|
|
12
|
+
// so a multi-hour run can't hit ENOBUFS just from status chatter.
|
|
13
|
+
maxBuffer: opts.maxBuffer ?? 256 * 1024 * 1024,
|
|
10
14
|
});
|
|
11
15
|
if (result.error) {
|
|
12
16
|
return {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inter-process lock around a state-file path. Uses proper-lockfile (the same
|
|
3
|
+
* dependency the claude-cli runner uses for its mutex), which creates a
|
|
4
|
+
* <path>.lock directory atomically via mkdir(2).
|
|
5
|
+
*
|
|
6
|
+
* The wrapped path itself does not need to exist yet — `realpath: false`
|
|
7
|
+
* tells proper-lockfile to skip the realpath check, so we can lock around a
|
|
8
|
+
* registry/manifest file that hasn't been written for the first time. The
|
|
9
|
+
* parent directory of <path> must exist (we ensureDir below) so the .lock
|
|
10
|
+
* mkdir can succeed.
|
|
11
|
+
*
|
|
12
|
+
* Important: this lock is NOT reentrant. Callers should wrap the outermost
|
|
13
|
+
* read-modify-write boundary (e.g. a CLI command, an MCP tool handler, a
|
|
14
|
+
* cron entry) and let inner helpers do plain unlocked reads/writes; the lock
|
|
15
|
+
* bounds the whole RMW. Locking inside helpers that the outer caller already
|
|
16
|
+
* locked will deadlock.
|
|
17
|
+
*/
|
|
18
|
+
export declare function withFileLock<T>(path: string, fn: () => Promise<T> | T): Promise<T>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import lockfile from 'proper-lockfile';
|
|
4
|
+
/**
|
|
5
|
+
* Inter-process lock around a state-file path. Uses proper-lockfile (the same
|
|
6
|
+
* dependency the claude-cli runner uses for its mutex), which creates a
|
|
7
|
+
* <path>.lock directory atomically via mkdir(2).
|
|
8
|
+
*
|
|
9
|
+
* The wrapped path itself does not need to exist yet — `realpath: false`
|
|
10
|
+
* tells proper-lockfile to skip the realpath check, so we can lock around a
|
|
11
|
+
* registry/manifest file that hasn't been written for the first time. The
|
|
12
|
+
* parent directory of <path> must exist (we ensureDir below) so the .lock
|
|
13
|
+
* mkdir can succeed.
|
|
14
|
+
*
|
|
15
|
+
* Important: this lock is NOT reentrant. Callers should wrap the outermost
|
|
16
|
+
* read-modify-write boundary (e.g. a CLI command, an MCP tool handler, a
|
|
17
|
+
* cron entry) and let inner helpers do plain unlocked reads/writes; the lock
|
|
18
|
+
* bounds the whole RMW. Locking inside helpers that the outer caller already
|
|
19
|
+
* locked will deadlock.
|
|
20
|
+
*/
|
|
21
|
+
export async function withFileLock(path, fn) {
|
|
22
|
+
const dir = dirname(path);
|
|
23
|
+
if (!existsSync(dir))
|
|
24
|
+
mkdirSync(dir, { recursive: true });
|
|
25
|
+
const release = await lockfile.lock(path, {
|
|
26
|
+
// Inter-process contention is normally microseconds (one process writes,
|
|
27
|
+
// releases). Retry up to ~5s of backoff so a slow disk / paused process
|
|
28
|
+
// doesn't immediately error out the second caller.
|
|
29
|
+
retries: { retries: 5, factor: 1.5, minTimeout: 50, maxTimeout: 500 },
|
|
30
|
+
// If a process crashes mid-lock, the .lock dir's mtime stops being
|
|
31
|
+
// refreshed. Anyone waiting longer than `stale` ms treats the lock as
|
|
32
|
+
// abandoned and steals it. 30s is generous for the kinds of operations
|
|
33
|
+
// that touch the registry/manifest (a write is < 100ms typically).
|
|
34
|
+
stale: 30_000,
|
|
35
|
+
// Allow locking paths that don't exist on disk yet (first-write case).
|
|
36
|
+
realpath: false,
|
|
37
|
+
});
|
|
38
|
+
try {
|
|
39
|
+
return await fn();
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
await release();
|
|
43
|
+
}
|
|
44
|
+
}
|
package/dist/core/git-onboard.js
CHANGED
|
@@ -4,23 +4,20 @@ import { load, findApp, save } from './registry.js';
|
|
|
4
4
|
export function detectScenario(status) {
|
|
5
5
|
if (!status.initialised)
|
|
6
6
|
return 'fresh';
|
|
7
|
-
if (status.remoteUrl && status.remoteUrl.includes('heskethwebdesign/'))
|
|
8
|
-
return 'resume';
|
|
9
|
-
if (status.remoteUrl && status.remoteUrl.includes('wrxck/'))
|
|
10
|
-
return 'migrate';
|
|
11
7
|
if (!status.remoteUrl)
|
|
12
8
|
return 'no-remote';
|
|
13
|
-
|
|
9
|
+
// a remote already on the configured org is a resume; any other org is a migrate.
|
|
10
|
+
return status.remoteUrl.includes(`${github.githubOrg()}/`) ? 'resume' : 'migrate';
|
|
14
11
|
}
|
|
15
12
|
export function describeOnboardPlan(scenario, repoName, _status) {
|
|
16
|
-
const repoUrl = `git@github.com
|
|
13
|
+
const repoUrl = `git@github.com:${github.githubOrg()}/${repoName}.git`;
|
|
17
14
|
const steps = [];
|
|
18
15
|
switch (scenario) {
|
|
19
16
|
case 'fresh':
|
|
20
17
|
steps.push('generate .gitignore');
|
|
21
18
|
steps.push('git init -b main');
|
|
22
19
|
steps.push('git add . && git commit -m "initial commit"');
|
|
23
|
-
steps.push(`create private repo
|
|
20
|
+
steps.push(`create private repo ${github.githubOrg()}/${repoName}`);
|
|
24
21
|
steps.push(`add remote origin ${repoUrl}`);
|
|
25
22
|
steps.push('push main');
|
|
26
23
|
steps.push('create and push develop branch');
|
|
@@ -29,7 +26,7 @@ export function describeOnboardPlan(scenario, repoName, _status) {
|
|
|
29
26
|
break;
|
|
30
27
|
case 'migrate':
|
|
31
28
|
steps.push('ensure .gitignore exists');
|
|
32
|
-
steps.push(`create private repo
|
|
29
|
+
steps.push(`create private repo ${github.githubOrg()}/${repoName}`);
|
|
33
30
|
steps.push(`git remote set-url origin ${repoUrl}`);
|
|
34
31
|
steps.push('git push --all origin');
|
|
35
32
|
steps.push('ensure develop branch exists');
|
|
@@ -39,7 +36,7 @@ export function describeOnboardPlan(scenario, repoName, _status) {
|
|
|
39
36
|
case 'no-remote':
|
|
40
37
|
steps.push('ensure .gitignore exists');
|
|
41
38
|
steps.push('commit any outstanding changes');
|
|
42
|
-
steps.push(`create private repo
|
|
39
|
+
steps.push(`create private repo ${github.githubOrg()}/${repoName}`);
|
|
43
40
|
steps.push(`add remote origin ${repoUrl}`);
|
|
44
41
|
steps.push('git push --all origin');
|
|
45
42
|
steps.push('ensure develop branch exists');
|
|
@@ -79,7 +76,7 @@ export function executeOnboard(scenario, cwd, repoName, appName, status) {
|
|
|
79
76
|
gitCommit(cwd, 'Initial commit');
|
|
80
77
|
steps.push('created initial commit');
|
|
81
78
|
github.createRepo(repoName);
|
|
82
|
-
steps.push(`created private repo
|
|
79
|
+
steps.push(`created private repo ${github.githubOrg()}/${repoName}`);
|
|
83
80
|
gitAddRemote(cwd, 'origin', repoUrl);
|
|
84
81
|
gitPush(cwd, 'main', true);
|
|
85
82
|
steps.push('pushed main to origin');
|
|
@@ -90,7 +87,7 @@ export function executeOnboard(scenario, cwd, repoName, appName, status) {
|
|
|
90
87
|
}
|
|
91
88
|
case 'migrate': {
|
|
92
89
|
github.createRepo(repoName);
|
|
93
|
-
steps.push(`created private repo
|
|
90
|
+
steps.push(`created private repo ${github.githubOrg()}/${repoName}`);
|
|
94
91
|
gitSetRemoteUrl(cwd, repoUrl);
|
|
95
92
|
steps.push(`updated remote to ${repoUrl}`);
|
|
96
93
|
gitPushAll(cwd);
|
|
@@ -110,7 +107,7 @@ export function executeOnboard(scenario, cwd, repoName, appName, status) {
|
|
|
110
107
|
steps.push('created initial commit');
|
|
111
108
|
}
|
|
112
109
|
github.createRepo(repoName);
|
|
113
|
-
steps.push(`created private repo
|
|
110
|
+
steps.push(`created private repo ${github.githubOrg()}/${repoName}`);
|
|
114
111
|
gitAddRemote(cwd, 'origin', repoUrl);
|
|
115
112
|
gitPushAll(cwd);
|
|
116
113
|
steps.push('added remote and pushed all branches');
|
|
@@ -139,7 +136,7 @@ export function executeOnboard(scenario, cwd, repoName, appName, status) {
|
|
|
139
136
|
const reg = load();
|
|
140
137
|
const app = findApp(reg, appName);
|
|
141
138
|
if (app) {
|
|
142
|
-
app.gitRepo =
|
|
139
|
+
app.gitRepo = `${github.githubOrg()}/${repoName}`;
|
|
143
140
|
app.gitRemoteUrl = repoUrl;
|
|
144
141
|
app.gitOnboardedAt = new Date().toISOString();
|
|
145
142
|
save(reg);
|
package/dist/core/github.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
/** the GitHub org this fleet instance publishes to — from operator config,
|
|
2
|
+
* with no default: guessing another operator's org is never correct. */
|
|
3
|
+
export declare function githubOrg(): string;
|
|
2
4
|
export interface PullRequest {
|
|
3
5
|
number: number;
|
|
4
6
|
title: string;
|
package/dist/core/github.js
CHANGED
|
@@ -3,8 +3,11 @@ import { tmpdir } from 'node:os';
|
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { execSafe } from './exec.js';
|
|
5
5
|
import { GitError } from './errors.js';
|
|
6
|
+
import { loadOperator } from './operator.js';
|
|
6
7
|
import { assertAppName } from './validate.js';
|
|
7
|
-
|
|
8
|
+
/** the GitHub org this fleet instance publishes to — from operator config,
|
|
9
|
+
* with no default: guessing another operator's org is never correct. */
|
|
10
|
+
export function githubOrg() { return loadOperator().githubOrg; }
|
|
8
11
|
export function isGhAuthenticated() {
|
|
9
12
|
return execSafe('gh', ['auth', 'status'], { timeout: 10_000 }).ok;
|
|
10
13
|
}
|
|
@@ -15,25 +18,25 @@ export function requireGhAuth() {
|
|
|
15
18
|
}
|
|
16
19
|
export function repoExists(name) {
|
|
17
20
|
assertAppName(name);
|
|
18
|
-
return execSafe('gh', ['repo', 'view', `${
|
|
21
|
+
return execSafe('gh', ['repo', 'view', `${githubOrg()}/${name}`, '--json', 'name'], { timeout: 15_000 }).ok;
|
|
19
22
|
}
|
|
20
23
|
export function createRepo(name) {
|
|
21
24
|
requireGhAuth();
|
|
22
25
|
assertAppName(name);
|
|
23
26
|
if (repoExists(name))
|
|
24
27
|
return;
|
|
25
|
-
const r = execSafe('gh', ['repo', 'create', `${
|
|
28
|
+
const r = execSafe('gh', ['repo', 'create', `${githubOrg()}/${name}`, '--private'], { timeout: 30_000 });
|
|
26
29
|
if (!r.ok)
|
|
27
30
|
throw new GitError(`failed to create repo: ${r.stderr}`);
|
|
28
31
|
}
|
|
29
32
|
export function getRepoUrl(name) {
|
|
30
|
-
return `git@github.com:${
|
|
33
|
+
return `git@github.com:${githubOrg()}/${name}.git`;
|
|
31
34
|
}
|
|
32
35
|
export function createPullRequest(repo, opts) {
|
|
33
36
|
requireGhAuth();
|
|
34
37
|
const r = execSafe('gh', [
|
|
35
38
|
'pr', 'create',
|
|
36
|
-
'--repo', `${
|
|
39
|
+
'--repo', `${githubOrg()}/${repo}`,
|
|
37
40
|
'--title', opts.title,
|
|
38
41
|
'--body', opts.body ?? '',
|
|
39
42
|
'--head', opts.head,
|
|
@@ -63,7 +66,7 @@ export function listPullRequests(repo, state = 'open') {
|
|
|
63
66
|
requireGhAuth();
|
|
64
67
|
const r = execSafe('gh', [
|
|
65
68
|
'pr', 'list',
|
|
66
|
-
'--repo', `${
|
|
69
|
+
'--repo', `${githubOrg()}/${repo}`,
|
|
67
70
|
'--state', state,
|
|
68
71
|
'--json', 'number,title,url,headRefName,baseRefName,state',
|
|
69
72
|
], { timeout: 15_000 });
|
|
@@ -97,7 +100,7 @@ export function protectBranch(repo, branch) {
|
|
|
97
100
|
try {
|
|
98
101
|
const r = execSafe('gh', [
|
|
99
102
|
'api', '-X', 'PUT',
|
|
100
|
-
`repos/${
|
|
103
|
+
`repos/${githubOrg()}/${repo}/branches/${branch}/protection`,
|
|
101
104
|
'--input', tmpFile,
|
|
102
105
|
], { timeout: 15_000 });
|
|
103
106
|
return r.ok;
|
|
@@ -18,6 +18,11 @@ export declare function effectivePolicy(app: AppEntry): LogPolicy;
|
|
|
18
18
|
*/
|
|
19
19
|
export declare function buildComposeOverride(app: AppEntry, policy: LogPolicy): string;
|
|
20
20
|
export declare function overridePath(app: AppEntry): string;
|
|
21
|
+
/** ensure the app repo's .gitignore covers .fleet/ so the auto-generated
|
|
22
|
+
* override file doesn't get committed accidentally. no-op when there's no
|
|
23
|
+
* .gitignore (operator may be using a different vcs or none at all) and
|
|
24
|
+
* idempotent — never appends a duplicate entry. */
|
|
25
|
+
export declare function ensureFleetGitignored(composePath: string): void;
|
|
21
26
|
export declare function writeComposeOverride(app: AppEntry, policy: LogPolicy): string;
|
|
22
27
|
export interface LogStatus {
|
|
23
28
|
app: string;
|