dsh-gitbash-shell 0.39.0 → 0.39.2

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.
package/src/index.js CHANGED
@@ -1168,7 +1168,13 @@ const CODE_LITERAL_SAFE = /^[A-Za-z0-9._@+~\-/ ]+$/
1168
1168
  /** Previous significant character that lets a '/' start a regex literal. */
1169
1169
  function regexCanStart(previous) {
1170
1170
  if (previous === '') return true
1171
- return '(,=:[!&|?{};+-*%<>~^'.indexOf(previous) >= 0
1171
+ if (['return', 'throw', 'yield', 'await', 'case', 'delete', 'void', 'typeof', 'in', 'instanceof', 'of', 'else', 'do', 'break', 'continue', 'debugger'].includes(previous)) return true
1172
+ return previous.length === 1 && '(,=:[!&|?{;+-*%<>~^'.includes(previous)
1173
+ }
1174
+
1175
+ /** Read an identifier as ONE token: keyword-led regexes are not division. */
1176
+ function codeWordAt(code, index) {
1177
+ return /^[A-Za-z_$][A-Za-z0-9_$]*/.exec(code.slice(index))?.[0]
1172
1178
  }
1173
1179
 
1174
1180
  /** Index after the closing quote of the literal at `index`, or -1. */
@@ -1239,6 +1245,15 @@ function skipBracedExpression(code, index) {
1239
1245
  last = 'x'
1240
1246
  continue
1241
1247
  }
