diff --git a/apps/cli b/apps/cli index 6ae695a..378e263 160000 --- a/apps/cli +++ b/apps/cli @@ -1 +1 @@ -Subproject commit 6ae695aa63122001e5c2fe6500a32cb939392e22 +Subproject commit 378e263c8ee8e262184ad72a5762ba19c73bffef diff --git a/package-lock.json b/package-lock.json index d15126e..998cc0c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,6 @@ "azot": "^0.6.1", "blowfish-node": "^1.1.4", "cross-spawn": "^7.0.6", - "metavm": "^1.4.2", "nanospinner": "^1.2.0", "picocolors": "^1.1.1", "tar": "^7.4.3", @@ -4654,6 +4653,10 @@ "resolved": "packages/crunchyroll", "link": true }, + "node_modules/@streamyx/loader": { + "resolved": "packages/loader", + "link": true + }, "node_modules/@streamyx/logger": { "resolved": "packages/logger", "link": true @@ -19708,7 +19711,7 @@ }, "packages/api": { "name": "@streamyx/api", - "version": "0.0.2", + "version": "0.0.5", "funding": [ { "type": "individual", @@ -20265,9 +20268,10 @@ }, "packages/core": { "name": "@streamyx/core", - "version": "2.2.1", + "version": "2.3.2", "license": "AGPL-3.0", "dependencies": { + "@streamyx/loader": "^0.0.3", "@streamyx/logger": "^0.0.2", "dasha": "^3.1.1", "got-scraping": "^3.2.15", @@ -20576,6 +20580,285 @@ "node": "*" } }, + "packages/loader": { + "name": "@streamyx/loader", + "version": "0.0.3", + "funding": [ + { + "type": "individual", + "url": "https://boosty.to/vitalygashkov" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/vitalygashkov" + } + ], + "license": "MIT", + "dependencies": { + "metavm": "^1.4.2" + }, + "devDependencies": { + "@eslint/js": "^9.16.0", + "@types/node": "^22.10.1", + "eslint": "^9.16.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.2.1", + "globals": "^15.12.0", + "prettier": "^3.4.1", + "typescript": "^5.7.2" + }, + "engines": { + "node": "20 || 21 || 22 || 23" + } + }, + "packages/loader/node_modules/@eslint/config-array": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.0.tgz", + "integrity": "sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "packages/loader/node_modules/@eslint/eslintrc": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", + "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "packages/loader/node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/loader/node_modules/@eslint/js": { + "version": "9.16.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.16.0.tgz", + "integrity": "sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "packages/loader/node_modules/@humanwhocodes/retry": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", + "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "packages/loader/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "packages/loader/node_modules/eslint": { + "version": "9.16.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.16.0.tgz", + "integrity": "sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.9.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.16.0", + "@eslint/plugin-kit": "^0.2.3", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.5", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "packages/loader/node_modules/eslint-scope": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "packages/loader/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "packages/loader/node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "packages/loader/node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "packages/loader/node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "packages/loader/node_modules/globals": { + "version": "15.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.12.0.tgz", + "integrity": "sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/loader/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "packages/logger": { "name": "@streamyx/logger", "version": "0.0.2", diff --git a/packages/api/package.json b/packages/api/package.json index 5a04814..0393fe7 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "@streamyx/api", - "version": "0.0.2", + "version": "0.0.5", "description": "Type definitions for the latest Streamyx API", "main": "streamyx.js", "types": "streamyx.d.ts", diff --git a/packages/api/streamyx.d.ts b/packages/api/streamyx.d.ts index 84d19d3..0c956d0 100644 --- a/packages/api/streamyx.d.ts +++ b/packages/api/streamyx.d.ts @@ -3,5 +3,5 @@ import './types/global'; export * from './types/app'; export * from './types/http'; export * from './types/logger'; -export * from './types/prompt'; -export * from './types/utils'; +export * from './types/question'; +export * from './types/common'; diff --git a/packages/api/types/app.d.ts b/packages/api/types/app.d.ts index 012a28d..14f7aff 100644 --- a/packages/api/types/app.d.ts +++ b/packages/api/types/app.d.ts @@ -1,9 +1,3 @@ -import type { AppLogger } from './logger'; -import type { AppPrompt } from './prompt'; -import type { AppUtils } from './utils'; - export type App = { - log: AppLogger; - prompt: AppPrompt; - utils: AppUtils; + version: string; }; diff --git a/packages/api/types/utils.d.ts b/packages/api/types/common.d.ts similarity index 92% rename from packages/api/types/utils.d.ts rename to packages/api/types/common.d.ts index ed7192e..91f23ae 100644 --- a/packages/api/types/utils.d.ts +++ b/packages/api/types/common.d.ts @@ -1,10 +1,10 @@ -export type AppUtils = { +export type Common = { sanitizeString: (text: string) => string; execUrlPatterns: ( url: string, patterns: string[], - baseUrls: string[] + baseUrls: string[], ) => { pathname: Record; search: Record; diff --git a/packages/api/types/global.d.ts b/packages/api/types/global.d.ts index cb7c92a..507e771 100644 --- a/packages/api/types/global.d.ts +++ b/packages/api/types/global.d.ts @@ -1,11 +1,15 @@ import { App } from './app'; import { AppStorage } from './storage'; -import { AppHttp } from './http'; -import { AppLogger } from './logger'; +import { HttpClient } from './http'; +import { Logger } from './logger'; +import { Question } from './question'; +import { Common } from './common'; declare global { const app: App; const storage: AppStorage; - const http: AppHttp; - const logger: AppLogger; + const http: HttpClient; + const logger: Logger; + const question: Question; + const common: Common; } diff --git a/packages/api/types/http.d.ts b/packages/api/types/http.d.ts index a64cd07..b3b7364 100644 --- a/packages/api/types/http.d.ts +++ b/packages/api/types/http.d.ts @@ -1,9 +1,18 @@ -export type AppHttp = { +export type HttpClient = { headers: Record; cookies: string[]; - userAgents: Record<'chromeWindows' | 'chromeMacOS' | 'chromeLinux' | 'smartTv' | 'tizen', string>; - fetch(resource: string | URL | Request, options?: RequestInit): Promise; - fetchAsChrome(resource: string | URL | Request, options?: RequestInit): Promise; + userAgents: Record< + 'chromeWindows' | 'chromeMacOS' | 'chromeLinux' | 'smartTv' | 'tizen', + string + >; + fetch( + resource: string | URL | Request, + options?: RequestInit, + ): Promise; + fetchAsChrome( + resource: string | URL | Request, + options?: RequestInit, + ): Promise; appendCookies(setCookie: string | string[]): void; setAgent(proxy?: string | null): void; setCookies(cookies: string[]): void; diff --git a/packages/api/types/logger.d.ts b/packages/api/types/logger.d.ts index 0d0fad7..3f85727 100644 --- a/packages/api/types/logger.d.ts +++ b/packages/api/types/logger.d.ts @@ -1,6 +1,6 @@ export type LogFn = (msg: string, ...args: any[]) => void; -export type AppLogger = { +export type Logger = { info: LogFn; warn: LogFn; error: LogFn; diff --git a/packages/api/types/prompt.d.ts b/packages/api/types/prompt.d.ts deleted file mode 100644 index 0907e89..0000000 --- a/packages/api/types/prompt.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -export type PromptForm = { - title?: string; - subtitle?: string; -} & { - [field: string]: { label: string; defaultValue?: string }; -}; - -export type PromptFormResponse = { [field: string]: string }; - -export type AppPrompt = { - ask(form: PromptForm): Promise; - listen(listener: (form: PromptForm) => Promise): void; -}; diff --git a/packages/api/types/question.d.ts b/packages/api/types/question.d.ts new file mode 100644 index 0000000..c2c42eb --- /dev/null +++ b/packages/api/types/question.d.ts @@ -0,0 +1,13 @@ +export type QuestionForm = { + title?: string; + subtitle?: string; +} & { + [field: string]: { label: string; defaultValue?: string }; +}; + +export type Answer = { [field: string]: string }; + +export type Question = { + ask(form: QuestionForm): Promise; + listen(listener: (form: QuestionForm) => Promise): void; +}; diff --git a/packages/api/types/storage.d.ts b/packages/api/types/storage.d.ts index e3c90ac..f9b08c0 100644 --- a/packages/api/types/storage.d.ts +++ b/packages/api/types/storage.d.ts @@ -4,5 +4,7 @@ export type AppStorage> = { set(key: string, value: any): Promise; delete(key: string): Promise; clear(): Promise; - save(items: T): Promise; + save(items?: T): Promise; + append(items: T): Promise; + items(): T; } & T; diff --git a/packages/core/lib/install.ts b/packages/core/lib/install.ts new file mode 100644 index 0000000..a4e856b --- /dev/null +++ b/packages/core/lib/install.ts @@ -0,0 +1,23 @@ +import path from 'node:path'; +import { load } from '@streamyx/loader'; +import { createStorage } from './store'; +import { Http } from './http'; +import { execUrlPatterns, extendEpisodes, safeEval, sanitizeString } from './utils'; +import { logger } from './log'; +import { PluginInstance } from './service'; +import { prompt } from './prompt'; + +export const install = async (pluginPath: string): Promise => { + const pluginName = path.parse(pluginPath).name; + const pluginDir = path.dirname(pluginPath); + + const app = { version: process.version }; + const storage = await createStorage(pluginName); + const http = new Http(); + const common = { sanitizeString, execUrlPatterns, safeEval, extendEpisodes }; + const context = { app, storage, http, logger, question: prompt, common }; + + const script = await load(pluginPath, { dirname: pluginDir, context }); + + return script.exports; +}; diff --git a/packages/core/lib/main.ts b/packages/core/lib/main.ts index 711f663..85e4996 100644 --- a/packages/core/lib/main.ts +++ b/packages/core/lib/main.ts @@ -10,4 +10,5 @@ export * from './utils'; export * from './args'; export * from './service'; +export * from './install'; export * from './shell'; diff --git a/packages/core/lib/store.ts b/packages/core/lib/store.ts index 83cdc5e..e8fe4f4 100644 --- a/packages/core/lib/store.ts +++ b/packages/core/lib/store.ts @@ -41,6 +41,14 @@ export const createStorage = async (name: string) => { return result; }; + const clear = (obj: any) => { + for (const [key, value] of Object.entries(obj)) { + const isFn = typeof value === 'function'; + if (!isFn) delete obj[key]; + } + return obj; + }; + const storage: Record = { async load() { const data = (await fs.readJson(storagePath).catch(() => {})) || {}; @@ -62,13 +70,18 @@ export const createStorage = async (name: string) => { await fs.writeJson(storagePath, serializable(storage)); }, async clear() { - for (const [key, value] of Object.entries(storage)) { - const isFn = typeof value === 'function'; - if (!isFn) delete storage[key]; - } + clear(storage); + await fs.writeJson(storagePath, {}); + }, + async append(items: Record) { + for (const [key, value] of Object.entries(items)) storage[key] = value; await fs.writeJson(storagePath, serializable(storage)); }, + items() { + return serializable(storage); + }, async save(items?: Record) { + clear(storage); const data = items || serializable(storage); for (const [key, value] of Object.entries(data)) storage[key] = value; await fs.writeJson(storagePath, data); diff --git a/packages/core/package.json b/packages/core/package.json index 496b6ed..11d02cf 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@streamyx/core", - "version": "2.2.1", + "version": "2.3.2", "author": "Vitaly Gashkov ", "description": "Core modules for Streamyx", "license": "AGPL-3.0", @@ -23,6 +23,7 @@ "dev": "tsup ./lib/main.ts --format esm,cjs --watch --dts --clean" }, "dependencies": { + "@streamyx/loader": "^0.0.3", "@streamyx/logger": "^0.0.2", "dasha": "^3.1.1", "got-scraping": "^3.2.15", diff --git a/packages/loader/.gitignore b/packages/loader/.gitignore new file mode 100644 index 0000000..becd09b --- /dev/null +++ b/packages/loader/.gitignore @@ -0,0 +1,4 @@ +node_modules +dist +build +.DS_Store diff --git a/packages/loader/LICENSE b/packages/loader/LICENSE new file mode 100644 index 0000000..6ed8df4 --- /dev/null +++ b/packages/loader/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Vitaly Gashkov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/loader/eslint.config.mjs b/packages/loader/eslint.config.mjs new file mode 100644 index 0000000..96072c8 --- /dev/null +++ b/packages/loader/eslint.config.mjs @@ -0,0 +1,10 @@ +import globals from 'globals'; +import pluginJs from '@eslint/js'; +import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; + +export default [ + { files: ['**/*.js'], languageOptions: { sourceType: 'commonjs' } }, + { languageOptions: { globals: globals.node } }, + pluginJs.configs.recommended, + eslintPluginPrettierRecommended, +]; diff --git a/packages/loader/index.d.ts b/packages/loader/index.d.ts new file mode 100644 index 0000000..1dfe052 --- /dev/null +++ b/packages/loader/index.d.ts @@ -0,0 +1,14 @@ +import { Context } from 'node:vm'; +import metavm from 'metavm'; + +type LoadOptions = { + context?: Context; + access?: Record; + dirname?: string; + format?: 'cjs' | 'esm'; +}; + +export function load( + scriptPath: string, + options?: LoadOptions, +): Promise; diff --git a/packages/loader/index.js b/packages/loader/index.js new file mode 100644 index 0000000..2783522 --- /dev/null +++ b/packages/loader/index.js @@ -0,0 +1,52 @@ +const fsp = require('node:fs/promises'); +const path = require('node:path'); +const metavm = require('metavm'); + +const { COMMON_CONTEXT } = metavm; + +const findAllScripts = async (moduleDir) => { + const findScripts = async (dir) => { + const files = await fsp.readdir(dir, { withFileTypes: true }); + const jsFiles = await Promise.all( + files.map(async (file) => { + const isJs = file.name.endsWith('.js'); + const isTest = file.name.endsWith('.test.js'); + if (file.isDirectory() && file.name !== 'node_modules') { + return findScripts(path.join(dir, file.name)); + } else if (file.isFile() && isJs && !isTest) { + return [path.join(dir, file.name)]; + } else { + return []; + } + }), + ); + return jsFiles.flat(); + }; + return findScripts(moduleDir); +}; + +const load = async (scriptPath, options = {}) => { + const access = {}; + if (options.dirname) { + const allowedFiles = await findAllScripts(options.dirname); + for (const file of allowedFiles) + access[file.replace(options.dirname, '.')] = true; + } + const readScriptOptions = { + context: metavm.createContext({ + ...COMMON_CONTEXT, + ...options.context, + crypto, + }), + dirname: options.dirname, + access: { '@streamyx/api': true, ...access, ...options.access }, + type: + options.format === 'esm' + ? metavm.MODULE_TYPE.ECMA + : metavm.MODULE_TYPE.COMMONJS, + }; + const script = await metavm.readScript(scriptPath, readScriptOptions); + return script; +}; + +module.exports = { load }; diff --git a/packages/loader/package.json b/packages/loader/package.json new file mode 100644 index 0000000..146e7fa --- /dev/null +++ b/packages/loader/package.json @@ -0,0 +1,58 @@ +{ + "name": "@streamyx/loader", + "version": "0.0.3", + "description": "Plugin loader for Streamyx", + "main": "index.js", + "types": "index.d.ts", + "files": [ + "index.js", + "index.d.ts" + ], + "scripts": { + "lint": "eslint . && prettier --check .", + "fix": "eslint . --fix && prettier --write ." + }, + "repository": { + "type": "git", + "url": "git+https://github.com/vitalygashkov/streamyx.git" + }, + "keywords": [ + "streamyx", + "loader", + "context", + "vm" + ], + "bugs": { + "url": "https://github.com/vitalygashkov/streamyx/issues", + "email": "vitalygashkov@vk.com" + }, + "author": "Vitaly Gashkov ", + "license": "MIT", + "readmeFilename": "README.md", + "funding": [ + { + "type": "individual", + "url": "https://boosty.to/vitalygashkov" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/vitalygashkov" + } + ], + "engines": { + "node": "20 || 21 || 22 || 23" + }, + "dependencies": { + "metavm": "^1.4.2" + }, + "devDependencies": { + "@eslint/js": "^9.16.0", + "@types/node": "^22.10.1", + "eslint": "^9.16.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.2.1", + "globals": "^15.12.0", + "prettier": "^3.4.1", + "typescript": "^5.7.2" + } +} diff --git a/packages/loader/prettier.config.mjs b/packages/loader/prettier.config.mjs new file mode 100644 index 0000000..0edf463 --- /dev/null +++ b/packages/loader/prettier.config.mjs @@ -0,0 +1,8 @@ +export default { + printWidth: 80, + singleQuote: true, + trailingComma: 'all', + tabWidth: 2, + useTabs: false, + semi: true, +};