mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-09 16:05:42 +00:00
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>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
fa3cb4c536
commit
9b255cd2e5
@@ -1,6 +1,3 @@
|
||||
import { existsSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import process from "node:process";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import { Command } from "@cliffy/command";
|
||||
import { Select } from "@cliffy/prompt/select";
|
||||
@@ -186,37 +183,6 @@ interface CommandOptions {
|
||||
|
||||
async function promptsAction(opts: CommandOptions): Promise<void> {
|
||||
await refreshPrompts({ yes: opts.yes === true });
|
||||
await refreshRtNamespaceIfPresent(opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep an existing `rt.d.ts` resource-type namespace in sync when the user
|
||||
* runs `wmill refresh prompts`. `wmill init` already generates it on first
|
||||
* bind; here we only refresh it when the file is already present (so we never
|
||||
* introduce it into projects that don't use it). Best-effort: a missing
|
||||
* workspace/login or offline run just skips with a warning rather than failing
|
||||
* the whole refresh.
|
||||
*
|
||||
* Not wired into the shared `refreshPrompts` helper on purpose — `wmill init`
|
||||
* regenerates the namespace itself, and `refreshPrompts` must stay
|
||||
* network-free for its other callers.
|
||||
*/
|
||||
async function refreshRtNamespaceIfPresent(opts: CommandOptions): Promise<void> {
|
||||
const rtPath = join(process.cwd(), "rt.d.ts");
|
||||
if (!existsSync(rtPath)) return;
|
||||
|
||||
try {
|
||||
const { generateRTNamespace } = await import(
|
||||
"../resource-type/resource-type.ts"
|
||||
);
|
||||
await generateRTNamespace(opts as any);
|
||||
} catch (error) {
|
||||
log.warn(
|
||||
`Could not refresh rt.d.ts resource type namespace: ${
|
||||
error instanceof Error ? error.message : error
|
||||
}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const command = new Command()
|
||||
|
||||
@@ -12,9 +12,15 @@ import {
|
||||
isGitRepository,
|
||||
renameCurrentGitBranch,
|
||||
} from "../../utils/git.ts";
|
||||
import process from "node:process";
|
||||
import { WM_FORK_PREFIX } from "../../core/constants.ts";
|
||||
import { tryResolveBranchWorkspace } from "../../core/context.ts";
|
||||
import { findWorkspaceByGitBranch, readConfigFile } from "../../core/conf.ts";
|
||||
import {
|
||||
findWorkspaceByGitBranch,
|
||||
getEffectiveGitBranch,
|
||||
getWorkspaceNames,
|
||||
readConfigFile,
|
||||
} from "../../core/conf.ts";
|
||||
|
||||
async function createWorkspaceFork(
|
||||
opts: GlobalOptions & {
|
||||
@@ -36,48 +42,70 @@ async function createWorkspaceFork(
|
||||
throw new Error("Could not get git branch name");
|
||||
}
|
||||
|
||||
// `--from-branch` enables the "already on a working branch" workflow: the
|
||||
// fork is based on <fromBranch> (the parent), and the current branch is
|
||||
// later renamed onto the fork branch. Without it, the fork is based on the
|
||||
// current branch and the user checks out a fresh fork branch.
|
||||
const fromBranch = opts.fromBranch;
|
||||
const config = await readConfigFile({ warnIfMissing: false });
|
||||
const originalBranchIfForked = getOriginalBranchForWorkspaceForks(currentBranch);
|
||||
|
||||
let workspace;
|
||||
if (fromBranch) {
|
||||
if (fromBranch === currentBranch) {
|
||||
// A "base branch" is one we must not rename onto a fork branch: mapped to a
|
||||
// workspace in wmill.yaml, or a conventional default (main/master).
|
||||
const isBaseBranch = (branch: string): boolean =>
|
||||
branch === "main" ||
|
||||
branch === "master" ||
|
||||
findWorkspaceByGitBranch(config.workspaces, branch) !== undefined;
|
||||
|
||||
// Decide the base branch the fork links to, and whether to rename the
|
||||
// current working branch onto the fork branch. Auto-detected from where you
|
||||
// are; `--from-branch` is the explicit/non-interactive override.
|
||||
let clonedBranchName: string;
|
||||
let renameCurrent: boolean;
|
||||
|
||||
if (opts.fromBranch) {
|
||||
// Explicit override: base on <fromBranch>, rename the current branch.
|
||||
if (opts.fromBranch === currentBranch) {
|
||||
throw new Error(
|
||||
`--from-branch is for converting a *different* working branch into the fork branch, but you are already on \`${currentBranch}\`. ` +
|
||||
`Either omit --from-branch (a fresh fork branch is created with \`git checkout -b\`), or check out the working branch you want to convert first.`,
|
||||
`Omit --from-branch to create a fresh fork branch with \`git checkout -b\`.`,
|
||||
);
|
||||
}
|
||||
const config = await readConfigFile({ warnIfMissing: false });
|
||||
// Protect base branches. This workflow renames the *current* branch onto
|
||||
// the fork branch, so refuse when the current branch is itself a base
|
||||
// branch — either mapped to a workspace in wmill.yaml, or a conventional
|
||||
// default (main/master). Renaming one of those would clobber it; the user
|
||||
// must check out a disposable working branch first. Fail fast, before any
|
||||
// fork workspace or git branch is created.
|
||||
const currentBranchIsBase =
|
||||
currentBranch === "main" ||
|
||||
currentBranch === "master" ||
|
||||
findWorkspaceByGitBranch(config.workspaces, currentBranch) !== undefined;
|
||||
if (currentBranchIsBase) {
|
||||
if (isBaseBranch(currentBranch)) {
|
||||
throw new Error(
|
||||
`Refusing to rename your current branch \`${currentBranch}\` — it looks like a base branch (mapped to a workspace in wmill.yaml, or main/master). ` +
|
||||
`The --from-branch workflow turns a *disposable working branch* into the fork branch. ` +
|
||||
`Check out the working branch you want to convert first, or omit --from-branch to create a fresh fork branch with \`git checkout -b\`.`,
|
||||
`Check out the disposable working branch you want to convert first.`,
|
||||
);
|
||||
}
|
||||
const match = findWorkspaceByGitBranch(config.workspaces, fromBranch);
|
||||
if (!match) {
|
||||
if (!findWorkspaceByGitBranch(config.workspaces, opts.fromBranch)) {
|
||||
throw new Error(
|
||||
`Could not find a workspace mapped to branch \`${fromBranch}\` in wmill.yaml's workspaces section. ` +
|
||||
`Could not find a workspace mapped to branch \`${opts.fromBranch}\` in wmill.yaml's workspaces section. ` +
|
||||
`Pass the base branch your fork should be based on (e.g. the branch bound to the parent workspace).`,
|
||||
);
|
||||
}
|
||||
workspace = await tryResolveBranchWorkspace(opts, match[0]);
|
||||
clonedBranchName = opts.fromBranch;
|
||||
renameCurrent = true;
|
||||
} else if (originalBranchIfForked) {
|
||||
// Fork of a fork: link to the original branch; user checks out a new branch.
|
||||
log.info(`You are creating a fork of a fork. The branch will be linked to the original branch this was forked from, i.e. \`${originalBranchIfForked}\`, for all settings and overrides.`);
|
||||
clonedBranchName = originalBranchIfForked;
|
||||
renameCurrent = false;
|
||||
} else if (isBaseBranch(currentBranch)) {
|
||||
// On a base branch: base the fork on it; user checks out a fresh fork branch.
|
||||
clonedBranchName = currentBranch;
|
||||
renameCurrent = false;
|
||||
} else {
|
||||
// On a non-base working branch: offer to base the fork on it and rename it.
|
||||
clonedBranchName = await resolveWorkingBranchBase(config, opts, currentBranch);
|
||||
renameCurrent = true;
|
||||
}
|
||||
|
||||
// Resolve the parent workspace. When the base differs from the current
|
||||
// branch (the rename workflows), resolve via the base branch's workspace;
|
||||
// otherwise use plain branch resolution.
|
||||
let workspace;
|
||||
if (clonedBranchName === currentBranch) {
|
||||
workspace = await tryResolveBranchWorkspace(opts);
|
||||
} else {
|
||||
const baseMatch = findWorkspaceByGitBranch(config.workspaces, clonedBranchName);
|
||||
workspace = baseMatch
|
||||
? await tryResolveBranchWorkspace(opts, baseMatch[0])
|
||||
: await tryResolveBranchWorkspace(opts);
|
||||
}
|
||||
|
||||
if (!workspace) {
|
||||
@@ -86,22 +114,6 @@ async function createWorkspaceFork(
|
||||
|
||||
log.info(`You are forking workspace (${workspace.workspaceId})`)
|
||||
|
||||
const originalBranchIfForked = getOriginalBranchForWorkspaceForks(currentBranch);
|
||||
|
||||
let clonedBranchName: string | null;
|
||||
if (fromBranch) {
|
||||
clonedBranchName = fromBranch;
|
||||
} else if (originalBranchIfForked) {
|
||||
log.info(`You are creating a fork of a fork. The branch will be linked to the original branch this was forked from, i.e. \`${originalBranchIfForked}\`, for all settings and overrides.`);
|
||||
clonedBranchName = originalBranchIfForked;
|
||||
} else {
|
||||
clonedBranchName = currentBranch;
|
||||
}
|
||||
|
||||
if (!clonedBranchName) {
|
||||
throw new Error("Failed to get current branch name, aborting operation");
|
||||
}
|
||||
|
||||
if (opts.workspace) {
|
||||
log.info(
|
||||
colors.red.bold(
|
||||
@@ -301,11 +313,13 @@ async function createWorkspaceFork(
|
||||
|
||||
const newBranchName = `${WM_FORK_PREFIX}/${clonedBranchName}/${workspaceId}`
|
||||
|
||||
// Workflow B (`--from-branch`): turn the current working branch into the
|
||||
// fork branch in place so its commits become the fork's. Workflow A: leave
|
||||
// the user on their base branch and have them check out a fresh fork branch.
|
||||
// Rename workflow: turn the current working branch into the fork branch in
|
||||
// place so its commits become the fork's. (Consent was already established —
|
||||
// by `--from-branch`, or the interactive prompt for a non-base branch.)
|
||||
// Otherwise: leave the user on their branch and have them check out a fresh
|
||||
// fork branch.
|
||||
let onForkBranch = false;
|
||||
if (fromBranch) {
|
||||
if (renameCurrent) {
|
||||
if (currentBranch === newBranchName) {
|
||||
onForkBranch = true;
|
||||
log.info(colors.green(`Your current branch is already \`${newBranchName}\`.`));
|
||||
@@ -315,27 +329,13 @@ async function createWorkspaceFork(
|
||||
`Check out the fork branch yourself (e.g. \`git checkout ${newBranchName}\`).`,
|
||||
);
|
||||
} else {
|
||||
let doRename = opts.yes === true;
|
||||
if (!doRename) {
|
||||
const { Select } = await import("@cliffy/prompt/select");
|
||||
const choice = await Select.prompt({
|
||||
message: `Rename your current branch \`${currentBranch}\` → \`${newBranchName}\` so its commits become the fork's branch?`,
|
||||
options: [
|
||||
{ name: "Yes, rename it", value: "confirm" },
|
||||
{ name: "No, I'll switch branches myself", value: "cancel" },
|
||||
],
|
||||
});
|
||||
doRename = choice === "confirm";
|
||||
}
|
||||
if (doRename) {
|
||||
renameCurrentGitBranch(newBranchName);
|
||||
onForkBranch = true;
|
||||
log.info(
|
||||
colors.green(
|
||||
`Renamed \`${currentBranch}\` → \`${newBranchName}\`. Your existing commits are now on the fork branch.`,
|
||||
),
|
||||
);
|
||||
}
|
||||
renameCurrentGitBranch(newBranchName);
|
||||
onForkBranch = true;
|
||||
log.info(
|
||||
colors.green(
|
||||
`Renamed \`${currentBranch}\` → \`${newBranchName}\`. Your existing commits are now on the fork branch.`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,6 +356,71 @@ To merge changes back to the parent workspace, you can:
|
||||
See: https://www.windmill.dev/docs/advanced/workspace_forks`);
|
||||
}
|
||||
|
||||
/**
|
||||
* When `wmill workspace fork` is run from a non-base working branch, confirm
|
||||
* the user wants to turn it into a fork branch, and resolve which base branch
|
||||
* the fork should be linked to. Throws in non-interactive mode (where the user
|
||||
* must pass `--from-branch <base>` instead).
|
||||
*/
|
||||
async function resolveWorkingBranchBase(
|
||||
config: Awaited<ReturnType<typeof readConfigFile>>,
|
||||
opts: { yes?: boolean },
|
||||
currentBranch: string,
|
||||
): Promise<string> {
|
||||
const interactive = process.stdin.isTTY && opts.yes !== true;
|
||||
if (!interactive) {
|
||||
throw new Error(
|
||||
`You are on working branch \`${currentBranch}\`, which is not a base branch. ` +
|
||||
`Pass --from-branch <base> to base the fork on a base branch and rename this branch onto the fork branch, ` +
|
||||
`or check out a base branch and run \`wmill workspace fork\` to create a fresh fork branch.`,
|
||||
);
|
||||
}
|
||||
|
||||
const { Select } = await import("@cliffy/prompt/select");
|
||||
const proceed = await Select.prompt({
|
||||
message: `You're on working branch \`${currentBranch}\`, not a base branch. Base a fork on it and rename it onto the fork branch?`,
|
||||
options: [
|
||||
{ name: "Yes, base the fork on this branch and rename it", value: "yes" },
|
||||
{ name: "No, cancel", value: "no" },
|
||||
],
|
||||
});
|
||||
if (proceed !== "yes") {
|
||||
throw new Error("Fork cancelled. Check out a base branch to create a fresh fork branch instead.");
|
||||
}
|
||||
|
||||
const baseBranches = listConfiguredBaseBranches(config);
|
||||
if (baseBranches.length === 0) {
|
||||
throw new Error(
|
||||
`No base branches are configured in wmill.yaml's workspaces section, so the fork can't be linked to a parent. ` +
|
||||
`Add the parent workspace to wmill.yaml, or pass --from-branch <base>.`,
|
||||
);
|
||||
}
|
||||
if (baseBranches.length === 1) {
|
||||
log.info(`Basing the fork on \`${baseBranches[0]}\`.`);
|
||||
return baseBranches[0];
|
||||
}
|
||||
return await Select.prompt({
|
||||
message: "Which base branch is this fork based on (the parent)?",
|
||||
options: baseBranches.map((b) => ({ name: b, value: b })),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Base branches configured in wmill.yaml, mirroring how `findWorkspaceByGitBranch`
|
||||
* keys them (effective git branch = `gitBranch ?? workspaceName`, reserved keys
|
||||
* excluded) so the chosen base resolves to a workspace afterwards.
|
||||
*/
|
||||
function listConfiguredBaseBranches(
|
||||
config: Awaited<ReturnType<typeof readConfigFile>>,
|
||||
): string[] {
|
||||
const workspaces = config.workspaces;
|
||||
const branches = new Set<string>();
|
||||
for (const name of getWorkspaceNames(workspaces)) {
|
||||
branches.add(getEffectiveGitBranch(name, workspaces![name]));
|
||||
}
|
||||
return [...branches];
|
||||
}
|
||||
|
||||
async function deleteWorkspaceFork(
|
||||
opts: GlobalOptions & {
|
||||
yes?: boolean;
|
||||
|
||||
@@ -814,9 +814,9 @@ const command = new Command()
|
||||
)
|
||||
.option(
|
||||
"--from-branch <branch:string>",
|
||||
"Fork based on this base branch (its bound workspace is the parent) and rename your current working branch onto the fork branch wm-fork/<branch>/<id>. Use when you're already on a branch (e.g. with forked-datatable edits) you want to turn into the fork. Omit to base the fork on the current branch and check out a fresh fork branch instead."
|
||||
"Non-interactive override for the 'turn my current working branch into the fork' workflow: base the fork on <branch> (its bound workspace is the parent) and rename the current branch onto wm-fork/<branch>/<id>. Usually unneeded — from a working branch `wmill workspace fork` offers this interactively; from a base branch it creates a fresh fork branch."
|
||||
)
|
||||
.option("-y --yes", "Skip interactive prompts (defaults datatable behavior to 'skip', and confirms the --from-branch rename)")
|
||||
.option("-y --yes", "Skip interactive prompts (defaults datatable behavior to 'skip'). On a non-base branch, requires --from-branch since the base branch can't be prompted for.")
|
||||
.action(createWorkspaceFork as any)
|
||||
.command("delete-fork")
|
||||
.description("Delete a forked workspace and git branch")
|
||||
|
||||
@@ -156,10 +156,12 @@ Only deploy when the user explicitly asks to deploy, publish, push, or ship —
|
||||
|
||||
A **fork** is an isolated copy of a workspace for parallel or experimental work — make changes (including to datatables, which are cloned per fork) without touching the parent, then merge back after review. Each fork is paired with a git branch named \`wm-fork/<base>/<id>\`. Forks require a git repo.
|
||||
|
||||
Create one with \`wmill workspace fork\`. There are two branch workflows — pick by where you are:
|
||||
Just run \`wmill workspace fork\` — it adapts to where you are:
|
||||
|
||||
- **Starting from the base branch** (no in-progress work to carry over): run \`wmill workspace fork\`. It bases the fork on your current branch and prints a \`git checkout -b wm-fork/<base>/<id>\` to start the fork branch.
|
||||
- **Already on a working branch you want to turn into the fork** (e.g. you've branched and already edited a forked datatable): run \`wmill workspace fork --from-branch <base>\`. It bases the fork on \`<base>\` (the parent's branch) and renames your current branch onto \`wm-fork/<base>/<id>\` in place, preserving its commits.
|
||||
- **On a base branch** (e.g. \`main\`, or a branch bound to a workspace): it bases the fork on that branch and prints a \`git checkout -b wm-fork/<base>/<id>\` to start a fresh fork branch.
|
||||
- **On a working branch** (e.g. you've branched and already edited a forked datatable): it offers to base the fork on that branch and rename it onto \`wm-fork/<base>/<id>\` in place, preserving its commits — asking which base branch is the parent if there's more than one.
|
||||
|
||||
For non-interactive runs from a working branch, pass \`--from-branch <base>\` to skip the prompts. The CLI refuses to rename a base branch.
|
||||
|
||||
Merge a fork back into its parent with \`wmill workspace merge\` (or the Merge UI on the fork's home page). Full reference: https://www.windmill.dev/docs/advanced/workspace_forks
|
||||
|
||||
|
||||
@@ -105,14 +105,16 @@ export function currentPromptsHash(nonDottedPaths: boolean): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* Read AGENTS.wmill.md in the current working directory, compare its embedded
|
||||
* hash to the current CLI's hash, and print a one-line warning if they
|
||||
* Read the managed guidance file in the current working directory, compare its
|
||||
* embedded hash to the current CLI's hash, and print a one-line warning if they
|
||||
* differ. Silent on every other code path (no managed file, no marker,
|
||||
* matching hash, IO error, …) so it never gets in the user's way.
|
||||
*
|
||||
* Back-compat: if only the legacy `AGENTS.cli.md` is present (no
|
||||
* `AGENTS.wmill.md` yet), warn that the managed file should be migrated —
|
||||
* `wmill refresh prompts` renames it and rewrites the include.
|
||||
* Back-compat: prefers `AGENTS.wmill.md` but falls back to the legacy
|
||||
* `AGENTS.cli.md` and runs the exact same staleness check on it. We do NOT
|
||||
* warn merely because the old filename is in use — an up-to-date `AGENTS.cli.md`
|
||||
* stays quiet; only a stale hash (which `wmill refresh prompts` fixes, and
|
||||
* which also migrates the filename) trips the warning.
|
||||
*/
|
||||
export async function warnIfPromptsStale(opts?: {
|
||||
cwd?: string;
|
||||
@@ -122,18 +124,17 @@ export async function warnIfPromptsStale(opts?: {
|
||||
if (opts?.argv && !shouldRunFreshnessCheck(opts.argv)) return;
|
||||
|
||||
const cwd = opts?.cwd ?? process.cwd();
|
||||
const path = `${cwd}/${AGENTS_WMILL_FILENAME}`;
|
||||
|
||||
// Prefer the current filename; fall back to the legacy one for back-compat.
|
||||
let fileName = AGENTS_WMILL_FILENAME;
|
||||
let path = `${cwd}/${fileName}`;
|
||||
if (!(await stat(path).catch(() => null))) {
|
||||
// No AGENTS.wmill.md. If the legacy AGENTS.cli.md is still around, nudge
|
||||
// the user to migrate it; otherwise this project just isn't wmill-managed.
|
||||
const legacyPath = `${cwd}/${LEGACY_AGENTS_CLI_FILENAME}`;
|
||||
if (await stat(legacyPath).catch(() => null)) {
|
||||
emitWarning(
|
||||
"Your AGENTS.cli.md is using the old managed filename. Run `wmill refresh prompts` to migrate it to AGENTS.wmill.md."
|
||||
);
|
||||
fileName = LEGACY_AGENTS_CLI_FILENAME;
|
||||
path = `${cwd}/${fileName}`;
|
||||
if (!(await stat(path).catch(() => null))) {
|
||||
// Neither file present — this project just isn't wmill-managed.
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let content: string;
|
||||
@@ -145,10 +146,10 @@ export async function warnIfPromptsStale(opts?: {
|
||||
|
||||
const stored = extractPromptsHash(content);
|
||||
if (!stored) {
|
||||
// Older AGENTS.wmill.md without a marker. Warn so the user re-runs
|
||||
// refresh and picks up the new format.
|
||||
// Managed file without a marker. Warn so the user re-runs refresh and
|
||||
// picks up the new format (and the new filename).
|
||||
emitWarning(
|
||||
"Your AGENTS.wmill.md predates prompt versioning. Run `wmill refresh prompts` to refresh and add a version marker."
|
||||
`Your ${fileName} predates prompt versioning. Run \`wmill refresh prompts\` to refresh and add a version marker.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -170,7 +171,7 @@ export async function warnIfPromptsStale(opts?: {
|
||||
const current = currentPromptsHash(nonDottedPaths);
|
||||
if (stored !== current) {
|
||||
emitWarning(
|
||||
"Your AGENTS.wmill.md is out of date. Run `wmill refresh prompts` to refresh."
|
||||
`Your ${fileName} is out of date. Run \`wmill refresh prompts\` to refresh.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6762,8 +6762,8 @@ workspace related commands
|
||||
- \`--create-workspace-name <workspace_name:string>\` - Specify the workspace name. Ignored if --create is not specified or the workspace already exists. Will default to the workspace id.
|
||||
- \`--color <color:string>\` - Workspace color (hex code, e.g. #ff0000)
|
||||
- \`--datatable-behavior <behavior:string>\` - How to handle datatables: skip, schema_only, or schema_and_data (default: interactive prompt)
|
||||
- \`--from-branch <branch:string>\` - Fork based on this base branch (its bound workspace is the parent) and rename your current working branch onto the fork branch wm-fork/<branch>/<id>. Use when you're already on a branch (e.g. with forked-datatable edits) you want to turn into the fork. Omit to base the fork on the current branch and check out a fresh fork branch instead.
|
||||
- \`-y --yes\` - Skip interactive prompts (defaults datatable behavior to 'skip', and confirms the --from-branch rename)
|
||||
- \`--from-branch <branch:string>\` - Non-interactive override for the 'turn my current working branch into the fork' workflow: base the fork on <branch> (its bound workspace is the parent) and rename the current branch onto wm-fork/<branch>/<id>. Usually unneeded — from a working branch \`wmill workspace fork\` offers this interactively; from a base branch it creates a fresh fork branch.
|
||||
- \`-y --yes\` - Skip interactive prompts (defaults datatable behavior to 'skip'). On a non-base branch, requires --from-branch since the base branch can't be prompted for.
|
||||
- \`workspace delete-fork <fork_name:string>\` - Delete a forked workspace and git branch
|
||||
- \`-y --yes\` - Skip confirmation prompt
|
||||
- \`workspace merge\` - Compare and deploy changes between a fork and its parent workspace
|
||||
|
||||
@@ -614,4 +614,64 @@ describe("prompts freshness — additional invariants", () => {
|
||||
expect(stdoutJoined).not.toContain("out of date");
|
||||
});
|
||||
});
|
||||
|
||||
// Back-compat: a legacy AGENTS.cli.md is still hash-checked, and we do NOT
|
||||
// warn merely because of the old filename — only when it's actually stale.
|
||||
test("warnIfPromptsStale stays silent for an up-to-date legacy AGENTS.cli.md", async () => {
|
||||
await withTempDir(async (tempDir) => {
|
||||
const content = injectPromptsHashMarker(
|
||||
"# Windmill CLI Agent Instructions\nbody\n",
|
||||
currentPromptsHash(false)
|
||||
);
|
||||
await writeFile(join(tempDir, "AGENTS.cli.md"), content, "utf8");
|
||||
|
||||
const stderrWrites: string[] = [];
|
||||
const originalStderr = process.stderr.write.bind(process.stderr);
|
||||
// @ts-expect-error — overriding write for the test
|
||||
process.stderr.write = (chunk: any) => {
|
||||
stderrWrites.push(String(chunk));
|
||||
return true;
|
||||
};
|
||||
try {
|
||||
await warnIfPromptsStale({
|
||||
cwd: tempDir,
|
||||
nonDottedPaths: false,
|
||||
argv: ["node", "wmill", "sync", "push"],
|
||||
});
|
||||
} finally {
|
||||
process.stderr.write = originalStderr;
|
||||
}
|
||||
expect(stderrWrites.join("")).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
test("warnIfPromptsStale warns (naming the legacy file) for a stale AGENTS.cli.md", async () => {
|
||||
await withTempDir(async (tempDir) => {
|
||||
await writeFile(
|
||||
join(tempDir, "AGENTS.cli.md"),
|
||||
"# Windmill CLI Agent Instructions\n<!-- wmill-prompts-hash: 000000000000 -->\nbody\n",
|
||||
"utf8"
|
||||
);
|
||||
|
||||
const stderrWrites: string[] = [];
|
||||
const originalStderr = process.stderr.write.bind(process.stderr);
|
||||
// @ts-expect-error — overriding write for the test
|
||||
process.stderr.write = (chunk: any) => {
|
||||
stderrWrites.push(String(chunk));
|
||||
return true;
|
||||
};
|
||||
try {
|
||||
await warnIfPromptsStale({
|
||||
cwd: tempDir,
|
||||
nonDottedPaths: false,
|
||||
argv: ["node", "wmill", "sync", "push"],
|
||||
});
|
||||
} finally {
|
||||
process.stderr.write = originalStderr;
|
||||
}
|
||||
const out = stderrWrites.join("");
|
||||
expect(out).toContain("out of date");
|
||||
expect(out).toContain("AGENTS.cli.md");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -726,8 +726,8 @@ workspace related commands
|
||||
- `--create-workspace-name <workspace_name:string>` - Specify the workspace name. Ignored if --create is not specified or the workspace already exists. Will default to the workspace id.
|
||||
- `--color <color:string>` - Workspace color (hex code, e.g. #ff0000)
|
||||
- `--datatable-behavior <behavior:string>` - How to handle datatables: skip, schema_only, or schema_and_data (default: interactive prompt)
|
||||
- `--from-branch <branch:string>` - Fork based on this base branch (its bound workspace is the parent) and rename your current working branch onto the fork branch wm-fork/<branch>/<id>. Use when you're already on a branch (e.g. with forked-datatable edits) you want to turn into the fork. Omit to base the fork on the current branch and check out a fresh fork branch instead.
|
||||
- `-y --yes` - Skip interactive prompts (defaults datatable behavior to 'skip', and confirms the --from-branch rename)
|
||||
- `--from-branch <branch:string>` - Non-interactive override for the 'turn my current working branch into the fork' workflow: base the fork on <branch> (its bound workspace is the parent) and rename the current branch onto wm-fork/<branch>/<id>. Usually unneeded — from a working branch `wmill workspace fork` offers this interactively; from a base branch it creates a fresh fork branch.
|
||||
- `-y --yes` - Skip interactive prompts (defaults datatable behavior to 'skip'). On a non-base branch, requires --from-branch since the base branch can't be prompted for.
|
||||
- `workspace delete-fork <fork_name:string>` - Delete a forked workspace and git branch
|
||||
- `-y --yes` - Skip confirmation prompt
|
||||
- `workspace merge` - Compare and deploy changes between a fork and its parent workspace
|
||||
|
||||
@@ -3277,8 +3277,8 @@ workspace related commands
|
||||
- \`--create-workspace-name <workspace_name:string>\` - Specify the workspace name. Ignored if --create is not specified or the workspace already exists. Will default to the workspace id.
|
||||
- \`--color <color:string>\` - Workspace color (hex code, e.g. #ff0000)
|
||||
- \`--datatable-behavior <behavior:string>\` - How to handle datatables: skip, schema_only, or schema_and_data (default: interactive prompt)
|
||||
- \`--from-branch <branch:string>\` - Fork based on this base branch (its bound workspace is the parent) and rename your current working branch onto the fork branch wm-fork/<branch>/<id>. Use when you're already on a branch (e.g. with forked-datatable edits) you want to turn into the fork. Omit to base the fork on the current branch and check out a fresh fork branch instead.
|
||||
- \`-y --yes\` - Skip interactive prompts (defaults datatable behavior to 'skip', and confirms the --from-branch rename)
|
||||
- \`--from-branch <branch:string>\` - Non-interactive override for the 'turn my current working branch into the fork' workflow: base the fork on <branch> (its bound workspace is the parent) and rename the current branch onto wm-fork/<branch>/<id>. Usually unneeded — from a working branch \`wmill workspace fork\` offers this interactively; from a base branch it creates a fresh fork branch.
|
||||
- \`-y --yes\` - Skip interactive prompts (defaults datatable behavior to 'skip'). On a non-base branch, requires --from-branch since the base branch can't be prompted for.
|
||||
- \`workspace delete-fork <fork_name:string>\` - Delete a forked workspace and git branch
|
||||
- \`-y --yes\` - Skip confirmation prompt
|
||||
- \`workspace merge\` - Compare and deploy changes between a fork and its parent workspace
|
||||
|
||||
@@ -731,8 +731,8 @@ workspace related commands
|
||||
- `--create-workspace-name <workspace_name:string>` - Specify the workspace name. Ignored if --create is not specified or the workspace already exists. Will default to the workspace id.
|
||||
- `--color <color:string>` - Workspace color (hex code, e.g. #ff0000)
|
||||
- `--datatable-behavior <behavior:string>` - How to handle datatables: skip, schema_only, or schema_and_data (default: interactive prompt)
|
||||
- `--from-branch <branch:string>` - Fork based on this base branch (its bound workspace is the parent) and rename your current working branch onto the fork branch wm-fork/<branch>/<id>. Use when you're already on a branch (e.g. with forked-datatable edits) you want to turn into the fork. Omit to base the fork on the current branch and check out a fresh fork branch instead.
|
||||
- `-y --yes` - Skip interactive prompts (defaults datatable behavior to 'skip', and confirms the --from-branch rename)
|
||||
- `--from-branch <branch:string>` - Non-interactive override for the 'turn my current working branch into the fork' workflow: base the fork on <branch> (its bound workspace is the parent) and rename the current branch onto wm-fork/<branch>/<id>. Usually unneeded — from a working branch `wmill workspace fork` offers this interactively; from a base branch it creates a fresh fork branch.
|
||||
- `-y --yes` - Skip interactive prompts (defaults datatable behavior to 'skip'). On a non-base branch, requires --from-branch since the base branch can't be prompted for.
|
||||
- `workspace delete-fork <fork_name:string>` - Delete a forked workspace and git branch
|
||||
- `-y --yes` - Skip confirmation prompt
|
||||
- `workspace merge` - Compare and deploy changes between a fork and its parent workspace
|
||||
|
||||
Reference in New Issue
Block a user