Skip to content

feat: support yeoman-environment v3 and v6 #557

Open
ilya-taupeka wants to merge 5 commits into
mainfrom
feat/upgrade-yeoman-env
Open

feat: support yeoman-environment v3 and v6 #557
ilya-taupeka wants to merge 5 commits into
mainfrom
feat/upgrade-yeoman-env

Conversation

@ilya-taupeka

Copy link
Copy Markdown
Member

No description provided.

@ilya-taupeka
ilya-taupeka requested a review from bd82 July 7, 2026 13:49
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Build Report

badge

Please note:

  1. Files only stay for around 14 days!
  2. This comment will be updated with the data of the last successful build of this PR.
Name Link
Commit d4c2603
Logs https://github.com/SAP/app-studio-toolkit/actions/runs/29093159217
VSIX Files https://github.com/SAP/app-studio-toolkit/actions/runs/29093159217/artifacts/8227663741

@bd82 bd82 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

lets discuss in person after you review the comments

Comment thread projects/yeoman-ui/packages/yeoman-env-v3/webpack.config.cjs Outdated
Comment thread projects/yeoman-ui/packages/backend/package.json Outdated
Comment thread projects/yeoman-ui/packages/backend/package.json Outdated
Comment thread projects/yeoman-ui/packages/backend/package.json Outdated
Comment thread projects/yeoman-ui/packages/backend/package.json Outdated
Comment thread projects/yeoman-ui/packages/backend/src/utils/env.ts
Comment thread projects/yeoman-ui/packages/backend/src/utils/env.ts Outdated
Comment thread projects/yeoman-ui/packages/backend/src/utils/env.ts Outdated
Comment thread projects/yeoman-ui/packages/backend/src/utils/env.ts Outdated
Comment thread projects/yeoman-ui/packages/backend/src/utils/legacyGenerators.ts Outdated
- remove redundant .gitignore
- fix index.ts for yeoman-env-v3 package
- move namespaceToName function to legacyGenerators.ts
- fix tsconfig in yeoman-env-v3
- fix backend and yeoman-env-v3 package.json's
- fix webpack.config.cjs's
- env.ts: reuse yeoman-env-v3's exported type, drop local LegacyCompat
- webpack: bundle spdx-* instead of externalizing
- build: remove sibling orchestration
- types: add narrower YeomanUIQuestion type, remove as-any casts in tests
- remove include-list; dynamic v6-then-v3-fallback routing
- yeoman-env-v3 typed via @types/yeoman-environment@2
- add yeoman-ui project root package.json
- make YeomanUIQuestion via Pick
@ilya-taupeka
ilya-taupeka requested a review from bd82 July 10, 2026 14:01
@bd82 bd82 changed the title feat: upgrade yeoman-environment to v6 feat: support yeoman-environment v3 and v6 Jul 15, 2026

@bd82 bd82 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks much better and less complex than initial iterations 👍

I think the main thing to resolve are:

  1. Assumption that all loading errors and env error vs possible valid generator error.
  2. More edge cases fixtures / tests

