From f682c9698ff7457f7165721a4598cc7161b651fa Mon Sep 17 00:00:00 2001 From: bfintal Date: Wed, 5 Aug 2026 18:16:20 +0800 Subject: [PATCH 01/16] Add Playwright e2e tests for JPG-to-WebP upload interception. Cover image block upload, media library drop, editor canvas drop, featured image drop, and Media Library admin upload against wp-env. Co-authored-by: Cursor --- .env.example | 5 + .eslintignore | 4 + .gitignore | 5 + .wp-env.json | 8 + e2e/config/global-setup.ts | 31 + e2e/fixtures/sample.jpg | Bin 0 -> 5812 bytes e2e/playwright.config.ts | 38 + e2e/readme.md | 61 + e2e/test-utils/index.ts | 9 + e2e/test-utils/media.ts | 99 + e2e/test-utils/requestUtils.ts | 117 + e2e/test-utils/test.ts | 24 + e2e/tests/upload-interception.spec.ts | 188 ++ package-lock.json | 4227 ++++++++++++++++++++++--- package.json | 10 +- tsconfig.json | 12 + 16 files changed, 4372 insertions(+), 466 deletions(-) create mode 100644 .env.example create mode 100644 .wp-env.json create mode 100644 e2e/config/global-setup.ts create mode 100644 e2e/fixtures/sample.jpg create mode 100644 e2e/playwright.config.ts create mode 100644 e2e/readme.md create mode 100644 e2e/test-utils/index.ts create mode 100644 e2e/test-utils/media.ts create mode 100644 e2e/test-utils/requestUtils.ts create mode 100644 e2e/test-utils/test.ts create mode 100644 e2e/tests/upload-interception.spec.ts create mode 100644 tsconfig.json diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..8838a6a --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +WP_BASE_URL=http://localhost:8889 +WP_AUTH_STORAGE=wp-auth.json +WP_USERNAME=admin +WP_PASSWORD=password +CIMO_SLUG=Cimo/cimo diff --git a/.eslintignore b/.eslintignore index 4a6a229..c21f34e 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,5 +3,9 @@ vendor/ build/ dist/ *.min.js +e2e/ +playwright-report/ +test-results/ +wp-auth.json diff --git a/.gitignore b/.gitignore index 9c461dd..c8aa35a 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,9 @@ src/**/*.map ehthumbs.db Thumbs.db +# Playwright / e2e +wp-auth.json +playwright-report/ +test-results/ + pro__premium_only/ \ No newline at end of file diff --git a/.wp-env.json b/.wp-env.json new file mode 100644 index 0000000..0fb9eb4 --- /dev/null +++ b/.wp-env.json @@ -0,0 +1,8 @@ +{ + "core": null, + "phpVersion": "8.2", + "plugins": [ "." ], + "config": { + "SCRIPT_DEBUG": false + } +} diff --git a/e2e/config/global-setup.ts b/e2e/config/global-setup.ts new file mode 100644 index 0000000..3c02ef3 --- /dev/null +++ b/e2e/config/global-setup.ts @@ -0,0 +1,31 @@ +import { request } from '@playwright/test' + +import { ExtendedRequestUtils } from 'e2e/test-utils' + +async function globalSetup() { + const requestContext = await request.newContext( { + baseURL: process.env.WP_BASE_URL, + ignoreHTTPSErrors: true, + } ) + const requestUtils = new ExtendedRequestUtils( requestContext, { + storageStatePath: process.env.WP_AUTH_STORAGE, + user: { + username: process.env.WP_USERNAME, + password: process.env.WP_PASSWORD, + }, + } ) + + await requestUtils.setupRest() + + const plugins = await requestUtils.getActivePlugins() + + for ( const slug of Object.keys( plugins ) ) { + await requestUtils.deactivatePlugin( slug ) + } + + await requestUtils.activatePlugin( process.env.CIMO_SLUG ) + + await requestContext.dispose() +} + +export default globalSetup diff --git a/e2e/fixtures/sample.jpg b/e2e/fixtures/sample.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e58222832f68061d42955762bb3ff914bc643f19 GIT binary patch literal 5812 zcmbVQ4NO&K82-*Z_wpwt7?;egOv-+)twll^s}Y!{EL(^dZpGHXGO(2?QduiX^CxU$ zmh{7BiIpJSP&6uCty0LMx%>eXTU%C``7_bbq{!3ze&2b&^L^(6%MOBfaL)U^KhOI- z?|ZBR)=w-UXX^B+%rF?624BoN#O|5$Oz9lPX3Ss###jt9mX|Uwe1cop82Dz)Tx&2B z{x|S!dnrpASK9H7v%dL}c%b0xEl&N=5o1T+5E0|vpy8nUM4?i;J(UQ`+ zWedxndit3~&n|g+>9XZ3Ua5TT_0`pHy!qDKHS6Ac|AX~)^&1*CZ~3U@<4-=_x^4T8 z*0!&Ae)Dbn-hCaNUHcDoA3XH)FTeI2{_V)`M^BypqwmbwKhO35g>gYXMOg4tMCOVs z5#sWAy{0!B<1#!?W8jHi-?-GMQCYdsMe{F7x%s7-r0i9-&0ob{HF0LIzj(o(xa6zT z4vjyBq4CK6TVRX-S7b6UC$1mZWaiG7C8(=>fiKDeS{qVS4|i{V$CeZ}+ZV!TGm z%K{??*zkJ2(eN)}MC^!!3%;x$0hUSDK^AWs046h$WwL?7P{a1N{I0;7;*DpXD6d*l zTUg&5Tw3{II9OENRGNQB!|289+VaA|$BP?B_{(ydkMC}pB_C;81ULBGSNcPthJ$VS z$${UhXLr}GxE>#Fd<=fuSQD(8yX?yP=2^%558`8md&AGArTV5+hHn6q8@d9A*UUax zyCQIdeBzy8)tsdZYS7xp=*iX(LTBrX<5v3bZ4S0o)`c3y4eP`WU>BS^ai7bn8uWTd z3*giobV|gT(N=gm{M;A)E3(^{|*cI2~WJlyvx1i?}T<7_$3pMR8CdpB z{LIdXGiC6Q9j#=Df_6xFtDk#KMdta0umDcwPRTedz^U)SsqC$}2q7H>;5En1;c7qu zoGC5zgwB=}WLkR$!cIQ>wgs!Ub0`qp_+tnjisg35Uwl?B0_#&WR(7xL>Zayk3xbC# zpp||?bv{-=#s?$L{J-fj=DHL@-borv!+%jGam_?)+f? z;uF{eR3pMTAr`^mq+>7(3InuYlHg47gpwagDn*pQ)+B^ClpUTR07_gCktr5*1{fy8 zN+L#D`vwM-gvK|HZ6Br`q1FyU$3)N@xw^&a6=R14i?um%iN-^1=@NaUXbbew zSgYgQD(pLM<%?}~q4Vh+>8|$MA<10;5_?yn5;rFxzY*SmAr-`ml5hf)WQ3BWk`dsK z$D+`gVnHAlWff38y@B5;ubOM$KuRk_t=yJkP#{h_kS@+k90d?&Iw@^r ziGcV=FR?oNQz4%MY+tmD51k+F&Znl}+_OTZ(t}E$&Hh}o0B#&SE^<$I=!|DW>BdOT zP&Eok1xl2?H7IewImAX2!A(?v`WG}x?_WNM1i}vM91~trD$t|`P*4V50S+wW^>%gE zu~|Ak%j)XiC3Wnf{hPxT@2<8Loa+a%zZ4ks3~wMR!r-yL3FB&Pe7=d0C@nT*H+MdP z-N>X+J!Cg5kyJe!U9xEq(FMwwCcrbym?mYA;D+ha7-13gv&sm1o7qhgF)J2Y`%m$T za@1GkwKt!i$6Z(SSmBK!r;)+XMXiGqkUhNd))_+Nbj(w;Io;^OJfAaE%me-u%u@*{ z4$82K3$7G=5FV_mGJ!IVd=ZC+P+4ZwMXsb3*6yDi<@;w`U}yhw4RAzf%0O)+Uy&pi zI%uMiileI>2{K4AUGVx4VaPG{B5?yB&)>E7d}j}#5ZTGRaK-9A3I#n?>LjK$cgKtNR6>Cp zv`}PoQ792JrZ&cE*|9xUO!75=1|AilY2>v%5kUd+oWRl~LE57;Ay^U@N0;&h5`SEe zz`QeciM>E7gWN0-;%Kr7hESM6Y3V%B6S7ze)M=$uaM_ { + const dt = new DataTransfer() + const blob = await fetch( `data:${ type };base64,${ data }` ).then( r => r.blob() ) + const file = new File( [ blob ], name, { type } ) + dt.items.add( file ) + return dt + }, + { data: base64, name: fileName, type: mimeType } + ) + + await target.dispatchEvent( 'dragenter', { dataTransfer } ) + await target.dispatchEvent( 'dragover', { dataTransfer } ) + await target.dispatchEvent( 'drop', { dataTransfer } ) +} + +/** + * Wait until Cimo's admin script has localized settings (interceptors are ready). + */ +export async function waitForCimoReady( page: Page ) { + await page.waitForFunction( () => { + return typeof ( window as Window & { cimoSettings?: unknown } ).cimoSettings === 'object' + }, undefined, { timeout: 30_000 } ) +} + +/** + * Wait until drop/select listeners are attached inside the block editor iframe. + */ +export async function waitForCimoEditorIframeReady( page: Page ) { + await page.waitForFunction( () => { + const iframe = document.querySelector( 'iframe[name="editor-canvas"]' ) as HTMLIFrameElement | null + const body = iframe?.contentDocument?.body as ( HTMLElement & { + __cimo_dropzone_listener_attached?: boolean; + __cimo_selectfiles_listener_attached?: boolean; + } ) | null | undefined + return !!( + body?.__cimo_dropzone_listener_attached && + body?.__cimo_selectfiles_listener_attached + ) + }, undefined, { timeout: 30_000 } ) +} + +/** + * Assert that a new media item was uploaded as WebP after `afterId`. + */ +export async function expectNewMediaIsWebp( + requestUtils: ExtendedRequestUtils, + afterId: number, + options: { timeout?: number } = {} +) { + const timeout = options.timeout ?? 60_000 + + await expect.poll( + async () => { + const created = await requestUtils.getMediaCreatedAfter( afterId ) + if ( ! created.length ) { + return null + } + const newest = created[ 0 ] + return { + id: newest.id, + mime: newest.mime_type, + url: newest.source_url as string, + } + }, + { timeout, message: 'Expected a new WebP media attachment after upload' } + ).toMatchObject( { + mime: 'image/webp', + } ) + + const created = await requestUtils.getMediaCreatedAfter( afterId ) + const newest = created[ 0 ] + expect( newest.source_url ).toMatch( /\.webp(\?|$)/i ) + return newest +} diff --git a/e2e/test-utils/requestUtils.ts b/e2e/test-utils/requestUtils.ts new file mode 100644 index 0000000..8974561 --- /dev/null +++ b/e2e/test-utils/requestUtils.ts @@ -0,0 +1,117 @@ +import { request as playwrightRequest } from '@playwright/test' +import { RequestUtils as BaseRequestUtils } from '@wordpress/e2e-test-utils-playwright' + +class ExtendedRequestUtils extends BaseRequestUtils { + static async setup( options: { + baseURL?: string; + user?: { username: string; password: string }; + storageStatePath?: string; + } = {} ) { + const { + baseURL = process.env.WP_BASE_URL, + user, + storageStatePath, + } = options + + let storageState + if ( storageStatePath ) { + const fs = await import( 'fs/promises' ) + const path = await import( 'path' ) + await fs.mkdir( path.dirname( storageStatePath ), { recursive: true } ) + try { + storageState = JSON.parse( await fs.readFile( storageStatePath, 'utf-8' ) ) + } catch ( error ) { + if ( ! ( error instanceof Error && 'code' in error && error.code === 'ENOENT' ) ) { + throw error + } + } + } + + const requestContext = await playwrightRequest.newContext( { + baseURL, + ignoreHTTPSErrors: true, + storageState: storageState && { + cookies: storageState.cookies, + origins: [], + }, + } ) + + return new this( requestContext, { + user, + storageState, + storageStatePath, + baseURL, + } ) + } + + getActivePlugins = async function() { + const plugins: { [key: string]: any }[] = await this.rest( { + path: '/wp/v2/plugins', + } ) + // eslint-disable-next-line no-console + console.info( 'plugins installed:', plugins.map( plugin => plugin.plugin ), '\n' ) + const activePlugins = plugins.filter( plugin => plugin.status === 'active' ).reduce( ( pluginsMap, plugin ) => { + pluginsMap[ plugin.plugin ] = plugin.plugin + return pluginsMap + }, {} ) + + return activePlugins + } + + deactivatePlugin = async function( slug: string ) { + try { + await this.rest( { + method: 'PUT', + path: `/wp/v2/plugins/${ slug }`, + data: { status: 'inactive' }, + } ) + } catch ( error ) { + throw new Error( `Cannot deactivate ${ slug }: ${ error.message }` ) + } + } + + activatePlugin = async function( slug: string ) { + try { + await this.rest( { + method: 'PUT', + path: `/wp/v2/plugins/${ slug }`, + data: { status: 'active' }, + } ) + } catch ( error ) { + throw new Error( `Cannot activate ${ slug }: ${ error.message }` ) + } + } + + deletePost = async function( pid: string, postType: string = 'posts' ) { + await this.rest( { + method: 'DELETE', + path: `/wp/v2/${ postType }/${ pid }`, + params: { + force: true, + }, + } ) + } + + /** + * Highest media attachment ID currently in the library (0 if empty). + */ + getMaxMediaId = async function(): Promise { + const media = await this.listMedia() + if ( ! media.length ) { + return 0 + } + return Math.max( ...media.map( ( item: { id: number } ) => item.id ) ) + } + + /** + * Newest media items created after `afterId`. + */ + getMediaCreatedAfter = async function( afterId: number ) { + const media = await this.listMedia() + return media + .filter( ( item: { id: number } ) => item.id > afterId ) + .sort( ( a: { id: number }, b: { id: number } ) => b.id - a.id ) + } +} + +export { ExtendedRequestUtils } diff --git a/e2e/test-utils/test.ts b/e2e/test-utils/test.ts new file mode 100644 index 0000000..a0851d0 --- /dev/null +++ b/e2e/test-utils/test.ts @@ -0,0 +1,24 @@ +import { + test as base, + expect, +} from '@wordpress/e2e-test-utils-playwright' + +import { ExtendedRequestUtils } from './requestUtils' + +const test = base.extend<{ + requestUtils: ExtendedRequestUtils; +}>( { + requestUtils: async ( {}, use ) => { + const requestUtils = await ExtendedRequestUtils.setup( { + baseURL: process.env.WP_BASE_URL, + user: { + username: process.env.WP_USERNAME, + password: process.env.WP_PASSWORD, + }, + } ) + + await use( requestUtils ) + }, +} ) + +export { test, expect } diff --git a/e2e/tests/upload-interception.spec.ts b/e2e/tests/upload-interception.spec.ts new file mode 100644 index 0000000..4996f47 --- /dev/null +++ b/e2e/tests/upload-interception.spec.ts @@ -0,0 +1,188 @@ +import { + test, + expect, + SAMPLE_JPG, + dropFile, + waitForCimoReady, + waitForCimoEditorIframeReady, + expectNewMediaIsWebp, +} from 'e2e/test-utils' + +test.describe.configure( { timeout: 90_000 } ) + +test.describe( 'Upload interception (JPG → WebP)', () => { + let pageId: string | null = null + + test.beforeEach( async ( { requestUtils } ) => { + await requestUtils.deleteAllMedia() + } ) + + test.afterEach( async ( { requestUtils } ) => { + if ( pageId ) { + await requestUtils.deletePost( pageId, 'pages' ) + pageId = null + } + await requestUtils.deleteAllMedia() + } ) + + async function openNewPage( admin, editor, page ) { + await admin.createNewPost( { + postType: 'page', + title: 'Cimo Upload E2E', + showWelcomeGuide: false, + } ) + await editor.saveDraft() + const postQuery = new URL( editor.page.url() ).search + pageId = new URLSearchParams( postQuery ).get( 'post' ) + await waitForCimoReady( page ) + await waitForCimoEditorIframeReady( page ) + } + + test( 'image block Upload button converts JPG to WebP', async ( { + admin, + editor, + page, + requestUtils, + } ) => { + await openNewPage( admin, editor, page ) + const afterId = await requestUtils.getMaxMediaId() + + await editor.insertBlock( { name: 'core/image' } ) + + const fileInput = editor.canvas.locator( + '.components-form-file-upload input[type="file"]' + ) + await expect( fileInput ).toBeAttached( { timeout: 15_000 } ) + await fileInput.setInputFiles( SAMPLE_JPG ) + + await expectNewMediaIsWebp( requestUtils, afterId ) + await expect( + editor.canvas.locator( 'img[src*=".webp"]' ).first() + ).toBeVisible( { timeout: 30_000 } ) + } ) + + test( 'image block Media Library drop converts JPG to WebP', async ( { + admin, + editor, + page, + requestUtils, + } ) => { + await openNewPage( admin, editor, page ) + const afterId = await requestUtils.getMaxMediaId() + + await editor.insertBlock( { name: 'core/image' } ) + await editor.canvas.getByRole( 'button', { name: 'Media Library' } ).click() + + const modal = page.locator( '.media-modal' ) + await expect( modal ).toBeVisible( { timeout: 15_000 } ) + + const uploadTab = modal.getByRole( 'tab', { name: /Upload files/i } ) + if ( await uploadTab.count() ) { + await uploadTab.click() + } + + const dropTarget = modal.locator( + '.media-frame-uploader, .uploader-inline, .upload-ui' + ).first() + await expect( dropTarget ).toBeVisible( { timeout: 15_000 } ) + await dropFile( dropTarget ) + + const media = await expectNewMediaIsWebp( requestUtils, afterId ) + + // Select the uploaded attachment in the modal if still open. + const webpAttachment = modal.locator( `.attachment[data-id="${ media.id }"]` ) + if ( await modal.isVisible() && await webpAttachment.count() ) { + await webpAttachment.click() + const selectButton = modal.getByRole( 'button', { + name: /Select|Insert/i, + } ) + if ( await selectButton.isEnabled() ) { + await selectButton.click() + } + } + + await expect( + editor.canvas.locator( 'img[src*=".webp"]' ).first() + ).toBeVisible( { timeout: 30_000 } ) + } ) + + test( 'dropping JPG on the editor canvas converts to WebP image block', async ( { + admin, + editor, + page, + requestUtils, + } ) => { + await openNewPage( admin, editor, page ) + const afterId = await requestUtils.getMaxMediaId() + + const canvas = editor.canvas.locator( '.editor-styles-wrapper' ) + await expect( canvas ).toBeVisible( { timeout: 15_000 } ) + await dropFile( canvas ) + + await expectNewMediaIsWebp( requestUtils, afterId ) + await expect( + editor.canvas.locator( '[data-type="core/image"] img[src*=".webp"]' ).first() + ).toBeVisible( { timeout: 30_000 } ) + } ) + + test( 'dropping JPG on featured image converts to WebP', async ( { + admin, + editor, + page, + requestUtils, + } ) => { + await openNewPage( admin, editor, page ) + const afterId = await requestUtils.getMaxMediaId() + + // Open the Page / Document sidebar (featured image lives there). + const pageTab = page.getByRole( 'button', { name: 'Page', exact: true } ) + const documentTab = page.getByRole( 'button', { + name: 'Document', + exact: true, + } ) + if ( await pageTab.count() ) { + await pageTab.click() + } else if ( await documentTab.count() ) { + await documentTab.click() + } else { + await page.getByLabel( /Settings/i ).click() + await page.getByRole( 'tab', { name: /Page|Document/i } ).click() + } + + const featured = page.locator( '.editor-post-featured-image' ) + await expect( featured ).toBeVisible( { timeout: 15_000 } ) + await dropFile( featured ) + + await expectNewMediaIsWebp( requestUtils, afterId ) + await expect( + featured.locator( 'img[src*=".webp"]' ).first() + ).toBeVisible( { timeout: 30_000 } ) + } ) + + test( 'Media Library admin upload converts JPG to WebP', async ( { + page, + requestUtils, + } ) => { + await page.goto( '/wp-admin/media-new.php' ) + await waitForCimoReady( page ) + const afterId = await requestUtils.getMaxMediaId() + + const fileInput = page.locator( + '.media-upload-form input[type="file"], #async-upload, input[name="async-upload"]' + ).first() + await expect( fileInput ).toBeAttached( { timeout: 15_000 } ) + await fileInput.setInputFiles( SAMPLE_JPG ) + + const media = await expectNewMediaIsWebp( requestUtils, afterId ) + + await page.goto( '/wp-admin/upload.php' ) + await expect( + page.locator( `.attachment[data-id="${ media.id }"]` ) + ).toBeVisible( { timeout: 30_000 } ) + await expect( + page.locator( + `.attachment[data-id="${ media.id }"] img[src*=".webp"], .media-icon img[src*=".webp"]` + ).first() + ).toBeVisible( { timeout: 15_000 } ) + } ) +} ) diff --git a/package-lock.json b/package-lock.json index 1e093c5..f3957a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cimo", - "version": "1.2.2", + "version": "1.4.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cimo", - "version": "1.2.2", + "version": "1.4.1", "license": "GPL-2.0-or-later", "dependencies": { "@mediabunny/mp3-encoder": "^1.25.0", @@ -19,11 +19,16 @@ "svgo": "^4.0.0" }, "devDependencies": { + "@playwright/test": "^1.61.1", + "@types/node": "^20.19.43", + "@wordpress/e2e-test-utils-playwright": "^1.51.0", + "@wordpress/env": "^10.39.0", "@wordpress/eslint-plugin": "^22.15.0", "@wordpress/prettier-config": "^4.29.0", "@wordpress/scripts": "^30.0.0", "@wordpress/stylelint-config": "^23.21.0", "archiver": "^6.0.1", + "dotenv": "^16.6.1", "eslint": "^8.57.0", "eslint-plugin-compat": "^6.0.2", "prettier": "^3.3.3" @@ -2234,541 +2239,1633 @@ "deprecated": "Use @eslint/object-schema instead", "dev": true }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "node_modules/@inquirer/ansi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "node_modules/@inquirer/checkbox": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz", + "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0" + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "node_modules/@inquirer/confirm": { + "version": "5.1.21", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18" }, "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + "@types/node": ">=18" }, "peerDependenciesMeta": { - "node-notifier": { + "@types/node": { "optional": true } } }, - "node_modules/@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "node_modules/@inquirer/core": { + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.3" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@jest/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "node_modules/@inquirer/core/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" - }, + "license": "ISC", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "node_modules/@inquirer/core/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, + "license": "MIT", "dependencies": { - "jest-get-type": "^29.6.3" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "node_modules/@inquirer/editor": { + "version": "4.2.23", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz", + "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" + "@inquirer/core": "^10.3.2", + "@inquirer/external-editor": "^1.0.3", + "@inquirer/type": "^3.0.10" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "node_modules/@inquirer/expand": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz", + "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", "dev": true, + "license": "MIT", "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18" }, "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + "@types/node": ">=18" }, "peerDependenciesMeta": { - "node-notifier": { + "@types/node": { "optional": true } } }, - "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "node_modules/@inquirer/external-editor/node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": ">=10" + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/@jest/reporters/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", "dev": true, - "bin": { - "semver": "bin/semver.js" - }, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=18" } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "node_modules/@inquirer/input": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz", + "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", "dev": true, + "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.27.8" + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz", + "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz", + "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.10.1.tgz", + "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^4.3.2", + "@inquirer/confirm": "^5.1.21", + "@inquirer/editor": "^4.2.23", + "@inquirer/expand": "^4.0.23", + "@inquirer/input": "^4.3.1", + "@inquirer/number": "^3.0.23", + "@inquirer/password": "^4.0.23", + "@inquirer/rawlist": "^4.1.11", + "@inquirer/search": "^3.2.2", + "@inquirer/select": "^4.4.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz", + "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz", + "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz", + "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@jest/reporters/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "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, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "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 + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@keyv/serialize": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.0.tgz", + "integrity": "sha512-RlDgexML7Z63Q8BSaqhXdCYNBy/JQnqYIwxofUrNLGCblOMHp+xux2Q8nLMLlPpgHQPoU0Do8Z6btCpRBEqZ8g==", + "dev": true + }, + "node_modules/@kwsites/file-exists": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", + "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1" + } + }, + "node_modules/@kwsites/promise-deferred": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "dev": true + }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "optional": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "optional": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@mdn/browser-compat-data": { + "version": "5.7.6", + "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-5.7.6.tgz", + "integrity": "sha512-7xdrMX0Wk7grrTZQwAoy1GkvPMFoizStUoL+VmtUkAxegbCCec+3FKwOM6yc/uGU5+BEczQHXAlWiqvM8JeENg==", + "dev": true + }, + "node_modules/@mediabunny/mp3-encoder": { + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/@mediabunny/mp3-encoder/-/mp3-encoder-1.25.0.tgz", + "integrity": "sha512-RfehCfmb3/by/Eti3B6m4998qQT3SlMd1fVRH9CYedOz5TV9crVLRXiBcS+UmDma8XN0Q/WTzlimLQ9356+yNA==", + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/Vanilagy" + }, + "peerDependencies": { + "mediabunny": "^1.0.0" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", + "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", + "dev": true, + "dependencies": { + "eslint-scope": "5.1.1" + } + }, + "node_modules/@nodable/entities": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-3.0.0.tgz", + "integrity": "sha512-8L9xFeTYKhm49xfIypoe2W5wV1m/3Z58kT+7kR9A8OyFxcPduI4VmxaUMQyKYrRjUoLLSXv6EKKID5Tvj9cUVw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nodable" + } + ], + "license": "MIT" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@octokit/app": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/app/-/app-14.1.0.tgz", + "integrity": "sha512-g3uEsGOQCBl1+W1rgfwoRFUIR6PtvB2T1E4RpygeUU5LrLvlOqcxrt5lfykIeRpUPpupreGJUYl70fqMDXdTpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-app": "^6.0.0", + "@octokit/auth-unauthenticated": "^5.0.0", + "@octokit/core": "^5.0.0", + "@octokit/oauth-app": "^6.0.0", + "@octokit/plugin-paginate-rest": "^9.0.0", + "@octokit/types": "^12.0.0", + "@octokit/webhooks": "^12.0.4" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-app": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-6.1.4.tgz", + "integrity": "sha512-QkXkSOHZK4dA5oUqY5Dk3S+5pN2s1igPjEASNQV8/vgJgW034fQWR16u7VsNOK/EljA00eyjYF5mWNxWKWhHRQ==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" + "@octokit/auth-oauth-app": "^7.1.0", + "@octokit/auth-oauth-user": "^4.1.0", + "@octokit/request": "^8.3.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.1.0", + "deprecation": "^2.3.1", + "lru-cache": "npm:@wolfy1339/lru-cache@^11.0.2-patch.1", + "universal-github-app-jwt": "^1.1.2", + "universal-user-agent": "^6.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 18" } }, - "node_modules/@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "node_modules/@octokit/auth-app/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/auth-app/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/auth-app/node_modules/lru-cache": { + "name": "@wolfy1339/lru-cache", + "version": "11.0.2-patch.1", + "resolved": "https://registry.npmjs.org/@wolfy1339/lru-cache/-/lru-cache-11.0.2-patch.1.tgz", + "integrity": "sha512-BgYZfL2ADCXKOw2wJtkM3slhHotawWkgIRRxq4wEybnZQPjvAp71SPX35xepMykTw8gXlzWcWPTY31hlbnRsDA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "18 >=18.20 || 20 || >=22" + } + }, + "node_modules/@octokit/auth-oauth-app": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-7.1.0.tgz", + "integrity": "sha512-w+SyJN/b0l/HEb4EOPRudo7uUOSW51jcK1jwLa+4r7PA8FPFpoxEnHBHMITqCsc/3Vo2qqFjgQfz/xUUvsSQnA==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" + "@octokit/auth-oauth-device": "^6.1.0", + "@octokit/auth-oauth-user": "^4.1.0", + "@octokit/request": "^8.3.1", + "@octokit/types": "^13.0.0", + "@types/btoa-lite": "^1.0.0", + "btoa-lite": "^1.0.0", + "universal-user-agent": "^6.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 18" } }, - "node_modules/@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "node_modules/@octokit/auth-oauth-app/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/auth-oauth-app/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "slash": "^3.0.0" + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/auth-oauth-device": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-6.1.0.tgz", + "integrity": "sha512-FNQ7cb8kASufd6Ej4gnJ3f1QB5vJitkoV1O0/g6e6lUsQ7+VsSNRHRmFScN2tV4IgKA12frrr/cegUs0t+0/Lw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/oauth-methods": "^4.1.0", + "@octokit/request": "^8.3.1", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^6.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 18" } }, - "node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "node_modules/@octokit/auth-oauth-device/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/auth-oauth-device/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/auth-oauth-user": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-4.1.0.tgz", + "integrity": "sha512-FrEp8mtFuS/BrJyjpur+4GARteUCrPeR/tZJzD8YourzoVhRics7u7we/aDcKv+yywRNwNi/P4fRi631rG/OyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-device": "^6.1.0", + "@octokit/oauth-methods": "^4.1.0", + "@octokit/request": "^8.3.1", + "@octokit/types": "^13.0.0", + "btoa-lite": "^1.0.0", + "universal-user-agent": "^6.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 18" } }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "node_modules/@octokit/auth-oauth-user/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/auth-oauth-user/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/auth-token": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", + "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-unauthenticated": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-5.0.1.tgz", + "integrity": "sha512-oxeWzmBFxWd+XolxKTc4zr+h3mt+yofn4r7OfoIkR/Cj/o70eEGmPsFbueyJE2iBAGpjgTnEOKM3pnuEGVmiqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/request-error": "^5.0.0", + "@octokit/types": "^12.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 18" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "node_modules/@octokit/core": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.2.tgz", + "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "@octokit/auth-token": "^4.0.0", + "@octokit/graphql": "^7.1.0", + "@octokit/request": "^8.4.1", + "@octokit/request-error": "^5.1.1", + "@octokit/types": "^13.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" } }, - "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==", + "node_modules/@octokit/core/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/core/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/endpoint": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz", + "integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==", "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, "engines": { - "node": ">=6.0.0" + "node": ">= 18" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", - "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "node_modules/@octokit/endpoint/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/endpoint/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" + "@octokit/openapi-types": "^24.2.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 + "node_modules/@octokit/graphql": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.1.tgz", + "integrity": "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/request": "^8.4.1", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.30", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", - "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", + "node_modules/@octokit/graphql/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/graphql/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "@octokit/openapi-types": "^24.2.0" } }, - "node_modules/@keyv/serialize": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.0.tgz", - "integrity": "sha512-RlDgexML7Z63Q8BSaqhXdCYNBy/JQnqYIwxofUrNLGCblOMHp+xux2Q8nLMLlPpgHQPoU0Do8Z6btCpRBEqZ8g==", - "dev": true + "node_modules/@octokit/oauth-app": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-6.1.0.tgz", + "integrity": "sha512-nIn/8eUJ/BKUVzxUXd5vpzl1rwaVxMyYbQkNZjHrF7Vk/yu98/YDF/N2KeWO7uZ0g3b5EyiFXFkZI8rJ+DH1/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-app": "^7.0.0", + "@octokit/auth-oauth-user": "^4.0.0", + "@octokit/auth-unauthenticated": "^5.0.0", + "@octokit/core": "^5.0.0", + "@octokit/oauth-authorization-url": "^6.0.2", + "@octokit/oauth-methods": "^4.0.0", + "@types/aws-lambda": "^8.10.83", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", - "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", - "dev": true + "node_modules/@octokit/oauth-authorization-url": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-6.0.2.tgz", + "integrity": "sha512-CdoJukjXXxqLNK4y/VOiVzQVjibqoj/xHgInekviUJV73y/BSIcwvJ/4aNHPBPKcPWFnd4/lO9uqRV65jXhcLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18" + } }, - "node_modules/@mapbox/node-pre-gyp": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", - "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", - "optional": true, + "node_modules/@octokit/oauth-methods": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-4.1.0.tgz", + "integrity": "sha512-4tuKnCRecJ6CG6gr0XcEXdZtkTDbfbnD5oaHBmLERTjTMZNi2CbfEHZxPU41xXLDG4DfKf+sonu00zvKI9NSbw==", + "dev": true, + "license": "MIT", "dependencies": { - "detect-libc": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "make-dir": "^3.1.0", - "node-fetch": "^2.6.7", - "nopt": "^5.0.0", - "npmlog": "^5.0.1", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.11" + "@octokit/oauth-authorization-url": "^6.0.2", + "@octokit/request": "^8.3.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.0.0", + "btoa-lite": "^1.0.0" }, - "bin": { - "node-pre-gyp": "bin/node-pre-gyp" + "engines": { + "node": ">= 18" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "optional": true, + "node_modules/@octokit/oauth-methods/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/oauth-methods/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-graphql": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-4.0.1.tgz", + "integrity": "sha512-R8ZQNmrIKKpHWC6V2gum4x9LG2qF1RxRjo27gjQcG3j+vf2tLsEfE7I/wRWEPzYMaenr1M+qDAtNcwZve1ce1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=5" + } + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.2.tgz", + "integrity": "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^12.6.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.4.1.tgz", + "integrity": "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^12.6.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-retry": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-6.1.0.tgz", + "integrity": "sha512-WrO3bvq4E1Xh1r2mT9w6SDFg01gFmP81nIG77+p/MqW1JeXXgL++6umim3t6x0Zj5pZm3rXAN+0HEjmmdhIRig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/request-error": "^5.0.0", + "@octokit/types": "^13.0.0", + "bottleneck": "^2.15.3" + }, "engines": { - "node": ">=8" + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "optional": true, + "node_modules/@octokit/plugin-retry/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-retry/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", "dependencies": { - "semver": "^6.0.0" + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/plugin-throttling": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-8.2.0.tgz", + "integrity": "sha512-nOpWtLayKFpgqmgD0y3GqXafMFuKcA4tRPZIfu7BArd2lEZeb1988nhWhwx4aZWmjDmUfdgVf7W+Tt4AmvRmMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^12.2.0", + "bottleneck": "^2.15.3" }, "engines": { - "node": ">=8" + "node": ">= 18" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@octokit/core": "^5.0.0" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "optional": true, - "bin": { - "semver": "bin/semver.js" + "node_modules/@octokit/request": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz", + "integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^9.0.6", + "@octokit/request-error": "^5.1.1", + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "optional": true, - "bin": { - "semver": "bin/semver.js" + "node_modules/@octokit/request-error": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz", + "integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" }, "engines": { - "node": ">=10" + "node": ">= 18" } }, - "node_modules/@mdn/browser-compat-data": { - "version": "5.7.6", - "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-5.7.6.tgz", - "integrity": "sha512-7xdrMX0Wk7grrTZQwAoy1GkvPMFoizStUoL+VmtUkAxegbCCec+3FKwOM6yc/uGU5+BEczQHXAlWiqvM8JeENg==", - "dev": true + "node_modules/@octokit/request-error/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" }, - "node_modules/@mediabunny/mp3-encoder": { - "version": "1.25.0", - "resolved": "https://registry.npmjs.org/@mediabunny/mp3-encoder/-/mp3-encoder-1.25.0.tgz", - "integrity": "sha512-RfehCfmb3/by/Eti3B6m4998qQT3SlMd1fVRH9CYedOz5TV9crVLRXiBcS+UmDma8XN0Q/WTzlimLQ9356+yNA==", - "funding": { - "type": "individual", - "url": "https://github.com/sponsors/Vanilagy" - }, - "peerDependencies": { - "mediabunny": "^1.0.0" + "node_modules/@octokit/request-error/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", - "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "node_modules/@octokit/request/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", "dev": true, - "optional": true, + "license": "MIT" + }, + "node_modules/@octokit/request/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.10.0" + "@octokit/openapi-types": "^24.2.0" } }, - "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { - "version": "5.1.1-v1", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", - "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", + "node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", "dev": true, + "license": "MIT", "dependencies": { - "eslint-scope": "5.1.1" + "@octokit/openapi-types": "^20.0.0" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@octokit/webhooks": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-12.3.2.tgz", + "integrity": "sha512-exj1MzVXoP7xnAcAB3jZ97pTvVPkQF9y6GA/dvYC47HV7vLv+24XRS6b/v/XnyikpEuvMhugEXdGtAlU086WkQ==", "dev": true, + "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@octokit/request-error": "^5.0.0", + "@octokit/webhooks-methods": "^4.1.0", + "@octokit/webhooks-types": "7.6.1", + "aggregate-error": "^3.1.0" }, "engines": { - "node": ">= 8" + "node": ">= 18" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/@octokit/webhooks-methods": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-4.1.0.tgz", + "integrity": "sha512-zoQyKw8h9STNPqtm28UGOYFE7O6D4Il8VJwhAtMHFt2C4L0VQT1qGKLeefUOqHNs1mNRYSadVv7x0z8U2yyeWQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 8" + "node": ">= 18" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/@octokit/webhooks-types": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-7.6.1.tgz", + "integrity": "sha512-S8u2cJzklBC0FgTwWVLaM8tMrDuDMVE4xiTK4EYXM9GntyvrdbSoxqDQa+Fh57CCNApyIpyeqPhhFEmHPfrXgw==", "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } + "license": "MIT" }, "node_modules/@parcel/watcher": { "version": "2.5.1", @@ -3046,34 +4143,336 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/@parcel/watcher-win32-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", - "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", - "cpu": [ - "x64" - ], + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@paulirish/trace_engine": { + "version": "0.0.53", + "resolved": "https://registry.npmjs.org/@paulirish/trace_engine/-/trace_engine-0.0.53.tgz", + "integrity": "sha512-PUl/vlfo08Oj804VI5nDPeSk9vyslnBlVzDDwFt8SUVxY8+KdGMkra/vrXjEEHe8gb7+RqVTfOIlGw0nyrEelA==", + "dev": true, + "dependencies": { + "legacy-javascript": "latest", + "third-party-web": "latest" + } + }, + "node_modules/@php-wasm/cli-util": { + "version": "3.1.45", + "resolved": "https://registry.npmjs.org/@php-wasm/cli-util/-/cli-util-3.1.45.tgz", + "integrity": "sha512-+ht22B6KPUsn5ORtApE5xYq+uIb7/Qv/NSOxYgNGSWf3O1rpxkbOMeCxtE7H3dvazJIlSF+/nsVHpHkcTZGOpA==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/util": "3.1.45", + "fast-xml-parser": "^5.8.0", + "jsonc-parser": "3.3.1" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/cli-util/node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@php-wasm/logger": { + "version": "3.1.45", + "resolved": "https://registry.npmjs.org/@php-wasm/logger/-/logger-3.1.45.tgz", + "integrity": "sha512-FYWGxaRZTM+aiRQ6/LlnFAdsixJ7VWQpM8Fe/61yWgpBamb1w3NEDRhYVR5DNjYWJVI5ha6pVxIsHtw018iA7g==", + "dev": true, + "license": "GPL-2.0-or-later", + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node": { + "version": "3.1.45", + "resolved": "https://registry.npmjs.org/@php-wasm/node/-/node-3.1.45.tgz", + "integrity": "sha512-IFnGdNsQ0g8RwpFzg3gAt8VckFwshQ7g2Izwt+i+wi2Rtnj54DcPZpfMwliiq+c1XU1//jtXHBdczbwR75yW7A==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/cli-util": "3.1.45", + "@php-wasm/logger": "3.1.45", + "@php-wasm/node-5-2": "3.1.45", + "@php-wasm/node-7-4": "3.1.45", + "@php-wasm/node-8-0": "3.1.45", + "@php-wasm/node-8-1": "3.1.45", + "@php-wasm/node-8-2": "3.1.45", + "@php-wasm/node-8-3": "3.1.45", + "@php-wasm/node-8-4": "3.1.45", + "@php-wasm/node-8-5": "3.1.45", + "@php-wasm/universal": "3.1.45", + "@php-wasm/util": "3.1.45", + "fs-ext-extra-prebuilt": "2.2.7", + "wasm-feature-detect": "1.8.0", + "ws": "8.21.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node-5-2": { + "version": "3.1.45", + "resolved": "https://registry.npmjs.org/@php-wasm/node-5-2/-/node-5-2-3.1.45.tgz", + "integrity": "sha512-mKYOI8/eWE7Gu9wOOwtcxGrhOfCfGlIqT3Jr1RK0qCsZ/GkiW2/EalsNC5wSWbaL9qdh4JUbXUoF7mE3dc+KRQ==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.45", + "wasm-feature-detect": "1.8.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node-7-4": { + "version": "3.1.45", + "resolved": "https://registry.npmjs.org/@php-wasm/node-7-4/-/node-7-4-3.1.45.tgz", + "integrity": "sha512-3+VgXIL4y4Acb/OyNQAsIfz5I5GYlFmjw4M8l2Xx8TNBrixuOEO5vHmMoTssynZlmjoTLxBBzL2SaRHoubwi6g==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.45", + "wasm-feature-detect": "1.8.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node-8-0": { + "version": "3.1.45", + "resolved": "https://registry.npmjs.org/@php-wasm/node-8-0/-/node-8-0-3.1.45.tgz", + "integrity": "sha512-6yJaW+mxgBWAy80IYRU41bh1LH4WsuDgqEWiHSumJcKOcNljSedUbm+YuLTY+Pnsov7u5yzkaBskslxGXcOlIQ==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.45", + "wasm-feature-detect": "1.8.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node-8-1": { + "version": "3.1.45", + "resolved": "https://registry.npmjs.org/@php-wasm/node-8-1/-/node-8-1-3.1.45.tgz", + "integrity": "sha512-/5haLrmCySu40aDGtVAOukDBNYzCJ0JTXslXs36Rh8P9V2nlctVGO+Kp1m/74yCtdoJrZsRrBnVqKBCliVQjdg==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.45", + "wasm-feature-detect": "1.8.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node-8-2": { + "version": "3.1.45", + "resolved": "https://registry.npmjs.org/@php-wasm/node-8-2/-/node-8-2-3.1.45.tgz", + "integrity": "sha512-fGsSHw6OTnNyjr6k87OqV8SYqRZlDrWYVSXFkOywXUbBvyAYeuttxHffYC4VoRZdRz+Ygu+2k2aPE06jpTTBFw==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.45", + "wasm-feature-detect": "1.8.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node-8-3": { + "version": "3.1.45", + "resolved": "https://registry.npmjs.org/@php-wasm/node-8-3/-/node-8-3-3.1.45.tgz", + "integrity": "sha512-dq22w7k6zxSgRKEjFej2k2etHpWA/f6UZFQCwi0qPFTejlVRfGqJoY+6gwtF4PCCGXGD7J36XyRuSFuZz833MA==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.45", + "wasm-feature-detect": "1.8.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node-8-4": { + "version": "3.1.45", + "resolved": "https://registry.npmjs.org/@php-wasm/node-8-4/-/node-8-4-3.1.45.tgz", + "integrity": "sha512-QElunKrFEpVa11Fdr6Afmw/gI/bCf5rxzAuXLBYfgghJP2w8sTT0M1ku1pocPEvMGeONbQgvHA06sDdKXhHEMA==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.45", + "wasm-feature-detect": "1.8.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/node-8-5": { + "version": "3.1.45", + "resolved": "https://registry.npmjs.org/@php-wasm/node-8-5/-/node-8-5-3.1.45.tgz", + "integrity": "sha512-FeKtaRMZorN1uRFbC6DILfv+Y9ImEMLjcpNVyT7zZ4WHPEf1yN0Q0Jo8VH9NNwCmkmiDAQN2gAIn3nKL8vsz+g==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.45", + "wasm-feature-detect": "1.8.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/progress": { + "version": "3.1.45", + "resolved": "https://registry.npmjs.org/@php-wasm/progress/-/progress-3.1.45.tgz", + "integrity": "sha512-rc/I9MSoqAPLRmrgArpWMrSq6s7R16ulrMjG3Ti5T4hfu+j2oxfBCHbrwzR57ITsgecjKZ+G9j/w+saQj9cgpQ==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/logger": "3.1.45" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/scopes": { + "version": "3.1.45", + "resolved": "https://registry.npmjs.org/@php-wasm/scopes/-/scopes-3.1.45.tgz", + "integrity": "sha512-7efo3IvGLMOcUuywB40avWz1c6c7YurmRW/hM44iPlhr72x9Wpth9eHjI+Gsn+qFISmUqjW+G2KHx9BD7BTQBA==", + "dev": true, + "license": "GPL-2.0-or-later", + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/stream-compression": { + "version": "3.1.45", + "resolved": "https://registry.npmjs.org/@php-wasm/stream-compression/-/stream-compression-3.1.45.tgz", + "integrity": "sha512-zLU1AGNEg1tuwm93LHrL8OcV24LyNglTg/CXV5CVZiMPpg1wfIDHJy11qqjZM8iCIJk87qYQhkKOjTnGinAVDg==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/util": "3.1.45" + } + }, + "node_modules/@php-wasm/universal": { + "version": "3.1.45", + "resolved": "https://registry.npmjs.org/@php-wasm/universal/-/universal-3.1.45.tgz", + "integrity": "sha512-3x0TWQg6NEeO+M/oPA3rAP0hJB2gnsqwkb2sUipz0RQQXRkWEJjdrT20UVmGN/dXILDWeQg/VGxZhCktoWZGkA==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/logger": "3.1.45", + "@php-wasm/progress": "3.1.45", + "@php-wasm/stream-compression": "3.1.45", + "@php-wasm/util": "3.1.45", + "ini": "4.1.2" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/universal/node_modules/ini": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.2.tgz", + "integrity": "sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@php-wasm/util": { + "version": "3.1.45", + "resolved": "https://registry.npmjs.org/@php-wasm/util/-/util-3.1.45.tgz", + "integrity": "sha512-czXqXd2NJWkapV/zTr3WPSPdcgyI5KN7xSUusYpgkfljSovfvHOKKrTMQtbfPXRa9+MD8LmPQt+XStKPRp76Tg==", "dev": true, - "optional": true, - "os": [ - "win32" - ], "engines": { - "node": ">= 10.0.0" + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@php-wasm/web-service-worker": { + "version": "3.1.45", + "resolved": "https://registry.npmjs.org/@php-wasm/web-service-worker/-/web-service-worker-3.1.45.tgz", + "integrity": "sha512-L8spIluLVlaOZ8CnNPfJ5IEM/e1YouQ9kB8GJL+NVm27Pci/pxBE5Hi7nsmau/oQJ6JTA6rXMuDh3wyicFU0Xg==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/scopes": "3.1.45", + "@php-wasm/universal": "3.1.45" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" } }, - "node_modules/@paulirish/trace_engine": { - "version": "0.0.53", - "resolved": "https://registry.npmjs.org/@paulirish/trace_engine/-/trace_engine-0.0.53.tgz", - "integrity": "sha512-PUl/vlfo08Oj804VI5nDPeSk9vyslnBlVzDDwFt8SUVxY8+KdGMkra/vrXjEEHe8gb7+RqVTfOIlGw0nyrEelA==", + "node_modules/@php-wasm/xdebug-bridge": { + "version": "3.1.45", + "resolved": "https://registry.npmjs.org/@php-wasm/xdebug-bridge/-/xdebug-bridge-3.1.45.tgz", + "integrity": "sha512-InIWrSl9ULFEyIYbIK+uh2TK3N7VhQuycx3tRSaRY7YNpUGS2xxfNSt4BeJgBgUNmslKsQojSjCcmvumJW1mgg==", "dev": true, + "license": "GPL-2.0-or-later", "dependencies": { - "legacy-javascript": "latest", - "third-party-web": "latest" + "@php-wasm/logger": "3.1.45", + "@php-wasm/universal": "3.1.45", + "ws": "8.21.0", + "xml2js": "0.6.2", + "yargs": "17.7.2" + }, + "bin": { + "xdebug-bridge": "xdebug-bridge.js" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" } }, "node_modules/@pkgr/core": { @@ -3089,13 +4488,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.58.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", - "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.1.tgz", + "integrity": "sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==", "dev": true, - "peer": true, + "license": "Apache-2.0", "dependencies": { - "playwright": "1.58.2" + "playwright": "1.61.1" }, "bin": { "playwright": "cli.js" @@ -3298,12 +4697,42 @@ "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", "dev": true }, + "node_modules/@simple-git/args-pathspec": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@simple-git/args-pathspec/-/args-pathspec-1.0.3.tgz", + "integrity": "sha512-ngJMaHlsWDTfjyq9F3VIQ8b7NXbBLq5j9i5bJ6XLYtD6qlDXT7fdKY2KscWWUF8t18xx052Y/PUO1K1TRc9yKA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@simple-git/argv-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@simple-git/argv-parser/-/argv-parser-1.1.1.tgz", + "integrity": "sha512-Q9lBcfQ+VQCpQqGJFHe5yooOS5hGdLFFbJ5R+R5aDsnkPCahtn1hSkMcORX65J2Z5lxSkD0lQorMsncuBQxYUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@simple-git/args-pathspec": "^1.0.3" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, "node_modules/@sinonjs/commons": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", @@ -3658,6 +5087,19 @@ "url": "https://github.com/sponsors/gregberge" } }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -3693,6 +5135,13 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/aws-lambda": { + "version": "8.10.162", + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.162.tgz", + "integrity": "sha512-Fn658grtLOci1oxi1391vvDWJRKNGWRSqfxRkmN/Iy3c0tQH1USMKEXcPYHLvope+ZgTFocx9FRQJx1muBL6qw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -3753,6 +5202,26 @@ "@types/node": "*" } }, + "node_modules/@types/btoa-lite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/btoa-lite/-/btoa-lite-1.0.2.tgz", + "integrity": "sha512-ZYbcE2x7yrvNFJiU7xJGrpF/ihpkM7zKgw8bha3LNJSesvTtUNxbpzaT7WXBIryf6jovisrxTBvymxMeLLj1Mg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -3856,6 +5325,13 @@ "@types/node": "*" } }, + "node_modules/@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/http-errors": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", @@ -3918,6 +5394,27 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -3930,11 +5427,19 @@ "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", "dev": true }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { - "version": "20.19.33", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", - "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", "dev": true, + "license": "MIT", "dependencies": { "undici-types": "~6.21.0" } @@ -3997,6 +5502,16 @@ "@types/react": "^18.0.0" } }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", @@ -5031,10 +6546,11 @@ "dev": true }, "node_modules/@wordpress/e2e-test-utils-playwright": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils-playwright/-/e2e-test-utils-playwright-1.40.0.tgz", - "integrity": "sha512-7EMx/5R0l9mlR4s01I06x8bw7qq30VlU98T/tvYJa+ycFQK3oetkoPyiNfki2Y2SILQGjI3Mu4MSV1NPCa/mEw==", + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils-playwright/-/e2e-test-utils-playwright-1.51.0.tgz", + "integrity": "sha512-ekxMfC8MUTf0fKjAQd2IO4m/l1jXC9NznveRf7r8ntmx9nXqd9IHOiYJcH2SBo20C53nWdA4w8+Mbqedf0qzEw==", "dev": true, + "license": "GPL-2.0-or-later", "dependencies": { "change-case": "^4.1.2", "get-port": "^5.1.1", @@ -5082,6 +6598,146 @@ "npm": ">=8.19.2" } }, + "node_modules/@wordpress/env": { + "version": "10.39.0", + "resolved": "https://registry.npmjs.org/@wordpress/env/-/env-10.39.0.tgz", + "integrity": "sha512-Hgl2RQAAzXFMqkpegGWT1/KkX88OVikRroPidWkij1WtU8p+AZniTcncWmlWqbdLdfGbPqQS5ZkqDZCzrQjgnA==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@inquirer/prompts": "^7.2.0", + "@wp-playground/cli": "^3.0.0", + "chalk": "^4.0.0", + "copy-dir": "^1.3.0", + "cross-spawn": "^7.0.6", + "docker-compose": "^0.24.3", + "extract-zip": "^1.6.7", + "got": "^11.8.5", + "js-yaml": "^3.13.1", + "ora": "^4.0.2", + "rimraf": "^5.0.10", + "simple-git": "^3.5.0", + "terminal-link": "^2.0.0", + "yargs": "^17.3.0" + }, + "bin": { + "wp-env": "bin/wp-env" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/env/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@wordpress/env/node_modules/extract-zip": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz", + "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "concat-stream": "^1.6.2", + "debug": "^2.6.9", + "mkdirp": "^0.5.4", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + } + }, + "node_modules/@wordpress/env/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@wordpress/env/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@wordpress/env/node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/@wordpress/env/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/@wordpress/env/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@wordpress/env/node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@wordpress/escape-html": { "version": "3.40.0", "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-3.40.0.tgz", @@ -5416,20 +7072,159 @@ "react-dom": "^18.0.0", "stylelint": "^16.8.2" }, - "peerDependenciesMeta": { - "stylelint": { - "optional": true - } + "peerDependenciesMeta": { + "stylelint": { + "optional": true + } + } + }, + "node_modules/@wordpress/warning": { + "version": "3.40.0", + "resolved": "https://registry.npmjs.org/@wordpress/warning/-/warning-3.40.0.tgz", + "integrity": "sha512-0l3OFa1Z+UdhWRRHX9JWWKofo7Lbi2MqOFzzzn0MC26HOyfieQycjLVLNVNXaaodIKUhap6uDQq+JXbbHm881A==", + "dev": true, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wp-playground/blueprints": { + "version": "3.1.45", + "resolved": "https://registry.npmjs.org/@wp-playground/blueprints/-/blueprints-3.1.45.tgz", + "integrity": "sha512-q7jEN5w6dgwpzk26mkSGLLPEsTQsRfily0wPolAwueyhq2bnY7Nil7r1k+JHTR2XpTSg3Y3rFyp3j1biOpbsDA==", + "dev": true, + "dependencies": { + "@php-wasm/logger": "3.1.45", + "@php-wasm/progress": "3.1.45", + "@php-wasm/stream-compression": "3.1.45", + "@php-wasm/universal": "3.1.45", + "@php-wasm/util": "3.1.45", + "@php-wasm/web-service-worker": "3.1.45", + "@wp-playground/common": "3.1.45", + "@wp-playground/storage": "3.1.45", + "@wp-playground/wordpress": "3.1.45", + "ajv": "8.18.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@wp-playground/blueprints/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@wp-playground/blueprints/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/@wp-playground/cli": { + "version": "3.1.45", + "resolved": "https://registry.npmjs.org/@wp-playground/cli/-/cli-3.1.45.tgz", + "integrity": "sha512-9cSDXOJoSdZSVrNBOraLlkiBMnvxy0EyaUrkr/8kBkNhWgNv2WhoPxMY25B0eh8nocH2mLVFHg9s4thuFghhug==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/cli-util": "3.1.45", + "@php-wasm/logger": "3.1.45", + "@php-wasm/node": "3.1.45", + "@php-wasm/progress": "3.1.45", + "@php-wasm/universal": "3.1.45", + "@php-wasm/util": "3.1.45", + "@php-wasm/xdebug-bridge": "3.1.45", + "@wp-playground/blueprints": "3.1.45", + "@wp-playground/common": "3.1.45", + "@wp-playground/storage": "3.1.45", + "@wp-playground/tools": "3.1.45", + "@wp-playground/wordpress": "3.1.45", + "express": "4.22.2", + "fs-extra": "11.1.1", + "tmp-promise": "3.0.3", + "wasm-feature-detect": "1.8.0", + "yargs": "17.7.2" + }, + "bin": { + "wp-playground-cli": "wp-playground.js" + } + }, + "node_modules/@wp-playground/common": { + "version": "3.1.45", + "resolved": "https://registry.npmjs.org/@wp-playground/common/-/common-3.1.45.tgz", + "integrity": "sha512-dhVGdPqp4D0RhodW9sxdI3gdKPI98ue0oyXX4AeL4toLvjzKF1nz3AwniojsuHbJr0RV0QhA/Z2rYcTjynLD8A==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/universal": "3.1.45", + "@php-wasm/util": "3.1.45" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/@wp-playground/storage": { + "version": "3.1.45", + "resolved": "https://registry.npmjs.org/@wp-playground/storage/-/storage-3.1.45.tgz", + "integrity": "sha512-c9iWgHHhwEElroXqWBi8B7rSsvAbSYjCDA1m5zTmg+jpscbT8zLrIohJ3WANbWljeSxG4sWA87PLqARdchyl3A==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/stream-compression": "3.1.45", + "@php-wasm/universal": "3.1.45", + "@php-wasm/util": "3.1.45", + "@zip.js/zip.js": "2.7.57", + "isomorphic-git": "1.37.6", + "octokit": "3.1.2", + "pako": "^1.0.10", + "sha.js": "2.4.12" + } + }, + "node_modules/@wp-playground/tools": { + "version": "3.1.45", + "resolved": "https://registry.npmjs.org/@wp-playground/tools/-/tools-3.1.45.tgz", + "integrity": "sha512-nhGuCsVgd0VIe+nQgrKKsVXCu2XHHSnj7+GiH4qIThYUpXLMZo/s+w1bI0ANGOCgpUTOv89Q2JOFPrn37+b3tA==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@wp-playground/blueprints": "3.1.45" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" } }, - "node_modules/@wordpress/warning": { - "version": "3.40.0", - "resolved": "https://registry.npmjs.org/@wordpress/warning/-/warning-3.40.0.tgz", - "integrity": "sha512-0l3OFa1Z+UdhWRRHX9JWWKofo7Lbi2MqOFzzzn0MC26HOyfieQycjLVLNVNXaaodIKUhap6uDQq+JXbbHm881A==", + "node_modules/@wp-playground/wordpress": { + "version": "3.1.45", + "resolved": "https://registry.npmjs.org/@wp-playground/wordpress/-/wordpress-3.1.45.tgz", + "integrity": "sha512-kQqx7m4oKRnSmTxiwdwVnUGetJINHVULMURff2+JZ4EGLqAUVscyCQ4YvQMnIho6gASyzt8TYLr3GqxU+1n6AA==", "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@php-wasm/logger": "3.1.45", + "@php-wasm/universal": "3.1.45", + "@php-wasm/util": "3.1.45", + "@wp-playground/common": "3.1.45", + "zstddec": "^0.2.0" + }, "engines": { - "node": ">=18.12.0", - "npm": ">=8.19.2" + "node": ">=20.10.0", + "npm": ">=10.2.3" } }, "node_modules/@xtuc/ieee754": { @@ -5444,6 +7239,18 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, + "node_modules/@zip.js/zip.js": { + "version": "2.7.57", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.57.tgz", + "integrity": "sha512-BtonQ1/jDnGiMed6OkV6rZYW78gLmLswkHOzyMrMb+CAR7CZO8phOHO6c2qw6qb1g1betN7kwEHhhZk30dv+NA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "bun": ">=0.7.0", + "deno": ">=1.0.0", + "node": ">=16.5.0" + } + }, "node_modules/abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", @@ -5457,6 +7264,19 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "optional": true }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -5555,6 +7375,20 @@ "node": ">= 6.0.0" } }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ajv": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", @@ -5725,6 +7559,19 @@ "node": ">= 8" } }, + "node_modules/anynum": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.1.tgz", + "integrity": "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/aproba": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", @@ -6075,6 +7922,13 @@ "node": ">= 0.4" } }, + "node_modules/async-lock": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz", + "integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==", + "dev": true, + "license": "MIT" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -6442,6 +8296,13 @@ "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", "dev": true }, + "node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -6464,10 +8325,11 @@ } }, "node_modules/body-parser": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", - "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "version": "1.20.6", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz", + "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==", "dev": true, + "license": "MIT", "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", @@ -6477,7 +8339,7 @@ "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", - "qs": "~6.14.0", + "qs": "~6.15.1", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" @@ -6492,6 +8354,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -6501,6 +8364,7 @@ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "dev": true, + "license": "MIT", "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", @@ -6521,6 +8385,7 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -6532,13 +8397,15 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/body-parser/node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -6558,6 +8425,13 @@ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" }, + "node_modules/bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", + "dev": true, + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -6629,6 +8503,13 @@ "node-int64": "^0.4.0" } }, + "node_modules/btoa-lite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz", + "integrity": "sha512-gvW7InbIyF8AicrqWoptdW08pUxuhq8BEgowNajy9RhiE86fmGAGl+bLKo6oB8QP0CkqHLowfN0oJdKC/J6LbA==", + "dev": true, + "license": "MIT" + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -6662,6 +8543,13 @@ "node": "*" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -6699,6 +8587,51 @@ "keyv": "^5.5.0" } }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cacheable/node_modules/keyv": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.5.0.tgz", @@ -6915,6 +8848,13 @@ "node": ">=10" } }, + "node_modules/chardet": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz", + "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==", + "dev": true, + "license": "MIT" + }, "node_modules/check-node-version": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/check-node-version/-/check-node-version-4.2.1.tgz", @@ -7038,6 +8978,59 @@ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" }, + "node_modules/clean-git-ref": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/clean-git-ref/-/clean-git-ref-2.0.1.tgz", + "integrity": "sha512-bLSptAy2P0s6hU4PzuIMKmMJJSE6gLXGH1cntDu7bWJUksvuM+7ReOK61mozULErYvP6a15rnYl0zFDef+pyPw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -7052,6 +9045,16 @@ "node": ">=12" } }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, "node_modules/clone-deep": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", @@ -7080,6 +9083,29 @@ "node": ">=0.10.0" } }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clone-response/node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -7247,6 +9273,62 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "devOptional": true }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/concat-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/configstore": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", @@ -7334,6 +9416,7 @@ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -7359,6 +9442,13 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "dev": true }, + "node_modules/copy-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/copy-dir/-/copy-dir-1.3.0.tgz", + "integrity": "sha512-Q4+qBFnN4bwGwvtXXzbp4P/4iNk0MaiGAzvQ8OiMtlLjkIKjmNN689uVzShSM0908q7GoFHXIPx4zi75ocoaHw==", + "dev": true, + "license": "MIT" + }, "node_modules/copy-webpack-plugin": { "version": "10.2.4", "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-10.2.4.tgz", @@ -8059,6 +10149,29 @@ "node": ">= 10" } }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -8140,6 +10253,13 @@ "node": ">= 0.8" } }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "dev": true, + "license": "ISC" + }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -8194,28 +10314,64 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/diff3": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/diff3/-/diff3-0.0.3.tgz", + "integrity": "sha512-iSq8ngPOt0K53A6eVr4d5Kn6GNrM2nQZtC740pzIriHtn4pOQ2lyzEXQMBeVcWERN0ye7fhBsk9PbLLQOnUx/g==", + "dev": true, + "license": "MIT" + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, "dependencies": { - "path-type": "^4.0.0" + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dev": true, + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/docker-compose": { + "version": "0.24.8", + "resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-0.24.8.tgz", + "integrity": "sha512-plizRs/Vf15H+GCVxq2EUvyPK7ei9b/cVesHvjnX4xaXjM9spHe2Ytq0BitndFgvTJ3E3NljPNUEl7BAN43iZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "yaml": "^2.2.2" }, "engines": { - "node": ">=8" + "node": ">= 6.0.0" } }, - "node_modules/dns-packet": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", - "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "node_modules/docker-compose/node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", "dev": true, - "dependencies": { - "@leichtgewicht/ip-codec": "^2.0.1" + "license": "ISC", + "bin": { + "yaml": "bin.mjs" }, "engines": { - "node": ">=6" + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" } }, "node_modules/doctrine": { @@ -8316,6 +10472,19 @@ "node": ">=8" } }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -8336,6 +10505,23 @@ "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "dev": true }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -9621,6 +11807,16 @@ "node": ">= 0.6" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -9703,14 +11899,15 @@ "dev": true }, "node_modules/express": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", - "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", "dev": true, + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "~1.20.3", + "body-parser": "~1.20.5", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", @@ -9729,7 +11926,7 @@ "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "~6.14.0", + "qs": "~6.15.1", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", @@ -9872,6 +12069,47 @@ } ] }, + "node_modules/fast-xml-builder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.3.0.tgz", + "integrity": "sha512-F74cZEdCvuw9P41GAC3rod4X04jjWGM1JPEv/GWSqFTWLsdyMSBMBMlm9Hk3GLBgLBbdBNY8yee0pQh2RBVESQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.6.2", + "xml-naming": "^0.3.0" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.10.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.10.1.tgz", + "integrity": "sha512-IEMIf7298kXuZSRFoGfMYrl7is8LpavODgbNz1cwIudv7KwVFnuU+UsMporfq6PD6aXSlawZlARiA3UywCTfMw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "@nodable/entities": "^3.0.0", + "fast-xml-builder": "^1.2.0", + "is-unsafe": "^2.0.0", + "path-expression-matcher": "^1.6.2", + "strnum": "^2.4.1", + "xml-naming": "^0.3.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastest-levenshtein": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", @@ -10162,6 +12400,36 @@ "node": ">=0.10.0" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", @@ -10218,6 +12486,45 @@ "node": ">=0.10.0" } }, + "node_modules/fs-ext-extra-prebuilt": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/fs-ext-extra-prebuilt/-/fs-ext-extra-prebuilt-2.2.7.tgz", + "integrity": "sha512-Q7rayYRBDIvDF01HWOwSSjoaP+05N1g+o3BXL1Zf8Frw2JkjSmi4EtvCBITuW30l6hB2m2TW1pehdh8wyU/+gw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "nan": "^2.24.0" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/fs-extra": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", + "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs-extra/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -10646,6 +12953,61 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/got/node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/got/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -10939,6 +13301,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/http-deceiver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", @@ -11028,6 +13397,33 @@ } } }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/http2-wrapper/node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -11597,6 +13993,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -11814,6 +14220,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-unsafe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-2.0.0.tgz", + "integrity": "sha512-2LdV822R+wmI86unXA93WCFpL6g+av8ynWk0nrHyJqGop5VoocYsSLFgN8jrfalT6iGeLNM4KXuVSsULP53kEA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", @@ -11895,8 +14314,141 @@ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", "dev": true, - "engines": { - "node": ">=0.10.0" + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isomorphic-git": { + "version": "1.37.6", + "resolved": "https://registry.npmjs.org/isomorphic-git/-/isomorphic-git-1.37.6.tgz", + "integrity": "sha512-qr1NFCPsVTZ6YGqTXw0CzamnsHyH9QQ1OTEfeXIweSljRUMzuHFCJdUn0wc6OcjtTDns6knxjPb7N6LmJeftOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-lock": "^1.4.1", + "clean-git-ref": "^2.0.1", + "crc-32": "^1.2.0", + "diff3": "0.0.3", + "ignore": "^5.1.4", + "minimisted": "^2.0.0", + "pako": "^1.0.10", + "pify": "^4.0.1", + "readable-stream": "^4.0.0", + "sha.js": "^2.4.12", + "simple-get": "^4.0.1" + }, + "bin": { + "isogit": "cli.cjs" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/isomorphic-git/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/isomorphic-git/node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isomorphic-git/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isomorphic-git/node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/isomorphic-git/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/isomorphic-git/node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" } }, "node_modules/istanbul-lib-coverage": { @@ -11991,6 +14543,22 @@ "node": ">= 0.4" } }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", @@ -12752,6 +15320,65 @@ "integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==", "dev": true }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonfile/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "dev": true, + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -12767,6 +15394,29 @@ "node": ">=4.0" } }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -13223,6 +15873,48 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "dev": true }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -13235,6 +15927,13 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", @@ -13303,6 +16002,16 @@ "tslib": "^2.0.3" } }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -13538,6 +16247,7 @@ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -13833,11 +16543,21 @@ "node": ">=0.10.0" } }, + "node_modules/minimisted": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minimisted/-/minimisted-2.0.1.tgz", + "integrity": "sha512-1oPjfuLQa2caorJUM8HV8lGgWCc0qqAO1MNv/k05G4qslmsndV/5WdNZrqCiyqiz3wohia2Ij2B7w2Dr7/IyrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5" + } + }, "node_modules/minipass": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "optional": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -13941,11 +16661,21 @@ "multicast-dns": "cli.js" } }, + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/nan": { "version": "2.25.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.25.0.tgz", "integrity": "sha512-0M90Ag7Xn5KMLLZ7zliPWP3rT90P6PN+IzVFS0VqmnPktBk3700xUVv8Ikm9EUaUE5SDWdp/BIxdENzVznpm1g==", - "optional": true + "devOptional": true }, "node_modules/nanoid": { "version": "3.3.11", @@ -14141,6 +16871,19 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/npm-bundled": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz", @@ -14405,6 +17148,28 @@ "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", "dev": true }, + "node_modules/octokit": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/octokit/-/octokit-3.1.2.tgz", + "integrity": "sha512-MG5qmrTL5y8KYwFgE1A4JWmgfQBaIETE/lOlfwNYx1QOtCQHGVxkRJmdUJltFc1HVn73d61TlMhMyNTOtMl+ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/app": "^14.0.2", + "@octokit/core": "^5.0.0", + "@octokit/oauth-app": "^6.0.0", + "@octokit/plugin-paginate-graphql": "^4.0.0", + "@octokit/plugin-paginate-rest": "^9.0.0", + "@octokit/plugin-rest-endpoint-methods": "^10.0.0", + "@octokit/plugin-retry": "^6.0.0", + "@octokit/plugin-throttling": "^8.0.0", + "@octokit/request-error": "^5.0.0", + "@octokit/types": "^12.0.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -14493,6 +17258,141 @@ "node": ">= 0.8.0" } }, + "node_modules/ora": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-4.1.1.tgz", + "integrity": "sha512-sjYP8QyVWBpBZWD6Vr1M/KwknSw6kJOz41tvGMlwWeClHBtYKTbHMki1PsLZnxKpXMPbTKv9b3pjQu3REib96A==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^3.0.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.2.0", + "is-interactive": "^1.0.0", + "log-symbols": "^3.0.0", + "mute-stream": "0.0.8", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/ora/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ora/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/ora/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ora/node_modules/log-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/log-symbols/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ora/node_modules/log-symbols/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ora/node_modules/log-symbols/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ora/node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true, + "license": "ISC" + }, "node_modules/os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", @@ -14519,6 +17419,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -14650,6 +17560,20 @@ "node": ">= 14" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true, + "license": "(MIT AND Zlib)" + }, "node_modules/param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", @@ -14764,7 +17688,23 @@ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "engines": { - "node": ">=8" + "node": ">=8" + } + }, + "node_modules/path-expression-matcher": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.2.tgz", + "integrity": "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" } }, "node_modules/path-is-absolute": { @@ -14791,6 +17731,30 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", @@ -14945,13 +17909,13 @@ } }, "node_modules/playwright": { - "version": "1.58.2", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", - "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz", + "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", "dev": true, - "peer": true, + "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.58.2" + "playwright-core": "1.61.1" }, "bin": { "playwright": "cli.js" @@ -14964,11 +17928,11 @@ } }, "node_modules/playwright-core": { - "version": "1.58.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", - "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", + "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", "dev": true, - "peer": true, + "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" }, @@ -14982,11 +17946,11 @@ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" ], - "peer": true, "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } @@ -15804,6 +18768,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -16011,12 +18985,14 @@ ] }, "node_modules/qs": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.1.0" + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" }, "engines": { "node": ">=0.6" @@ -16083,6 +19059,7 @@ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "dev": true, + "license": "MIT", "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", @@ -16098,6 +19075,7 @@ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "dev": true, + "license": "MIT", "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", @@ -16118,6 +19096,7 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -16130,6 +19109,7 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -16501,6 +19481,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, "node_modules/resolve-bin": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/resolve-bin/-/resolve-bin-0.4.3.tgz", @@ -16562,6 +19549,33 @@ "node": ">=10" } }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -17157,6 +20171,27 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "node_modules/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "dev": true, + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/shallow-clone": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", @@ -17227,14 +20262,15 @@ } }, "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "dev": true, + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, @@ -17246,13 +20282,14 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "dev": true, + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -17308,6 +20345,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "devOptional": true, "funding": [ { "type": "github", @@ -17321,8 +20359,7 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "optional": true + ] }, "node_modules/simple-get": { "version": "3.1.1", @@ -17335,6 +20372,24 @@ "simple-concat": "^1.0.0" } }, + "node_modules/simple-git": { + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.36.0.tgz", + "integrity": "sha512-cGQjLjK8bxJw4QuYT7gxHw3/IouVESbhahSsHrX97MzCL1gu2u7oy38W6L2ZIGECEfIBG4BabsWDPjBxJENv9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@kwsites/file-exists": "^1.1.1", + "@kwsites/promise-deferred": "^1.1.1", + "@simple-git/args-pathspec": "^1.0.3", + "@simple-git/argv-parser": "^1.1.0", + "debug": "^4.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/steveukx/git-js?sponsor=1" + } + }, "node_modules/sirv": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", @@ -17730,6 +20785,29 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, "node_modules/string-width/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -17855,6 +20933,20 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -17918,6 +21010,22 @@ "node": ">=0.8.0" } }, + "node_modules/strnum": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.1.tgz", + "integrity": "sha512-M9eUSMT2dCB2cTNPG7UYj6KuK7RJR2SN2+yCV/fTW3xzTCS6EaGZ5pSMgDIjB7r8zSfTGk+dvvn9rTjpVS9Mwg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "anynum": "^1.0.1" + } + }, "node_modules/style-search": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", @@ -18522,6 +21630,37 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "optional": true }, + "node_modules/terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terminal-link/node_modules/supports-hyperlinks": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/terser": { "version": "5.43.1", "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", @@ -18757,12 +21896,47 @@ "tldts-core": "^6.1.86" } }, + "node_modules/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tmp": "^0.2.0" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -18969,6 +22143,7 @@ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "dev": true, + "license": "MIT", "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -19057,6 +22232,13 @@ "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", "dev": true }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "dev": true, + "license": "MIT" + }, "node_modules/typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -19172,6 +22354,24 @@ "node": ">=8" } }, + "node_modules/universal-github-app-jwt": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-1.2.0.tgz", + "integrity": "sha512-dncpMpnsKBk0eetwfN8D8OUHGfiDhhJ+mtsbMl+7PfW7mYjiH8LIcqRmYMtzYLgSh47HjfdBtrBwIQ/gizKR3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/jsonwebtoken": "^9.0.0", + "jsonwebtoken": "^9.0.2" + } + }, + "node_modules/universal-user-agent": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", + "dev": true, + "license": "ISC" + }, "node_modules/universalify": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", @@ -19457,6 +22657,13 @@ "makeerror": "1.0.12" } }, + "node_modules/wasm-feature-detect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.8.0.tgz", + "integrity": "sha512-zksaLKM2fVlnB5jQQDqKXXwYHLQUVH9es+5TOOHwGOVJOCeRBCiPjwSg+3tN2AdTCzjgli4jijCH290kXb/zWQ==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/watchpack": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", @@ -19479,6 +22686,16 @@ "minimalistic-assert": "^1.0.0" } }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, "node_modules/web-vitals": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz", @@ -20056,6 +23273,25 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -20076,10 +23312,11 @@ } }, "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "dev": true, + "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -20114,6 +23351,46 @@ "node": ">=12" } }, + "node_modules/xml-naming": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.3.0.tgz", + "integrity": "sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "dev": true, + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, "node_modules/xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", @@ -20193,6 +23470,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zip-stream": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-5.0.2.tgz", @@ -20215,6 +23505,13 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "node_modules/zstddec": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/zstddec/-/zstddec-0.2.0.tgz", + "integrity": "sha512-oyPnDa1X5c13+Y7mA/FDMNJrn4S8UNBe0KCqtDmor40Re7ALrPN6npFwyYVRRh+PqozZQdeg23QtbcamZnG5rA==", + "dev": true, + "license": "MIT AND BSD-3-Clause" } } } diff --git a/package.json b/package.json index 48ff928..c170c57 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "start": "node scripts/update-build-type.js free && wp-scripts start", "build:premium": "cd pro__premium_only && npm run build", "start:premium": "cd pro__premium_only && npm run start", + "build:e2e": "node scripts/update-build-type.js free && wp-scripts build", "lint:js": "wp-scripts lint-js", "lint:js:fix": "wp-scripts lint-js --fix", "lint:css": "wp-scripts lint-style", @@ -20,17 +21,24 @@ "lint": "npm run lint:js && npm run lint:css", "format": "wp-scripts format", "package": "node scripts/package.js ${npm_config_suffix}", - "sync-version": "node scripts/sync-version.js" + "sync-version": "node scripts/sync-version.js", + "test": "npx playwright test --config e2e/playwright.config.ts", + "test:debug": "npx playwright test --config e2e/playwright.config.ts --ui" }, "browserslist": [ "extends @wordpress/browserslist-config" ], "devDependencies": { + "@playwright/test": "^1.61.1", + "@types/node": "^20.19.43", + "@wordpress/e2e-test-utils-playwright": "^1.51.0", + "@wordpress/env": "^10.39.0", "@wordpress/eslint-plugin": "^22.15.0", "@wordpress/prettier-config": "^4.29.0", "@wordpress/scripts": "^30.0.0", "@wordpress/stylelint-config": "^23.21.0", "archiver": "^6.0.1", + "dotenv": "^16.6.1", "eslint": "^8.57.0", "eslint-plugin-compat": "^6.0.2", "prettier": "^3.3.3" diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..f5b50a8 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "NodeNext", + "baseUrl": ".", + "paths": { + "@wordpress/*": [ "./node_modules/@wordpress/*" ], + "e2e/*": [ "./e2e/*" ] + }, + "types": [ "node" ] + } +} From e2613a2ef6ab21921f526c099d9525271083ada3 Mon Sep 17 00:00:00 2001 From: bfintal Date: Wed, 5 Aug 2026 18:36:17 +0800 Subject: [PATCH 02/16] Add GitHub Actions workflow to run Playwright e2e tests The PR check only built the plugin but never executed the e2e suite added in e2e/. This spins up wp-env, builds the plugin, and runs Playwright against the test WordPress instance on every push/PR to master and develop. Co-authored-by: Cursor --- .github/workflows/e2e-tests.yml | 58 +++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 .github/workflows/e2e-tests.yml diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml new file mode 100644 index 0000000..e0099cf --- /dev/null +++ b/.github/workflows/e2e-tests.yml @@ -0,0 +1,58 @@ +name: E2E Tests + +on: + push: + branches: [ master, develop ] + pull_request: + branches: [ master, develop ] + +jobs: + e2e: + runs-on: ubuntu-latest + + steps: + - name: Check out + uses: actions/checkout@v4 + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: npm + + - name: Install dependencies + run: npm ci --legacy-peer-deps + + - name: Build plugin for e2e testing + run: npm run build:e2e + + - name: Install Playwright browsers + run: npx playwright install --with-deps chromium + + - name: Start WordPress environment + run: npx wp-env start + + - name: Create .env file for Playwright + run: | + cat < .env + WP_BASE_URL=http://localhost:8889 + WP_AUTH_STORAGE=wp-auth.json + WP_USERNAME=admin + WP_PASSWORD=password + CIMO_SLUG=Cimo/cimo + EOF + + - name: Run Playwright e2e tests + run: npm run test + + - name: Upload Playwright report + if: always() + uses: actions/upload-artifact@v4 + with: + name: playwright-report + path: playwright-report/ + retention-days: 14 + + - name: Print WordPress logs on failure + if: failure() + run: npx wp-env logs From 234653ad834184a97ea6c27e74535be0da1e7c9a Mon Sep 17 00:00:00 2001 From: bfintal Date: Wed, 5 Aug 2026 18:47:04 +0800 Subject: [PATCH 03/16] Fix hanging E2E workflow: wp-env logs defaults to watch mode wp-env logs streams and never exits unless --no-watch is passed, which caused the failure-diagnostics step to hang the CI job indefinitely instead of completing. Co-authored-by: Cursor --- .github/workflows/e2e-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index e0099cf..9ffd3f7 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -55,4 +55,4 @@ jobs: - name: Print WordPress logs on failure if: failure() - run: npx wp-env logs + run: npx wp-env logs tests --no-watch From f3fd4ce2963fadee7292caa8851fad7fbbf1ce9c Mon Sep 17 00:00:00 2001 From: bfintal Date: Wed, 5 Aug 2026 23:59:07 +0800 Subject: [PATCH 04/16] Fix 3 failing e2e tests surfaced by newly-wired-up CI - dropFile(): build the File from decoded bytes instead of fetch()-ing a data: URL, since that fetch fails inside the block editor iframe's CSP. - Featured image test: getByLabel(/Settings/i) matched 3 elements (toggle button, sidebar region, close button); target the exact "Settings" toggle button instead. - Media Library modal test: the upload drop zone no longer has the legacy .media-frame-uploader/.uploader-inline/.upload-ui classes visible in this WP version; target the "Upload files" tabpanel by role instead. Co-authored-by: Cursor --- e2e/test-utils/media.ts | 13 ++++++++++--- e2e/tests/upload-interception.spec.ts | 6 ++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/e2e/test-utils/media.ts b/e2e/test-utils/media.ts index 3153a2e..7734f2c 100644 --- a/e2e/test-utils/media.ts +++ b/e2e/test-utils/media.ts @@ -22,11 +22,18 @@ export async function dropFile( const fileName = path.basename( filePath ) const base64 = buffer.toString( 'base64' ) + // Decode the base64 payload into bytes directly (no `fetch()` of a data: + // URL) so this also works inside contexts with a restrictive CSP, such as + // the block editor's iframe. const dataTransfer = await target.evaluateHandle( - async ( { data, name, type } ) => { + ( { data, name, type } ) => { + const byteString = atob( data ) + const bytes = new Uint8Array( byteString.length ) + for ( let i = 0; i < byteString.length; i++ ) { + bytes[ i ] = byteString.charCodeAt( i ) + } + const file = new File( [ bytes ], name, { type } ) const dt = new DataTransfer() - const blob = await fetch( `data:${ type };base64,${ data }` ).then( r => r.blob() ) - const file = new File( [ blob ], name, { type } ) dt.items.add( file ) return dt }, diff --git a/e2e/tests/upload-interception.spec.ts b/e2e/tests/upload-interception.spec.ts index 4996f47..b239c0b 100644 --- a/e2e/tests/upload-interception.spec.ts +++ b/e2e/tests/upload-interception.spec.ts @@ -81,9 +81,7 @@ test.describe( 'Upload interception (JPG → WebP)', () => { await uploadTab.click() } - const dropTarget = modal.locator( - '.media-frame-uploader, .uploader-inline, .upload-ui' - ).first() + const dropTarget = modal.getByRole( 'tabpanel', { name: /Upload files/i } ) await expect( dropTarget ).toBeVisible( { timeout: 15_000 } ) await dropFile( dropTarget ) @@ -145,7 +143,7 @@ test.describe( 'Upload interception (JPG → WebP)', () => { } else if ( await documentTab.count() ) { await documentTab.click() } else { - await page.getByLabel( /Settings/i ).click() + await page.getByRole( 'button', { name: 'Settings', exact: true } ).click() await page.getByRole( 'tab', { name: /Page|Document/i } ).click() } From d9732260eb4597d76127849a94a06e47610c138f Mon Sep 17 00:00:00 2001 From: bfintal Date: Thu, 6 Aug 2026 00:10:56 +0800 Subject: [PATCH 05/16] Fix dropFile(): Locator#evaluateHandle passes (element, arg), not (arg) The payload was always undefined because the callback treated the resolved element as the data argument. Also switched to passing a plain byte array instead of a base64 string/data-URL fetch, avoiding both the iframe CSP fetch failure and any string-serialization edge cases. Co-authored-by: Cursor --- e2e/test-utils/media.ts | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/e2e/test-utils/media.ts b/e2e/test-utils/media.ts index 7734f2c..cc74f15 100644 --- a/e2e/test-utils/media.ts +++ b/e2e/test-utils/media.ts @@ -20,24 +20,22 @@ export async function dropFile( ) { const buffer = fs.readFileSync( filePath ) const fileName = path.basename( filePath ) - const base64 = buffer.toString( 'base64' ) + // A plain array of byte values (rather than a base64 string, which also + // requires `fetch()`/`atob()` that can run afoul of the block editor + // iframe's CSP) serializes reliably as the evaluateHandle arg. + const bytes = Array.from( buffer ) - // Decode the base64 payload into bytes directly (no `fetch()` of a data: - // URL) so this also works inside contexts with a restrictive CSP, such as - // the block editor's iframe. + // Note: Locator#evaluateHandle calls pageFunction as (element, arg), not + // just (arg) — the element itself is unused here, but the parameter is + // required so `arg` correctly receives our payload. const dataTransfer = await target.evaluateHandle( - ( { data, name, type } ) => { - const byteString = atob( data ) - const bytes = new Uint8Array( byteString.length ) - for ( let i = 0; i < byteString.length; i++ ) { - bytes[ i ] = byteString.charCodeAt( i ) - } - const file = new File( [ bytes ], name, { type } ) + ( element, { data, name, type } ) => { + const file = new File( [ new Uint8Array( data ) ], name, { type } ) const dt = new DataTransfer() dt.items.add( file ) return dt }, - { data: base64, name: fileName, type: mimeType } + { data: bytes, name: fileName, type: mimeType } ) await target.dispatchEvent( 'dragenter', { dataTransfer } ) From 6c5c9d24c501fa0a3535756980bd635203542c80 Mon Sep 17 00:00:00 2001 From: bfintal Date: Thu, 6 Aug 2026 00:22:48 +0800 Subject: [PATCH 06/16] Fix remaining 2 e2e failures: modal tab switching and featured image drop target - Media Library modal test: the uploaded attachment only appears as a selectable grid item under the "Media Library" tab, not "Upload files". Switch tabs before selecting, and wait for visibility/enabled state instead of racing with count()/isEnabled() checks. - Featured image test: Cimo's interceptor re-dispatches the converted file onto Gutenberg's actual DropZone element (.components-drop-zone), nested inside .editor-post-featured-image. Dropping on the outer container missed that inner target; drop on the inner zone when present. - Bump the describe-level timeout to 120s since some flows legitimately take over a minute under CI load. Co-authored-by: Cursor --- e2e/tests/upload-interception.spec.ts | 35 +++++++++++++++++++++------ 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/e2e/tests/upload-interception.spec.ts b/e2e/tests/upload-interception.spec.ts index b239c0b..ba71d0e 100644 --- a/e2e/tests/upload-interception.spec.ts +++ b/e2e/tests/upload-interception.spec.ts @@ -8,7 +8,7 @@ import { expectNewMediaIsWebp, } from 'e2e/test-utils' -test.describe.configure( { timeout: 90_000 } ) +test.describe.configure( { timeout: 120_000 } ) test.describe( 'Upload interception (JPG → WebP)', () => { let pageId: string | null = null @@ -87,16 +87,25 @@ test.describe( 'Upload interception (JPG → WebP)', () => { const media = await expectNewMediaIsWebp( requestUtils, afterId ) - // Select the uploaded attachment in the modal if still open. - const webpAttachment = modal.locator( `.attachment[data-id="${ media.id }"]` ) - if ( await modal.isVisible() && await webpAttachment.count() ) { + // Select the uploaded attachment in the modal if still open. The + // upload lands on the "Upload files" tab, but the attachment only + // becomes a selectable grid item under "Media Library" — switch + // there first. + if ( await modal.isVisible() ) { + const libraryTab = modal.getByRole( 'tab', { name: /Media Library/i } ) + if ( await libraryTab.count() ) { + await libraryTab.click() + } + + const webpAttachment = modal.locator( `.attachment[data-id="${ media.id }"]` ) + await expect( webpAttachment ).toBeVisible( { timeout: 15_000 } ) await webpAttachment.click() + const selectButton = modal.getByRole( 'button', { name: /Select|Insert/i, } ) - if ( await selectButton.isEnabled() ) { - await selectButton.click() - } + await expect( selectButton ).toBeEnabled( { timeout: 10_000 } ) + await selectButton.click() } await expect( @@ -149,7 +158,17 @@ test.describe( 'Upload interception (JPG → WebP)', () => { const featured = page.locator( '.editor-post-featured-image' ) await expect( featured ).toBeVisible( { timeout: 15_000 } ) - await dropFile( featured ) + + // Cimo's interceptor re-dispatches the converted file onto Gutenberg's + // actual DropZone element (`.components-drop-zone`), which is nested + // inside `.editor-post-featured-image`. Dropping on the outer + // container directly doesn't reach it, so target the inner drop + // zone when present. + const innerDropZone = featured.locator( + '.components-drop-zone, [data-is-drop-zone="true"]' + ).first() + const dropTarget = ( await innerDropZone.count() ) ? innerDropZone : featured + await dropFile( dropTarget ) await expectNewMediaIsWebp( requestUtils, afterId ) await expect( From 18a16c8545789f1ce46145fa657d03b032e728b2 Mon Sep 17 00:00:00 2001 From: bfintal Date: Thu, 6 Aug 2026 00:32:48 +0800 Subject: [PATCH 07/16] Fix Media Library modal test: dblclick to select+insert in one step A single click plus waiting for the Select button to enable was flaky (button stayed disabled). Double-clicking the attachment selects and inserts it in one step, matching the standard WP media modal UX pattern; falls back to the click + Select button flow if the modal is still open afterwards. Co-authored-by: Cursor --- e2e/tests/upload-interception.spec.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/e2e/tests/upload-interception.spec.ts b/e2e/tests/upload-interception.spec.ts index ba71d0e..2462c1c 100644 --- a/e2e/tests/upload-interception.spec.ts +++ b/e2e/tests/upload-interception.spec.ts @@ -99,13 +99,19 @@ test.describe( 'Upload interception (JPG → WebP)', () => { const webpAttachment = modal.locator( `.attachment[data-id="${ media.id }"]` ) await expect( webpAttachment ).toBeVisible( { timeout: 15_000 } ) - await webpAttachment.click() - const selectButton = modal.getByRole( 'button', { - name: /Select|Insert/i, - } ) - await expect( selectButton ).toBeEnabled( { timeout: 10_000 } ) - await selectButton.click() + // Double-clicking an attachment selects and inserts it in one + // step; fall back to a single click + the Select button if the + // modal is still open afterwards. + await webpAttachment.dblclick() + if ( await modal.isVisible() ) { + await webpAttachment.click() + const selectButton = modal.getByRole( 'button', { + name: /Select|Insert/i, + } ) + await expect( selectButton ).toBeEnabled( { timeout: 10_000 } ) + await selectButton.click() + } } await expect( From 75855aeef10a6292a8e4de66892d6832247ade27 Mon Sep 17 00:00:00 2001 From: bfintal Date: Thu, 6 Aug 2026 00:45:07 +0800 Subject: [PATCH 08/16] DEBUG: add diagnostics for Media Library select-button state (temporary) Co-authored-by: Cursor --- .github/workflows/e2e-tests.yml | 2 +- e2e/tests/upload-interception.spec.ts | 30 +++++++++++++++------------ 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 9ffd3f7..f4acc2f 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -43,7 +43,7 @@ jobs: EOF - name: Run Playwright e2e tests - run: npm run test + run: npm run test -- -g "Media Library drop" - name: Upload Playwright report if: always() diff --git a/e2e/tests/upload-interception.spec.ts b/e2e/tests/upload-interception.spec.ts index 2462c1c..76dec60 100644 --- a/e2e/tests/upload-interception.spec.ts +++ b/e2e/tests/upload-interception.spec.ts @@ -99,19 +99,23 @@ test.describe( 'Upload interception (JPG → WebP)', () => { const webpAttachment = modal.locator( `.attachment[data-id="${ media.id }"]` ) await expect( webpAttachment ).toBeVisible( { timeout: 15_000 } ) - - // Double-clicking an attachment selects and inserts it in one - // step; fall back to a single click + the Select button if the - // modal is still open afterwards. - await webpAttachment.dblclick() - if ( await modal.isVisible() ) { - await webpAttachment.click() - const selectButton = modal.getByRole( 'button', { - name: /Select|Insert/i, - } ) - await expect( selectButton ).toBeEnabled( { timeout: 10_000 } ) - await selectButton.click() - } + await webpAttachment.click() + + // eslint-disable-next-line no-console + console.log( 'DEBUG attachment:', JSON.stringify( + await webpAttachment.evaluate( ( el: HTMLElement ) => ( { + className: el.className, + ariaChecked: el.getAttribute( 'aria-checked' ), + } ) ) + ) ) + // eslint-disable-next-line no-console + console.log( 'DEBUG modal html:', ( await modal.locator( '.media-frame-toolbar' ).innerHTML() ).slice( 0, 2000 ) ) + + const selectButton = modal.getByRole( 'button', { + name: /Select|Insert/i, + } ) + await expect( selectButton ).toBeEnabled( { timeout: 10_000 } ) + await selectButton.click() } await expect( From 2ac83d0e959ce2e7651729fe8f7ee8706a6ce4b8 Mon Sep 17 00:00:00 2001 From: bfintal Date: Thu, 6 Aug 2026 00:51:40 +0800 Subject: [PATCH 09/16] DEBUG: dump attachment innerHTML and try clicking .attachment-preview Co-authored-by: Cursor --- e2e/tests/upload-interception.spec.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/e2e/tests/upload-interception.spec.ts b/e2e/tests/upload-interception.spec.ts index 76dec60..223833e 100644 --- a/e2e/tests/upload-interception.spec.ts +++ b/e2e/tests/upload-interception.spec.ts @@ -99,17 +99,24 @@ test.describe( 'Upload interception (JPG → WebP)', () => { const webpAttachment = modal.locator( `.attachment[data-id="${ media.id }"]` ) await expect( webpAttachment ).toBeVisible( { timeout: 15_000 } ) - await webpAttachment.click() // eslint-disable-next-line no-console - console.log( 'DEBUG attachment:', JSON.stringify( + console.log( 'DEBUG attachment html:', ( await webpAttachment.innerHTML() ).slice( 0, 2000 ) ) + + const clickTarget = webpAttachment.locator( '.attachment-preview' ).first() + if ( await clickTarget.count() ) { + await clickTarget.click() + } else { + await webpAttachment.click() + } + + // eslint-disable-next-line no-console + console.log( 'DEBUG attachment after click:', JSON.stringify( await webpAttachment.evaluate( ( el: HTMLElement ) => ( { className: el.className, ariaChecked: el.getAttribute( 'aria-checked' ), } ) ) ) ) - // eslint-disable-next-line no-console - console.log( 'DEBUG modal html:', ( await modal.locator( '.media-frame-toolbar' ).innerHTML() ).slice( 0, 2000 ) ) const selectButton = modal.getByRole( 'button', { name: /Select|Insert/i, From c8c177ca5c13749cefa516e6c5d4ac02b85b58c9 Mon Sep 17 00:00:00 2001 From: bfintal Date: Thu, 6 Aug 2026 00:57:39 +0800 Subject: [PATCH 10/16] Fix Media Library modal test: wait for attachment content before clicking Diagnostics showed the grid item's
  • renders (and passes visibility checks) before its thumbnail/content is filled in; clicking that early landed on an inert placeholder that never toggled Backbone's selection state, leaving the Select button permanently disabled. Waiting for .attachment-preview to render first fixes it. Also removes the temporary debug logging and restores the full e2e test run in CI. Co-authored-by: Cursor --- .github/workflows/e2e-tests.yml | 2 +- e2e/tests/upload-interception.spec.ts | 24 ++++++------------------ 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index f4acc2f..9ffd3f7 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -43,7 +43,7 @@ jobs: EOF - name: Run Playwright e2e tests - run: npm run test -- -g "Media Library drop" + run: npm run test - name: Upload Playwright report if: always() diff --git a/e2e/tests/upload-interception.spec.ts b/e2e/tests/upload-interception.spec.ts index 223833e..6d951b4 100644 --- a/e2e/tests/upload-interception.spec.ts +++ b/e2e/tests/upload-interception.spec.ts @@ -99,24 +99,12 @@ test.describe( 'Upload interception (JPG → WebP)', () => { const webpAttachment = modal.locator( `.attachment[data-id="${ media.id }"]` ) await expect( webpAttachment ).toBeVisible( { timeout: 15_000 } ) - - // eslint-disable-next-line no-console - console.log( 'DEBUG attachment html:', ( await webpAttachment.innerHTML() ).slice( 0, 2000 ) ) - - const clickTarget = webpAttachment.locator( '.attachment-preview' ).first() - if ( await clickTarget.count() ) { - await clickTarget.click() - } else { - await webpAttachment.click() - } - - // eslint-disable-next-line no-console - console.log( 'DEBUG attachment after click:', JSON.stringify( - await webpAttachment.evaluate( ( el: HTMLElement ) => ( { - className: el.className, - ariaChecked: el.getAttribute( 'aria-checked' ), - } ) ) - ) ) + // The
  • itself renders (and passes the visibility check) before + // its content (thumbnail, etc.) is filled in; clicking too early + // lands on an inert placeholder that doesn't toggle selection. + // Wait for its actual content to be there first. + await expect( webpAttachment.locator( '.attachment-preview' ) ).toBeVisible( { timeout: 15_000 } ) + await webpAttachment.click() const selectButton = modal.getByRole( 'button', { name: /Select|Insert/i, From 03dbaabccfa1ae4348d6c21de75e0a35454d79f7 Mon Sep 17 00:00:00 2001 From: bfintal Date: Thu, 6 Aug 2026 01:06:12 +0800 Subject: [PATCH 11/16] DEBUG: poll attachment/button state over time Co-authored-by: Cursor --- .github/workflows/e2e-tests.yml | 2 +- e2e/tests/upload-interception.spec.ts | 20 ++++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 9ffd3f7..f4acc2f 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -43,7 +43,7 @@ jobs: EOF - name: Run Playwright e2e tests - run: npm run test + run: npm run test -- -g "Media Library drop" - name: Upload Playwright report if: always() diff --git a/e2e/tests/upload-interception.spec.ts b/e2e/tests/upload-interception.spec.ts index 6d951b4..25dbdf4 100644 --- a/e2e/tests/upload-interception.spec.ts +++ b/e2e/tests/upload-interception.spec.ts @@ -99,16 +99,28 @@ test.describe( 'Upload interception (JPG → WebP)', () => { const webpAttachment = modal.locator( `.attachment[data-id="${ media.id }"]` ) await expect( webpAttachment ).toBeVisible( { timeout: 15_000 } ) - // The
  • itself renders (and passes the visibility check) before - // its content (thumbnail, etc.) is filled in; clicking too early - // lands on an inert placeholder that doesn't toggle selection. - // Wait for its actual content to be there first. await expect( webpAttachment.locator( '.attachment-preview' ) ).toBeVisible( { timeout: 15_000 } ) await webpAttachment.click() const selectButton = modal.getByRole( 'button', { name: /Select|Insert/i, } ) + + for ( let i = 0; i < 6; i++ ) { + const state = await webpAttachment.evaluate( ( el: HTMLElement ) => ( { + className: el.className, + ariaChecked: el.getAttribute( 'aria-checked' ), + } ) ) + const btnDisabled = await selectButton.isDisabled() + // eslint-disable-next-line no-console + console.log( `DEBUG t+${ i * 1000 }ms:`, JSON.stringify( state ), 'btnDisabled=', btnDisabled ) + if ( ! btnDisabled ) { + break + } + // eslint-disable-next-line no-await-in-loop + await page.waitForTimeout( 1000 ) + } + await expect( selectButton ).toBeEnabled( { timeout: 10_000 } ) await selectButton.click() } From 6f6d5f6ebddc76745656322f806a4d510c654ee5 Mon Sep 17 00:00:00 2001 From: bfintal Date: Thu, 6 Aug 2026 01:12:54 +0800 Subject: [PATCH 12/16] DEBUG: try .check click and Enter keypress fallback Co-authored-by: Cursor --- e2e/tests/upload-interception.spec.ts | 30 +++++++++++++++------------ 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/e2e/tests/upload-interception.spec.ts b/e2e/tests/upload-interception.spec.ts index 25dbdf4..bff5b59 100644 --- a/e2e/tests/upload-interception.spec.ts +++ b/e2e/tests/upload-interception.spec.ts @@ -100,25 +100,29 @@ test.describe( 'Upload interception (JPG → WebP)', () => { const webpAttachment = modal.locator( `.attachment[data-id="${ media.id }"]` ) await expect( webpAttachment ).toBeVisible( { timeout: 15_000 } ) await expect( webpAttachment.locator( '.attachment-preview' ) ).toBeVisible( { timeout: 15_000 } ) - await webpAttachment.click() + + // eslint-disable-next-line no-console + console.log( 'DEBUG attachment html:', await webpAttachment.innerHTML() ) + + const checkTarget = webpAttachment.locator( '.check' ) + if ( await checkTarget.count() ) { + await checkTarget.click() + } else { + await webpAttachment.click() + } const selectButton = modal.getByRole( 'button', { name: /Select|Insert/i, } ) + const disabledAfterClick = await selectButton.isDisabled() + // eslint-disable-next-line no-console + console.log( 'DEBUG after check-click, btnDisabled=', disabledAfterClick ) - for ( let i = 0; i < 6; i++ ) { - const state = await webpAttachment.evaluate( ( el: HTMLElement ) => ( { - className: el.className, - ariaChecked: el.getAttribute( 'aria-checked' ), - } ) ) - const btnDisabled = await selectButton.isDisabled() + if ( disabledAfterClick ) { + await webpAttachment.focus() + await page.keyboard.press( 'Enter' ) // eslint-disable-next-line no-console - console.log( `DEBUG t+${ i * 1000 }ms:`, JSON.stringify( state ), 'btnDisabled=', btnDisabled ) - if ( ! btnDisabled ) { - break - } - // eslint-disable-next-line no-await-in-loop - await page.waitForTimeout( 1000 ) + console.log( 'DEBUG after Enter keypress, btnDisabled=', await selectButton.isDisabled() ) } await expect( selectButton ).toBeEnabled( { timeout: 10_000 } ) From e59c585937cf33727f7def43a05cd940bd76afcb Mon Sep 17 00:00:00 2001 From: bfintal Date: Thu, 6 Aug 2026 01:18:57 +0800 Subject: [PATCH 13/16] Fix Media Library modal test: click the .check checkbox, scope Select button locator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This grid's Attachment view only toggles selection via its .check checkbox button — clicking elsewhere on the item doesn't register. Separately, the loose /Select|Insert/i search wasn't scoped to the toolbar, so it ambiguously matched the attachment's own checkbox once its accessible name toggled to "Deselect". Scoping to .media-toolbar fixes that. Also restores the full e2e test run in CI. Co-authored-by: Cursor --- .github/workflows/e2e-tests.yml | 2 +- e2e/tests/upload-interception.spec.ts | 29 ++++++++++----------------- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index f4acc2f..9ffd3f7 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -43,7 +43,7 @@ jobs: EOF - name: Run Playwright e2e tests - run: npm run test -- -g "Media Library drop" + run: npm run test - name: Upload Playwright report if: always() diff --git a/e2e/tests/upload-interception.spec.ts b/e2e/tests/upload-interception.spec.ts index bff5b59..41625a7 100644 --- a/e2e/tests/upload-interception.spec.ts +++ b/e2e/tests/upload-interception.spec.ts @@ -101,30 +101,23 @@ test.describe( 'Upload interception (JPG → WebP)', () => { await expect( webpAttachment ).toBeVisible( { timeout: 15_000 } ) await expect( webpAttachment.locator( '.attachment-preview' ) ).toBeVisible( { timeout: 15_000 } ) - // eslint-disable-next-line no-console - console.log( 'DEBUG attachment html:', await webpAttachment.innerHTML() ) - - const checkTarget = webpAttachment.locator( '.check' ) - if ( await checkTarget.count() ) { - await checkTarget.click() + // This library's Attachment view only toggles selection via its + // `.check` checkbox button — clicking elsewhere on the grid item + // doesn't register. Fall back to a plain click if `.check` isn't + // rendered (e.g. a different WP media view). + const checkButton = webpAttachment.locator( '.check' ) + if ( await checkButton.count() ) { + await checkButton.click() } else { await webpAttachment.click() } - const selectButton = modal.getByRole( 'button', { + // Scope to the toolbar: the attachment's own `.check` button's + // accessible name toggles to "Deselect", which also matches a + // loose /Select|Insert/i search if not scoped away from it. + const selectButton = modal.locator( '.media-toolbar' ).getByRole( 'button', { name: /Select|Insert/i, } ) - const disabledAfterClick = await selectButton.isDisabled() - // eslint-disable-next-line no-console - console.log( 'DEBUG after check-click, btnDisabled=', disabledAfterClick ) - - if ( disabledAfterClick ) { - await webpAttachment.focus() - await page.keyboard.press( 'Enter' ) - // eslint-disable-next-line no-console - console.log( 'DEBUG after Enter keypress, btnDisabled=', await selectButton.isDisabled() ) - } - await expect( selectButton ).toBeEnabled( { timeout: 10_000 } ) await selectButton.click() } From d0e4f5bfb4660b211791e07274b47ba67398869b Mon Sep 17 00:00:00 2001 From: bfintal Date: Thu, 6 Aug 2026 01:39:27 +0800 Subject: [PATCH 14/16] Fix flaky Media Library and featured-image e2e steps Playwright clicks never update wp.media's Backbone selection, so Select stays disabled after a modal drop. Select the uploaded attachment via the media frame API instead. Also skip already-active sidebar tabs so the featured-image test does not hang waiting for a stable click. Co-authored-by: Cursor --- e2e/tests/upload-interception.spec.ts | 91 +++++++++++++++------------ 1 file changed, 51 insertions(+), 40 deletions(-) diff --git a/e2e/tests/upload-interception.spec.ts b/e2e/tests/upload-interception.spec.ts index 41625a7..e85c7f9 100644 --- a/e2e/tests/upload-interception.spec.ts +++ b/e2e/tests/upload-interception.spec.ts @@ -87,36 +87,30 @@ test.describe( 'Upload interception (JPG → WebP)', () => { const media = await expectNewMediaIsWebp( requestUtils, afterId ) - // Select the uploaded attachment in the modal if still open. The - // upload lands on the "Upload files" tab, but the attachment only - // becomes a selectable grid item under "Media Library" — switch - // there first. + // Insert the uploaded attachment into the image block. Playwright + // clicks on `.attachment` / `.check` do not reliably update + // wp.media's Backbone selection (Select stays disabled), so drive + // selection through the media frame API instead. if ( await modal.isVisible() ) { - const libraryTab = modal.getByRole( 'tab', { name: /Media Library/i } ) - if ( await libraryTab.count() ) { - await libraryTab.click() - } - - const webpAttachment = modal.locator( `.attachment[data-id="${ media.id }"]` ) - await expect( webpAttachment ).toBeVisible( { timeout: 15_000 } ) - await expect( webpAttachment.locator( '.attachment-preview' ) ).toBeVisible( { timeout: 15_000 } ) - - // This library's Attachment view only toggles selection via its - // `.check` checkbox button — clicking elsewhere on the grid item - // doesn't register. Fall back to a plain click if `.check` isn't - // rendered (e.g. a different WP media view). - const checkButton = webpAttachment.locator( '.check' ) - if ( await checkButton.count() ) { - await checkButton.click() - } else { - await webpAttachment.click() - } + await page.evaluate( async ( id ) => { + // wp.media is always present while the media modal is open. + const wpMedia = ( window as any ).wp?.media + if ( ! wpMedia?.frame ) { + throw new Error( 'wp.media.frame is not available' ) + } + + const attachment = wpMedia.attachment( id ) + await new Promise( ( resolve, reject ) => { + const result = attachment.fetch() + result.done( resolve ) + result.fail( reject ) + } ) + wpMedia.frame.state().get( 'selection' ).reset( [ attachment ] ) + }, media.id ) - // Scope to the toolbar: the attachment's own `.check` button's - // accessible name toggles to "Deselect", which also matches a - // loose /Select|Insert/i search if not scoped away from it. const selectButton = modal.locator( '.media-toolbar' ).getByRole( 'button', { - name: /Select|Insert/i, + name: 'Select', + exact: true, } ) await expect( selectButton ).toBeEnabled( { timeout: 10_000 } ) await selectButton.click() @@ -156,21 +150,38 @@ test.describe( 'Upload interception (JPG → WebP)', () => { const afterId = await requestUtils.getMaxMediaId() // Open the Page / Document sidebar (featured image lives there). - const pageTab = page.getByRole( 'button', { name: 'Page', exact: true } ) - const documentTab = page.getByRole( 'button', { - name: 'Document', - exact: true, - } ) - if ( await pageTab.count() ) { - await pageTab.click() - } else if ( await documentTab.count() ) { - await documentTab.click() - } else { - await page.getByRole( 'button', { name: 'Settings', exact: true } ).click() - await page.getByRole( 'tab', { name: /Page|Document/i } ).click() + // Skip clicking tabs that are already selected — Playwright can hang + // waiting for a stable click on an already-active tab. + const featured = page.locator( '.editor-post-featured-image' ) + if ( ! await featured.isVisible() ) { + const settingsButton = page.getByRole( 'button', { + name: 'Settings', + exact: true, + } ) + if ( await settingsButton.count() ) { + const pressed = await settingsButton.getAttribute( 'aria-pressed' ) + if ( pressed !== 'true' ) { + await settingsButton.click() + } + } + + const pageTab = page.getByRole( 'button', { name: 'Page', exact: true } ) + const documentTab = page.getByRole( 'button', { + name: 'Document', + exact: true, + } ) + const sidebarTab = ( await pageTab.count() ) + ? pageTab + : ( await documentTab.count() ) + ? documentTab + : page.getByRole( 'tab', { name: /Page|Document/i } ) + + const selected = await sidebarTab.getAttribute( 'aria-selected' ) + if ( selected !== 'true' ) { + await sidebarTab.click() + } } - const featured = page.locator( '.editor-post-featured-image' ) await expect( featured ).toBeVisible( { timeout: 15_000 } ) // Cimo's interceptor re-dispatches the converted file onto Gutenberg's From a6ac31831f4229937ec98cb483d1add28776941f Mon Sep 17 00:00:00 2001 From: bfintal Date: Tue, 11 Aug 2026 13:45:28 +0800 Subject: [PATCH 15/16] added cursor rules and skills --- .cursor/rules/cimo-anti-slop.mdc | 34 + .cursor/rules/cimo-javascript-react.mdc | 32 + .cursor/rules/cimo-project-repos.mdc | 9 + .cursor/rules/cimo-wordpress-plugin-check.mdc | 26 + .cursor/skills/code-review/SKILL.md | 87 + .cursor/skills/code-review/agents/openai.yaml | 3 + .cursor/skills/codebase-design/DEEPENING.md | 37 + .../skills/codebase-design/DESIGN-IT-TWICE.md | 44 + .cursor/skills/codebase-design/SKILL.md | 114 + .../skills/codebase-design/agents/openai.yaml | 3 + .cursor/skills/diagnosing-bugs/SKILL.md | 140 + .../skills/diagnosing-bugs/agents/openai.yaml | 3 + .../scripts/hitl-loop.template.sh | 44 + .cursor/skills/domain-modeling/ADR-FORMAT.md | 47 + .../skills/domain-modeling/CONTEXT-FORMAT.md | 60 + .cursor/skills/domain-modeling/SKILL.md | 74 + .../skills/domain-modeling/agents/openai.yaml | 3 + .../explain-codebase-concept/HTML-REPORT.md | 124 + .../skills/explain-codebase-concept/SKILL.md | 90 + .../agents/openai.yaml | 5 + .cursor/skills/grill-me/SKILL.md | 7 + .cursor/skills/grill-me/agents/openai.yaml | 5 + .cursor/skills/grill-with-docs/SKILL.md | 7 + .../skills/grill-with-docs/agents/openai.yaml | 5 + .cursor/skills/grilling/SKILL.md | 22 + .cursor/skills/grilling/agents/openai.yaml | 3 + .cursor/skills/implement/SKILL.md | 23 + .cursor/skills/implement/agents/openai.yaml | 5 + .../HTML-REPORT.md | 123 + .../improve-codebase-architecture/SKILL.md | 71 + .../agents/openai.yaml | 5 + .../skills/setup-matt-pocock-skills/SKILL.md | 116 + .../agents/openai.yaml | 5 + .../skills/setup-matt-pocock-skills/domain.md | 51 + .../issue-tracker-github.md | 45 + .../issue-tracker-gitlab.md | 46 + .../issue-tracker-local.md | 30 + .../setup-matt-pocock-skills/triage-labels.md | 15 + .cursor/skills/tdd/SKILL.md | 36 + .cursor/skills/tdd/agents/openai.yaml | 3 + .cursor/skills/tdd/mocking.md | 59 + .cursor/skills/tdd/tests.md | 77 + .cursor/skills/to-spec/SKILL.md | 75 + .cursor/skills/to-spec/agents/openai.yaml | 5 + .cursor/skills/to-tickets/SKILL.md | 105 + .cursor/skills/to-tickets/agents/openai.yaml | 5 + .../vercel-composition-patterns/AGENTS.md | 946 ++++ .../vercel-composition-patterns/README.md | 60 + .../vercel-composition-patterns/SKILL.md | 89 + .../vercel-composition-patterns/metadata.json | 11 + .../rules/_sections.md | 29 + .../rules/_template.md | 24 + .../rules/architecture-avoid-boolean-props.md | 100 + .../rules/architecture-compound-components.md | 112 + .../patterns-children-over-render-props.md | 87 + .../rules/patterns-explicit-variants.md | 100 + .../rules/react19-no-forwardref.md | 42 + .../rules/state-context-interface.md | 191 + .../rules/state-decouple-implementation.md | 113 + .../rules/state-lift-state.md | 125 + .../vercel-react-best-practices/AGENTS.md | 3810 +++++++++++++++++ .../vercel-react-best-practices/README.md | 123 + .../vercel-react-best-practices/SKILL.md | 158 + .../vercel-react-best-practices/metadata.json | 15 + .../rules/_sections.md | 46 + .../rules/_template.md | 28 + .../rules/advanced-effect-event-deps.md | 56 + .../rules/advanced-event-handler-refs.md | 55 + .../rules/advanced-init-once.md | 42 + .../rules/advanced-use-latest.md | 39 + .../rules/async-api-routes.md | 38 + .../async-cheap-condition-before-await.md | 37 + .../rules/async-defer-await.md | 82 + .../rules/async-dependencies.md | 51 + .../rules/async-parallel.md | 28 + .../rules/async-suspense-boundaries.md | 99 + .../rules/bundle-analyzable-paths.md | 63 + .../rules/bundle-barrel-imports.md | 60 + .../rules/bundle-conditional.md | 31 + .../rules/bundle-defer-third-party.md | 49 + .../rules/bundle-dynamic-imports.md | 35 + .../rules/bundle-preload.md | 50 + .../rules/client-event-listeners.md | 74 + .../rules/client-localstorage-schema.md | 71 + .../rules/client-passive-event-listeners.md | 48 + .../rules/client-swr-dedup.md | 56 + .../rules/js-batch-dom-css.md | 107 + .../rules/js-cache-function-results.md | 80 + .../rules/js-cache-property-access.md | 28 + .../rules/js-cache-storage.md | 70 + .../rules/js-combine-iterations.md | 32 + .../rules/js-early-exit.md | 50 + .../rules/js-flatmap-filter.md | 60 + .../rules/js-hoist-regexp.md | 45 + .../rules/js-index-maps.md | 37 + .../rules/js-length-check-first.md | 49 + .../rules/js-min-max-loop.md | 82 + .../rules/js-request-idle-callback.md | 105 + .../rules/js-set-map-lookups.md | 24 + .../rules/js-tosorted-immutable.md | 57 + .../rules/rendering-activity.md | 26 + .../rules/rendering-animate-svg-wrapper.md | 47 + .../rules/rendering-conditional-render.md | 40 + .../rules/rendering-content-visibility.md | 38 + .../rules/rendering-hoist-jsx.md | 46 + .../rules/rendering-hydration-no-flicker.md | 82 + .../rendering-hydration-suppress-warning.md | 30 + .../rules/rendering-resource-hints.md | 85 + .../rules/rendering-script-defer-async.md | 68 + .../rules/rendering-svg-precision.md | 28 + .../rules/rendering-usetransition-loading.md | 75 + .../rules/rerender-defer-reads.md | 39 + .../rules/rerender-dependencies.md | 45 + .../rules/rerender-derived-state-no-effect.md | 40 + .../rules/rerender-derived-state.md | 29 + .../rules/rerender-functional-setstate.md | 74 + .../rules/rerender-lazy-state-init.md | 58 + .../rules/rerender-memo-with-default-value.md | 38 + .../rules/rerender-memo.md | 44 + .../rules/rerender-move-effect-to-event.md | 45 + .../rules/rerender-no-inline-components.md | 82 + .../rerender-simple-expression-in-memo.md | 35 + .../rules/rerender-split-combined-hooks.md | 64 + .../rules/rerender-transitions.md | 40 + .../rules/rerender-use-deferred-value.md | 59 + .../rerender-use-ref-transient-values.md | 73 + .../rules/server-after-nonblocking.md | 73 + .../rules/server-auth-actions.md | 96 + .../rules/server-cache-lru.md | 41 + .../rules/server-cache-react.md | 76 + .../rules/server-dedup-props.md | 65 + .../rules/server-hoist-static-io.md | 149 + .../rules/server-no-shared-module-state.md | 50 + .../rules/server-parallel-fetching.md | 83 + .../rules/server-parallel-nested-fetching.md | 34 + .../rules/server-serialization.md | 38 + .cursor/skills/web-design-guidelines/SKILL.md | 39 + .cursor/skills/wordpress-router/SKILL.md | 51 + .../references/decision-tree.md | 61 + .cursor/skills/wp-abilities-api/SKILL.md | 107 + .../references/delegate-helper-pattern.md | 241 ++ .../references/domain-vs-projection.md | 113 + .../references/error-code-vocabulary.md | 123 + .../references/grouping-heuristic.md | 89 + .../references/input-schema-gotchas.md | 265 ++ .../references/php-registration.md | 94 + .../references/plugin-family-patterns.md | 233 + .../wp-abilities-api/references/rest-api.md | 13 + .../references/shared-core-service.md | 184 + .cursor/skills/wp-abilities-audit/SKILL.md | 198 + .../references/audit-schema.md | 300 ++ .../references/capability-gate-tracing.md | 197 + .../references/controller-enumeration.md | 116 + .cursor/skills/wp-abilities-verify/SKILL.md | 214 + .../references/annotation-correctness.md | 154 + .../references/audit-schema-validation.md | 131 + .../references/permission-roundtrip.md | 190 + .../references/runtime-harness.md | 462 ++ .../references/schema-lints.md | 118 + .../references/static-enumeration.md | 126 + .cursor/skills/wp-block-development/SKILL.md | 174 + .../attributes-and-serialization.md | 22 + .../references/block-json.md | 49 + .../references/creating-new-blocks.md | 46 + .../references/debugging.md | 36 + .../references/deprecations.md | 24 + .../references/dynamic-rendering.md | 23 + .../references/inner-blocks.md | 25 + .../references/registration.md | 30 + .../references/supports-and-wrappers.md | 18 + .../references/tooling-and-testing.md | 21 + .../scripts/list_blocks.mjs | 121 + .cursor/skills/wp-block-themes/SKILL.md | 116 + .../references/creating-new-block-theme.md | 37 + .../wp-block-themes/references/debugging.md | 25 + .../wp-block-themes/references/patterns.md | 18 + .../references/style-variations.md | 14 + .../references/templates-and-parts.md | 16 + .../wp-block-themes/references/theme-json.md | 73 + .../scripts/detect_block_themes.mjs | 117 + .cursor/skills/wp-patterns/SKILL.md | 308 ++ .../wp-patterns/references/anti-patterns.md | 151 + .../references/block-markup-reference.md | 220 + .../references/design-with-tokens.md | 152 + .../pattern-categories-and-types.md | 130 + .../references/pattern-registration.md | 169 + .cursor/skills/wp-performance/SKILL.md | 146 + .../references/autoload-options.md | 24 + .../skills/wp-performance/references/cron.md | 20 + .../wp-performance/references/database.md | 20 + .../wp-performance/references/http-api.md | 15 + .../wp-performance/references/measurement.md | 21 + .../wp-performance/references/object-cache.md | 24 + .../references/query-monitor-headless.md | 38 + .../references/server-timing.md | 22 + .../references/wp-cli-doctor.md | 24 + .../references/wp-cli-profile.md | 32 + .../wp-performance/scripts/perf_inspect.mjs | 128 + .cursor/skills/wp-plugin-development/SKILL.md | 112 + .../references/data-and-cron.md | 19 + .../references/debugging.md | 19 + .../references/lifecycle.md | 33 + .../references/security.md | 29 + .../references/settings-api.md | 22 + .../references/structure.md | 16 + .../scripts/detect_plugins.mjs | 122 + .../wp-plugin-directory-guidelines/SKILL.md | 132 + .../references/gpl-compliance.md | 217 + .../references/guideline-review-checklist.md | 592 +++ .../references/naming-rules.md | 121 + .cursor/skills/wp-project-triage/SKILL.md | 38 + .../references/triage.schema.json | 143 + .../scripts/detect_wp_project.mjs | 592 +++ .cursor/skills/wp-rest-api/SKILL.md | 114 + .../wp-rest-api/references/authentication.md | 18 + .../references/custom-content-types.md | 20 + .../references/discovery-and-params.md | 20 + .../references/responses-and-fields.md | 30 + .../references/routes-and-endpoints.md | 36 + .../skills/wp-rest-api/references/schema.md | 22 + .../writing-for-agents/SKILL-MECHANICS.md | 22 + .cursor/skills/writing-for-agents/SKILL.md | 81 + .../writing-for-agents/agents/openai.yaml | 3 + AGENTS.md | 64 + 224 files changed, 21167 insertions(+) create mode 100644 .cursor/rules/cimo-anti-slop.mdc create mode 100644 .cursor/rules/cimo-javascript-react.mdc create mode 100644 .cursor/rules/cimo-wordpress-plugin-check.mdc create mode 100644 .cursor/skills/code-review/SKILL.md create mode 100644 .cursor/skills/code-review/agents/openai.yaml create mode 100644 .cursor/skills/codebase-design/DEEPENING.md create mode 100644 .cursor/skills/codebase-design/DESIGN-IT-TWICE.md create mode 100644 .cursor/skills/codebase-design/SKILL.md create mode 100644 .cursor/skills/codebase-design/agents/openai.yaml create mode 100644 .cursor/skills/diagnosing-bugs/SKILL.md create mode 100644 .cursor/skills/diagnosing-bugs/agents/openai.yaml create mode 100644 .cursor/skills/diagnosing-bugs/scripts/hitl-loop.template.sh create mode 100644 .cursor/skills/domain-modeling/ADR-FORMAT.md create mode 100644 .cursor/skills/domain-modeling/CONTEXT-FORMAT.md create mode 100644 .cursor/skills/domain-modeling/SKILL.md create mode 100644 .cursor/skills/domain-modeling/agents/openai.yaml create mode 100644 .cursor/skills/explain-codebase-concept/HTML-REPORT.md create mode 100644 .cursor/skills/explain-codebase-concept/SKILL.md create mode 100644 .cursor/skills/explain-codebase-concept/agents/openai.yaml create mode 100644 .cursor/skills/grill-me/SKILL.md create mode 100644 .cursor/skills/grill-me/agents/openai.yaml create mode 100644 .cursor/skills/grill-with-docs/SKILL.md create mode 100644 .cursor/skills/grill-with-docs/agents/openai.yaml create mode 100644 .cursor/skills/grilling/SKILL.md create mode 100644 .cursor/skills/grilling/agents/openai.yaml create mode 100644 .cursor/skills/implement/SKILL.md create mode 100644 .cursor/skills/implement/agents/openai.yaml create mode 100644 .cursor/skills/improve-codebase-architecture/HTML-REPORT.md create mode 100644 .cursor/skills/improve-codebase-architecture/SKILL.md create mode 100644 .cursor/skills/improve-codebase-architecture/agents/openai.yaml create mode 100644 .cursor/skills/setup-matt-pocock-skills/SKILL.md create mode 100644 .cursor/skills/setup-matt-pocock-skills/agents/openai.yaml create mode 100644 .cursor/skills/setup-matt-pocock-skills/domain.md create mode 100644 .cursor/skills/setup-matt-pocock-skills/issue-tracker-github.md create mode 100644 .cursor/skills/setup-matt-pocock-skills/issue-tracker-gitlab.md create mode 100644 .cursor/skills/setup-matt-pocock-skills/issue-tracker-local.md create mode 100644 .cursor/skills/setup-matt-pocock-skills/triage-labels.md create mode 100644 .cursor/skills/tdd/SKILL.md create mode 100644 .cursor/skills/tdd/agents/openai.yaml create mode 100644 .cursor/skills/tdd/mocking.md create mode 100644 .cursor/skills/tdd/tests.md create mode 100644 .cursor/skills/to-spec/SKILL.md create mode 100644 .cursor/skills/to-spec/agents/openai.yaml create mode 100644 .cursor/skills/to-tickets/SKILL.md create mode 100644 .cursor/skills/to-tickets/agents/openai.yaml create mode 100644 .cursor/skills/vercel-composition-patterns/AGENTS.md create mode 100644 .cursor/skills/vercel-composition-patterns/README.md create mode 100644 .cursor/skills/vercel-composition-patterns/SKILL.md create mode 100644 .cursor/skills/vercel-composition-patterns/metadata.json create mode 100644 .cursor/skills/vercel-composition-patterns/rules/_sections.md create mode 100644 .cursor/skills/vercel-composition-patterns/rules/_template.md create mode 100644 .cursor/skills/vercel-composition-patterns/rules/architecture-avoid-boolean-props.md create mode 100644 .cursor/skills/vercel-composition-patterns/rules/architecture-compound-components.md create mode 100644 .cursor/skills/vercel-composition-patterns/rules/patterns-children-over-render-props.md create mode 100644 .cursor/skills/vercel-composition-patterns/rules/patterns-explicit-variants.md create mode 100644 .cursor/skills/vercel-composition-patterns/rules/react19-no-forwardref.md create mode 100644 .cursor/skills/vercel-composition-patterns/rules/state-context-interface.md create mode 100644 .cursor/skills/vercel-composition-patterns/rules/state-decouple-implementation.md create mode 100644 .cursor/skills/vercel-composition-patterns/rules/state-lift-state.md create mode 100644 .cursor/skills/vercel-react-best-practices/AGENTS.md create mode 100644 .cursor/skills/vercel-react-best-practices/README.md create mode 100644 .cursor/skills/vercel-react-best-practices/SKILL.md create mode 100644 .cursor/skills/vercel-react-best-practices/metadata.json create mode 100644 .cursor/skills/vercel-react-best-practices/rules/_sections.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/_template.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/advanced-effect-event-deps.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/advanced-init-once.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/advanced-use-latest.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/async-api-routes.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/async-cheap-condition-before-await.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/async-defer-await.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/async-dependencies.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/async-parallel.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/bundle-analyzable-paths.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/bundle-conditional.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/bundle-preload.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/client-event-listeners.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/client-localstorage-schema.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/client-swr-dedup.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/js-batch-dom-css.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/js-cache-function-results.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/js-cache-property-access.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/js-cache-storage.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/js-combine-iterations.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/js-early-exit.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/js-flatmap-filter.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/js-hoist-regexp.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/js-index-maps.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/js-length-check-first.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/js-min-max-loop.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/js-request-idle-callback.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/js-set-map-lookups.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/rendering-activity.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/rendering-conditional-render.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/rendering-content-visibility.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/rendering-resource-hints.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/rendering-script-defer-async.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/rendering-svg-precision.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/rerender-defer-reads.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/rerender-dependencies.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/rerender-derived-state.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/rerender-memo.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/rerender-no-inline-components.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/rerender-split-combined-hooks.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/rerender-transitions.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/rerender-use-deferred-value.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/server-after-nonblocking.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/server-auth-actions.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/server-cache-lru.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/server-cache-react.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/server-dedup-props.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/server-hoist-static-io.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/server-no-shared-module-state.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/server-parallel-fetching.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/server-parallel-nested-fetching.md create mode 100644 .cursor/skills/vercel-react-best-practices/rules/server-serialization.md create mode 100644 .cursor/skills/web-design-guidelines/SKILL.md create mode 100644 .cursor/skills/wordpress-router/SKILL.md create mode 100644 .cursor/skills/wordpress-router/references/decision-tree.md create mode 100644 .cursor/skills/wp-abilities-api/SKILL.md create mode 100644 .cursor/skills/wp-abilities-api/references/delegate-helper-pattern.md create mode 100644 .cursor/skills/wp-abilities-api/references/domain-vs-projection.md create mode 100644 .cursor/skills/wp-abilities-api/references/error-code-vocabulary.md create mode 100644 .cursor/skills/wp-abilities-api/references/grouping-heuristic.md create mode 100644 .cursor/skills/wp-abilities-api/references/input-schema-gotchas.md create mode 100644 .cursor/skills/wp-abilities-api/references/php-registration.md create mode 100644 .cursor/skills/wp-abilities-api/references/plugin-family-patterns.md create mode 100644 .cursor/skills/wp-abilities-api/references/rest-api.md create mode 100644 .cursor/skills/wp-abilities-api/references/shared-core-service.md create mode 100644 .cursor/skills/wp-abilities-audit/SKILL.md create mode 100644 .cursor/skills/wp-abilities-audit/references/audit-schema.md create mode 100644 .cursor/skills/wp-abilities-audit/references/capability-gate-tracing.md create mode 100644 .cursor/skills/wp-abilities-audit/references/controller-enumeration.md create mode 100644 .cursor/skills/wp-abilities-verify/SKILL.md create mode 100644 .cursor/skills/wp-abilities-verify/references/annotation-correctness.md create mode 100644 .cursor/skills/wp-abilities-verify/references/audit-schema-validation.md create mode 100644 .cursor/skills/wp-abilities-verify/references/permission-roundtrip.md create mode 100644 .cursor/skills/wp-abilities-verify/references/runtime-harness.md create mode 100644 .cursor/skills/wp-abilities-verify/references/schema-lints.md create mode 100644 .cursor/skills/wp-abilities-verify/references/static-enumeration.md create mode 100644 .cursor/skills/wp-block-development/SKILL.md create mode 100644 .cursor/skills/wp-block-development/references/attributes-and-serialization.md create mode 100644 .cursor/skills/wp-block-development/references/block-json.md create mode 100644 .cursor/skills/wp-block-development/references/creating-new-blocks.md create mode 100644 .cursor/skills/wp-block-development/references/debugging.md create mode 100644 .cursor/skills/wp-block-development/references/deprecations.md create mode 100644 .cursor/skills/wp-block-development/references/dynamic-rendering.md create mode 100644 .cursor/skills/wp-block-development/references/inner-blocks.md create mode 100644 .cursor/skills/wp-block-development/references/registration.md create mode 100644 .cursor/skills/wp-block-development/references/supports-and-wrappers.md create mode 100644 .cursor/skills/wp-block-development/references/tooling-and-testing.md create mode 100644 .cursor/skills/wp-block-development/scripts/list_blocks.mjs create mode 100644 .cursor/skills/wp-block-themes/SKILL.md create mode 100644 .cursor/skills/wp-block-themes/references/creating-new-block-theme.md create mode 100644 .cursor/skills/wp-block-themes/references/debugging.md create mode 100644 .cursor/skills/wp-block-themes/references/patterns.md create mode 100644 .cursor/skills/wp-block-themes/references/style-variations.md create mode 100644 .cursor/skills/wp-block-themes/references/templates-and-parts.md create mode 100644 .cursor/skills/wp-block-themes/references/theme-json.md create mode 100644 .cursor/skills/wp-block-themes/scripts/detect_block_themes.mjs create mode 100644 .cursor/skills/wp-patterns/SKILL.md create mode 100644 .cursor/skills/wp-patterns/references/anti-patterns.md create mode 100644 .cursor/skills/wp-patterns/references/block-markup-reference.md create mode 100644 .cursor/skills/wp-patterns/references/design-with-tokens.md create mode 100644 .cursor/skills/wp-patterns/references/pattern-categories-and-types.md create mode 100644 .cursor/skills/wp-patterns/references/pattern-registration.md create mode 100644 .cursor/skills/wp-performance/SKILL.md create mode 100644 .cursor/skills/wp-performance/references/autoload-options.md create mode 100644 .cursor/skills/wp-performance/references/cron.md create mode 100644 .cursor/skills/wp-performance/references/database.md create mode 100644 .cursor/skills/wp-performance/references/http-api.md create mode 100644 .cursor/skills/wp-performance/references/measurement.md create mode 100644 .cursor/skills/wp-performance/references/object-cache.md create mode 100644 .cursor/skills/wp-performance/references/query-monitor-headless.md create mode 100644 .cursor/skills/wp-performance/references/server-timing.md create mode 100644 .cursor/skills/wp-performance/references/wp-cli-doctor.md create mode 100644 .cursor/skills/wp-performance/references/wp-cli-profile.md create mode 100644 .cursor/skills/wp-performance/scripts/perf_inspect.mjs create mode 100644 .cursor/skills/wp-plugin-development/SKILL.md create mode 100644 .cursor/skills/wp-plugin-development/references/data-and-cron.md create mode 100644 .cursor/skills/wp-plugin-development/references/debugging.md create mode 100644 .cursor/skills/wp-plugin-development/references/lifecycle.md create mode 100644 .cursor/skills/wp-plugin-development/references/security.md create mode 100644 .cursor/skills/wp-plugin-development/references/settings-api.md create mode 100644 .cursor/skills/wp-plugin-development/references/structure.md create mode 100644 .cursor/skills/wp-plugin-development/scripts/detect_plugins.mjs create mode 100644 .cursor/skills/wp-plugin-directory-guidelines/SKILL.md create mode 100644 .cursor/skills/wp-plugin-directory-guidelines/references/gpl-compliance.md create mode 100644 .cursor/skills/wp-plugin-directory-guidelines/references/guideline-review-checklist.md create mode 100644 .cursor/skills/wp-plugin-directory-guidelines/references/naming-rules.md create mode 100644 .cursor/skills/wp-project-triage/SKILL.md create mode 100644 .cursor/skills/wp-project-triage/references/triage.schema.json create mode 100644 .cursor/skills/wp-project-triage/scripts/detect_wp_project.mjs create mode 100644 .cursor/skills/wp-rest-api/SKILL.md create mode 100644 .cursor/skills/wp-rest-api/references/authentication.md create mode 100644 .cursor/skills/wp-rest-api/references/custom-content-types.md create mode 100644 .cursor/skills/wp-rest-api/references/discovery-and-params.md create mode 100644 .cursor/skills/wp-rest-api/references/responses-and-fields.md create mode 100644 .cursor/skills/wp-rest-api/references/routes-and-endpoints.md create mode 100644 .cursor/skills/wp-rest-api/references/schema.md create mode 100644 .cursor/skills/writing-for-agents/SKILL-MECHANICS.md create mode 100644 .cursor/skills/writing-for-agents/SKILL.md create mode 100644 .cursor/skills/writing-for-agents/agents/openai.yaml create mode 100644 AGENTS.md diff --git a/.cursor/rules/cimo-anti-slop.mdc b/.cursor/rules/cimo-anti-slop.mdc new file mode 100644 index 0000000..15fa44e --- /dev/null +++ b/.cursor/rules/cimo-anti-slop.mdc @@ -0,0 +1,34 @@ +--- +description: Maintainability and anti-slop standards when changing Cimo code +alwaysApply: true +--- + +# Maintainability & anti-slop + +Prefer deepening existing seams over inventing parallel structure. Goal: one place to change a behaviour; freemium gates and shared heuristics stay single-sourced. + +## Before you paste + +When adding or extending behaviour, check the nearest sibling first. If the change would clone a block (converter wiring, media-manager upload hooks, settings field + REST save pairs, upsell/pricing URL builders, `CIMO_BUILD` gates), extract or reuse a shared helper **before** adding the nth copy. + +## Deepen, don’t layer + +Keep the runtime spine: + +- **Upload path:** media-manager hooks → shared converters → metadata saver +- **Settings path:** React settings UI → REST/options → localized `window.cimoAdmin` / script loader +- **Freemium:** free tree + `CIMO_BUILD` gates; premium only under `pro__premium_only/` + +New behaviour goes into an existing module cluster (converter, media-manager surface, settings section, shared util, premium module) when one fits. Do not add a second converter registry, parallel upload interceptor, or ad-hoc AJAX twin beside an existing REST route without an explicit extract. + +God-file rule of thumb: prefer extracting a coherent cluster over growing `admin-settings.js`, a media-manager entry, or a fat PHP admin class with another pasted case. + +## Single source for shared heuristics + +Heuristics that exist on both PHP and JS (settings defaults, “is premium” flags, pricing/upsell URLs, optimization labels) share one definition of truth — localized data, shared module, or a documented sync. Do not hand-sync a second map “for convenience.” + +Ship or silence: do not advertise a premium feature, format, or setting in free UI/copy unless the free build truly exposes it (or clearly marks it as premium upsell). + +## Completion check + +Before finishing a change that adds a converter, setting, upload hook, or freemium gate: every list/map/UI/REST surface that must know about it is updated, no phantom options remain, and no new duplicate helper was introduced beside an existing one. Keep premium logic out of the free tree. diff --git a/.cursor/rules/cimo-javascript-react.mdc b/.cursor/rules/cimo-javascript-react.mdc new file mode 100644 index 0000000..e062ef6 --- /dev/null +++ b/.cursor/rules/cimo-javascript-react.mdc @@ -0,0 +1,32 @@ +--- +description: Cimo uses plain JavaScript; React for settings, vanilla JS for media upload +alwaysApply: true +--- + +# JavaScript & React + +Cimo uses **regular JavaScript** (`.js`), not TypeScript, for plugin UI and runtime code. Do not add `.ts` / `.tsx` source files outside e2e tests (`e2e/**/*.ts` is fine). + +## Two JS surfaces + +1. **Media upload interception** (primary product runtime) — mostly **vanilla JS** under `src/admin/js/media-manager/` and shared converters under `src/shared/converters/`. This hooks Media Manager / editor / page-builder uploads and runs client-side optimize/convert before save. +2. **Settings UI** — **React** via `@wordpress/element` under `src/admin/js/page/` (and related bulk-optimizer UI). Mount with `createRoot` into `#cimo-admin-settings`. + +Prefer putting interactive upload/optimize logic in shared JS modules. PHP should expose settings, enqueue assets, REST/AJAX, and capability gates — not reimplement conversion in PHP when the product path is client-side. + +## React (settings / admin UI) + +- Build admin UI with **React** via `@wordpress/element` (WordPress’s React wrapper). +- Use JSX inside `.js` files (wp-scripts handles transpilation). +- Prefer WordPress packages (`@wordpress/element`, `@wordpress/components`, `@wordpress/i18n`, `@wordpress/api-fetch`, `@wordpress/hooks`, etc.) over adding a separate `react` / `react-dom` dependency. +- Text domain: `cimo-image-optimizer`. +- Freemium UI extensions use `applyFilters( 'cimo.…' )` so premium can swap/inject components without putting premium logic in the free tree. + +## Import alias + +- Use the `~cimo` alias (maps to `src/`) for cross-folder imports, e.g. `~cimo/shared/pricing-url`. + +## Media & converters + +- New optimize/convert formats deepen `src/shared/converters/` (extend `converter-abstract` / registry in `index.js`) rather than forking one-off paths in each media-manager file. +- Keep upload UX (drop zone, select files, progress, sidebar info) in `src/admin/js/media-manager/`; keep pure conversion/util code in `src/shared/`. diff --git a/.cursor/rules/cimo-project-repos.mdc b/.cursor/rules/cimo-project-repos.mdc index e56ca41..e36264d 100644 --- a/.cursor/rules/cimo-project-repos.mdc +++ b/.cursor/rules/cimo-project-repos.mdc @@ -17,6 +17,8 @@ This project has two GitHub repositories, used depending on whether we're buildi ### Free version - Main plugin repo: https://github.com/gambitph/Cimo +- Contains **only** free plugin code and must remain free of premium logic (WordPress.org guidelines). +- Must pass [WordPress Plugin Check](https://github.com/wordpress/plugin-check) for Plugin Directory inclusion (see `cimo-wordpress-plugin-check` rule). ### Premium version @@ -24,3 +26,10 @@ This project has two GitHub repositories, used depending on whether we're buildi - Premium-only repo: https://github.com/bfintal/cimo-premium - The premium repo contains **only** the premium plugin code. - It is placed in the `pro__premium_only` directory inside the free plugin's root folder. + +## Free / Premium boundaries + +- Do not put premium feature logic in the free repo. +- Premium code lives exclusively under `pro__premium_only/`. +- The free plugin may gate-load premium via `CIMO_BUILD === 'premium'` and `pro__premium_only/index.php`. +- When packaging the free build, `pro__premium_only` must not be included. diff --git a/.cursor/rules/cimo-wordpress-plugin-check.mdc b/.cursor/rules/cimo-wordpress-plugin-check.mdc new file mode 100644 index 0000000..570fcf1 --- /dev/null +++ b/.cursor/rules/cimo-wordpress-plugin-check.mdc @@ -0,0 +1,26 @@ +--- +description: Free plugin must pass WordPress Plugin Check for directory submission +alwaysApply: true +--- + +# WordPress Plugin Directory & Plugin Check + +The **free** Cimo plugin is intended for the [WordPress Plugin Directory](https://wordpress.org/plugins/). Directory inclusion requires meeting WordPress.org plugin guidelines. + +## Official checker + +Use **[WordPress/plugin-check](https://github.com/wordpress/plugin-check)** (Plugin Check / PCP) as the compliance bar: + +- Static + runtime checks for directory requirements and best practices +- WP Admin: **Tools → Plugin Check** +- WP-CLI: `wp plugin check ` (add `--require=…/plugin-check/cli.php` for runtime checks) +- Also: [wordpress.org/plugins/plugin-check](https://wordpress.org/plugins/plugin-check/) + +## What this means for Cimo + +- Free-repo code and the free build zip should be written to **pass Plugin Check**. +- Prefer WordPress coding / security / i18n / enqueue practices that PCP enforces (escaping, nonces, capability checks, no forbidden APIs, proper headers, etc.). +- Do not ship premium-only code, `pro__premium_only/`, or directory-disallowed patterns in the free package. +- When changing free-plugin PHP, assets, or packaging, keep Plugin Check green in mind — fix regressions rather than silencing them without cause. + +Premium code under `pro__premium_only/` is out of scope for Directory submission, but anything that lands in the **free** tree or free zip is in scope. diff --git a/.cursor/skills/code-review/SKILL.md b/.cursor/skills/code-review/SKILL.md new file mode 100644 index 0000000..2d276fe --- /dev/null +++ b/.cursor/skills/code-review/SKILL.md @@ -0,0 +1,87 @@ +--- +name: code-review +description: Review the changes since a fixed point (commit, branch, tag, or merge-base) along two axes — Standards (does the code follow this repo's documented coding standards?) and Spec (does the code match what the originating issue/spec asked for?). Runs both reviews in parallel sub-agents and reports them side by side. Use when the user wants to review a branch, a PR, work-in-progress changes, or asks to "review since X". +--- + +Two-axis review of the diff between `HEAD` and a fixed point the user supplies: + +- **Standards** — does the code conform to this repo's documented coding standards? +- **Spec** — does the code faithfully implement the originating issue / spec? + +Both axes run as **parallel sub-agents** so they don't pollute each other's context, then this skill aggregates their findings. + +The issue tracker should have been provided to you — run `/setup-matt-pocock-skills` if `docs/agents/issue-tracker.md` is missing. + +## Process + +### 1. Pin the fixed point + +Whatever the user said is the fixed point — a commit SHA, branch name, tag, `main`, `HEAD~5`, etc. If they didn't specify one, ask for it. + +Capture the diff command once: `git diff ...HEAD` (three-dot, so the comparison is against the merge-base). Also note the list of commits via `git log ..HEAD --oneline`. + +Before going further, confirm the fixed point resolves (`git rev-parse `) and the diff is non-empty. A bad ref or empty diff should fail here — not inside two parallel sub-agents. + +### 2. Identify the spec source + +Look for the originating spec, in this order: + +1. Issue references in the commit messages (`#123`, `Closes #45`, GitLab `!67`, etc.) — fetch via the workflow in `docs/agents/issue-tracker.md`. +2. A path the user passed as an argument. +3. A spec file under `docs/`, `specs/`, or `.scratch/` matching the branch name or feature. +4. If nothing is found, ask the user where the spec is. If they say there isn't one, the **Spec** sub-agent will skip and report "no spec available". + +### 3. Identify the standards sources + +Anything in the repo that documents how code should be written, such as `CODING_STANDARDS.md` or `CONTRIBUTING.md`. + +On top of whatever the repo documents, the Standards axis always carries the **smell baseline** below — a fixed set of Fowler code smells (_Refactoring_, ch.3) that applies even when a repo documents nothing. Two rules bind it: + +- **The repo overrides.** A documented repo standard always wins; where it endorses something the baseline would flag, suppress the smell. +- **Always a judgement call.** Each smell is a labelled heuristic ("possible Feature Envy"), never a hard violation — and, like any standard here, skip anything tooling already enforces. + +Each smell reads *what it is* → *how to fix*; match it against the diff: + +- **Mysterious Name** — a function, variable, or type whose name doesn't reveal what it does or holds. → rename it; if no honest name comes, the design's murky. +- **Duplicated Code** — the same logic shape appears in more than one hunk or file in the change. → extract the shared shape, call it from both. +- **Feature Envy** — a method that reaches into another object's data more than its own. → move the method onto the data it envies. +- **Data Clumps** — the same few fields or params keep travelling together (a type wanting to be born). → bundle them into one type, pass that. +- **Primitive Obsession** — a primitive or string standing in for a domain concept that deserves its own type. → give the concept its own small type. +- **Repeated Switches** — the same `switch`/`if`-cascade on the same type recurs across the change. → replace with polymorphism, or one map both sites share. +- **Shotgun Surgery** — one logical change forces scattered edits across many files in the diff. → gather what changes together into one module. +- **Divergent Change** — one file or module is edited for several unrelated reasons. → split so each module changes for one reason. +- **Speculative Generality** — abstraction, parameters, or hooks added for needs the spec doesn't have. → delete it; inline back until a real need shows. +- **Message Chains** — long `a.b().c().d()` navigation the caller shouldn't depend on. → hide the walk behind one method on the first object. +- **Middle Man** — a class or function that mostly just delegates onward. → cut it, call the real target direct. +- **Refused Bequest** — a subclass or implementer that ignores or overrides most of what it inherits. → drop the inheritance, use composition. + +### 4. Spawn both sub-agents in parallel + +**Standards sub-agent prompt** — include: + +- The full diff command and commit list. +- The list of standards-source files you found in step 3, **plus the smell baseline from step 3** pasted in full — the sub-agent has no other access to it. +- The brief: "Report — per file/hunk where relevant — (a) every place the diff violates a documented standard: cite the standard (file + the rule); and (b) any baseline smell you spot: name it and quote the hunk. Distinguish hard violations from judgement calls — documented-standard breaches can be hard, but baseline smells are always judgement calls, and a documented repo standard overrides the baseline. Skip anything tooling enforces. Under 400 words." + +**Spec sub-agent prompt** — include: + +- The diff command and commit list. +- The path or fetched contents of the spec. +- The brief: "Report: (a) requirements the spec asked for that are missing or partial; (b) behaviour in the diff that wasn't asked for (scope creep); (c) requirements that look implemented but where the implementation looks wrong. Quote the spec line for each finding. Under 400 words." + +If the spec is missing, skip the Spec sub-agent and note this in the final report. + +### 5. Aggregate + +Present the two reports under `## Standards` and `## Spec` headings, verbatim or lightly cleaned. Do **not** merge or rerank findings — the two axes are deliberately separate (see _Why two axes_). + +End with a one-line summary: total findings per axis, and the worst issue _within each axis_ (if any). Don't pick a single winner across axes — that's the reranking the separation exists to prevent. + +## Why two axes + +A change can pass one axis and fail the other: + +- Code that follows every standard but implements the wrong thing → **Standards pass, Spec fail.** +- Code that does exactly what the issue asked but breaks the project's conventions → **Spec pass, Standards fail.** + +Reporting them separately stops one axis from masking the other. diff --git a/.cursor/skills/code-review/agents/openai.yaml b/.cursor/skills/code-review/agents/openai.yaml new file mode 100644 index 0000000..9076774 --- /dev/null +++ b/.cursor/skills/code-review/agents/openai.yaml @@ -0,0 +1,3 @@ +interface: + display_name: "Code Review" + short_description: "Review a diff on standards and spec" diff --git a/.cursor/skills/codebase-design/DEEPENING.md b/.cursor/skills/codebase-design/DEEPENING.md new file mode 100644 index 0000000..3938457 --- /dev/null +++ b/.cursor/skills/codebase-design/DEEPENING.md @@ -0,0 +1,37 @@ +# Deepening + +How to deepen a cluster of shallow modules safely, given its dependencies. Assumes the vocabulary in [SKILL.md](SKILL.md) — **module**, **interface**, **seam**, **adapter**. + +## Dependency categories + +When assessing a candidate for deepening, classify its dependencies. The category determines how the deepened module is tested across its seam. + +### 1. In-process + +Pure computation, in-memory state, no I/O. Always deepenable — merge the modules and test through the new interface directly. No adapter needed. + +### 2. Local-substitutable + +Dependencies that have local test stand-ins (PGLite for Postgres, in-memory filesystem). Deepenable if the stand-in exists. The deepened module is tested with the stand-in running in the test suite. The seam is internal; no port at the module's external interface. + +### 3. Remote but owned (Ports & Adapters) + +Your own services across a network boundary (microservices, internal APIs). Define a **port** (interface) at the seam. The deep module owns the logic; the transport is injected as an **adapter**. Tests use an in-memory adapter. Production uses an HTTP/gRPC/queue adapter. + +Recommendation shape: *"Define a port at the seam, implement an HTTP adapter for production and an in-memory adapter for testing, so the logic sits in one deep module even though it's deployed across a network."* + +### 4. True external (Mock) + +Third-party services (Stripe, Twilio, etc.) you don't control. The deepened module takes the external dependency as an injected port; tests provide a mock adapter. + +## Seam discipline + +- **One adapter means a hypothetical seam. Two adapters means a real one.** Don't introduce a port unless at least two adapters are justified (typically production + test). A single-adapter seam is just indirection. +- **Internal seams vs external seams.** A deep module can have internal seams (private to its implementation, used by its own tests) as well as the external seam at its interface. Don't expose internal seams through the interface just because tests use them. + +## Testing strategy: replace, don't layer + +- Old unit tests on shallow modules become waste once tests at the deepened module's interface exist — delete them. +- Write new tests at the deepened module's interface. The **interface is the test surface**. +- Tests assert on observable outcomes through the interface, not internal state. +- Tests should survive internal refactors — they describe behaviour, not implementation. If a test has to change when the implementation changes, it's testing past the interface. diff --git a/.cursor/skills/codebase-design/DESIGN-IT-TWICE.md b/.cursor/skills/codebase-design/DESIGN-IT-TWICE.md new file mode 100644 index 0000000..8419ad6 --- /dev/null +++ b/.cursor/skills/codebase-design/DESIGN-IT-TWICE.md @@ -0,0 +1,44 @@ +# Design It Twice + +When the user wants to explore alternative interfaces for a chosen deepening candidate, use this parallel sub-agent pattern. Based on "Design It Twice" (Ousterhout) — your first idea is unlikely to be the best. + +Uses the vocabulary in [SKILL.md](SKILL.md) — **module**, **interface**, **seam**, **adapter**, **leverage**. + +## Process + +### 1. Frame the problem space + +Before spawning sub-agents, write a user-facing explanation of the problem space for the chosen candidate: + +- The constraints any new interface would need to satisfy +- The dependencies it would rely on, and which category they fall into (see [DEEPENING.md](DEEPENING.md)) +- A rough illustrative code sketch to ground the constraints — not a proposal, just a way to make the constraints concrete + +Show this to the user, then immediately proceed to Step 2. The user reads and thinks while the sub-agents work in parallel. + +### 2. Spawn sub-agents + +Spawn 3+ sub-agents in parallel. Each must produce a **radically different** interface for the deepened module. + +Prompt each sub-agent with a separate technical brief (file paths, coupling details, dependency category from [DEEPENING.md](DEEPENING.md), what sits behind the seam). The brief is independent of the user-facing problem-space explanation in Step 1. Give each agent a different design constraint: + +- Agent 1: "Minimize the interface — aim for 1–3 entry points max. Maximise leverage per entry point." +- Agent 2: "Maximise flexibility — support many use cases and extension." +- Agent 3: "Optimise for the most common caller — make the default case trivial." +- Agent 4 (if applicable): "Design around ports & adapters for cross-seam dependencies." + +Include both [SKILL.md](SKILL.md) vocabulary and CONTEXT.md vocabulary in the brief so each sub-agent names things consistently with the architecture language and the project's domain language. + +Each sub-agent outputs: + +1. Interface (types, methods, params — plus invariants, ordering, error modes) +2. Usage example showing how callers use it +3. What the implementation hides behind the seam +4. Dependency strategy and adapters (see [DEEPENING.md](DEEPENING.md)) +5. Trade-offs — where leverage is high, where it's thin + +### 3. Present and compare + +Present designs sequentially so the user can absorb each one, then compare them in prose. Contrast by **depth** (leverage at the interface), **locality** (where change concentrates), and **seam placement**. + +After comparing, give your own recommendation: which design you think is strongest and why. If elements from different designs would combine well, propose a hybrid. Be opinionated — the user wants a strong read, not a menu. diff --git a/.cursor/skills/codebase-design/SKILL.md b/.cursor/skills/codebase-design/SKILL.md new file mode 100644 index 0000000..16620c2 --- /dev/null +++ b/.cursor/skills/codebase-design/SKILL.md @@ -0,0 +1,114 @@ +--- +name: codebase-design +description: Shared vocabulary for designing deep modules. Use when the user wants to design or improve a module's interface, find deepening opportunities, decide where a seam goes, make code more testable or AI-navigable, or when another skill needs the deep-module vocabulary. +--- + +# Codebase Design + +Design **deep modules**: a lot of behaviour behind a small interface, placed at a clean seam, testable through that interface. Use this language and these principles wherever code is being designed or restructured. The aim is leverage for callers, locality for maintainers, and testability for everyone. + +## Glossary + +Use these terms exactly — don't substitute "component," "service," "API," or "boundary." Consistent language is the whole point. + +**Module** — anything with an interface and an implementation. Deliberately scale-agnostic: a function, class, package, or tier-spanning slice. _Avoid_: unit, component, service. + +**Interface** — everything a caller must know to use the module correctly: the type signature, but also invariants, ordering constraints, error modes, required configuration, and performance characteristics. _Avoid_: API, signature (too narrow — they refer only to the type-level surface). + +**Implementation** — what's inside a module, its body of code. Distinct from **Adapter**: a thing can be a small adapter with a large implementation (a Postgres repo) or a large adapter with a small implementation (an in-memory fake). Reach for "adapter" when the seam is the topic; "implementation" otherwise. + +**Depth** — leverage at the interface: the amount of behaviour a caller (or test) can exercise per unit of interface they have to learn. A module is **deep** when a large amount of behaviour sits behind a small interface, **shallow** when the interface is nearly as complex as the implementation. + +**Seam** _(Michael Feathers)_ — a place where you can alter behaviour without editing in that place; the *location* at which a module's interface lives. Where to put the seam is its own design decision, distinct from what goes behind it. _Avoid_: boundary (overloaded with DDD's bounded context). + +**Adapter** — a concrete thing that satisfies an interface at a seam. Describes *role* (what slot it fills), not substance (what's inside). + +**Leverage** — what callers get from depth: more capability per unit of interface they learn. One implementation pays back across N call sites and M tests. + +**Locality** — what maintainers get from depth: change, bugs, knowledge, and verification concentrate in one place rather than spreading across callers. Fix once, fixed everywhere. + +## Deep vs shallow + +**Deep module** = small interface + lots of implementation: + +``` +┌─────────────────────┐ +│ Small Interface │ ← Few methods, simple params +├─────────────────────┤ +│ │ +│ Deep Implementation│ ← Complex logic hidden +│ │ +└─────────────────────┘ +``` + +**Shallow module** = large interface + little implementation (avoid): + +``` +┌─────────────────────────────────┐ +│ Large Interface │ ← Many methods, complex params +├─────────────────────────────────┤ +│ Thin Implementation │ ← Just passes through +└─────────────────────────────────┘ +``` + +When designing an interface, ask: + +- Can I reduce the number of methods? +- Can I simplify the parameters? +- Can I hide more complexity inside? + +## Principles + +- **Depth is a property of the interface, not the implementation.** A deep module can be internally composed of small, mockable, swappable parts — they just aren't part of the interface. A module can have **internal seams** (private to its implementation, used by its own tests) as well as the **external seam** at its interface. +- **The deletion test.** Imagine deleting the module. If complexity vanishes, it was a pass-through. If complexity reappears across N callers, it was earning its keep. +- **The interface is the test surface.** Callers and tests cross the same seam. If you want to test *past* the interface, the module is probably the wrong shape. +- **One adapter means a hypothetical seam. Two adapters means a real one.** Don't introduce a seam unless something actually varies across it. + +## Designing for testability + +Good interfaces make testing natural: + +1. **Accept dependencies, don't create them.** + + ```typescript + // Testable + function processOrder(order, paymentGateway) {} + + // Hard to test + function processOrder(order) { + const gateway = new StripeGateway(); + } + ``` + +2. **Return results, don't produce side effects.** + + ```typescript + // Testable + function calculateDiscount(cart): Discount {} + + // Hard to test + function applyDiscount(cart): void { + cart.total -= discount; + } + ``` + +3. **Small surface area.** Fewer methods = fewer tests needed. Fewer params = simpler test setup. + +## Relationships + +- A **Module** has exactly one **Interface** (the surface it presents to callers and tests). +- **Depth** is a property of a **Module**, measured against its **Interface**. +- A **Seam** is where a **Module**'s **Interface** lives. +- An **Adapter** sits at a **Seam** and satisfies the **Interface**. +- **Depth** produces **Leverage** for callers and **Locality** for maintainers. + +## Rejected framings + +- **Depth as ratio of implementation-lines to interface-lines** (Ousterhout): rewards padding the implementation. We use depth-as-leverage instead. +- **"Interface" as the TypeScript `interface` keyword or a class's public methods**: too narrow — interface here includes every fact a caller must know. +- **"Boundary"**: overloaded with DDD's bounded context. Say **seam** or **interface**. + +## Going deeper + +- **Deepening a cluster given its dependencies** — see [DEEPENING.md](DEEPENING.md): dependency categories, seam discipline, and replace-don't-layer testing. +- **Exploring alternative interfaces** — see [DESIGN-IT-TWICE.md](DESIGN-IT-TWICE.md): spin up parallel sub-agents to design the interface several radically different ways, then compare on depth, locality, and seam placement. diff --git a/.cursor/skills/codebase-design/agents/openai.yaml b/.cursor/skills/codebase-design/agents/openai.yaml new file mode 100644 index 0000000..3180715 --- /dev/null +++ b/.cursor/skills/codebase-design/agents/openai.yaml @@ -0,0 +1,3 @@ +interface: + display_name: "Codebase Design" + short_description: "Vocabulary for deep-module design" diff --git a/.cursor/skills/diagnosing-bugs/SKILL.md b/.cursor/skills/diagnosing-bugs/SKILL.md new file mode 100644 index 0000000..7f8acf7 --- /dev/null +++ b/.cursor/skills/diagnosing-bugs/SKILL.md @@ -0,0 +1,140 @@ +--- +name: diagnosing-bugs +description: Diagnosis loop for hard bugs and performance regressions. Use when the user says "diagnose"/"debug this", or reports something broken/throwing/failing/slow. +--- + +# Diagnosing Bugs + +A discipline for hard bugs. Skip phases only when explicitly justified. + +When exploring the codebase, read `CONTEXT.md` (if it exists) to get a clear mental model of the relevant modules, and check ADRs in the area you're touching. + +## Redact + +This skill has you show commands, outputs and captured artifacts. **Redact every secret first** — write `` in its place. Build loops against env vars, so the credential stays in the environment rather than in what you show. Captured artifacts carry auth headers: quote only the lines that carry the signal. + +If the redacted output is not enough to diagnose the bug, say so and ask the user. + +## Phase 1 — Build a feedback loop + +**This is the skill.** Everything else is mechanical. If you have a **tight** pass/fail signal for the bug — one that goes red on _this_ bug — you will find the cause; bisection, hypothesis-testing, and instrumentation all just consume it. If you don't have one, no amount of staring at code will save you. + +Spend disproportionate effort here. **Be aggressive. Be creative. Refuse to give up.** + +### Ways to construct one — try them in roughly this order + +1. **Failing test** at whatever seam reaches the bug — unit, integration, e2e. +2. **Curl / HTTP script** against a running dev server. +3. **CLI invocation** with a fixture input, diffing stdout against a known-good snapshot. +4. **Headless browser script** (Playwright / Puppeteer) — drives the UI, asserts on DOM/console/network. +5. **Replay a captured trace.** Save a real network request / payload / event log to disk; replay it through the code path in isolation. +6. **Throwaway harness.** Spin up a minimal subset of the system (one service, mocked deps) that exercises the bug code path with a single function call. +7. **Property / fuzz loop.** If the bug is "sometimes wrong output", run 1000 random inputs and look for the failure mode. +8. **Bisection harness.** If the bug appeared between two known states (commit, dataset, version), automate "boot at state X, check, repeat" so you can `git bisect run` it. +9. **Differential loop.** Run the same input through old-version vs new-version (or two configs) and diff outputs. +10. **HITL bash script.** Last resort. If a human must click, drive _them_ with `scripts/hitl-loop.template.sh` so the loop is still structured. Captured output feeds back to you. + +Build the right feedback loop, and the bug is 90% fixed. + +### Tighten the loop + +Treat the loop as a product. Once you have _a_ loop, **tighten** it: + +- Can I make it faster? (Cache setup, skip unrelated init, narrow the test scope.) +- Can I make the signal sharper? (Assert on the specific symptom, not "didn't crash".) +- Can I make it more deterministic? (Pin time, seed RNG, isolate filesystem, freeze network.) + +A 30-second flaky loop is barely better than no loop; a 2-second deterministic one is tight — a debugging superpower. + +### Non-deterministic bugs + +The goal is not a clean repro but a **higher reproduction rate**. Loop the trigger 100×, parallelise, add stress, narrow timing windows, inject sleeps. A 50%-flake bug is debuggable; 1% is not — keep raising the rate until it's debuggable. + +### When you genuinely cannot build a loop + +Stop and say so explicitly. List what you tried. Ask the user for: (a) access to whatever environment reproduces it, (b) a redacted captured artifact (HAR file, log dump, core dump, screen recording with timestamps), or (c) permission to add temporary production instrumentation. Do **not** proceed to hypothesise without a loop. + +### Completion criterion — a tight loop that goes red + +Phase 1 is done when the loop is **tight** and **red-capable**: you can name **one command** — a script path, a test invocation, a curl — that you have **already run at least once** (show the invocation and its output, redacted), and that is: + +- [ ] **Red-capable** — it drives the actual bug code path and asserts the **user's exact symptom**, so it can go red on this bug and green once fixed. Not "runs without erroring" — it must be able to _catch this specific bug_. +- [ ] **Deterministic** — same verdict every run (flaky bugs: a pinned, high reproduction rate, per above). +- [ ] **Fast** — seconds, not minutes. +- [ ] **Agent-runnable** — you can run it unattended; a human in the loop only via `scripts/hitl-loop.template.sh`. + +If you catch yourself reading code to build a theory before this command exists, **stop — jumping straight to a hypothesis is the exact failure this skill prevents.** No red-capable command, no Phase 2. + +## Phase 2 — Reproduce + minimise + +Run the loop. Watch it go red — the bug appears. + +Confirm: + +- [ ] The loop produces the failure mode the **user** described — not a different failure that happens to be nearby. Wrong bug = wrong fix. +- [ ] The failure is reproducible across multiple runs (or, for non-deterministic bugs, reproducible at a high enough rate to debug against). +- [ ] You have captured the exact symptom (error message, wrong output, slow timing) so later phases can verify the fix actually addresses it. + +### Minimise + +Once it's red, shrink the repro to the **smallest scenario that still goes red**. Cut inputs, callers, config, data, and steps **one at a time**, re-running the loop after each cut — keep only what's load-bearing for the failure. + +Why bother: a minimal repro shrinks the hypothesis space in Phase 3 (fewer moving parts left to suspect) and becomes the clean regression test in Phase 5. + +Done when **every remaining element is load-bearing** — removing any one of them makes the loop go green. + +Do not proceed until you have reproduced **and** minimised. + +## Phase 3 — Hypothesise + +Generate **3–5 ranked hypotheses** before testing any of them. Single-hypothesis generation anchors on the first plausible idea. + +Each hypothesis must be **falsifiable**: state the prediction it makes. + +> Format: "If is the cause, then will make the bug disappear / will make it worse." + +If you cannot state the prediction, the hypothesis is a vibe — discard or sharpen it. + +**Show the ranked list to the user before testing.** They often have domain knowledge that re-ranks instantly ("we just deployed a change to #3"), or know hypotheses they've already ruled out. Cheap checkpoint, big time saver. Don't block on it — proceed with your ranking if the user is AFK. + +## Phase 4 — Instrument + +Each probe must map to a specific prediction from Phase 3. **Change one variable at a time.** + +Tool preference: + +1. **Debugger / REPL inspection** if the env supports it. One breakpoint beats ten logs. +2. **Targeted logs** at the boundaries that distinguish hypotheses. +3. Never "log everything and grep". + +**Tag every debug log** with a unique prefix, e.g. `[DEBUG-a4f2]`. Cleanup at the end becomes a single grep. Untagged logs survive; tagged logs die. + +**Perf branch.** For performance regressions, logs are usually wrong. Instead: establish a baseline measurement (timing harness, `performance.now()`, profiler, query plan), then bisect. Measure first, fix second. + +## Phase 5 — Fix + regression test + +Write the regression test **before the fix** — but only if there is a **correct seam** for it. + +A correct seam is one where the test exercises the **real bug pattern** as it occurs at the call site. If the only available seam is too shallow (single-caller test when the bug needs multiple callers, unit test that can't replicate the chain that triggered the bug), a regression test there gives false confidence. + +**If no correct seam exists, that itself is the finding.** Note it. The codebase architecture is preventing the bug from being locked down. Flag this for the next phase. + +If a correct seam exists: + +1. Turn the minimised repro into a failing test at that seam. +2. Watch it fail. +3. Apply the fix. +4. Watch it pass. +5. Re-run the Phase 1 feedback loop against the original (un-minimised) scenario. + +## Phase 6 — Cleanup + post-mortem + +Required before declaring done: + +- [ ] Original repro no longer reproduces (re-run the Phase 1 loop) +- [ ] Regression test passes (or absence of seam is documented) +- [ ] All `[DEBUG-...]` instrumentation removed (`grep` the prefix) +- [ ] Throwaway prototypes deleted (or moved to a clearly-marked debug location) +- [ ] The hypothesis that turned out correct is stated in the commit / PR message — so the next debugger learns + +**Then ask: what would have prevented this bug?** If the answer involves architectural change (no good test seam, tangled callers, hidden coupling) hand off to the `/improve-codebase-architecture` skill with the specifics. Make the recommendation **after** the fix is in, not before — you have more information now than when you started. diff --git a/.cursor/skills/diagnosing-bugs/agents/openai.yaml b/.cursor/skills/diagnosing-bugs/agents/openai.yaml new file mode 100644 index 0000000..a13a755 --- /dev/null +++ b/.cursor/skills/diagnosing-bugs/agents/openai.yaml @@ -0,0 +1,3 @@ +interface: + display_name: "Diagnosing Bugs" + short_description: "Diagnose hard bugs and regressions" diff --git a/.cursor/skills/diagnosing-bugs/scripts/hitl-loop.template.sh b/.cursor/skills/diagnosing-bugs/scripts/hitl-loop.template.sh new file mode 100644 index 0000000..43daedd --- /dev/null +++ b/.cursor/skills/diagnosing-bugs/scripts/hitl-loop.template.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# Human-in-the-loop reproduction loop. +# Copy this file, edit the steps below, and run it. +# The agent runs the script; the user follows prompts in their terminal. +# +# Usage: +# bash hitl-loop.template.sh +# +# Two helpers: +# step "" → show instruction, wait for Enter +# capture VAR "" → show question, read response into VAR +# +# At the end, captured values are printed as KEY=VALUE for the agent to parse. +# +# `capture` prints its value back to the terminal, where the agent reads it — so +# capture observations, and leave signing in to the user as a `step`. + +set -euo pipefail + +step() { + printf '\n>>> %s\n' "$1" + read -r -p " [Enter when done] " _ +} + +capture() { + local var="$1" question="$2" answer + printf '\n>>> %s\n' "$question" + read -r -p " > " answer + printf -v "$var" '%s' "$answer" +} + +# --- edit below --------------------------------------------------------- + +step "Open the app at http://localhost:3000 and sign in." + +capture ERRORED "Click the 'Export' button. Did it throw an error? (y/n)" + +capture ERROR_MSG "Paste the error message (or 'none'):" + +# --- edit above --------------------------------------------------------- + +printf '\n--- Captured ---\n' +printf 'ERRORED=%s\n' "$ERRORED" +printf 'ERROR_MSG=%s\n' "$ERROR_MSG" diff --git a/.cursor/skills/domain-modeling/ADR-FORMAT.md b/.cursor/skills/domain-modeling/ADR-FORMAT.md new file mode 100644 index 0000000..da7e78e --- /dev/null +++ b/.cursor/skills/domain-modeling/ADR-FORMAT.md @@ -0,0 +1,47 @@ +# ADR Format + +ADRs live in `docs/adr/` and use sequential numbering: `0001-slug.md`, `0002-slug.md`, etc. + +Create the `docs/adr/` directory lazily — only when the first ADR is needed. + +## Template + +```md +# {Short title of the decision} + +{1-3 sentences: what's the context, what did we decide, and why.} +``` + +That's it. An ADR can be a single paragraph. The value is in recording *that* a decision was made and *why* — not in filling out sections. + +## Optional sections + +Only include these when they add genuine value. Most ADRs won't need them. + +- **Status** frontmatter (`proposed | accepted | deprecated | superseded by ADR-NNNN`) — useful when decisions are revisited +- **Considered Options** — only when the rejected alternatives are worth remembering +- **Consequences** — only when non-obvious downstream effects need to be called out + +## Numbering + +Scan `docs/adr/` for the highest existing number and increment by one. + +## When to offer an ADR + +All three of these must be true: + +1. **Hard to reverse** — the cost of changing your mind later is meaningful +2. **Surprising without context** — a future reader will look at the code and wonder "why on earth did they do it this way?" +3. **The result of a real trade-off** — there were genuine alternatives and you picked one for specific reasons + +If a decision is easy to reverse, skip it — you'll just reverse it. If it's not surprising, nobody will wonder why. If there was no real alternative, there's nothing to record beyond "we did the obvious thing." + +### What qualifies + +- **Architectural shape.** "We're using a monorepo." "The write model is event-sourced, the read model is projected into Postgres." +- **Integration patterns between contexts.** "Ordering and Billing communicate via domain events, not synchronous HTTP." +- **Technology choices that carry lock-in.** Database, message bus, auth provider, deployment target. Not every library — just the ones that would take a quarter to swap out. +- **Boundary and scope decisions.** "Customer data is owned by the Customer context; other contexts reference it by ID only." The explicit no-s are as valuable as the yes-s. +- **Deliberate deviations from the obvious path.** "We're using manual SQL instead of an ORM because X." Anything where a reasonable reader would assume the opposite. These stop the next engineer from "fixing" something that was deliberate. +- **Constraints not visible in the code.** "We can't use AWS because of compliance requirements." "Response times must be under 200ms because of the partner API contract." +- **Rejected alternatives when the rejection is non-obvious.** If you considered GraphQL and picked REST for subtle reasons, record it — otherwise someone will suggest GraphQL again in six months. diff --git a/.cursor/skills/domain-modeling/CONTEXT-FORMAT.md b/.cursor/skills/domain-modeling/CONTEXT-FORMAT.md new file mode 100644 index 0000000..eaf2a18 --- /dev/null +++ b/.cursor/skills/domain-modeling/CONTEXT-FORMAT.md @@ -0,0 +1,60 @@ +# CONTEXT.md Format + +## Structure + +```md +# {Context Name} + +{One or two sentence description of what this context is and why it exists.} + +## Language + +**Order**: +{A one or two sentence description of the term} +_Avoid_: Purchase, transaction + +**Invoice**: +A request for payment sent to a customer after delivery. +_Avoid_: Bill, payment request + +**Customer**: +A person or organization that places orders. +_Avoid_: Client, buyer, account +``` + +## Rules + +- **Be opinionated.** When multiple words exist for the same concept, pick the best one and list the others under `_Avoid_`. +- **Keep definitions tight.** One or two sentences max. Define what it IS, not what it does. +- **Only include terms specific to this project's context.** General programming concepts (timeouts, error types, utility patterns) don't belong even if the project uses them extensively. Before adding a term, ask: is this a concept unique to this context, or a general programming concept? Only the former belongs. +- **Group terms under subheadings** when natural clusters emerge. If all terms belong to a single cohesive area, a flat list is fine. + +## Single vs multi-context repos + +**Single context (most repos):** One `CONTEXT.md` at the repo root. + +**Multiple contexts:** A `CONTEXT-MAP.md` at the repo root lists the contexts, where they live, and how they relate to each other: + +```md +# Context Map + +## Contexts + +- [Ordering](./src/ordering/CONTEXT.md) — receives and tracks customer orders +- [Billing](./src/billing/CONTEXT.md) — generates invoices and processes payments +- [Fulfillment](./src/fulfillment/CONTEXT.md) — manages warehouse picking and shipping + +## Relationships + +- **Ordering → Fulfillment**: Ordering emits `OrderPlaced` events; Fulfillment consumes them to start picking +- **Fulfillment → Billing**: Fulfillment emits `ShipmentDispatched` events; Billing consumes them to generate invoices +- **Ordering ↔ Billing**: Shared types for `CustomerId` and `Money` +``` + +The skill infers which structure applies: + +- If `CONTEXT-MAP.md` exists, read it to find contexts +- If only a root `CONTEXT.md` exists, single context +- If neither exists, create a root `CONTEXT.md` lazily when the first term is resolved + +When multiple contexts exist, infer which one the current topic relates to. If unclear, ask. diff --git a/.cursor/skills/domain-modeling/SKILL.md b/.cursor/skills/domain-modeling/SKILL.md new file mode 100644 index 0000000..d0f7e1a --- /dev/null +++ b/.cursor/skills/domain-modeling/SKILL.md @@ -0,0 +1,74 @@ +--- +name: domain-modeling +description: Build and sharpen a project's domain model. Use when the user wants to pin down domain terminology or a ubiquitous language, record an architectural decision, or when another skill needs to maintain the domain model. +--- + +# Domain Modeling + +Actively build and sharpen the project's domain model as you design. This is the *active* discipline — challenging terms, inventing edge-case scenarios, and writing the glossary and decisions down the moment they crystallise. (Merely *reading* `CONTEXT.md` for vocabulary is not this skill — that's a one-line habit any skill can do. This skill is for when you're changing the model, not just consuming it.) + +## File structure + +Most repos have a single context: + +``` +/ +├── CONTEXT.md +├── docs/ +│ └── adr/ +│ ├── 0001-event-sourced-orders.md +│ └── 0002-postgres-for-write-model.md +└── src/ +``` + +If a `CONTEXT-MAP.md` exists at the root, the repo has multiple contexts. The map points to where each one lives: + +``` +/ +├── CONTEXT-MAP.md +├── docs/ +│ └── adr/ ← system-wide decisions +├── src/ +│ ├── ordering/ +│ │ ├── CONTEXT.md +│ │ └── docs/adr/ ← context-specific decisions +│ └── billing/ +│ ├── CONTEXT.md +│ └── docs/adr/ +``` + +Create files lazily — only when you have something to write. If no `CONTEXT.md` exists, create one when the first term is resolved. If no `docs/adr/` exists, create it when the first ADR is needed. + +## During the session + +### Challenge against the glossary + +When the user uses a term that conflicts with the existing language in `CONTEXT.md`, call it out immediately. "Your glossary defines 'cancellation' as X, but you seem to mean Y — which is it?" + +### Sharpen fuzzy language + +When the user uses vague or overloaded terms, propose a precise canonical term. "You're saying 'account' — do you mean the Customer or the User? Those are different things." + +### Discuss concrete scenarios + +When domain relationships are being discussed, stress-test them with specific scenarios. Invent scenarios that probe edge cases and force the user to be precise about the boundaries between concepts. + +### Cross-reference with code + +When the user states how something works, check whether the code agrees. If you find a contradiction, surface it: "Your code cancels entire Orders, but you just said partial cancellation is possible — which is right?" + +### Update CONTEXT.md inline + +When a term is resolved, update `CONTEXT.md` right there. Don't batch these up — capture them as they happen. Use the format in [CONTEXT-FORMAT.md](./CONTEXT-FORMAT.md). + +`CONTEXT.md` should be totally devoid of implementation details. Do not treat `CONTEXT.md` as a spec, a scratch pad, or a repository for implementation decisions. It is a glossary and nothing else. + +### Offer ADRs sparingly + +Only offer to create an ADR when all three are true: + +1. **Hard to reverse** — the cost of changing your mind later is meaningful +2. **Surprising without context** — a future reader will wonder "why did they do it this way?" +3. **The result of a real trade-off** — there were genuine alternatives and you picked one for specific reasons + +If any of the three is missing, skip the ADR. Use the format in [ADR-FORMAT.md](./ADR-FORMAT.md). diff --git a/.cursor/skills/domain-modeling/agents/openai.yaml b/.cursor/skills/domain-modeling/agents/openai.yaml new file mode 100644 index 0000000..7f1522d --- /dev/null +++ b/.cursor/skills/domain-modeling/agents/openai.yaml @@ -0,0 +1,3 @@ +interface: + display_name: "Domain Modeling" + short_description: "Build and sharpen a domain model" diff --git a/.cursor/skills/explain-codebase-concept/HTML-REPORT.md b/.cursor/skills/explain-codebase-concept/HTML-REPORT.md new file mode 100644 index 0000000..a8e1856 --- /dev/null +++ b/.cursor/skills/explain-codebase-concept/HTML-REPORT.md @@ -0,0 +1,124 @@ +# HTML Report Format — Concept Walkthrough + +Pedagogical walkthrough of **one** Ahentic concept / feature / subsystem. Same **delivery** mechanics as `improve-codebase-architecture` (temp HTML, CDN Tailwind + Mermaid, open in browser) — different **pedagogy**. No before/after deepening cards, no Strong/Speculative badges. + +## Scaffold + +```html + + + + + Walkthrough — {{concept}} — Ahentic + + + + + +
    + + +
    + + +``` + +**Temp path:** `$TMPDIR/concept-walkthrough--.html` +**Durable path (optional):** `docs/walkthroughs/.html` + +When writing a durable copy, put this banner at the top of `
    `: + +```html + +``` + +## Mandatory sections (in order) + +Every report includes all nine. If a section has nothing to say, keep the heading and one line (e.g. “None.”). Do not omit sections. + +### 1. Header + +- Concept title (CONTEXT.md term when one exists) +- One-line “what it is” +- Scope statement (what this walkthrough covers / deliberately skips) +- Repo name + date + +No long introduction — the ELI5 is next. + +### 2. ELI5 + +Plain story of what happens for the user or system. **No file names, no class names.** A new teammate should understand the *idea* before the wiring. + +### 3. Wiring diagram + +Centrepiece. Mermaid `flowchart` or `sequenceDiagram` for the runtime path. Black-box neighbors: muted/dashed nodes labeled with “see `…/CONTRACT.md`” (or how-it-works path). + +```html +
    +
    +    sequenceDiagram
    +      participant U as User
    +      participant S as Sidebar
    +      participant R as REST
    +      participant O as Orchestrator
    +      U->>S: action
    +      S->>R: request
    +      R->>O: step
    +  
    +
    +``` + +Mix Mermaid with hand-built divs/SVG when layout fights you. Keep diagrams ~320–400px tall when possible. Variety is fine; generic identical graphs are not. + +### 4. Cast of modules + +Glossary terms + **primary** files only (roughly 5–12). Monospaced paths. One short role clause each. Not every file that greps. + +### 5. Step-through + +Numbered path matching the wiring diagram. Sparse code anchors: path + key symbol (function/class/ability name). Prefer “what happens at this hop” over pasted code blocks. If a snippet helps, keep it tiny. + +### 6. How it talks to neighbors + +Inbound / outbound only. Black boxes with pointers to CONTRACT / how-it-works. Name a **seam** here when it helps a contributor know what not to reach across. Otherwise plain language. + +### 7. Change map + +Contribution-ready: + +- “To extend / fix behavior in this slice, you usually touch …” (ordered touch path) +- Docs / checklists / tests / catalogues that must stay in sync (e.g. `docs/agents/ability-checklist.md`, ability catalog tests) — only when relevant +- Optional: one-line “don’t” that protects a known seam (positive phrasing preferred) + +Wins for contributors, not vague “easier to maintain.” + +### 8. Read next + +Links into the doc ladder for this slice: CONTEXT, architecture, CONTRACT, how-it-works, PRD/ADR if relevant. Absolute-from-repo-root paths in monospace or markdown links. + +### 9. Open questions / drift + +Doc↔code mismatches, missing glossary terms, phantom names, unclear ownership. If none: “No doc drift found.” +Do not silently “fix” the narrative to hide drift — list it and offer a fix in chat. + +## Style + +- Editorial, not dashboard. Generous whitespace. Serif optional for headings (`font-serif` with stone/slate). +- Colour sparingly: one accent; amber for the durable banner / drift callouts. +- Prose sparse. If the diagram needs a paragraph, redraw the diagram. +- Scripts: Tailwind CDN + Mermaid ESM only. Static otherwise. +- Domain vocabulary from `CONTEXT.md`. No architecture-review badge row. + +## Tone + +Teach the path. Concise. No throat-clearing. The reader should leave knowing what the concept is, how it is wired, where to read next, and where to change things. diff --git a/.cursor/skills/explain-codebase-concept/SKILL.md b/.cursor/skills/explain-codebase-concept/SKILL.md new file mode 100644 index 0000000..600283d --- /dev/null +++ b/.cursor/skills/explain-codebase-concept/SKILL.md @@ -0,0 +1,90 @@ +--- +name: explain-codebase-concept +description: Onboard to how one Ahentic concept, feature, or subsystem is wired — chat summary plus visual HTML walkthrough (optional durable copy under docs/walkthroughs/). +disable-model-invocation: true +--- + +# Explain Codebase Concept + +Onboard a **repo / Ahentic beginner** (stack-literate: WP/JS assumed) to **one** concept, feature, or subsystem so they can contribute — extend, fix bugs, or add related behavior — without inventing a second architecture doc. + +**Deliverables (every run):** chat summary **and** a self-contained HTML walkthrough (Tailwind + Mermaid). +**Authority:** compose and cite the doc ladder + live code. PRD / CONTRACT win on disagreement. Flag drift; **offer** doc fixes — do not auto-write product law. +**Not this skill:** deepening/refactor candidates (`improve-codebase-architecture`), bug hunts (`diagnosing-bugs`), or a whole-product tour (point at `docs/architecture.md` and offer a menu of slices). + +See [HTML-REPORT.md](HTML-REPORT.md) for the report scaffold and section rules. Human index: [`docs/agents/explain-concept.md`](../../../docs/agents/explain-concept.md). + +## Process + +### 1. Scope + +**Done when:** one coherent slice is named in a single sentence (concept / flow / subsystem), or the user picked from a short menu. + +- If the ask maps cleanly to `CONTEXT.md`, `docs/architecture.md`, or a `CONTRACT.md` → state the assumed scope and proceed. +- If several slices fit → offer a **short menu** (A / B / C), not a full grill. +- If they ask to “explain all of Ahentic” → point at [`docs/architecture.md`](../../../docs/architecture.md); offer 3–5 high-value follow-on concepts. Do not generate a mega-walkthrough. + +Normalize subsystem / filename asks into the **runtime path that makes the concept real** (sidebar → REST → orchestrator → …), not a folder dump. + +### 2. Explore + +**Done when:** glossary terms for the slice are loaded, the relevant CONTRACT + how-it-works are read, the live path is traced, and any doc↔code drift is noted. + +1. **Docs-first ladder** (skip missing files silently): `CONTEXT.md` → `docs/architecture.md` → relevant `src/**/CONTRACT.md` + colocated how-it-works → PRD/ADR only if the slice is about intended behavior or a recorded trade-off. +2. **Code trace** — use the Agent tool with `subagent_type=Explore` to follow the runtime path end-to-end. Neighbors that are not load-bearing stay **black boxes** with pointers to their CONTRACT / how-it-works. +3. Collect primary files and key symbols for the Cast and Step-through (prefer a tight set over exhaustiveness). +4. Note contribution hooks: usual touch path, ability-checklist / tests / catalogues that must stay in sync when this slice changes. + +Use **CONTEXT.md** vocabulary for domain names. Use codebase-design terms (`module`, `seam`, `interface`, …) **only** in Neighbors / Change map when they help a contributor locate a change. Keep ELI5 in plain English. + +### 3. Write the HTML walkthrough + +**Done when:** the file exists, opens in the browser, and contains all nine mandatory sections in order (see [HTML-REPORT.md](HTML-REPORT.md)). + +1. Resolve temp dir from `$TMPDIR`, else `/tmp` (or `%TEMP%` on Windows). +2. Write `$TMPDIR/concept-walkthrough--.html` (slug from the concept name; kebab-case). +3. Open it: `open ` (macOS), `xdg-open ` (Linux), `start ` (Windows). +4. Tell the user the absolute path. + +Empty sections still appear as a one-liner (e.g. “No doc drift found”) so the shape stays recognizable. + +### 4. Chat handoff + +**Done when:** the user has a short summary in-thread and clear next offers. + +In chat (≈5–8 lines): + +- What the concept is (one sentence) +- The runtime path in plain words +- Where to look first (2–4 primary files) +- How you’d usually change it (one sentence) +- Absolute path to the HTML file + +Then **offer**: + +- (a) zoom a black-boxed neighbor into its own walkthrough +- (b) walk a related concept +- (c) fix doc drift (if any were listed) +- (d) **save a durable copy** under `docs/walkthroughs/` for humans (see below) + +### 5. Durable docs (optional) + +**Done when:** either the user declined, or `docs/walkthroughs/.html` is written, the folder README index lists it, and the HTML banner states it is pedagogical — not product law. + +Default output stays in temp. When the user asks to keep / commit / add as docs: + +1. Write (or refresh) `docs/walkthroughs/.html` with the **same** mandatory outline and a visible banner: pedagogical walkthrough; PRD / CONTRACT / how-it-works win; regenerate when the slice drifts. +2. Update `docs/walkthroughs/README.md` index (slug, title, one-line summary, date). +3. Do **not** `git commit` unless the user explicitly asks. Do **not** invent product “should” in the walkthrough — cite PRDs; absorb real product gaps via domain-modeling / PRD edits only when the user accepts that offer. +4. Do **not** replace or duplicate `CONTRACT.md` / colocated how-it-works as the source of truth; the walkthrough **points at** them. + +Walkthroughs are supporting orientation maps (cross-stack, beginner-shaped). They are not a fourth product-law surface (see ADR-0001 / PRD index). + +## Collision guard + +| Ask | Skill | +| --- | --- | +| How does X work / onboard me on X | **This skill** | +| Deepen / shallow modules / architecture review | `improve-codebase-architecture` | +| Something’s broken / failing | `diagnosing-bugs` | +| Whole-system map only | `docs/architecture.md` (no mega-HTML) | diff --git a/.cursor/skills/explain-codebase-concept/agents/openai.yaml b/.cursor/skills/explain-codebase-concept/agents/openai.yaml new file mode 100644 index 0000000..a6c6ec8 --- /dev/null +++ b/.cursor/skills/explain-codebase-concept/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "Explain Codebase Concept" + short_description: "Onboard to how one Ahentic concept is wired" +policy: + allow_implicit_invocation: false diff --git a/.cursor/skills/grill-me/SKILL.md b/.cursor/skills/grill-me/SKILL.md new file mode 100644 index 0000000..9470cfc --- /dev/null +++ b/.cursor/skills/grill-me/SKILL.md @@ -0,0 +1,7 @@ +--- +name: grill-me +description: A relentless interview to sharpen a plan or design. +disable-model-invocation: true +--- + +Run a `/grilling` session. diff --git a/.cursor/skills/grill-me/agents/openai.yaml b/.cursor/skills/grill-me/agents/openai.yaml new file mode 100644 index 0000000..4d6fb0c --- /dev/null +++ b/.cursor/skills/grill-me/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "Grill Me" + short_description: "Sharpen a plan through interview" +policy: + allow_implicit_invocation: false diff --git a/.cursor/skills/grill-with-docs/SKILL.md b/.cursor/skills/grill-with-docs/SKILL.md new file mode 100644 index 0000000..bed05d2 --- /dev/null +++ b/.cursor/skills/grill-with-docs/SKILL.md @@ -0,0 +1,7 @@ +--- +name: grill-with-docs +description: A relentless interview to sharpen a plan or design, which also creates docs (ADR's and glossary) as we go. +disable-model-invocation: true +--- + +Run a `/grilling` session, using the `/domain-modeling` skill. diff --git a/.cursor/skills/grill-with-docs/agents/openai.yaml b/.cursor/skills/grill-with-docs/agents/openai.yaml new file mode 100644 index 0000000..5dbe278 --- /dev/null +++ b/.cursor/skills/grill-with-docs/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "Grill with Docs" + short_description: "Grill a design and write its docs" +policy: + allow_implicit_invocation: false diff --git a/.cursor/skills/grilling/SKILL.md b/.cursor/skills/grilling/SKILL.md new file mode 100644 index 0000000..95bd01e --- /dev/null +++ b/.cursor/skills/grilling/SKILL.md @@ -0,0 +1,22 @@ +--- +name: grilling +description: Grill the user relentlessly about a plan, decision, or idea. Use when the user wants to stress-test their thinking, or uses any 'grill' trigger phrases. +--- + +Interview the user relentlessly until you reach a shared understanding. Map this as a **design tree**: every decision branches into the decisions that hang off it. + +Work the tree in **rounds**. The **frontier** is every decision whose prerequisites are already settled — the questions you can ask _now_ without guessing at answers you haven't heard yet. Ask the whole frontier in one round: number each question and give your recommended answer. Then wait for the user's answers before the next round. + +Each question should be formatted like so: + +``` +❓ **Q1** - ****: + +➡️ +``` + +Each round the user answers reshapes the tree — settled decisions push the frontier outward and unblock questions that depended on them. Recompute the frontier and ask the next round. A question whose answer depends on another question still open in this round belongs to a _later_ round, not this one. + +Finding _facts_ is your job, never the user's. When a frontier question needs a fact from the environment (filesystem, tools, etc.), dispatch a sub-agent to find it — don't ask the user for anything you could look up yourself. Don't block on it: a running exploration is an unsettled prerequisite, so only the questions downstream of it wait for the sub-agent to report — ask the rest of the frontier now. The _decisions_ are the user's — put each to them and wait. + +The session is done when the frontier is empty: every branch of the design tree visited, nothing left silently assumed. Do not act on it until the user confirms you have reached a shared understanding. diff --git a/.cursor/skills/grilling/agents/openai.yaml b/.cursor/skills/grilling/agents/openai.yaml new file mode 100644 index 0000000..ddbdb96 --- /dev/null +++ b/.cursor/skills/grilling/agents/openai.yaml @@ -0,0 +1,3 @@ +interface: + display_name: "Grilling" + short_description: "Stress-test thinking a round of questions at a time" diff --git a/.cursor/skills/implement/SKILL.md b/.cursor/skills/implement/SKILL.md new file mode 100644 index 0000000..98e0dc0 --- /dev/null +++ b/.cursor/skills/implement/SKILL.md @@ -0,0 +1,23 @@ +--- +name: implement +description: "Implement a piece of work based on a spec or set of tickets." +disable-model-invocation: true +--- + +Implement the work described by the user in the spec or tickets. + +## Steps + +1. **Orient** — Read the issue/task and the owning `src/**/CONTRACT.md` (and PRD if linked). Done when you can name the seam this change belongs in. + +2. **Sibling first** — Search the nearest module/file that already does the same kind of thing. Deepen that seam; extract a shared helper before pasting the nth clone. Done when you know the target file(s) and are not inventing a parallel list, registry, or AJAX twin. + +3. **Ability surface?** — If the change registers, renames, removes, or names an ability in prompts/playbooks/UI, open [`docs/agents/ability-checklist.md`](../../../docs/agents/ability-checklist.md) and complete every applicable item before coding further. + +4. **Build with /tdd** where possible, at pre-agreed seams. Run the focused PHPUnit file (or Playwright module spec) after each vertical slice. + +5. **Finish** — Run the full test suite once (`composer test` / `npm test` as appropriate). Then `/code-review` against the issue or spec. Commit to the current branch when the user asked for a commit (or when this skill’s commit step applies). + +## Done + +Applicable ability-checklist boxes are checked; focused then full tests are green; code-review has been run. diff --git a/.cursor/skills/implement/agents/openai.yaml b/.cursor/skills/implement/agents/openai.yaml new file mode 100644 index 0000000..f8794dc --- /dev/null +++ b/.cursor/skills/implement/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "Implement" + short_description: "Build work from a spec or tickets" +policy: + allow_implicit_invocation: false diff --git a/.cursor/skills/improve-codebase-architecture/HTML-REPORT.md b/.cursor/skills/improve-codebase-architecture/HTML-REPORT.md new file mode 100644 index 0000000..17f6d2c --- /dev/null +++ b/.cursor/skills/improve-codebase-architecture/HTML-REPORT.md @@ -0,0 +1,123 @@ +# HTML Report Format + +The architectural review is rendered as a single self-contained HTML file in the OS temp directory. Tailwind and Mermaid both come from CDNs. Mermaid handles graph-shaped diagrams reliably; hand-built divs and inline SVG handle the more editorial visuals (mass diagrams, cross-sections). Mix the two — don't lean on Mermaid for everything, it'll start to look generic. + +## Scaffold + +```html + + + + + Architecture review — {{repo name}} + + + + + +
    +
    ...
    +
    ...
    +
    ...
    +
    + + +``` + +## Header + +Repo name, date, and a compact legend: solid box = module, dashed line = seam, red arrow = leakage, thick dark box = deep module. No introduction paragraph — straight into the candidates. + +## Candidate card + +The diagrams carry the weight. Prose is sparse, plain, and uses the glossary terms (from the `/codebase-design` skill) without ceremony. + +Each candidate is one `
    `: + +- **Title** — short, names the deepening (e.g. "Collapse the Order intake pipeline"). +- **Badge row** — recommendation strength (`Strong` = emerald, `Worth exploring` = amber, `Speculative` = slate), plus a tag for the dependency category (`in-process`, `local-substitutable`, `ports & adapters`, `mock`). +- **Files** — monospaced list, `font-mono text-sm`. +- **Before / After diagram** — the centrepiece. Two columns, side by side. See patterns below. +- **Problem** — one sentence. What hurts. +- **Solution** — one sentence. What changes. +- **Wins** — bullets, ≤6 words each. e.g. "Tests hit one interface", "Pricing logic stops leaking", "Delete 4 shallow wrappers". +- **ADR callout** (if applicable) — one line in an amber-tinted box. + +No paragraphs of explanation. If the diagram needs a paragraph to be understood, redraw the diagram. + +## Diagram patterns + +Pick the pattern that fits the candidate. Mix them. Don't make every diagram look the same — variety is part of the point. + +### Mermaid graph (the workhorse for dependencies / call flow) + +Use a Mermaid `flowchart` or `graph` when the point is "X calls Y calls Z, and look at the mess." Wrap it in a Tailwind-styled card so it doesn't feel parachuted in. Style with classDef to colour leakage edges red and the deep module dark. Sequence diagrams work well for "before: 6 round-trips; after: 1." + +```html +
    +
    +    flowchart LR
    +      A[OrderHandler] --> B[OrderValidator]
    +      B --> C[OrderRepo]
    +      C -.leak.-> D[PricingClient]
    +      classDef leak stroke:#dc2626,stroke-width:2px;
    +      class C,D leak
    +  
    +
    +``` + +### Hand-built boxes-and-arrows (when Mermaid's layout fights you) + +Modules as `
    `s with borders and labels. Arrows as inline SVG `` or `` elements positioned absolutely over a relative container. Reach for this when you want the "after" diagram to feel like one thick-bordered deep module with greyed-out internals — Mermaid won't render that with the right weight. + +### Cross-section (good for layered shallowness) + +Stack horizontal bands (`h-12 border-l-4`) to show layers a call passes through. Before: 6 thin layers each doing nothing. After: 1 thick band labelled with the consolidated responsibility. + +### Mass diagram (good for "interface as wide as implementation") + +Two rectangles per module — one for interface surface area, one for implementation. Before: interface rectangle is nearly as tall as the implementation rectangle (shallow). After: interface rectangle is short, implementation rectangle is tall (deep). + +### Call-graph collapse + +Before: a tree of function calls rendered as nested boxes. After: the same tree collapsed into one box, with the now-internal calls shown faded inside it. + +## Style guidance + +- Lean editorial, not corporate-dashboard. Generous whitespace. Serif optional for headings (`font-serif` works well with stone/slate). +- Colour sparingly: one accent (emerald or indigo) plus red for leakage and amber for warnings. +- Keep diagrams ~320px tall so before/after sits comfortably side by side without scrolling. +- Use `text-xs uppercase tracking-wider` for module labels inside diagrams — they should read as schematic, not as UI. +- The only scripts are the Tailwind CDN and the Mermaid ESM import. The report is otherwise static — no app code, no interactivity beyond Mermaid's own rendering. + +## Top recommendation section + +One larger card. Candidate name, one sentence on why, anchor link to its card. That's it. + +## Tone + +Plain English, concise — but the architectural nouns and verbs come straight from the `/codebase-design` skill. Concision is not an excuse to drift. + +**Use exactly:** module, interface, implementation, depth, deep, shallow, seam, adapter, leverage, locality. + +**Never substitute:** component, service, unit (for module) · API, signature (for interface) · boundary (for seam) · layer, wrapper (for module, when you mean module). + +**Phrasings that fit the style:** + +- "Order intake module is shallow — interface nearly matches the implementation." +- "Pricing leaks across the seam." +- "Deepen: one interface, one place to test." +- "Two adapters justify the seam: HTTP in prod, in-memory in tests." + +**Wins bullets** name the gain in glossary terms: *"locality: bugs concentrate in one module"*, *"leverage: one interface, N call sites"*, *"interface shrinks; implementation absorbs the wrappers"*. Don't write *"easier to maintain"* or *"cleaner code"* — those terms aren't in the glossary and don't earn their place. + +No hedging, no throat-clearing, no "it's worth noting that…". If a sentence could be a bullet, make it a bullet. If a bullet could be cut, cut it. If a term isn't in the `/codebase-design` glossary, reach for one that is before inventing a new one. diff --git a/.cursor/skills/improve-codebase-architecture/SKILL.md b/.cursor/skills/improve-codebase-architecture/SKILL.md new file mode 100644 index 0000000..b56969e --- /dev/null +++ b/.cursor/skills/improve-codebase-architecture/SKILL.md @@ -0,0 +1,71 @@ +--- +name: improve-codebase-architecture +description: Scan a codebase for deepening opportunities, present them as a visual HTML report, then grill through whichever one you pick. +disable-model-invocation: true +--- + +# Improve Codebase Architecture + +Surface architectural friction and propose **deepening opportunities** — refactors that turn shallow modules into deep ones. The aim is testability and AI-navigability. + +This command is _informed_ by the project's domain model and built on a shared design vocabulary: + +- Run the `/codebase-design` skill for the architecture vocabulary (**module**, **interface**, **depth**, **seam**, **adapter**, **leverage**, **locality**) and its principles (the deletion test, "the interface is the test surface", "one adapter = hypothetical seam, two = real"). Use these terms exactly in every suggestion — don't drift into "component," "service," "API," or "boundary." +- The domain language in `CONTEXT.md` gives names to good seams; ADRs in `docs/adr/` record decisions this command should not re-litigate. + +## Process + +### 1. Explore + +**Scope before you scan — YAGNI.** Deepening a module pays off by making future changes to it easier, so put extra weight on the parts of the codebase that have recently changed. Decide *where* to look before you look: + +- If the user named a direction — a module, a subsystem, a pain point — take it, and skip the inference below. +- Otherwise, walk back a good stretch of the commit history (`git log --oneline`) to find the codebase's hot spots — the files and areas that keep coming up — and let those paths pull your attention first. If the changes are scattered with no clear hot spot, widen the net. + +Read the project's domain glossary (`CONTEXT.md`) and any ADRs in the area you're touching first. + +Then use the Agent tool with `subagent_type=Explore` to walk the codebase. Don't follow rigid heuristics — explore organically and note where you experience friction: + +- Where does understanding one concept require bouncing between many small modules? +- Where are modules **shallow** — interface nearly as complex as the implementation? +- Where have pure functions been extracted just for testability, but the real bugs hide in how they're called (no **locality**)? +- Where do tightly-coupled modules leak across their seams? +- Which parts of the codebase are untested, or hard to test through their current interface? + +Apply the **deletion test** to anything you suspect is shallow: would deleting it concentrate complexity, or just move it? A "yes, concentrates" is the signal you want. + +### 2. Present candidates as an HTML report + +Write a self-contained HTML file to the OS temp directory so nothing lands in the repo. Resolve the temp dir from `$TMPDIR`, falling back to `/tmp` (or `%TEMP%` on Windows), and write to `/architecture-review-.html` so each run gets a fresh file. Open it for the user — `xdg-open ` on Linux, `open ` on macOS, `start ` on Windows — and tell them the absolute path. + +The report uses **Tailwind via CDN** for layout and styling, and **Mermaid via CDN** for diagrams where a graph/flow/sequence reliably communicates the structure. Mix Mermaid with hand-crafted CSS/SVG visuals — use Mermaid when relationships are graph-shaped (call graphs, dependencies, sequences), and hand-built divs/SVG when you want something more editorial (mass diagrams, cross-sections, collapse animations). Each candidate gets a **before/after visualisation**. Be visual. + +For each candidate, render a card with: + +- **Files** — which files/modules are involved +- **Problem** — why the current architecture is causing friction +- **Solution** — plain English description of what would change +- **Benefits** — explained in terms of locality and leverage, and how tests would improve +- **Before / After diagram** — side-by-side, custom-drawn, illustrating the shallowness and the deepening +- **Recommendation strength** — one of `Strong`, `Worth exploring`, `Speculative`, rendered as a badge + +End the report with a **Top recommendation** section: which candidate you'd tackle first and why. + +**Use CONTEXT.md vocabulary for the domain, and the `/codebase-design` vocabulary for the architecture.** If `CONTEXT.md` defines "Order," talk about "the Order intake module" — not "the FooBarHandler," and not "the Order service." + +**ADR conflicts**: if a candidate contradicts an existing ADR, only surface it when the friction is real enough to warrant revisiting the ADR. Mark it clearly in the card (e.g. a warning callout: _"contradicts ADR-0007 — but worth reopening because…"_). Don't list every theoretical refactor an ADR forbids. + +See [HTML-REPORT.md](HTML-REPORT.md) for the full HTML scaffold, diagram patterns, and styling guidance. + +Do NOT propose interfaces yet. After the file is written, ask the user: "Which of these would you like to explore?" + +### 3. Grilling loop + +Once the user picks a candidate, run the `/grilling` skill to walk the decision tree with them — constraints, dependencies, the shape of the deepened module, what sits behind the seam, what tests survive. + +Side effects happen inline as decisions crystallize — run the `/domain-modeling` skill to keep the domain model current as you go: + +- **Naming a deepened module after a concept not in `CONTEXT.md`?** Add the term to `CONTEXT.md`. Create the file lazily if it doesn't exist. +- **Sharpening a fuzzy term during the conversation?** Update `CONTEXT.md` right there. +- **User rejects the candidate with a load-bearing reason?** Offer an ADR, framed as: _"Want me to record this as an ADR so future architecture reviews don't re-suggest it?"_ Only offer when the reason would actually be needed by a future explorer to avoid re-suggesting the same thing — skip ephemeral reasons ("not worth it right now") and self-evident ones. +- **Want to explore alternative interfaces for the deepened module?** Run the `/codebase-design` skill and use its design-it-twice parallel sub-agent pattern. diff --git a/.cursor/skills/improve-codebase-architecture/agents/openai.yaml b/.cursor/skills/improve-codebase-architecture/agents/openai.yaml new file mode 100644 index 0000000..706fdca --- /dev/null +++ b/.cursor/skills/improve-codebase-architecture/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "Improve Codebase Architecture" + short_description: "Find and grill architecture improvements" +policy: + allow_implicit_invocation: false diff --git a/.cursor/skills/setup-matt-pocock-skills/SKILL.md b/.cursor/skills/setup-matt-pocock-skills/SKILL.md new file mode 100644 index 0000000..aecf855 --- /dev/null +++ b/.cursor/skills/setup-matt-pocock-skills/SKILL.md @@ -0,0 +1,116 @@ +--- +name: setup-matt-pocock-skills +description: Configure this repo for the engineering skills — set up its issue tracker, triage label vocabulary, and domain doc layout. Run once before first use of the other engineering skills. +disable-model-invocation: true +--- + +# Setup Matt Pocock's Skills + +Scaffold the per-repo configuration that the engineering skills assume: + +- **Issue tracker** — where issues live (GitHub by default; local markdown is also supported out of the box) +- **Triage labels** — the strings used for the five canonical triage roles +- **Domain docs** — where `CONTEXT.md` and ADRs live, and the consumer rules for reading them + +This is a prompt-driven skill, not a deterministic script. Explore, present what you found, confirm with the user, then write. + +## Process + +### 1. Explore + +Look at the current repo to understand its starting state. Read whatever exists; don't assume: + +- `git remote -v` and `.git/config` — is this a GitHub repo? Which one? +- `AGENTS.md` and `CLAUDE.md` at the repo root — does either exist? Is there already an `## Agent skills` section in either? +- `CONTEXT.md` and `CONTEXT-MAP.md` at the repo root +- `docs/adr/` and any `src/*/docs/adr/` directories +- `docs/agents/` — does this skill's prior output already exist? +- `.scratch/` — sign that a local-markdown issue tracker convention is already in use +- Is the `triage` skill installed? (a `triage` skill folder alongside this one, or `triage` in your available skills.) This decides whether Section B runs at all. +- Monorepo signals — a `pnpm-workspace.yaml`, a `workspaces` field in `package.json`, or a populated `packages/*` with its own `src/`. Present only in a genuinely large multi-package repo; their absence means single-context, which is almost every repo. + +### 2. Present findings and ask + +Summarise what's present and what's missing. Then take the sections in order — one section, one answer, then the next. + +Lead each section with the recommended answer so the user can accept it in a word. Give a one-line explainer only when the choice genuinely branches; skip the section entirely when exploration already settled it (Section B when `triage` isn't installed, Section C when there's no monorepo). + +**Section A — Issue tracker.** + +> Explainer: The "issue tracker" is where issues live for this repo. Skills like `to-tickets`, `triage`, `to-spec`, and `qa` read from and write to it — they need to know whether to call `gh issue create`, write a markdown file under `.scratch/`, or follow some other workflow you describe. Pick the place you actually track work for this repo. + +Default posture: these skills were designed for GitHub. If a `git remote` points at GitHub, propose that. If a `git remote` points at GitLab (`gitlab.com` or a self-hosted host), propose GitLab. Otherwise (or if the user prefers), offer: + +- **GitHub** — issues live in the repo's GitHub Issues (uses the `gh` CLI) +- **GitLab** — issues live in the repo's GitLab Issues (uses the [`glab`](https://gitlab.com/gitlab-org/cli) CLI) +- **Local markdown** — issues live as files under `.scratch//` in this repo (good for solo projects or repos without a remote) +- **Other** (Jira, Linear, etc.) — ask the user to describe the workflow in one paragraph; the skill will record it as freeform prose + +Record the choice in `docs/agents/issue-tracker.md`. The GitHub and GitLab templates carry a "PRs as a request surface" flag, defaulted **off** — leave it off and don't raise it; a user who wants external PRs in the triage queue can flip the flag in the file later. + +**Section B — Triage label vocabulary.** Skip this section entirely if the `triage` skill isn't installed (exploration told you) — an uninstalled skill needs no labels. + +If it is installed, ask exactly one question: + +> Do you want to keep the default triage labels? (recommended: **yes**) + +The defaults are the five canonical roles, each label string equal to its name: `needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `wontfix`. On **yes**, write them as-is. Only if the user says no — usually because their tracker already uses other names (e.g. `bug:triage` for `needs-triage`) — collect the overrides so `triage` applies existing labels instead of creating duplicates. + +**Section C — Domain docs.** Default to **single-context** — one `CONTEXT.md` + `docs/adr/` at the repo root. This fits almost every repo; write it without asking. + +Offer **multi-context** — a root `CONTEXT-MAP.md` pointing to per-context `CONTEXT.md` files — only when exploration found monorepo signals. Then confirm which layout they want. + +### 3. Confirm and edit + +Show the user a draft of: + +- The `## Agent skills` block to add to whichever of `CLAUDE.md` / `AGENTS.md` is being edited (see step 4 for selection rules) +- The contents of `docs/agents/issue-tracker.md`, `docs/agents/domain.md`, and `docs/agents/triage-labels.md` (the last only when `triage` is installed) + +Let them edit before writing. + +### 4. Write + +**Pick the file to edit:** + +- If `CLAUDE.md` exists, edit it. +- Else if `AGENTS.md` exists, edit it. +- If neither exists, ask the user which one to create — don't pick for them. + +Never create `AGENTS.md` when `CLAUDE.md` already exists (or vice versa) — always edit the one that's already there. + +If an `## Agent skills` block already exists in the chosen file, update its contents in-place rather than appending a duplicate. Don't overwrite user edits to the surrounding sections. + +The block: + +```markdown +## Agent skills + +### Issue tracker + +[one-line summary of where issues are tracked]. See `docs/agents/issue-tracker.md`. + +### Triage labels + +[one-line summary of the label vocabulary]. See `docs/agents/triage-labels.md`. + +### Domain docs + +[one-line summary of layout — "single-context" or "multi-context"]. See `docs/agents/domain.md`. +``` + +Include the `### Triage labels` sub-block, and write `docs/agents/triage-labels.md`, only when `triage` is installed and Section B ran. When it isn't, both are omitted. + +Then write the docs files using the seed templates in this skill folder as a starting point: + +- [issue-tracker-github.md](./issue-tracker-github.md) — GitHub issue tracker +- [issue-tracker-gitlab.md](./issue-tracker-gitlab.md) — GitLab issue tracker +- [issue-tracker-local.md](./issue-tracker-local.md) — local-markdown issue tracker +- [triage-labels.md](./triage-labels.md) — label mapping (only if `triage` is installed) +- [domain.md](./domain.md) — domain doc consumer rules + layout + +For "other" issue trackers, write `docs/agents/issue-tracker.md` from scratch using the user's description. + +### 5. Done + +Tell the user the setup is complete and which engineering skills will now read from these files. Mention they can edit `docs/agents/*.md` directly later — re-running this skill is only necessary if they want to switch issue trackers or restart from scratch. diff --git a/.cursor/skills/setup-matt-pocock-skills/agents/openai.yaml b/.cursor/skills/setup-matt-pocock-skills/agents/openai.yaml new file mode 100644 index 0000000..65a0da8 --- /dev/null +++ b/.cursor/skills/setup-matt-pocock-skills/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "Setup Matt Pocock Skills" + short_description: "Configure a repo for the skills" +policy: + allow_implicit_invocation: false diff --git a/.cursor/skills/setup-matt-pocock-skills/domain.md b/.cursor/skills/setup-matt-pocock-skills/domain.md new file mode 100644 index 0000000..b548c53 --- /dev/null +++ b/.cursor/skills/setup-matt-pocock-skills/domain.md @@ -0,0 +1,51 @@ +# Domain Docs + +How the engineering skills should consume this repo's domain documentation when exploring the codebase. + +## Before exploring, read these + +- **`CONTEXT.md`** at the repo root, or +- **`CONTEXT-MAP.md`** at the repo root if it exists — it points at one `CONTEXT.md` per context. Read each one relevant to the topic. +- **`docs/adr/`** — read ADRs that touch the area you're about to work in. In multi-context repos, also check `src//docs/adr/` for context-scoped decisions. + +If any of these files don't exist, **proceed silently**. Don't flag their absence; don't suggest creating them upfront. The `/domain-modeling` skill (reached via `/grill-with-docs` and `/improve-codebase-architecture`) creates them lazily when terms or decisions actually get resolved. + +## File structure + +Single-context repo (most repos): + +``` +/ +├── CONTEXT.md +├── docs/adr/ +│ ├── 0001-event-sourced-orders.md +│ └── 0002-postgres-for-write-model.md +└── src/ +``` + +Multi-context repo (presence of `CONTEXT-MAP.md` at the root): + +``` +/ +├── CONTEXT-MAP.md +├── docs/adr/ ← system-wide decisions +└── src/ + ├── ordering/ + │ ├── CONTEXT.md + │ └── docs/adr/ ← context-specific decisions + └── billing/ + ├── CONTEXT.md + └── docs/adr/ +``` + +## Use the glossary's vocabulary + +When your output names a domain concept (in an issue title, a refactor proposal, a hypothesis, a test name), use the term as defined in `CONTEXT.md`. Don't drift to synonyms the glossary explicitly avoids. + +If the concept you need isn't in the glossary yet, that's a signal — either you're inventing language the project doesn't use (reconsider) or there's a real gap (note it for `/domain-modeling`). + +## Flag ADR conflicts + +If your output contradicts an existing ADR, surface it explicitly rather than silently overriding: + +> _Contradicts ADR-0007 (event-sourced orders) — but worth reopening because…_ diff --git a/.cursor/skills/setup-matt-pocock-skills/issue-tracker-github.md b/.cursor/skills/setup-matt-pocock-skills/issue-tracker-github.md new file mode 100644 index 0000000..82cfbf5 --- /dev/null +++ b/.cursor/skills/setup-matt-pocock-skills/issue-tracker-github.md @@ -0,0 +1,45 @@ +# Issue tracker: GitHub + +Issues and PRDs for this repo live as GitHub issues. Use the `gh` CLI for all operations. + +## Conventions + +- **Create an issue**: `gh issue create --title "..." --body "..."`. Use a heredoc for multi-line bodies. +- **Read an issue**: `gh issue view --comments`, filtering comments by `jq` and also fetching labels. +- **List issues**: `gh issue list --state open --json number,title,body,labels,comments --jq '[.[] | {number, title, body, labels: [.labels[].name], comments: [.comments[].body]}]'` with appropriate `--label` and `--state` filters. +- **Comment on an issue**: `gh issue comment --body "..."` +- **Apply / remove labels**: `gh issue edit --add-label "..."` / `--remove-label "..."` +- **Close**: `gh issue close --comment "..."` + +Infer the repo from `git remote -v` — `gh` does this automatically when run inside a clone. + +## Pull requests as a triage surface + +**PRs as a request surface: no.** _(Set to `yes` if this repo treats external PRs as feature requests; `/triage` reads this flag.)_ + +When set to `yes`, PRs run through the same labels and states as issues, using the `gh pr` equivalents: + +- **Read a PR**: `gh pr view --comments` and `gh pr diff ` for the diff. +- **List external PRs for triage**: `gh pr list --state open --json number,title,body,labels,author,authorAssociation,comments` then keep only `authorAssociation` of `CONTRIBUTOR`, `FIRST_TIME_CONTRIBUTOR`, or `NONE` (drop `OWNER`/`MEMBER`/`COLLABORATOR`). +- **Comment / label / close**: `gh pr comment`, `gh pr edit --add-label`/`--remove-label`, `gh pr close`. + +GitHub shares one number space across issues and PRs, so a bare `#42` may be either — resolve with `gh pr view 42` and fall back to `gh issue view 42`. + +## When a skill says "publish to the issue tracker" + +Create a GitHub issue. + +## When a skill says "fetch the relevant ticket" + +Run `gh issue view --comments`. + +## Wayfinding operations + +Used by `/wayfinder`. The **map** is a single issue with **child** issues as tickets. + +- **Map**: a single issue labelled `wayfinder:map`, holding the Notes / Decisions-so-far / Fog body. `gh issue create --label wayfinder:map`. +- **Child ticket**: an issue linked to the map as a GitHub sub-issue (`gh api` on the sub-issues endpoint). Where sub-issues aren't enabled, add the child to a task list in the map body and put `Part of #` at the top of the child body. Labels: `wayfinder:` (`research`/`prototype`/`grilling`/`task`). Once claimed, the ticket is assigned to the driving dev. +- **Blocking**: GitHub's **native issue dependencies** — the canonical, UI-visible representation. Add an edge with `gh api --method POST repos///issues//dependencies/blocked_by -F issue_id=`, where `` is the blocker's numeric **database id** (`gh api repos///issues/ --jq .id`, _not_ the `#number` or `node_id`). GitHub reports `issue_dependencies_summary.blocked_by` (open blockers only — the live gate). Where dependencies aren't available, fall back to a `Blocked by: #, #` line at the top of the child body. A ticket is unblocked when every blocker is closed. +- **Frontier query**: list the map's open children (`gh issue list --state open`, scoped to the map's sub-issues / task list), drop any with an open blocker (`issue_dependencies_summary.blocked_by > 0`, or an open issue in the `Blocked by` line) or an assignee; first in map order wins. +- **Claim**: `gh issue edit --add-assignee @me` — the session's first write. +- **Resolve**: `gh issue comment --body ""`, then `gh issue close `, then append a context pointer (gist + link) to the map's Decisions-so-far. diff --git a/.cursor/skills/setup-matt-pocock-skills/issue-tracker-gitlab.md b/.cursor/skills/setup-matt-pocock-skills/issue-tracker-gitlab.md new file mode 100644 index 0000000..8a54714 --- /dev/null +++ b/.cursor/skills/setup-matt-pocock-skills/issue-tracker-gitlab.md @@ -0,0 +1,46 @@ +# Issue tracker: GitLab + +Issues and PRDs for this repo live as GitLab issues. Use the [`glab`](https://gitlab.com/gitlab-org/cli) CLI for all operations. + +## Conventions + +- **Create an issue**: `glab issue create --title "..." --description "..."`. Use a heredoc for multi-line descriptions. Pass `--description -` to open an editor. +- **Read an issue**: `glab issue view --comments`. Use `-F json` for machine-readable output. +- **List issues**: `glab issue list -F json` with appropriate `--label` filters. +- **Comment on an issue**: `glab issue note --message "..."`. GitLab calls comments "notes". +- **Apply / remove labels**: `glab issue update --label "..."` / `--unlabel "..."`. Multiple labels can be comma-separated or by repeating the flag. +- **Close**: `glab issue close `. `glab issue close` does not accept a closing comment, so post the explanation first with `glab issue note --message "..."`, then close. +- **Merge requests**: GitLab calls PRs "merge requests". Use `glab mr create`, `glab mr view`, `glab mr note`, etc. — the same shape as `gh pr ...` with `mr` in place of `pr` and `note`/`--message` in place of `comment`/`--body`. + +Infer the repo from `git remote -v` — `glab` does this automatically when run inside a clone. + +## Merge requests as a triage surface + +**MRs as a request surface: no.** _(Set to `yes` if this repo treats external merge requests as feature requests; `/triage` reads this flag.)_ + +When set to `yes`, MRs run through the same labels and states as issues, using the `glab mr` equivalents: + +- **Read an MR**: `glab mr view --comments` and `glab mr diff ` for the diff. +- **List external MRs for triage**: `glab mr list -F json`, then keep only MRs whose author is not a project member/owner (a contributor's MR, not a maintainer's in-flight work). +- **Comment / label / close**: `glab mr note`, `glab mr update --label`/`--unlabel`, `glab mr close`. + +Unlike GitHub, GitLab numbers issues and MRs separately, so `#42` is unambiguous once you know which surface the maintainer means. + +## When a skill says "publish to the issue tracker" + +Create a GitLab issue. + +## When a skill says "fetch the relevant ticket" + +Run `glab issue view --comments`. + +## Wayfinding operations + +Used by `/wayfinder`. The **map** is a single issue with **child** issues as tickets. + +- **Map**: a single issue labelled `wayfinder:map`, holding the Notes / Decisions-so-far / Fog body. `glab issue create --label wayfinder:map`. (On GitLab tiers with native epics, an epic may hold the map instead; a labelled issue works everywhere.) +- **Child ticket**: an issue carrying `Part of #` at the top of its description and labels `wayfinder:` (`research`/`prototype`/`grilling`/`task`). Once claimed, the ticket is assigned to the driving dev. +- **Blocking**: GitLab's **native blocking link** — the canonical, UI-visible representation. Add it with the `/blocked_by #` quick action, posted as a note (`glab issue note --message "/blocked_by #"`). Native blocking links are a Premium/Ultimate feature; on the free tier (or where unavailable) fall back to a `Blocked by: #, #` line at the top of the description. A ticket is unblocked when every blocker is closed. +- **Frontier query**: `glab issue list -F json` scoped to the map's children, drop any with an open blocker — a native `blocked_by` link to an open issue (`glab api projects/:id/issues/:iid/links`), or an open issue in the `Blocked by` line — or an assignee; first in map order wins. +- **Claim**: `glab issue update --assignee @me` — the session's first write. +- **Resolve**: `glab issue note --message ""`, then `glab issue close `, then append a context pointer (gist + link) to the map's Decisions-so-far. diff --git a/.cursor/skills/setup-matt-pocock-skills/issue-tracker-local.md b/.cursor/skills/setup-matt-pocock-skills/issue-tracker-local.md new file mode 100644 index 0000000..6b124b3 --- /dev/null +++ b/.cursor/skills/setup-matt-pocock-skills/issue-tracker-local.md @@ -0,0 +1,30 @@ +# Issue tracker: Local Markdown + +Issues and specs (you may know a spec as a PRD) for this repo live as markdown files in `.scratch/`. + +## Conventions + +- One feature per directory: `.scratch//` +- The spec is `.scratch//spec.md` +- Implementation issues are one file per ticket at `.scratch//issues/-.md`, numbered from `01` — never a single combined tickets file +- Triage state is recorded as a `Status:` line near the top of each issue file (see `triage-labels.md` for the role strings) +- Comments and conversation history append to the bottom of the file under a `## Comments` heading + +## When a skill says "publish to the issue tracker" + +Create a new file under `.scratch//` (creating the directory if needed). + +## When a skill says "fetch the relevant ticket" + +Read the file at the referenced path. The user will normally pass the path or the issue number directly. + +## Wayfinding operations + +Used by `/wayfinder`. The **map** is a file with one **child** file per ticket. + +- **Map**: `.scratch//map.md` — the Notes / Decisions-so-far / Fog body. +- **Child ticket**: `.scratch//issues/NN-.md`, numbered from `01`, with the question in the body. A `Type:` line records the ticket type (`research`/`prototype`/`grilling`/`task`); a `Status:` line records `claimed`/`resolved`. +- **Blocking**: a `Blocked by: NN, NN` line near the top. A ticket is unblocked when every file it lists is `resolved`. +- **Frontier**: scan `.scratch//issues/` for files that are open, unblocked, and unclaimed; first by number wins. +- **Claim**: set `Status: claimed` and save before any work. +- **Resolve**: append the answer under an `## Answer` heading, set `Status: resolved`, then append a context pointer (gist + link) to the map's Decisions-so-far in `map.md`. diff --git a/.cursor/skills/setup-matt-pocock-skills/triage-labels.md b/.cursor/skills/setup-matt-pocock-skills/triage-labels.md new file mode 100644 index 0000000..b716855 --- /dev/null +++ b/.cursor/skills/setup-matt-pocock-skills/triage-labels.md @@ -0,0 +1,15 @@ +# Triage Labels + +The skills speak in terms of five canonical triage roles. This file maps those roles to the actual label strings used in this repo's issue tracker. + +| Label in mattpocock/skills | Label in our tracker | Meaning | +| -------------------------- | -------------------- | ---------------------------------------- | +| `needs-triage` | `needs-triage` | Maintainer needs to evaluate this issue | +| `needs-info` | `needs-info` | Waiting on reporter for more information | +| `ready-for-agent` | `ready-for-agent` | Fully specified, ready for an AFK agent | +| `ready-for-human` | `ready-for-human` | Requires human implementation | +| `wontfix` | `wontfix` | Will not be actioned | + +When a skill mentions a role (e.g. "apply the AFK-ready triage label"), use the corresponding label string from this table. + +Edit the right-hand column to match whatever vocabulary you actually use. diff --git a/.cursor/skills/tdd/SKILL.md b/.cursor/skills/tdd/SKILL.md new file mode 100644 index 0000000..9a2e1d2 --- /dev/null +++ b/.cursor/skills/tdd/SKILL.md @@ -0,0 +1,36 @@ +--- +name: tdd +description: Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests. +--- + +# Test-Driven Development + +TDD is the red → green loop. This skill is the reference that makes that loop produce tests worth keeping: what a good test is, where tests go, the anti-patterns, and the rules of the loop. Every section applies on every cycle — consult them before and during the loop, not after. + +When exploring the codebase, read `CONTEXT.md` (if it exists) so test names and interface vocabulary match the project's domain language, and respect ADRs in the area you're touching. + +## What a good test is + +Tests verify behavior through public interfaces, not implementation details. Code can change entirely; tests shouldn't. A good test reads like a specification — "user can checkout with valid cart" tells you exactly what capability exists — and survives refactors because it doesn't care about internal structure. + +See [tests.md](tests.md) for examples and [mocking.md](mocking.md) for mocking guidelines. + +## Seams — where tests go + +A **seam** is the public boundary you test at: the interface where you observe behavior without reaching inside. Tests live at seams, never against internals. + +**Test only at pre-agreed seams.** Before writing any test, write down the seams under test and confirm them with the user. No test is written at an unconfirmed seam. You can't test everything — agreeing the seams up front is how testing effort lands on the critical paths and complex logic instead of every edge case. + +Ask: "What's the public interface, and which seams should we test?" + +## Anti-patterns + +- **Implementation-coupled** — mocks internal collaborators, tests private methods, or verifies through a side channel (querying the database instead of using the interface). The tell: the test breaks when you refactor but behavior hasn't changed. +- **Tautological** — the assertion recomputes the expected value the way the code does (`expect(add(a, b)).toBe(a + b)`, a snapshot derived by hand the same way, a constant asserted equal to itself), so it passes by construction and can never disagree with the code. Expected values must come from an independent source of truth — a known-good literal, a worked example, the spec. +- **Horizontal slicing** — writing all tests first, then all implementation. Bulk tests verify _imagined_ behavior: you test the _shape_ of things rather than user-facing behavior, the tests go insensitive to real changes, and you commit to test structure before understanding the implementation. Work in **vertical slices** instead — one test → one implementation → repeat, each test a **tracer bullet** that responds to what the last cycle taught you. + +## Rules of the loop + +- **Red before green.** Write the failing test first, then only enough code to pass it. Don't anticipate future tests or add speculative features. +- **One slice at a time.** One seam, one test, one minimal implementation per cycle. +- **Refactoring is not part of the loop.** It belongs to the review stage (see the `code-review` skill), not the red → green implementation cycle. diff --git a/.cursor/skills/tdd/agents/openai.yaml b/.cursor/skills/tdd/agents/openai.yaml new file mode 100644 index 0000000..651b838 --- /dev/null +++ b/.cursor/skills/tdd/agents/openai.yaml @@ -0,0 +1,3 @@ +interface: + display_name: "TDD" + short_description: "Test-driven red-green-refactor" diff --git a/.cursor/skills/tdd/mocking.md b/.cursor/skills/tdd/mocking.md new file mode 100644 index 0000000..71cbfee --- /dev/null +++ b/.cursor/skills/tdd/mocking.md @@ -0,0 +1,59 @@ +# When to Mock + +Mock at **system boundaries** only: + +- External APIs (payment, email, etc.) +- Databases (sometimes - prefer test DB) +- Time/randomness +- File system (sometimes) + +Don't mock: + +- Your own classes/modules +- Internal collaborators +- Anything you control + +## Designing for Mockability + +At system boundaries, design interfaces that are easy to mock: + +**1. Use dependency injection** + +Pass external dependencies in rather than creating them internally: + +```typescript +// Easy to mock +function processPayment(order, paymentClient) { + return paymentClient.charge(order.total); +} + +// Hard to mock +function processPayment(order) { + const client = new StripeClient(process.env.STRIPE_KEY); + return client.charge(order.total); +} +``` + +**2. Prefer SDK-style interfaces over generic fetchers** + +Create specific functions for each external operation instead of one generic function with conditional logic: + +```typescript +// GOOD: Each function is independently mockable +const api = { + getUser: (id) => fetch(`/users/${id}`), + getOrders: (userId) => fetch(`/users/${userId}/orders`), + createOrder: (data) => fetch('/orders', { method: 'POST', body: data }), +}; + +// BAD: Mocking requires conditional logic inside the mock +const api = { + fetch: (endpoint, options) => fetch(endpoint, options), +}; +``` + +The SDK approach means: +- Each mock returns one specific shape +- No conditional logic in test setup +- Easier to see which endpoints a test exercises +- Type safety per endpoint diff --git a/.cursor/skills/tdd/tests.md b/.cursor/skills/tdd/tests.md new file mode 100644 index 0000000..7ab8647 --- /dev/null +++ b/.cursor/skills/tdd/tests.md @@ -0,0 +1,77 @@ +# Good and Bad Tests + +## Good Tests + +**Integration-style**: Test through real interfaces, not mocks of internal parts. + +```typescript +// GOOD: Tests observable behavior +test("user can checkout with valid cart", async () => { + const cart = createCart(); + cart.add(product); + const result = await checkout(cart, paymentMethod); + expect(result.status).toBe("confirmed"); +}); +``` + +Characteristics: + +- Tests behavior users/callers care about +- Uses public API only +- Survives internal refactors +- Describes WHAT, not HOW +- One logical assertion per test + +## Bad Tests + +**Implementation-detail tests**: Coupled to internal structure. + +```typescript +// BAD: Tests implementation details +test("checkout calls paymentService.process", async () => { + const mockPayment = jest.mock(paymentService); + await checkout(cart, payment); + expect(mockPayment.process).toHaveBeenCalledWith(cart.total); +}); +``` + +Red flags: + +- Mocking internal collaborators +- Testing private methods +- Asserting on call counts/order +- Test breaks when refactoring without behavior change +- Test name describes HOW not WHAT +- Verifying through external means instead of interface + +```typescript +// BAD: Bypasses interface to verify +test("createUser saves to database", async () => { + await createUser({ name: "Alice" }); + const row = await db.query("SELECT * FROM users WHERE name = ?", ["Alice"]); + expect(row).toBeDefined(); +}); + +// GOOD: Verifies through interface +test("createUser makes user retrievable", async () => { + const user = await createUser({ name: "Alice" }); + const retrieved = await getUser(user.id); + expect(retrieved.name).toBe("Alice"); +}); +``` + +**Tautological tests**: Expected value restates the implementation, so the test passes by construction. + +```typescript +// BAD: Expected value is recomputed the way the code computes it +test("calculateTotal sums line items", () => { + const items = [{ price: 10 }, { price: 5 }]; + const expected = items.reduce((sum, i) => sum + i.price, 0); + expect(calculateTotal(items)).toBe(expected); +}); + +// GOOD: Expected value is an independent, known literal +test("calculateTotal sums line items", () => { + expect(calculateTotal([{ price: 10 }, { price: 5 }])).toBe(15); +}); +``` diff --git a/.cursor/skills/to-spec/SKILL.md b/.cursor/skills/to-spec/SKILL.md new file mode 100644 index 0000000..f3cca8d --- /dev/null +++ b/.cursor/skills/to-spec/SKILL.md @@ -0,0 +1,75 @@ +--- +name: to-spec +description: Turn the current conversation into a spec and publish it to the project issue tracker — no interview, just synthesis of what you've already discussed. +disable-model-invocation: true +--- + +This skill takes the current conversation context and codebase understanding and produces a spec (you may know this document as a PRD). Do NOT interview the user — just synthesize what you already know. + +The issue tracker and triage label vocabulary should have been provided to you — run `/setup-matt-pocock-skills` if not. + +## Process + +1. Explore the repo to understand the current state of the codebase, if you haven't already. Use the project's domain glossary vocabulary throughout the spec, and respect any ADRs in the area you're touching. + +2. Sketch out the seams at which you're going to test the feature. Existing seams should be preferred to new ones. Use the highest seam possible. If new seams are needed, propose them at the highest point you can. The fewer seams across the codebase, the better - the ideal number is one. + +Check with the user that these seams match their expectations. + +3. Write the spec using the template below, then publish it to the project issue tracker. Apply the `ready-for-agent` triage label - no need for additional triage. + + + +## Problem Statement + +The problem that the user is facing, from the user's perspective. + +## Solution + +The solution to the problem, from the user's perspective. + +## User Stories + +A LONG, numbered list of user stories. Each user story should be in the format of: + +1. As an , I want a , so that + + +1. As a mobile bank customer, I want to see balance on my accounts, so that I can make better informed decisions about my spending + + +This list of user stories should be extremely extensive and cover all aspects of the feature. + +## Implementation Decisions + +A list of implementation decisions that were made. This can include: + +- The modules that will be built/modified +- The interfaces of those modules that will be modified +- Technical clarifications from the developer +- Architectural decisions +- Schema changes +- API contracts +- Specific interactions + +Do NOT include specific file paths or code snippets. They may end up being outdated very quickly. + +Exception: if a prototype produced a snippet that encodes a decision more precisely than prose can (state machine, reducer, schema, type shape), inline it within the relevant decision and note briefly that it came from a prototype. Trim to the decision-rich parts — not a working demo, just the important bits. + +## Testing Decisions + +A list of testing decisions that were made. Include: + +- A description of what makes a good test (only test external behavior, not implementation details) +- Which modules will be tested +- Prior art for the tests (i.e. similar types of tests in the codebase) + +## Out of Scope + +A description of the things that are out of scope for this spec. + +## Further Notes + +Any further notes about the feature. + + diff --git a/.cursor/skills/to-spec/agents/openai.yaml b/.cursor/skills/to-spec/agents/openai.yaml new file mode 100644 index 0000000..549e6f7 --- /dev/null +++ b/.cursor/skills/to-spec/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "To Spec" + short_description: "Turn a conversation into a spec" +policy: + allow_implicit_invocation: false diff --git a/.cursor/skills/to-tickets/SKILL.md b/.cursor/skills/to-tickets/SKILL.md new file mode 100644 index 0000000..96deac5 --- /dev/null +++ b/.cursor/skills/to-tickets/SKILL.md @@ -0,0 +1,105 @@ +--- +name: to-tickets +description: Break a plan, spec, or the current conversation into a set of tracer-bullet tickets, each declaring its blocking edges, published to the configured tracker — edges as text in one file per ticket locally, or native blocking links on a real tracker. +disable-model-invocation: true +--- + +# To Tickets + +Break a plan, spec, or conversation into a set of **tickets** — tracer-bullet vertical slices, each declaring the tickets that **block** it. + +The issue tracker and triage label vocabulary should have been provided to you — run `/setup-matt-pocock-skills` if not. + +## Process + +### 1. Gather context + +Work from whatever is already in the conversation context. If the user passes a reference (a spec path, an issue number or URL) as an argument, fetch it and read its full body and comments. + +### 2. Explore the codebase (optional) + +If you have not already explored the codebase, do so to understand the current state of the code. Ticket titles and descriptions should use the project's domain glossary vocabulary, and respect ADRs in the area you're touching. + +Look for opportunities to prefactor the code to make the implementation easier. "Make the change easy, then make the easy change." + +### 3. Draft vertical slices + +Break the work into **tracer bullet** tickets. + + + +- Each slice cuts a narrow but COMPLETE path through every layer (schema, API, UI, tests) — vertical, NOT a horizontal slice of one layer +- A completed slice is demoable or verifiable on its own +- Each slice is sized to fit in a single fresh context window +- Any prefactoring should be done first + + + +Give each ticket its **blocking edges** — the other tickets that must complete before it can start. A ticket with no blockers can start immediately. + +**Wide refactors are the exception to vertical slicing.** A **wide refactor** is one mechanical change — rename a column, retype a shared symbol — whose **blast radius** fans across the whole codebase, so a single edit breaks thousands of call sites at once and no vertical slice can land green. Don't force it into a tracer bullet; sequence it as **expand–contract**. First expand: add the new form beside the old so nothing breaks. Then migrate the call sites over in batches sized by blast radius (per package, per directory), each batch its own ticket blocked by the expand, keeping CI green batch to batch because the old form still exists. Finally contract: delete the old form once no caller remains, in a ticket blocked by every migrate batch. When even the batches can't stay green alone, keep the sequence but let them share an integration branch that all block a final integrate-and-verify ticket — green is promised only there. + +### 4. Quiz the user + +Present the proposed breakdown as a numbered list. For each ticket, show: + +- **Title**: short descriptive name +- **Blocked by**: which other tickets (if any) must complete first +- **What it delivers**: the end-to-end behaviour this ticket makes work + +Ask the user: + +- Does the granularity feel right? (too coarse / too fine) +- Are the blocking edges correct — does each ticket only depend on tickets that genuinely gate it? +- Should any tickets be merged or split further? + +Iterate until the user approves the breakdown. + +### 5. Publish the tickets to the configured tracker + +Publish the approved tickets. **How** depends on the tracker `/setup-matt-pocock-skills` configured — the tickets are the same either way, only the shape of the blocking edges changes: + +- **Local files** → write one file per ticket under `.scratch//issues/-.md`, numbered from `01` in dependency order (blockers first). Each file's "Blocked by" lists the numbers/titles it depends on. Use the per-ticket file template below — one ticket per file, never a single combined file. +- **A real issue tracker (GitHub, Linear, …)** → publish one issue per ticket in dependency order (blockers first) so each ticket's blocking edges can reference real identifiers. Use the platform's native blocking / sub-issue relationship where it has one; otherwise set each ticket's "Blocked by" to the blocking issues. Apply the `ready-for-agent` triage label unless instructed otherwise — the tickets are agent-grabbable by construction. + +Work the **frontier**: any ticket whose blockers are all done. For a purely linear chain that means top to bottom. + +Do NOT close or modify any parent issue. + + + +# + +**What to build:** the end-to-end behaviour this ticket makes work, from the user's perspective — not a layer-by-layer implementation list. + +**Blocked by:** the numbers/titles of the tickets that gate this one, or "None — can start immediately". + +**Status:** ready-for-agent + +- [ ] Acceptance criterion 1 +- [ ] Acceptance criterion 2 + + + + + +## Parent + +A reference to the parent issue on the tracker (if the source was an existing issue, otherwise omit this section). + +## What to build + +The end-to-end behaviour this ticket makes work, from the user's perspective — not layer-by-layer implementation. + +## Acceptance criteria + +- [ ] Criterion 1 +- [ ] Criterion 2 + +## Blocked by + +- A reference to each blocking ticket, or "None — can start immediately". + + + +In either form, avoid specific file paths or code snippets — they go stale fast. Exception: if a prototype produced a snippet that encodes a decision more precisely than prose can (state machine, reducer, schema, type shape), inline it and note briefly that it came from a prototype. Trim to the decision-rich parts — not a working demo, just the important bits. diff --git a/.cursor/skills/to-tickets/agents/openai.yaml b/.cursor/skills/to-tickets/agents/openai.yaml new file mode 100644 index 0000000..24605a5 --- /dev/null +++ b/.cursor/skills/to-tickets/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "To Tickets" + short_description: "Split a plan into tracer-bullet tickets" +policy: + allow_implicit_invocation: false diff --git a/.cursor/skills/vercel-composition-patterns/AGENTS.md b/.cursor/skills/vercel-composition-patterns/AGENTS.md new file mode 100644 index 0000000..558bf9a --- /dev/null +++ b/.cursor/skills/vercel-composition-patterns/AGENTS.md @@ -0,0 +1,946 @@ +# React Composition Patterns + +**Version 1.0.0** +Engineering +January 2026 + +> **Note:** +> This document is mainly for agents and LLMs to follow when maintaining, +> generating, or refactoring React codebases using composition. Humans +> may also find it useful, but guidance here is optimized for automation +> and consistency by AI-assisted workflows. + +--- + +## Abstract + +Composition patterns for building flexible, maintainable React components. Avoid boolean prop proliferation by using compound components, lifting state, and composing internals. These patterns make codebases easier for both humans and AI agents to work with as they scale. + +--- + +## Table of Contents + +1. [Component Architecture](#1-component-architecture) — **HIGH** + - 1.1 [Avoid Boolean Prop Proliferation](#11-avoid-boolean-prop-proliferation) + - 1.2 [Use Compound Components](#12-use-compound-components) +2. [State Management](#2-state-management) — **MEDIUM** + - 2.1 [Decouple State Management from UI](#21-decouple-state-management-from-ui) + - 2.2 [Define Generic Context Interfaces for Dependency Injection](#22-define-generic-context-interfaces-for-dependency-injection) + - 2.3 [Lift State into Provider Components](#23-lift-state-into-provider-components) +3. [Implementation Patterns](#3-implementation-patterns) — **MEDIUM** + - 3.1 [Create Explicit Component Variants](#31-create-explicit-component-variants) + - 3.2 [Prefer Composing Children Over Render Props](#32-prefer-composing-children-over-render-props) +4. [React 19 APIs](#4-react-19-apis) — **MEDIUM** + - 4.1 [React 19 API Changes](#41-react-19-api-changes) + +--- + +## 1. Component Architecture + +**Impact: HIGH** + +Fundamental patterns for structuring components to avoid prop +proliferation and enable flexible composition. + +### 1.1 Avoid Boolean Prop Proliferation + +**Impact: CRITICAL (prevents unmaintainable component variants)** + +Don't add boolean props like `isThread`, `isEditing`, `isDMThread` to customize + +component behavior. Each boolean doubles possible states and creates + +unmaintainable conditional logic. Use composition instead. + +**Incorrect: boolean props create exponential complexity** + +```tsx +function Composer({ + onSubmit, + isThread, + channelId, + isDMThread, + dmId, + isEditing, + isForwarding, +}: Props) { + return ( +
    +
    + + {isDMThread ? ( + + ) : isThread ? ( + + ) : null} + {isEditing ? ( + + ) : isForwarding ? ( + + ) : ( + + )} +