pulumi-package-docs 0.7.0 → 0.9.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.
Files changed (59) hide show
  1. package/README.md +98 -4
  2. package/bin/args.js +54 -3
  3. package/bin/args.test.js +50 -0
  4. package/bin/cli-bun.js +54 -22
  5. package/bin/cli.js +74 -28
  6. package/bin/cli.test.js +18 -0
  7. package/bin/static.js +196 -0
  8. package/bin/static.test.js +176 -0
  9. package/dist/client/assets/MemberDetail-D8jkDkVP.js +1 -0
  10. package/dist/client/assets/{token-oQmZfAP3.js → examples-zkHFEaMt.js} +1 -1
  11. package/dist/client/assets/index-DM9VsIdh.js +13 -0
  12. package/dist/client/assets/providers._name-C-FSbmVZ.js +1 -0
  13. package/dist/client/assets/providers._name.functions._token-B3S8Gcfx.js +1 -0
  14. package/dist/client/assets/providers._name.functions._token-C4VPHGlG.js +1 -0
  15. package/dist/client/assets/providers._name.index-CWHPtoBD.js +1 -0
  16. package/dist/client/assets/providers._name.index-nlnQuJ9Q.js +1 -0
  17. package/dist/client/assets/providers._name.resources._token-BISpZVHI.js +1 -0
  18. package/dist/client/assets/providers._name.resources._token-CvMi94a3.js +1 -0
  19. package/dist/client/assets/{routes-DKNWlgPZ.js → routes-DwhZQHE3.js} +1 -1
  20. package/dist/mcp/stdio.js +449 -0
  21. package/dist/server/assets/{MemberDetail-C5sAHNZW.js → MemberDetail-CdHUSmQD.js} +2 -1
  22. package/dist/server/assets/{_tanstack-start-manifest_v-B0J-S-em.js → _tanstack-start-manifest_v-DNIp6gp3.js} +13 -15
  23. package/dist/server/assets/api-D49ZDhMh.js +27 -0
  24. package/dist/server/assets/{providers._name.functions._token-Gkw4zt3M.js → providers._name.functions._token-BvkwmQ-L.js} +3 -2
  25. package/dist/server/assets/{providers._name.index-CEXkl-76.js → providers._name.index-DBkfEjsy.js} +3 -2
  26. package/dist/server/assets/{providers._name.resources._token-R_i2MmRl.js → providers._name.resources._token-Sl31Qlvd.js} +3 -2
  27. package/dist/server/assets/{schema.server-B-VvU1qb.js → providers.server-B1ZHZ5jE.js} +48 -1
  28. package/dist/server/assets/{router-1INob9fh.js → router-_zs8P-B2.js} +24 -87
  29. package/dist/server/assets/{routes-4jiXnS1p.js → routes-BUxagg9r.js} +1 -1
  30. package/dist/server/assets/token-DBEFeZIN.js +92 -0
  31. package/dist/server/server.js +4 -4
  32. package/examples/github-pages.yml +56 -0
  33. package/examples/gitlab-ci.yml +22 -0
  34. package/package.json +6 -2
  35. package/src/components/ThemeToggle.tsx +37 -0
  36. package/src/lib/pulumi/api.ts +18 -69
  37. package/src/lib/pulumi/mcp.server.test.ts +112 -0
  38. package/src/lib/pulumi/mcp.server.ts +11 -6
  39. package/src/lib/pulumi/providers.server.ts +80 -0
  40. package/src/lib/pulumi/stdio.server.ts +16 -0
  41. package/src/lib/pulumi/token.test.ts +44 -1
  42. package/src/lib/pulumi/token.ts +28 -0
  43. package/src/lib/static-docs.ts +15 -0
  44. package/src/router.tsx +3 -0
  45. package/src/routes/__root.tsx +6 -3
  46. package/src/routes/providers.$name.functions.$token.tsx +7 -4
  47. package/src/routes/providers.$name.index.tsx +2 -2
  48. package/src/routes/providers.$name.resources.$token.tsx +7 -4
  49. package/vite.config.ts +41 -1
  50. package/dist/client/assets/MemberDetail-BS9DbqEt.js +0 -1
  51. package/dist/client/assets/index-2FzxAhzN.js +0 -13
  52. package/dist/client/assets/providers._name-FmJEVkdV.js +0 -1
  53. package/dist/client/assets/providers._name.functions._token-BzjmNHea.js +0 -1
  54. package/dist/client/assets/providers._name.functions._token-D768U6I-.js +0 -1
  55. package/dist/client/assets/providers._name.index-C42yQ95z.js +0 -1
  56. package/dist/client/assets/providers._name.index-Dm0YiZWP.js +0 -1
  57. package/dist/client/assets/providers._name.resources._token-CIYPoqyA.js +0 -1
  58. package/dist/client/assets/providers._name.resources._token-D0OLvgo_.js +0 -1
  59. package/dist/server/assets/api-C55bS4R6.js +0 -64
