@the-open-engine/zeroshot 6.7.1 → 6.7.2
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/cli/index.js +114 -16
- package/lib/git-remote-utils.js +90 -7
- package/package.json +6 -15
- package/protocol/openengine-cluster/v1/worker.schema.json +47 -0
- package/scripts/assert-release-published.js +128 -0
- package/scripts/release-dry-run.js +83 -0
- package/scripts/release-preflight.js +24 -12
- package/scripts/release-recovery.js +149 -0
- package/scripts/semantic-release-notes.js +44 -0
- package/scripts/setup-merge-queue.sh +104 -118
- package/src/agents/git-pusher-template.js +94 -19
- package/src/attach/socket-discovery.js +9 -19
- package/src/attach/socket-paths.js +85 -0
- package/src/isolation-manager.js +164 -74
- package/src/orchestrator.js +83 -34
- package/task-lib/attachable-watcher.js +3 -9
- package/task-lib/commands/run.js +17 -2
- package/task-lib/runner.js +21 -3
|
@@ -7,7 +7,7 @@ const { execFileSync } = require('child_process');
|
|
|
7
7
|
const RELEASE_ORDER = ['patch', 'minor', 'major'];
|
|
8
8
|
const REQUIRED_PLUGINS = [
|
|
9
9
|
'@semantic-release/commit-analyzer',
|
|
10
|
-
'
|
|
10
|
+
'./scripts/semantic-release-notes.js',
|
|
11
11
|
'@semantic-release/npm',
|
|
12
12
|
'@semantic-release/github',
|
|
13
13
|
];
|
|
@@ -50,7 +50,6 @@ function analyzeMessage(message) {
|
|
|
50
50
|
if (breaking) return 'major';
|
|
51
51
|
|
|
52
52
|
switch (type) {
|
|
53
|
-
case 'release':
|
|
54
53
|
case 'feat':
|
|
55
54
|
return 'minor';
|
|
56
55
|
case 'fix':
|
|
@@ -61,6 +60,14 @@ function analyzeMessage(message) {
|
|
|
61
60
|
}
|
|
62
61
|
}
|
|
63
62
|
|
|
63
|
+
function releaseTypeForMessages(messages) {
|
|
64
|
+
let releaseType = null;
|
|
65
|
+
for (const message of messages) {
|
|
66
|
+
releaseType = maxReleaseType(releaseType, analyzeMessage(message));
|
|
67
|
+
}
|
|
68
|
+
return releaseType;
|
|
69
|
+
}
|
|
70
|
+
|
|
64
71
|
function getPluginNames(releaseConfig) {
|
|
65
72
|
return (releaseConfig.plugins || []).map(normalizePlugin);
|
|
66
73
|
}
|
|
@@ -68,9 +75,7 @@ function getPluginNames(releaseConfig) {
|
|
|
68
75
|
function validateReleaseConfig(packageJson) {
|
|
69
76
|
const releaseConfig = packageJson.release;
|
|
70
77
|
if (!releaseConfig || typeof releaseConfig !== 'object') {
|
|
71
|
-
throw new Error(
|
|
72
|
-
'package.json#release is required so it takes precedence over stale .releaserc files'
|
|
73
|
-
);
|
|
78
|
+
throw new Error('package.json#release is required as the single release configuration');
|
|
74
79
|
}
|
|
75
80
|
|
|
76
81
|
const branches = Array.isArray(releaseConfig.branches)
|
|
@@ -103,6 +108,16 @@ function validateReleaseConfig(packageJson) {
|
|
|
103
108
|
throw new Error('@semantic-release/npm must publish to npm');
|
|
104
109
|
}
|
|
105
110
|
|
|
111
|
+
const analyzerPlugin = releaseConfig.plugins.find(
|
|
112
|
+
(plugin) => normalizePlugin(plugin) === '@semantic-release/commit-analyzer'
|
|
113
|
+
);
|
|
114
|
+
const analyzerOptions = Array.isArray(analyzerPlugin) ? analyzerPlugin[1] || {} : {};
|
|
115
|
+
if (Array.isArray(analyzerOptions.releaseRules) && analyzerOptions.releaseRules.length > 0) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
'@semantic-release/commit-analyzer must use the standard conventional release rules'
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
106
121
|
return pluginNames;
|
|
107
122
|
}
|
|
108
123
|
|
|
@@ -186,10 +201,7 @@ async function prTitleFromMergeQueue() {
|
|
|
186
201
|
async function releaseSignal() {
|
|
187
202
|
const tag = latestReleaseTag();
|
|
188
203
|
const messages = commitMessagesSince(tag);
|
|
189
|
-
let releaseType =
|
|
190
|
-
for (const message of messages) {
|
|
191
|
-
releaseType = maxReleaseType(releaseType, analyzeMessage(message));
|
|
192
|
-
}
|
|
204
|
+
let releaseType = releaseTypeForMessages(messages);
|
|
193
205
|
|
|
194
206
|
let title = prTitleFromEvent();
|
|
195
207
|
if (!title) title = await prTitleFromMergeQueue();
|
|
@@ -214,9 +226,8 @@ async function main() {
|
|
|
214
226
|
if (signal.prTitle) console.log(`Release PR title: ${signal.prTitle}`);
|
|
215
227
|
|
|
216
228
|
if (!signal.releaseType) {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
);
|
|
229
|
+
console.log('Release preflight passed: no publication expected');
|
|
230
|
+
return;
|
|
220
231
|
}
|
|
221
232
|
|
|
222
233
|
console.log(`Release preflight passed: ${signal.releaseType}`);
|
|
@@ -232,5 +243,6 @@ if (require.main === module) {
|
|
|
232
243
|
module.exports = {
|
|
233
244
|
analyzeMessage,
|
|
234
245
|
maxReleaseType,
|
|
246
|
+
releaseTypeForMessages,
|
|
235
247
|
validateReleaseConfig,
|
|
236
248
|
};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { execFileSync, spawnSync } = require('child_process');
|
|
6
|
+
|
|
7
|
+
function run(command, args, options = {}) {
|
|
8
|
+
return execFileSync(command, args, {
|
|
9
|
+
encoding: 'utf8',
|
|
10
|
+
stdio: options.stdio || ['ignore', 'pipe', 'pipe'],
|
|
11
|
+
...options,
|
|
12
|
+
}).trim();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function parseReleaseTag(tag) {
|
|
16
|
+
const match = String(tag || '').match(/^v(\d+\.\d+\.\d+)$/);
|
|
17
|
+
if (!match) throw new Error('release_tag must match vX.Y.Z');
|
|
18
|
+
return match[1];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function validateCommit(commit) {
|
|
22
|
+
if (!/^[0-9a-f]{40}$/.test(String(commit || ''))) {
|
|
23
|
+
throw new Error('release_commit must be a full lowercase commit SHA');
|
|
24
|
+
}
|
|
25
|
+
return commit;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function verifyImmutableSource(tag, commit) {
|
|
29
|
+
run('git', ['show-ref', '--verify', `refs/tags/${tag}`]);
|
|
30
|
+
const head = run('git', ['rev-parse', 'HEAD']);
|
|
31
|
+
const tagCommit = run('git', ['rev-list', '-n', '1', `refs/tags/${tag}`]);
|
|
32
|
+
if (head !== commit || tagCommit !== commit) {
|
|
33
|
+
throw new Error('checkout, protected tag, and release_commit must reference the same commit');
|
|
34
|
+
}
|
|
35
|
+
run('git', ['merge-base', '--is-ancestor', commit, 'origin/main']);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function npmPackageMetadata(name, version) {
|
|
39
|
+
const result = spawnSync(
|
|
40
|
+
'npm',
|
|
41
|
+
['view', `${name}@${version}`, 'version', 'gitHead', 'dist.attestations', '--json'],
|
|
42
|
+
{ encoding: 'utf8' }
|
|
43
|
+
);
|
|
44
|
+
if (result.status === 0) return JSON.parse(result.stdout);
|
|
45
|
+
if (/\bE404\b|is not in this registry|No match found/i.test(result.stderr || '')) return null;
|
|
46
|
+
throw new Error(`npm registry lookup failed: ${(result.stderr || result.stdout).trim()}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function verifyExistingNpmVersion(metadata, version, commit) {
|
|
50
|
+
if (metadata.version !== version) {
|
|
51
|
+
throw new Error(`npm returned ${metadata.version}; expected ${version}`);
|
|
52
|
+
}
|
|
53
|
+
if (metadata.gitHead !== commit) {
|
|
54
|
+
throw new Error(`npm gitHead ${metadata.gitHead || '(missing)'} does not match ${commit}`);
|
|
55
|
+
}
|
|
56
|
+
if (!metadata['dist.attestations']?.provenance) {
|
|
57
|
+
throw new Error('npm package exists without a provenance attestation');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function recoverNpm(name, version, commit) {
|
|
62
|
+
const existing = npmPackageMetadata(name, version);
|
|
63
|
+
if (existing) {
|
|
64
|
+
verifyExistingNpmVersion(existing, version, commit);
|
|
65
|
+
console.log(`${name}@${version} already exists with matching provenance; nothing to recover`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
run('npm', ['version', version, '--no-git-tag-version', '--allow-same-version']);
|
|
70
|
+
run('npm', ['publish', '--access', 'public', '--provenance'], { stdio: 'inherit' });
|
|
71
|
+
|
|
72
|
+
const published = npmPackageMetadata(name, version);
|
|
73
|
+
if (!published) throw new Error(`${name}@${version} was not visible after publication`);
|
|
74
|
+
verifyExistingNpmVersion(published, version, commit);
|
|
75
|
+
console.log(`Recovered ${name}@${version}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function releaseExists(tag) {
|
|
79
|
+
const result = spawnSync('gh', ['release', 'view', tag], { encoding: 'utf8' });
|
|
80
|
+
if (result.status === 0) return true;
|
|
81
|
+
if (/release not found|HTTP 404/i.test(`${result.stdout}\n${result.stderr}`)) return false;
|
|
82
|
+
throw new Error(`GitHub Release lookup failed: ${(result.stderr || result.stdout).trim()}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function recoverGithubRelease(tag, commit, version) {
|
|
86
|
+
if (releaseExists(tag)) {
|
|
87
|
+
console.log(`GitHub Release ${tag} already exists; nothing to recover`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const notesPath = path.join(process.cwd(), 'docs', 'releases', `${tag}.md`);
|
|
92
|
+
const args = ['release', 'create', tag, '--verify-tag', '--target', commit, '--title', tag];
|
|
93
|
+
if (fs.existsSync(notesPath)) {
|
|
94
|
+
args.push('--notes-file', notesPath);
|
|
95
|
+
} else {
|
|
96
|
+
args.push('--generate-notes');
|
|
97
|
+
const tags = run('git', [
|
|
98
|
+
'tag',
|
|
99
|
+
'--list',
|
|
100
|
+
'v[0-9]*',
|
|
101
|
+
'--merged',
|
|
102
|
+
`${commit}^`,
|
|
103
|
+
'--sort=-version:refname',
|
|
104
|
+
])
|
|
105
|
+
.split(/\r?\n/)
|
|
106
|
+
.filter(Boolean);
|
|
107
|
+
if (tags[0]) args.push('--notes-start-tag', tags[0]);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
run('gh', args, { stdio: 'inherit' });
|
|
111
|
+
if (!releaseExists(tag)) throw new Error(`GitHub Release ${tag} was not created`);
|
|
112
|
+
console.log(`Recovered GitHub Release ${tag} for ${version}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function main() {
|
|
116
|
+
const action = process.env.RECOVERY_ACTION;
|
|
117
|
+
if (!['recover-npm', 'recover-github-release'].includes(action)) {
|
|
118
|
+
throw new Error('RECOVERY_ACTION must be recover-npm or recover-github-release');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const tag = process.env.RELEASE_TAG;
|
|
122
|
+
const commit = validateCommit(process.env.RELEASE_COMMIT);
|
|
123
|
+
const version = parseReleaseTag(tag);
|
|
124
|
+
verifyImmutableSource(tag, commit);
|
|
125
|
+
|
|
126
|
+
const packageJson = require('../package.json');
|
|
127
|
+
if (action === 'recover-npm') {
|
|
128
|
+
recoverNpm(packageJson.name, version, commit);
|
|
129
|
+
} else {
|
|
130
|
+
recoverGithubRelease(tag, commit, version);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (require.main === module) {
|
|
135
|
+
try {
|
|
136
|
+
main();
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error(`Release recovery failed: ${error.message}`);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
module.exports = {
|
|
144
|
+
npmPackageMetadata,
|
|
145
|
+
parseReleaseTag,
|
|
146
|
+
releaseExists,
|
|
147
|
+
validateCommit,
|
|
148
|
+
verifyExistingNpmVersion,
|
|
149
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
function curatedNotesPath(cwd, version) {
|
|
5
|
+
return path.join(cwd, 'docs', 'releases', `v${version}.md`);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function readCuratedNotes(cwd, version) {
|
|
9
|
+
const notesPath = curatedNotesPath(cwd, version);
|
|
10
|
+
if (!fs.existsSync(notesPath)) return null;
|
|
11
|
+
|
|
12
|
+
const notes = fs.readFileSync(notesPath, 'utf8').trim();
|
|
13
|
+
if (!notes) {
|
|
14
|
+
throw new Error(`Curated release notes are empty: ${notesPath}`);
|
|
15
|
+
}
|
|
16
|
+
return `${notes}\n`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function conventionalNotes(pluginConfig, context) {
|
|
20
|
+
const generator = await import('@semantic-release/release-notes-generator');
|
|
21
|
+
return generator.generateNotes(pluginConfig.conventional || {}, context);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function generateNotesWithFallback(pluginConfig, context, fallback = conventionalNotes) {
|
|
25
|
+
const version = context.nextRelease?.version;
|
|
26
|
+
if (!version) throw new Error('nextRelease.version is required to generate release notes');
|
|
27
|
+
|
|
28
|
+
const curated = readCuratedNotes(context.cwd || process.cwd(), version);
|
|
29
|
+
if (curated) return curated;
|
|
30
|
+
const generated = await fallback(pluginConfig, context);
|
|
31
|
+
return generated;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function generateNotes(pluginConfig, context) {
|
|
35
|
+
const notes = await generateNotesWithFallback(pluginConfig, context);
|
|
36
|
+
return notes;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = {
|
|
40
|
+
curatedNotesPath,
|
|
41
|
+
generateNotes,
|
|
42
|
+
generateNotesWithFallback,
|
|
43
|
+
readCuratedNotes,
|
|
44
|
+
};
|
|
@@ -1,75 +1,79 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# Setup GitHub merge queue and branch protection for zeroshot
|
|
3
|
-
# Run once after creating the repo or to update settings
|
|
4
2
|
|
|
5
|
-
set -
|
|
3
|
+
set -euo pipefail
|
|
6
4
|
|
|
7
5
|
REPO="the-open-engine/zeroshot"
|
|
6
|
+
MAIN_RULESET_NAME="Protect main trunk"
|
|
7
|
+
TAG_RULESET_NAME="Make release tags immutable"
|
|
8
8
|
|
|
9
|
-
echo "╔═══════════════════════════════════════════════════════════════════╗"
|
|
10
|
-
echo "║ Setting up merge queue for $REPO ║"
|
|
11
|
-
echo "╚═══════════════════════════════════════════════════════════════════╝"
|
|
12
|
-
echo ""
|
|
13
|
-
|
|
14
|
-
# Check gh is authenticated
|
|
15
9
|
if ! gh auth status &>/dev/null; then
|
|
16
|
-
echo "
|
|
17
|
-
echo " Run: gh auth login"
|
|
10
|
+
echo "ERROR: Authenticate GitHub CLI with: gh auth login"
|
|
18
11
|
exit 1
|
|
19
12
|
fi
|
|
20
13
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
echo "❌ ERROR: You need admin access to $REPO"
|
|
14
|
+
if [[ "$(gh api "repos/$REPO" --jq '.permissions.admin')" != "true" ]]; then
|
|
15
|
+
echo "ERROR: Repository admin access is required"
|
|
24
16
|
exit 1
|
|
25
17
|
fi
|
|
26
18
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
--input - <<EOF
|
|
38
|
-
{
|
|
39
|
-
"required_status_checks": {
|
|
40
|
-
"strict": true,
|
|
41
|
-
"contexts": ["check", "install-matrix (ubuntu-latest, 20)", "install-matrix (macos-latest, 20)"]
|
|
42
|
-
},
|
|
43
|
-
"enforce_admins": false,
|
|
44
|
-
"required_pull_request_reviews": null,
|
|
45
|
-
"restrictions": null,
|
|
46
|
-
"allow_force_pushes": false,
|
|
47
|
-
"allow_deletions": false,
|
|
48
|
-
"required_linear_history": true,
|
|
49
|
-
"required_conversation_resolution": false
|
|
19
|
+
upsert_ruleset() {
|
|
20
|
+
local name="$1"
|
|
21
|
+
local payload="$2"
|
|
22
|
+
local id
|
|
23
|
+
id="$(gh api "repos/$REPO/rulesets" --jq ".[] | select(.name == \"$name\") | .id" | head -1)"
|
|
24
|
+
if [[ -n "$id" ]]; then
|
|
25
|
+
gh api --method PUT "repos/$REPO/rulesets/$id" --input "$payload" >/dev/null
|
|
26
|
+
else
|
|
27
|
+
gh api --method POST "repos/$REPO/rulesets" --input "$payload" >/dev/null
|
|
28
|
+
fi
|
|
50
29
|
}
|
|
51
|
-
EOF
|
|
52
|
-
|
|
53
|
-
echo "✓ 'dev' branch protection configured"
|
|
54
30
|
|
|
55
|
-
|
|
56
|
-
|
|
31
|
+
main_payload="$(mktemp)"
|
|
32
|
+
tag_payload="$(mktemp)"
|
|
33
|
+
trap 'rm -f "$main_payload" "$tag_payload"' EXIT
|
|
57
34
|
|
|
58
|
-
|
|
59
|
-
# Using the ruleset API which supports merge queue
|
|
60
|
-
gh api --method POST "repos/$REPO/rulesets" \
|
|
61
|
-
--input - <<EOF 2>/dev/null || echo " (ruleset may already exist)"
|
|
35
|
+
cat > "$main_payload" <<'JSON'
|
|
62
36
|
{
|
|
63
|
-
"name": "
|
|
37
|
+
"name": "Protect main trunk",
|
|
64
38
|
"target": "branch",
|
|
65
39
|
"enforcement": "active",
|
|
66
40
|
"conditions": {
|
|
67
41
|
"ref_name": {
|
|
68
|
-
"include": ["refs/heads/
|
|
42
|
+
"include": ["refs/heads/main"],
|
|
69
43
|
"exclude": []
|
|
70
44
|
}
|
|
71
45
|
},
|
|
72
46
|
"rules": [
|
|
47
|
+
{"type": "deletion"},
|
|
48
|
+
{"type": "non_fast_forward"},
|
|
49
|
+
{"type": "required_linear_history"},
|
|
50
|
+
{
|
|
51
|
+
"type": "pull_request",
|
|
52
|
+
"parameters": {
|
|
53
|
+
"required_approving_review_count": 0,
|
|
54
|
+
"dismiss_stale_reviews_on_push": true,
|
|
55
|
+
"required_reviewers": [],
|
|
56
|
+
"require_code_owner_review": false,
|
|
57
|
+
"dismissal_restriction": {
|
|
58
|
+
"enabled": false,
|
|
59
|
+
"allowed_actors": []
|
|
60
|
+
},
|
|
61
|
+
"require_last_push_approval": false,
|
|
62
|
+
"required_review_thread_resolution": true,
|
|
63
|
+
"allowed_merge_methods": ["squash"]
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"type": "required_status_checks",
|
|
68
|
+
"parameters": {
|
|
69
|
+
"strict_required_status_checks_policy": true,
|
|
70
|
+
"do_not_enforce_on_create": false,
|
|
71
|
+
"required_status_checks": [
|
|
72
|
+
{"context": "required", "integration_id": 15368},
|
|
73
|
+
{"context": "semantic", "integration_id": 15368}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
},
|
|
73
77
|
{
|
|
74
78
|
"type": "merge_queue",
|
|
75
79
|
"parameters": {
|
|
@@ -82,49 +86,34 @@ gh api --method POST "repos/$REPO/rulesets" \
|
|
|
82
86
|
"min_entries_to_merge_wait_minutes": 1
|
|
83
87
|
}
|
|
84
88
|
}
|
|
85
|
-
]
|
|
89
|
+
],
|
|
90
|
+
"bypass_actors": []
|
|
86
91
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
echo "✓ Merge queue enabled for 'dev'"
|
|
92
|
+
JSON
|
|
90
93
|
|
|
91
|
-
|
|
92
|
-
# Configure 'main' branch protection (release branch)
|
|
93
|
-
# ============================================================================
|
|
94
|
-
|
|
95
|
-
echo "→ Configuring 'main' branch protection..."
|
|
96
|
-
|
|
97
|
-
gh api --method PUT "repos/$REPO/branches/main/protection" \
|
|
98
|
-
--input - <<EOF
|
|
94
|
+
cat > "$tag_payload" <<'JSON'
|
|
99
95
|
{
|
|
100
|
-
"
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
"require_code_owner_reviews": false
|
|
96
|
+
"name": "Make release tags immutable",
|
|
97
|
+
"target": "tag",
|
|
98
|
+
"enforcement": "active",
|
|
99
|
+
"conditions": {
|
|
100
|
+
"ref_name": {
|
|
101
|
+
"include": ["refs/tags/v*"],
|
|
102
|
+
"exclude": []
|
|
103
|
+
}
|
|
109
104
|
},
|
|
110
|
-
"
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
"
|
|
105
|
+
"rules": [
|
|
106
|
+
{"type": "deletion"},
|
|
107
|
+
{"type": "non_fast_forward"}
|
|
108
|
+
],
|
|
109
|
+
"bypass_actors": []
|
|
115
110
|
}
|
|
116
|
-
|
|
111
|
+
JSON
|
|
117
112
|
|
|
118
|
-
|
|
113
|
+
upsert_ruleset "$MAIN_RULESET_NAME" "$main_payload"
|
|
114
|
+
upsert_ruleset "$TAG_RULESET_NAME" "$tag_payload"
|
|
119
115
|
|
|
120
|
-
|
|
121
|
-
# Configure repository settings
|
|
122
|
-
# ============================================================================
|
|
123
|
-
|
|
124
|
-
echo "→ Configuring repository settings..."
|
|
125
|
-
|
|
126
|
-
gh api --method PATCH "repos/$REPO" \
|
|
127
|
-
--input - <<EOF
|
|
116
|
+
gh api --method PATCH "repos/$REPO" --input - <<'JSON' >/dev/null
|
|
128
117
|
{
|
|
129
118
|
"allow_squash_merge": true,
|
|
130
119
|
"allow_merge_commit": false,
|
|
@@ -134,37 +123,34 @@ gh api --method PATCH "repos/$REPO" \
|
|
|
134
123
|
"delete_branch_on_merge": true,
|
|
135
124
|
"allow_auto_merge": true
|
|
136
125
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
echo "
|
|
168
|
-
echo " gh pr create --base main --head dev --title \"Release\""
|
|
169
|
-
echo " → CI passes → merge → semantic-release publishes"
|
|
170
|
-
echo ""
|
|
126
|
+
JSON
|
|
127
|
+
|
|
128
|
+
gh api --method PUT "repos/$REPO/environments/release" --input - <<'JSON' >/dev/null
|
|
129
|
+
{
|
|
130
|
+
"deployment_branch_policy": {
|
|
131
|
+
"protected_branches": false,
|
|
132
|
+
"custom_branch_policies": true
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
JSON
|
|
136
|
+
|
|
137
|
+
if ! gh api "repos/$REPO/environments/release/deployment-branch-policies" \
|
|
138
|
+
--jq '.branch_policies[] | select(.name == "main") | .id' | grep -q .; then
|
|
139
|
+
gh api --method POST "repos/$REPO/environments/release/deployment-branch-policies" \
|
|
140
|
+
-f name=main -f type=branch >/dev/null
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
gh variable set RELEASE_AUTOMATION_ENABLED --repo "$REPO" --body false
|
|
144
|
+
|
|
145
|
+
if gh api "repos/$REPO/branches/main/protection" >/dev/null 2>&1; then
|
|
146
|
+
gh api --method DELETE "repos/$REPO/branches/main/protection"
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
while IFS= read -r ruleset_id; do
|
|
150
|
+
[[ -n "$ruleset_id" ]] && gh api --method DELETE "repos/$REPO/rulesets/$ruleset_id"
|
|
151
|
+
done < <(
|
|
152
|
+
gh api "repos/$REPO/rulesets" \
|
|
153
|
+
--jq '.[] | select(.name == "dev-merge-queue" or .name == "main-merge-queue") | .id'
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
echo "Configured protected main trunk, merge queue, immutable release tags, and disabled release automation."
|