246 lines
6.9 KiB
JavaScript
246 lines
6.9 KiB
JavaScript
import { readFile } from 'node:fs/promises'
|
|
import path from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
import test from 'brittle'
|
|
import b4a from 'b4a'
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor
|
|
|
|
async function loadPearBin() {
|
|
const runtime = await readFile(path.join(__dirname, '../lib/runtime.js'), 'utf8')
|
|
const stage = await readFile(path.join(__dirname, '../lib/pear/pear-stage.js'), 'utf8')
|
|
const release = await readFile(path.join(__dirname, '../lib/pear/pear-release.js'), 'utf8')
|
|
const body = await readFile(path.join(__dirname, '../src/pear.js'), 'utf8')
|
|
return new AsyncFunction(
|
|
'ctx',
|
|
'argv',
|
|
`${runtime}\n${stage}\n${release}\n${body}\nif (typeof run === 'function') return await run(ctx, argv)\n`
|
|
)
|
|
}
|
|
|
|
function mkMemVfs(initial = {}) {
|
|
/** @type {Map<string, Uint8Array>} */
|
|
const files = new Map(Object.entries(initial))
|
|
const vfs = {
|
|
env: {},
|
|
files,
|
|
getcwd() {
|
|
return '/home/guest/pear-projects/my-pear-app'
|
|
},
|
|
async mkdir(p) {
|
|
void p
|
|
},
|
|
async readdir(p) {
|
|
const prefix = p.endsWith('/') ? p : p + '/'
|
|
const names = new Set()
|
|
for (const key of files.keys()) {
|
|
if (!key.startsWith(prefix)) continue
|
|
const rest = key.slice(prefix.length)
|
|
const seg = rest.split('/')[0]
|
|
if (seg) names.add(seg)
|
|
}
|
|
return [...names]
|
|
},
|
|
async stat(p) {
|
|
if (!files.has(p)) {
|
|
for (const key of files.keys()) {
|
|
if (key.startsWith(p + '/')) return { isDirectory: true, type: 'directory' }
|
|
}
|
|
throw new Error('ENOENT')
|
|
}
|
|
return { isDirectory: false, type: 'file' }
|
|
},
|
|
async readFile(p) {
|
|
if (!files.has(p)) throw new Error('ENOENT ' + p)
|
|
return files.get(p)
|
|
},
|
|
async writeFile(p, buf) {
|
|
files.set(p, buf instanceof Uint8Array ? buf : new Uint8Array(buf))
|
|
}
|
|
}
|
|
return vfs
|
|
}
|
|
|
|
function mkFakeHdms() {
|
|
const key = b4a.from('abc123')
|
|
const drive = {
|
|
key,
|
|
version: 7,
|
|
discoveryKey: b4a.from('disc'),
|
|
async flush() {}
|
|
}
|
|
/** @type {Map<string, { drive: typeof drive, writable: boolean, entry: object }>} */
|
|
const byLabel = new Map()
|
|
const hdms = {
|
|
active: true,
|
|
byLabel,
|
|
registry: { version: 1, drives: [] },
|
|
swarm: { join() {}, flush: async () => {} },
|
|
async create(ctx, label) {
|
|
byLabel.set(label, {
|
|
drive,
|
|
writable: true,
|
|
entry: { label, mode: 'writable', key: 'mockkey6' }
|
|
})
|
|
this.registry.drives.push({ label, mode: 'writable', key: 'mockkey6' })
|
|
ctx.console.log('Created ' + label)
|
|
},
|
|
async _openEntry(entry) {
|
|
byLabel.set(entry.label, {
|
|
drive,
|
|
writable: entry.mode === 'writable',
|
|
entry
|
|
})
|
|
}
|
|
}
|
|
return hdms
|
|
}
|
|
|
|
test('pear release mirrors stage to HDMS mount and writes release.json', async (t) => {
|
|
const project = '/home/guest/pear-projects/my-pear-app'
|
|
const stageDir = `${project}/.pear/stage`
|
|
const vfs = mkMemVfs({
|
|
[`${project}/.pear/stage/stage.json`]: b4a.from(
|
|
JSON.stringify({
|
|
name: 'my-pear-app',
|
|
stageDir,
|
|
entry: 'index.js',
|
|
bundleMethod: 'test'
|
|
}),
|
|
'utf8'
|
|
),
|
|
[`${project}/.pear/stage/package.json`]: b4a.from('{"name":"my-pear-app"}\n', 'utf8'),
|
|
[`${project}/.pear/stage/app.bundle.js`]: b4a.from('console.log(1)\n', 'utf8')
|
|
})
|
|
|
|
const hdms = mkFakeHdms()
|
|
const logs = []
|
|
const ctx = {
|
|
vfs,
|
|
b4a,
|
|
env: { HOME: '/home/guest', PWD: project },
|
|
identity: { state: 'unlocked' },
|
|
disk: { hdmsController: hdms },
|
|
bare: {
|
|
hypercoreIdEncoding: {
|
|
encode(buf) {
|
|
return 'z32_' + b4a.toString(buf, 'utf8')
|
|
},
|
|
decode() {
|
|
return b4a.alloc(0)
|
|
}
|
|
}
|
|
},
|
|
console: {
|
|
log(...a) {
|
|
logs.push(a.join(' '))
|
|
},
|
|
error() {},
|
|
warn() {}
|
|
}
|
|
}
|
|
|
|
const run = await loadPearBin()
|
|
await run(ctx, ['pear', 'release', '.'])
|
|
|
|
t.ok(vfs.files.has('/mnt/pear-my-pear-app/app.bundle.js'))
|
|
t.ok(vfs.files.has(`${project}/.pear/release.json`))
|
|
|
|
const rel = JSON.parse(b4a.toString(vfs.files.get(`${project}/.pear/release.json`), 'utf8'))
|
|
t.is(rel.label, 'pear-my-pear-app')
|
|
t.is(rel.latest.length, 7)
|
|
t.ok(rel.latest.pearLink.startsWith('pear://'))
|
|
t.ok(logs.some((l) => l.includes('versioned:')))
|
|
})
|
|
|
|
test('pear release requires staged tree', async (t) => {
|
|
const project = '/home/guest/pear-projects/empty'
|
|
const vfs = mkMemVfs({})
|
|
const ctx = {
|
|
vfs,
|
|
b4a,
|
|
env: { HOME: '/home/guest', PWD: project },
|
|
identity: { state: 'unlocked' },
|
|
disk: { hdmsController: mkFakeHdms() },
|
|
bare: { hypercoreIdEncoding: { encode: () => 'k', decode: () => b4a.alloc(0) } },
|
|
console: { log() {}, error() {}, warn() {} },
|
|
exitCode: 0
|
|
}
|
|
const run = await loadPearBin()
|
|
await run(ctx, ['pear', 'release', '.'])
|
|
t.is(ctx.exitCode, 1)
|
|
})
|
|
|
|
test('pear release works without ctx.bare (HDMS registry key)', async (t) => {
|
|
const project = '/home/guest/pear-projects/my-pear-app'
|
|
const stageDir = `${project}/.pear/stage`
|
|
const vfs = mkMemVfs({
|
|
[`${project}/.pear/stage/stage.json`]: b4a.from(
|
|
JSON.stringify({ name: 'my-pear-app', stageDir, entry: 'index.js' }),
|
|
'utf8'
|
|
),
|
|
[`${project}/.pear/stage/index.js`]: b4a.from('console.log(1)\n', 'utf8')
|
|
})
|
|
|
|
const hdms = mkFakeHdms()
|
|
const ctx = {
|
|
vfs,
|
|
b4a,
|
|
env: { HOME: '/home/guest', PWD: project },
|
|
identity: { state: 'unlocked' },
|
|
disk: { hdmsController: hdms },
|
|
console: { log() {}, error() {}, warn() {} }
|
|
}
|
|
|
|
const run = await loadPearBin()
|
|
await run(ctx, ['pear', 'release', '.', '--json'])
|
|
const rel = JSON.parse(b4a.toString(vfs.files.get(`${project}/.pear/release.json`), 'utf8'))
|
|
t.is(rel.latest.keyZ32, 'mockkey6')
|
|
})
|
|
|
|
test('pear seed opens existing release mount', async (t) => {
|
|
const project = '/home/guest/pear-projects/my-pear-app'
|
|
const vfs = mkMemVfs({
|
|
[`${project}/.pear/release.json`]: b4a.from(
|
|
JSON.stringify({
|
|
schema: 1,
|
|
label: 'pear-my-pear-app',
|
|
latest: {
|
|
label: 'pear-my-pear-app',
|
|
pearLink: 'pear://z32_abc123',
|
|
versionedLink: 'pear://0.7.z32_abc123',
|
|
length: 7
|
|
}
|
|
}),
|
|
'utf8'
|
|
)
|
|
})
|
|
|
|
const hdms = mkFakeHdms()
|
|
hdms.registry.drives.push({ label: 'pear-my-pear-app', mode: 'writable', key: 'mockkey6' })
|
|
|
|
const ctx = {
|
|
vfs,
|
|
b4a,
|
|
env: { HOME: '/home/guest', PWD: project },
|
|
identity: { state: 'unlocked' },
|
|
disk: { hdmsController: hdms },
|
|
bare: {
|
|
hypercoreIdEncoding: {
|
|
encode(buf) {
|
|
return 'z32_' + b4a.toString(buf, 'utf8')
|
|
},
|
|
decode() {
|
|
return b4a.alloc(0)
|
|
}
|
|
}
|
|
},
|
|
console: { log() {}, error() {}, warn() {} }
|
|
}
|
|
|
|
const run = await loadPearBin()
|
|
await run(ctx, ['pear', 'seed', '.', '--wait-ms', '0'])
|
|
t.ok(hdms.byLabel.has('pear-my-pear-app'))
|
|
})
|