diff --git a/packages/@vue/cli-service/package.json b/packages/@vue/cli-service/package.json index 92a144846a..430cc20969 100644 --- a/packages/@vue/cli-service/package.json +++ b/packages/@vue/cli-service/package.json @@ -26,6 +26,9 @@ "@intervolga/optimize-cssnano-plugin": "^1.0.5", "@soda/friendly-errors-webpack-plugin": "^1.7.1", "@soda/get-current-script": "^1.0.0", + "@types/minimist": "^1.2.0", + "@types/webpack": "^4.0.0", + "@types/webpack-dev-server": "^3.11.0", "@vue/cli-overlay": "^4.4.6", "@vue/cli-plugin-router": "^4.4.6", "@vue/cli-plugin-vuex": "^4.4.6", diff --git a/packages/@vue/cli-service/types/ProjectOptions.d.ts b/packages/@vue/cli-service/types/ProjectOptions.d.ts index df7daa2ddf..b4c0269e75 100644 --- a/packages/@vue/cli-service/types/ProjectOptions.d.ts +++ b/packages/@vue/cli-service/types/ProjectOptions.d.ts @@ -1,5 +1,5 @@ -import ChainableWebpackConfig from 'webpack-chain' -import { WebpackOptions } from 'webpack/declarations/WebpackOptions' +import ChainableWebpackConfig = require('webpack-chain') +import { Configuration as WebpackOptions } from 'webpack' type PageEntry = string | string[]; @@ -29,7 +29,7 @@ interface CSSOptions { loaderOptions?: LoaderOptions; } -export interface ProjectOptions { +interface ProjectOptions { publicPath?: string; outputDir?: string; assetsDir?: string; @@ -57,4 +57,6 @@ export interface ProjectOptions { pluginOptions?: object; } -export type ConfigFunction = () => ProjectOptions +type ConfigFunction = () => ProjectOptions + +export { ProjectOptions, ConfigFunction } diff --git a/packages/@vue/cli-service/types/cli-service-test.ts b/packages/@vue/cli-service/types/cli-service-test.ts new file mode 100644 index 0000000000..2f0a1b47a2 --- /dev/null +++ b/packages/@vue/cli-service/types/cli-service-test.ts @@ -0,0 +1,64 @@ +import { ServicePlugin } from '@vue/cli-service' + +const servicePlugin: ServicePlugin = (api, options) => { + const version = api.version + api.assertVersion(4) + api.assertVersion('^100') + api.getCwd() + api.resolve('src/main.js') + api.hasPlugin('eslint') + api.registerCommand( + 'lint', + { + description: 'lint and fix source files', + usage: 'vue-cli-service lint [options] [...files]', + options: { + '--format [formatter]': 'specify formatter (default: codeframe)' + }, + details: 'For more options, see https://eslint.org/docs/user-guide/command-line-interface#options' + }, + args => { + require('./lint')(args, api) + } + ) + api.registerCommand('lint', args => {}) + + api.chainWebpack(webpackConfig => { + if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') { + webpackConfig.devtool('cheap-module-eval-source-map') + + webpackConfig.plugin('hmr').use(require('webpack/lib/HotModuleReplacementPlugin')) + + webpackConfig.output.globalObject(`(typeof self !== 'undefined' ? self : this)`) + } + }) + + api.configureWebpack(config => { + config.output = { + path: 'test-dist-2' + } + }) + + api.configureWebpack(config => { + return { + devtool: config.devtool || 'source-map' + } + }) + + api.resolveWebpackConfig() + + api.resolveWebpackConfig(api.resolveChainableWebpackConfig()) + + const { cacheIdentifier, cacheDirectory } = api.genCacheConfig( + 'babel-loader', + { + '@babel/core': require('@babel/core/package.json').version, + '@vue/babel-preset-app': require('@vue/babel-preset-app/package.json').version, + 'babel-loader': require('babel-loader/package.json').version, + modern: !!process.env.VUE_CLI_MODERN_BUILD, + browserslist: api.service.pkg.browserslist + }, + ['babel.config.js', '.browserslistrc'] + ) +} +export = servicePlugin diff --git a/packages/@vue/cli-service/types/index.d.ts b/packages/@vue/cli-service/types/index.d.ts index d91403bdf8..dfb5e7f2d7 100644 --- a/packages/@vue/cli-service/types/index.d.ts +++ b/packages/@vue/cli-service/types/index.d.ts @@ -1 +1,137 @@ -export { ProjectOptions, ConfigFunction } from './ProjectOptions' +import minimist = require('minimist') +import ChainableConfig = require('webpack-chain') +import webpack = require('webpack') +import WebpackDevServer = require('webpack-dev-server') +import express = require('express') // @types/webpack-dev-server depends on @types/express +import { ProjectOptions } from './ProjectOptions' + +type RegisterCommandFn = (args: minimist.ParsedArgs, rawArgv: string[]) => any + +type RegisterCommandOpts = Partial<{ + description: string + usage: string + options: { + [flags: string]: string + } + details: string +}> + +type WebpackChainFn = (chainableConfig: ChainableConfig) => void + +type webpackRawConfigFn = ((config: webpack.Configuration) => webpack.Configuration | void) | webpack.Configuration + +type DevServerConfigFn = (app: express.Application, server: WebpackDevServer) => void + +interface CacheConfig { + cacheDirectory: string + cacheIdentifier: string +} +declare class PluginAPI { + id: string + + service: any + + readonly version: string + + assertVersion(range: number | string): void + + /** + * Current working directory. + */ + getCwd(): string + + /** + * Resolve path for a project. + * + * @param _path - Relative path from project root + * @return The resolved absolute path. + */ + resolve(_path: string): string + + /** + * Check if the project has a given plugin. + * + * @param id - Plugin id, can omit the (@vue/|vue-|@scope/vue)-cli-plugin- prefix + * @return `boolean` + */ + hasPlugin(id: string): boolean + + /** + * Register a command that will become available as `vue-cli-service [name]`. + * + * @param name + * @param [opts] + * @param fn + */ + registerCommand(name: string, fn: RegisterCommandFn): void + registerCommand(name: string, opts: RegisterCommandOpts, fn: RegisterCommandFn): void + + /** + * Register a function that will receive a chainable webpack config + * the function is lazy and won't be called until `resolveWebpackConfig` is + * called + * + * @param fn + */ + chainWebpack(fn: WebpackChainFn): void + + /** + * Register + * - a webpack configuration object that will be merged into the config + * OR + * - a function that will receive the raw webpack config. + * the function can either mutate the config directly or return an object + * that will be merged into the config. + * + * @param fn + */ + configureWebpack(fn: webpackRawConfigFn): void + + /** + * Register a dev serve config function. It will receive the express `app` + * instance of the dev server. + * + * @param fn + */ + configureDevServer(fn: DevServerConfigFn): void + + /** + * Resolve the final raw webpack config, that will be passed to webpack. + * + * @param [chainableConfig] + * @return Raw webpack config. + */ + resolveWebpackConfig(chainableConfig?: ChainableConfig): webpack.Configuration + + /** + * Resolve an intermediate chainable webpack config instance, which can be + * further tweaked before generating the final raw webpack config. + * You can call this multiple times to generate different branches of the + * base webpack config. + * See https://github.com/mozilla-neutrino/webpack-chain + * + * @return ChainableWebpackConfig + */ + resolveChainableWebpackConfig(): ChainableConfig + + /** + * Generate a cache identifier from a number of variables + */ + genCacheConfig(id: string, partialIdentifier: any, configFiles?: string | string[]): CacheConfig +} + +/** + * Service plugin serves for modifying webpack config, + * creating new vue-cli service commands or changing existing commands + * + * @param api - A PluginAPI instance + * @param options - An object containing project local options specified in vue.config.js, + * or in the "vue" field in package.json. + */ +type ServicePlugin = ( + api: PluginAPI, + options: ProjectOptions +) => any + +export { ProjectOptions, ServicePlugin, PluginAPI } +export { ConfigFunction } from './ProjectOptions' diff --git a/packages/@vue/cli-service/types/tsconfig.json b/packages/@vue/cli-service/types/tsconfig.json new file mode 100644 index 0000000000..06338ace90 --- /dev/null +++ b/packages/@vue/cli-service/types/tsconfig.json @@ -0,0 +1,22 @@ +{ + "files": [ + "cli-service-test.ts", + "index.d.ts", + "ProjectOptions.d.ts" + ], + "compilerOptions": { + "module": "commonjs", + "lib": [ + "es6" + ], + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "esModuleInterop": true, + "strictFunctionTypes": true, + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": "." + } +} diff --git a/packages/@vue/cli-test-utils/assertPromptModule.d.ts b/packages/@vue/cli-test-utils/assertPromptModule.d.ts new file mode 100644 index 0000000000..2671ded9e5 --- /dev/null +++ b/packages/@vue/cli-test-utils/assertPromptModule.d.ts @@ -0,0 +1,16 @@ +import { PromptModuleAPI } from '@vue/cli' + +interface CliPromptModule { + (api: PromptModuleAPI): void +} + +declare function assertPromptModule( + module: CliPromptModule | CliPromptModule[], + expectedPrompts: object[], + expectedOptions: object, + opts?: { + pluginsOnly?: boolean + }, +): Promise + +export = assertPromptModule diff --git a/packages/@vue/cli-test-utils/createJSONServer.d.ts b/packages/@vue/cli-test-utils/createJSONServer.d.ts new file mode 100644 index 0000000000..723b3a506b --- /dev/null +++ b/packages/@vue/cli-test-utils/createJSONServer.d.ts @@ -0,0 +1,21 @@ +import { Application } from 'express' + +declare function createJSONServer( + /** + * Either a path to a json file (e.g. 'db.json') or an object in memory + * + * Default: + *{ + * 'posts': [ + * { 'id': 1, 'title': 'json-server', 'author': 'typicode' } + * ], + * 'comments': [ + * { 'id': 1, 'body': 'some comment', 'postId': 1 } + * ], + * 'profile': { 'name': 'typicode' } + *} + */ + data?: string | object, +): Application + +export = createJSONServer diff --git a/packages/@vue/cli-test-utils/createServer.d.ts b/packages/@vue/cli-test-utils/createServer.d.ts new file mode 100644 index 0000000000..45b1c50c78 --- /dev/null +++ b/packages/@vue/cli-test-utils/createServer.d.ts @@ -0,0 +1,11 @@ +/// +import * as http from 'http' + +declare function createServer(options: { + /** + * Set a sub directory to be served + */ + root: string +}): http.Server + +export = createServer diff --git a/packages/@vue/cli-test-utils/createTestProject.d.ts b/packages/@vue/cli-test-utils/createTestProject.d.ts new file mode 100644 index 0000000000..a8564329d7 --- /dev/null +++ b/packages/@vue/cli-test-utils/createTestProject.d.ts @@ -0,0 +1,46 @@ +import execa = require('execa') // execa@1.0.0 needs @types/execa +import { Preset } from '@vue/cli' + +/** + * create project at path `cwd` + */ +declare function createTestProject( + /** + * project name + */ + name: string, + /** + * manual preset used to generate project. + * + * Example: + * { + * plugins: { + * '@vue/cli-plugin-babel': {} + * } + * } + */ + preset: Preset, + /** `path.resolve(cwd, name)` will be the project's root directory */ + cwd?: string | null, + /** + * if init git repo + * + * Default:`true` + */ + initGit?: boolean, +): Promise<{ + /** test project's root path */ + dir: string + /** test if project contains the file */ + has: (file: string) => boolean + /** read the content for the file */ + read: (file: string) => Promise + /** write file to project */ + write: (file: string, content: any) => Promise + /** execa command at root path of project */ + run: (command: string, args?: ReadonlyArray) => execa.ExecaChildProcess + /** delete the file of project */ + rm: (file: string) => Promise +}> + +export = createTestProject diff --git a/packages/@vue/cli-test-utils/generateWithPlugin.d.ts b/packages/@vue/cli-test-utils/generateWithPlugin.d.ts new file mode 100644 index 0000000000..6998c9c5a4 --- /dev/null +++ b/packages/@vue/cli-test-utils/generateWithPlugin.d.ts @@ -0,0 +1,32 @@ +import { GeneratorAPI, Preset } from '@vue/cli' + +type ApplyFn = ( + api: GeneratorAPI, + options: any, + rootOptions: Preset, + invoking: boolean, +) => any +interface Plugin { + /** package name from plugin */ + id: string + /** generator function from plugin */ + apply: ApplyFn + /** parameter passed to generator function */ + options?: any +} + +/** + * invoke generator function, and generate file tree in memory + */ +declare function generateWithPlugin( + plugin: Plugin | Plugin[], +): Promise<{ + /** package.json Object */ + pkg: Record + /** virtual file tree, file path is the key of Object */ + files: { + [filePath: string]: string | Buffer + } +}> + +export = generateWithPlugin diff --git a/packages/@vue/cli-test-utils/launchPuppeteer.d.ts b/packages/@vue/cli-test-utils/launchPuppeteer.d.ts new file mode 100644 index 0000000000..1cb8383b56 --- /dev/null +++ b/packages/@vue/cli-test-utils/launchPuppeteer.d.ts @@ -0,0 +1,12 @@ +import { Browser, Page } from 'puppeteer' + +declare function launchPuppeteer( + url: string, +): Promise<{ + browser: Browser + page: Page + logs: string[] + requestUrls: string[] +}> + +export = launchPuppeteer diff --git a/packages/@vue/cli-test-utils/package.json b/packages/@vue/cli-test-utils/package.json index e56cc57eb6..e6815b0dab 100644 --- a/packages/@vue/cli-test-utils/package.json +++ b/packages/@vue/cli-test-utils/package.json @@ -22,6 +22,11 @@ "access": "public" }, "dependencies": { + "@types/execa": "^0.9.0", + "@types/express": "4.17.4", + "@types/node": "*", + "@types/puppeteer": "^1.11.0", + "@vue/cli": "^4.3.0", "execa": "^1.0.0", "fs-extra": "^7.0.1", "json-server": "^0.15.0", diff --git a/packages/@vue/cli-test-utils/serveWithPuppeteer.d.ts b/packages/@vue/cli-test-utils/serveWithPuppeteer.d.ts new file mode 100644 index 0000000000..a18af5e6ee --- /dev/null +++ b/packages/@vue/cli-test-utils/serveWithPuppeteer.d.ts @@ -0,0 +1,31 @@ +import execa = require('execa') +import { Browser, Page } from 'puppeteer' + +interface Helpers { + getText: (selector: string) => Promise + hasElement: (selector: string) => Promise + hasClass: (selector: string, cls: string) => Promise +} + +interface Utils { + url: string + browser: Browser + page: Page + /** wait for hot replacement */ + nextUpdate: () => Promise + helpers: Helpers + requestUrls: string[] +} + +declare function serveWithPuppeteer( + serve: () => execa.ExecaChildProcess, + /** Function which executes test codes*/ + test: (arg: Utils) => any, + /** + * don't launch puppeteer. + * Defaults to `false`. + */ + noPuppeteer?: boolean, +): Promise + +export = serveWithPuppeteer diff --git a/packages/@vue/cli-test-utils/types/cli-test-utils-test.ts b/packages/@vue/cli-test-utils/types/cli-test-utils-test.ts new file mode 100644 index 0000000000..7ce15e862b --- /dev/null +++ b/packages/@vue/cli-test-utils/types/cli-test-utils-test.ts @@ -0,0 +1,106 @@ +import assertPromptModule from '@vue/cli-test-utils/assertPromptModule' +import createJSONServer from '@vue/cli-test-utils/createJSONServer' +import createServer from '@vue/cli-test-utils/createServer' +import createTestProject from '@vue/cli-test-utils/createTestProject' +import generateWithPlugin from '@vue/cli-test-utils/generateWithPlugin' +import launchPuppeteer from '@vue/cli-test-utils/launchPuppeteer' +import serveWithPuppeteer from '@vue/cli-test-utils/serveWithPuppeteer' +import path from 'path' + +const expectedPrompts = [{ choose: 0 }] + +const expectedOptions = { + useConfigFiles: false, + plugins: { + foo: {} + } +} + +assertPromptModule( + api => { + api.injectFeature({ + name: 'Foo', + value: 'foo' + }) + api.injectFeature({ + name: 'Bar', + value: 'bar' + }) + api.onPromptComplete((answers, options) => { + if (answers.features.includes('foo')) { + options.plugins.foo = {} + } + }) + }, + expectedPrompts, + expectedOptions +) + +const mockServer1 = createJSONServer({ + posts: [{ id: 1, title: 'server-one', author: 'typicode' }] +}).listen(3000, () => {}) + +const server = createServer({ root: path.resolve(__dirname, 'temp') }) + +async function createTest() { + const project = await createTestProject( + 'eslint', + { + plugins: { + '@vue/cli-plugin-babel': {}, + '@vue/cli-plugin-eslint': { + config: 'airbnb', + lintOn: 'commit' + } + } + }, + null, + true + ) + const { dir, has, read, write, run, rm } = project + + if (!has('src/main.js')) return + + const main = await read('src/main.js') + + const updatedMain = main.replace(/;/g, '') + + await write('src/main.js', updatedMain) + + await project.rm(`src/test.js`) + + const { stdout } = await run('vue-cli-service lint') + + await serveWithPuppeteer( + () => project.run('vue-cli-service serve'), + async ({ url, browser, page, nextUpdate, helpers, requestUrls }) => { + await helpers.getText('h1') + } + ) +} + +async function testGenerate() { + const { pkg, files } = await generateWithPlugin({ + id: 'test-plugin', + apply: (api, options, rootOptions, invoking) => { + if (options.skip) return + console.log(rootOptions.bare, rootOptions.projectName, rootOptions.useConfigFiles, rootOptions.cssPreprocessor) + if (rootOptions.plugins) console.log(rootOptions.plugins['@vue/cli-service']) + if (rootOptions.configs) console.log(rootOptions.configs.vue) + }, + options: { + skip: true + } + }) + const lint = pkg.scripts.lint + const main = files['src/main.js'] + + await generateWithPlugin({ + id: 'test-plugin-no-options', + apply: (api, options, rootOptions, invoking) => {} + }) +} + +async function testLaunchPuppeteer() { + const { browser, page, logs, requestUrls } = await launchPuppeteer(`http://localhost:8080/`) +} diff --git a/packages/@vue/cli-test-utils/types/tsconfig.json b/packages/@vue/cli-test-utils/types/tsconfig.json new file mode 100644 index 0000000000..7b031c212c --- /dev/null +++ b/packages/@vue/cli-test-utils/types/tsconfig.json @@ -0,0 +1,21 @@ +{ + "files": [ + "cli-test-utils-test.ts" + ], + "compilerOptions": { + "module": "commonjs", + "lib": [ + "es6", + "dom" + ], + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "esModuleInterop": true, + "strictFunctionTypes": true, + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": "." + } +} diff --git a/packages/@vue/cli/package.json b/packages/@vue/cli/package.json index 78b2c1a759..2c62a41659 100644 --- a/packages/@vue/cli/package.json +++ b/packages/@vue/cli/package.json @@ -5,6 +5,7 @@ "bin": { "vue": "bin/vue.js" }, + "types": "types/index.d.ts", "repository": { "type": "git", "url": "git+https://github.com/vuejs/vue-cli.git", @@ -24,6 +25,8 @@ "access": "public" }, "dependencies": { + "@types/ejs": "^2.7.0", + "@types/inquirer": "^6.5.0", "@vue/cli-shared-utils": "^4.4.6", "@vue/cli-ui": "^4.4.6", "@vue/cli-ui-addon-webpack": "^4.4.6", diff --git a/packages/@vue/cli/types/cli-test.ts b/packages/@vue/cli/types/cli-test.ts new file mode 100644 index 0000000000..c9294c23b6 --- /dev/null +++ b/packages/@vue/cli/types/cli-test.ts @@ -0,0 +1,188 @@ +import { GeneratorPlugin, PromptModuleAPI } from '@vue/cli' + +const testPromptAPI = (cli: PromptModuleAPI) => { + cli.injectFeature({ + name: 'Babel', + value: 'babel', + short: 'Babel', + // descriptions: 'Transpile modern JavaScript to older versions (for compatibility)', + // link: 'https://babeljs.io/', + checked: true + }) + + cli.injectOptionForPrompt('customBar', { + name: 'barChoice', + value: 'barChoice' + }) + cli.onPromptComplete<{ features: string[]; useTsWithBabel: boolean }>((answers, options) => { + if (answers.features.includes('ts')) { + if (!answers.useTsWithBabel) { + return + } + } else if (!answers.features.includes('babel')) { + return + } + options.plugins['@vue/cli-plugin-babel'] = {} + }) + + cli.injectFeature({ + name: 'CSS Pre-processors', + value: 'css-preprocessor' + // description: 'Add support for CSS pre-processors like Sass, Less or Stylus', + // link: 'https://cli.vuejs.org/guide/css.html' + }) + + const notice = 'PostCSS, Autoprefixer and CSS Modules are supported by default' + cli.injectPrompt<{ features: string[] }>({ + name: 'cssPreprocessor', + when: answers => answers.features.includes('css-preprocessor'), + type: 'list', + message: `Pick a CSS pre-processor${process.env.VUE_CLI_API_MODE ? '' : ` (${notice})`}:`, + // description: `${notice}.`, + choices: [ + { + name: 'Sass/SCSS (with dart-sass)', + value: 'dart-sass' + }, + { + name: 'Sass/SCSS (with node-sass)', + value: 'node-sass' + }, + { + name: 'Less', + value: 'less' + }, + { + name: 'Stylus', + value: 'stylus' + } + ] + }) +} + +const generator: GeneratorPlugin = (api, options, rootOptions, invoking) => { + const version = api.cliVersion + const cliServiceVersion = api.cliServiceVersion + api.assertCliServiceVersion(4) + api.assertCliServiceVersion('^100') + api.hasPlugin('eslint') + api.hasPlugin('eslint', '^6.0.0') + + api.addConfigTransform('fooConfig', { + file: { + json: ['foo.config.json'] + } + }) + + api.extendPackage({ + fooConfig: { + bar: 42 + }, + dependencies: { + 'vue-router-layout': '^0.1.2' + } + }) + api.extendPackage(() => ({ + fooConfig: { + bar: 42 + }, + dependencies: { + 'vue-router-layout': '^0.1.2' + } + })) + api.extendPackage(pkg => ({ + foo: pkg.foo + 1 + })) + api.extendPackage( + { + fooConfig: { + bar: 42 + }, + dependencies: { + 'vue-router-layout': '^0.1.2' + } + }, + true + ) + api.extendPackage( + { + fooConfig: { + bar: 42 + }, + dependencies: { + 'vue-router-layout': '^0.1.2' + } + }, + { + merge: true, + prune: true, + warnIncompatibleVersions: true + } + ) + + api.render('./template') + + api.render( + './template', + { + hasTS: api.hasPlugin('typescript'), + hasESLint: api.hasPlugin('eslint') + }, + { + strict: true, + rmWhitespace: false + } + ) + + api.render((files, render) => { + files['foo2.js'] = render('foo(<%- n %>)', { n: 3 }) + files['bar/bar2.js'] = render('bar(<%- n %>)', { n: 3 }, { rmWhitespace: false }) + }) + + api.postProcessFiles(files => { + delete files['src/test.js'] + }) + + api.onCreateComplete(() => { + console.log('complete') + }) + + api.afterInvoke(() => { + console.log('after invoke') + }) + + api.afterAnyInvoke(() => { + console.log('after any invoke') + }) + + api.exitLog('msg') + api.exitLog('msg', 'error') + api.genJSConfig({ foo: 1 }) + + api.extendPackage({ + vue: { + publicPath: api.makeJSOnlyValue(`process.env.VUE_CONTEXT`) + } + }) + api.transformScript( + 'src/test.js', + (fileInfo, api, { additionalData }) => { + const j = api.jscodeshift + const root = j(fileInfo.source) + return root.toSource() + }, + { + additionalData: [] + } + ) + + api.injectImports('main.js', `import bar from 'bar'`) + + api.injectRootOptions('main.js', ['foo', 'bar']) + + api.resolve(api.entryFile) + + const isInvoking = api.invoking +} + +export = generator diff --git a/packages/@vue/cli/types/index.d.ts b/packages/@vue/cli/types/index.d.ts new file mode 100644 index 0000000000..6b4cf5deca --- /dev/null +++ b/packages/@vue/cli/types/index.d.ts @@ -0,0 +1,243 @@ +import { DistinctQuestion, CheckboxChoiceOptions, Answers, ChoiceOptions } from 'inquirer' +import { Parser, Transform } from 'jscodeshift' +import * as ejs from 'ejs' + +interface RenderFile { + [path: string]: string | Buffer +} + +type FileMiddleware = (files: RenderFile, render: typeof ejs.render) => void +type PostProcessFilesCallback = (files: RenderFile) => void + +type RenderSource = string | RenderFile + +type TransformModule = Transform & { + default?: Transform + parser?: string | Parser +} +interface TransformOptions { + [prop: string]: any + parser?: string | Parser +} +interface __expressionFn { + (): void + __expression: string +} + +interface OnPromptCompleteCb { + ( + answers: T, + options: { + useConfigFiles: boolean + plugins: Record + } + ): void +} +type ExtendPackageOptions = + | { + prune?: boolean + merge?: boolean + warnIncompatibleVersions?: boolean + } + | boolean + +type Preset = Partial<{ + [props: string]: any + bare: boolean + projectName: string + useConfigFiles: boolean + plugins: Record + configs: Record + cssPreprocessor: 'sass' | 'dart-sass' | 'node-sass' | 'less' | 'stylus' +}> + +declare class PromptModuleAPI { + /** inject checkbox choice for feature prompt. */ + injectFeature(feature: CheckboxChoiceOptions): void + + injectPrompt(prompt: DistinctQuestion): void + + injectOptionForPrompt(name: string, option: ChoiceOptions): void + + /** run cb registered by prompt modules to finalize the preset. */ + onPromptComplete(cb: OnPromptCompleteCb): void +} + +declare class GeneratorAPI { + /** + * Resolve path for a project. + * + * @param _paths - A sequence of relative paths or path segments + * @return The resolved absolute path, calculated based on the current project root. + */ + resolve(..._paths: string[]): string + + readonly cliVersion: string + + assertCliVersion(range: number | string): void + + readonly cliServiceVersion: string + + assertCliServiceVersion(range: number | string): void + + /** + * Check if the project has a given plugin. + * + * @param id - Plugin id, can omit the (@vue/|vue-|@scope/vue)-cli-plugin- prefix + * @param version - Plugin version. Defaults to '' + * @return `boolean` + */ + hasPlugin(id: string, version?: string): boolean + + /** + * Configure how config files are extracted. + * + * @param key - Config key in package.json + * @param options - Options + * @param options.file - File descriptor + * Used to search for existing file. + * Each key is a file type (possible values: ['js', 'json', 'yaml', 'lines']). + * The value is a list of filenames. + * Example: + * { + * js: ['.eslintrc.js'], + * json: ['.eslintrc.json', '.eslintrc'] + * } + * By default, the first filename will be used to create the config file. + */ + addConfigTransform(key: string, options: { file: { [type: string]: string[] } }): void + + /** + * Extend the package.json of the project. + * Also resolves dependency conflicts between plugins. + * Tool configuration fields may be extracted into standalone files before + * files are written to disk. + * + * @param fields - Fields to merge. + * @param [options] - Options for extending / merging fields. + * @param [options.prune=false] - Remove null or undefined fields + * from the object after merging. + * @param [options.merge=true] deep-merge nested fields, note + * that dependency fields are always deep merged regardless of this option. + * @param [options.warnIncompatibleVersions=true] Output warning + * if two dependency version ranges don't intersect. + */ + extendPackage( + fields: (pkg: Record) => object, + options?: ExtendPackageOptions + ): void + extendPackage( + fields: T extends Function ? never : T, + options?: ExtendPackageOptions + ): void + + /** + * Render template files into the virtual files tree object. + * + * @param source - + * Can be one of: + * - relative path to a directory; + * - Object hash of { sourceTemplate: targetFile } mappings; + * - a custom file middleware function. + * @param [additionalData] - additional data available to templates. + * @param [ejsOptions] - options for ejs. + */ + render(source: RenderSource, additionalData?: object, ejsOptions?: ejs.Options): void + render(source: FileMiddleware): void + + /** + * Push a file middleware that will be applied after all normal file + * middlewares have been applied. + * + * @param cb + */ + postProcessFiles(cb: PostProcessFilesCallback): void + + /** + * Push a callback to be called when the files have been written to disk. + * + * @param cb + */ + onCreateComplete(cb: (...args: any[]) => any): void + + /** + * same to `onCreateComplete`. + * + * @param cb + */ + afterInvoke(cb: (...args: any[]) => any): void + + /** + * Push a callback to be called when the files have been written to disk + * from non invoked plugins + * + * @param cb + */ + afterAnyInvoke(cb: (...args: any[]) => any): void + + /** + * Add a message to be printed when the generator exits (after any other standard messages). + * + * @param msg String or value to print after the generation is completed + * @param [type='log'] Type of message + */ + exitLog(msg: any, type?: 'log' | 'info' | 'done' | 'warn' | 'error'): void + + /** + * convenience method for generating a js config file from json + */ + genJSConfig(value: any): string + + /** + * Turns a string expression into executable JS for JS configs. + * @param str JS expression as a string + */ + makeJSOnlyValue(str: string): __expressionFn + + /** + * Run codemod on a script file or the script part of a .vue file + * @param file the path to the file to transform + * @param codemod the codemod module to run + * @param options additional options for the codemod + */ + transformScript(file: string, codemod: TransformModule, options?: TransformOptions): void + + /** + * Add import statements to a file. + */ + injectImports(file: string, imports: string | string[]): void + + /** + * Add options to the root Vue instance (detected by `new Vue`). + */ + injectRootOptions(file: string, options: string | string[]): void + + /** + * Get the entry file taking into account typescript. + * + */ + readonly entryFile: 'src/main.ts' | 'src/main.js' + + /** + * Is the plugin being invoked? + * + */ + readonly invoking: boolean +} + +/** + * function exported by a generator + * @param api - A GeneratorAPI instance + * @param options - These options are resolved during the prompt phase of project creation, + * or loaded from a saved preset in ~/.vuerc + * @param rootOptions - The entire preset will be passed + * @param invoking - Is the plugin being invoked + */ +type GeneratorPlugin = ( + api: GeneratorAPI, + options: any, + rootOptions: Preset, + invoking: boolean +) => any + +export { PromptModuleAPI, GeneratorAPI, Preset, GeneratorPlugin } diff --git a/packages/@vue/cli/types/tsconfig.json b/packages/@vue/cli/types/tsconfig.json new file mode 100644 index 0000000000..d64e4de3b9 --- /dev/null +++ b/packages/@vue/cli/types/tsconfig.json @@ -0,0 +1,21 @@ +{ + "files": [ + "cli-test.ts", + "index.d.ts" + ], + "compilerOptions": { + "module": "commonjs", + "lib": [ + "es6" + ], + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "esModuleInterop": true, + "strictFunctionTypes": true, + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": "." + } +} diff --git a/yarn.lock b/yarn.lock index 66ce21cee7..6cc215c748 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3302,6 +3302,14 @@ resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== +"@types/connect-history-api-fallback@*": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.3.tgz#4772b79b8b53185f0f4c9deab09236baf76ee3b4" + integrity sha512-7SxFCd+FLlxCfwVwbyPxbR4khL9aNikJhrorw8nUIOqeuooc9gifBuDQOJw5kzN7i6i3vLn9G8Wde/4QDihpYw== + dependencies: + "@types/express-serve-static-core" "*" + "@types/node" "*" + "@types/connect@*": version "3.4.33" resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.33.tgz#31610c901eca573b8713c3330abc6e6b9f588546" @@ -3326,6 +3334,11 @@ dependencies: "@types/express" "*" +"@types/ejs@^2.7.0": + version "2.7.0" + resolved "https://registry.yarnpkg.com/@types/ejs/-/ejs-2.7.0.tgz#bc84e083eae38f64a287a6dab9012bbe1d96e295" + integrity sha512-kM2g9Fdk/du24fKuuQhA/LBleFR4Z4JP2MVKpLxQQSzofF1uJ06D+c05zfLDAkkDO55aEeNwJih0gHrE/Ci20A== + "@types/eslint-visitor-keys@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" @@ -3336,6 +3349,13 @@ resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== +"@types/execa@^0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@types/execa/-/execa-0.9.0.tgz#9b025d2755f17e80beaf9368c3f4f319d8b0fb93" + integrity sha512-mgfd93RhzjYBUHHV532turHC2j4l/qxsF/PbfDmprHDEUHmNZGlDn1CEsulGK3AfsPdhkWzZQT/S/k0UGhLGsA== + dependencies: + "@types/node" "*" + "@types/express-serve-static-core@*": version "4.17.2" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.2.tgz#f6f41fa35d42e79dbf6610eccbb2637e6008a0cf" @@ -3394,6 +3414,30 @@ resolved "https://registry.yarnpkg.com/@types/http-assert/-/http-assert-1.5.1.tgz#d775e93630c2469c2f980fc27e3143240335db3b" integrity sha512-PGAK759pxyfXE78NbKxyfRcWYA/KwW17X290cNev/qAsn9eQIxkH4shoNBafH37wewhDG/0p1cHPbK6+SzZjWQ== +"@types/http-proxy-middleware@*": + version "0.19.3" + resolved "https://registry.yarnpkg.com/@types/http-proxy-middleware/-/http-proxy-middleware-0.19.3.tgz#b2eb96fbc0f9ac7250b5d9c4c53aade049497d03" + integrity sha512-lnBTx6HCOUeIJMLbI/LaL5EmdKLhczJY5oeXZpX/cXE4rRqb3RmV7VcMpiEfYkmTjipv3h7IAyIINe4plEv7cA== + dependencies: + "@types/connect" "*" + "@types/http-proxy" "*" + "@types/node" "*" + +"@types/http-proxy@*": + version "1.17.4" + resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.4.tgz#e7c92e3dbe3e13aa799440ff42e6d3a17a9d045b" + integrity sha512-IrSHl2u6AWXduUaDLqYpt45tLVCtYv7o4Z0s1KghBCDgIIS9oW5K1H8mZG/A2CfeLdEa7rTd1ACOiHBc1EMT2Q== + dependencies: + "@types/node" "*" + +"@types/inquirer@^6.5.0": + version "6.5.0" + resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-6.5.0.tgz#b83b0bf30b88b8be7246d40e51d32fe9d10e09be" + integrity sha512-rjaYQ9b9y/VFGOpqBEXRavc3jh0a+e6evAbI31tMda8VlPaSy0AZJfXsvmIe3wklc7W6C3zCSfleuMXR7NOyXw== + dependencies: + "@types/through" "*" + rxjs "^6.4.0" + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff" @@ -3480,6 +3524,11 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== +"@types/minimist@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.0.tgz#69a23a3ad29caf0097f06eda59b361ee2f0639f6" + integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY= + "@types/mocha@^5.2.6": version "5.2.7" resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.7.tgz#315d570ccb56c53452ff8638738df60726d5b6ea" @@ -3507,6 +3556,13 @@ resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA== +"@types/puppeteer@^1.11.0": + version "1.20.4" + resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-1.20.4.tgz#30cb0a4ee5394c420119cbdf9f079d6595a07f67" + integrity sha512-T/kFgyLnYWk0H94hxI0HbOLnqHvzBRpfS0F0oo9ESGI24oiC2fEjDcMbBjuK3wH7VLsaIsp740vVXVzR1dsMNg== + dependencies: + "@types/node" "*" + "@types/q@^1.5.1": version "1.5.2" resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8" @@ -3560,6 +3616,13 @@ resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.5.tgz#9adbc12950582aa65ead76bffdf39fe0c27a3c02" integrity sha512-/gG2M/Imw7cQFp8PGvz/SwocNrmKFjFsm5Pb8HdbHkZ1K8pmuPzOX4VeVoiEecFCVf4CsN1r3/BRvx+6sNqwtQ== +"@types/through@*": + version "0.0.30" + resolved "https://registry.yarnpkg.com/@types/through/-/through-0.0.30.tgz#e0e42ce77e897bd6aead6f6ea62aeb135b8a3895" + integrity sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg== + dependencies: + "@types/node" "*" + "@types/uglify-js@*": version "3.9.2" resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.9.2.tgz#01992579debba674e1e359cd6bcb1a1d0ab2e02b" @@ -3567,24 +3630,35 @@ dependencies: source-map "^0.6.1" +"@types/webpack-dev-server@^3.11.0": + version "3.11.0" + resolved "https://registry.yarnpkg.com/@types/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz#bcc3b85e7dc6ac2db25330610513f2228c2fcfb2" + integrity sha512-3+86AgSzl18n5P1iUP9/lz3G3GMztCp+wxdDvVuNhx1sr1jE79GpYfKHL8k+Vht3N74K2n98CuAEw4YPJCYtDA== + dependencies: + "@types/connect-history-api-fallback" "*" + "@types/express" "*" + "@types/http-proxy-middleware" "*" + "@types/serve-static" "*" + "@types/webpack" "*" + "@types/webpack-env@^1.15.2": version "1.15.2" resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.15.2.tgz#927997342bb9f4a5185a86e6579a0a18afc33b0a" integrity sha512-67ZgZpAlhIICIdfQrB5fnDvaKFcDxpKibxznfYRVAT4mQE41Dido/3Ty+E3xGBmTogc5+0Qb8tWhna+5B8z1iQ== "@types/webpack-sources@*": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-1.4.0.tgz#e58f1f05f87d39a5c64cf85705bdbdbb94d4d57e" - integrity sha512-c88dKrpSle9BtTqR6ifdaxu1Lvjsl3C5OsfvuUbUwdXymshv1TkufUAXBajCCUM/f/TmnkZC/Esb03MinzSiXQ== + version "0.1.7" + resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-0.1.7.tgz#0a330a9456113410c74a5d64180af0cbca007141" + integrity sha512-XyaHrJILjK1VHVC4aVlKsdNN5KBTwufMb43cQs+flGxtPAf/1Qwl8+Q0tp5BwEGaI8D6XT1L+9bSWXckgkjTLw== dependencies: "@types/node" "*" "@types/source-list-map" "*" - source-map "^0.7.3" + source-map "^0.6.1" -"@types/webpack@*": - version "4.41.17" - resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.17.tgz#0a69005e644d657c85b7d6ec1c826a71bebd1c93" - integrity sha512-6FfeCidTSHozwKI67gIVQQ5Mp0g4X96c2IXxX75hYEQJwST/i6NyZexP//zzMOBb+wG9jJ7oO8fk9yObP2HWAw== +"@types/webpack@*", "@types/webpack@^4.0.0": + version "4.41.10" + resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.10.tgz#2e1f6b3508a249854efe3dcc7690905ac5ee10be" + integrity sha512-vIy0qaq8AjOjZLuFPqpo7nAJzcoVXMdw3mvpNN07Uvdy0p1IpJeLNBe3obdRP7FX2jIusDE7z1pZa0A6qYUgnA== dependencies: "@types/anymatch" "*" "@types/node" "*"