mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-18 16:02:10 +00:00
14c29b77e9
* fix(cli): surface shared UI (ui/) changes in sync push dry-run preview The git-sync "Pull from repo" preview never showed shared UI (ui/) changes, so users thought the shared-UI folder was not syncing. The apply step does sync it (pushSharedUi on dryRun=false); only the dry-run preview was blind. Shared UI maps a single top-level ui/ folder to the workspace_shared_ui store and is handled out-of-band from the normal file diff (isNotWmillFile excludes ui/). The dry-run path returns before pushSharedUi runs, so the `changes` list the modal consumes never contained any ui/ entry and read as "no changes". - Add exported diffSharedUi(workspace) computing added/edited/deleted ui/<rel> entries (push direction), and refactor pushSharedUi to reuse it so preview and apply never diverge. - Fold the diff into `changes` in the dry-run path (both JSON and terminal), guarded by try/catch. Apply path is unchanged. - Label ui/ paths as "shared UI" in prettyChanges (getTypeStrFromPath throws on non-wmill paths like ui/config.json). - Do not run pushSharedUi in the zero-changes branch during a dry-run. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cli): report shared-UI-only push in sync JSON output Address local review: when a real apply has only ui/ changes it reaches the zero-file-changes branch, pushes the shared-UI store, then printed "No changes to push" in --json-output. Surface pushSharedUi's result so the message no longer claims no changes when the store was written. Also correct the pushSharedUi docstring (empty-but-existing folder still clears a non-empty remote store). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(cli): trim shared_ui diff test header to the durable invariant Address Codex nit: replace the narrative regression header with a 4-line statement of the invariant (diffSharedUi mirrors pushSharedUi's apply semantics so preview and apply never diverge). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cli): own-property shared-UI diff and count ui/ in dry-run summary Address Codex review: - diffSharedUi used `rel in remote`/`rel in files`, so a file named after an Object.prototype member (e.g. ui/toString) always registered as present and was misdiffed; pushSharedUi could then skip deleting it. Use Object.hasOwn. - The dry-run "N changes to apply" summary logged before the shared UI fold, so a shared-UI-only dry-run printed "0 changes to apply" then listed the changes. Fold before the summary so the count includes ui/. - Add a unit test for the ui/toString inherited-property filename. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
93 lines
3.3 KiB
TypeScript
93 lines
3.3 KiB
TypeScript
/**
|
|
* diffSharedUi must mirror pushSharedUi's apply semantics so the git-sync
|
|
* dry-run preview and the real apply never diverge: it emits ui/<rel> entries
|
|
* exactly when the local ui/ folder differs from the remote store, and nothing
|
|
* (including no local folder) when the apply would be a no-op.
|
|
*/
|
|
|
|
import { expect, test, describe, beforeEach, afterEach, mock } from "bun:test";
|
|
import * as fs from "node:fs";
|
|
import * as os from "node:os";
|
|
import * as path from "node:path";
|
|
|
|
let remoteFiles: Record<string, string> = {};
|
|
|
|
mock.module("../gen/services.gen.ts", () => ({
|
|
getSharedUi: async (_args: { workspace: string }) => ({ files: remoteFiles }),
|
|
}));
|
|
|
|
const { diffSharedUi } = await import("../src/commands/shared_ui.ts");
|
|
|
|
describe("diffSharedUi", () => {
|
|
const ws = "test-workspace";
|
|
let tmpDir: string;
|
|
let prevCwd: string;
|
|
|
|
beforeEach(() => {
|
|
remoteFiles = {};
|
|
prevCwd = process.cwd();
|
|
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "wm-shared-ui-"));
|
|
process.chdir(tmpDir);
|
|
});
|
|
|
|
afterEach(() => {
|
|
process.chdir(prevCwd);
|
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
});
|
|
|
|
function writeUi(rel: string, content: string) {
|
|
const full = path.join(tmpDir, "ui", rel);
|
|
fs.mkdirSync(path.dirname(full), { recursive: true });
|
|
fs.writeFileSync(full, content, "utf-8");
|
|
}
|
|
|
|
test("emits an added ui/<rel> entry when local has a file the remote lacks", async () => {
|
|
writeUi("theme.json", "{}");
|
|
const changes = await diffSharedUi(ws);
|
|
expect(changes).toEqual([{ type: "added", path: "ui/theme.json" }]);
|
|
});
|
|
|
|
test("emits an edited ui/<rel> entry when local content differs", async () => {
|
|
remoteFiles = { "theme.json": "{}" };
|
|
writeUi("theme.json", '{"a":1}');
|
|
const changes = await diffSharedUi(ws);
|
|
expect(changes).toEqual([
|
|
{ type: "edited", path: "ui/theme.json", before: "{}", after: '{"a":1}' },
|
|
]);
|
|
});
|
|
|
|
test("emits a deleted ui/<rel> entry when remote has a file local lacks", async () => {
|
|
remoteFiles = { "theme.json": "{}", "extra.json": "1" };
|
|
writeUi("theme.json", "{}");
|
|
const changes = await diffSharedUi(ws);
|
|
expect(changes).toEqual([{ type: "deleted", path: "ui/extra.json" }]);
|
|
});
|
|
|
|
test("emits nothing when local matches the remote store", async () => {
|
|
remoteFiles = { "theme.json": "{}" };
|
|
writeUi("theme.json", "{}");
|
|
const changes = await diffSharedUi(ws);
|
|
expect(changes).toEqual([]);
|
|
});
|
|
|
|
test("diffs files named after Object.prototype members (e.g. toString)", async () => {
|
|
// Own-property check, not `in`: a local-only ui/toString is an add, and a
|
|
// remote-only ui/toString is a delete, despite Object.prototype.toString.
|
|
writeUi("toString", "x");
|
|
let changes = await diffSharedUi(ws);
|
|
expect(changes).toEqual([{ type: "added", path: "ui/toString" }]);
|
|
|
|
fs.rmSync(path.join(tmpDir, "ui", "toString"));
|
|
fs.mkdirSync(path.join(tmpDir, "ui"), { recursive: true });
|
|
remoteFiles = { toString: "x" };
|
|
changes = await diffSharedUi(ws);
|
|
expect(changes).toEqual([{ type: "deleted", path: "ui/toString" }]);
|
|
});
|
|
|
|
test("emits nothing when there is no local ui/ folder (apply is a no-op)", async () => {
|
|
remoteFiles = { "theme.json": "{}" };
|
|
const changes = await diffSharedUi(ws);
|
|
expect(changes).toEqual([]);
|
|
});
|
|
});
|