Skip to content

refactor: move the bit-aspect template from core to the core-aspect-env - #10531

Open
davidfirst wants to merge 4 commits into
masterfrom
move-bit-aspect-template-to-env
Open

refactor: move the bit-aspect template from core to the core-aspect-env#10531
davidfirst wants to merge 4 commits into
masterfrom
move-bit-aspect-template-to-env

Conversation

@davidfirst

Copy link
Copy Markdown
Member

The `bit-aspect` template is no longer registered by `teambit.harmony/aspect`. It now lives in `teambit.harmony/envs/core-aspect-env` (the env used by the aspects of this repo), which registers it via its `generators()` handler, and components created from it get that env instead of `teambit.harmony/aspect`.

  • `workspace.jsonc`: the env is added to the generator `envs` config (and installed via the root policy), so `bit templates` in this workspace shows `bit-aspect` from the env.
  • e2e: tests that used the template merely as an offline aspect scaffold now use a new `helper.fixtures.createAspect()`, which writes the same files and sets the same `teambit.harmony/aspect` env config. The create-command tests (`create.e2e.ts`) switched to the built-in `component-generator` template, which has an identical config.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Refactor: register bit-aspect template via core-aspect-env (not core)

✨ Enhancement 🧪 Tests ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

AI Description

• Stop registering the bit-aspect template from teambit.harmony/aspect core.
• Configure workspace generator envs to expose bit-aspect from core-aspect-env.
• Update e2e tests to scaffold aspects via fixtures or component-generator.
Diagram

graph TD
  W["workspace.jsonc"] --> G["Bit generator"] --> E["core-aspect-env"]
  A["aspect.templates.ts"] -->|"no longer registers"| G
  T["e2e tests"] --> F["e2e fixtures"] --> C["bit CLI commands"]
  C --> G

  subgraph Legend
    direction LR
    _cfg["Config"] ~~~ _mod["Module"] ~~~ _test["Tests"]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Keep bit-aspect template registered in core (backward-compat alias)
  • ➕ Avoids breaking e2e and external workflows that implicitly expect core registration
  • ➕ No workspace generator env configuration required
  • ➖ Keeps template ownership in the wrong layer (core instead of env)
  • ➖ Makes template env selection ambiguous (core env vs core-aspect-env)
2. Provide a deprecation shim template in core that forwards to env
  • ➕ Maintains existing template name while encouraging env-based usage
  • ➕ Allows controlled migration window for downstream consumers
  • ➖ Adds indirection and more maintenance in core
  • ➖ Still requires careful resolution rules to avoid duplicate templates

Recommendation: The PR’s approach (moving template registration to core-aspect-env and wiring it via generator envs) is the cleanest ownership model: the template lives with the env that should be applied to generated components. Given this change removes an implicit core registration, updating e2e scaffolding (fixtures + using component-generator where appropriate) is the right mitigation; consider adding a short deprecation note in release notes/changelog if external consumers relied on core registration.

Files changed (14) +1518 / -851

Refactor (1) +5 / -3
aspect.templates.tsStop registering bit-aspect template from aspect core templates +5/-3

Stop registering bit-aspect template from aspect core templates

• Removes the import and inclusion of 'aspectTemplate' from the templates list. Adds inline documentation clarifying that the template now lives in 'teambit.harmony/envs/core-aspect-env' and should be discovered via generator env config or '--env'.

scopes/harmony/aspect/aspect.templates.ts

Tests (10) +41 / -39
remove.e2e.tsUse fixtures.createAspect() instead of bit-aspect template +1/-1

Use fixtures.createAspect() instead of bit-aspect template

• Replaces 'helper.command.create('bit-aspect', ...)' with direct aspect scaffolding via 'helper.fixtures.createAspect()' to avoid relying on a core-registered template.

e2e/commands/remove.e2e.ts

repository-hooks-aspects.e2e.tsSwitch hooks aspect scaffolding to createAspect() fixture +1/-1

