windmill-cli 1.722.0 → 1.723.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/esm/main.js +2069 -1863
  2. package/package.json +1 -1
package/esm/main.js CHANGED
@@ -16772,7 +16772,7 @@ var init_OpenAPI = __esm(() => {
16772
16772
  PASSWORD: undefined,
16773
16773
  TOKEN: getEnv3("WM_TOKEN"),
16774
16774
  USERNAME: undefined,
16775
- VERSION: "1.722.0",
16775
+ VERSION: "1.723.0",
16776
16776
  WITH_CREDENTIALS: true,
16777
16777
  interceptors: {
16778
16778
  request: new Interceptors,
@@ -25304,16 +25304,18 @@ var init_auth = __esm(async () => {
25304
25304
  });
25305
25305
 
25306
25306
  // src/core/constants.ts
25307
- var WM_FORK_PREFIX = "wm-fork", VERSION = "1.722.0";
25307
+ var WM_FORK_PREFIX = "wm-fork", VERSION = "1.723.0";
25308
25308
 
25309
25309
  // src/utils/git.ts
25310
25310
  var exports_git = {};
25311
25311
  __export(exports_git, {
25312
+ renameCurrentGitBranch: () => renameCurrentGitBranch,
25312
25313
  isGitRepository: () => isGitRepository,
25313
25314
  isForkWorkspace: () => isForkWorkspace,
25314
25315
  gitSyncIncludePattern: () => gitSyncIncludePattern,
25315
25316
  gitSyncDeployPush: () => gitSyncDeployPush,
25316
25317
  gitSyncCommitMessage: () => gitSyncCommitMessage,
25318
+ gitBranchExists: () => gitBranchExists,
25317
25319
  getWorkspaceIdForWorkspaceForkFromBranchName: () => getWorkspaceIdForWorkspaceForkFromBranchName,
25318
25320
  getOriginalBranchForWorkspaceForks: () => getOriginalBranchForWorkspaceForks,
25319
25321
  getCurrentGitBranch: () => getCurrentGitBranch,
@@ -25337,6 +25339,19 @@ function getCurrentGitBranch() {
25337
25339
  return null;
25338
25340
  }
25339
25341
  }
25342
+ function gitBranchExists(branchName) {
25343
+ const r = spawnSync("git", ["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`], { stdio: "pipe" });
25344
+ return r.status === 0;
25345
+ }
25346
+ function renameCurrentGitBranch(newName) {
25347
+ const r = spawnSync("git", ["branch", "-m", newName], {
25348
+ encoding: "utf8",
25349
+ stdio: "pipe"
25350
+ });
25351
+ if ((r.status ?? 1) !== 0) {
25352
+ throw new Error(`git branch -m ${newName} failed (exit ${r.status}): ${r.stderr ?? ""}`);
25353
+ }
25354
+ }
25340
25355
  function getOriginalBranchForWorkspaceForks(branchName) {
25341
25356
  if (!branchName || !branchName.startsWith(WM_FORK_PREFIX)) {
25342
25357
  return null;
@@ -25578,1898 +25593,2027 @@ var init_git = __esm(() => {
25578
25593
  FORK_WORKSPACE_PREFIX = `${WM_FORK_PREFIX}-`;
25579
25594
  });
25580
25595
 
25581
- // src/commands/workspace/fork.ts
25582
- async function createWorkspaceFork2(opts, workspaceName, workspaceId = undefined) {
25583
- if (!isGitRepository()) {
25584
- throw new Error("You can only create forks within a git repo. Forks are tracked with git and synced to your instance with the git sync workflow.");
25585
- }
25586
- const workspace = await tryResolveBranchWorkspace(opts);
25587
- if (!workspace) {
25588
- throw new Error("Could not resolve workspace from branch name. Make sure you are in a git repo to use workspace forks");
25589
- }
25590
- info(`You are forking workspace (${workspace.workspaceId})`);
25591
- const currentBranch = getCurrentGitBranch();
25592
- if (!currentBranch) {
25593
- throw new Error("Could not get git branch name");
25594
- }
25595
- const originalBranchIfForked = getOriginalBranchForWorkspaceForks(currentBranch);
25596
- let clonedBranchName;
25597
- if (originalBranchIfForked) {
25598
- 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.`);
25599
- clonedBranchName = originalBranchIfForked;
25600
- } else {
25601
- clonedBranchName = currentBranch;
25602
- }
25603
- if (!clonedBranchName) {
25604
- throw new Error("Failed to get current branch name, aborting operation");
25596
+ // src/utils/resource_folders.ts
25597
+ import { sep as SEP2 } from "node:path";
25598
+ import * as fs6 from "node:fs";
25599
+ import * as path2 from "node:path";
25600
+ import process9 from "node:process";
25601
+ function setNonDottedPaths(value) {
25602
+ if (value && !_nonDottedPathsLogged) {
25603
+ debug("Using non-dotted paths (__flow, __app, __raw_app)");
25604
+ _nonDottedPathsLogged = true;
25605
25605
  }
25606
- if (opts.workspace) {
25607
- info(colors.red.bold("! Workspace needs to be specified as positional argument, not as option."));
25608
- return;
25606
+ _nonDottedPaths = value;
25607
+ }
25608
+ function getNonDottedPaths() {
25609
+ return _nonDottedPaths;
25610
+ }
25611
+ async function loadNonDottedPathsSetting() {
25612
+ let currentDir = process9.cwd();
25613
+ while (true) {
25614
+ const wmillYamlPath = path2.join(currentDir, "wmill.yaml");
25615
+ if (fs6.existsSync(wmillYamlPath)) {
25616
+ try {
25617
+ const config = await yamlParseFile(wmillYamlPath);
25618
+ setNonDottedPaths(config?.nonDottedPaths ?? false);
25619
+ debug(`Found wmill.yaml at ${wmillYamlPath}, nonDottedPaths=${config?.nonDottedPaths ?? false}`);
25620
+ } catch (e) {
25621
+ debug(`Failed to parse wmill.yaml at ${wmillYamlPath}: ${e}`);
25622
+ }
25623
+ return;
25624
+ }
25625
+ const parentDir = path2.dirname(currentDir);
25626
+ if (parentDir === currentDir) {
25627
+ debug("No wmill.yaml found, using default dotted paths");
25628
+ return;
25629
+ }
25630
+ currentDir = parentDir;
25609
25631
  }
25610
- while (workspaceName === undefined) {
25611
- if (!workspaceName) {
25612
- workspaceName = await Input.prompt("Name this forked workspace:");
25632
+ }
25633
+ function getFolderSuffixes() {
25634
+ return _nonDottedPaths ? NON_DOTTED_SUFFIXES : DOTTED_SUFFIXES;
25635
+ }
25636
+ function getFolderSuffix(type) {
25637
+ return getFolderSuffixes()[type];
25638
+ }
25639
+ function getMetadataFileName(type, format6) {
25640
+ return METADATA_FILES[type][format6];
25641
+ }
25642
+ function getMetadataPathSuffix(type, format6) {
25643
+ return getFolderSuffixes()[type] + "/" + METADATA_FILES[type][format6];
25644
+ }
25645
+ function hasWrongFormatSuffix(dirName) {
25646
+ const wrongSuffixes = _nonDottedPaths ? DOTTED_SUFFIXES : NON_DOTTED_SUFFIXES;
25647
+ for (const [type, suffix] of Object.entries(wrongSuffixes)) {
25648
+ if (dirName.endsWith(suffix)) {
25649
+ return type;
25613
25650
  }
25614
25651
  }
25615
- if (!workspaceId) {
25616
- workspaceId = await Input.prompt({
25617
- message: `Enter the ID of this forked workspace, it will then be prefixed by ${WM_FORK_PREFIX}. It will also determine the branch name`,
25618
- default: workspaceName,
25619
- suggestions: [workspaceName]
25620
- });
25652
+ return null;
25653
+ }
25654
+ function normalizeSep(p) {
25655
+ return p.replaceAll("\\", "/");
25656
+ }
25657
+ function isFlowPath(p) {
25658
+ return normalizeSep(p).includes(getFolderSuffixes().flow + "/");
25659
+ }
25660
+ function isAppPath(p) {
25661
+ return normalizeSep(p).includes(getFolderSuffixes().app + "/");
25662
+ }
25663
+ function isRawAppPath(p) {
25664
+ return normalizeSep(p).includes(getFolderSuffixes().raw_app + "/");
25665
+ }
25666
+ function isFolderResourcePathAnyFormat(p) {
25667
+ const n = normalizeSep(p);
25668
+ for (const suffixes of [DOTTED_SUFFIXES, NON_DOTTED_SUFFIXES]) {
25669
+ if (n.includes(suffixes.flow + "/") || n.includes(suffixes.app + "/") || n.includes(suffixes.raw_app + "/")) {
25670
+ return true;
25671
+ }
25621
25672
  }
25622
- const token = workspace.token;
25623
- if (!token) {
25624
- throw new Error("Not logged in. Please run 'wmill workspace add' first.");
25673
+ return false;
25674
+ }
25675
+ function isAppInlineScriptPath(filePath) {
25676
+ const suffixes = getFolderSuffixes();
25677
+ const normalizedPath = filePath.replaceAll(SEP2, "/");
25678
+ const escapedSuffix = suffixes.app.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
25679
+ const pattern = new RegExp(`${escapedSuffix}/`);
25680
+ return pattern.test(normalizedPath);
25681
+ }
25682
+ function isFlowInlineScriptPath(filePath) {
25683
+ const suffixes = getFolderSuffixes();
25684
+ const normalizedPath = filePath.replaceAll(SEP2, "/");
25685
+ const escapedSuffix = suffixes.flow.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
25686
+ const pattern = new RegExp(`${escapedSuffix}/`);
25687
+ return pattern.test(normalizedPath);
25688
+ }
25689
+ function extractResourceName(p, type) {
25690
+ const normalized = normalizeSep(p);
25691
+ const suffix = getFolderSuffixes()[type] + "/";
25692
+ const index = normalized.indexOf(suffix);
25693
+ if (index === -1)
25694
+ return null;
25695
+ return normalized.substring(0, index);
25696
+ }
25697
+ function extractFolderPath(p, type) {
25698
+ const normalized = normalizeSep(p);
25699
+ const suffix = getFolderSuffixes()[type] + "/";
25700
+ const index = normalized.indexOf(suffix);
25701
+ if (index === -1)
25702
+ return null;
25703
+ return normalized.substring(0, index) + suffix;
25704
+ }
25705
+ function buildFolderPath(resourceName, type) {
25706
+ return resourceName + getFolderSuffixes()[type];
25707
+ }
25708
+ function hasFolderSuffix(dirName, type) {
25709
+ return dirName.endsWith(getFolderSuffixes()[type]);
25710
+ }
25711
+ function extractNameFromFolder(folderName, type) {
25712
+ const suffix = getFolderSuffixes()[type];
25713
+ if (folderName.endsWith(suffix)) {
25714
+ return folderName.substring(0, folderName.length - suffix.length);
25625
25715
  }
25626
- const remote = workspace.remote;
25627
- setClient(token, remote.endsWith("/") ? remote.substring(0, remote.length - 1) : remote);
25628
- info(colors.blue(`Creating forked workspace: ${workspaceName}...`));
25629
- const trueWorkspaceId = `${WM_FORK_PREFIX}-${workspaceId}`;
25630
- let alreadyExists = false;
25631
- try {
25632
- alreadyExists = await existsWorkspace({
25633
- requestBody: { id: trueWorkspaceId }
25634
- });
25635
- } catch (e) {
25636
- info(colors.red.bold("! Credentials or instance is invalid. Aborting."));
25637
- throw e;
25716
+ return folderName;
25717
+ }
25718
+ function isFlowMetadataFile(p) {
25719
+ if (p.endsWith(DOTTED_SUFFIXES.flow + ".json") || p.endsWith(DOTTED_SUFFIXES.flow + ".yaml")) {
25720
+ return true;
25638
25721
  }
25639
- if (alreadyExists) {
25640
- throw new Error(`This forked workspace '${workspaceId}' (${workspaceName}) already exists, possibly archived (archiving keeps the id reserved). ` + `Permanently delete it with \`wmill workspace delete-fork ${workspaceId}\` to reuse the id, or choose a different id`);
25722
+ if (_nonDottedPaths) {
25723
+ return p.endsWith(NON_DOTTED_SUFFIXES.flow + ".json") || p.endsWith(NON_DOTTED_SUFFIXES.flow + ".yaml");
25641
25724
  }
25642
- const forkedDatatables = [];
25643
- let datatables = [];
25644
- try {
25645
- datatables = await listDataTables({
25646
- workspace: workspace.workspaceId
25647
- });
25648
- } catch (e) {
25649
- info(colors.yellow(`Note: Could not list datatables: ${e.message}`));
25725
+ return false;
25726
+ }
25727
+ function isAppMetadataFile(p) {
25728
+ if (p.endsWith(DOTTED_SUFFIXES.app + ".json") || p.endsWith(DOTTED_SUFFIXES.app + ".yaml")) {
25729
+ return true;
25650
25730
  }
25651
- if (datatables && datatables.length > 0) {
25652
- const behavior = opts.datatableBehavior ?? (opts.yes ? "skip" : undefined);
25653
- if (behavior !== "skip") {
25654
- info(`
25655
- Found ${datatables.length} datatable(s):`);
25656
- for (const dt of datatables) {
25657
- let dtBehavior;
25658
- if (behavior === "schema_only" || behavior === "schema_and_data") {
25659
- dtBehavior = behavior;
25660
- } else {
25661
- const { Select: Select2 } = await init_select().then(() => exports_select);
25662
- dtBehavior = await Select2.prompt({
25663
- message: `Datatable "${dt.name}" (${dt.resource_type}):`,
25664
- options: [
25665
- { name: "Keep original (no cloning)", value: "keep_original" },
25666
- { name: "Clone schema only", value: "schema_only" },
25667
- { name: "Clone schema and data", value: "schema_and_data" }
25668
- ]
25669
- });
25670
- }
25671
- if (dtBehavior === "keep_original") {
25672
- continue;
25673
- }
25674
- const newDbName = `${trueWorkspaceId.replace(/-/g, "_")}__${dt.name}`;
25675
- try {
25676
- info(colors.blue(` Creating database "${newDbName}" for datatable "${dt.name}"...`));
25677
- await createPgDatabase({
25678
- workspace: workspace.workspaceId,
25679
- requestBody: {
25680
- source: `datatable://${dt.name}`,
25681
- target_dbname: newDbName
25682
- }
25683
- });
25684
- info(colors.blue(` Importing ${dtBehavior === "schema_only" ? "schema" : "schema + data"}...`));
25685
- await importPgDatabase({
25686
- workspace: workspace.workspaceId,
25687
- requestBody: {
25688
- source: `datatable://${dt.name}`,
25689
- target: `datatable://${dt.name}`,
25690
- target_dbname_override: newDbName,
25691
- fork_behavior: dtBehavior
25692
- }
25693
- });
25694
- info(colors.green(` ✓ Datatable "${dt.name}" cloned.`));
25695
- forkedDatatables.push({ name: dt.name, new_dbname: newDbName });
25696
- } catch (e) {
25697
- info(colors.yellow(` ✗ Failed to clone datatable "${dt.name}": ${e.message}`));
25698
- }
25699
- }
25700
- }
25731
+ if (_nonDottedPaths) {
25732
+ return p.endsWith(NON_DOTTED_SUFFIXES.app + ".json") || p.endsWith(NON_DOTTED_SUFFIXES.app + ".yaml");
25701
25733
  }
25702
- const forkColor = opts.color;
25703
- try {
25704
- const gitSyncJobIds = await createWorkspaceForkGitBranch({
25705
- workspace: workspace.workspaceId,
25706
- requestBody: {
25707
- id: trueWorkspaceId,
25708
- name: opts.createWorkspaceName ?? workspaceName ?? trueWorkspaceId,
25709
- color: forkColor
25710
- }
25711
- });
25712
- if (gitSyncJobIds && gitSyncJobIds.length > 0) {
25713
- info(colors.blue(`Git sync branch creation triggered (${gitSyncJobIds.length} job(s)). These will complete asynchronously.`));
25714
- }
25715
- } catch (e) {
25716
- error(colors.red(`Failed to create git branch for fork: ${e.message}`));
25717
- throw e;
25734
+ return false;
25735
+ }
25736
+ function isRawAppMetadataFile(p) {
25737
+ if (p.endsWith(DOTTED_SUFFIXES.raw_app + ".json") || p.endsWith(DOTTED_SUFFIXES.raw_app + ".yaml")) {
25738
+ return true;
25718
25739
  }
25719
- try {
25720
- const result = await createWorkspaceFork({
25721
- workspace: workspace.workspaceId,
25722
- requestBody: {
25723
- id: trueWorkspaceId,
25724
- name: opts.createWorkspaceName ?? workspaceName ?? trueWorkspaceId,
25725
- color: forkColor,
25726
- forked_datatables: forkedDatatables
25727
- }
25728
- });
25729
- info(colors.green(`✅ ${result}`));
25730
- } catch (error2) {
25731
- error(colors.red(`Failed to create forked workspace: ${error2.message}`));
25732
- throw error2;
25740
+ if (_nonDottedPaths) {
25741
+ return p.endsWith(NON_DOTTED_SUFFIXES.raw_app + ".json") || p.endsWith(NON_DOTTED_SUFFIXES.raw_app + ".yaml");
25733
25742
  }
25734
- const newBranchName = `${WM_FORK_PREFIX}/${clonedBranchName}/${workspaceId}`;
25735
- info(`Created forked workspace ${trueWorkspaceId}. To start contributing to your fork, create and push edits to the branch \`${newBranchName}\` by using the command:
25736
-
25737
- ` + colors.white(`git checkout -b ${newBranchName}`) + `
25738
-
25739
- When doing operations on the forked workspace, it will use the remote setup in the workspaces section for the branch it was forked from.
25740
-
25741
- To merge changes back to the parent workspace, you can:
25742
- - Use the CLI: ` + colors.white(`git checkout ${newBranchName} && wmill workspace merge`) + `
25743
- - Use the Merge UI from the forked workspace home page
25744
- - Use git: ` + colors.white(`git checkout ${clonedBranchName} && git merge ${newBranchName} && wmill sync push`) + `
25745
- See: https://www.windmill.dev/docs/advanced/workspace_forks`);
25743
+ return false;
25746
25744
  }
25747
- async function deleteWorkspaceFork(opts, name) {
25748
- let forkWorkspaceId;
25749
- let token;
25750
- let remote;
25751
- let hasLocalProfile = false;
25752
- const orgWorkspaces = await allWorkspaces(opts.configDir);
25753
- const idxOf = orgWorkspaces.findIndex((x) => x.name === name);
25754
- if (idxOf !== -1) {
25755
- const workspace = orgWorkspaces[idxOf];
25756
- if (!workspace.workspaceId.startsWith(WM_FORK_PREFIX)) {
25757
- throw new Error(`You can only delete forked workspaces where the workspace id starts with \`${WM_FORK_PREFIX}.\` Failed while attempting to delete \`${workspace.workspaceId}\``);
25758
- }
25759
- forkWorkspaceId = workspace.workspaceId;
25760
- token = workspace.token;
25761
- remote = workspace.remote;
25762
- hasLocalProfile = true;
25763
- } else {
25764
- const parentWorkspace = await tryResolveBranchWorkspace(opts);
25765
- if (!parentWorkspace) {
25766
- throw new Error("Could not resolve parent workspace. Make sure you are in a git repo with 'workspaces' configured in wmill.yaml, or create a local workspace profile for the fork.");
25767
- }
25768
- forkWorkspaceId = name.startsWith(`${WM_FORK_PREFIX}-`) ? name : `${WM_FORK_PREFIX}-${name}`;
25769
- token = parentWorkspace.token;
25770
- remote = parentWorkspace.remote;
25771
- }
25772
- if (!opts.yes) {
25773
- const { Select: Select2 } = await init_select().then(() => exports_select);
25774
- const choice = await Select2.prompt({
25775
- message: `Are you sure you want to delete the forked workspace \`${forkWorkspaceId}\`?`,
25776
- options: [
25777
- { name: "Yes", value: "confirm" },
25778
- { name: "No", value: "cancel" }
25779
- ]
25780
- });
25781
- if (choice === "cancel") {
25782
- info("Operation cancelled");
25783
- return;
25784
- }
25745
+ function isRawAppFolderMetadataFile(p) {
25746
+ return p.endsWith(getMetadataPathSuffix("raw_app", "yaml")) || p.endsWith(getMetadataPathSuffix("raw_app", "json"));
25747
+ }
25748
+ function isAppFolderMetadataFile(p) {
25749
+ return p.endsWith(getMetadataPathSuffix("app", "yaml")) || p.endsWith(getMetadataPathSuffix("app", "json"));
25750
+ }
25751
+ function isFlowFolderMetadataFile(p) {
25752
+ return p.endsWith(getMetadataPathSuffix("flow", "yaml")) || p.endsWith(getMetadataPathSuffix("flow", "json"));
25753
+ }
25754
+ function getModuleFolderSuffix() {
25755
+ return MODULE_SUFFIX;
25756
+ }
25757
+ function isScriptModulePath(p) {
25758
+ return normalizeSep(p).includes(MODULE_SUFFIX + "/");
25759
+ }
25760
+ function isModuleEntryPoint(p) {
25761
+ const norm = normalizeSep(p);
25762
+ const suffix = MODULE_SUFFIX + "/";
25763
+ const idx = norm.indexOf(suffix);
25764
+ if (idx === -1)
25765
+ return false;
25766
+ const rest = norm.slice(idx + suffix.length);
25767
+ return rest.startsWith("script.") && !rest.includes("/");
25768
+ }
25769
+ function getScriptBasePathFromModulePath(p) {
25770
+ const norm = normalizeSep(p);
25771
+ const suffix = MODULE_SUFFIX + "/";
25772
+ const idx = norm.indexOf(suffix);
25773
+ if (idx === -1)
25774
+ return;
25775
+ return norm.slice(0, idx);
25776
+ }
25777
+ function scriptPathToRemotePath(p) {
25778
+ return (isModuleEntryPoint(p) ? getScriptBasePathFromModulePath(p) : p.substring(0, p.indexOf("."))).replaceAll(SEP2, "/");
25779
+ }
25780
+ function getDeleteSuffix(type, format6) {
25781
+ return getFolderSuffixes()[type] + "/" + METADATA_FILES[type][format6];
25782
+ }
25783
+ function transformJsonPathToDir(p, type) {
25784
+ const apiSuffix = DOTTED_SUFFIXES[type] + ".json";
25785
+ if (p.endsWith(apiSuffix)) {
25786
+ const basePath = p.substring(0, p.length - apiSuffix.length);
25787
+ return basePath + getFolderSuffixes()[type];
25785
25788
  }
25786
- setClient(token, remote.endsWith("/") ? remote.substring(0, remote.length - 1) : remote);
25787
- const result = await deleteWorkspace({
25788
- workspace: forkWorkspaceId
25789
- });
25790
- info(colors.green(`✅ Forked workspace '${forkWorkspaceId}' deleted successfully!
25791
- ${result}`));
25792
- if (hasLocalProfile) {
25793
- await removeWorkspace(name, false, opts);
25789
+ const userSuffix = getFolderSuffixes()[type] + ".json";
25790
+ if (p.endsWith(userSuffix)) {
25791
+ return p.substring(0, p.length - 5);
25794
25792
  }
25793
+ return p;
25795
25794
  }
25796
- var init_fork = __esm(async () => {
25797
- init_colors2();
25795
+ var DOTTED_SUFFIXES, NON_DOTTED_SUFFIXES, _nonDottedPaths = false, _nonDottedPathsLogged = false, METADATA_FILES, MODULE_SUFFIX = "__mod";
25796
+ var init_resource_folders = __esm(async () => {
25798
25797
  init_log();
25799
- init_client();
25800
- init_services_gen();
25801
- init_git();
25802
- await __promiseAll([
25803
- init_input(),
25804
- init_workspace(),
25805
- init_context()
25806
- ]);
25798
+ await init_yaml();
25799
+ DOTTED_SUFFIXES = {
25800
+ flow: ".flow",
25801
+ app: ".app",
25802
+ raw_app: ".raw_app"
25803
+ };
25804
+ NON_DOTTED_SUFFIXES = {
25805
+ flow: "__flow",
25806
+ app: "__app",
25807
+ raw_app: "__raw_app"
25808
+ };
25809
+ METADATA_FILES = {
25810
+ flow: { yaml: "flow.yaml", json: "flow.json" },
25811
+ app: { yaml: "app.yaml", json: "app.json" },
25812
+ raw_app: { yaml: "raw_app.yaml", json: "raw_app.json" }
25813
+ };
25807
25814
  });
25808
25815
 
25809
- // windmill-utils-internal/src/deploy.ts
25810
- function isTriggerKind(kind) {
25811
- return TRIGGER_KINDS.includes(kind);
25812
- }
25813
- function isTriggerOrScheduleKind(kind) {
25814
- return kind === "schedule" || isTriggerKind(kind);
25816
+ // src/core/conf.ts
25817
+ var exports_conf = {};
25818
+ __export(exports_conf, {
25819
+ validateBranchConfiguration: () => validateBranchConfiguration,
25820
+ showDiffs: () => showDiffs,
25821
+ setShowDiffs: () => setShowDiffs,
25822
+ readConfigFile: () => readConfigFile,
25823
+ parseSyncBehavior: () => parseSyncBehavior,
25824
+ mergeConfigWithConfigFile: () => mergeConfigWithConfigFile,
25825
+ getWorkspaceNames: () => getWorkspaceNames,
25826
+ getWmillYamlPath: () => getWmillYamlPath,
25827
+ getEffectiveWorkspaceId: () => getEffectiveWorkspaceId,
25828
+ getEffectiveSettings: () => getEffectiveSettings,
25829
+ getEffectiveGitBranch: () => getEffectiveGitBranch,
25830
+ findWorkspaceByGitBranch: () => findWorkspaceByGitBranch,
25831
+ convertGitBranchesToWorkspaces: () => convertGitBranchesToWorkspaces,
25832
+ SUPPORTED_SYNC_BEHAVIOR_VERSION: () => SUPPORTED_SYNC_BEHAVIOR_VERSION,
25833
+ GLOBAL_CONFIG_OPT: () => GLOBAL_CONFIG_OPT,
25834
+ DEFAULT_SYNC_OPTIONS: () => DEFAULT_SYNC_OPTIONS
25835
+ });
25836
+ import { join as join7, dirname as dirname6, resolve as resolve5, relative as relative4 } from "node:path";
25837
+ import { existsSync as existsSync2 } from "node:fs";
25838
+ import { writeFile } from "node:fs/promises";
25839
+ import { execSync as execSync2 } from "node:child_process";
25840
+ function setShowDiffs(value) {
25841
+ showDiffs = value;
25815
25842
  }
25816
- function folderName(path2) {
25817
- return path2.replace(/^f\//, "");
25843
+ function parseSyncBehavior(value) {
25844
+ if (!value && value !== 0)
25845
+ return 0;
25846
+ const s = String(value);
25847
+ const match = s.match(/^v(\d+)$/);
25848
+ return match ? parseInt(match[1], 10) : 0;
25818
25849
  }
25819
- function stripOperationalStateOnUpdate(payload, alreadyExists) {
25820
- if (!alreadyExists)
25821
- return payload;
25822
- const { mode: _mode, enabled: _enabled, ...rest } = payload;
25823
- return rest;
25850
+ function getGitRepoRoot() {
25851
+ try {
25852
+ const result = execSync2("git rev-parse --show-toplevel", {
25853
+ encoding: "utf8",
25854
+ stdio: "pipe"
25855
+ });
25856
+ return result.trim();
25857
+ } catch (error2) {
25858
+ return null;
25859
+ }
25824
25860
  }
25825
- function getSubModules(flowModule) {
25826
- const type = flowModule?.value?.type;
25827
- if (type === "forloopflow" || type === "whileloopflow") {
25828
- return [flowModule.value.modules ?? []];
25829
- } else if (type === "branchall") {
25830
- return (flowModule.value.branches ?? []).map((branch) => branch.modules ?? []);
25831
- } else if (type === "branchone") {
25832
- return [
25833
- ...(flowModule.value.branches ?? []).map((b) => b.modules ?? []),
25834
- flowModule.value.default ?? []
25835
- ];
25836
- } else if (type === "aiagent") {
25837
- if (flowModule.value.tools) {
25838
- return [
25839
- flowModule.value.tools.filter((t) => t.value?.type === "script" || t.value?.type === "flow").map((t) => ({
25840
- id: t.id,
25841
- value: t.value,
25842
- summary: t.summary
25843
- }))
25844
- ];
25861
+ function findWmillYaml() {
25862
+ const startDir = resolve5(process.cwd());
25863
+ const isInGitRepo = isGitRepository();
25864
+ const gitRoot = isInGitRepo ? getGitRepoRoot() : null;
25865
+ let currentDir = startDir;
25866
+ let foundPath = null;
25867
+ while (true) {
25868
+ const wmillYamlPath = join7(currentDir, "wmill.yaml");
25869
+ if (existsSync2(wmillYamlPath)) {
25870
+ foundPath = wmillYamlPath;
25871
+ break;
25845
25872
  }
25873
+ if (gitRoot && resolve5(currentDir) === resolve5(gitRoot)) {
25874
+ break;
25875
+ }
25876
+ const parentDir = dirname6(currentDir);
25877
+ if (parentDir === currentDir) {
25878
+ break;
25879
+ }
25880
+ currentDir = parentDir;
25846
25881
  }
25847
- return [];
25882
+ if (!GLOBAL_CONFIG_OPT.noCdToRoot && foundPath && resolve5(dirname6(foundPath)) !== resolve5(startDir)) {
25883
+ const configDir = dirname6(foundPath);
25884
+ const relativePath = relative4(startDir, foundPath);
25885
+ warn(`⚠️ wmill.yaml found in parent directory: ${relativePath}`);
25886
+ process.chdir(configDir);
25887
+ info(`\uD83D\uDCC1 Changed working directory to: ${configDir}`);
25888
+ }
25889
+ return foundPath;
25848
25890
  }
25849
- function getAllSubmodules(flowModule) {
25850
- return getSubModules(flowModule).map((modules) => modules.flatMap((m) => [m, ...getAllSubmodules(m)])).flat();
25891
+ function getWmillYamlPath() {
25892
+ return findWmillYaml();
25851
25893
  }
25852
- function getAllModules(flowModules, failureModule) {
25853
- return [
25854
- ...flowModules,
25855
- ...flowModules.flatMap((x) => getAllSubmodules(x)),
25856
- ...failureModule ? [failureModule] : []
25857
- ];
25858
- }
25859
- function toError(e) {
25860
- const err = e;
25861
- return err.body || err.message || String(e);
25862
- }
25863
- async function checkItemExists(provider, kind, path2, workspace) {
25864
- if (kind === "flow") {
25865
- return provider.existsFlowByPath({ workspace, path: path2 });
25866
- } else if (kind === "script") {
25867
- return provider.existsScriptByPath({ workspace, path: path2 });
25868
- } else if (kind === "app" || kind === "raw_app") {
25869
- return provider.existsApp({ workspace, path: path2 });
25870
- } else if (kind === "variable") {
25871
- return provider.existsVariable({ workspace, path: path2 });
25872
- } else if (kind === "resource") {
25873
- return provider.existsResource({ workspace, path: path2 });
25874
- } else if (kind === "resource_type") {
25875
- return provider.existsResourceType({ workspace, path: path2 });
25876
- } else if (kind === "folder") {
25877
- return provider.existsFolder({ workspace, name: folderName(path2) });
25878
- } else if (kind === "schedule") {
25879
- return provider.existsSchedule({ workspace, path: path2 });
25880
- } else if (isTriggerKind(kind)) {
25881
- return provider.existsTriggerByKind(kind, { workspace, path: path2 });
25882
- }
25883
- throw new Error(`Unknown kind: ${kind}`);
25884
- }
25885
- async function deployItem(provider, kind, path2, workspaceFrom, workspaceTo, onBehalfOf) {
25886
- const preserveOnBehalfOf = onBehalfOf !== undefined;
25887
- try {
25888
- const alreadyExists = await checkItemExists(provider, kind, path2, workspaceTo);
25889
- if (kind === "flow") {
25890
- const flow = await provider.getFlowByPath({
25891
- workspace: workspaceFrom,
25892
- path: path2
25893
- });
25894
- getAllModules(flow.value?.modules ?? [], flow.value?.failure_module).forEach((x) => {
25895
- if (x.value?.type === "script" && x.value.hash != null) {
25896
- x.value.hash = undefined;
25897
- }
25898
- });
25899
- if (alreadyExists) {
25900
- await provider.updateFlow({
25901
- workspace: workspaceTo,
25902
- path: path2,
25903
- requestBody: {
25904
- ...flow,
25905
- preserve_on_behalf_of: preserveOnBehalfOf,
25906
- on_behalf_of_email: onBehalfOf
25907
- }
25908
- });
25909
- } else {
25910
- await provider.createFlow({
25911
- workspace: workspaceTo,
25912
- requestBody: {
25913
- ...flow,
25914
- preserve_on_behalf_of: preserveOnBehalfOf,
25915
- on_behalf_of_email: onBehalfOf
25916
- }
25917
- });
25918
- }
25919
- } else if (kind === "script") {
25920
- const script = await provider.getScriptByPath({
25921
- workspace: workspaceFrom,
25922
- path: path2
25923
- });
25924
- let parentHash;
25925
- if (alreadyExists) {
25926
- const existing = await provider.getScriptByPath({
25927
- workspace: workspaceTo,
25928
- path: path2
25929
- });
25930
- parentHash = existing.hash;
25931
- }
25932
- await provider.createScript({
25933
- workspace: workspaceTo,
25934
- requestBody: {
25935
- ...script,
25936
- lock: script.lock,
25937
- parent_hash: parentHash,
25938
- preserve_on_behalf_of: preserveOnBehalfOf,
25939
- on_behalf_of_email: onBehalfOf
25940
- }
25941
- });
25942
- } else if (kind === "app" || kind === "raw_app") {
25943
- const app = await provider.getAppByPath({
25944
- workspace: workspaceFrom,
25945
- path: path2
25946
- });
25947
- if (alreadyExists) {
25948
- if (app.raw_app) {
25949
- const secret = await provider.getPublicSecretOfLatestVersionOfApp({
25950
- workspace: workspaceFrom,
25951
- path: app.path
25952
- });
25953
- const js = await provider.getRawAppData({
25954
- secretWithExtension: `${secret}.js`,
25955
- workspace: workspaceFrom
25956
- });
25957
- const css = await provider.getRawAppData({
25958
- secretWithExtension: `${secret}.css`,
25959
- workspace: workspaceFrom
25960
- });
25961
- await provider.updateAppRaw({
25962
- workspace: workspaceTo,
25963
- path: path2,
25964
- formData: {
25965
- app: { ...app, preserve_on_behalf_of: preserveOnBehalfOf },
25966
- css,
25967
- js
25968
- }
25969
- });
25970
- } else {
25971
- await provider.updateApp({
25972
- workspace: workspaceTo,
25973
- path: path2,
25974
- requestBody: {
25975
- ...app,
25976
- preserve_on_behalf_of: preserveOnBehalfOf
25977
- }
25978
- });
25979
- }
25980
- } else {
25981
- if (app.raw_app) {
25982
- const secret = await provider.getPublicSecretOfLatestVersionOfApp({
25983
- workspace: workspaceFrom,
25984
- path: app.path
25985
- });
25986
- const js = await provider.getRawAppData({
25987
- secretWithExtension: `${secret}.js`,
25988
- workspace: workspaceFrom
25989
- });
25990
- const css = await provider.getRawAppData({
25991
- secretWithExtension: `${secret}.css`,
25992
- workspace: workspaceFrom
25993
- });
25994
- await provider.createAppRaw({
25995
- workspace: workspaceTo,
25996
- formData: {
25997
- app: { ...app, preserve_on_behalf_of: preserveOnBehalfOf },
25998
- css,
25999
- js
26000
- }
26001
- });
26002
- } else {
26003
- await provider.createApp({
26004
- workspace: workspaceTo,
26005
- requestBody: {
26006
- ...app,
26007
- preserve_on_behalf_of: preserveOnBehalfOf
26008
- }
26009
- });
26010
- }
26011
- }
26012
- } else if (kind === "variable") {
26013
- const variable = await provider.getVariable({
26014
- workspace: workspaceFrom,
26015
- path: path2,
26016
- decryptSecret: true
26017
- });
26018
- if (alreadyExists) {
26019
- await provider.updateVariable({
26020
- workspace: workspaceTo,
26021
- path: path2,
26022
- requestBody: {
26023
- path: path2,
26024
- value: variable.value ?? "",
26025
- is_secret: variable.is_secret,
26026
- description: variable.description ?? ""
26027
- },
26028
- alreadyEncrypted: false
26029
- });
26030
- } else {
26031
- await provider.createVariable({
26032
- workspace: workspaceTo,
26033
- requestBody: {
26034
- path: path2,
26035
- value: variable.value ?? "",
26036
- is_secret: variable.is_secret,
26037
- description: variable.description ?? ""
26038
- }
26039
- });
26040
- }
26041
- } else if (kind === "resource") {
26042
- const resource = await provider.getResource({
26043
- workspace: workspaceFrom,
26044
- path: path2
26045
- });
26046
- if (alreadyExists) {
26047
- await provider.updateResource({
26048
- workspace: workspaceTo,
26049
- path: path2,
26050
- requestBody: {
26051
- path: path2,
26052
- value: resource.value ?? "",
26053
- description: resource.description ?? ""
26054
- }
26055
- });
26056
- } else {
26057
- await provider.createResource({
26058
- workspace: workspaceTo,
26059
- requestBody: {
26060
- path: path2,
26061
- value: resource.value ?? "",
26062
- resource_type: resource.resource_type,
26063
- description: resource.description ?? ""
26064
- }
26065
- });
26066
- }
26067
- } else if (kind === "resource_type") {
26068
- const rt = await provider.getResourceType({
26069
- workspace: workspaceFrom,
26070
- path: path2
26071
- });
26072
- if (alreadyExists) {
26073
- await provider.updateResourceType({
26074
- workspace: workspaceTo,
26075
- path: path2,
26076
- requestBody: {
26077
- schema: rt.schema,
26078
- description: rt.description ?? ""
26079
- }
26080
- });
26081
- } else {
26082
- await provider.createResourceType({
26083
- workspace: workspaceTo,
26084
- requestBody: {
26085
- name: rt.name,
26086
- schema: rt.schema,
26087
- description: rt.description ?? ""
26088
- }
26089
- });
26090
- }
26091
- } else if (kind === "folder") {
26092
- const name = folderName(path2);
26093
- const folder = await provider.getFolder({
26094
- workspace: workspaceFrom,
26095
- name
26096
- });
26097
- if (alreadyExists) {
26098
- await provider.updateFolder({
26099
- workspace: workspaceTo,
26100
- name,
26101
- requestBody: {
26102
- owners: folder.owners,
26103
- extra_perms: folder.extra_perms,
26104
- summary: folder.summary ?? undefined
26105
- }
26106
- });
26107
- } else {
26108
- await provider.createFolder({
26109
- workspace: workspaceTo,
26110
- requestBody: {
26111
- name,
26112
- owners: folder.owners,
26113
- extra_perms: folder.extra_perms,
26114
- summary: folder.summary ?? undefined
26115
- }
26116
- });
26117
- }
26118
- } else if (kind === "schedule") {
26119
- const schedule = await provider.getSchedule({
26120
- workspace: workspaceFrom,
26121
- path: path2
26122
- });
26123
- const baseBody = stripOperationalStateOnUpdate(schedule, alreadyExists);
26124
- const requestBody = {
26125
- ...baseBody,
26126
- permissioned_as: onBehalfOf,
26127
- preserve_permissioned_as: preserveOnBehalfOf
26128
- };
26129
- if (alreadyExists) {
26130
- await provider.updateSchedule({
26131
- workspace: workspaceTo,
26132
- path: path2,
26133
- requestBody
26134
- });
26135
- } else {
26136
- await provider.createSchedule({
26137
- workspace: workspaceTo,
26138
- requestBody
26139
- });
26140
- }
26141
- } else if (isTriggerKind(kind)) {
26142
- const triggerBody = await provider.getTriggerForDeploy(kind, {
26143
- workspace: workspaceFrom,
26144
- path: path2,
26145
- onBehalfOf
26146
- });
26147
- const requestBody = stripOperationalStateOnUpdate(triggerBody, alreadyExists);
26148
- if (alreadyExists) {
26149
- await provider.updateTriggerByKind(kind, {
26150
- workspace: workspaceTo,
26151
- path: path2,
26152
- requestBody
26153
- });
26154
- } else {
26155
- await provider.createTriggerByKind(kind, {
26156
- workspace: workspaceTo,
26157
- requestBody
26158
- });
26159
- }
26160
- } else {
26161
- throw new Error(`Unknown kind: ${kind}`);
26162
- }
26163
- return { success: true };
26164
- } catch (e) {
26165
- return { success: false, error: toError(e) };
26166
- }
26167
- }
26168
- async function deleteItemInWorkspace(provider, kind, path2, workspace) {
26169
- try {
26170
- if (kind === "script") {
26171
- await provider.archiveScriptByPath({ workspace, path: path2 });
26172
- } else if (kind === "flow") {
26173
- await provider.archiveFlowByPath({
26174
- workspace,
26175
- path: path2,
26176
- requestBody: { archived: true }
26177
- });
26178
- } else if (kind === "app" || kind === "raw_app") {
26179
- await provider.deleteApp({ workspace, path: path2 });
26180
- } else if (kind === "variable") {
26181
- await provider.deleteVariable({ workspace, path: path2 });
26182
- } else if (kind === "resource") {
26183
- await provider.deleteResource({ workspace, path: path2 });
26184
- } else if (kind === "resource_type") {
26185
- await provider.deleteResourceType({ workspace, path: path2 });
26186
- } else if (kind === "folder") {
26187
- await provider.deleteFolder({ workspace, name: folderName(path2) });
26188
- } else if (kind === "schedule") {
26189
- await provider.deleteSchedule({ workspace, path: path2 });
26190
- } else if (isTriggerKind(kind)) {
26191
- await provider.deleteTriggerByKind(kind, { workspace, path: path2 });
26192
- } else {
26193
- throw new Error(`Deletion not supported for kind: ${kind}`);
26194
- }
26195
- return { success: true };
26196
- } catch (e) {
26197
- return { success: false, error: toError(e) };
26198
- }
26199
- }
26200
- async function getOnBehalfOf(provider, kind, path2, workspace) {
25894
+ async function readConfigFile(opts) {
25895
+ const warnIfMissing = opts?.warnIfMissing ?? true;
26201
25896
  try {
26202
- if (kind === "flow") {
26203
- const flow = await provider.getFlowByPath({ workspace, path: path2 });
26204
- return flow.on_behalf_of_email;
26205
- } else if (kind === "script") {
26206
- const script = await provider.getScriptByPath({ workspace, path: path2 });
26207
- return script.on_behalf_of_email;
26208
- } else if (kind === "app" || kind === "raw_app") {
26209
- const app = await provider.getAppByPath({ workspace, path: path2 });
26210
- return app.policy?.on_behalf_of_email;
26211
- } else if (kind === "schedule") {
26212
- const schedule = await provider.getSchedule({ workspace, path: path2 });
26213
- return schedule.permissioned_as;
26214
- } else if (isTriggerKind(kind)) {
26215
- return await provider.getTriggerPermissionedAs(kind, { workspace, path: path2 });
26216
- }
26217
- } catch {}
26218
- return;
26219
- }
26220
- var TRIGGER_KINDS;
26221
- var init_deploy = __esm(() => {
26222
- TRIGGER_KINDS = [
26223
- "http_trigger",
26224
- "websocket_trigger",
26225
- "kafka_trigger",
26226
- "nats_trigger",
26227
- "postgres_trigger",
26228
- "mqtt_trigger",
26229
- "sqs_trigger",
26230
- "gcp_trigger",
26231
- "azure_trigger",
26232
- "email_trigger"
26233
- ];
26234
- });
26235
-
26236
- // node_modules/@cliffy/prompt/checkbox.js
26237
- var exports_checkbox = {};
26238
- __export(exports_checkbox, {
26239
- isCheckboxOptionGroup: () => isCheckboxOptionGroup,
26240
- Checkbox: () => Checkbox
26241
- });
26242
- function areSomeChecked(options) {
26243
- return options.some((option) => isOptionGroup(option) ? areSomeChecked(option.options) : option.checked);
26244
- }
26245
- function areAllChecked(options) {
26246
- return options.every((option) => isOptionGroup(option) ? areAllChecked(option.options) : option.checked);
26247
- }
26248
- function flatOptions(options) {
26249
- return flat(options);
26250
- function flat(options2, indentLevel = 0, opts = []) {
26251
- for (const option of options2) {
26252
- option.indentLevel = indentLevel;
26253
- if (isOption2(option)) {
26254
- opts.push(option);
26255
- }
26256
- if (isOptionGroup(option)) {
26257
- flat(option.options, ++indentLevel, opts);
25897
+ const wmillYamlPath = findWmillYaml();
25898
+ if (!wmillYamlPath) {
25899
+ if (warnIfMissing) {
25900
+ warn("No wmill.yaml found. Use 'wmill init' to bootstrap it.");
26258
25901
  }
25902
+ return {};
26259
25903
  }
26260
- return opts;
26261
- }
26262
- }
26263
- function isCheckboxOptionGroup(option) {
26264
- return isOptionGroup(option);
26265
- }
26266
- var Checkbox;
26267
- var init_checkbox = __esm(async () => {
26268
- init_equal();
26269
- init_colors();
26270
- init__figures();
26271
- await __promiseAll([
26272
- init__generic_list(),
26273
- init__generic_prompt()
26274
- ]);
26275
- Checkbox = class Checkbox extends GenericList {
26276
- settings;
26277
- options;
26278
- listIndex;
26279
- listOffset;
26280
- confirmSubmit = false;
26281
- static prompt(options) {
26282
- return new this(options).prompt();
26283
- }
26284
- static inject(value) {
26285
- GenericPrompt.inject(value);
26286
- }
26287
- constructor(options) {
26288
- super();
26289
- this.settings = this.getDefaultSettings(options);
26290
- this.options = this.settings.options.slice();
26291
- this.listIndex = this.getListIndex();
26292
- this.listOffset = this.getPageOffset(this.listIndex);
26293
- }
26294
- getDefaultSettings(options) {
26295
- const settings = super.getDefaultSettings(options);
26296
- return {
26297
- confirmSubmit: true,
26298
- ...settings,
26299
- check: options.check ?? green(Figures.TICK),
26300
- uncheck: options.uncheck ?? red(Figures.CROSS),
26301
- partialCheck: options.partialCheck ?? green(Figures.RADIO_ON),
26302
- minOptions: options.minOptions ?? 0,
26303
- maxOptions: options.maxOptions ?? Infinity,
26304
- options: this.mapOptions(options, options.options),
26305
- keys: {
26306
- check: [
26307
- "space"
26308
- ],
26309
- checkAll: [
26310
- "a"
26311
- ],
26312
- ...settings.keys ?? {},
26313
- open: options.keys?.open ?? [
26314
- "right"
26315
- ],
26316
- back: options.keys?.back ?? [
26317
- "left",
26318
- "escape"
26319
- ]
26320
- }
26321
- };
26322
- }
26323
- mapOptions(promptOptions, options) {
26324
- return options.map((option) => typeof option === "string" || typeof option === "number" ? this.mapOption(promptOptions, {
26325
- value: option
26326
- }) : isCheckboxOptionGroup(option) ? this.mapOptionGroup(promptOptions, option) : this.mapOption(promptOptions, option));
26327
- }
26328
- mapOption(options, option) {
26329
- if (isOption2(option)) {
26330
- return {
26331
- ...super.mapOption(options, option),
26332
- checked: typeof option.checked === "undefined" && options.default && options.default.indexOf(option.value) !== -1 ? true : !!option.checked,
26333
- icon: typeof option.icon === "undefined" ? true : option.icon
26334
- };
25904
+ const conf = await yamlParseFile(wmillYamlPath);
25905
+ if (conf && "overrides" in conf) {
25906
+ const overrides = conf.overrides;
25907
+ const hasSettings = overrides && typeof overrides === "object" && Object.keys(overrides).length > 0;
25908
+ if (hasSettings) {
25909
+ throw new Error(`❌ The 'overrides' field is no longer supported.
25910
+ ` + ` The configuration system now uses workspace-based configuration.
25911
+ ` + " Please delete your wmill.yaml and run 'wmill init' to recreate it with the new format.");
26335
25912
  } else {
26336
- return {
26337
- ...super.mapOption(options, option),
26338
- checked: false,
26339
- icon: false
26340
- };
26341
- }
26342
- }
26343
- mapOptionGroup(promptOptions, option) {
26344
- const options = this.mapOptions(promptOptions, option.options);
26345
- const optionGroup = super.mapOptionGroup(promptOptions, option, false);
26346
- return {
26347
- ...optionGroup,
26348
- get checked() {
26349
- return areAllChecked(options);
26350
- },
26351
- get disabled() {
26352
- return optionGroup.disabled || options.every((opt) => opt.disabled);
26353
- },
26354
- options,
26355
- icon: typeof option.icon === "undefined" ? true : option.icon
26356
- };
26357
- }
26358
- match() {
26359
- super.match();
26360
- if (this.isSearching()) {
26361
- this.selectSearch();
26362
- }
26363
- }
26364
- getListItemIcon(option) {
26365
- return this.getCheckboxIcon(option) + super.getListItemIcon(option);
26366
- }
26367
- getCheckboxIcon(option) {
26368
- if (!option.icon) {
26369
- return "";
26370
- }
26371
- const icon = option.checked ? this.settings.check + " " : isOptionGroup(option) && areSomeChecked(option.options) ? this.settings.partialCheck + " " : this.settings.uncheck + " ";
26372
- return option.disabled ? dim(icon) : icon;
26373
- }
26374
- getValue() {
26375
- return flatOptions(this.settings.options).filter((option) => option.checked).map((option) => option.value);
26376
- }
26377
- async handleEvent(event) {
26378
- const hasConfirmed = this.confirmSubmit;
26379
- this.confirmSubmit = false;
26380
- switch (true) {
26381
- case (this.isKey(this.settings.keys, "check", event) && !this.isSearchSelected()):
26382
- this.checkValue();
26383
- break;
26384
- case this.isKey(this.settings.keys, "submit", event):
26385
- await this.submit(hasConfirmed);
26386
- break;
26387
- case (event.ctrl && this.isKey(this.settings.keys, "checkAll", event)):
26388
- this.checkAllOption();
26389
- break;
26390
- default:
26391
- await super.handleEvent(event);
26392
- }
26393
- }
26394
- hint() {
26395
- if (this.confirmSubmit) {
26396
- const info2 = this.isBackButton(this.selectedOption) ? ` To leave the current group press ${getFiguresByKeys(this.settings.keys.back ?? []).join(", ")}.` : isOptionGroup(this.selectedOption) ? ` To open the selected group press ${getFiguresByKeys(this.settings.keys.open ?? []).join(", ")}.` : ` To check or uncheck the selected option press ${getFiguresByKeys(this.settings.keys.check ?? []).join(", ")}.`;
26397
- return this.settings.indent + brightBlue(`Press ${getFiguresByKeys(this.settings.keys.submit ?? [])} again to submit.${info2}`);
26398
- }
26399
- return super.hint();
26400
- }
26401
- async submit(hasConfirmed) {
26402
- if (!hasConfirmed && this.settings.confirmSubmit && !this.isSearchSelected()) {
26403
- this.confirmSubmit = true;
26404
- return;
25913
+ delete conf.overrides;
26405
25914
  }
26406
- await super.submit();
26407
25915
  }
26408
- checkValue() {
26409
- const option = this.options.at(this.listIndex);
26410
- if (!option) {
26411
- this.setErrorMessage("No option available to select.");
26412
- return;
26413
- } else if (option.disabled) {
26414
- this.setErrorMessage("This option is disabled and cannot be changed.");
26415
- return;
25916
+ if (conf && !conf.workspaces) {
25917
+ let legacyKey = null;
25918
+ let legacyData;
25919
+ if (conf.gitBranches) {
25920
+ legacyKey = "gitBranches";
25921
+ legacyData = conf.gitBranches;
25922
+ } else if (conf.environments) {
25923
+ legacyKey = "environments";
25924
+ legacyData = conf.environments;
25925
+ } else if (conf.git_branches) {
25926
+ legacyKey = "git_branches";
25927
+ legacyData = conf.git_branches;
26416
25928
  }
26417
- this.checkOption(option, !option.checked);
26418
- }
26419
- checkOption(option, checked) {
26420
- if (isOption2(option)) {
26421
- option.checked = checked;
26422
- } else {
26423
- for (const childOption of option.options) {
26424
- this.checkOption(childOption, checked);
25929
+ if (legacyKey && legacyData) {
25930
+ conf.workspaces = convertGitBranchesToWorkspaces(legacyData);
25931
+ if (!legacyConfigWarned) {
25932
+ warn(`⚠️ '${legacyKey}' in wmill.yaml is deprecated. Use 'workspaces' instead.
25933
+ ` + ` Run 'wmill config migrate' to update your configuration automatically.`);
25934
+ legacyConfigWarned = true;
26425
25935
  }
26426
25936
  }
26427
- }
26428
- checkAllOption() {
26429
- const checked = this.options.some((option) => option.checked);
26430
- for (const option of this.options) {
26431
- this.checkOption(option, !checked);
26432
- }
26433
- }
26434
- validate(value) {
26435
- const options = flatOptions(this.settings.options);
26436
- const isValidValue = Array.isArray(value) && value.every((val) => options.findIndex((option) => equal(option.value, val)) !== -1);
26437
- if (!isValidValue) {
26438
- return false;
26439
- }
26440
- if (value.length < this.settings.minOptions) {
26441
- return `The minimum number of options is ${this.settings.minOptions} but got ${value.length}.`;
26442
- }
26443
- if (value.length > this.settings.maxOptions) {
26444
- return `The maximum number of options is ${this.settings.maxOptions} but got ${value.length}.`;
25937
+ } else if (conf?.workspaces) {
25938
+ for (const legacyKey of ["gitBranches", "environments", "git_branches"]) {
25939
+ if (conf[legacyKey]) {
25940
+ warn(`⚠️ Both 'workspaces' and '${legacyKey}' found in wmill.yaml. Using 'workspaces' and ignoring '${legacyKey}'.`);
25941
+ }
26445
25942
  }
26446
- return true;
26447
25943
  }
26448
- transform(value) {
26449
- return value;
25944
+ delete conf?.gitBranches;
25945
+ delete conf?.environments;
25946
+ delete conf?.git_branches;
25947
+ if (conf?.defaultTs == undefined) {
25948
+ warn("No defaultTs defined in your wmill.yaml. Using 'bun' as default.");
26450
25949
  }
26451
- format(value) {
26452
- return value.map((val) => this.settings.format?.(val) ?? this.getOptionByValue(val)?.name ?? String(val)).join(", ");
25950
+ setNonDottedPaths(conf?.nonDottedPaths ?? false);
25951
+ const syncBehaviorVersion = parseSyncBehavior(conf?.syncBehavior);
25952
+ if (syncBehaviorVersion > SUPPORTED_SYNC_BEHAVIOR_VERSION) {
25953
+ error(`Your wmill.yaml specifies syncBehavior: ${conf.syncBehavior}, but this CLI only supports up to v${SUPPORTED_SYNC_BEHAVIOR_VERSION}. Run 'wmill upgrade' to update.`);
25954
+ process.exit(1);
26453
25955
  }
26454
- };
26455
- });
26456
-
26457
- // src/commands/workspace/merge.ts
26458
- function triggerService(kind) {
26459
- switch (kind) {
26460
- case "http_trigger":
26461
- return {
26462
- exists: existsHttpTrigger,
26463
- get: getHttpTrigger,
26464
- create: createHttpTrigger,
26465
- update: updateHttpTrigger,
26466
- delete: deleteHttpTrigger
26467
- };
26468
- case "websocket_trigger":
26469
- return {
26470
- exists: existsWebsocketTrigger,
26471
- get: getWebsocketTrigger,
26472
- create: createWebsocketTrigger,
26473
- update: updateWebsocketTrigger,
26474
- delete: deleteWebsocketTrigger
26475
- };
26476
- case "kafka_trigger":
26477
- return {
26478
- exists: existsKafkaTrigger,
26479
- get: getKafkaTrigger,
26480
- create: createKafkaTrigger,
26481
- update: updateKafkaTrigger,
26482
- delete: deleteKafkaTrigger
26483
- };
26484
- case "nats_trigger":
26485
- return {
26486
- exists: existsNatsTrigger,
26487
- get: getNatsTrigger,
26488
- create: createNatsTrigger,
26489
- update: updateNatsTrigger,
26490
- delete: deleteNatsTrigger
26491
- };
26492
- case "postgres_trigger":
26493
- return {
26494
- exists: existsPostgresTrigger,
26495
- get: getPostgresTrigger,
26496
- create: createPostgresTrigger,
26497
- update: updatePostgresTrigger,
26498
- delete: deletePostgresTrigger
26499
- };
26500
- case "mqtt_trigger":
26501
- return {
26502
- exists: existsMqttTrigger,
26503
- get: getMqttTrigger,
26504
- create: createMqttTrigger,
26505
- update: updateMqttTrigger,
26506
- delete: deleteMqttTrigger
26507
- };
26508
- case "sqs_trigger":
26509
- return {
26510
- exists: existsSqsTrigger,
26511
- get: getSqsTrigger,
26512
- create: createSqsTrigger,
26513
- update: updateSqsTrigger,
26514
- delete: deleteSqsTrigger
26515
- };
26516
- case "gcp_trigger":
26517
- return {
26518
- exists: existsGcpTrigger,
26519
- get: getGcpTrigger,
26520
- create: createGcpTrigger,
26521
- update: updateGcpTrigger,
26522
- delete: deleteGcpTrigger
26523
- };
26524
- case "azure_trigger":
26525
- return {
26526
- exists: existsAzureTrigger,
26527
- get: getAzureTrigger,
26528
- create: createAzureTrigger,
26529
- update: updateAzureTrigger,
26530
- delete: deleteAzureTrigger
26531
- };
26532
- case "email_trigger":
26533
- return {
26534
- exists: existsEmailTrigger,
26535
- get: getEmailTrigger,
26536
- create: createEmailTrigger,
26537
- update: updateEmailTrigger,
26538
- delete: deleteEmailTrigger
26539
- };
26540
- }
26541
- }
26542
- function preparePayload(kind, trigger, onBehalfOf) {
26543
- const preserve = onBehalfOf !== undefined;
26544
- const base = {
26545
- ...trigger,
26546
- permissioned_as: onBehalfOf,
26547
- preserve_permissioned_as: preserve
26548
- };
26549
- if (kind === "gcp_trigger") {
26550
- base.subscription_id = "";
26551
- base.subscription_mode = "create_update";
26552
- if (base.delivery_config) {
26553
- base.delivery_config = { ...base.delivery_config, audience: "" };
25956
+ return typeof conf == "object" ? conf : {};
25957
+ } catch (e) {
25958
+ if (e instanceof Error && (e.message.includes("overrides") || e.message.includes("Obsolete configuration format"))) {
25959
+ throw e;
26554
25960
  }
26555
- if (base.delivery_type === "push") {
26556
- base.base_endpoint = OpenAPI.BASE.replace(/\/api\/?$/, "");
25961
+ if (e instanceof Error && e.message.includes("Error parsing yaml")) {
25962
+ const yamlError = e.cause instanceof Error ? e.cause.message : String(e.cause);
25963
+ throw new Error(`❌ YAML syntax error in wmill.yaml:
25964
+ ` + " " + yamlError + `
25965
+ ` + " Please fix the YAML syntax in wmill.yaml or delete the file to start fresh.");
26557
25966
  } else {
26558
- base.base_endpoint = undefined;
25967
+ throw new Error(`❌ Failed to read wmill.yaml:
25968
+ ` + " " + (e instanceof Error ? e.message : String(e)) + `
25969
+ ` + " Please check file permissions or fix the syntax.");
26559
25970
  }
26560
25971
  }
26561
- return base;
26562
25972
  }
26563
- async function mergeWorkspaces(opts) {
26564
- const workspace = await tryResolveBranchWorkspace(opts);
26565
- if (!workspace) {
26566
- throw new Error("Could not resolve workspace from branch name. Make sure you are in a git repo with 'workspaces' configured in wmill.yaml.");
26567
- }
26568
- const token = workspace.token;
26569
- if (!token) {
26570
- throw new Error("Not logged in. Please run 'wmill workspace add' first.");
26571
- }
26572
- const remote = workspace.remote;
26573
- setClient(token, remote.endsWith("/") ? remote.substring(0, remote.length - 1) : remote);
26574
- const forkWorkspaceId = workspace.workspaceId;
26575
- const userWorkspaces = await listUserWorkspaces();
26576
- const forkEntry = userWorkspaces.workspaces?.find((w) => w.id === forkWorkspaceId);
26577
- if (!forkEntry?.parent_workspace_id) {
26578
- throw new Error(`Workspace '${forkWorkspaceId}' is not a fork (no parent_workspace_id). ` + `You can only merge from a forked workspace.`);
26579
- }
26580
- const parentWorkspaceId = forkEntry.parent_workspace_id;
26581
- info(`Fork: ${colors.bold(forkWorkspaceId)} → Parent: ${colors.bold(parentWorkspaceId)}`);
26582
- info("Comparing workspaces...");
26583
- const comparison = await compareWorkspaces({
26584
- workspace: parentWorkspaceId,
26585
- targetWorkspaceId: forkWorkspaceId
26586
- });
26587
- if (comparison.skipped_comparison) {
26588
- info(colors.yellow("This fork was created before change tracking was available. " + "Use the UI or git-based merge instead."));
25973
+ async function mergeConfigWithConfigFile(opts) {
25974
+ const configFile = await readConfigFile();
25975
+ return Object.assign(configFile ?? {}, opts);
25976
+ }
25977
+ async function validateBranchConfiguration(opts, workspaceNameOverride) {
25978
+ if (opts.skipBranchValidation || workspaceNameOverride || !isGitRepository()) {
26589
25979
  return;
26590
25980
  }
26591
- const summary = comparison.summary;
26592
- if (summary.total_diffs === 0) {
26593
- info(colors.green("Everything is up to date. No differences found."));
26594
- return;
25981
+ const config = await readConfigFile();
25982
+ const { workspaces } = config;
25983
+ const rawBranch = getCurrentGitBranch();
25984
+ const originalBranchIfForked = getOriginalBranchForWorkspaceForks(rawBranch);
25985
+ let currentBranch;
25986
+ if (originalBranchIfForked) {
25987
+ info(`Workspace fork detected from branch name \`${rawBranch}\`. Validating workspace configuration using original branch \`${originalBranchIfForked}\``);
25988
+ currentBranch = originalBranchIfForked;
25989
+ } else {
25990
+ currentBranch = rawBranch;
26595
25991
  }
26596
- info("");
26597
- info(colors.bold("Comparison Summary:"));
26598
- const summaryRows = [];
26599
- if (summary.scripts_changed > 0)
26600
- summaryRows.push(["Scripts", String(summary.scripts_changed)]);
26601
- if (summary.flows_changed > 0)
26602
- summaryRows.push(["Flows", String(summary.flows_changed)]);
26603
- if (summary.apps_changed > 0)
26604
- summaryRows.push(["Apps", String(summary.apps_changed)]);
26605
- if (summary.resources_changed > 0)
26606
- summaryRows.push(["Resources", String(summary.resources_changed)]);
26607
- if (summary.variables_changed > 0)
26608
- summaryRows.push(["Variables", String(summary.variables_changed)]);
26609
- if (summary.resource_types_changed > 0)
26610
- summaryRows.push(["Resource Types", String(summary.resource_types_changed)]);
26611
- if (summary.folders_changed > 0)
26612
- summaryRows.push(["Folders", String(summary.folders_changed)]);
26613
- if (summary.schedules_changed > 0)
26614
- summaryRows.push(["Schedules", String(summary.schedules_changed)]);
26615
- if (summary.triggers_changed > 0)
26616
- summaryRows.push(["Triggers", String(summary.triggers_changed)]);
26617
- summaryRows.push(["Total", String(summary.total_diffs)]);
26618
- if (summary.conflicts > 0)
26619
- summaryRows.push([
26620
- colors.red("Conflicts"),
26621
- colors.red(String(summary.conflicts))
26622
- ]);
26623
- new Table2().header(["Type", "Changed"]).padding(2).border(true).body(summaryRows).render();
26624
- const diffs = comparison.diffs.filter((d) => d.has_changes !== false);
26625
- if (diffs.length === 0) {
26626
- info(colors.green("No effective changes to deploy."));
25992
+ if (!workspaces || getWorkspaceNames(workspaces).length === 0) {
25993
+ warn(`⚠️ WARNING: In a Git repository, the 'workspaces' section is recommended in wmill.yaml.
25994
+ ` + ` Consider adding a workspaces section to map workspace names to Windmill instances.
25995
+ ` + " Run 'wmill init' to recreate the configuration file with proper workspace setup.");
26627
25996
  return;
26628
25997
  }
26629
- info("");
26630
- info(colors.bold("Changed items:"));
26631
- new Table2().header(["#", "Kind", "Path", "Ahead", "Behind", "Conflict"]).padding(1).border(true).body(diffs.map((d, i) => {
26632
- const isConflict = d.ahead > 0 && d.behind > 0;
26633
- return [
26634
- String(i + 1),
26635
- d.kind,
26636
- d.path,
26637
- d.ahead > 0 ? colors.green(String(d.ahead)) : "0",
26638
- d.behind > 0 ? colors.yellow(String(d.behind)) : "0",
26639
- isConflict ? colors.red("YES") : ""
26640
- ];
26641
- })).render();
26642
- let direction;
26643
- if (opts.direction === "to-parent" || opts.direction === "to-fork") {
26644
- direction = opts.direction;
26645
- } else if (opts.direction) {
26646
- throw new Error(`Invalid direction '${opts.direction}'. Use 'to-parent' or 'to-fork'.`);
26647
- } else if (opts.yes) {
26648
- direction = "to-parent";
26649
- } else {
26650
- const { Select: Select2 } = await init_select().then(() => exports_select);
26651
- direction = await Select2.prompt({
26652
- message: "Deploy direction:",
26653
- options: [
26654
- {
26655
- name: `Deploy to parent (${parentWorkspaceId}) ← fork changes`,
26656
- value: "to-parent"
26657
- },
26658
- {
26659
- name: `Update fork (${forkWorkspaceId}) ← parent changes`,
26660
- value: "to-fork"
26661
- }
26662
- ]
26663
- });
26664
- }
26665
- info(`
26666
- Direction: ${colors.bold(direction === "to-parent" ? `Fork → Parent (${parentWorkspaceId})` : `Parent → Fork (${forkWorkspaceId})`)}`);
26667
- const selectableDiffs = diffs.filter((d) => {
26668
- if (direction === "to-parent") {
26669
- return d.ahead > 0;
25998
+ const branchToWsNames = new Map;
25999
+ for (const name of getWorkspaceNames(workspaces)) {
26000
+ const entry = workspaces[name];
26001
+ const branch = getEffectiveGitBranch(name, entry);
26002
+ const existing = branchToWsNames.get(branch);
26003
+ if (existing) {
26004
+ existing.push(name);
26670
26005
  } else {
26671
- return d.behind > 0;
26006
+ branchToWsNames.set(branch, [name]);
26672
26007
  }
26673
- });
26674
- if (selectableDiffs.length === 0) {
26675
- info(colors.yellow(`No items to deploy in the '${direction}' direction.`));
26676
- return;
26677
26008
  }
26678
- let selectedDiffs = selectableDiffs;
26679
- if (opts.all) {
26680
- selectedDiffs = selectableDiffs;
26681
- } else if (opts.skipConflicts) {
26682
- selectedDiffs = selectableDiffs.filter((d) => !(d.ahead > 0 && d.behind > 0) && (!!opts.include || !isTriggerOrScheduleKind(d.kind)));
26683
- } else if (opts.yes && !opts.include && !opts.exclude) {
26684
- selectedDiffs = selectableDiffs.filter((d) => !isTriggerOrScheduleKind(d.kind) && (direction !== "to-fork" || !(d.ahead > 0 && d.behind > 0)));
26685
- } else if (!opts.yes) {
26686
- const { Checkbox: Checkbox2 } = await init_checkbox().then(() => exports_checkbox);
26687
- const defaultForToFork = direction === "to-fork";
26688
- const selectedValues = await Checkbox2.prompt({
26689
- message: `Select items to deploy (${selectableDiffs.length} available):`,
26690
- options: selectableDiffs.map((d) => {
26691
- const isConflict = d.ahead > 0 && d.behind > 0;
26692
- const isTriggerOrSchedule = isTriggerOrScheduleKind(d.kind);
26693
- const label = `${d.kind}:${d.path}${isConflict ? colors.red(" [CONFLICT]") : ""}`;
26694
- return {
26695
- name: label,
26696
- value: `${d.kind}:${d.path}`,
26697
- checked: !isTriggerOrSchedule && (defaultForToFork ? !isConflict : true)
26698
- };
26699
- })
26700
- });
26701
- selectedDiffs = selectableDiffs.filter((d) => selectedValues.includes(`${d.kind}:${d.path}`));
26702
- }
26703
- if (opts.include) {
26704
- const includeSet = new Set(opts.include.split(",").map((s) => s.trim()));
26705
- selectedDiffs = selectedDiffs.filter((d) => includeSet.has(`${d.kind}:${d.path}`));
26706
- }
26707
- if (opts.exclude) {
26708
- const excludeSet = new Set(opts.exclude.split(",").map((s) => s.trim()));
26709
- selectedDiffs = selectedDiffs.filter((d) => !excludeSet.has(`${d.kind}:${d.path}`));
26009
+ for (const [branch, names] of branchToWsNames) {
26010
+ if (names.length > 1) {
26011
+ warn(`⚠️ WARNING: Multiple workspaces map to git branch '${branch}': ${names.join(", ")}.
26012
+ ` + ` Only the first ('${names[0]}') will be used during auto-detection. Use --workspace to select explicitly.`);
26013
+ }
26710
26014
  }
26711
- if (selectedDiffs.length === 0) {
26712
- info(colors.yellow("No items selected for deployment."));
26713
- return;
26015
+ if (currentBranch && !findWorkspaceByGitBranch(workspaces, currentBranch)) {
26016
+ const wsNames = getWorkspaceNames(workspaces);
26017
+ const availableInfo = wsNames.map((n) => {
26018
+ const entry = workspaces[n];
26019
+ const branch = getEffectiveGitBranch(n, entry);
26020
+ return branch !== n ? `${n} (gitBranch: ${branch})` : n;
26021
+ }).join(", ");
26022
+ if (!!process.stdin.isTTY) {
26023
+ info(`Current Git branch '${currentBranch}' does not match any workspace in the configuration.
26024
+ ` + `Available workspaces: ${availableInfo}`);
26025
+ const shouldCreate = opts.yes || await Confirm.prompt({
26026
+ message: `Create empty workspace configuration for branch '${currentBranch}'?`,
26027
+ default: true
26028
+ });
26029
+ if (shouldCreate) {
26030
+ if (/[\/\\:*?"<>|.]/.test(currentBranch)) {
26031
+ const sanitizedBranchName = currentBranch.replace(/[\/\\:*?"<>|.]/g, "_");
26032
+ warn(`⚠️ WARNING: Branch name "${currentBranch}" contains filesystem-unsafe characters (/ \\ : * ? " < > | .).`);
26033
+ warn(` Branch-specific files will be saved with sanitized name: "${sanitizedBranchName}"`);
26034
+ warn(` Example: "file.variable.yaml" → "file.${sanitizedBranchName}.variable.yaml"`);
26035
+ }
26036
+ const currentConfig = await readConfigFile();
26037
+ if (!currentConfig.workspaces) {
26038
+ currentConfig.workspaces = {};
26039
+ }
26040
+ currentConfig.workspaces[currentBranch] = {};
26041
+ await writeFile("wmill.yaml", import_yaml3.stringify(currentConfig), "utf-8");
26042
+ info(`✅ Created empty workspace configuration for '${currentBranch}'`);
26043
+ } else {
26044
+ warn("⚠️ WARNING: Workspace creation cancelled. You can manually add a workspace to the 'workspaces' section in wmill.yaml or use 'wmill gitsync-settings pull' to pull configuration from an existing windmill workspace git-sync configuration.");
26045
+ return;
26046
+ }
26047
+ } else {
26048
+ if (/[\/\\:*?"<>|.]/.test(currentBranch)) {
26049
+ const sanitizedBranchName = currentBranch.replace(/[\/\\:*?"<>|.]/g, "_");
26050
+ warn(`⚠️ WARNING: Branch name "${currentBranch}" contains filesystem-unsafe characters (/ \\ : * ? " < > | .).`);
26051
+ warn(` Branch-specific files will use sanitized name: "${sanitizedBranchName}"`);
26052
+ }
26053
+ warn(`⚠️ WARNING: Current Git branch '${currentBranch}' does not match any workspace in the configuration.
26054
+ ` + ` Consider adding a workspace entry for branch '${currentBranch}' in the 'workspaces' section of wmill.yaml.
26055
+ ` + ` Available workspaces: ${availableInfo}`);
26056
+ return;
26057
+ }
26714
26058
  }
26715
- const conflicts = selectedDiffs.filter((d) => d.ahead > 0 && d.behind > 0);
26716
- if (conflicts.length > 0) {
26717
- info(colors.yellow(`
26718
- ${conflicts.length} conflicting item(s) will be deployed (source will overwrite target):`));
26719
- for (const c of conflicts) {
26720
- info(colors.yellow(` - ${c.kind}:${c.path}`));
26059
+ }
26060
+ async function getEffectiveSettings(config, promotion, skipBranchValidation, suppressLogs, workspaceNameOverride) {
26061
+ const { workspaces, ...topLevelSettings } = config;
26062
+ const effective = { ...topLevelSettings };
26063
+ let resolvedWsName = null;
26064
+ let resolvedWsEntry = null;
26065
+ let originalBranchIfForked = null;
26066
+ let rawGitBranch = null;
26067
+ if (workspaceNameOverride) {
26068
+ if (workspaces && workspaces[workspaceNameOverride]) {
26069
+ resolvedWsName = workspaceNameOverride;
26070
+ resolvedWsEntry = workspaces[workspaceNameOverride];
26721
26071
  }
26722
- if (!opts.yes) {
26723
- const { Confirm: Confirm2 } = await init_confirm().then(() => exports_confirm);
26724
- const proceed = await Confirm2.prompt("Proceed with deploying conflicting items?");
26725
- if (!proceed) {
26726
- info("Aborted.");
26727
- return;
26072
+ } else if (isGitRepository()) {
26073
+ rawGitBranch = getCurrentGitBranch();
26074
+ originalBranchIfForked = getOriginalBranchForWorkspaceForks(rawGitBranch);
26075
+ const branch = originalBranchIfForked ?? rawGitBranch;
26076
+ if (originalBranchIfForked) {
26077
+ info(`Using overrides from original branch \`${originalBranchIfForked}\``);
26078
+ }
26079
+ if (branch) {
26080
+ const match = findWorkspaceByGitBranch(workspaces, branch);
26081
+ if (match) {
26082
+ [resolvedWsName, resolvedWsEntry] = match;
26728
26083
  }
26729
26084
  }
26085
+ } else {
26086
+ debug("Not in a Git repository and no workspace override provided, using top-level settings");
26730
26087
  }
26731
- info(`
26732
- Deploying ${colors.bold(String(selectedDiffs.length))} item(s)...`);
26733
- const sorted = [...selectedDiffs].sort((a, b) => {
26734
- const aFolder = a.kind === "folder" ? 0 : 1;
26735
- const bFolder = b.kind === "folder" ? 0 : 1;
26736
- return aFolder - bFolder;
26737
- });
26738
- const workspaceFrom = direction === "to-parent" ? forkWorkspaceId : parentWorkspaceId;
26739
- const workspaceTo = direction === "to-parent" ? parentWorkspaceId : forkWorkspaceId;
26740
- let successCount = 0;
26741
- let failCount = 0;
26742
- for (const diff2 of sorted) {
26743
- const label = `${diff2.kind}:${diff2.path}`;
26744
- const itemDeletedInSource = direction === "to-parent" ? diff2.exists_in_fork === false : diff2.exists_in_source === false;
26745
- let result;
26746
- if (itemDeletedInSource) {
26747
- info(colors.yellow(` ⌫ ${label} (removing from target)`));
26748
- result = await deleteItemInWorkspace(provider, diff2.kind, diff2.path, workspaceTo);
26749
- } else {
26750
- let onBehalfOf;
26751
- if (opts.preserveOnBehalfOf) {
26752
- onBehalfOf = await getOnBehalfOf(provider, diff2.kind, diff2.path, workspaceFrom);
26088
+ if (promotion && workspaces) {
26089
+ const promotionMatch = findWorkspaceByGitBranch(workspaces, promotion);
26090
+ if (promotionMatch) {
26091
+ const [, targetWs] = promotionMatch;
26092
+ if (targetWs.promotionOverrides) {
26093
+ Object.assign(effective, targetWs.promotionOverrides);
26094
+ if (!suppressLogs) {
26095
+ info(`Applied promotion settings from workspace for branch: ${promotion}`);
26096
+ }
26097
+ } else if (targetWs.overrides) {
26098
+ Object.assign(effective, targetWs.overrides);
26099
+ if (!suppressLogs) {
26100
+ info(`Applied settings from workspace for branch: ${promotion} (no promotionOverrides found)`);
26101
+ }
26102
+ } else {
26103
+ debug(`No promotion or regular overrides found for '${promotion}', using top-level settings`);
26753
26104
  }
26754
- result = await deployItem(provider, diff2.kind, diff2.path, workspaceFrom, workspaceTo, onBehalfOf);
26755
26105
  }
26756
- if (result.success) {
26757
- info(colors.green(` ✓ ${label}`));
26758
- successCount++;
26759
- } else {
26760
- info(colors.red(` ${label}: ${result.error}`));
26761
- failCount++;
26106
+ } else if (resolvedWsEntry?.overrides) {
26107
+ Object.assign(effective, resolvedWsEntry.overrides);
26108
+ if (!suppressLogs) {
26109
+ const extraLog = originalBranchIfForked ? ` (because it is the origin of the workspace fork branch \`${rawGitBranch}\`)` : "";
26110
+ info(`Applied settings for workspace '${resolvedWsName}'${extraLog}`);
26762
26111
  }
26112
+ } else if (resolvedWsName) {
26113
+ debug(`No overrides found for workspace '${resolvedWsName}', using top-level settings`);
26763
26114
  }
26764
- if (successCount > 0) {
26765
- try {
26766
- await resetDiffTally({
26767
- workspace: parentWorkspaceId,
26768
- forkWorkspaceId
26769
- });
26770
- } catch {}
26771
- }
26772
- info("");
26773
- if (failCount === 0) {
26774
- info(colors.green(`✅ Successfully deployed ${successCount} item(s) from ${workspaceFrom} to ${workspaceTo}.`));
26775
- } else {
26776
- info(colors.yellow(`Deployed ${successCount} item(s), ${colors.red(String(failCount) + " failed")} from ${workspaceFrom} to ${workspaceTo}.`));
26777
- }
26115
+ return effective;
26778
26116
  }
26779
- var provider;
26780
- var init_merge = __esm(async () => {
26781
- init_colors2();
26782
- init_mod6();
26783
- init_log();
26784
- init_client();
26785
- init_services_gen();
26786
- init_OpenAPI();
26787
- init_deploy();
26788
- await init_context();
26789
- provider = {
26790
- existsFlowByPath,
26791
- existsScriptByPath,
26792
- existsApp,
26793
- existsVariable,
26794
- existsResource,
26795
- existsResourceType,
26796
- existsFolder,
26797
- getFlowByPath,
26798
- createFlow,
26799
- updateFlow,
26800
- archiveFlowByPath,
26801
- getScriptByPath,
26802
- createScript,
26803
- archiveScriptByPath,
26804
- getAppByPath,
26805
- createApp,
26806
- updateApp,
26807
- createAppRaw,
26808
- updateAppRaw,
26809
- getPublicSecretOfLatestVersionOfApp,
26810
- getRawAppData,
26811
- deleteApp,
26812
- getVariable,
26813
- createVariable,
26814
- updateVariable,
26815
- deleteVariable,
26816
- getResource,
26817
- createResource,
26818
- updateResource,
26819
- deleteResource,
26820
- getResourceType,
26821
- createResourceType,
26822
- updateResourceType,
26823
- deleteResourceType,
26824
- getFolder,
26825
- createFolder,
26826
- updateFolder,
26827
- deleteFolder,
26828
- existsTriggerByKind: (kind, p) => triggerService(kind).exists(p),
26829
- getTriggerForDeploy: async (kind, p) => {
26830
- const trigger = await triggerService(kind).get({
26831
- workspace: p.workspace,
26832
- path: p.path
26833
- });
26834
- return preparePayload(kind, trigger, p.onBehalfOf);
26835
- },
26836
- createTriggerByKind: (kind, p) => triggerService(kind).create(p),
26837
- updateTriggerByKind: (kind, p) => triggerService(kind).update(p),
26838
- deleteTriggerByKind: (kind, p) => triggerService(kind).delete(p),
26839
- getTriggerValue: (kind, p) => triggerService(kind).get(p),
26840
- getTriggerPermissionedAs: async (kind, p) => {
26841
- const trigger = await triggerService(kind).get(p);
26842
- return trigger?.permissioned_as;
26843
- },
26844
- existsSchedule,
26845
- getSchedule,
26846
- createSchedule,
26847
- updateSchedule,
26848
- deleteSchedule
26849
- };
26850
- });
26851
-
26852
- // src/commands/workspace/slack.ts
26853
- async function connectSlack2(opts) {
26854
- await requireLogin(opts);
26855
- const workspace = await resolveWorkspace(opts);
26856
- await connectSlack({
26857
- workspace: workspace.workspaceId,
26858
- requestBody: {
26859
- bot_token: opts.botToken,
26860
- team_id: opts.teamId,
26861
- team_name: opts.teamName
26117
+ function findWorkspaceByGitBranch(workspaces, branchName) {
26118
+ if (!workspaces)
26119
+ return;
26120
+ for (const [name, entry] of Object.entries(workspaces)) {
26121
+ if (RESERVED_WORKSPACE_KEYS.has(name))
26122
+ continue;
26123
+ const effectiveBranch = entry.gitBranch ?? name;
26124
+ if (effectiveBranch === branchName) {
26125
+ return [name, entry];
26862
26126
  }
26863
- });
26864
- info(colors.bold.underline.green(`Slack connected to workspace ${workspace.workspaceId} (team ${opts.teamName} / ${opts.teamId})`));
26127
+ }
26128
+ return;
26865
26129
  }
26866
- async function disconnectSlack2(opts) {
26867
- await requireLogin(opts);
26868
- const workspace = await resolveWorkspace(opts);
26869
- await disconnectSlack({ workspace: workspace.workspaceId });
26870
- info(colors.bold.underline.green(`Slack disconnected from workspace ${workspace.workspaceId} (slack_team_id / slack_name cleared). ` + `To also remove the bot token variable/resource/folder/group, delete the corresponding files from the local sync folder and run 'wmill sync push'. ` + `To remove the workspace-level OAuth override (if any), set slack_oauth_client_id/_secret to '' in settings.yaml and push.`));
26130
+ function getEffectiveWorkspaceId(workspaceName, config) {
26131
+ return config.workspaceId ?? workspaceName;
26871
26132
  }
26872
- var init_slack = __esm(async () => {
26873
- init_colors2();
26133
+ function getEffectiveGitBranch(workspaceName, config) {
26134
+ return config.gitBranch ?? workspaceName;
26135
+ }
26136
+ function getWorkspaceNames(workspaces) {
26137
+ if (!workspaces)
26138
+ return [];
26139
+ return Object.keys(workspaces).filter((k) => !RESERVED_WORKSPACE_KEYS.has(k));
26140
+ }
26141
+ function convertGitBranchesToWorkspaces(gitBranches) {
26142
+ const workspaces = {};
26143
+ for (const [key, value] of Object.entries(gitBranches)) {
26144
+ if (key === "commonSpecificItems") {
26145
+ workspaces.commonSpecificItems = value;
26146
+ continue;
26147
+ }
26148
+ workspaces[key] = { ...value };
26149
+ }
26150
+ return workspaces;
26151
+ }
26152
+ var import_yaml3, showDiffs = false, SUPPORTED_SYNC_BEHAVIOR_VERSION = 1, GLOBAL_CONFIG_OPT, legacyConfigWarned = false, DEFAULT_SYNC_OPTIONS, RESERVED_WORKSPACE_KEYS;
26153
+ var init_conf = __esm(async () => {
26874
26154
  init_log();
26875
- init_services_gen();
26155
+ init_git();
26876
26156
  await __promiseAll([
26877
- init_auth(),
26878
- init_context()
26157
+ init_yaml(),
26158
+ init_confirm(),
26159
+ init_resource_folders()
26879
26160
  ]);
26161
+ import_yaml3 = __toESM(require_dist(), 1);
26162
+ GLOBAL_CONFIG_OPT = { noCdToRoot: false };
26163
+ DEFAULT_SYNC_OPTIONS = {
26164
+ defaultTs: "bun",
26165
+ includes: ["f/**"],
26166
+ excludes: [],
26167
+ codebases: [],
26168
+ skipVariables: false,
26169
+ skipResources: false,
26170
+ skipResourceTypes: false,
26171
+ skipSecrets: true,
26172
+ skipScripts: false,
26173
+ skipFlows: false,
26174
+ skipApps: false,
26175
+ skipFolders: false,
26176
+ includeSchedules: false,
26177
+ includeTriggers: false,
26178
+ includeUsers: false,
26179
+ includeGroups: false,
26180
+ includeSettings: false,
26181
+ includeKey: false,
26182
+ skipWorkspaceDependencies: false,
26183
+ nonDottedPaths: false,
26184
+ syncBehavior: "v1"
26185
+ };
26186
+ RESERVED_WORKSPACE_KEYS = new Set(["commonSpecificItems"]);
26880
26187
  });
26881
26188
 
26882
- // src/utils/resource_folders.ts
26883
- import { sep as SEP2 } from "node:path";
26884
- import * as fs6 from "node:fs";
26885
- import * as path2 from "node:path";
26886
- import process9 from "node:process";
26887
- function setNonDottedPaths(value) {
26888
- if (value && !_nonDottedPathsLogged) {
26889
- debug("Using non-dotted paths (__flow, __app, __raw_app)");
26890
- _nonDottedPathsLogged = true;
26189
+ // src/commands/workspace/fork.ts
26190
+ import process10 from "node:process";
26191
+ async function createWorkspaceFork2(opts, workspaceName, workspaceId = undefined) {
26192
+ if (!isGitRepository()) {
26193
+ throw new Error("You can only create forks within a git repo. Forks are tracked with git and synced to your instance with the git sync workflow.");
26891
26194
  }
26892
- _nonDottedPaths = value;
26893
- }
26894
- function getNonDottedPaths() {
26895
- return _nonDottedPaths;
26896
- }
26897
- async function loadNonDottedPathsSetting() {
26898
- let currentDir = process9.cwd();
26899
- while (true) {
26900
- const wmillYamlPath = path2.join(currentDir, "wmill.yaml");
26901
- if (fs6.existsSync(wmillYamlPath)) {
26902
- try {
26903
- const config = await yamlParseFile(wmillYamlPath);
26904
- setNonDottedPaths(config?.nonDottedPaths ?? false);
26905
- debug(`Found wmill.yaml at ${wmillYamlPath}, nonDottedPaths=${config?.nonDottedPaths ?? false}`);
26906
- } catch (e) {
26907
- debug(`Failed to parse wmill.yaml at ${wmillYamlPath}: ${e}`);
26908
- }
26909
- return;
26195
+ const currentBranch = getCurrentGitBranch();
26196
+ if (!currentBranch) {
26197
+ throw new Error("Could not get git branch name");
26198
+ }
26199
+ const config = await readConfigFile({ warnIfMissing: false });
26200
+ const originalBranchIfForked = getOriginalBranchForWorkspaceForks(currentBranch);
26201
+ const isBaseBranch = (branch) => branch === "main" || branch === "master" || findWorkspaceByGitBranch(config.workspaces, branch) !== undefined;
26202
+ let clonedBranchName;
26203
+ let renameCurrent;
26204
+ if (opts.fromBranch) {
26205
+ if (opts.fromBranch === currentBranch) {
26206
+ throw new Error(`--from-branch is for converting a *different* working branch into the fork branch, but you are already on \`${currentBranch}\`. ` + `Omit --from-branch to create a fresh fork branch with \`git checkout -b\`.`);
26910
26207
  }
26911
- const parentDir = path2.dirname(currentDir);
26912
- if (parentDir === currentDir) {
26913
- debug("No wmill.yaml found, using default dotted paths");
26914
- return;
26208
+ if (isBaseBranch(currentBranch)) {
26209
+ 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). ` + `Check out the disposable working branch you want to convert first.`);
26915
26210
  }
26916
- currentDir = parentDir;
26211
+ if (getOriginalBranchForWorkspaceForks(currentBranch)) {
26212
+ throw new Error(`Refusing to rename your current branch \`${currentBranch}\` — it is already a fork branch. ` + `To fork a fork, omit --from-branch: \`wmill workspace fork\` bases the new fork on this fork's original branch and creates a fresh fork branch without renaming.`);
26213
+ }
26214
+ if (!findWorkspaceByGitBranch(config.workspaces, opts.fromBranch)) {
26215
+ throw new Error(`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).`);
26216
+ }
26217
+ clonedBranchName = opts.fromBranch;
26218
+ renameCurrent = true;
26219
+ } else if (originalBranchIfForked) {
26220
+ 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.`);
26221
+ clonedBranchName = originalBranchIfForked;
26222
+ renameCurrent = false;
26223
+ } else if (isBaseBranch(currentBranch)) {
26224
+ clonedBranchName = currentBranch;
26225
+ renameCurrent = false;
26226
+ } else {
26227
+ clonedBranchName = await resolveWorkingBranchBase(config, opts, currentBranch);
26228
+ renameCurrent = true;
26917
26229
  }
26918
- }
26919
- function getFolderSuffixes() {
26920
- return _nonDottedPaths ? NON_DOTTED_SUFFIXES : DOTTED_SUFFIXES;
26921
- }
26922
- function getFolderSuffix(type) {
26923
- return getFolderSuffixes()[type];
26924
- }
26925
- function getMetadataFileName(type, format6) {
26926
- return METADATA_FILES[type][format6];
26927
- }
26928
- function getMetadataPathSuffix(type, format6) {
26929
- return getFolderSuffixes()[type] + "/" + METADATA_FILES[type][format6];
26930
- }
26931
- function hasWrongFormatSuffix(dirName) {
26932
- const wrongSuffixes = _nonDottedPaths ? DOTTED_SUFFIXES : NON_DOTTED_SUFFIXES;
26933
- for (const [type, suffix] of Object.entries(wrongSuffixes)) {
26934
- if (dirName.endsWith(suffix)) {
26935
- return type;
26230
+ let workspace;
26231
+ if (clonedBranchName === currentBranch) {
26232
+ workspace = await tryResolveBranchWorkspace(opts);
26233
+ } else {
26234
+ const baseMatch = findWorkspaceByGitBranch(config.workspaces, clonedBranchName);
26235
+ workspace = baseMatch ? await tryResolveBranchWorkspace(opts, baseMatch[0]) : await tryResolveBranchWorkspace(opts);
26236
+ }
26237
+ if (!workspace) {
26238
+ throw new Error("Could not resolve workspace from branch name. Make sure you are in a git repo to use workspace forks");
26239
+ }
26240
+ info(`You are forking workspace (${workspace.workspaceId})`);
26241
+ if (opts.workspace) {
26242
+ info(colors.red.bold("! Workspace needs to be specified as positional argument, not as option."));
26243
+ return;
26244
+ }
26245
+ const branchDefaultId = renameCurrent ? branchToForkId(currentBranch) : undefined;
26246
+ const interactive = process10.stdin.isTTY && opts.yes !== true;
26247
+ if (workspaceName === undefined) {
26248
+ if (branchDefaultId && !interactive) {
26249
+ workspaceName = branchDefaultId;
26250
+ info(`Naming the fork after the current branch: \`${workspaceName}\``);
26251
+ } else {
26252
+ workspaceName = await Input.prompt({
26253
+ message: "Name this forked workspace:",
26254
+ default: branchDefaultId
26255
+ });
26936
26256
  }
26937
26257
  }
26938
- return null;
26939
- }
26940
- function normalizeSep(p) {
26941
- return p.replaceAll("\\", "/");
26942
- }
26943
- function isFlowPath(p) {
26944
- return normalizeSep(p).includes(getFolderSuffixes().flow + "/");
26945
- }
26946
- function isAppPath(p) {
26947
- return normalizeSep(p).includes(getFolderSuffixes().app + "/");
26948
- }
26949
- function isRawAppPath(p) {
26950
- return normalizeSep(p).includes(getFolderSuffixes().raw_app + "/");
26951
- }
26952
- function isFolderResourcePathAnyFormat(p) {
26953
- const n = normalizeSep(p);
26954
- for (const suffixes of [DOTTED_SUFFIXES, NON_DOTTED_SUFFIXES]) {
26955
- if (n.includes(suffixes.flow + "/") || n.includes(suffixes.app + "/") || n.includes(suffixes.raw_app + "/")) {
26956
- return true;
26258
+ if (!workspaceId) {
26259
+ const idDefault = branchToForkId(workspaceName);
26260
+ if (branchDefaultId && !interactive) {
26261
+ workspaceId = idDefault;
26262
+ } else {
26263
+ workspaceId = await Input.prompt({
26264
+ message: `Enter the ID of this forked workspace, it will then be prefixed by ${WM_FORK_PREFIX}. It will also determine the branch name`,
26265
+ default: idDefault,
26266
+ suggestions: [idDefault]
26267
+ });
26957
26268
  }
26958
26269
  }
26959
- return false;
26960
- }
26961
- function isAppInlineScriptPath(filePath) {
26962
- const suffixes = getFolderSuffixes();
26963
- const normalizedPath = filePath.replaceAll(SEP2, "/");
26964
- const escapedSuffix = suffixes.app.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
26965
- const pattern = new RegExp(`${escapedSuffix}/`);
26966
- return pattern.test(normalizedPath);
26967
- }
26968
- function isFlowInlineScriptPath(filePath) {
26969
- const suffixes = getFolderSuffixes();
26970
- const normalizedPath = filePath.replaceAll(SEP2, "/");
26971
- const escapedSuffix = suffixes.flow.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
26972
- const pattern = new RegExp(`${escapedSuffix}/`);
26973
- return pattern.test(normalizedPath);
26974
- }
26975
- function extractResourceName(p, type) {
26976
- const normalized = normalizeSep(p);
26977
- const suffix = getFolderSuffixes()[type] + "/";
26978
- const index = normalized.indexOf(suffix);
26979
- if (index === -1)
26980
- return null;
26981
- return normalized.substring(0, index);
26982
- }
26983
- function extractFolderPath(p, type) {
26984
- const normalized = normalizeSep(p);
26985
- const suffix = getFolderSuffixes()[type] + "/";
26986
- const index = normalized.indexOf(suffix);
26987
- if (index === -1)
26988
- return null;
26989
- return normalized.substring(0, index) + suffix;
26990
- }
26991
- function buildFolderPath(resourceName, type) {
26992
- return resourceName + getFolderSuffixes()[type];
26993
- }
26994
- function hasFolderSuffix(dirName, type) {
26995
- return dirName.endsWith(getFolderSuffixes()[type]);
26996
- }
26997
- function extractNameFromFolder(folderName2, type) {
26998
- const suffix = getFolderSuffixes()[type];
26999
- if (folderName2.endsWith(suffix)) {
27000
- return folderName2.substring(0, folderName2.length - suffix.length);
26270
+ const token = workspace.token;
26271
+ if (!token) {
26272
+ throw new Error("Not logged in. Please run 'wmill workspace add' first.");
26273
+ }
26274
+ const remote = workspace.remote;
26275
+ setClient(token, remote.endsWith("/") ? remote.substring(0, remote.length - 1) : remote);
26276
+ info(colors.blue(`Creating forked workspace: ${workspaceName}...`));
26277
+ const trueWorkspaceId = `${WM_FORK_PREFIX}-${workspaceId}`;
26278
+ validateForkWorkspaceId(trueWorkspaceId);
26279
+ let alreadyExists = false;
26280
+ try {
26281
+ alreadyExists = await existsWorkspace({
26282
+ requestBody: { id: trueWorkspaceId }
26283
+ });
26284
+ } catch (e) {
26285
+ info(colors.red.bold("! Credentials or instance is invalid. Aborting."));
26286
+ throw e;
26287
+ }
26288
+ if (alreadyExists) {
26289
+ throw new Error(`This forked workspace '${workspaceId}' (${workspaceName}) already exists, possibly archived (archiving keeps the id reserved). ` + `Permanently delete it with \`wmill workspace delete-fork ${workspaceId}\` to reuse the id, or choose a different id`);
26290
+ }
26291
+ const forkedDatatables = [];
26292
+ let datatables = [];
26293
+ try {
26294
+ datatables = await listDataTables({
26295
+ workspace: workspace.workspaceId
26296
+ });
26297
+ } catch (e) {
26298
+ info(colors.yellow(`Note: Could not list datatables: ${e.message}`));
26299
+ }
26300
+ if (datatables && datatables.length > 0) {
26301
+ const behavior = opts.datatableBehavior ?? (opts.yes ? "skip" : undefined);
26302
+ if (behavior !== "skip") {
26303
+ info(`
26304
+ Found ${datatables.length} datatable(s):`);
26305
+ for (const dt of datatables) {
26306
+ let dtBehavior;
26307
+ if (behavior === "schema_only" || behavior === "schema_and_data") {
26308
+ dtBehavior = behavior;
26309
+ } else {
26310
+ const { Select: Select2 } = await init_select().then(() => exports_select);
26311
+ dtBehavior = await Select2.prompt({
26312
+ message: `Datatable "${dt.name}" (${dt.resource_type}):`,
26313
+ options: [
26314
+ { name: "Keep original (no cloning)", value: "keep_original" },
26315
+ { name: "Clone schema only", value: "schema_only" },
26316
+ { name: "Clone schema and data", value: "schema_and_data" }
26317
+ ]
26318
+ });
26319
+ }
26320
+ if (dtBehavior === "keep_original") {
26321
+ continue;
26322
+ }
26323
+ const newDbName = `${trueWorkspaceId.replace(/-/g, "_")}__${dt.name}`;
26324
+ try {
26325
+ info(colors.blue(` Creating database "${newDbName}" for datatable "${dt.name}"...`));
26326
+ await createPgDatabase({
26327
+ workspace: workspace.workspaceId,
26328
+ requestBody: {
26329
+ source: `datatable://${dt.name}`,
26330
+ target_dbname: newDbName
26331
+ }
26332
+ });
26333
+ info(colors.blue(` Importing ${dtBehavior === "schema_only" ? "schema" : "schema + data"}...`));
26334
+ await importPgDatabase({
26335
+ workspace: workspace.workspaceId,
26336
+ requestBody: {
26337
+ source: `datatable://${dt.name}`,
26338
+ target: `datatable://${dt.name}`,
26339
+ target_dbname_override: newDbName,
26340
+ fork_behavior: dtBehavior
26341
+ }
26342
+ });
26343
+ info(colors.green(` ✓ Datatable "${dt.name}" cloned.`));
26344
+ forkedDatatables.push({ name: dt.name, new_dbname: newDbName });
26345
+ } catch (e) {
26346
+ info(colors.yellow(` ✗ Failed to clone datatable "${dt.name}": ${e.message}`));
26347
+ }
26348
+ }
26349
+ }
27001
26350
  }
27002
- return folderName2;
27003
- }
27004
- function isFlowMetadataFile(p) {
27005
- if (p.endsWith(DOTTED_SUFFIXES.flow + ".json") || p.endsWith(DOTTED_SUFFIXES.flow + ".yaml")) {
27006
- return true;
26351
+ const forkColor = opts.color;
26352
+ try {
26353
+ const gitSyncJobIds = await createWorkspaceForkGitBranch({
26354
+ workspace: workspace.workspaceId,
26355
+ requestBody: {
26356
+ id: trueWorkspaceId,
26357
+ name: opts.createWorkspaceName ?? workspaceName ?? trueWorkspaceId,
26358
+ color: forkColor
26359
+ }
26360
+ });
26361
+ if (gitSyncJobIds && gitSyncJobIds.length > 0) {
26362
+ info(colors.blue(`Git sync branch creation triggered (${gitSyncJobIds.length} job(s)). These will complete asynchronously.`));
26363
+ }
26364
+ } catch (e) {
26365
+ error(colors.red(`Failed to create git branch for fork: ${e.message}`));
26366
+ throw e;
27007
26367
  }
27008
- if (_nonDottedPaths) {
27009
- return p.endsWith(NON_DOTTED_SUFFIXES.flow + ".json") || p.endsWith(NON_DOTTED_SUFFIXES.flow + ".yaml");
26368
+ try {
26369
+ const result = await createWorkspaceFork({
26370
+ workspace: workspace.workspaceId,
26371
+ requestBody: {
26372
+ id: trueWorkspaceId,
26373
+ name: opts.createWorkspaceName ?? workspaceName ?? trueWorkspaceId,
26374
+ color: forkColor,
26375
+ forked_datatables: forkedDatatables
26376
+ }
26377
+ });
26378
+ info(colors.green(`✅ ${result}`));
26379
+ } catch (error2) {
26380
+ error(colors.red(`Failed to create forked workspace: ${error2.message}`));
26381
+ throw error2;
27010
26382
  }
27011
- return false;
26383
+ const newBranchName = `${WM_FORK_PREFIX}/${clonedBranchName}/${workspaceId}`;
26384
+ let onForkBranch = false;
26385
+ if (renameCurrent) {
26386
+ if (currentBranch === newBranchName) {
26387
+ onForkBranch = true;
26388
+ info(colors.green(`Your current branch is already \`${newBranchName}\`.`));
26389
+ } else if (gitBranchExists(newBranchName)) {
26390
+ warn(`Branch \`${newBranchName}\` already exists locally, so the current branch \`${currentBranch}\` was not renamed. Check out the fork branch yourself (e.g. \`git checkout ${newBranchName}\`).`);
26391
+ } else {
26392
+ renameCurrentGitBranch(newBranchName);
26393
+ onForkBranch = true;
26394
+ info(colors.green(`Renamed \`${currentBranch}\` → \`${newBranchName}\`. Your existing commits are now on the fork branch.`));
26395
+ }
26396
+ }
26397
+ const checkoutHint = onForkBranch ? `Created forked workspace ${trueWorkspaceId}. You are on the fork branch \`${newBranchName}\` — push it to sync your fork.` : `Created forked workspace ${trueWorkspaceId}. To start contributing to your fork, create and push edits to the branch \`${newBranchName}\` by using the command:
26398
+
26399
+ ` + colors.white(`git checkout -b ${newBranchName}`);
26400
+ info(`${checkoutHint}
26401
+
26402
+ When doing operations on the forked workspace, it will use the remote setup in the workspaces section for the branch it was forked from.
26403
+
26404
+ To merge changes back to the parent workspace, you can:
26405
+ - Use the CLI: ` + colors.white(`git checkout ${newBranchName} && wmill workspace merge`) + `
26406
+ - Use the Merge UI from the forked workspace home page
26407
+ - Use git: ` + colors.white(`git checkout ${clonedBranchName} && git merge ${newBranchName} && wmill sync push`) + `
26408
+ See: https://www.windmill.dev/docs/advanced/workspace_forks`);
27012
26409
  }
27013
- function isAppMetadataFile(p) {
27014
- if (p.endsWith(DOTTED_SUFFIXES.app + ".json") || p.endsWith(DOTTED_SUFFIXES.app + ".yaml")) {
27015
- return true;
26410
+ async function resolveWorkingBranchBase(config, opts, currentBranch) {
26411
+ const interactive = process10.stdin.isTTY && opts.yes !== true;
26412
+ if (!interactive) {
26413
+ 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.`);
27016
26414
  }
27017
- if (_nonDottedPaths) {
27018
- return p.endsWith(NON_DOTTED_SUFFIXES.app + ".json") || p.endsWith(NON_DOTTED_SUFFIXES.app + ".yaml");
26415
+ const { Select: Select2 } = await init_select().then(() => exports_select);
26416
+ const proceed = await Select2.prompt({
26417
+ message: `You're on working branch \`${currentBranch}\`, not a base branch. Base a fork on it and rename it onto the fork branch?`,
26418
+ options: [
26419
+ { name: "Yes, base the fork on this branch and rename it", value: "yes" },
26420
+ { name: "No, cancel", value: "no" }
26421
+ ]
26422
+ });
26423
+ if (proceed !== "yes") {
26424
+ throw new Error("Fork cancelled. Check out a base branch to create a fresh fork branch instead.");
26425
+ }
26426
+ const baseBranches = listConfiguredBaseBranches(config);
26427
+ if (baseBranches.length === 0) {
26428
+ 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>.`);
26429
+ }
26430
+ if (baseBranches.length === 1) {
26431
+ info(`Basing the fork on \`${baseBranches[0]}\`.`);
26432
+ return baseBranches[0];
26433
+ }
26434
+ return await Select2.prompt({
26435
+ message: "Which base branch is this fork based on (the parent)?",
26436
+ options: baseBranches.map((b) => ({ name: b, value: b }))
26437
+ });
26438
+ }
26439
+ function branchToForkId(branch) {
26440
+ const slug = branch.replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, MAX_FORK_ID_SLUG).replace(/-+$/g, "");
26441
+ return slug || "fork";
26442
+ }
26443
+ function validateForkWorkspaceId(id) {
26444
+ const reject = (reason) => {
26445
+ throw new Error(`Fork workspace id \`${id}\` is invalid: ${reason}. Choose a shorter or simpler name/id.`);
26446
+ };
26447
+ if (id.length > 50) {
26448
+ reject(`too long (${id.length} chars; max 50 including the \`${WM_FORK_PREFIX}-\` prefix)`);
26449
+ }
26450
+ if (id.endsWith("."))
26451
+ reject("cannot end with '.'");
26452
+ if (id.endsWith(".lock"))
26453
+ reject("cannot end with '.lock'");
26454
+ if (id.includes(".."))
26455
+ reject("cannot contain '..'");
26456
+ if (id.includes("@{"))
26457
+ reject("cannot contain '@{'");
26458
+ if (id.includes("//"))
26459
+ reject("cannot contain '//'");
26460
+ for (const ch of id) {
26461
+ if (":~^?*[\\ ".includes(ch))
26462
+ reject(`contains forbidden character '${ch}'`);
26463
+ const code2 = ch.charCodeAt(0);
26464
+ if (code2 < 32 || code2 === 127)
26465
+ reject("contains a control character");
26466
+ }
26467
+ for (const component of id.split("/")) {
26468
+ if (component.startsWith("."))
26469
+ reject("a path component cannot start with '.'");
26470
+ if (component.endsWith(".lock"))
26471
+ reject("a path component cannot end with '.lock'");
26472
+ }
26473
+ }
26474
+ function listConfiguredBaseBranches(config) {
26475
+ const workspaces = config.workspaces;
26476
+ const branches = new Set;
26477
+ for (const name of getWorkspaceNames(workspaces)) {
26478
+ branches.add(getEffectiveGitBranch(name, workspaces[name]));
27019
26479
  }
27020
- return false;
26480
+ return [...branches];
27021
26481
  }
27022
- function isRawAppMetadataFile(p) {
27023
- if (p.endsWith(DOTTED_SUFFIXES.raw_app + ".json") || p.endsWith(DOTTED_SUFFIXES.raw_app + ".yaml")) {
27024
- return true;
26482
+ async function deleteWorkspaceFork(opts, name) {
26483
+ let forkWorkspaceId;
26484
+ let token;
26485
+ let remote;
26486
+ let hasLocalProfile = false;
26487
+ const orgWorkspaces = await allWorkspaces(opts.configDir);
26488
+ const idxOf = orgWorkspaces.findIndex((x) => x.name === name);
26489
+ if (idxOf !== -1) {
26490
+ const workspace = orgWorkspaces[idxOf];
26491
+ if (!workspace.workspaceId.startsWith(WM_FORK_PREFIX)) {
26492
+ throw new Error(`You can only delete forked workspaces where the workspace id starts with \`${WM_FORK_PREFIX}.\` Failed while attempting to delete \`${workspace.workspaceId}\``);
26493
+ }
26494
+ forkWorkspaceId = workspace.workspaceId;
26495
+ token = workspace.token;
26496
+ remote = workspace.remote;
26497
+ hasLocalProfile = true;
26498
+ } else {
26499
+ const parentWorkspace = await tryResolveBranchWorkspace(opts);
26500
+ if (!parentWorkspace) {
26501
+ throw new Error("Could not resolve parent workspace. Make sure you are in a git repo with 'workspaces' configured in wmill.yaml, or create a local workspace profile for the fork.");
26502
+ }
26503
+ forkWorkspaceId = name.startsWith(`${WM_FORK_PREFIX}-`) ? name : `${WM_FORK_PREFIX}-${name}`;
26504
+ token = parentWorkspace.token;
26505
+ remote = parentWorkspace.remote;
27025
26506
  }
27026
- if (_nonDottedPaths) {
27027
- return p.endsWith(NON_DOTTED_SUFFIXES.raw_app + ".json") || p.endsWith(NON_DOTTED_SUFFIXES.raw_app + ".yaml");
26507
+ if (!opts.yes) {
26508
+ const { Select: Select2 } = await init_select().then(() => exports_select);
26509
+ const choice = await Select2.prompt({
26510
+ message: `Are you sure you want to delete the forked workspace \`${forkWorkspaceId}\`?`,
26511
+ options: [
26512
+ { name: "Yes", value: "confirm" },
26513
+ { name: "No", value: "cancel" }
26514
+ ]
26515
+ });
26516
+ if (choice === "cancel") {
26517
+ info("Operation cancelled");
26518
+ return;
26519
+ }
26520
+ }
26521
+ setClient(token, remote.endsWith("/") ? remote.substring(0, remote.length - 1) : remote);
26522
+ const result = await deleteWorkspace({
26523
+ workspace: forkWorkspaceId
26524
+ });
26525
+ info(colors.green(`✅ Forked workspace '${forkWorkspaceId}' deleted successfully!
26526
+ ${result}`));
26527
+ if (hasLocalProfile) {
26528
+ await removeWorkspace(name, false, opts);
27028
26529
  }
27029
- return false;
27030
26530
  }
27031
- function isRawAppFolderMetadataFile(p) {
27032
- return p.endsWith(getMetadataPathSuffix("raw_app", "yaml")) || p.endsWith(getMetadataPathSuffix("raw_app", "json"));
26531
+ var MAX_FORK_ID_SLUG = 42;
26532
+ var init_fork = __esm(async () => {
26533
+ init_colors2();
26534
+ init_log();
26535
+ init_client();
26536
+ init_services_gen();
26537
+ init_git();
26538
+ await __promiseAll([
26539
+ init_input(),
26540
+ init_workspace(),
26541
+ init_context(),
26542
+ init_conf()
26543
+ ]);
26544
+ });
26545
+
26546
+ // windmill-utils-internal/src/deploy.ts
26547
+ function isTriggerKind(kind) {
26548
+ return TRIGGER_KINDS.includes(kind);
27033
26549
  }
27034
- function isAppFolderMetadataFile(p) {
27035
- return p.endsWith(getMetadataPathSuffix("app", "yaml")) || p.endsWith(getMetadataPathSuffix("app", "json"));
26550
+ function isTriggerOrScheduleKind(kind) {
26551
+ return kind === "schedule" || isTriggerKind(kind);
27036
26552
  }
27037
- function isFlowFolderMetadataFile(p) {
27038
- return p.endsWith(getMetadataPathSuffix("flow", "yaml")) || p.endsWith(getMetadataPathSuffix("flow", "json"));
26553
+ function folderName(path3) {
26554
+ return path3.replace(/^f\//, "");
27039
26555
  }
27040
- function getModuleFolderSuffix() {
27041
- return MODULE_SUFFIX;
26556
+ function stripOperationalStateOnUpdate(payload, alreadyExists) {
26557
+ if (!alreadyExists)
26558
+ return payload;
26559
+ const { mode: _mode, enabled: _enabled, ...rest } = payload;
26560
+ return rest;
27042
26561
  }
27043
- function isScriptModulePath(p) {
27044
- return normalizeSep(p).includes(MODULE_SUFFIX + "/");
26562
+ function getSubModules(flowModule) {
26563
+ const type = flowModule?.value?.type;
26564
+ if (type === "forloopflow" || type === "whileloopflow") {
26565
+ return [flowModule.value.modules ?? []];
26566
+ } else if (type === "branchall") {
26567
+ return (flowModule.value.branches ?? []).map((branch) => branch.modules ?? []);
26568
+ } else if (type === "branchone") {
26569
+ return [
26570
+ ...(flowModule.value.branches ?? []).map((b) => b.modules ?? []),
26571
+ flowModule.value.default ?? []
26572
+ ];
26573
+ } else if (type === "aiagent") {
26574
+ if (flowModule.value.tools) {
26575
+ return [
26576
+ flowModule.value.tools.filter((t) => t.value?.type === "script" || t.value?.type === "flow").map((t) => ({
26577
+ id: t.id,
26578
+ value: t.value,
26579
+ summary: t.summary
26580
+ }))
26581
+ ];
26582
+ }
26583
+ }
26584
+ return [];
27045
26585
  }
27046
- function isModuleEntryPoint(p) {
27047
- const norm = normalizeSep(p);
27048
- const suffix = MODULE_SUFFIX + "/";
27049
- const idx = norm.indexOf(suffix);
27050
- if (idx === -1)
27051
- return false;
27052
- const rest = norm.slice(idx + suffix.length);
27053
- return rest.startsWith("script.") && !rest.includes("/");
26586
+ function getAllSubmodules(flowModule) {
26587
+ return getSubModules(flowModule).map((modules) => modules.flatMap((m) => [m, ...getAllSubmodules(m)])).flat();
27054
26588
  }
27055
- function getScriptBasePathFromModulePath(p) {
27056
- const norm = normalizeSep(p);
27057
- const suffix = MODULE_SUFFIX + "/";
27058
- const idx = norm.indexOf(suffix);
27059
- if (idx === -1)
27060
- return;
27061
- return norm.slice(0, idx);
26589
+ function getAllModules(flowModules, failureModule) {
26590
+ return [
26591
+ ...flowModules,
26592
+ ...flowModules.flatMap((x) => getAllSubmodules(x)),
26593
+ ...failureModule ? [failureModule] : []
26594
+ ];
27062
26595
  }
27063
- function scriptPathToRemotePath(p) {
27064
- return (isModuleEntryPoint(p) ? getScriptBasePathFromModulePath(p) : p.substring(0, p.indexOf("."))).replaceAll(SEP2, "/");
26596
+ function toError(e) {
26597
+ const err = e;
26598
+ return err.body || err.message || String(e);
27065
26599
  }
27066
- function getDeleteSuffix(type, format6) {
27067
- return getFolderSuffixes()[type] + "/" + METADATA_FILES[type][format6];
26600
+ async function checkItemExists(provider, kind, path3, workspace) {
26601
+ if (kind === "flow") {
26602
+ return provider.existsFlowByPath({ workspace, path: path3 });
26603
+ } else if (kind === "script") {
26604
+ return provider.existsScriptByPath({ workspace, path: path3 });
26605
+ } else if (kind === "app" || kind === "raw_app") {
26606
+ return provider.existsApp({ workspace, path: path3 });
26607
+ } else if (kind === "variable") {
26608
+ return provider.existsVariable({ workspace, path: path3 });
26609
+ } else if (kind === "resource") {
26610
+ return provider.existsResource({ workspace, path: path3 });
26611
+ } else if (kind === "resource_type") {
26612
+ return provider.existsResourceType({ workspace, path: path3 });
26613
+ } else if (kind === "folder") {
26614
+ return provider.existsFolder({ workspace, name: folderName(path3) });
26615
+ } else if (kind === "schedule") {
26616
+ return provider.existsSchedule({ workspace, path: path3 });
26617
+ } else if (isTriggerKind(kind)) {
26618
+ return provider.existsTriggerByKind(kind, { workspace, path: path3 });
26619
+ }
26620
+ throw new Error(`Unknown kind: ${kind}`);
27068
26621
  }
27069
- function transformJsonPathToDir(p, type) {
27070
- const apiSuffix = DOTTED_SUFFIXES[type] + ".json";
27071
- if (p.endsWith(apiSuffix)) {
27072
- const basePath = p.substring(0, p.length - apiSuffix.length);
27073
- return basePath + getFolderSuffixes()[type];
26622
+ async function deployItem(provider, kind, path3, workspaceFrom, workspaceTo, onBehalfOf) {
26623
+ const preserveOnBehalfOf = onBehalfOf !== undefined;
26624
+ try {
26625
+ const alreadyExists = await checkItemExists(provider, kind, path3, workspaceTo);
26626
+ if (kind === "flow") {
26627
+ const flow = await provider.getFlowByPath({
26628
+ workspace: workspaceFrom,
26629
+ path: path3
26630
+ });
26631
+ getAllModules(flow.value?.modules ?? [], flow.value?.failure_module).forEach((x) => {
26632
+ if (x.value?.type === "script" && x.value.hash != null) {
26633
+ x.value.hash = undefined;
26634
+ }
26635
+ });
26636
+ if (alreadyExists) {
26637
+ await provider.updateFlow({
26638
+ workspace: workspaceTo,
26639
+ path: path3,
26640
+ requestBody: {
26641
+ ...flow,
26642
+ preserve_on_behalf_of: preserveOnBehalfOf,
26643
+ on_behalf_of_email: onBehalfOf
26644
+ }
26645
+ });
26646
+ } else {
26647
+ await provider.createFlow({
26648
+ workspace: workspaceTo,
26649
+ requestBody: {
26650
+ ...flow,
26651
+ preserve_on_behalf_of: preserveOnBehalfOf,
26652
+ on_behalf_of_email: onBehalfOf
26653
+ }
26654
+ });
26655
+ }
26656
+ } else if (kind === "script") {
26657
+ const script = await provider.getScriptByPath({
26658
+ workspace: workspaceFrom,
26659
+ path: path3
26660
+ });
26661
+ let parentHash;
26662
+ if (alreadyExists) {
26663
+ const existing = await provider.getScriptByPath({
26664
+ workspace: workspaceTo,
26665
+ path: path3
26666
+ });
26667
+ parentHash = existing.hash;
26668
+ }
26669
+ await provider.createScript({
26670
+ workspace: workspaceTo,
26671
+ requestBody: {
26672
+ ...script,
26673
+ lock: script.lock,
26674
+ parent_hash: parentHash,
26675
+ preserve_on_behalf_of: preserveOnBehalfOf,
26676
+ on_behalf_of_email: onBehalfOf
26677
+ }
26678
+ });
26679
+ } else if (kind === "app" || kind === "raw_app") {
26680
+ const app = await provider.getAppByPath({
26681
+ workspace: workspaceFrom,
26682
+ path: path3
26683
+ });
26684
+ if (alreadyExists) {
26685
+ if (app.raw_app) {
26686
+ const secret = await provider.getPublicSecretOfLatestVersionOfApp({
26687
+ workspace: workspaceFrom,
26688
+ path: app.path
26689
+ });
26690
+ const js = await provider.getRawAppData({
26691
+ secretWithExtension: `${secret}.js`,
26692
+ workspace: workspaceFrom
26693
+ });
26694
+ const css = await provider.getRawAppData({
26695
+ secretWithExtension: `${secret}.css`,
26696
+ workspace: workspaceFrom
26697
+ });
26698
+ await provider.updateAppRaw({
26699
+ workspace: workspaceTo,
26700
+ path: path3,
26701
+ formData: {
26702
+ app: { ...app, preserve_on_behalf_of: preserveOnBehalfOf },
26703
+ css,
26704
+ js
26705
+ }
26706
+ });
26707
+ } else {
26708
+ await provider.updateApp({
26709
+ workspace: workspaceTo,
26710
+ path: path3,
26711
+ requestBody: {
26712
+ ...app,
26713
+ preserve_on_behalf_of: preserveOnBehalfOf
26714
+ }
26715
+ });
26716
+ }
26717
+ } else {
26718
+ if (app.raw_app) {
26719
+ const secret = await provider.getPublicSecretOfLatestVersionOfApp({
26720
+ workspace: workspaceFrom,
26721
+ path: app.path
26722
+ });
26723
+ const js = await provider.getRawAppData({
26724
+ secretWithExtension: `${secret}.js`,
26725
+ workspace: workspaceFrom
26726
+ });
26727
+ const css = await provider.getRawAppData({
26728
+ secretWithExtension: `${secret}.css`,
26729
+ workspace: workspaceFrom
26730
+ });
26731
+ await provider.createAppRaw({
26732
+ workspace: workspaceTo,
26733
+ formData: {
26734
+ app: { ...app, preserve_on_behalf_of: preserveOnBehalfOf },
26735
+ css,
26736
+ js
26737
+ }
26738
+ });
26739
+ } else {
26740
+ await provider.createApp({
26741
+ workspace: workspaceTo,
26742
+ requestBody: {
26743
+ ...app,
26744
+ preserve_on_behalf_of: preserveOnBehalfOf
26745
+ }
26746
+ });
26747
+ }
26748
+ }
26749
+ } else if (kind === "variable") {
26750
+ const variable = await provider.getVariable({
26751
+ workspace: workspaceFrom,
26752
+ path: path3,
26753
+ decryptSecret: true
26754
+ });
26755
+ if (alreadyExists) {
26756
+ await provider.updateVariable({
26757
+ workspace: workspaceTo,
26758
+ path: path3,
26759
+ requestBody: {
26760
+ path: path3,
26761
+ value: variable.value ?? "",
26762
+ is_secret: variable.is_secret,
26763
+ description: variable.description ?? ""
26764
+ },
26765
+ alreadyEncrypted: false
26766
+ });
26767
+ } else {
26768
+ await provider.createVariable({
26769
+ workspace: workspaceTo,
26770
+ requestBody: {
26771
+ path: path3,
26772
+ value: variable.value ?? "",
26773
+ is_secret: variable.is_secret,
26774
+ description: variable.description ?? ""
26775
+ }
26776
+ });
26777
+ }
26778
+ } else if (kind === "resource") {
26779
+ const resource = await provider.getResource({
26780
+ workspace: workspaceFrom,
26781
+ path: path3
26782
+ });
26783
+ if (alreadyExists) {
26784
+ await provider.updateResource({
26785
+ workspace: workspaceTo,
26786
+ path: path3,
26787
+ requestBody: {
26788
+ path: path3,
26789
+ value: resource.value ?? "",
26790
+ description: resource.description ?? ""
26791
+ }
26792
+ });
26793
+ } else {
26794
+ await provider.createResource({
26795
+ workspace: workspaceTo,
26796
+ requestBody: {
26797
+ path: path3,
26798
+ value: resource.value ?? "",
26799
+ resource_type: resource.resource_type,
26800
+ description: resource.description ?? ""
26801
+ }
26802
+ });
26803
+ }
26804
+ } else if (kind === "resource_type") {
26805
+ const rt = await provider.getResourceType({
26806
+ workspace: workspaceFrom,
26807
+ path: path3
26808
+ });
26809
+ if (alreadyExists) {
26810
+ await provider.updateResourceType({
26811
+ workspace: workspaceTo,
26812
+ path: path3,
26813
+ requestBody: {
26814
+ schema: rt.schema,
26815
+ description: rt.description ?? ""
26816
+ }
26817
+ });
26818
+ } else {
26819
+ await provider.createResourceType({
26820
+ workspace: workspaceTo,
26821
+ requestBody: {
26822
+ name: rt.name,
26823
+ schema: rt.schema,
26824
+ description: rt.description ?? ""
26825
+ }
26826
+ });
26827
+ }
26828
+ } else if (kind === "folder") {
26829
+ const name = folderName(path3);
26830
+ const folder = await provider.getFolder({
26831
+ workspace: workspaceFrom,
26832
+ name
26833
+ });
26834
+ if (alreadyExists) {
26835
+ await provider.updateFolder({
26836
+ workspace: workspaceTo,
26837
+ name,
26838
+ requestBody: {
26839
+ owners: folder.owners,
26840
+ extra_perms: folder.extra_perms,
26841
+ summary: folder.summary ?? undefined
26842
+ }
26843
+ });
26844
+ } else {
26845
+ await provider.createFolder({
26846
+ workspace: workspaceTo,
26847
+ requestBody: {
26848
+ name,
26849
+ owners: folder.owners,
26850
+ extra_perms: folder.extra_perms,
26851
+ summary: folder.summary ?? undefined
26852
+ }
26853
+ });
26854
+ }
26855
+ } else if (kind === "schedule") {
26856
+ const schedule = await provider.getSchedule({
26857
+ workspace: workspaceFrom,
26858
+ path: path3
26859
+ });
26860
+ const baseBody = stripOperationalStateOnUpdate(schedule, alreadyExists);
26861
+ const requestBody = {
26862
+ ...baseBody,
26863
+ permissioned_as: onBehalfOf,
26864
+ preserve_permissioned_as: preserveOnBehalfOf
26865
+ };
26866
+ if (alreadyExists) {
26867
+ await provider.updateSchedule({
26868
+ workspace: workspaceTo,
26869
+ path: path3,
26870
+ requestBody
26871
+ });
26872
+ } else {
26873
+ await provider.createSchedule({
26874
+ workspace: workspaceTo,
26875
+ requestBody
26876
+ });
26877
+ }
26878
+ } else if (isTriggerKind(kind)) {
26879
+ const triggerBody = await provider.getTriggerForDeploy(kind, {
26880
+ workspace: workspaceFrom,
26881
+ path: path3,
26882
+ onBehalfOf
26883
+ });
26884
+ const requestBody = stripOperationalStateOnUpdate(triggerBody, alreadyExists);
26885
+ if (alreadyExists) {
26886
+ await provider.updateTriggerByKind(kind, {
26887
+ workspace: workspaceTo,
26888
+ path: path3,
26889
+ requestBody
26890
+ });
26891
+ } else {
26892
+ await provider.createTriggerByKind(kind, {
26893
+ workspace: workspaceTo,
26894
+ requestBody
26895
+ });
26896
+ }
26897
+ } else {
26898
+ throw new Error(`Unknown kind: ${kind}`);
26899
+ }
26900
+ return { success: true };
26901
+ } catch (e) {
26902
+ return { success: false, error: toError(e) };
27074
26903
  }
27075
- const userSuffix = getFolderSuffixes()[type] + ".json";
27076
- if (p.endsWith(userSuffix)) {
27077
- return p.substring(0, p.length - 5);
26904
+ }
26905
+ async function deleteItemInWorkspace(provider, kind, path3, workspace) {
26906
+ try {
26907
+ if (kind === "script") {
26908
+ await provider.archiveScriptByPath({ workspace, path: path3 });
26909
+ } else if (kind === "flow") {
26910
+ await provider.archiveFlowByPath({
26911
+ workspace,
26912
+ path: path3,
26913
+ requestBody: { archived: true }
26914
+ });
26915
+ } else if (kind === "app" || kind === "raw_app") {
26916
+ await provider.deleteApp({ workspace, path: path3 });
26917
+ } else if (kind === "variable") {
26918
+ await provider.deleteVariable({ workspace, path: path3 });
26919
+ } else if (kind === "resource") {
26920
+ await provider.deleteResource({ workspace, path: path3 });
26921
+ } else if (kind === "resource_type") {
26922
+ await provider.deleteResourceType({ workspace, path: path3 });
26923
+ } else if (kind === "folder") {
26924
+ await provider.deleteFolder({ workspace, name: folderName(path3) });
26925
+ } else if (kind === "schedule") {
26926
+ await provider.deleteSchedule({ workspace, path: path3 });
26927
+ } else if (isTriggerKind(kind)) {
26928
+ await provider.deleteTriggerByKind(kind, { workspace, path: path3 });
26929
+ } else {
26930
+ throw new Error(`Deletion not supported for kind: ${kind}`);
26931
+ }
26932
+ return { success: true };
26933
+ } catch (e) {
26934
+ return { success: false, error: toError(e) };
27078
26935
  }
27079
- return p;
27080
26936
  }
27081
- var DOTTED_SUFFIXES, NON_DOTTED_SUFFIXES, _nonDottedPaths = false, _nonDottedPathsLogged = false, METADATA_FILES, MODULE_SUFFIX = "__mod";
27082
- var init_resource_folders = __esm(async () => {
27083
- init_log();
27084
- await init_yaml();
27085
- DOTTED_SUFFIXES = {
27086
- flow: ".flow",
27087
- app: ".app",
27088
- raw_app: ".raw_app"
27089
- };
27090
- NON_DOTTED_SUFFIXES = {
27091
- flow: "__flow",
27092
- app: "__app",
27093
- raw_app: "__raw_app"
27094
- };
27095
- METADATA_FILES = {
27096
- flow: { yaml: "flow.yaml", json: "flow.json" },
27097
- app: { yaml: "app.yaml", json: "app.json" },
27098
- raw_app: { yaml: "raw_app.yaml", json: "raw_app.json" }
27099
- };
26937
+ async function getOnBehalfOf(provider, kind, path3, workspace) {
26938
+ try {
26939
+ if (kind === "flow") {
26940
+ const flow = await provider.getFlowByPath({ workspace, path: path3 });
26941
+ return flow.on_behalf_of_email;
26942
+ } else if (kind === "script") {
26943
+ const script = await provider.getScriptByPath({ workspace, path: path3 });
26944
+ return script.on_behalf_of_email;
26945
+ } else if (kind === "app" || kind === "raw_app") {
26946
+ const app = await provider.getAppByPath({ workspace, path: path3 });
26947
+ return app.policy?.on_behalf_of_email;
26948
+ } else if (kind === "schedule") {
26949
+ const schedule = await provider.getSchedule({ workspace, path: path3 });
26950
+ return schedule.permissioned_as;
26951
+ } else if (isTriggerKind(kind)) {
26952
+ return await provider.getTriggerPermissionedAs(kind, { workspace, path: path3 });
26953
+ }
26954
+ } catch {}
26955
+ return;
26956
+ }
26957
+ var TRIGGER_KINDS;
26958
+ var init_deploy = __esm(() => {
26959
+ TRIGGER_KINDS = [
26960
+ "http_trigger",
26961
+ "websocket_trigger",
26962
+ "kafka_trigger",
26963
+ "nats_trigger",
26964
+ "postgres_trigger",
26965
+ "mqtt_trigger",
26966
+ "sqs_trigger",
26967
+ "gcp_trigger",
26968
+ "azure_trigger",
26969
+ "email_trigger"
26970
+ ];
27100
26971
  });
27101
26972
 
27102
- // src/core/conf.ts
27103
- var exports_conf = {};
27104
- __export(exports_conf, {
27105
- validateBranchConfiguration: () => validateBranchConfiguration,
27106
- showDiffs: () => showDiffs,
27107
- setShowDiffs: () => setShowDiffs,
27108
- readConfigFile: () => readConfigFile,
27109
- parseSyncBehavior: () => parseSyncBehavior,
27110
- mergeConfigWithConfigFile: () => mergeConfigWithConfigFile,
27111
- getWorkspaceNames: () => getWorkspaceNames,
27112
- getWmillYamlPath: () => getWmillYamlPath,
27113
- getEffectiveWorkspaceId: () => getEffectiveWorkspaceId,
27114
- getEffectiveSettings: () => getEffectiveSettings,
27115
- getEffectiveGitBranch: () => getEffectiveGitBranch,
27116
- findWorkspaceByGitBranch: () => findWorkspaceByGitBranch,
27117
- convertGitBranchesToWorkspaces: () => convertGitBranchesToWorkspaces,
27118
- SUPPORTED_SYNC_BEHAVIOR_VERSION: () => SUPPORTED_SYNC_BEHAVIOR_VERSION,
27119
- GLOBAL_CONFIG_OPT: () => GLOBAL_CONFIG_OPT,
27120
- DEFAULT_SYNC_OPTIONS: () => DEFAULT_SYNC_OPTIONS
26973
+ // node_modules/@cliffy/prompt/checkbox.js
26974
+ var exports_checkbox = {};
26975
+ __export(exports_checkbox, {
26976
+ isCheckboxOptionGroup: () => isCheckboxOptionGroup,
26977
+ Checkbox: () => Checkbox
27121
26978
  });
27122
- import { join as join7, dirname as dirname6, resolve as resolve5, relative as relative4 } from "node:path";
27123
- import { existsSync as existsSync2 } from "node:fs";
27124
- import { writeFile } from "node:fs/promises";
27125
- import { execSync as execSync2 } from "node:child_process";
27126
- function setShowDiffs(value) {
27127
- showDiffs = value;
26979
+ function areSomeChecked(options) {
26980
+ return options.some((option) => isOptionGroup(option) ? areSomeChecked(option.options) : option.checked);
27128
26981
  }
27129
- function parseSyncBehavior(value) {
27130
- if (!value && value !== 0)
27131
- return 0;
27132
- const s = String(value);
27133
- const match = s.match(/^v(\d+)$/);
27134
- return match ? parseInt(match[1], 10) : 0;
26982
+ function areAllChecked(options) {
26983
+ return options.every((option) => isOptionGroup(option) ? areAllChecked(option.options) : option.checked);
27135
26984
  }
27136
- function getGitRepoRoot() {
27137
- try {
27138
- const result = execSync2("git rev-parse --show-toplevel", {
27139
- encoding: "utf8",
27140
- stdio: "pipe"
27141
- });
27142
- return result.trim();
27143
- } catch (error2) {
27144
- return null;
26985
+ function flatOptions(options) {
26986
+ return flat(options);
26987
+ function flat(options2, indentLevel = 0, opts = []) {
26988
+ for (const option of options2) {
26989
+ option.indentLevel = indentLevel;
26990
+ if (isOption2(option)) {
26991
+ opts.push(option);
26992
+ }
26993
+ if (isOptionGroup(option)) {
26994
+ flat(option.options, ++indentLevel, opts);
26995
+ }
26996
+ }
26997
+ return opts;
27145
26998
  }
27146
26999
  }
27147
- function findWmillYaml() {
27148
- const startDir = resolve5(process.cwd());
27149
- const isInGitRepo = isGitRepository();
27150
- const gitRoot = isInGitRepo ? getGitRepoRoot() : null;
27151
- let currentDir = startDir;
27152
- let foundPath = null;
27153
- while (true) {
27154
- const wmillYamlPath = join7(currentDir, "wmill.yaml");
27155
- if (existsSync2(wmillYamlPath)) {
27156
- foundPath = wmillYamlPath;
27157
- break;
27000
+ function isCheckboxOptionGroup(option) {
27001
+ return isOptionGroup(option);
27002
+ }
27003
+ var Checkbox;
27004
+ var init_checkbox = __esm(async () => {
27005
+ init_equal();
27006
+ init_colors();
27007
+ init__figures();
27008
+ await __promiseAll([
27009
+ init__generic_list(),
27010
+ init__generic_prompt()
27011
+ ]);
27012
+ Checkbox = class Checkbox extends GenericList {
27013
+ settings;
27014
+ options;
27015
+ listIndex;
27016
+ listOffset;
27017
+ confirmSubmit = false;
27018
+ static prompt(options) {
27019
+ return new this(options).prompt();
27158
27020
  }
27159
- if (gitRoot && resolve5(currentDir) === resolve5(gitRoot)) {
27160
- break;
27021
+ static inject(value) {
27022
+ GenericPrompt.inject(value);
27161
27023
  }
27162
- const parentDir = dirname6(currentDir);
27163
- if (parentDir === currentDir) {
27164
- break;
27024
+ constructor(options) {
27025
+ super();
27026
+ this.settings = this.getDefaultSettings(options);
27027
+ this.options = this.settings.options.slice();
27028
+ this.listIndex = this.getListIndex();
27029
+ this.listOffset = this.getPageOffset(this.listIndex);
27165
27030
  }
27166
- currentDir = parentDir;
27167
- }
27168
- if (!GLOBAL_CONFIG_OPT.noCdToRoot && foundPath && resolve5(dirname6(foundPath)) !== resolve5(startDir)) {
27169
- const configDir = dirname6(foundPath);
27170
- const relativePath = relative4(startDir, foundPath);
27171
- warn(`⚠️ wmill.yaml found in parent directory: ${relativePath}`);
27172
- process.chdir(configDir);
27173
- info(`\uD83D\uDCC1 Changed working directory to: ${configDir}`);
27174
- }
27175
- return foundPath;
27176
- }
27177
- function getWmillYamlPath() {
27178
- return findWmillYaml();
27179
- }
27180
- async function readConfigFile(opts) {
27181
- const warnIfMissing = opts?.warnIfMissing ?? true;
27182
- try {
27183
- const wmillYamlPath = findWmillYaml();
27184
- if (!wmillYamlPath) {
27185
- if (warnIfMissing) {
27186
- warn("No wmill.yaml found. Use 'wmill init' to bootstrap it.");
27187
- }
27188
- return {};
27031
+ getDefaultSettings(options) {
27032
+ const settings = super.getDefaultSettings(options);
27033
+ return {
27034
+ confirmSubmit: true,
27035
+ ...settings,
27036
+ check: options.check ?? green(Figures.TICK),
27037
+ uncheck: options.uncheck ?? red(Figures.CROSS),
27038
+ partialCheck: options.partialCheck ?? green(Figures.RADIO_ON),
27039
+ minOptions: options.minOptions ?? 0,
27040
+ maxOptions: options.maxOptions ?? Infinity,
27041
+ options: this.mapOptions(options, options.options),
27042
+ keys: {
27043
+ check: [
27044
+ "space"
27045
+ ],
27046
+ checkAll: [
27047
+ "a"
27048
+ ],
27049
+ ...settings.keys ?? {},
27050
+ open: options.keys?.open ?? [
27051
+ "right"
27052
+ ],
27053
+ back: options.keys?.back ?? [
27054
+ "left",
27055
+ "escape"
27056
+ ]
27057
+ }
27058
+ };
27189
27059
  }
27190
- const conf = await yamlParseFile(wmillYamlPath);
27191
- if (conf && "overrides" in conf) {
27192
- const overrides = conf.overrides;
27193
- const hasSettings = overrides && typeof overrides === "object" && Object.keys(overrides).length > 0;
27194
- if (hasSettings) {
27195
- throw new Error(`❌ The 'overrides' field is no longer supported.
27196
- ` + ` The configuration system now uses workspace-based configuration.
27197
- ` + " Please delete your wmill.yaml and run 'wmill init' to recreate it with the new format.");
27060
+ mapOptions(promptOptions, options) {
27061
+ return options.map((option) => typeof option === "string" || typeof option === "number" ? this.mapOption(promptOptions, {
27062
+ value: option
27063
+ }) : isCheckboxOptionGroup(option) ? this.mapOptionGroup(promptOptions, option) : this.mapOption(promptOptions, option));
27064
+ }
27065
+ mapOption(options, option) {
27066
+ if (isOption2(option)) {
27067
+ return {
27068
+ ...super.mapOption(options, option),
27069
+ checked: typeof option.checked === "undefined" && options.default && options.default.indexOf(option.value) !== -1 ? true : !!option.checked,
27070
+ icon: typeof option.icon === "undefined" ? true : option.icon
27071
+ };
27198
27072
  } else {
27199
- delete conf.overrides;
27073
+ return {
27074
+ ...super.mapOption(options, option),
27075
+ checked: false,
27076
+ icon: false
27077
+ };
27200
27078
  }
27201
27079
  }
27202
- if (conf && !conf.workspaces) {
27203
- let legacyKey = null;
27204
- let legacyData;
27205
- if (conf.gitBranches) {
27206
- legacyKey = "gitBranches";
27207
- legacyData = conf.gitBranches;
27208
- } else if (conf.environments) {
27209
- legacyKey = "environments";
27210
- legacyData = conf.environments;
27211
- } else if (conf.git_branches) {
27212
- legacyKey = "git_branches";
27213
- legacyData = conf.git_branches;
27080
+ mapOptionGroup(promptOptions, option) {
27081
+ const options = this.mapOptions(promptOptions, option.options);
27082
+ const optionGroup = super.mapOptionGroup(promptOptions, option, false);
27083
+ return {
27084
+ ...optionGroup,
27085
+ get checked() {
27086
+ return areAllChecked(options);
27087
+ },
27088
+ get disabled() {
27089
+ return optionGroup.disabled || options.every((opt) => opt.disabled);
27090
+ },
27091
+ options,
27092
+ icon: typeof option.icon === "undefined" ? true : option.icon
27093
+ };
27094
+ }
27095
+ match() {
27096
+ super.match();
27097
+ if (this.isSearching()) {
27098
+ this.selectSearch();
27214
27099
  }
27215
- if (legacyKey && legacyData) {
27216
- conf.workspaces = convertGitBranchesToWorkspaces(legacyData);
27217
- if (!legacyConfigWarned) {
27218
- warn(`⚠️ '${legacyKey}' in wmill.yaml is deprecated. Use 'workspaces' instead.
27219
- ` + ` Run 'wmill config migrate' to update your configuration automatically.`);
27220
- legacyConfigWarned = true;
27221
- }
27100
+ }
27101
+ getListItemIcon(option) {
27102
+ return this.getCheckboxIcon(option) + super.getListItemIcon(option);
27103
+ }
27104
+ getCheckboxIcon(option) {
27105
+ if (!option.icon) {
27106
+ return "";
27222
27107
  }
27223
- } else if (conf?.workspaces) {
27224
- for (const legacyKey of ["gitBranches", "environments", "git_branches"]) {
27225
- if (conf[legacyKey]) {
27226
- warn(`⚠️ Both 'workspaces' and '${legacyKey}' found in wmill.yaml. Using 'workspaces' and ignoring '${legacyKey}'.`);
27108
+ const icon = option.checked ? this.settings.check + " " : isOptionGroup(option) && areSomeChecked(option.options) ? this.settings.partialCheck + " " : this.settings.uncheck + " ";
27109
+ return option.disabled ? dim(icon) : icon;
27110
+ }
27111
+ getValue() {
27112
+ return flatOptions(this.settings.options).filter((option) => option.checked).map((option) => option.value);
27113
+ }
27114
+ async handleEvent(event) {
27115
+ const hasConfirmed = this.confirmSubmit;
27116
+ this.confirmSubmit = false;
27117
+ switch (true) {
27118
+ case (this.isKey(this.settings.keys, "check", event) && !this.isSearchSelected()):
27119
+ this.checkValue();
27120
+ break;
27121
+ case this.isKey(this.settings.keys, "submit", event):
27122
+ await this.submit(hasConfirmed);
27123
+ break;
27124
+ case (event.ctrl && this.isKey(this.settings.keys, "checkAll", event)):
27125
+ this.checkAllOption();
27126
+ break;
27127
+ default:
27128
+ await super.handleEvent(event);
27129
+ }
27130
+ }
27131
+ hint() {
27132
+ if (this.confirmSubmit) {
27133
+ const info2 = this.isBackButton(this.selectedOption) ? ` To leave the current group press ${getFiguresByKeys(this.settings.keys.back ?? []).join(", ")}.` : isOptionGroup(this.selectedOption) ? ` To open the selected group press ${getFiguresByKeys(this.settings.keys.open ?? []).join(", ")}.` : ` To check or uncheck the selected option press ${getFiguresByKeys(this.settings.keys.check ?? []).join(", ")}.`;
27134
+ return this.settings.indent + brightBlue(`Press ${getFiguresByKeys(this.settings.keys.submit ?? [])} again to submit.${info2}`);
27135
+ }
27136
+ return super.hint();
27137
+ }
27138
+ async submit(hasConfirmed) {
27139
+ if (!hasConfirmed && this.settings.confirmSubmit && !this.isSearchSelected()) {
27140
+ this.confirmSubmit = true;
27141
+ return;
27142
+ }
27143
+ await super.submit();
27144
+ }
27145
+ checkValue() {
27146
+ const option = this.options.at(this.listIndex);
27147
+ if (!option) {
27148
+ this.setErrorMessage("No option available to select.");
27149
+ return;
27150
+ } else if (option.disabled) {
27151
+ this.setErrorMessage("This option is disabled and cannot be changed.");
27152
+ return;
27153
+ }
27154
+ this.checkOption(option, !option.checked);
27155
+ }
27156
+ checkOption(option, checked) {
27157
+ if (isOption2(option)) {
27158
+ option.checked = checked;
27159
+ } else {
27160
+ for (const childOption of option.options) {
27161
+ this.checkOption(childOption, checked);
27227
27162
  }
27228
27163
  }
27229
27164
  }
27230
- delete conf?.gitBranches;
27231
- delete conf?.environments;
27232
- delete conf?.git_branches;
27233
- if (conf?.defaultTs == undefined) {
27234
- warn("No defaultTs defined in your wmill.yaml. Using 'bun' as default.");
27165
+ checkAllOption() {
27166
+ const checked = this.options.some((option) => option.checked);
27167
+ for (const option of this.options) {
27168
+ this.checkOption(option, !checked);
27169
+ }
27235
27170
  }
27236
- setNonDottedPaths(conf?.nonDottedPaths ?? false);
27237
- const syncBehaviorVersion = parseSyncBehavior(conf?.syncBehavior);
27238
- if (syncBehaviorVersion > SUPPORTED_SYNC_BEHAVIOR_VERSION) {
27239
- error(`Your wmill.yaml specifies syncBehavior: ${conf.syncBehavior}, but this CLI only supports up to v${SUPPORTED_SYNC_BEHAVIOR_VERSION}. Run 'wmill upgrade' to update.`);
27240
- process.exit(1);
27171
+ validate(value) {
27172
+ const options = flatOptions(this.settings.options);
27173
+ const isValidValue = Array.isArray(value) && value.every((val) => options.findIndex((option) => equal(option.value, val)) !== -1);
27174
+ if (!isValidValue) {
27175
+ return false;
27176
+ }
27177
+ if (value.length < this.settings.minOptions) {
27178
+ return `The minimum number of options is ${this.settings.minOptions} but got ${value.length}.`;
27179
+ }
27180
+ if (value.length > this.settings.maxOptions) {
27181
+ return `The maximum number of options is ${this.settings.maxOptions} but got ${value.length}.`;
27182
+ }
27183
+ return true;
27241
27184
  }
27242
- return typeof conf == "object" ? conf : {};
27243
- } catch (e) {
27244
- if (e instanceof Error && (e.message.includes("overrides") || e.message.includes("Obsolete configuration format"))) {
27245
- throw e;
27185
+ transform(value) {
27186
+ return value;
27246
27187
  }
27247
- if (e instanceof Error && e.message.includes("Error parsing yaml")) {
27248
- const yamlError = e.cause instanceof Error ? e.cause.message : String(e.cause);
27249
- throw new Error(`❌ YAML syntax error in wmill.yaml:
27250
- ` + " " + yamlError + `
27251
- ` + " Please fix the YAML syntax in wmill.yaml or delete the file to start fresh.");
27252
- } else {
27253
- throw new Error(`❌ Failed to read wmill.yaml:
27254
- ` + " " + (e instanceof Error ? e.message : String(e)) + `
27255
- ` + " Please check file permissions or fix the syntax.");
27188
+ format(value) {
27189
+ return value.map((val) => this.settings.format?.(val) ?? this.getOptionByValue(val)?.name ?? String(val)).join(", ");
27256
27190
  }
27191
+ };
27192
+ });
27193
+
27194
+ // src/commands/workspace/merge.ts
27195
+ function triggerService(kind) {
27196
+ switch (kind) {
27197
+ case "http_trigger":
27198
+ return {
27199
+ exists: existsHttpTrigger,
27200
+ get: getHttpTrigger,
27201
+ create: createHttpTrigger,
27202
+ update: updateHttpTrigger,
27203
+ delete: deleteHttpTrigger
27204
+ };
27205
+ case "websocket_trigger":
27206
+ return {
27207
+ exists: existsWebsocketTrigger,
27208
+ get: getWebsocketTrigger,
27209
+ create: createWebsocketTrigger,
27210
+ update: updateWebsocketTrigger,
27211
+ delete: deleteWebsocketTrigger
27212
+ };
27213
+ case "kafka_trigger":
27214
+ return {
27215
+ exists: existsKafkaTrigger,
27216
+ get: getKafkaTrigger,
27217
+ create: createKafkaTrigger,
27218
+ update: updateKafkaTrigger,
27219
+ delete: deleteKafkaTrigger
27220
+ };
27221
+ case "nats_trigger":
27222
+ return {
27223
+ exists: existsNatsTrigger,
27224
+ get: getNatsTrigger,
27225
+ create: createNatsTrigger,
27226
+ update: updateNatsTrigger,
27227
+ delete: deleteNatsTrigger
27228
+ };
27229
+ case "postgres_trigger":
27230
+ return {
27231
+ exists: existsPostgresTrigger,
27232
+ get: getPostgresTrigger,
27233
+ create: createPostgresTrigger,
27234
+ update: updatePostgresTrigger,
27235
+ delete: deletePostgresTrigger
27236
+ };
27237
+ case "mqtt_trigger":
27238
+ return {
27239
+ exists: existsMqttTrigger,
27240
+ get: getMqttTrigger,
27241
+ create: createMqttTrigger,
27242
+ update: updateMqttTrigger,
27243
+ delete: deleteMqttTrigger
27244
+ };
27245
+ case "sqs_trigger":
27246
+ return {
27247
+ exists: existsSqsTrigger,
27248
+ get: getSqsTrigger,
27249
+ create: createSqsTrigger,
27250
+ update: updateSqsTrigger,
27251
+ delete: deleteSqsTrigger
27252
+ };
27253
+ case "gcp_trigger":
27254
+ return {
27255
+ exists: existsGcpTrigger,
27256
+ get: getGcpTrigger,
27257
+ create: createGcpTrigger,
27258
+ update: updateGcpTrigger,
27259
+ delete: deleteGcpTrigger
27260
+ };
27261
+ case "azure_trigger":
27262
+ return {
27263
+ exists: existsAzureTrigger,
27264
+ get: getAzureTrigger,
27265
+ create: createAzureTrigger,
27266
+ update: updateAzureTrigger,
27267
+ delete: deleteAzureTrigger
27268
+ };
27269
+ case "email_trigger":
27270
+ return {
27271
+ exists: existsEmailTrigger,
27272
+ get: getEmailTrigger,
27273
+ create: createEmailTrigger,
27274
+ update: updateEmailTrigger,
27275
+ delete: deleteEmailTrigger
27276
+ };
27257
27277
  }
27258
27278
  }
27259
- async function mergeConfigWithConfigFile(opts) {
27260
- const configFile = await readConfigFile();
27261
- return Object.assign(configFile ?? {}, opts);
27279
+ function preparePayload(kind, trigger, onBehalfOf) {
27280
+ const preserve = onBehalfOf !== undefined;
27281
+ const base = {
27282
+ ...trigger,
27283
+ permissioned_as: onBehalfOf,
27284
+ preserve_permissioned_as: preserve
27285
+ };
27286
+ if (kind === "gcp_trigger") {
27287
+ base.subscription_id = "";
27288
+ base.subscription_mode = "create_update";
27289
+ if (base.delivery_config) {
27290
+ base.delivery_config = { ...base.delivery_config, audience: "" };
27291
+ }
27292
+ if (base.delivery_type === "push") {
27293
+ base.base_endpoint = OpenAPI.BASE.replace(/\/api\/?$/, "");
27294
+ } else {
27295
+ base.base_endpoint = undefined;
27296
+ }
27297
+ }
27298
+ return base;
27262
27299
  }
27263
- async function validateBranchConfiguration(opts, workspaceNameOverride) {
27264
- if (opts.skipBranchValidation || workspaceNameOverride || !isGitRepository()) {
27300
+ async function mergeWorkspaces(opts) {
27301
+ const workspace = await tryResolveBranchWorkspace(opts);
27302
+ if (!workspace) {
27303
+ throw new Error("Could not resolve workspace from branch name. Make sure you are in a git repo with 'workspaces' configured in wmill.yaml.");
27304
+ }
27305
+ const token = workspace.token;
27306
+ if (!token) {
27307
+ throw new Error("Not logged in. Please run 'wmill workspace add' first.");
27308
+ }
27309
+ const remote = workspace.remote;
27310
+ setClient(token, remote.endsWith("/") ? remote.substring(0, remote.length - 1) : remote);
27311
+ const forkWorkspaceId = workspace.workspaceId;
27312
+ const userWorkspaces = await listUserWorkspaces();
27313
+ const forkEntry = userWorkspaces.workspaces?.find((w) => w.id === forkWorkspaceId);
27314
+ if (!forkEntry?.parent_workspace_id) {
27315
+ throw new Error(`Workspace '${forkWorkspaceId}' is not a fork (no parent_workspace_id). ` + `You can only merge from a forked workspace.`);
27316
+ }
27317
+ const parentWorkspaceId = forkEntry.parent_workspace_id;
27318
+ info(`Fork: ${colors.bold(forkWorkspaceId)} → Parent: ${colors.bold(parentWorkspaceId)}`);
27319
+ info("Comparing workspaces...");
27320
+ const comparison = await compareWorkspaces({
27321
+ workspace: parentWorkspaceId,
27322
+ targetWorkspaceId: forkWorkspaceId
27323
+ });
27324
+ if (comparison.skipped_comparison) {
27325
+ info(colors.yellow("This fork was created before change tracking was available. " + "Use the UI or git-based merge instead."));
27326
+ return;
27327
+ }
27328
+ const summary = comparison.summary;
27329
+ if (summary.total_diffs === 0) {
27330
+ info(colors.green("Everything is up to date. No differences found."));
27265
27331
  return;
27266
27332
  }
27267
- const config = await readConfigFile();
27268
- const { workspaces } = config;
27269
- const rawBranch = getCurrentGitBranch();
27270
- const originalBranchIfForked = getOriginalBranchForWorkspaceForks(rawBranch);
27271
- let currentBranch;
27272
- if (originalBranchIfForked) {
27273
- info(`Workspace fork detected from branch name \`${rawBranch}\`. Validating workspace configuration using original branch \`${originalBranchIfForked}\``);
27274
- currentBranch = originalBranchIfForked;
27275
- } else {
27276
- currentBranch = rawBranch;
27277
- }
27278
- if (!workspaces || getWorkspaceNames(workspaces).length === 0) {
27279
- warn(`⚠️ WARNING: In a Git repository, the 'workspaces' section is recommended in wmill.yaml.
27280
- ` + ` Consider adding a workspaces section to map workspace names to Windmill instances.
27281
- ` + " Run 'wmill init' to recreate the configuration file with proper workspace setup.");
27333
+ info("");
27334
+ info(colors.bold("Comparison Summary:"));
27335
+ const summaryRows = [];
27336
+ if (summary.scripts_changed > 0)
27337
+ summaryRows.push(["Scripts", String(summary.scripts_changed)]);
27338
+ if (summary.flows_changed > 0)
27339
+ summaryRows.push(["Flows", String(summary.flows_changed)]);
27340
+ if (summary.apps_changed > 0)
27341
+ summaryRows.push(["Apps", String(summary.apps_changed)]);
27342
+ if (summary.resources_changed > 0)
27343
+ summaryRows.push(["Resources", String(summary.resources_changed)]);
27344
+ if (summary.variables_changed > 0)
27345
+ summaryRows.push(["Variables", String(summary.variables_changed)]);
27346
+ if (summary.resource_types_changed > 0)
27347
+ summaryRows.push(["Resource Types", String(summary.resource_types_changed)]);
27348
+ if (summary.folders_changed > 0)
27349
+ summaryRows.push(["Folders", String(summary.folders_changed)]);
27350
+ if (summary.schedules_changed > 0)
27351
+ summaryRows.push(["Schedules", String(summary.schedules_changed)]);
27352
+ if (summary.triggers_changed > 0)
27353
+ summaryRows.push(["Triggers", String(summary.triggers_changed)]);
27354
+ summaryRows.push(["Total", String(summary.total_diffs)]);
27355
+ if (summary.conflicts > 0)
27356
+ summaryRows.push([
27357
+ colors.red("Conflicts"),
27358
+ colors.red(String(summary.conflicts))
27359
+ ]);
27360
+ new Table2().header(["Type", "Changed"]).padding(2).border(true).body(summaryRows).render();
27361
+ const diffs = comparison.diffs.filter((d) => d.has_changes !== false);
27362
+ if (diffs.length === 0) {
27363
+ info(colors.green("No effective changes to deploy."));
27282
27364
  return;
27283
27365
  }
27284
- const branchToWsNames = new Map;
27285
- for (const name of getWorkspaceNames(workspaces)) {
27286
- const entry = workspaces[name];
27287
- const branch = getEffectiveGitBranch(name, entry);
27288
- const existing = branchToWsNames.get(branch);
27289
- if (existing) {
27290
- existing.push(name);
27366
+ info("");
27367
+ info(colors.bold("Changed items:"));
27368
+ new Table2().header(["#", "Kind", "Path", "Ahead", "Behind", "Conflict"]).padding(1).border(true).body(diffs.map((d, i) => {
27369
+ const isConflict = d.ahead > 0 && d.behind > 0;
27370
+ return [
27371
+ String(i + 1),
27372
+ d.kind,
27373
+ d.path,
27374
+ d.ahead > 0 ? colors.green(String(d.ahead)) : "0",
27375
+ d.behind > 0 ? colors.yellow(String(d.behind)) : "0",
27376
+ isConflict ? colors.red("YES") : ""
27377
+ ];
27378
+ })).render();
27379
+ let direction;
27380
+ if (opts.direction === "to-parent" || opts.direction === "to-fork") {
27381
+ direction = opts.direction;
27382
+ } else if (opts.direction) {
27383
+ throw new Error(`Invalid direction '${opts.direction}'. Use 'to-parent' or 'to-fork'.`);
27384
+ } else if (opts.yes) {
27385
+ direction = "to-parent";
27386
+ } else {
27387
+ const { Select: Select2 } = await init_select().then(() => exports_select);
27388
+ direction = await Select2.prompt({
27389
+ message: "Deploy direction:",
27390
+ options: [
27391
+ {
27392
+ name: `Deploy to parent (${parentWorkspaceId}) ← fork changes`,
27393
+ value: "to-parent"
27394
+ },
27395
+ {
27396
+ name: `Update fork (${forkWorkspaceId}) ← parent changes`,
27397
+ value: "to-fork"
27398
+ }
27399
+ ]
27400
+ });
27401
+ }
27402
+ info(`
27403
+ Direction: ${colors.bold(direction === "to-parent" ? `Fork → Parent (${parentWorkspaceId})` : `Parent → Fork (${forkWorkspaceId})`)}`);
27404
+ const selectableDiffs = diffs.filter((d) => {
27405
+ if (direction === "to-parent") {
27406
+ return d.ahead > 0;
27291
27407
  } else {
27292
- branchToWsNames.set(branch, [name]);
27408
+ return d.behind > 0;
27293
27409
  }
27410
+ });
27411
+ if (selectableDiffs.length === 0) {
27412
+ info(colors.yellow(`No items to deploy in the '${direction}' direction.`));
27413
+ return;
27294
27414
  }
27295
- for (const [branch, names] of branchToWsNames) {
27296
- if (names.length > 1) {
27297
- warn(`⚠️ WARNING: Multiple workspaces map to git branch '${branch}': ${names.join(", ")}.
27298
- ` + ` Only the first ('${names[0]}') will be used during auto-detection. Use --workspace to select explicitly.`);
27299
- }
27415
+ let selectedDiffs = selectableDiffs;
27416
+ if (opts.all) {
27417
+ selectedDiffs = selectableDiffs;
27418
+ } else if (opts.skipConflicts) {
27419
+ selectedDiffs = selectableDiffs.filter((d) => !(d.ahead > 0 && d.behind > 0) && (!!opts.include || !isTriggerOrScheduleKind(d.kind)));
27420
+ } else if (opts.yes && !opts.include && !opts.exclude) {
27421
+ selectedDiffs = selectableDiffs.filter((d) => !isTriggerOrScheduleKind(d.kind) && (direction !== "to-fork" || !(d.ahead > 0 && d.behind > 0)));
27422
+ } else if (!opts.yes) {
27423
+ const { Checkbox: Checkbox2 } = await init_checkbox().then(() => exports_checkbox);
27424
+ const defaultForToFork = direction === "to-fork";
27425
+ const selectedValues = await Checkbox2.prompt({
27426
+ message: `Select items to deploy (${selectableDiffs.length} available):`,
27427
+ options: selectableDiffs.map((d) => {
27428
+ const isConflict = d.ahead > 0 && d.behind > 0;
27429
+ const isTriggerOrSchedule = isTriggerOrScheduleKind(d.kind);
27430
+ const label = `${d.kind}:${d.path}${isConflict ? colors.red(" [CONFLICT]") : ""}`;
27431
+ return {
27432
+ name: label,
27433
+ value: `${d.kind}:${d.path}`,
27434
+ checked: !isTriggerOrSchedule && (defaultForToFork ? !isConflict : true)
27435
+ };
27436
+ })
27437
+ });
27438
+ selectedDiffs = selectableDiffs.filter((d) => selectedValues.includes(`${d.kind}:${d.path}`));
27300
27439
  }
27301
- if (currentBranch && !findWorkspaceByGitBranch(workspaces, currentBranch)) {
27302
- const wsNames = getWorkspaceNames(workspaces);
27303
- const availableInfo = wsNames.map((n) => {
27304
- const entry = workspaces[n];
27305
- const branch = getEffectiveGitBranch(n, entry);
27306
- return branch !== n ? `${n} (gitBranch: ${branch})` : n;
27307
- }).join(", ");
27308
- if (!!process.stdin.isTTY) {
27309
- info(`Current Git branch '${currentBranch}' does not match any workspace in the configuration.
27310
- ` + `Available workspaces: ${availableInfo}`);
27311
- const shouldCreate = opts.yes || await Confirm.prompt({
27312
- message: `Create empty workspace configuration for branch '${currentBranch}'?`,
27313
- default: true
27314
- });
27315
- if (shouldCreate) {
27316
- if (/[\/\\:*?"<>|.]/.test(currentBranch)) {
27317
- const sanitizedBranchName = currentBranch.replace(/[\/\\:*?"<>|.]/g, "_");
27318
- warn(`⚠️ WARNING: Branch name "${currentBranch}" contains filesystem-unsafe characters (/ \\ : * ? " < > | .).`);
27319
- warn(` Branch-specific files will be saved with sanitized name: "${sanitizedBranchName}"`);
27320
- warn(` Example: "file.variable.yaml" → "file.${sanitizedBranchName}.variable.yaml"`);
27321
- }
27322
- const currentConfig = await readConfigFile();
27323
- if (!currentConfig.workspaces) {
27324
- currentConfig.workspaces = {};
27325
- }
27326
- currentConfig.workspaces[currentBranch] = {};
27327
- await writeFile("wmill.yaml", import_yaml3.stringify(currentConfig), "utf-8");
27328
- info(`✅ Created empty workspace configuration for '${currentBranch}'`);
27329
- } else {
27330
- warn("⚠️ WARNING: Workspace creation cancelled. You can manually add a workspace to the 'workspaces' section in wmill.yaml or use 'wmill gitsync-settings pull' to pull configuration from an existing windmill workspace git-sync configuration.");
27331
- return;
27332
- }
27333
- } else {
27334
- if (/[\/\\:*?"<>|.]/.test(currentBranch)) {
27335
- const sanitizedBranchName = currentBranch.replace(/[\/\\:*?"<>|.]/g, "_");
27336
- warn(`⚠️ WARNING: Branch name "${currentBranch}" contains filesystem-unsafe characters (/ \\ : * ? " < > | .).`);
27337
- warn(` Branch-specific files will use sanitized name: "${sanitizedBranchName}"`);
27338
- }
27339
- warn(`⚠️ WARNING: Current Git branch '${currentBranch}' does not match any workspace in the configuration.
27340
- ` + ` Consider adding a workspace entry for branch '${currentBranch}' in the 'workspaces' section of wmill.yaml.
27341
- ` + ` Available workspaces: ${availableInfo}`);
27342
- return;
27343
- }
27440
+ if (opts.include) {
27441
+ const includeSet = new Set(opts.include.split(",").map((s) => s.trim()));
27442
+ selectedDiffs = selectedDiffs.filter((d) => includeSet.has(`${d.kind}:${d.path}`));
27344
27443
  }
27345
- }
27346
- async function getEffectiveSettings(config, promotion, skipBranchValidation, suppressLogs, workspaceNameOverride) {
27347
- const { workspaces, ...topLevelSettings } = config;
27348
- const effective = { ...topLevelSettings };
27349
- let resolvedWsName = null;
27350
- let resolvedWsEntry = null;
27351
- let originalBranchIfForked = null;
27352
- let rawGitBranch = null;
27353
- if (workspaceNameOverride) {
27354
- if (workspaces && workspaces[workspaceNameOverride]) {
27355
- resolvedWsName = workspaceNameOverride;
27356
- resolvedWsEntry = workspaces[workspaceNameOverride];
27357
- }
27358
- } else if (isGitRepository()) {
27359
- rawGitBranch = getCurrentGitBranch();
27360
- originalBranchIfForked = getOriginalBranchForWorkspaceForks(rawGitBranch);
27361
- const branch = originalBranchIfForked ?? rawGitBranch;
27362
- if (originalBranchIfForked) {
27363
- info(`Using overrides from original branch \`${originalBranchIfForked}\``);
27444
+ if (opts.exclude) {
27445
+ const excludeSet = new Set(opts.exclude.split(",").map((s) => s.trim()));
27446
+ selectedDiffs = selectedDiffs.filter((d) => !excludeSet.has(`${d.kind}:${d.path}`));
27447
+ }
27448
+ if (selectedDiffs.length === 0) {
27449
+ info(colors.yellow("No items selected for deployment."));
27450
+ return;
27451
+ }
27452
+ const conflicts = selectedDiffs.filter((d) => d.ahead > 0 && d.behind > 0);
27453
+ if (conflicts.length > 0) {
27454
+ info(colors.yellow(`
27455
+ ${conflicts.length} conflicting item(s) will be deployed (source will overwrite target):`));
27456
+ for (const c of conflicts) {
27457
+ info(colors.yellow(` - ${c.kind}:${c.path}`));
27364
27458
  }
27365
- if (branch) {
27366
- const match = findWorkspaceByGitBranch(workspaces, branch);
27367
- if (match) {
27368
- [resolvedWsName, resolvedWsEntry] = match;
27459
+ if (!opts.yes) {
27460
+ const { Confirm: Confirm2 } = await init_confirm().then(() => exports_confirm);
27461
+ const proceed = await Confirm2.prompt("Proceed with deploying conflicting items?");
27462
+ if (!proceed) {
27463
+ info("Aborted.");
27464
+ return;
27369
27465
  }
27370
27466
  }
27371
- } else {
27372
- debug("Not in a Git repository and no workspace override provided, using top-level settings");
27373
27467
  }
27374
- if (promotion && workspaces) {
27375
- const promotionMatch = findWorkspaceByGitBranch(workspaces, promotion);
27376
- if (promotionMatch) {
27377
- const [, targetWs] = promotionMatch;
27378
- if (targetWs.promotionOverrides) {
27379
- Object.assign(effective, targetWs.promotionOverrides);
27380
- if (!suppressLogs) {
27381
- info(`Applied promotion settings from workspace for branch: ${promotion}`);
27382
- }
27383
- } else if (targetWs.overrides) {
27384
- Object.assign(effective, targetWs.overrides);
27385
- if (!suppressLogs) {
27386
- info(`Applied settings from workspace for branch: ${promotion} (no promotionOverrides found)`);
27387
- }
27388
- } else {
27389
- debug(`No promotion or regular overrides found for '${promotion}', using top-level settings`);
27468
+ info(`
27469
+ Deploying ${colors.bold(String(selectedDiffs.length))} item(s)...`);
27470
+ const sorted = [...selectedDiffs].sort((a, b) => {
27471
+ const aFolder = a.kind === "folder" ? 0 : 1;
27472
+ const bFolder = b.kind === "folder" ? 0 : 1;
27473
+ return aFolder - bFolder;
27474
+ });
27475
+ const workspaceFrom = direction === "to-parent" ? forkWorkspaceId : parentWorkspaceId;
27476
+ const workspaceTo = direction === "to-parent" ? parentWorkspaceId : forkWorkspaceId;
27477
+ let successCount = 0;
27478
+ let failCount = 0;
27479
+ for (const diff2 of sorted) {
27480
+ const label = `${diff2.kind}:${diff2.path}`;
27481
+ const itemDeletedInSource = direction === "to-parent" ? diff2.exists_in_fork === false : diff2.exists_in_source === false;
27482
+ let result;
27483
+ if (itemDeletedInSource) {
27484
+ info(colors.yellow(` ⌫ ${label} (removing from target)`));
27485
+ result = await deleteItemInWorkspace(provider, diff2.kind, diff2.path, workspaceTo);
27486
+ } else {
27487
+ let onBehalfOf;
27488
+ if (opts.preserveOnBehalfOf) {
27489
+ onBehalfOf = await getOnBehalfOf(provider, diff2.kind, diff2.path, workspaceFrom);
27390
27490
  }
27491
+ result = await deployItem(provider, diff2.kind, diff2.path, workspaceFrom, workspaceTo, onBehalfOf);
27391
27492
  }
27392
- } else if (resolvedWsEntry?.overrides) {
27393
- Object.assign(effective, resolvedWsEntry.overrides);
27394
- if (!suppressLogs) {
27395
- const extraLog = originalBranchIfForked ? ` (because it is the origin of the workspace fork branch \`${rawGitBranch}\`)` : "";
27396
- info(`Applied settings for workspace '${resolvedWsName}'${extraLog}`);
27493
+ if (result.success) {
27494
+ info(colors.green(` ✓ ${label}`));
27495
+ successCount++;
27496
+ } else {
27497
+ info(colors.red(` ${label}: ${result.error}`));
27498
+ failCount++;
27397
27499
  }
27398
- } else if (resolvedWsName) {
27399
- debug(`No overrides found for workspace '${resolvedWsName}', using top-level settings`);
27400
27500
  }
27401
- return effective;
27402
- }
27403
- function findWorkspaceByGitBranch(workspaces, branchName) {
27404
- if (!workspaces)
27405
- return;
27406
- for (const [name, entry] of Object.entries(workspaces)) {
27407
- if (RESERVED_WORKSPACE_KEYS.has(name))
27408
- continue;
27409
- const effectiveBranch = entry.gitBranch ?? name;
27410
- if (effectiveBranch === branchName) {
27411
- return [name, entry];
27412
- }
27501
+ if (successCount > 0) {
27502
+ try {
27503
+ await resetDiffTally({
27504
+ workspace: parentWorkspaceId,
27505
+ forkWorkspaceId
27506
+ });
27507
+ } catch {}
27508
+ }
27509
+ info("");
27510
+ if (failCount === 0) {
27511
+ info(colors.green(`✅ Successfully deployed ${successCount} item(s) from ${workspaceFrom} to ${workspaceTo}.`));
27512
+ } else {
27513
+ info(colors.yellow(`Deployed ${successCount} item(s), ${colors.red(String(failCount) + " failed")} from ${workspaceFrom} to ${workspaceTo}.`));
27413
27514
  }
27414
- return;
27415
- }
27416
- function getEffectiveWorkspaceId(workspaceName, config) {
27417
- return config.workspaceId ?? workspaceName;
27418
- }
27419
- function getEffectiveGitBranch(workspaceName, config) {
27420
- return config.gitBranch ?? workspaceName;
27421
- }
27422
- function getWorkspaceNames(workspaces) {
27423
- if (!workspaces)
27424
- return [];
27425
- return Object.keys(workspaces).filter((k) => !RESERVED_WORKSPACE_KEYS.has(k));
27426
27515
  }
27427
- function convertGitBranchesToWorkspaces(gitBranches) {
27428
- const workspaces = {};
27429
- for (const [key, value] of Object.entries(gitBranches)) {
27430
- if (key === "commonSpecificItems") {
27431
- workspaces.commonSpecificItems = value;
27432
- continue;
27516
+ var provider;
27517
+ var init_merge = __esm(async () => {
27518
+ init_colors2();
27519
+ init_mod6();
27520
+ init_log();
27521
+ init_client();
27522
+ init_services_gen();
27523
+ init_OpenAPI();
27524
+ init_deploy();
27525
+ await init_context();
27526
+ provider = {
27527
+ existsFlowByPath,
27528
+ existsScriptByPath,
27529
+ existsApp,
27530
+ existsVariable,
27531
+ existsResource,
27532
+ existsResourceType,
27533
+ existsFolder,
27534
+ getFlowByPath,
27535
+ createFlow,
27536
+ updateFlow,
27537
+ archiveFlowByPath,
27538
+ getScriptByPath,
27539
+ createScript,
27540
+ archiveScriptByPath,
27541
+ getAppByPath,
27542
+ createApp,
27543
+ updateApp,
27544
+ createAppRaw,
27545
+ updateAppRaw,
27546
+ getPublicSecretOfLatestVersionOfApp,
27547
+ getRawAppData,
27548
+ deleteApp,
27549
+ getVariable,
27550
+ createVariable,
27551
+ updateVariable,
27552
+ deleteVariable,
27553
+ getResource,
27554
+ createResource,
27555
+ updateResource,
27556
+ deleteResource,
27557
+ getResourceType,
27558
+ createResourceType,
27559
+ updateResourceType,
27560
+ deleteResourceType,
27561
+ getFolder,
27562
+ createFolder,
27563
+ updateFolder,
27564
+ deleteFolder,
27565
+ existsTriggerByKind: (kind, p) => triggerService(kind).exists(p),
27566
+ getTriggerForDeploy: async (kind, p) => {
27567
+ const trigger = await triggerService(kind).get({
27568
+ workspace: p.workspace,
27569
+ path: p.path
27570
+ });
27571
+ return preparePayload(kind, trigger, p.onBehalfOf);
27572
+ },
27573
+ createTriggerByKind: (kind, p) => triggerService(kind).create(p),
27574
+ updateTriggerByKind: (kind, p) => triggerService(kind).update(p),
27575
+ deleteTriggerByKind: (kind, p) => triggerService(kind).delete(p),
27576
+ getTriggerValue: (kind, p) => triggerService(kind).get(p),
27577
+ getTriggerPermissionedAs: async (kind, p) => {
27578
+ const trigger = await triggerService(kind).get(p);
27579
+ return trigger?.permissioned_as;
27580
+ },
27581
+ existsSchedule,
27582
+ getSchedule,
27583
+ createSchedule,
27584
+ updateSchedule,
27585
+ deleteSchedule
27586
+ };
27587
+ });
27588
+
27589
+ // src/commands/workspace/slack.ts
27590
+ async function connectSlack2(opts) {
27591
+ await requireLogin(opts);
27592
+ const workspace = await resolveWorkspace(opts);
27593
+ await connectSlack({
27594
+ workspace: workspace.workspaceId,
27595
+ requestBody: {
27596
+ bot_token: opts.botToken,
27597
+ team_id: opts.teamId,
27598
+ team_name: opts.teamName
27433
27599
  }
27434
- workspaces[key] = { ...value };
27435
- }
27436
- return workspaces;
27600
+ });
27601
+ info(colors.bold.underline.green(`Slack connected to workspace ${workspace.workspaceId} (team ${opts.teamName} / ${opts.teamId})`));
27437
27602
  }
27438
- var import_yaml3, showDiffs = false, SUPPORTED_SYNC_BEHAVIOR_VERSION = 1, GLOBAL_CONFIG_OPT, legacyConfigWarned = false, DEFAULT_SYNC_OPTIONS, RESERVED_WORKSPACE_KEYS;
27439
- var init_conf = __esm(async () => {
27603
+ async function disconnectSlack2(opts) {
27604
+ await requireLogin(opts);
27605
+ const workspace = await resolveWorkspace(opts);
27606
+ await disconnectSlack({ workspace: workspace.workspaceId });
27607
+ info(colors.bold.underline.green(`Slack disconnected from workspace ${workspace.workspaceId} (slack_team_id / slack_name cleared). ` + `To also remove the bot token variable/resource/folder/group, delete the corresponding files from the local sync folder and run 'wmill sync push'. ` + `To remove the workspace-level OAuth override (if any), set slack_oauth_client_id/_secret to '' in settings.yaml and push.`));
27608
+ }
27609
+ var init_slack = __esm(async () => {
27610
+ init_colors2();
27440
27611
  init_log();
27441
- init_git();
27612
+ init_services_gen();
27442
27613
  await __promiseAll([
27443
- init_yaml(),
27444
- init_confirm(),
27445
- init_resource_folders()
27614
+ init_auth(),
27615
+ init_context()
27446
27616
  ]);
27447
- import_yaml3 = __toESM(require_dist(), 1);
27448
- GLOBAL_CONFIG_OPT = { noCdToRoot: false };
27449
- DEFAULT_SYNC_OPTIONS = {
27450
- defaultTs: "bun",
27451
- includes: ["f/**"],
27452
- excludes: [],
27453
- codebases: [],
27454
- skipVariables: false,
27455
- skipResources: false,
27456
- skipResourceTypes: false,
27457
- skipSecrets: true,
27458
- skipScripts: false,
27459
- skipFlows: false,
27460
- skipApps: false,
27461
- skipFolders: false,
27462
- includeSchedules: false,
27463
- includeTriggers: false,
27464
- includeUsers: false,
27465
- includeGroups: false,
27466
- includeSettings: false,
27467
- includeKey: false,
27468
- skipWorkspaceDependencies: false,
27469
- nonDottedPaths: false,
27470
- syncBehavior: "v1"
27471
- };
27472
- RESERVED_WORKSPACE_KEYS = new Set(["commonSpecificItems"]);
27473
27617
  });
27474
27618
 
27475
27619
  // src/utils/resource_types.ts
@@ -27517,7 +27661,7 @@ __export(exports_resource_type, {
27517
27661
  import { writeFileSync } from "node:fs";
27518
27662
  import { stat as stat3, writeFile as writeFile2 } from "node:fs/promises";
27519
27663
  import path3 from "node:path";
27520
- import process10 from "node:process";
27664
+ import process11 from "node:process";
27521
27665
  async function pushResourceType(workspace, remotePath, resource, localResource) {
27522
27666
  remotePath = removeType(remotePath, "resource-type");
27523
27667
  try {
@@ -27637,7 +27781,7 @@ async function generateRTNamespace(opts) {
27637
27781
  `);
27638
27782
  namespaceContent += `
27639
27783
  }`;
27640
- writeFileSync(path3.join(process10.cwd(), "rt.d.ts"), namespaceContent);
27784
+ writeFileSync(path3.join(process11.cwd(), "rt.d.ts"), namespaceContent);
27641
27785
  info(colors.green("Created rt.d.ts with resource types namespace (RT) for TypeScript."));
27642
27786
  }
27643
27787
  var import_yaml4, command, resource_type_default;
@@ -27673,7 +27817,7 @@ __export(exports_workspace, {
27673
27817
  add: () => add
27674
27818
  });
27675
27819
  import { writeFile as writeFile3, open as fsOpen } from "node:fs/promises";
27676
- import process11 from "node:process";
27820
+ import process12 from "node:process";
27677
27821
  async function allWorkspaces(configDirOverride) {
27678
27822
  try {
27679
27823
  const file = await getWorkspaceConfigFilePath(configDirOverride);
@@ -27785,7 +27929,7 @@ async function add(opts, workspaceName, workspaceId, remote) {
27785
27929
  }
27786
27930
  remote = new URL(remote).toString();
27787
27931
  let token = await tryGetLoginInfo(opts);
27788
- if (!token && !(process11.stdin.isTTY ?? false)) {
27932
+ if (!token && !(process12.stdin.isTTY ?? false)) {
27789
27933
  info("Not a TTY, can't login interactively. Pass the token in --token");
27790
27934
  return;
27791
27935
  }
@@ -27827,7 +27971,7 @@ async function add(opts, workspaceName, workspaceId, remote) {
27827
27971
  info(`- ${workspace.id} (name: ${workspace.name})`);
27828
27972
  }
27829
27973
  }
27830
- process11.exit(1);
27974
+ process12.exit(1);
27831
27975
  }
27832
27976
  const added = await addWorkspace({
27833
27977
  name: workspaceName,
@@ -27844,7 +27988,7 @@ async function add(opts, workspaceName, workspaceId, remote) {
27844
27988
  async function addWorkspace(workspace, opts) {
27845
27989
  workspace.remote = new URL(workspace.remote).toString();
27846
27990
  const existingWorkspaces = await allWorkspaces(opts.configDir);
27847
- const isInteractive = (process11.stdin.isTTY ?? false) && (process11.stdout.isTTY ?? false) && !opts.force;
27991
+ const isInteractive = (process12.stdin.isTTY ?? false) && (process12.stdout.isTTY ?? false) && !opts.force;
27848
27992
  const nameConflict = existingWorkspaces.find((w) => w.name === workspace.name);
27849
27993
  if (nameConflict) {
27850
27994
  if (nameConflict.remote === workspace.remote && nameConflict.workspaceId === workspace.workspaceId) {
@@ -28020,7 +28164,7 @@ async function bind(opts, doBind) {
28020
28164
  if (!config.workspaces) {
28021
28165
  config.workspaces = {};
28022
28166
  }
28023
- const isInteractive = !!process11.stdin.isTTY;
28167
+ const isInteractive = !!process12.stdin.isTTY;
28024
28168
  const inGitRepo = isGitRepository2();
28025
28169
  const currentBranch = inGitRepo ? getCurrentGitBranch2() : null;
28026
28170
  if (doBind) {
@@ -28175,7 +28319,7 @@ var init_workspace = __esm(async () => {
28175
28319
  ]);
28176
28320
  command2 = new Command().alias("profile").description("workspace related commands").action(list3).command("switch").complete("workspace", async () => (await allWorkspaces()).map((x) => x.name)).description("Switch to another workspace").arguments("<workspace_name:string:workspace>").action(switchC).command("add").description("Add a workspace").arguments("[workspace_name:string] [workspace_id:string] [remote:string]").option("-c --create", "Create the workspace if it does not exist").option("--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.").option("--create-username <username:string>", "Specify your own username in the newly created workspace. Ignored if --create is not specified, the workspace already exists or automatic username creation is enabled on the instance.", {
28177
28321
  default: "admin"
28178
- }).action(add).command("remove").description("Remove a workspace").arguments("<workspace_name:string>").action(remove).command("whoami").description("Show the currently active user").action(whoami2).command("list").description("List local workspace profiles").action(list3).command("list-remote").description("List workspaces on the remote server that you have access to").option("--as-superadmin", "List ALL workspaces on the instance (requires the token to belong to a superadmin/devops user)").action(listRemote).command("list-forks").description("List forked workspaces on the remote server").action(listForks).command("bind").description("Create or update a workspace entry in wmill.yaml from the active profile").option("--workspace <name:string>", "Workspace name (default: current branch or workspaceId)").option("--branch <branch:string>", "Git branch to associate (default: workspace name)").action((opts) => bind(opts, true)).command("unbind").description("Remove baseUrl and workspaceId from a workspace entry").option("--workspace <name:string>", "Workspace to unbind").action((opts) => bind(opts, false)).command("fork").description("Create a forked workspace").arguments("[workspace_name:string] [workspace_id:string]").option("--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.").option("--color <color:string>", "Workspace color (hex code, e.g. #ff0000)").option("--datatable-behavior <behavior:string>", "How to handle datatables: skip, schema_only, or schema_and_data (default: interactive prompt)").option("-y --yes", "Skip interactive prompts (defaults datatable behavior to 'skip')").action(createWorkspaceFork2).command("delete-fork").description("Delete a forked workspace and git branch").arguments("<fork_name:string>").option("-y --yes", "Skip confirmation prompt").action(deleteWorkspaceFork).command("merge").description("Compare and deploy changes between a fork and its parent workspace").option("--direction <direction:string>", "Deploy direction: to-parent or to-fork").option("--all", "Deploy all changed items including conflicts").option("--skip-conflicts", "Skip items modified in both workspaces").option("--include <items:string>", "Comma-separated kind:path items to include (e.g. script:f/test/main,flow:f/my/flow)").option("--exclude <items:string>", "Comma-separated kind:path items to exclude").option("--preserve-on-behalf-of", "Preserve original on_behalf_of/permissioned_as values").option("-y --yes", "Non-interactive mode (deploy without prompts)").action(mergeWorkspaces).command("connect-slack").description("Non-interactively connect Slack to the active workspace using a pre-minted bot token (xoxb-...). Produces the same artifacts as the UI OAuth flow: workspace_settings fields, g/slack group, f/slack_bot folder, and the encrypted bot token variable + resource at f/slack_bot/bot_token.").option("--bot-token <bot_token:string>", "Slack bot token (xoxb-...)", { required: true }).option("--team-id <team_id:string>", "Slack team id", { required: true }).option("--team-name <team_name:string>", "Slack team name", { required: true }).action(connectSlack2).command("disconnect-slack").description("Clear slack_team_id / slack_name on the active workspace (marks the workspace as disconnected). Does NOT remove the bot token variable/resource/folder/group — delete those from the local sync folder and run 'wmill sync push' to tear them down. Does NOT remove the workspace-level OAuth override — set slack_oauth_client_id/_secret to '' in settings.yaml and push.").action(disconnectSlack2);
28322
+ }).action(add).command("remove").description("Remove a workspace").arguments("<workspace_name:string>").action(remove).command("whoami").description("Show the currently active user").action(whoami2).command("list").description("List local workspace profiles").action(list3).command("list-remote").description("List workspaces on the remote server that you have access to").option("--as-superadmin", "List ALL workspaces on the instance (requires the token to belong to a superadmin/devops user)").action(listRemote).command("list-forks").description("List forked workspaces on the remote server").action(listForks).command("bind").description("Create or update a workspace entry in wmill.yaml from the active profile").option("--workspace <name:string>", "Workspace name (default: current branch or workspaceId)").option("--branch <branch:string>", "Git branch to associate (default: workspace name)").action((opts) => bind(opts, true)).command("unbind").description("Remove baseUrl and workspaceId from a workspace entry").option("--workspace <name:string>", "Workspace to unbind").action((opts) => bind(opts, false)).command("fork").description("Create a forked workspace").arguments("[workspace_name:string] [workspace_id:string]").option("--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.").option("--color <color:string>", "Workspace color (hex code, e.g. #ff0000)").option("--datatable-behavior <behavior:string>", "How to handle datatables: skip, schema_only, or schema_and_data (default: interactive prompt)").option("--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.").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(createWorkspaceFork2).command("delete-fork").description("Delete a forked workspace and git branch").arguments("<fork_name:string>").option("-y --yes", "Skip confirmation prompt").action(deleteWorkspaceFork).command("merge").description("Compare and deploy changes between a fork and its parent workspace").option("--direction <direction:string>", "Deploy direction: to-parent or to-fork").option("--all", "Deploy all changed items including conflicts").option("--skip-conflicts", "Skip items modified in both workspaces").option("--include <items:string>", "Comma-separated kind:path items to include (e.g. script:f/test/main,flow:f/my/flow)").option("--exclude <items:string>", "Comma-separated kind:path items to exclude").option("--preserve-on-behalf-of", "Preserve original on_behalf_of/permissioned_as values").option("-y --yes", "Non-interactive mode (deploy without prompts)").action(mergeWorkspaces).command("connect-slack").description("Non-interactively connect Slack to the active workspace using a pre-minted bot token (xoxb-...). Produces the same artifacts as the UI OAuth flow: workspace_settings fields, g/slack group, f/slack_bot folder, and the encrypted bot token variable + resource at f/slack_bot/bot_token.").option("--bot-token <bot_token:string>", "Slack bot token (xoxb-...)", { required: true }).option("--team-id <team_id:string>", "Slack team id", { required: true }).option("--team-name <team_name:string>", "Slack team name", { required: true }).action(connectSlack2).command("disconnect-slack").description("Clear slack_team_id / slack_name on the active workspace (marks the workspace as disconnected). Does NOT remove the bot token variable/resource/folder/group — delete those from the local sync folder and run 'wmill sync push' to tear them down. Does NOT remove the workspace-level OAuth override — set slack_oauth_client_id/_secret to '' in settings.yaml and push.").action(disconnectSlack2);
28179
28323
  workspace_default = command2;
28180
28324
  });
28181
28325
 
@@ -33798,7 +33942,7 @@ var init_wrapper = __esm(() => {
33798
33942
  // src/commands/app/bundle.ts
33799
33943
  import * as fs7 from "node:fs";
33800
33944
  import * as path4 from "node:path";
33801
- import process12 from "node:process";
33945
+ import process13 from "node:process";
33802
33946
  import { spawn } from "node:child_process";
33803
33947
  function detectFrameworks(appDir) {
33804
33948
  const packageJsonPath = path4.join(appDir, "package.json");
@@ -33826,7 +33970,7 @@ function createSveltePlugin(appDir) {
33826
33970
  build.onLoad({ filter: /\.svelte$/ }, async (args) => {
33827
33971
  const svelte = await import("svelte/compiler");
33828
33972
  const source = await readTextFile(args.path);
33829
- const filename = path4.relative(process12.cwd(), args.path);
33973
+ const filename = path4.relative(process13.cwd(), args.path);
33830
33974
  const convertMessage = ({ message, start, end }) => {
33831
33975
  let location;
33832
33976
  if (start && end) {
@@ -33867,7 +34011,7 @@ async function createFrameworkPlugins(appDir) {
33867
34011
  return plugins;
33868
34012
  }
33869
34013
  async function ensureNodeModules(appDir) {
33870
- const targetDir = appDir ?? process12.cwd();
34014
+ const targetDir = appDir ?? process13.cwd();
33871
34015
  const nodeModulesPath = path4.join(targetDir, "node_modules");
33872
34016
  if (!fs7.existsSync(nodeModulesPath)) {
33873
34017
  info(colors.yellow("\uD83D\uDCE6 node_modules not found, running npm install..."));
@@ -33888,7 +34032,7 @@ async function ensureNodeModules(appDir) {
33888
34032
  }
33889
34033
  async function createBundle(options = {}) {
33890
34034
  const esbuild = await import("esbuild");
33891
- const appDir = options.entryPoint ? path4.dirname(options.entryPoint) : process12.cwd();
34035
+ const appDir = options.entryPoint ? path4.dirname(options.entryPoint) : process13.cwd();
33892
34036
  const frameworks = detectFrameworks(appDir);
33893
34037
  const defaultEntry = frameworks.svelte || frameworks.vue ? "index.ts" : "index.tsx";
33894
34038
  const entryPoint = options.entryPoint ?? defaultEntry;
@@ -33901,7 +34045,7 @@ async function createBundle(options = {}) {
33901
34045
  }
33902
34046
  await ensureNodeModules(appDir);
33903
34047
  const frameworkPlugins = await createFrameworkPlugins(appDir);
33904
- const distDir = path4.join(process12.cwd(), outDir);
34048
+ const distDir = path4.join(process13.cwd(), outDir);
33905
34049
  if (!fs7.existsSync(distDir)) {
33906
34050
  fs7.mkdirSync(distDir, { recursive: true });
33907
34051
  }
@@ -33977,8 +34121,8 @@ async function createBundle(options = {}) {
33977
34121
  throw new Error("Build failed with errors");
33978
34122
  }
33979
34123
  info(colors.green("✅ Bundle created successfully"));
33980
- const jsPath = path4.join(process12.cwd(), outfile);
33981
- const cssPath = path4.join(process12.cwd(), outDir, "bundle.css");
34124
+ const jsPath = path4.join(process13.cwd(), outfile);
34125
+ const cssPath = path4.join(process13.cwd(), outDir, "bundle.css");
33982
34126
  if (!fs7.existsSync(jsPath)) {
33983
34127
  throw new Error(`Expected JS bundle at ${jsPath} but file not found`);
33984
34128
  }
@@ -64780,7 +64924,7 @@ var init_script = __esm(async () => {
64780
64924
 
64781
64925
  // src/commands/lint/lint.ts
64782
64926
  import { stat as stat5, readdir as readdir2 } from "node:fs/promises";
64783
- import process13 from "node:process";
64927
+ import process14 from "node:process";
64784
64928
  import * as path8 from "node:path";
64785
64929
  import { sep as SEP7 } from "node:path";
64786
64930
  function normalizePath(p) {
@@ -65014,8 +65158,8 @@ async function checkRawAppRunnables(backendDir, rawAppYamlPath, defaultTs) {
65014
65158
  return issues;
65015
65159
  }
65016
65160
  async function checkMissingLocks(opts, directory) {
65017
- const initialCwd = process13.cwd();
65018
- const targetDirectory = directory ? path8.resolve(initialCwd, directory) : process13.cwd();
65161
+ const initialCwd = process14.cwd();
65162
+ const targetDirectory = directory ? path8.resolve(initialCwd, directory) : process14.cwd();
65019
65163
  const { ...syncOpts } = opts;
65020
65164
  const mergedOpts = await mergeConfigWithConfigFile(syncOpts);
65021
65165
  const ignore = await ignoreF(mergedOpts);
@@ -65151,11 +65295,11 @@ async function checkMissingLocks(opts, directory) {
65151
65295
  return issues;
65152
65296
  }
65153
65297
  async function runLint(opts, directory) {
65154
- const initialCwd = process13.cwd();
65298
+ const initialCwd = process14.cwd();
65155
65299
  const explicitTargetDirectory = directory ? path8.resolve(initialCwd, directory) : undefined;
65156
65300
  const { json: _json, ...syncOpts } = opts;
65157
65301
  const mergedOpts = await mergeConfigWithConfigFile(syncOpts);
65158
- const targetDirectory = explicitTargetDirectory ?? process13.cwd();
65302
+ const targetDirectory = explicitTargetDirectory ?? process14.cwd();
65159
65303
  const stats = await stat5(targetDirectory).catch(() => null);
65160
65304
  if (!stats) {
65161
65305
  throw new Error(`Directory not found: ${targetDirectory}`);
@@ -65270,7 +65414,7 @@ async function lint(opts, directory) {
65270
65414
  const report = await runLint(opts, directory);
65271
65415
  printReport(report, !!opts.json);
65272
65416
  if (report.exitCode !== 0) {
65273
- process13.exit(report.exitCode);
65417
+ process14.exit(report.exitCode);
65274
65418
  }
65275
65419
  } catch (error2) {
65276
65420
  const message = error2 instanceof Error ? error2.message : String(error2);
@@ -65283,17 +65427,17 @@ async function lint(opts, directory) {
65283
65427
  } else {
65284
65428
  error(colors.red(`❌ ${message}`));
65285
65429
  }
65286
- process13.exit(1);
65430
+ process14.exit(1);
65287
65431
  }
65288
65432
  }
65289
65433
  async function lintWatch(opts, directory) {
65290
65434
  const { watch } = await import("node:fs");
65291
- const targetDir = directory ? path8.resolve(process13.cwd(), directory) : process13.cwd();
65435
+ const targetDir = directory ? path8.resolve(process14.cwd(), directory) : process14.cwd();
65292
65436
  info(colors.blue(`Watching ${targetDir} for changes... (Ctrl+C to stop)`));
65293
65437
  async function runAndReport() {
65294
65438
  try {
65295
65439
  const report = await runLint(opts, directory);
65296
- process13.stdout.write("\x1Bc");
65440
+ process14.stdout.write("\x1Bc");
65297
65441
  info(colors.gray(`[${new Date().toLocaleTimeString()}] Lint results:
65298
65442
  `));
65299
65443
  printReport(report, false);
@@ -70757,7 +70901,7 @@ var init_app_metadata = __esm(async () => {
70757
70901
  import * as fs10 from "node:fs";
70758
70902
  import { writeFile as writeFile9 } from "node:fs/promises";
70759
70903
  import path14 from "node:path";
70760
- import process14 from "node:process";
70904
+ import process15 from "node:process";
70761
70905
  function generateDatatablesMarkdown(schemas, localData) {
70762
70906
  const defaultDatatable = localData?.datatable;
70763
70907
  const defaultSchema = localData?.schema;
@@ -70905,7 +71049,7 @@ async function regenerateAgentDocs(workspaceId, targetDir, silent = false) {
70905
71049
  }
70906
71050
  }
70907
71051
  async function generateAgents(opts, appFolder) {
70908
- const cwd = process14.cwd();
71052
+ const cwd = process15.cwd();
70909
71053
  let targetDir = cwd;
70910
71054
  if (appFolder) {
70911
71055
  targetDir = path14.isAbsolute(appFolder) ? appFolder : path14.join(cwd, appFolder);
@@ -70916,13 +71060,13 @@ async function generateAgents(opts, appFolder) {
70916
71060
  if (!hasFolderSuffix(path14.basename(cwd), "raw_app") && !appFolder) {
70917
71061
  error(colors.red(`Error: Must be run inside a ${getFolderSuffix("raw_app")} folder or specify one as argument.`));
70918
71062
  info(colors.gray("Usage: wmill app generate-agents [app_folder]"));
70919
- process14.exit(1);
71063
+ process15.exit(1);
70920
71064
  }
70921
71065
  }
70922
71066
  const rawAppPath = path14.join(targetDir, "raw_app.yaml");
70923
71067
  if (!fs10.existsSync(rawAppPath)) {
70924
71068
  error(colors.red(`Error: raw_app.yaml not found in ${targetDir}`));
70925
- process14.exit(1);
71069
+ process15.exit(1);
70926
71070
  }
70927
71071
  const workspace = await resolveWorkspace(opts);
70928
71072
  await requireLogin(opts);
@@ -70950,18 +71094,18 @@ import { sep as SEP13 } from "node:path";
70950
71094
  import * as http2 from "node:http";
70951
71095
  import * as fs11 from "node:fs";
70952
71096
  import * as path15 from "node:path";
70953
- import process15 from "node:process";
71097
+ import process16 from "node:process";
70954
71098
  import { writeFileSync as writeFileSync5 } from "node:fs";
70955
71099
  async function dev(opts, appFolder) {
70956
71100
  GLOBAL_CONFIG_OPT.noCdToRoot = true;
70957
71101
  await loadNonDottedPathsSetting();
70958
- const originalCwd = process15.cwd();
71102
+ const originalCwd = process16.cwd();
70959
71103
  let targetDir = originalCwd;
70960
71104
  if (appFolder) {
70961
71105
  targetDir = path15.isAbsolute(appFolder) ? appFolder : path15.join(originalCwd, appFolder);
70962
71106
  if (!fs11.existsSync(targetDir)) {
70963
71107
  error(colors.red(`Error: Directory not found: ${targetDir}`));
70964
- process15.exit(1);
71108
+ process16.exit(1);
70965
71109
  }
70966
71110
  }
70967
71111
  const targetDirName = path15.basename(targetDir);
@@ -70969,13 +71113,13 @@ async function dev(opts, appFolder) {
70969
71113
  error(colors.red(`Error: The dev command must be run inside a ${getFolderSuffix("raw_app")} folder.
70970
71114
  ` + `Target directory: ${targetDirName}
70971
71115
  ` + `Please navigate to a folder ending with '${getFolderSuffix("raw_app")}' or specify one as argument.`));
70972
- process15.exit(1);
71116
+ process16.exit(1);
70973
71117
  }
70974
71118
  const rawAppPath = path15.join(targetDir, "raw_app.yaml");
70975
71119
  if (!fs11.existsSync(rawAppPath)) {
70976
71120
  error(colors.red(`Error: raw_app.yaml not found in ${targetDir}.
70977
71121
  ` + `The dev command requires a ${getFolderSuffix("raw_app")} folder containing a raw_app.yaml file.`));
70978
- process15.exit(1);
71122
+ process16.exit(1);
70979
71123
  }
70980
71124
  const workspace = await resolveWorkspace(opts);
70981
71125
  await requireLogin(opts);
@@ -70988,20 +71132,20 @@ async function dev(opts, appFolder) {
70988
71132
  const mergedOpts = await mergeConfigWithConfigFile(opts);
70989
71133
  const codebases = await listSyncCodebases(mergedOpts);
70990
71134
  const { buildPreviewTempScriptRefs: buildPreviewTempScriptRefs2 } = await init_generate_metadata().then(() => exports_generate_metadata);
70991
- const savedCwd = process15.cwd();
71135
+ const savedCwd = process16.cwd();
70992
71136
  if (workspaceRoot !== savedCwd) {
70993
- process15.chdir(workspaceRoot);
71137
+ process16.chdir(workspaceRoot);
70994
71138
  }
70995
71139
  try {
70996
71140
  appTempRefs = await buildPreviewTempScriptRefs2(workspace, mergedOpts, codebases, { kind: "app", folder: relAppFolder, rawApp: true });
70997
71141
  } finally {
70998
71142
  if (workspaceRoot !== savedCwd) {
70999
- process15.chdir(savedCwd);
71143
+ process16.chdir(savedCwd);
71000
71144
  }
71001
71145
  }
71002
71146
  }
71003
71147
  if (appFolder) {
71004
- process15.chdir(targetDir);
71148
+ process16.chdir(targetDir);
71005
71149
  }
71006
71150
  const rawApp = await yamlParseFile(rawAppPath);
71007
71151
  const appPath = rawApp?.custom_path ?? "u/unknown/newapp";
@@ -71015,30 +71159,30 @@ async function dev(opts, appFolder) {
71015
71159
  port: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((p) => p + DEFAULT_PORT)
71016
71160
  });
71017
71161
  const shouldOpen = opts.open ?? true;
71018
- const frameworks = detectFrameworks(process15.cwd());
71162
+ const frameworks = detectFrameworks(process16.cwd());
71019
71163
  const defaultEntry = frameworks.svelte || frameworks.vue ? "index.ts" : "index.tsx";
71020
71164
  const entryPoint = opts.entry ?? defaultEntry;
71021
71165
  if (!fs11.existsSync(entryPoint)) {
71022
71166
  error(colors.red(`Entry point "${entryPoint}" not found. Please specify a valid entry point with --entry.`));
71023
- process15.exit(1);
71167
+ process16.exit(1);
71024
71168
  }
71025
- const appDir = path15.dirname(entryPoint) || process15.cwd();
71169
+ const appDir = path15.dirname(entryPoint) || process16.cwd();
71026
71170
  await ensureNodeModules(appDir);
71027
71171
  const inferredSchemas = {};
71028
71172
  try {
71029
- Object.assign(inferredSchemas, await inferAllInlineSchemas(process15.cwd()));
71173
+ Object.assign(inferredSchemas, await inferAllInlineSchemas(process16.cwd()));
71030
71174
  } catch (err) {
71031
71175
  warn(colors.yellow(`Could not seed inline schemas at startup: ${err.message}`));
71032
71176
  }
71033
71177
  const pathRunnableSchemas = {};
71034
71178
  try {
71035
- const initialRunnables = await loadRunnablesFromBackend(path15.join(process15.cwd(), APP_BACKEND_FOLDER));
71179
+ const initialRunnables = await loadRunnablesFromBackend(path15.join(process16.cwd(), APP_BACKEND_FOLDER));
71036
71180
  Object.assign(pathRunnableSchemas, await fetchPathRunnableSchemas(workspaceId, initialRunnables));
71037
71181
  } catch (err) {
71038
71182
  warn(colors.yellow(`Could not fetch schemas for path-based runnables: ${err.message}`));
71039
71183
  }
71040
71184
  await genRunnablesTs(inferredSchemas, pathRunnableSchemas);
71041
- const distDir = path15.join(process15.cwd(), "dist");
71185
+ const distDir = path15.join(process16.cwd(), "dist");
71042
71186
  if (!fs11.existsSync(distDir)) {
71043
71187
  fs11.mkdirSync(distDir);
71044
71188
  }
@@ -71101,7 +71245,7 @@ data: reload
71101
71245
  info(colors.blue(`\uD83D\uDC40 Watching for file changes...
71102
71246
  `));
71103
71247
  await ctx.rebuild();
71104
- const runnablesPath = path15.join(process15.cwd(), APP_BACKEND_FOLDER);
71248
+ const runnablesPath = path15.join(process16.cwd(), APP_BACKEND_FOLDER);
71105
71249
  let runnablesWatcher;
71106
71250
  if (fs11.existsSync(runnablesPath)) {
71107
71251
  info(colors.blue(`\uD83D\uDC41️ Watching runnables folder at: ${runnablesPath}
@@ -71114,7 +71258,7 @@ data: reload
71114
71258
  return;
71115
71259
  const fileStr = typeof filename === "string" ? filename : filename.toString();
71116
71260
  const changedPath = path15.join(runnablesPath, fileStr);
71117
- const relativePath = path15.relative(process15.cwd(), changedPath);
71261
+ const relativePath = path15.relative(process16.cwd(), changedPath);
71118
71262
  const relativeToRunnables = fileStr;
71119
71263
  if (changedPath.endsWith(".lock")) {
71120
71264
  return;
@@ -71127,7 +71271,7 @@ data: reload
71127
71271
  delete schemaInferenceTimeouts[changedPath];
71128
71272
  try {
71129
71273
  info(colors.cyan(`\uD83D\uDCDD Inferring schema for: ${relativeToRunnables}`));
71130
- const result = await inferRunnableSchemaFromFile(process15.cwd(), relativeToRunnables);
71274
+ const result = await inferRunnableSchemaFromFile(process16.cwd(), relativeToRunnables);
71131
71275
  if (result) {
71132
71276
  inferredSchemas[result.runnableId] = result.schema;
71133
71277
  info(colors.green(` Inferred Schemas: ${JSON.stringify(inferredSchemas, null, 2)}`));
@@ -71165,7 +71309,7 @@ data: reload
71165
71309
  return;
71166
71310
  }
71167
71311
  if (url === "/dist/bundle.js" || url === "/bundle.js") {
71168
- const jsPath = path15.join(process15.cwd(), "dist/bundle.js");
71312
+ const jsPath = path15.join(process16.cwd(), "dist/bundle.js");
71169
71313
  if (fs11.existsSync(jsPath)) {
71170
71314
  res.writeHead(200, { "Content-Type": "application/javascript" });
71171
71315
  res.end(fs11.readFileSync(jsPath));
@@ -71176,7 +71320,7 @@ data: reload
71176
71320
  return;
71177
71321
  }
71178
71322
  if (url === "/dist/bundle.css" || url === "/bundle.css") {
71179
- const cssPath = path15.join(process15.cwd(), "dist/bundle.css");
71323
+ const cssPath = path15.join(process16.cwd(), "dist/bundle.css");
71180
71324
  if (fs11.existsSync(cssPath)) {
71181
71325
  res.writeHead(200, { "Content-Type": "text/css" });
71182
71326
  res.end(fs11.readFileSync(cssPath));
@@ -71187,7 +71331,7 @@ data: reload
71187
71331
  return;
71188
71332
  }
71189
71333
  if (url === "/dist/bundle.js.map" || url === "/bundle.js.map") {
71190
- const mapPath = path15.join(process15.cwd(), "dist/bundle.js.map");
71334
+ const mapPath = path15.join(process16.cwd(), "dist/bundle.js.map");
71191
71335
  if (fs11.existsSync(mapPath)) {
71192
71336
  res.writeHead(200, { "Content-Type": "application/json" });
71193
71337
  res.end(fs11.readFileSync(mapPath));
@@ -71198,7 +71342,7 @@ data: reload
71198
71342
  return;
71199
71343
  }
71200
71344
  if (url === "/dist/bundle.css.map" || url === "/bundle.css.map") {
71201
- const mapPath = path15.join(process15.cwd(), "dist/bundle.css.map");
71345
+ const mapPath = path15.join(process16.cwd(), "dist/bundle.css.map");
71202
71346
  if (fs11.existsSync(mapPath)) {
71203
71347
  res.writeHead(200, { "Content-Type": "application/json" });
71204
71348
  res.end(fs11.readFileSync(mapPath));
@@ -71212,12 +71356,12 @@ data: reload
71212
71356
  res.end(createHTML("/dist/bundle.js", "/dist/bundle.css"));
71213
71357
  });
71214
71358
  const wss = new import_websocket_server.default({ server });
71215
- const sqlToApplyPath = path15.join(process15.cwd(), "sql_to_apply");
71359
+ const sqlToApplyPath = path15.join(process16.cwd(), "sql_to_apply");
71216
71360
  const sqlFileQueue = [];
71217
71361
  let currentSqlFile = null;
71218
71362
  async function getDatatableConfig() {
71219
71363
  try {
71220
- const rawApp2 = await yamlParseFile(path15.join(process15.cwd(), "raw_app.yaml"));
71364
+ const rawApp2 = await yamlParseFile(path15.join(process16.cwd(), "raw_app.yaml"));
71221
71365
  return rawApp2?.data?.datatable;
71222
71366
  } catch {
71223
71367
  return;
@@ -71436,7 +71580,7 @@ data: reload
71436
71580
  await onSqlFileCompleted(currentSqlFile, true);
71437
71581
  }
71438
71582
  try {
71439
- await regenerateAgentDocs(workspaceId, process15.cwd(), true);
71583
+ await regenerateAgentDocs(workspaceId, process16.cwd(), true);
71440
71584
  info(colors.gray(`[SQL Migration] Refreshed AGENTS.md and DATATABLES.md`));
71441
71585
  } catch (regenError) {
71442
71586
  warn(colors.yellow(`[SQL Migration] Could not refresh docs: ${regenError.message}`));
@@ -71535,7 +71679,7 @@ data: reload
71535
71679
  const url = `http://${host}:${port}`;
71536
71680
  info(colors.bold.green(`\uD83D\uDE80 Dev server running at ${url}`));
71537
71681
  info(colors.cyan(`\uD83D\uDD0C WebSocket server running at ws://${host}:${port}`));
71538
- info(colors.gray(`\uD83D\uDCE6 Serving files from: ${process15.cwd()}`));
71682
+ info(colors.gray(`\uD83D\uDCE6 Serving files from: ${process16.cwd()}`));
71539
71683
  info(colors.gray(`\uD83D\uDD04 Live reload enabled
71540
71684
  `));
71541
71685
  if (shouldOpen) {
@@ -71549,7 +71693,7 @@ data: reload
71549
71693
  }
71550
71694
  }
71551
71695
  });
71552
- process15.on("SIGINT", async () => {
71696
+ process16.on("SIGINT", async () => {
71553
71697
  info(colors.yellow(`
71554
71698
 
71555
71699
  \uD83D\uDED1 Shutting down...`));
@@ -71562,12 +71706,12 @@ data: reload
71562
71706
  sqlWatcher.close();
71563
71707
  }
71564
71708
  await ctx.dispose();
71565
- process15.exit(0);
71709
+ process16.exit(0);
71566
71710
  });
71567
71711
  }
71568
71712
  async function genRunnablesTs(inlineSchemaOverrides = {}, pathSchemaOverrides = {}) {
71569
71713
  info(colors.blue("\uD83D\uDD04 Generating wmill.d.ts..."));
71570
- const localPath = process15.cwd();
71714
+ const localPath = process16.cwd();
71571
71715
  const backendPath = path15.join(localPath, APP_BACKEND_FOLDER);
71572
71716
  let runnables = await loadRunnablesFromBackend(backendPath);
71573
71717
  if (Object.keys(runnables).length === 0) {
@@ -71580,7 +71724,7 @@ async function genRunnablesTs(inlineSchemaOverrides = {}, pathSchemaOverrides =
71580
71724
  }
71581
71725
  try {
71582
71726
  const newWmillTs = buildWmillTs(runnables, inlineSchemaOverrides, pathSchemaOverrides);
71583
- writeFileSync5(path15.join(process15.cwd(), "wmill.d.ts"), newWmillTs);
71727
+ writeFileSync5(path15.join(process16.cwd(), "wmill.d.ts"), newWmillTs);
71584
71728
  } catch (error2) {
71585
71729
  error(colors.red(`Failed to generate wmill.d.ts: ${error2.message}`));
71586
71730
  }
@@ -71649,7 +71793,7 @@ function convertRunnablesToApiFormat(runnables) {
71649
71793
  }
71650
71794
  async function loadRunnables() {
71651
71795
  try {
71652
- const localPath = process15.cwd();
71796
+ const localPath = process16.cwd();
71653
71797
  const backendPath = path15.join(localPath, APP_BACKEND_FOLDER);
71654
71798
  let runnables = await loadRunnablesFromBackend(backendPath);
71655
71799
  if (Object.keys(runnables).length === 0) {
@@ -72158,7 +72302,7 @@ var init_dev = __esm(async () => {
72158
72302
  // src/commands/app/lint.ts
72159
72303
  import * as fs12 from "node:fs";
72160
72304
  import * as path16 from "node:path";
72161
- import process16 from "node:process";
72305
+ import process17 from "node:process";
72162
72306
  function validateRawAppYaml(appData) {
72163
72307
  const errors = [];
72164
72308
  const warnings = [];
@@ -72254,7 +72398,7 @@ async function lintRawApp(appDir, opts) {
72254
72398
  }
72255
72399
  async function lint2(opts, appFolder) {
72256
72400
  await loadNonDottedPathsSetting();
72257
- const targetDir = appFolder ?? process16.cwd();
72401
+ const targetDir = appFolder ?? process17.cwd();
72258
72402
  info(colors.bold.blue(`
72259
72403
  \uD83D\uDD0D Linting raw app: ${targetDir}
72260
72404
  `));
@@ -72275,7 +72419,7 @@ async function lint2(opts, appFolder) {
72275
72419
  info(colors.red(`
72276
72420
  ❌ Lint failed
72277
72421
  `));
72278
- process16.exit(1);
72422
+ process17.exit(1);
72279
72423
  }
72280
72424
  info(colors.green(`
72281
72425
  ✅ All checks passed
@@ -73948,7 +74092,7 @@ async function decrypt(combinedCiphertext, keyString) {
73948
74092
  var init_local_encryption = () => {};
73949
74093
 
73950
74094
  // src/core/settings.ts
73951
- import process17 from "node:process";
74095
+ import process18 from "node:process";
73952
74096
  import { writeFile as writeFile14 } from "node:fs/promises";
73953
74097
  function migrateToGroupedFormat(settings) {
73954
74098
  const result = { name: settings.name ?? "" };
@@ -74252,7 +74396,7 @@ async function pushWorkspaceKey(workspace, _path, key, localKey, opts) {
74252
74396
  }
74253
74397
  if (localKey && key !== localKey) {
74254
74398
  let reencrypt;
74255
- const explicitSkip = opts?.skipReencrypt || (process17.env.WMILL_NO_REENCRYPT_ON_KEY_CHANGE ?? "").toLowerCase() === "true";
74399
+ const explicitSkip = opts?.skipReencrypt || (process18.env.WMILL_NO_REENCRYPT_ON_KEY_CHANGE ?? "").toLowerCase() === "true";
74256
74400
  if (explicitSkip) {
74257
74401
  reencrypt = false;
74258
74402
  info("Workspace encryption key changed; leaving remote ciphertexts untouched (skip re-encryption requested).");
@@ -74291,7 +74435,7 @@ async function readInstanceSettings(opts) {
74291
74435
  return localSettings;
74292
74436
  }
74293
74437
  async function processInstanceSettings(settings, mode) {
74294
- const encKey = process17.env.WMILL_INSTANCE_LOCAL_ENCRYPTION_KEY;
74438
+ const encKey = process18.env.WMILL_INSTANCE_LOCAL_ENCRYPTION_KEY;
74295
74439
  if (encKey) {
74296
74440
  const res = [];
74297
74441
  for (const s of settings) {
@@ -77236,7 +77380,7 @@ class GitSyncSettingsConverter {
77236
77380
  }
77237
77381
 
77238
77382
  // src/commands/gitsync-settings/legacySettings.ts
77239
- import process18 from "node:process";
77383
+ import process19 from "node:process";
77240
77384
  async function handleLegacyRepositoryMigration(selectedRepo, gitSyncSettings, workspace, opts, operationName = "operation") {
77241
77385
  if (selectedRepo.settings) {
77242
77386
  return selectedRepo;
@@ -77246,7 +77390,7 @@ async function handleLegacyRepositoryMigration(selectedRepo, gitSyncSettings, wo
77246
77390
  }
77247
77391
  const workspaceIncludePath = gitSyncSettings.include_path;
77248
77392
  const workspaceIncludeType = gitSyncSettings.include_type;
77249
- if (!!process18.stdout.isTTY && !opts.yes) {
77393
+ if (!!process19.stdout.isTTY && !opts.yes) {
77250
77394
  console.log(colors.yellow(`
77251
77395
  ⚠️ Legacy git-sync settings detected!`));
77252
77396
  console.log(`
@@ -77352,7 +77496,7 @@ Repository "${selectedRepo.git_repo_resource_path}" has legacy settings format.`
77352
77496
  console.error(` wmill gitsync-settings push
77353
77497
  `);
77354
77498
  }
77355
- process18.exit(1);
77499
+ process19.exit(1);
77356
77500
  }
77357
77501
  }
77358
77502
  var init_legacySettings = __esm(async () => {
@@ -77808,14 +77952,14 @@ var init_pull2 = __esm(async () => {
77808
77952
  });
77809
77953
 
77810
77954
  // src/commands/gitsync-settings/push.ts
77811
- import process19 from "node:process";
77955
+ import process20 from "node:process";
77812
77956
  async function pushGitSyncSettings(opts) {
77813
77957
  try {
77814
77958
  await validateBranchConfiguration({ yes: opts.yes });
77815
77959
  } catch (error2) {
77816
77960
  if (error2 instanceof Error && error2.message.includes("overrides")) {
77817
77961
  error(error2.message);
77818
- process19.exit(1);
77962
+ process20.exit(1);
77819
77963
  }
77820
77964
  throw error2;
77821
77965
  }
@@ -77825,7 +77969,7 @@ async function pushGitSyncSettings(opts) {
77825
77969
  const wmillYamlPath = getWmillYamlPath();
77826
77970
  if (!wmillYamlPath) {
77827
77971
  error(colors.red("No wmill.yaml file found. Please run 'wmill init' first to create the configuration file."));
77828
- process19.exit(1);
77972
+ process20.exit(1);
77829
77973
  }
77830
77974
  const localConfig = await readConfigFile();
77831
77975
  let settings;
@@ -77941,7 +78085,7 @@ async function pushGitSyncSettings(opts) {
77941
78085
  displayChanges(changes);
77942
78086
  }
77943
78087
  }
77944
- if (!opts.yes && !!process19.stdin.isTTY) {
78088
+ if (!opts.yes && !!process20.stdin.isTTY) {
77945
78089
  const confirmed = await Confirm.prompt({
77946
78090
  message: `Do you want to apply these changes to the remote?`,
77947
78091
  default: true
@@ -78060,7 +78204,7 @@ The line below pulls in Windmill's managed CLI guidance (skills, deploy flow,
78060
78204
  debugging jobs, etc.). Refresh it with \`wmill refresh prompts\`. Remove the
78061
78205
  include line if you don't want the managed guidance in this project.
78062
78206
 
78063
- ${AGENTS_CLI_INCLUDE_LINE}
78207
+ ${AGENTS_WMILL_INCLUDE_LINE}
78064
78208
 
78065
78209
  ## Project-specific instructions
78066
78210
 
@@ -78139,10 +78283,11 @@ There are two ways local changes reach the workspace. Pick based on how the repo
78139
78283
  Before deploying, check whether this repo has a **GitHub Actions (or other CI) workflow that runs \`wmill sync push\` on push**. That workflow is the signal that pushing a branch will deploy:
78140
78284
 
78141
78285
  - Look for \`.github/workflows/*.yml\` (or other CI configs) that invoke \`wmill sync push\`, \`wmill\` deployment commands, or similar.
78142
- - Cache the result for the rest of the session — don't re-scan on every deploy.
78143
78286
 
78144
78287
  If such a workflow exists → **use \`git push\`** (Option A). Otherwise → **use \`wmill sync push\`** directly (Option B).
78145
78288
 
78289
+ **Save the preference so you don't re-detect it every session.** Once you've determined which option this repo uses (or the user tells you), record it in the **project-specific instructions** section of \`AGENTS.md\` (user-owned — never overwritten by \`wmill refresh prompts\`), e.g. a line like \`Deploy mode: git push (CI runs wmill sync push)\` or \`Deploy mode: wmill sync push (no CI wiring)\`. On later sessions, read that line first and skip the scan. Re-detect only if the CI wiring visibly changed.
78290
+
78146
78291
  ### Option A — \`git push\` (CI is wired to sync)
78147
78292
 
78148
78293
  The CI workflow will pick up the commit and run \`wmill sync push\` on the backend, which is how deployments are intended to happen in this repo. Don't bypass it.
@@ -78164,6 +78309,19 @@ No CI workflow runs \`wmill sync push\` automatically, so deploy directly from t
78164
78309
 
78165
78310
  Only deploy when the user explicitly asks to deploy, publish, push, or ship — not when they say "run", "try", or "test". For testing local edits use the per-entity \`preview\` commands (\`wmill script preview\`, \`wmill flow preview\`) — they don't deploy.
78166
78311
 
78312
+ ## Workspace forks
78313
+
78314
+ 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.
78315
+
78316
+ Just run \`wmill workspace fork\` — it adapts to where you are:
78317
+
78318
+ - **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.
78319
+ - **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.
78320
+
78321
+ For non-interactive runs from a working branch, pass \`--from-branch <base>\` to skip the prompts. The CLI refuses to rename a base branch.
78322
+
78323
+ 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
78324
+
78167
78325
  ## Debugging Jobs
78168
78326
 
78169
78327
  When the user reports a script or flow failure, is investigating unexpected output, or asks why something ran the way it did, use the CLI to fetch job details before speculating. See the \`cli-commands\` skill for all flags.
@@ -78194,7 +78352,7 @@ For Windmill concepts not covered by the skills (triggers, schedules, workers, f
78194
78352
  - https://www.windmill.dev/llms-full.txt is the entire documentation as a single ~2.3 MB file — only for bulk indexing/RAG, do NOT load it directly into context.
78195
78353
  `;
78196
78354
  }
78197
- var AGENTS_CLI_INCLUDE_LINE = "@AGENTS.cli.md";
78355
+ var AGENTS_WMILL_FILENAME = "AGENTS.wmill.md", AGENTS_WMILL_INCLUDE_LINE = "@AGENTS.wmill.md", LEGACY_AGENTS_CLI_FILENAME = "AGENTS.cli.md", LEGACY_AGENTS_CLI_INCLUDE_LINE = "@AGENTS.cli.md";
78198
78356
 
78199
78357
  // src/guidance/skills.gen.ts
78200
78358
  var SKILLS, SKILL_CONTENT, SCHEMAS, SCHEMA_MAPPINGS;
@@ -78482,6 +78640,10 @@ import Stripe from "stripe";
78482
78640
  import { someFunction } from "some-package";
78483
78641
  \`\`\`
78484
78642
 
78643
+ ## Prefer \`//native\` when the runtime allows it
78644
+
78645
+ If a script only needs \`fetch\` and the JavaScript standard library — including when it uses \`windmill-client\` — prefer making it a **native** script: add \`//native\` as the first line and write it with the \`write-script-bunnative\` skill. Native scripts run on a lightweight V8 isolate, start faster, and parallelize heavily. \`windmill-client\` works on the native worker (its calls go over \`fetch\`), so needing the Windmill client is **not** a reason to avoid \`//native\`. Use the regular \`bun\` language only when the code (or a dependency) needs Node/Bun runtime APIs — \`node:*\` modules, the filesystem, child processes, or native addons.
78646
+
78485
78647
  ## Windmill Client
78486
78648
 
78487
78649
  Import the windmill client for platform interactions:
@@ -78490,7 +78652,9 @@ Import the windmill client for platform interactions:
78490
78652
  import * as wmill from "windmill-client";
78491
78653
  \`\`\`
78492
78654
 
78493
- See the SDK documentation for available methods.
78655
+ **Prefer \`windmill-client\` over raw \`fetch\` for anything that talks to Windmill** — reading resources/variables/states, running scripts and flows, S3 object operations, etc. It handles auth, the workspace, and the base URL for you, so you don't hand-roll URLs or tokens. Reserve \`fetch\` for calling *external* HTTP APIs that aren't Windmill.
78656
+
78657
+ The full \`windmill-client\` API reference (every exported function and its signature) is included in this skill below — consult it for the exact method to use instead of guessing or falling back to \`fetch\`.
78494
78658
 
78495
78659
  ## Preprocessor Scripts
78496
78660
 
@@ -79203,7 +79367,9 @@ export async function main(url: string) {
79203
79367
 
79204
79368
  ## Windmill Client
79205
79369
 
79206
- \`windmill-client\` is available for Windmill-specific primitives such as the S3 helpers below (\`loadS3File\`, \`loadS3FileStream\`, \`writeS3File\`, \`S3Object\`). Use \`fetch\` for plain HTTP.
79370
+ \`windmill-client\` works on the native worker (its calls go over \`fetch\`), so use it as the **preferred way to talk to Windmill** — reading resources/variables/states, running scripts and flows, and the S3 helpers below (\`loadS3File\`, \`loadS3FileStream\`, \`writeS3File\`, \`S3Object\`). It handles auth, the workspace, and the base URL for you. Reserve raw \`fetch\` for calling *external* HTTP APIs that aren't Windmill.
79371
+
79372
+ The full \`windmill-client\` API reference (every exported function and its signature) is included in this skill below — consult it for the exact method instead of hand-rolling a \`fetch\` against the Windmill API.
79207
79373
 
79208
79374
  ## Preprocessor Scripts
79209
79375
 
@@ -80004,7 +80170,9 @@ Import the windmill client for platform interactions:
80004
80170
  import * as wmill from "windmill-client";
80005
80171
  \`\`\`
80006
80172
 
80007
- See the SDK documentation for available methods.
80173
+ **Prefer \`windmill-client\` over raw \`fetch\` for anything that talks to Windmill** — reading resources/variables/states, running scripts and flows, S3 object operations, etc. It handles auth, the workspace, and the base URL for you. Reserve \`fetch\` for calling *external* HTTP APIs that aren't Windmill.
80174
+
80175
+ The full \`windmill-client\` API reference (every exported function and its signature) is included in this skill below — consult it for the exact method instead of guessing or falling back to \`fetch\`.
80008
80176
 
80009
80177
  ## Preprocessor Scripts
80010
80178
 
@@ -82685,7 +82853,7 @@ Once the flow has real content, **offer** to open the visual preview as a one-se
82685
82853
 
82686
82854
  ## CLI Commands — running, previewing, deploying
82687
82855
 
82688
- After writing, tell the user which command fits what they want to do:
82856
+ After writing, act on the user's intent instead of just listing commands. Run the safe, non-deploying command yourself when it fits (\`wmill flow preview\` — see "After writing — offer to run, don't wait passively" below); only *name* the commands that deploy or rewrite files (\`wmill sync push\`, \`wmill generate-metadata\`) so the user can approve them. The options:
82689
82857
 
82690
82858
  - \`wmill flow preview <flow_path>\` — **default when iterating on a local flow.** Runs the local \`flow.yaml\` against local inline scripts without deploying. Add \`--remote\` to use deployed workspace scripts for PathScript steps instead of local files. Add \`--step <step_id>\` to run only one module in isolation (see "Single-step vs whole-flow preview" below).
82691
82859
  - \`wmill flow run <path>\` — runs the flow **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits.
@@ -83151,7 +83319,7 @@ The runnable ID is the filename without extension. For example, \`get_user.ts\`
83151
83319
  | C# | \`.cs\` | \`myFunc.cs\` |
83152
83320
  | Java | \`.java\` | \`myFunc.java\` |
83153
83321
 
83154
- After creating a runnable, tell the user they can generate lock files by running:
83322
+ After creating a runnable, offer to generate its lock files as a one-sentence next step (e.g. "Want me to generate the lock files?") and run it yourself once they agree don't just name the command and wait. If the user already asked you to finish/lock the app, run it directly. It writes local lock files (not a deploy), so offer rather than running silently:
83155
83323
  \`\`\`bash
83156
83324
  wmill generate-metadata
83157
83325
  \`\`\`
@@ -83242,15 +83410,16 @@ data:
83242
83410
 
83243
83411
  ## CLI Commands
83244
83412
 
83245
- \`wmill app new\` is the exception: you run it yourself, with flags, per the "Creating a Raw App" section above.
83413
+ Two commands you run yourself, not the user:
83414
+ - \`wmill app new\` — run it with flags, per the "Creating a Raw App" section above.
83415
+ - \`wmill generate-metadata\` — generates local lock files; offer it and run it on consent, per "After creating a runnable" above (it writes local lock files, not a deploy).
83246
83416
 
83247
- For everything else, tell the user which command fits their intent and let them run it — these touch the workspace or local lock files, and the user should consent each time:
83417
+ For the rest, tell the user which command fits their intent and let them run it — these deploy to the workspace, overwrite local files, or launch a long-running server, so the user should consent each time:
83248
83418
 
83249
83419
  | Command | Description |
83250
83420
  |---------|-------------|
83251
83421
  | \`wmill app dev\` | Start dev server with live reload (see the \`preview\` skill for the full open-the-app-in-the-IDE-pane procedure). |
83252
83422
  | \`wmill app generate-agents\` | Refresh AGENTS.md and DATATABLES.md |
83253
- | \`wmill generate-metadata\` | Generate lock files for backend runnables |
83254
83423
  | \`wmill sync push\` | Deploy app to Windmill |
83255
83424
  | \`wmill sync pull\` | Pull latest from Windmill |
83256
83425
 
@@ -84647,12 +84816,12 @@ List all queues with their metrics
84647
84816
 
84648
84817
  ### refresh
84649
84818
 
84650
- Refresh wmill-managed project files (AGENTS.cli.md, skills, tsconfig.wmill.json)
84819
+ Refresh wmill-managed project files (AGENTS.wmill.md, skills, tsconfig.wmill.json)
84651
84820
 
84652
84821
  **Subcommands:**
84653
84822
 
84654
- - \`refresh prompts\` - Refresh AGENTS.cli.md and managed skills. User-owned AGENTS.md and CLAUDE.md are never overwritten unless you opt in.
84655
- - \`--yes\` - Non-interactive: append the @AGENTS.cli.md include to an existing AGENTS.md / CLAUDE.md without prompting. Without it, a non-interactive run leaves an unlinked file untouched.
84823
+ - \`refresh prompts\` - Refresh AGENTS.wmill.md and managed skills. User-owned AGENTS.md and CLAUDE.md are never overwritten unless you opt in.
84824
+ - \`--yes\` - Non-interactive: append the @AGENTS.wmill.md include to an existing AGENTS.md / CLAUDE.md without prompting. Without it, a non-interactive run leaves an unlinked file untouched.
84656
84825
  - \`refresh tsconfig\` - Refresh the wmill-managed tsconfig.wmill.json (and Deno import map for Deno projects)
84657
84826
  - \`--yes\` - Non-interactive: wire an existing custom tsconfig.json/deno.json to the managed file without prompting (a previously-generated config is always migrated automatically).
84658
84827
 
@@ -84943,7 +85112,8 @@ workspace related commands
84943
85112
  - \`--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.
84944
85113
  - \`--color <color:string>\` - Workspace color (hex code, e.g. #ff0000)
84945
85114
  - \`--datatable-behavior <behavior:string>\` - How to handle datatables: skip, schema_only, or schema_and_data (default: interactive prompt)
84946
- - \`-y --yes\` - Skip interactive prompts (defaults datatable behavior to 'skip')
85115
+ - \`--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.
85116
+ - \`-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.
84947
85117
  - \`workspace delete-fork <fork_name:string>\` - Delete a forked workspace and git branch
84948
85118
  - \`-y --yes\` - Skip confirmation prompt
84949
85119
  - \`workspace merge\` - Compare and deploy changes between a fork and its parent workspace
@@ -86304,9 +86474,15 @@ async function warnIfPromptsStale(opts) {
86304
86474
  if (opts?.argv && !shouldRunFreshnessCheck(opts.argv))
86305
86475
  return;
86306
86476
  const cwd = opts?.cwd ?? process.cwd();
86307
- const path21 = `${cwd}/AGENTS.cli.md`;
86308
- if (!await stat18(path21).catch(() => null))
86309
- return;
86477
+ let fileName = AGENTS_WMILL_FILENAME;
86478
+ let path21 = `${cwd}/${fileName}`;
86479
+ if (!await stat18(path21).catch(() => null)) {
86480
+ fileName = LEGACY_AGENTS_CLI_FILENAME;
86481
+ path21 = `${cwd}/${fileName}`;
86482
+ if (!await stat18(path21).catch(() => null)) {
86483
+ return;
86484
+ }
86485
+ }
86310
86486
  let content;
86311
86487
  try {
86312
86488
  content = await readTextFile(path21);
@@ -86315,7 +86491,7 @@ async function warnIfPromptsStale(opts) {
86315
86491
  }
86316
86492
  const stored = extractPromptsHash(content);
86317
86493
  if (!stored) {
86318
- emitWarning("Your AGENTS.cli.md predates prompt versioning. Run `wmill refresh prompts` to refresh and add a version marker.");
86494
+ emitWarning(`Your ${fileName} predates prompt versioning. Run \`wmill refresh prompts\` to refresh and add a version marker.`);
86319
86495
  return;
86320
86496
  }
86321
86497
  let nonDottedPaths = opts?.nonDottedPaths;
@@ -86330,7 +86506,7 @@ async function warnIfPromptsStale(opts) {
86330
86506
  }
86331
86507
  const current = currentPromptsHash(nonDottedPaths);
86332
86508
  if (stored !== current) {
86333
- emitWarning("Your AGENTS.cli.md is out of date. Run `wmill refresh prompts` to refresh.");
86509
+ emitWarning(`Your ${fileName} is out of date. Run \`wmill refresh prompts\` to refresh.`);
86334
86510
  }
86335
86511
  }
86336
86512
  function emitWarning(message) {
@@ -86357,7 +86533,7 @@ import { execSync as execSync6 } from "node:child_process";
86357
86533
  import { createHash as createHash2 } from "node:crypto";
86358
86534
  import { existsSync as existsSync12, readFileSync as readFileSync4, writeFileSync as writeFileSync7 } from "node:fs";
86359
86535
  import path21 from "node:path";
86360
- import process22 from "node:process";
86536
+ import process23 from "node:process";
86361
86537
  function buildManagedTsconfig() {
86362
86538
  const paths = {};
86363
86539
  for (const dir of WORKSPACE_IMPORT_DIRS) {
@@ -86408,7 +86584,7 @@ async function refreshTsconfig(opts) {
86408
86584
  const assumeYes = opts?.yes === true;
86409
86585
  const mode = {
86410
86586
  assumeYes,
86411
- interactive: !!process22.stdin.isTTY && !assumeYes
86587
+ interactive: !!process23.stdin.isTTY && !assumeYes
86412
86588
  };
86413
86589
  await refreshManagedTsconfig(defaultTs, mode);
86414
86590
  if (defaultTs === "deno") {
@@ -86423,7 +86599,7 @@ async function refreshManagedTsconfig(defaultTs, mode) {
86423
86599
  }
86424
86600
  const header = MANAGED_NOTICE + TSCONFIG_HASH_PREFIX + currentTsconfigHash() + `
86425
86601
  `;
86426
- writeFileSync7(path21.join(process22.cwd(), MANAGED_TSCONFIG), header + JSON.stringify(managed, null, 2) + `
86602
+ writeFileSync7(path21.join(process23.cwd(), MANAGED_TSCONFIG), header + JSON.stringify(managed, null, 2) + `
86427
86603
  `);
86428
86604
  info(colors.green(`Refreshed ${MANAGED_TSCONFIG}`));
86429
86605
  await ensureUserReferencesManaged({
@@ -86459,7 +86635,7 @@ async function refreshManagedDenoImportMap(mode) {
86459
86635
  for (const dir of WORKSPACE_IMPORT_DIRS) {
86460
86636
  imports[`/${dir}/`] = `./${dir}/`;
86461
86637
  }
86462
- writeFileSync7(path21.join(process22.cwd(), MANAGED_IMPORT_MAP), JSON.stringify({ imports }, null, 2) + `
86638
+ writeFileSync7(path21.join(process23.cwd(), MANAGED_IMPORT_MAP), JSON.stringify({ imports }, null, 2) + `
86463
86639
  `);
86464
86640
  info(colors.green(`Refreshed ${MANAGED_IMPORT_MAP}`));
86465
86641
  await ensureUserReferencesManaged({
@@ -86482,9 +86658,9 @@ async function refreshManagedDenoImportMap(mode) {
86482
86658
  });
86483
86659
  }
86484
86660
  async function ensureUserReferencesManaged(opts) {
86485
- const existing = [opts.file, ...opts.altFiles ?? []].map((f) => path21.join(process22.cwd(), f)).find((p) => existsSync12(p));
86661
+ const existing = [opts.file, ...opts.altFiles ?? []].map((f) => path21.join(process23.cwd(), f)).find((p) => existsSync12(p));
86486
86662
  if (!existing) {
86487
- const userPath = path21.join(process22.cwd(), opts.file);
86663
+ const userPath = path21.join(process23.cwd(), opts.file);
86488
86664
  writeFileSync7(userPath, JSON.stringify(opts.create, null, 2) + `
86489
86665
  `);
86490
86666
  info(colors.green(`Created ${opts.file} (references ${opts.token})`));
@@ -86533,7 +86709,7 @@ async function ensureUserReferencesManaged(opts) {
86533
86709
  info(colors.green(`Linked ${existingName} → ${opts.token}`));
86534
86710
  }
86535
86711
  function ensureBunTypesAvailable() {
86536
- const cwd = process22.cwd();
86712
+ const cwd = process23.cwd();
86537
86713
  if (existsSync12(path21.join(cwd, "node_modules", "bun-types"))) {
86538
86714
  return true;
86539
86715
  }
@@ -86555,7 +86731,7 @@ function ensureBunTypesAvailable() {
86555
86731
  }
86556
86732
  }
86557
86733
  async function warnIfTsconfigStale(opts) {
86558
- const cwd = opts?.cwd ?? process22.cwd();
86734
+ const cwd = opts?.cwd ?? process23.cwd();
86559
86735
  const managedPath = path21.join(cwd, MANAGED_TSCONFIG);
86560
86736
  if (!existsSync12(managedPath))
86561
86737
  return;
@@ -86575,7 +86751,7 @@ async function warnIfTsconfigStale(opts) {
86575
86751
  }
86576
86752
  }
86577
86753
  function emitTsconfigWarning(message) {
86578
- process22.stderr.write(`${colors.yellow(message)}
86754
+ process23.stderr.write(`${colors.yellow(message)}
86579
86755
  `);
86580
86756
  }
86581
86757
  var WORKSPACE_IMPORT_DIRS, MANAGED_TSCONFIG = "tsconfig.wmill.json", MANAGED_IMPORT_MAP = "import_map.wmill.json", MANAGED_NOTICE, TSCONFIG_HASH_PREFIX = "// wmill-tsconfig-hash: ", TSCONFIG_HASH_REGEX, LEGACY_GENERATED_TSCONFIGS, command30, tsconfig_default;
@@ -88061,7 +88237,7 @@ async function configureClientForWorkspace(opts, ws, resolver) {
88061
88237
  // src/commands/protection-rules/utils.ts
88062
88238
  init_colors2();
88063
88239
  init_log();
88064
- import process20 from "node:process";
88240
+ import process21 from "node:process";
88065
88241
  function outputResult2(opts, result) {
88066
88242
  if (opts.jsonOutput) {
88067
88243
  console.log(JSON.stringify(result));
@@ -88073,7 +88249,7 @@ function outputResult2(opts, result) {
88073
88249
  }
88074
88250
  function fail(opts, result) {
88075
88251
  outputResult2(opts, { ...result, success: false });
88076
- process20.exit(1);
88252
+ process21.exit(1);
88077
88253
  }
88078
88254
  function describeEntry(entry) {
88079
88255
  const n = ProtectionRulesConverter.normalizeEntry(entry);
@@ -88217,7 +88393,7 @@ await __promiseAll([
88217
88393
  init_confirm(),
88218
88394
  init_conf()
88219
88395
  ]);
88220
- import process21 from "node:process";
88396
+ import process22 from "node:process";
88221
88397
  import { existsSync as existsSync10 } from "node:fs";
88222
88398
  async function pushProtectionRules(opts, workspaceArg) {
88223
88399
  if (opts.jsonOutput)
@@ -88298,7 +88474,7 @@ async function pushProtectionRules(opts, workspaceArg) {
88298
88474
  workspaces: Object.fromEntries(wsPlans.map((w) => [w.ws, structuredPlan(w.plan)]))
88299
88475
  }));
88300
88476
  if (hadError)
88301
- process21.exit(1);
88477
+ process22.exit(1);
88302
88478
  return;
88303
88479
  }
88304
88480
  if (!opts.jsonOutput) {
@@ -88312,7 +88488,7 @@ async function pushProtectionRules(opts, workspaceArg) {
88312
88488
  error: "One or more workspaces failed (see errors above); the rest are in sync",
88313
88489
  partialFailure: true
88314
88490
  });
88315
- process21.exit(1);
88491
+ process22.exit(1);
88316
88492
  }
88317
88493
  if (!opts.dryRun) {
88318
88494
  outputResult2(opts, {
@@ -88324,7 +88500,7 @@ async function pushProtectionRules(opts, workspaceArg) {
88324
88500
  }
88325
88501
  if (opts.dryRun) {
88326
88502
  if (hadError)
88327
- process21.exit(1);
88503
+ process22.exit(1);
88328
88504
  return;
88329
88505
  }
88330
88506
  for (const w of wsPlans) {
@@ -88333,7 +88509,7 @@ async function pushProtectionRules(opts, workspaceArg) {
88333
88509
  }
88334
88510
  }
88335
88511
  const totalDeletes = changed.reduce((n, w) => n + w.plan.toDelete.length, 0);
88336
- if (!opts.yes && !!process21.stdin.isTTY) {
88512
+ if (!opts.yes && !!process22.stdin.isTTY) {
88337
88513
  const confirmed = await Confirm.prompt({
88338
88514
  message: totalDeletes > 0 ? `Apply these changes? This DELETES ${totalDeletes} protection rule(s) across ${changed.length} workspace(s).` : `Apply these changes to ${changed.length} workspace(s)?`,
88339
88515
  default: totalDeletes === 0
@@ -88395,7 +88571,7 @@ async function pushProtectionRules(opts, workspaceArg) {
88395
88571
  partialFailure: true,
88396
88572
  ...applied
88397
88573
  });
88398
- process21.exit(1);
88574
+ process22.exit(1);
88399
88575
  }
88400
88576
  outputResult2(opts, {
88401
88577
  success: true,
@@ -89433,7 +89609,7 @@ await __promiseAll([
89433
89609
  init_workspace(),
89434
89610
  init_resource_type()
89435
89611
  ]);
89436
- import { stat as stat20, writeFile as writeFile22, rm as rm5 } from "node:fs/promises";
89612
+ import { stat as stat20, writeFile as writeFile22, rm as rm6 } from "node:fs/promises";
89437
89613
 
89438
89614
  // src/commands/init/template.ts
89439
89615
  var NON_SCHEMA_KEYS = new Set([
@@ -89914,7 +90090,7 @@ await __promiseAll([
89914
90090
  init_utils(),
89915
90091
  init_freshness()
89916
90092
  ]);
89917
- import { cp, mkdir as mkdir13, readdir as readdir11, stat as stat19, writeFile as writeFile21 } from "node:fs/promises";
90093
+ import { cp, mkdir as mkdir13, readdir as readdir11, rm as rm5, stat as stat19, writeFile as writeFile21 } from "node:fs/promises";
89918
90094
  import { join as join20 } from "node:path";
89919
90095
  var WMILL_INIT_AI_SKILLS_SOURCE_ENV = "WMILL_INIT_AI_SKILLS_SOURCE";
89920
90096
  var WMILL_INIT_AI_AGENTS_SOURCE_ENV = "WMILL_INIT_AI_AGENTS_SOURCE";
@@ -89928,13 +90104,14 @@ async function writeAiGuidanceFiles(options) {
89928
90104
  const skillMetadata = options.skillsSourcePath ? await readSkillMetadataFromDirectory(options.skillsSourcePath) : getGeneratedSkillMetadata();
89929
90105
  const rawAgentsCliContent = options.agentsSourcePath != null ? await readTextFile(options.agentsSourcePath) : generateAgentsCliMdContent(buildSkillsReference(skillMetadata));
89930
90106
  const agentsCliContent = injectPromptsHashMarker(rawAgentsCliContent, currentPromptsHash(nonDottedPaths));
89931
- const agentsCliPath = join20(options.targetDir, "AGENTS.cli.md");
90107
+ const agentsCliPath = join20(options.targetDir, AGENTS_WMILL_FILENAME);
89932
90108
  await writeFile21(agentsCliPath, agentsCliContent, "utf8");
89933
90109
  const agentsCliWritten = true;
90110
+ const legacyManagedRemoved = await migrateLegacyManagedFile(options.targetDir);
89934
90111
  const resolveMigration = cacheOnce(options.resolveAgentsMdMigration);
89935
90112
  const agentsMdResult = await reconcileIncludingFile({
89936
90113
  path: join20(options.targetDir, "AGENTS.md"),
89937
- includeLine: AGENTS_CLI_INCLUDE_LINE,
90114
+ includeLine: AGENTS_WMILL_INCLUDE_LINE,
89938
90115
  skeleton: generateAgentsMdSkeleton(),
89939
90116
  resolveMigration
89940
90117
  });
@@ -89956,9 +90133,35 @@ async function writeAiGuidanceFiles(options) {
89956
90133
  agentsMigration: agentsMdResult.migration,
89957
90134
  claudeCreated: claudeMdResult.created,
89958
90135
  claudeMigration: claudeMdResult.migration,
89959
- skillCount: skillMetadata.length
90136
+ skillCount: skillMetadata.length,
90137
+ legacyManagedRemoved
89960
90138
  };
89961
90139
  }
90140
+ async function migrateLegacyManagedFile(targetDir) {
90141
+ for (const fileName of ["AGENTS.md", "CLAUDE.md"]) {
90142
+ const filePath = join20(targetDir, fileName);
90143
+ const existing = await readTextFile(filePath).catch(() => null);
90144
+ if (existing == null)
90145
+ continue;
90146
+ const rewritten = rewriteIncludeToken(existing, LEGACY_AGENTS_CLI_INCLUDE_LINE, AGENTS_WMILL_INCLUDE_LINE);
90147
+ if (rewritten !== existing) {
90148
+ await writeFile21(filePath, rewritten, "utf8");
90149
+ }
90150
+ }
90151
+ const legacyPath = join20(targetDir, LEGACY_AGENTS_CLI_FILENAME);
90152
+ const legacyExists = await stat19(legacyPath).catch(() => null) != null;
90153
+ if (legacyExists) {
90154
+ await rm5(legacyPath, { force: true });
90155
+ }
90156
+ return legacyExists;
90157
+ }
90158
+ function escapeRegExp(value) {
90159
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
90160
+ }
90161
+ function rewriteIncludeToken(content, from, to) {
90162
+ const re = new RegExp(`(?<=^|\\s)${escapeRegExp(from)}(?=\\s|$)`, "gm");
90163
+ return content.replace(re, to);
90164
+ }
89962
90165
  function cacheOnce(resolver) {
89963
90166
  if (!resolver)
89964
90167
  return;
@@ -90159,10 +90362,13 @@ async function refreshPrompts(opts) {
90159
90362
  return "skip";
90160
90363
  }
90161
90364
  });
90162
- info(colors.green("Refreshed AGENTS.cli.md"));
90365
+ info(colors.green("Refreshed AGENTS.wmill.md"));
90366
+ if (result.legacyManagedRemoved) {
90367
+ info(colors.yellow("Migrated legacy AGENTS.cli.md → AGENTS.wmill.md (removed the old file and rewrote any @AGENTS.cli.md include)."));
90368
+ }
90163
90369
  reportReconciliation({
90164
90370
  file: "AGENTS.md",
90165
- includeLine: "@AGENTS.cli.md",
90371
+ includeLine: "@AGENTS.wmill.md",
90166
90372
  created: result.agentsCreated,
90167
90373
  migration: result.agentsMigration
90168
90374
  });
@@ -90231,7 +90437,7 @@ async function promptMigration() {
90231
90437
  async function promptsAction(opts) {
90232
90438
  await refreshPrompts({ yes: opts.yes === true });
90233
90439
  }
90234
- var command29 = new Command().description("Refresh AGENTS.cli.md and managed skills. User-owned AGENTS.md and CLAUDE.md are never overwritten unless you opt in.").option("--yes", "Non-interactive: append the @AGENTS.cli.md include to an existing AGENTS.md / CLAUDE.md without prompting. Without it, a non-interactive run leaves an unlinked file untouched.").action(promptsAction);
90440
+ var command29 = new Command().description("Refresh AGENTS.wmill.md and managed skills. User-owned AGENTS.md and CLAUDE.md are never overwritten unless you opt in.").option("--yes", "Non-interactive: append the @AGENTS.wmill.md include to an existing AGENTS.md / CLAUDE.md without prompting. Without it, a non-interactive run leaves an unlinked file untouched.").action(promptsAction);
90235
90441
  var prompts_default = command29;
90236
90442
 
90237
90443
  // src/commands/init/init.ts
@@ -90349,8 +90555,8 @@ async function initAction(opts) {
90349
90555
  });
90350
90556
  if (choice === "cancel") {
90351
90557
  try {
90352
- await rm5("wmill.yaml");
90353
- await rm5("wmill-lock.yaml");
90558
+ await rm6("wmill.yaml");
90559
+ await rm6("wmill-lock.yaml");
90354
90560
  } catch {}
90355
90561
  info("Init cancelled");
90356
90562
  process.exit(0);
@@ -90403,7 +90609,7 @@ var init_default = command31;
90403
90609
  // src/commands/refresh/refresh.ts
90404
90610
  init_mod3();
90405
90611
  await init_tsconfig();
90406
- var command32 = new Command().description("Refresh wmill-managed project files (AGENTS.cli.md, skills, tsconfig.wmill.json)").command("prompts", prompts_default).command("tsconfig", tsconfig_default);
90612
+ var command32 = new Command().description("Refresh wmill-managed project files (AGENTS.wmill.md, skills, tsconfig.wmill.json)").command("prompts", prompts_default).command("tsconfig", tsconfig_default);
90407
90613
  var refresh_default = command32;
90408
90614
 
90409
90615
  // src/main.ts