@@ -119,4 +119,51 @@ async function resolveProviderSchema(projectRoot, ref) {
119
119
  }
120
120
  }
121
121
  //#endregion
122
- export { getTargetDir as n, loadPulumiProject as r, resolveProviderSchema as t };
122
+ //#region src/lib/pulumi/providers.server.ts
123
+ /**
124
+ * The project + provider summaries shown on the index route and by the MCP
125
+ * `list_providers` tool. Plain async functions (no TanStack Start server-fn
126
+ * wrapper) so they can run either inside the web server or in the stdio MCP
127
+ * process, which has no HTTP server at all.
128
+ */
129
+ async function loadProjectSummary() {
130
+ const targetDir = getTargetDir();
131
+ const project = loadPulumiProject(targetDir);
132
+ if (!project) return {
133
+ targetDir,
134
+ project: null,
135
+ providers: []
136
+ };
137
+ const providers = (await Promise.all(project.packages.map((ref) => resolveProviderSchema(project.root, ref)))).map((entry) => ({
138
+ name: entry.ref.name,
139
+ source: entry.ref.source,
140
+ version: entry.ref.version,
141
+ parameters: entry.ref.parameters,
142
+ resourceCount: Object.keys(entry.schema?.resources ?? {}).length,
143
+ functionCount: Object.keys(entry.schema?.functions ?? {}).length,
144
+ origin: entry.origin,
145
+ error: entry.error
146
+ }));
147
+ return {
148
+ targetDir,
149
+ project: {
150
+ root: project.root,
151
+ name: project.name,
152
+ runtime: project.runtime
153
+ },
154
+ providers
155
+ };
156
+ }
157
+ /** The fully resolved schema for one local provider, or null if the project or provider isn't found. */
158
+ async function loadProviderDetail(name) {
159
+ const project = loadPulumiProject(getTargetDir());
160
+ if (!project) return null;
161
+ const ref = project.packages.find((p) => p.name === name);
162
+ if (!ref) return null;
163
+ return {
164
+ ...await resolveProviderSchema(project.root, ref),
165
+ runtime: project.runtime
166
+ };
167
+ }
168
+ //#endregion
169
+ export { loadPulumiProject as a, getTargetDir as i, loadProviderDetail as n, resolveProviderSchema as r, loadProjectSummary as t };
@@ -1,5 +1,6 @@
1
- import { n as getTargetDir, r as loadPulumiProject, t as resolveProviderSchema } from "./schema.server-B-VvU1qb.js";
1
+ import { a as loadPulumiProject, i as getTargetDir, n as loadProviderDetail, r as resolveProviderSchema, t as loadProjectSummary } from "./providers.server-B1ZHZ5jE.js";
2
2
  import { n as listProviders, t as getProvider } from "./api-BV6lykkP.js";
3
+ import { a as pickExample, c as resolveDescription, i as extractExamples, n as tokenDisplayName, o as stripExamples, s as summarizeDescription, t as findTokenBySlug } from "./token-DBEFeZIN.js";
3
4
  import { useEffect, useState } from "react";
4
5
  import { HeadContent, Link, Scripts, createFileRoute, createRootRoute, createRouter, lazyRouteComponent, notFound } from "@tanstack/react-router";
5
6
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -328,71 +329,12 @@ function RootDocument({ children }) {
328
329
  }
329
330
  //#endregion
330
331
  //#region src/routes/index.tsx
331
- var $$splitComponentImporter$4 = () => import("./routes-4jiXnS1p.js");
332
+ var $$splitComponentImporter$4 = () => import("./routes-BUxagg9r.js");
332
333
  var Route$5 = createFileRoute("/")({
333
334
  loader: () => listProviders(),
334
335
  component: lazyRouteComponent($$splitComponentImporter$4, "component")
335
336
  });
336
337
  //#endregion
