@slidev/cli 52.15.1 → 52.16.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.
@@ -1,5 +1,4 @@
1
- import "./resolver-XedCEhxI.mjs";
2
- import { t as resolveViteConfigs } from "./shared-ClfjhvJo.mjs";
1
+ import { n as resolveViteConfigs } from "./serve-bnzg299W.mjs";
3
2
  import { join, resolve } from "node:path";
4
3
  import { build as build$1 } from "vite";
5
4
  import { existsSync } from "node:fs";
@@ -7,7 +6,6 @@ import fs$1 from "node:fs/promises";
7
6
  import http from "node:http";
8
7
  import connect from "connect";
9
8
  import sirv from "sirv";
10
-
11
9
  //#region node/commands/build.ts
12
10
  async function build(options, viteConfig = {}, args) {
13
11
  const indexHtmlId = resolve(options.userRoot, "index.html");
@@ -53,7 +51,7 @@ async function build(options, viteConfig = {}, args) {
53
51
  dev: true
54
52
  }));
55
53
  server.listen(port);
56
- const { exportSlides } = await import("./export-DA_9kPXd.mjs");
54
+ const { exportSlides } = await import("./export-BHxAO7pf.mjs");
57
55
  const tempDir = resolve(outDir, "temp");
58
56
  await fs$1.mkdir(tempDir, { recursive: true });
