@saptools/bruno 0.3.0 → 0.3.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/dist/index.js CHANGED
@@ -3,38 +3,7 @@
3
3
  // src/types.ts
4
4
  var CF_META_KEYS = ["__cf_region", "__cf_org", "__cf_space", "__cf_app"];
5
5
 
6
- // src/paths.ts
7
- import { homedir } from "os";
8
- import { join } from "path";
9
- var SAPTOOLS_DIR_NAME = ".saptools";
10
- var BRUNO_CONTEXT_FILENAME = "bruno-context.json";
11
- var REGION_FOLDER_PREFIX = "region__";
12
- var ORG_FOLDER_PREFIX = "org__";
13
- var SPACE_FOLDER_PREFIX = "space__";
14
- var ENVIRONMENTS_DIR = "environments";
15
- function saptoolsDir() {
16
- return join(homedir(), SAPTOOLS_DIR_NAME);
17
- }
18
- function brunoContextPath() {
19
- return join(saptoolsDir(), BRUNO_CONTEXT_FILENAME);
20
- }
21
- function regionFolderName(key) {
22
- return `${REGION_FOLDER_PREFIX}${key}`;
23
- }
24
- function orgFolderName(name) {
25
- return `${ORG_FOLDER_PREFIX}${name}`;
26
- }
27
- function spaceFolderName(name) {
28
- return `${SPACE_FOLDER_PREFIX}${name}`;
29
- }
30
- function parsePrefixedName(dirName, prefix) {
31
- if (!dirName.startsWith(prefix)) {
32
- return void 0;
33
- }
34
- return dirName.slice(prefix.length);
35
- }
36
-
37
- // src/bru-parser.ts
6
+ // src/bruno/parser.ts
38
7
  var HEADER_REGEX = /(^|\n)[^\S\n]*([a-zA-Z][a-zA-Z0-9:_-]*)[^\S\n]*([{[])/g;
39
8
  function findMatchingClose(raw, open, openIdx) {
40
9
  const close = open === "{" ? "}" : "]";
@@ -122,7 +91,7 @@ function parseBruEnvFile(raw) {
122
91
  return { vars: { entries }, secrets };
123
92
  }
124
93
 
125
- // src/bru-writer.ts
94
+ // src/bruno/writer.ts
126
95
  function formatVarsBlock(entries) {
127
96
  const lines = [];
128
97
  for (const [key, value] of entries) {
@@ -185,7 +154,7 @@ function ensureSecretEntry(raw, secretName) {
185
154
  return { content: `${before}${rebuilt}${after}`, changed: true };
186
155
  }
187
156
 
188
- // src/cf-info.ts
157
+ // src/cf/info.ts
189
158
  import {
190
159
  getRegionView as getRegionViewApi,
191
160
  readRegionsView,
@@ -260,7 +229,7 @@ async function resolveRef(ref, deps = defaultCfInfoDeps) {
260
229
  return { region, org, space, app };
261
230
  }
262
231
 
263
- // src/cf-meta.ts
232
+ // src/cf/meta.ts
264
233
  import { readFile, writeFile } from "fs/promises";
265
234
  function readCfMetaFromVars(vars) {
266
235
  const region = vars.get("__cf_region");
@@ -309,9 +278,42 @@ async function writeCfMetaToFile(path, ref, baseUrl) {
309
278
  return changed;
310
279
  }
311
280
 
312
- // src/folder-scan.ts
281
+ // src/collection/folder-scan.ts
313
282
  import { readdir, readFile as readFile2 } from "fs/promises";
314
283
  import { join as join2 } from "path";
284
+
285
+ // src/collection/paths.ts
286
+ import { homedir } from "os";
287
+ import { join } from "path";
288
+ var SAPTOOLS_DIR_NAME = ".saptools";
289
+ var BRUNO_CONTEXT_FILENAME = "bruno-context.json";
290
+ var REGION_FOLDER_PREFIX = "region__";
291
+ var ORG_FOLDER_PREFIX = "org__";
292
+ var SPACE_FOLDER_PREFIX = "space__";
293
+ var ENVIRONMENTS_DIR = "environments";
294
+ function saptoolsDir() {
295
+ return join(homedir(), SAPTOOLS_DIR_NAME);
296
+ }
297
+ function brunoContextPath() {
298
+ return join(saptoolsDir(), BRUNO_CONTEXT_FILENAME);
299
+ }
300
+ function regionFolderName(key) {
301
+ return `${REGION_FOLDER_PREFIX}${key}`;
302
+ }
303
+ function orgFolderName(name) {
304
+ return `${ORG_FOLDER_PREFIX}${name}`;
305
+ }
306
+ function spaceFolderName(name) {
307
+ return `${SPACE_FOLDER_PREFIX}${name}`;
308
+ }
309
+ function parsePrefixedName(dirName, prefix) {
310
+ if (!dirName.startsWith(prefix)) {
311
+ return void 0;
312
+ }
313
+ return dirName.slice(prefix.length);
314
+ }
315
+
316
+ // src/collection/folder-scan.ts
315
317
  async function safeReaddir(path) {
316
318
  try {
317
319
  const entries = await readdir(path, { withFileTypes: true });
@@ -429,193 +431,11 @@ function parseShorthandPath(shorthand) {
429
431
  return environment ? { region, org, space, app, environment, filePath } : { region, org, space, app, filePath };
430
432
  }
431
433
 
432
- // src/context.ts
433
- import { mkdir, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
434
- import { dirname } from "path";
435
- async function readContext() {
436
- try {
437
- const raw = await readFile3(brunoContextPath(), "utf8");
438
- return JSON.parse(raw);
439
- } catch (err) {
440
- if (err.code === "ENOENT") {
441
- return void 0;
442
- }
443
- throw err;
444
- }
445
- }
446
- async function writeContext(ctx) {
447
- const updated = { ...ctx, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
448
- const path = brunoContextPath();
449
- await mkdir(dirname(path), { recursive: true });
450
- await writeFile2(path, `${JSON.stringify(updated, null, 2)}
451
- `, "utf8");
452
- return updated;
453
- }
454
-
455
- // src/setup-app.ts
456
- import { mkdir as mkdir2, readdir as readdir2, writeFile as writeFile3 } from "fs/promises";
457
- import { basename, join as join3 } from "path";
458
- var COMMON_ENVIRONMENTS = ["local", "dev", "staging", "prod"];
459
- var BRUNO_COLLECTION_CONFIG_FILENAME = "bruno.json";
460
- var ENV_NAME_PATTERN = /^[A-Za-z0-9._-]+$/;
461
- function assertValidEnvName(name) {
462
- if (!ENV_NAME_PATTERN.test(name)) {
463
- throw new Error(
464
- `Invalid environment name '${name}': only letters, digits, dot, underscore, and dash are allowed.`
465
- );
466
- }
467
- }
468
- function emptyEnvContent(envName, ref) {
469
- const lines = [
470
- "vars {",
471
- ` __cf_region: ${ref.region}`,
472
- ` __cf_org: ${ref.org}`,
473
- ` __cf_space: ${ref.space}`,
474
- ` __cf_app: ${ref.app}`,
475
- ` environment: ${envName}`,
476
- " baseUrl: ",
477
- "}",
478
- ""
479
- ];
480
- return lines.join("\n");
481
- }
482
- function normalizeCollectionName(root) {
483
- const candidate = basename(root).replace(/^\.+/, "").trim();
484
- return candidate.length > 0 ? candidate : "bruno-collection";
485
- }
486
- function defaultBrunoConfig(root) {
487
- return `${JSON.stringify(
488
- {
489
- version: "1",
490
- name: normalizeCollectionName(root),
491
- type: "collection",
492
- ignore: ["node_modules", ".git"]
493
- },
494
- null,
495
- 2
496
- )}
497
- `;
498
- }
499
- async function ensureCollectionConfig(root) {
500
- const filePath = join3(root, BRUNO_COLLECTION_CONFIG_FILENAME);
501
- try {
502
- await writeFile3(filePath, defaultBrunoConfig(root), { encoding: "utf8", flag: "wx" });
503
- } catch (err) {
504
- if (err.code !== "EEXIST") {
505
- throw err;
506
- }
507
- }
508
- }
509
- async function ensureEnvFile(appPath, envName, ref) {
510
- const envDir = join3(appPath, ENVIRONMENTS_DIR);
511
- await mkdir2(envDir, { recursive: true });
512
- const filePath = join3(envDir, `${envName}.bru`);
513
- try {
514
- await writeFile3(filePath, emptyEnvContent(envName, ref), { encoding: "utf8", flag: "wx" });
515
- } catch (err) {
516
- if (err.code !== "EEXIST") {
517
- throw err;
518
- }
519
- await writeCfMetaToFile(filePath, ref);
520
- }
521
- return filePath;
522
- }
523
- function pickRegion(regions) {
524
- return regions.map((r) => ({ value: r.key, name: `${r.key} \u2014 ${r.label} (${r.orgCount.toString()} org${r.orgCount === 1 ? "" : "s"})` }));
525
- }
526
- function pickOrg(region) {
527
- return region.orgs.map((o) => ({ value: o.name, name: `${o.name} (${o.spaces.length.toString()} space${o.spaces.length === 1 ? "" : "s"})` }));
528
- }
529
- function pickSpace(org) {
530
- return org.spaces.map((s) => ({ value: s.name, name: `${s.name} (${s.apps.length.toString()} app${s.apps.length === 1 ? "" : "s"})` }));
531
- }
532
- function pickApp(space) {
533
- return space.apps.map((a) => ({ value: a.name, name: a.name }));
534
- }
535
- async function setupApp(options) {
536
- const deps = options.deps ?? defaultCfInfoDeps;
537
- const log = options.log ?? (() => void 0);
538
- const regions = await listRegionsWithContent(deps);
539
- if (regions.length === 0) {
540
- throw new Error("No CF regions with orgs are cached. Run `cf-sync sync` first.");
541
- }
542
- const regionKey = await options.prompts.selectRegion(pickRegion(regions));
543
- const regionView = await deps.readRegionView(regionKey);
544
- if (!regionView) {
545
- throw new Error(`Region ${regionKey} is not cached. Run \`cf-sync region ${regionKey}\` or \`cf-sync sync\` first.`);
546
- }
547
- const region = regionView.region;
548
- if (region.orgs.length === 0) {
549
- throw new Error(`Region ${regionKey} has no accessible orgs.`);
550
- }
551
- const orgName = await options.prompts.selectOrg(pickOrg(region));
552
- const org = region.orgs.find((o) => o.name === orgName);
553
- if (!org) {
554
- throw new Error(`Org ${orgName} not found in region ${regionKey}`);
555
- }
556
- if (org.spaces.length === 0) {
557
- throw new Error(`Org ${orgName} has no spaces.`);
558
- }
559
- const spaceName = await options.prompts.selectSpace(pickSpace(org));
560
- const space = org.spaces.find((s) => s.name === spaceName);
561
- if (!space) {
562
- throw new Error(`Space ${spaceName} not found in org ${orgName}`);
563
- }
564
- if (space.apps.length === 0) {
565
- throw new Error(`Space ${spaceName} has no apps.`);
566
- }
567
- const appName = await options.prompts.selectApp(pickApp(space));
568
- const ref = { region: regionKey, org: orgName, space: spaceName, app: appName };
569
- const appPath = join3(
570
- options.root,
571
- regionFolderName(regionKey),
572
- orgFolderName(orgName),
573
- spaceFolderName(spaceName),
574
- appName
575
- );
576
- const confirmed = await options.prompts.confirmCreate(appPath);
577
- if (!confirmed) {
578
- return { ref, appPath, environments: [], created: false };
579
- }
580
- await mkdir2(appPath, { recursive: true });
581
- await ensureCollectionConfig(appPath);
582
- const existingEnvs = await listExistingEnvs(appPath);
583
- const common = [...COMMON_ENVIRONMENTS];
584
- const selected = await options.prompts.selectEnvironments({ common, existing: existingEnvs });
585
- const merged = [];
586
- for (const name of selected) {
587
- const trimmed = name.trim();
588
- if (trimmed.length === 0 || merged.includes(trimmed)) {
589
- continue;
590
- }
591
- assertValidEnvName(trimmed);
592
- merged.push(trimmed);
593
- }
594
- if (merged.length === 0) {
595
- throw new Error("At least one environment is required.");
596
- }
597
- const created = [];
598
- for (const envName of merged) {
599
- const path = await ensureEnvFile(appPath, envName, ref);
600
- created.push(path);
601
- log(`\u2022 ${path}`);
602
- }
603
- return { ref, appPath, environments: created, created: true };
604
- }
605
- async function listExistingEnvs(appPath) {
606
- try {
607
- const entries = await readdir2(join3(appPath, ENVIRONMENTS_DIR), { withFileTypes: true });
608
- return entries.filter((e) => e.isFile() && e.name.endsWith(".bru")).map((e) => e.name.replace(/\.bru$/, ""));
609
- } catch {
610
- return [];
611
- }
612
- }
613
-
614
- // src/run.ts
434
+ // src/commands/run.ts
615
435
  import { spawn } from "child_process";
616
- import { readFile as readFile4, stat, writeFile as writeFile4 } from "fs/promises";
436
+ import { readFile as readFile3, stat, writeFile as writeFile2 } from "fs/promises";
617
437
  import { createRequire } from "module";
618
- import { delimiter, dirname as dirname2, isAbsolute, join as join4, relative, resolve, sep } from "path";
438
+ import { delimiter, dirname, isAbsolute, join as join3, relative, resolve, sep } from "path";
619
439
  import { getTokenCached as getTokenCachedApi } from "@saptools/cf-xsuaa";
620
440
  var require2 = createRequire(import.meta.url);
621
441
  function pathEntries(env) {
@@ -633,7 +453,7 @@ async function findCommandOnPath(command, env) {
633
453
  const candidates = pathCandidates(command, env);
634
454
  for (const entry of pathEntries(env)) {
635
455
  for (const candidate of candidates) {
636
- const fullPath = join4(entry, candidate);
456
+ const fullPath = join3(entry, candidate);
637
457
  if (await exists(fullPath)) {
638
458
  return fullPath;
639
459
  }
@@ -662,7 +482,7 @@ function defaultResolvePackageJsonPath() {
662
482
  return require2.resolve("@usebruno/cli/package.json");
663
483
  }
664
484
  async function defaultReadTextFile(path) {
665
- return await readFile4(path, "utf8");
485
+ return await readFile3(path, "utf8");
666
486
  }
667
487
  async function resolveBundledBruBinPath(deps) {
668
488
  try {
@@ -672,7 +492,7 @@ async function resolveBundledBruBinPath(deps) {
672
492
  if (!binPath) {
673
493
  return void 0;
674
494
  }
675
- return resolve(dirname2(packageJsonPath), binPath);
495
+ return resolve(dirname(packageJsonPath), binPath);
676
496
  } catch {
677
497
  return void 0;
678
498
  }
@@ -728,7 +548,7 @@ async function resolveTarget(root, target) {
728
548
  throw new Error(`Target not found: ${target}`);
729
549
  }
730
550
  const { region, org, space, app, filePath } = shorthand;
731
- const appDir = join4(
551
+ const appDir = join3(
732
552
  root,
733
553
  regionFolderName(region),
734
554
  orgFolderName(org),
@@ -738,7 +558,7 @@ async function resolveTarget(root, target) {
738
558
  if (!filePath) {
739
559
  return { filePath: appDir, shorthand };
740
560
  }
741
- const candidate = join4(appDir, filePath);
561
+ const candidate = join3(appDir, filePath);
742
562
  if (await exists(candidate)) {
743
563
  return { filePath: candidate, shorthand };
744
564
  }
@@ -750,7 +570,7 @@ async function resolveTarget(root, target) {
750
570
  }
751
571
  async function chooseEnvironmentFile(appDir, environment) {
752
572
  if (environment) {
753
- const envFile = join4(appDir, ENVIRONMENTS_DIR, `${environment}.bru`);
573
+ const envFile = join3(appDir, ENVIRONMENTS_DIR, `${environment}.bru`);
754
574
  if (!await exists(envFile)) {
755
575
  throw new Error(`Environment file not found: ${envFile}`);
756
576
  }
@@ -782,13 +602,13 @@ function findAppDirFromFile(filePath, root) {
782
602
  if (!regionDir || !orgDir || !spaceDir || !appDir) {
783
603
  throw new Error(`File is not inside a CF-structured bruno collection: ${filePath}`);
784
604
  }
785
- return join4(root, regionDir, orgDir, spaceDir, appDir);
605
+ return join3(root, regionDir, orgDir, spaceDir, appDir);
786
606
  }
787
607
  async function persistAccessToken(envFile, token) {
788
- const raw = await readFile4(envFile, "utf8");
608
+ const raw = await readFile3(envFile, "utf8");
789
609
  const { content, changed } = upsertVars(raw, /* @__PURE__ */ new Map([["accessToken", token]]));
790
610
  if (changed) {
791
- await writeFile4(envFile, content, "utf8");
611
+ await writeFile2(envFile, content, "utf8");
792
612
  }
793
613
  }
794
614
  async function buildRunPlan(options) {
@@ -840,7 +660,189 @@ async function runBruno(options) {
840
660
  return { ...plan, ...result };
841
661
  }
842
662
 
843
- // src/use.ts
663
+ // src/commands/setup-app.ts
664
+ import { mkdir, readdir as readdir2, writeFile as writeFile3 } from "fs/promises";
665
+ import { basename, join as join4 } from "path";
666
+ var COMMON_ENVIRONMENTS = ["local", "dev", "staging", "prod"];
667
+ var BRUNO_COLLECTION_CONFIG_FILENAME = "bruno.json";
668
+ var ENV_NAME_PATTERN = /^[A-Za-z0-9._-]+$/;
669
+ function assertValidEnvName(name) {
670
+ if (!ENV_NAME_PATTERN.test(name)) {
671
+ throw new Error(
672
+ `Invalid environment name '${name}': only letters, digits, dot, underscore, and dash are allowed.`
673
+ );
674
+ }
675
+ }
676
+ function emptyEnvContent(envName, ref) {
677
+ const lines = [
678
+ "vars {",
679
+ ` __cf_region: ${ref.region}`,
680
+ ` __cf_org: ${ref.org}`,
681
+ ` __cf_space: ${ref.space}`,
682
+ ` __cf_app: ${ref.app}`,
683
+ ` environment: ${envName}`,
684
+ " baseUrl: ",
685
+ "}",
686
+ ""
687
+ ];
688
+ return lines.join("\n");
689
+ }
690
+ function normalizeCollectionName(root) {
691
+ const candidate = basename(root).replace(/^\.+/, "").trim();
692
+ return candidate.length > 0 ? candidate : "bruno-collection";
693
+ }
694
+ function defaultBrunoConfig(root) {
695
+ return `${JSON.stringify(
696
+ {
697
+ version: "1",
698
+ name: normalizeCollectionName(root),
699
+ type: "collection",
700
+ ignore: ["node_modules", ".git"]
701
+ },
702
+ null,
703
+ 2
704
+ )}
705
+ `;
706
+ }
707
+ async function ensureCollectionConfig(root) {
708
+ const filePath = join4(root, BRUNO_COLLECTION_CONFIG_FILENAME);
709
+ try {
710
+ await writeFile3(filePath, defaultBrunoConfig(root), { encoding: "utf8", flag: "wx" });
711
+ } catch (err) {
712
+ if (err.code !== "EEXIST") {
713
+ throw err;
714
+ }
715
+ }
716
+ }
717
+ async function ensureEnvFile(appPath, envName, ref) {
718
+ const envDir = join4(appPath, ENVIRONMENTS_DIR);
719
+ await mkdir(envDir, { recursive: true });
720
+ const filePath = join4(envDir, `${envName}.bru`);
721
+ try {
722
+ await writeFile3(filePath, emptyEnvContent(envName, ref), { encoding: "utf8", flag: "wx" });
723
+ } catch (err) {
724
+ if (err.code !== "EEXIST") {
725
+ throw err;
726
+ }
727
+ await writeCfMetaToFile(filePath, ref);
728
+ }
729
+ return filePath;
730
+ }
731
+ function pickRegion(regions) {
732
+ return regions.map((r) => ({ value: r.key, name: `${r.key} \u2014 ${r.label} (${r.orgCount.toString()} org${r.orgCount === 1 ? "" : "s"})` }));
733
+ }
734
+ function pickOrg(region) {
735
+ return region.orgs.map((o) => ({ value: o.name, name: `${o.name} (${o.spaces.length.toString()} space${o.spaces.length === 1 ? "" : "s"})` }));
736
+ }
737
+ function pickSpace(org) {
738
+ return org.spaces.map((s) => ({ value: s.name, name: `${s.name} (${s.apps.length.toString()} app${s.apps.length === 1 ? "" : "s"})` }));
739
+ }
740
+ function pickApp(space) {
741
+ return space.apps.map((a) => ({ value: a.name, name: a.name }));
742
+ }
743
+ async function setupApp(options) {
744
+ const deps = options.deps ?? defaultCfInfoDeps;
745
+ const log = options.log ?? (() => void 0);
746
+ const regions = await listRegionsWithContent(deps);
747
+ if (regions.length === 0) {
748
+ throw new Error("No CF regions with orgs are cached. Run `cf-sync sync` first.");
749
+ }
750
+ const regionKey = await options.prompts.selectRegion(pickRegion(regions));
751
+ const regionView = await deps.readRegionView(regionKey);
752
+ if (!regionView) {
753
+ throw new Error(`Region ${regionKey} is not cached. Run \`cf-sync region ${regionKey}\` or \`cf-sync sync\` first.`);
754
+ }
755
+ const region = regionView.region;
756
+ if (region.orgs.length === 0) {
757
+ throw new Error(`Region ${regionKey} has no accessible orgs.`);
758
+ }
759
+ const orgName = await options.prompts.selectOrg(pickOrg(region));
760
+ const org = region.orgs.find((o) => o.name === orgName);
761
+ if (!org) {
762
+ throw new Error(`Org ${orgName} not found in region ${regionKey}`);
763
+ }
764
+ if (org.spaces.length === 0) {
765
+ throw new Error(`Org ${orgName} has no spaces.`);
766
+ }
767
+ const spaceName = await options.prompts.selectSpace(pickSpace(org));
768
+ const space = org.spaces.find((s) => s.name === spaceName);
769
+ if (!space) {
770
+ throw new Error(`Space ${spaceName} not found in org ${orgName}`);
771
+ }
772
+ if (space.apps.length === 0) {
773
+ throw new Error(`Space ${spaceName} has no apps.`);
774
+ }
775
+ const appName = await options.prompts.selectApp(pickApp(space));
776
+ const ref = { region: regionKey, org: orgName, space: spaceName, app: appName };
777
+ const appPath = join4(
778
+ options.root,
779
+ regionFolderName(regionKey),
780
+ orgFolderName(orgName),
781
+ spaceFolderName(spaceName),
782
+ appName
783
+ );
784
+ const confirmed = await options.prompts.confirmCreate(appPath);
785
+ if (!confirmed) {
786
+ return { ref, appPath, environments: [], created: false };
787
+ }
788
+ await mkdir(appPath, { recursive: true });
789
+ await ensureCollectionConfig(appPath);
790
+ const existingEnvs = await listExistingEnvs(appPath);
791
+ const common = [...COMMON_ENVIRONMENTS];
792
+ const selected = await options.prompts.selectEnvironments({ common, existing: existingEnvs });
793
+ const merged = [];
794
+ for (const name of selected) {
795
+ const trimmed = name.trim();
796
+ if (trimmed.length === 0 || merged.includes(trimmed)) {
797
+ continue;
798
+ }
799
+ assertValidEnvName(trimmed);
800
+ merged.push(trimmed);
801
+ }
802
+ if (merged.length === 0) {
803
+ throw new Error("At least one environment is required.");
804
+ }
805
+ const created = [];
806
+ for (const envName of merged) {
807
+ const path = await ensureEnvFile(appPath, envName, ref);
808
+ created.push(path);
809
+ log(`\u2022 ${path}`);
810
+ }
811
+ return { ref, appPath, environments: created, created: true };
812
+ }
813
+ async function listExistingEnvs(appPath) {
814
+ try {
815
+ const entries = await readdir2(join4(appPath, ENVIRONMENTS_DIR), { withFileTypes: true });
816
+ return entries.filter((e) => e.isFile() && e.name.endsWith(".bru")).map((e) => e.name.replace(/\.bru$/, ""));
817
+ } catch {
818
+ return [];
819
+ }
820
+ }
821
+
822
+ // src/state/context.ts
823
+ import { mkdir as mkdir2, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
824
+ import { dirname as dirname2 } from "path";
825
+ async function readContext() {
826
+ try {
827
+ const raw = await readFile4(brunoContextPath(), "utf8");
828
+ return JSON.parse(raw);
829
+ } catch (err) {
830
+ if (err.code === "ENOENT") {
831
+ return void 0;
832
+ }
833
+ throw err;
834
+ }
835
+ }
836
+ async function writeContext(ctx) {
837
+ const updated = { ...ctx, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
838
+ const path = brunoContextPath();
839
+ await mkdir2(dirname2(path), { recursive: true });
840
+ await writeFile4(path, `${JSON.stringify(updated, null, 2)}
841
+ `, "utf8");
842
+ return updated;
843
+ }
844
+
845
+ // src/commands/use.ts
844
846
  function parseContextShorthand(shorthand) {
845
847
  const segs = shorthand.split("/").filter((s) => s.length > 0);
846
848
  if (segs.length !== 4) {