reg
Version:
`reg` is a package manager for native ES Modules. It's built to enable dependency management for Universal JavaScript (JavaScript that can run in the Browser and in Node.js w/o a compiler).
133 lines (115 loc) • 3.38 kB
JavaScript
const createTypes = require('./types.js')
const path = require('path')
const types = createTypes({ codec: 'dag-json' })
const CID = require('cids')
const makeRegistry = require('./registry')
/*
Registry layout is simple. Every user has their own namespace
and can publish a package to that name.
/:github-username/:package-name
The registry is simply an authenticated k/v store that points
these namespaces to CIDs.
/@mikeal/bent => CID
The CID must be a valid Package.
*/
const { parse, print } = require('recast')
const { readFile } = require('fs').promises
// TODO: replace with better API on unixfs File, File.fromString
const fileIter = async function * (str) { yield Buffer.from(str) }
const importer = async function * (parser) {
const registry = makeRegistry()
const ast = await parser.parsed
const pending = []
const isLocal = s => {
if (s.startsWith('./')) return true
if (s.startsWith('../')) return true
return false
}
const deps = {}
const _parse = async function * (i) {
const dec = ast.program.body[i]
const source = dec.source.value
let cid
if (source.startsWith('@reg/')) {
// For some reason, this is already hash linked.
deps[source] = new CID(source.slice('@reg/'.length))
return
} else if (source.startsWith('@')) {
/* reserve @std/ for browser standard library */
if (source.startsWith('@std/')) return
const info = await registry.pkg(source)
if (!info) throw new Error(`No package in registry named ${source}`)
cid = new CID(info.pkg)
} else if (isLocal(source)) {
for await (let { root, block } of parser.resolve(source)) {
if (root) {
block = root.block()
cid = await block.cid()
}
yield { block }
}
} else {
throw new Error(`Unknown import "${source}"`)
}
deps[source] = cid
dec.source.value = `/@reg/${cid.toString()}.js`
const comment = parse(`// static-link("${source}")`).program.comments[0]
// TODO: figure out how to add the comment to the line
}
let i = 0
for (const dec of [...ast.program.body]) {
if (dec.type === 'ImportDeclaration') {
yield * _parse(i)
}
i++
}
const code = print(ast).code
let fileLink
const iter = types.File.fromIter(fileIter(code), 'test')
for await (let { block, root } of iter) {
if (root) {
block = root.block()
fileLink = await block.cid()
}
yield { block }
}
const pkg = types.Package.encoder({ v1: { file: await fileLink, deps } })
const block = pkg.block()
yield { root: pkg }
}
class Parser {
constructor (file) {
this.file = file
this.parsed = this.parse()
}
async parse () {
const buffer = await readFile(this.file)
return parse(buffer.toString())
}
imports () {
return importer(this)
}
resolve (local) {
const f = path.resolve(path.dirname(this.file), local)
const parser = new Parser(f)
return parser.imports()
}
}
const linker = async function * (file) {
const parser = new Parser(file)
yield * parser.imports()
}
module.exports = linker
/*
const push = async (file, putBlock) => {
for await (let { block, root } of linker(file)) {
// noop
}
return
const puts = []
const files = {}
// TODO: parse file and re-write all imports to
// CID references.
}
module.exports = push
*/