From 425c61de7c5c6cb90863e3b933b6a1b1173d5c31 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 6 Jul 2026 22:15:27 +0200 Subject: [PATCH 01/21] chore: monorepo tooling: pnpm workspace, turbo scripts, CI regroup, docs Companion to the pure-rename commit; together they convert the repo into a pnpm monorepo with the app living in apps/app (@aragon/app, version and package name unchanged). - pnpm-workspace.yaml: packages [apps/*, packages/*]; lockfile regenerated - new root package.json (no version): tooling deps (turbo, biome, ultracite, husky, lint-staged, changesets, vercel) and proxy scripts via `turbo run` / `pnpm --filter @aragon/app`, so root `pnpm type-check|lint|test|e2e|setup` keep working and CI needs no rewiring - turbo.json: dev/build/lint:check/test:coverage tasks added - workflows regrouped per app: app-release-v2 -> app-release-start, release-pr-* -> app-release-pr-*, hotfix-start -> app-hotfix-start, slot-docs -> app-slot-docs; legacy app-release.yml deleted (superseded by the release-PR flow) - CI paths: 1Password secrets to apps/app/.env, playwright/synpress steps get working-directory, release flows read version/CHANGELOG from apps/app, slot-docs filters prefixed - biome/.changeset/.gitignore globs point at apps/app; jest drops agent-hook ignore patterns; guardrails scripts stay at the root - rule-skill globs prefixed with apps/app/ (matched repo-root-relative) - AGENTS.md monorepo section, new root README.md, MIGRATION.md with a verified branch-migration recipe (incl. post-rebase leftovers step: git silently leaves new files in new subdirs at old paths) - .git-blame-ignore-revs: the pure-rename commit --- .agents/shared/skills/rules/README.md | 2 +- .../skills/rules/core-action-decoded-input.md | 2 +- .../shared/skills/rules/dialog-conventions.md | 2 +- .../skills/rules/error-and-monitoring.md | 2 +- .../skills/rules/plugin-slot-registration.md | 2 +- .../shared/skills/rules/plugin-visibility.md | 2 +- .../shared/skills/rules/query-and-cache.md | 2 +- .agents/shared/skills/rules/rule-authoring.md | 2 +- .changeset/monorepo-skeleton.md | 5 + .git-blame-ignore-revs | 1 + .github/actions/changeset-version/action.yml | 2 +- .github/actions/read-changelog/action.yml | 2 +- ...{hotfix-start.yml => app-hotfix-start.yml} | 35 +-- .github/workflows/app-production.yml | 8 +- ...r-cancel.yml => app-release-pr-cancel.yml} | 8 +- ...r-deploy.yml => app-release-pr-deploy.yml} | 12 +- ...nalize.yml => app-release-pr-finalize.yml} | 28 +- ...p-release-v2.yml => app-release-start.yml} | 24 +- .github/workflows/app-release.yml | 87 ------- .github/workflows/app-rollback.yml | 2 +- .../{slot-docs.yml => app-slot-docs.yml} | 13 +- .github/workflows/shared-bv.yml | 15 +- .github/workflows/shared-deploy.yml | 22 +- .github/workflows/shared-e2e.yml | 10 +- .gitignore | 23 +- .lintstagedrc.json | 2 +- AGENTS.md | 37 ++- MIGRATION.md | 74 ++++++ README.md | 29 +++ apps/app/docs/projectDocs/release-process.md | 28 +- apps/app/docs/slots/_metrics.md | 2 +- apps/app/jest.config.js | 7 +- apps/app/next.config.mjs | 3 + apps/app/package.json | 245 +++++++++--------- apps/app/scripts/generateSlotDocs.mjs | 6 +- apps/app/tsconfig.json | 120 ++++----- apps/app/vercel.json | 4 +- biome.json | 12 +- package.json | 39 +++ pnpm-lock.yaml | 132 +++++----- pnpm-workspace.yaml | 4 + turbo.json | 34 ++- 42 files changed, 620 insertions(+), 471 deletions(-) create mode 100644 .changeset/monorepo-skeleton.md rename .github/workflows/{hotfix-start.yml => app-hotfix-start.yml} (90%) rename .github/workflows/{release-pr-cancel.yml => app-release-pr-cancel.yml} (93%) rename .github/workflows/{release-pr-deploy.yml => app-release-pr-deploy.yml} (97%) rename .github/workflows/{release-pr-finalize.yml => app-release-pr-finalize.yml} (83%) rename .github/workflows/{app-release-v2.yml => app-release-start.yml} (83%) delete mode 100644 .github/workflows/app-release.yml rename .github/workflows/{slot-docs.yml => app-slot-docs.yml} (60%) create mode 100644 MIGRATION.md create mode 100644 README.md create mode 100644 package.json diff --git a/.agents/shared/skills/rules/README.md b/.agents/shared/skills/rules/README.md index b3947fbff7..5a8b693ea4 100644 --- a/.agents/shared/skills/rules/README.md +++ b/.agents/shared/skills/rules/README.md @@ -66,7 +66,7 @@ pnpm test:guardrails --- name: query-and-cache description: One-line summary of what the rule covers. -globs: src/**/api/**, src/**/queries/** +globs: apps/app/src/**/api/**, apps/app/src/**/queries/** kind: rule --- ``` diff --git a/.agents/shared/skills/rules/core-action-decoded-input.md b/.agents/shared/skills/rules/core-action-decoded-input.md index 640827a71c..e3aefe2abf 100644 --- a/.agents/shared/skills/rules/core-action-decoded-input.md +++ b/.agents/shared/skills/rules/core-action-decoded-input.md @@ -1,7 +1,7 @@ --- name: core-action-decoded-input description: Decoded core-action inputData is backend-guaranteed non-null; the `as unknown as` registry cast in *ActionDetails components is intentional, not a smell. -globs: src/actions/** +globs: apps/app/src/actions/** kind: rule --- diff --git a/.agents/shared/skills/rules/dialog-conventions.md b/.agents/shared/skills/rules/dialog-conventions.md index 4cf3ccf561..7e31a98481 100644 --- a/.agents/shared/skills/rules/dialog-conventions.md +++ b/.agents/shared/skills/rules/dialog-conventions.md @@ -1,7 +1,7 @@ --- name: dialog-conventions description: Dialog barrel + definitions wiring — dynamic-import the component, static-export the types, register by id through the dialogProvider. -globs: src/**/dialogs/*/index.ts, src/**/constants/*DialogsDefinitions.ts +globs: apps/app/src/**/dialogs/*/index.ts, apps/app/src/**/constants/*DialogsDefinitions.ts kind: rule --- diff --git a/.agents/shared/skills/rules/error-and-monitoring.md b/.agents/shared/skills/rules/error-and-monitoring.md index b4a1bdcb34..e89f60588a 100644 --- a/.agents/shared/skills/rules/error-and-monitoring.md +++ b/.agents/shared/skills/rules/error-and-monitoring.md @@ -1,7 +1,7 @@ --- name: error-and-monitoring description: How to report errors/info to Sentry — route through monitoringUtils, classify by what a failure IS, don't dress expected/external/not-found as bugs. -globs: src/shared/utils/monitoringUtils/**, src/shared/utils/errorUtils/**, src/shared/utils/responseUtils/**, src/shared/api/aragonBackendService/**, src/shared/components/transactionDialog/**, src/**/pageError/**, src/**/errorBoundary/**, src/**/globalError/**, src/**/error.tsx, src/**/global-error.tsx, src/**/*MetadataUtils/** +globs: apps/app/src/shared/utils/monitoringUtils/**, apps/app/src/shared/utils/errorUtils/**, apps/app/src/shared/utils/responseUtils/**, apps/app/src/shared/api/aragonBackendService/**, apps/app/src/shared/components/transactionDialog/**, apps/app/src/**/pageError/**, apps/app/src/**/errorBoundary/**, apps/app/src/**/globalError/**, apps/app/src/**/error.tsx, apps/app/src/**/global-error.tsx, apps/app/src/**/*MetadataUtils/** kind: rule --- diff --git a/.agents/shared/skills/rules/plugin-slot-registration.md b/.agents/shared/skills/rules/plugin-slot-registration.md index 53aff0d263..7b9900e67b 100644 --- a/.agents/shared/skills/rules/plugin-slot-registration.md +++ b/.agents/shared/skills/rules/plugin-slot-registration.md @@ -1,7 +1,7 @@ --- name: plugin-slot-registration description: Plugin index.ts wiring — registry chain, slot kinds, and the silent-failure modes that aren't visible from a single file. -globs: src/plugins/*/index.ts +globs: apps/app/src/plugins/*/index.ts kind: rule --- diff --git a/.agents/shared/skills/rules/plugin-visibility.md b/.agents/shared/skills/rules/plugin-visibility.md index cea1fb114c..d502361cb8 100644 --- a/.agents/shared/skills/rules/plugin-visibility.md +++ b/.agents/shared/skills/rules/plugin-visibility.md @@ -1,7 +1,7 @@ --- name: plugin-visibility description: Plugin visibility (CMS pluginsToHide) is presentation-only — never filter a list you then look up by address/slug/type. -globs: src/shared/hooks/useDaoPlugins/**, src/shared/utils/daoVisibilityUtils/**, src/plugins/*/hooks/*NormalizeActions/**, src/modules/governance/dialogs/selectPluginDialog/** +globs: apps/app/src/shared/hooks/useDaoPlugins/**, apps/app/src/shared/utils/daoVisibilityUtils/**, apps/app/src/plugins/*/hooks/*NormalizeActions/**, apps/app/src/modules/governance/dialogs/selectPluginDialog/** kind: rule --- diff --git a/.agents/shared/skills/rules/query-and-cache.md b/.agents/shared/skills/rules/query-and-cache.md index d1d106899f..2f08336024 100644 --- a/.agents/shared/skills/rules/query-and-cache.md +++ b/.agents/shared/skills/rules/query-and-cache.md @@ -1,7 +1,7 @@ --- name: query-and-cache description: TanStack Query conventions — query key factories, cache invalidation, prefetch parity, server vs client reads. -globs: src/**/api/**, src/**/queries/**, src/**/mutations/** +globs: apps/app/src/**/api/**, apps/app/src/**/queries/**, apps/app/src/**/mutations/** kind: rule --- diff --git a/.agents/shared/skills/rules/rule-authoring.md b/.agents/shared/skills/rules/rule-authoring.md index aa6e687ef5..39a706b3d3 100644 --- a/.agents/shared/skills/rules/rule-authoring.md +++ b/.agents/shared/skills/rules/rule-authoring.md @@ -47,7 +47,7 @@ Don't write rules speculatively — empty rules atrophy and the routing cost out --- name: short-kebab-id description: One sentence; what the rule covers. -globs: src/some/narrow/path/**, src/another/specific/glob/** +globs: apps/app/src/some/narrow/path/**, apps/app/src/another/specific/glob/** kind: rule --- ``` diff --git a/.changeset/monorepo-skeleton.md b/.changeset/monorepo-skeleton.md new file mode 100644 index 0000000000..c44c03655a --- /dev/null +++ b/.changeset/monorepo-skeleton.md @@ -0,0 +1,5 @@ +--- +'@aragon/app': patch +--- + +Restructure the repository into a pnpm monorepo: the app now lives in `apps/app`, shared tooling (turbo, biome, husky, changesets) stays at the workspace root. No runtime changes. diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 6792c5c78c..5d47ad3ece 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -1 +1,2 @@ 3b3d17d0e45057fb2451a7ccad01193c5a69105e +2b2cfe1ff84efb2f3f99bb6d46d708ea5f298adf diff --git a/.github/actions/changeset-version/action.yml b/.github/actions/changeset-version/action.yml index 976ee9ef5c..7c6f4be381 100644 --- a/.github/actions/changeset-version/action.yml +++ b/.github/actions/changeset-version/action.yml @@ -22,6 +22,6 @@ runs: set -euo pipefail pnpm changeset version if [ "$PRETTIER_CHANGELOG" = "true" ]; then - pnpm dlx prettier --write --prose-wrap never CHANGELOG.md + pnpm dlx prettier --write --prose-wrap never apps/app/CHANGELOG.md fi diff --git a/.github/actions/read-changelog/action.yml b/.github/actions/read-changelog/action.yml index fa0fe79cd5..3ed88a1ecb 100644 --- a/.github/actions/read-changelog/action.yml +++ b/.github/actions/read-changelog/action.yml @@ -8,7 +8,7 @@ inputs: path: description: Path to changelog file required: false - default: ./CHANGELOG.md + default: ./apps/app/CHANGELOG.md outputs: changes: diff --git a/.github/workflows/hotfix-start.yml b/.github/workflows/app-hotfix-start.yml similarity index 90% rename from .github/workflows/hotfix-start.yml rename to .github/workflows/app-hotfix-start.yml index d3bb303845..e3e9f433ed 100644 --- a/.github/workflows/hotfix-start.yml +++ b/.github/workflows/app-hotfix-start.yml @@ -1,18 +1,18 @@ -# Hotfix Start — branch from a release tag with optional cherry-picks from main. +# Hotfix Start — branch from an app release tag (@aragon/app@X.Y.Z) with optional cherry-picks from main. # Supports resuming an existing hotfix branch via existing_hotfix_branch input. # If hotfix HEAD equals the base release tag, downstream jobs are skipped. # Order: unit tests → staging deploy → git tag (not gated on E2E) → smoke E2E parallel with tag. # Prod: run App Production workflow_dispatch with the hotfix tag. -name: Hotfix Start +name: App Hotfix Start on: workflow_dispatch: inputs: base_tag: description: > - Release tag to branch from (e.g. v1.2.0). Leave empty for latest tag. - Only needed when resuming a branch whose name doesn't match hotfix/vX.Y.Z_…. + Release tag to branch from (e.g. @aragon/app@1.2.0). Leave empty for the latest app tag. + Only needed when resuming a branch whose name doesn't match hotfix/app/X.Y.Z_…. required: false type: string commits: @@ -24,7 +24,7 @@ on: existing_hotfix_branch: description: > Continue an existing hotfix branch (after local commits/push). - Empty = create hotfix/_ from base_tag or latest tag. + Empty = create hotfix/app/_ from base_tag or the latest app tag. required: false type: string @@ -93,25 +93,30 @@ jobs: run: | set -euo pipefail git fetch --tags --force >/dev/null 2>&1 || true + TAG_PREFIX="@aragon/app@" - # --- resolve tag --- + # --- resolve tag (app tags only; other workspaces have their own prefix) --- if [ -n "$INPUT_TAG" ]; then - TAG="$INPUT_TAG" + case "$INPUT_TAG" in + "$TAG_PREFIX"*) TAG="$INPUT_TAG" ;; + *) echo "::error::base_tag must be a full app tag (e.g. ${TAG_PREFIX}1.2.0)."; exit 1 ;; + esac elif [ "$MODE" = "resume" ]; then - rest="${RESUME_BRANCH#hotfix/}" - [ "$rest" = "$RESUME_BRANCH" ] && { echo "::error::Branch must start with hotfix/ or provide base_tag."; exit 1; } - TAG="${rest%%_*}" - [ -z "$TAG" ] || [ "$TAG" = "$rest" ] && { echo "::error::Cannot parse tag from branch name; provide base_tag."; exit 1; } + rest="${RESUME_BRANCH#hotfix/app/}" + [ "$rest" = "$RESUME_BRANCH" ] && { echo "::error::Branch must start with hotfix/app/ or provide base_tag."; exit 1; } + VERSION="${rest%%_*}" + [ -z "$VERSION" ] || [ "$VERSION" = "$rest" ] && { echo "::error::Cannot parse version from branch name; provide base_tag."; exit 1; } + TAG="${TAG_PREFIX}${VERSION}" else - TAG="$(git describe --tags --abbrev=0 2>/dev/null || true)" - [ -z "$TAG" ] && { echo "::error::No tags found — provide base_tag."; exit 1; } + TAG="$(git describe --tags --abbrev=0 --match "${TAG_PREFIX}*" 2>/dev/null || true)" + [ -z "$TAG" ] && { echo "::error::No ${TAG_PREFIX}* tags found — provide base_tag."; exit 1; } fi - # --- resolve branch --- + # --- resolve branch (version only in the name; the app prefix is implied) --- if [ "$MODE" = "resume" ]; then BRANCH="$RESUME_BRANCH" else - BRANCH="hotfix/${TAG}_$(date +'%Y-%m-%d_%H-%M')" + BRANCH="hotfix/app/${TAG#"$TAG_PREFIX"}_$(date +'%Y-%m-%d_%H-%M')" fi echo "tag=$TAG" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/app-production.yml b/.github/workflows/app-production.yml index dbfce24a77..59888e776b 100644 --- a/.github/workflows/app-production.yml +++ b/.github/workflows/app-production.yml @@ -7,7 +7,7 @@ on: workflow_dispatch: inputs: tag: - description: "Release tag to deploy (e.g. v1.17.0)" + description: "Release tag to deploy (e.g. @aragon/app@1.17.0)" required: true type: string release: @@ -30,9 +30,11 @@ jobs: git ls-remote --tags --exit-code "${{ github.server_url }}/${{ github.repository }}.git" "refs/tags/${{ inputs.tag }}" || \ (echo "::error::Tag '${{ inputs.tag }}' does not exist" && exit 1) + # On release events, only react to app tags (@aragon/app@…) — other workspaces + # (future apps/packages) publish their own releases with their own prefixes. notify-start: needs: validate - if: ${{ !cancelled() && (needs.validate.result == 'success' || needs.validate.result == 'skipped') }} + if: ${{ !cancelled() && (needs.validate.result == 'success' || needs.validate.result == 'skipped') && (github.event_name != 'release' || startsWith(github.event.release.tag_name, '@aragon/app@')) }} runs-on: ubuntu-latest outputs: slack-ts: ${{ steps.set-ts.outputs.ts }} @@ -152,7 +154,7 @@ jobs: deploy: needs: [validate, notify-start] - if: ${{ !cancelled() && (needs.validate.result == 'success' || needs.validate.result == 'skipped') }} + if: ${{ !cancelled() && (needs.validate.result == 'success' || needs.validate.result == 'skipped') && (github.event_name != 'release' || startsWith(github.event.release.tag_name, '@aragon/app@')) }} uses: ./.github/workflows/shared-deploy.yml secrets: inherit with: diff --git a/.github/workflows/release-pr-cancel.yml b/.github/workflows/app-release-pr-cancel.yml similarity index 93% rename from .github/workflows/release-pr-cancel.yml rename to .github/workflows/app-release-pr-cancel.yml index 304dc952d6..a925e79b7e 100644 --- a/.github/workflows/release-pr-cancel.yml +++ b/.github/workflows/app-release-pr-cancel.yml @@ -1,6 +1,6 @@ -# Release PR Cancel — notifies Slack when a release PR is closed without merge. +# App Release PR Cancel — notifies Slack when a release PR is closed without merge. -name: Release PR Cancel +name: App Release PR Cancel on: pull_request: @@ -13,7 +13,7 @@ permissions: jobs: notify: - if: github.event.pull_request.merged == false && startsWith(github.event.pull_request.head.ref, 'release/') + if: github.event.pull_request.merged == false && startsWith(github.event.pull_request.head.ref, 'release/app/') runs-on: ubuntu-latest steps: - name: Load secrets @@ -55,7 +55,7 @@ jobs: - name: Extract version from PR title id: version run: | - VERSION=$(echo "${PR_TITLE}" | sed -nE 's/^Release (v[0-9]+\.[0-9]+\.[0-9]+).*$/\1/p') + VERSION=$(echo "${PR_TITLE}" | sed -nE 's#^Release (@aragon/app@[0-9]+\.[0-9]+\.[0-9]+).*$#\1#p') echo "name=${VERSION}" >> "$GITHUB_OUTPUT" env: PR_TITLE: ${{ github.event.pull_request.title }} diff --git a/.github/workflows/release-pr-deploy.yml b/.github/workflows/app-release-pr-deploy.yml similarity index 97% rename from .github/workflows/release-pr-deploy.yml rename to .github/workflows/app-release-pr-deploy.yml index 4755be9218..bfc978df7b 100644 --- a/.github/workflows/release-pr-deploy.yml +++ b/.github/workflows/app-release-pr-deploy.yml @@ -1,4 +1,4 @@ -name: Release PR Flow +name: App Release PR Deploy on: pull_request: @@ -15,7 +15,7 @@ permissions: jobs: notify-start: - if: startsWith(github.head_ref, 'release/') + if: startsWith(github.head_ref, 'release/app/') runs-on: ubuntu-latest outputs: slack-ts: ${{ steps.notify.outputs.ts }} @@ -97,7 +97,7 @@ jobs: echo "ts=$TS" >> "$GITHUB_OUTPUT" test: - if: startsWith(github.head_ref, 'release/') + if: startsWith(github.head_ref, 'release/app/') needs: notify-start runs-on: ubuntu-latest steps: @@ -123,7 +123,7 @@ jobs: run: pnpm test:coverage deploy: - if: startsWith(github.head_ref, 'release/') + if: startsWith(github.head_ref, 'release/app/') needs: test uses: ./.github/workflows/shared-deploy.yml secrets: inherit @@ -197,7 +197,7 @@ jobs: • All good? Approve the PR and merge → production deploy will start automatically e2e: - if: startsWith(github.head_ref, 'release/') + if: startsWith(github.head_ref, 'release/app/') needs: deploy uses: ./.github/workflows/shared-e2e.yml secrets: inherit @@ -206,7 +206,7 @@ jobs: environment: staging bv: - if: startsWith(github.head_ref, 'release/') + if: startsWith(github.head_ref, 'release/app/') needs: deploy uses: ./.github/workflows/shared-bv.yml secrets: inherit diff --git a/.github/workflows/release-pr-finalize.yml b/.github/workflows/app-release-pr-finalize.yml similarity index 83% rename from .github/workflows/release-pr-finalize.yml rename to .github/workflows/app-release-pr-finalize.yml index c529b4a35f..56c6aea005 100644 --- a/.github/workflows/release-pr-finalize.yml +++ b/.github/workflows/app-release-pr-finalize.yml @@ -1,4 +1,4 @@ -name: Release PR Finalize +name: App Release PR Finalize on: pull_request: @@ -17,7 +17,7 @@ jobs: finalize: if: > github.event.pull_request.merged == true && - startsWith(github.event.pull_request.head.ref, 'release/') + startsWith(github.event.pull_request.head.ref, 'release/app/') runs-on: ubuntu-latest steps: - name: Checkout actions @@ -61,13 +61,25 @@ jobs: - name: Get version id: version uses: martinbeentjes/npm-get-version-action@3cf273023a0dda27efcd3164bdfb51908dd46a5b # v1.3.1 (https://github.com/martinbeentjes/npm-get-version-action/releases/tag/v1.3.1) + with: + path: apps/app + + # Per-package tag in the changesets-native format (@aragon/app@1.17.0). + # `encoded` is the URL-safe form for release links in Slack messages. + - name: Compute release tag + id: release-tag + env: + VERSION: ${{ steps.version.outputs.current-version }} + run: | + echo "name=@aragon/app@${VERSION}" >> $GITHUB_OUTPUT + echo "encoded=%40aragon%2Fapp%40${VERSION}" >> $GITHUB_OUTPUT - name: Read changelog id: changelog uses: ./.github/actions/read-changelog with: version: ${{ steps.version.outputs.current-version }} - path: ./CHANGELOG.md + path: ./apps/app/CHANGELOG.md - name: Read PR body id: pr @@ -104,15 +116,15 @@ jobs: id: tag uses: ./.github/actions/gh-ensure-tag with: - tag: v${{ steps.version.outputs.current-version }} + tag: ${{ steps.release-tag.outputs.name }} sha: ${{ github.event.pull_request.head.sha }} - name: Create GitHub Release id: release uses: ./.github/actions/gh-ensure-release with: - tag: v${{ steps.version.outputs.current-version }} - title: Release v${{ steps.version.outputs.current-version }} + tag: ${{ steps.release-tag.outputs.name }} + title: Release ${{ steps.release-tag.outputs.name }} notes_path: ${{ steps.notes.outputs.path }} token: ${{ steps.secrets.outputs.ARABOT_PAT }} @@ -125,7 +137,7 @@ jobs: thread_ts: ${{ steps.ts.outputs.ts }} message: | 📦 *GitHub Release Created!* - *Release:* + *Release:* _Production build & deploy will start automatically..._ @@ -137,6 +149,6 @@ jobs: slack_channel_id: ${{ steps.secrets.outputs.SLACK_CHANNEL_ID }} message: | 📦 *GitHub Release Created!* - *Release:* + *Release:* _Production build & deploy will start automatically..._ diff --git a/.github/workflows/app-release-v2.yml b/.github/workflows/app-release-start.yml similarity index 83% rename from .github/workflows/app-release-v2.yml rename to .github/workflows/app-release-start.yml index d1399a2974..c21d764dca 100644 --- a/.github/workflows/app-release-v2.yml +++ b/.github/workflows/app-release-start.yml @@ -1,4 +1,4 @@ -name: App Release (New) +name: App Release Start on: workflow_dispatch: @@ -58,7 +58,7 @@ jobs: - name: Generate Release Branch Name id: branch-name - run: echo "name=release/$(date +'%Y-%m-%d_%H-%M')" >> $GITHUB_OUTPUT + run: echo "name=release/app/$(date +'%Y-%m-%d_%H-%M')" >> $GITHUB_OUTPUT - name: Ensure Release Branch uses: ./.github/actions/git-ensure-branch @@ -81,11 +81,19 @@ jobs: - name: Get package version id: package-version uses: martinbeentjes/npm-get-version-action@3cf273023a0dda27efcd3164bdfb51908dd46a5b # v1.3.1 (https://github.com/martinbeentjes/npm-get-version-action/releases/tag/v1.3.1) + with: + path: apps/app + + # Per-package tag in the changesets-native format: @aragon/app@1.17.0. Other + # workspaces (future apps/packages) get their own prefix and release flows. + - name: Compute release tag + id: release-tag + run: echo "name=@aragon/app@${{ steps.package-version.outputs.current-version }}" >> $GITHUB_OUTPUT - name: Commit changes run: | git add --all - git commit -am "Release v${{ steps.package-version.outputs.current-version}}" + git commit -am "Release ${{ steps.release-tag.outputs.name }}" git push origin ${{ steps.branch-name.outputs.name }} - name: Ensure Pull Request @@ -94,7 +102,7 @@ jobs: with: base: main head: ${{ steps.branch-name.outputs.name }} - title: Release v${{ steps.package-version.outputs.current-version }} + title: Release ${{ steps.release-tag.outputs.name }} body: ${{ steps.release-summary.outputs.summary }} token: ${{ steps.load-secrets.outputs.ARABOT_PAT }} @@ -105,8 +113,8 @@ jobs: with: access_key: ${{ steps.load-secrets.outputs.LINEAR_PIPELINE_ACCESS_KEY }} command: sync - name: v${{ steps.package-version.outputs.current-version }} - version: v${{ steps.package-version.outputs.current-version }} + name: ${{ steps.release-tag.outputs.name }} + version: ${{ steps.release-tag.outputs.name }} - name: Linear — set stage to in_progress if: steps.load-secrets.outputs.LINEAR_PIPELINE_ACCESS_KEY != '' @@ -115,7 +123,7 @@ jobs: with: access_key: ${{ steps.load-secrets.outputs.LINEAR_PIPELINE_ACCESS_KEY }} command: update - version: v${{ steps.package-version.outputs.current-version }} + version: ${{ steps.release-tag.outputs.name }} stage: in_progress - name: Notify Slack @@ -126,7 +134,7 @@ jobs: slack_bot_token: ${{ steps.load-secrets.outputs.SLACK_BOT_TOKEN }} slack_channel_id: ${{ steps.load-secrets.outputs.SLACK_CHANNEL_ID }} message: | - 🚀 *Release v${{ steps.package-version.outputs.current-version }} — Started* ${{ steps.load-secrets.outputs.SLACK_CODEOWNERS_GROUP_ID && format('', steps.load-secrets.outputs.SLACK_CODEOWNERS_GROUP_ID) || '' }} + 🚀 *Release ${{ steps.release-tag.outputs.name }} — Started* ${{ steps.load-secrets.outputs.SLACK_CODEOWNERS_GROUP_ID && format('', steps.load-secrets.outputs.SLACK_CODEOWNERS_GROUP_ID) || '' }} *PR:* ${{ steps.create-pr.outputs.url }} ${{ steps.release-summary.outputs.summary }} diff --git a/.github/workflows/app-release.yml b/.github/workflows/app-release.yml deleted file mode 100644 index d6d69ff62a..0000000000 --- a/.github/workflows/app-release.yml +++ /dev/null @@ -1,87 +0,0 @@ -# The "App Release" workflow is manually triggered to release the application to production. It updates the version and -# changelog, creates a new tag, commits the changes, and pushes them to the repository when triggered on the main branch. - -name: App Release - -on: - workflow_dispatch: - -concurrency: - group: app-release - cancel-in-progress: true - -permissions: - contents: write - -jobs: - release: - runs-on: ubuntu-latest - if: github.ref == 'refs/heads/main' - steps: - - name: Load secrets - id: load-secrets - uses: 1password/load-secrets-action@3a12b0ab99d9cd590a3e9b5a90ea017210ed9556 # v4.0.0 (https://github.com/1Password/load-secrets-action/releases/tag/v4.0.0) - with: - export-env: false - env: - OP_SERVICE_ACCOUNT_TOKEN: "${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}" - GPG_PASSPHRASE: op://kv_app_infra/arabot-1_SIGN_CERTS/credential - GPG_PRIVATE_KEY: op://kv_app_infra/arabot-1_SIGN_CERTS/private_key - ARABOT_PAT: op://kv_app_infra/ARABOT_PAT/credential - - - name: Checkout actions - uses: actions/checkout@v7.0.0 - with: - fetch-depth: 1 - sparse-checkout: | - .github/actions/setup - - - name: Setup - uses: ./.github/actions/setup - with: - token: ${{ steps.load-secrets.outputs.ARABOT_PAT }} - - - name: Import GPG key - uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0 (https://github.com/crazy-max/ghaction-import-gpg/releases/tag/v7.0.0) - with: - gpg_private_key: ${{ steps.load-secrets.outputs.GPG_PRIVATE_KEY }} - passphrase: ${{ steps.load-secrets.outputs.GPG_PASSPHRASE }} - git_user_signingkey: true - git_commit_gpgsign: true - - name: Update version and changelog - # Format CHANGELOG.md (used for Github release notes) to remove automatic formatting applied by changeset as - # Github does not support soft line breaks. - # (See https://github.com/changesets/changesets/issues/1452, https://github.com/orgs/community/discussions/10981) - # Note: Biome doesn't support Markdown formatting, so we use Prettier for this specific file. - run: | - pnpm changeset version - pnpm dlx prettier --write --prose-wrap never CHANGELOG.md - env: - GITHUB_TOKEN: ${{ steps.load-secrets.outputs.ARABOT_PAT }} - - name: Create tag - run: pnpm changeset tag - - name: Get package version - id: package-version - uses: martinbeentjes/npm-get-version-action@3cf273023a0dda27efcd3164bdfb51908dd46a5b # v1.3.1 (https://github.com/martinbeentjes/npm-get-version-action/releases/tag/v1.3.1) - - name: Commit changes - run: | - git add --all - git commit -am "Release v${{ steps.package-version.outputs.current-version}}" - git push --follow-tags - - name: Read changelog updates - id: read-changelog - uses: actions/github-script@v9.0.0 - env: - version: "${{ steps.package-version.outputs.current-version }}" - path: ./CHANGELOG.md - with: - script: | - const readChangelog = require('./.github/workflows/scripts/readChangelog.js'); - readChangelog({ github, context, core }); - - name: Create release - uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.0 (https://github.com/softprops/action-gh-release/releases/tag/v3.0.0) - with: - prerelease: false - tag_name: "v${{ steps.package-version.outputs.current-version}}" - body: ${{ steps.read-changelog.outputs.changes }} - token: ${{ steps.load-secrets.outputs.ARABOT_PAT }} diff --git a/.github/workflows/app-rollback.yml b/.github/workflows/app-rollback.yml index e81c1c9870..87aca2dc0c 100644 --- a/.github/workflows/app-rollback.yml +++ b/.github/workflows/app-rollback.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: inputs: tag: - description: 'Tag to rollback to (e.g., v1.16.0)' + description: 'Tag to rollback to (e.g., @aragon/app@1.16.0)' required: true type: string diff --git a/.github/workflows/slot-docs.yml b/.github/workflows/app-slot-docs.yml similarity index 60% rename from .github/workflows/slot-docs.yml rename to .github/workflows/app-slot-docs.yml index ba0cdc1e11..4fe4c9cebc 100644 --- a/.github/workflows/slot-docs.yml +++ b/.github/workflows/app-slot-docs.yml @@ -1,16 +1,16 @@ # Validates that generated slot documentation is up to date with source code. # Triggers on PRs that touch slot enums, plugin registrations, or the docs themselves. -name: Slot Docs +name: App Slot Docs on: pull_request: paths: - - 'src/modules/*/constants/moduleSlots.ts' - - 'src/modules/*/constants/moduleDaoSlots.ts' - - 'src/plugins/*/index.ts' - - 'docs/slots/**' - - 'scripts/generateSlotDocs.mjs' + - 'apps/app/src/modules/*/constants/moduleSlots.ts' + - 'apps/app/src/modules/*/constants/moduleDaoSlots.ts' + - 'apps/app/src/plugins/*/index.ts' + - 'apps/app/docs/slots/**' + - 'apps/app/scripts/generateSlotDocs.mjs' permissions: contents: read @@ -23,3 +23,4 @@ jobs: uses: actions/checkout@v7.0.0 - name: Validate slot docs are up to date run: node scripts/generateSlotDocs.mjs --check + working-directory: apps/app diff --git a/.github/workflows/shared-bv.yml b/.github/workflows/shared-bv.yml index 3088817103..d28bd78ed5 100644 --- a/.github/workflows/shared-bv.yml +++ b/.github/workflows/shared-bv.yml @@ -64,6 +64,7 @@ jobs: - name: Get Playwright version id: pw-version run: echo "version=$(pnpm exec playwright --version | awk '{print $2}')" >> "$GITHUB_OUTPUT" + working-directory: apps/app - name: Cache Playwright browsers id: pw-cache @@ -75,22 +76,25 @@ jobs: - name: Install Playwright browsers if: steps.pw-cache.outputs.cache-hit != 'true' run: pnpm e2e:install + working-directory: apps/app - name: Install Playwright OS deps if: steps.pw-cache.outputs.cache-hit == 'true' run: pnpm exec playwright install-deps chromium + working-directory: apps/app - name: Cache Synpress wallet id: synpress-cache uses: actions/cache@v6.1.0 with: - path: .cache-synpress - key: synpress-${{ runner.os }}-${{ hashFiles('e2e/setup/wallet.setup.ts', 'package.json') }} + path: apps/app/.cache-synpress + key: synpress-${{ runner.os }}-${{ hashFiles('apps/app/e2e/setup/wallet.setup.ts', 'apps/app/package.json') }} - name: Build Synpress wallet cache if: steps.synpress-cache.outputs.cache-hit != 'true' # xvfb-run: MetaMask extension requires a display for onboarding UI. run: xvfb-run pnpm exec synpress e2e/setup --force && node e2e/scripts/syncWalletSetupCache.mjs + working-directory: apps/app - name: Warmup deployment run: | @@ -108,6 +112,7 @@ jobs: id: bv continue-on-error: true run: xvfb-run pnpm e2e:bv + working-directory: apps/app env: E2E_BASE_URL: ${{ inputs.deployment-url }} @@ -116,7 +121,7 @@ jobs: if: always() uses: ./.github/actions/parse-playwright-results with: - report-path: e2e/test-results/results.json + report-path: apps/app/e2e/test-results/results.json step-outcome: ${{ steps.bv.outcome }} - name: Upload HTML report @@ -125,7 +130,7 @@ jobs: if: always() with: name: playwright-bv-report-${{ inputs.environment }} - path: e2e/playwright-report/ + path: apps/app/e2e/playwright-report/ retention-days: 14 - name: Add report link to job summary @@ -147,5 +152,5 @@ jobs: if: ${{ always() && steps.playwright-result.outputs.result != 'passed' }} with: name: playwright-bv-traces-${{ inputs.environment }} - path: e2e/test-results/ + path: apps/app/e2e/test-results/ retention-days: 7 diff --git a/.github/workflows/shared-deploy.yml b/.github/workflows/shared-deploy.yml index aef3f4f6b9..d5332d1890 100644 --- a/.github/workflows/shared-deploy.yml +++ b/.github/workflows/shared-deploy.yml @@ -2,7 +2,7 @@ # `prod` input. It retrieves the build artifacts, prepares secrets, bundles the Vercel application, and deploys it. # If a custom domain is specified, it assigns the deployment URL to that domain. -name: App Shared Deploy +name: Shared Deploy on: workflow_call: @@ -58,15 +58,33 @@ jobs: - name: Setup env file run: pnpm run setup ${{ inputs.env }} + working-directory: apps/app - name: Setup env file secrets uses: aragon/github-templates/steps/credential-retrieval@ed00481c78d797f591f19a417bb75c129af1ce1a # v0.4 (https://github.com/aragon/github-templates/releases/tag/v0.4) with: op-token: "${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}" op-vault: "kv_app_${{ inputs.env }}" - secret-filepath: ".env" + secret-filepath: "apps/app/.env" mode: alltofile + # Vercel never builds from git here: the artifact is built in this job and uploaded as-is + # (deploy --prebuilt). All vercel commands run from the repo root so Turbopack/Next treat the + # pnpm workspace root as the build root (pnpm symlinks point outside apps/app); project + # linking comes from VERCEL_ORG_ID/VERCEL_PROJECT_ID (1Password), independent of cwd. + - name: Pull Vercel project settings + run: pnpm vercel pull --yes --environment=${{ (inputs.env == 'production' && 'production') || 'preview' }} --token=${{ env.VERCEL_TOKEN }} --scope ${{ env.VERCEL_SCOPE }} + + # TEMPORARY workaround: the Vercel project has no Root Directory configured (pre-monorepo + # branches still run the CLI from the repo root where the app used to live). Set it locally + # instead: `vercel build` reads .vercel/project.json offline, builds apps/app and keeps the + # repo root as the workspace root for Turbopack and output file tracing. Once old-layout + # branches are gone, set Root Directory=apps/app in the Vercel dashboard and drop this step. + - name: Set Vercel root directory + run: | + jq '.settings.rootDirectory = "apps/app"' .vercel/project.json > .vercel/project.json.tmp + mv .vercel/project.json.tmp .vercel/project.json + - name: Bundle Vercel application run: pnpm vercel build ${{ env.VERCEL_ARGS }} --yes --token=${{ env.VERCEL_TOKEN }} --scope ${{ env.VERCEL_SCOPE }} diff --git a/.github/workflows/shared-e2e.yml b/.github/workflows/shared-e2e.yml index ea88eff797..cb94d225a4 100644 --- a/.github/workflows/shared-e2e.yml +++ b/.github/workflows/shared-e2e.yml @@ -55,6 +55,7 @@ jobs: - name: Get Playwright version id: pw-version run: echo "version=$(pnpm exec playwright --version | awk '{print $2}')" >> "$GITHUB_OUTPUT" + working-directory: apps/app - name: Cache Playwright browsers id: pw-cache @@ -66,10 +67,12 @@ jobs: - name: Install Playwright browsers if: steps.pw-cache.outputs.cache-hit != 'true' run: pnpm e2e:install + working-directory: apps/app - name: Install Playwright OS deps if: steps.pw-cache.outputs.cache-hit == 'true' run: pnpm exec playwright install-deps chromium + working-directory: apps/app - name: Warmup deployment run: | @@ -87,6 +90,7 @@ jobs: id: smoke continue-on-error: true run: pnpm e2e:smoke + working-directory: apps/app env: E2E_BASE_URL: ${{ inputs.deployment-url }} @@ -95,7 +99,7 @@ jobs: if: always() uses: ./.github/actions/parse-playwright-results with: - report-path: e2e/test-results/results.json + report-path: apps/app/e2e/test-results/results.json step-outcome: ${{ steps.smoke.outcome }} - name: Upload HTML report @@ -104,7 +108,7 @@ jobs: if: always() with: name: playwright-report-${{ inputs.environment }} - path: e2e/playwright-report/ + path: apps/app/e2e/playwright-report/ retention-days: 14 - name: Add report link to job summary @@ -126,5 +130,5 @@ jobs: if: ${{ always() && steps.playwright-result.outputs.result != 'passed' }} with: name: playwright-traces-${{ inputs.environment }} - path: e2e/test-results/ + path: apps/app/e2e/test-results/ retention-days: 7 diff --git a/.gitignore b/.gitignore index 2fbf822007..433cb514b1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,28 +1,27 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies -/node_modules +node_modules/ .pnp.* .pnpm-store # testing -/coverage +coverage/ # playwright .cache-synpress -/e2e/playwright-report/ -/e2e/test-results/ -/e2e/blob-report/ -/e2e/.last-run.json -/playwright-report/ -/test-results/ +playwright-report/ +test-results/ +blob-report/ +e2e/.last-run.json +apps/app/e2e/.last-run.json # next.js -/.next/ -/out/ +.next/ +out/ # production -/build +build/ # misc .DS_Store @@ -41,7 +40,7 @@ yarn-error.log* pnpm-debug.log* # local env files -/.env*.local +.env*.local .env # vercel diff --git a/.lintstagedrc.json b/.lintstagedrc.json index b0ce62f4c3..5848450563 100644 --- a/.lintstagedrc.json +++ b/.lintstagedrc.json @@ -1,5 +1,5 @@ { - "*.{ts,tsx,js,jsx,json,css}": [ + "*.{ts,tsx,mts,cts,js,jsx,mjs,cjs,json,css}": [ "biome check --write --no-errors-on-unmatched --files-ignore-unknown=true" ] } diff --git a/AGENTS.md b/AGENTS.md index def9c5055e..6d698764f1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,16 +1,23 @@ -# Aragon app-next +# Aragon App monorepo -Next.js 15 + React 19 + TypeScript plugin-based DAO governance platform. Node >=22, pnpm. +pnpm workspaces + Turborepo monorepo. Node >=24.16, pnpm. The main package is `apps/app` (`@aragon/app`) — a Next.js 16 + React 19 + TypeScript plugin-based DAO governance platform. This file is the team-shared agent entry point. `CLAUDE.md` imports it via `@AGENTS.md` so Claude Code picks up the same content. Personal/IC-local agent context lives under `.agents/local/` and `.claude/` (both gitignored) and **supplements** — never replaces — what's here. +## Monorepo layout + +- `apps/app/` — the Aragon App (app.aragon.org): all app source, configs (`next.config.mjs`, `tsconfig.json`, `jest.config.js`), `e2e/`, `docs/`, `scripts/`, `CHANGELOG.md`. Package name stays `@aragon/app`. +- `apps/*`, `packages/*` — reserved for future workspaces. +- Root — workspace infra only: `pnpm-workspace.yaml`, `turbo.json`, `biome.json`, `.github/`, `.husky/`, `.changeset/`, agent infra (`.agents/`, `.claude/`). Root `package.json` has no version; each workspace is versioned independently via changesets with per-package tags (`@aragon/app@1.17.0`) and release branches (`release/app/…`). See `apps/app/docs/projectDocs/release-process.md`. +- CI: workflows in `.github/workflows/` are grouped per app (`app-*.yml` for `apps/app`, `shared-*.yml` reusable). Root scripts proxy through `turbo run `, so `pnpm type-check` etc. work from the repo root. + ## Where things live -- **Architecture & structure** — `docs/projectDocs/projectStructure.md`, `docs/projectDocs/pluginEncapsulation.md`, `docs/slots/overview.md` -- **Coding standards** — `docs/codingGuidelines/codingGuidelines.md`, `docs/codingGuidelines/namingConventions.md`. Ultracite (Biome preset) enforces formatting and lint via `pnpm dlx ultracite fix`. -- **Data fetching** — `docs/projectDocs/dataFetching.md` -- **Slots system** — `docs/slots/` -- **Testing** — `docs/projectDocs/testing.md` +- **Architecture & structure** — `apps/app/docs/projectDocs/projectStructure.md`, `apps/app/docs/projectDocs/pluginEncapsulation.md`, `apps/app/docs/slots/overview.md` +- **Coding standards** — `apps/app/docs/codingGuidelines/codingGuidelines.md`, `apps/app/docs/codingGuidelines/namingConventions.md`. Ultracite (Biome preset) enforces formatting and lint via `pnpm dlx ultracite fix`. +- **Data fetching** — `apps/app/docs/projectDocs/dataFetching.md` +- **Slots system** — `apps/app/docs/slots/` +- **Testing** — `apps/app/docs/projectDocs/testing.md` ## Repo layout for agent infra @@ -39,11 +46,17 @@ Authorship is bottom-up: when a code review surfaces a non-obvious convention, o ## Scripts +The root only carries workspace-wide tasks (turbo fan-out) and root infra: + ```sh -pnpm dev # Dev server (Turbopack) -pnpm test # Jest watch mode -pnpm test:guardrails # Guardrails loader + adapter contract tests -pnpm lint:fix # Auto-fix ESLint -pnpm type-check # TypeScript check +pnpm dev # Dev servers of all workspaces (turbo) +pnpm test # Jest across workspaces (turbo) +pnpm lint # Biome check --write (auto-fix) +pnpm lint:check # Biome check (CI mode) +pnpm type-check # TypeScript check across workspaces +pnpm test:guardrails # Guardrails loader + adapter contract tests (root infra) pnpm dlx ultracite fix # Format + lint ``` + +Workspace-specific scripts (e2e, env setup, watch modes, codegen) live in the owning +workspace — run them there: `cd apps/app && pnpm test:watch` (or `pnpm --filter @aragon/app