@bigbinary/neeto-audit-frontend 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/.github/CODEOWNERS +1 -0
  2. package/common/.husky/nano/pre-push +9 -0
  3. package/common/eslint/nano/.eslintrc.js +14 -0
  4. package/common/recommendedDependencies/common.js +110 -0
  5. package/common/recommendedDependencies/frontend.js +2 -101
  6. package/common/recommendedDependencies/index.js +4 -2
  7. package/common/recommendedDependencies/nano.js +11 -0
  8. package/dist/index.js +15173 -2524
  9. package/package.json +13 -6
  10. package/src/cli.js +4 -32
  11. package/src/constants/index.js +3 -0
  12. package/src/utils/index.js +121 -10
  13. package/src/verifiers/currentNodeVersion/index.js +3 -3
  14. package/src/verifiers/eslint/constants.js +0 -4
  15. package/src/verifiers/eslint/index.js +15 -7
  16. package/src/verifiers/husky/constants.js +4 -0
  17. package/src/verifiers/husky/index.js +29 -6
  18. package/src/verifiers/index.js +5 -6
  19. package/src/verifiers/prettier/index.js +3 -5
  20. package/src/verifiers/recommendedPackageVersions/constants.js +5 -5
  21. package/src/verifiers/recommendedPackageVersions/index.js +38 -14
  22. package/src/verifiers/recommendedPackageVersions/utils.js +21 -19
  23. /package/common/.husky/{_ → common/_}/husky.sh +0 -0
  24. /package/common/.husky/{helpers → common/helpers}/lint_staged.sh +0 -0
  25. /package/common/.husky/{helpers → common/helpers}/neeto_audit_frontend.sh +0 -0
  26. /package/common/.husky/{helpers → common/helpers}/prevent_conflict_markers.sh +0 -0
  27. /package/common/.husky/{helpers → common/helpers}/prevent_pushing_to_main.sh +0 -0
  28. /package/common/.husky/{pre-commit → common/pre-commit} +0 -0
  29. /package/common/.husky/{pre-push → frontend/pre-push} +0 -0
  30. /package/common/eslint/{.eslintrc.js → common/.eslintrc.js} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bigbinary/neeto-audit-frontend",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Audits neeto frontend codebase for issues and suggests a fix.",
5
5
  "type": "module",
6
6
  "bin": "./dist/index.js",
@@ -15,22 +15,29 @@
15
15
  "author": "Thejus Paul <thejuspaul@protonmail.ch>",
16
16
  "license": "UNLICENSED",
17
17
  "devDependencies": {
18
- "@rollup/plugin-commonjs": "25.0.2",
18
+ "@rollup/plugin-commonjs": "25.0.3",
19
19
  "@rollup/plugin-json": "6.0.0",
20
20
  "@rollup/plugin-node-resolve": "15.1.0",
21
- "rollup": "3.26.2",
21
+ "rollup": "3.26.3",
22
22
  "rollup-plugin-analyzer": "4.0.0",
23
23
  "rollup-plugin-cleaner": "1.0.0",
24
24
  "rollup-plugin-executable-script": "1.0.1",
25
25
  "rollup-plugin-peer-deps-external": "2.2.4"
26
26
  },
27
27
  "dependencies": {
28
+ "@bigbinary/neeto-commons-frontend": "2.0.101",
28
29
  "chalk": "5.3.0",
29
30
  "commander": "11.0.0",
30
- "ramda": "0.29.0"
31
+ "ora": "6.3.1",
32
+ "ramda": "0.29.0",
33
+ "simple-git": "3.19.1"
31
34
  },
35
+ "peerDependencies": {
36
+ "@bigbinary/neeto-commons-frontend": "^2.0.101",
37
+ "ramda": "^0.29.0"
38
+ },
39
+ "packageManager": "pnpm@8.6.9",
32
40
  "engines": {
33
- "node": ">=18.16",
34
- "pnpm": ">=8.6"
41
+ "node": ">=18"
35
42
  }
36
43
  }
package/src/cli.js CHANGED
@@ -1,38 +1,10 @@
1
- #!/usr/bin/env node
2
- import { renderErrors } from "./utils/index.js";
3
- import verifiers from "./verifiers/index.js";
4
- import chalk from "chalk";
5
- import { program } from "commander";
1
+ import { getCliOptions, runVerifiers } from "./utils";
2
+ import verifiers from "./verifiers";
6
3
 
