pulumi-package-docs 0.8.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
@@ -0,0 +1 @@
1
+ import{o as e}from"./useSelector-BIt42tGI.js";import{s as t}from"./index-DM9VsIdh.js";var n=e(),r=()=>(0,n.jsx)(t,{});export{r as component};
@@ -0,0 +1 @@
1
+ import{o as e}from"./useSelector-BIt42tGI.js";import{c as t}from"./index-DM9VsIdh.js";var n=e(),r=()=>(0,n.jsx)(`main`,{children:(0,n.jsxs)(`p`,{children:[`No function with that name was found.`,` `,(0,n.jsx)(t,{to:`/`,children:`Back to all providers.`})]})});export{r as notFoundComponent};
@@ -0,0 +1 @@
1
+ import{o as e}from"./useSelector-BIt42tGI.js";import{n as t,r as n}from"./index-DM9VsIdh.js";import{t as r}from"./MemberDetail-D8jkDkVP.js";var i=e();function a(){let{providerName:e,token:a,fn:o,runtime:s}=t.useLoaderData();return(0,i.jsx)(r,{providerName:e,kind:`Function`,displayName:n(a),token:a,description:o.description,runtime:s,inputProperties:o.inputs?.properties,requiredInputs:o.inputs?.required,outputProperties:o.outputs?.properties,requiredOutputs:o.outputs?.required})}export{a as component};
@@ -0,0 +1 @@
1
+ import{o as e}from"./useSelector-BIt42tGI.js";import{c as t}from"./index-DM9VsIdh.js";var n=e(),r=()=>(0,n.jsx)(`main`,{children:(0,n.jsxs)(`p`,{children:[`No local provider with that name was found.`,` `,(0,n.jsx)(t,{to:`/`,children:`Back to all providers.`})]})});export{r as notFoundComponent};
@@ -0,0 +1 @@
1
+ import{o as e}from"./useSelector-BIt42tGI.js";import{a as t,c as n,i as r,r as i}from"./index-DM9VsIdh.js";import{a,i as o}from"./examples-zkHFEaMt.js";var s=e();function c({token:e,member:t,runtime:c,to:l,providerName:u}){let d=o(a(t.description,c)),f=`deprecationMessage`in t&&t.deprecationMessage;return(0,s.jsxs)(`li`,{className:`doc-list-item`,children:[(0,s.jsx)(n,{to:l,params:{name:u,token:r(e)},children:(0,s.jsx)(`code`,{children:i(e)})}),f&&(0,s.jsx)(`span`,{className:`badge badge-warning`,children:`Deprecated`}),d&&(0,s.jsx)(`p`,{className:`doc-summary`,children:d})]})}function l(){let{ref:e,schema:r,origin:i,error:o,runtime:l}=t.useLoaderData(),u=Object.entries(r?.resources??{}),d=Object.entries(r?.functions??{});return(0,s.jsxs)(`main`,{className:`provider-detail`,children:[(0,s.jsx)(n,{to:`/`,className:`back-link`,children:`← All providers`}),(0,s.jsx)(`h1`,{children:e.name}),(0,s.jsxs)(`p`,{className:`subtitle`,children:[e.source,e.version?` @ ${e.version}`:``,e.parameters?.length?` (${e.parameters.join(` `)})`:``]}),i===`error`||!r?(0,s.jsxs)(`p`,{className:`banner banner-failure`,children:[`Failed to load schema: `,o??`unknown error`]}):(0,s.jsxs)(s.Fragment,{children:[i===`cache`&&(0,s.jsxs)(`p`,{className:`banner banner-info`,children:[`Showing a cached schema (the `,(0,s.jsx)(`code`,{children:`pulumi`}),` CLI call failed on this load).`]}),r.description&&(0,s.jsx)(`p`,{children:a(r.description,l)}),(0,s.jsx)(`h2`,{children:`Resources`}),u.length===0?(0,s.jsx)(`p`,{className:`empty-state`,children:`No resources.`}):(0,s.jsx)(`ul`,{className:`doc-list`,children:u.map(([t,n])=>(0,s.jsx)(c,{token:t,member:n,runtime:l,to:`/providers/$name/resources/$token`,providerName:e.name},t))}),(0,s.jsx)(`h2`,{children:`Functions`}),d.length===0?(0,s.jsx)(`p`,{className:`empty-state`,children:`No functions.`}):(0,s.jsx)(`ul`,{className:`doc-list`,children:d.map(([t,n])=>(0,s.jsx)(c,{token:t,member:n,runtime:l,to:`/providers/$name/functions/$token`,providerName:e.name},t))})]})]})}export{l as component};
@@ -0,0 +1 @@
1
+ import{o as e}from"./useSelector-BIt42tGI.js";import{r as t,t as n}from"./index-DM9VsIdh.js";import{t as r}from"./MemberDetail-D8jkDkVP.js";var i=e();function a(){let{providerName:e,token:a,resource:o,runtime:s}=n.useLoaderData();return(0,i.jsx)(r,{providerName:e,kind:`Resource`,displayName:t(a),token:a,description:o.description,deprecationMessage:o.deprecationMessage,runtime:s,inputProperties:o.inputProperties,requiredInputs:o.requiredInputs,outputProperties:o.properties,requiredOutputs:o.required})}export{a as component};
@@ -0,0 +1 @@
1
+ import{o as e}from"./useSelector-BIt42tGI.js";import{c as t}from"./index-DM9VsIdh.js";var n=e(),r=()=>(0,n.jsx)(`main`,{children:(0,n.jsxs)(`p`,{children:[`No resource with that name was found.`,` `,(0,n.jsx)(t,{to:`/`,children:`Back to all providers.`})]})});export{r as notFoundComponent};
@@ -1 +1 @@
1
- import{o as e}from"./useSelector-BIt42tGI.js";import{i as t,o as n}from"./index-2FzxAhzN.js";var r=e();function i(){let{targetDir:e,project:i,providers:a}=t.useLoaderData();return i?(0,r.jsxs)(`main`,{children:[(0,r.jsx)(`h1`,{children:`Pulumi Package Docs`}),(0,r.jsxs)(`p`,{className:`subtitle`,children:[i.name??`Pulumi project`,` · `,(0,r.jsx)(`code`,{children:i.root})]}),a.length===0?(0,r.jsxs)(`p`,{className:`empty-state`,children:[`This project's `,(0,r.jsx)(`code`,{children:`Pulumi.yaml`}),` has no `,(0,r.jsx)(`code`,{children:`packages`}),` `,`section, so there are no local or parameterized providers to show docs for yet. Add one with `,(0,r.jsx)(`code`,{children:`pulumi package add`}),`.`]}):(0,r.jsx)(`ul`,{className:`provider-list`,children:a.map(e=>(0,r.jsxs)(`li`,{className:`provider-card`,children:[(0,r.jsxs)(n,{to:`/providers/$name`,params:{name:e.name},children:[(0,r.jsx)(`span`,{className:`provider-name`,children:e.name}),(0,r.jsxs)(`span`,{className:`provider-source`,children:[e.source,e.version?` @ ${e.version}`:``,e.parameters?.length?` (${e.parameters.join(` `)})`:``]})]}),e.origin===`error`?(0,r.jsxs)(`p`,{className:`banner banner-failure`,children:[`Failed to load schema: `,e.error]}):(0,r.jsxs)(`p`,{className:`provider-stats`,children:[e.resourceCount,` resource`,e.resourceCount===1?``:`s`,` · `,e.functionCount,` function`,e.functionCount===1?``:`s`,e.origin===`cache`&&(0,r.jsxs)(r.Fragment,{children:[` · `,(0,r.jsx)(`span`,{className:`badge badge-info`,children:`from cache`})]})]})]},e.name))})]}):(0,r.jsxs)(`main`,{children:[(0,r.jsx)(`h1`,{children:`Pulumi Package Docs`}),(0,r.jsxs)(`p`,{className:`empty-state`,children:[`No Pulumi project was found at `,(0,r.jsx)(`code`,{children:e}),` (or any parent directory). Run this from inside a Pulumi project directory, or with`,` `,(0,r.jsx)(`code`,{children:`--dir <path>`}),` pointing at one.`]})]})}export{i as component};
1
+ import{o as e}from"./useSelector-BIt42tGI.js";import{c as t,o as n}from"./index-DM9VsIdh.js";var r=e();function i(){let{targetDir:e,project:i,providers:a}=n.useLoaderData();return i?(0,r.jsxs)(`main`,{children:[(0,r.jsx)(`h1`,{children:`Pulumi Package Docs`}),(0,r.jsxs)(`p`,{className:`subtitle`,children:[i.name??`Pulumi project`,` · `,(0,r.jsx)(`code`,{children:i.root})]}),a.length===0?(0,r.jsxs)(`p`,{className:`empty-state`,children:[`This project's `,(0,r.jsx)(`code`,{children:`Pulumi.yaml`}),` has no `,(0,r.jsx)(`code`,{children:`packages`}),` `,`section, so there are no local or parameterized providers to show docs for yet. Add one with `,(0,r.jsx)(`code`,{children:`pulumi package add`}),`.`]}):(0,r.jsx)(`ul`,{className:`provider-list`,children:a.map(e=>(0,r.jsxs)(`li`,{className:`provider-card`,children:[(0,r.jsxs)(t,{to:`/providers/$name`,params:{name:e.name},children:[(0,r.jsx)(`span`,{className:`provider-name`,children:e.name}),(0,r.jsxs)(`span`,{className:`provider-source`,children:[e.source,e.version?` @ ${e.version}`:``,e.parameters?.length?` (${e.parameters.join(` `)})`:``]})]}),e.origin===`error`?(0,r.jsxs)(`p`,{className:`banner banner-failure`,children:[`Failed to load schema: `,e.error]}):(0,r.jsxs)(`p`,{className:`provider-stats`,children:[e.resourceCount,` resource`,e.resourceCount===1?``:`s`,` · `,e.functionCount,` function`,e.functionCount===1?``:`s`,e.origin===`cache`&&(0,r.jsxs)(r.Fragment,{children:[` · `,(0,r.jsx)(`span`,{className:`badge badge-info`,children:`from cache`})]})]})]},e.name))})]}):(0,r.jsxs)(`main`,{children:[(0,r.jsx)(`h1`,{children:`Pulumi Package Docs`}),(0,r.jsxs)(`p`,{className:`empty-state`,children:[`No Pulumi project was found at `,(0,r.jsx)(`code`,{children:e}),` (or any parent directory). Run this from inside a Pulumi project directory, or with`,` `,(0,r.jsx)(`code`,{children:`--dir <path>`}),` pointing at one.`]})]})}export{i as component};
@@ -0,0 +1,449 @@
1
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { z } from "zod";
4
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
+ import { dirname, join } from "node:path";
6
+ import { parse } from "yaml";
7
+ import { execFile } from "node:child_process";
8
+ import { createHash } from "node:crypto";
9
+ import { tmpdir } from "node:os";
10
+ import { promisify } from "node:util";
11
+ //#region src/lib/pulumi/description.ts
12
+ /**
13
+ * Pulumi schema descriptions embed per-language text as HTML-ish spans, e.g.
14
+ * `<span pulumi-lang-nodejs="`length`" pulumi-lang-python="`length`" ...>`length`</span>`.
15
+ * pulumi.com's docs site picks an attribute based on the selected language tab;
16
+ * we don't have tabs, so we resolve each span to the project's own runtime up
17
+ * front and strip the markup rather than rendering (or leaking) raw HTML.
18
+ */
19
+ var LANG_SPAN_RE = /<span\s+((?:pulumi-lang-[a-z]+="[^"]*"\s*)+)>([\s\S]*?)<\/span>/g;
20
+ var LANG_ATTR_RE = /pulumi-lang-([a-z]+)="([^"]*)"/g;
21
+ function resolveLangSpans(text, runtime) {
22
+ return text.replace(LANG_SPAN_RE, (_match, attrsSource, fallback) => {
23
+ if (!runtime) return fallback;
24
+ LANG_ATTR_RE.lastIndex = 0;
25
+ let match;
26
+ while (match = LANG_ATTR_RE.exec(attrsSource)) if (match[1] === runtime) return match[2];
27
+ return fallback;
28
+ });
29
+ }
30
+ /** Resolves a schema description's per-language spans for the given Pulumi runtime. */
31
+ function resolveDescription(text, runtime) {
32
+ if (!text) return text;
33
+ return resolveLangSpans(text, runtime);
34
+ }
35
+ //#endregion
36
+ //#region src/lib/pulumi/discovery.server.ts
37
+ var PROJECT_FILE_NAMES = ["Pulumi.yaml", "Pulumi.yml"];
38
+ /**
39
+ * The directory of the Pulumi program to inspect. `bin/cli.js` sets this to the
40
+ * directory the user ran `npx pulumi-package-docs` from, since the dev/preview
41
+ * server itself runs with this package's own directory as its cwd.
42
+ */
43
+ function getTargetDir() {
44
+ return process.env.PULUMI_LOCAL_DOCS_DIR || process.cwd();
45
+ }
46
+ function findProjectFile(startDir) {
47
+ let dir = startDir;
48
+ for (;;) {
49
+ for (const fileName of PROJECT_FILE_NAMES) {
50
+ const candidate = join(dir, fileName);
51
+ if (existsSync(candidate)) return candidate;
52
+ }
53
+ const parent = dirname(dir);
54
+ if (parent === dir) return null;
55
+ dir = parent;
56
+ }
57
+ }
58
+ function toLocalPackageRefs(packages) {
59
+ if (!packages || typeof packages !== "object") return [];
60
+ return Object.entries(packages).map(([name, value]) => {
61
+ const entry = value ?? {};
62
+ const parameters = Array.isArray(entry.parameters) ? entry.parameters.filter((p) => typeof p === "string") : void 0;
63
+ return {
64
+ name,
65
+ source: typeof entry.source === "string" ? entry.source : name,
66
+ version: typeof entry.version === "string" ? entry.version : void 0,
67
+ parameters
68
+ };
69
+ });
70
+ }
71
+ /**
72
+ * Walks up from `startDir` looking for a Pulumi.yaml/Pulumi.yml, then reads its
73
+ * `packages` section to find the local/parameterized providers used by the project.
74
+ */
75
+ function loadPulumiProject(startDir = getTargetDir()) {
76
+ const projectFile = findProjectFile(startDir);
77
+ if (!projectFile) return null;
78
+ const raw = readFileSync(projectFile, "utf-8");
79
+ const doc = parse(raw);
80
+ return {
81
+ root: dirname(projectFile),
82
+ name: typeof doc?.name === "string" ? doc.name : void 0,
83
+ runtime: typeof doc?.runtime === "string" ? doc.runtime : typeof (doc?.runtime)?.name === "string" ? doc.runtime.name : void 0,
84
+ packages: toLocalPackageRefs(doc?.packages)
85
+ };
86
+ }
87
+ //#endregion
88
+ //#region src/lib/pulumi/examples.ts
89
+ var CODE_FENCE_RE = /```([a-zA-Z0-9+#-]+)\n([\s\S]*?)```/g;
90
+ function extractExamples(text) {
91
+ if (!text) return [];
92
+ return [...text.matchAll(CODE_FENCE_RE)].map((match) => ({
93
+ language: match[1].toLowerCase(),
94
+ code: match[2].trimEnd()
95
+ }));
96
+ }
97
+ /** The description text with any fenced code examples removed. */
98
+ function stripExamples(text) {
99
+ if (!text) return text;
100
+ return text.replace(CODE_FENCE_RE, "").trim() || void 0;
101
+ }
102
+ /** A short summary for list views: the first prose paragraph, examples excluded. */
103
+ function summarizeDescription(text) {
104
+ const stripped = stripExamples(text);
105
+ if (!stripped) return void 0;
106
+ return stripped.split(/\n{2,}/)[0]?.trim() || void 0;
107
+ }
108
+ var RUNTIME_LANGUAGES = {
109
+ nodejs: ["typescript", "javascript"],
110
+ python: ["python"],
111
+ go: ["go", "golang"],
112
+ dotnet: ["csharp"],
113
+ java: ["java"],
114
+ yaml: ["yaml"]
115
+ };
116
+ /** Picks the example matching the project's runtime, falling back to the first available one. */
117
+ function pickExample(examples, runtime) {
118
+ const languages = runtime ? RUNTIME_LANGUAGES[runtime] : void 0;
119
+ return (languages ? examples.find((example) => languages.includes(example.language)) : void 0) ?? examples[0];
120
+ }
121
+ //#endregion
122
+ //#region src/lib/pulumi/format.ts
123
+ function formatType(prop) {
124
+ if (!prop) return "any";
125
+ if (prop.$ref) {
126
+ const short = prop.$ref.split("/").pop() ?? prop.$ref;
127
+ return decodeURIComponent(short);
128
+ }
129
+ if (prop.type === "array" && prop.items) return `${formatType(prop.items)}[]`;
130
+ return prop.type ?? "any";
131
+ }
132
+ //#endregion
133
+ //#region src/lib/pulumi/naming.ts
134
+ /**
135
+ * Pulumi schema property names are always camelCase; each language's generated
136
+ * SDK renders them in that language's own convention. This approximates that
137
+ * per-runtime rendering for display purposes (it doesn't reproduce Pulumi's
138
+ * codegen exactly, e.g. its acronym handling, but matches the common case).
139
+ */
140
+ function toSnakeCase(name) {
141
+ return name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").toLowerCase();
142
+ }
143
+ function toPascalCase(name) {
144
+ if (name.length === 0) return name;
145
+ return name.charAt(0).toUpperCase() + name.slice(1);
146
+ }
147
+ /** Renders a schema property name the way it would appear in the given Pulumi runtime's generated SDK. */
148
+ function formatPropertyName(name, runtime) {
149
+ switch (runtime) {
150
+ case "python": return toSnakeCase(name);
151
+ case "go":
152
+ case "dotnet": return toPascalCase(name);
153
+ default: return name;
154
+ }
155
+ }
156
+ //#endregion
157
+ //#region src/lib/pulumi/schema.server.ts
158
+ var execFileAsync = promisify(execFile);
159
+ var CLI_TIMEOUT_MS = 6e4;
160
+ var MAX_BUFFER_BYTES = 67108864;
161
+ function cacheFilePath(projectRoot, ref) {
162
+ const projectHash = createHash("sha256").update(projectRoot).digest("hex").slice(0, 16);
163
+ const dir = join(tmpdir(), "pulumi-package-docs", projectHash);
164
+ mkdirSync(dir, { recursive: true });
165
+ return join(dir, `${ref.name}.json`);
166
+ }
167
+ function readCache(path) {
168
+ if (!existsSync(path)) return null;
169
+ try {
170
+ return JSON.parse(readFileSync(path, "utf-8"));
171
+ } catch {
172
+ return null;
173
+ }
174
+ }
175
+ async function fetchViaCli(projectRoot, ref) {
176
+ const { stdout } = await execFileAsync("pulumi", [
177
+ "package",
178
+ "get-schema",
179
+ ref.source,
180
+ ...ref.parameters ?? []
181
+ ], {
182
+ cwd: projectRoot,
183
+ timeout: CLI_TIMEOUT_MS,
184
+ maxBuffer: MAX_BUFFER_BYTES
185
+ });
186
+ return JSON.parse(stdout);
187
+ }
188
+ /**
189
+ * Resolves the schema for a local package: try the `pulumi` CLI first (it's the
190
+ * source of truth and works for parameterized providers), and fall back to the
191
+ * last schema we successfully cached for this project if the CLI call fails.
192
+ */
193
+ async function resolveProviderSchema(projectRoot, ref) {
194
+ const cachePath = cacheFilePath(projectRoot, ref);
195
+ try {
196
+ const schema = await fetchViaCli(projectRoot, ref);
197
+ writeFileSync(cachePath, JSON.stringify(schema));
198
+ return {
199
+ ref,
200
+ schema,
201
+ origin: "cli"
202
+ };
203
+ } catch (cliError) {
204
+ const cached = readCache(cachePath);
205
+ if (cached) return {
206
+ ref,
207
+ schema: cached,
208
+ origin: "cache"
209
+ };
210
+ return {
211
+ ref,
212
+ schema: null,
213
+ origin: "error",
214
+ error: cliError instanceof Error ? cliError.message : String(cliError)
215
+ };
216
+ }
217
+ }
218
+ //#endregion
219
+ //#region src/lib/pulumi/providers.server.ts
220
+ /**
221
+ * The project + provider summaries shown on the index route and by the MCP
222
+ * `list_providers` tool. Plain async functions (no TanStack Start server-fn
223
+ * wrapper) so they can run either inside the web server or in the stdio MCP
224
+ * process, which has no HTTP server at all.
225
+ */
226
+ async function loadProjectSummary() {
227
+ const targetDir = getTargetDir();
228
+ const project = loadPulumiProject(targetDir);
229
+ if (!project) return {
230
+ targetDir,
231
+ project: null,
232
+ providers: []
233
+ };
234
+ const providers = (await Promise.all(project.packages.map((ref) => resolveProviderSchema(project.root, ref)))).map((entry) => ({
235
+ name: entry.ref.name,
236
+ source: entry.ref.source,
237
+ version: entry.ref.version,
238
+ parameters: entry.ref.parameters,
239
+ resourceCount: Object.keys(entry.schema?.resources ?? {}).length,
240
+ functionCount: Object.keys(entry.schema?.functions ?? {}).length,
241
+ origin: entry.origin,
242
+ error: entry.error
243
+ }));
244
+ return {
245
+ targetDir,
246
+ project: {
247
+ root: project.root,
248
+ name: project.name,
249
+ runtime: project.runtime
250
+ },
251
+ providers
252
+ };
253
+ }
254
+ /** The fully resolved schema for one local provider, or null if the project or provider isn't found. */
255
+ async function loadProviderDetail(name) {
256
+ const project = loadPulumiProject(getTargetDir());
257
+ if (!project) return null;
258
+ const ref = project.packages.find((p) => p.name === name);
259
+ if (!ref) return null;
260
+ return {
261
+ ...await resolveProviderSchema(project.root, ref),
262
+ runtime: project.runtime
263
+ };
264
+ }
265
+ //#endregion
266
+ //#region src/lib/pulumi/token.ts
267
+ /**
268
+ * Schema tokens look like `random:index/randomString:RandomString` (resources)
269
+ * or `random:index/randomInteger:randomInteger` (functions). The segment after
270
+ * the last colon is the name every generated SDK exports for that member, and
271
+ * is unique within the provider, so it makes a readable display name.
272
+ */
273
+ function tokenDisplayName(token) {
274
+ const short = token.split(":").pop();
275
+ return short && short.length > 0 ? short : token;
276
+ }
277
+ //#endregion
278
+ //#region src/lib/pulumi/mcp.server.ts
279
+ function textResult(data) {
280
+ return { content: [{
281
+ type: "text",
282
+ text: JSON.stringify(data, null, 2)
283
+ }] };
284
+ }
285
+ /** Loads a local provider's resolved schema, throwing a descriptive error a tool call can surface to the client. */
286
+ async function requireProvider(providerName) {
287
+ const detail = await loadProviderDetail(providerName);
288
+ 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.`);
289
+ if (!detail.schema) throw new Error(`Failed to load the schema for provider "${providerName}": ${detail.error ?? "unknown error"}`);
290
+ return {
291
+ ...detail,
292
+ schema: detail.schema
293
+ };
294
+ }
295
+ function summarizeMember(token, member, runtime) {
296
+ return {
297
+ token,
298
+ name: tokenDisplayName(token),
299
+ deprecated: Boolean("deprecationMessage" in member && member.deprecationMessage),
300
+ summary: summarizeDescription(resolveDescription(member.description, runtime))
301
+ };
302
+ }
303
+ function formatProperties(properties, required, runtime) {
304
+ const requiredSet = new Set(required ?? []);
305
+ return Object.entries(properties ?? {}).map(([name, prop]) => ({
306
+ name: formatPropertyName(name, runtime),
307
+ type: formatType(prop),
308
+ required: requiredSet.has(name),
309
+ description: resolveDescription(prop.description, runtime)
310
+ }));
311
+ }
312
+ function memberDetail(provider, token, description, runtime, inputs, outputs, deprecationMessage) {
313
+ const resolved = resolveDescription(description, runtime);
314
+ const example = pickExample(extractExamples(resolved), runtime);
315
+ return {
316
+ provider,
317
+ token,
318
+ name: tokenDisplayName(token),
319
+ deprecationMessage,
320
+ description: stripExamples(resolved),
321
+ example: example ?? null,
322
+ inputs,
323
+ outputs
324
+ };
325
+ }
326
+ /**
327
+ * Builds a fresh MCP server with the local-provider-docs tools registered. Transport
328
+ * agnostic: `src/routes/mcp.ts` calls it once per HTTP request (so concurrent requests
329
+ * never share state), and `src/lib/pulumi/stdio.server.ts` calls it once for the
330
+ * lifetime of a stdio process.
331
+ */
332
+ function createMcpServer() {
333
+ const server = new McpServer({
334
+ name: "pulumi-package-docs",
335
+ version: "0.1.0"
336
+ });
337
+ server.registerTool("list_providers", {
338
+ title: "List local providers",
339
+ 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."
340
+ }, async () => textResult(await loadProjectSummary()));
341
+ server.registerTool("list_resources", {
342
+ title: "List a provider’s resources",
343
+ 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.",
344
+ inputSchema: { provider: z.string().describe("The provider name as it appears in Pulumi.yaml and list_providers, e.g. \"random\".") }
345
+ }, async ({ provider }) => {
346
+ const detail = await requireProvider(provider);
347
+ const resources = Object.entries(detail.schema.resources ?? {}).map(([token, resource]) => summarizeMember(token, resource, detail.runtime));
348
+ return textResult({
349
+ provider,
350
+ resourceCount: resources.length,
351
+ resources
352
+ });
353
+ });
354
+ server.registerTool("list_functions", {
355
+ title: "List a provider’s functions",
356
+ description: "List the function (data source) tokens for a local provider, each with a display name, a one-line summary, and whether it is deprecated. Use get_function for full details (inputs/outputs/example) on one of them.",
357
+ inputSchema: { provider: z.string().describe("The provider name as it appears in Pulumi.yaml and list_providers, e.g. \"random\".") }
358
+ }, async ({ provider }) => {
359
+ const detail = await requireProvider(provider);
360
+ const functions = Object.entries(detail.schema.functions ?? {}).map(([token, fn]) => summarizeMember(token, fn, detail.runtime));
361
+ return textResult({
362
+ provider,
363
+ functionCount: functions.length,
364
+ functions
365
+ });
366
+ });
367
+ server.registerTool("get_resource", {
368
+ title: "Get resource details",
369
+ description: "Get the full documentation for one resource of a local provider: its description, a usage example (in the Pulumi project’s own language), and its input and output properties (name, type, required, description). Get the exact token from list_resources first.",
370
+ inputSchema: {
371
+ provider: z.string().describe("The provider name, e.g. \"random\"."),
372
+ token: z.string().describe("The resource’s full schema token from list_resources, e.g. \"random:index/randomString:RandomString\".")
373
+ }
374
+ }, async ({ provider, token }) => {
375
+ const detail = await requireProvider(provider);
376
+ const resource = detail.schema.resources?.[token];
377
+ if (!resource) throw new Error(`No resource with token "${token}" in provider "${provider}". Call list_resources to find the exact token.`);
378
+ return textResult(memberDetail(provider, token, resource.description, detail.runtime, formatProperties(resource.inputProperties, resource.requiredInputs, detail.runtime), formatProperties(resource.properties, resource.required, detail.runtime), resource.deprecationMessage));
379
+ });
380
+ server.registerTool("get_function", {
381
+ title: "Get function details",
382
+ description: "Get the full documentation for one function (data source) of a local provider: its description, a usage example (in the Pulumi project’s own language), and its input and output properties. Get the exact token from list_functions first.",
383
+ inputSchema: {
384
+ provider: z.string().describe("The provider name, e.g. \"random\"."),
385
+ token: z.string().describe("The function’s full schema token from list_functions, e.g. \"random:index/randomInteger:getRandomInteger\".")
386
+ }
387
+ }, async ({ provider, token }) => {
388
+ const detail = await requireProvider(provider);
389
+ const fn = detail.schema.functions?.[token];
390
+ if (!fn) throw new Error(`No function with token "${token}" in provider "${provider}". Call list_functions to find the exact token.`);
391
+ return textResult(memberDetail(provider, token, fn.description, detail.runtime, formatProperties(fn.inputs?.properties, fn.inputs?.required, detail.runtime), formatProperties(fn.outputs?.properties, fn.outputs?.required, detail.runtime)));
392
+ });
393
+ server.registerTool("search_members", {
394
+ title: "Search resources and functions",
395
+ description: "Search resource and function tokens/descriptions across one or all local providers for a keyword. Use this instead of list_resources/list_functions when a provider is large or you only know roughly what you’re looking for (e.g. \"bucket\", \"random string\").",
396
+ inputSchema: {
397
+ query: z.string().describe("Case-insensitive text to search for."),
398
+ provider: z.string().optional().describe("Limit the search to this provider name. Omit to search all local providers.")
399
+ }
400
+ }, async ({ query, provider }) => {
401
+ const project = loadPulumiProject(getTargetDir());
402
+ if (!project) return textResult({
403
+ query,
404
+ matchCount: 0,
405
+ matches: []
406
+ });
407
+ const refs = provider ? project.packages.filter((p) => p.name === provider) : project.packages;
408
+ if (provider && refs.length === 0) throw new Error(`No local provider named "${provider}" was found. Call list_providers to see the providers declared in this project's Pulumi.yaml.`);
409
+ const needle = query.toLowerCase();
410
+ const matches = [];
411
+ for (const ref of refs) {
412
+ const entry = await resolveProviderSchema(project.root, ref);
413
+ if (!entry.schema) continue;
414
+ const groups = [["resource", entry.schema.resources], ["function", entry.schema.functions]];
415
+ for (const [kind, members] of groups) for (const [token, member] of Object.entries(members ?? {})) {
416
+ const name = tokenDisplayName(token);
417
+ const summary = summarizeDescription(resolveDescription(member.description, project.runtime));
418
+ if (`${name} ${token} ${summary ?? ""}`.toLowerCase().includes(needle)) matches.push({
419
+ provider: ref.name,
420
+ kind,
421
+ token,
422
+ name,
423
+ summary
424
+ });
425
+ }
426
+ }
427
+ return textResult({
428
+ query,
429
+ matchCount: matches.length,
430
+ matches: matches.slice(0, 50)
431
+ });
432
+ });
433
+ return server;
434
+ }
435
+ //#endregion
436
+ //#region src/lib/pulumi/stdio.server.ts
437
+ /**
438
+ * Serves the same provider-docs tools as the `/mcp` HTTP route over stdio, for MCP
439
+ * clients that spawn a server process instead of connecting to a URL.
440
+ *
441
+ * Unlike the HTTP route this is a single long-lived server (one process, one client),
442
+ * and stdout is the protocol channel — nothing on this path may write to it, so any
443
+ * diagnostics belong on stderr.
444
+ */
445
+ async function runStdioMcpServer() {
446
+ await createMcpServer().connect(new StdioServerTransport());
447
+ }
448
+ //#endregion
449
+ export { runStdioMcpServer };
@@ -1,4 +1,5 @@
1
- import { c as extractExamples, f as resolveDescription, l as pickExample, o as formatPropertyName, s as formatType, u as stripExamples } from "./router-1INob9fh.js";
1
+ import { a as pickExample, c as resolveDescription, i as extractExamples, o as stripExamples } from "./token-DBEFeZIN.js";
2
+ import { a as formatPropertyName, o as formatType } from "./router-_zs8P-B2.js";
2
3
  import { Link } from "@tanstack/react-router";
