fix: keep the app editor's parent_version out of deployed apps (#11719)

* fix: keep the app editor's parent_version out of deployed apps

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: drop parent_version when deploying an app draft

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Ruben Fiszel
2026-10-13 09:18:26 +00:00
committed by GitHub
co-authored by Claude Opus 5.5
parent a298ed3fc1
commit 2c6de9d896
4 changed files with 44 additions and 9 deletions
+9
View File
@@ -115,6 +115,14 @@ export function replaceInlineScripts(
}
return [];
}
/** `parent_version` is the editor's stale-draft base: a version id that changes on
* every deploy and means nothing in another workspace, so it is kept out of the
* tracked file and out of what a push deploys. */
export function stripEditorState(app: any) {
if (app?.value && typeof app.value == "object") {
delete app.value.parent_version;
}
}
export function isExecutionModeAnonymous(app: any) {
return app?.["policy"]?.["execution_mode"] == "anonymous";
}
@@ -294,6 +302,7 @@ export async function pushApp(
}
const path = localPath + "app.yaml";
const localApp = (await yamlParseFile(path)) as AppFile;
stripEditorState(localApp);
const isolation = statedIsolation(localApp, false);
replaceInlineScripts(localApp.value, localPath, true);
+13 -2
View File
@@ -149,7 +149,11 @@ import {
extractCurrentMapping,
} from "../../../windmill-utils-internal/src/inline-scripts/extractor.ts";
import { generateFlowLockInternal } from "../flow/flow_metadata.ts";
import { markAccessFromPolicy, markIsolationFromPolicy } from "../app/app.ts";
import {
markAccessFromPolicy,
markIsolationFromPolicy,
stripEditorState,
} from "../app/app.ts";
import {
APP_BACKEND_FOLDER,
generateAppLocksInternal,
@@ -1329,6 +1333,7 @@ export function ZipFSElement(
log.error(`Failed to parse app.yaml at path: ${p}`);
throw error;
}
stripEditorState(app);
let inlineScripts;
try {
inlineScripts = extractInlineScriptsForApps(
@@ -2749,12 +2754,18 @@ export async function compareDynFSElement(
}
return o;
} else {
let o: any;
try {
return yamlParseContent(k, v);
o = yamlParseContent(k, v);
} catch (error) {
log.error(`Failed to parse YAML content at path: ${k}`);
throw error;
}
// A file tracked before the field was dropped still carries it.
if (path.basename(k) == "app.yaml") {
stripEditorState(o);
}
return o;
}
}
@@ -284,13 +284,21 @@
saveDrawerOpen = false
}
// `parent_version` is the draft's stale-check base. Deployed, it would put a version
// id that changes on every deploy into git-synced files and version diffs.
function withoutDraftBase<T extends App | undefined>(a: T): T {
if (!a) return a
const { parent_version: _pv, ...rest } = a
return rest as T
}
async function createApp(path: string) {
policy = await updatePolicy($app, policy)
try {
await AppService.createApp({
workspace: $workspaceStore!,
requestBody: {
value: $app,
value: withoutDraftBase($app),
path,
summary: $summary,
policy,
@@ -349,7 +357,7 @@
orderedJsonStringify(
replaceFalseWithUndefined({
summary: $summary,
value: $app,
value: withoutDraftBase($app),
path: newEditedPath || savedApp.path,
policy,
custom_path: customPath,
@@ -392,6 +400,7 @@
// Strip off extra information
deployedValue = replaceFalseWithUndefined({
...deployedApp,
value: withoutDraftBase(deployedApp.value as App),
id: undefined,
created_at: undefined,
created_by: undefined,
@@ -406,7 +415,7 @@
workspace: $workspaceStore!,
path: $appPath!,
requestBody: {
value: $app!,
value: withoutDraftBase($app!),
summary: $summary,
policy,
path: npath,
@@ -753,7 +762,7 @@
deployed: deployedValue ?? savedApp,
current: {
summary: $summary,
value: $app,
value: withoutDraftBase($app),
path: newEditedPath || savedApp.path,
policy,
custom_path: customPath,
@@ -847,7 +856,7 @@
bind:deployedValue
currentValue={{
summary: $summary,
value: $app,
value: withoutDraftBase($app),
path: newEditedPath || savedApp?.path,
policy,
custom_path: customPath,
@@ -898,7 +907,7 @@
deployed: deployedValue ?? savedApp,
current: {
summary: $summary,
value: $app,
value: withoutDraftBase($app),
path: newEditedPath || savedApp.path,
policy,
custom_path: customPath,
+7 -1
View File
@@ -538,8 +538,14 @@ export async function deployDraft(
// no draft. `draft_path` and `summary` are draft-only fields mirrored onto
// the App value (the editor drops them on deploy), so strip them from the
// value and apply them as the deploy path / summary column.
// `parent_version` is the draft's stale-check base, never deployed.
const draft = r.draft as Record<string, any> | undefined
const { draft_path: draftPath, summary: draftSummary, ...appValue } = draft ?? r.value ?? {}
const {
draft_path: draftPath,
summary: draftSummary,
parent_version: _pv,
...appValue
} = draft ?? r.value ?? {}
// Policy isn't carried in the app draft, so it comes from the deployed app
// (or a default). custom_path requires admin on update; non-admins send
// undefined so the backend preserves the existing route. The draft has no