From 757a2a5e8de27c6a38a9c8f929018aae2fc7b19b Mon Sep 17 00:00:00 2001 From: Stefano Verna Date: Fri, 14 Jul 2017 12:41:22 +0200 Subject: [PATCH] WIP --- lib/datoMetaTags.js | 35 +++++++++ lib/index.js | 187 ++++++++------------------------------------ package.json | 5 +- yarn.lock | 126 +++++++++++++---------------- 4 files changed, 127 insertions(+), 226 deletions(-) create mode 100644 lib/datoMetaTags.js diff --git a/lib/datoMetaTags.js b/lib/datoMetaTags.js new file mode 100644 index 0000000..cde33db --- /dev/null +++ b/lib/datoMetaTags.js @@ -0,0 +1,35 @@ +const htmlTag = require('html-tag') +const { modifyNodes } = require('reshape-plugin-util') + +module.exports = function datoMetaTags () { + return function datoMetaTagsPlugin (tree, ctx) { + if (ctx) { + Object.assign(ctx.runtime, { htmlTag }) + } + + return modifyNodes( + tree, + (node) => node.name === 'dato-meta-tags', + (node) => { + if (!(node.attrs && node.attrs.record)) { + throw new ctx.PluginError({ + message: 'dato-meta-tags tag has no "record" attribute', + plugin: 'spike-datocms', + location: node.location + }) + } + + return { + type: 'code', + content: `(function() { + const record = locals.${node.attrs.record[0].content}; + if (!record || !record.seoMetaTags) { return null; } + return record.seoMetaTags.map(tag => ( + __runtime.htmlTag(tag.tagName, tag.attributes || {}, tag.content) + )).join(''); + })()` + } + } + ) + } +} diff --git a/lib/index.js b/lib/index.js index 96b0434..870f73f 100644 --- a/lib/index.js +++ b/lib/index.js @@ -7,13 +7,13 @@ const fs = require('fs') const reshape = require('reshape') const loader = require('reshape-loader') const {SiteClient} = require('datocms-client') +const DatoLoader = require('datocms-client/lib/local/Loader') const bindAllClass = require('es6bindall') -const mkdirp = require('mkdirp') module.exports = class SpikeDatoCMS { constructor (opts) { Object.assign(this, this.validate(opts)) - this.client = new SiteClient(opts.token) + this.client = new DatoLoader(new SiteClient(opts.token)) bindAllClass(this, ['apply', 'run']) } @@ -21,11 +21,6 @@ module.exports = class SpikeDatoCMS { this.util = new SpikeUtil(compiler.options) this.util.runAll(compiler, this.run) - // set cache to full path for use in emit + run functions - if (this.cache) { - this.cache = path.join(compiler.options.context, this.cache) - } - // TODO: this pulls the incorrect loader context compiler.plugin('compilation', (compilation) => { compilation.plugin('normal-module-loader', (loaderContext) => { @@ -34,61 +29,21 @@ module.exports = class SpikeDatoCMS { }) compiler.plugin('emit', (compilation, done) => { - if (this.json) { - writeJson(compilation, this.json, this.addDataTo.dato) - } - - // if cache is set and cache file doesn't exist; write it - if (this.cache && this.rewrite) { - writeCache(this.cache, this.addDataTo.dato) + if (this.singlePages) { + W.map(this.singlePages(this.client.itemsRepo), (page) => { + return writeTemplate.call(this, compiler, compilation, page) + }).done(() => done(), done) } - this.models.filter((m) => m.json).map((m) => { - return writeJson(compilation, m.json, this.addDataTo.dato[m.type]) - }) - - W.map(this.models.filter((m) => m.template), (contentType) => { - return writeTemplate.call(this, compiler, compilation, contentType) - }).done(() => done(), done) + done() }) } run (compilation, done) { - return W.all([ - fetchData.call(this), - W.reduce(this.models, (memo, model) => { - // format options - const options = {} - if (model.ids) { options['filter[ids]'] = model.ids } - if (model.query) { options['filter[query]'] = model.query } - if (model.offset) { options['page[offset]'] = model.offset } - if (model.limit) { options['page[limit]'] = model.limit } - const transformFn = model.transform ? model.transform : (x) => x - - // fetch items and itemTypes - return W.all([ - W(this.client.items.all(options)), - W(this.client.itemTypes.all()) - ]) - // make sure linked entries are resolved - .then(resolveLinks) - // filter to the model type, if necessary - .then(([records, itemTypes]) => { - if (!model.type) return records - const t = itemTypes.find((it) => it.apiKey === model.type) - return records.filter((r) => r.itemType === t.id) - }) - // transform if necessary - .then((res) => W.map(res, (entry) => transformFn(entry))) - // add resolved item to the response - .tap((res) => { memo[model.type || 'all'] = res }) - .yield(memo) - }, {}) - ]).done(([site, models]) => { - // add to the locals - Object.assign(this.addDataTo, { dato: Object.assign(models, { _meta: site }) }) + return this.client.load().then(() => { + Object.assign(this.addDataTo, { dato: this.client.itemsRepo }) done() - }, done) + }) } /** @@ -99,24 +54,7 @@ module.exports = class SpikeDatoCMS { const schema = Joi.object().keys({ token: Joi.string().required(), addDataTo: Joi.object().required(), - json: Joi.string(), - cache: Joi.string(), - models: Joi.array().items( - Joi.object().keys({ - type: Joi.string().default(Joi.ref('name')), - name: Joi.string(), // this is an alias for type - ids: Joi.array().single(), - query: Joi.string(), - offset: Joi.number(), - limit: Joi.number(), - transform: Joi.func(), - json: Joi.string(), - template: Joi.object().keys({ - path: Joi.string(), - output: Joi.func() - }) - }) - ) + singlePages: Joi.func() }) const res = Joi.validate(opts, schema, { @@ -125,92 +63,33 @@ module.exports = class SpikeDatoCMS { object: { child: '!![spike-datocms constructor] option {{reason}}' } } }) + if (res.error) { throw new Error(res.error) } return res.value } } -function fetchData () { - // if cache is set - if (this.cache) { - // if cache is set and cache file doesn't exist; write it - if (!fs.existsSync(this.cache)) { - this.rewrite = true - } else { // if cache is set and cache file exists; parse it - return W.resolve(JSON.parse(fs.readFileSync(this.cache, 'utf8'))) - } - } else { // if cache isn't set, hit the API - return this.client.site.find() - } -} - -// TODO: use proxies so there can be no infinite loopz -function resolveLinks ([records, itemTypes]) { - // find all model ids - const recordIds = records.map((r) => r.id) - // scan all model values - records.map((r) => { - for (let k in r) { - if (k === 'id') continue - // check to see if it is a model id, which means it's a link - // if so, replace the id with the actual item - if (Array.isArray(r[k])) { - r[k] = r[k].map(resolveLink.bind(null, recordIds, records)) - } else { - r[k] = resolveLink(recordIds, records, r[k]) - } - } - }) - return [records, itemTypes] -} - -function resolveLink (ids, records, record) { - if (ids.indexOf(record) > -1) { - return records.find((r2) => r2.id === record) - } else { - return record - } -} - -function writeJson (compilation, filename, data) { - const src = JSON.stringify(data, null, 2) - compilation.assets[filename] = { - source: () => src, - size: () => src.length - } -} - -function writeCache (filename, data) { - const src = JSON.stringify(data, null, 2) - return mkdirp(path.dirname(filename), function () { - fs.writeFileSync(filename, src) - }) -} - // TODO: get rid of this, put the templates through webpack -function writeTemplate (compiler, compilation, model) { - const data = this.addDataTo.dato[model.type] - const filePath = path.join(compiler.options.context, model.template.path) - - return node.call(fs.readFile.bind(fs), filePath, 'utf8').then((template) => { - return W.map(data, (item) => { - const newLocals = Object.assign({}, this.addDataTo, { item }) - - const options = loader.parseOptions.call(this.loaderContext, this.util.getSpikeOptions().reshape, {}) - - // for any plugins that pull locals from the options - options.locals = newLocals - options.filename = filePath - - return reshape(options) - .process(template) - .then((res) => { - const html = res.output(newLocals) - compilation.assets[model.template.output(item)] = { - source: () => html, - size: () => html.length - } - }) - }) +function writeTemplate (compiler, compilation, page) { + const filePath = path.join(compiler.options.context, page.template) + + return node.call(fs.readFile.bind(fs), filePath, 'utf8') + .then((template) => { + const newLocals = Object.assign({}, this.addDataTo, page.locals) + const options = loader.parseOptions.call(this.loaderContext, this.util.getSpikeOptions().reshape, {}) + + // for any plugins that pull locals from the options + options.locals = newLocals + options.filename = filePath + + return reshape(options) + .process(template) + .then((res) => { + const html = res.output(newLocals) + compilation.assets[page.output] = { + source: () => html, + size: () => html.length + } + }) }) } diff --git a/package.json b/package.json index e9e6870..04926e0 100644 --- a/package.json +++ b/package.json @@ -34,11 +34,12 @@ "dependencies": { "datocms-client": "^0.3.25", "es6bindall": "^0.0.9", + "html-tag": "^1.0.0", "joi": "^10.6.0", - "mkdirp": "^0.5.1", "reshape": "^0.4.1", "reshape-loader": "^1.0.0", - "spike-util": "^1.2.0", + "reshape-plugin-util": "^0.2.1", + "spike-util": "^1.3.0", "when": "^3.7.8" } } diff --git a/yarn.lock b/yarn.lock index 71c92e2..3357aac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -462,7 +462,7 @@ babel-code-frame@^6.16.0, babel-code-frame@^6.22.0: esutils "^2.0.2" js-tokens "^3.0.0" -babel-core@^6.17.0, babel-core@^6.24.1: +babel-core@^6.17.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.24.1.tgz#8c428564dce1e1f41fb337ec34f4c3b022b5ad83" dependencies: @@ -486,7 +486,7 @@ babel-core@^6.17.0, babel-core@^6.24.1: slash "^1.0.0" source-map "^0.5.0" -babel-core@^6.25.0: +babel-core@^6.24.1, babel-core@^6.25.0: version "6.25.0" resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.25.0.tgz#7dd42b0463c742e9d5296deb3ec67a9322dad729" dependencies: @@ -510,26 +510,26 @@ babel-core@^6.25.0: slash "^1.0.0" source-map "^0.5.0" -babel-generator@^6.1.0, babel-generator@^6.18.0, babel-generator@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.24.1.tgz#e715f486c58ded25649d888944d52aa07c5d9497" +babel-generator@^6.1.0, babel-generator@^6.25.0: + version "6.25.0" + resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.25.0.tgz#33a1af70d5f2890aeb465a4a7793c1df6a9ea9fc" dependencies: babel-messages "^6.23.0" babel-runtime "^6.22.0" - babel-types "^6.24.1" + babel-types "^6.25.0" detect-indent "^4.0.0" jsesc "^1.3.0" lodash "^4.2.0" source-map "^0.5.0" trim-right "^1.0.1" -babel-generator@^6.25.0: - version "6.25.0" - resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.25.0.tgz#33a1af70d5f2890aeb465a4a7793c1df6a9ea9fc" +babel-generator@^6.18.0, babel-generator@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.24.1.tgz#e715f486c58ded25649d888944d52aa07c5d9497" dependencies: babel-messages "^6.23.0" babel-runtime "^6.22.0" - babel-types "^6.25.0" + babel-types "^6.24.1" detect-indent "^4.0.0" jsesc "^1.3.0" lodash "^4.2.0" @@ -826,14 +826,14 @@ babel-types@^6.25.0: lodash "^4.2.0" to-fast-properties "^1.0.1" -babylon@^6.1.0, babylon@^6.11.0, babylon@^6.15.0: - version "6.16.1" - resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.16.1.tgz#30c5a22f481978a9e7f8cdfdf496b11d94b404d3" - -babylon@^6.17.2, babylon@^6.17.4: +babylon@^6.1.0, babylon@^6.15.0, babylon@^6.17.2, babylon@^6.17.4: version "6.17.4" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.17.4.tgz#3e8b7402b88d22c3423e137a1577883b15ff869a" +babylon@^6.11.0: + version "6.16.1" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.16.1.tgz#30c5a22f481978a9e7f8cdfdf496b11d94b404d3" + backo2@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" @@ -846,10 +846,6 @@ balanced-match@^0.4.1, balanced-match@^0.4.2: version "0.4.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - base64-arraybuffer@0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" @@ -946,20 +942,13 @@ boxen@^1.0.0: term-size "^0.1.0" widest-line "^1.0.0" -brace-expansion@^1.0.0: +brace-expansion@^1.0.0, brace-expansion@^1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.7.tgz#3effc3c50e000531fb720eaff80f0ae8ef23cf59" dependencies: balanced-match "^0.4.1" concat-map "0.0.1" -brace-expansion@^1.1.7: - version "1.1.8" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - braces@^1.8.2: version "1.8.5" resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" @@ -1283,7 +1272,7 @@ chalk@^2.0.1: escape-string-regexp "^1.0.5" supports-color "^4.0.0" -chokidar@1.7.0: +chokidar@1.7.0, chokidar@^1.4.3: version "1.7.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" dependencies: @@ -1298,7 +1287,7 @@ chokidar@1.7.0: optionalDependencies: fsevents "^1.0.0" -chokidar@^1.4.2, chokidar@^1.4.3, chokidar@^1.6.1: +chokidar@^1.4.2, chokidar@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.6.1.tgz#2f4447ab5e96e50fb3d789fd90d4c72e0e4c70c2" dependencies: @@ -1829,11 +1818,11 @@ debug-log@^1.0.0, debug-log@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debug-log/-/debug-log-1.0.1.tgz#2307632d4c04382b8df8a32f70b895046d52745f" -debug@2, debug@^2.3.3: - version "2.6.8" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" +debug@2, debug@^2.1.1, debug@^2.2.0, debug@^2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.3.tgz#0f7eb8c30965ec08c72accfa0130c8b79984141d" dependencies: - ms "2.0.0" + ms "0.7.2" debug@2.2.0, debug@~2.2.0: version "2.2.0" @@ -1847,18 +1836,12 @@ debug@2.3.3: dependencies: ms "0.7.2" -debug@2.6.4: +debug@2.6.4, debug@^2.3.3: version "2.6.4" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.4.tgz#7586a9b3c39741c0282ae33445c4e8ac74734fe0" dependencies: ms "0.7.3" -debug@^2.1.1, debug@^2.2.0, debug@^2.6.3: - version "2.6.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.3.tgz#0f7eb8c30965ec08c72accfa0130c8b79984141d" - dependencies: - ms "0.7.2" - decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -2529,14 +2512,10 @@ extend-shallow@^2.0.1: dependencies: is-extendable "^0.1.0" -extend@3: +extend@3, extend@^3.0.0, extend@~3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" -extend@^3.0.0, extend@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.0.tgz#5a474353b9f3353ddd8176dfd37b91c83a46f1d4" - extglob@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" @@ -2861,25 +2840,25 @@ glob-parent@^2.0.0: dependencies: is-glob "^2.0.0" -glob@^7.0.0, glob@^7.0.3, glob@^7.0.4, glob@^7.0.5, glob@^7.0.6: - version "7.1.1" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" +glob@^7.0.0, glob@^7.0.3, glob@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "^3.0.2" + minimatch "^3.0.4" once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" +glob@^7.0.4, glob@^7.0.5, glob@^7.0.6: + version "7.1.1" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "^3.0.4" + minimatch "^3.0.2" once "^1.3.0" path-is-absolute "^1.0.0" @@ -3068,6 +3047,13 @@ html-comment-regex@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e" +html-tag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/html-tag/-/html-tag-1.0.0.tgz#95e5612aec82bea928ed44595f854145e9f7e0b5" + dependencies: + isobject "^3.0.0" + void-elements "^2.0.1" + htmlparser2@~3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.3.0.tgz#cc70d05a59f6542e43f0e685c982e14c924a9efe" @@ -3605,16 +3591,7 @@ jodid25519@^1.0.0: dependencies: jsbn "~0.1.0" -joi@^10.0.0, joi@^10.2.0: - version "10.4.1" - resolved "https://registry.yarnpkg.com/joi/-/joi-10.4.1.tgz#a2fca1f0d603d1b843f2c1e086b52461f6be1f36" - dependencies: - hoek "4.x.x" - isemail "2.x.x" - items "2.x.x" - topo "2.x.x" - -joi@^10.6.0: +joi@^10.0.0, joi@^10.2.0, joi@^10.6.0: version "10.6.0" resolved "https://registry.yarnpkg.com/joi/-/joi-10.6.0.tgz#52587f02d52b8b75cdb0c74f0b164a191a0e1fc2" dependencies: @@ -4062,7 +4039,7 @@ micromatch@2.3.11, micromatch@^2.1.5, micromatch@^2.3.11, micromatch@^2.3.8: parse-glob "^3.0.4" regex-cache "^0.4.2" -micromatch@^3.0.3: +micromatch@^3.0.3, micromatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.0.4.tgz#1543f1d04813447ac852001c5f5a933401786d1d" dependencies: @@ -4117,13 +4094,13 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" -minimatch@3.0.3, minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3: +minimatch@3.0.3, minimatch@^3.0.0, minimatch@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774" dependencies: brace-expansion "^1.0.0" -minimatch@^3.0.4: +minimatch@^3.0.3, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" dependencies: @@ -4174,10 +4151,6 @@ ms@1.0.0, ms@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-1.0.0.tgz#59adcd22edc543f7b5381862d31387b1f4bc9473" -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - multimatch@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-2.1.0.tgz#9c7906a22fb4c02919e2f5f75161b4cdbd4b2a2b" @@ -6108,6 +6081,15 @@ spike-util@^1.2.0: micromatch "^2.3.8" when "^3.7.7" +spike-util@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/spike-util/-/spike-util-1.3.0.tgz#140a141ddb4beb0f02c63192074e24c4eb91d680" + dependencies: + filewrap "1.0.0" + glob "^7.1.2" + micromatch "^3.0.4" + when "^3.7.7" + split-string@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/split-string/-/split-string-2.1.1.tgz#af4b06d821560426446c3cd931cda618940d37d0" @@ -6797,6 +6779,10 @@ vm-browserify@0.0.4: dependencies: indexof "0.0.1" +void-elements@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" + watchpack@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.3.1.tgz#7d8693907b28ce6013e7f3610aa2a1acf07dad87"