Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Bug report
description: Report a reproducible problem in a Flutter Forge package or tool.
title: 'fix: '
body:
- type: input
id: package
attributes:
label: Package and version
placeholder: 'nonstop_cli 0.0.8+2'
validations:
required: true
- type: textarea
id: environment
attributes:
label: Environment
description: Include Flutter/Dart versions, operating system and affected platforms.
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Steps and minimal reproduction
description: Provide steps and a minimal code example or public reproduction repository. Remove credentials and private data.
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected behavior
validations:
required: true
- type: textarea
id: actual
attributes:
label: Actual behavior and logs
description: Include the complete relevant error or stack trace, with secrets removed.
validations:
required: true
- type: checkboxes
id: checks
attributes:
label: Before submitting
options:
- label: I searched existing issues for this problem.
required: true
- label: This is not a security vulnerability (see SECURITY.md for private reporting).
required: true
8 changes: 8 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
blank_issues_enabled: true
contact_links:
- name: Report a security vulnerability privately
url: https://github.com/nonstopio/flutter_forge/blob/main/SECURITY.md
about: Follow the private reporting instructions; do not open a public vulnerability issue.
- name: Release setup and troubleshooting
url: https://github.com/nonstopio/flutter_forge/blob/main/docs/RELEASING.md
about: Authentication, release commands, and recovering a failed publication.
29 changes: 29 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Feature request
description: Suggest an improvement to a package, plugin or development tool.
title: 'feat: '
body:
- type: input
id: package
attributes:
label: Affected package or tool
validations:
required: true
- type: textarea
id: problem
attributes:
label: What problem would this solve?
description: Describe the use case and the current limitation.
validations:
required: true
- type: textarea
id: proposal
attributes:
label: Proposed behavior
description: Include a small API or usage example if useful.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives and compatibility
description: Mention workarounds, alternatives, and any expected breaking changes.
20 changes: 20 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## What changes?

Describe the problem and resulting behavior. Link the related issue (for example, `Fixes #123`).

## Validation

List the commands you ran and their results. For UI changes, include screenshots.

## Compatibility

List affected packages, breaking changes and migration steps, or write “None”.

## Checklist

- [ ] The PR title follows Conventional Commits, for example `fix(ns_utils): handle empty input`.
- [ ] Relevant analysis and tests pass; behavior changes have appropriate tests.
- [ ] Documentation/examples are updated where needed.
- [ ] Package versions and changelogs are left to Melos.
- [ ] New public packages include a README, LICENSE, tests and release setup from `docs/RELEASING.md`.
- [ ] No credentials, private data or generated build output are included.
12 changes: 12 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: monthly
open-pull-requests-limit: 5
commit-message:
prefix: chore
groups:
github-actions:
patterns: ['*']
62 changes: 62 additions & 0 deletions .github/scripts/release-failure.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const failures = new Set(['failure', 'timed_out', 'cancelled', 'action_required', 'startup_failure']);

