diff --git a/.circleci/config.yml b/.circleci/config.yml index cd64def255..bb0ea87866 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -14,6 +14,12 @@ version: 2.1 GARDEN_TASK_CONCURRENCY_LIMIT: "10" resource_class: large + # Configuration for release jobs + release-config: &release-config + docker: + # Image that contains ghr for publishing releases to Github + - image: cibuilds/github:0.10 + # Attach's the current saved workspace attach-workspace: &attach-workspace attach_workspace: @@ -161,6 +167,29 @@ commands: gcloud --quiet config set project $GOOGLE_PROJECT_ID && gcloud --quiet config set compute/zone $GOOGLE_COMPUTE_ZONE gcloud --quiet container clusters get-credentials $GOOGLE_CLUSTER_ID --zone $GOOGLE_COMPUTE_ZONE gcloud --quiet auth configure-docker + + build_service_dist: + description: Package built code into executables and persist to garden-service/dist directory + parameters: + version: + description: | + The version tag used when building. Use to set the version string in the generated zip file names, + e.g. when creating unstable releases. The script defaults to using the version from garden-service/package.json. + type: string + default: "" + steps: + - checkout + - npm_install + - run: sudo apt-get update && sudo apt-get -y install rsync + - *attach-workspace + - include_dashboard + - run: + name: Run dist command with the appropriate argument + command: npm run dist -- -- <> + - persist_to_workspace: + root: ./ + paths: + - garden-service/dist/ # # Jobs section # @@ -278,15 +307,53 @@ jobs: build-service-dist: <<: *node-config steps: + - build_service_dist: + version: $(./garden-service/bin/garden --version) + build-service-dist-next: + <<: *node-config + steps: + - build_service_dist: + version: next + release-service-dist: + <<: *release-config + steps: + # Need to checkout to read version from garden-service/package.json - checkout - - npm_install - - run: sudo apt-get update && sudo apt-get -y install rsync - *attach-workspace - - include_dashboard - - run: npm run dist - - store_artifacts: - path: garden-service/dist/ - destination: /downloads + - run: + name: Create a release on GitHub. If the release is a pre-release we publish it right away, otherwise we make a draft. + command: | + VERSION="v$(cat garden-service/package.json | jq -r .version)" + PRERELEASE="" + DRAFT="" + if [[ $VERSION == *"-"* ]]; then DRAFT=-draft; PRERELEASE=-prerelease; fi + ghr \ + -t ${GITHUB_TOKEN} \ + -u ${CIRCLE_PROJECT_USERNAME} \ + -r ${CIRCLE_PROJECT_REPONAME} \ + -c ${CIRCLE_SHA1} \ + -n ${VERSION} \ + -delete \ + ${DRAFT} \ + ${PRERELEASE} \ + ${VERSION} ./garden-service/dist + release-service-dist-next: + <<: *release-config + steps: + - *attach-workspace + - run: + name: Publish a pre-release on GitHub with the tag 'next' + command: | + VERSION=next + ghr \ + -t ${GITHUB_TOKEN} \ + -u ${CIRCLE_PROJECT_USERNAME} \ + -r ${CIRCLE_PROJECT_REPONAME} \ + -c ${CIRCLE_SHA1} \ + -n ${VERSION} \ + -delete \ + -prerelease \ + ${VERSION} ./garden-service/dist workflows: version: 2 @@ -310,6 +377,7 @@ workflows: - test-dashboard: requires: - build-service + master: jobs: # Duplicated here so we can reference steps that depends on it @@ -335,10 +403,14 @@ workflows: context: docker requires: - release-service-docker - - build-service-dist: + - build-service-dist-next: <<: *only-master requires: - build-service + - release-service-dist-next: + <<: *only-master + requires: + - build-service-dist-next tags: jobs: @@ -369,3 +441,8 @@ workflows: <<: *only-tags requires: - build-service + - release-service-dist: + <<: *only-tags + requires: + - build-service-dist + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d370b74513..4ac7571814 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -183,13 +183,18 @@ Our release process generates the following packages: ### Process -The release process is twofold, first a [release script](https://github.com/garden-io/garden/blob/master/bin/release.ts) is run. The script has the signature: `./bin/release.tsx [--force]` and does the following: +The [release script](https://github.com/garden-io/garden/blob/master/bin/release.ts) has the signature: +```sh +./bin/release.tsx [--force] [--dry-run] +``` +and does the following: * Checks out a branch named `release-`. -* Updates `package.json` and `package-lock.json` for `garden-service` and the changelog. -* Commits the changes, tags the commit and pushes the tag and branch, triggering a CI process the creates the release artifacts. +* Updates `garden-service/package.json`, `garden-service/package-lock.json` and `CHANGELOG.md`. +* Commits the changes, tags the commit, and pushes the tag and branch. +* Pushing the tag triggers a CI process the creates the release artifacts and publishes them to Github. If the the release is not a pre-release, we create a draft instead of actually publishing. -Second, we manually upload the artifacts generated in CI to our Github releases page and then write the release notes. +On every merge to `master` we also publish an **unstable** release with the version `next` that is always flagged as a pre-release. ### Steps @@ -202,14 +207,12 @@ To make a new release, set your current working directory to the garden root dir * `git rebase master` * `./bin/release.ts prerelease` * If you’re ready to make a proper release, run `./bin/release.ts minor | patch` from `master`. This way, the version bump commits created by the prereleases are omitted from the final history. -2. Open the [Garden project on CircleCI](https://circleci.com/gh/garden-io/garden) and browse to the job marked `release-service-pkg`. Open the **Artifacts** tab and download the listed artifacts. -3. Go to our Github [Releases tab](https://github.com/garden-io/garden/releases) and click the **Draft a new release** button. -4. Fill in the **Tag version** and **Release title** fields with the new release version (same as you used for the tag). -5. Upload the downloaded artifacts. -6. Write release notes (not necessary for RCs). The notes should give an overview of the release and mention all relevant features. They should also **acknowledge all external contributors** and contain the changelog for that release. (To generate a changelog for just that tag, run `git-chglog `.) -7. Click the **Publish release** button. -8. Make a pull request for the branch that was pushed by the script. -9. If you're making an RC, you're done! Otherwise, you need to update Homebrew package: `gulp update-brew`. +2. If you're making a pre-release you're done, and you can now start testing the binaries that were just published to our Github [Releases tab](https://github.com/garden-io/garden/releases). Otherwise go to **step 3**. +3. Go to our Github [Releases tab](https://github.com/garden-io/garden/releases) and click the **Edit** button for the draft just created from CI. Note that for drafts, a new one is always created instead of replacing a previous one. +4. Write release notes. The notes should give an overview of the release and mention all relevant features. They should also **acknowledge all external contributors** and contain the changelog for that release. (To generate a changelog for just that tag, run `git-chglog `.) +5. Click the **Publish release** button. +6. Make a pull request for the branch that was pushed by the script. +7. Update the Homebrew package: `gulp update-brew`. ## Changelog diff --git a/bin/release.ts b/bin/release.ts index 5b7aaf4a3c..1ba69ceede 100755 --- a/bin/release.ts +++ b/bin/release.ts @@ -5,9 +5,10 @@ import * as semver from "semver" import * as inquirer from "inquirer" import chalk from "chalk" import parseArgs = require("minimist") -import replace = require("replace-in-file") import deline = require("deline") import { join, resolve } from "path" +import { ReplaceResults } from "replace-in-file" +const replace = require("replace-in-file") type ReleaseType = "minor" | "patch" | "preminor" | "prepatch" | "prerelease" const RELEASE_TYPES = ["minor", "patch", "preminor", "prepatch", "prerelease"] @@ -22,23 +23,24 @@ const gardenServiceRoot = join(gardenRoot, "garden-service") * 5. Update the changelog. * 6. Add and commit CHANGELOG.md, garden-service/package.json and garden-service/package-lock.json * 7. Tag the commit. - * 8. Push the tag. This triggers CircleCI process that creates the release artifacts. + * 8. Push the tag. This triggers a CircleCI job that creates the release artifacts and publishes them to Github. * 9. If we're making a minor release, update links to examples and re-push the tag. * 10. Pushes the release branch to Github. * - * Usage: ./bin/release.ts [--force] + * Usage: ./bin/release.ts [--force] [--dry-run] */ async function release() { // Parse arguments const argv = parseArgs(process.argv.slice(2)) const releaseType = argv._[0] - const force = argv.force + const force = !!argv.force + const dryRun = !!argv["dry-run"] // Check if branch is clean try { await execa("git", ["diff", "--exit-code"], { cwd: gardenRoot }) } catch (_) { - // throw new Error("Current branch has unstaged changes, aborting.") + throw new Error("Current branch has unstaged changes, aborting.") } if (!RELEASE_TYPES.includes(releaseType)) { @@ -127,8 +129,10 @@ async function release() { ], { cwd: gardenRoot }) // Tag the commit and push the tag - console.log("Pushing tag...") - await createTag(version, force) + if (!dryRun) { + console.log("Pushing tag...") + await createTag(version, force) + } // Reset local tag state (after stripping release tags) await execa("git", ["fetch", "origin", "--tags"], { cwd: gardenRoot }) @@ -149,11 +153,19 @@ async function release() { await execa("git", ["commit", "--amend", "--no-edit"], { cwd: gardenRoot }) // Tag the commit and force push the tag after updating the links (this triggers another CI build) - await createTag(version, true) + if (!dryRun) { + await createTag(version, true) + } } - console.log("Pushing release branch...") - await execa("git", ["push", "origin", branchName, "--no-verify"], { cwd: gardenRoot }) + if (!dryRun) { + console.log("Pushing release branch...") + const pushArgs = ["push", "origin", branchName, "--no-verify"] + if (force) { + pushArgs.push("-f") + } + await execa("git", pushArgs, { cwd: gardenRoot }) + } console.log(deline` \nVersion ${chalk.bold.cyan(version)} has been ${chalk.bold("tagged")}, ${chalk.bold("committed")}, @@ -191,8 +203,8 @@ async function updateExampleLinks(version: string) { from: /github\.com\/garden-io\/garden\/tree\/[^\/]*\/examples/g, to: `github.com/garden-io/garden/tree/${version}/examples`, } - const changes = await replace(options) - console.log("Modified files:", changes.join(", ")) + const results = await replace(options) as ReplaceResults[] + console.log("Modified files:", results.filter(r => r.hasChanged).map(r => r.file).join(", ")) } async function rollBack() { diff --git a/garden-service/bin/build-pkg.sh b/garden-service/bin/build-pkg.sh index f7c81fc515..9652dbcea2 100755 --- a/garden-service/bin/build-pkg.sh +++ b/garden-service/bin/build-pkg.sh @@ -1,11 +1,26 @@ #!/bin/bash -e +# Usage ./build-pkg.sh [version] +# +# Use the optional version argument to override the version that is included in the +# zip file names. Used for setting the version on unstable releases. +# Defaults to the version in garden-service/package.json. +# Note that this is only for the version string used in the file name, not the version of the +# code that is built. + garden_service_root=$(cd `dirname $0` && cd .. && pwd) cd ${garden_service_root} commit_hash=$(git rev-parse --short HEAD) -version="v$(cat package.json | jq -r .version)" + +# Use version argument if provided, otherwise read version from package.json +if [ -n "$1" ]; then + version=$1 +else + version="v$(cat package.json | jq -r .version)" +fi + echo "Packaging version ${version}-${commit_hash}" diff --git a/package-lock.json b/package-lock.json index 4ba6a0ce35..66ac15e1fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12647,9 +12647,9 @@ } }, "replace-in-file": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/replace-in-file/-/replace-in-file-3.4.4.tgz", - "integrity": "sha512-ehq0dFsxSpfPiPLBU5kli38Ud8bZL0CQKG8WQVbvhmyilXaMJ8y4LtDZs/K3MD8C0+rHbsfW8c9r2bUEy0B/6Q==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/replace-in-file/-/replace-in-file-4.1.1.tgz", + "integrity": "sha512-0Va403DpFFRpm6oIsEf2U9fH9mVuDgRmSbXwrzpC3tmGduah9FhJJmu424rlogJo+0t7ho9f1HOpR+0qcXtzWQ==", "dev": true, "requires": { "chalk": "^2.4.2", @@ -12673,9 +12673,9 @@ } }, "camelcase": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.2.0.tgz", - "integrity": "sha512-IXFsBS2pC+X0j0N/GE7Dm7j3bsEBp+oTpb7F50dwEVX7rf3IgwO9XatnegTsDtniKCUtEJH4fSU6Asw7uoVLfQ==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, "chalk": { @@ -12689,6 +12689,17 @@ "supports-color": "^5.3.0" } }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, "find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", @@ -12705,9 +12716,9 @@ "dev": true }, "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -12779,9 +12790,9 @@ } }, "p-try": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", - "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, "require-main-filename": { @@ -12802,9 +12813,9 @@ } }, "strip-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.1.0.tgz", - "integrity": "sha512-TjxrkPONqO2Z8QDCpeE2j6n0M6EwxzyDgzEeGp+FbdvaJAt//ClYi6W5my+3ROlC/hZX2KACUwDfK49Ka5eDvg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { "ansi-regex": "^4.1.0" @@ -12819,6 +12830,17 @@ "has-flag": "^3.0.0" } }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", @@ -12826,12 +12848,12 @@ "dev": true }, "yargs": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz", - "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.4.tgz", + "integrity": "sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==", "dev": true, "requires": { - "cliui": "^4.0.0", + "cliui": "^5.0.0", "find-up": "^3.0.0", "get-caller-file": "^2.0.1", "os-locale": "^3.1.0", @@ -12841,13 +12863,13 @@ "string-width": "^3.0.0", "which-module": "^2.0.0", "y18n": "^4.0.0", - "yargs-parser": "^13.0.0" + "yargs-parser": "^13.1.0" } }, "yargs-parser": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.0.0.tgz", - "integrity": "sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==", + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", + "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", "dev": true, "requires": { "camelcase": "^5.0.0", diff --git a/package.json b/package.json index eb47259ba4..9f3aa9fcd7 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "lerna": "^3.15.0", "lodash": "^4.17.11", "markdown-link-check": "^3.7.2", - "replace-in-file": "^3.4.4", + "replace-in-file": "^4.1.1", "semver": "^5.6.0", "shx": "^0.3.2", "snyk": "^1.136.1",