@stim-cli/core 1.20.1 → 1.21.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.
@@ -0,0 +1,64 @@
1
+ //#region toolchain.d.ts
2
+ /** How `discoverToolchain` and `projectNodeBin` look at this machine; tests pass a fake file system. */
3
+ interface ToolchainProbe {
4
+ home: string;
5
+ /** The running Node, whose directory is the last `PATH` entry so `node` always resolves. */
6
+ execPath: string;
7
+ isDir(path: string): boolean;
8
+ isFile(path: string): boolean;
9
+ read(path: string): string | null;
10
+ list(path: string): string[];
11
+ }
12
+ declare function defaultToolchainProbe(): ToolchainProbe;
13
+ /**
14
+ * One toolchain fact for `stim doctor`: `source` is `environment` when the caller's environment already set it,
15
+ * `discovered` when `discoverToolchain` found it at `from`, and `missing` when neither did. A discovered
16
+ * `JAVA_HOME` for a system JDK is reported but not set, since `/usr/bin/java` already finds it.
17
+ */
18
+ interface ToolchainEntry {
19
+ name: string;
20
+ value: string | null;
21
+ source: "environment" | "discovered" | "missing";
22
+ from?: string;
23
+ }
24
+ interface Toolchain {
25
+ env: Record<string, string>;
26
+ entries: ToolchainEntry[];
27
+ }
28
+ /** Whether Node `version`, such as `22.12.0`, satisfies a pin such as `22`, `v22.12` or `22.12.0`. */
29
+ declare function satisfiesNodePin(version: string, spec: string): boolean;
30
+ /** The `bin` directory of the Node a version manager makes the default in new shells, and where that came from. */
31
+ declare function defaultNodeBin(env: NodeJS.ProcessEnv, probe: ToolchainProbe): {
32
+ bin: string;
33
+ from: string;
34
+ } | null;
35
+ /**
36
+ * The toolchain environment a process started outside a login shell, such as stim-server under Stim Desktop or
37
+ * launchd, needs: `base` with Homebrew, `~/.local/bin`, the default Node's directory and the Android SDK tools
38
+ * appended to `PATH`, and `ANDROID_HOME` and `JAVA_HOME` set when `base` lacks them and they can be found. It only
39
+ * fills gaps: nothing `base` sets is replaced, and every added `PATH` entry goes after the existing ones. Unless
40
+ * `runningNode` is false, the running Node's directory is always the last entry, so `node` resolves. `PNPM_HOME` is
41
+ * set to pnpm's global directory when that is added, since pnpm's global commands need it.
42
+ */
43
+ declare function discoverToolchain(base: NodeJS.ProcessEnv, probe?: ToolchainProbe, { runningNode, platform }?: {
44
+ runningNode?: boolean;
45
+ platform?: NodeJS.Platform;
46
+ }): Toolchain;
47
+ /** The Node version `root` pins, from `.nvmrc`, `.node-version`, `.tool-versions` or `package.json`'s `volta.node`. */
48
+ declare function projectNodePin(root: string, probe?: ToolchainProbe): {
49
+ spec: string;
50
+ file: string;
51
+ } | null;
52
+ /**
53
+ * The `bin` directory of an installed Node that satisfies `root`'s pin, from nvm, fnm, Volta, asdf or mise, or
54
+ * nil when the project pins none or no installed version satisfies the pin. An `lts/...` pin resolves through
55
+ * nvm's aliases.
56
+ */
57
+ declare function projectNodeBin(root: string, env: NodeJS.ProcessEnv, probe?: ToolchainProbe): {
58
+ bin: string;
59
+ version: string;
60
+ pin: string;
61
+ spec: string;
62
+ } | null;
63
+ //#endregion
64
+ export { Toolchain, ToolchainEntry, ToolchainProbe, defaultNodeBin, defaultToolchainProbe, discoverToolchain, projectNodeBin, projectNodePin, satisfiesNodePin };
@@ -0,0 +1,326 @@
1
+ import { readFileSync, readdirSync, statSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { delimiter, dirname, join } from "node:path";
4
+ //#region toolchain.ts
5
+ const stat = (path) => statSync(path, { throwIfNoEntry: false });
6
+ function defaultToolchainProbe() {
7
+ return {
8
+ home: homedir(),
9
+ execPath: process.execPath,
10
+ isDir: (path) => stat(path)?.isDirectory() ?? false,
11
+ isFile: (path) => stat(path)?.isFile() ?? false,
12
+ read: (path) => {
13
+ try {
14
+ return readFileSync(path, "utf8");
15
+ } catch {
16
+ return null;
17
+ }
18
+ },
19
+ list: (path) => {
20
+ try {
21
+ return readdirSync(path);
22
+ } catch {
23
+ return [];
24
+ }
25
+ }
26
+ };
27
+ }
28
+ const HOMEBREW_BINS = [
29
+ "/opt/homebrew/bin",
30
+ "/opt/homebrew/sbin",
31
+ "/usr/local/bin"
32
+ ];
33
+ const SYSTEM_JDKS = "/Library/Java/JavaVirtualMachines";
34
+ const HOMEBREW_PREFIXES = ["/opt/homebrew", "/usr/local"];
35
+ /** Homebrew JDKs in the order Android builds prefer them: 17, then 21, then the rest from newest. */
36
+ function jdkRank(name) {
37
+ const major = Number(/@(\d+)$/.exec(name)?.[1] ?? 1e3);
38
+ return major === 17 ? 0 : major === 21 ? 1 : 1e4 - major;
39
+ }
40
+ const ANDROID_STUDIOS = ["/Applications/Android Studio.app", "/Applications/Android Studio Preview.app"];
41
+ function parseVersion(text) {
42
+ const match = /^v?(\d+)\.(\d+)\.(\d+)$/.exec(text.trim());
43
+ return match ? [
44
+ Number(match[1]),
45
+ Number(match[2]),
46
+ Number(match[3])
47
+ ] : null;
48
+ }
49
+ function compareVersions(a, b) {
50
+ return a[0] - b[0] || a[1] - b[1] || a[2] - b[2];
51
+ }
52
+ /** The newest installed version, of `names` such as `v22.12.0` or `22.12.0`, that `wanted` names. */
53
+ function newestMatching(names, wanted) {
54
+ const spec = wanted.trim().replace(/^v/, "");
55
+ const exact = parseVersion(spec);
56
+ let best = null;
57
+ for (const name of names) {
58
+ const version = parseVersion(name);
59
+ if (!version) continue;
60
+ if (exact ? compareVersions(version, exact) !== 0 : !matchesPrefix(version, spec)) continue;
61
+ if (!best || compareVersions(version, best.version) > 0) best = {
62
+ name,
63
+ version
64
+ };
65
+ }
66
+ return best?.name ?? null;
67
+ }
68
+ /** Whether Node `version`, such as `22.12.0`, satisfies a pin such as `22`, `v22.12` or `22.12.0`. */
69
+ function satisfiesNodePin(version, spec) {
70
+ return newestMatching([version], spec) !== null;
71
+ }
72
+ function matchesPrefix(version, spec) {
73
+ if (spec === "" || spec === "node" || spec === "stable") return true;
74
+ const parts = spec.split(".");
75
+ if (parts.length > 3 || parts.some((part) => !/^\d+$/.test(part))) return false;
76
+ return parts.every((part, index) => Number(part) === version[index]);
77
+ }
78
+ /** nvm's `alias/<name>` chain, which can name another alias such as `lts/*`, down to a version spec. */
79
+ function nvmAlias(nvm, name, probe) {
80
+ let spec = name;
81
+ for (let hops = 0; hops < 8; hops++) {
82
+ const next = probe.read(join(nvm, "alias", spec))?.trim();
83
+ if (!next) return spec;
84
+ spec = next;
85
+ }
86
+ return spec;
87
+ }
88
+ function nvmDir(env, probe) {
89
+ return env.NVM_DIR || join(probe.home, ".nvm");
90
+ }
91
+ /** The `bin` directory of the Node a version manager makes the default in new shells, and where that came from. */
92
+ function defaultNodeBin(env, probe) {
93
+ const nvm = nvmDir(env, probe);
94
+ if (probe.isFile(join(nvm, "alias", "default"))) {
95
+ const spec = nvmAlias(nvm, "default", probe);
96
+ const version = newestMatching(probe.list(join(nvm, "versions", "node")), spec);
97
+ if (version) return {
98
+ bin: join(nvm, "versions", "node", version, "bin"),
99
+ from: `nvm default (${spec})`
100
+ };
101
+ }
102
+ return [
103
+ {
104
+ bin: join(probe.home, ".local", "share", "fnm", "aliases", "default", "bin"),
105
+ from: "fnm default"
106
+ },
107
+ {
108
+ bin: join(probe.home, "Library", "Application Support", "fnm", "aliases", "default", "bin"),
109
+ from: "fnm default"
110
+ },
111
+ {
112
+ bin: join(probe.home, ".volta", "bin"),
113
+ from: "Volta"
114
+ },
115
+ {
116
+ bin: join(probe.home, ".local", "share", "mise", "shims"),
117
+ from: "mise shims"
118
+ },
119
+ {
120
+ bin: join(env.ASDF_DATA_DIR || join(probe.home, ".asdf"), "shims"),
121
+ from: "asdf shims"
122
+ }
123
+ ].find(({ bin }) => probe.isFile(join(bin, "node"))) ?? null;
124
+ }
125
+ function pathEntries(path) {
126
+ return (path ?? "").split(delimiter).filter(Boolean);
127
+ }
128
+ /**
129
+ * The toolchain environment a process started outside a login shell, such as stim-server under Stim Desktop or
130
+ * launchd, needs: `base` with Homebrew, `~/.local/bin`, the default Node's directory and the Android SDK tools
131
+ * appended to `PATH`, and `ANDROID_HOME` and `JAVA_HOME` set when `base` lacks them and they can be found. It only
132
+ * fills gaps: nothing `base` sets is replaced, and every added `PATH` entry goes after the existing ones. Unless
133
+ * `runningNode` is false, the running Node's directory is always the last entry, so `node` resolves. `PNPM_HOME` is
134
+ * set to pnpm's global directory when that is added, since pnpm's global commands need it.
135
+ */
136
+ function discoverToolchain(base, probe = defaultToolchainProbe(), { runningNode = true, platform = process.platform } = {}) {
137
+ const env = {};
138
+ for (const [key, value] of Object.entries(base)) if (value !== void 0) env[platform === "win32" && /^path$/i.test(key) ? "PATH" : key] = value;
139
+ const entries = [];
140
+ const path = pathEntries(env.PATH);
141
+ const append = (dir) => {
142
+ if (!path.includes(dir) && probe.isDir(dir)) path.push(dir);
143
+ };
144
+ const onPath = (name) => path.find((dir) => probe.isFile(join(dir, name))) ?? null;
145
+ const nodeBefore = onPath("node");
146
+ const defaultNode = nodeBefore ? null : defaultNodeBin(env, probe);
147
+ if (defaultNode) append(defaultNode.bin);
148
+ for (const dir of HOMEBREW_BINS) append(dir);
149
+ for (const dir of [
150
+ ".local/bin",
151
+ "Library/pnpm",
152
+ ".bun/bin"
153
+ ]) append(join(probe.home, dir));
154
+ const pnpmHome = join(probe.home, "Library", "pnpm");
155
+ if (!env.PNPM_HOME && path.includes(pnpmHome)) env.PNPM_HOME = pnpmHome;
156
+ const sdkSet = env.ANDROID_HOME || env.ANDROID_SDK_ROOT;
157
+ const standardSdk = join(probe.home, "Library", "Android", "sdk");
158
+ const sdk = sdkSet || (probe.isDir(standardSdk) ? standardSdk : null);
159
+ if (sdk && !sdkSet) env.ANDROID_HOME = sdk;
160
+ if (sdk) for (const tools of ["platform-tools", "emulator"]) append(join(sdk, tools));
161
+ entries.push(sdkSet ? {
162
+ name: "ANDROID_HOME",
163
+ value: sdkSet,
164
+ source: "environment"
165
+ } : sdk ? {
166
+ name: "ANDROID_HOME",
167
+ value: sdk,
168
+ source: "discovered",
169
+ from: "the Android Studio default location"
170
+ } : {
171
+ name: "ANDROID_HOME",
172
+ value: null,
173
+ source: "missing"
174
+ });
175
+ entries.push(javaEntry(env, probe));
176
+ const running = dirname(probe.execPath);
177
+ if (runningNode) append(running);
178
+ env.PATH = path.join(delimiter);
179
+ const node = onPath("node");
180
+ const nodeFrom = nodeBefore ? null : defaultNode && node === defaultNode.bin ? defaultNode.from : node === running ? "the Node running Stim" : node && `${node} on the discovered PATH`;
181
+ entries.unshift({
182
+ name: "node",
183
+ value: node && join(node, "node"),
184
+ source: nodeBefore ? "environment" : "discovered",
185
+ ...nodeFrom ? { from: nodeFrom } : {}
186
+ });
187
+ for (const tool of [
188
+ "stim",
189
+ "brew",
190
+ "adb"
191
+ ]) {
192
+ const dir = onPath(tool);
193
+ entries.push(dir ? {
194
+ name: tool,
195
+ value: join(dir, tool),
196
+ source: pathEntries(base.PATH).includes(dir) ? "environment" : "discovered"
197
+ } : {
198
+ name: tool,
199
+ value: null,
200
+ source: "missing"
201
+ });
202
+ }
203
+ return {
204
+ env,
205
+ entries
206
+ };
207
+ }
208
+ /**
209
+ * `JAVA_HOME` for Gradle. A JDK under /Library/Java/JavaVirtualMachines needs none, because `/usr/bin/java` finds
210
+ * it. Without one, a Homebrew `openjdk`, which /usr/bin/java does not find, is set (17, then 21, then the newest),
211
+ * else Android Studio's bundled runtime.
212
+ */
213
+ function javaEntry(env, probe) {
214
+ if (env.JAVA_HOME) return {
215
+ name: "JAVA_HOME",
216
+ value: env.JAVA_HOME,
217
+ source: "environment"
218
+ };
219
+ const system = probe.list(SYSTEM_JDKS).map((name) => join(SYSTEM_JDKS, name, "Contents", "Home")).find((home) => probe.isFile(join(home, "bin", "java")));
220
+ if (system) return {
221
+ name: "JAVA_HOME",
222
+ value: system,
223
+ source: "discovered",
224
+ from: "a system JDK, which /usr/bin/java runs"
225
+ };
226
+ const brewed = HOMEBREW_PREFIXES.flatMap((prefix) => probe.list(join(prefix, "opt")).filter((name) => /^openjdk(@\d+)?$/.test(name)).map((name) => ({
227
+ name,
228
+ home: join(prefix, "opt", name, "libexec", "openjdk.jdk", "Contents", "Home")
229
+ }))).filter(({ home }) => probe.isFile(join(home, "bin", "java"))).toSorted((a, b) => jdkRank(a.name) - jdkRank(b.name))[0];
230
+ if (brewed) {
231
+ env.JAVA_HOME = brewed.home;
232
+ return {
233
+ name: "JAVA_HOME",
234
+ value: brewed.home,
235
+ source: "discovered",
236
+ from: `Homebrew ${brewed.name}`
237
+ };
238
+ }
239
+ for (const studio of ANDROID_STUDIOS) {
240
+ const jbr = join(studio, "Contents", "jbr", "Contents", "Home");
241
+ if (probe.isFile(join(jbr, "bin", "java"))) {
242
+ env.JAVA_HOME = jbr;
243
+ return {
244
+ name: "JAVA_HOME",
245
+ value: jbr,
246
+ source: "discovered",
247
+ from: "Android Studio's bundled runtime"
248
+ };
249
+ }
250
+ }
251
+ return {
252
+ name: "JAVA_HOME",
253
+ value: null,
254
+ source: "missing"
255
+ };
256
+ }
257
+ /** The Node version `root` pins, from `.nvmrc`, `.node-version`, `.tool-versions` or `package.json`'s `volta.node`. */
258
+ function projectNodePin(root, probe = defaultToolchainProbe()) {
259
+ for (const file of [".nvmrc", ".node-version"]) {
260
+ const line = probe.read(join(root, file))?.split("\n").map((text) => text.trim()).find((text) => text && !text.startsWith("#"));
261
+ if (line) return {
262
+ spec: line,
263
+ file
264
+ };
265
+ }
266
+ const tools = probe.read(join(root, ".tool-versions"));
267
+ const asdf = tools && /^nodejs\s+(\S+)/m.exec(tools)?.[1];
268
+ if (asdf) return {
269
+ spec: asdf,
270
+ file: ".tool-versions"
271
+ };
272
+ const volta = /"volta"\s*:\s*\{[^}]*"node"\s*:\s*"([^"]+)"/.exec(probe.read(join(root, "package.json")) ?? "")?.[1];
273
+ return volta ? {
274
+ spec: volta,
275
+ file: "package.json"
276
+ } : null;
277
+ }
278
+ /**
279
+ * The `bin` directory of an installed Node that satisfies `root`'s pin, from nvm, fnm, Volta, asdf or mise, or
280
+ * nil when the project pins none or no installed version satisfies the pin. An `lts/...` pin resolves through
281
+ * nvm's aliases.
282
+ */
283
+ function projectNodeBin(root, env, probe = defaultToolchainProbe()) {
284
+ const pin = projectNodePin(root, probe);
285
+ if (!pin) return null;
286
+ const nvm = nvmDir(env, probe);
287
+ const spec = pin.spec.startsWith("lts/") ? nvmAlias(nvm, pin.spec, probe) : pin.spec;
288
+ const stores = [
289
+ {
290
+ dir: join(nvm, "versions", "node"),
291
+ bin: (name) => join(nvm, "versions", "node", name, "bin")
292
+ },
293
+ {
294
+ dir: join(probe.home, ".local", "share", "fnm", "node-versions"),
295
+ bin: (name) => join(probe.home, ".local", "share", "fnm", "node-versions", name, "installation", "bin")
296
+ },
297
+ {
298
+ dir: join(probe.home, "Library", "Application Support", "fnm", "node-versions"),
299
+ bin: (name) => join(probe.home, "Library", "Application Support", "fnm", "node-versions", name, "installation", "bin")
300
+ },
301
+ {
302
+ dir: join(probe.home, ".volta", "tools", "image", "node"),
303
+ bin: (name) => join(probe.home, ".volta", "tools", "image", "node", name, "bin")
304
+ },
305
+ {
306
+ dir: join(env.ASDF_DATA_DIR || join(probe.home, ".asdf"), "installs", "nodejs"),
307
+ bin: (name) => join(env.ASDF_DATA_DIR || join(probe.home, ".asdf"), "installs", "nodejs", name, "bin")
308
+ },
309
+ {
310
+ dir: join(probe.home, ".local", "share", "mise", "installs", "node"),
311
+ bin: (name) => join(probe.home, ".local", "share", "mise", "installs", "node", name, "bin")
312
+ }
313
+ ];
314
+ for (const store of stores) {
315
+ const name = newestMatching(probe.list(store.dir), spec);
316
+ if (name && probe.isFile(join(store.bin(name), "node"))) return {
317
+ bin: store.bin(name),
318
+ version: name.replace(/^v/, ""),
319
+ pin: pin.file,
320
+ spec
321
+ };
322
+ }
323
+ return null;
324
+ }
325
+ //#endregion
326
+ export { defaultNodeBin, defaultToolchainProbe, discoverToolchain, projectNodeBin, projectNodePin, satisfiesNodePin };
@@ -0,0 +1,16 @@
1
+ //#region workspace-link.d.ts
2
+ declare const WORKSPACE_LINK_ORIGIN = "https://stim.appandflow.com";
3
+ /** A navigation destination on one Stim home, resolved only through an already-paired server. */
4
+ interface SharedWorkspaceLink {
5
+ machine: string;
6
+ workspace: string;
7
+ platform?: "ios" | "android" | "macos" | "web";
8
+ slot?: string;
9
+ }
10
+ declare function validHomeId(value: unknown): value is string;
11
+ /** Parses a v1 HTTPS workspace link; unsupported origins, destinations and ambiguous query fields return null. */
12
+ declare function parseWorkspaceLink(value: string): SharedWorkspaceLink | null;
13
+ /** Builds a share URL containing only nonsecret destination identifiers. */
14
+ declare function sharedWorkspaceURL(link: SharedWorkspaceLink): string;
15
+ //#endregion
16
+ export { SharedWorkspaceLink, WORKSPACE_LINK_ORIGIN, parseWorkspaceLink, sharedWorkspaceURL, validHomeId };
@@ -0,0 +1,50 @@
1
+ //#region workspace-link.ts
2
+ const WORKSPACE_LINK_ORIGIN = "https://stim.appandflow.com";
3
+ function validHomeId(value) {
4
+ return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
5
+ }
6
+ /** Parses a v1 HTTPS workspace link; unsupported origins, destinations and ambiguous query fields return null. */
7
+ function parseWorkspaceLink(value) {
8
+ let url;
9
+ try {
10
+ url = new URL(value);
11
+ } catch {
12
+ return null;
13
+ }
14
+ if (url.origin !== "https://stim.appandflow.com" || url.username || url.password || !["/open", "/open/"].includes(url.pathname)) return null;
15
+ const params = url.searchParams;
16
+ for (const name of [
17
+ "v",
18
+ "machine",
19
+ "workspace",
20
+ "platform",
21
+ "slot"
22
+ ]) if (params.getAll(name).length > 1) return null;
23
+ const machine = params.get("machine");
24
+ const workspace = params.get("workspace");
25
+ if (params.get("v") !== "1" || !validHomeId(machine) || !workspace || !/^[0-9a-f]{16}$/.test(workspace)) return null;
26
+ const platform = params.get("platform");
27
+ const slot = params.get("slot");
28
+ const link = {
29
+ machine: machine.toLowerCase(),
30
+ workspace,
31
+ ...slot && slot !== "default" ? { slot } : {}
32
+ };
33
+ if (platform) {
34
+ if (platform !== "ios" && platform !== "android" && platform !== "macos" && platform !== "web") return null;
35
+ link.platform = platform;
36
+ }
37
+ return link;
38
+ }
39
+ /** Builds a share URL containing only nonsecret destination identifiers. */
40
+ function sharedWorkspaceURL(link) {
41
+ const url = new URL("/open", WORKSPACE_LINK_ORIGIN);
42
+ url.searchParams.set("v", "1");
43
+ url.searchParams.set("machine", link.machine);
44
+ url.searchParams.set("workspace", link.workspace);
45
+ if (link.platform) url.searchParams.set("platform", link.platform);
46
+ if (link.slot && link.slot !== "default") url.searchParams.set("slot", link.slot);
47
+ return url.href;
48
+ }
49
+ //#endregion
50
+ export { WORKSPACE_LINK_ORIGIN, parseWorkspaceLink, sharedWorkspaceURL, validHomeId };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stim-cli/core",
3
- "version": "1.20.1",
3
+ "version": "1.21.0",
4
4
  "description": "Shared internal cache and runtime primitives for Stim packages.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -22,6 +22,16 @@
