diff --git a/.eslintrc.js b/.eslintrc.js index 45712871..22749063 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,7 +1,7 @@ module.exports = { root: true, parserOptions: { - ecmaVersion: 2017, + ecmaVersion: 2018, sourceType: 'module', }, plugins: ['ember', 'prettier'], @@ -17,6 +17,7 @@ module.exports = { '.eslintrc.js', '.prettierrc.js', '.template-lintrc.js', + 'colocated-broccoli-plugin.js', 'ember-cli-build.js', 'lib/**/*.js', 'testem.js', @@ -27,7 +28,7 @@ module.exports = { excludedFiles: ['addon/**', 'addon-test-support/**', 'app/**', 'tests/dummy/app/**'], parserOptions: { sourceType: 'script', - ecmaVersion: 2015, + ecmaVersion: 2018, }, env: { browser: false, @@ -44,7 +45,7 @@ module.exports = { files: ['node-tests/**/*.js'], parserOptions: { sourceType: 'script', - ecmaVersion: 2015, + ecmaVersion: 2018, }, env: { browser: false, diff --git a/lib/colocated-babel-plugin.js b/lib/colocated-babel-plugin.js new file mode 100644 index 00000000..5f6dbf26 --- /dev/null +++ b/lib/colocated-babel-plugin.js @@ -0,0 +1,61 @@ +// For ease of debuggin / tweaking: +// https://astexplorer.net/#/gist/bcca584efdab6c981a75618642c76a22/1e1d262eaeb47b7da66150e0781a02b96e597b25 +module.exports = function(babel) { + let t = babel.types; + + return { + name: 'ember-cli-htmlbars-colocation-template', + + visitor: { + VariableDeclarator(path, state) { + if (path.node.id.name === '__COLOCATED_TEMPLATE__') { + state.colocatedTemplateFound = true; + } + }, + + ExportDefaultDeclaration(path, state) { + if (!state.colocatedTemplateFound) { + return; + } + + let defaultExportDeclaration = path.node.declaration; + let setComponentTemplateMemberExpression = t.memberExpression( + t.identifier('Ember'), + t.identifier('_setComponentTemplate') + ); + let colocatedTemplateIdentifier = t.identifier('__COLOCATED_TEMPLATE__'); + + if (defaultExportDeclaration.type === 'ClassDeclaration') { + // when the default export is a ClassDeclaration with an `id`, + // wrapping it in a CallExpression would remove that class from the + // local scope which would cause issues for folks using the declared + // name _after_ the export + if (defaultExportDeclaration.id !== null) { + path.parent.body.push( + t.expressionStatement( + t.callExpression(setComponentTemplateMemberExpression, [ + colocatedTemplateIdentifier, + defaultExportDeclaration.id, + ]) + ) + ); + } else { + path.node.declaration = t.callExpression(setComponentTemplateMemberExpression, [ + colocatedTemplateIdentifier, + t.classExpression( + null, + defaultExportDeclaration.superClass, + defaultExportDeclaration.body + ), + ]); + } + } else { + path.node.declaration = t.callExpression(setComponentTemplateMemberExpression, [ + colocatedTemplateIdentifier, + defaultExportDeclaration, + ]); + } + }, + }, + }; +}; diff --git a/lib/colocated-broccoli-plugin.js b/lib/colocated-broccoli-plugin.js new file mode 100644 index 00000000..85d337a8 --- /dev/null +++ b/lib/colocated-broccoli-plugin.js @@ -0,0 +1,121 @@ +'use strict'; + +const fs = require('fs'); +const mkdirp = require('mkdirp'); +const copyFileSync = require('fs-copy-file-sync'); +const path = require('path'); +const walkSync = require('walk-sync'); +const Plugin = require('broccoli-plugin'); + +function detectRootName(files) { + let [first] = files; + let parts = first.split('/'); + + let root; + if (parts[0].startsWith('@')) { + root = parts.slice(0, 2).join('/'); + } else { + root = parts[0]; + } + + if (!files.every(f => f.startsWith(root))) { + root = null; + } + + return root; +} + +module.exports = class ColocatedTemplateProcessor extends Plugin { + constructor(tree, options) { + super([tree], options); + } + + build() { + let files = walkSync(this.inputPaths[0], { directories: false }); + + let root = detectRootName(files); + + let filesToCopy = []; + files.forEach(filePath => { + if (root === null) { + // do nothing, we cannot detect the proper root path for the app/addon + // being processed + filesToCopy.push(filePath); + return; + } + + let filePathParts = path.parse(filePath); + let inputPath = path.join(this.inputPaths[0], filePath); + + // TODO: why are these different? + // Apps: my-app/components/foo.hbs, my-app/templates/components/foo.hbs + // Addons: components/foo.js, templates/components/foo.hbs + // + // will be fixed by https://github.com/ember-cli/ember-cli/pull/8834 + + let isInsideComponentsFolder = filePath.startsWith(`${root}/components/`); + + // copy forward non-hbs files + // TODO: don't copy .js files that will ultimately be overridden + if (!isInsideComponentsFolder || filePathParts.ext !== '.hbs') { + filesToCopy.push(filePath); + return; + } + + // TODO: deal with alternate extensions (e.g. ts) + let possibleJSPath = path.join(filePathParts.dir, filePathParts.name + '.js'); + let hasJSFile = fs.existsSync(path.join(this.inputPaths[0], possibleJSPath)); + + if (filePathParts.name === 'template') { + // TODO: maybe warn? + return; + } + + let templateContents = fs.readFileSync(inputPath, { encoding: 'utf8' }); + let jsContents = null; + + // TODO: deal with hygiene? + let prefix = `import { hbs } from 'ember-cli-htmlbars';\nconst __COLOCATED_TEMPLATE__ = hbs\`${templateContents}\`;\n`; + + if (hasJSFile) { + // add the template, call setComponentTemplate + + jsContents = fs.readFileSync(path.join(this.inputPaths[0], possibleJSPath), { + encoding: 'utf8', + }); + + if (!jsContents.includes('export default')) { + let message = `\`${filePath}\` does not contain a \`default export\`. Did you forget to export the component class?`; + jsContents = `${jsContents}\nthrow new Error(${JSON.stringify(message)});`; + prefix = ''; + } + } else { + // create JS file, use null component pattern + + jsContents = `import templateOnly from '@ember/component/template-only';\n\nexport default templateOnly();\n`; + } + + jsContents = prefix + jsContents; + + let outputPath = path.join(this.outputPath, possibleJSPath); + + // TODO: don't speculatively mkdirSync (likely do in a try/catch with ENOENT) + mkdirp.sync(path.dirname(outputPath)); + fs.writeFileSync(outputPath, jsContents, { encoding: 'utf8' }); + }); + + filesToCopy.forEach(filePath => { + let inputPath = path.join(this.inputPaths[0], filePath); + let outputPath = path.join(this.outputPath, filePath); + + // avoid copying file over top of a previously written one + if (fs.existsSync(outputPath)) { + return; + } + + // TODO: don't speculatively mkdirSync (likely do in a try/catch with ENOENT) + mkdirp.sync(path.dirname(outputPath)); + copyFileSync(inputPath, outputPath); + }); + } +}; diff --git a/lib/ember-addon-main.js b/lib/ember-addon-main.js index c7b90ca0..1c1a412f 100644 --- a/lib/ember-addon-main.js +++ b/lib/ember-addon-main.js @@ -2,32 +2,79 @@ const path = require('path'); const utils = require('./utils'); -const debugGenerator = require('heimdalljs-logger'); -const logger = debugGenerator('ember-cli-htmlbars'); +const logger = require('heimdalljs-logger')('ember-cli-htmlbars'); +const hasEdition = require('@ember/edition-utils').has; + +let registryInvocationCounter = 0; module.exports = { name: require('../package').name, parentRegistry: null, + _shouldColocateTemplates() { + if (this._cachedShouldColocateTemplates) { + return this._cachedShouldColocateTemplates; + } + + const semver = require('semver'); + + let babel = this.parent.addons.find(a => a.name === 'ember-cli-babel'); + let hasBabel = babel !== undefined; + let babelVersion = hasBabel && babel.pkg.version; + + // using this.project.emberCLIVersion() allows us to avoid issues when `npm + // link` is used; if this addon were linked and we did something like + // `require('ember-cli/package').version` we would get our own ember-cli + // version **not** the one in use currently + let emberCLIVersion = this.project.emberCLIVersion(); + + let hasValidBabelVersion = hasBabel && semver.gte(babelVersion, '7.11.0'); + let hasValidEmberCLIVersion = semver.gte(emberCLIVersion, '3.12.0-beta.2'); + let hasOctane = hasEdition('octane'); + + this._cachedShouldColocateTemplates = + hasOctane && hasValidBabelVersion && hasValidEmberCLIVersion; + + return this._cachedShouldColocateTemplates; + }, + setupPreprocessorRegistry(type, registry) { // ensure that broccoli-ember-hbs-template-compiler is not processing hbs files registry.remove('template', 'broccoli-ember-hbs-template-compiler'); + // when this.parent === this.project, `this.parent.name` is a function 😭 + let parentName = typeof this.parent.name === 'function' ? this.parent.name() : this.parent.name; + registry.add('template', { name: 'ember-cli-htmlbars', ext: 'hbs', _addon: this, toTree(tree) { + let debugTree = require('broccoli-debug').buildDebugCallback( + `ember-cli-htmlbars:${parentName}:tree-${registryInvocationCounter++}` + ); + + let shouldColocateTemplates = this._addon._shouldColocateTemplates(); let htmlbarsOptions = this._addon.htmlbarsOptions(); - let TemplateCompiler = require('./template-compiler-plugin'); - return new TemplateCompiler(tree, htmlbarsOptions); + + let inputTree = debugTree(tree, '01-input'); + + if (shouldColocateTemplates) { + const ColocatedTemplateProcessor = require('./colocated-broccoli-plugin'); + + inputTree = debugTree(new ColocatedTemplateProcessor(inputTree), '02-colocated-output'); + } + + const TemplateCompiler = require('./template-compiler-plugin'); + return debugTree(new TemplateCompiler(inputTree, htmlbarsOptions), '03-output'); }, - precompile(string) { + precompile(string, options) { let htmlbarsOptions = this._addon.htmlbarsOptions(); let templateCompiler = htmlbarsOptions.templateCompiler; - return utils.template(templateCompiler, string); + + return utils.template(templateCompiler, string, options); }, }); @@ -46,7 +93,7 @@ module.exports = { // add the babel-plugin-htmlbars-inline-precompile to the list of plugins // used by `ember-cli-babel` addon - if (!this._isBabelPluginRegistered(babelPlugins)) { + if (!this._isInlinePrecompileBabelPluginRegistered(babelPlugins)) { let pluginWrappers = this.astPlugins(); let templateCompilerPath = this.templateCompilerPath(); let pluginInfo = utils.setupPlugins(pluginWrappers); @@ -110,6 +157,15 @@ module.exports = { babelPlugins.push(htmlBarsPlugin); } } + + if (this._shouldColocateTemplates()) { + const { hasPlugin, addPlugin } = require('ember-cli-babel-plugin-helpers'); + let colocatedPluginPath = require.resolve('./colocated-babel-plugin'); + + if (!hasPlugin(babelPlugins, colocatedPluginPath)) { + addPlugin(babelPlugins, colocatedPluginPath); + } + } }, /** @@ -119,7 +175,7 @@ module.exports = { * For non parallel api, check the 'modules' to see if it contains the babel plugin * @param {*} plugins */ - _isBabelPluginRegistered(plugins) { + _isInlinePrecompileBabelPluginRegistered(plugins) { return plugins.some(plugin => { if (Array.isArray(plugin)) { return plugin[0] === require.resolve('babel-plugin-htmlbars-inline-precompile'); diff --git a/lib/utils.js b/lib/utils.js index 12e9b790..3391042a 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -2,7 +2,6 @@ const fs = require('fs'); const path = require('path'); -const HTMLBarsInlinePrecompilePlugin = require.resolve('babel-plugin-htmlbars-inline-precompile'); const hashForDep = require('hash-for-dep'); const debugGenerator = require('heimdalljs-logger'); const logger = debugGenerator('ember-cli-htmlbars'); @@ -136,12 +135,13 @@ function setup(pluginInfo, options) { precompile.baseDir = () => path.resolve(__dirname, '..'); precompile.cacheKey = () => cacheKey; - let precompileInlineHTMLBarsPlugin = [ - HTMLBarsInlinePrecompilePlugin, + let plugin = [ + require.resolve('babel-plugin-htmlbars-inline-precompile'), { precompile, modules: options.modules }, + 'ember-cli-htmlbars:inline-precompile', ]; - return precompileInlineHTMLBarsPlugin; + return plugin; } function makeCacheKey(templateCompilerPath, pluginInfo, extra) { diff --git a/node-tests/colocated-plugin-test.js b/node-tests/colocated-plugin-test.js new file mode 100644 index 00000000..bd4a1f7d --- /dev/null +++ b/node-tests/colocated-plugin-test.js @@ -0,0 +1,310 @@ +'use strict'; + +const assert = require('assert'); +const ColocatedTemplateCompiler = require('../lib/colocated-broccoli-plugin'); +const { createTempDir, createBuilder } = require('broccoli-test-helper'); +const { stripIndent } = require('common-tags'); + +describe('ColocatedTemplateCompiler', function() { + this.timeout(10000); + + let input, output; + + beforeEach(async function() { + input = await createTempDir(); + }); + + afterEach(async function() { + await input.dispose(); + + if (output) { + await output.dispose(); + } + }); + + it('works for template only component', async function() { + input.write({ + 'app-name-here': { + components: { + 'foo.hbs': `{{yield}}`, + }, + templates: { + 'application.hbs': `{{outlet}}`, + }, + }, + }); + + let tree = new ColocatedTemplateCompiler(input.path(), { + precompile(template) { + return JSON.stringify({ template }); + }, + }); + + output = createBuilder(tree); + await output.build(); + + assert.deepStrictEqual(output.read(), { + 'app-name-here': { + components: { + 'foo.js': + stripIndent` + import { hbs } from 'ember-cli-htmlbars'; + const __COLOCATED_TEMPLATE__ = hbs\`{{yield}}\`; + import templateOnly from '@ember/component/template-only'; + + export default templateOnly();` + '\n', + }, + templates: { + 'application.hbs': '{{outlet}}', + }, + }, + }); + }); + + it('works for component with template and class', async function() { + input.write({ + 'app-name-here': { + components: { + 'foo.hbs': `{{yield}}`, + 'foo.js': stripIndent` + import Component from '@glimmer/component'; + + export default class FooComponent extends Component {} + `, + }, + templates: { + 'application.hbs': `{{outlet}}`, + }, + }, + }); + + let tree = new ColocatedTemplateCompiler(input.path(), { + precompile(template) { + return JSON.stringify({ template }); + }, + }); + + output = createBuilder(tree); + await output.build(); + + assert.deepStrictEqual(output.read(), { + 'app-name-here': { + components: { + 'foo.js': stripIndent` + import { hbs } from 'ember-cli-htmlbars'; + const __COLOCATED_TEMPLATE__ = hbs\`{{yield}}\`; + import Component from '@glimmer/component'; + + export default class FooComponent extends Component {} + `, + }, + templates: { + 'application.hbs': '{{outlet}}', + }, + }, + }); + }); + + it('works for scoped addon using template only component', async function() { + input.write({ + '@scope-name': { + 'addon-name-here': { + components: { + 'foo.hbs': `{{yield}}`, + }, + templates: { + 'application.hbs': `{{outlet}}`, + }, + }, + }, + }); + + let tree = new ColocatedTemplateCompiler(input.path(), { + precompile(template) { + return JSON.stringify({ template }); + }, + }); + + output = createBuilder(tree); + await output.build(); + + assert.deepStrictEqual(output.read(), { + '@scope-name': { + 'addon-name-here': { + components: { + 'foo.js': + stripIndent` + import { hbs } from 'ember-cli-htmlbars'; + const __COLOCATED_TEMPLATE__ = hbs\`{{yield}}\`; + import templateOnly from '@ember/component/template-only'; + + export default templateOnly();` + '\n', + }, + templates: { + 'application.hbs': '{{outlet}}', + }, + }, + }, + }); + }); + + it('works for scoped addon using component with template and class', async function() { + input.write({ + '@scope-name': { + 'addon-name-here': { + components: { + 'foo.hbs': `{{yield}}`, + 'foo.js': stripIndent` + import Component from '@glimmer/component'; + + export default class FooComponent extends Component {} + `, + }, + templates: { + 'application.hbs': `{{outlet}}`, + }, + }, + }, + }); + + let tree = new ColocatedTemplateCompiler(input.path(), { + precompile(template) { + return JSON.stringify({ template }); + }, + }); + + output = createBuilder(tree); + await output.build(); + + assert.deepStrictEqual(output.read(), { + '@scope-name': { + 'addon-name-here': { + components: { + 'foo.js': stripIndent` + import { hbs } from 'ember-cli-htmlbars'; + const __COLOCATED_TEMPLATE__ = hbs\`{{yield}}\`; + import Component from '@glimmer/component'; + + export default class FooComponent extends Component {} + `, + }, + templates: { + 'application.hbs': '{{outlet}}', + }, + }, + }, + }); + }); + + it('does nothing for "classic" location components', async function() { + input.write({ + 'app-name-here': { + components: { + 'foo.js': stripIndent` + import Component from '@glimmer/component'; + + export default class FooComponent extends Component {} + `, + }, + templates: { + 'application.hbs': `{{outlet}}`, + components: { + 'foo.hbs': `{{yield}}`, + }, + }, + }, + }); + + let tree = new ColocatedTemplateCompiler(input.path(), { + precompile(template) { + return JSON.stringify({ template }); + }, + }); + + output = createBuilder(tree); + await output.build(); + + assert.deepStrictEqual(output.read(), input.read()); + }); + + it('it works if input is manually using setComponentTemplate - no colocated template exists', async function() { + input.write({ + 'app-name-here': { + components: { + 'foo.js': stripIndent` + import Component from '@glimmer/component'; + import { setComponentTemplate } from '@ember/component'; + import hbs from 'ember-cli-htmlbars-inline-precompile'; + + export default class FooComponent extends Component {} + setComponentTemplate(FooComponent, hbs\`sometemplate\`); + `, + }, + templates: { + 'application.hbs': `{{outlet}}`, + }, + }, + }); + + let tree = new ColocatedTemplateCompiler(input.path(), { + precompile(template) { + return JSON.stringify({ template }); + }, + }); + + output = createBuilder(tree); + await output.build(); + + assert.deepStrictEqual(output.read(), { + 'app-name-here': { + components: { + 'foo.js': stripIndent` + import Component from '@glimmer/component'; + import { setComponentTemplate } from '@ember/component'; + import hbs from 'ember-cli-htmlbars-inline-precompile'; + + export default class FooComponent extends Component {} + setComponentTemplate(FooComponent, hbs\`sometemplate\`); + `, + }, + templates: { + 'application.hbs': '{{outlet}}', + }, + }, + }); + }); + + it('emits an error when a default export is not present in a component JS file', async function() { + input.write({ + 'app-name-here': { + components: { + 'foo.hbs': `{{yield}}`, + 'foo.js': stripIndent` + export function whatever() {} + `, + }, + }, + }); + + let tree = new ColocatedTemplateCompiler(input.path(), { + precompile(template) { + return JSON.stringify({ template }); + }, + }); + + output = createBuilder(tree); + await output.build(); + + assert.deepStrictEqual(output.read(), { + 'app-name-here': { + components: { + 'foo.js': stripIndent` + export function whatever() {}\nthrow new Error("\`app-name-here/components/foo.hbs\` does not contain a \`default export\`. Did you forget to export the component class?"); + `, + }, + }, + }); + }); + + it('does not break class decorator usage'); +}); diff --git a/package.json b/package.json index 0c49c41c..d80858b1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ember-cli-htmlbars", - "version": "3.1.0", + "version": "3.2.0-alpha.0", "description": "A library for adding htmlbars to ember CLI", "keywords": [ "ember-addon", @@ -31,12 +31,21 @@ "test:node:debug": "mocha debug node-tests/*.js" }, "dependencies": { + "@ember/edition-utils": "^1.1.1", "babel-plugin-htmlbars-inline-precompile": "^2.1.0", + "broccoli-debug": "^0.6.5", "broccoli-persistent-filter": "^2.3.1", + "broccoli-plugin": "^2.0.0", + "common-tags": "^1.8.0", + "ember-cli-babel-plugin-helpers": "^1.1.0", + "fs-copy-file-sync": "^1.1.1", "hash-for-dep": "^1.5.1", "heimdalljs-logger": "^0.1.10", "json-stable-stringify": "^1.0.1", - "strip-bom": "^4.0.0" + "mkdirp": "^0.5.1", + "semver": "^6.3.0", + "strip-bom": "^3.0.0", + "walk-sync": "^1.1.3" }, "devDependencies": { "@ember/optional-features": "^1.0.0", diff --git a/tests/dummy/app/components/bar.hbs b/tests/dummy/app/components/bar.hbs new file mode 100644 index 00000000..9490021b --- /dev/null +++ b/tests/dummy/app/components/bar.hbs @@ -0,0 +1 @@ +Hello from bar! diff --git a/tests/dummy/app/components/bar.js b/tests/dummy/app/components/bar.js new file mode 100644 index 00000000..006191ce --- /dev/null +++ b/tests/dummy/app/components/bar.js @@ -0,0 +1,5 @@ +import Component from '@ember/component'; + +export default Component.extend({ + attributeBindings: ['lolol'], +}); diff --git a/tests/dummy/app/components/foo.hbs b/tests/dummy/app/components/foo.hbs new file mode 100644 index 00000000..627079e6 --- /dev/null +++ b/tests/dummy/app/components/foo.hbs @@ -0,0 +1 @@ +Stuff!! diff --git a/tests/dummy/app/components/pods-comp/template.hbs b/tests/dummy/app/components/pods-comp/template.hbs new file mode 100644 index 00000000..e69de29b diff --git a/yarn.lock b/yarn.lock index 4a7a509c..1be6fc28 100644 --- a/yarn.lock +++ b/yarn.lock @@ -727,6 +727,11 @@ resolved "https://registry.yarnpkg.com/@ember-data/rfc395-data/-/rfc395-data-0.0.4.tgz#ecb86efdf5d7733a76ff14ea651a1b0ed1f8a843" integrity sha512-tGRdvgC9/QMQSuSuJV45xoyhI0Pzjm7A9o/MVVA3HakXIImJbbzx/k/6dO9CUEQXIyS2y0fW6C1XaYOG7rY0FQ== +"@ember/edition-utils@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@ember/edition-utils/-/edition-utils-1.1.1.tgz#d5732c3da593f202e6e1ac6dbee56a758242403f" + integrity sha512-GEhri78jdQp/xxPpM6z08KlB0wrHfnfrJ9dmQk7JeQ4XCiMzXsJci7yooQgg/IcTKCM/PxE/IkGCQAo80adMkw== + "@ember/optional-features@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@ember/optional-features/-/optional-features-1.0.0.tgz#8e54ff37f4d9642212b45387f182cf7322aaaab9" @@ -2166,6 +2171,16 @@ broccoli-plugin@^1.0.0, broccoli-plugin@^1.2.0, broccoli-plugin@^1.2.1, broccoli rimraf "^2.3.4" symlink-or-copy "^1.1.8" +broccoli-plugin@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/broccoli-plugin/-/broccoli-plugin-2.0.0.tgz#c248d0241ba14be42b3253f6707370ee9d71c88e" + integrity sha512-p/p6pqDI+FlXwPSnwlFhzUgHNn1yj/EKaBys4/gL094+cVRbdsojFStrIjDoOtL8j+7iU+DazPnH3+/rD9PuLA== + dependencies: + promise-map-series "^0.2.1" + quick-temp "^0.1.3" + rimraf "^2.3.4" + symlink-or-copy "^1.1.8" + broccoli-slow-trees@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/broccoli-slow-trees/-/broccoli-slow-trees-3.0.1.tgz#9bf2a9e2f8eb3ed3a3f2abdde988da437ccdc9b4" @@ -2625,7 +2640,7 @@ commander@^2.15.1, commander@^2.6.0, commander@~2.20.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== -common-tags@^1.4.0: +common-tags@^1.4.0, common-tags@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937" integrity sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw== @@ -4121,6 +4136,11 @@ from2@^2.1.1: inherits "^2.0.1" readable-stream "^2.0.0" +fs-copy-file-sync@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/fs-copy-file-sync/-/fs-copy-file-sync-1.1.1.tgz#11bf32c096c10d126e5f6b36d06eece776062918" + integrity sha512-2QY5eeqVv4m2PfyMiEuy9adxNP+ajf+8AR05cEi+OAzPcOj90hvFImeZhTmKLBgSd9EvG33jsD7ZRxsx9dThkQ== + fs-extra@^0.24.0: version "0.24.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.24.0.tgz#d4e4342a96675cb7846633a6099249332b539952" @@ -6785,13 +6805,20 @@ resolve@1.9.0: dependencies: path-parse "^1.0.6" -resolve@^1.1.3, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.1, resolve@^1.12.0, resolve@^1.3.2, resolve@^1.3.3, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.8.1: +resolve@^1.1.3, resolve@^1.10.0, resolve@^1.11.1, resolve@^1.12.0, resolve@^1.3.2, resolve@^1.3.3, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.8.1: version "1.12.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6" integrity sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w== dependencies: path-parse "^1.0.6" +resolve@^1.10.1: + version "1.10.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.1.tgz#664842ac960795bbe758221cdccda61fb64b5f18" + integrity sha512-KuIe4mf++td/eFb6wkaPbMDnP6kObCaEtIDuHOUED6MNUo4K670KZUHuuvYPZDxNF0WVLw49n06M2m2dXphEzA== + dependencies: + path-parse "^1.0.6" + responselike@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" @@ -7318,11 +7345,6 @@ strip-bom@^3.0.0: resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= -strip-bom@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" - integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== - strip-eof@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"