-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Problem
Currently, runtime_ci_tooling generates CI/CD workflow files from Mustache skeleton templates driven by .runtime_ci/config.json. While the system is highly configurable for common patterns (Dart SDK version, feature flags like proto, lfs, format_check, secrets, sub-packages, multi-platform matrices), there is no structured way for consumer repos to inject custom CI/CD steps into the generated workflows.
Some repos — such as runtime_isomorphic_ipc — have additional CI/CD requirements that fall outside the standard template (e.g., starting background services, setting up custom tool chains, running integration test suites, or deploying artifacts). Today, these repos must either:
- Manually edit the generated
ci.yamlafter everyupdate --workflows— fragile and easy to lose on the next regeneration. - Rely on the
BEGIN USER/END USERhooks — which exist inci.skeleton.yamlbut are limited to only two fixed injection points (pre-testandpost-test), are not configurable fromconfig.json, and have no documentation or validation.
Prior Art: Existing BEGIN USER / END USER Hooks
The skeleton template (templates/github/workflows/ci.skeleton.yaml) already has four hook points (two per platform mode):
# --- BEGIN USER: pre-test ---
# --- END USER: pre-test ---
# (test step)
# --- BEGIN USER: post-test ---
# --- END USER: post-test ---The WorkflowGenerator._preserveUserSections() method in lib/src/cli/utils/workflow_generator.dart parses these from the existing deployed ci.yaml and re-injects them after Mustache rendering:
final sectionPattern = RegExp(
r'# --- BEGIN USER: (\S+) ---\n(.*?)# --- END USER: \1 ---',
dotAll: true,
);This is a good foundation but has several limitations:
- Only two hook names (
pre-test,post-test) — no hooks for pre-analyze, post-analyze, pre-checkout, post-setup, etc. - No config-driven injection — users must manually edit the generated YAML to fill the hooks; there is no way to define custom steps in
config.jsonor a separate file. - No documentation — the hooks are not mentioned in README or config.json comments.
- No validation — invalid YAML inside user sections silently produces broken workflows.
- No composability — cannot combine multiple custom step sources (e.g., a shared org-level snippet + repo-specific snippet).
Proposed Solution
1. Expand hook points in the skeleton template
Add more injection points to ci.skeleton.yaml:
# --- BEGIN USER: post-checkout ---
# --- END USER: post-checkout ---
# (setup-dart, cache, pub get steps)
# --- BEGIN USER: post-setup ---
# --- END USER: post-setup ---
# (analyze step)
# --- BEGIN USER: post-analyze ---
# --- END USER: post-analyze ---
# --- BEGIN USER: pre-test ---
# --- END USER: pre-test ---
# (test step)
# --- BEGIN USER: post-test ---
# --- END USER: post-test ---
# --- BEGIN USER: finalize ---
# --- END USER: finalize ---2. Config-driven step injection via config.json
Add a ci.custom_steps section to config.json that maps hook names to inline step definitions:
{
"ci": {
"custom_steps": {
"post-checkout": [
{
"name": "Start IPC broker",
"run": "docker compose -f test/docker-compose.yml up -d"
}
],
"post-test": [
{
"name": "Stop IPC broker",
"run": "docker compose -f test/docker-compose.yml down"
},
{
"name": "Upload coverage",
"uses": "codecov/codecov-action@v4",
"with": {
"files": "coverage/lcov.info"
}
}
]
}
}
}WorkflowGenerator._buildContext() would serialize these into valid YAML step blocks and inject them at the corresponding BEGIN USER / END USER markers — without requiring the user to manually edit the generated file.
3. File-based step injection via .runtime_ci/custom_steps/
For more complex steps, support loading from separate YAML files:
.runtime_ci/
config.json
custom_steps/
pre-test.yaml
post-test.yaml
Each file contains a list of GitHub Actions step objects:
# .runtime_ci/custom_steps/pre-test.yaml
- name: Start integration environment
run: |
./scripts/setup-integration-env.sh
sleep 5
- name: Verify environment health
run: curl --fail http://localhost:8080/healthThe generator would:
- Check
ci.custom_stepsin config.json (inline definitions). - Check
.runtime_ci/custom_steps/<hook-name>.yamlfiles (file-based definitions). - Merge both (config.json steps first, file-based steps second).
- Inject into the corresponding hook point.
- Continue to preserve any manually-edited
BEGIN USER/END USERcontent as a final fallback.
4. Validation
- Validate that hook names in
ci.custom_stepsmatch known hook points. - Validate that step definitions have at least
nameand eitherrunoruses. - Validate resulting YAML is syntactically correct (catch indentation issues early).
- Add a
manage_cicd validatesubcommand or--dry-runflag to preview generated output.
5. Update init and update commands
init: When generating a fresh config.json, include an emptycustom_steps: {}key with a comment explaining available hooks.update --workflows: Read custom steps from config and/or files, inject them, then preserve any remaining manual user sections as before.- New flag:
update --workflows --dry-runto preview the generated ci.yaml without writing it.
Example: runtime_isomorphic_ipc
{
"ci": {
"dart_sdk": "3.9.2",
"features": { "format_check": true },
"custom_steps": {
"pre-test": [
{
"name": "Start IPC test broker",
"run": "dart run tool/start_broker.dart &"
},
{
"name": "Wait for broker",
"run": "sleep 3"
}
],
"post-test": [
{
"name": "Collect broker logs",
"if": "always()",
"run": "cat /tmp/broker.log || true"
}
]
}
}
}Acceptance Criteria
- Additional hook points added to
ci.skeleton.yaml(post-checkout,post-setup,post-analyze,finalize) -
ci.custom_stepsconfig key supported inconfig.jsonwith inline step definitions -
.runtime_ci/custom_steps/<hook>.yamlfile-based injection supported - Config-driven steps and file-based steps merge correctly (config first, files second)
- Manual
BEGIN USER/END USERedits continue to work as a fallback -
WorkflowGenerator.validate()validates custom step definitions -
update --workflows --dry-runflag for previewing output -
initscaffolds emptycustom_stepswith documentation - Unit tests for step injection, merging, and validation
- README/docs updated with hook point reference and examples
Benefits
- Consumer repos can define custom CI/CD logic declaratively without touching generated files
- Regeneration via
update --workflowsis safe — custom steps survive intact - Three layers of customization (config inline, file-based, manual edit) for different complexity levels
- Existing
BEGIN USER/END USERbehavior is preserved as backward-compatible fallback - Validation catches errors before they hit GitHub Actions