From 175dbf57e18347478a0594e7082d17d2bf9836a0 Mon Sep 17 00:00:00 2001 From: Andrew Stein Date: Sat, 18 May 2024 20:49:51 -0400 Subject: [PATCH 01/10] Port to `pnpm` Signed-off-by: Andrew Stein --- .../PULL_REQUEST_TEMPLATE/Documentation.md | 8 +- .github/workflows/build.yml | 318 +++++------------- .husky/pre-push | 2 +- cpp/perspective/build.js | 2 +- docs/package.json | 12 +- examples/blocks/index.mjs | 10 +- examples/blocks/package.json | 12 +- examples/blocks/src/olympics/build.mjs | 11 +- examples/esbuild-example/package.json | 14 +- examples/esbuild-remote/package.json | 12 +- examples/git-history/package.json | 8 +- examples/promo/package.json | 12 +- examples/python-aiohttp/package.json | 12 +- examples/python-starlette/package.json | 12 +- .../python-tornado-streaming/package.json | 12 +- examples/python-tornado/package.json | 12 +- examples/react-example/package.json | 18 +- examples/webpack-cross-origin/package.json | 10 +- examples/webpack-example/package.json | 13 +- .../workspace-editing-python/package.json | 14 +- examples/workspace/package.json | 12 +- package.json | 9 +- packages/perspective-cli/package.json | 12 +- packages/perspective-jupyterlab/package.json | 14 +- packages/perspective-viewer-d3fc/package.json | 8 +- .../perspective-viewer-datagrid/package.json | 8 +- .../package.json | 7 +- .../perspective-webpack-plugin/package.json | 4 +- packages/perspective-workspace/package.json | 8 +- packages/perspective/package.json | 4 +- pnpm-workspace.yaml | 19 ++ python/perspective/package.json | 10 +- .../perspective/perspective/widget/widget.py | 2 +- rust/perspective-viewer/package.json | 8 +- tools/perspective-bench/package.json | 6 +- tools/perspective-scripts/bench.mjs | 5 +- tools/perspective-scripts/build.mjs | 1 + tools/perspective-scripts/sh_perspective.mjs | 85 +---- tools/perspective-test/src/js/utils.ts | 2 +- 39 files changed, 277 insertions(+), 471 deletions(-) create mode 100644 pnpm-workspace.yaml diff --git a/.github/PULL_REQUEST_TEMPLATE/Documentation.md b/.github/PULL_REQUEST_TEMPLATE/Documentation.md index 901684c595..9574b2df55 100644 --- a/.github/PULL_REQUEST_TEMPLATE/Documentation.md +++ b/.github/PULL_REQUEST_TEMPLATE/Documentation.md @@ -12,18 +12,18 @@ about: Improves documentation for the API or on the docs website. - [ ] I have linted, spell-checked, and grammar-checked my documentation additions. -- [ ] I have run `yarn docs` to regenerate the API documentation from source. -- [ ] Within `/docs`, I have run `yarn start` to regenerate the Docusaurus site, and I have validated the changes +- [ ] I have run `pnpm run docs` to regenerate the API documentation from source. +- [ ] Within `/docs`, I have run `pnpm run start` to regenerate the Docusaurus site, and I have validated the changes - [ ] My additions are styled and structured correctly on the Docusaurus site ## Checklist diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 008b23b7f3..c5ae1523ae 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -217,25 +217,13 @@ jobs: ########## # Caches # ########## - ################ - # Yarn Cache - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT - shell: bash - if: ${{ needs.initialize.outputs.SKIP_CACHE == 'false' }} - - name: Setup yarn cache - uses: actions/cache@v3 - id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) + ################ + # PNPM Cache + - name: Install pnpm + uses: pnpm/action-setup@v3 with: - path: | - ~/.cache/ms-playwright - ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - if: ${{ needs.initialize.outputs.SKIP_CACHE == 'false' }} + version: 9 - name: Setup emsdk cache uses: actions/cache@v3 @@ -293,8 +281,8 @@ jobs: uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - cache: "yarn" - cache-dependency-path: yarn.lock + cache: "pnpm" + cache-dependency-path: pnpm-lock.yaml - name: Install latest nightly rust uses: dtolnay/rust-toolchain@nightly @@ -314,16 +302,13 @@ jobs: directory: "./.llvm" # JS - - name: Install yarn - run: npm install -g yarn - - name: Install js dependencies - run: yarn + run: pnpm install ################ # Python - name: Install python dependencies - run: yarn _requires_python + run: pnpm run _requires_python ################ # Linux @@ -342,15 +327,15 @@ jobs: ############### - name: WebAssembly Build - run: yarn build_js --ci + run: pnpm run build_js --ci env: PSP_USE_CCACHE: 1 # - name: Docs Build - # run: yarn docs + # run: pnpm run docs - name: Lint - run: yarn lint + run: pnpm run lint env: PSP_PROJECT: "js" @@ -471,25 +456,13 @@ jobs: ########## # Caches # ########## - ################ - # Yarn Cache - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT - shell: bash - if: ${{ needs.initialize.outputs.SKIP_CACHE == 'false' }} - - name: Setup yarn cache - uses: actions/cache@v3 - id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) + ################ + # PNPM Cache + - name: Install pnpm + uses: pnpm/action-setup@v3 with: - path: | - ~/.cache/ms-playwright - ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - if: ${{ needs.initialize.outputs.SKIP_CACHE == 'false' }} + version: 9 ################ # Pip Cache @@ -515,26 +488,23 @@ jobs: uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - cache: "yarn" - cache-dependency-path: yarn.lock + cache: "pnpm" + cache-dependency-path: pnpm-lock.yaml ################ # Dependencies # ################ ################ # JS - - name: Install yarn - run: npm install -g yarn - - name: Install js dependencies - run: yarn + run: pnpm install env: PSP_SKIP_EMSDK_INSTALL: 1 ################ # Python - name: Install python dependencies - run: yarn _requires_python + run: pnpm run _requires_python #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~# #~~~~~~~~~ Build Pipelines ~~~~~~~~~# @@ -545,13 +515,13 @@ jobs: ######## # Linux - name: Python Lint (Linux) - run: yarn lint + run: pnpm run lint env: PYTHON_VERSION: ${{ matrix.python-version }} PSP_PROJECT: python - name: JavaScript Lint - run: yarn lint + run: pnpm run lint env: PYTHON_VERSION: ${{ matrix.python-version }} PSP_PROJECT: js @@ -598,24 +568,10 @@ jobs: # Caches # ########## ################ - # Yarn Cache - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT - shell: bash - if: ${{ needs.initialize.outputs.SKIP_CACHE == 'false' }} - - - name: Setup yarn cache - uses: actions/cache@v3 - id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) + - name: Install pnpm + uses: pnpm/action-setup@v3 with: - path: | - ${{ steps.yarn-cache-dir-path.outputs.dir }} - ~/.cache/ms-playwright - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - if: ${{ needs.initialize.outputs.SKIP_CACHE == 'false' }} + version: 9 ################ # Pip Cache @@ -655,26 +611,23 @@ jobs: uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - cache: "yarn" - cache-dependency-path: yarn.lock + cache: "pnpm" + cache-dependency-path: pnpm-lock.yaml ################ # Dependencies # ################ ################ # JS - - name: Install yarn - run: npm install -g yarn - - name: Install js dependencies - run: yarn + run: pnpm install env: PSP_SKIP_EMSDK_INSTALL: 1 ################ # Python - name: Install python dependencies - run: yarn _requires_python + run: pnpm run _requires_python ################ # Rust @@ -727,7 +680,7 @@ jobs: # WebAssembly # ############### - name: WebAssembly Test - run: yarn test_js --ci + run: pnpm run test_js --ci ###################### # Fancy HTML reports # @@ -768,42 +721,25 @@ jobs: # Caches # ########## ################ - # Yarn Cache - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT - shell: bash - if: ${{ needs.initialize.outputs.SKIP_CACHE == 'false' }} - - - name: Setup yarn cache - uses: actions/cache@v3 - id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) + - name: Install pnpm + uses: pnpm/action-setup@v3 with: - path: | - ${{ steps.yarn-cache-dir-path.outputs.dir }} - ~/.cache/ms-playwright - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - if: ${{ needs.initialize.outputs.SKIP_CACHE == 'false' }} + version: 9 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - cache: "yarn" - cache-dependency-path: yarn.lock + cache: "pnpm" + cache-dependency-path: pnpm-lock.yaml ################ # Dependencies # ################ ################ # JS - - name: Install yarn - run: npm install -g yarn - - name: Install js dependencies - run: yarn + run: pnpm install env: PSP_SKIP_EMSDK_INSTALL: 1 @@ -846,7 +782,7 @@ jobs: path: rust/perspective-viewer/dist - name: Benchmarks - run: yarn bench + run: pnpm run bench - uses: actions/upload-artifact@v3 with: @@ -988,24 +924,10 @@ jobs: # Caches # ########## ################ - # Yarn Cache - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT - shell: bash - if: ${{ needs.initialize.outputs.SKIP_CACHE == 'false' }} - - - name: Setup yarn cache - uses: actions/cache@v3 - id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) + - name: Install pnpm + uses: pnpm/action-setup@v3 with: - path: | - ${{ steps.yarn-cache-dir-path.outputs.dir }} - ~/.cache/ms-playwright - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - if: ${{ needs.initialize.outputs.SKIP_CACHE == 'false' }} + version: 9 ################ # Pip Cache @@ -1059,8 +981,8 @@ jobs: uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - cache: "yarn" - cache-dependency-path: yarn.lock + cache: "pnpm" + cache-dependency-path: pnpm-lock.yaml if: ${{ runner.os != 'Linux' }} # skip on manylinux2014 ################ @@ -1075,19 +997,15 @@ jobs: # directory: "./.llvm" # JS - - name: Install yarn - run: npm install -g yarn - if: ${{ runner.os != 'Linux' }} - - name: Install js dependencies - run: yarn + run: pnpm install env: PSP_SKIP_EMSDK_INSTALL: 1 ################ # Python - name: Install python dependencies - run: yarn _requires_python + run: pnpm run _requires_python if: ${{ runner.os != 'Linux' }} # skip on manylinux2014 ################ @@ -1123,7 +1041,7 @@ jobs: ######## # Linux - name: Python Wheel Steps (Linux) - run: yarn _wheel_python --ci + run: pnpm run _wheel_python --ci env: PYTHON_VERSION: ${{ matrix.python-version }} MANYLINUX: ${{ matrix.container }} @@ -1134,8 +1052,8 @@ jobs: # Macos - name: Python Build Steps (Macos) run: | - yarn _wheel_python --ci --macos - yarn _wheel_python --ci --macos --arm + pnpm run _wheel_python --ci --macos + pnpm run _wheel_python --ci --macos --arm env: PYTHON_VERSION: ${{ matrix.python-version }} if: ${{ runner.os == 'macOS' }} @@ -1152,7 +1070,7 @@ jobs: ########## # Windows - name: Python Build Steps (Windows vc14.3) - run: yarn _wheel_python --ci + run: pnpm run _wheel_python --ci env: PSP_GENERATOR: Visual Studio 17 2022 PSP_VCPKG_PATH: C:/vcpkg/scripts/buildsystems/vcpkg.cmake @@ -1210,9 +1128,9 @@ jobs: - name: copy files run: | - npm install -g yarn - yarn --frozen-lockfile - yarn build_python --setup-only + npm install -g pnpm + pnpm install --frozen-lockfile + pnpm run build_python --setup-only - name: Linux init steps run: sudo node tools/perspective-scripts/install_tools.mjs @@ -1317,24 +1235,10 @@ jobs: # Caches # ########## ################ - # Yarn Cache - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT - shell: bash - if: ${{ needs.initialize.outputs.SKIP_CACHE == 'false' }} - - - name: Setup yarn cache - uses: actions/cache@v3 - id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) + - name: Install pnpm + uses: pnpm/action-setup@v3 with: - path: | - ${{ steps.yarn-cache-dir-path.outputs.dir }} - ~/.cache/ms-playwright - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - if: ${{ needs.initialize.outputs.SKIP_CACHE == 'false' }} + version: 9 ################ # Pip Cache @@ -1360,19 +1264,16 @@ jobs: uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - cache: "yarn" - cache-dependency-path: yarn.lock + cache: "pnpm" + cache-dependency-path: pnpm-lock.yaml ################ # Dependencies # ################ ################ # JS - - name: Install yarn - run: npm install -g yarn - - name: Install js dependencies - run: yarn --frozen-lockfile + run: pnpm install --frozen-lockfile env: PSP_SKIP_EMSDK_INSTALL: 1 @@ -1382,7 +1283,7 @@ jobs: ################ # Python - name: Install python dependencies - run: yarn _requires_python + run: pnpm run _requires_python # Download artifact - uses: actions/download-artifact@v3 @@ -1400,7 +1301,7 @@ jobs: run: jupyter labextension list - name: Run Jupyterlab tests - run: yarn test_js --jupyter + run: pnpm run test_js --jupyter env: PACKAGE: perspective-jupyterlab @@ -1488,24 +1389,10 @@ jobs: # Caches # ########## ################ - # Yarn Cache - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT - shell: bash - if: ${{ needs.initialize.outputs.SKIP_CACHE == 'false' }} - - - name: Setup yarn cache - uses: actions/cache@v3 - id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) + - name: Install pnpm + uses: pnpm/action-setup@v3 with: - path: | - ~/.cache/ms-playwright - ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - if: ${{ needs.initialize.outputs.SKIP_CACHE == 'false' }} + version: 9 ################ # Pip Cache @@ -1531,26 +1418,23 @@ jobs: uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - cache: "yarn" - cache-dependency-path: yarn.lock + cache: "pnpm" + cache-dependency-path: pnpm-lock.yaml ################ # Dependencies # ################ ################ # JS - - name: Install yarn - run: npm install -g yarn - - name: Install js dependencies - run: yarn + run: pnpm install env: PSP_SKIP_EMSDK_INSTALL: 1 ################ # Python - name: Install python dependencies - run: yarn _requires_python + run: pnpm run _requires_python #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~# #~~~~~~~~~ Build Pipelines ~~~~~~~~~# @@ -1584,7 +1468,7 @@ jobs: # Run tests - name: Python Test Steps - run: yarn test_python --debug + run: pnpm run test_python --debug ########################################################################################################################## ########################################################################################################################## @@ -1628,24 +1512,10 @@ jobs: # Caches # ########## ################ - # Yarn Cache - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT - shell: bash - if: ${{ needs.initialize.outputs.SKIP_CACHE == 'false' }} - - - name: Setup yarn cache - uses: actions/cache@v3 - id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) + - name: Install pnpm + uses: pnpm/action-setup@v3 with: - path: | - ~/.cache/ms-playwright - ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - if: ${{ needs.initialize.outputs.SKIP_CACHE == 'false' }} + version: 9 ################ # Pip Cache @@ -1671,19 +1541,16 @@ jobs: uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - cache: "yarn" - cache-dependency-path: yarn.lock + cache: "pnpm" + cache-dependency-path: pnpm-lock.yaml ################ # Dependencies # ################ ################ # JS - - name: Install yarn - run: npm install -g yarn - - name: Install js dependencies - run: yarn --frozen-lockfile + run: pnpm install --frozen-lockfile env: PSP_SKIP_EMSDK_INSTALL: 1 @@ -1695,7 +1562,7 @@ jobs: ################ # Python - name: Install python dependencies - run: yarn _requires_python + run: pnpm run _requires_python #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~# #~~~~~~~~~ Build Pipelines ~~~~~~~~~# @@ -1748,24 +1615,10 @@ jobs: # Caches # ########## ################ - # Yarn Cache - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT - shell: bash - if: ${{ needs.initialize.outputs.SKIP_CACHE == 'false' }} - - - name: Setup yarn cache - uses: actions/cache@v3 - id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) + - name: Install pnpm + uses: pnpm/action-setup@v3 with: - path: | - ~/.cache/ms-playwright - ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - if: ${{ needs.initialize.outputs.SKIP_CACHE == 'false' }} + version: 9 ################ # Pip Cache @@ -1791,26 +1644,23 @@ jobs: uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - cache: "yarn" - cache-dependency-path: yarn.lock + cache: "pnpm" + cache-dependency-path: pnpm-lock.yaml ################ # Dependencies # ################ ################ # JS - - name: Install yarn - run: npm install -g yarn - - name: Install js dependencies - run: yarn + run: pnpm install env: PSP_SKIP_EMSDK_INSTALL: 1 ################ # Python - name: Install python dependencies - run: yarn _requires_python + run: pnpm rin _requires_python #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~# #~~~~~~~~~ Build Pipelines ~~~~~~~~~# @@ -1829,7 +1679,7 @@ jobs: if: ${{ runner.os == 'Linux' }} - name: Run Benchmark - run: yarn bench + run: pnpm run bench shell: bash env: PSP_PROJECT: python @@ -2016,7 +1866,7 @@ jobs: - name: NPM pack run: | - npm pack \ + pnpm pack \ --workspace=@finos/perspective \ --workspace=@finos/perspective-viewer \ --workspace=@finos/perspective-viewer-datagrid \ diff --git a/.husky/pre-push b/.husky/pre-push index 84489edd6a..bf440db5fa 100755 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1,4 +1,4 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -yarn prepush +pnpm run prepush diff --git a/cpp/perspective/build.js b/cpp/perspective/build.js index 320ebdd488..b6d27df83b 100644 --- a/cpp/perspective/build.js +++ b/cpp/perspective/build.js @@ -21,7 +21,7 @@ const cwd = path.join(process.cwd(), "dist", env); delete process.env.NODE; function bootstrap(file) { - execSync(`cargo run -p perspective-bootstrap -- ${file}`, { + execSync(`cargo run --color always -p perspective-bootstrap -- ${file}`, { cwd: path.join(process.cwd(), "..", "..", "rust", "perspective-viewer"), stdio, }); diff --git a/docs/package.json b/docs/package.json index e474ad8f18..afaf1f64bd 100644 --- a/docs/package.json +++ b/docs/package.json @@ -16,13 +16,13 @@ "dependencies": { "@docusaurus/core": "2.2.0", "@docusaurus/preset-classic": "2.2.0", - "@finos/perspective": "^2.10.0", - "@finos/perspective-viewer": "^2.10.0", - "@finos/perspective-viewer-d3fc": "^2.10.0", - "@finos/perspective-viewer-datagrid": "^2.10.0", - "@finos/perspective-webpack-plugin": "^2.10.0", + "@finos/perspective": "workspace:^", + "@finos/perspective-viewer": "workspace:^", + "@finos/perspective-viewer-d3fc": "workspace:^", + "@finos/perspective-viewer-datagrid": "workspace:^", + "@finos/perspective-webpack-plugin": "workspace:^", "@mdx-js/react": "^1.6.22", - "blocks": "^2.10.0", + "blocks": "workspace:^", "clsx": "^1.1.1", "prism-react-renderer": "^1.3.3", "react": "^17.0.2", diff --git a/examples/blocks/index.mjs b/examples/blocks/index.mjs index 6c1d513a92..42828be440 100644 --- a/examples/blocks/index.mjs +++ b/examples/blocks/index.mjs @@ -12,9 +12,9 @@ import * as fs from "fs"; import { get_examples, LOCAL_EXAMPLES } from "./examples.js"; -import sh from "@finos/perspective-scripts/sh.mjs"; import * as url from "url"; import * as path from "node:path"; +import { execSync } from "child_process"; const version = JSON.parse(fs.readFileSync("./package.json")).version; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)).slice(0, -1); @@ -37,7 +37,7 @@ const replacements = { export async function dist_examples( outpath = `${__dirname}/../../docs/static/blocks` ) { - sh`mkdir -p ${outpath}`.runSync(); + execSync(`mkdir -p ${outpath}`, {stdio:"inherit"}); const readme = generate_readme(); let existing = fs.readFileSync(`${__dirname}/../../README.md`).toString(); existing = existing.replace( @@ -51,7 +51,7 @@ export async function dist_examples( if (fs.existsSync(`${__dirname}/src/${name}`)) { // Copy for (const filename of fs.readdirSync(`${__dirname}/src/${name}`)) { - sh`mkdir -p ${outpath}/${name}`.runSync(); + execSync(`mkdir -p ${outpath}/${name}`, {stdio:"inherit"}); if ( filename.endsWith(".mjs") || filename.endsWith(".js") || @@ -71,7 +71,7 @@ export async function dist_examples( filecontents ); } else if (filename !== ".git") { - sh`cp ${__dirname}/src/${name}/${filename} ${outpath}/${name}/${filename}`.runSync(); + execSync(`cp ${__dirname}/src/${name}/${filename} ${outpath}/${name}/${filename}`, {stdio:"inherit"}); } } @@ -79,7 +79,7 @@ export async function dist_examples( if (fs.existsSync(path.join(outpath, name, "build.mjs"))) { console.log("Building " + name); const script = `${outpath}/${name}/build.mjs`; - sh`node ${script}`.runSync(); + execSync(`node ${script}`, {stdio:"inherit"}); } } } diff --git a/examples/blocks/package.json b/examples/blocks/package.json index 663ac74583..b244140d94 100644 --- a/examples/blocks/package.json +++ b/examples/blocks/package.json @@ -10,12 +10,12 @@ "keywords": [], "license": "Apache-2.0", "dependencies": { - "@finos/perspective": "^2.10.0", - "@finos/perspective-viewer": "^2.10.0", - "@finos/perspective-viewer-d3fc": "^2.10.0", - "@finos/perspective-viewer-datagrid": "^2.10.0", - "@finos/perspective-viewer-openlayers": "^2.10.0", - "@finos/perspective-workspace": "^2.10.0", + "@finos/perspective": "workspace:^", + "@finos/perspective-viewer": "workspace:^", + "@finos/perspective-viewer-d3fc": "workspace:^", + "@finos/perspective-viewer-datagrid": "workspace:^", + "@finos/perspective-viewer-openlayers": "workspace:^", + "@finos/perspective-workspace": "workspace:^", "superstore-arrow": "3.0.0" }, "devDependencies": { diff --git a/examples/blocks/src/olympics/build.mjs b/examples/blocks/src/olympics/build.mjs index d6f1adc79c..f9452200d8 100644 --- a/examples/blocks/src/olympics/build.mjs +++ b/examples/blocks/src/olympics/build.mjs @@ -10,9 +10,7 @@ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ -import sh from "@finos/perspective-scripts/sh.mjs"; -// import * as url from "url"; - +import { execSync } from "child_process"; import perspective from "@finos/perspective"; import { Uint8ArrayReader, @@ -34,9 +32,10 @@ async function main() { return; } - sh`kaggle datasets download -d heesoo37/120-years-of-olympic-history-athletes-and-results` - .cwd(__dirname) - .runSync(); + execSync( + `cd ${__dirname} && kaggle datasets download -d heesoo37/120-years-of-olympic-history-athletes-and-results`, + { stdio: "inherit" } + ); const zip = fs.readFileSync( `${__dirname}/120-years-of-olympic-history-athletes-and-results.zip` diff --git a/examples/esbuild-example/package.json b/examples/esbuild-example/package.json index cbc5bf3f43..de12f6aa5e 100644 --- a/examples/esbuild-example/package.json +++ b/examples/esbuild-example/package.json @@ -5,20 +5,20 @@ "description": "An esbuild example app built using `@finos/perspective-viewer`.", "scripts": { "build": "node build.js", - "start": "yarn build && http-server dist" + "start": "pnpm run build && http-server dist" }, "keywords": [], "license": "Apache-2.0", "dependencies": { - "@finos/perspective": "^2.10.0", - "@finos/perspective-viewer": "^2.10.0", - "@finos/perspective-viewer-d3fc": "^2.10.0", - "@finos/perspective-viewer-datagrid": "^2.10.0", - "@finos/perspective-viewer-openlayers": "^2.10.0", + "@finos/perspective": "workspace:^", + "@finos/perspective-viewer": "workspace:^", + "@finos/perspective-viewer-d3fc": "workspace:^", + "@finos/perspective-viewer-datagrid": "workspace:^", + "@finos/perspective-viewer-openlayers": "workspace:^", "superstore-arrow": "^3.0.0" }, "devDependencies": { - "@finos/perspective-esbuild-plugin": "^2.10.0", + "@finos/perspective-esbuild-plugin": "workspace:^", "esbuild": "^0.14.54", "http-server": "^14.1.1" } diff --git a/examples/esbuild-remote/package.json b/examples/esbuild-remote/package.json index 9a3338e535..4a18e4637b 100644 --- a/examples/esbuild-remote/package.json +++ b/examples/esbuild-remote/package.json @@ -9,15 +9,15 @@ "keywords": [], "license": "Apache-2.0", "dependencies": { - "@finos/perspective": "^2.10.0", - "@finos/perspective-viewer": "^2.10.0", - "@finos/perspective-viewer-d3fc": "^2.10.0", - "@finos/perspective-viewer-datagrid": "^2.10.0", - "express": "^4.17.1", + "@finos/perspective": "workspace:^", + "@finos/perspective-viewer": "workspace:^", + "@finos/perspective-viewer-d3fc": "workspace:^", + "@finos/perspective-viewer-datagrid": "workspace:^", + "express": "4.18.2", "express-ws": "^5.0.2" }, "devDependencies": { - "@finos/perspective-esbuild-plugin": "^2.10.0", + "@finos/perspective-esbuild-plugin": "workspace:^", "esbuild": "^0.14.54" } } diff --git a/examples/git-history/package.json b/examples/git-history/package.json index 46313ebaa9..b93b7cc751 100644 --- a/examples/git-history/package.json +++ b/examples/git-history/package.json @@ -9,9 +9,9 @@ "keywords": [], "license": "Apache-2.0", "dependencies": { - "@finos/perspective": "^2.10.0", - "@finos/perspective-viewer": "^2.10.0", - "@finos/perspective-viewer-d3fc": "^2.10.0", - "@finos/perspective-viewer-datagrid": "^2.10.0" + "@finos/perspective": "workspace:^", + "@finos/perspective-viewer": "workspace:^", + "@finos/perspective-viewer-d3fc": "workspace:^", + "@finos/perspective-viewer-datagrid": "workspace:^" } } diff --git a/examples/promo/package.json b/examples/promo/package.json index 66afb1577a..b5694585ca 100644 --- a/examples/promo/package.json +++ b/examples/promo/package.json @@ -13,14 +13,14 @@ "keywords": [], "license": "Apache-2.0", "dependencies": { - "@finos/perspective": "^2.10.0", - "@finos/perspective-viewer": "^2.10.0", - "@finos/perspective-viewer-d3fc": "^2.10.0", - "@finos/perspective-viewer-datagrid": "^2.10.0", - "@finos/perspective-workspace": "^2.10.0" + "@finos/perspective": "workspace:^", + "@finos/perspective-viewer": "workspace:^", + "@finos/perspective-viewer-d3fc": "workspace:^", + "@finos/perspective-viewer-datagrid": "workspace:^", + "@finos/perspective-workspace": "workspace:^" }, "devDependencies": { - "@finos/perspective-webpack-plugin": "^2.10.0", + "@finos/perspective-webpack-plugin": "workspace:^", "http-server": "^14.1.1", "npm-run-all": "^4.1.3", "rimraf": "^2.5.2" diff --git a/examples/python-aiohttp/package.json b/examples/python-aiohttp/package.json index 2170d4a63f..d75adb2cb2 100644 --- a/examples/python-aiohttp/package.json +++ b/examples/python-aiohttp/package.json @@ -9,15 +9,15 @@ "keywords": [], "license": "Apache-2.0", "dependencies": { - "@finos/perspective": "^2.10.0", - "@finos/perspective-viewer": "^2.10.0", - "@finos/perspective-viewer-d3fc": "^2.10.0", - "@finos/perspective-viewer-datagrid": "^2.10.0", - "@finos/perspective-workspace": "^2.10.0", + "@finos/perspective": "workspace:^", + "@finos/perspective-viewer": "workspace:^", + "@finos/perspective-viewer-d3fc": "workspace:^", + "@finos/perspective-viewer-datagrid": "workspace:^", + "@finos/perspective-workspace": "workspace:^", "superstore-arrow": "^3.0.0" }, "devDependencies": { - "@finos/perspective-webpack-plugin": "^2.10.0", + "@finos/perspective-webpack-plugin": "workspace:^", "npm-run-all": "^4.1.3", "rimraf": "^2.5.2" } diff --git a/examples/python-starlette/package.json b/examples/python-starlette/package.json index 7ac8f413ed..b81572f117 100644 --- a/examples/python-starlette/package.json +++ b/examples/python-starlette/package.json @@ -9,15 +9,15 @@ "keywords": [], "license": "Apache-2.0", "dependencies": { - "@finos/perspective": "^2.10.0", - "@finos/perspective-viewer": "^2.10.0", - "@finos/perspective-viewer-d3fc": "^2.10.0", - "@finos/perspective-viewer-datagrid": "^2.10.0", - "@finos/perspective-workspace": "^2.10.0", + "@finos/perspective": "workspace:^", + "@finos/perspective-viewer": "workspace:^", + "@finos/perspective-viewer-d3fc": "workspace:^", + "@finos/perspective-viewer-datagrid": "workspace:^", + "@finos/perspective-workspace": "workspace:^", "superstore-arrow": "^3.0.0" }, "devDependencies": { - "@finos/perspective-webpack-plugin": "^2.10.0", + "@finos/perspective-webpack-plugin": "workspace:^", "npm-run-all": "^4.1.3", "rimraf": "^2.5.2" } diff --git a/examples/python-tornado-streaming/package.json b/examples/python-tornado-streaming/package.json index 6fe8388dd3..7c7036b7be 100644 --- a/examples/python-tornado-streaming/package.json +++ b/examples/python-tornado-streaming/package.json @@ -9,15 +9,15 @@ "keywords": [], "license": "Apache-2.0", "dependencies": { - "@finos/perspective": "^2.10.0", - "@finos/perspective-viewer": "^2.10.0", - "@finos/perspective-viewer-d3fc": "^2.10.0", - "@finos/perspective-viewer-datagrid": "^2.10.0", - "@finos/perspective-workspace": "^2.10.0", + "@finos/perspective": "workspace:^", + "@finos/perspective-viewer": "workspace:^", + "@finos/perspective-viewer-d3fc": "workspace:^", + "@finos/perspective-viewer-datagrid": "workspace:^", + "@finos/perspective-workspace": "workspace:^", "superstore-arrow": "^3.0.0" }, "devDependencies": { - "@finos/perspective-webpack-plugin": "^2.10.0", + "@finos/perspective-webpack-plugin": "workspace:^", "npm-run-all": "^4.1.3", "rimraf": "^2.5.2" } diff --git a/examples/python-tornado/package.json b/examples/python-tornado/package.json index 6b6fa0b604..76611846ed 100644 --- a/examples/python-tornado/package.json +++ b/examples/python-tornado/package.json @@ -9,15 +9,15 @@ "keywords": [], "license": "Apache-2.0", "dependencies": { - "@finos/perspective": "^2.10.0", - "@finos/perspective-viewer": "^2.10.0", - "@finos/perspective-viewer-d3fc": "^2.10.0", - "@finos/perspective-viewer-datagrid": "^2.10.0", - "@finos/perspective-workspace": "^2.10.0", + "@finos/perspective": "workspace:^", + "@finos/perspective-viewer": "workspace:^", + "@finos/perspective-viewer-d3fc": "workspace:^", + "@finos/perspective-viewer-datagrid": "workspace:^", + "@finos/perspective-workspace": "workspace:^", "superstore-arrow": "^3.0.0" }, "devDependencies": { - "@finos/perspective-webpack-plugin": "^2.10.0", + "@finos/perspective-webpack-plugin": "workspace:^", "npm-run-all": "^4.1.3", "rimraf": "^2.5.2" } diff --git a/examples/react-example/package.json b/examples/react-example/package.json index 9714a28aee..3b2d997f6f 100644 --- a/examples/react-example/package.json +++ b/examples/react-example/package.json @@ -10,17 +10,17 @@ "keywords": [], "license": "Apache-2.0", "dependencies": { - "@finos/perspective": "^2.10.0", - "@finos/perspective-viewer": "^2.10.0", - "@finos/perspective-viewer-d3fc": "^2.10.0", - "@finos/perspective-viewer-datagrid": "^2.10.0", - "react": "^16.14.0", - "react-dom": "^16.9.17" + "@finos/perspective": "workspace:^", + "@finos/perspective-viewer": "workspace:^", + "@finos/perspective-viewer-d3fc": "workspace:^", + "@finos/perspective-viewer-datagrid": "workspace:^", + "react": "^17.0.2", + "react-dom": "^17.0.2" }, "devDependencies": { - "@finos/perspective-webpack-plugin": "^2.10.0", - "@types/react": "^16.14.0", - "@types/react-dom": "^16.9.17", + "@finos/perspective-webpack-plugin": "workspace:^", + "@types/react": "^17.0.2", + "@types/react-dom": "^17.0.2", "source-map-loader": "^0.2.4", "ts-loader": "^6.2.1" } diff --git a/examples/webpack-cross-origin/package.json b/examples/webpack-cross-origin/package.json index a4e23be542..b4f61d5167 100644 --- a/examples/webpack-cross-origin/package.json +++ b/examples/webpack-cross-origin/package.json @@ -12,13 +12,13 @@ "keywords": [], "license": "Apache-2.0", "dependencies": { - "@finos/perspective": "^2.10.0", - "@finos/perspective-viewer": "^2.10.0", - "@finos/perspective-viewer-d3fc": "^2.10.0", - "@finos/perspective-viewer-datagrid": "^2.10.0" + "@finos/perspective": "workspace:^", + "@finos/perspective-viewer": "workspace:^", + "@finos/perspective-viewer-d3fc": "workspace:^", + "@finos/perspective-viewer-datagrid": "workspace:^" }, "devDependencies": { - "@finos/perspective-webpack-plugin": "^2.10.0", + "@finos/perspective-webpack-plugin": "workspace:^", "css-loader": "^0.28.7", "html-webpack-plugin": "^5.1.0", "http-server": "^14.1.1", diff --git a/examples/webpack-example/package.json b/examples/webpack-example/package.json index 8d86e86eef..ac4ca4cd3f 100644 --- a/examples/webpack-example/package.json +++ b/examples/webpack-example/package.json @@ -4,19 +4,20 @@ "version": "2.10.0", "description": "An example app built using `@finos/perspective-viewer`.", "scripts": { - "build": "webpack", + "webpack_build": "webpack", "start": "webpack serve" }, "keywords": [], "license": "Apache-2.0", "dependencies": { - "@finos/perspective": "^2.10.0", - "@finos/perspective-viewer": "^2.10.0", - "@finos/perspective-viewer-d3fc": "^2.10.0", - "@finos/perspective-viewer-datagrid": "^2.10.0" + "@finos/perspective": "workspace:^", + "@finos/perspective-viewer": "workspace:^", + "@finos/perspective-viewer-d3fc": "workspace:^", + "@finos/perspective-viewer-datagrid": "workspace:^" }, "devDependencies": { - "@finos/perspective-webpack-plugin": "^2.10.0", + "@finos/perspective-webpack-plugin": "workspace:^", + "arraybuffer-loader": "^1.0.8", "css-loader": "^0.28.7", "html-webpack-plugin": "^5.1.0", "style-loader": "^0.18.2", diff --git a/examples/workspace-editing-python/package.json b/examples/workspace-editing-python/package.json index 37f41790df..60ab30eb7b 100644 --- a/examples/workspace-editing-python/package.json +++ b/examples/workspace-editing-python/package.json @@ -4,7 +4,7 @@ "version": "2.10.0", "description": "An example app demonstrating client/server editing, built using `@finos/perspective-workspace` and `perspective-python`.", "scripts": { - "start": "yarn webpack && yarn start:server", + "start": "pnpm run webpack && pnpm run start:server", "start:client": "webpack-dev-server --open", "start:server": "PYTHONPATH=../../python/perspective python3 src/server.py", "webpack": "webpack --color" @@ -12,14 +12,14 @@ "keywords": [], "license": "Apache-2.0", "dependencies": { - "@finos/perspective": "^2.10.0", - "@finos/perspective-viewer": "^2.10.0", - "@finos/perspective-viewer-d3fc": "^2.10.0", - "@finos/perspective-viewer-datagrid": "^2.10.0", - "@finos/perspective-workspace": "^2.10.0" + "@finos/perspective": "workspace:^", + "@finos/perspective-viewer": "workspace:^", + "@finos/perspective-viewer-d3fc": "workspace:^", + "@finos/perspective-viewer-datagrid": "workspace:^", + "@finos/perspective-workspace": "workspace:^" }, "devDependencies": { - "@finos/perspective-webpack-plugin": "^2.10.0", + "@finos/perspective-webpack-plugin": "workspace:^", "http-server": "^14.1.1", "npm-run-all": "^4.1.3", "rimraf": "^2.5.2" diff --git a/examples/workspace/package.json b/examples/workspace/package.json index 5b8b218d7a..cbcf2ac811 100644 --- a/examples/workspace/package.json +++ b/examples/workspace/package.json @@ -10,14 +10,14 @@ "keywords": [], "license": "Apache-2.0", "dependencies": { - "@finos/perspective": "^2.10.0", - "@finos/perspective-viewer": "^2.10.0", - "@finos/perspective-viewer-d3fc": "^2.10.0", - "@finos/perspective-viewer-datagrid": "^2.10.0", - "@finos/perspective-workspace": "^2.10.0" + "@finos/perspective": "workspace:^", + "@finos/perspective-viewer": "workspace:^", + "@finos/perspective-viewer-d3fc": "workspace:^", + "@finos/perspective-viewer-datagrid": "workspace:^", + "@finos/perspective-workspace": "workspace:^" }, "devDependencies": { - "@finos/perspective-webpack-plugin": "^2.10.0", + "@finos/perspective-webpack-plugin": "workspace:^", "http-server": "^14.1.1", "npm-run-all": "^4.1.3", "rimraf": "^2.5.2" diff --git a/package.json b/package.json index c485d1a4ed..8a7afbf421 100644 --- a/package.json +++ b/package.json @@ -36,8 +36,12 @@ "python/perspective" ], "devDependencies": { + "@finos/perspective": "workspace:^", + "@finos/perspective-viewer": "workspace:^", + "@finos/perspective-viewer-datagrid": "workspace:^", + "@finos/perspective-viewer-d3fc": "workspace:^", "@fontsource/roboto-mono": "4.5.10", - "@playwright/test": "^1.30.0", + "@playwright/test": "^1.37.1", "@types/ws": "^7.2.2", "@zip.js/zip.js": "^2.7.27", "auto-changelog": "^2.4.0", @@ -68,6 +72,7 @@ "sinon": "^7.3.1", "stoppable": "^1.1.0", "style-loader": "^3.3.1", + "superstore-arrow": "3.0.0", "tar": "^6.1.13", "term-img": "^4.1.0", "timezone-mock": "^1.3.6", @@ -98,7 +103,7 @@ "setup": "node tools/perspective-scripts/setup.mjs", "docs": "node tools/perspective-scripts/docs.mjs", "test": "node tools/perspective-scripts/test.mjs", - "test:jupyter": "yarn workspace @finos/perspective-jupyterlab test:jupyter", + "test:jupyter": "pnpm run --recursive --filter @finos/perspective-jupyterlab test:jupyter", "test_js": "node tools/perspective-scripts/test_js.mjs", "test_python": "node tools/perspective-scripts/test_python.mjs", "clean": "node tools/perspective-scripts/clean.mjs", diff --git a/packages/perspective-cli/package.json b/packages/perspective-cli/package.json index 72e0e37855..0b46830416 100644 --- a/packages/perspective-cli/package.json +++ b/packages/perspective-cli/package.json @@ -24,12 +24,12 @@ "perspective": "perspective" }, "dependencies": { - "@finos/perspective": "^2.10.0", - "@finos/perspective-viewer": "^2.10.0", - "@finos/perspective-viewer-d3fc": "^2.10.0", - "@finos/perspective-viewer-datagrid": "^2.10.0", - "@finos/perspective-viewer-openlayers": "^2.10.0", - "@finos/perspective-workspace": "^2.10.0", + "@finos/perspective": "workspace:^", + "@finos/perspective-viewer": "workspace:^", + "@finos/perspective-viewer-d3fc": "workspace:^", + "@finos/perspective-viewer-datagrid": "workspace:^", + "@finos/perspective-viewer-openlayers": "workspace:^", + "@finos/perspective-workspace": "workspace:^", "commander": "^2.19.0", "puppeteer": "^13.1.3" } diff --git a/packages/perspective-jupyterlab/package.json b/packages/perspective-jupyterlab/package.json index c051a498b8..1a899bf051 100644 --- a/packages/perspective-jupyterlab/package.json +++ b/packages/perspective-jupyterlab/package.json @@ -34,19 +34,19 @@ "version": "yarn build" }, "dependencies": { - "@finos/perspective": "^2.10.0", - "@finos/perspective-viewer": "^2.10.0", - "@finos/perspective-viewer-d3fc": "^2.10.0", - "@finos/perspective-viewer-datagrid": "^2.10.0", - "@finos/perspective-viewer-openlayers": "^2.10.0", + "@finos/perspective": "workspace:^", + "@finos/perspective-viewer": "workspace:^", + "@finos/perspective-viewer-d3fc": "workspace:^", + "@finos/perspective-viewer-datagrid": "workspace:^", + "@finos/perspective-viewer-openlayers": "workspace:^", "@jupyter-widgets/base": "^2 || ^3 || ^4 || ^5 || ^6", "@jupyterlab/application": "^3.6.1", "@lumino/application": "^1.27.0", "@lumino/widgets": "^1.37.0" }, "devDependencies": { - "@finos/perspective-esbuild-plugin": "^2.10.0", - "@finos/perspective-test": "^2.10.0", + "@finos/perspective-esbuild-plugin": "workspace:^", + "@finos/perspective-test": "workspace:^", "@jupyterlab/builder": "^3.4.0", "@prospective.co/procss": "^0.1.15", "cpy": "^9.0.1" diff --git a/packages/perspective-viewer-d3fc/package.json b/packages/perspective-viewer-d3fc/package.json index 86896fe689..413cd6a49a 100644 --- a/packages/perspective-viewer-d3fc/package.json +++ b/packages/perspective-viewer-d3fc/package.json @@ -46,8 +46,8 @@ "author": "", "license": "Apache-2.0", "dependencies": { - "@finos/perspective": "^2.10.0", - "@finos/perspective-viewer": "^2.10.0", + "@finos/perspective": "workspace:^", + "@finos/perspective-viewer": "workspace:^", "chroma-js": "^1.3.4", "d3fc": "^15.2.4", "d3-selection": "^3.0.0", @@ -58,8 +58,8 @@ }, "devDependencies": { "@types/d3": "^7.0.0", - "@finos/perspective-esbuild-plugin": "^2.10.0", - "@finos/perspective-test": "^2.10.0", + "@finos/perspective-esbuild-plugin": "workspace:^", + "@finos/perspective-test": "workspace:^", "@prospective.co/procss": "^0.1.15" } } diff --git a/packages/perspective-viewer-datagrid/package.json b/packages/perspective-viewer-datagrid/package.json index 02d5e4ba2b..5552aad7f4 100644 --- a/packages/perspective-viewer-datagrid/package.json +++ b/packages/perspective-viewer-datagrid/package.json @@ -29,14 +29,14 @@ "author": "", "license": "Apache-2.0", "dependencies": { - "@finos/perspective": "^2.10.0", - "@finos/perspective-viewer": "^2.10.0", + "@finos/perspective": "workspace:^", + "@finos/perspective-viewer": "workspace:^", "chroma-js": "^1.3.4", "regular-table": "=0.6.4" }, "devDependencies": { "@prospective.co/procss": "^0.1.13", - "@finos/perspective-esbuild-plugin": "^2.10.0", - "@finos/perspective-test": "^2.10.0" + "@finos/perspective-esbuild-plugin": "workspace:^", + "@finos/perspective-test": "workspace:^" } } diff --git a/packages/perspective-viewer-openlayers/package.json b/packages/perspective-viewer-openlayers/package.json index d7dc456aef..5730e60f24 100644 --- a/packages/perspective-viewer-openlayers/package.json +++ b/packages/perspective-viewer-openlayers/package.json @@ -24,8 +24,8 @@ "clean:screenshots": "rimraf \"test/screenshots/**/*.@(failed|diff).png\"" }, "dependencies": { - "@finos/perspective": "^2.10.0", - "@finos/perspective-viewer": "^2.10.0", + "@finos/perspective": "workspace:^", + "@finos/perspective-viewer": "workspace:^", "d3": "^7.1.1", "d3-color": "^3.0.1", "gradient-parser": "1.0.2", @@ -33,6 +33,7 @@ "ol": "^5.3.2" }, "devDependencies": { - "@finos/perspective-esbuild-plugin": "^2.10.0" + "@finos/perspective-esbuild-plugin": "workspace:^", + "@prospective.co/procss": "^0.1.13" } } diff --git a/packages/perspective-webpack-plugin/package.json b/packages/perspective-webpack-plugin/package.json index 707c928f66..7a42b8a786 100644 --- a/packages/perspective-webpack-plugin/package.json +++ b/packages/perspective-webpack-plugin/package.json @@ -25,8 +25,8 @@ "worker-loader": "^3.0.7" }, "peerDependencies": { - "@finos/perspective": "^2.10.0", - "@finos/perspective-viewer": "^2.10.0", + "@finos/perspective": "workspace:^", + "@finos/perspective-viewer": "workspace:^", "webpack": "^5.60.0" } } diff --git a/packages/perspective-workspace/package.json b/packages/perspective-workspace/package.json index a6b14ff2f0..7a390a967d 100644 --- a/packages/perspective-workspace/package.json +++ b/packages/perspective-workspace/package.json @@ -31,18 +31,18 @@ "author": "", "license": "Apache-2.0", "dependencies": { - "@finos/perspective-viewer": "^2.10.0", + "@finos/perspective-viewer": "workspace:^", "@lumino/algorithm": "^1.9.1", "@lumino/commands": "^1.20.0", "@lumino/domutils": "^1.8.1", "@lumino/messaging": "^1.10.1", "@lumino/virtualdom": "^1.14.1", "@lumino/widgets": "^1.37.0", - "lodash": "^4.17.4" + "lodash": "^4.17.21" }, "devDependencies": { "@prospective.co/procss": "^0.1.15", - "@finos/perspective-esbuild-plugin": "^2.10.0", - "@finos/perspective-test": "^2.10.0" + "@finos/perspective-esbuild-plugin": "workspace:^", + "@finos/perspective-test": "workspace:^" } } diff --git a/packages/perspective/package.json b/packages/perspective/package.json index b7359e37da..bbaa2c4d6e 100644 --- a/packages/perspective/package.json +++ b/packages/perspective/package.json @@ -46,8 +46,8 @@ "ws": "^6.1.2" }, "devDependencies": { - "@finos/perspective-cpp": "^2.10.0", - "@finos/perspective-esbuild-plugin": "^2.10.0", + "@finos/perspective-cpp": "workspace:^", + "@finos/perspective-esbuild-plugin": "workspace:^", "cpy": "^9.0.1", "jsverify": "^0.8.4", "lodash": "^4.17.4", diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000000..1d805c6688 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,19 @@ +packages: + - "tools/perspective-test" + - "tools/perspective-scripts" + - "tools/perspective-bench" + - "cpp/perspective" + - "packages/perspective-esbuild-plugin" + - "packages/perspective-viewer-datagrid" + - "packages/perspective-viewer-d3fc" + - "packages/perspective-viewer-openlayers" + - "packages/perspective-workspace" + - "packages/perspective-jupyterlab" + - "packages/perspective-webpack-plugin" + - "packages/perspective-cli" + - "packages/perspective" + - "rust/perspective-viewer" + - "python/perspective" + - "examples/*" + - "docs" + - "python/perspective" diff --git a/python/perspective/package.json b/python/perspective/package.json index e67f6edfce..99392968e4 100644 --- a/python/perspective/package.json +++ b/python/perspective/package.json @@ -7,10 +7,10 @@ "docs": "python3 docs/generate.py" }, "devDependencies": { - "@finos/perspective": "^2.10.0", - "@finos/perspective-viewer": "^2.10.0", - "@finos/perspective-viewer-d3fc": "^2.10.0", - "@finos/perspective-viewer-datagrid": "^2.10.0", - "@finos/perspective-webpack-plugin": "^2.10.0" + "@finos/perspective": "workspace:^", + "@finos/perspective-viewer": "workspace:^", + "@finos/perspective-viewer-d3fc": "workspace:^", + "@finos/perspective-viewer-datagrid": "workspace:^", + "@finos/perspective-webpack-plugin": "workspace:^" } } diff --git a/python/perspective/perspective/widget/widget.py b/python/perspective/perspective/widget/widget.py index 9240918b49..e8f001266d 100644 --- a/python/perspective/perspective/widget/widget.py +++ b/python/perspective/perspective/widget/widget.py @@ -537,7 +537,7 @@ def _repr_mimebundle_(self, **kwargs): def psp_cdn(module, path=None): if path is None: path = f"cdn/{module}.js" - # perspective developer affordance: works with your local `yarn start blocks` + # perspective developer affordance: works with your local `pnpm run start blocks` # return f"http://localhost:8080/node_modules/@finos/{module}/dist/{path}" return f"https://cdn.jsdelivr.net/npm/@finos/{module}@{__version__}/dist/{path}" diff --git a/rust/perspective-viewer/package.json b/rust/perspective-viewer/package.json index 0bd02eed9c..c6ff622058 100644 --- a/rust/perspective-viewer/package.json +++ b/rust/perspective-viewer/package.json @@ -40,12 +40,12 @@ "access": "public" }, "dependencies": { - "@finos/perspective": "^2.10.0" + "@finos/perspective": "workspace:^" }, "devDependencies": { - "react": "^16.14.0", - "@finos/perspective-esbuild-plugin": "^2.10.0", - "@finos/perspective-test": "^2.10.0", + "@finos/perspective-esbuild-plugin": "workspace:^", + "@finos/perspective-test": "workspace:^", + "@types/react": "^17.0.2", "cpy": "^9.0.1" } } diff --git a/tools/perspective-bench/package.json b/tools/perspective-bench/package.json index e1c7ae91f7..3805ef2dbc 100644 --- a/tools/perspective-bench/package.json +++ b/tools/perspective-bench/package.json @@ -19,7 +19,11 @@ "author": "", "license": "Apache-2.0", "devDependencies": { - "microtime": "^3.0.0" + "microtime": "^3.0.0", + "superstore-arrow": "3.0.0", + "express": "4.18.2", + "@finos/perspective": "workspace:^", + "express-ws": "^5.0.2" }, "dependencies": { "perspective-2-7-0": "npm:@finos/perspective@2.7.0", diff --git a/tools/perspective-scripts/bench.mjs b/tools/perspective-scripts/bench.mjs index ed3bb57a5b..b7212f0a44 100644 --- a/tools/perspective-scripts/bench.mjs +++ b/tools/perspective-scripts/bench.mjs @@ -14,12 +14,9 @@ import * as dotenv from "dotenv"; import sh from "./sh.mjs"; dotenv.config({ path: "./.perspectiverc" }); -const args = process.argv.slice(2); if (process.env.PSP_PROJECT === undefined || process.env.PSP_PROJECT === "js") { - sh`yarn && nice -n 0 npm run bench` - .cwd("tools/perspective-bench") - .runSync(); + sh`pnpm run --recursive --filter perspective-bench bench`.runSync(); } else { sh`PYTHONPATH=python/perspective nice -n 0 python3 python/perspective/bench/runtime/run_perspective_benchmark.py` // .env({ PYTHONPATH: "python/perspective" }) diff --git a/tools/perspective-scripts/build.mjs b/tools/perspective-scripts/build.mjs index a21ecacbab..06febbe2ab 100644 --- a/tools/perspective-scripts/build.mjs +++ b/tools/perspective-scripts/build.mjs @@ -12,6 +12,7 @@ import * as fs from "fs"; import * as dotenv from "dotenv"; +import { run_with_scope } from "./sh_perspective.mjs"; dotenv.config({ path: "./.perspectiverc" }); diff --git a/tools/perspective-scripts/sh_perspective.mjs b/tools/perspective-scripts/sh_perspective.mjs index f6d47c5354..73bba7be99 100644 --- a/tools/perspective-scripts/sh_perspective.mjs +++ b/tools/perspective-scripts/sh_perspective.mjs @@ -18,6 +18,7 @@ import rimraf from "rimraf"; import { createRequire } from "node:module"; import sh from "./sh.mjs"; import * as url from "url"; +import { execSync } from "child_process"; dotenv.config({ path: "./.perspectiverc" }); process.env.FORCE_COLOR = true; @@ -133,85 +134,13 @@ export const run_with_scope = async function run_recursive(strings, ...args) { let scope = process.env.PACKAGE && process.env.PACKAGE !== "" ? process.env.PACKAGE.split(",").map((x) => `@finos/${x}`) - : null; + : []; - const stdout = await sh`npm ls --depth 1 --json`.exec({ silent: true }); - const workspaces = JSON.parse(stdout.toString()); - const compiled = new Set( - Object.keys(workspaces.dependencies).filter( - (x) => !workspaces.dependencies[x].resolved?.startsWith("file:") - ) - ); - - let uncompiled = Object.keys(workspaces.dependencies).filter( - (x) => - workspaces.dependencies[x].resolved?.startsWith("file:") && - (scope === null || scope.indexOf(x) >= 0) - ); - - const is_valid = new Set(uncompiled); - - while (uncompiled.length > 0) { - const batch = []; - const new_uncompiled = []; - for (const pkgname of uncompiled) { - let deps_met = true; - const pkg = workspaces.dependencies[pkgname]; - if (pkg.dependencies) { - for (const dep of Object.keys(pkg.dependencies)) { - deps_met = - deps_met && (!is_valid.has(dep) || compiled.has(dep)); - if (!deps_met) { - // console.debug( - // `${pkgname} has unmet dependency ${dep} ${is_valid.has( - // dep - // )}` - // ); - - break; - } - } - - if (deps_met) { - batch.push(pkgname); - } else { - new_uncompiled.push(pkgname); - } - } else { - batch.push(pkgname); - } - } - - if (batch.length === 0) { - throw new Error( - `Failed to resolved dependencies for ${new_uncompiled.join( - "," - )}` - ); - } - - const cmd = strings[0].split(" ")[0]; - console.log(`-- Running ${cmd} for ${batch.join(",")}`); - for (const pkgname of batch) { - const pkg = JSON.parse( - fs.readFileSync(_require.resolve(pkgname + "/package.json")) - ); - - if (pkg.scripts?.[cmd]) { - sh` - ${sh(PACKAGE_MANAGER)} --workspace ${pkgname} run \ - ${Array.isArray(strings) ? sh(strings, ...args) : strings} - `.runSync(); - } - - compiled.add(pkgname); - } - - uncompiled = new_uncompiled; - } - - if (scope?.length === 0) { - } + const cmd = strings[0].split(" ")[0]; + const filters = scope.map((x) => `--filter ${x}`).join(" "); + execSync(`pnpm run --sequential --recursive ${filters} ${cmd}`, { + stdio: "inherit", + }); }; /** diff --git a/tools/perspective-test/src/js/utils.ts b/tools/perspective-test/src/js/utils.ts index f4bb3b45df..9862dfeeb7 100644 --- a/tools/perspective-test/src/js/utils.ts +++ b/tools/perspective-test/src/js/utils.ts @@ -141,7 +141,7 @@ export const getSvgContentString = (selector: string) => async (page: Page) => { /** * Compares the content of an HTML element to a snapshot. - * To generate new snapshots, run `yarn test --update-snapshots`. + * To generate new snapshots, run `pnpm run test --update-snapshots`. * This first runs the focused project(s) tests, which generates new * snapshots, and then updates the contents of results.tar.gz which * you can commit. From 3def09ad98cabd910011b3eea436d6d44a669d89 Mon Sep 17 00:00:00 2001 From: Andrew Stein Date: Fri, 10 May 2024 23:39:09 -0400 Subject: [PATCH 02/10] Update benchmarks and test suite Signed-off-by: Andrew Stein --- .github/workflows/build.yml | 52 ++- package.json | 4 + .../src/ts/axis/axisType.ts | 2 +- .../perspective-viewer-datagrid/package.json | 2 +- .../src/less/mitered-headers.less | 10 +- .../package.json | 2 +- .../test/html/superstore.html | 6 +- .../test/js/superstore.spec.js | 2 +- tools/perspective-bench/basic_suite.js | 286 +++++++++++++++ tools/perspective-bench/package.json | 5 +- tools/perspective-bench/remote_suite.js | 330 +++++++++++++++++ .../perspective-bench/src/html/benchmark.html | 49 --- tools/perspective-bench/src/html/index.html | 50 +++ tools/perspective-bench/src/js/bench.js | 96 ----- tools/perspective-bench/src/js/benchmark.js | 344 ++++++++++++++++++ tools/perspective-bench/src/js/worker.js | 296 --------------- tools/perspective-scripts/lint_headers.mjs | 2 + tools/perspective-test/load-viewer-csv.js | 7 +- .../load-viewer-superstore.js | 7 +- .../perspective-test/load-workspace-arrow.js | 7 +- tools/perspective-test/package.json | 1 - tools/perspective-test/playwright.config.ts | 4 +- .../perspective-test/src/html/basic-test.html | 12 +- .../src/html/superstore-test.html | 8 +- .../src/html/themed-test.html | 10 +- .../src/html/workspace-test.html | 16 +- 26 files changed, 1113 insertions(+), 497 deletions(-) create mode 100644 tools/perspective-bench/basic_suite.js create mode 100644 tools/perspective-bench/remote_suite.js delete mode 100644 tools/perspective-bench/src/html/benchmark.html create mode 100644 tools/perspective-bench/src/html/index.html delete mode 100644 tools/perspective-bench/src/js/bench.js create mode 100644 tools/perspective-bench/src/js/benchmark.js delete mode 100644 tools/perspective-bench/src/js/worker.js diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c5ae1523ae..99dff05d41 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1864,19 +1864,45 @@ jobs: with: name: perspective-python-benchmarks - - name: NPM pack - run: | - pnpm pack \ - --workspace=@finos/perspective \ - --workspace=@finos/perspective-viewer \ - --workspace=@finos/perspective-viewer-datagrid \ - --workspace=@finos/perspective-viewer-d3fc \ - --workspace=@finos/perspective-viewer-openlayers \ - --workspace=@finos/perspective-workspace \ - --workspace=@finos/perspective-cli \ - --workspace=@finos/perspective-webpack-plugin \ - --workspace=@finos/perspective-esbuild-plugin \ - --workspace=@finos/perspective-jupyterlab + - name: NPM pack perspective + run: pnpm pack --pack-destination=../.. + working-directory: ./packages/perspective + + - name: NPM pack perspective-viewer + run: pnpm pack --pack-destination=../.. + working-directory: ./rust/perspective-viewer + + - name: NPM pack perspective-viewer-datagrid + run: pnpm pack --pack-destination=../.. + working-directory: ./packages/perspective-viewer-datagrid + + - name: NPM pack perspective-viewer-d3fc + run: pnpm pack --pack-destination=../.. + working-directory: ./packages/perspective-viewer-d3fc + + - name: NPM pack perspective-viewer-openlayers + run: pnpm pack --pack-destination=../.. + working-directory: ./packages/perspective-viewer-openlayers + + - name: NPM pack perspective-workspace + run: pnpm pack --pack-destination=../.. + working-directory: ./packages/perspective-workspace + + - name: NPM pack perspective-cli + run: pnpm pack --pack-destination=../.. + working-directory: ./packages/perspective-cli + + - name: NPM pack perspective-webpack-plugin + run: pnpm pack --pack-destination=../.. + working-directory: ./packages/perspective-webpack-plugin + + - name: NPM pack perspective-esbuild-plugin + run: pnpm pack --pack-destination=../.. + working-directory: ./packages/perspective-esbuild-plugin + + - name: NPM pack perspective-jupyterlab + run: pnpm pack --pack-destination=../.. + working-directory: ./packages/perspective-jupyterlab - name: Debug run: ls -lah diff --git a/package.json b/package.json index 8a7afbf421..6e051786e6 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,10 @@ "@finos/perspective-viewer": "workspace:^", "@finos/perspective-viewer-datagrid": "workspace:^", "@finos/perspective-viewer-d3fc": "workspace:^", + "@finos/perspective-viewer-openlayers": "workspace:^", + "@finos/perspective-workspace": "workspace:^", + "@finos/perspective-jupyterlab": "workspace:^", + "@finos/perspective-test": "workspace:^", "@fontsource/roboto-mono": "4.5.10", "@playwright/test": "^1.37.1", "@types/ws": "^7.2.2", diff --git a/packages/perspective-viewer-d3fc/src/ts/axis/axisType.ts b/packages/perspective-viewer-d3fc/src/ts/axis/axisType.ts index 957bca9dad..6c24579622 100644 --- a/packages/perspective-viewer-d3fc/src/ts/axis/axisType.ts +++ b/packages/perspective-viewer-d3fc/src/ts/axis/axisType.ts @@ -19,7 +19,7 @@ export const AXIS_TYPES = { linear: "linear", } as const; -export type AxisTypeValues = typeof AXIS_TYPES[keyof typeof AXIS_TYPES]; +export type AxisTypeValues = (typeof AXIS_TYPES)[keyof typeof AXIS_TYPES]; export interface AxisType { (): AxisTypeValues; diff --git a/packages/perspective-viewer-datagrid/package.json b/packages/perspective-viewer-datagrid/package.json index 5552aad7f4..3f8a0e47f0 100644 --- a/packages/perspective-viewer-datagrid/package.json +++ b/packages/perspective-viewer-datagrid/package.json @@ -35,7 +35,7 @@ "regular-table": "=0.6.4" }, "devDependencies": { - "@prospective.co/procss": "^0.1.13", + "@prospective.co/procss": "^0.1.15", "@finos/perspective-esbuild-plugin": "workspace:^", "@finos/perspective-test": "workspace:^" } diff --git a/packages/perspective-viewer-datagrid/src/less/mitered-headers.less b/packages/perspective-viewer-datagrid/src/less/mitered-headers.less index 546b202f07..4eefb86ef8 100644 --- a/packages/perspective-viewer-datagrid/src/less/mitered-headers.less +++ b/packages/perspective-viewer-datagrid/src/less/mitered-headers.less @@ -31,10 +31,12 @@ 0px 10px 0 -9px var(--inactive--border-color, #8b868045); } -.psp-header-border.psp-header-group:not(.psp-is-top):not(.psp-header-group-corner) { - // right and bottom - box-shadow: 1px 0px var(--inactive--border-color, #8b868045), - 0px 10px 0 -9px var(--inactive--border-color, #8b868045); +.psp-header-border.psp-header-group { + &:not(.psp-is-top):not(.psp-header-group-corner) { + // right and bottom + box-shadow: 1px 0px var(--inactive--border-color, #8b868045), + 0px 10px 0 -9px var(--inactive--border-color, #8b868045); + } } @mixin disabled-menu-funky-box-shadow { diff --git a/packages/perspective-viewer-openlayers/package.json b/packages/perspective-viewer-openlayers/package.json index 5730e60f24..c48d7e6dd2 100644 --- a/packages/perspective-viewer-openlayers/package.json +++ b/packages/perspective-viewer-openlayers/package.json @@ -34,6 +34,6 @@ }, "devDependencies": { "@finos/perspective-esbuild-plugin": "workspace:^", - "@prospective.co/procss": "^0.1.13" + "@prospective.co/procss": "^0.1.15" } } diff --git a/packages/perspective-viewer-openlayers/test/html/superstore.html b/packages/perspective-viewer-openlayers/test/html/superstore.html index 5d2a4f687f..574e6f9580 100644 --- a/packages/perspective-viewer-openlayers/test/html/superstore.html +++ b/packages/perspective-viewer-openlayers/test/html/superstore.html @@ -10,9 +10,9 @@ - - - + + + - - - - - - - - diff --git a/tools/perspective-bench/src/html/index.html b/tools/perspective-bench/src/html/index.html new file mode 100644 index 0000000000..c8eec3efe4 --- /dev/null +++ b/tools/perspective-bench/src/html/index.html @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/perspective-bench/src/js/bench.js b/tools/perspective-bench/src/js/bench.js deleted file mode 100644 index f89c4ba179..0000000000 --- a/tools/perspective-bench/src/js/bench.js +++ /dev/null @@ -1,96 +0,0 @@ -// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃ -// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃ -// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃ -// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃ -// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫ -// ┃ Copyright (c) 2017, the Perspective Authors. ┃ -// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃ -// ┃ This file is part of the Perspective library, distributed under the terms ┃ -// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃ -// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -const fs = require("fs"); -const cp = require("child_process"); -const path = require("path"); - -/** - * Convert a list to arrow and write it to disk. `@finos/perspective` is - * imported in this scope to prevent interpreter-wide side effects of the - * library from impacting forked processes, based on an observation that some - * runs inline had anomalies across many observations that couldn't be explained - * by contemporary system load. - * @param {Array} obs_records an array of records to persist - */ -async function persist_to_arrow(obs_records) { - const psp = require("@finos/perspective"); - const table = await psp.table({ - version: "string", - time: "float", - version_idx: "integer", - benchmark: "string", - }); - - const view = await table.view(); - await table.update(obs_records); - const arrow = await view.to_arrow(); - if (!fs.existsSync(path.join(__dirname, "../../dist/"))) { - fs.mkdirSync(path.join(__dirname, "../../dist/")); - } - - fs.writeFileSync( - path.join(__dirname, "../../dist/benchmark-js.arrow"), - Buffer.from(arrow), - "binary" - ); - - fs.writeFileSync( - path.join(__dirname, "../../dist/benchmark.html"), - fs.readFileSync(path.join(__dirname, "../html/benchmark.html")), - "binary" - ); -} - -/** - * Run the benchmarks in a forked process and colelct the observations. - * @param {{version: string, i: number}} version the versions spec to send to - * the child process. - * @returns an array of observation records for this version. - */ -async function benchmark_version(version) { - let obs_records = []; - const worker = cp.fork("./src/js/worker.js"); - let cont; - worker.on("message", (details) => { - if (details.finished) { - cont(); - } else { - obs_records = obs_records.concat(details.obs_records); - } - }); - - worker.send(version); - await new Promise((r) => { - cont = r; - }); - - worker.kill(); - return obs_records; -} - -async function main() { - const pkg_path = path.join(__dirname, "../../package.json"); - const pkg_json = fs.readFileSync(pkg_path); - const pkg_deps = Object.keys(JSON.parse(pkg_json).dependencies); - const versions = ["@finos/perspective", ...pkg_deps]; - - let obs_records = []; - for (let i = 0; i < versions.length; i++) { - const batch = await benchmark_version({ path: versions[i], i }); - obs_records = obs_records.concat(batch); - } - - await persist_to_arrow(obs_records); -} - -main(); diff --git a/tools/perspective-bench/src/js/benchmark.js b/tools/perspective-bench/src/js/benchmark.js new file mode 100644 index 0000000000..183d0dd871 --- /dev/null +++ b/tools/perspective-bench/src/js/benchmark.js @@ -0,0 +1,344 @@ +// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃ +// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃ +// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃ +// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃ +// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫ +// ┃ Copyright (c) 2017, the Perspective Authors. ┃ +// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃ +// ┃ This file is part of the Perspective library, distributed under the terms ┃ +// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃ +// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + +const fs = require("node:fs"); +const cp = require("node:child_process"); +const process = require("node:process"); +const path = require("node:path"); +const microtime = require("microtime"); +const express = require("express"); +const expressWs = require("express-ws"); + +const MAX_ITERATIONS = 200; +const MIN_ITERATIONS = 5; +const WARM_UP_ITERATIONS = 10; + +/** + * Utility function to push/filter. + * @param {*} a + * @param {*} x + */ +function push_if(a, x) { + if (x !== undefined) { + a.push(x); + } +} + +/** + * Calculate avg fora key. + * @param {*} a + * @param {*} key + * @returns + */ +function avg(a, key) { + return a.map((x) => x[key]).reduce((x, y) => x + y, 0) / a.length / 1000; +} + +/** + * Calculate stddev for a key. + * @param {*} array + * @param {*} key + * @returns + */ +function stddev(array, key) { + const n = array.length; + const mean = array.reduce((a, b) => a + b[key] / 1000, 0) / n; + return Math.sqrt( + array + .map((x) => Math.pow(x[key] / 1000 - mean, 2)) + .reduce((a, b) => a + b) / n + ); +} + +/** + * Calculate outliers + * @param {*} someArray + * @returns + */ +function markOutliers(someArray) { + var values = someArray.concat(); + values.sort(function (a, b) { + return a.cpu_time - b.cpu_time; + }); + + var q1 = values[Math.floor(values.length / 4)].cpu_time; + var q3 = values[Math.ceil(values.length * (3 / 4))].cpu_time; + var iqr = q3 - q1; + var maxValue = q3 + iqr * 1.5; + var minValue = q1 - iqr * 1.5; + return someArray.map(function (x) { + x.outlier = !(x.cpu_time <= maxValue && x.cpu_time >= minValue); + return x; + }); +} + +/** + * Convert a list to arrow and write it to disk. `@finos/perspective` is + * imported in this scope to prevent interpreter-wide side effects of the + * library from impacting forked processes, based on an observation that some + * runs inline had anomalies across many observations that couldn't be explained + * by contemporary system load. + * @param {Array} obs_records an array of records to persist + */ +async function persist_to_arrow(benchmarks_table, out_path = "") { + const view = await benchmarks_table.view(); + const arrow = await view.to_arrow(); + fs.writeFileSync(out_path, Buffer.from(arrow), "binary"); +} + +/** + * Run a single benchmark case, reporting the runtime + * @param {*} param0 + * @returns An observation record with timing and metadata fields for a single + * iteration of this case. + */ +async function benchmark_case({ + args, + before, + test, + benchmark, + metadata, + after, + i, +}) { + const args2 = args.slice(); + push_if(args2, await before?.(...args2)); + global.gc(false); + await new Promise(setTimeout); + const start_time = microtime.now(); + const start_cpu = process.cpuUsage(); + const x = await test(...args2); + const end = process.cpuUsage(start_cpu); + const end_time = microtime.now() - start_time; + push_if(args2, x); + await after?.(...args2); + return { + cpu_time: end.user + end.system, + real_time: end_time, + user_time: end.user, + system_time: end.system, + benchmark, + iteration: i, + ...structuredClone(metadata), + }; +} + +/** + * Run the benchmarks in a forked process and colelct the observations. + * @param {{version: string, i: number}} version the versions spec to send to + * the child process. + * @returns an array of observation records for this version. + */ +async function benchmark_version(version, benchmarks_table) { + const suite_path = path.join(process.argv[1]); + let stats = []; + const worker = cp.fork(suite_path, { + execArgv: ["--expose-gc"], + env: { BENCH_FLAG: "1" }, + }); + + let cont; + worker.on("message", (details) => { + if (details.finished) { + cont(); + } else { + benchmarks_table.update(details.obs_records); + stats.push(details.stats); + } + }); + + worker.send({ + ...version, + stats, + }); + + await new Promise((r) => { + cont = r; + }); + + worker.kill(); + return { stats }; +} + +/** + * Register a benchmark for a test case. + * @param {*} param0 + */ +exports.benchmark = async function benchmark({ + name: benchmark, + before, + before_all, + test, + after, + after_all, + args = [], + metadata = {}, + warm_up_iterations = WARM_UP_ITERATIONS, + max_iterations = MAX_ITERATIONS, + min_iterations = MIN_ITERATIONS, + max_time = 3_000_000, +} = {}) { + let obs_records = []; + push_if(args, await before_all?.(...args)); + const start_time = microtime.now(); + for (let i = 0; i < warm_up_iterations; i++) { + await benchmark_case({ + args, + before, + test, + benchmark, + metadata, + after, + i, + }); + } + + let i; + for (i = 0; i < max_iterations; i++) { + if (microtime.now() - start_time > max_time && i >= min_iterations) { + break; + } + + obs_records.push( + await benchmark_case({ + args, + before, + test, + benchmark, + metadata, + after, + i, + }) + ); + } + + obs_records = markOutliers(obs_records); + const filtered = obs_records.filter((x) => !x.outlier); + const n_outliers = obs_records.length - filtered.length; + const filtered_avg_cpu = avg(filtered, "cpu_time"); + const filtered_avg_time = avg(filtered, "real_time").toFixed(3); + const stddev_cpu = stddev(filtered, "cpu_time").toFixed(3); + const stats = { + benchmark, + metadata, + filtered_avg_cpu: filtered_avg_cpu.toFixed(3), + stddev_percent: ((stddev_cpu / filtered_avg_cpu) * 100).toFixed(1), + stddev_cpu, + filtered_avg_time, + hr_time: avg(filtered, "hr_time"), + iterations: i, + non_outliers: i - n_outliers, + }; + + console.log( + ` - ${stats.filtered_avg_cpu}ms +/-${stats.stddev_percent}% (CPU), ${stats.filtered_avg_time}ms (Real) ${stats.non_outliers}/${stats.iterations} iterations - ${benchmark}` + ); + + await after_all?.(...args); + process.send({ obs_records, stats }); +}; + +function buffer_to_arraybuffer(buffer) { + return new Int8Array( + buffer.buffer.slice( + buffer.byteOffset, + buffer.byteOffset + buffer.length + ) + ); +} + +/** + * Host a Perspective server to view the live benchmark data as it accumulates. + * @param {*} param0 + * @returns + */ +function start_server({ cwd_static_file_handler, make_server }) { + const app = expressWs(express()).app; + app.ws("/subscribe", (ws) => { + const server = make_server((proto) => + ws.send(buffer_to_arraybuffer(proto)) + ); + + ws.on("message", (proto) => + server.handle_message(buffer_to_arraybuffer(proto)) + ); + }); + + app.use("/", (x, y) => + cwd_static_file_handler(x, y, ["src/html/", "../.."]) + ); + + const server = app.listen(8081, () => { + const port = server.address().port; + console.log(`Live benchmarks at http://localhost:${port}\n`); + }); + + return server; +} + +/** + * Register a suite of benchmarks to run against a set of packages with + * similar APIs. + * @param {*} versions + * @param {*} run_version_callback + */ +exports.suite = async function ( + versions, + out_path, + run_version_callback, + start_server_callback +) { + if (!!process.env.BENCH_FLAG) { + process.on("message", async function bench_all({ path, i }) { + await run_version_callback(path, i); + process.send({ finished: true }); + }); + } else { + const psp = await import("@finos/perspective"); + const benchmarks_table = await psp.default.table( + { + version: "string", + cpu_time: "float", + system_time: "float", + user_time: "float", + real_time: "float", + version_idx: "integer", + benchmark: "string", + outlier: "boolean", + }, + { name: "benchmarks" } + ); + + const app = start_server(psp); + + for (let i = 0; i < versions.length; i++) { + let s; + if (start_server_callback) { + s = await start_server_callback(versions[i]); + } + + await Promise.all([ + benchmark_version({ path: versions[i], i }, benchmarks_table), + // benchmark_version({ path: versions[i], i }, benchmarks_table), + // benchmark_version({ path: versions[i], i }, benchmarks_table), + // benchmark_version({ path: versions[i], i }, benchmarks_table), + ]); + + await persist_to_arrow(benchmarks_table, out_path); + if (s) { + await s.close(); + } + } + + await app.close(); + } +}; diff --git a/tools/perspective-bench/src/js/worker.js b/tools/perspective-bench/src/js/worker.js deleted file mode 100644 index e391eede98..0000000000 --- a/tools/perspective-bench/src/js/worker.js +++ /dev/null @@ -1,296 +0,0 @@ -// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃ -// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃ -// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃ -// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃ -// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫ -// ┃ Copyright (c) 2017, the Perspective Authors. ┃ -// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃ -// ┃ This file is part of the Perspective library, distributed under the terms ┃ -// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃ -// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -const fs = require("fs"); -const microtime = require("microtime"); - -let PERSPECTIVE, VERSION; -function load_version(path, i) { - const module = require(path); - let { version } = JSON.parse( - fs.readFileSync(require.resolve(`${path}/package.json`)) - ); - - PERSPECTIVE = module.default || module; - PERSPECTIVE.version = version.split(".").map((x) => parseInt(x)); - - if (i === 0) { - version = `${version} (master)`; - } - - console.log(`${version}`); - - VERSION = version; - VERSION_ID = i; -} - -const SUPERSTORE_ARROW = fs.readFileSync( - require.resolve("superstore-arrow/superstore.arrow") -).buffer; - -const SUPERSTORE_FEATHER = fs.readFileSync( - require.resolve("superstore-arrow/superstore.lz4.arrow") -).buffer; - -function new_table(perspective) { - const [M, m, _] = perspective.version; - if (M >= 2 && m >= 5) { - return SUPERSTORE_ARROW.slice(); - } else { - return SUPERSTORE_ARROW.slice(); - } -} - -const ITERATIONS = 10; -const WARM_UP_ITERATIONS = 1; - -Object.defineProperty(Array.prototype, "push_if", { - value: function (x) { - if (x !== undefined) { - this.push(x); - } - }, -}); - -Object.defineProperty(Array.prototype, "sum", { - value: function () { - return this.reduce((x, y) => x + y, 0); - }, -}); - -async function benchmark({ name, before, before_all, test, after, after_all }) { - let obs_records = []; - const args = []; - args.push_if(await before_all?.(PERSPECTIVE)); - const observations = []; - for (let i = 0; i < ITERATIONS + WARM_UP_ITERATIONS; i++) { - const args2 = args.slice(); - args2.push_if(await before?.(PERSPECTIVE, ...args2)); - const start = microtime.now(); - args2.push_if(await test(PERSPECTIVE, ...args2)); - if (i >= WARM_UP_ITERATIONS) { - observations.push(microtime.now() - start); - } - - await after?.(PERSPECTIVE, ...args2); - } - - const avg = observations.sum() / observations.length / 1000; - console.log(` - ${avg.toFixed(3)}ms - ${name}`); - await after_all?.(PERSPECTIVE, ...args); - - obs_records = obs_records.concat( - observations.map((obs) => ({ - version: VERSION, - version_idx: VERSION_ID, - time: obs, - benchmark: name, - })) - ); - - process.send({ obs_records }); -} - -async function to_data_suite() { - async function before_all(perspective) { - const table = await perspective.table(new_table(perspective)); - const view = await table.view(); - return { table, view }; - } - - async function after_all(perspective, { table, view }) { - await view.delete(); - await table.delete(); - } - - await benchmark({ - name: `.to_arrow()`, - before_all, - after_all, - async test(_perspective, { view }) { - const _arrow = await view.to_arrow(); - }, - }); - - await benchmark({ - name: `.to_csv()`, - before_all, - after_all, - async test(_perspective, { view }) { - const _csv = await view.to_csv(); - }, - }); - - await benchmark({ - name: `.to_columns()`, - before_all, - after_all, - async test(_perspective, { view }) { - const _columns = await view.to_columns(); - }, - }); - - await benchmark({ - name: `.to_json()`, - before_all, - after_all, - async test(_perspective, { view }) { - const _json = await view.to_json(); - }, - }); -} - -async function view_suite() { - async function before_all(perspective) { - const table = await perspective.table(new_table(perspective)); - for (let i = 0; i < 4; i++) { - await table.update(new_table(perspective)); - } - - const schema = await table.schema(); - - return { table, schema }; - } - - async function after_all(perspective, { table }) { - await table.delete(); - } - - async function after(perspective, { table }, view) { - await view.delete(); - } - - await benchmark({ - name: `.view()`, - before_all, - after_all, - after, - async test(_perspective, { table }) { - return await table.view(); - }, - }); - - await benchmark({ - name: `.view({group_by})`, - before_all, - after_all, - after, - async test(perspective, { table }) { - const [M, m, _] = perspective.version; - if ((M === 1 && m >= 2) || M === 2) { - return await table.view({ group_by: ["Product Name"] }); - } else { - return await table.view({ row_pivots: ["Product Name"] }); - } - }, - }); - - // await benchmark({ - // name: `.view({expressions: ["Sales" + "Profit"].reduce((x, y) => Object.assign(x, {[y]: y}), {})})`, - // before_all, - // after_all, - // after, - // async test(perspective, { table, schema }) { - // const [M, m, _] = perspective.version; - // // const columns = Object.keys(schema); - // columns = [`"Sales" + "Profit"`]; - // if (M === 0 && m < 9) { - - // } else if ((M === 1 && m >= 2) || M === 2) { - // return await table.view({ - // columns, - // group_by: ["Product Name"], - // expressions: [`"Sales" + "Profit"`].reduce((x, y) => Object.assign(x, {[y]: y}), {}), - // }); - // } else { - // return await table.view({ - // columns, - // row_pivots: ["Product Name"], - // expressions: [`"Sales" + "Profit"`].reduce((x, y) => Object.assign(x, {[y]: y}), {}), - // }); - // } - // }, - // }); - - await benchmark({ - name: `.view({group_by, aggregates: "median"})`, - before_all, - after_all, - after, - async test(perspective, { table, schema }) { - const columns = ["Sales", "Quantity", "City"]; - const aggregates = Object.fromEntries( - Object.keys(schema).map((x) => [x, "median"]) - ); - const [M, m, _] = perspective.version; - if ((M === 1 && m >= 2) || M === 2) { - return await table.view({ - group_by: ["State"], - aggregates, - columns, - }); - } else { - return await table.view({ - row_pivots: ["State"], - aggregates, - columns, - }); - } - }, - }); -} - -async function table_suite() { - async function before_all(perspective) { - const table = await perspective.table(new_table(perspective)); - const view = await table.view(); - const csv = await view.to_csv(); - await view.delete(); - await table.delete(); - return { csv }; - } - - await benchmark({ - name: `.table(arrow)`, - before_all, - async after(_perspective, _, table) { - await table.delete(); - }, - async test(perspective) { - return await perspective.table(new_table(perspective)); - }, - }); - - await benchmark({ - name: `.table(csv)`, - before_all, - async after(_perspective, _, table) { - await table.delete(); - }, - - async test(perspective, { table, csv }) { - return await perspective.table(csv); - }, - }); -} - -async function bench_all() { - await table_suite(); - await view_suite(); - await to_data_suite(); - process.send({ finished: true }); -} - -process.on("message", ({ path, i }) => { - load_version(path, i); - bench_all(); -}); diff --git a/tools/perspective-scripts/lint_headers.mjs b/tools/perspective-scripts/lint_headers.mjs index 540e2f11a6..3ff160e4a3 100644 --- a/tools/perspective-scripts/lint_headers.mjs +++ b/tools/perspective-scripts/lint_headers.mjs @@ -23,6 +23,8 @@ const IGNORE_PATHS = fs_sync .concat([ "llvm/*", "cmake/*", + "pnpm-lock.yaml", + "pnpm-workspace.yaml", "cpp/perspective/src/cpp/vendor", "cpp/perspective/src/include/perspective/vendor", "python/perspective/perspective/core/_version.py", diff --git a/tools/perspective-test/load-viewer-csv.js b/tools/perspective-test/load-viewer-csv.js index 2d24f8afcf..29e1407b12 100644 --- a/tools/perspective-test/load-viewer-csv.js +++ b/tools/perspective-test/load-viewer-csv.js @@ -10,10 +10,13 @@ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ -import perspective from "/perspective.js"; +import perspective from "/node_modules/@finos/perspective/dist/cdn/perspective.js"; async function load() { - let resp = await fetch("/@finos/perspective-test/assets/superstore.csv"); + let resp = await fetch( + "/node_modules/@finos/perspective-test/assets/superstore.csv" + ); + let csv = await resp.text(); const viewer = document.querySelector("perspective-viewer"); const worker = perspective.worker(); diff --git a/tools/perspective-test/load-viewer-superstore.js b/tools/perspective-test/load-viewer-superstore.js index ce42eae8f3..f90dbb1204 100644 --- a/tools/perspective-test/load-viewer-superstore.js +++ b/tools/perspective-test/load-viewer-superstore.js @@ -10,10 +10,13 @@ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ -import perspective from "/perspective.js"; +import perspective from "/node_modules/@finos/perspective/dist/cdn/perspective.js"; async function load() { - let resp = await fetch("/@finos/perspective-test/assets/superstore.csv"); + let resp = await fetch( + "/node_modules/@finos/perspective-test/assets/superstore.csv" + ); + let csv = await resp.text(); const viewer = document.querySelector("perspective-viewer"); const worker = perspective.worker(); diff --git a/tools/perspective-test/load-workspace-arrow.js b/tools/perspective-test/load-workspace-arrow.js index b869ff6f26..d73d185474 100644 --- a/tools/perspective-test/load-workspace-arrow.js +++ b/tools/perspective-test/load-workspace-arrow.js @@ -10,10 +10,13 @@ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ -import perspective from "/perspective.js"; +import perspective from "/node_modules/@finos/perspective/dist/cdn/perspective.js"; async function load() { - let resp = await fetch("/superstore-arrow/superstore.lz4.arrow"); + let resp = await fetch( + "/node_modules/superstore-arrow/superstore.lz4.arrow" + ); + let arrow = await resp.arrayBuffer(); const worker = perspective.worker(); const table = await worker.table(arrow); diff --git a/tools/perspective-test/package.json b/tools/perspective-test/package.json index e8725e2016..7acdee8c5d 100644 --- a/tools/perspective-test/package.json +++ b/tools/perspective-test/package.json @@ -19,7 +19,6 @@ "author": "", "license": "Apache-2.0", "dependencies": { - "@finos/perspective": "^2.10.0", "xml-formatter": "2.4.0" } } diff --git a/tools/perspective-test/playwright.config.ts b/tools/perspective-test/playwright.config.ts index 13fa82ab12..64828802d5 100644 --- a/tools/perspective-test/playwright.config.ts +++ b/tools/perspective-test/playwright.config.ts @@ -160,7 +160,7 @@ export default defineConfig({ }, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, - quiet: true, + quiet: !process.env.PSP_DEBUG, reporter: process.env.CI ? [["github"], ["html"]] : [["dot"]], projects: PROJECTS, outputDir: "dist/results", @@ -187,5 +187,7 @@ export default defineConfig({ command: "yarn ts-node src/js/start_test_server.ts", port: TEST_SERVER_PORT, reuseExistingServer: true, + stdout: "pipe", + stderr: "pipe", }, }); diff --git a/tools/perspective-test/src/html/basic-test.html b/tools/perspective-test/src/html/basic-test.html index 210dd9300d..67f06a1512 100644 --- a/tools/perspective-test/src/html/basic-test.html +++ b/tools/perspective-test/src/html/basic-test.html @@ -10,13 +10,13 @@ - - - - + + + + - - + +