Switch hooks aspect scaffolding to createAspect() fixture

• Updates the test setup to scaffold the hooks aspect via fixtures rather than the removed 'bit-aspect' core template.

e2e/functionalities/repository-hooks-aspects.e2e.ts

aspect.e2e.tsReplace bit-aspect template usage with createAspect() fixture +3/-3

Replace bit-aspect template usage with createAspect() fixture

• Updates multiple aspect-related e2e flows to scaffold aspects via 'helper.fixtures.createAspect()' while keeping the env config aligned with 'teambit.harmony/aspect'.

e2e/harmony/aspect.e2e.ts

create.e2e.tsUpdate create-command tests to use component-generator template +22/-20

Update create-command tests to use component-generator template

• Replaces 'bit-aspect' template invocations with 'component-generator' and updates file expectations accordingly (e.g., 'my-aspect.ts' and 'files/component-file.ts'). Keeps coverage focused on 'bit create' behavior rather than env-provided templates.

e2e/harmony/create.e2e.ts

custom-aspects.e2e.tsScaffold custom aspects via fixtures rather than bit-aspect template +5/-5

Scaffold custom aspects via fixtures rather than bit-aspect template

• Replaces multiple aspect creations from the 'bit-aspect' template with 'helper.fixtures.createAspect()' to keep tests independent of template registration location.

e2e/harmony/custom-aspects.e2e.ts

bit-import-on-lanes.e2e.tsUse fixtures.createAspect() for lane import scenario +1/-1

Use fixtures.createAspect() for lane import scenario

• Updates lane-related e2e setup to scaffold the aspect directly via fixtures rather than using the removed core template.

e2e/harmony/lanes/bit-import-on-lanes.e2e.ts

rename.e2e.tsScaffold rename-test aspects via createAspect() fixture +3/-3

Scaffold rename-test aspects via createAspect() fixture

• Switches rename e2e scenarios from 'bit-aspect' template creation to 'helper.fixtures.createAspect()' to preserve the expected aspect structure.

e2e/harmony/rename.e2e.ts

root-components.e2e.tsReplace bit-aspect template usage with fixture aspect scaffolding +3/-3

Replace bit-aspect template usage with fixture aspect scaffolding

• Updates root-components e2e setup to create aspect dependencies via 'helper.fixtures.createAspect()' rather than invoking the 'bit-aspect' template.

e2e/harmony/root-components.e2e.ts

update-cmd.e2e.tsUpdate update-cmd test to use createAspect() with custom path +1/-1

Update update-cmd test to use createAspect() with custom path

• Replaces 'bit create bit-aspect --path' usage with 'helper.fixtures.createAspect()' and passes a path override, keeping the scenario focused on update behavior.

e2e/harmony/update-cmd.e2e.ts

use.e2e.tsUse createAspect() fixture for bit use tests +1/-1

Use createAspect() fixture for bit use tests

• Updates the setup to scaffold a new aspect via fixtures rather than relying on the removed 'bit-aspect' core template.

e2e/harmony/use.e2e.ts

Other (3) +1472 / -809
e2e-fixtures-helper.tsAdd createAspect() fixture to scaffold aspects without core template +56/-0

Add createAspect() fixture to scaffold aspects without core template

• Introduces a 'createAspect()' helper that writes the same files as the former 'bit-aspect' core template and sets 'teambit.harmony/aspect' as the env. This decouples e2e tests from core template registration while keeping the scaffolded aspect structure consistent.

components/legacy/e2e-helper/e2e-fixtures-helper.ts

pnpm-lock.yamlLockfile update for new core-aspect-env dependency +1412/-809

Lockfile update for new core-aspect-env dependency

• Updates the pnpm lockfile to reflect adding '@teambit/harmony.envs.core-aspect-env' (and any transitive resolution changes).

pnpm-lock.yaml

