windmill-cli 1.720.0 → 1.721.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 +178 -7
  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.720.0",
16775
+ VERSION: "1.721.0",
16776
16776
  WITH_CREDENTIALS: true,
16777
16777
  interceptors: {
16778
16778
  request: new Interceptors,
@@ -25304,7 +25304,7 @@ var init_auth = __esm(async () => {
25304
25304
  });
25305
25305
 
25306
25306
  // src/core/constants.ts
25307
- var WM_FORK_PREFIX = "wm-fork", VERSION = "1.720.0";
25307
+ var WM_FORK_PREFIX = "wm-fork", VERSION = "1.721.0";
25308
25308
 
25309
25309
  // src/utils/git.ts
25310
25310
  var exports_git = {};
@@ -65751,21 +65751,25 @@ var exports_sync = {};
65751
65751
  __export(exports_sync, {
65752
65752
  yamlSortedEntries: () => yamlSortedEntries,
65753
65753
  yamlOptions: () => yamlOptions,
65754
+ summarizeCaseRewrites: () => summarizeCaseRewrites,
65754
65755
  resolveWsNameForConfigFromFlags: () => resolveWsNameForConfigFromFlags,
65755
65756
  readDirRecursiveWithIgnore: () => readDirRecursiveWithIgnore2,
65756
65757
  push: () => push4,
65757
65758
  pull: () => pull,
65758
65759
  isWhitelisted: () => isWhitelisted,
65760
+ isCaseInsensitiveFilesystem: () => isCaseInsensitiveFilesystem,
65759
65761
  ignoreF: () => ignoreF,
65760
65762
  gitDeploy: () => gitDeploy,
65761
65763
  generateDatatablesDocumentation: () => generateDatatablesDocumentation,
65762
65764
  generateAgentsDocumentation: () => generateAgentsDocumentation,
65763
65765
  findCodebase: () => findCodebase,
65766
+ findCaseInsensitiveCollisions: () => findCaseInsensitiveCollisions,
65764
65767
  extractInlineScriptsForApps: () => extractInlineScriptsForApps,
65765
65768
  extractFieldsForRawApps: () => extractFieldsForRawApps,
65766
65769
  elementsToMap: () => elementsToMap,
65767
65770
  default: () => sync_default,
65768
65771
  computeWsSpecificFlagOnlyPushes: () => computeWsSpecificFlagOnlyPushes,
65772
+ canonicalizeCaseInsensitiveKeys: () => canonicalizeCaseInsensitiveKeys,
65769
65773
  FSFSElement: () => FSFSElement
65770
65774
  });
65771
65775
  import { writeFile as writeFile7, readdir as readdir4, stat as stat7, rm, copyFile, mkdir as mkdir5 } from "node:fs/promises";
@@ -66930,11 +66934,178 @@ ${configHint}`);
66930
66934
  }
66931
66935
  return map;
66932
66936
  }
66933
- async function compareDynFSElement(els1, els2, ignore, json, skips, ignoreMetadataDeletion, codebases, ignoreCodebaseChanges, specificItems, branchOverride, isEls1Remote) {
66934
- const [m1, m2] = els2 ? await Promise.all([
66937
+ function findCaseInsensitiveCollisions(paths) {
66938
+ const byLower = new Map;
66939
+ for (const full of paths) {
66940
+ const segs = full.split(/[\\/]/).filter((s) => s.length > 0);
66941
+ let acc = "";
66942
+ for (let i = 0;i < segs.length; i++) {
66943
+ acc = i === 0 ? segs[i] : `${acc}/${segs[i]}`;
66944
+ const lower = acc.toLowerCase();
66945
+ let set = byLower.get(lower);
66946
+ if (!set) {
66947
+ set = new Set;
66948
+ byLower.set(lower, set);
66949
+ }
66950
+ set.add(acc);
66951
+ }
66952
+ }
66953
+ const collidingLowers = new Set;
66954
+ for (const [lower, set] of byLower) {
66955
+ if (set.size > 1)
66956
+ collidingLowers.add(lower);
66957
+ }
66958
+ const collisions = [];
66959
+ for (const lower of collidingLowers) {
66960
+ const parts = lower.split("/");
66961
+ let hasCollidingAncestor = false;
66962
+ for (let i = 1;i < parts.length; i++) {
66963
+ if (collidingLowers.has(parts.slice(0, i).join("/"))) {
66964
+ hasCollidingAncestor = true;
66965
+ break;
66966
+ }
66967
+ }
66968
+ if (!hasCollidingAncestor) {
66969
+ collisions.push([...byLower.get(lower)].sort());
66970
+ }
66971
+ }
66972
+ return collisions;
66973
+ }
66974
+ function canonicalizeCaseInsensitiveKeys(localMap, remoteMap) {
66975
+ const root = { children: new Map };
66976
+ for (const k of Object.keys(remoteMap)) {
66977
+ let node = root;
66978
+ for (const seg of k.split(/[\\/]/)) {
66979
+ if (seg.length === 0)
66980
+ continue;
66981
+ const lk = seg.toLowerCase();
66982
+ let entry = node.children.get(lk);
66983
+ if (!entry) {
66984
+ entry = { canonical: seg, ambiguous: false, node: { children: new Map } };
66985
+ node.children.set(lk, entry);
66986
+ } else if (entry.canonical !== seg) {
66987
+ entry.ambiguous = true;
66988
+ }
66989
+ node = entry.node;
66990
+ }
66991
+ }
66992
+ const out = {};
66993
+ const rewritten = [];
66994
+ for (const [k, v] of Object.entries(localMap)) {
66995
+ const sep3 = k.includes("\\") ? "\\" : "/";
66996
+ const segs = k.split(/[\\/]/);
66997
+ const canonSegs = [];
66998
+ let node = root;
66999
+ let changed = false;
67000
+ for (const seg of segs) {
67001
+ if (seg.length === 0) {
67002
+ canonSegs.push(seg);
67003
+ continue;
67004
+ }
67005
+ const entry = node?.children.get(seg.toLowerCase());
67006
+ if (entry && !entry.ambiguous) {
67007
+ if (entry.canonical !== seg)
67008
+ changed = true;
67009
+ canonSegs.push(entry.canonical);
67010
+ node = entry.node;
67011
+ } else {
67012
+ canonSegs.push(seg);
67013
+ node = undefined;
67014
+ }
67015
+ }
67016
+ const canonKey = canonSegs.join(sep3);
67017
+ if (changed && canonKey !== k) {
67018
+ out[canonKey] = v;
67019
+ rewritten.push({ from: k, to: canonKey });
67020
+ } else {
67021
+ out[k] = v;
67022
+ }
67023
+ }
67024
+ return {
67025
+ map: out,
67026
+ ambiguous: findCaseInsensitiveCollisions(Object.keys(remoteMap)),
67027
+ rewritten
67028
+ };
67029
+ }
67030
+ function summarizeCaseRewrites(rewritten) {
67031
+ const seen = new Set;
67032
+ const out = [];
67033
+ for (const { from, to } of rewritten) {
67034
+ const fromSegs = from.split(/[\\/]/);
67035
+ const toSegs = to.split(/[\\/]/);
67036
+ let i = 0;
67037
+ while (i < fromSegs.length && i < toSegs.length && fromSegs[i] === toSegs[i]) {
67038
+ i++;
67039
+ }
67040
+ const fromPrefix = fromSegs.slice(0, i + 1).join("/");
67041
+ const toPrefix = toSegs.slice(0, i + 1).join("/");
67042
+ const key = `${fromPrefix} -> ${toPrefix}`;
67043
+ if (!seen.has(key)) {
67044
+ seen.add(key);
67045
+ out.push(key);
67046
+ }
67047
+ }
67048
+ return out;
67049
+ }
67050
+ function warnUnrepresentableCaseCollisions(collisions) {
67051
+ if (collisions.length === 0)
67052
+ return;
67053
+ const groups = collisions.map((g) => ` - ${g.join(" <-> ")}`).join(`
67054
+ `);
67055
+ warn(`Found ${collisions.length} path(s) that differ only by letter case:
67056
+ ` + `${groups}
67057
+ ` + `On case-insensitive filesystems (Windows, default macOS) these collapse ` + `into a single file/directory and cannot both be synced. Rename one side ` + `to a distinct path to make the tree sync reliably across platforms.`);
67058
+ }
67059
+ async function isCaseInsensitiveFilesystem(dir) {
67060
+ const override = (process.env.WMILL_CASE_INSENSITIVE_FS ?? "").trim().toLowerCase();
67061
+ if (override === "true" || override === "1")
67062
+ return true;
67063
+ if (override === "false" || override === "0")
67064
+ return false;
67065
+ if (_caseInsensitiveFsCache !== undefined)
67066
+ return _caseInsensitiveFsCache;
67067
+ let result = false;
67068
+ try {
67069
+ const upper = path10.join(dir, `.wmill-CASEPROBE-${process.pid}.tmp`);
67070
+ const lower = path10.join(dir, `.wmill-caseprobe-${process.pid}.tmp`);
67071
+ await writeFile7(upper, "", "utf-8");
67072
+ try {
67073
+ await stat7(lower);
67074
+ result = true;
67075
+ } catch {
67076
+ result = false;
67077
+ }
67078
+ await rm(upper).catch(() => {});
67079
+ await rm(lower).catch(() => {});
67080
+ } catch {
67081
+ result = false;
67082
+ }
67083
+ _caseInsensitiveFsCache = result;
67084
+ return result;
67085
+ }
67086
+ async function compareDynFSElement(els1, els2, ignore, json, skips, ignoreMetadataDeletion, codebases, ignoreCodebaseChanges, specificItems, branchOverride, isEls1Remote, caseInsensitiveFs) {
67087
+ let [m1, m2] = els2 ? await Promise.all([
66935
67088
  elementsToMap(els1, ignore, json, skips, specificItems, branchOverride, isEls1Remote),
66936
67089
  elementsToMap(els2, ignore, json, skips, specificItems, branchOverride, !isEls1Remote)
66937
67090
  ]) : [await elementsToMap(els1, ignore, json, skips, specificItems, branchOverride, isEls1Remote), {}];
67091
+ if (els2 && isEls1Remote !== undefined) {
67092
+ const remoteMap = isEls1Remote ? m1 : m2;
67093
+ warnUnrepresentableCaseCollisions(findCaseInsensitiveCollisions(Object.keys(remoteMap)));
67094
+ if (caseInsensitiveFs) {
67095
+ const { map, rewritten } = canonicalizeCaseInsensitiveKeys(isEls1Remote ? m2 : m1, remoteMap);
67096
+ if (isEls1Remote) {
67097
+ m2 = map;
67098
+ } else {
67099
+ m1 = map;
67100
+ }
67101
+ const summary = summarizeCaseRewrites(rewritten);
67102
+ if (summary.length > 0) {
67103
+ info(`Reconciled ${summary.length} local path(s) to the server's casing ` + `(case-insensitive filesystem):
67104
+ ` + summary.map((s) => ` ${s}`).join(`
67105
+ `));
67106
+ }
67107
+ }
67108
+ }
66938
67109
  const changes = [];
66939
67110
  function parseYaml(k, v) {
66940
67111
  if (k.endsWith(".script.yaml")) {
@@ -67374,7 +67545,7 @@ async function pull(opts) {
67374
67545
  const zipFile = 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.skipWorkspaceDependencies, opts.defaultTs);
67375
67546
  const remote = ZipFSElement(zipFile, !opts.json, opts.defaultTs ?? "bun", resourceTypeToFormatExtension, resourceTypeToIsFileset, true, parseSyncBehavior(opts.syncBehavior) >= 1);
67376
67547
  const local = !opts.stateful ? await FSFSElement(process.cwd(), codebases, true) : await FSFSElement(path10.join(process.cwd(), ".wmill"), [], true);
67377
- const { changes, localMap } = await compareDynFSElement(remote, local, await ignoreF(opts), opts.json ?? false, opts, false, codebases, true, specificItems, wsNameForFiles, true);
67548
+ const { changes, localMap } = await compareDynFSElement(remote, local, await ignoreF(opts), opts.json ?? false, opts, false, codebases, true, specificItems, wsNameForFiles, true, await isCaseInsensitiveFilesystem(process.cwd()));
67378
67549
  info(`remote (${workspace.name}) -> local: ${changes.length} changes to apply`);
67379
67550
  if (wsSpecificMerge.serverItems && wsSpecificMerge.serverItems.length > 0) {
67380
67551
  const changedPaths = new Set(changes.map((c) => c.path));
@@ -67737,7 +67908,7 @@ Push aborted: ${lockIssues.length} script(s) missing locks.`));
67737
67908
  } catch {}
