diff --git a/e2e/commands/init.e2e.1.ts b/e2e/commands/init.e2e.1.ts index 5b75b1e2c3f0..f22774576ade 100644 --- a/e2e/commands/init.e2e.1.ts +++ b/e2e/commands/init.e2e.1.ts @@ -160,7 +160,7 @@ describe('run bit init', function () { }); describe('bit init', () => { before(() => { - helper.scopeHelper.initWorkspace(); + helper.scopeHelper.initWorkspace(undefined, { 'no-package-json': true }); }); it('should not change BitMap file', () => { const currentBitMap = helper.bitMap.read(); @@ -175,7 +175,7 @@ describe('run bit init', function () { describe('bit init --reset', () => { before(() => { helper.scopeHelper.getClonedLocalScope(localScope); - helper.command.runCmd('bit init --reset'); + helper.command.runCmd('bit init --reset --no-package-json'); }); it('should not change BitMap file', () => { const currentBitMap = helper.bitMap.read(); diff --git a/scopes/generator/generator/workspace-generator.ts b/scopes/generator/generator/workspace-generator.ts index 8d381c86bd57..b3bca6d431b5 100644 --- a/scopes/generator/generator/workspace-generator.ts +++ b/scopes/generator/generator/workspace-generator.ts @@ -57,7 +57,7 @@ export class WorkspaceGenerator { try { process.chdir(this.workspacePath); await this.initGit(); - await init(this.workspacePath, this.options.skipGit, false, false, false, false, false, false, {}); + await init(this.workspacePath, this.options.skipGit, false, false, false, false, false, false, false, {}); await this.writeWorkspaceFiles(); await this.reloadBitInWorkspaceDir(); // Setting the workspace to be in install context to prevent errors during the workspace generation diff --git a/src/api/consumer/lib/init.ts b/src/api/consumer/lib/init.ts index a2f06afd8be1..0edd1e908e3c 100644 --- a/src/api/consumer/lib/init.ts +++ b/src/api/consumer/lib/init.ts @@ -10,6 +10,7 @@ import ObjectsWithoutConsumer from './exceptions/objects-without-consumer'; export default async function init( absPath: string = process.cwd(), noGit = false, + noPackageJson = false, reset = false, resetNew = false, resetLaneNew = false, @@ -23,7 +24,7 @@ export default async function init( } let consumer: Consumer | undefined; try { - consumer = await Consumer.create(absPath, noGit, workspaceConfigProps); + consumer = await Consumer.create(absPath, noGit, noPackageJson, workspaceConfigProps); } catch (err) { // it's possible that at this stage the consumer fails to load due to scope issues. // still we want to load it to include its instance of "scope.json", so then later when "consumer.write()", we @@ -34,7 +35,7 @@ export default async function init( if (!scopePath) throw new Error(`fatal: scope not found in the path: ${process.cwd()}`); await Scope.reset(scopePath, true); } - if (!consumer) consumer = await Consumer.create(absPath, noGit, workspaceConfigProps); + if (!consumer) consumer = await Consumer.create(absPath, noGit, noPackageJson, workspaceConfigProps); if (!force && !resetScope) { await throwForOutOfSyncScope(consumer); } diff --git a/src/cli/commands/public-cmds/init-cmd.ts b/src/cli/commands/public-cmds/init-cmd.ts index eefb5d163029..f07efc870327 100644 --- a/src/cli/commands/public-cmds/init-cmd.ts +++ b/src/cli/commands/public-cmds/init-cmd.ts @@ -34,6 +34,7 @@ export default class Init implements LegacyCommand { 'standalone', 'do not nest component store within .git directory and do not write config data inside package.json', ], + ['', 'no-package-json', 'do not generate package.json'], ['r', 'reset', 'write missing or damaged Bit files'], ['', 'reset-new', 'reset .bitmap file as if the components were newly added and remove all model data (objects)'], [ @@ -59,7 +60,6 @@ export default class Init implements LegacyCommand { ['', 'default-scope ', 'set the default scope for components in the workspace'], ['p', 'package-manager ', 'set the package manager (npm or yarn) to be used in the workspace'], ['f', 'force', 'force workspace initialization without clearing local objects'], - ['', 'harmony', 'DEPRECATED. no need for this flag. Harmony is the default now'], ['I', 'interactive', 'EXPERIMENTAL. start an interactive process'], ] as CommandOptions; @@ -72,6 +72,7 @@ export default class Init implements LegacyCommand { bare, shared, standalone, + noPackageJson, reset, resetNew, resetLaneNew, @@ -105,6 +106,7 @@ export default class Init implements LegacyCommand { return init( path, standalone, + noPackageJson, reset, resetNew, resetLaneNew, diff --git a/src/consumer/component/package-json-file.ts b/src/consumer/component/package-json-file.ts index c34665bccb21..831eb146b1dc 100644 --- a/src/consumer/component/package-json-file.ts +++ b/src/consumer/component/package-json-file.ts @@ -61,8 +61,7 @@ export default class PackageJsonFile { * load from the given dir, if not exist, don't throw an error, just set packageJsonObject as an * empty object */ - // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! - static async load(workspaceDir: PathOsBasedAbsolute, componentDir?: PathRelative = '.'): Promise { + static async load(workspaceDir: PathOsBasedAbsolute, componentDir: PathRelative = '.'): Promise { const filePath = composePath(componentDir); const filePathAbsolute = path.join(workspaceDir, filePath); const packageJsonStr = await PackageJsonFile.getPackageJsonStrIfExist(filePathAbsolute); @@ -75,6 +74,25 @@ export default class PackageJsonFile { return new PackageJsonFile({ filePath, packageJsonObject, fileExist: true, workspaceDir, indent, newline }); } + static create( + workspaceDir: PathOsBasedAbsolute, + componentDir: PathRelative = '.', + content: Record, + indent = ' ', + newline = '\n' + ): PackageJsonFile { + const filePath = composePath(componentDir); + + return new PackageJsonFile({ + filePath, + packageJsonObject: content, + fileExist: false, + workspaceDir, + indent, + newline, + }); + } + static async reset(workspaceDir: PathOsBasedAbsolute) { const pkgJsonFile = await PackageJsonFile.load(workspaceDir); if (pkgJsonFile.fileExist) { diff --git a/src/consumer/consumer.ts b/src/consumer/consumer.ts index f0c8ca8ac54f..9046af1a73aa 100644 --- a/src/consumer/consumer.ts +++ b/src/consumer/consumer.ts @@ -1,7 +1,7 @@ import fs from 'fs-extra'; import * as path from 'path'; import R from 'ramda'; -import { compact } from 'lodash'; +import { compact, isEmpty } from 'lodash'; import { ComponentID, ComponentIdList } from '@teambit/component-id'; import { DEFAULT_LANE, LaneId } from '@teambit/lane-id'; import { BitIdStr } from '@teambit/legacy-bit-id'; @@ -73,7 +73,7 @@ export default class Consumer { _componentsStatusCache: Record = {}; // cache loaded components packageManagerArgs: string[] = []; // args entered by the user in the command line after '--' componentLoader: ComponentLoader; - packageJson: any; + packageJson: PackageJsonFile; public onCacheClear: Array<() => void | Promise> = []; constructor({ projectPath, @@ -98,6 +98,10 @@ export default class Consumer { this.bitMap = await BitMap.load(this); } + setPackageJson(packageJson: PackageJsonFile) { + this.packageJson = packageJson; + } + // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! get dirStructure(): DirStructure { if (!this._dirStructure) { @@ -183,6 +187,7 @@ export default class Consumer { await Promise.all([this.config.write({ workspaceDir: this.projectPath }), this.scope.ensureDir()]); this.bitMap.markAsChanged(); await this.writeBitMap(); + await this.writePackageJson(); return this; } @@ -424,9 +429,10 @@ export default class Consumer { static create( projectPath: PathOsBasedAbsolute, noGit = false, + noPackageJson = false, workspaceConfigProps?: WorkspaceConfigProps ): Promise { - return this.ensure(projectPath, noGit, workspaceConfigProps); + return this.ensure(projectPath, noGit, noPackageJson, workspaceConfigProps); } static _getScopePath(projectPath: PathOsBasedAbsolute, noGit: boolean): PathOsBasedAbsolute { @@ -441,6 +447,7 @@ export default class Consumer { static async ensure( projectPath: PathOsBasedAbsolute, standAlone = false, + noPackageJson = false, workspaceConfigProps?: WorkspaceConfigProps ): Promise { const resolvedScopePath = Consumer._getScopePath(projectPath, standAlone); @@ -458,16 +465,40 @@ export default class Consumer { existingGitHooks, }); await consumer.setBitMap(); - // understands why tests break with gilad and david. - // await Consumer.ensurePackageJson(projectPath); + if (!noPackageJson) { + consumer.setPackageJsonWithTypeModule(); + } return consumer; } + private setPackageJsonWithTypeModule() { + const exists = this.packageJson && this.packageJson.fileExist; + if (exists) { + const content = this.packageJson.packageJsonObject; + if (content.type === 'module') return; + logger.console( + '\nEnable ESM by adding "type":"module" to the package.json file (https://nodejs.org/api/esm.html#enabling). If you are looking to use CJS. Use the Bit CJS environments.' + ); + return; + } + const jsonContent = { type: 'module' }; + const packageJson = PackageJsonFile.create(this.projectPath, undefined, jsonContent); + this.setPackageJson(packageJson); + } + static async ensurePackageJson(projectPath: string) { const packageJsonPath = path.join(projectPath, 'package.json'); const exists = fs.existsSync(packageJsonPath); - if (exists) return; - fs.writeFileSync(packageJsonPath, `{\n "type": "module" \n}`); + if (exists) { + const content = await fs.readJson(packageJsonPath); + if (content.type === 'module') return; + logger.console( + '\nEnable ESM by adding "type":"module" to the package.json file (https://nodejs.org/api/esm.html#enabling). If you are looking to use CJS. Use the Bit CJS environments.' + ); + return; + } + const jsonContent = { type: 'module' }; + fs.writeJSONSync(packageJsonPath, jsonContent, { spaces: 2 }); } /** @@ -605,6 +636,12 @@ export default class Consumer { await this.bitMap.write(); } + async writePackageJson() { + if (!isEmpty(this.packageJson.packageJsonObject)) { + await this.packageJson.write(); + } + } + getBitmapHistoryDir(): PathOsBasedAbsolute { return path.join(this.scope.path, BITMAP_HISTORY_DIR_NAME); } diff --git a/src/e2e-helper/e2e-scope-helper.ts b/src/e2e-helper/e2e-scope-helper.ts index 3f58360004b9..2ea06098093f 100644 --- a/src/e2e-helper/e2e-scope-helper.ts +++ b/src/e2e-helper/e2e-scope-helper.ts @@ -20,6 +20,7 @@ type SetupWorkspaceOpts = { disableMissingManuallyConfiguredPackagesIssue?: boolean; // default to true. otherwise, it'll always show missing babel/jest from react-env registry?: string; initGit?: boolean; + generatePackageJson?: boolean; yarnRCConfig?: any; npmrcConfig?: any; }; @@ -84,7 +85,8 @@ export default class ScopeHelper { reInitLocalScope(opts?: SetupWorkspaceOpts) { this.cleanLocalScope(); if (opts?.initGit) this.command.runCmd('git init'); - this.initWorkspace(); + const initWsOpts = opts?.generatePackageJson ? undefined : { 'no-package-json': true }; + this.initWorkspace(undefined, initWsOpts); if (opts?.addRemoteScopeAsDefaultScope ?? true) this.workspaceJsonc.addDefaultScope(); if (opts?.disablePreview ?? true) this.workspaceJsonc.disablePreview(); @@ -122,8 +124,9 @@ export default class ScopeHelper { this.command.new(templateName, flags, this.scopes.local, this.scopes.e2eDir); } - initWorkspace(workspacePath?: string) { - return this.command.runCmd(`bit init`, workspacePath); + initWorkspace(workspacePath?: string, options?: Record) { + const opts = this.command.parseOptions(options); + return this.command.runCmd(`bit init ${opts}`, workspacePath); } async initInteractive(inputs: InteractiveInputs) { @@ -143,13 +146,14 @@ export default class ScopeHelper { this.addRemoteScope(); } - initNewLocalScope(deleteCurrentScope = true) { + initNewLocalScope(deleteCurrentScope = true, generatePackageJson = false) { if (deleteCurrentScope) { fs.removeSync(this.scopes.localPath); } this.scopes.setLocalScope(); fs.ensureDirSync(this.scopes.localPath); - return this.initWorkspace(); + const initWsOpts = generatePackageJson ? undefined : { 'no-package-json': true }; + return this.initWorkspace(undefined, initWsOpts); } addRemoteScope( remoteScopePath: string = this.scopes.remotePath, diff --git a/src/interactive/commands/init-interactive.ts b/src/interactive/commands/init-interactive.ts index 8e4daeb21580..14b59ba6a7d2 100644 --- a/src/interactive/commands/init-interactive.ts +++ b/src/interactive/commands/init-interactive.ts @@ -214,7 +214,7 @@ export default (async function initInteractive() { actualCompiler = undefined; } answers.compiler = actualCompiler; - return init(undefined, false, false, false, false, false, false, false, answers).then( + return init(undefined, false, false, false, false, false, false, false, false, answers).then( ({ created, addedGitHooks, existingGitHooks }) => { return { created,