22
22
  "module-sync": "./dist/index.mjs",
23
23
  "default": "./dist/index.mjs"
24
24
  },
25
+ "./noise": {
26
+ "types": "./dist/noise.d.mts",
27
+ "module-sync": "./dist/noise.mjs",
28
+ "default": "./dist/noise.mjs"
29
+ },
30
+ "./noise-session": {
31
+ "types": "./dist/noise-session.d.mts",
32
+ "module-sync": "./dist/noise-session.mjs",
33
+ "default": "./dist/noise-session.mjs"
34
+ },
25
35
  "./ownership-claim": {
26
36
  "types": "./dist/ownership-claim.d.mts",
27
37
  "module-sync": "./dist/ownership-claim.mjs",
@@ -56,6 +66,16 @@
56
66
  "types": "./dist/receive-protocol.d.mts",
57
67
  "module-sync": "./dist/receive-protocol.mjs",
58
68
  "default": "./dist/receive-protocol.mjs"
69
+ },
70
+ "./toolchain": {
71
+ "types": "./dist/toolchain.d.mts",
72
+ "module-sync": "./dist/toolchain.mjs",
73
+ "default": "./dist/toolchain.mjs"
74
+ },
75
+ "./workspace-link": {
76
+ "types": "./dist/workspace-link.d.mts",
77
+ "module-sync": "./dist/workspace-link.mjs",
78
+ "default": "./dist/workspace-link.mjs"
59
79
  }
60
80
  },
61
81
  "dependencies": {