@simplysf/simply-gitlab-core 0.1.0 → 0.3.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/LICENSE.txt +206 -0
- package/README.md +76 -76
- package/lib/baseline-apply.d.ts +35 -0
- package/lib/baseline-apply.js +177 -0
- package/lib/baseline-apply.js.map +1 -0
- package/lib/baseline-config.d.ts +100 -0
- package/lib/baseline-config.js +342 -0
- package/lib/baseline-config.js.map +1 -0
- package/lib/baseline-export.d.ts +21 -0
- package/lib/baseline-export.js +245 -0
- package/lib/baseline-export.js.map +1 -0
- package/lib/baseline-plan.d.ts +96 -0
- package/lib/baseline-plan.js +412 -0
- package/lib/baseline-plan.js.map +1 -0
- package/lib/baseline-render.d.ts +13 -0
- package/lib/baseline-render.js +139 -0
- package/lib/baseline-render.js.map +1 -0
- package/lib/baseline-run.d.ts +49 -0
- package/lib/baseline-run.js +102 -0
- package/lib/baseline-run.js.map +1 -0
- package/lib/baseline-targets.d.ts +31 -0
- package/lib/baseline-targets.js +100 -0
- package/lib/baseline-targets.js.map +1 -0
- package/lib/gitlab-client.d.ts +32 -0
- package/lib/gitlab-client.js +96 -0
- package/lib/gitlab-client.js.map +1 -1
- package/lib/index.d.ts +7 -0
- package/lib/index.js +8 -0
- package/lib/index.js.map +1 -1
- package/package.json +131 -130
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026, SimplySF.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Reads a live project and writes down what it looks like, as a config someone can edit.
|
|
18
|
+
*
|
|
19
|
+
* Writing a baseline by hand against forty attributes is miserable and the first draft would be
|
|
20
|
+
* wrong. Exporting the project that already looks right and deleting what you do not care about is
|
|
21
|
+
* how anyone actually starts, so this errs towards saying too much: deleting a line is easier than
|
|
22
|
+
* discovering that a key exists.
|
|
23
|
+
*/
|
|
24
|
+
/** The sections `export` can emit, in the order they appear in the file. */
|
|
25
|
+
export const EXPORTABLE_SECTIONS = [
|
|
26
|
+
'settings',
|
|
27
|
+
'protectedBranches',
|
|
28
|
+
'protectedTags',
|
|
29
|
+
'approvals',
|
|
30
|
+
'approvalRules',
|
|
31
|
+
'pushRules',
|
|
32
|
+
'variables',
|
|
33
|
+
];
|
|
34
|
+
/**
|
|
35
|
+
* The project attributes worth putting in a baseline.
|
|
36
|
+
*
|
|
37
|
+
* A deliberate list rather than everything `GET /projects/:id` returns: that payload carries ids,
|
|
38
|
+
* timestamps, counts, and URLs, none of which can be set and all of which would look like intent
|
|
39
|
+
* once they were sitting in a config file.
|
|
40
|
+
*/
|
|
41
|
+
const SETTING_KEYS = [
|
|
42
|
+
// Merge request behaviour
|
|
43
|
+
'merge_method',
|
|
44
|
+
'squash_option',
|
|
45
|
+
'only_allow_merge_if_pipeline_succeeds',
|
|
46
|
+
'only_allow_merge_if_all_discussions_are_resolved',
|
|
47
|
+
'allow_merge_on_skipped_pipeline',
|
|
48
|
+
'remove_source_branch_after_merge',
|
|
49
|
+
'merge_trains_enabled',
|
|
50
|
+
'printing_merge_request_link_enabled',
|
|
51
|
+
'resolve_outdated_diff_discussions',
|
|
52
|
+
'merge_requests_template',
|
|
53
|
+
// CI/CD
|
|
54
|
+
'ci_config_path',
|
|
55
|
+
'build_timeout',
|
|
56
|
+
'auto_cancel_pending_pipelines',
|
|
57
|
+
'ci_default_git_depth',
|
|
58
|
+
'ci_forward_deployment_enabled',
|
|
59
|
+
'ci_separated_caches',
|
|
60
|
+
'ci_allow_fork_pipelines_to_run_in_parent_project',
|
|
61
|
+
'keep_latest_artifact',
|
|
62
|
+
'auto_devops_enabled',
|
|
63
|
+
'shared_runners_enabled',
|
|
64
|
+
'group_runners_enabled',
|
|
65
|
+
// Repository
|
|
66
|
+
'default_branch',
|
|
67
|
+
'issues_enabled',
|
|
68
|
+
'wiki_enabled',
|
|
69
|
+
'packages_enabled',
|
|
70
|
+
];
|
|
71
|
+
/** Approval settings that can be set back, as opposed to reported. */
|
|
72
|
+
const APPROVAL_KEYS = [
|
|
73
|
+
'approvals_before_merge',
|
|
74
|
+
'reset_approvals_on_push',
|
|
75
|
+
'disable_overriding_approvers_per_merge_request',
|
|
76
|
+
'merge_requests_author_approval',
|
|
77
|
+
'merge_requests_disable_committers_approval',
|
|
78
|
+
'require_password_to_approve',
|
|
79
|
+
];
|
|
80
|
+
/** Push-rule attributes, minus the id and the project it belongs to. */
|
|
81
|
+
const PUSH_RULE_KEYS = [
|
|
82
|
+
'commit_message_regex',
|
|
83
|
+
'commit_message_negative_regex',
|
|
84
|
+
'branch_name_regex',
|
|
85
|
+
'author_email_regex',
|
|
86
|
+
'file_name_regex',
|
|
87
|
+
'deny_delete_tag',
|
|
88
|
+
'member_check',
|
|
89
|
+
'prevent_secrets',
|
|
90
|
+
'max_file_size',
|
|
91
|
+
'commit_committer_check',
|
|
92
|
+
'commit_committer_name_check',
|
|
93
|
+
'reject_unsigned_commits',
|
|
94
|
+
'reject_non_dco_commits',
|
|
95
|
+
];
|
|
96
|
+
/** Protected-branch attributes, keeping the access arrays but dropping their server-side ids. */
|
|
97
|
+
const BRANCH_KEYS = ['name', 'allow_force_push', 'code_owner_approval_required'];
|
|
98
|
+
const ACCESS_ARRAYS = ['push_access_levels', 'merge_access_levels', 'unprotect_access_levels'];
|
|
99
|
+
const ACCESS_TARGET = {
|
|
100
|
+
push_access_levels: 'allowed_to_push',
|
|
101
|
+
merge_access_levels: 'allowed_to_merge',
|
|
102
|
+
unprotect_access_levels: 'allowed_to_unprotect',
|
|
103
|
+
};
|
|
104
|
+
function asRecord(value) {
|
|
105
|
+
return (typeof value === 'object' && value !== null ? value : {});
|
|
106
|
+
}
|
|
107
|
+
/** Copies the named keys that are actually present, so an absent attribute stays absent. */
|
|
108
|
+
function pick(source, keys) {
|
|
109
|
+
const picked = {};
|
|
110
|
+
for (const key of keys) {
|
|
111
|
+
if (source[key] !== undefined && source[key] !== null)
|
|
112
|
+
picked[key] = source[key];
|
|
113
|
+
}
|
|
114
|
+
return picked;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Turns an access-level array as GitLab reports it into the shape it accepts back.
|
|
118
|
+
*
|
|
119
|
+
* The read shape carries `id` and `access_level_description`, neither of which is writable, and the
|
|
120
|
+
* write shape is a different key. Exporting the raw read would produce a config that fails on its
|
|
121
|
+
* first apply.
|
|
122
|
+
*/
|
|
123
|
+
function accessEntries(levels) {
|
|
124
|
+
if (!Array.isArray(levels))
|
|
125
|
+
return undefined;
|
|
126
|
+
const mapped = levels.map((raw) => {
|
|
127
|
+
const level = asRecord(raw);
|
|
128
|
+
const entry = {};
|
|
129
|
+
if (typeof level.user_id === 'number')
|
|
130
|
+
entry.user_id = level.user_id;
|
|
131
|
+
else if (typeof level.group_id === 'number')
|
|
132
|
+
entry.group_id = level.group_id;
|
|
133
|
+
else if (typeof level.access_level === 'number')
|
|
134
|
+
entry.access_level = level.access_level;
|
|
135
|
+
return entry;
|
|
136
|
+
});
|
|
137
|
+
return mapped.length === 0 ? undefined : mapped;
|
|
138
|
+
}
|
|
139
|
+
function exportBranch(raw) {
|
|
140
|
+
const branch = asRecord(raw);
|
|
141
|
+
const entry = pick(branch, BRANCH_KEYS);
|
|
142
|
+
for (const key of ACCESS_ARRAYS) {
|
|
143
|
+
const mapped = accessEntries(branch[key]);
|
|
144
|
+
if (mapped !== undefined)
|
|
145
|
+
entry[ACCESS_TARGET[key]] = mapped;
|
|
146
|
+
}
|
|
147
|
+
return entry;
|
|
148
|
+
}
|
|
149
|
+
function exportTag(raw) {
|
|
150
|
+
const tag = asRecord(raw);
|
|
151
|
+
const entry = { name: tag.name };
|
|
152
|
+
const levels = Array.isArray(tag.create_access_levels) ? tag.create_access_levels.map(asRecord) : [];
|
|
153
|
+
const first = levels[0];
|
|
154
|
+
if (first !== undefined && typeof first.access_level === 'number')
|
|
155
|
+
entry.create_access_level = first.access_level;
|
|
156
|
+
return entry;
|
|
157
|
+
}
|
|
158
|
+
function exportRule(raw) {
|
|
159
|
+
const rule = asRecord(raw);
|
|
160
|
+
// The implicit fallback rule is not something a config declares; exporting it would invite
|
|
161
|
+
// someone to prune it and lose the project's default approval behaviour.
|
|
162
|
+
if (rule.rule_type === 'any_approver')
|
|
163
|
+
return undefined;
|
|
164
|
+
const entry = { name: rule.name, approvals_required: rule.approvals_required };
|
|
165
|
+
const usernames = (Array.isArray(rule.eligible_approvers) ? rule.eligible_approvers : [])
|
|
166
|
+
.map((user) => asRecord(user).username)
|
|
167
|
+
.filter((username) => typeof username === 'string');
|
|
168
|
+
if (usernames.length > 0)
|
|
169
|
+
entry.usernames = usernames.sort();
|
|
170
|
+
const groups = (Array.isArray(rule.groups) ? rule.groups : [])
|
|
171
|
+
.map((group) => asRecord(group).full_path)
|
|
172
|
+
.filter((path) => typeof path === 'string');
|
|
173
|
+
if (groups.length > 0)
|
|
174
|
+
entry.groups = groups.sort();
|
|
175
|
+
const branches = (Array.isArray(rule.protected_branches) ? rule.protected_branches : [])
|
|
176
|
+
.map((branch) => asRecord(branch).name)
|
|
177
|
+
.filter((name) => typeof name === 'string');
|
|
178
|
+
if (branches.length > 0)
|
|
179
|
+
entry.protectedBranches = branches.sort();
|
|
180
|
+
else if (rule.applies_to_all_protected_branches === true)
|
|
181
|
+
entry.applies_to_all_protected_branches = true;
|
|
182
|
+
return entry;
|
|
183
|
+
}
|
|
184
|
+
/** Variables are exported as metadata. The `value` GitLab returns is never copied into the file. */
|
|
185
|
+
function exportVariable(raw) {
|
|
186
|
+
const variable = asRecord(raw);
|
|
187
|
+
return pick(variable, ['key', 'environment_scope', 'variable_type', 'protected', 'masked', 'raw']);
|
|
188
|
+
}
|
|
189
|
+
/** Reads a section, treating a Premium-only endpoint that refuses as simply absent. */
|
|
190
|
+
async function optional(read) {
|
|
191
|
+
try {
|
|
192
|
+
return await read();
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
return undefined;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/** Reads one project and returns the `project` block of a config describing it. */
|
|
199
|
+
export async function exportProject(client, project, options = {}) {
|
|
200
|
+
const wanted = new Set(options.sections ?? EXPORTABLE_SECTIONS);
|
|
201
|
+
const desired = {};
|
|
202
|
+
if (wanted.has('settings')) {
|
|
203
|
+
desired.settings = pick(asRecord(await client.getProject(project)), SETTING_KEYS);
|
|
204
|
+
}
|
|
205
|
+
if (wanted.has('protectedBranches')) {
|
|
206
|
+
desired.protectedBranches = (await client.listProtectedBranches(project)).items.map(exportBranch);
|
|
207
|
+
}
|
|
208
|
+
if (wanted.has('protectedTags')) {
|
|
209
|
+
desired.protectedTags = (await client.listProtectedTags(project)).items.map(exportTag);
|
|
210
|
+
}
|
|
211
|
+
if (wanted.has('approvals')) {
|
|
212
|
+
const approvals = await optional(() => client.getApprovalSettings(project));
|
|
213
|
+
if (approvals !== undefined)
|
|
214
|
+
desired.approvals = pick(asRecord(approvals), APPROVAL_KEYS);
|
|
215
|
+
}
|
|
216
|
+
if (wanted.has('approvalRules')) {
|
|
217
|
+
const rules = await optional(() => client.listApprovalRules(project));
|
|
218
|
+
if (rules !== undefined) {
|
|
219
|
+
desired.approvalRules = rules.items.map(exportRule).filter((rule) => rule !== undefined);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (wanted.has('pushRules')) {
|
|
223
|
+
const pushRules = await optional(() => client.getPushRules(project));
|
|
224
|
+
if (pushRules !== undefined && pushRules !== null)
|
|
225
|
+
desired.pushRules = pick(asRecord(pushRules), PUSH_RULE_KEYS);
|
|
226
|
+
}
|
|
227
|
+
if (wanted.has('variables')) {
|
|
228
|
+
const variables = await optional(() => client.listVariables(project, 200));
|
|
229
|
+
if (variables !== undefined)
|
|
230
|
+
desired.variables = variables.items.map(exportVariable);
|
|
231
|
+
}
|
|
232
|
+
return desired;
|
|
233
|
+
}
|
|
234
|
+
/** A whole config file describing one project, ready to be widened to a group of them. */
|
|
235
|
+
export async function exportConfig(client, project, options = {}) {
|
|
236
|
+
const desired = await exportProject(client, project, options);
|
|
237
|
+
return {
|
|
238
|
+
version: 1,
|
|
239
|
+
// Deliberately the project it came from, not a group: whoever runs this next should choose the
|
|
240
|
+
// blast radius on purpose rather than inherit one from an export.
|
|
241
|
+
targets: { projects: [project] },
|
|
242
|
+
project: desired,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
//# sourceMappingURL=baseline-export.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseline-export.js","sourceRoot":"","sources":["../src/baseline-export.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { type GroupDesired, type ProjectDesired } from './baseline-config.js';
|
|
2
|
+
import type { GitLabClient } from './gitlab-client.js';
|
|
3
|
+
/**
|
|
4
|
+
* What a run would do, computed before anything is sent.
|
|
5
|
+
*
|
|
6
|
+
* A plan carries two views of the same decision: `changes`, which is what a person reads, and
|
|
7
|
+
* `actions`, which is what `apply` executes. They are produced together, by one pass, so apply can
|
|
8
|
+
* only ever do what plan showed — the alternative, re-deriving the work at apply time, is how a
|
|
9
|
+
* preview stops being a guarantee.
|
|
10
|
+
*/
|
|
11
|
+
export interface BaselinePlan {
|
|
12
|
+
readonly targets: readonly TargetPlan[];
|
|
13
|
+
}
|
|
14
|
+
export interface TargetPlan {
|
|
15
|
+
readonly kind: 'project' | 'group';
|
|
16
|
+
readonly path: string;
|
|
17
|
+
readonly id: number;
|
|
18
|
+
readonly sections: readonly SectionPlan[];
|
|
19
|
+
/** Set when the whole target could not be planned; its sections are then empty. */
|
|
20
|
+
readonly error?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface SectionPlan {
|
|
23
|
+
readonly section: string;
|
|
24
|
+
/** False when the instance does not offer this section — a Premium feature on a Free plan. */
|
|
25
|
+
readonly supported: boolean;
|
|
26
|
+
/** Why it is unsupported, for the plan output. */
|
|
27
|
+
readonly unsupportedReason?: string;
|
|
28
|
+
readonly changes: readonly Change[];
|
|
29
|
+
readonly actions: readonly Action[];
|
|
30
|
+
}
|
|
31
|
+
export type ChangeOp = 'create' | 'update' | 'delete' | 'recreate' | 'report';
|
|
32
|
+
export interface Change {
|
|
33
|
+
readonly op: ChangeOp;
|
|
34
|
+
/** What changed: an attribute name for `update`, an entry's identity otherwise. */
|
|
35
|
+
readonly key: string;
|
|
36
|
+
readonly from?: unknown;
|
|
37
|
+
readonly to?: unknown;
|
|
38
|
+
/** Extra context a reader needs — why a recreate, or why a delete is not being executed. */
|
|
39
|
+
readonly detail?: string;
|
|
40
|
+
}
|
|
41
|
+
/** One request `apply` will make. Opaque to the renderer; executed by `baseline-apply.ts`. */
|
|
42
|
+
export type Action = {
|
|
43
|
+
readonly kind: 'updateProject';
|
|
44
|
+
readonly body: Record<string, unknown>;
|
|
45
|
+
} | {
|
|
46
|
+
readonly kind: 'updateGroup';
|
|
47
|
+
readonly body: Record<string, unknown>;
|
|
48
|
+
} | {
|
|
49
|
+
readonly kind: 'protectBranch';
|
|
50
|
+
readonly body: Record<string, unknown>;
|
|
51
|
+
} | {
|
|
52
|
+
readonly kind: 'patchProtectedBranch';
|
|
53
|
+
readonly name: string;
|
|
54
|
+
readonly body: Record<string, unknown>;
|
|
55
|
+
} | {
|
|
56
|
+
readonly kind: 'unprotectBranch';
|
|
57
|
+
readonly name: string;
|
|
58
|
+
} | {
|
|
59
|
+
readonly kind: 'protectTag';
|
|
60
|
+
readonly body: Record<string, unknown>;
|
|
61
|
+
} | {
|
|
62
|
+
readonly kind: 'unprotectTag';
|
|
63
|
+
readonly name: string;
|
|
64
|
+
} | {
|
|
65
|
+
readonly kind: 'updateApprovalSettings';
|
|
66
|
+
readonly body: Record<string, unknown>;
|
|
67
|
+
} | {
|
|
68
|
+
readonly kind: 'createApprovalRule';
|
|
69
|
+
readonly body: Record<string, unknown>;
|
|
70
|
+
readonly branches?: readonly string[];
|
|
71
|
+
} | {
|
|
72
|
+
readonly kind: 'updateApprovalRule';
|
|
73
|
+
readonly ruleId: number;
|
|
74
|
+
readonly body: Record<string, unknown>;
|
|
75
|
+
readonly branches?: readonly string[];
|
|
76
|
+
} | {
|
|
77
|
+
readonly kind: 'deleteApprovalRule';
|
|
78
|
+
readonly ruleId: number;
|
|
79
|
+
} | {
|
|
80
|
+
readonly kind: 'setPushRules';
|
|
81
|
+
readonly body: Record<string, unknown>;
|
|
82
|
+
readonly exists: boolean;
|
|
83
|
+
};
|
|
84
|
+
/** Whether a plan would change anything if applied. */
|
|
85
|
+
export declare function hasChanges(plan: BaselinePlan): boolean;
|
|
86
|
+
/** Every drift a plan found, including the parts it would not correct. */
|
|
87
|
+
export declare function hasDrift(plan: BaselinePlan): boolean;
|
|
88
|
+
/** Plans one project. Never throws for an instance failure; the target carries the error instead. */
|
|
89
|
+
export declare function planProject(client: GitLabClient, target: {
|
|
90
|
+
readonly path: string;
|
|
91
|
+
readonly id: number;
|
|
92
|
+
}, desired: ProjectDesired): Promise<TargetPlan>;
|
|
93
|
+
export declare function planGroup(client: GitLabClient, target: {
|
|
94
|
+
readonly path: string;
|
|
95
|
+
readonly id: number;
|
|
96
|
+
}, desired: GroupDesired): Promise<TargetPlan>;
|