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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
42 changes: 34 additions & 8 deletions .github/ci/packages.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"_comment": "One entry per folder CI builds. 'upstream' names the folders whose change can break this one; the change detector runs a folder when it or anything upstream of it changes. 'dotnet' configures the .NET job (absent for a folder with no .NET build; 'release-pack' names the one project a release packs instead of the solution; 'state-machine' installs Trax.Api.StateMachine's npm packages first, for tests that run its generators under node; 'playwright' installs chromium, and the npm packages of Trax.Api.StateMachine and the Recovery page, which the browser tests build, Trax.Docs/adr/0049; 'api-dashboard' installs the npm packages of Trax.Api.Dashboard's React app before the build, which compiles it into the package); 'adr' configures the ADR guard over that folder's corpus. 'suites' are test projects carved out of a folder into a job of their own: one runs when anything in its 'upstream' (closed over like a folder's) or under one of its 'paths' changed, its folder's own job leaves it out with that row's 'test-args' filter, and its 'dotnet' takes the same keys as a folder's, with 'chromium' to install the browser, 'postgres-user'/'postgres-password' and 'rabbitmq-user'/'rabbitmq-password' for services that log in as someone other than trax, and 'web' for an npm folder (relative to the folder) whose tests and build run after the .NET tests.",
"_comment": "One entry per folder CI builds. 'upstream' names the folders whose change can break this one; the change detector runs a folder when it or anything upstream of it changes. 'dotnet' configures the .NET job (absent for a folder with no .NET build; 'release-pack' names the one project a release packs instead of the solution; 'state-machine' installs the npm packages of Trax.StateMachine/ts, the TypeScript engine, first, for tests that run its generators under node; 'playwright' installs chromium, and the npm packages of Trax.StateMachine/ts and the Recovery page, which the browser tests build, Trax.Docs/adr/0049; 'api-dashboard' installs the npm packages of Trax.Api.Dashboard's React app before the build, which compiles it into the package); 'adr' configures the ADR guard over that folder's corpus. 'suites' are jobs of their own beside the folders' jobs: one runs when anything in its 'upstream' (closed over like a folder's) or under one of its 'paths' changed. A suite with 'dotnet' restores, builds and tests its 'project' (a test project or a solution) from its 'folder', and its 'dotnet' takes the same keys as a folder's, with 'chromium' to install the browser, 'postgres-user'/'postgres-password' and 'rabbitmq-user'/'rabbitmq-password' for services that log in as someone other than trax, 'web' for an npm folder (relative to the folder) whose tests and build run after the .NET tests, 'checks' for a suite that is a folder's whole solution and so gets the folder job's formatting, lock and deprecation checks (and the Dependabot lockfile workflow's regeneration), 'pack' to pack it with package validation (the release packs it too), and 'conformance' to run the conformance kit through the TypeScript engine after the tests. A test project carved out of a folder's own job is left out of that job with the row's 'test-args' filter. A suite with no 'dotnet' is an npm folder with a job of its own in ci.yml (state-machine-ts). Trax.StateMachine has no folder job: its C#, TypeScript and cross-engine checks are three suites, so a change to one language does not rebuild the other.",
"packages": [
{
"folder": "Trax.Core",
Expand All @@ -9,13 +9,18 @@
},
{
"folder": "Trax.Effect",
"upstream": ["Trax.Core", "Trax.Api.StateMachine"],
"dotnet": { "solution": "Trax.Effect.slnx", "state-machine": true, "pack": true, "coverage": true, "postgres": "trax_data_tests", "rabbitmq": true, "databases": [], "timeout": 15, "test-args": "" },
"upstream": ["Trax.Core"],
"dotnet": { "solution": "Trax.Effect.slnx", "pack": true, "coverage": true, "postgres": "trax_data_tests", "rabbitmq": true, "databases": [], "timeout": 15, "test-args": "" },
"adr": { "adr-root": "docs/adr", "known-areas": "migrations,providers,data-model,testing,platform,auth", "census-root": "tests/Trax.Effect.Tests.Meta" }
},
{
"folder": "Trax.Mediator",
"folder": "Trax.StateMachine",
"upstream": ["Trax.Effect"],
"adr": { "adr-root": "docs/adr", "test-roots": "dotnet/tests", "known-areas": "platform,data-model,testing,auth", "census-root": "dotnet/tests/Trax.StateMachine.Tests.Meta" }
},
{
"folder": "Trax.Mediator",
"upstream": ["Trax.StateMachine"],
"dotnet": { "solution": "Trax.Mediator.slnx", "pack": true, "coverage": true, "postgres": "trax_data_tests", "rabbitmq": false, "databases": [], "timeout": 15, "test-args": "" },
"adr": { "adr-root": "docs/adr", "known-areas": "auth,platform,testing", "census-root": "tests/Trax.Mediator.Tests.Meta" }
},
Expand All @@ -33,7 +38,7 @@
},
{
"folder": "Trax.Cli",
"upstream": ["Trax.Scheduler", "Trax.Api.StateMachine"],
"upstream": ["Trax.Scheduler", "Trax.StateMachine"],
"dotnet": { "solution": "Trax.Cli.slnx", "state-machine": true, "pack": true, "coverage": true, "postgres": "", "rabbitmq": false, "databases": [], "timeout": 15, "test-args": "" },
"adr": { "adr-root": "docs/adr", "known-areas": "tooling,platform,testing", "census-root": "tests/Trax.Cli.Tests.Meta" }
},
Expand All @@ -47,10 +52,9 @@
"folder": "Trax.Docs",
"upstream": ["Trax.Api", "Trax.Cli", "Trax.Samples"],
"dotnet": { "solution": "Trax.Docs.slnx", "pack": false, "coverage": false, "postgres": "", "rabbitmq": false, "databases": [], "timeout": 15, "test-args": "" },
"adr": { "adr-root": "adr", "known-areas": "testing,platform,packaging,ci,naming,data-model,graphql,docs,migrations,providers", "known-repos": "core,effect,mediator,scheduler,dashboard,api,cli,samples,docs,website", "require-repos-key": "true", "census-root": "tests/Trax.Docs.Tests" }
"adr": { "adr-root": "adr", "known-areas": "testing,platform,packaging,ci,naming,data-model,graphql,docs,migrations,providers", "known-repos": "core,effect,statemachine,mediator,scheduler,dashboard,api,cli,samples,docs,website", "require-repos-key": "true", "census-root": "tests/Trax.Docs.Tests" }
},
{ "folder": "Trax.Website", "upstream": ["Trax.Docs", "Trax.Api"] },
{ "folder": "Trax.Api.StateMachine", "upstream": [] }
{ "folder": "Trax.Website", "upstream": ["Trax.Docs", "Trax.Api"] }
],
"suites": [
{
Expand All @@ -68,6 +72,28 @@
"upstream": ["Trax.Api"],
"paths": ["Trax.Samples/book/"],
"dotnet": { "api-dashboard": true, "web": "book/Shopfront/web", "postgres": "shopfront", "postgres-user": "postgres", "postgres-password": "shopfront", "rabbitmq": true, "rabbitmq-user": "guest", "rabbitmq-password": "guest", "databases": [], "timeout": 15, "test-args": "" }
},
{
"name": "state-machine-dotnet",
"folder": "Trax.StateMachine/dotnet",
"project": "Trax.StateMachine.slnx",
"upstream": ["Trax.Effect"],
"paths": ["Trax.StateMachine/dotnet/", "Trax.StateMachine/conformance/"],
"dotnet": { "checks": true, "pack": true, "coverage": true, "postgres": "trax_data_tests", "rabbitmq": false, "databases": [], "timeout": 15, "test-args": "--filter TestCategory!=CrossEngine" }
},
{
"name": "state-machine-ts",
"folder": "Trax.StateMachine/ts",
"upstream": [],
"paths": ["Trax.StateMachine/ts/", "Trax.StateMachine/conformance/"]
},
{
"name": "state-machine-conformance",
"folder": "Trax.StateMachine/dotnet",
"project": "tests/Trax.StateMachine.Tests/Trax.StateMachine.Tests.csproj",
"upstream": ["Trax.Effect"],
"paths": ["Trax.StateMachine/"],
"dotnet": { "state-machine": true, "conformance": true, "postgres": "", "rabbitmq": false, "databases": [], "timeout": 15, "test-args": "--filter TestCategory=CrossEngine" }
}
]
}
2 changes: 1 addition & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ updates:
directories:
# The release tooling.
- "/"
- "/Trax.Api.StateMachine"
- "/Trax.StateMachine/ts"
# The operations dashboard that Trax.Api.Dashboard ships.
- "/Trax.Api/src/Trax.Api.Dashboard/ClientApp"
- "/Trax.Core/plugins/vscode/trax-hints"
Expand Down
15 changes: 13 additions & 2 deletions .github/release/release-workflow.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,12 @@ describe('build job', () => {
it('packs the engine at the released version, without re-running the tests CI ran', () => {
const pack = step(build, 'Pack the TypeScript engine');

assert.equal(pack['working-directory'], 'Trax.Api.StateMachine');
assert.equal(pack['working-directory'], 'Trax.StateMachine/ts');
assert.equal(pack.env.PKG_VERSION, '${{ needs.release.outputs.version }}');
assert.match(pack.run, /npm ci --ignore-scripts/);
assert.doesNotMatch(pack.run, /npm test|npm run typecheck/);
assert.match(pack.run, /npm version "\$PKG_VERSION" --no-git-tag-version/);
assert.match(pack.run, /npm pack --pack-destination \.\.\/npm/);
assert.match(pack.run, /npm pack --pack-destination \.\.\/\.\.\/npm/);
});

it('versions the engine before the NuGet packs, which bundle it into Trax.Cli', () => {
Expand All @@ -99,6 +99,17 @@ describe('build job', () => {
assert.ok(indexOf(build, 'Install the API dashboard (locked, no install scripts)') < indexOf(build, 'Pack every package'));
});

it('packs every folder solution and every suite marked pack, the C# state-machine engine among them', () => {
const run = step(build, 'Pack every package').run;
const program = run.match(/jq -r '([\s\S]*?)' \\/)[1];
const packages = join(dirname(workflowPath), '..', 'ci', 'packages.json');
const list = execFileSync('jq', ['-r', program, packages], { encoding: 'utf8' }).trim().split('\n');

assert.ok(list.includes('Trax.Effect/Trax.Effect.slnx'));
assert.ok(list.includes('Trax.StateMachine/dotnet/Trax.StateMachine.slnx'));
assert.equal(new Set(list).size, list.length);
});

it('hands publish the packages and their digests', () => {
assert.equal(build.outputs.digests, '${{ steps.digests.outputs.digests }}');
assert.equal(build.outputs['npm-digests'], '${{ steps.npm-digests.outputs.digests }}');
Expand Down
25 changes: 18 additions & 7 deletions .github/scripts/ci-changes.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
# here). A change outside every folder, such as a workflow, global.json or this
# script, runs everything, and so does any event other than a pull request.
#
# A suite (packages.json "suites") is a test project with a job of its own. It runs when a
# folder it lists upstream is selected (so anything upstream of that folder too), or when a
# changed file is under one of its paths; on any event but a pull request it always runs.
# A suite (packages.json "suites") is a test project, a solution or an npm folder with a job of
# its own. It runs when a folder it lists upstream is selected (so anything upstream of that
# folder too), or when a changed file is under one of its paths; on any event but a pull
# request it always runs.
#
# Usage, from the repository root: ci-changes.sh <event-name>
# On a pull request the checkout must be the merge commit with its first parent
Expand Down Expand Up @@ -44,19 +45,29 @@ dotnet=$(jq -c --argjson selected "$selected" \
adr=$(jq -c --argjson selected "$selected" \
'[.packages[] | select(.adr and (.folder as $f | $selected | index($f))) | {folder} + .adr]' "$config")

suites=$(jq -c --argjson selected "$selected" --argjson files "$changed" --arg event "$event" '
selected_suites=$(jq -c --argjson selected "$selected" --argjson files "$changed" --arg event "$event" '
[(.suites // [])[]
| select($event != "pull_request"
or any(.upstream[]; . as $u | $selected | index($u))
or any(.paths[]; . as $p | $files | any(startswith($p))))
| {name, folder, project} + .dotnet]' "$config")
or any(.paths[]; . as $p | $files | any(startswith($p))))]' "$config")
# A suite with a "dotnet" key runs in the suite job; one without (an npm folder) has a job of
# its own, which reads suite-names.
suites=$(jq -c '[.[] | select(.dotnet) | {name, folder, project} + .dotnet]' <<<"$selected_suites")
suite_names=$(jq -c '[.[].name]' <<<"$selected_suites")
# The coverage each .NET job uploads: a folder's under its folder name, a suite's under its own
# name, both flagged with the top-level folder they measure.
coverage=$(jq -c --argjson dotnet "$dotnet" --argjson suites "$suites" -n '
[($dotnet[] | select(.coverage) | {artifact: .folder, flag: .folder}),
($suites[] | select(.coverage) | {artifact: .name, flag: (.folder | split("/")[0])})]')

echo "Running: $(jq -r 'join(", ")' <<<"$selected")"
echo "Suites: $(jq -r 'map(.name) | join(", ")' <<<"$suites")"
echo "Suites: $(jq -r 'join(", ")' <<<"$suite_names")"

{
echo "folders=$selected"
echo "dotnet=$dotnet"
echo "adr=$adr"
echo "suites=$suites"
echo "suite-names=$suite_names"
echo "coverage=$coverage"
} >>"${GITHUB_OUTPUT:-/dev/stdout}"
Loading
Loading