59
57
  await exportSlides({
@@ -94,7 +92,7 @@ async function build(options, viteConfig = {}, args) {
94
92
  "true",
95
93
  "auto"
96
94
  ].includes(options.data.config.download)) {
97
- const { exportSlides, getExportOptions } = await import("./export-DA_9kPXd.mjs");
95
+ const { exportSlides, getExportOptions } = await import("./export-BHxAO7pf.mjs");
98
96
  const port = 12445;
99
97
  const app = connect();
100
98
  const server = http.createServer(app);
@@ -113,6 +111,5 @@ async function build(options, viteConfig = {}, args) {
113
111
  server.close();
114
112
  }
115
113
  }
116
-
117
114
  //#endregion
118
- export { build };
115
+ export { build };
package/dist/cli.mjs CHANGED
@@ -1,10 +1,8 @@
1
- import { i as resolveEntry, n as getRoots, r as isInstalledGlobally } from "./resolver-XedCEhxI.mjs";
2
- import { a as parser, c as resolveAddons, d as updateFrontmatterPatch, i as resolveOptions, l as version, o as getThemeMeta, s as resolveTheme, u as loadSetups } from "./shared-ClfjhvJo.mjs";
3
- import { t as createServer } from "./serve-CYSWeFFN.mjs";
1
+ import { i as resolveEntry, r as isInstalledGlobally } from "./resolver-CZx9LFrt.mjs";
2
+ import { a as resolveOptions, c as resolveTheme, d as updateFrontmatterPatch, l as version, o as parser, s as getThemeMeta, t as createServer, u as loadSetups } from "./serve-bnzg299W.mjs";
4
3
  import path from "node:path";
5
4
  import process from "node:process";
6
5
  import fs from "node:fs/promises";
7
- import { uniq } from "@antfu/utils";
8
6
  import { blue, bold, cyan, cyanBright, dim, gray, green, underline, yellow } from "ansis";
9
7
  import { injectPreparserExtensionLoader } from "@slidev/parser/fs";
10
8
  import equal from "fast-deep-equal";
@@ -15,20 +13,16 @@ import { verifyConfig } from "@slidev/parser";
15
13
  import { getPort } from "get-port-please";
16
14
  import openBrowser from "open";
17
15
  import yargs from "yargs";
18
-
19
16
  //#region node/setups/preparser.ts
20
17
  function setupPreparser() {
21
- injectPreparserExtensionLoader(async (headmatter, filepath, mode) => {
22
- const addons = Array.isArray(headmatter?.addons) ? headmatter.addons : [];
23
- const { userRoot } = await getRoots();
24
- return (await loadSetups(uniq([...await resolveAddons(addons), userRoot]), "preparser.ts", [{
18
+ injectPreparserExtensionLoader(async (roots, headmatter, filepath, mode) => {
19
+ return (await loadSetups(roots, "preparser.ts", [{
25
20
  filepath,
26
21
  headmatter,
27
22
  mode
28
23
  }])).flat();
29
24
  });
30
25
  }
31
-
32
26
  //#endregion
33
27
  //#region node/cli.ts
34
28
  const RE_NODE_MODULES_OR_GIT = /node_modules|\.git/;
@@ -131,7 +125,6 @@ cli.command("* [entry]", "Start a local server for Slidev", (args) => commonOpti
131
125
  server: {
132
126
  port,
133
127
  strictPort: true,
134
- open,
135
128
  host,
136
129
  force
137
130
  },
@@ -139,8 +132,8 @@ cli.command("* [entry]", "Start a local server for Slidev", (args) => commonOpti
139
132
  logLevel: log,
140
133
  base
141
134
  }, { async loadData(loadedSource) {
142
- const { data: oldData, entry: entry$1 } = options;
143
- const loaded = await parser.load(options.userRoot, entry$1, loadedSource, "dev");
135
+ const { data: oldData, entry } = options;
136
+ const loaded = await parser.load(options, entry, loadedSource, "dev");
144
137
  const themeRaw = theme || loaded.headmatter.theme || "default";
145
138
  if (options.themeRaw !== themeRaw) {
146
139
  console.log(yellow("\n restarting on theme change\n"));
@@ -151,7 +144,7 @@ cli.command("* [entry]", "Start a local server for Slidev", (args) => commonOpti
151
144
  const newData = {
152
145
  ...loaded,
153
146
  themeMeta,
154
- config: parser.resolveConfig(loaded.headmatter, themeMeta, entry$1)
147
+ config: parser.resolveConfig(loaded.headmatter, themeMeta, entry)
155
148
  };
156
149
  if (CONFIG_RESTART_FIELDS.some((i) => !equal(newData.config[i], oldData.config[i]))) {
157
150
  console.log(yellow("\n restarting on config change\n"));
@@ -172,12 +165,21 @@ cli.command("* [entry]", "Start a local server for Slidev", (args) => commonOpti
172
165
  let publicIp;
173
166
  if (remote) publicIp = await import("public-ip").then((r) => r.publicIpv4());
174
167
  lastRemoteUrl = printInfo(options, port, base, remote, tunnelUrl, publicIp);
168
+ if (open) await openSlidevInBrowser();
175
169
  return options;
176
170
  }
177
- async function openTunnel(port$1) {
171
+ async function openSlidevInBrowser() {
172
+ const url = `http://localhost:${port}${base}`;
173
+ try {
174
+ await openBrowser(url);
175
+ } catch {
176
+ console.log(yellow(`\n Could not open the browser automatically. Please open ${url} in your browser.\n`));
177
+ }
178
+ }
179
+ async function openTunnel(port) {
178
180
  const { startTunnel } = await import("untun");
179
181
  return await (await startTunnel({
180
- port: port$1,
182
+ port,
181
183
  acceptCloudflareNotice: true
182
184
  }))?.getURL() ?? "";
183
185
  }
@@ -193,7 +195,7 @@ cli.command("* [entry]", "Start a local server for Slidev", (args) => commonOpti
193
195
  name: "o",
194
196
  fullname: "open",
195
197
  action() {
196
- openBrowser(`http://localhost:${port}${base}`);
198
+ openSlidevInBrowser();
197
199
  }
198
200
  },
199
201
  {
@@ -223,7 +225,7 @@ cli.command("* [entry]", "Start a local server for Slidev", (args) => commonOpti
223
225
  const code = r.renderUnicodeCompact(lastRemoteUrl);
224
226
  console.log(`\n${dim(" QR Code for remote control: ")}\n ${blue(lastRemoteUrl)}\n`);
225
227
  console.log(code.split("\n").map((i) => ` ${i}`).join("\n"));
226
- const publicIp = await import("public-ip").then((r$1) => r$1.publicIpv4());
228
+ const publicIp = await import("public-ip").then((r) => r.publicIpv4());
227
229
  if (publicIp) console.log(`\n${dim(" Public IP: ")} ${blue(publicIp)}\n`);
228
230
  });
229
231
  }
@@ -294,7 +296,7 @@ cli.command("build [entry..]", "Build hostable SPA", (args) => exportOptions(com
294
296
  describe: "enable the inspect plugin for debugging"
295
297
  }).strict().help(), async (args) => {
296
298
  const { entry, theme, base, download, out, inspect, "without-notes": withoutNotes, "router-mode": routerMode } = args;
297
- const { build } = await import("./build-yk6Ejluk.mjs");
299
+ const { build } = await import("./build-BtXN8hyb.mjs");
298
300
  for (const entryFile of entry) {
299
301
  const options = await resolveOptions({
300
302
  entry: entryFile,
@@ -328,8 +330,8 @@ cli.command("theme [subcommand]", "Theme related operations", (command) => {
328
330
  default: "theme"
329
331
  }), async ({ entry: entryRaw, dir, theme: themeInput }) => {
330
332
  const entry = await resolveEntry(entryRaw);
331
- const roots = await getRoots(entry);
332
- const data = await parser.load(roots.userRoot, entry);
333
+ const options = await resolveOptions({ entry }, "dev");
334
+ const data = await parser.load(options, entry);
333
335
  let themeRaw = themeInput || data.headmatter.theme;
334
336
  themeRaw = themeRaw === null ? "none" : themeRaw || "default";
335
337
  if (themeRaw === "none") {
@@ -359,7 +361,7 @@ cli.command("theme [subcommand]", "Theme related operations", (command) => {
359
361
  });
360
362
  cli.command("export [entry..]", "Export slides to PDF", (args) => exportOptions(commonOptions(args)).strict().help(), async (args) => {
361
363
  const { entry, theme } = args;
362
- const { exportSlides, getExportOptions } = await import("./export-DA_9kPXd.mjs");
364
+ const { exportSlides, getExportOptions } = await import("./export-BHxAO7pf.mjs");
363
365
  const candidatePort = await getPort(12445);
364
366
  let warned = false;
365
367
  for (const entryFile of entry) {
@@ -411,7 +413,7 @@ cli.command("export-notes [entry..]", "Export slide notes to PDF", (args) => arg
411
413
  type: "number",
412
414
  describe: "wait for the specified ms before exporting"
413
415
  }).strict().help(), async ({ entry, output, timeout, wait }) => {
414
- const { exportNotes } = await import("./export-DA_9kPXd.mjs");
416
+ const { exportNotes } = await import("./export-BHxAO7pf.mjs");
415
417
  const candidatePort = await getPort(12445);
416
418
  for (const entryFile of entry) {
417
419
  const options = await resolveOptions({ entry: entryFile }, "export");
@@ -558,6 +560,5 @@ function printInfo(options, port, base, remote, tunnelUrl, publicIp) {
558
560
  return lastRemoteUrl;
559
561
  }
560
562
  }
561
-
562
563
  //#endregion
563
- export { };
564
+ export {};
@@ -1,4 +1,4 @@
1
- import { n as getRoots } from "./resolver-XedCEhxI.mjs";
1
+ import { n as getRoots } from "./resolver-CZx9LFrt.mjs";
2
2
  import path, { dirname, relative } from "node:path";
3
3
  import process from "node:process";
4
4
  import fs from "node:fs/promises";
@@ -11,7 +11,6 @@ import { parseRangeString } from "@slidev/parser/core";
11
11
  import { Presets, SingleBar } from "cli-progress";
12
12
  import * as pdfLib from "pdf-lib";
13
13
  import { PDFDocument } from "pdf-lib";
14
-
15
14
  //#region node/commands/export.ts
16
15
  const RE_CLICKS_PARAM = /clicks=([1-9]\d*)/;
17
16
  function addToTree(tree, info, slideIndexes, level = 1) {
@@ -28,8 +27,8 @@ function addToTree(tree, info, slideIndexes, level = 1) {
28
27
  });
29
28
  }
30
29
  function makeOutline(tree) {
31
- return tree.map(({ title, path: path$1, level, children }) => {
32
- const rootOutline = title ? `${path$1}|${"-".repeat(level - 1)}|${title}` : null;
30
+ return tree.map(({ title, path, level, children }) => {
31
+ const rootOutline = title ? `${path}|${"-".repeat(level - 1)}|${title}` : null;
33
32
  const childrenOutline = makeOutline(children);
34
33
  return childrenOutline.length > 0 ? `${rootOutline}\n${childrenOutline}` : rootOutline;
35
34
  }).filter((outline) => !!outline).join("\n");
@@ -101,8 +100,8 @@ async function exportNotes({ port = 18724, base = "/", output = "notes", timeout
101
100
  browser.close();
102
101
  return output;
103
102
  }
104
- async function exportSlides({ port = 18724, total = 0, range: range$1, format = "pdf", output = "slides", slides, base = "/", timeout = 3e4, wait = 0, dark = false, routerMode = "history", width = 1920, height = 1080, withClicks = false, executablePath = void 0, withToc = false, perSlide = false, scale = 1, waitUntil, omitBackground = false }) {
105
- const pages = parseRangeString(total, range$1);
103
+ async function exportSlides({ port = 18724, total = 0, range, format = "pdf", output = "slides", slides, base = "/", timeout = 3e4, wait = 0, dark = false, routerMode = "history", width = 1920, height = 1080, withClicks = false, executablePath = void 0, withToc = false, perSlide = false, scale = 1, waitUntil, omitBackground = false }) {
104
+ const pages = parseRangeString(total, range);
106
105
  const { chromium } = await importPlaywright();
107
106
  const browser = await chromium.launch({ executablePath });
108
107
  const page = await (await browser.newContext({
@@ -127,7 +126,7 @@ async function exportSlides({ port = 18724, total = 0, range: range$1, format =
127
126
  const query = new URLSearchParams();
128
127
  if (withClicks) query.set("print", "clicks");
129
128
  else query.set("print", "true");
130
- if (range$1) query.set("range", range$1);
129
+ if (range) query.set("range", range);
131
130
  if (clicks) query.set("clicks", clicks);
132
131
  const url = routerMode === "hash" ? `http://localhost:${port}${base}?${query}#${no}` : `http://localhost:${port}${base}${no}?${query}`;
133
132
  await page.goto(url, {
@@ -182,15 +181,15 @@ async function exportSlides({ port = 18724, total = 0, range: range$1, format =
182
181
  }
183
182
  async function getSlidesIndex() {
184
183
  const clicksBySlide = {};
185
- const slides$1 = page.locator(".print-slide-container");
186
- const count = await slides$1.count();
184
+ const slides = page.locator(".print-slide-container");
185
+ const count = await slides.count();
187
186
  for (let i = 0; i < count; i++) {
188
- const id = await slides$1.nth(i).getAttribute("id") || "";
189
- const path$1 = Number(id.split("-")[0]);
190
- clicksBySlide[path$1] = (clicksBySlide[path$1] || 0) + 1;
187
+ const id = await slides.nth(i).getAttribute("id") || "";
188
+ const path = Number(id.split("-")[0]);
189
+ clicksBySlide[path] = (clicksBySlide[path] || 0) + 1;
191
190
  }
192
- return Object.fromEntries(Object.entries(clicksBySlide).reduce((acc, [path$1, clicks], i) => {
193
- acc.push([path$1, clicks + (acc[i - 1]?.[1] ?? 0)]);
191
+ return Object.fromEntries(Object.entries(clicksBySlide).reduce((acc, [path, clicks], i) => {
192
+ acc.push([path, clicks + (acc[i - 1]?.[1] ?? 0)]);
194
193
  return acc;
195
194
  }, []));
196
195
  }
@@ -232,8 +231,8 @@ async function exportSlides({ port = 18724, total = 0, range: range$1, format =
232
231
  let mergedPdf = await PDFDocument.create({});
233
232
  for (const pdfBytes of buffers) {
234
233
  const pdf = await PDFDocument.load(pdfBytes);
235
- (await mergedPdf.copyPages(pdf, pdf.getPageIndices())).forEach((page$1) => {
236
- mergedPdf.addPage(page$1);
234
+ (await mergedPdf.copyPages(pdf, pdf.getPageIndices())).forEach((page) => {
235
+ mergedPdf.addPage(page);
237
236
  });
238
237
  }
239
238
  addPdfMetadata(mergedPdf);
@@ -304,9 +303,9 @@ async function exportSlides({ port = 18724, total = 0, range: range$1, format =
304
303
  if (!output.endsWith(".pdf")) output = `${output}.pdf`;
305
304
  return perSlide ? genPagePdfPerSlide() : genPagePdfOnePiece();
306
305
  }
307
- async function genPagePng(writeToDisk) {
306
+ async function genPagePng(writeToDisk, cleanOutput = true) {
308
307
  if (writeToDisk) {
309
- await fs.rm(writeToDisk, {
308
+ if (cleanOutput) await fs.rm(writeToDisk, {
310
309
  force: true,
311
310
  recursive: true
312
311
  });
@@ -315,7 +314,7 @@ async function exportSlides({ port = 18724, total = 0, range: range$1, format =
315
314
  return perSlide ? genPagePngPerSlide(writeToDisk) : genPagePngOnePiece(writeToDisk);
316
315
  }
317
316
  async function genPageMd() {
318
- const pngs = await genPagePng(dirname(output));
317
+ const pngs = await genPagePng(dirname(output), false);
319
318
  const content = slides.filter(({ index }) => pages.includes(index + 1)).map(({ title, index, note }) => pngs.filter(({ slideIndex }) => slideIndex === index).map(({ filename }) => `![${title || index + 1}](./${filename})\n\n`).join("") + (note ? `${note.trim()}\n\n` : "")).join("---\n\n");
320
319
  await fs.writeFile(ensureSuffix(".md", output), content);
321
320
  }
@@ -334,9 +333,9 @@ async function exportSlides({ port = 18724, total = 0, range: range$1, format =
334
333
  pptx.company = "Created using Slidev";
335
334
  if (titleSlide?.title) pptx.title = titleSlide?.title;
336
335
  if (titleSlide?.frontmatter?.info) pptx.subject = titleSlide?.frontmatter?.info;
337
- pngs.forEach(({ slideIndex, buffer: buffer$1 }) => {
336
+ pngs.forEach(({ slideIndex, buffer }) => {
338
337
  const slide = pptx.addSlide();
339
- slide.background = { data: `data:image/png;base64,${buffer$1.toString("base64")}` };
338
+ slide.background = { data: `data:image/png;base64,${buffer.toString("base64")}` };
340
339
  const note = slides[slideIndex].note;
341
340
  if (note) slide.addNotes(note);
342
341
  });
@@ -365,7 +364,7 @@ async function exportSlides({ port = 18724, total = 0, range: range$1, format =
365
364
  }
366
365
  }
367
366
  function getExportOptions(args, options, outFilename) {
368
- const { entry, output, format, timeout, wait, waitUntil, range: range$1, dark, withClicks, executablePath, withToc, perSlide, scale, omitBackground } = {
367
+ const { entry, output, format, timeout, wait, waitUntil, range, dark, withClicks, executablePath, withToc, perSlide, scale, omitBackground } = {
369
368
  ...options.data.config.export,
370
369
  ...args,
371
370
  ...clearUndefined({
@@ -382,7 +381,7 @@ function getExportOptions(args, options, outFilename) {
382
381
  output: outFilename,
383
382
  slides: options.data.slides,
384
383
  total: options.data.slides.length,
385
- range: range$1,
384
+ range,
386
385
  format: format || "pdf",
387
386
  timeout: timeout ?? 3e4,
388
387
  wait: wait ?? 0,
@@ -417,6 +416,5 @@ async function importPlaywright() {
417
416
  } catch {}
418
417
  throw new Error("The exporting for Slidev is powered by Playwright, please install it via `npm i -D playwright-chromium`");
419
418
  }
420
-
421
419
  //#endregion
422
- export { exportNotes, exportSlides, getExportOptions };
420
+ export { exportNotes, exportSlides, getExportOptions };
package/dist/index.d.mts CHANGED
@@ -1,10 +1,9 @@
1
- import * as vite0 from "vite";
2
1
  import { InlineConfig, PluginOption } from "vite";
3
2
  import * as parser from "@slidev/parser/fs";
4
3
  import { ResolvedSlidevOptions, ResolvedSlidevUtils, SlidevEntryOptions, SlidevPluginOptions, SlidevServerOptions } from "@slidev/types";
5
4
 
6
5
  //#region node/commands/serve.d.ts
7
- declare function createServer(options: ResolvedSlidevOptions, viteConfig?: InlineConfig, serverOptions?: SlidevServerOptions): Promise<vite0.ViteDevServer>;
6
+ declare function createServer(options: ResolvedSlidevOptions, viteConfig?: InlineConfig, serverOptions?: SlidevServerOptions): Promise<import("vite").ViteDevServer>;
8
7
  //#endregion
9
8
  //#region node/options.d.ts
10
9
  declare function resolveOptions(entryOptions: SlidevEntryOptions, mode: ResolvedSlidevOptions['mode']): Promise<ResolvedSlidevOptions>;
package/dist/index.mjs CHANGED
@@ -1,5 +1,2 @@
1
- import "./resolver-XedCEhxI.mjs";
2
- import { a as parser, i as resolveOptions, n as ViteSlidevPlugin, r as createDataUtils } from "./shared-ClfjhvJo.mjs";
3
- import { t as createServer } from "./serve-CYSWeFFN.mjs";
4
-
5
- export { ViteSlidevPlugin, createDataUtils, createServer, parser, resolveOptions };
1
+ import { a as resolveOptions, i as createDataUtils, o as parser, r as ViteSlidevPlugin, t as createServer } from "./serve-bnzg299W.mjs";
2
+ export { ViteSlidevPlugin, createDataUtils, createServer, parser, resolveOptions };
@@ -1,8 +1,8 @@
1
- import { dirname, join, relative, resolve } from "node:path";
1
+ import { dirname, join, relative, resolve, sep } from "node:path";
2
2
  import process from "node:process";
3
- import { existsSync } from "node:fs";
3
+ import { existsSync, lstatSync, readdirSync, readlinkSync } from "node:fs";
4
4
  import { copyFile, readFile } from "node:fs/promises";
5
- import { fileURLToPath } from "node:url";
5
+ import { fileURLToPath, pathToFileURL } from "node:url";
6
6
  import { parseNi, run } from "@antfu/ni";
7
7
  import { ensurePrefix, slash } from "@antfu/utils";
8
8
  import { underline, yellow } from "ansis";
@@ -11,10 +11,90 @@ import { resolvePath } from "mlly";
11
11
  import prompts from "prompts";
12
12
  import { resolveGlobal } from "resolve-global";
13
13
  import { findClosestPkgJsonPath, findDepPkgJsonPath } from "vitefu";
14
-
15
14
  //#region node/resolver.ts
16
15
  const RE_PATH_SEPARATOR = /[/\\]/;
16
+ const RE_SAFE_PKG_NAME = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
17
17
  const cliRoot = fileURLToPath(new URL("..", import.meta.url));
18
+ /**
19
+ * Detect the `node_modules/` directory the running slidev binary was
20
+ * dispatched from. The simple `findDepPkgJsonPath(name, cliRoot)` walk fails
21
+ * in pnpm v11+ global installs because `cliRoot` is the realpath inside the
22
+ * content-addressable store, with no path back to the install directory. The
23
+ * invocation path (`process.argv[1]`), however, is one of:
24
+ *
25
+ * - `{PNPM_HOME}/v11/{hash}/node_modules/@slidev/cli/bin/slidev.mjs` — pnpm
26
+ * v11 globals dispatch through a shell shim in `{PNPM_HOME}/bin/` that exec's
27
+ * `node` with this argv[1].
28
+ * - `{globals}/node_modules/@slidev/cli/bin/slidev.mjs` — npm/yarn globals,
29
+ * where the bin is a fs symlink and the resolved script path keeps the
30
+ * `node_modules` segment.
31
+ * - `./node_modules/.bin/slidev` — local project, where Node resolves the
32
+ * `.bin` symlink before populating argv[1].
33
+ *
34
+ * Returns the trailing `node_modules` directory of that path, or `undefined`
35
+ * if `argv1` doesn't pass through one.
36
+ *
37
+ * Exported for tests.
38
+ */
39
+ function findInvocationNodeModulesPath(argv1) {
40
+ if (!argv1) return void 0;
41
+ const direct = resolve(argv1);
42
+ const segment = `${sep}node_modules${sep}`;
43
+ const directIdx = direct.lastIndexOf(segment);
44
+ if (directIdx >= 0) return direct.slice(0, directIdx + 1 + 12);
45
+ let current = direct;
46
+ for (let i = 0; i < 16; i++) {
47
+ let stat;
48
+ try {
49
+ stat = lstatSync(current);
50
+ } catch {
51
+ return;
52
+ }
53
+ if (!stat.isSymbolicLink()) return void 0;
54
+ const target = readlinkSync(current);
55
+ current = resolve(dirname(current), target);
56
+ const idx = current.lastIndexOf(segment);
57
+ if (idx >= 0) return current.slice(0, idx + 1 + 12);
58
+ }
59
+ }
60
+ /**
61
+ * Candidate `node_modules` directories to search when slidev is installed
62
+ * globally. Always includes the invocation's own `node_modules` (the install
63
+ * group `@slidev/cli` lives in), plus every sibling install group's
64
+ * `node_modules` so cross-group lookups can succeed.
65
+ *
66
+ * pnpm v11 lays globals out as `{root}/v11/{hash}/node_modules/...`; each
67
+ * `pnpm add -g <pkg>` invocation creates its own `{hash}` directory even
68
+ * when multiple packages are listed in one call. Walking the parent of the
69
+ * cli's install group lets us discover packages that landed in sibling
70
+ * groups (e.g. a theme installed alongside `@slidev/cli`).
71
+ *
72
+ * Exported for tests.
73
+ */
74
+ function computeInvocationSearchPaths(ownNodeModules) {
75
+ if (!ownNodeModules) return [];
76
+ const paths = [ownNodeModules];
77
+ const installDir = dirname(ownNodeModules);
78
+ const globalRoot = dirname(installDir);
79
+ if (!globalRoot || globalRoot === installDir) return paths;
80
+ let siblings;
81
+ try {
82
+ siblings = readdirSync(globalRoot);
83
+ } catch {
84
+ return paths;
85
+ }
86
+ for (const sib of siblings) {
87
+ const sibInstall = join(globalRoot, sib);
88
+ if (sibInstall === installDir) continue;
89
+ const sibNm = join(sibInstall, "node_modules");
90
+ try {
91
+ if (lstatSync(sibNm).isDirectory()) paths.push(sibNm);
92
+ } catch {}
93
+ }
94
+ return paths;
95
+ }
96
+ const invocationNodeModules = findInvocationNodeModulesPath(process.argv[1]);
97
+ const invocationSearchPaths = computeInvocationSearchPaths(invocationNodeModules);
18
98
  const isInstalledGlobally = {};
19
99
  /**
20
100
  * Resolve path for import url on Vite client side
@@ -22,27 +102,38 @@ const isInstalledGlobally = {};
22
102
  async function resolveImportUrl(id) {
23
103
  return toAtFS(await resolveImportPath(id, true));
24
104
  }
25
- function toAtFS(path$1) {
26
- return `/@fs${ensurePrefix("/", slash(path$1))}`;
105
+ function toAtFS(path) {
106
+ return `/@fs${ensurePrefix("/", slash(path))}`;
27
107
  }
28
108
  async function resolveImportPath(importName, ensure = false) {
29
109
  try {
30
110
  return await resolvePath(importName, { url: import.meta.url });
31
111
  } catch {}
32
- if (isInstalledGlobally.value) try {
33
- return resolveGlobal(importName);
34
- } catch {}
112
+ if (isInstalledGlobally.value) {
113
+ for (const nm of invocationSearchPaths) try {
114
+ return await resolvePath(importName, { url: pathToFileURL(`${nm}${sep}`) });
115
+ } catch {}
116
+ try {
117
+ return resolveGlobal(importName);
118
+ } catch {}
119
+ }
35
120
  if (ensure) throw new Error(`Failed to resolve package "${importName}"`);
36
121
  }
37
122
  async function findPkgRoot(dep, parent, ensure = false) {
38
123
  const pkgJsonPath = await findDepPkgJsonPath(dep, parent);
39
- const path$1 = pkgJsonPath ? dirname(pkgJsonPath) : isInstalledGlobally.value ? await findGlobalPkgRoot(dep, false) : void 0;
40
- if (ensure && !path$1) throw new Error(`Failed to resolve package "${dep}"`);
41
- return path$1;
124
+ const path = pkgJsonPath ? dirname(pkgJsonPath) : isInstalledGlobally.value ? await findGlobalPkgRoot(dep, false) : void 0;
125
+ if (ensure && !path) throw new Error(`Failed to resolve package "${dep}"`);
126
+ return path;
42
127
  }
43
128
  async function findGlobalPkgRoot(name, ensure = false) {
44
129
  const localPath = await findDepPkgJsonPath(name, cliRoot);
45
130
  if (localPath) return dirname(localPath);
131
+ for (const nm of invocationSearchPaths) {
132
+ const direct = join(nm, ...name.split("/"), "package.json");
133
+ if (existsSync(direct)) return dirname(direct);
134
+ const walked = await findDepPkgJsonPath(name, nm);
135
+ if (walked) return dirname(walked);
136
+ }
46
137
  const yarnPath = join(globalDirs.yarn.packages, name);
47
138
  if (existsSync(`${yarnPath}/package.json`)) return yarnPath;
48
139
  const npmPath = join(globalDirs.npm.packages, name);
@@ -93,12 +184,13 @@ function createResolver(type, officials) {
93
184
  if (name[0] === "/") return [name, name];
94
185
  if (name.startsWith("@/")) return [name, resolve(userRoot, name.slice(2))];
95
186
  if (name[0] === "." || name[0] !== "@" && name.includes("/")) return [name, resolve(dirname(importer), name)];
187
+ if (!RE_SAFE_PKG_NAME.test(name)) throw new Error(`Invalid ${type} name "${name}". Only valid npm package names are allowed.`);
96
188
  {
97
189
  const possiblePkgNames = [name];
98
190
  if (!name.includes("/") && !name.startsWith("@")) possiblePkgNames.unshift(`@slidev/${type}-${name}`, `slidev-${type}-${name}`);
99
- for (const pkgName$1 of possiblePkgNames) {
100
- const pkgRoot = await findPkgRoot(pkgName$1, importer);
101
- if (pkgRoot) return [pkgName$1, pkgRoot];
191
+ for (const pkgName of possiblePkgNames) {
192
+ const pkgRoot = await findPkgRoot(pkgName, importer);
193
+ if (pkgRoot) return [pkgName, pkgRoot];
102
194
  }
103
195
  }
104
196
  const pkgName = officials[name] ?? (name[0] === "@" ? name : `slidev-${type}-${name}`);
@@ -107,14 +199,14 @@ function createResolver(type, officials) {
107
199
  };
108
200
  }
109
201
  async function getUserPkgJson(userRoot) {
110
- const path$1 = resolve(userRoot, "package.json");
111
- if (existsSync(path$1)) return JSON.parse(await readFile(path$1, "utf-8"));
202
+ const path = resolve(userRoot, "package.json");
203
+ if (existsSync(path)) return JSON.parse(await readFile(path, "utf-8"));
112
204
  return {};
113
205
  }
114
206
  async function hasWorkspacePackageJSON(root) {
115
- const path$1 = join(root, "package.json");
116
- if (!existsSync(path$1)) return false;
117
- return !!(JSON.parse(await readFile(path$1, "utf-8")) || {}).workspaces;
207
+ const path = join(root, "package.json");
208
+ if (!existsSync(path)) return false;
209
+ return !!(JSON.parse(await readFile(path, "utf-8")) || {}).workspaces;
118
210
  }
119
211
  function hasRootFile(root) {
120
212
  return ["pnpm-workspace.yaml"].some((file) => existsSync(join(root, file)));
@@ -134,7 +226,7 @@ async function getRoots(entry) {
134
226
  if (rootsInfo) return rootsInfo;
135
227
  if (!entry) throw new Error("[slidev] Cannot find roots without entry");
136
228
  const userRoot = dirname(entry);
137
- isInstalledGlobally.value = slash(relative(userRoot, process.argv[1])).includes("/.pnpm/") || (await import("is-installed-globally")).default;
229
+ isInstalledGlobally.value = slash(relative(userRoot, process.argv[1])).includes("/.pnpm/") || invocationNodeModules != null && slash(relative(userRoot, invocationNodeModules)).startsWith("..") || (await import("is-installed-globally")).default;
138
230
  const clientRoot = await findPkgRoot("@slidev/client", cliRoot, true);
139
231
  const closestPkgRoot = dirname(await findClosestPkgJsonPath(userRoot) || userRoot);
140
232
  rootsInfo = {
@@ -162,6 +254,5 @@ function resolveSourceFiles(roots, subpath, extensions = [
162
254
  }
163
255
  return results;
164
256
  }
165
-
166
257
  //#endregion
167
- export { resolveImportPath as a, toAtFS as c, resolveEntry as i, getRoots as n, resolveImportUrl as o, isInstalledGlobally as r, resolveSourceFiles as s, createResolver as t };
258
+ export { resolveImportPath as a, toAtFS as c, resolveEntry as i, getRoots as n, resolveImportUrl as o, isInstalledGlobally as r, resolveSourceFiles as s, createResolver as t };