@bifravst/ci 2.1.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -20,3 +20,19 @@ file.
20
20
  ```bash
21
21
  npx cdk deploy
22
22
  ```
23
+
24
+ ## Node & NPM
25
+
26
+ This project requires Node.js `>=24.18.1 <25` and npm `>=12.0.2 <13` (enforced
27
+ via `check-node-version` on `npm install` and `npm ci`).
28
+
29
+ The check is skipped during `npm publish` and `npm pack`, because
30
+ `semantic-release` bundles its own npm (`@semantic-release/npm` depends on
31
+ `npm@^11.6.2`) and runs the publish with that version rather than the one
32
+ installed in CI.
33
+
34
+ ## TypeScript 6 and 7
35
+
36
+ This repo
37
+ [runs TypeScript 6 and 7 side by side](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/#running-side-by-side-with-typescript-6.0),
38
+ [so that eslint works](https://github.com/typescript-eslint/typescript-eslint/issues/10940#issuecomment-4922812181).
@@ -1,7 +1,7 @@
1
1
  import { App } from 'aws-cdk-lib';
2
2
  import { CIStack } from './CIStack.js';
3
3
  export class CIApp extends App {
4
- constructor(name, args){
4
+ constructor(name, args) {
5
5
  super();
6
6
  new CIStack(this, name, args);
7
7
  }
@@ -1,21 +1,17 @@
1
1
  import { CfnOutput, Stack } from 'aws-cdk-lib';
2
2
  import { RepoPermission } from './RepoPermission.js';
3
3
  export class CIStack extends Stack {
4
- constructor(parent, name, { repos, gitHubOICDProviderArn }){
4
+ constructor(parent, name, { repos, gitHubOICDProviderArn, }) {
5
5
  super(parent, name);
6
- for (const { id, owner, name: repo } of repos){
6
+ for (const { id, owner, name: repo } of repos) {
7
7
  const perm = new RepoPermission(this, repo, {
8
- repository: {
9
- id,
10
- owner,
11
- name: repo
12
- },
13
- gitHubOICDProviderArn
8
+ repository: { id, owner, name: repo },
9
+ gitHubOICDProviderArn,
14
10
  });
15
11
  new CfnOutput(this, `${id}:ciRoleArn`, {
16
12
  exportName: `${name}:${id}:ciRoleArn`,
17
13
  description: `Role ARN to use for running continuous integration tests using GitHub Actions in the repository ${owner}/${repo} (${id})`,
18
- value: perm.role.roleArn
14
+ value: perm.role.roleArn,
19
15
  });
20
16
  }
21
17
  }
@@ -2,7 +2,7 @@ import { Duration, aws_iam as IAM, Stack } from 'aws-cdk-lib';
2
2
  import { Construct } from 'constructs';
3
3
  export class ContinuousDeployment extends Construct {
4
4
  role;
5
- constructor(parent, { repository: { owner, repo }, gitHubOICDProviderArn, environment }){
5
+ constructor(parent, { repository: { owner, repo }, gitHubOICDProviderArn, environment, }) {
6
6
  super(parent, 'cd');
7
7
  const gitHubOIDC = IAM.OpenIdConnectProvider.fromOpenIdConnectProviderArn(this, 'gitHubOICDProvider', gitHubOICDProviderArn);
8
8
  this.role = new IAM.Role(this, 'ghRole', {
@@ -10,14 +10,14 @@ export class ContinuousDeployment extends Construct {
10
10
  assumedBy: new IAM.WebIdentityPrincipal(gitHubOIDC.openIdConnectProviderArn, {
11
11
  StringEquals: {
12
12
  [`token.actions.githubusercontent.com:sub`]: `repo:${owner}/${repo}:environment:${environment ?? 'production'}`,
13
- [`token.actions.githubusercontent.com:aud`]: 'sts.amazonaws.com'
14
- }
13
+ [`token.actions.githubusercontent.com:aud`]: 'sts.amazonaws.com',
14
+ },
15
15
  }),
16
16
  description: `This role is used by GitHub Actions to deploy ${Stack.of(this).stackName}`,
17
17
  maxSessionDuration: Duration.hours(1),
18
18
  managedPolicies: [
19
- IAM.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess')
20
- ]
19
+ IAM.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess'),
20
+ ],
21
21
  });
22
22
  }
23
23
  }
@@ -3,24 +3,24 @@ import { Construct } from 'constructs';
3
3
  import { sanitize } from './sanitize.js';
4
4
  export class RepoPermission extends Construct {
5
5
  role;
6
- constructor(parent, id, { repository, gitHubOICDProviderArn }){
6
+ constructor(parent, id, { repository, gitHubOICDProviderArn, }) {
7
7
  super(parent, id);
8
8
  const gitHubOIDC = IAM.OpenIdConnectProvider.fromOpenIdConnectProviderArn(this, 'gitHubOICDProvider', gitHubOICDProviderArn);
9
9
  this.role = new IAM.Role(this, 'ghRole', {
10
10
  roleName: `${Stack.of(this).stackName}-${sanitize(repository.name)}`,
11
11
  assumedBy: new IAM.WebIdentityPrincipal(gitHubOIDC.openIdConnectProviderArn, {
12
12
  StringEquals: {
13
- [`token.actions.githubusercontent.com:aud`]: 'sts.amazonaws.com'
13
+ [`token.actions.githubusercontent.com:aud`]: 'sts.amazonaws.com',
14
14
  },
15
15
  StringLike: {
16
- [`token.actions.githubusercontent.com:sub`]: `repo:${repository.owner}/${repository.name}:*`
17
- }
16
+ [`token.actions.githubusercontent.com:sub`]: `repo:${repository.owner}/${repository.name}:*`,
17
+ },
18
18
  }),
19
19
  description: `This role is used by GitHub Actions to run Continuous Integration Tests ${Stack.of(this).stackName} in the repository ${repository.owner}/${repository.name} (${repository.id}).`,
20
20
  maxSessionDuration: Duration.hours(1),
21
21
  managedPolicies: [
22
- IAM.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess')
23
- ]
22
+ IAM.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess'),
23
+ ],
24
24
  });
25
25
  }
26
26
  }
@@ -5,16 +5,16 @@ import { ensureGitHubOIDCProvider } from './ensureGitHubOIDCProvider.js';
5
5
  import { listRepos } from './listRepos.js';
6
6
  import { loadRepoList } from './loadRepoList.js';
7
7
  const { token } = fromEnv({
8
- token: 'GITHUB_TOKEN'
8
+ token: 'GITHUB_TOKEN',
9
9
  })(process.env);
10
10
  const repos = await listRepos(token, await loadRepoList(process.env.REPOS_LIST ?? './repos.txt'));
11
- for (const repo of repos){
11
+ for (const repo of repos) {
12
12
  console.debug(`Setting up permissions for ${repo.name} (${repo.id})...`);
13
13
  }
14
14
  const iam = new IAMClient({});
15
15
  new CIApp(process.env.STACK_NAME ?? 'bifravst-ci', {
16
16
  gitHubOICDProviderArn: await ensureGitHubOIDCProvider({
17
- iam
17
+ iam,
18
18
  }),
19
- repos
19
+ repos,
20
20
  });
@@ -0,0 +1,37 @@
1
+ import { CreateOpenIDConnectProviderCommand, GetOpenIDConnectProviderCommand, ListOpenIDConnectProvidersCommand, } from '@aws-sdk/client-iam';
2
+ import chalk from 'chalk';
3
+ /**
4
+ * Returns the ARN of the OpenID Connect provider for GitHub of the account.
5
+ */
6
+ export const ensureGitHubOIDCProvider = async ({ iam, }) => {
7
+ const { OpenIDConnectProviderList } = await iam.send(new ListOpenIDConnectProvidersCommand({}));
8
+ const maybeGithubProvider = (await Promise.all(OpenIDConnectProviderList?.map(async ({ Arn }) => iam
9
+ .send(new GetOpenIDConnectProviderCommand({
10
+ OpenIDConnectProviderArn: Arn,
11
+ }))
12
+ .then((provider) => ({ Arn, provider }))) ?? [])).find(({ provider: { Url } }) => Url === 'token.actions.githubusercontent.com');
13
+ if (maybeGithubProvider?.Arn !== undefined) {
14
+ console.debug(chalk.green(`OIDC provider for GitHub exists: ${maybeGithubProvider.Arn}`));
15
+ return maybeGithubProvider.Arn;
16
+ }
17
+ console.log(chalk.yellow(`OIDC provider for GitHub does not exist. Creating ...`));
18
+ const provider = await iam.send(new CreateOpenIDConnectProviderCommand({
19
+ Url: `https://token.actions.githubusercontent.com`,
20
+ ClientIDList: ['sts.amazonaws.com'],
21
+ /**
22
+ * Starting July 6, 2023, AWS began securing communication with
23
+ * GitHub’s OIDC identity provider (IdP) using our library of
24
+ * trusted root Certificate Authorities instead of using a
25
+ * certificate thumbprint to verify the IdP’s server certificate.
26
+ * This approach ensures that your GitHub OIDC configuration behaves
27
+ * correctly without disruption during future certificate rotations
28
+ * and changes. With this new validation approach in place, your
29
+ * legacy thumbprint(s) will remain in your configuration but will
30
+ * no longer be needed for validation purposes.
31
+ */
32
+ ThumbprintList: ['6938fd4d98bab03faadb97b34396831e3780aea1'],
33
+ }));
34
+ if (provider.OpenIDConnectProviderArn === undefined)
35
+ throw new Error(`Failed to create OpenID Connect Provider for GitHub!`);
36
+ return provider.OpenIDConnectProviderArn;
37
+ };
@@ -0,0 +1,17 @@
1
+ import { Octokit } from '@octokit/rest';
2
+ export const listRepos = async (token, repos) => {
3
+ const client = new Octokit({
4
+ auth: token,
5
+ });
6
+ return (await Promise.all(repos.map(async (repo) => client.repos.get({
7
+ repo: repo.name,
8
+ owner: repo.owner,
9
+ }))))
10
+ .map((res) => res.data)
11
+ .filter((repo) => repo.archived !== true)
12
+ .map((repo) => ({
13
+ id: repo.id,
14
+ name: repo.name,
15
+ owner: repo.full_name.split('/')[0],
16
+ }));
17
+ };
@@ -0,0 +1,9 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ export const loadRepoList = async (location) => (await readFile(location, 'utf-8'))
3
+ .trim()
4
+ .split('\n')
5
+ .filter((s) => !s.startsWith('#'))
6
+ .map((s) => {
7
+ const [owner, name] = s.split('/');
8
+ return { owner, name };
9
+ });
@@ -0,0 +1,18 @@
1
+ import assert from 'node:assert/strict';
2
+ import path from 'node:path';
3
+ import { describe, it } from 'node:test';
4
+ import { loadRepoList } from './loadRepoList.js';
5
+ void describe('loadRepoList()', () => {
6
+ void it('should load the list of repos', async () => {
7
+ assert.deepEqual(await loadRepoList(path.join(process.cwd(), 'cdk', 'test', 'repos.txt')), [
8
+ {
9
+ name: 'ci',
10
+ owner: 'bifravst',
11
+ },
12
+ {
13
+ name: 'aws-cdk-ecr-helpers',
14
+ owner: 'bifravst',
15
+ },
16
+ ]);
17
+ });
18
+ });
@@ -0,0 +1 @@
1
+ export const sanitize = (s) => s.replace(/[^a-z0-9-]/g, '');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bifravst/ci",
3
- "version": "2.1.2",
3
+ "version": "3.0.0",
4
4
  "description": "Sets up the permissions in our CI account for all repositories in this account to be able to use it for CI runs.",
5
5
  "exports": {
6
6
  ".": {
@@ -14,7 +14,7 @@
14
14
  "scripts": {
15
15
  "prepare": "husky && case \"$npm_command\" in install|ci) check-node-version --package ;; esac",
16
16
  "test": "node --no-warnings --experimental-transform-types --test \"!(node_modules)/**/*.spec.ts\"",
17
- "prepublishOnly": "node --experimental-strip-types ./.npm/compile.ts && npx tsc -P ./.npm/tsconfig.npm.json --outDir ./dist"
17
+ "prepublishOnly": "npx tsc --noEmit false -d"
18
18
  },
19
19
  "repository": {
20
20
  "type": "git",
@@ -34,19 +34,18 @@
34
34
  "author": "Nordic Semiconductor ASA | nordicsemi.no",
35
35
  "license": "BSD-3-Clause",
36
36
  "devDependencies": {
37
- "@bifravst/eslint-config-typescript": "7.0.32",
38
- "@bifravst/from-env": "3.0.2",
39
- "@bifravst/prettier-config": "1.1.17",
40
- "@commitlint/config-conventional": "21.2.0",
37
+ "@bifravst/eslint-config-typescript": "8.0.0",
38
+ "@bifravst/from-env": "4.0.0",
39
+ "@bifravst/prettier-config": "2.0.0",
40
+ "@commitlint/config-conventional": "21.2.2",
41
41
  "@octokit/rest": "22.0.1",
42
- "@swc/core": "1.15.47",
43
- "@types/node": "26.1.2",
42
+ "@types/node": "26.2.0",
44
43
  "@typescript/native": "npm:typescript@7.0.2",
45
- "aws-cdk": "2.1135.0",
46
- "aws-cdk-lib": "2.263.0",
47
- "cdk": "2.1135.0",
44
+ "aws-cdk": "2.1136.0",
45
+ "aws-cdk-lib": "2.265.0",
46
+ "cdk": "2.1136.0",
48
47
  "check-node-version": "4.2.1",
49
- "commitlint": "21.2.1",
48
+ "commitlint": "21.2.2",
50
49
  "constructs": "10.8.1",
51
50
  "globstar": "1.0.0",
52
51
  "husky": "9.1.7",
@@ -99,7 +98,7 @@
99
98
  ],
100
99
  "prettier": "@bifravst/prettier-config",
101
100
  "peerDependencies": {
102
- "@aws-sdk/client-iam": "^3.1031.0",
103
- "chalk": "^5.6.2"
101
+ "@aws-sdk/client-iam": ">=3.1111.0 <4",
102
+ "chalk": "6.x"
104
103
  }
105
104
  }
@@ -1,39 +0,0 @@
1
- import { CreateOpenIDConnectProviderCommand, GetOpenIDConnectProviderCommand, ListOpenIDConnectProvidersCommand } from '@aws-sdk/client-iam';
2
- import chalk from 'chalk';
3
- /**
4
- * Returns the ARN of the OpenID Connect provider for GitHub of the account.
5
- */ export const ensureGitHubOIDCProvider = async ({ iam })=>{
6
- const { OpenIDConnectProviderList } = await iam.send(new ListOpenIDConnectProvidersCommand({}));
7
- const maybeGithubProvider = (await Promise.all(OpenIDConnectProviderList?.map(async ({ Arn })=>iam.send(new GetOpenIDConnectProviderCommand({
8
- OpenIDConnectProviderArn: Arn
9
- })).then((provider)=>({
10
- Arn,
11
- provider
12
- }))) ?? [])).find(({ provider: { Url } })=>Url === 'token.actions.githubusercontent.com');
13
- if (maybeGithubProvider?.Arn !== undefined) {
14
- console.debug(chalk.green(`OIDC provider for GitHub exists: ${maybeGithubProvider.Arn}`));
15
- return maybeGithubProvider.Arn;
16
- }
17
- console.log(chalk.yellow(`OIDC provider for GitHub does not exist. Creating ...`));
18
- const provider = await iam.send(new CreateOpenIDConnectProviderCommand({
19
- Url: `https://token.actions.githubusercontent.com`,
20
- ClientIDList: [
21
- 'sts.amazonaws.com'
22
- ],
23
- /**
24
- * Starting July 6, 2023, AWS began securing communication with
25
- * GitHub’s OIDC identity provider (IdP) using our library of
26
- * trusted root Certificate Authorities instead of using a
27
- * certificate thumbprint to verify the IdP’s server certificate.
28
- * This approach ensures that your GitHub OIDC configuration behaves
29
- * correctly without disruption during future certificate rotations
30
- * and changes. With this new validation approach in place, your
31
- * legacy thumbprint(s) will remain in your configuration but will
32
- * no longer be needed for validation purposes.
33
- */ ThumbprintList: [
34
- '6938fd4d98bab03faadb97b34396831e3780aea1'
35
- ]
36
- }));
37
- if (provider.OpenIDConnectProviderArn === undefined) throw new Error(`Failed to create OpenID Connect Provider for GitHub!`);
38
- return provider.OpenIDConnectProviderArn;
39
- };
@@ -1,14 +0,0 @@
1
- import { Octokit } from '@octokit/rest';
2
- export const listRepos = async (token, repos)=>{
3
- const client = new Octokit({
4
- auth: token
5
- });
6
- return (await Promise.all(repos.map(async (repo)=>client.repos.get({
7
- repo: repo.name,
8
- owner: repo.owner
9
- })))).map((res)=>res.data).filter((repo)=>repo.archived !== true).map((repo)=>({
10
- id: repo.id,
11
- name: repo.name,
12
- owner: repo.full_name.split('/')[0]
13
- }));
14
- };
@@ -1,8 +0,0 @@
1
- import { readFile } from 'node:fs/promises';
2
- export const loadRepoList = async (location)=>(await readFile(location, 'utf-8')).trim().split('\n').filter((s)=>!s.startsWith('#')).map((s)=>{
3
- const [owner, name] = s.split('/');
4
- return {
5
- owner,
6
- name
7
- };
8
- });
@@ -1 +0,0 @@
1
- export const sanitize = (s)=>s.replace(/[^a-z0-9-]/g, '');
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes