@spunto/design-system 0.10.0 → 0.11.1

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 (28) hide show
  1. package/README.md +85 -3
  2. package/package.json +9 -1
  3. package/src/components/devcontainer/catalog-card.tsx +269 -0
  4. package/src/components/devcontainer/catalog-tile.tsx +133 -0
  5. package/src/components/devcontainer/extension-card.tsx +146 -0
  6. package/src/components/devcontainer/feature-card.tsx +157 -0
  7. package/src/components/devcontainer/feature-tile.tsx +63 -0
  8. package/src/components/devcontainer/image-card.tsx +107 -0
  9. package/src/components/devcontainer/image-tile.tsx +56 -0
  10. package/src/components/devcontainer/index.ts +68 -0
  11. package/src/components/devcontainer/publisher-badge.tsx +91 -0
  12. package/src/components/devcontainer/registry-ref.tsx +89 -0
  13. package/src/components/devcontainer/registry.ts +154 -0
  14. package/src/components/devcontainer/stack-mark-paths.ts +60 -0
  15. package/src/components/devcontainer/stack-mark.tsx +92 -0
  16. package/src/components/devcontainer/types.ts +84 -0
  17. package/src/components/projects/advanced-sections.tsx +148 -0
  18. package/src/components/projects/build-manifest.tsx +116 -0
  19. package/src/components/projects/extension-picker.tsx +169 -0
  20. package/src/components/projects/feature-picker.tsx +193 -0
  21. package/src/components/projects/form-section.tsx +97 -0
  22. package/src/components/projects/image-picker.tsx +206 -0
  23. package/src/components/projects/index.ts +58 -0
  24. package/src/components/projects/project-form.tsx +495 -0
  25. package/src/components/projects/repo-list.tsx +302 -0
  26. package/src/components/projects/secret-list.tsx +159 -0
  27. package/src/components/projects/types.ts +136 -0
  28. package/styles.css +13 -0
