Skip to content

Commit 1c1e266

Browse files
committed
Add experimental demo fleet control plane
1 parent bd55456 commit 1c1e266

27 files changed

Lines changed: 3995 additions & 3 deletions
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: Demo Fleet CI
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- main
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- uses: ruby/setup-ruby@v1
16+
with:
17+
ruby-version: '3.4'
18+
bundler-cache: true
19+
20+
- name: Run lint
21+
run: bundle exec rubocop lib/demo_fleet.rb lib/demo_fleet script test/demo_fleet_test.rb Rakefile
22+
23+
- name: Run tests
24+
run: ruby -Ilib test/demo_fleet_test.rb
25+
26+
- name: Validate manifest
27+
run: script/demo-fleet validate --manifest test/fixtures/demo-fleet.yml
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: Demo Fleet Freshness Plan
2+
3+
on:
4+
workflow_dispatch:
5+
schedule:
6+
- cron: '17 10 * * 1'
7+
8+
permissions:
9+
contents: read
10+
11+
jobs:
12+
plan:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
- uses: ruby/setup-ruby@v1
18+
with:
19+
ruby-version: '3.4'
20+
21+
- name: Validate manifest
22+
run: script/demo-fleet validate
23+
24+
- name: Render freshness update plan
25+
run: script/demo-fleet update-plan --track freshness >> "$GITHUB_STEP_SUMMARY"
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
name: Demo Fleet Release Plan
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
react_on_rails:
7+
description: React on Rails gem version
8+
required: true
9+
react_on_rails_pro:
10+
description: React on Rails Pro gem version
11+
required: true
12+
react_on_rails_npm:
13+
description: react-on-rails npm version
14+
required: true
15+
react_on_rails_pro_npm:
16+
description: react-on-rails-pro npm version
17+
required: true
18+
react_on_rails_pro_node_renderer_npm:
19+
description: react-on-rails-pro-node-renderer npm version
20+
required: true
21+
react_on_rails_rsc_npm:
22+
description: react-on-rails-rsc npm version
23+
required: true
24+
shakapacker:
25+
description: Optional ShakaPacker gem version
26+
required: false
27+
shakapacker_npm:
28+
description: Optional ShakaPacker npm version
29+
required: false
30+
shakapacker_rspack_npm:
31+
description: Optional shakapacker-rspack npm version
32+
required: false
33+
cpflow:
34+
description: Optional CPFlow gem version
35+
required: false
36+
tier:
37+
description: Optional tier filter, such as hard_gate
38+
required: false
39+
repo:
40+
description: Optional repo id filter, such as react-on-rails-demo-marketplace-rsc
41+
required: false
42+
43+
permissions:
44+
contents: read
45+
46+
jobs:
47+
plan:
48+
runs-on: ubuntu-latest
49+
steps:
50+
- uses: actions/checkout@v4
51+
52+
- uses: ruby/setup-ruby@v1
53+
with:
54+
ruby-version: '3.4'
55+
56+
- name: Validate manifest
57+
run: script/demo-fleet validate
58+
59+
- name: Dry-run release update plan
60+
env:
61+
REACT_ON_RAILS_GEM: ${{ inputs.react_on_rails }}
62+
REACT_ON_RAILS_PRO_GEM: ${{ inputs.react_on_rails_pro }}
63+
REACT_ON_RAILS_NPM: ${{ inputs.react_on_rails_npm }}
64+
REACT_ON_RAILS_PRO_NPM: ${{ inputs.react_on_rails_pro_npm }}
65+
REACT_ON_RAILS_PRO_NODE_RENDERER_NPM: ${{ inputs.react_on_rails_pro_node_renderer_npm }}
66+
REACT_ON_RAILS_RSC_NPM: ${{ inputs.react_on_rails_rsc_npm }}
67+
SHAKAPACKER_GEM: ${{ inputs.shakapacker }}
68+
SHAKAPACKER_NPM: ${{ inputs.shakapacker_npm }}
69+
SHAKAPACKER_RSPACK_NPM: ${{ inputs.shakapacker_rspack_npm }}
70+
CPFLOW_GEM: ${{ inputs.cpflow }}
71+
TIER: ${{ inputs.tier }}
72+
REPO: ${{ inputs.repo }}
73+
run: |
74+
args=(
75+
--track release
76+
--gem "react_on_rails=$REACT_ON_RAILS_GEM"
77+
--gem "react_on_rails_pro=$REACT_ON_RAILS_PRO_GEM"
78+
--npm "react-on-rails=$REACT_ON_RAILS_NPM"
79+
--npm "react-on-rails-pro=$REACT_ON_RAILS_PRO_NPM"
80+
--npm "react-on-rails-pro-node-renderer=$REACT_ON_RAILS_PRO_NODE_RENDERER_NPM"
81+
--npm "react-on-rails-rsc=$REACT_ON_RAILS_RSC_NPM"
82+
)
83+
84+
if [[ -n "$SHAKAPACKER_GEM" ]]; then
85+
args+=(--gem "shakapacker=$SHAKAPACKER_GEM")
86+
fi
87+
88+
if [[ -n "$SHAKAPACKER_NPM" ]]; then
89+
args+=(--npm "shakapacker=$SHAKAPACKER_NPM")
90+
fi
91+
92+
if [[ -n "$SHAKAPACKER_RSPACK_NPM" ]]; then
93+
args+=(--npm "shakapacker-rspack=$SHAKAPACKER_RSPACK_NPM")
94+
fi
95+
96+
if [[ -n "$CPFLOW_GEM" ]]; then
97+
args+=(--gem "cpflow=$CPFLOW_GEM")
98+
fi
99+
100+
if [[ -n "$TIER" ]]; then
101+
args+=(--tier "$TIER")
102+
fi
103+
104+
if [[ -n "$REPO" ]]; then
105+
args+=(--repo "$REPO")
106+
fi
107+
108+
script/demo-fleet execute-plan --dry-run "${args[@]}" >> "$GITHUB_STEP_SUMMARY"

