diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..c36f76b --- /dev/null +++ b/.gitattributes @@ -0,0 +1,15 @@ +# Skeleton-only files: plugin-elementary's own repository settings, which must +# not reach a plugin created from the skeleton. `composer create-project` +# installs the Packagist dist archive, and export-ignore leaves these out of +# it. For a git clone, `npm run init` removes the same files (cleanup.targets +# in bin/scaffold.config.js); keep the two lists in step. +/.gitattributes export-ignore +/.github/CODEOWNERS export-ignore +/.github/ISSUE_TEMPLATE/bug.yml export-ignore +/.github/ISSUE_TEMPLATE/config.yml export-ignore +/.github/ISSUE_TEMPLATE/feature.yml export-ignore +/.github/labels.yml export-ignore +/SECURITY.md export-ignore +/.github/release-starter export-ignore +/.github/workflows/release-starter.yml export-ignore +/tests/js/starter-files.test.js export-ignore diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md deleted file mode 100644 index 4bc8afb..0000000 --- a/.github/ISSUE_TEMPLATE/bug.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -name: Bug Report -about: There is a bug in functionality that needs to be fixed. -title: '' -labels: '' -assignees: '' - ---- - -## Description - - - -## Steps to Reproduce - - - -## Screenshots - - - -## Additional Information - - diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index c55b6e9..0cd4b09 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -1,50 +1,128 @@ -name: Bug Report Form -description: If something isn't working as expected +name: Bug report +description: Something in the plugin-elementary skeleton is broken or behaves unexpectedly. +type: Bug labels: - - bug + - "Type: Bug" body: - - type: textarea + - type: markdown + attributes: + value: >- + Thanks for reporting a bug. Please search + [existing issues](https://github.com/rtCamp/plugin-elementary/issues?q=is%3Aissue) + first. Bugs in the framework belong in + [wp-primitives](https://github.com/rtCamp/wp-primitives/issues), and + bugs in `npm run init` or `npx wp-tooling` in + [wp-tooling](https://github.com/rtCamp/wp-tooling/issues). + - type: markdown + attributes: + value: >- + **Found a security issue?** Do not open a public issue. Report it + privately through + [GitHub's vulnerability reporting](https://github.com/rtCamp/plugin-elementary/security/advisories/new), + as [SECURITY.md](https://github.com/rtCamp/plugin-elementary/blob/main/SECURITY.md) + describes. + - type: input + id: starter-version attributes: - label: Current Behaviour - description: A clear and concise description of the buggy behavior. + label: Skeleton version or commit + description: >- + The release you started from (`composer create-project` prints it) or, + if you cloned `main`, the commit SHA. + placeholder: 1.0.0, or a1b2c3d validations: required: true - - type: textarea + - type: dropdown + id: where attributes: - label: Expected Behaviour - description: >- - A clear and concise description of what you expected to happen (or - code). + label: Where does it happen? + options: + - In the skeleton itself, before `npm run init` + - In a plugin created from the skeleton, after `npm run init` + - During `composer create-project` or `npm run init` validations: required: true + - type: dropdown + id: area + attributes: + label: Area + options: + - "Init and capabilities (npm run init, bin/scaffold.config.js)" + - "Modules and examples (inc/Modules/)" + - "Core services (inc/Core/)" + - "Blocks and assets (src/, webpack, HMR, Tailwind)" + - "Tests and lint (PHPUnit, Jest, PHPCS, PHPStan, ESLint, Stylelint)" + - "CI workflows" + - Documentation + - Not sure + validations: + required: false + - type: input + id: wp-version + attributes: + label: WordPress version + placeholder: "6.8" + validations: + required: true + - type: input + id: php-version + attributes: + label: PHP version + placeholder: "8.3" + validations: + required: true + - type: input + id: node-version + attributes: + label: Node version + description: The output of `node --version`. The skeleton expects the version in `.nvmrc`. + placeholder: v22.22.2 + validations: + required: true + - type: input + id: environment + attributes: + label: Local environment + description: Where WordPress runs, if relevant, and whether it is multisite. + placeholder: wp-env, VIP dev-env, Local, a remote host + validations: + required: false - type: textarea + id: what-happened attributes: - label: Steps to reproduce the bug - description: Steps to reproduce if any. - placeholder: Steps to reproduce bug. - value: |- - 1. Go to '...' - 2. Click on '....' - 3. Scroll down to '....' - 4. See error + label: What happened? + description: The error or behaviour you saw. Screenshots help for editor and admin bugs. validations: required: true - type: textarea + id: expected attributes: - label: Testing Environment (if applicable) - description: Steps to reproduce if any. + label: What did you expect to happen? validations: required: true - type: textarea + id: reproduce attributes: - label: Additional context/Screenshots - description: >- - Add any other context about the problem here. If applicable, add - screenshots to help explain. + label: Steps to reproduce + placeholder: | + 1. composer create-project rtcamp/plugin-elementary demo-features + 2. cd demo-features && npm install && npm run init + 3. ... validations: required: true - - type: input + - type: textarea + id: logs + attributes: + label: Relevant log output + description: Terminal output, PHP errors or browser console errors. Rendered as code, so no backticks are needed. + render: shell + validations: + required: false + - type: checkboxes + id: checks attributes: - label: Estimated time in hours - description: Enter the estimated time required to finish this task. - placeholder: 40 Hours + label: Before you submit + options: + - label: I searched the existing issues and this is not a duplicate. + required: true + - label: I removed secrets, keys and private URLs from everything above. + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..3b74a35 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Report a security vulnerability + url: https://github.com/rtCamp/plugin-elementary/security/advisories/new + about: Report security issues privately, never in a public issue. See SECURITY.md. diff --git a/.github/ISSUE_TEMPLATE/enhancement.yml b/.github/ISSUE_TEMPLATE/enhancement.yml deleted file mode 100644 index 9ea3baa..0000000 --- a/.github/ISSUE_TEMPLATE/enhancement.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Enhancement -description: template for missing feature -title: '[Enhancement]: ' -labels: - - enhancement -body: - - type: textarea - attributes: - label: Description - description: A clear and concise description of the problem or missing capability - validations: - required: true - - type: textarea - attributes: - label: Describe the solution you'd like - description: If you have a solution in mind, please describe it. - - type: textarea - attributes: - label: Describe alternatives you've considered - description: Have you considered any alternative solutions or workarounds? - - type: input - attributes: - label: Estimated time in hours - description: Enter the estimated time required to finish this task. - placeholder: 40 Hours diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml index 789e1df..eea5268 100644 --- a/.github/ISSUE_TEMPLATE/feature.yml +++ b/.github/ISSUE_TEMPLATE/feature.yml @@ -1,25 +1,78 @@ -name: Feature Request -description: template for missing feature -title: '[Feature Request]: ' +name: Feature request +description: Suggest a new capability, or an improvement to an existing one, for the skeleton. +type: Feature labels: - - feature + - "Type: Feature" body: + - type: markdown + attributes: + value: >- + Thanks for the suggestion. Please search + [existing issues](https://github.com/rtCamp/plugin-elementary/issues?q=is%3Aissue) + first. + - type: markdown + attributes: + value: >- + Every plugin created from this skeleton copies it, so additions should + be general enough for most projects. Framework behaviour belongs in + [wp-primitives](https://github.com/rtCamp/wp-primitives), the init and + scaffold engine in [wp-tooling](https://github.com/rtCamp/wp-tooling), + and CI jobs in + [wp-shared-workflows](https://github.com/rtCamp/wp-shared-workflows). + [CONTRIBUTING.md](https://github.com/rtCamp/plugin-elementary/blob/main/CONTRIBUTING.md) + explains how to build it yourself. + - type: dropdown + id: kind + attributes: + label: Kind of request + options: + - Something new the skeleton does not do yet + - An improvement to something it already does + validations: + required: true + - type: dropdown + id: area + attributes: + label: Area + options: + - "Init and capabilities (npm run init, bin/scaffold.config.js)" + - "Modules and examples (inc/Modules/)" + - "Core services (inc/Core/)" + - "Blocks and assets (src/, webpack, HMR, Tailwind)" + - "Tests and lint (PHPUnit, Jest, PHPCS, PHPStan, ESLint, Stylelint)" + - "CI workflows" + - "AI tooling (AGENTS.md, Claude skills, Copilot prompts)" + - Documentation + - Not sure + validations: + required: true - type: textarea + id: problem attributes: - label: Description - description: A clear and concise description of the problem or missing capability + label: What problem does this solve? + description: A clear description of the problem or missing capability. validations: required: true - type: textarea + id: proposal attributes: label: Describe the solution you'd like - description: If you have a solution in mind, please describe it. + description: >- + What the skeleton should ship, and whether it should be always on, a + capability that init can remove, or an optional feature. + validations: + required: true - type: textarea + id: alternatives attributes: label: Describe alternatives you've considered - description: Have you considered any alternative solutions or workarounds? - - type: input + description: Workarounds you use now, or other designs you ruled out. + validations: + required: false + - type: checkboxes + id: checks attributes: - label: Estimated time in hours - description: Enter the estimated time required to finish this task. - placeholder: 40 Hours + label: Before you submit + options: + - label: I searched the existing issues and this is not a duplicate. + required: true diff --git a/.github/labels.yml b/.github/labels.yml new file mode 100644 index 0000000..eec0e6e --- /dev/null +++ b/.github/labels.yml @@ -0,0 +1,123 @@ +# Labels for rtCamp/plugin-elementary. +# +# Labels are repository settings, not files, so adding an entry here does not +# create the label. A maintainer applies this list by running +# `gh label create --color --description --force` +# for each entry: missing labels are created and existing ones updated. That +# never deletes a label, so removing an entry here does not remove it on GitHub. +# +# The "Shared" sections are the same in all five rtCamp framework repositories +# (wp-shared-workflows, wp-primitives, wp-tooling, theme-elementary and +# plugin-elementary), apart from the wording of the BC descriptions; change them +# in all five together. The sections after them belong to this repository. +# release-please writes the release notes from pull request titles, so these +# labels are for triage only. + +# --- Shared: type ------------------------------------------------------------ +- name: "Type: Bug" + color: "D93F0B" + description: "Something is broken or behaves unexpectedly" +- name: "Type: Feature" + color: "A2EEEF" + description: "New capability or an improvement to an existing one" +- name: "Type: Task" + color: "0E8A16" + description: "Self-contained unit of work for a milestone" +- name: "Type: Chore" + color: "C5DEF5" + description: "Maintenance, tooling, docs, housekeeping" +- name: "Type: Epic" + color: "5319E7" + description: "Large initiative spanning multiple issues" +- name: "Type: Story" + color: "1D76DB" + description: "Group of related tasks within an epic" +- name: "Type: Docs" + color: "BFD4F2" + description: "Documentation-only change" + +# --- Shared: priority -------------------------------------------------------- +- name: "Priority: P0" + color: "B60205" + description: "Critical: blocks milestone" +- name: "Priority: P1" + color: "D93F0B" + description: "High: sprint commitment" +- name: "Priority: P2" + color: "FBCA04" + description: "Medium: nice to have this sprint" +- name: "Priority: P3" + color: "C2E0C6" + description: "Low: can slip" + +# --- Shared: status ---------------------------------------------------------- +- name: "Status: Blocked" + color: "E99695" + description: "Waiting on another issue or external dependency" +- name: "Status: Needs Review" + color: "FEF2C0" + description: "Human review requested" +- name: "Status: Rotating" + color: "D4C5F9" + description: "Engineer rotated out: awaiting pickup (see Handoff log)" +- name: "Status: Needs BC Check" + color: "F9D0C4" + description: "Run /check-bc before merge" + +# --- Shared: backward compatibility ------------------------------------------ +- name: "BC: Breaking" + color: "B60205" + description: "Breaking change: targets release/vNext" +- name: "BC: Additive" + color: "0E8A16" + description: "Additive only: safe for current major" +- name: "BC: Deprecation" + color: "FBCA04" + description: "Adds or removes a @deprecated symbol" + +# --- Shared: dependencies and outside contributors --------------------------- +- name: "dependencies" + color: "0366d6" + description: "Pull requests that update a dependency file" +- name: "good first issue" + color: "7057ff" + description: "Good for newcomers" +- name: "help wanted" + color: "008672" + description: "Extra attention is needed" + +# --- This repository: scope (proposed) --------------------------------------- +- name: "Scope: Init" + color: "0075CA" + description: "npm run init, bin/scaffold.config.js and capability selection" +- name: "Scope: Modules" + color: "0075CA" + description: "inc/Modules/ capabilities and their Example classes" +- name: "Scope: Core" + color: "0075CA" + description: "Always-loaded inc/Core/ services (assets, templates, setup)" +- name: "Scope: Assets" + color: "0075CA" + description: "src/ sources, blocks, webpack build, HMR and Tailwind" +- name: "Scope: CI" + color: "0075CA" + description: "Caller workflows for rtCamp/wp-shared-workflows" +- name: "Scope: AI Tooling" + color: "0075CA" + description: "AGENTS.md, Claude skills, Copilot prompts and graphify" +- name: "Scope: Docs" + color: "FEF2C0" + description: "Documentation" +- name: "Scope: Tests" + color: "BFDADC" + description: "PHPUnit and Jest suites" + +# release-please adds "autorelease: pending" to its release pull request and +# finds the merged one to tag by it, then swaps it for "autorelease: tagged". +# Never delete these two, or a merged release may not get tagged. +- name: "autorelease: pending" + color: "ededed" + description: "release-please release PR, not tagged yet" +- name: "autorelease: tagged" + color: "ededed" + description: "release-please release PR, tagged" diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..624ab13 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,32 @@ +# Security policy + +## Supported versions + +| Version | Supported | +|---|---| +| Latest 1.x release and `main` | Yes | +| Copies of `features-plugin-skeleton`, its earlier repository | No; start new plugins from 1.x | + +Plugin Elementary is a skeleton: a plugin created from it is a copy, and it +does not update itself. A fix lands in the skeleton's next release, and the +advisory names the affected files so existing plugins can apply it by hand. +Code a project adds after creating its plugin belongs to that project's +maintainers, and issues in `rtcamp/wp-primitives` or `@rtcamp/wp-tooling` +belong in those repositories. + +## Reporting a vulnerability + +Please do not report security issues in public issues, pull requests or +discussions. + +Report them privately through GitHub instead: open the repository's +**Security** tab and choose **Report a vulnerability**, or go straight to +. The +report stays visible only to you and the maintainers, and the fix, the release +and the advisory are coordinated in that same thread. + +Include what you can of: + +- the affected file or capability, and the skeleton release or commit; +- the impact, and the conditions an attacker needs; +- steps or a minimal proof of concept that reproduce it. diff --git a/bin/scaffold.config.js b/bin/scaffold.config.js index c82a648..1d2b4fd 100644 --- a/bin/scaffold.config.js +++ b/bin/scaffold.config.js @@ -69,6 +69,29 @@ const workflow = ( key, label, file ) => ( { remove: [ `.github/workflows/${ file }` ], } ); +const fs = require( 'fs' ); +const path = require( 'path' ); + +// plugin-elementary's own repository files, kept out of new projects. Cleanup also runs on +// --reinit, "Re-run full setup" and --clean, and a project's own CODEOWNERS, +// SECURITY.md, .gitattributes or issue forms live at these same paths, so they +// are removed on first setup only: before init writes .wp-scaffold.json. +// .gitattributes export-ignores the same paths; tests/js/starter-files.test.js +// keeps the two lists in step. +const SKELETON_REPO_FILES = [ + 'tests/js/starter-files.test.js', + '.gitattributes', + '.github/CODEOWNERS', + '.github/ISSUE_TEMPLATE/bug.yml', + '.github/ISSUE_TEMPLATE/config.yml', + '.github/ISSUE_TEMPLATE/feature.yml', + '.github/labels.yml', + 'SECURITY.md', +]; +const firstSetup = ! fs.existsSync( + path.join( __dirname, '..', '.wp-scaffold.json' ) +); + module.exports = { kind: 'plugin', vendor: 'rtcamp', @@ -228,11 +251,13 @@ module.exports = { ], }, - // The skeleton's own release workflow, config and manifest. Cleanup also runs - // on re-init and --clean, so both paths are named for the skeleton: a - // project's own release-please files never live there. + // Skeleton-only repository files. A composer create-project copy never has them + // (.gitattributes export-ignores each one); a git clone does, so init removes them. + // Cleanup also runs on re-init and --clean, so the release paths are named for the + // skeleton: a project's own release-please files never live there. cleanup: { targets: [ + ...( firstSetup ? SKELETON_REPO_FILES : [] ), '.github/release-starter', '.github/workflows/release-starter.yml', ], diff --git a/docs/initialization.md b/docs/initialization.md index 79accf5..8836687 100644 --- a/docs/initialization.md +++ b/docs/initialization.md @@ -74,7 +74,7 @@ What each set contains is listed in [Included features](features.md). | Kept example sets | Stay as working references. Their `// wp:example:` markers are removed. | | Optional features | `hmr` edits `.env.local`; `tailwind` adds `src/css/tailwind.css`, `postcss.config.js`, dependencies and flips the Tailwind constant. | | State | `.wp-scaffold.json` records identity and feature choices. Do not edit it by hand. | -| Cleanup | Only the skeleton's own release automation is removed: `.github/workflows/release-starter.yml` and `.github/release-starter/`. Copilot files, the other workflows and templates under `.github/` stay. | +| Cleanup | The skeleton's own repository files are removed: `.gitattributes`, `.github/CODEOWNERS`, the bug and feature issue forms and their `config.yml`, `.github/labels.yml`, `SECURITY.md`, and its release workflow, config and manifest (`.github/workflows/release-starter.yml`, `.github/release-starter/`). A `composer create-project` copy never contains them; a git clone does until init runs. Copilot files, the other workflows and templates under `.github/` stay. | | AI instructions | `sync-ai` refreshes `.github/instructions/framework-php.instructions.md` from the installed framework. | Init does not generate a translation template. Run `npm run pot` when preparing translations. diff --git a/tests/js/starter-files.test.js b/tests/js/starter-files.test.js new file mode 100644 index 0000000..e13cddb --- /dev/null +++ b/tests/js/starter-files.test.js @@ -0,0 +1,23 @@ +/** Keep the skeleton-only files out of every install route. */ +const fs = require( 'fs' ); +const path = require( 'path' ); + +const root = path.resolve( __dirname, '../..' ); +// A plugin made from the skeleton has run init, which removes .gitattributes and +// this test; skip there in case it was kept. +const inSkeleton = ! fs.existsSync( path.join( root, '.wp-scaffold.json' ) ); + +( inSkeleton ? describe : describe.skip )( 'skeleton-only files', () => { + test( 'every export-ignore path is also an init cleanup target', () => { + const ignored = fs + .readFileSync( path.join( root, '.gitattributes' ), 'utf8' ) + .split( '\n' ) + .map( ( line ) => line.trim() ) + .filter( ( line ) => /\sexport-ignore$/.test( line ) ) + .map( ( line ) => line.split( /\s+/ )[ 0 ].replace( /^\//, '' ) ); + const { cleanup } = require( '../../bin/scaffold.config.js' ); + + expect( ignored.length ).toBeGreaterThan( 0 ); + expect( cleanup.targets ).toEqual( expect.arrayContaining( ignored ) ); + } ); +} );