337
- //#region src/lib/pulumi/description.ts
338
- /**
339
- * Pulumi schema descriptions embed per-language text as HTML-ish spans, e.g.
340
- * `<span pulumi-lang-nodejs="`length`" pulumi-lang-python="`length`" ...>`length`</span>`.
341
- * pulumi.com's docs site picks an attribute based on the selected language tab;
342
- * we don't have tabs, so we resolve each span to the project's own runtime up
343
- * front and strip the markup rather than rendering (or leaking) raw HTML.
344
- */
345
- var LANG_SPAN_RE = /<span\s+((?:pulumi-lang-[a-z]+="[^"]*"\s*)+)>([\s\S]*?)<\/span>/g;
346
- var LANG_ATTR_RE = /pulumi-lang-([a-z]+)="([^"]*)"/g;
347
- function resolveLangSpans(text, runtime) {
348
- return text.replace(LANG_SPAN_RE, (_match, attrsSource, fallback) => {
349
- if (!runtime) return fallback;
350
- LANG_ATTR_RE.lastIndex = 0;
351
- let match;
352
- while (match = LANG_ATTR_RE.exec(attrsSource)) if (match[1] === runtime) return match[2];
353
- return fallback;
354
- });
355
- }
356
- /** Resolves a schema description's per-language spans for the given Pulumi runtime. */
357
- function resolveDescription(text, runtime) {
358
- if (!text) return text;
359
- return resolveLangSpans(text, runtime);
360
- }
361
- //#endregion
362
- //#region src/lib/pulumi/examples.ts
363
- var CODE_FENCE_RE = /```([a-zA-Z0-9+#-]+)\n([\s\S]*?)```/g;
364
- function extractExamples(text) {
365
- if (!text) return [];
366
- return [...text.matchAll(CODE_FENCE_RE)].map((match) => ({
367
- language: match[1].toLowerCase(),
368
- code: match[2].trimEnd()
369
- }));
370
- }
371
- /** The description text with any fenced code examples removed. */
372
- function stripExamples(text) {
373
- if (!text) return text;
374
- return text.replace(CODE_FENCE_RE, "").trim() || void 0;
375
- }
376
- /** A short summary for list views: the first prose paragraph, examples excluded. */
377
- function summarizeDescription(text) {
378
- const stripped = stripExamples(text);
379
- if (!stripped) return void 0;
380
- return stripped.split(/\n{2,}/)[0]?.trim() || void 0;
381
- }
382
- var RUNTIME_LANGUAGES = {
383
- nodejs: ["typescript", "javascript"],
384
- python: ["python"],
385
- go: ["go", "golang"],
386
- dotnet: ["csharp"],
387
- java: ["java"],
388
- yaml: ["yaml"]
389
- };
390
- /** Picks the example matching the project's runtime, falling back to the first available one. */
391
- function pickExample(examples, runtime) {
392
- const languages = runtime ? RUNTIME_LANGUAGES[runtime] : void 0;
393
- return (languages ? examples.find((example) => languages.includes(example.language)) : void 0) ?? examples[0];
394
- }
395
- //#endregion
396
338
  //#region src/lib/pulumi/format.ts
397
339
  function formatType(prop) {
398
340
  if (!prop) return "any";
@@ -428,18 +370,6 @@ function formatPropertyName(name, runtime) {
428
370
  }
429
371
  }
430
372
  //#endregion
431
- //#region src/lib/pulumi/token.ts
432
- /**
433
- * Schema tokens look like `random:index/randomString:RandomString` (resources)
434
- * or `random:index/randomInteger:randomInteger` (functions). The segment after
435
- * the last colon is the name every generated SDK exports for that member, and
436
- * is unique within the provider, so it makes a readable display name.
437
- */
438
- function tokenDisplayName(token) {
439
- const short = token.split(":").pop();
440
- return short && short.length > 0 ? short : token;
441
- }
442
- //#endregion
443
373
  //#region src/lib/pulumi/mcp.server.ts