.rubocop.yml

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ AllCops:
99
TargetRubyVersion: 3.1
1010
Exclude:
1111
- 'node_modules/**/*'
12-
- 'demos/**/*' # Each demo has its own Gemfile and rubocop config
12+
- 'demos/**/*' # Each demo has its own Gemfile and rubocop config
1313
- 'packages/shakacode_demo_common/node_modules/**/*'
14-
- 'bin/conductor-exec' # Shell script, not Ruby
14+
- 'bin/conductor-exec' # Shell script, not Ruby
1515

1616
# Allow longer blocks in specs, Rakefiles, and bin scripts
1717
Metrics/BlockLength:
@@ -22,29 +22,49 @@ Metrics/BlockLength:
2222
- '**/Rakefile'
2323
- 'packages/shakacode_demo_common/lib/tasks/**/*.rake'
2424
- 'bin/*'
25+
- 'test/**/*_test.rb'
2526

2627
# Allow longer methods in demo creation (complex setup)
2728
Metrics/MethodLength:
2829
Max: 20
30+
AllowedMethods:
31+
- split_ruby_arguments
2932
Exclude:
3033
- 'lib/demo_scripts/demo_creator.rb'
3134
- 'lib/demo_scripts/demo_scaffolder.rb'
35+
- 'lib/demo_fleet/cli.rb'
36+
- 'lib/demo_fleet/update_planner.rb'
37+
- 'test/**/*_test.rb'
3238

3339
# Allow longer classes for demo creators and bin scripts (lots of setup code)
3440
Metrics/ClassLength:
3541
Max: 300
3642
Exclude:
3743
- 'bin/*'
44+
- 'test/**/*_test.rb'
3845

3946
# Allow more complex methods for demo scaffolding
4047
Metrics/AbcSize:
4148
Max: 25
49+
AllowedMethods:
50+
- split_ruby_arguments
51+
Exclude:
52+
- 'lib/demo_fleet/cli.rb'
53+
- 'lib/demo_fleet/executor.rb'
54+
- 'lib/demo_fleet/update_planner.rb'
55+
- 'test/**/*_test.rb'
4256

