@cloud-ru/ft-deps-validator 1.1.1
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.
- package/CHANGELOG.md +32 -0
- package/LICENSE +201 -0
- package/README.md +77 -0
- package/dist/cjs/index.d.ts +2 -0
- package/dist/cjs/index.js +75 -0
- package/dist/cjs/types/cliArguments.d.ts +4 -0
- package/dist/cjs/types/cliArguments.js +2 -0
- package/dist/cjs/types/config.d.ts +12 -0
- package/dist/cjs/types/config.js +2 -0
- package/dist/cjs/types/state.d.ts +6 -0
- package/dist/cjs/types/state.js +2 -0
- package/dist/cjs/utils/console.d.ts +5 -0
- package/dist/cjs/utils/console.js +34 -0
- package/dist/cjs/utils/getCliArguments.d.ts +10 -0
- package/dist/cjs/utils/getCliArguments.js +63 -0
- package/dist/cjs/utils/getConfigFile.d.ts +3 -0
- package/dist/cjs/utils/getConfigFile.js +23 -0
- package/dist/cjs/utils/getMonorepoPrefix.d.ts +6 -0
- package/dist/cjs/utils/getMonorepoPrefix.js +33 -0
- package/dist/cjs/utils/initializeState.d.ts +8 -0
- package/dist/cjs/utils/initializeState.js +40 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +70 -0
- package/dist/esm/types/cliArguments.d.ts +4 -0
- package/dist/esm/types/cliArguments.js +1 -0
- package/dist/esm/types/config.d.ts +12 -0
- package/dist/esm/types/config.js +1 -0
- package/dist/esm/types/state.d.ts +6 -0
- package/dist/esm/types/state.js +1 -0
- package/dist/esm/utils/console.d.ts +5 -0
- package/dist/esm/utils/console.js +23 -0
- package/dist/esm/utils/getCliArguments.d.ts +10 -0
- package/dist/esm/utils/getCliArguments.js +57 -0
- package/dist/esm/utils/getConfigFile.d.ts +3 -0
- package/dist/esm/utils/getConfigFile.js +17 -0
- package/dist/esm/utils/getMonorepoPrefix.d.ts +6 -0
- package/dist/esm/utils/getMonorepoPrefix.js +27 -0
- package/dist/esm/utils/initializeState.d.ts +8 -0
- package/dist/esm/utils/initializeState.js +34 -0
- package/package.json +42 -0
- package/src/index.ts +77 -0
- package/src/types/cliArguments.ts +5 -0
- package/src/types/config.ts +12 -0
- package/src/types/state.ts +6 -0
- package/src/utils/console.ts +27 -0
- package/src/utils/getCliArguments.ts +66 -0
- package/src/utils/getConfigFile.ts +23 -0
- package/src/utils/getMonorepoPrefix.ts +32 -0
- package/src/utils/initializeState.ts +51 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.initializeState = initializeState;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const getMonorepoPrefix_1 = require("./getMonorepoPrefix");
|
|
9
|
+
function initializeState({ cwd, folders, prefix }) {
|
|
10
|
+
const internalPackages = {};
|
|
11
|
+
const initialState = {
|
|
12
|
+
wrongVersions: [],
|
|
13
|
+
internalAsDev: [],
|
|
14
|
+
unusedDeps: [],
|
|
15
|
+
missing: [],
|
|
16
|
+
};
|
|
17
|
+
const monorepoName = prefix || (0, getMonorepoPrefix_1.getMonorepoPrefix)({ cwd, folders });
|
|
18
|
+
if (!monorepoName) {
|
|
19
|
+
throw new Error('[ERROR] Prefix was not specified and was not found');
|
|
20
|
+
}
|
|
21
|
+
const monorepoPackageRegexp = new RegExp(`${monorepoName}\\/`);
|
|
22
|
+
for (const folder of folders) {
|
|
23
|
+
// eslint-disable-next-line import/no-dynamic-require, @typescript-eslint/no-var-requires
|
|
24
|
+
const pkg = require(path_1.default.resolve(folder, 'package.json'));
|
|
25
|
+
internalPackages[pkg.name] = pkg.version;
|
|
26
|
+
}
|
|
27
|
+
for (const folder of folders) {
|
|
28
|
+
// eslint-disable-next-line import/no-dynamic-require, @typescript-eslint/no-var-requires
|
|
29
|
+
const pkg = require(path_1.default.resolve(folder, 'package.json'));
|
|
30
|
+
const usedInternal = Object.keys(pkg.dependencies || {}).filter(x => monorepoPackageRegexp.test(x));
|
|
31
|
+
usedInternal.forEach(dep => {
|
|
32
|
+
if (pkg.dependencies[dep] !== internalPackages[dep]) {
|
|
33
|
+
initialState.wrongVersions.push(`Error in ${pkg.name}: ${dep} has ${pkg.dependencies[dep]}, but correct version is ${internalPackages[dep]}`);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
const usedInternalDev = Object.keys(pkg.devDependencies || {}).filter(x => monorepoPackageRegexp.test(x));
|
|
37
|
+
usedInternalDev.forEach(dep => initialState.internalAsDev.push(`Error in ${pkg.name}: ${dep}`));
|
|
38
|
+
}
|
|
39
|
+
return initialState;
|
|
40
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
import depCheck from 'depcheck';
|
|
12
|
+
import { globSync } from 'glob';
|
|
13
|
+
import { logDebug, logError, logInfo } from './utils/console';
|
|
14
|
+
import { getCliArguments } from './utils/getCliArguments';
|
|
15
|
+
import { getConfigFile } from './utils/getConfigFile';
|
|
16
|
+
import { initializeState } from './utils/initializeState';
|
|
17
|
+
(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
18
|
+
try {
|
|
19
|
+
const args = getCliArguments();
|
|
20
|
+
const configFile = getConfigFile(args.cwd);
|
|
21
|
+
const config = configFile ? Object.assign(args, configFile) : args;
|
|
22
|
+
const options = {
|
|
23
|
+
ignoreBinPackage: false,
|
|
24
|
+
skipMissing: false,
|
|
25
|
+
ignorePatterns: config.ignorePatterns.map(pattern => pattern.toString()),
|
|
26
|
+
ignoreMatches: config.ignoreMatches.map(match => match.toString()),
|
|
27
|
+
};
|
|
28
|
+
const folders = globSync(config.rootPackagesFolderPattern, {
|
|
29
|
+
ignore: config.ignoredPackagesFolderFiles.map(path => path.toString()),
|
|
30
|
+
});
|
|
31
|
+
const state = initializeState({
|
|
32
|
+
cwd: args.cwd,
|
|
33
|
+
folders,
|
|
34
|
+
prefix: config.prefix,
|
|
35
|
+
});
|
|
36
|
+
for (const folder of folders) {
|
|
37
|
+
const { dependencies, missing: missingDepsPerPackage } = yield depCheck(folder, options);
|
|
38
|
+
state.unusedDeps.push(...dependencies.map(x => `${folder}: ${x}`));
|
|
39
|
+
if (Object.keys(missingDepsPerPackage).length) {
|
|
40
|
+
state.missing.push(missingDepsPerPackage);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (Object.values(state).every(result => result.length < 1)) {
|
|
44
|
+
logInfo('Dependencies have been checked. Everything is ok.');
|
|
45
|
+
process.exit(0);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (state.wrongVersions.length) {
|
|
49
|
+
logError('You have to fix wrong version of internal packages:');
|
|
50
|
+
state.wrongVersions.forEach(logDebug);
|
|
51
|
+
}
|
|
52
|
+
if (state.internalAsDev.length) {
|
|
53
|
+
logError('You have to fix wrong usage of internal packages in dev dependencies (either delete them or move to dependencies):');
|
|
54
|
+
state.internalAsDev.forEach(logDebug);
|
|
55
|
+
}
|
|
56
|
+
if (state.unusedDeps.length) {
|
|
57
|
+
logError('You have to fix following unused dependencies:');
|
|
58
|
+
state.unusedDeps.forEach(logDebug);
|
|
59
|
+
}
|
|
60
|
+
if (state.missing.length) {
|
|
61
|
+
logError('You have to fix following missed dependencies:');
|
|
62
|
+
state.missing.forEach(x => logDebug(JSON.stringify(x, null, 2)));
|
|
63
|
+
}
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
console.error(err);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
}))();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type Config = {
|
|
2
|
+
/** Directories names that should be ignored */
|
|
3
|
+
ignorePatterns?: string[];
|
|
4
|
+
/** Names of packages that should be ignored */
|
|
5
|
+
ignoreMatches?: string[];
|
|
6
|
+
/** Monorepo prefix (if skipped will try to find automatically) */
|
|
7
|
+
prefix?: string;
|
|
8
|
+
/** One or more paths that should be ignored in packages folder */
|
|
9
|
+
ignoredPackagesFolderFiles?: string[];
|
|
10
|
+
/** Folder containing packages (glob pattern, e.g., "packages/*" or "apps/*") */
|
|
11
|
+
rootPackagesFolderPattern?: string;
|
|
12
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const logError: (message: string) => void;
|
|
2
|
+
export declare const logInfo: (message: string) => void;
|
|
3
|
+
export declare const logHelp: (message: string) => void;
|
|
4
|
+
export declare const logSilly: (message: string) => void;
|
|
5
|
+
export declare const logDebug: (message: string) => void;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import colors from 'colors/safe';
|
|
2
|
+
const themes = {
|
|
3
|
+
silly: 'rainbow',
|
|
4
|
+
input: 'grey',
|
|
5
|
+
verbose: 'cyan',
|
|
6
|
+
prompt: 'grey',
|
|
7
|
+
info: 'green',
|
|
8
|
+
data: 'grey',
|
|
9
|
+
help: 'cyan',
|
|
10
|
+
warn: 'yellow',
|
|
11
|
+
debug: 'blue',
|
|
12
|
+
error: 'red',
|
|
13
|
+
};
|
|
14
|
+
colors.setTheme(themes);
|
|
15
|
+
const log = (message, theme = 'warn') => {
|
|
16
|
+
// eslint-disable-next-line no-console
|
|
17
|
+
console.log(colors[themes[theme]](`${message}\n`));
|
|
18
|
+
};
|
|
19
|
+
export const logError = (message) => log(message, 'error');
|
|
20
|
+
export const logInfo = (message) => log(message, 'info');
|
|
21
|
+
export const logHelp = (message) => log(message, 'help');
|
|
22
|
+
export const logSilly = (message) => log(message, 'silly');
|
|
23
|
+
export const logDebug = (message) => log(message, 'debug');
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function getCliArguments(): {
|
|
2
|
+
cwd: string;
|
|
3
|
+
rootPackagesFolderPattern: string;
|
|
4
|
+
ignoredPackagesFolderFiles: string[];
|
|
5
|
+
prefix: string | undefined;
|
|
6
|
+
ignorePatterns: string[] | (string | number)[];
|
|
7
|
+
ignoreMatches: string[] | (string | number)[];
|
|
8
|
+
_: (string | number)[];
|
|
9
|
+
$0: string;
|
|
10
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import yargs from 'yargs';
|
|
3
|
+
export function getCliArguments() {
|
|
4
|
+
const parsed = yargs(process.argv.slice(2))
|
|
5
|
+
.option('cwd', {
|
|
6
|
+
alias: 'd',
|
|
7
|
+
type: 'string',
|
|
8
|
+
default: process.cwd(),
|
|
9
|
+
description: 'working directory (default: current working directory, cwd)',
|
|
10
|
+
})
|
|
11
|
+
.option('prefix', {
|
|
12
|
+
type: 'string',
|
|
13
|
+
description: 'monorepo prefix (if skipped will try to find automatically)',
|
|
14
|
+
})
|
|
15
|
+
.option('rootPackagesFolderPattern', {
|
|
16
|
+
alias: 'p',
|
|
17
|
+
type: 'string',
|
|
18
|
+
default: 'packages/*',
|
|
19
|
+
description: 'folder containing packages (glob pattern, e.g., "packages/*" or "apps/*")',
|
|
20
|
+
})
|
|
21
|
+
.option('ignoredPackagesFolderFiles', {
|
|
22
|
+
type: 'array',
|
|
23
|
+
default: ['packages/tsconfig.cjs.json', 'packages/tsconfig.esm.json'],
|
|
24
|
+
description: 'specify one or more paths that should be ignored in packages folder',
|
|
25
|
+
})
|
|
26
|
+
.option('ignorePatterns', {
|
|
27
|
+
type: 'array',
|
|
28
|
+
default: ['stories', 'dist', '__tests__', '__e2e__'],
|
|
29
|
+
description: 'specify one or more directories names that should be ignored',
|
|
30
|
+
})
|
|
31
|
+
.option('ignoreMatches', {
|
|
32
|
+
type: 'array',
|
|
33
|
+
default: [
|
|
34
|
+
'react',
|
|
35
|
+
'react-dom',
|
|
36
|
+
'react-docgen-typescript',
|
|
37
|
+
'@snack-uikit/figma-tokens',
|
|
38
|
+
'@sbercloud/figma-tokens-cloud-platform',
|
|
39
|
+
'@sbercloud/figma-tokens-mlspace',
|
|
40
|
+
'@sbercloud/figma-tokens-admin',
|
|
41
|
+
'@sbercloud/figma-tokens-web',
|
|
42
|
+
'@sbercloud/figma-tokens-giga-id',
|
|
43
|
+
],
|
|
44
|
+
description: 'specify one or more packages that should be ignored',
|
|
45
|
+
})
|
|
46
|
+
.locale('en')
|
|
47
|
+
.help()
|
|
48
|
+
.alias('h', 'help')
|
|
49
|
+
.alias('v', 'version')
|
|
50
|
+
.parseSync();
|
|
51
|
+
const cwd = path.resolve(parsed.cwd);
|
|
52
|
+
const rootPackagesFolderPattern = path.resolve(cwd, parsed.rootPackagesFolderPattern);
|
|
53
|
+
const ignoredPackagesFolderFiles = parsed.ignoredPackagesFolderFiles.map(file => path.resolve(cwd, file.toString()));
|
|
54
|
+
return Object.assign(Object.assign({}, parsed), { cwd,
|
|
55
|
+
rootPackagesFolderPattern,
|
|
56
|
+
ignoredPackagesFolderFiles });
|
|
57
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
const CONFIG_FILE_NAME = 'deps-validator.config.json';
|
|
4
|
+
export function getConfigFile(cwd) {
|
|
5
|
+
const configPath = path.resolve(cwd, CONFIG_FILE_NAME);
|
|
6
|
+
if (!fs.existsSync(configPath)) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
try {
|
|
10
|
+
const configContent = fs.readFileSync(configPath, 'utf-8');
|
|
11
|
+
const config = JSON.parse(configContent);
|
|
12
|
+
return config;
|
|
13
|
+
}
|
|
14
|
+
catch (_a) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
export function getMonorepoPrefix({ cwd, folders }) {
|
|
3
|
+
var _a;
|
|
4
|
+
try {
|
|
5
|
+
// eslint-disable-next-line import/no-dynamic-require, @typescript-eslint/no-var-requires
|
|
6
|
+
const rootPkg = require(path.resolve(cwd, 'package.json'));
|
|
7
|
+
return rootPkg.name || null;
|
|
8
|
+
}
|
|
9
|
+
catch (_b) {
|
|
10
|
+
if (folders.length < 1) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
// eslint-disable-next-line import/no-dynamic-require, @typescript-eslint/no-var-requires
|
|
15
|
+
const firstPkg = require(path.resolve(folders[0], 'package.json'));
|
|
16
|
+
// Extract monorepo scope from package name (e.g., "@cloud-ru" from "@cloud-ru/ft-deps-validator")
|
|
17
|
+
const match = (_a = firstPkg.name) === null || _a === void 0 ? void 0 : _a.match(/^(@[^/]+)/);
|
|
18
|
+
if (match) {
|
|
19
|
+
return match[1];
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
catch (_c) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { getMonorepoPrefix } from './getMonorepoPrefix';
|
|
3
|
+
export function initializeState({ cwd, folders, prefix }) {
|
|
4
|
+
const internalPackages = {};
|
|
5
|
+
const initialState = {
|
|
6
|
+
wrongVersions: [],
|
|
7
|
+
internalAsDev: [],
|
|
8
|
+
unusedDeps: [],
|
|
9
|
+
missing: [],
|
|
10
|
+
};
|
|
11
|
+
const monorepoName = prefix || getMonorepoPrefix({ cwd, folders });
|
|
12
|
+
if (!monorepoName) {
|
|
13
|
+
throw new Error('[ERROR] Prefix was not specified and was not found');
|
|
14
|
+
}
|
|
15
|
+
const monorepoPackageRegexp = new RegExp(`${monorepoName}\\/`);
|
|
16
|
+
for (const folder of folders) {
|
|
17
|
+
// eslint-disable-next-line import/no-dynamic-require, @typescript-eslint/no-var-requires
|
|
18
|
+
const pkg = require(path.resolve(folder, 'package.json'));
|
|
19
|
+
internalPackages[pkg.name] = pkg.version;
|
|
20
|
+
}
|
|
21
|
+
for (const folder of folders) {
|
|
22
|
+
// eslint-disable-next-line import/no-dynamic-require, @typescript-eslint/no-var-requires
|
|
23
|
+
const pkg = require(path.resolve(folder, 'package.json'));
|
|
24
|
+
const usedInternal = Object.keys(pkg.dependencies || {}).filter(x => monorepoPackageRegexp.test(x));
|
|
25
|
+
usedInternal.forEach(dep => {
|
|
26
|
+
if (pkg.dependencies[dep] !== internalPackages[dep]) {
|
|
27
|
+
initialState.wrongVersions.push(`Error in ${pkg.name}: ${dep} has ${pkg.dependencies[dep]}, but correct version is ${internalPackages[dep]}`);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
const usedInternalDev = Object.keys(pkg.devDependencies || {}).filter(x => monorepoPackageRegexp.test(x));
|
|
31
|
+
usedInternalDev.forEach(dep => initialState.internalAsDev.push(`Error in ${pkg.name}: ${dep}`));
|
|
32
|
+
}
|
|
33
|
+
return initialState;
|
|
34
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cloud-ru/ft-deps-validator",
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"description": "Validator for unused, missing or wrong version dependencies in monorepo's packages",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"keywords": [
|
|
9
|
+
"dependency",
|
|
10
|
+
"internal"
|
|
11
|
+
],
|
|
12
|
+
"author": "Ilya Belyavskiy <iabelyavskiy@cloud.ru>",
|
|
13
|
+
"license": "Apache-2.0",
|
|
14
|
+
"bin": {
|
|
15
|
+
"deps-validator": "./dist/cjs/index.js"
|
|
16
|
+
},
|
|
17
|
+
"types": "./dist/esm/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
"import": "./dist/esm/index.js",
|
|
20
|
+
"require": "./dist/cjs/index.js"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist/esm",
|
|
24
|
+
"dist/cjs",
|
|
25
|
+
"src",
|
|
26
|
+
"./CHANGELOG.md",
|
|
27
|
+
"LICENSE"
|
|
28
|
+
],
|
|
29
|
+
"homepage": "https://github.com/cloud-ru-tech/frontend-tools/tree/master/packages/deps-validator",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/cloud-ru-tech/frontend-tools.git",
|
|
33
|
+
"directory": "packages/deps-validator"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"colors": "1.4.0",
|
|
37
|
+
"depcheck": "1.4.7",
|
|
38
|
+
"glob": "10.5.0",
|
|
39
|
+
"yargs": "18.0.0"
|
|
40
|
+
},
|
|
41
|
+
"gitHead": "c8e488ecae08fbcda9738fc27674415b707038ec"
|
|
42
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import depCheck from 'depcheck';
|
|
4
|
+
import { globSync } from 'glob';
|
|
5
|
+
|
|
6
|
+
import { logDebug, logError, logInfo } from './utils/console';
|
|
7
|
+
import { getCliArguments } from './utils/getCliArguments';
|
|
8
|
+
import { getConfigFile } from './utils/getConfigFile';
|
|
9
|
+
import { initializeState } from './utils/initializeState';
|
|
10
|
+
|
|
11
|
+
(async () => {
|
|
12
|
+
try {
|
|
13
|
+
const args = getCliArguments();
|
|
14
|
+
const configFile = getConfigFile(args.cwd);
|
|
15
|
+
|
|
16
|
+
const config = configFile ? Object.assign(args, configFile) : args;
|
|
17
|
+
|
|
18
|
+
const options = {
|
|
19
|
+
ignoreBinPackage: false,
|
|
20
|
+
skipMissing: false,
|
|
21
|
+
ignorePatterns: config.ignorePatterns.map(pattern => pattern.toString()),
|
|
22
|
+
ignoreMatches: config.ignoreMatches.map(match => match.toString()),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const folders = globSync(config.rootPackagesFolderPattern, {
|
|
26
|
+
ignore: config.ignoredPackagesFolderFiles.map(path => path.toString()),
|
|
27
|
+
});
|
|
28
|
+
const state = initializeState({
|
|
29
|
+
cwd: args.cwd,
|
|
30
|
+
folders,
|
|
31
|
+
prefix: config.prefix,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
for (const folder of folders) {
|
|
35
|
+
const { dependencies, missing: missingDepsPerPackage } = await depCheck(folder, options);
|
|
36
|
+
|
|
37
|
+
state.unusedDeps.push(...dependencies.map(x => `${folder}: ${x}`));
|
|
38
|
+
|
|
39
|
+
if (Object.keys(missingDepsPerPackage).length) {
|
|
40
|
+
state.missing.push(missingDepsPerPackage);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (Object.values(state).every(result => result.length < 1)) {
|
|
45
|
+
logInfo('Dependencies have been checked. Everything is ok.');
|
|
46
|
+
process.exit(0);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (state.wrongVersions.length) {
|
|
51
|
+
logError('You have to fix wrong version of internal packages:');
|
|
52
|
+
state.wrongVersions.forEach(logDebug);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (state.internalAsDev.length) {
|
|
56
|
+
logError(
|
|
57
|
+
'You have to fix wrong usage of internal packages in dev dependencies (either delete them or move to dependencies):',
|
|
58
|
+
);
|
|
59
|
+
state.internalAsDev.forEach(logDebug);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (state.unusedDeps.length) {
|
|
63
|
+
logError('You have to fix following unused dependencies:');
|
|
64
|
+
state.unusedDeps.forEach(logDebug);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (state.missing.length) {
|
|
68
|
+
logError('You have to fix following missed dependencies:');
|
|
69
|
+
state.missing.forEach(x => logDebug(JSON.stringify(x, null, 2)));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
process.exit(1);
|
|
73
|
+
} catch (err) {
|
|
74
|
+
console.error(err);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
})();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type Config = {
|
|
2
|
+
/** Directories names that should be ignored */
|
|
3
|
+
ignorePatterns?: string[];
|
|
4
|
+
/** Names of packages that should be ignored */
|
|
5
|
+
ignoreMatches?: string[];
|
|
6
|
+
/** Monorepo prefix (if skipped will try to find automatically) */
|
|
7
|
+
prefix?: string;
|
|
8
|
+
/** One or more paths that should be ignored in packages folder */
|
|
9
|
+
ignoredPackagesFolderFiles?: string[];
|
|
10
|
+
/** Folder containing packages (glob pattern, e.g., "packages/*" or "apps/*") */
|
|
11
|
+
rootPackagesFolderPattern?: string;
|
|
12
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import colors from 'colors/safe';
|
|
2
|
+
|
|
3
|
+
const themes = {
|
|
4
|
+
silly: 'rainbow',
|
|
5
|
+
input: 'grey',
|
|
6
|
+
verbose: 'cyan',
|
|
7
|
+
prompt: 'grey',
|
|
8
|
+
info: 'green',
|
|
9
|
+
data: 'grey',
|
|
10
|
+
help: 'cyan',
|
|
11
|
+
warn: 'yellow',
|
|
12
|
+
debug: 'blue',
|
|
13
|
+
error: 'red',
|
|
14
|
+
} as const;
|
|
15
|
+
|
|
16
|
+
colors.setTheme(themes);
|
|
17
|
+
|
|
18
|
+
const log = (message: string, theme: keyof typeof themes = 'warn'): void => {
|
|
19
|
+
// eslint-disable-next-line no-console
|
|
20
|
+
console.log(colors[themes[theme]](`${message}\n`));
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const logError = (message: string) => log(message, 'error');
|
|
24
|
+
export const logInfo = (message: string) => log(message, 'info');
|
|
25
|
+
export const logHelp = (message: string) => log(message, 'help');
|
|
26
|
+
export const logSilly = (message: string) => log(message, 'silly');
|
|
27
|
+
export const logDebug = (message: string) => log(message, 'debug');
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
|
|
3
|
+
import yargs from 'yargs';
|
|
4
|
+
|
|
5
|
+
export function getCliArguments() {
|
|
6
|
+
const parsed = yargs(process.argv.slice(2))
|
|
7
|
+
.option('cwd', {
|
|
8
|
+
alias: 'd',
|
|
9
|
+
type: 'string',
|
|
10
|
+
default: process.cwd(),
|
|
11
|
+
description: 'working directory (default: current working directory, cwd)',
|
|
12
|
+
})
|
|
13
|
+
.option('prefix', {
|
|
14
|
+
type: 'string',
|
|
15
|
+
description: 'monorepo prefix (if skipped will try to find automatically)',
|
|
16
|
+
})
|
|
17
|
+
.option('rootPackagesFolderPattern', {
|
|
18
|
+
alias: 'p',
|
|
19
|
+
type: 'string',
|
|
20
|
+
default: 'packages/*',
|
|
21
|
+
description: 'folder containing packages (glob pattern, e.g., "packages/*" or "apps/*")',
|
|
22
|
+
})
|
|
23
|
+
.option('ignoredPackagesFolderFiles', {
|
|
24
|
+
type: 'array',
|
|
25
|
+
default: ['packages/tsconfig.cjs.json', 'packages/tsconfig.esm.json'],
|
|
26
|
+
description: 'specify one or more paths that should be ignored in packages folder',
|
|
27
|
+
})
|
|
28
|
+
.option('ignorePatterns', {
|
|
29
|
+
type: 'array',
|
|
30
|
+
default: ['stories', 'dist', '__tests__', '__e2e__'],
|
|
31
|
+
description: 'specify one or more directories names that should be ignored',
|
|
32
|
+
})
|
|
33
|
+
.option('ignoreMatches', {
|
|
34
|
+
type: 'array',
|
|
35
|
+
default: [
|
|
36
|
+
'react',
|
|
37
|
+
'react-dom',
|
|
38
|
+
'react-docgen-typescript',
|
|
39
|
+
'@snack-uikit/figma-tokens',
|
|
40
|
+
'@sbercloud/figma-tokens-cloud-platform',
|
|
41
|
+
'@sbercloud/figma-tokens-mlspace',
|
|
42
|
+
'@sbercloud/figma-tokens-admin',
|
|
43
|
+
'@sbercloud/figma-tokens-web',
|
|
44
|
+
'@sbercloud/figma-tokens-giga-id',
|
|
45
|
+
],
|
|
46
|
+
description: 'specify one or more packages that should be ignored',
|
|
47
|
+
})
|
|
48
|
+
.locale('en')
|
|
49
|
+
.help()
|
|
50
|
+
.alias('h', 'help')
|
|
51
|
+
.alias('v', 'version')
|
|
52
|
+
.parseSync();
|
|
53
|
+
|
|
54
|
+
const cwd = path.resolve(parsed.cwd);
|
|
55
|
+
|
|
56
|
+
const rootPackagesFolderPattern = path.resolve(cwd, parsed.rootPackagesFolderPattern);
|
|
57
|
+
|
|
58
|
+
const ignoredPackagesFolderFiles = parsed.ignoredPackagesFolderFiles.map(file => path.resolve(cwd, file.toString()));
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
...parsed,
|
|
62
|
+
cwd,
|
|
63
|
+
rootPackagesFolderPattern,
|
|
64
|
+
ignoredPackagesFolderFiles,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
import { CliArguments } from '../types/cliArguments';
|
|
5
|
+
import { Config } from '../types/config';
|
|
6
|
+
|
|
7
|
+
const CONFIG_FILE_NAME = 'deps-validator.config.json';
|
|
8
|
+
|
|
9
|
+
export function getConfigFile(cwd: CliArguments['cwd']): Config | null {
|
|
10
|
+
const configPath = path.resolve(cwd, CONFIG_FILE_NAME);
|
|
11
|
+
|
|
12
|
+
if (!fs.existsSync(configPath)) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const configContent = fs.readFileSync(configPath, 'utf-8');
|
|
18
|
+
const config = JSON.parse(configContent) as Config;
|
|
19
|
+
return config;
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
|
|
3
|
+
type Options = {
|
|
4
|
+
cwd: string;
|
|
5
|
+
folders: string[];
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export function getMonorepoPrefix({ cwd, folders }: Options): string | null {
|
|
9
|
+
try {
|
|
10
|
+
// eslint-disable-next-line import/no-dynamic-require, @typescript-eslint/no-var-requires
|
|
11
|
+
const rootPkg = require(path.resolve(cwd, 'package.json'));
|
|
12
|
+
return rootPkg.name || null;
|
|
13
|
+
} catch {
|
|
14
|
+
if (folders.length < 1) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
// eslint-disable-next-line import/no-dynamic-require, @typescript-eslint/no-var-requires
|
|
20
|
+
const firstPkg = require(path.resolve(folders[0], 'package.json'));
|
|
21
|
+
|
|
22
|
+
// Extract monorepo scope from package name (e.g., "@cloud-ru" from "@cloud-ru/ft-deps-validator")
|
|
23
|
+
const match = firstPkg.name?.match(/^(@[^/]+)/);
|
|
24
|
+
if (match) {
|
|
25
|
+
return match[1];
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|