workspace.jsoncAdd core-aspect-env dependency and expose env templates to generator +4/-0

Add core-aspect-env dependency and expose env templates to generator

• Adds '@teambit/harmony.envs.core-aspect-env' to the root policy and configures 'teambit.generator/generator.envs' to include 'teambit.harmony/envs/core-aspect-env', making the env-provided templates visible in the workspace.

workspace.jsonc

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jul 27, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Wrong scope in createAspect ✓ Resolved 🐞 Bug ≡ Correctness
Description
FixtureHelper.createAspect() hardcodes the aspect/component scope to this.scopes.remote for both
the generated directory and Aspect.create({ id }), which can diverge from the workspace default
scope when addRemoteScopeAsDefaultScope is false. This can make the scaffolded aspect’s ID/path
not match the IDs referenced in workspace config/tests, causing e2e failures or the wrong aspect ID
being registered.
Code

components/legacy/e2e-helper/e2e-fixtures-helper.ts[R239-241]

+    const scope = this.scopes.remote;
+    const scopeWithoutOwner = scope.includes('.') ? scope.split('.')[1] : scope;
+    const dir = options.path || path.join(scopeWithoutOwner, name);
Evidence
The new fixture derives the aspect ID and directory from this.scopes.remote, but the e2e harness
explicitly documents and uses a different default scope (my-scope) when
addRemoteScopeAsDefaultScope is false; one migrated test sets that flag and then references
my-scope/my-aspect in workspace config. This mismatch can make the created aspect not align with
what the test config expects.

components/legacy/e2e-helper/e2e-fixtures-helper.ts[238-263]
components/legacy/e2e-helper/e2e-scope-helper.ts[16-18]
e2e/harmony/aspect.e2e.ts[42-47]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`FixtureHelper.createAspect()` always derives the scope from `this.scopes.remote`, but some tests explicitly create workspaces where the default scope is *not* the remote scope (e.g. `addRemoteScopeAsDefaultScope: false` leaves the default scope as `my-scope`). In those cases, the fixture generates files under the remote-derived directory and sets `Aspect.create({ id: '${remote}/${name}' })`, which can conflict with IDs used by the test/workspace configuration.
## Issue Context
At least one migrated test configures `addRemoteScopeAsDefaultScope: false` and then references `my-scope/my-aspect` in workspace config. The fixture currently generates an aspect with `id: '${this.scopes.remote}/${name}'`.
## Fix Focus Areas
- components/legacy/e2e-helper/e2e-fixtures-helper.ts[238-286]
- e2e/harmony/aspect.e2e.ts[42-47]
## Suggested fix
1. Update `createAspect()` to accept an optional `scope` (and possibly `scopeWithoutOwner`) in `options`, and use it for:
- computing `scopeWithoutOwner` and `dir`
- setting `Aspect.create({ id })`
2. In tests that set `addRemoteScopeAsDefaultScope: false` (e.g. `e2e/harmony/aspect.e2e.ts`), call `createAspect('my-aspect', { scope: 'my-scope' })` (or derive the scope from workspace defaultScope if you prefer a more automatic approach).
This ensures the generated Aspect id matches the workspace’s intended component scope.

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



Remediation recommended

2. Namespaced aspect scaffolding breaks 🐞 Bug ≡ Correctness ⭐ New
Description
FixtureHelper.createAspect() uses the raw component name to build TypeScript identifiers and
filenames, so names containing namespaces (e.g. ui/my-aspect) will produce invalid TS identifiers
(contain /) and unintended nested file paths. This makes the new helper incorrect for valid,
namespaced component IDs and can cause fixture setup/compile/add steps to fail when such names are
used.
Code

components/legacy/e2e-helper/e2e-fixtures-helper.ts[R242-266]

