Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions .github/scripts/list_workspaces.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/**
* Lists the workspaces a CI matrix should cover, derived from the root
* `package.json` and each workspace's own files, so that adding a package to
* the root `workspaces` array is all it takes to get it into CI.
*
* Usage: node list_workspaces.js <unit|e2e|e2e-lmi>
*
* unit every non-private workspace under `packages/`; the other workspaces
* (layers, examples, testing utils) have dedicated jobs
* e2e every workspace with `tests/e2e/*.test.ts` files other than LMI ones
* e2e-lmi every workspace with `tests/e2e/lmi*.test.ts` files
*
* A workspace with e2e suites must also define the script the matrix job runs
* (`test:e2e`, or `test:e2e:lmi` for LMI suites), otherwise this fails loudly
* instead of letting the job fail on a missing script.
*
* Prints a sorted JSON array of workspace paths, ready for `fromJSON()` in a
* matrix definition.
*/
import { existsSync, readdirSync, readFileSync } from 'node:fs';
import { join } from 'node:path';

const rootDir = join(import.meta.dirname, '..', '..');

const readManifest = (workspace) =>
JSON.parse(readFileSync(join(rootDir, workspace, 'package.json'), 'utf-8'));

const e2eTestFiles = (workspace) => {
const dir = join(rootDir, workspace, 'tests', 'e2e');
if (!existsSync(dir)) return [];
return readdirSync(dir).filter((file) => file.endsWith('.test.ts'));
};

/** LMI suites run in their own job, see `test:e2e:lmi` in the package scripts. */
const isLmiTest = (file) => file.startsWith('lmi.');

const fail = (message) => {
console.error(message);
process.exit(1);
};

const hasE2eSuites = (workspace, matches, script) => {
const hasSuites = e2eTestFiles(workspace).some(matches);
if (hasSuites && readManifest(workspace).scripts?.[script] === undefined) {
fail(`${workspace} has e2e suites but no "${script}" script to run them`);
}
return hasSuites;
};

const selectors = {
unit: (workspace) =>
workspace.startsWith('packages/') &&
readManifest(workspace).private !== true,
e2e: (workspace) =>
hasE2eSuites(workspace, (file) => !isLmiTest(file), 'test:e2e'),
'e2e-lmi': (workspace) => hasE2eSuites(workspace, isLmiTest, 'test:e2e:lmi'),
};

const [mode] = process.argv.slice(2);
const select = selectors[mode];
if (select === undefined) {
console.error(
`Usage: node list_workspaces.js <${Object.keys(selectors).join('|')}>`
);
process.exit(1);
}

const { workspaces } = readManifest('.');
const selected = [];
for (const workspace of workspaces) {
if (workspace.includes('*')) {
console.error(
`Workspace pattern "${workspace}" is a glob; list every workspace explicitly in the root package.json`
);
process.exit(1);
}
if (select(workspace)) selected.push(workspace);
}

if (selected.length === 0) {
console.error(`No workspaces matched mode "${mode}"`);
process.exit(1);
}

console.log(JSON.stringify(selected.toSorted()));
104 changes: 6 additions & 98 deletions .github/workflows/quality_check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,109 +2,17 @@ name: Code quality

# USAGE
#
# Automatically triggered when a PR is added to the merge queue.
# Automatically triggered when a PR is added to the merge queue. Runs the same linting
# and unit test jobs as the PR workflow, see ./.github/workflows/reusable-run-linting-check-and-unit-tests.yml.

on:
merge_group:
branches:
- main

permissions: {}
permissions:
contents: read

