-
Notifications
You must be signed in to change notification settings - Fork 953
feat: bit update should update dependencies in env.jsonc #10128
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c9dd8ce
aebcee6
be82c86
fc2082e
3d3ab9d
c811f72
2b5eabf
ae29be1
bad13be
3b0c7ab
5585c44
006b23f
31987b7
2280e0d
6df1cc9
cff1715
cd22a5e
c371ae5
572cd2b
b92a2c0
94274a6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -498,4 +498,71 @@ describe('env-jsonc-policies', function () { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| describe('bit update', function () { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let envId; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| before(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| helper = new Helper(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| helper.scopeHelper.setWorkspaceWithRemoteScope(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| envId = 'react-based-env'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| helper.env.setCustomNewEnv(undefined, undefined, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| policy: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| runtime: [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: 'is-string', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| version: '1.0.5', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| force: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, false, envId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| helper.command.install(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| after(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| helper.scopeHelper.destroy(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it('should update env.jsonc dependency to latest version', () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const envJsoncPath = path.join(helper.scopes.localPath, envId, 'env.jsonc'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const originalEnvJsonc = fs.readJsonSync(envJsoncPath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(originalEnvJsonc.policy.runtime[0].version).to.equal('1.0.5'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| helper.command.update('is-string --yes'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const updatedEnvJsonc = fs.readJsonSync(envJsoncPath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(updatedEnvJsonc.policy.runtime[0].version).to.not.equal('1.0.5'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it('should update supportedRange for peerDependencies when new version is outside existing range', () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const envId2 = 'react-based-env-peers'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| helper.env.setCustomNewEnv(undefined, undefined, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| policy: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| peers: [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: 'react', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| version: '16.8.0', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| supportedRange: '^16.8.0', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, false, envId2); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| helper.command.install(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const envJsoncPath = path.join(helper.scopes.localPath, envId2, 'env.jsonc'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const originalEnvJsonc = fs.readJsonSync(envJsoncPath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(originalEnvJsonc.policy.peers[0].supportedRange).to.equal('^16.8.0'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Update react to latest (which is > 16.8.0, likely 18.x) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| helper.command.update('react --yes'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const updatedEnvJsonc = fs.readJsonSync(envJsoncPath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const newVersion = updatedEnvJsonc.policy.peers[0].version; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const newSupportedRange = updatedEnvJsonc.policy.peers[0].supportedRange; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(newVersion).to.not.equal('16.8.0'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Should now contain the old range OR the new range/version | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(newSupportedRange).to.include(' || '); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(newSupportedRange).to.include('^16.8.0'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(newSupportedRange).to.include(newVersion); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
zkochan marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
zkochan marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | |
| }); | |
| it('should not overwrite peer version "+" and should update supportedRange to include the new version', () => { | |
| const envId3 = 'react-based-env-peers-plus'; | |
| helper.env.setCustomNewEnv(undefined, undefined, { | |
| policy: { | |
| peers: [ | |
| { | |
| name: 'react', | |
| version: '+', | |
| supportedRange: '^16.8.0', | |
| }, | |
| ], | |
| }, | |
| }, false, envId3); | |
| helper.command.install(); | |
| const envJsoncPath = path.join(helper.scopes.localPath, envId3, 'env.jsonc'); | |
| const originalEnvJsonc = fs.readJsonSync(envJsoncPath); | |
| expect(originalEnvJsonc.policy.peers[0].version).to.equal('+'); | |
| expect(originalEnvJsonc.policy.peers[0].supportedRange).to.equal('^16.8.0'); | |
| // Update react to latest while version is resolved via "+" and supportedRange | |
| helper.command.update('react --yes'); | |
| const updatedEnvJsonc = fs.readJsonSync(envJsoncPath); | |
| const updatedPeer = updatedEnvJsonc.policy.peers[0]; | |
| // The special-case "+" version must be preserved | |
| expect(updatedPeer.version).to.equal('+'); | |
| const updatedSupportedRange = updatedPeer.supportedRange; | |
| expect(updatedSupportedRange).to.not.equal('^16.8.0'); | |
| expect(updatedSupportedRange).to.include('^16.8.0'); | |
| // The updated supportedRange should include the newly installed react version | |
| const reactPkgJsonPath = resolveFrom(helper.scopes.localPath, 'react/package.json'); | |
| const reactPkgJson = fs.readJsonSync(reactPkgJsonPath); | |
| const installedVersion = reactPkgJson.version; | |
| expect(updatedSupportedRange).to.include(installedVersion); | |
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| /* eslint-disable max-lines */ | ||
| import multimatch from 'multimatch'; | ||
| import { isSnap } from '@teambit/component-version'; | ||
| import { BitError } from '@teambit/bit-error'; | ||
|
|
@@ -37,7 +38,7 @@ import { Http } from '@teambit/scope.network'; | |
| import type { Dependency as LegacyDependency } from '@teambit/legacy.consumer-component'; | ||
| import { ConsumerComponent as LegacyComponent } from '@teambit/legacy.consumer-component'; | ||
| import fs from 'fs-extra'; | ||
| import { assign } from 'comment-json'; | ||
| import { assign, parse } from 'comment-json'; | ||
| import { ComponentID } from '@teambit/component-id'; | ||
| import { readCAFileSync } from '@pnpm/network.ca-file'; | ||
| import { parseBareSpecifier } from '@pnpm/npm-resolver'; | ||
|
|
@@ -88,7 +89,7 @@ import { DependenciesFragment, DevDependenciesFragment, PeerDependenciesFragment | |
| import { dependencyResolverSchema } from './dependency-resolver.graphql'; | ||
| import type { DependencyDetector } from './detector-hook'; | ||
| import { DependenciesService } from './dependencies.service'; | ||
| import { EnvPolicy } from './policy/env-policy'; | ||
| import { EnvPolicy, type EnvJsoncPolicyEntry } from './policy/env-policy'; | ||
| import type { ConfigStoreMain } from '@teambit/config-store'; | ||
| import { ConfigStoreAspect } from '@teambit/config-store'; | ||
|
|
||
|
|
@@ -1504,6 +1505,7 @@ as an alternative, you can use "+" to keep the same version installed in the wor | |
| variantPoliciesByPatterns, | ||
| componentPolicies, | ||
| components, | ||
| includeEnvJsoncDeps: true, | ||
| }); | ||
| if (patterns?.length) { | ||
| const selectedPkgNames = new Set( | ||
|
|
@@ -1525,10 +1527,12 @@ as an alternative, you can use "+" to keep the same version installed in the wor | |
| variantPoliciesByPatterns, | ||
| componentPolicies, | ||
| components, | ||
| includeEnvJsoncDeps = false, | ||
| }: { | ||
| variantPoliciesByPatterns: Record<string, VariantPolicyConfigObject>; | ||
| componentPolicies: Array<{ componentId: ComponentID; policy: any }>; | ||
| components: Component[]; | ||
| includeEnvJsoncDeps?: boolean; | ||
| }): CurrentPkg[] { | ||
| const localComponentPkgNames = new Set(components.map((component) => this.getPackageName(component))); | ||
| const componentModelVersions: ComponentModelVersion[] = components | ||
|
|
@@ -1553,12 +1557,55 @@ as an alternative, you can use "+" to keep the same version installed in the wor | |
| })); | ||
| }) | ||
| .flat(); | ||
| return getAllPolicyPkgs({ | ||
| rootPolicy: this.getWorkspacePolicyFromConfig(), | ||
| variantPoliciesByPatterns, | ||
| componentPolicies, | ||
| componentModelVersions, | ||
| }); | ||
| return [ | ||
| ...getAllPolicyPkgs({ | ||
| rootPolicy: this.getWorkspacePolicyFromConfig(), | ||
| variantPoliciesByPatterns, | ||
| componentPolicies, | ||
| componentModelVersions, | ||
| }), | ||
| ...(includeEnvJsoncDeps ? this.getEnvJsoncPolicyPkgs(components) : []), | ||
| ]; | ||
| } | ||
|
|
||
|
zkochan marked this conversation as resolved.
|
||
| getEnvJsoncPolicyPkgs(components: Component[]): CurrentPkg[] { | ||
| const policies = [ | ||
| { field: 'peers', targetField: 'peerDependencies' as const }, | ||
| { field: 'dev', targetField: 'devDependencies' as const }, | ||
| { field: 'runtime', targetField: 'dependencies' as const }, | ||
| ]; | ||
| const pkgs: CurrentPkg[] = []; | ||
| for (const component of components) { | ||
| const isEnv = this.envs.isEnv(component); | ||
| if (!isEnv) continue; | ||
|
|
||
| const envJsoncFile = component.filesystem.files.find((file) => file.relative === 'env.jsonc'); | ||
| if (!envJsoncFile) continue; | ||
|
|
||
| let envJsonc: EnvJsonc; | ||
| try { | ||
| envJsonc = parse(envJsoncFile.contents.toString()) as EnvJsonc; | ||
| } catch (error: unknown) { | ||
| const errorMessage = error instanceof Error ? error.message : String(error); | ||
| this.logger.warn(`Failed to parse env.jsonc for component ${component.id.toString()}: ${errorMessage}`); | ||
| continue; | ||
| } | ||
| if (!envJsonc.policy) continue; | ||
|
|
||
| for (const { field, targetField } of policies) { | ||
| const deps: EnvJsoncPolicyEntry[] = envJsonc.policy?.[field] || []; | ||
| for (const dep of deps) { | ||
| pkgs.push({ | ||
| name: dep.name, | ||
| currentRange: dep.version, | ||
| source: 'env-jsonc', | ||
| componentId: component.id, | ||
| targetField, | ||
| }); | ||
|
Comment on lines
+1595
to
+1604
|
||
| } | ||
|
Comment on lines
+1571
to
+1605
|
||
| } | ||
| } | ||
| return pkgs; | ||
| } | ||
|
|
||
| getAllDedupedDirectDependencies(opts: { | ||
|
|
@@ -1629,9 +1676,7 @@ as an alternative, you can use "+" to keep the same version installed in the wor | |
| rootDir: string; | ||
| forceVersionBump?: 'major' | 'minor' | 'patch' | 'compatible'; | ||
| }, | ||
| pkgs: Array< | ||
| { name: string; currentRange: string; source: 'variants' | 'component' | 'rootPolicy' | 'component-model' } & T | ||
| > | ||
| pkgs: Array<{ name: string; currentRange: string; source: CurrentPkgSource; } & T> | ||
| ): Promise<Array<{ name: string; currentRange: string; latestRange: string } & T>> { | ||
| this.logger.setStatusLine('checking the latest versions of dependencies'); | ||
| const resolver = await this.getVersionResolver(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| export { EnvPolicy, EnvPolicyConfigObject } from './env-policy'; | ||
| export { EnvPolicy, EnvPolicyConfigObject, EnvJsoncPolicyEntry, EnvPolicyEnvJsoncConfigObject } from './env-policy'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| export { | ||
| detectJsoncFormatting, | ||
| parseJsoncWithFormatting, | ||
| stringifyJsonc, | ||
| updateJsoncPreservingFormatting, | ||
| type JsoncFormatting, | ||
| } from './jsonc-utils'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| --- | ||
| labels: ['typescript', 'utils', 'json', 'jsonc', 'formatting', 'comments', 'parser'] | ||
| description: 'Utilities for parsing and stringifying JSONC files while preserving formatting and comments.' | ||
| --- | ||
|
|
||
| # JSONC Utils | ||
|
|
||
| Utilities for working with JSONC (JSON with Comments) files while preserving their original formatting, including: | ||
| - **Indentation style** (2 spaces, 4 spaces, tabs) | ||
| - **Newline characters** (LF, CRLF) | ||
| - **Comments** | ||
|
|
||
| This is particularly useful when you need to programmatically update configuration files without losing their human-readable formatting. | ||
|
|
||
| ## Installation | ||
|
|
||
| ```bash | ||
| bit install @teambit/toolbox.json.jsonc-utils | ||
| ``` | ||
|
|
||
| ## Quick Start | ||
|
|
||
| The easiest way to update a JSONC file while preserving its formatting: | ||
|
|
||
| ```ts | ||
| import { updateJsoncPreservingFormatting } from '@teambit/toolbox.json.jsonc-utils'; | ||
|
|
||
| const originalContent = `{ | ||
| // This is a comment | ||
| "name": "my-package", | ||
| "version": "1.0.0" | ||
| }`; | ||
|
|
||
| const updatedContent = updateJsoncPreservingFormatting(originalContent, (data) => { | ||
| data.version = "2.0.0"; | ||
| return data; | ||
| }); | ||
|
|
||
| // Output preserves comments and formatting: | ||
| // { | ||
| // // This is a comment | ||
| // "name": "my-package", | ||
| // "version": "2.0.0" | ||
| // } | ||
| ``` | ||
|
|
||
| ## Use Cases | ||
|
|
||
| ### Updating env.jsonc Files | ||
|
|
||
| When updating component environment configurations, preserve the original formatting: | ||
|
|
||
| ```ts | ||
| import fs from 'fs-extra'; | ||
| import { updateJsoncPreservingFormatting } from '@teambit/toolbox.json.jsonc-utils'; | ||
|
|
||
| async function updateEnvDependency( | ||
| envJsoncPath: string, | ||
| pkgName: string, | ||
| newVersion: string | ||
| ) { | ||
| const content = await fs.readFile(envJsoncPath, 'utf-8'); | ||
|
|
||
| const updated = updateJsoncPreservingFormatting(content, (envJsonc) => { | ||
| const dep = envJsonc.policy?.runtime?.find((d) => d.name === pkgName); | ||
| if (dep) { | ||
| dep.version = newVersion; | ||
| } | ||
| return envJsonc; | ||
| }); | ||
|
|
||
| await fs.writeFile(envJsoncPath, updated); | ||
| } | ||
| ``` | ||
|
|
||
| ### Working with Different Indentation Styles | ||
|
|
||
| Handle files with different formatting preferences: | ||
|
|
||
| ```ts | ||
| import { detectJsoncFormatting, stringifyJsonc } from '@teambit/toolbox.json.jsonc-utils'; | ||
|
|
||
| // Detect existing formatting | ||
| const formatting = detectJsoncFormatting(originalContent); | ||
|
|
||
| // Modify data | ||
| const updatedData = { ...parsedData, version: '2.0.0' }; | ||
|
|
||
| // Stringify with original formatting | ||
| const result = stringifyJsonc(updatedData, formatting); | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This e2e uses
reactas the package to update. React installs are relatively heavy and can make CI slower/flakier compared to the smaller packages used elsewhere in the update tests (e.g. is-odd/is-string). Consider switching to a lightweight package with a known non-intersecting semver jump to validate thesupportedRangebehavior without significantly increasing install time.