Skip to content

Commit d98fb37

Browse files
feat: adding support for child lookups
1 parent bfdc496 commit d98fb37

File tree

7 files changed

+351
-47
lines changed

7 files changed

+351
-47
lines changed

index.ts

Lines changed: 33 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { spawn } from 'child_process'
33
import { mkdir, readFile, writeFile, stat, utimes } from 'fs/promises'
44
import dbg from 'debug'
55
import * as path from 'path'
6+
import { getMatcher, normalize } from './util'
67

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

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

202-
interface Cause {
203-
useImport: boolean
204-
cause: string
205-
}
206-
207-
const CAUSE_FALLBACK: Cause = { cause: 'require', useImport: false }
208-
const CAUSE_TYPE: Cause = { cause: 'import because of type=module', useImport: true }
209-
const CAUSE_MODULE: Cause = { cause: 'import because of a defined module', useImport: true }
210-
const CAUSE_DOT_EXPORT: Cause = { cause: 'import because of exports["."]', useImport: true }
211-
const CAUSE_DOT_ANY: Cause = { cause: 'import because of exports["./*"]', useImport: true }
212-
213-
function fuzzyChooseImport (pkg: any): Cause {
214-
if (pkg.type === 'module') return CAUSE_TYPE
215-
if (pkg.module !== undefined) return CAUSE_MODULE
216-
if (typeof pkg.exports === 'object' && pkg.exports !== null) {
217-
if (pkg.exports['.']?.import !== undefined) {
218-
return CAUSE_DOT_EXPORT
219-
}
220-
if (pkg.exports['./*']?.import !== undefined) {
221-
return CAUSE_DOT_ANY
222-
}
223-
}
224-
return CAUSE_FALLBACK
225-
}
226-
227-
async function loadData (name: string, depPath: string, pkg: any): Promise<any> {
228-
const { cause, useImport } = fuzzyChooseImport(pkg)
229-
debug('Loading package for %s from %s (%s)', name, depPath, cause)
230-
return useImport ? await import(depPath) : require(depPath)
231-
}
232-
233203
async function loadPackage (name: string, depPath: string): Promise<any> {
234204
const pkgPath = path.join(depPath, 'package.json')
235-
debug('Loading package.json for %s from %s', name, pkgPath)
236-
const data = await readFile(pkgPath, 'utf-8')
237-
return JSON.parse(data)
205+
let raw = '{}'
206+
try {
207+
raw = await readFile(pkgPath, 'utf-8')
208+
debug('Using package.json for %s from %s', name, pkgPath)
209+
} catch (err) {
210+
// Package json is optional
211+
debug('No package.json found at %s, using regular lookup', pkgPath)
212+
}
213+
return JSON.parse(raw)
238214
}
239215

