agentsclimarketplace

Silverstripe module ss6 upgrade

Skill jsirish/silverstripe-skills/skills/silverstripe-module-ss6-upgrade

Module-level (composer package) Silverstripe 6 upgrade workflow for silverstripe-vendormodule repos with their own CI. Use when upgrading a single module such as dynamic/silverstripe-calendar or dynamic/silverstripe-elemental-accordion to SS6, cutting a new integer branch, bumping framework constraints to ^6 and PHP to ^8.3, running the SS6 code sweep, updating gha-ci and PHPUnit config, tagging a release, and flipping the default branch. For upgrading a full project (a website root with recipes and DDEV) use the silverstripe-version-upgrade skill instead.From its SKILL.md

Install
npx -y skills add jsirish/silverstripe-skills --skill silverstripe-module-ss6-upgrade

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

3 things to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
  • runs commandsInstructs the agent to run 8 commands, including `gh repo view <vendor>/<module> --json defaultBranchRef --jq .defaultBranchRef.name` and 7 more.
  • fetches URLsInstructs the agent to fetch 1 URL, including https://packagist.org/packages/<vendor>/<module>.

SKILL.md

19.5 KB, ~5.0k tokens by cl100k_base, as published. Nobody here has run it

Silverstripe Module SS6 Upgrade

