Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adding support for child lookups #3

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
76 changes: 33 additions & 43 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { spawn } from 'child_process'
import { mkdir, readFile, writeFile, stat, utimes } from 'fs/promises'
import dbg from 'debug'
import * as path from 'path'
import { getMatcher, normalize } from './util'

const debug = dbg('@tradle/lambda-plugins')

Expand Down Expand Up @@ -199,49 +200,24 @@ async function assertInstalled (plugins: FNOrResult<string[]>, { tmpDir, maxAge
}
}

interface Cause {
useImport: boolean
cause: string
}

const CAUSE_FALLBACK: Cause = { cause: 'require', useImport: false }
const CAUSE_TYPE: Cause = { cause: 'import because of type=module', useImport: true }
const CAUSE_MODULE: Cause = { cause: 'import because of a defined module', useImport: true }
const CAUSE_DOT_EXPORT: Cause = { cause: 'import because of exports["."]', useImport: true }
const CAUSE_DOT_ANY: Cause = { cause: 'import because of exports["./*"]', useImport: true }

function fuzzyChooseImport (pkg: any): Cause {
if (pkg.type === 'module') return CAUSE_TYPE
if (pkg.module !== undefined) return CAUSE_MODULE
if (typeof pkg.exports === 'object' && pkg.exports !== null) {
if (pkg.exports['.']?.import !== undefined) {
return CAUSE_DOT_EXPORT
}
if (pkg.exports['./*']?.import !== undefined) {
return CAUSE_DOT_ANY
}
}
return CAUSE_FALLBACK
}

async function loadData (name: string, depPath: string, pkg: any): Promise<any> {
const { cause, useImport } = fuzzyChooseImport(pkg)
debug('Loading package for %s from %s (%s)', name, depPath, cause)
return useImport ? await import(depPath) : require(depPath)
}

async function loadPackage (name: string, depPath: string): Promise<any> {
const pkgPath = path.join(depPath, 'package.json')
debug('Loading package.json for %s from %s', name, pkgPath)
const data = await readFile(pkgPath, 'utf-8')
return JSON.parse(data)
let raw = '{}'
try {
raw = await readFile(pkgPath, 'utf-8')
debug('Using package.json for %s from %s', name, pkgPath)
} catch (err) {
// Package json is optional
debug('No package.json found at %s, using regular lookup', pkgPath)
}
return JSON.parse(raw)
}

