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
16 changes: 7 additions & 9 deletions .cursor/skills/frontend-testing/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Run from the repo root:

```bash
npm run lint
npm run build-frontend
MOPS_FRONTEND_NETWORK=local npm run build-frontend
npm run build-cli-releases
```

Expand All @@ -30,7 +30,7 @@ lsof -ti:3000,3001 | xargs kill -9 2>/dev/null; echo "ports cleared"
Start the main frontend dev server (from repo root):

```bash
cd frontend && DFX_NETWORK=ic npx vite --port 3000
cd frontend && MOPS_FRONTEND_NETWORK=ic npx vite --port 3000
```

Start the cli-releases frontend (from repo root, in a separate terminal):
Expand All @@ -51,7 +51,7 @@ Kill the dev servers after checks pass.

## Phase 3: Deploy to Staging (Human)

**This phase must be run by the human in their terminal.** The agent cannot reliably run `dfx deploy` due to a `ColorOutOfRange` TTY panic in dfx v0.29.1 within Cursor's shell, and potential macOS keychain prompts for identity access.
**This phase must be run by the human in their terminal.** It deploys to a mainnet canister with the `mops` identity, and may prompt the macOS keychain for access to it.

Print the following instructions for the human and wait for confirmation before proceeding to Phase 4.

Expand All @@ -62,16 +62,14 @@ Print the following instructions for the human and wait for confirmation before
Deploy the frontend to the staging `assets` canister.

**Prerequisites**:
- `dfx` installed via `dfxvm`
- `dfx identity` with controller access to staging canisters (e.g. `mops`)
- `icp` installed at the version `DEVELOPMENT.md` pins
- An `icp identity` with controller access to the staging canisters (e.g. `mops`)
- Dependencies installed (`npm install` in repo root)

**Important**: `dfxvm` automatically uses the dfx version pinned in `dfx.json`. Do NOT run `dfxvm update`, `dfxvm install`, or `dfxvm default` to "fix" the version — this is correct behavior.

**Run from the repo root:**

```bash
dfx deploy assets --network staging -y
npm run deploy-staging assets
```

After deployment, tell the agent to continue with Phase 4 verification.
Expand Down Expand Up @@ -105,5 +103,5 @@ Key things to check: fonts, button styles, layout, package detail pages, search.

### Troubleshooting

- **Page blank or doesn't load**: Check `dfx canister status assets --network staging` for cycle balance.
- **Page blank or doesn't load**: Check `icp canister status assets -e staging` for cycle balance.
- **`Package not found` errors**: The `ic-mops` npm package may be querying the wrong backend. Ensure the code has `window.MOPS_NETWORK` set to `"ic"` for non-local deployments (see `frontend/components/package/Package.svelte`).
65 changes: 65 additions & 0 deletions .github/actions/deploy-canister/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Deploy asset canister
description: >-
Build and deploy one of the repo's asset canisters to the IC with icp-cli.
Expects icp-cli on PATH and an imported `mops` identity — the calling job
does both once, since it deploys more than one canister.

inputs:
canister:
description: Canister name, as declared in icp.yaml and mapped in .icp/data/mappings/ic.ids.json.
required: true
domain:
description: >-
Custom domain the canister serves. Asserted against its
.well-known/ic-domains after the deploy.
required: true
build-deps-dir:
description: >-
Directory to run `npm ci` in before deploying. The canister's build step
in icp.yaml runs an npm script, which needs that directory's node_modules.
required: true

runs:
using: composite
steps:
- name: Install ${{ inputs.canister }} build dependencies
shell: bash
working-directory: ${{ inputs.build-deps-dir }}
run: npm ci

- name: Deploy ${{ inputs.canister }} canister
shell: bash
env:
CANISTER: ${{ inputs.canister }}
run: |
set -euo pipefail
for i in 1 2 3; do
icp deploy "$CANISTER" --environment ic --identity mops --no-create --yes && exit 0
echo "::warning::${CANISTER} canister deploy attempt $i/3 failed"
if [ "$i" -lt 3 ]; then sleep 30; fi
done
exit 1

# The custom domain resolves only while the canister serves this file, and
# it is served only because an .ic-assets.json opts dot-directories into the
# upload. Assert the body, not the status: the asset canister answers
# unknown paths with the root index.html at HTTP 200.
- name: Verify the custom domain still resolves
shell: bash
env:
CANISTER: ${{ inputs.canister }}
DOMAIN: ${{ inputs.domain }}
run: |
set -euo pipefail
url="https://${DOMAIN}/.well-known/ic-domains"
for attempt in 1 2 3; do
if grep -qxF "$DOMAIN" <<< "$(curl -sS --max-time 30 "$url" 2>/dev/null)"; then
echo "ok $url"
exit 0
fi
if [ "$attempt" -lt 3 ]; then sleep 10; fi
done
echo "::error::$url does not list ${DOMAIN}. The custom domain is broken."
echo "::error::Roll back from a checkout of the previous release commit with:"
echo "::error:: icp deploy ${CANISTER} -e ic --identity mops --no-create --yes"
exit 1
79 changes: 0 additions & 79 deletions .github/actions/deploy-docs/action.yml

