@slidev/cli 52.15.2 → 52.17.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.
package/README.md CHANGED
@@ -19,7 +19,7 @@ Presentation <b>slide</b>s for <b>dev</b>elopers 🧑‍💻👩‍💻👨‍
19
19
  </p>
20
20
 
21
21
  <p align="center">
22
- <a href="https://twitter.com/antfu7/status/1389604687502995457">Video Preview</a> | <a href="https://sli.dev">Documentation</a>
22
+ <a href="https://x.com/antfu7/status/1389604687502995457">Video Preview</a> | <a href="https://sli.dev">Documentation</a>
23
23
  </p>
24
24
 
25
25
  <div align="center">
@@ -1,5 +1,4 @@
1
- import "./resolver-BhqywfGz.mjs";
2
- import { t as resolveViteConfigs } from "./shared-CmTSrQGO.mjs";
1
+ import { n as resolveViteConfigs } from "./serve-CgOXLPUI.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-C2XL3OpJ.mjs");
54
+ const { exportSlides } = await import("./export-CU2GL9dm.mjs");
57
55
  const tempDir = resolve(outDir, "temp");
58
56
  await fs$1.mkdir(tempDir, { recursive: true });
59
57
  await exportSlides({
@@ -66,7 +64,7 @@ async function build(options, viteConfig = {}, args) {
66
64
  range: "1",
67
65
  width: options.data.config.canvasWidth,
68
66
  height: Math.round(options.data.config.canvasWidth / options.data.config.aspectRatio),
69
- routerMode: options.data.config.routerMode,
67
+ routerMode: options.data.config.routerMode === "memory" ? "history" : options.data.config.routerMode,
70
68
  waitUntil: "networkidle",
71
69
  timeout: args.timeout || 3e4,
72
70
  perSlide: true,
@@ -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-C2XL3OpJ.mjs");
95
+ const { exportSlides, getExportOptions } = await import("./export-CU2GL9dm.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-BhqywfGz.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-CmTSrQGO.mjs";
3
- import { t as createServer } from "./serve-CLoYUI-M.mjs";
1
+ import { i as resolveEntry, r as isInstalledGlobally } from "./resolver-BBT539iz.mjs";
2
+ import { c as getThemeMeta, d as loadSetups, f as updateFrontmatterPatch, l as resolveTheme, o as resolveOptions, s as parser, t as createServer, u as version } from "./serve-CgOXLPUI.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
  }
@@ -286,15 +288,19 @@ cli.command("build [entry..]", "Build hostable SPA", (args) => exportOptions(com
286
288
  describe: "exclude speaker notes from the built output"
287
289
  }).option("router-mode", {
288
290
  type: "string",
289
- choices: ["hash", "history"],
290
- describe: "override routerMode in the built output (hash for subdirectory deploys like GitHub Pages)"
291
+ choices: [
292
+ "hash",
293
+ "history",
294
+ "memory"
295
+ ],
296
+ describe: "override routerMode in the built output (hash for subdirectory deploys like GitHub Pages; memory keeps the slide number out of the URL, for kiosk/follower decks)"
291
297
  }).option("inspect", {
292
298
  default: false,
293
299
  type: "boolean",
294
300
  describe: "enable the inspect plugin for debugging"
295
301
  }).strict().help(), async (args) => {
296
302
  const { entry, theme, base, download, out, inspect, "without-notes": withoutNotes, "router-mode": routerMode } = args;
297
- const { build } = await import("./build-BZYW8gn1.mjs");
303
+ const { build } = await import("./build-Cszi5hDI.mjs");
298
304
  for (const entryFile of entry) {
299
305
  const options = await resolveOptions({
300
306
  entry: entryFile,
@@ -322,14 +328,18 @@ cli.command("format [entry..]", "Format the markdown file", (args) => commonOpti
322
328
  await parser.save(md);
323
329
  }
324
330
  });
331
+ cli.command("mcp [entry]", "Start an MCP (Model Context Protocol) server over stdio for AI agents to inspect and edit the slides", (args) => commonOptions(args).strict().help(), async ({ entry }) => {
332
+ const { startMcpStdioServer } = await import("./stdio-LsewwOTO.mjs");
333
+ await startMcpStdioServer(await resolveEntry(entry));
334
+ });
325
335
  cli.command("theme [subcommand]", "Theme related operations", (command) => {
326
336
  return command.command("eject", "Eject current theme into local file system", (args) => commonOptions(args).option("dir", {
327
337
  type: "string",
328
338
  default: "theme"
329
339
  }), async ({ entry: entryRaw, dir, theme: themeInput }) => {
330
340
  const entry = await resolveEntry(entryRaw);
331
- const roots = await getRoots(entry);
332
- const data = await parser.load(roots.userRoot, entry);
341
+ const options = await resolveOptions({ entry }, "dev");
342
+ const data = await parser.load(options, entry);
333
343
  let themeRaw = themeInput || data.headmatter.theme;
334
344
  themeRaw = themeRaw === null ? "none" : themeRaw || "default";
335
345
  if (themeRaw === "none") {
@@ -359,7 +369,7 @@ cli.command("theme [subcommand]", "Theme related operations", (command) => {
359
369
  });
360
370
  cli.command("export [entry..]", "Export slides to PDF", (args) => exportOptions(commonOptions(args)).strict().help(), async (args) => {
361
371
  const { entry, theme } = args;
362
- const { exportSlides, getExportOptions } = await import("./export-C2XL3OpJ.mjs");
372
+ const { exportSlides, getExportOptions } = await import("./export-CU2GL9dm.mjs");
363
373
  const candidatePort = await getPort(12445);
364
374
  let warned = false;
365
375
  for (const entryFile of entry) {
@@ -411,7 +421,7 @@ cli.command("export-notes [entry..]", "Export slide notes to PDF", (args) => arg
411
421
  type: "number",
412
422
  describe: "wait for the specified ms before exporting"
413
423
  }).strict().help(), async ({ entry, output, timeout, wait }) => {
414
- const { exportNotes } = await import("./export-C2XL3OpJ.mjs");
424
+ const { exportNotes } = await import("./export-CU2GL9dm.mjs");
415
425
  const candidatePort = await getPort(12445);
416
426
  for (const entryFile of entry) {
417
427
  const options = await resolveOptions({ entry: entryFile }, "export");
@@ -537,6 +547,7 @@ function printInfo(options, port, base, remote, tunnelUrl, publicIp) {
537
547
  if (options.utils.define.__SLIDEV_FEATURE_PRESENTER__) console.log(`${dim(" presenter mode ")} > ${blue(`${baseUrl}${presenterPath}`)}`);
538
548
  console.log(`${dim(" slides overview ")} > ${blue(`${baseUrl}${overviewPath}`)}`);
539
549
  if (options.utils.define.__SLIDEV_FEATURE_BROWSER_EXPORTER__) console.log(`${dim(" export slides")} > ${blue(`${baseUrl}/export/`)}`);
550
+ if (options.mode === "dev" && options.data.config.mcp !== false) console.log(`${dim(" mcp server (ai)")} > ${blue(`http://localhost:${bold(port)}/__mcp`)}`);
540
551
  if (options.inspect) console.log(`${dim(" vite inspector")} > ${yellow(`${baseUrl}/__inspect/`)}`);
541
552
  let lastRemoteUrl = "";
542
553
  if (remote !== void 0) {
@@ -558,6 +569,5 @@ function printInfo(options, port, base, remote, tunnelUrl, publicIp) {
558
569
  return lastRemoteUrl;
559
570
  }
560
571
  }
561
-
562
572
  //#endregion
563
- export { };
573
+ export {};
@@ -1,4 +1,4 @@
1
- import { n as getRoots } from "./resolver-BhqywfGz.mjs";
1
+ import { n as getRoots } from "./resolver-BBT539iz.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);
@@ -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,13 +381,13 @@ 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,
389
388
  waitUntil: waitUntil === "none" ? void 0 : waitUntil ?? "networkidle",
390
389
  dark: dark || options.data.config.colorSchema === "dark",
391
- routerMode: options.data.config.routerMode,
390
+ routerMode: options.data.config.routerMode === "memory" ? "history" : options.data.config.routerMode,
392
391
  width: options.data.config.canvasWidth,
393
392
  height: Math.round(options.data.config.canvasWidth / options.data.config.aspectRatio),
394
393
  withClicks: withClicks ?? format === "pptx",
@@ -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,8 @@
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
-
6
4
  //#region node/commands/serve.d.ts
7
- declare function createServer(options: ResolvedSlidevOptions, viteConfig?: InlineConfig, serverOptions?: SlidevServerOptions): Promise<vite0.ViteDevServer>;
5
+ declare function createServer(options: ResolvedSlidevOptions, viteConfig?: InlineConfig, serverOptions?: SlidevServerOptions): Promise<import("vite").ViteDevServer>;
8
6
  //#endregion
9
7
  //#region node/options.d.ts
10
8
  declare function resolveOptions(entryOptions: SlidevEntryOptions, mode: ResolvedSlidevOptions['mode']): Promise<ResolvedSlidevOptions>;
package/dist/index.mjs CHANGED
@@ -1,5 +1,2 @@
1
- import "./resolver-BhqywfGz.mjs";
2
- import { a as parser, i as resolveOptions, n as ViteSlidevPlugin, r as createDataUtils } from "./shared-CmTSrQGO.mjs";
3
- import { t as createServer } from "./serve-CLoYUI-M.mjs";
4
-
5
- export { ViteSlidevPlugin, createDataUtils, createServer, parser, resolveOptions };
1
+ import { a as createDataUtils, o as resolveOptions, r as ViteSlidevPlugin, s as parser, t as createServer } from "./serve-CgOXLPUI.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,11 +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 = /[/\\]/;
17
16
  const RE_SAFE_PKG_NAME = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
18
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);
19
98
  const isInstalledGlobally = {};
20
99
  /**
21
100
  * Resolve path for import url on Vite client side
@@ -23,27 +102,38 @@ const isInstalledGlobally = {};
23
102
  async function resolveImportUrl(id) {
24
103
  return toAtFS(await resolveImportPath(id, true));
25
104
  }
26
- function toAtFS(path$1) {
27
- return `/@fs${ensurePrefix("/", slash(path$1))}`;
105
+ function toAtFS(path) {
106
+ return `/@fs${ensurePrefix("/", slash(path))}`;
28
107
  }
29
108
  async function resolveImportPath(importName, ensure = false) {
30
109
  try {
31
110
  return await resolvePath(importName, { url: import.meta.url });
32
111
  } catch {}
33
- if (isInstalledGlobally.value) try {
34
- return resolveGlobal(importName);
35
- } 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
+ }
36
120
  if (ensure) throw new Error(`Failed to resolve package "${importName}"`);
37
121
  }
38
122
  async function findPkgRoot(dep, parent, ensure = false) {
39
123
  const pkgJsonPath = await findDepPkgJsonPath(dep, parent);
40
- const path$1 = pkgJsonPath ? dirname(pkgJsonPath) : isInstalledGlobally.value ? await findGlobalPkgRoot(dep, false) : void 0;
41
- if (ensure && !path$1) throw new Error(`Failed to resolve package "${dep}"`);
42
- 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;
43
127
  }
44
128
  async function findGlobalPkgRoot(name, ensure = false) {
45
129
  const localPath = await findDepPkgJsonPath(name, cliRoot);
46
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
+ }
47
137
  const yarnPath = join(globalDirs.yarn.packages, name);
48
138
  if (existsSync(`${yarnPath}/package.json`)) return yarnPath;
49
139
  const npmPath = join(globalDirs.npm.packages, name);
@@ -98,9 +188,9 @@ function createResolver(type, officials) {
98
188
  {
99
189
  const possiblePkgNames = [name];
100
190
  if (!name.includes("/") && !name.startsWith("@")) possiblePkgNames.unshift(`@slidev/${type}-${name}`, `slidev-${type}-${name}`);
101
- for (const pkgName$1 of possiblePkgNames) {
102
- const pkgRoot = await findPkgRoot(pkgName$1, importer);
103
- 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];
104
194
  }
105
195
  }
106
196
  const pkgName = officials[name] ?? (name[0] === "@" ? name : `slidev-${type}-${name}`);
@@ -109,14 +199,14 @@ function createResolver(type, officials) {
109
199
  };
110
200
  }
111
201
  async function getUserPkgJson(userRoot) {
112
- const path$1 = resolve(userRoot, "package.json");
113
- 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"));
114
204
  return {};
115
205
  }
116
206
  async function hasWorkspacePackageJSON(root) {
117
- const path$1 = join(root, "package.json");
118
- if (!existsSync(path$1)) return false;
119
- 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;
120
210
  }
121
211
  function hasRootFile(root) {
122
212
  return ["pnpm-workspace.yaml"].some((file) => existsSync(join(root, file)));
@@ -136,15 +226,17 @@ async function getRoots(entry) {
136
226
  if (rootsInfo) return rootsInfo;
137
227
  if (!entry) throw new Error("[slidev] Cannot find roots without entry");
138
228
  const userRoot = dirname(entry);
139
- 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;
140
230
  const clientRoot = await findPkgRoot("@slidev/client", cliRoot, true);
141
231
  const closestPkgRoot = dirname(await findClosestPkgJsonPath(userRoot) || userRoot);
232
+ const userPkgJson = await getUserPkgJson(closestPkgRoot);
233
+ const userWorkspaceRoot = await searchForWorkspaceRoot(closestPkgRoot);
142
234
  rootsInfo = {
143
235
  cliRoot,
144
236
  clientRoot,
145
237
  userRoot,
146
- userPkgJson: await getUserPkgJson(closestPkgRoot),
147
- userWorkspaceRoot: await searchForWorkspaceRoot(closestPkgRoot)
238
+ userPkgJson,
239
+ userWorkspaceRoot
148
240
  };
149
241
  return rootsInfo;
150
242
  }
@@ -164,6 +256,5 @@ function resolveSourceFiles(roots, subpath, extensions = [
164
256
  }
165
257
  return results;
166
258
  }
167
-
168
259
  //#endregion
169
- 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 };
260
+ 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 };