1248
+ // A slash after a closing group may start a statement regex OR division.
1249
+ // Without a parser that distinction is uncertain: abort the WHOLE scan.
1250
+ if (c === '/' && (last === ')' || last === '}')) return -1
1251
+ const word = codeWordAt(code, i)
1252
+ if (word) {
1253
+ last = last === '.' ? 'x' : word
1254
+ i += word.length
1255
+ continue
1256
+ }
1242
1257
  if (c === '/' && regexCanStart(last)) {
1243
1258
  const end = skipRegexLiteral(code, i)
1244
1259
  if (end === -1) return -1
@@ -1320,6 +1335,15 @@ export function scanCodeLiterals(code) {
1320
1335
  last = 'x'
1321
1336
  continue
1322
1337
  }
1338
+ // A slash after a closing group may start a statement regex OR division.
1339
+ // Without a parser that distinction is uncertain: abort the WHOLE scan.
1340
+ if (c === '/' && (last === ')' || last === '}')) return null
1341
+ const word = codeWordAt(code, i)
1342
+ if (word) {
1343
+ last = last === '.' ? 'x' : word
1344
+ i += word.length
1345
+ continue
1346
+ }
1323
1347
  if (c === '/' && regexCanStart(last)) {
1324
1348
  const end = skipRegexLiteral(code, i)
1325
1349
  if (end === -1) return null
@@ -1380,8 +1404,8 @@ export function rewriteCodePaths(code, env) {
1380
1404
  // is written into, and a quote inside it (a home directory with an
1381
1405
  // apostrophe) would end that literal early — refuse rather than corrupt.
1382
1406
  const quote = code[span.start - 1]
1383
- if (translated.indexOf(quote) >= 0) continue
1384
- if (quote === '`' && translated.indexOf('${') >= 0) continue
1407
+ if (translated.indexOf(quote) >= 0) return code
1408
+ if (quote === '`' && translated.indexOf('${') >= 0) return code
1385
1409
  const source = translated.replace(/\\/g, '\\\\')
1386
1410
  out += code.slice(cursor, span.start) + source
1387
1411
  cursor = span.end
@@ -1469,9 +1493,6 @@ const SIDEBAR_NS = 'dsh-better-sidebar'
1469
1493
  const SIDEBAR_TRIES = 12
1470
1494
  const SIDEBAR_RETRY_MS = 1500
1471
1495
 
1472
- // Reconciler installed by adoptSidebarShell; a no-op until then.
1473
- let reconcileSidebar = () => {}
1474
-
1475
1496
  /**
1476
1497
  * Reconcile the better-sidebar `terminalShell` takeover with the adoptSidebar
1477
1498
  * setting (v0.15.0, default ON). ON writes our bashPath (remembering what was
@@ -1491,95 +1512,69 @@ function adoptSidebarShell(ctx, bashPath, readAdopt) {
1491
1512
  let previous = ''
1492
1513
  let tried = 0
1493
1514
  let timer = null
1494
- ctx.effect(() => () => { if (timer) clearTimeout(timer) }, 'dsh-gitbash-shell: sidebar adoption polling')
1495
-
1515
+ let closed = false
1516
+ let serial = Promise.resolve()
1496
1517
  const readShell = () => {
1497
1518
  const settings = ctx.get('settings')
1498
1519
  if (!settings || typeof settings.update !== 'function') return null
1499
1520
  try {
1500
- // The sidebar's value lives in ITS row Config, projected into the
1501
- // settings describe() list; the service exposes describe/update only
1502
- // (the old `get(ns)` reader died with dsh 0.1.6).
1503
- if (typeof settings.describe === 'function') {
1504
- const entry = settings.describe().find((row) => row !== null && typeof row === 'object' && row.ns === SIDEBAR_NS)
1505
- const value = entry === undefined ? undefined : entry.value
1506
- return value && typeof value === 'object' && typeof value.terminalShell === 'string' ? value.terminalShell : ''
1507
- }
1508
- return null
1509
- } catch {
1510
- return null // namespace not registered yet / sidebar absent
1521
+ if (typeof settings.describe !== 'function') return null
1522
+ const entry = settings.describe().find((row) => row && row.ns === SIDEBAR_NS)
1523
+ const value = entry?.value
1524
+ return value && typeof value.terminalShell === 'string' ? value.terminalShell : ''
1525
+ } catch { return null }
1526
+ }
1527
+ const restore = async () => {
1528
+ if (!adopted) return
1529
+ const now = readShell()
1530
+ if (now === null) return
1531
+ // The user may have selected another shell since our takeover.
1532
+ if (now === bashPath) {
1533
+ await ctx.get('settings').update(SIDEBAR_NS, { terminalShell: previous })
1534
+ console.log(TAG + " better-sidebar shell restored ('" + previous + "')")
1511
1535
  }
1536
+ adopted = false
1512
1537
  }
1513
-
1514
- reconcileSidebar = (desired) => {
1538
+ const reconcileSidebar = async (desired) => {
1515
1539
  const current = readShell()
1516
1540
  if (current === null) return false
1517
- const settings = ctx.get('settings')
1518
- if (desired && current !== bashPath) {
1519
- const was = current
1520
- settings.update(SIDEBAR_NS, { terminalShell: bashPath })
1521
- .then(() => {
1522
- previous = was
1523
- adopted = true
1524
- console.log(
1525
- `${TAG} dsh-better-sidebar terminal shell -> Git Bash (${bashPath})` +
1526
- (was ? ` (took over from '${was}')` : ''),
1527
- )
1528
- })
1529
- .catch((error) => {
1530
- console.log(`${TAG} better-sidebar shell adoption unavailable: ${error?.message ?? error}`)
1531
- })
1532
- return true
1533
- }
1534
- if (desired && current === bashPath) {
1535
- adopted = true
1536
- return true
1537
- }
1538
- if (!desired && adopted && current === bashPath) {
1539
- settings.update(SIDEBAR_NS, { terminalShell: previous })
1540
- .then(() => {
1541
- adopted = false
1542
- console.log(`${TAG} dsh-better-sidebar terminal shell restored ('${previous || 'sidebar default'}') — adoptSidebar off`)
1543
- })
1544
- .catch(() => {})
1545
- return true
1546
- }
1541
+ if (!desired) { await restore(); return true }
1542
+ if (adopted || current === bashPath) return true
1543
+ previous = current
1544
+ await ctx.get('settings').update(SIDEBAR_NS, { terminalShell: bashPath })
1545
+ adopted = true
1546
+ console.log(TAG + ' dsh-better-sidebar terminal shell -> Git Bash (' + bashPath + ')')
1547
1547
  return true
1548
1548
  }
1549
-
1550
1549
  const run = () => {
1551
- tried += 1
1552
- const ok = reconcileSidebar(readAdopt())
1553
- if (ok) return
1554
- if (tried < SIDEBAR_TRIES) {
1555
- timer = setTimeout(run, SIDEBAR_RETRY_MS)
1556
- return
1557
- }
1558
- console.log(`${TAG} better-sidebar shell adoption skipped: settings service unavailable after ${tried} tries`)
1559
- }
1560
- run.watched = false
1561
- void run()
1562
-
1563
- // Dispose: while we still hold the value, restore what was there before.
1564
- let reverted = false
1565
- ctx.effect(
1566
- () => () => {
1567
- if (reverted || !adopted) return
1568
- reverted = true
1569
- const s = ctx.get('settings')
1570
- if (!s || typeof s.update !== 'function') return
1571
- // Era-aware read (v0.25.1): `settings.get(ns)` is gone on dsh >= 0.1.7, so
1572
- // the old call threw straight into the catch and the takeover was NEVER
1573
- // reverted when this plugin unloaded — same missed-era-read class as
1574
- // issue #8 (readShell above carries both branches).
1575
- const now = readShell()
1576
- if (now === null) return
1577
- if (String(now) === bashPath) {
1578
- s.update(SIDEBAR_NS, { terminalShell: previous }).catch(() => {})
1579
- }
1580
- },
1581
- 'dsh-gitbash-shell: sidebar shell revert',
1582
- )
1550
+ if (closed) return serial
1551
+ if (timer) { clearTimeout(timer); timer = null }
1552
+ serial = serial.then(async () => {
1553
+ if (closed) return
1554
+ tried += 1
1555
+ const ok = await reconcileSidebar(readAdopt())
1556
+ if (ok || closed) return
1557
+ if (tried < SIDEBAR_TRIES) timer = setTimeout(run, SIDEBAR_RETRY_MS)
1558
+ else console.log(TAG + ' better-sidebar shell adoption skipped: settings unavailable')
1559
+ }).catch((error) => {
1560
+ console.log(TAG + ' better-sidebar shell adoption unavailable: ' + (error?.message ?? error))
1561
+ })
1562
+ return serial
1563
+ }
1564
+ try {
1565
+ ctx.on('loader/volatile-update', (paths) => {
1566
+ if (!Array.isArray(paths) || !paths.some((path) => Array.isArray(path) && path[0] === 'adoptSidebar')) return
1567
+ tried = 0
1568
+ void run()
1569
+ })
1570
+ } catch { /* a diagnostic context may have no event service */ }
1571
+ ctx.effect(() => async () => {
1572
+ closed = true
1573
+ if (timer) clearTimeout(timer)
1574
+ await serial
1575
+ await restore()
1576
+ }, 'dsh-gitbash-shell: sidebar shell revert')
1577
+ return run()
1583
1578
  }
1584
1579
 
1585
1580
  // ── plugin ──────────────────────────────────────────────────────────────────
@@ -1759,6 +1754,38 @@ export function minimalPtyBashPath(readResolution) {
1759
1754
  * failures stay visible through the roster's broken diagnostic instead of
1760
1755
  * failing the boot.
1761
1756
  */
1757
+ async function migrateVariantDefault(ctx, readVariants) {
1758
+ if (readVariants() === true) return true
1759
+ const readDefault = () => { try { return ctx.agentPresets.defaultId } catch { return undefined } }
1760
+ const current = readDefault()
1761
+ if (typeof current !== 'string' || !PRESET_IDS.includes(current)) return true
1762
+ const target = VARIANT_DEFAULT_SUCCESSORS[current]
1763
+ try {
1764
+ const settings = ctx.get('settings')
1765
+ if (!settings || typeof settings.update !== 'function') return false
1766
+ const roster = await ctx.agentPresets.list()
1767
+ if (!Array.isArray(roster) || !roster.some((row) => row && row.id === target)) {
1768
+ console.warn(TAG + ' default-preset migration stood down: successor "' + target + '" is not registered. Set an OFFICIAL default or turn variantPresets on.')
1769
+ return false
1770
+ }
1771
+ // A roster read is asynchronous: never overwrite a newer user selection.
1772
+ if (readVariants() === true || readDefault() !== current) return true
1773
+ await settings.update('agent-preset-registry', { selectedDefault: target })
1774
+ const after = readDefault()
1775
+ if (after !== target) {
1776
+ console.warn(TAG + ' default-preset migration did not stick (read back "' + after + '"). Set an OFFICIAL default or turn variantPresets on.')
1777
+ return false
1778
+ }
1779
+ console.log(TAG + ' deployment default migrated: "' + current + '" -> "' + target
1780
+ + '" (variantPresets is off). Pinned sessions are unchanged; agent-preset/locked still requires a NEW session.')
1781
+ return true
1782
+ } catch (error) {
1783
+ console.warn(TAG + ' default-preset migration failed: ' + (error?.message ?? error)
1784
+ + '. Set an OFFICIAL default or turn variantPresets on; variants were not retired.')
1785
+ return false
1786
+ }
1787
+ }
1788
+
1762
1789
  async function registerVariant(ctx, presetId, { gitBashActive, skillsDir, pythonActive, hostExtras = {}, readBashResolution }) {
1763
1790
  const meta = PRESET_META[presetId]
1764
1791
  if (!meta) return undefined
@@ -1811,6 +1838,17 @@ async function runDeclarativeEra(ctx, presetIds, readSuppress, readBashResolutio
1811
1838
  /** The CPython fact the LIVE registrations were built with. */
1812
1839
  let registeredPython = false
1813
1840
  let serial = Promise.resolve()
1841
+ let closed = false
1842
+ ctx.effect(() => async () => {
1843
+ closed = true
1844
+ await serial
1845
+ const failures = []
1846
+ for (const [presetId, off] of [...live]) {
1847
+ live.delete(presetId)
1848
+ try { await off() } catch (error) { failures.push(error) }
1849
+ }
1850
+ if (failures.length) throw new AggregateError(failures, 'Git Bash preset cleanup failed')
1851
+ }, 'dsh-gitbash-shell: declarative preset registrations')
1814
1852
 
1815
1853
  // v0.35.0: the variant roster is opt-in (`variantPresets`, default OFF).
1816
1854
  // OFF means an EMPTY desired set — the cleanup above still runs, so a
@@ -1820,8 +1858,16 @@ async function runDeclarativeEra(ctx, presetIds, readSuppress, readBashResolutio
1820
1858
  : [])
1821
1859
 
1822
1860
  const reconcile = () => {
1861
+ if (closed) return serial
1823
1862
  serial = serial.then(async () => {
1863
+ if (closed) return
1824
1864
  const want = desired()
1865
+ const pythonActive = peerPython
1866
+ // Dynamic OFF repairs the deployment default BEFORE retiring its id.
1867
+ // Keep registrations if the official migration cannot be verified.
1868
+ if (live.size > 0 && readVariants() !== true
1869
+ && !await migrateVariantDefault(ctx, readVariants)) return
1870
+ if (closed) return
1825
1871
  // The peer's EFFECTIVE CPython backend rewrites the workflow rows of
1826
1872
  // EVERY variant (the official Python composition disables them, and
1827
1873
  // workflow-ptc refuses a non-TypeScript backend), so a flip re-registers
@@ -1829,14 +1875,16 @@ async function runDeclarativeEra(ctx, presetIds, readSuppress, readBashResolutio
1829
1875
  // as the backend swap itself: the peer's patch is evaluated at boot. A
1830
1876
  // bare INTENT without the effective backend changes nothing here — the
1831
1877
  // composition still runs Node, so the workflow rows must stay live.
1832
- if (live.size > 0 && registeredPython !== peerPython) {
1878
+ if (live.size > 0 && registeredPython !== pythonActive) {
1833
1879
  for (const [presetId, off] of [...live]) {
1834
1880
  live.delete(presetId)
1835
1881
  try {
1836
1882
  await off()
1837
1883
  console.log(TAG + " preset '" + presetId + "' retired (peer CPython backend changed; rows are rebuilt)")
1838
1884
  } catch (error) {
1885
+ live.set(presetId, off)
1839
1886
  console.log(TAG + " preset '" + presetId + "' retirement failed: " + (error && error.message ? error.message : error))
1887
+ return // never register replacements after an unverified retirement
1840
1888
  }
1841
1889
  }
1842
1890
  }
@@ -1847,14 +1895,18 @@ async function runDeclarativeEra(ctx, presetIds, readSuppress, readBashResolutio
1847
1895
  await off()
1848
1896
  console.log(TAG + " preset '" + presetId + "' retired (dsh-ptc-cordis-preset covers Creation mode on Git Bash)")
1849
1897
  } catch (error) {
1898
+ live.set(presetId, off)
1850
1899
  console.log(TAG + " preset '" + presetId + "' retirement failed: " + (error && error.message ? error.message : error))
1900
+ return
1851
1901
  }
1852
1902
  }
1853
1903
  for (const presetId of want) {
1904
+ if (closed) break
1854
1905
  if (live.has(presetId)) continue
1855
1906
  try {
1856
- const unregister = await registerVariant(ctx, presetId, { gitBashActive, skillsDir, pythonActive: peerPython, hostExtras, readBashResolution })
1907
+ const unregister = await registerVariant(ctx, presetId, { gitBashActive, skillsDir, pythonActive, hostExtras, readBashResolution })
1857
1908
  if (unregister) {
1909
+ if (closed) { await unregister(); break }
1858
1910
  live.set(presetId, unregister)
1859
1911
  console.log(TAG + " preset '" + presetId + "' registered declaratively")
1860
1912
  }
@@ -1862,7 +1914,7 @@ async function runDeclarativeEra(ctx, presetIds, readSuppress, readBashResolutio
1862
1914
  console.log(TAG + " preset '" + presetId + "' registration failed: " + (error && error.message ? error.message : error))
1863
1915
  }
1864
1916
  }
1865
- registeredPython = peerPython
1917
+ registeredPython = pythonActive
1866
1918
  }).catch((error) => {
1867
1919
  console.log(TAG + ' preset reconcile failed: ' + (error && error.message ? error.message : error))
1868
1920
  })
@@ -1870,6 +1922,7 @@ async function runDeclarativeEra(ctx, presetIds, readSuppress, readBashResolutio
1870
1922
  }
1871
1923
 
1872
1924
  await reconcile()
1925
+ if (closed) return
1873
1926
  console.log(TAG + ' registered ' + live.size + ' preset(s) declaratively (' + [...live.keys()].join(', ') + ')'
1874
1927
  + (live.size === 0 ? ' — variantPresets is off: the official presets carry Git Bash through the inline tool patch (see the CHANGELOG)'
1875
1928
  : '')
@@ -1932,7 +1985,8 @@ async function runDeclarativeEra(ctx, presetIds, readSuppress, readBashResolutio
1932
1985
  console.log(TAG + ' dedupe switch wiring failed: ' + (error && error.message ? error.message : error))
1933
1986
  }
1934
1987
 
1935
- ctx.effect(() => () => { for (const off of live.values()) void off() }, 'dsh-gitbash-shell: declarative preset registrations')
1988
+ // If settings appears after an OFF transition failed closed, retry it.
1989
+ ctx.inject(['settings'], () => { void reconcile() })
1936
1990
  }
1937
1991
 
1938
1992
  export async function apply(ctx, config = {}) {
@@ -2611,8 +2665,8 @@ export async function apply(ctx, config = {}) {
2611
2665
  // official tool surface mirrored call-for-call) plus a HIDE on the real pwsh
2612
2666
  // tool while the pwsh switch is off (route B: the official executor stays in
2613
2667
  // place, so pwsh is real PowerShell and hiding it is a product choice). The
2614
- // variantPresets switch is honored BY OBSERVATION — a variant agent's face
2615
- // already has bash, so this patch never fires there.
2668
+ // Full Windows variants keep the same pwsh-only face as official presets,
2669
+ // so they receive this same private-executor patch. Minimal stays persistent.
2616
2670
  if (process.platform === 'win32') {
2617
2671
  try {
2618
2672
  mountInlineTools(ctx, {
@@ -2661,55 +2715,10 @@ export async function apply(ctx, config = {}) {
2661
2715
  // With variantPresets ON nothing here runs: the variants stay registered,
2662
2716
  // so a variant default keeps working as-is.
2663
2717
  try {
2664
- if (ctx.agentPresets && typeof ctx.agentPresets.register === 'function') {
2665
- ctx.inject(['settings'], (settingsCtx) => {
2666
- void (async () => {
2667
- try {
2668
- const settings = settingsCtx.settings
2669
- if (!settings || typeof settings.update !== 'function') return
2670
- // The switch is the whole contract: keep-old-modes ON means a
2671
- // variant default is a legitimate choice and stays untouched.
2672
- if (liveSettings.variantPresets() === true) return
2673
- const readDefault = () => { try { return ctx.agentPresets.defaultId } catch { return undefined } }
2674
- const current = readDefault()
2675
- if (typeof current !== 'string' || !PRESET_IDS.includes(current)) return
2676
- const target = VARIANT_DEFAULT_SUCCESSORS[current]
2677
- if (typeof target !== 'string') return
2678
- // The successor must exist in the CURRENT roster: official ids
2679
- // renamed once already (code→ptc), and writing an unknown id
2680
- // would trade a known failure for a worse one.
2681
- const roster = await ctx.agentPresets.list()
2682
- if (!Array.isArray(roster) || !roster.some((row) => row && row.id === target)) {
2683
- console.warn(TAG + ' default-preset migration stood down: the deployment default is the variant "'
2684
- + current + '" but the successor "' + target + '" is not registered on this host.'
2685
- + ' Set the default manually among the OFFICIAL presets, or turn variantPresets on.')
2686
- return
2687
- }
2688
- try {
2689
- await settings.update('agent-preset-registry', { selectedDefault: target })
2690
- } catch (error) {
2691
- console.warn(TAG + ' default-preset migration write failed (' + (error?.message ?? error)
2692
- + '): the deployment default is still the variant "' + current
2693
- + '" — NEW sessions would fail with agent-preset/not-found.'
2694
- + ' Fix: set an OFFICIAL default in the preset picker, or turn variantPresets on.')
2695
- return
2696
- }
2697
- const after = readDefault()
2698
- if (after === target) {
2699
- console.log(TAG + ' deployment default migrated: "' + current + '" -> "' + target
2700
- + '" (variantPresets is off). Old sessions pinned to a variant still need a NEW'
2701
- + ' session — the official agent-preset/locked rule blocks a mid-session switch.')
2702
- } else {
2703
- console.warn(TAG + ' default-preset migration did not stick (read back "'
2704
- + (after === undefined ? 'unreadable' : after) + '"). Set the default manually in'
2705
- + ' Settings -> agent preset, or turn variantPresets on.')
2706
- }
2707
- } catch (error) {
2708
- console.log(TAG + ' default-preset migration failed: ' + (error?.message ?? error))
2709
- }
2710
- })()
2711
- })
2712
- }
2718
+ ctx.inject(['settings'], () => {
2719
+ // Boot migration is detached: registry.list() may await the Loader boot.
2720
+ void migrateVariantDefault(ctx, () => liveSettings.variantPresets())
2721
+ })
2713
2722
  } catch (error) {
2714
2723
  console.log(TAG + ' default-preset migration wiring failed: ' + (error?.message ?? error))
2715
2724
  }
@@ -2727,8 +2736,21 @@ export async function apply(ctx, config = {}) {
2727
2736
  enabled: () => liveSettings.fastctxTools(),
2728
2737
  createRequireFrom: (base) => createRequire(base ?? import.meta.url),
2729
2738
  tools: (c) => { try { return c.get('tools') } catch { return undefined } },
2730
- msysToWin: (p) => translateMsysPath(p, buildTranslateEnv(readConfiguredBashPath())),
2731
- echo: (text) => (liveSettings.dialect().posixPaths === true ? windowsToMsys(text) : text),
2739
+ msysToWin: (p, execution) => {
2740
+ const dialect = liveSettings.dialect()
2741
+ if (dialect.posixPaths !== true || !dialectApplies(dialect, execution?.agent)) return p
2742
+ if (!dialect.virtualMounts) {
2743
+ return typeof p === 'string' && (MSYS_DRIVE_ROOT.test(p) || MSYS_BARE_DRIVE.test(p))
2744
+ ? translateMsysPath(p, null) : p
2745
+ }
2746
+ return translateMsysPath(p, buildTranslateEnv(readConfiguredBashPath()))
2747
+ },
2748
+ echo: (text, execution) => {
2749
+ const dialect = liveSettings.dialect()
2750
+ if (dialect.posixPaths !== true || !dialectApplies(dialect, execution?.agent)) return text
2751
+ const env = dialect.virtualMounts ? buildTranslateEnv(readConfiguredBashPath()) : null
2752
+ return env ? msysEcho(text, env) : windowsToMsys(text)
2753
+ },
2732
2754
  log: (m) => console.log(TAG + ' ' + m),
2733
2755
  })
2734
2756
  } catch (error) {