7
4
  const run = async () => {
8
- program
9
- .usage("[OPTIONS]...")
10
- .option("-d, --debug", "Runs in debug mode")
11
- .option("-a, --auto-fix", "Automatically fixes the errors")
12
- .parse(process.argv);
5
+ const { autoFix, debug } = getCliOptions();
13
6
 
14
- const { autoFix, debug } = program.opts();
15
-
16
- let errors = [];
17
- const results = verifiers.map(async ([name, verifier]) => {
18
- const { isSuccess = false, error, fix } = await verifier(debug);
19
-
20
- !isSuccess && errors.push(error);
21
- error && autoFix && fix(debug);
22
-
23
- return { name, isSuccess };
24
- });
25
-
26
- const allResults = await Promise.all(results);
27
-
28
- allResults.map(({ name, isSuccess }) => {
29
- const resultText = isSuccess ? chalk.bgGreen("PASS") : chalk.bgRed("FAIL");
30
- console.log(resultText, name);
31
- });
32
-
33
- errors.length > 0 && renderErrors(errors, autoFix);
34
- autoFix && console.log(chalk.green("All issues have been fixed!"));
35
- console.log();
7
+ runVerifiers({ verifiers, autoFix, debug });
36
8
  };
37
9
 
38
10
  run();
@@ -0,0 +1,3 @@
1
+ import path from "path";
2
+
3
+ export const PACKAGE_JSON_PATH = path.resolve("./package.json");
@@ -1,10 +1,16 @@
1
+ import { isNotEmpty } from "@bigbinary/neeto-commons-frontend/pure";
1
2
  import chalk from "chalk";
3
+ import { exec } from "child_process";
4
+ import { program } from "commander";
2
5
  import { lstat, mkdir, readFile, writeFile } from "fs/promises";
3
- import path from "path";
4
6
  import { promises } from "fs";
7
+ import ora from "ora";
8
+ import path from "path";
9
+ import { identity, last, split } from "ramda";
10
+ import { simpleGit } from "simple-git";
5
11
  import util from "util";
6
- import { exec } from "child_process";
7
- import { identity } from "ramda";
12
+
13
+ import { PACKAGE_JSON_PATH } from "../constants";
8
14
 
9
15
  const run = util.promisify(exec);
10
16
 
@@ -14,6 +20,65 @@ export const execute = async (command, debug) => {
14
20
  debug && console.log("stderr:", stderr);
15
21
  };
16
22
 