@@ -0,0 +1,154 @@
1
+ // Pure helpers: what an OCI reference says about who publishes it.
2
+ //
3
+ // None of the catalogs carry an author today (`DevImage` is `{id, label, image,
4
+ // description}`), and adding one to the API would only move the problem for
5
+ // hand-typed refs, which have no catalog entry at all. So the author is
6
+ // *derived* from the reference itself — `ghcr.io/<owner>/…` is an owner on
7
+ // GitHub, `mcr.microsoft.com/devcontainers/…` is the Dev Containers team — and
8
+ // the `publisher` prop stays as an override for the day the API knows better.
9
+ //
10
+ // Everything here is a pure function on a string: no fetch, no React. That's
11
+ // what lets the cards stay presentational and the showcase draw them from
12
+ // hand-written fixtures.
13
+
14
+ import type { CatalogPublisher } from "./types"
15
+
16
+ export interface ParsedRegistryRef {
17
+ /** Registry host — `ghcr.io`, `mcr.microsoft.com`. Absent = implicit Docker Hub. */
18
+ registry?: string
19
+ /** First path segment under the registry: the org/owner (`devcontainers`, `coderhammer`). */
20
+ owner?: string
21
+ /** Segments between the owner and the name (`features` in `ghcr.io/x/features/node`). */
22
+ path: string[]
23
+ /** Last path segment, tag and digest stripped. */
24
+ name?: string
25
+ tag?: string
26
+ digest?: string
27
+ /** The ref without its tag/digest — `ghcr.io/devcontainers/features/node`. */
28
+ repository: string
29
+ }
30
+
31
+ const EMPTY: ParsedRegistryRef = { path: [], repository: "" }
32
+
33
+ /**
34
+ * Split an image/feature reference into its parts.
35
+ *
36
+ * Deliberately tolerant: an unparseable string comes back with everything
37
+ * `undefined` rather than throwing, because one of the three call sites is a
38
+ * free-text input the user is still typing into.
39
+ */
40
+ export function parseRegistryRef(ref?: string | null): ParsedRegistryRef {
41
+ const raw = ref?.trim()
42
+ if (!raw) return EMPTY
43
+
44
+ const [beforeDigest, digest] = splitOnce(raw, "@")
45
+ const segments = beforeDigest.split("/").filter(Boolean)
46
+ if (segments.length === 0) return EMPTY
47
+
48
+ // A first segment is a registry host only if it looks like one: a dot (a
49
+ // domain), a colon (a port) or literally localhost. Otherwise it's a Docker
50
+ // Hub namespace — `library/node` is a repository, not a host.
51
+ const first = segments[0]
52
+ const hasRegistry = segments.length > 1 && (first.includes(".") || first.includes(":") || first === "localhost")
53
+ const registry = hasRegistry ? first : undefined
54
+ const pathSegments = hasRegistry ? segments.slice(1) : segments
55
+
56
+ const last = pathSegments[pathSegments.length - 1] ?? ""
57
+ const [namePart, tag] = splitOnce(last, ":")
58
+ const middle = pathSegments.slice(0, -1)
59
+
60
+ return {
61
+ registry,
62
+ owner: middle[0],
63
+ path: middle.slice(1),
64
+ name: namePart || undefined,
65
+ tag: tag || undefined,
66
+ digest: digest || undefined,
67
+ repository: [registry, ...middle, namePart].filter(Boolean).join("/"),
68
+ }
69
+ }
70
+
71
+ function splitOnce(value: string, separator: string): [string, string | undefined] {
72
+ const at = value.lastIndexOf(separator)
73
+ if (at < 0) return [value, undefined]
74
+ const rest = value.slice(at + 1)
75
+ // A colon in a path segment is a port, not a tag: `localhost:5000/node`.
76
+ if (separator === ":" && rest.includes("/")) return [value, undefined]
77
+ return [value.slice(0, at), rest]
78
+ }
79
+
80
+ /**
81
+ * GitHub org behind a `ghcr.io/<owner>/…` ref — `undefined` for any other
82
+ * registry. That single mapping covers the whole feature catalog today
83
+ * (`devcontainers`, `coderhammer`, `stu-bell`).
84
+ */
85
+ export function githubOwner(ref?: string | null): string | undefined {
86
+ const parsed = parseRegistryRef(ref)
87
+ return parsed.registry === "ghcr.io" ? parsed.owner : undefined
88
+ }
89
+
90
+ /**
91
+ * Avatar of a GitHub account. `github.com/<login>.png` 302s to
92
+ * avatars.githubusercontent.com, so it works straight in an `<img>` — no API
93
+ * call, no token, no id to look up.
94
+ */
95
+ export function githubAvatarUrl(login: string, size = 80): string {
96
+ return `https://github.com/${encodeURIComponent(login)}.png?size=${size}`
97
+ }
98
+
99
+ /** Registries whose publisher we can name without asking anyone. */
100
+ const KNOWN_REGISTRIES: Record<string, (parsed: ParsedRegistryRef) => CatalogPublisher | undefined> = {
101
+ "ghcr.io": (parsed) =>
102
+ parsed.owner
103
+ ? { name: parsed.owner, login: parsed.owner, avatarUrl: githubAvatarUrl(parsed.owner) }
104
+ : undefined,
105
+ "mcr.microsoft.com": (parsed) =>
106
+ parsed.owner === "devcontainers"
107
+ ? { name: "Dev Containers", login: "devcontainers", avatarUrl: githubAvatarUrl("devcontainers"), verified: true }
108
+ : { name: "Microsoft", login: "microsoft", avatarUrl: githubAvatarUrl("microsoft"), verified: true },
109
+ "quay.io": (parsed) => (parsed.owner ? { name: parsed.owner, login: parsed.owner } : undefined),
110
+ }
111
+
112
+ /**
113
+ * Who publishes this ref, as far as the ref itself admits. `undefined` when
114
+ * nothing is derivable — the caller then falls back to a monogram, which is the
115
+ * honest answer for `my-registry.internal/team/thing:1`.
116
+ */
117
+ export function derivePublisher(ref?: string | null): CatalogPublisher | undefined {
118
+ const parsed = parseRegistryRef(ref)
119
+ if (!parsed.registry) return parsed.owner ? { name: parsed.owner, login: parsed.owner } : undefined
120
+ const known = KNOWN_REGISTRIES[parsed.registry]
121
+ if (known) return known(parsed)
122
+ return parsed.owner ? { name: parsed.owner, login: parsed.owner } : undefined
123
+ }
124
+
125
+ /** `12 400 000` → `12.4M`. Open VSX download counts, at a glance. */
126
+ export function formatCount(value?: number | null): string | undefined {
127
+ if (value == null || !Number.isFinite(value) || value < 0) return undefined
128
+ if (value < 1000) return String(Math.round(value))
129
+ if (value < 1_000_000) return `${trim(value / 1000)}k`
130
+ return `${trim(value / 1_000_000)}M`
131
+ }
132
+
133
+ function trim(value: number): string {
134
+ return value >= 100 ? String(Math.round(value)) : value.toFixed(1).replace(/\.0$/, "")
135
+ }
136
+
137
+ /** `namespace.name`, or whatever the payload already carries as an id. */
138
+ export function extensionId(extension: { id?: string; namespace?: string; name?: string }): string {
139
+ if (extension.id) return extension.id
140
+ return [extension.namespace, extension.name].filter(Boolean).join(".")
141
+ }
142
+
143
+ /**
144
+ * The reverse: `dbaeumer.vscode-eslint` → publisher + extension name. A project
145
+ * only stores the id, so a card drawn from the database has nothing else to go
146
+ * on — and "dbaeumer" is still worth showing.
147
+ */
148
+ export function parseExtensionId(id?: string | null): { namespace?: string; name?: string } {
149
+ const value = id?.trim()
150
+ if (!value) return {}
151
+ const at = value.indexOf(".")
152
+ if (at <= 0) return { name: value }
153
+ return { namespace: value.slice(0, at), name: value.slice(at + 1) || undefined }
154
+ }
@@ -0,0 +1,60 @@
1
+ // Brand marks for the devcontainer catalogs, as single-path SVGs drawn with
2
+ // `currentColor` — so a logo inherits the warm accent of the card it sits in
3
+ // instead of dragging 20 clashing brand colors into the grid. Same approach (and
4
+ // same source, simple-icons) as the marketing templates' logos; embedded in the
5
+ // package rather than fetched from a CDN so the cards render offline, in the
6
+ // showcase, and inside a worker with no egress.
7
+ //
8
+ // Paths only — the component that draws them is `StackMark`.
9
+
10
+ export const STACK_MARK_PATHS = {
11
+ node:
12
+ "M11.998,24c-0.321,0-0.641-0.084-0.922-0.247l-2.936-1.737c-0.438-0.245-0.224-0.332-0.08-0.383 c0.585-0.203,0.703-0.25,1.328-0.604c0.065-0.037,0.151-0.023,0.218,0.017l2.256,1.339c0.082,0.045,0.197,0.045,0.272,0l8.795-5.076 c0.082-0.047,0.134-0.141,0.134-0.238V6.921c0-0.099-0.053-0.192-0.137-0.242l-8.791-5.072c-0.081-0.047-0.189-0.047-0.271,0 L3.075,6.68C2.99,6.729,2.936,6.825,2.936,6.921v10.15c0,0.097,0.054,0.189,0.139,0.235l2.409,1.392 c1.307,0.654,2.108-0.116,2.108-0.89V7.787c0-0.142,0.114-0.253,0.256-0.253h1.115c0.139,0,0.255,0.112,0.255,0.253v10.021 c0,1.745-0.95,2.745-2.604,2.745c-0.508,0-0.909,0-2.026-0.551L2.28,18.675c-0.57-0.329-0.922-0.945-0.922-1.604V6.921 c0-0.659,0.353-1.275,0.922-1.603l8.795-5.082c0.557-0.315,1.296-0.315,1.848,0l8.794,5.082c0.57,0.329,0.924,0.944,0.924,1.603 v10.15c0,0.659-0.354,1.273-0.924,1.604l-8.794,5.078C12.643,23.916,12.324,24,11.998,24z M19.099,13.993 c0-1.9-1.284-2.406-3.987-2.763c-2.731-0.361-3.009-0.548-3.009-1.187c0-0.528,0.235-1.233,2.258-1.233 c1.807,0,2.473,0.389,2.747,1.607c0.024,0.115,0.129,0.199,0.247,0.199h1.141c0.071,0,0.138-0.031,0.186-0.081 c0.048-0.054,0.074-0.123,0.067-0.196c-0.177-2.098-1.571-3.076-4.388-3.076c-2.508,0-4.004,1.058-4.004,2.833 c0,1.925,1.488,2.457,3.895,2.695c2.88,0.282,3.103,0.703,3.103,1.269c0,0.983-0.789,1.402-2.642,1.402 c-2.327,0-2.839-0.584-3.011-1.742c-0.02-0.124-0.126-0.215-0.253-0.215h-1.137c-0.141,0-0.254,0.112-0.254,0.253 c0,1.482,0.806,3.248,4.655,3.248C17.501,17.007,19.099,15.91,19.099,13.993z",
13
+ typescript:
14
+ "M1.125 0C.502 0 0 .502 0 1.125v21.75C0 23.498.502 24 1.125 24h21.75c.623 0 1.125-.502 1.125-1.125V1.125C24 .502 23.498 0 22.875 0zm17.363 9.75c.612 0 1.154.037 1.627.111a6.38 6.38 0 0 1 1.306.34v2.458a3.95 3.95 0 0 0-.643-.361 5.093 5.093 0 0 0-.717-.26 5.453 5.453 0 0 0-1.426-.2c-.3 0-.573.028-.819.086a2.1 2.1 0 0 0-.623.242c-.17.104-.3.229-.393.374a.888.888 0 0 0-.14.49c0 .196.053.373.156.529.104.156.252.304.443.444s.423.276.696.41c.273.135.582.274.926.416.47.197.892.407 1.266.628.374.222.695.473.963.753.268.279.472.598.614.957.142.359.214.776.214 1.253 0 .657-.125 1.21-.373 1.656a3.033 3.033 0 0 1-1.012 1.085 4.38 4.38 0 0 1-1.487.596c-.566.12-1.163.18-1.79.18a9.916 9.916 0 0 1-1.84-.164 5.544 5.544 0 0 1-1.512-.493v-2.63a5.033 5.033 0 0 0 3.237 1.2c.333 0 .624-.03.872-.09.249-.06.456-.144.623-.25.166-.108.29-.234.373-.38a1.023 1.023 0 0 0-.074-1.089 2.12 2.12 0 0 0-.537-.5 5.597 5.597 0 0 0-.807-.444 27.72 27.72 0 0 0-1.007-.436c-.918-.383-1.602-.852-2.053-1.405-.45-.553-.676-1.222-.676-2.005 0-.614.123-1.141.369-1.582.246-.441.58-.804 1.004-1.089a4.494 4.494 0 0 1 1.47-.629 7.536 7.536 0 0 1 1.77-.201zm-15.113.188h9.563v2.166H9.506v9.646H6.789v-9.646H3.375z",
15
+ javascript:
16
+ "M0 0h24v24H0V0zm22.034 18.276c-.175-1.095-.888-2.015-3.003-2.873-.736-.345-1.554-.585-1.797-1.14-.091-.33-.105-.51-.046-.705.15-.646.915-.84 1.515-.66.39.12.75.42.976.9 1.034-.676 1.034-.676 1.755-1.125-.27-.42-.404-.601-.586-.78-.63-.705-1.469-1.065-2.834-1.034l-.705.089c-.676.165-1.32.525-1.71 1.005-1.14 1.291-.811 3.541.569 4.471 1.365 1.02 3.361 1.244 3.616 2.205.24 1.17-.87 1.545-1.966 1.41-.811-.18-1.26-.586-1.755-1.336l-1.83 1.051c.21.48.45.689.81 1.109 1.74 1.756 6.09 1.666 6.871-1.004.029-.09.24-.705.074-1.65l.046.067zm-8.983-7.245h-2.248c0 1.938-.009 3.864-.009 5.805 0 1.232.063 2.363-.138 2.711-.33.689-1.18.601-1.566.48-.396-.196-.597-.466-.83-.855-.063-.105-.11-.196-.127-.196l-1.825 1.125c.305.63.75 1.172 1.324 1.517.855.51 2.004.675 3.207.405.783-.226 1.458-.691 1.811-1.411.51-.93.402-2.07.397-3.346.012-2.054 0-4.109 0-6.179l.004-.056z",
17
+ python:
18
+ "M14.25.18l.9.2.73.26.59.3.45.32.34.34.25.34.16.33.1.3.04.26.02.2-.01.13V8.5l-.05.63-.13.55-.21.46-.26.38-.3.31-.33.25-.35.19-.35.14-.33.1-.3.07-.26.04-.21.02H8.77l-.69.05-.59.14-.5.22-.41.27-.33.32-.27.35-.2.36-.15.37-.1.35-.07.32-.04.27-.02.21v3.06H3.17l-.21-.03-.28-.07-.32-.12-.35-.18-.36-.26-.36-.36-.35-.46-.32-.59-.28-.73-.21-.88-.14-1.05-.05-1.23.06-1.22.16-1.04.24-.87.32-.71.36-.57.4-.44.42-.33.42-.24.4-.16.36-.1.32-.05.24-.01h.16l.06.01h8.16v-.83H6.18l-.01-2.75-.02-.37.05-.34.11-.31.17-.28.25-.26.31-.23.38-.2.44-.18.51-.15.58-.12.64-.1.71-.06.77-.04.84-.02 1.27.05zm-6.3 1.98l-.23.33-.08.41.08.41.23.34.33.22.41.09.41-.09.33-.22.23-.34.08-.41-.08-.41-.23-.33-.33-.22-.41-.09-.41.09zm13.09 3.95l.28.06.32.12.35.18.36.27.36.35.35.47.32.59.28.73.21.88.14 1.04.05 1.23-.06 1.23-.16 1.04-.24.86-.32.71-.36.57-.4.45-.42.33-.42.24-.4.16-.36.09-.32.05-.24.02-.16-.01h-8.22v.82h5.84l.01 2.76.02.36-.05.34-.11.31-.17.29-.25.25-.31.24-.38.2-.44.17-.51.15-.58.13-.64.09-.71.07-.77.04-.84.01-1.27-.04-1.07-.14-.9-.2-.73-.25-.59-.3-.45-.33-.34-.34-.25-.34-.16-.33-.1-.3-.04-.25-.02-.2.01-.13v-5.34l.05-.64.13-.54.21-.46.26-.38.3-.32.33-.24.35-.2.35-.14.33-.1.3-.06.26-.04.21-.02.13-.01h5.84l.69-.05.59-.14.5-.21.41-.28.33-.32.27-.35.2-.36.15-.36.1-.35.07-.32.04-.28.02-.21V6.07h2.09l.14.01zm-6.47 14.25l-.23.33-.08.41.08.41.23.33.33.23.41.08.41-.08.33-.23.23-.33.08-.41-.08-.41-.23-.33-.33-.23-.41-.08-.41.08z",
19
+ anaconda:
20
+ "M12.045.033a12.181 12.182 0 00-1.361.078 17.512 17.513 0 011.813 1.433l.48.438-.465.45a15.047 15.048 0 00-1.126 1.205l-.178.215a8.527 8.527 0 01.86-.05 8.154 8.155 0 11-4.286 15.149 15.764 15.765 0 01-1.841.106h-.86a21.847 21.848 0 00.264 2.866 11.966 11.967 0 106.7-21.89zM8.17.678a12.181 12.182 0 00-2.624 1.275 15.506 15.507 0 011.813.43A18.551 18.552 0 018.17.678zM9.423.75a16.237 16.238 0 00-.995 1.998 16.15 16.152 0 011.605.66 6.98 6.98 0 01.43-.509c.234-.286.472-.559.716-.817A15.047 15.048 0 009.423.75zM4.68 2.949a14.969 14.97 0 000 2.336c.587-.065 1.196-.1 1.812-.107a16.617 16.617 0 01.48-1.748 16.48 16.481 0 00-2.292-.481zM3.62 3.5A11.938 11.938 0 001.762 5.88a17.004 17.004 0 011.877-.444A17.39 17.391 0 013.62 3.5zm4.406.287c-.143.437-.265.888-.38 1.347a8.255 8.255 0 011.67-.803c-.423-.2-.845-.38-1.29-.544zM6.3 6.216a14.051 14.052 0 00-1.555.108c.064.523.157 1.038.272 1.554a8.39 8.391 0 011.283-1.662zm-2.55.137a15.313 15.313 0 00-2.602.716h-.078v.079a17.104 17.105 0 001.267 2.544l.043.071.072-.049a16.309 16.31 0 011.734-1.083l.057-.035V8.54a16.867 16.868 0 01-.408-2.094v-.092zM.644 8.095l-.063.2A11.844 11.845 0 000 11.655v.209l.143-.152a17.706 17.707 0 011.584-1.447l.057-.043-.043-.064a16.18 16.18 0 01-1.025-1.87zm3.77 1.253l-.18.1c-.465.273-.93.573-1.375.889l-.065.05.05.064c.309.437.645.867.996 1.276l.137.165v-.208a8.176 8.176 0 01.364-2.15zM2.2 10.853l-.072.05a16.574 16.574 0 00-1.813 1.734l-.058.058.066.057a15.449 15.45 0 001.991 1.483l.072.05.043-.08a16.738 16.74 0 011.053-1.64v-.05l-.043-.05a16.99 16.99 0 01-1.19-1.54zm1.855 2.071l-.121.172a15.363 15.363 0 00-.917 1.433l-.043.072.071.043a16.61 16.61 0 001.562.766l.193.086-.086-.193a8.04 8.04 0 01-.66-2.172zm-3.976.48v.2a11.758 11.759 0 00.946 3.326l.078.186.072-.194a16.215 16.216 0 01.845-2l.057-.063-.064-.043a17.197 17.198 0 01-1.776-1.284zm2.543 1.805l-.035.08a15.764 15.765 0 00-.983 2.479v.08h.086a16.15 16.152 0 002.688.5l.072.007v-.086a17.562 17.563 0 01.164-2.056v-.065H4.55a16.266 16.266 0 01-1.849-.896zm2.544 1.169v.114a17.254 17.255 0 00-.151 1.828v.078h.931c.287 0 .624.014.946 0h.209l-.166-.129a8.011 8.011 0 01-1.64-1.834zm-3.29 2.1l.115.172a11.988 11.988 0 002.502 2.737l.157.129v-.201a22.578 22.58 0 01-.2-2.336v-.071h-.072a16.23 16.23 0 01-2.3-.387z",
21
+ go:
22
+ "M1.811 10.231c-.047 0-.058-.023-.035-.059l.246-.315c.023-.035.081-.058.128-.058h4.172c.046 0 .058.035.035.07l-.199.303c-.023.036-.082.07-.117.07zM.047 11.306c-.047 0-.059-.023-.035-.058l.245-.316c.023-.035.082-.058.129-.058h5.328c.047 0 .07.035.058.07l-.093.28c-.012.047-.058.07-.105.07zm2.828 1.075c-.047 0-.059-.035-.035-.07l.163-.292c.023-.035.07-.07.117-.07h2.337c.047 0 .07.035.07.082l-.023.28c0 .047-.047.082-.082.082zm12.129-2.36c-.736.187-1.239.327-1.963.514-.176.046-.187.058-.34-.117-.174-.199-.303-.327-.548-.444-.737-.362-1.45-.257-2.115.175-.795.514-1.204 1.274-1.192 2.22.011.935.654 1.706 1.577 1.835.795.105 1.46-.175 1.987-.77.105-.13.198-.27.315-.434H10.47c-.245 0-.304-.152-.222-.35.152-.362.432-.97.596-1.274a.315.315 0 01.292-.187h4.253c-.023.316-.023.631-.07.947a4.983 4.983 0 01-.958 2.29c-.841 1.11-1.94 1.8-3.33 1.986-1.145.152-2.209-.07-3.143-.77-.865-.655-1.356-1.52-1.484-2.595-.152-1.274.222-2.419.993-3.424.83-1.086 1.928-1.776 3.272-2.02 1.098-.2 2.15-.07 3.096.571.62.41 1.063.97 1.356 1.648.07.105.023.164-.117.2m3.868 6.461c-1.064-.024-2.034-.328-2.852-1.029a3.665 3.665 0 01-1.262-2.255c-.21-1.32.152-2.489.947-3.529.853-1.122 1.881-1.706 3.272-1.95 1.192-.21 2.314-.095 3.33.595.923.63 1.496 1.484 1.648 2.605.198 1.578-.257 2.863-1.344 3.962-.771.783-1.718 1.273-2.805 1.495-.315.06-.63.07-.934.106zm2.78-4.72c-.011-.153-.011-.27-.034-.387-.21-1.157-1.274-1.81-2.384-1.554-1.087.245-1.788.935-2.045 2.033-.21.912.234 1.835 1.075 2.21.643.28 1.285.244 1.905-.07.923-.48 1.425-1.228 1.484-2.233z",
23
+ rust:
24
+ "M23.8346 11.7033l-1.0073-.6236a13.7268 13.7268 0 00-.0283-.2936l.8656-.8069a.3483.3483 0 00-.1154-.578l-1.1066-.414a8.4958 8.4958 0 00-.087-.2856l.6904-.9587a.3462.3462 0 00-.2257-.5446l-1.1663-.1894a9.3574 9.3574 0 00-.1407-.2622l.49-1.0761a.3437.3437 0 00-.0274-.3361.3486.3486 0 00-.3006-.154l-1.1845.0416a6.7444 6.7444 0 00-.1873-.2268l.2723-1.153a.3472.3472 0 00-.417-.4172l-1.1532.2724a14.0183 14.0183 0 00-.2278-.1873l.0415-1.1845a.3442.3442 0 00-.49-.328l-1.076.491c-.0872-.0476-.1742-.0952-.2623-.1407l-.1903-1.1673A.3483.3483 0 0016.256.955l-.9597.6905a8.4867 8.4867 0 00-.2855-.086l-.414-1.1066a.3483.3483 0 00-.5781-.1154l-.8069.8666a9.2936 9.2936 0 00-.2936-.0284L12.2946.1683a.3462.3462 0 00-.5892 0l-.6236 1.0073a13.7383 13.7383 0 00-.2936.0284L9.9803.3374a.3462.3462 0 00-.578.1154l-.4141 1.1065c-.0962.0274-.1903.0567-.2855.086L7.744.955a.3483.3483 0 00-.5447.2258L7.009 2.348a9.3574 9.3574 0 00-.2622.1407l-1.0762-.491a.3462.3462 0 00-.49.328l.0416 1.1845a7.9826 7.9826 0 00-.2278.1873L3.8413 3.425a.3472.3472 0 00-.4171.4171l.2713 1.1531c-.0628.075-.1255.1509-.1863.2268l-1.1845-.0415a.3462.3462 0 00-.328.49l.491 1.0761a9.167 9.167 0 00-.1407.2622l-1.1662.1894a.3483.3483 0 00-.2258.5446l.6904.9587a13.303 13.303 0 00-.087.2855l-1.1065.414a.3483.3483 0 00-.1155.5781l.8656.807a9.2936 9.2936 0 00-.0283.2935l-1.0073.6236a.3442.3442 0 000 .5892l1.0073.6236c.008.0982.0182.1964.0283.2936l-.8656.8079a.3462.3462 0 00.1155.578l1.1065.4141c.0273.0962.0567.1914.087.2855l-.6904.9587a.3452.3452 0 00.2268.5447l1.1662.1893c.0456.088.0922.1751.1408.2622l-.491 1.0762a.3462.3462 0 00.328.49l1.1834-.0415c.0618.0769.1235.1528.1873.2277l-.2713 1.1541a.3462.3462 0 00.4171.4161l1.153-.2713c.075.0638.151.1255.2279.1863l-.0415 1.1845a.3442.3442 0 00.49.327l1.0761-.49c.087.0486.1741.0951.2622.1407l.1903 1.1662a.3483.3483 0 00.5447.2268l.9587-.6904a9.299 9.299 0 00.2855.087l.414 1.1066a.3452.3452 0 00.5781.1154l.8079-.8656c.0972.0111.1954.0203.2936.0294l.6236 1.0073a.3472.3472 0 00.5892 0l.6236-1.0073c.0982-.0091.1964-.0183.2936-.0294l.8069.8656a.3483.3483 0 00.578-.1154l.4141-1.1066a8.4626 8.4626 0 00.2855-.087l.9587.6904a.3452.3452 0 00.5447-.2268l.1903-1.1662c.088-.0456.1751-.0931.2622-.1407l1.0762.49a.3472.3472 0 00.49-.327l-.0415-1.1845a6.7267 6.7267 0 00.2267-.1863l1.1531.2713a.3472.3472 0 00.4171-.416l-.2713-1.1542c.0628-.0749.1255-.1508.1863-.2278l1.1845.0415a.3442.3442 0 00.328-.49l-.49-1.076c.0475-.0872.0951-.1742.1407-.2623l1.1662-.1893a.3483.3483 0 00.2258-.5447l-.6904-.9587.087-.2855 1.1066-.414a.3462.3462 0 00.1154-.5781l-.8656-.8079c.0101-.0972.0202-.1954.0283-.2936l1.0073-.6236a.3442.3442 0 000-.5892zm-6.7413 8.3551a.7138.7138 0 01.2986-1.396.714.714 0 11-.2997 1.396zm-.3422-2.3142a.649.649 0 00-.7715.5l-.3573 1.6685c-1.1035.501-2.3285.7795-3.6193.7795a8.7368 8.7368 0 01-3.6951-.814l-.3574-1.6684a.648.648 0 00-.7714-.499l-1.473.3158a8.7216 8.7216 0 01-.7613-.898h7.1676c.081 0 .1356-.0141.1356-.088v-2.536c0-.074-.0536-.0881-.1356-.0881h-2.0966v-1.6077h2.2677c.2065 0 1.1065.0587 1.394 1.2088.0901.3533.2875 1.5044.4232 1.8729.1346.413.6833 1.2381 1.2685 1.2381h3.5716a.7492.7492 0 00.1296-.0131 8.7874 8.7874 0 01-.8119.9526zM6.8369 20.024a.714.714 0 11-.2997-1.396.714.714 0 01.2997 1.396zM4.1177 8.9972a.7137.7137 0 11-1.304.5791.7137.7137 0 011.304-.579zm-.8352 1.9813l1.5347-.6824a.65.65 0 00.33-.8585l-.3158-.7147h1.2432v5.6025H3.5669a8.7753 8.7753 0 01-.2834-3.348zm6.7343-.5437V8.7836h2.9601c.153 0 1.0792.1772 1.0792.8697 0 .575-.7107.7815-1.2948.7815zm10.7574 1.4862c0 .2187-.008.4363-.0243.651h-.9c-.09 0-.1265.0586-.1265.1477v.413c0 .973-.5487 1.1846-1.0296 1.2382-.4576.0517-.9648-.1913-1.0275-.4717-.2704-1.5186-.7198-1.8436-1.4305-2.4034.8817-.5599 1.799-1.386 1.799-2.4915 0-1.1936-.819-1.9458-1.3769-2.3153-.7825-.5163-1.6491-.6195-1.883-.6195H5.4682a8.7651 8.7651 0 014.907-2.7699l1.0974 1.151a.648.648 0 00.9182.0213l1.227-1.1743a8.7753 8.7753 0 016.0044 4.2762l-.8403 1.8982a.652.652 0 00.33.8585l1.6178.7188c.0283.2875.0425.577.0425.8717zm-9.3006-9.5993a.7128.7128 0 11.984 1.0316.7137.7137 0 01-.984-1.0316zm8.3389 6.71a.7107.7107 0 01.9395-.3625.7137.7137 0 11-.9405.3635z",
25
+ java:
26
+ "M11.915 0 11.7.215C9.515 2.4 7.47 6.39 6.046 10.483c-1.064 1.024-3.633 2.81-3.711 3.551-.093.87 1.746 2.611 1.55 3.235-.198.625-1.304 1.408-1.014 1.939.1.188.823.011 1.277-.491a13.389 13.389 0 0 0-.017 2.14c.076.906.27 1.668.643 2.232.372.563.956.911 1.667.911.397 0 .727-.114 1.024-.264.298-.149.571-.33.91-.5.68-.34 1.634-.666 3.53-.604 1.903.062 2.872.39 3.559.704.687.314 1.15.664 1.925.664.767 0 1.395-.336 1.807-.9.412-.563.631-1.33.72-2.24.06-.623.055-1.32 0-2.066.454.45 1.117.604 1.213.424.29-.53-.816-1.314-1.013-1.937-.198-.624 1.642-2.366 1.549-3.236-.08-.748-2.707-2.568-3.748-3.586C16.428 6.374 14.308 2.394 12.13.215zm.175 6.038a2.95 2.95 0 0 1 2.943 2.942 2.95 2.95 0 0 1-2.943 2.943A2.95 2.95 0 0 1 9.148 8.98a2.95 2.95 0 0 1 2.942-2.942zM8.685 7.983a3.515 3.515 0 0 0-.145.997c0 1.951 1.6 3.55 3.55 3.55 1.95 0 3.55-1.598 3.55-3.55 0-.329-.046-.648-.132-.951.334.095.64.208.915.336a42.699 42.699 0 0 1 2.042 5.829c.678 2.545 1.01 4.92.846 6.607-.082.844-.29 1.51-.606 1.94-.315.431-.713.651-1.315.651-.593 0-.932-.27-1.673-.61-.741-.338-1.825-.694-3.792-.758-1.974-.064-3.073.293-3.821.669-.375.188-.659.373-.911.5s-.466.2-.752.2c-.53 0-.876-.209-1.16-.64-.285-.43-.474-1.101-.545-1.948-.141-1.693.176-4.069.823-6.614a43.155 43.155 0 0 1 1.934-5.783c.348-.167.749-.31 1.192-.425zm-3.382 4.362a.216.216 0 0 1 .13.031c-.166.56-.323 1.116-.463 1.665a33.849 33.849 0 0 0-.547 2.555 3.9 3.9 0 0 0-.2-.39c-.58-1.012-.914-1.642-1.16-2.08.315-.24 1.679-1.755 2.24-1.781zm13.394.01c.562.027 1.926 1.543 2.24 1.783-.246.438-.58 1.068-1.16 2.08a4.428 4.428 0 0 0-.163.309 32.354 32.354 0 0 0-.562-2.49 40.579 40.579 0 0 0-.482-1.652.216.216 0 0 1 .127-.03z",
27
+ dotnet:
28
+ "M24 8.77h-2.468v7.565h-1.425V8.77h-2.462V7.53H24zm-6.852 7.565h-4.821V7.53h4.63v1.24h-3.205v2.494h2.953v1.234h-2.953v2.604h3.396zm-6.708 0H8.882L4.78 9.863a2.896 2.896 0 0 1-.258-.51h-.036c.032.189.048.592.048 1.21v5.772H3.157V7.53h1.659l3.965 6.32c.167.261.275.442.323.54h.024c-.04-.233-.06-.629-.06-1.185V7.529h1.372zm-8.703-.693a.868.829 0 0 1-.869.829.868.829 0 0 1-.868-.83.868.829 0 0 1 .868-.828.868.829 0 0 1 .869.829Z",
29
+ php:
30
+ "M7.01 10.207h-.944l-.515 2.648h.838c.556 0 .97-.105 1.242-.314.272-.21.455-.559.55-1.049.092-.47.05-.802-.124-.995-.175-.193-.523-.29-1.047-.29zM12 5.688C5.373 5.688 0 8.514 0 12s5.373 6.313 12 6.313S24 15.486 24 12c0-3.486-5.373-6.312-12-6.312zm-3.26 7.451c-.261.25-.575.438-.917.551-.336.108-.765.164-1.285.164H5.357l-.327 1.681H3.652l1.23-6.326h2.65c.797 0 1.378.209 1.744.628.366.418.476 1.002.33 1.752a2.836 2.836 0 0 1-.305.847c-.143.255-.33.49-.561.703zm4.024.715l.543-2.799c.063-.318.039-.536-.068-.651-.107-.116-.336-.174-.687-.174H11.46l-.704 3.625H9.388l1.23-6.327h1.367l-.327 1.682h1.218c.767 0 1.295.134 1.586.401s.378.7.263 1.299l-.572 2.944h-1.389zm7.597-2.265a2.782 2.782 0 0 1-.305.847c-.143.255-.33.49-.561.703a2.44 2.44 0 0 1-.917.551c-.336.108-.765.164-1.286.164h-1.18l-.327 1.682h-1.378l1.23-6.326h2.649c.797 0 1.378.209 1.744.628.366.417.477 1.001.331 1.751zM17.766 10.207h-.943l-.516 2.648h.838c.557 0 .971-.105 1.242-.314.272-.21.455-.559.551-1.049.092-.47.049-.802-.125-.995s-.524-.29-1.047-.29z",
31
+ ruby:
32
+ "M20.156.083c3.033.525 3.893 2.598 3.829 4.77L24 4.822 22.635 22.71 4.89 23.926h.016C3.433 23.864.15 23.729 0 19.139l1.645-3 2.819 6.586.503 1.172 2.805-9.144-.03.007.016-.03 9.255 2.956-1.396-5.431-.99-3.9 8.82-.569-.615-.51L16.5 2.114 20.159.073l-.003.01zM0 19.089zM5.13 5.073c3.561-3.533 8.157-5.621 9.922-3.84 1.762 1.777-.105 6.105-3.673 9.636-3.563 3.532-8.103 5.734-9.864 3.957-1.766-1.777.045-6.217 3.612-9.75l.003-.003z",
33
+ jekyll:
34
+ "M8.073 24c-.348 0-.689-.063-1.02-.189-1.375-.525-2.104-2.02-1.726-3.402l-.015-.006.09-.226L12.399 2.01c.105-.27.057-.91.006-1.267-.016-.085-.016-.161.008-.24l.008-.023.006-.015V.458l.009-.019c.108-.292.45-.439 1.008-.439.673 0 1.602.21 2.551.573.797.307 1.523.689 2.033 1.075.602.45.842.854.707 1.2l-.031.045-.016.015c-.045.061-.09.12-.15.165-.314.271-.764.735-.84.945l-7.063 18.421-.016-.006c-.494.948-1.457 1.557-2.543 1.561H8.07l.003.006zm-2.187-3.718l-.02.05c-.447 1.201.162 2.557 1.364 3.018.271.105.551.154.837.154.971 0 1.83-.585 2.188-1.5l.027-.061 6.959-18.09c.146-.39.84-1.02.979-1.14l.016-.016c.012-.015.02-.015.02-.03 0-.06-.061-.27-.557-.645-.479-.36-1.154-.72-1.904-1.005-.868-.328-1.768-.539-2.368-.539-.39 0-.524.082-.545.126v.04c.016.104.147 1.035-.034 1.515l-6.962 18.12v.003zm8.95-11.507s-.964 1.109-1.843 1.509c-.88.398-1.529.293-2.32.756-.789.461-1.188 1.103-1.188 1.103L6.27 20.505c-.348.944.168 2.05 1.125 2.42.96.369 2.04-.12 2.412-1.056l5.029-13.094zM9.905 18.76c.104-.041.225 0 .266.105.042.104 0 .222-.105.264-.104.043-.225 0-.266-.104-.042-.097 0-.216.105-.265zm-1.014-1.802c-.152.068-.334 0-.397-.155-.07-.152 0-.334.154-.397.154-.07.335 0 .398.153.074.15.008.314-.155.39v.009zm.286-1.096c-.123-.288 0-.623.287-.758.285-.124.615 0 .75.285.121.289 0 .624-.285.757-.3.126-.629 0-.765-.285l.013.001zm2.426-2.258c.153-.074.335 0 .398.15.07.154 0 .336-.153.399-.155.07-.337 0-.399-.155-.074-.152 0-.334.154-.397v.003zm-1.293-1.379c.105-.042.226 0 .266.105.043.104 0 .226-.104.266-.104.042-.226 0-.265-.104-.044-.106.006-.227.103-.267zM13.681 1.14c.1-.261.993-.162 1.995.226.999.384 1.729.909 1.63 1.17-.104.264-.997.164-1.996-.221-1.005-.385-1.734-.91-1.632-1.176h.003z",
35
+ cpp:
36
+ "M22.394 6c-.167-.29-.398-.543-.652-.69L12.926.22c-.509-.294-1.34-.294-1.848 0L2.26 5.31c-.508.293-.923 1.013-.923 1.6v10.18c0 .294.104.62.271.91.167.29.398.543.652.69l8.816 5.09c.508.293 1.34.293 1.848 0l8.816-5.09c.254-.147.485-.4.652-.69.167-.29.27-.616.27-.91V6.91c.003-.294-.1-.62-.268-.91zM12 19.11c-3.92 0-7.109-3.19-7.109-7.11 0-3.92 3.19-7.11 7.11-7.11a7.133 7.133 0 016.156 3.553l-3.076 1.78a3.567 3.567 0 00-3.08-1.78A3.56 3.56 0 008.444 12 3.56 3.56 0 0012 15.555a3.57 3.57 0 003.08-1.778l3.078 1.78A7.135 7.135 0 0112 19.11zm7.11-6.715h-.79v.79h-.79v-.79h-.79v-.79h.79v-.79h.79v.79h.79zm2.962 0h-.79v.79h-.79v-.79h-.79v-.79h.79v-.79h.79v.79h.79z",
37
+ docker:
38
+ "M13.983 11.078h2.119a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.119a.185.185 0 00-.185.185v1.888c0 .102.083.185.185.185m-2.954-5.43h2.118a.186.186 0 00.186-.186V3.574a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m0 2.716h2.118a.187.187 0 00.186-.186V6.29a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.887c0 .102.082.185.185.186m-2.93 0h2.12a.186.186 0 00.184-.186V6.29a.185.185 0 00-.185-.185H8.1a.185.185 0 00-.185.185v1.887c0 .102.083.185.185.186m-2.964 0h2.119a.186.186 0 00.185-.186V6.29a.185.185 0 00-.185-.185H5.136a.186.186 0 00-.186.185v1.887c0 .102.084.185.186.186m5.893 2.715h2.118a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m-2.93 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.083.185.185.185m-2.964 0h2.119a.185.185 0 00.185-.185V9.006a.185.185 0 00-.184-.186h-2.12a.186.186 0 00-.186.186v1.887c0 .102.084.185.186.185m-2.92 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.082.185.185.185M23.763 9.89c-.065-.051-.672-.51-1.954-.51-.338.001-.676.03-1.01.087-.248-1.7-1.653-2.53-1.716-2.566l-.344-.199-.226.327c-.284.438-.49.922-.612 1.43-.23.97-.09 1.882.403 2.661-.595.332-1.55.413-1.744.42H.751a.751.751 0 00-.75.748 11.376 11.376 0 00.692 4.062c.545 1.428 1.355 2.48 2.41 3.124 1.18.723 3.1 1.137 5.275 1.137.983.003 1.963-.086 2.93-.266a12.248 12.248 0 003.823-1.389c.98-.567 1.86-1.288 2.61-2.136 1.252-1.418 1.998-2.997 2.553-4.4h.221c1.372 0 2.215-.549 2.68-1.009.309-.293.55-.65.707-1.046l.098-.288Z",
39
+ kubernetes:
40
+ "M10.204 14.35l.007.01-.999 2.413a5.171 5.171 0 0 1-2.075-2.597l2.578-.437.004.005a.44.44 0 0 1 .484.606zm-.833-2.129a.44.44 0 0 0 .173-.756l.002-.011L7.585 9.7a5.143 5.143 0 0 0-.73 3.255l2.514-.725.002-.009zm1.145-1.98a.44.44 0 0 0 .699-.337l.01-.005.15-2.62a5.144 5.144 0 0 0-3.01 1.442l2.147 1.523.004-.002zm.76 2.75l.723.349.722-.347.18-.78-.5-.623h-.804l-.5.623.179.779zm1.5-3.095a.44.44 0 0 0 .7.336l.008.003 2.134-1.513a5.188 5.188 0 0 0-2.992-1.442l.148 2.615.002.001zm10.876 5.97l-5.773 7.181a1.6 1.6 0 0 1-1.248.594l-9.261.003a1.6 1.6 0 0 1-1.247-.596l-5.776-7.18a1.583 1.583 0 0 1-.307-1.34L2.1 5.573c.108-.47.425-.864.863-1.073L11.305.513a1.606 1.606 0 0 1 1.385 0l8.345 3.985c.438.209.755.604.863 1.073l2.062 8.955c.108.47-.005.963-.308 1.34zm-3.289-2.057c-.042-.01-.103-.026-.145-.034-.174-.033-.315-.025-.479-.038-.35-.037-.638-.067-.895-.148-.105-.04-.18-.165-.216-.216l-.201-.059a6.45 6.45 0 0 0-.105-2.332 6.465 6.465 0 0 0-.936-2.163c.052-.047.15-.133.177-.159.008-.09.001-.183.094-.282.197-.185.444-.338.743-.522.142-.084.273-.137.415-.242.032-.024.076-.062.11-.089.24-.191.295-.52.123-.736-.172-.216-.506-.236-.745-.045-.034.027-.08.062-.111.088-.134.116-.217.23-.33.35-.246.25-.45.458-.673.609-.097.056-.239.037-.303.033l-.19.135a6.545 6.545 0 0 0-4.146-2.003l-.012-.223c-.065-.062-.143-.115-.163-.25-.022-.268.015-.557.057-.905.023-.163.061-.298.068-.475.001-.04-.001-.099-.001-.142 0-.306-.224-.555-.5-.555-.275 0-.499.249-.499.555l.001.014c0 .041-.002.092 0 .128.006.177.044.312.067.475.042.348.078.637.056.906a.545.545 0 0 1-.162.258l-.012.211a6.424 6.424 0 0 0-4.166 2.003 8.373 8.373 0 0 1-.18-.128c-.09.012-.18.04-.297-.029-.223-.15-.427-.358-.673-.608-.113-.12-.195-.234-.329-.349-.03-.026-.077-.062-.111-.088a.594.594 0 0 0-.348-.132.481.481 0 0 0-.398.176c-.172.216-.117.546.123.737l.007.005.104.083c.142.105.272.159.414.242.299.185.546.338.743.522.076.082.09.226.1.288l.16.143a6.462 6.462 0 0 0-1.02 4.506l-.208.06c-.055.072-.133.184-.215.217-.257.081-.546.11-.895.147-.164.014-.305.006-.48.039-.037.007-.09.02-.133.03l-.004.002-.007.002c-.295.071-.484.342-.423.608.061.267.349.429.645.365l.007-.001.01-.003.129-.029c.17-.046.294-.113.448-.172.33-.118.604-.217.87-.256.112-.009.23.069.288.101l.217-.037a6.5 6.5 0 0 0 2.88 3.596l-.09.218c.033.084.069.199.044.282-.097.252-.263.517-.452.813-.091.136-.185.242-.268.399-.02.037-.045.095-.064.134-.128.275-.034.591.213.71.248.12.556-.007.69-.282v-.002c.02-.039.046-.09.062-.127.07-.162.094-.301.144-.458.132-.332.205-.68.387-.897.05-.06.13-.082.215-.105l.113-.205a6.453 6.453 0 0 0 4.609.012l.106.192c.086.028.18.042.256.155.136.232.229.507.342.84.05.156.074.295.145.457.016.037.043.09.062.129.133.276.442.402.69.282.247-.118.341-.435.213-.71-.02-.039-.045-.096-.065-.134-.083-.156-.177-.261-.268-.398-.19-.296-.346-.541-.443-.793-.04-.13.007-.21.038-.294-.018-.022-.059-.144-.083-.202a6.499 6.499 0 0 0 2.88-3.622c.064.01.176.03.213.038.075-.05.144-.114.28-.104.266.039.54.138.87.256.154.06.277.128.448.173.036.01.088.019.13.028l.009.003.007.001c.297.064.584-.098.645-.365.06-.266-.128-.537-.423-.608zM16.4 9.701l-1.95 1.746v.005a.44.44 0 0 0 .173.757l.003.01 2.526.728a5.199 5.199 0 0 0-.108-1.674A5.208 5.208 0 0 0 16.4 9.7zm-4.013 5.325a.437.437 0 0 0-.404-.232.44.44 0 0 0-.372.233h-.002l-1.268 2.292a5.164 5.164 0 0 0 3.326.003l-1.27-2.296h-.01zm1.888-1.293a.44.44 0 0 0-.27.036.44.44 0 0 0-.214.572l-.003.004 1.01 2.438a5.15 5.15 0 0 0 2.081-2.615l-2.6-.44-.004.005z",
41
+ helm:
42
+ "M12.337 0c-.475 0-.861 1.016-.861 2.269 0 .527.069 1.011.183 1.396a8.514 8.514 0 0 0-3.961 1.22 5.229 5.229 0 0 0-.595-1.093c-.606-.866-1.34-1.436-1.79-1.43a.381.381 0 0 0-.217.066c-.39.273-.123 1.326.596 2.353.267.381.559.705.84.948a8.683 8.683 0 0 0-1.528 1.716h1.734a7.179 7.179 0 0 1 5.381-2.421 7.18 7.18 0 0 1 5.382 2.42h1.733a8.687 8.687 0 0 0-1.32-1.53c.35-.249.735-.643 1.078-1.133.719-1.027.986-2.08.596-2.353a.382.382 0 0 0-.217-.065c-.45-.007-1.184.563-1.79 1.43a4.897 4.897 0 0 0-.676 1.325 8.52 8.52 0 0 0-3.899-1.42c.12-.39.193-.887.193-1.429 0-1.253-.386-2.269-.862-2.269zM1.624 9.443v5.162h1.358v-1.968h1.64v1.968h1.357V9.443H4.62v1.838H2.98V9.443zm5.912 0v5.162h3.21v-1.108H8.893v-.95h1.64v-1.142h-1.64v-.84h1.853V9.443zm4.698 0v5.162h3.218v-1.362h-1.86v-3.8zm4.706 0v5.162h1.364v-2.643l1.357 1.225 1.35-1.232v2.65h1.365V9.443h-.614l-2.1 1.914-2.109-1.914zm-11.82 7.28a8.688 8.688 0 0 0 1.412 1.548 5.206 5.206 0 0 0-.841.948c-.719 1.027-.985 2.08-.596 2.353.39.273 1.289-.338 2.007-1.364a5.23 5.23 0 0 0 .595-1.092 8.514 8.514 0 0 0 3.961 1.219 5.01 5.01 0 0 0-.183 1.396c0 1.253.386 2.269.861 2.269.476 0 .862-1.016.862-2.269 0-.542-.072-1.04-.193-1.43a8.52 8.52 0 0 0 3.9-1.42c.121.4.352.865.675 1.327.719 1.026 1.617 1.637 2.007 1.364.39-.273.123-1.326-.596-2.353-.343-.49-.727-.885-1.077-1.135a8.69 8.69 0 0 0 1.202-1.36h-1.771a7.174 7.174 0 0 1-5.227 2.252 7.174 7.174 0 0 1-5.226-2.252z",
43
+ terraform:
44
+ "M1.44 0v7.575l6.561 3.79V3.787zm21.12 4.227l-6.561 3.791v7.574l6.56-3.787zM8.72 4.23v7.575l6.561 3.787V8.018zm0 8.405v7.575L15.28 24v-7.578z",
45
+ googlecloud:
46
+ "M12.19 2.38a9.344 9.344 0 0 0-9.234 6.893c.053-.02-.055.013 0 0-3.875 2.551-3.922 8.11-.247 10.941l.006-.007-.007.03a6.717 6.717 0 0 0 4.077 1.356h5.173l.03.03h5.192c6.687.053 9.376-8.605 3.835-12.35a9.365 9.365 0 0 0-2.821-4.552l-.043.043.006-.05A9.344 9.344 0 0 0 12.19 2.38zm-.358 4.146c1.244-.04 2.518.368 3.486 1.15a5.186 5.186 0 0 1 1.862 4.078v.518c3.53-.07 3.53 5.262 0 5.193h-5.193l-.008.009v-.04H6.785a2.59 2.59 0 0 1-1.067-.23h.001a2.597 2.597 0 1 1 3.437-3.437l3.013-3.012A6.747 6.747 0 0 0 8.11 8.24c.018-.01.04-.026.054-.023a5.186 5.186 0 0 1 3.67-1.69z",
47
+ github:
48
+ "M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12",
49
+ ubuntu:
50
+ "M17.61.455a3.41 3.41 0 0 0-3.41 3.41 3.41 3.41 0 0 0 3.41 3.41 3.41 3.41 0 0 0 3.41-3.41 3.41 3.41 0 0 0-3.41-3.41zM12.92.8C8.923.777 5.137 2.941 3.148 6.451a4.5 4.5 0 0 1 .26-.007 4.92 4.92 0 0 1 2.585.737A8.316 8.316 0 0 1 12.688 3.6 4.944 4.944 0 0 1 13.723.834 11.008 11.008 0 0 0 12.92.8zm9.226 4.994a4.915 4.915 0 0 1-1.918 2.246 8.36 8.36 0 0 1-.273 8.303 4.89 4.89 0 0 1 1.632 2.54 11.156 11.156 0 0 0 .559-13.089zM3.41 7.932A3.41 3.41 0 0 0 0 11.342a3.41 3.41 0 0 0 3.41 3.409 3.41 3.41 0 0 0 3.41-3.41 3.41 3.41 0 0 0-3.41-3.41zm2.027 7.866a4.908 4.908 0 0 1-2.915.358 11.1 11.1 0 0 0 7.991 6.698 11.234 11.234 0 0 0 2.422.249 4.879 4.879 0 0 1-.999-2.85 8.484 8.484 0 0 1-.836-.136 8.304 8.304 0 0 1-5.663-4.32zm11.405.928a3.41 3.41 0 0 0-3.41 3.41 3.41 3.41 0 0 0 3.41 3.41 3.41 3.41 0 0 0 3.41-3.41 3.41 3.41 0 0 0-3.41-3.41z",
51
+ debian:
52
+ "M13.88 12.685c-.4 0 .08.2.601.28.14-.1.27-.22.39-.33a3.001 3.001 0 01-.99.05m2.14-.53c.23-.33.4-.69.47-1.06-.06.27-.2.5-.33.73-.75.47-.07-.27 0-.56-.8 1.01-.11.6-.14.89m.781-2.05c.05-.721-.14-.501-.2-.221.07.04.13.5.2.22M12.38.31c.2.04.45.07.42.12.23-.05.28-.1-.43-.12m.43.12l-.15.03.14-.01V.43m6.633 9.944c.02.64-.2.95-.38 1.5l-.35.181c-.28.54.03.35-.17.78-.44.39-1.34 1.22-1.62 1.301-.201 0 .14-.25.19-.34-.591.4-.481.6-1.371.85l-.03-.06c-2.221 1.04-5.303-1.02-5.253-3.842-.03.17-.07.13-.12.2a3.551 3.552 0 012.001-3.501 3.361 3.362 0 013.732.48 3.341 3.342 0 00-2.721-1.3c-1.18.01-2.281.76-2.651 1.57-.6.38-.67 1.47-.93 1.661-.361 2.601.66 3.722 2.38 5.042.27.19.08.21.12.35a4.702 4.702 0 01-1.53-1.16c.23.33.47.66.8.91-.55-.18-1.27-1.3-1.48-1.35.93 1.66 3.78 2.921 5.261 2.3a6.203 6.203 0 01-2.33-.28c-.33-.16-.77-.51-.7-.57a5.802 5.803 0 005.902-.84c.44-.35.93-.94 1.07-.95-.2.32.04.16-.12.44.44-.72-.2-.3.46-1.24l.24.33c-.09-.6.74-1.321.66-2.262.19-.3.2.3 0 .97.29-.74.08-.85.15-1.46.08.2.18.42.23.63-.18-.7.2-1.2.28-1.6-.09-.05-.28.3-.32-.53 0-.37.1-.2.14-.28-.08-.05-.26-.32-.38-.861.08-.13.22.33.34.34-.08-.42-.2-.75-.2-1.08-.34-.68-.12.1-.4-.3-.34-1.091.3-.25.34-.74.54.77.84 1.96.981 2.46-.1-.6-.28-1.2-.49-1.76.16.07-.26-1.241.21-.37A7.823 7.824 0 0017.702 1.6c.18.17.42.39.33.42-.75-.45-.62-.48-.73-.67-.61-.25-.65.02-1.06 0C15.082.73 14.862.8 13.8.4l.05.23c-.77-.25-.9.1-1.73 0-.05-.04.27-.14.53-.18-.741.1-.701-.14-1.431.03.17-.13.36-.21.55-.32-.6.04-1.44.35-1.18.07C9.6.68 7.847 1.3 6.867 2.22L6.838 2c-.45.54-1.96 1.611-2.08 2.311l-.131.03c-.23.4-.38.85-.57 1.261-.3.52-.45.2-.4.28-.6 1.22-.9 2.251-1.16 3.102.18.27 0 1.65.07 2.76-.3 5.463 3.84 10.776 8.363 12.006.67.23 1.65.23 2.49.25-.99-.28-1.12-.15-2.08-.49-.7-.32-.85-.7-1.34-1.13l.2.35c-.971-.34-.57-.42-1.361-.67l.21-.27c-.31-.03-.83-.53-.97-.81l-.34.01c-.41-.501-.63-.871-.61-1.161l-.111.2c-.13-.21-1.52-1.901-.8-1.511-.13-.12-.31-.2-.5-.55l.14-.17c-.35-.44-.64-1.02-.62-1.2.2.24.32.3.45.33-.88-2.172-.93-.12-1.601-2.202l.15-.02c-.1-.16-.18-.34-.26-.51l.06-.6c-.63-.74-.18-3.102-.09-4.402.07-.54.53-1.1.88-1.981l-.21-.04c.4-.71 2.341-2.872 3.241-2.761.43-.55-.09 0-.18-.14.96-.991 1.26-.7 1.901-.88.7-.401-.6.16-.27-.151 1.2-.3.85-.7 2.421-.85.16.1-.39.14-.52.26 1-.49 3.151-.37 4.562.27 1.63.77 3.461 3.011 3.531 5.132l.08.02c-.04.85.13 1.821-.17 2.711l.2-.42M9.54 13.236l-.05.28c.26.35.47.73.8 1.01-.24-.47-.42-.66-.75-1.3m.62-.02c-.14-.15-.22-.34-.31-.52.08.32.26.6.43.88l-.12-.36m10.945-2.382l-.07.15c-.1.76-.34 1.511-.69 2.212.4-.73.65-1.541.75-2.362M12.45.12c.27-.1.66-.05.95-.12-.37.03-.74.05-1.1.1l.15.02M3.006 5.142c.07.57-.43.8.11.42.3-.66-.11-.18-.1-.42m-.64 2.661c.12-.39.15-.62.2-.84-.35.44-.17.53-.2.83",
53
+ bash:
54
+ "M21.038,4.9l-7.577-4.498C13.009,0.134,12.505,0,12,0c-0.505,0-1.009,0.134-1.462,0.403L2.961,4.9 C2.057,5.437,1.5,6.429,1.5,7.503v8.995c0,1.073,0.557,2.066,1.462,2.603l7.577,4.497C10.991,23.866,11.495,24,12,24 c0.505,0,1.009-0.134,1.461-0.402l7.577-4.497c0.904-0.537,1.462-1.529,1.462-2.603V7.503C22.5,6.429,21.943,5.437,21.038,4.9z M15.17,18.946l0.013,0.646c0.001,0.078-0.05,0.167-0.111,0.198l-0.383,0.22c-0.061,0.031-0.111-0.007-0.112-0.085L14.57,19.29 c-0.328,0.136-0.66,0.169-0.872,0.084c-0.04-0.016-0.057-0.075-0.041-0.142l0.139-0.584c0.011-0.046,0.036-0.092,0.069-0.121 c0.012-0.011,0.024-0.02,0.036-0.026c0.022-0.011,0.043-0.014,0.062-0.006c0.229,0.077,0.521,0.041,0.802-0.101 c0.357-0.181,0.596-0.545,0.592-0.907c-0.003-0.328-0.181-0.465-0.613-0.468c-0.55,0.001-1.064-0.107-1.072-0.917 c-0.007-0.667,0.34-1.361,0.889-1.8l-0.007-0.652c-0.001-0.08,0.048-0.168,0.111-0.2l0.37-0.236 c0.061-0.031,0.111,0.007,0.112,0.087l0.006,0.653c0.273-0.109,0.511-0.138,0.726-0.088c0.047,0.012,0.067,0.076,0.048,0.151 l-0.144,0.578c-0.011,0.044-0.036,0.088-0.065,0.116c-0.012,0.012-0.025,0.021-0.038,0.028c-0.019,0.01-0.038,0.013-0.057,0.009 c-0.098-0.022-0.332-0.073-0.699,0.113c-0.385,0.195-0.52,0.53-0.517,0.778c0.003,0.297,0.155,0.387,0.681,0.396 c0.7,0.012,1.003,0.318,1.01,1.023C16.105,17.747,15.736,18.491,15.17,18.946z M19.143,17.859c0,0.06-0.008,0.116-0.058,0.145 l-1.916,1.164c-0.05,0.029-0.09,0.004-0.09-0.056v-0.494c0-0.06,0.037-0.093,0.087-0.122l1.887-1.129 c0.05-0.029,0.09-0.004,0.09,0.056V17.859z M20.459,6.797l-7.168,4.427c-0.894,0.523-1.553,1.109-1.553,2.187v8.833 c0,0.645,0.26,1.063,0.66,1.184c-0.131,0.023-0.264,0.039-0.398,0.039c-0.42,0-0.833-0.114-1.197-0.33L3.226,18.64 c-0.741-0.44-1.201-1.261-1.201-2.142V7.503c0-0.881,0.46-1.702,1.201-2.142l7.577-4.498c0.363-0.216,0.777-0.33,1.197-0.33 c0.419,0,0.833,0.114,1.197,0.33l7.577,4.498c0.624,0.371,1.046,1.013,1.164,1.732C21.686,6.557,21.12,6.411,20.459,6.797z",
55
+ claude:
56
+ "m4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z",
57
+ } as const satisfies Record<string, string>
58
+
59
+ /** Every brand mark the catalogs can draw. */
60
+ export type StackMarkName = keyof typeof STACK_MARK_PATHS
@@ -0,0 +1,92 @@
1
+ import { BoxesIcon } from "lucide-react"
2
+
3
+ import { cn } from "../../utils"
4
+ import { STACK_MARK_PATHS, type StackMarkName } from "./stack-mark-paths"
5
+
6
+ export type { StackMarkName }
7
+ export { STACK_MARK_PATHS }
8
+
9
+ /**
10
+ * Which brand a catalog entry is about, guessed from its id/ref.
11
+ *
12
+ * Order matters and is the whole trick: `typescript-node` is TypeScript, not
13
+ * Node, and `javascript-node` is neither Java nor JavaScript-then-Node. So the
14
+ * table is scanned top to bottom and the first hit wins — the more specific
15
+ * patterns sit above the ones they'd otherwise be swallowed by.
16
+ */
17
+ const PATTERNS: [RegExp, StackMarkName][] = [
18
+ [/typescript|(^|[^a-z])ts([^a-z]|$)/, "typescript"],
19
+ [/javascript/, "javascript"],
20
+ [/node|nvm|npm|pnpm|yarn/, "node"],
21
+ [/anaconda|miniconda|conda/, "anaconda"],
22
+ [/python|(^|[^a-z])py([^a-z]|$)/, "python"],
23
+ [/docker|moby|dind/, "docker"],
24
+ [/kubernetes|kubectl|minikube|(^|[^a-z])k8s([^a-z]|$)/, "kubernetes"],
25
+ [/helm/, "helm"],
26
+ [/terraform|hashicorp/, "terraform"],
27
+ [/google-?cloud|gcloud|(^|[^a-z])gcp([^a-z]|$)/, "googlecloud"],
28
+ [/github|(^|[^a-z])gh([^a-z]|$)/, "github"],
29
+ [/claude|anthropic/, "claude"],
30
+ [/jekyll/, "jekyll"],
31
+ [/ruby|rails/, "ruby"],
32
+ [/rust|cargo/, "rust"],
33
+ [/dotnet|\.net|csharp|(^|[^a-z])c#/, "dotnet"],
34
+ [/(^|[^a-z])php([^a-z]|$)/, "php"],
35
+ [/(^|[^a-z])(cpp|c\+\+)([^a-z]|$)/, "cpp"],
36
+ [/java|jdk|maven|gradle/, "java"],
37
+ [/ubuntu/, "ubuntu"],
38
+ [/debian/, "debian"],
39
+ [/common-utils|zsh|(^|[^a-z])(bash|sh)([^a-z]|$)/, "bash"],
40
+ // Last: "go" is two letters and lives inside django, mongo, google… so it
41
+ // only ever matches a whole segment, and only once nothing else has.
42
+ [/(^|[^a-z])go(lang)?([^a-z]|$)/, "go"],
43
+ ]
44
+
45
+ /**
46
+ * First mark matching any of the hints, in the order they're given — pass the
47
+ * most specific first (an explicit `mark`, then the catalog id, then the ref).
48
+ * `undefined` when nothing matches: the caller falls back to the publisher
49
+ * avatar or a monogram rather than drawing a wrong logo.
50
+ */
51
+ export function resolveStackMark(...hints: (string | null | undefined)[]): StackMarkName | undefined {
52
+ for (const hint of hints) {
53
+ if (!hint) continue
54
+ const value = hint.toLowerCase()
55
+ if (markPath(value)) return value as StackMarkName
56
+ for (const [pattern, mark] of PATTERNS) {
57
+ if (pattern.test(value)) return mark
58
+ }
59
+ }
60
+ return undefined
61
+ }
62
+
63
+ /**
64
+ * `hasOwnProperty`, not `in`: the names come from user-controlled catalog data,
65
+ * and `"constructor" in STACK_MARK_PATHS` is `true` — which would hand a
66
+ * function to `<path d>`.
67
+ */
68
+ function markPath(name?: string | null): string | undefined {
69
+ if (!name || !Object.prototype.hasOwnProperty.call(STACK_MARK_PATHS, name)) return undefined
70
+ return STACK_MARK_PATHS[name as StackMarkName]
71
+ }
72
+
73
+ export interface StackMarkProps {
74
+ /** Known brand. Unknown/absent → a generic "box of stuff" glyph. */
75
+ name?: StackMarkName | string | null
76
+ className?: string
77
+ }
78
+
79
+ /**
80
+ * A runtime's logo, single-path and `currentColor`, so it takes the warm tone
81
+ * of whatever it sits in instead of importing a brand's own palette into the
82
+ * grid. Never a network request: the paths ship with the package.
83
+ */
84
+ export function StackMark({ name, className }: StackMarkProps) {
85
+ const d = markPath(name)
86
+ if (!d) return <BoxesIcon className={cn("h-4 w-4", className)} aria-hidden />
87
+ return (
88
+ <svg viewBox="0 0 24 24" fill="currentColor" className={cn("h-4 w-4", className)} aria-hidden>
89
+ <path d={d} />
90
+ </svg>
91
+ )
92
+ }
@@ -0,0 +1,84 @@
1
+ // Structural, deliberately laxist shapes for the three devcontainer catalogs.
2
+ //
3
+ // Same rule as the worker surface: the design system never imports the
4
+ // OpenAPI-generated types of an app. The dashboard's `DevFeature` has an
5
+ // `ociRef` *or* a `localScript`, Spunto Lite's catalog is a hand-written
6
+ // constant, and an Open VSX search result is whatever open-vsx.org felt like
7
+ // returning that day. So the types below describe the *minimum* the cards read
8
+ // — everything optional — and every derivation degrades to a fallback instead
9
+ // of throwing on a shape it doesn't recognise.
10
+
11
+ /** Who publishes a catalog entry: an avatar, a name, and whether it's vouched for. */
12
+ export interface CatalogPublisher {
13
+ /** Human label — "Dev Containers", "dbaeumer". Falls back to `login`. */
14
+ name?: string
15
+ /** Handle behind the registry namespace (`devcontainers`, `stu-bell`…). */
16
+ login?: string
17
+ avatarUrl?: string | null
18
+ /** Registry/marketplace-verified namespace → a small check next to the name. */
19
+ verified?: boolean
20
+ }
21
+
22
+ /** A base image of the devcontainer catalog. */
23
+ export interface DevcontainerImageEntry {
24
+ id?: string
25
+ label?: string
26
+ /** Full image reference, e.g. `mcr.microsoft.com/devcontainers/typescript-node:20`. */
27
+ image?: string
28
+ description?: string
29
+ /** Overrides the publisher derived from the registry. */
30
+ publisher?: CatalogPublisher
31
+ /** Overrides the brand mark derived from the ref (`node`, `python`…). */
32
+ mark?: string
33
+ /** Real logo (a URL) instead of an embedded brand mark. */
34
+ iconUrl?: string | null
35
+ }
36
+
37
+ /** One configurable option of a feature, as the catalog advertises it. */
38
+ export interface DevcontainerFeatureOption {
39
+ name?: string
40
+ default?: string
41
+ description?: string
42
+ }
43
+
44
+ /** A devcontainer feature of the catalog. */
45
+ export interface DevcontainerFeatureEntry {
46
+ id?: string
47
+ label?: string
48
+ /** OCI reference the feature is installed from. */
49
+ ociRef?: string
50
+ /** Feature installed from a script shipped by the platform, not from a registry. */
51
+ localScript?: string
52
+ description?: string
53
+ options?: DevcontainerFeatureOption[]
54
+ /** Options the platform always passes, on top of the catalog defaults. */
55
+ defaultOptions?: Record<string, string> | null
56
+ publisher?: CatalogPublisher
57
+ mark?: string
58
+ iconUrl?: string | null
59
+ }
60
+
61
+ /**
62
+ * A VS Code extension. Shaped to swallow an Open VSX search result as-is
63
+ * (`namespace` + `name` + `files.icon` + `downloadCount`…) as well as the
64
+ * flattened form an app might store.
65
+ */
66
+ export interface VscodeExtensionEntry {
67
+ /** `namespace.name` — the id code-server installs. Derived from the two when absent. */
68
+ id?: string
69
+ namespace?: string
70
+ name?: string
71
+ displayName?: string
72
+ description?: string
73
+ version?: string
74
+ /** Flattened icon URL. Takes precedence over `files.icon`. */
75
+ iconUrl?: string | null
76
+ /** Raw Open VSX payload — only `icon` is read. */
77
+ files?: { icon?: string | null } | null
78
+ downloadCount?: number | null
79
+ averageRating?: number | null
80
+ reviewCount?: number | null
81
+ /** Open VSX `verified` — the namespace is owned by its publisher. */
82
+ verified?: boolean
83
+ publisher?: CatalogPublisher
84
+ }
@@ -0,0 +1,148 @@
1
+ "use client"
2
+
3
+ import type { ReactNode } from "react"
4
+ import { ChevronDownIcon, Settings2Icon } from "lucide-react"
5
+
6
+ import { cn } from "../../utils"
7
+ import type { ProjectFormSectionId, ProjectFormValue, ProjectSecretRef } from "./types"
8
+
9
+ export interface AdvancedDisclosureProps {
10
+ open: boolean
11
+ onOpenChange: (open: boolean) => void
12
+ label?: string
13
+ hint?: string
14
+ /**
15
+ * What's configured inside, shown while it's closed. Empty → a "nothing set"
16
+ * line. This is what makes folding the sections away safe rather than sneaky.
17
+ */
18
+ summary?: string[]
19
+ children: ReactNode
20
+ className?: string
21
+ }
22
+
23
+ /**
24
+ * The fold that hides the sections most people never touch.
25
+ *
26
+ * Two rules make it honest. It **says what's inside while it's closed** — a
27
+ * summary of the configured items, not just a chevron — and the parent form
28
+ * **opens it on its own** when any of those sections already holds a value.
29
+ * Between them, no setting can ever be hidden behind a shut drawer: a fold that
30
+ * conceals configuration you didn't set yourself is how people end up debugging
31
+ * a worker for twenty minutes over a post-create command they can't see.
32
+ */
33
+ export function AdvancedDisclosure({
34
+ open,
35
+ onOpenChange,
36
+ label = "Advanced options",
37
+ hint = "Features, extensions, lifecycle commands, secrets — the environment beyond the base image.",
38
+ summary = [],
39
+ children,
40
+ className,
41
+ }: AdvancedDisclosureProps) {
42
+ return (
43
+ <div className={cn("space-y-4", className)}>
44
+ <button
45
+ type="button"
46
+ onClick={() => onOpenChange(!open)}
47
+ aria-expanded={open}
48
+ data-slot="advanced-disclosure"
49
+ className={cn(
50
+ // `items-start`: on a phone the hint wraps to three or four lines, and
51
+ // a vertically-centred icon then floats halfway down a paragraph it's
52
+ // supposed to label. Padding follows the form's width, like the sections.
53
+ "flex w-full cursor-pointer items-start gap-3 rounded-xl border border-dashed px-4 py-4 text-left transition-colors @min-[30rem]/form:px-5",
54
+ open ? "border-border bg-muted/30" : "border-border hover:border-muted-foreground/40 hover:bg-muted/30"
55
+ )}
56
+ >
57
+ <span className="mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-muted text-muted-foreground">
58
+ <Settings2Icon className="h-4 w-4" />
59
+ </span>
60
+
61
+ <span className="min-w-0 flex-1">
62
+ <span className="block font-display text-base font-bold tracking-tight">{label}</span>
63
+ {/* While it's open the hint is redundant — the sections are right
64
+ there. While it's closed, the summary is the only trace of what
65
+ they hold, so it takes the line. */}
66
+ {open ? (
67
+ <span className="block text-xs text-muted-foreground">{hint}</span>
68
+ ) : summary.length > 0 ? (
69
+ <span className="mt-1 flex flex-wrap items-center gap-x-1.5 gap-y-1">
70
+ {summary.map((item) => (
71
+ <span
72
+ key={item}
73
+ className="inline-flex items-center rounded border border-primary/25 bg-primary/5 px-1.5 py-0.5 font-mono text-[10px] leading-none text-primary"
74
+ >
75
+ {item}
76
+ </span>
77
+ ))}
78
+ </span>
79
+ ) : (
80
+ // Clamped: closed and empty, this is a caption, and four lines of it
81
+ // on a phone is longer than the control it describes.
82
+ <span className="line-clamp-2 block text-xs text-muted-foreground">{hint}</span>
83
+ )}
84
+ </span>
85
+
86
+ <ChevronDownIcon
87
+ className={cn("mt-2 h-4 w-4 shrink-0 text-muted-foreground transition-transform", open && "rotate-180")}
88
+ aria-hidden
89
+ />
90
+ </button>
91
+
92
+ {/* Unmounted rather than hidden: these sections carry inputs, and a
93
+ `display:none` fieldset still tab-focuses and still validates. */}
94
+ {open && children}
95
+ </div>
96
+ )
97
+ }
98
+
99
+ /**
100
+ * One short label per advanced section that holds something — `2 features`,
101
+ * `lifecycle`, `3 secrets`. Pure, and exported, because a caller rendering its
102
+ * own fold needs the same sentence.
103
+ */
104
+ export function advancedSummary(
105
+ value: ProjectFormValue,
106
+ sections: ProjectFormSectionId[],
107
+ /**
108
+ * Secrets already stored server-side. They count: a project whose only
109
+ * advanced setting is three secrets would otherwise show an empty summary and
110
+ * keep the fold shut over them — exactly the "hides configuration you didn't
111
+ * set yourself" failure this component exists to avoid.
112
+ */
113
+ savedSecrets: ProjectSecretRef[] = []
114
+ ): string[] {
115
+ const out: string[] = []
116
+ for (const id of sections) {
117
+ switch (id) {
118
+ case "features":
119
+ if (value.features.length) out.push(`${value.features.length} feature${value.features.length > 1 ? "s" : ""}`)
120
+ break
121
+ case "extensions":
122
+ if (value.vscodeExtensions.length)
123
+ out.push(`${value.vscodeExtensions.length} extension${value.vscodeExtensions.length > 1 ? "s" : ""}`)
124
+ break
125
+ case "lifecycle":
126
+ if (value.postCreateCommand || value.postStartCommand) out.push("lifecycle")
127
+ break
128
+ case "ports":
129
+ if (value.forwardPorts.length) out.push(`ports ${value.forwardPorts.join(", ")}`)
130
+ break
131
+ case "prewarm":
132
+ if (value.prewarmImages.length) out.push(`${value.prewarmImages.length} prewarm`)
133
+ break
134
+ case "docker":
135
+ if (value.dockerInDocker) out.push("docker-in-docker")
136
+ break
137
+ case "secrets": {
138
+ const n = value.secrets.length + savedSecrets.filter((x) => !value.removedSecretIds.includes(x.id)).length
139
+ if (n) out.push(`${n} secret${n > 1 ? "s" : ""}`)
140
+ break
141
+ }
142
+ // Identity, image and repositories are never advanced — they're the form.
143
+ default:
144
+ break
145
+ }
146
+ }
147
+ return out
148
+ }