From c9ffc601749dae6619e541d969173ac051e6f573 Mon Sep 17 00:00:00 2001 From: Przemyslaw Zan Date: Mon, 5 Dec 2022 16:38:42 +0100 Subject: [PATCH 1/6] Added support for presets. --- index.js | 2 + lib/commands/save.js | 34 ++- lib/utils/getoptions.js | 12 +- tests/commands/save.js | 219 +++++++++++++++++- .../fixtures/project-with-presets/mrgit.json | 12 + tests/utils/getoptions.js | 32 +++ 6 files changed, 302 insertions(+), 9 deletions(-) create mode 100644 tests/fixtures/project-with-presets/mrgit.json diff --git a/index.js b/index.js index fa5d537..0ed3333 100755 --- a/index.js +++ b/index.js @@ -114,6 +114,8 @@ function handleCli() { ${ y( '--scope' ) } Restricts the command to packages which names match the given glob pattern. ${ g( 'Default: null' ) } + ${ y( '--preset' ) } Uses an alternative set of dependencies defined in the config file. + ${ u( 'Git Options:' ) } Git options are supported by the following commands: commit, diff, fetch, push. Type "mrgit [command] -h" in order to see which options are supported. diff --git a/lib/commands/save.js b/lib/commands/save.js index 04c3216..527c75e 100644 --- a/lib/commands/save.js +++ b/lib/commands/save.js @@ -99,8 +99,9 @@ module.exports = { * * @param {Set} processedPackages Collection of processed packages. * @param {Set} commandResponses Results of executed command for each package. + * @param {Options} toolOptions Options resolved by mrgit. */ - afterExecute( processedPackages, commandResponses ) { + afterExecute( processedPackages, commandResponses, toolOptions ) { const cwd = require( '../utils/getcwd' )(); const mrgitJsonPath = path.join( cwd, 'mrgit.json' ); @@ -112,15 +113,42 @@ module.exports = { .replace( tagPattern, '' ) .split( '#' )[ 0 ]; + const objectToUpdate = getObjectToUpdate( json, toolOptions, response.packageName ); + // If returned branch is equal to 'master', save only the repository path. if ( response.branch && response.data === 'master' ) { - json.dependencies[ response.packageName ] = repository; + objectToUpdate[ response.packageName ] = repository; } else { - json.dependencies[ response.packageName ] = `${ repository }#${ response.data }`; + objectToUpdate[ response.packageName ] = `${ repository }#${ response.data }`; } } return json; } ); + + /** + * If preset is being used it should update the value defined in the preset, + * rather than the one in the base "dependencies" object. + * + * @param {Object} json + * @param {Options} toolOptions + * @param {String} packageName + * @returns + */ + function getObjectToUpdate( json, toolOptions ) { + if ( !toolOptions.preset ) { + return json.dependencies; + } + + if ( !json.presets ) { + return json.dependencies; + } + + if ( !json.presets[ toolOptions.preset ] ) { + return json.dependencies; + } + + return json.presets[ toolOptions.preset ]; + } } }; diff --git a/lib/utils/getoptions.js b/lib/utils/getoptions.js index caf12e1..649b1d1 100644 --- a/lib/utils/getoptions.js +++ b/lib/utils/getoptions.js @@ -14,7 +14,7 @@ const shell = require( 'shelljs' ); * @param {String} cwd An absolute path to the directory where `mrgit.json` is available. * @returns {Options} The options object. */ -module.exports = function cwdResolver( callOptions, cwd ) { +module.exports = function getOptions( callOptions, cwd ) { const mrgitJsonPath = path.resolve( cwd, 'mrgit.json' ); // Default options. @@ -52,6 +52,16 @@ module.exports = function cwdResolver( callOptions, cwd ) { options.cwdPackageBranch = response.stdout.trim(); } + if ( !options.preset ) { + return options; + } + + if ( !options.presets || !options.presets[ options.preset ] ) { + throw new Error( `Preset "${ options.preset }" is not defined in "mrgit.json" file.` ); + } + + options.dependencies = Object.assign( options.dependencies, options.presets[ options.preset ] ); + return options; }; diff --git a/tests/commands/save.js b/tests/commands/save.js index f329915..1a5aeb2 100644 --- a/tests/commands/save.js +++ b/tests/commands/save.js @@ -176,7 +176,7 @@ describe( 'commands/save', () => { } ); describe( 'afterExecute()', () => { - it( 'updates collected hashes in "mrgit.json" (--hash option)', () => { + it( 'updates collected hashes in "mrgit.json" (--hash option, default behavior)', () => { const processedPackages = new Set(); const commandResponses = new Set(); @@ -196,7 +196,7 @@ describe( 'commands/save', () => { branch: false } ); - saveCommand.afterExecute( processedPackages, commandResponses ); + saveCommand.afterExecute( processedPackages, commandResponses, toolOptions ); let json = { dependencies: { @@ -238,7 +238,7 @@ describe( 'commands/save', () => { branch: true } ); - saveCommand.afterExecute( processedPackages, commandResponses ); + saveCommand.afterExecute( processedPackages, commandResponses, toolOptions ); let json = { dependencies: { @@ -260,6 +260,215 @@ describe( 'commands/save', () => { } ); } ); + it( 'updates collected hashes in "mrgit.json" (--preset option)', () => { + const processedPackages = new Set(); + const commandResponses = new Set(); + + processedPackages.add( 'test-package' ); + processedPackages.add( 'package-test' ); + + commandResponses.add( { + packageName: 'test-package', + data: '584f341', + hash: true, + branch: false + } ); + commandResponses.add( { + packageName: 'package-test', + data: '52910fe', + hash: true, + branch: false + } ); + + saveCommand.afterExecute( processedPackages, commandResponses, toolOptions ); + + let json = { + dependencies: { + 'test-package': 'organization/test-package', + 'package-test': 'organization/package-test' + }, + preset: 'development', + presets: { + development: { + 'test-package': 'organization/test-package#development', + 'package-test': 'organization/package-test#development' + } + } + }; + + toolOptions.preset = 'development'; + + expect( mrgitJsonPath ).to.equal( normalizedDirname + '/mrgit.json' ); + expect( updateFunction ).to.be.a( 'function' ); + + json = updateFunction( json ); + + expect( json ).to.deep.equal( { + dependencies: { + 'test-package': 'organization/test-package', + 'package-test': 'organization/package-test' + }, + preset: 'development', + presets: { + development: { + 'test-package': 'organization/test-package#584f341', + 'package-test': 'organization/package-test#52910fe' + } + } + } ); + } ); + + it( 'adds new value to the preset if its not in it, but it is in the base dependencies (--preset option)', () => { + const processedPackages = new Set(); + const commandResponses = new Set(); + + processedPackages.add( 'test-package' ); + processedPackages.add( 'package-test' ); + + commandResponses.add( { + packageName: 'test-package', + data: '584f341', + hash: true, + branch: false + } ); + commandResponses.add( { + packageName: 'package-test', + data: '52910fe', + hash: true, + branch: false + } ); + + saveCommand.afterExecute( processedPackages, commandResponses, toolOptions ); + + let json = { + dependencies: { + 'test-package': 'organization/test-package', + 'package-test': 'organization/package-test' + }, + preset: 'development', + presets: { + development: { + 'test-package': 'organization/test-package#development' + } + } + }; + + toolOptions.preset = 'development'; + + expect( mrgitJsonPath ).to.equal( normalizedDirname + '/mrgit.json' ); + expect( updateFunction ).to.be.a( 'function' ); + + json = updateFunction( json ); + + expect( json ).to.deep.equal( { + dependencies: { + 'test-package': 'organization/test-package', + 'package-test': 'organization/package-test' + }, + preset: 'development', + presets: { + development: { + 'test-package': 'organization/test-package#584f341', + 'package-test': 'organization/package-test#52910fe' + } + } + } ); + } ); + + it( 'updates base dependencies if specified preset is not defined (--preset option)', () => { + const processedPackages = new Set(); + const commandResponses = new Set(); + + processedPackages.add( 'test-package' ); + processedPackages.add( 'package-test' ); + + commandResponses.add( { + packageName: 'test-package', + data: '584f341', + hash: true, + branch: false + } ); + commandResponses.add( { + packageName: 'package-test', + data: '52910fe', + hash: true, + branch: false + } ); + + saveCommand.afterExecute( processedPackages, commandResponses, toolOptions ); + + let json = { + dependencies: { + 'test-package': 'organization/test-package', + 'package-test': 'organization/package-test' + }, + preset: 'development', + presets: {} + }; + + toolOptions.preset = 'development'; + + expect( mrgitJsonPath ).to.equal( normalizedDirname + '/mrgit.json' ); + expect( updateFunction ).to.be.a( 'function' ); + + json = updateFunction( json ); + + expect( json ).to.deep.equal( { + dependencies: { + 'test-package': 'organization/test-package#584f341', + 'package-test': 'organization/package-test#52910fe' + }, + preset: 'development', + presets: {} + } ); + } ); + + it( 'updates base dependencies if specified presets are not defined (--preset option)', () => { + const processedPackages = new Set(); + const commandResponses = new Set(); + + processedPackages.add( 'test-package' ); + processedPackages.add( 'package-test' ); + + commandResponses.add( { + packageName: 'test-package', + data: '584f341', + hash: true, + branch: false + } ); + commandResponses.add( { + packageName: 'package-test', + data: '52910fe', + hash: true, + branch: false + } ); + + saveCommand.afterExecute( processedPackages, commandResponses, toolOptions ); + + let json = { + dependencies: { + 'test-package': 'organization/test-package', + 'package-test': 'organization/package-test' + }, + preset: 'development' + }; + + toolOptions.preset = 'development'; + + expect( mrgitJsonPath ).to.equal( normalizedDirname + '/mrgit.json' ); + expect( updateFunction ).to.be.a( 'function' ); + + json = updateFunction( json ); + + expect( json ).to.deep.equal( { + dependencies: { + 'test-package': 'organization/test-package#584f341', + 'package-test': 'organization/package-test#52910fe' + }, + preset: 'development' + } ); + } ); + it( 'does not save "#master" branch because it is default branch', () => { const processedPackages = new Set(); const commandResponses = new Set(); @@ -273,7 +482,7 @@ describe( 'commands/save', () => { branch: true } ); - saveCommand.afterExecute( processedPackages, commandResponses ); + saveCommand.afterExecute( processedPackages, commandResponses, toolOptions ); let json = { dependencies: { @@ -311,7 +520,7 @@ describe( 'commands/save', () => { branch: true } ); - saveCommand.afterExecute( processedPackages, commandResponses ); + saveCommand.afterExecute( processedPackages, commandResponses, toolOptions ); let json = { dependencies: { diff --git a/tests/fixtures/project-with-presets/mrgit.json b/tests/fixtures/project-with-presets/mrgit.json new file mode 100644 index 0000000..936d4bd --- /dev/null +++ b/tests/fixtures/project-with-presets/mrgit.json @@ -0,0 +1,12 @@ +{ + "packages": "packages/", + "dependencies": { + "linters-config": "foo/linters-config@latest", + "dev-tools": "foo/dev-tools@latest" + }, + "presets": { + "development": { + "dev-tools": "foo/dev-tools#developmentBranch" + } + } +} diff --git a/tests/utils/getoptions.js b/tests/utils/getoptions.js index 0db2ce9..bdf3878 100644 --- a/tests/utils/getoptions.js +++ b/tests/utils/getoptions.js @@ -178,5 +178,37 @@ describe( 'utils', () => { fsExistsStub.restore(); shelljsStub.restore(); } ); + + it( 'throws an error when --preset option is used, but presets are not defined in "mrgit.json"', () => { + try { + getOptions( { preset: 'foo' }, cwd ); + throw new Error( 'Expected to throw.' ); + } catch ( error ) { + expect( error.message ).to.equal( 'Preset "foo" is not defined in "mrgit.json" file.' ); + } + } ); + + it( 'throws an error when --preset option is used, but the specific preset is not defined in "mrgit.json"', () => { + const cwdForPresets = path.resolve( __dirname, '..', 'fixtures', 'project-with-presets' ); + + try { + getOptions( { preset: 'foo' }, cwdForPresets ); + throw new Error( 'Expected to throw.' ); + } catch ( error ) { + expect( error.message ).to.equal( 'Preset "foo" is not defined in "mrgit.json" file.' ); + } + } ); + + it( 'returns options with preset merged with dependencies when --preset option is used', () => { + const cwdForPresets = path.resolve( __dirname, '..', 'fixtures', 'project-with-presets' ); + + const options = getOptions( { preset: 'development' }, cwdForPresets ); + + expect( options ).to.have.property( 'dependencies' ); + expect( options.dependencies ).to.deep.equal( { + 'linters-config': 'foo/linters-config@latest', + 'dev-tools': 'foo/dev-tools#developmentBranch' + } ); + } ); } ); } ); From 5440fca1d96b0d69a6d42e2b8e8cd2db20c20c31 Mon Sep 17 00:00:00 2001 From: Przemyslaw Zan Date: Mon, 5 Dec 2022 17:16:00 +0100 Subject: [PATCH 2/6] Updated message displayed by sync command when being on a specific commit. --- lib/commands/status.js | 11 ++++++++--- tests/commands/status.js | 42 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/lib/commands/status.js b/lib/commands/status.js index 1b4cef4..3a46bce 100644 --- a/lib/commands/status.js +++ b/lib/commands/status.js @@ -146,11 +146,15 @@ module.exports = { shouldDisplaySyncHint = true; } - if ( !shouldUseTag && status.branch !== mrgitBranch ) { + if ( !shouldUseTag && status.branch !== mrgitBranch && commit !== mrgitBranch ) { branchOrTag = `${ chalk.cyan( '!' ) } ${ branchOrTag }`; shouldDisplaySyncHint = true; } + if ( !shouldUseTag && mrgitBranch === commit ) { + branchOrTag = 'Using saved commit →'; + } + if ( status.ahead ) { branchOrTag += chalk.yellow( ` ↑${ status.ahead }` ); } @@ -199,8 +203,9 @@ module.exports = { hints.push( [ chalk.green( 'L' ), 'This is the latest local tag. To ensure having latest remote tag, execute', - chalk.blue( 'mrgit sync' ) - ].join( ' ' ) + '.' ); + chalk.blue( 'mrgit fetch' ), + 'before checking status.' + ].join( ' ' ) ); } if ( shouldDisplaySyncHint ) { diff --git a/tests/commands/status.js b/tests/commands/status.js index 5f1a804..9ba83b9 100644 --- a/tests/commands/status.js +++ b/tests/commands/status.js @@ -627,6 +627,48 @@ describe( 'commands/status', () => { logStub.restore(); } ); + it( 'displays appropriate message when being checked out on a specific commit', () => { + const logStub = sinon.stub( console, 'log' ); + + const processedPackages = new Set(); + const commandResponses = new Set(); + + processedPackages.add( '@ckeditor/ckeditor5-bar' ); + + commandResponses.add( { + packageName: 'bar', + status: { + branch: 'HEAD (no branch)', + tag: 'v35.3.2', + detachedHead: true, + ahead: null, + behind: null, + staged: [], + modified: [], + untracked: [], + unmerged: [] + }, + commit: 'ef45678', + mrgitBranch: 'ef45678', + mrgitTag: undefined, + currentTag: 'v35.3.2', + latestTag: 'v35.3.2' + } ); + + statusCommand.afterExecute( processedPackages, commandResponses ); + + expect( stubs.table.push.firstCall.args[ 0 ] ).to.deep.equal( + [ 'bar', 'Using saved commit →', 'ef45678', '' ] + ); + + expect( stubs.chalk.cyan.callCount ).to.equal( 1 ); + + // Table + expect( stubs.chalk.cyan.getCall( 0 ).args[ 0 ] ).to.equal( '!' ); + + logStub.restore(); + } ); + it( 'sorts packages alphabetically', () => { const logStub = sinon.stub( console, 'log' ); From 2e434650dc4f97a6a9b3087d36b3d690012beb0c Mon Sep 17 00:00:00 2001 From: Przemyslaw Zan Date: Mon, 5 Dec 2022 19:15:59 +0100 Subject: [PATCH 3/6] Updated README.md. --- README.md | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 09a07cb..c0affa7 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,8 @@ CLI options: --scope Restricts the command to packages which names match the given glob pattern. Default: null + +--preset Uses an alternative set of dependencies defined in the config file. ``` All these options can also be specified in `mrgit.json` (options passed through CLI takes precedence): @@ -137,34 +139,54 @@ The dependency keys can be any strings, but it's recommended to use package name Examples: -```js +```json // Clone 'git@github.com:cksource/foo.git' and check out to 'master'. { "foo": "git@github.com:cksource/foo.git" } ``` -```js +```json // Short format. Clone 'git@github.com:cksource/foo.git' and check out to branch 'dev'. { "@cksource/foo": "cksource/foo#dev" } ``` -```js +```json // Clone 'https://github.com/cksource/foo.git' (via HTTPS) and check out to tag 'v1.2.3'. { "foo": "https://github.com/cksource/foo.git@v1.2.3" } ``` -```js +```json // Clone 'cksource/foo' and check out to the latest available tag. { "foo": "cksource/foo@latest" } ``` +### The `presets` option + +This option allows the user to create an easy switch between different states of the dependencies. When using any command with the `--preset` option, it will behave as if the `dependencies` option was using values from the given preset. Dependencies that are not defined in the preset but are defined in the `dependencies`, will use version from the `dependencies` as a fallback. + +Example: + +```json +{ + "presets": { + "dev": { + "@cksource/foo": "cksource/foo#dev" + }, + "newFeature": { + "@cksource/foo": "cksource/foo#newFeatureBranch", + "@cksource/bar": "cksource/foo#newFeatureBranch" + } + } +} +``` + ### Recursive cloning When the `--recursive` option is used `mrgit` will clone repositories recursively. First, it will clone the `dependencies` specified in `mrgit.json` and, then, their `dependencies` and `devDependencies` specified in `package.json` files located in cloned repositories. From aa4427adb8a4f7d8831b58193e1da7180f02fede Mon Sep 17 00:00:00 2001 From: Przemyslaw Zan Date: Tue, 6 Dec 2022 10:51:17 +0100 Subject: [PATCH 4/6] Review requests. --- README.md | 6 +++--- tests/utils/getoptions.js | 14 ++++---------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index c0affa7..8bcbf34 100644 --- a/README.md +++ b/README.md @@ -179,9 +179,9 @@ Example: "dev": { "@cksource/foo": "cksource/foo#dev" }, - "newFeature": { - "@cksource/foo": "cksource/foo#newFeatureBranch", - "@cksource/bar": "cksource/foo#newFeatureBranch" + "exampleFeature": { + "@cksource/foo": "cksource/foo#i/1-example-feature", + "@cksource/bar": "cksource/foo#i/1-example-feature" } } } diff --git a/tests/utils/getoptions.js b/tests/utils/getoptions.js index bdf3878..57a953a 100644 --- a/tests/utils/getoptions.js +++ b/tests/utils/getoptions.js @@ -180,23 +180,17 @@ describe( 'utils', () => { } ); it( 'throws an error when --preset option is used, but presets are not defined in "mrgit.json"', () => { - try { + expect( () => { getOptions( { preset: 'foo' }, cwd ); - throw new Error( 'Expected to throw.' ); - } catch ( error ) { - expect( error.message ).to.equal( 'Preset "foo" is not defined in "mrgit.json" file.' ); - } + } ).to.throw( Error, 'Preset "foo" is not defined in "mrgit.json" file.' ); } ); it( 'throws an error when --preset option is used, but the specific preset is not defined in "mrgit.json"', () => { const cwdForPresets = path.resolve( __dirname, '..', 'fixtures', 'project-with-presets' ); - try { + expect( () => { getOptions( { preset: 'foo' }, cwdForPresets ); - throw new Error( 'Expected to throw.' ); - } catch ( error ) { - expect( error.message ).to.equal( 'Preset "foo" is not defined in "mrgit.json" file.' ); - } + } ).to.throw( Error, 'Preset "foo" is not defined in "mrgit.json" file.' ); } ); it( 'returns options with preset merged with dependencies when --preset option is used', () => { From cc7d94138941908488c635ccc15e1340b6b6a296 Mon Sep 17 00:00:00 2001 From: przemyslaw-zan <69513154+przemyslaw-zan@users.noreply.github.com> Date: Tue, 6 Dec 2022 11:05:49 +0100 Subject: [PATCH 5/6] Update README.md Co-authored-by: Kamil Piechaczek --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8bcbf34..9573f99 100644 --- a/README.md +++ b/README.md @@ -179,7 +179,7 @@ Example: "dev": { "@cksource/foo": "cksource/foo#dev" }, - "exampleFeature": { + "example-feature": { "@cksource/foo": "cksource/foo#i/1-example-feature", "@cksource/bar": "cksource/foo#i/1-example-feature" } From 4d65d82f3251fdf72f4e61bed420f0a5780c48c4 Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Tue, 6 Dec 2022 20:35:01 +0100 Subject: [PATCH 6/6] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9573f99..9c627a8 100644 --- a/README.md +++ b/README.md @@ -169,7 +169,7 @@ Examples: ### The `presets` option -This option allows the user to create an easy switch between different states of the dependencies. When using any command with the `--preset` option, it will behave as if the `dependencies` option was using values from the given preset. Dependencies that are not defined in the preset but are defined in the `dependencies`, will use version from the `dependencies` as a fallback. +This option allows the user to switch between different states of dependencies easily. When using any command with the `--preset` option, it will behave as if the `dependencies` option was using values from the given preset. Dependencies not specified in the preset but in the `dependencies` object will use a version from the latter as a fallback. Example: