@teambit/eject 1.0.107 → 1.0.108

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.
@@ -0,0 +1,197 @@
1
+ /**
2
+ * a classic use case of eject is when a user imports a component using `bit import` to update it,
3
+ * but the user has no intention to have the code as part of the project source code.
4
+ * the eject provides the option to delete the component locally and install it via the NPM client.
5
+ *
6
+ * an implementation note, the entire process is done with rollback in mind.
7
+ * since installing the component via NPM client is an error prone process, we do it first, before
8
+ * removing the component files, so then it's easier to rollback.
9
+ */
10
+ import { Workspace } from '@teambit/workspace';
11
+ import { Consumer } from '@teambit/legacy/dist/consumer';
12
+ import { ComponentIdList, ComponentID } from '@teambit/component-id';
13
+ import defaultErrorHandler from '@teambit/legacy/dist/cli/default-error-handler';
14
+ import { getScopeRemotes } from '@teambit/legacy/dist/scope/scope-remotes';
15
+ import componentIdToPackageName from '@teambit/legacy/dist/utils/bit/component-id-to-package-name';
16
+ import Component from '@teambit/legacy/dist/consumer/component/consumer-component';
17
+ import PackageJsonFile from '@teambit/legacy/dist/consumer/component/package-json-file';
18
+ import * as packageJsonUtils from '@teambit/legacy/dist/consumer/component/package-json-utils';
19
+ import DataToPersist from '@teambit/legacy/dist/consumer/component/sources/data-to-persist';
20
+ import RemovePath from '@teambit/legacy/dist/consumer/component/sources/remove-path';
21
+ import { Logger } from '@teambit/logger';
22
+ import { InstallMain } from '@teambit/install';
23
+
24
+ export type EjectResults = {
25
+ ejectedComponents: ComponentIdList;
26
+ failedComponents: FailedComponents;
27
+ };
28
+
29
+ export type EjectOptions = {
30
+ force?: boolean; // eject although a component is modified/staged
31
+ keepFiles?: boolean; // keep component files on the workspace
32
+ };
33
+
34
+ type FailedComponents = {
35
+ modifiedComponents: ComponentIdList;
36
+ stagedComponents: ComponentIdList;
37
+ notExportedComponents: ComponentIdList;
38
+ selfHostedExportedComponents: ComponentIdList;
39
+ };
40
+
41
+ export class ComponentsEjector {
42
+ consumer: Consumer;
43
+ idsToEject: ComponentIdList;
44
+ componentsToEject: Component[] = [];
45
+ // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
46
+ notEjectedDependents: Array<{ dependent: Component; ejectedDependencies: Component[] }>;
47
+ failedComponents: FailedComponents;
48
+ // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
49
+ packageJsonFilesBeforeChanges: PackageJsonFile[]; // for rollback in case of errors
50
+ constructor(
51
+ private workspace: Workspace,
52
+ private install: InstallMain,
53
+ private logger: Logger,
54
+ private componentsIds: ComponentID[],
55
+ private ejectOptions: EjectOptions
56
+ ) {
57
+ this.consumer = this.workspace.consumer;
58
+ this.idsToEject = new ComponentIdList();
59
+ this.failedComponents = {
60
+ modifiedComponents: new ComponentIdList(),
61
+ stagedComponents: new ComponentIdList(),
62
+ notExportedComponents: new ComponentIdList(),
63
+ selfHostedExportedComponents: new ComponentIdList(),
64
+ };
65
+ }
66
+
67
+ async eject(): Promise<EjectResults> {
68
+ await this.decideWhichComponentsToEject();
69
+ this.logger.debug(`${this.idsToEject.length} to eject`);
70
+ await this.loadComponentsToEject();
71
+ if (this.idsToEject.length) {
72
+ this._validateIdsHaveScopesAndVersions();
73
+ await this.removeComponentsFromNodeModules();
74
+ await this.untrackComponents();
75
+ await this.installPackages();
76
+ await this.removeComponentsFiles();
77
+ await this.consumer.writeBitMap();
78
+ }
79
+ this.logger.debug('eject: completed successfully');
80
+ return {
81
+ ejectedComponents: this.idsToEject,
82
+ failedComponents: this.failedComponents,
83
+ };
84
+ }
85
+
86
+ async decideWhichComponentsToEject(): Promise<void> {
87
+ this.logger.setStatusLine('Eject: getting the components status');
88
+ if (!this.componentsIds.length) return;
89
+ const remotes = await getScopeRemotes(this.consumer.scope);
90
+ const hubExportedComponents = new ComponentIdList();
91
+ this.componentsIds.forEach((componentId) => {
92
+ const bitId = componentId;
93
+ if (!this.workspace.isExported(bitId)) this.failedComponents.notExportedComponents.push(bitId);
94
+ else if (remotes.isHub(bitId.scope as string)) hubExportedComponents.push(bitId);
95
+ else this.failedComponents.selfHostedExportedComponents.push(bitId);
96
+ });
97
+ if (this.ejectOptions.force) {
98
+ this.idsToEject = hubExportedComponents;
99
+ } else {
100
+ await Promise.all(
101
+ hubExportedComponents.map(async (id) => {
102
+ try {
103
+ const componentStatus = await this.consumer.getComponentStatusById(id);
104
+ if (componentStatus.modified) this.failedComponents.modifiedComponents.push(id);
105
+ else if (componentStatus.staged) this.failedComponents.stagedComponents.push(id);
106
+ else this.idsToEject.push(id);
107
+ } catch (err: any) {
108
+ this.throwEjectError(
109
+ `eject operation failed getting the status of ${id.toString()}, no action has been done.
110
+ please fix the issue to continue.`,
111
+ err
112
+ );
113
+ }
114
+ })
115
+ );
116
+ }
117
+ this.logger.consoleSuccess();
118
+ }
119
+
120
+ async loadComponentsToEject() {
121
+ // TODO: check if we really need the { loadExtensions: true } here
122
+ const { components } = await this.consumer.loadComponents(this.idsToEject, undefined, { loadExtensions: true });
123
+ this.componentsToEject = components;
124
+ }
125
+
126
+ async removeComponentsFromNodeModules() {
127
+ const action = 'Eject: removing the existing components from node_modules';
128
+ this.logger.setStatusLine(action);
129
+ this.logger.debug(action);
130
+ await packageJsonUtils.removeComponentsFromNodeModules(this.consumer, this.componentsToEject);
131
+ this.logger.consoleSuccess(action);
132
+ }
133
+
134
+ async installPackages() {
135
+ this.logger.setStatusLine('Eject: installing packages using the package-manager');
136
+ const packages = this.getPackagesToInstall();
137
+ await this.install.install(packages);
138
+ }
139
+
140
+ getPackagesToInstall(): string[] {
141
+ return this.componentsToEject.map((c) => componentIdToPackageName(c));
142
+ }
143
+
144
+ _buildExceptionMessageWithRollbackData(action: string): string {
145
+ return `eject failed ${action}.
146
+ your package.json (if existed) has been restored, however, some bit generated data may have been deleted, please run "bit link" to restore them.`;
147
+ }
148
+
149
+ /**
150
+ * as part of the 'eject' operation, a component is removed locally. as opposed to the remove
151
+ * command, in this case, no need to remove the objects from the scope, only remove from the
152
+ * filesystem, which means, delete the component files, untrack from .bitmap and clean
153
+ * package.json and bit.json traces.
154
+ */
155
+ private async removeComponentsFiles() {
156
+ if (this.ejectOptions.keepFiles) {
157
+ return;
158
+ }
159
+ this.logger.setStatusLine('Eject: removing the components files from the filesystem');
160
+ const dataToPersist = new DataToPersist();
161
+ this.componentsToEject.forEach((component) => {
162
+ const componentMap = component.componentMap;
163
+ if (!componentMap) {
164
+ throw new Error('ComponentEjector.removeComponentsFiles expect a component to have componentMap prop');
165
+ }
166
+ const rootDir = componentMap.rootDir;
167
+ if (!rootDir) {
168
+ throw new Error('ComponentEjector.removeComponentsFiles expect a componentMap to have rootDir');
169
+ }
170
+ dataToPersist.removePath(new RemovePath(rootDir, true));
171
+ });
172
+ dataToPersist.addBasePath(this.consumer.getPath());
173
+ await dataToPersist.persistAllToFS();
174
+ this.logger.consoleSuccess();
175
+ }
176
+
177
+ private async untrackComponents() {
178
+ this.logger.debug('eject: removing the components from the .bitmap');
179
+ await this.consumer.cleanFromBitMap(this.idsToEject);
180
+ }
181
+
182
+ throwEjectError(message: string, originalError: Error) {
183
+ const { message: originalErrorMessage } = defaultErrorHandler(originalError);
184
+ this.logger.error(`eject has stopped due to an error ${originalErrorMessage}`, originalError);
185
+ throw new Error(`${message}
186
+
187
+ got the following error: ${originalErrorMessage}`);
188
+ }
189
+
190
+ _validateIdsHaveScopesAndVersions() {
191
+ this.idsToEject.forEach((id) => {
192
+ if (!id.hasScope() || !id.hasVersion()) {
193
+ throw new TypeError(`EjectComponents expects ids with scope and version, got ${id.toString()}`);
194
+ }
195
+ });
196
+ }
197
+ }
@@ -14,15 +14,15 @@ import Component from '@teambit/legacy/dist/consumer/component/consumer-componen
14
14
  import PackageJsonFile from '@teambit/legacy/dist/consumer/component/package-json-file';
