UNPKG

13.3 kBJavaScriptView Raw
1'use strict';
2
3var fs = require('fs-extra');
4var chalk = require('chalk');
5var diff$1 = require('diff');
6var path = require('path');
7var inquirer = require('inquirer');
8var handlebars = require('handlebars');
9var recursive = require('recursive-readdir');
10var index = require('./index-09611511.cjs.js');
11require('commander');
12require('semver');
13require('@backstage/cli-common');
14require('@backstage/errors');
15
16function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
17
18var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
19var chalk__default = /*#__PURE__*/_interopDefaultLegacy(chalk);
20var inquirer__default = /*#__PURE__*/_interopDefaultLegacy(inquirer);
21var handlebars__default = /*#__PURE__*/_interopDefaultLegacy(handlebars);
22var recursive__default = /*#__PURE__*/_interopDefaultLegacy(recursive);
23
24function sortObjectKeys(obj) {
25 const sortedKeys = Object.keys(obj).sort();
26 for (const key of sortedKeys) {
27 const value = obj[key];
28 delete obj[key];
29 obj[key] = value;
30 }
31}
32class PackageJsonHandler {
33 constructor(writeFunc, prompt, pkg, targetPkg, variant) {
34 this.writeFunc = writeFunc;
35 this.prompt = prompt;
36 this.pkg = pkg;
37 this.targetPkg = targetPkg;
38 this.variant = variant;
39 }
40 static async handler({ path, write, missing, targetContents, templateContents }, prompt, variant) {
41 console.log("Checking package.json");
42 if (missing) {
43 throw new Error(`${path} doesn't exist`);
44 }
45 const pkg = JSON.parse(templateContents);
46 const targetPkg = JSON.parse(targetContents);
47 const handler = new PackageJsonHandler(write, prompt, pkg, targetPkg, variant);
48 await handler.handle();
49 }
50 static async appHandler(file, prompt) {
51 return PackageJsonHandler.handler(file, prompt, "app");
52 }
53 async handle() {
54 await this.syncField("main");
55 if (this.variant !== "app") {
56 await this.syncField("main:src");
57 }
58 await this.syncField("types");
59 await this.syncFiles();
60 await this.syncScripts();
61 await this.syncPublishConfig();
62 await this.syncDependencies("dependencies");
63 await this.syncDependencies("peerDependencies", true);
64 await this.syncDependencies("devDependencies");
65 await this.syncReactDeps();
66 }
67 async syncField(fieldName, obj = this.pkg, targetObj = this.targetPkg, prefix, sort, optional) {
68 const fullFieldName = chalk__default["default"].cyan(prefix ? `${prefix}[${fieldName}]` : fieldName);
69 const newValue = obj[fieldName];
70 const coloredNewValue = chalk__default["default"].cyan(JSON.stringify(newValue));
71 if (fieldName in targetObj) {
72 const oldValue = targetObj[fieldName];
73 if (JSON.stringify(oldValue) === JSON.stringify(newValue)) {
74 return;
75 }
76 const coloredOldValue = chalk__default["default"].cyan(JSON.stringify(oldValue));
77 const msg = `package.json has mismatched field, ${fullFieldName}, change from ${coloredOldValue} to ${coloredNewValue}?`;
78 if (await this.prompt(msg)) {
79 targetObj[fieldName] = newValue;
80 if (sort) {
81 sortObjectKeys(targetObj);
82 }
83 await this.write();
84 }
85 } else if (fieldName in obj && optional !== true) {
86 if (await this.prompt(`package.json is missing field ${fullFieldName}, set to ${coloredNewValue}?`)) {
87 targetObj[fieldName] = newValue;
88 if (sort) {
89 sortObjectKeys(targetObj);
90 }
91 await this.write();
92 }
93 }
94 }
95 async syncFiles() {
96 const { configSchema } = this.targetPkg;
97 const hasSchemaFile = typeof configSchema === "string";
98 if (!this.targetPkg.files) {
99 const expected = hasSchemaFile ? ["dist", configSchema] : ["dist"];
100 if (await this.prompt(`package.json is missing field "files", set to ${JSON.stringify(expected)}?`)) {
101 this.targetPkg.files = expected;
102 await this.write();
103 }
104 } else {
105 const missing = [];
106 if (!this.targetPkg.files.includes("dist")) {
107 missing.push("dist");
108 }
109 if (hasSchemaFile && !this.targetPkg.files.includes(configSchema)) {
110 missing.push(configSchema);
111 }
112 if (missing.length) {
113 if (await this.prompt(`package.json is missing ${JSON.stringify(missing)} in the "files" field, add?`)) {
114 this.targetPkg.files.push(...missing);
115 await this.write();
116 }
117 }
118 }
119 }
120 async syncScripts() {
121 const pkgScripts = this.pkg.scripts;
122 const targetScripts = this.targetPkg.scripts = this.targetPkg.scripts || {};
123 if (!pkgScripts) {
124 return;
125 }
126 const hasNewScript = Object.values(targetScripts).some((script) => String(script).includes("backstage-cli package "));
127 if (hasNewScript) {
128 return;
129 }
130 for (const key of Object.keys(pkgScripts)) {
131 await this.syncField(key, pkgScripts, targetScripts, "scripts");
132 }
133 }
134 async syncPublishConfig() {
135 const pkgPublishConf = this.pkg.publishConfig;
136 const targetPublishConf = this.targetPkg.publishConfig;
137 if (!pkgPublishConf) {
138 return;
139 }
140 if (!targetPublishConf) {
141 if (await this.prompt("Missing publishConfig, do you want to add it?")) {
142 this.targetPkg.publishConfig = pkgPublishConf;
143 await this.write();
144 }
145 return;
146 }
147 for (const key of Object.keys(pkgPublishConf)) {
148 if (!["access", "registry"].includes(key)) {
149 await this.syncField(key, pkgPublishConf, targetPublishConf, "publishConfig");
150 }
151 }
152 }
153 async syncDependencies(fieldName, required = false) {
154 const pkgDeps = this.pkg[fieldName];
155 const targetDeps = this.targetPkg[fieldName] = this.targetPkg[fieldName] || {};
156 if (!pkgDeps && !required) {
157 return;
158 }
159 await this.syncField("@backstage/core", {}, targetDeps, fieldName, true);
160 await this.syncField("@backstage/core-api", {}, targetDeps, fieldName, true);
161 for (const key of Object.keys(pkgDeps)) {
162 if (this.variant === "app" && key.startsWith("plugin-")) {
163 continue;
164 }
165 await this.syncField(key, pkgDeps, targetDeps, fieldName, true, !required);
166 }
167 }
168 async syncReactDeps() {
169 const targetDeps = this.targetPkg.dependencies = this.targetPkg.dependencies || {};
170 await this.syncField("react", {}, targetDeps, "dependencies");
171 await this.syncField("react-dom", {}, targetDeps, "dependencies");
172 }
173 async write() {
174 await this.writeFunc(`${JSON.stringify(this.targetPkg, null, 2)}
175`);
176 }
177}
178async function exactMatchHandler({ path, write, missing, targetContents, templateContents }, prompt) {
179 console.log(`Checking ${path}`);
180 const coloredPath = chalk__default["default"].cyan(path);
181 if (missing) {
182 if (await prompt(`Missing ${coloredPath}, do you want to add it?`)) {
183 await write(templateContents);
184 }
185 return;
186 }
187 if (targetContents === templateContents) {
188 return;
189 }
190 const diffs = diff$1.diffLines(targetContents, templateContents);
191 for (const diff of diffs) {
192 if (diff.added) {
193 process.stdout.write(chalk__default["default"].green(`+${diff.value}`));
194 } else if (diff.removed) {
195 process.stdout.write(chalk__default["default"].red(`-${diff.value}`));
196 } else {
197 process.stdout.write(` ${diff.value}`);
198 }
199 }
200 if (await prompt(`Outdated ${coloredPath}, do you want to apply the above patch?`)) {
201 await write(templateContents);
202 }
203}
204async function existsHandler({ path, write, missing, templateContents }, prompt) {
205 console.log(`Making sure ${path} exists`);
206 const coloredPath = chalk__default["default"].cyan(path);
207 if (missing) {
208 if (await prompt(`Missing ${coloredPath}, do you want to add it?`)) {
209 await write(templateContents);
210 }
211 return;
212 }
213}
214async function skipHandler({ path }) {
215 console.log(`Skipping ${path}`);
216}
217const handlers = {
218 skip: skipHandler,
219 exists: existsHandler,
220 exactMatch: exactMatchHandler,
221 packageJson: PackageJsonHandler.handler,
222 appPackageJson: PackageJsonHandler.appHandler
223};
224async function handleAllFiles(fileHandlers, files, promptFunc) {
225 for (const file of files) {
226 const path$1 = file.path.split(path.sep).join(path.posix.sep);
227 const fileHandler = fileHandlers.find((handler) => handler.patterns.some((pattern) => typeof pattern === "string" ? pattern === path$1 : pattern.test(path$1)));
228 if (fileHandler) {
229 await fileHandler.handler(file, promptFunc);
230 } else {
231 throw new Error(`No template file handler found for ${path$1}`);
232 }
233 }
234}
235
236const inquirerPromptFunc = async (msg) => {
237 const { result } = await inquirer__default["default"].prompt({
238 type: "confirm",
239 name: "result",
240 message: chalk__default["default"].blue(msg)
241 });
242 return result;
243};
244const makeCheckPromptFunc = () => {
245 let failed = false;
246 const promptFunc = async (msg) => {
247 failed = true;
248 console.log(chalk__default["default"].red(`[Check Failed] ${msg}`));
249 return false;
250 };
251 const finalize = () => {
252 if (failed) {
253 throw new Error("Check failed, the plugin is not in sync with the latest template");
254 }
255 };
256 return [promptFunc, finalize];
257};
258const yesPromptFunc = async (msg) => {
259 console.log(`Accepting: "${msg}"`);
260 return true;
261};
262
263async function readTemplateFile(templateFile, templateVars) {
264 const contents = await fs__default["default"].readFile(templateFile, "utf8");
265 if (!templateFile.endsWith(".hbs")) {
266 return contents;
267 }
268 const packageVersionProvider = index.createPackageVersionProvider(void 0);
269 return handlebars__default["default"].compile(contents)(templateVars, {
270 helpers: {
271 versionQuery(name, hint) {
272 return packageVersionProvider(name, typeof hint === "string" ? hint : void 0);
273 }
274 }
275 });
276}
277async function readTemplate(templateDir, templateVars) {
278 const templateFilePaths = await recursive__default["default"](templateDir).catch((error) => {
279 throw new Error(`Failed to read template directory: ${error.message}`);
280 });
281 const templatedFiles = new Array();
282 for (const templateFile of templateFilePaths) {
283 const path$1 = path.relative(templateDir, templateFile).replace(/\.hbs$/, "");
284 const contents = await readTemplateFile(templateFile, templateVars);
285 templatedFiles.push({ path: path$1, contents });
286 }
287 return templatedFiles;
288}
289async function diffTemplatedFiles(targetDir, templatedFiles) {
290 const fileDiffs = new Array();
291 for (const { path: path$1, contents: templateContents } of templatedFiles) {
292 const targetPath = path.resolve(targetDir, path$1);
293 const targetExists = await fs__default["default"].pathExists(targetPath);
294 const write = async (contents) => {
295 await fs__default["default"].ensureDir(path.dirname(targetPath));
296 await fs__default["default"].writeFile(targetPath, contents, "utf8");
297 };
298 if (targetExists) {
299 const targetContents = await fs__default["default"].readFile(targetPath, "utf8");
300 fileDiffs.push({
301 path: path$1,
302 write,
303 missing: false,
304 targetContents,
305 templateContents
306 });
307 } else {
308 fileDiffs.push({
309 path: path$1,
310 write,
311 missing: true,
312 targetContents: "",
313 templateContents
314 });
315 }
316 }
317 return fileDiffs;
318}
319async function diffTemplateFiles(template, templateData) {
320 const templateDir = index.paths.resolveOwn("templates", template);
321 const templatedFiles = await readTemplate(templateDir, templateData);
322 const fileDiffs = await diffTemplatedFiles(index.paths.targetDir, templatedFiles);
323 return fileDiffs;
324}
325
326const fileHandlers = [
327 {
328 patterns: ["package.json"],
329 handler: handlers.packageJson
330 },
331 {
332 patterns: [".eslintrc.js"],
333 handler: handlers.exists
334 },
335 {
336 patterns: ["README.md", "tsconfig.json", /^src\//, /^dev\//],
337 handler: handlers.skip
338 }
339];
340var diff = async (opts) => {
341 let promptFunc = inquirerPromptFunc;
342 let finalize = () => {
343 };
344 if (opts.check) {
345 [promptFunc, finalize] = makeCheckPromptFunc();
346 } else if (opts.yes) {
347 promptFunc = yesPromptFunc;
348 }
349 const data = await readPluginData();
350 const templateFiles = await diffTemplateFiles("default-plugin", data);
351 await handleAllFiles(fileHandlers, templateFiles, promptFunc);
352 finalize();
353};
354async function readPluginData() {
355 let name;
356 let privatePackage;
357 let pluginVersion;
358 let npmRegistry;
359 try {
360 const pkg = require(index.paths.resolveTarget("package.json"));
361 name = pkg.name;
362 privatePackage = pkg.private;
363 pluginVersion = pkg.version;
364 const scope = name.split("/")[0];
365 if (`${scope}:registry` in pkg.publishConfig) {
366 const registryURL = pkg.publishConfig[`${scope}:registry`];
367 npmRegistry = `"${scope}:registry" : "${registryURL}"`;
368 } else
369 npmRegistry = "";
370 } catch (error) {
371 throw new Error(`Failed to read target package, ${error}`);
372 }
373 const pluginTsContents = await fs__default["default"].readFile(index.paths.resolveTarget("src/plugin.ts"), "utf8");
374 const pluginIdMatch = pluginTsContents.match(/id: ['"`](.+?)['"`]/);
375 if (!pluginIdMatch) {
376 throw new Error(`Failed to parse plugin.ts, no plugin ID found`);
377 }
378 const id = pluginIdMatch[1];
379 return { id, name, privatePackage, pluginVersion, npmRegistry };
380}
381
382exports["default"] = diff;
383//# sourceMappingURL=diff-d523fc6a.cjs.js.map