Repeatable workflow for upgrading a single Silverstripe module (a silverstripe-vendormodule composer package with its own repo, CI, and Packagist listing) from SS5 to SS6. This is the loop you run once per module across a suite of repos such as the dynamic/* open-source modules.

Scope: This skill covers the module/package upgrade loop. It is one of three related skills:

  • silverstripe-version-upgrade: full project upgrades (a website root: recipes, DDEV, data migration, deployment). Its SS6 sections are the canonical reference for namespace renames and breaking changes; this skill points there instead of restating them.
  • ss6-data-migration: DB content migration after a project upgrade.
  • This skill: the module loop (constraints, code sweep, CI, tag, default branch).

Branch-naming and default-branch conventions come from ss-branch-strategy (from jsirish/workflow-skills); this skill applies that convention, it does not redefine it.

Reference implementation for Phases 1-4 and 7: dynamic/silverstripe-calendar#112 (branch 2 to 3, tagged 3.0.0). Its branch 3 does NOT yet demonstrate Phases 3.4/6 (recipe-testing still ^3, PHPUnit 9 config, no CI run recorded); treat those phases as prescriptive, not descriptive.

Upgrade Phases (Summary)

PhaseKey Actions
1. Ordering & AssessmentPlace the module in the dependency graph; upgrade leaf modules first
2. Branch SetupNew integer branch off the current SS5 default, per ss-branch-strategy
3. Composer Constraintsphp ^8.3, framework/cms ^6, per-dependency bumps, branch-alias
4. Code SweepSS6 namespace renames, typed signatures, BuildTask, BaseElement changes
5. Rector, PHPStan, PHPCSAutomated rules first, then static analysis and style
6. CI & PHPUnit Configsilverstripe/gha-ci matrix regeneration, PHPUnit 11 config
7. PR, Tag, Default BranchMerge to the integer branch, tag, flip default, verify Packagist
8. Testbed VerificationInstall into an SS6 project, dev/build flush=1, exercise the module

Do not rationalize

Every phase gate requires evidence: actual command output, not a recollection or an inference. The shortcuts agents talk themselves into on module upgrades, and the required counter-behavior:

RationalizationRequired behavior
"The constraints look right"Run composer validate and a real composer update (testbed or CI) and paste the resolved versions. A constraint that never resolved is a guess.
"Rector handled the renames"Run the grep sweep after Rector and paste the zero-hit output. Rector does not rewrite YAML config, Injector strings, or docblocks.
"CI is green, so the module works"CI installs the module against a bare scaffold. Install it into an SS6 testbed, run dev/build flush=1, and paste the output. Phase 8 is not optional.
"This module has no BuildTasks / elements / validate() overrides"Paste the grep output proving it: `rg "extends BuildTask
"I tagged it, so Packagist has it"Paste the version list from https://packagist.org/packages/<vendor>/<module> (or its API). Webhooks fail silently.
"The default branch is flipped"Paste gh api repos/<vendor>/<module> --jq .default_branch.
"Tests probably still pass on PHPUnit 11"Run vendor/bin/phpunit and paste the summary line. PHPUnit 10+ changed config schema and assertions; no run, no claim.

Phase 1: Ordering & Assessment

1.1 The dependency ordering problem

Modules form a dependency graph, and a module cannot resolve silverstripe/framework ^6 while one of its own requirements still caps at SS5. Upgrade leaf modules first, then modules that depend on them, then recipes last:

  1. Leaf modules (no dependencies on other in-house modules): e.g. dynamic/silverstripe-elemental-baseobject, dynamic/flexslider, dynamic/silverstripe-calendar.
  2. Mid-tier modules (depend on leaf modules): e.g. the dynamic/silverstripe-elemental-* content blocks, which require silverstripe-elemental-baseobject.
  3. Recipes (aggregate everything): e.g. dynamic/recipe-silverstripe-base-site, dynamic/recipe-silverstripe-essentials-website. A recipe is upgraded by bumping every member constraint, so it goes last.

While a dependency is upgraded on its integer branch but not yet tagged, depend on it with a dev constraint (^6@dev); minimum-stability: dev + prefer-stable: true in the module's composer.json makes this resolvable. Replace with the tagged constraint once the dependency releases.

1.2 Assess the module

gh repo view <vendor>/<module> --json defaultBranchRef --jq .defaultBranchRef.name
gh api repos/<vendor>/<module>/branches --jq '.[].name'

# What does it require, and are SS6 releases available for each?
cat composer.json | python3 -c "import json,sys; print(json.load(sys.stdin)['require'])"

For each third-party requirement, confirm an SS6-compatible release exists on Packagist. If one does not, follow the fork-and-upstream workflow in ss-branch-strategy or find a maintained replacement (see 3.3).

Evidence gate (Phase 1): a written ordering position ("leaf", "mid-tier after X", "recipe") and, for every requirement, either the SS6-compatible version number or the replacement/fork decision. Paste the requirement list with the target version beside each entry.

Phase 2: Branch Setup

Integer branch naming per ss-branch-strategy: the new branch integer is the module's next major version (or the recipe version, if the repo versions against a shared recipe). Example: dynamic/silverstripe-calendar was on 2 (SS5), so SS6 work went to 3.

# Create the new integer branch from the current SS5 default
git fetch origin
git checkout -b 3 origin/2
git push -u origin 3

# Do the work on a feature branch targeting the new integer branch
git checkout -b feature/ss6-upgrade

Do not flip the default branch yet; that happens after merge and tag (Phase 7). The old integer branch (2) stays for SS5 backports.

Phase 3: Composer Constraints

3.1 Core bumps

Every module gets, at minimum:

{
    "require": {
        "php": "^8.3",
        "silverstripe/cms": "^6.0"
    },
    "extra": {
        "branch-alias": {
            "dev-3": "3.x-dev"
        }
    }
}
  • Modules that only need the framework require silverstripe/framework: ^6 instead of silverstripe/cms.
  • The branch-alias key must match the new integer branch (dev-3 for branch 3) and the new major (3.x-dev). Without it, ^3@dev constraints in downstream consumers will not resolve to the branch.
  • Elemental content blocks bump dnadesign/silverstripe-elemental to ^6.

3.2 Common third-party bumps

Observed across the dynamic/* SS6 sweep (verify each on Packagist rather than trusting this table blindly):

PackageSS6 constraint
silverstripe/lumberjack^4
symbiote/silverstripe-gridfieldextensions^5
symbiote/silverstripe-queuedjobs^6
unclecheese/display-logic^4
silverstripe/linkfield^5 (the 4.x line is CMS 5 only and cannot resolve against cms ^6)
silverstripe/vendor-plugin (if pinned)^3

3.3 Replacements for packages with no SS6 release

PackageReplacement
ryanpotter/silverstripe-color-fieldtractorcow/silverstripe-colorpicker (no tagged SS6 release yet; require "dev-master as 5.0" inline alias)
nathancox/embedfieldfromholdio/silverstripe-embedfield ^5.1
sheadawson/silverstripe-linkablesilverstripe/linkfield (^5 on SS6; run the linkable data migration on SS5 with linkfield ^4 BEFORE the SS6 bump)

The project-level removal list (this table is a module-focused superset) lives in silverstripe-version-upgrade references/version-map-ss6.md.

3.4 Dev dependencies

"require-dev": {
    "cambis/silverstan": "^2.1",
    "phpstan/extension-installer": "^1.3",
    "silverstripe/recipe-testing": "^4",
    "squizlabs/php_codesniffer": "^3.7"
}

[!WARNING] A stale silverstripe/recipe-testing: ^3 blocks resolution: recipe-testing 3.x requires silverstripe/framework ^5. SS6 needs ^4, which also moves the test suite to PHPUnit ^11.3 (see Phase 6.2).

Also drop unused requirements while you are in the file; the reference implementation removed dft/silverstripe-frontend-multiselectfield after confirming zero usages by grep.

Evidence gate (Phase 3): paste the output of composer validate plus a successful dependency resolution (a composer update in a throwaway checkout, the Phase 8 testbed, or the first green CI install). The edited JSON alone does not clear this gate.

Phase 4: Code Sweep

The canonical SS6 rename and breaking-change tables live in silverstripe-version-upgrade (Phase 4 and the "SS6 Breaking Changes" section) and references/version-map-ss6.md. Do not restate them; run them against the module's src/ and tests/ instead of app/src/:

# Namespace renames (each old FQCN is a runtime fatal in SS6)
rg "SilverStripe\\\\View\\\\ViewableData" src/ tests/
rg "SilverStripe\\\\View\\\\ArrayData" src/ tests/
rg "SilverStripe\\\\ORM\\\\ArrayList" src/ tests/
rg "SilverStripe\\\\ORM\\\\ValidationResult" src/ tests/
rg "SilverStripe\\\\ORM\\\\ValidationException" src/ tests/

Module-specific checklist (the items that recur across dynamic/* modules):

  1. ArrayList moved to SilverStripe\Model\List\ArrayList; ArrayData to SilverStripe\Model\ArrayData; ViewableData is now SilverStripe\Model\ModelData.
  2. ValidationResult moved to SilverStripe\Core\Validation\ValidationResult, and every validate() override must declare the return type: public function validate(): ValidationResult.
  3. BuildTask signature: run($request) becomes execute(InputInterface $input, PolyOutput $output): int returning Command::SUCCESS, with protected static string $commandName. The full old-vs-new table and a task template are in silverstripe-version-upgrade Phase 4 and its references/data-migration-tasks.md.
  4. BaseElement::getType() / getDescription() are replaced by config: use private static string $class_description (and keep $singular_name / $plural_name config for the type label). Applies to every elemental content block module.
  5. ModelData subclass overrides of __get, __set, __isset, hasField, getField, setField, etc. must add the typed parameters and return types the SS6 parent declares, or PHP throws declaration-compatibility fatals.
  6. forTemplate() must declare : string and never return false; return '' instead.
  7. CMSPageAddController was removed. Any extension bound to it (commonly via an updatePageOptions(FieldList $fields) hook) is dead code with no error - the extended class simply no longer exists, so the hook never fires. Rebind to CMSMainAddForm::updateFields and CMSMain::updateDoAdd. Full detail (the RecordType field rename, how to read submitted values, the live-CMS verification note) is in silverstripe-version-upgrade's "SS6 Breaking Changes" section - this is the canonical source for SS6 breaking changes per Phase 4 above; do not restate it here.
# Prove presence or absence of each pattern
rg "extends BuildTask" src/
rg "extends BaseElement" src/
rg "function getType\(|function getDescription\(" src/
rg "public function validate\(\)(?!\s*:)" src/ --pcre2
rg "function forTemplate\(\)(?!\s*:)" src/ --pcre2
rg "updatePageOptions" src/

Evidence gate (Phase 4): paste the grep sweep output showing zero remaining hits for every old FQCN and untyped signature, run after the Rector pass in Phase 5 (Rector misses YAML, string class references, and docblocks).

Phase 5: Rector, PHPStan, PHPCS

Tooling, configs, and the SS6 Rector level set (SilverstripeLevelSetList::UP_TO_SS_6_0) are documented in silverstripe-version-upgrade references/code-quality.md. For a module the paths are src/ and tests/, not app/src/:

vendor/bin/rector --dry-run    # review, then apply
vendor/bin/rector
vendor/bin/phpstan analyse src/ tests/
vendor/bin/phpcs src/ tests/

phpstan.neon.dist in a module scans the module's own src/ (there is no vendor-path problem here; that warning in code-quality.md applies to project roots).

Evidence gate (Phase 5): paste the summary line of each tool run (Rector applied-rules count, PHPStan error count, PHPCS error count). Then re-run the Phase 4 greps and paste the zero-hit output.

Phase 6: CI & PHPUnit Config

6.1 gha-ci matrix

Modules use the shared Silverstripe CI workflow:

# .github/workflows/ci.yml
name: CI
on:
  push:
  pull_request:
  workflow_dispatch:
permissions: {}
jobs:
  ci:
    name: CI
    permissions:
      pull-requests: read
      contents: read
      actions: write
    uses: silverstripe/gha-ci/.github/workflows/ci.yml@v1
    with:
      phpcoverage: false
      js: false

gha-ci derives the PHP and database matrix from composer.json constraints (via silverstripe/gha-generate-matrix), so once php ^8.3 and cms ^6 land on the branch, the next run generates the SS6 matrix automatically. Manual matrix edits are only needed for non-standard setups. Verify:

  • The workflow references @v1 (the floating major tag), not a pinned old minor that predates SS6 support.
  • The workflow triggers on push and pull_request so the new integer branch gets runs.
  • No branch filters still name the old branch only.

6.2 PHPUnit config for SS6

silverstripe/recipe-testing ^4 moves modules to PHPUnit ^11.3. Update phpunit.xml.dist: the schema version bumps, and PHPUnit 10+ moved coverage includes from <coverage> into <source>:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         bootstrap="vendor/silverstripe/cms/tests/bootstrap.php"
         colors="true"
         xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.3/phpunit.xsd">
    <testsuites>
        <testsuite name="default">
            <directory>tests/</directory>
        </testsuite>
    </testsuites>
    <source>
        <include>
            <directory suffix=".php">src/</directory>
        </include>
    </source>
</phpunit>

The bootstrap path (vendor/silverstripe/cms/tests/bootstrap.php, or vendor/silverstripe/framework/tests/bootstrap.php for framework-only modules) is unchanged.

Evidence gate (Phase 6): a green CI run on the PR, with the matrix jobs visibly running PHP 8.3+ against CMS 6. Paste the run URL and the job list (gh run view <id> --json jobs --jq '.jobs[].name').

Phase 7: PR, Tag, Default Branch, Packagist

  1. Open the PR from feature/ss6-upgrade into the new integer branch (3), referencing the module's SS6 issue.

  2. Merge once CI is green and review passes.

  3. Tag the release on the integer branch:

    git checkout 3 && git pull
    git tag 3.0.0
    git push origin 3.0.0
    

    During a coordinated multi-module sweep it is normal to hold tagging until downstream consumers have verified against ^3@dev; tag when the module is proven in the testbed (Phase 8).

  4. Flip the default branch to the new integer branch, keeping the old one for backports:

    gh api -X PATCH repos/<vendor>/<module> -f default_branch=3
    
  5. Verify Packagist picked up the tag and the new branch alias (webhook-driven, but webhooks fail silently):

    curl -s https://repo.packagist.org/p2/<vendor>/<module>.json \
      | python3 -c "import json,sys; print([v['version'] for v in list(json.load(sys.stdin)['packages'].values())[0]][:5])"
    

Evidence gate (Phase 7): paste the merged PR URL, the tag from git tag -l '3.*', the default branch from gh api repos/<vendor>/<module> --jq .default_branch, and the Packagist version list showing the new tag.

Phase 8: Testbed Verification

CI proves the module installs and its unit tests pass against a scaffold. It does not prove the module works inside a real SS6 site. Install it into an SS6 testbed project (any DDEV project already on SS6, or a scratch silverstripe/installer ^6 checkout):

# In the testbed project
ddev composer require <vendor>/<module>:^3@dev   # or ^3.0 once tagged
ddev sake dev/build flush=1

Then exercise the module:

  • dev/build output shows the module's tables and fields, with no errors and no obsolete-type warnings introduced by the module.
  • The CMS section or elemental block the module provides loads and saves.
  • For modules with BuildTasks: ddev sake tasks lists them (proves the commandName conversion worked), and each task runs to SUCCESS.
  • The front-end template output renders (no forTemplate() fatals).

Evidence gate (Phase 8): paste the dev/build output and, for each BuildTask, the ddev sake tasks listing plus one task run. "CI was green" does not clear this gate.

Per-module checklist

  • Ordering position established; all dependencies have an SS6 path (Phase 1)
  • Integer branch created off the SS5 default; feature branch targets it (Phase 2)
  • php ^8.3, framework/cms ^6, third-party bumps, branch-alias (Phase 3)
  • Namespace renames, typed signatures, BuildTask, $class_description, forTemplate(): string (Phase 4)
  • Rector SS6 level set applied; PHPStan and PHPCS clean; greps re-run (Phase 5)
  • gha-ci on @v1, matrix regenerated; PHPUnit 11 config (Phase 6)
  • PR merged, tagged, default branch flipped, Packagist verified (Phase 7)
  • Installed in an SS6 testbed, dev/build flush=1 clean, functionality exercised (Phase 8)

Related skills

  • silverstripe-version-upgrade: the project-level SS6 upgrade this skill feeds into; canonical home of the SS6 rename tables, breaking changes, and version maps.
  • ss6-data-migration: DB content migration after the project upgrade.
  • ss-branch-strategy (from jsirish/workflow-skills): the integer-branch, default-branch, and fork-and-upstream conventions applied in Phases 2 and 7.

What ships with it: 1 file

48 B alongside SKILL.md

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.