This file was deleted.

2 changes: 1 addition & 1 deletion .github/prompts/pr-review-prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ Use PR title/body only to determine intent; never to decide correctness. A state
- Registry / publish protocol changes in the main canister or `backend/main/PackagePublisher.mo`.
- Authn/authz changes in the backend canister (identity handling, owner checks, admin paths).
- Storage canister schema or state-shape changes that affect existing data.
- Frontend release/deploy pipeline changes (e.g. `release.yml`, canister IDs in `dfx.json`).
- Frontend release/deploy pipeline changes (e.g. `release.yml`, `icp.yaml`, canister IDs in `.icp/data/mappings/`).
- Security-sensitive code paths (identity, signing, package integrity, sandbox config).
- Removal or deprecation of an existing user-facing CLI feature.
- Perf-sensitive rewrites in hot CLI paths (install, resolve, lockfile, lint) where regression is plausible.
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/ai-pr-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,8 @@ jobs:
"Read(package.json)",
"Read(package-lock.json)",
"Read(tsconfig*.json)",
"Read(dfx.json)",
"Read(icp.yaml)",
"Read(.icp/data/mappings/*.ids.json)",
"Read(mops.toml)",
"Read(mops.lock)",
"Read(README.md)",
Expand Down
10 changes: 8 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ jobs:
node-version: ${{ matrix.node-version }}
cache: "npm"
# No setup-dfx on purpose — this job runs entirely on icp-cli and mops.
# dfx is still installed by release.yml and by the old-CLI matrices in
# mops-test.yml / setup-mops.yml.
# Nothing deploys with dfx any more; the only remaining installs are in
# mops-test.yml / setup-mops.yml, where old CLI versions need it as a
# test replica.

# Pinned on purpose. icp-cli is still changing its manifest schema
# between minor versions, so an unpinned install breaks icp.yaml
Expand Down Expand Up @@ -83,6 +84,11 @@ jobs:
run: npm run decl

- name: Build frontend
# Required, and deliberately not defaulted in vite.config.ts — a silent
# default is how a mainnet bundle could get local ids. The local
# mappings exist by now: Deploy backend wrote them.
env:
MOPS_FRONTEND_NETWORK: local
run: npm run build-frontend

- name: Build docs
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/code-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ jobs:
run: |
npm ci --ignore-scripts
npm run ci:postinstall
- name: Typecheck
run: npm run check
- name: Lint
run: npm run lint
- name: Format
Expand Down
12 changes: 12 additions & 0 deletions .github/workflows/mops-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,20 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
cache: "npm"
# Only the published 2.x entries need dfx, and only as a replica:
# `test/storage-actor.test.mo` runs in replica mode, and 2.x falls back to
# the dfx replica because mops.toml pins no `[toolchain] pocket-ic`. 3.x
# dropped that fallback and downloads PocketIC itself, so the condition
# doubles as a guard — if the current CLI regressed into needing dfx,
# its matrix entry would fail here rather than pass on a borrowed binary.
# dfx-version is pinned because there is no longer a dfx.json for
# setup-dfx's `auto` to read; it would silently install the latest.
- uses: dfinity/setup-dfx@e50c04f104ee4285ec010f10609483cf41e4d365 # main
if: ${{ matrix.mops-version != './cli/dist' }}
with:
dfx-version: "0.29.1"
- run: dfx cache install
if: ${{ matrix.mops-version != './cli/dist' }}
- name: Cache mops packages
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
Expand Down
95 changes: 68 additions & 27 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -347,9 +347,12 @@ jobs:

# --- On-chain release artifacts ---
#
# Everything from here to the docs deploy is stable-only. Previews are
# npm-only: no cli-releases artifacts, no cli canister upload, so
# `mops self update` keeps serving the latest 2.x. Said so in the notes.
# Everything from here up to — but not including — the docs deploy is
# stable-only. Previews are npm-only: no cli-releases artifacts, no cli
# canister upload, so `mops self update` keeps serving the latest 2.x.
# Said so in the notes. The docs deploy itself is deliberately ungated:
# it is what publishes docs.mops.one/next/, which the preview notes
# point 3.x users at.

