sourcey 3.6.0 → 3.6.2

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 (86) hide show
  1. package/README.md +5 -2
  2. package/dist/adapters/index.d.ts +3 -2
  3. package/dist/adapters/index.d.ts.map +1 -1
  4. package/dist/adapters/index.js +35 -3
  5. package/dist/adapters/types.d.ts +8 -2
  6. package/dist/adapters/types.d.ts.map +1 -1
  7. package/dist/cli.js +28 -34
  8. package/dist/client/search.js +181 -119
  9. package/dist/client/tabs.js +23 -0
  10. package/dist/components/layout/Head.d.ts.map +1 -1
  11. package/dist/components/layout/Head.js +7 -6
  12. package/dist/components/openapi/Introduction.d.ts.map +1 -1
  13. package/dist/components/openapi/Introduction.js +16 -1
  14. package/dist/components/openapi/Security.d.ts.map +1 -1
  15. package/dist/components/openapi/Security.js +18 -2
  16. package/dist/config.d.ts +70 -0
  17. package/dist/config.d.ts.map +1 -1
  18. package/dist/config.js +23 -7
  19. package/dist/core/api-rendering.d.ts +47 -0
  20. package/dist/core/api-rendering.d.ts.map +1 -0
  21. package/dist/core/api-rendering.js +76 -0
  22. package/dist/core/doxygen-loader.d.ts +1 -1
  23. package/dist/core/doxygen-loader.d.ts.map +1 -1
  24. package/dist/core/doxygen-loader.js +37 -19
  25. package/dist/core/godoc-loader.d.ts.map +1 -1
  26. package/dist/core/godoc-loader.js +17 -6
  27. package/dist/core/markdown-loader.d.ts.map +1 -1
  28. package/dist/core/markdown-loader.js +8 -5
  29. package/dist/core/mcp-normalizer.d.ts.map +1 -1
  30. package/dist/core/mcp-normalizer.js +48 -34
  31. package/dist/core/parser.d.ts.map +1 -1
  32. package/dist/core/parser.js +75 -4
  33. package/dist/core/rustdoc-introspector.d.ts +29 -0
  34. package/dist/core/rustdoc-introspector.d.ts.map +1 -0
  35. package/dist/core/rustdoc-introspector.js +236 -0
  36. package/dist/core/rustdoc-loader.d.ts +25 -0
  37. package/dist/core/rustdoc-loader.d.ts.map +1 -0
  38. package/dist/core/rustdoc-loader.js +472 -0
  39. package/dist/core/rustdoc-render.d.ts +82 -0
  40. package/dist/core/rustdoc-render.d.ts.map +1 -0
  41. package/dist/core/rustdoc-render.js +808 -0
  42. package/dist/core/rustdoc-types.d.ts +314 -0
  43. package/dist/core/rustdoc-types.d.ts.map +1 -0
  44. package/dist/core/rustdoc-types.js +18 -0
  45. package/dist/core/search-indexer.js +1 -1
  46. package/dist/core/sourcey-rustdoc/Cargo.lock +705 -0
  47. package/dist/core/sourcey-rustdoc/Cargo.toml +38 -0
  48. package/dist/core/sourcey-rustdoc/README.md +59 -0
  49. package/dist/core/sourcey-rustdoc/src/diagnostics.rs +42 -0
  50. package/dist/core/sourcey-rustdoc/src/doctest.rs +268 -0
  51. package/dist/core/sourcey-rustdoc/src/extract.rs +492 -0
  52. package/dist/core/sourcey-rustdoc/src/lib.rs +58 -0
  53. package/dist/core/sourcey-rustdoc/src/links.rs +126 -0
  54. package/dist/core/sourcey-rustdoc/src/main.rs +256 -0
  55. package/dist/core/sourcey-rustdoc/src/signature.rs +415 -0
  56. package/dist/core/sourcey-rustdoc/src/spec.rs +390 -0
  57. package/dist/core/sourcey-rustdoc/tests/format_version.rs +45 -0
  58. package/dist/dev-server.d.ts.map +1 -1
  59. package/dist/dev-server.js +50 -1
  60. package/dist/index.d.ts +4 -2
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +22 -13
  63. package/dist/init.js +2 -2
  64. package/dist/renderer/changelog-feed.d.ts.map +1 -1
  65. package/dist/renderer/changelog-feed.js +27 -19
  66. package/dist/renderer/context.d.ts +1 -0
  67. package/dist/renderer/context.d.ts.map +1 -1
  68. package/dist/renderer/html-builder.d.ts.map +1 -1
  69. package/dist/renderer/html-builder.js +4 -3
  70. package/dist/renderer/llms.js +57 -2
  71. package/dist/site-assembly.d.ts +5 -0
  72. package/dist/site-assembly.d.ts.map +1 -1
  73. package/dist/site-assembly.js +57 -2
  74. package/dist/themes/default/sourcey.css +300 -0
  75. package/dist/utils/code-samples.d.ts +1 -0
  76. package/dist/utils/code-samples.d.ts.map +1 -1
  77. package/dist/utils/code-samples.js +30 -22
  78. package/dist/utils/html.d.ts +10 -0
  79. package/dist/utils/html.d.ts.map +1 -0
  80. package/dist/utils/html.js +152 -0
  81. package/dist/utils/http.d.ts +0 -2
  82. package/dist/utils/http.d.ts.map +1 -1
  83. package/dist/utils/http.js +19 -34
  84. package/dist/utils/markdown.d.ts.map +1 -1
  85. package/dist/utils/markdown.js +102 -37
  86. package/package.json +10 -5
