@toptal/davinci-monorepo 7.0.5 → 7.1.0

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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # @toptal/davinci-monorepo
2
2
 
3
+ ## 7.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#1876](https://github.com/toptal/davinci/pull/1876) [`0346b98e`](https://github.com/toptal/davinci/commit/0346b98ec60a105fed14dae179ac2dff0f7b99a0) Thanks [@OndrejTuma](https://github.com/OndrejTuma)! - ---
8
+
9
+ - add optional `--webpack-config` flag to `graph generate` command
10
+
3
11
  ## 7.0.5
4
12
 
5
13
  ### Patch Changes
@@ -3,12 +3,11 @@
3
3
  import cliEngine from '@toptal/davinci-cli-shared'
4
4
 
5
5
  import detectCircularityCommand from '../src/commands/detect-circularity.js'
6
- import graphGenerateCommand from '../src/commands/graph-generate.js'
6
+ import { createGraphGenerateCommand } from '../src/commands/graph-generate.js'
7
7
  import metricsCommand from '../src/commands/metrics.js'
8
8
 
9
- cliEngine.loadCommands([
10
- detectCircularityCommand,
11
- graphGenerateCommand,
12
- metricsCommand,
13
- ], 'davinci-monorepo')
9
+ cliEngine.loadCommands(
10
+ [detectCircularityCommand, createGraphGenerateCommand, metricsCommand],
11
+ 'davinci-monorepo'
12
+ )
14
13
  cliEngine.bootstrap()
@@ -24,10 +24,11 @@ davinci-monorepo graph generate [options]
24
24
  ### Options
25
25
 
26
26
  | Option | Default | Description |
27
- | ----------------- | ------------------ | ------------------------------------------------ |
27
+ | ----------------- | ------------------ |--------------------------------------------------|
28
28
  | -o, --output-file | monorepo-graph.svg | Name of the output file (.svg) |
29
29
  | --ts-config | tsconfig.json | Name of the tsconfig file |
30
30
  | -d, --diff | | Base branch to use for showing affected packages |
31
+ | --webpack-config | | Use a webpack configuration |
31
32
 
32
33
  ## FAQs
33
34
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toptal/davinci-monorepo",
3
- "version": "7.0.5",
3
+ "version": "7.1.0",
4
4
  "description": "Monorepo utility tools",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -39,7 +39,6 @@
39
39
  "dependency-cruiser": "^12.5.0",
40
40
  "execa": "^5.1.1",
41
41
  "glob": "^8.0.3",
42
- "ora": "^5.4.1",
43
42
  "ramda": "^0.28.0"
44
43
  },
