From 80d4ceb03f59b478f56f97fe1b130591f2c73fd9 Mon Sep 17 00:00:00 2001 From: Dirk-Jan Rutten Date: Sun, 29 Jan 2017 11:21:22 +0100 Subject: [PATCH] Publish scripts for deployment to npm via travis. (#14) --- .gitignore | 9 +-- .travis.yml | 31 ++++++++--- CONTRIBUTING.md | 18 ++++++ package.json | 10 ++-- resources/prepublish.sh | 32 +++++++++++ resources/travis_after_all.py | 101 ++++++++++++++++++++++++++++++++++ 6 files changed, 181 insertions(+), 20 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 resources/prepublish.sh create mode 100644 resources/travis_after_all.py diff --git a/.gitignore b/.gitignore index 04a24a9..113076c 100755 --- a/.gitignore +++ b/.gitignore @@ -2,13 +2,6 @@ dist/ -# Generated when running 'npm pack' -graphql-iso-date-1.0.1.tgz - -# Generated when running 'yarn pack' -graphql-iso-date-v1.0.1.tgz - - ### Node ### # Logs logs @@ -50,4 +43,4 @@ jspm_packages ### WebStorm ### -.idea/ \ No newline at end of file +.idea/ diff --git a/.travis.yml b/.travis.yml index ff34643..9367374 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,28 @@ language: node_js - cache: yarn - node_js: - - "7" - - "6" - - "5" - - "4" +- '7' +- '6' +- '5' +- '4' + +script: + - if [[ "$TRAVIS_JOB_NUMBER" == *.1 ]]; then npm test; else npm run testonly; fi + +after_failure: + - (cd resources; python travis_after_all.py) + +after_success: + - (cd resources; python travis_after_all.py) + - export $(cat resources/.to_export_back) -script: yarn run travis +deploy: + provider: npm + skip_cleanup: true + email: "dirkjanrutten@gmail.com" + api_key: + secure: VezZd77ytFyIGsRB124Z9fDDppm/MiHARrHRv8ZvEH1sdj39Y/Gm54381FuUSbaoemnWlOnRtgspaKjdyJsBwKUkhTJaud6YZqP2NIG4xSBJHRxnqOJ7PbLSFusXKq429wJE8DZTb/d/M9lPG/DxrinTDtp+7r/YAnkljGMOjDLhxwR544iHh9lzWUN/s9RHOs6klaE0oKzh36AFdcukSWOe5E9v8CJBJlhid9UJzmnjb9NTv0tjX9VP9dON6kkDO5XvNzwKmdu62Y4ch4WtyDXMSqxy01a46XduW5qVOVdlza3PxTq2Y2Zu0ok3rRzKKlxA35qGvWFbtskbZcdRkWOa5aIxaAFK+F1bKhuKRHidufzZLtiP9fVAC0sF+AOpMjQBhBBScvZU5S35yQUU+elesg9w+3+IELp7v0C/bOQYkUWEhgdmyCTxxwtITvvLSOAe/L0zumBz5v6j7spquDJ7x/ZkqmMxBvIzxmgnJXUdgaSJCTBoB3MhsIJiHXfAwdJ6ficOgAkaKSoDSqIf7N9cZWWl2iBJ9fow+6mzmKvfNROc+yFu6e1843XrwTSPFAD2Xx3Js2BGwhZz6h+3NoRaASgb3+bKAj04DN33OMxnCMHsXqQyKBx8dUSWBxQMafD0KeroVZ6fC4rQqMndRj8vFWuJO1+43Ja7Gn8V9CQ= + on: + tags: true + branch: master + condition: "$BUILD_LEADER$BUILD_AGGREGATE_STATUS = YESothers_succeeded" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..e6dbc93 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,18 @@ +## Release on NPM + +*Only core contributors may release to NPM.* + +To release a new version on NPM, first ensure all tests pass with `npm test`, +then use `npm version patch|minor|major` in order to increment the version in +package.json and tag and commit a release. Then `git push && git push --tags` +this change so Travis CI can deploy to NPM. *Do not run `npm publish` directly.* +Once published, add [release notes](https://github.com/excitement-engineer/graphql-iso-date/tags). +Use [semver](http://semver.org/) to determine which version part to increment. + +Example for a patch release: + +```sh +npm test +npm version patch +git push --follow-tags +``` diff --git a/package.json b/package.json index f0ab061..009bf83 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "graphql-iso-date", - "version": "3.0.0", + "version": "2.1.0", "description": "A set of RFC 3339 compliant date/time GraphQL scalar types.", "main": "dist/index.js", "homepage": "https://github.com/excitement-engineer/graphql-iso-date", @@ -14,12 +14,12 @@ "scripts": { "examples": "npm run build && babel-node examples/index.js", "build": "babel src --ignore __tests__,*.test.js --out-dir dist/", - "test": "jest", + "test": "npm run examples && npm run testonly && npm run check && npm run lint", + "testonly": "jest", "watch": "jest --watch", "check": "flow check", - "prepublish": "npm run build", - "lint": "standard", - "travis": "npm run test && npm run check && npm run lint && npm run examples" + "prepublish": ". ./resources/prepublish.sh", + "lint": "standard" }, "keywords": [ "GraphQL", diff --git a/resources/prepublish.sh b/resources/prepublish.sh new file mode 100644 index 0000000..2062b05 --- /dev/null +++ b/resources/prepublish.sh @@ -0,0 +1,32 @@ +# This script is adapted from https://github.com/graphql/graphql-js. + +# Because of a long-running npm issue (https://github.com/npm/npm/issues/3059) +# prepublish runs after `npm install` and `npm pack`. +# In order to only run prepublish before `npm publish`, we have to check argv. +if node -e "process.exit(($npm_config_argv).original[0].indexOf('pu') === 0)"; then + exit 0; +fi + +# Publishing to NPM is currently supported by Travis CI, which ensures that all +# tests pass first and the deployed module contains the correct file structure. +# In order to prevent inadvertently circumventing this, we ensure that a CI +# environment exists before continuing. +if [ "$CI" != true ]; then + echo "\n\n\n \033[101;30m Only Travis CI can publish to NPM. \033[0m" 1>&2; + echo " Ensure git is left is a good state by backing out any commits and deleting any tags." 1>&2; + echo " Then read CONTRIBUTING.md to learn how to publish to NPM.\n\n\n" 1>&2; + exit 1; +fi; + +# When Travis CI publishes to NPM, we need to make +# sure that the files are built into dist. +npm run build; + +# Ensure a vanilla package.json before deploying so other tools do not interpret +# The built output as requiring any further transformation. +node -e "var package = require('./package.json'); \ + delete package.scripts; \ + delete package.options; \ + delete package.devDependencies; \ + delete package.standard; \ + require('fs').writeFileSync('package.json', JSON.stringify(package));" diff --git a/resources/travis_after_all.py b/resources/travis_after_all.py new file mode 100644 index 0000000..f813c8f --- /dev/null +++ b/resources/travis_after_all.py @@ -0,0 +1,101 @@ +""" +https://github.com/dmakhno/travis_after_all/blob/master/travis_after_all.py +""" + +import os +import json +import time +import logging + +try: + import urllib.request as urllib2 +except ImportError: + import urllib2 + +log = logging.getLogger("travis.leader") +log.addHandler(logging.StreamHandler()) +log.setLevel(logging.INFO) + +TRAVIS_JOB_NUMBER = 'TRAVIS_JOB_NUMBER' +TRAVIS_BUILD_ID = 'TRAVIS_BUILD_ID' +POLLING_INTERVAL = 'LEADER_POLLING_INTERVAL' + +build_id = os.getenv(TRAVIS_BUILD_ID) +polling_interval = int(os.getenv(POLLING_INTERVAL, '5')) + +#assume, first job is the leader +is_leader = lambda job_number: job_number.endswith('.1') + +if not os.getenv(TRAVIS_JOB_NUMBER): + # seems even for builds with only one job, this won't get here + log.fatal("Don't use defining leader for build without matrix") + exit(1) +elif is_leader(os.getenv(TRAVIS_JOB_NUMBER)): + log.info("This is a leader") +else: + #since python is subprocess, env variables are exported back via file + with open(".to_export_back", "w") as export_var: + export_var.write("BUILD_MINION=YES") + log.info("This is a minion") + exit(0) + + +class MatrixElement(object): + def __init__(self, json_raw): + self.is_finished = json_raw['finished_at'] is not None + self.is_succeeded = json_raw['result'] == 0 + self.number = json_raw['number'] + self.is_leader = is_leader(self.number) + + +def matrix_snapshot(): + """ + :return: Matrix List + """ + response = urllib2.build_opener().open("https://api.travis-ci.org/builds/{0}".format(build_id)).read() + raw_json = json.loads(response) + matrix_without_leader = [MatrixElement(element) for element in raw_json["matrix"]] + return matrix_without_leader + + +def wait_others_to_finish(): + def others_finished(): + """ + Dumps others to finish + Leader cannot finish, it is working now + :return: tuple(True or False, List of not finished jobs) + """ + snapshot = matrix_snapshot() + finished = [el.is_finished for el in snapshot if not el.is_leader] + return reduce(lambda a, b: a and b, finished), [el.number for el in snapshot if + not el.is_leader and not el.is_finished] + + while True: + finished, waiting_list = others_finished() + if finished: break + log.info("Leader waits for minions {0}...".format(waiting_list)) # just in case do not get "silence timeout" + time.sleep(polling_interval) + + +try: + wait_others_to_finish() + + final_snapshot = matrix_snapshot() + log.info("Final Results: {0}".format([(e.number, e.is_succeeded) for e in final_snapshot])) + + BUILD_AGGREGATE_STATUS = 'BUILD_AGGREGATE_STATUS' + others_snapshot = [el for el in final_snapshot if not el.is_leader] + if reduce(lambda a, b: a and b, [e.is_succeeded for e in others_snapshot]): + os.environ[BUILD_AGGREGATE_STATUS] = "others_succeeded" + elif reduce(lambda a, b: a and b, [not e.is_succeeded for e in others_snapshot]): + log.error("Others Failed") + os.environ[BUILD_AGGREGATE_STATUS] = "others_failed" + else: + log.warn("Others Unknown") + os.environ[BUILD_AGGREGATE_STATUS] = "unknown" + #since python is subprocess, env variables are exported back via file + with open(".to_export_back", "w") as export_var: + export_var.write("BUILD_LEADER=YES {0}={1}".format(BUILD_AGGREGATE_STATUS, os.environ[BUILD_AGGREGATE_STATUS])) + +except Exception as e: + log.fatal(e)