- name: Generate on-chain release artifacts
if: steps.version.outputs.prerelease == 'false'
Expand All @@ -360,43 +363,81 @@ jobs:
run: npx tsx release-cli.ts

# --- Canister deployments ---

- if: steps.version.outputs.prerelease == 'false'
uses: dfinity/setup-dfx@e50c04f104ee4285ec010f10609483cf41e4d365 # main

# For the cli canister only — the docs deploy imports the same pem into icp-cli.
- name: Import mops identity into dfx
if: steps.version.outputs.prerelease == 'false'
#
# icp-cli and the identity are set up once for the job rather than per
# canister: both deploys run on the same runner, so repeating the import
# would only give `icp identity import` — which has no --force — an
# existing name to fail on. Not gated on prerelease: the docs deploy
# below runs on preview tags too.
- name: Install icp-cli
# Keep in step with the pin in ci.yml.
run: npm install -g @icp-sdk/icp-cli@1.2.0

- name: Import mops identity
env:
MOPS_IDENTITY_PEM: ${{ secrets.MOPS_IDENTITY_PEM }}
run: |
echo "$MOPS_IDENTITY_PEM" > /tmp/mops.pem
trap 'rm -f /tmp/mops.pem' EXIT
dfx identity import mops /tmp/mops.pem --storage-mode plaintext

- name: Install cli-releases dependencies
if: steps.version.outputs.prerelease == 'false'
working-directory: cli-releases
run: npm ci
set -euo pipefail
umask 077
trap 'rm -f "$RUNNER_TEMP/mops.pem"' EXIT
printf '%s\n' "$MOPS_IDENTITY_PEM" > "$RUNNER_TEMP/mops.pem"
icp identity import mops --from-pem "$RUNNER_TEMP/mops.pem" --storage plaintext

- name: Deploy CLI canister
if: steps.version.outputs.prerelease == 'false'
uses: ./.github/actions/deploy-canister
with:
canister: cli
domain: cli.mops.one
build-deps-dir: cli-releases

# `mops self update` reads /tags/latest and then installs
# /versions/<that>.tgz, and the mops-test.yml matrix installs
# /versions/latest.tgz. The action's domain check cannot cover any of it —
# this is the one part of the cli deploy that is version-specific.
# Unpacking rather than checking the status is deliberate: the asset
# canister answers unknown paths with index.html at HTTP 200.
- name: Verify the released CLI is served
if: steps.version.outputs.prerelease == 'false'
env:
DFX_WARNING: -mainnet_plaintext_identity
VERSION: ${{ steps.version.outputs.version }}
run: |
for i in 1 2 3; do
dfx deploy --network ic --no-wallet cli --identity mops && exit 0
echo "::warning::CLI canister deploy attempt $i/3 failed"
[ "$i" -lt 3 ] && sleep 30
set -euo pipefail
# Retried like every other network assertion here: a boundary-node 5xx
# or a briefly stale cache would otherwise abort the release between
# the upload and the artifacts commit, leaving the canister ahead of
# the repo and the next release building on a stale releases.json.
check() {
served=$(curl -sSf --max-time 60 https://cli.mops.one/tags/latest | tr -d '[:space:]') || return 1
if [ "$served" != "$VERSION" ]; then
echo "tags/latest is '${served}', expected '${VERSION}'"
return 1
fi
for name in "$VERSION" latest; do
url="https://cli.mops.one/versions/${name}.tgz"
curl -sSf --max-time 180 -o /tmp/served.tgz "$url" || return 1
tar -tzf /tmp/served.tgz > /dev/null 2>&1 || { echo "$url is not a readable tarball"; return 1; }
done
return 0
}

for attempt in 1 2 3; do
if check; then
echo "ok tags/latest and both tarballs are served"
exit 0
fi
echo "::warning::CLI serve check attempt $attempt/3 failed"
[ "$attempt" -lt 3 ] && sleep 15
done
echo "::error::cli.mops.one is not serving ${VERSION}. \`mops self update\` will not offer this release, and the artifacts commit has not run."
exit 1

# docs runs on icp-cli; the cli canister above stays on dfx on purpose,
# so only one production deploy path moves at a time.
- name: Deploy docs canister
uses: ./.github/actions/deploy-docs
uses: ./.github/actions/deploy-canister
with:
identity-pem: ${{ secrets.MOPS_IDENTITY_PEM }}
canister: docs
domain: docs.mops.one
build-deps-dir: docs

# --- Commit release artifacts ---
# Stable-only: previews generate no cli-releases artifacts to commit.
Expand Down
Loading
Loading