From b821061526ca5576014b74b362cb4cb771744752 Mon Sep 17 00:00:00 2001 From: "sonarly[bot]" <251243324+sonarly[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 07:39:53 +0000 Subject: [PATCH] fix(create-twenty-app): preserve .yarnrc.yml in template (#20623) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Source:** https://sonarly.com/issue/37981?type=bug ## Summary New apps created with `create-twenty-app@2.5.0` can fail at `yarn twenty dev` with `Could not resolve "twenty-sdk/define"`, blocking onboarding for app developers. ## Root cause Proximate cause: manifest module loading in the SDK fails to resolve `twenty-sdk/define` when the generated app uses Yarn PnP (no `node_modules` tree), and esbuild is invoked with normal Node-style resolution. - The failing path is `extractManifestFromFile()` → `loadModule()` in `packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-extract-config-from-file.ts`, which calls `esbuild.build({ bundle: true, ... })` and does not stub/mock `twenty-sdk/define` [ref: read `manifest-extract-config-from-file.ts`]. - The scaffolded template imports `twenty-sdk/define` in `src/application-config.ts` and `src/default-role.ts` [ref: grep in `packages/create-twenty-app/src/constants/template/src/*`]. - If esbuild cannot resolve that import from the app environment, the exact error matches the issue: `src/application-config.ts:1:34: ERROR: Could not resolve "twenty-sdk/define"`. Triggering cause (why now): `create-twenty-app@2.5.0` (the current npm `latest`) ships a template tarball without `.yarnrc.yml`, so new projects silently default to Yarn PnP instead of `node-modules`. Evidence: - Source template contains `.yarnrc.yml` with `nodeLinker: node-modules` [ref: read `packages/create-twenty-app/src/constants/template/.yarnrc.yml`]. - Published npm tarball for `create-twenty-app-2.5.0.tgz` does **not** contain `template/.yarnrc.yml` (but does contain renamed `gitignore`/`github`) [ref: tarball listing command output: `hasYarnrc: false`]. - Dotfile-preservation logic in `copyBaseApplicationProject()` only renames `gitignore` and `github`; it does not preserve `.yarnrc.yml` [ref: read `packages/create-twenty-app/src/utils/app-template.ts`]. - `npm dist-tags` shows `latest: 2.5.0`, so users following docs with `@latest` receive this broken scaffold path now [ref: npm registry query]. Why this is attributable to a specific change: - Commit `15eb3e7edccdf4e9770a00a07bfbd026420f7c3b` introduced dotfile-preservation mechanics for template publish (`gitignore`/`github`) but left out `.yarnrc.yml`, creating the regression window for newly scaffolded apps [ref: `git show --stat 15eb3e7...`, `git blame` on `renameDotfiles()`]. ## Fix Implemented a targeted fix in `create-twenty-app` so `.yarnrc.yml` is preserved through npm packaging the same way `.gitignore` and `.github` are handled. What changed: 1) Template dotfile preservation - Removed `packages/create-twenty-app/src/constants/template/.yarnrc.yml` - Added `packages/create-twenty-app/src/constants/template/yarnrc.yml` with identical content: - `nodeLinker: node-modules` This avoids npm stripping the file from the published tarball. 2) Scaffold rename logic - Updated `packages/create-twenty-app/src/utils/app-template.ts`: - Added `{ from: 'yarnrc.yml', to: '.yarnrc.yml' }` in `renameDotfiles()` - Updated progress text and inline comment to include `.yarnrc.yml` So generated apps reliably restore `.yarnrc.yml` after template copy. 3) Regression test - Updated `packages/create-twenty-app/src/utils/__tests__/app-template.spec.ts`: - Added a test asserting `yarnrc.yml` is renamed to `.yarnrc.yml` - Added a small constant for the test path This locks the behavior and prevents reintroducing the publish omission regression. Validation notes: - Attempted to run the focused Jest test, but execution failed due missing workspace dependency state (`@nx/jest/preset` / node_modules state not installed in this environment). ## Original request fix(create-twenty-app): preserve .yarnrc.yml in template _Created by Sonarly by autonomous analysis (run 43375)._ --------- Co-authored-by: sonarly-bot Co-authored-by: martmull --- .../template/{.yarnrc.yml => yarnrc.yml} | 0 .../src/utils/__tests__/app-template.spec.ts | 22 +++++++++++++++++++ .../src/utils/app-template.ts | 3 ++- 3 files changed, 24 insertions(+), 1 deletion(-) rename packages/create-twenty-app/src/constants/template/{.yarnrc.yml => yarnrc.yml} (100%) diff --git a/packages/create-twenty-app/src/constants/template/.yarnrc.yml b/packages/create-twenty-app/src/constants/template/yarnrc.yml similarity index 100% rename from packages/create-twenty-app/src/constants/template/.yarnrc.yml rename to packages/create-twenty-app/src/constants/template/yarnrc.yml diff --git a/packages/create-twenty-app/src/utils/__tests__/app-template.spec.ts b/packages/create-twenty-app/src/utils/__tests__/app-template.spec.ts index b7de51bfd1..066cb2ce0c 100644 --- a/packages/create-twenty-app/src/utils/__tests__/app-template.spec.ts +++ b/packages/create-twenty-app/src/utils/__tests__/app-template.spec.ts @@ -17,6 +17,7 @@ const UNIVERSAL_IDENTIFIERS_PATH = join( 'constants', 'universal-identifiers.ts', ); +const YARNRC_PATH = 'yarnrc.yml'; // Template content matching template/src/constants/universal-identifiers.ts const TEMPLATE_UNIVERSAL_IDENTIFIERS = `export const APP_DISPLAY_NAME = 'DISPLAY-NAME-TO-BE-GENERATED'; @@ -173,6 +174,27 @@ describe('copyBaseApplicationProject', () => { expect(publicDirectoryContents).toHaveLength(0); }); + it('should rename yarnrc.yml to .yarnrc.yml in the scaffolded project', async () => { + await fs.writeFile( + join(testAppDirectory, YARNRC_PATH), + 'nodeLinker: node-modules', + ); + + await copyBaseApplicationProject({ + appName: 'my-test-app', + appDisplayName: 'My Test App', + appDescription: 'A test application', + appDirectory: testAppDirectory, + }); + + expect(await fs.pathExists(join(testAppDirectory, YARNRC_PATH))).toBe( + false, + ); + expect(await fs.pathExists(join(testAppDirectory, '.yarnrc.yml'))).toBe( + true, + ); + }); + it('should handle empty description', async () => { await copyBaseApplicationProject({ appName: 'my-test-app', diff --git a/packages/create-twenty-app/src/utils/app-template.ts b/packages/create-twenty-app/src/utils/app-template.ts index f16309726f..06884d8825 100644 --- a/packages/create-twenty-app/src/utils/app-template.ts +++ b/packages/create-twenty-app/src/utils/app-template.ts @@ -22,7 +22,7 @@ export const copyBaseApplicationProject = async ({ onProgress?.('Copying base template'); await fs.copy(join(__dirname, './constants/template'), appDirectory); - onProgress?.('Configuring dotfiles (.gitignore, .github)'); + onProgress?.('Configuring dotfiles (.gitignore, .github, .yarnrc.yml)'); await renameDotfiles({ appDirectory }); onProgress?.('Mirroring AGENTS.md to CLAUDE.md'); @@ -47,6 +47,7 @@ const renameDotfiles = async ({ appDirectory }: { appDirectory: string }) => { const renames = [ { from: 'gitignore', to: '.gitignore' }, { from: 'github', to: '.github' }, + { from: 'yarnrc.yml', to: '.yarnrc.yml' }, ]; for (const { from, to } of renames) {