3
4
  import { jsx, jsxs } from "react/jsx-runtime";
4
5
  //#region src/components/CodeExample.tsx
@@ -7,17 +7,17 @@ var tsrStartManifest = () => ({ routes: {
7
7
  "/mcp",
8
8
  "/providers/$name"
9
9
  ],
10
- preloads: ["/assets/index-2FzxAhzN.js", "/assets/useSelector-BIt42tGI.js"],
10
+ preloads: ["/assets/index-DM9VsIdh.js", "/assets/useSelector-BIt42tGI.js"],
11
11
  scripts: [{ attrs: {
12
12
  type: "module",
13
13
  async: !0,
14
- src: "/assets/index-2FzxAhzN.js"
14
+ src: "/assets/index-DM9VsIdh.js"
15
15
  } }]
16
16
  },
17
17
  "/": {
18
18
  filePath: "/home/runner/work/pulumi-package-docs/pulumi-package-docs/src/routes/index.tsx",
19
19
  children: void 0,
20
- preloads: ["/assets/routes-DKNWlgPZ.js"]
20
+ preloads: ["/assets/routes-DwhZQHE3.js"]
21
21
  },
22
22
  "/providers/$name": {
23
23
  filePath: "/home/runner/work/pulumi-package-docs/pulumi-package-docs/src/routes/providers.$name.tsx",
@@ -26,35 +26,33 @@ var tsrStartManifest = () => ({ routes: {
26
26
  "/providers/$name/functions/$token",
27
27
  "/providers/$name/resources/$token"
28
28
  ],
29
- preloads: ["/assets/providers._name-FmJEVkdV.js"]
29
+ preloads: ["/assets/providers._name-C-FSbmVZ.js"]
30
30
  },
31
31
  "/providers/$name/": {
32
32
  filePath: "/home/runner/work/pulumi-package-docs/pulumi-package-docs/src/routes/providers.$name.index.tsx",
33
33
  children: void 0,
34
34
  preloads: [
35
- "/assets/providers._name.index-C42yQ95z.js",
36
- "/assets/providers._name.index-Dm0YiZWP.js",
37
- "/assets/token-oQmZfAP3.js"
35
+ "/assets/providers._name.index-CWHPtoBD.js",
36
+ "/assets/providers._name.index-nlnQuJ9Q.js",
37
+ "/assets/examples-zkHFEaMt.js"
38
38
  ]
39
39
  },
40
40
  "/providers/$name/functions/$token": {
41
41
  filePath: "/home/runner/work/pulumi-package-docs/pulumi-package-docs/src/routes/providers.$name.functions.$token.tsx",
42
42
  children: void 0,
43
43
  preloads: [
44
- "/assets/providers._name.functions._token-BzjmNHea.js",
45
- "/assets/providers._name.functions._token-D768U6I-.js",
46
- "/assets/MemberDetail-BS9DbqEt.js",
47
- "/assets/token-oQmZfAP3.js"
44
+ "/assets/providers._name.functions._token-B3S8Gcfx.js",
45
+ "/assets/providers._name.functions._token-C4VPHGlG.js",
46
+ "/assets/MemberDetail-D8jkDkVP.js"
48
47
  ]
49
48
  },
50
49
  "/providers/$name/resources/$token": {
51
50
  filePath: "/home/runner/work/pulumi-package-docs/pulumi-package-docs/src/routes/providers.$name.resources.$token.tsx",
52
51
  children: void 0,
53
52
  preloads: [
54
- "/assets/providers._name.resources._token-CIYPoqyA.js",
55
- "/assets/MemberDetail-BS9DbqEt.js",
56
- "/assets/token-oQmZfAP3.js",
57
- "/assets/providers._name.resources._token-D0OLvgo_.js"
53
+ "/assets/providers._name.resources._token-BISpZVHI.js",
54
+ "/assets/MemberDetail-D8jkDkVP.js",
55
+ "/assets/providers._name.resources._token-CvMi94a3.js"
58
56
  ]
59
57
  }
60
58
  } });
