Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ commands:
pnpm dlx @ialdama/jsonmod --key "pnpm.overrides.cross-spawn@7" --values "^7.0.5" &&
pnpm dlx @ialdama/jsonmod --key "pnpm.overrides.lodash@4" --values "4.17.21" &&
pnpm dlx @ialdama/jsonmod --key "pnpm.overrides.trim@0" --values "0.0.3" &&
pnpm dlx @ialdama/jsonmod --key "pnpm.overrides.@teambit/harmony" --values "0.4.11" &&
pnpm dlx @ialdama/jsonmod --key "pnpm.overrides.@teambit/harmony" --values "0.4.12" &&
pnpm dlx @ialdama/jsonmod --key "pnpm.overrides.postcss@8" --values "^8.4.19" &&
pnpm dlx @ialdama/jsonmod --key "pnpm.overrides.ajv@6" --values "^6.12.6" &&
pnpm dlx json -I -f package.json -e "this.pnpm.overrides['@teambit/semantics.entities.semantic-schema']='^0.0.99'" &&
Expand Down Expand Up @@ -139,7 +139,7 @@ commands:
pnpm dlx @ialdama/jsonmod --key "pnpm.overrides.cross-spawn@7" --values "^7.0.5" &&
pnpm dlx @ialdama/jsonmod --key "pnpm.overrides.lodash@4" --values "4.17.21" &&
pnpm dlx @ialdama/jsonmod --key "pnpm.overrides.trim@0" --values "0.0.3" &&
pnpm dlx @ialdama/jsonmod --key "pnpm.overrides.@teambit/harmony" --values "0.4.11" &&
pnpm dlx @ialdama/jsonmod --key "pnpm.overrides.@teambit/harmony" --values "0.4.12" &&
pnpm dlx @ialdama/jsonmod --key "pnpm.overrides.postcss@8" --values "^8.4.19" &&
pnpm dlx @ialdama/jsonmod --key "pnpm.overrides.ajv@6" --values "^6.12.6" &&
pnpm dlx json -I -f package.json -e "this.pnpm.overrides['@teambit/semantics.entities.semantic-schema']='^0.0.99'" &&
Expand Down Expand Up @@ -642,12 +642,15 @@ jobs:
check_env_cache_sync:
<<: *defaults
steps:
# Only needs .bitmap and .circleci/config.yml from a plain checkout, so it
# runs (and fails) in seconds without waiting for setup_harmony.
# Only needs .bitmap, workspace.jsonc and .circleci/config.yml from a plain
# checkout, so it runs (and fails) in seconds without waiting for setup_harmony.
- checkout
- run:
name: 'check env cache synchronization'
command: './scripts/check-env-cache-sync.sh'
- run:
name: 'check @teambit/harmony version synchronization'
command: './scripts/check-harmony-version-sync.sh'