240216
export class Plugin {
241217
readonly name: string
242218
readonly path: string
243219

244-
#data: Promise<any> | undefined
220+
#data: { [child: string]: Promise<any> } | undefined
245221
#pkg: Promise<any> | undefined
246222

247223
constructor (name: string, path: string) {
@@ -259,15 +235,29 @@ export class Plugin {
259235
return pkg
260236
}
261237

262-
/* eslint-disable-next-line @typescript-eslint/promise-function-async */
263-
data (opts?: { force?: boolean }): Promise<any> {
264-
let data = this.#data
265-
if (data === undefined || opts?.force !== true) {
266-
/* eslint-disable-next-line @typescript-eslint/promise-function-async */
267-
data = this.package(opts).then(pkg => loadData(this.name, this.path, pkg))
268-
this.#data = data
238+
async #loadData (child: string, force: boolean): Promise<any> {
239+
const pkg = await this.package({ force })
240+
const matcher = getMatcher(this.path, pkg)
241+
const mjs = matcher(child, 'module')
242+
/* eslint-disable-next-line @typescript-eslint/prefer-optional-chain */
243+
if (mjs !== undefined && mjs.location !== null) {
244+
debug('Importing package for %s from %s (%s)', this.name, mjs.location, mjs.cause)
245+
return await import(mjs.location)
269246
}
270-
return data
247+
const cjs = matcher(child, 'commonjs')
248+
/* eslint-disable-next-line @typescript-eslint/prefer-optional-chain */
249+
if (cjs !== undefined && cjs.location !== null) {
250+
debug('Requiring package for %s from %s (%s)', this.name, cjs.location, cjs.cause)
251+
return require(cjs.location)
252+
}
253+
throw new Error(`Can not require or import a package for ${this.name} at ${this.path}`)
254+
}
255+
256+
/* eslint-disable-next-line @typescript-eslint/promise-function-async */
257+
data (opts?: { force?: boolean, child?: string }): Promise<any> {
258+
const all = this.#data ?? (this.#data = {})
259+
const child = normalize(opts?.child)
260+
return all[child] ?? (all[child] = this.#loadData(child, opts?.force ?? false))
271261
}
272262
}
273263

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
"bin": "./bin/lambda-plugins",
88
"scripts": {
99
"prepare": "npm run build",
10-
"build": "tsc && tsc -p tsconfig.cjs.json",
10+
"build": "tsc -p tsconfig.mjs.json && tsc -p tsconfig.cjs.json",
1111
"lint": "ts-standard",
12+
"unit": "c8 ts-node test/*.test.ts",
1213
"test": "npm run lint"
1314
},
1415
"ts-standard": {
@@ -21,6 +22,9 @@
2122
"devDependencies": {
2223
"@types/debug": "^4.1.7",
2324
"@types/node": "^17.0.16",
25+
"c8": "^7.11.0",
26+
"fresh-tape": "^5.5.0",
27+
"ts-node": "^10.5.0",
2428
"ts-standard": "^11.0.0",
2529
"typescript": "^4.4.4"
2630
},

test/util.test.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { createMatcher, getMatcher } from '../util'
2+
import * as test from 'fresh-tape'
3+
4+
test('repeat, on-demand matcher', async t => {
5+
const pkg = { main: 'index.js' }
6+
const matcher = getMatcher('/root', pkg)
7+
t.deepEqual(matcher('.', 'commonjs'), { cause: '.main', location: '/root/index.js' })
8+
t.equal(getMatcher('/root', pkg), matcher)
9+
})
10+
test('simple main', async t => {
11+
const match = createMatcher('/root', { main: './test.js' })
12+
for (const input of [
13+
'',
14+
'.',
15+
'./'
16+
]) {
17+
t.deepEqual(match(input, 'commonjs'), { cause: '.main', location: '/root/test.js' })
18+
}
19+
t.equal(match('', 'module'), undefined)
20+
})
21+
test('export override string', async t => {
22+
const a = createMatcher('/root', { main: 'a.js', exports: './b.js' })
23+
t.deepEqual(a('', 'commonjs'), { cause: '.exports[\'.\']', location: '/root/b.js' })
24+
t.deepEqual(a('', 'module'), { cause: '.exports[\'.\']', location: '/root/b.js' })
25+
})
26+
test('export override object', async t => {
27+
const a = createMatcher('/root', { main: 'a.js', exports: { import: './b.js', require: './c.js' } })
28+
t.deepEqual(a('', 'commonjs'), { cause: '.exports[\'.\'].require', location: '/root/c.js' })
29+
t.deepEqual(a('', 'module'), { cause: '.exports[\'.\'].import', location: '/root/b.js' })
30+
})
31+
test('export deep override', async t => {
32+
const match = createMatcher('/root', {
33+
main: 'a.js',
34+
exports: {
35+
'.': { require: './b.js', import: './c.js' },
36+
'./c-a': { require: './d.js', import: './e.js' },
37+
'./c-b': { default: './f.js' },
38+
'./c-c': { node: './g.js', require: './h.js', default: './i.js' }
39+
}
40+
})
41+
t.deepEqual(match('c-a', 'commonjs'), { cause: '.exports[\'./c-a\'].require', location: '/root/d.js' })
42+
t.equals(match('c-a.js', 'commonjs'), undefined)
43+
t.deepEqual(match('c-a', 'module'), { cause: '.exports[\'./c-a\'].import', location: '/root/e.js' })
44+
t.deepEqual(match('c-b', 'commonjs'), { cause: '.exports[\'./c-b\'].default', location: '/root/f.js' })
45+
t.deepEqual(match('c-b', 'module'), { cause: '.exports[\'./c-b\'].default', location: '/root/f.js' })
46+
t.deepEqual(match('c-c', 'commonjs'), { cause: '.exports[\'./c-c\'].node', location: '/root/g.js' })
47+
t.deepEqual(match('c-c', 'module'), { cause: '.exports[\'./c-c\'].default', location: '/root/i.js' })
48+
})
49+
test('pattern', async t => {
50+
const match = createMatcher('/root', {
51+
main: 'a.js',
52+
exports: {
53+
'./foo/*': null,
54+
'./bar/*': { require: null },
55+
'.': { require: './b.js', import: './c.js' },
56+
'./bak/*.ts': { require: './cjs/*.js' },
57+
'./*': { require: './cjs/*.js', import: './mjs/*' }
58+
}
59+
})
60+
t.deepEqual(match('c-a', 'commonjs'), { cause: '.exports[\'./*\'].require', location: '/root/cjs/c-a.js' })
61+
t.deepEqual(match('foo/c-a', 'commonjs'), { cause: '.exports[\'./foo/*\']', location: null })
62+
t.deepEqual(match('bar/c-a', 'commonjs'), { cause: '.exports[\'./bar/*\'].require', location: null })
63+
t.deepEqual(match('baz/c-a', 'module'), { cause: '.exports[\'./*\'].import', location: '/root/mjs/baz/c-a' })
64+
t.deepEqual(match('bak/d.ts', 'commonjs'), { cause: '.exports[\'./bak/*.ts\'].require', location: '/root/cjs/d.js' })
65+
})
66+
test('deep require', async t => {
67+
const deep = createMatcher('/root', { exports: { './test': './a.js' } })
68+
t.deepEqual(deep('test', 'commonjs'), { cause: '.exports[\'./test\']', location: '/root/a.js' })
69+
})
70+
test('main and type=module', async t => {
71+
const match = createMatcher('/root', { main: './test.js', type: 'module' })
72+
t.equal(match('', 'commonjs'), undefined)
73+
t.deepEqual(match('', 'module'), { cause: '.main and .type=module', location: '/root/test.js' })
74+
})
75+
test('module', async t => {
76+
const match = createMatcher('/root', { main: './a.cjs', module: 'b.mjs' })
77+
t.deepEqual(match('', 'commonjs'), { cause: '.main', location: '/root/a.cjs' })
78+
t.deepEqual(match('', 'module'), { cause: '.module', location: '/root/b.mjs' })
79+
})
80+
test.skip('empty match', async t => {
81+
const match = createMatcher('/root', {})
82+
t.deepEqual(match('', 'commonjs'), { cause: 'fs', location: '/root/indexs.js' })
83+
t.equal(match('', 'module'), undefined)
84+
})

tsconfig.cjs.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
{
22
"extends": "./tsconfig.json",
33
"compilerOptions": {
4-
"module": "commonjs",
54
"outDir": "cjs"
65
}
76
}

tsconfig.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
"strictNullChecks": true,
66
"sourceMap": true,
77
"strict": true,
8-
"module": "es2020",
8+
"module": "commonjs",
99
"strictFunctionTypes": true,
1010
"forceConsistentCasingInFileNames": true,
1111
"target": "ES2017",
1212
"moduleResolution": "node",
1313
"declaration": true,
1414
"lib": ["es2018"],
15-
"outDir": "./mjs",
15+
"outDir": ".work",
1616
}
1717
}

tsconfig.mjs.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"extends": "./tsconfig.json",
3+
"compilerOptions": {
4+
"module": "es2020",
5+
"outDir": "mjs"
6+
}
7+
}

0 commit comments

Comments
 (0)