fix(cli): stop a deleted raw-app .lock from dropping the whole app push (#10473)

* fix(cli): stop a deleted raw-app .lock from dropping the whole app push

* test(cli): assert the removed raw-app runnable is gone, trim comments
This commit is contained in:
Ruben Fiszel
2026-08-03 16:23:34 +00:00
committed by GitHub
parent 9d820f3517
commit 42543240e8
2 changed files with 80 additions and 2 deletions
+12 -2
View File
@@ -5003,6 +5003,10 @@ export async function push(
if (deleteRawApp) {
changes = [deleteRawApp];
} else {
// The app is one bundle, so a single change re-pushes all of it.
// That leaves the loop exactly one change: any skip it takes for
// a raw-app path drops the whole app from the push, and nothing
// downstream records that as a failure.
changes.splice(1, changes.length - 1);
}
}
@@ -5331,8 +5335,14 @@ export async function push(
}
} else if (change.name === "deleted") {
// Same as the added branch: a dbt project's own `.lock` is one of
// its files, so deleting it has to reach the parent script.
if (change.path.endsWith(".lock") && !isDbtModulePath(change.path)) {
// its files, so deleting it has to reach the parent script. A raw
// app's `.lock` is part of its bundle, and a raw-app group is
// collapsed to one change, so skipping it drops the whole app.
if (
!isRawAppFile(change.path) &&
change.path.endsWith(".lock") &&
!isDbtModulePath(change.path)
) {
continue;
}
if (isScriptModulePath(change.path)) {
+68
View File
@@ -464,6 +464,74 @@ excludes: []`, "utf-8");
});
});
test("Raw App: deleted .lock sorting first does not short-circuit the app push", async () => {
// Regression: raw-app changes collapse to one representative change and
// deletes sort first, so removing a backend runnable makes its `.lock` the
// representative. Skipping a `.lock` there drops the whole app while the
// push still reports success.
await withTestBackend(async (backend, tempDir) => {
const testWorkspace = {
remote: backend.baseUrl,
workspaceId: backend.workspace,
name: "raw_app_lock_first_test",
token: backend.token
};
await addWorkspace(testWorkspace, { force: true, configDir: backend.testConfigDir });
await writeFile(`${tempDir}/wmill.yaml`, `defaultTs: bun
includes:
- "**"
excludes: []`, "utf-8");
const appDir = path.join(tempDir, "f", "test", "lock_first_app.raw_app");
await mkdir(path.join(tempDir, "f", "test"), { recursive: true });
await createRawAppOnDisk(appDir, true);
// A backend runnable's lockfile. Among the app's files it sorts first,
// so once deleted it becomes changes[0] for the whole group.
const queryLockPath = path.join(appDir, "backend", "query.lock");
await writeFile(queryLockPath, INLINE_SCRIPT_A_LOCK, "utf-8");
const pushResult1 = await backend.runCLICommand(
['sync', 'push', '--yes'],
tempDir, "raw_app_lock_first_test"
);
expect(pushResult1.code).toEqual(0);
await waitForDeploymentJobs(backend);
// Remove the backend runnable (lock included) and edit a frontend file.
await rm(queryLockPath);
await rm(path.join(appDir, "backend", "query.ts"));
await rm(path.join(appDir, "backend", "query.yaml"));
const appTsxPath = path.join(appDir, "App.tsx");
const appTsxContent = await readFileContent(appTsxPath);
await writeFile(
appTsxPath,
appTsxContent.replace("hello world", "REGRESSION MARKER"),
"utf-8"
);
const pushResult2 = await backend.runCLICommand(
['sync', 'push', '--yes'],
tempDir, "raw_app_lock_first_test"
);
expect(pushResult2.code).toEqual(0);
await waitForDeploymentJobs(backend);
// The edit must have reached the remote bundle, and the removed runnable
// must be gone from it.
const appResp = await backend.apiRequest!(
`/api/w/${backend.workspace}/apps/get/p/f/test/lock_first_app`
);
expect(appResp.status).toEqual(200);
const appJson = await appResp.json();
const files = appJson?.value?.files ?? {};
expect(files["/App.tsx"]).toContain("REGRESSION MARKER");
// Backend runnables live in value.runnables, not in the bundled files.
expect(appJson?.value?.runnables?.query).toBeUndefined();
});
});
test("Raw App: delete file and push", async () => {
await withTestBackend(async (backend, tempDir) => {
// Set up workspace