Skip to content

Commit 4ac4c0f

Browse files
committed
feat: add support for a template registry
1 parent 705d10d commit 4ac4c0f

File tree

4 files changed

+97
-1
lines changed

4 files changed

+97
-1
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
"unbuild": "^3.3.1",
6060
"vitest": "^3.0.5"
6161
},
62-
"packageManager": "pnpm@10.2.1",
62+
"packageManager": "pnpm@10.5.1+sha512.c424c076bd25c1a5b188c37bb1ca56cc1e136fbf530d98bcb3289982a08fd25527b8c9c4ec113be5e3393c39af04521dd647bcf1d0801eaf8ac6a7b14da313af",
6363
"dependencies": {
6464
"@clack/prompts": "^0.7.0",
6565
"commander": "^13.1.0",

src/registry/registry.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Registry, validateRegistry } from './validate-registry'
2+
3+
export const defaultIndex = `https://raw.githubusercontent.com/solana-developers/solana-templates/refs/heads/main/index.json`
4+
export const solanaTemplatesIndex = process.env.SOLANA_TEMPLATES_INDEX ?? defaultIndex
5+
6+
export async function getRegistry(): Promise<Registry> {
7+
const result = await fetch(solanaTemplatesIndex)
8+
9+
if (!result.ok) {
10+
throw new Error(`Failed to fetch registry index file: ${solanaTemplatesIndex}`)
11+
}
12+
13+
return validateRegistry(await result.json())
14+
}

src/registry/validate-registry.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { z } from 'zod'
2+
3+
// Template schema
4+
const registryTemplateSchema = z.object({
5+
name: z.string(),
6+
description: z.string(),
7+
repository: z.string(),
8+
})
9+
10+
// Group schema
11+
const registryGroupSchema = z.object({
12+
id: z.string(),
13+
name: z.string(),
14+
description: z.string(),
15+
templates: z.array(registryTemplateSchema),
16+
})
17+
18+
// Section schema (default/legacy)
19+
const registrySectionSchema = z.object({
20+
id: z.string(),
21+
name: z.string(),
22+
groups: z.array(registryGroupSchema),
23+
})
24+
25+
// Main schema: requires 'default' and allows additional sections
26+
const registrySchema = z
27+
.object({
28+
default: registrySectionSchema, // 'default' is required
29+
})
30+
.catchall(registrySectionSchema) // Any other key is optional and must match registrySectionSchema
31+
32+
// Type inference
33+
export type Registry = z.infer<typeof registrySchema>
34+
export type RegistrySection = z.infer<typeof registrySectionSchema>
35+
export type RegistryGroup = z.infer<typeof registryGroupSchema>
36+
export type RegistryTemplate = z.infer<typeof registryTemplateSchema>
37+
38+
export async function validateRegistry(resultJson: unknown): Promise<Registry> {
39+
return registrySchema.parse(resultJson)
40+
}

src/utils/get-args.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { intro, log, outro } from '@clack/prompts'
22
import { program } from 'commander'
3+
import pico from 'picocolors'
4+
import { getRegistry } from '../registry/registry'
5+
import { Registry, RegistrySection } from '../registry/validate-registry'
36
import { findTemplate, listTemplates, Template } from '../templates/templates'
47
import { AppInfo } from './get-app-info'
58
import { GetArgsResult } from './get-args-result'
@@ -19,6 +22,7 @@ export async function getArgs(argv: string[], app: AppInfo, pm: PackageManager =
1922
.option('-d, --dry-run', help('Dry run (default: false)'))
2023
.option('-t, --template <template-name>', help('Use a template'))
2124
.option('--list-templates', help('List available templates'))
25+
.option('--list-registry', help('List the templates in the registry'))
2226
.option('--list-versions', help('Verify your versions of Anchor, AVM, Rust, and Solana'))
2327
.option('--skip-git', help('Skip git initialization'))
2428
.option('--skip-init', help('Skip running the init script'))
@@ -42,10 +46,16 @@ Examples:
4246
// Get the options from the command line
4347
const result = input.opts()
4448

49+
const registry = await getRegistry()
50+
4551
if (result.listVersions) {
4652
listVersions()
4753
process.exit(0)
4854
}
55+
if (result.listRegistry) {
56+
listRegistry(registry)
57+
process.exit(0)
58+
}
4959
if (result.listTemplates) {
5060
listTemplates()
5161
outro(
@@ -116,3 +126,35 @@ function help(text: string) {
116126
117127
${text}`
118128
}
129+
130+
function listRegistry(registry: Registry) {
131+
const { default: defaultRegistry, ...otherRegistries } = registry
132+
133+
printRegistrySection(defaultRegistry)
134+
135+
const others = Object.keys(otherRegistries)
136+
137+
for (const key of others) {
138+
printRegistrySection(otherRegistries[key])
139+
}
140+
}
141+
142+
function printRegistrySection(section: RegistrySection) {
143+
console.log(` === ${section.id} === `)
144+
console.log(`Name: ${section.name}`)
145+
// console.log(`Description: ${section.description}`) // TODO: add description to index.json and the validation
146+
console.log(`Groups: ${section.groups.length}`)
147+
148+
for (const group of section.groups) {
149+
console.log(` === ${group.id} === `)
150+
console.log(`Name: ${group.name}`)
151+
console.log(pico.gray(`Description: ${group.description}`))
152+
console.log(pico.gray(`Templates: ${group.templates.length}`))
153+
154+
for (const template of group.templates) {
155+
console.log(` ${template.name}`)
156+
console.log(pico.gray(` Description: ${template.description}`))
157+
console.log(pico.gray(` Repository: ${template.repository}`))
158+
}
159+
}
160+
}

0 commit comments

Comments
 (0)