diff --git a/.github/scripts/pypi-cleanup/package-lock.json b/.github/scripts/pypi-cleanup/package-lock.json new file mode 100644 index 000000000..bf03014e8 --- /dev/null +++ b/.github/scripts/pypi-cleanup/package-lock.json @@ -0,0 +1,349 @@ +{ + "name": "pypi-cleanup", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pypi-cleanup", + "dependencies": { + "otplib": "^12.0.1", + "playwright": "^1.48.2" + }, + "devDependencies": { + "@types/node": "^22.5.1", + "ts-node": "^10.9.2", + "typescript": "^5.6.3" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@otplib/core": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/core/-/core-12.0.1.tgz", + "integrity": "sha512-4sGntwbA/AC+SbPhbsziRiD+jNDdIzsZ3JUyfZwjtKyc/wufl1pnSIaG4Uqx8ymPagujub0o92kgBnB89cuAMA==", + "license": "MIT" + }, + "node_modules/@otplib/plugin-crypto": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/plugin-crypto/-/plugin-crypto-12.0.1.tgz", + "integrity": "sha512-qPuhN3QrT7ZZLcLCyKOSNhuijUi9G5guMRVrxq63r9YNOxxQjPm59gVxLM+7xGnHnM6cimY57tuKsjK7y9LM1g==", + "license": "MIT", + "dependencies": { + "@otplib/core": "^12.0.1" + } + }, + "node_modules/@otplib/plugin-thirty-two": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/plugin-thirty-two/-/plugin-thirty-two-12.0.1.tgz", + "integrity": "sha512-MtT+uqRso909UkbrrYpJ6XFjj9D+x2Py7KjTO9JDPhL0bJUYVu5kFP4TFZW4NFAywrAtFRxOVY261u0qwb93gA==", + "license": "MIT", + "dependencies": { + "@otplib/core": "^12.0.1", + "thirty-two": "^1.0.2" + } + }, + "node_modules/@otplib/preset-default": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/preset-default/-/preset-default-12.0.1.tgz", + "integrity": "sha512-xf1v9oOJRyXfluBhMdpOkr+bsE+Irt+0D5uHtvg6x1eosfmHCsCC6ej/m7FXiWqdo0+ZUI6xSKDhJwc8yfiOPQ==", + "license": "MIT", + "dependencies": { + "@otplib/core": "^12.0.1", + "@otplib/plugin-crypto": "^12.0.1", + "@otplib/plugin-thirty-two": "^12.0.1" + } + }, + "node_modules/@otplib/preset-v11": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/preset-v11/-/preset-v11-12.0.1.tgz", + "integrity": "sha512-9hSetMI7ECqbFiKICrNa4w70deTUfArtwXykPUvSHWOdzOlfa9ajglu7mNCntlvxycTiOAXkQGwjQCzzDEMRMg==", + "license": "MIT", + "dependencies": { + "@otplib/core": "^12.0.1", + "@otplib/plugin-crypto": "^12.0.1", + "@otplib/plugin-thirty-two": "^12.0.1" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.1.tgz", + "integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/otplib": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/otplib/-/otplib-12.0.1.tgz", + "integrity": "sha512-xDGvUOQjop7RDgxTQ+o4pOol0/3xSZzawTiPKRrHnQWAy0WjhNs/5HdIDJCrqC4MBynmjXgULc6YfioaxZeFgg==", + "license": "MIT", + "dependencies": { + "@otplib/core": "^12.0.1", + "@otplib/preset-default": "^12.0.1", + "@otplib/preset-v11": "^12.0.1" + } + }, + "node_modules/playwright": { + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz", + "integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.56.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz", + "integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/thirty-two": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/thirty-two/-/thirty-two-1.0.2.tgz", + "integrity": "sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA==", + "engines": { + "node": ">=0.2.6" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + } + } +} diff --git a/.github/scripts/pypi-cleanup/package.json b/.github/scripts/pypi-cleanup/package.json new file mode 100644 index 000000000..bacb020a5 --- /dev/null +++ b/.github/scripts/pypi-cleanup/package.json @@ -0,0 +1,15 @@ +{ + "name": "pypi-cleanup", + "scripts": { + "cleanup": "ts-node src/delete-oldest-release.ts" + }, + "dependencies": { + "otplib": "^12.0.1", + "playwright": "^1.48.2" + }, + "devDependencies": { + "@types/node": "^22.5.1", + "ts-node": "^10.9.2", + "typescript": "^5.6.3" + } +} diff --git a/.github/scripts/pypi-cleanup/src/delete-oldest-release.ts b/.github/scripts/pypi-cleanup/src/delete-oldest-release.ts new file mode 100644 index 000000000..25f930c25 --- /dev/null +++ b/.github/scripts/pypi-cleanup/src/delete-oldest-release.ts @@ -0,0 +1,231 @@ +import { authenticator } from "otplib"; +import { BrowserContext, chromium, Page } from "playwright"; + +type ReleaseInfo = { + version: string; + uploadedAt: Date; +}; + +type PyPIResponse = { + releases?: Record< + string, + Array<{ + upload_time?: string; + upload_time_iso_8601?: string; + }> + >; +}; + +const PYPI_BASE_URL = "https://pypi.org"; +const BROWSER_UA = + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " + + "(KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36"; + +function readEnv(name: string): string { + const value = process.env[name]; + if (!value) { + throw new Error(`Missing required environment variable: ${name}`); + } + return value; +} + +async function humanPause(min = 300, max = 1100) { + await new Promise((resolve) => + setTimeout(resolve, min + Math.random() * (max - min)) + ); +} + +async function fetchOldestRelease( + packageName: string +): Promise { + const response = await fetch(`${PYPI_BASE_URL}/pypi/${packageName}/json`, { + headers: { + "User-Agent": BROWSER_UA, + Accept: "application/json", + "Accept-Language": "en-US,en;q=0.9", + }, + }); + + if (!response.ok) { + throw new Error( + `Failed to fetch release metadata (${response.status} ${response.statusText})` + ); + } + + const data = (await response.json()) as PyPIResponse; + + const releases = Object.entries(data.releases ?? {}) + .map(([version, files]) => { + if (!files?.length) return null; + + const ts = files[0].upload_time_iso_8601 ?? files[0].upload_time ?? null; + + if (!ts) return null; + + return { + version, + uploadedAt: new Date(ts), + } as ReleaseInfo; + }) + .filter((r): r is ReleaseInfo => r !== null) + .sort((a, b) => a.uploadedAt.getTime() - b.uploadedAt.getTime()); + + return releases[0] ?? null; +} + +async function loginToPyPI( + page: Page, + username: string, + password: string, + totpSecret: string +) { + await page.goto(`${PYPI_BASE_URL}/account/login/`, { + waitUntil: "networkidle", + }); + await humanPause(); + + await page.fill('input[name="username"]', username); + await humanPause(); + + await page.fill('input[name="password"]', password); + await humanPause(); + + await Promise.all([ + page.waitForNavigation({ waitUntil: "networkidle" }), + page.locator('input[type="submit"][value="Log in"]').click(), + ]); + + await humanPause(); + + const totpInput = page.locator( + 'input[type="text"][name="totp_value"][id="totp_value"]' + ); + if (await totpInput.count()) { + const code = authenticator.generate(totpSecret); + await totpInput.fill(code); + await humanPause(); + + await Promise.all([ + page.waitForNavigation({ waitUntil: "networkidle" }), + page.locator('input[type="submit"][value="Verify"]').first().click(), + ]); + } + + await humanPause(); + + // if menu bar not found = login success! + const menuLink = page.locator("a.horizontal-menu__link"); + if (await menuLink.count()) { + throw new Error("Login failed: Menu link was found after login attempt."); + } +} + +async function deleteRelease(page: Page, packageName: string, version: string) { + const url = `${PYPI_BASE_URL}/manage/project/${packageName}/release/${version}/`; + + await page.goto(url, { waitUntil: "networkidle" }); + await humanPause(); + + const checkboxes = page.locator( + 'input[type="checkbox"][data-action="input->delete-confirm#check"][data-delete-confirm-target="input"]' + ); + + const count = await checkboxes.count(); + if (count === 0) { + throw new Error("Delete checkbox not found on release management page."); + } + + for (let i = 0; i < count; i++) { + await checkboxes.nth(i).check(); + await humanPause(); + } + + const deleteButton = page.locator( + 'a.button.button--danger[data-delete-confirm-target="button"]' + ); + if (!(await deleteButton.count())) { + throw new Error("Delete button not found on release management page."); + } + + await deleteButton.click(); + await humanPause(); + + const confirmInput = page.locator( + 'input[type="text"][id="delete_version-modal-confirm_delete_version"]' + ); + if (!(await confirmInput.count())) { + throw new Error( + "Confirmation input not found on delete confirmation modal." + ); + } + + await confirmInput.fill(version); + await humanPause(); + + const finalDeleteButton = page.locator( + `#delete_version-modal button.js-confirm[data-expected="${version}"]` + ); + + if ((await finalDeleteButton.count()) === 0) { + throw new Error( + "Final delete button not found on delete confirmation modal." + ); + } + + await Promise.all([ + page.waitForURL(/\/manage\/project\/.*\/releases\/?$/, { timeout: 15000 }), + finalDeleteButton.click(), + ]); + + await humanPause(); +} + +async function launchBrowser(): Promise { + return await chromium.launchPersistentContext("/tmp/pypi-profile", { + headless: true, + viewport: { width: 1300, height: 840 }, + userAgent: BROWSER_UA, + args: ["--no-sandbox", "--disable-dev-shm-usage"], + }); +} + +async function main() { + const username = readEnv("PYPI_UI_USERNAME"); + const password = readEnv("PYPI_UI_PASSWORD"); + const packageName = readEnv("PACKAGE_NAME"); + const totpSecret = readEnv("PYPI_UI_TOTP_SECRET"); + + console.log(`Checking oldest release for ${packageName}...`); + + const oldest = await fetchOldestRelease(packageName); + if (!oldest) { + console.log("No releases found with files — nothing to delete."); + return; + } + + console.log( + `Oldest release: ${ + oldest.version + } (uploaded ${oldest.uploadedAt.toDateString()})` + ); + + const context = await launchBrowser(); + const page = context.pages()[0] ?? (await context.newPage()); + + try { + console.log("Logging into PyPI..."); + await loginToPyPI(page, username, password, totpSecret); + console.log("Login successful."); + + console.log("Deleting oldest release..."); + await deleteRelease(page, packageName, oldest.version); + console.log(`Deleted release ${oldest.version}.`); + } finally { + await context.close(); + } +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/.github/scripts/pypi-cleanup/tsconfig.json b/.github/scripts/pypi-cleanup/tsconfig.json new file mode 100644 index 000000000..bb8e9c461 --- /dev/null +++ b/.github/scripts/pypi-cleanup/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2021", + "module": "CommonJS", + "moduleResolution": "node", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "types": ["node"], + "lib": ["ES2021", "DOM"], + "outDir": "dist" + }, + "include": ["src/**/*.ts"] +} diff --git a/.github/workflows/pypi-cleanup-example.yml b/.github/workflows/pypi-cleanup-example.yml new file mode 100644 index 000000000..3047f1955 --- /dev/null +++ b/.github/workflows/pypi-cleanup-example.yml @@ -0,0 +1,82 @@ +name: PyPI Cleanup Example +on: + workflow_dispatch: + push: + branches: + - "**" + pull_request: + release: + types: + - published + +jobs: + cleanup: + runs-on: windows-latest + defaults: + run: + shell: pwsh + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Install Google Chrome + shell: pwsh + run: | + choco install googlechrome -y --ignore-checksums + Start-Sleep -Seconds 5 + $chromePath = "C:\Program Files\Google\Chrome\Application\chrome.exe" + if (-not (Test-Path $chromePath)) { + Write-Error "Chrome is not installed" + } + + - name: Disable Chrome sign-in prompt + shell: pwsh + run: | + New-Item -Path "HKLM:\SOFTWARE\Policies\Google\Chrome" -Force | Out-Null + New-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Google\Chrome" -Name "BrowserSignin" -PropertyType DWord -Value 0 -Force | Out-Null + New-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Google\Chrome" -Name "SyncDisabled" -PropertyType DWord -Value 1 -Force | Out-Null + + - name: Prime Chrome profile (skip first-run UI) + shell: pwsh + run: | + $chrome = "C:\Program Files\Google\Chrome\Application\chrome.exe" + if (-not (Test-Path $chrome)) { $chrome = "chrome.exe" } + Start-Process $chrome "--no-first-run --no-default-browser-check --disable-search-engine-choice-screen --disable-fre" + Start-Sleep -Seconds 10 + Get-Process chrome -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue + + - run: npm ci + working-directory: examples/pypi_cleanup_workflow + + - run: npm run build + working-directory: examples/pypi_cleanup_workflow + + - run: npm run run + working-directory: examples/pypi_cleanup_workflow + env: + PYPI_UI_USERNAME: ${{ secrets.PYPI_UI_USERNAME }} + PYPI_UI_PASSWORD: ${{ secrets.PYPI_UI_PASSWORD }} + PYPI_UI_TOTP_SECRET: ${{ secrets.PYPI_UI_TOTP_SECRET }} + PACKAGE_NAME: ${{ secrets.PACKAGE_NAME }} + + - name: Capture final desktop screenshot + if: always() + shell: pwsh + run: | + Add-Type -AssemblyName System.Windows.Forms + Add-Type -AssemblyName System.Drawing + $bounds = [System.Windows.Forms.SystemInformation]::VirtualScreen + $bmp = New-Object System.Drawing.Bitmap $bounds.Width, $bounds.Height + $gfx = [System.Drawing.Graphics]::FromImage($bmp) + $gfx.CopyFromScreen($bounds.Location, [System.Drawing.Point]::Empty, $bounds.Size) + $path = "$env:RUNNER_TEMP\desktop.png" + $bmp.Save($path, [System.Drawing.Imaging.ImageFormat]::Png) + Write-Host "Saved screenshot to $path" + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: desktop-screenshot + path: ${{ runner.temp }}\desktop.png \ No newline at end of file diff --git a/examples/pypi_cleanup_workflow/bun.lock b/examples/pypi_cleanup_workflow/bun.lock new file mode 100644 index 000000000..8d3a89d76 --- /dev/null +++ b/examples/pypi_cleanup_workflow/bun.lock @@ -0,0 +1,119 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "pypi_cleanup_workflow", + "dependencies": { + "@mediar-ai/terminator": "^0.22.0", + "@mediar-ai/workflow": "^0.22.0", + "@types/node": "^20.0.0", + "node-fetch": "^3.3.2", + "otplib": "^12.0.1", + "typescript": "^5.0.0", + }, + }, + }, + "packages": { + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + + "@mediar-ai/terminator": ["@mediar-ai/terminator@0.22.25", "", { "dependencies": { "esbuild": "^0.25.11" }, "optionalDependencies": { "@mediar-ai/terminator-darwin-arm64": "0.22.25", "@mediar-ai/terminator-darwin-x64": "0.22.25", "@mediar-ai/terminator-linux-x64-gnu": "0.22.25", "@mediar-ai/terminator-win32-arm64-msvc": "0.22.25", "@mediar-ai/terminator-win32-x64-msvc": "0.22.25" } }, "sha512-ZcNkDCQgoCzUZQvckwTVwBy5rnyj8EFWb0EFwnV9SOssBZUiqrSfK7vL2w2AxOZU6aL0lx0GA72lSb0Tx4rWDQ=="], + + "@mediar-ai/terminator-linux-x64-gnu": ["@mediar-ai/terminator-linux-x64-gnu@0.22.25", "", { "os": "linux", "cpu": "x64" }, "sha512-rzJ1XGDWfJSBvcniVss+83U392pd8kQ6KcYRwAA7EqN10dfzP6iFVsdF8l66gUDA37rYgikbfchPqc8nk/NqPQ=="], + + "@mediar-ai/terminator-win32-arm64-msvc": ["@mediar-ai/terminator-win32-arm64-msvc@0.22.25", "", { "os": "win32", "cpu": "arm64" }, "sha512-zyAbOryiXs4j6n6CF2H2XHaeACyROxgG7fGi57jLPmkztsS4th9kbWB1qhUvwCj0/zPatwEwTdkeqnPHFiyxNQ=="], + + "@mediar-ai/terminator-win32-x64-msvc": ["@mediar-ai/terminator-win32-x64-msvc@0.22.25", "", { "os": "win32", "cpu": "x64" }, "sha512-topxEi+Uc7bp0AD74pkqCWYzOVa8lCgPEdqIoI699hzPq1FVN4e5zFEyjtgT6VIzyWtluhA9dEWO3NLkQpbavw=="], + + "@mediar-ai/workflow": ["@mediar-ai/workflow@0.22.25", "", { "dependencies": { "zod": "^3.22.4" }, "peerDependencies": { "@mediar-ai/terminator": "^0.22.25" } }, "sha512-IC3uywONUiLsK+0N2ujmEhilf/yXFWIoqlod9gBbiWtIYylWiMEtFou2nIBzpVV14LHH28E8sl6KU9jzW3u4DA=="], + + "@otplib/core": ["@otplib/core@12.0.1", "", {}, "sha512-4sGntwbA/AC+SbPhbsziRiD+jNDdIzsZ3JUyfZwjtKyc/wufl1pnSIaG4Uqx8ymPagujub0o92kgBnB89cuAMA=="], + + "@otplib/plugin-crypto": ["@otplib/plugin-crypto@12.0.1", "", { "dependencies": { "@otplib/core": "^12.0.1" } }, "sha512-qPuhN3QrT7ZZLcLCyKOSNhuijUi9G5guMRVrxq63r9YNOxxQjPm59gVxLM+7xGnHnM6cimY57tuKsjK7y9LM1g=="], + + "@otplib/plugin-thirty-two": ["@otplib/plugin-thirty-two@12.0.1", "", { "dependencies": { "@otplib/core": "^12.0.1", "thirty-two": "^1.0.2" } }, "sha512-MtT+uqRso909UkbrrYpJ6XFjj9D+x2Py7KjTO9JDPhL0bJUYVu5kFP4TFZW4NFAywrAtFRxOVY261u0qwb93gA=="], + + "@otplib/preset-default": ["@otplib/preset-default@12.0.1", "", { "dependencies": { "@otplib/core": "^12.0.1", "@otplib/plugin-crypto": "^12.0.1", "@otplib/plugin-thirty-two": "^12.0.1" } }, "sha512-xf1v9oOJRyXfluBhMdpOkr+bsE+Irt+0D5uHtvg6x1eosfmHCsCC6ej/m7FXiWqdo0+ZUI6xSKDhJwc8yfiOPQ=="], + + "@otplib/preset-v11": ["@otplib/preset-v11@12.0.1", "", { "dependencies": { "@otplib/core": "^12.0.1", "@otplib/plugin-crypto": "^12.0.1", "@otplib/plugin-thirty-two": "^12.0.1" } }, "sha512-9hSetMI7ECqbFiKICrNa4w70deTUfArtwXykPUvSHWOdzOlfa9ajglu7mNCntlvxycTiOAXkQGwjQCzzDEMRMg=="], + + "@types/node": ["@types/node@20.19.25", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ=="], + + "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], + + "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": "bin/esbuild" }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], + + "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], + + "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], + + "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], + + "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], + + "otplib": ["otplib@12.0.1", "", { "dependencies": { "@otplib/core": "^12.0.1", "@otplib/preset-default": "^12.0.1", "@otplib/preset-v11": "^12.0.1" } }, "sha512-xDGvUOQjop7RDgxTQ+o4pOol0/3xSZzawTiPKRrHnQWAy0WjhNs/5HdIDJCrqC4MBynmjXgULc6YfioaxZeFgg=="], + + "thirty-two": ["thirty-two@1.0.2", "", {}, "sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], + + "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + + "@mediar-ai/terminator/@mediar-ai/terminator-darwin-arm64": ["@mediar-ai/terminator-darwin-arm64@file:node_modules/@mediar-ai/terminator/node_modules/@mediar-ai/terminator-darwin-arm64", {}], + + "@mediar-ai/terminator/@mediar-ai/terminator-darwin-x64": ["@mediar-ai/terminator-darwin-x64@file:node_modules/@mediar-ai/terminator/node_modules/@mediar-ai/terminator-darwin-x64", {}], + } +} diff --git a/examples/pypi_cleanup_workflow/package-lock.json b/examples/pypi_cleanup_workflow/package-lock.json new file mode 100644 index 000000000..2144a9351 --- /dev/null +++ b/examples/pypi_cleanup_workflow/package-lock.json @@ -0,0 +1,753 @@ +{ + "name": "pypi_cleanup_workflow", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pypi_cleanup_workflow", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@mediar-ai/terminator": "^0.22.0", + "@mediar-ai/workflow": "^0.22.0", + "@types/node": "^20.0.0", + "node-fetch": "^3.3.2", + "otplib": "^12.0.1", + "typescript": "^5.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@mediar-ai/terminator": { + "version": "0.22.25", + "resolved": "https://registry.npmjs.org/@mediar-ai/terminator/-/terminator-0.22.25.tgz", + "integrity": "sha512-ZcNkDCQgoCzUZQvckwTVwBy5rnyj8EFWb0EFwnV9SOssBZUiqrSfK7vL2w2AxOZU6aL0lx0GA72lSb0Tx4rWDQ==", + "dependencies": { + "esbuild": "^0.25.11" + }, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@mediar-ai/terminator-darwin-arm64": "0.22.25", + "@mediar-ai/terminator-darwin-x64": "0.22.25", + "@mediar-ai/terminator-linux-x64-gnu": "0.22.25", + "@mediar-ai/terminator-win32-arm64-msvc": "0.22.25", + "@mediar-ai/terminator-win32-x64-msvc": "0.22.25" + } + }, + "node_modules/@mediar-ai/terminator-linux-x64-gnu": { + "version": "0.22.25", + "resolved": "https://registry.npmjs.org/@mediar-ai/terminator-linux-x64-gnu/-/terminator-linux-x64-gnu-0.22.25.tgz", + "integrity": "sha512-rzJ1XGDWfJSBvcniVss+83U392pd8kQ6KcYRwAA7EqN10dfzP6iFVsdF8l66gUDA37rYgikbfchPqc8nk/NqPQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mediar-ai/terminator-win32-arm64-msvc": { + "version": "0.22.25", + "resolved": "https://registry.npmjs.org/@mediar-ai/terminator-win32-arm64-msvc/-/terminator-win32-arm64-msvc-0.22.25.tgz", + "integrity": "sha512-zyAbOryiXs4j6n6CF2H2XHaeACyROxgG7fGi57jLPmkztsS4th9kbWB1qhUvwCj0/zPatwEwTdkeqnPHFiyxNQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mediar-ai/terminator-win32-x64-msvc": { + "version": "0.22.25", + "resolved": "https://registry.npmjs.org/@mediar-ai/terminator-win32-x64-msvc/-/terminator-win32-x64-msvc-0.22.25.tgz", + "integrity": "sha512-topxEi+Uc7bp0AD74pkqCWYzOVa8lCgPEdqIoI699hzPq1FVN4e5zFEyjtgT6VIzyWtluhA9dEWO3NLkQpbavw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mediar-ai/terminator/node_modules/@mediar-ai/terminator-darwin-arm64": { + "optional": true + }, + "node_modules/@mediar-ai/terminator/node_modules/@mediar-ai/terminator-darwin-x64": { + "optional": true + }, + "node_modules/@mediar-ai/workflow": { + "version": "0.22.25", + "resolved": "https://registry.npmjs.org/@mediar-ai/workflow/-/workflow-0.22.25.tgz", + "integrity": "sha512-IC3uywONUiLsK+0N2ujmEhilf/yXFWIoqlod9gBbiWtIYylWiMEtFou2nIBzpVV14LHH28E8sl6KU9jzW3u4DA==", + "license": "MIT", + "dependencies": { + "zod": "^3.22.4" + }, + "peerDependencies": { + "@mediar-ai/terminator": "^0.22.25" + } + }, + "node_modules/@otplib/core": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/core/-/core-12.0.1.tgz", + "integrity": "sha512-4sGntwbA/AC+SbPhbsziRiD+jNDdIzsZ3JUyfZwjtKyc/wufl1pnSIaG4Uqx8ymPagujub0o92kgBnB89cuAMA==", + "license": "MIT" + }, + "node_modules/@otplib/plugin-crypto": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/plugin-crypto/-/plugin-crypto-12.0.1.tgz", + "integrity": "sha512-qPuhN3QrT7ZZLcLCyKOSNhuijUi9G5guMRVrxq63r9YNOxxQjPm59gVxLM+7xGnHnM6cimY57tuKsjK7y9LM1g==", + "license": "MIT", + "dependencies": { + "@otplib/core": "^12.0.1" + } + }, + "node_modules/@otplib/plugin-thirty-two": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/plugin-thirty-two/-/plugin-thirty-two-12.0.1.tgz", + "integrity": "sha512-MtT+uqRso909UkbrrYpJ6XFjj9D+x2Py7KjTO9JDPhL0bJUYVu5kFP4TFZW4NFAywrAtFRxOVY261u0qwb93gA==", + "license": "MIT", + "dependencies": { + "@otplib/core": "^12.0.1", + "thirty-two": "^1.0.2" + } + }, + "node_modules/@otplib/preset-default": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/preset-default/-/preset-default-12.0.1.tgz", + "integrity": "sha512-xf1v9oOJRyXfluBhMdpOkr+bsE+Irt+0D5uHtvg6x1eosfmHCsCC6ej/m7FXiWqdo0+ZUI6xSKDhJwc8yfiOPQ==", + "license": "MIT", + "dependencies": { + "@otplib/core": "^12.0.1", + "@otplib/plugin-crypto": "^12.0.1", + "@otplib/plugin-thirty-two": "^12.0.1" + } + }, + "node_modules/@otplib/preset-v11": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/preset-v11/-/preset-v11-12.0.1.tgz", + "integrity": "sha512-9hSetMI7ECqbFiKICrNa4w70deTUfArtwXykPUvSHWOdzOlfa9ajglu7mNCntlvxycTiOAXkQGwjQCzzDEMRMg==", + "license": "MIT", + "dependencies": { + "@otplib/core": "^12.0.1", + "@otplib/plugin-crypto": "^12.0.1", + "@otplib/plugin-thirty-two": "^12.0.1" + } + }, + "node_modules/@types/node": { + "version": "20.19.25", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.25.tgz", + "integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/otplib": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/otplib/-/otplib-12.0.1.tgz", + "integrity": "sha512-xDGvUOQjop7RDgxTQ+o4pOol0/3xSZzawTiPKRrHnQWAy0WjhNs/5HdIDJCrqC4MBynmjXgULc6YfioaxZeFgg==", + "license": "MIT", + "dependencies": { + "@otplib/core": "^12.0.1", + "@otplib/preset-default": "^12.0.1", + "@otplib/preset-v11": "^12.0.1" + } + }, + "node_modules/thirty-two": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/thirty-two/-/thirty-two-1.0.2.tgz", + "integrity": "sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA==", + "engines": { + "node": ">=0.2.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/examples/pypi_cleanup_workflow/package.json b/examples/pypi_cleanup_workflow/package.json new file mode 100644 index 000000000..260e2f834 --- /dev/null +++ b/examples/pypi_cleanup_workflow/package.json @@ -0,0 +1,26 @@ +{ + "name": "pypi_cleanup_workflow", + "version": "1.0.0", + "description": "PyPI oldest release cleanup workflow using terminator browser automation", + "keywords": [ + "pypi", + "cleanup", + "automation", + "workflow", + "typescript" + ], + "author": "", + "license": "ISC", + "dependencies": { + "@mediar-ai/terminator": "^0.22.0", + "@mediar-ai/workflow": "^0.22.0", + "@types/node": "^20.0.0", + "node-fetch": "^3.3.2", + "otplib": "^12.0.1", + "typescript": "^5.0.0" + }, + "scripts": { + "build": "tsc", + "run": "node dist/terminator.js" + } +} diff --git a/examples/pypi_cleanup_workflow/src/steps/01-fetch-oldest-release.ts b/examples/pypi_cleanup_workflow/src/steps/01-fetch-oldest-release.ts new file mode 100644 index 000000000..d121111ea --- /dev/null +++ b/examples/pypi_cleanup_workflow/src/steps/01-fetch-oldest-release.ts @@ -0,0 +1,65 @@ +import { createStep } from "@mediar-ai/workflow"; +import fetch from "node-fetch"; + +interface ReleaseInfo { + version: string; + uploadTime: Date; +} + +export const fetchOldestRelease = createStep({ + id: "fetch-oldest-release", + name: "Fetch Oldest Release", + description: "Fetch the oldest PyPI release information via JSON API", + + execute: async ({ context, input, logger }: any) => { + const packageName = input.packageName; + logger.info(`📡 Fetching oldest release for package: ${packageName}`); + + try { + const url = `https://pypi.org/pypi/${packageName}/json`; + const response = await fetch(url); + + if (!response.ok) { + throw new Error( + `Failed to fetch PyPI JSON: ${response.status} ${response.statusText}` + ); + } + + const data: any = await response.json(); + const releases: ReleaseInfo[] = Object.entries(data.releases) + .filter(([_, files]) => Array.isArray(files) && files.length > 0) + .map(([version, files]) => ({ + version, + uploadTime: new Date(files[0].upload_time), + })) + .sort((a, b) => a.uploadTime.getTime() - b.uploadTime.getTime()); + + if (releases.length === 0) { + throw new Error("No releases found with files"); + } + + const oldest = releases[0]; + logger.success( + `✅ Found oldest release: ${ + oldest.version + } (uploaded ${oldest.uploadTime.toDateString()})` + ); + + context.data.oldestVersion = oldest.version; + context.data.oldestUploadTime = oldest.uploadTime.toISOString(); + context.data.totalReleases = releases.length; + + return { + success: true, + data: { + version: oldest.version, + uploadTime: oldest.uploadTime.toISOString(), + totalReleases: releases.length, + }, + }; + } catch (error: any) { + logger.error(`❌ Failed to fetch release info: ${error.message}`); + throw error; + } + }, +}); diff --git a/examples/pypi_cleanup_workflow/src/steps/02-login-pypi.ts b/examples/pypi_cleanup_workflow/src/steps/02-login-pypi.ts new file mode 100644 index 000000000..1a6a7424f --- /dev/null +++ b/examples/pypi_cleanup_workflow/src/steps/02-login-pypi.ts @@ -0,0 +1,76 @@ +import { createStep } from "@mediar-ai/workflow"; +import { authenticator } from "otplib"; + +export const loginToPyPI = createStep({ + id: "login-pypi", + name: "Login to PyPI", + description: "Login to PyPI using credentials", + + execute: async ({ desktop, input, logger }: any) => { + logger.info("🔐 Logging into PyPI..."); + + try { + await desktop.navigateBrowser( + "https://pypi.org/account/login/", + "Chrome" + ); + await desktop.delay(3000); + + // Fill username/password via accessibility locators + const usernameField = await desktop + .locator("role:Edit|name:Username||name:Email") + .first(10000); + await usernameField.click(); + await usernameField.typeText(input.username); + + const passwordField = await desktop.locator("role:Edit|name:Password").first(10000); + await passwordField.click(); + await passwordField.typeText(input.password); + + const loginButton = await desktop.locator("role:Button|name:Log in").first(5000); + await loginButton.click(); + + await desktop.delay(4000); + + // Handle TOTP if present + const totpFieldResult = await desktop + .locator("role:Edit|name:Authentication code||name:TOTP||name:Verification code") + .validate(3000); + if (totpFieldResult.exists) { + logger.info("🔒 2FA required, generating TOTP code..."); + if (!input.totpSecret) { + throw new Error("TOTP secret is required for 2FA but not provided"); + } + const code = authenticator.generate(input.totpSecret); + const totpField = totpFieldResult.element!; + await totpField.click(); + await totpField.typeText(code); + const verifyButton = await desktop + .locator("role:Button|name:Verify||name:Continue") + .first(5000); + await verifyButton.click(); + await desktop.delay(4000); + } + + // Pause to allow manual device/email approval if required + logger.info("⏳ Waiting 2 minutes to allow manual login approval if needed..."); + await desktop.delay(120000); + + // Basic success check (account menu or username on page) + const successCheck = + (await desktop + .locator("name:Account settings||name:Logout||name:Log out") + .validate(3000)).exists || + (await desktop.locator(`text:${input.username}`).validate(3000)).exists; + if (successCheck) { + logger.success("✅ Successfully logged into PyPI"); + return { success: true, data: { loggedIn: true } }; + } + + throw new Error("Login failed - expected account elements not found"); + } catch (error: any) { + logger.error(`❌ PyPI login failed: ${error.message}`); + throw error; + } + }, +}); diff --git a/examples/pypi_cleanup_workflow/src/steps/03-navigate-release.ts b/examples/pypi_cleanup_workflow/src/steps/03-navigate-release.ts new file mode 100644 index 000000000..2de6c4dae --- /dev/null +++ b/examples/pypi_cleanup_workflow/src/steps/03-navigate-release.ts @@ -0,0 +1,48 @@ +import { createStep } from "@mediar-ai/workflow"; + +export const navigateToRelease = createStep({ + id: "navigate-release", + name: "Navigate to Release Management", + description: "Navigate to the oldest release management page", + + execute: async ({ desktop, context, input, logger }: any) => { + const version = context.data.oldestVersion; + const packageName = input.packageName; + + logger.info( + `🧭 Navigating to release management for ${packageName} v${version}` + ); + + try { + const releaseManageUrl = `https://pypi.org/manage/project/${packageName}/release/${version}/`; + await desktop.navigateBrowser(releaseManageUrl, "Chrome"); + await desktop.delay(4000); + + for (let i = 0; i < 6; i++) { + await desktop.pressKey("End"); + await desktop.delay(300); + } + + const deleteCheckboxes = await desktop.locator("role:CheckBox").all(8000); + const deleteElementsFound = deleteCheckboxes.length; + + if (deleteElementsFound === 0) { + throw new Error( + "Delete checkboxes not found - may not be on correct release management page" + ); + } + + return { + data: { + navigated: true, + releaseUrl: releaseManageUrl, + deleteElementsFound, + }, + state: { deleteElementsFound }, + }; + } catch (error: any) { + logger.error(`❌ Navigation failed: ${error.message}`); + throw error; + } + }, +}); diff --git a/examples/pypi_cleanup_workflow/src/steps/04-delete-release.ts b/examples/pypi_cleanup_workflow/src/steps/04-delete-release.ts new file mode 100644 index 000000000..862046a38 --- /dev/null +++ b/examples/pypi_cleanup_workflow/src/steps/04-delete-release.ts @@ -0,0 +1,90 @@ +import { createStep } from "@mediar-ai/workflow"; + +export const deleteRelease = createStep({ + id: "delete-release", + name: "Delete Release", + description: "Delete the oldest PyPI release", + + execute: async ({ desktop, context, input, logger }: any) => { + const version = context.data.oldestVersion; + const packageName = input.packageName; + + logger.info(`🗑️ Deleting release ${packageName} v${version}`); + + try { + if (!version) { + throw new Error("Missing release version from previous steps"); + } + + // Scroll to bottom to make sure controls exist + for (let i = 0; i < 8; i++) { + await desktop.pressKey("End"); + await desktop.delay(200); + } + + const checkboxes = await desktop.locator("role:CheckBox").all(8000); + if (checkboxes.length === 0) { + throw new Error("No delete checkboxes found on the page"); + } + + for (const cb of checkboxes) { + await cb.click(); + await desktop.delay(200); + } + + const deleteButton = await desktop + .locator( + "role:Button|name:Delete release||role:Link|name:Delete release||role:Button|name:Delete||role:Link|name:Delete" + ) + .first(10000); + await deleteButton.click(); + + const confirmInput = await desktop + .locator( + "role:Edit|name:Confirm||role:Edit|name:Confirm delete||role:Edit|name:Confirm version" + ) + .first(8000); + await confirmInput.click(); + await desktop.pressKey("Ctrl+A"); + // PyPI typically requires the project name to confirm deletion + await confirmInput.typeText(packageName); + + const confirmButton = await desktop + .locator("role:Button|name:Delete release||role:Button|name:Delete") + .first(8000); + await confirmButton.click(); + + await desktop.delay(5000); + + const currentUrl = await desktop.getCurrentUrl(); + if ( + currentUrl == `https://pypi.org/manage/project/${packageName}/releases/` + ) { + logger.success(`✅ Successfully deleted release ${version}`); + + return { + deletedCheckboxes: checkboxes.length, + deleted: true, + version, + packageName, + redirectUrl: currentUrl, + }; + } + + logger.warning( + `⚠️ Delete completion suspected - unexpected URL: ${currentUrl}` + ); + return { + deletedCheckboxes: checkboxes.length, + deleted: true, + version, + packageName, + redirectUrl: currentUrl, + warning: "Unexpected redirect URL", + }; + } catch (error: any) { + logger.error(`❌ Delete operation failed: ${error.message}`); + throw error; + } + }, +}); diff --git a/examples/pypi_cleanup_workflow/src/terminator.ts b/examples/pypi_cleanup_workflow/src/terminator.ts new file mode 100644 index 000000000..b94b56c58 --- /dev/null +++ b/examples/pypi_cleanup_workflow/src/terminator.ts @@ -0,0 +1,76 @@ +/** + * PyPI Cleanup Workflow + * This workflow demonstrates PyPI release management automation using Terminator + * Steps: + * 1. Fetch oldest release info from PyPI JSON API + * 2. Login to PyPI + * 3. Navigate to release management page + * 4. Delete the oldest release + */ + +import { fetchOldestRelease } from "./steps/01-fetch-oldest-release"; +import { loginToPyPI } from "./steps/02-login-pypi"; +import { navigateToRelease } from "./steps/03-navigate-release"; +import { deleteRelease } from "./steps/04-delete-release"; +import { createWorkflow, createWorkflowRunner, z } from "@mediar-ai/workflow"; + +const inputSchema = z.object({ + packageName: z.string(), + username: z.string(), + password: z.string(), + totpSecret: z.string(), +}); + +const workflowOrBuilder = createWorkflow({ + name: "PyPI Oldest Release Cleanup", + description: "Automatically finds and deletes the oldest PyPI release", + version: "1.0.0", + input: inputSchema, + + steps: [fetchOldestRelease, loginToPyPI, navigateToRelease, deleteRelease], +}); + +const workflow = + "build" in workflowOrBuilder ? workflowOrBuilder.build() : workflowOrBuilder; + +async function main() { + const input = { + packageName: process.env.PACKAGE_NAME || "", + username: process.env.PYPI_UI_USERNAME || "", + password: process.env.PYPI_UI_PASSWORD || "", + totpSecret: process.env.PYPI_UI_TOTP_SECRET || "", + }; + + if ( + !input.username || + !input.password || + !input.packageName || + !input.totpSecret + ) { + console.error( + "❌ Missing PYPI_UI_USERNAME, PYPI_UI_PASSWORD, PACKAGE_NAME, or PYPI_UI_TOTP_SECRET environment variables" + ); + process.exit(1); + } + + try { + console.log("🚀 Starting PyPI Cleanup Workflow..."); + const runner = createWorkflowRunner({ + workflow, + inputs: input, + }); + const result = await runner.run(); + console.log(JSON.stringify(result, null, 2)); + } catch (error) { + console.error("❌ Workflow execution failed:", error); + process.exit(1); + } +} + +// Export the workflow for MCP to run +export default workflow; + +// Run if this is the main module +if (require.main === module) { + main(); +} diff --git a/examples/pypi_cleanup_workflow/tsconfig.json b/examples/pypi_cleanup_workflow/tsconfig.json new file mode 100644 index 000000000..102db4e0a --- /dev/null +++ b/examples/pypi_cleanup_workflow/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020", "DOM"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "moduleResolution": "node", + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +}