diff --git a/package-lock.json b/package-lock.json index e8973fc..e99e019 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2310,9 +2310,9 @@ "dev": true }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -3838,8 +3838,7 @@ "version": "2.1.1", "resolved": false, "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -3863,15 +3862,13 @@ "version": "1.0.0", "resolved": false, "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "resolved": false, "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3888,22 +3885,19 @@ "version": "1.1.0", "resolved": false, "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "resolved": false, "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "resolved": false, "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -4034,8 +4028,7 @@ "version": "2.0.3", "resolved": false, "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -4049,7 +4042,6 @@ "resolved": false, "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -4066,7 +4058,6 @@ "resolved": false, "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -4075,15 +4066,13 @@ "version": "0.0.8", "resolved": false, "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.2.4", "resolved": false, "integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==", "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -4104,7 +4093,6 @@ "resolved": false, "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -4193,8 +4181,7 @@ "version": "1.0.1", "resolved": false, "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -4208,7 +4195,6 @@ "resolved": false, "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -4304,8 +4290,7 @@ "version": "5.1.1", "resolved": false, "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -4347,7 +4332,6 @@ "resolved": false, "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -4369,7 +4353,6 @@ "resolved": false, "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -4418,15 +4401,13 @@ "version": "1.0.2", "resolved": false, "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.2", "resolved": false, "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=", - "dev": true, - "optional": true + "dev": true } } }, diff --git a/package.json b/package.json index 1aca617..2186ee8 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "babel-core": "^7.0.0-bridge.0", "babel-eslint": "^8.0.3", "babel-jest": "^23.4.2", + "chalk": "^2.4.2", "dotenv": "^6.2.0", "eslint": "^4.13.1", "eslint-import-resolver-node": "^0.3.2", diff --git a/scripts/purge-cache.js b/scripts/purge-cache.js new file mode 100644 index 0000000..aa9c202 --- /dev/null +++ b/scripts/purge-cache.js @@ -0,0 +1,60 @@ +const chalk = require('chalk'); + +const { getZone, purgeFiles } = require('./utils/cloudflare'); +const { die } = require('./utils/process'); +const { getFiles } = require('./utils/unpkg'); + +const packageName = process.argv[2]; +const version = process.argv[3]; + +if (packageName == null) { + die( + 'Missing the PACKAGE_NAME argument; use `node purge-cache.js PACKAGE_NAME VERSION`' + ); +} + +if (version == null) { + die( + 'Missing the VERSION argument; use `node purge-cache.js PACKAGE_NAME VERSION`' + ); +} + +function groupBy(array, n) { + const groups = []; + + while (array.length) { + groups.push(array.splice(0, n)); + } + + return groups; +} + +getFiles(packageName, version) + .then(files => { + console.log( + chalk.yellow( + `Found ${files.length} files for package ${packageName}@${version}` + ) + ); + + return getZone('unpkg.com').then(zone => { + let promise = Promise.resolve(); + + groupBy(files, 30).forEach(group => { + promise = promise.then(() => { + const urls = group.map( + file => `https://unpkg.com/${packageName}@${version}${file.path}` + ); + + return purgeFiles(zone.id, urls).then(data => { + group.forEach(file => + console.log(chalk.green(`Purged ${file.path}`)) + ); + }); + }); + }); + + return promise; + }); + }) + .catch(die); diff --git a/scripts/show-log.js b/scripts/show-log.js index 637474b..bea61fb 100644 --- a/scripts/show-log.js +++ b/scripts/show-log.js @@ -1,49 +1,14 @@ -require("isomorphic-fetch"); -const invariant = require("invariant"); +const { getZones, getLog } = require('./utils/cloudflare'); +const { die } = require('./utils/process'); -const CloudflareEmail = process.env.CLOUDFLARE_EMAIL; -const CloudflareKey = process.env.CLOUDFLARE_KEY; -const RayID = process.argv[2]; +const RayId = process.argv[2]; -invariant( - CloudflareEmail, - "Missing the $CLOUDFLARE_EMAIL environment variable" -); - -invariant(CloudflareKey, "Missing the $CLOUDFLARE_KEY environment variable"); - -invariant( - RayID, - "Missing the RAY_ID argument; use `heroku run node show-log.js RAY_ID`" -); - -function getZones(domain) { - return fetch(`https://api.cloudflare.com/client/v4/zones?name=${domain}`, { - method: "GET", - headers: { - "X-Auth-Email": CloudflareEmail, - "X-Auth-Key": CloudflareKey - } - }) - .then(res => res.json()) - .then(data => data.result); +if (RayId == null) { + die('Missing the RAY_ID argument; use `node show-log.js RAY_ID`'); } -function getLog(zoneId, rayId) { - return fetch( - `https://api.cloudflare.com/client/v4/zones/${zoneId}/logs/requests/${rayId}`, - { - method: "GET", - headers: { - "X-Auth-Email": CloudflareEmail, - "X-Auth-Key": CloudflareKey - } - } - ).then(res => (res.status === 404 ? "NOT FOUND" : res.json())); -} - -getZones("unpkg.com").then(zones => { - getLog(zones[0].id, RayID).then(entry => { - console.log(entry); +getZone('unpkg.com').then(zone => { + getLog(zone.id, RayId).then(entry => { + console.log(entry || 'NOT FOUND'); }); }); diff --git a/scripts/utils/cloudflare.js b/scripts/utils/cloudflare.js new file mode 100644 index 0000000..652087b --- /dev/null +++ b/scripts/utils/cloudflare.js @@ -0,0 +1,90 @@ +const fetch = require('isomorphic-fetch'); + +const CloudflareEmail = process.env.CLOUDFLARE_EMAIL; +const CloudflareKey = process.env.CLOUDFLARE_KEY; + +if (CloudflareEmail == null) { + console.error('Missing the $CLOUDFLARE_EMAIL environment variable'); + process.exit(1); +} + +if (CloudflareKey == null) { + console.error('Missing the $CLOUDFLARE_KEY environment variable'); + process.exit(1); +} + +function get(path) { + return fetch(`https://api.cloudflare.com/client/v4${path}`, { + method: 'GET', + headers: { + 'X-Auth-Email': CloudflareEmail, + 'X-Auth-Key': CloudflareKey + } + }); +} + +function getLog(zoneId, rayId) { + return get(`/zones/${zoneId}/logs/requests/${rayId}`).then( + res => (res.status === 404 ? null : res.json()) + ); +} + +function getZone(domain) { + return get(`/zones?name=${domain}`) + .then(res => res.json()) + .then(data => { + if (!data.success) throw data; + + const zones = data.result; + + if (zones.length > 1) { + console.error( + `Domain "${domain}" has more than one zone: ${zones.join(', ')}` + ); + } + + return zones[0]; + }); +} + +function post(path, data) { + const options = { + method: 'POST', + headers: { + 'X-Auth-Email': CloudflareEmail, + 'X-Auth-Key': CloudflareKey + } + }; + + if (data) { + options.headers['Content-Type'] = 'application/json'; + options.body = JSON.stringify(data); + } + + return fetch(`https://api.cloudflare.com/client/v4${path}`, options); +} + +function purgeFiles(zoneId, files) { + return post(`/zones/${zoneId}/purge_cache`, { files }) + .then(res => res.json()) + .then(data => { + if (data.success) return data; + throw data; + }); +} + +function purgeTags(zoneId, tags) { + return post(`/zones/${zoneId}/purge_cache`, { tags }) + .then(res => res.json()) + .then(data => { + if (data.success) return data; + throw data; + }); +} + +module.exports = { + getLog, + getZone, + purgeFiles, + purgeTags +}; diff --git a/scripts/utils/process.js b/scripts/utils/process.js new file mode 100644 index 0000000..93434b0 --- /dev/null +++ b/scripts/utils/process.js @@ -0,0 +1,12 @@ +const util = require('util'); +const chalk = require('chalk'); + +function die(why) { + const message = typeof why === 'string' ? why : util.inspect(why); + console.error(chalk.red(message)); + process.exit(1); +} + +module.exports = { + die +}; diff --git a/scripts/utils/unpkg.js b/scripts/utils/unpkg.js new file mode 100644 index 0000000..e108b7a --- /dev/null +++ b/scripts/utils/unpkg.js @@ -0,0 +1,22 @@ +const fetch = require('isomorphic-fetch'); + +function getMetadata(packageName, version) { + return fetch(`https://unpkg.com/${packageName}@${version}/?meta`, { + method: 'GET' + }).then(res => res.json()); +} + +function collectFiles(directory) { + return directory.files.reduce((memo, file) => { + return memo.concat(file.type === 'directory' ? collectFiles(file) : file); + }, []); +} + +function getFiles(packageName, version) { + return getMetadata(packageName, version).then(collectFiles); +} + +module.exports = { + getMetadata, + getFiles +};