67738
67909
  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.skipWorkspaceDependencies, opts.defaultTs), !opts.json, opts.defaultTs ?? "bun", resourceTypeToFormatExtension, resourceTypeToIsFileset, false, parseSyncBehavior(opts.syncBehavior) >= 1);
67739
67910
  const local = await FSFSElement(path10.join(process.cwd(), ""), codebases, false);
67740
- const { changes, localMap } = await compareDynFSElement(local, remote, await ignoreF(opts), opts.json ?? false, opts, true, codebases, false, specificItems, wsNameForFiles, false);
67911
+ const { changes, localMap } = await compareDynFSElement(local, remote, await ignoreF(opts), opts.json ?? false, opts, true, codebases, false, specificItems, wsNameForFiles, false, await isCaseInsensitiveFilesystem(process.cwd()));
67741
67912
  const wsSpecificFlagOnly = computeWsSpecificFlagOnlyPushes(localMap, localSpecificItems, serverWsSpecificItems);
67742
67913
  for (const item of wsSpecificFlagOnly) {
67743
67914
  if (changes.some((c) => c.path === item.filePath))
@@ -68523,7 +68694,7 @@ Done! All ${changes.length} changes pushed to the remote workspace ${workspace.w
68523
68694
  }
68524
68695
  }
68525
68696
  }
68526
- var import_yaml11, branchDeprecationWarned = false, yamlOptions, isNotWmillFile = (p, isDirectory2) => {
68697
+ var import_yaml11, branchDeprecationWarned = false, yamlOptions, _caseInsensitiveFsCache, isNotWmillFile = (p, isDirectory2) => {
68527
68698
  if (p.endsWith(SEP9)) {
68528
68699
  return false;
68529
68700
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "windmill-cli",
3
- "version": "1.720.0",
3
+ "version": "1.721.0",
4
4
  "description": "CLI for Windmill",
5
5
  "license": "Apache 2.0",
6
6
  "type": "module",