mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-21 04:31:20 -08:00
refactor(dash): [PKG-DASH-03] adapt SDK generations and own menu lifecycles
This commit is contained in:
1 parent
762cc63431
commit
7de04573b6
24 files changed
+2107
-533
No files matched your search
@@ -10,22 +10,14 @@ let sourceCode
|
||||
let evidence
|
||||
test.beforeAll(async () => {
|
||||
const { release } = JSON.parse(fs.readFileSync(new URL('../../refactor/baselines/dash-control-release.json', import.meta.url)))
|
||||
|
||||
const member = 'package/dist/artplayer-plugin-dash-control.js'
|
||||
|
||||
const bytes = readMember(await ensureArchive(release), member)
|
||||
|
||||
assert.equal(hash(bytes), release.files[member])
|
||||
|
||||
publishedCode = bytes.toString()
|
||||
|
||||
assert(!process.env.ARTPLAYER_BROWSER_ARTIFACTS, 'Controlled DASH source tests do not represent installed SDK/package acceptance')
|
||||
|
||||
sourceCode = process.env.ARTPLAYER_DASH_ARTIFACT ? fs.readFileSync(process.env.ARTPLAYER_DASH_ARTIFACT, 'utf8') : await compilePackage(release.name, 'umd')
|
||||
|
||||
evidence = { published: release, sourceSHA256: hash(sourceCode), candidate: process.env.ARTPLAYER_DASH_ARTIFACT || 'workspace source build', scope: 'Real ArtPlayer DOM and native MP4 with controlled SDK methods. No dash.js instance, MPD, ABR or adaptive media validation.' }
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
const state = await page.evaluate(() => ({
|
||||
calls: window.dashCalls,
|
||||
@@ -36,23 +28,20 @@ test.afterEach(async ({ page }, testInfo) => {
|
||||
audio: window.art?.controls['dash-audio']?.textContent,
|
||||
settingRows: [...document.querySelectorAll('.art-setting-panel.art-current .art-setting-item')].map(row => ({ text: row.textContent, label: row.querySelector('.art-setting-item-left-text')?.textContent })),
|
||||
})).catch(error => ({ error: error.message }))
|
||||
|
||||
await testInfo.attach('controlled-dash-state', { contentType: 'application/json', body: JSON.stringify(state) })
|
||||
|
||||
if (!page.isClosed())
|
||||
await page.evaluate(() => window.art?.destroy())
|
||||
if (!page.isClosed()) {
|
||||
await page.evaluate(() => {
|
||||
if (window.art && !window.art.isDestroy)
|
||||
window.art.destroy()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
async function openDash(page, core, plugin, testInfo) {
|
||||
async function openDash(page, core, plugin, sdk, testInfo) {
|
||||
await testInfo.attach('controlled-dash-inputs', { contentType: 'application/json', body: JSON.stringify(evidence) })
|
||||
|
||||
await page.goto(`/test/player.html?core=${core}`)
|
||||
|
||||
await page.addScriptTag({ content: plugin === 'published' ? publishedCode : sourceCode })
|
||||
|
||||
await page.evaluate((sdk) => {
|
||||
window.dashCalls = []
|
||||
|
||||
window.art = new window.Artplayer({
|
||||
container: '.player',
|
||||
url: '/test/pattern.mp4',
|
||||
@@ -62,13 +51,9 @@ async function openDash(page, core, plugin, testInfo) {
|
||||
const levels = sdk === 4
|
||||
? [{ qualityIndex: 0, height: 360 }, { qualityIndex: 1, height: 720 }]
|
||||
: [{ id: 'low', height: 360 }, { id: 'high', height: 720 }]
|
||||
|
||||
const tracks = [{ id: 'en', lang: 'en' }, { id: 'fr', lang: 'fr' }]
|
||||
|
||||
window.dashState = { levels, tracks, track: tracks[0], current: levels[0], quality: 0, settings: { streaming: { abr: { autoSwitchBitrate: { video: true } } } } }
|
||||
|
||||
const state = window.dashState
|
||||
|
||||
art.dash = {
|
||||
getVideoElement: () => art.template.$video,
|
||||
getSettings: () => state.settings,
|
||||
@@ -83,7 +68,6 @@ async function openDash(page, core, plugin, testInfo) {
|
||||
window.dashCalls.push(['track', track.id, tracks.includes(track)])
|
||||
},
|
||||
}
|
||||
|
||||
if (sdk === 4) {
|
||||
Object.assign(art.dash, {
|
||||
getBitrateInfoListFor: () => levels,
|
||||
@@ -104,68 +88,83 @@ async function openDash(page, core, plugin, testInfo) {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return window.artplayerPluginDashControl({ quality: { control: true, setting: true }, audio: { control: true, setting: true } })(art)
|
||||
}],
|
||||
})
|
||||
|
||||
document.querySelector('#play').onclick = () => window.art.play()
|
||||
|
||||
document.querySelector('#pause').onclick = () => window.art.pause()
|
||||
}, plugin === 'published' ? 4 : 5)
|
||||
|
||||
}, sdk)
|
||||
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
|
||||
|
||||
await expect(page.locator('.art-control-dash-quality .art-selector-value')).toHaveText('Auto')
|
||||
|
||||
await expect(page.locator('.art-control-dash-audio .art-selector-value')).toHaveText('en')
|
||||
}
|
||||
|
||||
for (const core of ['published', 'candidate']) {
|
||||
for (const plugin of ['published', 'candidate']) {
|
||||
for (const [plugin, sdk] of [['published', 4], ['candidate', 4], ['candidate', 5]]) {
|
||||
for (const surface of ['control', 'setting']) {
|
||||
for (const type of ['quality', 'audio']) {
|
||||
test(`${core} core + ${plugin} DASH: controlled SDK ${type} selection through real ${surface} DOM`, async ({ page }, testInfo) => {
|
||||
await openDash(page, core, plugin, testInfo)
|
||||
|
||||
test(`${core} core + ${plugin} DASH SDK ${sdk}: controlled SDK ${type} selection through real ${surface} DOM`, async ({ page }, testInfo) => {
|
||||
await openDash(page, core, plugin, sdk, testInfo)
|
||||
const text = type === 'quality' ? '720p' : 'fr'
|
||||
|
||||
if (surface === 'control') {
|
||||
const control = page.locator(`.art-control-dash-${type}`)
|
||||
|
||||
await control.hover()
|
||||
|
||||
await expect(control.locator('.art-selector-list')).toHaveCSS('opacity', '1')
|
||||
|
||||
await control.locator('.art-selector-item').filter({ hasText: new RegExp(`^${text}$`) }).click()
|
||||
}
|
||||
else {
|
||||
await page.locator('.art-control-setting').click()
|
||||
|
||||
await page.locator(`[data-name="dash-${type}"]`).click()
|
||||
|
||||
await page.locator('.art-setting-panel.art-current .art-setting-item-left-text').filter({ hasText: new RegExp(`^${text}$`) }).click()
|
||||
}
|
||||
|
||||
const expected = type === 'quality'
|
||||
? [['auto', false], plugin === 'published' ? ['qualityIndex', 'video', 1] : ['representationId', 'video', 'high']]
|
||||
? [['auto', false], sdk === 4 ? ['qualityIndex', 'video', 1] : ['representationId', 'video', 'high']]
|
||||
: [['track', 'fr', true]]
|
||||
|
||||
await expect.poll(() => page.evaluate(() => window.dashCalls)).toEqual(expected)
|
||||
|
||||
await expect(page.locator(`.art-control-dash-${type} .art-selector-value`)).toHaveText(text)
|
||||
|
||||
expect(await page.evaluate(type => window.art.setting.find(`dash-${type}`).tooltip, type)).toBe(text)
|
||||
|
||||
await testInfo.attach('controlled-dash-selection', { contentType: 'image/png', body: await page.screenshot() })
|
||||
|
||||
await page.locator('#play').click()
|
||||
|
||||
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.2)
|
||||
|
||||
expect(await page.evaluate(() => window.art.video.error)).toBeNull()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const core of ['published', 'candidate']) {
|
||||
for (const sdk of [4, 5]) {
|
||||
test(`${core} core + candidate DASH SDK ${sdk}: empty topology and destroy clear real menus and retained callbacks`, async ({ page }, testInfo) => {
|
||||
await openDash(page, core, 'candidate', sdk, testInfo)
|
||||
const result = await page.evaluate((sdk) => {
|
||||
const art = window.art
|
||||
const plugin = art.plugins.artplayerPluginDashControl
|
||||
const quality = art.controls.cache.get('dash-quality').option
|
||||
const audio = art.setting.find('dash-audio')
|
||||
const state = window.dashState
|
||||
const levels = state.levels
|
||||
const tracks = state.tracks
|
||||
state.levels.splice(0)
|
||||
state.tracks = []
|
||||
plugin.update()
|
||||
const cleared = !document.querySelector('.art-control-dash-quality, .art-control-dash-audio') && !art.setting.find('dash-quality') && !art.setting.find('dash-audio')
|
||||
window.dashCalls.length = 0
|
||||
quality.onSelect(quality.selector[0])
|
||||
audio.onSelect(audio.selector[1])
|
||||
const staleWrites = window.dashCalls.length
|
||||
// Replace the lists after the empty-topology observation.
|
||||
state.levels.push(...(sdk === 4 ? [{ qualityIndex: 0, height: 360 }, { qualityIndex: 1, height: 720 }] : [{ id: 'low', height: 360 }, { id: 'high', height: 720 }]))
|
||||
state.tracks = tracks
|
||||
plugin.update()
|
||||
const restored = Boolean(art.controls['dash-quality'] && art.controls['dash-audio'])
|
||||
const retained = art.controls.cache.get('dash-quality').option
|
||||
art.destroy(false)
|
||||
window.dashCalls.length = 0
|
||||
retained.onSelect(retained.selector[0])
|
||||
plugin.update()
|
||||
return { cleared, staleWrites, restored, afterDestroyWrites: window.dashCalls.length, sameList: state.levels === levels }
|
||||
}, sdk)
|
||||
expect(result).toEqual({ cleared: true, staleWrites: 0, restored: true, afterDestroyWrites: 0, sameList: true })
|
||||
await testInfo.attach('controlled-dash-cleanup', { contentType: 'application/json', body: JSON.stringify(result) })
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
import assert from 'node:assert/strict'
|
||||
// eslint-disable-next-line test/no-import-node-test -- Runtime lifecycle contracts use Node's built-in runner.
|
||||
import test from 'node:test'
|
||||
import { bothMenus, dashHost, dashImplementations } from './helpers/dash-control.js'
|
||||
|
||||
const candidates = (await dashImplementations()).filter(item => item.name.startsWith('source') || item.name.startsWith('artifact'))
|
||||
const { factory } = candidates[0]
|
||||
for (const { sdk, name, factory } of candidates) {
|
||||
test(`DASH ${sdk} (${name}): supports the original SDK method generation and stable selection keys`, () => {
|
||||
const host = dashHost(sdk)
|
||||
factory(bothMenus())(host.art).update()
|
||||
const menu = host.controls.get('dash-quality')
|
||||
const selected = menu.selector.find(item => item.html === '720p')
|
||||
host.calls.length = 0
|
||||
assert.equal(menu.onSelect(selected), '720p')
|
||||
assert.deepEqual(host.calls[1], sdk === 4 ? ['setQualityFor', 'video', 1] : ['setRepresentationForTypeById', 'video', 'high'])
|
||||
})
|
||||
|
||||
test(`DASH ${sdk} (${name}): empty lists clear owned UI and invalidate retained selectors`, () => {
|
||||
const host = dashHost(sdk)
|
||||
const plugin = factory(bothMenus())(host.art)
|
||||
plugin.update()
|
||||
const menu = host.controls.get('dash-quality')
|
||||
const { levels, tracks } = host.state
|
||||
host.state.levels = []
|
||||
host.state.tracks = []
|
||||
plugin.update()
|
||||
assert.equal(host.controls.size + host.settings.size, 0)
|
||||
host.calls.length = 0
|
||||
assert.equal(menu.onSelect(menu.selector[0]), '1080p')
|
||||
assert.equal(host.calls.length, 0)
|
||||
host.state.levels = levels
|
||||
host.state.tracks = tracks
|
||||
host.art.emit('restart')
|
||||
assert.equal(host.controls.size + host.settings.size, 4)
|
||||
})
|
||||
|
||||
test(`DASH ${sdk} (${name}): disabling one surface removes only that surface`, () => {
|
||||
const host = dashHost(sdk)
|
||||
const options = bothMenus()
|
||||
const plugin = factory(options)(host.art)
|
||||
plugin.update()
|
||||
options.quality.control = false
|
||||
options.audio.setting = false
|
||||
plugin.update()
|
||||
assert.deepEqual([...host.controls.keys()], ['dash-audio'])
|
||||
assert.deepEqual([...host.settings.keys()], ['dash-quality'])
|
||||
})
|
||||
|
||||
test(`DASH ${sdk} (${name}): destroy unsubscribes and makes update/retained callbacks inert without owning SDK`, () => {
|
||||
const host = dashHost(sdk)
|
||||
const other = dashHost(sdk)
|
||||
factory(bothMenus())(other.art).update()
|
||||
const plugin = factory(bothMenus())(host.art)
|
||||
plugin.update()
|
||||
const menu = host.controls.get('dash-audio')
|
||||
host.art.destroy()
|
||||
assert.equal(host.controls.size + host.settings.size, 0)
|
||||
assert.equal([...host.listeners.values()].reduce((sum, set) => sum + set.size, 0), 0)
|
||||
host.calls.length = 0
|
||||
plugin.update()
|
||||
host.art.emit('ready')
|
||||
host.art.emit('restart')
|
||||
host.art.destroy()
|
||||
assert.equal(menu.onSelect(menu.selector[1]), 'fr')
|
||||
assert.equal(host.calls.length, 0)
|
||||
assert.equal(other.controls.size + other.settings.size, 4)
|
||||
})
|
||||
|
||||
test(`DASH ${sdk} (${name}): replacing SDK immediately silences old callbacks before and after update`, () => {
|
||||
const host = dashHost(sdk)
|
||||
const next = dashHost(sdk)
|
||||
const plugin = factory(bothMenus())(host.art)
|
||||
plugin.update()
|
||||
const menu = host.controls.get('dash-quality')
|
||||
next.state.video = host.video
|
||||
host.art.dash = next.dash
|
||||
host.calls.length = 0
|
||||
menu.onSelect(menu.selector[0])
|
||||
assert.equal(host.calls.length, 0)
|
||||
plugin.update()
|
||||
host.calls.length = 0
|
||||
menu.onSelect(menu.selector[0])
|
||||
assert.equal(host.calls.length, 0)
|
||||
assert.equal(next.calls.length, 0)
|
||||
})
|
||||
|
||||
test(`DASH ${sdk} (${name}): duplicate labels preserve the current selectable object`, () => {
|
||||
const host = dashHost(sdk)
|
||||
host.state.settings.streaming.abr.autoSwitchBitrate.video = false
|
||||
host.state.levels[1].height = 360
|
||||
host.state.tracks[1].lang = 'en'
|
||||
host.state.currentTrack = host.state.tracks[1]
|
||||
factory(bothMenus())(host.art).update()
|
||||
const quality = host.controls.get('dash-quality')
|
||||
const selected = quality.selector.filter(item => item.default)
|
||||
assert.equal(selected.length, 1)
|
||||
host.calls.length = 0
|
||||
quality.onSelect(selected[0])
|
||||
assert.deepEqual(host.calls[1], sdk === 4 ? ['setQualityFor', 'video', 1] : ['setRepresentationForTypeById', 'video', 'high'])
|
||||
const audio = host.controls.get('dash-audio').selector.filter(item => item.default)
|
||||
assert.equal(audio.length, 1)
|
||||
assert.equal(audio[0].value, host.state.tracks[1])
|
||||
})
|
||||
|
||||
test(`DASH ${sdk} (${name}): cloned current track matches a unique track but never fabricates ambiguous selection`, () => {
|
||||
const host = dashHost(sdk)
|
||||
const plugin = factory(bothMenus())(host.art)
|
||||
host.state.currentTrack = { ...host.state.tracks[1] }
|
||||
plugin.update()
|
||||
assert.equal(host.controls.get('dash-audio').selector.find(item => item.default).value, host.state.tracks[1])
|
||||
host.state.tracks.push({ ...host.state.tracks[1], lang: 'fr-duplicate' })
|
||||
host.state.currentTrack = { id: 'fr', index: 1 }
|
||||
plugin.update()
|
||||
assert.equal(host.controls.get('dash-audio').selector.some(item => item.default), false)
|
||||
})
|
||||
|
||||
test(`DASH ${sdk} (${name}): reentrant getName destruction cannot repopulate menus`, () => {
|
||||
const host = dashHost(sdk)
|
||||
const options = bothMenus()
|
||||
let calls = 0
|
||||
options.quality.getName = (level) => {
|
||||
calls++
|
||||
host.art.destroy()
|
||||
return `${level.height}p`
|
||||
}
|
||||
factory(options)(host.art).update()
|
||||
assert.equal(host.controls.size + host.settings.size, 0)
|
||||
assert.equal(host.calls.length, 0)
|
||||
assert.equal(calls, 1)
|
||||
})
|
||||
|
||||
test(`DASH ${sdk} (${name}): synchronous SDK destruction stops later selection writes and notifications`, () => {
|
||||
const host = dashHost(sdk)
|
||||
factory(bothMenus())(host.art).update()
|
||||
const menu = host.controls.get('dash-quality')
|
||||
const original = host.dash.updateSettings
|
||||
host.dash.updateSettings = (value) => {
|
||||
original.call(host.dash, value)
|
||||
host.art.destroy()
|
||||
}
|
||||
host.calls.length = 0
|
||||
assert.equal(menu.onSelect(menu.selector[0]), '1080p')
|
||||
assert.equal(host.calls.some(call => /^set(?:Quality|Representation)/.test(call[0])), false)
|
||||
assert.equal(host.calls.some(call => call[0] === 'notice' || call[0].endsWith('.check')), false)
|
||||
})
|
||||
|
||||
test(`DASH ${sdk} (${name}): rendering errors clear partial own menus and allow an explicit retry`, () => {
|
||||
const host = dashHost(sdk)
|
||||
const failure = new Error('setting rendering failed')
|
||||
const original = host.art.setting.update
|
||||
host.art.setting.update = (option) => {
|
||||
original(option)
|
||||
throw failure
|
||||
}
|
||||
const plugin = factory(bothMenus())(host.art)
|
||||
assert.throws(plugin.update, error => error === failure)
|
||||
assert.equal(host.controls.size + host.settings.size, 0)
|
||||
host.art.setting.update = original
|
||||
plugin.update()
|
||||
assert.equal(host.controls.size + host.settings.size, 4)
|
||||
})
|
||||
|
||||
test(`DASH ${sdk} (${name}): nested update from a formatter keeps the latest render`, () => {
|
||||
const host = dashHost(sdk)
|
||||
const options = bothMenus()
|
||||
const plugin = factory(options)(host.art)
|
||||
let nested = false
|
||||
options.quality.getName = (level) => {
|
||||
if (!nested) {
|
||||
nested = true
|
||||
host.state.levels = [host.state.levels[2]]
|
||||
plugin.update()
|
||||
}
|
||||
return `${level.height}p`
|
||||
}
|
||||
plugin.update()
|
||||
assert.deepEqual(Array.from(host.controls.get('dash-quality').selector, item => item.html), ['1080p', 'Auto'])
|
||||
assert.equal(host.controls.size + host.settings.size, 4)
|
||||
})
|
||||
|
||||
test(`DASH ${sdk} (${name}): a cleanup-triggered restart does not remove newly installed menus`, () => {
|
||||
const host = dashHost(sdk)
|
||||
const plugin = factory(bothMenus())(host.art)
|
||||
plugin.update()
|
||||
const { levels } = host.state
|
||||
host.state.levels = []
|
||||
const remove = host.art.controls.remove
|
||||
host.art.controls.remove = (name) => {
|
||||
remove(name)
|
||||
host.state.levels = levels
|
||||
plugin.update()
|
||||
}
|
||||
plugin.update()
|
||||
assert.equal(host.controls.size + host.settings.size, 4)
|
||||
})
|
||||
|
||||
test(`DASH ${sdk} (${name}): clearing a caller-replaced registry entry preserves the caller entry`, () => {
|
||||
const host = dashHost(sdk)
|
||||
host.art.controls.cache = { get: name => ({ option: host.controls.get(name) }) }
|
||||
host.art.setting.find = name => host.settings.get(name)
|
||||
factory(bothMenus())(host.art).update()
|
||||
const replacement = { onSelect() {} }
|
||||
host.controls.set('dash-quality', replacement)
|
||||
host.settings.set('dash-audio', replacement)
|
||||
host.art.destroy()
|
||||
assert.equal(host.controls.get('dash-quality'), replacement)
|
||||
assert.equal(host.settings.get('dash-audio'), replacement)
|
||||
assert.equal(host.controls.size + host.settings.size, 2)
|
||||
})
|
||||
|
||||
test(`DASH ${sdk} (${name}): cleanup failure does not retain other surfaces or listeners`, () => {
|
||||
const host = dashHost(sdk)
|
||||
factory(bothMenus())(host.art).update()
|
||||
const failure = new Error('control cleanup failed')
|
||||
const remove = host.art.controls.remove
|
||||
host.art.controls.remove = (name) => {
|
||||
remove(name)
|
||||
throw failure
|
||||
}
|
||||
assert.throws(() => host.art.destroy(), error => error === failure)
|
||||
assert.equal(host.controls.size + host.settings.size, 0)
|
||||
assert.equal([...host.listeners.values()].reduce((sum, set) => sum + set.size, 0), 0)
|
||||
host.art.destroy()
|
||||
})
|
||||
}
|
||||
|
||||
test('DASH v5: a numeric zero ID is a valid manually selected representation', () => {
|
||||
const host = dashHost(5)
|
||||
host.state.levels[1].id = 0
|
||||
host.state.settings.streaming.abr.autoSwitchBitrate.video = false
|
||||
factory(bothMenus())(host.art).update()
|
||||
assert.deepEqual(host.controls.get('dash-quality').selector.filter(item => item.default).map(item => item.id), [0])
|
||||
})
|
||||
|
||||
test('DASH installation failure removes even the listener registered by a throwing host', () => {
|
||||
const host = dashHost()
|
||||
const failure = new Error('subscription failed')
|
||||
const on = host.art.on
|
||||
host.art.on = (name, callback) => {
|
||||
on(name, callback)
|
||||
if (name === 'destroy')
|
||||
throw failure
|
||||
}
|
||||
assert.throws(() => factory(bothMenus())(host.art), error => error === failure)
|
||||
assert.equal([...host.listeners.values()].reduce((sum, set) => sum + set.size, 0), 0)
|
||||
})
|
||||
|
||||
test('DASH reentrant destruction during subscription cannot install later listeners', () => {
|
||||
const host = dashHost()
|
||||
const on = host.art.on
|
||||
host.art.on = (name, callback) => {
|
||||
on(name, callback)
|
||||
if (name === 'destroy')
|
||||
callback()
|
||||
}
|
||||
factory()(host.art)
|
||||
assert.equal([...host.listeners.values()].reduce((sum, set) => sum + set.size, 0), 0)
|
||||
})
|
||||
@@ -22,6 +22,7 @@ export async function dashImplementations() {
|
||||
const { release } = baseline
|
||||
const archive = await ensureArchive(release)
|
||||
const implementations = [{ name: 'source', sdk: 5, factory: (await loadPackage(release.name)).default }]
|
||||
implementations.push({ name: 'source-v4', sdk: 4, factory: implementations[0].factory })
|
||||
const historicalFile = `packages/${release.name}/dist/artplayer-plugin-dash-control.js`
|
||||
const historical = execFileSync('git', ['show', `${baseline.sourceCommit}:${historicalFile}`])
|
||||
assert.equal(hash(historical.toString().replace(/\r\n/g, '\n')), baseline.source[historicalFile])
|
||||
@@ -34,7 +35,9 @@ export async function dashImplementations() {
|
||||
}
|
||||
for (const filename of (process.env.ARTPLAYER_TEST_DASH || '').split(path.delimiter).filter(Boolean)) {
|
||||
const bytes = fs.readFileSync(filename)
|
||||
implementations.push({ name: `artifact-${path.basename(filename)}`, sdk: 5, factory: await factoryFromBytes(bytes, filename.endsWith('.mjs')) })
|
||||
const factory = await factoryFromBytes(bytes, filename.endsWith('.mjs'))
|
||||
for (const sdk of [4, 5])
|
||||
implementations.push({ name: `artifact-${path.basename(filename)}-v${sdk}`, sdk, factory })
|
||||
}
|
||||
return implementations
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user