Skip to content

Commit

Permalink
Node.js and Deno support
Browse files Browse the repository at this point in the history
  • Loading branch information
christian-bromann committed Nov 1, 2022
1 parent 015bb4b commit 1321c57
Show file tree
Hide file tree
Showing 16 changed files with 1,271 additions and 2 deletions.
15 changes: 15 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org

root = true

[*]

indent_style = space
indent_size = 2

end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
82 changes: 82 additions & 0 deletions .github/scripts/downloadWasm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import fsp from 'node:fs/promises'
import url from 'node:url'
import path from 'node:path'
import zlib from 'node:zlib'
import readline from 'node:readline'
import tar from 'tar-fs'
import { promisify } from 'node:util'
import { pipeline } from 'node:stream'
import { Octokit } from '@octokit/rest'

const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
const streamPipeline = promisify(pipeline)

const owner = 'stateful'
const repo = 'runme'
const assetName = 'runme_js_wasm.tar.gz'

function verifyNodeVersion () {
const [major] = process.version.slice(1).split('.')
if (parseInt(major, 10) < 18) {
throw new Error(`This script requires Node.js v18! Please run "nvm install".`)
}
}

async function getGitHubToken () {
let token = process.env.GITHUB_TOKEN
if (!token) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});

token = await new Promise((resolve, reject) => rl.question('🔐 Enter a valid GitHub token...\n> ', (name) => {
if (!name) {
return reject(new Error('⚠️ Invalid token!'))
}
rl.close()
return resolve(name)
}))
}
return token
}

async function downloadWasm (token) {
const octokit = new Octokit({ auth: token })
const wasmReleaseDefined = Boolean(process.env.RUNME_VERSION)
const releases = await octokit.repos.listReleases({ owner, repo })
const release = wasmReleaseDefined
? releases.data.find((r) => r.tag_name === process.env.RUNME_VERSION)
: releases.data[0]

if (wasmReleaseDefined && !release) {
throw new Error(
`No release found with tag name "${process.env.RUNME_VERSION}", ` +
`available releases: ${releases.data.map((r) => r.tag_name).join(', ')}`
)
}
if (!wasmReleaseDefined) {
console.info(`No specific runme CLI version defined via "RUNME_VERSION", downloading ${release.tag_name}`)
}

const asset = release.assets.find((a) => a.name === assetName)
if (!asset) {
throw new Error(`Couldn't find WASM asset with name "${assetName}" in release with tag name ${release.tag_name}`)
}

console.log(`Downloading ${asset.browser_download_url}...`)
const res = await fetch(asset.browser_download_url)
const targetDir = path.resolve(__dirname, '..', '..', 'wasm')
const wasmFilePath = path.resolve(targetDir, 'runme.wasm')

await fsp.mkdir(targetDir, { recursive: true })
await streamPipeline(res.body, zlib.createGunzip(), tar.extract(targetDir))
console.log(`✅ Successfully downloaded and unpacked WASM file to ${wasmFilePath}`)
}

/**
* program:
*/
verifyNodeVersion()
const token = await getGitHubToken()
await downloadWasm(token)
21 changes: 21 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Test Changes

on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x, 18.x]
steps:
- name: Clone Repository
uses: actions/checkout@v2
- name: Setup Node version
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Run Tests
run: yarn test
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,6 @@ dist

# TernJS port file
.tern-port

# Runme wasm download dir
/wasm
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v18.11.0
34 changes: 32 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,32 @@
# runmejs
A JavaScript module to use Runme in Node.js, Deno or browser environments
# Runme.js

