From 5b6572ed688a42e8cc1503772fdd0b1608d830db Mon Sep 17 00:00:00 2001 From: Andrii Furmanets Date: Thu, 30 Apr 2026 17:11:20 +0300 Subject: [PATCH 1/2] Fix Bun lockfile cleanup --- .changeset/fix-bun-lockfile-cleanup.md | 6 ++++++ .../src/cli/services/init/template/cleanup.test.ts | 4 ++++ .../app/src/cli/services/init/template/cleanup.ts | 13 +++++++------ .../src/public/node/node-package-manager.test.ts | 7 +++++++ .../cli-kit/src/public/node/node-package-manager.ts | 11 +++++++++-- 5 files changed, 33 insertions(+), 8 deletions(-) create mode 100644 .changeset/fix-bun-lockfile-cleanup.md diff --git a/.changeset/fix-bun-lockfile-cleanup.md b/.changeset/fix-bun-lockfile-cleanup.md new file mode 100644 index 00000000000..51c64f9fea4 --- /dev/null +++ b/.changeset/fix-bun-lockfile-cleanup.md @@ -0,0 +1,6 @@ +--- +'@shopify/app': patch +'@shopify/cli-kit': patch +--- + +Handle modern Bun `bun.lock` files when cleaning up app templates so non-Bun projects do not keep stale Bun lockfiles or `.gitignore` entries. diff --git a/packages/app/src/cli/services/init/template/cleanup.test.ts b/packages/app/src/cli/services/init/template/cleanup.test.ts index 37fe613d02e..367a9d10791 100644 --- a/packages/app/src/cli/services/init/template/cleanup.test.ts +++ b/packages/app/src/cli/services/init/template/cleanup.test.ts @@ -68,6 +68,7 @@ describe('cleanup', () => { await writeFile(joinPath(tmpDir, 'package-lock.json'), '{}') await writeFile(joinPath(tmpDir, 'yarn.lock'), '{}') await writeFile(joinPath(tmpDir, 'pnpm-lock.yaml'), '{}') + await writeFile(joinPath(tmpDir, 'bun.lock'), '{}') await writeFile(joinPath(tmpDir, 'bun.lockb'), '{}') // When @@ -77,6 +78,7 @@ describe('cleanup', () => { await expect(fileExists(joinPath(tmpDir, 'package-lock.json'))).resolves.toBe(packageManager === 'npm') await expect(fileExists(joinPath(tmpDir, 'yarn.lock'))).resolves.toBe(packageManager === 'yarn') await expect(fileExists(joinPath(tmpDir, 'pnpm-lock.yaml'))).resolves.toBe(packageManager === 'pnpm') + await expect(fileExists(joinPath(tmpDir, 'bun.lock'))).resolves.toBe(packageManager === 'bun') await expect(fileExists(joinPath(tmpDir, 'bun.lockb'))).resolves.toBe(packageManager === 'bun') }) }) @@ -87,6 +89,7 @@ describe('cleanup', () => { await writeFile(joinPath(tmpDir, 'package-lock.json'), '{}') await writeFile(joinPath(tmpDir, 'yarn.lock'), '{}') await writeFile(joinPath(tmpDir, 'pnpm-lock.yaml'), '{}') + await writeFile(joinPath(tmpDir, 'bun.lock'), '{}') await writeFile(joinPath(tmpDir, 'bun.lockb'), '{}') // When @@ -96,6 +99,7 @@ describe('cleanup', () => { await expect(fileExists(joinPath(tmpDir, 'package-lock.json'))).resolves.toBe(false) await expect(fileExists(joinPath(tmpDir, 'yarn.lock'))).resolves.toBe(false) await expect(fileExists(joinPath(tmpDir, 'pnpm-lock.yaml'))).resolves.toBe(false) + await expect(fileExists(joinPath(tmpDir, 'bun.lock'))).resolves.toBe(false) await expect(fileExists(joinPath(tmpDir, 'bun.lockb'))).resolves.toBe(false) }) }) diff --git a/packages/app/src/cli/services/init/template/cleanup.ts b/packages/app/src/cli/services/init/template/cleanup.ts index fde535065e4..7938e491aef 100644 --- a/packages/app/src/cli/services/init/template/cleanup.ts +++ b/packages/app/src/cli/services/init/template/cleanup.ts @@ -1,5 +1,5 @@ import {rmdir, glob, fileExistsSync, unlinkFile} from '@shopify/cli-kit/node/fs' -import {Lockfile, lockfilesByManager, PackageManager} from '@shopify/cli-kit/node/node-package-manager' +import {lockfiles, lockfilesForPackageManager, PackageManager} from '@shopify/cli-kit/node/node-package-manager' import {joinPath} from '@shopify/cli-kit/node/path' export default async function cleanup(webOutputDirectory: string, packageManager: PackageManager) { @@ -23,12 +23,13 @@ export default async function cleanup(webOutputDirectory: string, packageManager const gitPathPromises = gitPaths.map((path) => rmdir(path, {force: true})) - const lockfilePromises = Object.entries(lockfilesByManager) - .filter(([manager, lockfile]) => manager !== packageManager && lockfile) - .map(([_, lockfile]) => { - const path = joinPath(webOutputDirectory, lockfile as Lockfile) + const lockfilesToKeep = new Set(lockfilesForPackageManager(packageManager)) + const lockfilePromises = lockfiles + .filter((lockfile) => !lockfilesToKeep.has(lockfile)) + .map((lockfile) => { + const path = joinPath(webOutputDirectory, lockfile) if (fileExistsSync(path)) return unlinkFile(path) - }, []) + }) return Promise.all([...gitPathPromises, ...lockfilePromises]) } diff --git a/packages/cli-kit/src/public/node/node-package-manager.test.ts b/packages/cli-kit/src/public/node/node-package-manager.test.ts index df1d2ff0a46..93ee0933b49 100644 --- a/packages/cli-kit/src/public/node/node-package-manager.test.ts +++ b/packages/cli-kit/src/public/node/node-package-manager.test.ts @@ -22,6 +22,7 @@ import { inferPackageManager, PackageManager, npmLockfile, + lockfilesForPackageManager, } from './node-package-manager.js' import {captureOutput, exec} from './system.js' import {inTemporaryDirectory, mkdir, touchFile, writeFile} from './fs.js' @@ -132,6 +133,12 @@ describe('install', () => { }) }) +describe('lockfilesForPackageManager', () => { + test('returns both Bun lockfile names', () => { + expect(lockfilesForPackageManager('bun')).toEqual(['bun.lockb', 'bun.lock']) + }) +}) + describe('getPackageName', () => { test('returns package name', async () => { await inTemporaryDirectory(async (tmpDir) => { diff --git a/packages/cli-kit/src/public/node/node-package-manager.ts b/packages/cli-kit/src/public/node/node-package-manager.ts index fe4d17e3aa0..695d592f552 100644 --- a/packages/cli-kit/src/public/node/node-package-manager.ts +++ b/packages/cli-kit/src/public/node/node-package-manager.ts @@ -29,7 +29,7 @@ const modernBunLockfile = 'bun.lock' export const pnpmWorkspaceFile = 'pnpm-workspace.yaml' /** An array containing the lockfiles from all the package managers */ -export const lockfiles: Lockfile[] = [yarnLockfile, pnpmLockfile, npmLockfile, bunLockfile] +export const lockfiles: Lockfile[] = [yarnLockfile, pnpmLockfile, npmLockfile, bunLockfile, modernBunLockfile] export const lockfilesByManager: Record = { yarn: yarnLockfile, npm: npmLockfile, @@ -38,7 +38,7 @@ export const lockfilesByManager: Record = homebrew: undefined, unknown: undefined, } -export type Lockfile = 'yarn.lock' | 'package-lock.json' | 'pnpm-lock.yaml' | 'bun.lockb' +export type Lockfile = 'yarn.lock' | 'package-lock.json' | 'pnpm-lock.yaml' | 'bun.lockb' | 'bun.lock' /** * A union type that represents the type of dependencies in the package.json @@ -55,6 +55,13 @@ export const packageManager = ['yarn', 'npm', 'pnpm', 'bun', 'homebrew', 'unknow export type PackageManager = (typeof packageManager)[number] type ProjectPackageManager = Extract +export function lockfilesForPackageManager(packageManager: PackageManager): Lockfile[] { + if (packageManager === 'bun') return [bunLockfile, modernBunLockfile] + + const lockfile = lockfilesByManager[packageManager] + return lockfile ? [lockfile] : [] +} + /** * Returns an abort error that's thrown when the package manager can't be determined. * @returns An abort error. From face0aea3f7dafda21b936dcb60a26a62f3078a9 Mon Sep 17 00:00:00 2001 From: Andrii Furmanets Date: Tue, 5 May 2026 12:17:03 +0300 Subject: [PATCH 2/2] Apply lockfile mapping review suggestion --- .../src/cli/services/init/template/cleanup.ts | 4 ++-- .../public/node/node-package-manager.test.ts | 8 +++---- .../src/public/node/node-package-manager.ts | 21 +++++++------------ 3 files changed, 13 insertions(+), 20 deletions(-) diff --git a/packages/app/src/cli/services/init/template/cleanup.ts b/packages/app/src/cli/services/init/template/cleanup.ts index 7938e491aef..37df8717f82 100644 --- a/packages/app/src/cli/services/init/template/cleanup.ts +++ b/packages/app/src/cli/services/init/template/cleanup.ts @@ -1,5 +1,5 @@ import {rmdir, glob, fileExistsSync, unlinkFile} from '@shopify/cli-kit/node/fs' -import {lockfiles, lockfilesForPackageManager, PackageManager} from '@shopify/cli-kit/node/node-package-manager' +import {lockfiles, lockfilesByManager, PackageManager} from '@shopify/cli-kit/node/node-package-manager' import {joinPath} from '@shopify/cli-kit/node/path' export default async function cleanup(webOutputDirectory: string, packageManager: PackageManager) { @@ -23,7 +23,7 @@ export default async function cleanup(webOutputDirectory: string, packageManager const gitPathPromises = gitPaths.map((path) => rmdir(path, {force: true})) - const lockfilesToKeep = new Set(lockfilesForPackageManager(packageManager)) + const lockfilesToKeep = new Set(lockfilesByManager[packageManager]) const lockfilePromises = lockfiles .filter((lockfile) => !lockfilesToKeep.has(lockfile)) .map((lockfile) => { diff --git a/packages/cli-kit/src/public/node/node-package-manager.test.ts b/packages/cli-kit/src/public/node/node-package-manager.test.ts index 93ee0933b49..c47933d0f13 100644 --- a/packages/cli-kit/src/public/node/node-package-manager.test.ts +++ b/packages/cli-kit/src/public/node/node-package-manager.test.ts @@ -22,7 +22,7 @@ import { inferPackageManager, PackageManager, npmLockfile, - lockfilesForPackageManager, + lockfilesByManager, } from './node-package-manager.js' import {captureOutput, exec} from './system.js' import {inTemporaryDirectory, mkdir, touchFile, writeFile} from './fs.js' @@ -133,9 +133,9 @@ describe('install', () => { }) }) -describe('lockfilesForPackageManager', () => { - test('returns both Bun lockfile names', () => { - expect(lockfilesForPackageManager('bun')).toEqual(['bun.lockb', 'bun.lock']) +describe('lockfilesByManager', () => { + test('maps Bun to both lockfile names', () => { + expect(lockfilesByManager.bun).toEqual(['bun.lockb', 'bun.lock']) }) }) diff --git a/packages/cli-kit/src/public/node/node-package-manager.ts b/packages/cli-kit/src/public/node/node-package-manager.ts index 695d592f552..7d0899500a6 100644 --- a/packages/cli-kit/src/public/node/node-package-manager.ts +++ b/packages/cli-kit/src/public/node/node-package-manager.ts @@ -30,13 +30,13 @@ export const pnpmWorkspaceFile = 'pnpm-workspace.yaml' /** An array containing the lockfiles from all the package managers */ export const lockfiles: Lockfile[] = [yarnLockfile, pnpmLockfile, npmLockfile, bunLockfile, modernBunLockfile] -export const lockfilesByManager: Record = { - yarn: yarnLockfile, - npm: npmLockfile, - pnpm: pnpmLockfile, - bun: bunLockfile, - homebrew: undefined, - unknown: undefined, +export const lockfilesByManager: Record = { + yarn: [yarnLockfile], + npm: [npmLockfile], + pnpm: [pnpmLockfile], + bun: [bunLockfile, modernBunLockfile], + homebrew: [], + unknown: [], } export type Lockfile = 'yarn.lock' | 'package-lock.json' | 'pnpm-lock.yaml' | 'bun.lockb' | 'bun.lock' @@ -55,13 +55,6 @@ export const packageManager = ['yarn', 'npm', 'pnpm', 'bun', 'homebrew', 'unknow export type PackageManager = (typeof packageManager)[number] type ProjectPackageManager = Extract -export function lockfilesForPackageManager(packageManager: PackageManager): Lockfile[] { - if (packageManager === 'bun') return [bunLockfile, modernBunLockfile] - - const lockfile = lockfilesByManager[packageManager] - return lockfile ? [lockfile] : [] -} - /** * Returns an abort error that's thrown when the package manager can't be determined. * @returns An abort error.