feat: support yeoman-environment v3 and v6 #557
Conversation
Build ReportPlease note:
|
bd82
left a comment
There was a problem hiding this comment.
lets discuss in person after you review the comments
- 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
bd82
left a comment
There was a problem hiding this comment.
This looks much better and less complex than initial iterations 👍
I think the main thing to resolve are:
- Assumption that all loading errors and env error vs possible valid generator error.
- 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 |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
[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
envtov6EnvordefaultEnv. - Rename
createLegacyEnvAndGentocreateV3EnvAndGenorcreateLegacyV3EnvAndGen. - 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) { |
There was a problem hiding this comment.
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/exportsin 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 |
There was a problem hiding this comment.
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 @@ | |||
| /** | |||
There was a problem hiding this comment.
[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 = { |
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
it looks env-v3 is already exporting the bundle
so why was does it require copy step to use in backend package?
There was a problem hiding this comment.
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", | |||
There was a problem hiding this comment.
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: { |
| // 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 |
There was a problem hiding this comment.
Neutralizing these does not affect runtime in any way?
So they are not needed at runtime inside our *.vsix?
No description provided.