@volter/editor-live 0.5.63 → 0.5.65
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/BUNDLED_NOTICES +4 -2
- package/dist/index.js +184 -17
- package/dist/index.js.map +4 -4
- package/package.json +3 -3
- package/src/session.ts +24 -4
package/BUNDLED_NOTICES
CHANGED
|
@@ -3,8 +3,9 @@ Bundled license notices
|
|
|
3
3
|
These notices accompany code incorporated into this package. Separate npm dependencies retain their own licenses.
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
@volter/editor-project@0.5.
|
|
6
|
+
@volter/editor-project@0.5.65
|
|
7
7
|
Declared license: Apache-2.0
|
|
8
|
+
Source: the unmodified npm package @volter/editor-project@0.5.65, from https://github.com/volter-ai/editor.git
|
|
8
9
|
|
|
9
10
|
--- packages/editor-project/LICENSE ---
|
|
10
11
|
|
|
@@ -212,8 +213,9 @@ Declared license: Apache-2.0
|
|
|
212
213
|
limitations under the License.
|
|
213
214
|
|
|
214
215
|
|
|
215
|
-
@volter/editor-sdk@0.5.
|
|
216
|
+
@volter/editor-sdk@0.5.65
|
|
216
217
|
Declared license: Apache-2.0
|
|
218
|
+
Source: the unmodified npm package @volter/editor-sdk@0.5.65, from https://github.com/volter-ai/editor.git
|
|
217
219
|
|
|
218
220
|
--- packages/editor-sdk/LICENSE ---
|
|
219
221
|
|
package/dist/index.js
CHANGED
|
@@ -168,7 +168,7 @@ var EditorClient = class {
|
|
|
168
168
|
commandDispatcher = void 0;
|
|
169
169
|
if (error?.name === "TimeoutError") {
|
|
170
170
|
throw new EditorCommandError(
|
|
171
|
-
`The editor at ${this.baseUrl} never answered "${type}" within ${Math.round(deadlineMs / 1e3)}s \u2014 past every server-side budget, so the server itself is not answering. Check the terminal
|
|
171
|
+
`The editor at ${this.baseUrl} never answered "${type}" within ${Math.round(deadlineMs / 1e3)}s \u2014 past every server-side budget, so the server itself is not answering. Check the terminal that started this editor session.`,
|
|
172
172
|
void 0,
|
|
173
173
|
true
|
|
174
174
|
);
|
|
@@ -180,7 +180,7 @@ var EditorClient = class {
|
|
|
180
180
|
continue;
|
|
181
181
|
}
|
|
182
182
|
throw new EditorCommandError(
|
|
183
|
-
`POST ${url} ("${type}") never reached the editor: ${code}${detail ? ` (${detail})` : ""}.` + (retried ? ` Retried once after ${TRANSPORT_RETRY_DELAY_MS}ms; it failed the same way.` : "") + (options?.retryTransport === true ? "" : " Not retried automatically: this command can change editor state, and a socket that died after the request was written cannot prove the editor did not already run it.") + " A transport failure means the port stopped answering, not that the editor refused \u2014 the dev server restarts on any watched source edit, and it shuts itself down after an idle window. Check the terminal
|
|
183
|
+
`POST ${url} ("${type}") never reached the editor: ${code}${detail ? ` (${detail})` : ""}.` + (retried ? ` Retried once after ${TRANSPORT_RETRY_DELAY_MS}ms; it failed the same way.` : "") + (options?.retryTransport === true ? "" : " Not retried automatically: this command can change editor state, and a socket that died after the request was written cannot prove the editor did not already run it.") + " A transport failure means the port stopped answering, not that the editor refused \u2014 the dev server restarts on any watched source edit, and it shuts itself down after an idle window. Check the terminal that started this editor session and confirm the port this client resolved.",
|
|
184
184
|
code,
|
|
185
185
|
false
|
|
186
186
|
);
|
|
@@ -1017,7 +1017,7 @@ var EditorClient = class {
|
|
|
1017
1017
|
if (error?.name === "TimeoutError") throw error;
|
|
1018
1018
|
const { code, detail } = describeFetchFailure(error);
|
|
1019
1019
|
throw new EditorCommandError(
|
|
1020
|
-
`${init?.method ?? "GET"} ${url} never reached the editor: ${code}${detail ? ` (${detail})` : ""}. Nothing answered on that port \u2014 check the terminal
|
|
1020
|
+
`${init?.method ?? "GET"} ${url} never reached the editor: ${code}${detail ? ` (${detail})` : ""}. Nothing answered on that port \u2014 check the terminal that started this editor session and confirm the port this client resolved.`,
|
|
1021
1021
|
code,
|
|
1022
1022
|
false
|
|
1023
1023
|
);
|
|
@@ -1033,7 +1033,7 @@ var EditorClient = class {
|
|
|
1033
1033
|
const contentType = res.headers.get("content-type") ?? "";
|
|
1034
1034
|
if (!contentType.includes("application/json")) {
|
|
1035
1035
|
throw new Error(
|
|
1036
|
-
`The editor at ${this.baseUrl} answered with its page fallback (${contentType || "no content-type"}) rather than JSON, so no editor server handled the request. Check that this URL is a running
|
|
1036
|
+
`The editor at ${this.baseUrl} answered with its page fallback (${contentType || "no content-type"}) rather than JSON, so no editor server handled the request. Check that this URL is a running editor session.`
|
|
1037
1037
|
);
|
|
1038
1038
|
}
|
|
1039
1039
|
const body = await res.json();
|
|
@@ -1786,8 +1786,8 @@ function lazyChainProxy(resolveRoot, path = []) {
|
|
|
1786
1786
|
}
|
|
1787
1787
|
|
|
1788
1788
|
// src/session.ts
|
|
1789
|
-
import { existsSync as
|
|
1790
|
-
import { dirname, join as
|
|
1789
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
|
|
1790
|
+
import { dirname as dirname2, join as join4, resolve as resolve2 } from "node:path";
|
|
1791
1791
|
|
|
1792
1792
|
// ../editor-project/src/manifest/locate.ts
|
|
1793
1793
|
import { existsSync } from "node:fs";
|
|
@@ -1812,18 +1812,172 @@ function resolveManifestPath(dir) {
|
|
|
1812
1812
|
return join(dir, MANIFEST_FILENAME);
|
|
1813
1813
|
}
|
|
1814
1814
|
|
|
1815
|
+
// ../editor-sdk/src/session/product-locator.ts
|
|
1816
|
+
import { existsSync as existsSync2, readFileSync } from "node:fs";
|
|
1817
|
+
import { createRequire } from "node:module";
|
|
1818
|
+
import { dirname, join as join2 } from "node:path";
|
|
1819
|
+
import { pathToFileURL } from "node:url";
|
|
1820
|
+
var PRODUCT_DECLARATION_KEY = "vgai.product";
|
|
1821
|
+
var PRODUCT_INSTALL_LINES = [" npm install --save-dev @volter/editor"];
|
|
1822
|
+
var PRODUCT_CREATE_LINES = [" npx @volter/editor create <name>"];
|
|
1823
|
+
var PRODUCT_COLOR_THEMES = ["dark", "light"];
|
|
1824
|
+
function readProductManifest(packageDir) {
|
|
1825
|
+
const manifestPath = join2(packageDir, "package.json");
|
|
1826
|
+
let manifest;
|
|
1827
|
+
try {
|
|
1828
|
+
manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
1829
|
+
} catch {
|
|
1830
|
+
return null;
|
|
1831
|
+
}
|
|
1832
|
+
const declared = manifest.vgai?.product;
|
|
1833
|
+
if (declared === void 0) return null;
|
|
1834
|
+
const entry = declared.entry;
|
|
1835
|
+
if (typeof entry !== "string" || !entry.startsWith("./"))
|
|
1836
|
+
throw new Error(
|
|
1837
|
+
`${manifestPath} declares ${PRODUCT_DECLARATION_KEY} but its "entry" is ${JSON.stringify(entry)}. It must be a ./-relative module \u2014 the product's ONE source entry, which is also its frame entry (e.g. "./src/index.ts").`
|
|
1838
|
+
);
|
|
1839
|
+
const resolved = join2(packageDir, entry);
|
|
1840
|
+
if (!existsSync2(resolved))
|
|
1841
|
+
throw new Error(
|
|
1842
|
+
`${manifestPath} declares ${PRODUCT_DECLARATION_KEY}.entry ${JSON.stringify(entry)}, which does not exist (${resolved}).`
|
|
1843
|
+
);
|
|
1844
|
+
const colorTheme = declared.colorTheme;
|
|
1845
|
+
if (typeof colorTheme !== "string" || !PRODUCT_COLOR_THEMES.includes(colorTheme))
|
|
1846
|
+
throw new Error(
|
|
1847
|
+
`${manifestPath} declares ${PRODUCT_DECLARATION_KEY} but its "colorTheme" is ${JSON.stringify(colorTheme)}. It must be ${PRODUCT_COLOR_THEMES.map(
|
|
1848
|
+
(one) => JSON.stringify(one)
|
|
1849
|
+
).join(" or ")} \u2014 which way round this product paints, read before any of its code runs so the workbench's first frame is already the product's own background.`
|
|
1850
|
+
);
|
|
1851
|
+
const bin = manifest.bin;
|
|
1852
|
+
const commands = bin !== null && typeof bin === "object" ? Object.keys(bin) : [];
|
|
1853
|
+
if (commands.length !== 1)
|
|
1854
|
+
throw new Error(
|
|
1855
|
+
`${manifestPath} declares ${PRODUCT_DECLARATION_KEY} but its "bin" names ${commands.length === 0 ? "no command" : `${commands.length} commands`}. A product has exactly one command \u2014 the one every session message tells a person to run.`
|
|
1856
|
+
);
|
|
1857
|
+
const displayName = declared.displayName;
|
|
1858
|
+
if (typeof displayName !== "string" || displayName.trim() === "")
|
|
1859
|
+
throw new Error(
|
|
1860
|
+
`${manifestPath} declares ${PRODUCT_DECLARATION_KEY} but its "displayName" is ${JSON.stringify(displayName)}. It must be the name a person sees (e.g. "Volter Editor").`
|
|
1861
|
+
);
|
|
1862
|
+
return {
|
|
1863
|
+
name: typeof manifest.name === "string" ? manifest.name : packageDir,
|
|
1864
|
+
version: typeof manifest.version === "string" ? manifest.version : "unknown",
|
|
1865
|
+
command: commands[0],
|
|
1866
|
+
displayName,
|
|
1867
|
+
dir: packageDir,
|
|
1868
|
+
entry,
|
|
1869
|
+
colorTheme,
|
|
1870
|
+
// A private source product may use an explicitly selected matching checkout.
|
|
1871
|
+
// Public packages must always carry a downloadable, checksummed workbench.
|
|
1872
|
+
workbench: manifest.private === true && declared.workbench === void 0 ? null : readProductWorkbench(manifestPath, declared.workbench)
|
|
1873
|
+
};
|
|
1874
|
+
}
|
|
1875
|
+
var SHA256_HEX = /^[0-9a-f]{64}$/;
|
|
1876
|
+
function readProductWorkbench(manifestPath, declared) {
|
|
1877
|
+
const example = ' "vgai": { "product": { "workbench": { "release": "<product>-<fork sha 12>-<platform>", "tarballSha256": "<64 hex>" } } }\n`node scripts/workbench/build-release.mjs --publish --out <release dir>` cuts the release and prints exactly that block.';
|
|
1878
|
+
if (typeof declared !== "object" || declared === null || Array.isArray(declared))
|
|
1879
|
+
throw new Error(
|
|
1880
|
+
`${manifestPath} declares ${PRODUCT_DECLARATION_KEY} but its "workbench" is ${JSON.stringify(declared)}. A product IS the Code-OSS workbench it was compiled into, so it names the published release those bytes are:
|
|
1881
|
+
${example}`
|
|
1882
|
+
);
|
|
1883
|
+
const record = declared;
|
|
1884
|
+
const extra = Object.keys(record).filter((key) => key !== "release" && key !== "tarballSha256");
|
|
1885
|
+
if (extra.length > 0)
|
|
1886
|
+
throw new Error(
|
|
1887
|
+
`${manifestPath}'s ${PRODUCT_DECLARATION_KEY}.workbench carries ${extra.map((key) => `"${key}"`).join(", ")}, which this declaration has no meaning for. It names the release and the pin, and nothing else:
|
|
1888
|
+
${example}`
|
|
1889
|
+
);
|
|
1890
|
+
const release = record["release"];
|
|
1891
|
+
if (typeof release !== "string" || release.trim() === "")
|
|
1892
|
+
throw new Error(
|
|
1893
|
+
`${manifestPath}'s ${PRODUCT_DECLARATION_KEY}.workbench names no "release" \u2014 the GitHub Release tag its workbench was published under:
|
|
1894
|
+
${example}`
|
|
1895
|
+
);
|
|
1896
|
+
const tarballSha256 = record["tarballSha256"];
|
|
1897
|
+
if (typeof tarballSha256 !== "string" || !SHA256_HEX.test(tarballSha256))
|
|
1898
|
+
throw new Error(
|
|
1899
|
+
`${manifestPath}'s ${PRODUCT_DECLARATION_KEY}.workbench has tarballSha256 ${JSON.stringify(tarballSha256)}, which is not 64 hex characters. It is the PIN \u2014 the hash the downloaded tarball is checked against before a byte of it is extracted, so it is never approximate:
|
|
1900
|
+
${example}`
|
|
1901
|
+
);
|
|
1902
|
+
return { release, tarballSha256 };
|
|
1903
|
+
}
|
|
1904
|
+
function declaredVgaiPackages(projectRoot) {
|
|
1905
|
+
let manifest;
|
|
1906
|
+
try {
|
|
1907
|
+
manifest = JSON.parse(
|
|
1908
|
+
readFileSync(join2(projectRoot, "package.json"), "utf8")
|
|
1909
|
+
);
|
|
1910
|
+
} catch {
|
|
1911
|
+
return [];
|
|
1912
|
+
}
|
|
1913
|
+
return Object.keys({ ...manifest.dependencies ?? {}, ...manifest.devDependencies ?? {} }).filter((name) => name.startsWith("@volter/")).sort();
|
|
1914
|
+
}
|
|
1915
|
+
function locatePackage(projectRoot, name) {
|
|
1916
|
+
const require2 = createRequire(pathToFileURL(join2(projectRoot, "package.json")));
|
|
1917
|
+
try {
|
|
1918
|
+
return dirname(require2.resolve(`${name}/package.json`));
|
|
1919
|
+
} catch {
|
|
1920
|
+
try {
|
|
1921
|
+
let dir = dirname(require2.resolve(name));
|
|
1922
|
+
for (; ; ) {
|
|
1923
|
+
const candidate = join2(dir, "package.json");
|
|
1924
|
+
try {
|
|
1925
|
+
if (JSON.parse(readFileSync(candidate, "utf8")).name === name)
|
|
1926
|
+
return dir;
|
|
1927
|
+
} catch {
|
|
1928
|
+
}
|
|
1929
|
+
const parent = dirname(dir);
|
|
1930
|
+
if (parent === dir) return null;
|
|
1931
|
+
dir = parent;
|
|
1932
|
+
}
|
|
1933
|
+
} catch {
|
|
1934
|
+
return null;
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
function declaredProducts(projectRoot) {
|
|
1939
|
+
const found = [];
|
|
1940
|
+
for (const name of declaredVgaiPackages(projectRoot)) {
|
|
1941
|
+
const dir = locatePackage(projectRoot, name);
|
|
1942
|
+
if (dir === null) continue;
|
|
1943
|
+
const product = readProductManifest(dir);
|
|
1944
|
+
if (product !== null) found.push(product);
|
|
1945
|
+
}
|
|
1946
|
+
return found;
|
|
1947
|
+
}
|
|
1948
|
+
function resolveProductForProject(projectRoot) {
|
|
1949
|
+
const products = declaredProducts(projectRoot);
|
|
1950
|
+
if (products.length === 1) return products[0];
|
|
1951
|
+
if (products.length === 0)
|
|
1952
|
+
throw new Error(
|
|
1953
|
+
`${projectRoot} declares no editor product, so there is no editor to open it in.
|
|
1954
|
+
The installed product supplies the editor,
|
|
1955
|
+
and which one runs is what the project installed, never a flag.
|
|
1956
|
+
For THIS folder, install the one you are building with:
|
|
1957
|
+
${PRODUCT_INSTALL_LINES.join("\n")}
|
|
1958
|
+
then run that product's \`edit\` command again. For a NEW project, the product creates it:
|
|
1959
|
+
${PRODUCT_CREATE_LINES.join("\n")}`
|
|
1960
|
+
);
|
|
1961
|
+
throw new Error(
|
|
1962
|
+
`${projectRoot} declares ${products.length} editor products \u2014 ${products.map((product) => product.name).join(" and ")}.
|
|
1963
|
+
One page runs one product, and nothing here may choose between them. Remove the one you
|
|
1964
|
+
are not building with:
|
|
1965
|
+
` + products.map((product) => ` npm uninstall ${product.name}`).join("\n")
|
|
1966
|
+
);
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1815
1969
|
// ../editor-sdk/src/session/registry-format.ts
|
|
1816
1970
|
import {
|
|
1817
1971
|
mkdirSync,
|
|
1818
1972
|
readdirSync,
|
|
1819
|
-
readFileSync,
|
|
1973
|
+
readFileSync as readFileSync2,
|
|
1820
1974
|
realpathSync,
|
|
1821
1975
|
unlinkSync,
|
|
1822
1976
|
writeFileSync
|
|
1823
1977
|
} from "node:fs";
|
|
1824
1978
|
import { homedir } from "node:os";
|
|
1825
|
-
import { join as
|
|
1826
|
-
var EDITOR_SESSIONS_REGISTRY_FILE =
|
|
1979
|
+
import { join as join3, resolve } from "node:path";
|
|
1980
|
+
var EDITOR_SESSIONS_REGISTRY_FILE = join3(homedir(), ".vgai", "editor-sessions.json");
|
|
1827
1981
|
function isEditorSessionEntry(v) {
|
|
1828
1982
|
if (typeof v !== "object" || v === null) return false;
|
|
1829
1983
|
const s = v;
|
|
@@ -1854,7 +2008,7 @@ function pidAlive(pid) {
|
|
|
1854
2008
|
}
|
|
1855
2009
|
function readLiveRegisteredSessions() {
|
|
1856
2010
|
try {
|
|
1857
|
-
const raw = JSON.parse(
|
|
2011
|
+
const raw = JSON.parse(readFileSync2(EDITOR_SESSIONS_REGISTRY_FILE, "utf8"));
|
|
1858
2012
|
return Array.isArray(raw) ? raw.filter(isEditorSessionEntry).map(normalizeEditorSessionEntry).filter((s) => pidAlive(s.pid)) : [];
|
|
1859
2013
|
} catch {
|
|
1860
2014
|
return [];
|
|
@@ -1933,8 +2087,8 @@ var HttpSessionDiscovery = class {
|
|
|
1933
2087
|
function findProjectRootFrom(dir) {
|
|
1934
2088
|
let cur = resolve2(dir);
|
|
1935
2089
|
for (; ; ) {
|
|
1936
|
-
if (
|
|
1937
|
-
const parent =
|
|
2090
|
+
if (existsSync3(resolveManifestPath(cur))) return cur;
|
|
2091
|
+
const parent = dirname2(cur);
|
|
1938
2092
|
if (parent === cur) return null;
|
|
1939
2093
|
cur = parent;
|
|
1940
2094
|
}
|
|
@@ -1949,7 +2103,7 @@ function canonicalPath(path) {
|
|
|
1949
2103
|
function readProjectSession(projectRoot) {
|
|
1950
2104
|
try {
|
|
1951
2105
|
const value = JSON.parse(
|
|
1952
|
-
|
|
2106
|
+
readFileSync3(join4(projectRoot, ".vgai", "session.json"), "utf8")
|
|
1953
2107
|
);
|
|
1954
2108
|
if (typeof value !== "object" || value === null) return null;
|
|
1955
2109
|
const hint = value;
|
|
@@ -1981,12 +2135,12 @@ ${manifestError}`
|
|
|
1981
2135
|
function noMatchingSessionRefusal(projectRoot, canon, sessions, discoveryFailure) {
|
|
1982
2136
|
if (discoveryFailure !== null) {
|
|
1983
2137
|
return new Error(
|
|
1984
|
-
`@volter/editor-live: could not READ the editor session registry while looking for ${projectRoot} \u2014 ${discoveryFailure}. This is not the answer "no editor is running": the question went unanswered, so nothing is known about what is live. Retry (a probe can time out while the box is loaded); if it keeps failing,
|
|
2138
|
+
`@volter/editor-live: could not READ the editor session registry while looking for ${projectRoot} \u2014 ${discoveryFailure}. This is not the answer "no editor is running": the question went unanswered, so nothing is known about what is live. Retry (a probe can time out while the box is loaded); if it keeps failing, ${sessionsCommandFor(projectRoot)} asks the same question directly.`
|
|
1985
2139
|
);
|
|
1986
2140
|
}
|
|
1987
2141
|
if (sessions.length === 0) {
|
|
1988
2142
|
return new Error(
|
|
1989
|
-
`@volter/editor-live: the editor session registry is readable and lists NO live sessions, so none covers ${projectRoot}. @volter/editor-live only attaches to an already-running session \u2014 it never starts one \u2014 so run
|
|
2143
|
+
`@volter/editor-live: the editor session registry is readable and lists NO live sessions, so none covers ${projectRoot}. @volter/editor-live only attaches to an already-running session \u2014 it never starts one \u2014 so run ${editCommandFor(projectRoot)} in that project first, then retry.`
|
|
1990
2144
|
);
|
|
1991
2145
|
}
|
|
1992
2146
|
const listed = sessions.map((s) => ` port ${s.port} \u2192 ${s.project === null ? "(no project)" : s.project}`).join("\n");
|
|
@@ -1995,7 +2149,7 @@ function noMatchingSessionRefusal(projectRoot, canon, sessions, discoveryFailure
|
|
|
1995
2149
|
looking for (resolved): ${canon}
|
|
1996
2150
|
live sessions:
|
|
1997
2151
|
${listed}
|
|
1998
|
-
If one of those is meant to be this project, the two paths differ after resolution \u2014 the usual cause is a git worktree or a symlink, where the session was opened through a different path to the same files. Run
|
|
2152
|
+
If one of those is meant to be this project, the two paths differ after resolution \u2014 the usual cause is a git worktree or a symlink, where the session was opened through a different path to the same files. Run ${editCommandFor(projectRoot)} from THIS path, or use the path the session lists.`
|
|
1999
2153
|
);
|
|
2000
2154
|
}
|
|
2001
2155
|
async function resolveSession(projectDir = process.cwd(), deps = {}) {
|
|
@@ -2004,7 +2158,7 @@ async function resolveSession(projectDir = process.cwd(), deps = {}) {
|
|
|
2004
2158
|
const projectRoot = findRoot(projectDir);
|
|
2005
2159
|
if (projectRoot === null) {
|
|
2006
2160
|
throw new Error(
|
|
2007
|
-
`@volter/editor-live: no vgai.project.json found in ${projectDir} or any parent directory \u2014 is this a
|
|
2161
|
+
`@volter/editor-live: no vgai.project.json found in ${projectDir} or any parent directory \u2014 is this a project?`
|
|
2008
2162
|
);
|
|
2009
2163
|
}
|
|
2010
2164
|
const localHint = (deps.readProjectSession ?? readProjectSession)(projectRoot);
|
|
@@ -2039,6 +2193,19 @@ async function resolveSession(projectDir = process.cwd(), deps = {}) {
|
|
|
2039
2193
|
if (match.manifestError != null) throw manifestRefusal(projectRoot, match.manifestError);
|
|
2040
2194
|
return { port: match.port, projectRoot };
|
|
2041
2195
|
}
|
|
2196
|
+
function editCommandFor(projectRoot) {
|
|
2197
|
+
return verbFor(projectRoot, "edit");
|
|
2198
|
+
}
|
|
2199
|
+
function sessionsCommandFor(projectRoot) {
|
|
2200
|
+
return verbFor(projectRoot, "sessions");
|
|
2201
|
+
}
|
|
2202
|
+
function verbFor(projectRoot, verb) {
|
|
2203
|
+
try {
|
|
2204
|
+
return `\`${resolveProductForProject(projectRoot).command} ${verb}\``;
|
|
2205
|
+
} catch {
|
|
2206
|
+
return `its product's \`${verb}\` command`;
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2042
2209
|
|
|
2043
2210
|
// src/singleton.ts
|
|
2044
2211
|
function createLazySession(factory) {
|