@macedon-technologies/batman 1.17.2 → 1.18.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/.jsii +205 -26
- package/.swamp.yaml +6 -0
- package/API.md +164 -1
- package/CLAUDE.md +44 -0
- package/architecture-rules/README.md +42 -0
- package/architecture-rules/check.ts +162 -0
- package/architecture-rules/rules.json +3 -0
- package/architecture-rules/tsconfig.json +14 -0
- package/attestation/manifest.ts +40 -0
- package/attestation/tsconfig.json +13 -0
- package/attestation/verify.ts +142 -0
- package/build-tools/scan-generated-workflows.ts +18 -11
- package/lib/Application.js +6 -1
- package/lib/BatmanProject.js +8 -2
- package/lib/BootcampProject.js +8 -2
- package/lib/CodeOwnersFile.js +1 -1
- package/lib/ProductionDeployWorkflow.js +1 -1
- package/lib/ProjenWorkflowHardening.d.ts +18 -0
- package/lib/ProjenWorkflowHardening.js +59 -0
- package/lib/SoftwareFactoryArchitectureRules.d.ts +10 -0
- package/lib/SoftwareFactoryArchitectureRules.js +245 -0
- package/lib/SoftwareFactoryAttestation.d.ts +18 -0
- package/lib/SoftwareFactoryAttestation.js +221 -0
- package/lib/SoftwareFactoryFiles.d.ts +45 -0
- package/lib/SoftwareFactoryFiles.js +102 -0
- package/lib/SoftwareFactoryModels.d.ts +25 -0
- package/lib/SoftwareFactoryModels.js +306 -0
- package/lib/SoftwareFactoryOpenSpecGates.d.ts +8 -0
- package/lib/SoftwareFactoryOpenSpecGates.js +75 -0
- package/lib/SoftwareFactoryWorkflows.d.ts +28 -0
- package/lib/SoftwareFactoryWorkflows.js +326 -0
- package/lib/StagingDeployWorkflow.js +1 -1
- package/lib/WorkflowActionVersions.d.ts +31 -1
- package/lib/WorkflowActionVersions.js +5 -1
- package/lib/WorkflowAutoDiscover.js +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +2 -1
- package/lib/utils.js +29 -1
- package/models/@goodcraft/github/platform-common-pr.yaml +10 -0
- package/models/@swamp/software-factory/platform-common-factory.yaml +240 -0
- package/models/@webframp/github/platform-common-issues.yaml +8 -0
- package/models/command/shell/platform-common-shell-architecture.yaml +8 -0
- package/models/command/shell/platform-common-shell-manifest.yaml +8 -0
- package/models/command/shell/platform-common-shell.yaml +8 -0
- package/openspec-gates/check-tasks-complete.ts +44 -0
- package/openspec-gates/tsconfig.json +13 -0
- package/package.json +6 -1
- package/workflows/workflow-openspec-plan-verify.yaml +51 -0
- package/workflows/workflow-openspec-tasks-verify.yaml +51 -0
- package/workflows/workflow-pre-pr-verify.yaml +118 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SoftwareFactoryAttestation = void 0;
|
|
4
|
+
const projen_1 = require("projen");
|
|
5
|
+
/**
|
|
6
|
+
* The tamper-evident PR attestation mechanism: `manifest.ts` hashes the
|
|
7
|
+
* files that determine what pre-pr-verify checked, `verify.ts` re-checks
|
|
8
|
+
* those hashes (plus commit/status/freshness) against a PR's embedded
|
|
9
|
+
* `<!-- attestation-manifest -->` block at merge time. Fully generic engine
|
|
10
|
+
* logic — Batman owns and evolves these, so both are managed `TextFile`s.
|
|
11
|
+
*/
|
|
12
|
+
class SoftwareFactoryAttestation {
|
|
13
|
+
constructor(project, options) {
|
|
14
|
+
new projen_1.TextFile(project, 'attestation/manifest.ts', {
|
|
15
|
+
marker: true,
|
|
16
|
+
lines: [
|
|
17
|
+
'/**',
|
|
18
|
+
' * Single source of truth for which files\' content determines "was this PR',
|
|
19
|
+
' * actually verified against the rules it claims." Hashed by both the',
|
|
20
|
+
' * pre-pr-verify workflow (to embed in the PR\'s attestation) and the',
|
|
21
|
+
' * verify-attestation GitHub Action (to check nothing drifted since). Keeping',
|
|
22
|
+
' * one list in one file is what keeps generator and checker from disagreeing',
|
|
23
|
+
' * about what "the config" even is.',
|
|
24
|
+
' */',
|
|
25
|
+
"import { createHash } from 'node:crypto';",
|
|
26
|
+
"import { readFileSync } from 'node:fs';",
|
|
27
|
+
"import { join } from 'node:path';",
|
|
28
|
+
'',
|
|
29
|
+
'export const MANIFEST_FILES: readonly string[] = [',
|
|
30
|
+
" 'workflows/workflow-pre-pr-verify.yaml',",
|
|
31
|
+
" 'workflows/workflow-openspec-plan-verify.yaml',",
|
|
32
|
+
" 'workflows/workflow-openspec-tasks-verify.yaml',",
|
|
33
|
+
` 'models/@swamp/software-factory/${options.repoName}-factory.yaml',`,
|
|
34
|
+
" 'architecture-rules/rules.json',",
|
|
35
|
+
" 'architecture-rules/check.ts',",
|
|
36
|
+
" 'openspec-gates/check-tasks-complete.ts',",
|
|
37
|
+
" 'CLAUDE.md',",
|
|
38
|
+
'];',
|
|
39
|
+
'',
|
|
40
|
+
'export interface Manifest {',
|
|
41
|
+
' generatedAt: string;',
|
|
42
|
+
' files: Record<string, string>;',
|
|
43
|
+
'}',
|
|
44
|
+
'',
|
|
45
|
+
"export function computeManifest(repoRoot: string = join(__dirname, '..')): Manifest {",
|
|
46
|
+
' const files: Record<string, string> = {};',
|
|
47
|
+
' for (const relativePath of MANIFEST_FILES) {',
|
|
48
|
+
' const contents = readFileSync(join(repoRoot, relativePath));',
|
|
49
|
+
" files[relativePath] = createHash('sha256').update(contents).digest('hex');",
|
|
50
|
+
' }',
|
|
51
|
+
' return { generatedAt: new Date().toISOString(), files };',
|
|
52
|
+
'}',
|
|
53
|
+
'',
|
|
54
|
+
'if (require.main === module) {',
|
|
55
|
+
' console.log(JSON.stringify(computeManifest(), null, 2));',
|
|
56
|
+
'}',
|
|
57
|
+
],
|
|
58
|
+
});
|
|
59
|
+
new projen_1.TextFile(project, 'attestation/verify.ts', {
|
|
60
|
+
marker: true,
|
|
61
|
+
lines: [
|
|
62
|
+
'/**',
|
|
63
|
+
' * Checks the `<!-- attestation-manifest ... -->` block a factory-opened PR',
|
|
64
|
+
' * embeds in its body (see the open-pr stage in',
|
|
65
|
+
` * models/@swamp/software-factory/${options.repoName}-factory.yaml) against reality at the`,
|
|
66
|
+
' * commit being merged. Per stack72.dev/ai-broke-the-assumptions-behind-ci/:',
|
|
67
|
+
' * this proves the verification-relevant config hasn\'t drifted since the',
|
|
68
|
+
' * attestation was generated — it does NOT independently prove the tests',
|
|
69
|
+
' * in that attestation actually ran. That\'s an accepted limitation, not an',
|
|
70
|
+
' * oversight; see the article and attestation/manifest.ts\'s own comment.',
|
|
71
|
+
' */',
|
|
72
|
+
"import { readFileSync } from 'node:fs';",
|
|
73
|
+
"import { computeManifest, type Manifest } from './manifest';",
|
|
74
|
+
'',
|
|
75
|
+
'const MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days',
|
|
76
|
+
'// Global, and deliberately loose about what\'s *between* the marker and the',
|
|
77
|
+
'// block\'s own closing "-->" — PR bodies can (and, it turns out, do) mention',
|
|
78
|
+
'// the literal "<!-- attestation-manifest -->" syntax in prose describing',
|
|
79
|
+
'// this mechanism. Each match is independently validated below; a mention',
|
|
80
|
+
'// with no JSON inside it is skipped rather than treated as a parse error.',
|
|
81
|
+
'const MANIFEST_BLOCK_RE = /<!--\\s*attestation-manifest([\\s\\S]*?)-->/g;',
|
|
82
|
+
'',
|
|
83
|
+
'interface EmbeddedManifest extends Manifest {',
|
|
84
|
+
' commit: string;',
|
|
85
|
+
' status: string;',
|
|
86
|
+
'}',
|
|
87
|
+
'',
|
|
88
|
+
'function isEmbeddedManifest(value: unknown): value is EmbeddedManifest {',
|
|
89
|
+
' const candidate = value as Partial<EmbeddedManifest> | null;',
|
|
90
|
+
' return (',
|
|
91
|
+
" typeof candidate === 'object' &&",
|
|
92
|
+
' candidate !== null &&',
|
|
93
|
+
" typeof candidate.commit === 'string' &&",
|
|
94
|
+
" typeof candidate.status === 'string' &&",
|
|
95
|
+
" typeof candidate.generatedAt === 'string' &&",
|
|
96
|
+
" typeof candidate.files === 'object' &&",
|
|
97
|
+
' candidate.files !== null',
|
|
98
|
+
' );',
|
|
99
|
+
'}',
|
|
100
|
+
'',
|
|
101
|
+
'// Balanced-brace scan rather than a regex for the JSON itself: the manifest',
|
|
102
|
+
'// has nested objects (files: {...}), so a non-greedy `\\{.*?\\}` would stop at',
|
|
103
|
+
'// the first inner "}" instead of the real end of the object.',
|
|
104
|
+
'function extractBalancedJson(text: string): string | null {',
|
|
105
|
+
" const start = text.indexOf('{');",
|
|
106
|
+
' if (start === -1) return null;',
|
|
107
|
+
' let depth = 0;',
|
|
108
|
+
' for (let i = start; i < text.length; i++) {',
|
|
109
|
+
" if (text[i] === '{') depth++;",
|
|
110
|
+
" else if (text[i] === '}') {",
|
|
111
|
+
' depth--;',
|
|
112
|
+
' if (depth === 0) return text.slice(start, i + 1);',
|
|
113
|
+
' }',
|
|
114
|
+
' }',
|
|
115
|
+
' return null;',
|
|
116
|
+
'}',
|
|
117
|
+
'',
|
|
118
|
+
'function extractEmbeddedManifest(body: string): EmbeddedManifest {',
|
|
119
|
+
' let matchedAnyBlock = false;',
|
|
120
|
+
' for (const match of body.matchAll(MANIFEST_BLOCK_RE)) {',
|
|
121
|
+
' matchedAnyBlock = true;',
|
|
122
|
+
' const jsonText = extractBalancedJson(match[1]);',
|
|
123
|
+
' if (!jsonText) continue;',
|
|
124
|
+
' try {',
|
|
125
|
+
' const parsed = JSON.parse(jsonText);',
|
|
126
|
+
' if (isEmbeddedManifest(parsed)) return parsed;',
|
|
127
|
+
' } catch {',
|
|
128
|
+
' // Not this one — keep looking at the next attestation-manifest mention.',
|
|
129
|
+
' }',
|
|
130
|
+
' }',
|
|
131
|
+
' throw new Error(',
|
|
132
|
+
' matchedAnyBlock',
|
|
133
|
+
" ? 'Found <!-- attestation-manifest --> mention(s) in the PR body, but none contained a complete, valid manifest.'",
|
|
134
|
+
" : 'No <!-- attestation-manifest --> block found in the PR body. This PR was never verified by ' +",
|
|
135
|
+
' \'pre-pr-verify (or the block was removed) — run the factory\\\'s pre-pr-verify workflow and re-open/update the PR.\',',
|
|
136
|
+
' );',
|
|
137
|
+
'}',
|
|
138
|
+
'',
|
|
139
|
+
'function verify(prBody: string, expectedCommit: string): string[] {',
|
|
140
|
+
' let embedded: EmbeddedManifest;',
|
|
141
|
+
' try {',
|
|
142
|
+
' embedded = extractEmbeddedManifest(prBody);',
|
|
143
|
+
' } catch (err) {',
|
|
144
|
+
' return [err instanceof Error ? err.message : String(err)];',
|
|
145
|
+
' }',
|
|
146
|
+
'',
|
|
147
|
+
' const errors: string[] = [];',
|
|
148
|
+
'',
|
|
149
|
+
' if (embedded.commit !== expectedCommit) {',
|
|
150
|
+
' errors.push(',
|
|
151
|
+
' `Attestation is for commit ${embedded.commit}, but the PR\'s head commit is ${expectedCommit}. ` +',
|
|
152
|
+
" 'New commits were pushed without re-running pre-pr-verify.',",
|
|
153
|
+
' );',
|
|
154
|
+
' }',
|
|
155
|
+
'',
|
|
156
|
+
" if (embedded.status !== 'succeeded') {",
|
|
157
|
+
' errors.push(`Attestation status is "${embedded.status}", not "succeeded".`);',
|
|
158
|
+
' }',
|
|
159
|
+
'',
|
|
160
|
+
' const ageMs = Date.now() - Date.parse(embedded.generatedAt);',
|
|
161
|
+
' if (!Number.isFinite(ageMs)) {',
|
|
162
|
+
' errors.push(`Attestation generatedAt ("${embedded.generatedAt}") is not a parseable timestamp.`);',
|
|
163
|
+
' } else if (ageMs > MAX_AGE_MS) {',
|
|
164
|
+
' errors.push(`Attestation is ${Math.floor(ageMs / (24 * 60 * 60 * 1000))} days old (max ${MAX_AGE_MS / (24 * 60 * 60 * 1000)}) — re-run pre-pr-verify.`);',
|
|
165
|
+
' }',
|
|
166
|
+
'',
|
|
167
|
+
' const actual = computeManifest();',
|
|
168
|
+
' for (const file of Object.keys(actual.files)) {',
|
|
169
|
+
' if (embedded.files[file] !== actual.files[file]) {',
|
|
170
|
+
' errors.push(`${file} has changed since the attestation was generated (expected ${embedded.files[file] ?? \'missing\'}, got ${actual.files[file]}).`);',
|
|
171
|
+
' }',
|
|
172
|
+
' }',
|
|
173
|
+
' for (const file of Object.keys(embedded.files)) {',
|
|
174
|
+
' if (!(file in actual.files)) {',
|
|
175
|
+
' errors.push(`${file} is in the attestation manifest but no longer in the current file list (attestation/manifest.ts\'s MANIFEST_FILES changed).`);',
|
|
176
|
+
' }',
|
|
177
|
+
' }',
|
|
178
|
+
'',
|
|
179
|
+
' return errors;',
|
|
180
|
+
'}',
|
|
181
|
+
'',
|
|
182
|
+
'function parseArgs(argv: string[]): { bodyFile: string; commit: string } {',
|
|
183
|
+
" const bodyFile = argv[argv.indexOf('--body-file') + 1];",
|
|
184
|
+
" const commit = argv[argv.indexOf('--commit') + 1];",
|
|
185
|
+
' if (!bodyFile || !commit) {',
|
|
186
|
+
" throw new Error('Usage: verify.ts --body-file <path> --commit <sha>');",
|
|
187
|
+
' }',
|
|
188
|
+
' return { bodyFile, commit };',
|
|
189
|
+
'}',
|
|
190
|
+
'',
|
|
191
|
+
'if (require.main === module) {',
|
|
192
|
+
' const { bodyFile, commit } = parseArgs(process.argv.slice(2));',
|
|
193
|
+
" const prBody = readFileSync(bodyFile, 'utf8');",
|
|
194
|
+
' const errors = verify(prBody, commit);',
|
|
195
|
+
' if (errors.length > 0) {',
|
|
196
|
+
" console.error('Attestation verification failed:\\n');",
|
|
197
|
+
' for (const error of errors) {',
|
|
198
|
+
' console.error(` - ${error}`);',
|
|
199
|
+
' }',
|
|
200
|
+
' process.exit(1);',
|
|
201
|
+
' }',
|
|
202
|
+
' console.log(`Attestation verified: commit ${commit}, all ${Object.keys(computeManifest().files).length} tracked files match.`);',
|
|
203
|
+
'}',
|
|
204
|
+
],
|
|
205
|
+
});
|
|
206
|
+
new projen_1.JsonFile(project, 'attestation/tsconfig.json', {
|
|
207
|
+
marker: false,
|
|
208
|
+
obj: {
|
|
209
|
+
extends: '../tsconfig.json',
|
|
210
|
+
compilerOptions: {
|
|
211
|
+
noEmit: true,
|
|
212
|
+
rootDir: '.',
|
|
213
|
+
},
|
|
214
|
+
include: ['*.ts'],
|
|
215
|
+
exclude: ['node_modules'],
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
exports.SoftwareFactoryAttestation = SoftwareFactoryAttestation;
|
|
221
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU29mdHdhcmVGYWN0b3J5QXR0ZXN0YXRpb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvU29mdHdhcmVGYWN0b3J5QXR0ZXN0YXRpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEsbUNBQTRDO0FBVzVDOzs7Ozs7R0FNRztBQUNILE1BQWEsMEJBQTBCO0lBQ3JDLFlBQVksT0FBb0IsRUFBRSxPQUEwQztRQUMxRSxJQUFJLGlCQUFRLENBQUMsT0FBTyxFQUFFLHlCQUF5QixFQUFFO1lBQy9DLE1BQU0sRUFBRSxJQUFJO1lBQ1osS0FBSyxFQUFFO2dCQUNMLEtBQUs7Z0JBQ0wsNkVBQTZFO2dCQUM3RSx1RUFBdUU7Z0JBQ3ZFLHVFQUF1RTtnQkFDdkUsK0VBQStFO2dCQUMvRSw4RUFBOEU7Z0JBQzlFLHFDQUFxQztnQkFDckMsS0FBSztnQkFDTCwyQ0FBMkM7Z0JBQzNDLHlDQUF5QztnQkFDekMsbUNBQW1DO2dCQUNuQyxFQUFFO2dCQUNGLG9EQUFvRDtnQkFDcEQsNENBQTRDO2dCQUM1QyxtREFBbUQ7Z0JBQ25ELG9EQUFvRDtnQkFDcEQscUNBQXFDLE9BQU8sQ0FBQyxRQUFRLGlCQUFpQjtnQkFDdEUsb0NBQW9DO2dCQUNwQyxrQ0FBa0M7Z0JBQ2xDLDZDQUE2QztnQkFDN0MsZ0JBQWdCO2dCQUNoQixJQUFJO2dCQUNKLEVBQUU7Z0JBQ0YsNkJBQTZCO2dCQUM3Qix3QkFBd0I7Z0JBQ3hCLGtDQUFrQztnQkFDbEMsR0FBRztnQkFDSCxFQUFFO2dCQUNGLHVGQUF1RjtnQkFDdkYsNkNBQTZDO2dCQUM3QyxnREFBZ0Q7Z0JBQ2hELGtFQUFrRTtnQkFDbEUsZ0ZBQWdGO2dCQUNoRixLQUFLO2dCQUNMLDREQUE0RDtnQkFDNUQsR0FBRztnQkFDSCxFQUFFO2dCQUNGLGdDQUFnQztnQkFDaEMsNERBQTREO2dCQUM1RCxHQUFHO2FBQ0o7U0FDRixDQUFDLENBQUM7UUFFSCxJQUFJLGlCQUFRLENBQUMsT0FBTyxFQUFFLHVC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MEM7Z0JBQzFDLDRCQUE0QjtnQkFDNUIsMkRBQTJEO2dCQUMzRCxtQ0FBbUM7Z0JBQ25DLHNDQUFzQztnQkFDdEMsT0FBTztnQkFDUCxzQkFBc0I7Z0JBQ3RCLEtBQUs7Z0JBQ0wsbUlBQW1JO2dCQUNuSSxHQUFHO2FBQ0o7U0FDRixDQUFDLENBQUM7UUFFSCxJQUFJLGlCQUFRLENBQUMsT0FBTyxFQUFFLDJCQUEyQixFQUFFO1lBQ2pELE1BQU0sRUFBRSxLQUFLO1lBQ2IsR0FBRyxFQUFFO2dCQUNILE9BQU8sRUFBRSxrQkFBa0I7Z0JBQzNCLGVBQWUsRUFBRTtvQkFDZixNQUFNLEVBQUUsSUFBSTtvQkFDWixPQUFPLEVBQUUsR0FBRztpQkFDYjtnQkFDRCxPQUFPLEVBQUUsQ0FBQyxNQUFNLENBQUM7Z0JBQ2pCLE9BQU8sRUFBRSxDQUFDLGNBQWMsQ0FBQzthQUMxQjtTQUNGLENBQUMsQ0FBQztJQUNMLENBQUM7Q0FDRjtBQWpORCxnRUFpTkMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBKc29uRmlsZSwgVGV4dEZpbGUgfSBmcm9tICdwcm9qZW4nO1xuaW1wb3J0IHsgTm9kZVByb2plY3QgfSBmcm9tICdwcm9qZW4vbGliL2phdmFzY3JpcHQnO1xuXG5leHBvcnQgaW50ZXJmYWNlIFNvZnR3YXJlRmFjdG9yeUF0dGVzdGF0aW9uT3B0aW9ucyB7XG4gIC8qKlxuICAgKiBUaGUgYmFyZSByZXBvIG5hbWUsIHVzZWQgdG8gcG9pbnQgTUFOSUZFU1RfRklMRVMgYXRcbiAgICogYG1vZGVscy9Ac3dhbXAvc29mdHdhcmUtZmFjdG9yeS88cmVwb05hbWU+LWZhY3RvcnkueWFtbGAuXG4gICAqL1xuICByZWFkb25seSByZXBvTmFtZTogc3RyaW5nO1xufVxuXG4vKipcbiAqIFRoZSB0YW1wZXItZXZpZGVudCBQUiBhdHRlc3RhdGlvbiBtZWNoYW5pc206IGBtYW5pZmVzdC50c2AgaGFzaGVzIHRoZVxuICogZmlsZXMgdGhhdCBkZXRlcm1pbmUgd2hhdCBwcmUtcHItdmVyaWZ5IGNoZWNrZWQsIGB2ZXJpZnkudHNgIHJlLWNoZWNrc1xuICogdGhvc2UgaGFzaGVzIChwbHVzIGNvbW1pdC9zdGF0dXMvZnJlc2huZXNzKSBhZ2FpbnN0IGEgUFIncyBlbWJlZGRlZFxuICogYDwhLS0gYXR0ZXN0YXRpb24tbWFuaWZlc3QgLS0+YCBibG9jayBhdCBtZXJnZSB0aW1lLiBGdWxseSBnZW5lcmljIGVuZ2luZVxuICogbG9naWMg4oCUIEJhdG1hbiBvd25zIGFuZCBldm9sdmVzIHRoZXNlLCBzbyBib3RoIGFyZSBtYW5hZ2VkIGBUZXh0RmlsZWBzLlxuICovXG5leHBvcnQgY2xhc3MgU29mdHdhcmVGYWN0b3J5QXR0ZXN0YXRpb24ge1xuICBjb25zdHJ1Y3Rvcihwcm9qZWN0OiBOb2RlUHJvamVjdCwgb3B0aW9uczogU29mdHdhcmVGYWN0b3J5QXR0ZXN0YXRpb25PcHRpb25zKSB7XG4gICAgbmV3IFRleHRGaWxlKHByb2plY3QsICdhdHRlc3RhdGlvbi9tYW5pZmVzdC50cycsIHtcbiAgICAgIG1hcmtlcjogdHJ1ZSxcbiAgICAgIGxpbmVzOiBbXG4gICAgICAgICcvKionLFxuICAgICAgICAnICogU2luZ2xlIHNvdXJjZSBvZiB0cnV0aCBmb3Igd2hpY2ggZmlsZXNcXCcgY29udGVudCBkZXRlcm1pbmVzIFwid2FzIHRoaXMgUFInLFxuICAgICAgICAnICogYWN0dWFsbHkgdmVyaWZpZWQgYWdhaW5zdCB0aGUgcnVsZXMgaXQgY2xhaW1zLlwiIEhhc2hlZCBieSBib3RoIHRoZScsXG4gICAgICAgICcgKiBwcmUtcHItdmVyaWZ5IHdvcmtmbG93ICh0byBlbWJlZCBpbiB0aGUgUFJcXCdzIGF0dGVzdGF0aW9uKSBhbmQgdGhlJyxcbiAgICAgICAgJyAqIHZlcmlmeS1hdHRlc3RhdGlvbiBHaXRIdWIgQWN0aW9uICh0byBjaGVjayBub3RoaW5nIGRyaWZ0ZWQgc2luY2UpLiBLZWVwaW5nJyxcbiAgICAgICAgJyAqIG9uZSBsaXN0IGluIG9uZSBmaWxlIGlzIHdoYXQga2VlcHMgZ2VuZXJhdG9yIGFuZCBjaGVja2VyIGZyb20gZGlzYWdyZWVpbmcnLFxuICAgICAgICAnICogYWJvdXQgd2hhdCBcInRoZSBjb25maWdcIiBldmVuIGlzLicsXG4gICAgICAgICcgKi8nLFxuICAgICAgICBcImltcG9ydCB7IGNyZWF0ZUhhc2ggfSBmcm9tICdub2RlOmNyeXB0byc7XCIsXG4gICAgICAgIFwiaW1wb3J0IHsgcmVhZEZpbGVTeW5jIH0gZnJvbSAnbm9kZTpmcyc7XCIsXG4gICAgICAgIFwiaW1wb3J0IHsgam9pbiB9IGZyb20gJ25vZGU6cGF0aCc7XCIsXG4gICAgICAgICcnLFxuICAgICAgICAnZXhwb3J0IGNvbnN0IE1BTklGRVNUX0ZJTEVTOiByZWFkb25seSBzdHJpbmdbXSA9IFsnLFxuICAgICAgICBcIiAgJ3dvcmtmbG93cy93b3JrZmxvdy1wcmUtcHItdmVyaWZ5LnlhbWwnLFwiLFxuICAgICAgICBcIiAgJ3dvcmtmbG93cy93b3JrZmxvdy1vcGVuc3BlYy1wbGFuLXZlcmlmeS55YW1sJyxcIixcbiAgICAgICAgXCIgICd3b3JrZmxvd3Mvd29ya2Zsb3ctb3BlbnNwZWMtdGFza3MtdmVyaWZ5LnlhbWwnLFwiLFxuICAgICAgICBgICAnbW9kZWxzL0Bzd2FtcC9zb2Z0d2FyZS1mYWN0b3J5LyR7b3B0aW9ucy5yZXBvTmFtZX0tZmFjdG9yeS55YW1sJyxgLFxuICAgICAgICBcIiAgJ2FyY2hpdGVjdHVyZS1ydWxlcy9ydWxlcy5qc29uJyxcIixcbiAgICAgICAgXCIgICdhcmNoaXRlY3R1cmUtcnVsZXMvY2hlY2sudHMnLFwiLFxuICAgICAgICBcIiAgJ29wZW5zcGVjLWdhdGVzL2NoZWNrLXRhc2tzLWNvbXBsZXRlLnRzJyxcIixcbiAgICAgICAgXCIgICdDTEFVREUubWQnLFwiLFxuICAgICAgICAnXTsnLFxuICAgICAgICAnJyxcbiAgICAgICAgJ2V4cG9ydCBpbnRlcmZhY2UgTWFuaWZlc3QgeycsXG4gICAgICAgICcgIGdlbmVyYXRlZEF0OiBzdHJpbmc7JyxcbiAgICAgICAgJyAgZmlsZXM6IFJlY29yZDxzdHJpbmcsIHN0cmluZz47JyxcbiAgICAgICAgJ30nLFxuICAgICAgICAnJyxcbiAgICAgICAgXCJleHBvcnQgZnVuY3Rpb24gY29tcHV0ZU1hbmlmZXN0KHJlcG9Sb290OiBzdHJpbmcgPSBqb2luKF9fZGlybmFtZSwgJy4uJykpOiBNYW5pZmVzdCB7XCIsXG4gICAgICAgICcgIGNvbnN0IGZpbGVzOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+ID0ge307JyxcbiAgICAgICAgJyAgZm9yIChjb25zdCByZWxhdGl2ZVBhdGggb2YgTUFOSUZFU1RfRklMRVMpIHsnLFxuICAgICAgICAnICAgIGNvbnN0IGNvbnRlbnRzID0gcmVhZEZpbGVTeW5jKGpvaW4ocmVwb1Jvb3QsIHJlbGF0aXZlUGF0aCkpOycsXG4gICAgICAgIFwiICAgIGZpbGVzW3JlbGF0aXZlUGF0aF0gPSBjcmVhdGVIYXNoKCdzaGEyNTYnKS51cGRhdGUoY29udGVudHMpLmRpZ2VzdCgnaGV4Jyk7XCIsXG4gICAgICAgICcgIH0nLFxuICAgICAgICAnICByZXR1cm4geyBnZW5lcmF0ZWRBdDogbmV3IERhdGUoKS50b0lTT1N0cmluZygpLCBmaWxlcyB9OycsXG4gICAgICAgICd9JyxcbiAgICAgICAgJycsXG4gICAgICAgICdpZiAocmVxdWlyZS5tYWluID09PSBtb2R1bGUpIHsnLFxuICAgICAgICAnICBjb25zb2xlLmxvZyhKU09OLnN0cmluZ2lmeShjb21wdXRlTWFuaWZlc3QoKSwgbnVsbCwgMikpOycsXG4gICAgICAgICd9JyxcbiAgICAgIF0sXG4gICAgfSk7XG5cbiAgICBuZXcgVGV4dEZpbGUocHJvamVjdCwgJ2F0dGVzdGF0aW9uL3ZlcmlmeS50cycsIHtcbiAgICAgIG1hcmtlcjogdHJ1ZSxcbiAgICAgIGxpbmVzOiBbXG4gICAgICAgICcvKionLFxuICAgICAgICAnICogQ2hlY2tzIHRoZSBgPCEtLSBhdHRlc3RhdGlvbi1tYW5pZmVzdCAuLi4gLS0+YCBibG9jayBhIGZhY3Rvcnktb3BlbmVkIFBSJyxcbiAgICAgICAgJyAqIGVtYmVkcyBpbiBpdHMgYm9keSAoc2VlIHRoZSBvcGVuLXByIHN0YWdlIGluJyxcbiAgICAgICAgYCAqIG1vZGVscy9Ac3dhbXAvc29mdHdhcmUtZmFjdG9yeS8ke29wdGlvbnMucmVwb05hbWV9LWZhY3RvcnkueWFtbCkgYWdhaW5zdCByZWFsaXR5IGF0IHRoZWAsXG4gICAgICAgICcgKiBjb21taXQgYmVpbmcgbWVyZ2VkLiBQZXIgc3RhY2s3Mi5kZXYvYWktYnJva2UtdGhlLWFzc3VtcHRpb25zLWJlaGluZC1jaS86JyxcbiAgICAgICAgJyAqIHRoaXMgcHJvdmVzIHRoZSB2ZXJpZmljYXRpb24tcmVsZXZhbnQgY29uZmlnIGhhc25cXCd0IGRyaWZ0ZWQgc2luY2UgdGhlJyxcbiAgICAgICAgJyAqIGF0dGVzdGF0aW9uIHdhcyBnZW5lcmF0ZWQg4oCUIGl0IGRvZXMgTk9UIGluZGVwZW5kZW50bHkgcHJvdmUgdGhlIHRlc3RzJyxcbiAgICAgICAgJyAqIGluIHRoYXQgYXR0ZXN0YXRpb24gYWN0dWFsbHkgcmFuLiBUaGF0XFwncyBhbiBhY2NlcHRlZCBsaW1pdGF0aW9uLCBub3QgYW4nLFxuICAgICAgICAnICogb3ZlcnNpZ2h0OyBzZWUgdGhlIGFydGljbGUgYW5kIGF0dGVzdGF0aW9uL21hbmlmZXN0LnRzXFwncyBvd24gY29tbWVudC4nLFxuICAgICAgICAnICovJyxcbiAgICAgICAgXCJpbXBvcnQgeyByZWFkRmlsZVN5bmMgfSBmcm9tICdub2RlOmZzJztcIixcbiAgICAgICAgXCJpbXBvcnQgeyBjb21wdXRlTWFuaWZlc3QsIHR5cGUgTWFuaWZlc3QgfSBmcm9tICcuL21hbmlmZXN0JztcIixcbiAgICAgICAgJycsXG4gICAgICAgICdjb25zdCBNQVhfQUdFX01TID0gNyAqIDI0ICogNjAgKiA2MCAqIDEwMDA7IC8vIDcgZGF5cycsXG4gICAgICAgICcvLyBHbG9iYWwsIGFuZCBkZWxpYmVyYXRlbHkgbG9vc2UgYWJvdXQgd2hhdFxcJ3MgKmJldHdlZW4qIHRoZSBtYXJrZXIgYW5kIHRoZScsXG4gICAgICAgICcvLyBibG9ja1xcJ3Mgb3duIGNsb3NpbmcgXCItLT5cIiDigJQgUFIgYm9kaWVzIGNhbiAoYW5kLCBpdCB0dXJucyBvdXQsIGRvKSBtZW50aW9uJyxcbiAgICAgICAgJy8vIHRoZSBsaXRlcmFsIFwiPCEtLSBhdHRlc3RhdGlvbi1tYW5pZmVzdCAtLT5cIiBzeW50YXggaW4gcHJvc2UgZGVzY3JpYmluZycsXG4gICAgICAgICcvLyB0aGlzIG1lY2hhbmlzbS4gRWFjaCBtYXRjaCBpcyBpbmRlcGVuZGVudGx5IHZhbGlkYXRlZCBiZWxvdzsgYSBtZW50aW9uJyxcbiAgICAgICAgJy8vIHdpdGggbm8gSlNPTiBpbnNpZGUgaXQgaXMgc2tpcHBlZCByYXRoZXIgdGhhbiB0cmVhdGVkIGFzIGEgcGFyc2UgZXJyb3IuJyxcbiAgICAgICAgJ2NvbnN0IE1BTklGRVNUX0JMT0NLX1JFID0gLzwhLS1cXFxccyphdHRlc3RhdGlvbi1tYW5pZmVzdChbXFxcXHNcXFxcU10qPyktLT4vZzsnLFxuICAgICAgICAnJyxcbiAgICAgICAgJ2ludGVyZmFjZSBFbWJlZGRlZE1hbmlmZXN0IGV4dGVuZHMgTWFuaWZlc3QgeycsXG4gICAgICAgICcgIGNvbW1pdDogc3RyaW5nOycsXG4gICAgICAgICcgIHN0YXR1czogc3RyaW5nOycsXG4gICAgICAgICd9JyxcbiAgICAgICAgJycsXG4gICAgICAgICdmdW5jdGlvbiBpc0VtYmVkZGVkTWFuaWZlc3QodmFsdWU6IHVua25vd24pOiB2YWx1ZSBpcyBFbWJlZGRlZE1hbmlmZXN0IHsnLFxuICAgICAgICAnICBjb25zdCBjYW5kaWRhdGUgPSB2YWx1ZSBhcyBQYXJ0aWFsPEVtYmVkZGVkTWFuaWZlc3Q+IHwgbnVsbDsnLFxuICAgICAgICAnICByZXR1cm4gKCcsXG4gICAgICAgIFwiICAgIHR5cGVvZiBjYW5kaWRhdGUgPT09ICdvYmplY3QnICYmXCIsXG4gICAgICAgICcgICAgY2FuZGlkYXRlICE9PSBudWxsICYmJyxcbiAgICAgICAgXCIgICAgdHlwZW9mIGNhbmRpZGF0ZS5jb21taXQgPT09ICdzdHJpbmcnICYmXCIsXG4gICAgICAgIFwiICAgIHR5cGVvZiBjYW5kaWRhdGUuc3RhdHVzID09PSAnc3RyaW5nJyAmJlwiLFxuICAgICAgICBcIiAgICB0eXBlb2YgY2FuZGlkYXRlLmdlbmVyYXRlZEF0ID09PSAnc3RyaW5nJyAmJlwiLFxuICAgICAgICBcIiAgICB0eXBlb2YgY2FuZGlkYXRlLmZpbGVzID09PSAnb2JqZWN0JyAmJlwiLFxuICAgICAgICAnICAgIGNhbmRpZGF0ZS5maWxlcyAhPT0gbnVsbCcsXG4gICAgICAgICcgICk7JyxcbiAgICAgICAgJ30nLFxuICAgICAgICAnJyxcbiAgICAgICAgJy8vIEJhbGFuY2VkLWJyYWNlIHNjYW4gcmF0aGVyIHRoYW4gYSByZWdleCBmb3IgdGhlIEpTT04gaXRzZWxmOiB0aGUgbWFuaWZlc3QnLFxuICAgICAgICAnLy8gaGFzIG5lc3RlZCBvYmplY3RzIChmaWxlczogey4uLn0pLCBzbyBhIG5vbi1ncmVlZHkgYFxcXFx7Lio/XFxcXH1gIHdvdWxkIHN0b3AgYXQnLFxuICAgICAgICAnLy8gdGhlIGZpcnN0IGlubmVyIFwifVwiIGluc3RlYWQgb2YgdGhlIHJlYWwgZW5kIG9mIHRoZSBvYmplY3QuJyxcbiAgICAgICAgJ2Z1bmN0aW9uIGV4dHJhY3RCYWxhbmNlZEpzb24odGV4dDogc3RyaW5nKTogc3RyaW5nIHwgbnVsbCB7JyxcbiAgICAgICAgXCIgIGNvbnN0IHN0YXJ0ID0gdGV4dC5pbmRleE9mKCd7Jyk7XCIsXG4gICAgICAgICcgIGlmIChzdGFydCA9PT0gLTEpIHJldHVybiBudWxsOycsXG4gICAgICAgICcgIGxldCBkZXB0aCA9IDA7JyxcbiAgICAgICAgJyAgZm9yIChsZXQgaSA9IHN0YXJ0OyBpIDwgdGV4dC5sZW5ndGg7IGkrKykgeycsXG4gICAgICAgIFwiICAgIGlmICh0ZXh0W2ldID09PSAneycpIGRlcHRoKys7XCIsXG4gICAgICAgIFwiICAgIGVsc2UgaWYgKHRleHRbaV0gPT09ICd9Jykge1wiLFxuICAgICAgICAnICAgICAgZGVwdGgtLTsnLFxuICAgICAgICAnICAgICAgaWYgKGRlcHRoID09PSAwKSByZXR1cm4gdGV4dC5zbGljZShzdGFydCwgaSArIDEpOycsXG4gICAgICAgICcgICAgfScsXG4gICAgICAgICcgIH0nLFxuICAgICAgICAnICByZXR1cm4gbnVsbDsnLFxuICAgICAgICAnfScsXG4gICAgICAgICcnLFxuICAgICAgICAnZnVuY3Rpb24gZXh0cmFjdEVtYmVkZGVkTWFuaWZlc3QoYm9keTogc3RyaW5nKTogRW1iZWRkZWRNYW5pZmVzdCB7JyxcbiAgICAgICAgJyAgbGV0IG1hdGNoZWRBbnlCbG9jayA9IGZhbHNlOycsXG4gICAgICAgICcgIGZvciAoY29uc3QgbWF0Y2ggb2YgYm9keS5tYXRjaEFsbChNQU5JRkVTVF9CTE9DS19SRSkpIHsnLFxuICAgICAgICAnICAgIG1hdGNoZWRBbnlCbG9jayA9IHRydWU7JyxcbiAgICAgICAgJyAgICBjb25zdCBqc29uVGV4dCA9IGV4dHJhY3RCYWxhbmNlZEpzb24obWF0Y2hbMV0pOycsXG4gICAgICAgICcgICAgaWYgKCFqc29uVGV4dCkgY29udGludWU7JyxcbiAgICAgICAgJyAgICB0cnkgeycsXG4gICAgICAgICcgICAgICBjb25zdCBwYXJzZWQgPSBKU09OLnBhcnNlKGpzb25UZXh0KTsnLFxuICAgICAgICAnICAgICAgaWYgKGlzRW1iZWRkZWRNYW5pZmVzdChwYXJzZWQpKSByZXR1cm4gcGFyc2VkOycsXG4gICAgICAgICcgICAgfSBjYXRjaCB7JyxcbiAgICAgICAgJyAgICAgIC8vIE5vdCB0aGlzIG9uZSDigJQga2VlcCBsb29raW5nIGF0IHRoZSBuZXh0IGF0dGVzdGF0aW9uLW1hbmlmZXN0IG1lbnRpb24uJyxcbiAgICAgICAgJyAgICB9JyxcbiAgICAgICAgJyAgfScsXG4gICAgICAgICcgIHRocm93IG5ldyBFcnJvcignLFxuICAgICAgICAnICAgIG1hdGNoZWRBbnlCbG9jaycsXG4gICAgICAgIFwiICAgICAgPyAnRm91bmQgPCEtLSBhdHRlc3RhdGlvbi1tYW5pZmVzdCAtLT4gbWVudGlvbihzKSBpbiB0aGUgUFIgYm9keSwgYnV0IG5vbmUgY29udGFpbmVkIGEgY29tcGxldGUsIHZhbGlkIG1hbmlmZXN0LidcIixcbiAgICAgICAgXCIgICAgICA6ICdObyA8IS0tIGF0dGVzdGF0aW9uLW1hbmlmZXN0IC0tPiBibG9jayBmb3VuZCBpbiB0aGUgUFIgYm9keS4gVGhpcyBQUiB3YXMgbmV2ZXIgdmVyaWZpZWQgYnkgJyArXCIsXG4gICAgICAgICcgICAgICAgIFxcJ3ByZS1wci12ZXJpZnkgKG9yIHRoZSBibG9jayB3YXMgcmVtb3ZlZCkg4oCUIHJ1biB0aGUgZmFjdG9yeVxcXFxcXCdzIHByZS1wci12ZXJpZnkgd29ya2Zsb3cgYW5kIHJlLW9wZW4vdXBkYXRlIHRoZSBQUi5cXCcsJyxcbiAgICAgICAgJyAgKTsnLFxuICAgICAgICAnfScsXG4gICAgICAgICcnLFxuICAgICAgICAnZnVuY3Rpb24gdmVyaWZ5KHByQm9keTogc3RyaW5nLCBleHBlY3RlZENvbW1pdDogc3RyaW5nKTogc3RyaW5nW10geycsXG4gICAgICAgICcgIGxldCBlbWJlZGRlZDogRW1iZWRkZWRNYW5pZmVzdDsnLFxuICAgICAgICAnICB0cnkgeycsXG4gICAgICAgICcgICAgZW1iZWRkZWQgPSBleHRyYWN0RW1iZWRkZWRNYW5pZmVzdChwckJvZHkpOycsXG4gICAgICAgICcgIH0gY2F0Y2ggKGVycikgeycsXG4gICAgICAgICcgICAgcmV0dXJuIFtlcnIgaW5zdGFuY2VvZiBFcnJvciA/IGVyci5tZXNzYWdlIDogU3RyaW5nKGVycildOycsXG4gICAgICAgICcgIH0nLFxuICAgICAgICAnJyxcbiAgICAgICAgJyAgY29uc3QgZXJyb3JzOiBzdHJpbmdbXSA9IFtdOycsXG4gICAgICAgICcnLFxuICAgICAgICAnICBpZiAoZW1iZWRkZWQuY29tbWl0ICE9PSBleHBlY3RlZENvbW1pdCkgeycsXG4gICAgICAgICcgICAgZXJyb3JzLnB1c2goJyxcbiAgICAgICAgJyAgICAgIGBBdHRlc3RhdGlvbiBpcyBmb3IgY29tbWl0ICR7ZW1iZWRkZWQuY29tbWl0fSwgYnV0IHRoZSBQUlxcJ3MgaGVhZCBjb21taXQgaXMgJHtleHBlY3RlZENvbW1pdH0uIGAgKycsXG4gICAgICAgIFwiICAgICAgJ05ldyBjb21taXRzIHdlcmUgcHVzaGVkIHdpdGhvdXQgcmUtcnVubmluZyBwcmUtcHItdmVyaWZ5LicsXCIsXG4gICAgICAgICcgICAgKTsnLFxuICAgICAgICAnICB9JyxcbiAgICAgICAgJycsXG4gICAgICAgIFwiICBpZiAoZW1iZWRkZWQuc3RhdHVzICE9PSAnc3VjY2VlZGVkJykge1wiLFxuICAgICAgICAnICAgIGVycm9ycy5wdXNoKGBBdHRlc3RhdGlvbiBzdGF0dXMgaXMgXCIke2VtYmVkZGVkLnN0YXR1c31cIiwgbm90IFwic3VjY2VlZGVkXCIuYCk7JyxcbiAgICAgICAgJyAgfScsXG4gICAgICAgICcnLFxuICAgICAgICAnICBjb25zdCBhZ2VNcyA9IERhdGUubm93KCkgLSBEYXRlLnBhcnNlKGVtYmVkZGVkLmdlbmVyYXRlZEF0KTsnLFxuICAgICAgICAnICBpZiAoIU51bWJlci5pc0Zpbml0ZShhZ2VNcykpIHsnLFxuICAgICAgICAnICAgIGVycm9ycy5wdXNoKGBBdHRlc3RhdGlvbiBnZW5lcmF0ZWRBdCAoXCIke2VtYmVkZGVkLmdlbmVyYXRlZEF0fVwiKSBpcyBub3QgYSBwYXJzZWFibGUgdGltZXN0YW1wLmApOycsXG4gICAgICAgICcgIH0gZWxzZSBpZiAoYWdlTXMgPiBNQVhfQUdFX01TKSB7JyxcbiAgICAgICAgJyAgICBlcnJvcnMucHVzaChgQXR0ZXN0YXRpb24gaXMgJHtNYXRoLmZsb29yKGFnZU1zIC8gKDI0ICogNjAgKiA2MCAqIDEwMDApKX0gZGF5cyBvbGQgKG1heCAke01BWF9BR0VfTVMgLyAoMjQgKiA2MCAqIDYwICogMTAwMCl9KSDigJQgcmUtcnVuIHByZS1wci12ZXJpZnkuYCk7JyxcbiAgICAgICAgJyAgfScsXG4gICAgICAgICcnLFxuICAgICAgICAnICBjb25zdCBhY3R1YWwgPSBjb21wdXRlTWFuaWZlc3QoKTsnLFxuICAgICAgICAnICBmb3IgKGNvbnN0IGZpbGUgb2YgT2JqZWN0LmtleXMoYWN0dWFsLmZpbGVzKSkgeycsXG4gICAgICAgICcgICAgaWYgKGVtYmVkZGVkLmZpbGVzW2ZpbGVdICE9PSBhY3R1YWwuZmlsZXNbZmlsZV0pIHsnLFxuICAgICAgICAnICAgICAgZXJyb3JzLnB1c2goYCR7ZmlsZX0gaGFzIGNoYW5nZWQgc2luY2UgdGhlIGF0dGVzdGF0aW9uIHdhcyBnZW5lcmF0ZWQgKGV4cGVjdGVkICR7ZW1iZWRkZWQuZmlsZXNbZmlsZV0gPz8gXFwnbWlzc2luZ1xcJ30sIGdvdCAke2FjdHVhbC5maWxlc1tmaWxlXX0pLmApOycsXG4gICAgICAgICcgICAgfScsXG4gICAgICAgICcgIH0nLFxuICAgICAgICAnICBmb3IgKGNvbnN0IGZpbGUgb2YgT2JqZWN0LmtleXMoZW1iZWRkZWQuZmlsZXMpKSB7JyxcbiAgICAgICAgJyAgICBpZiAoIShmaWxlIGluIGFjdHVhbC5maWxlcykpIHsnLFxuICAgICAgICAnICAgICAgZXJyb3JzLnB1c2goYCR7ZmlsZX0gaXMgaW4gdGhlIGF0dGVzdGF0aW9uIG1hbmlmZXN0IGJ1dCBubyBsb25nZXIgaW4gdGhlIGN1cnJlbnQgZmlsZSBsaXN0IChhdHRlc3RhdGlvbi9tYW5pZmVzdC50c1xcJ3MgTUFOSUZFU1RfRklMRVMgY2hhbmdlZCkuYCk7JyxcbiAgICAgICAgJyAgICB9JyxcbiAgICAgICAgJyAgfScsXG4gICAgICAgICcnLFxuICAgICAgICAnICByZXR1cm4gZXJyb3JzOycsXG4gICAgICAgICd9JyxcbiAgICAgICAgJycsXG4gICAgICAgICdmdW5jdGlvbiBwYXJzZUFyZ3MoYXJndjogc3RyaW5nW10pOiB7IGJvZHlGaWxlOiBzdHJpbmc7IGNvbW1pdDogc3RyaW5nIH0geycsXG4gICAgICAgIFwiICBjb25zdCBib2R5RmlsZSA9IGFyZ3ZbYXJndi5pbmRleE9mKCctLWJvZHktZmlsZScpICsgMV07XCIsXG4gICAgICAgIFwiICBjb25zdCBjb21taXQgPSBhcmd2W2FyZ3YuaW5kZXhPZignLS1jb21taXQnKSArIDFdO1wiLFxuICAgICAgICAnICBpZiAoIWJvZHlGaWxlIHx8ICFjb21taXQpIHsnLFxuICAgICAgICBcIiAgICB0aHJvdyBuZXcgRXJyb3IoJ1VzYWdlOiB2ZXJpZnkudHMgLS1ib2R5LWZpbGUgPHBhdGg+IC0tY29tbWl0IDxzaGE+Jyk7XCIsXG4gICAgICAgICcgIH0nLFxuICAgICAgICAnICByZXR1cm4geyBib2R5RmlsZSwgY29tbWl0IH07JyxcbiAgICAgICAgJ30nLFxuICAgICAgICAnJyxcbiAgICAgICAgJ2lmIChyZXF1aXJlLm1haW4gPT09IG1vZHVsZSkgeycsXG4gICAgICAgICcgIGNvbnN0IHsgYm9keUZpbGUsIGNvbW1pdCB9ID0gcGFyc2VBcmdzKHByb2Nlc3MuYXJndi5zbGljZSgyKSk7JyxcbiAgICAgICAgXCIgIGNvbnN0IHByQm9keSA9IHJlYWRGaWxlU3luYyhib2R5RmlsZSwgJ3V0ZjgnKTtcIixcbiAgICAgICAgJyAgY29uc3QgZXJyb3JzID0gdmVyaWZ5KHByQm9keSwgY29tbWl0KTsnLFxuICAgICAgICAnICBpZiAoZXJyb3JzLmxlbmd0aCA+IDApIHsnLFxuICAgICAgICBcIiAgICBjb25zb2xlLmVycm9yKCdBdHRlc3RhdGlvbiB2ZXJpZmljYXRpb24gZmFpbGVkOlxcXFxuJyk7XCIsXG4gICAgICAgICcgICAgZm9yIChjb25zdCBlcnJvciBvZiBlcnJvcnMpIHsnLFxuICAgICAgICAnICAgICAgY29uc29sZS5lcnJvcihgICAtICR7ZXJyb3J9YCk7JyxcbiAgICAgICAgJyAgICB9JyxcbiAgICAgICAgJyAgICBwcm9jZXNzLmV4aXQoMSk7JyxcbiAgICAgICAgJyAgfScsXG4gICAgICAgICcgIGNvbnNvbGUubG9nKGBBdHRlc3RhdGlvbiB2ZXJpZmllZDogY29tbWl0ICR7Y29tbWl0fSwgYWxsICR7T2JqZWN0LmtleXMoY29tcHV0ZU1hbmlmZXN0KCkuZmlsZXMpLmxlbmd0aH0gdHJhY2tlZCBmaWxlcyBtYXRjaC5gKTsnLFxuICAgICAgICAnfScsXG4gICAgICBdLFxuICAgIH0pO1xuXG4gICAgbmV3IEpzb25GaWxlKHByb2plY3QsICdhdHRlc3RhdGlvbi90c2NvbmZpZy5qc29uJywge1xuICAgICAgbWFya2VyOiBmYWxzZSxcbiAgICAgIG9iajoge1xuICAgICAgICBleHRlbmRzOiAnLi4vdHNjb25maWcuanNvbicsXG4gICAgICAgIGNvbXBpbGVyT3B0aW9uczoge1xuICAgICAgICAgIG5vRW1pdDogdHJ1ZSxcbiAgICAgICAgICByb290RGlyOiAnLicsXG4gICAgICAgIH0sXG4gICAgICAgIGluY2x1ZGU6IFsnKi50cyddLFxuICAgICAgICBleGNsdWRlOiBbJ25vZGVfbW9kdWxlcyddLFxuICAgICAgfSxcbiAgICB9KTtcbiAgfVxufVxuIl19
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { NodeProject } from 'projen/lib/javascript';
|
|
2
|
+
import { WorkflowActionVersions } from './WorkflowActionVersions';
|
|
3
|
+
export interface SoftwareFactoryFilesOptions {
|
|
4
|
+
/**
|
|
5
|
+
* The bare repo name (e.g. `expense-tracker`), used to derive the factory's
|
|
6
|
+
* model names (`<repoName>-factory`, `<repoName>-issues`, `<repoName>-pr`,
|
|
7
|
+
* `<repoName>-shell*`) and vault name (`<repoName>-secrets`).
|
|
8
|
+
*/
|
|
9
|
+
readonly repoName: string;
|
|
10
|
+
/**
|
|
11
|
+
* The branch the factory opens pull requests against.
|
|
12
|
+
*
|
|
13
|
+
* @default 'main'
|
|
14
|
+
*/
|
|
15
|
+
readonly baseBranch?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Overrides for the pinned action versions used by `verify-attestation.yml`.
|
|
18
|
+
*/
|
|
19
|
+
readonly actionVersions?: WorkflowActionVersions;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Backports exman's swamp "software factory" pattern
|
|
23
|
+
* (macedon-technologies/exman#13) as generated scaffolding: a
|
|
24
|
+
* `@swamp/software-factory` state machine that drives a GitHub issue from
|
|
25
|
+
* intake through an opened PR, enforcing OpenSpec usage, an architecture
|
|
26
|
+
* rule registry, and a pre-PR verification gate whose attestation is
|
|
27
|
+
* re-checked at merge time.
|
|
28
|
+
*
|
|
29
|
+
* What swamp itself would generate (extension docs pulled via
|
|
30
|
+
* `swamp extension pull`, `.swamp.yaml`, the vault, and the "swamp managed
|
|
31
|
+
* section" in CLAUDE.md) is deliberately NOT hand-templated here — those are
|
|
32
|
+
* produced by the real `swamp` CLI. `swamp init` is idempotent (a no-op if
|
|
33
|
+
* the repo is already initialized), so it's wired into the project's
|
|
34
|
+
* `install`/`install:ci` tasks and runs automatically on every install,
|
|
35
|
+
* guarded so it's a silent no-op on a machine without `swamp` on PATH (e.g.
|
|
36
|
+
* CI). The extension pulls and vault creation are one-time, not idempotent
|
|
37
|
+
* in a way that's safe to repeat on every install, so they stay in the
|
|
38
|
+
* `swamp:bootstrap` task this construct adds. Only exman's own authored
|
|
39
|
+
* config (the factory's state machine, the architecture-rule engine, the
|
|
40
|
+
* attestation mechanism, the OpenSpec task-completion gate, and the 3 swamp
|
|
41
|
+
* workflows that wire them together) is templated.
|
|
42
|
+
*/
|
|
43
|
+
export declare class SoftwareFactoryFiles {
|
|
44
|
+
constructor(project: NodeProject, options: SoftwareFactoryFilesOptions);
|
|
45
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SoftwareFactoryFiles = void 0;
|
|
4
|
+
const JSII_RTTI_SYMBOL_1 = Symbol.for("jsii.rtti");
|
|
5
|
+
const SoftwareFactoryArchitectureRules_1 = require("./SoftwareFactoryArchitectureRules");
|
|
6
|
+
const SoftwareFactoryAttestation_1 = require("./SoftwareFactoryAttestation");
|
|
7
|
+
const SoftwareFactoryModels_1 = require("./SoftwareFactoryModels");
|
|
8
|
+
const SoftwareFactoryOpenSpecGates_1 = require("./SoftwareFactoryOpenSpecGates");
|
|
9
|
+
const SoftwareFactoryWorkflows_1 = require("./SoftwareFactoryWorkflows");
|
|
10
|
+
const GITHUB_OWNER = 'macedon-technologies';
|
|
11
|
+
/**
|
|
12
|
+
* Backports exman's swamp "software factory" pattern
|
|
13
|
+
* (macedon-technologies/exman#13) as generated scaffolding: a
|
|
14
|
+
* `@swamp/software-factory` state machine that drives a GitHub issue from
|
|
15
|
+
* intake through an opened PR, enforcing OpenSpec usage, an architecture
|
|
16
|
+
* rule registry, and a pre-PR verification gate whose attestation is
|
|
17
|
+
* re-checked at merge time.
|
|
18
|
+
*
|
|
19
|
+
* What swamp itself would generate (extension docs pulled via
|
|
20
|
+
* `swamp extension pull`, `.swamp.yaml`, the vault, and the "swamp managed
|
|
21
|
+
* section" in CLAUDE.md) is deliberately NOT hand-templated here — those are
|
|
22
|
+
* produced by the real `swamp` CLI. `swamp init` is idempotent (a no-op if
|
|
23
|
+
* the repo is already initialized), so it's wired into the project's
|
|
24
|
+
* `install`/`install:ci` tasks and runs automatically on every install,
|
|
25
|
+
* guarded so it's a silent no-op on a machine without `swamp` on PATH (e.g.
|
|
26
|
+
* CI). The extension pulls and vault creation are one-time, not idempotent
|
|
27
|
+
* in a way that's safe to repeat on every install, so they stay in the
|
|
28
|
+
* `swamp:bootstrap` task this construct adds. Only exman's own authored
|
|
29
|
+
* config (the factory's state machine, the architecture-rule engine, the
|
|
30
|
+
* attestation mechanism, the OpenSpec task-completion gate, and the 3 swamp
|
|
31
|
+
* workflows that wire them together) is templated.
|
|
32
|
+
*/
|
|
33
|
+
class SoftwareFactoryFiles {
|
|
34
|
+
static [JSII_RTTI_SYMBOL_1] = { fqn: "@macedon-technologies/batman.SoftwareFactoryFiles", version: "1.18.0" };
|
|
35
|
+
constructor(project, options) {
|
|
36
|
+
const { repoName, actionVersions } = options;
|
|
37
|
+
const baseBranch = options.baseBranch || 'main';
|
|
38
|
+
const secretsVault = `${repoName}-secrets`;
|
|
39
|
+
// A single redirect only: projen's built-in cross-platform task shell
|
|
40
|
+
// (dax) rejects a command with more than one redirect (e.g. combining
|
|
41
|
+
// `>/dev/null` with `2>&1`) with "Multiple redirects are currently not
|
|
42
|
+
// supported" — confirmed by dogfooding this construct on this very repo.
|
|
43
|
+
const swampInitStep = 'command -v swamp >/dev/null && swamp init --tool claude || true';
|
|
44
|
+
new SoftwareFactoryModels_1.SoftwareFactoryModels(project, {
|
|
45
|
+
repoName,
|
|
46
|
+
githubOwner: GITHUB_OWNER,
|
|
47
|
+
baseBranch,
|
|
48
|
+
});
|
|
49
|
+
new SoftwareFactoryArchitectureRules_1.SoftwareFactoryArchitectureRules(project);
|
|
50
|
+
new SoftwareFactoryAttestation_1.SoftwareFactoryAttestation(project, { repoName });
|
|
51
|
+
new SoftwareFactoryOpenSpecGates_1.SoftwareFactoryOpenSpecGates(project);
|
|
52
|
+
new SoftwareFactoryWorkflows_1.SoftwareFactoryWorkflows(project, { repoName, actionVersions });
|
|
53
|
+
project.addTask('validate:architecture', {
|
|
54
|
+
description: 'Run the architecture rule registry (architecture-rules/rules.json) against the repo',
|
|
55
|
+
exec: 'ts-node --project architecture-rules/tsconfig.json architecture-rules/check.ts',
|
|
56
|
+
});
|
|
57
|
+
project.addTask('openspec:check-tasks-complete', {
|
|
58
|
+
description: 'Fail unless every task in the named OpenSpec change is complete (usage: -- <change-slug>)',
|
|
59
|
+
exec: 'ts-node --project openspec-gates/tsconfig.json openspec-gates/check-tasks-complete.ts',
|
|
60
|
+
receiveArgs: true,
|
|
61
|
+
});
|
|
62
|
+
project.addTask('attestation:manifest', {
|
|
63
|
+
description: 'Print SHA256 checksums of the files a PR attestation is verified against (see attestation/manifest.ts)',
|
|
64
|
+
exec: 'ts-node --project attestation/tsconfig.json attestation/manifest.ts',
|
|
65
|
+
});
|
|
66
|
+
project.addTask('attestation:verify', {
|
|
67
|
+
description: 'Verify a PR\'s embedded attestation-manifest against the current checkout (usage: -- --body-file <path> --commit <sha>)',
|
|
68
|
+
exec: 'ts-node --project attestation/tsconfig.json attestation/verify.ts',
|
|
69
|
+
receiveArgs: true,
|
|
70
|
+
});
|
|
71
|
+
project.addTask('swamp:bootstrap', {
|
|
72
|
+
description: 'One-time setup: pull the software-factory/github extensions and create the secrets vault (swamp init itself already runs on every install)',
|
|
73
|
+
steps: [
|
|
74
|
+
{ exec: 'swamp extension pull @swamp/software-factory' },
|
|
75
|
+
{ exec: 'swamp extension pull @webframp/github' },
|
|
76
|
+
{ exec: 'swamp extension pull @goodcraft/github' },
|
|
77
|
+
{ exec: `swamp vault create local_encryption ${secretsVault}` },
|
|
78
|
+
{
|
|
79
|
+
exec: `echo "Next: swamp vault put ${secretsVault} GITHUB_TOKEN --refresh-from 'gh auth token'"`,
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
});
|
|
83
|
+
// `swamp init` is safe to re-run (idempotent when already initialized),
|
|
84
|
+
// so every install keeps the repo initialized without a separate manual
|
|
85
|
+
// step — including on a fresh clone that hasn't run swamp:bootstrap yet.
|
|
86
|
+
project.package.installTask.exec(swampInitStep);
|
|
87
|
+
project.package.installCiTask.exec(swampInitStep);
|
|
88
|
+
// Mirrors the patterns `swamp init` itself would append to .gitignore.
|
|
89
|
+
// projen's IgnoreFile fully regenerates .gitignore from its own tracked
|
|
90
|
+
// entries on every synth, so anything `swamp init` appends out-of-band
|
|
91
|
+
// gets silently wiped by the next `npx projen` run (caught by CI's
|
|
92
|
+
// self-mutation check) unless it's also added here.
|
|
93
|
+
project.addGitIgnore('.swamp/');
|
|
94
|
+
project.addGitIgnore('.swamp-sources.yaml');
|
|
95
|
+
project.addGitIgnore('.claude/worktrees/');
|
|
96
|
+
project.addGitIgnore('.claude/settings.local.json');
|
|
97
|
+
project.addGitIgnore('.claude/scheduled_tasks.lock');
|
|
98
|
+
project.addGitIgnore('.claude/scheduled_tasks.json');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
exports.SoftwareFactoryFiles = SoftwareFactoryFiles;
|
|
102
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU29mdHdhcmVGYWN0b3J5RmlsZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvU29mdHdhcmVGYWN0b3J5RmlsZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7OztBQUNBLHlGQUFzRjtBQUN0Riw2RUFBMEU7QUFDMUUsbUVBQWdFO0FBQ2hFLGlGQUE4RTtBQUM5RSx5RUFBc0U7QUFHdEUsTUFBTSxZQUFZLEdBQUcsc0JBQXNCLENBQUM7QUF1QjVDOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FxQkc7QUFDSCxNQUFhLG9CQUFvQjs7SUFDL0IsWUFBWSxPQUFvQixFQUFFLE9BQW9DO1FBQ3BFLE1BQU0sRUFBRSxRQUFRLEVBQUUsY0FBYyxFQUFFLEdBQUcsT0FBTyxDQUFDO1FBQzdDLE1BQU0sVUFBVSxHQUFHLE9BQU8sQ0FBQyxVQUFVLElBQUksTUFBTSxDQUFDO1FBQ2hELE1BQU0sWUFBWSxHQUFHLEdBQUcsUUFBUSxVQUFVLENBQUM7UUFDM0Msc0VBQXNFO1FBQ3RFLHNFQUFzRTtRQUN0RSx1RUFBdUU7UUFDdkUseUVBQXlFO1FBQ3pFLE1BQU0sYUFBYSxHQUFHLGlFQUFpRSxDQUFDO1FBRXhGLElBQUksNkNBQXFCLENBQUMsT0FBTyxFQUFFO1lBQ2pDLFFBQVE7WUFDUixXQUFXLEVBQUUsWUFBWTtZQUN6QixVQUFVO1NBQ1gsQ0FBQyxDQUFDO1FBRUgsSUFBSSxtRUFBZ0MsQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUU5QyxJQUFJLHVEQUEwQixDQUFDLE9BQU8sRUFBRSxFQUFFLFFBQVEsRUFBRSxDQUFDLENBQUM7UUFFdEQsSUFBSSwyREFBNEIsQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUUxQyxJQUFJLG1EQUF3QixDQUFDLE9BQU8sRUFBRSxFQUFFLFFBQVEsRUFBRSxjQUFjLEVBQUUsQ0FBQyxDQUFDO1FBRXBFLE9BQU8sQ0FBQyxPQUFPLENBQUMsdUJBQXVCLEVBQUU7WUFDdkMsV0FBVyxFQUFFLHFGQUFxRjtZQUNsRyxJQUFJLEVBQUUsZ0ZBQWdGO1NBQ3ZGLENBQUMsQ0FBQztRQUVILE9BQU8sQ0FBQyxPQUFPLENBQUMsK0JBQStCLEVBQUU7WUFDL0MsV0FBVyxFQUFFLDJGQUEyRjtZQUN4RyxJQUFJLEVBQUUsdUZBQXVGO1lBQzdGLFdBQVcsRUFBRSxJQUFJO1NBQ2xCLENBQUMsQ0FBQztRQUVILE9BQU8sQ0FBQyxPQUFPLENBQUMsc0JBQXNCLEVBQUU7WUFDdEMsV0FBVyxFQUFFLHdHQUF3RztZQUNySCxJQUFJLEVBQUUscUVBQXFFO1NBQzVFLENBQUMsQ0FBQztRQUVILE9BQU8sQ0FBQyxPQUFPLENBQUMsb0JBQW9CLEVBQUU7WUFDcEMsV0FBVyxFQUFFLHlIQUF5SDtZQUN0SSxJQUFJLEVBQUUsbUVBQW1FO1lBQ3pFLFdBQVcsRUFBRSxJQUFJO1NBQ2xCLENBQUMsQ0FBQztRQUVILE9BQU8sQ0FBQyxPQUFPLENBQUMsaUJBQWlCLEVBQUU7WUFDakMsV0FBVyxFQUFFLDRJQUE0STtZQUN6SixLQUFLLEVBQUU7Z0JBQ0wsRUFBRSxJQUFJLEVBQUUsOENBQThDLEVBQUU7Z0JBQ3hELEVBQUUsSUFBSSxFQUFFLHVDQUF1QyxFQUFFO2dCQUNqRCxFQUFFLElBQUksRUFBRSx3Q0FBd0MsRUFBRTtnQkFDbEQsRUFBRSxJQUFJLEVBQUUsdUNBQXVDLFlBQVksRUFBRSxFQUFFO2dCQUMvRDtvQkFDRSxJQUFJLEVBQUUsK0JBQStCLFlBQVksK0NBQStDO2lCQUNqRzthQUNGO1NBQ0YsQ0FBQyxDQUFDO1FBRUgsd0VBQXdFO1FBQ3hFLHdFQUF3RTtRQUN4RSx5RUFBeUU7UUFDekUsT0FBTyxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLGFBQWEsQ0FBQyxDQUFDO1FBQ2hELE9BQU8sQ0FBQyxPQUFPLENBQUMsYUFBYSxDQUFDLElBQUksQ0FBQyxhQUFhLENBQUMsQ0FBQztRQUVsRCx1RUFBdUU7UUFDdkUsd0VBQXdFO1FBQ3hFLHVFQUF1RTtRQUN2RSxtRUFBbUU7UUFDbkUsb0RBQW9EO1FBQ3BELE9BQU8sQ0FBQyxZQUFZLENBQUMsU0FBUyxDQUFDLENBQUM7UUFDaEMsT0FBTyxDQUFDLFlBQVksQ0FBQyxxQkFBcUIsQ0FBQyxDQUFDO1FBQzVDLE9BQU8sQ0FBQyxZQUFZLENBQUMsb0JBQW9CLENBQUMsQ0FBQztRQUMzQyxPQUFPLENBQUMsWUFBWSxDQUFDLDZCQUE2QixDQUFDLENBQUM7UUFDcEQsT0FBTyxDQUFDLFlBQVksQ0FBQyw4QkFBOEIsQ0FBQyxDQUFDO1FBQ3JELE9BQU8sQ0FBQyxZQUFZLENBQUMsOEJBQThCLENBQUMsQ0FBQztJQUN2RCxDQUFDOztBQTdFSCxvREE4RUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBOb2RlUHJvamVjdCB9IGZyb20gJ3Byb2plbi9saWIvamF2YXNjcmlwdCc7XG5pbXBvcnQgeyBTb2Z0d2FyZUZhY3RvcnlBcmNoaXRlY3R1cmVSdWxlcyB9IGZyb20gJy4vU29mdHdhcmVGYWN0b3J5QXJjaGl0ZWN0dXJlUnVsZXMnO1xuaW1wb3J0IHsgU29mdHdhcmVGYWN0b3J5QXR0ZXN0YXRpb24gfSBmcm9tICcuL1NvZnR3YXJlRmFjdG9yeUF0dGVzdGF0aW9uJztcbmltcG9ydCB7IFNvZnR3YXJlRmFjdG9yeU1vZGVscyB9IGZyb20gJy4vU29mdHdhcmVGYWN0b3J5TW9kZWxzJztcbmltcG9ydCB7IFNvZnR3YXJlRmFjdG9yeU9wZW5TcGVjR2F0ZXMgfSBmcm9tICcuL1NvZnR3YXJlRmFjdG9yeU9wZW5TcGVjR2F0ZXMnO1xuaW1wb3J0IHsgU29mdHdhcmVGYWN0b3J5V29ya2Zsb3dzIH0gZnJvbSAnLi9Tb2Z0d2FyZUZhY3RvcnlXb3JrZmxvd3MnO1xuaW1wb3J0IHsgV29ya2Zsb3dBY3Rpb25WZXJzaW9ucyB9IGZyb20gJy4vV29ya2Zsb3dBY3Rpb25WZXJzaW9ucyc7XG5cbmNvbnN0IEdJVEhVQl9PV05FUiA9ICdtYWNlZG9uLXRlY2hub2xvZ2llcyc7XG5cbmV4cG9ydCBpbnRlcmZhY2UgU29mdHdhcmVGYWN0b3J5RmlsZXNPcHRpb25zIHtcbiAgLyoqXG4gICAqIFRoZSBiYXJlIHJlcG8gbmFtZSAoZS5nLiBgZXhwZW5zZS10cmFja2VyYCksIHVzZWQgdG8gZGVyaXZlIHRoZSBmYWN0b3J5J3NcbiAgICogbW9kZWwgbmFtZXMgKGA8cmVwb05hbWU+LWZhY3RvcnlgLCBgPHJlcG9OYW1lPi1pc3N1ZXNgLCBgPHJlcG9OYW1lPi1wcmAsXG4gICAqIGA8cmVwb05hbWU+LXNoZWxsKmApIGFuZCB2YXVsdCBuYW1lIChgPHJlcG9OYW1lPi1zZWNyZXRzYCkuXG4gICAqL1xuICByZWFkb25seSByZXBvTmFtZTogc3RyaW5nO1xuXG4gIC8qKlxuICAgKiBUaGUgYnJhbmNoIHRoZSBmYWN0b3J5IG9wZW5zIHB1bGwgcmVxdWVzdHMgYWdhaW5zdC5cbiAgICpcbiAgICogQGRlZmF1bHQgJ21haW4nXG4gICAqL1xuICByZWFkb25seSBiYXNlQnJhbmNoPzogc3RyaW5nO1xuXG4gIC8qKlxuICAgKiBPdmVycmlkZXMgZm9yIHRoZSBwaW5uZWQgYWN0aW9uIHZlcnNpb25zIHVzZWQgYnkgYHZlcmlmeS1hdHRlc3RhdGlvbi55bWxgLlxuICAgKi9cbiAgcmVhZG9ubHkgYWN0aW9uVmVyc2lvbnM/OiBXb3JrZmxvd0FjdGlvblZlcnNpb25zO1xufVxuXG4vKipcbiAqIEJhY2twb3J0cyBleG1hbidzIHN3YW1wIFwic29mdHdhcmUgZmFjdG9yeVwiIHBhdHRlcm5cbiAqIChtYWNlZG9uLXRlY2hub2xvZ2llcy9leG1hbiMxMykgYXMgZ2VuZXJhdGVkIHNjYWZmb2xkaW5nOiBhXG4gKiBgQHN3YW1wL3NvZnR3YXJlLWZhY3RvcnlgIHN0YXRlIG1hY2hpbmUgdGhhdCBkcml2ZXMgYSBHaXRIdWIgaXNzdWUgZnJvbVxuICogaW50YWtlIHRocm91Z2ggYW4gb3BlbmVkIFBSLCBlbmZvcmNpbmcgT3BlblNwZWMgdXNhZ2UsIGFuIGFyY2hpdGVjdHVyZVxuICogcnVsZSByZWdpc3RyeSwgYW5kIGEgcHJlLVBSIHZlcmlmaWNhdGlvbiBnYXRlIHdob3NlIGF0dGVzdGF0aW9uIGlzXG4gKiByZS1jaGVja2VkIGF0IG1lcmdlIHRpbWUuXG4gKlxuICogV2hhdCBzd2FtcCBpdHNlbGYgd291bGQgZ2VuZXJhdGUgKGV4dGVuc2lvbiBkb2NzIHB1bGxlZCB2aWFcbiAqIGBzd2FtcCBleHRlbnNpb24gcHVsbGAsIGAuc3dhbXAueWFtbGAsIHRoZSB2YXVsdCwgYW5kIHRoZSBcInN3YW1wIG1hbmFnZWRcbiAqIHNlY3Rpb25cIiBpbiBDTEFVREUubWQpIGlzIGRlbGliZXJhdGVseSBOT1QgaGFuZC10ZW1wbGF0ZWQgaGVyZSDigJQgdGhvc2UgYXJlXG4gKiBwcm9kdWNlZCBieSB0aGUgcmVhbCBgc3dhbXBgIENMSS4gYHN3YW1wIGluaXRgIGlzIGlkZW1wb3RlbnQgKGEgbm8tb3AgaWZcbiAqIHRoZSByZXBvIGlzIGFscmVhZHkgaW5pdGlhbGl6ZWQpLCBzbyBpdCdzIHdpcmVkIGludG8gdGhlIHByb2plY3Qnc1xuICogYGluc3RhbGxgL2BpbnN0YWxsOmNpYCB0YXNrcyBhbmQgcnVucyBhdXRvbWF0aWNhbGx5IG9uIGV2ZXJ5IGluc3RhbGwsXG4gKiBndWFyZGVkIHNvIGl0J3MgYSBzaWxlbnQgbm8tb3Agb24gYSBtYWNoaW5lIHdpdGhvdXQgYHN3YW1wYCBvbiBQQVRIIChlLmcuXG4gKiBDSSkuIFRoZSBleHRlbnNpb24gcHVsbHMgYW5kIHZhdWx0IGNyZWF0aW9uIGFyZSBvbmUtdGltZSwgbm90IGlkZW1wb3RlbnRcbiAqIGluIGEgd2F5IHRoYXQncyBzYWZlIHRvIHJlcGVhdCBvbiBldmVyeSBpbnN0YWxsLCBzbyB0aGV5IHN0YXkgaW4gdGhlXG4gKiBgc3dhbXA6Ym9vdHN0cmFwYCB0YXNrIHRoaXMgY29uc3RydWN0IGFkZHMuIE9ubHkgZXhtYW4ncyBvd24gYXV0aG9yZWRcbiAqIGNvbmZpZyAodGhlIGZhY3RvcnkncyBzdGF0ZSBtYWNoaW5lLCB0aGUgYXJjaGl0ZWN0dXJlLXJ1bGUgZW5naW5lLCB0aGVcbiAqIGF0dGVzdGF0aW9uIG1lY2hhbmlzbSwgdGhlIE9wZW5TcGVjIHRhc2stY29tcGxldGlvbiBnYXRlLCBhbmQgdGhlIDMgc3dhbXBcbiAqIHdvcmtmbG93cyB0aGF0IHdpcmUgdGhlbSB0b2dldGhlcikgaXMgdGVtcGxhdGVkLlxuICovXG5leHBvcnQgY2xhc3MgU29mdHdhcmVGYWN0b3J5RmlsZXMge1xuICBjb25zdHJ1Y3Rvcihwcm9qZWN0OiBOb2RlUHJvamVjdCwgb3B0aW9uczogU29mdHdhcmVGYWN0b3J5RmlsZXNPcHRpb25zKSB7XG4gICAgY29uc3QgeyByZXBvTmFtZSwgYWN0aW9uVmVyc2lvbnMgfSA9IG9wdGlvbnM7XG4gICAgY29uc3QgYmFzZUJyYW5jaCA9IG9wdGlvbnMuYmFzZUJyYW5jaCB8fCAnbWFpbic7XG4gICAgY29uc3Qgc2VjcmV0c1ZhdWx0ID0gYCR7cmVwb05hbWV9LXNlY3JldHNgO1xuICAgIC8vIEEgc2luZ2xlIHJlZGlyZWN0IG9ubHk6IHByb2plbidzIGJ1aWx0LWluIGNyb3NzLXBsYXRmb3JtIHRhc2sgc2hlbGxcbiAgICAvLyAoZGF4KSByZWplY3RzIGEgY29tbWFuZCB3aXRoIG1vcmUgdGhhbiBvbmUgcmVkaXJlY3QgKGUuZy4gY29tYmluaW5nXG4gICAgLy8gYD4vZGV2L251bGxgIHdpdGggYDI+JjFgKSB3aXRoIFwiTXVsdGlwbGUgcmVkaXJlY3RzIGFyZSBjdXJyZW50bHkgbm90XG4gICAgLy8gc3VwcG9ydGVkXCIg4oCUIGNvbmZpcm1lZCBieSBkb2dmb29kaW5nIHRoaXMgY29uc3RydWN0IG9uIHRoaXMgdmVyeSByZXBvLlxuICAgIGNvbnN0IHN3YW1wSW5pdFN0ZXAgPSAnY29tbWFuZCAtdiBzd2FtcCA+L2Rldi9udWxsICYmIHN3YW1wIGluaXQgLS10b29sIGNsYXVkZSB8fCB0cnVlJztcblxuICAgIG5ldyBTb2Z0d2FyZUZhY3RvcnlNb2RlbHMocHJvamVjdCwge1xuICAgICAgcmVwb05hbWUsXG4gICAgICBnaXRodWJPd25lcjogR0lUSFVCX09XTkVSLFxuICAgICAgYmFzZUJyYW5jaCxcbiAgICB9KTtcblxuICAgIG5ldyBTb2Z0d2FyZUZhY3RvcnlBcmNoaXRlY3R1cmVSdWxlcyhwcm9qZWN0KTtcblxuICAgIG5ldyBTb2Z0d2FyZUZhY3RvcnlBdHRlc3RhdGlvbihwcm9qZWN0LCB7IHJlcG9OYW1lIH0pO1xuXG4gICAgbmV3IFNvZnR3YXJlRmFjdG9yeU9wZW5TcGVjR2F0ZXMocHJvamVjdCk7XG5cbiAgICBuZXcgU29mdHdhcmVGYWN0b3J5V29ya2Zsb3dzKHByb2plY3QsIHsgcmVwb05hbWUsIGFjdGlvblZlcnNpb25zIH0pO1xuXG4gICAgcHJvamVjdC5hZGRUYXNrKCd2YWxpZGF0ZTphcmNoaXRlY3R1cmUnLCB7XG4gICAgICBkZXNjcmlwdGlvbjogJ1J1biB0aGUgYXJjaGl0ZWN0dXJlIHJ1bGUgcmVnaXN0cnkgKGFyY2hpdGVjdHVyZS1ydWxlcy9ydWxlcy5qc29uKSBhZ2FpbnN0IHRoZSByZXBvJyxcbiAgICAgIGV4ZWM6ICd0cy1ub2RlIC0tcHJvamVjdCBhcmNoaXRlY3R1cmUtcnVsZXMvdHNjb25maWcuanNvbiBhcmNoaXRlY3R1cmUtcnVsZXMvY2hlY2sudHMnLFxuICAgIH0pO1xuXG4gICAgcHJvamVjdC5hZGRUYXNrKCdvcGVuc3BlYzpjaGVjay10YXNrcy1jb21wbGV0ZScsIHtcbiAgICAgIGRlc2NyaXB0aW9uOiAnRmFpbCB1bmxlc3MgZXZlcnkgdGFzayBpbiB0aGUgbmFtZWQgT3BlblNwZWMgY2hhbmdlIGlzIGNvbXBsZXRlICh1c2FnZTogLS0gPGNoYW5nZS1zbHVnPiknLFxuICAgICAgZXhlYzogJ3RzLW5vZGUgLS1wcm9qZWN0IG9wZW5zcGVjLWdhdGVzL3RzY29uZmlnLmpzb24gb3BlbnNwZWMtZ2F0ZXMvY2hlY2stdGFza3MtY29tcGxldGUudHMnLFxuICAgICAgcmVjZWl2ZUFyZ3M6IHRydWUsXG4gICAgfSk7XG5cbiAgICBwcm9qZWN0LmFkZFRhc2soJ2F0dGVzdGF0aW9uOm1hbmlmZXN0Jywge1xuICAgICAgZGVzY3JpcHRpb246ICdQcmludCBTSEEyNTYgY2hlY2tzdW1zIG9mIHRoZSBmaWxlcyBhIFBSIGF0dGVzdGF0aW9uIGlzIHZlcmlmaWVkIGFnYWluc3QgKHNlZSBhdHRlc3RhdGlvbi9tYW5pZmVzdC50cyknLFxuICAgICAgZXhlYzogJ3RzLW5vZGUgLS1wcm9qZWN0IGF0dGVzdGF0aW9uL3RzY29uZmlnLmpzb24gYXR0ZXN0YXRpb24vbWFuaWZlc3QudHMnLFxuICAgIH0pO1xuXG4gICAgcHJvamVjdC5hZGRUYXNrKCdhdHRlc3RhdGlvbjp2ZXJpZnknLCB7XG4gICAgICBkZXNjcmlwdGlvbjogJ1ZlcmlmeSBhIFBSXFwncyBlbWJlZGRlZCBhdHRlc3RhdGlvbi1tYW5pZmVzdCBhZ2FpbnN0IHRoZSBjdXJyZW50IGNoZWNrb3V0ICh1c2FnZTogLS0gLS1ib2R5LWZpbGUgPHBhdGg+IC0tY29tbWl0IDxzaGE+KScsXG4gICAgICBleGVjOiAndHMtbm9kZSAtLXByb2plY3QgYXR0ZXN0YXRpb24vdHNjb25maWcuanNvbiBhdHRlc3RhdGlvbi92ZXJpZnkudHMnLFxuICAgICAgcmVjZWl2ZUFyZ3M6IHRydWUsXG4gICAgfSk7XG5cbiAgICBwcm9qZWN0LmFkZFRhc2soJ3N3YW1wOmJvb3RzdHJhcCcsIHtcbiAgICAgIGRlc2NyaXB0aW9uOiAnT25lLXRpbWUgc2V0dXA6IHB1bGwgdGhlIHNvZnR3YXJlLWZhY3RvcnkvZ2l0aHViIGV4dGVuc2lvbnMgYW5kIGNyZWF0ZSB0aGUgc2VjcmV0cyB2YXVsdCAoc3dhbXAgaW5pdCBpdHNlbGYgYWxyZWFkeSBydW5zIG9uIGV2ZXJ5IGluc3RhbGwpJyxcbiAgICAgIHN0ZXBzOiBbXG4gICAgICAgIHsgZXhlYzogJ3N3YW1wIGV4dGVuc2lvbiBwdWxsIEBzd2FtcC9zb2Z0d2FyZS1mYWN0b3J5JyB9LFxuICAgICAgICB7IGV4ZWM6ICdzd2FtcCBleHRlbnNpb24gcHVsbCBAd2ViZnJhbXAvZ2l0aHViJyB9LFxuICAgICAgICB7IGV4ZWM6ICdzd2FtcCBleHRlbnNpb24gcHVsbCBAZ29vZGNyYWZ0L2dpdGh1YicgfSxcbiAgICAgICAgeyBleGVjOiBgc3dhbXAgdmF1bHQgY3JlYXRlIGxvY2FsX2VuY3J5cHRpb24gJHtzZWNyZXRzVmF1bHR9YCB9LFxuICAgICAgICB7XG4gICAgICAgICAgZXhlYzogYGVjaG8gXCJOZXh0OiBzd2FtcCB2YXVsdCBwdXQgJHtzZWNyZXRzVmF1bHR9IEdJVEhVQl9UT0tFTiAtLXJlZnJlc2gtZnJvbSAnZ2ggYXV0aCB0b2tlbidcImAsXG4gICAgICAgIH0sXG4gICAgICBdLFxuICAgIH0pO1xuXG4gICAgLy8gYHN3YW1wIGluaXRgIGlzIHNhZmUgdG8gcmUtcnVuIChpZGVtcG90ZW50IHdoZW4gYWxyZWFkeSBpbml0aWFsaXplZCksXG4gICAgLy8gc28gZXZlcnkgaW5zdGFsbCBrZWVwcyB0aGUgcmVwbyBpbml0aWFsaXplZCB3aXRob3V0IGEgc2VwYXJhdGUgbWFudWFsXG4gICAgLy8gc3RlcCDigJQgaW5jbHVkaW5nIG9uIGEgZnJlc2ggY2xvbmUgdGhhdCBoYXNuJ3QgcnVuIHN3YW1wOmJvb3RzdHJhcCB5ZXQuXG4gICAgcHJvamVjdC5wYWNrYWdlLmluc3RhbGxUYXNrLmV4ZWMoc3dhbXBJbml0U3RlcCk7XG4gICAgcHJvamVjdC5wYWNrYWdlLmluc3RhbGxDaVRhc2suZXhlYyhzd2FtcEluaXRTdGVwKTtcblxuICAgIC8vIE1pcnJvcnMgdGhlIHBhdHRlcm5zIGBzd2FtcCBpbml0YCBpdHNlbGYgd291bGQgYXBwZW5kIHRvIC5naXRpZ25vcmUuXG4gICAgLy8gcHJvamVuJ3MgSWdub3JlRmlsZSBmdWxseSByZWdlbmVyYXRlcyAuZ2l0aWdub3JlIGZyb20gaXRzIG93biB0cmFja2VkXG4gICAgLy8gZW50cmllcyBvbiBldmVyeSBzeW50aCwgc28gYW55dGhpbmcgYHN3YW1wIGluaXRgIGFwcGVuZHMgb3V0LW9mLWJhbmRcbiAgICAvLyBnZXRzIHNpbGVudGx5IHdpcGVkIGJ5IHRoZSBuZXh0IGBucHggcHJvamVuYCBydW4gKGNhdWdodCBieSBDSSdzXG4gICAgLy8gc2VsZi1tdXRhdGlvbiBjaGVjaykgdW5sZXNzIGl0J3MgYWxzbyBhZGRlZCBoZXJlLlxuICAgIHByb2plY3QuYWRkR2l0SWdub3JlKCcuc3dhbXAvJyk7XG4gICAgcHJvamVjdC5hZGRHaXRJZ25vcmUoJy5zd2FtcC1zb3VyY2VzLnlhbWwnKTtcbiAgICBwcm9qZWN0LmFkZEdpdElnbm9yZSgnLmNsYXVkZS93b3JrdHJlZXMvJyk7XG4gICAgcHJvamVjdC5hZGRHaXRJZ25vcmUoJy5jbGF1ZGUvc2V0dGluZ3MubG9jYWwuanNvbicpO1xuICAgIHByb2plY3QuYWRkR2l0SWdub3JlKCcuY2xhdWRlL3NjaGVkdWxlZF90YXNrcy5sb2NrJyk7XG4gICAgcHJvamVjdC5hZGRHaXRJZ25vcmUoJy5jbGF1ZGUvc2NoZWR1bGVkX3Rhc2tzLmpzb24nKTtcbiAgfVxufVxuIl19
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { NodeProject } from 'projen/lib/javascript';
|
|
2
|
+
export interface SoftwareFactoryModelsOptions {
|
|
3
|
+
/**
|
|
4
|
+
* The bare repo name (e.g. `expense-tracker`), used to derive model names
|
|
5
|
+
* like `<repoName>-factory`, `<repoName>-shell`, `<repoName>-secrets`.
|
|
6
|
+
*/
|
|
7
|
+
readonly repoName: string;
|
|
8
|
+
/**
|
|
9
|
+
* The GitHub org the repo lives in.
|
|
10
|
+
*/
|
|
11
|
+
readonly githubOwner: string;
|
|
12
|
+
/**
|
|
13
|
+
* The branch the factory opens pull requests against.
|
|
14
|
+
*/
|
|
15
|
+
readonly baseBranch: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Seed swamp model instances wiring the `@swamp/software-factory` state
|
|
19
|
+
* machine to this repo's GitHub issues/PRs and shell steps. These are
|
|
20
|
+
* `SampleFile`s (written once, then owned by the team) since the factory's
|
|
21
|
+
* stages/gates are the kind of thing a team tunes for its own workflow.
|
|
22
|
+
*/
|
|
23
|
+
export declare class SoftwareFactoryModels {
|
|
24
|
+
constructor(project: NodeProject, options: SoftwareFactoryModelsOptions);
|
|
25
|
+
}
|