Files
windmill/cli/src/utils/app_files.ts
T
Ruben FiszelandClaude Opus 5 0af7675588 fix: keep an app's deployed policy on wmill push (#11049)
* fix: keep an app's deployed policy on wmill push

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X47gNFqf1SsA67zTm71f8c

* fix: keep a first push's file-stated app policy

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X47gNFqf1SsA67zTm71f8c

* fix: deploy a file-stated viewer app as viewer, not publisher

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X47gNFqf1SsA67zTm71f8c

* fix: never put a repo-stated run identity on the wire

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X47gNFqf1SsA67zTm71f8c

* fix: send the deployed run identity only when the push may claim it

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X47gNFqf1SsA67zTm71f8c

* fix: let a file-stated execution mode win in both directions

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X47gNFqf1SsA67zTm71f8c

* refactor: drop the app-file execution mode helper with no caller

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X47gNFqf1SsA67zTm71f8c

* feat: warn before a raw-app push takes over the run-as user

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X47gNFqf1SsA67zTm71f8c

* fix: keep an app's run-as user when a push only deletes one of its files

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X47gNFqf1SsA67zTm71f8c

* refactor: settle an app's ownership check before any content parsing

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X47gNFqf1SsA67zTm71f8c

* fix: share one rule for which raw-app files a push sends

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X47gNFqf1SsA67zTm71f8c

* fix: stop tracking raw-app files no push ever sends

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X47gNFqf1SsA67zTm71f8c

* test: bundle for real instead of stubbing the module for every suite

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X47gNFqf1SsA67zTm71f8c

* docs: record why a module mock cannot be undone by afterAll

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X47gNFqf1SsA67zTm71f8c

* fix: keep tracking a runnable whose file shares a bundle-excluded name

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X47gNFqf1SsA67zTm71f8c

* test: pin both halves of the backend runnable rule

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X47gNFqf1SsA67zTm71f8c

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 17:19:02 +02:00

49 lines
1.8 KiB
TypeScript

import {
APP_BACKEND_FOLDER,
RECORDINGS_FOLDER,
} from "../commands/app/app_metadata.ts";
/** Directories under a raw app that no push sends. */
const NEVER_DEPLOYED_DIRS = new Set([
"node_modules",
"dist",
".claude",
"sql_to_apply",
]);
/** Files under a raw app that no push sends. */
const NEVER_DEPLOYED_FILES = new Set([
"package-lock.json",
"DATATABLES.md",
"AGENTS.md",
"wmill.d.ts",
]);
/**
* Whether an app-root-relative path (`/` separators, leading slash optional)
* reaches the server through any of a push's three channels: `raw_app.yaml` as
* metadata, the backend folder as runnables, the rest bundled by
* `collectAppFiles`. A path this rejects deploys nothing, so changing it is not
* a change to the app however much the sync diff lists it. `collectAppFiles`
* must not drift from this — it reads the same two sets.
*/
export function deploysWithRawApp(relativePath: string): boolean {
const segments = relativePath.split("/").filter(Boolean);
if (segments.length === 0) return false;
const name = segments[segments.length - 1];
const dirs = segments.slice(0, -1);
// The sets below describe the bundle, which never walks into the backend
// folder — applying them there would strip a runnable whose file shares a
// name (`backend/wmill.d.ts` is the runnable `wmill.d`). Depth 1 because
// `loadRunnablesFromBackend` reads that folder's top level only.
if (dirs[0] === APP_BACKEND_FOLDER) return dirs.length === 1;
if (NEVER_DEPLOYED_FILES.has(name)) return false;
if (dirs.some((d) => NEVER_DEPLOYED_DIRS.has(d))) return false;
// Session recordings are written at the app root only, so an app with a
// `recordings/` component folder of its own still ships it.
if (dirs[0] === RECORDINGS_FOLDER) return false;
return true;
}
export { NEVER_DEPLOYED_DIRS, NEVER_DEPLOYED_FILES };