windmill-cli 1.532.0 → 1.533.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.
@@ -32,7 +32,7 @@ export const OpenAPI = {
32
32
  PASSWORD: undefined,
33
33
  TOKEN: getEnv("WM_TOKEN"),
34
34
  USERNAME: undefined,
35
- VERSION: '1.532.0',
35
+ VERSION: '1.533.0',
36
36
  WITH_CREDENTIALS: true,
37
37
  interceptors: {
38
38
  request: new Interceptors(),
@@ -3,7 +3,7 @@ import { colors, log, yamlStringify } from "../../../deps.js";
3
3
  import { requireLogin } from "../../core/auth.js";
4
4
  import { resolveWorkspace } from "../../core/context.js";
5
5
  import * as wmill from "../../../gen/services.gen.js";
6
- import { readConfigFile, getEffectiveSettings, DEFAULT_SYNC_OPTIONS } from "../../core/conf.js";
6
+ import { readConfigFile, getEffectiveSettings, DEFAULT_SYNC_OPTIONS, getWmillYamlPath } from "../../core/conf.js";
7
7
  import { deepEqual } from "../../utils/utils.js";
8
8
  import { getCurrentGitBranch, isGitRepository } from "../../utils/git.js";
9
9
  import { GitSyncSettingsConverter } from "./converter.js";
@@ -83,12 +83,9 @@ export async function pullGitSyncSettings(opts) {
83
83
  // Convert backend settings to SyncOptions format
84
84
  const backendSyncOptions = GitSyncSettingsConverter.fromBackendFormat(selectedRepo.settings);
85
85
  // Check if wmill.yaml exists - create a default one if it doesn't exist
86
- let wmillYamlExists = true;
87
- try {
88
- await dntShim.Deno.stat("wmill.yaml");
89
- }
90
- catch (error) {
91
- wmillYamlExists = false;
86
+ const wmillYamlPath = getWmillYamlPath();
87
+ const wmillYamlExists = wmillYamlPath !== null;
88
+ if (!wmillYamlExists) {
92
89
  if (!opts.jsonOutput) {
93
90
  log.info(colors.yellow("No wmill.yaml file found. Will create one with backend git-sync settings."));
94
91
  }
@@ -110,11 +107,11 @@ export async function pullGitSyncSettings(opts) {
110
107
  if (isGitRepository()) {
111
108
  const currentBranch = getCurrentGitBranch();
112
109
  if (currentBranch) {
113
- if (!updatedConfig.git_branches) {
114
- updatedConfig.git_branches = {};
110
+ if (!updatedConfig.gitBranches) {
111
+ updatedConfig.gitBranches = {};
115
112
  }
116
- if (!updatedConfig.git_branches[currentBranch]) {
117
- updatedConfig.git_branches[currentBranch] = { overrides: {} };
113
+ if (!updatedConfig.gitBranches[currentBranch]) {
114
+ updatedConfig.gitBranches[currentBranch] = { overrides: {} };
118
115
  }
119
116
  }
120
117
  }
@@ -268,15 +265,15 @@ export async function pullGitSyncSettings(opts) {
268
265
  let needsBranchStructure = false;
269
266
  if (isGitRepository()) {
270
267
  const currentBranch = getCurrentGitBranch();
271
- if (currentBranch && (!localConfig.git_branches || !localConfig.git_branches[currentBranch])) {
268
+ if (currentBranch && (!localConfig.gitBranches || !localConfig.gitBranches[currentBranch])) {
272
269
  needsBranchStructure = true;
273
270
  // Create empty branch structure
274
271
  const updatedConfig = { ...localConfig };
275
- if (!updatedConfig.git_branches) {
276
- updatedConfig.git_branches = {};
272
+ if (!updatedConfig.gitBranches) {
273
+ updatedConfig.gitBranches = {};
277
274
  }
278
- if (!updatedConfig.git_branches[currentBranch]) {
279
- updatedConfig.git_branches[currentBranch] = { overrides: {} };
275
+ if (!updatedConfig.gitBranches[currentBranch]) {
276
+ updatedConfig.gitBranches[currentBranch] = { overrides: {} };
280
277
  }
281
278
  // Write updated configuration
282
279
  await dntShim.Deno.writeTextFile("wmill.yaml", yamlStringify(updatedConfig));
@@ -322,11 +319,11 @@ export async function pullGitSyncSettings(opts) {
322
319
  const currentBranch = getCurrentGitBranch();
323
320
  if (currentBranch) {
324
321
  log.info(`Detected Git repository, adding empty branch structure for: ${currentBranch}`);
325
- if (!updatedConfig.git_branches) {
326
- updatedConfig.git_branches = {};
322
+ if (!updatedConfig.gitBranches) {
323
+ updatedConfig.gitBranches = {};
327
324
  }
328
- if (!updatedConfig.git_branches[currentBranch]) {
329
- updatedConfig.git_branches[currentBranch] = { overrides: {} };
325
+ if (!updatedConfig.gitBranches[currentBranch]) {
326
+ updatedConfig.gitBranches[currentBranch] = { overrides: {} };
330
327
  }
331
328
  }
332
329
  }
@@ -3,7 +3,7 @@ import { colors, log, Confirm } from "../../../deps.js";
3
3
  import { requireLogin } from "../../core/auth.js";
4
4
  import { resolveWorkspace } from "../../core/context.js";
5
5
  import * as wmill from "../../../gen/services.gen.js";
6
- import { readConfigFile, validateBranchConfiguration, getEffectiveSettings } from "../../core/conf.js";
6
+ import { readConfigFile, validateBranchConfiguration, getEffectiveSettings, getWmillYamlPath } from "../../core/conf.js";
7
7
  import { deepEqual } from "../../utils/utils.js";
8
8
  import { GitSyncSettingsConverter } from "./converter.js";
9
9
  import { handleLegacyRepositoryMigration } from "./legacySettings.js";
@@ -24,10 +24,8 @@ export async function pushGitSyncSettings(opts) {
24
24
  await requireLogin(opts);
25
25
  try {
26
26
  // Check if wmill.yaml exists - require it for git-sync settings commands
27
- try {
28
- await dntShim.Deno.stat("wmill.yaml");
29
- }
30
- catch (error) {
27
+ const wmillYamlPath = getWmillYamlPath();
28
+ if (!wmillYamlPath) {
31
29
  log.error(colors.red("No wmill.yaml file found. Please run 'wmill init' first to create the configuration file."));
32
30
  dntShim.Deno.exit(1);
33
31
  }
@@ -21,11 +21,11 @@ export function normalizeRepoPath(path) {
21
21
  }
22
22
  // Helper to get or create branch configuration
23
23
  export function getOrCreateBranchConfig(config, branchName) {
24
- if (!config.git_branches) {
25
- config.git_branches = {};
24
+ if (!config.gitBranches) {
25
+ config.gitBranches = {};
26
26
  }
27
- if (!config.git_branches[branchName]) {
28
- config.git_branches[branchName] = {};
27
+ if (!config.gitBranches[branchName]) {
28
+ config.gitBranches[branchName] = {};
29
29
  }
30
30
  return {
31
31
  config,
@@ -36,20 +36,20 @@ export function getOrCreateBranchConfig(config, branchName) {
36
36
  export function applyBackendSettingsToBranch(config, branchName, backendSettings) {
37
37
  const { config: updatedConfig } = getOrCreateBranchConfig(config, branchName);
38
38
  // Get the base settings (top-level + defaults) to compare against
39
- const { git_branches, ...topLevelSettings } = config;
39
+ const { gitBranches, ...topLevelSettings } = config;
40
40
  const baseSettings = { ...DEFAULT_SYNC_OPTIONS, ...topLevelSettings };
41
41
  // Only store fields that differ from the base settings
42
42
  Object.keys(backendSettings).forEach(key => {
43
- if (key !== 'git_branches' && backendSettings[key] !== undefined) {
43
+ if (key !== 'gitBranches' && backendSettings[key] !== undefined) {
44
44
  const backendValue = backendSettings[key];
45
45
  const baseValue = baseSettings[key];
46
46
  // Only store if different from base
47
47
  const isDifferent = GitSyncSettingsConverter.isDifferent(backendValue, baseValue);
48
48
  if (isDifferent) {
49
- if (!updatedConfig.git_branches[branchName].overrides) {
50
- updatedConfig.git_branches[branchName].overrides = {};
49
+ if (!updatedConfig.gitBranches[branchName].overrides) {
50
+ updatedConfig.gitBranches[branchName].overrides = {};
51
51
  }
52
- updatedConfig.git_branches[branchName].overrides[key] = backendValue;
52
+ updatedConfig.gitBranches[branchName].overrides[key] = backendValue;
53
53
  }
54
54
  }
55
55
  });
@@ -20,16 +20,16 @@ async function initAction(opts) {
20
20
  if (isGitRepository()) {
21
21
  const currentBranch = getCurrentGitBranch();
22
22
  if (currentBranch) {
23
- initialConfig.git_branches = {
23
+ initialConfig.gitBranches = {
24
24
  [currentBranch]: { overrides: {} },
25
25
  };
26
26
  }
27
27
  else {
28
- initialConfig.git_branches = {};
28
+ initialConfig.gitBranches = {};
29
29
  }
30
30
  }
31
31
  else {
32
- initialConfig.git_branches = {};
32
+ initialConfig.gitBranches = {};
33
33
  }
34
34
  await dntShim.Deno.writeTextFile("wmill.yaml", yamlStringify(initialConfig));
35
35
  log.info(colors.green("wmill.yaml created with default settings"));
@@ -66,15 +66,15 @@ async function initAction(opts) {
66
66
  })))) {
67
67
  // Update the config with workspace binding
68
68
  const currentConfig = await import("../../core/conf.js").then((m) => m.readConfigFile());
69
- if (!currentConfig.git_branches) {
70
- currentConfig.git_branches = {};
69
+ if (!currentConfig.gitBranches) {
70
+ currentConfig.gitBranches = {};
71
71
  }
72
- if (!currentConfig.git_branches[currentBranch]) {
73
- currentConfig.git_branches[currentBranch] = { overrides: {} };
72
+ if (!currentConfig.gitBranches[currentBranch]) {
73
+ currentConfig.gitBranches[currentBranch] = { overrides: {} };
74
74
  }
75
- currentConfig.git_branches[currentBranch].baseUrl =
75
+ currentConfig.gitBranches[currentBranch].baseUrl =
76
76
  activeWorkspace.remote;
77
- currentConfig.git_branches[currentBranch].workspaceId =
77
+ currentConfig.gitBranches[currentBranch].workspaceId =
78
78
  activeWorkspace.workspaceId;
79
79
  await dntShim.Deno.writeTextFile("wmill.yaml", yamlStringify(currentConfig));
80
80
  log.info(colors.green(`✓ Bound branch '${currentBranch}' to workspace '${activeWorkspace.name}'`));
@@ -8,7 +8,9 @@ import { downloadZip } from "./pull.js";
8
8
  import { exts, findContentFile, findGlobalDeps, findResourceFile, handleScriptMetadata, removeExtensionToPath, } from "../script/script.js";
9
9
  import { handleFile } from "../script/script.js";
10
10
  import { deepEqual, isFileResource } from "../../utils/utils.js";
11
- import { readConfigFile, getEffectiveSettings, validateBranchConfiguration, } from "../../core/conf.js";
11
+ import { getEffectiveSettings, validateBranchConfiguration, mergeConfigWithConfigFile, } from "../../core/conf.js";
12
+ import { getSpecificItemsForCurrentBranch, isSpecificItem, getBranchSpecificPath, fromBranchSpecificPath, isCurrentBranchFile, isBranchSpecificFile, } from "../../core/specific_items.js";
13
+ import { getCurrentGitBranch } from "../../utils/git.js";
12
14
  import { removePathPrefix } from "../../types.js";
13
15
  import { listSyncCodebases } from "../../utils/codebase.js";
14
16
  import { generateFlowLockInternal, generateScriptMetadataInternal, readLockfile, } from "../../utils/metadata.js";
@@ -21,8 +23,7 @@ function mergeCliWithEffectiveOptions(cliOpts, effectiveOpts) {
21
23
  return Object.assign({}, effectiveOpts, cliOpts);
22
24
  }
23
25
  // Resolve effective sync options using branch-based configuration
24
- async function resolveEffectiveSyncOptions(workspace, promotion) {
25
- const localConfig = await readConfigFile();
26
+ async function resolveEffectiveSyncOptions(workspace, localConfig, promotion) {
26
27
  return await getEffectiveSettings(localConfig, promotion);
27
28
  }
28
29
  export function findCodebase(path, codebases) {
@@ -505,8 +506,30 @@ export async function* readDirRecursiveWithIgnore(ignore, root) {
505
506
  }
506
507
  }
507
508
  }
508
- export async function elementsToMap(els, ignore, json, skips) {
509
+ export async function elementsToMap(els, ignore, json, skips, specificItems) {
509
510
  const map = {};
511
+ const processedBasePaths = new Set();
512
+ // First pass: collect all file paths to identify branch-specific files
513
+ const allPaths = [];
514
+ for await (const entry of readDirRecursiveWithIgnore(ignore, els)) {
515
+ if (!entry.isDirectory && !entry.ignored) {
516
+ allPaths.push(entry.path);
517
+ }
518
+ }
519
+ const branchSpecificExists = new Set();
520
+ if (specificItems) {
521
+ const currentBranch = getCurrentGitBranch();
522
+ if (currentBranch) {
523
+ for (const path of allPaths) {
524
+ if (isCurrentBranchFile(path)) {
525
+ const basePath = fromBranchSpecificPath(path, currentBranch);
526
+ if (isSpecificItem(basePath, specificItems)) {
527
+ branchSpecificExists.add(basePath);
528
+ }
529
+ }
530
+ }
531
+ }
532
+ }
510
533
  for await (const entry of readDirRecursiveWithIgnore(ignore, els)) {
511
534
  if (entry.isDirectory || entry.ignored)
512
535
  continue;
@@ -578,10 +601,18 @@ export async function elementsToMap(els, ignore, json, skips) {
578
601
  "nu",
579
602
  "java",
580
603
  "rb",
581
- // for related places search: ADD_NEW_LANG
604
+ // for related places search: ADD_NEW_LANG
582
605
  ].includes(path.split(".").pop() ?? "") &&
583
606
  !isFileResource(path))
584
607
  continue;
608
+ // Handle branch-specific files - skip files for other branches
609
+ if (specificItems && isBranchSpecificFile(path)) {
610
+ const currentBranch = getCurrentGitBranch();
611
+ if (!currentBranch || !isCurrentBranchFile(path)) {
612
+ // Skip branch-specific files for other branches
613
+ continue;
614
+ }
615
+ }
585
616
  const content = await entry.getContentText();
586
617
  if (skips.skipSecrets && path.endsWith(".variable" + ext)) {
587
618
  try {
@@ -612,17 +643,45 @@ export async function elementsToMap(els, ignore, json, skips) {
612
643
  log.warn(`Error reading variable ${path} to check for secrets`);
613
644
  }
614
645
  }
615
- map[entry.path] = content;
646
+ // Handle branch-specific path mapping after all filtering
647
+ if (specificItems) {
648
+ const currentBranch = getCurrentGitBranch();
649
+ if (currentBranch && isCurrentBranchFile(path)) {
650
+ // This is a branch-specific file for current branch
651
+ const basePath = fromBranchSpecificPath(path, currentBranch);
652
+ if (isSpecificItem(basePath, specificItems)) {
653
+ // Map to base path for push operations
654
+ map[basePath] = content;
655
+ processedBasePaths.add(basePath);
656
+ }
657
+ else {
658
+ // Branch-specific file doesn't match pattern, skip it
659
+ continue;
660
+ }
661
+ }
662
+ else if (!isBranchSpecificFile(path)) {
663
+ // This is a regular base file, check if we should skip it
664
+ if (processedBasePaths.has(path)) {
665
+ // Skip base file, we already processed branch-specific version
666
+ continue;
667
+ }
668
+ map[path] = content;
669
+ }
670
+ }
671
+ else {
672
+ // No specific items configuration, use regular path
673
+ map[entry.path] = content;
674
+ }
616
675
  }
617
676
  return map;
618
677
  }
619
- async function compareDynFSElement(els1, els2, ignore, json, skips, ignoreMetadataDeletion, codebases, ignoreCodebaseChanges) {
678
+ async function compareDynFSElement(els1, els2, ignore, json, skips, ignoreMetadataDeletion, codebases, ignoreCodebaseChanges, specificItems) {
620
679
  const [m1, m2] = els2
621
680
  ? await Promise.all([
622
- elementsToMap(els1, ignore, json, skips),
623
- elementsToMap(els2, ignore, json, skips),
681
+ elementsToMap(els1, ignore, json, skips, specificItems),
682
+ elementsToMap(els2, ignore, json, skips, specificItems),
624
683
  ])
625
- : [await elementsToMap(els1, ignore, json, skips), {}];
684
+ : [await elementsToMap(els1, ignore, json, skips, specificItems), {}];
626
685
  const changes = [];
627
686
  function parseYaml(k, v) {
628
687
  if (k.endsWith(".script.yaml")) {
@@ -985,6 +1044,8 @@ async function buildTracker(changes) {
985
1044
  return tracker;
986
1045
  }
987
1046
  export async function pull(opts) {
1047
+ const originalCliOpts = { ...opts };
1048
+ opts = await mergeConfigWithConfigFile(opts);
988
1049
  // Validate branch configuration early
989
1050
  try {
990
1051
  await validateBranchConfiguration(false, opts.yes);
@@ -1002,9 +1063,11 @@ export async function pull(opts) {
1002
1063
  const workspace = await resolveWorkspace(opts);
1003
1064
  await requireLogin(opts);
1004
1065
  // Resolve effective sync options with branch awareness
1005
- const effectiveOpts = await resolveEffectiveSyncOptions(workspace, opts.promotion);
1066
+ const effectiveOpts = await resolveEffectiveSyncOptions(workspace, opts, opts.promotion);
1067
+ // Extract specific items configuration before merging overwrites gitBranches
1068
+ const specificItems = getSpecificItemsForCurrentBranch(opts);
1006
1069
  // Merge CLI flags with resolved settings (CLI flags take precedence only for explicit overrides)
1007
- opts = mergeCliWithEffectiveOptions(opts, effectiveOpts);
1070
+ opts = mergeCliWithEffectiveOptions(originalCliOpts, effectiveOpts);
1008
1071
  const codebases = await listSyncCodebases(opts);
1009
1072
  log.info(colors.gray("Computing the files to update locally to match remote (taking wmill.yaml into account)"));
1010
1073
  let resourceTypeToFormatExtension = {};
@@ -1020,7 +1083,7 @@ export async function pull(opts) {
1020
1083
  const local = !opts.stateful
1021
1084
  ? await FSFSElement(dntShim.Deno.cwd(), codebases, true)
1022
1085
  : await FSFSElement(path.join(dntShim.Deno.cwd(), ".wmill"), [], true);
1023
- const changes = await compareDynFSElement(remote, local, await ignoreF(opts), opts.json ?? false, opts, false, codebases, true);
1086
+ const changes = await compareDynFSElement(remote, local, await ignoreF(opts), opts.json ?? false, opts, false, codebases, true, specificItems);
1024
1087
  log.info(`remote (${workspace.name}) -> local: ${changes.length} changes to apply`);
1025
1088
  // Handle JSON output for dry-run
1026
1089
  if (opts.dryRun && opts.jsonOutput) {
@@ -1032,6 +1095,12 @@ export async function pull(opts) {
1032
1095
  ...(change.name === "edited" && change.codebase
1033
1096
  ? { codebase_changed: true }
1034
1097
  : {}),
1098
+ ...(specificItems && isSpecificItem(change.path, specificItems)
1099
+ ? {
1100
+ branch_specific: true,
1101
+ branch_specific_path: getBranchSpecificPath(change.path, specificItems)
1102
+ }
1103
+ : {}),
1035
1104
  })),
1036
1105
  total: changes.length,
1037
1106
  };
@@ -1040,7 +1109,7 @@ export async function pull(opts) {
1040
1109
  }
1041
1110
  if (changes.length > 0) {
1042
1111
  if (!opts.jsonOutput) {
1043
- prettyChanges(changes);
1112
+ prettyChanges(changes, specificItems);
1044
1113
  }
1045
1114
  if (opts.dryRun) {
1046
1115
  log.info(colors.gray(`Dry run complete.`));
@@ -1056,8 +1125,16 @@ export async function pull(opts) {
1056
1125
  const conflicts = [];
1057
1126
  log.info(colors.gray(`Applying changes to files ...`));
1058
1127
  for await (const change of changes) {
1059
- const target = path.join(dntShim.Deno.cwd(), change.path);
1060
- const stateTarget = path.join(dntShim.Deno.cwd(), ".wmill", change.path);
1128
+ // Determine if this file should be written to a branch-specific path
1129
+ let targetPath = change.path;
1130
+ if (specificItems && isSpecificItem(change.path, specificItems)) {
1131
+ const branchSpecificPath = getBranchSpecificPath(change.path, specificItems);
1132
+ if (branchSpecificPath) {
1133
+ targetPath = branchSpecificPath;
1134
+ }
1135
+ }
1136
+ const target = path.join(dntShim.Deno.cwd(), targetPath);
1137
+ const stateTarget = path.join(dntShim.Deno.cwd(), ".wmill", targetPath);
1061
1138
  if (change.name === "edited") {
1062
1139
  if (opts.stateful) {
1063
1140
  try {
@@ -1089,11 +1166,11 @@ export async function pull(opts) {
1089
1166
  }
1090
1167
  }
1091
1168
  if (exts.some((e) => change.path.endsWith(e))) {
1092
- log.info(`Editing script content of ${change.path}`);
1169
+ log.info(`Editing script content of ${targetPath}${targetPath !== change.path ? colors.gray(` (branch-specific override for ${change.path})`) : ""}`);
1093
1170
  }
1094
1171
  else if (change.path.endsWith(".yaml") ||
1095
1172
  change.path.endsWith(".json")) {
1096
- log.info(`Editing ${getTypeStrFromPath(change.path)} ${change.path}`);
1173
+ log.info(`Editing ${getTypeStrFromPath(change.path)} ${targetPath}${targetPath !== change.path ? colors.gray(` (branch-specific override for ${change.path})`) : ""}`);
1097
1174
  }
1098
1175
  await dntShim.Deno.writeTextFile(target, change.after);
1099
1176
  if (opts.stateful) {
@@ -1105,10 +1182,10 @@ export async function pull(opts) {
1105
1182
  await ensureDir(path.dirname(target));
1106
1183
  if (opts.stateful) {
1107
1184
  await ensureDir(path.dirname(stateTarget));
1108
- log.info(`Adding ${getTypeStrFromPath(change.path)} ${change.path}`);
1185
+ log.info(`Adding ${getTypeStrFromPath(change.path)} ${targetPath}${targetPath !== change.path ? colors.gray(` (branch-specific override for ${change.path})`) : ""}`);
1109
1186
  }
1110
1187
  await dntShim.Deno.writeTextFile(target, change.content);
1111
- log.info(`Writing ${getTypeStrFromPath(change.path)} ${change.path}`);
1188
+ log.info(`Writing ${getTypeStrFromPath(change.path)} ${targetPath}${targetPath !== change.path ? colors.gray(` (branch-specific override for ${change.path})`) : ""}`);
1112
1189
  if (opts.stateful) {
1113
1190
  await dntShim.Deno.copyFile(target, stateTarget);
1114
1191
  }
@@ -1166,6 +1243,12 @@ export async function pull(opts) {
1166
1243
  ...(change.name === "edited" && change.codebase
1167
1244
  ? { codebase_changed: true }
1168
1245
  : {}),
1246
+ ...(specificItems && isSpecificItem(change.path, specificItems)
1247
+ ? {
1248
+ branch_specific: true,
1249
+ branch_specific_path: getBranchSpecificPath(change.path, specificItems)
1250
+ }
1251
+ : {}),
1169
1252
  })),
1170
1253
  total: changes.length,
1171
1254
  };
@@ -1179,17 +1262,27 @@ export async function pull(opts) {
1179
1262
  console.log(JSON.stringify({ success: true, message: "No changes to apply", total: 0 }, null, 2));
1180
1263
  }
1181
1264
  }
1182
- function prettyChanges(changes) {
1265
+ function prettyChanges(changes, specificItems) {
1183
1266
  for (const change of changes) {
1267
+ let displayPath = change.path;
1268
+ let branchNote = "";
1269
+ // Check if this will be written as a branch-specific file
1270
+ if (specificItems && isSpecificItem(change.path, specificItems)) {
1271
+ const branchSpecificPath = getBranchSpecificPath(change.path, specificItems);
1272
+ if (branchSpecificPath) {
1273
+ displayPath = branchSpecificPath;
1274
+ branchNote = " (branch-specific)";
1275
+ }
1276
+ }
1184
1277
  if (change.name === "added") {
1185
- log.info(colors.green(`+ ${getTypeStrFromPath(change.path)} ` + change.path));
1278
+ log.info(colors.green(`+ ${getTypeStrFromPath(change.path)} ` + displayPath + colors.gray(branchNote)));
1186
1279
  }
1187
1280
  else if (change.name === "deleted") {
1188
- log.info(colors.red(`- ${getTypeStrFromPath(change.path)} ` + change.path));
1281
+ log.info(colors.red(`- ${getTypeStrFromPath(change.path)} ` + displayPath + colors.gray(branchNote)));
1189
1282
  }
1190
1283
  else if (change.name === "edited") {
1191
1284
  log.info(colors.yellow(`~ ${getTypeStrFromPath(change.path)} ` +
1192
- change.path +
1285
+ displayPath + colors.gray(branchNote) +
1193
1286
  (change.codebase ? ` (codebase changed)` : "")));
1194
1287
  if (change.before != change.after) {
1195
1288
  showDiff(change.before, change.after);
@@ -1222,6 +1315,10 @@ function removeSuffix(str, suffix) {
1222
1315
  return str.slice(0, str.length - suffix.length);
1223
1316
  }
1224
1317
  export async function push(opts) {
1318
+ // Save original CLI options before merging with config file
1319
+ const originalCliOpts = { ...opts };
1320
+ // Load configuration from wmill.yaml and merge with CLI options
1321
+ opts = await mergeConfigWithConfigFile(opts);
1225
1322
  // Validate branch configuration early
1226
1323
  try {
1227
1324
  await validateBranchConfiguration(false, opts.yes);
@@ -1236,9 +1333,11 @@ export async function push(opts) {
1236
1333
  const workspace = await resolveWorkspace(opts);
1237
1334
  await requireLogin(opts);
1238
1335
  // Resolve effective sync options with branch awareness
1239
- const effectiveOpts = await resolveEffectiveSyncOptions(workspace, opts.promotion);
1336
+ const effectiveOpts = await resolveEffectiveSyncOptions(workspace, opts, opts.promotion);
1337
+ // Extract specific items configuration BEFORE merging overwrites gitBranches
1338
+ const specificItems = getSpecificItemsForCurrentBranch(opts);
1240
1339
  // Merge CLI flags with resolved settings (CLI flags take precedence only for explicit overrides)
1241
- opts = mergeCliWithEffectiveOptions(opts, effectiveOpts);
1340
+ opts = mergeCliWithEffectiveOptions(originalCliOpts, effectiveOpts);
1242
1341
  const codebases = await listSyncCodebases(opts);
1243
1342
  if (opts.raw) {
1244
1343
  log.info("--raw is now the default, you can remove it as a flag");
@@ -1263,7 +1362,7 @@ export async function push(opts) {
1263
1362
  }
1264
1363
  const remote = ZipFSElement((await downloadZip(workspace, opts.plainSecrets, opts.skipVariables, opts.skipResources, opts.skipResourceTypes, opts.skipSecrets, opts.includeSchedules, opts.includeTriggers, opts.includeUsers, opts.includeGroups, opts.includeSettings, opts.includeKey, opts.defaultTs)), !opts.json, opts.defaultTs ?? "bun", resourceTypeToFormatExtension, false);
1265
1364
  const local = await FSFSElement(path.join(dntShim.Deno.cwd(), ""), codebases, false);
1266
- const changes = await compareDynFSElement(local, remote, await ignoreF(opts), opts.json ?? false, opts, true, codebases, false);
1365
+ const changes = await compareDynFSElement(local, remote, await ignoreF(opts), opts.json ?? false, opts, true, codebases, false, specificItems);
1267
1366
  const globalDeps = await findGlobalDeps();
1268
1367
  const tracker = await buildTracker(changes);
1269
1368
  const staleScripts = [];
@@ -1308,6 +1407,12 @@ export async function push(opts) {
1308
1407
  ...(change.name === "edited" && change.codebase
1309
1408
  ? { codebase_changed: true }
1310
1409
  : {}),
1410
+ ...(specificItems && isSpecificItem(change.path, specificItems)
1411
+ ? {
1412
+ branch_specific: true,
1413
+ branch_specific_path: getBranchSpecificPath(change.path, specificItems)
1414
+ }
1415
+ : {}),
1311
1416
  })),
1312
1417
  total: changes.length,
1313
1418
  };
@@ -1316,7 +1421,7 @@ export async function push(opts) {
1316
1421
  }
1317
1422
  if (changes.length > 0) {
1318
1423
  if (!opts.jsonOutput) {
1319
- prettyChanges(changes);
1424
+ prettyChanges(changes, specificItems);
1320
1425
  }
1321
1426
  if (opts.dryRun) {
1322
1427
  log.info(colors.gray(`Dry run complete.`));
@@ -1603,6 +1708,12 @@ export async function push(opts) {
1603
1708
  ...(change.name === "edited" && change.codebase
1604
1709
  ? { codebase_changed: true }
1605
1710
  : {}),
1711
+ ...(specificItems && isSpecificItem(change.path, specificItems)
1712
+ ? {
1713
+ branch_specific: true,
1714
+ branch_specific_path: getBranchSpecificPath(change.path, specificItems)
1715
+ }
1716
+ : {}),
1606
1717
  })),
1607
1718
  total: changes.length,
1608
1719
  duration_ms: Math.round(performance.now() - start),
@@ -279,27 +279,27 @@ async function bind(opts, bindWorkspace) {
279
279
  return;
280
280
  }
281
281
  // For unbind, check if branch exists
282
- if (!bindWorkspace && (!config.git_branches || !config.git_branches[branch])) {
283
- log.error(colors.red(`Branch '${branch}' not found in wmill.yaml git_branches`));
282
+ if (!bindWorkspace && (!config.gitBranches || !config.gitBranches[branch])) {
283
+ log.error(colors.red(`Branch '${branch}' not found in wmill.yaml gitBranches`));
284
284
  return;
285
285
  }
286
286
  // Update the branch configuration with workspace binding
287
- if (!config.git_branches) {
288
- config.git_branches = {};
287
+ if (!config.gitBranches) {
288
+ config.gitBranches = {};
289
289
  }
290
- if (!config.git_branches[branch]) {
291
- config.git_branches[branch] = { overrides: {} };
290
+ if (!config.gitBranches[branch]) {
291
+ config.gitBranches[branch] = { overrides: {} };
292
292
  }
293
293
  if (bindWorkspace && activeWorkspace) {
294
- config.git_branches[branch].baseUrl = activeWorkspace.remote;
295
- config.git_branches[branch].workspaceId = activeWorkspace.workspaceId;
294
+ config.gitBranches[branch].baseUrl = activeWorkspace.remote;
295
+ config.gitBranches[branch].workspaceId = activeWorkspace.workspaceId;
296
296
  log.info(colors.green(`✓ Bound branch '${branch}' to workspace '${activeWorkspace.name}'\n` +
297
297
  ` ${activeWorkspace.workspaceId} on ${activeWorkspace.remote}`));
298
298
  }
299
299
  else {
300
300
  // Unbind
301
- delete config.git_branches[branch].baseUrl;
302
- delete config.git_branches[branch].workspaceId;
301
+ delete config.gitBranches[branch].baseUrl;
302
+ delete config.gitBranches[branch].workspaceId;
303
303
  log.info(colors.green(`✓ Removed workspace binding from branch '${branch}'`));
304
304
  }
305
305
  // Write back the updated config
@@ -1,13 +1,80 @@
1
1
  import * as dntShim from "../../_dnt.shims.js";
2
2
  import { log, yamlParseFile, Confirm, yamlStringify } from "../../deps.js";
3
3
  import { getCurrentGitBranch, isGitRepository } from "../utils/git.js";
4
+ import { join, dirname, resolve, relative } from "node:path";
5
+ import { existsSync } from "node:fs";
6
+ import { execSync } from "node:child_process";
4
7
  export let showDiffs = false;
5
8
  export function setShowDiffs(value) {
6
9
  showDiffs = value;
7
10
  }
11
+ function getGitRepoRoot() {
12
+ try {
13
+ const result = execSync("git rev-parse --show-toplevel", {
14
+ encoding: "utf8",
15
+ stdio: "pipe"
16
+ });
17
+ return result.trim();
18
+ }
19
+ catch (error) {
20
+ return null;
21
+ }
22
+ }
23
+ function findWmillYaml() {
24
+ const startDir = resolve(dntShim.Deno.cwd());
25
+ const isInGitRepo = isGitRepository();
26
+ // If not in git repo, only check current directory
27
+ if (!isInGitRepo) {
28
+ const wmillYamlPath = join(startDir, "wmill.yaml");
29
+ return existsSync(wmillYamlPath) ? wmillYamlPath : null;
30
+ }
31
+ // If in git repo, search up to git repository root
32
+ const gitRoot = getGitRepoRoot();
33
+ let currentDir = startDir;
34
+ let foundPath = null;
35
+ while (true) {
36
+ const wmillYamlPath = join(currentDir, "wmill.yaml");
37
+ if (existsSync(wmillYamlPath)) {
38
+ foundPath = wmillYamlPath;
39
+ break;
40
+ }
41
+ // Check if we've reached the git repository root
42
+ if (gitRoot && resolve(currentDir) === resolve(gitRoot)) {
43
+ break;
44
+ }
45
+ // Check if we've reached the filesystem root
46
+ const parentDir = dirname(currentDir);
47
+ if (parentDir === currentDir) {
48
+ break;
49
+ }
50
+ currentDir = parentDir;
51
+ }
52
+ // If wmill.yaml was found in a parent directory, warn the user and change working directory
53
+ if (foundPath && resolve(dirname(foundPath)) !== resolve(startDir)) {
54
+ const configDir = dirname(foundPath);
55
+ const relativePath = relative(startDir, foundPath);
56
+ log.warn(`⚠️ wmill.yaml found in parent directory: ${relativePath}`);
57
+ // Change working directory to where wmill.yaml was found
58
+ dntShim.Deno.chdir(configDir);
59
+ log.info(`📁 Changed working directory to: ${configDir}`);
60
+ }
61
+ return foundPath;
62
+ }
63
+ export function getWmillYamlPath() {
64
+ return findWmillYaml();
65
+ }
8
66
  export async function readConfigFile() {
9
67
  try {
10
- const conf = (await yamlParseFile("wmill.yaml"));
68
+ // First, try to find wmill.yaml recursively
69
+ const wmillYamlPath = findWmillYaml();
70
+ if (!wmillYamlPath) {
71
+ log.warn("No wmill.yaml found. Use 'wmill init' to bootstrap it. Using 'bun' as default typescript runtime.");
72
+ return {};
73
+ }
74
+ const conf = (await yamlParseFile(wmillYamlPath));
75
+ // Handle legacy format migrations (combine overrides and git_branches)
76
+ let needsConfigWrite = false;
77
+ const migrationMessages = [];
11
78
  // Handle obsolete overrides format
12
79
  if (conf && 'overrides' in conf) {
13
80
  const overrides = conf.overrides;
@@ -18,17 +85,55 @@ export async function readConfigFile() {
18
85
  " Please delete your wmill.yaml and run 'wmill init' to recreate it with the new format.");
19
86
  }
20
87
  else {
21
- // Remove empty overrides with a note
22
- log.info("ℹ️ Removing empty 'overrides: {}' from wmill.yaml (migrated to git_branches format)");
88
+ // Remove empty overrides
23
89
  delete conf.overrides;
24
- // Write the updated config back to file
25
- try {
26
- await dntShim.Deno.writeTextFile("wmill.yaml", yamlStringify(conf));
90
+ needsConfigWrite = true;
91
+ migrationMessages.push("ℹ️ Removing empty 'overrides: {}' from wmill.yaml (migrated to gitBranches format)");
92
+ }
93
+ }
94
+ // Handle git_branches to gitBranches migration
95
+ if (conf && 'git_branches' in conf) {
96
+ if (!conf.gitBranches) {
97
+ // Deep copy git_branches to gitBranches (even if empty)
98
+ conf.gitBranches = JSON.parse(JSON.stringify(conf.git_branches));
99
+ needsConfigWrite = true;
100
+ migrationMessages.push("⚠️ Migrating 'git_branches' to 'gitBranches' (camelCase). The snake_case format is deprecated.");
101
+ migrationMessages.push("✅ Successfully migrated 'git_branches' to 'gitBranches' in wmill.yaml");
102
+ }
103
+ else {
104
+ migrationMessages.push("⚠️ Both 'git_branches' and 'gitBranches' found in wmill.yaml. Using 'gitBranches' and ignoring 'git_branches'.");
105
+ }
106
+ // Always remove the old field from config object (both file and memory)
107
+ delete conf.git_branches;
108
+ }
109
+ // Perform single atomic write if any migrations are needed
110
+ if (needsConfigWrite) {
111
+ try {
112
+ await dntShim.Deno.writeTextFile(wmillYamlPath, yamlStringify(conf));
113
+ // Log all migration messages after successful write
114
+ migrationMessages.forEach(msg => {
115
+ if (msg.startsWith('⚠️')) {
116
+ log.warn(msg);
117
+ }
118
+ else {
119
+ log.info(msg);
120
+ }
121
+ });
122
+ }
123
+ catch (error) {
124
+ log.warn(`Could not update wmill.yaml to apply migrations: ${error instanceof Error ? error.message : error}`);
125
+ }
126
+ }
127
+ else if (migrationMessages.length > 0) {
128
+ // Log messages for non-write cases (like "both found")
129
+ migrationMessages.forEach(msg => {
130
+ if (msg.startsWith('⚠️')) {
131
+ log.warn(msg);
27
132
  }
28
- catch (error) {
29
- log.warn(`Could not update wmill.yaml to remove empty overrides: ${error instanceof Error ? error.message : error}`);
133
+ else {
134
+ log.info(msg);
30
135
  }
31
- }
136
+ });
32
137
  }
33
138
  if (conf?.defaultTs == undefined) {
34
139
  log.warn("No defaultTs defined in your wmill.yaml. Using 'bun' as default.");
@@ -39,8 +144,19 @@ export async function readConfigFile() {
39
144
  if (e instanceof Error && (e.message.includes("overrides") || e.message.includes("Obsolete configuration format"))) {
40
145
  throw e; // Re-throw the specific obsolete format error
41
146
  }
42
- log.warn("No wmill.yaml found. Use 'wmill init' to bootstrap it. Using 'bun' as default typescript runtime.");
43
- return {};
147
+ // Since we already found the file path, this is likely a parsing or access error
148
+ if (e instanceof Error && e.message.includes("Error parsing yaml")) {
149
+ const yamlError = e.cause instanceof Error ? e.cause.message : String(e.cause);
150
+ throw new Error("❌ YAML syntax error in wmill.yaml:\n" +
151
+ " " + yamlError + "\n" +
152
+ " Please fix the YAML syntax in wmill.yaml or delete the file to start fresh.");
153
+ }
154
+ else {
155
+ // File exists but has other issues (permissions, etc.)
156
+ throw new Error("❌ Failed to read wmill.yaml:\n" +
157
+ " " + (e instanceof Error ? e.message : String(e)) + "\n" +
158
+ " Please check file permissions or fix the syntax.");
159
+ }
44
160
  }
45
161
  }
46
162
  // Default sync options - shared across the codebase to prevent duplication
@@ -74,33 +190,40 @@ export async function validateBranchConfiguration(skipValidation, autoAccept) {
74
190
  return;
75
191
  }
76
192
  const config = await readConfigFile();
77
- const { git_branches } = config;
193
+ const { gitBranches } = config;
78
194
  const currentBranch = getCurrentGitBranch();
79
- // In a git repository, git_branches section is recommended
80
- if (!git_branches || Object.keys(git_branches).length === 0) {
81
- log.warn("⚠️ WARNING: In a Git repository, the 'git_branches' section is recommended in wmill.yaml.\n" +
82
- " Consider adding a git_branches section with configuration for your Git branches.\n" +
195
+ // In a git repository, gitBranches section is recommended
196
+ if (!gitBranches || Object.keys(gitBranches).length === 0) {
197
+ log.warn("⚠️ WARNING: In a Git repository, the 'gitBranches' section is recommended in wmill.yaml.\n" +
198
+ " Consider adding a gitBranches section with configuration for your Git branches.\n" +
83
199
  " Run 'wmill init' to recreate the configuration file with proper branch setup.");
84
200
  return;
85
201
  }
86
- // Current branch must be defined in git_branches config
87
- if (currentBranch && !git_branches[currentBranch]) {
202
+ // Current branch must be defined in gitBranches config
203
+ if (currentBranch && !gitBranches[currentBranch]) {
88
204
  // In interactive mode, offer to create the branch
89
205
  if (dntShim.Deno.stdin.isTerminal()) {
90
- const availableBranches = Object.keys(git_branches).join(', ');
91
- log.info(`Current Git branch '${currentBranch}' is not defined in the git_branches configuration.\n` +
206
+ const availableBranches = Object.keys(gitBranches).join(', ');
207
+ log.info(`Current Git branch '${currentBranch}' is not defined in the gitBranches configuration.\n` +
92
208
  `Available branches: ${availableBranches}`);
93
209
  const shouldCreate = autoAccept || await Confirm.prompt({
94
210
  message: `Create empty branch configuration for '${currentBranch}'?`,
95
211
  default: true,
96
212
  });
97
213
  if (shouldCreate) {
214
+ // Warn if branch name contains filesystem-unsafe characters
215
+ if (/[\/\\:*?"<>|.]/.test(currentBranch)) {
216
+ const sanitizedBranchName = currentBranch.replace(/[\/\\:*?"<>|.]/g, '_');
217
+ log.warn(`⚠️ WARNING: Branch name "${currentBranch}" contains filesystem-unsafe characters (/ \\ : * ? " < > | .).`);
218
+ log.warn(` Branch-specific files will be saved with sanitized name: "${sanitizedBranchName}"`);
219
+ log.warn(` Example: "file.variable.yaml" → "file.${sanitizedBranchName}.variable.yaml"`);
220
+ }
98
221
  // Read current config, add branch, and write it back
99
222
  const currentConfig = await readConfigFile();
100
- if (!currentConfig.git_branches) {
101
- currentConfig.git_branches = {};
223
+ if (!currentConfig.gitBranches) {
224
+ currentConfig.gitBranches = {};
102
225
  }
103
- currentConfig.git_branches[currentBranch] = { overrides: {} };
226
+ currentConfig.gitBranches[currentBranch] = { overrides: {} };
104
227
  await dntShim.Deno.writeTextFile("wmill.yaml", yamlStringify(currentConfig));
105
228
  log.info(`✅ Created empty branch configuration for '${currentBranch}'`);
106
229
  }
@@ -110,9 +233,15 @@ export async function validateBranchConfiguration(skipValidation, autoAccept) {
110
233
  }
111
234
  }
112
235
  else {
113
- log.warn(`⚠️ WARNING: Current Git branch '${currentBranch}' is not defined in the git_branches configuration.\n` +
114
- ` Consider adding configuration for branch '${currentBranch}' in the git_branches section of wmill.yaml.\n` +
115
- ` Available branches: ${Object.keys(git_branches).join(', ')}`);
236
+ // Warn about filesystem-unsafe characters in branch name
237
+ if (/[\/\\:*?"<>|.]/.test(currentBranch)) {
238
+ const sanitizedBranchName = currentBranch.replace(/[\/\\:*?"<>|.]/g, '_');
239
+ log.warn(`⚠️ WARNING: Branch name "${currentBranch}" contains filesystem-unsafe characters (/ \\ : * ? " < > | .).`);
240
+ log.warn(` Branch-specific files will use sanitized name: "${sanitizedBranchName}"`);
241
+ }
242
+ log.warn(`⚠️ WARNING: Current Git branch '${currentBranch}' is not defined in the gitBranches configuration.\n` +
243
+ ` Consider adding configuration for branch '${currentBranch}' in the gitBranches section of wmill.yaml.\n` +
244
+ ` Available branches: ${Object.keys(gitBranches).join(', ')}`);
116
245
  return;
117
246
  }
118
247
  }
@@ -120,13 +249,13 @@ export async function validateBranchConfiguration(skipValidation, autoAccept) {
120
249
  // Get effective settings by merging top-level settings with branch-specific overrides
121
250
  export async function getEffectiveSettings(config, promotion, skipBranchValidation, suppressLogs) {
122
251
  // Start with top-level settings from config
123
- const { git_branches, ...topLevelSettings } = config;
252
+ const { gitBranches, ...topLevelSettings } = config;
124
253
  let effective = { ...topLevelSettings };
125
254
  if (isGitRepository()) {
126
255
  const currentBranch = getCurrentGitBranch();
127
256
  // If promotion is specified, use that branch's promotionOverrides or overrides
128
- if (promotion && git_branches && git_branches[promotion]) {
129
- const targetBranch = git_branches[promotion];
257
+ if (promotion && gitBranches && gitBranches[promotion]) {
258
+ const targetBranch = gitBranches[promotion];
130
259
  // First try promotionOverrides, then fall back to overrides
131
260
  if (targetBranch.promotionOverrides) {
132
261
  Object.assign(effective, targetBranch.promotionOverrides);
@@ -145,8 +274,8 @@ export async function getEffectiveSettings(config, promotion, skipBranchValidati
145
274
  }
146
275
  }
147
276
  // Otherwise use current branch overrides (existing behavior)
148
- else if (currentBranch && git_branches && git_branches[currentBranch] && git_branches[currentBranch].overrides) {
149
- Object.assign(effective, git_branches[currentBranch].overrides);
277
+ else if (currentBranch && gitBranches && gitBranches[currentBranch] && gitBranches[currentBranch].overrides) {
278
+ Object.assign(effective, gitBranches[currentBranch].overrides);
150
279
  if (!suppressLogs) {
151
280
  log.info(`Applied settings for Git branch: ${currentBranch}`);
152
281
  }
@@ -79,7 +79,7 @@ async function tryResolveBranchWorkspace(opts) {
79
79
  }
80
80
  // Read wmill.yaml to check for branch workspace configuration
81
81
  const config = await readConfigFile();
82
- const branchConfig = config.git_branches?.[currentBranch];
82
+ const branchConfig = config.gitBranches?.[currentBranch];
83
83
  // Check if branch has workspace configuration
84
84
  if (!branchConfig?.baseUrl || !branchConfig?.workspaceId) {
85
85
  return undefined;
@@ -0,0 +1,141 @@
1
+ import { minimatch } from "../../deps.js";
2
+ import { getCurrentGitBranch, isGitRepository } from "../utils/git.js";
3
+ /**
4
+ * Get the specific items configuration for the current git branch
5
+ * Merges commonSpecificItems with branch-specific specificItems
6
+ */
7
+ export function getSpecificItemsForCurrentBranch(config) {
8
+ if (!isGitRepository() || !config.gitBranches) {
9
+ return undefined;
10
+ }
11
+ const currentBranch = getCurrentGitBranch();
12
+ if (!currentBranch) {
13
+ return undefined;
14
+ }
15
+ const commonItems = config.gitBranches.commonSpecificItems;
16
+ const branchItems = config.gitBranches[currentBranch]?.specificItems;
17
+ // If neither common nor branch-specific items exist, return undefined
18
+ if (!commonItems && !branchItems) {
19
+ return undefined;
20
+ }
21
+ // Merge common and branch-specific items
22
+ const merged = {};
23
+ // Add common items
24
+ if (commonItems?.variables) {
25
+ merged.variables = [...commonItems.variables];
26
+ }
27
+ if (commonItems?.resources) {
28
+ merged.resources = [...commonItems.resources];
29
+ }
30
+ // Add branch-specific items (extending common items)
31
+ if (branchItems?.variables) {
32
+ merged.variables = [...(merged.variables || []), ...branchItems.variables];
33
+ }
34
+ if (branchItems?.resources) {
35
+ merged.resources = [...(merged.resources || []), ...branchItems.resources];
36
+ }
37
+ return merged;
38
+ }
39
+ /**
40
+ * Check if a path matches any of the patterns in the given list
41
+ */
42
+ function matchesPatterns(path, patterns) {
43
+ return patterns.some(pattern => minimatch(path, pattern));
44
+ }
45
+ /**
46
+ * Check if a file path should be treated as branch-specific
47
+ */
48
+ export function isSpecificItem(path, specificItems) {
49
+ if (!specificItems) {
50
+ return false;
51
+ }
52
+ // Determine the item type from the file path
53
+ if (path.endsWith('.variable.yaml')) {
54
+ return specificItems.variables ? matchesPatterns(path, specificItems.variables) : false;
55
+ }
56
+ if (path.endsWith('.resource.yaml')) {
57
+ return specificItems.resources ? matchesPatterns(path, specificItems.resources) : false;
58
+ }
59
+ return false;
60
+ }
61
+ /**
62
+ * Convert a base path to a branch-specific path
63
+ */
64
+ export function toBranchSpecificPath(basePath, branchName) {
65
+ // Extract the extension (e.g., ".variable.yaml" or ".resource.yaml")
66
+ const extensionMatch = basePath.match(/(\.(variable|resource)\.yaml)$/);
67
+ if (!extensionMatch) {
68
+ return basePath; // Return unchanged if no recognized extension
69
+ }
70
+ const extension = extensionMatch[1];
71
+ const pathWithoutExtension = basePath.substring(0, basePath.length - extension.length);
72
+ // Sanitize branch name to be filesystem-safe
73
+ const sanitizedBranchName = branchName.replace(/[\/\\:*?"<>|.]/g, '_');
74
+ // Warn about potential collisions if sanitization occurred
75
+ if (sanitizedBranchName !== branchName) {
76
+ console.warn(`Warning: Branch name "${branchName}" contains filesystem-unsafe characters (/ \\ : * ? " < > | .) and was sanitized to "${sanitizedBranchName}". This may cause collisions with other similarly named branches.`);
77
+ }
78
+ return `${pathWithoutExtension}.${sanitizedBranchName}${extension}`;
79
+ }
80
+ /**
81
+ * Convert a branch-specific path back to a base path
82
+ */
83
+ export function fromBranchSpecificPath(branchSpecificPath, branchName) {
84
+ // Sanitize branch name the same way as in toBranchSpecificPath
85
+ const sanitizedBranchName = branchName.replace(/[\/\\:*?"<>|.]/g, '_');
86
+ // Pattern: path.sanitizedBranchName.extension
87
+ const escapedBranchName = sanitizedBranchName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
88
+ const pattern = new RegExp(`\\.${escapedBranchName}(\\.(variable|resource)\\.yaml)$`);
89
+ const match = branchSpecificPath.match(pattern);
90
+ if (!match) {
91
+ return branchSpecificPath; // Return unchanged if not a branch-specific path
92
+ }
93
+ const extension = match[1];
94
+ const pathWithoutBranchAndExtension = branchSpecificPath.substring(0, branchSpecificPath.length - `.${sanitizedBranchName}${extension}`.length);
95
+ return `${pathWithoutBranchAndExtension}${extension}`;
96
+ }
97
+ /**
98
+ * Get the branch-specific path for the current branch if the item should be branch-specific
99
+ */
100
+ export function getBranchSpecificPath(basePath, specificItems) {
101
+ if (!isGitRepository() || !specificItems) {
102
+ return undefined;
103
+ }
104
+ const currentBranch = getCurrentGitBranch();
105
+ if (!currentBranch) {
106
+ return undefined;
107
+ }
108
+ if (isSpecificItem(basePath, specificItems)) {
109
+ return toBranchSpecificPath(basePath, currentBranch);
110
+ }
111
+ return undefined;
112
+ }
113
+ // Cache for compiled regex patterns to avoid recompilation
114
+ const branchPatternCache = new Map();
115
+ /**
116
+ * Check if a path is a branch-specific file for the current branch
117
+ */
118
+ export function isCurrentBranchFile(path) {
119
+ if (!isGitRepository()) {
120
+ return false;
121
+ }
122
+ const currentBranch = getCurrentGitBranch();
123
+ if (!currentBranch) {
124
+ return false;
125
+ }
126
+ // Use cached pattern or create and cache new one
127
+ let pattern = branchPatternCache.get(currentBranch);
128
+ if (!pattern) {
129
+ pattern = new RegExp(`\\.${currentBranch}\\.(variable|resource)\\.yaml$`);
130
+ branchPatternCache.set(currentBranch, pattern);
131
+ }
132
+ return pattern.test(path);
133
+ }
134
+ /**
135
+ * Check if a path is a branch-specific file for ANY branch (not necessarily current)
136
+ * Used to identify and skip files from other branches during sync operations
137
+ */
138
+ export function isBranchSpecificFile(path) {
139
+ // Pattern: *.branchName.variable.yaml or *.branchName.resource.yaml
140
+ return /\.[^.]+\.(variable|resource)\.yaml$/.test(path);
141
+ }
package/esm/src/main.js CHANGED
@@ -38,7 +38,7 @@ export { flow, app, script, workspace, resource, resourceType, user, variable, h
38
38
  // console.error(JSON.stringify(event.error, null, 4));
39
39
  // }
40
40
  // });
41
- export const VERSION = "1.532.0";
41
+ export const VERSION = "1.533.0";
42
42
  const command = new Command()
43
43
  .name("wmill")
44
44
  .action(() => log.info(`Welcome to Windmill CLI ${VERSION}. Use -h for help.`))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "windmill-cli",
3
- "version": "1.532.0",
3
+ "version": "1.533.0",
4
4
  "description": "CLI for Windmill",
5
5
  "repository": {
6
6
  "type": "git",
@@ -22,6 +22,12 @@ declare global {
22
22
  }
23
23
  }
24
24
  export {};
25
+ declare global {
26
+ interface Error {
27
+ cause?: unknown;
28
+ }
29
+ }
30
+ export {};
25
31
  declare global {
26
32
  interface Object {
27
33
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"_dnt.polyfills.d.ts","sourceRoot":"","sources":["../src/_dnt.polyfills.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,UAAU;QAClB;;;;;;;;WAQG;QACH,IAAI,EAAE,OAAO,CAAC;QAEd;;;;;;;WAOG;QAEH,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;KACpC;CACF;AAED,OAAO,EAAE,CAAA;AAgBT,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd;;;;WAIG;QACH,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;KAC5C;CACF;AAED,OAAO,EAAE,CAAC;AAEV,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,gBAAgB;QACxB,SAAS,CAAC,CAAC,EACP,mBAAmB,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,GAC7F,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;QAEhB,SAAS,CAAC,CAAC,EAAE,CAAC,EACV,mBAAmB,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,EAClE,KAAK,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,EAC/B,OAAO,CAAC,EAAE,GAAG,GACd,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;KAC1B;CACF;AAoID,OAAO,EAAE,CAAC"}
1
+ {"version":3,"file":"_dnt.polyfills.d.ts","sourceRoot":"","sources":["../src/_dnt.polyfills.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,UAAU;QAClB;;;;;;;;WAQG;QACH,IAAI,EAAE,OAAO,CAAC;QAEd;;;;;;;WAOG;QAEH,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;KACpC;CACF;AAED,OAAO,EAAE,CAAA;AACT,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,KAAK;QACb,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB;CACF;AAED,OAAO,EAAE,CAAC;AAgBV,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd;;;;WAIG;QACH,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;KAC5C;CACF;AAED,OAAO,EAAE,CAAC;AAEV,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,gBAAgB;QACxB,SAAS,CAAC,CAAC,EACP,mBAAmB,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,GAC7F,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;QAEhB,SAAS,CAAC,CAAC,EAAE,CAAC,EACV,mBAAmB,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,EAClE,KAAK,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,EAC/B,OAAO,CAAC,EAAE,GAAG,GACd,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;KAC1B;CACF;AAoID,OAAO,EAAE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../../../../src/src/commands/gitsync-settings/pull.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAqB/C,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,aAAa,GAAG;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,iBAycF"}
1
+ {"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../../../../src/src/commands/gitsync-settings/pull.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAqB/C,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,aAAa,GAAG;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,iBAucF"}
@@ -1 +1 @@
1
- {"version":3,"file":"push.d.ts","sourceRoot":"","sources":["../../../../src/src/commands/gitsync-settings/push.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAmB/C,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,aAAa,GAAG;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,iBA4UF"}
1
+ {"version":3,"file":"push.d.ts","sourceRoot":"","sources":["../../../../src/src/commands/gitsync-settings/push.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAmB/C,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,aAAa,GAAG;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,iBA2UF"}
@@ -1,6 +1,7 @@
1
1
  import { Command } from "../../../deps.js";
2
2
  import { GlobalOptions } from "../../types.js";
3
3
  import { SyncOptions } from "../../core/conf.js";
4
+ import { SpecificItemsConfig } from "../../core/specific_items.js";
4
5
  import { SyncCodebase } from "../../utils/codebase.js";
5
6
  import { PathAssigner } from "../../../windmill-utils-internal/src/path-utils/path-assigner.js";
6
7
  type DynFSElement = {
@@ -28,7 +29,7 @@ export declare function readDirRecursiveWithIgnore(ignore: (path: string, isDire
28
29
  isDirectory: boolean;
29
30
  getContentText(): Promise<string>;
30
31
  }>;
31
- export declare function elementsToMap(els: DynFSElement, ignore: (path: string, isDirectory: boolean) => boolean, json: boolean, skips: Skips): Promise<{
32
+ export declare function elementsToMap(els: DynFSElement, ignore: (path: string, isDirectory: boolean) => boolean, json: boolean, skips: Skips, specificItems?: SpecificItemsConfig): Promise<{
32
33
  [key: string]: string;
33
34
  }>;
34
35
  export interface Skips {
@@ -1 +1 @@
1
- {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../../../src/src/commands/sync/sync.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../../../src/src/commands/sync/sync.ts"],"names":[],"mappings":"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"}
@@ -31,12 +31,38 @@ export interface SyncOptions {
31
31
  codebases?: Codebase[];
32
32
  parallel?: number;
33
33
  jsonOutput?: boolean;
34
+ gitBranches?: {
35
+ commonSpecificItems?: {
36
+ variables?: string[];
37
+ resources?: string[];
38
+ };
39
+ } & {
40
+ [branchName: string]: SyncOptions & {
41
+ overrides?: Partial<SyncOptions>;
42
+ promotionOverrides?: Partial<SyncOptions>;
43
+ baseUrl?: string;
44
+ workspaceId?: string;
45
+ specificItems?: {
46
+ variables?: string[];
47
+ resources?: string[];
48
+ };
49
+ };
50
+ };
34
51
  git_branches?: {
52
+ commonSpecificItems?: {
53
+ variables?: string[];
54
+ resources?: string[];
55
+ };
56
+ } & {
35
57
  [branchName: string]: SyncOptions & {
36
58
  overrides?: Partial<SyncOptions>;
37
59
  promotionOverrides?: Partial<SyncOptions>;
38
60
  baseUrl?: string;
39
61
  workspaceId?: string;
62
+ specificItems?: {
63
+ variables?: string[];
64
+ resources?: string[];
65
+ };
40
66
  };
41
67
  };
42
68
  promotion?: string;
@@ -56,6 +82,7 @@ export interface Codebase {
56
82
  };
57
83
  inject?: string[];
58
84
  }
85
+ export declare function getWmillYamlPath(): string | null;
59
86
  export declare function readConfigFile(): Promise<SyncOptions>;
60
87
  export declare const DEFAULT_SYNC_OPTIONS: Readonly<Required<Pick<SyncOptions, 'defaultTs' | 'includes' | 'excludes' | 'codebases' | 'skipVariables' | 'skipResources' | 'skipResourceTypes' | 'skipSecrets' | 'includeSchedules' | 'includeTriggers' | 'skipScripts' | 'skipFlows' | 'skipApps' | 'skipFolders' | 'includeUsers' | 'includeGroups' | 'includeSettings' | 'includeKey'>>>;
61
88
  export declare function mergeConfigWithConfigFile<T>(opts: T): Promise<T & SyncOptions>;
@@ -1 +1 @@
1
- {"version":3,"file":"conf.d.ts","sourceRoot":"","sources":["../../../src/src/core/conf.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"conf.d.ts","sourceRoot":"","sources":["../../../src/src/core/conf.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import { SyncOptions } from "./conf.js";
2
+ export interface SpecificItemsConfig {
3
+ variables?: string[];
4
+ resources?: string[];
5
+ }
6
+ /**
7
+ * Get the specific items configuration for the current git branch
8
+ * Merges commonSpecificItems with branch-specific specificItems
9
+ */
10
+ export declare function getSpecificItemsForCurrentBranch(config: SyncOptions): SpecificItemsConfig | undefined;
11
+ /**
12
+ * Check if a file path should be treated as branch-specific
13
+ */
14
+ export declare function isSpecificItem(path: string, specificItems: SpecificItemsConfig | undefined): boolean;
15
+ /**
16
+ * Convert a base path to a branch-specific path
17
+ */
18
+ export declare function toBranchSpecificPath(basePath: string, branchName: string): string;
19
+ /**
20
+ * Convert a branch-specific path back to a base path
21
+ */
22
+ export declare function fromBranchSpecificPath(branchSpecificPath: string, branchName: string): string;
23
+ /**
24
+ * Get the branch-specific path for the current branch if the item should be branch-specific
25
+ */
26
+ export declare function getBranchSpecificPath(basePath: string, specificItems: SpecificItemsConfig | undefined): string | undefined;
27
+ /**
28
+ * Check if a path is a branch-specific file for the current branch
29
+ */
30
+ export declare function isCurrentBranchFile(path: string): boolean;
31
+ /**
32
+ * Check if a path is a branch-specific file for ANY branch (not necessarily current)
33
+ * Used to identify and skip files from other branches during sync operations
34
+ */
35
+ export declare function isBranchSpecificFile(path: string): boolean;
36
+ //# sourceMappingURL=specific_items.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"specific_items.d.ts","sourceRoot":"","sources":["../../../src/src/core/specific_items.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,gCAAgC,CAAC,MAAM,EAAE,WAAW,GAAG,mBAAmB,GAAG,SAAS,CAsCrG;AASD;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,mBAAmB,GAAG,SAAS,GAAG,OAAO,CAepG;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAmBjF;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,kBAAkB,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAoB7F;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,mBAAmB,GAAG,SAAS,GAC7C,MAAM,GAAG,SAAS,CAepB;AAKD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAkBzD;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAG1D"}
@@ -22,7 +22,7 @@ import { pull as hubPull } from "./commands/hub/hub.js";
22
22
  import { pull, push } from "./commands/sync/sync.js";
23
23
  import { add as workspaceAdd } from "./commands/workspace/workspace.js";
24
24
  export { flow, app, script, workspace, resource, resourceType, user, variable, hub, folder, schedule, trigger, sync, gitsyncSettings, instance, dev, hubPull, pull, push, workspaceAdd, };
25
- export declare const VERSION = "1.532.0";
25
+ export declare const VERSION = "1.533.0";
26
26
  declare const command: Command<{
27
27
  workspace?: (import("../deps/jsr.io/@windmill-labs/cliffy-command/1.0.0-rc.5/mod.js").StringType & string) | undefined;
28
28
  } & {