diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4c415668..795dc971 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,9 +6,13 @@ on: - '*' paths-ignore: - 'examples/**' + - 'docs/**' pull_request: branches: - main + paths-ignore: + - 'examples/**' + - 'docs/**' workflow_dispatch: {} jobs: @@ -60,31 +64,11 @@ jobs: reporter: mochawesome-json - name: deploy report - uses: ./.github/workflows/shared/deploy-e2e-report + uses: ./.github/workflows/shared/deploy-netlify if: ${{ !cancelled() && github.repository == 'mistic100/Photo-Sphere-Viewer' && github.event_name != 'pull_request' }} with: - netlify-auth-token: ${{ secrets.NETLIFY_AUTH_TOKEN }} - netlify-site-id: ${{ secrets.NETLIFY_REPORTS_SITE_ID }} - - build-doc: - runs-on: ubuntu-latest - needs: build - - steps: - - uses: actions/checkout@v4 - - - uses: dorny/paths-filter@v3 - id: changes - with: - filters: | - docs: - - 'docs/**' - - 'package.json' - - - if: steps.changes.outputs.docs == 'true' - name: setup - uses: ./.github/workflows/shared/setup - - - if: steps.changes.outputs.docs == 'true' - name: build-doc - run: yarn ci:build-doc + env: cypress + root-folder: cypress/reports/html + excludes: .jsons + auth-token: ${{ secrets.NETLIFY_AUTH_TOKEN }} + site-id: ${{ secrets.NETLIFY_REPORTS_SITE_ID }} diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 00000000..d6eb5476 --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,31 @@ +name: documentation + +on: + push: + branches: + - '*' + paths-ignore: + - 'cypress/**' + - 'examples/**' + - 'packages/**' + pull_request: + branches: + - main + paths-ignore: + - 'cypress/**' + - 'examples/**' + - 'packages/**' + workflow_dispatch: {} + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: setup + uses: ./.github/workflows/shared/setup + + - name: build + run: yarn ci:build-doc diff --git a/.github/workflows/shared/deploy-e2e-report/action.yml b/.github/workflows/shared/deploy-e2e-report/action.yml deleted file mode 100644 index 9d0a6ad3..00000000 --- a/.github/workflows/shared/deploy-e2e-report/action.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: deploy-e2e-report - -inputs: - netlify-auth-token: - required: true - netlify-site-id: - required: true - -runs: - using: composite - - steps: - - name: start deploy - uses: bobheadxi/deployments@v1 - id: deployment - with: - step: start - env: cypress - - - name: deploy - id: netlify - shell: bash - run: node ./build/deploy-netlify.mjs --rootFolder=cypress/reports/html --exclude=.jsons --branch=${{ github.ref_name }} - env: - NETLIFY_AUTH_TOKEN: ${{ inputs.netlify-auth-token }} - NETLIFY_SITE_ID: ${{ inputs.netlify-site-id }} - - - name: finish deploy - uses: bobheadxi/deployments@v1 - with: - step: finish - status: success - env: ${{ steps.deployment.outputs.env }} - deployment_id: ${{ steps.deployment.outputs.deployment_id }} - env_url: ${{ steps.netlify.url.deploy_url }} diff --git a/.github/workflows/shared/deploy-netlify/action.yml b/.github/workflows/shared/deploy-netlify/action.yml new file mode 100644 index 00000000..c7ca8ec3 --- /dev/null +++ b/.github/workflows/shared/deploy-netlify/action.yml @@ -0,0 +1,43 @@ +name: deploy-netlify + +inputs: + env: + required: true + root-folder: + required: true + excludes: + default: '' + functions-folder: + default: '' + auth-token: + required: true + site-id: + required: true + +runs: + using: composite + + steps: + - name: start deploy + uses: bobheadxi/deployments@v1 + id: deployment + with: + step: start + env: ${{ inputs.env }} + + - name: deploy + id: netlify + shell: bash + run: node ./build/deploy-netlify.mjs --rootFolder=${{ inputs.root-folder }} --exclude=${{ inputs.excludes }} --functionsFolder=${{ inputs.functions-folder }} --branch=${{ github.ref_name }} + env: + NETLIFY_AUTH_TOKEN: ${{ inputs.auth-token }} + NETLIFY_SITE_ID: ${{ inputs.site-id }} + + - name: finish deploy + uses: bobheadxi/deployments@v1 + with: + step: finish + status: success + env: ${{ steps.deployment.outputs.env }} + deployment_id: ${{ steps.deployment.outputs.deployment_id }} + env_url: ${{ steps.netlify.deploy_url }} diff --git a/build/deploy-netlify.mjs b/build/deploy-netlify.mjs index b3712463..8431162a 100644 --- a/build/deploy-netlify.mjs +++ b/build/deploy-netlify.mjs @@ -12,6 +12,8 @@ import path from 'path'; import yargs from 'yargs'; import Queue from 'queue'; +const MAX_RETRIES = 5; + (async () => { // --branch (optional) // --rootFolder @@ -111,43 +113,69 @@ async function listFilesWithHashes(dir, exclude, hashfn) { * Creates a new deployment on Netlify */ async function createDeploy(branch, files, functions) { - const result = await fetch(`https://api.netlify.com/api/v1/sites/${process.env.NETLIFY_SITE_ID}/deploys`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'Bearer ' + process.env.NETLIFY_AUTH_TOKEN, - }, - body: JSON.stringify({ - branch, - files, - functions: Object.entries(functions).reduce((res, [name, hash]) => ({ - ...res, - [name.replace('.zip', '')]: hash, - }), {}), - }), - }); + try { + const result = await retryFetch(`https://api.netlify.com/api/v1/sites/${process.env.NETLIFY_SITE_ID}/deploys`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer ' + process.env.NETLIFY_AUTH_TOKEN, + }, + body: JSON.stringify({ + branch, + files, + functions: Object.entries(functions).reduce((res, [name, hash]) => ({ + ...res, + [name.replace('.zip', '')]: hash, + }), {}), + }), + }); + + const deploy = await result.json(); - const deploy = await result.json(); + console.log(`Created deploy #${deploy.id} (${deploy.deploy_ssl_url}). ${deploy.required.length} new files.`); - console.log(`Created deploy #${deploy.id} (${deploy.deploy_ssl_url}). ${deploy.required.length} new files.`) + return deploy; - return deploy; + } catch { + console.error('Cannot create deploy'); + process.exit(1); + } } /** * Publish the deploy */ async function publishDeploy(deploy) { - const result = await fetch(`https://api.netlify.com/api/v1/sites/${process.env.NETLIFY_SITE_ID}/deploys/${deploy.id}/restore`, { - method: 'POST', - headers: { - 'Authorization': 'Bearer ' + process.env.NETLIFY_AUTH_TOKEN, - }, - }); + try { + await retryFetch(`https://api.netlify.com/api/v1/sites/${process.env.NETLIFY_SITE_ID}/deploys/${deploy.id}/restore`, { + method: 'POST', + headers: { + 'Authorization': 'Bearer ' + process.env.NETLIFY_AUTH_TOKEN, + }, + }); - deploy = await result.json(); + console.log(`Published deploy #${deploy.id} (${deploy.ssl_url}).`); + } catch { + console.warn(`Cannot publish deploy`); + } +} - console.log(`Published deploy #${deploy.id} (${deploy.ssl_url}).`) +/** + * Cancel the deploy + */ +async function cancelDeploy(deploy) { + try { + await retryFetch(`https://api.netlify.com/api/v1/deploys/${deploy.id}/cancel`, { + method: 'POST', + headers: { + 'Authorization': 'Bearer ' + process.env.NETLIFY_AUTH_TOKEN, + }, + }); + + console.log(`Cancelled deploy #${deploy.id} (${deploy.ssl_url}).`); + } catch { + console.warn(`Cannot cancel deploy`); + } } /** @@ -172,7 +200,7 @@ async function uploadFiles(dir, files, deploy) { console.log(`Upload ${file}`); - fetch(`https://api.netlify.com/api/v1/deploys/${deploy.id}/files/${encodeURIComponent(file)}`, { + retryFetch(`https://api.netlify.com/api/v1/deploys/${deploy.id}/files/${encodeURIComponent(file)}`, { method: 'PUT', headers: { 'Content-Type': 'application/octet-stream', @@ -186,11 +214,17 @@ async function uploadFiles(dir, files, deploy) { }); }); - return new Promise((resolve, reject) => { - queue.start((err) => { - err ? reject(err) : resolve(); + try { + await new Promise((resolve, reject) => { + queue.start((err) => { + err ? reject(err) : resolve(); + }); }); - }); + } catch { + console.error(`Cannot upload files`); + await cancelDeploy(deploy); + process.exit(1); + } } /** @@ -227,9 +261,32 @@ async function uploadFunctions(dir, functions, deploy) { }); }); - return new Promise((resolve, reject) => { - queue.start((err) => { - err ? reject(err) : resolve(); + try { + await new Promise((resolve, reject) => { + queue.start((err) => { + err ? reject(err) : resolve(); + }); }); - }); + } catch { + console.error(`Cannot upload functions`); + await cancelDeploy(deploy); + process.exit(1); + } +} + +async function retryFetch(url, params) { + for (let i = 0; i < MAX_RETRIES; i++) { + const result = await fetch(url, params); + + if (result.status < 300) { + return result; + } + + if (i < MAX_RETRIES - 1) { + console.warn(`http status=${result.status}; retry ${i + 1}/${MAX_RETRIES}`); + await new Promise(resolve => setTimeout(resolve, 500)); + } + } + + throw new Error(); }