declapract-typescript-ehmpathy 0.47.64 → 0.47.65
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/dist/practices/cicd-app-react-native-expo/best-practice/.github/workflows/deploy.yml +1 -0
- package/dist/practices/cicd-common/best-practice/.github/workflows/.test.yml +20 -0
- package/dist/practices/cicd-common/best-practice/.github/workflows/.test.yml.declapract.ts +7 -106
- package/dist/practices/cicd-common/best-practice/.github/workflows/test.yml +1 -0
- package/dist/practices/cicd-common/best-practice/.github/workflows/test.yml.declapract.ts +7 -37
- package/dist/practices/cicd-package/best-practice/.github/workflows/publish.yml +1 -0
- package/dist/practices/cicd-package/best-practice/.github/workflows/publish.yml.declapract.ts +7 -37
- package/dist/practices/cicd-service/best-practice/.github/workflows/deploy.yml +1 -0
- package/dist/practices/cicd-service/best-practice/.github/workflows/deploy.yml.declapract.ts +7 -37
- package/package.json +6 -6
- package/dist/.test/infra/withKeyrackContext.ts +0 -50
- package/dist/utils/buildWorkflowSecretsBlock.ts +0 -46
package/dist/practices/cicd-app-react-native-expo/best-practice/.github/workflows/deploy.yml
CHANGED
|
@@ -37,6 +37,7 @@ jobs:
|
|
|
37
37
|
with:
|
|
38
38
|
creds-aws-region: us-east-1
|
|
39
39
|
creds-aws-role-arn: arn:aws:iam::@declapract{variable.awsAccountId.dev}:role/@declapract{variable.projectName}-github-actions-dev
|
|
40
|
+
secrets: inherit # keyrack firewall in .test.yml filters to declared keys
|
|
40
41
|
|
|
41
42
|
dev:
|
|
42
43
|
uses: ./.github/workflows/.deploy-expo.yml
|
|
@@ -206,6 +206,16 @@ jobs:
|
|
|
206
206
|
- name: prepare:rhachet
|
|
207
207
|
run: npm run prepare:rhachet --if-present
|
|
208
208
|
|
|
209
|
+
# .why = keyrack firewall translates and validates secrets before tests run
|
|
210
|
+
# - filters to declared keys in keyrack.yml
|
|
211
|
+
# - translates mechanisms (e.g., GitHub App → ghs_* token)
|
|
212
|
+
# - blocks dangerous patterns (ghp_*, AKIA*, etc.)
|
|
213
|
+
# - exports to $GITHUB_ENV with mask applied
|
|
214
|
+
- name: keyrack firewall
|
|
215
|
+
run: npx rhachet keyrack firewall --env test --from 'json(env://SECRETS_JSON)' --into github.actions
|
|
216
|
+
env:
|
|
217
|
+
SECRETS_JSON: ${{ toJSON(secrets) }}
|
|
218
|
+
|
|
209
219
|
- name: get aws auth, if creds supplied
|
|
210
220
|
if: ${{ inputs.creds-aws-role-arn }}
|
|
211
221
|
uses: aws-actions/configure-aws-credentials@v4
|
|
@@ -280,6 +290,16 @@ jobs:
|
|
|
280
290
|
- name: prepare:rhachet
|
|
281
291
|
run: npm run prepare:rhachet --if-present
|
|
282
292
|
|
|
293
|
+
# .why = keyrack firewall translates and validates secrets before tests run
|
|
294
|
+
# - filters to declared keys in keyrack.yml
|
|
295
|
+
# - translates mechanisms (e.g., GitHub App → ghs_* token)
|
|
296
|
+
# - blocks dangerous patterns (ghp_*, AKIA*, etc.)
|
|
297
|
+
# - exports to $GITHUB_ENV with mask applied
|
|
298
|
+
- name: keyrack firewall
|
|
299
|
+
run: npx rhachet keyrack firewall --env test --from 'json(env://SECRETS_JSON)' --into github.actions
|
|
300
|
+
env:
|
|
301
|
+
SECRETS_JSON: ${{ toJSON(secrets) }}
|
|
302
|
+
|
|
283
303
|
- name: get aws auth, if creds supplied
|
|
284
304
|
if: ${{ inputs.creds-aws-role-arn }}
|
|
285
305
|
uses: aws-actions/configure-aws-credentials@v4
|
|
@@ -1,114 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
|
|
4
|
-
import type { FileCheckFunction, FileFixFunction } from 'declapract';
|
|
5
|
-
import { keyrack, type KeyrackGrantAttempt } from 'rhachet/keyrack';
|
|
1
|
+
import { FileCheckType, type FileFixFunction } from 'declapract';
|
|
6
2
|
|
|
7
3
|
/**
|
|
8
|
-
* .what =
|
|
9
|
-
* .why =
|
|
4
|
+
* .what = validates .test.yml matches the template exactly
|
|
5
|
+
* .why = keyrack firewall handles secrets at runtime; no build-time injection needed
|
|
10
6
|
*/
|
|
11
|
-
const
|
|
12
|
-
const keyrackYmlPath = join(projectRootDir, '.agent/keyrack.yml');
|
|
13
|
-
if (!existsSync(keyrackYmlPath)) return [];
|
|
14
|
-
|
|
15
|
-
const keys = (await keyrack.get({
|
|
16
|
-
for: { repo: true },
|
|
17
|
-
env: 'test',
|
|
18
|
-
})) as KeyrackGrantAttempt[];
|
|
19
|
-
if (!keys.length) return [];
|
|
20
|
-
|
|
21
|
-
// extract key names from slugs (format: org.env.KEY_NAME)
|
|
22
|
-
// .note = KeyrackGrantAttempt is a union of 4 variants (granted/absent/locked/blocked)
|
|
23
|
-
// per rhachet/dist/domain.objects/keyrack/KeyrackGrantAttempt.d.ts:8
|
|
24
|
-
// granted has slug at grant.slug; others have slug at top level
|
|
25
|
-
return keys.map((k) =>
|
|
26
|
-
(k.status === 'granted' ? k.grant.slug : k.slug).split('.').pop()!,
|
|
27
|
-
);
|
|
28
|
-
};
|
|
7
|
+
export const check = FileCheckType.EQUALS;
|
|
29
8
|
|
|
30
9
|
/**
|
|
31
|
-
* .what =
|
|
32
|
-
* .why =
|
|
33
|
-
*/
|
|
34
|
-
export const buildExpectedContent = (input: {
|
|
35
|
-
template: string;
|
|
36
|
-
keys: string[];
|
|
37
|
-
}): string => {
|
|
38
|
-
let result = input.template;
|
|
39
|
-
|
|
40
|
-
// if no keys, return template as-is
|
|
41
|
-
if (!input.keys.length) {
|
|
42
|
-
return result;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// build secrets declaration block for workflow_call
|
|
46
|
-
const secretsDeclaration = input.keys
|
|
47
|
-
.map(
|
|
48
|
-
(key) =>
|
|
49
|
-
` ${key}:\n description: "api key for ${key.toLowerCase().replace(/_/g, ' ')}"\n required: false`,
|
|
50
|
-
)
|
|
51
|
-
.join('\n');
|
|
52
|
-
|
|
53
|
-
// build env block for test-integration job
|
|
54
|
-
const envBlock = input.keys
|
|
55
|
-
.map((key) => ` ${key}: \${{ secrets.${key} }}`)
|
|
56
|
-
.join('\n');
|
|
57
|
-
|
|
58
|
-
// insert secrets declaration after workflow_call inputs
|
|
59
|
-
// look for the pattern: inputs: ... (multiline) followed by blank line or permissions:
|
|
60
|
-
result = result.replace(
|
|
61
|
-
/(on:\n workflow_call:\n inputs:\n(?: [^\n]+\n)+)/,
|
|
62
|
-
`$1 secrets:\n${secretsDeclaration}\n\n`,
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
// insert env block to test-integration job
|
|
66
|
-
// add env block after needs: [install] line
|
|
67
|
-
result = result.replace(
|
|
68
|
-
/(test-integration:\n runs-on: ubuntu-24\.04\n needs: \[install\]\n)/,
|
|
69
|
-
`$1 env:\n${envBlock}\n`,
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
return result;
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* .what = ensures .test.yml matches expected content with keyrack secrets
|
|
77
|
-
* .why = enables integration tests to access required api keys via github secrets
|
|
78
|
-
*/
|
|
79
|
-
export const check: FileCheckFunction = async (contents, context) => {
|
|
80
|
-
// get keyrack keys from project
|
|
81
|
-
const keys = await getKeyrackKeys(context.getProjectRootDirectory());
|
|
82
|
-
|
|
83
|
-
// build expected content from template + keys
|
|
84
|
-
const expected = buildExpectedContent({
|
|
85
|
-
template: context.declaredFileContents ?? '',
|
|
86
|
-
keys,
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
// if contents don't match expected, best practice is violated
|
|
90
|
-
if (contents !== expected) {
|
|
91
|
-
throw new Error(
|
|
92
|
-
'file does not match expected content with keyrack secrets',
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// return = file matches expected (best practice followed)
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* .what = fixes .test.yml to include keyrack secrets declaration and env vars
|
|
101
|
-
* .why = ensures integration tests have access to required api keys
|
|
10
|
+
* .what = replaces .test.yml with the template content
|
|
11
|
+
* .why = ensures workflow has firewall step for keyrack secrets
|
|
102
12
|
*/
|
|
103
13
|
export const fix: FileFixFunction = async (_contents, context) => {
|
|
104
|
-
|
|
105
|
-
const keys = await getKeyrackKeys(context.getProjectRootDirectory());
|
|
106
|
-
|
|
107
|
-
// build expected content from template + keys
|
|
108
|
-
const expected = buildExpectedContent({
|
|
109
|
-
template: context.declaredFileContents ?? '',
|
|
110
|
-
keys,
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
return { contents: expected };
|
|
14
|
+
return { contents: context.declaredFileContents ?? '' };
|
|
114
15
|
};
|
|
@@ -1,45 +1,15 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { UnexpectedCodePathError } from 'helpful-errors';
|
|
3
|
-
|
|
4
|
-
import { buildWorkflowSecretsBlock } from '../../../../../utils/buildWorkflowSecretsBlock';
|
|
1
|
+
import { FileCheckType, type FileFixFunction } from 'declapract';
|
|
5
2
|
|
|
6
3
|
/**
|
|
7
|
-
* .what =
|
|
8
|
-
* .why =
|
|
4
|
+
* .what = validates test.yml matches the template exactly
|
|
5
|
+
* .why = secrets: inherit passes all secrets to .test.yml; firewall handles key filter at runtime
|
|
9
6
|
*/
|
|
10
|
-
export const check
|
|
11
|
-
// fail fast if template not found
|
|
12
|
-
if (!context.declaredFileContents)
|
|
13
|
-
throw new UnexpectedCodePathError('declaredFileContents not found', {
|
|
14
|
-
relativeFilePath: context.relativeFilePath,
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
const expected = await buildWorkflowSecretsBlock(
|
|
18
|
-
{ template: context.declaredFileContents },
|
|
19
|
-
context,
|
|
20
|
-
);
|
|
21
|
-
|
|
22
|
-
// if contents don't match expected, best practice is violated
|
|
23
|
-
if (contents !== expected)
|
|
24
|
-
throw new Error('file does not match expected content with apikey secrets');
|
|
25
|
-
|
|
26
|
-
// return = file matches expected (best practice followed)
|
|
27
|
-
};
|
|
7
|
+
export const check = FileCheckType.EQUALS;
|
|
28
8
|
|
|
29
9
|
/**
|
|
30
|
-
* .what =
|
|
31
|
-
* .why = ensures
|
|
10
|
+
* .what = replaces test.yml with the template content
|
|
11
|
+
* .why = ensures caller workflow has secrets: inherit for firewall
|
|
32
12
|
*/
|
|
33
13
|
export const fix: FileFixFunction = async (_contents, context) => {
|
|
34
|
-
|
|
35
|
-
if (!context.declaredFileContents)
|
|
36
|
-
throw new UnexpectedCodePathError('declaredFileContents not found', {
|
|
37
|
-
relativeFilePath: context.relativeFilePath,
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
const expected = await buildWorkflowSecretsBlock(
|
|
41
|
-
{ template: context.declaredFileContents },
|
|
42
|
-
context,
|
|
43
|
-
);
|
|
44
|
-
return { contents: expected };
|
|
14
|
+
return { contents: context.declaredFileContents ?? '' };
|
|
45
15
|
};
|
|
@@ -19,6 +19,7 @@ jobs:
|
|
|
19
19
|
with:
|
|
20
20
|
creds-aws-region: us-east-1
|
|
21
21
|
creds-aws-role-arn: ${{ vars.CREDS_CICD_AWS_DEV_OIDC_ROLE_ARN }} # use aws auth via oidc, if this repo supplies it
|
|
22
|
+
secrets: inherit # keyrack firewall in .test.yml filters to declared keys
|
|
22
23
|
|
|
23
24
|
publish:
|
|
24
25
|
uses: ./.github/workflows/.publish-npm.yml
|
package/dist/practices/cicd-package/best-practice/.github/workflows/publish.yml.declapract.ts
CHANGED
|
@@ -1,45 +1,15 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { UnexpectedCodePathError } from 'helpful-errors';
|
|
3
|
-
|
|
4
|
-
import { buildWorkflowSecretsBlock } from '../../../../../utils/buildWorkflowSecretsBlock';
|
|
1
|
+
import { FileCheckType, type FileFixFunction } from 'declapract';
|
|
5
2
|
|
|
6
3
|
/**
|
|
7
|
-
* .what =
|
|
8
|
-
* .why =
|
|
4
|
+
* .what = validates publish.yml matches the template exactly
|
|
5
|
+
* .why = secrets: inherit passes all secrets to .test.yml; firewall handles key filter at runtime
|
|
9
6
|
*/
|
|
10
|
-
export const check
|
|
11
|
-
// fail fast if template not found
|
|
12
|
-
if (!context.declaredFileContents)
|
|
13
|
-
throw new UnexpectedCodePathError('declaredFileContents not found', {
|
|
14
|
-
relativeFilePath: context.relativeFilePath,
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
const expected = await buildWorkflowSecretsBlock(
|
|
18
|
-
{ template: context.declaredFileContents },
|
|
19
|
-
context,
|
|
20
|
-
);
|
|
21
|
-
|
|
22
|
-
// if contents don't match expected, best practice is violated
|
|
23
|
-
if (contents !== expected)
|
|
24
|
-
throw new Error('file does not match expected content with apikey secrets');
|
|
25
|
-
|
|
26
|
-
// return = file matches expected (best practice followed)
|
|
27
|
-
};
|
|
7
|
+
export const check = FileCheckType.EQUALS;
|
|
28
8
|
|
|
29
9
|
/**
|
|
30
|
-
* .what =
|
|
31
|
-
* .why = ensures
|
|
10
|
+
* .what = replaces publish.yml with the template content
|
|
11
|
+
* .why = ensures caller workflow has secrets: inherit for firewall
|
|
32
12
|
*/
|
|
33
13
|
export const fix: FileFixFunction = async (_contents, context) => {
|
|
34
|
-
|
|
35
|
-
if (!context.declaredFileContents)
|
|
36
|
-
throw new UnexpectedCodePathError('declaredFileContents not found', {
|
|
37
|
-
relativeFilePath: context.relativeFilePath,
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
const expected = await buildWorkflowSecretsBlock(
|
|
41
|
-
{ template: context.declaredFileContents },
|
|
42
|
-
context,
|
|
43
|
-
);
|
|
44
|
-
return { contents: expected };
|
|
14
|
+
return { contents: context.declaredFileContents ?? '' };
|
|
45
15
|
};
|
|
@@ -41,6 +41,7 @@ jobs:
|
|
|
41
41
|
with:
|
|
42
42
|
creds-aws-region: us-east-1
|
|
43
43
|
creds-aws-role-arn: ${{ vars.CREDS_CICD_AWS_DEV_OIDC_ROLE_ARN }} # use aws auth via oidc, if this repo supplies it
|
|
44
|
+
secrets: inherit # keyrack firewall in .test.yml filters to declared keys
|
|
44
45
|
|
|
45
46
|
dev:
|
|
46
47
|
uses: ./.github/workflows/.deploy-sls.yml
|
package/dist/practices/cicd-service/best-practice/.github/workflows/deploy.yml.declapract.ts
CHANGED
|
@@ -1,45 +1,15 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { UnexpectedCodePathError } from 'helpful-errors';
|
|
3
|
-
|
|
4
|
-
import { buildWorkflowSecretsBlock } from '../../../../../utils/buildWorkflowSecretsBlock';
|
|
1
|
+
import { FileCheckType, type FileFixFunction } from 'declapract';
|
|
5
2
|
|
|
6
3
|
/**
|
|
7
|
-
* .what =
|
|
8
|
-
* .why =
|
|
4
|
+
* .what = validates deploy.yml matches the template exactly
|
|
5
|
+
* .why = secrets: inherit passes all secrets to .test.yml; firewall handles key filter at runtime
|
|
9
6
|
*/
|
|
10
|
-
export const check
|
|
11
|
-
// fail fast if template not found
|
|
12
|
-
if (!context.declaredFileContents)
|
|
13
|
-
throw new UnexpectedCodePathError('declaredFileContents not found', {
|
|
14
|
-
relativeFilePath: context.relativeFilePath,
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
const expected = await buildWorkflowSecretsBlock(
|
|
18
|
-
{ template: context.declaredFileContents },
|
|
19
|
-
context,
|
|
20
|
-
);
|
|
21
|
-
|
|
22
|
-
// if contents don't match expected, best practice is violated
|
|
23
|
-
if (contents !== expected)
|
|
24
|
-
throw new Error('file does not match expected content with apikey secrets');
|
|
25
|
-
|
|
26
|
-
// return = file matches expected (best practice followed)
|
|
27
|
-
};
|
|
7
|
+
export const check = FileCheckType.EQUALS;
|
|
28
8
|
|
|
29
9
|
/**
|
|
30
|
-
* .what =
|
|
31
|
-
* .why =
|
|
10
|
+
* .what = replaces deploy.yml with the template content
|
|
11
|
+
* .why = ensures caller workflow has secrets: inherit for firewall
|
|
32
12
|
*/
|
|
33
13
|
export const fix: FileFixFunction = async (_contents, context) => {
|
|
34
|
-
|
|
35
|
-
if (!context.declaredFileContents)
|
|
36
|
-
throw new UnexpectedCodePathError('declaredFileContents not found', {
|
|
37
|
-
relativeFilePath: context.relativeFilePath,
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
const expected = await buildWorkflowSecretsBlock(
|
|
41
|
-
{ template: context.declaredFileContents },
|
|
42
|
-
context,
|
|
43
|
-
);
|
|
44
|
-
return { contents: expected };
|
|
14
|
+
return { contents: context.declaredFileContents ?? '' };
|
|
45
15
|
};
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "declapract-typescript-ehmpathy",
|
|
3
3
|
"author": "ehmpathy",
|
|
4
4
|
"description": "declapract best practices declarations for typescript",
|
|
5
|
-
"version": "0.47.
|
|
5
|
+
"version": "0.47.65",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"main": "src/index.js",
|
|
8
8
|
"repository": "ehmpathy/declapract-typescript-ehmpathy",
|
|
@@ -75,12 +75,12 @@
|
|
|
75
75
|
"esbuild-register": "3.6.0",
|
|
76
76
|
"husky": "8.0.3",
|
|
77
77
|
"jest": "30.2.0",
|
|
78
|
-
"rhachet": "1.
|
|
79
|
-
"rhachet-brains-anthropic": "0.4.
|
|
78
|
+
"rhachet": "1.41.1",
|
|
79
|
+
"rhachet-brains-anthropic": "0.4.1",
|
|
80
80
|
"rhachet-brains-xai": "0.3.3",
|
|
81
|
-
"rhachet-roles-bhrain": "0.
|
|
82
|
-
"rhachet-roles-bhuild": "0.
|
|
83
|
-
"rhachet-roles-ehmpathy": "1.
|
|
81
|
+
"rhachet-roles-bhrain": "0.27.5",
|
|
82
|
+
"rhachet-roles-bhuild": "0.21.1",
|
|
83
|
+
"rhachet-roles-ehmpathy": "1.35.0",
|
|
84
84
|
"tsc-alias": "1.8.10",
|
|
85
85
|
"tsx": "4.20.6",
|
|
86
86
|
"type-fns": "1.21.2",
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import os from 'os';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
|
|
5
|
-
import type { KeyrackGrantAttempt } from 'rhachet/keyrack';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* .what = creates a mock context with keyrack config in a temp directory
|
|
9
|
-
* .why = enables test of buildWorkflowSecretsBlock with different keyrack configs
|
|
10
|
-
*/
|
|
11
|
-
export const withKeyrackContext = async (
|
|
12
|
-
input: { keys: string[] },
|
|
13
|
-
fn: (context: { getProjectRootDirectory: () => string }) => Promise<void>,
|
|
14
|
-
): Promise<void> => {
|
|
15
|
-
// create temp dir with keyrack.yml
|
|
16
|
-
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'test-'));
|
|
17
|
-
const agentDir = path.join(tempDir, '.agent');
|
|
18
|
-
fs.mkdirSync(agentDir, { recursive: true });
|
|
19
|
-
fs.writeFileSync(
|
|
20
|
-
path.join(agentDir, 'keyrack.yml'),
|
|
21
|
-
`org: test\nenv.test:\n${input.keys.map((k) => ` - ${k}`).join('\n')}`,
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
// mock keyrack.get to return grant attempts
|
|
25
|
-
const mockGrants: KeyrackGrantAttempt[] = input.keys.map((key) => ({
|
|
26
|
-
status: 'granted' as const,
|
|
27
|
-
grant: {
|
|
28
|
-
slug: `test.test.${key}`,
|
|
29
|
-
value: 'mock-value',
|
|
30
|
-
mech: 'PERMANENT_VIA_REPLICA' as const,
|
|
31
|
-
vault: 'os.direct' as const,
|
|
32
|
-
},
|
|
33
|
-
}));
|
|
34
|
-
|
|
35
|
-
// store original module
|
|
36
|
-
const keyrackModule = await import('rhachet/keyrack');
|
|
37
|
-
const originalGet = keyrackModule.keyrack.get;
|
|
38
|
-
|
|
39
|
-
// replace with mock
|
|
40
|
-
(keyrackModule.keyrack as { get: typeof originalGet }).get = async () =>
|
|
41
|
-
mockGrants;
|
|
42
|
-
|
|
43
|
-
try {
|
|
44
|
-
await fn({ getProjectRootDirectory: () => tempDir });
|
|
45
|
-
} finally {
|
|
46
|
-
// restore original
|
|
47
|
-
(keyrackModule.keyrack as { get: typeof originalGet }).get = originalGet;
|
|
48
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
49
|
-
}
|
|
50
|
-
};
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
|
|
4
|
-
import { keyrack, type KeyrackGrantAttempt } from 'rhachet/keyrack';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* .what = builds workflow content with keyrack secrets block for .test.yml
|
|
8
|
-
* .why = single source of truth for test.yml, publish.yml, deploy.yml check+fix
|
|
9
|
-
*/
|
|
10
|
-
export const buildWorkflowSecretsBlock = async (
|
|
11
|
-
input: { template: string },
|
|
12
|
-
context: { getProjectRootDirectory: () => string },
|
|
13
|
-
): Promise<string> => {
|
|
14
|
-
// check if keyrack.yml exists
|
|
15
|
-
const keyrackYmlPath = join(
|
|
16
|
-
context.getProjectRootDirectory(),
|
|
17
|
-
'.agent/keyrack.yml',
|
|
18
|
-
);
|
|
19
|
-
if (!existsSync(keyrackYmlPath)) return input.template;
|
|
20
|
-
|
|
21
|
-
// get required keys from keyrack sdk
|
|
22
|
-
const keys = (await keyrack.get({
|
|
23
|
-
for: { repo: true },
|
|
24
|
-
env: 'test',
|
|
25
|
-
})) as KeyrackGrantAttempt[];
|
|
26
|
-
if (!keys.length) return input.template;
|
|
27
|
-
|
|
28
|
-
// extract key names from slugs (format: org.env.KEY_NAME)
|
|
29
|
-
// .note = KeyrackGrantAttempt is a union of 4 variants (granted/absent/locked/blocked)
|
|
30
|
-
// per rhachet/dist/domain.objects/keyrack/KeyrackGrantAttempt.d.ts:8
|
|
31
|
-
// granted has slug at grant.slug; others have slug at top level
|
|
32
|
-
const keyrackVars = keys.map((k) =>
|
|
33
|
-
(k.status === 'granted' ? k.grant.slug : k.slug).split('.').pop()!,
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
// build secrets block
|
|
37
|
-
const secretsBlock = keyrackVars
|
|
38
|
-
.map((key) => ` ${key}: \${{ secrets.${key} }}`)
|
|
39
|
-
.join('\n');
|
|
40
|
-
|
|
41
|
-
// find jobs that call .test.yml with 'with:' block and add secrets block after
|
|
42
|
-
return input.template.replace(
|
|
43
|
-
/(uses: \.\/\.github\/workflows\/\.test\.yml\n(?: if: [^\n]+\n)? with:\n(?: [^\n]+\n)+)/g,
|
|
44
|
-
`$1 secrets:\n${secretsBlock}\n`,
|
|
45
|
-
);
|
|
46
|
-
};
|