@simplysf/simply-gitlab 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 +1392 -1203
- package/bin/run.js +9 -9
- package/lib/commands/gitlab/config/apply.d.ts +15 -0
- package/lib/commands/gitlab/config/apply.js +78 -0
- package/lib/commands/gitlab/config/apply.js.map +1 -0
- package/lib/commands/gitlab/config/export.d.ts +11 -0
- package/lib/commands/gitlab/config/export.js +56 -0
- package/lib/commands/gitlab/config/export.js.map +1 -0
- package/lib/commands/gitlab/config/plan.d.ts +14 -0
- package/lib/commands/gitlab/config/plan.js +69 -0
- package/lib/commands/gitlab/config/plan.js.map +1 -0
- package/lib/shared/baseline-flags.d.ts +11 -0
- package/lib/shared/baseline-flags.js +50 -0
- package/lib/shared/baseline-flags.js.map +1 -0
- package/package.json +247 -243
package/bin/run.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// eslint-disable-next-line node/shebang
|
|
4
|
-
async function main() {
|
|
5
|
-
const { execute } = await import('@oclif/core');
|
|
6
|
-
await execute({ dir: import.meta.url });
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
await main();
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// eslint-disable-next-line node/shebang
|
|
4
|
+
async function main() {
|
|
5
|
+
const { execute } = await import('@oclif/core');
|
|
6
|
+
await execute({ dir: import.meta.url });
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
await main();
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { GitLabCommand } from '../../../shared/base-command.js';
|
|
2
|
+
export default class GitlabConfigApply extends GitLabCommand<typeof GitlabConfigApply> {
|
|
3
|
+
static isWrite: boolean;
|
|
4
|
+
static readonly summary = "Bring projects onto a baseline config.";
|
|
5
|
+
static readonly description: string;
|
|
6
|
+
static readonly examples: string[];
|
|
7
|
+
static readonly flags: {
|
|
8
|
+
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
config: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
target: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
concurrency: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
'fail-fast': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
};
|
|
14
|
+
run(): Promise<unknown>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
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 { applyFailures, applyPlan, CliError, hasChanges, loadBaselineConfig, planRun, planToJson, renderApply, renderPlan, } from '@simplysf/simply-gitlab-core';
|
|
17
|
+
import { GitLabCommand, writeFlags } from '../../../shared/base-command.js';
|
|
18
|
+
import { baselineFlags } from '../../../shared/baseline-flags.js';
|
|
19
|
+
export default class GitlabConfigApply extends GitLabCommand {
|
|
20
|
+
static isWrite = true;
|
|
21
|
+
static summary = 'Bring projects onto a baseline config.';
|
|
22
|
+
static description = 'Computes the same plan as config plan, then executes it. Only what the plan showed is sent, ' +
|
|
23
|
+
'so a preview is a guarantee rather than an approximation, and a second run against a ' +
|
|
24
|
+
'converged project sends no requests at all.\n\n' +
|
|
25
|
+
'Sections are applied in a fixed order — settings, protected branches, protected tags, ' +
|
|
26
|
+
'approval settings, approval rules, push rules — because an approval rule scoped to a branch ' +
|
|
27
|
+
'needs that branch protected before it can reference it.\n\n' +
|
|
28
|
+
'By default the config only adds and corrects what it declares. A section that sets ' +
|
|
29
|
+
'"prune": true also deletes entries the config does not mention.\n\n' +
|
|
30
|
+
'One target failing does not abandon the rest; the summary says which ones failed and where ' +
|
|
31
|
+
'they stopped. There is no transaction across GitLab endpoints, so a target that failed ' +
|
|
32
|
+
'part-way keeps the sections already applied — run config plan afterwards to see what is left.';
|
|
33
|
+
static examples = [
|
|
34
|
+
'<%= config.bin %> <%= command.id %> --config ./baseline.json --dry-run',
|
|
35
|
+
'<%= config.bin %> <%= command.id %> --config ./baseline.json',
|
|
36
|
+
'<%= config.bin %> <%= command.id %> --config ./baseline.json --target group/project',
|
|
37
|
+
];
|
|
38
|
+
static flags = {
|
|
39
|
+
...baselineFlags,
|
|
40
|
+
...writeFlags,
|
|
41
|
+
};
|
|
42
|
+
async run() {
|
|
43
|
+
const config = loadBaselineConfig(this.flags.config);
|
|
44
|
+
const options = {
|
|
45
|
+
concurrency: this.flags.concurrency,
|
|
46
|
+
failFast: this.flags['fail-fast'],
|
|
47
|
+
only: this.flags.target,
|
|
48
|
+
};
|
|
49
|
+
const { targets, plan } = await planRun(this.gitlab(), config, options);
|
|
50
|
+
if (!this.jsonEnabled()) {
|
|
51
|
+
this.log(`Targets: ${targets.projects.length} project(s), ${targets.groups.length} group(s).`);
|
|
52
|
+
if (targets.excluded.length > 0)
|
|
53
|
+
this.logSafe(`Excluded: ${targets.excluded.join(', ')}`);
|
|
54
|
+
this.log('');
|
|
55
|
+
this.logSafe(renderPlan(plan));
|
|
56
|
+
this.log('');
|
|
57
|
+
}
|
|
58
|
+
if (this.flags['dry-run']) {
|
|
59
|
+
if (!this.jsonEnabled())
|
|
60
|
+
this.log('Dry run — nothing sent.');
|
|
61
|
+
return planToJson(plan);
|
|
62
|
+
}
|
|
63
|
+
if (!hasChanges(plan)) {
|
|
64
|
+
if (!this.jsonEnabled())
|
|
65
|
+
this.log('Nothing to apply.');
|
|
66
|
+
return { ...planToJson(plan), applied: [] };
|
|
67
|
+
}
|
|
68
|
+
const result = await applyPlan(this.gitlab(), plan, options);
|
|
69
|
+
if (!this.jsonEnabled())
|
|
70
|
+
this.logSafe(renderApply(result));
|
|
71
|
+
const failed = applyFailures(result);
|
|
72
|
+
if (failed.length > 0) {
|
|
73
|
+
throw new CliError(`${failed.length} of ${result.targets.length} target(s) failed. See the output above.`);
|
|
74
|
+
}
|
|
75
|
+
return { ...planToJson(plan), applied: result.targets };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=apply.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply.js","sourceRoot":"","sources":["../../../../src/commands/gitlab/config/apply.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { GitLabCommand } from '../../../shared/base-command.js';
|
|
2
|
+
export default class GitlabConfigExport extends GitLabCommand<typeof GitlabConfigExport> {
|
|
3
|
+
static readonly summary = "Write a baseline config describing an existing project.";
|
|
4
|
+
static readonly description: string;
|
|
5
|
+
static readonly examples: string[];
|
|
6
|
+
static readonly flags: {
|
|
7
|
+
section: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
project: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
};
|
|
10
|
+
run(): Promise<unknown>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
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 { Flags } from '@oclif/core';
|
|
17
|
+
import { EXPORTABLE_SECTIONS, exportConfig } from '@simplysf/simply-gitlab-core';
|
|
18
|
+
import { GitLabCommand, projectFlag } from '../../../shared/base-command.js';
|
|
19
|
+
export default class GitlabConfigExport extends GitLabCommand {
|
|
20
|
+
static summary = 'Write a baseline config describing an existing project.';
|
|
21
|
+
static description = 'Reads one project and prints a configuration file that describes it, ready to be edited and ' +
|
|
22
|
+
'pointed at the rest of your projects. Writing a baseline by hand against forty attributes is ' +
|
|
23
|
+
'miserable and the first draft is wrong; exporting the project that already looks right and ' +
|
|
24
|
+
'deleting what you do not care about is the way to start.\n\n' +
|
|
25
|
+
'The output is deliberately verbose — every supported key, not a curated subset — because ' +
|
|
26
|
+
'deleting a line is easier than discovering that a key exists. Variables are exported as ' +
|
|
27
|
+
'metadata only: keys, scopes, and flags, never values.\n\n' +
|
|
28
|
+
'The exported targets name only the project it came from. Widen that on purpose rather than ' +
|
|
29
|
+
'inheriting a blast radius from an export.';
|
|
30
|
+
static examples = [
|
|
31
|
+
'<%= config.bin %> <%= command.id %> --project group/project > baseline.json',
|
|
32
|
+
'<%= config.bin %> <%= command.id %> --project group/project --section settings,protectedBranches',
|
|
33
|
+
];
|
|
34
|
+
static flags = {
|
|
35
|
+
...projectFlag,
|
|
36
|
+
section: Flags.string({
|
|
37
|
+
summary: 'Comma-separated sections to export. Defaults to all of them.',
|
|
38
|
+
options: [...EXPORTABLE_SECTIONS],
|
|
39
|
+
multiple: true,
|
|
40
|
+
delimiter: ',',
|
|
41
|
+
multipleNonGreedy: true,
|
|
42
|
+
}),
|
|
43
|
+
};
|
|
44
|
+
async run() {
|
|
45
|
+
const sections = this.flags.section;
|
|
46
|
+
const config = await exportConfig(this.gitlab(), this.flags.project, {
|
|
47
|
+
sections: sections === undefined || sections.length === 0 ? undefined : sections,
|
|
48
|
+
});
|
|
49
|
+
// Straight to stdout, formatted, because the point of this command is to be redirected to a
|
|
50
|
+
// file. Under --json oclif prints the same object itself.
|
|
51
|
+
if (!this.jsonEnabled())
|
|
52
|
+
this.logSafe(JSON.stringify(config, null, 2));
|
|
53
|
+
return config;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=export.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"export.js","sourceRoot":"","sources":["../../../../src/commands/gitlab/config/export.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAA0B,MAAM,8BAA8B,CAAC;AACzG,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAE7E,MAAM,CAAC,OAAO,OAAO,kBAAmB,SAAQ,aAAwC;IAC/E,MAAM,CAAmB,OAAO,GAAG,yDAAyD,CAAC;IAC7F,MAAM,CAAmB,WAAW,GACzC,8FAA8F;QAC9F,+FAA+F;QAC/F,6FAA6F;QAC7F,8DAA8D;QAC9D,2FAA2F;QAC3F,0FAA0F;QAC1F,2DAA2D;QAC3D,6FAA6F;QAC7F,2CAA2C,CAAC;IAEvC,MAAM,CAAmB,QAAQ,GAAG;QACzC,6EAA6E;QAC7E,kGAAkG;KACnG,CAAC;IAEK,MAAM,CAAmB,KAAK,GAAG;QACtC,GAAG,WAAW;QACd,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC;YACpB,OAAO,EAAE,8DAA8D;YACvE,OAAO,EAAE,CAAC,GAAG,mBAAmB,CAAC;YACjC,QAAQ,EAAE,IAAI;YACd,SAAS,EAAE,GAAG;YACd,iBAAiB,EAAE,IAAI;SACxB,CAAC;KACH,CAAC;IAEK,KAAK,CAAC,GAAG;QACd,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,OAA0C,CAAC;QACvE,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE;YACnE,QAAQ,EAAE,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ;SACjF,CAAC,CAAC;QAEH,4FAA4F;QAC5F,0DAA0D;QAC1D,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;YAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QACvE,OAAO,MAAM,CAAC;IAChB,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { GitLabCommand } from '../../../shared/base-command.js';
|
|
2
|
+
export default class GitlabConfigPlan extends GitLabCommand<typeof GitlabConfigPlan> {
|
|
3
|
+
static readonly summary = "Report how projects differ from a baseline config.";
|
|
4
|
+
static readonly description: string;
|
|
5
|
+
static readonly examples: string[];
|
|
6
|
+
static readonly flags: {
|
|
7
|
+
'fail-on-drift': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
config: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
target: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
concurrency: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
'fail-fast': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<unknown>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
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 { Flags } from '@oclif/core';
|
|
17
|
+
import { CliError, hasDrift, loadBaselineConfig, planRun, planToJson, renderPlan } from '@simplysf/simply-gitlab-core';
|
|
18
|
+
import { GitLabCommand } from '../../../shared/base-command.js';
|
|
19
|
+
import { baselineFlags } from '../../../shared/baseline-flags.js';
|
|
20
|
+
export default class GitlabConfigPlan extends GitLabCommand {
|
|
21
|
+
static summary = 'Report how projects differ from a baseline config.';
|
|
22
|
+
static description = 'Reads the baseline, expands its targets, and prints what differs on each one. Nothing is ' +
|
|
23
|
+
'sent that changes anything, so this is safe to run against every project you have.\n\n' +
|
|
24
|
+
'Only the attributes the config declares are compared. A project with forty settings the ' +
|
|
25
|
+
'config never mentions shows nothing for them — that is what makes a baseline safe to point ' +
|
|
26
|
+
'at projects that already exist.\n\n' +
|
|
27
|
+
'Entries that exist on the instance but are not in the config are shown as removals, marked ' +
|
|
28
|
+
'as not pruned unless that section opts into pruning. Seeing what you are choosing not to fix ' +
|
|
29
|
+
'is most of the value of running this across fifty projects.\n\n' +
|
|
30
|
+
'Use --fail-on-drift in a scheduled job: it exits 1 when anything differs, so CI can tell you ' +
|
|
31
|
+
'that something drifted without anyone having to read the output.';
|
|
32
|
+
static examples = [
|
|
33
|
+
'<%= config.bin %> <%= command.id %> --config ./baseline.json',
|
|
34
|
+
'<%= config.bin %> <%= command.id %> --config ./baseline.json --target group/project',
|
|
35
|
+
'<%= config.bin %> <%= command.id %> --config ./baseline.json --fail-on-drift',
|
|
36
|
+
'<%= config.bin %> <%= command.id %> --config ./baseline.json --json | jq .summary',
|
|
37
|
+
];
|
|
38
|
+
static flags = {
|
|
39
|
+
...baselineFlags,
|
|
40
|
+
'fail-on-drift': Flags.boolean({
|
|
41
|
+
summary: 'Exit 1 when any target differs from the baseline.',
|
|
42
|
+
default: false,
|
|
43
|
+
}),
|
|
44
|
+
};
|
|
45
|
+
async run() {
|
|
46
|
+
const config = loadBaselineConfig(this.flags.config);
|
|
47
|
+
const { targets, plan } = await planRun(this.gitlab(), config, {
|
|
48
|
+
concurrency: this.flags.concurrency,
|
|
49
|
+
failFast: this.flags['fail-fast'],
|
|
50
|
+
only: this.flags.target,
|
|
51
|
+
});
|
|
52
|
+
if (!this.jsonEnabled()) {
|
|
53
|
+
// What a run is about to touch is the first question anyone should ask and the last one
|
|
54
|
+
// they should have to guess at.
|
|
55
|
+
this.log(`Targets: ${targets.projects.length} project(s), ${targets.groups.length} group(s).`);
|
|
56
|
+
if (targets.excluded.length > 0)
|
|
57
|
+
this.logSafe(`Excluded: ${targets.excluded.join(', ')}`);
|
|
58
|
+
this.log('');
|
|
59
|
+
this.logSafe(renderPlan(plan));
|
|
60
|
+
}
|
|
61
|
+
const drifted = hasDrift(plan);
|
|
62
|
+
if (this.flags['fail-on-drift'] && drifted) {
|
|
63
|
+
// Deliberately after the output: the point of the flag is to fail a job, not to hide why.
|
|
64
|
+
throw new CliError('The baseline does not match. Run without --fail-on-drift to see the plan.');
|
|
65
|
+
}
|
|
66
|
+
return planToJson(plan);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=plan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan.js","sourceRoot":"","sources":["../../../../src/commands/gitlab/config/plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,kBAAkB,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AACvH,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAElE,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,aAAsC;IAC3E,MAAM,CAAmB,OAAO,GAAG,oDAAoD,CAAC;IACxF,MAAM,CAAmB,WAAW,GACzC,2FAA2F;QAC3F,wFAAwF;QACxF,0FAA0F;QAC1F,6FAA6F;QAC7F,qCAAqC;QACrC,6FAA6F;QAC7F,+FAA+F;QAC/F,iEAAiE;QACjE,+FAA+F;QAC/F,kEAAkE,CAAC;IAE9D,MAAM,CAAmB,QAAQ,GAAG;QACzC,8DAA8D;QAC9D,qFAAqF;QACrF,8EAA8E;QAC9E,mFAAmF;KACpF,CAAC;IAEK,MAAM,CAAmB,KAAK,GAAG;QACtC,GAAG,aAAa;QAChB,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC;YAC7B,OAAO,EAAE,mDAAmD;YAC5D,OAAO,EAAE,KAAK;SACf,CAAC;KACH,CAAC;IAEK,KAAK,CAAC,GAAG;QACd,MAAM,MAAM,GAAG,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACrD,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE;YAC7D,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW;YACnC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC;YACjC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM;SACxB,CAAC,CAAC;QAEH,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,wFAAwF;YACxF,gCAAgC;YAChC,IAAI,CAAC,GAAG,CAAC,YAAY,OAAO,CAAC,QAAQ,CAAC,MAAM,gBAAgB,OAAO,CAAC,MAAM,CAAC,MAAM,YAAY,CAAC,CAAC;YAC/F,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC;gBAAE,IAAI,CAAC,OAAO,CAAC,aAAa,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC1F,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACb,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;QACjC,CAAC;QAED,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,IAAI,OAAO,EAAE,CAAC;YAC3C,0FAA0F;YAC1F,MAAM,IAAI,QAAQ,CAAC,2EAA2E,CAAC,CAAC;QAClG,CAAC;QAED,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The flags `config plan` and `config apply` share, defined once so the two cannot disagree about
|
|
3
|
+
* what a run means. Both commands read the same file and resolve the same targets; only what they
|
|
4
|
+
* do afterwards differs.
|
|
5
|
+
*/
|
|
6
|
+
export declare const baselineFlags: {
|
|
7
|
+
config: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
target: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
concurrency: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
'fail-fast': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
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 { Flags } from '@oclif/core';
|
|
17
|
+
import { DEFAULT_CONCURRENCY } from '@simplysf/simply-gitlab-core';
|
|
18
|
+
/**
|
|
19
|
+
* The flags `config plan` and `config apply` share, defined once so the two cannot disagree about
|
|
20
|
+
* what a run means. Both commands read the same file and resolve the same targets; only what they
|
|
21
|
+
* do afterwards differs.
|
|
22
|
+
*/
|
|
23
|
+
export const baselineFlags = {
|
|
24
|
+
config: Flags.string({
|
|
25
|
+
char: 'c',
|
|
26
|
+
summary: 'Path to the baseline configuration file.',
|
|
27
|
+
required: true,
|
|
28
|
+
}),
|
|
29
|
+
target: Flags.string({
|
|
30
|
+
summary: 'Limit the run to this project path. Repeatable.',
|
|
31
|
+
description: 'Narrows a run without editing the config, for the loop of fixing one project and checking ' +
|
|
32
|
+
'it again. Naming a project the config does not target is an error rather than a silent ' +
|
|
33
|
+
'no-op.',
|
|
34
|
+
multiple: true,
|
|
35
|
+
}),
|
|
36
|
+
concurrency: Flags.integer({
|
|
37
|
+
summary: 'How many targets to work on at once.',
|
|
38
|
+
description: 'Small by default. The client already retries a rate-limited request with a capped ' +
|
|
39
|
+
'Retry-After, so raising this trades a shared instance responsiveness for very little ' +
|
|
40
|
+
'wall-clock time.',
|
|
41
|
+
default: DEFAULT_CONCURRENCY,
|
|
42
|
+
min: 1,
|
|
43
|
+
max: 16,
|
|
44
|
+
}),
|
|
45
|
+
'fail-fast': Flags.boolean({
|
|
46
|
+
summary: 'Stop at the first target that fails instead of continuing.',
|
|
47
|
+
default: false,
|
|
48
|
+
}),
|
|
49
|
+
};
|
|
50
|
+
//# sourceMappingURL=baseline-flags.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseline-flags.js","sourceRoot":"","sources":["../../src/shared/baseline-flags.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAEnE;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC;QACnB,IAAI,EAAE,GAAG;QACT,OAAO,EAAE,0CAA0C;QACnD,QAAQ,EAAE,IAAI;KACf,CAAC;IACF,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC;QACnB,OAAO,EAAE,iDAAiD;QAC1D,WAAW,EACT,4FAA4F;YAC5F,yFAAyF;YACzF,QAAQ;QACV,QAAQ,EAAE,IAAI;KACf,CAAC;IACF,WAAW,EAAE,KAAK,CAAC,OAAO,CAAC;QACzB,OAAO,EAAE,sCAAsC;QAC/C,WAAW,EACT,oFAAoF;YACpF,uFAAuF;YACvF,kBAAkB;QACpB,OAAO,EAAE,mBAAmB;QAC5B,GAAG,EAAE,CAAC;QACN,GAAG,EAAE,EAAE;KACR,CAAC;IACF,WAAW,EAAE,KAAK,CAAC,OAAO,CAAC;QACzB,OAAO,EAAE,4DAA4D;QACrE,OAAO,EAAE,KAAK;KACf,CAAC;CACH,CAAC"}
|