> A JavaScript module to use Runme in Node.js, Deno or browser environments.
_Runme.js_ contains the the [Runme CLI](https://github.com/stateful/runme) as WASM and allows to access its functionality through a simple JavaScript interface. The module exposes the following methods:

### `parse`

Parse markdown into AST:

```ts
import { parse } from 'runme'

console.log(await parse('./README.md'))
/**
* outputs:
* [
* ...
* {
* content: 'echo "hello world"\n',
* name: 'echo-hello',
* language: 'sh',
* lines: [ 'echo "hello world"' ]
* },
* ...
* ]
*/
```

---

<p align="center"><small>Copyright 2022 © <a href="http://stateful.com/">Stateful</a> – Apache 2.0 License</small></p>
36 changes: 36 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "runme",
"version": "0.0.0",
"author": "Christian Bromann <christian@stateful.com>",
"license": "Apache-2.0",
"description": "A JavaScript module to use Runme in Node.js, Deno or browser environments",
"homepage": "https://github.com/stateful/runmejs#readme",
"exports": "./dist/index.js",
"type": "module",
"repository": {
"type": "git",
"url": "git+https://github.com/stateful/runmejs.git"
},
"keywords": [
"runme"
],
"bugs": {
"url": "https://github.com/stateful/runmejs/issues"
},
"scripts": {
"compile": "tsc -p ./tsconfig.json",
"download:wasm": "node .github/scripts/downloadWasm.js",
"test": "echo \"Error: no test specified\" && exit 1",
"watch": "npm run compile -- --watch"
},
"devDependencies": {
"@octokit/rest": "^19.0.5",
"@types/node": "^18.11.9",
"tar-fs": "^2.1.1",
"typescript": "^4.8.4"
},
"dependencies": {
"@assemblyscript/loader": "^0.22.0",
"commander": "^9.4.1"
}
}
45 changes: 45 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { loadWasm } from './runtime/index'
import type { ParsedDocument } from './types'

import './wasm/wasm_exec.js'

declare global {
var Go: new () => any
var GetDocument: (md: string) => ParsedDocument
}

const go = new globalThis.Go()
let wasm: WebAssembly.WebAssemblyInstantiatedSource

async function initWasm () {
/**
* check if already initiated
*/
if (typeof globalThis['GetDocument'] === 'function') {
return
}

const wasmBuffer = await loadWasm()
if (!wasm) {
wasm = await WebAssembly.instantiate(wasmBuffer, go.importObject)
}

/**
* listen on process exit to avoid deadlock
*/
const initPromise = process.on && new Promise((resolve) => process.on('exit', resolve))
go.run(wasm.instance)
await initPromise
}

export default async function parse (content: string) {
await initWasm()

const { document } = await globalThis.GetDocument(content)
return document
}

console.log(await parse('## Hello World\n'))
console.log(await parse('## Hello World\n'))
console.log(await parse('## Hello World\n'))
console.log(await parse('## Hello World\n'))
8 changes: 8 additions & 0 deletions src/runtime/browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const runmeWASMUrl = 'http://localhost:8080/wasm/runme.wasm'
let runme: Promise<Response>
export async function loadWasm () {
if (!runme) {
runme = fetch(runmeWASMUrl)
}
return (await runme).arrayBuffer()
}
13 changes: 13 additions & 0 deletions src/runtime/deno.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// @ts-expect-error Deno
import { resolve } from 'https://deno.land/std/path/mod.ts'

const __dirname = new URL('.', import.meta.url).pathname
const runmeWASMPath = resolve(__dirname, '..', '..', 'wasm', 'runme.wasm')
let runme: Promise<Buffer>
export function loadWasm () {
if (!runme) {
// @ts-expect-error Deno
runme = Deno.readFile(runmeWASMPath)
}
return runme
}
18 changes: 18 additions & 0 deletions src/runtime/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export async function loadWasm () {
if (typeof process !== 'undefined' && process.version) {
const runtime = await import('./node.js')
return runtime.loadWasm()
}

if (typeof window !== 'undefined' && 'Deno' in window) {
// @ts-expect-error
const runtime = await import('./deno.ts')
return runtime.loadWasm()
}

if (typeof window !== 'undefined') {
throw new Error(`browser environment not yet supported`)
}

throw new Error(`No other environment supported yet`)
}
20 changes: 20 additions & 0 deletions src/runtime/node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import fsImport from 'node:fs/promises'
import urlImport from 'node:url'
import pathImport from 'node:path'

/**
* export built in modules
*/
export const fs = fsImport
export const url = urlImport
export const path = pathImport

const dir = url.fileURLToPath(new URL('.', import.meta.url))
const runmeWASMPath = path.resolve(dir, '..', '..', 'wasm', 'runme.wasm')
let runme: Promise<Buffer>
export function loadWasm () {
if (!runme) {
runme = fs.readFile(runmeWASMPath)
}
return runme
}
17 changes: 17 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export interface MarkdownSection {
name?: string
content?: string
description?: string
markdown?: string
language?: string
lines?: string[]
attributes?: Metadata
}

export interface ParsedDocument {
document?: MarkdownSection[]
}

export interface Metadata {
[key: string]: any
}
Loading

0 comments on commit 1321c57

Please sign in to comment.