Compare commits

...

48 Commits

Author SHA1 Message Date
github-actions[bot]
6ad1cd1900 release(cli): 1.34.0 (#3553)
Co-authored-by: dbarrosop <dbarrosop@users.noreply.github.com>
2025-10-09 11:48:54 +02:00
David Barroso
5c5223d871 chore(cli): bump nhost/dashboard to 2.38.4 (#3539)
Co-authored-by: dbarrosop <dbarrosop@users.noreply.github.com>
2025-10-09 11:39:23 +02:00
github-actions[bot]
00ef639455 release(dashboard): 2.38.4 (#3574)
Co-authored-by: dbarrosop <dbarrosop@users.noreply.github.com>
2025-10-09 10:38:57 +02:00
David Barroso
a54da9c072 fix(dashboard): remove NODE_ENV from restricted env vars (#3573) 2025-10-09 10:36:25 +02:00
David Barroso
63edfa2600 feat(cli): MCP refactor and documentation prior to official release (#3571) 2025-10-09 08:55:10 +02:00
David Barroso
381baf2e51 feat(docs): added react urql guide (#3570) 2025-10-08 11:02:50 +02:00
dependabot[bot]
951ce168e8 chore(ci): bump github/codeql-action from 3 to 4 (#3569)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-08 08:34:39 +02:00
github-actions[bot]
be8f4e5b1b release(dashboard): 2.38.3 (#3563)
Co-authored-by: dbm03 <dbm03@users.noreply.github.com>
2025-10-07 15:03:40 +02:00
David BM
010573cc31 fix(dashboard): improve remote schema preview search (#3558) 2025-10-07 14:37:21 +02:00
David BM
629bbe7a78 fix(dashboard): remote schema edit graphql customizations, default value for root field namespace is empty (#3565) 2025-10-06 14:56:46 +02:00
David BM
166889be1b fix(dashboard): show paused banner in Run page (#3564) 2025-10-06 10:04:30 +02:00
David BM
c80f6292c6 fix(dashboard): show paused banner in remote schemas/database page if project is paused (#3557) 2025-10-06 08:59:49 +02:00
David Barroso
5c7a6788b4 feat(cli): mcp: added support for environment variables in the configuration (#3556) 2025-10-03 10:24:38 +02:00
David Barroso
6ae4e17ffe feat(cli): mcp: move configuration to .nhost folder and integrate cloud credentials (#3555) 2025-10-03 10:17:41 +02:00
David Barroso
515fde79a3 chore(nixops): update nhost-cli (#3554) 2025-10-02 16:59:16 +02:00
David Barroso
545d0e33d9 feat(cli): added mcp server functionality from mcp-nhost (#3550) 2025-10-02 14:53:55 +02:00
github-actions[bot]
9d1853742e release(cli): 1.33.0 (#3547)
Co-authored-by: dbarrosop <dbarrosop@users.noreply.github.com>
2025-10-02 14:32:21 +02:00
David Barroso
c0beb07b77 chore(cli): update certs (#3552) 2025-10-02 14:29:55 +02:00
David Barroso
3a79db6277 fix(cli): fix breaking change in go-getter dependency (#3551) 2025-10-02 14:03:00 +02:00
David Barroso
3378739967 fix(cli): disable tls on AUTH_SERVER_URL when auth uses custom port (#3549) 2025-10-02 11:13:05 +02:00
David Barroso
e31ac82a55 fix(examples/docker-compose): added missing .env.example (#3548) 2025-10-02 10:55:26 +02:00
David Barroso
bdd88161c6 feat(cli): migrate from urfave/v2 to urfave/v3 (#3545) 2025-10-02 09:13:35 +02:00
github-actions[bot]
17e8acb368 release(cli): 1.32.2 (#3544)
Co-authored-by: dbarrosop <dbarrosop@users.noreply.github.com>
2025-10-01 08:40:31 +02:00
David Barroso
a2bc1fee6f chore(cli): remove hasura- prefix from auth/storage images (#3538) 2025-10-01 08:33:06 +02:00
github-actions[bot]
ba2ac461e1 release(dashboard): 2.38.2 (#3541)
Co-authored-by: dbarrosop <dbarrosop@users.noreply.github.com>
2025-09-30 17:01:40 +02:00
github-actions[bot]
d2cc79e838 release(services/storage): 0.8.1 (#3543)
Co-authored-by: dbarrosop <dbarrosop@users.noreply.github.com>
2025-09-30 16:45:02 +02:00
David Barroso
31a30cd460 fix(storage): pass buildVersion correctly (#3542) 2025-09-30 16:39:23 +02:00
David BM
f5ecbdac22 fix(dashboard): update remote schemas url tooltip (#3540) 2025-09-30 15:11:43 +02:00
github-actions[bot]
565aee6d34 release(dashboard): 2.38.1 (#3535)
Co-authored-by: dbarrosop <dbarrosop@users.noreply.github.com>
2025-09-30 13:21:54 +02:00
David Barroso
47013da462 chore(ci): validate PR title (#3537) 2025-09-30 13:20:02 +02:00
dependabot[bot]
2e701456d3 chore(ci): bump actions/checkout from 4 to 5 (#3526)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: David Barroso <dbarrosop@dravetech.com>
2025-09-30 11:41:42 +02:00
dependabot[bot]
f08bbc62f6 chore(ci): bump aws-actions/configure-aws-credentials from 4 to 5 (#3522)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: David Barroso <dbarrosop@dravetech.com>
2025-09-30 11:40:09 +02:00
robertkasza
93c233deb0 fix (dashboard): delay generating auth service url when creating users (#3530) 2025-09-30 09:22:46 +02:00
David Barroso
ff2a84aa37 chore(ci): use variables in gen_ai_review workflow to configure models (#3534) 2025-09-30 08:45:56 +02:00
github-actions[bot]
3ca082d368 release(cli): 1.32.1 (#3533)
Co-authored-by: dbarrosop <dbarrosop@users.noreply.github.com>
2025-09-29 17:51:37 +02:00
David Barroso
cec16c6b89 chore(cli): update schema (#3529) 2025-09-29 17:48:59 +02:00
David Barroso
543f2c2b0e fix(nixops): export correctly (#3531) 2025-09-29 17:20:51 +02:00
David Barroso
53ac9263c1 fix(ci): specify base when bumping the dashboard in the cli (#3532) 2025-09-29 14:46:29 +02:00
github-actions[bot]
81b304a715 release(dashboard): 2.38.0 (#3503)
Co-authored-by: robertkasza <robertkasza@users.noreply.github.com>
2025-09-29 13:44:58 +02:00
robertkasza
ac9956bcdb feat(dashboard): datatable column header redesign (#3500) 2025-09-29 13:34:38 +02:00
David Barroso
7d2bc4c06e chore(ci): minor improvements to the ci (#3527) 2025-09-29 12:02:04 +02:00
David Barroso
48ef43202c feat(docs): added links to react-apollo and react-query guides (#3528) 2025-09-29 10:28:45 +02:00
David BM
e6ae494336 feat(dashboard): add remote schemas (#3299)
Co-authored-by: David Barroso Murcia <davidbm@air-m4.local>
Co-authored-by: David Barroso <dbarrosop@dravetech.com>
2025-09-26 16:14:34 +02:00
David Barroso
092e98358f chore(ci): implement storage releases (#3525) 2025-09-26 15:03:29 +02:00
dependabot[bot]
dd945daa1a chore(ci): bump nixbuild/nix-quick-install-action from 26 to 34 (#3524) 2025-09-26 13:52:23 +02:00
dependabot[bot]
0fe38e206b chore(ci): bump github/codeql-action from 2 to 3 (#3523) 2025-09-26 13:52:14 +02:00
dependabot[bot]
373657339c chore(ci): bump actions/checkout from 4 to 5 (#3521) 2025-09-26 13:52:02 +02:00
dependabot[bot]
3833158107 chore(ci): bump peter-evans/create-pull-request from 6 to 7 (#3520) 2025-09-26 13:51:55 +02:00
2325 changed files with 318123 additions and 685228 deletions

View File

@@ -0,0 +1,41 @@
---
name: "Validate PR Title"
description: "Validates that PR title follows the required format: TYPE(PKG): SUMMARY"
inputs:
pr_title:
description: "The PR title to validate"
required: true
runs:
using: "composite"
steps:
- name: "Validate PR title format"
shell: bash
run: |
PR_TITLE="${{ inputs.pr_title }}"
echo "Validating PR title: $PR_TITLE"
# Define valid types and packages
VALID_TYPES="feat|fix|chore"
VALID_PKGS="ci|cli|codegen|dashboard|deps|docs|examples|mintlify-openapi|nhost-js|nixops|storage"
# Check if title matches the pattern TYPE(PKG): SUMMARY
if [[ ! "$PR_TITLE" =~ ^(${VALID_TYPES})\((${VALID_PKGS})\):\ .+ ]]; then
echo "❌ PR title does not follow the required format!"
echo ""
echo "Expected format: TYPE(PKG): SUMMARY"
echo ""
echo "Valid TYPEs:"
echo " - feat: mark this pull request as a feature"
echo " - fix: mark this pull request as a bug fix"
echo " - chore: mark this pull request as a maintenance item"
echo ""
echo "Valid PKGs:"
echo " - ci, cli, codegen, dashboard, deps, docs, examples,"
echo " - mintlify-openapi, nhost-js, nixops, storage"
echo ""
echo "Example: feat(cli): add new command for database migrations"
exit 1
fi
echo "✅ PR title is valid!"

View File

@@ -7,7 +7,7 @@ updates:
interval: "daily"
time: "04:00"
commit-message:
prefix: "chore"
prefix: "chore(ci)"
labels:
- "dependencies"
- "github_actions"

View File

@@ -36,9 +36,8 @@ jobs:
cli:
needs: extract-project
if: needs.extract-project.outputs.project == 'cli'
uses: ./.github/workflows/cli_release.yaml
uses: ./.github/workflows/cli_wf_release.yaml
with:
NAME: dashboard
GIT_REF: ${{ github.sha }}
VERSION: ${{ needs.extract-project.outputs.version }}
secrets:
@@ -52,9 +51,8 @@ jobs:
dashboard:
needs: extract-project
if: needs.extract-project.outputs.project == '@nhost/dashboard'
uses: ./.github/workflows/dashboard_release.yaml
uses: ./.github/workflows/dashboard_wf_release.yaml
with:
NAME: dashboard
GIT_REF: ${{ github.sha }}
VERSION: ${{ needs.extract-project.outputs.version }}
secrets:
@@ -83,3 +81,17 @@ jobs:
NIX_CACHE_PRIV_KEY: ${{ secrets.NIX_CACHE_PRIV_KEY }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_PRODUCTION }}
storage:
needs: extract-project
if: needs.extract-project.outputs.project == 'storage'
uses: ./.github/workflows/storage_wf_release.yaml
with:
GIT_REF: ${{ github.sha }}
VERSION: ${{ needs.extract-project.outputs.version }}
secrets:
AWS_ACCOUNT_ID: ${{ secrets.AWS_PRODUCTION_CORE_ACCOUNT_ID }}
NIX_CACHE_PUB_KEY: ${{ secrets.NIX_CACHE_PUB_KEY }}
NIX_CACHE_PRIV_KEY: ${{ secrets.NIX_CACHE_PRIV_KEY }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}

View File

@@ -27,6 +27,10 @@ on:
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || format('push-{0}', github.sha) }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
check-permissions:
runs-on: ubuntu-latest
@@ -70,7 +74,7 @@ jobs:
NIX_CACHE_PRIV_KEY: ${{ secrets.NIX_CACHE_PRIV_KEY }}
test_cli_build:
uses: ./.github/workflows/cli_test_new_project.yaml
uses: ./.github/workflows/cli_wf_test_new_project.yaml
needs:
- check-permissions
- build_artifacts

View File

@@ -3,9 +3,6 @@ name: "cli: release"
on:
workflow_call:
inputs:
NAME:
required: true
type: string
GIT_REF:
required: true
type: string
@@ -71,7 +68,7 @@ jobs:
ref: ${{ inputs.GIT_REF }}
- name: Configure aws
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-actions-nhost-${{ github.event.repository.name }}
aws-region: eu-central-1
@@ -79,7 +76,7 @@ jobs:
- name: Setup Nix with Cache
uses: ./.github/actions/setup-nix
with:
NAME: ${{ inputs.NAME }}
NAME: cli
NIX_CACHE_PUB_KEY: ${{ secrets.NIX_CACHE_PUB_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -50,7 +50,7 @@ jobs:
comment_on_pr: false
- name: Configure aws
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-actions-nhost-${{ github.event.repository.name }}
aws-region: eu-central-1

View File

@@ -25,6 +25,10 @@ on:
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || format('push-{0}', github.sha) }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
check-permissions:
runs-on: ubuntu-latest

View File

@@ -32,6 +32,10 @@ on:
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || format('push-{0}', github.sha) }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
check-permissions:
runs-on: ubuntu-latest
@@ -59,6 +63,7 @@ jobs:
VERCEL_TEAM_ID: ${{ secrets.DASHBOARD_VERCEL_TEAM_ID }}
VERCEL_PROJECT_ID: ${{ secrets.DASHBOARD_STAGING_VERCEL_PROJECT_ID }}
VERCEL_DEPLOY_TOKEN: ${{ secrets.DASHBOARD_VERCEL_DEPLOY_TOKEN }}
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
build_artifacts:
@@ -71,6 +76,7 @@ jobs:
GIT_REF: ${{ github.sha }}
VERSION: 0.0.0-dev # we use a fixed version here to avoid unnecessary rebuilds
DOCKER: true
OS_MATRIX: '["blacksmith-2vcpu-ubuntu-2404"]'
secrets:
AWS_ACCOUNT_ID: ${{ secrets.AWS_PRODUCTION_CORE_ACCOUNT_ID }}
NIX_CACHE_PUB_KEY: ${{ secrets.NIX_CACHE_PUB_KEY }}
@@ -93,7 +99,7 @@ jobs:
e2e_staging:
uses: ./.github/workflows/wf_dashboard_e2e_staging.yaml
uses: ./.github/workflows/dashboard_wf_e2e_staging.yaml
needs:
- check-permissions
- deploy-vercel

View File

@@ -4,6 +4,32 @@ on:
push:
branches:
- main
paths:
- '.github/workflows/wf_build_artifacts.yaml'
- '.github/workflows/wf_check.yaml'
- '.github/workflows/dashboard_checks.yaml'
# common build
- 'flake.nix'
- 'flake.lock'
- 'nixops/**'
- 'build/**'
# common javascript
- ".npmrc"
- ".prettierignore"
- ".prettierrc.js"
- "audit-ci.jsonc"
- "package.json"
- "pnpm-workspace.yaml"
- "pnpm-lock.yaml"
- "turbo.json"
# dashboard
- "dashboard/**"
# nhost-js
- packages/nhost-js/**
jobs:
deploy-vercel:
@@ -19,4 +45,5 @@ jobs:
VERCEL_TEAM_ID: ${{ secrets.DASHBOARD_VERCEL_TEAM_ID }}
VERCEL_PROJECT_ID: ${{ secrets.DASHBOARD_STAGING_VERCEL_PROJECT_ID }}
VERCEL_DEPLOY_TOKEN: ${{ secrets.DASHBOARD_VERCEL_DEPLOY_TOKEN }}
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_STAGING }}

View File

@@ -59,6 +59,10 @@ on:
PLAYWRIGHT_REPORT_ENCRYPTION_KEY:
required: true
concurrency:
group: dashboard-e2e-staging
cancel-in-progress: false
env:
NEXT_PUBLIC_ENV: dev
NEXT_TELEMETRY_DISABLED: 1
@@ -101,7 +105,7 @@ jobs:
comment_on_pr: false
- name: Configure aws
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-actions-nhost-${{ github.event.repository.name }}
aws-region: eu-central-1

View File

@@ -4,9 +4,6 @@ name: 'dashboard: release'
on:
workflow_call:
inputs:
NAME:
required: true
type: string
GIT_REF:
required: true
type: string
@@ -49,6 +46,7 @@ jobs:
VERCEL_TEAM_ID: ${{ secrets.VERCEL_TEAM_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
VERCEL_DEPLOY_TOKEN: ${{ secrets.VERCEL_DEPLOY_TOKEN }}
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
build_artifacts:
@@ -90,6 +88,7 @@ jobs:
- name: Bump version in source code
run: |
find cli -type f -exec sed -i 's/"nhost\/dashboard:[^"]*"/"nhost\/dashboard:${{ inputs.VERSION }}"/g' {} +
sed -i 's/"nhost\/dashboard:[^"]*"/"nhost\/dashboard:${{ inputs.VERSION }}"/g' docs/reference/cli/commands.mdx
- name: "Create Pull Request"
uses: peter-evans/create-pull-request@v7
@@ -100,6 +99,7 @@ jobs:
committer: GitHub <noreply@github.com>
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
body: |
This PR bumps the Nhost Dashboard Docker image to version ${{ needs.version.outputs.dashboardVersion }}.
This PR bumps the Nhost Dashboard Docker image to version ${{ inputs.VERSION }}.
branch: bump-dashboard-version
base: main
delete-branch: true

View File

@@ -27,10 +27,17 @@ on:
# nhost-js
- packages/nhost-js/**
# cli
- cli/**
push:
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || format('push-{0}', github.sha) }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
check-permissions:
runs-on: ubuntu-latest

View File

@@ -41,6 +41,10 @@ on:
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || format('push-{0}', github.sha) }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
check-permissions:
runs-on: ubuntu-latest
@@ -77,6 +81,7 @@ jobs:
GIT_REF: ${{ github.sha }}
VERSION: 0.0.0-dev # we use a fixed version here to avoid unnecessary rebuilds
DOCKER: false
OS_MATRIX: '["blacksmith-2vcpu-ubuntu-2404"]'
secrets:
AWS_ACCOUNT_ID: ${{ secrets.AWS_PRODUCTION_CORE_ACCOUNT_ID }}
NIX_CACHE_PUB_KEY: ${{ secrets.NIX_CACHE_PUB_KEY }}

View File

@@ -41,6 +41,10 @@ on:
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || format('push-{0}', github.sha) }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
check-permissions:
runs-on: ubuntu-latest
@@ -77,6 +81,7 @@ jobs:
GIT_REF: ${{ github.sha }}
VERSION: 0.0.0-dev # we use a fixed version here to avoid unnecessary rebuilds
DOCKER: false
OS_MATRIX: '["blacksmith-2vcpu-ubuntu-2404"]'
secrets:
AWS_ACCOUNT_ID: ${{ secrets.AWS_PRODUCTION_CORE_ACCOUNT_ID }}
NIX_CACHE_PUB_KEY: ${{ secrets.NIX_CACHE_PUB_KEY }}

View File

@@ -41,6 +41,10 @@ on:
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || format('push-{0}', github.sha) }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
check-permissions:
runs-on: ubuntu-latest
@@ -77,6 +81,7 @@ jobs:
GIT_REF: ${{ github.sha }}
VERSION: 0.0.0-dev # we use a fixed version here to avoid unnecessary rebuilds
DOCKER: false
OS_MATRIX: '["blacksmith-2vcpu-ubuntu-2404"]'
secrets:
AWS_ACCOUNT_ID: ${{ secrets.AWS_PRODUCTION_CORE_ACCOUNT_ID }}
NIX_CACHE_PUB_KEY: ${{ secrets.NIX_CACHE_PUB_KEY }}

View File

@@ -21,6 +21,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
OPENAI_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
config.model: "anthropic/claude-sonnet-4-20250514"
config.model_turbo: "anthropic/claude-sonnet-4-20250514"
config.model: ${{ vars.GEN_AI_MODEL }}
config.model_turbo: $${{ vars.GEN_AI_MODEL_TURBO }}
config.max_model_tokens: 200000
ignore.glob: "['pnpm-lock.yaml','**/pnpm-lock.yaml', 'vendor/**','**/client_gen.go','**/models_gen.go','**/generated.go','**/*.gen.go']"

View File

@@ -1,8 +1,6 @@
name: "CodeQL"
on:
push: {}
pull_request: {}
schedule:
- cron: '20 23 * * 3'
@@ -18,7 +16,7 @@ jobs:
strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
language: [ 'javascript', 'go' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
@@ -28,7 +26,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -39,7 +37,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v4
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -53,4 +51,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v4

View File

@@ -18,12 +18,12 @@ jobs:
uses: actions/checkout@v5
- name: Configure aws
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_PRODUCTION_CORE_ACCOUNT_ID }}:role/github-actions-nhost-${{ github.event.repository.name }}
aws-region: eu-central-1
- uses: nixbuild/nix-quick-install-action@v26
- uses: nixbuild/nix-quick-install-action@v34
with:
nix_version: 2.16.2
nix_conf: |
@@ -51,7 +51,7 @@ jobs:
"
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: Update dependencies

View File

@@ -38,6 +38,10 @@ on:
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || format('push-{0}', github.sha) }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
check-permissions:
runs-on: ubuntu-latest

View File

@@ -17,6 +17,10 @@ on:
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || format('push-{0}', github.sha) }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
check-permissions:
runs-on: ubuntu-latest

View File

@@ -21,11 +21,15 @@ on:
- 'vendor/**'
# storage
- 'storage/**'
- 'services/storage/**'
push:
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || format('push-{0}', github.sha) }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
check-permissions:
runs-on: ubuntu-latest

View File

@@ -0,0 +1,60 @@
---
name: "storage: release"
on:
workflow_call:
inputs:
GIT_REF:
required: true
type: string
VERSION:
required: true
type: string
secrets:
AWS_ACCOUNT_ID:
required: true
NIX_CACHE_PUB_KEY:
required: true
NIX_CACHE_PRIV_KEY:
required: true
DOCKER_USERNAME:
required: true
DOCKER_PASSWORD:
required: true
jobs:
build_artifacts:
uses: ./.github/workflows/wf_build_artifacts.yaml
with:
NAME: storage
PATH: services/storage
GIT_REF: ${{ inputs.GIT_REF }}
VERSION: ${{ inputs.VERSION }}
DOCKER: true
secrets:
AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
NIX_CACHE_PUB_KEY: ${{ secrets.NIX_CACHE_PUB_KEY }}
NIX_CACHE_PRIV_KEY: ${{ secrets.NIX_CACHE_PRIV_KEY }}
push-docker-hub:
uses: ./.github/workflows/wf_docker_push_image.yaml
needs:
- build_artifacts
with:
NAME: storage
PATH: services/storage
VERSION: ${{ inputs.VERSION }}
secrets:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
push-docker-ecr:
uses: ./.github/workflows/wf_docker_push_image_ecr.yaml
needs:
- build_artifacts
with:
NAME: storage
PATH: services/storage
VERSION: ${{ inputs.VERSION }}
secrets:
AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
CONTAINER_REGISTRY: ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.eu-central-1.amazonaws.com

View File

@@ -17,6 +17,10 @@ on:
DOCKER:
type: boolean
required: true
OS_MATRIX:
type: string
required: false
default: '["blacksmith-4vcpu-ubuntu-2404-arm", "blacksmith-2vcpu-ubuntu-2404"]'
secrets:
AWS_ACCOUNT_ID:
required: true
@@ -37,7 +41,7 @@ jobs:
strategy:
matrix:
os: [blacksmith-4vcpu-ubuntu-2404-arm, blacksmith-2vcpu-ubuntu-2404]
os: ${{ fromJSON(inputs.OS_MATRIX) }}
fail-fast: true
runs-on: ${{ matrix.os }}
@@ -49,8 +53,14 @@ jobs:
with:
ref: ${{ inputs.GIT_REF }}
- name: "Validate PR title"
uses: ./.github/actions/validate-pr-title
with:
pr_title: ${{ github.event.pull_request.title }}
if: ${{ github.event_name == 'pull_request' }}
- name: Configure aws
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-actions-nhost-${{ github.event.repository.name }}
aws-region: eu-central-1

View File

@@ -44,13 +44,19 @@ jobs:
with:
ref: ${{ inputs.GIT_REF }}
- name: "Validate PR title"
uses: ./.github/actions/validate-pr-title
with:
pr_title: ${{ github.event.pull_request.title }}
if: ${{ github.event_name == 'pull_request' }}
- name: Collect Workflow Telemetry
uses: catchpoint/workflow-telemetry-action@v2
with:
comment_on_pr: false
- name: Configure aws
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-actions-nhost-${{ github.event.repository.name }}
aws-region: eu-central-1

View File

@@ -27,6 +27,8 @@ on:
required: true
DISCORD_WEBHOOK:
required: false
TURBO_TOKEN:
required: true
outputs:
preview-url:
@@ -52,7 +54,7 @@ jobs:
ref: ${{ inputs.GIT_REF }}
- name: Configure aws
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-actions-nhost-${{ github.event.repository.name }}
aws-region: eu-central-1
@@ -69,6 +71,8 @@ jobs:
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_TEAM_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: nhost
run: |
TARGET_OPTS="--target=${{ inputs.ENVIRONMENT }}"
echo "Deploying to: ${{ inputs.ENVIRONMENT }}..."

View File

@@ -33,7 +33,7 @@ jobs:
steps:
- name: "Check out repository"
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
submodules: true

View File

@@ -0,0 +1,84 @@
---
on:
workflow_call:
inputs:
NAME:
type: string
required: true
PATH:
type: string
required: true
VERSION:
type: string
required: true
secrets:
AWS_ACCOUNT_ID:
required: true
CONTAINER_REGISTRY:
required: true
jobs:
push-to-registry:
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
id-token: write
contents: write
defaults:
run:
working-directory: ${{ inputs.PATH }}
steps:
- name: "Check out repository"
uses: actions/checkout@v5
with:
fetch-depth: 0
submodules: true
- name: Configure aws
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-actions-nhost-${{ github.event.repository.name }}
aws-region: eu-central-1
- name: "Login to Amazon ECR"
uses: aws-actions/amazon-ecr-login@v2
with:
mask-password: 'true'
- name: "Compute common env vars"
id: vars
run: |
echo "VERSION=$(make get-version VER=${{ inputs.VERSION }})" >> $GITHUB_OUTPUT
- name: "Get artifacts"
uses: actions/download-artifact@v5
with:
path: ~/artifacts
- name: "Inspect artifacts"
run: find ~/artifacts
- name: "Push docker image to docker hub"
run: |
export NAME=${{ inputs.NAME }}
export VERSION=${{ steps.vars.outputs.VERSION }}
export CONTAINER_REGISTRY=${{ secrets.CONTAINER_REGISTRY }}
export CONTAINER_NAME=$CONTAINER_REGISTRY/$NAME
for ARCH in "x86_64" "aarch64"; do
skopeo copy --insecure-policy \
dir:/home/runner/artifacts/${{ inputs.NAME }}-docker-image-$ARCH-$VERSION \
docker-daemon:$CONTAINER_NAME:$VERSION-$ARCH
docker push $CONTAINER_NAME:$VERSION-$ARCH
done
docker manifest create \
$CONTAINER_NAME:$VERSION \
--amend $CONTAINER_NAME:$VERSION-x86_64 \
--amend $CONTAINER_NAME:$VERSION-aarch64
docker manifest push $CONTAINER_NAME:$VERSION

View File

@@ -42,7 +42,7 @@ jobs:
uses: actions/checkout@v5
- name: Configure aws
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-actions-nhost-${{ github.event.repository.name }}
aws-region: eu-central-1

View File

@@ -7,6 +7,8 @@ linters:
settings:
funlen:
lines: 65
wsl_v5:
allow-whole-block: true
disable:
- canonicalheader
- depguard

View File

@@ -1,20 +0,0 @@
## Description
<!--
Use one of the following title prefix to categorize the pull request:
feat: mark this pull request as a feature
fix: mark this pull request as a bug fix
chore: mark this pull request as a maintenance item
To auto merge this pull request when it was approved
by another member of the organization: set the label `auto-merge`
-->
## Problem
A short description of the problem this PR is addressing.
## Solution
A short description of the chosen method to resolve the problem
with an overview of the logic and implementation details when needed.
## Notes
Other notes that you want to share but do not fit into _Problem_ or _Solution_.

36
cli/.github/cert.sh vendored
View File

@@ -1,36 +0,0 @@
#!/bin/bash
set -euo pipefail
mkdir -p /tmp/letsencrypt
echo "Generating SSL certificate for hostnames: local.nhost.run, local.graphql.nhost.run, local.auth.nhost.run, local.storage.nhost.run, local.functions.nhost.run, local.mail.nhost.run"
docker run --rm \
--name certbot \
-e AWS_ACCESS_KEY_ID \
-e AWS_SECRET_ACCESS_KEY \
-e AWS_SESSION_TOKEN \
-e AWS_REGION \
-v /tmp/letsencrypt:/etc/letsencrypt \
-v /tmp/letsencrypt:/var/lib/letsencrypt \
certbot/dns-route53 certonly --dns-route53 --dns-route53-propagation-seconds 60 \
-d local.auth.nhost.run \
-d local.dashboard.nhost.run \
-d local.db.nhost.run \
-d local.functions.nhost.run \
-d local.graphql.nhost.run \
-d local.hasura.nhost.run \
-d local.mailhog.nhost.run \
-d local.storage.nhost.run \
-d *.auth.local.nhost.run \
-d *.dashboard.local.nhost.run \
-d *.db.local.nhost.run \
-d *.functions.local.nhost.run \
-d *.graphql.local.nhost.run \
-d *.hasura.local.nhost.run \
-d *.mailhog.local.nhost.run \
-d *.storage.local.nhost.run \
-m 'admin@nhost.io' --non-interactive --agree-tos --server https://acme-v02.api.letsencrypt.org/directory
sudo cp /tmp/letsencrypt/live/local.db.nhost.run/fullchain.pem ssl/.ssl/
sudo cp /tmp/letsencrypt/live/local.db.nhost.run/privkey.pem ssl/.ssl/

View File

@@ -1,8 +0,0 @@
labels:
'feature':
- '^(?i:feat)'
- '^(?i:feature)'
'fix':
- '^(?i:fix)'
'chore':
- '^(?i:chore)'

View File

@@ -1,39 +0,0 @@
name-template: 'v$RESOLVED_VERSION'
tag-template: 'v$RESOLVED_VERSION'
categories:
- title: '🚀 Features'
label: 'feature'
- title: '🐛 Bug Fixes'
label: 'fix'
- title: '🧰 Maintenance'
label: 'chore'
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.
version-resolver:
major:
labels:
- 'major'
minor:
labels:
- 'minor'
patch:
labels:
- 'patch'
default: patch
autolabeler:
- label: 'feature'
title:
- '/^feat/i'
- '/^feature/i'
- label: 'fix'
title:
- '/^fix/i'
- label: 'chore'
title:
- '/^chore/i'
prerelease: true
template: |
## Changes
$CHANGES

16
cli/.github/stale.yml vendored
View File

@@ -1,16 +0,0 @@
# Configuration for probot-stale - https://github.com/probot/stale
daysUntilStale: 180
daysUntilClose: 7
limitPerRun: 30
onlyLabels: []
exemptLabels: []
exemptProjects: false
exemptMilestones: false
exemptAssignees: false
staleLabel: stale
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.

View File

@@ -1,17 +0,0 @@
# this workflow will run on all pull requests opened but in the context of the base of the pull request.
on:
pull_request_target:
types: [opened]
name: "assign labels"
jobs:
# labeler will label pull requests based on their title.
# the configuration is at .github/labeler.yml.
label_pull_request:
runs-on: ubuntu-latest
steps:
-
name: Label Pull Request
uses: jimschubert/labeler-action@v2
with:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

View File

@@ -1,53 +0,0 @@
---
name: "build certificate weekly"
on:
schedule:
- cron: '0 0 * * 1'
jobs:
run:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- name: Check out repository
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Configure aws
uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: arn:aws:iam::796351718684:role/github-actions-nhost-cli
aws-region: eu-central-1
- name: fetch let's encrypt cert
id: certs
run: |
.github/cert.sh
echo "CERT_FULL_CHAIN<<EOF" >> $GITHUB_OUTPUT
sudo cat /tmp/letsencrypt/live/local.db.nhost.run/fullchain.pem >> "$GITHUB_OUTPUT"
echo EOF >> $GITHUB_OUTPUT
echo "CERT_PRIV_KEY<<EOF" >> $GITHUB_OUTPUT
sudo cat /tmp/letsencrypt/live/local.db.nhost.run/privkey.pem >> "$GITHUB_OUTPUT"
echo EOF >> $GITHUB_OUTPUT
shell: bash
- uses: hmanzur/actions-set-secret@v2.0.0
with:
name: 'CERT_FULL_CHAIN'
value: "${{ steps.certs.outputs.CERT_FULL_CHAIN }}"
repository: nhost/cli
token: ${{ secrets.GH_PAT }}
- uses: hmanzur/actions-set-secret@v2.0.0
with:
name: 'CERT_PRIV_KEY'
value: "${{ steps.certs.outputs.CERT_PRIV_KEY }}"
repository: nhost/cli
token: ${{ secrets.GH_PAT }}

View File

@@ -1,27 +0,0 @@
---
name: "check and build"
on:
pull_request:
push:
branches:
- main
jobs:
tests:
uses: ./.github/workflows/wf_check.yaml
secrets:
NHOST_PAT: ${{ secrets.NHOST_PAT }}
build_artifacts:
strategy:
fail-fast: true
matrix:
GOOS: ["darwin", "linux"]
GOARCH: ["amd64", "arm64"]
uses: ./.github/workflows/wf_build_artifacts.yaml
with:
GOOS: ${{ matrix.GOOS }}
GOARCH: ${{ matrix.GOARCH }}
VERSION: ${{ github.sha }}
secrets:
NHOST_PAT: ${{ secrets.NHOST_PAT }}

View File

@@ -1,56 +0,0 @@
name: "CodeQL"
on:
push: {}
pull_request: {}
schedule:
- cron: '20 23 * * 3'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

View File

@@ -1,27 +0,0 @@
---
name: "gen: AI review"
on:
pull_request:
types: [opened, reopened, ready_for_review]
issue_comment:
jobs:
pr_agent_job:
if: ${{ github.event.sender.type != 'Bot' }}
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
issues: write
pull-requests: write
name: Run pr agent on every pull request, respond to user comments
steps:
- name: PR Agent action step
id: pragent
uses: Codium-ai/pr-agent@v0.29
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
OPENAI_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
config.max_model_tokens: 100000
config.model: "anthropic/claude-sonnet-4-20250514"
config.model_turbo: "anthropic/claude-sonnet-4-20250514"
ignore.glob: "['vendor/**','**/client_gen.go','**/models_gen.go','**/generated.go','**/*.gen.go']"

View File

@@ -1,91 +0,0 @@
---
name: "gen: update depenendencies"
on:
schedule:
- cron: '0 2 1 2,5,8,11 *'
jobs:
run:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: write
pull-requests: write
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure aws
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_PRODUCTION_CORE_ACCOUNT_ID }}:role/github-actions-nhost-${{ github.event.repository.name }}
aws-region: eu-central-1
- uses: nixbuild/nix-quick-install-action@v26
with:
nix_version: 2.16.2
nix_conf: |
experimental-features = nix-command flakes
sandbox = false
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
substituters = https://cache.nixos.org/?priority=40 s3://nhost-nix-cache?region=eu-central-1&priority=50
trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= ${{ secrets.NIX_CACHE_PUB_KEY }}
- name: Cache nix store
uses: actions/cache@v4
with:
path: /nix
key: nix-update-deps-${{ hashFiles('flakes.nix', 'flake.lock') }}
- name: Update nix flakes
run: nix flake update
- uses: shaunco/ssh-agent@git-repo-mapping
with:
ssh-private-key: |
${{ secrets.NHOST_BE_DEPLOY_SSH_PRIVATE_KEY}}
repo-mappings: |
github.com/nhost/be
- name: Update golang dependencies
run: |
export GOPRIVATE=github.com/nhost/be
nix develop -c bash -c "
go mod tidy
go get -u $(cat go.mod | grep nhost\/be | tr ' ' '@') ./...
go mod tidy
go mod vendor
"
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: Update dependencies
committer: GitHub <noreply@github.com>
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
signoff: false
branch: automated/update-deps
delete-branch: true
title: '[Scheduled] Update dependencies'
body: |
Dependencies updated
Note - If you see this PR and the checks haven't run, close and reopen the PR. See https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#triggering-further-workflow-runs
labels: |
dependencies
draft: false
- name: "Cache nix store on s3"
run: |
echo ${{ secrets.NIX_CACHE_PRIV_KEY }} > cache-priv-key.pem
nix build .\#devShells.x86_64-linux.default
nix store sign --key-file cache-priv-key.pem --all
nix copy --to s3://nhost-nix-cache\?region=eu-central-1 .\#devShells.x86_64-linux.default
- run: rm cache-priv-key.pem
if: always()

View File

@@ -1,35 +0,0 @@
---
name: "release"
on:
release:
types: [published]
jobs:
tests:
uses: ./.github/workflows/wf_check.yaml
secrets:
NHOST_PAT: ${{ secrets.NHOST_PAT }}
build_artifacts:
strategy:
matrix:
GOOS: ["darwin", "linux"]
GOARCH: ["amd64", "arm64"]
uses: ./.github/workflows/wf_build_artifacts.yaml
with:
GOOS: ${{ matrix.GOOS }}
GOARCH: ${{ matrix.GOARCH }}
VERSION: ${{ github.ref_name }}
secrets:
NHOST_PAT: ${{ secrets.NHOST_PAT }}
publish:
uses: ./.github/workflows/wf_publish.yaml
needs:
- tests
- build_artifacts
with:
VERSION: ${{ github.ref_name }}
secrets:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}

View File

@@ -1,17 +0,0 @@
name: "release drafter"
on:
push:
branches:
- main
jobs:
# draft your next release notes as pull requests are merged into "master"
# the configuration is at /.github/release-drafter.yml.
update_release_draft:
runs-on: ubuntu-latest
steps:
- uses: release-drafter/release-drafter@v5
with:
config-name: release-drafter.yml
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,89 +0,0 @@
---
on:
workflow_call:
inputs:
GIT_REF:
type: string
required: false
VERSION:
type: string
required: true
GOOS:
type: string
required: true
GOARCH:
type: string
required: true
secrets:
NHOST_PAT:
required: true
jobs:
artifacts:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: write
steps:
- name: "Check out repository"
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ inputs.GIT_REF }}
submodules: true
- uses: cachix/install-nix-action@v27
with:
install_url: "https://releases.nixos.org/nix/nix-2.22.3/install"
install_options: "--no-daemon"
extra_nix_config: |
experimental-features = nix-command flakes
sandbox = false
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
substituters = https://cache.nixos.org/?priority=40
trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=
- name: Compute common env vars
id: vars
run: |
echo "VERSION=$(make get-version VERSION=${{ inputs.VERSION }})" >> $GITHUB_OUTPUT
- name: "Build artifact"
run: |
make build ARCH=${{ inputs.GOARCH }} OS=${{ inputs.GOOS }}
find -L result -type f -exec cp {} nhost-cli \;
- name: "Push artifact to artifact repository"
uses: actions/upload-artifact@v4
with:
name: cli-${{ steps.vars.outputs.VERSION }}-${{ inputs.GOOS }}-${{ inputs.GOARCH }}
path: nhost-cli
retention-days: 7
- name: "Build docker-image"
run: |
make build-docker-image ARCH=${{ inputs.GOARCH }}
if: ${{ ( inputs.GOOS == 'linux' ) }}
- name: "Create a new project"
run: |
export NHOST_DOMAIN=staging.nhost.run
export NHOST_CONFIGSERVER_IMAGE=nhost/cli:${{ steps.vars.outputs.VERSION }}
mkdir new-project
cd new-project
../nhost-cli login --pat ${{ secrets.NHOST_PAT }}
../nhost-cli init
../nhost-cli up --down-on-error
../nhost-cli down
if: ${{ ( inputs.GOOS == 'linux' && inputs.GOARCH == 'amd64' ) }}
- name: "Push docker-image to artifact repository"
uses: actions/upload-artifact@v4
with:
name: cli-docker-image-${{ steps.vars.outputs.VERSION }}-${{ inputs.GOOS }}-${{ inputs.GOARCH }}
path: result
retention-days: 7
if: ${{ ( inputs.GOOS == 'linux' ) }}

View File

@@ -1,42 +0,0 @@
---
on:
workflow_call:
inputs:
GIT_REF:
type: string
required: false
secrets:
NHOST_PAT:
required: true
jobs:
tests:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: write
steps:
- name: "Check out repository"
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ inputs.GIT_REF }}
submodules: true
- uses: cachix/install-nix-action@v27
with:
install_url: "https://releases.nixos.org/nix/nix-2.22.3/install"
install_options: "--no-daemon"
extra_nix_config: |
experimental-features = nix-command flakes
sandbox = false
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
substituters = https://cache.nixos.org/?priority=40
trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=
- name: "Run checks"
run: |
export NHOST_PAT=${{ secrets.NHOST_PAT }}
make check

View File

@@ -1,93 +0,0 @@
---
on:
workflow_call:
inputs:
VERSION:
type: string
required: true
secrets:
DOCKER_USERNAME:
required: true
DOCKER_PASSWORD:
required: true
name: release
jobs:
release:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: write
steps:
- name: "Check out repository"
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ inputs.GIT_REF }}
submodules: true
- name: Compute common env vars
id: vars
run: |
echo "VERSION=$(make get-version VERSION=${{ inputs.VERSION }})" >> $GITHUB_OUTPUT
- name: "Get artifacts"
uses: actions/download-artifact@v4
with:
path: ~/artifacts
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Upload docker images
shell: bash
run: |
export VERSION=${{ steps.vars.outputs.VERSION }}
export CONTAINER_NAME=nhost/cli
skopeo copy --insecure-policy \
dir:/home/runner/artifacts/cli-docker-image-$VERSION-linux-amd64 \
docker-daemon:$CONTAINER_NAME:$VERSION-amd64
docker push $CONTAINER_NAME:$VERSION-amd64
skopeo copy --insecure-policy \
dir:/home/runner/artifacts/cli-docker-image-$VERSION-linux-arm64 \
docker-daemon:$CONTAINER_NAME:$VERSION-arm64
docker push $CONTAINER_NAME:$VERSION-arm64
docker manifest create \
$CONTAINER_NAME:$VERSION \
--amend $CONTAINER_NAME:$VERSION-amd64 \
--amend $CONTAINER_NAME:$VERSION-arm64
docker manifest push $CONTAINER_NAME:$VERSION
- name: Upload assets
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
export VERSION=${{ steps.vars.outputs.VERSION }}
mkdir upload
find ~/artifacts -type f -name "nhost-cli" -exec bash -c 'chmod +x "$0" && mv "$0" "${0//nhost-cli/cli}"' {} \;
tar cvzf upload/cli-$VERSION-darwin-amd64.tar.gz -C ~/artifacts/cli-$VERSION-darwin-amd64 cli
tar cvzf upload/cli-$VERSION-darwin-arm64.tar.gz -C ~/artifacts/cli-$VERSION-darwin-arm64 cli
tar cvzf upload/cli-$VERSION-linux-amd64.tar.gz -C ~/artifacts/cli-$VERSION-linux-amd64 cli
tar cvzf upload/cli-$VERSION-linux-arm64.tar.gz -C ~/artifacts/cli-$VERSION-linux-arm64 cli
cd upload
find . -type f -exec sha256sum {} + > ../checksums.txt
cd ..
cat checksums.txt
gh release upload \
--clobber "${{ github.ref_name }}" \
./upload/* checksums.txt

54
cli/CHANGELOG.md Normal file
View File

@@ -0,0 +1,54 @@
# Changelog
All notable changes to this project will be documented in this file.
## [cli@1.34.0] - 2025-10-09
### 🚀 Features
- *(cli)* Added mcp server functionality from mcp-nhost (#3550)
- *(cli)* Mcp: move configuration to .nhost folder and integrate cloud credentials (#3555)
- *(cli)* Mcp: added support for environment variables in the configuration (#3556)
- *(cli)* MCP refactor and documentation prior to official release (#3571)
### 🐛 Bug Fixes
- *(dashboard)* Remove NODE_ENV from restricted env vars (#3573)
### ⚙️ Miscellaneous Tasks
- *(nixops)* Update nhost-cli (#3554)
- *(cli)* Bump nhost/dashboard to 2.38.4 (#3539)
## [cli@1.33.0] - 2025-10-02
### 🚀 Features
- *(cli)* Migrate from urfave/v2 to urfave/v3 (#3545)
### 🐛 Bug Fixes
- *(cli)* Disable tls on AUTH_SERVER_URL when auth uses custom port (#3549)
- *(cli)* Fix breaking change in go-getter dependency (#3551)
### ⚙️ Miscellaneous Tasks
- *(cli)* Update certs (#3552)
## [cli@1.32.2] - 2025-10-01
### ⚙️ Miscellaneous Tasks
- *(cli)* Remove hasura- prefix from auth/storage images (#3538)
## [cli@1.32.1] - 2025-09-29
### ⚙️ Miscellaneous Tasks
- *(ci)* Minor improvements to the ci (#3527)
- *(cli)* Update schema (#3529)

View File

@@ -12,9 +12,9 @@ It's recommended to use the Nhost CLI and the [Nhost GitHub Integration](https:/
- [Nhost Dashboard](https://github.com/nhost/nhost/tree/main/dashboard)
- [Postgres Database](https://www.postgresql.org/)
- [Hasura's GraphQL Engine](https://github.com/hasura/graphql-engine)
- [Hasura Auth](https://github.com/nhost/hasura-auth)
- [Hasura Storage](https://github.com/nhost/hasura-storage)
- [GraphQL Engine](https://github.com/hasura/graphql-engine)
- [Auth](https://github.com/nhost/nhost/main/auth)
- [Storage](https://github.com/nhost/nhost/main/storage)
- [Nhost Serverless Functions](https://github.com/nhost/functions)
- [Minio S3](https://github.com/minio/minio)
- [Mailhog](https://github.com/mailhog/MailHog)
@@ -51,11 +51,18 @@ nhost up
nhost up --ui nhost
```
## MCP Server
The Nhost cli ships with an MCP server that lets you interact with your Nhost projects through AI assistants using the Model Context Protocol. It provides secure, controlled access to your GraphQL data, project configuration, and documentation—with granular permissions that let you specify exactly which queries and mutations an LLM can execute. For development, it streamlines your workflow by enabling AI-assisted schema management, metadata changes, and migrations, while providing direct access to your GraphQL schema for intelligent query building.
You can read more about the MCP server in the [MCP Server documentation](https://docs.nhost.io/platform/cli/mcp/overview).
## Documentation
- [Get started with Nhost CLI (longer version)](https://docs.nhost.io/platform/overview/get-started-with-nhost-cli)
- [Nhost CLI](https://docs.nhost.io/platform/cli)
- [Reference](https://docs.nhost.io/reference/cli)
- [MCP Server](https://docs.nhost.io/platform/cli/mcp/overview)
## Build from Source

View File

@@ -10,7 +10,7 @@ import (
"github.com/nhost/nhost/cli/nhostclient"
"github.com/nhost/nhost/cli/nhostclient/graphql"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func sanitizeName(name string) string {
@@ -55,28 +55,28 @@ func New(
}
}
func FromCLI(cCtx *cli.Context) *CliEnv {
func FromCLI(cmd *cli.Command) *CliEnv {
cwd, err := os.Getwd()
if err != nil {
panic(err)
}
return &CliEnv{
stdout: cCtx.App.Writer,
stderr: cCtx.App.ErrWriter,
stdout: cmd.Writer,
stderr: cmd.ErrWriter,
Path: NewPathStructure(
cwd,
cCtx.String(flagRootFolder),
cCtx.String(flagDotNhostFolder),
cCtx.String(flagNhostFolder),
cmd.String(flagRootFolder),
cmd.String(flagDotNhostFolder),
cmd.String(flagNhostFolder),
),
authURL: cCtx.String(flagAuthURL),
graphqlURL: cCtx.String(flagGraphqlURL),
branch: cCtx.String(flagBranch),
projectName: sanitizeName(cCtx.String(flagProjectName)),
authURL: cmd.String(flagAuthURL),
graphqlURL: cmd.String(flagGraphqlURL),
branch: cmd.String(flagBranch),
projectName: sanitizeName(cmd.String(flagProjectName)),
nhclient: nil,
nhpublicclient: nil,
localSubdomain: cCtx.String(flagLocalSubdomain),
localSubdomain: cmd.String(flagLocalSubdomain),
}
}

View File

@@ -6,7 +6,7 @@ import (
"path/filepath"
"github.com/go-git/go-git/v5"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
const (
@@ -53,42 +53,42 @@ func Flags() ([]cli.Flag, error) {
&cli.StringFlag{ //nolint:exhaustruct
Name: flagAuthURL,
Usage: "Nhost auth URL",
EnvVars: []string{"NHOST_CLI_AUTH_URL"},
Sources: cli.EnvVars("NHOST_CLI_AUTH_URL"),
Value: "https://otsispdzcwxyqzbfntmj.auth.eu-central-1.nhost.run/v1",
Hidden: true,
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagGraphqlURL,
Usage: "Nhost GraphQL URL",
EnvVars: []string{"NHOST_CLI_GRAPHQL_URL"},
Sources: cli.EnvVars("NHOST_CLI_GRAPHQL_URL"),
Value: "https://otsispdzcwxyqzbfntmj.graphql.eu-central-1.nhost.run/v1",
Hidden: true,
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagBranch,
Usage: "Git branch name. If not set, it will be detected from the current git repository. This flag is used to dynamically create docker volumes for each branch. If you want to have a static volume name or if you are not using git, set this flag to a static value.", //nolint:lll
EnvVars: []string{"BRANCH"},
Sources: cli.EnvVars("BRANCH"),
Value: branch,
Hidden: false,
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagRootFolder,
Usage: "Root folder of project\n\t",
EnvVars: []string{"NHOST_ROOT_FOLDER"},
Sources: cli.EnvVars("NHOST_ROOT_FOLDER"),
Value: workingDir,
Category: "Project structure",
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagDotNhostFolder,
Usage: "Path to .nhost folder\n\t",
EnvVars: []string{"NHOST_DOT_NHOST_FOLDER"},
Sources: cli.EnvVars("NHOST_DOT_NHOST_FOLDER"),
Value: dotNhostFolder,
Category: "Project structure",
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagNhostFolder,
Usage: "Path to nhost folder\n\t",
EnvVars: []string{"NHOST_NHOST_FOLDER"},
Sources: cli.EnvVars("NHOST_NHOST_FOLDER"),
Value: nhostFolder,
Category: "Project structure",
},
@@ -96,13 +96,13 @@ func Flags() ([]cli.Flag, error) {
Name: flagProjectName,
Usage: "Project name",
Value: filepath.Base(fullWorkingDir),
EnvVars: []string{"NHOST_PROJECT_NAME"},
Sources: cli.EnvVars("NHOST_PROJECT_NAME"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagLocalSubdomain,
Usage: "Local subdomain to reach the development environment",
Value: "local",
EnvVars: []string{"NHOST_LOCAL_SUBDOMAIN"},
Sources: cli.EnvVars("NHOST_LOCAL_SUBDOMAIN"),
},
}, nil
}

View File

@@ -29,3 +29,12 @@ func (ce *CliEnv) LoadSession(
return session, nil
}
func (ce *CliEnv) Credentials() (credentials.Credentials, error) {
var creds credentials.Credentials
if err := UnmarshalFile(ce.Path.AuthFile(), &creds, json.Unmarshal); err != nil {
return credentials.Credentials{}, err
}
return creds, nil
}

View File

@@ -8,7 +8,7 @@ import (
"github.com/nhost/be/services/mimir/model"
"github.com/nhost/nhost/cli/clienv"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func CommandApply() *cli.Command {
@@ -22,38 +22,42 @@ func CommandApply() *cli.Command {
Name: flagSubdomain,
Usage: "Subdomain of the Nhost project to apply configuration to. Defaults to linked project",
Required: true,
EnvVars: []string{"NHOST_SUBDOMAIN"},
Sources: cli.EnvVars("NHOST_SUBDOMAIN"),
},
&cli.BoolFlag{ //nolint:exhaustruct
Name: flagYes,
Usage: "Skip confirmation",
EnvVars: []string{"NHOST_YES"},
Sources: cli.EnvVars("NHOST_YES"),
},
},
}
}
func commandApply(cCtx *cli.Context) error {
ce := clienv.FromCLI(cCtx)
func commandApply(ctx context.Context, cmd *cli.Command) error {
ce := clienv.FromCLI(cmd)
proj, err := ce.GetAppInfo(cCtx.Context, cCtx.String(flagSubdomain))
proj, err := ce.GetAppInfo(ctx, cmd.String(flagSubdomain))
if err != nil {
return fmt.Errorf("failed to get app info: %w", err)
return cli.Exit(fmt.Sprintf("Failed to get app info: %v", err), 1)
}
ce.Infoln("Validating configuration...")
cfg, _, err := ValidateRemote(
cCtx.Context,
ctx,
ce,
proj.GetSubdomain(),
proj.GetID(),
)
if err != nil {
return err
return cli.Exit(err.Error(), 1)
}
return Apply(cCtx.Context, ce, proj.ID, cfg, cCtx.Bool(flagYes))
if err := Apply(ctx, ce, proj.ID, cfg, cmd.Bool(flagYes)); err != nil {
return cli.Exit(err.Error(), 1)
}
return nil
}
func Apply(

View File

@@ -1,6 +1,6 @@
package config
import "github.com/urfave/cli/v2"
import "github.com/urfave/cli/v3"
const flagSubdomain = "subdomain"
@@ -9,7 +9,7 @@ func Command() *cli.Command {
Name: "config",
Aliases: []string{},
Usage: "Perform config operations",
Subcommands: []*cli.Command{
Commands: []*cli.Command{
CommandDefault(),
CommandExample(),
CommandApply(),

View File

@@ -1,6 +1,7 @@
package config
import (
"context"
"fmt"
"os"
@@ -8,7 +9,7 @@ import (
"github.com/nhost/nhost/cli/project"
"github.com/nhost/nhost/cli/project/env"
"github.com/pelletier/go-toml/v2"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func CommandDefault() *cli.Command {
@@ -21,8 +22,8 @@ func CommandDefault() *cli.Command {
}
}
func commandDefault(cCtx *cli.Context) error {
ce := clienv.FromCLI(cCtx)
func commandDefault(_ context.Context, cmd *cli.Command) error {
ce := clienv.FromCLI(cmd)
if err := os.MkdirAll(ce.Path.NhostFolder(), 0o755); err != nil { //nolint:mnd
return fmt.Errorf("failed to create nhost folder: %w", err)

View File

@@ -13,7 +13,7 @@ import (
"github.com/nhost/be/services/mimir/model"
"github.com/nhost/nhost/cli/clienv"
"github.com/pelletier/go-toml/v2"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"github.com/wI2L/jsondiff"
)
@@ -31,13 +31,13 @@ func CommandEdit() *cli.Command {
&cli.StringFlag{ //nolint:exhaustruct
Name: flagSubdomain,
Usage: "If specified, edit this subdomain's overlay, otherwise edit base configuation",
EnvVars: []string{"NHOST_SUBDOMAIN"},
Sources: cli.EnvVars("NHOST_SUBDOMAIN"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagEditor,
Usage: "Editor to use",
Value: "vim",
EnvVars: []string{"EDITOR"},
Sources: cli.EnvVars("EDITOR"),
},
},
}
@@ -139,11 +139,11 @@ func GenerateJSONPatch(origfilepath, newfilepath, dst string) error {
return nil
}
func edit(cCtx *cli.Context) error {
ce := clienv.FromCLI(cCtx)
func edit(ctx context.Context, cmd *cli.Command) error {
ce := clienv.FromCLI(cmd)
if cCtx.String(flagSubdomain) == "" {
if err := EditFile(cCtx.Context, cCtx.String(flagEditor), ce.Path.NhostToml()); err != nil {
if cmd.String(flagSubdomain) == "" {
if err := EditFile(ctx, cmd.String(flagEditor), ce.Path.NhostToml()); err != nil {
return fmt.Errorf("failed to edit config: %w", err)
}
@@ -163,17 +163,17 @@ func edit(cCtx *cli.Context) error {
tmpfileName := filepath.Join(tmpdir, "nhost.toml")
if err := CopyConfig[model.ConfigConfig](
ce.Path.NhostToml(), tmpfileName, ce.Path.Overlay(cCtx.String(flagSubdomain)),
ce.Path.NhostToml(), tmpfileName, ce.Path.Overlay(cmd.String(flagSubdomain)),
); err != nil {
return fmt.Errorf("failed to copy config: %w", err)
}
if err := EditFile(cCtx.Context, cCtx.String(flagEditor), tmpfileName); err != nil {
if err := EditFile(ctx, cmd.String(flagEditor), tmpfileName); err != nil {
return fmt.Errorf("failed to edit config: %w", err)
}
if err := GenerateJSONPatch(
ce.Path.NhostToml(), tmpfileName, ce.Path.Overlay(cCtx.String(flagSubdomain)),
ce.Path.NhostToml(), tmpfileName, ce.Path.Overlay(cmd.String(flagSubdomain)),
); err != nil {
return fmt.Errorf("failed to generate json patch: %w", err)
}

View File

@@ -1,13 +1,14 @@
package config
import (
"context"
"fmt"
"github.com/nhost/be/services/mimir/model"
"github.com/nhost/be/services/mimir/schema"
"github.com/nhost/nhost/cli/clienv"
"github.com/pelletier/go-toml/v2"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func CommandExample() *cli.Command {
@@ -22,8 +23,8 @@ func CommandExample() *cli.Command {
func ptr[T any](v T) *T { return &v }
func commandExample(cCtx *cli.Context) error { //nolint:funlen,maintidx
ce := clienv.FromCLI(cCtx)
func commandExample(_ context.Context, cmd *cli.Command) error { //nolint:funlen,maintidx
ce := clienv.FromCLI(cmd)
//nolint:mnd
cfg := model.ConfigConfig{

View File

@@ -13,7 +13,7 @@ import (
"github.com/nhost/nhost/cli/project/env"
"github.com/nhost/nhost/cli/system"
"github.com/pelletier/go-toml/v2"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
const (
@@ -36,21 +36,21 @@ func CommandPull() *cli.Command {
&cli.StringFlag{ //nolint:exhaustruct
Name: flagSubdomain,
Usage: "Pull this subdomain's configuration. Defaults to linked project",
EnvVars: []string{"NHOST_SUBDOMAIN"},
Sources: cli.EnvVars("NHOST_SUBDOMAIN"),
},
&cli.BoolFlag{ //nolint:exhaustruct
Name: flagYes,
Usage: "Skip confirmation",
EnvVars: []string{"NHOST_YES"},
Sources: cli.EnvVars("NHOST_YES"),
},
},
}
}
func commandPull(cCtx *cli.Context) error {
ce := clienv.FromCLI(cCtx)
func commandPull(ctx context.Context, cmd *cli.Command) error {
ce := clienv.FromCLI(cmd)
skipConfirmation := cCtx.Bool(flagYes)
skipConfirmation := cmd.Bool(flagYes)
if !skipConfirmation {
if err := verifyFile(ce, ce.Path.NhostToml()); err != nil {
@@ -66,12 +66,12 @@ func commandPull(cCtx *cli.Context) error {
}
}
proj, err := ce.GetAppInfo(cCtx.Context, cCtx.String(flagSubdomain))
proj, err := ce.GetAppInfo(ctx, cmd.String(flagSubdomain))
if err != nil {
return fmt.Errorf("failed to get app info: %w", err)
}
_, err = Pull(cCtx.Context, ce, proj, writeSecrets)
_, err = Pull(ctx, ce, proj, writeSecrets)
return err
}

View File

@@ -1,13 +1,14 @@
package config
import (
"context"
"fmt"
"github.com/nhost/be/services/mimir/model"
"github.com/nhost/nhost/cli/clienv"
"github.com/nhost/nhost/cli/project/env"
"github.com/pelletier/go-toml/v2"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func CommandShow() *cli.Command {
@@ -21,14 +22,14 @@ func CommandShow() *cli.Command {
&cli.StringFlag{ //nolint:exhaustruct
Name: flagSubdomain,
Usage: "Show this subdomain's rendered configuration. Defaults to base configuration",
EnvVars: []string{"NHOST_SUBDOMAIN"},
Sources: cli.EnvVars("NHOST_SUBDOMAIN"),
},
},
}
}
func commandShow(c *cli.Context) error {
ce := clienv.FromCLI(c)
func commandShow(_ context.Context, cmd *cli.Command) error {
ce := clienv.FromCLI(cmd)
var secrets model.Secrets
if err := clienv.UnmarshalFile(ce.Path.Secrets(), &secrets, env.Unmarshal); err != nil {
@@ -38,7 +39,7 @@ func commandShow(c *cli.Context) error {
)
}
cfg, err := Validate(ce, c.String(flagSubdomain), secrets)
cfg, err := Validate(ce, cmd.String(flagSubdomain), secrets)
if err != nil {
return err
}

View File

@@ -13,7 +13,7 @@ import (
"github.com/nhost/nhost/cli/clienv"
"github.com/nhost/nhost/cli/project/env"
"github.com/pelletier/go-toml/v2"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
jsonpatch "gopkg.in/evanphx/json-patch.v5"
)
@@ -27,24 +27,24 @@ func CommandValidate() *cli.Command {
&cli.StringFlag{ //nolint:exhaustruct
Name: flagSubdomain,
Usage: "Validate this subdomain's configuration. Defaults to linked project",
EnvVars: []string{"NHOST_SUBDOMAIN"},
Sources: cli.EnvVars("NHOST_SUBDOMAIN"),
},
},
}
}
func commandValidate(cCtx *cli.Context) error {
ce := clienv.FromCLI(cCtx)
func commandValidate(ctx context.Context, cmd *cli.Command) error {
ce := clienv.FromCLI(cmd)
subdomain := cCtx.String(flagSubdomain)
subdomain := cmd.String(flagSubdomain)
if subdomain != "" && subdomain != "local" {
proj, err := ce.GetAppInfo(cCtx.Context, cCtx.String(flagSubdomain))
proj, err := ce.GetAppInfo(ctx, cmd.String(flagSubdomain))
if err != nil {
return fmt.Errorf("failed to get app info: %w", err)
}
_, _, err = ValidateRemote(
cCtx.Context,
ctx,
ce,
proj.GetSubdomain(),
proj.GetID(),

View File

@@ -9,7 +9,7 @@ import (
"github.com/google/uuid"
"github.com/nhost/be/services/mimir/graph"
cors "github.com/rs/cors/wrapper/gin"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
const (
@@ -48,27 +48,27 @@ func Command() *cli.Command {
Name: enablePlaygroundFlag,
Usage: "enable graphql playground (under /v1)",
Category: "server",
EnvVars: []string{"ENABLE_PLAYGROUND"},
Sources: cli.EnvVars("ENABLE_PLAYGROUND"),
},
&cli.StringFlag{ //nolint: exhaustruct
Name: storageLocalConfigPath,
Usage: "Path to the local mimir config file",
Value: "/tmp/root/nhost/nhost.toml",
Category: "plugins",
EnvVars: []string{"STORAGE_LOCAL_CONFIG_PATH"},
Sources: cli.EnvVars("STORAGE_LOCAL_CONFIG_PATH"),
},
&cli.StringFlag{ //nolint: exhaustruct
Name: storageLocalSecretsPath,
Usage: "Path to the local mimir secrets file",
Value: "/tmp/root/.secrets",
Category: "plugins",
EnvVars: []string{"STORAGE_LOCAL_SECRETS_PATH"},
Sources: cli.EnvVars("STORAGE_LOCAL_SECRETS_PATH"),
},
&cli.StringSliceFlag{ //nolint: exhaustruct
Name: storageLocalRunServicesPath,
Usage: "Path to the local mimir run services files",
Category: "plugins",
EnvVars: []string{"STORAGE_LOCAL_RUN_SERVICES_PATH"},
Sources: cli.EnvVars("STORAGE_LOCAL_RUN_SERVICES_PATH"),
},
},
Action: serve,
@@ -103,14 +103,14 @@ func runServicesFiles(runServices ...string) map[string]string {
return m
}
func serve(cCtx *cli.Context) error {
logger := getLogger(cCtx.Bool(debugFlag), cCtx.Bool(logFormatJSONFlag))
logger.Info(cCtx.App.Name + " v" + cCtx.App.Version)
logFlags(logger, cCtx)
func serve(_ context.Context, cmd *cli.Command) error {
logger := getLogger(cmd.Bool(debugFlag), cmd.Bool(logFormatJSONFlag))
logger.Info(cmd.Root().Name + " v" + cmd.Root().Version)
logFlags(logger, cmd)
configFile := cCtx.String(storageLocalConfigPath)
secretsFile := cCtx.String(storageLocalSecretsPath)
runServices := runServicesFiles(cCtx.StringSlice(storageLocalRunServicesPath)...)
configFile := cmd.String(storageLocalConfigPath)
secretsFile := cmd.String(storageLocalSecretsPath)
runServices := runServicesFiles(cmd.StringSlice(storageLocalRunServicesPath)...)
st := NewLocal(configFile, secretsFile, runServices)
@@ -131,13 +131,13 @@ func serve(cCtx *cli.Context) error {
resolver,
dummyMiddleware,
dummyMiddleware2,
cCtx.Bool(enablePlaygroundFlag),
cCtx.App.Version,
cmd.Bool(enablePlaygroundFlag),
cmd.Root().Version,
[]graphql.FieldMiddleware{},
gin.Recovery(),
cors.Default(),
)
if err := r.Run(cCtx.String(bindFlag)); err != nil {
if err := r.Run(cmd.String(bindFlag)); err != nil {
return fmt.Errorf("failed to run gin: %w", err)
}

View File

@@ -5,7 +5,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func getLogger(debug bool, formatJSON bool) *logrus.Logger {
@@ -29,15 +29,15 @@ func getLogger(debug bool, formatJSON bool) *logrus.Logger {
return logger
}
func logFlags(logger logrus.FieldLogger, cCtx *cli.Context) {
func logFlags(logger logrus.FieldLogger, cmd *cli.Command) {
fields := logrus.Fields{}
for _, flag := range cCtx.App.Flags {
for _, flag := range cmd.Root().Flags {
name := flag.Names()[0]
fields[name] = cCtx.Generic(name)
fields[name] = cmd.Value(name)
}
for _, flag := range cCtx.Command.Flags {
for _, flag := range cmd.Flags {
name := flag.Names()[0]
if strings.Contains(name, "pass") ||
strings.Contains(name, "token") ||
@@ -47,7 +47,7 @@ func logFlags(logger logrus.FieldLogger, cCtx *cli.Context) {
continue
}
fields[name] = cCtx.Generic(name)
fields[name] = cmd.Value(name)
}
logger.WithFields(fields).Info("started with settings")

View File

@@ -1,6 +1,6 @@
package deployments
import "github.com/urfave/cli/v2"
import "github.com/urfave/cli/v3"
const flagSubdomain = "subdomain"
@@ -9,7 +9,7 @@ func commonFlags() []cli.Flag {
&cli.StringFlag{ //nolint:exhaustruct
Name: flagSubdomain,
Usage: "Project's subdomain to operate on, defaults to linked project",
EnvVars: []string{"NHOST_SUBDOMAIN"},
Sources: cli.EnvVars("NHOST_SUBDOMAIN"),
},
}
}
@@ -19,7 +19,7 @@ func Command() *cli.Command {
Name: "deployments",
Aliases: []string{},
Usage: "Manage deployments",
Subcommands: []*cli.Command{
Commands: []*cli.Command{
CommandList(),
CommandLogs(),
CommandNew(),

View File

@@ -1,12 +1,13 @@
package deployments
import (
"context"
"fmt"
"time"
"github.com/nhost/nhost/cli/clienv"
"github.com/nhost/nhost/cli/nhostclient/graphql"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func CommandList() *cli.Command {
@@ -77,21 +78,21 @@ func printDeployments(ce *clienv.CliEnv, deployments []*graphql.ListDeployments_
ce.Println("%s", clienv.Table(id, date, duration, status, user, ref, message))
}
func commandList(cCtx *cli.Context) error {
ce := clienv.FromCLI(cCtx)
func commandList(ctx context.Context, cmd *cli.Command) error {
ce := clienv.FromCLI(cmd)
proj, err := ce.GetAppInfo(cCtx.Context, cCtx.String(flagSubdomain))
proj, err := ce.GetAppInfo(ctx, cmd.String(flagSubdomain))
if err != nil {
return fmt.Errorf("failed to get app info: %w", err)
}
cl, err := ce.GetNhostClient(cCtx.Context)
cl, err := ce.GetNhostClient(ctx)
if err != nil {
return fmt.Errorf("failed to get nhost client: %w", err)
}
deployments, err := cl.ListDeployments(
cCtx.Context,
ctx,
proj.ID,
)
if err != nil {

View File

@@ -8,7 +8,7 @@ import (
"github.com/nhost/nhost/cli/clienv"
"github.com/nhost/nhost/cli/nhostclient"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
const (
@@ -101,28 +101,28 @@ func showLogsFollow(
}
}
func commandLogs(cCtx *cli.Context) error {
deploymentID := cCtx.Args().First()
func commandLogs(ctx context.Context, cmd *cli.Command) error {
deploymentID := cmd.Args().First()
if deploymentID == "" {
return errors.New("deployment_id is required") //nolint:err113
}
ce := clienv.FromCLI(cCtx)
ce := clienv.FromCLI(cmd)
cl, err := ce.GetNhostClient(cCtx.Context)
cl, err := ce.GetNhostClient(ctx)
if err != nil {
return fmt.Errorf("failed to get nhost client: %w", err)
}
if cCtx.Bool(flagFollow) {
ctx, cancel := context.WithTimeout(cCtx.Context, cCtx.Duration(flagTimeout))
if cmd.Bool(flagFollow) {
ctxWithTimeout, cancel := context.WithTimeout(ctx, cmd.Duration(flagTimeout))
defer cancel()
if _, err := showLogsFollow(ctx, ce, cl, deploymentID); err != nil {
if _, err := showLogsFollow(ctxWithTimeout, ce, cl, deploymentID); err != nil {
return err
}
} else {
if err := showLogsSimple(cCtx.Context, ce, cl, deploymentID); err != nil {
if err := showLogsSimple(ctx, ce, cl, deploymentID); err != nil {
return err
}
}

View File

@@ -7,7 +7,7 @@ import (
"github.com/nhost/nhost/cli/clienv"
"github.com/nhost/nhost/cli/nhostclient/graphql"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
const (
@@ -40,7 +40,7 @@ func CommandNew() *cli.Command {
&cli.StringFlag{ //nolint:exhaustruct
Name: flagRef,
Usage: "Git reference",
EnvVars: []string{"GITHUB_SHA"},
Sources: cli.EnvVars("GITHUB_SHA"),
Required: true,
},
&cli.StringFlag{ //nolint:exhaustruct
@@ -51,7 +51,7 @@ func CommandNew() *cli.Command {
&cli.StringFlag{ //nolint:exhaustruct
Name: flagUser,
Usage: "Commit user name",
EnvVars: []string{"GITHUB_ACTOR"},
Sources: cli.EnvVars("GITHUB_ACTOR"),
Required: true,
},
&cli.StringFlag{ //nolint:exhaustruct
@@ -67,28 +67,28 @@ func ptr[i any](v i) *i {
return &v
}
func commandNew(cCtx *cli.Context) error {
ce := clienv.FromCLI(cCtx)
func commandNew(ctx context.Context, cmd *cli.Command) error {
ce := clienv.FromCLI(cmd)
cl, err := ce.GetNhostClient(cCtx.Context)
cl, err := ce.GetNhostClient(ctx)
if err != nil {
return fmt.Errorf("failed to get nhost client: %w", err)
}
proj, err := ce.GetAppInfo(cCtx.Context, cCtx.String(flagSubdomain))
proj, err := ce.GetAppInfo(ctx, cmd.String(flagSubdomain))
if err != nil {
return fmt.Errorf("failed to get app info: %w", err)
}
resp, err := cl.InsertDeployment(
cCtx.Context,
ctx,
graphql.DeploymentsInsertInput{
App: nil,
AppID: ptr(proj.ID),
CommitMessage: ptr(cCtx.String(flagMessage)),
CommitSha: ptr(cCtx.String(flagRef)),
CommitUserAvatarURL: ptr(cCtx.String(flagUserAvatarURL)),
CommitUserName: ptr(cCtx.String(flagUser)),
CommitMessage: ptr(cmd.String(flagMessage)),
CommitSha: ptr(cmd.String(flagRef)),
CommitUserAvatarURL: ptr(cmd.String(flagUserAvatarURL)),
CommitUserName: ptr(cmd.String(flagUser)),
DeploymentStatus: ptr("SCHEDULED"),
},
)
@@ -98,13 +98,13 @@ func commandNew(cCtx *cli.Context) error {
ce.Println("Deployment created: %s", resp.InsertDeployment.ID)
if cCtx.Bool(flagFollow) {
if cmd.Bool(flagFollow) {
ce.Println("")
ctx, cancel := context.WithTimeout(cCtx.Context, cCtx.Duration(flagTimeout))
ctxWithTimeout, cancel := context.WithTimeout(ctx, cmd.Duration(flagTimeout))
defer cancel()
status, err := showLogsFollow(ctx, ce, cl, resp.InsertDeployment.ID)
status, err := showLogsFollow(ctxWithTimeout, ce, cl, resp.InsertDeployment.ID)
if err != nil {
return fmt.Errorf("error streaming logs: %w", err)
}

View File

@@ -15,7 +15,7 @@ import (
"github.com/nhost/nhost/cli/cmd/software"
"github.com/nhost/nhost/cli/dockercompose"
"github.com/nhost/nhost/cli/nhostclient/graphql"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
const (
@@ -34,19 +34,19 @@ func CommandCloud() *cli.Command {
Name: flagHTTPPort,
Usage: "HTTP port to listen on",
Value: defaultHTTPPort,
EnvVars: []string{"NHOST_HTTP_PORT"},
Sources: cli.EnvVars("NHOST_HTTP_PORT"),
},
&cli.BoolFlag{ //nolint:exhaustruct
Name: flagDisableTLS,
Usage: "Disable TLS",
Value: false,
EnvVars: []string{"NHOST_DISABLE_TLS"},
Sources: cli.EnvVars("NHOST_DISABLE_TLS"),
},
&cli.BoolFlag{ //nolint:exhaustruct
Name: flagApplySeeds,
Usage: "Apply seeds. If the .nhost folder does not exist, seeds will be applied regardless of this flag",
Value: false,
EnvVars: []string{"NHOST_APPLY_SEEDS"},
Sources: cli.EnvVars("NHOST_APPLY_SEEDS"),
},
&cli.UintFlag{ //nolint:exhaustruct
Name: flagsHasuraConsolePort,
@@ -56,42 +56,42 @@ func CommandCloud() *cli.Command {
&cli.StringFlag{ //nolint:exhaustruct
Name: flagDashboardVersion,
Usage: "Dashboard version to use",
Value: "nhost/dashboard:2.33.0",
EnvVars: []string{"NHOST_DASHBOARD_VERSION"},
Value: "nhost/dashboard:2.38.4",
Sources: cli.EnvVars("NHOST_DASHBOARD_VERSION"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagConfigserverImage,
Hidden: true,
Value: "",
EnvVars: []string{"NHOST_CONFIGSERVER_IMAGE"},
Sources: cli.EnvVars("NHOST_CONFIGSERVER_IMAGE"),
},
&cli.BoolFlag{ //nolint:exhaustruct
Name: flagDownOnError,
Usage: "Skip confirmation",
EnvVars: []string{"NHOST_YES"},
Sources: cli.EnvVars("NHOST_YES"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagCACertificates,
Usage: "Mounts and everrides path to CA certificates in the containers",
EnvVars: []string{"NHOST_CA_CERTIFICATES"},
Sources: cli.EnvVars("NHOST_CA_CERTIFICATES"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagSubdomain,
Usage: "Project's subdomain to operate on, defaults to linked project",
EnvVars: []string{"NHOST_SUBDOMAIN"},
Sources: cli.EnvVars("NHOST_SUBDOMAIN"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagPostgresURL,
Usage: "Postgres URL",
Required: true,
EnvVars: []string{"NHOST_POSTGRES_URL"},
Sources: cli.EnvVars("NHOST_POSTGRES_URL"),
},
},
}
}
func commandCloud(cCtx *cli.Context) error {
ce := clienv.FromCLI(cCtx)
func commandCloud(ctx context.Context, cmd *cli.Command) error {
ce := clienv.FromCLI(cmd)
if !clienv.PathExists(ce.Path.NhostToml()) {
return errors.New( //nolint:err113
@@ -105,38 +105,38 @@ func commandCloud(cCtx *cli.Context) error {
)
}
proj, err := ce.GetAppInfo(cCtx.Context, cCtx.String(flagSubdomain))
proj, err := ce.GetAppInfo(ctx, cmd.String(flagSubdomain))
if err != nil {
return fmt.Errorf("failed to get app info: %w", err)
}
configserverImage := cCtx.String(flagConfigserverImage)
configserverImage := cmd.String(flagConfigserverImage)
if configserverImage == "" {
configserverImage = "nhost/cli:" + cCtx.App.Version
configserverImage = "nhost/cli:" + cmd.Root().Version
}
applySeeds := cCtx.Bool(flagApplySeeds)
applySeeds := cmd.Bool(flagApplySeeds)
return Cloud(
cCtx.Context,
ctx,
ce,
cCtx.App.Version,
cCtx.Uint(flagHTTPPort),
!cCtx.Bool(flagDisableTLS),
cmd.Root().Version,
cmd.Uint(flagHTTPPort),
!cmd.Bool(flagDisableTLS),
applySeeds,
dockercompose.ExposePorts{
Auth: cCtx.Uint(flagAuthPort),
Storage: cCtx.Uint(flagStoragePort),
Graphql: cCtx.Uint(flagsHasuraPort),
Console: cCtx.Uint(flagsHasuraConsolePort),
Functions: cCtx.Uint(flagsFunctionsPort),
Auth: cmd.Uint(flagAuthPort),
Storage: cmd.Uint(flagStoragePort),
Graphql: cmd.Uint(flagsHasuraPort),
Console: cmd.Uint(flagsHasuraConsolePort),
Functions: cmd.Uint(flagsFunctionsPort),
},
cCtx.String(flagDashboardVersion),
cmd.String(flagDashboardVersion),
configserverImage,
cCtx.String(flagCACertificates),
cCtx.Bool(flagDownOnError),
cmd.String(flagCACertificates),
cmd.Bool(flagDownOnError),
proj,
cCtx.String(flagPostgresURL),
cmd.String(flagPostgresURL),
)
}

View File

@@ -1,9 +1,11 @@
package dev
import (
"context"
"github.com/nhost/nhost/cli/clienv"
"github.com/nhost/nhost/cli/dockercompose"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func CommandCompose() *cli.Command {
@@ -17,9 +19,9 @@ func CommandCompose() *cli.Command {
}
}
func commandCompose(cCtx *cli.Context) error {
ce := clienv.FromCLI(cCtx)
func commandCompose(ctx context.Context, cmd *cli.Command) error {
ce := clienv.FromCLI(cmd)
dc := dockercompose.New(ce.Path.WorkingDir(), ce.Path.DockerCompose(), ce.ProjectName())
return dc.Wrapper(cCtx.Context, cCtx.Args().Slice()...) //nolint:wrapcheck
return dc.Wrapper(ctx, cmd.Args().Slice()...) //nolint:wrapcheck
}

View File

@@ -1,13 +1,13 @@
package dev
import "github.com/urfave/cli/v2"
import "github.com/urfave/cli/v3"
func Command() *cli.Command {
return &cli.Command{ //nolint:exhaustruct
Name: "dev",
Aliases: []string{},
Usage: "Operate local development environment",
Subcommands: []*cli.Command{
Commands: []*cli.Command{
CommandCompose(),
CommandHasura(),
},

View File

@@ -1,9 +1,11 @@
package dev
import (
"context"
"github.com/nhost/nhost/cli/clienv"
"github.com/nhost/nhost/cli/dockercompose"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
const (
@@ -26,12 +28,12 @@ func CommandDown() *cli.Command {
}
}
func commandDown(cCtx *cli.Context) error {
ce := clienv.FromCLI(cCtx)
func commandDown(ctx context.Context, cmd *cli.Command) error {
ce := clienv.FromCLI(cmd)
dc := dockercompose.New(ce.Path.WorkingDir(), ce.Path.DockerCompose(), ce.ProjectName())
if err := dc.Stop(cCtx.Context, cCtx.Bool(flagVolumes)); err != nil {
if err := dc.Stop(ctx, cmd.Bool(flagVolumes)); err != nil {
ce.Warnln("failed to stop Nhost development environment: %s", err)
}

View File

@@ -1,13 +1,14 @@
package dev
import (
"context"
"fmt"
"github.com/nhost/be/services/mimir/model"
"github.com/nhost/nhost/cli/clienv"
"github.com/nhost/nhost/cli/dockercompose"
"github.com/pelletier/go-toml/v2"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func CommandHasura() *cli.Command {
@@ -21,8 +22,8 @@ func CommandHasura() *cli.Command {
}
}
func commandHasura(cCtx *cli.Context) error {
ce := clienv.FromCLI(cCtx)
func commandHasura(ctx context.Context, cmd *cli.Command) error {
ce := clienv.FromCLI(cmd)
cfg := &model.ConfigConfig{} //nolint:exhaustruct
if err := clienv.UnmarshalFile(ce.Path.NhostToml(), cfg, toml.Unmarshal); err != nil {
@@ -32,10 +33,10 @@ func commandHasura(cCtx *cli.Context) error {
docker := dockercompose.NewDocker()
return docker.HasuraWrapper( //nolint:wrapcheck
cCtx.Context,
ctx,
ce.LocalSubdomain(),
ce.Path.NhostFolder(),
*cfg.Hasura.Version,
cCtx.Args().Slice()...,
cmd.Args().Slice()...,
)
}

View File

@@ -1,9 +1,11 @@
package dev
import (
"context"
"github.com/nhost/nhost/cli/clienv"
"github.com/nhost/nhost/cli/dockercompose"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func CommandLogs() *cli.Command {
@@ -17,12 +19,12 @@ func CommandLogs() *cli.Command {
}
}
func commandLogs(cCtx *cli.Context) error {
ce := clienv.FromCLI(cCtx)
func commandLogs(ctx context.Context, cmd *cli.Command) error {
ce := clienv.FromCLI(cmd)
dc := dockercompose.New(ce.Path.WorkingDir(), ce.Path.DockerCompose(), ce.ProjectName())
if err := dc.Logs(cCtx.Context, cCtx.Args().Slice()...); err != nil {
if err := dc.Logs(ctx, cmd.Args().Slice()...); err != nil {
ce.Warnln("%s", err)
}

View File

@@ -19,7 +19,7 @@ import (
"github.com/nhost/nhost/cli/cmd/software"
"github.com/nhost/nhost/cli/dockercompose"
"github.com/nhost/nhost/cli/project/env"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func deptr[T any](t *T) T { //nolint:ireturn
@@ -63,25 +63,25 @@ func CommandUp() *cli.Command { //nolint:funlen
Name: flagHTTPPort,
Usage: "HTTP port to listen on",
Value: defaultHTTPPort,
EnvVars: []string{"NHOST_HTTP_PORT"},
Sources: cli.EnvVars("NHOST_HTTP_PORT"),
},
&cli.BoolFlag{ //nolint:exhaustruct
Name: flagDisableTLS,
Usage: "Disable TLS",
Value: false,
EnvVars: []string{"NHOST_DISABLE_TLS"},
Sources: cli.EnvVars("NHOST_DISABLE_TLS"),
},
&cli.UintFlag{ //nolint:exhaustruct
Name: flagPostgresPort,
Usage: "Postgres port to listen on",
Value: defaultPostgresPort,
EnvVars: []string{"NHOST_POSTGRES_PORT"},
Sources: cli.EnvVars("NHOST_POSTGRES_PORT"),
},
&cli.BoolFlag{ //nolint:exhaustruct
Name: flagApplySeeds,
Usage: "Apply seeds. If the .nhost folder does not exist, seeds will be applied regardless of this flag",
Value: false,
EnvVars: []string{"NHOST_APPLY_SEEDS"},
Sources: cli.EnvVars("NHOST_APPLY_SEEDS"),
},
&cli.UintFlag{ //nolint:exhaustruct
Name: flagAuthPort,
@@ -111,39 +111,39 @@ func CommandUp() *cli.Command { //nolint:funlen
&cli.StringFlag{ //nolint:exhaustruct
Name: flagDashboardVersion,
Usage: "Dashboard version to use",
Value: "nhost/dashboard:2.33.0",
EnvVars: []string{"NHOST_DASHBOARD_VERSION"},
Value: "nhost/dashboard:2.38.4",
Sources: cli.EnvVars("NHOST_DASHBOARD_VERSION"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagConfigserverImage,
Hidden: true,
Value: "",
EnvVars: []string{"NHOST_CONFIGSERVER_IMAGE"},
Sources: cli.EnvVars("NHOST_CONFIGSERVER_IMAGE"),
},
&cli.StringSliceFlag{ //nolint:exhaustruct
Name: flagRunService,
Usage: "Run service to add to the development environment. Can be passed multiple times. Comma-separated values are also accepted. Format: /path/to/run-service.toml[:overlay_name]", //nolint:lll
EnvVars: []string{"NHOST_RUN_SERVICE"},
Sources: cli.EnvVars("NHOST_RUN_SERVICE"),
},
&cli.BoolFlag{ //nolint:exhaustruct
Name: flagDownOnError,
Usage: "Skip confirmation",
EnvVars: []string{"NHOST_YES"},
Sources: cli.EnvVars("NHOST_YES"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagCACertificates,
Usage: "Mounts and everrides path to CA certificates in the containers",
EnvVars: []string{"NHOST_CA_CERTIFICATES"},
Sources: cli.EnvVars("NHOST_CA_CERTIFICATES"),
},
},
Subcommands: []*cli.Command{
Commands: []*cli.Command{
CommandCloud(),
},
}
}
func commandUp(cCtx *cli.Context) error {
ce := clienv.FromCLI(cCtx)
func commandUp(ctx context.Context, cmd *cli.Command) error {
ce := clienv.FromCLI(cmd)
// projname to be root directory
@@ -159,33 +159,33 @@ func commandUp(cCtx *cli.Context) error {
)
}
configserverImage := cCtx.String(flagConfigserverImage)
configserverImage := cmd.String(flagConfigserverImage)
if configserverImage == "" {
configserverImage = "nhost/cli:" + cCtx.App.Version
configserverImage = "nhost/cli:" + cmd.Root().Version
}
applySeeds := cCtx.Bool(flagApplySeeds) || !clienv.PathExists(ce.Path.DotNhostFolder())
applySeeds := cmd.Bool(flagApplySeeds) || !clienv.PathExists(ce.Path.DotNhostFolder())
return Up(
cCtx.Context,
ctx,
ce,
cCtx.App.Version,
cCtx.Uint(flagHTTPPort),
!cCtx.Bool(flagDisableTLS),
cCtx.Uint(flagPostgresPort),
cmd.Root().Version,
cmd.Uint(flagHTTPPort),
!cmd.Bool(flagDisableTLS),
cmd.Uint(flagPostgresPort),
applySeeds,
dockercompose.ExposePorts{
Auth: cCtx.Uint(flagAuthPort),
Storage: cCtx.Uint(flagStoragePort),
Graphql: cCtx.Uint(flagsHasuraPort),
Console: cCtx.Uint(flagsHasuraConsolePort),
Functions: cCtx.Uint(flagsFunctionsPort),
Auth: cmd.Uint(flagAuthPort),
Storage: cmd.Uint(flagStoragePort),
Graphql: cmd.Uint(flagsHasuraPort),
Console: cmd.Uint(flagsHasuraConsolePort),
Functions: cmd.Uint(flagsFunctionsPort),
},
cCtx.String(flagDashboardVersion),
cmd.String(flagDashboardVersion),
configserverImage,
cCtx.String(flagCACertificates),
cCtx.StringSlice(flagRunService),
cCtx.Bool(flagDownOnError),
cmd.String(flagCACertificates),
cmd.StringSlice(flagRunService),
cmd.Bool(flagDownOnError),
)
}

View File

@@ -8,7 +8,7 @@ import (
"os/exec"
"github.com/nhost/nhost/cli/clienv"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
const (
@@ -35,13 +35,13 @@ func CommandConfigure() *cli.Command {
&cli.StringFlag{ //nolint:exhaustruct
Name: flagDockerConfig,
Usage: "Path to docker config file",
EnvVars: []string{"DOCKER_CONFIG"},
Sources: cli.EnvVars("DOCKER_CONFIG"),
Value: home + "/.docker/config.json",
},
&cli.BoolFlag{ //nolint:exhaustruct
Name: flagNoInteractive,
Usage: "Do not prompt for confirmation",
EnvVars: []string{"NO_INTERACTIVE"},
Sources: cli.EnvVars("NO_INTERACTIVE"),
Value: false,
},
},
@@ -140,21 +140,21 @@ func configureDocker(dockerConfig string) error {
return nil
}
func actionConfigure(c *cli.Context) error {
ce := clienv.FromCLI(c)
func actionConfigure(ctx context.Context, cmd *cli.Command) error {
ce := clienv.FromCLI(cmd)
if err := writeScript(c.Context, ce); err != nil {
if err := writeScript(ctx, ce); err != nil {
return err
}
if c.Bool(flagNoInteractive) {
return configureDocker(c.String(flagDockerConfig))
if cmd.Bool(flagNoInteractive) {
return configureDocker(cmd.String(flagDockerConfig))
}
//nolint:lll
ce.PromptMessage(
"I am about to configure docker to authenticate with Nhost's registry. This will modify your docker config file on %s. Should I continue? [y/N] ",
c.String(flagDockerConfig),
cmd.String(flagDockerConfig),
)
v, err := ce.PromptInput(false)
@@ -163,7 +163,7 @@ func actionConfigure(c *cli.Context) error {
}
if v == "y" || v == "Y" {
return configureDocker(c.String(flagDockerConfig))
return configureDocker(cmd.String(flagDockerConfig))
}
return nil

View File

@@ -1,13 +1,13 @@
package dockercredentials
import "github.com/urfave/cli/v2"
import "github.com/urfave/cli/v3"
func Command() *cli.Command {
return &cli.Command{ //nolint:exhaustruct
Name: "docker-credentials",
Aliases: []string{},
Usage: "Perform docker-credentials operations",
Subcommands: []*cli.Command{
Commands: []*cli.Command{
CommandGet(),
CommandErase(),
CommandStore(),

View File

@@ -1,7 +1,9 @@
package dockercredentials
import (
"github.com/urfave/cli/v2"
"context"
"github.com/urfave/cli/v3"
)
func CommandErase() *cli.Command {
@@ -14,7 +16,7 @@ func CommandErase() *cli.Command {
}
}
func actionErase(c *cli.Context) error {
_, _ = c.App.Writer.Write([]byte("Please, use the nhost CLI to logout\n"))
func actionErase(_ context.Context, cmd *cli.Command) error {
_, _ = cmd.Root().Writer.Write([]byte("Please, use the nhost CLI to logout\n"))
return nil
}

View File

@@ -8,7 +8,7 @@ import (
"os"
"github.com/nhost/nhost/cli/clienv"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
const (
@@ -26,14 +26,14 @@ func CommandGet() *cli.Command {
&cli.StringFlag{ //nolint:exhaustruct
Name: flagAuthURL,
Usage: "Nhost auth URL",
EnvVars: []string{"NHOST_CLI_AUTH_URL"},
Sources: cli.EnvVars("NHOST_CLI_AUTH_URL"),
Value: "https://otsispdzcwxyqzbfntmj.auth.eu-central-1.nhost.run/v1",
Hidden: true,
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagGraphqlURL,
Usage: "Nhost GraphQL URL",
EnvVars: []string{"NHOST_CLI_GRAPHQL_URL"},
Sources: cli.EnvVars("NHOST_CLI_GRAPHQL_URL"),
Value: "https://otsispdzcwxyqzbfntmj.graphql.eu-central-1.nhost.run/v1",
Hidden: true,
},
@@ -69,15 +69,15 @@ type response struct {
Secret string `json:"Secret"`
}
func actionGet(c *cli.Context) error {
scanner := bufio.NewScanner(c.App.Reader)
func actionGet(ctx context.Context, cmd *cli.Command) error {
scanner := bufio.NewScanner(cmd.Root().Reader)
var input string
for scanner.Scan() {
input += scanner.Text()
}
token, err := getToken(c.Context, c.String(flagAuthURL), c.String(flagGraphqlURL))
token, err := getToken(ctx, cmd.String(flagAuthURL), cmd.String(flagGraphqlURL))
if err != nil {
return err
}
@@ -91,7 +91,7 @@ func actionGet(c *cli.Context) error {
return fmt.Errorf("failed to marshal response: %w", err)
}
if _, err = c.App.Writer.Write(b); err != nil {
if _, err = cmd.Root().Writer.Write(b); err != nil {
return fmt.Errorf("failed to write response: %w", err)
}

View File

@@ -1,7 +1,9 @@
package dockercredentials
import (
"github.com/urfave/cli/v2"
"context"
"github.com/urfave/cli/v3"
)
func CommandStore() *cli.Command {
@@ -14,7 +16,7 @@ func CommandStore() *cli.Command {
}
}
func actionStore(c *cli.Context) error {
_, _ = c.App.Writer.Write([]byte("Please, use the nhost CLI to login\n"))
func actionStore(_ context.Context, cmd *cli.Command) error {
_, _ = cmd.Root().Writer.Write([]byte("Please, use the nhost CLI to login\n"))
return nil
}

View File

@@ -0,0 +1,92 @@
package config
import (
"context"
"fmt"
"os"
"path/filepath"
"github.com/nhost/nhost/cli/mcp/config"
"github.com/pelletier/go-toml/v2"
"github.com/urfave/cli/v3"
)
const (
flagConfirm = "confirm"
)
func Command() *cli.Command {
return &cli.Command{ //nolint:exhaustruct
Name: "config",
Usage: "Generate and save configuration file",
Flags: []cli.Flag{
&cli.BoolFlag{ //nolint:exhaustruct
Name: flagConfirm,
Usage: "Skip confirmation prompt",
Value: false,
Sources: cli.EnvVars("CONFIRM"),
},
},
Commands: []*cli.Command{
{
Name: "dump",
Usage: "Dump the configuration to stdout for verification",
Flags: []cli.Flag{},
Action: actionDump,
},
},
Action: action,
}
}
//nolint:forbidigo
func action(_ context.Context, cmd *cli.Command) error {
cfg, err := config.RunWizard()
if err != nil {
return cli.Exit(fmt.Sprintf("failed to run wizard: %s", err), 1)
}
tomlData, err := toml.Marshal(cfg)
if err != nil {
return cli.Exit(fmt.Sprintf("failed to marshal config: %s", err), 1)
}
fmt.Println("Configuration Preview:")
fmt.Println("---------------------")
fmt.Println(string(tomlData))
fmt.Println()
filePath := config.GetConfigPath(cmd)
fmt.Printf("Save configuration to %s?\n", filePath)
fmt.Print("Proceed? (y/N): ")
var confirm string
if _, err := fmt.Scanln(&confirm); err != nil {
return cli.Exit(fmt.Sprintf("failed to read input: %s", err), 1)
}
if confirm != "y" && confirm != "Y" {
fmt.Println("Operation cancelled.")
return nil
}
dir := filepath.Dir(filePath)
if err := os.MkdirAll(dir, 0o755); err != nil { //nolint:mnd
return fmt.Errorf("failed to create config directory: %w", err)
}
data, err := toml.Marshal(cfg)
if err != nil {
return fmt.Errorf("failed to marshal config: %w", err)
}
if err := os.WriteFile(filePath, data, 0o600); err != nil { //nolint:mnd
return fmt.Errorf("failed to write config file: %w", err)
}
fmt.Println("\nConfiguration saved successfully!")
fmt.Println("Note: Review the documentation for additional configuration options,")
fmt.Println(" especially for fine-tuning LLM access permissions.")
return nil
}

View File

@@ -0,0 +1,35 @@
package config
import (
"context"
"fmt"
"github.com/nhost/nhost/cli/mcp/config"
"github.com/pelletier/go-toml/v2"
"github.com/urfave/cli/v3"
)
func actionDump(_ context.Context, cmd *cli.Command) error {
configPath := config.GetConfigPath(cmd)
if configPath == "" {
return cli.Exit("config file path is required", 1)
}
cfg, err := config.Load(configPath)
if err != nil {
fmt.Println("Please, run `mcp-nhost config` to configure the service.") //nolint:forbidigo
return cli.Exit("failed to load config file "+err.Error(), 1)
}
b, err := toml.Marshal(cfg)
if err != nil {
return cli.Exit("failed to marshal config file "+err.Error(), 1)
}
fmt.Println("Configuration Preview:") //nolint:forbidigo
fmt.Println("---------------------") //nolint:forbidigo
fmt.Println(string(b)) //nolint:forbidigo
fmt.Println() //nolint:forbidigo
return nil
}

117
cli/cmd/mcp/gen/gen.go Normal file
View File

@@ -0,0 +1,117 @@
package gen
import (
"context"
"fmt"
"github.com/nhost/nhost/cli/mcp/graphql"
"github.com/nhost/nhost/cli/mcp/nhost/auth"
"github.com/urfave/cli/v3"
)
const (
flagNhostAuthURL = "nhost-auth-url"
flagNhostGraphqlURL = "nhost-graphql-url"
flagNhostPAT = "nhost-pat"
flagWithMutations = "with-mutations"
)
func Command() *cli.Command {
return &cli.Command{ //nolint:exhaustruct
Name: "gen",
Usage: "Generate GraphQL schema for Nhost Cloud",
Flags: []cli.Flag{
&cli.StringFlag{ //nolint:exhaustruct
Name: flagNhostAuthURL,
Usage: "Nhost auth URL",
Hidden: true,
Value: "https://otsispdzcwxyqzbfntmj.auth.eu-central-1.nhost.run/v1",
Sources: cli.EnvVars("NHOST_AUTH_URL"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagNhostGraphqlURL,
Usage: "Nhost GraphQL URL",
Hidden: true,
Value: "https://otsispdzcwxyqzbfntmj.graphql.eu-central-1.nhost.run/v1",
Sources: cli.EnvVars("NHOST_GRAPHQL_URL"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagNhostPAT,
Usage: "Personal Access Token",
Required: true,
Sources: cli.EnvVars("NHOST_PAT"),
},
&cli.BoolFlag{ //nolint:exhaustruct
Name: flagWithMutations,
Usage: "Include mutations in the generated schema",
Value: false,
Sources: cli.EnvVars("WITH_MUTATIONS"),
},
},
Action: action,
}
}
func action(ctx context.Context, cmd *cli.Command) error {
interceptor, err := auth.WithPAT(
cmd.String(flagNhostAuthURL), cmd.String(flagNhostPAT),
)
if err != nil {
return cli.Exit(err.Error(), 1)
}
var introspection graphql.ResponseIntrospection
if err := graphql.Query(
ctx,
cmd.String(flagNhostGraphqlURL),
graphql.IntrospectionQuery,
nil,
&introspection,
nil,
nil,
interceptor,
); err != nil {
return cli.Exit(err.Error(), 1)
}
filter := graphql.Filter{
AllowQueries: []graphql.Queries{
{
Name: "organizations",
DisableNesting: true,
},
{
Name: "organization",
DisableNesting: true,
},
{
Name: "app",
DisableNesting: true,
},
{
Name: "apps",
DisableNesting: true,
},
{
Name: "config",
DisableNesting: false,
},
},
AllowMutations: []graphql.Queries{},
}
if cmd.Bool(flagWithMutations) {
filter.AllowMutations = []graphql.Queries{
{
Name: "updateConfig",
DisableNesting: false,
},
}
}
schema := graphql.ParseSchema(introspection, filter)
fmt.Print(schema) //nolint:forbidigo
return nil
}

33
cli/cmd/mcp/mcp.go Normal file
View File

@@ -0,0 +1,33 @@
package mcp
import (
"github.com/nhost/nhost/cli/cmd/mcp/config"
"github.com/nhost/nhost/cli/cmd/mcp/gen"
"github.com/nhost/nhost/cli/cmd/mcp/start"
"github.com/urfave/cli/v3"
)
const (
flagConfigFile = "config-file"
)
func Command() *cli.Command {
return &cli.Command{ //nolint:exhaustruct
Name: "mcp",
Aliases: []string{},
Usage: "Model Context Protocol (MCP) related commands",
Flags: []cli.Flag{
&cli.StringFlag{ //nolint:exhaustruct
Name: flagConfigFile,
Usage: "Configuration file path. Defaults to $NHOST_DOT_NHOST_FOLDER/nhost-mcp.toml",
Value: "",
Sources: cli.EnvVars("NHOST_MCP_CONFIG_FILE"),
},
},
Commands: []*cli.Command{
config.Command(),
start.Command(),
gen.Command(),
},
}
}

401
cli/cmd/mcp/mcp_test.go Normal file
View File

@@ -0,0 +1,401 @@
package mcp_test
import (
"bytes"
"context"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/mark3labs/mcp-go/client"
"github.com/mark3labs/mcp-go/client/transport"
"github.com/mark3labs/mcp-go/mcp"
nhostmcp "github.com/nhost/nhost/cli/cmd/mcp"
"github.com/nhost/nhost/cli/cmd/mcp/start"
"github.com/nhost/nhost/cli/cmd/user"
"github.com/nhost/nhost/cli/mcp/resources"
"github.com/nhost/nhost/cli/mcp/tools/cloud"
"github.com/nhost/nhost/cli/mcp/tools/docs"
"github.com/nhost/nhost/cli/mcp/tools/project"
"github.com/nhost/nhost/cli/mcp/tools/schemas"
)
func ptr[T any](v T) *T {
return &v
}
func TestStart(t *testing.T) { //nolint:cyclop,maintidx,paralleltest
loginCmd := user.CommandLogin()
mcpCmd := nhostmcp.Command()
buf := bytes.NewBuffer(nil)
mcpCmd.Writer = buf
go func() {
t.Setenv("HOME", t.TempDir())
if err := loginCmd.Run(
context.Background(),
[]string{
"main",
"--pat=user-pat",
},
); err != nil {
panic(err)
}
if err := mcpCmd.Run(
context.Background(),
[]string{
"main",
"start",
"--bind=:9000",
"--config-file=testdata/sample.toml",
},
); err != nil {
panic(err)
}
}()
time.Sleep(time.Second)
transportClient, err := transport.NewSSE("http://localhost:9000/sse")
if err != nil {
t.Fatalf("failed to create transport client: %v", err)
}
mcpClient := client.NewClient(transportClient)
if err := mcpClient.Start(t.Context()); err != nil {
t.Fatalf("failed to start mcp client: %v", err)
}
defer mcpClient.Close()
initRequest := mcp.InitializeRequest{} //nolint:exhaustruct
initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
initRequest.Params.ClientInfo = mcp.Implementation{
Name: "example-client",
Version: "1.0.0",
}
res, err := mcpClient.Initialize(
context.Background(),
initRequest,
)
if err != nil {
t.Fatalf("failed to initialize mcp client: %v", err)
}
if diff := cmp.Diff(
res,
//nolint:tagalign
&mcp.InitializeResult{
ProtocolVersion: "2025-06-18",
Capabilities: mcp.ServerCapabilities{
Elicitation: nil,
Experimental: nil,
Logging: nil,
Prompts: nil,
Resources: &struct {
Subscribe bool "json:\"subscribe,omitempty\""
ListChanged bool "json:\"listChanged,omitempty\""
}{
Subscribe: false,
ListChanged: false,
},
Sampling: nil,
Tools: &struct {
ListChanged bool "json:\"listChanged,omitempty\""
}{
ListChanged: true,
},
},
ServerInfo: mcp.Implementation{
Name: "mcp",
Version: "",
},
Instructions: start.ServerInstructions + `
Configured projects:
- local (local): Local development project running via the Nhost CLI
- asdasdasdasdasd (eu-central-1): Staging project for my awesome app
- qweqweqweqweqwe (us-east-1): Production project for my awesome app
The following resources are available:
- schema://nhost-cloud: Schema to interact with the Nhost Cloud. Projects are equivalent
to apps in the schema. IDs are typically uuids.
- schema://graphql-management: GraphQL's management schema for an Nhost project.
This tool is useful to properly understand how manage hasura metadata, migrations,
permissions, remote schemas, etc.
- schema://nhost.toml: Cuelang schema for the nhost.toml configuration file. Run nhost
config validate after making changes to your nhost.toml file to ensure it is valid.
`,
Result: mcp.Result{
Meta: nil,
},
},
); diff != "" {
t.Errorf("ServerInfo mismatch (-want +got):\n%s", diff)
}
tools, err := mcpClient.ListTools(
context.Background(),
mcp.ListToolsRequest{}, //nolint:exhaustruct
)
if err != nil {
t.Fatalf("failed to list tools: %v", err)
}
if diff := cmp.Diff(
tools,
//nolint:exhaustruct,lll
&mcp.ListToolsResult{
Tools: []mcp.Tool{
{
Name: "cloud-graphql-query",
Description: cloud.ToolGraphqlQueryInstructions,
InputSchema: mcp.ToolInputSchema{
Type: "object",
Properties: map[string]any{
"query": map[string]any{
"description": "graphql query to perform",
"type": "string",
},
"variables": map[string]any{
"description": "variables to use in the query",
"type": "string",
},
},
Required: []string{"query"},
},
Annotations: mcp.ToolAnnotation{
Title: "Perform GraphQL Query on Nhost Cloud Platform",
ReadOnlyHint: ptr(false),
DestructiveHint: ptr(true),
IdempotentHint: ptr(false),
OpenWorldHint: ptr(true),
},
},
{
Name: "get-schema",
Description: schemas.ToolGetGraphqlSchemaInstructions,
InputSchema: mcp.ToolInputSchema{
Type: "object",
Properties: map[string]any{
"role": map[string]any{
"description": string("role to use when executing queries. Keep in mind the schema depends on the role so if you retrieved the schema for a different role previously retrieve it for this role beforehand as it might differ"),
"type": string("string"),
},
"subdomain": map[string]any{
"description": string("Project to get the GraphQL schema for. Required when service is `project`"),
"enum": []any{string("local"), string("asdasdasdasdasd"), string("qweqweqweqweqwe")},
"type": string("string"),
},
"mutations": map[string]any{
"description": string("list of mutations to fetch"),
"type": string("array"),
},
"queries": map[string]any{
"description": string("list of queries to fetch"),
"type": string("array"),
},
"summary": map[string]any{
"default": bool(true),
"description": string("only return a summary of the schema"),
"type": string("boolean"),
},
},
Required: []string{"role", "subdomain"},
},
Annotations: mcp.ToolAnnotation{
Title: "Get GraphQL/API schema for various services",
ReadOnlyHint: ptr(true),
DestructiveHint: ptr(false),
IdempotentHint: ptr(true),
OpenWorldHint: ptr(true),
},
},
{
Name: "graphql-query",
Description: project.ToolGraphqlQueryInstructions,
InputSchema: mcp.ToolInputSchema{
Type: "object",
Properties: map[string]any{
"query": map[string]any{
"description": "graphql query to perform",
"type": "string",
},
"subdomain": map[string]any{
"description": "Project to perform the GraphQL query against",
"type": "string",
"enum": []any{
string("local"),
string("asdasdasdasdasd"),
string("qweqweqweqweqwe"),
},
},
"role": map[string]any{
"description": "role to use when executing queries. Keep in mind the schema depends on the role so if you retrieved the schema for a different role previously retrieve it for this role beforehand as it might differ",
"type": "string",
},
"userId": map[string]any{
"description": string("Overrides X-Hasura-User-Id in the GraphQL query/mutation. Credentials must allow it (i.e. admin secret must be in use)"),
"type": string("string"),
},
"variables": map[string]any{
"description": "variables to use in the query",
"type": "string",
},
},
Required: []string{"query", "subdomain", "role"},
},
Annotations: mcp.ToolAnnotation{
Title: "Perform GraphQL Query on Nhost Project running on Nhost Cloud",
ReadOnlyHint: ptr(false),
DestructiveHint: ptr(true),
IdempotentHint: ptr(false),
OpenWorldHint: ptr(true),
},
},
{
Name: "manage-graphql",
Description: project.ToolManageGraphqlInstructions,
InputSchema: mcp.ToolInputSchema{
Type: "object",
Properties: map[string]any{
"body": map[string]any{
"description": "The body for the HTTP request",
"type": "string",
},
"path": map[string]any{
"description": "The path for the HTTP request",
"type": "string",
},
"subdomain": map[string]any{
"description": "Project to perform the GraphQL management operation against",
"type": "string",
"enum": []any{
string("local"),
string("asdasdasdasdasd"),
string("qweqweqweqweqwe"),
},
},
},
Required: []string{"subdomain", "path", "body"},
},
Annotations: mcp.ToolAnnotation{
Title: "Manage GraphQL's Metadata on an Nhost Development Project",
ReadOnlyHint: ptr(false),
DestructiveHint: ptr(true),
IdempotentHint: ptr(true),
OpenWorldHint: ptr(true),
},
},
{
Name: "search",
Description: docs.ToolSearchInstructions,
InputSchema: mcp.ToolInputSchema{
Type: "object",
Properties: map[string]any{
"query": map[string]any{
"description": string("The search query"),
"type": string("string"),
},
},
Required: []string{"query"},
},
Annotations: mcp.ToolAnnotation{
Title: "Search Nhost Docs",
ReadOnlyHint: ptr(true),
IdempotentHint: ptr(true),
DestructiveHint: ptr(false),
OpenWorldHint: ptr(true),
},
},
},
},
cmpopts.SortSlices(func(a, b mcp.Tool) bool {
return a.Name < b.Name
}),
); diff != "" {
t.Errorf("ListToolsResult mismatch (-want +got):\n%s", diff)
}
resourceList, err := mcpClient.ListResources(
context.Background(),
mcp.ListResourcesRequest{}, //nolint:exhaustruct
)
if err != nil {
t.Fatalf("failed to list resources: %v", err)
}
if diff := cmp.Diff(
resourceList,
//nolint:exhaustruct
&mcp.ListResourcesResult{
Resources: []mcp.Resource{
{
Annotated: mcp.Annotated{
Annotations: &mcp.Annotations{
Audience: []mcp.Role{"agent"},
Priority: 9,
},
},
URI: "schema://graphql-management",
Name: "graphql-management",
Description: resources.GraphqlManagementDescription,
MIMEType: "text/plain",
},
{
Annotated: mcp.Annotated{
Annotations: &mcp.Annotations{
Audience: []mcp.Role{"agent"},
Priority: 9,
},
},
URI: "schema://nhost-cloud",
Name: "nhost-cloud",
Description: resources.CloudDescription,
MIMEType: "text/plain",
},
{
Annotated: mcp.Annotated{
Annotations: &mcp.Annotations{
Audience: []mcp.Role{"agent"},
Priority: 9,
},
},
URI: "schema://nhost.toml",
Name: "nhost.toml",
Description: resources.NhostTomlResourceDescription,
MIMEType: "text/plain",
},
},
},
); diff != "" {
t.Errorf("ListResourcesResult mismatch (-want +got):\n%s", diff)
}
if res.Capabilities.Prompts != nil {
prompts, err := mcpClient.ListPrompts(
context.Background(),
mcp.ListPromptsRequest{}, //nolint:exhaustruct
)
if err != nil {
t.Fatalf("failed to list prompts: %v", err)
}
if diff := cmp.Diff(
prompts,
//nolint:exhaustruct
&mcp.ListPromptsResult{
Prompts: []mcp.Prompt{},
},
); diff != "" {
t.Errorf("ListPromptsResult mismatch (-want +got):\n%s", diff)
}
}
}

208
cli/cmd/mcp/start/start.go Normal file
View File

@@ -0,0 +1,208 @@
package start
import (
"context"
"fmt"
"github.com/mark3labs/mcp-go/server"
"github.com/nhost/nhost/cli/clienv"
"github.com/nhost/nhost/cli/mcp/config"
"github.com/nhost/nhost/cli/mcp/nhost/auth"
"github.com/nhost/nhost/cli/mcp/resources"
"github.com/nhost/nhost/cli/mcp/tools/cloud"
"github.com/nhost/nhost/cli/mcp/tools/docs"
"github.com/nhost/nhost/cli/mcp/tools/project"
"github.com/nhost/nhost/cli/mcp/tools/schemas"
"github.com/urfave/cli/v3"
)
const (
flagNhostAuthURL = "nhost-auth-url"
flagNhostGraphqlURL = "nhost-graphql-url"
flagBind = "bind"
)
const (
// this seems to be largely ignored by clients, or at least by cursor.
// we also need to look into roots and resources as those might be helpful.
ServerInstructions = `
This is an MCP server to interact with the Nhost Cloud and with Nhost projects.
Important notes to anyone using this MCP server. Do not use this MCP server without
following these instructions:
1. Make sure you are clear on which environment the user wants to operate against.
2. Before attempting to call any tool, always make sure you list resources, roots, and
resource templates to understand what is available.
3. Apps and projects are the same and while users may talk about projects in Nhost's GraphQL
api those are referred as apps.
4. If you have an error querying the GraphQL API, please check the schema again. The schema may
have changed and the query you are using may be invalid.
5. Always follow the instructions provided by each tool. If you need to deviate from these
instructions, please, confirm with the user before doing so.
`
)
func Command() *cli.Command {
return &cli.Command{ //nolint:exhaustruct
Name: "start",
Usage: "Starts the MCP server",
Flags: []cli.Flag{
&cli.StringFlag{ //nolint:exhaustruct
Name: flagNhostAuthURL,
Usage: "Nhost auth URL",
Hidden: true,
Value: "https://otsispdzcwxyqzbfntmj.auth.eu-central-1.nhost.run/v1",
Category: "Cloud Platform",
Sources: cli.EnvVars("NHOST_AUTH_URL"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagNhostGraphqlURL,
Usage: "Nhost GraphQL URL",
Hidden: true,
Value: "https://otsispdzcwxyqzbfntmj.graphql.eu-central-1.nhost.run/v1",
Category: "Cloud Platform",
Sources: cli.EnvVars("NHOST_GRAPHQL_URL"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagBind,
Usage: "Bind address in the form $host:$port. If omitted use stdio",
Required: false,
Category: "General",
Sources: cli.EnvVars("BIND"),
},
},
Action: action,
}
}
func action(ctx context.Context, cmd *cli.Command) error {
cfg, err := getConfig(cmd)
if err != nil {
return err
}
ServerInstructions := ServerInstructions
ServerInstructions += "\n\n"
ServerInstructions += cfg.Projects.Instructions()
ServerInstructions += "\n"
ServerInstructions += resources.Instructions()
mcpServer := server.NewMCPServer(
cmd.Root().Name,
cmd.Root().Version,
server.WithInstructions(ServerInstructions),
)
if err := resources.Register(cfg, mcpServer); err != nil {
return cli.Exit(fmt.Sprintf("failed to register resources: %s", err), 1)
}
if cfg.Cloud != nil {
if err := registerCloud(
cmd,
mcpServer,
cfg,
cmd.String(flagNhostAuthURL),
cmd.String(flagNhostGraphqlURL),
); err != nil {
return cli.Exit(fmt.Sprintf("failed to register cloud tools: %s", err), 1)
}
}
if len(cfg.Projects) > 0 {
if err := registerProjectTool(mcpServer, cfg); err != nil {
return cli.Exit(fmt.Sprintf("failed to register project tools: %s", err), 1)
}
}
resources := schemas.NewTool(cfg)
resources.Register(mcpServer)
d, err := docs.NewTool(ctx)
if err != nil {
return cli.Exit(fmt.Sprintf("failed to initialize docs tools: %s", err), 1)
}
d.Register(mcpServer)
return start(mcpServer, cmd.String(flagBind))
}
func getConfig(cmd *cli.Command) (*config.Config, error) {
configPath := config.GetConfigPath(cmd)
if configPath == "" {
return nil, cli.Exit("config file path is required", 1)
}
cfg, err := config.Load(configPath)
if err != nil {
fmt.Println("Please, run `mcp-nhost config` to configure the service.") //nolint:forbidigo
return nil, cli.Exit("failed to load config file "+err.Error(), 1)
}
return cfg, nil
}
func registerCloud(
cmd *cli.Command,
mcpServer *server.MCPServer,
cfg *config.Config,
authURL string,
graphqlURL string,
) error {
ce := clienv.FromCLI(cmd)
creds, err := ce.Credentials()
if err != nil {
return fmt.Errorf("failed to load credentials: %w", err)
}
interceptor, err := auth.WithPAT(
authURL,
creds.PersonalAccessToken,
)
if err != nil {
return fmt.Errorf("failed to create PAT interceptor: %w", err)
}
cloudTool := cloud.NewTool(
graphqlURL, cfg.Cloud.EnableMutations, interceptor,
)
if err := cloudTool.Register(mcpServer); err != nil {
return fmt.Errorf("failed to register tools: %w", err)
}
return nil
}
func registerProjectTool(
mcpServer *server.MCPServer,
cfg *config.Config,
) error {
projectTool := project.NewTool(cfg)
if err := projectTool.Register(mcpServer); err != nil {
return fmt.Errorf("failed to register tool: %w", err)
}
return nil
}
func start(
mcpServer *server.MCPServer,
bind string,
) error {
if bind != "" {
sseServer := server.NewSSEServer(mcpServer, server.WithBaseURL(bind))
if err := sseServer.Start(bind); err != nil {
return cli.Exit(fmt.Sprintf("failed to serve tcp: %v", err), 1)
}
} else {
if err := server.ServeStdio(mcpServer); err != nil {
return cli.Exit(fmt.Sprintf("failed to serve stdio: %v", err), 1)
}
}
return nil
}

29
cli/cmd/mcp/testdata/sample.toml vendored Normal file
View File

@@ -0,0 +1,29 @@
[cloud]
enable_mutations = true
[[projects]]
subdomain = 'local'
region = 'local'
description = 'Local development project running via the Nhost CLI'
admin_secret = 'nhost-admin-secret'
manage_metadata = true
allow_queries = ['*']
allow_mutations = ['*']
[[projects]]
subdomain = 'asdasdasdasdasd'
region = 'eu-central-1'
description = 'Staging project for my awesome app'
manage_metadata = false
admin_secret = 'your-admin-secret-1'
allow_queries = ['*']
allow_mutations = ['*']
[[projects]]
subdomain = 'qweqweqweqweqwe'
region = 'us-east-1'
description = 'Production project for my awesome app'
manage_metadata = false
pat = 'pat-for-qweqweqweqweqwe'
allow_queries = ['getComments']
allow_mutations = ['insertComment', 'updateComment', 'deleteComment']

View File

@@ -9,12 +9,12 @@ import (
"os"
"path/filepath"
"github.com/hashicorp/go-getter"
"github.com/hashicorp/go-getter/v2"
"github.com/nhost/be/services/mimir/model"
"github.com/nhost/nhost/cli/clienv"
"github.com/nhost/nhost/cli/cmd/config"
"github.com/nhost/nhost/cli/dockercompose"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"gopkg.in/yaml.v3"
)
@@ -75,14 +75,14 @@ func CommandInit() *cli.Command {
Name: flagRemote,
Usage: "Initialize pulling configuration, migrations and metadata from the linked project",
Value: false,
EnvVars: []string{"NHOST_REMOTE"},
Sources: cli.EnvVars("NHOST_REMOTE"),
},
},
}
}
func commandInit(cCtx *cli.Context) error {
ce := clienv.FromCLI(cCtx)
func commandInit(ctx context.Context, cmd *cli.Command) error {
ce := clienv.FromCLI(cmd)
if clienv.PathExists(ce.Path.NhostFolder()) {
return errors.New("nhost folder already exists") //nolint:err113
@@ -98,12 +98,12 @@ func commandInit(cCtx *cli.Context) error {
return fmt.Errorf("failed to initialize configuration: %w", err)
}
if cCtx.Bool(flagRemote) {
if err := InitRemote(cCtx.Context, ce); err != nil {
if cmd.Bool(flagRemote) {
if err := InitRemote(ctx, ce); err != nil {
return fmt.Errorf("failed to initialize remote project: %w", err)
}
} else {
if err := initInit(cCtx.Context, ce.Path); err != nil {
if err := initInit(ctx, ce.Path); err != nil {
return fmt.Errorf("failed to initialize project: %w", err)
}
}
@@ -129,17 +129,12 @@ func initInit(
return err
}
getclient := &getter.Client{ //nolint:exhaustruct
Ctx: ctx,
Src: "github.com/nhost/hasura-auth/email-templates",
Dst: "nhost/emails",
Mode: getter.ClientModeAny,
Detectors: []getter.Detector{
&getter.GitHubDetector{},
},
}
if err := getclient.Get(); err != nil {
getclient := &getter.Client{} //nolint:exhaustruct
if _, err := getclient.Get(ctx, &getter.Request{ //nolint:exhaustruct
Src: "git::https://github.com/nhost/hasura-auth.git//email-templates",
Dst: "nhost/emails",
DisableSymlinks: true,
}); err != nil {
return fmt.Errorf("failed to download email templates: %w", err)
}

View File

@@ -1,11 +1,12 @@
package project
import (
"context"
"fmt"
"os"
"github.com/nhost/nhost/cli/clienv"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func CommandLink() *cli.Command {
@@ -18,14 +19,14 @@ func CommandLink() *cli.Command {
}
}
func commandLink(cCtx *cli.Context) error {
ce := clienv.FromCLI(cCtx)
func commandLink(ctx context.Context, cmd *cli.Command) error {
ce := clienv.FromCLI(cmd)
if err := os.MkdirAll(ce.Path.DotNhostFolder(), 0o755); err != nil { //nolint:mnd
return fmt.Errorf("failed to create .nhost folder: %w", err)
}
_, err := ce.Link(cCtx.Context)
_, err := ce.Link(ctx)
return err //nolint:wrapcheck
}

View File

@@ -5,7 +5,7 @@ import (
"fmt"
"github.com/nhost/nhost/cli/clienv"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func CommandList() *cli.Command {
@@ -18,9 +18,9 @@ func CommandList() *cli.Command {
}
}
func commandList(cCtx *cli.Context) error {
ce := clienv.FromCLI(cCtx)
return List(cCtx.Context, ce)
func commandList(ctx context.Context, cmd *cli.Command) error {
ce := clienv.FromCLI(cmd)
return List(ctx, ce)
}
func List(ctx context.Context, ce *clienv.CliEnv) error {

View File

@@ -1,13 +1,14 @@
package run
import (
"context"
"encoding/json"
"fmt"
"github.com/nhost/be/services/mimir/model"
"github.com/nhost/nhost/cli/clienv"
"github.com/nhost/nhost/cli/nhostclient/graphql"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func CommandConfigDeploy() *cli.Command {
@@ -23,13 +24,13 @@ func CommandConfigDeploy() *cli.Command {
Usage: "Service configuration file",
Value: "nhost-run-service.toml",
Required: true,
EnvVars: []string{"NHOST_RUN_SERVICE_CONFIG"},
Sources: cli.EnvVars("NHOST_RUN_SERVICE_CONFIG"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagServiceID,
Usage: "Service ID to update. Applies overlay of the same name",
Required: true,
EnvVars: []string{"NHOST_RUN_SERVICE_ID"},
Sources: cli.EnvVars("NHOST_RUN_SERVICE_ID"),
},
},
}
@@ -49,23 +50,23 @@ func transform[T, V any](t *T) (*V, error) {
return &v, nil
}
func commandConfigDeploy(cCtx *cli.Context) error {
ce := clienv.FromCLI(cCtx)
func commandConfigDeploy(ctx context.Context, cmd *cli.Command) error {
ce := clienv.FromCLI(cmd)
cl, err := ce.GetNhostClient(cCtx.Context)
cl, err := ce.GetNhostClient(ctx)
if err != nil {
return fmt.Errorf("failed to get nhost client: %w", err)
}
secrets, appID, err := getRemoteSecrets(cCtx.Context, cl, cCtx.String(flagServiceID))
secrets, appID, err := getRemoteSecrets(ctx, cl, cmd.String(flagServiceID))
if err != nil {
return err
}
cfg, err := Validate(
ce,
cCtx.String(flagConfig),
cCtx.String(flagServiceID),
cmd.String(flagConfig),
cmd.String(flagServiceID),
secrets,
true,
)
@@ -81,9 +82,9 @@ func commandConfigDeploy(cCtx *cli.Context) error {
}
if _, err := cl.ReplaceRunServiceConfig(
cCtx.Context,
ctx,
appID,
cCtx.String(flagServiceID),
cmd.String(flagServiceID),
*replaceConfig,
); err != nil {
return fmt.Errorf("failed to replace service config: %w", err)

View File

@@ -1,6 +1,7 @@
package run
import (
"context"
"fmt"
"os"
"path/filepath"
@@ -8,7 +9,7 @@ import (
"github.com/nhost/be/services/mimir/model"
"github.com/nhost/nhost/cli/clienv"
"github.com/nhost/nhost/cli/cmd/config"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
const flagEditor = "editor"
@@ -25,31 +26,31 @@ func CommandConfigEdit() *cli.Command {
Usage: "Service configuration file",
Value: "nhost-run-service.toml",
Required: true,
EnvVars: []string{"NHOST_RUN_SERVICE_CONFIG"},
Sources: cli.EnvVars("NHOST_RUN_SERVICE_CONFIG"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagEditor,
Usage: "Editor to use",
Value: "vim",
EnvVars: []string{"EDITOR"},
Sources: cli.EnvVars("EDITOR"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagOverlayName,
Usage: "If specified, apply this overlay",
EnvVars: []string{"NHOST_RUN_SERVICE_ID", "NHOST_SERVICE_OVERLAY_NAME"},
Sources: cli.EnvVars("NHOST_RUN_SERVICE_ID", "NHOST_SERVICE_OVERLAY_NAME"),
},
},
Action: commandConfigEdit,
}
}
func commandConfigEdit(cCtx *cli.Context) error {
ce := clienv.FromCLI(cCtx)
func commandConfigEdit(ctx context.Context, cmd *cli.Command) error {
ce := clienv.FromCLI(cmd)
overlayName := cCtx.String(flagOverlayName)
overlayName := cmd.String(flagOverlayName)
if overlayName == "" {
if err := config.EditFile(
cCtx.Context, cCtx.String(flagEditor), cCtx.String(flagConfig),
ctx, cmd.String(flagEditor), cmd.String(flagConfig),
); err != nil {
return fmt.Errorf("failed to edit config: %w", err)
}
@@ -58,7 +59,7 @@ func commandConfigEdit(cCtx *cli.Context) error {
}
if err := os.MkdirAll(ce.Path.RunServiceOverlaysFolder(
cCtx.String(flagConfig),
cmd.String(flagConfig),
), 0o755); err != nil { //nolint:mnd
return fmt.Errorf("failed to create json patches directory: %w", err)
}
@@ -72,21 +73,21 @@ func commandConfigEdit(cCtx *cli.Context) error {
tmpfileName := filepath.Join(tmpdir, "nhost.toml")
if err := config.CopyConfig[model.ConfigRunServiceConfig](
cCtx.String(flagConfig),
cmd.String(flagConfig),
tmpfileName,
ce.Path.RunServiceOverlay(cCtx.String(flagConfig), overlayName),
ce.Path.RunServiceOverlay(cmd.String(flagConfig), overlayName),
); err != nil {
return fmt.Errorf("failed to copy config: %w", err)
}
if err := config.EditFile(cCtx.Context, cCtx.String(flagEditor), tmpfileName); err != nil {
if err := config.EditFile(ctx, cmd.String(flagEditor), tmpfileName); err != nil {
return fmt.Errorf("failed to edit config: %w", err)
}
if err := config.GenerateJSONPatch(
cCtx.String(flagConfig),
cmd.String(flagConfig),
tmpfileName,
ce.Path.RunServiceOverlay(cCtx.String(flagConfig), overlayName),
ce.Path.RunServiceOverlay(cmd.String(flagConfig), overlayName),
); err != nil {
return fmt.Errorf("failed to generate json patch: %w", err)
}

View File

@@ -1,12 +1,13 @@
package run
import (
"context"
"fmt"
"github.com/nhost/be/services/mimir/model"
"github.com/nhost/nhost/cli/clienv"
"github.com/pelletier/go-toml/v2"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
const flagImage = "image"
@@ -22,29 +23,29 @@ func CommandConfigEditImage() *cli.Command {
Aliases: []string{},
Usage: "Service configuration file",
Value: "nhost-run-service.toml",
EnvVars: []string{"NHOST_RUN_SERVICE_CONFIG"},
Sources: cli.EnvVars("NHOST_RUN_SERVICE_CONFIG"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagImage,
Aliases: []string{},
Usage: "Image to use",
Required: true,
EnvVars: []string{"NHOST_RUN_SERVICE_IMAGE"},
Sources: cli.EnvVars("NHOST_RUN_SERVICE_IMAGE"),
},
},
Action: commandConfigEditImage,
}
}
func commandConfigEditImage(cCtx *cli.Context) error {
func commandConfigEditImage(_ context.Context, cmd *cli.Command) error {
var cfg model.ConfigRunServiceConfig
if err := clienv.UnmarshalFile(cCtx.String(flagConfig), &cfg, toml.Unmarshal); err != nil {
if err := clienv.UnmarshalFile(cmd.String(flagConfig), &cfg, toml.Unmarshal); err != nil {
return fmt.Errorf("failed to unmarshal config: %w", err)
}
cfg.Image.Image = cCtx.String(flagImage)
cfg.Image.Image = cmd.String(flagImage)
if err := clienv.MarshalFile(cfg, cCtx.String(flagConfig), toml.Marshal); err != nil {
if err := clienv.MarshalFile(cfg, cmd.String(flagConfig), toml.Marshal); err != nil {
return fmt.Errorf("failed to marshal config: %w", err)
}

View File

@@ -1,13 +1,14 @@
package run
import (
"context"
"fmt"
"github.com/nhost/be/services/mimir/model"
"github.com/nhost/be/services/mimir/schema"
"github.com/nhost/nhost/cli/clienv"
"github.com/pelletier/go-toml/v2"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func ptr[T any](v T) *T {
@@ -24,8 +25,8 @@ func CommandConfigExample() *cli.Command {
}
}
func commandConfigExample(cCtx *cli.Context) error { //nolint:funlen
ce := clienv.FromCLI(cCtx)
func commandConfigExample(_ context.Context, cmd *cli.Command) error { //nolint:funlen
ce := clienv.FromCLI(cmd)
//nolint:mnd
cfg := &model.ConfigRunServiceConfig{

View File

@@ -1,13 +1,14 @@
package run
import (
"context"
"encoding/json"
"fmt"
"github.com/nhost/be/services/mimir/model"
"github.com/nhost/nhost/cli/clienv"
"github.com/pelletier/go-toml/v2"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
const flagServiceID = "service-id"
@@ -23,36 +24,36 @@ func CommandConfigPull() *cli.Command {
Aliases: []string{},
Usage: "Service configuration file",
Value: "nhost-run-service.toml",
EnvVars: []string{"NHOST_RUN_SERVICE_CONFIG"},
Sources: cli.EnvVars("NHOST_RUN_SERVICE_CONFIG"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagServiceID,
Usage: "Service ID to update",
Required: true,
EnvVars: []string{"NHOST_RUN_SERVICE_ID"},
Sources: cli.EnvVars("NHOST_RUN_SERVICE_ID"),
},
},
Action: commandConfigPull,
}
}
func commandConfigPull(cCtx *cli.Context) error {
ce := clienv.FromCLI(cCtx)
func commandConfigPull(ctx context.Context, cmd *cli.Command) error {
ce := clienv.FromCLI(cmd)
cl, err := ce.GetNhostClient(cCtx.Context)
cl, err := ce.GetNhostClient(ctx)
if err != nil {
return fmt.Errorf("failed to get nhost client: %w", err)
}
appID, err := getAppIDFromServiceID(cCtx.Context, cl, cCtx.String(flagServiceID))
appID, err := getAppIDFromServiceID(ctx, cl, cmd.String(flagServiceID))
if err != nil {
return err
}
resp, err := cl.GetRunServiceConfigRawJSON(
cCtx.Context,
ctx,
appID,
cCtx.String(flagServiceID),
cmd.String(flagServiceID),
false,
)
if err != nil {
@@ -64,7 +65,7 @@ func commandConfigPull(cCtx *cli.Context) error {
return fmt.Errorf("failed to unmarshal config: %w", err)
}
if err := clienv.MarshalFile(v, cCtx.String(flagConfig), toml.Marshal); err != nil {
if err := clienv.MarshalFile(v, cmd.String(flagConfig), toml.Marshal); err != nil {
return fmt.Errorf("failed to save config to file: %w", err)
}

View File

@@ -1,13 +1,14 @@
package run
import (
"context"
"fmt"
"github.com/nhost/be/services/mimir/model"
"github.com/nhost/nhost/cli/clienv"
"github.com/nhost/nhost/cli/project/env"
"github.com/pelletier/go-toml/v2"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func CommandConfigShow() *cli.Command {
@@ -23,19 +24,19 @@ func CommandConfigShow() *cli.Command {
Aliases: []string{},
Usage: "Service configuration file",
Value: "nhost-run-service.toml",
EnvVars: []string{"NHOST_RUN_SERVICE_CONFIG"},
Sources: cli.EnvVars("NHOST_RUN_SERVICE_CONFIG"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagOverlayName,
Usage: "If specified, apply this overlay",
EnvVars: []string{"NHOST_RUN_SERVICE_ID", "NHOST_SERVICE_OVERLAY_NAME"},
Sources: cli.EnvVars("NHOST_RUN_SERVICE_ID", "NHOST_SERVICE_OVERLAY_NAME"),
},
},
}
}
func commandConfigShow(cCtx *cli.Context) error {
ce := clienv.FromCLI(cCtx)
func commandConfigShow(_ context.Context, cmd *cli.Command) error {
ce := clienv.FromCLI(cmd)
var secrets model.Secrets
if err := clienv.UnmarshalFile(ce.Path.Secrets(), &secrets, env.Unmarshal); err != nil {
@@ -47,8 +48,8 @@ func commandConfigShow(cCtx *cli.Context) error {
cfg, err := Validate(
ce,
cCtx.String(flagConfig),
cCtx.String(flagOverlayName),
cmd.String(flagConfig),
cmd.String(flagOverlayName),
secrets,
false,
)

View File

@@ -15,7 +15,7 @@ import (
"github.com/nhost/nhost/cli/nhostclient/graphql"
"github.com/nhost/nhost/cli/project/env"
"github.com/pelletier/go-toml/v2"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
const (
@@ -35,17 +35,17 @@ func CommandConfigValidate() *cli.Command {
Aliases: []string{},
Usage: "Service configuration file",
Value: "nhost-run-service.toml",
EnvVars: []string{"NHOST_RUN_SERVICE_CONFIG"},
Sources: cli.EnvVars("NHOST_RUN_SERVICE_CONFIG"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagOverlayName,
Usage: "If specified, apply this overlay",
EnvVars: []string{"NHOST_SERVICE_OVERLAY_NAME"},
Sources: cli.EnvVars("NHOST_SERVICE_OVERLAY_NAME"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagServiceID,
Usage: "If specified, apply this overlay and remote secrets for this service",
EnvVars: []string{"NHOST_RUN_SERVICE_ID"},
Sources: cli.EnvVars("NHOST_RUN_SERVICE_ID"),
},
},
}
@@ -157,35 +157,35 @@ func getRemoteSecrets(
return respToSecrets(secretsResp.GetAppSecrets()), appID, nil
}
func commandConfigValidate(cCtx *cli.Context) error {
func commandConfigValidate(ctx context.Context, cmd *cli.Command) error {
var (
overlayName string
serviceID string
)
switch {
case cCtx.String(flagServiceID) != "" && cCtx.String(flagOverlayName) != "":
case cmd.String(flagServiceID) != "" && cmd.String(flagOverlayName) != "":
return errors.New("cannot specify both service id and overlay name") //nolint:err113
case cCtx.String(flagServiceID) != "":
serviceID = cCtx.String(flagServiceID)
case cmd.String(flagServiceID) != "":
serviceID = cmd.String(flagServiceID)
overlayName = serviceID
case cCtx.String(flagOverlayName) != "":
overlayName = cCtx.String(flagOverlayName)
case cmd.String(flagOverlayName) != "":
overlayName = cmd.String(flagOverlayName)
}
ce := clienv.FromCLI(cCtx)
ce := clienv.FromCLI(cmd)
var secrets model.Secrets
ce.Infoln("Getting secrets...")
if serviceID != "" {
cl, err := ce.GetNhostClient(cCtx.Context)
cl, err := ce.GetNhostClient(ctx)
if err != nil {
return fmt.Errorf("failed to get nhost client: %w", err)
}
secrets, _, err = getRemoteSecrets(cCtx.Context, cl, serviceID)
secrets, _, err = getRemoteSecrets(ctx, cl, serviceID)
if err != nil {
return err
}
@@ -202,7 +202,7 @@ func commandConfigValidate(cCtx *cli.Context) error {
if _, err := Validate(
ce,
cCtx.String(flagConfig),
cmd.String(flagConfig),
overlayName,
secrets,
false,

View File

@@ -1,13 +1,14 @@
package run
import (
"context"
"fmt"
"regexp"
"github.com/nhost/be/services/mimir/model"
"github.com/nhost/nhost/cli/clienv"
"github.com/nhost/nhost/cli/project/env"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
const (
@@ -28,17 +29,17 @@ func CommandEnv() *cli.Command {
Aliases: []string{},
Usage: "Service configuration file",
Value: "nhost-run-service.toml",
EnvVars: []string{"NHOST_RUN_SERVICE_CONFIG"},
Sources: cli.EnvVars("NHOST_RUN_SERVICE_CONFIG"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagOverlayName,
Usage: "If specified, apply this overlay",
EnvVars: []string{"NHOST_RUN_SERVICE_ID", "NHOST_SERVICE_OVERLAY_NAME"},
Sources: cli.EnvVars("NHOST_RUN_SERVICE_ID", "NHOST_SERVICE_OVERLAY_NAME"),
},
&cli.BoolFlag{ //nolint:exhaustruct
Name: flagDevPrependExport,
Usage: "Prepend 'export' to each line",
EnvVars: []string{"NHOST_RuN_SERVICE_ENV_PREPEND_EXPORT"},
Sources: cli.EnvVars("NHOST_RuN_SERVICE_ENV_PREPEND_EXPORT"),
},
},
}
@@ -49,8 +50,8 @@ func escape(s string) string {
return re.ReplaceAllString(s, "\\$0")
}
func commandConfigDev(cCtx *cli.Context) error {
ce := clienv.FromCLI(cCtx)
func commandConfigDev(_ context.Context, cmd *cli.Command) error {
ce := clienv.FromCLI(cmd)
var secrets model.Secrets
if err := clienv.UnmarshalFile(ce.Path.Secrets(), &secrets, env.Unmarshal); err != nil {
@@ -62,8 +63,8 @@ func commandConfigDev(cCtx *cli.Context) error {
cfg, err := Validate(
ce,
cCtx.String(flagConfig),
cCtx.String(flagOverlayName),
cmd.String(flagConfig),
cmd.String(flagOverlayName),
secrets,
false,
)
@@ -73,7 +74,7 @@ func commandConfigDev(cCtx *cli.Context) error {
for _, v := range cfg.GetEnvironment() {
value := escape(v.Value)
if cCtx.Bool(flagDevPrependExport) {
if cmd.Bool(flagDevPrependExport) {
ce.Println("export %s=\"%s\"", v.Name, value)
} else {
ce.Println("%s=\"%s\"", v.Name, value)

View File

@@ -1,13 +1,13 @@
package run
import "github.com/urfave/cli/v2"
import "github.com/urfave/cli/v3"
func Command() *cli.Command {
return &cli.Command{ //nolint:exhaustruct
Name: "run",
Aliases: []string{},
Usage: "Perform operations on Nhost Run",
Subcommands: []*cli.Command{
Commands: []*cli.Command{
CommandConfigShow(),
CommandConfigDeploy(),
CommandConfigEdit(),

View File

@@ -1,11 +1,12 @@
package secrets //nolint:dupl
import (
"context"
"errors"
"fmt"
"github.com/nhost/nhost/cli/clienv"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func CommandCreate() *cli.Command {
@@ -19,28 +20,28 @@ func CommandCreate() *cli.Command {
}
}
func commandCreate(cCtx *cli.Context) error {
if cCtx.NArg() != 2 { //nolint:mnd
func commandCreate(ctx context.Context, cmd *cli.Command) error {
if cmd.NArg() != 2 { //nolint:mnd
return errors.New("invalid number of arguments") //nolint:err113
}
ce := clienv.FromCLI(cCtx)
ce := clienv.FromCLI(cmd)
proj, err := ce.GetAppInfo(cCtx.Context, cCtx.String(flagSubdomain))
proj, err := ce.GetAppInfo(ctx, cmd.String(flagSubdomain))
if err != nil {
return fmt.Errorf("failed to get app info: %w", err)
}
cl, err := ce.GetNhostClient(cCtx.Context)
cl, err := ce.GetNhostClient(ctx)
if err != nil {
return fmt.Errorf("failed to get nhost client: %w", err)
}
if _, err := cl.CreateSecret(
cCtx.Context,
ctx,
proj.ID,
cCtx.Args().Get(0),
cCtx.Args().Get(1),
cmd.Args().Get(0),
cmd.Args().Get(1),
); err != nil {
return fmt.Errorf("failed to create secret: %w", err)
}

View File

@@ -1,11 +1,12 @@
package secrets
import (
"context"
"errors"
"fmt"
"github.com/nhost/nhost/cli/clienv"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func CommandDelete() *cli.Command {
@@ -19,27 +20,27 @@ func CommandDelete() *cli.Command {
}
}
func commandDelete(cCtx *cli.Context) error {
if cCtx.NArg() != 1 {
func commandDelete(ctx context.Context, cmd *cli.Command) error {
if cmd.NArg() != 1 {
return errors.New("invalid number of arguments") //nolint:err113
}
ce := clienv.FromCLI(cCtx)
ce := clienv.FromCLI(cmd)
proj, err := ce.GetAppInfo(cCtx.Context, cCtx.String(flagSubdomain))
proj, err := ce.GetAppInfo(ctx, cmd.String(flagSubdomain))
if err != nil {
return fmt.Errorf("failed to get app info: %w", err)
}
cl, err := ce.GetNhostClient(cCtx.Context)
cl, err := ce.GetNhostClient(ctx)
if err != nil {
return fmt.Errorf("failed to get nhost client: %w", err)
}
if _, err := cl.DeleteSecret(
cCtx.Context,
ctx,
proj.ID,
cCtx.Args().Get(0),
cmd.Args().Get(0),
); err != nil {
return fmt.Errorf("failed to delete secret: %w", err)
}

View File

@@ -1,10 +1,11 @@
package secrets
import (
"context"
"fmt"
"github.com/nhost/nhost/cli/clienv"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func CommandList() *cli.Command {
@@ -17,21 +18,21 @@ func CommandList() *cli.Command {
}
}
func commandList(cCtx *cli.Context) error {
ce := clienv.FromCLI(cCtx)
func commandList(ctx context.Context, cmd *cli.Command) error {
ce := clienv.FromCLI(cmd)
proj, err := ce.GetAppInfo(cCtx.Context, cCtx.String(flagSubdomain))
proj, err := ce.GetAppInfo(ctx, cmd.String(flagSubdomain))
if err != nil {
return fmt.Errorf("failed to get app info: %w", err)
}
cl, err := ce.GetNhostClient(cCtx.Context)
cl, err := ce.GetNhostClient(ctx)
if err != nil {
return fmt.Errorf("failed to get nhost client: %w", err)
}
secrets, err := cl.GetSecrets(
cCtx.Context,
ctx,
proj.ID,
)
if err != nil {

View File

@@ -1,6 +1,6 @@
package secrets
import "github.com/urfave/cli/v2"
import "github.com/urfave/cli/v3"
const flagSubdomain = "subdomain"
@@ -9,7 +9,7 @@ func commonFlags() []cli.Flag {
&cli.StringFlag{ //nolint:exhaustruct
Name: flagSubdomain,
Usage: "Project's subdomain to operate on, defaults to linked project",
EnvVars: []string{"NHOST_SUBDOMAIN"},
Sources: cli.EnvVars("NHOST_SUBDOMAIN"),
},
}
}
@@ -19,7 +19,7 @@ func Command() *cli.Command {
Name: "secrets",
Aliases: []string{},
Usage: "Manage secrets",
Subcommands: []*cli.Command{
Commands: []*cli.Command{
CommandCreate(),
CommandDelete(),
CommandList(),

View File

@@ -1,11 +1,12 @@
package secrets //nolint:dupl
import (
"context"
"errors"
"fmt"
"github.com/nhost/nhost/cli/clienv"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func CommandUpdate() *cli.Command {
@@ -19,28 +20,28 @@ func CommandUpdate() *cli.Command {
}
}
func commandUpdate(cCtx *cli.Context) error {
if cCtx.NArg() != 2 { //nolint:mnd
func commandUpdate(ctx context.Context, cmd *cli.Command) error {
if cmd.NArg() != 2 { //nolint:mnd
return errors.New("invalid number of arguments") //nolint:err113
}
ce := clienv.FromCLI(cCtx)
ce := clienv.FromCLI(cmd)
proj, err := ce.GetAppInfo(cCtx.Context, cCtx.String(flagSubdomain))
proj, err := ce.GetAppInfo(ctx, cmd.String(flagSubdomain))
if err != nil {
return fmt.Errorf("failed to get app info: %w", err)
}
cl, err := ce.GetNhostClient(cCtx.Context)
cl, err := ce.GetNhostClient(ctx)
if err != nil {
return fmt.Errorf("failed to get nhost client: %w", err)
}
if _, err := cl.UpdateSecret(
cCtx.Context,
ctx,
proj.ID,
cCtx.Args().Get(0),
cCtx.Args().Get(1),
cmd.Args().Get(0),
cmd.Args().Get(1),
); err != nil {
return fmt.Errorf("failed to update secret: %w", err)
}

Some files were not shown because too many files have changed in this diff Show More