@sluice-dev/plugin-campaign 0.0.0-stage → 0.1.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 +201 -0
- package/dist/campaign.d.ts +103 -0
- package/dist/campaign.js +324 -0
- package/dist/campaign.js.map +1 -0
- package/dist/codemod.d.ts +22 -0
- package/dist/codemod.js +83 -0
- package/dist/codemod.js.map +1 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +55 -0
- package/dist/index.js.map +1 -0
- package/package.json +37 -3
- package/README.md +0 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "{}"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { type RuleLanguage } from '@sluice-dev/policy';
|
|
2
|
+
import { type RunInContainerOptions } from '@sluice-dev/sandbox';
|
|
3
|
+
import { type Codemod } from './codemod.js';
|
|
4
|
+
export type Evidence = {
|
|
5
|
+
tests: string;
|
|
6
|
+
scope?: 'repo' | 'all';
|
|
7
|
+
} | {
|
|
8
|
+
rule: {
|
|
9
|
+
language: RuleLanguage;
|
|
10
|
+
pattern: string;
|
|
11
|
+
expect: 'none';
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
export interface StepDef {
|
|
15
|
+
id: string;
|
|
16
|
+
title?: string;
|
|
17
|
+
repos: string[];
|
|
18
|
+
destructive?: boolean;
|
|
19
|
+
apply: {
|
|
20
|
+
copy: string;
|
|
21
|
+
} | {
|
|
22
|
+
codemod: Codemod;
|
|
23
|
+
} | {
|
|
24
|
+
command: string;
|
|
25
|
+
};
|
|
26
|
+
evidence: Evidence[];
|
|
27
|
+
/** Dependency prepare for this step's `tests` evidence (overrides the repo/campaign setting). */
|
|
28
|
+
prepare?: Prepare;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Dependency prepare before offline `tests` evidence: a command run in the
|
|
32
|
+
* repo directory with network at image build time (only dependency manifests
|
|
33
|
+
* are present), `true` for the toolchain default, `false` for none. Omitted:
|
|
34
|
+
* the toolchain default when the repo needs one (Go modules, .NET projects);
|
|
35
|
+
* none for Node.
|
|
36
|
+
*/
|
|
37
|
+
export type Prepare = string | boolean;
|
|
38
|
+
export interface RepoDef {
|
|
39
|
+
name: string;
|
|
40
|
+
path: string;
|
|
41
|
+
/** Evidence image for this repo (default: campaign `evidenceImage`, else the detected toolchain's). */
|
|
42
|
+
image?: string;
|
|
43
|
+
prepare?: Prepare;
|
|
44
|
+
}
|
|
45
|
+
export interface CampaignDef {
|
|
46
|
+
id: string;
|
|
47
|
+
title: string;
|
|
48
|
+
repos: RepoDef[];
|
|
49
|
+
steps: StepDef[];
|
|
50
|
+
/** Image for `tests` evidence; default per repo from its detected toolchain (node/golang/dotnet sdk). */
|
|
51
|
+
evidenceImage?: string;
|
|
52
|
+
prepare?: Prepare;
|
|
53
|
+
}
|
|
54
|
+
export type StepStatus = 'pending' | 'passed' | 'failed' | 'rolled_back' | 'blocked';
|
|
55
|
+
export interface EvidenceResult {
|
|
56
|
+
kind: 'tests' | 'rule';
|
|
57
|
+
repo: string;
|
|
58
|
+
passed: boolean;
|
|
59
|
+
detail: string;
|
|
60
|
+
}
|
|
61
|
+
export interface StepRun {
|
|
62
|
+
status: StepStatus;
|
|
63
|
+
before?: string;
|
|
64
|
+
after?: string;
|
|
65
|
+
changed: string[];
|
|
66
|
+
evidence: EvidenceResult[];
|
|
67
|
+
reason?: string;
|
|
68
|
+
}
|
|
69
|
+
export interface CampaignState {
|
|
70
|
+
id: string;
|
|
71
|
+
status: 'running' | 'completed' | 'failed' | 'blocked';
|
|
72
|
+
reason?: string;
|
|
73
|
+
branch: string;
|
|
74
|
+
repos: Record<string, {
|
|
75
|
+
baseSha: string;
|
|
76
|
+
}>;
|
|
77
|
+
steps: Record<string, Record<string, StepRun>>;
|
|
78
|
+
log: string[];
|
|
79
|
+
}
|
|
80
|
+
export interface LoadedCampaign {
|
|
81
|
+
file: string;
|
|
82
|
+
dir: string;
|
|
83
|
+
def: CampaignDef;
|
|
84
|
+
stateFile: string;
|
|
85
|
+
repoPath(name: string): string;
|
|
86
|
+
}
|
|
87
|
+
export declare function validateCampaign(raw: unknown, source?: string): CampaignDef;
|
|
88
|
+
export declare function loadCampaign(file: string): LoadedCampaign;
|
|
89
|
+
export declare function readState(c: LoadedCampaign): CampaignState | undefined;
|
|
90
|
+
/** Image and prepare step for `tests` evidence in repo `name` of a composed workspace. */
|
|
91
|
+
export declare function evidenceEnv(c: LoadedCampaign, step: Pick<StepDef, 'prepare'>, ws: string, name: string): {
|
|
92
|
+
image: string;
|
|
93
|
+
options: RunInContainerOptions;
|
|
94
|
+
};
|
|
95
|
+
export interface RunOptions {
|
|
96
|
+
log?: (m: string) => void;
|
|
97
|
+
/** Stop after this step (inclusive). */
|
|
98
|
+
until?: string;
|
|
99
|
+
}
|
|
100
|
+
export declare function runCampaign(c: LoadedCampaign, o?: RunOptions): Promise<CampaignState>;
|
|
101
|
+
/** Manually roll back a passed step in one repo (and invalidate every later step there). */
|
|
102
|
+
export declare function rollbackStep(c: LoadedCampaign, stepId: string, repo: string): Promise<CampaignState>;
|
|
103
|
+
export declare function renderStatus(c: LoadedCampaign, s: CampaignState | undefined): string;
|
package/dist/campaign.js
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-repo migration campaigns: an ordered list of steps, each applied to a
|
|
3
|
+
* set of repositories on a `campaign/<id>` branch and gated by evidence
|
|
4
|
+
* (tests in no-network containers against the *current* state of every repo,
|
|
5
|
+
* structural rule checks). A failing step is rolled back and halts the
|
|
6
|
+
* campaign; a `destructive` step runs only when every earlier step has passed
|
|
7
|
+
* for every repository. State is persisted so campaigns resume.
|
|
8
|
+
*/
|
|
9
|
+
import { execFile } from 'node:child_process';
|
|
10
|
+
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
|
|
11
|
+
import { tmpdir } from 'node:os';
|
|
12
|
+
import { dirname, join, resolve } from 'node:path';
|
|
13
|
+
import { parse as parseYaml } from 'yaml';
|
|
14
|
+
import { defaultPrepare, detectToolchain, inRoot, isDependencyManifest } from '@sluice-dev/lang';
|
|
15
|
+
import { scanTree, validateRule } from '@sluice-dev/policy';
|
|
16
|
+
import { git, runInContainer } from '@sluice-dev/sandbox';
|
|
17
|
+
import { applyCodemod } from './codemod.js';
|
|
18
|
+
export function validateCampaign(raw, source = 'campaign') {
|
|
19
|
+
const c = raw;
|
|
20
|
+
if (!c?.id || !/^[a-z0-9][a-z0-9-]*$/.test(c.id))
|
|
21
|
+
throw new Error(`${source}: id required (kebab-case)`);
|
|
22
|
+
if (!Array.isArray(c.repos) || !c.repos.length)
|
|
23
|
+
throw new Error(`${source}: repos required`);
|
|
24
|
+
const names = new Set(c.repos.map((r) => r.name));
|
|
25
|
+
if (names.size !== c.repos.length)
|
|
26
|
+
throw new Error(`${source}: duplicate repo names`);
|
|
27
|
+
if (!Array.isArray(c.steps) || !c.steps.length)
|
|
28
|
+
throw new Error(`${source}: steps required`);
|
|
29
|
+
const prepareOk = (v) => v === undefined || typeof v === 'string' || typeof v === 'boolean';
|
|
30
|
+
if (!prepareOk(c.prepare))
|
|
31
|
+
throw new Error(`${source}: prepare must be a command or true/false`);
|
|
32
|
+
for (const r of c.repos)
|
|
33
|
+
if (!prepareOk(r.prepare))
|
|
34
|
+
throw new Error(`${source}: repo ${r.name} prepare must be a command or true/false`);
|
|
35
|
+
const ids = new Set();
|
|
36
|
+
for (const s of c.steps) {
|
|
37
|
+
if (!s.id || ids.has(s.id))
|
|
38
|
+
throw new Error(`${source}: step ids must be unique (${s.id})`);
|
|
39
|
+
ids.add(s.id);
|
|
40
|
+
for (const r of s.repos ?? [])
|
|
41
|
+
if (!names.has(r))
|
|
42
|
+
throw new Error(`${source}: step ${s.id} targets unknown repo ${r}`);
|
|
43
|
+
if (!s.repos?.length)
|
|
44
|
+
throw new Error(`${source}: step ${s.id} needs repos`);
|
|
45
|
+
if (!prepareOk(s.prepare))
|
|
46
|
+
throw new Error(`${source}: step ${s.id} prepare must be a command or true/false`);
|
|
47
|
+
const kinds = ['copy', 'codemod', 'command'].filter((k) => k in (s.apply ?? {}));
|
|
48
|
+
if (kinds.length !== 1)
|
|
49
|
+
throw new Error(`${source}: step ${s.id} apply needs exactly one of copy/codemod/command`);
|
|
50
|
+
if (!Array.isArray(s.evidence) || !s.evidence.length)
|
|
51
|
+
throw new Error(`${source}: step ${s.id} has no evidence — every step must be evidence-gated`);
|
|
52
|
+
for (const e of s.evidence)
|
|
53
|
+
if ('rule' in e)
|
|
54
|
+
validateRule({ id: 'campaign-rule', message: 'm', severity: 'error', language: e.rule.language, pattern: e.rule.pattern }, `${source}: step ${s.id}`);
|
|
55
|
+
}
|
|
56
|
+
return c;
|
|
57
|
+
}
|
|
58
|
+
export function loadCampaign(file) {
|
|
59
|
+
const abs = resolve(file);
|
|
60
|
+
const dir = dirname(abs);
|
|
61
|
+
const def = validateCampaign(parseYaml(readFileSync(abs, 'utf8')), abs);
|
|
62
|
+
return {
|
|
63
|
+
file: abs,
|
|
64
|
+
dir,
|
|
65
|
+
def,
|
|
66
|
+
stateFile: join(dir, '.sluice', 'campaigns', `${def.id}.json`),
|
|
67
|
+
repoPath: (name) => resolve(dir, def.repos.find((r) => r.name === name).path),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
export function readState(c) {
|
|
71
|
+
return existsSync(c.stateFile) ? JSON.parse(readFileSync(c.stateFile, 'utf8')) : undefined;
|
|
72
|
+
}
|
|
73
|
+
function saveState(c, s) {
|
|
74
|
+
mkdirSync(dirname(c.stateFile), { recursive: true });
|
|
75
|
+
writeFileSync(c.stateFile, JSON.stringify(s, null, 2) + '\n');
|
|
76
|
+
}
|
|
77
|
+
const gitc = (repo, ...args) => git(['-c', 'user.email=campaign@sluice.dev', '-c', 'user.name=sluice-campaign', '-c', 'commit.gpgsign=false', ...args], repo);
|
|
78
|
+
function shell(cmd, cwd) {
|
|
79
|
+
return new Promise((r) => execFile('sh', ['-c', cmd], { cwd, maxBuffer: 16 * 1024 * 1024 }, (e, so, se) => r({ code: e ? 1 : 0, output: String(so) + String(se) })));
|
|
80
|
+
}
|
|
81
|
+
/** Snapshot every repo's campaign branch side by side (consumers import siblings by relative path). */
|
|
82
|
+
async function composeWorkspace(c) {
|
|
83
|
+
const ws = mkdtempSync(join(tmpdir(), 'sluice-campaign-'));
|
|
84
|
+
for (const r of c.def.repos) {
|
|
85
|
+
const target = join(ws, r.name);
|
|
86
|
+
mkdirSync(target, { recursive: true });
|
|
87
|
+
const tar = await new Promise((res, rej) => execFile('git', ['archive', '--format=tar', 'HEAD'], { cwd: c.repoPath(r.name), encoding: 'buffer', maxBuffer: 256 * 1024 * 1024 }, (e, out) => (e ? rej(e) : res(out))));
|
|
88
|
+
await new Promise((res, rej) => {
|
|
89
|
+
const t = execFile('tar', ['-x', '-C', target], (e) => (e ? rej(e) : res()));
|
|
90
|
+
t.stdin.end(tar);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return ws;
|
|
94
|
+
}
|
|
95
|
+
async function gather(c, step, repo, log) {
|
|
96
|
+
const out = [];
|
|
97
|
+
for (const e of step.evidence) {
|
|
98
|
+
if ('tests' in e) {
|
|
99
|
+
const targets = e.scope === 'all' ? c.def.repos.map((r) => r.name) : [repo];
|
|
100
|
+
const ws = await composeWorkspace(c);
|
|
101
|
+
for (const t of targets) {
|
|
102
|
+
const env = evidenceEnv(c, step, ws, t);
|
|
103
|
+
const r = await runInContainer(ws, `cd ${t} && ${e.tests}`, env.image, 300, env.options);
|
|
104
|
+
out.push({
|
|
105
|
+
kind: 'tests',
|
|
106
|
+
repo: t,
|
|
107
|
+
passed: r.passed,
|
|
108
|
+
detail: r.passed ? `${e.tests}: pass` : `${e.tests}: exit ${r.exitCode}\n${r.outputTail}`,
|
|
109
|
+
});
|
|
110
|
+
log(` evidence tests ${t}: ${r.passed ? 'pass' : 'FAIL'}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
const findings = scanTree([
|
|
115
|
+
{
|
|
116
|
+
id: 'campaign-rule',
|
|
117
|
+
message: 'campaign invariant',
|
|
118
|
+
severity: 'error',
|
|
119
|
+
language: e.rule.language,
|
|
120
|
+
pattern: e.rule.pattern,
|
|
121
|
+
},
|
|
122
|
+
], c.repoPath(repo));
|
|
123
|
+
const passed = findings.length === 0;
|
|
124
|
+
out.push({
|
|
125
|
+
kind: 'rule',
|
|
126
|
+
repo,
|
|
127
|
+
passed,
|
|
128
|
+
detail: passed ? `no ${e.rule.pattern}` : findings.map((f) => `${f.file}:${f.line} ${f.text}`).join('\n'),
|
|
129
|
+
});
|
|
130
|
+
log(` evidence rule ${e.rule.pattern} in ${repo}: ${passed ? 'clean' : `${findings.length} left`}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
/** Image and prepare step for `tests` evidence in repo `name` of a composed workspace. */
|
|
136
|
+
export function evidenceEnv(c, step, ws, name) {
|
|
137
|
+
const repo = c.def.repos.find((r) => r.name === name);
|
|
138
|
+
const toolchain = detectToolchain(join(ws, name));
|
|
139
|
+
const image = repo.image ?? c.def.evidenceImage ?? toolchain?.image ?? 'node:22-alpine';
|
|
140
|
+
const setting = step.prepare ?? repo.prepare ?? c.def.prepare;
|
|
141
|
+
let command;
|
|
142
|
+
if (typeof setting === 'string')
|
|
143
|
+
command = setting;
|
|
144
|
+
else if (setting === true) {
|
|
145
|
+
if (!toolchain)
|
|
146
|
+
throw new Error(`prepare: true but no toolchain detected in repo ${name}`);
|
|
147
|
+
command = inRoot(toolchain.root, toolchain.prepareCommand);
|
|
148
|
+
}
|
|
149
|
+
else if (setting === undefined) {
|
|
150
|
+
// the default runs at the repo's detected project root (e.g. backend/go.mod)
|
|
151
|
+
const p = defaultPrepare(toolchain, join(ws, name, toolchain?.root ?? '.'));
|
|
152
|
+
command = p && inRoot(toolchain.root, p);
|
|
153
|
+
}
|
|
154
|
+
const env = toolchain && Object.keys(toolchain.env).length ? toolchain.env : undefined;
|
|
155
|
+
return {
|
|
156
|
+
image,
|
|
157
|
+
options: {
|
|
158
|
+
env,
|
|
159
|
+
prepare: command ? { command: inRoot(name, command), manifest: isDependencyManifest, env } : undefined,
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
async function apply(c, step, repo) {
|
|
164
|
+
const dir = c.repoPath(repo);
|
|
165
|
+
if ('copy' in step.apply)
|
|
166
|
+
cpSync(resolve(c.dir, step.apply.copy), dir, { recursive: true });
|
|
167
|
+
else if ('codemod' in step.apply)
|
|
168
|
+
applyCodemod(dir, step.apply.codemod);
|
|
169
|
+
else {
|
|
170
|
+
const r = await shell(step.apply.command, dir);
|
|
171
|
+
if (r.code !== 0)
|
|
172
|
+
throw new Error(`apply command failed: ${r.output.slice(-2000)}`);
|
|
173
|
+
}
|
|
174
|
+
return (await git(['status', '--porcelain'], dir))
|
|
175
|
+
.split('\n')
|
|
176
|
+
.filter(Boolean)
|
|
177
|
+
.map((l) => l.slice(3))
|
|
178
|
+
.sort();
|
|
179
|
+
}
|
|
180
|
+
export async function runCampaign(c, o = {}) {
|
|
181
|
+
const log = (m) => {
|
|
182
|
+
state.log.push(m);
|
|
183
|
+
o.log?.(m);
|
|
184
|
+
};
|
|
185
|
+
const branch = `campaign/${c.def.id}`;
|
|
186
|
+
const state = readState(c) ?? {
|
|
187
|
+
id: c.def.id,
|
|
188
|
+
status: 'running',
|
|
189
|
+
branch,
|
|
190
|
+
repos: {},
|
|
191
|
+
steps: {},
|
|
192
|
+
log: [],
|
|
193
|
+
};
|
|
194
|
+
if (state.status === 'completed')
|
|
195
|
+
return state;
|
|
196
|
+
state.status = 'running';
|
|
197
|
+
delete state.reason;
|
|
198
|
+
for (const r of c.def.repos) {
|
|
199
|
+
const dir = c.repoPath(r.name);
|
|
200
|
+
if (!existsSync(join(dir, '.git')))
|
|
201
|
+
throw new Error(`repo ${r.name} (${dir}) is not a git repository`);
|
|
202
|
+
const dirty = (await git(['status', '--porcelain'], dir)).trim();
|
|
203
|
+
if (dirty)
|
|
204
|
+
throw new Error(`repo ${r.name} has uncommitted changes`);
|
|
205
|
+
const branches = (await git(['branch', '--list', branch], dir)).trim();
|
|
206
|
+
if (!branches) {
|
|
207
|
+
state.repos[r.name] = { baseSha: (await git(['rev-parse', 'HEAD'], dir)).trim() };
|
|
208
|
+
await git(['checkout', '-q', '-b', branch], dir);
|
|
209
|
+
}
|
|
210
|
+
else
|
|
211
|
+
await git(['checkout', '-q', branch], dir);
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
for (const step of c.def.steps) {
|
|
215
|
+
const runs = (state.steps[step.id] ??= {});
|
|
216
|
+
if (step.destructive) {
|
|
217
|
+
const earlier = c.def.steps.slice(0, c.def.steps.indexOf(step));
|
|
218
|
+
const unmet = earlier.flatMap((s) => s.repos.filter((r) => state.steps[s.id]?.[r]?.status !== 'passed').map((r) => `${r}/${s.id}`));
|
|
219
|
+
if (unmet.length) {
|
|
220
|
+
state.status = 'blocked';
|
|
221
|
+
state.reason = `destructive step ${step.id} blocked: ${unmet.join(', ')} not passed with evidence`;
|
|
222
|
+
for (const r of step.repos)
|
|
223
|
+
runs[r] = { status: 'blocked', changed: [], evidence: [], reason: state.reason };
|
|
224
|
+
log(state.reason);
|
|
225
|
+
return state;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
for (const repo of step.repos) {
|
|
229
|
+
if (runs[repo]?.status === 'passed')
|
|
230
|
+
continue;
|
|
231
|
+
const dir = c.repoPath(repo);
|
|
232
|
+
const before = (await git(['rev-parse', 'HEAD'], dir)).trim();
|
|
233
|
+
log(`${step.id} @ ${repo}${step.destructive ? ' (destructive)' : ''}`);
|
|
234
|
+
let changed = [];
|
|
235
|
+
let evidence = [];
|
|
236
|
+
let error;
|
|
237
|
+
try {
|
|
238
|
+
changed = await apply(c, step, repo);
|
|
239
|
+
if (changed.length) {
|
|
240
|
+
await gitc(dir, 'add', '-A');
|
|
241
|
+
await gitc(dir, 'commit', '-qm', `campaign ${c.def.id}: ${step.id}${step.title ? ` — ${step.title}` : ''}`);
|
|
242
|
+
}
|
|
243
|
+
evidence = await gather(c, step, repo, log);
|
|
244
|
+
}
|
|
245
|
+
catch (e) {
|
|
246
|
+
error = e.message;
|
|
247
|
+
}
|
|
248
|
+
const failed = error ??
|
|
249
|
+
evidence.filter((e) => !e.passed).map((e) => `${e.kind} in ${e.repo}: ${e.detail.split('\n')[0]}`)[0];
|
|
250
|
+
if (failed) {
|
|
251
|
+
// Roll this step back in this repo; the campaign halts here.
|
|
252
|
+
await git(['reset', '-q', '--hard', before], dir);
|
|
253
|
+
await git(['clean', '-qfd'], dir);
|
|
254
|
+
runs[repo] = { status: 'rolled_back', before, changed, evidence, reason: failed };
|
|
255
|
+
state.status = 'failed';
|
|
256
|
+
state.reason = `${step.id} failed in ${repo}: ${failed} (rolled back)`;
|
|
257
|
+
log(state.reason);
|
|
258
|
+
// Every later destructive step is explicitly blocked by this failure.
|
|
259
|
+
for (const later of c.def.steps.slice(c.def.steps.indexOf(step) + 1).filter((x) => x.destructive)) {
|
|
260
|
+
const lr = (state.steps[later.id] ??= {});
|
|
261
|
+
for (const r of later.repos)
|
|
262
|
+
lr[r] = {
|
|
263
|
+
status: 'blocked',
|
|
264
|
+
changed: [],
|
|
265
|
+
evidence: [],
|
|
266
|
+
reason: `blocked: ${repo}/${step.id} did not pass`,
|
|
267
|
+
};
|
|
268
|
+
log(`${later.id} (destructive) blocked: ${repo}/${step.id} did not pass`);
|
|
269
|
+
}
|
|
270
|
+
return state;
|
|
271
|
+
}
|
|
272
|
+
runs[repo] = {
|
|
273
|
+
status: 'passed',
|
|
274
|
+
before,
|
|
275
|
+
after: (await git(['rev-parse', 'HEAD'], dir)).trim(),
|
|
276
|
+
changed,
|
|
277
|
+
evidence,
|
|
278
|
+
};
|
|
279
|
+
log(` passed (${changed.length} file(s) changed)`);
|
|
280
|
+
}
|
|
281
|
+
if (o.until === step.id)
|
|
282
|
+
return state;
|
|
283
|
+
}
|
|
284
|
+
state.status = 'completed';
|
|
285
|
+
log(`campaign ${c.def.id} completed`);
|
|
286
|
+
return state;
|
|
287
|
+
}
|
|
288
|
+
finally {
|
|
289
|
+
saveState(c, state);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
/** Manually roll back a passed step in one repo (and invalidate every later step there). */
|
|
293
|
+
export async function rollbackStep(c, stepId, repo) {
|
|
294
|
+
const state = readState(c);
|
|
295
|
+
if (!state)
|
|
296
|
+
throw new Error('campaign has not run');
|
|
297
|
+
const run = state.steps[stepId]?.[repo];
|
|
298
|
+
if (!run?.before)
|
|
299
|
+
throw new Error(`step ${stepId} has not run in ${repo}`);
|
|
300
|
+
const dir = c.repoPath(repo);
|
|
301
|
+
await git(['checkout', '-q', state.branch], dir);
|
|
302
|
+
await git(['reset', '-q', '--hard', run.before], dir);
|
|
303
|
+
const idx = c.def.steps.findIndex((s) => s.id === stepId);
|
|
304
|
+
for (const s of c.def.steps.slice(idx))
|
|
305
|
+
if (state.steps[s.id]?.[repo])
|
|
306
|
+
state.steps[s.id][repo] = {
|
|
307
|
+
...state.steps[s.id][repo],
|
|
308
|
+
status: 'rolled_back',
|
|
309
|
+
reason: `manual rollback of ${stepId}`,
|
|
310
|
+
};
|
|
311
|
+
state.status = 'running';
|
|
312
|
+
state.log.push(`rolled back ${stepId} in ${repo} to ${run.before.slice(0, 8)}`);
|
|
313
|
+
saveState(c, state);
|
|
314
|
+
return state;
|
|
315
|
+
}
|
|
316
|
+
export function renderStatus(c, s) {
|
|
317
|
+
const lines = [`campaign ${c.def.id}: ${s?.status ?? 'not started'}${s?.reason ? ` — ${s.reason}` : ''}`];
|
|
318
|
+
for (const step of c.def.steps) {
|
|
319
|
+
const cells = step.repos.map((r) => `${r}=${s?.steps[step.id]?.[r]?.status ?? 'pending'}`);
|
|
320
|
+
lines.push(` ${step.id}${step.destructive ? ' (destructive)' : ''}: ${cells.join(' ')}`);
|
|
321
|
+
}
|
|
322
|
+
return lines.join('\n') + '\n';
|
|
323
|
+
}
|
|
324
|
+
//# sourceMappingURL=campaign.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"campaign.js","sourceRoot":"","sources":["../src/campaign.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAClG,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACjG,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAqB,MAAM,oBAAoB,CAAC;AAC/E,OAAO,EAAE,GAAG,EAAE,cAAc,EAA8B,MAAM,qBAAqB,CAAC;AACtF,OAAO,EAAE,YAAY,EAAgB,MAAM,cAAc,CAAC;AA+E1D,MAAM,UAAU,gBAAgB,CAAC,GAAY,EAAE,MAAM,GAAG,UAAU;IAChE,MAAM,CAAC,GAAG,GAAkB,CAAC;IAC7B,IAAI,CAAC,CAAC,EAAE,EAAE,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,4BAA4B,CAAC,CAAC;IACzG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,kBAAkB,CAAC,CAAC;IAC7F,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAClD,IAAI,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,KAAK,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,wBAAwB,CAAC,CAAC;IACtF,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,kBAAkB,CAAC,CAAC;IAC7F,MAAM,SAAS,GAAG,CAAC,CAAU,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,SAAS,CAAC;IACrG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,2CAA2C,CAAC,CAAC;IACjG,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK;QACrB,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,UAAU,CAAC,CAAC,IAAI,0CAA0C,CAAC,CAAC;IAClH,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;IAC9B,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;QACxB,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,8BAA8B,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QAC5F,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACd,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE;YAC3B,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,UAAU,CAAC,CAAC,EAAE,yBAAyB,CAAC,EAAE,CAAC,CAAC;QAC1F,IAAI,CAAC,CAAC,CAAC,KAAK,EAAE,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,UAAU,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC;QAC7E,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,UAAU,CAAC,CAAC,EAAE,0CAA0C,CAAC,CAAC;QAC9G,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC;QACjF,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,UAAU,CAAC,CAAC,EAAE,kDAAkD,CAAC,CAAC;QACnH,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM;YAClD,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,UAAU,CAAC,CAAC,EAAE,sDAAsD,CAAC,CAAC;QACjG,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ;YACxB,IAAI,MAAM,IAAI,CAAC;gBACb,YAAY,CACV,EAAE,EAAE,EAAE,eAAe,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,EAC5G,GAAG,MAAM,UAAU,CAAC,CAAC,EAAE,EAAE,CAC1B,CAAC;IACR,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACzB,MAAM,GAAG,GAAG,gBAAgB,CAAC,SAAS,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACxE,OAAO;QACL,IAAI,EAAE,GAAG;QACT,GAAG;QACH,GAAG;QACH,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,WAAW,EAAE,GAAG,GAAG,CAAC,EAAE,OAAO,CAAC;QAC9D,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAE,CAAC,IAAI,CAAC;KAC/E,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,CAAiB;IACzC,OAAO,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,EAAE,MAAM,CAAC,CAAmB,CAAC,CAAC,CAAC,SAAS,CAAC;AAChH,CAAC;AAED,SAAS,SAAS,CAAC,CAAiB,EAAE,CAAgB;IACpD,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACrD,aAAa,CAAC,CAAC,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;AAChE,CAAC;AAED,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,GAAG,IAAc,EAAE,EAAE,CAC/C,GAAG,CACD,CAAC,IAAI,EAAE,gCAAgC,EAAE,IAAI,EAAE,2BAA2B,EAAE,IAAI,EAAE,sBAAsB,EAAE,GAAG,IAAI,CAAC,EAClH,IAAI,CACL,CAAC;AAEJ,SAAS,KAAK,CAAC,GAAW,EAAE,GAAW;IACrC,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CACvB,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAC9E,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CACxD,CACF,CAAC;AACJ,CAAC;AAED,uGAAuG;AACvG,KAAK,UAAU,gBAAgB,CAAC,CAAiB;IAC/C,MAAM,EAAE,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,kBAAkB,CAAC,CAAC,CAAC;IAC3D,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;QAChC,SAAS,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACvC,MAAM,GAAG,GAAG,MAAM,IAAI,OAAO,CAAS,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CACjD,QAAQ,CACN,KAAK,EACL,CAAC,SAAS,EAAE,cAAc,EAAE,MAAM,CAAC,EACnC,EAAE,GAAG,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,GAAG,IAAI,GAAG,IAAI,EAAE,EAC7E,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CACpC,CACF,CAAC;QACF,MAAM,IAAI,OAAO,CAAO,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;YACnC,MAAM,CAAC,GAAG,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YAC7E,CAAC,CAAC,KAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACpB,CAAC,CAAC,CAAC;IACL,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,KAAK,UAAU,MAAM,CACnB,CAAiB,EACjB,IAAa,EACb,IAAY,EACZ,GAAwB;IAExB,MAAM,GAAG,GAAqB,EAAE,CAAC;IACjC,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC9B,IAAI,OAAO,IAAI,CAAC,EAAE,CAAC;YACjB,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAC5E,MAAM,EAAE,GAAG,MAAM,gBAAgB,CAAC,CAAC,CAAC,CAAC;YACrC,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;gBACxB,MAAM,GAAG,GAAG,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;gBACxC,MAAM,CAAC,GAAG,MAAM,cAAc,CAAC,EAAE,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,GAAG,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;gBACzF,GAAG,CAAC,IAAI,CAAC;oBACP,IAAI,EAAE,OAAO;oBACb,IAAI,EAAE,CAAC;oBACP,MAAM,EAAE,CAAC,CAAC,MAAM;oBAChB,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,UAAU,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,UAAU,EAAE;iBAC1F,CAAC,CAAC;gBACH,GAAG,CAAC,sBAAsB,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;YAChE,CAAC;QACH,CAAC;aAAM,CAAC;YACN,MAAM,QAAQ,GAAG,QAAQ,CACvB;gBACE;oBACE,EAAE,EAAE,eAAe;oBACnB,OAAO,EAAE,oBAAoB;oBAC7B,QAAQ,EAAE,OAAO;oBACjB,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ;oBACzB,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO;iBACxB;aACF,EACD,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CACjB,CAAC;YACF,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC;YACrC,GAAG,CAAC,IAAI,CAAC;gBACP,IAAI,EAAE,MAAM;gBACZ,IAAI;gBACJ,MAAM;gBACN,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;aAC1G,CAAC,CAAC;YACH,GAAG,CAAC,qBAAqB,CAAC,CAAC,IAAI,CAAC,OAAO,OAAO,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,MAAM,OAAO,EAAE,CAAC,CAAC;QACzG,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,WAAW,CACzB,CAAiB,EACjB,IAA8B,EAC9B,EAAU,EACV,IAAY;IAEZ,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAE,CAAC;IACvD,MAAM,SAAS,GAAG,eAAe,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC;IAClD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,aAAa,IAAI,SAAS,EAAE,KAAK,IAAI,gBAAgB,CAAC;IACxF,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC;IAC9D,IAAI,OAA2B,CAAC;IAChC,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,GAAG,OAAO,CAAC;SAC9C,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QAC1B,IAAI,CAAC,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,mDAAmD,IAAI,EAAE,CAAC,CAAC;QAC3F,OAAO,GAAG,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,SAAS,CAAC,cAAc,CAAC,CAAC;IAC7D,CAAC;SAAM,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QACjC,6EAA6E;QAC7E,MAAM,CAAC,GAAG,cAAc,CAAC,SAAS,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC;QAC5E,OAAO,GAAG,CAAC,IAAI,MAAM,CAAC,SAAU,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC5C,CAAC;IACD,MAAM,GAAG,GAAG,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;IACvF,OAAO;QACL,KAAK;QACL,OAAO,EAAE;YACP,GAAG;YACH,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,QAAQ,EAAE,oBAAoB,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS;SACvG;KACF,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,KAAK,CAAC,CAAiB,EAAE,IAAa,EAAE,IAAY;IACjE,MAAM,GAAG,GAAG,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC7B,IAAI,MAAM,IAAI,IAAI,CAAC,KAAK;QAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;SACvF,IAAI,SAAS,IAAI,IAAI,CAAC,KAAK;QAAE,YAAY,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;SACnE,CAAC;QACJ,MAAM,CAAC,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QAC/C,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACtF,CAAC;IACD,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,QAAQ,EAAE,aAAa,CAAC,EAAE,GAAG,CAAC,CAAC;SAC/C,KAAK,CAAC,IAAI,CAAC;SACX,MAAM,CAAC,OAAO,CAAC;SACf,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;SACtB,IAAI,EAAE,CAAC;AACZ,CAAC;AAQD,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,CAAiB,EAAE,IAAgB,EAAE;IACrE,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,EAAE;QACxB,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;IACb,CAAC,CAAC;IACF,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;IACtC,MAAM,KAAK,GAAkB,SAAS,CAAC,CAAC,CAAC,IAAI;QAC3C,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE;QACZ,MAAM,EAAE,SAAS;QACjB,MAAM;QACN,KAAK,EAAE,EAAE;QACT,KAAK,EAAE,EAAE;QACT,GAAG,EAAE,EAAE;KACR,CAAC;IACF,IAAI,KAAK,CAAC,MAAM,KAAK,WAAW;QAAE,OAAO,KAAK,CAAC;IAC/C,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC;IACzB,OAAO,KAAK,CAAC,MAAM,CAAC;IACpB,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,KAAK,GAAG,2BAA2B,CAAC,CAAC;QACvG,MAAM,KAAK,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,QAAQ,EAAE,aAAa,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACjE,IAAI,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,0BAA0B,CAAC,CAAC;QACrE,MAAM,QAAQ,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACvE,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,WAAW,EAAE,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;YAClF,MAAM,GAAG,CAAC,CAAC,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC;QACnD,CAAC;;YAAM,MAAM,GAAG,CAAC,CAAC,UAAU,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC;IACpD,CAAC;IACD,IAAI,CAAC;QACH,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;YAC/B,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC;YAC3C,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;gBACrB,MAAM,OAAO,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;gBAChE,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAClC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,KAAK,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,CAC9F,CAAC;gBACF,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;oBACjB,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC;oBACzB,KAAK,CAAC,MAAM,GAAG,oBAAoB,IAAI,CAAC,EAAE,aAAa,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,2BAA2B,CAAC;oBACnG,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK;wBAAE,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;oBAC7G,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;oBAClB,OAAO,KAAK,CAAC;gBACf,CAAC;YACH,CAAC;YACD,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;gBAC9B,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,KAAK,QAAQ;oBAAE,SAAS;gBAC9C,MAAM,GAAG,GAAG,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;gBAC7B,MAAM,MAAM,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,WAAW,EAAE,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;gBAC9D,GAAG,CAAC,GAAG,IAAI,CAAC,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACvE,IAAI,OAAO,GAAa,EAAE,CAAC;gBAC3B,IAAI,QAAQ,GAAqB,EAAE,CAAC;gBACpC,IAAI,KAAyB,CAAC;gBAC9B,IAAI,CAAC;oBACH,OAAO,GAAG,MAAM,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;oBACrC,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;wBACnB,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;wBAC7B,MAAM,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;oBAC9G,CAAC;oBACD,QAAQ,GAAG,MAAM,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;gBAC9C,CAAC;gBAAC,OAAO,CAAC,EAAE,CAAC;oBACX,KAAK,GAAI,CAAW,CAAC,OAAO,CAAC;gBAC/B,CAAC;gBACD,MAAM,MAAM,GACV,KAAK;oBACL,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;gBACxG,IAAI,MAAM,EAAE,CAAC;oBACX,6DAA6D;oBAC7D,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC;oBAClD,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC;oBAClC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;oBAClF,KAAK,CAAC,MAAM,GAAG,QAAQ,CAAC;oBACxB,KAAK,CAAC,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE,cAAc,IAAI,KAAK,MAAM,gBAAgB,CAAC;oBACvE,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;oBAClB,sEAAsE;oBACtE,KAAK,MAAM,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC;wBAClG,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC;wBAC1C,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,KAAK;4BACzB,EAAE,CAAC,CAAC,CAAC,GAAG;gCACN,MAAM,EAAE,SAAS;gCACjB,OAAO,EAAE,EAAE;gCACX,QAAQ,EAAE,EAAE;gCACZ,MAAM,EAAE,YAAY,IAAI,IAAI,IAAI,CAAC,EAAE,eAAe;6BACnD,CAAC;wBACJ,GAAG,CAAC,GAAG,KAAK,CAAC,EAAE,2BAA2B,IAAI,IAAI,IAAI,CAAC,EAAE,eAAe,CAAC,CAAC;oBAC5E,CAAC;oBACD,OAAO,KAAK,CAAC;gBACf,CAAC;gBACD,IAAI,CAAC,IAAI,CAAC,GAAG;oBACX,MAAM,EAAE,QAAQ;oBAChB,MAAM;oBACN,KAAK,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,WAAW,EAAE,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE;oBACrD,OAAO;oBACP,QAAQ;iBACT,CAAC;gBACF,GAAG,CAAC,aAAa,OAAO,CAAC,MAAM,mBAAmB,CAAC,CAAC;YACtD,CAAC;YACD,IAAI,CAAC,CAAC,KAAK,KAAK,IAAI,CAAC,EAAE;gBAAE,OAAO,KAAK,CAAC;QACxC,CAAC;QACD,KAAK,CAAC,MAAM,GAAG,WAAW,CAAC;QAC3B,GAAG,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,EAAE,YAAY,CAAC,CAAC;QACtC,OAAO,KAAK,CAAC;IACf,CAAC;YAAS,CAAC;QACT,SAAS,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACtB,CAAC;AACH,CAAC;AAED,4FAA4F;AAC5F,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,CAAiB,EAAE,MAAc,EAAE,IAAY;IAChF,MAAM,KAAK,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;IAC3B,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;IACpD,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;IACxC,IAAI,CAAC,GAAG,EAAE,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,QAAQ,MAAM,mBAAmB,IAAI,EAAE,CAAC,CAAC;IAC3E,MAAM,GAAG,GAAG,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC7B,MAAM,GAAG,CAAC,CAAC,UAAU,EAAE,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC;IACjD,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC;IACtD,MAAM,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,CAAC;IAC1D,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;QACpC,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC;YAC3B,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAE,CAAC,IAAI,CAAC,GAAG;gBACzB,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAE,CAAC,IAAI,CAAE;gBAC5B,MAAM,EAAE,aAAa;gBACrB,MAAM,EAAE,sBAAsB,MAAM,EAAE;aACvC,CAAC;IACN,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC;IACzB,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,eAAe,MAAM,OAAO,IAAI,OAAO,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;IAChF,SAAS,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACpB,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,CAAiB,EAAE,CAA4B;IAC1E,MAAM,KAAK,GAAG,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,IAAI,aAAa,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC1G,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,IAAI,SAAS,EAAE,CAAC,CAAC;QAC3F,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC7F,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AACjC,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type Language } from '@sluice-dev/lang';
|
|
2
|
+
export interface CodemodRule {
|
|
3
|
+
pattern: string;
|
|
4
|
+
rewrite: string;
|
|
5
|
+
}
|
|
6
|
+
export interface Codemod {
|
|
7
|
+
language: Language;
|
|
8
|
+
rules: CodemodRule[];
|
|
9
|
+
/** Only files under these prefixes. */
|
|
10
|
+
paths?: string[];
|
|
11
|
+
}
|
|
12
|
+
/** Languages a codemod touches: JS and TS codemods cover both; Go and C# only their own files. */
|
|
13
|
+
export declare function codemodLanguages(language: Language): Language[];
|
|
14
|
+
export declare function applyCodemodToSource(text: string, mod: Codemod, file?: string): {
|
|
15
|
+
text: string;
|
|
16
|
+
edits: number;
|
|
17
|
+
};
|
|
18
|
+
/** Apply a codemod to every file of its language family in a tree; returns files changed. */
|
|
19
|
+
export declare function applyCodemod(dir: string, mod: Codemod): {
|
|
20
|
+
file: string;
|
|
21
|
+
edits: number;
|
|
22
|
+
}[];
|
package/dist/codemod.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural codemods on ast-grep: `pattern` with metavariables ($A, $$$ARGS)
|
|
3
|
+
* rewritten to a template using the same metavariables. Rules apply in order;
|
|
4
|
+
* the tree is re-parsed between rules so later rules see earlier rewrites.
|
|
5
|
+
* Languages: JavaScript/TypeScript (one family), Go, C#.
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { LANGUAGES, isLanguage, languageOf, listSourceFiles, parseSource } from '@sluice-dev/lang';
|
|
10
|
+
import { findPattern } from '@sluice-dev/policy';
|
|
11
|
+
/** Languages a codemod touches: JS and TS codemods cover both; Go and C# only their own files. */
|
|
12
|
+
export function codemodLanguages(language) {
|
|
13
|
+
if (!isLanguage(language))
|
|
14
|
+
throw new Error(`codemod language must be ${LANGUAGES.join('|')}`);
|
|
15
|
+
return language === 'javascript' || language === 'typescript' ? ['javascript', 'typescript'] : [language];
|
|
16
|
+
}
|
|
17
|
+
const DEFAULT_FILE = {
|
|
18
|
+
javascript: 'file.js',
|
|
19
|
+
typescript: 'file.ts',
|
|
20
|
+
go: 'file.go',
|
|
21
|
+
csharp: 'File.cs',
|
|
22
|
+
};
|
|
23
|
+
function expand(template, node) {
|
|
24
|
+
return template.replace(/\$\$\$([A-Z_][A-Z0-9_]*)|\$([A-Z_][A-Z0-9_]*)/g, (whole, multi, single) => {
|
|
25
|
+
if (multi) {
|
|
26
|
+
const nodes = node.getMultipleMatches(multi);
|
|
27
|
+
if (!nodes.length)
|
|
28
|
+
return '';
|
|
29
|
+
const src = node.getRoot().root().text();
|
|
30
|
+
return src.slice(nodes[0].range().start.index, nodes[nodes.length - 1].range().end.index);
|
|
31
|
+
}
|
|
32
|
+
const m = node.getMatch(single);
|
|
33
|
+
if (!m)
|
|
34
|
+
throw new Error(`codemod rewrite uses ${whole} but the pattern does not bind it`);
|
|
35
|
+
return m.text();
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
export function applyCodemodToSource(text, mod, file = DEFAULT_FILE[mod.language]) {
|
|
39
|
+
const family = codemodLanguages(mod.language);
|
|
40
|
+
const fileLang = languageOf(file);
|
|
41
|
+
if (!fileLang || !family.includes(fileLang))
|
|
42
|
+
return { text, edits: 0 };
|
|
43
|
+
// a TypeScript codemod parses .js files as TypeScript too (types in the patterns/templates)
|
|
44
|
+
const lang = mod.language === 'typescript' ? 'typescript' : fileLang;
|
|
45
|
+
const parseAs = lang === 'typescript' && fileLang === 'javascript'
|
|
46
|
+
? file.replace(/\.[^.]+$/, file.endsWith('x') ? '.tsx' : '.ts')
|
|
47
|
+
: file;
|
|
48
|
+
// statement patterns match the trailing semicolon (JS/TS/C#); keep it if the template omits it. Go has none.
|
|
49
|
+
const semicolons = lang !== 'go';
|
|
50
|
+
let current = text;
|
|
51
|
+
let count = 0;
|
|
52
|
+
for (const r of mod.rules) {
|
|
53
|
+
const root = parseSource(lang, current, parseAs).root();
|
|
54
|
+
const edits = findPattern(root, lang, r.pattern).map((n) => {
|
|
55
|
+
let out = expand(r.rewrite, n);
|
|
56
|
+
if (semicolons && n.text().endsWith(';') && !out.trimEnd().endsWith(';'))
|
|
57
|
+
out += ';';
|
|
58
|
+
return n.replace(out);
|
|
59
|
+
});
|
|
60
|
+
if (edits.length) {
|
|
61
|
+
current = root.commitEdits(edits);
|
|
62
|
+
count += edits.length;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { text: current, edits: count };
|
|
66
|
+
}
|
|
67
|
+
/** Apply a codemod to every file of its language family in a tree; returns files changed. */
|
|
68
|
+
export function applyCodemod(dir, mod) {
|
|
69
|
+
const out = [];
|
|
70
|
+
for (const f of listSourceFiles(dir, codemodLanguages(mod.language))) {
|
|
71
|
+
if (mod.paths?.length && !mod.paths.some((p) => f.startsWith(p)))
|
|
72
|
+
continue;
|
|
73
|
+
const p = join(dir, f);
|
|
74
|
+
const before = readFileSync(p, 'utf8');
|
|
75
|
+
const r = applyCodemodToSource(before, mod, f);
|
|
76
|
+
if (r.edits && r.text !== before) {
|
|
77
|
+
writeFileSync(p, r.text);
|
|
78
|
+
out.push({ file: f, edits: r.edits });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=codemod.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codemod.js","sourceRoot":"","sources":["../src/codemod.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export * from './campaign.js';
|
|
2
|
+
export * from './codemod.js';
|
|
3
|
+
declare const _default: {
|
|
4
|
+
name: string;
|
|
5
|
+
version: string;
|
|
6
|
+
apiVersion: string;
|
|
7
|
+
description: string;
|
|
8
|
+
commands: ({
|
|
9
|
+
path: string[];
|
|
10
|
+
summary: string;
|
|
11
|
+
args: {
|
|
12
|
+
name: string;
|
|
13
|
+
required: true;
|
|
14
|
+
}[];
|
|
15
|
+
options: {
|
|
16
|
+
flags: string;
|
|
17
|
+
description: string;
|
|
18
|
+
}[];
|
|
19
|
+
run({ args, options }: import("@sluice-dev/plugin-api").CommandInput, ctx: import("@sluice-dev/plugin-api").PluginContext): Promise<0 | 1>;
|
|
20
|
+
} | {
|
|
21
|
+
path: string[];
|
|
22
|
+
summary: string;
|
|
23
|
+
args: {
|
|
24
|
+
name: string;
|
|
25
|
+
required: true;
|
|
26
|
+
}[];
|
|
27
|
+
options: {
|
|
28
|
+
flags: string;
|
|
29
|
+
description: string;
|
|
30
|
+
}[];
|
|
31
|
+
run({ args, options }: import("@sluice-dev/plugin-api").CommandInput, ctx: import("@sluice-dev/plugin-api").PluginContext): number;
|
|
32
|
+
} | {
|
|
33
|
+
path: string[];
|
|
34
|
+
summary: string;
|
|
35
|
+
args: {
|
|
36
|
+
name: string;
|
|
37
|
+
required: true;
|
|
38
|
+
}[];
|
|
39
|
+
options: {
|
|
40
|
+
flags: string;
|
|
41
|
+
description: string;
|
|
42
|
+
required: true;
|
|
43
|
+
}[];
|
|
44
|
+
run({ args, options }: import("@sluice-dev/plugin-api").CommandInput, ctx: import("@sluice-dev/plugin-api").PluginContext): Promise<number>;
|
|
45
|
+
})[];
|
|
46
|
+
};
|
|
47
|
+
export default _default;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { definePlugin } from '@sluice-dev/plugin-api';
|
|
3
|
+
import { loadCampaign, readState, renderStatus, rollbackStep, runCampaign } from './campaign.js';
|
|
4
|
+
export * from './campaign.js';
|
|
5
|
+
export * from './codemod.js';
|
|
6
|
+
export default definePlugin({
|
|
7
|
+
name: '@sluice-dev/plugin-campaign',
|
|
8
|
+
version: '0.1.0',
|
|
9
|
+
apiVersion: '^1.0.0',
|
|
10
|
+
description: 'Multi-repo migrations with evidence-gated steps and rollback',
|
|
11
|
+
commands: [
|
|
12
|
+
{
|
|
13
|
+
path: ['campaign', 'run'],
|
|
14
|
+
summary: 'Run (or resume) a campaign; every step is evidence-gated, failures roll back and halt',
|
|
15
|
+
args: [{ name: 'file', required: true }],
|
|
16
|
+
options: [{ flags: '--until <step>', description: 'stop after this step' }],
|
|
17
|
+
async run({ args, options }, ctx) {
|
|
18
|
+
const c = loadCampaign(resolve(ctx.cwd, args[0]));
|
|
19
|
+
const s = await runCampaign(c, {
|
|
20
|
+
until: options.until,
|
|
21
|
+
log: (m) => ctx.stdout.write(m + '\n'),
|
|
22
|
+
});
|
|
23
|
+
ctx.stdout.write(renderStatus(c, s));
|
|
24
|
+
return s.status === 'completed' || (options.until && s.status === 'running') ? 0 : 1;
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
path: ['campaign', 'status'],
|
|
29
|
+
summary: 'Show the per-repo state of every campaign step',
|
|
30
|
+
args: [{ name: 'file', required: true }],
|
|
31
|
+
options: [{ flags: '--json', description: 'JSON output' }],
|
|
32
|
+
run({ args, options }, ctx) {
|
|
33
|
+
const c = loadCampaign(resolve(ctx.cwd, args[0]));
|
|
34
|
+
const s = readState(c);
|
|
35
|
+
ctx.stdout.write(options.json ? JSON.stringify(s ?? null, null, 2) + '\n' : renderStatus(c, s));
|
|
36
|
+
return 0;
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
path: ['campaign', 'rollback'],
|
|
41
|
+
summary: 'Roll back a step in one repo (and every later step there)',
|
|
42
|
+
args: [{ name: 'file', required: true }],
|
|
43
|
+
options: [
|
|
44
|
+
{ flags: '--step <id>', description: 'step', required: true },
|
|
45
|
+
{ flags: '--repo <name>', description: 'repository', required: true },
|
|
46
|
+
],
|
|
47
|
+
async run({ args, options }, ctx) {
|
|
48
|
+
const c = loadCampaign(resolve(ctx.cwd, args[0]));
|
|
49
|
+
ctx.stdout.write(renderStatus(c, await rollbackStep(c, String(options.step), String(options.repo))));
|
|
50
|
+
return 0;
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
});
|
|
55
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,40 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sluice-dev/plugin-campaign",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Multi-repo migration state machine with evidence-gated steps and rollback.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"@sluice-dev/source": "./src/index.ts",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=22.13.0"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/ODumpling/sluice.git",
|
|
23
|
+
"directory": "plugins/campaign"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/ODumpling/sluice#readme",
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@ast-grep/napi": "^0.51.2",
|
|
31
|
+
"yaml": "^2.9.1",
|
|
32
|
+
"@sluice-dev/lang": "0.1.0",
|
|
33
|
+
"@sluice-dev/policy": "0.1.0",
|
|
34
|
+
"@sluice-dev/sandbox": "0.1.0",
|
|
35
|
+
"@sluice-dev/plugin-api": "0.1.0"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsc -p tsconfig.build.json"
|
|
39
|
+
}
|
|
6
40
|
}
|
package/README.md
DELETED