export class Plugin {
readonly name: string
readonly path: string

#data: Promise<any> | undefined
#data: { [child: string]: Promise<any> } | undefined
#pkg: Promise<any> | undefined

constructor (name: string, path: string) {
Expand All @@ -259,15 +235,29 @@ export class Plugin {
return pkg
}

/* eslint-disable-next-line @typescript-eslint/promise-function-async */
data (opts?: { force?: boolean }): Promise<any> {
let data = this.#data
if (data === undefined || opts?.force !== true) {
/* eslint-disable-next-line @typescript-eslint/promise-function-async */
data = this.package(opts).then(pkg => loadData(this.name, this.path, pkg))
this.#data = data
async #loadData (child: string, force: boolean): Promise<any> {
const pkg = await this.package({ force })
const matcher = getMatcher(this.path, pkg)
const mjs = matcher(child, 'module')
/* eslint-disable-next-line @typescript-eslint/prefer-optional-chain */
if (mjs !== undefined && mjs.location !== null) {
debug('Importing package for %s from %s (%s)', this.name, mjs.location, mjs.cause)
return await import(mjs.location)
}
return data
const cjs = matcher(child, 'commonjs')
/* eslint-disable-next-line @typescript-eslint/prefer-optional-chain */
if (cjs !== undefined && cjs.location !== null) {
debug('Requiring package for %s from %s (%s)', this.name, cjs.location, cjs.cause)
return require(cjs.location)
}
throw new Error(`Can not require or import a package for ${this.name} at ${this.path}`)
}

/* eslint-disable-next-line @typescript-eslint/promise-function-async */
data (opts?: { force?: boolean, child?: string }): Promise<any> {
const all = this.#data ?? (this.#data = {})
const child = normalize(opts?.child)
return all[child] ?? (all[child] = this.#loadData(child, opts?.force ?? false))
}
}

Expand Down
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
"bin": "./bin/lambda-plugins",
"scripts": {
"prepare": "npm run build",
"build": "tsc && tsc -p tsconfig.cjs.json",
"build": "tsc -p tsconfig.mjs.json && tsc -p tsconfig.cjs.json",
"lint": "ts-standard",
"unit": "c8 ts-node test/*.test.ts",
"test": "npm run lint"
},
"ts-standard": {
Expand All @@ -21,6 +22,9 @@
"devDependencies": {
"@types/debug": "^4.1.7",
"@types/node": "^17.0.16",
"c8": "^7.11.0",
"fresh-tape": "^5.5.0",
"ts-node": "^10.5.0",
"ts-standard": "^11.0.0",
"typescript": "^4.4.4"
},
Expand Down
84 changes: 84 additions & 0 deletions test/util.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { createMatcher, getMatcher } from '../util'
import * as test from 'fresh-tape'

test('repeat, on-demand matcher', async t => {
const pkg = { main: 'index.js' }
const matcher = getMatcher('/root', pkg)
t.deepEqual(matcher('.', 'commonjs'), { cause: '.main', location: '/root/index.js' })
t.equal(getMatcher('/root', pkg), matcher)
})
test('simple main', async t => {
const match = createMatcher('/root', { main: './test.js' })
for (const input of [
'',
'.',
'./'
]) {
t.deepEqual(match(input, 'commonjs'), { cause: '.main', location: '/root/test.js' })
}
t.equal(match('', 'module'), undefined)
})
test('export override string', async t => {
const a = createMatcher('/root', { main: 'a.js', exports: './b.js' })
t.deepEqual(a('', 'commonjs'), { cause: '.exports[\'.\']', location: '/root/b.js' })
t.deepEqual(a('', 'module'), { cause: '.exports[\'.\']', location: '/root/b.js' })
})
test('export override object', async t => {
const a = createMatcher('/root', { main: 'a.js', exports: { import: './b.js', require: './c.js' } })
t.deepEqual(a('', 'commonjs'), { cause: '.exports[\'.\'].require', location: '/root/c.js' })
t.deepEqual(a('', 'module'), { cause: '.exports[\'.\'].import', location: '/root/b.js' })
})
test('export deep override', async t => {
const match = createMatcher('/root', {
main: 'a.js',
exports: {
'.': { require: './b.js', import: './c.js' },
'./c-a': { require: './d.js', import: './e.js' },
'./c-b': { default: './f.js' },
'./c-c': { node: './g.js', require: './h.js', default: './i.js' }
}
})
t.deepEqual(match('c-a', 'commonjs'), { cause: '.exports[\'./c-a\'].require', location: '/root/d.js' })
t.equals(match('c-a.js', 'commonjs'), undefined)
t.deepEqual(match('c-a', 'module'), { cause: '.exports[\'./c-a\'].import', location: '/root/e.js' })
t.deepEqual(match('c-b', 'commonjs'), { cause: '.exports[\'./c-b\'].default', location: '/root/f.js' })
t.deepEqual(match('c-b', 'module'), { cause: '.exports[\'./c-b\'].default', location: '/root/f.js' })
t.deepEqual(match('c-c', 'commonjs'), { cause: '.exports[\'./c-c\'].node', location: '/root/g.js' })
t.deepEqual(match('c-c', 'module'), { cause: '.exports[\'./c-c\'].default', location: '/root/i.js' })
})
test('pattern', async t => {
const match = createMatcher('/root', {
main: 'a.js',
exports: {
'./foo/*': null,
'./bar/*': { require: null },
'.': { require: './b.js', import: './c.js' },
'./bak/*.ts': { require: './cjs/*.js' },
'./*': { require: './cjs/*.js', import: './mjs/*' }
}
})
t.deepEqual(match('c-a', 'commonjs'), { cause: '.exports[\'./*\'].require', location: '/root/cjs/c-a.js' })
t.deepEqual(match('foo/c-a', 'commonjs'), { cause: '.exports[\'./foo/*\']', location: null })
t.deepEqual(match('bar/c-a', 'commonjs'), { cause: '.exports[\'./bar/*\'].require', location: null })
t.deepEqual(match('baz/c-a', 'module'), { cause: '.exports[\'./*\'].import', location: '/root/mjs/baz/c-a' })
t.deepEqual(match('bak/d.ts', 'commonjs'), { cause: '.exports[\'./bak/*.ts\'].require', location: '/root/cjs/d.js' })
})
test('deep require', async t => {
const deep = createMatcher('/root', { exports: { './test': './a.js' } })
t.deepEqual(deep('test', 'commonjs'), { cause: '.exports[\'./test\']', location: '/root/a.js' })
})
test('main and type=module', async t => {
const match = createMatcher('/root', { main: './test.js', type: 'module' })
t.equal(match('', 'commonjs'), undefined)
t.deepEqual(match('', 'module'), { cause: '.main and .type=module', location: '/root/test.js' })
})
test('module', async t => {
const match = createMatcher('/root', { main: './a.cjs', module: 'b.mjs' })
t.deepEqual(match('', 'commonjs'), { cause: '.main', location: '/root/a.cjs' })
t.deepEqual(match('', 'module'), { cause: '.module', location: '/root/b.mjs' })
})
test.skip('empty match', async t => {
const match = createMatcher('/root', {})
t.deepEqual(match('', 'commonjs'), { cause: 'fs', location: '/root/indexs.js' })
t.equal(match('', 'module'), undefined)
})
1 change: 0 additions & 1 deletion tsconfig.cjs.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"outDir": "cjs"
}
}
4 changes: 2 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
"strictNullChecks": true,
"sourceMap": true,
"strict": true,
"module": "es2020",
"module": "commonjs",
"strictFunctionTypes": true,
"forceConsistentCasingInFileNames": true,
"target": "ES2017",
"moduleResolution": "node",
"declaration": true,
"lib": ["es2018"],
"outDir": "./mjs",
"outDir": ".work",
}
}
7 changes: 7 additions & 0 deletions tsconfig.mjs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "es2020",
"outDir": "mjs"
}
}
Loading