From 75e4bf2af9d73a5fbf02746d395e3932632bc943 Mon Sep 17 00:00:00 2001 From: Ivan Goncharov Date: Wed, 10 Jun 2020 21:15:03 +0300 Subject: [PATCH] CI: add initial support of Deno build Motivation #2566 --- .babelrc-deno.json | 13 +++ .babelrc.json | 2 +- .github/workflows/ci.yml | 49 ++++++++- .gitignore | 3 +- package.json | 8 +- resources/add-extension-to-import-paths.js | 12 +-- resources/build-deno.js | 39 +++++++ resources/build-npm.js | 74 +++++++++++++ resources/build.js | 120 --------------------- resources/gitpublish.sh | 38 ++++--- resources/utils.js | 47 ++++++++ 11 files changed, 252 insertions(+), 153 deletions(-) create mode 100644 .babelrc-deno.json create mode 100644 resources/build-deno.js create mode 100644 resources/build-npm.js delete mode 100644 resources/build.js diff --git a/.babelrc-deno.json b/.babelrc-deno.json new file mode 100644 index 00000000000..561f7347be3 --- /dev/null +++ b/.babelrc-deno.json @@ -0,0 +1,13 @@ +{ + "plugins": [ + "@babel/plugin-transform-flow-strip-types", + ["./resources/add-extension-to-import-paths", { "extension": "mjs" }], + "./resources/inline-invariant" + ], + "overrides": [ + { + "include": "src/error/GraphQLError.js", + "plugins": [["@babel/plugin-transform-classes", { "loose": false }]] + } + ] +} diff --git a/.babelrc.json b/.babelrc.json index a6bde80c559..601a5026c0c 100644 --- a/.babelrc.json +++ b/.babelrc.json @@ -23,7 +23,7 @@ "mjs": { "presets": [["@babel/preset-env", { "modules": false }]], "plugins": [ - "./resources/add-extension-to-import-paths", + ["./resources/add-extension-to-import-paths", { "extension": "mjs" }], "./resources/inline-invariant" ] } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3006de8480b..8ecfc9f23c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,8 +49,11 @@ jobs: - name: Spellcheck run: npm run check:spelling - - name: Build package - run: npm run build + - name: Build NPM package + run: npm run build:npm + + - name: Build Deno package + run: npm run build:deno fuzz: name: Run fuzzing tests @@ -196,10 +199,46 @@ jobs: - name: Install Dependencies run: npm ci - - name: Build package - run: npm run build + - name: Build NPM package + run: npm run build:npm + + - name: Deploy to `npm` branch + run: npm run gitpublish:npm + env: + GH_TOKEN: ${{ secrets.GH_NPM_BRANCH_PUBLISH_TOKEN }} + + deploy-to-deno-branch: + name: Deploy to `deno` branch + runs-on: ubuntu-latest + if: | + github.event_name == 'push' && + github.repository == 'graphql/graphql-js' && + github.ref == 'refs/heads/master' + needs: [test, fuzz, lint] + steps: + - name: Checkout repo + uses: actions/checkout@v2 + + - name: Setup Node.js + uses: actions/setup-node@v1 + with: + node-version: ${{ env.NODE_VERSION_USED_FOR_DEVELOPMENT }} + + - name: Cache Node.js modules + uses: actions/cache@v2 + with: + path: ~/.npm + key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.OS }}-node- + + - name: Install Dependencies + run: npm ci + + - name: Build Deno package + run: npm run build:deno - name: Deploy to `npm` branch - run: npm run gitpublish + run: npm run gitpublish:deno env: GH_TOKEN: ${{ secrets.GH_NPM_BRANCH_PUBLISH_TOKEN }} diff --git a/.gitignore b/.gitignore index 973e93731ca..8ab4ab680a1 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ .eslintcache node_modules coverage -dist +npmDist +denoDist benchmarkDist npm diff --git a/package.json b/package.json index 6cc4bf24f8c..f1d3bde99b0 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ }, "scripts": { "test": "npm run prettier:check && npm run lint && npm run check && npm run testonly && npm run check:ts && npm run check:spelling", - "test:ci": "npm run prettier:check && npm run lint -- --no-cache && npm run check && npm run testonly:cover && npm run check:ts && npm run check:spelling && npm run build", + "test:ci": "npm run prettier:check && npm run lint -- --no-cache && npm run check && npm run testonly:cover && npm run check:ts && npm run check:spelling && npm run build:npm && npm run build:deno", "fuzzonly": "mocha --full-trace src/**/__tests__/**/*-fuzz.js", "testonly": "mocha --full-trace src/**/__tests__/**/*-test.js", "testonly:cover": "nyc npm run testonly", @@ -40,11 +40,13 @@ "check:ts": "dtslint src", "check:cover": "node resources/check-cover.js && nyc report --nycrc-path .nycflowrc.yml", "check:spelling": "cspell \"./{src/**/,resources/**/}*.{js,ts,md,graphql}\"", - "build": "node resources/build.js", + "build:npm": "node resources/build-npm.js", + "build:deno": "node resources/build-deno.js", "changelog": "node resources/gen-changelog.js", "preversion": ". ./resources/checkgit.sh && npm ci", "version": "node resources/gen-version.js && npm test && git add src/version.js", - "gitpublish": ". ./resources/gitpublish.sh" + "gitpublish:npm": "node ./resources/gitpublish.js npm npmDist", + "gitpublish:deno": "node ./resources/gitpublish.js deon denoDist" }, "dependencies": {}, "devDependencies": { diff --git a/resources/add-extension-to-import-paths.js b/resources/add-extension-to-import-paths.js index cfa6ae62d67..a99e7d9f9c6 100644 --- a/resources/add-extension-to-import-paths.js +++ b/resources/add-extension-to-import-paths.js @@ -2,6 +2,8 @@ 'use strict'; +const path = require('path'); + /** * Adds extension to all paths imported inside MJS files * @@ -16,7 +18,7 @@ * export { foo } from './bar.mjs'; * */ -module.exports = function addExtensionToImportPaths(context) { +module.exports = function addExtensionToImportPaths(context, { extension }) { const { types } = context; return { @@ -26,7 +28,7 @@ module.exports = function addExtensionToImportPaths(context) { }, }; - function replaceImportPath(path) { + function replaceImportPath(path, state) { // bail if the declaration doesn't have a source, e.g. "export { foo };" if (!path.node.source) { return; @@ -34,10 +36,8 @@ module.exports = function addExtensionToImportPaths(context) { const source = path.node.source.value; if (source.startsWith('./') || source.startsWith('../')) { - if (!source.endsWith('.mjs')) { - const newSourceNode = types.stringLiteral(source + '.mjs'); - path.get('source').replaceWith(newSourceNode); - } + const newSourceNode = types.stringLiteral(source + '.' + extension); + path.get('source').replaceWith(newSourceNode); } } }; diff --git a/resources/build-deno.js b/resources/build-deno.js new file mode 100644 index 00000000000..10cb183e705 --- /dev/null +++ b/resources/build-deno.js @@ -0,0 +1,39 @@ +// @noflow + +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +const babel = require('@babel/core'); + +const flowRemoveTypes = require('flow-remove-types'); + +const { rmdirRecursive, readdirRecursive, showDirStats } = require('./utils'); + +if (require.main === module) { + rmdirRecursive('./denoDist'); + fs.mkdirSync('./denoDist'); + + const srcFiles = readdirRecursive('./src', { ignoreDir: /^__.*__$/ }); + for (const filepath of srcFiles) { + const srcPath = path.join('./src', filepath); + const destPath = path.join('./denoDist', filepath); + + fs.mkdirSync(path.dirname(destPath), { recursive: true }); + if (filepath.endsWith('.js')) { + const source = fs.readFileSync(srcPath, 'utf8'); + + const options = { babelrc: false, configFile: './.babelrc-deno.json' }; + const output = babel.transformFileSync(srcPath, options).code + '\n'; + fs.writeFileSync(destPath, output); + } else if (filepath.endsWith('.d.ts')) { + fs.copyFileSync(srcPath, destPath); + } + } + + fs.copyFileSync('./LICENSE', './denoDist/LICENSE'); + fs.copyFileSync('./README.md', './denoDist/README.md'); + + showDirStats('./denoDist'); +} diff --git a/resources/build-npm.js b/resources/build-npm.js new file mode 100644 index 00000000000..6d26d0dd478 --- /dev/null +++ b/resources/build-npm.js @@ -0,0 +1,74 @@ +// @noflow + +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const assert = require('assert'); + +const babel = require('@babel/core'); + +const { rmdirRecursive, readdirRecursive, showDirStats } = require('./utils'); + +if (require.main === module) { + rmdirRecursive('./npmDist'); + fs.mkdirSync('./npmDist'); + + const srcFiles = readdirRecursive('./src', { ignoreDir: /^__.*__$/ }); + for (const filepath of srcFiles) { + const srcPath = path.join('./src', filepath); + const destPath = path.join('./npmDist', filepath); + + fs.mkdirSync(path.dirname(destPath), { recursive: true }); + if (filepath.endsWith('.js')) { + fs.copyFileSync(srcPath, destPath + '.flow'); + + const cjs = babelBuild(srcPath, { envName: 'cjs' }); + fs.writeFileSync(destPath, cjs); + + const mjs = babelBuild(srcPath, { envName: 'mjs' }); + fs.writeFileSync(destPath.replace(/\.js$/, '.mjs'), mjs); + } else if (filepath.endsWith('.d.ts')) { + fs.copyFileSync(srcPath, destPath); + } + } + + fs.copyFileSync('./LICENSE', './npmDist/LICENSE'); + fs.copyFileSync('./README.md', './npmDist/README.md'); + + // Should be done as the last step so only valid packages can be published + const packageJSON = buildPackageJSON(); + fs.writeFileSync('./npmDist/package.json', JSON.stringify(packageJSON, null, 2)); + + showDirStats('./npmDist'); +} + +function babelBuild(srcPath, options) { + return babel.transformFileSync(srcPath, options).code + '\n'; +} + +function buildPackageJSON() { + const packageJSON = require('../package.json'); + delete packageJSON.private; + delete packageJSON.scripts; + delete packageJSON.devDependencies; + + packageJSON.engines = packageJSON.engines_on_npm; + delete packageJSON.engines_on_npm; + + const versionJS = require('../npmDist/version.js'); + assert( + versionJS.version === packageJSON.version, + 'Version in package.json and version.js should match', + ); + + if (versionJS.preReleaseTag != null) { + const [tag] = versionJS.preReleaseTag.split('.'); + assert(['alpha', 'beta', 'rc'].includes(tag), `"${tag}" tag is supported.`); + + assert(!packageJSON.publishConfig, 'Can not override "publishConfig".'); + packageJSON.publishConfig = { tag: tag || 'latest' }; + } + + return packageJSON; +} diff --git a/resources/build.js b/resources/build.js deleted file mode 100644 index 16ccc0890cb..00000000000 --- a/resources/build.js +++ /dev/null @@ -1,120 +0,0 @@ -// @noflow - -'use strict'; - -const fs = require('fs'); -const path = require('path'); -const assert = require('assert'); - -const babel = require('@babel/core'); - -const { rmdirRecursive, readdirRecursive } = require('./utils'); - -if (require.main === module) { - rmdirRecursive('./dist'); - fs.mkdirSync('./dist'); - - const srcFiles = readdirRecursive('./src', { ignoreDir: /^__.*__$/ }); - for (const filepath of srcFiles) { - const srcPath = path.join('./src', filepath); - const destPath = path.join('./dist', filepath); - - fs.mkdirSync(path.dirname(destPath), { recursive: true }); - if (filepath.endsWith('.js')) { - fs.copyFileSync(srcPath, destPath + '.flow'); - - const cjs = babelBuild(srcPath, { envName: 'cjs' }); - fs.writeFileSync(destPath, cjs); - - const mjs = babelBuild(srcPath, { envName: 'mjs' }); - fs.writeFileSync(destPath.replace(/\.js$/, '.mjs'), mjs); - } else if (filepath.endsWith('.d.ts')) { - fs.copyFileSync(srcPath, destPath); - } - } - - fs.copyFileSync('./LICENSE', './dist/LICENSE'); - fs.copyFileSync('./README.md', './dist/README.md'); - - // Should be done as the last step so only valid packages can be published - const packageJSON = buildPackageJSON(); - fs.writeFileSync('./dist/package.json', JSON.stringify(packageJSON, null, 2)); - - showStats(); -} - -function babelBuild(srcPath, options) { - return babel.transformFileSync(srcPath, options).code + '\n'; -} - -function buildPackageJSON() { - const packageJSON = require('../package.json'); - delete packageJSON.private; - delete packageJSON.scripts; - delete packageJSON.devDependencies; - - packageJSON.engines = packageJSON.engines_on_npm; - delete packageJSON.engines_on_npm; - - const versionJS = require('../dist/version.js'); - assert( - versionJS.version === packageJSON.version, - 'Version in package.json and version.js should match', - ); - - if (versionJS.preReleaseTag != null) { - const [tag] = versionJS.preReleaseTag.split('.'); - assert(['alpha', 'beta', 'rc'].includes(tag), `"${tag}" tag is supported.`); - - assert(!packageJSON.publishConfig, 'Can not override "publishConfig".'); - packageJSON.publishConfig = { tag: tag || 'latest' }; - } - - return packageJSON; -} - -function showStats() { - const fileTypes = {}; - let totalSize = 0; - - for (const filepath of readdirRecursive('./dist')) { - const name = filepath.split(path.sep).pop(); - const [base, ...splitExt] = name.split('.'); - const ext = splitExt.join('.'); - - const filetype = ext ? '*.' + ext : base; - fileTypes[filetype] = fileTypes[filetype] || { filepaths: [], size: 0 }; - - const { size } = fs.lstatSync(path.join('./dist', filepath)); - totalSize += size; - fileTypes[filetype].size += size; - fileTypes[filetype].filepaths.push(filepath); - } - - let stats = []; - for (const [filetype, typeStats] of Object.entries(fileTypes)) { - const numFiles = typeStats.filepaths.length; - - if (numFiles > 1) { - stats.push([filetype + ' x' + numFiles, typeStats.size]); - } else { - stats.push([typeStats.filepaths[0], typeStats.size]); - } - } - stats.sort((a, b) => b[1] - a[1]); - stats = stats.map(([type, size]) => [type, (size / 1024).toFixed(2) + ' KB']); - - const typeMaxLength = Math.max(...stats.map((x) => x[0].length)); - const sizeMaxLength = Math.max(...stats.map((x) => x[1].length)); - for (const [type, size] of stats) { - console.log( - type.padStart(typeMaxLength) + ' | ' + size.padStart(sizeMaxLength), - ); - } - - console.log('-'.repeat(typeMaxLength + 3 + sizeMaxLength)); - const totalMB = (totalSize / 1024 / 1024).toFixed(2) + ' MB'; - console.log( - 'Total'.padStart(typeMaxLength) + ' | ' + totalMB.padStart(sizeMaxLength), - ); -} diff --git a/resources/gitpublish.sh b/resources/gitpublish.sh index 332036783c4..a71dbe38f35 100644 --- a/resources/gitpublish.sh +++ b/resources/gitpublish.sh @@ -1,43 +1,47 @@ -#!/bin/sh +#!/bin/bash -# Exit immediately if any subcommand terminated -trap "exit 1" ERR - -# This script maintains a git branch which mirrors master but in a form that -# what will eventually be deployed to npm, allowing npm dependencies to use: +# This script maintains a git branch which mirrors master but in a form that +# what will eventually be deployed to npm, allowing npm dependencies to use: # -# "graphql": "git://github.com/graphql/graphql-js.git#npm" +# "graphql": "git://github.com/graphql/graphql-js.git#npm" # +# Additionaly it use use to push Deno build to `deno` branch. + +BRANCH=$1 +DIST_DIR=$2 + +# Exit immediately if any subcommand terminated +set -e -if [ ! -d "./dist" ]; then - echo 'Directory `dist` does not exist, please run `npm run build`!' +if [ ! -d $DIST_DIR ]; then + echo "Directory `${DIST_DIR}` does not exist!" exit 1; fi; -# Create empty npm directory -rm -rf npm -git clone -b npm "https://${GH_TOKEN}@github.com/graphql/graphql-js.git" npm +# Create empty directory +rm -rf $BRANCH +git clone -b $BRANCH "https://${GH_TOKEN}@github.com/graphql/graphql-js.git" $BRANCH # Remove existing files first -rm -rf npm/**/* -rm -rf npm/* +rm -rf $BRANCH/**/* +rm -rf $BRANCH/* # Copy over necessary files -cp -r dist/* npm/ +cp -r $DIST_DIR/* $BRANCH/ # Reference current commit HEADREV=`git rev-parse HEAD` echo $HEADREV # Deploy -cd npm +cd $BRANCH git config user.name "GitHub Action Script" git config user.email "please@open.issue" git add -A . if git diff --staged --quiet; then echo "Nothing to publish" else - git commit -a -m "Deploy $HEADREV to NPM branch" + git commit -a -m "Deploy $HEADREV to `$BRANCH` branch" git push > /dev/null 2>&1 echo "Pushed" fi diff --git a/resources/utils.js b/resources/utils.js index e72f9c84d18..0014133ba18 100644 --- a/resources/utils.js +++ b/resources/utils.js @@ -69,9 +69,56 @@ function readdirRecursive(dirPath, opts = {}) { return result; } +function showDirStats(dirPath) { + const fileTypes = {}; + let totalSize = 0; + + for (const filepath of readdirRecursive(dirPath)) { + const name = filepath.split(path.sep).pop(); + const [base, ...splitExt] = name.split('.'); + const ext = splitExt.join('.'); + + const filetype = ext ? '*.' + ext : base; + fileTypes[filetype] = fileTypes[filetype] || { filepaths: [], size: 0 }; + + const { size } = fs.lstatSync(path.join(dirPath, filepath)); + totalSize += size; + fileTypes[filetype].size += size; + fileTypes[filetype].filepaths.push(filepath); + } + + let stats = []; + for (const [filetype, typeStats] of Object.entries(fileTypes)) { + const numFiles = typeStats.filepaths.length; + + if (numFiles > 1) { + stats.push([filetype + ' x' + numFiles, typeStats.size]); + } else { + stats.push([typeStats.filepaths[0], typeStats.size]); + } + } + stats.sort((a, b) => b[1] - a[1]); + stats = stats.map(([type, size]) => [type, (size / 1024).toFixed(2) + ' KB']); + + const typeMaxLength = Math.max(...stats.map((x) => x[0].length)); + const sizeMaxLength = Math.max(...stats.map((x) => x[1].length)); + for (const [type, size] of stats) { + console.log( + type.padStart(typeMaxLength) + ' | ' + size.padStart(sizeMaxLength), + ); + } + + console.log('-'.repeat(typeMaxLength + 3 + sizeMaxLength)); + const totalMB = (totalSize / 1024 / 1024).toFixed(2) + ' MB'; + console.log( + 'Total'.padStart(typeMaxLength) + ' | ' + totalMB.padStart(sizeMaxLength), + ); +} + module.exports = { exec, execAsync, rmdirRecursive, readdirRecursive, + showDirStats, };