diff --git a/package-lock.json b/package-lock.json index 6ae5728a7a..b36bd56f6b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -172,7 +172,8 @@ "supertest": "^6.1.6", "typescript": "^4.4.3", "url-template": "^2.0.8", - "website-scraper": "^4.2.3" + "website-scraper": "^4.2.3", + "yesno": "^0.3.1" }, "engines": { "node": ">= 16.0.0" @@ -25346,6 +25347,12 @@ "fd-slicer": "~1.1.0" } }, + "node_modules/yesno": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/yesno/-/yesno-0.3.1.tgz", + "integrity": "sha512-7RbCXegyu6DykWPWU0YEtW8gFJH8KBL2d5l2fqB0XpkH0Y9rk59YSSWpzEv7yNJBGAouPc67h3kkq0CZkpBdFw==", + "dev": true + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -45079,6 +45086,12 @@ "fd-slicer": "~1.1.0" } }, + "yesno": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/yesno/-/yesno-0.3.1.tgz", + "integrity": "sha512-7RbCXegyu6DykWPWU0YEtW8gFJH8KBL2d5l2fqB0XpkH0Y9rk59YSSWpzEv7yNJBGAouPc67h3kkq0CZkpBdFw==", + "dev": true + }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index b9e5f9aef1..a4f0a1673b 100644 --- a/package.json +++ b/package.json @@ -174,7 +174,8 @@ "supertest": "^6.1.6", "typescript": "^4.4.3", "url-template": "^2.0.8", - "website-scraper": "^4.2.3" + "website-scraper": "^4.2.3", + "yesno": "^0.3.1" }, "engines": { "node": ">= 16.0.0" diff --git a/script/deploy.js b/script/deploy.js index 0c045bd345..673c6dedb6 100755 --- a/script/deploy.js +++ b/script/deploy.js @@ -11,6 +11,14 @@ // - Optionally, supply a GitHub PAT as the DOCUBOT_REPO_PAT environment // variable if you want to support content from the `docs-early-access` repo // +// For production deployment in particular, you should ideally: +// - Supply the aforementioned DOCUBOT_REPO_PAT environment variable to support +// content from the `docs-early-access` repo. In most cases, you should be +// able to just set this to the same value as GITHUB_TOKEN when running this +// script locally as it just needs read access to that repo. +// - Supply our Fastly API token as the FASTLY_TOKEN enviroment variable +// - Supply our Fastly Service ID as the FASTLY_SERVICE_ID environment variable +// // Examples: // - Deploy a PR to Staging and force the Heroku App to be rebuilt from scratch (by default): // script/deploy.js --staging https://github.com/github/docs/pull/9876 @@ -29,10 +37,13 @@ import dotenv from 'dotenv' import program from 'commander' import { has } from 'lodash-es' +import yesno from 'yesno' import getOctokit from './helpers/github.js' import parsePrUrl from './deployment/parse-pr-url.js' import deployToStaging from './deployment/deploy-to-staging.js' import undeployFromStaging from './deployment/undeploy-from-staging.js' +import deployToProduction from './deployment/deploy-to-production.js' +import purgeEdgeCache from './deployment/purge-edge-cache.js' dotenv.config() @@ -71,7 +82,7 @@ const opts = program.opts() const isProduction = opts.production === true const isStaging = has(opts, 'staging') const prUrl = opts.staging -const forceRebuild = opts.rebuild !== false +const forceRebuild = !isProduction && opts.rebuild !== false const destroy = opts.destroy === true // @@ -138,12 +149,57 @@ async function deploy() { } async function deployProduction() { - // TODO: Request confirmation before deploying to production + const { DOCUBOT_REPO_PAT, FASTLY_TOKEN, FASTLY_SERVICE_ID } = process.env - invalidateAndExit( - 'commander.invalidArgument', - `error: option '${PRODUCTION_FLAG}' is not yet implemented. SOON!` - ) + // Warn if @docubot PAT is not found + if (!DOCUBOT_REPO_PAT) { + console.warn( + '⚠️ You did not supply a DOCUBOT_REPO_PAT environment variable.\nWithout it, this deployment will not contain any Early Access content!' + ) + } + + // Warn if Fastly credentials are not found + if (!FASTLY_TOKEN) { + console.warn( + '⚠️ You did not supply a FASTLY_TOKEN environment variable.\nWithout it, this deployment will not soft-purge the Fastly cache!' + ) + } + if (!FASTLY_SERVICE_ID) { + console.warn( + '⚠️ You did not supply a FASTLY_SERVICE_ID environment variable.\nWithout it, this deployment will not soft-purge the Fastly cache!' + ) + } + if (!process.env.FASTLY_SURROGATE_KEY) { + // Default to our current Fastly surrogate key if unspecified + process.env.FASTLY_SURROGATE_KEY = 'all-the-things' + } + + // Request confirmation before deploying to production + const proceed = await yesno({ + question: '\n🛑 You have selected to deploy to production. ARE YOU CERTAIN!?', + defaultValue: null, + }) + + if (!proceed) { + console.error('\n❌ User canceled the production deployment! Halting...') + process.exit(1) + } + + // This helper uses the `GITHUB_TOKEN` implicitly + const octokit = getOctokit() + + try { + await deployToProduction({ + octokit, + includeDelayForPreboot: !!(FASTLY_TOKEN && FASTLY_SERVICE_ID), + }) + + await purgeEdgeCache() + } catch (error) { + console.error(`Failed to deploy production: ${error.message}`) + console.error(error) + process.exit(1) + } } async function deployStaging({ owner, repo, pullNumber, forceRebuild = false, destroy = false }) { diff --git a/script/deployment/deploy-to-production.js b/script/deployment/deploy-to-production.js index 5ccc4f4ca5..16f6c5831a 100755 --- a/script/deployment/deploy-to-production.js +++ b/script/deployment/deploy-to-production.js @@ -355,6 +355,10 @@ export default async function deployToProduction({ // Is there a faster alternative than this arbitrary delay? For example, // is there some Heroku API we can query to see when this release is // considered to be the live one, or when the old dynos are shut down? + } else { + console.warn( + '⚠️ Bypassing the wait for Heroku Preboot....\nPlease understand that your changes will not be visible for at least another 2 minutes!' + ) } // Report success!