Skip to content
This repository has been archived by the owner on Sep 14, 2023. It is now read-only.

feat: add cliffy #946

Merged
merged 10 commits into from
May 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions cli/bin.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,38 @@
import { CapiBinary } from "../deps/capi_binary_builds.ts"
import { Command } from "../deps/cliffy.ts"

export default async function(
export const bin = new Command()
.description("Execute <binary>@<version> with [args...]")
.arguments("<binary:string> <version:string> [...args:string]")
.stopEarly()
.example("run a polkadot node in dev mode", "capi bin polkadot v0.9.41 --dev")
.example(
"build a chain spec for Rococo local",
"capi bin polkadot v0.9.41 build-spec --chain rococo-local",
)
.action(runBin)

async function runBin(
_options: void,
binary: string,
version: string,
...args: string[]
) {
if (!binary || !version) throw new Error("Must specify binary and version")

const bin = new CapiBinary(binary, version)

harrysolovay marked this conversation as resolved.
Show resolved Hide resolved
if (!(await bin.exists())) {
console.error("Downloading", bin.key)
await bin.download()
}

const child = new Deno.Command(bin.path, {
args,
stdin: "inherit",
stdout: "inherit",
stderr: "inherit",
}).spawn()

for (const signal of ["SIGTERM", "SIGINT"] satisfies Deno.Signal[]) {
Deno.addSignalListener(signal, () => {
child.kill(signal)
})
}

Deno.exit((await child.status).code)
}
27 changes: 19 additions & 8 deletions cli/resolveNets.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
import * as $ from "../deps/scale.ts"
import * as flags from "../deps/std/flags.ts"
import * as path from "../deps/std/path.ts"
import { NetSpec } from "../nets/mod.ts"

const $nets = $.record($.instance(NetSpec as new() => NetSpec, $.tuple(), () => []))

export async function resolveNets(...args: string[]): Promise<Record<string, NetSpec>> {
const { nets: netsPathRaw } = flags.parse(args, {
string: ["nets"],
default: { nets: "./nets.ts" },
})
const netsPath = path.resolve(netsPathRaw)
await Deno.stat(netsPath)
export async function resolveNets(maybeNetsPath?: string): Promise<Record<string, NetSpec>> {
let netsPath
if (maybeNetsPath) {
netsPath = path.resolve(maybeNetsPath)
await Deno.stat(netsPath)
} else {
for (const p of ["nets.ts", "nets.js"]) {
try {
const resolved = path.resolve(p)
await Deno.stat(resolved)
netsPath = resolved
} catch (_e) {}
}
}
if (!netsPath) {
throw new Error(
"Could not resolve net specs path. Create a `nets.ts` file and export a net spec.",
)
}
const nets = await import(path.toFileUrl(netsPath).toString())
$.assert($nets, nets)
for (const key in nets) nets[key]!.name = key
Expand Down
46 changes: 27 additions & 19 deletions cli/serve.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as flags from "../deps/std/flags.ts"
import { Command } from "../deps/cliffy.ts"
import { blue, gray, yellow } from "../deps/std/fmt/colors.ts"
import { serve } from "../deps/std/http.ts"
import { serve as httpServe } from "../deps/std/http.ts"
import {
createCodegenHandler,
createCorsHandler,
Expand All @@ -12,33 +12,41 @@ import { gracefulExit } from "../util/mod.ts"
import { tempDir } from "../util/tempDir.ts"
import { resolveNets } from "./resolveNets.ts"

export default async function(...args: string[]) {
const { port, "--": cmd, out, target } = flags.parse(args, {
string: ["port", "out", "target"],
default: {
port: "4646",
out: "target/capi",
},
"--": true,
})
export const serve = new Command()
.description("Start the Capi server")
.option("-n, --nets <nets:file>", "nets.ts file path")
.option("-p, --port <port:number>", "", { default: 4646 })
.option(
"-o, --out <out:string>",
"Directory at which disk-related operations (such as storing devnet logs and caching metadata) can occur",
{ default: "target/capi" },
)
.option("--target <target:string>", "target name in net.ts")
.action(runServe)

const nets = await resolveNets(...args)
export interface RunServeOptions {
nets?: string
port: number
out: string
target?: string
}

async function runServe(
this: { getLiteralArgs(): string[] },
{ nets: netsPath, port, out, target }: RunServeOptions,
) {
const literalArgs = this.getLiteralArgs()
const nets = await resolveNets(netsPath)
const devnetTempDir = await tempDir(out, "devnet")

const href = `http://localhost:${port}/`

const controller = new AbortController()
const { signal } = controller

const dataCache = new FsCache(out, signal)
const tempCache = new InMemoryCache(signal)

const running = await fetch(`${href}capi_cwd`)
.then((r) => r.text())
.then((r) => r === Deno.cwd())
.catch(() => false)

if (!running) {
const devnetsHandler = createDevnetsHandler(devnetTempDir, nets, signal)
const codegenHandler = createCodegenHandler(dataCache, tempCache)
Expand All @@ -52,7 +60,7 @@ export default async function(...args: string[]) {
}
return await codegenHandler(request)
}))
await serve(handler, {
await httpServe(handler, {
hostname: "::",
port: +port,
signal,
Expand All @@ -70,7 +78,7 @@ export default async function(...args: string[]) {
}

async function onReady() {
const [bin, ...args] = cmd
const [bin, ...args] = literalArgs
if (bin) {
const command = new Deno.Command(bin, {
args,
Expand Down
60 changes: 36 additions & 24 deletions cli/sync.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,53 @@
import * as flags from "../deps/std/flags.ts"
import { Command, EnumType } from "../deps/cliffy.ts"
import { blue, gray } from "../deps/std/fmt/colors.ts"
import { assertEquals } from "../deps/std/testing/asserts.ts"
import { syncNets } from "../server/mod.ts"
import { normalizePackageName } from "../util/mod.ts"
import { tempDir } from "../util/tempDir.ts"
import { resolveNets } from "./resolveNets.ts"

export default async function(...args: string[]) {
const {
"import-map": importMapFile,
"package-json": packageJsonFile,
check,
out,
server,
} = flags.parse(args, {
string: ["config", "import-map", "out", "package-json", "server"],
boolean: ["check"],
default: {
server: "https://capi.dev/",
out: "target/capi",
},
export const sync = new Command()
.type("runtime", new EnumType(["deno", "node"]))
.description("Sync net specs and update your manifest")
.arguments("<runtime:runtime>")
.option("-n, --nets <nets:file>", "nets.ts file path", { default: "./nets.ts" })
.option("--check", "ensures that metadata and codegen are in sync")
.option("-o, --out <out:string>", "Metadata and codegen output directory", {
default: "target/capi",
})
.option("-s, --server <server:string>", "", { default: "https://capi.dev/" })
.option(
"--runtime-config <runtimeConfig:string>",
"the import_map.json or package.json file path",
)
.action(runSync)

const netSpecs = await resolveNets(...args)
export interface RunSyncOptions {
nets: string
check?: true
out: string
server: string
runtimeConfig?: string
}

async function runSync({
nets: netsFile,
check,
out,
server,
runtimeConfig,
}: RunSyncOptions, runtime: string) {
const netSpecs = await resolveNets(netsFile)
const devnetTempDir = await tempDir(out, "devnet")

const baseUrl = await syncNets(server, devnetTempDir, netSpecs)

if (importMapFile) {
syncFile(importMapFile, (importMap) => {
if (runtime === "deno") {
runtimeConfig ??= "import_map.json"
syncFile(runtimeConfig, (importMap) => {
importMap.imports["@capi/"] = baseUrl
})
}

if (packageJsonFile) {
syncFile(packageJsonFile, (packageJson) => {
} else if (runtime === "node") {
runtimeConfig ??= "package.json"
syncFile(runtimeConfig, (packageJson) => {
const addedPackages = new Set()
for (const rawName of Object.keys(netSpecs ?? {})) {
const name = normalizePackageName(rawName)
Expand Down
2 changes: 1 addition & 1 deletion deno.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"cache": "deno task capi serve -- deno cache -r=http://localhost:4646/",
"check": "deno task capi serve -- deno cache --check",
"star": "deno task run _tasks/star.ts && deno task check target/star.ts",
"sync": "mkdir -p target && deno task capi serve -- deno task capi sync --server http://localhost:4646/ --import-map import_map.json",
"sync": "mkdir -p target && deno task capi serve -- deno task capi sync deno --server http://localhost:4646/",
"run": "deno task capi serve -- deno run -A -r=http://localhost:4646/"
}
}
1 change: 1 addition & 0 deletions deps/cliffy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "https://deno.land/x/[email protected]/command/mod.ts"
21 changes: 11 additions & 10 deletions main.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import "./deps/shims/register.ts"
import { Command } from "./deps/cliffy.ts"

import bin from "./cli/bin.ts"
import serve from "./cli/serve.ts"
import sync from "./cli/sync.ts"
import { bin } from "./cli/bin.ts"
import { serve } from "./cli/serve.ts"
import { sync } from "./cli/sync.ts"

const commands: Record<string, (...args: string[]) => void> = { bin, serve, sync }

if (Deno.args[0]! in commands) {
commands[Deno.args[0]!]!(...Deno.args.slice(1))
} else {
throw new Error("Unrecognized command")
}
await new Command()
.name("capi")
.description("Capi is a framework for crafting interactions with Substrate chains")
.command("bin", bin)
.command("sync", sync)
.command("serve", serve)
.parse(Deno.args)