this.unloadGeneratorModules(genNamespace);
const env: Environment<Environment.Options> = this.createEnvInstance(

// Try the default (modern) yeoman-environment v6 first. Generators written

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Perhaps this logic can be improved, instead of assuming every error is an env error

  • Could we identify the specific env errors when loading wrong env?
  • Does the current implementation mean we are swallowing and not showing valid errors on generator init?
  • We don't need to solve all the various matrix of env's possible errors, only a sub-set because we only have V3/V6 envs right now.

AI Review Comment related to this:

[Medium] v6 generator errors are retried as legacy incompatibility

This fails when a modern v6 generator throws from module import, constructor, or _postConstruct for a real generator error, such as invalid options or missing project state. The broad catch treats every v6 creation failure as a runtime-compatibility signal and calls createLegacyEnvAndGen() with the same generator metadata, so generator initialization can run a second time under v3 and the user may see the fallback failure instead of the original v6 error.

Suggested direction: only fall back for recognized v6/v3 shape or loading incompatibility errors, and rethrow errors that originate from the generator after it has started importing or constructing.

Validation: add a v6 fixture generator whose constructor or _postConstruct increments a counter and throws; createEnvAndGen() should reject with that original error and the counter should be 1, not retry the same generator via v3.


// Try the default (modern) yeoman-environment v6 first. Generators written
// against the v3/v4 API shape throw when v6 instantiates them; in that case
// fall back to the bundled v3 runtime. This keeps the routing fully dynamic:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[Low] Let names describe the v6/default routing flow

This comment is long enough that it is describing control flow the code itself could express. The implementation would be easier to maintain if the default-v6 path and fallback-v3 path were visible in names instead of explained in a paragraph.

Suggested direction:

  • Log routing generator ${genNamespace} to default yeoman-environment v6.
  • Rename local env to v6Env or defaultEnv.
  • Rename createLegacyEnvAndGen to createV3EnvAndGen or createLegacyV3EnvAndGen.
  • Keep only a short comment for the non-obvious policy, e.g. // v6 is the default runtime; retry with v3 for legacy generator shapes.

Example

this.logger?.debug(
  `routing generator ${genNamespace} to default yeoman-environment v6`
);

try {
  const v6Env: Environment = this.createEnvInstance(
    { sharedOptions: { forwardErrorToEnvironment: true } as any },
    adapter
  );

  v6Env.register(meta.resolved!, {
    namespace: genNamespace,
    packagePath: meta.packagePath,
  });

  const gen: any = await v6Env.create(genNamespace, { options } as any);

  return { env: v6Env, gen };
} catch (error) {
  this.logger?.info(
    `default yeoman-environment v6 could not create ${genNamespace}, falling back to legacy yeoman-environment v3`,
    { error: (error as Error)?.message }
  );
  return this.createLegacyV3EnvAndGen(genNamespace, meta, options, adapter);
}

options: any,
adapter: any
): EnvGen {
if (!this.legacyCompat) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we really need the complexity of lazy loading? how much time does it save?
If we did not do lazy loading, could we also avoid the copy into dist folder step?
basically treat this as a regular package?

  • Note the main/exports in package json of legacyV3Env pacakge.json can point to its bundled artifact if neede

}

/**
* Legacy code path: load yeoman-environment v3 from the pre-bundled compat

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this comment exposes too much CI/Build context in the scope of the source code.
If we change the bundling logic in webpack/package.json we might forget to update this comment
to align with new logic.

So, do we really need this duplication?
also see comment below, if we simplify the v3Legacy loading we won't need to comment about it explaining the complexity.

@@ -0,0 +1,13 @@
/**

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[Low] Prefer names over a line-by-line namespace comment

The doc comment repeats each transformation performed below, so it can easily become stale if the namespace handling changes. The function would be easier to maintain if the intermediate value was named after the Yeoman concept it represents.

e.g.:

// Yeoman namespaces may include a sub-generator suffix, e.g. "generator-foo:app".
export function namespaceToName(namespace: string): string {
  const packageNamespace = namespace.replace(/:.*$/, "");

  if (packageNamespace.startsWith("@")) {
    return packageNamespace.replace("/generator-", "/");
  }

  return packageNamespace.replace(/^generator-/, "");
}

const TerserPlugin = require("terser-webpack-plugin");

/** @type {import('webpack').Configuration} */
module.exports = {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is none trivial bundling, even if this iteration is might simpler than previous ones.

[Medium]: You could add a tiny smoke test that tries to load the bundled environment to verify it loads
after bundling.

[LOW]: If you believe you can add small smoke tests to also check the env runs properly after bundling
It might have value because it would allow to distinguish between bundling issues in yeoman-env-v3 vs in the whole backend package. But only if this is not high effort...

"url": "https://github.com/SAP/app-studio-toolkit.git",
"directory": "projects/yeoman-ui/packages/yeoman-env-v3"
},
"main": "./dist/index.js",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

it looks env-v3 is already exporting the bundle
so why was does it require copy step to use in backend package?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think if you move the bundle flow into compile (inline or via another script name)
the topological sorting of pnpm -r will handle this package being READY before backend tries to import it.

So no copy would be needed

@@ -0,0 +1,27 @@
{
"name": "yeoman-env-v3",
"version": "1.25.1",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

we have changesets, we don't need to lock to version of other pacakges.
Use the version of the real env-v3: 3.19.3

Maybe even add a tiny script to validate these two versions are aligned

// `backend/node_modules/`; the aliases here point webpack directly at
// those symlinks so the resolver finds them regardless of where in the
// pnpm store it started the walk.
alias: {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

👍

// on another machine (BAS, CI, …) readFileSync throws ENOENT for the frozen
// absolute path. Also, the un-referenced `fileURLToPath(import.meta.url)` call
// survives dead-code elimination and leaks the build-machine path as a string
// literal into the shipped bundle. Neutralize all four preamble constants so

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Neutralizing these does not affect runtime in any way?
So they are not needed at runtime inside our *.vsix?

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