Skip to content

Commit

Permalink
feat: add cliffy + node support (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanleecode authored Jun 6, 2023
1 parent 56fde66 commit d3dca06
Show file tree
Hide file tree
Showing 8 changed files with 200 additions and 146 deletions.
27 changes: 27 additions & 0 deletions cli/browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { unimplemented } from "../deps/std/assert.ts"
import { Buffer } from "../deps/std/io.ts"

export interface BrowserRunFlags {
browser?: string
project: string
reload: string
}

export default function run(_: BrowserRunFlags) {
return async (pathname: string, logs: Buffer): Promise<number> => {
unimplemented()

// TODO
// const project = rest.project ?? await (async () => {
// for (const pathname of ["deno.json", "deno.jsonc"]) {
// try {
// return JSON.parse(await Deno.readTextFile(pathname))
// } catch (_e) {}
// }
// return
// })()
// const _importMapURL = project.importMap
// ? path.toFileUrl(path.join(Deno.cwd(), project.importMap))
// : undefined
}
}
41 changes: 41 additions & 0 deletions cli/process.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Buffer, readLines } from "../deps/std/io.ts"
import { readerFromStreamReader, writeAll } from "../deps/std/streams.ts"

export interface DenoRunFlags {
reload?: string
}

export function runDeno({ reload }: DenoRunFlags) {
return async (pathname: string, logs: Buffer): Promise<number> => {
const flags = reload ? [`-r${reload === "" ? "" : `=${reload}`}`] : []
const process = new Deno.Command(Deno.execPath(), {
args: ["run", "-A", ...flags, pathname],
stdout: "piped",
stderr: "piped",
}).spawn()
return collectResults(process, logs)
}
}

export function runNode({}: Record<string, unknown>) {
return async (pathname: string, logs: Buffer): Promise<number> => {
const process = new Deno.Command("node", {
args: [pathname],
stdout: "piped",
stderr: "piped",
}).spawn()
return collectResults(process, logs)
}
}

async function collectResults(process: Deno.ChildProcess, logs: Buffer) {
const [{ code }] = await Promise.all([
process.status,
...[process.stdout, process.stderr].map(async (stream) => {
const lineIter = readLines(readerFromStreamReader(stream.getReader()))
const encoder = new TextEncoder()
for await (const line of lineIter) await writeAll(logs, encoder.encode(`${line}\n`))
}),
])
return code
}
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/cliffy@v0.25.7/command/mod.ts"
1 change: 0 additions & 1 deletion deps/esbuild.ts

This file was deleted.

1 change: 0 additions & 1 deletion deps/esbuild_deno_loader.ts

This file was deleted.

106 changes: 106 additions & 0 deletions main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import runBrowser from "./cli/browser.ts"
import { runDeno, runNode } from "./cli/process.ts"
import { Command } from "./deps/cliffy.ts"
import { blue, dim, gray, green, red, yellow } from "./deps/std/fmt/colors.ts"
import { walk } from "./deps/std/fs.ts"
import { Buffer } from "./deps/std/io.ts"
import * as path from "./deps/std/path.ts"
import { parseFrontmatter } from "./frontmatter.ts"
import { runWithConcurrency } from "./util.ts"

interface GlobalRunnerParams {
concurrency: number
skip: boolean
}

type Run<T extends GlobalRunnerParams> = (
t: Omit<T, keyof GlobalRunnerParams>,
) => (pathname: string, logs: Buffer) => Promise<number>

const globalRunner = <T extends GlobalRunnerParams>(f: Run<T>) => {
return async ({ concurrency, skip, ...rest }: T, includePatterns: string) => {
const include: string[] = []
for await (
const { path: pathname } of walk(".", {
exts: [".ts", ".tsx", ".js"],
followSymlinks: true,
includeDirs: false,
match: includePatterns.split(" ").map((value) => {
if (typeof value !== "string") {
throw new Error(
`Specified an invalid include \`${value}\` (expected a glob or path to example file)`,
)
}
return path.isGlob(value) ? path.globToRegExp(value) : new RegExp(value)
}),
})
) include.push(pathname)

const failed: string[] = []
let passed = 0
let skipped = 0

await runWithConcurrency(
include.map((pathname) => async () => {
const { frontmatter } = parseFrontmatter(pathname, await Deno.readTextFile(pathname), {
test_skip(value) {
return value !== undefined
},
})
const quotedPathname = `"${pathname}"`
if (skip && frontmatter.test_skip) {
console.log(yellow("Skipped"), quotedPathname)
skipped++
return
}
console.log(gray("Testing"), quotedPathname)
const logs = new Buffer()
const code = await f(rest)(pathname, logs)
passed++
const progress = dim(`(${passed + skipped}/${include.length})`)
if (code) {
failed.push(pathname)
console.log(red("Failed"), progress, quotedPathname)
console.log(new TextDecoder().decode(logs.bytes()))
} else {
console.log(green("Passed"), progress, quotedPathname)
}
}),
concurrency,
)

if (failed.length) {
console.log(`${red("Erroring examples")}: \n - "${failed.join(`"\n - "`)}"`)
Deno.exit(1)
} else {
if (passed) console.log(blue(`${passed} examples passed`))
if (skipped) console.log(gray(`${skipped} examples skipped`))
Deno.exit(0)
}
}
}

await new Command()
.name("egts")
.description("Example-related utilities used in Capi")
.command(
"test",
new Command()
.name("test")
.globalOption("-c, --concurrency <concurrency:integer>", "concurrency", { default: Infinity })
.globalOption("--no-skip", "ignore skip frontmatter")
.command("deno")
.arguments("<includePatterns..>")
.option("-r, --reload <reload>", "reload")
.action(globalRunner(runDeno))
.command("node")
.arguments("<includePatterns..>")
.action(globalRunner(runNode))
.command("browser")
.arguments("<includePatterns..>")
.option("-b, --browser <binary>", "browser binary")
.option("-p, --project <project>", "project", { required: true })
.option("-r, --reload <reload>", "reload", { required: true })
.action(globalRunner(runBrowser)),
)
.parse(Deno.args)
144 changes: 0 additions & 144 deletions test.ts

This file was deleted.

25 changes: 25 additions & 0 deletions util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { deferred } from "./deps/std/async.ts"

export function runWithConcurrency<T>(fns: ReadonlyArray<() => Promise<T>>, concurrency: number) {
const queue = [...fns]
let running = 0
const results: Promise<T>[] = []
const final = deferred<T[]>()
flushQueue()
return final

function flushQueue() {
for (; running < concurrency; running++) {
if (!queue.length) {
final.resolve(Promise.all(results))
return
}
const promise = queue.shift()!()
results.push(promise)
promise.finally(() => {
running--
flushQueue()
})
}
}
}

0 comments on commit d3dca06

Please sign in to comment.