diff --git a/.github/workflows/create-deploy.yml b/.github/workflows/create-deploy.yml index 5ab1c8968f..fe4644a116 100644 --- a/.github/workflows/create-deploy.yml +++ b/.github/workflows/create-deploy.yml @@ -12,7 +12,7 @@ jobs: - name: setup node uses: actions/setup-node@v3 with: - node-version: 20 + node-version: 24 - name: Npm Install run: | npm ci diff --git a/.github/workflows/desktop-linux-prod-test-pull.yml b/.github/workflows/desktop-linux-prod-test-pull.yml index b2933728f9..1740ad3858 100644 --- a/.github/workflows/desktop-linux-prod-test-pull.yml +++ b/.github/workflows/desktop-linux-prod-test-pull.yml @@ -12,22 +12,19 @@ jobs: runs-on: ubuntu-22.04 timeout-minutes: 90 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: setup node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: 20 - - name: install Rust stable - uses: dtolnay/rust-toolchain@stable - with: - toolchain: 1.85.1 + node-version: 24 - name: install dependencies (ubuntu only) run: | sudo apt-get update - sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf - sudo apt install build-essential curl wget file libssl-dev libayatana-appindicator3-dev + sudo apt install build-essential curl wget file libssl-dev sudo apt-get install xvfb + # libfuse2 is required for AppImages to run + sudo apt-get install libfuse2 - name: build phoenix dist-test env: @@ -50,50 +47,50 @@ jobs: npm ci npm run releaseDistTestDebug - - name: Run tauri unit tests - uses: nick-fields/retry@v2 + - name: Run desktop unit tests + uses: nick-fields/retry@v3 id: linuxRunUnit continue-on-error: true with: timeout_minutes: 12 max_attempts: 3 - command: xvfb-run ../phoenix-desktop/src-tauri/target/debug/phoenix-test --run-tests=unit -q + command: xvfb-run ../phoenix-desktop/src-electron/dist/phoenix-test-runner.appimage --run-tests=unit -q - - name: Run tauri integration tests - uses: nick-fields/retry@v2 + - name: Run desktop integration tests + uses: nick-fields/retry@v3 id: linuxRunIntegration continue-on-error: true with: timeout_minutes: 12 max_attempts: 3 - command: xvfb-run ../phoenix-desktop/src-tauri/target/debug/phoenix-test --run-tests=integration -q + command: xvfb-run ../phoenix-desktop/src-electron/dist/phoenix-test-runner.appimage --run-tests=integration -q - - name: Run tauri mainview tests - uses: nick-fields/retry@v2 + - name: Run desktop mainview tests + uses: nick-fields/retry@v3 id: linuxRunMainview continue-on-error: true with: timeout_minutes: 12 max_attempts: 3 - command: xvfb-run ../phoenix-desktop/src-tauri/target/debug/phoenix-test --run-tests=mainview -q + command: xvfb-run ../phoenix-desktop/src-electron/dist/phoenix-test-runner.appimage --run-tests=mainview -q - - name: Run tauri livepreview tests - uses: nick-fields/retry@v2 + - name: Run desktop livepreview tests + uses: nick-fields/retry@v3 id: linuxRunLivepreview continue-on-error: true with: timeout_minutes: 12 max_attempts: 3 - command: xvfb-run ../phoenix-desktop/src-tauri/target/debug/phoenix-test --run-tests=livepreview -q + command: xvfb-run ../phoenix-desktop/src-electron/dist/phoenix-test-runner.appimage --run-tests=livepreview -q - - name: Run tauri LegacyInteg tests - uses: nick-fields/retry@v2 + - name: Run desktop LegacyInteg tests + uses: nick-fields/retry@v3 id: linuxRunLegacyInteg continue-on-error: true with: timeout_minutes: 20 max_attempts: 3 - command: xvfb-run ../phoenix-desktop/src-tauri/target/debug/phoenix-test --run-tests=LegacyInteg -q + command: xvfb-run ../phoenix-desktop/src-electron/dist/phoenix-test-runner.appimage --run-tests=LegacyInteg -q - name: Fail on test runs failed in Linux if: steps.linuxRunUnit.outcome == 'failure' || steps.linuxRunIntegration.outcome == 'failure' || steps.linuxRunMainview.outcome == 'failure' || steps.linuxRunLivepreview.outcome == 'failure' || steps.linuxRunLegacyInteg.outcome == 'failure' @@ -101,18 +98,18 @@ jobs: echo "Linux tests failed, marking step as failed" echo "Failed tests:" if [ "${{ steps.linuxRunUnit.outcome }}" == "failure" ]; then - echo "- Run tauri unit tests" + echo "- Run desktop unit tests" fi if [ "${{ steps.linuxRunIntegration.outcome }}" == "failure" ]; then - echo "- Run tauri integration tests" + echo "- Run desktop integration tests" fi if [ "${{ steps.linuxRunMainview.outcome }}" == "failure" ]; then - echo "- Run tauri mainview tests" + echo "- Run desktop mainview tests" fi if [ "${{ steps.linuxRunLivepreview.outcome }}" == "failure" ]; then - echo "- Run tauri livepreview tests" + echo "- Run desktop livepreview tests" fi if [ "${{ steps.linuxRunLegacyInteg.outcome }}" == "failure" ]; then - echo "- Run tauri LegacyInteg tests" + echo "- Run desktop LegacyInteg tests" fi exit 1 diff --git a/.github/workflows/desktop-linux-test-pull.yml b/.github/workflows/desktop-linux-test-pull.yml index b6f9aa0b27..228af7890e 100644 --- a/.github/workflows/desktop-linux-test-pull.yml +++ b/.github/workflows/desktop-linux-test-pull.yml @@ -12,22 +12,19 @@ jobs: runs-on: ubuntu-22.04 timeout-minutes: 90 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: setup node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: 20 - - name: install Rust stable - uses: dtolnay/rust-toolchain@stable - with: - toolchain: 1.85.1 + node-version: 24 - name: install dependencies (ubuntu only) run: | sudo apt-get update - sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf - sudo apt install build-essential curl wget file libssl-dev libayatana-appindicator3-dev + sudo apt install build-essential curl wget file libssl-dev sudo apt-get install xvfb + # libfuse2 is required for AppImages to run + sudo apt-get install libfuse2 - name: build phoenix dist-test env: @@ -50,50 +47,50 @@ jobs: npm ci npm run releaseDistTestDebug - - name: Run tauri unit tests - uses: nick-fields/retry@v2 + - name: Run desktop unit tests + uses: nick-fields/retry@v3 id: linuxRunUnit continue-on-error: true with: timeout_minutes: 12 max_attempts: 3 - command: xvfb-run ../phoenix-desktop/src-tauri/target/debug/phoenix-test --run-tests=unit -q + command: xvfb-run ../phoenix-desktop/src-electron/dist/phoenix-test-runner.appimage --run-tests=unit -q - - name: Run tauri integration tests - uses: nick-fields/retry@v2 + - name: Run desktop integration tests + uses: nick-fields/retry@v3 id: linuxRunIntegration continue-on-error: true with: timeout_minutes: 12 max_attempts: 3 - command: xvfb-run ../phoenix-desktop/src-tauri/target/debug/phoenix-test --run-tests=integration -q + command: xvfb-run ../phoenix-desktop/src-electron/dist/phoenix-test-runner.appimage --run-tests=integration -q - - name: Run tauri mainview tests - uses: nick-fields/retry@v2 + - name: Run desktop mainview tests + uses: nick-fields/retry@v3 id: linuxRunMainview continue-on-error: true with: timeout_minutes: 12 max_attempts: 3 - command: xvfb-run ../phoenix-desktop/src-tauri/target/debug/phoenix-test --run-tests=mainview -q + command: xvfb-run ../phoenix-desktop/src-electron/dist/phoenix-test-runner.appimage --run-tests=mainview -q - - name: Run tauri livepreview tests - uses: nick-fields/retry@v2 + - name: Run desktop livepreview tests + uses: nick-fields/retry@v3 id: linuxRunLivepreview continue-on-error: true with: timeout_minutes: 12 max_attempts: 3 - command: xvfb-run ../phoenix-desktop/src-tauri/target/debug/phoenix-test --run-tests=livepreview -q + command: xvfb-run ../phoenix-desktop/src-electron/dist/phoenix-test-runner.appimage --run-tests=livepreview -q - - name: Run tauri LegacyInteg tests - uses: nick-fields/retry@v2 + - name: Run desktop LegacyInteg tests + uses: nick-fields/retry@v3 id: linuxRunLegacyInteg continue-on-error: true with: timeout_minutes: 20 max_attempts: 3 - command: xvfb-run ../phoenix-desktop/src-tauri/target/debug/phoenix-test --run-tests=LegacyInteg -q + command: xvfb-run ../phoenix-desktop/src-electron/dist/phoenix-test-runner.appimage --run-tests=LegacyInteg -q - name: Fail on test runs failed in Linux if: steps.linuxRunUnit.outcome == 'failure' || steps.linuxRunIntegration.outcome == 'failure' || steps.linuxRunMainview.outcome == 'failure' || steps.linuxRunLivepreview.outcome == 'failure' || steps.linuxRunLegacyInteg.outcome == 'failure' @@ -101,18 +98,18 @@ jobs: echo "Linux tests failed, marking step as failed" echo "Failed tests:" if [ "${{ steps.linuxRunUnit.outcome }}" == "failure" ]; then - echo "- Run tauri unit tests" + echo "- Run desktop unit tests" fi if [ "${{ steps.linuxRunIntegration.outcome }}" == "failure" ]; then - echo "- Run tauri integration tests" + echo "- Run desktop integration tests" fi if [ "${{ steps.linuxRunMainview.outcome }}" == "failure" ]; then - echo "- Run tauri mainview tests" + echo "- Run desktop mainview tests" fi if [ "${{ steps.linuxRunLivepreview.outcome }}" == "failure" ]; then - echo "- Run tauri livepreview tests" + echo "- Run desktop livepreview tests" fi if [ "${{ steps.linuxRunLegacyInteg.outcome }}" == "failure" ]; then - echo "- Run tauri LegacyInteg tests" + echo "- Run desktop LegacyInteg tests" fi exit 1 diff --git a/.github/workflows/desktop-mac-m1-prod-communityEd-test-pull.yml b/.github/workflows/desktop-mac-m1-prod-communityEd-test-pull.yml index 5b8525ce3a..3cf1ada28a 100644 --- a/.github/workflows/desktop-mac-m1-prod-communityEd-test-pull.yml +++ b/.github/workflows/desktop-mac-m1-prod-communityEd-test-pull.yml @@ -16,7 +16,7 @@ jobs: - name: setup node uses: actions/setup-node@v3 with: - node-version: 20 + node-version: 24 - name: install Rust stable uses: dtolnay/rust-toolchain@stable with: diff --git a/.github/workflows/desktop-mac-m1-prod-test-pull.yml b/.github/workflows/desktop-mac-m1-prod-test-pull.yml index 54a6f7bd88..9a23285c8d 100644 --- a/.github/workflows/desktop-mac-m1-prod-test-pull.yml +++ b/.github/workflows/desktop-mac-m1-prod-test-pull.yml @@ -16,7 +16,7 @@ jobs: - name: setup node uses: actions/setup-node@v3 with: - node-version: 20 + node-version: 24 - name: install Rust stable uses: dtolnay/rust-toolchain@stable with: diff --git a/.github/workflows/desktop-mac-m1-test-pull.yml b/.github/workflows/desktop-mac-m1-test-pull.yml index e51a98380c..b7f52a5e4e 100644 --- a/.github/workflows/desktop-mac-m1-test-pull.yml +++ b/.github/workflows/desktop-mac-m1-test-pull.yml @@ -16,7 +16,7 @@ jobs: - name: setup node uses: actions/setup-node@v3 with: - node-version: 20 + node-version: 24 - name: install Rust stable uses: dtolnay/rust-toolchain@stable with: diff --git a/.github/workflows/desktop-mac-test-pull.yml b/.github/workflows/desktop-mac-test-pull.yml index 98429cfeba..f47ed8e474 100644 --- a/.github/workflows/desktop-mac-test-pull.yml +++ b/.github/workflows/desktop-mac-test-pull.yml @@ -16,7 +16,7 @@ jobs: - name: setup node uses: actions/setup-node@v3 with: - node-version: 20 + node-version: 24 - name: install Rust stable uses: dtolnay/rust-toolchain@stable with: diff --git a/.github/workflows/desktop-windows-test-pull.yml b/.github/workflows/desktop-windows-test-pull.yml index 85c4c99c3c..b269da8d0f 100644 --- a/.github/workflows/desktop-windows-test-pull.yml +++ b/.github/workflows/desktop-windows-test-pull.yml @@ -16,7 +16,7 @@ jobs: - name: setup node uses: actions/setup-node@v3 with: - node-version: 20 + node-version: 24 - name: install Rust stable uses: dtolnay/rust-toolchain@stable with: diff --git a/.github/workflows/minor-version-bump.yml b/.github/workflows/minor-version-bump.yml index db2f0e11bd..e68fe3beb2 100644 --- a/.github/workflows/minor-version-bump.yml +++ b/.github/workflows/minor-version-bump.yml @@ -11,7 +11,7 @@ jobs: - name: setup node uses: actions/setup-node@v3 with: - node-version: 20 + node-version: 24 - uses: actions/checkout@v3 with: ref: main diff --git a/.github/workflows/patch-version-bump.yml b/.github/workflows/patch-version-bump.yml index dbb600c0d4..20d1641d34 100644 --- a/.github/workflows/patch-version-bump.yml +++ b/.github/workflows/patch-version-bump.yml @@ -11,7 +11,7 @@ jobs: - name: setup node uses: actions/setup-node@v3 with: - node-version: 20 + node-version: 24 - uses: actions/checkout@v3 with: ref: main diff --git a/.github/workflows/playwright-chromium-linux-community.yml b/.github/workflows/playwright-chromium-linux-community.yml index 11f0589913..2b846d7f2c 100644 --- a/.github/workflows/playwright-chromium-linux-community.yml +++ b/.github/workflows/playwright-chromium-linux-community.yml @@ -16,7 +16,7 @@ jobs: - name: setup node uses: actions/setup-node@v3 with: - node-version: 20 + node-version: 24 - name: Install dependencies run: npm ci - name: Build phoenix diff --git a/.github/workflows/playwright-chromium-linux.yml b/.github/workflows/playwright-chromium-linux.yml index 8c0e126342..01ec519bea 100644 --- a/.github/workflows/playwright-chromium-linux.yml +++ b/.github/workflows/playwright-chromium-linux.yml @@ -16,7 +16,7 @@ jobs: - name: setup node uses: actions/setup-node@v3 with: - node-version: 20 + node-version: 24 - name: Install dependencies run: npm ci - name: Build phoenix diff --git a/.github/workflows/playwright-chromium-macos.yml b/.github/workflows/playwright-chromium-macos.yml index 6cb1af1607..e2b35f5cb3 100644 --- a/.github/workflows/playwright-chromium-macos.yml +++ b/.github/workflows/playwright-chromium-macos.yml @@ -16,7 +16,7 @@ jobs: - name: setup node uses: actions/setup-node@v3 with: - node-version: 20 + node-version: 24 - name: Install dependencies run: npm ci - name: Build phoenix diff --git a/.github/workflows/playwright-chromium-windows-prod.yml b/.github/workflows/playwright-chromium-windows-prod.yml index 8a1087b938..723eb051c2 100644 --- a/.github/workflows/playwright-chromium-windows-prod.yml +++ b/.github/workflows/playwright-chromium-windows-prod.yml @@ -17,7 +17,7 @@ jobs: - name: setup node uses: actions/setup-node@v3 with: - node-version: 20 + node-version: 24 - name: Install dependencies run: npm ci - name: Build phoenix diff --git a/.github/workflows/playwright-chromium-windows.yml b/.github/workflows/playwright-chromium-windows.yml index 13cfec9396..e2bcc3d7aa 100644 --- a/.github/workflows/playwright-chromium-windows.yml +++ b/.github/workflows/playwright-chromium-windows.yml @@ -16,7 +16,7 @@ jobs: - name: setup node uses: actions/setup-node@v3 with: - node-version: 20 + node-version: 24 - name: Install dependencies run: npm ci - name: Build phoenix diff --git a/.github/workflows/playwright-firefox-linux.yml b/.github/workflows/playwright-firefox-linux.yml index 02e8e62465..8c2e334857 100644 --- a/.github/workflows/playwright-firefox-linux.yml +++ b/.github/workflows/playwright-firefox-linux.yml @@ -16,7 +16,7 @@ jobs: - name: setup node uses: actions/setup-node@v3 with: - node-version: 20 + node-version: 24 - name: Install dependencies run: npm ci - name: Build phoenix diff --git a/.github/workflows/playwright-firefox-macos.yml b/.github/workflows/playwright-firefox-macos.yml index 5b83ff378f..bb8940361b 100644 --- a/.github/workflows/playwright-firefox-macos.yml +++ b/.github/workflows/playwright-firefox-macos.yml @@ -19,7 +19,7 @@ # - name: setup node # uses: actions/setup-node@v3 # with: -# node-version: 20 +# node-version: 24 # - name: Install dependencies # run: npm ci # - name: Build phoenix diff --git a/.github/workflows/playwright-firefox-windows.yml b/.github/workflows/playwright-firefox-windows.yml index e225a123b4..1d8d38920f 100644 --- a/.github/workflows/playwright-firefox-windows.yml +++ b/.github/workflows/playwright-firefox-windows.yml @@ -16,7 +16,7 @@ jobs: - name: setup node uses: actions/setup-node@v3 with: - node-version: 20 + node-version: 24 - name: Install dependencies run: npm ci - name: Build phoenix diff --git a/.github/workflows/playwright-staging-prod.yml b/.github/workflows/playwright-staging-prod.yml index dd95030491..651ff58337 100644 --- a/.github/workflows/playwright-staging-prod.yml +++ b/.github/workflows/playwright-staging-prod.yml @@ -13,7 +13,7 @@ jobs: - name: setup node uses: actions/setup-node@v3 with: - node-version: 20 + node-version: 24 - name: Install dependencies run: npm ci - name: Build phoenix @@ -101,7 +101,7 @@ jobs: - name: setup node uses: actions/setup-node@v3 with: - node-version: 20 + node-version: 24 - name: Install dependencies run: npm ci - name: Build phoenix @@ -190,7 +190,7 @@ jobs: - name: setup node uses: actions/setup-node@v3 with: - node-version: 20 + node-version: 24 - name: Install dependencies run: npm ci - name: Build phoenix @@ -278,7 +278,7 @@ jobs: - name: setup node uses: actions/setup-node@v3 with: - node-version: 20 + node-version: 24 - name: Install dependencies run: npm ci - name: Build phoenix @@ -366,7 +366,7 @@ jobs: - name: setup node uses: actions/setup-node@v3 with: - node-version: 20 + node-version: 24 - name: Install dependencies run: npm ci - name: Build phoenix @@ -455,7 +455,7 @@ jobs: - name: setup node uses: actions/setup-node@v3 with: - node-version: 20 + node-version: 24 - name: Install dependencies run: npm ci - name: Build phoenix diff --git a/.github/workflows/prod-deploy.yml b/.github/workflows/prod-deploy.yml index 1c371e5057..155cde1002 100644 --- a/.github/workflows/prod-deploy.yml +++ b/.github/workflows/prod-deploy.yml @@ -12,7 +12,7 @@ jobs: - name: setup node uses: actions/setup-node@v3 with: - node-version: 20 + node-version: 24 - name: Npm Install run: | npm ci diff --git a/.github/workflows/pull_request_verify.yml b/.github/workflows/pull_request_verify.yml index c2068ddee1..17a5ea729a 100644 --- a/.github/workflows/pull_request_verify.yml +++ b/.github/workflows/pull_request_verify.yml @@ -12,7 +12,7 @@ jobs: - name: setup node uses: actions/setup-node@v3 with: - node-version: 20 + node-version: 24 - name: Npm Install run: | npm ci diff --git a/.github/workflows/staging-deploy.yml b/.github/workflows/staging-deploy.yml index 58b72072a6..ae0f37a5b4 100644 --- a/.github/workflows/staging-deploy.yml +++ b/.github/workflows/staging-deploy.yml @@ -12,7 +12,7 @@ jobs: - name: setup node uses: actions/setup-node@v3 with: - node-version: 20 + node-version: 24 - name: Npm Install run: | npm ci diff --git a/.github/workflows/tauri-deploy.yml b/.github/workflows/tauri-deploy.yml index 0db082ddfb..6b31c15197 100644 --- a/.github/workflows/tauri-deploy.yml +++ b/.github/workflows/tauri-deploy.yml @@ -17,7 +17,7 @@ jobs: - name: setup node uses: actions/setup-node@v3 with: - node-version: 20 + node-version: 24 - name: Npm Install run: | npm ci diff --git a/.gitignore b/.gitignore index da9257abfa..09a8da1989 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,16 @@ Thumbs.db /test/pro-test-suite.js /src/extensionsIntegrated/phoenix-pro +# ignore node_modules inside phoenix-builder-mcp +/phoenix-builder-mcp/node_modules + +# ignore MCP server runtime files +/phoenix-builder-mcp/.mcp-server.pid + +# ignore chrome extension build artifacts +/phoenix-builder-mcp/chrome_extension/build/ +/phoenix-builder-mcp/chrome_extension/*.zip + # ignore node_modules inside src /src/node_modules /src-node/node_modules diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000000..a2dc6bede8 --- /dev/null +++ b/.mcp.json @@ -0,0 +1,11 @@ +{ + "mcpServers": { + "phoenix-builder": { + "command": "node", + "args": ["phoenix-builder-mcp/index.js"], + "env": { + "PHOENIX_DESKTOP_PATH": "../phoenix-desktop" + } + } + } +} \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..cf905ef066 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,14 @@ +# Claude Code Instructions + +## Git Commits +- Use Conventional Commits format: `type(scope): description` (e.g. `fix: ...`, `feat: ...`, `chore: ...`). +- Keep commit subject lines concise; use the body for detail. +- Never include `Co-Authored-By` lines in commit messages. + +## Code Style +- 4-space indentation, never tabs. +- Always use semicolons. +- Brace style: (`if (x) {`), single-line blocks allowed. +- Always use curly braces for `if`/`else`/`for`/`while`. +- No trailing whitespace. +- Use `const` and `let` instead of `var`. diff --git a/docs/API-Reference/command/Commands.md b/docs/API-Reference/command/Commands.md index 0e2fef4988..e5b864be48 100644 --- a/docs/API-Reference/command/Commands.md +++ b/docs/API-Reference/command/Commands.md @@ -734,6 +734,12 @@ Opens support resources ## HELP\_GET\_PRO Opens Phoenix Pro page +**Kind**: global variable + + +## HELP\_CANCEL\_TRIAL +Cancels Phoenix Pro trial + **Kind**: global variable diff --git a/docs/API-Reference/utils/ValidationUtils.md b/docs/API-Reference/utils/ValidationUtils.md index 4c12d7aa6f..fdfc7a404d 100644 --- a/docs/API-Reference/utils/ValidationUtils.md +++ b/docs/API-Reference/utils/ValidationUtils.md @@ -18,7 +18,8 @@ Used to validate whether type of unknown value is an integer. ## isIntegerInRange(value, [lowerLimit], [upperLimit]) ⇒ boolean -Used to validate whether type of unknown value is an integer, and, if so, is it within the option lower and upper limits. +Used to validate whether type of unknown value is an integer, and, if so, +is it within the option lower and upper limits. **Kind**: global function **Returns**: boolean - true if value is an interger, and optionally in specified range. @@ -29,3 +30,18 @@ Used to validate whether type of unknown value is an integer, and, if so, is it | [lowerLimit] | number | Optional lower limit (inclusive) | | [upperLimit] | number | Optional upper limit (inclusive) | + + +## isWithinRange(value, [lowerLimit], [upperLimit]) ⇒ boolean +Used to validate whether type of unknown value is a number (including decimals), +and, if so, is it within the optional lower and upper limits. + +**Kind**: global function +**Returns**: boolean - true if value is a finite number, and optionally in specified range. + +| Param | Type | Description | +| --- | --- | --- | +| value | \* | Value for which to validate its type | +| [lowerLimit] | number | Optional lower limit (inclusive) | +| [upperLimit] | number | Optional upper limit (inclusive) | + diff --git a/docs/API-Reference/view/MainViewFactory.md b/docs/API-Reference/view/MainViewFactory.md index 51e1db4793..5678cf93d9 100644 --- a/docs/API-Reference/view/MainViewFactory.md +++ b/docs/API-Reference/view/MainViewFactory.md @@ -3,39 +3,80 @@ const MainViewFactory = brackets.getModule("view/MainViewFactory") ``` - + -## \_ -MainViewFactory is a singleton for managing view factories. Registering a view factory: ```js registerViewFactory({ canOpenFile: function (fullPath) { return (fullPath.slice(-4) === ".ico"); }, openFile: function(file, pane) { return createIconView(file, pane); } }); ``` The openFile method is used to open the file and construct a view of it. Implementation should add the view to the pane ```js function createIconView(file, pane) { // IconView will construct its DOM and append // it to pane.$el var view = new IconView(file, pane.$el); // Then tell the pane to add it to // its view map and show it pane.addView(view, true); return new $.Deferred().resolve().promise(); } ``` Factories should only create 1 view of a file per pane. Brackets currently only supports 1 view of a file open at a given time but that may change to allow the same file open in more than 1 pane. Therefore Factories can do a simple check to see if a view already exists and show it before creating a new one: ```js var view = pane.getViewForPath(file.fullPath); if (view) { pane.showView(view); } else { return createIconView(file, pane); } ``` +## view/MainViewFactory +MainViewFactory is a singleton for managing view factories. -**Kind**: global variable - +Registering a view factory: +```js + registerViewFactory({ + canOpenFile: function (fullPath) { + return (fullPath.slice(-4) === ".ico"); + }, + openFile: function(file, pane) { + return createIconView(file, pane); + } + }); +``` + The openFile method is used to open the file and construct + a view of it. Implementation should add the view to the pane +```js + function createIconView(file, pane) { + // IconView will construct its DOM and append + // it to pane.$el + var view = new IconView(file, pane.$el); + // Then tell the pane to add it to + // its view map and show it + pane.addView(view, true); + return new $.Deferred().resolve().promise(); + } +``` + Factories should only create 1 view of a file per pane. Brackets currently only supports 1 view of + a file open at a given time but that may change to allow the same file open in more than 1 pane. Therefore + Factories can do a simple check to see if a view already exists and show it before creating a new one: +```js + var view = pane.getViewForPath(file.fullPath); + if (view) { + pane.showView(view); + } else { + return createIconView(file, pane); + } +``` + + +* [view/MainViewFactory](#module_view/MainViewFactory) + * [.registerViewFactory(factory)](#module_view/MainViewFactory..registerViewFactory) + * [.findSuitableFactoryForPath(fullPath)](#module_view/MainViewFactory..findSuitableFactoryForPath) ⇒ Factory + * [.Factory](#module_view/MainViewFactory..Factory) : Object + + -## registerViewFactory(factory) +### view/MainViewFactory.registerViewFactory(factory) Registers a view factory -**Kind**: global function +**Kind**: inner method of [view/MainViewFactory](#module_view/MainViewFactory) | Param | Type | Description | | --- | --- | --- | -| factory | [Factory](#Factory) | The view factory to register. | +| factory | Factory | The view factory to register. | - + -## findSuitableFactoryForPath(fullPath) ⇒ [Factory](#Factory) +### view/MainViewFactory.findSuitableFactoryForPath(fullPath) ⇒ Factory Finds a factory that can open the specified file -**Kind**: global function -**Returns**: [Factory](#Factory) - A factory that can create a view for the path or undefined if there isn't one. +**Kind**: inner method of [view/MainViewFactory](#module_view/MainViewFactory) +**Returns**: Factory - A factory that can create a view for the path or undefined if there isn't one. | Param | Type | Description | | --- | --- | --- | | fullPath | string | The file to open. | - + -## Factory : Object -**Kind**: global typedef +### view/MainViewFactory.Factory : Object +**Kind**: inner typedef of [view/MainViewFactory](#module_view/MainViewFactory) **Properties** | Name | Type | Description | diff --git a/docs/API-Reference/view/MainViewManager.md b/docs/API-Reference/view/MainViewManager.md index f4a71dde3e..0d38ca3362 100644 --- a/docs/API-Reference/view/MainViewManager.md +++ b/docs/API-Reference/view/MainViewManager.md @@ -3,9 +3,9 @@ const MainViewManager = brackets.getModule("view/MainViewManager") ``` - + -## \_ +## view/MainViewManager MainViewManager manages the arrangement of all open panes as well as provides the controller logic behind all views in the MainView (e.g. ensuring that a file doesn't appear in 2 lists) @@ -56,226 +56,261 @@ This module dispatches several events: To listen for events, do something like this: (see EventDispatcher for details on this pattern) `MainViewManager.on("eventname", handler);` -**Kind**: global variable - -## EVENT\_CURRENT\_FILE\_CHANGE : string +* [view/MainViewManager](#module_view/MainViewManager) + * [.EVENT_CURRENT_FILE_CHANGE](#module_view/MainViewManager..EVENT_CURRENT_FILE_CHANGE) : string + * [.ALL_PANES](#module_view/MainViewManager..ALL_PANES) + * [.ACTIVE_PANE](#module_view/MainViewManager..ACTIVE_PANE) + * [.isExclusiveToPane(File)](#module_view/MainViewManager..isExclusiveToPane) ⇒ Object + * [.getActivePaneId()](#module_view/MainViewManager..getActivePaneId) ⇒ string + * [.focusActivePane()](#module_view/MainViewManager..focusActivePane) + * [.setActivePaneId(paneId)](#module_view/MainViewManager..setActivePaneId) + * [.getCurrentlyViewedFile(paneId)](#module_view/MainViewManager..getCurrentlyViewedFile) ⇒ File + * [.getCurrentlyViewedEditor(paneId)](#module_view/MainViewManager..getCurrentlyViewedEditor) ⇒ Editor + * [.getAllViewedEditors()](#module_view/MainViewManager..getAllViewedEditors) ⇒ Object + * [.getCurrentlyViewedPath(paneId)](#module_view/MainViewManager..getCurrentlyViewedPath) ⇒ string + * [.cacheScrollState(paneId)](#module_view/MainViewManager..cacheScrollState) + * [.restoreAdjustedScrollState(paneId, heightDelta)](#module_view/MainViewManager..restoreAdjustedScrollState) + * [.getWorkingSet(paneId)](#module_view/MainViewManager..getWorkingSet) ⇒ Array.<File> + * [.getAllOpenFiles()](#module_view/MainViewManager..getAllOpenFiles) ⇒ array.<File> + * [.getPaneIdList()](#module_view/MainViewManager..getPaneIdList) ⇒ array.<string> + * [.getWorkingSetSize(paneId)](#module_view/MainViewManager..getWorkingSetSize) ⇒ number + * [.getPaneTitle(paneId)](#module_view/MainViewManager..getPaneTitle) ⇒ string + * [.getPaneCount()](#module_view/MainViewManager..getPaneCount) ⇒ number + * [.findInAllWorkingSets(fullPath)](#module_view/MainViewManager..findInAllWorkingSets) ⇒ Object + * [.findInOpenPane(fullPath)](#module_view/MainViewManager..findInOpenPane) ⇒ Object + * [.findInWorkingSet(paneId, fullPath)](#module_view/MainViewManager..findInWorkingSet) ⇒ number + * [.findInWorkingSetByAddedOrder(paneId, fullPath)](#module_view/MainViewManager..findInWorkingSetByAddedOrder) ⇒ number + * [.findInWorkingSetByMRUOrder(paneId, fullPath)](#module_view/MainViewManager..findInWorkingSetByMRUOrder) ⇒ number + * [.addToWorkingSet(paneId, file, [index], [forceRedraw])](#module_view/MainViewManager..addToWorkingSet) + * [.addListToWorkingSet(paneId, fileList)](#module_view/MainViewManager..addListToWorkingSet) + * [.switchPaneFocus()](#module_view/MainViewManager..switchPaneFocus) + * [.traverseToNextViewByMRU(direction)](#module_view/MainViewManager..traverseToNextViewByMRU) ⇒ Object + * [.traverseToNextViewInListOrder(direction)](#module_view/MainViewManager..traverseToNextViewInListOrder) ⇒ Object + * [.beginTraversal()](#module_view/MainViewManager..beginTraversal) + * [.endTraversal()](#module_view/MainViewManager..endTraversal) + * [.setLayoutScheme(rows, columns)](#module_view/MainViewManager..setLayoutScheme) + * [.getLayoutScheme()](#module_view/MainViewManager..getLayoutScheme) ⇒ Object + + + +### view/MainViewManager.EVENT\_CURRENT\_FILE\_CHANGE : string Event current file change -**Kind**: global constant - +**Kind**: inner constant of [view/MainViewManager](#module_view/MainViewManager) + -## ALL\_PANES +### view/MainViewManager.ALL\_PANES Special paneId shortcut that can be used to specify that all panes should be targeted by the API. Not all APIs support this constnant. Check the API documentation before use. -**Kind**: global constant - +**Kind**: inner constant of [view/MainViewManager](#module_view/MainViewManager) + -## ACTIVE\_PANE +### view/MainViewManager.ACTIVE\_PANE Special paneId shortcut that can be used to specify that the API should target the focused pane only. All APIs support this shortcut. -**Kind**: global constant - +**Kind**: inner constant of [view/MainViewManager](#module_view/MainViewManager) + -## isExclusiveToPane(File) ⇒ Object +### view/MainViewManager.isExclusiveToPane(File) ⇒ Object Checks whether a file is listed exclusively in the provided pane -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) | Param | Type | Description | | --- | --- | --- | | File | File | the file | - + -## getActivePaneId() ⇒ string +### view/MainViewManager.getActivePaneId() ⇒ string Retrieves the currently active Pane Id -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: string - Active Pane's ID. - + -## focusActivePane() +### view/MainViewManager.focusActivePane() Focuses the current pane. If the current pane has a current view, then the pane will focus the view. -**Kind**: global function - +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) + -## setActivePaneId(paneId) +### view/MainViewManager.setActivePaneId(paneId) Switch active pane to the specified pane id (or ACTIVE_PANE/ALL_PANES, in which case this call does nothing). -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) | Param | Type | Description | | --- | --- | --- | | paneId | string | the id of the pane to activate | - + -## getCurrentlyViewedFile(paneId) ⇒ File +### view/MainViewManager.getCurrentlyViewedFile(paneId) ⇒ File Retrieves the currently viewed file of the specified paneId -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: File - File object of the currently viewed file, or null if there isn't one or there's no such pane | Param | Type | Description | | --- | --- | --- | | paneId | string | the id of the pane in which to retrieve the currently viewed file | - + -## getCurrentlyViewedEditor(paneId) ⇒ Editor +### view/MainViewManager.getCurrentlyViewedEditor(paneId) ⇒ Editor Retrieves the currently viewed editor of the specified paneId -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: Editor - currently editor, or null if there isn't one or there's no such pane | Param | Type | Description | | --- | --- | --- | | paneId | string | the id of the pane in which to retrieve the currently viewed editor | - + -## getAllViewedEditors() ⇒ Object +### view/MainViewManager.getAllViewedEditors() ⇒ Object Gets an array of editors open in panes with their pane IDs. Can return an empty array if no editors are open. -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: Object - An array of objects, each containing an editor and its corresponding pane ID. - + -## getCurrentlyViewedPath(paneId) ⇒ string +### view/MainViewManager.getCurrentlyViewedPath(paneId) ⇒ string Retrieves the currently viewed path of the pane specified by paneId -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: string - the path of the currently viewed file or null if there isn't one | Param | Type | Description | | --- | --- | --- | | paneId | string | the id of the pane in which to retrieve the currently viewed path | - + -## cacheScrollState(paneId) +### view/MainViewManager.cacheScrollState(paneId) Caches the specified pane's current scroll state If there was already cached state for the specified pane, it is discarded and overwritten -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) | Param | Type | Description | | --- | --- | --- | | paneId | string | id of the pane in which to cache the scroll state, ALL_PANES or ACTIVE_PANE | - + -## restoreAdjustedScrollState(paneId, heightDelta) +### view/MainViewManager.restoreAdjustedScrollState(paneId, heightDelta) Restores the scroll state from cache and applies the heightDelta The view implementation is responsible for applying or ignoring the heightDelta. This is used primarily when a modal bar opens to keep the editor from scrolling the current page out of view in order to maintain the appearance. The state is removed from the cache after calling this function. -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) | Param | Type | Description | | --- | --- | --- | | paneId | string | id of the pane in which to adjust the scroll state, ALL_PANES or ACTIVE_PANE | | heightDelta | number | delta H to apply to the scroll state | - + -## getWorkingSet(paneId) ⇒ Array.<File> +### view/MainViewManager.getWorkingSet(paneId) ⇒ Array.<File> Retrieves the WorkingSet for the given paneId not including temporary views -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) | Param | Type | Description | | --- | --- | --- | | paneId | string | id of the pane in which to get the view list, ALL_PANES or ACTIVE_PANE | - + -## getAllOpenFiles() ⇒ array.<File> +### view/MainViewManager.getAllOpenFiles() ⇒ array.<File> Retrieves the list of all open files including temporary views -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: array.<File> - the list of all open files in all open panes - + -## getPaneIdList() ⇒ array.<string> +### view/MainViewManager.getPaneIdList() ⇒ array.<string> Retrieves the list of all open pane ids -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: array.<string> - the list of all open panes - + -## getWorkingSetSize(paneId) ⇒ number +### view/MainViewManager.getWorkingSetSize(paneId) ⇒ number Retrieves the size of the selected pane's view list -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: number - the number of items in the specified pane | Param | Type | Description | | --- | --- | --- | | paneId | string | id of the pane in which to get the workingset size. Can use `ALL_PANES` or `ACTIVE_PANE` | - + -## getPaneTitle(paneId) ⇒ string +### view/MainViewManager.getPaneTitle(paneId) ⇒ string Retrieves the title to display in the workingset view -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: string - title | Param | Type | Description | | --- | --- | --- | | paneId | string | id of the pane in which to get the title | - + -## getPaneCount() ⇒ number +### view/MainViewManager.getPaneCount() ⇒ number Retrieves the number of panes -**Kind**: global function - +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) + -## findInAllWorkingSets(fullPath) ⇒ Object +### view/MainViewManager.findInAllWorkingSets(fullPath) ⇒ Object Finds all instances of the specified file in all working sets. If there is a temporary view of the file, it is not part of the result set -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: Object - an array of paneId/index records | Param | Type | Description | | --- | --- | --- | | fullPath | string | path of the file to find views of | - + -## findInOpenPane(fullPath) ⇒ Object -Returns the pane IDs and editors (if present) of the given file in any open and viewable pane. -If the same file is open in multiple panes, all matching panes will be returned. +### view/MainViewManager.findInOpenPane(fullPath) ⇒ Object +Returns the pane IDs and editors (if present) of the given file in any open and viewable pane. +If the same file is open in multiple panes, all matching panes will be returned. If not found in any panes, an empty array will be returned. -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: Object - An array of objects, each containing the pane ID and the corresponding editor, if present. | Param | Type | Description | | --- | --- | --- | | fullPath | string | The full path of the file to search for. | - + -## findInWorkingSet(paneId, fullPath) ⇒ number +### view/MainViewManager.findInWorkingSet(paneId, fullPath) ⇒ number Gets the index of the file matching fullPath in the workingset -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: number - index, -1 if not found. | Param | Type | Description | @@ -283,12 +318,12 @@ Gets the index of the file matching fullPath in the workingset | paneId | string | id of the pane in which to search or ALL_PANES or ACTIVE_PANE | | fullPath | string | full path of the file to search for | - + -## findInWorkingSetByAddedOrder(paneId, fullPath) ⇒ number +### view/MainViewManager.findInWorkingSetByAddedOrder(paneId, fullPath) ⇒ number Gets the index of the file matching fullPath in the added order workingset -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: number - index, -1 if not found. | Param | Type | Description | @@ -296,12 +331,12 @@ Gets the index of the file matching fullPath in the added order workingset | paneId | string | id of the pane in which to search or ALL_PANES or ACTIVE_PANE | | fullPath | string | full path of the file to search for | - + -## findInWorkingSetByMRUOrder(paneId, fullPath) ⇒ number +### view/MainViewManager.findInWorkingSetByMRUOrder(paneId, fullPath) ⇒ number Gets the index of the file matching fullPath in the MRU order workingset -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: number - index, -1 if not found. | Param | Type | Description | @@ -309,9 +344,9 @@ Gets the index of the file matching fullPath in the MRU order workingset | paneId | string | id of the pane in which to search or ALL_PANES or ACTIVE_PANE | | fullPath | string | full path of the file to search for | - + -## addToWorkingSet(paneId, file, [index], [forceRedraw]) +### view/MainViewManager.addToWorkingSet(paneId, file, [index], [forceRedraw]) Adds the given file to the end of the workingset, if it is not already there. This API does not create a view of the file, it just adds it to the working set Views of files in the working set are persisted and are not destroyed until the user @@ -319,7 +354,7 @@ Views of files in the working set are persisted and are not destroyed until the made the current view. If a File is already opened then the file is just made current and its view is shown. -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) | Param | Type | Description | | --- | --- | --- | @@ -328,30 +363,30 @@ Views of files in the working set are persisted and are not destroyed until the | [index] | number | Position to add to list (defaults to last); -1 is ignored | | [forceRedraw] | boolean | If true, a workingset change notification is always sent (useful if suppressRedraw was used with removeView() earlier) | - + -## addListToWorkingSet(paneId, fileList) +### view/MainViewManager.addListToWorkingSet(paneId, fileList) Adds the given file list to the end of the workingset. -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) | Param | Type | Description | | --- | --- | --- | | paneId | string | The id of the pane in which to add the file object to or ACTIVE_PANE | | fileList | Array.<File> | Array of files to add to the pane | - + -## switchPaneFocus() +### view/MainViewManager.switchPaneFocus() Switch between panes -**Kind**: global function - +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) + -## traverseToNextViewByMRU(direction) ⇒ Object +### view/MainViewManager.traverseToNextViewByMRU(direction) ⇒ Object Get the next or previous file in the MRU list. -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: Object - The File object of the next item in the traversal order or null if there aren't any files to traverse. May return current file if there are no other files to traverse. @@ -359,12 +394,12 @@ Get the next or previous file in the MRU list. | --- | --- | --- | | direction | number | Must be 1 or -1 to traverse forward or backward | - + -## traverseToNextViewInListOrder(direction) ⇒ Object +### view/MainViewManager.traverseToNextViewInListOrder(direction) ⇒ Object Get the next or previous file in list order. -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: Object - The File object of the next item in the traversal order or null if there aren't any files to traverse. May return current file if there are no other files to traverse. @@ -372,26 +407,26 @@ Get the next or previous file in list order. | --- | --- | --- | | direction | number | Must be 1 or -1 to traverse forward or backward | - + -## beginTraversal() +### view/MainViewManager.beginTraversal() Indicates that traversal has begun. Can be called any number of times. -**Kind**: global function - +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) + -## endTraversal() +### view/MainViewManager.endTraversal() Un-freezes the MRU list after one or more beginTraversal() calls. Whatever file is current is bumped to the front of the MRU list. -**Kind**: global function - +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) + -## setLayoutScheme(rows, columns) +### view/MainViewManager.setLayoutScheme(rows, columns) Changes the layout scheme -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Summay**: Rows or Columns may be 1 or 2 but both cannot be 2. 1x2, 2x1 or 1x1 are the legal values | Param | Type | Description | @@ -399,10 +434,10 @@ Changes the layout scheme | rows | number | (may be 1 or 2) | | columns | number | (may be 1 or 2) | - + -## getLayoutScheme() ⇒ Object +### view/MainViewManager.getLayoutScheme() ⇒ Object Retrieves the current layout scheme. -**Kind**: global function +**Kind**: inner method of [view/MainViewManager](#module_view/MainViewManager) **Returns**: Object - - An object containing the number of rows and columns in the layout. diff --git a/docs/API-Reference/view/Pane.md b/docs/API-Reference/view/Pane.md index 399b3df8e7..2ca1cdab71 100644 --- a/docs/API-Reference/view/Pane.md +++ b/docs/API-Reference/view/Pane.md @@ -3,59 +3,235 @@ const Pane = brackets.getModule("view/Pane") ``` - + -## Pane -**Kind**: global class +## view/Pane +Pane objects host views of files, editors, etc... Clients cannot access +Pane objects directly. Instead the implementation is protected by the +MainViewManager -- however View Factories are given a Pane object which +they can use to add views. References to Pane objects should not be kept +as they may be destroyed and removed from the DOM. + +To get a custom view, there are two components: + + 1) A View Factory + 2) A View Object + +View objects are anonymous object that have a particular interface. + +Views can be added to a pane but do not have to exist in the Pane object's view list. +Such views are "temporary views". Temporary views are not serialized with the Pane state +or reconstituted when the pane is serialized from disk. They are destroyed at the earliest +opportunity. + +Temporary views are added by calling `Pane.showView()` and passing it the view object. The view +will be destroyed when the next view is shown, the pane is mereged with another pane or the "Close All" +command is exectuted on the Pane. Temporary Editor Views do not contain any modifications and are +added to the workingset (and are no longer tempoary views) once the document has been modified. They +will remain in the working set until closed from that point on. + +Views that have a longer life span are added by calling addView to associate the view with a +filename in the _views object. These views are not destroyed until they are removed from the pane +by calling one of the following: removeView, removeViews, or _reset + +Pane Object Events: + + - viewListChange - Whenever there is a file change to a file in the working set. These 2 events: `DocumentManager.pathRemove` + and `DocumentManager.fileNameChange` will cause a `viewListChange` event so the WorkingSetView can update. + + - currentViewChange - Whenever the current view changes. + (e, newView:View, oldView:View) + + - viewDestroy - Whenever a view has been destroyed + (e, view:View) + +View Interface: + +The view is an anonymous object which has the following method signatures. see ImageViewer for an example or the sample +provided with Brackets `src/extensions/samples/BracketsConfigCentral` + +```js + { + $el:jQuery + getFile: function ():!File + updateLayout: function(forceRefresh:boolean) + destroy: function() + getScrollPos: function():*= + adjustScrollPos: function(state:Object=, heightDelta:number)= + notifyContainerChange: function()= + notifyVisibilityChange: function(boolean)= + focus:function()= + } +``` +When views are created they can be added to the pane by calling `pane.addView()`. +Views can be created and parented by attaching directly to `pane.$el` + + this._codeMirror = new CodeMirror(pane.$el, ...) + +Factories can create a view that's initially hidden by calling `pane.addView(view)` and passing `false` for the show parameter. +Hidden views can be later shown by calling `pane.showView(view)` + +`$el:jQuery!` + + property that stores the jQuery wrapped DOM element of the view. All views must have one so pane objects can manipulate the DOM + element when necessary (e.g. `showView`, `_reparent`, etc...) + +`getFile():File!` + + Called throughout the life of a View when the current file is queried by the system. + +`updateLayout(forceRefresh:boolean)` + + Called to notify the view that it should be resized to fit its parent container. This may be called several times + or only once. Views can ignore the `forceRefresh` flag. It is used for editor views to force a relayout of the editor + which probably isn't necessary for most views. Views should implement their html to be dynamic and not rely on this + function to be called whenever possible. + +`destroy()` + + Views must implement a destroy method to remove their DOM element at the very least. There is no default + implementation and views are hidden before this method is called. The Pane object doesn't make assumptions + about when it is safe to remove a node. In some instances other cleanup must take place before a the DOM + node is destroyed so the implementation details are left to the view. + + Views can implement a simple destroy by calling + + this.$el.remove() + + These members are optional and need not be implemented by Views + + getScrollPos() + adjustScrollPos() + + The system at various times will want to save and restore a view's scroll position. The data returned by `getScrollPos()` + is specific to the view and will be passed back to `adjustScrollPos()` when the scroll position needs to be restored. + + When Modal Bars are invoked, the system calls `getScrollPos()` so that the current scroll psotion of all visible Views can be cached. + That cached scroll position is later passed to `adjustScrollPos()` along with a height delta. The height delta is used to + scroll the view so that it doesn't appear to have "jumped" when invoking the Modal Bar. + + Height delta will be a positive when the Modal Bar is being shown and negative number when the Modal Bar is being hidden. + + `getViewState()` is another optional member that is used to cache a view's state when hiding or destroying a view or closing the project. + The data returned by this member is stored in `ViewStateManager` and is saved with the project. + + Views or View Factories are responsible for restoring the view state when the view of that file is created by recalling the cached state + + var view = createIconView(file, pane); + view.restoreViewState(ViewStateManager.getViewState(file.fullPath)); + + Notifications + The following optional methods receive notifications from the Pane object when certain events take place which affect the view: + +`notifyContainerChange()` + + Optional Notification callback called when the container changes. The view can perform any synchronization or state update + it needs to do when its parent container changes. + +`notifyVisiblityChange()` + + Optional Notification callback called when the view's vsibility changes. The view can perform any synchronization or + state update it needs to do when its visiblity state changes. + + +* [view/Pane](#module_view/Pane) + * [.Pane](#module_view/Pane..Pane) + * [new Pane(id, $container)](#new_module_view/Pane..Pane_new) + * [.id](#module_view/Pane..Pane+id) : string + * [.$container](#module_view/Pane..Pane+$container) : JQuery + * [.$el](#module_view/Pane..Pane+$el) : JQuery + * [.$header](#module_view/Pane..Pane+$header) : JQuery + * [.$headerText](#module_view/Pane..Pane+$headerText) : JQuery + * [.$headerFlipViewBtn](#module_view/Pane..Pane+$headerFlipViewBtn) : JQuery + * [.$headerCloseBtn](#module_view/Pane..Pane+$headerCloseBtn) : JQuery + * [.$content](#module_view/Pane..Pane+$content) : JQuery + * [.ITEM_NOT_FOUND](#module_view/Pane..Pane+ITEM_NOT_FOUND) + * [.ITEM_FOUND_NO_SORT](#module_view/Pane..Pane+ITEM_FOUND_NO_SORT) + * [.ITEM_FOUND_NEEDS_SORT](#module_view/Pane..Pane+ITEM_FOUND_NEEDS_SORT) + * [.mergeFrom(other)](#module_view/Pane..Pane+mergeFrom) + * [.destroy()](#module_view/Pane..Pane+destroy) + * [.getViewList()](#module_view/Pane..Pane+getViewList) ⇒ Array.<File> + * [.getViewListSize()](#module_view/Pane..Pane+getViewListSize) ⇒ number + * [.findInViewList(fullPath)](#module_view/Pane..Pane+findInViewList) ⇒ number + * [.findInViewListAddedOrder(fullPath)](#module_view/Pane..Pane+findInViewListAddedOrder) ⇒ number + * [.findInViewListMRUOrder(fullPath)](#module_view/Pane..Pane+findInViewListMRUOrder) ⇒ number + * [.reorderItem(file, [index], [force])](#module_view/Pane..Pane+reorderItem) ⇒ number + * [.addToViewList(file, [index])](#module_view/Pane..Pane+addToViewList) ⇒ number + * [.addListToViewList(fileList)](#module_view/Pane..Pane+addListToViewList) ⇒ Array.<File> + * [.makeViewMostRecent(file)](#module_view/Pane..Pane+makeViewMostRecent) + * [.sortViewList(compareFn)](#module_view/Pane..Pane+sortViewList) + * [.swapViewListIndexes(index1, index2)](#module_view/Pane..Pane+swapViewListIndexes) ⇒ boolean + * [.traverseViewListByMRU(direction, [current])](#module_view/Pane..Pane+traverseViewListByMRU) ⇒ File + * [.showInterstitial(show)](#module_view/Pane..Pane+showInterstitial) + * [.getViewForPath(path)](#module_view/Pane..Pane+getViewForPath) ⇒ boolean + * [.addView(view, show)](#module_view/Pane..Pane+addView) + * [.showView(view)](#module_view/Pane..Pane+showView) + * [.updateLayout(forceRefresh)](#module_view/Pane..Pane+updateLayout) + * [.getCurrentlyViewedFile()](#module_view/Pane..Pane+getCurrentlyViewedFile) ⇒ File + * [.getCurrentlyViewedEditor()](#module_view/Pane..Pane+getCurrentlyViewedEditor) ⇒ File + * [.getCurrentlyViewedPath()](#module_view/Pane..Pane+getCurrentlyViewedPath) ⇒ string + * [.destroyViewIfNotNeeded(view)](#module_view/Pane..Pane+destroyViewIfNotNeeded) + * [.removeView(file, suppressOpenNextFile, preventViewChange)](#module_view/Pane..Pane+removeView) ⇒ boolean + * [.removeViews(list)](#module_view/Pane..Pane+removeViews) ⇒ Array.<File> + * [.focus()](#module_view/Pane..Pane+focus) + * [.loadState(state)](#module_view/Pane..Pane+loadState) ⇒ jQuery.Promise + * [.saveState()](#module_view/Pane..Pane+saveState) ⇒ Object + * [.getScrollState()](#module_view/Pane..Pane+getScrollState) ⇒ Object + * [.restoreAndAdjustScrollState([state], [heightDelta])](#module_view/Pane..Pane+restoreAndAdjustScrollState) + + + +### view/Pane.Pane +**Kind**: inner class of [view/Pane](#module_view/Pane) **See**: [MainViewManager](MainViewManager) for more information -* [Pane](#Pane) - * [new Pane(id, $container)](#new_Pane_new) - * [.id](#Pane+id) : string - * [.$container](#Pane+$container) : JQuery - * [.$el](#Pane+$el) : JQuery - * [.$header](#Pane+$header) : JQuery - * [.$headerText](#Pane+$headerText) : JQuery - * [.$headerFlipViewBtn](#Pane+$headerFlipViewBtn) : JQuery - * [.$headerCloseBtn](#Pane+$headerCloseBtn) : JQuery - * [.$content](#Pane+$content) : JQuery - * [.ITEM_NOT_FOUND](#Pane+ITEM_NOT_FOUND) - * [.ITEM_FOUND_NO_SORT](#Pane+ITEM_FOUND_NO_SORT) - * [.ITEM_FOUND_NEEDS_SORT](#Pane+ITEM_FOUND_NEEDS_SORT) - * [.mergeFrom(other)](#Pane+mergeFrom) - * [.destroy()](#Pane+destroy) - * [.getViewList()](#Pane+getViewList) ⇒ Array.<File> - * [.getViewListSize()](#Pane+getViewListSize) ⇒ number - * [.findInViewList(fullPath)](#Pane+findInViewList) ⇒ number - * [.findInViewListAddedOrder(fullPath)](#Pane+findInViewListAddedOrder) ⇒ number - * [.findInViewListMRUOrder(fullPath)](#Pane+findInViewListMRUOrder) ⇒ number - * [.reorderItem(file, [index], [force])](#Pane+reorderItem) ⇒ number - * [.addToViewList(file, [index])](#Pane+addToViewList) ⇒ number - * [.addListToViewList(fileList)](#Pane+addListToViewList) ⇒ Array.<File> - * [.makeViewMostRecent(file)](#Pane+makeViewMostRecent) - * [.sortViewList(compareFn)](#Pane+sortViewList) - * [.swapViewListIndexes(index1, index2)](#Pane+swapViewListIndexes) ⇒ boolean - * [.traverseViewListByMRU(direction, [current])](#Pane+traverseViewListByMRU) ⇒ File - * [.showInterstitial(show)](#Pane+showInterstitial) - * [.getViewForPath(path)](#Pane+getViewForPath) ⇒ boolean - * [.addView(view, show)](#Pane+addView) - * [.showView(view)](#Pane+showView) - * [.updateLayout(forceRefresh)](#Pane+updateLayout) - * [.getCurrentlyViewedFile()](#Pane+getCurrentlyViewedFile) ⇒ File - * [.getCurrentlyViewedEditor()](#Pane+getCurrentlyViewedEditor) ⇒ File - * [.getCurrentlyViewedPath()](#Pane+getCurrentlyViewedPath) ⇒ string - * [.destroyViewIfNotNeeded(view)](#Pane+destroyViewIfNotNeeded) - * [.removeView(file, suppressOpenNextFile, preventViewChange)](#Pane+removeView) ⇒ boolean - * [.removeViews(list)](#Pane+removeViews) ⇒ Array.<File> - * [.focus()](#Pane+focus) - * [.loadState(state)](#Pane+loadState) ⇒ jQuery.Promise - * [.saveState()](#Pane+saveState) ⇒ Object - * [.getScrollState()](#Pane+getScrollState) ⇒ Object - * [.restoreAndAdjustScrollState([state], [heightDelta])](#Pane+restoreAndAdjustScrollState) - - - -### new Pane(id, $container) +* [.Pane](#module_view/Pane..Pane) + * [new Pane(id, $container)](#new_module_view/Pane..Pane_new) + * [.id](#module_view/Pane..Pane+id) : string + * [.$container](#module_view/Pane..Pane+$container) : JQuery + * [.$el](#module_view/Pane..Pane+$el) : JQuery + * [.$header](#module_view/Pane..Pane+$header) : JQuery + * [.$headerText](#module_view/Pane..Pane+$headerText) : JQuery + * [.$headerFlipViewBtn](#module_view/Pane..Pane+$headerFlipViewBtn) : JQuery + * [.$headerCloseBtn](#module_view/Pane..Pane+$headerCloseBtn) : JQuery + * [.$content](#module_view/Pane..Pane+$content) : JQuery + * [.ITEM_NOT_FOUND](#module_view/Pane..Pane+ITEM_NOT_FOUND) + * [.ITEM_FOUND_NO_SORT](#module_view/Pane..Pane+ITEM_FOUND_NO_SORT) + * [.ITEM_FOUND_NEEDS_SORT](#module_view/Pane..Pane+ITEM_FOUND_NEEDS_SORT) + * [.mergeFrom(other)](#module_view/Pane..Pane+mergeFrom) + * [.destroy()](#module_view/Pane..Pane+destroy) + * [.getViewList()](#module_view/Pane..Pane+getViewList) ⇒ Array.<File> + * [.getViewListSize()](#module_view/Pane..Pane+getViewListSize) ⇒ number + * [.findInViewList(fullPath)](#module_view/Pane..Pane+findInViewList) ⇒ number + * [.findInViewListAddedOrder(fullPath)](#module_view/Pane..Pane+findInViewListAddedOrder) ⇒ number + * [.findInViewListMRUOrder(fullPath)](#module_view/Pane..Pane+findInViewListMRUOrder) ⇒ number + * [.reorderItem(file, [index], [force])](#module_view/Pane..Pane+reorderItem) ⇒ number + * [.addToViewList(file, [index])](#module_view/Pane..Pane+addToViewList) ⇒ number + * [.addListToViewList(fileList)](#module_view/Pane..Pane+addListToViewList) ⇒ Array.<File> + * [.makeViewMostRecent(file)](#module_view/Pane..Pane+makeViewMostRecent) + * [.sortViewList(compareFn)](#module_view/Pane..Pane+sortViewList) + * [.swapViewListIndexes(index1, index2)](#module_view/Pane..Pane+swapViewListIndexes) ⇒ boolean + * [.traverseViewListByMRU(direction, [current])](#module_view/Pane..Pane+traverseViewListByMRU) ⇒ File + * [.showInterstitial(show)](#module_view/Pane..Pane+showInterstitial) + * [.getViewForPath(path)](#module_view/Pane..Pane+getViewForPath) ⇒ boolean + * [.addView(view, show)](#module_view/Pane..Pane+addView) + * [.showView(view)](#module_view/Pane..Pane+showView) + * [.updateLayout(forceRefresh)](#module_view/Pane..Pane+updateLayout) + * [.getCurrentlyViewedFile()](#module_view/Pane..Pane+getCurrentlyViewedFile) ⇒ File + * [.getCurrentlyViewedEditor()](#module_view/Pane..Pane+getCurrentlyViewedEditor) ⇒ File + * [.getCurrentlyViewedPath()](#module_view/Pane..Pane+getCurrentlyViewedPath) ⇒ string + * [.destroyViewIfNotNeeded(view)](#module_view/Pane..Pane+destroyViewIfNotNeeded) + * [.removeView(file, suppressOpenNextFile, preventViewChange)](#module_view/Pane..Pane+removeView) ⇒ boolean + * [.removeViews(list)](#module_view/Pane..Pane+removeViews) ⇒ Array.<File> + * [.focus()](#module_view/Pane..Pane+focus) + * [.loadState(state)](#module_view/Pane..Pane+loadState) ⇒ jQuery.Promise + * [.saveState()](#module_view/Pane..Pane+saveState) ⇒ Object + * [.getScrollState()](#module_view/Pane..Pane+getScrollState) ⇒ Object + * [.restoreAndAdjustScrollState([state], [heightDelta])](#module_view/Pane..Pane+restoreAndAdjustScrollState) + + + +#### new Pane(id, $container) Pane Objects are constructed by the MainViewManager object when a Pane view is needed. @@ -64,145 +240,145 @@ Pane Objects are constructed by the MainViewManager object when a Pane view is n | id | string | The id to use to identify this pane. | | $container | jQuery | The parent jQuery container to place the pane view. | - + -### pane.id : string +#### pane.id : string id of the pane -**Kind**: instance property of [Pane](#Pane) +**Kind**: instance property of [Pane](#module_view/Pane..Pane) **Read only**: true - + -### pane.$container : JQuery +#### pane.$container : JQuery container where the pane lives -**Kind**: instance property of [Pane](#Pane) +**Kind**: instance property of [Pane](#module_view/Pane..Pane) **Read only**: true - + -### pane.$el : JQuery +#### pane.$el : JQuery the wrapped DOM node of this pane -**Kind**: instance property of [Pane](#Pane) +**Kind**: instance property of [Pane](#module_view/Pane..Pane) **Read only**: true - + -### pane.$header : JQuery +#### pane.$header : JQuery the wrapped DOM node container that contains name of current view and the switch view button, or informational string if there is no view -**Kind**: instance property of [Pane](#Pane) +**Kind**: instance property of [Pane](#module_view/Pane..Pane) **Read only**: true - + -### pane.$headerText : JQuery +#### pane.$headerText : JQuery the wrapped DOM node that contains name of current view, or informational string if there is no view -**Kind**: instance property of [Pane](#Pane) +**Kind**: instance property of [Pane](#module_view/Pane..Pane) **Read only**: true - + -### pane.$headerFlipViewBtn : JQuery +#### pane.$headerFlipViewBtn : JQuery the wrapped DOM node that is used to flip the view to another pane -**Kind**: instance property of [Pane](#Pane) +**Kind**: instance property of [Pane](#module_view/Pane..Pane) **Read only**: true - + -### pane.$headerCloseBtn : JQuery +#### pane.$headerCloseBtn : JQuery close button of the pane -**Kind**: instance property of [Pane](#Pane) +**Kind**: instance property of [Pane](#module_view/Pane..Pane) **Read only**: true - + -### pane.$content : JQuery +#### pane.$content : JQuery the wrapped DOM node that contains views -**Kind**: instance property of [Pane](#Pane) +**Kind**: instance property of [Pane](#module_view/Pane..Pane) **Read only**: true - + -### pane.ITEM\_NOT\_FOUND +#### pane.ITEM\_NOT\_FOUND Return value from reorderItem when the Item was not found -**Kind**: instance constant of [Pane](#Pane) -**See**: [reorderItem](#Pane+reorderItem) - +**Kind**: instance constant of [Pane](#module_view/Pane..Pane) +**See**: [Pane#reorderItem](Pane#reorderItem) + -### pane.ITEM\_FOUND\_NO\_SORT +#### pane.ITEM\_FOUND\_NO\_SORT Return value from reorderItem when the Item was found at its natural index and the workingset does not need to be resorted -**Kind**: instance constant of [Pane](#Pane) -**See**: [reorderItem](#Pane+reorderItem) - +**Kind**: instance constant of [Pane](#module_view/Pane..Pane) +**See**: [Pane#reorderItem](Pane#reorderItem) + -### pane.ITEM\_FOUND\_NEEDS\_SORT +#### pane.ITEM\_FOUND\_NEEDS\_SORT Return value from reorderItem when the Item was found and reindexed and the workingset needs to be resorted -**Kind**: instance constant of [Pane](#Pane) -**See**: [reorderItem](#Pane+reorderItem) - +**Kind**: instance constant of [Pane](#module_view/Pane..Pane) +**See**: [Pane#reorderItem](Pane#reorderItem) + -### pane.mergeFrom(other) +#### pane.mergeFrom(other) Merges the another Pane object's contents into this Pane -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) | Param | Type | Description | | --- | --- | --- | -| other | [Pane](#Pane) | Pane from which to copy | +| other | Pane | Pane from which to copy | - + -### pane.destroy() +#### pane.destroy() Removes the DOM node for the Pane, removes all event handlers and _resets all internal data structures -**Kind**: instance method of [Pane](#Pane) - +**Kind**: instance method of [Pane](#module_view/Pane..Pane) + -### pane.getViewList() ⇒ Array.<File> +#### pane.getViewList() ⇒ Array.<File> Returns a copy of the view file list -**Kind**: instance method of [Pane](#Pane) - +**Kind**: instance method of [Pane](#module_view/Pane..Pane) + -### pane.getViewListSize() ⇒ number +#### pane.getViewListSize() ⇒ number Returns the number of entries in the view file list -**Kind**: instance method of [Pane](#Pane) - +**Kind**: instance method of [Pane](#module_view/Pane..Pane) + -### pane.findInViewList(fullPath) ⇒ number +#### pane.findInViewList(fullPath) ⇒ number Returns the index of the item in the view file list -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: number - index of the item or -1 if not found | Param | Type | Description | | --- | --- | --- | | fullPath | string | the full path of the item to look for | - + -### pane.findInViewListAddedOrder(fullPath) ⇒ number +#### pane.findInViewListAddedOrder(fullPath) ⇒ number Returns the order in which the item was added -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: number - order of the item or -1 if not found | Param | Type | Description | | --- | --- | --- | | fullPath | string | the full path of the item to look for | - + -### pane.findInViewListMRUOrder(fullPath) ⇒ number +#### pane.findInViewListMRUOrder(fullPath) ⇒ number Returns the order in which the item was last used -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: number - order of the item or -1 if not found. 0 indicates most recently used, followed by 1 and so on... @@ -210,12 +386,12 @@ Returns the order in which the item was last used | --- | --- | --- | | fullPath | string | the full path of the item to look for | - + -### pane.reorderItem(file, [index], [force]) ⇒ number +#### pane.reorderItem(file, [index], [force]) ⇒ number reorders the specified file in the view list to the desired position -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: number - this function returns one of the following manifest constants: ITEM_NOT_FOUND : The request file object was not found ITEM_FOUND_NO_SORT : The request file object was found but it was already at the requested index @@ -227,13 +403,13 @@ reorders the specified file in the view list to the desired position | [index] | number | the new position of the item | | [force] | boolean | true to force the item into that position, false otherwise. (Requires an index be requested) | - + -### pane.addToViewList(file, [index]) ⇒ number +#### pane.addToViewList(file, [index]) ⇒ number Adds the given file to the end of the workingset, if it is not already in the list Does not change which document is currently open in the editor. Completes synchronously. -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: number - index of where the item was added | Param | Type | Description | @@ -241,46 +417,46 @@ Does not change which document is currently open in the editor. Completes synchr | file | File | file to add | | [index] | number | position where to add the item | - + -### pane.addListToViewList(fileList) ⇒ Array.<File> +#### pane.addListToViewList(fileList) ⇒ Array.<File> Adds the given file list to the end of the workingset. -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: Array.<File> - list of files added to the list | Param | Type | | --- | --- | | fileList | Array.<File> | - + -### pane.makeViewMostRecent(file) +#### pane.makeViewMostRecent(file) Moves the specified file to the front of the MRU (Most Recently Used) list. -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) | Param | Type | Description | | --- | --- | --- | | file | File | The file to move to the front of the MRU list. | - + -### pane.sortViewList(compareFn) +#### pane.sortViewList(compareFn) Sorts items in the pane's view list. -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) | Param | Type | Description | | --- | --- | --- | | compareFn | function | The function used to compare items in the view list. | - + -### pane.swapViewListIndexes(index1, index2) ⇒ boolean +#### pane.swapViewListIndexes(index1, index2) ⇒ boolean Swaps two items in the file view list (used while dragging items in the working set view) -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: boolean - } true | Param | Type | Description | @@ -288,12 +464,12 @@ Swaps two items in the file view list (used while dragging items in the working | index1 | number | the index of the first item to swap | | index2 | number | the index of the second item to swap | - + -### pane.traverseViewListByMRU(direction, [current]) ⇒ File +#### pane.traverseViewListByMRU(direction, [current]) ⇒ File Traverses the list and returns the File object of the next item in the MRU order -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: File - The File object of the next item in the travesal order or null if there isn't one. | Param | Type | Description | @@ -301,103 +477,103 @@ Traverses the list and returns the File object of the next item in the MRU order | direction | number | Must be 1 or -1 to traverse forward or backward | | [current] | string | the fullPath of the item where traversal is to start. If this parameter is omitted then the path of the current view is used. If the current view is a temporary view then the first item in the MRU list is returned | - + -### pane.showInterstitial(show) +#### pane.showInterstitial(show) Shows the pane's interstitial page -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) | Param | Type | Description | | --- | --- | --- | | show | boolean | show or hide the interstitial page | - + -### pane.getViewForPath(path) ⇒ boolean +#### pane.getViewForPath(path) ⇒ boolean retrieves the view object for the given path -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: boolean - show - show or hide the interstitial page | Param | Type | Description | | --- | --- | --- | | path | string | the fullPath of the view to retrieve | - + -### pane.addView(view, show) +#### pane.addView(view, show) Adds a view to the pane -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) | Param | Type | Description | | --- | --- | --- | | view | View | the View object to add | | show | boolean | true to show the view right away, false otherwise | - + -### pane.showView(view) +#### pane.showView(view) Swaps the current view with the requested view. If the interstitial page is shown, it is hidden. If the currentView is a temporary view, it is destroyed. -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) | Param | Type | Description | | --- | --- | --- | | view | View | the to show | - + -### pane.updateLayout(forceRefresh) +#### pane.updateLayout(forceRefresh) Sets pane content height. Updates the layout causing the current view to redraw itself -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) | Param | Type | Description | | --- | --- | --- | | forceRefresh | boolean | true to force a resize and refresh of the current view, false if just to resize forceRefresh is only used by Editor views to force a relayout of all editor DOM elements. Custom View implementations should just ignore this flag. | - + -### pane.getCurrentlyViewedFile() ⇒ File +#### pane.getCurrentlyViewedFile() ⇒ File Retrieves the File object of the current view -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: File - the File object of the current view or null if there isn't one - + -### pane.getCurrentlyViewedEditor() ⇒ File +#### pane.getCurrentlyViewedEditor() ⇒ File Retrieves the File object of the current view -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: File - the File object of the current view or null if there isn't one - + -### pane.getCurrentlyViewedPath() ⇒ string +#### pane.getCurrentlyViewedPath() ⇒ string Retrieves the path of the current view -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: string - the path of the current view or null if there isn't one - + -### pane.destroyViewIfNotNeeded(view) +#### pane.destroyViewIfNotNeeded(view) destroys the view if it isn't needed -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) | Param | Type | Description | | --- | --- | --- | | view | View | the view to destroy | - + -### pane.removeView(file, suppressOpenNextFile, preventViewChange) ⇒ boolean +#### pane.removeView(file, suppressOpenNextFile, preventViewChange) ⇒ boolean Removes the view and opens the next view -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: boolean - true if the file was removed from the working set This function will remove a temporary view of a file but will return false in that case @@ -407,13 +583,13 @@ Removes the view and opens the next view | suppressOpenNextFile | boolean | suppresses opening the next file in MRU order | | preventViewChange | boolean | if suppressOpenNextFile is truthy, this flag can be used to prevent the current view from being destroyed. Ignored if suppressOpenNextFile is falsy | - + -### pane.removeViews(list) ⇒ Array.<File> +#### pane.removeViews(list) ⇒ Array.<File> Removes the specifed file from all internal lists, destroys the view of the file (if there is one) and shows the interstitial page if the current view is destroyed. -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: Array.<File> - Array of File objects removed from the working set. This function will remove temporary views but the file objects for those views will not be found in the result set. Only the file objects removed from the working set are returned. @@ -422,178 +598,47 @@ Removes the specifed file from all internal lists, destroys the view of the file | --- | --- | --- | | list | Array.<File> | Array of files to remove | - + -### pane.focus() +#### pane.focus() Gives focus to the last thing that had focus, the current view or the pane in that order -**Kind**: instance method of [Pane](#Pane) - +**Kind**: instance method of [Pane](#module_view/Pane..Pane) + -### pane.loadState(state) ⇒ jQuery.Promise +#### pane.loadState(state) ⇒ jQuery.Promise serializes the pane state from JSON -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: jQuery.Promise - A promise which resolves to \{fullPath:string, paneId:string} which can be passed as command data to FILE_OPEN | Param | Type | Description | | --- | --- | --- | | state | Object | the state to load | - + -### pane.saveState() ⇒ Object +#### pane.saveState() ⇒ Object Returns the JSON-ified state of the object so it can be serialize -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: Object - state - the state to save - + -### pane.getScrollState() ⇒ Object +#### pane.getScrollState() ⇒ Object gets the current view's scroll state data -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) **Returns**: Object - scroll state - the current scroll state - + -### pane.restoreAndAdjustScrollState([state], [heightDelta]) +#### pane.restoreAndAdjustScrollState([state], [heightDelta]) tells the current view to restore its scroll state from cached data and apply a height delta -**Kind**: instance method of [Pane](#Pane) +**Kind**: instance method of [Pane](#module_view/Pane..Pane) | Param | Type | Description | | --- | --- | --- | | [state] | Object | the current scroll state | | [heightDelta] | number | the amount to add or subtract from the state | - - -## \_ -Pane objects host views of files, editors, etc... Clients cannot access -Pane objects directly. Instead the implementation is protected by the -MainViewManager -- however View Factories are given a Pane object which -they can use to add views. References to Pane objects should not be kept -as they may be destroyed and removed from the DOM. - -To get a custom view, there are two components: - - 1) A View Factory - 2) A View Object - -View objects are anonymous object that have a particular interface. - -Views can be added to a pane but do not have to exist in the Pane object's view list. -Such views are "temporary views". Temporary views are not serialized with the Pane state -or reconstituted when the pane is serialized from disk. They are destroyed at the earliest -opportunity. - -Temporary views are added by calling `Pane.showView()` and passing it the view object. The view -will be destroyed when the next view is shown, the pane is mereged with another pane or the "Close All" -command is exectuted on the Pane. Temporary Editor Views do not contain any modifications and are -added to the workingset (and are no longer tempoary views) once the document has been modified. They -will remain in the working set until closed from that point on. - -Views that have a longer life span are added by calling addView to associate the view with a -filename in the _views object. These views are not destroyed until they are removed from the pane -by calling one of the following: removeView, removeViews, or _reset - -Pane Object Events: - - - viewListChange - Whenever there is a file change to a file in the working set. These 2 events: `DocumentManager.pathRemove` - and `DocumentManager.fileNameChange` will cause a `viewListChange` event so the WorkingSetView can update. - - - currentViewChange - Whenever the current view changes. - (e, newView:View, oldView:View) - - - viewDestroy - Whenever a view has been destroyed - (e, view:View) - -View Interface: - -The view is an anonymous object which has the following method signatures. see ImageViewer for an example or the sample -provided with Brackets `src/extensions/samples/BracketsConfigCentral` - -```js - { - $el:jQuery - getFile: function ():!File - updateLayout: function(forceRefresh:boolean) - destroy: function() - getScrollPos: function():*= - adjustScrollPos: function(state:Object=, heightDelta:number)= - notifyContainerChange: function()= - notifyVisibilityChange: function(boolean)= - focus:function()= - } -``` -When views are created they can be added to the pane by calling `pane.addView()`. -Views can be created and parented by attaching directly to `pane.$el` - - this._codeMirror = new CodeMirror(pane.$el, ...) - -Factories can create a view that's initially hidden by calling `pane.addView(view)` and passing `false` for the show parameter. -Hidden views can be later shown by calling `pane.showView(view)` - -`$el:jQuery!` - - property that stores the jQuery wrapped DOM element of the view. All views must have one so pane objects can manipulate the DOM - element when necessary (e.g. `showView`, `_reparent`, etc...) - -`getFile():File!` - - Called throughout the life of a View when the current file is queried by the system. - -`updateLayout(forceRefresh:boolean)` - - Called to notify the view that it should be resized to fit its parent container. This may be called several times - or only once. Views can ignore the `forceRefresh` flag. It is used for editor views to force a relayout of the editor - which probably isn't necessary for most views. Views should implement their html to be dynamic and not rely on this - function to be called whenever possible. - -`destroy()` - - Views must implement a destroy method to remove their DOM element at the very least. There is no default - implementation and views are hidden before this method is called. The Pane object doesn't make assumptions - about when it is safe to remove a node. In some instances other cleanup must take place before a the DOM - node is destroyed so the implementation details are left to the view. - - Views can implement a simple destroy by calling - - this.$el.remove() - - These members are optional and need not be implemented by Views - - getScrollPos() - adjustScrollPos() - - The system at various times will want to save and restore a view's scroll position. The data returned by `getScrollPos()` - is specific to the view and will be passed back to `adjustScrollPos()` when the scroll position needs to be restored. - - When Modal Bars are invoked, the system calls `getScrollPos()` so that the current scroll psotion of all visible Views can be cached. - That cached scroll position is later passed to `adjustScrollPos()` along with a height delta. The height delta is used to - scroll the view so that it doesn't appear to have "jumped" when invoking the Modal Bar. - - Height delta will be a positive when the Modal Bar is being shown and negative number when the Modal Bar is being hidden. - - `getViewState()` is another optional member that is used to cache a view's state when hiding or destroying a view or closing the project. - The data returned by this member is stored in `ViewStateManager` and is saved with the project. - - Views or View Factories are responsible for restoring the view state when the view of that file is created by recalling the cached state - - var view = createIconView(file, pane); - view.restoreViewState(ViewStateManager.getViewState(file.fullPath)); - - Notifications - The following optional methods receive notifications from the Pane object when certain events take place which affect the view: - -`notifyContainerChange()` - - Optional Notification callback called when the container changes. The view can perform any synchronization or state update - it needs to do when its parent container changes. - -`notifyVisiblityChange()` - - Optional Notification callback called when the view's vsibility changes. The view can perform any synchronization or - state update it needs to do when its visiblity state changes. - -**Kind**: global variable diff --git a/docs/API-Reference/view/SidebarTabs.md b/docs/API-Reference/view/SidebarTabs.md new file mode 100644 index 0000000000..fbbd4bdd60 --- /dev/null +++ b/docs/API-Reference/view/SidebarTabs.md @@ -0,0 +1,139 @@ +### Import : +```js +const SidebarTabs = brackets.getModule("view/SidebarTabs") +``` + + + +## view/SidebarTabs +SidebarTabs manages multiple tab panes within the sidebar. It inserts a +`#navTabBar` element after `#mainNavBar` and provides an API for registering +tabs, associating DOM content with tabs, and switching between them. + +Existing sidebar children that are not explicitly associated with a tab via +`addToTab` are treated as belonging to the default "Files" tab. This means +extensions that add DOM nodes to the sidebar will continue to work without +any code changes. + +Tab switching works purely by toggling the `.sidebar-tab-hidden` CSS class +(`display: none !important`). No DOM reparenting or detaching occurs, so +cached jQuery/DOM references held by extensions remain valid. + + +* [view/SidebarTabs](#module_view/SidebarTabs) + * [.SIDEBAR_TAB_FILES](#module_view/SidebarTabs..SIDEBAR_TAB_FILES) : string + * [.EVENT_TAB_ADDED](#module_view/SidebarTabs..EVENT_TAB_ADDED) : string + * [.EVENT_TAB_REMOVED](#module_view/SidebarTabs..EVENT_TAB_REMOVED) : string + * [.EVENT_TAB_CHANGED](#module_view/SidebarTabs..EVENT_TAB_CHANGED) : string + * [.addTab(id, label, iconClass, [options])](#module_view/SidebarTabs..addTab) + * [.addToTab(tabId, $content)](#module_view/SidebarTabs..addToTab) + * [.removeFromTab(tabId, $content)](#module_view/SidebarTabs..removeFromTab) + * [.removeTab(id)](#module_view/SidebarTabs..removeTab) ⇒ boolean + * [.setActiveTab(id)](#module_view/SidebarTabs..setActiveTab) + * [.getActiveTab()](#module_view/SidebarTabs..getActiveTab) ⇒ string + * [.getAllTabs()](#module_view/SidebarTabs..getAllTabs) ⇒ Array.<{id: string, label: string, iconClass: string, priority: number}> + + + +### view/SidebarTabs.SIDEBAR\_TAB\_FILES : string +The built-in Files tab id. + +**Kind**: inner constant of [view/SidebarTabs](#module_view/SidebarTabs) + + +### view/SidebarTabs.EVENT\_TAB\_ADDED : string +Fired when a new tab is registered via `addTab`. + +**Kind**: inner constant of [view/SidebarTabs](#module_view/SidebarTabs) + + +### view/SidebarTabs.EVENT\_TAB\_REMOVED : string +Fired when a tab is removed via `removeTab`. + +**Kind**: inner constant of [view/SidebarTabs](#module_view/SidebarTabs) + + +### view/SidebarTabs.EVENT\_TAB\_CHANGED : string +Fired when the active tab changes via `setActiveTab`. + +**Kind**: inner constant of [view/SidebarTabs](#module_view/SidebarTabs) + + +### view/SidebarTabs.addTab(id, label, iconClass, [options]) +Register a new sidebar tab. + +**Kind**: inner method of [view/SidebarTabs](#module_view/SidebarTabs) + +| Param | Type | Default | Description | +| --- | --- | --- | --- | +| id | string | | Unique tab identifier | +| label | string | | Display text shown in the tab bar | +| iconClass | string | | FontAwesome (or other) icon class string | +| [options] | Object | | | +| [options.priority] | number | 100 | Lower values appear further left | + + + +### view/SidebarTabs.addToTab(tabId, $content) +Associate a DOM node (or jQuery element) with a tab. If the node is not +already a child of `#sidebar`, it is appended. If the tab is not the +currently active tab, the node is immediately hidden. + +**Kind**: inner method of [view/SidebarTabs](#module_view/SidebarTabs) + +| Param | Type | Description | +| --- | --- | --- | +| tabId | string | The tab to associate with | +| $content | jQuery \| Element | DOM node or jQuery wrapper | + + + +### view/SidebarTabs.removeFromTab(tabId, $content) +Remove a DOM node's association with a tab. If the node was appended by +`addToTab` (was not originally in the sidebar) and is no longer +associated with any tab, it is also removed from the DOM. + +**Kind**: inner method of [view/SidebarTabs](#module_view/SidebarTabs) + +| Param | Type | Description | +| --- | --- | --- | +| tabId | string | The tab to disassociate from | +| $content | jQuery \| Element | DOM node or jQuery wrapper | + + + +### view/SidebarTabs.removeTab(id) ⇒ boolean +Remove a tab entirely. Only succeeds if all content has been removed via +`removeFromTab` first. Returns false if content still exists. + +**Kind**: inner method of [view/SidebarTabs](#module_view/SidebarTabs) +**Returns**: boolean - true if removed, false if content still associated + +| Param | Type | Description | +| --- | --- | --- | +| id | string | The tab id to remove | + + + +### view/SidebarTabs.setActiveTab(id) +Switch the active sidebar tab. Shows nodes associated with the target +tab, hides all others. + +**Kind**: inner method of [view/SidebarTabs](#module_view/SidebarTabs) + +| Param | Type | Description | +| --- | --- | --- | +| id | string | The tab id to activate | + + + +### view/SidebarTabs.getActiveTab() ⇒ string +Get the currently active tab id. + +**Kind**: inner method of [view/SidebarTabs](#module_view/SidebarTabs) + + +### view/SidebarTabs.getAllTabs() ⇒ Array.<{id: string, label: string, iconClass: string, priority: number}> +Get an array of all registered tab descriptors. + +**Kind**: inner method of [view/SidebarTabs](#module_view/SidebarTabs) diff --git a/docs/API-Reference/view/ViewStateManager.md b/docs/API-Reference/view/ViewStateManager.md index b888fee6a1..e0092f2fdc 100644 --- a/docs/API-Reference/view/ViewStateManager.md +++ b/docs/API-Reference/view/ViewStateManager.md @@ -3,9 +3,9 @@ const ViewStateManager = brackets.getModule("view/ViewStateManager") ``` - + -## \_ +## view/ViewStateManager ViewStateManager is a singleton for views to park their global viwe state. The state is saved with project data but the View or View Factory is responsible for restoring the view state when the view is created. @@ -16,43 +16,49 @@ for later use. Views or View Factories are responsible for restoring the view state when the view of that file is created by recalling the cached state. Views determine what data is store in the view state and how to restore it. -**Kind**: global variable - -## reset() +* [view/ViewStateManager](#module_view/ViewStateManager) + * [.reset()](#module_view/ViewStateManager..reset) + * [.updateViewState(view, viewState)](#module_view/ViewStateManager..updateViewState) + * [.getViewState(file)](#module_view/ViewStateManager..getViewState) ⇒ \* + * [.addViewStates(viewStates)](#module_view/ViewStateManager..addViewStates) + + + +### view/ViewStateManager.reset() resets the view state cache -**Kind**: global function - +**Kind**: inner method of [view/ViewStateManager](#module_view/ViewStateManager) + -## updateViewState(view, viewState) +### view/ViewStateManager.updateViewState(view, viewState) Updates the view state for the specified view -**Kind**: global function +**Kind**: inner method of [view/ViewStateManager](#module_view/ViewStateManager) | Param | Type | Description | | --- | --- | --- | | view | Object | the to save state | | viewState | \* | any data that the view needs to restore the view state. | - + -## getViewState(file) ⇒ \* +### view/ViewStateManager.getViewState(file) ⇒ \* gets the view state for the specified file -**Kind**: global function +**Kind**: inner method of [view/ViewStateManager](#module_view/ViewStateManager) **Returns**: \* - whatever data that was saved earlier with a call setViewState | Param | Type | Description | | --- | --- | --- | | file | File | the file to record the view state for | - + -## addViewStates(viewStates) +### view/ViewStateManager.addViewStates(viewStates) adds an array of view states -**Kind**: global function +**Kind**: inner method of [view/ViewStateManager](#module_view/ViewStateManager) | Param | Type | Description | | --- | --- | --- | diff --git a/docs/API-Reference/view/WorkspaceManager.md b/docs/API-Reference/view/WorkspaceManager.md index 34948a2665..8d39dbbaaf 100644 --- a/docs/API-Reference/view/WorkspaceManager.md +++ b/docs/API-Reference/view/WorkspaceManager.md @@ -3,21 +3,9 @@ const WorkspaceManager = brackets.getModule("view/WorkspaceManager") ``` - + -## PANEL\_TYPE\_BOTTOM\_PANEL : string -Constant representing the type of bottom panel - -**Kind**: global variable - - -## PANEL\_TYPE\_PLUGIN\_PANEL : string -Constant representing the type of plugin panel - -**Kind**: global variable - - -## AppInit +## view/WorkspaceManager Manages layout of panels surrounding the editor area, and size of the editor area (but not its contents). Updates panel sizes when the window is resized. Maintains the max resizing limits for panels, based on @@ -28,32 +16,62 @@ Events: The 2nd arg is the available workspace height. The 3rd arg is a refreshHint flag for internal use (passed in to recomputeLayout) -**Kind**: global constant - -## EVENT\_WORKSPACE\_UPDATE\_LAYOUT +* [view/WorkspaceManager](#module_view/WorkspaceManager) + * _static_ + * [.PANEL_TYPE_BOTTOM_PANEL](#module_view/WorkspaceManager.PANEL_TYPE_BOTTOM_PANEL) : string + * [.PANEL_TYPE_PLUGIN_PANEL](#module_view/WorkspaceManager.PANEL_TYPE_PLUGIN_PANEL) : string + * _inner_ + * [.EVENT_WORKSPACE_UPDATE_LAYOUT](#module_view/WorkspaceManager..EVENT_WORKSPACE_UPDATE_LAYOUT) + * [.EVENT_WORKSPACE_PANEL_SHOWN](#module_view/WorkspaceManager..EVENT_WORKSPACE_PANEL_SHOWN) + * [.EVENT_WORKSPACE_PANEL_HIDDEN](#module_view/WorkspaceManager..EVENT_WORKSPACE_PANEL_HIDDEN) + * [.createBottomPanel(id, $panel, [minSize])](#module_view/WorkspaceManager..createBottomPanel) ⇒ Panel + * [.createPluginPanel(id, $panel, [minSize], $toolbarIcon, [initialSize])](#module_view/WorkspaceManager..createPluginPanel) ⇒ Panel + * [.getAllPanelIDs()](#module_view/WorkspaceManager..getAllPanelIDs) ⇒ Array + * [.getPanelForID(panelID)](#module_view/WorkspaceManager..getPanelForID) ⇒ Object + * [.recomputeLayout(refreshHint)](#module_view/WorkspaceManager..recomputeLayout) + * [.isPanelVisible(panelID)](#module_view/WorkspaceManager..isPanelVisible) ⇒ boolean + * [.setPluginPanelWidth(width)](#module_view/WorkspaceManager..setPluginPanelWidth) + * [.addEscapeKeyEventHandler(consumerName, eventHandler)](#module_view/WorkspaceManager..addEscapeKeyEventHandler) ⇒ boolean + * [.removeEscapeKeyEventHandler(consumerName)](#module_view/WorkspaceManager..removeEscapeKeyEventHandler) ⇒ boolean + + + +### view/WorkspaceManager.PANEL\_TYPE\_BOTTOM\_PANEL : string +Constant representing the type of bottom panel + +**Kind**: static property of [view/WorkspaceManager](#module_view/WorkspaceManager) + + +### view/WorkspaceManager.PANEL\_TYPE\_PLUGIN\_PANEL : string +Constant representing the type of plugin panel + +**Kind**: static property of [view/WorkspaceManager](#module_view/WorkspaceManager) + + +### view/WorkspaceManager.EVENT\_WORKSPACE\_UPDATE\_LAYOUT Event triggered when the workspace layout updates. -**Kind**: global constant - +**Kind**: inner constant of [view/WorkspaceManager](#module_view/WorkspaceManager) + -## EVENT\_WORKSPACE\_PANEL\_SHOWN +### view/WorkspaceManager.EVENT\_WORKSPACE\_PANEL\_SHOWN Event triggered when a panel is shown. -**Kind**: global constant - +**Kind**: inner constant of [view/WorkspaceManager](#module_view/WorkspaceManager) + -## EVENT\_WORKSPACE\_PANEL\_HIDDEN +### view/WorkspaceManager.EVENT\_WORKSPACE\_PANEL\_HIDDEN Event triggered when a panel is hidden. -**Kind**: global constant - +**Kind**: inner constant of [view/WorkspaceManager](#module_view/WorkspaceManager) + -## createBottomPanel(id, $panel, [minSize]) ⇒ Panel +### view/WorkspaceManager.createBottomPanel(id, $panel, [minSize]) ⇒ Panel Creates a new resizable panel beneath the editor area and above the status bar footer. Panel is initially invisible. The panel's size & visibility are automatically saved & restored as a view-state preference. -**Kind**: global function +**Kind**: inner method of [view/WorkspaceManager](#module_view/WorkspaceManager) | Param | Type | Description | | --- | --- | --- | @@ -61,14 +79,14 @@ The panel's size & visibility are automatically saved & restored as a view-state | $panel | jQueryObject | DOM content to use as the panel. Need not be in the document yet. Must have an id attribute, for use as a preferences key. | | [minSize] | number | Minimum height of panel in px. | - + -## createPluginPanel(id, $panel, [minSize], $toolbarIcon, [initialSize]) ⇒ Panel +### view/WorkspaceManager.createPluginPanel(id, $panel, [minSize], $toolbarIcon, [initialSize]) ⇒ Panel Creates a new resizable plugin panel associated with the given toolbar icon. Panel is initially invisible. The panel's size & visibility are automatically saved & restored. Only one panel can be associated with a toolbar icon. -**Kind**: global function +**Kind**: inner method of [view/WorkspaceManager](#module_view/WorkspaceManager) | Param | Type | Description | | --- | --- | --- | @@ -78,57 +96,71 @@ toolbar icon. | $toolbarIcon | jQueryObject | An icon that should be present in main-toolbar to associate this panel to. The panel will be shown only if the icon is visible on the toolbar and the user clicks on the icon. | | [initialSize] | number | Optional Initial size of panel in px. If not given, panel will use minsize or current size. | - + -## getAllPanelIDs() ⇒ Array +### view/WorkspaceManager.getAllPanelIDs() ⇒ Array Returns an array of all panel ID's -**Kind**: global function +**Kind**: inner method of [view/WorkspaceManager](#module_view/WorkspaceManager) **Returns**: Array - List of ID's of all bottom panels - + -## getPanelForID(panelID) ⇒ Object +### view/WorkspaceManager.getPanelForID(panelID) ⇒ Object Gets the Panel interface for the given ID. Can return undefined if no panel with the ID is found. -**Kind**: global function +**Kind**: inner method of [view/WorkspaceManager](#module_view/WorkspaceManager) **Returns**: Object - Panel object for the ID or undefined | Param | Type | | --- | --- | | panelID | string | - + -## recomputeLayout(refreshHint) +### view/WorkspaceManager.recomputeLayout(refreshHint) Called when an external widget has appeared and needs some of the space occupied by the mainview manager -**Kind**: global function +**Kind**: inner method of [view/WorkspaceManager](#module_view/WorkspaceManager) | Param | Type | Description | | --- | --- | --- | | refreshHint | boolean | true to refresh the editor, false if not | - + -## isPanelVisible(panelID) ⇒ boolean +### view/WorkspaceManager.isPanelVisible(panelID) ⇒ boolean Responsible to check if the panel is visible or not. Returns true if visible else false. -**Kind**: global function +**Kind**: inner method of [view/WorkspaceManager](#module_view/WorkspaceManager) | Param | | --- | | panelID | - + + +### view/WorkspaceManager.setPluginPanelWidth(width) +Programmatically sets the plugin panel content width to the given value in pixels. +The total toolbar width is adjusted to account for the plugin icons bar. +Width is clamped to respect panel minWidth and max size (75% of window). +No-op if no panel is currently visible. + +**Kind**: inner method of [view/WorkspaceManager](#module_view/WorkspaceManager) + +| Param | Type | Description | +| --- | --- | --- | +| width | number | Desired content width in pixels | + + -## addEscapeKeyEventHandler(consumerName, eventHandler) ⇒ boolean +### view/WorkspaceManager.addEscapeKeyEventHandler(consumerName, eventHandler) ⇒ boolean If any widgets related to the editor needs to handle the escape key event, add it here. returning true from the registered handler will prevent primary escape key toggle panel behavior of phoenix. Note that returning true will no stop the event bubbling, that has to be controlled with the event parameter forwarded to the handler. -**Kind**: global function +**Kind**: inner method of [view/WorkspaceManager](#module_view/WorkspaceManager) **Returns**: boolean - true if added | Param | Type | Description | @@ -136,12 +168,12 @@ will no stop the event bubbling, that has to be controlled with the event parame | consumerName | string | a unique name for your consumer | | eventHandler | function | If the eventHandler returns true for this callback, the escape key event will not lead to panel toggle default behavior. | - + -## removeEscapeKeyEventHandler(consumerName) ⇒ boolean +### view/WorkspaceManager.removeEscapeKeyEventHandler(consumerName) ⇒ boolean Removing the escape key event consumer. -**Kind**: global function +**Kind**: inner method of [view/WorkspaceManager](#module_view/WorkspaceManager) **Returns**: boolean - true if removed | Param | Type | Description | diff --git a/gulpfile.js/thirdparty-lib-copy.js b/gulpfile.js/thirdparty-lib-copy.js index 466db4ec62..3e35b93808 100644 --- a/gulpfile.js/thirdparty-lib-copy.js +++ b/gulpfile.js/thirdparty-lib-copy.js @@ -242,7 +242,9 @@ let copyThirdPartyLibs = series( // jasmine-reporters copyFiles.bind(copyFiles, ['node_modules/jasmine-reporters/src/**/*'], 'test/thirdparty/jasmine-reporters/'), - copyLicence.bind(copyLicence, 'node_modules/jasmine-reporters/LICENSE', 'jasmine-reporters') + copyLicence.bind(copyLicence, 'node_modules/jasmine-reporters/LICENSE', 'jasmine-reporters'), + // lmdb + copyLicence.bind(copyLicence, 'node_modules/lmdb/LICENSE', 'lmdb') ); diff --git a/package-lock.json b/package-lock.json index 27808ef6cc..2d1ac373ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,19 @@ { "name": "phoenix", - "version": "4.1.2-0", + "version": "5.1.4-0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "phoenix", - "version": "4.1.2-0", + "version": "5.1.4-0", + "hasInstallScript": true, "dependencies": { "@bugsnag/js": "^7.18.0", "@floating-ui/dom": "^0.5.4", "@fortawesome/fontawesome-free": "^6.1.2", "@highlightjs/cdn-assets": "^11.5.1", - "@phcode/fs": "^3.0.1", + "@phcode/fs": "^4.0.2", "@phcode/language-support": "^1.1.0", "@pixelbrackets/gfm-stylesheet": "^1.1.0", "@prettier/plugin-php": "^0.22.2", @@ -28,7 +29,7 @@ "jshint": "^2.13.5", "jszip": "^3.8.0", "less": "^4.1.3", - "lodash": "^4.17.21", + "lodash": "^4.17.23", "lru-cache": "^10.2.0", "marked": "^4.0.18", "mime-db": "^1.52.0", @@ -64,6 +65,7 @@ "jasmine-core": "^4.2.0", "jasmine-reporters": "^2.5.0", "jsdoc-to-markdown": "^9.1.1", + "lmdb": "^3.5.1", "readable-stream": "^3.6.0", "through2": "^4.0.2" } @@ -797,6 +799,13 @@ "xtend": "~4.0.1" } }, + "node_modules/@harperfast/extended-iterable": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@harperfast/extended-iterable/-/extended-iterable-1.0.3.tgz", + "integrity": "sha512-sSAYhQca3rDWtQUHSAPeO7axFIUJOI6hn1gjRC5APVE1a90tuyT8f5WIgRsFhhWA7htNkju2veB9eWL6YHi/Lw==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/@highlightjs/cdn-assets": { "version": "11.5.1", "resolved": "https://registry.npmjs.org/@highlightjs/cdn-assets/-/cdn-assets-11.5.1.tgz", @@ -862,6 +871,188 @@ "node": ">=v12.0.0" } }, + "node_modules/@lmdb/lmdb-darwin-arm64": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.5.1.tgz", + "integrity": "sha512-tpfN4kKrrMpQ+If1l8bhmoNkECJi0iOu6AEdrTJvWVC+32sLxTARX5Rsu579mPImRP9YFWfWgeRQ5oav7zApQQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lmdb/lmdb-darwin-x64": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.5.1.tgz", + "integrity": "sha512-+a2tTfc3rmWhLAolFUWRgJtpSuu+Fw/yjn4rF406NMxhfjbMuiOUTDRvRlMFV+DzyjkwnokisskHbCWkS3Ly5w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lmdb/lmdb-linux-arm": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.5.1.tgz", + "integrity": "sha512-0EgcE6reYr8InjD7V37EgXcYrloqpxVPINy3ig1MwDSbl6LF/vXTYRH9OE1Ti1D8YZnB35ZH9aTcdfSb5lql2A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-linux-arm64": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.5.1.tgz", + "integrity": "sha512-aoERa5B6ywXdyFeYGQ1gbQpkMkDbEo45qVoXE5QpIRavqjnyPwjOulMkmkypkmsbJ5z4Wi0TBztON8agCTG0Vg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-linux-x64": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.5.1.tgz", + "integrity": "sha512-SqNDY1+vpji7bh0sFH5wlWyFTOzjbDOl0/kB5RLLYDAFyd/uw3n7wyrmas3rYPpAW7z18lMOi1yKlTPv967E3g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-win32-arm64": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-arm64/-/lmdb-win32-arm64-3.5.1.tgz", + "integrity": "sha512-50v0O1Lt37cwrmR9vWZK5hRW0Aw+KEmxJJ75fge/zIYdvNKB/0bSMSVR5Uc2OV9JhosIUyklOmrEvavwNJ8D6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@lmdb/lmdb-win32-x64": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.5.1.tgz", + "integrity": "sha512-qwosvPyl+zpUlp3gRb7UcJ3H8S28XHCzkv0Y0EgQToXjQP91ZD67EHSCDmaLjtKhe+GVIW5om1KUpzVLA0l6pg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", + "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", + "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", + "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", + "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", + "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", + "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -898,10 +1089,9 @@ } }, "node_modules/@phcode/fs": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@phcode/fs/-/fs-3.0.1.tgz", - "integrity": "sha512-IntWO/GdUyC7BX+WeDQUTZmL8inGY//aiFzaphw1eni+0LRh3aGSK61eOlwlpcdA3TRxEzbEyH15NZYaUaWRHg==", - "license": "GNU-AGPL3.0", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@phcode/fs/-/fs-4.0.2.tgz", + "integrity": "sha512-vjR2EtMizOj/tf30FObT1KkbBFwLRkVWFsnyUdt0sex6H/lpxRgjKp7NO8YBUKROw66eMoL+7kGyHKAlfV9zWA==", "dependencies": { "chokidar": "^3.5.3", "ignore": "^5.2.4", @@ -1083,6 +1273,7 @@ "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", "dev": true, + "peer": true, "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" @@ -1104,7 +1295,8 @@ "version": "17.0.8", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.8.tgz", "integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@types/normalize-package-data": { "version": "2.4.4", @@ -1150,6 +1342,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3822,6 +4015,16 @@ "node": ">=0.10.0" } }, + "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==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/devicon": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/devicon/-/devicon-2.15.1.tgz", @@ -4202,6 +4405,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.19.0.tgz", "integrity": "sha512-SXOPj3x9VKvPe81TjjUJCYlV4oJjQw68Uek+AM0X4p+33dj2HY5bpTZOgnQHcG2eAm1mtCU9uNMnJi7exU/kYw==", "dev": true, + "peer": true, "dependencies": { "@eslint/eslintrc": "^1.3.0", "@humanwhocodes/config-array": "^0.9.2", @@ -5901,6 +6105,7 @@ "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", "dev": true, + "peer": true, "dependencies": { "glob-watcher": "^5.0.3", "gulp-cli": "^2.2.0", @@ -8029,6 +8234,34 @@ "uc.micro": "^2.0.0" } }, + "node_modules/lmdb": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.5.1.tgz", + "integrity": "sha512-NYHA0MRPjvNX+vSw8Xxg6FLKxzAG+e7Pt8RqAQA/EehzHVXq9SxDqJIN3JL1hK0dweb884y8kIh6rkWvPyg9Wg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@harperfast/extended-iterable": "^1.0.3", + "msgpackr": "^1.11.2", + "node-addon-api": "^6.1.0", + "node-gyp-build-optional-packages": "5.2.2", + "ordered-binary": "^1.5.3", + "weak-lru-cache": "^1.2.2" + }, + "bin": { + "download-lmdb-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@lmdb/lmdb-darwin-arm64": "3.5.1", + "@lmdb/lmdb-darwin-x64": "3.5.1", + "@lmdb/lmdb-linux-arm": "3.5.1", + "@lmdb/lmdb-linux-arm64": "3.5.1", + "@lmdb/lmdb-linux-x64": "3.5.1", + "@lmdb/lmdb-win32-arm64": "3.5.1", + "@lmdb/lmdb-win32-x64": "3.5.1" + } + }, "node_modules/load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", @@ -8061,9 +8294,10 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" }, "node_modules/lodash._escapehtmlchar": { "version": "2.4.1", @@ -8819,6 +9053,39 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "devOptional": true }, + "node_modules/msgpackr": { + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.8.tgz", + "integrity": "sha512-bC4UGzHhVvgDNS7kn9tV8fAucIYUBuGojcaLiz7v+P63Lmtm0Xeji8B/8tYKddALXxJLpwIeBmUN3u64C4YkRA==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "msgpackr-extract": "^3.0.2" + } + }, + "node_modules/msgpackr-extract": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", + "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.2.2" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" + } + }, "node_modules/multipipe": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", @@ -8939,6 +9206,28 @@ "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", "dev": true }, + "node_modules/node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, "node_modules/node.extend": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/node.extend/-/node.extend-1.1.8.tgz", @@ -9270,6 +9559,13 @@ "node": ">= 0.8.0" } }, + "node_modules/ordered-binary": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.6.1.tgz", + "integrity": "sha512-QkCdPooczexPLiXIrbVOPYkR3VO3T6v2OyKRkR1Xbhpy7/LAVXwahnRCgRp78Oe/Ehf0C/HATAxfSr6eA1oX+w==", + "dev": true, + "license": "MIT" + }, "node_modules/ordered-read-streams": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", @@ -10054,6 +10350,7 @@ "version": "3.2.5", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -12148,6 +12445,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true, + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -12607,6 +12905,13 @@ "node >=0.1.95" ] }, + "node_modules/weak-lru-cache": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", + "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==", + "dev": true, + "license": "MIT" + }, "node_modules/websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", @@ -13533,6 +13838,12 @@ } } }, + "@harperfast/extended-iterable": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@harperfast/extended-iterable/-/extended-iterable-1.0.3.tgz", + "integrity": "sha512-sSAYhQca3rDWtQUHSAPeO7axFIUJOI6hn1gjRC5APVE1a90tuyT8f5WIgRsFhhWA7htNkju2veB9eWL6YHi/Lw==", + "dev": true + }, "@highlightjs/cdn-assets": { "version": "11.5.1", "resolved": "https://registry.npmjs.org/@highlightjs/cdn-assets/-/cdn-assets-11.5.1.tgz", @@ -13586,6 +13897,97 @@ "lodash": "^4.17.21" } }, + "@lmdb/lmdb-darwin-arm64": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.5.1.tgz", + "integrity": "sha512-tpfN4kKrrMpQ+If1l8bhmoNkECJi0iOu6AEdrTJvWVC+32sLxTARX5Rsu579mPImRP9YFWfWgeRQ5oav7zApQQ==", + "dev": true, + "optional": true + }, + "@lmdb/lmdb-darwin-x64": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.5.1.tgz", + "integrity": "sha512-+a2tTfc3rmWhLAolFUWRgJtpSuu+Fw/yjn4rF406NMxhfjbMuiOUTDRvRlMFV+DzyjkwnokisskHbCWkS3Ly5w==", + "dev": true, + "optional": true + }, + "@lmdb/lmdb-linux-arm": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.5.1.tgz", + "integrity": "sha512-0EgcE6reYr8InjD7V37EgXcYrloqpxVPINy3ig1MwDSbl6LF/vXTYRH9OE1Ti1D8YZnB35ZH9aTcdfSb5lql2A==", + "dev": true, + "optional": true + }, + "@lmdb/lmdb-linux-arm64": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.5.1.tgz", + "integrity": "sha512-aoERa5B6ywXdyFeYGQ1gbQpkMkDbEo45qVoXE5QpIRavqjnyPwjOulMkmkypkmsbJ5z4Wi0TBztON8agCTG0Vg==", + "dev": true, + "optional": true + }, + "@lmdb/lmdb-linux-x64": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.5.1.tgz", + "integrity": "sha512-SqNDY1+vpji7bh0sFH5wlWyFTOzjbDOl0/kB5RLLYDAFyd/uw3n7wyrmas3rYPpAW7z18lMOi1yKlTPv967E3g==", + "dev": true, + "optional": true + }, + "@lmdb/lmdb-win32-arm64": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-arm64/-/lmdb-win32-arm64-3.5.1.tgz", + "integrity": "sha512-50v0O1Lt37cwrmR9vWZK5hRW0Aw+KEmxJJ75fge/zIYdvNKB/0bSMSVR5Uc2OV9JhosIUyklOmrEvavwNJ8D6w==", + "dev": true, + "optional": true + }, + "@lmdb/lmdb-win32-x64": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.5.1.tgz", + "integrity": "sha512-qwosvPyl+zpUlp3gRb7UcJ3H8S28XHCzkv0Y0EgQToXjQP91ZD67EHSCDmaLjtKhe+GVIW5om1KUpzVLA0l6pg==", + "dev": true, + "optional": true + }, + "@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", + "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", + "dev": true, + "optional": true + }, + "@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", + "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", + "dev": true, + "optional": true + }, + "@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", + "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", + "dev": true, + "optional": true + }, + "@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", + "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", + "dev": true, + "optional": true + }, + "@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", + "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", + "dev": true, + "optional": true + }, + "@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", + "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", + "dev": true, + "optional": true + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -13613,9 +14015,9 @@ } }, "@phcode/fs": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@phcode/fs/-/fs-3.0.1.tgz", - "integrity": "sha512-IntWO/GdUyC7BX+WeDQUTZmL8inGY//aiFzaphw1eni+0LRh3aGSK61eOlwlpcdA3TRxEzbEyH15NZYaUaWRHg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@phcode/fs/-/fs-4.0.2.tgz", + "integrity": "sha512-vjR2EtMizOj/tf30FObT1KkbBFwLRkVWFsnyUdt0sex6H/lpxRgjKp7NO8YBUKROw66eMoL+7kGyHKAlfV9zWA==", "requires": { "chokidar": "^3.5.3", "ignore": "^5.2.4", @@ -13749,6 +14151,7 @@ "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", "dev": true, + "peer": true, "requires": { "@types/linkify-it": "^5", "@types/mdurl": "^2" @@ -13770,7 +14173,8 @@ "version": "17.0.8", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.8.tgz", "integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==", - "dev": true + "dev": true, + "peer": true }, "@types/normalize-package-data": { "version": "2.4.4", @@ -13809,7 +14213,8 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true + "dev": true, + "peer": true }, "acorn-jsx": { "version": "5.3.2", @@ -15924,6 +16329,12 @@ "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", "dev": true }, + "detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true + }, "devicon": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/devicon/-/devicon-2.15.1.tgz", @@ -16250,6 +16661,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.19.0.tgz", "integrity": "sha512-SXOPj3x9VKvPe81TjjUJCYlV4oJjQw68Uek+AM0X4p+33dj2HY5bpTZOgnQHcG2eAm1mtCU9uNMnJi7exU/kYw==", "dev": true, + "peer": true, "requires": { "@eslint/eslintrc": "^1.3.0", "@humanwhocodes/config-array": "^0.9.2", @@ -17603,6 +18015,7 @@ "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", "dev": true, + "peer": true, "requires": { "glob-watcher": "^5.0.3", "gulp-cli": "^2.2.0", @@ -19310,6 +19723,27 @@ "uc.micro": "^2.0.0" } }, + "lmdb": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.5.1.tgz", + "integrity": "sha512-NYHA0MRPjvNX+vSw8Xxg6FLKxzAG+e7Pt8RqAQA/EehzHVXq9SxDqJIN3JL1hK0dweb884y8kIh6rkWvPyg9Wg==", + "dev": true, + "requires": { + "@harperfast/extended-iterable": "^1.0.3", + "@lmdb/lmdb-darwin-arm64": "3.5.1", + "@lmdb/lmdb-darwin-x64": "3.5.1", + "@lmdb/lmdb-linux-arm": "3.5.1", + "@lmdb/lmdb-linux-arm64": "3.5.1", + "@lmdb/lmdb-linux-x64": "3.5.1", + "@lmdb/lmdb-win32-arm64": "3.5.1", + "@lmdb/lmdb-win32-x64": "3.5.1", + "msgpackr": "^1.11.2", + "node-addon-api": "^6.1.0", + "node-gyp-build-optional-packages": "5.2.2", + "ordered-binary": "^1.5.3", + "weak-lru-cache": "^1.2.2" + } + }, "load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", @@ -19333,9 +19767,9 @@ } }, "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==" }, "lodash._escapehtmlchar": { "version": "2.4.1", @@ -19977,6 +20411,31 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "devOptional": true }, + "msgpackr": { + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.8.tgz", + "integrity": "sha512-bC4UGzHhVvgDNS7kn9tV8fAucIYUBuGojcaLiz7v+P63Lmtm0Xeji8B/8tYKddALXxJLpwIeBmUN3u64C4YkRA==", + "dev": true, + "requires": { + "msgpackr-extract": "^3.0.2" + } + }, + "msgpackr-extract": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", + "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", + "dev": true, + "optional": true, + "requires": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3", + "node-gyp-build-optional-packages": "5.2.2" + } + }, "multipipe": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", @@ -20078,6 +20537,21 @@ "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", "dev": true }, + "node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", + "dev": true + }, + "node-gyp-build-optional-packages": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", + "dev": true, + "requires": { + "detect-libc": "^2.0.1" + } + }, "node.extend": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/node.extend/-/node.extend-1.1.8.tgz", @@ -20329,6 +20803,12 @@ "word-wrap": "^1.2.3" } }, + "ordered-binary": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.6.1.tgz", + "integrity": "sha512-QkCdPooczexPLiXIrbVOPYkR3VO3T6v2OyKRkR1Xbhpy7/LAVXwahnRCgRp78Oe/Ehf0C/HATAxfSr6eA1oX+w==", + "dev": true + }, "ordered-read-streams": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", @@ -20987,7 +21467,8 @@ "prettier": { "version": "3.2.5", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", - "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==" + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "peer": true }, "pretty-hrtime": { "version": "1.0.3", @@ -22668,7 +23149,8 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true + "dev": true, + "peer": true }, "typical": { "version": "7.3.0", @@ -23043,6 +23525,12 @@ "integrity": "sha1-6NugkbdFZ5mjr1eXi5hud+EyBAY=", "dev": true }, + "weak-lru-cache": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", + "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==", + "dev": true + }, "websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", diff --git a/package.json b/package.json index 72a8396309..e93657da42 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "phoenix", - "version": "5.0.5-0", - "apiVersion": "5.0.5", + "version": "5.1.4-0", + "apiVersion": "5.1.4", "homepage": "https://core.ai", "issues": { "url": "https://github.com/phcode-dev/phoenix/issues" @@ -38,9 +38,11 @@ "jasmine-reporters": "^2.5.0", "jsdoc-to-markdown": "^9.1.1", "readable-stream": "^3.6.0", - "through2": "^4.0.2" + "through2": "^4.0.2", + "lmdb": "^3.5.1" }, "scripts": { + "postinstall": "npm install --prefix phoenix-builder-mcp", "lint": "eslint --quiet src test", "lint:fix": "eslint --quiet --fix src test", "prepare": "husky install", @@ -94,7 +96,7 @@ "@floating-ui/dom": "^0.5.4", "@fortawesome/fontawesome-free": "^6.1.2", "@highlightjs/cdn-assets": "^11.5.1", - "@phcode/fs": "^3.0.1", + "@phcode/fs": "^4.0.2", "@phcode/language-support": "^1.1.0", "@pixelbrackets/gfm-stylesheet": "^1.1.0", "@prettier/plugin-php": "^0.22.2", @@ -110,7 +112,7 @@ "jshint": "^2.13.5", "jszip": "^3.8.0", "less": "^4.1.3", - "lodash": "^4.17.21", + "lodash": "^4.17.23", "lru-cache": "^10.2.0", "marked": "^4.0.18", "mime-db": "^1.52.0", diff --git a/phoenix-builder-mcp/README.md b/phoenix-builder-mcp/README.md new file mode 100644 index 0000000000..9d088b8535 --- /dev/null +++ b/phoenix-builder-mcp/README.md @@ -0,0 +1,128 @@ +# Phoenix Builder MCP + +An MCP (Model Context Protocol) server that lets Claude Code launch, control, and inspect a running Phoenix Code instance. It also includes a Chrome extension that enables screenshot capture when Phoenix runs in a browser. + +## Prerequisites + +- Node.js +- The [phoenix-desktop](https://github.com/nicedoc/phoenix-desktop) repo cloned alongside this repo (i.e. `../phoenix-desktop`) + +## Setup + +### 1. Install dependencies + +```bash +cd phoenix-builder-mcp +npm install +``` + +### 2. Claude Code MCP configuration + +The project root already contains `.mcp.json` which registers the server automatically: + +```json +{ + "mcpServers": { + "phoenix-builder": { + "command": "node", + "args": ["phoenix-builder-mcp/index.js"], + "env": { + "PHOENIX_DESKTOP_PATH": "../phoenix-desktop" + } + } + } +} +``` + +Set `PHOENIX_DESKTOP_PATH` to the path of your phoenix-desktop checkout if it is not at `../phoenix-desktop`. + +You can also set `PHOENIX_MCP_WS_PORT` (default `38571`) to change the WebSocket port used for communication between the MCP server and the Phoenix browser runtime. + +### 3. Chrome extension (for browser screenshots) + +Screenshots work out of the box in the Electron/Tauri desktop app. If you are running Phoenix in a browser (e.g. `localhost` or `phcode.dev`), you need to install the Chrome extension: + +#### Loading as an unpacked extension (development) + +1. Open `chrome://extensions` in Chrome. +2. Enable **Developer mode** (toggle in the top-right corner). +3. Click **Load unpacked**. +4. Select the `phoenix-builder-mcp/chrome_extension/` directory. +5. The extension will appear as "Phoenix Code Screenshot". + +Once loaded, any Phoenix page on `localhost` or `phcode.dev` will have `window._phoenixScreenshotExtensionAvailable` set to `true`, and the `take_screenshot` MCP tool and `Phoenix.app.screenShotBinary()` API will work in the browser. + +#### Building a .zip for distribution + +```bash +cd phoenix-builder-mcp/chrome_extension +./build.sh +``` + +This produces `chrome_extension/build/phoenix-screenshot-extension.zip`. + +To build a signed `.crx` you need the Chrome binary and a private key: + +```bash +chrome --pack-extension=./phoenix-builder-mcp/chrome_extension --pack-extension-key=key.pem +``` + +## MCP Tools + +Once the MCP server is running, the following tools are available in Claude Code: + +### `start_phoenix` +Launches the Phoenix Code Electron app by running `npm run serve:electron` in the phoenix-desktop directory. Returns the process PID and WebSocket port. + +### `stop_phoenix` +Stops the running Phoenix Code process (SIGTERM, then SIGKILL after 5s). + +### `get_phoenix_status` +Returns process status, PID, WebSocket connection state, connected instance names, and the WS port. + +### `get_terminal_logs` +Returns stdout/stderr from the Electron process. By default returns only new logs since the last call. Pass `clear: true` to get all logs and clear the buffer. + +### `get_browser_console_logs` +Returns `console.log`/`warn`/`error` output forwarded from the Phoenix browser runtime over WebSocket. Supports the same `clear` flag. When multiple Phoenix instances are connected, pass `instance` to target a specific one (e.g. `"Phoenix-a3f2"`). + +### `take_screenshot` +Captures a PNG screenshot of the Phoenix window. Optionally pass a `selector` (CSS selector string) to capture a specific element. Returns the image directly as `image/png`. + +In Electron/Tauri this uses the native capture API. In the browser it requires the Chrome extension (see above). + +### `reload_phoenix` +Reloads the Phoenix app. Prompts to save unsaved files before reloading. + +### `force_reload_phoenix` +Force-reloads the Phoenix app without saving unsaved changes. + +## Typical Claude Code workflow + +``` +> start_phoenix # launches the app +> take_screenshot # see what the UI looks like +> get_browser_console_logs # check for errors +> reload_phoenix # pick up code changes +> take_screenshot # verify the fix +> stop_phoenix # done +``` + +## Architecture + +``` +Claude Code <--stdio--> MCP Server (index.js) + | + +-- process-manager.js (spawns/kills Electron) + +-- ws-control-server.js (WebSocket on port 38571) + | + Phoenix browser runtime + (connects back over WS for logs, screenshots, reload) +``` + +For browser-mode screenshots the flow is: + +``` +MCP Server --WS--> Phoenix runtime --postMessage--> Content Script --chrome.runtime--> Background SW + (captureVisibleTab) +``` diff --git a/phoenix-builder-mcp/chrome_extension/background.js b/phoenix-builder-mcp/chrome_extension/background.js new file mode 100644 index 0000000000..aa83f8959f --- /dev/null +++ b/phoenix-builder-mcp/chrome_extension/background.js @@ -0,0 +1,13 @@ +chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (message.type !== "phoenix_screenshot_capture") { + return false; + } + chrome.tabs.captureVisibleTab(null, { format: "png" }) + .then(dataUrl => { + sendResponse({ success: true, dataUrl }); + }) + .catch(err => { + sendResponse({ success: false, error: err.message || String(err) }); + }); + return true; // keep channel open for async sendResponse +}); diff --git a/phoenix-builder-mcp/chrome_extension/build.sh b/phoenix-builder-mcp/chrome_extension/build.sh new file mode 100755 index 0000000000..45fcd1e96e --- /dev/null +++ b/phoenix-builder-mcp/chrome_extension/build.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# Builds a .zip of the Chrome extension for distribution or local install. +# Usage: ./build.sh +# +# To load as an unpacked extension during development: +# 1. Open chrome://extensions +# 2. Enable "Developer mode" +# 3. Click "Load unpacked" and select this directory +# +# To build a .crx (signed package) you need the Chrome binary and a private key: +# chrome --pack-extension=./phoenix-builder-mcp/chrome_extension --pack-extension-key=key.pem + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +BUILD_DIR="$SCRIPT_DIR/build" + +rm -rf "$BUILD_DIR" +mkdir -p "$BUILD_DIR" + +zip -j "$BUILD_DIR/phoenix-screenshot-extension.zip" \ + "$SCRIPT_DIR/manifest.json" \ + "$SCRIPT_DIR/background.js" \ + "$SCRIPT_DIR/content-script.js" \ + "$SCRIPT_DIR/page-script.js" + +echo "Built: $BUILD_DIR/phoenix-screenshot-extension.zip" diff --git a/phoenix-builder-mcp/chrome_extension/content-script.js b/phoenix-builder-mcp/chrome_extension/content-script.js new file mode 100644 index 0000000000..a6993556fb --- /dev/null +++ b/phoenix-builder-mcp/chrome_extension/content-script.js @@ -0,0 +1,27 @@ +// Relay screenshot requests from the page to the background service worker. +// The availability flag (window._phoenixScreenshotExtensionAvailable) is set by +// page-script.js which runs in the MAIN world via the manifest. +window.addEventListener("message", (event) => { + if (event.source !== window || !event.data || event.data.type !== "phoenix_screenshot_request") { + return; + } + const requestId = event.data.id; + chrome.runtime.sendMessage({ type: "phoenix_screenshot_capture" }, (response) => { + if (chrome.runtime.lastError) { + window.postMessage({ + type: "phoenix_screenshot_response", + id: requestId, + success: false, + error: chrome.runtime.lastError.message || "Extension communication error" + }, "*"); + return; + } + window.postMessage({ + type: "phoenix_screenshot_response", + id: requestId, + success: response.success, + dataUrl: response.dataUrl, + error: response.error + }, "*"); + }); +}); diff --git a/phoenix-builder-mcp/chrome_extension/manifest.json b/phoenix-builder-mcp/chrome_extension/manifest.json new file mode 100644 index 0000000000..33e83db6ff --- /dev/null +++ b/phoenix-builder-mcp/chrome_extension/manifest.json @@ -0,0 +1,36 @@ +{ + "manifest_version": 3, + "name": "Phoenix Code Screenshot", + "version": "1.0.0", + "description": "Enables screenshot capture in Phoenix Code when running in the browser.", + "permissions": [], + "host_permissions": [ + "" + ], + "background": { + "service_worker": "background.js" + }, + "content_scripts": [ + { + "matches": [ + "http://localhost/*", + "https://phcode.dev/*", + "https://*.phcode.dev/*" + ], + "js": ["page-script.js"], + "run_at": "document_start", + "all_frames": false, + "world": "MAIN" + }, + { + "matches": [ + "http://localhost/*", + "https://phcode.dev/*", + "https://*.phcode.dev/*" + ], + "js": ["content-script.js"], + "run_at": "document_start", + "all_frames": false + } + ] +} diff --git a/phoenix-builder-mcp/chrome_extension/page-script.js b/phoenix-builder-mcp/chrome_extension/page-script.js new file mode 100644 index 0000000000..9b76622271 --- /dev/null +++ b/phoenix-builder-mcp/chrome_extension/page-script.js @@ -0,0 +1,3 @@ +// Runs in the MAIN world (the page's own JS context) at document_start, +// so it executes before deferred modules like shell.js. +window._phoenixScreenshotExtensionAvailable = true; diff --git a/phoenix-builder-mcp/index.js b/phoenix-builder-mcp/index.js new file mode 100644 index 0000000000..4b8f7f282e --- /dev/null +++ b/phoenix-builder-mcp/index.js @@ -0,0 +1,73 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { createWSControlServer } from "./ws-control-server.js"; +import { createProcessManager } from "./process-manager.js"; +import { registerTools } from "./mcp-tools.js"; +import { fileURLToPath } from "url"; +import path from "path"; +import fs from "fs"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const PID_FILE = path.join(__dirname, ".mcp-server.pid"); + +// Kill any previous MCP server instance that wasn't cleaned up (e.g. parent crashed). +try { + const oldPid = parseInt(fs.readFileSync(PID_FILE, "utf8").trim(), 10); + if (oldPid && oldPid !== process.pid) { + try { + process.kill(oldPid, "SIGTERM"); + // Wait up to 3 seconds for it to exit + const deadline = Date.now() + 3000; + while (Date.now() < deadline) { + try { + process.kill(oldPid, 0); // throws if process is gone + await new Promise(r => setTimeout(r, 100)); + } catch { + break; + } + } + } catch { + // Process already dead — nothing to do + } + } +} catch { + // No PID file or unreadable — first run +} +fs.writeFileSync(PID_FILE, String(process.pid)); + +function removePidFile() { + try { fs.unlinkSync(PID_FILE); } catch { /* ignore */ } +} + +const wsPort = parseInt(process.env.PHOENIX_MCP_WS_PORT || "38571", 10); +const phoenixDesktopPath = process.env.PHOENIX_DESKTOP_PATH + || path.resolve(__dirname, "../../phoenix-desktop"); + +const wsControlServer = createWSControlServer(wsPort); +const processManager = createProcessManager(); + +const server = new McpServer({ + name: "phoenix-builder", + version: "1.0.0" +}); + +registerTools(server, processManager, wsControlServer, phoenixDesktopPath); + +const transport = new StdioServerTransport(); +await server.connect(transport); + +process.on("SIGINT", async () => { + await processManager.stop(); + wsControlServer.close(); + removePidFile(); + process.exit(0); +}); + +process.on("SIGTERM", async () => { + await processManager.stop(); + wsControlServer.close(); + removePidFile(); + process.exit(0); +}); diff --git a/phoenix-builder-mcp/log-buffer.js b/phoenix-builder-mcp/log-buffer.js new file mode 100644 index 0000000000..f11b3d2a28 --- /dev/null +++ b/phoenix-builder-mcp/log-buffer.js @@ -0,0 +1,51 @@ +const MAX_ENTRIES = 10000; + +export class LogBuffer { + constructor() { + this._entries = []; + this._readIndex = 0; + this._totalPushed = 0; + } + + push(entry) { + this._entries.push(entry); + this._totalPushed++; + if (this._entries.length > MAX_ENTRIES) { + const overflow = this._entries.length - MAX_ENTRIES; + this._entries.splice(0, overflow); + this._readIndex = Math.max(0, this._readIndex - overflow); + } + } + + getAll() { + return this._entries.slice(); + } + + getSinceLastRead() { + const newEntries = this._entries.slice(this._readIndex); + this._readIndex = this._entries.length; + return newEntries; + } + + totalPushed() { + return this._totalPushed; + } + + getTail(n, before) { + const firstIndex = this._totalPushed - this._entries.length; + let endIdx = this._entries.length; + if (before != null) { + endIdx = Math.max(0, Math.min(this._entries.length, before - firstIndex)); + } + if (n === 0) { + return this._entries.slice(0, endIdx); + } + const startIdx = Math.max(0, endIdx - n); + return this._entries.slice(startIdx, endIdx); + } + + clear() { + this._entries = []; + this._readIndex = 0; + } +} diff --git a/phoenix-builder-mcp/mcp-tools.js b/phoenix-builder-mcp/mcp-tools.js new file mode 100644 index 0000000000..ded3e6d45a --- /dev/null +++ b/phoenix-builder-mcp/mcp-tools.js @@ -0,0 +1,396 @@ +import { z } from "zod"; + +const DEFAULT_MAX_CHARS = 10000; + +function _trimToCharBudget(lines, maxChars) { + let total = 0; + // Walk backwards (newest first) to keep the most recent entries + let startIdx = lines.length; + for (let i = lines.length - 1; i >= 0; i--) { + const cost = lines[i].length + 1; // +1 for newline + if (total + cost > maxChars) { break; } + total += cost; + startIdx = i; + } + return { lines: lines.slice(startIdx), trimmed: startIdx }; +} + +export function registerTools(server, processManager, wsControlServer, phoenixDesktopPath) { + server.tool( + "start_phoenix", + "Start the Phoenix Code desktop app (Electron). Launches npm run serve:electron in the phoenix-desktop directory.", + {}, + async () => { + try { + if (processManager.isRunning()) { + return { + content: [{ + type: "text", + text: JSON.stringify({ + success: false, + error: "Phoenix is already running", + pid: processManager.getPid() + }) + }] + }; + } + const result = await processManager.start(phoenixDesktopPath); + return { + content: [{ + type: "text", + text: JSON.stringify({ + success: true, + pid: result.pid, + wsPort: wsControlServer.getPort() + }) + }] + }; + } catch (err) { + return { + content: [{ + type: "text", + text: JSON.stringify({ success: false, error: err.message }) + }] + }; + } + } + ); + + server.tool( + "stop_phoenix", + "Stop the running Phoenix Code desktop app.", + {}, + async () => { + try { + const result = await processManager.stop(); + return { + content: [{ + type: "text", + text: JSON.stringify(result) + }] + }; + } catch (err) { + return { + content: [{ + type: "text", + text: JSON.stringify({ success: false, error: err.message }) + }] + }; + } + } + ); + + server.tool( + "get_terminal_logs", + "Get stdout/stderr output from the Electron process. Returns last 50 entries by default. " + + "USAGE: Start with default tail=50. Use filter (regex) to narrow results (e.g. filter='error|warn'). " + + "Use before=N (from previous totalEntries) to page back. Avoid tail=0 unless necessary — " + + "prefer filter + small tail to keep responses compact.", + { + clear: z.boolean().default(false).describe("If true, return all logs and clear the buffer. If false, return only new logs since last read."), + tail: z.number().default(50).describe("Return last N entries. 0 = all."), + before: z.number().optional().describe("Cursor: return entries before this totalEntries position. Use the totalEntries value from a previous response to page back stably."), + filter: z.string().optional().describe("Optional regex to filter log entries by text content. Applied before tail/before."), + maxChars: z.number().default(DEFAULT_MAX_CHARS).describe("Max character budget for log content. Oldest entries are dropped first to fit. 0 = unlimited.") + }, + async ({ clear, tail, before, filter, maxChars }) => { + let logs; + if (clear) { + logs = processManager.getTerminalLogs(false); + processManager.clearTerminalLogs(); + } else { + logs = processManager.getTerminalLogs(true); + } + const totalEntries = processManager.getTerminalLogsTotalPushed(); + let filterRe; + if (filter) { + try { + filterRe = new RegExp(filter, "i"); + } catch (e) { + return { + content: [{ + type: "text", + text: `Invalid filter regex: ${e.message}` + }] + }; + } + logs = logs.filter(e => filterRe.test(e.text)); + } + const matchedEntries = logs.length; + const endIdx = before != null ? Math.max(0, Math.min(matchedEntries, before)) : matchedEntries; + if (tail > 0) { + const startIdx = Math.max(0, endIdx - tail); + logs = logs.slice(startIdx, endIdx); + } else { + logs = logs.slice(0, endIdx); + } + let lines = logs.map(e => `[${e.stream}] ${e.text}`); + let trimmed = 0; + if (maxChars > 0) { + const result = _trimToCharBudget(lines, maxChars); + lines = result.lines; + trimmed = result.trimmed; + } + const showing = lines.length; + const rangeEnd = endIdx; + const rangeStart = rangeEnd - logs.length; + const actualStart = rangeStart + trimmed; + const hasMore = actualStart > 0; + let header = `[Logs: ${totalEntries} total`; + if (filter) { + header += `, ${matchedEntries} matched /${filter}/i`; + } + header += `, showing ${actualStart}-${rangeEnd} (${showing} entries).`; + if (trimmed > 0) { + header += ` ${trimmed} entries trimmed to fit maxChars=${maxChars}.`; + } + if (hasMore) { + header += ` hasMore=true, use before=${actualStart} to page back.`; + } + header += `]`; + const text = lines.join(""); + return { + content: [{ + type: "text", + text: text ? header + "\n" + text : "(no terminal logs)" + }] + }; + } + ); + + server.tool( + "get_browser_console_logs", + "Get console logs from the Phoenix browser runtime. Returns last 50 entries by default. " + + "This includes both browser-side console logs and Node.js (PhNode) logs, which are prefixed with 'PhNode:'. " + + "USAGE: Start with default tail=50. Use filter (regex) to narrow results (e.g. filter='error|warn'). " + + "Use before=N (from previous totalEntries) to page back. Avoid tail=0 unless necessary — " + + "prefer filter + small tail to keep responses compact.", + { + instance: z.string().optional().describe("Target a specific Phoenix instance by name (e.g. 'Phoenix-a3f2'). Required when multiple instances are connected."), + tail: z.number().default(50).describe("Return last N entries. 0 = all."), + before: z.number().optional().describe("Cursor: return entries before this totalEntries position. Use the totalEntries value from a previous response to page back stably."), + filter: z.string().optional().describe("Optional regex to filter log entries by message content. Applied before tail/before."), + maxChars: z.number().default(DEFAULT_MAX_CHARS).describe("Max character budget for log content. Oldest entries are dropped first to fit. 0 = unlimited.") + }, + async ({ instance, tail, before, filter, maxChars }) => { + try { + const result = await wsControlServer.requestLogs(instance, { tail, before, filter }); + const entries = result.entries || []; + const totalEntries = result.totalEntries || entries.length; + const matchedEntries = result.matchedEntries != null ? result.matchedEntries : entries.length; + const rangeEnd = result.rangeEnd != null ? result.rangeEnd : matchedEntries; + let lines = entries.map(e => `[${e.level}] ${e.message}`); + let trimmed = 0; + if (maxChars > 0) { + const trimResult = _trimToCharBudget(lines, maxChars); + lines = trimResult.lines; + trimmed = trimResult.trimmed; + } + const showing = lines.length; + const rangeStart = rangeEnd - entries.length; + const actualStart = rangeStart + trimmed; + const hasMore = actualStart > 0; + let header = `[Logs: ${totalEntries} total`; + if (filter) { + header += `, ${matchedEntries} matched /${filter}/i`; + } + header += `, showing ${actualStart}-${rangeEnd} (${showing} entries).`; + if (trimmed > 0) { + header += ` ${trimmed} entries trimmed to fit maxChars=${maxChars}.`; + } + if (hasMore) { + header += ` hasMore=true, use before=${actualStart} to page back.`; + } + header += `]`; + if (showing === 0) { + return { + content: [{ + type: "text", + text: "(no browser logs)" + }] + }; + } + const text = lines.join("\n"); + return { + content: [{ + type: "text", + text: header + "\n" + text + }] + }; + } catch (err) { + return { + content: [{ + type: "text", + text: JSON.stringify({ error: err.message }) + }] + }; + } + } + ); + + server.tool( + "take_screenshot", + "Take a screenshot of the Phoenix Code app window. Returns a PNG image.", + { + selector: z.string().optional().describe("Optional CSS selector to capture a specific element"), + instance: z.string().optional().describe("Target a specific Phoenix instance by name (e.g. 'Phoenix-a3f2'). Required when multiple instances are connected.") + }, + async ({ selector, instance }) => { + try { + const base64Data = await wsControlServer.requestScreenshot(selector, instance); + return { + content: [{ + type: "image", + data: base64Data, + mimeType: "image/png" + }] + }; + } catch (err) { + return { + content: [{ + type: "text", + text: JSON.stringify({ error: err.message }) + }] + }; + } + } + ); + + server.tool( + "reload_phoenix", + "Reload the Phoenix Code app. Closes all open files (prompting to save unsaved changes) then reloads the app.", + { + instance: z.string().optional().describe("Target a specific Phoenix instance by name (e.g. 'Phoenix-a3f2'). Required when multiple instances are connected.") + }, + async ({ instance }) => { + try { + const result = await wsControlServer.requestReload(false, instance); + return { + content: [{ + type: "text", + text: JSON.stringify({ success: true, message: "Phoenix is reloading" }) + }] + }; + } catch (err) { + return { + content: [{ + type: "text", + text: JSON.stringify({ error: err.message }) + }] + }; + } + } + ); + + server.tool( + "force_reload_phoenix", + "Force reload the Phoenix Code app without saving. Closes all open files without saving unsaved changes, then reloads the app.", + { + instance: z.string().optional().describe("Target a specific Phoenix instance by name (e.g. 'Phoenix-a3f2'). Required when multiple instances are connected.") + }, + async ({ instance }) => { + try { + const result = await wsControlServer.requestReload(true, instance); + return { + content: [{ + type: "text", + text: JSON.stringify({ success: true, message: "Phoenix is force reloading (unsaved changes discarded)" }) + }] + }; + } catch (err) { + return { + content: [{ + type: "text", + text: JSON.stringify({ error: err.message }) + }] + }; + } + } + ); + + server.tool( + "exec_js", + "Execute JavaScript in the Phoenix Code browser runtime and return the result. " + + "Code runs async in the page context with access to: " + + "$ (jQuery) for DOM queries/clicks, " + + "brackets.test.CommandManager, brackets.test.EditorManager, brackets.test.ProjectManager, " + + "brackets.test.DocumentManager, brackets.test.FileSystem, brackets.test.FileUtils, " + + "and 50+ other modules on brackets.test.* — " + + "supports await.", + { + code: z.string().describe("JavaScript code to execute in Phoenix"), + instance: z.string().optional().describe("Target a specific Phoenix instance by name (e.g. 'Phoenix-a3f2'). Required when multiple instances are connected.") + }, + async ({ code, instance }) => { + try { + const result = await wsControlServer.requestExecJs(code, instance); + return { + content: [{ + type: "text", + text: result !== undefined ? String(result) : "(undefined)" + }] + }; + } catch (err) { + return { + content: [{ + type: "text", + text: JSON.stringify({ error: err.message }) + }] + }; + } + } + ); + + server.tool( + "exec_js_in_live_preview", + "Execute JavaScript in the live preview iframe (the page being previewed), NOT in Phoenix itself. " + + "Auto-opens the live preview panel if it is not already visible. " + + "Code is evaluated via eval() in the global scope of the previewed page. " + + "Note: eval() is synchronous — async/await is NOT supported. " + + "Only available when an HTML file is selected in the live preview — " + + "does not work for markdown or other non-HTML file types. " + + "Use this to inspect or manipulate the user's live-previewed web page (e.g. document.title, DOM queries).", + { + code: z.string().describe("JavaScript code to execute in the live preview iframe"), + instance: z.string().optional().describe("Target a specific Phoenix instance by name (e.g. 'Phoenix-a3f2'). Required when multiple instances are connected.") + }, + async ({ code, instance }) => { + try { + const result = await wsControlServer.requestExecJsLivePreview(code, instance); + return { + content: [{ + type: "text", + text: result !== undefined ? String(result) : "(undefined)" + }] + }; + } catch (err) { + return { + content: [{ + type: "text", + text: JSON.stringify({ error: err.message }) + }] + }; + } + } + ); + + server.tool( + "get_phoenix_status", + "Check the status of the Phoenix process and WebSocket connection.", + {}, + async () => { + return { + content: [{ + type: "text", + text: JSON.stringify({ + processRunning: processManager.isRunning(), + pid: processManager.getPid(), + wsConnected: wsControlServer.isClientConnected(), + connectedInstances: wsControlServer.getConnectedInstances(), + wsPort: wsControlServer.getPort() + }) + }] + }; + } + ); +} diff --git a/phoenix-builder-mcp/package-lock.json b/phoenix-builder-mcp/package-lock.json new file mode 100644 index 0000000000..64359b6072 --- /dev/null +++ b/phoenix-builder-mcp/package-lock.json @@ -0,0 +1,1164 @@ +{ + "name": "phoenix-builder-mcp", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "phoenix-builder-mcp", + "version": "1.0.0", + "dependencies": { + "@modelcontextprotocol/sdk": "latest", + "ws": "^8.0.0", + "zod": "^3.25.0" + } + }, + "node_modules/@hono/node-server": { + "version": "1.19.9", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", + "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz", + "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "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/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "peer": true, + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", + "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", + "license": "MIT", + "dependencies": { + "ip-address": "10.0.1" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.9.tgz", + "integrity": "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } + } + } +} diff --git a/phoenix-builder-mcp/package.json b/phoenix-builder-mcp/package.json new file mode 100644 index 0000000000..48c700d3e3 --- /dev/null +++ b/phoenix-builder-mcp/package.json @@ -0,0 +1,12 @@ +{ + "name": "phoenix-builder-mcp", + "version": "1.0.0", + "private": true, + "type": "module", + "main": "index.js", + "dependencies": { + "@modelcontextprotocol/sdk": "latest", + "ws": "^8.0.0", + "zod": "^3.25.0" + } +} diff --git a/phoenix-builder-mcp/process-manager.js b/phoenix-builder-mcp/process-manager.js new file mode 100644 index 0000000000..cafc8b0289 --- /dev/null +++ b/phoenix-builder-mcp/process-manager.js @@ -0,0 +1,128 @@ +import { spawn } from "child_process"; +import { LogBuffer } from "./log-buffer.js"; + +export function createProcessManager() { + let childProcess = null; + const terminalLogs = new LogBuffer(); + + function start(phoenixDesktopPath) { + if (childProcess) { + throw new Error("Phoenix is already running. Stop it first."); + } + + return new Promise((resolve, reject) => { + const child = spawn("npm", ["run", "serve:electron"], { + cwd: phoenixDesktopPath, + shell: true, + stdio: ["ignore", "pipe", "pipe"], + env: { ...process.env } + }); + + childProcess = child; + + child.stdout.on("data", (data) => { + const text = data.toString(); + terminalLogs.push({ + stream: "stdout", + text, + timestamp: new Date().toISOString() + }); + }); + + child.stderr.on("data", (data) => { + const text = data.toString(); + terminalLogs.push({ + stream: "stderr", + text, + timestamp: new Date().toISOString() + }); + }); + + child.on("error", (err) => { + terminalLogs.push({ + stream: "stderr", + text: `Process error: ${err.message}`, + timestamp: new Date().toISOString() + }); + childProcess = null; + reject(err); + }); + + child.on("exit", (code, signal) => { + terminalLogs.push({ + stream: "stderr", + text: `Process exited with code=${code} signal=${signal}`, + timestamp: new Date().toISOString() + }); + childProcess = null; + }); + + // Give the process a moment to start or fail + setTimeout(() => { + if (childProcess) { + resolve({ pid: child.pid }); + } + }, 500); + }); + } + + function stop() { + return new Promise((resolve) => { + if (!childProcess) { + resolve({ success: true, message: "No process running" }); + return; + } + + const child = childProcess; + let killed = false; + + const forceKillTimeout = setTimeout(() => { + if (childProcess === child) { + child.kill("SIGKILL"); + killed = true; + } + }, 5000); + + child.on("exit", () => { + clearTimeout(forceKillTimeout); + childProcess = null; + resolve({ success: true, forced: killed }); + }); + + child.kill("SIGTERM"); + }); + } + + function isRunning() { + return childProcess !== null; + } + + function getPid() { + return childProcess ? childProcess.pid : null; + } + + function getTerminalLogs(sinceLast) { + if (sinceLast) { + return terminalLogs.getSinceLastRead(); + } + return terminalLogs.getAll(); + } + + function clearTerminalLogs() { + terminalLogs.clear(); + } + + function getTerminalLogsTotalPushed() { + return terminalLogs.totalPushed(); + } + + return { + start, + stop, + isRunning, + getPid, + getTerminalLogs, + clearTerminalLogs, + getTerminalLogsTotalPushed + }; +} diff --git a/phoenix-builder-mcp/ws-control-server.js b/phoenix-builder-mcp/ws-control-server.js new file mode 100644 index 0000000000..452a2ac866 --- /dev/null +++ b/phoenix-builder-mcp/ws-control-server.js @@ -0,0 +1,452 @@ +import { WebSocketServer } from "ws"; +import { LogBuffer } from "./log-buffer.js"; + +export function createWSControlServer(port) { + const wss = new WebSocketServer({ port }); + const clients = new Map(); // name -> { ws, logs, isAlive } + let unknownCounter = 0; + let requestIdCounter = 0; + const pendingRequests = new Map(); + let heartbeatInterval = null; + + wss.on("connection", (ws) => { + // Name is assigned when the client sends a "hello" message. + // Track the ws temporarily so we can map it back on close/error. + let clientName = null; + + ws.on("message", (data) => { + let msg; + try { + msg = JSON.parse(data.toString()); + } catch { + return; + } + + switch (msg.type) { + case "hello": { + clientName = msg.name || ("Unknown-" + (++unknownCounter)); + + // If same name reconnects (e.g. tab reload), close old connection + // but preserve the existing log buffer so logs survive across reloads + const existing = clients.get(clientName); + if (existing) { + try { + existing.ws.close(1000, "Replaced by new connection"); + } catch { + // ignore + } + clients.set(clientName, { + ws: ws, + logs: existing.logs, + isAlive: true + }); + } else { + clients.set(clientName, { + ws: ws, + logs: new LogBuffer(), + isAlive: true + }); + } + break; + } + + case "console_log": { + const client = clientName && clients.get(clientName); + if (client && Array.isArray(msg.entries)) { + for (const entry of msg.entries) { + client.logs.push(entry); + } + } + break; + } + + case "screenshot_response": { + const pending = pendingRequests.get(msg.id); + if (pending) { + pendingRequests.delete(msg.id); + pending.resolve(msg.data); + } + break; + } + + case "get_logs_response": { + const pending4 = pendingRequests.get(msg.id); + if (pending4) { + pendingRequests.delete(msg.id); + pending4.resolve({ + entries: msg.entries || [], + totalEntries: msg.totalEntries || (msg.entries ? msg.entries.length : 0), + matchedEntries: msg.matchedEntries, + rangeEnd: msg.rangeEnd + }); + } + break; + } + + case "exec_js_response": { + const pending5 = pendingRequests.get(msg.id); + if (pending5) { + pendingRequests.delete(msg.id); + if (msg.error) { + pending5.reject(new Error(msg.error)); + } else { + pending5.resolve(msg.result); + } + } + break; + } + + case "exec_js_live_preview_response": { + const pending6 = pendingRequests.get(msg.id); + if (pending6) { + pendingRequests.delete(msg.id); + if (msg.error) { + pending6.reject(new Error(msg.error)); + } else { + pending6.resolve(msg.result); + } + } + break; + } + + case "reload_response": { + const pending3 = pendingRequests.get(msg.id); + if (pending3) { + pendingRequests.delete(msg.id); + if (msg.success) { + pending3.resolve({ success: true }); + } else { + pending3.reject(new Error(msg.message || "Reload failed")); + } + } + break; + } + + case "error": { + const pending2 = pendingRequests.get(msg.id); + if (pending2) { + pendingRequests.delete(msg.id); + pending2.reject(new Error(msg.message || "Unknown error from Phoenix")); + } + break; + } + + case "pong": { + const client = clientName && clients.get(clientName); + if (client) { + client.isAlive = true; + } + break; + } + } + }); + + ws.on("close", () => { + if (clientName && clients.get(clientName)?.ws === ws) { + clients.delete(clientName); + } + }); + + ws.on("error", () => { + if (clientName && clients.get(clientName)?.ws === ws) { + clients.delete(clientName); + } + }); + }); + + // Heartbeat + heartbeatInterval = setInterval(() => { + for (const [name, client] of clients) { + if (!client.isAlive) { + client.ws.terminate(); + clients.delete(name); + continue; + } + client.isAlive = false; + try { + client.ws.send(JSON.stringify({ type: "ping" })); + } catch { + // ignore send errors + } + } + }, 15000); + + function _resolveClient(instanceName) { + if (clients.size === 0) { + return { error: "No Phoenix client connected" }; + } + + if (!instanceName) { + if (clients.size === 1) { + const [name, client] = [...clients.entries()][0]; + return { name, client }; + } + const names = [...clients.keys()]; + return { + error: "Multiple Phoenix instances connected. Specify an instance name: " + + names.join(", ") + }; + } + + const client = clients.get(instanceName); + if (!client) { + const names = [...clients.keys()]; + return { + error: "Instance \"" + instanceName + "\" not found. Available: " + + names.join(", ") + }; + } + + return { name: instanceName, client }; + } + + function requestScreenshot(selector, instanceName) { + return new Promise((resolve, reject) => { + const resolved = _resolveClient(instanceName); + if (resolved.error) { + reject(new Error(resolved.error)); + return; + } + + const { client } = resolved; + if (client.ws.readyState !== 1) { + reject(new Error("Phoenix client \"" + resolved.name + "\" is not connected")); + return; + } + + const id = ++requestIdCounter; + const timeout = setTimeout(() => { + pendingRequests.delete(id); + reject(new Error("Screenshot request timed out (30s)")); + }, 30000); + + pendingRequests.set(id, { + resolve: (data) => { + clearTimeout(timeout); + resolve(data); + }, + reject: (err) => { + clearTimeout(timeout); + reject(err); + } + }); + + const msg = { type: "screenshot_request", id }; + if (selector) { + msg.selector = selector; + } + client.ws.send(JSON.stringify(msg)); + }); + } + + function requestReload(forceClose, instanceName) { + return new Promise((resolve, reject) => { + const resolved = _resolveClient(instanceName); + if (resolved.error) { + reject(new Error(resolved.error)); + return; + } + + const { client } = resolved; + if (client.ws.readyState !== 1) { + reject(new Error("Phoenix client \"" + resolved.name + "\" is not connected")); + return; + } + + const id = ++requestIdCounter; + const timeout = setTimeout(() => { + pendingRequests.delete(id); + reject(new Error("Reload request timed out (30s)")); + }, 30000); + + pendingRequests.set(id, { + resolve: (data) => { + clearTimeout(timeout); + resolve(data); + }, + reject: (err) => { + clearTimeout(timeout); + reject(err); + } + }); + + client.ws.send(JSON.stringify({ + type: "reload_request", + id, + forceClose: !!forceClose + })); + }); + } + + function requestLogs(instanceName, { tail = 50, before, filter } = {}) { + return new Promise((resolve, reject) => { + const resolved = _resolveClient(instanceName); + if (resolved.error) { + reject(new Error(resolved.error)); + return; + } + + const { client } = resolved; + if (client.ws.readyState !== 1) { + reject(new Error("Phoenix client \"" + resolved.name + "\" is not connected")); + return; + } + + const id = ++requestIdCounter; + const timeout = setTimeout(() => { + pendingRequests.delete(id); + reject(new Error("Log request timed out (10s)")); + }, 10000); + + pendingRequests.set(id, { + resolve: (data) => { + clearTimeout(timeout); + resolve(data); + }, + reject: (err) => { + clearTimeout(timeout); + reject(err); + } + }); + + const msg = { type: "get_logs_request", id, tail }; + if (before != null) { + msg.before = before; + } + if (filter) { + msg.filter = filter; + } + client.ws.send(JSON.stringify(msg)); + }); + } + + function requestExecJs(code, instanceName) { + return new Promise((resolve, reject) => { + const resolved = _resolveClient(instanceName); + if (resolved.error) { + reject(new Error(resolved.error)); + return; + } + + const { client } = resolved; + if (client.ws.readyState !== 1) { + reject(new Error("Phoenix client \"" + resolved.name + "\" is not connected")); + return; + } + + const id = ++requestIdCounter; + const timeout = setTimeout(() => { + pendingRequests.delete(id); + reject(new Error("exec_js request timed out (30s)")); + }, 30000); + + pendingRequests.set(id, { + resolve: (data) => { + clearTimeout(timeout); + resolve(data); + }, + reject: (err) => { + clearTimeout(timeout); + reject(err); + } + }); + + client.ws.send(JSON.stringify({ type: "exec_js_request", id, code })); + }); + } + + function requestExecJsLivePreview(code, instanceName) { + return new Promise((resolve, reject) => { + const resolved = _resolveClient(instanceName); + if (resolved.error) { + reject(new Error(resolved.error)); + return; + } + + const { client } = resolved; + if (client.ws.readyState !== 1) { + reject(new Error("Phoenix client \"" + resolved.name + "\" is not connected")); + return; + } + + const id = ++requestIdCounter; + const timeout = setTimeout(() => { + pendingRequests.delete(id); + reject(new Error("exec_js_live_preview request timed out (60s)")); + }, 60000); + + pendingRequests.set(id, { + resolve: (data) => { + clearTimeout(timeout); + resolve(data); + }, + reject: (err) => { + clearTimeout(timeout); + reject(err); + } + }); + + client.ws.send(JSON.stringify({ type: "exec_js_live_preview_request", id, code })); + }); + } + + function getBrowserLogs(sinceLast, instanceName) { + const resolved = _resolveClient(instanceName); + if (resolved.error) { + return { error: resolved.error }; + } + + const { client } = resolved; + if (sinceLast) { + return client.logs.getSinceLastRead(); + } + return client.logs.getAll(); + } + + function clearBrowserLogs(instanceName) { + const resolved = _resolveClient(instanceName); + if (resolved.error) { + return { error: resolved.error }; + } + resolved.client.logs.clear(); + } + + function isClientConnected() { + return clients.size > 0; + } + + function getConnectedInstances() { + return [...clients.keys()]; + } + + function close() { + clearInterval(heartbeatInterval); + for (const [id, pending] of pendingRequests) { + pending.reject(new Error("Server shutting down")); + } + pendingRequests.clear(); + for (const [name, client] of clients) { + try { + client.ws.close(1000, "Server shutting down"); + } catch { + // ignore + } + } + clients.clear(); + wss.close(); + } + + return { + requestScreenshot, + requestReload, + requestLogs, + requestExecJs, + requestExecJsLivePreview, + getBrowserLogs, + clearBrowserLogs, + isClientConnected, + getConnectedInstances, + close, + getPort: () => port + }; +} diff --git a/src-node/claude-code-agent.js b/src-node/claude-code-agent.js new file mode 100644 index 0000000000..4bb66e5d34 --- /dev/null +++ b/src-node/claude-code-agent.js @@ -0,0 +1,486 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2021 - present core.ai . All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + * + */ + +/** + * Claude Code SDK integration via NodeConnector. + * + * Provides AI chat capabilities by bridging the Claude Code CLI/SDK + * with Phoenix's browser-side chat panel. Handles streaming responses, + * edit/write interception, and session management. + */ + +const { execSync } = require("child_process"); +const path = require("path"); + +const CONNECTOR_ID = "ph_ai_claude"; + +// Lazy-loaded ESM module reference +let queryModule = null; + +// Session state +let currentSessionId = null; + +// Active query state +let currentAbortController = null; + +// Streaming throttle +const TEXT_STREAM_THROTTLE_MS = 50; + +const nodeConnector = global.createNodeConnector(CONNECTOR_ID, exports); + +/** + * Lazily import the ESM @anthropic-ai/claude-code module. + */ +async function getQueryFn() { + if (!queryModule) { + queryModule = await import("@anthropic-ai/claude-code"); + } + return queryModule.query; +} + +/** + * Find the user's globally installed Claude CLI, skipping node_modules copies. + */ +function findGlobalClaudeCli() { + const locations = [ + "/usr/local/bin/claude", + "/usr/bin/claude", + (process.env.HOME || "") + "/.local/bin/claude", + (process.env.HOME || "") + "/.nvm/versions/node/" + + (process.version.startsWith("v") ? process.version : "v" + process.version) + + "/bin/claude" + ]; + + // Try 'which -a' first to find all claude binaries, filtering out node_modules + try { + const allPaths = execSync("which -a claude 2>/dev/null || which claude", { encoding: "utf8" }) + .trim() + .split("\n") + .filter(p => p && !p.includes("node_modules")); + if (allPaths.length > 0) { + console.log("[Phoenix AI] Found global Claude CLI at:", allPaths[0]); + return allPaths[0]; + } + } catch { + // which failed, try manual locations + } + + // Check common locations + for (const loc of locations) { + try { + execSync(`test -x "${loc}"`, { encoding: "utf8" }); + console.log("[Phoenix AI] Found global Claude CLI at:", loc); + return loc; + } catch { + // Not found at this location + } + } + + console.log("[Phoenix AI] Global Claude CLI not found"); + return null; +} + +/** + * Check whether Claude CLI is available. + * Called from browser via execPeer("checkAvailability"). + */ +exports.checkAvailability = async function () { + try { + const claudePath = findGlobalClaudeCli(); + if (claudePath) { + // Also verify the SDK can be imported + await getQueryFn(); + return { available: true, claudePath: claudePath }; + } + // No global CLI found — try importing SDK anyway (it might find its own) + await getQueryFn(); + return { available: true, claudePath: null }; + } catch (err) { + return { available: false, claudePath: null, error: err.message }; + } +}; + +/** + * Send a prompt to Claude and stream results back to the browser. + * Called from browser via execPeer("sendPrompt", {prompt, projectPath, sessionAction, model}). + * + * Returns immediately with a requestId. Results are sent as events: + * aiProgress, aiTextStream, aiEditResult, aiError, aiComplete + */ +exports.sendPrompt = async function (params) { + const { prompt, projectPath, sessionAction, model } = params; + const requestId = Date.now().toString(36) + Math.random().toString(36).slice(2, 7); + + // Handle session + if (sessionAction === "new") { + currentSessionId = null; + } + + // Cancel any in-flight query + if (currentAbortController) { + currentAbortController.abort(); + currentAbortController = null; + } + + currentAbortController = new AbortController(); + + // Run the query asynchronously — don't await here so we return requestId immediately + _runQuery(requestId, prompt, projectPath, model, currentAbortController.signal) + .catch(err => { + console.error("[Phoenix AI] Query error:", err); + }); + + return { requestId: requestId }; +}; + +/** + * Cancel the current in-flight query. + */ +exports.cancelQuery = async function () { + if (currentAbortController) { + currentAbortController.abort(); + currentAbortController = null; + // Clear session so next query starts fresh instead of resuming a killed session + currentSessionId = null; + return { success: true }; + } + return { success: false }; +}; + +/** + * Destroy the current session (clear session ID). + */ +exports.destroySession = async function () { + currentSessionId = null; + currentAbortController = null; + return { success: true }; +}; + +/** + * Internal: run a Claude SDK query and stream results back to the browser. + */ +async function _runQuery(requestId, prompt, projectPath, model, signal) { + const collectedEdits = []; + let queryFn; + + try { + queryFn = await getQueryFn(); + } catch (err) { + nodeConnector.triggerPeer("aiError", { + requestId: requestId, + error: "Failed to load Claude Code SDK: " + err.message + }); + return; + } + + // Send initial progress + nodeConnector.triggerPeer("aiProgress", { + requestId: requestId, + message: "Analyzing...", + phase: "start" + }); + + const queryOptions = { + cwd: projectPath || process.cwd(), + maxTurns: 10, + allowedTools: ["Read", "Edit", "Write", "Glob", "Grep"], + permissionMode: "acceptEdits", + includePartialMessages: true, + abortController: currentAbortController, + hooks: { + PreToolUse: [ + { + matcher: "Edit", + hooks: [ + async (input) => { + console.log("[Phoenix AI] Intercepted Edit tool"); + const edit = { + file: input.tool_input.file_path, + oldText: input.tool_input.old_string, + newText: input.tool_input.new_string + }; + collectedEdits.push(edit); + try { + await nodeConnector.execPeer("applyEditToBuffer", edit); + } catch (err) { + console.warn("[Phoenix AI] Failed to apply edit to buffer:", err.message); + } + return { + hookSpecificOutput: { + hookEventName: "PreToolUse", + permissionDecision: "deny", + permissionDecisionReason: "Edit applied successfully via Phoenix editor." + } + }; + } + ] + }, + { + matcher: "Read", + hooks: [ + async (input) => { + const filePath = input.tool_input.file_path; + if (!filePath) { + return undefined; + } + try { + const result = await nodeConnector.execPeer("getFileContent", { filePath }); + if (result && result.isDirty && result.content !== null) { + const MAX_LINES = 2000; + const MAX_LINE_LENGTH = 2000; + const lines = result.content.split("\n"); + const offset = input.tool_input.offset || 0; + const limit = input.tool_input.limit || MAX_LINES; + const selected = lines.slice(offset, offset + limit); + let formatted = selected.map((line, i) => { + const truncated = line.length > MAX_LINE_LENGTH + ? line.slice(0, MAX_LINE_LENGTH) + "..." + : line; + return String(offset + i + 1).padStart(6) + "\t" + truncated; + }).join("\n"); + formatted = filePath + " (unsaved editor content, " + + lines.length + " lines total)\n\n" + formatted; + console.log("[Phoenix AI] Serving dirty file content for:", filePath); + return { + hookSpecificOutput: { + hookEventName: "PreToolUse", + permissionDecision: "deny", + permissionDecisionReason: formatted + } + }; + } + } catch (err) { + console.warn("[Phoenix AI] Failed to check dirty state:", filePath, err.message); + } + return undefined; + } + ] + }, + { + matcher: "Write", + hooks: [ + async (input) => { + console.log("[Phoenix AI] Intercepted Write tool"); + const edit = { + file: input.tool_input.file_path, + oldText: null, + newText: input.tool_input.content + }; + collectedEdits.push(edit); + try { + await nodeConnector.execPeer("applyEditToBuffer", edit); + } catch (err) { + console.warn("[Phoenix AI] Failed to apply write to buffer:", err.message); + } + return { + hookSpecificOutput: { + hookEventName: "PreToolUse", + permissionDecision: "deny", + permissionDecisionReason: "Write applied successfully via Phoenix editor." + } + }; + } + ] + } + ] + } + }; + + // Set Claude CLI path if found + const claudePath = findGlobalClaudeCli(); + if (claudePath) { + queryOptions.pathToClaudeCodeExecutable = claudePath; + } + + if (model) { + queryOptions.model = model; + } + + // Resume session if we have an existing one (already cleared if sessionAction was "new") + if (currentSessionId) { + queryOptions.resume = currentSessionId; + } + + try { + const result = queryFn({ + prompt: prompt, + options: queryOptions + }); + + let accumulatedText = ""; + let lastStreamTime = 0; + + // Tool input tracking + let activeToolName = null; + let activeToolIndex = null; + let activeToolInputJson = ""; + let toolCounter = 0; + let lastToolStreamTime = 0; + + for await (const message of result) { + // Check abort + if (signal.aborted) { + break; + } + + // Capture session_id from first message + if (message.session_id && !currentSessionId) { + currentSessionId = message.session_id; + } + + // Handle streaming events + if (message.type === "stream_event") { + const event = message.event; + + // Tool use start — send initial indicator + if (event.type === "content_block_start" && + event.content_block?.type === "tool_use") { + activeToolName = event.content_block.name; + activeToolIndex = event.index; + activeToolInputJson = ""; + toolCounter++; + nodeConnector.triggerPeer("aiProgress", { + requestId: requestId, + toolName: activeToolName, + toolId: toolCounter, + phase: "tool_use" + }); + } + + // Accumulate tool input JSON and stream preview + if (event.type === "content_block_delta" && + event.delta?.type === "input_json_delta" && + event.index === activeToolIndex) { + activeToolInputJson += event.delta.partial_json; + const now = Date.now(); + if (now - lastToolStreamTime >= TEXT_STREAM_THROTTLE_MS) { + lastToolStreamTime = now; + nodeConnector.triggerPeer("aiToolStream", { + requestId: requestId, + toolId: toolCounter, + toolName: activeToolName, + partialJson: activeToolInputJson + }); + } + } + + // Tool block complete — flush final stream preview and send details + if (event.type === "content_block_stop" && + event.index === activeToolIndex && + activeToolName) { + // Final flush of tool stream (bypasses throttle) + if (activeToolInputJson) { + nodeConnector.triggerPeer("aiToolStream", { + requestId: requestId, + toolId: toolCounter, + toolName: activeToolName, + partialJson: activeToolInputJson + }); + } + let toolInput = {}; + try { + toolInput = JSON.parse(activeToolInputJson); + } catch (e) { + // ignore parse errors + } + nodeConnector.triggerPeer("aiToolInfo", { + requestId: requestId, + toolName: activeToolName, + toolId: toolCounter, + toolInput: toolInput + }); + activeToolName = null; + activeToolIndex = null; + activeToolInputJson = ""; + } + + // Stream text deltas (throttled) + if (event.type === "content_block_delta" && + event.delta?.type === "text_delta") { + accumulatedText += event.delta.text; + const now = Date.now(); + if (now - lastStreamTime >= TEXT_STREAM_THROTTLE_MS) { + lastStreamTime = now; + nodeConnector.triggerPeer("aiTextStream", { + requestId: requestId, + text: accumulatedText + }); + accumulatedText = ""; + } + } + } + } + + // Flush any remaining accumulated text + if (accumulatedText) { + nodeConnector.triggerPeer("aiTextStream", { + requestId: requestId, + text: accumulatedText + }); + } + + // Send collected edits if any + if (collectedEdits.length > 0) { + nodeConnector.triggerPeer("aiEditResult", { + requestId: requestId, + edits: collectedEdits + }); + } + + // Signal completion + nodeConnector.triggerPeer("aiComplete", { + requestId: requestId, + sessionId: currentSessionId + }); + + } catch (err) { + const errMsg = err.message || String(err); + const isAbort = signal.aborted || /abort/i.test(errMsg); + + if (isAbort) { + // Query was cancelled — clear session so next query starts fresh + currentSessionId = null; + nodeConnector.triggerPeer("aiComplete", { + requestId: requestId, + sessionId: null + }); + return; + } + + // If we collected edits before error, send them + if (collectedEdits.length > 0) { + nodeConnector.triggerPeer("aiEditResult", { + requestId: requestId, + edits: collectedEdits + }); + } + + nodeConnector.triggerPeer("aiError", { + requestId: requestId, + error: errMsg + }); + + // Always send aiComplete after aiError so the UI exits streaming state + nodeConnector.triggerPeer("aiComplete", { + requestId: requestId, + sessionId: currentSessionId + }); + } +} diff --git a/src-node/git/processUtils.js b/src-node/git/processUtils.js index e2f3dee2bc..86d760bdc3 100644 --- a/src-node/git/processUtils.js +++ b/src-node/git/processUtils.js @@ -4,8 +4,22 @@ var exec = require("child_process").exec, which = require("which"); var isWin = /^win/.test(process.platform); +var isMac = process.platform === "darwin"; var noop = function () {}; +// Cache for xcode-select CLT check (null = not yet checked) +var _xcodeCliToolsInstalled = null; + +function _isXcodeCliToolsInstalled(callback) { + if (_xcodeCliToolsInstalled !== null) { + return callback(_xcodeCliToolsInstalled); + } + exec("xcode-select -p", function (err) { + _xcodeCliToolsInstalled = !err; + callback(_xcodeCliToolsInstalled); + }); +} + function fixEOL(str) { if (str[str.length - 1] === "\n") { str = str.slice(0, -1); @@ -104,6 +118,17 @@ function executableExists(filename, dir, callback) { var exists = stats.isFile(); if (!exists) { path = undefined; } + // On macOS, /usr/bin/git is a shim that triggers an "Install Xcode CLT" dialog + // when spawned if CLT is not installed. Check for CLT before allowing it. + if (exists && isMac && Path.normalize(path) === "/usr/bin/git") { + return _isXcodeCliToolsInstalled(function (installed) { + if (!installed) { + return callback(null, false, undefined); + } + return callback(null, true, path); + }); + } + return callback(null, exists, path); }); }); diff --git a/src-node/index.js b/src-node/index.js index c3087273f4..95472485af 100644 --- a/src-node/index.js +++ b/src-node/index.js @@ -69,6 +69,7 @@ const LivePreview = require("./live-preview"); require("./test-connection"); require("./utils"); require("./git/cli"); +require("./claude-code-agent"); function randomNonce(byteLength) { const randomBuffer = new Uint8Array(byteLength); crypto.getRandomValues(randomBuffer); diff --git a/src-node/package-lock.json b/src-node/package-lock.json index 6d6d3779b3..9ba207622a 100644 --- a/src-node/package-lock.json +++ b/src-node/package-lock.json @@ -1,26 +1,47 @@ { "name": "@phcode/node-core", - "version": "5.0.0-0", + "version": "5.1.4-0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@phcode/node-core", - "version": "5.0.0-0", + "version": "5.1.4-0", "license": "GNU-AGPL3.0", "dependencies": { + "@anthropic-ai/claude-code": "^1.0.0", "@expo/sudo-prompt": "^9.3.2", - "@phcode/fs": "^3.0.1", + "@phcode/fs": "^4.0.2", "cross-spawn": "^7.0.6", - "lmdb": "^2.9.2", + "lmdb": "^3.5.1", "mime-types": "^2.1.35", - "npm": "10.1.0", + "npm": "11.8.0", "open": "^10.1.0", "which": "^2.0.1", "ws": "^8.17.1" }, "engines": { - "node": "20" + "node": "24" + } + }, + "node_modules/@anthropic-ai/claude-code": { + "version": "1.0.128", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code/-/claude-code-1.0.128.tgz", + "integrity": "sha512-uUg5cFMJfeQetQzFw76Vpbro6DAXst2Lpu8aoZWRFSoQVYu5ZSAnbBoxaWmW/IgnHSqIIvtMwzCoqmcA9j9rNQ==", + "license": "SEE LICENSE IN README.md", + "bin": { + "claude": "cli.js" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "^0.33.5", + "@img/sharp-darwin-x64": "^0.33.5", + "@img/sharp-linux-arm": "^0.33.5", + "@img/sharp-linux-arm64": "^0.33.5", + "@img/sharp-linux-x64": "^0.33.5", + "@img/sharp-win32-x64": "^0.33.5" } }, "node_modules/@expo/sudo-prompt": { @@ -28,155 +49,394 @@ "resolved": "https://registry.npmjs.org/@expo/sudo-prompt/-/sudo-prompt-9.3.2.tgz", "integrity": "sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw==" }, + "node_modules/@harperfast/extended-iterable": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@harperfast/extended-iterable/-/extended-iterable-1.0.3.tgz", + "integrity": "sha512-sSAYhQca3rDWtQUHSAPeO7axFIUJOI6hn1gjRC5APVE1a90tuyT8f5WIgRsFhhWA7htNkju2veB9eWL6YHi/Lw==", + "license": "Apache-2.0" + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@lmdb/lmdb-darwin-arm64": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-2.9.2.tgz", - "integrity": "sha512-+GX51Fi8nZOrEXCFiQHnrCpKAzkfDA2sY5+M6Ry4wZEu711o2qlvg+7xXP+j7OT7+JsfB9ayGCdhra2AAaX02g==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.5.1.tgz", + "integrity": "sha512-tpfN4kKrrMpQ+If1l8bhmoNkECJi0iOu6AEdrTJvWVC+32sLxTARX5Rsu579mPImRP9YFWfWgeRQ5oav7zApQQ==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@lmdb/lmdb-darwin-x64": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-2.9.2.tgz", - "integrity": "sha512-ajkq2oZTd/RXXpgaZqVm6LHoJYf4A42q+S+U4gYKRYpeR4ERGvG+VGCK9bi9MXInQfeq0KM1yv6rsYpvCOoNhQ==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.5.1.tgz", + "integrity": "sha512-+a2tTfc3rmWhLAolFUWRgJtpSuu+Fw/yjn4rF406NMxhfjbMuiOUTDRvRlMFV+DzyjkwnokisskHbCWkS3Ly5w==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@lmdb/lmdb-linux-arm": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-2.9.2.tgz", - "integrity": "sha512-AAdmxDIh1tMYzXOUuDP+TNhvl9pLgvS63M6xhwgVArr79As4msraUSjIJ8J0jlhFKsN7nVoXzPB/jvpp8aK49w==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.5.1.tgz", + "integrity": "sha512-0EgcE6reYr8InjD7V37EgXcYrloqpxVPINy3ig1MwDSbl6LF/vXTYRH9OE1Ti1D8YZnB35ZH9aTcdfSb5lql2A==", "cpu": [ "arm" ], + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@lmdb/lmdb-linux-arm64": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-2.9.2.tgz", - "integrity": "sha512-WqQqWwFyL8JPVpKJyKnyyg7tnsVlD08PHEyxSMxDQC2EkPpvZuUz2oMqasDoy5tmYB0jANOI13/Qz3Mbh9endQ==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.5.1.tgz", + "integrity": "sha512-aoERa5B6ywXdyFeYGQ1gbQpkMkDbEo45qVoXE5QpIRavqjnyPwjOulMkmkypkmsbJ5z4Wi0TBztON8agCTG0Vg==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@lmdb/lmdb-linux-x64": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-2.9.2.tgz", - "integrity": "sha512-rB4tE80EOxXwTJr9rsATWZghOVP8+mV085P5u/dBdttJSq3TLxY0CMZ8NKB/WJpryNnsfCI4OvjOAibF/fg+GQ==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.5.1.tgz", + "integrity": "sha512-SqNDY1+vpji7bh0sFH5wlWyFTOzjbDOl0/kB5RLLYDAFyd/uw3n7wyrmas3rYPpAW7z18lMOi1yKlTPv967E3g==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" ] }, + "node_modules/@lmdb/lmdb-win32-arm64": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-arm64/-/lmdb-win32-arm64-3.5.1.tgz", + "integrity": "sha512-50v0O1Lt37cwrmR9vWZK5hRW0Aw+KEmxJJ75fge/zIYdvNKB/0bSMSVR5Uc2OV9JhosIUyklOmrEvavwNJ8D6w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@lmdb/lmdb-win32-x64": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-2.9.2.tgz", - "integrity": "sha512-VRrM/Zq/k8YEZlGuDvFi3NU753cm+vOa1kUcq4iNyeAEVXzjrSg5K3sHI0d6Od5gLsKctjlQeaFn6+21inU4bw==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.5.1.tgz", + "integrity": "sha512-qwosvPyl+zpUlp3gRb7UcJ3H8S28XHCzkv0Y0EgQToXjQP91ZD67EHSCDmaLjtKhe+GVIW5om1KUpzVLA0l6pg==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.2.tgz", - "integrity": "sha512-9bfjwDxIDWmmOKusUcqdS4Rw+SETlp9Dy39Xui9BEGEk19dDwH0jhipwFzEff/pFg95NKymc6TOTbRKcWeRqyQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", + "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.2.tgz", - "integrity": "sha512-lwriRAHm1Yg4iDf23Oxm9n/t5Zpw1lVnxYU3HnJPTi2lJRkKTrps1KVgvL6m7WvmhYVt/FIsssWay+k45QHeuw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", + "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.2.tgz", - "integrity": "sha512-MOI9Dlfrpi2Cuc7i5dXdxPbFIgbDBGgKR5F2yWEa6FVEtSWncfVNKW5AKjImAQ6CZlBK9tympdsZJ2xThBiWWA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", + "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", "cpu": [ "arm" ], + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.2.tgz", - "integrity": "sha512-FU20Bo66/f7He9Fp9sP2zaJ1Q8L9uLPZQDub/WlUip78JlPeMbVL8546HbZfcW9LNciEXc8d+tThSJjSC+tmsg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", + "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.2.tgz", - "integrity": "sha512-gsWNDCklNy7Ajk0vBBf9jEx04RUxuDQfBse918Ww+Qb9HCPoGzS+XJTLe96iN3BVK7grnLiYghP/M4L8VsaHeA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", + "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.2.tgz", - "integrity": "sha512-O+6Gs8UeDbyFpbSh2CPEz/UOrrdWPTBYNblZK5CxxLisYt4kGX3Sc+czffFonyjiGSq3jWLwJS/CCJc7tBr4sQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", + "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@phcode/fs": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@phcode/fs/-/fs-3.0.1.tgz", - "integrity": "sha512-IntWO/GdUyC7BX+WeDQUTZmL8inGY//aiFzaphw1eni+0LRh3aGSK61eOlwlpcdA3TRxEzbEyH15NZYaUaWRHg==", - "license": "GNU-AGPL3.0", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@phcode/fs/-/fs-4.0.2.tgz", + "integrity": "sha512-vjR2EtMizOj/tf30FObT1KkbBFwLRkVWFsnyUdt0sex6H/lpxRgjKp7NO8YBUKROw66eMoL+7kGyHKAlfV9zWA==", "dependencies": { "chokidar": "^3.5.3", "ignore": "^5.2.4", @@ -305,9 +565,10 @@ } }, "node_modules/detect-libc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", - "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", "engines": { "node": ">=8" } @@ -444,27 +705,30 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/lmdb": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-2.9.2.tgz", - "integrity": "sha512-Q5SQzu4u4sdz4U8QT1uCS04beS7hS/1YYb1suJwaijqVETGAkrPBKr0ERxTeza/u2F6ei5+8UTnzm4ae3PJG3w==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.5.1.tgz", + "integrity": "sha512-NYHA0MRPjvNX+vSw8Xxg6FLKxzAG+e7Pt8RqAQA/EehzHVXq9SxDqJIN3JL1hK0dweb884y8kIh6rkWvPyg9Wg==", "hasInstallScript": true, + "license": "MIT", "dependencies": { - "msgpackr": "^1.9.9", + "@harperfast/extended-iterable": "^1.0.3", + "msgpackr": "^1.11.2", "node-addon-api": "^6.1.0", - "node-gyp-build-optional-packages": "5.1.1", - "ordered-binary": "^1.4.1", + "node-gyp-build-optional-packages": "5.2.2", + "ordered-binary": "^1.5.3", "weak-lru-cache": "^1.2.2" }, "bin": { "download-lmdb-prebuilds": "bin/download-prebuilds.js" }, "optionalDependencies": { - "@lmdb/lmdb-darwin-arm64": "2.9.2", - "@lmdb/lmdb-darwin-x64": "2.9.2", - "@lmdb/lmdb-linux-arm": "2.9.2", - "@lmdb/lmdb-linux-arm64": "2.9.2", - "@lmdb/lmdb-linux-x64": "2.9.2", - "@lmdb/lmdb-win32-x64": "2.9.2" + "@lmdb/lmdb-darwin-arm64": "3.5.1", + "@lmdb/lmdb-darwin-x64": "3.5.1", + "@lmdb/lmdb-linux-arm": "3.5.1", + "@lmdb/lmdb-linux-arm64": "3.5.1", + "@lmdb/lmdb-linux-x64": "3.5.1", + "@lmdb/lmdb-win32-arm64": "3.5.1", + "@lmdb/lmdb-win32-x64": "3.5.1" } }, "node_modules/mime-db": { @@ -487,43 +751,34 @@ } }, "node_modules/msgpackr": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.10.1.tgz", - "integrity": "sha512-r5VRLv9qouXuLiIBrLpl2d5ZvPt8svdQTl5/vMvE4nzDMyEX4sgW5yWhuBBj5UmgwOTWj8CIdSXn5sAfsHAWIQ==", + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.8.tgz", + "integrity": "sha512-bC4UGzHhVvgDNS7kn9tV8fAucIYUBuGojcaLiz7v+P63Lmtm0Xeji8B/8tYKddALXxJLpwIeBmUN3u64C4YkRA==", + "license": "MIT", "optionalDependencies": { "msgpackr-extract": "^3.0.2" } }, "node_modules/msgpackr-extract": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.2.tgz", - "integrity": "sha512-SdzXp4kD/Qf8agZ9+iTu6eql0m3kWm1A2y1hkpTeVNENutaB0BwHlSvAIaMxwntmRUAUjon2V4L8Z/njd0Ct8A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", + "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", "hasInstallScript": true, + "license": "MIT", "optional": true, "dependencies": { - "node-gyp-build-optional-packages": "5.0.7" + "node-gyp-build-optional-packages": "5.2.2" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" }, "optionalDependencies": { - "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.2", - "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.2", - "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.2", - "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.2", - "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.2", - "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.2" - } - }, - "node_modules/msgpackr-extract/node_modules/node-gyp-build-optional-packages": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.7.tgz", - "integrity": "sha512-YlCCc6Wffkx0kHkmam79GKvDQ6x+QZkMjFGrIMxgFNILFvGSbCp2fCBC55pGTT9gVaz8Na5CLmxt/urtzRv36w==", - "optional": true, - "bin": { - "node-gyp-build-optional-packages": "bin.js", - "node-gyp-build-optional-packages-optional": "optional.js", - "node-gyp-build-optional-packages-test": "build-test.js" + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" } }, "node_modules/node-addon-api": { @@ -532,9 +787,10 @@ "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==" }, "node_modules/node-gyp-build-optional-packages": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz", - "integrity": "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", + "license": "MIT", "dependencies": { "detect-libc": "^2.0.1" }, @@ -553,17 +809,19 @@ } }, "node_modules/npm": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/npm/-/npm-10.1.0.tgz", - "integrity": "sha512-pZ2xybXzNGbJFZEKNbPoEXsE38Xou9VTnxxBk+B3pz0ndsGCs7iWHoUCPSsISU2hjmkWfDkJo3bYKE8RDOg4eg==", + "version": "11.8.0", + "resolved": "https://registry.npmjs.org/npm/-/npm-11.8.0.tgz", + "integrity": "sha512-n19sJeW+RGKdkHo8SCc5xhSwkKhQUFfZaFzSc+EsYXLjSqIV0tl72aDYQVuzVvfrbysGwdaQsNLNy58J10EBSQ==", "bundleDependencies": [ "@isaacs/string-locale-compare", "@npmcli/arborist", "@npmcli/config", "@npmcli/fs", "@npmcli/map-workspaces", + "@npmcli/metavuln-calculator", "@npmcli/package-json", "@npmcli/promise-spawn", + "@npmcli/redact", "@npmcli/run-script", "@sigstore/tuf", "abbrev", @@ -572,8 +830,6 @@ "chalk", "ci-info", "cli-columns", - "cli-table3", - "columnify", "fastest-levenshtein", "fs-minipass", "glob", @@ -587,7 +843,6 @@ "libnpmdiff", "libnpmexec", "libnpmfund", - "libnpmhook", "libnpmorg", "libnpmpack", "libnpmpublish", @@ -608,7 +863,6 @@ "npm-profile", "npm-registry-fetch", "npm-user-validate", - "npmlog", "p-map", "pacote", "parse-conflict-json", @@ -616,6 +870,7 @@ "qrcode-terminal", "read", "semver", + "spdx-expression-parse", "ssri", "supports-color", "tar", @@ -623,156 +878,120 @@ "tiny-relative-date", "treeverse", "validate-npm-package-name", - "which", - "write-file-atomic" + "which" + ], + "license": "Artistic-2.0", + "workspaces": [ + "docs", + "smoke-tests", + "mock-globals", + "mock-registry", + "workspaces/*" ], "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^7.1.0", - "@npmcli/config": "^7.2.0", - "@npmcli/fs": "^3.1.0", - "@npmcli/map-workspaces": "^3.0.4", - "@npmcli/package-json": "^5.0.0", - "@npmcli/promise-spawn": "^7.0.0", - "@npmcli/run-script": "^7.0.1", - "@sigstore/tuf": "^2.1.0", - "abbrev": "^2.0.0", + "@npmcli/arborist": "^9.1.10", + "@npmcli/config": "^10.5.0", + "@npmcli/fs": "^5.0.0", + "@npmcli/map-workspaces": "^5.0.3", + "@npmcli/metavuln-calculator": "^9.0.3", + "@npmcli/package-json": "^7.0.4", + "@npmcli/promise-spawn": "^9.0.1", + "@npmcli/redact": "^4.0.0", + "@npmcli/run-script": "^10.0.3", + "@sigstore/tuf": "^4.0.1", + "abbrev": "^4.0.0", "archy": "~1.0.0", - "cacache": "^18.0.0", - "chalk": "^5.3.0", - "ci-info": "^3.8.0", + "cacache": "^20.0.3", + "chalk": "^5.6.2", + "ci-info": "^4.3.1", "cli-columns": "^4.0.0", - "cli-table3": "^0.6.3", - "columnify": "^1.6.0", "fastest-levenshtein": "^1.0.16", "fs-minipass": "^3.0.3", - "glob": "^10.3.3", + "glob": "^13.0.0", "graceful-fs": "^4.2.11", - "hosted-git-info": "^7.0.0", - "ini": "^4.1.1", - "init-package-json": "^6.0.0", - "is-cidr": "^4.0.2", - "json-parse-even-better-errors": "^3.0.0", - "libnpmaccess": "^8.0.0", - "libnpmdiff": "^6.0.1", - "libnpmexec": "^7.0.1", - "libnpmfund": "^4.1.1", - "libnpmhook": "^10.0.0", - "libnpmorg": "^6.0.0", - "libnpmpack": "^6.0.1", - "libnpmpublish": "^9.0.0", - "libnpmsearch": "^7.0.0", - "libnpmteam": "^6.0.0", - "libnpmversion": "^5.0.0", - "make-fetch-happen": "^13.0.0", - "minimatch": "^9.0.3", - "minipass": "^7.0.3", + "hosted-git-info": "^9.0.2", + "ini": "^6.0.0", + "init-package-json": "^8.2.4", + "is-cidr": "^6.0.1", + "json-parse-even-better-errors": "^5.0.0", + "libnpmaccess": "^10.0.3", + "libnpmdiff": "^8.0.13", + "libnpmexec": "^10.1.12", + "libnpmfund": "^7.0.13", + "libnpmorg": "^8.0.1", + "libnpmpack": "^9.0.13", + "libnpmpublish": "^11.1.3", + "libnpmsearch": "^9.0.1", + "libnpmteam": "^8.0.2", + "libnpmversion": "^8.0.3", + "make-fetch-happen": "^15.0.3", + "minimatch": "^10.1.1", + "minipass": "^7.1.1", "minipass-pipeline": "^1.2.4", "ms": "^2.1.2", - "node-gyp": "^9.4.0", - "nopt": "^7.2.0", - "npm-audit-report": "^5.0.0", - "npm-install-checks": "^6.2.0", - "npm-package-arg": "^11.0.0", - "npm-pick-manifest": "^9.0.0", - "npm-profile": "^9.0.0", - "npm-registry-fetch": "^16.0.0", - "npm-user-validate": "^2.0.0", - "npmlog": "^7.0.1", - "p-map": "^4.0.0", - "pacote": "^17.0.4", - "parse-conflict-json": "^3.0.1", - "proc-log": "^3.0.0", + "node-gyp": "^12.1.0", + "nopt": "^9.0.0", + "npm-audit-report": "^7.0.0", + "npm-install-checks": "^8.0.0", + "npm-package-arg": "^13.0.2", + "npm-pick-manifest": "^11.0.3", + "npm-profile": "^12.0.1", + "npm-registry-fetch": "^19.1.1", + "npm-user-validate": "^4.0.0", + "p-map": "^7.0.4", + "pacote": "^21.0.4", + "parse-conflict-json": "^5.0.1", + "proc-log": "^6.1.0", "qrcode-terminal": "^0.12.0", - "read": "^2.1.0", - "semver": "^7.5.4", - "ssri": "^10.0.5", - "supports-color": "^9.4.0", - "tar": "^6.1.15", + "read": "^5.0.1", + "semver": "^7.7.3", + "spdx-expression-parse": "^4.0.0", + "ssri": "^13.0.0", + "supports-color": "^10.2.2", + "tar": "^7.5.4", "text-table": "~0.2.0", - "tiny-relative-date": "^1.3.0", + "tiny-relative-date": "^2.0.2", "treeverse": "^3.0.0", - "validate-npm-package-name": "^5.0.0", - "which": "^4.0.0", - "write-file-atomic": "^5.0.1" + "validate-npm-package-name": "^7.0.2", + "which": "^6.0.0" }, "bin": { "npm": "bin/npm-cli.js", "npx": "bin/npx-cli.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@colors/colors": { - "version": "1.5.0", - "inBundle": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/npm/node_modules/@isaacs/cliui": { - "version": "8.0.2", - "inBundle": 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": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", + "node_modules/npm/node_modules/@isaacs/balanced-match": { + "version": "4.0.1", "inBundle": true, "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "node": "20 || >=22" } }, - "node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", + "node_modules/npm/node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", "inBundle": true, "license": "MIT", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "@isaacs/balanced-match": "^4.0.1" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "20 || >=22" } }, - "node_modules/npm/node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", + "node_modules/npm/node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", "inBundle": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "ansi-regex": "^6.0.1" + "minipass": "^7.0.4" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": ">=18.0.0" } }, "node_modules/npm/node_modules/@isaacs/string-locale-compare": { @@ -781,343 +1000,302 @@ "license": "ISC" }, "node_modules/npm/node_modules/@npmcli/agent": { - "version": "2.1.1", + "version": "4.0.0", "inBundle": true, "license": "ISC", "dependencies": { + "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", - "lru-cache": "^10.0.1", - "socks-proxy-agent": "^8.0.1" + "lru-cache": "^11.2.1", + "socks-proxy-agent": "^8.0.3" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/@npmcli/agent/node_modules/agent-base": { - "version": "7.1.0", + "node_modules/npm/node_modules/@npmcli/arborist": { + "version": "9.1.10", "inBundle": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "debug": "^4.3.4" + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^5.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/map-workspaces": "^5.0.0", + "@npmcli/metavuln-calculator": "^9.0.2", + "@npmcli/name-from-folder": "^4.0.0", + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/query": "^5.0.0", + "@npmcli/redact": "^4.0.0", + "@npmcli/run-script": "^10.0.0", + "bin-links": "^6.0.0", + "cacache": "^20.0.1", + "common-ancestor-path": "^2.0.0", + "hosted-git-info": "^9.0.0", + "json-stringify-nice": "^1.1.4", + "lru-cache": "^11.2.1", + "minimatch": "^10.0.3", + "nopt": "^9.0.0", + "npm-install-checks": "^8.0.0", + "npm-package-arg": "^13.0.0", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "pacote": "^21.0.2", + "parse-conflict-json": "^5.0.1", + "proc-log": "^6.0.0", + "proggy": "^4.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^3.0.1", + "semver": "^7.3.7", + "ssri": "^13.0.0", + "treeverse": "^3.0.0", + "walk-up-path": "^4.0.0" + }, + "bin": { + "arborist": "bin/index.js" }, "engines": { - "node": ">= 14" - } - }, - "node_modules/npm/node_modules/@npmcli/agent/node_modules/http-proxy-agent": { - "version": "7.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/npm/node_modules/@npmcli/agent/node_modules/https-proxy-agent": { - "version": "7.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/npm/node_modules/@npmcli/agent/node_modules/socks-proxy-agent": { - "version": "8.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.0.1", - "debug": "^4.3.4", - "socks": "^2.7.1" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/npm/node_modules/@npmcli/arborist": { - "version": "7.1.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/fs": "^3.1.0", - "@npmcli/installed-package-contents": "^2.0.2", - "@npmcli/map-workspaces": "^3.0.2", - "@npmcli/metavuln-calculator": "^7.0.0", - "@npmcli/name-from-folder": "^2.0.0", - "@npmcli/node-gyp": "^3.0.0", - "@npmcli/package-json": "^5.0.0", - "@npmcli/query": "^3.0.0", - "@npmcli/run-script": "^7.0.1", - "bin-links": "^4.0.1", - "cacache": "^18.0.0", - "common-ancestor-path": "^1.0.1", - "hosted-git-info": "^7.0.0", - "json-parse-even-better-errors": "^3.0.0", - "json-stringify-nice": "^1.1.4", - "minimatch": "^9.0.0", - "nopt": "^7.0.0", - "npm-install-checks": "^6.2.0", - "npm-package-arg": "^11.0.0", - "npm-pick-manifest": "^9.0.0", - "npm-registry-fetch": "^16.0.0", - "npmlog": "^7.0.1", - "pacote": "^17.0.4", - "parse-conflict-json": "^3.0.0", - "proc-log": "^3.0.0", - "promise-all-reject-late": "^1.0.0", - "promise-call-limit": "^1.0.2", - "read-package-json-fast": "^3.0.2", - "semver": "^7.3.7", - "ssri": "^10.0.5", - "treeverse": "^3.0.0", - "walk-up-path": "^3.0.1" - }, - "bin": { - "arborist": "bin/index.js" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/config": { - "version": "7.2.0", + "version": "10.5.0", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/map-workspaces": "^3.0.2", - "ci-info": "^3.8.0", - "ini": "^4.1.0", - "nopt": "^7.0.0", - "proc-log": "^3.0.0", - "read-package-json-fast": "^3.0.2", + "@npmcli/map-workspaces": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "ci-info": "^4.0.0", + "ini": "^6.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.5", - "walk-up-path": "^3.0.1" + "walk-up-path": "^4.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/@npmcli/disparity-colors": { - "version": "3.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "ansi-styles": "^4.3.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/fs": { - "version": "3.1.0", + "version": "5.0.0", "inBundle": true, "license": "ISC", "dependencies": { "semver": "^7.3.5" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/git": { - "version": "5.0.3", + "version": "7.0.1", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/promise-spawn": "^7.0.0", - "lru-cache": "^10.0.1", - "npm-pick-manifest": "^9.0.0", - "proc-log": "^3.0.0", - "promise-inflight": "^1.0.1", + "@npmcli/promise-spawn": "^9.0.0", + "ini": "^6.0.0", + "lru-cache": "^11.2.1", + "npm-pick-manifest": "^11.0.1", + "proc-log": "^6.0.0", "promise-retry": "^2.0.1", "semver": "^7.3.5", - "which": "^4.0.0" + "which": "^6.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/installed-package-contents": { - "version": "2.0.2", + "version": "4.0.0", "inBundle": true, "license": "ISC", "dependencies": { - "npm-bundled": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" + "npm-bundled": "^5.0.0", + "npm-normalize-package-bin": "^5.0.0" }, "bin": { - "installed-package-contents": "lib/index.js" + "installed-package-contents": "bin/index.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/map-workspaces": { - "version": "3.0.4", + "version": "5.0.3", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/name-from-folder": "^2.0.0", - "glob": "^10.2.2", - "minimatch": "^9.0.0", - "read-package-json-fast": "^3.0.0" + "@npmcli/name-from-folder": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "glob": "^13.0.0", + "minimatch": "^10.0.3" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { - "version": "7.0.0", + "version": "9.0.3", "inBundle": true, "license": "ISC", "dependencies": { - "cacache": "^18.0.0", - "json-parse-even-better-errors": "^3.0.0", - "pacote": "^17.0.0", + "cacache": "^20.0.0", + "json-parse-even-better-errors": "^5.0.0", + "pacote": "^21.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.5" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/name-from-folder": { - "version": "2.0.0", + "version": "4.0.0", "inBundle": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/node-gyp": { - "version": "3.0.0", + "version": "5.0.0", "inBundle": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/package-json": { - "version": "5.0.0", + "version": "7.0.4", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/git": "^5.0.0", - "glob": "^10.2.2", - "hosted-git-info": "^7.0.0", - "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^6.0.0", - "proc-log": "^3.0.0", - "semver": "^7.5.3" + "@npmcli/git": "^7.0.0", + "glob": "^13.0.0", + "hosted-git-info": "^9.0.0", + "json-parse-even-better-errors": "^5.0.0", + "proc-log": "^6.0.0", + "semver": "^7.5.3", + "validate-npm-package-license": "^3.0.4" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/promise-spawn": { - "version": "7.0.0", + "version": "9.0.1", "inBundle": true, "license": "ISC", "dependencies": { - "which": "^4.0.0" + "which": "^6.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/query": { - "version": "3.0.0", + "version": "5.0.0", "inBundle": true, "license": "ISC", "dependencies": { - "postcss-selector-parser": "^6.0.10" + "postcss-selector-parser": "^7.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/@npmcli/run-script": { - "version": "7.0.1", + "node_modules/npm/node_modules/@npmcli/redact": { + "version": "4.0.0", "inBundle": true, "license": "ISC", - "dependencies": { - "@npmcli/node-gyp": "^3.0.0", - "@npmcli/promise-spawn": "^7.0.0", - "node-gyp": "^9.0.0", - "read-package-json-fast": "^3.0.0", - "which": "^4.0.0" - }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/@pkgjs/parseargs": { - "version": "0.11.0", + "node_modules/npm/node_modules/@npmcli/run-script": { + "version": "10.0.3", "inBundle": true, - "license": "MIT", - "optional": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "node-gyp": "^12.1.0", + "proc-log": "^6.0.0", + "which": "^6.0.0" + }, "engines": { - "node": ">=14" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@sigstore/bundle": { - "version": "2.1.0", + "version": "4.0.0", "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.2.1" + "@sigstore/protobuf-specs": "^0.5.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@sigstore/core": { + "version": "3.1.0", + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@sigstore/protobuf-specs": { - "version": "0.2.1", + "version": "0.5.0", "inBundle": true, "license": "Apache-2.0", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm/node_modules/@sigstore/sign": { - "version": "2.1.0", + "version": "4.1.0", "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.1.0", - "@sigstore/protobuf-specs": "^0.2.1", - "make-fetch-happen": "^13.0.0" + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0", + "make-fetch-happen": "^15.0.3", + "proc-log": "^6.1.0", + "promise-retry": "^2.0.1" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@sigstore/tuf": { - "version": "2.1.0", + "version": "4.0.1", "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.2.1", - "tuf-js": "^2.1.0" + "@sigstore/protobuf-specs": "^0.5.0", + "tuf-js": "^4.1.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/@tootallnate/once": { - "version": "2.0.0", + "node_modules/npm/node_modules/@sigstore/verify": { + "version": "3.1.0", "inBundle": true, - "license": "MIT", + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0" + }, "engines": { - "node": ">= 10" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@tufjs/canonical-json": { @@ -1129,68 +1307,31 @@ } }, "node_modules/npm/node_modules/@tufjs/models": { - "version": "2.0.0", + "version": "4.1.0", "inBundle": true, "license": "MIT", "dependencies": { "@tufjs/canonical-json": "2.0.0", - "minimatch": "^9.0.3" + "minimatch": "^10.1.1" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/abbrev": { - "version": "2.0.0", + "version": "4.0.0", "inBundle": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/abort-controller": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/agent-base": { - "version": "6.0.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/npm/node_modules/agentkeepalive": { - "version": "4.5.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/npm/node_modules/aggregate-error": { - "version": "3.1.0", + "version": "7.1.4", "inBundle": true, "license": "MIT", - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, "engines": { - "node": ">=8" + "node": ">= 14" } }, "node_modules/npm/node_modules/ansi-regex": { @@ -1201,22 +1342,8 @@ "node": ">=8" } }, - "node_modules/npm/node_modules/ansi-styles": { - "version": "4.3.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/npm/node_modules/aproba": { - "version": "2.0.0", + "version": "2.1.0", "inBundle": true, "license": "ISC" }, @@ -1225,127 +1352,55 @@ "inBundle": true, "license": "MIT" }, - "node_modules/npm/node_modules/are-we-there-yet": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^4.1.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/balanced-match": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/base64-js": { - "version": "1.5.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "inBundle": true, - "license": "MIT" - }, "node_modules/npm/node_modules/bin-links": { - "version": "4.0.2", + "version": "6.0.0", "inBundle": true, "license": "ISC", "dependencies": { - "cmd-shim": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0", - "read-cmd-shim": "^4.0.0", - "write-file-atomic": "^5.0.0" + "cmd-shim": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "proc-log": "^6.0.0", + "read-cmd-shim": "^6.0.0", + "write-file-atomic": "^7.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/binary-extensions": { - "version": "2.2.0", + "version": "3.1.0", "inBundle": true, "license": "MIT", "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/brace-expansion": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/npm/node_modules/buffer": { - "version": "6.0.3", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "inBundle": true, - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/npm/node_modules/builtins": { - "version": "5.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "semver": "^7.0.0" + "node": ">=18.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/npm/node_modules/cacache": { - "version": "18.0.0", + "version": "20.0.3", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/fs": "^3.1.0", + "@npmcli/fs": "^5.0.0", "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^10.0.1", + "glob": "^13.0.0", + "lru-cache": "^11.1.0", "minipass": "^7.0.3", - "minipass-collect": "^1.0.2", + "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", - "p-map": "^4.0.0", - "ssri": "^10.0.0", - "tar": "^6.1.11", - "unique-filename": "^3.0.0" + "p-map": "^7.0.2", + "ssri": "^13.0.0", + "unique-filename": "^5.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/chalk": { - "version": "5.3.0", + "version": "5.6.2", "inBundle": true, "license": "MIT", "engines": { @@ -1356,15 +1411,15 @@ } }, "node_modules/npm/node_modules/chownr": { - "version": "2.0.0", + "version": "3.0.0", "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/npm/node_modules/ci-info": { - "version": "3.8.0", + "version": "4.3.1", "funding": [ { "type": "github", @@ -1378,22 +1433,14 @@ } }, "node_modules/npm/node_modules/cidr-regex": { - "version": "3.1.1", + "version": "5.0.1", "inBundle": true, "license": "BSD-2-Clause", "dependencies": { - "ip-regex": "^4.1.0" + "ip-regex": "5.0.0" }, "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/clean-stack": { - "version": "2.2.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" + "node": ">=20" } }, "node_modules/npm/node_modules/cli-columns": { @@ -1408,112 +1455,20 @@ "node": ">= 10" } }, - "node_modules/npm/node_modules/cli-table3": { - "version": "0.6.3", + "node_modules/npm/node_modules/cmd-shim": { + "version": "8.0.0", "inBundle": true, - "license": "MIT", - "dependencies": { - "string-width": "^4.2.0" - }, + "license": "ISC", "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, - "node_modules/npm/node_modules/clone": { - "version": "1.0.4", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/npm/node_modules/cmd-shim": { - "version": "6.0.1", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/color-convert": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/npm/node_modules/color-name": { - "version": "1.1.4", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/color-support": { - "version": "1.1.3", - "inBundle": true, - "license": "ISC", - "bin": { - "color-support": "bin.js" - } - }, - "node_modules/npm/node_modules/columnify": { - "version": "1.6.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "strip-ansi": "^6.0.1", - "wcwidth": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/common-ancestor-path": { - "version": "1.0.1", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/concat-map": { - "version": "0.0.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/console-control-strings": { - "version": "1.1.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/cross-spawn": { - "version": "7.0.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/cross-spawn/node_modules/which": { - "version": "2.0.2", + "version": "2.0.0", "inBundle": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, + "license": "BlueOak-1.0.0", "engines": { - "node": ">= 8" + "node": ">= 18" } }, "node_modules/npm/node_modules/cssesc": { @@ -1528,11 +1483,11 @@ } }, "node_modules/npm/node_modules/debug": { - "version": "4.3.4", + "version": "4.4.3", "inBundle": true, "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -1543,40 +1498,14 @@ } } }, - "node_modules/npm/node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/defaults": { - "version": "1.0.4", - "inBundle": true, - "license": "MIT", - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm/node_modules/delegates": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, "node_modules/npm/node_modules/diff": { - "version": "5.1.0", + "version": "8.0.3", "inBundle": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } }, - "node_modules/npm/node_modules/eastasianwidth": { - "version": "0.2.0", - "inBundle": true, - "license": "MIT" - }, "node_modules/npm/node_modules/emoji-regex": { "version": "8.0.0", "inBundle": true, @@ -1604,24 +1533,8 @@ "inBundle": true, "license": "MIT" }, - "node_modules/npm/node_modules/event-target-shim": { - "version": "5.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/events": { - "version": "3.3.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, "node_modules/npm/node_modules/exponential-backoff": { - "version": "3.1.1", + "version": "3.1.3", "inBundle": true, "license": "Apache-2.0" }, @@ -1633,21 +1546,6 @@ "node": ">= 4.9.1" } }, - "node_modules/npm/node_modules/foreground-child": { - "version": "3.1.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/npm/node_modules/fs-minipass": { "version": "3.0.3", "inBundle": true, @@ -1659,50 +1557,17 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/fs.realpath": { - "version": "1.0.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/function-bind": { - "version": "1.1.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/gauge": { - "version": "5.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^4.0.1", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/npm/node_modules/glob": { - "version": "10.3.3", + "version": "13.0.0", "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.0.3", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" - }, - "bin": { - "glob": "dist/cjs/src/bin.js" + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "path-scurry": "^2.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -1713,69 +1578,44 @@ "inBundle": true, "license": "ISC" }, - "node_modules/npm/node_modules/has": { - "version": "1.0.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/npm/node_modules/has-unicode": { - "version": "2.0.1", - "inBundle": true, - "license": "ISC" - }, "node_modules/npm/node_modules/hosted-git-info": { - "version": "7.0.0", + "version": "9.0.2", "inBundle": true, "license": "ISC", "dependencies": { - "lru-cache": "^10.0.1" + "lru-cache": "^11.1.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/http-cache-semantics": { - "version": "4.1.1", + "version": "4.2.0", "inBundle": true, "license": "BSD-2-Clause" }, "node_modules/npm/node_modules/http-proxy-agent": { - "version": "5.0.0", + "version": "7.0.2", "inBundle": true, "license": "MIT", "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/npm/node_modules/https-proxy-agent": { - "version": "5.0.1", + "version": "7.0.6", "inBundle": true, "license": "MIT", "dependencies": { - "agent-base": "6", + "agent-base": "^7.1.2", "debug": "4" }, "engines": { - "node": ">= 6" - } - }, - "node_modules/npm/node_modules/humanize-ms": { - "version": "1.2.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ms": "^2.0.0" + "node": ">= 14" } }, "node_modules/npm/node_modules/iconv-lite": { @@ -1790,34 +1630,15 @@ "node": ">=0.10.0" } }, - "node_modules/npm/node_modules/ieee754": { - "version": "1.2.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "inBundle": true, - "license": "BSD-3-Clause" - }, "node_modules/npm/node_modules/ignore-walk": { - "version": "6.0.3", + "version": "8.0.0", "inBundle": true, "license": "ISC", "dependencies": { - "minimatch": "^9.0.0" + "minimatch": "^10.0.3" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/imurmurhash": { @@ -1828,86 +1649,59 @@ "node": ">=0.8.19" } }, - "node_modules/npm/node_modules/indent-string": { - "version": "4.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/inflight": { - "version": "1.0.6", - "inBundle": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/npm/node_modules/inherits": { - "version": "2.0.4", - "inBundle": true, - "license": "ISC" - }, "node_modules/npm/node_modules/ini": { - "version": "4.1.1", + "version": "6.0.0", "inBundle": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/init-package-json": { - "version": "6.0.0", + "version": "8.2.4", "inBundle": true, "license": "ISC", "dependencies": { - "npm-package-arg": "^11.0.0", - "promzard": "^1.0.0", - "read": "^2.0.0", - "read-package-json": "^7.0.0", - "semver": "^7.3.5", + "@npmcli/package-json": "^7.0.0", + "npm-package-arg": "^13.0.0", + "promzard": "^3.0.1", + "read": "^5.0.1", + "semver": "^7.7.2", "validate-npm-package-license": "^3.0.4", - "validate-npm-package-name": "^5.0.0" + "validate-npm-package-name": "^7.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/ip": { - "version": "2.0.0", + "node_modules/npm/node_modules/ip-address": { + "version": "10.1.0", "inBundle": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">= 12" + } }, "node_modules/npm/node_modules/ip-regex": { - "version": "4.3.0", + "version": "5.0.0", "inBundle": true, "license": "MIT", "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/npm/node_modules/is-cidr": { - "version": "4.0.2", + "version": "6.0.1", "inBundle": true, "license": "BSD-2-Clause", "dependencies": { - "cidr-regex": "^3.1.1" + "cidr-regex": "5.0.1" }, "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/is-core-module": { - "version": "2.12.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=20" } }, "node_modules/npm/node_modules/is-fullwidth-code-point": { @@ -1918,39 +1712,20 @@ "node": ">=8" } }, - "node_modules/npm/node_modules/is-lambda": { - "version": "1.0.1", - "inBundle": true, - "license": "MIT" - }, "node_modules/npm/node_modules/isexe": { - "version": "2.0.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/jackspeak": { - "version": "2.2.1", + "version": "3.1.1", "inBundle": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, + "license": "ISC", "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "node": ">=16" } }, "node_modules/npm/node_modules/json-parse-even-better-errors": { - "version": "3.0.0", + "version": "5.0.0", "inBundle": true, "license": "MIT", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/json-stringify-nice": { @@ -1980,207 +1755,195 @@ "license": "MIT" }, "node_modules/npm/node_modules/libnpmaccess": { - "version": "8.0.0", + "version": "10.0.3", "inBundle": true, "license": "ISC", "dependencies": { - "npm-package-arg": "^11.0.0", - "npm-registry-fetch": "^16.0.0" + "npm-package-arg": "^13.0.0", + "npm-registry-fetch": "^19.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmdiff": { - "version": "6.0.1", + "version": "8.0.13", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^7.1.0", - "@npmcli/disparity-colors": "^3.0.0", - "@npmcli/installed-package-contents": "^2.0.2", - "binary-extensions": "^2.2.0", - "diff": "^5.1.0", - "minimatch": "^9.0.0", - "npm-package-arg": "^11.0.0", - "pacote": "^17.0.4", - "tar": "^6.1.13" + "@npmcli/arborist": "^9.1.10", + "@npmcli/installed-package-contents": "^4.0.0", + "binary-extensions": "^3.0.0", + "diff": "^8.0.2", + "minimatch": "^10.0.3", + "npm-package-arg": "^13.0.0", + "pacote": "^21.0.2", + "tar": "^7.5.1" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmexec": { - "version": "7.0.1", + "version": "10.1.12", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^7.1.0", - "@npmcli/run-script": "^7.0.1", - "ci-info": "^3.7.1", - "npm-package-arg": "^11.0.0", - "npmlog": "^7.0.1", - "pacote": "^17.0.4", - "proc-log": "^3.0.0", - "read": "^2.0.0", - "read-package-json-fast": "^3.0.2", + "@npmcli/arborist": "^9.1.10", + "@npmcli/package-json": "^7.0.0", + "@npmcli/run-script": "^10.0.0", + "ci-info": "^4.0.0", + "npm-package-arg": "^13.0.0", + "pacote": "^21.0.2", + "proc-log": "^6.0.0", + "promise-retry": "^2.0.1", + "read": "^5.0.1", "semver": "^7.3.7", - "walk-up-path": "^3.0.1" + "signal-exit": "^4.1.0", + "walk-up-path": "^4.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmfund": { - "version": "4.1.1", + "version": "7.0.13", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^7.1.0" + "@npmcli/arborist": "^9.1.10" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/libnpmhook": { - "version": "10.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^16.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmorg": { - "version": "6.0.0", + "version": "8.0.1", "inBundle": true, "license": "ISC", "dependencies": { "aproba": "^2.0.0", - "npm-registry-fetch": "^16.0.0" + "npm-registry-fetch": "^19.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmpack": { - "version": "6.0.1", + "version": "9.0.13", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^7.1.0", - "@npmcli/run-script": "^7.0.1", - "npm-package-arg": "^11.0.0", - "pacote": "^17.0.4" + "@npmcli/arborist": "^9.1.10", + "@npmcli/run-script": "^10.0.0", + "npm-package-arg": "^13.0.0", + "pacote": "^21.0.2" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmpublish": { - "version": "9.0.0", + "version": "11.1.3", "inBundle": true, "license": "ISC", "dependencies": { - "ci-info": "^3.6.1", - "normalize-package-data": "^6.0.0", - "npm-package-arg": "^11.0.0", - "npm-registry-fetch": "^16.0.0", - "proc-log": "^3.0.0", + "@npmcli/package-json": "^7.0.0", + "ci-info": "^4.0.0", + "npm-package-arg": "^13.0.0", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.7", - "sigstore": "^2.1.0", - "ssri": "^10.0.5" + "sigstore": "^4.0.0", + "ssri": "^13.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmsearch": { - "version": "7.0.0", + "version": "9.0.1", "inBundle": true, "license": "ISC", "dependencies": { - "npm-registry-fetch": "^16.0.0" + "npm-registry-fetch": "^19.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmteam": { - "version": "6.0.0", + "version": "8.0.2", "inBundle": true, "license": "ISC", "dependencies": { "aproba": "^2.0.0", - "npm-registry-fetch": "^16.0.0" + "npm-registry-fetch": "^19.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmversion": { - "version": "5.0.0", + "version": "8.0.3", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/git": "^5.0.3", - "@npmcli/run-script": "^7.0.1", - "json-parse-even-better-errors": "^3.0.0", - "proc-log": "^3.0.0", + "@npmcli/git": "^7.0.0", + "@npmcli/run-script": "^10.0.0", + "json-parse-even-better-errors": "^5.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.7" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/lru-cache": { - "version": "10.0.1", + "version": "11.2.4", "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { - "node": "14 || >=16.14" + "node": "20 || >=22" } }, "node_modules/npm/node_modules/make-fetch-happen": { - "version": "13.0.0", + "version": "15.0.3", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/agent": "^2.0.0", - "cacache": "^18.0.0", + "@npmcli/agent": "^4.0.0", + "cacache": "^20.0.1", "http-cache-semantics": "^4.1.1", - "is-lambda": "^1.0.1", "minipass": "^7.0.2", - "minipass-fetch": "^3.0.0", + "minipass-fetch": "^5.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", + "negotiator": "^1.0.0", + "proc-log": "^6.0.0", "promise-retry": "^2.0.1", - "ssri": "^10.0.0" + "ssri": "^13.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/minimatch": { - "version": "9.0.3", + "version": "10.1.1", "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/npm/node_modules/minipass": { - "version": "7.0.3", + "version": "7.1.2", "inBundle": true, "license": "ISC", "engines": { @@ -2188,38 +1951,27 @@ } }, "node_modules/npm/node_modules/minipass-collect": { - "version": "1.0.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/minipass-collect/node_modules/minipass": { - "version": "3.3.6", + "version": "2.0.1", "inBundle": true, "license": "ISC", "dependencies": { - "yallist": "^4.0.0" + "minipass": "^7.0.3" }, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, "node_modules/npm/node_modules/minipass-fetch": { - "version": "3.0.4", + "version": "5.0.0", "inBundle": true, "license": "MIT", "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" + "minizlib": "^3.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" }, "optionalDependencies": { "encoding": "^0.1.13" @@ -2247,26 +1999,6 @@ "node": ">=8" } }, - "node_modules/npm/node_modules/minipass-json-stream": { - "version": "1.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "jsonparse": "^1.3.1", - "minipass": "^3.0.0" - } - }, - "node_modules/npm/node_modules/minipass-json-stream/node_modules/minipass": { - "version": "3.3.6", - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/npm/node_modules/minipass-pipeline": { "version": "1.2.4", "inBundle": true, @@ -2312,37 +2044,14 @@ } }, "node_modules/npm/node_modules/minizlib": { - "version": "2.1.2", + "version": "3.1.0", "inBundle": true, "license": "MIT", "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" + "minipass": "^7.1.2" }, "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/mkdirp": { - "version": "1.0.4", - "inBundle": true, - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" + "node": ">= 18" } }, "node_modules/npm/node_modules/ms": { @@ -2351,15 +2060,15 @@ "license": "MIT" }, "node_modules/npm/node_modules/mute-stream": { - "version": "1.0.0", + "version": "3.0.0", "inBundle": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/negotiator": { - "version": "0.6.3", + "version": "1.0.0", "inBundle": true, "license": "MIT", "engines": { @@ -2367,532 +2076,229 @@ } }, "node_modules/npm/node_modules/node-gyp": { - "version": "9.4.0", + "version": "12.1.0", "inBundle": true, "license": "MIT", "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", - "glob": "^7.1.4", "graceful-fs": "^4.2.6", - "make-fetch-happen": "^11.0.3", - "nopt": "^6.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", + "make-fetch-happen": "^15.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" + "tar": "^7.5.2", + "tinyglobby": "^0.2.12", + "which": "^6.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" }, "engines": { - "node": "^12.13 || ^14.13 || >=16" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/abbrev": { - "version": "1.1.1", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/node-gyp/node_modules/are-we-there-yet": { - "version": "3.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/brace-expansion": { - "version": "1.1.11", - "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/cacache": { - "version": "17.1.4", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/fs": "^3.1.0", - "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^7.7.1", - "minipass": "^7.0.3", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "p-map": "^4.0.0", - "ssri": "^10.0.0", - "tar": "^6.1.11", - "unique-filename": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/cacache/node_modules/brace-expansion": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/cacache/node_modules/glob": { - "version": "10.3.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.0.3", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" - }, - "bin": { - "glob": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/cacache/node_modules/minimatch": { - "version": "9.0.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/cacache/node_modules/minipass": { - "version": "7.0.3", - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/gauge": { - "version": "4.0.4", - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/glob": { - "version": "7.2.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/lru-cache": { - "version": "7.18.3", - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/make-fetch-happen": { - "version": "11.1.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^17.0.0", - "http-cache-semantics": "^4.1.1", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^5.0.0", - "minipass-fetch": "^3.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^10.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/minimatch": { - "version": "3.1.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/minipass": { - "version": "5.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/nopt": { - "version": "6.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "abbrev": "^1.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/npmlog": { - "version": "6.0.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/readable-stream": { - "version": "3.6.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/signal-exit": { - "version": "3.0.7", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/node-gyp/node_modules/which": { - "version": "2.0.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/nopt": { - "version": "7.2.0", + "version": "9.0.0", "inBundle": true, "license": "ISC", "dependencies": { - "abbrev": "^2.0.0" + "abbrev": "^4.0.0" }, "bin": { "nopt": "bin/nopt.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/normalize-package-data": { - "version": "6.0.0", - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^7.0.0", - "is-core-module": "^2.8.1", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-audit-report": { - "version": "5.0.0", + "version": "7.0.0", "inBundle": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-bundled": { - "version": "3.0.0", + "version": "5.0.0", "inBundle": true, "license": "ISC", "dependencies": { - "npm-normalize-package-bin": "^3.0.0" + "npm-normalize-package-bin": "^5.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-install-checks": { - "version": "6.2.0", + "version": "8.0.0", "inBundle": true, "license": "BSD-2-Clause", "dependencies": { "semver": "^7.1.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-normalize-package-bin": { - "version": "3.0.1", + "version": "5.0.0", "inBundle": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-package-arg": { - "version": "11.0.0", + "version": "13.0.2", "inBundle": true, "license": "ISC", "dependencies": { - "hosted-git-info": "^7.0.0", - "proc-log": "^3.0.0", + "hosted-git-info": "^9.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.5", - "validate-npm-package-name": "^5.0.0" + "validate-npm-package-name": "^7.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-packlist": { - "version": "8.0.0", + "version": "10.0.3", "inBundle": true, "license": "ISC", "dependencies": { - "ignore-walk": "^6.0.0" + "ignore-walk": "^8.0.0", + "proc-log": "^6.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-pick-manifest": { - "version": "9.0.0", + "version": "11.0.3", "inBundle": true, "license": "ISC", "dependencies": { - "npm-install-checks": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0", - "npm-package-arg": "^11.0.0", + "npm-install-checks": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "npm-package-arg": "^13.0.0", "semver": "^7.3.5" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-profile": { - "version": "9.0.0", + "version": "12.0.1", "inBundle": true, "license": "ISC", "dependencies": { - "npm-registry-fetch": "^16.0.0", - "proc-log": "^3.0.0" + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-registry-fetch": { - "version": "16.0.0", + "version": "19.1.1", "inBundle": true, "license": "ISC", "dependencies": { - "make-fetch-happen": "^13.0.0", + "@npmcli/redact": "^4.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^15.0.0", "minipass": "^7.0.2", - "minipass-fetch": "^3.0.0", - "minipass-json-stream": "^1.0.1", - "minizlib": "^2.1.2", - "npm-package-arg": "^11.0.0", - "proc-log": "^3.0.0" + "minipass-fetch": "^5.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^13.0.0", + "proc-log": "^6.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-user-validate": { - "version": "2.0.0", + "version": "4.0.0", "inBundle": true, "license": "BSD-2-Clause", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/npmlog": { - "version": "7.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "are-we-there-yet": "^4.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^5.0.0", - "set-blocking": "^2.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/once": { - "version": "1.4.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/p-map": { - "version": "4.0.0", + "version": "7.0.4", "inBundle": true, "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/npm/node_modules/pacote": { - "version": "17.0.4", + "version": "21.0.4", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/git": "^5.0.0", - "@npmcli/installed-package-contents": "^2.0.1", - "@npmcli/promise-spawn": "^7.0.0", - "@npmcli/run-script": "^7.0.0", - "cacache": "^18.0.0", + "@npmcli/git": "^7.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "@npmcli/run-script": "^10.0.0", + "cacache": "^20.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", - "npm-package-arg": "^11.0.0", - "npm-packlist": "^8.0.0", - "npm-pick-manifest": "^9.0.0", - "npm-registry-fetch": "^16.0.0", - "proc-log": "^3.0.0", + "npm-package-arg": "^13.0.0", + "npm-packlist": "^10.0.1", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0", "promise-retry": "^2.0.1", - "read-package-json": "^7.0.0", - "read-package-json-fast": "^3.0.0", - "sigstore": "^2.0.0", - "ssri": "^10.0.0", - "tar": "^6.1.11" + "sigstore": "^4.0.0", + "ssri": "^13.0.0", + "tar": "^7.4.3" }, "bin": { - "pacote": "lib/bin.js" + "pacote": "bin/index.js" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/parse-conflict-json": { - "version": "3.0.1", + "version": "5.0.1", "inBundle": true, "license": "ISC", "dependencies": { - "json-parse-even-better-errors": "^3.0.0", + "json-parse-even-better-errors": "^5.0.0", "just-diff": "^6.0.0", "just-diff-apply": "^5.2.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/path-is-absolute": { - "version": "1.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/path-key": { - "version": "3.1.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/path-scurry": { - "version": "1.10.1", + "version": "2.0.1", "inBundle": true, "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/npm/node_modules/postcss-selector-parser": { - "version": "6.0.13", + "version": "7.1.1", "inBundle": true, "license": "MIT", "dependencies": { @@ -2904,19 +2310,19 @@ } }, "node_modules/npm/node_modules/proc-log": { - "version": "3.0.0", + "version": "6.1.0", "inBundle": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/process": { - "version": "0.11.10", + "node_modules/npm/node_modules/proggy": { + "version": "4.0.0", "inBundle": true, - "license": "MIT", + "license": "ISC", "engines": { - "node": ">= 0.6.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/promise-all-reject-late": { @@ -2928,18 +2334,13 @@ } }, "node_modules/npm/node_modules/promise-call-limit": { - "version": "1.0.2", + "version": "3.0.2", "inBundle": true, "license": "ISC", "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/npm/node_modules/promise-inflight": { - "version": "1.0.1", - "inBundle": true, - "license": "ISC" - }, "node_modules/npm/node_modules/promise-retry": { "version": "2.0.1", "inBundle": true, @@ -2953,14 +2354,14 @@ } }, "node_modules/npm/node_modules/promzard": { - "version": "1.0.0", + "version": "3.0.1", "inBundle": true, "license": "ISC", "dependencies": { - "read": "^2.0.0" + "read": "^5.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/qrcode-terminal": { @@ -2970,202 +2371,52 @@ "qrcode-terminal": "bin/qrcode-terminal.js" } }, - "node_modules/npm/node_modules/read": { - "version": "2.1.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "mute-stream": "~1.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/read-cmd-shim": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/read-package-json": { - "version": "7.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "glob": "^10.2.2", - "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/read-package-json-fast": { - "version": "3.0.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "json-parse-even-better-errors": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/readable-stream": { - "version": "4.4.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/retry": { - "version": "0.12.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/npm/node_modules/rimraf": { - "version": "3.0.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/rimraf/node_modules/brace-expansion": { - "version": "1.1.11", - "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/npm/node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/rimraf/node_modules/minimatch": { - "version": "3.1.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/npm/node_modules/safe-buffer": { - "version": "5.2.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/safer-buffer": { - "version": "2.1.2", - "inBundle": true, - "license": "MIT", - "optional": true - }, - "node_modules/npm/node_modules/semver": { - "version": "7.5.4", + "node_modules/npm/node_modules/read": { + "version": "5.0.1", "inBundle": true, "license": "ISC", "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "mute-stream": "^3.0.0" }, "engines": { - "node": ">=10" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/semver/node_modules/lru-cache": { + "node_modules/npm/node_modules/read-cmd-shim": { "version": "6.0.0", "inBundle": true, "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, "engines": { - "node": ">=10" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/set-blocking": { - "version": "2.0.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/shebang-command": { - "version": "2.0.0", + "node_modules/npm/node_modules/retry": { + "version": "0.12.0", "inBundle": true, "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, "engines": { - "node": ">=8" + "node": ">= 4" } }, - "node_modules/npm/node_modules/shebang-regex": { - "version": "3.0.0", + "node_modules/npm/node_modules/safer-buffer": { + "version": "2.1.2", "inBundle": true, "license": "MIT", + "optional": true + }, + "node_modules/npm/node_modules/semver": { + "version": "7.7.3", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": ">=8" + "node": ">=10" } }, "node_modules/npm/node_modules/signal-exit": { - "version": "4.0.2", + "version": "4.1.0", "inBundle": true, "license": "ISC", "engines": { @@ -3176,17 +2427,19 @@ } }, "node_modules/npm/node_modules/sigstore": { - "version": "2.1.0", + "version": "4.1.0", "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.1.0", - "@sigstore/protobuf-specs": "^0.2.1", - "@sigstore/sign": "^2.1.0", - "@sigstore/tuf": "^2.1.0" + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0", + "@sigstore/sign": "^4.1.0", + "@sigstore/tuf": "^4.0.1", + "@sigstore/verify": "^3.1.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/smart-buffer": { @@ -3199,29 +2452,29 @@ } }, "node_modules/npm/node_modules/socks": { - "version": "2.7.1", + "version": "2.8.7", "inBundle": true, "license": "MIT", "dependencies": { - "ip": "^2.0.0", + "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" }, "engines": { - "node": ">= 10.13.0", + "node": ">= 10.0.0", "npm": ">= 3.0.0" } }, "node_modules/npm/node_modules/socks-proxy-agent": { - "version": "7.0.0", + "version": "8.0.5", "inBundle": true, "license": "MIT", "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" }, "engines": { - "node": ">= 10" + "node": ">= 14" } }, "node_modules/npm/node_modules/spdx-correct": { @@ -3233,13 +2486,22 @@ "spdx-license-ids": "^3.0.0" } }, + "node_modules/npm/node_modules/spdx-correct/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, "node_modules/npm/node_modules/spdx-exceptions": { - "version": "2.3.0", + "version": "2.5.0", "inBundle": true, "license": "CC-BY-3.0" }, "node_modules/npm/node_modules/spdx-expression-parse": { - "version": "3.0.1", + "version": "4.0.0", "inBundle": true, "license": "MIT", "dependencies": { @@ -3248,27 +2510,19 @@ } }, "node_modules/npm/node_modules/spdx-license-ids": { - "version": "3.0.13", + "version": "3.0.22", "inBundle": true, "license": "CC0-1.0" }, "node_modules/npm/node_modules/ssri": { - "version": "10.0.5", + "version": "13.0.0", "inBundle": true, "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/string_decoder": { - "version": "1.3.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/string-width": { @@ -3284,20 +2538,6 @@ "node": ">=8" } }, - "node_modules/npm/node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "inBundle": 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/npm/node_modules/strip-ansi": { "version": "6.0.1", "inBundle": true, @@ -3309,84 +2549,92 @@ "node": ">=8" } }, - "node_modules/npm/node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/npm/node_modules/supports-color": { - "version": "9.4.0", + "version": "10.2.2", "inBundle": true, "license": "MIT", "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/npm/node_modules/tar": { - "version": "6.1.15", + "version": "7.5.4", "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" } }, - "node_modules/npm/node_modules/tar/node_modules/fs-minipass": { - "version": "2.1.0", + "node_modules/npm/node_modules/tar/node_modules/yallist": { + "version": "5.0.0", "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, + "license": "BlueOak-1.0.0", "engines": { - "node": ">= 8" + "node": ">=18" } }, - "node_modules/npm/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", + "node_modules/npm/node_modules/text-table": { + "version": "0.2.0", "inBundle": true, - "license": "ISC", + "license": "MIT" + }, + "node_modules/npm/node_modules/tiny-relative-date": { + "version": "2.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/tinyglobby": { + "version": "0.2.15", + "inBundle": true, + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { - "node": ">=8" + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/npm/node_modules/tar/node_modules/minipass": { - "version": "5.0.0", + "node_modules/npm/node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", "inBundle": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/npm/node_modules/text-table": { - "version": "0.2.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/tiny-relative-date": { - "version": "1.3.0", + "node_modules/npm/node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", "inBundle": true, - "license": "MIT" + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } }, "node_modules/npm/node_modules/treeverse": { "version": "3.0.0", @@ -3397,38 +2645,38 @@ } }, "node_modules/npm/node_modules/tuf-js": { - "version": "2.1.0", + "version": "4.1.0", "inBundle": true, "license": "MIT", "dependencies": { - "@tufjs/models": "2.0.0", - "debug": "^4.3.4", - "make-fetch-happen": "^13.0.0" + "@tufjs/models": "4.1.0", + "debug": "^4.4.3", + "make-fetch-happen": "^15.0.1" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/unique-filename": { - "version": "3.0.0", + "version": "5.0.0", "inBundle": true, "license": "ISC", "dependencies": { - "unique-slug": "^4.0.0" + "unique-slug": "^6.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/unique-slug": { - "version": "4.0.0", + "version": "6.0.0", "inBundle": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/util-deprecate": { @@ -3445,157 +2693,47 @@ "spdx-expression-parse": "^3.0.0" } }, - "node_modules/npm/node_modules/validate-npm-package-name": { - "version": "5.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "builtins": "^5.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/walk-up-path": { + "node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { "version": "3.0.1", "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/wcwidth": { - "version": "1.0.1", - "inBundle": true, "license": "MIT", "dependencies": { - "defaults": "^1.0.3" + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, - "node_modules/npm/node_modules/which": { - "version": "4.0.0", + "node_modules/npm/node_modules/validate-npm-package-name": { + "version": "7.0.2", "inBundle": true, "license": "ISC", - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/which/node_modules/isexe": { - "version": "3.1.1", + "node_modules/npm/node_modules/walk-up-path": { + "version": "4.0.0", "inBundle": true, "license": "ISC", "engines": { - "node": ">=16" + "node": "20 || >=22" } }, - "node_modules/npm/node_modules/wide-align": { - "version": "1.1.5", + "node_modules/npm/node_modules/which": { + "version": "6.0.0", "inBundle": true, "license": "ISC", "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, - "node_modules/npm/node_modules/wrap-ansi": { - "version": "8.1.0", - "inBundle": 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/npm/node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "inBundle": 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/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { - "version": "9.2.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": { - "version": "5.1.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" + "isexe": "^3.1.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" + "bin": { + "node-which": "bin/which.js" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/wrappy": { - "version": "1.0.2", - "inBundle": true, - "license": "ISC" - }, "node_modules/npm/node_modules/write-file-atomic": { - "version": "5.0.1", + "version": "7.0.0", "inBundle": true, "license": "ISC", "dependencies": { @@ -3603,7 +2741,7 @@ "signal-exit": "^4.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/yallist": { @@ -3629,9 +2767,10 @@ } }, "node_modules/ordered-binary": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.5.1.tgz", - "integrity": "sha512-5VyHfHY3cd0iza71JepYG50My+YUbrFtGoUz2ooEydPyPM7Aai/JW098juLr+RG6+rDJuzNNTsEQu2DZa1A41A==" + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.6.1.tgz", + "integrity": "sha512-QkCdPooczexPLiXIrbVOPYkR3VO3T6v2OyKRkR1Xbhpy7/LAVXwahnRCgRp78Oe/Ehf0C/HATAxfSr6eA1oX+w==", + "license": "MIT" }, "node_modules/path-key": { "version": "3.1.1", diff --git a/src-node/package.json b/src-node/package.json index f4a0133db3..59eb1cf455 100644 --- a/src-node/package.json +++ b/src-node/package.json @@ -1,8 +1,8 @@ { "name": "@phcode/node-core", "description": "Phoenix Node Core", - "version": "5.0.5-0", - "apiVersion": "5.0.5", + "version": "5.1.4-0", + "apiVersion": "5.1.4", "keywords": [], "author": "arun@core.ai", "homepage": "https://github.com/phcode-dev/phoenix", @@ -11,7 +11,7 @@ "_watch_src-node": "cd .. && npm run _watch_src-node" }, "engines": { - "node": "20" + "node": "24" }, "repository": { "type": "git", @@ -19,14 +19,15 @@ }, "IMPORTANT!!": "Adding things here will bloat up the package size", "dependencies": { - "@phcode/fs": "^3.0.1", + "@phcode/fs": "^4.0.2", "open": "^10.1.0", - "npm": "10.1.0", + "npm": "11.8.0", "ws": "^8.17.1", - "lmdb": "^2.9.2", + "lmdb": "^3.5.1", "mime-types": "^2.1.35", "cross-spawn": "^7.0.6", "which": "^2.0.1", - "@expo/sudo-prompt": "^9.3.2" + "@expo/sudo-prompt": "^9.3.2", + "@anthropic-ai/claude-code": "^1.0.0" } } \ No newline at end of file diff --git a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js index 0256043f31..4c03e8fbd8 100644 --- a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js +++ b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js @@ -24,7 +24,9 @@ function RemoteFunctions(config = {}) { const MessageBroker = window._Brackets_MessageBroker; // to be used by plugins. const SHARED_STATE = { - __description: "Use this to keep shared state for Live Preview Edit instead of window.*" + __description: "Use this to keep shared state for Live Preview Edit instead of window.*", + _suppressDOMEditDismissal: false, + _suppressDOMEditDismissalTimeout: null }; let _hoverHighlight; @@ -1257,10 +1259,19 @@ function RemoteFunctions(config = {}) { this.rememberedNodes = {}; // this check makes sure that if the element is no more in the DOM then we remove it - if (previouslySelectedElement && !previouslySelectedElement.isConnected) { - dismissUIAndCleanupState(); + // skip this check if suppression is active (e.g., when some internal feature updates source) + if (!SHARED_STATE._suppressDOMEditDismissal) { + if (previouslySelectedElement && !previouslySelectedElement.isConnected) { + dismissUIAndCleanupState(); + } else { + redrawEverything(); + } } else { - redrawEverything(); + // Suppression is active - re-apply outline since attrChange may have wiped it + if (previouslySelectedElement && previouslySelectedElement.isConnected) { + const outlineColor = previouslySelectedElement.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR) ? "#4285F4" : "#3C3F41"; + previouslySelectedElement.style.outline = `1px solid ${outlineColor}`; + } } }; @@ -1335,6 +1346,24 @@ function RemoteFunctions(config = {}) { } } + /** + * Temporarily suppress the DOM edit dismissal check in apply() + * Used when source is modified from UI panels to prevent + * the panel from being dismissed when the DOM is updated. + * @param {Number} durationMs - Duration in milliseconds to suppress (default 100) + */ + function suppressDOMEditDismissal(durationMs) { + durationMs = durationMs || 100; + if (SHARED_STATE._suppressDOMEditDismissalTimeout) { + clearTimeout(SHARED_STATE._suppressDOMEditDismissalTimeout); + } + SHARED_STATE._suppressDOMEditDismissal = true; + SHARED_STATE._suppressDOMEditDismissalTimeout = setTimeout(function() { + SHARED_STATE._suppressDOMEditDismissal = false; + SHARED_STATE._suppressDOMEditDismissalTimeout = null; + }, durationMs); + } + /** * This function dismisses all UI elements and cleans up application state * Called when user presses Esc key, clicks on HTML/Body tags, or other dismissal events @@ -1425,7 +1454,8 @@ function RemoteFunctions(config = {}) { "updateConfig": updateConfig, "dismissUIAndCleanupState": dismissUIAndCleanupState, "escapeKeyPressInEditor": _handleEscapeKeyPress, - "getMode": function() { return config.mode; } + "getMode": function() { return config.mode; }, + "suppressDOMEditDismissal": suppressDOMEditDismissal }; // the below code comment is replaced by added scripts for extensibility diff --git a/src/LiveDevelopment/LiveDevMultiBrowser.js b/src/LiveDevelopment/LiveDevMultiBrowser.js index 93a1ae6740..50f3e9bd90 100644 --- a/src/LiveDevelopment/LiveDevMultiBrowser.js +++ b/src/LiveDevelopment/LiveDevMultiBrowser.js @@ -189,15 +189,14 @@ define(function (require, exports, module) { * @param {string} url Absolute URL of the related document */ function _handleRelatedDocumentDeleted(url) { - var liveDoc = _relatedDocuments[url]; + const liveDoc = _relatedDocuments[url]; if (liveDoc) { delete _relatedDocuments[url]; + if (_server) { + _server.remove(liveDoc); + } + _closeDocument(liveDoc); } - - if (_server) { - _server.remove(liveDoc); - } - _closeDocument(liveDoc); } /** @@ -313,12 +312,17 @@ define(function (require, exports, module) { var docPromise = DocumentManager.getDocumentForPath(path); docPromise.done(function (doc) { + // Re-check after async gap: another StylesheetAdded event may have + // already created a live document for this URL while we were waiting. + if (_relatedDocuments[url]) { + return; + } if ((_classForDocument(doc) === LiveCSSDocument) && (!_liveDocument || (doc !== _liveDocument.doc))) { var liveDoc = _createLiveDocument(doc, doc._masterEditor, roots); if (liveDoc) { _server.add(liveDoc); - _relatedDocuments[doc.url] = liveDoc; + _relatedDocuments[url] = liveDoc; liveDoc.on("updateDoc", function (event, url) { var path = _server.urlToPath(url), doc = getLiveDocForPath(path); diff --git a/src/LiveDevelopment/main.js b/src/LiveDevelopment/main.js index ae3069b37b..60d4caebf5 100644 --- a/src/LiveDevelopment/main.js +++ b/src/LiveDevelopment/main.js @@ -39,7 +39,6 @@ define(function main(require, exports, module) { LivePreviewTransport = require("LiveDevelopment/MultiBrowserImpl/transports/LivePreviewTransport"), CommandManager = require("command/CommandManager"), PreferencesManager = require("preferences/PreferencesManager"), - StateManager = require("preferences/StateManager"), UrlParams = require("utils/UrlParams").UrlParams, Strings = require("strings"), ExtensionUtils = require("utils/ExtensionUtils"), @@ -57,10 +56,6 @@ define(function main(require, exports, module) { const PREFERENCE_LIVE_PREVIEW_MODE = CONSTANTS.PREFERENCE_LIVE_PREVIEW_MODE; - // state manager key to track image gallery selected state, by default we keep this as selected - // if this is true we show the image gallery when an image element is clicked - const IMAGE_GALLERY_STATE = "livePreview.imageGallery.state"; - PreferencesManager.definePreference(PREFERENCE_LIVE_PREVIEW_MODE, "string", LIVE_HIGHLIGHT_MODE, { description: StringUtils.format( Strings.LIVE_PREVIEW_MODE_PREFERENCE, LIVE_PREVIEW_MODE, LIVE_HIGHLIGHT_MODE, LIVE_EDIT_MODE), @@ -70,34 +65,11 @@ define(function main(require, exports, module) { _previewModeUpdated(); }); - /** - * get the image gallery state from StateManager - * @returns {boolean} true (default) - */ - function _getImageGalleryState() { - const savedState = StateManager.get(IMAGE_GALLERY_STATE); - return savedState !== undefined && savedState !== null ? savedState : true; - } - - /** - * sets the image gallery state - * @param {Boolean} the state that we need to set - */ - function setImageGalleryState(state) { - StateManager.set(IMAGE_GALLERY_STATE, state); - - // update the config with the new state - const config = MultiBrowserLiveDev.getConfig(); - config.imageGalleryState = state; - MultiBrowserLiveDev.updateConfig(config); - } - let params = new UrlParams(); const defaultConfig = { mode: LIVE_HIGHLIGHT_MODE, // will be updated when we fetch entitlements elemHighlights: CONSTANTS.HIGHLIGHT_HOVER, // default value, this will get updated when the extension loads showRulerLines: false, // default value, this will get updated when the extension loads - imageGalleryState: _getImageGalleryState(), // image gallery selected state isPaidUser: false, // will be updated when we fetch entitlements isLoggedIn: false, // will be updated when we fetch entitlements hasLiveEditCapability: false // handled inside _liveEditCapabilityChanged function @@ -373,7 +345,6 @@ define(function main(require, exports, module) { exports.isActive = isActive; exports.setLivePreviewPinned = setLivePreviewPinned; exports.setLivePreviewTransportBridge = setLivePreviewTransportBridge; - exports.setImageGalleryState = setImageGalleryState; exports.updateElementHighlightConfig = updateElementHighlightConfig; exports.updateRulerLinesConfig = updateRulerLinesConfig; exports.getConnectionIds = MultiBrowserLiveDev.getConnectionIds; diff --git a/src/assets/new-project/assets/js/code-editor.js b/src/assets/new-project/assets/js/code-editor.js index 77cb07768b..5eea795ef6 100644 --- a/src/assets/new-project/assets/js/code-editor.js +++ b/src/assets/new-project/assets/js/code-editor.js @@ -178,7 +178,7 @@ function _attachSettingBtnEventListeners() { function _openURLInTauri(url) { // in tauri, the tag will not open a browser window. So we have to use phcode apis to do it. // else, the browser itself will open the url. so we dont have to do this in normal browsers. - if(window.top.__TAURI__) { + if(window.top.__IS_NATIVE_SHELL__) { window.top.Phoenix.app.openURLInDefaultBrowser(url); } } diff --git a/src/brackets.js b/src/brackets.js index 75aabed6d2..b14f4e1b99 100644 --- a/src/brackets.js +++ b/src/brackets.js @@ -126,6 +126,8 @@ define(function (require, exports, module) { require("search/QuickOpenHelper"); require("file/FileUtils"); require("project/SidebarView"); + require("view/SidebarTabs"); + require("core-ai/main"); require("utils/Resizer"); require("LiveDevelopment/main"); require("utils/NodeConnection"); @@ -139,6 +141,7 @@ define(function (require, exports, module) { require("widgets/InlineMenu"); require("thirdparty/tinycolor"); require("utils/LocalizationUtils"); + require("phoenix-builder/main"); // DEPRECATED: In future we want to remove the global CodeMirror, but for now we // expose our required CodeMirror globally so as to avoid breaking extensions in the @@ -290,6 +293,8 @@ define(function (require, exports, module) { WorkspaceManager: require("view/WorkspaceManager"), SearchResultsView: require("search/SearchResultsView"), ScrollTrackMarkers: require("search/ScrollTrackMarkers"), + SidebarTabs: require("view/SidebarTabs"), + SidebarView: require("project/SidebarView"), WorkingSetView: require("project/WorkingSetView"), doneLoading: false }; @@ -324,7 +329,9 @@ define(function (require, exports, module) { ViewCommandHandlers.restoreFontSize(); ProjectManager.getStartupProjectPath().then((initialProjectPath)=>{ ProjectManager.openProject(initialProjectPath).always(function () { - _initTest(); + if (Phoenix.isTestWindow || window._phoenixBuilder) { + _initTest(); + } // If this is the first launch, and we have an index.html file in the project folder (which should be // the samples folder on first launch), open it automatically. (We explicitly check for the diff --git a/src/command/Commands.js b/src/command/Commands.js index 216561b404..dbd94cb219 100644 --- a/src/command/Commands.js +++ b/src/command/Commands.js @@ -405,6 +405,9 @@ define(function (require, exports, module) { /** Opens Phoenix Pro page */ exports.HELP_GET_PRO = "help.getPro"; // HelpCommandHandlers.js _handleLinkMenuItem() + /** Cancels Phoenix Pro trial */ + exports.HELP_CANCEL_TRIAL = "help.cancelTrial"; + /** Opens Phoenix License page */ exports.HELP_VIEW_LICENSE = "help.viewLicense"; // HelpCommandHandlers.js _handleLinkMenuItem() diff --git a/src/config.json b/src/config.json index e104b7bb1a..5d502d24c7 100644 --- a/src/config.json +++ b/src/config.json @@ -46,8 +46,8 @@ "bugsnagEnv": "development" }, "name": "Phoenix Code", - "version": "5.0.5-0", - "apiVersion": "5.0.5", + "version": "5.1.4-0", + "apiVersion": "5.1.4", "homepage": "https://core.ai", "issues": { "url": "https://github.com/phcode-dev/phoenix/issues" diff --git a/src/core-ai/AIChatPanel.js b/src/core-ai/AIChatPanel.js new file mode 100644 index 0000000000..9244ec580a --- /dev/null +++ b/src/core-ai/AIChatPanel.js @@ -0,0 +1,990 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2021 - present core.ai . All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + * + */ + +/** + * AI Chat Panel — renders the chat UI in the AI sidebar tab, handles streaming + * responses from Claude Code, and manages edit application to documents. + */ +define(function (require, exports, module) { + + const SidebarTabs = require("view/SidebarTabs"), + DocumentManager = require("document/DocumentManager"), + CommandManager = require("command/CommandManager"), + Commands = require("command/Commands"), + ProjectManager = require("project/ProjectManager"), + FileSystem = require("filesystem/FileSystem"), + marked = require("thirdparty/marked.min"); + + let _nodeConnector = null; + let _isStreaming = false; + let _currentRequestId = null; + let _segmentText = ""; // text for the current segment only + let _autoScroll = true; + let _hasReceivedContent = false; // tracks if we've received any text/tool in current response + const _previousContentMap = {}; // filePath → previous content before edit, for undo support + + // DOM references + let $panel, $messages, $status, $statusText, $textarea, $sendBtn, $stopBtn; + + const PANEL_HTML = + '
' + + '
' + + 'AI Assistant' + + '' + + '
' + + '
' + + '
' + + '' + + 'Thinking...' + + '
' + + '
' + + '
' + + '' + + '' + + '' + + '
' + + '
' + + '
'; + + const UNAVAILABLE_HTML = + '
' + + '
' + + '
' + + '
Claude CLI Not Found
' + + '
' + + 'Install the Claude CLI to use AI features:
' + + 'npm install -g @anthropic-ai/claude-code