export function diagnostic(text) {
return String(text)
.replace(/\x1b\[[0-9;]*m/g, '')
.replace(/(?:gh[pousr]_[A-Za-z0-9_]+|github_pat_[A-Za-z0-9_]+|eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)/g, '[REDACTED]')
.replace(/(authorization\s*[:=]\s*(?:bearer|basic)\s+)\S+/gi, '$1[REDACTED]')
.replace(/(https?:\/\/)[^\s/@]+:[^\s/@]+@/g, '$1[REDACTED]@')
.replace(/`/g, "'")
.replace(/@/g, '@\u200b')
.slice(-7000);
}

export async function reportFailure({ github, context, core }) {
const run = context.payload.workflow_run;
if (!failures.has(run.conclusion)) return;
const repo = context.repo;
const marker = `<!-- pub-release-run:${run.id} -->`;
const body = [marker, `## ${run.name} failed`, '',
`- Run: [${run.id}, attempt ${run.run_attempt}](${run.html_url})`,
`- Commit: \`${run.head_sha}\``, `- Ref: \`${diagnostic(run.head_branch)}\``,
`- Result: **${run.conclusion}**`, '', '### Failure details', ''];
try {
const jobs = await github.paginate(github.rest.actions.listJobsForWorkflowRunAttempt, {
...repo, run_id: run.id, attempt_number: run.run_attempt, per_page: 100,
});
const failed = jobs.filter((job) => failures.has(job.conclusion));
if (!failed.length) body.push('No failed job details are available. The run may have failed during startup; see the run link for GitHub’s diagnostic.');
for (const job of failed) {
body.push(`#### [${diagnostic(job.name)}](${job.html_url}) — ${job.conclusion}`);
for (const step of job.steps || []) if (failures.has(step.conclusion)) body.push(`- Step ${step.number}: **${diagnostic(step.name)}** (${step.conclusion})`);
try {
const checkId = Number(job.check_run_url?.split('/').at(-1));
if (checkId) {
const annotations = await github.paginate(github.rest.checks.listAnnotations, { ...repo, check_run_id: checkId, per_page: 100 });
for (const annotation of annotations.filter((item) => item.annotation_level === 'failure').slice(0, 5)) {
body.push(`\n\`\`\`text\n${diagnostic(annotation.message)}\n\`\`\``);
}
}
const logs = await github.rest.actions.downloadJobLogsForWorkflowRun({ ...repo, job_id: job.id });
const text = typeof logs.data === 'string' ? logs.data : Buffer.from(logs.data).toString('utf8');
body.push(`\n<details><summary>Job log tail (GitHub-masked)</summary>\n\n\`\`\`text\n${diagnostic(text.split('\n').slice(-45).join('\n'))}\n\`\`\`\n</details>`);
} catch (error) {
core.warning(`Could not retrieve some diagnostics for job ${job.id}: ${error.message}`);
body.push('Some annotations/logs were unavailable. Open the job link above for the full error.');
}
}
} catch (error) {
core.warning(`Could not list failed jobs: ${error.message}`);
body.push('GitHub did not return job details. Open the run link above to inspect its startup or runner error.');
}
// Leave room under GitHub's issue body limit even for many failed jobs.
const details = body.join('\n').slice(0, 55_000);
const finalBody = `${details}\n\n### Recovery\n\nFix the reported cause, then follow [release recovery](https://github.com/${repo.owner}/${repo.repo}/blob/main/docs/RELEASING.md#recover-a-failed-release). Already published versions are skipped on retry. Never move a published tag or reuse a version.\n`;
const issues = await github.paginate(github.rest.issues.listForRepo, { ...repo, state: 'all', creator: 'github-actions[bot]', per_page: 100 });
const existing = issues.find((issue) => !issue.pull_request && issue.body?.includes(marker));
const fields = { ...repo, title: `[release] ${run.name} failed (run ${run.id})`, body: finalBody };
const result = existing
? await github.rest.issues.update({ ...fields, issue_number: existing.number, state: 'open' })
: await github.rest.issues.create(fields);
core.notice(`Release failure issue: ${result.data.html_url}`);
}
46 changes: 46 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Publish to pub.dev

on:
push:
tags: ['*-v[0-9]*']

permissions:
contents: read

concurrency:
group: publish-${{ github.ref }}
cancel-in-progress: false

jobs:
publish:
if: github.event.deleted == false
runs-on: ubuntu-latest
timeout-minutes: 45
environment: pub.dev
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version: '22'
# setup-dart configures short-lived pub.dev OIDC credentials. Install
# Flutter afterward so all commands use Flutter's bundled Dart SDK.
- name: Authenticate to pub.dev with OIDC
uses: dart-lang/setup-dart@v1
with:
sdk: '3.13.4'
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.47.5'
cache: true
- name: Resolve workspace tools
run: flutter pub get --enforce-lockfile
- name: Validate and publish tagged package
env:
RELEASE_TAG: ${{ github.ref_name }}
run: node tools/release/release.mjs publish "$RELEASE_TAG" --execute
37 changes: 37 additions & 0 deletions .github/workflows/release-failure.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Report release failure

on:
workflow_run:
workflows: ['Prepare pub.dev release', 'Publish to pub.dev']
types: [completed]

permissions:
contents: read
actions: read
checks: read
issues: write

concurrency:
group: release-report-${{ github.event.workflow_run.id }}
cancel-in-progress: false

jobs:
report:
if: >-
github.event.workflow_run.repository.full_name == github.repository &&
(github.event.workflow_run.event == 'push' || github.event.workflow_run.event == 'workflow_dispatch') &&
contains(fromJSON('["failure", "timed_out", "cancelled", "action_required", "startup_failure"]'), github.event.workflow_run.conclusion)
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
# workflow_run is privileged: execute only the default branch's reporter,
# never scripts/artifacts checked out from the failed run's head SHA.
- uses: actions/checkout@v4
with:
ref: ${{ github.event.repository.default_branch }}
persist-credentials: false
- uses: actions/github-script@v7
with:
script: |
const { reportFailure } = await import(`${process.env.GITHUB_WORKSPACE}/.github/scripts/release-failure.mjs`);
await reportFailure({ github, context, core });
25 changes: 25 additions & 0 deletions .github/workflows/release-tooling.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Release tooling

on:
pull_request:
paths: ['tools/release/**', '.github/scripts/**', '.github/workflows/**', '.github/ISSUE_TEMPLATE/**']
push:
branches: [main]
paths: ['tools/release/**', '.github/scripts/**', '.github/workflows/**', '.github/ISSUE_TEMPLATE/**']

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
- run: node --test tools/release/*.test.mjs
- name: Validate GitHub Actions workflows
# actionlint also checks expressions, action inputs, and embedded shell.
run: |
docker run --rm -v "$PWD:/repo" -w /repo rhysd/actionlint:1.7.7 -color .github/workflows/release.yml .github/workflows/publish.yml .github/workflows/release-failure.yml .github/workflows/release-tooling.yml
71 changes: 71 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: Prepare pub.dev release

on:
push:
branches: [main]
workflow_dispatch:
inputs:
recovery_sha:
description: 'Optional full release commit SHA: retry only missing tags'
type: string
required: false

permissions:
contents: read

concurrency:
group: release-main
cancel-in-progress: false

jobs:
prepare:
# The App's version commit must not recursively version the workspace.
if: >-
github.event_name == 'workflow_dispatch' ||
!startsWith(github.event.head_commit.message, 'chore(release): publish packages')
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Create release App token
id: app
uses: actions/create-github-app-token@v2
with:
app-id: ${{ vars.RELEASE_APP_ID }}
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
permission-contents: write
- uses: actions/checkout@v4
with:
# Read main when this serialized job starts, so queued merges accumulate.
ref: main
fetch-depth: 0
token: ${{ steps.app.outputs.token }}
- uses: actions/setup-node@v4
with:
node-version: '22'
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.47.5'
cache: true
- name: Install release tools
run: |
flutter pub get --enforce-lockfile
dart run melos --version
- name: Configure release author
run: |
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
- name: Test release tooling
env:
RELEASE_INTEGRATION: '1'
run: node --test tools/release/*.test.mjs
- name: Version and validate changed packages
if: inputs.recovery_sha == ''
run: node tools/release/release.mjs prepare
- name: Push release commit and individual tags
if: inputs.recovery_sha == ''
run: node tools/release/release.mjs push
- name: Recover missing release tags
if: inputs.recovery_sha != ''
env:
RECOVERY_SHA: ${{ inputs.recovery_sha }}
run: node tools/release/release.mjs push-tags "$RECOVERY_SHA"
17 changes: 17 additions & 0 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Code of conduct

We welcome contributors with different backgrounds and levels of experience.
Treat people respectfully, give constructive feedback, and focus disagreement
on ideas and code. Respect privacy and credit others' work.

Harassment, discriminatory language, threats, personal attacks, sexualized
content, and sharing private information without consent are not acceptable.
These expectations apply to this repository's issues, pull requests and other
project spaces.

Report concerns privately to [ajay.kumar@nonstopio.com](mailto:ajay.kumar@nonstopio.com),
the maintainer contact listed in the repository metadata. Include links and
context where possible. Maintainers will review reports, protect reporter
privacy as far as possible, and may remove content, limit participation, or
exclude contributors according to the circumstances. Maintainers are expected
to apply these standards fairly.
Loading
Loading