mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-18 16:02:10 +00:00
5bdc4f83ce
* feat(cli): improve agent prompts/skills and workspace fork workflow
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): refuse fork --from-branch rename of a base branch
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(cli): auto-detect fork branch workflow, drop rt.d.ts refresh and legacy-name warning
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(skills): reconcile raw-app generate-metadata stance (agent offers+runs)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(skills): agent runs all CLI commands, gated on intent not on user typing them
Extends #9467's safe-vs-destructive model: the agent runs consequential commands (sync push, generate-metadata) itself too, gated on explicit user intent rather than handed to the user to type. The explicit-intent rule is the safeguard; an approval prompt is treated as a possible backstop, not assumed (auto-approve/headless runs have none).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Revert "docs(skills): agent runs all CLI commands, gated on intent not on user typing them"
Reverts 9225e1759b. That commit over-reached: #9467 already established the safe-vs-destructive split, and the targeted item-6 fix already removed the passive "tell the user they can run <safe next step>" phrasing. The blanket "agent runs everything" principle pushed deploys to be more eager and carried a wrong "permission layer prompts for approval" claim (untrue in auto-approve/headless mode). Keep deploys conservative.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(cli): default fork workspace name/id to the current branch when renaming it
When 'wmill workspace fork' converts the current working branch into the fork branch, default the fork's name and id to that branch (sanitized to a slug, since branch names can contain '/'). Interactive: the prompt is pre-filled (enter to accept); non-interactive (--yes): used automatically. Adds a unit test for the slug derivation.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): address fork review — guard fork-branch rename, cap+validate fork id
Two P2s from review:
- --from-branch refused when the current branch is already a fork branch (would detach the existing fork by renaming its branch).
- fork id slug capped to 42 chars (backend max 50 incl. wm-fork- prefix); auto-derived id is slugged; full id validated client-side before existsWorkspace/datatable cloning so an invalid id fails fast instead of leaving cloned Postgres databases behind.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
76 lines
2.7 KiB
TypeScript
76 lines
2.7 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import {
|
|
branchToForkId,
|
|
validateForkWorkspaceId,
|
|
} from "../src/commands/workspace/fork.ts";
|
|
|
|
describe("branchToForkId — branch name → fork workspace id slug", () => {
|
|
test.each<[string, string]>([
|
|
["feature-x", "feature-x"],
|
|
["my_feature", "my_feature"],
|
|
// `/` (common in branch names) must not survive — it would break the
|
|
// wm-fork/<base>/<id> branch-name parsing.
|
|
["feat/foo", "feat-foo"],
|
|
["feature/JIRA-123", "feature-JIRA-123"],
|
|
["a/b/c", "a-b-c"],
|
|
// Other invalid characters collapse to a single dash.
|
|
["hot fix!", "hot-fix"],
|
|
["weird@@name", "weird-name"],
|
|
// Leading/trailing separators are trimmed.
|
|
["/leading", "leading"],
|
|
["trailing/", "trailing"],
|
|
["--dashes--", "dashes"],
|
|
// Degenerate input falls back to a usable id.
|
|
["///", "fork"],
|
|
["", "fork"],
|
|
])("%p → %p", (branch, expected) => {
|
|
expect(branchToForkId(branch)).toBe(expected);
|
|
});
|
|
|
|
test("result never contains a slash (would break fork branch parsing)", () => {
|
|
for (const branch of ["a/b", "x/y/z", "feat/foo/bar"]) {
|
|
expect(branchToForkId(branch)).not.toContain("/");
|
|
}
|
|
});
|
|
|
|
test("caps the slug so wm-fork-<slug> stays within the backend's 50-char limit", () => {
|
|
const long = "feature/TICKET-1234-" + "a".repeat(80);
|
|
const slug = branchToForkId(long);
|
|
expect(slug.length).toBeLessThanOrEqual(42);
|
|
// The full id the backend validates is `wm-fork-<slug>`.
|
|
expect(`wm-fork-${slug}`.length).toBeLessThanOrEqual(50);
|
|
// Truncation must not leave a trailing dash.
|
|
expect(slug.endsWith("-")).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("validateForkWorkspaceId — mirrors backend validate_fork_workspace_id", () => {
|
|
test("accepts a normal slugged id", () => {
|
|
expect(() => validateForkWorkspaceId("wm-fork-feature-x")).not.toThrow();
|
|
});
|
|
|
|
test.each<[string, string]>([
|
|
["too long (> 50 chars)", "wm-fork-" + "a".repeat(60)],
|
|
["ends with '.'", "wm-fork-foo."],
|
|
["ends with '.lock'", "wm-fork-foo.lock"],
|
|
["contains '..'", "wm-fork-foo..bar"],
|
|
["contains '//'", "wm-fork-foo//bar"],
|
|
["contains a space", "wm-fork-foo bar"],
|
|
["contains a forbidden char", "wm-fork-foo~bar"],
|
|
])("rejects: %s", (_label, id) => {
|
|
expect(() => validateForkWorkspaceId(id)).toThrow();
|
|
});
|
|
|
|
test("a branchToForkId slug always passes validation (with the prefix)", () => {
|
|
for (const branch of [
|
|
"feat/foo",
|
|
"feature/TICKET-1234-" + "a".repeat(80),
|
|
"weird@@name",
|
|
"///",
|
|
]) {
|
|
const id = `wm-fork-${branchToForkId(branch)}`;
|
|
expect(() => validateForkWorkspaceId(id)).not.toThrow();
|
|
}
|
|
});
|
|
});
|