4357
Metrics/CyclomaticComplexity:
4458
Max: 10
59+
AllowedMethods:
60+
- split_ruby_arguments
61+
Exclude:
62+
- 'test/**/*_test.rb'
4563

4664
Metrics/PerceivedComplexity:
4765
Max: 12
66+
AllowedMethods:
67+
- split_ruby_arguments
4868

4969
# Allow more parameters for demo scaffolder (lots of options)
5070
Metrics/ParameterLists:

README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,48 @@ npm run format:check
6666

6767
See [Development Setup](./docs/CONTRIBUTING_SETUP.md) for details.
6868

69+
## Cross-Repository Demo Fleet
70+
71+
This repository also hosts the experimental runtime for planning dependency updates across the
72+
ShakaCode demo fleet. Release policy, the fleet inventory, RC prompts, and the final go/no-go
73+
decision remain authoritative in
74+
[`shakacode/react_on_rails`](https://github.com/shakacode/react_on_rails/tree/main/internal/contributor-info).
75+
The runtime reads that canonical manifest directly instead of maintaining a second fleet list.
76+
77+
```bash
78+
# Validate the current canonical manifest.
79+
script/demo-fleet validate
80+
81+
# Render a non-mutating plan for one published release.
82+
script/demo-fleet execute-plan \
83+
--track release \
84+
--dry-run \
85+
--gem react_on_rails=17.0.0.rc.10 \
86+
--gem react_on_rails_pro=17.0.0.rc.10 \
87+
--npm react-on-rails=17.0.0-rc.10 \
88+
--npm react-on-rails-pro=17.0.0-rc.10 \
89+
--npm react-on-rails-pro-node-renderer=17.0.0-rc.10 \
90+
--npm react-on-rails-rsc=19.2.1-rc.1
91+
```
92+
93+
Entries marked `verify: true` in the canonical manifest are deliberately excluded from plans. At
94+
the time this pilot was added, all canonical entries were still pending that one-time verification,
95+
so execution refuses to run until the release-policy owner confirms the metadata, including the
96+
review-app name, and clears those flags. This makes stale or placeholder metadata visible without
97+
turning it into repository mutations.
98+
99+
The release updater handles direct npm declarations in nested applications, runs installs beside
100+
each changed manifest, and permits missing targets only when a repo explicitly marks them as
101+
transitive-only. It rejects tracked, staged, or untracked output outside dependency manifests,
102+
lockfiles, Yarn Berry dependency artifacts, and the verification script.
103+
104+
Use `--manifest PATH_OR_URL` or `DEMO_FLEET_MANIFEST` to test an unmerged manifest change. Remote
105+
mutation is opt-in: `execute-plan` requires both `--execute --workspace PATH`, and it only pushes
106+
branches or opens draft PRs when `--allow-remote-prs` is also present.
107+
108+
See the [control-plane notes](./docs/demo-fleet-control-plane-design.md) for the ownership boundary,
109+
current limitations, and verification commands.
110+
69111
### Bootstrap All Demos
70112

71113
```bash

Rakefile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ require 'rubocop/rake_task'
77
RSpec::Core::RakeTask.new(:spec)
88
RuboCop::RakeTask.new
99

10+
desc 'Run demo fleet control-plane tests'
11+
task :demo_fleet_test do
12+
ruby '-Ilib', 'test/demo_fleet_test.rb'
13+
end
14+
1015
task default: %i[spec rubocop]
1116

1217
desc 'Run all tests and linting'
13-
task test: :default
18+
task test: %i[default demo_fleet_test]
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Demo Fleet Control Plane
2+
3+
## Ownership
4+
5+
The control plane has a deliberately split ownership model:
6+
7+
1. [`shakacode/react_on_rails`](https://github.com/shakacode/react_on_rails/tree/main/internal/contributor-info)
8+
owns release policy, the canonical `demo-fleet.yml`, RC prompts, tracking issues, and final
9+
release decisions.
10+
2. This repository owns reusable runtime mechanics: parsing the canonical manifest, rendering
11+
deterministic plans, applying exact versions, bounded parallel execution, and opt-in PR setup.
12+
3. Each demo repository owns its tests, build, review-app deployment, smoke behavior, and any
13+
repository-specific upgrade work.
14+
15+
The runtime defaults to the manifest on `react_on_rails/main`. A release manager can pass
16+
`--manifest PATH_OR_URL` (or set `DEMO_FLEET_MANIFEST`) to exercise an unmerged policy change. This
17+
keeps one inventory and prevents an operational snapshot here from silently drifting.
18+
19+
The canonical manifest's `verify: true` marker means an entry is still a draft. Such entries are
20+
reported by `validate` but excluded from actionable plans. `execute-plan --dry-run` reports an empty
21+
selection successfully for hosted planning, while mutating execution refuses an empty selection,
22+
and an entry cannot become actionable until `review_app.cpflow_app_name` is present unless it
23+
explicitly sets `review_app: null` to opt out. The exact-version updater fails if a requested gem is
24+
absent from the checkout's root `Gemfile`. For npm, it searches
25+
bounded repository manifests (excluding dependency, cache, and generated directories), updates every
26+
direct declaration, and only permits a missing declaration when that package is named in the repo's
27+
`transitive_only_npm_packages`. Installs run in each directory whose `package.json` changed, which
28+
covers split layouts such as HiChee's root and `client` applications.
29+
30+
Pro-only apps are a bounded exception: directly declaring the Pro gem or npm package satisfies the
31+
corresponding base React on Rails target because Pro depends on it. Other absent targets still fail
32+
closed unless the canonical manifest marks them as transitive-only.
33+
34+
## Safety Boundary
35+
36+
`update-plan` and `execute-plan --dry-run` do not mutate demo repositories. The pilot executor
37+
requires an explicit workspace for mutations. It commits local changes but does not push or open a
38+
draft PR unless `--allow-remote-prs` is supplied.
39+
40+
Mutating runs require an authenticated GitHub CLI. Fresh checkouts use its configured Git protocol
41+
so the same path works for public and private fleet repositories and later pushes retain that
42+
authenticated remote.
43+
44+
The executor starts from a clean checkout, stages dependency manifests and lockfiles at any depth,
45+
plus Yarn Berry PnP/cache artifacts, and rejects tracked, staged, or untracked changes outside that
46+
allowlist. Verification-generated drift therefore fails the lane instead of being silently included
47+
or left behind.
48+
49+
Freshness is plan-only in this pilot. The CLI rejects `--track freshness --execute` until it can
50+
resolve registry candidates and enforce the canonical minimum-age policy before changing a repo.
51+
Release-track execution remains available for explicitly supplied React on Rails package versions.
52+
53+
The executor is not yet a release gate. It does not poll hosted CI, discover CPFlow review-app
54+
URLs, run behavioral browser smoke, update the tracking issue, or make a go/no-go decision. Those
55+
steps remain in the canonical React on Rails release process and its copy/paste batch prompts.
56+
57+
## Verification
58+
59+
```bash
60+
ruby -Ilib test/demo_fleet_test.rb
61+
script/demo-fleet validate
62+
script/demo-fleet update-plan --track freshness
63+
```
64+
65+
The Ruby runtime uses only the standard library. The default manifest validation requires network
66+
access; tests pass local temporary manifests and are deterministic. When every canonical entry is
67+
still pending verification, hosted dry-runs report an intentionally empty plan and mutating execution
68+
refuses to run it.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Demo Fleet Agent Prompts
2+
3+
Copy/paste release prompts live with the release policy in the canonical
4+
[`RC Testing Plan`](https://github.com/shakacode/react_on_rails/blob/main/internal/contributor-info/rc-testing-plan.md).
5+
They are filled with exact release versions, tag and commit evidence, tracking issue, owned lanes,
6+
model routing, privacy constraints, and merge authority.
7+
8+
Do not keep a release prompt template in this repository. A generic placeholder prompt is too easy
9+
to launch with stale versions or the wrong fleet snapshot. Use this runtime to render a mechanical
10+
plan, then use the current release's filled prompt from `react_on_rails` to coordinate the work.

0 commit comments

Comments
 (0)