@simplysf/simply-gitlab-core 0.2.0 → 0.4.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 +5 -0
- 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 +2 -2
|
@@ -0,0 +1,139 @@
|
|
|
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
|
+
import { summarise } from './baseline-run.js';
|
|
17
|
+
import { stripControlOneLine } from './text.js';
|
|
18
|
+
/**
|
|
19
|
+
* Renders a plan for a person to read before deciding to apply it.
|
|
20
|
+
*
|
|
21
|
+
* Lives in core rather than in the command for the same reason the table column sets do: the MCP
|
|
22
|
+
* server publishes this plan too, and a renderer only the CLI knew about would drift from what the
|
|
23
|
+
* tool describes. Nothing here writes to a stream; it returns a string.
|
|
24
|
+
*/
|
|
25
|
+
/** Values in a plan are whatever GitLab returned, so they are rendered defensively. */
|
|
26
|
+
function value(input) {
|
|
27
|
+
if (input === undefined)
|
|
28
|
+
return 'unset';
|
|
29
|
+
if (input === null)
|
|
30
|
+
return 'null';
|
|
31
|
+
if (typeof input === 'string')
|
|
32
|
+
return stripControlOneLine(input) || "''";
|
|
33
|
+
if (Array.isArray(input))
|
|
34
|
+
return input.length === 0 ? '[]' : `[${input.map((item) => value(item)).join(', ')}]`;
|
|
35
|
+
if (typeof input === 'object')
|
|
36
|
+
return stripControlOneLine(JSON.stringify(input));
|
|
37
|
+
if (typeof input === 'number' || typeof input === 'boolean')
|
|
38
|
+
return String(input);
|
|
39
|
+
// Anything else came from server JSON; serialise rather than coerce it to [object Object].
|
|
40
|
+
return stripControlOneLine(JSON.stringify(input) ?? '');
|
|
41
|
+
}
|
|
42
|
+
const MARKS = {
|
|
43
|
+
create: '+',
|
|
44
|
+
update: '~',
|
|
45
|
+
delete: '-',
|
|
46
|
+
recreate: '±',
|
|
47
|
+
report: '!',
|
|
48
|
+
};
|
|
49
|
+
function renderChange(change) {
|
|
50
|
+
const mark = MARKS[change.op];
|
|
51
|
+
const key = stripControlOneLine(change.key);
|
|
52
|
+
if (change.op === 'update') {
|
|
53
|
+
const detail = change.detail === undefined ? '' : ` (${change.detail})`;
|
|
54
|
+
return ` ${mark} ${key} ${value(change.from)} → ${value(change.to)}${detail}`;
|
|
55
|
+
}
|
|
56
|
+
if (change.op === 'report') {
|
|
57
|
+
const shift = change.to === undefined ? '' : ` ${value(change.from)} → ${value(change.to)}`;
|
|
58
|
+
return ` ${mark} ${key}${shift} (${change.detail ?? 'reported only'})`;
|
|
59
|
+
}
|
|
60
|
+
const detail = change.detail === undefined ? '' : ` (${change.detail})`;
|
|
61
|
+
const extra = change.to === undefined ? '' : ` ${value(change.to)}`;
|
|
62
|
+
return ` ${mark} ${key}${extra}${detail}`;
|
|
63
|
+
}
|
|
64
|
+
function renderSection(section) {
|
|
65
|
+
if (!section.supported) {
|
|
66
|
+
return [` ${section.section}`, ` ! ${section.unsupportedReason ?? 'unsupported on this instance'}`];
|
|
67
|
+
}
|
|
68
|
+
if (section.changes.length === 0)
|
|
69
|
+
return [];
|
|
70
|
+
return [` ${section.section}`, ...section.changes.map(renderChange)];
|
|
71
|
+
}
|
|
72
|
+
function renderTarget(target) {
|
|
73
|
+
const heading = target.kind === 'group' ? `${target.path} (group)` : target.path;
|
|
74
|
+
if (target.error !== undefined) {
|
|
75
|
+
return [heading, ` ✗ ${stripControlOneLine(target.error)}`];
|
|
76
|
+
}
|
|
77
|
+
const body = target.sections.flatMap(renderSection);
|
|
78
|
+
if (body.length === 0)
|
|
79
|
+
return [heading, ' ✓ matches the baseline'];
|
|
80
|
+
return [heading, ...body];
|
|
81
|
+
}
|
|
82
|
+
/** The whole plan, target by target, with a closing summary. */
|
|
83
|
+
export function renderPlan(plan) {
|
|
84
|
+
const blocks = plan.targets.map((target) => renderTarget(target).join('\n'));
|
|
85
|
+
const counts = summarise(plan);
|
|
86
|
+
const parts = [
|
|
87
|
+
`${counts.targets} target(s): ${counts.matching} match, ${counts.differing} differ`,
|
|
88
|
+
counts.failed > 0 ? `${counts.failed} failed` : undefined,
|
|
89
|
+
counts.unsupported > 0 ? `${counts.unsupported} with an unsupported section` : undefined,
|
|
90
|
+
].filter((part) => part !== undefined);
|
|
91
|
+
return [blocks.join('\n\n'), `${parts.join(', ')}.`].join('\n\n');
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The plan as data, for `--json` and for the MCP tool.
|
|
95
|
+
*
|
|
96
|
+
* `actions` are deliberately dropped: they are how apply executes, not something a caller should
|
|
97
|
+
* depend on, and publishing them would make an internal shape part of the contract.
|
|
98
|
+
*/
|
|
99
|
+
export function planToJson(plan) {
|
|
100
|
+
return {
|
|
101
|
+
targets: plan.targets.map((target) => ({
|
|
102
|
+
kind: target.kind,
|
|
103
|
+
path: target.path,
|
|
104
|
+
id: target.id,
|
|
105
|
+
...(target.error === undefined ? {} : { error: target.error }),
|
|
106
|
+
sections: target.sections.map((section) => ({
|
|
107
|
+
section: section.section,
|
|
108
|
+
supported: section.supported,
|
|
109
|
+
...(section.unsupportedReason === undefined ? {} : { unsupportedReason: section.unsupportedReason }),
|
|
110
|
+
changes: section.changes,
|
|
111
|
+
willChange: section.actions.length > 0,
|
|
112
|
+
})),
|
|
113
|
+
})),
|
|
114
|
+
summary: summarise(plan),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/** What an apply actually did, after the fact. */
|
|
118
|
+
export function renderApply(result) {
|
|
119
|
+
const lines = [];
|
|
120
|
+
let applied = 0;
|
|
121
|
+
let failed = 0;
|
|
122
|
+
for (const target of result.targets) {
|
|
123
|
+
applied += target.applied;
|
|
124
|
+
if (target.error !== undefined) {
|
|
125
|
+
failed += 1;
|
|
126
|
+
const where = target.failedSection === undefined ? '' : ` in ${target.failedSection}`;
|
|
127
|
+
lines.push(`${target.path}`);
|
|
128
|
+
lines.push(` ✗ stopped${where} after ${target.applied} change(s): ${stripControlOneLine(target.error)}`);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (target.applied > 0)
|
|
132
|
+
lines.push(`${target.path} ✓ ${target.applied} change(s)`);
|
|
133
|
+
}
|
|
134
|
+
const summary = failed === 0
|
|
135
|
+
? `Applied ${applied} change(s) across ${result.targets.length} target(s).`
|
|
136
|
+
: `Applied ${applied} change(s); ${failed} target(s) failed. Run plan again to see what is left.`;
|
|
137
|
+
return lines.length === 0 ? summary : [lines.join('\n'), summary].join('\n\n');
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=baseline-render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseline-render.js","sourceRoot":"","sources":["../src/baseline-render.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type BaselineConfig } from './baseline-config.js';
|
|
2
|
+
import { type BaselinePlan, type TargetPlan } from './baseline-plan.js';
|
|
3
|
+
import { type ResolvedTargets } from './baseline-targets.js';
|
|
4
|
+
import type { GitLabClient } from './gitlab-client.js';
|
|
5
|
+
/** How a run is paced and how it reacts to a target failing. */
|
|
6
|
+
export interface RunOptions {
|
|
7
|
+
/**
|
|
8
|
+
* How many targets are in flight at once. Small on purpose: the transport already retries 429
|
|
9
|
+
* with a capped Retry-After, so setting this high trades a shared instance's responsiveness for
|
|
10
|
+
* very little wall-clock.
|
|
11
|
+
*/
|
|
12
|
+
readonly concurrency?: number;
|
|
13
|
+
/** Stop at the first target that fails, for when a systematic problem makes the rest pointless. */
|
|
14
|
+
readonly failFast?: boolean;
|
|
15
|
+
/** Narrow the run to these project paths without editing the config. */
|
|
16
|
+
readonly only?: readonly string[];
|
|
17
|
+
}
|
|
18
|
+
export declare const DEFAULT_CONCURRENCY = 4;
|
|
19
|
+
export interface PlannedRun {
|
|
20
|
+
readonly targets: ResolvedTargets;
|
|
21
|
+
readonly plan: BaselinePlan;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Runs `worker` over `items` with at most `limit` in flight, preserving input order in the result.
|
|
25
|
+
*
|
|
26
|
+
* A pool rather than `Promise.all` in chunks: with fifty targets of uneven size, chunking waits for
|
|
27
|
+
* the slowest project in each batch before starting the next, which is most of the run.
|
|
28
|
+
*/
|
|
29
|
+
export declare function mapPool<T, R>(items: readonly T[], limit: number, worker: (item: T, index: number) => Promise<R>,
|
|
30
|
+
/** Return true to stop scheduling further items. Those already in flight still finish. */
|
|
31
|
+
stopAfter?: (result: R) => boolean): Promise<R[]>;
|
|
32
|
+
/**
|
|
33
|
+
* Resolves the targets and plans every one of them.
|
|
34
|
+
*
|
|
35
|
+
* Groups are planned before projects, matching the order `apply` writes them in: a group setting
|
|
36
|
+
* that projects inherit should be in place before the projects are looked at.
|
|
37
|
+
*/
|
|
38
|
+
export declare function planRun(client: GitLabClient, config: BaselineConfig, options?: RunOptions): Promise<PlannedRun>;
|
|
39
|
+
/** What a plan or an apply amounted to, for the closing summary and the exit code. */
|
|
40
|
+
export interface RunSummary {
|
|
41
|
+
readonly targets: number;
|
|
42
|
+
readonly matching: number;
|
|
43
|
+
readonly differing: number;
|
|
44
|
+
readonly failed: number;
|
|
45
|
+
readonly unsupported: number;
|
|
46
|
+
}
|
|
47
|
+
export declare function summarise(plan: BaselinePlan): RunSummary;
|
|
48
|
+
/** Targets that could not be planned or applied, for reporting before the exit code. */
|
|
49
|
+
export declare function failures(plan: BaselinePlan): readonly TargetPlan[];
|
|
@@ -0,0 +1,102 @@
|
|
|
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
|
+
import { effectiveGroup, effectiveProject } from './baseline-config.js';
|
|
17
|
+
import { planGroup, planProject } from './baseline-plan.js';
|
|
18
|
+
import { resolveTargets } from './baseline-targets.js';
|
|
19
|
+
import { ConfigError } from './errors.js';
|
|
20
|
+
export const DEFAULT_CONCURRENCY = 4;
|
|
21
|
+
const MAX_CONCURRENCY = 16;
|
|
22
|
+
/**
|
|
23
|
+
* Runs `worker` over `items` with at most `limit` in flight, preserving input order in the result.
|
|
24
|
+
*
|
|
25
|
+
* A pool rather than `Promise.all` in chunks: with fifty targets of uneven size, chunking waits for
|
|
26
|
+
* the slowest project in each batch before starting the next, which is most of the run.
|
|
27
|
+
*/
|
|
28
|
+
export async function mapPool(items, limit, worker,
|
|
29
|
+
/** Return true to stop scheduling further items. Those already in flight still finish. */
|
|
30
|
+
stopAfter) {
|
|
31
|
+
const results = [];
|
|
32
|
+
let next = 0;
|
|
33
|
+
let stopped = false;
|
|
34
|
+
const run = async () => {
|
|
35
|
+
for (;;) {
|
|
36
|
+
if (stopped)
|
|
37
|
+
return;
|
|
38
|
+
const index = next++;
|
|
39
|
+
if (index >= items.length)
|
|
40
|
+
return;
|
|
41
|
+
// eslint-disable-next-line no-await-in-loop -- one worker handles its items in sequence
|
|
42
|
+
const value = await worker(items[index], index);
|
|
43
|
+
results.push({ index, value });
|
|
44
|
+
if (stopAfter?.(value) === true)
|
|
45
|
+
stopped = true;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const width = Math.max(1, Math.min(limit, items.length));
|
|
49
|
+
await Promise.all(Array.from({ length: width }, () => run()));
|
|
50
|
+
// Input order, not completion order: a plan that listed fifty projects in a different order on
|
|
51
|
+
// every run would be unreadable as a diff between runs.
|
|
52
|
+
return results.sort((a, b) => a.index - b.index).map(({ value }) => value);
|
|
53
|
+
}
|
|
54
|
+
function resolveConcurrency(value) {
|
|
55
|
+
if (value === undefined)
|
|
56
|
+
return DEFAULT_CONCURRENCY;
|
|
57
|
+
if (!Number.isInteger(value) || value < 1 || value > MAX_CONCURRENCY) {
|
|
58
|
+
throw new ConfigError(`--concurrency must be a whole number between 1 and ${MAX_CONCURRENCY}, got ${value}.`);
|
|
59
|
+
}
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Resolves the targets and plans every one of them.
|
|
64
|
+
*
|
|
65
|
+
* Groups are planned before projects, matching the order `apply` writes them in: a group setting
|
|
66
|
+
* that projects inherit should be in place before the projects are looked at.
|
|
67
|
+
*/
|
|
68
|
+
export async function planRun(client, config, options = {}) {
|
|
69
|
+
const concurrency = resolveConcurrency(options.concurrency);
|
|
70
|
+
const targets = await resolveTargets(client, config, { only: options.only });
|
|
71
|
+
const stopAfter = options.failFast === true ? (result) => result.error !== undefined : undefined;
|
|
72
|
+
const groupPlans = await mapPool(targets.groups, concurrency, (target) => planGroup(client, target, effectiveGroup(config, target.path)), stopAfter);
|
|
73
|
+
// A group that could not be planned under --fail-fast stops the run before any project is read.
|
|
74
|
+
const projectPlans = options.failFast === true && groupPlans.some((group) => group.error !== undefined)
|
|
75
|
+
? []
|
|
76
|
+
: await mapPool(targets.projects, concurrency, (target) => planProject(client, target, effectiveProject(config, target.path)), stopAfter);
|
|
77
|
+
return { targets, plan: { targets: [...groupPlans, ...projectPlans] } };
|
|
78
|
+
}
|
|
79
|
+
export function summarise(plan) {
|
|
80
|
+
let matching = 0;
|
|
81
|
+
let differing = 0;
|
|
82
|
+
let failed = 0;
|
|
83
|
+
let unsupported = 0;
|
|
84
|
+
for (const target of plan.targets) {
|
|
85
|
+
if (target.error !== undefined) {
|
|
86
|
+
failed += 1;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (target.sections.some((section) => !section.supported))
|
|
90
|
+
unsupported += 1;
|
|
91
|
+
if (target.sections.some((section) => section.changes.length > 0))
|
|
92
|
+
differing += 1;
|
|
93
|
+
else
|
|
94
|
+
matching += 1;
|
|
95
|
+
}
|
|
96
|
+
return { targets: plan.targets.length, matching, differing, failed, unsupported };
|
|
97
|
+
}
|
|
98
|
+
/** Targets that could not be planned or applied, for reporting before the exit code. */
|
|
99
|
+
export function failures(plan) {
|
|
100
|
+
return plan.targets.filter((target) => target.error !== undefined);
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=baseline-run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseline-run.js","sourceRoot":"","sources":["../src/baseline-run.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { BaselineConfig } from './baseline-config.js';
|
|
2
|
+
import type { GitLabClient } from './gitlab-client.js';
|
|
3
|
+
/** One thing a run will act on. */
|
|
4
|
+
export interface ResolvedTarget {
|
|
5
|
+
readonly kind: 'project' | 'group';
|
|
6
|
+
/** Full path, as GitLab reports it — what overrides are keyed by, and what output names. */
|
|
7
|
+
readonly path: string;
|
|
8
|
+
readonly id: number;
|
|
9
|
+
}
|
|
10
|
+
export interface ResolvedTargets {
|
|
11
|
+
readonly groups: readonly ResolvedTarget[];
|
|
12
|
+
readonly projects: readonly ResolvedTarget[];
|
|
13
|
+
/** Paths removed by an `exclude` pattern, so a run can say what it left out. */
|
|
14
|
+
readonly excluded: readonly string[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Matches an exclude pattern against a path. Only a trailing `*` is supported: it drops a subtree,
|
|
18
|
+
* which is the case that comes up. A full glob language here would be a second thing to learn for
|
|
19
|
+
* a file whose whole point is being obvious.
|
|
20
|
+
*/
|
|
21
|
+
export declare function matchesExclude(path: string, pattern: string): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Turns the `targets` block into the concrete list a run will act on.
|
|
24
|
+
*
|
|
25
|
+
* A target the token cannot see is a hard error rather than a silent omission: a baseline that
|
|
26
|
+
* quietly skipped six of fifty projects would be worse than one that refused to start, because the
|
|
27
|
+
* six would stay wrong and nobody would know to look.
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolveTargets(client: GitLabClient, config: BaselineConfig, options?: {
|
|
30
|
+
readonly only?: readonly string[];
|
|
31
|
+
}): Promise<ResolvedTargets>;
|
|
@@ -0,0 +1,100 @@
|
|
|
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
|
+
import { ConfigError } from './errors.js';
|
|
17
|
+
/**
|
|
18
|
+
* Matches an exclude pattern against a path. Only a trailing `*` is supported: it drops a subtree,
|
|
19
|
+
* which is the case that comes up. A full glob language here would be a second thing to learn for
|
|
20
|
+
* a file whose whole point is being obvious.
|
|
21
|
+
*/
|
|
22
|
+
export function matchesExclude(path, pattern) {
|
|
23
|
+
const lowered = path.toLowerCase();
|
|
24
|
+
const wanted = pattern.trim().toLowerCase();
|
|
25
|
+
if (wanted.endsWith('*'))
|
|
26
|
+
return lowered.startsWith(wanted.slice(0, -1));
|
|
27
|
+
return lowered === wanted;
|
|
28
|
+
}
|
|
29
|
+
function readProject(row) {
|
|
30
|
+
return typeof row === 'object' && row !== null ? row : {};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Turns the `targets` block into the concrete list a run will act on.
|
|
34
|
+
*
|
|
35
|
+
* A target the token cannot see is a hard error rather than a silent omission: a baseline that
|
|
36
|
+
* quietly skipped six of fifty projects would be worse than one that refused to start, because the
|
|
37
|
+
* six would stay wrong and nobody would know to look.
|
|
38
|
+
*/
|
|
39
|
+
export async function resolveTargets(client, config, options = {}) {
|
|
40
|
+
const targets = config.targets;
|
|
41
|
+
if (targets === undefined) {
|
|
42
|
+
throw new ConfigError('The config declares no targets. Add a "targets" block naming groups or projects.');
|
|
43
|
+
}
|
|
44
|
+
const groups = [];
|
|
45
|
+
const byPath = new Map();
|
|
46
|
+
const excluded = new Set();
|
|
47
|
+
const exclude = targets.exclude ?? [];
|
|
48
|
+
const admit = (path, id) => {
|
|
49
|
+
if (exclude.some((pattern) => matchesExclude(path, pattern))) {
|
|
50
|
+
excluded.add(path);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (!byPath.has(path))
|
|
54
|
+
byPath.set(path, { kind: 'project', path, id });
|
|
55
|
+
};
|
|
56
|
+
/* Each group is a separate paged read; awaiting in sequence keeps the request rate predictable
|
|
57
|
+
against an instance that is also serving everyone else. */
|
|
58
|
+
/* eslint-disable no-await-in-loop */
|
|
59
|
+
for (const reference of targets.groups ?? []) {
|
|
60
|
+
const group = (await client.getGroup(reference).catch((error) => {
|
|
61
|
+
throw new ConfigError(`Target group "${reference}" could not be read: ${error instanceof Error ? error.message : String(error)}`);
|
|
62
|
+
}));
|
|
63
|
+
const path = typeof group.full_path === 'string' ? group.full_path : reference;
|
|
64
|
+
const id = typeof group.id === 'number' ? group.id : 0;
|
|
65
|
+
groups.push({ kind: 'group', path, id });
|
|
66
|
+
const listed = await client.listGroupProjects(reference);
|
|
67
|
+
for (const row of listed.items) {
|
|
68
|
+
const project = readProject(row);
|
|
69
|
+
if (typeof project.path_with_namespace === 'string' && typeof project.id === 'number') {
|
|
70
|
+
admit(project.path_with_namespace, project.id);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (!listed.complete) {
|
|
74
|
+
throw new ConfigError(`Group "${path}" has more projects than one run reads. Narrow it with subgroups or list the projects explicitly.`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
for (const reference of targets.projects ?? []) {
|
|
78
|
+
const project = (await client.getProject(reference).catch((error) => {
|
|
79
|
+
throw new ConfigError(`Target project "${reference}" could not be read: ${error instanceof Error ? error.message : String(error)}`);
|
|
80
|
+
}));
|
|
81
|
+
const path = typeof project.path_with_namespace === 'string' ? project.path_with_namespace : reference;
|
|
82
|
+
const id = typeof project.id === 'number' ? project.id : 0;
|
|
83
|
+
admit(path, id);
|
|
84
|
+
}
|
|
85
|
+
/* eslint-enable no-await-in-loop */
|
|
86
|
+
let projects = [...byPath.values()].sort((a, b) => a.path.localeCompare(b.path));
|
|
87
|
+
// `--target` narrows a run without editing the file, for the loop of fixing one project and
|
|
88
|
+
// re-checking it. Naming something the config does not target is a mistake worth reporting.
|
|
89
|
+
if (options.only !== undefined && options.only.length > 0) {
|
|
90
|
+
const wanted = new Set(options.only.map((path) => path.toLowerCase()));
|
|
91
|
+
const matched = projects.filter((target) => wanted.has(target.path.toLowerCase()));
|
|
92
|
+
const missing = [...wanted].filter((path) => !projects.some((target) => target.path.toLowerCase() === path));
|
|
93
|
+
if (missing.length > 0) {
|
|
94
|
+
throw new ConfigError(`--target named ${missing.join(', ')}, which this config does not target.`);
|
|
95
|
+
}
|
|
96
|
+
projects = matched;
|
|
97
|
+
}
|
|
98
|
+
return { groups, projects, excluded: [...excluded].sort() };
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=baseline-targets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseline-targets.js","sourceRoot":"","sources":["../src/baseline-targets.ts"],"names":[],"mappings":"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"}
|
package/lib/gitlab-client.d.ts
CHANGED
|
@@ -120,6 +120,38 @@ export declare class GitLabClient {
|
|
|
120
120
|
listVariables(project: ProjectRef, limit?: number): Promise<ListResult>;
|
|
121
121
|
listEnvironments(project: ProjectRef, options?: ListEnvironmentsOptions, limit?: number): Promise<ListResult>;
|
|
122
122
|
listReleases(project: ProjectRef, limit?: number): Promise<ListResult>;
|
|
123
|
+
/**
|
|
124
|
+
* Partially updates a project. Only the attributes sent are changed — GitLab does not document
|
|
125
|
+
* that outright, and `test/gitlab-client.test.ts` pins it, because a full-replace PUT would make
|
|
126
|
+
* every baseline apply destructive.
|
|
127
|
+
*/
|
|
128
|
+
updateProject(project: ProjectRef, body: Record<string, unknown>): Promise<unknown>;
|
|
129
|
+
getGroup(group: ProjectRef): Promise<unknown>;
|
|
130
|
+
updateGroup(group: ProjectRef, body: Record<string, unknown>): Promise<unknown>;
|
|
131
|
+
/** Every project in a group, subgroups included — how a baseline names fifty projects at once. */
|
|
132
|
+
listGroupProjects(group: ProjectRef, limit?: number): Promise<ListResult>;
|
|
133
|
+
listProtectedBranches(project: ProjectRef, limit?: number): Promise<ListResult>;
|
|
134
|
+
protectBranch(project: ProjectRef, body: Record<string, unknown>): Promise<unknown>;
|
|
135
|
+
/**
|
|
136
|
+
* Changes an existing branch protection in place. This is what keeps a baseline apply safe: the
|
|
137
|
+
* alternative is DELETE followed by POST, which leaves the branch unprotected in between.
|
|
138
|
+
*/
|
|
139
|
+
patchProtectedBranch(project: ProjectRef, name: string, body: Record<string, unknown>): Promise<unknown>;
|
|
140
|
+
unprotectBranch(project: ProjectRef, name: string): Promise<unknown>;
|
|
141
|
+
listProtectedTags(project: ProjectRef, limit?: number): Promise<ListResult>;
|
|
142
|
+
protectTag(project: ProjectRef, body: Record<string, unknown>): Promise<unknown>;
|
|
143
|
+
unprotectTag(project: ProjectRef, name: string): Promise<unknown>;
|
|
144
|
+
getApprovalSettings(project: ProjectRef): Promise<unknown>;
|
|
145
|
+
/** Approval settings are updated with POST rather than PUT. That is GitLab's shape, not a slip. */
|
|
146
|
+
updateApprovalSettings(project: ProjectRef, body: Record<string, unknown>): Promise<unknown>;
|
|
147
|
+
listApprovalRules(project: ProjectRef, limit?: number): Promise<ListResult>;
|
|
148
|
+
createApprovalRule(project: ProjectRef, body: Record<string, unknown>): Promise<unknown>;
|
|
149
|
+
updateApprovalRule(project: ProjectRef, ruleId: number, body: Record<string, unknown>): Promise<unknown>;
|
|
150
|
+
deleteApprovalRule(project: ProjectRef, ruleId: number): Promise<unknown>;
|
|
151
|
+
getPushRules(project: ProjectRef): Promise<unknown>;
|
|
152
|
+
/** POST creates the project push rule set; PUT edits it once one exists. */
|
|
153
|
+
createPushRules(project: ProjectRef, body: Record<string, unknown>): Promise<unknown>;
|
|
154
|
+
updatePushRules(project: ProjectRef, body: Record<string, unknown>): Promise<unknown>;
|
|
123
155
|
/**
|
|
124
156
|
* Global, group, or project search. GitLab exposes these as three separate endpoints rather
|
|
125
157
|
* than one endpoint with a filter, and the scopes each accepts differ, so the caller's choice
|
package/lib/gitlab-client.js
CHANGED
|
@@ -170,6 +170,102 @@ export class GitLabClient {
|
|
|
170
170
|
async listReleases(project, limit = 20) {
|
|
171
171
|
return this.collect(`/projects/${segment(project)}/releases`, {}, limit);
|
|
172
172
|
}
|
|
173
|
+
// --- Configuration: project and group settings ---
|
|
174
|
+
/**
|
|
175
|
+
* Partially updates a project. Only the attributes sent are changed — GitLab does not document
|
|
176
|
+
* that outright, and `test/gitlab-client.test.ts` pins it, because a full-replace PUT would make
|
|
177
|
+
* every baseline apply destructive.
|
|
178
|
+
*/
|
|
179
|
+
async updateProject(project, body) {
|
|
180
|
+
return this.request(`/projects/${segment(project)}`, { method: 'PUT', body, mutating: true });
|
|
181
|
+
}
|
|
182
|
+
async getGroup(group) {
|
|
183
|
+
return this.request(`/groups/${segment(group)}`, { method: 'GET' });
|
|
184
|
+
}
|
|
185
|
+
async updateGroup(group, body) {
|
|
186
|
+
return this.request(`/groups/${segment(group)}`, { method: 'PUT', body, mutating: true });
|
|
187
|
+
}
|
|
188
|
+
/** Every project in a group, subgroups included — how a baseline names fifty projects at once. */
|
|
189
|
+
async listGroupProjects(group, limit = 500) {
|
|
190
|
+
return this.collect(`/groups/${segment(group)}/projects`, { include_subgroups: true, archived: false, with_shared: false, simple: true }, limit);
|
|
191
|
+
}
|
|
192
|
+
// --- Configuration: protected branches and tags ---
|
|
193
|
+
async listProtectedBranches(project, limit = 100) {
|
|
194
|
+
return this.collect(`/projects/${segment(project)}/protected_branches`, {}, limit);
|
|
195
|
+
}
|
|
196
|
+
async protectBranch(project, body) {
|
|
197
|
+
return this.request(`/projects/${segment(project)}/protected_branches`, {
|
|
198
|
+
method: 'POST',
|
|
199
|
+
body,
|
|
200
|
+
mutating: true,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Changes an existing branch protection in place. This is what keeps a baseline apply safe: the
|
|
205
|
+
* alternative is DELETE followed by POST, which leaves the branch unprotected in between.
|
|
206
|
+
*/
|
|
207
|
+
async patchProtectedBranch(project, name, body) {
|
|
208
|
+
return this.request(`/projects/${segment(project)}/protected_branches/${segment(name)}`, {
|
|
209
|
+
method: 'PATCH',
|
|
210
|
+
body,
|
|
211
|
+
mutating: true,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
async unprotectBranch(project, name) {
|
|
215
|
+
return this.request(`/projects/${segment(project)}/protected_branches/${segment(name)}`, {
|
|
216
|
+
method: 'DELETE',
|
|
217
|
+
mutating: true,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
async listProtectedTags(project, limit = 100) {
|
|
221
|
+
return this.collect(`/projects/${segment(project)}/protected_tags`, {}, limit);
|
|
222
|
+
}
|
|
223
|
+
async protectTag(project, body) {
|
|
224
|
+
return this.request(`/projects/${segment(project)}/protected_tags`, { method: 'POST', body, mutating: true });
|
|
225
|
+
}
|
|
226
|
+
async unprotectTag(project, name) {
|
|
227
|
+
return this.request(`/projects/${segment(project)}/protected_tags/${segment(name)}`, {
|
|
228
|
+
method: 'DELETE',
|
|
229
|
+
mutating: true,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
// --- Configuration: approvals and push rules (Premium/Ultimate) ---
|
|
233
|
+
async getApprovalSettings(project) {
|
|
234
|
+
return this.request(`/projects/${segment(project)}/approvals`, { method: 'GET' });
|
|
235
|
+
}
|
|
236
|
+
/** Approval settings are updated with POST rather than PUT. That is GitLab's shape, not a slip. */
|
|
237
|
+
async updateApprovalSettings(project, body) {
|
|
238
|
+
return this.request(`/projects/${segment(project)}/approvals`, { method: 'POST', body, mutating: true });
|
|
239
|
+
}
|
|
240
|
+
async listApprovalRules(project, limit = 100) {
|
|
241
|
+
return this.collect(`/projects/${segment(project)}/approval_rules`, {}, limit);
|
|
242
|
+
}
|
|
243
|
+
async createApprovalRule(project, body) {
|
|
244
|
+
return this.request(`/projects/${segment(project)}/approval_rules`, { method: 'POST', body, mutating: true });
|
|
245
|
+
}
|
|
246
|
+
async updateApprovalRule(project, ruleId, body) {
|
|
247
|
+
return this.request(`/projects/${segment(project)}/approval_rules/${ruleId}`, {
|
|
248
|
+
method: 'PUT',
|
|
249
|
+
body,
|
|
250
|
+
mutating: true,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
async deleteApprovalRule(project, ruleId) {
|
|
254
|
+
return this.request(`/projects/${segment(project)}/approval_rules/${ruleId}`, {
|
|
255
|
+
method: 'DELETE',
|
|
256
|
+
mutating: true,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
async getPushRules(project) {
|
|
260
|
+
return this.request(`/projects/${segment(project)}/push_rule`, { method: 'GET' });
|
|
261
|
+
}
|
|
262
|
+
/** POST creates the project push rule set; PUT edits it once one exists. */
|
|
263
|
+
async createPushRules(project, body) {
|
|
264
|
+
return this.request(`/projects/${segment(project)}/push_rule`, { method: 'POST', body, mutating: true });
|
|
265
|
+
}
|
|
266
|
+
async updatePushRules(project, body) {
|
|
267
|
+
return this.request(`/projects/${segment(project)}/push_rule`, { method: 'PUT', body, mutating: true });
|
|
268
|
+
}
|
|
173
269
|
// --- Search ---
|
|
174
270
|
/**
|
|
175
271
|
* Global, group, or project search. GitLab exposes these as three separate endpoints rather
|