45
44
  "devDependencies": {
@@ -1,39 +1,116 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
- exports[`graphGenerateCommandCreator has the correct command structure 1`] = `
4
- {
5
- "action": [Function],
6
- "args": [
7
- Argument {
8
- "_name": "command",
9
- "argChoices": [
10
- "generate",
11
- ],
12
- "defaultValue": undefined,
13
- "defaultValueDescription": undefined,
14
- "description": "Graph related command to execute",
15
- "parseArg": [Function],
16
- "required": true,
17
- "variadic": false,
18
- },
19
- ],
20
- "command": "graph",
21
- "description": "Generate dependency graph in a monorepo",
3
+ exports[`createGraphGenerateCommand has the correct command structure 1`] = `
4
+ "{
5
+ "_events": {},
6
+ "_eventsCount": 4,
7
+ "commands": [],
22
8
  "options": [
23
9
  {
24
- "default": "monorepo-graph.svg",
25
- "label": "Output file name. Default is monorepo-graph.svg",
26
- "name": "-o, --output-file [outputFile]",
10
+ "flags": "-o, --output-file [outputFile]",
11
+ "description": "Output file name. Default is monorepo-graph.svg",
12
+ "required": false,
13
+ "optional": true,
14
+ "variadic": false,
15
+ "mandatory": false,
16
+ "short": "-o",
17
+ "long": "--output-file",
18
+ "negate": false,
19
+ "defaultValue": "monorepo-graph.svg",
20
+ "hidden": false,
21
+ "conflictsWith": []
27
22
  },
28
23
  {
29
- "default": "tsconfig.json",
30
- "label": "Path to typescript configuration file. Default is tsconfig.json",
31
- "name": "--ts-config [tsConfig]",
24
+ "flags": "--ts-config [tsConfig]",
25
+ "description": "Path to typescript configuration file. Default is tsconfig.json",
26
+ "required": false,
27
+ "optional": true,
28
+ "variadic": false,
29
+ "mandatory": false,
30
+ "long": "--ts-config",
31
+ "negate": false,
32
+ "defaultValue": "tsconfig.json",
33
+ "hidden": false,
34
+ "conflictsWith": []
32
35
  },
33
36
  {
34
- "label": "Branch to use as a base for comparison and display of affected packages",
35
- "name": "-d, --diff [diff]",
37
+ "flags": "-d, --diff <branch>",
38
+ "description": "Branch to use as a base for comparison and display of affected packages",
39
+ "required": true,
40
+ "optional": false,
41
+ "variadic": false,
42
+ "mandatory": false,
43
+ "short": "-d",
44
+ "long": "--diff",
45
+ "negate": false,
46
+ "hidden": false,
47
+ "conflictsWith": []
36
48
  },
49
+ {
50
+ "flags": "--webpack-config [webpackConfig]",
51
+ "description": "Path to webpack configuration file",
52
+ "required": false,
53
+ "optional": true,
54
+ "variadic": false,
55
+ "mandatory": false,
56
+ "long": "--webpack-config",
57
+ "negate": false,
58
+ "hidden": false,
59
+ "conflictsWith": []
60
+ }
61
+ ],
62
+ "parent": null,
63
+ "_allowUnknownOption": false,
64
+ "_allowExcessArguments": true,
65
+ "_args": [
66
+ {
67
+ "description": "Graph related command to execute",
68
+ "variadic": false,
69
+ "argChoices": [
70
+ "generate"
71
+ ],
72
+ "required": true,
73
+ "_name": "command"
74
+ }
37
75
  ],
38
- }
76
+ "args": [],
77
+ "rawArgs": [],
78
+ "processedArgs": [],
79
+ "_scriptPath": null,
80
+ "_name": "graph",
81
+ "_optionValues": {
82
+ "outputFile": "monorepo-graph.svg",
83
+ "tsConfig": "tsconfig.json"
84
+ },
85
+ "_optionValueSources": {
86
+ "outputFile": "default",
87
+ "tsConfig": "default"
88
+ },
89
+ "_storeOptionsAsProperties": false,
90
+ "_executableHandler": false,
91
+ "_executableFile": null,
92
+ "_executableDir": null,
93
+ "_defaultCommandName": null,
94
+ "_exitCallback": null,
95
+ "_aliases": [],
96
+ "_combineFlagAndOptionalValue": true,
97
+ "_description": "Generate dependency graph in a monorepo",
98
+ "_summary": "",
99
+ "_enablePositionalOptions": false,
100
+ "_passThroughOptions": false,
101
+ "_lifeCycleHooks": {},
102
+ "_showHelpAfterError": false,
103
+ "_showSuggestionAfterError": true,
104
+ "_outputConfiguration": {},
105
+ "_hidden": false,
106
+ "_hasHelpOption": true,
107
+ "_helpFlags": "-h, --help",
108
+ "_helpDescription": "display help for command",
109
+ "_helpShortFlag": "-h",
110
+ "_helpLongFlag": "--help",
111
+ "_helpCommandName": "help",
112
+ "_helpCommandnameAndArgs": "help [command]",
113
+ "_helpCommandDescription": "display help for command",
114
+ "_helpConfiguration": {}
115
+ }"
39
116
  `;
@@ -1,132 +1,77 @@
1
- import path from 'node:path'
2
1
  import execa from 'execa'
3
- import ora from 'ora'
4
2
  import { createArgument, print } from '@toptal/davinci-cli-shared'
5
- import { fileURLToPath } from 'node:url'
6
3
 
7
- import getPackages from '../utils/get-packages.js'
4
+ import { getDepcruiseCommandOptions } from '../utils/graph/utils.js'
5
+ import { getPackageLocations } from '../utils/graph/packages.js'
8
6
 
9
- /**
10
- * @return {Array<string>}
11
- */
12
- const getPackageLocations = () => {
13
- const packages = getPackages(process.cwd())
7
+ export const graphGenerateCommand = async ({
8
+ outputFile,
9
+ ...depcruiseOptions
10
+ }) => {
11
+ console.log(print.davinciGradient('Dependency graph generator\n'))
14
12
 
15
- if (packages.length === 0) {
16
- print.red('Your project does not seem to be a monorepo. Exiting...')
17
- process.exit(1)
18
- }
19
-
20
- return packages.map(({ location }) => location)
21
- }
13
+ const spinner = print.startSpinner('Getting Packages Locations...\n', {
14
+ prefixText: '[GRAPH GENERATOR]',
15
+ })
22
16
 
23
- /**
24
- * @param {string} baseBranch
25
- * @returns {Array<string>}
26
- */
27
- const getChangedFiles = baseBranch => {
28
17
  try {
29
- const { stdout } = execa.sync('git', [
30
- '--no-pager',
31
- 'diff',
32
- '--name-only',
33
- `origin/${baseBranch}`,
34
- ])
35
-
36
- return stdout.split('\n')
37
- } catch (e) {
38
- return []
39
- }
40
- }
41
-
42
- /**
43
- * @param {Array<string>} changedPackages
44
- * @returns {string}
45
- */
46
- const evaluateChangedPackages = changedPackages =>
47
- changedPackages.length === 0
48
- ? 'undefined'
49
- : `^(${changedPackages.join('|')})/`
50
-
51
- /**
52
- * @param {string} baseBranch
53
- * @param {Array<string>} packageLocations
54
- * @returns {Array<string>}
55
- */
56
- const getChangedPackages = (baseBranch, packageLocations) => {
57
- const changedFiles = getChangedFiles(baseBranch)
18
+ const packageLocations = getPackageLocations()
58
19
 
59
- return packageLocations.filter(packageLocation =>
60
- changedFiles.find(filePath => filePath.startsWith(packageLocation + '/'))
61
- )
62
- }
20
+ spinner.succeed('Packages Locations are fetched!')
63
21
 
64
- const graphGenerateCommand = async ({ outputFile, tsConfig, diff }) => {
65
- const __dirname = path.dirname(fileURLToPath(import.meta.url))
66
- const packageLocations = getPackageLocations()
22
+ spinner.start('Generating dependency graph...\n')
67
23
 
68
- const spinner = ora('Generating dependency graph...\n').start()
24
+ const graphGenerationOutput = await execa(
25
+ 'depcruise',
26
+ getDepcruiseCommandOptions({
27
+ ...depcruiseOptions,
28
+ packageLocations,
29
+ }),
30
+ { stdout: 'pipe' }
31
+ )
69
32
 
70
- const graphDefinitionProcess = execa('depcruise', [
71
- '--config',
72
- path.join(__dirname, '..', 'configs', 'depcruise-config.cjs'),
73
- '--ts-config',
74
- tsConfig,
75
- '--output-type',
76
- 'archi',
77
- '--include-only',
78
- `^(${packageLocations.join('|')})/`,
79
- '--collapse',
80
- `^(${packageLocations.join('|')})/`,
81
- ...(diff
82
- ? [
83
- '--reaches',
84
- evaluateChangedPackages(getChangedPackages(diff, packageLocations)),
85
- ]
86
- : []),
87
- '.',
88
- ])
89
- const graphTransformProcess = execa('dot', ['-T', 'svg', '-o', outputFile])
33
+ spinner.succeed('Dependency graph has been generated!')
90
34
 
91
- graphDefinitionProcess.stdout.pipe(graphTransformProcess.stdin)
35
+ spinner.start(`Creating SVG file to ${outputFile}...\n`)
92
36
 
93
- try {
94
- await graphDefinitionProcess
95
- await graphTransformProcess
37
+ await execa('dot', ['-T', 'svg', '-o', outputFile], {
38
+ input: graphGenerationOutput.stdout,
39
+ })
96
40
 
97
- spinner.succeed('Graph file is created!\n')
41
+ spinner.succeed('Graph SVG file is created!')
98
42
  } catch (error) {
99
- spinner.fail(`Failed to generate dependency graph. ${error.message}\n`)
43
+ spinner.fail()
44
+ console.error(error)
100
45
  process.exit(1)
101
46
  }
102
47
  }
103
48
 
104
- const graphGenerateCommandCreator = {
105
- action: (_, options) => graphGenerateCommand(options),
106
- command: 'graph',
107
- description: 'Generate dependency graph in a monorepo',
108
- args: [
109
- createArgument('<command>', 'Graph related command to execute').choices([
110
- 'generate',
111
- ]),
112
- ],
113
- options: [
114
- {
115
- name: '-o, --output-file [outputFile]',
116
- label: 'Output file name. Default is monorepo-graph.svg',
117
- default: 'monorepo-graph.svg',
118
- },
119
- {
120
- name: '--ts-config [tsConfig]',
121
- label: 'Path to typescript configuration file. Default is tsconfig.json',
122
- default: 'tsconfig.json',
123
- },
124
- {
125
- name: '-d, --diff [diff]',
126
- label:
127
- 'Branch to use as a base for comparison and display of affected packages',
128
- },
129
- ],
49
+ export const createGraphGenerateCommand = program => {
50
+ return program
51
+ .createCommand('graph')
52
+ .description('Generate dependency graph in a monorepo')
53
+ .addArgument(
54
+ createArgument('<command>', 'Graph related command to execute').choices([
55
+ 'generate',
56
+ ])
57
+ )
58
+ .option(
59
+ '-o, --output-file [outputFile]',
60
+ 'Output file name. Default is monorepo-graph.svg',
61
+ 'monorepo-graph.svg'
62
+ )
63
+ .option(
64
+ '--ts-config [tsConfig]',
65
+ 'Path to typescript configuration file. Default is tsconfig.json',
66
+ 'tsconfig.json'
67
+ )
68
+ .option(
69
+ '-d, --diff <branch>',
70
+ 'Branch to use as a base for comparison and display of affected packages'
71
+ )
72
+ .option(
73
+ '--webpack-config [webpackConfig]',
74
+ 'Path to webpack configuration file'
75
+ )
76
+ .action((_, options) => graphGenerateCommand(options))
130
77
  }
131
-
132
- export default graphGenerateCommandCreator
@@ -1,7 +1,23 @@
1
- import graphGenerateCommandCreator from './graph-generate.js'
1
+ import { jest } from '@jest/globals'
2
+ // eslint-disable-next-line import/no-extraneous-dependencies
3
+ import { Command } from 'commander'
4
+
5
+ import { createGraphGenerateCommand } from './graph-generate.js'
6
+
7
+ describe('createGraphGenerateCommand', () => {
8
+ let program
9
+
10
+ beforeEach(() => {
11
+ program = new Command()
12
+ })
13
+
14
+ afterEach(() => {
15
+ jest.clearAllMocks()
16
+ })
2
17
 
3
- describe('graphGenerateCommandCreator', () => {
4
18
  it('has the correct command structure', () => {
5
- expect(graphGenerateCommandCreator).toMatchSnapshot()
19
+ const command = createGraphGenerateCommand(program)
20
+
21
+ expect(JSON.stringify(command, null, 2)).toMatchSnapshot()
6
22
  })
7
23
  })
package/src/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import detectCircularityCommandCreator from './commands/detect-circularity.js'
2
2
  import metricsCommandCreator from './commands/metrics.js'
3
- import graphGenerateCommandCreator from './commands/graph-generate.js'
3
+ import { createGraphGenerateCommand } from './commands/graph-generate.js'
4
4
  import checkIfMonorepo from './utils/check-if-monorepo.js'
5
5
  import getPackages from './utils/get-packages.js'
6
6
 
@@ -12,7 +12,7 @@ export const utils = {
12
12
  export const commands = [
13
13
  detectCircularityCommandCreator,
14
14
  metricsCommandCreator,
15
- graphGenerateCommandCreator,
15
+ createGraphGenerateCommand,
16
16
  ]
17
17
 
18
18
  export default {
@@ -0,0 +1,83 @@
1
+ import execa from 'execa'
2
+
3
+ import getPackages from '../get-packages.js'
4
+
5
+ /**
6
+ * Get Locations of packages in monorepo
7
+ * @return {Array<string>}
8
+ * @example
9
+ * getPackageLocations()
10
+ * // => ['packages/picasso', 'packages/picasso-lab']
11
+ */
12
+ export const getPackageLocations = () => {
13
+ const packages = getPackages(process.cwd())
14
+
15
+ if (packages.length === 0) {
16
+ throw new Error('Your project does not seem to be a monorepo.')
17
+ }
18
+
19
+ return packages.map(({ location }) => location)
20
+ }
21
+
22
+ /**
23
+ * Transforms an array of package names to a regex string
24
+ * that can be used to match against a file path.
25
+ * Example:
26
+ * ['@toptal/picasso', '@toptal/picasso-lab'] => '^(@toptal\\/picasso|@toptal\\/picasso-lab)\\/'
27
+ * @param {Array<string>} packages
28
+ * @returns {string}
29
+ * @example
30
+ * transformPackagesToRegex(['@toptal/picasso', '@toptal/picasso-lab'])
31
+ * // => '^(@toptal\\/picasso|@toptal\\/picasso-lab)\\/'
32
+ * @example
33
+ * transformPackagesToRegex(['@toptal/picasso'])
34
+ * // => '^(@toptal\\/picasso)\\/'
35
+ * @example
36
+ * transformPackagesToRegex([])
37
+ * // => 'undefined'
38
+ * @example
39
+ */
40
+ export const transformPackagesToRegex = packages => {
41
+ if (packages.length === 0) {
42
+ return 'undefined'
43
+ }
44
+
45
+ const regex = packages
46
+ .map(packageName => packageName.replace('/', '\\/'))
47
+ .join('|')
48
+
49
+ return `^(${regex})/`
50
+ }
51
+
52
+ /**
53
+ * Get changed packages in a monorepo
54
+ * @param {string} baseBranch
55
+ * @param {Array<string>} packageLocations
56
+ * @returns {Array<string>}
57
+ * @example
58
+ * getChangedPackages('master', ['packages/picasso', 'packages/picasso-lab'])
59
+ * // => ['packages/picasso']
60
+ * @example
61
+ * getChangedPackages('master', [])
62
+ * // => []
63
+ */
64
+ export const getChangedPackages = (baseBranch, packageLocations) => {
65
+ const { stdout } = execa.sync('git', [
66
+ '--no-pager',
67
+ 'diff',
68
+ '--name-only',
69
+ `origin/${baseBranch}`,
70
+ ])
71
+
72
+ const changedFiles = stdout.split('\n')
73
+
74
+ return packageLocations.filter(packageLocation =>
75
+ changedFiles.find(filePath => filePath.startsWith(packageLocation + '/'))
76
+ )
77
+ }
78
+
79
+ export default {
80
+ getPackageLocations,
81
+ getChangedPackages,
82
+ transformPackagesToRegex,
83
+ }
@@ -0,0 +1,109 @@
1
+ import { describe, jest } from '@jest/globals'
2
+ import execa from 'execa'
3
+
4
+ const mockedGetPackages = jest.fn()
5
+
6
+ jest.unstable_mockModule('../get-packages.js', () => ({
7
+ __esModule: true,
8
+ default: mockedGetPackages,
9
+ }))
10
+
11
+ const { getPackageLocations, transformPackagesToRegex, getChangedPackages } =
12
+ await import('./packages.js')
13
+
14
+ describe('getPackageLocations', () => {
15
+ beforeEach(() => {
16
+ mockedGetPackages.mockImplementation(() => [
17
+ {
18
+ location: 'packages/picasso',
19
+ name: '@toptal/picasso',
20
+ },
21
+ {
22
+ location: 'packages/picasso-lab',
23
+ name: '@toptal/picasso-lab',
24
+ },
25
+ {
26
+ location: 'packages/picasso-charts',
27
+ name: '@toptal/picasso-charts',
28
+ },
29
+ ])
30
+ })
31
+
32
+ afterEach(() => {
33
+ jest.resetAllMocks()
34
+ })
35
+
36
+ it('returns package locations', () => {
37
+ expect(getPackageLocations()).toEqual([
38
+ 'packages/picasso',
39
+ 'packages/picasso-lab',
40
+ 'packages/picasso-charts',
41
+ ])
42
+ })
43
+
44
+ describe('when there are no packages', () => {
45
+ it('throws an error', () => {
46
+ mockedGetPackages.mockImplementation(() => [])
47
+
48
+ expect(getPackageLocations).toThrow(
49
+ 'Your project does not seem to be a monorepo.'
50
+ )
51
+ })
52
+ })
53
+ })
54
+
55
+ describe('transformPackagesToRegex', () => {
56
+ it('transforms an array of package names to a regex string', () => {
57
+ expect(
58
+ transformPackagesToRegex(['@toptal/picasso', '@toptal/picasso-lab'])
59
+ ).toBe('^(@toptal\\/picasso|@toptal\\/picasso-lab)/')
60
+ })
61
+
62
+ it('transforms an array with single package to a regex string', () => {
63
+ expect(transformPackagesToRegex(['@toptal/picasso'])).toBe(
64
+ '^(@toptal\\/picasso)/'
65
+ )
66
+ })
67
+
68
+ describe('when there are no packages', () => {
69
+ it('returns undefined', () => {
70
+ expect(transformPackagesToRegex([])).toBe('undefined')
71
+ })
72
+ })
73
+ })
74
+
75
+ describe('getChangedPackages', () => {
76
+ const baseBranch = 'master'
77
+ const packageLocations = ['packages/picasso', 'packages/picasso-lab']
78
+
79
+ it('returns changed packages', async () => {
80
+ jest.spyOn(execa, 'sync').mockImplementation(() => ({
81
+ stdout: 'packages/picasso/\npackages/picasso-lab/',
82
+ }))
83
+
84
+ expect(await getChangedPackages(baseBranch, packageLocations)).toEqual([
85
+ 'packages/picasso',
86
+ 'packages/picasso-lab',
87
+ ])
88
+ })
89
+
90
+ describe('when there are changed files that are not related to packages', () => {
91
+ it('returns an empty array', async () => {
92
+ jest.spyOn(execa, 'sync').mockImplementation(() => ({
93
+ stdout: 'packages/picasso2/\npackages/picasso-lab3/\nREADME.md',
94
+ }))
95
+
96
+ expect(await getChangedPackages(baseBranch, packageLocations)).toEqual([])
97
+ })
98
+ })
99
+
100
+ describe('when there are no changed packages', () => {
101
+ it('returns an empty array', async () => {
102
+ jest.spyOn(execa, 'sync').mockImplementation(() => ({
103
+ stdout: '',
104
+ }))
105
+
106
+ expect(await getChangedPackages(baseBranch, packageLocations)).toEqual([])
107
+ })
108
+ })
109
+ })
@@ -0,0 +1,65 @@
1
+ import { fileURLToPath } from 'node:url'
2
+ import path from 'node:path'
3
+
4
+ import { getChangedPackages, transformPackagesToRegex } from './packages.js'
5
+
6
+ /**
7
+ * Get depcruise command options
8
+ * @param {Object} options
9
+ * @param {string} options.tsConfig
10
+ * @param {string} options.webpackConfig
11
+ * @param {string} options.diff
12
+ * @param {Array<string>} options.packageLocations
13
+ * @returns {Array<string>}
14
+ * @example
15
+ * getDepcruiseCommandOptions({
16
+ * tsConfig: 'tsconfig.json',
17
+ * webpackConfig: 'webpack.config.js',
18
+ * diff: 'master',
19
+ * packageLocations: ['packages/picasso', 'packages/picasso-lab']
20
+ * })
21
+ * // => [
22
+ * // '--config',
23
+ * // 'packages/monorepo/src/utils/graph/configs/depcruise-config.cjs',
24
+ * // '--ts-config',
25
+ * // 'tsconfig.json',
26
+ * // '--output-type',
27
+ * // 'archi',
28
+ * // '--include-only',
29
+ * // '^(@toptal\\/picasso|@toptal\\/picasso-lab)\\/',
30
+ * // '--collapse',
31
+ * // '^(@toptal\\/picasso|@toptal\\/picasso-lab)\\/',
32
+ * // '--reaches',
33
+ * // '^(@toptal\\/picasso)\\/',
34
+ * // '.',
35
+ * // ]
36
+ */
37
+ export const getDepcruiseCommandOptions = options => {
38
+ const { tsConfig, webpackConfig, diff, packageLocations } = options
39
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
40
+
41
+ return [
42
+ '--config',
43
+ path.join(__dirname, '../../', 'configs', 'depcruise-config.cjs'),
44
+ '--ts-config',
45
+ tsConfig,
46
+ '--output-type',
47
+ 'archi',
48
+ '--include-only',
49
+ transformPackagesToRegex(packageLocations),
50
+ '--collapse',
51
+ transformPackagesToRegex(packageLocations),
52
+ ...(webpackConfig ? ['--webpack-config', webpackConfig] : []),
53
+ ...(diff
54
+ ? [
55
+ '--reaches',
56
+ transformPackagesToRegex(getChangedPackages(diff, packageLocations)),
57
+ ]
58
+ : []),
59
+ '.',
60
+ ]
61
+ }
62
+
63
+ export default {
64
+ getDepcruiseCommandOptions,
65
+ }
@@ -0,0 +1,102 @@
1
+ import { jest } from '@jest/globals'
2
+ import path from 'node:path'
3
+
4
+ const mockedGetChangedPackages = jest.fn()
5
+ const mockedTransformPackagesToRegex = jest.fn()
6
+
7
+ jest.unstable_mockModule('./packages.js', () => ({
8
+ getChangedPackages: mockedGetChangedPackages,
9
+ transformPackagesToRegex: mockedTransformPackagesToRegex,
10
+ }))
11
+
12
+ const { getDepcruiseCommandOptions } = await import('./utils.js')
13
+
14
+ describe('getDepcruiseCommandOptions', () => {
15
+ beforeEach(() => {
16
+ mockedGetChangedPackages.mockReturnValue(['packages/picasso-lab'])
17
+
18
+ mockedTransformPackagesToRegex.mockImplementation(packages =>
19
+ packages.join('|')
20
+ )
21
+
22
+ jest
23
+ .spyOn(path, 'join')
24
+ .mockReturnValue(
25
+ 'packages/monorepo/src/utils/graph/configs/depcruise-config.cjs'
26
+ )
27
+ })
28
+
29
+ afterEach(() => {
30
+ jest.resetAllMocks()
31
+ })
32
+
33
+ const options = {
34
+ tsConfig: 'tsconfig.json',
35
+ webpackConfig: 'webpack.config.js',
36
+ diff: 'master',
37
+ packageLocations: ['packages/picasso', 'packages/picasso-lab'],
38
+ }
39
+
40
+ it('returns an array with all the required depcruise options', () => {
41
+ const result = getDepcruiseCommandOptions(options)
42
+
43
+ expect(result).toEqual([
44
+ '--config',
45
+ 'packages/monorepo/src/utils/graph/configs/depcruise-config.cjs',
46
+ '--ts-config',
47
+ 'tsconfig.json',
48
+ '--output-type',
49
+ 'archi',
50
+ '--include-only',
51
+ 'packages/picasso|packages/picasso-lab',
52
+ '--collapse',
53
+ 'packages/picasso|packages/picasso-lab',
54
+ '--webpack-config',
55
+ 'webpack.config.js',
56
+ '--reaches',
57
+ 'packages/picasso-lab',
58
+ '.',
59
+ ])
60
+ })
61
+
62
+ describe('when webpackConfig is not provided', () => {
63
+ it('does not include webpackConfig in the result', () => {
64
+ const result = getDepcruiseCommandOptions({
65
+ ...options,
66
+ webpackConfig: undefined,
67
+ })
68
+
69
+ expect(result).not.toContain('--webpack-config')
70
+ })
71
+ })
72
+
73
+ describe('when diff is not provided', () => {
74
+ it('does not include diff in the result', () => {
75
+ const result = getDepcruiseCommandOptions({
76
+ ...options,
77
+ diff: undefined,
78
+ })
79
+
80
+ expect(result).not.toContain('--reaches')
81
+ })
82
+ })
83
+
84
+ describe('when diff is provided', () => {
85
+ it('calls getChangedPackages with the correct arguments', () => {
86
+ getDepcruiseCommandOptions(options)
87
+
88
+ expect(mockedGetChangedPackages).toHaveBeenCalledWith('master', [
89
+ 'packages/picasso',
90
+ 'packages/picasso-lab',
91
+ ])
92
+ })
93
+
94
+ it('calls transformPackagesToRegex with the correct arguments', () => {
95
+ getDepcruiseCommandOptions(options)
96
+
97
+ expect(mockedTransformPackagesToRegex).toHaveBeenCalledWith([
98
+ 'packages/picasso-lab',
99
+ ])
100
+ })
101
+ })
102
+ })