@@ -1893,3 +1893,303 @@ h1[id], h2[id], h3[id], h4[id], h5[id], h6[id] {
1893
1893
  #sourcey .godoc-type .godoc-symbol {
1894
1894
  word-break: break-word;
1895
1895
  }
1896
+
1897
+ /* ── Rust API rendering (rustdoc adapter) ────────────────────────── */
1898
+ /* Shared API primitives (api-*) introduced for rustdoc and reusable
1899
+ by godoc/doxygen in a follow-on retrofit. Class names mirror
1900
+ rustdoc's DOM so deep-links from a rustdoc URL line up with
1901
+ sourcey output. */
1902
+
1903
+ #sourcey .rust-item {
1904
+ border-top: 1px solid rgb(var(--color-gray-200));
1905
+ padding-top: 1rem;
1906
+ margin-top: 1.25rem;
1907
+ }
1908
+ .dark #sourcey .rust-item {
1909
+ border-top-color: rgb(var(--color-gray-800));
1910
+ }
1911
+
1912
+ #sourcey .code-header,
1913
+ #sourcey .rust-signature {
1914
+ font-family: ui-monospace, "SF Mono", "Cascadia Code", Consolas, "Liberation Mono", Menlo, monospace;
1915
+ font-size: 0.9375rem;
1916
+ color: rgb(var(--color-gray-900));
1917
+ white-space: pre-wrap;
1918
+ word-break: break-word;
1919
+ }
1920
+ .dark #sourcey .code-header,
1921
+ .dark #sourcey .rust-signature {
1922
+ color: rgb(var(--color-gray-100));
1923
+ }
1924
+
1925
+ #sourcey .rust-signature .kw {
1926
+ color: rgb(var(--color-rose-600));
1927
+ font-weight: 600;
1928
+ }
1929
+ .dark #sourcey .rust-signature .kw {
1930
+ color: rgb(var(--color-rose-300));
1931
+ }
1932
+
1933
+ #sourcey .rust-signature .ident {
1934
+ color: rgb(var(--color-indigo-600));
1935
+ }
1936
+ .dark #sourcey .rust-signature .ident {
1937
+ color: rgb(var(--color-indigo-300));
1938
+ }
1939
+
1940
+ #sourcey .rust-signature .lifetime {
1941
+ color: rgb(var(--color-amber-700));
1942
+ font-style: italic;
1943
+ }
1944
+ .dark #sourcey .rust-signature .lifetime {
1945
+ color: rgb(var(--color-amber-300));
1946
+ }
1947
+
1948
+ #sourcey .api-rightside,
1949
+ #sourcey .rightside {
1950
+ float: right;
1951
+ font-size: 0.8125rem;
1952
+ color: rgb(var(--color-gray-500));
1953
+ }
1954
+
1955
+ #sourcey .api-since,
1956
+ #sourcey .since {
1957
+ font-feature-settings: "tnum";
1958
+ }
1959
+
1960
+ #sourcey .api-srclink,
1961
+ #sourcey .srclink {
1962
+ color: rgb(var(--color-indigo-600));
1963
+ text-decoration: none;
1964
+ }
1965
+ #sourcey .api-srclink:hover,
1966
+ #sourcey .srclink:hover {
1967
+ text-decoration: underline;
1968
+ }
1969
+ .dark #sourcey .api-srclink,
1970
+ .dark #sourcey .srclink {
1971
+ color: rgb(var(--color-indigo-300));
1972
+ }
1973
+
1974
+ #sourcey .docblock,
1975
+ #sourcey .rust-doc {
1976
+ color: rgb(var(--color-gray-700));
1977
+ margin-top: 0.5rem;
1978
+ }
1979
+ .dark #sourcey .docblock,
1980
+ .dark #sourcey .rust-doc {
1981
+ color: rgb(var(--color-gray-300));
1982
+ }
1983
+
1984
+ #sourcey .stab,
1985
+ #sourcey .api-stab {
1986
+ display: block;
1987
+ margin: 0.5rem 0;
1988
+ padding: 0.5rem 0.75rem;
1989
+ border-radius: 0.375rem;
1990
+ font-size: 0.875rem;
1991
+ border: 1px solid transparent;
1992
+ }
1993
+ #sourcey .stab.unstable,
1994
+ #sourcey .api-stab-unstable {
1995
+ background-color: rgb(var(--color-amber-50));
1996
+ border-color: rgb(var(--color-amber-200));
1997
+ color: rgb(var(--color-amber-900));
1998
+ }
1999
+ #sourcey .stab.deprecated,
2000
+ #sourcey .api-stab-deprecated {
2001
+ background-color: rgb(var(--color-rose-50));
2002
+ border-color: rgb(var(--color-rose-200));
2003
+ color: rgb(var(--color-rose-900));
2004
+ }
2005
+ #sourcey .stab.portability,
2006
+ #sourcey .api-stab-portability {
2007
+ background-color: rgb(var(--color-emerald-50));
2008
+ border-color: rgb(var(--color-emerald-200));
2009
+ color: rgb(var(--color-emerald-900));
2010
+ }
2011
+ .dark #sourcey .stab.unstable,
2012
+ .dark #sourcey .api-stab-unstable {
2013
+ background-color: rgba(251, 191, 36, 0.08);
2014
+ border-color: rgba(251, 191, 36, 0.25);
2015
+ color: rgb(var(--color-amber-200));
2016
+ }
2017
+ .dark #sourcey .stab.deprecated,
2018
+ .dark #sourcey .api-stab-deprecated {
2019
+ background-color: rgba(244, 63, 94, 0.08);
2020
+ border-color: rgba(244, 63, 94, 0.25);
2021
+ color: rgb(var(--color-rose-200));
2022
+ }
2023
+ .dark #sourcey .stab.portability,
2024
+ .dark #sourcey .api-stab-portability {
2025
+ background-color: rgba(16, 185, 129, 0.08);
2026
+ border-color: rgba(16, 185, 129, 0.25);
2027
+ color: rgb(var(--color-emerald-200));
2028
+ }
2029
+
2030
+ /* Collapsible trait-impl block. Chevron pattern mirrors .godoc-example so the
2031
+ disclosure reads the same across adapters. */
2032
+ #sourcey .api-toggle {
2033
+ margin: 0.75rem 0;
2034
+ border: 1px solid rgb(var(--color-gray-200));
2035
+ border-radius: 6px;
2036
+ background: rgba(var(--color-gray-50), 0.4);
2037
+ }
2038
+ .dark #sourcey .api-toggle {
2039
+ border-color: rgb(var(--color-gray-800));
2040
+ }
2041
+ #sourcey .api-toggle > summary {
2042
+ cursor: pointer;
2043
+ padding: 0.5rem 0.875rem;
2044
+ list-style: none;
2045
+ font-family: var(--font-mono);
2046
+ font-size: 0.8125rem;
2047
+ color: rgb(var(--color-gray-700));
2048
+ }
2049
+ .dark #sourcey .api-toggle > summary {
2050
+ color: rgb(var(--color-gray-300));
2051
+ }
2052
+ #sourcey .api-toggle > summary::-webkit-details-marker {
2053
+ display: none;
2054
+ }
2055
+ #sourcey .api-toggle > summary::before {
2056
+ content: "▸ ";
2057
+ color: rgb(var(--color-gray-400));
2058
+ margin-right: 0.25rem;
2059
+ }
2060
+ #sourcey .api-toggle[open] > summary::before {
2061
+ content: "▾ ";
2062
+ }
2063
+ /* The impl header lives inside the summary; keep it inline with the chevron
2064
+ and strip heading margins. */
2065
+ #sourcey .api-toggle > summary .rust-impl-header {
2066
+ display: inline;
2067
+ margin: 0;
2068
+ font-size: inherit;
2069
+ }
2070
+ #sourcey .api-toggle-body {
2071
+ padding: 0.25rem 0.875rem 0.5rem;
2072
+ border-top: 1px solid rgb(var(--color-gray-200));
2073
+ }
2074
+ .dark #sourcey .api-toggle-body {
2075
+ border-top-color: rgb(var(--color-gray-800));
2076
+ }
2077
+ /* Marker / auto / blanket trait impls: a quiet list, not expandable boxes. */
2078
+ #sourcey .rust-impl-rows {
2079
+ margin: 0.5rem 0 0.75rem;
2080
+ }
2081
+ #sourcey .rust-impl-row {
2082
+ padding: 0.2rem 0;
2083
+ }
2084
+ #sourcey .rust-impl-row .rust-impl-header {
2085
+ display: inline;
2086
+ margin: 0;
2087
+ font-size: 0.8125rem;
2088
+ font-weight: 400;
2089
+ color: rgb(var(--color-gray-600));
2090
+ }
2091
+ .dark #sourcey .rust-impl-row .rust-impl-header {
2092
+ color: rgb(var(--color-gray-400));
2093
+ }
2094
+
2095
+ #sourcey .anchor,
2096
+ #sourcey .api-anchor {
2097
+ margin-left: 0.4rem;
2098
+ color: rgb(var(--color-gray-400));
2099
+ text-decoration: none;
2100
+ opacity: 0;
2101
+ transition: opacity 0.1s ease;
2102
+ }
2103
+ #sourcey .section-header:hover .anchor,
2104
+ #sourcey .section-header:hover .api-anchor,
2105
+ #sourcey .code-header:hover .anchor,
2106
+ #sourcey .code-header:hover .api-anchor {
2107
+ opacity: 1;
2108
+ }
2109
+
2110
+ /* Inherent-impl methods and trait members render inline as their own sections.
2111
+ Code (signatures, doctests) flows through the shared Shiki code block, so no
2112
+ bespoke code styling lives here. */
2113
+ #sourcey .rust-member {
2114
+ margin: 0.75rem 0;
2115
+ }
2116
+ #sourcey .rust-method-header {
2117
+ margin: 0.5rem 0 0.25rem;
2118
+ }
2119
+ #sourcey .rust-doctest {
2120
+ margin: 0.75rem 0;
2121
+ }
2122
+
2123
+ #sourcey .rust-doctest-badges {
2124
+ display: flex;
2125
+ flex-wrap: wrap;
2126
+ gap: 0.25rem;
2127
+ margin-bottom: 0.25rem;
2128
+ }
2129
+ #sourcey .rust-doctest-badge {
2130
+ display: inline-block;
2131
+ padding: 0.05rem 0.4rem;
2132
+ font-size: 0.6875rem;
2133
+ font-weight: 600;
2134
+ border-radius: 0.25rem;
2135
+ background-color: rgb(var(--color-gray-100));
2136
+ color: rgb(var(--color-gray-700));
2137
+ text-transform: lowercase;
2138
+ letter-spacing: 0.02em;
2139
+ }
2140
+ .dark #sourcey .rust-doctest-badge {
2141
+ background-color: rgb(var(--color-gray-800));
2142
+ color: rgb(var(--color-gray-300));
2143
+ }
2144
+ #sourcey .rust-doctest-badge-should_panic,
2145
+ #sourcey .rust-doctest-badge-compile_fail {
2146
+ background-color: rgb(var(--color-rose-100));
2147
+ color: rgb(var(--color-rose-800));
2148
+ }
2149
+ #sourcey .rust-doctest-badge-no_run,
2150
+ #sourcey .rust-doctest-badge-ignore {
2151
+ background-color: rgb(var(--color-amber-100));
2152
+ color: rgb(var(--color-amber-800));
2153
+ }
2154
+
2155
+ #sourcey .rust-doctest-controls {
2156
+ display: flex;
2157
+ gap: 0.5rem;
2158
+ margin-top: 0.5rem;
2159
+ font-size: 0.8125rem;
2160
+ }
2161
+ #sourcey .rust-doctest-run,
2162
+ #sourcey .rust-doctest-toggle-hidden {
2163
+ cursor: pointer;
2164
+ padding: 0.2rem 0.6rem;
2165
+ border-radius: 0.375rem;
2166
+ border: 1px solid rgb(var(--color-gray-300));
2167
+ background-color: rgb(var(--color-white));
2168
+ color: rgb(var(--color-gray-800));
2169
+ text-decoration: none;
2170
+ line-height: 1;
2171
+ }
2172
+ #sourcey .rust-doctest-run:hover,
2173
+ #sourcey .rust-doctest-toggle-hidden:hover {
2174
+ background-color: rgb(var(--color-gray-50));
2175
+ }
2176
+ .dark #sourcey .rust-doctest-run,
2177
+ .dark #sourcey .rust-doctest-toggle-hidden {
2178
+ background-color: rgb(var(--color-gray-900));
2179
+ border-color: rgb(var(--color-gray-700));
2180
+ color: rgb(var(--color-gray-200));
2181
+ }
2182
+
2183
+ /* On the aggregated doctests page the code block already renders as a card, so
2184
+ the section itself stays borderless. Just space the entries and accent the
2185
+ heading so they don't read as cards within cards. */
2186
+ #sourcey .rust-doctests-index .rust-doctest {
2187
+ margin: 1.5rem 0;
2188
+ }
2189
+ #sourcey .rust-doctests-index .rust-doctest > h3 {
2190
+ padding-left: 0.625rem;
2191
+ border-left: 3px solid rgb(var(--color-emerald-500));
2192
+ }
2193
+ .dark #sourcey .rust-doctests-index .rust-doctest > h3 {
2194
+ border-left-color: rgb(var(--color-emerald-400));
2195
+ }
@@ -2,6 +2,7 @@ import type { NormalizedOperation } from "../core/types.js";
2
2
  import type { CodeSample } from "../core/types.js";