23
+ const renderErrors = (errors = [], isAutoFixFlagPresent = false) => {
24
+ console.log(`\n${chalk.bgRed.black("ERRORS")}`);
25
+ console.log(chalk.red(` \u2022 ${errors.join("\n \u2022 ")}`));
26
+ if (!isAutoFixFlagPresent) {
27
+ console.log(
28
+ chalk.yellowBright(
29
+ "\nPlease run the following command to auto-fix the errors: "
30
+ ) + chalk.bgYellowBright.black(" yarn neeto-audit-frontend -a ")
31
+ );
32
+ process.exitCode = 1;
33
+ }
34
+ };
35
+
36
+ const getResults = async ({ verifiers, debug }) => {
37
+ let errors = [];
38
+ const results = verifiers.map(async ([name, verifier]) => {
39
+ const { isSuccess = false, error } = await verifier(debug);
40
+ !isSuccess && errors.push(error);
41
+
42
+ return { name, isSuccess };
43
+ });
44
+
45
+ const allResults = await Promise.all(results);
46
+
47
+ return { results: allResults, errors };
48
+ };
49
+
50
+ const runAutoFix = async ({ verifiers, autoFix, debug }) => {
51
+ const results = verifiers.map(async ([name, verifier]) => {
52
+ const spinner = ora({ text: name, spinner: "dots" });
53
+ const { error, fix } = await verifier(debug);
54
+
55
+ try {
56
+ spinner.start();
57
+ error && autoFix && (await fix(debug));
58
+ spinner.succeed(name);
59
+ return true;
60
+ } catch (errors) {
61
+ spinner.fail(name);
62
+ debug && console.log(errors);
63
+ return false;
64
+ }
65
+ });
66
+
67
+ const allResults = await Promise.all(results);
68
+
69
+ return allResults.every(identity);
70
+ };
71
+
72
+ export const getCliOptions = () => {
73
+ program
74
+ .usage("[OPTIONS]...")
75
+ .option("-d, --debug", "Runs in debug mode")
76
+ .option("-a, --auto-fix", "Automatically fixes the errors")
77
+ .parse(process.argv);
78
+
79
+ return program.opts();
80
+ };
81
+
17
82
  export const getFileContent = async ({
18
83
  relativeFilePath = "",
19
84
  debug = false,
@@ -48,13 +113,6 @@ export const createOrReplaceFile = async ({
48
113
  }
49
114
  };
50
115
 
51
- export const renderErrors = (errors = [], isAutoFixFlagPresent = false) => {
52
- console.log();
53
- console.log(chalk.bgRed("ERRORS"));
54
- console.log(chalk.red(` \u2022 ${errors.join("\n \u2022 ")}`));
55
- if (!isAutoFixFlagPresent) process.exitCode = 1;
56
- };
57
-
58
116
  export const identicalFiles = async ({
59
117
  sourceFile = "",
60
118
  destinationFile = "",
@@ -156,3 +214,56 @@ export const createOrReplaceDirectoryFiles = async ({
156
214
  return null;
157
215
  }
158
216
  };
217
+
218
+ export const runVerifiers = async ({ verifiers, autoFix, debug }) => {
219
+ const { results, errors } = await getResults({ verifiers, autoFix, debug });
220
+ const isErrorPresent = isNotEmpty(errors);
221
+
222
+ results.map(({ name, isSuccess }) => {
223
+ const resultText = isSuccess
224
+ ? chalk.bgGreen.black("PASS")
225
+ : chalk.bgRed.black("FAIL");
226
+ console.log(resultText, name);
227
+ });
228
+
229
+ isErrorPresent && renderErrors(errors, autoFix);
230
+
231
+ if (isErrorPresent && autoFix) {
232
+ console.log("\nRunning auto-fix:");
233
+ const result = await runAutoFix({ verifiers, autoFix, debug });
234
+ if (result) {
235
+ console.log("\n" + chalk.green("All issues have been fixed!"));
236
+ } else {
237
+ console.log(
238
+ "\n" +
239
+ chalk.red(
240
+ "We were not able to fix all issues. Please fix the issues manually."
241
+ )
242
+ );
243
+ process.exitCode = 1;
244
+ }
245
+ }
246
+ console.log();
247
+ };
248
+
249
+ export const getNanoType = async () => {
250
+ const git = simpleGit();
251
+ const { value: remoteOriginUrl } = await git.getConfig("remote.origin.url");
252
+
253
+ const projectName = remoteOriginUrl
254
+ .split("https://github.com/bigbinary/")
255
+ .at(1)
256
+ .split(".git")
257
+ .at(0);
258
+
259
+ return last(split("-", projectName));
260
+ };
261
+
262
+ export const getPackageJson = async (debug) => {
263
+ const packageJsonContent = await getFileContent({
264
+ relativeFilePath: PACKAGE_JSON_PATH,
265
+ debug,
266
+ });
267
+
268
+ return JSON.parse(packageJsonContent);
269
+ };
@@ -1,6 +1,6 @@
1
- import { createOrReplaceFile, getFileContent } from "../../utils/index.js";
2
- import { NVMRC_FILE_PATH, REQUIRED_NODE_VERSION } from "./constants.js";
3
- import { getSemVer } from "./utils.js";
1
+ import { NVMRC_FILE_PATH, REQUIRED_NODE_VERSION } from "./constants";
2
+ import { createOrReplaceFile, getFileContent } from "../../utils";
3
+ import { getSemVer } from "./utils";
4
4
 
5
5
  const currentNodeVersion = async (debug) => {
6
6
  const fix = async (debug) => {
@@ -1,7 +1,3 @@
1
1
  import path from "path";
2
2
 
3
- export const ESLINT_SOURCE_DIRECTORY = path.resolve(
4
- "./node_modules/@bigbinary/neeto-audit-frontend/common/eslint"
5
- );
6
-
7
3
  export const ESLINT_DESTINATION_DIRECTORY = path.resolve("./");
@@ -1,24 +1,32 @@
1
+ import path from "path";
1
2
  import { identity } from "ramda";
3
+
2
4
  import {
3
5
  createOrReplaceDirectoryFiles,
6
+ getNanoType,
4
7
  identicalDirectoryFiles,
5
- } from "../../utils/index.js";
6
- import {
7
- ESLINT_DESTINATION_DIRECTORY,
8
- ESLINT_SOURCE_DIRECTORY,
9
- } from "./constants.js";
8
+ } from "../../utils";
9
+ import { ESLINT_DESTINATION_DIRECTORY } from "./constants";
10
10
 
11
11
  const eslint = async (debug) => {
12
+ const nanoType = await getNanoType();
13
+
14
+ const eslintCommonSource = nanoType !== "nano" ? "common" : nanoType;
15
+
16
+ const eslintSourceDirectory = path.resolve(
17
+ `./node_modules/@bigbinary/neeto-audit-frontend/common/eslint/${eslintCommonSource}`
18
+ );
19
+
12
20
  const fix = async (debug) => {
13
21
  await createOrReplaceDirectoryFiles({
14
- sourceDirectory: ESLINT_SOURCE_DIRECTORY,
22
+ sourceDirectory: eslintSourceDirectory,
15
23
  destinationDirectory: ESLINT_DESTINATION_DIRECTORY,
16
24
  debug,
17
25
  });
18
26
  };
19
27
 
20
28
  const eslintDirectory = await identicalDirectoryFiles({
21
- sourceDirectory: ESLINT_SOURCE_DIRECTORY,
29
+ sourceDirectory: eslintSourceDirectory,
22
30
  destinationDirectory: ESLINT_DESTINATION_DIRECTORY,
23
31
  debug,
24
32
  });
@@ -4,6 +4,10 @@ export const HUSKY_SOURCE_DIRECTORY = path.resolve(
4
4
  "./node_modules/@bigbinary/neeto-audit-frontend/common/.husky"
5
5
  );
6
6
 
7
+ export const HUSKY_COMMON_SOURCE_DIRECTORY = path.resolve(
8
+ "./node_modules/@bigbinary/neeto-audit-frontend/common/.husky/common"
9
+ );
10
+
7
11
  export const HUSKY_DESTINATION_DIRECTORY = path.resolve("./.husky");
8
12
 
9
13
  export const HUSKY_GIT_IGNORE_PATH = path.resolve("./.husky/.gitignore");
@@ -1,19 +1,33 @@
1
1
  import { identity } from "ramda";
2
+
2
3
  import {
3
4
  createOrReplaceDirectoryFiles,
4
5
  createOrReplaceFile,
6
+ getNanoType,
5
7
  identicalDirectoryFiles,
6
- } from "../../utils/index.js";
8
+ } from "../../utils";
7
9
  import {
8
10
  HUSKY_DESTINATION_DIRECTORY,
9
11
  HUSKY_GIT_IGNORE_PATH,
10
12
  HUSKY_SOURCE_DIRECTORY,
11
- } from "./constants.js";
13
+ HUSKY_COMMON_SOURCE_DIRECTORY,
14
+ } from "./constants";
12
15
 
13
16
  const husky = async (debug) => {
17
+ const nanoType = await getNanoType();
18
+
19
+ const nanoSourceDirectory = `${HUSKY_SOURCE_DIRECTORY}/${nanoType}`;
20
+
14
21
  const fix = async (debug) => {
15
22
  await createOrReplaceDirectoryFiles({
16
- sourceDirectory: HUSKY_SOURCE_DIRECTORY,
23
+ sourceDirectory: HUSKY_COMMON_SOURCE_DIRECTORY,
24
+ destinationDirectory: HUSKY_DESTINATION_DIRECTORY,
25
+ debug,
26
+ options: { mode: 0o755 },
27
+ });
28
+
29
+ await createOrReplaceDirectoryFiles({
30
+ sourceDirectory: nanoSourceDirectory,
17
31
  destinationDirectory: HUSKY_DESTINATION_DIRECTORY,
18
32
  debug,
19
33
  options: { mode: 0o755 },
@@ -26,13 +40,22 @@ const husky = async (debug) => {
26
40
  });
27
41
  };
28
42
 
29
- const huskyDirectory = await identicalDirectoryFiles({
30
- sourceDirectory: HUSKY_SOURCE_DIRECTORY,
43
+ const huskyCommonDirectory = await identicalDirectoryFiles({
44
+ sourceDirectory: HUSKY_COMMON_SOURCE_DIRECTORY,
45
+ destinationDirectory: HUSKY_DESTINATION_DIRECTORY,
46
+ debug,
47
+ });
48
+
49
+ const huskyNanoDirectory = await identicalDirectoryFiles({
50
+ sourceDirectory: nanoSourceDirectory,
31
51
  destinationDirectory: HUSKY_DESTINATION_DIRECTORY,
32
52
  debug,
33
53
  });
34
54
 
35
- const huskyFiles = await Promise.all(huskyDirectory);
55
+ const huskyFiles = await Promise.all([
56
+ ...huskyCommonDirectory,
57
+ ...huskyNanoDirectory,
58
+ ]);
36
59
 
37
60
  const isSuccess = huskyFiles.every(identity);
38
61
 
@@ -1,9 +1,8 @@
1
- import currentNodeVersion from "./currentNodeVersion/index.js";
2
-
3
- import husky from "./husky/index.js";
4
- import prettier from "./prettier/index.js";
5
- import eslint from "./eslint/index.js";
6
- import recommendedPackageVersions from "./recommendedPackageVersions/index.js";
1
+ import currentNodeVersion from "./currentNodeVersion";
2
+ import eslint from "./eslint";
3
+ import husky from "./husky";
4
+ import prettier from "./prettier";
5
+ import recommendedPackageVersions from "./recommendedPackageVersions";
7
6
 
8
7
  const verifiers = [
9
8
  ["Current node version", currentNodeVersion],
@@ -1,12 +1,10 @@
1
1
  import { identity } from "ramda";
2
+
2
3
  import {
3
4
  createOrReplaceDirectoryFiles,
4
5
  identicalDirectoryFiles,
5
- } from "../../utils/index.js";
6
- import {
7
- PRETTIER_DESTINATION_PATH,
8
- PRETTIER_SOURCE_PATH,
9
- } from "./constants.js";
6
+ } from "../../utils";
7
+ import { PRETTIER_DESTINATION_PATH, PRETTIER_SOURCE_PATH } from "./constants";
10
8
 
11
9
  const prettier = async (debug) => {
12
10
  const fix = async (debug) => {
@@ -2,8 +2,8 @@ import path from "path";
2
2
 
3
3
  export const PACKAGE_JSON_PATH = path.resolve("./package.json");
4
4
 
5
- export const DEPENDENCY_TYPES = [
6
- "dependencies",
7
- "peerDependencies",
8
- "devDependencies",
9
- ];
5
+ export const DEPENDENCY_TYPES = {
6
+ dependencies: "",
7
+ devDependencies: "--dev",
8
+ peerDependencies: "--peer",
9
+ };
@@ -1,24 +1,40 @@
1
- import { identity, isEmpty, mergeDeepRight } from "ramda";
1
+ import { existsBy, isNotEmpty } from "@bigbinary/neeto-commons-frontend/pure";
2
+ import { mergeDeepRight, objOf } from "ramda";
3
+
4
+ import recommendedDependencies from "../../../common/recommendedDependencies";
5
+ import { PACKAGE_JSON_PATH } from "../../constants";
2
6
  import {
7
+ createOrReplaceFile,
8
+ execute,
3
9
  getNanoType,
4
- getOutdatedPackages,
5
10
  getPackageJson,
11
+ } from "../../utils";
12
+ import {
13
+ getInstallationCommands,
14
+ getOutdatedPackages,
6
15
  sortByKey,
7
- } from "./utils.js";
8
- import { createOrReplaceFile, execute } from "../../utils/index.js";
9
- import recommendedDependencies from "../../../common/recommendedDependencies/index.js";
10
- import { PACKAGE_JSON_PATH } from "./constants.js";
16
+ } from "./utils";
11
17
 
12
18
  const recommendedPackageVersions = async (debug) => {
13
19
  const packageJson = await getPackageJson(debug);
14
20
 
15
- const nanoType = getNanoType(packageJson);
21
+ const nanoType = await getNanoType();
16
22
 
17
- const dependencyTypes = getOutdatedPackages(nanoType, packageJson);
23
+ const recommendedVersions = mergeDeepRight(
24
+ recommendedDependencies["common"],
25
+ recommendedDependencies[nanoType]
26
+ );
27
+
28
+ const packagesToUpdate = getOutdatedPackages(
29
+ recommendedVersions,
30
+ packageJson
31
+ );
18
32
 
19
33
  const fix = async (debug) => {
20
- const recommendedVersions = recommendedDependencies[nanoType];
21
- let newPackageJson = mergeDeepRight(packageJson, recommendedVersions);
34
+ let newPackageJson = mergeDeepRight(
35
+ packageJson,
36
+ objOf("peerDependencies", recommendedVersions["peerDependencies"])
37
+ );
22
38
  newPackageJson = sortByKey(newPackageJson);
23
39
 
24
40
  await createOrReplaceFile({
@@ -27,12 +43,20 @@ const recommendedPackageVersions = async (debug) => {
27
43
  debug,
28
44
  });
29
45
 
30
- await execute("yarn install --check-files");
46
+ const commands = getInstallationCommands(
47
+ recommendedVersions,
48
+ packagesToUpdate
49
+ );
50
+
51
+ const results = commands.map((command) => command && execute(command));
52
+
53
+ return Promise.all(results);
31
54
  };
32
55
 
33
- const isSuccess = dependencyTypes
34
- .map(({ outdatedPackages }) => isEmpty(outdatedPackages))
35
- .every(identity);
56
+ const isSuccess = !existsBy(
57
+ { outdatedPackages: isNotEmpty },
58
+ packagesToUpdate
59
+ );
36
60
 
37
61
  if (!isSuccess) {
38
62
  return {
@@ -1,34 +1,21 @@
1
- import recommendedDependencies from "../../../common/recommendedDependencies/index.js";
2
- import { getFileContent } from "../../utils/index.js";
3
- import { DEPENDENCY_TYPES, PACKAGE_JSON_PATH } from "./constants.js";
4
-
5
1
  import {
6
2
  eqProps,
7
- last,
8
3
  pickBy,
9
4
  mapObjIndexed,
10
5
  not,
11
- split,
12
6
  map,
13
7
  is,
14
8
  fromPairs,
15
9
  toPairs,
10
+ isEmpty,
16
11
  } from "ramda";
12
+ import { findBy } from "@bigbinary/neeto-commons-frontend/pure";
17
13
 
18
- export const getPackageJson = async (debug) => {
19
- const packageJsonContent = await getFileContent({
20
- relativeFilePath: PACKAGE_JSON_PATH,
21
- debug,
22
- });
23
-
24
- return JSON.parse(packageJsonContent);
25
- };
14
+ import { DEPENDENCY_TYPES } from "./constants";
26
15
 
27
- export const getNanoType = ({ name }) => last(split("-", name));
28
-
29
- export const getOutdatedPackages = (nanoType, packageJson) =>
30
- DEPENDENCY_TYPES.map((type) => {
31
- const recommendedList = recommendedDependencies[nanoType][type];
16
+ export const getOutdatedPackages = (recommendedVersions, packageJson) =>
17
+ Object.keys(DEPENDENCY_TYPES).map((type) => {
18
+ const recommendedList = recommendedVersions[type];
32
19
 
33
20
  const packages = mapObjIndexed(
34
21
  (_, key) => eqProps(key, recommendedList, packageJson[type]),
@@ -48,3 +35,18 @@ export const sortByKey = (object) =>
48
35
  : value,
49
36
  object
50
37
  );
38
+
39
+ export const getInstallationCommands = (recommendPackages, packagesToUpdate) =>
40
+ Object.entries(recommendPackages).map(([type, packages]) => {
41
+ const { outdatedPackages } = findBy({ type }, packagesToUpdate);
42
+
43
+ if (isEmpty(outdatedPackages) || type === "peerDependencies") {
44
+ return;
45
+ }
46
+
47
+ const packagesWithVersion = outdatedPackages
48
+ .map((packageName) => `${packageName}@${packages[packageName]}`)
49
+ .join(" ");
50
+
51
+ return `yarn add ${DEPENDENCY_TYPES[type]} ${packagesWithVersion}`;
52
+ });
File without changes
File without changes
File without changes