From 819074bc3d1d585a530e99adbc2c543d0c9440af Mon Sep 17 00:00:00 2001 From: eric <236061711+eric-stacks@users.noreply.github.com> Date: Fri, 30 Jan 2026 15:30:19 -0500 Subject: [PATCH] add new workflow file to checking updates on the openapi spec --- .DS_Store | Bin 6148 -> 8196 bytes .github/labeler.yml | 13 - .../.markdown-link-check_config.json | 20 -- .github/workflows/add-to-project.yml.draft | 18 -- .../workflows/auto-approve-translations.yml | 13 - .github/workflows/check-openapi-spec.yml | 244 ++++++++++++++++++ .github/workflows/code-quality.yml.old | 23 -- .github/workflows/label.yml | 22 -- .github/workflows/markdown-link-check.yml | 25 -- README.md | 7 +- docs/.DS_Store | Bin 0 -> 8196 bytes docs/build/.DS_Store | Bin 0 -> 8196 bytes docs/cookbook/.gitkeep | 0 13 files changed, 250 insertions(+), 135 deletions(-) delete mode 100644 .github/labeler.yml delete mode 100644 .github/workflows/.markdown-link-check_config.json delete mode 100644 .github/workflows/add-to-project.yml.draft delete mode 100644 .github/workflows/auto-approve-translations.yml create mode 100644 .github/workflows/check-openapi-spec.yml delete mode 100644 .github/workflows/code-quality.yml.old delete mode 100644 .github/workflows/label.yml delete mode 100644 .github/workflows/markdown-link-check.yml create mode 100644 docs/.DS_Store create mode 100644 docs/build/.DS_Store create mode 100644 docs/cookbook/.gitkeep diff --git a/.DS_Store b/.DS_Store index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..ca8db84c9009be60ff4793563be78590461a6865 100644 GIT binary patch literal 8196 zcmeHMU2GIp6u#fIzzh>$s4Wy^*p(HiP{L9Qf+%deZRDrrZ(I7qPublW>4fP_*_qv? zHZ}E01vT+SqvFq#Q6EsF5cJ7JiJBN62omK*jEPUY_=gWB#&hS+mX@DK4Z*p|z2}~D z&$+Yb%y;gcdzUeW)}pb7v3kasOqEkjrRElekMnv{kwQr|QII{$mA&Dd?dFI-cawG` zh$0Y0Ac{Z~fhYn|1nz_g(4NhUe4Bk=j7Dt~fhYq1%LwrGAx@RkR7l4K4VDgSf+GM? zegp^#^)=oh8V_kIq~n6p2o(rZqQVs65d*@Uy^894-5~Ql?Y13!OGh|ghm>G1-89(b6 zz1D1DKj*q!*UnY?+1x(MEDvb4Y1i?ymSYwI1IsW;giy=m{jQG3X7PY4$fE(2`V4KAIW?2ql0 z(&9d%DpT?2a>XBIrmD`;dwTo&_a7M0w4!J3HSMf#mCUxRZ+ZjT9FN@FmUo@q1+zqM z_2gZWZZ@=dj{B$r` zuSN3oLnLD9T(kb~)K0^I}c?f`#`k(siM8-KH{2Z!u>bD_Z`D zmXKxf41GyLk+m}i405v5HTaB1Frw>Amdo;fE>9OsYK2(US2mJCw#y@z!=IU4qbQo! zb(CJaFjd#Bmle(EwzE0Y4n+@bBGHbXlJGbb)@_lM-n=_BM9z3Zrl;FvS^M%3e{e_E z^LGuK&cshjZ?C`Uy}>?WXV_VGo_)hEu`BFn_6z%!{l>1bzfg^tsKp{I#d0*@0W@JPnz0^f zbf6PE(T_X~m^cC(MU+s+2%f-GcpA^(BwoNPconDcI^M#2cpo3&6MTlxaUNgb5`MrH z{D`ag1K04Uq)0W=B1xAPOUtDOX@#^>+AM96wn}@XerZ4&6h26mR697~Q_zt{h)gUU zlxpGCpLF`xsTO;5>$dGXI_}t1`+hc0u6t_h=FN{UU9q}(!y_#d5EO{5fOInW1H4bk zk^t`kCT_%PkcH~vST`kvdHi}BinOdGqc(#lu6&Ve-=`*G+B8ZT!7fu9V%l^{7s0NK zt%_+9WvO73u_jfkq8wBL+jX&3ss>pw8`Vv!R!!L`*v;x8Ua+dW!hRvcDpr?pS9yu4^!oZ x>x~Nw5KPGg_gAhnpCgF}!R pBnTAa1`@77th%xAJM(0I8AV3M$)+;eJWLRCKt?lcj^~-f3;-`E4{!hg diff --git a/.github/labeler.yml b/.github/labeler.yml deleted file mode 100644 index e85768c855..0000000000 --- a/.github/labeler.yml +++ /dev/null @@ -1,13 +0,0 @@ -# Add labels when specific files are changed -translations: - - any: ['i18n/**/*'] - -documentation: - - any: ['docs/**/*.md', 'docs/**/*.mdx'] - -dependencies: - - 'package.json' - - 'package-lock-json' - -docusaurus-config: - - 'docusaurus.config.js' \ No newline at end of file diff --git a/.github/workflows/.markdown-link-check_config.json b/.github/workflows/.markdown-link-check_config.json deleted file mode 100644 index e0e786f3fb..0000000000 --- a/.github/workflows/.markdown-link-check_config.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "ignorePatterns": [ - { - "pattern": "^https:\/\/www\.coinbase\.com\b" - }, - { - "pattern": "^https:\/\/www\.kraken\.com\b" - } - ], - "replacementPatterns": [ - { - "pattern": "^/", - "replacement": "/github/workspace/" - } - ], - "aliveStatusCodes": [ - 200, - 429 - ] -} \ No newline at end of file diff --git a/.github/workflows/add-to-project.yml.draft b/.github/workflows/add-to-project.yml.draft deleted file mode 100644 index 2afce92968..0000000000 --- a/.github/workflows/add-to-project.yml.draft +++ /dev/null @@ -1,18 +0,0 @@ -name: Add issue/PR to project - -on: - issues: - types: - - opened - pull_request: - branches: [master] - -jobs: - add-to-project: - name: Add issue to project - runs-on: ubuntu-latest - steps: - - uses: actions/add-to-project@main - with: - project-url: https://github.com/orgs/stacks-network/projects/56 - github-token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/auto-approve-translations.yml b/.github/workflows/auto-approve-translations.yml deleted file mode 100644 index 0aaf99a723..0000000000 --- a/.github/workflows/auto-approve-translations.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: Auto approve translations -on: - pull_request -jobs: - build: - runs-on: ubuntu-latest - permissions: - pull-requests: write - steps: - - uses: hmarr/auto-approve-action@v2 - if: github.actor == 'bot-translations' - with: - github-token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/check-openapi-spec.yml b/.github/workflows/check-openapi-spec.yml new file mode 100644 index 0000000000..efca39394b --- /dev/null +++ b/.github/workflows/check-openapi-spec.yml @@ -0,0 +1,244 @@ +name: Check Stacks Node RPC OpenAPI Spec Updates + +on: + schedule: + - cron: '0 0 */2 * *' # Every 2 days at midnight UTC + workflow_dispatch: # Manual trigger for testing + +jobs: + check-updates: + runs-on: ubuntu-latest + permissions: + issues: write # Allow creating issues + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + # Restore the previously cached spec (if exists) + - name: Restore cached OpenAPI spec + id: cache-restore + uses: actions/cache/restore@v3 + with: + path: openapi-previous.yaml + key: openapi-spec-cache + + # Download the latest spec with error handling + - name: Download latest OpenAPI spec + id: download + run: | + echo "🔄 Downloading OpenAPI spec..." + + HTTP_CODE=$(curl -w "%{http_code}" -o openapi-new.yaml -s \ + https://raw.githubusercontent.com/stacks-network/stacks-core/master/docs/rpc/openapi.yaml) + + if [ "$HTTP_CODE" -ne 200 ]; then + echo "❌ Failed to download OpenAPI spec. HTTP status: $HTTP_CODE" + echo "error=true" >> $GITHUB_OUTPUT + echo "error_message=Failed to download OpenAPI spec (HTTP $HTTP_CODE)" >> $GITHUB_OUTPUT + exit 1 + fi + + # Verify the file is valid YAML + if ! python3 -c "import yaml; yaml.safe_load(open('openapi-new.yaml'))" 2>/dev/null; then + echo "❌ Downloaded file is not valid YAML" + echo "error=true" >> $GITHUB_OUTPUT + echo "error_message=Downloaded file is not valid YAML" >> $GITHUB_OUTPUT + exit 1 + fi + + echo "✅ Successfully downloaded valid OpenAPI spec" + echo "error=false" >> $GITHUB_OUTPUT + + # Check if it changed + - name: Check for changes + if: steps.download.outputs.error != 'true' + id: check + run: | + if [ -f openapi-previous.yaml ]; then + echo "đŸ“Ļ Found cached previous spec" + if ! diff -q openapi-new.yaml openapi-previous.yaml > /dev/null; then + echo "changed=true" >> $GITHUB_OUTPUT + echo "✅ Spec has changed!" + else + echo "changed=false" >> $GITHUB_OUTPUT + echo "â„šī¸ No changes detected" + fi + else + echo "âš ī¸ No previous spec found (first run or cache expired)" + echo "changed=true" >> $GITHUB_OUTPUT + echo "first_run=true" >> $GITHUB_OUTPUT + echo "Treating as changed to establish baseline" + fi + + # Get upstream commit information + - name: Get upstream commits + if: steps.check.outputs.changed == 'true' && steps.download.outputs.error != 'true' + id: commits + continue-on-error: true # Don't fail workflow if this step fails + run: | + echo "🔍 Fetching upstream commits..." + + # Get the 5 most recent commits affecting the OpenAPI file + COMMITS_JSON=$(curl -s "https://api.github.com/repos/stacks-network/stacks-core/commits?path=docs/rpc/openapi.yaml&per_page=5") + + # Check if API call was successful + if echo "$COMMITS_JSON" | jq -e '. | length > 0' >/dev/null 2>&1; then + # Extract commit info and format as markdown + echo "### Recent Commits Affecting OpenAPI Spec" > commits.md + echo "" >> commits.md + + echo "$COMMITS_JSON" | jq -r '.[] | + "- **[\(.commit.message | split("\n")[0])](\(.html_url))**\n" + + " - Author: \(.commit.author.name)\n" + + " - Date: \(.commit.author.date[:10])\n" + + " - SHA: `\(.sha[:7])`\n" + ' >> commits.md + + # Get the most recent commit SHA for reference + LATEST_SHA=$(echo "$COMMITS_JSON" | jq -r '.[0].sha[:7]') + echo "latest_sha=$LATEST_SHA" >> $GITHUB_OUTPUT + + # Get the commit message of the most recent change + LATEST_MESSAGE=$(echo "$COMMITS_JSON" | jq -r '.[0].commit.message' | head -n 1) + echo "latest_message=$LATEST_MESSAGE" >> $GITHUB_OUTPUT + + echo "✅ Successfully fetched commit information" + else + echo "âš ī¸ Could not fetch commit information (API may be rate limited)" + echo "No commit information available" > commits.md + echo "latest_sha=unknown" >> $GITHUB_OUTPUT + echo "latest_message=Unknown" >> $GITHUB_OUTPUT + fi + + # Report error via GitHub Issue if download failed + - name: Create error issue + if: steps.download.outputs.error == 'true' + uses: actions/github-script@v7 + with: + script: | + github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: '❌ OpenAPI Spec Check Failed', + body: `Failed to check the OpenAPI spec. + + **Error:** ${{ steps.download.outputs.error_message }} + + **Workflow run:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + + **Source URL:** https://raw.githubusercontent.com/stacks-network/stacks-core/master/docs/rpc/openapi.yaml + + Please check: + - Is the upstream repository accessible? + - Is the file path correct? + - Are there any network issues? + + The workflow will retry on the next scheduled run.`, + labels: ['openapi-update', 'automation', 'error'] + }); + + # Alert via GitHub Issue with commit info + - name: Create GitHub Issue alert + if: steps.check.outputs.changed == 'true' && steps.download.outputs.error != 'true' + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + + let commitInfo = ''; + let isFirstRun = '${{ steps.check.outputs.first_run }}' === 'true'; + + // Read commit information (may not exist if step failed) + if (fs.existsSync('commits.md')) { + commitInfo = fs.readFileSync('commits.md', 'utf8'); + } else { + commitInfo = '_Commit information unavailable_'; + } + + const latestSha = '${{ steps.commits.outputs.latest_sha }}' || 'unknown'; + const latestMessage = '${{ steps.commits.outputs.latest_message }}' || 'Unknown'; + + const firstRunNote = isFirstRun ? '\n\n> **Note:** This is the first run. No previous version to compare against.\n' : ''; + + const body = `The upstream OpenAPI spec has been updated. + + **Source:** https://raw.githubusercontent.com/stacks-network/stacks-core/master/docs/rpc/openapi.yaml + + **Latest Commit:** \`${latestSha}\` - ${latestMessage} + + **Workflow run:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + ${firstRunNote} + --- + + ${commitInfo} + + --- + + ### Next Steps: + 1. Download the updated spec from the [workflow artifacts](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) + 2. Review the [commit history](https://github.com/stacks-network/stacks-core/commits/master/docs/rpc/openapi.yaml) for context + 3. Run dereferencing process (see [DEREFERENCING_STEPS.md](../blob/main/DEREFERENCING_STEPS.md)) + 4. Fix circular references if needed + 5. Update GitBook documentation + + ### Quick Commands: + \`\`\`bash + # Download and dereference + npx @redocly/cli bundle https://raw.githubusercontent.com/stacks-network/stacks-core/master/docs/rpc/openapi.yaml -o openapi-dereferenced.yaml --dereferenced + + # Search for circular references + grep -n "\\*ref_0" openapi-dereferenced.yaml + + # View specific commit (if available) + git show ${latestSha} + \`\`\``; + + github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: '🚨 OpenAPI Spec Updated - ' + latestMessage, + body: body, + labels: ['openapi-update', 'automation'] + }); + + # Save the new spec to cache for next run + - name: Save new spec to cache + if: steps.check.outputs.changed == 'true' && steps.download.outputs.error != 'true' + uses: actions/cache/save@v3 + with: + path: openapi-new.yaml + key: openapi-spec-cache + + # Upload artifacts for manual download + - name: Upload spec as artifacts + if: steps.check.outputs.changed == 'true' && steps.download.outputs.error != 'true' + uses: actions/upload-artifact@v4 + with: + name: openapi-spec-${{ github.run_number }} + path: | + openapi-new.yaml + openapi-previous.yaml + commits.md + retention-days: 30 + + # Final status summary + - name: Workflow summary + if: always() + run: | + echo "## Workflow Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "${{ steps.download.outputs.error }}" == "true" ]; then + echo "❌ **Status:** Failed to download OpenAPI spec" >> $GITHUB_STEP_SUMMARY + echo "- Error: ${{ steps.download.outputs.error_message }}" >> $GITHUB_STEP_SUMMARY + elif [ "${{ steps.check.outputs.changed }}" == "true" ]; then + echo "✅ **Status:** OpenAPI spec changed" >> $GITHUB_STEP_SUMMARY + echo "- Issue created with details" >> $GITHUB_STEP_SUMMARY + echo "- Artifacts uploaded" >> $GITHUB_STEP_SUMMARY + else + echo "â„šī¸ **Status:** No changes detected" >> $GITHUB_STEP_SUMMARY + fi + + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Next check:** In 2 days" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.github/workflows/code-quality.yml.old b/.github/workflows/code-quality.yml.old deleted file mode 100644 index 3989cc53ab..0000000000 --- a/.github/workflows/code-quality.yml.old +++ /dev/null @@ -1,23 +0,0 @@ - pull_request: - branches: [master] - -jobs: - code_quality: - runs-on: ubuntu-latest - steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.5.0 - with: - access_token: ${{ github.token }} - - name: Checkout - uses: actions/checkout@v2 - - name: Set Node Version - uses: actions/setup-node@v1 - with: - node-version: '14.x' - - name: Install deps - run: yarn --frozen-lockfile - - name: Lint - run: yarn lint - - name: Typecheck - run: yarn typecheck diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml deleted file mode 100644 index ef3d7d51f2..0000000000 --- a/.github/workflows/label.yml +++ /dev/null @@ -1,22 +0,0 @@ -# This workflow will triage pull requests and apply a label based on the -# paths that are modified in the pull request. -# -# To use this workflow, you will need to set up a .github/labeler.yml -# file with configuration. For more information, see: -# https://github.com/actions/labeler - -name: Labeler -on: [pull_request] - -jobs: - label: - - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - - steps: - - uses: actions/labeler@v4 - with: - repo-token: "${{ secrets.GITHUB_TOKEN }}" \ No newline at end of file diff --git a/.github/workflows/markdown-link-check.yml b/.github/workflows/markdown-link-check.yml deleted file mode 100644 index 2e3fcf9bcf..0000000000 --- a/.github/workflows/markdown-link-check.yml +++ /dev/null @@ -1,25 +0,0 @@ -# This only works for external links -# (local links are already checked in normal build) -name: Check Markdown links - -on: - push: - branches: - - master - pull_request: - -jobs: - markdown-link-check: - name: Check for broken links - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - submodules: recursive - - name: Run link check - uses: gaurav-nelson/github-action-markdown-link-check@v1 - with: - use-verbose-mode: 'yes' - folder-path: './' - config-file: '.github/workflows/.markdown-link-check_config.json' diff --git a/README.md b/README.md index af04777922..6a988e5680 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ Below are the current main Spaces that make up the docs: - **Operate**: Setup and configuration guides on running Stacks nodes, miners, and signers. - **Reference**: Clarity language details, protocol specs, SDK & APIs definitions, and other tooling references. - **Tutorials**: End-to-end learning walkthroughs on building complete applications with Stacks. +- **Cookbook**: Collection of reusable Clarity and Stacks.js code snippets that solve common problems. Besides the actual content, each Space consists of: @@ -31,4 +32,8 @@ Besides the actual content, each Space consists of: What kinds of changes are we looking for? -If you see a typo, a missing guide or tutorial, an unclear explanation, or really anything else you think could improve the quality of the documentation, please feel free to open an issue or create a pull request. \ No newline at end of file +If you see a typo, a missing guide or tutorial, an unclear explanation, or really anything else you think could improve the quality of the documentation, please feel free to open an issue or create a pull request. + +Curate contributions that **adds new value** to the docs. + +We will not accept contributions that are deemed as redundant, such as rewording of existing content. There is no need to run our docs through an LLM for the purpose of rearranging or reformatting the content if the general substance of the what the content is trying to convey is the same. \ No newline at end of file diff --git a/docs/.DS_Store b/docs/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..6c391d80b4b0d8727ef7b9b06ec18fb54fa5d0b4 GIT binary patch literal 8196 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 Oe|Y~-PL=