3
3
  /** Default languages when none configured. */
4
4
  export declare const DEFAULT_CODE_SAMPLE_LANGS: string[];
5
+ export declare const SUPPORTED_CODE_SAMPLE_LANGS: readonly string[];
5
6
  /**
6
7
  * Generate code samples for an operation.
7
8
  *
@@ -1 +1 @@
1
- {"version":3,"file":"code-samples.d.ts","sourceRoot":"","sources":["../../src/utils/code-samples.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAuMnD,8CAA8C;AAC9C,eAAO,MAAM,yBAAyB,UAAmC,CAAC;AAM1E;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,mBAAmB,EACvB,SAAS,EAAE,MAAM,EACjB,KAAK,GAAE,MAAM,EAA8B,GAC1C,UAAU,EAAE,CAmBd"}
1
+ {"version":3,"file":"code-samples.d.ts","sourceRoot":"","sources":["../../src/utils/code-samples.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAoNnD,8CAA8C;AAC9C,eAAO,MAAM,yBAAyB,UAAmC,CAAC;AAC1E,eAAO,MAAM,2BAA2B,mBAAyC,CAAC;AAMlF;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,mBAAmB,EACvB,SAAS,EAAE,MAAM,EACjB,KAAK,GAAE,MAAM,EAA8B,GAC1C,UAAU,EAAE,CAiBd"}
@@ -4,11 +4,11 @@ import { generateExample } from "./example-generator.js";
4
4
  // ---------------------------------------------------------------------------
5
5
  const generators = {
6
6
  curl({ url, method, contentType, body }) {
7
- const parts = [`curl -X ${method} '${url}'`];
7
+ const parts = [`curl -X ${method} ${shellSingleQuoted(url)}`];
8
8
  if (contentType)
9
- parts.push(` -H 'Content-Type: ${contentType}'`);
9
+ parts.push(` -H ${shellSingleQuoted(`Content-Type: ${contentType}`)}`);
10
10
  if (body)
11
- parts.push(` -d '${indent(body, 2)}'`);
11
+ parts.push(` -d ${shellSingleQuoted(indent(body, 2))}`);
12
12
  return { lang: "bash", label: "cURL", source: parts.join(" \\\n") };
13
13
  },
14
14
  javascript({ url, method, contentType, body }) {
@@ -51,7 +51,7 @@ const generators = {
51
51
  return { lang: "python", label: "Python", source: lines.join("\n") };
52
52
  },
53
53
  go({ url, method, body }) {
54
- const lines = ["package main", "", "import (", ` "fmt"`, ` "io"`, ` "net/http"`,];
54
+ const lines = ["package main", "", "import (", ` "fmt"`, ` "io"`, ` "net/http"`];
55
55
  if (body)
56
56
  lines.push(` "strings"`);
57
57
  lines.push(")", "");
@@ -63,7 +63,8 @@ const generators = {
63
63
  else {
64
64
  lines.push(` req, _ := http.NewRequest("${method}", "${url}", nil)`);
65
65
  }
66
- lines.push(` req.Header.Set("Content-Type", "application/json")`);
66
+ if (body)
67
+ lines.push(` req.Header.Set("Content-Type", "application/json")`);
67
68
  lines.push(` resp, _ := http.DefaultClient.Do(req)`);
68
69
  lines.push(` defer resp.Body.Close()`);
69
70
  lines.push(` data, _ := io.ReadAll(resp.Body)`);
@@ -76,21 +77,23 @@ const generators = {
76
77
  `require 'net/http'`,
77
78
  `require 'json'`,
78
79
  ``,
79
- `uri = URI('${url}')`,
80
+ `uri = URI('${rubySingleQuoted(url)}')`,
80
81
  ];
81
82
  const methodClass = rubyShortcutMethod(method);
82
83
  if (methodClass) {
83
84
  lines.push(`request = Net::HTTP::${methodClass}.new(uri)`);
84
85
  if (contentType)
85
- lines.push(`request['Content-Type'] = '${contentType}'`);
86
+ lines.push(`request['Content-Type'] = '${rubySingleQuoted(contentType)}'`);
86
87
  if (body)
87
- lines.push(`request.body = '${indent(body, 2)}'`);
88
+ lines.push(`request.body = '${rubySingleQuoted(indent(body, 2))}'`);
88
89
  }
89
90
  else {
90
- const headers = contentType ? `{ 'Content-Type' => '${contentType}' }` : "{}";
91
+ const headers = contentType
92
+ ? `{ 'Content-Type' => '${rubySingleQuoted(contentType)}' }`
93
+ : "{}";
91
94
  lines.push(`request = Net::HTTPGenericRequest.new('${method}', ${body ? "true" : "false"}, true, uri.request_uri, ${headers})`);
92
95
  if (body)
93
- lines.push(`request.body = '${indent(body, 2)}'`);
96
+ lines.push(`request.body = '${rubySingleQuoted(indent(body, 2))}'`);
94
97
  }
95
98
  lines.push(``, `response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(request) }`);
96
99
  lines.push(`data = JSON.parse(response.body)`);
@@ -121,16 +124,16 @@ const generators = {
121
124
  return { lang: "java", label: "Java", source: lines.join("\n") };
122
125
  },
123
126
  php({ url, method, contentType, body }) {
124
- const lines = [`<?php`, `$ch = curl_init('${url}');`];
127
+ const lines = [`<?php`, `$ch = curl_init('${phpSingleQuoted(url)}');`];
125
128
  lines.push(`curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);`);
126
129
  if (method !== "GET") {
127
- lines.push(`curl_setopt($ch, CURLOPT_CUSTOMREQUEST, '${method}');`);
130
+ lines.push(`curl_setopt($ch, CURLOPT_CUSTOMREQUEST, '${phpSingleQuoted(method)}');`);
128
131
  }
129
132
  if (contentType) {
130
- lines.push(`curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: ${contentType}']);`);
133
+ lines.push(`curl_setopt($ch, CURLOPT_HTTPHEADER, ['${phpSingleQuoted(`Content-Type: ${contentType}`)}']);`);
131
134
  }
132
135
  if (body) {
133
- lines.push(`curl_setopt($ch, CURLOPT_POSTFIELDS, '${indent(body, 2)}');`);
136
+ lines.push(`curl_setopt($ch, CURLOPT_POSTFIELDS, '${phpSingleQuoted(indent(body, 2))}');`);
134
137
  }
135
138
  lines.push(``, `$response = curl_exec($ch);`, `curl_close($ch);`);
136
139
  lines.push(`$data = json_decode($response, true);`);
@@ -165,10 +168,7 @@ const generators = {
165
168
  return { lang: "rust", label: "Rust", source: lines.join("\n") };
166
169
  },
167
170
  csharp({ url, method, body }) {
168
- const lines = [
169
- `using var client = new HttpClient();`,
170
- ``,
171
- ];
171
+ const lines = [`using var client = new HttpClient();`, ``];
172
172
  const httpMethod = csharpShortcutMethod(method)
173
173
  ? `HttpMethod.${method[0] + method.slice(1).toLowerCase()}`
174
174
  : `new HttpMethod("${method}")`;
@@ -185,6 +185,7 @@ const generators = {
185
185
  };
186
186
  /** Default languages when none configured. */