15
15
  import { Logger } from '@teambit/logger';
16
16
  import { InstallMain } from '@teambit/install';
17
- export declare type EjectResults = {
17
+ export type EjectResults = {
18
18
  ejectedComponents: ComponentIdList;
19
19
  failedComponents: FailedComponents;
20
20
  };
21
- export declare type EjectOptions = {
21
+ export type EjectOptions = {
22
22
  force?: boolean;
23
23
  keepFiles?: boolean;
24
24
  };
25
- declare type FailedComponents = {
25
+ type FailedComponents = {
26
26
  modifiedComponents: ComponentIdList;
27
27
  stagedComponents: ComponentIdList;
28
28
  notExportedComponents: ComponentIdList;
@@ -1,2 +1,2 @@
1
- import React from 'react';
2
- export declare const Logo: () => React.JSX.Element;
1
+ /// <reference types="react" />
2
+ export declare const Logo: () => JSX.Element;
@@ -1,5 +1,5 @@
1
- import * as compositions_0 from '/home/circleci/Library/Caches/Bit/capsules/8891be5ad3d35bfc38b9cd90c0e05b598a5a55af/teambit.workspace_eject@1.0.107/dist/eject.composition.js';
2
- import * as overview_0 from '/home/circleci/Library/Caches/Bit/capsules/8891be5ad3d35bfc38b9cd90c0e05b598a5a55af/teambit.workspace_eject@1.0.107/dist/eject.docs.mdx';
1
+ import * as compositions_0 from '/home/circleci/Library/Caches/Bit/capsules/8891be5ad3d35bfc38b9cd90c0e05b598a5a55af/teambit.workspace_eject@1.0.108/dist/eject.composition.js';
2
+ import * as overview_0 from '/home/circleci/Library/Caches/Bit/capsules/8891be5ad3d35bfc38b9cd90c0e05b598a5a55af/teambit.workspace_eject@1.0.108/dist/eject.docs.mdx';
3
3
 
4
4
  export const compositions = [compositions_0];
5
5
  export const overview = [overview_0];
package/eject-cmd.ts ADDED
@@ -0,0 +1,43 @@
1
+ import { Command, CommandOptions } from '@teambit/cli';
2
+ import { Workspace } from '@teambit/workspace';
3
+ import { COMPONENT_PATTERN_HELP } from '@teambit/legacy/dist/constants';
4
+ import { ejectTemplate } from './eject-template';
5
+ import { EjectMain } from './eject.main.runtime';
6
+
7
+ export class EjectCmd implements Command {
8
+ name = 'eject <component-pattern>';
9
+ description = 'remove component from the workspace and install it instead as a regular npm package.';
10
+ extendedDescription = 'By default the component files will be removed from the workspace';
11
+ helpUrl = 'reference/components/exporting-components#ejecting-components';
12
+ arguments = [
13
+ {
14
+ name: 'component-pattern',
15
+ description: COMPONENT_PATTERN_HELP,
16
+ },
17
+ ];
18
+ alias = 'E';
19
+ options = [
20
+ [
21
+ 'f',
22
+ 'force',
23
+ 'ignore local changes/versions. eject component/s even when they are staged or modified. Note: unexported tags/snaps will be lost',
24
+ ],
25
+ ['j', 'json', 'print the results in JSON format'],
26
+ ['', 'keep-files', 'keep the component files in the workspace intact'],
27
+ ] as CommandOptions;
28
+ loader = true;
29
+ migration = true;
30
+ group = 'development';
31
+
32
+ constructor(private ejectMain: EjectMain, private workspace: Workspace) {}
33
+
34
+ async report(
35
+ [pattern]: [string],
36
+ { force = false, json = false, keepFiles = false }: { force: boolean; json: boolean; keepFiles: boolean }
37
+ ): Promise<string> {
38
+ const componentIds = await this.workspace.idsByPattern(pattern);
39
+ const ejectResults = await this.ejectMain.eject(componentIds, { force, keepFiles });
40
+ if (json) return JSON.stringify(ejectResults, null, 2);
41
+ return ejectTemplate(ejectResults);
42
+ }
43
+ }
@@ -0,0 +1,38 @@
1
+ import chalk from 'chalk';
2
+
3
+ import { getCloudDomain } from '@teambit/legacy/dist/constants';
4
+ import { EjectResults } from './components-ejector';
5
+
6
+ export const successEjectMessage = 'successfully ejected the following components';
7
+ export const failureEjectMessage = 'failed to eject the following components';
8
+
9
+ export function ejectTemplate(ejectResults: EjectResults): string {
10
+ const getEjectedOutput = () => {
11
+ if (!ejectResults.ejectedComponents.length) return '';
12
+ return chalk.green(`${successEjectMessage} ${chalk.bold(ejectResults.ejectedComponents.toString())}\n`);
13
+ };
14
+ const getFailureOutput = () => {
15
+ const failures = ejectResults.failedComponents;
16
+ const title = chalk.red(`${failureEjectMessage}\n`);
17
+ const modified = failures.modifiedComponents.length
18
+ ? `stopped the eject process for the following components because they have local changes.
19
+ ${chalk.bold(failures.modifiedComponents.toString())}
20
+ ejecting modified components discards all local, unstaged, changes.
21
+ use 'bit diff <component id>' to see the changes and decide if to 'bit tag' them or not.
22
+ use the '--force' flag to discard local modifications and proceed with the eject process.\n`
23
+ : '';
24
+ const staged = failures.stagedComponents.length
25
+ ? `components with local versions (use --force to ignore): ${failures.stagedComponents.toString()}\n`
26
+ : '';
27
+ const notExported = failures.notExportedComponents.length
28
+ ? `local components that were not exported yet: ${failures.notExportedComponents.toString()}\n`
29
+ : '';
30
+ const selfHosted = failures.selfHostedExportedComponents.length
31
+ ? `components that were exported to a self hosted scope (not ${getCloudDomain()}): ${failures.selfHostedExportedComponents.toString()}\n`
32
+ : '';
33
+ const body = modified + staged + notExported + selfHosted;
34
+ if (body) return chalk.underline(title) + chalk.red(body);
35
+ return '';
36
+ };
37
+ return getEjectedOutput() + getFailureOutput();
38
+ }
@@ -0,0 +1,5 @@
1
+ import { Aspect } from '@teambit/harmony';
2
+
3
+ export const EjectAspect = Aspect.create({
4
+ id: 'teambit.workspace/eject',
5
+ });
@@ -0,0 +1,37 @@
1
+ import { CLIAspect, CLIMain, MainRuntime } from '@teambit/cli';
2
+ import { ComponentID } from '@teambit/component-id';
3
+ import { Logger, LoggerAspect, LoggerMain } from '@teambit/logger';
4
+ import WorkspaceAspect, { OutsideWorkspaceError, Workspace } from '@teambit/workspace';
5
+ import { InstallAspect, InstallMain } from '@teambit/install';
6
+ import { EjectCmd } from './eject-cmd';
7
+ import { EjectAspect } from './eject.aspect';
8
+ import { ComponentsEjector, EjectOptions, EjectResults } from './components-ejector';
9
+
10
+ export class EjectMain {
11
+ constructor(private workspace: Workspace, private install: InstallMain, private logger: Logger) {}
12
+ async eject(componentIds: ComponentID[], ejectOptions: EjectOptions = {}): Promise<EjectResults> {
13
+ if (!this.workspace) throw new OutsideWorkspaceError();
14
+ const componentEjector = new ComponentsEjector(
15
+ this.workspace,
16
+ this.install,
17
+ this.logger,
18
+ componentIds,
19
+ ejectOptions
20
+ );
21
+ return componentEjector.eject();
22
+ }
23
+
24
+ static runtime = MainRuntime;
25
+
26
+ static dependencies = [CLIAspect, WorkspaceAspect, LoggerAspect, InstallAspect];
27
+
28
+ static async provider([cli, workspace, loggerMain, install]: [CLIMain, Workspace, LoggerMain, InstallMain]) {
29
+ const logger = loggerMain.createLogger(EjectAspect.id);
30
+ const ejectMain = new EjectMain(workspace, install, logger);
31
+ cli.register(new EjectCmd(ejectMain, workspace));
32
+
33
+ return ejectMain;
34
+ }
35
+ }
36
+
37
+ EjectAspect.addRuntime(EjectMain);
package/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export type { EjectMain } from './eject.main.runtime';
2
+ export type { EjectResults } from './components-ejector';
3
+ export { successEjectMessage, ejectTemplate } from './eject-template';
4
+ export { EjectAspect } from './eject.aspect';
package/package.json CHANGED
@@ -1,36 +1,32 @@
1
1
  {
2
2
  "name": "@teambit/eject",
3
- "version": "1.0.107",
3
+ "version": "1.0.108",
4
4
  "homepage": "https://bit.cloud/teambit/workspace/eject",
5
5
  "main": "dist/index.js",
6
6
  "componentId": {
7
7
  "scope": "teambit.workspace",
8
8
  "name": "eject",
9
- "version": "1.0.107"
9
+ "version": "1.0.108"
10
10
  },
11
11
  "dependencies": {
12
12
  "chalk": "2.4.2",
13
- "core-js": "^3.0.0",
14
- "@babel/runtime": "7.20.0",
15
13
  "@teambit/component-id": "1.2.0",
16
14
  "@teambit/harmony": "0.4.6",
17
- "@teambit/install": "1.0.107",
18
- "@teambit/logger": "0.0.932",
19
- "@teambit/workspace": "1.0.107",
20
- "@teambit/cli": "0.0.839"
15
+ "@teambit/install": "1.0.108",
16
+ "@teambit/logger": "0.0.933",
17
+ "@teambit/workspace": "1.0.108",
18
+ "@teambit/cli": "0.0.840"
21
19
  },
22
20
  "devDependencies": {
23
- "@types/react": "^17.0.8",
24
21
  "@types/mocha": "9.1.0",
25
- "@types/node": "12.20.4",
26
- "@types/react-dom": "^17.0.5",
27
- "@types/jest": "^26.0.0",
28
- "@types/testing-library__jest-dom": "5.9.5"
22
+ "@types/jest": "^29.2.2",
23
+ "@types/testing-library__jest-dom": "^5.9.5",
24
+ "@teambit/harmony.envs.core-aspect-env": "0.0.13"
29
25
  },
30
26
  "peerDependencies": {
31
- "@teambit/legacy": "1.0.624",
32
- "react": "^16.8.0 || ^17.0.0",
33
- "react-dom": "^16.8.0 || ^17.0.0"
27
+ "react": "^17.0.0 || ^18.0.0",
28
+ "@types/react": "^18.2.12",
29
+ "@teambit/legacy": "1.0.624"
34
30
  },
35
31
  "license": "Apache-2.0",
36
32
  "optionalDependencies": {},
@@ -44,7 +40,7 @@
44
40
  },
45
41
  "private": false,
46
42
  "engines": {
47
- "node": ">=12.22.0"
43
+ "node": ">=16.0.0"
48
44
  },
49
45
  "repository": {
50
46
  "type": "git",
@@ -53,12 +49,9 @@
53
49
  "keywords": [
54
50
  "bit",
55
51
  "bit-aspect",
52
+ "bit-core-aspect",
56
53
  "components",
57
54
  "collaboration",
58
- "web",
59
- "react",
60
- "react-components",
61
- "angular",
62
- "angular-components"
55
+ "web"
63
56
  ]
64
57
  }
package/tsconfig.json CHANGED
@@ -1,38 +1,33 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "lib": [
4
- "es2019",
5
- "DOM",
6
- "ES6",
7
- "DOM.Iterable",
8
- "ScriptHost"
4
+ "esnext",
5
+ "dom",
6
+ "dom.Iterable"
9
7
  ],
10
- "target": "es2015",
11
- "module": "CommonJS",
12
- "jsx": "react",
13
- "allowJs": true,
14
- "composite": true,
8
+ "target": "es2020",
9
+ "module": "es2020",
10
+ "jsx": "react-jsx",
15
11
  "declaration": true,
16
12
  "sourceMap": true,
17
- "skipLibCheck": true,
18
13
  "experimentalDecorators": true,
19
- "outDir": "dist",
14
+ "skipLibCheck": true,
20
15
  "moduleResolution": "node",
21
16
  "esModuleInterop": true,
22
- "rootDir": ".",
23
17
  "resolveJsonModule": true,
24
- "emitDeclarationOnly": true,
25
- "emitDecoratorMetadata": true,
26
- "allowSyntheticDefaultImports": true,
27
- "strictPropertyInitialization": false,
28
- "strict": true,
29
- "noImplicitAny": false,
30
- "preserveConstEnums": true
18
+ "allowJs": true,
19
+ "outDir": "dist",
20
+ "emitDeclarationOnly": true
31
21
  },
32
22
  "exclude": [
23
+ "artifacts",
24
+ "public",
33
25
  "dist",
26
+ "node_modules",
27
+ "package.json",
34
28
  "esm.mjs",
35
- "package.json"
29
+ "**/*.cjs",
30
+ "./dist"
36
31
  ],
37
32
  "include": [
38
33
  "**/*",
package/types/asset.d.ts CHANGED
@@ -5,12 +5,12 @@ declare module '*.png' {
5
5
  declare module '*.svg' {
6
6
  import type { FunctionComponent, SVGProps } from 'react';
7
7
 
8
- export const ReactComponent: FunctionComponent<SVGProps<SVGSVGElement> & { title?: string }>;
8
+ export const ReactComponent: FunctionComponent<
9
+ SVGProps<SVGSVGElement> & { title?: string }
10
+ >;
9
11
  const src: string;
10
12
  export default src;
11
13
  }
12
-
13
- // @TODO Gilad
14
14
  declare module '*.jpg' {
15
15
  const value: any;
16
16
  export = value;
@@ -27,3 +27,15 @@ declare module '*.bmp' {
27
27
  const value: any;
28
28
  export = value;
29
29
  }
30
+ declare module '*.otf' {
31
+ const value: any;
32
+ export = value;
33
+ }
34
+ declare module '*.woff' {
35
+ const value: any;
36
+ export = value;
37
+ }
38
+ declare module '*.woff2' {
39
+ const value: any;
40
+ export = value;
41
+ }