diff --git a/.changeset/align-missing-schema-host-hints.md b/.changeset/align-missing-schema-host-hints.md new file mode 100644 index 000000000..87f803bc3 --- /dev/null +++ b/.changeset/align-missing-schema-host-hints.md @@ -0,0 +1,9 @@ +--- +"@arkenv/vite-plugin": patch +"@arkenv/nextjs": patch +"@arkenv/nuxt": patch +--- + +#### Align missing-schema errors with short, actionable host guidance + +Point missing-schema errors at checked paths / `schemaPath` and `arkenv init`, matching the Bun plugin style, without embedding starter `env.ts` modules. diff --git a/.changeset/align-v1-shared-missing-schema-errors.md b/.changeset/align-v1-shared-missing-schema-errors.md new file mode 100644 index 000000000..6e3e2409b --- /dev/null +++ b/.changeset/align-v1-shared-missing-schema-errors.md @@ -0,0 +1,17 @@ +--- +"@arkenv/build": patch +"@arkenv/bun-plugin": patch +"@arkenv/vite-plugin": patch +"@arkenv/nextjs": patch +"@arkenv/nuxt": patch +--- + +#### Make missing-schema errors short and actionable across hosts + +When a host cannot find an env schema, throw a consistent message that names the expected path / `schemaPath` and points to `npx arkenv@latest init`, without embedding a starter `env.ts` module. + +Example: + +```text +[ArkEnv] Could not find schema file at src/env.ts or env.ts. Please specify 'schemaPath' in ArkEnv options (or run `npx arkenv@latest init`). +``` diff --git a/.changeset/optional-strict-shared-v1.md b/.changeset/optional-strict-shared-v1.md new file mode 100644 index 000000000..4eafdded5 --- /dev/null +++ b/.changeset/optional-strict-shared-v1.md @@ -0,0 +1,18 @@ +--- +"@arkenv/build": patch +"@arkenv/nextjs": patch +"@arkenv/nuxt": patch +--- + +#### Make `env/internal/shared.ts` optional in strict layout + +Strict layout now works with just `client.ts` and `server.ts`. Omit `internal/shared.ts` when you have nothing to share — shared keys are treated as empty. + +```ts +// env/client.ts + env/server.ts alone is enough +export default withArkEnv(nextConfig, { + layout: "strict", +}); +``` + +The CLI still scaffolds `shared.ts` by default for convenience. diff --git a/.changeset/pre.json b/.changeset/pre.json index d6163b9a6..520e6dfec 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -1,62 +1,66 @@ { - "mode": "pre", - "tag": "alpha", - "initialVersions": { - "arkenv-cli-playground": "0.0.0", - "bun-react-playground": "0.1.0", - "vite-playground": "0.0.0", - "vite-legacy-playground": "0.0.0", - "@repo/playwright-www": "0.0.0", - "www": "0.0.0", - "arkenv": "0.11.1", - "@arkenv/bun-plugin": "0.1.7", - "@arkenv/cli": "0.2.10", - "@arkenv/fumadocs-ui": "0.0.8", - "@repo/scope": "0.1.3", - "@repo/types": "0.1.0", - "@arkenv/nextjs": "0.0.7", - "@arkenv/vite-plugin": "0.1.1", - "@arkenv/build": "0.0.1", - "@arkenv/nuxt": "0.0.1", - "@arkenv/core": "1.0.0-alpha.2", - "@repo/utils": "0.1.3", - "@arkenv/standard": "1.0.0-alpha.2", - "@repo/log": "0.0.1" - }, - "changesets": [ - "add-host-preset-add-command", - "add-host-presets-phase-4", - "add-host-strict-layouts-forward-port", - "align-missing-schema-throw", - "bun-env-module-transform", - "drop-example-e-alias", - "epg3nspi", - "fix-nuxt-proxy-coercion", - "forward-port-hosting-presets", - "forward-port-keywords-typetable", - "host-preset-alias", - "improve-npm-keywords", - "init-v1", - "injectable-logger-api", - "machine-actionable-error-codes", - "nuxt-augment-config-key", - "nuxt-boot-gate-thin-accessors", - "nuxt-flat-layout-cli-alignment", - "nuxt-module-options-jsdoc-v1", - "nuxt-skip-setup-before-boot-gate", - "nuxt-strict-auto-extend", - "nuxt-strict-client-shared-auto-extend", - "reconcile-v0-features", - "remove-framework-shared-exports", - "rename-create-env-to-arkenv", - "standard-isolation-guards", - "standard-mode-flat-layout", - "standard-mode-packaging", - "trim-short-flag-aliases", - "unified-error-normalization", - "unify-coercion", - "valibot-example-registry", - "vite-env-module-transform", - "walk-back-env-starters" - ] + "mode": "pre", + "tag": "alpha", + "initialVersions": { + "arkenv-cli-playground": "0.0.0", + "bun-react-playground": "0.1.0", + "vite-playground": "0.0.0", + "vite-legacy-playground": "0.0.0", + "@repo/playwright-www": "0.0.0", + "www": "0.0.0", + "arkenv": "0.11.1", + "@arkenv/bun-plugin": "0.1.7", + "@arkenv/cli": "0.2.10", + "@arkenv/fumadocs-ui": "0.0.8", + "@repo/scope": "0.1.3", + "@repo/types": "0.1.0", + "@arkenv/nextjs": "0.0.7", + "@arkenv/vite-plugin": "0.1.1", + "@arkenv/build": "0.0.1", + "@arkenv/nuxt": "0.0.1", + "@arkenv/core": "1.0.0-alpha.2", + "@repo/utils": "0.1.3", + "@arkenv/standard": "1.0.0-alpha.2", + "@repo/log": "0.0.1" + }, + "changesets": [ + "add-host-preset-add-command", + "add-host-presets-phase-4", + "add-host-strict-layouts-forward-port", + "align-missing-schema-host-hints", + "align-missing-schema-throw", + "align-v1-shared-missing-schema-errors", + "bun-env-module-transform", + "drop-example-e-alias", + "epg3nspi", + "fix-nuxt-proxy-coercion", + "forward-port-hosting-presets", + "forward-port-keywords-typetable", + "host-preset-alias", + "improve-npm-keywords", + "init-v1", + "injectable-logger-api", + "machine-actionable-error-codes", + "nuxt-augment-config-key", + "nuxt-boot-gate-thin-accessors", + "nuxt-flat-layout-cli-alignment", + "nuxt-module-options-jsdoc-v1", + "nuxt-skip-setup-before-boot-gate", + "nuxt-strict-auto-extend", + "nuxt-strict-client-shared-auto-extend", + "optional-strict-shared-v1", + "reconcile-v0-features", + "remove-framework-shared-exports", + "rename-create-env-to-arkenv", + "split-help-global-init-options", + "standard-isolation-guards", + "standard-mode-flat-layout", + "standard-mode-packaging", + "trim-short-flag-aliases", + "unified-error-normalization", + "unify-coercion", + "valibot-example-registry", + "vite-env-module-transform", + "walk-back-env-starters" + ] } diff --git a/.changeset/split-help-global-init-options.md b/.changeset/split-help-global-init-options.md new file mode 100644 index 000000000..d8df262b5 --- /dev/null +++ b/.changeset/split-help-global-init-options.md @@ -0,0 +1,30 @@ +--- +"arkenv": patch +--- + +#### Split `--help` options into Global and `init` sections + +List shared flags under **Global options** and scaffolding flags under **init options**, matching the multi-command `/docs/cli` taxonomy. + +```bash +npx arkenv@alpha --help +``` + +```text +Usage: + arkenv init [project-name] ... + arkenv add host [provider] ... + +Global options: + --yes, -y Skip prompts and use defaults ... + --quiet, -q Quiet mode ... + --json, -j Output structured JSON ... + --agent Enable non-interactive, machine-readable mode ... + --help, -h Show this help message + +init options: + --example Specify an example name ... + --force, -f Bypass checks and force scaffolding + --no-codegen Disable automatic env.gen.ts code generation ... + --host-preset, -H Specify a hosting provider preset ... +``` diff --git a/.github/workflows/deploy-www-manual.yml b/.github/workflows/deploy-www-manual.yml new file mode 100644 index 000000000..f00b5bcc8 --- /dev/null +++ b/.github/workflows/deploy-www-manual.yml @@ -0,0 +1,111 @@ +name: Deploy www (manual SHA) + +# Maintainer escape hatch: deploy any commit to a stable www URL without +# pushing that commit to the target branch. Requires write access to run. +on: + workflow_dispatch: + inputs: + sha: + description: Full commit SHA to build and deploy + required: true + type: string + target: + description: Stable URL to update + required: true + type: choice + options: + - arkenv-dev.vercel.app + - arkenv-v1.vercel.app + - arkenv.js.org + +concurrency: + group: deploy-www-manual-${{ inputs.target }} + cancel-in-progress: true + +jobs: + Deploy-Manual: + runs-on: ubuntu-latest + env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} + TURBO_TEAM: ${{ vars.TURBO_TEAM }} + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + TARGET: ${{ inputs.target }} + DEPLOY_SHA: ${{ inputs.sha }} + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ inputs.sha }} + fetch-depth: 0 + - name: Resolve git metadata for the deployed commit + id: meta + run: | + set -euo pipefail + case "$TARGET" in + arkenv-dev.vercel.app) echo "git_ref=dev" >> "$GITHUB_OUTPUT" ;; + arkenv-v1.vercel.app) echo "git_ref=v1" >> "$GITHUB_OUTPUT" ;; + arkenv.js.org) echo "git_ref=main" >> "$GITHUB_OUTPUT" ;; + *) + echo "Unknown target: $TARGET" >&2 + exit 1 + ;; + esac + echo "short_msg=$(git log -1 --format=%s "$DEPLOY_SHA" | head -n1)" >> "$GITHUB_OUTPUT" + echo "author_name=$(git log -1 --format=%an "$DEPLOY_SHA")" >> "$GITHUB_OUTPUT" + - uses: pnpm/action-setup@v6 + - uses: actions/setup-node@v6 + with: + node-version: lts/* + cache: pnpm + - name: Install dependencies + run: pnpm install + - name: Install Vercel CLI + # Pin the CLI to avoid upstream breakage from newly published majors. + run: npm install --global vercel@54.7.1 + - name: Pull Vercel Environment Information + run: | + if [ "$TARGET" = "arkenv.js.org" ]; then + node scripts/vercel-wrapper.cjs pull --yes --environment=production --token="$VERCEL_TOKEN" + else + node scripts/vercel-wrapper.cjs pull --yes --environment=preview --token="$VERCEL_TOKEN" + fi + - name: Build Project Artifacts + run: | + if [ "$TARGET" = "arkenv.js.org" ]; then + node scripts/vercel-wrapper.cjs build --prod --token="$VERCEL_TOKEN" + else + node scripts/vercel-wrapper.cjs build --token="$VERCEL_TOKEN" + fi + - name: Deploy and attach to stable URL + env: + GIT_ORG: ${{ github.repository_owner }} + GIT_REPO: ${{ github.event.repository.name }} + GIT_REF: ${{ steps.meta.outputs.git_ref }} + GIT_SHA: ${{ inputs.sha }} + GIT_COMMIT_MESSAGE: ${{ steps.meta.outputs.short_msg }} + GIT_COMMIT_AUTHOR_NAME: ${{ steps.meta.outputs.author_name }} + GIT_COMMIT_AUTHOR_LOGIN: ${{ github.actor }} + run: | + set -o pipefail + META_ARGS=( + -m githubDeployment=1 + -m githubOrg="$GIT_ORG" + -m githubRepo="$GIT_REPO" + -m githubCommitOrg="$GIT_ORG" + -m githubCommitRepo="$GIT_REPO" + -m githubCommitRef="$GIT_REF" + -m githubCommitSha="$GIT_SHA" + -m githubCommitMessage="$GIT_COMMIT_MESSAGE" + -m githubCommitAuthorName="$GIT_COMMIT_AUTHOR_NAME" + -m githubCommitAuthorLogin="$GIT_COMMIT_AUTHOR_LOGIN" + ) + if [ "$TARGET" = "arkenv.js.org" ]; then + node scripts/vercel-wrapper.cjs deploy --prebuilt --prod --archive=tgz --token="$VERCEL_TOKEN" "${META_ARGS[@]}" + echo "Deployed $GIT_SHA to production (arkenv.js.org)." + else + DEPLOYMENT_URL=$(node scripts/vercel-wrapper.cjs deploy --prebuilt --archive=tgz --token="$VERCEL_TOKEN" "${META_ARGS[@]}" | tail -n 1) + echo "Deployment URL: $DEPLOYMENT_URL" + echo "Aliasing $DEPLOYMENT_URL -> $TARGET" + node scripts/vercel-wrapper.cjs alias set "$DEPLOYMENT_URL" "$TARGET" --token="$VERCEL_TOKEN" + fi diff --git a/.github/workflows/deploy-www.yml b/.github/workflows/deploy-www.yml index e1183419a..22b25f3ce 100644 --- a/.github/workflows/deploy-www.yml +++ b/.github/workflows/deploy-www.yml @@ -42,4 +42,28 @@ jobs: - name: Build Project Artifacts run: node scripts/vercel-wrapper.cjs build --prod --token=${{ secrets.VERCEL_TOKEN }} - name: Deploy Project Artifacts to Vercel - run: node scripts/vercel-wrapper.cjs deploy --prebuilt --prod --archive=tgz --token=${{ secrets.VERCEL_TOKEN }} + env: + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + GIT_ORG: ${{ github.repository_owner }} + GIT_REPO: ${{ github.event.repository.name }} + GIT_REF: ${{ github.ref_name }} + GIT_SHA: ${{ github.sha }} + GIT_ACTOR: ${{ github.actor }} + GIT_COMMIT_MESSAGE: ${{ github.event.head_commit.message || github.sha }} + GIT_COMMIT_AUTHOR_NAME: ${{ github.event.head_commit.author.name || github.actor }} + run: | + set -o pipefail + SHORT_MSG="$(printf '%s\n' "$GIT_COMMIT_MESSAGE" | head -n1)" + # --prod already assigns production domains (arkenv.js.org / arkenv.vercel.app). + # Git meta keeps the deployment linked to main in the Vercel dashboard. + node scripts/vercel-wrapper.cjs deploy --prebuilt --prod --archive=tgz --token="$VERCEL_TOKEN" \ + -m githubDeployment=1 \ + -m githubOrg="$GIT_ORG" \ + -m githubRepo="$GIT_REPO" \ + -m githubCommitOrg="$GIT_ORG" \ + -m githubCommitRepo="$GIT_REPO" \ + -m githubCommitRef="$GIT_REF" \ + -m githubCommitSha="$GIT_SHA" \ + -m githubCommitMessage="$SHORT_MSG" \ + -m githubCommitAuthorName="$GIT_COMMIT_AUTHOR_NAME" \ + -m githubCommitAuthorLogin="$GIT_ACTOR" diff --git a/.github/workflows/preview-www-default.yml b/.github/workflows/preview-www-default.yml index b6cee9371..be975c16c 100644 --- a/.github/workflows/preview-www-default.yml +++ b/.github/workflows/preview-www-default.yml @@ -1,7 +1,9 @@ name: Preview www (Default Events) on: - pull_request: + # pull_request_target so fork PRs receive repository secrets (VERCEL_*). + # Workflow YAML is taken from the base branch; we checkout PR HEAD in the reusable job. + pull_request_target: types: [opened, synchronize, ready_for_review] branches: - dev @@ -19,6 +21,7 @@ jobs: Deploy-Preview: # Branch previews (push to dev/v1) always deploy. PR previews are opt-in: # they only deploy when the PR carries the `preview` label, regardless of draft state. + # Fork authors cannot apply labels (triage+ required), so this is not self-serve spamable. if: github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'preview') uses: ./.github/workflows/preview-www-reusable.yml secrets: inherit diff --git a/.github/workflows/preview-www-labeled.yml b/.github/workflows/preview-www-labeled.yml index 8043e4714..13ba6fd8f 100644 --- a/.github/workflows/preview-www-labeled.yml +++ b/.github/workflows/preview-www-labeled.yml @@ -1,7 +1,8 @@ name: Preview www (Label Triggered) on: - pull_request: + # pull_request_target so fork PRs receive repository secrets when a maintainer applies `preview`. + pull_request_target: types: [labeled] branches: - dev diff --git a/.github/workflows/preview-www-reusable.yml b/.github/workflows/preview-www-reusable.yml index b65dd6daf..b01c9983c 100644 --- a/.github/workflows/preview-www-reusable.yml +++ b/.github/workflows/preview-www-reusable.yml @@ -3,25 +3,33 @@ name: Preview www Reusable on: workflow_call: +# Non-secret defaults only. Vercel credentials are injected solely on Vercel CLI steps +# so untrusted PR HEAD install/build scripts do not see them ambiently. env: - VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} - APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }} - TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: ${{ vars.TURBO_TEAM }} TZ: ${{ vars.TIMEZONE || 'Asia/Almaty' }} jobs: Deploy-Preview: runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write steps: - uses: actions/checkout@v6 with: fetch-depth: 0 + # pull_request_target defaults to the base ref; use the base repo's pull head + # ref so fork commits resolve and base history remains available for turbo. + ref: ${{ github.event.pull_request.number && format('refs/pull/{0}/head', github.event.pull_request.number) || github.sha }} + persist-credentials: false - name: Set Turbo base/head for PRs + env: + PR_BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }} + PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} run: | - echo "TURBO_SCM_BASE=${{ github.event.pull_request.base.sha || github.event.before }}" >> $GITHUB_ENV - echo "TURBO_SCM_HEAD=${{ github.sha }}" >> $GITHUB_ENV + echo "TURBO_SCM_BASE=${PR_BASE_SHA}" >> "$GITHUB_ENV" + echo "TURBO_SCM_HEAD=${PR_HEAD_SHA}" >> "$GITHUB_ENV" - uses: pnpm/action-setup@v6 - uses: actions/setup-node@v6 with: @@ -31,45 +39,117 @@ jobs: run: pnpm install - name: Check if www is affected id: affected + env: + TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} + BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }} + HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} run: | - BASE_SHA="${{ github.event.pull_request.base.sha || github.event.before }}" if [ -z "$BASE_SHA" ] || [ "$BASE_SHA" = "0000000000000000000000000000000000000000" ]; then BASE_SHA="HEAD~1" fi - echo "Comparing base ref: $BASE_SHA with head: ${{ github.sha }}" - - if pnpm exec turbo query affected --packages=www --base="$BASE_SHA" --head="${{ github.sha }}" --exit-code; then + echo "Comparing base ref: $BASE_SHA with head: $HEAD_SHA" + + # Deploy-path edits must still rebuild/alias branch domains (turbo won't mark www). + if git diff --name-only "$BASE_SHA" "$HEAD_SHA" | grep -qE '^(\.github/workflows/preview-www|scripts/vercel-wrapper\.cjs)'; then + echo "Preview deploy path changed; forcing www deploy." + echo "is_affected=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + if pnpm exec turbo query affected --packages=www --base="$BASE_SHA" --head="$HEAD_SHA" --exit-code; then echo "www is NOT affected." - echo "is_affected=false" >> $GITHUB_OUTPUT + echo "is_affected=false" >> "$GITHUB_OUTPUT" else echo "www IS affected." - echo "is_affected=true" >> $GITHUB_OUTPUT + echo "is_affected=true" >> "$GITHUB_OUTPUT" fi - name: Install Vercel CLI # Pin the CLI to avoid upstream breakage from newly published majors. run: npm install --global vercel@54.7.1 - name: Pull Vercel Environment Information if: steps.affected.outputs.is_affected == 'true' - run: node scripts/vercel-wrapper.cjs pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }} + env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + run: node scripts/vercel-wrapper.cjs pull --yes --environment=preview --token="$VERCEL_TOKEN" - name: Build Project Artifacts if: steps.affected.outputs.is_affected == 'true' - run: node scripts/vercel-wrapper.cjs build --token=${{ secrets.VERCEL_TOKEN }} + env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} + run: node scripts/vercel-wrapper.cjs build --token="$VERCEL_TOKEN" - name: Deploy Project Artifacts to Vercel if: steps.affected.outputs.is_affected == 'true' id: deploy + env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + # Git meta so CLI deploys associate with the correct branch (branch domains / env). + # See https://vercel.com/kb/guide/branch-variables-and-domains-not-linked-to-cli-deployments + GIT_ORG: ${{ github.repository_owner }} + GIT_REPO: ${{ github.event.repository.name }} + GIT_REF: ${{ github.event.pull_request.head.ref || github.ref_name }} + GIT_SHA: ${{ github.event.pull_request.head.sha || github.sha }} + GIT_ACTOR: ${{ github.event.pull_request.head.user.login || github.actor }} + GIT_COMMIT_MESSAGE: ${{ github.event.pull_request.title || github.event.head_commit.message || github.sha }} + GIT_COMMIT_AUTHOR_NAME: ${{ github.event.head_commit.author.name || github.actor }} run: | set -o pipefail - DEPLOYMENT_URL=$(node scripts/vercel-wrapper.cjs deploy --prebuilt --archive=tgz --token=${{ secrets.VERCEL_TOKEN }} | tail -n 1) - echo "DEPLOYMENT_URL=$DEPLOYMENT_URL" >> $GITHUB_OUTPUT + SHORT_MSG="$(printf '%s\n' "$GIT_COMMIT_MESSAGE" | head -n1)" + DEPLOYMENT_URL=$(node scripts/vercel-wrapper.cjs deploy --prebuilt --archive=tgz --token="$VERCEL_TOKEN" \ + -m githubDeployment=1 \ + -m githubOrg="$GIT_ORG" \ + -m githubRepo="$GIT_REPO" \ + -m githubCommitOrg="$GIT_ORG" \ + -m githubCommitRepo="$GIT_REPO" \ + -m githubCommitRef="$GIT_REF" \ + -m githubCommitSha="$GIT_SHA" \ + -m githubCommitMessage="$SHORT_MSG" \ + -m githubCommitAuthorName="$GIT_COMMIT_AUTHOR_NAME" \ + -m githubCommitAuthorLogin="$GIT_ACTOR" \ + | tail -n 1) + echo "DEPLOYMENT_URL=$DEPLOYMENT_URL" >> "$GITHUB_OUTPUT" + # Explicit alias so branch domains stay current without native Vercel Git builds (#1460). + # Only on push to dev/v1 — labeled PR previews keep ephemeral URLs and must not steal these. + - name: Alias branch preview domain + if: >- + github.event_name == 'push' && + (github.ref_name == 'dev' || github.ref_name == 'v1') && + steps.affected.outputs.is_affected == 'true' + env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + DEPLOYMENT_URL: ${{ steps.deploy.outputs.DEPLOYMENT_URL }} + BRANCH: ${{ github.ref_name }} + run: | + case "$BRANCH" in + dev) DOMAIN="arkenv-dev.vercel.app" ;; + v1) DOMAIN="arkenv-v1.vercel.app" ;; + *) + echo "No branch domain mapping for $BRANCH; skipping alias." + exit 0 + ;; + esac + if [ -z "$DEPLOYMENT_URL" ]; then + echo "No deployment URL; skipping alias." + exit 0 + fi + echo "Aliasing $DEPLOYMENT_URL -> $DOMAIN" + node scripts/vercel-wrapper.cjs alias set "$DEPLOYMENT_URL" "$DOMAIN" --token="$VERCEL_TOKEN" - name: Generate GitHub App Token id: generate-token uses: actions/create-github-app-token@v3 - if: env.APP_PRIVATE_KEY != '' + if: vars.APP_ID != '' with: client-id: ${{ vars.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} - name: Comment PR - if: github.event_name == 'pull_request' && steps.affected.outputs.is_affected == 'true' + if: github.event_name != 'push' && steps.affected.outputs.is_affected == 'true' uses: actions/github-script@v9 with: github-token: ${{ steps.generate-token.outputs.token || secrets.GITHUB_TOKEN }} diff --git a/.hallmark/log.json b/.hallmark/log.json new file mode 100644 index 000000000..2dc5d2f1b --- /dev/null +++ b/.hallmark/log.json @@ -0,0 +1,11 @@ +[ + { + "date": "2026-06-05", + "macrostructure": "Workbench", + "theme": "custom", + "theme_axes": "dark / mono / cool", + "vibe": "nautical terminal, ocean-deep navy, marine-cyan accent", + "enrichment": "none", + "brief": "Nautical terminal-forward homepage redesign" + } +] diff --git a/.hallmark/preflight.json b/.hallmark/preflight.json new file mode 100644 index 000000000..3b22cf081 --- /dev/null +++ b/.hallmark/preflight.json @@ -0,0 +1,7 @@ +{ + "fonts": "Geist + JetBrains Mono (next/font/google, apps/www/app/layout.tsx L10, L14)", + "palette": "HSL & Hex custom properties (apps/www/app/styles/theme/light.css :root)", + "motion": "no framework motion library detected (package.json)", + "spacing": "Tailwind spacing (apps/www/app/styles/theme/light.css)", + "framework": "Next.js 16 (app router)" +} diff --git a/apps/playwright-www/tests/homepage.test.ts b/apps/playwright-www/tests/homepage.test.ts index 9ed299604..6a4b4810f 100644 --- a/apps/playwright-www/tests/homepage.test.ts +++ b/apps/playwright-www/tests/homepage.test.ts @@ -1,26 +1,28 @@ import { expect, test } from "@playwright/test"; test.describe("Homepage Interactivity", () => { - test("should have functional 'Quickstart' button", async ({ page }) => { + test("should have functional docs link", async ({ page }) => { await page.goto("/"); - const sailButton = page.locator("a[href='/docs/arkenv/quickstart']"); - await expect(sailButton).toBeVisible(); + const docsLink = page.locator("a[href='/docs/arkenv']").first(); + await expect(docsLink).toBeVisible(); await Promise.all([ - page.waitForURL("**/docs/arkenv/quickstart", { timeout: 30000 }), - sailButton.click(), + page.waitForURL("**/docs/arkenv", { timeout: 30000 }), + docsLink.click(), ]); - await expect(page).toHaveURL("/docs/arkenv/quickstart"); + await expect(page).toHaveURL("/docs/arkenv"); }); - test("should have GitHub star link with correct security attributes", async ({ + test("should have GitHub link with correct security attributes", async ({ page, }) => { await page.goto("/"); await page.waitForLoadState("networkidle"); - // On desktop, the header GitHub action is visible; mobile CTA is hidden (`sm:hidden`). - const githubLink = page.getByRole("link", { name: /^GitHub$/i }); + // Header has desktop + menu GitHub actions; assert the first visible one. + const githubLink = page + .getByRole("link", { name: "GitHub", exact: true }) + .first(); await expect(githubLink).toBeVisible(); await expect(githubLink).toHaveAttribute("target", "_blank"); diff --git a/apps/www/app/(home)/home-page-client.tsx b/apps/www/app/(home)/home-page-client.tsx new file mode 100644 index 000000000..bee53844e --- /dev/null +++ b/apps/www/app/(home)/home-page-client.tsx @@ -0,0 +1,395 @@ +"use client"; + +import { ExternalLink } from "@arkenv/fumadocs-ui/components"; +import { + Anchor, + ArrowRight, + Check, + Compass, + Copy, + Cpu, + FileCode, + ExternalLink as LinkIcon, + ShieldAlert, + Terminal, +} from "lucide-react"; +import Image from "next/image"; +import Link from "next/link"; +import { useState } from "react"; +import { AnnouncementBadge } from "~/components/announcement-badge"; +import { CompatibilityRails } from "~/components/page/compatibility-rails"; +import { VideoDemo } from "~/components/page/video-demo"; +import { ThemeToggle } from "~/components/ui/theme-toggle"; +import { useCopyCommand } from "~/hooks/use-copy-command"; + +export function HomePageClient() { + const initCommand = "npx arkenv init"; + const { copied, copy } = useCopyCommand(initCommand); + const [activeTab, setActiveTab] = useState<"init" | "schema" | "fail">( + "init", + ); + + return ( +
+ {/* Nautical Radar/Sonar background visual - absolute positioned */} +