diff --git a/package.json b/package.json index be450f6e5..159d5265e 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,9 @@ "workspaces": [ "packages/*" ], + "bin": { + "ocap": "packages/cli/dist/app.mjs" + }, "scripts": { "build": "yarn run build:source", "build:clean": "yarn clean && yarn build", diff --git a/packages/cli/README.md b/packages/cli/README.md new file mode 100644 index 000000000..86239786c --- /dev/null +++ b/packages/cli/README.md @@ -0,0 +1,7 @@ +# `cli` + +Ocap Kernel cli. + +## Contributing + +This package is part of a monorepo. Instructions for contributing can be found in the [monorepo README](https://github.com/MetaMask/ocap-kernel#readme). diff --git a/packages/cli/package.json b/packages/cli/package.json new file mode 100644 index 000000000..c22494e34 --- /dev/null +++ b/packages/cli/package.json @@ -0,0 +1,87 @@ +{ + "name": "@ocap/cli", + "version": "0.0.0", + "private": true, + "description": "Ocap Kernel cli", + "repository": { + "type": "git", + "url": "https://github.com/MetaMask/ocap-kernel.git" + }, + "type": "module", + "bin": { + "ocap": "./dist/app.mjs" + }, + "files": [ + "dist/" + ], + "scripts": { + "build": "ts-bridge --project tsconfig.build.json --clean", + "build:docs": "typedoc", + "changelog:validate": "../../scripts/validate-changelog.sh utils", + "clean": "rimraf --glob ./dist './*.tsbuildinfo'", + "lint": "yarn lint:ts && yarn lint:eslint && yarn lint:misc --check && yarn constraints && yarn lint:dependencies", + "lint:dependencies": "depcheck", + "lint:eslint": "eslint . --cache", + "lint:fix": "yarn lint:ts && yarn lint:eslint --fix && yarn lint:misc --write && yarn constraints --fix && yarn lint:dependencies", + "lint:misc": "prettier --no-error-on-unmatched-pattern '**/*.json' '**/*.md' '**/*.html' '!**/CHANGELOG.old.md' '**/*.yml' '!.yarnrc.yml' '!merged-packages/**' --ignore-path ../../.gitignore", + "lint:ts": "tsc --project tsconfig.lint.json", + "publish:preview": "yarn npm publish --tag preview", + "test": "vitest run --config vitest.config.ts", + "test:clean": "yarn test --no-cache --coverage.clean", + "test:dev": "yarn test --coverage false", + "test:verbose": "yarn test --reporter verbose", + "test:watch": "vitest --config vitest.config.ts" + }, + "dependencies": { + "@endo/bundle-source": "^3.5.0", + "@endo/init": "^1.1.6", + "@metamask/snaps-utils": "^8.3.0", + "@ocap/shims": "workspace:^", + "@ocap/utils": "workspace:^", + "@types/node": "^18.18.14", + "glob": "^11.0.0", + "serve-handler": "^6.1.6", + "yargs": "^17.7.2" + }, + "devDependencies": { + "@arethetypeswrong/cli": "^0.16.4", + "@metamask/auto-changelog": "^3.4.4", + "@metamask/eslint-config": "^14.0.0", + "@metamask/eslint-config-nodejs": "^14.0.0", + "@metamask/eslint-config-typescript": "^14.0.0", + "@metamask/utils": "^9.3.0", + "@ts-bridge/cli": "^0.5.1", + "@ts-bridge/shims": "^0.1.1", + "@types/serve-handler": "^6", + "@types/yargs": "^17.0.33", + "@typescript-eslint/eslint-plugin": "^8.8.1", + "@typescript-eslint/parser": "^8.8.1", + "@typescript-eslint/utils": "^8.8.1", + "@vitest/eslint-plugin": "^1.1.7", + "depcheck": "^1.4.7", + "eslint": "^9.12.0", + "eslint-config-prettier": "^9.1.0", + "eslint-import-resolver-typescript": "^3.6.3", + "eslint-plugin-import-x": "^4.3.1", + "eslint-plugin-jsdoc": "^50.3.1", + "eslint-plugin-n": "^17.11.1", + "eslint-plugin-prettier": "^5.2.1", + "eslint-plugin-promise": "^7.1.0", + "jsdom": "^24.1.1", + "node-fetch": "^3.3.2", + "prettier": "^3.3.3", + "rimraf": "^6.0.1", + "ses": "^1.9.0", + "typedoc": "^0.26.8", + "typescript": "~5.5.4", + "typescript-eslint": "^8.8.1", + "vite": "^5.3.5", + "vitest": "^2.1.2" + }, + "engines": { + "node": "^18.18 || >=20" + }, + "exports": { + "./package.json": "./package.json" + } +} diff --git a/packages/cli/src/app.ts b/packages/cli/src/app.ts new file mode 100755 index 000000000..02180bdc2 --- /dev/null +++ b/packages/cli/src/app.ts @@ -0,0 +1,58 @@ +import yargs from 'yargs'; +import { hideBin } from 'yargs/helpers'; + +import { createBundle } from './commands/bundle.js'; +import { getServer } from './commands/serve.js'; +import { defaultConfig } from './config.js'; + +await yargs(hideBin(process.argv)) + .usage('$0 [options]') + .demandCommand(1) + .strict() + .command( + 'bundle ', + 'Bundle user code to be used in a vat', + (_yargs) => + _yargs.option('targets', { + type: 'string', + file: true, + dir: true, + array: true, + demandOption: true, + describe: 'The files or directories of files to bundle', + }), + async (args) => { + await Promise.all(args.targets.map(createBundle)); + }, + ) + .command( + 'serve [-p port]', + 'Serve bundled user code by filename', + (_yargs) => + _yargs + .option('port', { + alias: 'p', + type: 'number', + default: defaultConfig.server.port, + }) + .option('dir', { + alias: 'd', + type: 'string', + dir: true, + required: true, + describe: 'A directory of files to bundle', + }), + async (args) => { + console.info(`serving ${args.dir} on localhost:${args.port}`); + const server = getServer({ + server: { + port: args.port, + }, + dir: args.dir, + }); + await server.listen(); + }, + ) + .help('h') + .alias('h', 'help') + .parse(); diff --git a/packages/cli/src/commands/bundle.test.ts b/packages/cli/src/commands/bundle.test.ts new file mode 100644 index 000000000..dca91c8dc --- /dev/null +++ b/packages/cli/src/commands/bundle.test.ts @@ -0,0 +1,93 @@ +import bundleSourceImport from '@endo/bundle-source'; +import { createHash } from 'crypto'; +import { readFile, rm } from 'fs/promises'; +import { glob } from 'glob'; +import { join, basename } from 'path'; +import { + describe, + it, + expect, + vi, + beforeAll, + beforeEach, + afterEach, +} from 'vitest'; + +import { createBundleFile, createBundleDir } from './bundle.js'; +import { getTestBundles } from '../../test/bundles.js'; +import { fileExists } from '../file.js'; + +const bundleSource = bundleSourceImport as ReturnType; + +vi.mock('@endo/bundle-source', () => ({ + default: vi.fn(), +})); + +describe('bundle', async () => { + beforeEach(() => { + vi.resetModules(); + }); + + const { testBundleRoot, testBundleNames, testBundleSpecs } = + await getTestBundles(); + + const deleteTestBundles = async (): Promise => + await Promise.all( + testBundleSpecs.map(async ({ bundle }) => rm(bundle, { force: true })), + ).then(() => undefined); + + beforeAll(deleteTestBundles); + afterEach(deleteTestBundles); + + describe('createBundleFile', () => { + it.for(testBundleSpecs)( + 'bundles a single file: $name', + async ({ script, expected, bundle }, ctx) => { + if (!(await fileExists(expected))) { + // this test case has no expected bundle + // reporting handled in `describe('[meta]'` above + ctx.skip(); + } + ctx.expect(await fileExists(bundle)).toBe(false); + + const expectedBundleContent = await readFile(expected); + + bundleSource.mockImplementationOnce(() => expectedBundleContent); + + await createBundleFile(script); + + ctx.expect(await fileExists(bundle)).toBe(true); + + const bundleContent = await readFile(bundle); + const expectedBundleHash = createHash('sha256') + .update(expectedBundleContent) + .digest(); + const bundleHash = createHash('sha256').update(bundleContent).digest(); + + ctx + .expect(bundleHash.toString('hex')) + .toStrictEqual(expectedBundleHash.toString('hex')); + }, + ); + }); + + describe('createBundleDir', () => { + it('bundles a directory', async () => { + expect( + (await glob(join(testBundleRoot, '*.bundle'))).map((filepath) => + basename(filepath, '.bundle'), + ), + ).toStrictEqual([]); + + bundleSource.mockImplementation(() => 'test content'); + + await createBundleDir(testBundleRoot); + + expect( + (await glob(join(testBundleRoot, '*.bundle'))).map((filepath) => + basename(filepath, '.bundle'), + ), + ).toStrictEqual(testBundleNames); + }); + }); +}); diff --git a/packages/cli/src/commands/bundle.ts b/packages/cli/src/commands/bundle.ts new file mode 100644 index 000000000..6edfdd23b --- /dev/null +++ b/packages/cli/src/commands/bundle.ts @@ -0,0 +1,51 @@ +import '@endo/init'; +import bundleSource from '@endo/bundle-source'; +import { glob } from 'glob'; +import { writeFile } from 'node:fs/promises'; +import { resolve, parse, format, join } from 'node:path'; + +import { isDirectory } from '../file.js'; + +/** + * Create a bundle given path to an entry point. + * + * @param sourcePath - Path to the source file that is the root of the bundle. + * @returns A promise that resolves when the bundle has been written. + */ +export async function createBundleFile(sourcePath: string): Promise { + const sourceFullPath = resolve(sourcePath); + console.log(sourceFullPath); + const { dir, name } = parse(sourceFullPath); + const bundlePath = format({ dir, name, ext: '.bundle' }); + const bundle = await bundleSource(sourceFullPath); + const bundleString = JSON.stringify(bundle); + await writeFile(bundlePath, bundleString); + console.log(`wrote ${bundlePath}: ${new Blob([bundleString]).size} bytes`); +} + +/** + * Create a bundle given path to an entry point. + * + * @param sourceDir - Path to a directory of source files to bundle. + * @returns A promise that resolves when the bundles have been written. + */ +export async function createBundleDir(sourceDir: string): Promise { + console.log('bundling dir', sourceDir); + await Promise.all( + (await glob(join(sourceDir, '*.js'))).map( + async (source) => await createBundleFile(source), + ), + ); +} + +/** + * Bundle a target file or every file in the target directory. + * + * @param target The file or directory to apply the bundler to. + * @returns A promise that resolves when bundling is done. + */ +export async function createBundle(target: string): Promise { + await ((await isDirectory(target)) ? createBundleDir : createBundleFile)( + target, + ); +} diff --git a/packages/cli/src/commands/serve.test.ts b/packages/cli/src/commands/serve.test.ts new file mode 100644 index 000000000..7c665d160 --- /dev/null +++ b/packages/cli/src/commands/serve.test.ts @@ -0,0 +1,147 @@ +import '@ocap/shims/endoify'; +import type { BundleSourceResult } from '@endo/bundle-source'; +import { isObject, hasProperty } from '@metamask/utils'; +import { makeCounter, stringify } from '@ocap/utils'; +import { createHash } from 'crypto'; +import { readFile } from 'fs/promises'; +import nodeFetch from 'node-fetch'; +import { join, resolve } from 'path'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +import { getServer } from './serve.js'; +import { getTestBundles } from '../../test/bundles.js'; +import { defaultConfig } from '../config.js'; + +const isBundleSourceResult = ( + value: unknown, +): value is BundleSourceResult<'endoZipBase64'> => + isObject(value) && + hasProperty(value, 'moduleFormat') && + value.moduleFormat === 'endoZipBase64' && + hasProperty(value, 'endoZipBase64') && + typeof value.endoZipBase64 === 'string' && + hasProperty(value, 'endoZipBase64Sha512') && + typeof value.endoZipBase64Sha512 === 'string'; + +describe('serve', async () => { + beforeEach(() => { + vi.resetModules(); + }); + + const { testBundleRoot, testBundleSpecs } = await getTestBundles(); + + const getServerPort = makeCounter(defaultConfig.server.port); + + describe('getServer', () => { + it('returns an object with a listen property', () => { + const server = getServer({ + server: { + port: getServerPort(), + }, + dir: testBundleRoot, + }); + + expect(server).toHaveProperty('listen'); + }); + + it(`throws if 'dir' is not specified`, () => { + expect(() => getServer({ server: { port: getServerPort() } })).toThrow( + /dir/u, + ); + }); + }); + + describe('server', () => { + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type + const makeServer = (root: string = testBundleRoot) => { + const port = getServerPort(); + const { listen } = getServer({ + server: { + port, + }, + dir: root, + }); + const requestBundle = async (path: string): Promise => { + const resp = await nodeFetch(`http://localhost:${port}/${path}`); + if (resp.ok) { + return resp.json(); + } + throw new Error(resp.statusText, { cause: resp.status }); + }; + return { + listen, + requestBundle, + }; + }; + + it('serves bundles', async () => { + const bundleName = 'test.bundle'; + const bundleRoot = join(testBundleRoot, '..'); + const bundlePath = join(bundleRoot, bundleName); + const { listen, requestBundle } = makeServer(bundleRoot); + + const { close } = await listen(); + + try { + const bundleData = await readFile(bundlePath); + const expectedBundleContent = JSON.parse(bundleData.toString()); + if (!isBundleSourceResult(expectedBundleContent)) { + throw new Error( + [ + `Could not read expected bundle ${bundlePath}`, + `Parsed JSON: ${stringify(expectedBundleContent)}`, + ].join('\n'), + ); + } + const expectedBundleHash = expectedBundleContent.endoZipBase64Sha512; + + const receivedBundleContent = await requestBundle(bundleName); + if (!isBundleSourceResult(receivedBundleContent)) { + throw new Error( + `Received unexpected response from server: ${stringify(receivedBundleContent)}`, + ); + } + const receivedBundleHash = createHash('sha512') + .update(Buffer.from(receivedBundleContent.endoZipBase64)) + .digest('hex'); + + expect(receivedBundleHash).toStrictEqual(expectedBundleHash); + } finally { + await Promise.race([ + new Promise((_resolve) => setTimeout(_resolve, 400)), + close(), + ]); + } + }); + + it('only serves *.bundle files', async () => { + const { listen, requestBundle } = makeServer(); + + const script = testBundleSpecs[0]?.script as string; + + const { close } = await listen(); + try { + await expect(requestBundle(script)).rejects.toMatchObject({ + cause: 404, + }); + } finally { + await close(); + } + }); + + it('only serves files in the target dir', async () => { + const { listen, requestBundle } = makeServer(); + + const extraneousBundle = resolve(testBundleRoot, '../test.bundle'); + + const { close } = await listen(); + try { + await expect(requestBundle(extraneousBundle)).rejects.toMatchObject({ + cause: 404, + }); + } finally { + await close(); + } + }); + }); +}); diff --git a/packages/cli/src/commands/serve.ts b/packages/cli/src/commands/serve.ts new file mode 100644 index 000000000..aa4fc7472 --- /dev/null +++ b/packages/cli/src/commands/serve.ts @@ -0,0 +1,113 @@ +import { logError } from '@metamask/snaps-utils/node'; +import type { IncomingMessage, Server, ServerResponse } from 'http'; +import { createServer } from 'http'; +import type { AddressInfo } from 'net'; +import { resolve as resolvePath } from 'path'; +import serveMiddleware from 'serve-handler'; +import { promisify } from 'util'; + +import type { Config } from '../config.js'; + +/** + * Get a static server for development purposes. + * + * @param config - The config object. + * @returns An object with a `listen` method that returns a promise that + * resolves when the server is listening. + */ +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +export function getServer(config: Config) { + if (!config.dir) { + throw new Error(`Config option 'dir' must be specified.`); + } + const bundleRoot = resolvePath(config.dir); + // Only serve .bundle files + const isAllowedPath = (path?: string): boolean => + typeof path === 'string' && path.endsWith('.bundle'); + + /** + * Get the response for a request. This is extracted into a function so that + * we can easily catch errors and send a 500 response. + * + * @param request - The request. + * @param response - The response. + * @returns A promise that resolves when the response is sent. + */ + async function getResponse( + request: IncomingMessage, + response: ServerResponse, + ): Promise { + const pathname = + request.url && + request.headers.host && + new URL(request.url, `http://${request.headers.host}`).pathname; + const path = pathname?.slice(1); + + if (!isAllowedPath(path)) { + response.statusCode = 404; + response.end(); + return; + } + + await serveMiddleware(request, response, { + public: bundleRoot, + directoryListing: false, + headers: [ + { + source: '**/*', + headers: [ + { + key: 'Cache-Control', + value: 'no-cache', + }, + { + key: 'Access-Control-Allow-Origin', + value: '*', + }, + ], + }, + ], + }); + } + + const server = createServer((request, response) => { + getResponse(request, response).catch( + /* istanbul ignore next */ + (error) => { + logError(error); + response.statusCode = 500; + response.end(); + }, + ); + }); + + /** + * Start the server on the port specified in the config. + * + * @param port - The port to listen on. + * @returns A promise that resolves when the server is listening. The promise + * resolves to an object with the port and the server instance. Note that if + * the `config.server.port` is `0`, the OS will choose a random port for us, + * so we need to get the port from the server after it starts. + */ + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type + const listen = async (port = config.server.port) => { + return new Promise<{ + port: number; + server: Server; + close: () => Promise; + }>((resolve, reject) => { + try { + server.listen(port, () => { + const close = promisify(server.close.bind(server)); + const address = server.address() as AddressInfo; + resolve({ port: address.port, server, close }); + }); + } catch (listenError) { + reject(listenError as Error); + } + }); + }; + + return { listen }; +} diff --git a/packages/cli/src/config.ts b/packages/cli/src/config.ts new file mode 100644 index 000000000..fc24834c5 --- /dev/null +++ b/packages/cli/src/config.ts @@ -0,0 +1,12 @@ +export type Config = { + server: { + port: number; + }; + dir?: string; +}; + +export const defaultConfig: Config = { + server: { + port: 3000, + }, +}; diff --git a/packages/cli/src/file.ts b/packages/cli/src/file.ts new file mode 100644 index 000000000..1af55f910 --- /dev/null +++ b/packages/cli/src/file.ts @@ -0,0 +1,45 @@ +import { isObject } from '@metamask/utils'; +import { copyFile, lstat, access } from 'fs/promises'; + +/** + * Check if the target path is a directory. + * + * @param target The path to check. + * @returns A promise which resolves to true if the target path is a directory. + */ +export async function isDirectory(target: string): Promise { + return (await lstat(target)).isDirectory(); +} + +/** + * Asynchronously copy file(s) from source to destination. + * + * @param source - Where to copy file(s) from. + * @param destination - Where to copy file(s) to. + * @returns A promise that resolves when copying is complete. + */ +export async function cp(source: string, destination: string): Promise { + if (await isDirectory(source)) { + throw new Error('Directory cp not implemented.'); + } + await copyFile(source, destination); +} + +/** + * Asynchronously check if a file exists. + * + * @param path - The path to check + * @returns A promise that resolves to true iff a file exists at the given path + */ +export async function fileExists(path: string): Promise { + try { + // if the file can be accessed, it didn't exist yet + await access(path); + return false; + } catch (error) { + if (isObject(error) && error.code === 'EEXIST') { + return true; + } + throw error; + } +} diff --git a/packages/cli/test/bundles.ts b/packages/cli/test/bundles.ts new file mode 100644 index 000000000..9cfaced4f --- /dev/null +++ b/packages/cli/test/bundles.ts @@ -0,0 +1,57 @@ +import { mkdir } from 'fs/promises'; +import { glob } from 'glob'; +import { tmpdir } from 'os'; +import { resolve, join, basename, format } from 'path'; + +import { cp } from '../src/file.js'; + +const makeTestBundleRoot = async (): Promise => { + const testRoot = resolve(import.meta.url.split(':')[1] as string, '..'); + + // copy bundle targets to staging area + const testBundleRoot = resolve(testRoot, 'bundles'); + const stageBundleRoot = resolve(tmpdir(), 'test/bundles'); + await mkdir(stageBundleRoot, { recursive: true }); + for (const ext of ['.js', '.expected']) { + await Promise.all( + (await glob(join(testBundleRoot, `*${ext}`))).map(async (filePath) => { + const name = basename(filePath, ext); + await cp(filePath, format({ dir: stageBundleRoot, name, ext })); + }), + ); + } + await cp( + join(testRoot, 'test.bundle'), + join(stageBundleRoot, '../test.bundle'), + ); + + // return the staging area, ready for testing + return stageBundleRoot; +}; + +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +const getTestBundleNames = async (bundleRoot: string) => + (await glob(join(bundleRoot, '*.js'))).map((filepath) => + basename(filepath, '.js'), + ); + +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +const getTestBundleSpecs = (bundleRoot: string, bundleNames: string[]) => + bundleNames.map((bundleName) => ({ + name: bundleName, + script: join(bundleRoot, `${bundleName}.js`), + expected: join(bundleRoot, `${bundleName}.expected`), + bundle: join(bundleRoot, `${bundleName}.bundle`), + })); + +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +export const getTestBundles = async () => { + const testBundleRoot = await makeTestBundleRoot(); + const testBundleNames = await getTestBundleNames(testBundleRoot); + const testBundleSpecs = getTestBundleSpecs(testBundleRoot, testBundleNames); + return { + testBundleRoot, + testBundleNames, + testBundleSpecs, + }; +}; diff --git a/packages/cli/test/bundles/sample-vat-esp.expected b/packages/cli/test/bundles/sample-vat-esp.expected new file mode 100644 index 000000000..df049ce35 --- /dev/null +++ b/packages/cli/test/bundles/sample-vat-esp.expected @@ -0,0 +1 @@ +{"moduleFormat":"endoZipBase64","endoZipBase64":"UEsDBAoAAAAAAAAAAADqdnMTbwIAAG8CAAAUAAAAY29tcGFydG1lbnQtbWFwLmpzb257CiAgInRhZ3MiOiBbXSwKICAiZW50cnkiOiB7CiAgICAiY29tcGFydG1lbnQiOiAiQG9jYXAvY2xpLXYwLjAuMCIsCiAgICAibW9kdWxlIjogIi4vdGVzdC9zdGFnZS9idW5kbGVzL3NhbXBsZS12YXQtZXNwLmpzIgogIH0sCiAgImNvbXBhcnRtZW50cyI6IHsKICAgICJAb2NhcC9jbGktdjAuMC4wIjogewogICAgICAibmFtZSI6ICJAb2NhcC9jbGkiLAogICAgICAibGFiZWwiOiAiQG9jYXAvY2xpLXYwLjAuMCIsCiAgICAgICJsb2NhdGlvbiI6ICJAb2NhcC9jbGktdjAuMC4wIiwKICAgICAgIm1vZHVsZXMiOiB7CiAgICAgICAgIi4vdGVzdC9zdGFnZS9idW5kbGVzL3NhbXBsZS12YXQtZXNwLmpzIjogewogICAgICAgICAgImxvY2F0aW9uIjogInRlc3Qvc3RhZ2UvYnVuZGxlcy9zYW1wbGUtdmF0LWVzcC5qcyIsCiAgICAgICAgICAicGFyc2VyIjogInByZS1tanMtanNvbiIsCiAgICAgICAgICAic2hhNTEyIjogIjU4MDc5MGZjMGZjZmNmOTAyNzBkMTliOTIyY2FkYzRiMGIzMWRlYmYzYzU2ZjQ4ODJmYmViZDkxYWVjMDk5MTMyN2Q4ZGY3ZGZlMDFiNWU3OWIzNWE2MzE1ODRjYTFiMDE2ZjRkMTY2MzI4OWU4OTE0OGNlYmQ5ZjMxN2YxNTg1IgogICAgICAgIH0KICAgICAgfQogICAgfQogIH0KfVBLAwQKAAAAAAAAAAAAs+4sO00DAABNAwAANQAAAEBvY2FwL2NsaS12MC4wLjAvdGVzdC9zdGFnZS9idW5kbGVzL3NhbXBsZS12YXQtZXNwLmpzeyJpbXBvcnRzIjpbXSwiZXhwb3J0cyI6WyJzdGFydCJdLCJyZWV4cG9ydHMiOltdLCJfX3N5bmNNb2R1bGVQcm9ncmFtX18iOiIoeyAgIGltcG9ydHM6ICRozY9faW1wb3J0cywgICBsaXZlVmFyOiAkaM2PX2xpdmUsICAgb25jZVZhcjogJGjNj19vbmNlLCAgIGltcG9ydE1ldGE6ICRozY9fX19fbWV0YSwgfSkgPT4gKGZ1bmN0aW9uICgpIHsgJ3VzZSBzdHJpY3QnOyAgICRozY9faW1wb3J0cyhbXSk7T2JqZWN0LmRlZmluZVByb3BlcnR5KHN0YXJ0LCAnbmFtZScsIHt2YWx1ZTogXCJzdGFydFwifSk7JGjNj19vbmNlLnN0YXJ0KHN0YXJ0KTsgICAvKipcbiAqIFN0YXJ0IGZ1bmN0aW9uIGZvciBnZW5lcmljIHRlc3QgdmF0LlxuICpcbiAqIEBwYXJhbSB7dW5rbm93bn0gcGFyYW1ldGVycyAtIEluaXRpYWxpemF0aW9uIHBhcmFtZXRlcnMgZnJvbSB0aGUgdmF0J3MgY29uZmlnIG9iamVjdC5cbiAqIEByZXR1cm5zIHt1bmtub3dufSBUaGUgcm9vdCBvYmplY3QgZm9yIHRoZSBuZXcgdmF0LlxuICovXG5mdW5jdGlvbiAgICAgICAgc3RhcnQocGFyYW1ldGVycyl7XG5jb25zdCBuYW1lPXBhcmFtZXRlcnM/Lm5hbWU/Pydhbm9ueW1vdXMnO1xuY29uc29sZS5sb2coIGBlbXBlY2UgdmF0IHJvb3Qgb2JqZWN0byBcIiR7bmFtZX1cImApO1xucmV0dXJue1xubmFtZSxcbnN0dWZmOiBgc2UgaW5pY2nDsyBwb3IgJHtKU09OLnN0cmluZ2lmeShwYXJhbWV0ZXJzKX1gfTtcblxuIH1cbn0pKClcbiIsIl9fbGl2ZUV4cG9ydE1hcF9fIjp7fSwiX19yZWV4cG9ydE1hcF9fIjp7fSwiX19maXhlZEV4cG9ydE1hcF9fIjp7InN0YXJ0IjpbInN0YXJ0Il19LCJfX25lZWRzSW1wb3J0TWV0YV9fIjpmYWxzZX1QSwECHgMKAAAAAAAAAAAA6nZzE28CAABvAgAAFAAAAAAAAAAAAAAApIEAAAAAY29tcGFydG1lbnQtbWFwLmpzb25QSwECHgMKAAAAAAAAAAAAs+4sO00DAABNAwAANQAAAAAAAAAAAAAApIGhAgAAQG9jYXAvY2xpLXYwLjAuMC90ZXN0L3N0YWdlL2J1bmRsZXMvc2FtcGxlLXZhdC1lc3AuanNQSwUGAAAAAAIAAgClAAAAQQYAAAAA","endoZipBase64Sha512":"5f5e720ff9793741f3026c6cc078d8395f411c8896129a6ba63698739dd71f13e6ff31682189ab596018091aa47595e28dee72ec937a6bb777c12e8d621e48ab"} \ No newline at end of file diff --git a/packages/cli/test/bundles/sample-vat-esp.js b/packages/cli/test/bundles/sample-vat-esp.js new file mode 100644 index 000000000..787c9025e --- /dev/null +++ b/packages/cli/test/bundles/sample-vat-esp.js @@ -0,0 +1,14 @@ +/** + * Start function for generic test vat. + * + * @param {unknown} parameters - Initialization parameters from the vat's config object. + * @returns {unknown} The root object for the new vat. + */ +export function start(parameters) { + const name = parameters?.name ?? 'anonymous'; + console.log(`empece vat root objecto "${name}"`); + return { + name, + stuff: `se inició por ${JSON.stringify(parameters)}`, + }; +} diff --git a/packages/cli/test/bundles/sample-vat.expected b/packages/cli/test/bundles/sample-vat.expected new file mode 100644 index 000000000..b964b43ec --- /dev/null +++ b/packages/cli/test/bundles/sample-vat.expected @@ -0,0 +1 @@ +{"moduleFormat":"endoZipBase64","endoZipBase64":"UEsDBAoAAAAAAAAAAADY08YWYwIAAGMCAAAUAAAAY29tcGFydG1lbnQtbWFwLmpzb257CiAgInRhZ3MiOiBbXSwKICAiZW50cnkiOiB7CiAgICAiY29tcGFydG1lbnQiOiAiQG9jYXAvY2xpLXYwLjAuMCIsCiAgICAibW9kdWxlIjogIi4vdGVzdC9zdGFnZS9idW5kbGVzL3NhbXBsZS12YXQuanMiCiAgfSwKICAiY29tcGFydG1lbnRzIjogewogICAgIkBvY2FwL2NsaS12MC4wLjAiOiB7CiAgICAgICJuYW1lIjogIkBvY2FwL2NsaSIsCiAgICAgICJsYWJlbCI6ICJAb2NhcC9jbGktdjAuMC4wIiwKICAgICAgImxvY2F0aW9uIjogIkBvY2FwL2NsaS12MC4wLjAiLAogICAgICAibW9kdWxlcyI6IHsKICAgICAgICAiLi90ZXN0L3N0YWdlL2J1bmRsZXMvc2FtcGxlLXZhdC5qcyI6IHsKICAgICAgICAgICJsb2NhdGlvbiI6ICJ0ZXN0L3N0YWdlL2J1bmRsZXMvc2FtcGxlLXZhdC5qcyIsCiAgICAgICAgICAicGFyc2VyIjogInByZS1tanMtanNvbiIsCiAgICAgICAgICAic2hhNTEyIjogIjA0ZmI2Y2EwMTk0Mzc2ZjIwMzE5ZGEwMjQwYWM5OTg0NDM2MTQ5MTU0MDgzZjQ4ZDIzMWFjNTI5ZTEyZmNlZGRjYmU0OTZlNzViYTg2ZGFjYTcyY2Q5OTVlZDNkNzQ5NWY5NmNmODM1OWY1ZGRiYjk2MmM4YmY3ZTMyYjg3YWYyIgogICAgICAgIH0KICAgICAgfQogICAgfQogIH0KfVBLAwQKAAAAAAAAAAAAPl/YgU0DAABNAwAAMQAAAEBvY2FwL2NsaS12MC4wLjAvdGVzdC9zdGFnZS9idW5kbGVzL3NhbXBsZS12YXQuanN7ImltcG9ydHMiOltdLCJleHBvcnRzIjpbInN0YXJ0Il0sInJlZXhwb3J0cyI6W10sIl9fc3luY01vZHVsZVByb2dyYW1fXyI6Iih7ICAgaW1wb3J0czogJGjNj19pbXBvcnRzLCAgIGxpdmVWYXI6ICRozY9fbGl2ZSwgICBvbmNlVmFyOiAkaM2PX29uY2UsICAgaW1wb3J0TWV0YTogJGjNj19fX19tZXRhLCB9KSA9PiAoZnVuY3Rpb24gKCkgeyAndXNlIHN0cmljdCc7ICAgJGjNj19pbXBvcnRzKFtdKTtPYmplY3QuZGVmaW5lUHJvcGVydHkoc3RhcnQsICduYW1lJywge3ZhbHVlOiBcInN0YXJ0XCJ9KTskaM2PX29uY2Uuc3RhcnQoc3RhcnQpOyAgIC8qKlxuICogU3RhcnQgZnVuY3Rpb24gZm9yIGdlbmVyaWMgdGVzdCB2YXQuXG4gKlxuICogQHBhcmFtIHt1bmtub3dufSBwYXJhbWV0ZXJzIC0gSW5pdGlhbGl6YXRpb24gcGFyYW1ldGVycyBmcm9tIHRoZSB2YXQncyBjb25maWcgb2JqZWN0LlxuICogQHJldHVybnMge3Vua25vd259IFRoZSByb290IG9iamVjdCBmb3IgdGhlIG5ldyB2YXQuXG4gKi9cbmZ1bmN0aW9uICAgICAgICBzdGFydChwYXJhbWV0ZXJzKXtcbmNvbnN0IG5hbWU9cGFyYW1ldGVycz8ubmFtZT8/J2Fub255bW91cyc7XG5jb25zb2xlLmxvZyggYHN0YXJ0IHZhdCByb290IG9iamVjdCBcIiR7bmFtZX1cImApO1xucmV0dXJue1xubmFtZSxcbnN0dWZmOiBgaW5pdGlhbGl6ZWQgd2l0aCAke0pTT04uc3RyaW5naWZ5KHBhcmFtZXRlcnMpfWB9O1xuXG4gfVxufSkoKVxuIiwiX19saXZlRXhwb3J0TWFwX18iOnt9LCJfX3JlZXhwb3J0TWFwX18iOnt9LCJfX2ZpeGVkRXhwb3J0TWFwX18iOnsic3RhcnQiOlsic3RhcnQiXX0sIl9fbmVlZHNJbXBvcnRNZXRhX18iOmZhbHNlfVBLAQIeAwoAAAAAAAAAAADY08YWYwIAAGMCAAAUAAAAAAAAAAAAAACkgQAAAABjb21wYXJ0bWVudC1tYXAuanNvblBLAQIeAwoAAAAAAAAAAAA+X9iBTQMAAE0DAAAxAAAAAAAAAAAAAACkgZUCAABAb2NhcC9jbGktdjAuMC4wL3Rlc3Qvc3RhZ2UvYnVuZGxlcy9zYW1wbGUtdmF0LmpzUEsFBgAAAAACAAIAoQAAADEGAAAAAA==","endoZipBase64Sha512":"bcefe2bfbe1f9730d46eccd9f93bcca779f223e2d0a79dbc25bac6b86478a66435bf2298cf0c3c4dc6fd5728e38962a534c66c45f21469cfaafd5c31e967713a"} \ No newline at end of file diff --git a/packages/cli/test/bundles/sample-vat.js b/packages/cli/test/bundles/sample-vat.js new file mode 100644 index 000000000..118704e89 --- /dev/null +++ b/packages/cli/test/bundles/sample-vat.js @@ -0,0 +1,14 @@ +/** + * Start function for generic test vat. + * + * @param {unknown} parameters - Initialization parameters from the vat's config object. + * @returns {unknown} The root object for the new vat. + */ +export function start(parameters) { + const name = parameters?.name ?? 'anonymous'; + console.log(`start vat root object "${name}"`); + return { + name, + stuff: `initialized with ${JSON.stringify(parameters)}`, + }; +} diff --git a/packages/cli/test/test.bundle b/packages/cli/test/test.bundle new file mode 100644 index 000000000..cb26f3a08 --- /dev/null +++ b/packages/cli/test/test.bundle @@ -0,0 +1 @@ +{"moduleFormat":"endoZipBase64","endoZipBase64":"This is merely a test bundle!","endoZipBase64Sha512":"d0ef05812fb8a7cce57dcb8d6f6fbe5677f175e2ef966df1d81dec9868a8f56b0e89b691fdb3d43a3902962adcf37d1eee6902a113476cca85f8d76957bdf154"} \ No newline at end of file diff --git a/packages/cli/tsconfig.build.json b/packages/cli/tsconfig.build.json new file mode 100644 index 000000000..e7e91e4a3 --- /dev/null +++ b/packages/cli/tsconfig.build.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.packages.build.json", + "compilerOptions": { + "baseUrl": "./", + "lib": ["DOM", "ES2022"], + "outDir": "./dist", + "emitDeclarationOnly": false, + "rootDir": "./src", + "types": ["ses", "node"] + }, + "references": [ + { "path": "../utils/tsconfig.build.json" }, + { "path": "../shims/tsconfig.build.json" } + ], + "files": [], + "include": ["./src"] +} diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json new file mode 100644 index 000000000..b4cc7f68c --- /dev/null +++ b/packages/cli/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.packages.json", + "compilerOptions": { + "baseUrl": "./", + "lib": ["DOM", "ES2022"], + "types": ["ses", "vitest", "node"], + "noEmit": true + }, + "references": [{ "path": "../test-utils" }, { "path": "../utils" }], + "include": [ + "../../vitest.config.ts", + "./src/**/*.ts", + "./test", + "./vitest.config.ts" + ] +} diff --git a/packages/cli/tsconfig.lint.json b/packages/cli/tsconfig.lint.json new file mode 100644 index 000000000..66aa16ef3 --- /dev/null +++ b/packages/cli/tsconfig.lint.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "composite": false, + "noEmit": true, + "skipLibCheck": true + }, + "include": ["./src", "./scripts", "./test"], + "exclude": [] +} diff --git a/packages/cli/typedoc.json b/packages/cli/typedoc.json new file mode 100644 index 000000000..0ffe09d54 --- /dev/null +++ b/packages/cli/typedoc.json @@ -0,0 +1,7 @@ +{ + "entryPoints": ["./src/app.ts"], + "excludePrivate": true, + "hideGenerator": true, + "out": "docs", + "tsconfig": "./tsconfig.build.json" +} diff --git a/packages/cli/vitest.config.ts b/packages/cli/vitest.config.ts new file mode 100644 index 000000000..6236edfda --- /dev/null +++ b/packages/cli/vitest.config.ts @@ -0,0 +1,32 @@ +import path from 'path'; +import { defineProject, mergeConfig } from 'vitest/config'; + +import defaultConfig from '../../vitest.config.js'; + +const config = mergeConfig( + defaultConfig, + defineProject({ + build: { + ssr: true, + rollupOptions: { + output: { + esModule: true, + }, + }, + }, + test: { + name: 'cli', + alias: [ + { + find: '@ocap/shims/endoify', + replacement: path.resolve('../shims/src/endoify.js'), + customResolver: (id) => ({ external: true, id }), + }, + ], + }, + }), +); + +config.test.coverage.thresholds = true; + +export default config; diff --git a/tsconfig.build.json b/tsconfig.build.json index 2f070b5d9..57892aef3 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -3,6 +3,7 @@ "include": [], "references": [ // We exclude the extension and shims here due to their special build processes. + { "path": "./packages/cli/tsconfig.build.json" }, { "path": "./packages/errors/tsconfig.build.json" }, { "path": "./packages/kernel/tsconfig.build.json" }, { "path": "./packages/streams/tsconfig.build.json" }, diff --git a/tsconfig.json b/tsconfig.json index 8630999cd..613fdac49 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,6 +10,7 @@ "files": [], "include": [], "references": [ + { "path": "./packages/cli" }, { "path": "./packages/errors" }, { "path": "./packages/extension" }, { "path": "./packages/kernel" }, diff --git a/vitest.config.ts b/vitest.config.ts index 531d0514a..9e825de6f 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -34,6 +34,12 @@ export default defineConfig({ ], thresholds: { autoUpdate: true, + 'packages/cli/**': { + statements: 75.47, + functions: 72.22, + branches: 61.11, + lines: 75.47, + }, 'packages/errors/**': { statements: 100, functions: 100, diff --git a/yarn.lock b/yarn.lock index 5db07b591..08138f28f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1599,6 +1599,57 @@ __metadata: languageName: node linkType: hard +"@ocap/cli@workspace:packages/cli": + version: 0.0.0-use.local + resolution: "@ocap/cli@workspace:packages/cli" + dependencies: + "@arethetypeswrong/cli": "npm:^0.16.4" + "@endo/bundle-source": "npm:^3.5.0" + "@endo/init": "npm:^1.1.6" + "@metamask/auto-changelog": "npm:^3.4.4" + "@metamask/eslint-config": "npm:^14.0.0" + "@metamask/eslint-config-nodejs": "npm:^14.0.0" + "@metamask/eslint-config-typescript": "npm:^14.0.0" + "@metamask/snaps-utils": "npm:^8.3.0" + "@metamask/utils": "npm:^9.3.0" + "@ocap/shims": "workspace:^" + "@ocap/utils": "workspace:^" + "@ts-bridge/cli": "npm:^0.5.1" + "@ts-bridge/shims": "npm:^0.1.1" + "@types/node": "npm:^18.18.14" + "@types/serve-handler": "npm:^6" + "@types/yargs": "npm:^17.0.33" + "@typescript-eslint/eslint-plugin": "npm:^8.8.1" + "@typescript-eslint/parser": "npm:^8.8.1" + "@typescript-eslint/utils": "npm:^8.8.1" + "@vitest/eslint-plugin": "npm:^1.1.7" + depcheck: "npm:^1.4.7" + eslint: "npm:^9.12.0" + eslint-config-prettier: "npm:^9.1.0" + eslint-import-resolver-typescript: "npm:^3.6.3" + eslint-plugin-import-x: "npm:^4.3.1" + eslint-plugin-jsdoc: "npm:^50.3.1" + eslint-plugin-n: "npm:^17.11.1" + eslint-plugin-prettier: "npm:^5.2.1" + eslint-plugin-promise: "npm:^7.1.0" + glob: "npm:^11.0.0" + jsdom: "npm:^24.1.1" + node-fetch: "npm:^3.3.2" + prettier: "npm:^3.3.3" + rimraf: "npm:^6.0.1" + serve-handler: "npm:^6.1.6" + ses: "npm:^1.9.0" + typedoc: "npm:^0.26.8" + typescript: "npm:~5.5.4" + typescript-eslint: "npm:^8.8.1" + vite: "npm:^5.3.5" + vitest: "npm:^2.1.2" + yargs: "npm:^17.7.2" + bin: + ocap: ./dist/app.mjs + languageName: unknown + linkType: soft + "@ocap/errors@workspace:^, @ocap/errors@workspace:packages/errors": version: 0.0.0-use.local resolution: "@ocap/errors@workspace:packages/errors" @@ -1785,6 +1836,8 @@ __metadata: vite-tsconfig-paths: "npm:^4.3.2" vitest: "npm:^2.1.2" webextension-polyfill: "npm:^0.12.0" + bin: + ocap: packages/cli/dist/app.mjs languageName: unknown linkType: soft @@ -2527,6 +2580,15 @@ __metadata: languageName: node linkType: hard +"@types/serve-handler@npm:^6": + version: 6.1.4 + resolution: "@types/serve-handler@npm:6.1.4" + dependencies: + "@types/node": "npm:*" + checksum: 10/c92ae204605659b37202af97cfcc7690be43b9290692c1d6c3c93805b399044fd67573af4eb2e7b1fd975451db6d0d5c6cd2f09b20997209fa3341f345f661e4 + languageName: node + linkType: hard + "@types/statuses@npm:^2.0.4": version: 2.0.5 resolution: "@types/statuses@npm:2.0.5" @@ -2555,6 +2617,22 @@ __metadata: languageName: node linkType: hard +"@types/yargs-parser@npm:*": + version: 21.0.3 + resolution: "@types/yargs-parser@npm:21.0.3" + checksum: 10/a794eb750e8ebc6273a51b12a0002de41343ffe46befef460bdbb57262d187fdf608bc6615b7b11c462c63c3ceb70abe2564c8dd8ee0f7628f38a314f74a9b9b + languageName: node + linkType: hard + +"@types/yargs@npm:^17.0.33": + version: 17.0.33 + resolution: "@types/yargs@npm:17.0.33" + dependencies: + "@types/yargs-parser": "npm:*" + checksum: 10/16f6681bf4d99fb671bf56029141ed01db2862e3db9df7fc92d8bea494359ac96a1b4b1c35a836d1e95e665fb18ad753ab2015fc0db663454e8fd4e5d5e2ef91 + languageName: node + linkType: hard + "@typescript-eslint/eslint-plugin@npm:8.9.0, @typescript-eslint/eslint-plugin@npm:^8.8.1": version: 8.9.0 resolution: "@typescript-eslint/eslint-plugin@npm:8.9.0" @@ -3380,6 +3458,13 @@ __metadata: languageName: node linkType: hard +"bytes@npm:3.0.0": + version: 3.0.0 + resolution: "bytes@npm:3.0.0" + checksum: 10/a2b386dd8188849a5325f58eef69c3b73c51801c08ffc6963eddc9be244089ba32d19347caf6d145c86f315ae1b1fc7061a32b0c1aa6379e6a719090287ed101 + languageName: node + linkType: hard + "cac@npm:^6.7.14": version: 6.7.14 resolution: "cac@npm:6.7.14" @@ -3823,6 +3908,13 @@ __metadata: languageName: node linkType: hard +"content-disposition@npm:0.5.2": + version: 0.5.2 + resolution: "content-disposition@npm:0.5.2" + checksum: 10/97c5e7c8c72a0524c5d92866ecd3da28d4596925321aa3252d7ce3122d354b099d73cc1981fec8f24848d74314089928f626af8f9d7b51c3bc625d47f11e1d90 + languageName: node + linkType: hard + "convert-source-map@npm:^2.0.0": version: 2.0.0 resolution: "convert-source-map@npm:2.0.0" @@ -3924,6 +4016,13 @@ __metadata: languageName: node linkType: hard +"data-uri-to-buffer@npm:^4.0.0": + version: 4.0.1 + resolution: "data-uri-to-buffer@npm:4.0.1" + checksum: 10/0d0790b67ffec5302f204c2ccca4494f70b4e2d940fea3d36b09f0bb2b8539c2e86690429eb1f1dc4bcc9e4df0644193073e63d9ee48ac9fce79ec1506e4aa4c + languageName: node + linkType: hard + "data-urls@npm:^5.0.0": version: 5.0.0 resolution: "data-urls@npm:5.0.0" @@ -4906,6 +5005,16 @@ __metadata: languageName: node linkType: hard +"fetch-blob@npm:^3.1.2, fetch-blob@npm:^3.1.4": + version: 3.2.0 + resolution: "fetch-blob@npm:3.2.0" + dependencies: + node-domexception: "npm:^1.0.0" + web-streams-polyfill: "npm:^3.0.3" + checksum: 10/5264ecceb5fdc19eb51d1d0359921f12730941e333019e673e71eb73921146dceabcb0b8f534582be4497312d656508a439ad0f5edeec2b29ab2e10c72a1f86b + languageName: node + linkType: hard + "fflate@npm:^0.8.2": version: 0.8.2 resolution: "fflate@npm:0.8.2" @@ -5014,6 +5123,15 @@ __metadata: languageName: node linkType: hard +"formdata-polyfill@npm:^4.0.10": + version: 4.0.10 + resolution: "formdata-polyfill@npm:4.0.10" + dependencies: + fetch-blob: "npm:^3.1.2" + checksum: 10/9b5001d2edef3c9449ac3f48bd4f8cc92e7d0f2e7c1a5c8ba555ad4e77535cc5cf621fabe49e97f304067037282dd9093b9160a3cb533e46420b446c4e6bc06f + languageName: node + linkType: hard + "fs-extra@npm:^11.1.0": version: 11.2.0 resolution: "fs-extra@npm:11.2.0" @@ -6492,6 +6610,22 @@ __metadata: languageName: node linkType: hard +"mime-db@npm:~1.33.0": + version: 1.33.0 + resolution: "mime-db@npm:1.33.0" + checksum: 10/b3b89cff1d3569d02280f8d5b3b6e3c6df4dd340647b48228b2624293a73da0a7c784712aec8eac0aaccd353ac04b4d50309ab9f6a87d7ee79b4dca0ebb70ed8 + languageName: node + linkType: hard + +"mime-types@npm:2.1.18": + version: 2.1.18 + resolution: "mime-types@npm:2.1.18" + dependencies: + mime-db: "npm:~1.33.0" + checksum: 10/65d69085abda6732d4372e9874018fbe491894ff25f7329b8c8815fe40989599488567e08dcee39f1bb54729c4311fb660195ab551603d1cb97d7f2bf33ca8a2 + languageName: node + linkType: hard + "mime-types@npm:^2.1.12": version: 2.1.35 resolution: "mime-types@npm:2.1.35" @@ -6522,6 +6656,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:3.1.2, minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": + version: 3.1.2 + resolution: "minimatch@npm:3.1.2" + dependencies: + brace-expansion: "npm:^1.1.7" + checksum: 10/e0b25b04cd4ec6732830344e5739b13f8690f8a012d73445a4a19fbc623f5dd481ef7a5827fde25954cd6026fede7574cc54dc4643c99d6c6b653d6203f94634 + languageName: node + linkType: hard + "minimatch@npm:^10.0.0": version: 10.0.1 resolution: "minimatch@npm:10.0.1" @@ -6531,15 +6674,6 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": - version: 3.1.2 - resolution: "minimatch@npm:3.1.2" - dependencies: - brace-expansion: "npm:^1.1.7" - checksum: 10/e0b25b04cd4ec6732830344e5739b13f8690f8a012d73445a4a19fbc623f5dd481ef7a5827fde25954cd6026fede7574cc54dc4643c99d6c6b653d6203f94634 - languageName: node - linkType: hard - "minimatch@npm:^7.4.6": version: 7.4.6 resolution: "minimatch@npm:7.4.6" @@ -6752,6 +6886,13 @@ __metadata: languageName: node linkType: hard +"node-domexception@npm:^1.0.0": + version: 1.0.0 + resolution: "node-domexception@npm:1.0.0" + checksum: 10/e332522f242348c511640c25a6fc7da4f30e09e580c70c6b13cb0be83c78c3e71c8d4665af2527e869fc96848924a4316ae7ec9014c091e2156f41739d4fa233 + languageName: node + linkType: hard + "node-emoji@npm:^2.1.3": version: 2.1.3 resolution: "node-emoji@npm:2.1.3" @@ -6778,6 +6919,17 @@ __metadata: languageName: node linkType: hard +"node-fetch@npm:^3.3.2": + version: 3.3.2 + resolution: "node-fetch@npm:3.3.2" + dependencies: + data-uri-to-buffer: "npm:^4.0.0" + fetch-blob: "npm:^3.1.4" + formdata-polyfill: "npm:^4.0.10" + checksum: 10/24207ca8c81231c7c59151840e3fded461d67a31cf3e3b3968e12201a42f89ce4a0b5fb7079b1fa0a4655957b1ca9257553200f03a9f668b45ebad265ca5593d + languageName: node + linkType: hard + "node-gyp-build@npm:^4.2.2": version: 4.8.3 resolution: "node-gyp-build@npm:4.8.3" @@ -7183,6 +7335,13 @@ __metadata: languageName: node linkType: hard +"path-is-inside@npm:1.0.2": + version: 1.0.2 + resolution: "path-is-inside@npm:1.0.2" + checksum: 10/0b5b6c92d3018b82afb1f74fe6de6338c4c654de4a96123cb343f2b747d5606590ac0c890f956ed38220a4ab59baddfd7b713d78a62d240b20b14ab801fa02cb + languageName: node + linkType: hard + "path-key@npm:^3.0.0, path-key@npm:^3.1.0": version: 3.1.1 resolution: "path-key@npm:3.1.1" @@ -7224,6 +7383,13 @@ __metadata: languageName: node linkType: hard +"path-to-regexp@npm:3.3.0": + version: 3.3.0 + resolution: "path-to-regexp@npm:3.3.0" + checksum: 10/8d256383af8db66233ee9027cfcbf8f5a68155efbb4f55e784279d3ab206dcaee554ddb72ff0dae97dd2882af9f7fa802634bb7cffa2e796927977e31b829259 + languageName: node + linkType: hard + "path-to-regexp@npm:^6.3.0": version: 6.3.0 resolution: "path-to-regexp@npm:6.3.0" @@ -7505,6 +7671,13 @@ __metadata: languageName: node linkType: hard +"range-parser@npm:1.2.0": + version: 1.2.0 + resolution: "range-parser@npm:1.2.0" + checksum: 10/1a561fef1feae1cee3a3cb2440d4d9d3ab96cf2eebaf0d3a5cf06aecf91bc869f273ca0e2f05f73a4c530e751e4af0ed2723b7b86aeef296e3eaea7cfd0a5bfb + languageName: node + linkType: hard + "react-is@npm:^17.0.1": version: 17.0.2 resolution: "react-is@npm:17.0.2" @@ -7857,6 +8030,21 @@ __metadata: languageName: node linkType: hard +"serve-handler@npm:^6.1.6": + version: 6.1.6 + resolution: "serve-handler@npm:6.1.6" + dependencies: + bytes: "npm:3.0.0" + content-disposition: "npm:0.5.2" + mime-types: "npm:2.1.18" + minimatch: "npm:3.1.2" + path-is-inside: "npm:1.0.2" + path-to-regexp: "npm:3.3.0" + range-parser: "npm:1.2.0" + checksum: 10/7e7d93eb7e69fcd9f9c5afc2ef2b46cb0072b4af13cbabef9bca725afb350ddae6857d8c8be2c256f7ce1f7677c20347801399c11caa5805c0090339f894e8f2 + languageName: node + linkType: hard + "ses@npm:^1.1.0, ses@npm:^1.10.0, ses@npm:^1.9.0, ses@npm:^1.9.1": version: 1.10.0 resolution: "ses@npm:1.10.0" @@ -9151,6 +9339,13 @@ __metadata: languageName: node linkType: hard +"web-streams-polyfill@npm:^3.0.3": + version: 3.3.3 + resolution: "web-streams-polyfill@npm:3.3.3" + checksum: 10/8e7e13501b3834094a50abe7c0b6456155a55d7571312b89570012ef47ec2a46d766934768c50aabad10a9c30dd764a407623e8bfcc74fcb58495c29130edea9 + languageName: node + linkType: hard + "webextension-polyfill@npm:^0.12.0": version: 0.12.0 resolution: "webextension-polyfill@npm:0.12.0"