187
187
  export const DEFAULT_CODE_SAMPLE_LANGS = ["curl", "javascript", "python"];
188
+ export const SUPPORTED_CODE_SAMPLE_LANGS = Object.freeze(Object.keys(generators));
188
189
  // ---------------------------------------------------------------------------
189
190
  // Public API
190
191
  // ---------------------------------------------------------------------------
@@ -203,14 +204,12 @@ export function generateCodeSamples(op, serverUrl, langs = DEFAULT_CODE_SAMPLE_L
203
204
  const method = op.method.toUpperCase();
204
205
  const hasBody = !!op.requestBody;
205
206
  const contentType = hasBody
206
- ? Object.keys(op.requestBody.content)[0] ?? "application/json"
207
+ ? (Object.keys(op.requestBody.content)[0] ?? "application/json")
207
208
  : undefined;
208
209
  const bodyExample = hasBody ? getRequestBodyExample(op) : undefined;
209
210
  const body = bodyExample ? JSON.stringify(bodyExample, null, 2) : undefined;
210
211
  const req = { url, method, contentType, body };
211
- return langs
212
- .map((lang) => generators[lang]?.(req))
213
- .filter((s) => !!s);
212
+ return langs.map((lang) => generators[lang]?.(req)).filter((s) => !!s);
214
213
  }