generate_and_check_types:
<<: *defaults
Expand Down
4 changes: 3 additions & 1 deletion components/legacy/dependency-graph/vizgraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { Digraph, Subgraph, Node, Edge, toDot } from 'ts-graphviz';
// @ts-ignore
// eslint-disable-next-line import/no-unresolved
import { toFile } from 'ts-graphviz/adapter';
// @ts-ignore
import type { Format } from 'ts-graphviz/adapter';
import { instance } from '@viz-js/viz';
import type { Graph as ClearGraph } from '@teambit/graph.cleargraph';
import { generateRandomStr } from '@teambit/toolbox.string.random';
Expand Down Expand Up @@ -206,7 +208,7 @@ export class VisualDependencyGraph {
*/
async image(imagePath: string = this.getTmpFilename()): Promise<string> {
await checkGraphvizInstalled();
const type: string = path.extname(imagePath).replace('.', '') || 'png';
const type = (path.extname(imagePath).replace('.', '') || 'png') as Format;

const dot = this.dot();
await toFile(dot, imagePath, { format: type });
Expand Down
2 changes: 1 addition & 1 deletion components/legacy/e2e-helper/e2e-fixtures-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import chalk from 'chalk';
import fs from 'fs-extra';
import { capitalize } from 'lodash';
import * as path from 'path';
import tar from 'tar';
import * as tar from 'tar';

import * as fixtures from './fixtures';
import type CommandHelper from './e2e-command-helper';
Expand Down
1 change: 1 addition & 0 deletions components/legacy/e2e-helper/e2e-fs-helper.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/// <reference types="chai-fs" />
import fs from 'fs-extra';
import { use, expect } from 'chai';
import { globSync } from 'glob';
Expand Down
2 changes: 1 addition & 1 deletion components/legacy/e2e-helper/e2e-general-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect } from 'chai';
import fs from 'fs-extra';
import * as path from 'path';
import { compact } from 'lodash';
import tar from 'tar';
import * as tar from 'tar';
import { DEFAULT_LANE } from '@teambit/lane-id';
import { generateRandomStr } from '@teambit/toolbox.string.random';
import { defaultErrorHandler } from '@teambit/cli';
Expand Down
1,941 changes: 1,000 additions & 941 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions scopes/component/checkout/checkout.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/// <reference types="chai-fs" />
import { expect, use } from 'chai';
import fs from 'fs-extra';
import type { Harmony } from '@teambit/harmony';
Expand Down
60 changes: 60 additions & 0 deletions scopes/workspace/config-merger/component-config-merger.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { expect } from 'chai';
import { ComponentID } from '@teambit/component-id';
import { EnvsAspect } from '@teambit/envs';
import { ExtensionDataList } from '@teambit/legacy.extension-data';
import type { Logger } from '@teambit/logger';
import { ComponentConfigMerger } from './component-config-merger';

const noopLogger = { debug() {}, trace() {} } as unknown as Logger;

describe('ComponentConfigMerger', () => {
describe('env changed on "other" while the current env is a workspace component', () => {
// Reproduces the `bit ci pr` config-sync failure: the lane still uses a workspace env (so
// envStrategy bails out with "keep the current env"), and main migrated the component onto a
// different EXTERNAL env. The generic aspect merge must NOT copy main's `teambit.envs/envs`
// config verbatim — that config only holds the env id without its version, so leaking it would
// produce an unversioned external env and crash the snap with ExternalEnvWithoutVersion.
let mergedConfig: Record<string, any>;
before(() => {
const wsEnv = 'my-scope.envs/ws-env';
const extEnv = 'other-scope.envs/ext-env';
// current & base: component uses the workspace env `wsEnv`.
const current = ExtensionDataList.fromConfigObject({
[EnvsAspect.id]: { env: wsEnv },
[`${wsEnv}@0.0.1`]: {},
});
const base = ExtensionDataList.fromConfigObject({
[EnvsAspect.id]: { env: wsEnv },
[`${wsEnv}@0.0.1`]: {},
});
// other (main): component migrated onto the external env `extEnv`. As stored in a committed
// version, `teambit.envs/envs.config.env` carries the id WITHOUT a version; the version lives
// only in the separate env-aspect entry.
const other = ExtensionDataList.fromConfigObject({
[EnvsAspect.id]: { env: extEnv },
[`${extEnv}@1.0.0`]: {},
});
// `wsEnv` is part of the workspace — this is what makes envStrategy decline.
const workspaceIds = [ComponentID.fromString(`${wsEnv}@0.0.1`)];
const merger = new ComponentConfigMerger(
'my-scope/some-comp',
workspaceIds,
undefined,
current,
base,
other,
'lane',
'main',
noopLogger,
'ours'
);
mergedConfig = merger.merge().getSuccessfullyMergedConfig();
});
it('should NOT sync teambit.envs/envs from the generic aspect merge (no unversioned env leak)', () => {
expect(
mergedConfig[EnvsAspect.id],
`expected no env to be synced, got: ${JSON.stringify(mergedConfig[EnvsAspect.id])}`
).to.be.undefined;
});
});
});
8 changes: 7 additions & 1 deletion scopes/workspace/config-merger/component-config-merger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,13 @@ export class ComponentConfigMerger {
private currentEnv: EnvData;
private otherEnv: EnvData;
private baseEnv?: EnvData;
private handledExtIds: string[] = [BuilderAspect.id]; // don't try to merge builder, it's possible that at one end it wasn't built yet, so it's empty
// don't try to merge builder, it's possible that at one end it wasn't built yet, so it's empty.
// teambit.envs/envs is handled exclusively by envStrategy() — the generic aspect merge would copy
// `config.env` verbatim, which never carries the env's version (the version lives in the separate
// env-aspect entry that only envStrategy knows to attach). If envStrategy declines (e.g. the
// current env is a workspace component), letting the generic merge run would leak an unversioned
// external env and break the snap with ExternalEnvWithoutVersion.
private handledExtIds: string[] = [BuilderAspect.id, EnvsAspect.id];
Comment on lines +74 to +80

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. Broken env lane check 🐞 Bug ≡ Correctness

After adding EnvsAspect.id to handledExtIds, envStrategy() becomes the only path that can merge
teambit.envs/envs. envStrategy() calls `isIdInWorkspaceOrOtherLane(this.currentEnv.id,
this.otherEnv.version), but populateEnvs() derives otherEnv.version` from a different extension id
than currentEnv.id, so the otherLaneIdsStr.includes(id@version) check can fail to detect that
the current env exists in the other lane and incorrectly proceed to merge/sync env config.
Agent Prompt
### Issue description
`ComponentConfigMerger` now skips generic merging of `teambit.envs/envs` by seeding `handledExtIds` with `EnvsAspect.id`. This makes `envStrategy()` the sole source of a merge result for `EnvsAspect.id`.

However, `envStrategy()` currently calls:

```ts
this.isIdInWorkspaceOrOtherLane(this.currentEnv.id, this.otherEnv.version)
```

`populateEnvs()` computes `currentEnv` and `otherEnv` (and their versions) from different extension IDs, so when the env IDs differ, `otherEnv.version` does not correspond to `currentEnv.id`. Since `isIdInWorkspaceOrOtherLane()` checks `otherLaneIdsStr.includes(`${id}@${versionOnOtherLane}`)`, this makes the “other lane” portion of the condition unreliable.

### Issue Context
This can cause `envStrategy()` to fall through to `basicConfigMerge()` and emit an env merge result when it should have returned `null` (i.e., keep the current env because it exists in the other lane).

### Fix Focus Areas
- scopes/workspace/config-merger/component-config-merger.ts[196-217]
- scopes/workspace/config-merger/component-config-merger.ts[157-186]
- scopes/workspace/config-merger/component-config-merger.ts[631-633]

### Suggested fix
Adjust the check so the version used matches the ID being checked, e.g.:
- Pass `this.currentEnv.version` when checking `this.currentEnv.id`, **or**
- Change the helper to support an id-only check against `otherLaneIdsStr` (ignore version) for this env-specific branch.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

private otherLaneIdsStr: string[];
constructor(
private compIdStr: string,
Expand Down
6 changes: 5 additions & 1 deletion scopes/workspace/workspace/ui/workspace/use-workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import { ComponentID } from '@teambit/component-id';

import { Workspace } from './workspace-model';

type UseWorkspaceResult = Omit<ReturnType<typeof useDataQuery>, 'data' | 'previousData'> & {
workspace: Workspace | undefined;
};

type UseWorkspaceOptions = {
onComponentAdded?: (component: ComponentModel[]) => void;
onComponentUpdated?: (component: ComponentModel[]) => void;
Expand Down Expand Up @@ -127,7 +131,7 @@ const COMPONENT_SERVER_STARTED = gql`
}
`;

export function useWorkspace(options: UseWorkspaceOptions = {}) {
export function useWorkspace(options: UseWorkspaceOptions = {}): UseWorkspaceResult {
const { data, subscribeToMore, ...rest } = useDataQuery(WORKSPACE);
const optionsRef = useLatest(options);

Expand Down
91 changes: 91 additions & 0 deletions scripts/check-harmony-version-sync.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#!/bin/bash

# This script validates that the @teambit/harmony version is consistent across
# workspace.jsonc and the two bundle-install overrides in .circleci/config.yml.
#
# Why this can drift: workspace.jsonc pins harmony for the dev workspace build,
# while .circleci/config.yml re-pins it via `pnpm.overrides.@teambit/harmony`
# for the freshly-installed published `@teambit/bit` bundle used by e2e. Both
# must reference the SAME harmony version so e2e exercises what the workspace
# built with. It's easy to bump one and forget the other.
#
# Usage:
# ./scripts/check-harmony-version-sync.sh # validate (used by CI); exits 1 on mismatch
# ./scripts/check-harmony-version-sync.sh --fix # rewrite the config.yml overrides to match workspace.jsonc

set -e

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

FIX=false
if [ "${1:-}" = "--fix" ]; then
FIX=true
fi

WORKSPACE_FILE="workspace.jsonc"
CONFIG_FILE=".circleci/config.yml"

echo "Checking @teambit/harmony version synchronization..."

# Source of truth: the harmony pins in workspace.jsonc. There are two (the
# dependency policy and the dependency-resolver overrides); they must agree.
WS_VERSIONS=$(grep -oE '"@teambit/harmony": *"[0-9]+\.[0-9]+\.[0-9]+"' "$WORKSPACE_FILE" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | sort -u)
WS_COUNT=$(echo "$WS_VERSIONS" | grep -c .)

if [ -z "$WS_VERSIONS" ]; then
echo -e "${RED}✗ ERROR: no @teambit/harmony pin found in ${WORKSPACE_FILE}${NC}"
exit 1
fi
Comment on lines +16 to +41

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. Set -e aborts counting 🐞 Bug ☼ Reliability

In check-harmony-version-sync.sh, WS_COUNT is computed using grep -c . while set -e is
enabled; when WS_VERSIONS is empty (e.g. the grep patterns don’t match), grep exits non-zero and
the script terminates before reaching the explicit “no @teambit/harmony pin found” error handling.
This makes the new CI step fail with less actionable output than intended for that edge case.
Agent Prompt
### Issue description
`scripts/check-harmony-version-sync.sh` enables `set -e` and then uses `grep -c .` to count lines in `WS_VERSIONS`. When `WS_VERSIONS` is empty (no matches), `grep -c` returns exit code 1, which can abort the script before it prints the intended explicit error message and guidance.

### Issue Context
This script is executed in CI (CircleCI job `check_env_cache_sync`) to validate harmony version synchronization.

### Fix Focus Areas
- scripts/check-harmony-version-sync.sh[16-47]

### Suggested fix
Move the `WS_COUNT` computation after the `-z "$WS_VERSIONS"` check, and/or replace the `grep -c .` counting with a command that does not fail on empty input. For example:
- After verifying `WS_VERSIONS` is non-empty, use `wc -l`:
  - `WS_COUNT=$(printf '%s\n' "$WS_VERSIONS" | wc -l | tr -d ' ')`
Or keep `grep` but neutralize its exit code:
- `WS_COUNT=$(echo "$WS_VERSIONS" | grep -c . || true)`

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


if [ "$WS_COUNT" -ne 1 ]; then
echo -e "${RED}✗ ERROR: ${WORKSPACE_FILE} has inconsistent @teambit/harmony pins:${NC}"
echo "$WS_VERSIONS" | sed 's/^/ /'
echo -e "${YELLOW}Fix workspace.jsonc first so all @teambit/harmony pins match, then re-run.${NC}"
Comment on lines +18 to +46

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Hardcoded ansi + ✓/✗ output 📘 Rule violation ⚙ Maintainability

scripts/check-harmony-version-sync.sh introduces hardcoded ANSI color escape sequences and Unicode
symbols (/) in CLI output. This violates the requirement to avoid ad-hoc styling/symbols and
instead use the shared CLI output formatting toolkit for consistent output.
Agent Prompt
## Issue description
The new bash script prints hardcoded ANSI color codes and Unicode symbols (`✓`/`✗`) in its output, which is disallowed by the CLI output compliance rule.

## Issue Context
This repository requires CLI output to avoid ad-hoc styling/symbols and instead use the shared formatting toolkit from `@teambit/cli` (or, for non-TS scripts, fall back to plain, unstyled ASCII output).

## Fix Focus Areas
- scripts/check-harmony-version-sync.sh[18-46]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

exit 1
fi

EXPECTED="$WS_VERSIONS"
echo "Found @teambit/harmony in ${WORKSPACE_FILE}: $EXPECTED"

# The config.yml overrides look like:
# pnpm.overrides.@teambit/harmony" --values "0.4.12"
CONFIG_VERSIONS=$(grep -oE 'pnpm\.overrides\.@teambit/harmony" --values "[0-9]+\.[0-9]+\.[0-9]+"' "$CONFIG_FILE" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | sort -u)

Comment on lines +33 to +56

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Informational

2. Strict harmony version regex 🐞 Bug ⚙ Maintainability

scripts/check-harmony-version-sync.sh only recognizes @teambit/harmony pins that match an exact
numeric "x.y.z" regex, so pins like "^0.4.12" or "0.4.13-rc.1" will be treated as missing/mismatched
and fail the new CI step. This can unexpectedly block future bumps if the pinning format changes
(ranges/prereleases).
Agent Prompt
### Issue description
`check-harmony-version-sync.sh` extracts versions using regexes that only match exact `x.y.z` strings. If the repo ever uses semver ranges (e.g. `^0.4.12`) or prereleases/build metadata (e.g. `0.4.13-rc.1`), the script will report “no pin found” / mismatch and fail CI.

### Issue Context
The script is now executed in CircleCI (`check_env_cache_sync` job). It is intended to prevent drift between `workspace.jsonc` and the `.circleci/config.yml` `pnpm.overrides.@teambit/harmony` overrides.

### Fix Focus Areas
- scripts/check-harmony-version-sync.sh[35-56]

### Suggested fix
Choose one:
1) **If exact pins are required by policy**: update script comments/error messages to explicitly state it enforces exact `x.y.z` pins (and consider validating that the pins are exact to make failures clearer).
2) **If flexibility is desired**: broaden the regex to accept semver prerelease/build metadata and optional `^`/`~`, e.g.:
   - `\^?([0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)`
   and use that capture for both `workspace.jsonc` and `.circleci/config.yml` extraction.
3) **Most robust**: replace grep-regex parsing with a small `node` snippet that parses JSONC/YAML and reads the relevant fields explicitly.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

if [ -z "$CONFIG_VERSIONS" ]; then
echo -e "${RED}✗ ERROR: no 'pnpm.overrides.@teambit/harmony' override found in ${CONFIG_FILE}${NC}"
exit 1
fi

if [ "$CONFIG_VERSIONS" = "$EXPECTED" ]; then
echo -e "${GREEN}✓ config.yml overrides match: @teambit/harmony@${EXPECTED}${NC}"
exit 0
fi

# Mismatch.
if [ "$FIX" = true ]; then
# Rewrite every harmony override version in config.yml to EXPECTED.
# Temp file next to the target (explicit template for BSD/macOS portability)
# so the final mv is an atomic same-filesystem rename.
TMP=$(mktemp "${CONFIG_FILE}.XXXXXX")
trap 'rm -f "$TMP"' EXIT
sed -E "s|(pnpm\.overrides\.@teambit/harmony\" --values \")[0-9]+\.[0-9]+\.[0-9]+(\")|\1${EXPECTED}\2|g" "$CONFIG_FILE" > "$TMP"
mv "$TMP" "$CONFIG_FILE"

NEW_VERSIONS=$(grep -oE 'pnpm\.overrides\.@teambit/harmony" --values "[0-9]+\.[0-9]+\.[0-9]+"' "$CONFIG_FILE" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | sort -u)
if [ "$NEW_VERSIONS" = "$EXPECTED" ]; then
echo -e "${GREEN}✓ Updated @teambit/harmony overrides in ${CONFIG_FILE} to ${EXPECTED}${NC}"
exit 0
fi
echo -e "${RED}✗ --fix failed to update overrides in ${CONFIG_FILE}${NC}"
exit 1
fi

echo -e "${RED}✗ ERROR: @teambit/harmony version mismatch!${NC}"
echo -e "${YELLOW}${WORKSPACE_FILE} has @teambit/harmony@${EXPECTED}, but ${CONFIG_FILE} has:${NC}"
echo "$CONFIG_VERSIONS" | sed 's/^/ /'
echo ""
echo -e "${YELLOW}Run: ./scripts/check-harmony-version-sync.sh --fix${NC}"
exit 1
4 changes: 2 additions & 2 deletions workspace.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@
"@teambit/git.modules.git-ignore": "^1.0.2",
"@teambit/gitconfig": "2.0.10",
"@teambit/graph.cleargraph": "0.0.11",
"@teambit/harmony": "0.4.11",
"@teambit/harmony": "0.4.12",
"@teambit/html.modules.inject-html-element": "0.0.6",
"@teambit/lane-id": "~0.0.312",
"@teambit/lanes.ui.compare.lane-compare": "^0.0.206",
Expand Down Expand Up @@ -696,7 +696,7 @@
"@teambit/base-react.navigation.link@2": "2.0.33",
"lodash@4": "4.17.21",
"trim@0": "0.0.3",
"@teambit/harmony": "0.4.11",
"@teambit/harmony": "0.4.12",
"@types/fs-capacitor": "2.0.0",
// @types/node is used as a peer dependency, so to reduce duplication,
// we force the version installed in the root.
Expand Down