Skip to content

Commit

Permalink
resolve #2004, ask for approval before exporting a component to anoth…
Browse files Browse the repository at this point in the history
…er scope (fork), unless --force was used
  • Loading branch information
davidfirst committed Sep 19, 2019
1 parent 8640fb7 commit d5c23fb
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 24 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

## [unreleased]

- [#2004](https://github.com/teambit/bit/issues/2004) ask for approval before exporting a component to another scope (fork)
- [#1956](https://github.com/teambit/bit/issues/1956) introduce a new flag `--codemod` for `bit export` to replace the import/require statements in the source to the newly exported scope
- fix "Converting circular structure to JSON" error when logging a circular metadata object
- fix exporting to a different scope than workspace configuration of `defaultScope`
Expand Down
21 changes: 10 additions & 11 deletions e2e/commands/export.e2e.1.js
Original file line number Diff line number Diff line change
Expand Up @@ -835,9 +835,10 @@ describe('bit export command', function () {
before(() => {
output = helper.general.runWithTryCatch(`bit export ${forkScope}`);
});
it('should throw an error warning about the scope change and suggesting to use --force', () => {
expect(output).to.have.string('is about to change the scope');
expect(output).to.have.string('please use "--force" flag');
it('should prompt for a confirmation before forking', () => {
expect(output).to.have.string(
`bit is about to fork the following components and export them to ${forkScope}.`
);
});
it('should not export anything', () => {
const remoteScope = helper.command.listScopeParsed(forkScope);
Expand All @@ -846,7 +847,7 @@ describe('bit export command', function () {
});
describe('with --force', () => {
before(() => {
helper.command.export(`${forkScope} --force`);
helper.command.export(`${forkScope}`);
});
it('should export them all successfully', () => {
const remoteScope = helper.command.listScopeParsed(forkScope);
Expand All @@ -866,7 +867,7 @@ describe('bit export command', function () {
});
describe('export without --all flag', () => {
before(() => {
helper.command.export(`${forkScope} --force`);
helper.command.export(`${forkScope}`);
});
it('should export only the staged component', () => {
const remoteScope = helper.command.listScopeParsed(forkScope);
Expand All @@ -878,7 +879,7 @@ describe('bit export command', function () {
before(() => {
helper.scopeHelper.getClonedLocalScope(localScopeWithFoo2);
helper.scopeHelper.reInitRemoteScope(forkScopePath);
helper.command.export(`${forkScope} --force --all`);
helper.command.export(`${forkScope} --all`);
});
it('should export all even non-staged components', () => {
const remoteScope = helper.command.listScopeParsed(forkScope);
Expand All @@ -895,7 +896,7 @@ describe('bit export command', function () {
helper.fixtures.createComponentBarFoo(fixtures.barFooModulePath(helper.scopes.remote));
helper.env.importDummyCompiler();
helper.command.tagScope('1.0.0');
helper.command.export(`${forkScope} --include-dependencies --force --codemod`);
helper.command.export(`${forkScope} --include-dependencies --codemod`);
});
it('should not change the files locally on the workspace', () => {
const barFoo = helper.fs.readFile('bar/foo.js');
Expand Down Expand Up @@ -931,7 +932,7 @@ describe('bit export command', function () {
helper.env.importDummyCompiler();
helper.command.tagScope('1.0.0');
localBeforeFork = helper.scopeHelper.cloneLocalScope();
helper.command.export(`${forkScope} --include-dependencies --force --set-current-scope --codemod`);
helper.command.export(`${forkScope} --include-dependencies --set-current-scope --codemod`);
});
it('should change the files locally on the workspace', () => {
const barFoo = helper.fs.readFile('bar/foo.js');
Expand Down Expand Up @@ -975,9 +976,7 @@ describe('bit export command', function () {

helper.scopeHelper.reInitRemoteScope(forkScopePath);
helper.scopeHelper.addRemoteScope(forkScopePath);
output = helper.command.export(
`${forkScope} --include-dependencies --force --set-current-scope --codemod --all`
);
output = helper.command.export(`${forkScope} --include-dependencies --set-current-scope --codemod --all`);
});
it('should change the files locally on the workspace', () => {
const barFoo = helper.fs.readFile('components/bar/foo/foo.js');
Expand Down
22 changes: 13 additions & 9 deletions src/api/consumer/lib/export.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/** @flow */
import R from 'ramda';
import yn from 'yn';
import pMapSeries from 'p-map-series';
import path from 'path';
import fs from 'fs-extra';
Expand All @@ -21,6 +22,7 @@ import GeneralError from '../../../error/general-error';
import { COMPONENT_ORIGINS } from '../../../constants';
import ManyComponentsWriter from '../../../consumer/component-ops/many-components-writer';
import * as packageJsonUtils from '../../../consumer/component/package-json-utils';
import { forkComponentsPrompt } from '../../../prompts';

export default (async function exportAction(params: {
ids: string[],
Expand Down Expand Up @@ -117,6 +119,15 @@ async function getComponentsToExport(
const [idsToExport, missingScope] = R.partition(id => id.hasScope() || defaultScope, bitIds);
return { idsToExport: BitIds.fromArray(idsToExport), missingScope };
};
const promptForFork = async (bitIds: BitIds | BitId[]) => {
if (force || !remote) return;
const idsToFork = bitIds.filter(id => id.scope && id.scope !== remote);
if (!idsToFork.length) return;
const forkPromptResult = await forkComponentsPrompt(idsToFork, remote)();
if (!yn(forkPromptResult.shouldFork)) {
throw new GeneralError('the operation has been canceled');
}
};
if (!ids || !ids.length || idsHaveWildcard) {
loader.start(BEFORE_LOADING_COMPONENTS);
const exportPendingComponents: BitIds = includeNonStaged
Expand All @@ -125,15 +136,7 @@ async function getComponentsToExport(
const componentsToExport = idsHaveWildcard // $FlowFixMe ids are set at this point
? ComponentsList.filterComponentsByWildcard(exportPendingComponents, ids)
: exportPendingComponents;
if (!force && remote) {
componentsToExport.forEach((id) => {
if (id.scope && id.scope !== remote) {
throw new GeneralError( // $FlowFixMe
`a component "${id.toString()}" is about to change the scope to "${remote}", if this is done deliberately, please use "--force" flag`
);
}
});
}
await promptForFork(componentsToExport);
const loaderMsg = componentsToExport.length > 1 ? BEFORE_EXPORTS : BEFORE_EXPORT;
loader.start(loaderMsg);
return filterNonScopeIfNeeded(componentsToExport);
Expand All @@ -154,6 +157,7 @@ async function getComponentsToExport(
});
loader.start(BEFORE_EXPORT); // show single export
const idsToExport = await Promise.all(idsToExportP);
await promptForFork(idsToExport);
return filterNonScopeIfNeeded(BitIds.fromArray(idsToExport));
}

Expand Down
4 changes: 2 additions & 2 deletions src/cli/commands/public-cmds/export-cmd.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ export default class Export extends Command {
alias = 'e';
opts = [
['e', 'eject', 'replaces the exported components from the local scope with the corresponding packages'],
['f', 'force', 'force changing a component remote when exporting multiple components'],
['a', 'all', 'export all components include non-staged'],
[
'd',
Expand All @@ -36,7 +35,8 @@ export default class Export extends Command {
'c',
'codemod',
'EXPERIMENTAL. when exporting to a different scope, replace import/require statements in the source code to the new scope'
]
],
['f', 'force', 'force changing a component remote without asking for a confirmation']
];
loader = true;
migration = true;
Expand Down
3 changes: 2 additions & 1 deletion src/e2e-helper/e2e-command-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@ export default class CommandHelper {
return this.runCmd(`bit export ${CURRENT_UPSTREAM} ${ids || ''}`);
}
export(options?: string = '') {
return this.runCmd(`bit export ${options}`);
// --force just silents the prompt, which obviously needed for CIs
return this.runCmd(`bit export --force ${options}`);
}
ejectComponents(ids: string, flags?: string) {
return this.runCmd(`bit eject ${ids} ${flags || ''}`);
Expand Down
12 changes: 11 additions & 1 deletion src/prompts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import removeSchema from './schemas/remote-remove';
import resolveConflictSchema from './schemas/resolve-conflict';
import analyticsSchema from './schemas/analytics-reporting';
import errorReportingSchema from './schemas/error-reporting';
import forkComponentsSchema from './schemas/fork-components';
import prompt from './prompt';

const passphrase = prompt(passphraseSchema);
Expand All @@ -12,5 +13,14 @@ const removePrompt = prompt(removeSchema);
const resolveConflictPrompt = prompt(resolveConflictSchema);
const analyticsPrompt = prompt(analyticsSchema);
const errorReportingPrompt = prompt(errorReportingSchema);
const forkComponentsPrompt = (bitIds, remote) => prompt(forkComponentsSchema(bitIds, remote));

export { passphrase, userpass, removePrompt, resolveConflictPrompt, analyticsPrompt, errorReportingPrompt };
export {
passphrase,
userpass,
removePrompt,
resolveConflictPrompt,
analyticsPrompt,
errorReportingPrompt,
forkComponentsPrompt
};
36 changes: 36 additions & 0 deletions src/prompts/schemas/fork-components.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/** @flow */
import chalk from 'chalk';
import BitId from '../../bit-id/bit-id';

/**
* schema for forking components
*/
export default function (bitIds: BitId[], remote: string) {
return {
properties: {
shouldFork: {
required: true,
description: `bit is about to fork the following components and export them to ${chalk.bold(remote)}.
\t${bitIds.map(id => chalk.bold(id.toStringWithoutVersion())).join('\n\t')}
also, if they're staged, bit will not change their status to exported unless '--set-current-scope' flag is used.
there are additional flags for the 'export' command to specifically handle forking components:
1. '--include-dependencies' exports all dependencies to the destination alongside the component.
2. '--set-current-scope' sets your workspace to use the destination scope as the main remote for the component.
3. '--codemod' changes all dependencies to point to the new destination.
would you like to proceed with forking the components? (yes/no)`,
message: 'please type yes or no.',
type: 'string',
conform(value: string) {
return (
value.toLowerCase() === 'y' ||
value.toLowerCase() === 'n' ||
value.toLowerCase() === 'yes' ||
value.toLowerCase() === 'no'
);
}
}
}
};
}

0 comments on commit d5c23fb

Please sign in to comment.