From cae9010718ba9bb0fd7b6188314f29b80ecb6a8c Mon Sep 17 00:00:00 2001 From: Shevchik Igor Date: Wed, 26 Aug 2026 11:21:58 +0000 Subject: [PATCH 1/2] docs(governance): add CONTRIBUTING.md and the two issue forms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GitHub surfaces governance files by filename, and this repository had none, so none of the guides it does have were reachable from the places a contributor actually looks: the sidebar link, the banner on a new issue, the "how do I report this" moment. The content already existed — `.github/contributing/*` are thorough, `AGENTS.md` is a strong de-facto guide — it was simply invisible (#97). `CONTRIBUTING.md` is a map, not a guide. A table routing each intent to the file that already answers it, plus the few things a first pull request needs to know and nothing else: the pinned Node and pnpm, that `dev:prepare` comes before anything typechecks, that the title is a conventional commit and is load-bearing because the squashed subject generates the changelog, and that snapshots are regenerated with the whole suite. Two issue forms. The bug report requires a reproduction and says why in the field itself: most reports that look like a defect here depend on something in the application around the component, and prose cannot separate those. It also asks whether the behaviour reproduces upstream, because this is a fork and a bug present in Nuxt UI is fixed differently from one introduced by a port. The feature request asks for the problem before the proposal, and asks whether the thing already exists upstream — "port and reconcile" is a different and much shorter conversation than "design and build". Blank issues stay enabled: the maintainer files audit notes and follow-ups that fit neither form. The README's contribution section promised a "reporting bugs guide" and linked nothing at all. It now links the form, the guide, and CONTRIBUTING.md. `documented-scripts.spec.ts` gains `CONTRIBUTING.md`, so a command quoted there that does not exist fails a test rather than costing every reader a minute. Verified by mutation. `README.md` stays out of that list on purpose: its `pnpm` lines install this package into somebody else's application. SECURITY.md and CODE_OF_CONDUCT.md are deliberately not here. Both need a private contact to report into, the repository publishes none, and inventing one would send a reporter into a void — deferred to the maintainer's decision along with the CODEOWNERS entry and branch protection. --- .github/ISSUE_TEMPLATE/bug-report.yml | 91 ++++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 14 ++++ .github/ISSUE_TEMPLATE/feature-request.yml | 69 ++++++++++++++++ CONTRIBUTING.md | 78 +++++++++++++++++++ README.md | 5 +- test/utils/documented-scripts.spec.ts | 5 ++ 6 files changed, 260 insertions(+), 2 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug-report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature-request.yml create mode 100644 CONTRIBUTING.md diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 00000000..641fedda --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,91 @@ +name: Bug report +description: Something in Bitrix24 UI behaves differently from what it documents. +title: "fix(Component): " +body: + - type: markdown + attributes: + value: | + Before filing, please check the [open issues](https://github.com/bitrix24/b24ui/issues?q=is%3Aissue) — + this library is a fork of [Nuxt UI](https://github.com/nuxt/ui) and ports land continuously, + so a bug reported a week ago may already be fixed on `main`. + + Keep the title a [conventional commit](https://www.conventionalcommits.org/) subject + if you can: `fix(Button): the loading icon keeps spinning after loading ends`. + It is not required to file, but it makes the fix's changelog entry write itself. + + - type: input + id: component + attributes: + label: Component or composable + description: The name as it is exported — `B24Button`, `useOverlay`, `defineShortcuts`. + placeholder: B24Button + validations: + required: true + + - type: textarea + id: what-happens + attributes: + label: What happens + description: What you see, and what you expected instead. Screenshots help for anything visual. + validations: + required: true + + - type: input + id: reproduction + attributes: + label: Reproduction + description: | + A [StackBlitz](https://stackblitz.com/) or a small repository someone can open and see it in. + + This is the field that decides whether the report can be acted on. Most reports that + look like a bug in this library turn out to depend on something in the application + around it — a Tailwind config, a competing plugin, a wrapper component — and a + description alone cannot tell those apart from a real defect. + + If you genuinely cannot produce one, say so in the field and explain what blocks it. + A report with a reason is worth more than a report with an empty box. + placeholder: https://stackblitz.com/edit/... + validations: + required: true + + - type: input + id: version + attributes: + label: "`@bitrix24/b24ui-nuxt` version" + description: From your lockfile, not from memory — `pnpm why @bitrix24/b24ui-nuxt` prints it. + placeholder: 2.12.0 + validations: + required: true + + - type: dropdown + id: environment + attributes: + label: Environment + options: + - Nuxt + - Vue (without Nuxt) + - Both + validations: + required: true + + - type: textarea + id: versions + attributes: + label: Versions around it + description: | + Nuxt or Vue, Node, and your package manager. `npx nuxi info` prints the lot for a + Nuxt project. + render: shell + validations: + required: false + + - type: textarea + id: extra + attributes: + label: Anything else + description: | + Whether it also reproduces in [Nuxt UI](https://ui.nuxt.com/) is worth a line if you + know — this is a fork, and a bug present upstream is fixed differently from one that + arrived in the port. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..dc465639 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,14 @@ +# Blank issues stay on: the maintainer files a lot of them, and an audit note or +# a follow-up does not fit either form. The forms are for reports arriving from +# outside, where the missing field is usually the reproduction. +blank_issues_enabled: true +contact_links: + - name: Documentation + url: https://bitrix24.github.io/b24ui/ + about: Component reference, theming, and the getting-started guides. + - name: Contributing + url: https://github.com/bitrix24/b24ui/blob/main/CONTRIBUTING.md + about: How to set the repository up, and where each guide lives. + - name: Bitrix24 REST API + url: https://apidocs.bitrix24.com/ + about: Questions about the Bitrix24 API itself rather than this UI library. diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml new file mode 100644 index 00000000..47204943 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -0,0 +1,69 @@ +name: Feature request +description: Something Bitrix24 UI should be able to do and cannot. +title: "feat(Component): " +labels: [enhancement] +body: + - type: markdown + attributes: + value: | + Please check the [open issues](https://github.com/bitrix24/b24ui/issues?q=is%3Aissue+label%3Aenhancement) + first, and the [documentation](https://bitrix24.github.io/b24ui/) — a fair number of + requests turn out to be a prop that already exists under a different name. + + - type: textarea + id: problem + attributes: + label: What are you trying to do + description: | + The problem, not the solution. What are you building, and where does the library stop + being able to help? + + This is asked first on purpose. A request phrased as a solution can usually only be + accepted or rejected; a request phrased as a problem can be answered with something + better than what was asked for. + validations: + required: true + + - type: textarea + id: proposal + attributes: + label: What you have in mind + description: The API you would want, if you have a shape in mind. Code is fine. + render: vue + validations: + required: false + + - type: dropdown + id: upstream + attributes: + label: Does this exist in Nuxt UI? + description: | + Bitrix24 UI is a fork of [Nuxt UI](https://github.com/nuxt/ui), ported deliberately + rather than merged wholesale. If the thing already exists upstream, the work is + "port and reconcile" instead of "design and build" — a much shorter conversation, + and knowing which one this is changes where it lands in the queue. + options: + - "Yes — it exists upstream" + - "No — this would be new" + - "I do not know" + validations: + required: true + + - type: input + id: upstream-link + attributes: + label: Link to it upstream + description: If you answered yes — the documentation page, the component, or the pull request. + placeholder: https://ui.nuxt.com/components/... + validations: + required: false + + - type: textarea + id: workaround + attributes: + label: What you are doing instead today + description: | + The workaround, if there is one. It tells us how much this hurts, and it is often the + clearest statement of the problem — including whether a fix is needed at all. + validations: + required: false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..5ac49913 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,78 @@ +# Contributing to Bitrix24 UI + +Thanks for being here. This file is deliberately short: it is a map, not a +guide. Everything it points at is written properly somewhere else, and this +page exists so GitHub can surface it — the "Contributing" link in the sidebar +and the banner on a new issue both look for a file with this name. + +## Start here + +| I want to… | Read | +|---|---| +| set up the repository and run it | [Contribution guide](https://bitrix24.github.io/b24ui/docs/getting-started/contribution/) | +| understand how the project is laid out | [`AGENTS.md`](AGENTS.md) | +| add or change a component | [Component structure](.github/contributing/component-structure.md) | +| change how something looks | [Theme structure](.github/contributing/theme-structure.md) | +| write or update tests | [Testing](.github/contributing/testing.md) | +| write documentation | [Documentation](.github/contributing/documentation.md) | +| understand how a release happens | [Releasing](.github/contributing/releasing.md) | + +`AGENTS.md` is written for AI coding agents and is picked up automatically by +most of them. It is also the fastest orientation for a human — project layout, +commands, conventions, in one file. + +## Reporting a bug + +[Open a bug report.](https://github.com/bitrix24/b24ui/issues/new?template=bug-report.yml) + +The form asks for a reproduction, and it asks because that is what decides +whether the report can be acted on. A component name and a description of what +looked wrong is usually not enough to tell a bug in this library from a bug in +the application using it — the shortest useful reproduction is a +[StackBlitz](https://stackblitz.com/) or a small repository someone can open +and see the problem in. + +If you are not sure whether it is a bug in this library or in the application +around it, open the report anyway and say so — a report that names the +uncertainty is more useful than a question that never gets asked. + +## Suggesting something + +[Open a feature request.](https://github.com/bitrix24/b24ui/issues/new?template=feature-request.yml) + +This library is a fork of [Nuxt UI](https://github.com/nuxt/ui), tracked and +ported deliberately rather than merged wholesale. If the thing you want exists +upstream, say so in the request — it changes the work from "design and build" +to "port and reconcile", which is a different and usually much shorter +conversation. + +## Opening a pull request + +Before you start on anything substantial, open an issue. It is not +bureaucracy: this repository carries divergences from upstream that are +recorded and intentional, and the fastest way to lose an afternoon is to fix +something that is deliberately the way it is. + +Then: + +- **Node** `^20.19.0 || >=22.12.0`, **pnpm** `11.20.0` (the version is pinned in + `package.json`; `corepack enable` picks it up). +- Run `pnpm run dev:prepare` after installing. Nothing typechecks before it. +- `pnpm run lint`, `pnpm run typecheck` and `pnpm run test run` all have to + pass. CI runs the same commands, plus a coverage gate — see + [Testing → Coverage](.github/contributing/testing.md#coverage). +- **The PR title is a [conventional commit](https://www.conventionalcommits.org/)** + and it is load-bearing. The title of the squashed commit is what generates the + changelog and decides the version bump, so a title that does not parse is + dropped from the release notes silently. A CI check enforces the format. +- Snapshots are regenerated with `pnpm run test:update` — the whole suite, never + a path. [Why.](.github/contributing/testing.md#snapshot-updates) + +The pull request template asks for a linked issue and a description. Both are +read. + +## Code of conduct and security + +Be decent to each other. A formal code of conduct and a security policy are +being written; until they land, raise anything urgent with the maintainer +through the repository. diff --git a/README.md b/README.md index 42e07a83..ec149156 100644 --- a/README.md +++ b/README.md @@ -104,8 +104,9 @@ Learn more in the [installation guide](https://bitrix24.github.io/b24ui/docs/get Thank you for considering contributing to Bitrix24 UI. Here are a few ways you can get involved: -- Reporting Bugs: If you come across any bugs or issues, please check out the reporting bugs guide to learn how to submit a bug report. -- Suggestions: Have any thoughts to enhance Bitrix24 UI? We'd love to hear them! Check out the [contribution guide](https://bitrix24.github.io/b24ui/docs/getting-started/contribution/) to share your suggestions. +- **Reporting bugs**: [open a bug report](https://github.com/bitrix24/b24ui/issues/new?template=bug-report.yml). The form asks for a reproduction, and that is the field that decides whether the report can be acted on — see [reporting a bug](https://github.com/bitrix24/b24ui/blob/main/CONTRIBUTING.md#reporting-a-bug). +- **Suggestions**: [open a feature request](https://github.com/bitrix24/b24ui/issues/new?template=feature-request.yml). Have any thoughts to enhance Bitrix24 UI? We'd love to hear them. +- **Code**: [CONTRIBUTING.md](https://github.com/bitrix24/b24ui/blob/main/CONTRIBUTING.md) is the map — setup, the deep-dive guides, and what CI expects of a pull request. The [contribution guide](https://bitrix24.github.io/b24ui/docs/getting-started/contribution/) on the documentation site covers getting the repository running. > [!TIP] > We provide contributing guidelines through [`AGENTS.md`](https://github.com/bitrix24/b24ui/blob/main/AGENTS.md) for AI assistants to help you contribute to Bitrix24 UI. It is automatically picked up by all AI coding agents and guides through component structure, theming patterns, testing conventions, and documentation guidelines. diff --git a/test/utils/documented-scripts.spec.ts b/test/utils/documented-scripts.spec.ts index 67175354..4c57cd61 100644 --- a/test/utils/documented-scripts.spec.ts +++ b/test/utils/documented-scripts.spec.ts @@ -16,11 +16,16 @@ import { describe, it, expect } from 'vitest' * `4.contribution.md` is the published mirror of the contributor guide. The * installation and usage pages are out, because there `pnpm add` and `pnpm dev` * belong to the reader's project, which this `package.json` says nothing about. + * + * `README.md` is out for the same reason: its `pnpm` lines install this package + * into somebody else's application. `CONTRIBUTING.md` is in — it is the first + * file GitHub shows a contributor, and every command in it runs here. */ const repoRoot = process.cwd() const DOC_FILES = [ 'AGENTS.md', + 'CONTRIBUTING.md', '.github/contributing/testing.md', '.github/contributing/documentation.md', '.github/contributing/component-structure.md', From 957e752369362fff898942ec4e0fefb875887331 Mon Sep 17 00:00:00 2001 From: Shevchik Igor Date: Wed, 26 Aug 2026 11:56:00 +0000 Subject: [PATCH 2/2] docs(review): stop the security section inviting a public disclosure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two reviewers flagged the same sentence independently. Under a heading about security, "raise anything urgent with the maintainer through the repository" means a public issue, because that is the only channel this repository has — so the file was instructing vulnerability reporters to disclose to everyone before there is a fix. That is worse than saying nothing. It now says plainly not to open a public issue, and that there is nowhere good to send one yet. The same section claimed a code of conduct and a security policy were "being written". There is no draft, no branch and no issue for either; the claim was a promise that would quietly never land. It now says what is true. Also from review, each checked against the repository first: - The reproduction field was a single-line `input` whose own description asks for a paragraph explaining what blocked a reproduction. Now a `textarea`. - Neither form warned that issues are public. Both now ask for tokens, keys, portal URLs and customer data to be redacted, and the `nuxi info` hint says to read the output first — it prints local paths and the full module list. - "CI runs the same commands" was wrong: CI runs `test:coverage` rather than the plain run, plus `test:module` and `build`, either of which can fail on a branch where the three local commands are green. - `bug-report.yml` set no label while the other form did; `bug` exists. - The title example said `fix(Button)` and the component field said `B24Button`, with nothing saying why they differ. Now stated. - The docs-site contribution page still filtered issues by `label:v3`, which is upstream's label and matches nothing in a fork at 2.x, and still said "open a new issue" with no mention of the forms. `governance-files.spec.ts` covers the two things nothing else could see. An issue form that breaks the schema is not an error anywhere — GitHub stops offering it and falls back to a blank issue box, which looks exactly like a repository that never had forms. And the pinned Node and pnpm versions are quoted as prose in `CONTRIBUTING.md`, where `documented-scripts.spec.ts` only checks script names, so a `packageManager` bump would leave the doc wrong with nothing red. Six mutations, six failures: a `validations` key on a markdown block, an unknown `render` language, a duplicate id, unparseable YAML, a `?template=` link that no longer names a form, and a bumped pnpm version. A parse failure is carried as data rather than thrown, because throwing at collection time reports as "no tests" — the same output as a spec nobody wrote. Found by mutating for it. `yaml` becomes an explicit devDependency; it was only in the tree transitively, through vite. --- .github/ISSUE_TEMPLATE/bug-report.yml | 24 +++- .github/ISSUE_TEMPLATE/feature-request.yml | 3 + CONTRIBUTING.md | 26 +++- .../docs/1.getting-started/4.contribution.md | 6 +- package.json | 3 +- pnpm-lock.yaml | 3 + test/utils/governance-files.spec.ts | 125 ++++++++++++++++++ 7 files changed, 175 insertions(+), 15 deletions(-) create mode 100644 test/utils/governance-files.spec.ts diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 641fedda..927418f0 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -1,6 +1,7 @@ name: Bug report description: Something in Bitrix24 UI behaves differently from what it documents. title: "fix(Component): " +labels: [bug] body: - type: markdown attributes: @@ -11,13 +12,20 @@ body: Keep the title a [conventional commit](https://www.conventionalcommits.org/) subject if you can: `fix(Button): the loading icon keeps spinning after loading ends`. - It is not required to file, but it makes the fix's changelog entry write itself. + The scope is the component without its `B24` prefix — `Button`, not `B24Button` — + because that is what reaches the changelog. It is not required to file. + + **Issues here are public.** Please redact tokens, API keys, portal URLs and + customer data from anything you paste — logs, screenshots and reproductions + included. - type: input id: component attributes: label: Component or composable - description: The name as it is exported — `B24Button`, `useOverlay`, `defineShortcuts`. + description: | + The name as it is exported — `B24Button`, `useOverlay`, `defineShortcuts`. The + `B24` prefix belongs here but not in the title; see above. placeholder: B24Button validations: required: true @@ -30,7 +38,7 @@ body: validations: required: true - - type: input + - type: textarea id: reproduction attributes: label: Reproduction @@ -42,9 +50,12 @@ body: around it — a Tailwind config, a competing plugin, a wrapper component — and a description alone cannot tell those apart from a real defect. - If you genuinely cannot produce one, say so in the field and explain what blocks it. + If you genuinely cannot produce one, say so here and explain what blocks it. A report with a reason is worth more than a report with an empty box. - placeholder: https://stackblitz.com/edit/... + placeholder: | + https://stackblitz.com/edit/... + + Or: what you tried, and what stopped you from isolating it. validations: required: true @@ -74,7 +85,8 @@ body: label: Versions around it description: | Nuxt or Vue, Node, and your package manager. `npx nuxi info` prints the lot for a - Nuxt project. + Nuxt project — check its output before pasting, it includes local paths and your + full module list. render: shell validations: required: false diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml index 47204943..a32a69e3 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -10,6 +10,9 @@ body: first, and the [documentation](https://bitrix24.github.io/b24ui/) — a fair number of requests turn out to be a prop that already exists under a different name. + **Issues here are public.** Please redact tokens, API keys, portal URLs and + customer data from any code you paste. + - type: textarea id: problem attributes: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5ac49913..ecd4c035 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -59,8 +59,11 @@ Then: `package.json`; `corepack enable` picks it up). - Run `pnpm run dev:prepare` after installing. Nothing typechecks before it. - `pnpm run lint`, `pnpm run typecheck` and `pnpm run test run` all have to - pass. CI runs the same commands, plus a coverage gate — see - [Testing → Coverage](.github/contributing/testing.md#coverage). + pass locally. CI runs more than that: `pnpm run test:coverage` in place of + the plain run, so a large new area arriving untested is red — see + [Testing → Coverage](.github/contributing/testing.md#coverage) — plus + `pnpm test:module` and `pnpm build`, both of which can fail on a branch where + the three commands above are green. - **The PR title is a [conventional commit](https://www.conventionalcommits.org/)** and it is load-bearing. The title of the squashed commit is what generates the changelog and decides the version bump, so a title that does not parse is @@ -71,8 +74,19 @@ Then: The pull request template asks for a linked issue and a description. Both are read. -## Code of conduct and security +## Code of conduct -Be decent to each other. A formal code of conduct and a security policy are -being written; until they land, raise anything urgent with the maintainer -through the repository. +There is no formal one yet. Be decent to each other in the meantime — the +usual reading applies: assume good faith, argue with the work rather than the +person, and take the hint when someone asks you to drop it. + +## Security + +**Please do not open a public issue for a vulnerability.** + +This repository does not yet publish a private channel to send one to, which +means there is currently nowhere good for a report to go — and a public issue +is the one place it should not go, because it discloses the problem to +everyone before there is a fix. If you have found something security-sensitive, +hold it until this section names a channel. That is the next thing to land +here. diff --git a/docs/content/docs/1.getting-started/4.contribution.md b/docs/content/docs/1.getting-started/4.contribution.md index 71da3b35..b081c889 100644 --- a/docs/content/docs/1.getting-started/4.contribution.md +++ b/docs/content/docs/1.getting-started/4.contribution.md @@ -8,7 +8,9 @@ The goal of this project is to provide components identical to Bitrix24 to enhan We have chosen [Nuxt UI](https://ui.nuxt.com/) as the foundation. We welcome your contributions in the form of bug reports, pull requests, and feedback to make this library even better. ::caution -Before reporting a bug or requesting a feature, make sure that you have read through our [documentation](https://bitrix24.github.io/b24ui/) and existing [issues](https://github.com/bitrix24/b24ui/issues?q=is%3Aissue%20is%3Aopen%20sort%3Aupdated-desc%20label%3Av3). +Before reporting a bug or requesting a feature, make sure that you have read through our [documentation](https://bitrix24.github.io/b24ui/) and the existing [issues](https://github.com/bitrix24/b24ui/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc). + +Then use a form: [bug report](https://github.com/bitrix24/b24ui/issues/new?template=bug-report.yml) or [feature request](https://github.com/bitrix24/b24ui/issues/new?template=feature-request.yml). The bug form asks for a reproduction, and that is the field that decides whether the report can be acted on. :: ## AI assistance @@ -70,7 +72,7 @@ The module code resides in the `src` folder. Here's a breakdown of its structure Before you start, check if there's an existing issue describing the problem or feature request you're working on. If there is, please leave a comment on the issue to let us know you're working on it. -If there isn't, open a new issue to discuss the problem or feature. +If there isn't, open a [feature request](https://github.com/bitrix24/b24ui/issues/new?template=feature-request.yml) to discuss the problem or feature. ### Local development diff --git a/package.json b/package.json index 6e93e033..723302b6 100644 --- a/package.json +++ b/package.json @@ -237,7 +237,8 @@ "vitest-axe": "^0.1.0", "vitest-environment-nuxt": "^2.0.0", "vue": "^3.5.41", - "vue-tsc": "^3.3.11" + "vue-tsc": "^3.3.11", + "yaml": "^2.9.0" }, "peerDependencies": { "@inertiajs/vue3": "^2.0.7 || ^3.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 575dbe48..7b465e57 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -311,6 +311,9 @@ importers: vue-tsc: specifier: ^3.3.11 version: 3.3.11(typescript@6.0.3) + yaml: + specifier: ^2.9.0 + version: 2.9.0 cli: dependencies: diff --git a/test/utils/governance-files.spec.ts b/test/utils/governance-files.spec.ts new file mode 100644 index 00000000..9b1a512b --- /dev/null +++ b/test/utils/governance-files.spec.ts @@ -0,0 +1,125 @@ +import { readdirSync, readFileSync } from 'node:fs' +import { join } from 'node:path' +import { describe, it, expect } from 'vitest' +import { parse } from 'yaml' + +const repoRoot = process.cwd() +const TEMPLATE_DIR = join(repoRoot, '.github/ISSUE_TEMPLATE') + +/** + * An issue form that does not match GitHub's schema does not fail loudly. The + * form silently stops being offered and the repository falls back to a blank + * issue box, which looks exactly like a repository that never had forms — so + * the first person to notice is whoever wonders why reports stopped carrying + * a reproduction. + * + * Nothing else here validates these files: eslint does not read the directory, + * and the two `.github` assert scripts look at `uses:` pins and install lines. + * This is the cheap half of the schema — the parts that actually go wrong when + * somebody edits a form by copying a block. + */ +const BLOCK_TYPES = new Set(['markdown', 'input', 'textarea', 'dropdown', 'checkboxes']) + +/** `render` takes a Linguist language name, lowercased. Only what the forms use. */ +const RENDER_LANGUAGES = new Set(['shell', 'vue', 'typescript', 'javascript', 'json', 'html', 'css']) + +/** + * Parsed, or the parse error carried as data. + * + * Throwing here would kill the whole file at collection time, and a spec that + * fails to collect reports as "no tests" — the same output as a spec nobody + * wrote. The error belongs in an assertion where it names the file. + */ +function forms(): [string, any, string | null][] { + return readdirSync(TEMPLATE_DIR) + .filter(name => name.endsWith('.yml') && name !== 'config.yml') + .sort() + .map((name) => { + try { + return [name, parse(readFileSync(join(TEMPLATE_DIR, name), 'utf8')), null] + } catch (error) { + return [name, null, String(error)] + } + }) +} + +describe('issue forms', () => { + const parsed = forms() + + it('are found at all', () => { + // The assertions below all pass on an empty list, which is how a directory + // that got renamed reads exactly like a directory that is fine. + expect(parsed.map(([name]) => name)).toEqual(['bug-report.yml', 'feature-request.yml']) + }) + + it.each(parsed)('%s matches the shape GitHub accepts', (name, form, parseError) => { + expect(parseError, `${name} is not valid YAML`).toBeNull() + expect(typeof form.name).toBe('string') + expect(typeof form.description).toBe('string') + expect(Array.isArray(form.body)).toBe(true) + + const ids: string[] = [] + + for (const block of form.body) { + expect(BLOCK_TYPES).toContain(block.type) + + if (block.type === 'markdown') { + // GitHub rejects a markdown block carrying `validations` or an `id`. + expect(block.validations).toBeUndefined() + expect(block.id).toBeUndefined() + expect(typeof block.attributes.value).toBe('string') + continue + } + + expect(block.id, `${block.type} block without an id`).toMatch(/^[a-z][a-z0-9-]*$/) + ids.push(block.id) + expect(typeof block.attributes.label).toBe('string') + + if (block.type === 'dropdown') { + expect(Array.isArray(block.attributes.options)).toBe(true) + expect(block.attributes.options.length).toBeGreaterThan(1) + // `None` is reserved on a required dropdown and silently breaks it. + if (block.validations?.required) expect(block.attributes.options).not.toContain('None') + } + + if (block.attributes.render !== undefined) { + expect(RENDER_LANGUAGES).toContain(block.attributes.render) + } + } + + expect(ids, 'duplicate ids').toEqual([...new Set(ids)]) + }) + + it('are reachable by the links that advertise them', () => { + // `?template=` names the file. A rename that misses one of these leaves a + // link that opens a blank issue instead of the form, which is not an error + // anywhere — the page just quietly shows the wrong thing. + const advertised = ['README.md', 'CONTRIBUTING.md', 'docs/content/docs/1.getting-started/4.contribution.md'] + .flatMap(file => [...readFileSync(join(repoRoot, file), 'utf8').matchAll(/\?template=([\w.-]+)/g)]) + .map(match => match[1]!) + + expect(advertised.length).toBeGreaterThan(0) + expect([...new Set(advertised)].sort()).toEqual(parsed.map(([name]) => name)) + }) +}) + +/** + * `CONTRIBUTING.md` quotes the pinned Node and pnpm versions as prose. That is + * the right place for them — it is the first thing a contributor reads — but + * prose does not run, and `documented-scripts.spec.ts` only checks script + * names, so a `packageManager` bump would leave the doc wrong with nothing + * red anywhere. + */ +describe('CONTRIBUTING.md', () => { + const contributing = readFileSync(join(repoRoot, 'CONTRIBUTING.md'), 'utf8') + const pkg = JSON.parse(readFileSync(join(repoRoot, 'package.json'), 'utf8')) + + it('quotes the pinned pnpm version', () => { + const pinned = pkg.packageManager.replace(/^pnpm@/, '') + expect(contributing, `package.json pins pnpm@${pinned}`).toContain(pinned) + }) + + it('quotes the supported Node range', () => { + expect(contributing, `package.json declares engines.node ${pkg.engines.node}`).toContain(pkg.engines.node) + }) +})