215
214
  // ---------------------------------------------------------------------------
216
215
  // Helpers
@@ -259,6 +258,15 @@ function rustShortcutMethod(method) {
259
258
  function csharpShortcutMethod(method) {
260
259
  return ["GET", "POST", "PUT", "DELETE", "PATCH"].includes(method);
261
260
  }
261
+ function shellSingleQuoted(value) {
262
+ return `'${value.replace(/'/g, "'\\''")}'`;
263
+ }
264
+ function rubySingleQuoted(value) {
265
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
266
+ }
267
+ function phpSingleQuoted(value) {
268
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
269
+ }
262
270
  /** Indent all lines of a multi-line string except the first. */
263
271
  function indent(str, spaces) {
264
272
  const pad = " ".repeat(spaces);
@@ -0,0 +1,10 @@
1
+ export interface SafeUrlOptions {
2
+ allowedProtocols?: readonly string[];
3
+ allowRelative?: boolean;
4
+ allowAnchor?: boolean;
5
+ }
6
+ export declare function escapeHtml(value: string): string;
7
+ export declare function escapeAttr(value: string): string;
8
+ export declare function safeUrl(input: string, options?: SafeUrlOptions): string | null;
9
+ export declare function sanitizeRenderedHtml(input: string): string;
10
+ //# sourceMappingURL=html.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../src/utils/html.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,cAAc;IAC7B,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOhD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,MAAM,GAAG,IAAI,CAkBlF;AAUD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CA2G1D"}
@@ -0,0 +1,152 @@
1
+ import sanitizeHtml from "sanitize-html";
2
+ const BASE_URL = "https://sourcey.invalid/";
3
+ const EXPLICIT_SCHEME_RE = /^[a-z][a-z\d+.-]*:/i;
4
+ export function escapeHtml(value) {
5
+ return value
6
+ .replace(/&/g, "&amp;")
7
+ .replace(/</g, "&lt;")
8
+ .replace(/>/g, "&gt;")
9
+ .replace(/"/g, "&quot;")
10
+ .replace(/'/g, "&#39;");
11
+ }
12
+ export function escapeAttr(value) {
13
+ return escapeHtml(value);
14
+ }
15
+ export function safeUrl(input, options = {}) {
16
+ const allowedProtocols = options.allowedProtocols ?? ["http:", "https:", "mailto:"];
17
+ const allowRelative = options.allowRelative ?? true;
18
+ const allowAnchor = options.allowAnchor ?? true;
19
+ const value = input.trim();
20
+ if (!value || hasRawControlOrSpace(value))
21
+ return null;
22
+ if (value.startsWith("#"))
23
+ return allowAnchor ? value : null;
24
+ if (value.startsWith("//"))
25
+ return null;
26
+ try {
27
+ const parsed = new URL(value, BASE_URL);
28
+ const hasExplicitScheme = EXPLICIT_SCHEME_RE.test(value);
29
+ if (!hasExplicitScheme)
30
+ return allowRelative ? value : null;
31
+ return allowedProtocols.includes(parsed.protocol) ? parsed.href : null;
32
+ }
33
+ catch {
34
+ return null;
35
+ }
36
+ }
37
+ function hasRawControlOrSpace(value) {
38
+ for (const char of value) {
39
+ const code = char.charCodeAt(0);
40
+ if (code <= 0x20 || code === 0x7f)
41
+ return true;
42
+ }
43
+ return false;
44
+ }
45
+ export function sanitizeRenderedHtml(input) {
46
+ return sanitizeHtml(input, {
47
+ allowedTags: [
48
+ ...sanitizeHtml.defaults.allowedTags,
49
+ "button",
50
+ "details",
51
+ "iframe",
52
+ "img",
53
+ "path",
54
+ "source",
55
+ "summary",
56
+ "svg",
57
+ "video",
58
+ ],
59
+ allowedAttributes: {
60
+ "*": [
61
+ "class",
62
+ "id",
63
+ "role",
64
+ "tabindex",
65
+ "title",
66
+ "aria-expanded",
67
+ "aria-hidden",
68
+ "aria-label",
69
+ "aria-selected",
70
+ "data-clipboard-target",
71
+ "data-cols",
72
+ "data-copy-source",
73
+ "data-display",
74
+ "data-tab-group",
75
+ "data-tab-index",
76
+ "data-target",
77
+ "data-traverse-target",
78
+ ],
79
+ a: ["href", "name", "target", "rel", "title", "class", "id"],
80
+ button: [
81
+ "type",
82
+ "class",
83
+ "aria-expanded",
84
+ "aria-label",
85
+ "aria-selected",
86
+ "data-copy-source",
87
+ "data-tab-group",
88
+ "data-tab-index",
89
+ ],
90
+ div: [
91
+ "class",
92
+ "id",
93
+ "role",
94
+ "style",
95
+ "data-cols",
96
+ "data-tab-group",
97
+ "data-tab-index",
98
+ "data-traverse-target",
99
+ ],
100
+ details: ["class", "id", "open"],
101
+ h1: ["id", "class"],
102
+ h2: ["id", "class"],
103
+ h3: ["id", "class"],
104
+ h4: ["id", "class"],
105
+ h5: ["id", "class"],
106
+ h6: ["id", "class"],
107
+ iframe: ["src", "title", "frameborder", "allow", "allowfullscreen", "loading", "class", "id"],
108
+ img: ["src", "alt", "title", "width", "height", "loading", "class", "id"],
109
+ path: ["d", "fill", "stroke", "stroke-width", "stroke-linecap", "stroke-linejoin"],
110
+ pre: ["class", "tabindex", "style"],
111
+ source: ["src", "type"],
112
+ span: ["class", "style", "title"],
113
+ svg: [
114
+ "xmlns",
115
+ "width",
116
+ "height",
117
+ "viewBox",
118
+ "fill",
119
+ "stroke",
120
+ "stroke-width",
121
+ "stroke-linecap",
122
+ "stroke-linejoin",
123
+ "class",
124
+ "aria-hidden",
125
+ "focusable",
126
+ ],
127
+ table: ["class"],
128
+ td: ["class", "colspan", "rowspan"],
129
+ th: ["class", "colspan", "rowspan", "scope"],
130
+ video: ["controls", "preload", "title", "class", "id"],
131
+ },
132
+ allowedSchemes: ["http", "https", "mailto"],
133
+ allowedSchemesByTag: {
134
+ iframe: ["http", "https"],
135
+ img: ["http", "https"],
136
+ source: ["http", "https"],
137
+ },
138
+ allowProtocolRelative: false,
139
+ allowedStyles: {
140
+ "*": {
141
+ background: [/^transparent$/, /^#[0-9a-fA-F]{3,8}$/],
142
+ "background-color": [/^#[0-9a-fA-F]{3,8}$/],
143
+ color: [/^#[0-9a-fA-F]{3,8}$/],
144
+ height: [/^\d{1,4}px$/],
145
+ "--shiki-dark": [/^#[0-9a-fA-F]{3,8}$/],
146
+ "--shiki-dark-bg": [/^#[0-9a-fA-F]{3,8}$/],
147
+ "--shiki-light": [/^#[0-9a-fA-F]{3,8}$/],
148
+ "--shiki-light-bg": [/^#[0-9a-fA-F]{3,8}$/],
149
+ },
150
+ },
151
+ });
152
+ }
@@ -9,6 +9,4 @@ export declare function schemaRange(schema: {
9
9
  exclusiveMinimum?: number | boolean;
10
10
  exclusiveMaximum?: number | boolean;
11
11
  }): string | undefined;
12
- /** HTTP method color classes for badges */
13
- export declare function methodColor(method: string): string;
14
12
  //# sourceMappingURL=http.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/utils/http.ts"],"names":[],"mappings":"AAiDA,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACpC,gBAAgB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACrC,GAAG,MAAM,GAAG,SAAS,CA0BrB;AAED,2CAA2C;AAC3C,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAmBlD"}
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/utils/http.ts"],"names":[],"mappings":"AAiDA,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACpC,gBAAgB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACrC,GAAG,MAAM,GAAG,SAAS,CA4BrB"}