444
374
  function textResult(data) {
445
375
  return { content: [{
@@ -449,7 +379,7 @@ function textResult(data) {
449
379
  }
450
380
  /** Loads a local provider's resolved schema, throwing a descriptive error a tool call can surface to the client. */
451
381
  async function requireProvider(providerName) {
452
- const detail = await getProvider({ data: providerName });
382
+ const detail = await loadProviderDetail(providerName);
453
383
  if (!detail) throw new Error(`No local provider named "${providerName}" was found. Call list_providers to see the providers declared in this project's Pulumi.yaml.`);
454
384
  if (!detail.schema) throw new Error(`Failed to load the schema for provider "${providerName}": ${detail.error ?? "unknown error"}`);
455
385
  return {
@@ -489,8 +419,10 @@ function memberDetail(provider, token, description, runtime, inputs, outputs, de
489
419
  };
490
420
  }
491
421
  /**
492
- * Builds a fresh MCP server with the local-provider-docs tools registered. Called once
493
- * per HTTP request (see `src/routes/mcp.ts`) so concurrent requests never share state.
422
+ * Builds a fresh MCP server with the local-provider-docs tools registered. Transport
423
+ * agnostic: `src/routes/mcp.ts` calls it once per HTTP request (so concurrent requests
424
+ * never share state), and `src/lib/pulumi/stdio.server.ts` calls it once for the
425
+ * lifetime of a stdio process.
494
426
  */
495
427
  function createMcpServer() {
496
428
  const server = new McpServer({
@@ -500,7 +432,7 @@ function createMcpServer() {
500
432
  server.registerTool("list_providers", {
501
433
  title: "List local providers",
502
434
  description: "List the local/parameterized Pulumi providers declared in this project's Pulumi.yaml (added with `pulumi package add`), with their source, version, and resource/function counts."
503
- }, async () => textResult(await listProviders()));
435
+ }, async () => textResult(await loadProjectSummary()));
504
436
  server.registerTool("list_resources", {
505
437
  title: "List a provider’s resources",
506
438
  description: "List the resource tokens for a local provider, each with a display name, a one-line summary, and whether it is deprecated. Use get_resource for full details (inputs/outputs/example) on one of them.",
@@ -622,7 +554,7 @@ var Route$3 = createFileRoute("/providers/$name")({ component: lazyRouteComponen
622
554
  //#endregion
623
555
  //#region src/routes/providers.$name.index.tsx
624
556
  var $$splitNotFoundComponentImporter$2 = () => import("./providers._name.index-BEwt-7U0.js");
625
- var $$splitComponentImporter$2 = () => import("./providers._name.index-CEXkl-76.js");
557
+ var $$splitComponentImporter$2 = () => import("./providers._name.index-DBkfEjsy.js");
626
558
  var Route$2 = createFileRoute("/providers/$name/")({
627
559
  loader: async ({ params }) => {
628
560
  const entry = await getProvider({ data: params.name });
@@ -635,15 +567,17 @@ var Route$2 = createFileRoute("/providers/$name/")({
635
567
  //#endregion
636
568
  //#region src/routes/providers.$name.functions.$token.tsx
637
569
  var $$splitNotFoundComponentImporter$1 = () => import("./providers._name.functions._token-D8LsoIum.js");
638
- var $$splitComponentImporter$1 = () => import("./providers._name.functions._token-Gkw4zt3M.js");
570
+ var $$splitComponentImporter$1 = () => import("./providers._name.functions._token-BvkwmQ-L.js");
639
571
  var Route$1 = createFileRoute("/providers/$name/functions/$token")({
640
572
  loader: async ({ params }) => {
641
573
  const entry = await getProvider({ data: params.name });
642
- const fn = entry?.schema?.functions?.[params.token];
643
- if (!fn) throw notFound();
574
+ const functions = entry?.schema?.functions;
575
+ const token = functions && findTokenBySlug(Object.keys(functions), params.token);
576
+ const fn = token ? functions[token] : void 0;
577
+ if (!token || !fn) throw notFound();
644
578
  return {
645
579
  providerName: params.name,
646
- token: params.token,
580
+ token,
647
581
  fn,
648
582
  runtime: entry?.runtime
649
583
  };
@@ -654,15 +588,17 @@ var Route$1 = createFileRoute("/providers/$name/functions/$token")({
654
588
  //#endregion
655
589
  //#region src/routes/providers.$name.resources.$token.tsx
656
590
  var $$splitNotFoundComponentImporter = () => import("./providers._name.resources._token-DJPF8nMg.js");
657
- var $$splitComponentImporter = () => import("./providers._name.resources._token-R_i2MmRl.js");
591
+ var $$splitComponentImporter = () => import("./providers._name.resources._token-Sl31Qlvd.js");
658
592
  var Route = createFileRoute("/providers/$name/resources/$token")({
659
593
  loader: async ({ params }) => {
660
594
  const entry = await getProvider({ data: params.name });
661
- const resource = entry?.schema?.resources?.[params.token];
662
- if (!resource) throw notFound();
595
+ const resources = entry?.schema?.resources;
596
+ const token = resources && findTokenBySlug(Object.keys(resources), params.token);
597
+ const resource = token ? resources[token] : void 0;
598
+ if (!token || !resource) throw notFound();
663
599
  return {
664
600
  providerName: params.name,
665
- token: params.token,
601
+ token,
666
602
  resource,
667
603
  runtime: entry?.runtime
668
604
  };
@@ -716,10 +652,11 @@ var router_exports = /* @__PURE__ */ __exportAll({ getRouter: () => getRouter })
716
652
  function getRouter() {
717
653
  return createRouter({
718
654
  routeTree,
655
+ basepath: "/",
719
656
  scrollRestoration: true,
720
657
  defaultPreload: "intent",
721
658
  defaultPreloadStaleTime: 0
722
659
  });
723
660
  }
724
661
  //#endregion
725
- export { tokenDisplayName as a, extractExamples as c, summarizeDescription as d, resolveDescription as f, getRouter, Route$2 as i, pickExample as l, Route as n, formatPropertyName as o, Route$5 as p, Route$1 as r, formatType as s, router_exports as t, stripExamples as u };
662
+ export { formatPropertyName as a, getRouter, Route$2 as i, Route as n, formatType as o, Route$1 as r, Route$5 as s, router_exports as t };
@@ -1,4 +1,4 @@
1
- import { p as Route } from "./router-1INob9fh.js";
1
+ import { s as Route } from "./router-_zs8P-B2.js";
2
2
  import { Link } from "@tanstack/react-router";
3
3
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
4
  //#region src/routes/index.tsx?tsr-split=component
@@ -0,0 +1,92 @@
1
+ //#region src/lib/pulumi/description.ts
2
+ /**
3
+ * Pulumi schema descriptions embed per-language text as HTML-ish spans, e.g.
4
+ * `<span pulumi-lang-nodejs="`length`" pulumi-lang-python="`length`" ...>`length`</span>`.
5
+ * pulumi.com's docs site picks an attribute based on the selected language tab;
6
+ * we don't have tabs, so we resolve each span to the project's own runtime up
7
+ * front and strip the markup rather than rendering (or leaking) raw HTML.
8
+ */
9
+ var LANG_SPAN_RE = /<span\s+((?:pulumi-lang-[a-z]+="[^"]*"\s*)+)>([\s\S]*?)<\/span>/g;
10
+ var LANG_ATTR_RE = /pulumi-lang-([a-z]+)="([^"]*)"/g;
11
+ function resolveLangSpans(text, runtime) {
12
+ return text.replace(LANG_SPAN_RE, (_match, attrsSource, fallback) => {
13
+ if (!runtime) return fallback;
14
+ LANG_ATTR_RE.lastIndex = 0;
15
+ let match;
16
+ while (match = LANG_ATTR_RE.exec(attrsSource)) if (match[1] === runtime) return match[2];
17
+ return fallback;
18
+ });
19
+ }
20
+ /** Resolves a schema description's per-language spans for the given Pulumi runtime. */
21
+ function resolveDescription(text, runtime) {
22
+ if (!text) return text;
23
+ return resolveLangSpans(text, runtime);
24
+ }
25
+ //#endregion
26
+ //#region src/lib/pulumi/examples.ts
27
+ var CODE_FENCE_RE = /```([a-zA-Z0-9+#-]+)\n([\s\S]*?)```/g;
28
+ function extractExamples(text) {
29
+ if (!text) return [];
30
+ return [...text.matchAll(CODE_FENCE_RE)].map((match) => ({
31
+ language: match[1].toLowerCase(),
32
+ code: match[2].trimEnd()
33
+ }));
34
+ }
35
+ /** The description text with any fenced code examples removed. */
36
+ function stripExamples(text) {
37
+ if (!text) return text;
38
+ return text.replace(CODE_FENCE_RE, "").trim() || void 0;
39
+ }
40
+ /** A short summary for list views: the first prose paragraph, examples excluded. */
41
+ function summarizeDescription(text) {
42
+ const stripped = stripExamples(text);
43
+ if (!stripped) return void 0;
44
+ return stripped.split(/\n{2,}/)[0]?.trim() || void 0;
45
+ }
46
+ var RUNTIME_LANGUAGES = {
47
+ nodejs: ["typescript", "javascript"],
48
+ python: ["python"],
49
+ go: ["go", "golang"],
50
+ dotnet: ["csharp"],
51
+ java: ["java"],
52
+ yaml: ["yaml"]
53
+ };
54
+ /** Picks the example matching the project's runtime, falling back to the first available one. */
55
+ function pickExample(examples, runtime) {
56
+ const languages = runtime ? RUNTIME_LANGUAGES[runtime] : void 0;
57
+ return (languages ? examples.find((example) => languages.includes(example.language)) : void 0) ?? examples[0];
58
+ }
59
+ //#endregion
60
+ //#region src/lib/pulumi/token.ts
61
+ /**
62
+ * Schema tokens look like `random:index/randomString:RandomString` (resources)
63
+ * or `random:index/randomInteger:randomInteger` (functions). The segment after
64
+ * the last colon is the name every generated SDK exports for that member, and
65
+ * is unique within the provider, so it makes a readable display name.
66
+ */
67
+ function tokenDisplayName(token) {
68
+ const short = token.split(":").pop();
69
+ return short && short.length > 0 ? short : token;
70
+ }
71
+ /**
72
+ * A URL- and filesystem-safe form of a token, used as the `$token` route param.
73
+ *
74
+ * Tokens contain `:` and `/`, which only survive a URL as percent-escapes —
75
+ * and static hosts (GitHub/GitLab Pages) decode those before matching a file,
76
+ * so `random%3Aindex%2FrandomString%3ARandomString` would never resolve to the
77
+ * page generated for it. Replacing the separators keeps one URL shape that
78
+ * works both when served dynamically and as prerendered files on disk.
79
+ */
80
+ function tokenSlug(token) {
81
+ return token.replace(/[^a-zA-Z0-9._~-]+/g, "-");
82
+ }
83
+ /**
84
+ * Resolves a `$token` route param back to the schema token it names. Accepts
85
+ * either a slug (what links produce) or the raw token, so a URL pasted from
86
+ * schema output still works.
87
+ */
88
+ function findTokenBySlug(tokens, slug) {
89
+ for (const token of tokens) if (token === slug || tokenSlug(token) === slug) return token;
90
+ }
91
+ //#endregion
92
+ export { pickExample as a, resolveDescription as c, extractExamples as i, tokenDisplayName as n, stripExamples as o, tokenSlug as r, summarizeDescription as s, findTokenBySlug as t };
@@ -91,7 +91,7 @@ var HEADERS = { TSS_SHELL: "X-TSS_SHELL" };
91
91
  * the dev styles URL for route-scoped CSS collection.
92
92
  */
93
93
  async function getStartManifest(matchedRoutes) {
94
- const { tsrStartManifest } = await import("./assets/_tanstack-start-manifest_v-B0J-S-em.js");
94
+ const { tsrStartManifest } = await import("./assets/_tanstack-start-manifest_v-DNIp6gp3.js");
95
95
  const startManifest = tsrStartManifest();
96
96
  let routes = startManifest.routes;
97
97
  routes[rootRouteId];
@@ -115,11 +115,11 @@ async function getStartManifest(matchedRoutes) {
115
115
  var manifest = {
116
116
  "20c32dac0b2237041a59da458130ed4f320ce92dddd59a28b88581987c58d61d": {
117
117
  functionName: "getProvider_createServerFn_handler",
118
- importer: () => import("./assets/api-C55bS4R6.js")
118
+ importer: () => import("./assets/api-D49ZDhMh.js")
119
119
  },
120
120
  "a3ba03544bae9a67523c84875835e6e9b6dfa1b22a99b2c4c57d7ae25dbf7350": {
121
121
  functionName: "listProviders_createServerFn_handler",
122
- importer: () => import("./assets/api-C55bS4R6.js")
122
+ importer: () => import("./assets/api-D49ZDhMh.js")
123
123
  }
124
124
  };
125
125
  async function getServerFnById(id, access) {
@@ -1547,7 +1547,7 @@ var getBaseManifest = getProdBaseManifest;
1547
1547
  var createEarlyHintsForRequest = createEarlyHintsCollector;
1548
1548
  async function loadEntries() {
1549
1549
  const [routerEntry, startEntry, pluginAdapters] = await Promise.all([
1550
- import("./assets/router-1INob9fh.js").then((n) => n.t),
1550
+ import("./assets/router-_zs8P-B2.js").then((n) => n.t),
1551
1551
  import("./assets/start-5Z2QO8AU.js"),
1552
1552
  import("./assets/empty-plugin-adapters-D9UWiqvJ.js")
1553
1553
  ]);
@@ -0,0 +1,56 @@
1
+ # Publishes the local provider docs for this Pulumi project to GitHub Pages.
2
+ # Copy to .github/workflows/docs.yml in your Pulumi project's repo, then enable
3
+ # Pages for the repo with "Source: GitHub Actions" (Settings -> Pages).
4
+ name: Docs
5
+
6
+ on:
7
+ push:
8
+ branches: [main]
9
+ workflow_dispatch:
10
+
11
+ permissions:
12
+ contents: read
13
+ pages: write
14
+ id-token: write
15
+
16
+ # One deployment at a time; don't cancel one that's already publishing.
17
+ concurrency:
18
+ group: pages
19
+ cancel-in-progress: false
20
+
21
+ jobs:
22
+ build:
23
+ runs-on: ubuntu-latest
24
+ steps:
25
+ - uses: actions/checkout@v7
26
+
27
+ - uses: actions/setup-node@v7
28
+ with:
29
+ node-version: 24
30
+
31
+ # Puts the `pulumi` CLI on PATH; the docs are generated from the schemas
32
+ # it fetches for each package in Pulumi.yaml.
33
+ - uses: pulumi/actions@v7
34
+
35
+ # A project site is served from https://<owner>.github.io/<repo>/, so the
36
+ # site is built for that path. For a user/org site (<owner>.github.io),
37
+ # drop --base.
38
+ - name: Generate docs
39
+ run: npx -y pulumi-package-docs static --out ./site --base "/${GITHUB_REPOSITORY#*/}/"
40
+
41
+ - uses: actions/upload-pages-artifact@v5
42
+ with:
43
+ path: ./site
44
+ # The generated site includes a `.nojekyll` file, and this action
45
+ # drops dotfiles unless asked not to.
46
+ include-hidden-files: true
47
+
48
+ deploy:
49
+ needs: build
50
+ runs-on: ubuntu-latest
51
+ environment:
52
+ name: github-pages
53
+ url: ${{ steps.deployment.outputs.page_url }}
54
+ steps:
55
+ - id: deployment
56
+ uses: actions/deploy-pages@v5
@@ -0,0 +1,22 @@
1
+ # Publishes the local provider docs for this Pulumi project to GitLab Pages.
2
+ # Copy this job into your Pulumi project's .gitlab-ci.yml.
3
+ pages:
4
+ image: node:24
5
+ stage: deploy
6
+ before_script:
7
+ # Puts the `pulumi` CLI on PATH; the docs are generated from the schemas
8
+ # it fetches for each package in Pulumi.yaml.
9
+ - curl -fsSL https://get.pulumi.com | sh
10
+ - export PATH="$PATH:$HOME/.pulumi/bin"
11
+ script:
12
+ # CI_PAGES_URL is where this site will be served from — its path is what
13
+ # the docs have to be built for ("/" with unique domains enabled, "/<project>/"
14
+ # otherwise).
15
+ - export DOCS_BASE="$(node -e 'console.log(new URL(process.env.CI_PAGES_URL).pathname)')"
16
+ - npx -y pulumi-package-docs static --out public --base "$DOCS_BASE"
17
+ artifacts:
18
+ paths:
19
+ # GitLab Pages publishes this directory, and only under this name.
20
+ - public
21
+ rules:
22
+ - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pulumi-package-docs",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "Local documentation viewer for the parameterized/local Pulumi providers used by a Pulumi project.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -18,6 +18,7 @@
18
18
  "files": [
19
19
  "bin",
20
20
  "dist",
21
+ "examples",
21
22
  "src",
22
23
  "tsconfig.json",
23
24
  "vite.config.ts"
@@ -28,11 +29,14 @@
28
29
  "scripts": {
29
30
  "dev": "vite dev --port 3000",
30
31
  "generate-routes": "tsr generate",
31
- "build": "vite build",
32
+ "build": "vite build && npm run build:mcp",
33
+ "build:mcp": "vite build --config vite.mcp.config.ts",
32
34
  "preview": "vite preview",
33
35
  "start": "node bin/cli.js",
36
+ "static": "node bin/cli.js static",
34
37
  "compile:bun": "node bin/generate-embedded-assets.js && bun build ./bin/cli-bun.js --compile --outfile pulumi-package-docs",
35
38
  "test": "vitest run",
39
+ "test:stdio": "node scripts/smoke-test-stdio.js node bin/cli.js --stdio && node scripts/smoke-test-stdio.js ./pulumi-package-docs --stdio",
36
40
  "test:watch": "vitest",
37
41
  "lint": "oxlint",
38
42
  "format": "oxfmt . && oxlint --fix",
@@ -119,3 +119,40 @@ function MonitorIcon() {
119
119
  </IconSvg>
120
120
  )
121
121
  }
122
+
123
+ // Wires the static build's theme switch up without hydration: one delegated
124
+ // click listener, using the same `data-theme` + localStorage contract as
125
+ // ThemeToggle above (and as the pre-hydration script in `__root.tsx`).
126
+ const staticThemeScript = `(function(){var g=document.currentScript.previousElementSibling;if(!g)return;
127
+ function apply(m){try{if(m==='system'){delete document.documentElement.dataset.theme;localStorage.removeItem('theme')}else{document.documentElement.dataset.theme=m;localStorage.setItem('theme',m)}}catch(e){if(m==='system'){delete document.documentElement.dataset.theme}else{document.documentElement.dataset.theme=m}}sync(m)}
128
+ function sync(m){var b=g.querySelectorAll('button[data-theme-mode]');for(var i=0;i<b.length;i++){b[i].setAttribute('aria-pressed',String(b[i].getAttribute('data-theme-mode')===m))}}
129
+ g.addEventListener('click',function(e){var b=e.target.closest('button[data-theme-mode]');if(b){apply(b.getAttribute('data-theme-mode'))}});
130
+ var s=null;try{s=localStorage.getItem('theme')}catch(e){}
131
+ sync(s==='light'||s==='dark'?s:'system')})()`
132
+
133
+ /**
134
+ * The theme switch for the static build. Same markup as `ThemeToggle`, but
135
+ * driven by an inline script rather than React, since static pages ship
136
+ * without the hydration bundle.
137
+ */
138
+ export function StaticThemeToggle() {
139
+ return (
140
+ <>
141
+ <div className="theme-switch" role="group" aria-label="Theme">
142
+ {OPTIONS.map(({ mode, label, Icon }) => (
143
+ <button
144
+ key={mode}
145
+ type="button"
146
+ className="theme-switch-option"
147
+ data-theme-mode={mode}
148
+ aria-pressed={mode === 'system'}
149
+ aria-label={label}
150
+ >
151
+ <Icon />
152
+ </button>
153
+ ))}
154
+ </div>
155
+ <script dangerouslySetInnerHTML={{ __html: staticThemeScript }} />
156
+ </>
157
+ )
158
+ }
@@ -1,77 +1,26 @@
1
1
  import { createServerFn } from '@tanstack/react-start'
2
2
 
3
- import { getTargetDir, loadPulumiProject } from '@/lib/pulumi/discovery.server'
4
- import { resolveProviderSchema } from '@/lib/pulumi/schema.server'
5
- import type { ProviderEntry, PulumiProject } from '@/lib/pulumi/types'
6
-
7
- export interface ProviderSummary {
8
- name: string
9
- source: string
10
- version?: string
11
- parameters?: string[]
12
- resourceCount: number
13
- functionCount: number
14
- origin: ProviderEntry['origin']
15
- error?: string
16
- }
17
-
18
- export interface ProjectSummary {
19
- targetDir: string
20
- project: Pick<PulumiProject, 'root' | 'name' | 'runtime'> | null
21
- providers: ProviderSummary[]
22
- }
3
+ import {
4
+ loadProjectSummary,
5
+ loadProviderDetail,
6
+ } from '@/lib/pulumi/providers.server'
7
+ import type {
8
+ ProjectSummary,
9
+ ProviderDetail,
10
+ } from '@/lib/pulumi/providers.server'
11
+
12
+ export type {
13
+ ProjectSummary,
14
+ ProviderDetail,
15
+ ProviderSummary,
16
+ } from '@/lib/pulumi/providers.server'
23
17
 
24
18
  export const listProviders = createServerFn({ method: 'GET' }).handler(
25
- async (): Promise<ProjectSummary> => {
26
- const targetDir = getTargetDir()
27
- const project = loadPulumiProject(targetDir)
28
-
29
- if (!project) {
30
- return { targetDir, project: null, providers: [] }
31
- }
32
-
33
- const entries = await Promise.all(
34
- project.packages.map((ref) => resolveProviderSchema(project.root, ref)),
35
- )
36
-
37
- const providers: ProviderSummary[] = entries.map((entry) => ({
38
- name: entry.ref.name,
39
- source: entry.ref.source,
40
- version: entry.ref.version,
41
- parameters: entry.ref.parameters,
42
- resourceCount: Object.keys(entry.schema?.resources ?? {}).length,
43
- functionCount: Object.keys(entry.schema?.functions ?? {}).length,
44
- origin: entry.origin,
45
- error: entry.error,
46
- }))
47
-
48
- return {
49
- targetDir,
50
- project: {
51
- root: project.root,
52
- name: project.name,
53
- runtime: project.runtime,
54
- },
55
- providers,
56
- }
57
- },
19
+ (): Promise<ProjectSummary> => loadProjectSummary(),
58
20
  )
59
21
 
60
- export interface ProviderDetail extends ProviderEntry {
61
- /** The Pulumi project's runtime (e.g. "nodejs", "python", "go"), used to render Inputs/Outputs names in that language's casing. */
62
- runtime?: string
63
- }
64
-
65
22
  export const getProvider = createServerFn({ method: 'GET' })
66
23
  .validator((name: string) => name)
67
- .handler(async ({ data: name }): Promise<ProviderDetail | null> => {
68
- const targetDir = getTargetDir()
69
- const project = loadPulumiProject(targetDir)
70
- if (!project) return null
71
-
72
- const ref = project.packages.find((p) => p.name === name)
73
- if (!ref) return null
74
-
75
- const entry = await resolveProviderSchema(project.root, ref)
76
- return { ...entry, runtime: project.runtime }
77
- })
24
+ .handler(({ data: name }): Promise<ProviderDetail | null> =>
25
+ loadProviderDetail(name),
26
+ )