@@ -0,0 +1,27 @@
1
+ import { n as TSS_SERVER_FUNCTION, t as createServerFn } from "../server.js";
2
+ import { n as loadProviderDetail, t as loadProjectSummary } from "./providers.server-B1ZHZ5jE.js";
3
+ //#region node_modules/@tanstack/start-server-core/dist/esm/createServerRpc.js
4
+ var createServerRpc = (serverFnMeta, splitImportFn) => {
5
+ const url = "/_serverFn/" + serverFnMeta.id;
6
+ return Object.assign(splitImportFn, {
7
+ url,
8
+ serverFnMeta,
9
+ [TSS_SERVER_FUNCTION]: true
10
+ });
11
+ };
12
+ //#endregion
13
+ //#region src/lib/pulumi/api.ts?tss-serverfn-split
14
+ var listProviders_createServerFn_handler = createServerRpc({
15
+ id: "a3ba03544bae9a67523c84875835e6e9b6dfa1b22a99b2c4c57d7ae25dbf7350",
16
+ name: "listProviders",
17
+ filename: "src/lib/pulumi/api.ts"
18
+ }, (opts) => listProviders.__executeServer(opts));
19
+ var listProviders = createServerFn({ method: "GET" }).handler(listProviders_createServerFn_handler, () => loadProjectSummary());
20
+ var getProvider_createServerFn_handler = createServerRpc({
21
+ id: "20c32dac0b2237041a59da458130ed4f320ce92dddd59a28b88581987c58d61d",
22
+ name: "getProvider",
23
+ filename: "src/lib/pulumi/api.ts"
24
+ }, (opts) => getProvider.__executeServer(opts));
25
+ var getProvider = createServerFn({ method: "GET" }).validator((name) => name).handler(getProvider_createServerFn_handler, ({ data: name }) => loadProviderDetail(name));
26
+ //#endregion
27
+ export { getProvider_createServerFn_handler, listProviders_createServerFn_handler };
@@ -1,5 +1,6 @@
1
- import { a as tokenDisplayName, r as Route } from "./router-1INob9fh.js";
2
- import { t as MemberDetail } from "./MemberDetail-C5sAHNZW.js";
1
+ import { n as tokenDisplayName } from "./token-DBEFeZIN.js";
2
+ import { r as Route } from "./router-_zs8P-B2.js";
3
+ import { t as MemberDetail } from "./MemberDetail-CdHUSmQD.js";
3
4
  import { jsx } from "react/jsx-runtime";
