diff --git a/.changeset/dry-donkeys-cheer.md b/.changeset/dry-donkeys-cheer.md new file mode 100644 index 0000000000..70ed45fa74 --- /dev/null +++ b/.changeset/dry-donkeys-cheer.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Added a check to ensure that Yarn v1 is used when creating new projects. diff --git a/packages/create-app/src/lib/tasks.test.ts b/packages/create-app/src/lib/tasks.test.ts index 2894f1ffe9..95ced49490 100644 --- a/packages/create-app/src/lib/tasks.test.ts +++ b/packages/create-app/src/lib/tasks.test.ts @@ -193,27 +193,58 @@ describe('tasks', () => { // requires callback implementation to support `promisify` wrapper // https://stackoverflow.com/a/60579617/10044859 mockExec.mockImplementation((_command, callback) => { - callback(null, 'standard out', 'standard error'); + if (_command === 'yarn --version') { + callback(null, { stdout: '1.22.5', stderr: 'standard error' }); + } else { + callback(null, { stdout: 'standard out', stderr: 'standard error' }); + } }); const appDir = 'projects/dir'; await expect(buildAppTask(appDir)).resolves.not.toThrow(); - expect(mockChdir).toHaveBeenCalledTimes(2); + expect(mockChdir).toHaveBeenCalledTimes(1); expect(mockChdir).toHaveBeenNthCalledWith(1, appDir); - expect(mockChdir).toHaveBeenNthCalledWith(2, appDir); - expect(mockExec).toHaveBeenCalledTimes(2); + expect(mockExec).toHaveBeenCalledTimes(3); expect(mockExec).toHaveBeenNthCalledWith( 1, - 'yarn install', + 'yarn --version', expect.any(Function), ); expect(mockExec).toHaveBeenNthCalledWith( 2, + 'yarn install', + expect.any(Function), + ); + expect(mockExec).toHaveBeenNthCalledWith( + 3, 'yarn tsc', expect.any(Function), ); }); + it('should error out on incorrect yarn version', async () => { + const mockChdir = jest.spyOn(process, 'chdir'); + + // requires callback implementation to support `promisify` wrapper + // https://stackoverflow.com/a/60579617/10044859 + mockExec.mockImplementation((_command, callback) => { + callback(null, { stdout: '3.2.1', stderr: 'standard error' }); + }); + + const appDir = 'projects/dir'; + await expect(buildAppTask(appDir)).rejects.toThrow( + /^@backstage\/create-app requires Yarn v1, found '3\.2\.1'/, + ); + expect(mockChdir).toHaveBeenCalledTimes(1); + expect(mockChdir).toHaveBeenNthCalledWith(1, appDir); + expect(mockExec).toHaveBeenCalledTimes(1); + expect(mockExec).toHaveBeenNthCalledWith( + 1, + 'yarn --version', + expect.any(Function), + ); + }); + it('should fail if project directory does not exist', async () => { const appDir = 'projects/missingProject'; await expect(buildAppTask(appDir)).rejects.toThrow( diff --git a/packages/create-app/src/lib/tasks.ts b/packages/create-app/src/lib/tasks.ts index 23017c8ec7..f158a2d1f1 100644 --- a/packages/create-app/src/lib/tasks.ts +++ b/packages/create-app/src/lib/tasks.ts @@ -199,9 +199,21 @@ export async function createTemporaryAppFolderTask(tempDir: string) { * @param appDir - location of application to build */ export async function buildAppTask(appDir: string) { + process.chdir(appDir); + + await Task.forItem('determining', 'yarn version', async () => { + const result = await exec('yarn --version'); + const yarnVersion = result.stdout?.trim(); + + if (yarnVersion && !yarnVersion.startsWith('1.')) { + throw new Error( + `@backstage/create-app requires Yarn v1, found '${yarnVersion}'. You can migrate the project to Yarn 3 after creation using https://backstage.io/docs/tutorials/yarn-migration`, + ); + } + }); + const runCmd = async (cmd: string) => { await Task.forItem('executing', cmd, async () => { - process.chdir(appDir); await exec(cmd).catch(error => { process.stdout.write(error.stderr); process.stdout.write(error.stdout);