jobs:
code-quality:
permissions:
contents: read
runs-on: ubuntu-latest
env:
NODE_ENV: dev
strategy:
matrix:
version: [22, 24]
workspace: [
"packages/batch",
"packages/commons",
"packages/event-handler",
"packages/idempotency",
"packages/jmespath",
"packages/logger",
"packages/tracer",
"packages/parser",
"packages/parameters",
"packages/metrics"
]
fail-fast: false
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup NodeJS
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: ${{ matrix.version }}
cache: "npm"
- name: Setup dependencies
uses: aws-powertools/actions/.github/actions/cached-node-modules@29979bc5339bf54f76a11ac36ff67701986bb0f0
with:
nodeVersion: ${{ matrix.version }}
- name: Linting
run: npm run lint -w ${{ matrix.workspace }}
- name: Unit tests
run: |
npm run test:unit:coverage -w ${{ matrix.workspace }}
npm run test:unit:types -w ${{ matrix.workspace }}
check-layer-publisher:
permissions:
contents: read
runs-on: ubuntu-latest
env:
NODE_ENV: dev
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup NodeJS
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24
cache: "npm"
- name: Setup dependencies
uses: aws-powertools/actions/.github/actions/cached-node-modules@29979bc5339bf54f76a11ac36ff67701986bb0f0
- name: Run linting
run: npm run lint -w layers
- name: Run tests
run: npm run test:unit -w layers
check-docs-snippets:
permissions:
contents: read
runs-on: ubuntu-latest
env:
NODE_ENV: dev
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup NodeJS
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24
cache: "npm"
- name: Setup dependencies
uses: aws-powertools/actions/.github/actions/cached-node-modules@29979bc5339bf54f76a11ac36ff67701986bb0f0
- name: Run linting
run: npm run lint -w examples/snippets
check-docs:
permissions:
contents: read
runs-on: ubuntu-latest
env:
NODE_ENV: dev
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup NodeJS
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24
cache: "npm"
- name: Setup dependencies
uses: aws-powertools/actions/.github/actions/cached-node-modules@29979bc5339bf54f76a11ac36ff67701986bb0f0
- name: Run linting
run: npm run lint:markdown
run-unit-tests:
uses: ./.github/workflows/reusable-run-linting-check-and-unit-tests.yml
50 changes: 27 additions & 23 deletions .github/workflows/reusable-run-linting-check-and-unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ name: Run unit tests
# and code snippets (`check-docs-snippets`) jobs run checks only for the current LTS version of Node.js.
#
# For the Powertools for AWS main features (aka `packages/*`), instead we run linting and unit tests for all the supported
# versions of Node.js.
# versions of Node.js. The list of packages is derived from the root `package.json` workspaces by
# `.github/scripts/list_workspaces.js`, so adding a workspace there is all it takes to get it tested here.
#
# Since #2938, we are in the process of improving our test suite, so we are gradually extracting the tests for each package
# from (`run-linting-check-and-unit-tests-on-utilities`) to their own job, so we can run them in parallel using the matrix
Expand All @@ -23,7 +24,8 @@ name: Run unit tests
# USAGE
#
# NOTE: meant to be called by ./.github/workflows/pr-run-linting-check-and-unit-tests.yml when a PR is opened or updated,
# or by ./.github/workflows/make-release.yml when a release is made.
# by ./.github/workflows/quality_check.yml when a PR enters the merge queue, or by ./.github/workflows/make-release.yml
# when a release is made.
#

on:
Expand All @@ -32,7 +34,23 @@ on:
permissions: {}

jobs:
resolve-workspaces:
permissions:
contents: read
runs-on: ubuntu-latest
outputs:
workspaces: ${{ steps.list.outputs.workspaces }}
steps:
- &checkout_code
name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: List non-private packages
id: list
run: |
workspaces=$(node .github/scripts/list_workspaces.js unit)
echo "workspaces=${workspaces}" >> "$GITHUB_OUTPUT"
code-quality:
needs: resolve-workspaces
permissions:
contents: read
runs-on: ubuntu-latest
Expand All @@ -41,25 +59,10 @@ jobs:
strategy:
matrix:
version: [22, 24]
workspace: [
"packages/batch",
"packages/commons",
"packages/data-masking",
"packages/event-handler",
"packages/idempotency",
"packages/jmespath",
"packages/logger",
"packages/tracer",
"packages/parser",
"packages/parameters",
"packages/validation",
"packages/metrics",
"packages/kafka"
]
workspace: ${{ fromJSON(needs.resolve-workspaces.outputs.workspaces) }}
fail-fast: false
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- *checkout_code
- name: Setup NodeJS
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
Expand All @@ -84,9 +87,7 @@ jobs:
env:
NODE_ENV: dev
steps:
- &checkout_code
name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- *checkout_code
- &setup_node
name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
Expand Down Expand Up @@ -131,11 +132,14 @@ jobs:
# require, so that adding a Node.js version or a workspace to the `code-quality` matrix
# doesn't silently change which checks are required.
#
# `resolve-workspaces` is listed so that a failure to derive the matrix, which skips
# `code-quality` entirely, is reported here rather than passing as "nothing ran".
#
# `if: always()` is required so this job still runs when a dependency fails - otherwise it
# would be skipped, and a skipped required check counts as satisfied. For the same reason
# `skipped` is treated as a failure below: none of the jobs above are conditional, so a skip
# means something upstream went wrong.
needs: [code-quality, check-layer-publisher, check-docs-snippets, check-docs]
needs: [resolve-workspaces, code-quality, check-layer-publisher, check-docs-snippets, check-docs]
if: always()
runs-on: ubuntu-latest
steps:
Expand Down
43 changes: 26 additions & 17 deletions .github/workflows/run-e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,34 @@ jobs:
- name: Deploy shared LMI capacity providers (all architectures)
run: npm run lmi:deploy -w packages/testing