+  createAspect(name = 'my-aspect', options: { path?: string } = {}) {
+    const scope = this.workspaceJsonc.getDefaultScope() || this.scopes.remote;
+    const scopeWithoutOwner = scope.includes('.') ? scope.split('.')[1] : scope;
+    const dir = options.path || path.join(scopeWithoutOwner, name);
+    const namePascalCase = name
+      .split('-')
+      .map((part) => capitalize(part))
+      .join('');
+    this.fs.outputFile(
+      path.join(dir, 'index.ts'),
+      `import { ${namePascalCase}Aspect } from './${name}.aspect';
+
+export type { ${namePascalCase}Main } from './${name}.main.runtime';
+export default ${namePascalCase}Aspect;
+export { ${namePascalCase}Aspect };
+`
+    );
+    this.fs.outputFile(
+      path.join(dir, `${name}.aspect.ts`),
+      `import { Aspect } from '@teambit/harmony';
+
+export const ${namePascalCase}Aspect = Aspect.create({
+  id: '${scope}/${name}',
+});
+`
Evidence
createAspect() derives namePascalCase by splitting only on - and uses the raw name in
generated filenames/imports, which will embed / into identifiers/paths for namespaced component
names. Namespaced component names are treated as valid elsewhere in the e2e suite (e.g. create tests
use ui/my-aspect and another/level/ui/my-aspect), so the helper should not assume name never
contains /.

components/legacy/e2e-helper/e2e-fixtures-helper.ts[236-290]
e2e/harmony/create.e2e.ts[25-74]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`createAspect(name)` assumes `name` is a simple kebab-case basename. In Bit, component IDs can include namespaces via `/` (e.g. `ui/my-aspect`). Using the raw `name` in (1) TS identifiers (`namePascalCase`) and (2) filenames like `${name}.aspect.ts` produces invalid TypeScript and wrong nested paths.

### Issue Context
The e2e suite already treats names with `/` as valid component names (e.g. `ui/my-aspect`) via the `bit create` flow; the fixture helper should either support these names or explicitly reject them.

### Fix Focus Areas
- components/legacy/e2e-helper/e2e-fixtures-helper.ts[242-290]

### Suggested fix
- Split the input into `fullName` vs `leafName`:
 - `const parts = name.split('/'); const leafName = parts.pop()!; const namespacePath = parts.join('/');`
- Build `namePascalCase` from `leafName` (and sanitize further if needed).
- Generate filenames/imports using `leafName` (e.g. `${leafName}.aspect.ts`, `${leafName}.main.runtime.ts`).
- Keep the component ID passed to Bit as the full name (`i: name`) and keep `Aspect.create({ id: '${scope}/${name}' })` using the full name.
- For `dir`, avoid duplicating the namespace:
 - either include `namespacePath` once (e.g. `path.join(scopeWithoutOwner, namespacePath, leafName)`),
 - or require callers to pass `options.path` when `name` includes `/` (and throw with a clear error otherwise).

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


Grey Divider

Previous review results

Review updated until commit dff64b1 ⚖️ Balanced

Results up to commit c589e75


🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)


Action required
1. Wrong scope in createAspect ✓ Resolved 🐞 Bug ≡ Correctness
Description
FixtureHelper.createAspect() hardcodes the aspect/component scope to this.scopes.remote for both
the generated directory and Aspect.create({ id }), which can diverge from the workspace default
scope when addRemoteScopeAsDefaultScope is false. This can make the scaffolded aspect’s ID/path
not match the IDs referenced in workspace config/tests, causing e2e failures or the wrong aspect ID
being registered.
Code

components/legacy/e2e-helper/e2e-fixtures-helper.ts[R239-241]

+    const scope = this.scopes.remote;
+    const scopeWithoutOwner = scope.includes('.') ? scope.split('.')[1] : scope;
+    const dir = options.path || path.join(scopeWithoutOwner, name);
Evidence
The new fixture derives the aspect ID and directory from this.scopes.remote, but the e2e harness
explicitly documents and uses a different default scope (my-scope) when
addRemoteScopeAsDefaultScope is false; one migrated test sets that flag and then references
my-scope/my-aspect in workspace config. This mismatch can make the created aspect not align with
what the test config expects.

components/legacy/e2e-helper/e2e-fixtures-helper.ts[238-263]
components/legacy/e2e-helper/e2e-scope-helper.ts[16-18]
e2e/harmony/aspect.e2e.ts[42-47]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`FixtureHelper.createAspect()` always derives the scope from `this.scopes.remote`, but some tests explicitly create workspaces where the default scope is *not* the remote scope (e.g. `addRemoteScopeAsDefaultScope: false` leaves the default scope as `my-scope`). In those cases, the fixture generates files under the remote-derived directory and sets `Aspect.create({ id: '${remote}/${name}' })`, which can conflict with IDs used by the test/workspace configuration.
## Issue Context
At least one migrated test configures `addRemoteScopeAsDefaultScope: false` and then references `my-scope/my-aspect` in workspace config. The fixture currently generates an aspect with `id: '${this.scopes.remote}/${name}'`.
## Fix Focus Areas
- components/legacy/e2e-helper/e2e-fixtures-helper.ts[238-286]
- e2e/harmony/aspect.e2e.ts[42-47]
## Suggested fix
1. Update `createAspect()` to accept an optional `scope` (and possibly `scopeWithoutOwner`) in `options`, and use it for:
- computing `scopeWithoutOwner` and `dir`
- setting `Aspect.create({ id })`
2. In tests that set `addRemoteScopeAsDefaultScope: false` (e.g. `e2e/harmony/aspect.e2e.ts`), call `createAspect('my-aspect', { scope: 'my-scope' })` (or derive the scope from workspace defaultScope if you prefer a more automatic approach).
This ensures the generated Aspect id matches the workspace’s intended component scope.

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


Results up to commit 5ba03fd


🐞 Bugs (1) 📘 Rule violations (0) 📜 Skill insights (0)


Action required
1. Wrong scope in createAspect 🐞 Bug ≡ Correctness
Description
FixtureHelper.createAspect() hardcodes the aspect/component scope to this.scopes.remote for both
the generated directory and Aspect.create({ id }), which can diverge from the workspace default
scope when addRemoteScopeAsDefaultScope is false. This can make the scaffolded aspect’s ID/path
not match the IDs referenced in workspace config/tests, causing e2e failures or the wrong aspect ID
being registered.
Code

components/legacy/e2e-helper/e2e-fixtures-helper.ts[R239-241]

+    const scope = this.scopes.remote;
+    const scopeWithoutOwner = scope.includes('.') ? scope.split('.')[1] : scope;
+    const dir = options.path || path.join(scopeWithoutOwner, name);
Evidence
The new fixture derives the aspect ID and directory from this.scopes.remote, but the e2e harness
explicitly documents and uses a different default scope (my-scope) when
addRemoteScopeAsDefaultScope is false; one migrated test sets that flag and then references
my-scope/my-aspect in workspace config. This mismatch can make the created aspect not align with
what the test config expects.

components/legacy/e2e-helper/e2e-fixtures-helper.ts[238-263]
components/legacy/e2e-helper/e2e-scope-helper.ts[16-18]
e2e/harmony/aspect.e2e.ts[42-47]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`FixtureHelper.createAspect()` always derives the scope from `this.scopes.remote`, but some tests explicitly create workspaces where the default scope is *not* the remote scope (e.g. `addRemoteScopeAsDefaultScope: false` leaves the default scope as `my-scope`). In those cases, the fixture generates files under the remote-derived directory and sets `Aspect.create({ id: '${remote}/${name}' })`, which can conflict with IDs used by the test/workspace configuration.

## Issue Context
At least one migrated test configures `addRemoteScopeAsDefaultScope: false` and then references `my-scope/my-aspect` in workspace config. The fixture currently generates an aspect with `id: '${this.scopes.remote}/${name}'`.

## Fix Focus Areas
- components/legacy/e2e-helper/e2e-fixtures-helper.ts[238-286]
- e2e/harmony/aspect.e2e.ts[42-47]

## Suggested fix
1. Update `createAspect()` to accept an optional `scope` (and possibly `scopeWithoutOwner`) in `options`, and use it for:
  - computing `scopeWithoutOwner` and `dir`
  - setting `Aspect.create({ id })`
2. In tests that set `addRemoteScopeAsDefaultScope: false` (e.g. `e2e/harmony/aspect.e2e.ts`), call `createAspect('my-aspect', { scope: 'my-scope' })` (or derive the scope from workspace defaultScope if you prefer a more automatic approach).

This ensures the generated Aspect id matches the workspace’s intended component scope.

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


Results up to commit 800644b


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Great, no issues found!

Qodo reviewed your code and found no material issues that require review
Qodo Logo

Comment thread components/legacy/e2e-helper/e2e-fixtures-helper.ts Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 5ba03fd

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit c589e75

Comment on lines +242 to +266
createAspect(name = 'my-aspect', options: { path?: string } = {}) {
const scope = this.workspaceJsonc.getDefaultScope() || this.scopes.remote;
const scopeWithoutOwner = scope.includes('.') ? scope.split('.')[1] : scope;
const dir = options.path || path.join(scopeWithoutOwner, name);
const namePascalCase = name
.split('-')
.map((part) => capitalize(part))
.join('');
this.fs.outputFile(
path.join(dir, 'index.ts'),
`import { ${namePascalCase}Aspect } from './${name}.aspect';

export type { ${namePascalCase}Main } from './${name}.main.runtime';
export default ${namePascalCase}Aspect;
export { ${namePascalCase}Aspect };
`
);
this.fs.outputFile(
path.join(dir, `${name}.aspect.ts`),
`import { Aspect } from '@teambit/harmony';

export const ${namePascalCase}Aspect = Aspect.create({
id: '${scope}/${name}',
});
`

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. Namespaced aspect scaffolding breaks 🐞 Bug ≡ Correctness

FixtureHelper.createAspect() uses the raw component name to build TypeScript identifiers and
filenames, so names containing namespaces (e.g. ui/my-aspect) will produce invalid TS identifiers
(contain /) and unintended nested file paths. This makes the new helper incorrect for valid,
namespaced component IDs and can cause fixture setup/compile/add steps to fail when such names are
used.
Agent Prompt
### Issue description
`createAspect(name)` assumes `name` is a simple kebab-case basename. In Bit, component IDs can include namespaces via `/` (e.g. `ui/my-aspect`). Using the raw `name` in (1) TS identifiers (`namePascalCase`) and (2) filenames like `${name}.aspect.ts` produces invalid TypeScript and wrong nested paths.

### Issue Context
The e2e suite already treats names with `/` as valid component names (e.g. `ui/my-aspect`) via the `bit create` flow; the fixture helper should either support these names or explicitly reject them.

### Fix Focus Areas
- components/legacy/e2e-helper/e2e-fixtures-helper.ts[242-290]

### Suggested fix
- Split the input into `fullName` vs `leafName`:
  - `const parts = name.split('/'); const leafName = parts.pop()!; const namespacePath = parts.join('/');`
- Build `namePascalCase` from `leafName` (and sanitize further if needed).
- Generate filenames/imports using `leafName` (e.g. `${leafName}.aspect.ts`, `${leafName}.main.runtime.ts`).
- Keep the component ID passed to Bit as the full name (`i: name`) and keep `Aspect.create({ id: '${scope}/${name}' })` using the full name.
- For `dir`, avoid duplicating the namespace:
  - either include `namespacePath` once (e.g. `path.join(scopeWithoutOwner, namespacePath, leafName)`),
  - or require callers to pass `options.path` when `name` includes `/` (and throw with a clear error otherwise).

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

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit dff64b1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants