github-settings-as-code 2.1.2 → 3.0.1
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 +21 -15
- package/dist/cmd/repo-settings.d.mts.map +1 -1
- package/dist/cmd/repo-settings.mjs +30 -29
- package/dist/cmd/repo-settings.mjs.map +1 -1
- package/dist/github/actions-settings/api/get-actions-settings.d.mts.map +1 -1
- package/dist/github/actions-settings/api/get-actions-settings.mjs +0 -1
- package/dist/github/actions-settings/api/get-actions-settings.mjs.map +1 -1
- package/dist/github/actions-settings/apply.d.mts +1 -1
- package/dist/github/actions-settings/apply.d.mts.map +1 -1
- package/dist/github/actions-settings/apply.mjs +3 -3
- package/dist/github/actions-settings/apply.mjs.map +1 -1
- package/dist/github/actions-settings/constants.d.mts +87 -1
- package/dist/github/actions-settings/constants.d.mts.map +1 -1
- package/dist/github/actions-settings/constants.mjs +1 -1
- package/dist/github/constants.d.mts.map +1 -1
- package/dist/github/constants.mjs +7 -5
- package/dist/github/constants.mjs.map +1 -1
- package/dist/github/pages/api/get-pages-settings.d.mts.map +1 -1
- package/dist/github/pages/api/get-pages-settings.mjs +0 -1
- package/dist/github/pages/api/get-pages-settings.mjs.map +1 -1
- package/dist/github/pages/apply.d.mts +1 -1
- package/dist/github/pages/apply.mjs +3 -3
- package/dist/github/pages/apply.mjs.map +1 -1
- package/dist/github/pages/constants.d.mts +21 -1
- package/dist/github/pages/constants.d.mts.map +1 -1
- package/dist/github/pages/constants.mjs +1 -1
- package/dist/github/repository/api/get-repository.mjs +2 -2
- package/dist/github/repository/api/get-repository.mjs.map +1 -1
- package/dist/github/repository/api/update-repository.d.mts +1 -1
- package/dist/github/repository/api/update-repository.d.mts.map +1 -1
- package/dist/github/repository/api/update-repository.mjs +5 -2
- package/dist/github/repository/api/update-repository.mjs.map +1 -1
- package/dist/github/repository/apply.mjs +2 -2
- package/dist/github/repository/apply.mjs.map +1 -1
- package/dist/github/repository/constants.d.mts +176 -0
- package/dist/github/repository/constants.d.mts.map +1 -1
- package/dist/github/ruleset/api/get-all-rulesets.mjs +2 -2
- package/dist/github/ruleset/api/get-all-rulesets.mjs.map +1 -1
- package/dist/github/ruleset/api/get-ruleset.mjs +2 -2
- package/dist/github/ruleset/api/get-ruleset.mjs.map +1 -1
- package/dist/github/ruleset/constants.d.mts +88 -8
- package/dist/github/ruleset/constants.d.mts.map +1 -1
- package/dist/github/variables/apply.d.mts.map +1 -1
- package/dist/github/variables/apply.mjs +1 -0
- package/dist/github/variables/apply.mjs.map +1 -1
- package/package.json +22 -27
- package/src/cmd/repo-settings.mts +37 -36
- package/src/github/actions-settings/api/get-actions-settings.mts +0 -1
- package/src/github/actions-settings/apply.mts +3 -3
- package/src/github/actions-settings/constants.mts +1 -1
- package/src/github/constants.mts +10 -5
- package/src/github/pages/api/get-pages-settings.mts +0 -1
- package/src/github/pages/apply.mts +3 -3
- package/src/github/pages/constants.mts +1 -1
- package/src/github/repository/api/get-repository.mts +2 -2
- package/src/github/repository/api/update-repository.mts +6 -1
- package/src/github/repository/apply.mts +2 -2
- package/src/github/ruleset/api/get-all-rulesets.mts +2 -2
- package/src/github/ruleset/api/get-ruleset.mts +2 -2
- package/src/github/ruleset/schema.test.mts +164 -0
- package/src/github/variables/apply.mts +2 -0
|
@@ -4,7 +4,7 @@ import * as fs from 'node:fs/promises';
|
|
|
4
4
|
import * as path from 'node:path';
|
|
5
5
|
import { type UpdateRepositoryRequest } from 'octokit-safe-types';
|
|
6
6
|
import { Obj } from 'ts-data-forge';
|
|
7
|
-
import
|
|
7
|
+
import * as t from 'ts-fortress';
|
|
8
8
|
import {
|
|
9
9
|
formatUncommittedFiles,
|
|
10
10
|
isDirectlyExecuted,
|
|
@@ -95,7 +95,7 @@ const readSettings = async (): Promise<RepositoryPicked> => {
|
|
|
95
95
|
|
|
96
96
|
if (Result.isErr(validationResult)) {
|
|
97
97
|
throw new Error(
|
|
98
|
-
validationErrorsToMessages(validationResult.value).join('\n'),
|
|
98
|
+
t.validationErrorsToMessages(validationResult.value).join('\n'),
|
|
99
99
|
);
|
|
100
100
|
}
|
|
101
101
|
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
type OctokitResponse,
|
|
6
6
|
} from 'octokit-safe-types';
|
|
7
7
|
import { expectType } from 'ts-data-forge';
|
|
8
|
-
import
|
|
8
|
+
import * as t from 'ts-fortress';
|
|
9
9
|
import { Result } from 'ts-repo-utils';
|
|
10
10
|
import { type DeepReadonly } from 'ts-type-forge';
|
|
11
11
|
import { octokitHeaders, OWNER, REPO } from '../../constants.mjs';
|
|
@@ -64,7 +64,7 @@ export const getAllRulesets = async (): Promise<GetAllRulesetsResponse> => {
|
|
|
64
64
|
const res = GetAllRulesetsResponse.validate(getAllRulesetsResponse.data);
|
|
65
65
|
|
|
66
66
|
if (Result.isErr(res)) {
|
|
67
|
-
console.warn(validationErrorsToMessages(res.value));
|
|
67
|
+
console.warn(t.validationErrorsToMessages(res.value));
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { GetRulesetResponse, type EndpointKeys } from 'octokit-safe-types';
|
|
2
|
-
import
|
|
2
|
+
import * as t from 'ts-fortress';
|
|
3
3
|
import { Result } from 'ts-repo-utils';
|
|
4
4
|
import { octokitHeaders, OWNER, REPO } from '../../constants.mjs';
|
|
5
5
|
import { octokit } from '../../octokit.mjs';
|
|
@@ -22,7 +22,7 @@ export const getRuleset = async (
|
|
|
22
22
|
const res = GetRulesetResponse.validate(getRulesetResult.data);
|
|
23
23
|
|
|
24
24
|
if (Result.isErr(res)) {
|
|
25
|
-
console.warn(validationErrorsToMessages(res.value));
|
|
25
|
+
console.warn(t.validationErrorsToMessages(res.value));
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { Arr, Obj, Result } from 'ts-data-forge';
|
|
4
|
+
import * as t from 'ts-fortress';
|
|
5
|
+
import { RulesetPicked, rulesetKeysToPick } from './constants.mjs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The rulesets this repository declares, read from where `repo-settings apply`
|
|
9
|
+
* reads them.
|
|
10
|
+
*
|
|
11
|
+
* The real files rather than copies of them: what is worth knowing is that the
|
|
12
|
+
* payloads actually committed here parse. `apply` throws outright when one does
|
|
13
|
+
* not, and by then it is running against GitHub.
|
|
14
|
+
*/
|
|
15
|
+
const rulesetsDir = path.resolve(
|
|
16
|
+
import.meta.dirname,
|
|
17
|
+
'../../../../../repo-settings/rulesets',
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const readJsonFilesIn = async (
|
|
21
|
+
dir: string,
|
|
22
|
+
): Promise<readonly (readonly [string, unknown])[]> => {
|
|
23
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
24
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
25
|
+
|
|
26
|
+
return Promise.all(
|
|
27
|
+
entries
|
|
28
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith('.json'))
|
|
29
|
+
.map(async (entry) => {
|
|
30
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
31
|
+
const content = await fs.readFile(
|
|
32
|
+
path.resolve(dir, entry.name),
|
|
33
|
+
'utf8',
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
return [entry.name, JSON.parse(content)] as const;
|
|
37
|
+
}),
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The file name on success, and the name with what went wrong on failure.
|
|
43
|
+
*
|
|
44
|
+
* Compared against the bare name rather than asserted on directly, because
|
|
45
|
+
* `assert.isTrue` here takes no message argument — this way a failure still
|
|
46
|
+
* prints which file it was and what the validator objected to.
|
|
47
|
+
*/
|
|
48
|
+
const outcomeOf = (
|
|
49
|
+
name: string,
|
|
50
|
+
result: ReturnType<typeof RulesetPicked.validate>,
|
|
51
|
+
): string =>
|
|
52
|
+
Result.isErr(result)
|
|
53
|
+
? (`${name}: ${t.validationErrorsToMessages(result.value).join('\n')}` as const)
|
|
54
|
+
: name;
|
|
55
|
+
|
|
56
|
+
const validRuleset = {
|
|
57
|
+
id: 1,
|
|
58
|
+
name: 'x',
|
|
59
|
+
target: 'branch',
|
|
60
|
+
enforcement: 'active',
|
|
61
|
+
bypass_actors: [],
|
|
62
|
+
conditions: { ref_name: { include: [], exclude: [] } },
|
|
63
|
+
rules: [{ type: 'deletion' }],
|
|
64
|
+
} as const;
|
|
65
|
+
|
|
66
|
+
describe('the ruleset schema', () => {
|
|
67
|
+
test('accepts every ruleset this repository declares', async () => {
|
|
68
|
+
const files = await readJsonFilesIn(rulesetsDir);
|
|
69
|
+
|
|
70
|
+
assert.isTrue(Arr.isNonEmpty(files));
|
|
71
|
+
|
|
72
|
+
for (const [name, content] of files) {
|
|
73
|
+
assert.deepStrictEqual(
|
|
74
|
+
outcomeOf(name, RulesetPicked.validate(content)),
|
|
75
|
+
name,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('accepts every backed-up ruleset', async () => {
|
|
81
|
+
const files = await readJsonFilesIn(path.resolve(rulesetsDir, './bk'));
|
|
82
|
+
|
|
83
|
+
assert.isTrue(Arr.isNonEmpty(files));
|
|
84
|
+
|
|
85
|
+
for (const [name, content] of files) {
|
|
86
|
+
assert.deepStrictEqual(
|
|
87
|
+
outcomeOf(name, RulesetPicked.validate(content)),
|
|
88
|
+
name,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('accepts the fields GitHub adds to what apply sends back', async () => {
|
|
94
|
+
const files = await readJsonFilesIn(rulesetsDir);
|
|
95
|
+
|
|
96
|
+
assert.isTrue(Arr.isNonEmpty(files));
|
|
97
|
+
|
|
98
|
+
const declared = RulesetPicked.validate(files[0][1]);
|
|
99
|
+
|
|
100
|
+
assert.deepStrictEqual(outcomeOf(files[0][0], declared), files[0][0]);
|
|
101
|
+
|
|
102
|
+
if (Result.isOk(declared)) {
|
|
103
|
+
// `getRuleset` hands the whole API response to this type, and a response
|
|
104
|
+
// carries more than the seven fields that go back out again.
|
|
105
|
+
const result = RulesetPicked.validate({
|
|
106
|
+
...declared.value,
|
|
107
|
+
source: 'noshiro-pf/mono',
|
|
108
|
+
source_type: 'Repository',
|
|
109
|
+
node_id: 'RRS_lACqUmVwb3NpdG9yeQ',
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
assert.isTrue(Result.isOk(result));
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('accepts a ruleset built from values GitHub does use', () => {
|
|
117
|
+
// The control for the two rejections below: without it they could be
|
|
118
|
+
// passing for some reason other than the field under test.
|
|
119
|
+
assert.isTrue(Result.isOk(RulesetPicked.validate(validRuleset)));
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('rejects an enforcement value GitHub does not use', () => {
|
|
123
|
+
const result = RulesetPicked.validate({
|
|
124
|
+
...validRuleset,
|
|
125
|
+
enforcement: 'enabled',
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
assert.isTrue(Result.isErr(result));
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('rejects a rule type GitHub does not define', () => {
|
|
132
|
+
const result = RulesetPicked.validate({
|
|
133
|
+
...validRuleset,
|
|
134
|
+
rules: [{ type: 'no_such_rule' }],
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
assert.isTrue(Result.isErr(result));
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('narrows to the seven fields apply sends, which is what backup writes', () => {
|
|
141
|
+
// Validation is a check rather than a filter, so `Obj.pick` is the step
|
|
142
|
+
// that narrows — this mirrors what `backupRulesets` does to a response
|
|
143
|
+
// before writing it.
|
|
144
|
+
const picked = Obj.pick(
|
|
145
|
+
{
|
|
146
|
+
...validRuleset,
|
|
147
|
+
source: 'noshiro-pf/mono',
|
|
148
|
+
source_type: 'Repository',
|
|
149
|
+
node_id: 'RRS_lACqUmVwb3NpdG9yeQ',
|
|
150
|
+
},
|
|
151
|
+
rulesetKeysToPick,
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
assert.deepStrictEqual(Object.keys(picked).toSorted(), [
|
|
155
|
+
'bypass_actors',
|
|
156
|
+
'conditions',
|
|
157
|
+
'enforcement',
|
|
158
|
+
'id',
|
|
159
|
+
'name',
|
|
160
|
+
'rules',
|
|
161
|
+
'target',
|
|
162
|
+
]);
|
|
163
|
+
});
|
|
164
|
+
});
|