' + + 'Then run claude login to authenticate.' + + '
' + + '' + + '
' + + '
'; + + const PLACEHOLDER_HTML = + '
' + + '
' + + '
' + + '
AI Assistant
' + + '
' + + 'AI features require the Phoenix desktop app.' + + '
' + + '
' + + '
'; + + /** + * Initialize the chat panel with a NodeConnector instance. + * @param {Object} nodeConnector - NodeConnector for communicating with the node-side Claude agent. + */ + function init(nodeConnector) { + _nodeConnector = nodeConnector; + + // Wire up events from node side + _nodeConnector.on("aiTextStream", _onTextStream); + _nodeConnector.on("aiProgress", _onProgress); + _nodeConnector.on("aiToolInfo", _onToolInfo); + _nodeConnector.on("aiToolStream", _onToolStream); + _nodeConnector.on("aiEditResult", _onEditResult); + _nodeConnector.on("aiError", _onError); + _nodeConnector.on("aiComplete", _onComplete); + + // Check availability and render appropriate UI + _checkAvailability(); + } + + /** + * Show placeholder UI for non-native (browser) builds. + */ + function initPlaceholder() { + const $placeholder = $(PLACEHOLDER_HTML); + SidebarTabs.addToTab("ai", $placeholder); + } + + /** + * Check if Claude CLI is available and render the appropriate UI. + */ + function _checkAvailability() { + _nodeConnector.execPeer("checkAvailability") + .then(function (result) { + if (result.available) { + _renderChatUI(); + } else { + _renderUnavailableUI(result.error); + } + }) + .catch(function (err) { + _renderUnavailableUI(err.message || String(err)); + }); + } + + /** + * Render the full chat UI. + */ + function _renderChatUI() { + $panel = $(PANEL_HTML); + $messages = $panel.find(".ai-chat-messages"); + $status = $panel.find(".ai-chat-status"); + $statusText = $panel.find(".ai-status-text"); + $textarea = $panel.find(".ai-chat-textarea"); + $sendBtn = $panel.find(".ai-send-btn"); + $stopBtn = $panel.find(".ai-stop-btn"); + + // Event handlers + $sendBtn.on("click", _sendMessage); + $stopBtn.on("click", _cancelQuery); + $panel.find(".ai-new-session-btn").on("click", _newSession); + + // Hide "+ New" button initially (no conversation yet) + $panel.find(".ai-new-session-btn").hide(); + + $textarea.on("keydown", function (e) { + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + _sendMessage(); + } + if (e.key === "Escape") { + if (_isStreaming) { + _cancelQuery(); + } else { + $textarea.val(""); + } + } + }); + + // Auto-resize textarea + $textarea.on("input", function () { + this.style.height = "auto"; + this.style.height = Math.min(this.scrollHeight, 96) + "px"; // max ~6rem + }); + + // Track scroll position for auto-scroll + $messages.on("scroll", function () { + const el = $messages[0]; + _autoScroll = (el.scrollHeight - el.scrollTop - el.clientHeight) < 50; + }); + + SidebarTabs.addToTab("ai", $panel); + } + + /** + * Render the unavailable UI (CLI not found). + */ + function _renderUnavailableUI(error) { + const $unavailable = $(UNAVAILABLE_HTML); + $unavailable.find(".ai-retry-btn").on("click", function () { + $unavailable.remove(); + _checkAvailability(); + }); + SidebarTabs.addToTab("ai", $unavailable); + } + + /** + * Send the current input as a message to Claude. + */ + function _sendMessage() { + const text = $textarea.val().trim(); + if (!text || _isStreaming) { + return; + } + + // Show "+ New" button once a conversation starts + $panel.find(".ai-new-session-btn").show(); + + // Append user message + _appendUserMessage(text); + + // Clear input + $textarea.val(""); + $textarea.css("height", "auto"); + + // Set streaming state + _setStreaming(true); + + // Reset segment tracking and show thinking indicator + _segmentText = ""; + _hasReceivedContent = false; + _appendThinkingIndicator(); + + // Get project path + const projectPath = _getProjectRealPath(); + + _nodeConnector.execPeer("sendPrompt", { + prompt: text, + projectPath: projectPath, + sessionAction: "continue" + }).then(function (result) { + _currentRequestId = result.requestId; + }).catch(function (err) { + _setStreaming(false); + _appendErrorMessage("Failed to send message: " + (err.message || String(err))); + }); + } + + /** + * Cancel the current streaming query. + */ + function _cancelQuery() { + if (_nodeConnector && _isStreaming) { + _nodeConnector.execPeer("cancelQuery").catch(function () { + // ignore cancel errors + }); + } + } + + /** + * Start a new session: destroy server-side session and clear chat. + */ + function _newSession() { + if (_nodeConnector) { + _nodeConnector.execPeer("destroySession").catch(function () { + // ignore + }); + } + _currentRequestId = null; + _segmentText = ""; + _hasReceivedContent = false; + _isStreaming = false; + if ($messages) { + $messages.empty(); + } + // Hide "+ New" button since we're back to empty state + if ($panel) { + $panel.find(".ai-new-session-btn").hide(); + } + if ($status) { + $status.removeClass("active"); + } + if ($textarea) { + $textarea.prop("disabled", false); + $textarea[0].focus({ preventScroll: true }); + } + if ($sendBtn) { + $sendBtn.prop("disabled", false); + } + } + + // --- Event handlers for node-side events --- + + function _onTextStream(_event, data) { + // Remove thinking indicator on first content + if (!_hasReceivedContent) { + _hasReceivedContent = true; + $messages.find(".ai-thinking").remove(); + } + + // If no active stream target exists, create a new text segment + if (!$messages.find(".ai-stream-target").length) { + _appendAssistantSegment(); + } + + _segmentText += data.text; + _renderAssistantStream(); + } + + // Tool type configuration: icon, color, label + const TOOL_CONFIG = { + Glob: { icon: "fa-solid fa-magnifying-glass", color: "#6b9eff", label: "Search files" }, + Grep: { icon: "fa-solid fa-magnifying-glass-location", color: "#6b9eff", label: "Search code" }, + Read: { icon: "fa-solid fa-file-lines", color: "#6bc76b", label: "Read" }, + Edit: { icon: "fa-solid fa-pen", color: "#e8a838", label: "Edit" }, + Write: { icon: "fa-solid fa-file-pen", color: "#e8a838", label: "Write" }, + Bash: { icon: "fa-solid fa-terminal", color: "#c084fc", label: "Run command" }, + Skill: { icon: "fa-solid fa-puzzle-piece", color: "#e0c060", label: "Skill" } + }; + + function _onProgress(_event, data) { + if ($statusText) { + const toolName = data.toolName || ""; + const config = TOOL_CONFIG[toolName]; + $statusText.text(config ? config.label + "..." : "Thinking..."); + } + if (data.phase === "tool_use") { + _appendToolIndicator(data.toolName, data.toolId); + } + } + + function _onToolInfo(_event, data) { + _updateToolIndicator(data.toolId, data.toolName, data.toolInput); + } + + function _onToolStream(_event, data) { + const uniqueToolId = (_currentRequestId || "") + "-" + data.toolId; + const $tool = $messages.find('.ai-msg-tool[data-tool-id="' + uniqueToolId + '"]'); + if (!$tool.length) { + return; + } + + // Update label with filename as soon as file_path is available + if (!$tool.data("labelUpdated")) { + const filePath = _extractJsonStringValue(data.partialJson, "file_path"); + if (filePath) { + const fileName = filePath.split("/").pop(); + const config = TOOL_CONFIG[data.toolName] || {}; + $tool.find(".ai-tool-label").text((config.label || data.toolName) + " " + fileName + "..."); + $tool.data("labelUpdated", true); + } + } + + const preview = _extractToolPreview(data.toolName, data.partialJson); + if (preview) { + $tool.find(".ai-tool-preview").text(preview); + _scrollToBottom(); + } + } + + /** + * Extract a complete string value for a given key from partial JSON. + * Returns null if the key isn't found or the value isn't complete yet. + */ + function _extractJsonStringValue(partialJson, key) { + // Try both with and without space after colon: "key":"val" or "key": "val" + let pattern = '"' + key + '":"'; + let idx = partialJson.indexOf(pattern); + if (idx === -1) { + pattern = '"' + key + '": "'; + idx = partialJson.indexOf(pattern); + } + if (idx === -1) { + return null; + } + const start = idx + pattern.length; + // Find the closing quote (not escaped) + let end = start; + while (end < partialJson.length) { + if (partialJson[end] === '"' && partialJson[end - 1] !== '\\') { + return partialJson.slice(start, end).replace(/\\"/g, '"').replace(/\\\\/g, '\\'); + } + end++; + } + return null; // value not complete yet + } + + /** + * Extract a readable one-line preview from partial tool input JSON. + * Looks for the "interesting" key per tool type (e.g. content for Write). + */ + function _extractToolPreview(toolName, partialJson) { + if (!partialJson) { + return ""; + } + // Map tool names to the key whose value we want to preview + const interestingKey = { + Write: "content", + Edit: "new_string", + Bash: "command", + Grep: "pattern", + Glob: "pattern" + }[toolName]; + + let raw = ""; + if (interestingKey) { + // Find the interesting key and grab everything after it + const keyPattern = '"' + interestingKey + '":'; + const idx = partialJson.indexOf(keyPattern); + if (idx !== -1) { + raw = partialJson.slice(idx + keyPattern.length).slice(-120); + } + // If the interesting key hasn't appeared yet, show nothing + // rather than raw JSON noise like {"file_path":... + } else { + // No interesting key defined for this tool — use the tail + raw = partialJson.slice(-120); + } + if (!raw) { + return ""; + } + // Clean up JSON syntax noise into readable text + let preview = raw + .replace(/\\n/g, " ") + .replace(/\\t/g, " ") + .replace(/\\"/g, '"') + .replace(/\s+/g, " ") + .trim(); + // Strip leading JSON artifacts (quotes, whitespace) + preview = preview.replace(/^[\s"]+/, ""); + // Strip trailing incomplete JSON artifacts + preview = preview.replace(/["{}\[\]]*$/, "").trim(); + return preview; + } + + function _onEditResult(_event, data) { + if (data.edits && data.edits.length > 0) { + data.edits.forEach(function (edit) { + _appendEditCard(edit); + }); + } + } + + function _onError(_event, data) { + _appendErrorMessage(data.error); + // Don't stop streaming — the node side may continue (partial results) + } + + function _onComplete(_event, data) { + _setStreaming(false); + } + + // --- DOM helpers --- + + function _appendUserMessage(text) { + const $msg = $( + '
' + + '
You
' + + '
' + + '
' + ); + $msg.find(".ai-msg-content").text(text); + $messages.append($msg); + _scrollToBottom(); + } + + /** + * Append a thinking/typing indicator while waiting for first content. + */ + function _appendThinkingIndicator() { + const $thinking = $( + '
' + + '
Claude
' + + '
' + + '' + + '' + + '' + + '
' + + '
' + ); + $messages.append($thinking); + _scrollToBottom(); + } + + /** + * Append a new assistant text segment. Creates a fresh content block + * that subsequent text deltas will stream into. Shows the "Claude" label + * only for the first segment in a response. + */ + function _appendAssistantSegment() { + // Check if this is a continuation (there's already assistant content or tools above) + const isFirst = !$messages.find(".ai-msg-assistant").not(".ai-thinking").length; + const $msg = $( + '
' + + (isFirst ? '
Claude
' : '') + + '
' + + '
' + ); + $messages.append($msg); + } + + /** + * Re-render the current streaming segment from accumulated segment text. + */ + function _renderAssistantStream() { + const $target = $messages.find(".ai-stream-target").last(); + if ($target.length) { + try { + $target.html(marked.parse(_segmentText, { breaks: true, gfm: true })); + } catch (e) { + $target.text(_segmentText); + } + _scrollToBottom(); + } + } + + function _appendToolIndicator(toolName, toolId) { + // Remove thinking indicator on first content + if (!_hasReceivedContent) { + _hasReceivedContent = true; + $messages.find(".ai-thinking").remove(); + } + + // Finalize the current text segment so tool appears after it, not at the end + $messages.find(".ai-stream-target").removeClass("ai-stream-target"); + _segmentText = ""; + + // Mark any previous active tool indicator as done + _finishActiveTools(); + + const config = TOOL_CONFIG[toolName] || { icon: "fa-solid fa-gear", color: "#adb9bd", label: toolName }; + + // Use requestId + toolId to ensure globally unique data-tool-id + const uniqueToolId = (_currentRequestId || "") + "-" + toolId; + const $tool = $( + '
' + + '
' + + '' + + '' + + '
' + + '
' + + '
' + ); + $tool.find(".ai-tool-label").text(config.label + "..."); + $tool.css("--tool-color", config.color); + $tool.attr("data-tool-icon", config.icon); + $messages.append($tool); + _scrollToBottom(); + } + + /** + * Update an existing tool indicator with details once tool input is known. + */ + function _updateToolIndicator(toolId, toolName, toolInput) { + const uniqueToolId = (_currentRequestId || "") + "-" + toolId; + const $tool = $messages.find('.ai-msg-tool[data-tool-id="' + uniqueToolId + '"]'); + if (!$tool.length) { + return; + } + + const config = TOOL_CONFIG[toolName] || { icon: "fa-solid fa-gear", color: "#adb9bd", label: toolName }; + const detail = _getToolDetail(toolName, toolInput); + + // Replace spinner with colored icon immediately + $tool.find(".ai-tool-spinner").replaceWith( + '' + + '' + + '' + ); + + // Update label to include summary + $tool.find(".ai-tool-label").text(detail.summary); + + // Add expandable detail if available + if (detail.lines && detail.lines.length) { + const $detail = $('
'); + detail.lines.forEach(function (line) { + $detail.append($('
').text(line)); + }); + $tool.append($detail); + + // Make header clickable to expand + $tool.find(".ai-tool-header").on("click", function () { + $tool.toggleClass("ai-tool-expanded"); + }).css("cursor", "pointer"); + } + + // For file-related tools, make label clickable to open the file + if (toolInput && toolInput.file_path && + (toolName === "Read" || toolName === "Write" || toolName === "Edit")) { + const filePath = toolInput.file_path; + $tool.find(".ai-tool-label").on("click", function (e) { + e.stopPropagation(); + const vfsPath = _realToVfsPath(filePath); + CommandManager.execute(Commands.CMD_OPEN, { fullPath: vfsPath }); + }).css("cursor", "pointer").addClass("ai-tool-label-clickable"); + } + + // Delay marking as done so the streaming preview stays visible briefly. + // The ai-tool-done class hides the preview via CSS; deferring it lets the + // browser paint the preview before it disappears. + setTimeout(function () { + $tool.addClass("ai-tool-done"); + $tool.find(".ai-tool-preview").text(""); + }, 1500); + + _scrollToBottom(); + } + + /** + * Extract a summary and detail lines from tool input. + */ + function _getToolDetail(toolName, input) { + if (!input) { + return { summary: toolName, lines: [] }; + } + switch (toolName) { + case "Glob": + return { + summary: "Searched: " + (input.pattern || ""), + lines: input.path ? ["in " + input.path] : [] + }; + case "Grep": + return { + summary: "Grep: " + (input.pattern || ""), + lines: [input.path ? "in " + input.path : "", input.include ? "include " + input.include : ""] + .filter(Boolean) + }; + case "Read": + return { + summary: "Read " + (input.file_path || "").split("/").pop(), + lines: [input.file_path || ""] + }; + case "Edit": + return { + summary: "Edit " + (input.file_path || "").split("/").pop(), + lines: [input.file_path || ""] + }; + case "Write": + return { + summary: "Write " + (input.file_path || "").split("/").pop(), + lines: [input.file_path || ""] + }; + case "Bash": + return { + summary: "Ran command", + lines: input.command ? [input.command] : [] + }; + case "Skill": + return { + summary: input.skill ? "Skill: " + input.skill : "Skill", + lines: input.args ? [input.args] : [] + }; + default: + return { summary: toolName, lines: [] }; + } + } + + /** + * Mark all active (non-done) tool indicators as finished. + */ + function _finishActiveTools() { + $messages.find(".ai-msg-tool:not(.ai-tool-done)").each(function () { + const $prev = $(this); + $prev.addClass("ai-tool-done"); + const iconClass = $prev.attr("data-tool-icon") || "fa-solid fa-check"; + const color = $prev.css("--tool-color") || "#adb9bd"; + $prev.find(".ai-tool-spinner").replaceWith( + '' + + '' + + '' + ); + }); + } + + function _appendEditCard(edit) { + const fileName = edit.file; + // Show just the filename, not full path + const displayName = fileName.split("/").pop(); + + const $card = $('
'); + const $header = $( + '
' + + '' + + '' + + '
' + ); + $header.find(".ai-edit-file").text(displayName); + + // Click filename to open file in editor + $header.find(".ai-edit-file").on("click", function () { + const vfsPath = _realToVfsPath(fileName); + CommandManager.execute(Commands.CMD_OPEN, { fullPath: vfsPath }); + }); + + const $toggle = $(''); + const $diff = $('
'); + + // Build diff content + if (edit.oldText) { + const oldLines = edit.oldText.split("\n"); + const newLines = edit.newText.split("\n"); + oldLines.forEach(function (line) { + $diff.append($('
').text("- " + line)); + }); + newLines.forEach(function (line) { + $diff.append($('
').text("+ " + line)); + }); + } else { + // Write (new file) — show all as new + edit.newText.split("\n").forEach(function (line) { + $diff.append($('
').text("+ " + line)); + }); + } + + $toggle.on("click", function () { + $diff.toggleClass("expanded"); + $toggle.text($diff.hasClass("expanded") ? "Hide diff" : "Show diff"); + }); + + // Undo button — restores previous content + $header.find(".ai-edit-undo-btn").on("click", function () { + const $btn = $(this); + if ($btn.hasClass("undone")) { + return; + } + if (edit.oldText === null) { + // Write (new file) — undo by restoring previous content + _undoEdit(edit.file, _previousContentMap[edit.file] || "") + .done(function () { + $btn.text("Undone").addClass("undone"); + }) + .fail(function (err) { + $card.append($('
').text(err.message || String(err))); + }); + } else { + // Edit — undo by reversing the replacement + const reverseEdit = { file: edit.file, oldText: edit.newText, newText: edit.oldText }; + _applySingleEdit(reverseEdit) + .done(function () { + $btn.text("Undone").addClass("undone"); + }) + .fail(function (err) { + $card.append($('
').text(err.message || String(err))); + }); + } + }); + + $card.append($header); + $card.append($toggle); + $card.append($diff); + $messages.append($card); + _scrollToBottom(); + } + + function _appendErrorMessage(text) { + const $msg = $( + '
' + + '
' + + '
' + ); + $msg.find(".ai-msg-content").text(text); + $messages.append($msg); + _scrollToBottom(); + } + + function _setStreaming(streaming) { + _isStreaming = streaming; + if ($status) { + $status.toggleClass("active", streaming); + } + if ($textarea) { + $textarea.prop("disabled", streaming); + $textarea.closest(".ai-chat-input-wrap").toggleClass("disabled", streaming); + if (!streaming) { + $textarea[0].focus({ preventScroll: true }); + } + } + if ($sendBtn && $stopBtn) { + if (streaming) { + $sendBtn.hide(); + $stopBtn.show(); + } else { + $stopBtn.hide(); + $sendBtn.show(); + } + } + if (!streaming && $messages) { + // Clean up thinking indicator if still present + $messages.find(".ai-thinking").remove(); + + // Finalize: remove ai-stream-target class so future messages get their own container + $messages.find(".ai-stream-target").removeClass("ai-stream-target"); + + // Mark all active tool indicators as done + _finishActiveTools(); + } + } + + function _scrollToBottom() { + if (_autoScroll && $messages && $messages.length) { + const el = $messages[0]; + el.scrollTop = el.scrollHeight; + } + } + + function _escapeAttr(str) { + return str.replace(/&/g, "&").replace(/"/g, """).replace(//g, ">"); + } + + // --- Edit application --- + + /** + * Apply a single edit to a document buffer (makes it a dirty tab). + * Called immediately when Claude's Write/Edit is intercepted, so + * subsequent Reads see the new content via the dirty-file hook. + * @param {Object} edit - {file, oldText, newText} + * @return {$.Promise} resolves with {previousContent} for undo support + */ + function _applySingleEdit(edit) { + const result = new $.Deferred(); + const vfsPath = _realToVfsPath(edit.file); + + function _applyToDoc() { + DocumentManager.getDocumentForPath(vfsPath) + .done(function (doc) { + try { + const previousContent = doc.getText(); + if (edit.oldText === null) { + // Write (new file or full replacement) + doc.setText(edit.newText); + } else { + // Edit — find oldText and replace + const docText = doc.getText(); + const idx = docText.indexOf(edit.oldText); + if (idx === -1) { + result.reject(new Error("Text not found in file — it may have changed")); + return; + } + const startPos = doc._masterEditor ? + doc._masterEditor._codeMirror.posFromIndex(idx) : + _indexToPos(docText, idx); + const endPos = doc._masterEditor ? + doc._masterEditor._codeMirror.posFromIndex(idx + edit.oldText.length) : + _indexToPos(docText, idx + edit.oldText.length); + doc.replaceRange(edit.newText, startPos, endPos); + } + // Open the file in the editor + CommandManager.execute(Commands.CMD_OPEN, { fullPath: vfsPath }); + result.resolve({ previousContent: previousContent }); + } catch (err) { + result.reject(err); + } + }) + .fail(function (err) { + result.reject(err || new Error("Could not open document")); + }); + } + + if (edit.oldText === null) { + // Write — file may not exist yet. Create it on disk first so + // getDocumentForPath succeeds, then set content in the buffer. + const file = FileSystem.getFileForPath(vfsPath); + file.write("", function (err) { + if (err) { + result.reject(new Error("Could not create file: " + err)); + return; + } + _applyToDoc(); + }); + } else { + // Edit — file must already exist + _applyToDoc(); + } + + return result.promise(); + } + + /** + * Undo a previously applied edit by restoring the document content. + * For new files (previousContent is empty string), closes the document. + * @param {string} filePath - real filesystem path + * @param {string} previousContent - content to restore + * @return {$.Promise} + */ + function _undoEdit(filePath, previousContent) { + const result = new $.Deferred(); + const vfsPath = _realToVfsPath(filePath); + + DocumentManager.getDocumentForPath(vfsPath) + .done(function (doc) { + try { + doc.setText(previousContent); + result.resolve(); + } catch (err) { + result.reject(err); + } + }) + .fail(function (err) { + result.reject(err || new Error("Could not open document for undo")); + }); + + return result.promise(); + } + + /** + * Convert a character index in text to a {line, ch} position. + */ + function _indexToPos(text, index) { + let line = 0, ch = 0; + for (let i = 0; i < index; i++) { + if (text[i] === "\n") { + line++; + ch = 0; + } else { + ch++; + } + } + return { line: line, ch: ch }; + } + + // --- Path utilities --- + + /** + * Get the real filesystem path for the current project root. + */ + function _getProjectRealPath() { + const root = ProjectManager.getProjectRoot(); + if (!root) { + return "/"; + } + const fullPath = root.fullPath; + // Desktop (Tauri) paths: /tauri/real/path → /real/path + if (fullPath.startsWith("/tauri/")) { + return fullPath.replace("/tauri", ""); + } + return fullPath; + } + + /** + * Convert a real filesystem path back to a VFS path that Phoenix understands. + */ + function _realToVfsPath(realPath) { + // If it already looks like a VFS path, return as-is + if (realPath.startsWith("/tauri/") || realPath.startsWith("/mnt/")) { + return realPath; + } + // Desktop builds use /tauri/ prefix + if (Phoenix.isNativeApp) { + return "/tauri" + realPath; + } + return realPath; + } + + /** + * Check if a file has unsaved changes in the editor and return its content. + * Used by the node-side Read hook to serve dirty buffer content to Claude. + */ + function getFileContent(params) { + const vfsPath = _realToVfsPath(params.filePath); + const doc = DocumentManager.getOpenDocumentForPath(vfsPath); + if (doc && doc.isDirty) { + return { isDirty: true, content: doc.getText() }; + } + return { isDirty: false, content: null }; + } + + /** + * Apply an edit to the editor buffer immediately (called by node-side hooks). + * The file appears as a dirty tab so subsequent Reads see the new content. + * @param {Object} params - {file, oldText, newText} + * @return {Promise<{applied: boolean, error?: string}>} + */ + function applyEditToBuffer(params) { + const deferred = new $.Deferred(); + _applySingleEdit(params) + .done(function (result) { + if (result && result.previousContent !== undefined) { + _previousContentMap[params.file] = result.previousContent; + } + deferred.resolve({ applied: true }); + }) + .fail(function (err) { + deferred.resolve({ applied: false, error: err.message || String(err) }); + }); + return deferred.promise(); + } + + // Public API + exports.init = init; + exports.initPlaceholder = initPlaceholder; + exports.getFileContent = getFileContent; + exports.applyEditToBuffer = applyEditToBuffer; +}); diff --git a/src/core-ai/main.js b/src/core-ai/main.js new file mode 100644 index 0000000000..01ffb04927 --- /dev/null +++ b/src/core-ai/main.js @@ -0,0 +1,54 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2021 - present core.ai . All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + * + */ + +/** + * AI sidebar tab integration. Sets up a NodeConnector to the claude-code-agent + * running in the node process and initializes the AIChatPanel UI. + * + * In non-native (browser) builds, shows a placeholder message instead. + */ +define(function (require, exports, module) { + + var AppInit = require("utils/AppInit"), + SidebarTabs = require("view/SidebarTabs"), + NodeConnector = require("NodeConnector"), + AIChatPanel = require("core-ai/AIChatPanel"); + + var AI_CONNECTOR_ID = "ph_ai_claude"; + + exports.getFileContent = async function (params) { + return AIChatPanel.getFileContent(params); + }; + + exports.applyEditToBuffer = async function (params) { + return AIChatPanel.applyEditToBuffer(params); + }; + + AppInit.appReady(function () { + SidebarTabs.addTab("ai", "AI", "fa-solid fa-wand-magic-sparkles", { priority: 200 }); + + if (Phoenix.isNativeApp) { + var nodeConnector = NodeConnector.createNodeConnector(AI_CONNECTOR_ID, exports); + AIChatPanel.init(nodeConnector); + } else { + AIChatPanel.initPlaceholder(); + } + }); +}); diff --git a/src/desktop-metrics.html b/src/desktop-metrics.html index d97da34b1a..23adc0f6c0 100644 --- a/src/desktop-metrics.html +++ b/src/desktop-metrics.html @@ -5,8 +5,15 @@ Desktop Metrics logger with Google Analytics Phoenix Desktop Metrics emitter to GA. - \ No newline at end of file + diff --git a/src/document/DocumentCommandHandlers.js b/src/document/DocumentCommandHandlers.js index 9029468d3d..387089dfed 100644 --- a/src/document/DocumentCommandHandlers.js +++ b/src/document/DocumentCommandHandlers.js @@ -60,6 +60,7 @@ define(function (require, exports, module) { NodeConnector = require("NodeConnector"), NodeUtils = require("utils/NodeUtils"), ChangeHelper = require("editor/EditorHelper/ChangeHelper"), + SidebarTabs = require("view/SidebarTabs"), _ = require("thirdparty/lodash"); const KernalModeTrust = window.KernalModeTrust; @@ -1731,7 +1732,21 @@ define(function (require, exports, module) { .finally(()=>{ raceAgainstTime(_safeNodeTerminate()) .finally(()=>{ - Phoenix.app.closeWindow(); + // In Electron multi-window case, use allowClose() to bypass + // the close handler (which would otherwise trigger another + // cleanup cycle). But for last window, closeWindow() calls + // quitApp() (no loop) and runs quitTimeAppUpdateHandler. + if(window.__ELECTRON__) { + Phoenix.app.getPhoenixInstanceCount().then(count => { + if(count === 1) { + Phoenix.app.closeWindow(); + } else { + window.electronAPI.allowClose(); + } + }); + } else { + Phoenix.app.closeWindow(); + } }); }); }, @@ -1742,8 +1757,10 @@ define(function (require, exports, module) { } function newPhoenixWindow(cliArgsArray = null, cwd=null) { - let width = window.innerWidth; - let height = window.innerHeight; + // Electron needs outerWidth/outerHeight (includes window chrome). + // Tauri needs innerWidth/innerHeight. + let width = window.__ELECTRON__ ? window.outerWidth : window.innerWidth; + let height = window.__ELECTRON__ ? window.outerHeight : window.innerHeight; Phoenix.app.openNewPhoenixEditorWindow(width, height, cliArgsArray, cwd); } @@ -1939,6 +1956,7 @@ define(function (require, exports, module) { function handleShowInTree() { let activeFile = MainViewManager.getCurrentlyViewedFile(MainViewManager.ACTIVE_PANE); if(activeFile){ + SidebarTabs.setActiveTab(SidebarTabs.SIDEBAR_TAB_FILES); ProjectManager.showInTree(activeFile); } } @@ -2252,16 +2270,14 @@ define(function (require, exports, module) { Phoenix.app.closeWindow(true); } } - function attachTauriUnloadHandler() { - window.__TAURI__.window.appWindow.onCloseRequested((event)=>{ + function _attachNativeUnloadHandler() { + Phoenix.app.onCloseWindowRequested(()=>{ _forceQuitIfNeeded(); if(closeInProgress){ - event.preventDefault(); - return; + return false; } closeInProgress = true; PreferencesManager.setViewState("windowClosingTime", new Date().getTime()); - event.preventDefault(); _handleWindowGoingAway(null, closeSuccess=>{ console.log('close success: ', closeSuccess); exitWaitPromises.push(_safeFlushDB()); @@ -2270,20 +2286,35 @@ define(function (require, exports, module) { raceAgainstTime(_safeNodeTerminate()) .finally(()=>{ closeInProgress = false; - Phoenix.app.closeWindow(); + // In Electron multi-window case, we must call allowClose() to + // complete the original close request. Calling closeWindow() would + // trigger a new close sequence and cause an infinite loop. + // But for last window, closeWindow() calls quitApp() (no loop), + // and we need it to run quitTimeAppUpdateHandler. + if(window.__ELECTRON__) { + Phoenix.app.getPhoenixInstanceCount().then(count => { + if(count === 1) { + Phoenix.app.closeWindow(); + } else { + window.electronAPI.allowClose(); + } + }); + } else { + Phoenix.app.closeWindow(); + } }); }); }, closeFail=>{ console.log('close fail: ', closeFail); closeInProgress = false; }); + return false; }); } - let isTestWindow = (new window.URLSearchParams(window.location.search || "")).get("testEnvironment"); - if (!isTestWindow) { + if (!Phoenix.isTestWindow) { if(Phoenix.isNativeApp) { - attachTauriUnloadHandler(); + _attachNativeUnloadHandler(); } else { attachBrowserUnloadHandler(); } diff --git a/src/editor/Editor.js b/src/editor/Editor.js index 7d680ff7e9..c19540a58f 100644 --- a/src/editor/Editor.js +++ b/src/editor/Editor.js @@ -135,7 +135,8 @@ define(function (require, exports, module) { const LINE_NUMBER_GUTTER = EditorPreferences.LINE_NUMBER_GUTTER, LINE_NUMBER_GUTTER_PRIORITY = EditorPreferences.LINE_NUMBER_GUTTER_PRIORITY, - CODE_FOLDING_GUTTER_PRIORITY = EditorPreferences.CODE_FOLDING_GUTTER_PRIORITY; + CODE_FOLDING_GUTTER_PRIORITY = EditorPreferences.CODE_FOLDING_GUTTER_PRIORITY, + MOUSE_WHEEL_SCROLL_SENSITIVITY = EditorPreferences.MOUSE_WHEEL_SCROLL_SENSITIVITY; let editorOptions = [...Object.keys(cmOptions), AUTO_TAB_SPACES]; @@ -148,6 +149,13 @@ define(function (require, exports, module) { */ var _duringFocus = false; + /** + * Cached mouse wheel scroll sensitivity value from preferences + * @private + * @type {number} + */ + let _mouseWheelScrollSensitivity = 1; + /** * Constant: Normal boundary check when centering text. * @type {number} @@ -464,11 +472,11 @@ define(function (require, exports, module) { const $cmElement = this.$el; $cmElement[0].addEventListener("wheel", (event) => { const $editor = $cmElement.find(".CodeMirror-scroll"); - // we need to slow down the scroll by the factor of line height. else the scrolling is too fast. - // this became a problem after we added the custom line height feature causing jumping scrolls esp in safari - // and mac if we dont do this scroll scaling. + // We need to scale the scroll by the factor of line height. This became a problem after we added + // the custom line height feature causing jumping scrolls esp in safari and mac if we dont do + // this scroll scaling. const lineHeight = parseFloat(getComputedStyle($editor[0]).lineHeight); - const defaultHeight = 14, scrollScaleFactor = lineHeight/defaultHeight; + const defaultHeight = 14, scrollScaleFactor = lineHeight / defaultHeight; // when user is pressing the 'Shift' key, we need to convert the vertical scroll to horizontal scroll if (event.shiftKey) { @@ -493,8 +501,19 @@ define(function (require, exports, module) { // apply the vertical scrolling normally if (event.deltaY !== 0) { - const scrollDelta = event.deltaY; - $editor[0].scrollTop += (scrollDelta/scrollScaleFactor); + let scrollAmount; + if (event.deltaMode === 0) { + // Pixel mode - browser reports delta in pixels, system scroll settings already applied. + // Scale by line height factor to normalize for custom line heights. + scrollAmount = event.deltaY / scrollScaleFactor; + } else if (event.deltaMode === 1) { + // Line mode - delta is in lines, convert to pixels using actual line height + scrollAmount = event.deltaY * defaultHeight; + } else { + // Page mode - delta is in pages, convert to viewport height + scrollAmount = event.deltaY * $editor[0].clientHeight; + } + $editor[0].scrollTop += scrollAmount * _mouseWheelScrollSensitivity; event.preventDefault(); } }); @@ -3171,6 +3190,12 @@ define(function (require, exports, module) { }); }); + // Set up listener for mouse wheel scroll sensitivity preference + PreferencesManager.on("change", MOUSE_WHEEL_SCROLL_SENSITIVITY, function () { + _mouseWheelScrollSensitivity = PreferencesManager.get(MOUSE_WHEEL_SCROLL_SENSITIVITY); + }); + _mouseWheelScrollSensitivity = PreferencesManager.get(MOUSE_WHEEL_SCROLL_SENSITIVITY); + // Define public API exports.Editor = Editor; exports.BOUNDARY_CHECK_NORMAL = BOUNDARY_CHECK_NORMAL; diff --git a/src/editor/EditorHelper/EditorPreferences.js b/src/editor/EditorHelper/EditorPreferences.js index 7a6daba14a..a96430b1c4 100644 --- a/src/editor/EditorHelper/EditorPreferences.js +++ b/src/editor/EditorHelper/EditorPreferences.js @@ -49,7 +49,8 @@ define(function (require, exports, module) { WORD_WRAP = "wordWrap", AUTO_HIDE_SEARCH = "autoHideSearch", INDENT_LINE_COMMENT = "indentLineComment", - INPUT_STYLE = "inputStyle"; + INPUT_STYLE = "inputStyle", + MOUSE_WHEEL_SCROLL_SENSITIVITY = "mouseWheelScrollSensitivity"; /** * Constants @@ -167,6 +168,10 @@ define(function (require, exports, module) { PreferencesManager.definePreference(INPUT_STYLE, "string", "textarea", { description: Strings.DESCRIPTION_INPUT_STYLE }); + PreferencesManager.definePreference(MOUSE_WHEEL_SCROLL_SENSITIVITY, "number", 1, { + validator: _.partialRight(ValidationUtils.isWithinRange, 0.1, 10), + description: Strings.DESCRIPTION_MOUSE_WHEEL_SCROLL_SENSITIVITY + }); function isValidTabSize (size) { return ValidationUtils.isIntegerInRange(size, MIN_TAB_SIZE, MAX_TAB_SIZE); @@ -215,6 +220,7 @@ define(function (require, exports, module) { exports.AUTO_HIDE_SEARCH = AUTO_HIDE_SEARCH; exports.INDENT_LINE_COMMENT = INDENT_LINE_COMMENT; exports.INPUT_STYLE = INPUT_STYLE; + exports.MOUSE_WHEEL_SCROLL_SENSITIVITY = MOUSE_WHEEL_SCROLL_SENSITIVITY; exports.MIN_SPACE_UNITS = MIN_SPACE_UNITS; exports.MIN_TAB_SIZE = MIN_TAB_SIZE; diff --git a/src/editor/EditorManager.js b/src/editor/EditorManager.js index 40f9d30092..4dd00f2807 100644 --- a/src/editor/EditorManager.js +++ b/src/editor/EditorManager.js @@ -775,7 +775,7 @@ define(function (require, exports, module) { // Set up event dispatching EventDispatcher.makeEventDispatcher(exports); - EventDispatcher.setLeakThresholdForEvent(EVENT_ACTIVE_EDITOR_CHANGED, 30); + EventDispatcher.setLeakThresholdForEvent(EVENT_ACTIVE_EDITOR_CHANGED, 50); // File-based preferences handling exports.on(EVENT_ACTIVE_EDITOR_CHANGED, function (e, current) { diff --git a/src/extensions/default/DebugCommands/main.js b/src/extensions/default/DebugCommands/main.js index e2a5b97824..58d792abd8 100644 --- a/src/extensions/default/DebugCommands/main.js +++ b/src/extensions/default/DebugCommands/main.js @@ -19,7 +19,7 @@ * */ -/*globals path, logger, Phoenix*/ +/*globals path, logger, Phoenix, AppConfig*/ /*jslint regexp: true */ define(function (require, exports, module) { @@ -828,6 +828,9 @@ define(function (require, exports, module) { diagnosticsSubmenu.addMenuItem(DEBUG_RUN_UNIT_TESTS); CommandManager.register(Strings.CMD_BUILD_TESTS, DEBUG_BUILD_TESTS, TestBuilder.toggleTestBuilder); diagnosticsSubmenu.addMenuItem(DEBUG_BUILD_TESTS); + if (AppConfig.config.environment === "dev") { + diagnosticsSubmenu.addMenuItem("debug.phoenixBuilderConnect"); + } diagnosticsSubmenu.addMenuDivider(); diagnosticsSubmenu.addMenuItem(DEBUG_ENABLE_LOGGING); diagnosticsSubmenu.addMenuItem(DEBUG_ENABLE_PHNODE_INSPECTOR, undefined, undefined, undefined, { diff --git a/src/extensions/default/Git/src/git/GitCli.js b/src/extensions/default/Git/src/git/GitCli.js index 39cd9aa56f..16d269776e 100644 --- a/src/extensions/default/Git/src/git/GitCli.js +++ b/src/extensions/default/Git/src/git/GitCli.js @@ -1022,9 +1022,33 @@ define(function (require, exports) { function getGitRoot() { var projectRoot = Utils.getProjectRoot(); - return git(["rev-parse", "--show-toplevel"], { - cwd: fs.getTauriPlatformPath(projectRoot) - }) + + // Quick filesystem pre-check: if .git doesn't exist in the project root, + // skip spawning git entirely. This avoids triggering macOS CLT shim dialogs + // on non-git projects and is a minor optimization on all platforms. + return new Promise(function (resolve) { + var checkPath = projectRoot; + if (strEndsWith(checkPath, "/")) { + checkPath = checkPath.slice(0, -1); + } + if (typeof brackets !== "undefined" && brackets.fs && brackets.fs.stat) { + brackets.fs.stat(checkPath + "/.git", function (err, result) { + var exists = err ? false : (result.isFile() || result.isDirectory()); + resolve(exists); + }); + } else { + FileSystem.resolve(checkPath + "/.git", function (err, item, stat) { + var exists = err ? false : (stat.isFile || stat.isDirectory); + resolve(exists); + }); + } + }).then(function (hasGitDir) { + if (!hasGitDir) { + return null; + } + return git(["rev-parse", "--show-toplevel"], { + cwd: fs.getTauriPlatformPath(projectRoot) + }) .catch(function (e) { if (ErrorHandler.contains(e, "Not a git repository")) { return null; @@ -1095,6 +1119,7 @@ define(function (require, exports) { }); }); + }); } function setTagName(tagname, commitHash) { diff --git a/src/extensions/default/Git/src/utils/Setup.js b/src/extensions/default/Git/src/utils/Setup.js index af109c30b9..a172da134c 100644 --- a/src/extensions/default/Git/src/utils/Setup.js +++ b/src/extensions/default/Git/src/utils/Setup.js @@ -16,9 +16,10 @@ define(function (require, exports) { ]; let standardGitPathsNonWin = [ + "/opt/homebrew/bin/git", // Apple Silicon Homebrew "/usr/local/git/bin/git", "/usr/local/bin/git", - "/usr/bin/git" + "/usr/bin/git" // macOS CLT shim check handled on node side ]; let extensionActivated = false; @@ -27,7 +28,7 @@ define(function (require, exports) { function getGitVersion() { return new Promise(function (resolve, reject) { - // TODO: do this in two steps - first check user config and then check all + // User-configured path gets priority, then "git" (PATH lookup), then standard paths var pathsToLook = [Preferences.get("gitPath"), "git"].concat(brackets.platform === "win" ? standardGitPathsWin : standardGitPathsNonWin); pathsToLook = _.unique(_.compact(pathsToLook)); diff --git a/src/extensions/default/HealthData/SendToAnalytics.js b/src/extensions/default/HealthData/SendToAnalytics.js index 459f41c331..8c200ccf26 100644 --- a/src/extensions/default/HealthData/SendToAnalytics.js +++ b/src/extensions/default/HealthData/SendToAnalytics.js @@ -135,14 +135,16 @@ define(function (require, exports, module) { _sendStorageMetrics(); } + const envName = window.__TAURI__ ? "tauri-" : (window.__ELECTRON__ ? "electron-" : ""); + let bugsnagPerformanceInited = false; function _initBugsnagPerformance() { bugsnagPerformanceInited = true; BugsnagPerformance.start({ apiKey: '94ef94f4daf871ca0f2fc912c6d4764d', appVersion: AppConfig.version, - releaseStage: window.__TAURI__ ? - `tauri-${AppConfig.config.bugsnagEnv}-${Phoenix.platform}` : AppConfig.config.bugsnagEnv, + releaseStage: window.__IS_NATIVE_SHELL__ ? + `${envName}${AppConfig.config.bugsnagEnv}-${Phoenix.platform}` : AppConfig.config.bugsnagEnv, autoInstrumentRouteChanges: false, autoInstrumentNetworkRequests: false, autoInstrumentFullPageLoads: false diff --git a/src/extensionsIntegrated/Phoenix-live-preview/live-preview.css b/src/extensionsIntegrated/Phoenix-live-preview/live-preview.css index ec9f7cef78..6e3536afd7 100644 --- a/src/extensionsIntegrated/Phoenix-live-preview/live-preview.css +++ b/src/extensionsIntegrated/Phoenix-live-preview/live-preview.css @@ -105,6 +105,51 @@ padding-left: 7.5px; } +.lp-device-size-icon { + color: #a0a0a0; + display: flex; + align-items: center; + padding-left: 7.5px; + margin-right: 7.5px; +} + +#deviceSizeBtn.btn-dropdown::after { + position: static; + margin-top: 2px; + margin-left: 3px; +} + +.device-size-item-icon { + margin-right: 6px; + width: 12px; + text-align: center; + font-size: inherit; +} + +.device-size-item-row { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; +} + +.device-size-item-width { + margin-left: 10px; + opacity: 0.5; +} + +.device-size-item-disabled { + opacity: 0.35; +} + +.device-size-item-breakpoint-icon { + margin-right: 6px; + width: 12px; + text-align: center; + font-size: inherit; + color: rgba(100, 180, 255, 0.8); +} + #livePreviewModeBtn { min-width: fit-content; display: flex; diff --git a/src/extensionsIntegrated/Phoenix-live-preview/main.js b/src/extensionsIntegrated/Phoenix-live-preview/main.js index b28c84eaa4..1c393d0ef6 100644 --- a/src/extensionsIntegrated/Phoenix-live-preview/main.js +++ b/src/extensionsIntegrated/Phoenix-live-preview/main.js @@ -545,7 +545,8 @@ define(function (require, exports, module) { let panel, urlPinned, currentLivePreviewURL = "", - currentPreviewFile = ''; + currentPreviewFile = '', + _loadGeneration = 0; function _blankIframe() { // we have to remove the dom node altog as at time chrome fails to clear workers if we just change @@ -795,8 +796,12 @@ define(function (require, exports, module) { if(!isPreviewLoadable){ return; } + const thisGeneration = ++_loadGeneration; // panel-live-preview-title let previewDetails = await StaticServer.getPreviewDetails(); + if(thisGeneration !== _loadGeneration) { + return; // A newer _loadPreview call has been made; this one is stale + } if(urlPinned && !force) { return; } diff --git a/src/extensionsIntegrated/appUpdater/main.js b/src/extensionsIntegrated/appUpdater/main.js index 4cf67dc540..72bcfdb26c 100644 --- a/src/extensionsIntegrated/appUpdater/main.js +++ b/src/extensionsIntegrated/appUpdater/main.js @@ -24,6 +24,7 @@ // shell.js file. This is app updates are pretty core level even though we do it as an extension here. define(function (require, exports, module) { + require("./update-electron"); const AppInit = require("utils/AppInit"), Metrics = require("utils/Metrics"), FileSystem = require("filesystem/FileSystem"), @@ -517,7 +518,14 @@ define(function (require, exports, module) { let updateInstalledDialogShown = false, updateFailedDialogShown = false; AppInit.appReady(function () { - if(!Phoenix.isNativeApp || Phoenix.isTestWindow) { + if(Phoenix.isTestWindow) { + return; + } + if(window.__ELECTRON__) { + // Electron updates handled by update-electron.js + return; + } + if(!window.__TAURI__) { // app updates are only for desktop builds return; } diff --git a/src/extensionsIntegrated/appUpdater/update-electron.js b/src/extensionsIntegrated/appUpdater/update-electron.js new file mode 100644 index 0000000000..db351b0402 --- /dev/null +++ b/src/extensionsIntegrated/appUpdater/update-electron.js @@ -0,0 +1,515 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2021 - present core.ai . All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + * + */ + +/*global logger*/ + +// Electron-specific app updater for Linux +// Windows/Mac are not supported in Electron edge builds + +define(function (require, exports, module) { + const AppInit = require("utils/AppInit"), + Metrics = require("utils/Metrics"), + Commands = require("command/Commands"), + CommandManager = require("command/CommandManager"), + Menus = require("command/Menus"), + Dialogs = require("widgets/Dialogs"), + DefaultDialogs = require("widgets/DefaultDialogs"), + Strings = require("strings"), + marked = require('thirdparty/marked.min'), + semver = require("thirdparty/semver.browser"), + NotificationUI = require("widgets/NotificationUI"), + TaskManager = require("features/TaskManager"), + NativeApp = require("utils/NativeApp"), + PreferencesManager = require("preferences/PreferencesManager"); + + let updateTask, updatePendingRestart, updateFailed; + + const KEY_LAST_UPDATE_CHECK_TIME = "PH_LAST_UPDATE_CHECK_TIME", + KEY_LAST_UPDATE_DESCRIPTION = "PH_LAST_UPDATE_DESCRIPTION", + KEY_UPDATE_AVAILABLE = "PH_UPDATE_AVAILABLE"; + + const PREFS_AUTO_UPDATE = "autoUpdate"; + const MAX_LOG_LINES = 500; + let isAutoUpdateFlow = true; + let updateScheduled = false; + let cachedUpdateDetails = null; + + function showOrHideUpdateIcon() { + if(updateScheduled && !updateTask) { + updateTask = TaskManager.addNewTask(Strings.UPDATING_APP, Strings.UPDATING_APP_MESSAGE, + ``, { + noSpinnerNotification: isAutoUpdateFlow, + onSelect: function () { + if(updatePendingRestart){ + Dialogs.showInfoDialog(Strings.UPDATE_READY_RESTART_TITLE, + Strings.UPDATE_READY_RESTART_INSTALL_MESSAGE); + } else if(updateFailed){ + Dialogs.showInfoDialog(Strings.UPDATE_FAILED_TITLE, Strings.UPDATE_FAILED_MESSAGE); + } else { + Dialogs.showInfoDialog(Strings.UPDATING_APP, Strings.UPDATING_APP_DIALOG_MESSAGE); + } + } + }); + if(!isAutoUpdateFlow) { + updateTask.show(); + } else { + updateTask.flashSpinnerForAttention(); + } + } + let updateAvailable = PreferencesManager.getViewState(KEY_UPDATE_AVAILABLE); + if(updateAvailable){ + $("#update-notification").removeClass("forced-hidden"); + } else { + $("#update-notification").addClass("forced-hidden"); + } + } + + function fetchJSON(url) { + return fetch(url) + .then(response => { + if (!response.ok) { + return null; + } + return response.json(); + }); + } + + async function getUpdatePlatformKey() { + const platformArch = await Phoenix.app.getPlatformArch(); + let os = 'windows'; + if (brackets.platform === "mac") { + os = "darwin"; + } else if (brackets.platform === "linux") { + os = "linux"; + } + return `${os}-${platformArch}`; + } + + async function getUpdateDetails() { + const updatePlatformKey = await getUpdatePlatformKey(); + const updateDetails = { + shouldUpdate: false, + updatePendingRestart: false, + downloadURL: null, + currentVersion: Phoenix.metadata.apiVersion, + updateVersion: null, + releaseNotesMarkdown: null, + updatePlatform: updatePlatformKey + }; + try{ + const updateMetadata = await fetchJSON(brackets.config.app_update_url); + // In Electron, binary version and loaded app version are always the same + // since both are loaded at app start and only change after full restart + const currentVersion = await window.electronAPI.getAppVersion(); + if(semver.gt(updateMetadata.version, currentVersion)){ + console.log("Update available: ", updateMetadata, "Detected platform: ", updatePlatformKey); + PreferencesManager.setViewState(KEY_UPDATE_AVAILABLE, true); + updateDetails.shouldUpdate = true; + updateDetails.updateVersion = updateMetadata.version; + updateDetails.releaseNotesMarkdown = updateMetadata.notes; + if(updateMetadata.platforms && updateMetadata.platforms[updatePlatformKey]){ + updateDetails.downloadURL = updateMetadata.platforms[updatePlatformKey].url; + } + } else { + console.log("no updates available for platform: ", updateDetails.updatePlatform); + PreferencesManager.setViewState(KEY_UPDATE_AVAILABLE, false); + } + showOrHideUpdateIcon(); + } catch (e) { + console.error("Error getting update metadata", e); + logger.reportError(e, `Error getting app update metadata`); + updateFailed = true; + Metrics.countEvent(Metrics.EVENT_TYPE.UPDATES, 'fail', "Unknown"+Phoenix.platform); + } + return updateDetails; + } + + /** + * Check if we're at an upgradable location. + * For Electron on Linux, we require the AppImage to be in ~/.phoenix-code/ + */ + async function isUpgradableLocation() { + try { + const isPackaged = await window.electronAPI.isPackaged(); + if (!isPackaged) { + return false; + } + const homeDir = await window.electronFSAPI.homeDir(); + const phoenixInstallDir = `${homeDir}.phoenix-code/`; + const execPath = await window.electronAPI.getExecutablePath(); + return execPath.startsWith(phoenixInstallDir); + } catch (e) { + console.error(e); + return false; + } + } + + function _getButtons(isUpgradableLoc) { + const updateLater = + {className: Dialogs.DIALOG_BTN_CLASS_NORMAL, id: Dialogs.DIALOG_BTN_CANCEL, text: Strings.UPDATE_LATER }; + const getItNow = + { className: Dialogs.DIALOG_BTN_CLASS_PRIMARY, id: Dialogs.DIALOG_BTN_OK, text: Strings.GET_IT_NOW }; + const updateOnExit = + { className: Dialogs.DIALOG_BTN_CLASS_PRIMARY, id: Dialogs.DIALOG_BTN_OK, text: Strings.UPDATE_ON_EXIT }; + if(!isUpgradableLoc) { + return [updateLater, getItNow]; + } + return [updateLater, updateOnExit]; + } + + async function scheduleUpdate(updateDetails) { + updateScheduled = true; + updatePendingRestart = true; + cachedUpdateDetails = updateDetails; + // Store in shared state so other windows know update is scheduled + await window.electronAPI.setUpdateScheduled(true); + showOrHideUpdateIcon(); + Metrics.countEvent(Metrics.EVENT_TYPE.UPDATES, 'scheduled', Phoenix.platform); + updateTask.setSucceded(); + updateTask.setTitle(Strings.UPDATE_DONE); + updateTask.setMessage(Strings.UPDATE_RESTART_INSTALL); + NotificationUI.createToastFromTemplate(Strings.UPDATE_READY_RESTART_TITLE, + `
${Strings.UPDATE_READY_RESTART_INSTALL_MESSAGE}
`, { + toastStyle: NotificationUI.NOTIFICATION_STYLES_CSS_CLASS.SUCCESS, + dismissOnClick: true + }); + Phoenix.app.registerQuitTimeAppUpdateHandler(quitTimeAppUpdateHandler); + } + + async function _updateWithConfirmDialog(isUpgradableLoc, updateDetails) { + const buttons = _getButtons(isUpgradableLoc); + let markdownHtml = marked.parse(updateDetails.releaseNotesMarkdown || ""); + Metrics.countEvent(Metrics.EVENT_TYPE.UPDATES, 'dialog', "shown"+Phoenix.platform); + Dialogs.showModalDialog(DefaultDialogs.DIALOG_ID_INFO, Strings.UPDATE_AVAILABLE_TITLE, markdownHtml, buttons) + .done(option=>{ + if(option === Dialogs.DIALOG_BTN_CANCEL){ + Metrics.countEvent(Metrics.EVENT_TYPE.UPDATES, 'dialog', "cancel"+Phoenix.platform); + return; + } + if(!isUpgradableLoc) { + const downloadPage = brackets.config.homepage_url || "https://phcode.io"; + NativeApp.openURLInDefaultBrowser(downloadPage); + Metrics.countEvent(Metrics.EVENT_TYPE.UPDATES, 'dialog', "nonUpgradable"+Phoenix.platform); + return; + } + if(option === Dialogs.DIALOG_BTN_OK && !updateScheduled){ + Metrics.countEvent(Metrics.EVENT_TYPE.UPDATES, 'dialog', "okUpdate"+Phoenix.platform); + scheduleUpdate(updateDetails); + } + }); + } + + async function checkForUpdates(isAutoUpdate) { + isAutoUpdateFlow = isAutoUpdate; + showOrHideUpdateIcon(); + if(!navigator.onLine) { + return; + } + if(updateTask){ + $("#status-tasks .btn-dropdown").click(); + return; + } + const updateDetails = await getUpdateDetails(); + if(updateFailed) { + if(!isAutoUpdate) { + Dialogs.showInfoDialog(Strings.UPDATE_FAILED_TITLE, Strings.UPDATE_FAILED_MESSAGE); + } + return; + } + if(updatePendingRestart || updateDetails.updatePendingRestart){ + if(!isAutoUpdate){ + Dialogs.showInfoDialog(Strings.UPDATE_READY_RESTART_TITLE, + Strings.UPDATE_READY_RESTART_INSTALL_MESSAGE); + } + return; + } + if(!updateDetails.shouldUpdate){ + (!isAutoUpdate) && Dialogs.showInfoDialog(Strings.UPDATE_NOT_AVAILABLE_TITLE, Strings.UPDATE_UP_TO_DATE); + return; + } + const autoUpdateEnabled = PreferencesManager.get(PREFS_AUTO_UPDATE); + if(isAutoUpdate && !autoUpdateEnabled){ + return; + } + const isUpgradableLoc = await isUpgradableLocation(); + if(!isUpgradableLoc || !isAutoUpdate) { + _updateWithConfirmDialog(isUpgradableLoc, updateDetails); + } else if(!updateScheduled) { + Metrics.countEvent(Metrics.EVENT_TYPE.UPDATES, 'auto', "silent"+Phoenix.platform); + PreferencesManager.setViewState(KEY_LAST_UPDATE_DESCRIPTION, { + releaseNotesMarkdown: updateDetails.releaseNotesMarkdown, + updateVersion: updateDetails.updateVersion + }); + scheduleUpdate(updateDetails); + } + } + + /** + * Launches the Linux updater using spawnProcess with streaming output + * @param {function} onOutput - Callback for stdout/stderr lines + * @returns {Promise} Resolves when update completes, rejects on error + */ + function launchLinuxUpdater(onOutput) { + return new Promise((resolve, reject) => { + const stageValue = Phoenix.config.environment; + console.log('Stage:', stageValue); + let scriptUrl = 'https://updates.phcode.io/linux/installer.sh'; + if(stageValue === 'dev' || stageValue === 'stage'){ + scriptUrl = "https://updates.phcode.io/linux/installer-latest-experimental-build.sh"; + } + + // Use spawnProcess to run bash with the wget|bash command + const command = '/bin/bash'; + const args = ['-c', `wget -qO- ${scriptUrl} | bash -s -- --upgrade`]; + + window.electronAppAPI.spawnProcess(command, args) + .then(instanceId => { + // Set up output handlers + window.electronAppAPI.onProcessStdout((id, line) => { + if (id === instanceId && onOutput) { + onOutput('stdout', line); + } + }); + window.electronAppAPI.onProcessStderr((id, line) => { + if (id === instanceId && onOutput) { + onOutput('stderr', line); + } + }); + window.electronAppAPI.onProcessClose((id, data) => { + if (id === instanceId) { + if (data.code === 0) { + resolve(); + } else { + reject(new Error(`Update script exited with code: ${data.code}`)); + } + } + }); + window.electronAppAPI.onProcessError((id, err) => { + if (id === instanceId) { + reject(new Error(`Update process error: ${err}`)); + } + }); + }) + .catch(reject); + }); + } + + async function quitTimeAppUpdateHandler() { + if(!updateScheduled){ + return; + } + // Clear the scheduled flag in shared state + await window.electronAPI.setUpdateScheduled(false); + console.log("Installing update at quit time"); + return new Promise(resolve => { + let dialog; + let logLines = []; + + function appendLogLine(text) { + // Split text into lines and add each + const lines = text.split('\n').filter(l => l.trim()); + for (const line of lines) { + logLines.push(line); + // Keep only last MAX_LOG_LINES + if (logLines.length > MAX_LOG_LINES) { + logLines.shift(); + } + } + // Update the log display + const logElement = document.getElementById('update-log-output'); + if (logElement) { + logElement.textContent = logLines.join('\n'); + logElement.scrollTop = logElement.scrollHeight; + } + } + + function failUpdateDialogAndExit(err) { + console.error("error updating: ", err); + dialog && dialog.close(); + // Build full log text for copying + const fullLogText = logLines.join('\n') + '\n\nError: ' + (err.message || err); + // Show failure dialog with log output and hover copy icon + const failContent = ` +

${Strings.UPDATE_FAILED_VISIT_SITE_MESSAGE}

+
+
${fullLogText}
+ +
+ `; + const failDialog = Dialogs.showModalDialog( + DefaultDialogs.DIALOG_ID_ERROR, + Strings.UPDATE_FAILED_TITLE, + failContent, + [{ className: Dialogs.DIALOG_BTN_CLASS_PRIMARY, id: Dialogs.DIALOG_BTN_OK, text: Strings.OK }] + ); + // Set up hover and click handlers for copy icon + const $container = $('#update-fail-log-container'); + const $copyBtn = $('#update-log-copy-btn'); + $container.on('mouseenter', () => $copyBtn.css('opacity', '1')); + $container.on('mouseleave', () => $copyBtn.css('opacity', '0')); + $copyBtn.on('click', () => { + Phoenix.app.copyToClipboard(fullLogText); + $copyBtn.removeClass('fa-copy').addClass('fa-check'); + setTimeout(() => { + $copyBtn.removeClass('fa-check').addClass('fa-copy'); + }, 1500); + }); + $copyBtn.on('mouseenter', () => $copyBtn.css({ 'background': '#333', 'color': '#fff' })); + $copyBtn.on('mouseleave', () => $copyBtn.css({ 'background': 'transparent', 'color': '#888' })); + + failDialog.done(() => { + NativeApp.openURLInDefaultBrowser(Phoenix.config.update_download_page) + .catch(console.error) + .finally(resolve); + }); + } + + // Create dialog with terminal-style log output + const dialogContent = ` +

${Strings.UPDATE_INSTALLING_MESSAGE}

+

+            `;
+
+            dialog = Dialogs.showModalDialog(
+                DefaultDialogs.DIALOG_ID_INFO,
+                Strings.UPDATE_INSTALLING,
+                dialogContent,
+                [
+                    {
+                        className: "forced-hidden",
+                        id: Dialogs.DIALOG_BTN_OK,
+                        text: Strings.OK
+                    }
+                ],
+                false
+            );
+
+            launchLinuxUpdater((type, text) => {
+                appendLogLine(text);
+            })
+                .then(resolve)
+                .catch(failUpdateDialogAndExit);
+        });
+    }
+
+    AppInit.appReady(async function () {
+        if(!window.__ELECTRON__ || Phoenix.isTestWindow) {
+            return;
+        }
+        // Electron updates only supported on Linux currently
+        if (brackets.platform !== "linux") {
+            console.error("App updates not yet implemented on this platform in Electron builds!");
+            return;
+        }
+        // Check if another window already scheduled an update (multi-window state persistence)
+        // This ensures the quit handler is registered in this window too
+        try {
+            const isUpdateScheduled = await window.electronAPI.getUpdateScheduled();
+            if (isUpdateScheduled) {
+                updateScheduled = true;
+                updatePendingRestart = true;
+                // Create task in success state (update ready, waiting for restart)
+                updateTask = TaskManager.addNewTask(Strings.UPDATE_DONE, Strings.UPDATE_RESTART_INSTALL,
+                    ``, {
+                        noSpinnerNotification: true,
+                        onSelect: function () {
+                            Dialogs.showInfoDialog(Strings.UPDATE_READY_RESTART_TITLE,
+                                Strings.UPDATE_READY_RESTART_INSTALL_MESSAGE);
+                        }
+                    });
+                updateTask.setSucceded();
+                Phoenix.app.registerQuitTimeAppUpdateHandler(quitTimeAppUpdateHandler);
+                console.log("Update was scheduled in another window, registering quit handler");
+            }
+        } catch (e) {
+            console.error("Error checking shared state for update state:", e);
+        }
+        $("#update-notification").click(()=>{
+            checkForUpdates();
+        });
+        CommandManager.register(Strings.CMD_CHECK_FOR_UPDATE, Commands.HELP_CHECK_UPDATES, ()=>{
+            checkForUpdates();
+        });
+        CommandManager.register(Strings.CMD_AUTO_UPDATE, Commands.HELP_AUTO_UPDATE, ()=>{
+            PreferencesManager.set(PREFS_AUTO_UPDATE, !PreferencesManager.get(PREFS_AUTO_UPDATE));
+        });
+        const helpMenu = Menus.getMenu(Menus.AppMenuBar.HELP_MENU);
+        helpMenu.addMenuItem(Commands.HELP_CHECK_UPDATES, "", Menus.AFTER, Commands.HELP_GET_INVOLVED);
+        PreferencesManager.definePreference(PREFS_AUTO_UPDATE, "boolean", true, {
+            description: Strings.DESCRIPTION_AUTO_UPDATE
+        });
+        showOrHideUpdateIcon();
+        const lastUpdateDetails = PreferencesManager.getViewState(KEY_LAST_UPDATE_DESCRIPTION);
+        if(lastUpdateDetails && (lastUpdateDetails.updateVersion === Phoenix.metadata.apiVersion)) {
+            let markdownHtml = marked.parse(lastUpdateDetails.releaseNotesMarkdown || "");
+            Dialogs.showInfoDialog(Strings.UPDATE_WHATS_NEW, markdownHtml);
+            PreferencesManager.setViewState(KEY_LAST_UPDATE_DESCRIPTION, null);
+            PreferencesManager.setViewState(KEY_UPDATE_AVAILABLE, false);
+            $("#update-notification").addClass("forced-hidden");
+        }
+        // check for updates at boot
+        let lastUpdateCheckTime = PreferencesManager.getViewState(KEY_LAST_UPDATE_CHECK_TIME);
+        const currentTime = Date.now();
+        const oneDayInMilliseconds = 24 * 60 * 60 * 1000;
+        if(lastUpdateCheckTime && ((currentTime - lastUpdateCheckTime) < oneDayInMilliseconds)){
+            console.log("Skipping update check: last update check was within one day");
+            return;
+        }
+        PreferencesManager.setViewState(KEY_LAST_UPDATE_CHECK_TIME, currentTime);
+        checkForUpdates(true);
+    });
+});
diff --git a/src/htmlContent/themes-settings.html b/src/htmlContent/themes-settings.html
index 997e32b51a..e8aa471ffe 100644
--- a/src/htmlContent/themes-settings.html
+++ b/src/htmlContent/themes-settings.html
@@ -56,6 +56,21 @@ 

{{Strings.THEMES_SETTINGS}}

{{settings.editorLineHeight}} + +
+ +
+ + {{settings.scrollSensitivity}} +
+