From 2d2710a7c486ab2dc5c005695df1089d5d1d03a5 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 21 May 2020 13:51:08 +0200 Subject: [PATCH 1/6] packages/cli: move plugin diff prompts to separate file --- .../cli/src/commands/plugin/diff/index.ts | 44 +++------------ .../cli/src/commands/plugin/diff/prompts.ts | 53 +++++++++++++++++++ 2 files changed, 59 insertions(+), 38 deletions(-) create mode 100644 packages/cli/src/commands/plugin/diff/prompts.ts diff --git a/packages/cli/src/commands/plugin/diff/index.ts b/packages/cli/src/commands/plugin/diff/index.ts index 5338594a3b..6300ff64d8 100644 --- a/packages/cli/src/commands/plugin/diff/index.ts +++ b/packages/cli/src/commands/plugin/diff/index.ts @@ -14,12 +14,14 @@ * limitations under the License. */ -import chalk from 'chalk'; -import inquirer from 'inquirer'; import { Command } from 'commander'; import { readTemplateFiles } from './read'; import { handlers, handleAllFiles } from './handlers'; -import { PromptFunc } from './types'; +import { + inquirerPromptFunc, + makeCheckPromptFunc, + yesPromptFunc, +} from './prompts'; const fileHandlers = [ { @@ -41,46 +43,12 @@ const fileHandlers = [ }, ]; -const inquirerPromptFunc: PromptFunc = async (msg) => { - const { result } = await inquirer.prompt({ - type: 'confirm', - name: 'result', - message: chalk.blue(msg), - }); - return result; -}; - -const makeCheck = () => { - let failed = false; - - const promptFunc: PromptFunc = async (msg) => { - failed = true; - console.log(chalk.red(`[Check Failed] ${msg}`)); - return false; - }; - - const finalize = () => { - if (failed) { - throw new Error( - 'Check failed, the plugin is not in sync with the latest template', - ); - } - }; - - return [promptFunc, finalize] as const; -}; - -const yesPromptFunc: PromptFunc = async (msg) => { - console.log(`Accepting: "${msg}"`); - return true; -}; - export default async (cmd: Command) => { let promptFunc = inquirerPromptFunc; let finalize = () => {}; if (cmd.check) { - [promptFunc, finalize] = makeCheck(); + [promptFunc, finalize] = makeCheckPromptFunc(); } else if (cmd.yes) { promptFunc = yesPromptFunc; } diff --git a/packages/cli/src/commands/plugin/diff/prompts.ts b/packages/cli/src/commands/plugin/diff/prompts.ts new file mode 100644 index 0000000000..ac04fc3559 --- /dev/null +++ b/packages/cli/src/commands/plugin/diff/prompts.ts @@ -0,0 +1,53 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import chalk from 'chalk'; +import inquirer from 'inquirer'; +import { PromptFunc } from './types'; + +export const inquirerPromptFunc: PromptFunc = async msg => { + const { result } = await inquirer.prompt({ + type: 'confirm', + name: 'result', + message: chalk.blue(msg), + }); + return result; +}; + +export const makeCheckPromptFunc = () => { + let failed = false; + + const promptFunc: PromptFunc = async msg => { + failed = true; + console.log(chalk.red(`[Check Failed] ${msg}`)); + return false; + }; + + const finalize = () => { + if (failed) { + throw new Error( + 'Check failed, the plugin is not in sync with the latest template', + ); + } + }; + + return [promptFunc, finalize] as const; +}; + +export const yesPromptFunc: PromptFunc = async msg => { + console.log(`Accepting: "${msg}"`); + return true; +}; From 18139238a4cedb1a457b3be24fdacb05568949de Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 21 May 2020 15:54:02 +0200 Subject: [PATCH 2/6] packages/cli: move plugin diff modules to separate diff lib --- .../plugin/{diff/index.ts => diff.ts} | 7 ++++--- .../{commands/plugin => lib}/diff/handlers.ts | 6 +++--- packages/cli/src/lib/diff/index.ts | 20 +++++++++++++++++++ .../{commands/plugin => lib}/diff/prompts.ts | 0 .../src/{commands/plugin => lib}/diff/read.ts | 12 +++++------ .../{commands/plugin => lib}/diff/types.ts | 0 6 files changed, 32 insertions(+), 13 deletions(-) rename packages/cli/src/commands/plugin/{diff/index.ts => diff.ts} (92%) rename packages/cli/src/{commands/plugin => lib}/diff/handlers.ts (97%) create mode 100644 packages/cli/src/lib/diff/index.ts rename packages/cli/src/{commands/plugin => lib}/diff/prompts.ts (100%) rename packages/cli/src/{commands/plugin => lib}/diff/read.ts (93%) rename packages/cli/src/{commands/plugin => lib}/diff/types.ts (100%) diff --git a/packages/cli/src/commands/plugin/diff/index.ts b/packages/cli/src/commands/plugin/diff.ts similarity index 92% rename from packages/cli/src/commands/plugin/diff/index.ts rename to packages/cli/src/commands/plugin/diff.ts index 6300ff64d8..542b94b0ff 100644 --- a/packages/cli/src/commands/plugin/diff/index.ts +++ b/packages/cli/src/commands/plugin/diff.ts @@ -15,13 +15,14 @@ */ import { Command } from 'commander'; -import { readTemplateFiles } from './read'; -import { handlers, handleAllFiles } from './handlers'; import { + readTemplateFiles, + handlers, + handleAllFiles, inquirerPromptFunc, makeCheckPromptFunc, yesPromptFunc, -} from './prompts'; +} from '../../lib/diff'; const fileHandlers = [ { diff --git a/packages/cli/src/commands/plugin/diff/handlers.ts b/packages/cli/src/lib/diff/handlers.ts similarity index 97% rename from packages/cli/src/commands/plugin/diff/handlers.ts rename to packages/cli/src/lib/diff/handlers.ts index 87518eab3e..643c018ddc 100644 --- a/packages/cli/src/commands/plugin/diff/handlers.ts +++ b/packages/cli/src/lib/diff/handlers.ts @@ -18,7 +18,7 @@ import fs from 'fs-extra'; import chalk from 'chalk'; import { dirname } from 'path'; import { diffLines } from 'diff'; -import { paths } from '../../../lib/paths'; +import { paths } from '../paths'; import { TemplateFile, PromptFunc, FileHandler } from './types'; export async function writeTargetFile(targetPath: string, contents: string) { @@ -221,8 +221,8 @@ export async function handleAllFiles( ) { for (const file of files) { const { targetPath } = file; - const fileHandler = fileHandlers.find((handler) => - handler.patterns.some((pattern) => + const fileHandler = fileHandlers.find(handler => + handler.patterns.some(pattern => typeof pattern === 'string' ? pattern === targetPath : pattern.test(targetPath), diff --git a/packages/cli/src/lib/diff/index.ts b/packages/cli/src/lib/diff/index.ts new file mode 100644 index 0000000000..04c5ee2e45 --- /dev/null +++ b/packages/cli/src/lib/diff/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './handlers'; +export * from './prompts'; +export * from './read'; +export * from './types'; diff --git a/packages/cli/src/commands/plugin/diff/prompts.ts b/packages/cli/src/lib/diff/prompts.ts similarity index 100% rename from packages/cli/src/commands/plugin/diff/prompts.ts rename to packages/cli/src/lib/diff/prompts.ts diff --git a/packages/cli/src/commands/plugin/diff/read.ts b/packages/cli/src/lib/diff/read.ts similarity index 93% rename from packages/cli/src/commands/plugin/diff/read.ts rename to packages/cli/src/lib/diff/read.ts index a05d7166e2..b408da94e9 100644 --- a/packages/cli/src/commands/plugin/diff/read.ts +++ b/packages/cli/src/lib/diff/read.ts @@ -18,8 +18,8 @@ import fs from 'fs-extra'; import { relative as relativePath } from 'path'; import handlebars from 'handlebars'; import recursiveReadDir from 'recursive-readdir'; -import { paths } from '../../../lib/paths'; -import { version } from '../../../lib/version'; +import { paths } from '../paths'; +import { version } from '../version'; import { PluginInfo, TemplateFile } from './types'; // Reads info from the existing plugin @@ -64,11 +64,9 @@ export async function readTemplate( templateDir: string, templateVars: any, ): Promise { - const templateFilePaths = await recursiveReadDir(templateDir).catch( - (error) => { - throw new Error(`Failed to read template directory: ${error.message}`); - }, - ); + const templateFilePaths = await recursiveReadDir(templateDir).catch(error => { + throw new Error(`Failed to read template directory: ${error.message}`); + }); const templateFiles = new Array(); for (const templateFile of templateFilePaths) { diff --git a/packages/cli/src/commands/plugin/diff/types.ts b/packages/cli/src/lib/diff/types.ts similarity index 100% rename from packages/cli/src/commands/plugin/diff/types.ts rename to packages/cli/src/lib/diff/types.ts From 12c669e904f7686845bd21d8d6866b03f8d1b0b8 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 21 May 2020 18:57:33 +0200 Subject: [PATCH 3/6] packages/cli: split diff template read into two steps and tweak handlers api --- packages/cli/src/commands/plugin/diff.ts | 4 +- packages/cli/src/lib/diff/handlers.ts | 82 +++++++++++------------- packages/cli/src/lib/diff/read.ts | 81 ++++++++++++++++------- packages/cli/src/lib/diff/types.ts | 33 ++++------ 4 files changed, 107 insertions(+), 93 deletions(-) diff --git a/packages/cli/src/commands/plugin/diff.ts b/packages/cli/src/commands/plugin/diff.ts index 542b94b0ff..ba3e99527f 100644 --- a/packages/cli/src/commands/plugin/diff.ts +++ b/packages/cli/src/commands/plugin/diff.ts @@ -16,7 +16,7 @@ import { Command } from 'commander'; import { - readTemplateFiles, + diffTemplateFiles, handlers, handleAllFiles, inquirerPromptFunc, @@ -54,7 +54,7 @@ export default async (cmd: Command) => { promptFunc = yesPromptFunc; } - const templateFiles = await readTemplateFiles('default-plugin'); + const templateFiles = await diffTemplateFiles('default-plugin'); await handleAllFiles(fileHandlers, templateFiles, promptFunc); await finalize(); }; diff --git a/packages/cli/src/lib/diff/handlers.ts b/packages/cli/src/lib/diff/handlers.ts index 643c018ddc..9e46dd92cf 100644 --- a/packages/cli/src/lib/diff/handlers.ts +++ b/packages/cli/src/lib/diff/handlers.ts @@ -14,36 +14,30 @@ * limitations under the License. */ -import fs from 'fs-extra'; import chalk from 'chalk'; -import { dirname } from 'path'; import { diffLines } from 'diff'; -import { paths } from '../paths'; -import { TemplateFile, PromptFunc, FileHandler } from './types'; - -export async function writeTargetFile(targetPath: string, contents: string) { - const path = paths.resolveTarget(targetPath); - await fs.ensureDir(dirname(path)); - await fs.writeFile(path, contents, 'utf8'); -} +import { FileDiff, PromptFunc, FileHandler, WriteFileFunc } from './types'; class PackageJsonHandler { - static async handler(file: TemplateFile, prompt: PromptFunc) { + static async handler( + { path, write, missing, targetContents, templateContents }: FileDiff, + prompt: PromptFunc, + ) { console.log('Checking package.json'); - if (!file.targetExists) { - throw new Error(`${file.targetPath} doesn't exist`); + if (missing) { + throw new Error(`${path} doesn't exist`); } - const pkg = JSON.parse(file.templateContents); - const targetPkg = JSON.parse(file.targetContents); + const pkg = JSON.parse(templateContents); + const targetPkg = JSON.parse(targetContents); - const handler = new PackageJsonHandler(file, prompt, pkg, targetPkg); + const handler = new PackageJsonHandler(write, prompt, pkg, targetPkg); await handler.handle(); } constructor( - private readonly file: TemplateFile, + private readonly writeFunc: WriteFileFunc, private readonly prompt: PromptFunc, private readonly pkg: any, private readonly targetPkg: any, @@ -144,31 +138,29 @@ class PackageJsonHandler { } private async write() { - await fs.writeFile( - paths.resolveTarget(this.file.targetPath), - `${JSON.stringify(this.targetPkg, null, 2)}\n`, - ); + await this.writeFunc(`${JSON.stringify(this.targetPkg, null, 2)}\n`); } } // Make sure the file is an exact match of the template -async function exactMatchHandler(file: TemplateFile, prompt: PromptFunc) { - console.log(`Checking ${file.targetPath}`); +async function exactMatchHandler( + { path, write, missing, targetContents, templateContents }: FileDiff, + prompt: PromptFunc, +) { + console.log(`Checking ${path}`); + const coloredPath = chalk.cyan(path); - const { targetPath, templateContents } = file; - const coloredPath = chalk.cyan(targetPath); - - if (!file.targetExists) { + if (missing) { if (await prompt(`Missing ${coloredPath}, do you want to add it?`)) { - await writeTargetFile(targetPath, templateContents); + await write(templateContents); } return; } - if (file.targetContents === templateContents) { + if (targetContents === templateContents) { return; } - const diffs = diffLines(file.targetContents, templateContents); + const diffs = diffLines(targetContents, templateContents); for (const diff of diffs) { if (diff.added) { process.stdout.write(chalk.green(`+${diff.value}`)); @@ -184,27 +176,29 @@ async function exactMatchHandler(file: TemplateFile, prompt: PromptFunc) { `Outdated ${coloredPath}, do you want to apply the above patch?`, ) ) { - await writeTargetFile(targetPath, templateContents); + await write(templateContents); } } // Adds the file if it is missing, but doesn't check existing files -async function existsHandler(file: TemplateFile, prompt: PromptFunc) { - console.log(`Making sure ${file.targetPath} exists`); +async function existsHandler( + { path, write, missing, templateContents }: FileDiff, + prompt: PromptFunc, +) { + console.log(`Making sure ${path} exists`); - const { targetPath, templateContents } = file; - const coloredPath = chalk.cyan(targetPath); + const coloredPath = chalk.cyan(path); - if (!file.targetExists) { + if (missing) { if (await prompt(`Missing ${coloredPath}, do you want to add it?`)) { - await writeTargetFile(targetPath, templateContents); + await write(templateContents); } return; } } -async function skipHandler(file: TemplateFile) { - console.log(`Skipping ${file.targetPath}`); +async function skipHandler({ path }: FileDiff) { + console.log(`Skipping ${path}`); } export const handlers = { @@ -216,22 +210,20 @@ export const handlers = { export async function handleAllFiles( fileHandlers: FileHandler[], - files: TemplateFile[], + files: FileDiff[], promptFunc: PromptFunc, ) { for (const file of files) { - const { targetPath } = file; + const { path } = file; const fileHandler = fileHandlers.find(handler => handler.patterns.some(pattern => - typeof pattern === 'string' - ? pattern === targetPath - : pattern.test(targetPath), + typeof pattern === 'string' ? pattern === path : pattern.test(path), ), ); if (fileHandler) { await fileHandler.handler(file, promptFunc); } else { - throw new Error(`No template file handler found for ${targetPath}`); + throw new Error(`No template file handler found for ${path}`); } } } diff --git a/packages/cli/src/lib/diff/read.ts b/packages/cli/src/lib/diff/read.ts index b408da94e9..a0bb6364ca 100644 --- a/packages/cli/src/lib/diff/read.ts +++ b/packages/cli/src/lib/diff/read.ts @@ -15,15 +15,29 @@ */ import fs from 'fs-extra'; -import { relative as relativePath } from 'path'; +import { + dirname, + resolve as resolvePath, + relative as relativePath, +} from 'path'; import handlebars from 'handlebars'; import recursiveReadDir from 'recursive-readdir'; import { paths } from '../paths'; import { version } from '../version'; -import { PluginInfo, TemplateFile } from './types'; +import { FileDiff } from './types'; + +export type PluginInfo = { + id: string; + name: string; +}; + +export type TemplatedFile = { + path: string; + contents: string; +}; // Reads info from the existing plugin -export async function readPluginInfo(): Promise { +async function readPluginInfo(): Promise { let name: string; try { const pkg = require(paths.resolveTarget('package.json')); @@ -47,7 +61,7 @@ export async function readPluginInfo(): Promise { return { id, name }; } -export async function readTemplateFile( +async function readTemplateFile( templateFile: string, templateVars: any, ): Promise { @@ -60,51 +74,70 @@ export async function readTemplateFile( return handlebars.compile(contents)(templateVars); } -export async function readTemplate( +async function readTemplate( templateDir: string, templateVars: any, -): Promise { +): Promise { const templateFilePaths = await recursiveReadDir(templateDir).catch(error => { throw new Error(`Failed to read template directory: ${error.message}`); }); - const templateFiles = new Array(); + const templatedFiles = new Array(); for (const templateFile of templateFilePaths) { - // Target file inside the target dir without template extension - const targetFile = templateFile - .replace(templateDir, paths.targetDir) - .replace(/\.hbs$/, ''); - const targetPath = relativePath(paths.targetDir, targetFile); + const path = relativePath(templateDir, templateFile).replace(/\.hbs$/, ''); + const contents = await readTemplateFile(templateFile, templateVars); - const templateContents = await readTemplateFile(templateFile, templateVars); + templatedFiles.push({ path, contents }); + } + + return templatedFiles; +} + +async function diffTemplatedFiles( + targetDir: string, + templatedFiles: TemplatedFile[], +): Promise { + const fileDiffs = new Array(); + for (const { path, contents: templateContents } of templatedFiles) { + const targetPath = resolvePath(targetDir, path); + const targetExists = await fs.pathExists(targetPath); + + const write = async (contents: string) => { + await fs.ensureDir(dirname(targetPath)); + await fs.writeFile(targetPath, contents, 'utf8'); + }; - const targetExists = await fs.pathExists(targetFile); if (targetExists) { - const targetContents = await fs.readFile(targetFile, 'utf8'); - templateFiles.push({ - targetPath, - targetExists, + const targetContents = await fs.readFile(targetPath, 'utf8'); + fileDiffs.push({ + path, + write, + missing: false, targetContents, templateContents, }); } else { - templateFiles.push({ - targetPath, - targetExists, + fileDiffs.push({ + path, + write, + missing: true, + targetContents: '', templateContents, }); } } - return templateFiles; + return fileDiffs; } // Read all template files for a given template, along with all matching files in the target dir -export async function readTemplateFiles(template: string) { +export async function diffTemplateFiles(template: string) { const pluginInfo = await readPluginInfo(); const templateVars = { version, ...pluginInfo }; const templateDir = paths.resolveOwn('templates', template); - return await readTemplate(templateDir, templateVars); + const templatedFiles = await readTemplate(templateDir, templateVars); + const fileDiffs = await diffTemplatedFiles(paths.targetDir, templatedFiles); + return fileDiffs; } diff --git a/packages/cli/src/lib/diff/types.ts b/packages/cli/src/lib/diff/types.ts index 7a04cf8d2e..6c50d9fcf9 100644 --- a/packages/cli/src/lib/diff/types.ts +++ b/packages/cli/src/lib/diff/types.ts @@ -14,35 +14,24 @@ * limitations under the License. */ -export type PluginInfo = { - id: string; - name: string; -}; +export type WriteFileFunc = (contents: string) => Promise; -export type TemplateFile = { +export type FileDiff = { // Relative path within the target directory - targetPath: string; + path: string; + // Wether the target file exists in the target directory. + missing: boolean; + // Contents of the file in the target directory, or an empty string if the file is missing. + targetContents: string; // Contents of the compiled template file templateContents: string; -} & ( - | { - // Whether the template file exists in the target directory - targetExists: true; - // Contents of the file in the target directory, if it exists - targetContents: string; - } - | { - // Whether the template file exists in the target directory - targetExists: false; - } -); + // Write new contents to the target file + write: WriteFileFunc; +}; export type PromptFunc = (msg: string) => Promise; -export type HandlerFunc = ( - file: TemplateFile, - prompt: PromptFunc, -) => Promise; +export type HandlerFunc = (file: FileDiff, prompt: PromptFunc) => Promise; export type FileHandler = { patterns: Array; From f0107283f856441e8fae0111d975c3fdd671e6e1 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 21 May 2020 19:31:14 +0200 Subject: [PATCH 4/6] packages/cli: move reading of plugin template data to plugin diff --- packages/cli/src/commands/plugin/diff.ts | 39 +++++++++++++++++++++++- packages/cli/src/lib/diff/read.ts | 38 ++--------------------- 2 files changed, 40 insertions(+), 37 deletions(-) diff --git a/packages/cli/src/commands/plugin/diff.ts b/packages/cli/src/commands/plugin/diff.ts index ba3e99527f..66db692ab4 100644 --- a/packages/cli/src/commands/plugin/diff.ts +++ b/packages/cli/src/commands/plugin/diff.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import fs from 'fs-extra'; import { Command } from 'commander'; import { diffTemplateFiles, @@ -23,6 +24,13 @@ import { makeCheckPromptFunc, yesPromptFunc, } from '../../lib/diff'; +import { paths } from '../../lib/paths'; +import { version } from '../../lib/version'; + +export type PluginData = { + id: string; + name: string; +}; const fileHandlers = [ { @@ -54,7 +62,36 @@ export default async (cmd: Command) => { promptFunc = yesPromptFunc; } - const templateFiles = await diffTemplateFiles('default-plugin'); + const data = await readPluginData(); + const templateFiles = await diffTemplateFiles('default-plugin', { + version, + ...data, + }); await handleAllFiles(fileHandlers, templateFiles, promptFunc); await finalize(); }; + +// Reads templating data from the existing plugin +async function readPluginData(): Promise { + let name: string; + try { + const pkg = require(paths.resolveTarget('package.json')); + name = pkg.name; + } catch (error) { + throw new Error(`Failed to read target package, ${error}`); + } + + const pluginTsContents = await fs.readFile( + paths.resolveTarget('src/plugin.ts'), + 'utf8', + ); + // TODO: replace with some proper parsing logic or plugin metadata file + const pluginIdMatch = pluginTsContents.match(/id: ['"`](.+?)['"`]/); + if (!pluginIdMatch) { + throw new Error(`Failed to parse plugin.ts, no plugin ID found`); + } + + const id = pluginIdMatch[1]; + + return { id, name }; +} diff --git a/packages/cli/src/lib/diff/read.ts b/packages/cli/src/lib/diff/read.ts index a0bb6364ca..75e1b86d24 100644 --- a/packages/cli/src/lib/diff/read.ts +++ b/packages/cli/src/lib/diff/read.ts @@ -23,44 +23,13 @@ import { import handlebars from 'handlebars'; import recursiveReadDir from 'recursive-readdir'; import { paths } from '../paths'; -import { version } from '../version'; import { FileDiff } from './types'; -export type PluginInfo = { - id: string; - name: string; -}; - export type TemplatedFile = { path: string; contents: string; }; -// Reads info from the existing plugin -async function readPluginInfo(): Promise { - let name: string; - try { - const pkg = require(paths.resolveTarget('package.json')); - name = pkg.name; - } catch (error) { - throw new Error(`Failed to read target package, ${error}`); - } - - const pluginTsContents = await fs.readFile( - paths.resolveTarget('src/plugin.ts'), - 'utf8', - ); - // TODO: replace with some proper parsing logic or plugin metadata file - const pluginIdMatch = pluginTsContents.match(/id: ['"`](.+?)['"`]/); - if (!pluginIdMatch) { - throw new Error(`Failed to parse plugin.ts, no plugin ID found`); - } - - const id = pluginIdMatch[1]; - - return { id, name }; -} - async function readTemplateFile( templateFile: string, templateVars: any, @@ -131,13 +100,10 @@ async function diffTemplatedFiles( } // Read all template files for a given template, along with all matching files in the target dir -export async function diffTemplateFiles(template: string) { - const pluginInfo = await readPluginInfo(); - const templateVars = { version, ...pluginInfo }; - +export async function diffTemplateFiles(template: string, templateData: any) { const templateDir = paths.resolveOwn('templates', template); - const templatedFiles = await readTemplate(templateDir, templateVars); + const templatedFiles = await readTemplate(templateDir, templateData); const fileDiffs = await diffTemplatedFiles(paths.targetDir, templatedFiles); return fileDiffs; } From f38ae61f5ba3f5ecc5f8f0daa9834b925ef608cc Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 21 May 2020 19:42:46 +0200 Subject: [PATCH 5/6] packages/cli: added app:diff + fixes to template and diff lib --- packages/cli/src/commands/app/diff.ts | 74 +++++++++++++++++++ packages/cli/src/index.ts | 9 ++- packages/cli/src/lib/diff/handlers.ts | 31 +++++++- .../default-app/packages/app/tsconfig.json | 20 ----- .../default-app/plugins/welcome/tsconfig.json | 5 -- .../templates/default-app/prettier.config.js | 1 - 6 files changed, 111 insertions(+), 29 deletions(-) create mode 100644 packages/cli/src/commands/app/diff.ts delete mode 100644 packages/cli/templates/default-app/packages/app/tsconfig.json delete mode 100644 packages/cli/templates/default-app/plugins/welcome/tsconfig.json delete mode 100644 packages/cli/templates/default-app/prettier.config.js diff --git a/packages/cli/src/commands/app/diff.ts b/packages/cli/src/commands/app/diff.ts new file mode 100644 index 0000000000..a667407f23 --- /dev/null +++ b/packages/cli/src/commands/app/diff.ts @@ -0,0 +1,74 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Command } from 'commander'; +import { + diffTemplateFiles, + handlers, + handleAllFiles, + inquirerPromptFunc, + makeCheckPromptFunc, + yesPromptFunc, +} from '../../lib/diff'; +import { version } from '../../lib/version'; + +const fileHandlers = [ + { + patterns: ['packages/app/package.json'], + handler: handlers.appPackageJson, + }, + { + patterns: [/tsconfig\.json$/], + handler: handlers.exactMatch, + }, + { + patterns: [ + /README\.md$/, + /\.eslintrc\.js$/, + // make sure files in 1st level of src/ and dev/ exist + /^packages\/app\/(src|dev)\/[^/]+$/, + ], + handler: handlers.exists, + }, + { + patterns: [ + 'lerna.json', + /^src\//, + /^patches\//, + /^packages\/app\/public\//, + /^packages\/app\/cypress/, + // Let plugin:diff take care of the plugins + /^plugins/, + /package\.json$/, + ], + handler: handlers.skip, + }, +]; + +export default async (cmd: Command) => { + let promptFunc = inquirerPromptFunc; + let finalize = () => {}; + + if (cmd.check) { + [promptFunc, finalize] = makeCheckPromptFunc(); + } else if (cmd.yes) { + promptFunc = yesPromptFunc; + } + + const templateFiles = await diffTemplateFiles('default-app', { version }); + await handleAllFiles(fileHandlers, templateFiles, promptFunc); + await finalize(); +}; diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index f02bf6524f..bdd0d3e1f2 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -39,6 +39,13 @@ const main = (argv: string[]) => { .option('--check', 'Enable type checking and linting') .action(actionHandler(() => require('./commands/app/serve'))); + program + .command('app:diff') + .option('--check', 'Fail if changes are required') + .option('--yes', 'Apply all changes') + .description('Diff an existing app with the creation template') + .action(actionHandler(() => require('./commands/app/diff'))); + program .command('create-plugin') .description('Creates a new plugin in the current repository') @@ -157,7 +164,7 @@ function actionHandler( }; } -process.on('unhandledRejection', (rejection) => { +process.on('unhandledRejection', rejection => { if (rejection instanceof Error) { exitWithError(rejection); } else { diff --git a/packages/cli/src/lib/diff/handlers.ts b/packages/cli/src/lib/diff/handlers.ts index 9e46dd92cf..c4efacb198 100644 --- a/packages/cli/src/lib/diff/handlers.ts +++ b/packages/cli/src/lib/diff/handlers.ts @@ -22,6 +22,7 @@ class PackageJsonHandler { static async handler( { path, write, missing, targetContents, templateContents }: FileDiff, prompt: PromptFunc, + variant?: string, ) { console.log('Checking package.json'); @@ -32,19 +33,33 @@ class PackageJsonHandler { const pkg = JSON.parse(templateContents); const targetPkg = JSON.parse(targetContents); - const handler = new PackageJsonHandler(write, prompt, pkg, targetPkg); + const handler = new PackageJsonHandler( + write, + prompt, + pkg, + targetPkg, + variant, + ); await handler.handle(); } + static async appHandler(file: FileDiff, prompt: PromptFunc) { + return PackageJsonHandler.handler(file, prompt, 'app'); + } + constructor( private readonly writeFunc: WriteFileFunc, private readonly prompt: PromptFunc, private readonly pkg: any, private readonly targetPkg: any, + private readonly variant?: string, ) {} async handle() { await this.syncField('main'); + if (this.variant !== 'app') { + await this.syncField('main:src'); + } await this.syncField('types'); await this.syncField('files'); await this.syncScripts(); @@ -78,7 +93,7 @@ class PackageJsonHandler { targetObj[fieldName] = newValue; await this.write(); } - } else { + } else if (fieldName in obj) { if ( await this.prompt( `package.json is missing field ${fullFieldName}, set to ${coloredNewValue}?`, @@ -95,6 +110,10 @@ class PackageJsonHandler { const targetScripts = (this.targetPkg.scripts = this.targetPkg.scripts || {}); + if (!pkgScripts) { + return; + } + for (const key of Object.keys(pkgScripts)) { await this.syncField(key, pkgScripts, targetScripts, 'scripts'); } @@ -132,7 +151,14 @@ class PackageJsonHandler { const targetDeps = (this.targetPkg[fieldName] = this.targetPkg[fieldName] || {}); + if (!pkgDeps) { + return; + } + for (const key of Object.keys(pkgDeps)) { + if (this.variant === 'app' && key.startsWith('plugin-')) { + continue; + } await this.syncField(key, pkgDeps, targetDeps, fieldName); } } @@ -206,6 +232,7 @@ export const handlers = { exists: existsHandler, exactMatch: exactMatchHandler, packageJson: PackageJsonHandler.handler, + appPackageJson: PackageJsonHandler.appHandler, }; export async function handleAllFiles( diff --git a/packages/cli/templates/default-app/packages/app/tsconfig.json b/packages/cli/templates/default-app/packages/app/tsconfig.json deleted file mode 100644 index 0457810645..0000000000 --- a/packages/cli/templates/default-app/packages/app/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extends": "@spotify/web-scripts/config/tsconfig.json", - "compilerOptions": { - "target": "es5", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "react", - "incremental": false - }, - "include": ["src"] -} diff --git a/packages/cli/templates/default-app/plugins/welcome/tsconfig.json b/packages/cli/templates/default-app/plugins/welcome/tsconfig.json deleted file mode 100644 index 5a3931ffce..0000000000 --- a/packages/cli/templates/default-app/plugins/welcome/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "include": ["src"], - "compilerOptions": {} -} diff --git a/packages/cli/templates/default-app/prettier.config.js b/packages/cli/templates/default-app/prettier.config.js deleted file mode 100644 index 93df970dd6..0000000000 --- a/packages/cli/templates/default-app/prettier.config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('@spotify/web-scripts/config/prettier.config.js'); From 6c2a9d471ae67220c89e85e674108a99950f0175 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 21 May 2020 19:45:12 +0200 Subject: [PATCH 6/6] packages/cli: update prettier config in plugin template --- packages/cli/templates/default-app/package.json.hbs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/cli/templates/default-app/package.json.hbs b/packages/cli/templates/default-app/package.json.hbs index 589e20e298..4fda7ff2d7 100644 --- a/packages/cli/templates/default-app/package.json.hbs +++ b/packages/cli/templates/default-app/package.json.hbs @@ -28,8 +28,19 @@ }, "devDependencies": { "@backstage/cli": "^{{version}}", + "@spotify/prettier-config": "^7.0.0", "lerna": "^3.20.2", "patch-package": "^6.2.2", "prettier": "^1.19.1" + }, + "prettier": "@spotify/prettier-config", + "lint-staged": { + "*.{js,jsx,ts,tsx}": [ + "eslint --fix", + "prettier --write" + ], + "*.{json,md}": [ + "prettier --write" + ] } }