diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 0000000..2c94f74 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,117 @@ +name: Bug +description: Something isn't working as expected. +title: "[Bug]: " +labels: ["bug", "needs triage"] +body: + - type: markdown + attributes: + value: | + Thanks for the report. Please search [existing issues](https://github.com/mcinquin/markit/issues) first. + + **Security problems:** do not use this form — see [SECURITY.md](https://github.com/mcinquin/markit/blob/main/SECURITY.md). + + - type: checkboxes + id: existing + attributes: + label: Existing issues + options: + - label: I have searched existing issues and this is not a duplicate + required: true + + - type: textarea + id: summary + attributes: + label: Summary + description: What went wrong in one or two sentences? + placeholder: e.g. Checking a cell on the live board does not sync for other players. + validations: + required: true + + - type: textarea + id: steps + attributes: + label: Steps to reproduce + description: Minimal steps someone else can follow. + placeholder: | + 1. Go to … + 2. Click … + 3. See error … + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected behaviour + placeholder: What should have happened? + validations: + required: true + + - type: textarea + id: actual + attributes: + label: Actual behaviour + description: Include error messages, status codes, or console output if relevant. + placeholder: What happened instead? + validations: + required: true + + - type: dropdown + id: environment + attributes: + label: How do you run MarkIt? + options: + - Local (`npm run dev`) + - Docker Compose + - Other (describe below) + validations: + required: true + + - type: input + id: version + attributes: + label: Version + description: App version from the UI / package, or Docker image tag (e.g. `1.8.46`). + placeholder: e.g. 1.8.46 + validations: + required: false + + - type: input + id: browser + attributes: + label: Browser / OS + description: Where you saw the bug (if UI-related). + placeholder: e.g. Firefox 133 on Linux + validations: + required: false + + - type: dropdown + id: area + attributes: + label: Area (optional) + description: Closest product surface — maintainers may adjust the label. + multiple: false + options: + - Unsure + - Landing + - Dashboard + - Teams + - Boards + - Phrases + - Play + - Realtime (Socket.io) + - Admin + - Auth + - Account + - UI + - Infra (Docker / CI / Postgres) + validations: + required: false + + - type: textarea + id: extras + attributes: + label: Extra context + description: Screenshots, logs, or links. Drag files into this field to attach them. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..4045db4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Security vulnerability + url: https://github.com/mcinquin/markit/security/advisories/new + about: Do not open a public issue — report privately via GitHub Security Advisories (see SECURITY.md). + - name: Documentation (Wiki) + url: https://github.com/mcinquin/markit/wiki + about: Architecture, Docker, CI, backups, and development notes. diff --git a/.github/ISSUE_TEMPLATE/enhancement.yml b/.github/ISSUE_TEMPLATE/enhancement.yml new file mode 100644 index 0000000..880597f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/enhancement.yml @@ -0,0 +1,75 @@ +name: Enhancement +description: Propose a new feature or an improvement to existing behaviour. +title: "[Enhancement]: " +labels: ["enhancement", "needs triage"] +body: + - type: markdown + attributes: + value: | + Describe the need first; a concrete proposal helps, but is optional. + + Check the [wiki](https://github.com/mcinquin/markit/wiki) and [existing issues](https://github.com/mcinquin/markit/issues) before opening a duplicate. + + - type: checkboxes + id: existing + attributes: + label: Existing issues + options: + - label: I have searched existing issues and this is not a duplicate + required: true + + - type: textarea + id: problem + attributes: + label: Problem / motivation + description: What are you trying to do, and what is painful or missing today? + placeholder: e.g. Creating a board is awkward because selecting phrases takes too many clicks… + validations: + required: true + + - type: textarea + id: proposal + attributes: + label: Proposed solution + description: How would you like it to work? + placeholder: e.g. Add a “select all unused” action on the phrase picker… + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + description: Other approaches you thought about (or “none”). + validations: + required: false + + - type: dropdown + id: area + attributes: + label: Area (optional) + description: Closest product surface — maintainers may adjust the label. + options: + - Unsure + - Landing + - Dashboard + - Teams + - Boards + - Phrases + - Play + - Realtime (Socket.io) + - Admin + - Auth + - Account + - UI + - Infra (Docker / CI / Postgres) + validations: + required: false + + - type: textarea + id: extras + attributes: + label: Extra context + description: Mockups, examples from other apps, or links. Drag files into this field to attach them. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml new file mode 100644 index 0000000..5119fda --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -0,0 +1,59 @@ +name: Question +description: Ask about usage, setup, or behaviour — not a bug or feature request. +title: "[Question]: " +labels: ["question", "needs triage"] +body: + - type: markdown + attributes: + value: | + Prefer the [wiki](https://github.com/mcinquin/markit/wiki) for architecture, Docker, CI, and backups. + + **Security:** do not discuss vulnerabilities here — see [SECURITY.md](https://github.com/mcinquin/markit/blob/main/SECURITY.md). + + - type: checkboxes + id: docs + attributes: + label: Documentation checked + options: + - label: I have checked the README and wiki for an answer + required: true + + - type: textarea + id: question + attributes: + label: Question + description: What do you need clarified? + validations: + required: true + + - type: textarea + id: context + attributes: + label: Context + description: How you run MarkIt, what you already tried, and any relevant config (no secrets). + placeholder: | + - Setup: local / Docker + - Already tried: … + validations: + required: false + + - type: dropdown + id: area + attributes: + label: Area (optional) + options: + - Unsure + - Landing + - Dashboard + - Teams + - Boards + - Phrases + - Play + - Realtime (Socket.io) + - Admin + - Auth + - Account + - UI + - Infra (Docker / CI / Postgres) + validations: + required: false diff --git a/.github/labels.yml b/.github/labels.yml new file mode 100644 index 0000000..ae21c5d --- /dev/null +++ b/.github/labels.yml @@ -0,0 +1,121 @@ +# MarkIt GitHub issue/PR labels. +# Sync with: npm run sync:labels +# +# Taxonomy (pick what applies): +# - type: bug | enhancement | documentation | question | security | performance | maintenance +# - priority: critical | high | medium | low +# - area: product surface impacted +# - triage / community: GitHub defaults + needs triage +# - bot: Dependabot / release labels (kept for tooling) + +# --- Type --- +- name: bug + color: "d73a4a" + description: Something isn't working +- name: enhancement + color: "a2eeef" + description: New feature or improvement to existing behaviour +- name: documentation + color: "0075ca" + description: Improvements or additions to documentation +- name: question + color: "d876e3" + description: Further information is requested +- name: security + color: "b60205" + description: Security hardening (public only — see SECURITY.md for vulns) +- name: performance + color: "f9d0c4" + description: Performance improvement +- name: maintenance + color: "cfd3d7" + description: Refactor, tests, tooling, or non-user-facing upkeep + +# --- Priority --- +- name: "priority: critical" + color: "b60205" + description: Blocks release, data loss, or security incident +- name: "priority: high" + color: "d93f0b" + description: Important — resolve in the current work cycle +- name: "priority: medium" + color: "fbca04" + description: Normal priority — schedule soon +- name: "priority: low" + color: "0e8a16" + description: Nice to have — no time pressure + +# --- Area (product surfaces) --- +- name: "area: landing" + color: "1d76db" + description: Public landing / marketing page +- name: "area: dashboard" + color: "1d76db" + description: Home dashboard and team list +- name: "area: teams" + color: "1d76db" + description: Team create/join, invite codes, roles +- name: "area: boards" + color: "1d76db" + description: Bingo board creation, size, FREE cell, history +- name: "area: phrases" + color: "1d76db" + description: Phrase bank and custom phrases +- name: "area: play" + color: "1d76db" + description: Live play board, cell checks, bingo / confetti +- name: "area: realtime" + color: "1d76db" + description: Socket.io sync and live presence +- name: "area: admin" + color: "1d76db" + description: Admin users and invite codes +- name: "area: auth" + color: "1d76db" + description: Sign-in, sign-up, sessions, NextAuth +- name: "area: account" + color: "1d76db" + description: User account settings / password change +- name: "area: ui" + color: "5319e7" + description: Cross-cutting UI / UX / accessibility +- name: "area: infra" + color: "000000" + description: Docker, CI, Postgres, release, Node ops + +# --- Triage / community --- +- name: "needs triage" + color: "ededed" + description: New issue awaiting type, priority, and area +- name: duplicate + color: "cfd3d7" + description: This issue or pull request already exists +- name: invalid + color: "e4e669" + description: This doesn't seem right +- name: wontfix + color: "ffffff" + description: This will not be worked on +- name: "good first issue" + color: "7057ff" + description: Good for newcomers +- name: "help wanted" + color: "008672" + description: Extra attention is needed + +# --- Bot / tooling (Dependabot & releases) --- +- name: dependencies + color: "0366d6" + description: Pull requests that update a dependency file +- name: github-actions + color: "000000" + description: Pull requests that update GitHub Actions code +- name: javascript + color: "168700" + description: Pull requests that update javascript code +- name: docker + color: "21ceff" + description: Pull requests that update docker code +- name: released + color: "ededed" + description: Included in a published release diff --git a/package.json b/package.json index 64a5ac2..abd6d78 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "db:migrate": "prisma migrate dev", "db:migrate:deploy": "prisma migrate deploy", "db:studio": "prisma studio", + "sync:labels": "node scripts/sync-github-labels.mjs", "prepare": "husky", "release": "semantic-release" }, diff --git a/scripts/sync-github-labels.mjs b/scripts/sync-github-labels.mjs new file mode 100644 index 0000000..68a5d92 --- /dev/null +++ b/scripts/sync-github-labels.mjs @@ -0,0 +1,148 @@ +/** + * Sync GitHub labels from `.github/labels.yml` (create or update). + * Requires `gh` authenticated against the current repo remote. + * + * Usage: node scripts/sync-github-labels.mjs + */ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { spawnSync } from "node:child_process"; +import { getLogger } from "./lib/logger.mjs"; + +const log = getLogger("sync-github-labels"); +const root = path.join(path.dirname(fileURLToPath(import.meta.url)), ".."); +const labelsPath = path.join(root, ".github", "labels.yml"); + +/** + * Minimal YAML list parser for `{ name, color, description }` entries. + * @param {string} source + * @returns {{ name: string, color: string, description: string }[]} + */ +function parseLabelsYaml(source) { + /** @type {{ name: string, color: string, description: string }[]} */ + const labels = []; + /** @type {{ name?: string, color?: string, description?: string } | null} */ + let current = null; + + const flush = () => { + if (!current) return; + if (!current.name || !current.color || current.description === undefined) { + throw new Error( + `Label incomplet: ${JSON.stringify(current)} (name, color, description requis)` + ); + } + labels.push({ + name: current.name, + color: current.color.replace(/^#/, ""), + description: current.description, + }); + current = null; + }; + + for (const rawLine of source.split(/\r?\n/)) { + const line = rawLine.replace(/\s+#.*$/, "").trimEnd(); + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith("#")) continue; + + const itemMatch = trimmed.match(/^- name:\s*(.+)$/); + if (itemMatch) { + flush(); + current = { name: unquote(itemMatch[1].trim()) }; + continue; + } + + if (!current) { + throw new Error(`Ligne hors entrée label: ${trimmed}`); + } + + const fieldMatch = trimmed.match(/^(color|description):\s*(.*)$/); + if (!fieldMatch) { + throw new Error(`Champ inconnu: ${trimmed}`); + } + current[/** @type {'color'|'description'} */ (fieldMatch[1])] = unquote( + fieldMatch[2].trim() + ); + } + + flush(); + return labels; +} + +/** @param {string} value */ +function unquote(value) { + if ( + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) + ) { + return value.slice(1, -1); + } + return value; +} + +function requireGh() { + const version = spawnSync("gh", ["--version"], { encoding: "utf8" }); + if (version.status !== 0) { + log.error({ msg: "gh CLI introuvable — installez GitHub CLI" }); + process.exit(1); + } +} + +/** + * @param {string[]} args + * @returns {{ status: number | null, stdout: string, stderr: string }} + */ +function runGh(args) { + const result = spawnSync("gh", args, { encoding: "utf8" }); + return { + status: result.status, + stdout: result.stdout ?? "", + stderr: result.stderr ?? "", + }; +} + +const source = fs.readFileSync(labelsPath, "utf8"); +const labels = parseLabelsYaml(source); + +if (labels.length === 0) { + log.error({ msg: "Aucun label dans .github/labels.yml" }); + process.exit(1); +} + +requireGh(); + +let synced = 0; +let failed = 0; + +for (const label of labels) { + const result = runGh([ + "label", + "create", + label.name, + "--color", + label.color, + "--description", + label.description, + "--force", + ]); + + if (result.status === 0) { + synced += 1; + log.info({ msg: "Label synchronisé", name: label.name, color: label.color }); + continue; + } + + failed += 1; + log.error({ + msg: "Échec sync label", + name: label.name, + stderr: result.stderr.trim() || result.stdout.trim(), + }); +} + +if (failed > 0) { + log.error({ msg: "Sync terminée avec erreurs", ok: synced, failed }); + process.exit(1); +} + +log.info({ msg: "Labels GitHub synchronisés", count: synced });