4
5
  //#region src/routes/providers.$name.functions.$token.tsx?tsr-split=component
5
6
  function FunctionDetail() {
@@ -1,4 +1,5 @@
1
- import { a as tokenDisplayName, d as summarizeDescription, f as resolveDescription, i as Route } from "./router-1INob9fh.js";
1
+ import { c as resolveDescription, n as tokenDisplayName, r as tokenSlug, s as summarizeDescription } from "./token-DBEFeZIN.js";
2
+ import { i as Route } from "./router-_zs8P-B2.js";
2
3
  import { Link } from "@tanstack/react-router";
3
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
5
  //#region src/routes/providers.$name.index.tsx?tsr-split=component
@@ -12,7 +13,7 @@ function MemberListItem({ token, member, runtime, to, providerName }) {
12
13
  to,
13
14
  params: {
14
15
  name: providerName,
15
- token
16
+ token: tokenSlug(token)
16
17
  },
17
18
  children: /* @__PURE__ */ jsx("code", { children: tokenDisplayName(token) })
18
19
  }),
@@ -1,5 +1,6 @@
1
- import { a as tokenDisplayName, n as Route } from "./router-1INob9fh.js";
2
- import { t as MemberDetail } from "./MemberDetail-C5sAHNZW.js";
1
+ import { n as tokenDisplayName } from "./token-DBEFeZIN.js";
2
+ import { n as Route } from "./router-_zs8P-B2.js";
3
+ import { t as MemberDetail } from "./MemberDetail-CdHUSmQD.js";
3
4
  import { jsx } from "react/jsx-runtime";
4
5
  //#region src/routes/providers.$name.resources.$token.tsx?tsr-split=component
5
6
  function ResourceDetail() {