# Derives both e2e matrices from the workspaces that have suites under
# `tests/e2e`, so a package gets e2e coverage as soon as it has a suite.
resolve-workspaces:
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
e2e: ${{ steps.list.outputs.e2e }}
lmi: ${{ steps.list.outputs.lmi }}
steps:
- name: Checkout Repo
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: List workspaces with e2e suites
id: list
run: |
e2e=$(node .github/scripts/list_workspaces.js e2e)
lmi=$(node .github/scripts/list_workspaces.js e2e-lmi)
echo "e2e=${e2e}" >> "$GITHUB_OUTPUT"
echo "lmi=${lmi}" >> "$GITHUB_OUTPUT"

# Runs every package's e2e suites EXCEPT the LMI ones (each package's
# `test:e2e` script excludes `tests/e2e/lmi.*`). Deliberately does NOT
# depend on the LMI setup job: these suites don't use the capacity
# provider, so they fan out immediately and its EC2-backed provisioning
# time is amortized behind them.
# provider, so they fan out as soon as the matrix is resolved and its
# EC2-backed provisioning time is amortized behind them.
run-e2e-tests-on-utils:
runs-on: ubuntu-latest
needs: resolve-workspaces
env:
NODE_ENV: dev
environment: e2e-tests
Expand All @@ -62,19 +83,7 @@ jobs:
strategy:
max-parallel: 25
matrix:
package:
[
packages/idempotency,
packages/logger,
packages/metrics,
packages/parameters,
packages/event-handler,
packages/tracer,
packages/batch,
packages/data-masking,
packages/signer,
layers,
]
package: ${{ fromJSON(needs.resolve-workspaces.outputs.e2e) }}
version: [22, 24]
arch: [x86_64, arm64]
fail-fast: false
Expand Down Expand Up @@ -110,7 +119,7 @@ jobs:
run-e2e-tests-lmi:
runs-on: ubuntu-latest
timeout-minutes: 60
needs: setup-lmi-capacity-providers
needs: [setup-lmi-capacity-providers, resolve-workspaces]
env:
NODE_ENV: dev
environment: e2e-tests
Expand All @@ -119,7 +128,7 @@ jobs:
contents: read
strategy:
matrix:
package: [packages/logger]
package: ${{ fromJSON(needs.resolve-workspaces.outputs.lmi) }}
version: [22, 24]
arch: [x86_64, arm64]
fail-fast: false
Expand Down
8 changes: 2 additions & 6 deletions MAINTAINERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,8 @@ Trusted publishers can only be configured for a package that **already exists**

### Wiring the package into CI

The PR that adds the package must also add it to:

- the `workspaces` array in the root `package.json` — this is also what gets it into the Lambda layer, since the layer bundles every non-private `@aws-lambda-powertools/*` workspace;
- `.github/workflows/reusable-run-linting-check-and-unit-tests.yml`;
- `.github/workflows/quality_check.yml`;
- `.github/workflows/run-e2e-tests.yml`, if it has end-to-end tests.
The PR that adds the package must also add it to the `workspaces` array in the root `package.json`. That is all CI needs: the unit test and e2e workflows derive their package lists from it through `.github/scripts/list_workspaces.js`, the e2e workflow picking up any workspace with suites under `tests/e2e`.
The same array is what gets the package into the Lambda layer, since the layer bundles every non-private `@aws-lambda-powertools/*` workspace.

Once that's done, the package ships like any other on the next `Make Release` run.

Expand Down
Loading
Loading