From 7cd2b274e133f931d5ef32e9fcb24ca29a207351 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Thu, 21 Nov 2024 11:51:55 -0700 Subject: [PATCH 01/32] init testing matrix --- .github/workflows/test.yaml | 64 +++++++++++++++++++ .../harperdb-base-component/config.yaml | 5 ++ .../harperdb-base-component/package.json | 4 ++ .../harperdb-base-component/resources.js | 18 ++++++ .../harperdb-base-component/schema.graphql | 5 ++ 5 files changed, 96 insertions(+) create mode 100644 .github/workflows/test.yaml create mode 100644 test/integration/harperdb-base-component/config.yaml create mode 100644 test/integration/harperdb-base-component/package.json create mode 100644 test/integration/harperdb-base-component/resources.js create mode 100644 test/integration/harperdb-base-component/schema.graphql diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..39b3043 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,64 @@ +name: Next.js Version Matrix Test + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +env: + HDB_ADMIN_USERNAME: "hdb_admin" + HDB_ADMIN_PASSWORD: "password" + HDB_ROOT_PATH: "/tmp/hdb" + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + matrix: + # next-version: [9, 10, 11, 12, 13, 14, 15] + node-version: [16, 18, 20, 21, 22] + # exclude: + # - next-version: 9 + # node-version: 18 + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + + - name: Setup HarperDB + run: | + npm install -g harperdb + harperdb install \ + --TC_AGREEMENT yes \ + --HDB_ADMIN_USERNAME ${{ env.HDB_ADMIN_USERNAME }} \ + --HDB_ADMIN_PASSWORD ${{ env.HDB_ADMIN_PASSWORD }} \ + --ROOTPATH ${{ env.HDB_ROOT_PATH }} \ + --OPERATIONSAPI_NETWORK_PORT 9925 + + - name: Load Base Component + run: | + cp -r test/integration/harperdb-base-component ${{ env.HDB_ROOT_PATH }}/components/ + + - name: Start HarperDB + run: | + harperdb start + + - name: Query + run: | + curl http://localhost:9926/Dog/0 + # - name: Install dependencies + # run: | + # npm install + # npm install next@${{ matrix.next-version }} + + # - name: Build + # run: npm run build + + # - name: Test + # run: npm test \ No newline at end of file diff --git a/test/integration/harperdb-base-component/config.yaml b/test/integration/harperdb-base-component/config.yaml new file mode 100644 index 0000000..046c969 --- /dev/null +++ b/test/integration/harperdb-base-component/config.yaml @@ -0,0 +1,5 @@ +rest: true +graphqlSchema: + files: './schema.graphql' +jsResource: + files: './resources.js' \ No newline at end of file diff --git a/test/integration/harperdb-base-component/package.json b/test/integration/harperdb-base-component/package.json new file mode 100644 index 0000000..d492976 --- /dev/null +++ b/test/integration/harperdb-base-component/package.json @@ -0,0 +1,4 @@ +{ + "name": "harperdb-base-component", + "private": true +} \ No newline at end of file diff --git a/test/integration/harperdb-base-component/resources.js b/test/integration/harperdb-base-component/resources.js new file mode 100644 index 0000000..90862e7 --- /dev/null +++ b/test/integration/harperdb-base-component/resources.js @@ -0,0 +1,18 @@ +const dogs = [ + { id: '0', name: 'Lincoln', breed: 'Shepherd' }, + { id: '1', name: 'Max', breed: 'Cocker Spaniel' }, + { id: '2', name: 'Bella', breed: 'Lab' }, + { id: '3', name: 'Charlie', breed: 'Great Dane' }, + { id: '4', name: 'Lucy', breed: 'Newfoundland' }, + { id: '5', name: 'Cooper', breed: 'Pug' }, + { id: '6', name: 'Daisy', breed: 'Bull Dog' }, + { id: '7', name: 'Rocky', breed: 'Akita' }, + { id: '8', name: 'Luna', breed: 'Wolf' }, + { id: '9', name: 'Buddy', breed: 'Border Collie' }, + { id: '10', name: 'Bailey', breed: 'Golden Retriever' }, + { id: '11', name: 'Sadie', breed: 'Belgian Malinois' }, +]; + +for (const dog of dogs) { + tables.Dog.put(dog); +} diff --git a/test/integration/harperdb-base-component/schema.graphql b/test/integration/harperdb-base-component/schema.graphql new file mode 100644 index 0000000..15c11b8 --- /dev/null +++ b/test/integration/harperdb-base-component/schema.graphql @@ -0,0 +1,5 @@ +type Dog @table @export { + id: ID @primaryKey + name: String + breed: String +} \ No newline at end of file From 203b39cb1a551b05d75d50b3e051053c56e18183 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Thu, 21 Nov 2024 11:55:28 -0700 Subject: [PATCH 02/32] add hdb stop --- .github/workflows/test.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 39b3043..960c9b1 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -40,6 +40,7 @@ jobs: --HDB_ADMIN_PASSWORD ${{ env.HDB_ADMIN_PASSWORD }} \ --ROOTPATH ${{ env.HDB_ROOT_PATH }} \ --OPERATIONSAPI_NETWORK_PORT 9925 + harperdb stop - name: Load Base Component run: | @@ -51,7 +52,7 @@ jobs: - name: Query run: | - curl http://localhost:9926/Dog/0 + curl http://localhost:9925/Dog/0 # - name: Install dependencies # run: | # npm install From 3a54320b6954e45d0cec4136544fbaf1b154e898 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Thu, 21 Nov 2024 12:01:31 -0700 Subject: [PATCH 03/32] try timeout --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 960c9b1..7fd05ac 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -34,7 +34,7 @@ jobs: - name: Setup HarperDB run: | npm install -g harperdb - harperdb install \ + timeout 30s harperdb install \ --TC_AGREEMENT yes \ --HDB_ADMIN_USERNAME ${{ env.HDB_ADMIN_USERNAME }} \ --HDB_ADMIN_PASSWORD ${{ env.HDB_ADMIN_PASSWORD }} \ From f4e6afe06833c457ac559d66e1a7f1c53821a651 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Thu, 21 Nov 2024 12:03:38 -0700 Subject: [PATCH 04/32] add || true --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 7fd05ac..75e90be 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -39,7 +39,7 @@ jobs: --HDB_ADMIN_USERNAME ${{ env.HDB_ADMIN_USERNAME }} \ --HDB_ADMIN_PASSWORD ${{ env.HDB_ADMIN_PASSWORD }} \ --ROOTPATH ${{ env.HDB_ROOT_PATH }} \ - --OPERATIONSAPI_NETWORK_PORT 9925 + --OPERATIONSAPI_NETWORK_PORT 9925 || true harperdb stop - name: Load Base Component From 2718f1e079adcf9cbddb606a9a3f68000541d0bb Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Thu, 21 Nov 2024 12:10:22 -0700 Subject: [PATCH 05/32] try this --- .github/workflows/test.yaml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 75e90be..e213a1e 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -34,12 +34,12 @@ jobs: - name: Setup HarperDB run: | npm install -g harperdb - timeout 30s harperdb install \ + harperdb start \ --TC_AGREEMENT yes \ --HDB_ADMIN_USERNAME ${{ env.HDB_ADMIN_USERNAME }} \ --HDB_ADMIN_PASSWORD ${{ env.HDB_ADMIN_PASSWORD }} \ --ROOTPATH ${{ env.HDB_ROOT_PATH }} \ - --OPERATIONSAPI_NETWORK_PORT 9925 || true + --OPERATIONSAPI_NETWORK_PORT 9925 harperdb stop - name: Load Base Component @@ -53,6 +53,10 @@ jobs: - name: Query run: | curl http://localhost:9925/Dog/0 + + - name: Stop HarperDB + run: | + harperdb stop # - name: Install dependencies # run: | # npm install From 68ef8ff94b06d7f1eb21d0f11ff05b2c51ac9b1a Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Thu, 21 Nov 2024 12:13:29 -0700 Subject: [PATCH 06/32] try sleep 30 --- .github/workflows/test.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index e213a1e..7cc00d2 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -40,6 +40,7 @@ jobs: --HDB_ADMIN_PASSWORD ${{ env.HDB_ADMIN_PASSWORD }} \ --ROOTPATH ${{ env.HDB_ROOT_PATH }} \ --OPERATIONSAPI_NETWORK_PORT 9925 + sleep 30 harperdb stop - name: Load Base Component @@ -49,6 +50,7 @@ jobs: - name: Start HarperDB run: | harperdb start + sleep 30 - name: Query run: | From ef7a747947785dde8bd84f2e4c092b302ecb97e7 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Thu, 21 Nov 2024 12:19:10 -0700 Subject: [PATCH 07/32] whoops --- .github/workflows/test.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 7cc00d2..74ac63b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -40,7 +40,7 @@ jobs: --HDB_ADMIN_PASSWORD ${{ env.HDB_ADMIN_PASSWORD }} \ --ROOTPATH ${{ env.HDB_ROOT_PATH }} \ --OPERATIONSAPI_NETWORK_PORT 9925 - sleep 30 + sleep 15 harperdb stop - name: Load Base Component @@ -50,11 +50,11 @@ jobs: - name: Start HarperDB run: | harperdb start - sleep 30 + sleep 15 - name: Query run: | - curl http://localhost:9925/Dog/0 + curl http://localhost:9926/Dog/0 - name: Stop HarperDB run: | From e64e5340e871d4b8473a6a264215720c99f201c0 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Thu, 21 Nov 2024 12:24:35 -0700 Subject: [PATCH 08/32] oh actions I love you --- .github/workflows/test.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 74ac63b..f73b2ac 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -45,6 +45,7 @@ jobs: - name: Load Base Component run: | + ls . cp -r test/integration/harperdb-base-component ${{ env.HDB_ROOT_PATH }}/components/ - name: Start HarperDB From ebb2cd3202866855a7c0cc4e276728e6ff485a0b Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Thu, 21 Nov 2024 12:43:46 -0700 Subject: [PATCH 09/32] . --- .github/workflows/test.yaml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index f73b2ac..9af36f2 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -39,23 +39,22 @@ jobs: --HDB_ADMIN_USERNAME ${{ env.HDB_ADMIN_USERNAME }} \ --HDB_ADMIN_PASSWORD ${{ env.HDB_ADMIN_PASSWORD }} \ --ROOTPATH ${{ env.HDB_ROOT_PATH }} \ - --OPERATIONSAPI_NETWORK_PORT 9925 + --OPERATIONSAPI_NETWORK_PORT 9925 \ + --LOGGING_STDSTREAMS true sleep 15 - harperdb stop - name: Load Base Component run: | - ls . cp -r test/integration/harperdb-base-component ${{ env.HDB_ROOT_PATH }}/components/ - name: Start HarperDB run: | - harperdb start + harperdb restart sleep 15 - + - name: Query run: | - curl http://localhost:9926/Dog/0 + curl "http://localhost:9926/Dog/0" - name: Stop HarperDB run: | From 1737633c22ef7ffc5b53a7ba902499e384fe7e9e Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Fri, 22 Nov 2024 12:35:37 -0700 Subject: [PATCH 10/32] use docker and js. wip --- .github/workflows/test.yaml | 65 ++------- README.md | 5 +- package.json | 3 +- scripts/run-integration-tests.js | 123 ++++++++++++++++++ test/integration/Dockerfile | 19 +++ .../harperdb-base-component/config.yaml | 2 +- .../harperdb-base-component/package.json | 2 +- .../harperdb-base-component/schema.graphql | 2 +- test/integration/next-10/index.js | 12 ++ test/integration/next-10/package.json | 4 + test/integration/next-11/index.js | 12 ++ test/integration/next-11/package.json | 4 + test/integration/next-12/index.js | 12 ++ test/integration/next-12/package.json | 4 + test/integration/next-13/index.js | 12 ++ test/integration/next-13/package.json | 4 + test/integration/next-14/index.js | 12 ++ test/integration/next-14/package.json | 4 + test/integration/next-15/index.js | 12 ++ test/integration/next-15/package.json | 4 + test/integration/next-9/index.js | 12 ++ test/integration/next-9/package.json | 4 + 22 files changed, 270 insertions(+), 63 deletions(-) create mode 100644 scripts/run-integration-tests.js create mode 100644 test/integration/Dockerfile create mode 100644 test/integration/next-10/index.js create mode 100644 test/integration/next-10/package.json create mode 100644 test/integration/next-11/index.js create mode 100644 test/integration/next-11/package.json create mode 100644 test/integration/next-12/index.js create mode 100644 test/integration/next-12/package.json create mode 100644 test/integration/next-13/index.js create mode 100644 test/integration/next-13/package.json create mode 100644 test/integration/next-14/index.js create mode 100644 test/integration/next-14/package.json create mode 100644 test/integration/next-15/index.js create mode 100644 test/integration/next-15/package.json create mode 100644 test/integration/next-9/index.js create mode 100644 test/integration/next-9/package.json diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 9af36f2..86842c0 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -2,70 +2,25 @@ name: Next.js Version Matrix Test on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] - -env: - HDB_ADMIN_USERNAME: "hdb_admin" - HDB_ADMIN_PASSWORD: "password" - HDB_ROOT_PATH: "/tmp/hdb" + branches: [main] jobs: + # Skip for now + if: ${{ false }} test: runs-on: ubuntu-latest strategy: matrix: - # next-version: [9, 10, 11, 12, 13, 14, 15] - node-version: [16, 18, 20, 21, 22] + next-version: [9, 10, 11, 12, 13, 14, 15] + node-version: [16, 18, 20, 22] # exclude: - # - next-version: 9 - # node-version: 18 + # - next-version: 9 + # node-version: 18 steps: - uses: actions/checkout@v4 - - - name: Setup Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - - - name: Setup HarperDB - run: | - npm install -g harperdb - harperdb start \ - --TC_AGREEMENT yes \ - --HDB_ADMIN_USERNAME ${{ env.HDB_ADMIN_USERNAME }} \ - --HDB_ADMIN_PASSWORD ${{ env.HDB_ADMIN_PASSWORD }} \ - --ROOTPATH ${{ env.HDB_ROOT_PATH }} \ - --OPERATIONSAPI_NETWORK_PORT 9925 \ - --LOGGING_STDSTREAMS true - sleep 15 - - - name: Load Base Component - run: | - cp -r test/integration/harperdb-base-component ${{ env.HDB_ROOT_PATH }}/components/ - - - name: Start HarperDB - run: | - harperdb restart - sleep 15 - - - name: Query - run: | - curl "http://localhost:9926/Dog/0" - - - name: Stop HarperDB - run: | - harperdb stop - # - name: Install dependencies - # run: | - # npm install - # npm install next@${{ matrix.next-version }} - - # - name: Build - # run: npm run build - - # - name: Test - # run: npm test \ No newline at end of file + # TODO: invoke the integration container + # TODO: find interoperability with the scripts/run-integration-tests.js diff --git a/README.md b/README.md index 2ef4323..7f65d34 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Most Next.js features are supported as we rely on the Next.js Server provided by ## Usage -> [!NOTE] +> [!NOTE] > This guide assumes you're already familiar with [HarperDb Components](https://docs.harperdb.io/docs/developers/components). Please review the documentation, or check out the HarperDB [Next.js Example](https://github.com/HarperDB/nextjs-example) for more information. 1. Install: @@ -59,7 +59,6 @@ export async function listDogs() { export async function getDog(id) { return tables.Dog.get(id); } - ``` ```js @@ -83,7 +82,6 @@ export default async function Dog({ params }) { ); } - ``` ## Options @@ -118,7 +116,6 @@ Specify a port for the Next.js server. Defaults to `3000`. When enabled, the extension will look for a `.next` directory in the root of the component and skip executing the `buildCommand`. Defaults to `false`. - ## CLI This package includes a CLI (`harperdb-nextjs`) that is meant to replace certain functions of the Next.js CLI. It will launch HarperDB and set sensible configuration values. diff --git a/package.json b/package.json index f22bfaa..a08088c 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,8 @@ "cli.js" ], "scripts": { - "format": "prettier --write ." + "format": "prettier --write .", + "test:integration": "node ./scripts/run-integration-tests.js" }, "dependencies": { "semver": "^7.6.3", diff --git a/scripts/run-integration-tests.js b/scripts/run-integration-tests.js new file mode 100644 index 0000000..6b276dc --- /dev/null +++ b/scripts/run-integration-tests.js @@ -0,0 +1,123 @@ +import { spawnSync, spawn } from 'node:child_process'; +import assert from 'node:assert'; + +const NEXT_VERSIONS = [9, 10, 11, 12, 13, 14, 15]; +const NODE_VERSIONS = [16, 18, 20, 22]; + +function getContainerEngine() { + for (const engine of ['podman', 'docker']) { + if (spawnSync(engine, ['--version'], { stdio: 'ignore' }).status === 0) { + return engine; + } + } +} + +// Determine Container Engine +const containerEngine = getContainerEngine(); + +console.log(`๐Ÿณ Using container engine: ${containerEngine}`); + +let overallExitCode = 0; + +console.log(`๐ŸŽฏ Running tests with the following matrix: + Next.js versions: ${NEXT_VERSIONS.join(', ')} + Node.js versions: ${NODE_VERSIONS.join(', ')} +`); + +function runIntegrationTest(nextVersion, nodeVersion) { + return new Promise((resolve, reject) => { + console.log(`๐Ÿš€ Testing with Next.js: v${nextVersion} and Node.js: v${nodeVersion}`); + + const imageName = `integration-test-image-next-${nextVersion}-node-${nodeVersion}`; + const containerName = `integration-test-container-next-${nextVersion}-node-${nodeVersion}`; + + function stopAndRemove() { + if (spawnSync(containerEngine, ['ps', '-aq', '-f', `name=${containerName}`]).stdout !== '') { + spawnSync(containerEngine, ['stop', containerName], { stdio: 'ignore' }); + spawnSync(containerEngine, ['rm', containerName], { stdio: 'ignore' }); + } + } + + stopAndRemove(); + + console.log(`๐Ÿ—๏ธ Building ${imageName}...`); + + spawnSync( + containerEngine, + [ + 'build', + '--build-arg', + `NEXT_MAJOR=${nextVersion}`, + '--build-arg', + `NODE_MAJOR=${nodeVersion}`, + '-t', + imageName, + 'test/integration', + ], + { stdio: 'inherit' } + ); + + console.log(`๐Ÿงช Running tests...`); + + const runProcess = spawn(containerEngine, ['run', '-p', '3000:3000', '--name', containerName, imageName]); + + runProcess.on('exit', (code) => { + console.log(`๐Ÿงน Cleaning up...`); + stopAndRemove(); + resolve(code); + }); + + runProcess.on('error', (err) => { + console.error('Process error:', err); + stopAndRemove(); + reject(err); + }); + + // Gotta figure out the actual test flow here, but this is the idea. + // Start the container which (eventually) runs a Next.js app + // Then execute some test file against that app which probably uses fetch to verify pages are correct + // Then when that test file is done, shutdown the container and report the results. + runProcess.stdout.on('data', (data) => { + console.log(`stdout: ${data}`); + fetch('http://localhost:3000') + .then((res) => res.text()) + .then((body) => { + assert(body === `Next.js v${nextVersion}`); + stopAndRemove(); + }) + .catch((err) => { + console.error('Error fetching:', err); + stopAndRemove(); + reject(err); + }); + }); + + runProcess.stderr.on('data', (data) => { + console.log(`stderr: ${data}`); + }); + }); +} + +const results = {}; + +for (const nextVersion of NEXT_VERSIONS) { + const nextVersionKey = `Next.js v${nextVersion}`; + results[nextVersionKey] = {}; + for (const nodeVersion of NODE_VERSIONS) { + const nodeVersionKey = `Node.js v${nodeVersion}`; + const exitCode = await runIntegrationTest(nextVersion, nodeVersion); + console.log(`๐Ÿ Test finished with exit code: ${exitCode}`); + // Replace this with 0 when the container actually has a 0 exit + if (exitCode === 137) { + results[nextVersionKey][nodeVersionKey] = 'โœ…'; + } else { + results[nextVersionKey][nodeVersionKey] = 'โŒ'; + overallExitCode = 1; + } + } +} + +console.log(`๐Ÿ“Š Test Summary:`); +console.table(results); + +process.exit(overallExitCode); diff --git a/test/integration/Dockerfile b/test/integration/Dockerfile new file mode 100644 index 0000000..eee615a --- /dev/null +++ b/test/integration/Dockerfile @@ -0,0 +1,19 @@ +ARG NODE_MAJOR + +FROM node:${NODE_MAJOR} as node-base-${NODE_MAJOR} + +RUN apt-get update && apt-get install -y \ + curl \ + && rm -rf /var/lib/apt/lists/* + +RUN npm install -g harperdb + +FROM node-base-${NODE_MAJOR} + +ARG NEXT_MAJOR +ARG NODE_MAJOR + +COPY next-${NEXT_MAJOR}/ ./template/ + +EXPOSE 3000 +CMD ["node", "template/index.js"] \ No newline at end of file diff --git a/test/integration/harperdb-base-component/config.yaml b/test/integration/harperdb-base-component/config.yaml index 046c969..118c3f8 100644 --- a/test/integration/harperdb-base-component/config.yaml +++ b/test/integration/harperdb-base-component/config.yaml @@ -2,4 +2,4 @@ rest: true graphqlSchema: files: './schema.graphql' jsResource: - files: './resources.js' \ No newline at end of file + files: './resources.js' diff --git a/test/integration/harperdb-base-component/package.json b/test/integration/harperdb-base-component/package.json index d492976..de0cb1b 100644 --- a/test/integration/harperdb-base-component/package.json +++ b/test/integration/harperdb-base-component/package.json @@ -1,4 +1,4 @@ { "name": "harperdb-base-component", "private": true -} \ No newline at end of file +} diff --git a/test/integration/harperdb-base-component/schema.graphql b/test/integration/harperdb-base-component/schema.graphql index 15c11b8..d92a5be 100644 --- a/test/integration/harperdb-base-component/schema.graphql +++ b/test/integration/harperdb-base-component/schema.graphql @@ -2,4 +2,4 @@ type Dog @table @export { id: ID @primaryKey name: String breed: String -} \ No newline at end of file +} diff --git a/test/integration/next-10/index.js b/test/integration/next-10/index.js new file mode 100644 index 0000000..e3e6d25 --- /dev/null +++ b/test/integration/next-10/index.js @@ -0,0 +1,12 @@ +import http from 'http'; + +const server = http.createServer((req, res) => { + res.statusCode = 200; + res.setHeader('Content-Type', 'text/plain'); + res.end('Next.js v10'); +}); + +const PORT = 3000; +server.listen(PORT, () => { + console.log(`Server running at http://localhost:${PORT}/`); +}); diff --git a/test/integration/next-10/package.json b/test/integration/next-10/package.json new file mode 100644 index 0000000..215f117 --- /dev/null +++ b/test/integration/next-10/package.json @@ -0,0 +1,4 @@ +{ + "name": "next-10", + "type": "module" +} diff --git a/test/integration/next-11/index.js b/test/integration/next-11/index.js new file mode 100644 index 0000000..ac107be --- /dev/null +++ b/test/integration/next-11/index.js @@ -0,0 +1,12 @@ +import http from 'http'; + +const server = http.createServer((req, res) => { + res.statusCode = 200; + res.setHeader('Content-Type', 'text/plain'); + res.end('Next.js v11'); +}); + +const PORT = 3000; +server.listen(PORT, () => { + console.log(`Server running at http://localhost:${PORT}/`); +}); diff --git a/test/integration/next-11/package.json b/test/integration/next-11/package.json new file mode 100644 index 0000000..08d7451 --- /dev/null +++ b/test/integration/next-11/package.json @@ -0,0 +1,4 @@ +{ + "name": "next-11", + "type": "module" +} diff --git a/test/integration/next-12/index.js b/test/integration/next-12/index.js new file mode 100644 index 0000000..804358c --- /dev/null +++ b/test/integration/next-12/index.js @@ -0,0 +1,12 @@ +import http from 'http'; + +const server = http.createServer((req, res) => { + res.statusCode = 200; + res.setHeader('Content-Type', 'text/plain'); + res.end('Next.js v12'); +}); + +const PORT = 3000; +server.listen(PORT, () => { + console.log(`Server running at http://localhost:${PORT}/`); +}); diff --git a/test/integration/next-12/package.json b/test/integration/next-12/package.json new file mode 100644 index 0000000..1fbe281 --- /dev/null +++ b/test/integration/next-12/package.json @@ -0,0 +1,4 @@ +{ + "name": "next-12", + "type": "module" +} diff --git a/test/integration/next-13/index.js b/test/integration/next-13/index.js new file mode 100644 index 0000000..b6faaa4 --- /dev/null +++ b/test/integration/next-13/index.js @@ -0,0 +1,12 @@ +import http from 'http'; + +const server = http.createServer((req, res) => { + res.statusCode = 200; + res.setHeader('Content-Type', 'text/plain'); + res.end('Next.js v13'); +}); + +const PORT = 3000; +server.listen(PORT, () => { + console.log(`Server running at http://localhost:${PORT}/`); +}); diff --git a/test/integration/next-13/package.json b/test/integration/next-13/package.json new file mode 100644 index 0000000..d2f7e17 --- /dev/null +++ b/test/integration/next-13/package.json @@ -0,0 +1,4 @@ +{ + "name": "next-13", + "type": "module" +} diff --git a/test/integration/next-14/index.js b/test/integration/next-14/index.js new file mode 100644 index 0000000..c172664 --- /dev/null +++ b/test/integration/next-14/index.js @@ -0,0 +1,12 @@ +import http from 'http'; + +const server = http.createServer((req, res) => { + res.statusCode = 200; + res.setHeader('Content-Type', 'text/plain'); + res.end('Next.js v14'); +}); + +const PORT = 3000; +server.listen(PORT, () => { + console.log(`Server running at http://localhost:${PORT}/`); +}); diff --git a/test/integration/next-14/package.json b/test/integration/next-14/package.json new file mode 100644 index 0000000..f194e75 --- /dev/null +++ b/test/integration/next-14/package.json @@ -0,0 +1,4 @@ +{ + "name": "next-14", + "type": "module" +} diff --git a/test/integration/next-15/index.js b/test/integration/next-15/index.js new file mode 100644 index 0000000..75bde3e --- /dev/null +++ b/test/integration/next-15/index.js @@ -0,0 +1,12 @@ +import http from 'http'; + +const server = http.createServer((req, res) => { + res.statusCode = 200; + res.setHeader('Content-Type', 'text/plain'); + res.end('Next.js v15'); +}); + +const PORT = 3000; +server.listen(PORT, () => { + console.log(`Server running at http://localhost:${PORT}/`); +}); diff --git a/test/integration/next-15/package.json b/test/integration/next-15/package.json new file mode 100644 index 0000000..0a07662 --- /dev/null +++ b/test/integration/next-15/package.json @@ -0,0 +1,4 @@ +{ + "name": "next-15", + "type": "module" +} diff --git a/test/integration/next-9/index.js b/test/integration/next-9/index.js new file mode 100644 index 0000000..d2a07bb --- /dev/null +++ b/test/integration/next-9/index.js @@ -0,0 +1,12 @@ +import http from 'http'; + +const server = http.createServer((req, res) => { + res.statusCode = 200; + res.setHeader('Content-Type', 'text/plain'); + res.end('Next.js v9'); +}); + +const PORT = 3000; +server.listen(PORT, () => { + console.log(`Server running at http://localhost:${PORT}/`); +}); diff --git a/test/integration/next-9/package.json b/test/integration/next-9/package.json new file mode 100644 index 0000000..78a6a8d --- /dev/null +++ b/test/integration/next-9/package.json @@ -0,0 +1,4 @@ +{ + "name": "next-9", + "type": "module" +} From 10e3db37452c1aa39bfad29168614dad463d9bf4 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Fri, 22 Nov 2024 13:00:56 -0700 Subject: [PATCH 11/32] update dockerfil to copy the base component --- test/integration/Dockerfile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/integration/Dockerfile b/test/integration/Dockerfile index eee615a..0ae67f8 100644 --- a/test/integration/Dockerfile +++ b/test/integration/Dockerfile @@ -8,12 +8,16 @@ RUN apt-get update && apt-get install -y \ RUN npm install -g harperdb +# TODO Setup HarperDB to rootpath /hdb/ + +COPY harperdb-base-component/ ./hdb/components/ + FROM node-base-${NODE_MAJOR} ARG NEXT_MAJOR ARG NODE_MAJOR -COPY next-${NEXT_MAJOR}/ ./template/ +COPY next-${NEXT_MAJOR}/ ./hdb/components/ EXPOSE 3000 CMD ["node", "template/index.js"] \ No newline at end of file From 610617b4969fa7ee0988ab8ac3d6cf6ca8ae4a69 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Fri, 22 Nov 2024 13:01:47 -0700 Subject: [PATCH 12/32] fix arg order in dockerfile --- test/integration/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/integration/Dockerfile b/test/integration/Dockerfile index 0ae67f8..3985940 100644 --- a/test/integration/Dockerfile +++ b/test/integration/Dockerfile @@ -12,10 +12,11 @@ RUN npm install -g harperdb COPY harperdb-base-component/ ./hdb/components/ +ARG NODE_MAJOR + FROM node-base-${NODE_MAJOR} ARG NEXT_MAJOR -ARG NODE_MAJOR COPY next-${NEXT_MAJOR}/ ./hdb/components/ From 0c43aba047dd03b2a0d82036b4253de8fab80668 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Tue, 26 Nov 2024 16:12:27 -0700 Subject: [PATCH 13/32] use node test runner --- .node-version | 1 + package.json | 2 +- schema.graphql | 2 +- scripts/run-integration-tests.js | 123 ------------------ test/fixtures/Dockerfile | 35 +++++ .../harperdb-base-component/config.yaml | 0 .../harperdb-base-component/package.json | 0 .../harperdb-base-component/resources.js | 0 .../harperdb-base-component/schema.graphql | 0 .../next-10/index.js | 0 .../next-10/package.json | 0 .../next-11/index.js | 0 .../next-11/package.json | 0 .../next-12/index.js | 0 .../next-12/package.json | 0 .../next-13/index.js | 0 .../next-13/package.json | 0 .../next-14/index.js | 0 .../next-14/package.json | 0 .../next-15/index.js | 0 .../next-15/package.json | 0 .../{integration => fixtures}/next-9/index.js | 0 .../next-9/package.json | 0 test/integration/Dockerfile | 24 ---- test/next-15-node-20.test.js | 28 ++++ test/util.js | 93 +++++++++++++ 26 files changed, 159 insertions(+), 149 deletions(-) create mode 100644 .node-version delete mode 100644 scripts/run-integration-tests.js create mode 100644 test/fixtures/Dockerfile rename test/{integration => fixtures}/harperdb-base-component/config.yaml (100%) rename test/{integration => fixtures}/harperdb-base-component/package.json (100%) rename test/{integration => fixtures}/harperdb-base-component/resources.js (100%) rename test/{integration => fixtures}/harperdb-base-component/schema.graphql (100%) rename test/{integration => fixtures}/next-10/index.js (100%) rename test/{integration => fixtures}/next-10/package.json (100%) rename test/{integration => fixtures}/next-11/index.js (100%) rename test/{integration => fixtures}/next-11/package.json (100%) rename test/{integration => fixtures}/next-12/index.js (100%) rename test/{integration => fixtures}/next-12/package.json (100%) rename test/{integration => fixtures}/next-13/index.js (100%) rename test/{integration => fixtures}/next-13/package.json (100%) rename test/{integration => fixtures}/next-14/index.js (100%) rename test/{integration => fixtures}/next-14/package.json (100%) rename test/{integration => fixtures}/next-15/index.js (100%) rename test/{integration => fixtures}/next-15/package.json (100%) rename test/{integration => fixtures}/next-9/index.js (100%) rename test/{integration => fixtures}/next-9/package.json (100%) delete mode 100644 test/integration/Dockerfile create mode 100644 test/next-15-node-20.test.js create mode 100644 test/util.js diff --git a/.node-version b/.node-version new file mode 100644 index 0000000..fdb2eaa --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +22.11.0 \ No newline at end of file diff --git a/package.json b/package.json index d7496c4..8c77644 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ ], "scripts": { "format": "prettier --write .", - "test:integration": "node ./scripts/run-integration-tests.js" + "test": "node --test \"test/**/*.test.js\"" }, "dependencies": { "shell-quote": "^1.8.1" diff --git a/schema.graphql b/schema.graphql index a04f44e..e74b5c4 100644 --- a/schema.graphql +++ b/schema.graphql @@ -1,4 +1,4 @@ -type NextCache @table(database: "cache" expiration: 3600) @export { +type NextCache @table(database: "cache", expiration: 3600) @export { id: ID @primaryKey headers: Any content: Bytes diff --git a/scripts/run-integration-tests.js b/scripts/run-integration-tests.js deleted file mode 100644 index 6b276dc..0000000 --- a/scripts/run-integration-tests.js +++ /dev/null @@ -1,123 +0,0 @@ -import { spawnSync, spawn } from 'node:child_process'; -import assert from 'node:assert'; - -const NEXT_VERSIONS = [9, 10, 11, 12, 13, 14, 15]; -const NODE_VERSIONS = [16, 18, 20, 22]; - -function getContainerEngine() { - for (const engine of ['podman', 'docker']) { - if (spawnSync(engine, ['--version'], { stdio: 'ignore' }).status === 0) { - return engine; - } - } -} - -// Determine Container Engine -const containerEngine = getContainerEngine(); - -console.log(`๐Ÿณ Using container engine: ${containerEngine}`); - -let overallExitCode = 0; - -console.log(`๐ŸŽฏ Running tests with the following matrix: - Next.js versions: ${NEXT_VERSIONS.join(', ')} - Node.js versions: ${NODE_VERSIONS.join(', ')} -`); - -function runIntegrationTest(nextVersion, nodeVersion) { - return new Promise((resolve, reject) => { - console.log(`๐Ÿš€ Testing with Next.js: v${nextVersion} and Node.js: v${nodeVersion}`); - - const imageName = `integration-test-image-next-${nextVersion}-node-${nodeVersion}`; - const containerName = `integration-test-container-next-${nextVersion}-node-${nodeVersion}`; - - function stopAndRemove() { - if (spawnSync(containerEngine, ['ps', '-aq', '-f', `name=${containerName}`]).stdout !== '') { - spawnSync(containerEngine, ['stop', containerName], { stdio: 'ignore' }); - spawnSync(containerEngine, ['rm', containerName], { stdio: 'ignore' }); - } - } - - stopAndRemove(); - - console.log(`๐Ÿ—๏ธ Building ${imageName}...`); - - spawnSync( - containerEngine, - [ - 'build', - '--build-arg', - `NEXT_MAJOR=${nextVersion}`, - '--build-arg', - `NODE_MAJOR=${nodeVersion}`, - '-t', - imageName, - 'test/integration', - ], - { stdio: 'inherit' } - ); - - console.log(`๐Ÿงช Running tests...`); - - const runProcess = spawn(containerEngine, ['run', '-p', '3000:3000', '--name', containerName, imageName]); - - runProcess.on('exit', (code) => { - console.log(`๐Ÿงน Cleaning up...`); - stopAndRemove(); - resolve(code); - }); - - runProcess.on('error', (err) => { - console.error('Process error:', err); - stopAndRemove(); - reject(err); - }); - - // Gotta figure out the actual test flow here, but this is the idea. - // Start the container which (eventually) runs a Next.js app - // Then execute some test file against that app which probably uses fetch to verify pages are correct - // Then when that test file is done, shutdown the container and report the results. - runProcess.stdout.on('data', (data) => { - console.log(`stdout: ${data}`); - fetch('http://localhost:3000') - .then((res) => res.text()) - .then((body) => { - assert(body === `Next.js v${nextVersion}`); - stopAndRemove(); - }) - .catch((err) => { - console.error('Error fetching:', err); - stopAndRemove(); - reject(err); - }); - }); - - runProcess.stderr.on('data', (data) => { - console.log(`stderr: ${data}`); - }); - }); -} - -const results = {}; - -for (const nextVersion of NEXT_VERSIONS) { - const nextVersionKey = `Next.js v${nextVersion}`; - results[nextVersionKey] = {}; - for (const nodeVersion of NODE_VERSIONS) { - const nodeVersionKey = `Node.js v${nodeVersion}`; - const exitCode = await runIntegrationTest(nextVersion, nodeVersion); - console.log(`๐Ÿ Test finished with exit code: ${exitCode}`); - // Replace this with 0 when the container actually has a 0 exit - if (exitCode === 137) { - results[nextVersionKey][nodeVersionKey] = 'โœ…'; - } else { - results[nextVersionKey][nodeVersionKey] = 'โŒ'; - overallExitCode = 1; - } - } -} - -console.log(`๐Ÿ“Š Test Summary:`); -console.table(results); - -process.exit(overallExitCode); diff --git a/test/fixtures/Dockerfile b/test/fixtures/Dockerfile new file mode 100644 index 0000000..fd6a557 --- /dev/null +++ b/test/fixtures/Dockerfile @@ -0,0 +1,35 @@ +ARG NODE_MAJOR + +FROM node:${NODE_MAJOR} as node-base-${NODE_MAJOR} + +RUN apt-get update && apt-get install -y \ + curl \ + && rm -rf /var/lib/apt/lists/* + +RUN npm install -g harperdb + +RUN mkdir -p /hdb/components + +ENV TC_AGREEMENT=yes +ENV HDB_ADMIN_USERNAME=hdb_admin +ENV HDB_ADMIN_PASSWORD=password +ENV ROOTPATH=/hdb +ENV OPERATIONSAPI_NETWORK_PORT=9925 +ENV HTTP_PORT=9926 + +# TODO Setup HarperDB to rootpath /hdb/ + +COPY harperdb-base-component /hdb/components/harperdb-base-component + +ARG NODE_MAJOR + +FROM node-base-${NODE_MAJOR} + +ARG NEXT_MAJOR + +COPY next-${NEXT_MAJOR} /hdb/components/next-${NEXT_MAJOR} + +EXPOSE 9925 +EXPOSE 9926 + +CMD ["harperdb", "run"] diff --git a/test/integration/harperdb-base-component/config.yaml b/test/fixtures/harperdb-base-component/config.yaml similarity index 100% rename from test/integration/harperdb-base-component/config.yaml rename to test/fixtures/harperdb-base-component/config.yaml diff --git a/test/integration/harperdb-base-component/package.json b/test/fixtures/harperdb-base-component/package.json similarity index 100% rename from test/integration/harperdb-base-component/package.json rename to test/fixtures/harperdb-base-component/package.json diff --git a/test/integration/harperdb-base-component/resources.js b/test/fixtures/harperdb-base-component/resources.js similarity index 100% rename from test/integration/harperdb-base-component/resources.js rename to test/fixtures/harperdb-base-component/resources.js diff --git a/test/integration/harperdb-base-component/schema.graphql b/test/fixtures/harperdb-base-component/schema.graphql similarity index 100% rename from test/integration/harperdb-base-component/schema.graphql rename to test/fixtures/harperdb-base-component/schema.graphql diff --git a/test/integration/next-10/index.js b/test/fixtures/next-10/index.js similarity index 100% rename from test/integration/next-10/index.js rename to test/fixtures/next-10/index.js diff --git a/test/integration/next-10/package.json b/test/fixtures/next-10/package.json similarity index 100% rename from test/integration/next-10/package.json rename to test/fixtures/next-10/package.json diff --git a/test/integration/next-11/index.js b/test/fixtures/next-11/index.js similarity index 100% rename from test/integration/next-11/index.js rename to test/fixtures/next-11/index.js diff --git a/test/integration/next-11/package.json b/test/fixtures/next-11/package.json similarity index 100% rename from test/integration/next-11/package.json rename to test/fixtures/next-11/package.json diff --git a/test/integration/next-12/index.js b/test/fixtures/next-12/index.js similarity index 100% rename from test/integration/next-12/index.js rename to test/fixtures/next-12/index.js diff --git a/test/integration/next-12/package.json b/test/fixtures/next-12/package.json similarity index 100% rename from test/integration/next-12/package.json rename to test/fixtures/next-12/package.json diff --git a/test/integration/next-13/index.js b/test/fixtures/next-13/index.js similarity index 100% rename from test/integration/next-13/index.js rename to test/fixtures/next-13/index.js diff --git a/test/integration/next-13/package.json b/test/fixtures/next-13/package.json similarity index 100% rename from test/integration/next-13/package.json rename to test/fixtures/next-13/package.json diff --git a/test/integration/next-14/index.js b/test/fixtures/next-14/index.js similarity index 100% rename from test/integration/next-14/index.js rename to test/fixtures/next-14/index.js diff --git a/test/integration/next-14/package.json b/test/fixtures/next-14/package.json similarity index 100% rename from test/integration/next-14/package.json rename to test/fixtures/next-14/package.json diff --git a/test/integration/next-15/index.js b/test/fixtures/next-15/index.js similarity index 100% rename from test/integration/next-15/index.js rename to test/fixtures/next-15/index.js diff --git a/test/integration/next-15/package.json b/test/fixtures/next-15/package.json similarity index 100% rename from test/integration/next-15/package.json rename to test/fixtures/next-15/package.json diff --git a/test/integration/next-9/index.js b/test/fixtures/next-9/index.js similarity index 100% rename from test/integration/next-9/index.js rename to test/fixtures/next-9/index.js diff --git a/test/integration/next-9/package.json b/test/fixtures/next-9/package.json similarity index 100% rename from test/integration/next-9/package.json rename to test/fixtures/next-9/package.json diff --git a/test/integration/Dockerfile b/test/integration/Dockerfile deleted file mode 100644 index 3985940..0000000 --- a/test/integration/Dockerfile +++ /dev/null @@ -1,24 +0,0 @@ -ARG NODE_MAJOR - -FROM node:${NODE_MAJOR} as node-base-${NODE_MAJOR} - -RUN apt-get update && apt-get install -y \ - curl \ - && rm -rf /var/lib/apt/lists/* - -RUN npm install -g harperdb - -# TODO Setup HarperDB to rootpath /hdb/ - -COPY harperdb-base-component/ ./hdb/components/ - -ARG NODE_MAJOR - -FROM node-base-${NODE_MAJOR} - -ARG NEXT_MAJOR - -COPY next-${NEXT_MAJOR}/ ./hdb/components/ - -EXPOSE 3000 -CMD ["node", "template/index.js"] \ No newline at end of file diff --git a/test/next-15-node-20.test.js b/test/next-15-node-20.test.js new file mode 100644 index 0000000..66bf949 --- /dev/null +++ b/test/next-15-node-20.test.js @@ -0,0 +1,28 @@ +import { suite, test, before, after } from 'node:test'; +import { deepStrictEqual } from 'node:assert'; +import { once } from 'node:events'; +import { Fixture } from './util.js'; + +suite('Next.js v15 - Node.js v20', (t) => { + before(async () => { + t.fixture = new Fixture({ nextMajor: 15, nodeMajor: 20 }); + + await once(t.fixture, 'ready'); + }); + + test('should run base component', async (t) => { + const response = await fetch('http://localhost:9926/Dog/0', { + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Basic aGRiX2FkbWluOnBhc3N3b3Jk', + }, + }); + const json = await response.json(); + + deepStrictEqual(json, { id: '0', name: 'Lincoln', breed: 'Shepherd' }); + }); + + after(() => { + t.fixture.cleanup(); + }); +}); diff --git a/test/util.js b/test/util.js new file mode 100644 index 0000000..a1db2d0 --- /dev/null +++ b/test/util.js @@ -0,0 +1,93 @@ +import child_process from 'node:child_process'; +import EventEmitter from 'node:events'; + +const FIXTURE_PATH_URL = new URL('./fixtures/', import.meta.url); + +const CONTAINER_ENGINE_LIST = ['podman', 'docker']; + +function getContainerEngine() { + for (const containerEngine of CONTAINER_ENGINE_LIST) { + const { status } = child_process.spawnSync(containerEngine, ['--version'], { stdio: 'ignore' }); + if (status === 0) { + return containerEngine; + } + } + + throw new Error(`No container engine found in ${CONTAINER_ENGINE_LIST.join(', ')}`); +} + +function clearContainer({ containerEngine, containerName }) { + const { stdout } = child_process.spawnSync(containerEngine, ['ps', '-aq', '-f', `name=${containerName}`]); + if (stdout !== '') { + child_process.spawnSync(containerEngine, ['stop', containerName], { stdio: 'ignore' }); + child_process.spawnSync(containerEngine, ['rm', containerName], { stdio: 'ignore' }); + } +} + +function buildContainer({ nextMajor, nodeMajor, containerEngine }) { + if (!containerEngine) { + containerEngine = getContainerEngine(); + } + + const imageName = `hdb-next-integration-test-image-next-${nextMajor}-node-${nodeMajor}`; + + console.log(`๐Ÿ—๏ธ Building ${imageName}...`); + + child_process.spawnSync( + containerEngine, + ['build', '--build-arg', `NEXT_MAJOR=${nextMajor}`, '--build-arg', `NODE_MAJOR=${nodeMajor}`, '-t', imageName, '.'], + { cwd: FIXTURE_PATH_URL, stdio: process.env.DEBUG === '1' ? 'inherit' : 'ignore' } + ); + + console.log(`๐Ÿ—๏ธ Build complete!`); + + return imageName; +} + +function runContainer({ nextMajor, nodeMajor, imageName, containerEngine }) { + if (!containerEngine) { + containerEngine = getContainerEngine(); + } + + const containerName = `hdb-next-integration-test-container-next-${nextMajor}-node-${nodeMajor}`; + + clearContainer({ containerEngine, containerName }); + + const runProcess = child_process.spawn( + containerEngine, + ['run', '-p', '9925:9925', '-p', '9926:9926', '--name', containerName, imageName] /*, { + stdio: process.env.DEBUG ? 'inherit' : 'ignore' + }*/ + ); + + return { containerName, runProcess }; +} + +export class Fixture extends EventEmitter { + constructor({ nextMajor, nodeMajor }) { + super(); + + this.containerEngine = getContainerEngine(); + + this.imageName = buildContainer({ nextMajor, nodeMajor, containerEngine: this.containerEngine }); + + const { containerName, runProcess } = runContainer({ + imageName: this.imageName, + containerName: this.containerName, + containerEngine: this.containerEngine, + }); + + this.containerName = containerName; + this.runProcess = runProcess; + + this.runProcess.stdout.on('data', (data) => { + if (data.toString().includes('HarperDB 4.4.5 successfully started')) { + this.emit('ready'); + } + }); + } + + cleanup() { + clearContainer({ containerEngine: this.containerEngine, containerName: this.containerName }); + } +} From 8a7bd2c34d0fb0003897394aa7b2e14ee7b57ed5 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Tue, 26 Nov 2024 16:55:49 -0700 Subject: [PATCH 14/32] try out some concurrency --- package.json | 2 +- test/next-15-node-18.test.js | 29 +++++++++++++++++++++++++++++ test/next-15-node-20.test.js | 19 ++++++++++--------- test/next-15-node-22.test.js | 29 +++++++++++++++++++++++++++++ test/util.js | 32 ++++++++++++++++++++++---------- 5 files changed, 91 insertions(+), 20 deletions(-) create mode 100644 test/next-15-node-18.test.js create mode 100644 test/next-15-node-22.test.js diff --git a/package.json b/package.json index 8c77644..e55e017 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ ], "scripts": { "format": "prettier --write .", - "test": "node --test \"test/**/*.test.js\"" + "test": "node --test --test-concurrency=3 --experimental-test-isolation=process \"test/**/*.test.js\"" }, "dependencies": { "shell-quote": "^1.8.1" diff --git a/test/next-15-node-18.test.js b/test/next-15-node-18.test.js new file mode 100644 index 0000000..18e90b6 --- /dev/null +++ b/test/next-15-node-18.test.js @@ -0,0 +1,29 @@ +import { suite, test, before, after } from 'node:test'; +import { once } from 'node:events'; +import { Fixture } from './util.js'; + +suite('Next.js v15 - Node.js v18', async () => { + const ctx = {}; + + before(async () => { + ctx.fixture = new Fixture({ nextMajor: '15', nodeMajor: '18' }); + await once(ctx.fixture, 'ready'); + ctx.rest = `http://${ctx.fixture.portMap.get('9926')}`; + }); + + await test('should run base component', async (t) => { + const response = await fetch(`${ctx.rest}/Dog/0`, { + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Basic aGRiX2FkbWluOnBhc3N3b3Jk', + }, + }); + const json = await response.json(); + + t.assert.deepStrictEqual(json, { id: '0', name: 'Lincoln', breed: 'Shepherd' }); + }); + + after(() => { + ctx.fixture.cleanup(); + }); +}); diff --git a/test/next-15-node-20.test.js b/test/next-15-node-20.test.js index 66bf949..4cf6730 100644 --- a/test/next-15-node-20.test.js +++ b/test/next-15-node-20.test.js @@ -1,17 +1,18 @@ import { suite, test, before, after } from 'node:test'; -import { deepStrictEqual } from 'node:assert'; import { once } from 'node:events'; import { Fixture } from './util.js'; -suite('Next.js v15 - Node.js v20', (t) => { - before(async () => { - t.fixture = new Fixture({ nextMajor: 15, nodeMajor: 20 }); +suite('Next.js v15 - Node.js v20', async () => { + const ctx = {}; - await once(t.fixture, 'ready'); + before(async () => { + ctx.fixture = new Fixture({ nextMajor: '15', nodeMajor: '20' }); + await once(ctx.fixture, 'ready'); + ctx.rest = `http://${ctx.fixture.portMap.get('9926')}`; }); - test('should run base component', async (t) => { - const response = await fetch('http://localhost:9926/Dog/0', { + await test('should run base component', async (t) => { + const response = await fetch(`${ctx.rest}/Dog/0`, { headers: { 'Content-Type': 'application/json', 'Authorization': 'Basic aGRiX2FkbWluOnBhc3N3b3Jk', @@ -19,10 +20,10 @@ suite('Next.js v15 - Node.js v20', (t) => { }); const json = await response.json(); - deepStrictEqual(json, { id: '0', name: 'Lincoln', breed: 'Shepherd' }); + t.assert.deepStrictEqual(json, { id: '0', name: 'Lincoln', breed: 'Shepherd' }); }); after(() => { - t.fixture.cleanup(); + ctx.fixture.cleanup(); }); }); diff --git a/test/next-15-node-22.test.js b/test/next-15-node-22.test.js new file mode 100644 index 0000000..d3d2fd1 --- /dev/null +++ b/test/next-15-node-22.test.js @@ -0,0 +1,29 @@ +import { suite, test, before, after } from 'node:test'; +import { once } from 'node:events'; +import { Fixture } from './util.js'; + +suite('Next.js v15 - Node.js v22', async () => { + const ctx = {}; + + before(async () => { + ctx.fixture = new Fixture({ nextMajor: '15', nodeMajor: '22' }); + await once(ctx.fixture, 'ready'); + ctx.rest = `http://${ctx.fixture.portMap.get('9926')}`; + }); + + await test('should run base component', async (t) => { + const response = await fetch(`${ctx.rest}/Dog/0`, { + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Basic aGRiX2FkbWluOnBhc3N3b3Jk', + }, + }); + const json = await response.json(); + + t.assert.deepStrictEqual(json, { id: '0', name: 'Lincoln', breed: 'Shepherd' }); + }); + + after(() => { + ctx.fixture.cleanup(); + }); +}); diff --git a/test/util.js b/test/util.js index a1db2d0..efd4b0c 100644 --- a/test/util.js +++ b/test/util.js @@ -31,7 +31,7 @@ function buildContainer({ nextMajor, nodeMajor, containerEngine }) { const imageName = `hdb-next-integration-test-image-next-${nextMajor}-node-${nodeMajor}`; - console.log(`๐Ÿ—๏ธ Building ${imageName}...`); + // console.log(`๐Ÿ—๏ธ Building ${imageName}...`); child_process.spawnSync( containerEngine, @@ -39,11 +39,20 @@ function buildContainer({ nextMajor, nodeMajor, containerEngine }) { { cwd: FIXTURE_PATH_URL, stdio: process.env.DEBUG === '1' ? 'inherit' : 'ignore' } ); - console.log(`๐Ÿ—๏ธ Build complete!`); + // console.log(`๐Ÿ—๏ธ Build complete!`); return imageName; } +function determinePortMapping({ containerName, containerEngine }) { + const portMap = new Map(); + for (const port of ['9925', '9926']) { + const { stdout } = child_process.spawnSync(containerEngine, ['port', containerName, port]); + portMap.set(port, stdout.toString().trim()); + } + return portMap; +} + function runContainer({ nextMajor, nodeMajor, imageName, containerEngine }) { if (!containerEngine) { containerEngine = getContainerEngine(); @@ -53,12 +62,7 @@ function runContainer({ nextMajor, nodeMajor, imageName, containerEngine }) { clearContainer({ containerEngine, containerName }); - const runProcess = child_process.spawn( - containerEngine, - ['run', '-p', '9925:9925', '-p', '9926:9926', '--name', containerName, imageName] /*, { - stdio: process.env.DEBUG ? 'inherit' : 'ignore' - }*/ - ); + const runProcess = child_process.spawn(containerEngine, ['run', '-P', '--name', containerName, imageName]); return { containerName, runProcess }; } @@ -71,9 +75,10 @@ export class Fixture extends EventEmitter { this.imageName = buildContainer({ nextMajor, nodeMajor, containerEngine: this.containerEngine }); - const { containerName, runProcess } = runContainer({ + const { containerName, runProcess, portMap } = runContainer({ + nextMajor, + nodeMajor, imageName: this.imageName, - containerName: this.containerName, containerEngine: this.containerEngine, }); @@ -82,6 +87,11 @@ export class Fixture extends EventEmitter { this.runProcess.stdout.on('data', (data) => { if (data.toString().includes('HarperDB 4.4.5 successfully started')) { + this.portMap = determinePortMapping({ + containerName: this.containerName, + containerEngine: this.containerEngine, + }); + this.emit('ready'); } }); @@ -91,3 +101,5 @@ export class Fixture extends EventEmitter { clearContainer({ containerEngine: this.containerEngine, containerName: this.containerName }); } } + +// const f = new Fixture({ nextMajor: '15', nodeMajor: '20' }); From 808280a8a38292102d8d44392772cbb81b001cb8 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Wed, 27 Nov 2024 12:43:47 -0700 Subject: [PATCH 15/32] add debugging and make operations non-blocking --- package.json | 2 +- test/next-15-node-18.test.js | 7 +- test/next-15-node-20.test.js | 7 +- test/next-15-node-22.test.js | 8 +- test/util.js | 245 +++++++++++++++++++++++++---------- 5 files changed, 185 insertions(+), 84 deletions(-) diff --git a/package.json b/package.json index e55e017..8c77644 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ ], "scripts": { "format": "prettier --write .", - "test": "node --test --test-concurrency=3 --experimental-test-isolation=process \"test/**/*.test.js\"" + "test": "node --test \"test/**/*.test.js\"" }, "dependencies": { "shell-quote": "^1.8.1" diff --git a/test/next-15-node-18.test.js b/test/next-15-node-18.test.js index 18e90b6..1109e36 100644 --- a/test/next-15-node-18.test.js +++ b/test/next-15-node-18.test.js @@ -1,5 +1,4 @@ import { suite, test, before, after } from 'node:test'; -import { once } from 'node:events'; import { Fixture } from './util.js'; suite('Next.js v15 - Node.js v18', async () => { @@ -7,7 +6,7 @@ suite('Next.js v15 - Node.js v18', async () => { before(async () => { ctx.fixture = new Fixture({ nextMajor: '15', nodeMajor: '18' }); - await once(ctx.fixture, 'ready'); + await ctx.fixture.ready; ctx.rest = `http://${ctx.fixture.portMap.get('9926')}`; }); @@ -23,7 +22,7 @@ suite('Next.js v15 - Node.js v18', async () => { t.assert.deepStrictEqual(json, { id: '0', name: 'Lincoln', breed: 'Shepherd' }); }); - after(() => { - ctx.fixture.cleanup(); + after(async () => { + await ctx.fixture.clear(); }); }); diff --git a/test/next-15-node-20.test.js b/test/next-15-node-20.test.js index 4cf6730..988092a 100644 --- a/test/next-15-node-20.test.js +++ b/test/next-15-node-20.test.js @@ -1,5 +1,4 @@ import { suite, test, before, after } from 'node:test'; -import { once } from 'node:events'; import { Fixture } from './util.js'; suite('Next.js v15 - Node.js v20', async () => { @@ -7,7 +6,7 @@ suite('Next.js v15 - Node.js v20', async () => { before(async () => { ctx.fixture = new Fixture({ nextMajor: '15', nodeMajor: '20' }); - await once(ctx.fixture, 'ready'); + await ctx.fixture.ready; ctx.rest = `http://${ctx.fixture.portMap.get('9926')}`; }); @@ -23,7 +22,7 @@ suite('Next.js v15 - Node.js v20', async () => { t.assert.deepStrictEqual(json, { id: '0', name: 'Lincoln', breed: 'Shepherd' }); }); - after(() => { - ctx.fixture.cleanup(); + after(async () => { + await ctx.fixture.clear(); }); }); diff --git a/test/next-15-node-22.test.js b/test/next-15-node-22.test.js index d3d2fd1..2ecc906 100644 --- a/test/next-15-node-22.test.js +++ b/test/next-15-node-22.test.js @@ -1,5 +1,5 @@ import { suite, test, before, after } from 'node:test'; -import { once } from 'node:events'; + import { Fixture } from './util.js'; suite('Next.js v15 - Node.js v22', async () => { @@ -7,7 +7,7 @@ suite('Next.js v15 - Node.js v22', async () => { before(async () => { ctx.fixture = new Fixture({ nextMajor: '15', nodeMajor: '22' }); - await once(ctx.fixture, 'ready'); + await ctx.fixture.ready; ctx.rest = `http://${ctx.fixture.portMap.get('9926')}`; }); @@ -23,7 +23,7 @@ suite('Next.js v15 - Node.js v22', async () => { t.assert.deepStrictEqual(json, { id: '0', name: 'Lincoln', breed: 'Shepherd' }); }); - after(() => { - ctx.fixture.cleanup(); + after(async () => { + await ctx.fixture.clear(); }); }); diff --git a/test/util.js b/test/util.js index efd4b0c..f2d9678 100644 --- a/test/util.js +++ b/test/util.js @@ -1,105 +1,208 @@ import child_process from 'node:child_process'; -import EventEmitter from 'node:events'; +import EventEmitter, { once } from 'node:events'; +import { Transform } from 'node:stream'; -const FIXTURE_PATH_URL = new URL('./fixtures/', import.meta.url); +class CollectOutput extends Transform { + constructor() { + super(); + this.chunks = []; + } + + _transform(chunk, encoding, callback) { + this.chunks.push(chunk); + callback(null, chunk); + } +} -const CONTAINER_ENGINE_LIST = ['podman', 'docker']; +export class Fixture { + static CONTAINER_ENGINE_LIST = ['podman', 'docker']; + static FIXTURE_PATH_URL = new URL('./fixtures/', import.meta.url); -function getContainerEngine() { - for (const containerEngine of CONTAINER_ENGINE_LIST) { - const { status } = child_process.spawnSync(containerEngine, ['--version'], { stdio: 'ignore' }); - if (status === 0) { - return containerEngine; + /** @type {string} */ + #containerEngine; + + #readyResolve; + #readyReject; + + constructor({ nextMajor, nodeMajor, debug = false, autoSetup = true }) { + if (!nextMajor || !nodeMajor) { + throw new Error(`Fixture options nextMajor and nodeMajor are required`); } - } - throw new Error(`No container engine found in ${CONTAINER_ENGINE_LIST.join(', ')}`); -} + this.nextMajor = nextMajor; + this.nodeMajor = nodeMajor; -function clearContainer({ containerEngine, containerName }) { - const { stdout } = child_process.spawnSync(containerEngine, ['ps', '-aq', '-f', `name=${containerName}`]); - if (stdout !== '') { - child_process.spawnSync(containerEngine, ['stop', containerName], { stdio: 'ignore' }); - child_process.spawnSync(containerEngine, ['rm', containerName], { stdio: 'ignore' }); - } -} + this.debug = debug; -function buildContainer({ nextMajor, nodeMajor, containerEngine }) { - if (!containerEngine) { - containerEngine = getContainerEngine(); - } + this.imageName = `hdb-next-integration-test-image-next-${nextMajor}-node-${nodeMajor}`; + this.containerName = `hdb-next-integration-test-container-next-${nextMajor}-node-${nodeMajor}`; - const imageName = `hdb-next-integration-test-image-next-${nextMajor}-node-${nodeMajor}`; + if (autoSetup) { + this.ready = new Promise((resolve, reject) => { + this.#readyResolve = resolve; + this.#readyReject = reject; + }); + this.clear() + .then(() => this.build()) + .then(() => this.run()) + .then(this.#readyResolve, this.#readyReject); + } + } - // console.log(`๐Ÿ—๏ธ Building ${imageName}...`); + get containerEngine() { + if (this.#containerEngine) { + return this.#containerEngine; + } - child_process.spawnSync( - containerEngine, - ['build', '--build-arg', `NEXT_MAJOR=${nextMajor}`, '--build-arg', `NODE_MAJOR=${nodeMajor}`, '-t', imageName, '.'], - { cwd: FIXTURE_PATH_URL, stdio: process.env.DEBUG === '1' ? 'inherit' : 'ignore' } - ); + for (const containerEngine of Fixture.CONTAINER_ENGINE_LIST) { + const { status } = child_process.spawnSync(containerEngine, ['--version'], { stdio: 'ignore' }); + if (status === 0) { + return (this.#containerEngine = containerEngine); + } + } - // console.log(`๐Ÿ—๏ธ Build complete!`); + throw new Error(`No container engine found in ${CONTAINER_ENGINE_LIST.join(', ')}`); + } - return imageName; -} + get #stdio() { + return ['ignore', this.debug ? 'inherit' : 'ignore', this.debug ? 'inherit' : 'ignore']; + } -function determinePortMapping({ containerName, containerEngine }) { - const portMap = new Map(); - for (const port of ['9925', '9926']) { - const { stdout } = child_process.spawnSync(containerEngine, ['port', containerName, port]); - portMap.set(port, stdout.toString().trim()); + build() { + return new Promise((resolve, reject) => { + const buildProcess = child_process.spawn( + this.containerEngine, + [ + 'build', + '--build-arg', + `NEXT_MAJOR=${this.nextMajor}`, + '--build-arg', + `NODE_MAJOR=${this.nodeMajor}`, + '-t', + this.imageName, + '.', + ], + { + cwd: Fixture.FIXTURE_PATH_URL, + stdio: this.#stdio, + } + ); + + buildProcess.on('error', reject); + + buildProcess.on('exit', (code) => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`\`${this.containerEngine} build\` exited with code ${code}`)); + } + }); + }); } - return portMap; -} -function runContainer({ nextMajor, nodeMajor, imageName, containerEngine }) { - if (!containerEngine) { - containerEngine = getContainerEngine(); + stop() { + return new Promise((resolve, reject) => { + const stopProcess = child_process.spawn(this.containerEngine, ['stop', this.containerName], { + stdio: this.#stdio, + }); + + stopProcess.on('error', reject); + + stopProcess.on('exit', (code) => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`\`${this.containerEngine} stop\` exited with code ${code}`)); + } + }); + }); } - const containerName = `hdb-next-integration-test-container-next-${nextMajor}-node-${nodeMajor}`; + rm() { + return new Promise((resolve, reject) => { + const rmProcess = child_process.spawn(this.containerEngine, ['rm', this.containerName], { stdio: this.#stdio }); - clearContainer({ containerEngine, containerName }); + rmProcess.on('error', reject); - const runProcess = child_process.spawn(containerEngine, ['run', '-P', '--name', containerName, imageName]); + rmProcess.on('exit', (code) => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`\`${this.containerEngine} rm\` exited with code ${code}`)); + } + }); + }); + } - return { containerName, runProcess }; -} + clear() { + return new Promise((resolve, reject) => { + const psProcess = child_process.spawn(this.containerEngine, ['ps', '-aq', '-f', `name=${this.containerName}`]); -export class Fixture extends EventEmitter { - constructor({ nextMajor, nodeMajor }) { - super(); + psProcess.on('error', reject); - this.containerEngine = getContainerEngine(); + const collectedStdout = psProcess.stdout.pipe(new CollectOutput()); - this.imageName = buildContainer({ nextMajor, nodeMajor, containerEngine: this.containerEngine }); + if (this.debug) { + collectedStdout.pipe(process.stdout); + psProcess.stderr.pipe(process.stderr); + } - const { containerName, runProcess, portMap } = runContainer({ - nextMajor, - nodeMajor, - imageName: this.imageName, - containerEngine: this.containerEngine, + psProcess.on('exit', (code) => { + if (code === 0) { + if (collectedStdout.chunks.length !== 0) { + this.stop() + .then(() => this.rm()) + .then(resolve, reject); + } + resolve(); + } else { + reject(new Error(`\`${this.containerEngine} ps\` exited with code ${code}`)); + } + }); }); + } - this.containerName = containerName; - this.runProcess = runProcess; + run() { + return new Promise((resolve, reject) => { + const runProcess = child_process.spawn( + this.containerEngine, + ['run', '-P', '--name', this.containerName, this.imageName], + { stdio: ['ignore', 'pipe', this.debug ? 'inherit' : 'ignore'] } + ); + const resolveReady = this.#readyResolve; + const stdout = runProcess.stdout.pipe( + new Transform({ + transform(chunk, encoding, callback) { + if (chunk.toString().includes('HarperDB 4.4.5 successfully started')) { + resolveReady(); + } + callback(null, chunk); + }, + }) + ); + + if (this.debug) { + stdout.pipe(process.stdout); + } - this.runProcess.stdout.on('data', (data) => { - if (data.toString().includes('HarperDB 4.4.5 successfully started')) { - this.portMap = determinePortMapping({ - containerName: this.containerName, - containerEngine: this.containerEngine, - }); + runProcess.on('error', reject); - this.emit('ready'); - } + runProcess.on('exit', (code) => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`\`${this.containerEngine} run\` exited with code ${code}`)); + } + }); }); } - cleanup() { - clearContainer({ containerEngine: this.containerEngine, containerName: this.containerName }); + get portMap() { + const portMap = new Map(); + for (const port of ['9925', '9926']) { + const { stdout } = child_process.spawnSync(this.containerEngine, ['port', this.containerName, port]); + portMap.set(port, stdout.toString().trim()); + } + return portMap; } } - -// const f = new Fixture({ nextMajor: '15', nodeMajor: '20' }); From 4041498bd6170b7475c0f72399dbe32fa8d587dc Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Wed, 27 Nov 2024 12:52:38 -0700 Subject: [PATCH 16/32] DRY --- test/util.js | 79 ++++++++++++++++++++-------------------------------- 1 file changed, 30 insertions(+), 49 deletions(-) diff --git a/test/util.js b/test/util.js index f2d9678..0e9a1db 100644 --- a/test/util.js +++ b/test/util.js @@ -68,70 +68,51 @@ export class Fixture { return ['ignore', this.debug ? 'inherit' : 'ignore', this.debug ? 'inherit' : 'ignore']; } - build() { - return new Promise((resolve, reject) => { - const buildProcess = child_process.spawn( - this.containerEngine, - [ - 'build', - '--build-arg', - `NEXT_MAJOR=${this.nextMajor}`, - '--build-arg', - `NODE_MAJOR=${this.nodeMajor}`, - '-t', - this.imageName, - '.', - ], - { - cwd: Fixture.FIXTURE_PATH_URL, - stdio: this.#stdio, - } - ); - - buildProcess.on('error', reject); - - buildProcess.on('exit', (code) => { - if (code === 0) { - resolve(); - } else { - reject(new Error(`\`${this.containerEngine} build\` exited with code ${code}`)); - } - }); - }); - } - - stop() { + #runCommand(args = [], options = {}) { return new Promise((resolve, reject) => { - const stopProcess = child_process.spawn(this.containerEngine, ['stop', this.containerName], { + const childProcess = child_process.spawn(this.containerEngine, args, { stdio: this.#stdio, + ...options, }); - stopProcess.on('error', reject); + childProcess.on('error', reject); - stopProcess.on('exit', (code) => { + childProcess.on('exit', (code) => { if (code === 0) { resolve(); } else { - reject(new Error(`\`${this.containerEngine} stop\` exited with code ${code}`)); + reject( + new Error(`\`${this.containerEngine}${args.length > 0 ? ` ${args[1]}` : ''}\` exited with code ${code}`) + ); } }); }); } - rm() { - return new Promise((resolve, reject) => { - const rmProcess = child_process.spawn(this.containerEngine, ['rm', this.containerName], { stdio: this.#stdio }); + build() { + return this.#runCommand( + [ + 'build', + '--build-arg', + `NEXT_MAJOR=${this.nextMajor}`, + '--build-arg', + `NODE_MAJOR=${this.nodeMajor}`, + '-t', + this.imageName, + '.', + ], + { + cwd: Fixture.FIXTURE_PATH_URL, + } + ); + } - rmProcess.on('error', reject); + stop() { + return this.#runCommand(['stop', this.containerName]); + } - rmProcess.on('exit', (code) => { - if (code === 0) { - resolve(); - } else { - reject(new Error(`\`${this.containerEngine} rm\` exited with code ${code}`)); - } - }); - }); + rm() { + return this.#runCommand(['rm', this.containerName]); } clear() { From dc290a6f744ebec9f17ffce81e9ae7ddb5190c67 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Sat, 30 Nov 2024 12:35:30 -0700 Subject: [PATCH 17/32] progress --- .github/workflows/test.yaml | 27 ++--- {test/fixtures => fixtures}/Dockerfile | 5 +- .../harperdb-base-component/config.yaml | 0 .../harperdb-base-component/package.json | 0 .../harperdb-base-component/resources.js | 0 .../harperdb-base-component/schema.graphql | 0 {test/fixtures => fixtures}/next-10/index.js | 0 .../next-10/package.json | 0 {test/fixtures => fixtures}/next-11/index.js | 0 .../next-11/package.json | 0 {test/fixtures => fixtures}/next-12/index.js | 0 .../next-12/package.json | 0 {test/fixtures => fixtures}/next-13/index.js | 0 .../next-13/package.json | 0 {test/fixtures => fixtures}/next-14/index.js | 0 .../next-14/package.json | 0 {test/fixtures => fixtures}/next-15/index.js | 0 .../next-15/package.json | 0 {test/fixtures => fixtures}/next-9/index.js | 0 .../fixtures => fixtures}/next-9/package.json | 0 package.json | 6 +- test/next-15-node-18.test.js | 3 +- test/next-15-node-20.test.js | 2 +- test/next-15-node-22.test.js | 3 +- test/util.js => util/fixture.js | 100 +++++++----------- 25 files changed, 61 insertions(+), 85 deletions(-) rename {test/fixtures => fixtures}/Dockerfile (90%) rename {test/fixtures => fixtures}/harperdb-base-component/config.yaml (100%) rename {test/fixtures => fixtures}/harperdb-base-component/package.json (100%) rename {test/fixtures => fixtures}/harperdb-base-component/resources.js (100%) rename {test/fixtures => fixtures}/harperdb-base-component/schema.graphql (100%) rename {test/fixtures => fixtures}/next-10/index.js (100%) rename {test/fixtures => fixtures}/next-10/package.json (100%) rename {test/fixtures => fixtures}/next-11/index.js (100%) rename {test/fixtures => fixtures}/next-11/package.json (100%) rename {test/fixtures => fixtures}/next-12/index.js (100%) rename {test/fixtures => fixtures}/next-12/package.json (100%) rename {test/fixtures => fixtures}/next-13/index.js (100%) rename {test/fixtures => fixtures}/next-13/package.json (100%) rename {test/fixtures => fixtures}/next-14/index.js (100%) rename {test/fixtures => fixtures}/next-14/package.json (100%) rename {test/fixtures => fixtures}/next-15/index.js (100%) rename {test/fixtures => fixtures}/next-15/package.json (100%) rename {test/fixtures => fixtures}/next-9/index.js (100%) rename {test/fixtures => fixtures}/next-9/package.json (100%) rename test/util.js => util/fixture.js (66%) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 86842c0..af66ed1 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -7,20 +7,23 @@ on: branches: [main] jobs: - # Skip for now - if: ${{ false }} test: runs-on: ubuntu-latest - strategy: - matrix: - next-version: [9, 10, 11, 12, 13, 14, 15] - node-version: [16, 18, 20, 22] - # exclude: - # - next-version: 9 - # node-version: 18 - steps: - uses: actions/checkout@v4 - # TODO: invoke the integration container - # TODO: find interoperability with the scripts/run-integration-tests.js + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install dependencies + run: npm install + + - name: Check formatting + continue-on-error: true + run: npm run format:check + + - name: Run tests + run: npm run test diff --git a/test/fixtures/Dockerfile b/fixtures/Dockerfile similarity index 90% rename from test/fixtures/Dockerfile rename to fixtures/Dockerfile index fd6a557..921f3fb 100644 --- a/test/fixtures/Dockerfile +++ b/fixtures/Dockerfile @@ -17,8 +17,6 @@ ENV ROOTPATH=/hdb ENV OPERATIONSAPI_NETWORK_PORT=9925 ENV HTTP_PORT=9926 -# TODO Setup HarperDB to rootpath /hdb/ - COPY harperdb-base-component /hdb/components/harperdb-base-component ARG NODE_MAJOR @@ -29,7 +27,6 @@ ARG NEXT_MAJOR COPY next-${NEXT_MAJOR} /hdb/components/next-${NEXT_MAJOR} -EXPOSE 9925 -EXPOSE 9926 +EXPOSE 9925 9926 CMD ["harperdb", "run"] diff --git a/test/fixtures/harperdb-base-component/config.yaml b/fixtures/harperdb-base-component/config.yaml similarity index 100% rename from test/fixtures/harperdb-base-component/config.yaml rename to fixtures/harperdb-base-component/config.yaml diff --git a/test/fixtures/harperdb-base-component/package.json b/fixtures/harperdb-base-component/package.json similarity index 100% rename from test/fixtures/harperdb-base-component/package.json rename to fixtures/harperdb-base-component/package.json diff --git a/test/fixtures/harperdb-base-component/resources.js b/fixtures/harperdb-base-component/resources.js similarity index 100% rename from test/fixtures/harperdb-base-component/resources.js rename to fixtures/harperdb-base-component/resources.js diff --git a/test/fixtures/harperdb-base-component/schema.graphql b/fixtures/harperdb-base-component/schema.graphql similarity index 100% rename from test/fixtures/harperdb-base-component/schema.graphql rename to fixtures/harperdb-base-component/schema.graphql diff --git a/test/fixtures/next-10/index.js b/fixtures/next-10/index.js similarity index 100% rename from test/fixtures/next-10/index.js rename to fixtures/next-10/index.js diff --git a/test/fixtures/next-10/package.json b/fixtures/next-10/package.json similarity index 100% rename from test/fixtures/next-10/package.json rename to fixtures/next-10/package.json diff --git a/test/fixtures/next-11/index.js b/fixtures/next-11/index.js similarity index 100% rename from test/fixtures/next-11/index.js rename to fixtures/next-11/index.js diff --git a/test/fixtures/next-11/package.json b/fixtures/next-11/package.json similarity index 100% rename from test/fixtures/next-11/package.json rename to fixtures/next-11/package.json diff --git a/test/fixtures/next-12/index.js b/fixtures/next-12/index.js similarity index 100% rename from test/fixtures/next-12/index.js rename to fixtures/next-12/index.js diff --git a/test/fixtures/next-12/package.json b/fixtures/next-12/package.json similarity index 100% rename from test/fixtures/next-12/package.json rename to fixtures/next-12/package.json diff --git a/test/fixtures/next-13/index.js b/fixtures/next-13/index.js similarity index 100% rename from test/fixtures/next-13/index.js rename to fixtures/next-13/index.js diff --git a/test/fixtures/next-13/package.json b/fixtures/next-13/package.json similarity index 100% rename from test/fixtures/next-13/package.json rename to fixtures/next-13/package.json diff --git a/test/fixtures/next-14/index.js b/fixtures/next-14/index.js similarity index 100% rename from test/fixtures/next-14/index.js rename to fixtures/next-14/index.js diff --git a/test/fixtures/next-14/package.json b/fixtures/next-14/package.json similarity index 100% rename from test/fixtures/next-14/package.json rename to fixtures/next-14/package.json diff --git a/test/fixtures/next-15/index.js b/fixtures/next-15/index.js similarity index 100% rename from test/fixtures/next-15/index.js rename to fixtures/next-15/index.js diff --git a/test/fixtures/next-15/package.json b/fixtures/next-15/package.json similarity index 100% rename from test/fixtures/next-15/package.json rename to fixtures/next-15/package.json diff --git a/test/fixtures/next-9/index.js b/fixtures/next-9/index.js similarity index 100% rename from test/fixtures/next-9/index.js rename to fixtures/next-9/index.js diff --git a/test/fixtures/next-9/package.json b/fixtures/next-9/package.json similarity index 100% rename from test/fixtures/next-9/package.json rename to fixtures/next-9/package.json diff --git a/package.json b/package.json index 8c77644..2a1ed31 100644 --- a/package.json +++ b/package.json @@ -33,8 +33,10 @@ "schema.graphql" ], "scripts": { - "format": "prettier --write .", - "test": "node --test \"test/**/*.test.js\"" + "format": "prettier .", + "format:check": "npm run format -- --check", + "format:write": "npm run format -- --write", + "test": "node --test" }, "dependencies": { "shell-quote": "^1.8.1" diff --git a/test/next-15-node-18.test.js b/test/next-15-node-18.test.js index 1109e36..fd3c2d1 100644 --- a/test/next-15-node-18.test.js +++ b/test/next-15-node-18.test.js @@ -1,5 +1,6 @@ import { suite, test, before, after } from 'node:test'; -import { Fixture } from './util.js'; + +import { Fixture } from '../util/fixture.js'; suite('Next.js v15 - Node.js v18', async () => { const ctx = {}; diff --git a/test/next-15-node-20.test.js b/test/next-15-node-20.test.js index 988092a..b877872 100644 --- a/test/next-15-node-20.test.js +++ b/test/next-15-node-20.test.js @@ -1,5 +1,5 @@ import { suite, test, before, after } from 'node:test'; -import { Fixture } from './util.js'; +import { Fixture } from '../util/fixture.js'; suite('Next.js v15 - Node.js v20', async () => { const ctx = {}; diff --git a/test/next-15-node-22.test.js b/test/next-15-node-22.test.js index 2ecc906..a14148c 100644 --- a/test/next-15-node-22.test.js +++ b/test/next-15-node-22.test.js @@ -1,6 +1,5 @@ import { suite, test, before, after } from 'node:test'; - -import { Fixture } from './util.js'; +import { Fixture } from '../util/fixture.js'; suite('Next.js v15 - Node.js v22', async () => { const ctx = {}; diff --git a/test/util.js b/util/fixture.js similarity index 66% rename from test/util.js rename to util/fixture.js index 0e9a1db..51de91e 100644 --- a/test/util.js +++ b/util/fixture.js @@ -1,34 +1,16 @@ import child_process from 'node:child_process'; -import EventEmitter, { once } from 'node:events'; +import path from 'node:path'; import { Transform } from 'node:stream'; - -class CollectOutput extends Transform { - constructor() { - super(); - this.chunks = []; - } - - _transform(chunk, encoding, callback) { - this.chunks.push(chunk); - callback(null, chunk); - } -} +import { fileURLToPath } from 'node:url'; export class Fixture { static CONTAINER_ENGINE_LIST = ['podman', 'docker']; - static FIXTURE_PATH_URL = new URL('./fixtures/', import.meta.url); + static FIXTURE_PATH = fileURLToPath(new URL('../fixtures/', import.meta.url)); /** @type {string} */ #containerEngine; - #readyResolve; - #readyReject; - - constructor({ nextMajor, nodeMajor, debug = false, autoSetup = true }) { - if (!nextMajor || !nodeMajor) { - throw new Error(`Fixture options nextMajor and nodeMajor are required`); - } - + constructor({ autoSetup = true, debug = false, nextMajor, nodeMajor }) { this.nextMajor = nextMajor; this.nodeMajor = nodeMajor; @@ -38,14 +20,9 @@ export class Fixture { this.containerName = `hdb-next-integration-test-container-next-${nextMajor}-node-${nodeMajor}`; if (autoSetup) { - this.ready = new Promise((resolve, reject) => { - this.#readyResolve = resolve; - this.#readyReject = reject; - }); - this.clear() + this.ready = this.clear() .then(() => this.build()) - .then(() => this.run()) - .then(this.#readyResolve, this.#readyReject); + .then(() => this.run()); } } @@ -75,36 +52,29 @@ export class Fixture { ...options, }); - childProcess.on('error', reject); + childProcess.on('error', (error) => { + reject(error); + }); childProcess.on('exit', (code) => { - if (code === 0) { - resolve(); - } else { - reject( - new Error(`\`${this.containerEngine}${args.length > 0 ? ` ${args[1]}` : ''}\` exited with code ${code}`) - ); - } + resolve(code); }); }); } build() { - return this.#runCommand( - [ - 'build', - '--build-arg', - `NEXT_MAJOR=${this.nextMajor}`, - '--build-arg', - `NODE_MAJOR=${this.nodeMajor}`, - '-t', - this.imageName, - '.', - ], - { - cwd: Fixture.FIXTURE_PATH_URL, - } - ); + return this.#runCommand([ + 'build', + '--build-arg', + `NEXT_MAJOR=${this.nextMajor}`, + '--build-arg', + `NODE_MAJOR=${this.nodeMajor}`, + '-t', + this.imageName, + '-f', + path.join(Fixture.FIXTURE_PATH, 'Dockerfile'), + Fixture.FIXTURE_PATH, + ]); } stop() { @@ -121,7 +91,18 @@ export class Fixture { psProcess.on('error', reject); - const collectedStdout = psProcess.stdout.pipe(new CollectOutput()); + const collectedStdout = psProcess.stdout.pipe( + new Transform({ + construct(cb) { + this.chunks = []; + cb(null); + }, + transform(chunk, encoding, callback) { + this.chunks.push(chunk); + callback(null, chunk); + }, + }) + ); if (this.debug) { collectedStdout.pipe(process.stdout); @@ -150,12 +131,12 @@ export class Fixture { ['run', '-P', '--name', this.containerName, this.imageName], { stdio: ['ignore', 'pipe', this.debug ? 'inherit' : 'ignore'] } ); - const resolveReady = this.#readyResolve; + const stdout = runProcess.stdout.pipe( new Transform({ transform(chunk, encoding, callback) { if (chunk.toString().includes('HarperDB 4.4.5 successfully started')) { - resolveReady(); + resolve(); } callback(null, chunk); }, @@ -167,14 +148,7 @@ export class Fixture { } runProcess.on('error', reject); - - runProcess.on('exit', (code) => { - if (code === 0) { - resolve(); - } else { - reject(new Error(`\`${this.containerEngine} run\` exited with code ${code}`)); - } - }); + runProcess.on('exit', resolve); }); } From 25ceca379df817356701b2288dacdbdc0a2c6223 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Sat, 30 Nov 2024 12:43:02 -0700 Subject: [PATCH 18/32] ci? --- .github/workflows/test.yaml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index af66ed1..537fe36 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,4 +1,4 @@ -name: Next.js Version Matrix Test +name: Test on: push: @@ -17,13 +17,10 @@ jobs: uses: actions/setup-node@v4 with: node-version: '22' + cache: 'npm' - name: Install dependencies - run: npm install - - - name: Check formatting - continue-on-error: true - run: npm run format:check + run: npm ci - name: Run tests run: npm run test From 53469ca4b90ad3f7c8a5e63aed0e4a02230bd304 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Sat, 30 Nov 2024 12:54:05 -0700 Subject: [PATCH 19/32] debug in ci --- package.json | 2 +- util/fixture.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 2a1ed31..588f43d 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "format": "prettier .", "format:check": "npm run format -- --check", "format:write": "npm run format -- --write", - "test": "node --test" + "test": "DEBUG=1 node --test" }, "dependencies": { "shell-quote": "^1.8.1" diff --git a/util/fixture.js b/util/fixture.js index 51de91e..fde2bcc 100644 --- a/util/fixture.js +++ b/util/fixture.js @@ -14,7 +14,7 @@ export class Fixture { this.nextMajor = nextMajor; this.nodeMajor = nodeMajor; - this.debug = debug; + this.debug = debug || process.env.DEBUG === '1'; this.imageName = `hdb-next-integration-test-image-next-${nextMajor}-node-${nodeMajor}`; this.containerName = `hdb-next-integration-test-container-next-${nextMajor}-node-${nodeMajor}`; From e73ae757c7f77ae661f04dbc38730f53aabc33e1 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Sat, 30 Nov 2024 13:06:55 -0700 Subject: [PATCH 20/32] try localhost: --- fixtures/Dockerfile | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fixtures/Dockerfile b/fixtures/Dockerfile index 921f3fb..f7ec7ad 100644 --- a/fixtures/Dockerfile +++ b/fixtures/Dockerfile @@ -21,7 +21,7 @@ COPY harperdb-base-component /hdb/components/harperdb-base-component ARG NODE_MAJOR -FROM node-base-${NODE_MAJOR} +FROM localhost:node-base-${NODE_MAJOR} ARG NEXT_MAJOR diff --git a/package.json b/package.json index 588f43d..2a1ed31 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "format": "prettier .", "format:check": "npm run format -- --check", "format:write": "npm run format -- --write", - "test": "DEBUG=1 node --test" + "test": "node --test" }, "dependencies": { "shell-quote": "^1.8.1" From fcf0f9659f48353da56b34f4fa4e823a52bc32d2 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Sat, 30 Nov 2024 13:41:54 -0700 Subject: [PATCH 21/32] okay working(ish). component loading issue related to dep install. --- fixtures/Dockerfile | 2 +- fixtures/next-15/app/layout.js | 13 + fixtures/next-15/app/page.js | 7 + fixtures/next-15/config.yaml | 4 + fixtures/next-15/package-lock.json | 901 +++++++++++++++++++++++++++++ fixtures/next-15/package.json | 17 +- test/next-15-node-18.test.js | 15 +- util/fixture.js | 2 +- 8 files changed, 956 insertions(+), 5 deletions(-) create mode 100644 fixtures/next-15/app/layout.js create mode 100644 fixtures/next-15/app/page.js create mode 100644 fixtures/next-15/config.yaml create mode 100644 fixtures/next-15/package-lock.json diff --git a/fixtures/Dockerfile b/fixtures/Dockerfile index f7ec7ad..921f3fb 100644 --- a/fixtures/Dockerfile +++ b/fixtures/Dockerfile @@ -21,7 +21,7 @@ COPY harperdb-base-component /hdb/components/harperdb-base-component ARG NODE_MAJOR -FROM localhost:node-base-${NODE_MAJOR} +FROM node-base-${NODE_MAJOR} ARG NEXT_MAJOR diff --git a/fixtures/next-15/app/layout.js b/fixtures/next-15/app/layout.js new file mode 100644 index 0000000..9bd3c4b --- /dev/null +++ b/fixtures/next-15/app/layout.js @@ -0,0 +1,13 @@ +export const metadata = { + title: 'HarperDB - Next.js v15 App', +}; + +export default function RootLayout({ children }) { + return ( + + + {children} + + + ); +} diff --git a/fixtures/next-15/app/page.js b/fixtures/next-15/app/page.js new file mode 100644 index 0000000..4a48cf9 --- /dev/null +++ b/fixtures/next-15/app/page.js @@ -0,0 +1,7 @@ +export default async function Page() { + return ( +
+

Next.js v15

+
+ ); +} diff --git a/fixtures/next-15/config.yaml b/fixtures/next-15/config.yaml new file mode 100644 index 0000000..9cc50d3 --- /dev/null +++ b/fixtures/next-15/config.yaml @@ -0,0 +1,4 @@ +'@harperdb/nextjs': + package: '@harperdb/nextjs' + files: '/*' + port: 9926 diff --git a/fixtures/next-15/package-lock.json b/fixtures/next-15/package-lock.json new file mode 100644 index 0000000..0f8978f --- /dev/null +++ b/fixtures/next-15/package-lock.json @@ -0,0 +1,901 @@ +{ + "name": "next-15", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "next-15", + "dependencies": { + "@harperdb/nextjs": "^0.0.12", + "next": "15.0.3", + "react": "19.0.0-rc-66855b96-20241106", + "react-dom": "19.0.0-rc-66855b96-20241106" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", + "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@harperdb/nextjs": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@harperdb/nextjs/-/nextjs-0.0.12.tgz", + "integrity": "sha512-QClIwYACdhv+iVyvyrJiq5/IFbR/GN4P5CvEi7FdCsu2yT3S/dJkUVni03HMKWGEmlE1NfiwcvJtQb3m4Az1sA==", + "license": "MIT", + "dependencies": { + "shell-quote": "^1.8.1" + }, + "bin": { + "harperdb-nextjs": "cli.js" + } + }, + "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-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "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-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "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-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "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-s390x": "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-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "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-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "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-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "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/@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/@next/env": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.0.3.tgz", + "integrity": "sha512-t9Xy32pjNOvVn2AS+Utt6VmyrshbpfUMhIjFO60gI58deSo/KgLOp31XZ4O+kY/Is8WAGYwA5gR7kOb1eORDBA==", + "license": "MIT" + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.0.3.tgz", + "integrity": "sha512-s3Q/NOorCsLYdCKvQlWU+a+GeAd3C8Rb3L1YnetsgwXzhc3UTWrtQpB/3eCjFOdGUj5QmXfRak12uocd1ZiiQw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.0.3.tgz", + "integrity": "sha512-Zxl/TwyXVZPCFSf0u2BNj5sE0F2uR6iSKxWpq4Wlk/Sv9Ob6YCKByQTkV2y6BCic+fkabp9190hyrDdPA/dNrw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.0.3.tgz", + "integrity": "sha512-T5+gg2EwpsY3OoaLxUIofmMb7ohAUlcNZW0fPQ6YAutaWJaxt1Z1h+8zdl4FRIOr5ABAAhXtBcpkZNwUcKI2fw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.0.3.tgz", + "integrity": "sha512-WkAk6R60mwDjH4lG/JBpb2xHl2/0Vj0ZRu1TIzWuOYfQ9tt9NFsIinI1Epma77JVgy81F32X/AeD+B2cBu/YQA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.0.3.tgz", + "integrity": "sha512-gWL/Cta1aPVqIGgDb6nxkqy06DkwJ9gAnKORdHWX1QBbSZZB+biFYPFti8aKIQL7otCE1pjyPaXpFzGeG2OS2w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.0.3.tgz", + "integrity": "sha512-QQEMwFd8r7C0GxQS62Zcdy6GKx999I/rTO2ubdXEe+MlZk9ZiinsrjwoiBL5/57tfyjikgh6GOU2WRQVUej3UA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.0.3.tgz", + "integrity": "sha512-9TEp47AAd/ms9fPNgtgnT7F3M1Hf7koIYYWCMQ9neOwjbVWJsHZxrFbI3iEDJ8rf1TDGpmHbKxXf2IFpAvheIQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.0.3.tgz", + "integrity": "sha512-VNAz+HN4OGgvZs6MOoVfnn41kBzT+M+tB+OK4cww6DNyWS6wKaDpaAm/qLeOUbnMh0oVx1+mg0uoYARF69dJyA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "license": "Apache-2.0" + }, + "node_modules/@swc/helpers": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.13.tgz", + "integrity": "sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001684", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001684.tgz", + "integrity": "sha512-G1LRwLIQjBQoyq0ZJGqGIJUXzJ8irpbjHLpVRXDvBEScFJ9b17sgK6vlx0GAJFE21okD7zXl08rRRUfq6HdoEQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "optional": true, + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT", + "optional": true + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "optional": true, + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT", + "optional": true + }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/next": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/next/-/next-15.0.3.tgz", + "integrity": "sha512-ontCbCRKJUIoivAdGB34yCaOcPgYXr9AAkV/IwqFfWWTXEPUgLYkSkqBhIk9KK7gGmgjc64B+RdoeIDM13Irnw==", + "license": "MIT", + "dependencies": { + "@next/env": "15.0.3", + "@swc/counter": "0.1.3", + "@swc/helpers": "0.5.13", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "15.0.3", + "@next/swc-darwin-x64": "15.0.3", + "@next/swc-linux-arm64-gnu": "15.0.3", + "@next/swc-linux-arm64-musl": "15.0.3", + "@next/swc-linux-x64-gnu": "15.0.3", + "@next/swc-linux-x64-musl": "15.0.3", + "@next/swc-win32-arm64-msvc": "15.0.3", + "@next/swc-win32-x64-msvc": "15.0.3", + "sharp": "^0.33.5" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.41.2", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-66855b96-20241106", + "react-dom": "^18.2.0 || 19.0.0-rc-66855b96-20241106", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.0.0-rc-66855b96-20241106", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0-rc-66855b96-20241106.tgz", + "integrity": "sha512-klH7xkT71SxRCx4hb1hly5FJB21Hz0ACyxbXYAECEqssUjtJeFUAaI2U1DgJAzkGEnvEm3DkxuBchMC/9K4ipg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.0.0-rc-66855b96-20241106", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0-rc-66855b96-20241106.tgz", + "integrity": "sha512-D25vdaytZ1wFIRiwNU98NPQ/upS2P8Co4/oNoa02PzHbh8deWdepjm5qwZM/46OdSiGv4WSWwxP55RO9obqJEQ==", + "license": "MIT", + "dependencies": { + "scheduler": "0.25.0-rc-66855b96-20241106" + }, + "peerDependencies": { + "react": "19.0.0-rc-66855b96-20241106" + } + }, + "node_modules/scheduler": { + "version": "0.25.0-rc-66855b96-20241106", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0-rc-66855b96-20241106.tgz", + "integrity": "sha512-HQXp/Mnp/MMRSXMQF7urNFla+gmtXW/Gr1KliuR0iboTit4KvZRY8KYaq5ccCTAOJiUqQh2rE2F3wgUekmgdlA==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, + "node_modules/shell-quote": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", + "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "optional": true, + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + } + } +} diff --git a/fixtures/next-15/package.json b/fixtures/next-15/package.json index 0a07662..a4500c0 100644 --- a/fixtures/next-15/package.json +++ b/fixtures/next-15/package.json @@ -1,4 +1,17 @@ { "name": "next-15", - "type": "module" -} + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@harperdb/nextjs": "^0.0.12", + "react": "19.0.0-rc-66855b96-20241106", + "react-dom": "19.0.0-rc-66855b96-20241106", + "next": "15.0.3" + } + } + \ No newline at end of file diff --git a/test/next-15-node-18.test.js b/test/next-15-node-18.test.js index fd3c2d1..69f60ef 100644 --- a/test/next-15-node-18.test.js +++ b/test/next-15-node-18.test.js @@ -6,8 +6,10 @@ suite('Next.js v15 - Node.js v18', async () => { const ctx = {}; before(async () => { - ctx.fixture = new Fixture({ nextMajor: '15', nodeMajor: '18' }); + ctx.fixture = new Fixture({ nextMajor: '15', nodeMajor: '18',debug: true }); await ctx.fixture.ready; + console.log(ctx.fixture.portMap); + ctx.rest = `http://${ctx.fixture.portMap.get('9926')}`; }); @@ -23,6 +25,17 @@ suite('Next.js v15 - Node.js v18', async () => { t.assert.deepStrictEqual(json, { id: '0', name: 'Lincoln', breed: 'Shepherd' }); }); + await test('should reach home page', async (t) => { + const response = await fetch(`${ctx.rest}/`, { + headers: { + 'Content-Type': 'text/html', + } + }); + + const text = await response.text(); + t.assert.match(text, /Next\.js v15/); + }) + after(async () => { await ctx.fixture.clear(); }); diff --git a/util/fixture.js b/util/fixture.js index fde2bcc..9b2555d 100644 --- a/util/fixture.js +++ b/util/fixture.js @@ -135,7 +135,7 @@ export class Fixture { const stdout = runProcess.stdout.pipe( new Transform({ transform(chunk, encoding, callback) { - if (chunk.toString().includes('HarperDB 4.4.5 successfully started')) { + if (/HarperDB \d+.\d+.\d+ successfully started/.test(chunk.toString())) { resolve(); } callback(null, chunk); From ca16551f834f571ee7a993441b475511eaa44c86 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Sat, 30 Nov 2024 16:42:58 -0700 Subject: [PATCH 22/32] install install --- extension.js | 1 + fixtures/Dockerfile | 14 ++++++++++++-- fixtures/next-15/index.js | 12 ------------ fixtures/next-15/next.config.js | 1 + fixtures/next-15/package.json | 2 +- test/next-15-node-18.test.js | 6 ++---- test/next-15-node-20.test.js | 11 +++++++++++ test/next-15-node-22.test.js | 11 +++++++++++ util/fixture.js | 2 +- 9 files changed, 40 insertions(+), 20 deletions(-) delete mode 100644 fixtures/next-15/index.js create mode 100644 fixtures/next-15/next.config.js diff --git a/extension.js b/extension.js index f9fa1a9..0d5e5d6 100644 --- a/extension.js +++ b/extension.js @@ -208,6 +208,7 @@ function executeCommand(commandInput, componentPath) { * @returns */ export function startOnMainThread(options = {}) { + const config = resolveConfig(options); logger.debug('Next.js Extension Configuration:', JSON.stringify(config, undefined, 2)); diff --git a/fixtures/Dockerfile b/fixtures/Dockerfile index 921f3fb..ee59db5 100644 --- a/fixtures/Dockerfile +++ b/fixtures/Dockerfile @@ -1,3 +1,7 @@ +# This Dockerfile must be built from the root of the repository +# i.e. docker build ... -f fixtures/Dockerfile . +# Review the README.md for more information + ARG NODE_MAJOR FROM node:${NODE_MAJOR} as node-base-${NODE_MAJOR} @@ -6,6 +10,10 @@ RUN apt-get update && apt-get install -y \ curl \ && rm -rf /var/lib/apt/lists/* +RUN mkdir -p /harperdb-nextjs +COPY --exclude=.github --exclude=fixtures --exclude=test --exclude=util \ + . /harperdb-nextjs/ + RUN npm install -g harperdb RUN mkdir -p /hdb/components @@ -17,7 +25,7 @@ ENV ROOTPATH=/hdb ENV OPERATIONSAPI_NETWORK_PORT=9925 ENV HTTP_PORT=9926 -COPY harperdb-base-component /hdb/components/harperdb-base-component +COPY fixtures/harperdb-base-component /hdb/components/harperdb-base-component ARG NODE_MAJOR @@ -25,7 +33,9 @@ FROM node-base-${NODE_MAJOR} ARG NEXT_MAJOR -COPY next-${NEXT_MAJOR} /hdb/components/next-${NEXT_MAJOR} +COPY fixtures/next-${NEXT_MAJOR} /hdb/components/next-${NEXT_MAJOR} + +RUN npm install -C hdb/components/next-${NEXT_MAJOR} EXPOSE 9925 9926 diff --git a/fixtures/next-15/index.js b/fixtures/next-15/index.js deleted file mode 100644 index 75bde3e..0000000 --- a/fixtures/next-15/index.js +++ /dev/null @@ -1,12 +0,0 @@ -import http from 'http'; - -const server = http.createServer((req, res) => { - res.statusCode = 200; - res.setHeader('Content-Type', 'text/plain'); - res.end('Next.js v15'); -}); - -const PORT = 3000; -server.listen(PORT, () => { - console.log(`Server running at http://localhost:${PORT}/`); -}); diff --git a/fixtures/next-15/next.config.js b/fixtures/next-15/next.config.js new file mode 100644 index 0000000..a099545 --- /dev/null +++ b/fixtures/next-15/next.config.js @@ -0,0 +1 @@ +module.exports = {}; \ No newline at end of file diff --git a/fixtures/next-15/package.json b/fixtures/next-15/package.json index a4500c0..9ef5381 100644 --- a/fixtures/next-15/package.json +++ b/fixtures/next-15/package.json @@ -8,7 +8,7 @@ "lint": "next lint" }, "dependencies": { - "@harperdb/nextjs": "^0.0.12", + "@harperdb/nextjs": "file:/harperdb-nextjs", "react": "19.0.0-rc-66855b96-20241106", "react-dom": "19.0.0-rc-66855b96-20241106", "next": "15.0.3" diff --git a/test/next-15-node-18.test.js b/test/next-15-node-18.test.js index 69f60ef..f7635c5 100644 --- a/test/next-15-node-18.test.js +++ b/test/next-15-node-18.test.js @@ -6,10 +6,8 @@ suite('Next.js v15 - Node.js v18', async () => { const ctx = {}; before(async () => { - ctx.fixture = new Fixture({ nextMajor: '15', nodeMajor: '18',debug: true }); + ctx.fixture = new Fixture({ nextMajor: '15', nodeMajor: '18' }); await ctx.fixture.ready; - console.log(ctx.fixture.portMap); - ctx.rest = `http://${ctx.fixture.portMap.get('9926')}`; }); @@ -34,7 +32,7 @@ suite('Next.js v15 - Node.js v18', async () => { const text = await response.text(); t.assert.match(text, /Next\.js v15/); - }) + }); after(async () => { await ctx.fixture.clear(); diff --git a/test/next-15-node-20.test.js b/test/next-15-node-20.test.js index b877872..ccb4e92 100644 --- a/test/next-15-node-20.test.js +++ b/test/next-15-node-20.test.js @@ -22,6 +22,17 @@ suite('Next.js v15 - Node.js v20', async () => { t.assert.deepStrictEqual(json, { id: '0', name: 'Lincoln', breed: 'Shepherd' }); }); + await test('should reach home page', async (t) => { + const response = await fetch(`${ctx.rest}/`, { + headers: { + 'Content-Type': 'text/html', + } + }); + + const text = await response.text(); + t.assert.match(text, /Next\.js v15/); + }); + after(async () => { await ctx.fixture.clear(); }); diff --git a/test/next-15-node-22.test.js b/test/next-15-node-22.test.js index a14148c..7c2b3f2 100644 --- a/test/next-15-node-22.test.js +++ b/test/next-15-node-22.test.js @@ -22,6 +22,17 @@ suite('Next.js v15 - Node.js v22', async () => { t.assert.deepStrictEqual(json, { id: '0', name: 'Lincoln', breed: 'Shepherd' }); }); + await test('should reach home page', async (t) => { + const response = await fetch(`${ctx.rest}/`, { + headers: { + 'Content-Type': 'text/html', + } + }); + + const text = await response.text(); + t.assert.match(text, /Next\.js v15/); + }); + after(async () => { await ctx.fixture.clear(); }); diff --git a/util/fixture.js b/util/fixture.js index 9b2555d..b853004 100644 --- a/util/fixture.js +++ b/util/fixture.js @@ -73,7 +73,7 @@ export class Fixture { this.imageName, '-f', path.join(Fixture.FIXTURE_PATH, 'Dockerfile'), - Fixture.FIXTURE_PATH, + path.join(Fixture.FIXTURE_PATH, '..'), ]); } From f84b972a9311137e95f1e5cbb5229fd57520516e Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Sun, 1 Dec 2024 11:40:52 -0700 Subject: [PATCH 23/32] eureka! separate out build step to improve test speed --- fixtures/Dockerfile | 42 ------------------ fixtures/next-10/index.js | 12 ------ fixtures/next-10/package.json | 4 -- fixtures/next-11/index.js | 12 ------ fixtures/next-11/package.json | 4 -- fixtures/next-12/index.js | 12 ------ fixtures/next-12/package.json | 4 -- fixtures/next-13/app/layout.js | 13 ++++++ fixtures/next-13/app/page.js | 7 +++ fixtures/next-13/config.yaml | 4 ++ fixtures/next-13/index.js | 12 ------ fixtures/next-13/next.config.js | 1 + fixtures/next-13/package.json | 15 ++++++- fixtures/next-14/app/layout.js | 13 ++++++ fixtures/next-14/app/page.js | 7 +++ fixtures/next-14/config.yaml | 4 ++ fixtures/next-14/index.js | 12 ------ fixtures/next-14/next.config.js | 1 + fixtures/next-14/package.json | 14 +++++- fixtures/next-9/index.js | 12 ------ fixtures/next-9/package.json | 4 -- package.json | 1 + test/next-15-node-18.test.js | 27 ++---------- util/base.dockerfile | 27 ++++++++++++ util/getContainerEngine.js | 17 ++++++++ util/next.dockerfile | 13 ++++++ util/pretest.js | 75 +++++++++++++++++++++++++++++++++ util/tests.js | 32 ++++++++++++++ 28 files changed, 244 insertions(+), 157 deletions(-) delete mode 100644 fixtures/Dockerfile delete mode 100644 fixtures/next-10/index.js delete mode 100644 fixtures/next-10/package.json delete mode 100644 fixtures/next-11/index.js delete mode 100644 fixtures/next-11/package.json delete mode 100644 fixtures/next-12/index.js delete mode 100644 fixtures/next-12/package.json create mode 100644 fixtures/next-13/app/layout.js create mode 100644 fixtures/next-13/app/page.js create mode 100644 fixtures/next-13/config.yaml delete mode 100644 fixtures/next-13/index.js create mode 100644 fixtures/next-13/next.config.js create mode 100644 fixtures/next-14/app/layout.js create mode 100644 fixtures/next-14/app/page.js create mode 100644 fixtures/next-14/config.yaml delete mode 100644 fixtures/next-14/index.js create mode 100644 fixtures/next-14/next.config.js delete mode 100644 fixtures/next-9/index.js delete mode 100644 fixtures/next-9/package.json create mode 100644 util/base.dockerfile create mode 100644 util/getContainerEngine.js create mode 100644 util/next.dockerfile create mode 100644 util/pretest.js create mode 100644 util/tests.js diff --git a/fixtures/Dockerfile b/fixtures/Dockerfile deleted file mode 100644 index ee59db5..0000000 --- a/fixtures/Dockerfile +++ /dev/null @@ -1,42 +0,0 @@ -# This Dockerfile must be built from the root of the repository -# i.e. docker build ... -f fixtures/Dockerfile . -# Review the README.md for more information - -ARG NODE_MAJOR - -FROM node:${NODE_MAJOR} as node-base-${NODE_MAJOR} - -RUN apt-get update && apt-get install -y \ - curl \ - && rm -rf /var/lib/apt/lists/* - -RUN mkdir -p /harperdb-nextjs -COPY --exclude=.github --exclude=fixtures --exclude=test --exclude=util \ - . /harperdb-nextjs/ - -RUN npm install -g harperdb - -RUN mkdir -p /hdb/components - -ENV TC_AGREEMENT=yes -ENV HDB_ADMIN_USERNAME=hdb_admin -ENV HDB_ADMIN_PASSWORD=password -ENV ROOTPATH=/hdb -ENV OPERATIONSAPI_NETWORK_PORT=9925 -ENV HTTP_PORT=9926 - -COPY fixtures/harperdb-base-component /hdb/components/harperdb-base-component - -ARG NODE_MAJOR - -FROM node-base-${NODE_MAJOR} - -ARG NEXT_MAJOR - -COPY fixtures/next-${NEXT_MAJOR} /hdb/components/next-${NEXT_MAJOR} - -RUN npm install -C hdb/components/next-${NEXT_MAJOR} - -EXPOSE 9925 9926 - -CMD ["harperdb", "run"] diff --git a/fixtures/next-10/index.js b/fixtures/next-10/index.js deleted file mode 100644 index e3e6d25..0000000 --- a/fixtures/next-10/index.js +++ /dev/null @@ -1,12 +0,0 @@ -import http from 'http'; - -const server = http.createServer((req, res) => { - res.statusCode = 200; - res.setHeader('Content-Type', 'text/plain'); - res.end('Next.js v10'); -}); - -const PORT = 3000; -server.listen(PORT, () => { - console.log(`Server running at http://localhost:${PORT}/`); -}); diff --git a/fixtures/next-10/package.json b/fixtures/next-10/package.json deleted file mode 100644 index 215f117..0000000 --- a/fixtures/next-10/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "next-10", - "type": "module" -} diff --git a/fixtures/next-11/index.js b/fixtures/next-11/index.js deleted file mode 100644 index ac107be..0000000 --- a/fixtures/next-11/index.js +++ /dev/null @@ -1,12 +0,0 @@ -import http from 'http'; - -const server = http.createServer((req, res) => { - res.statusCode = 200; - res.setHeader('Content-Type', 'text/plain'); - res.end('Next.js v11'); -}); - -const PORT = 3000; -server.listen(PORT, () => { - console.log(`Server running at http://localhost:${PORT}/`); -}); diff --git a/fixtures/next-11/package.json b/fixtures/next-11/package.json deleted file mode 100644 index 08d7451..0000000 --- a/fixtures/next-11/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "next-11", - "type": "module" -} diff --git a/fixtures/next-12/index.js b/fixtures/next-12/index.js deleted file mode 100644 index 804358c..0000000 --- a/fixtures/next-12/index.js +++ /dev/null @@ -1,12 +0,0 @@ -import http from 'http'; - -const server = http.createServer((req, res) => { - res.statusCode = 200; - res.setHeader('Content-Type', 'text/plain'); - res.end('Next.js v12'); -}); - -const PORT = 3000; -server.listen(PORT, () => { - console.log(`Server running at http://localhost:${PORT}/`); -}); diff --git a/fixtures/next-12/package.json b/fixtures/next-12/package.json deleted file mode 100644 index 1fbe281..0000000 --- a/fixtures/next-12/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "next-12", - "type": "module" -} diff --git a/fixtures/next-13/app/layout.js b/fixtures/next-13/app/layout.js new file mode 100644 index 0000000..9bd3c4b --- /dev/null +++ b/fixtures/next-13/app/layout.js @@ -0,0 +1,13 @@ +export const metadata = { + title: 'HarperDB - Next.js v15 App', +}; + +export default function RootLayout({ children }) { + return ( + + + {children} + + + ); +} diff --git a/fixtures/next-13/app/page.js b/fixtures/next-13/app/page.js new file mode 100644 index 0000000..4a48cf9 --- /dev/null +++ b/fixtures/next-13/app/page.js @@ -0,0 +1,7 @@ +export default async function Page() { + return ( +
+

Next.js v15

+
+ ); +} diff --git a/fixtures/next-13/config.yaml b/fixtures/next-13/config.yaml new file mode 100644 index 0000000..9cc50d3 --- /dev/null +++ b/fixtures/next-13/config.yaml @@ -0,0 +1,4 @@ +'@harperdb/nextjs': + package: '@harperdb/nextjs' + files: '/*' + port: 9926 diff --git a/fixtures/next-13/index.js b/fixtures/next-13/index.js deleted file mode 100644 index b6faaa4..0000000 --- a/fixtures/next-13/index.js +++ /dev/null @@ -1,12 +0,0 @@ -import http from 'http'; - -const server = http.createServer((req, res) => { - res.statusCode = 200; - res.setHeader('Content-Type', 'text/plain'); - res.end('Next.js v13'); -}); - -const PORT = 3000; -server.listen(PORT, () => { - console.log(`Server running at http://localhost:${PORT}/`); -}); diff --git a/fixtures/next-13/next.config.js b/fixtures/next-13/next.config.js new file mode 100644 index 0000000..a099545 --- /dev/null +++ b/fixtures/next-13/next.config.js @@ -0,0 +1 @@ +module.exports = {}; \ No newline at end of file diff --git a/fixtures/next-13/package.json b/fixtures/next-13/package.json index d2f7e17..8a25415 100644 --- a/fixtures/next-13/package.json +++ b/fixtures/next-13/package.json @@ -1,4 +1,15 @@ { "name": "next-13", - "type": "module" -} + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@harperdb/nextjs": "file:/harperdb-nextjs", + "next": "13.5.7" + } + } + \ No newline at end of file diff --git a/fixtures/next-14/app/layout.js b/fixtures/next-14/app/layout.js new file mode 100644 index 0000000..9bd3c4b --- /dev/null +++ b/fixtures/next-14/app/layout.js @@ -0,0 +1,13 @@ +export const metadata = { + title: 'HarperDB - Next.js v15 App', +}; + +export default function RootLayout({ children }) { + return ( + + + {children} + + + ); +} diff --git a/fixtures/next-14/app/page.js b/fixtures/next-14/app/page.js new file mode 100644 index 0000000..4a48cf9 --- /dev/null +++ b/fixtures/next-14/app/page.js @@ -0,0 +1,7 @@ +export default async function Page() { + return ( +
+

Next.js v15

+
+ ); +} diff --git a/fixtures/next-14/config.yaml b/fixtures/next-14/config.yaml new file mode 100644 index 0000000..9cc50d3 --- /dev/null +++ b/fixtures/next-14/config.yaml @@ -0,0 +1,4 @@ +'@harperdb/nextjs': + package: '@harperdb/nextjs' + files: '/*' + port: 9926 diff --git a/fixtures/next-14/index.js b/fixtures/next-14/index.js deleted file mode 100644 index c172664..0000000 --- a/fixtures/next-14/index.js +++ /dev/null @@ -1,12 +0,0 @@ -import http from 'http'; - -const server = http.createServer((req, res) => { - res.statusCode = 200; - res.setHeader('Content-Type', 'text/plain'); - res.end('Next.js v14'); -}); - -const PORT = 3000; -server.listen(PORT, () => { - console.log(`Server running at http://localhost:${PORT}/`); -}); diff --git a/fixtures/next-14/next.config.js b/fixtures/next-14/next.config.js new file mode 100644 index 0000000..a099545 --- /dev/null +++ b/fixtures/next-14/next.config.js @@ -0,0 +1 @@ +module.exports = {}; \ No newline at end of file diff --git a/fixtures/next-14/package.json b/fixtures/next-14/package.json index f194e75..34ff903 100644 --- a/fixtures/next-14/package.json +++ b/fixtures/next-14/package.json @@ -1,4 +1,16 @@ { "name": "next-14", - "type": "module" + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@harperdb/nextjs": "file:/harperdb-nextjs", + "react": "^18", + "react-dom": "^18", + "next": "14.2.18" + } } diff --git a/fixtures/next-9/index.js b/fixtures/next-9/index.js deleted file mode 100644 index d2a07bb..0000000 --- a/fixtures/next-9/index.js +++ /dev/null @@ -1,12 +0,0 @@ -import http from 'http'; - -const server = http.createServer((req, res) => { - res.statusCode = 200; - res.setHeader('Content-Type', 'text/plain'); - res.end('Next.js v9'); -}); - -const PORT = 3000; -server.listen(PORT, () => { - console.log(`Server running at http://localhost:${PORT}/`); -}); diff --git a/fixtures/next-9/package.json b/fixtures/next-9/package.json deleted file mode 100644 index 78a6a8d..0000000 --- a/fixtures/next-9/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "next-9", - "type": "module" -} diff --git a/package.json b/package.json index 2a1ed31..b5956bc 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "format": "prettier .", "format:check": "npm run format -- --check", "format:write": "npm run format -- --write", + "pretest": "node scripts/pretest.js", "test": "node --test" }, "dependencies": { diff --git a/test/next-15-node-18.test.js b/test/next-15-node-18.test.js index f7635c5..6b3e798 100644 --- a/test/next-15-node-18.test.js +++ b/test/next-15-node-18.test.js @@ -1,5 +1,5 @@ import { suite, test, before, after } from 'node:test'; - +import { base, next15 } from '../util/tests.js'; import { Fixture } from '../util/fixture.js'; suite('Next.js v15 - Node.js v18', async () => { @@ -8,31 +8,10 @@ suite('Next.js v15 - Node.js v18', async () => { before(async () => { ctx.fixture = new Fixture({ nextMajor: '15', nodeMajor: '18' }); await ctx.fixture.ready; - ctx.rest = `http://${ctx.fixture.portMap.get('9926')}`; - }); - - await test('should run base component', async (t) => { - const response = await fetch(`${ctx.rest}/Dog/0`, { - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'Basic aGRiX2FkbWluOnBhc3N3b3Jk', - }, - }); - const json = await response.json(); - - t.assert.deepStrictEqual(json, { id: '0', name: 'Lincoln', breed: 'Shepherd' }); + ctx.rest = new URL(`http://${ctx.fixture.portMap.get('9926')}`); }); - await test('should reach home page', async (t) => { - const response = await fetch(`${ctx.rest}/`, { - headers: { - 'Content-Type': 'text/html', - } - }); - - const text = await response.text(); - t.assert.match(text, /Next\.js v15/); - }); + await Promise.all(base.concat(next15).map(async ({ name, testFunction }) => test(name, t => testFunction(t, ctx)))); after(async () => { await ctx.fixture.clear(); diff --git a/util/base.dockerfile b/util/base.dockerfile new file mode 100644 index 0000000..366c92f --- /dev/null +++ b/util/base.dockerfile @@ -0,0 +1,27 @@ +ARG NODE_MAJOR + +FROM node:${NODE_MAJOR} + +RUN apt-get update && apt-get install -y \ + curl \ + && rm -rf /var/lib/apt/lists/* + +RUN mkdir -p /@harperdb/nextjs + +COPY --exclude=.github --exclude=fixtures --exclude=test --exclude=util --exclude=node_modules --exclude=.node-version --exclude=.git \ + . /@harperdb/nextjs + +RUN npm install -C /@harperdb/nextjs + +RUN npm install -g harperdb + +RUN mkdir -p /hdb/components + +ENV TC_AGREEMENT=yes +ENV HDB_ADMIN_USERNAME=hdb_admin +ENV HDB_ADMIN_PASSWORD=password +ENV ROOTPATH=/hdb +ENV OPERATIONSAPI_NETWORK_PORT=9925 +ENV HTTP_PORT=9926 + +COPY /fixtures/harperdb-base-component /hdb/components/harperdb-base-component \ No newline at end of file diff --git a/util/getContainerEngine.js b/util/getContainerEngine.js new file mode 100644 index 0000000..248f8e3 --- /dev/null +++ b/util/getContainerEngine.js @@ -0,0 +1,17 @@ +import { spawnSync } from 'child_process'; + + +const CONTAINER_ENGINE_LIST = ['podman', 'docker']; + +export function getContainerEngine() { + for (const engine of CONTAINER_ENGINE_LIST) { + const { status } = spawnSync(engine, ['--version'], { stdio: 'ignore' }); + if (status === 0) { + return engine; + } + } + + throw new Error(`No container engine found in ${CONTAINER_ENGINE_LIST.join(', ')}`); +} + +export const containerEngine = getContainerEngine(); \ No newline at end of file diff --git a/util/next.dockerfile b/util/next.dockerfile new file mode 100644 index 0000000..0606cf3 --- /dev/null +++ b/util/next.dockerfile @@ -0,0 +1,13 @@ +ARG BASE_IMAGE + +FROM ${BASE_IMAGE} + +ARG NEXT_MAJOR + +COPY fixtures/next-${NEXT_MAJOR} /hdb/components/next-${NEXT_MAJOR} + +RUN npm install -C hdb/components/next-${NEXT_MAJOR} + +EXPOSE 9925 9926 + +CMD ["harperdb", "run"] \ No newline at end of file diff --git a/util/pretest.js b/util/pretest.js new file mode 100644 index 0000000..f27a312 --- /dev/null +++ b/util/pretest.js @@ -0,0 +1,75 @@ +import child_process from 'node:child_process'; +import { join } from 'node:path'; +import { containerEngine } from './getContainerEngine.js'; + +const DEBUG = process.env.DEBUG === '1'; + +const STDIO = ['ignore', DEBUG ? 'inherit' : 'ignore', DEBUG ? 'inherit' : 'ignore']; + +const NODE_MAJORS = ['18', '20', '22']; + +const getNodeBaseImageName = (nodeMajor) => `harperdb-nextjs/node-base-${nodeMajor}`; +// Build Node Base Images +const nodeImagesBuildResults = await Promise.all( + NODE_MAJORS.map( + (nodeMajor) => + new Promise((resolve, reject) => { + const buildProcess = child_process.spawn( + containerEngine, + [ + 'build', + '--build-arg', + `NODE_MAJOR=${nodeMajor}`, + '-t', + getNodeBaseImageName(nodeMajor), + '-f', + join(import.meta.dirname, 'base.dockerfile'), + join(import.meta.dirname, '..'), + ], + { stdio: STDIO } + ); + + buildProcess.on('error', reject); + buildProcess.on('exit', resolve); + }) + ) +); + +console.log(nodeImagesBuildResults); + +const NEXT_MAJORS = ['13', '14', '15']; + +const getNextImageName = (nextMajor, nodeMajor) => `harperdb-nextjs/test-image-next-${nextMajor}-node-${nodeMajor}`; + +const nextImageBuildResults = await Promise.all( + NEXT_MAJORS.flatMap((nextMajor) => + NODE_MAJORS.map( + async (nodeMajor) => + new Promise((resolve, reject) => { + const buildProcess = child_process.spawn( + containerEngine, + [ + 'build', + '--build-arg', + `BASE_IMAGE=${getNodeBaseImageName(nodeMajor)}`, + '--build-arg', + `NEXT_MAJOR=${nextMajor}`, + '-t', + getNextImageName(nextMajor, nodeMajor), + '-f', + join(import.meta.dirname, 'next.dockerfile'), + join(import.meta.dirname, '..'), + ], + { + stdio: STDIO, + } + ); + + buildProcess.on('error', reject); + buildProcess.on('exit', resolve); + }) + ) + ) +); + +console.log(nextImageBuildResults); diff --git a/util/tests.js b/util/tests.js new file mode 100644 index 0000000..7676893 --- /dev/null +++ b/util/tests.js @@ -0,0 +1,32 @@ +export const base = [ + { + name: "should enable /Dog rest endpoint", + testFunction: async (t, ctx) => { + const response = await fetch(`${ctx.rest}/Dog/0`, { + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Basic aGRiX2FkbWluOnBhc3N3b3Jk', + }, + }); + const json = await response.json(); + + t.assert.deepStrictEqual(json, { id: '0', name: 'Lincoln', breed: 'Shepherd' }); + } + } +] + +export const next15 = [ + { + name: 'should render home page', + testFunction: async (t, ctx) => { + const response = await fetch(`${ctx.rest}/`, { + headers: { + 'Content-Type': 'text/html', + } + }); + + const text = await response.text(); + t.assert.match(text, /Next\.js v15/); + } + } +]; \ No newline at end of file From ce61a7591e173f70d56199b299c620f61a722768 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Sun, 1 Dec 2024 17:55:31 -0700 Subject: [PATCH 24/32] lots of progress. but why do the tests fail in concurrency mode? --- .github/workflows/test.yaml | 2 +- extension.js | 1 - fixtures/next-13/app/layout.js | 4 +- fixtures/next-13/next.config.js | 2 +- fixtures/next-13/package.json | 5 +- fixtures/next-14/app/layout.js | 4 +- fixtures/next-14/next.config.js | 2 +- fixtures/next-14/package.json | 2 +- fixtures/next-15/app/layout.js | 4 +- fixtures/next-15/next.config.js | 2 +- fixtures/next-15/package.json | 5 +- package.json | 4 +- test/next-15-node-18.test.js | 11 +- test/next-15-node-20.test.js | 26 +--- test/next-15-node-22.test.js | 26 +--- util/base.dockerfile | 3 + util/collected-transform.js | 14 ++ util/fixture.js | 87 +++--------- ...ainerEngine.js => get-container-engine.js} | 5 +- util/get-next-image-name.js | 3 + util/next.dockerfile | 3 + util/pretest.js | 127 ++++++++++-------- util/tests.js | 20 +-- 23 files changed, 154 insertions(+), 208 deletions(-) create mode 100644 util/collected-transform.js rename util/{getContainerEngine.js => get-container-engine.js} (88%) create mode 100644 util/get-next-image-name.js diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 537fe36..2716789 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -21,6 +21,6 @@ jobs: - name: Install dependencies run: npm ci - + - name: Run tests run: npm run test diff --git a/extension.js b/extension.js index 0d5e5d6..f9fa1a9 100644 --- a/extension.js +++ b/extension.js @@ -208,7 +208,6 @@ function executeCommand(commandInput, componentPath) { * @returns */ export function startOnMainThread(options = {}) { - const config = resolveConfig(options); logger.debug('Next.js Extension Configuration:', JSON.stringify(config, undefined, 2)); diff --git a/fixtures/next-13/app/layout.js b/fixtures/next-13/app/layout.js index 9bd3c4b..ee00d88 100644 --- a/fixtures/next-13/app/layout.js +++ b/fixtures/next-13/app/layout.js @@ -5,9 +5,7 @@ export const metadata = { export default function RootLayout({ children }) { return ( - - {children} - + {children} ); } diff --git a/fixtures/next-13/next.config.js b/fixtures/next-13/next.config.js index a099545..f053ebf 100644 --- a/fixtures/next-13/next.config.js +++ b/fixtures/next-13/next.config.js @@ -1 +1 @@ -module.exports = {}; \ No newline at end of file +module.exports = {}; diff --git a/fixtures/next-13/package.json b/fixtures/next-13/package.json index 8a25415..a12eadb 100644 --- a/fixtures/next-13/package.json +++ b/fixtures/next-13/package.json @@ -8,8 +8,7 @@ "lint": "next lint" }, "dependencies": { - "@harperdb/nextjs": "file:/harperdb-nextjs", + "@harperdb/nextjs": "file:/@harperdb/nextjs", "next": "13.5.7" } - } - \ No newline at end of file +} diff --git a/fixtures/next-14/app/layout.js b/fixtures/next-14/app/layout.js index 9bd3c4b..ee00d88 100644 --- a/fixtures/next-14/app/layout.js +++ b/fixtures/next-14/app/layout.js @@ -5,9 +5,7 @@ export const metadata = { export default function RootLayout({ children }) { return ( - - {children} - + {children} ); } diff --git a/fixtures/next-14/next.config.js b/fixtures/next-14/next.config.js index a099545..f053ebf 100644 --- a/fixtures/next-14/next.config.js +++ b/fixtures/next-14/next.config.js @@ -1 +1 @@ -module.exports = {}; \ No newline at end of file +module.exports = {}; diff --git a/fixtures/next-14/package.json b/fixtures/next-14/package.json index 34ff903..db6467a 100644 --- a/fixtures/next-14/package.json +++ b/fixtures/next-14/package.json @@ -8,7 +8,7 @@ "lint": "next lint" }, "dependencies": { - "@harperdb/nextjs": "file:/harperdb-nextjs", + "@harperdb/nextjs": "file:/@harperdb/nextjs", "react": "^18", "react-dom": "^18", "next": "14.2.18" diff --git a/fixtures/next-15/app/layout.js b/fixtures/next-15/app/layout.js index 9bd3c4b..ee00d88 100644 --- a/fixtures/next-15/app/layout.js +++ b/fixtures/next-15/app/layout.js @@ -5,9 +5,7 @@ export const metadata = { export default function RootLayout({ children }) { return ( - - {children} - + {children} ); } diff --git a/fixtures/next-15/next.config.js b/fixtures/next-15/next.config.js index a099545..f053ebf 100644 --- a/fixtures/next-15/next.config.js +++ b/fixtures/next-15/next.config.js @@ -1 +1 @@ -module.exports = {}; \ No newline at end of file +module.exports = {}; diff --git a/fixtures/next-15/package.json b/fixtures/next-15/package.json index 9ef5381..ee2e14c 100644 --- a/fixtures/next-15/package.json +++ b/fixtures/next-15/package.json @@ -8,10 +8,9 @@ "lint": "next lint" }, "dependencies": { - "@harperdb/nextjs": "file:/harperdb-nextjs", + "@harperdb/nextjs": "file:/@harperdb/nextjs", "react": "19.0.0-rc-66855b96-20241106", "react-dom": "19.0.0-rc-66855b96-20241106", "next": "15.0.3" } - } - \ No newline at end of file +} diff --git a/package.json b/package.json index b5956bc..5e3a493 100644 --- a/package.json +++ b/package.json @@ -35,8 +35,8 @@ "scripts": { "format": "prettier .", "format:check": "npm run format -- --check", - "format:write": "npm run format -- --write", - "pretest": "node scripts/pretest.js", + "format:fix": "npm run format -- --write", + "pretest": "node util/pretest.js", "test": "node --test" }, "dependencies": { diff --git a/test/next-15-node-18.test.js b/test/next-15-node-18.test.js index 6b3e798..3e67c7b 100644 --- a/test/next-15-node-18.test.js +++ b/test/next-15-node-18.test.js @@ -8,10 +8,17 @@ suite('Next.js v15 - Node.js v18', async () => { before(async () => { ctx.fixture = new Fixture({ nextMajor: '15', nodeMajor: '18' }); await ctx.fixture.ready; - ctx.rest = new URL(`http://${ctx.fixture.portMap.get('9926')}`); + + const restPort = ctx.fixture.portMap.get('9926'); + + if (!restPort) { + throw new Error('Rest port not found'); + } + + ctx.rest = new URL(`http://${restPort}`); }); - await Promise.all(base.concat(next15).map(async ({ name, testFunction }) => test(name, t => testFunction(t, ctx)))); + await Promise.all(base.concat(next15).map(async ({ name, testFunction }) => test(name, (t) => testFunction(t, ctx)))); after(async () => { await ctx.fixture.clear(); diff --git a/test/next-15-node-20.test.js b/test/next-15-node-20.test.js index ccb4e92..36ee7ac 100644 --- a/test/next-15-node-20.test.js +++ b/test/next-15-node-20.test.js @@ -1,4 +1,5 @@ import { suite, test, before, after } from 'node:test'; +import { base, next15 } from '../util/tests.js'; import { Fixture } from '../util/fixture.js'; suite('Next.js v15 - Node.js v20', async () => { @@ -7,31 +8,10 @@ suite('Next.js v15 - Node.js v20', async () => { before(async () => { ctx.fixture = new Fixture({ nextMajor: '15', nodeMajor: '20' }); await ctx.fixture.ready; - ctx.rest = `http://${ctx.fixture.portMap.get('9926')}`; + ctx.rest = new URL(`http://${ctx.fixture.portMap.get('9926')}`); }); - await test('should run base component', async (t) => { - const response = await fetch(`${ctx.rest}/Dog/0`, { - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'Basic aGRiX2FkbWluOnBhc3N3b3Jk', - }, - }); - const json = await response.json(); - - t.assert.deepStrictEqual(json, { id: '0', name: 'Lincoln', breed: 'Shepherd' }); - }); - - await test('should reach home page', async (t) => { - const response = await fetch(`${ctx.rest}/`, { - headers: { - 'Content-Type': 'text/html', - } - }); - - const text = await response.text(); - t.assert.match(text, /Next\.js v15/); - }); + await Promise.all(base.concat(next15).map(async ({ name, testFunction }) => test(name, (t) => testFunction(t, ctx)))); after(async () => { await ctx.fixture.clear(); diff --git a/test/next-15-node-22.test.js b/test/next-15-node-22.test.js index 7c2b3f2..8304ac6 100644 --- a/test/next-15-node-22.test.js +++ b/test/next-15-node-22.test.js @@ -1,4 +1,5 @@ import { suite, test, before, after } from 'node:test'; +import { base, next15 } from '../util/tests.js'; import { Fixture } from '../util/fixture.js'; suite('Next.js v15 - Node.js v22', async () => { @@ -7,31 +8,10 @@ suite('Next.js v15 - Node.js v22', async () => { before(async () => { ctx.fixture = new Fixture({ nextMajor: '15', nodeMajor: '22' }); await ctx.fixture.ready; - ctx.rest = `http://${ctx.fixture.portMap.get('9926')}`; + ctx.rest = new URL(`http://${ctx.fixture.portMap.get('9926')}`); }); - await test('should run base component', async (t) => { - const response = await fetch(`${ctx.rest}/Dog/0`, { - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'Basic aGRiX2FkbWluOnBhc3N3b3Jk', - }, - }); - const json = await response.json(); - - t.assert.deepStrictEqual(json, { id: '0', name: 'Lincoln', breed: 'Shepherd' }); - }); - - await test('should reach home page', async (t) => { - const response = await fetch(`${ctx.rest}/`, { - headers: { - 'Content-Type': 'text/html', - } - }); - - const text = await response.text(); - t.assert.match(text, /Next\.js v15/); - }); + await Promise.all(base.concat(next15).map(async ({ name, testFunction }) => test(name, (t) => testFunction(t, ctx)))); after(async () => { await ctx.fixture.clear(); diff --git a/util/base.dockerfile b/util/base.dockerfile index 366c92f..5324c9d 100644 --- a/util/base.dockerfile +++ b/util/base.dockerfile @@ -1,3 +1,6 @@ +# Base Dockerfile for HarperDB Next.js Integration Tests fixtures +# Must be run from the root of the repository + ARG NODE_MAJOR FROM node:${NODE_MAJOR} diff --git a/util/collected-transform.js b/util/collected-transform.js new file mode 100644 index 0000000..8027cea --- /dev/null +++ b/util/collected-transform.js @@ -0,0 +1,14 @@ +import { Transform } from 'node:stream'; + +export class CollectedTransform extends Transform { + #chunks = []; + + _transform(chunk, _, callback) { + this.#chunks.push(chunk); + callback(null, chunk); + } + + get output() { + return Buffer.concat(this.#chunks).toString(); + } +} diff --git a/util/fixture.js b/util/fixture.js index b853004..414da2e 100644 --- a/util/fixture.js +++ b/util/fixture.js @@ -1,44 +1,21 @@ import child_process from 'node:child_process'; -import path from 'node:path'; import { Transform } from 'node:stream'; -import { fileURLToPath } from 'node:url'; +import { getNextImageName } from './get-next-image-name.js'; +import { containerEngine } from './get-container-engine.js'; export class Fixture { - static CONTAINER_ENGINE_LIST = ['podman', 'docker']; - static FIXTURE_PATH = fileURLToPath(new URL('../fixtures/', import.meta.url)); - - /** @type {string} */ - #containerEngine; - constructor({ autoSetup = true, debug = false, nextMajor, nodeMajor }) { this.nextMajor = nextMajor; this.nodeMajor = nodeMajor; this.debug = debug || process.env.DEBUG === '1'; - this.imageName = `hdb-next-integration-test-image-next-${nextMajor}-node-${nodeMajor}`; + this.imageName = getNextImageName(nextMajor, nodeMajor); this.containerName = `hdb-next-integration-test-container-next-${nextMajor}-node-${nodeMajor}`; if (autoSetup) { - this.ready = this.clear() - .then(() => this.build()) - .then(() => this.run()); - } - } - - get containerEngine() { - if (this.#containerEngine) { - return this.#containerEngine; + this.ready = this.clear().then(() => this.run()); } - - for (const containerEngine of Fixture.CONTAINER_ENGINE_LIST) { - const { status } = child_process.spawnSync(containerEngine, ['--version'], { stdio: 'ignore' }); - if (status === 0) { - return (this.#containerEngine = containerEngine); - } - } - - throw new Error(`No container engine found in ${CONTAINER_ENGINE_LIST.join(', ')}`); } get #stdio() { @@ -47,36 +24,16 @@ export class Fixture { #runCommand(args = [], options = {}) { return new Promise((resolve, reject) => { - const childProcess = child_process.spawn(this.containerEngine, args, { + const childProcess = child_process.spawn(containerEngine, args, { stdio: this.#stdio, ...options, }); - childProcess.on('error', (error) => { - reject(error); - }); - - childProcess.on('exit', (code) => { - resolve(code); - }); + childProcess.on('error', reject); + childProcess.on('exit', resolve); }); } - build() { - return this.#runCommand([ - 'build', - '--build-arg', - `NEXT_MAJOR=${this.nextMajor}`, - '--build-arg', - `NODE_MAJOR=${this.nodeMajor}`, - '-t', - this.imageName, - '-f', - path.join(Fixture.FIXTURE_PATH, 'Dockerfile'), - path.join(Fixture.FIXTURE_PATH, '..'), - ]); - } - stop() { return this.#runCommand(['stop', this.containerName]); } @@ -87,15 +44,15 @@ export class Fixture { clear() { return new Promise((resolve, reject) => { - const psProcess = child_process.spawn(this.containerEngine, ['ps', '-aq', '-f', `name=${this.containerName}`]); + const psProcess = child_process.spawn(containerEngine, ['ps', '-aq', '-f', `name=${this.containerName}`]); psProcess.on('error', reject); const collectedStdout = psProcess.stdout.pipe( new Transform({ - construct(cb) { + construct(callback) { this.chunks = []; - cb(null); + callback(null); }, transform(chunk, encoding, callback) { this.chunks.push(chunk); @@ -110,16 +67,12 @@ export class Fixture { } psProcess.on('exit', (code) => { - if (code === 0) { - if (collectedStdout.chunks.length !== 0) { - this.stop() - .then(() => this.rm()) - .then(resolve, reject); - } - resolve(); - } else { - reject(new Error(`\`${this.containerEngine} ps\` exited with code ${code}`)); + if (code === 0 && collectedStdout.chunks.length !== 0) { + return this.stop() + .then(() => this.rm()) + .then(resolve, reject); } + return resolve(code); }); }); } @@ -127,11 +80,14 @@ export class Fixture { run() { return new Promise((resolve, reject) => { const runProcess = child_process.spawn( - this.containerEngine, + containerEngine, ['run', '-P', '--name', this.containerName, this.imageName], { stdio: ['ignore', 'pipe', this.debug ? 'inherit' : 'ignore'] } ); + runProcess.on('error', reject); + runProcess.on('exit', resolve); + const stdout = runProcess.stdout.pipe( new Transform({ transform(chunk, encoding, callback) { @@ -146,16 +102,13 @@ export class Fixture { if (this.debug) { stdout.pipe(process.stdout); } - - runProcess.on('error', reject); - runProcess.on('exit', resolve); }); } get portMap() { const portMap = new Map(); for (const port of ['9925', '9926']) { - const { stdout } = child_process.spawnSync(this.containerEngine, ['port', this.containerName, port]); + const { stdout } = child_process.spawnSync(containerEngine, ['port', this.containerName, port]); portMap.set(port, stdout.toString().trim()); } return portMap; diff --git a/util/getContainerEngine.js b/util/get-container-engine.js similarity index 88% rename from util/getContainerEngine.js rename to util/get-container-engine.js index 248f8e3..0708692 100644 --- a/util/getContainerEngine.js +++ b/util/get-container-engine.js @@ -1,6 +1,5 @@ import { spawnSync } from 'child_process'; - const CONTAINER_ENGINE_LIST = ['podman', 'docker']; export function getContainerEngine() { @@ -10,8 +9,8 @@ export function getContainerEngine() { return engine; } } - + throw new Error(`No container engine found in ${CONTAINER_ENGINE_LIST.join(', ')}`); } -export const containerEngine = getContainerEngine(); \ No newline at end of file +export const containerEngine = getContainerEngine(); diff --git a/util/get-next-image-name.js b/util/get-next-image-name.js new file mode 100644 index 0000000..0845860 --- /dev/null +++ b/util/get-next-image-name.js @@ -0,0 +1,3 @@ +export function getNextImageName(nextMajor, nodeMajor) { + return `harperdb-nextjs/test-image-next-${nextMajor}-node-${nodeMajor}`; +} diff --git a/util/next.dockerfile b/util/next.dockerfile index 0606cf3..0e41386 100644 --- a/util/next.dockerfile +++ b/util/next.dockerfile @@ -1,3 +1,6 @@ +# Next.js Specific Dockerfile for HarperDB Next.js Integration Tests fixtures +# Must be run from the root of the repository + ARG BASE_IMAGE FROM ${BASE_IMAGE} diff --git a/util/pretest.js b/util/pretest.js index f27a312..90cf43c 100644 --- a/util/pretest.js +++ b/util/pretest.js @@ -1,75 +1,88 @@ -import child_process from 'node:child_process'; +import { spawn } from 'node:child_process'; import { join } from 'node:path'; -import { containerEngine } from './getContainerEngine.js'; +import { containerEngine } from './get-container-engine.js'; +import { getNextImageName } from './get-next-image-name.js'; +import { CollectedTransform } from './collected-transform.js'; const DEBUG = process.env.DEBUG === '1'; -const STDIO = ['ignore', DEBUG ? 'inherit' : 'ignore', DEBUG ? 'inherit' : 'ignore']; - const NODE_MAJORS = ['18', '20', '22']; const getNodeBaseImageName = (nodeMajor) => `harperdb-nextjs/node-base-${nodeMajor}`; -// Build Node Base Images -const nodeImagesBuildResults = await Promise.all( - NODE_MAJORS.map( - (nodeMajor) => - new Promise((resolve, reject) => { - const buildProcess = child_process.spawn( - containerEngine, - [ - 'build', - '--build-arg', - `NODE_MAJOR=${nodeMajor}`, - '-t', - getNodeBaseImageName(nodeMajor), - '-f', - join(import.meta.dirname, 'base.dockerfile'), - join(import.meta.dirname, '..'), - ], - { stdio: STDIO } - ); - buildProcess.on('error', reject); - buildProcess.on('exit', resolve); +function build(name, args, options = {}) { + return new Promise((resolve, reject) => { + const buildProcess = spawn(containerEngine, args, { stdio: ['ignore', 'pipe', 'pipe'], ...options }); + + const collectedStdout = buildProcess.stdout.pipe(new CollectedTransform()); + const collectedStderr = buildProcess.stderr.pipe(new CollectedTransform()); + + buildProcess.on('error', (error) => reject(error)); + buildProcess.on('close', (code) => + resolve({ + name, + code, + stdout: collectedStdout.output, + stderr: collectedStderr.output, }) + ); + }); +} + +// Build Node Base Images +const nodeImagesBuildResults = await Promise.all( + NODE_MAJORS.map((nodeMajor) => + build(getNodeBaseImageName(nodeMajor), [ + 'build', + '--build-arg', + `NODE_MAJOR=${nodeMajor}`, + '-t', + getNodeBaseImageName(nodeMajor), + '-f', + join(import.meta.dirname, 'base.dockerfile'), + join(import.meta.dirname, '..'), + ]) ) ); -console.log(nodeImagesBuildResults); +validateResults(nodeImagesBuildResults); const NEXT_MAJORS = ['13', '14', '15']; -const getNextImageName = (nextMajor, nodeMajor) => `harperdb-nextjs/test-image-next-${nextMajor}-node-${nodeMajor}`; +for (const nextMajor of NEXT_MAJORS) { + const nextImageBuildResults = await Promise.all( + NODE_MAJORS.map((nodeMajor) => + build(getNextImageName(nextMajor, nodeMajor), [ + 'build', + '--build-arg', + `BASE_IMAGE=${getNodeBaseImageName(nodeMajor)}`, + '--build-arg', + `NEXT_MAJOR=${nextMajor}`, + '-t', + getNextImageName(nextMajor, nodeMajor), + '-f', + join(import.meta.dirname, 'next.dockerfile'), + join(import.meta.dirname, '..'), + ]) + ) + ); -const nextImageBuildResults = await Promise.all( - NEXT_MAJORS.flatMap((nextMajor) => - NODE_MAJORS.map( - async (nodeMajor) => - new Promise((resolve, reject) => { - const buildProcess = child_process.spawn( - containerEngine, - [ - 'build', - '--build-arg', - `BASE_IMAGE=${getNodeBaseImageName(nodeMajor)}`, - '--build-arg', - `NEXT_MAJOR=${nextMajor}`, - '-t', - getNextImageName(nextMajor, nodeMajor), - '-f', - join(import.meta.dirname, 'next.dockerfile'), - join(import.meta.dirname, '..'), - ], - { - stdio: STDIO, - } - ); + validateResults(nextImageBuildResults); +} - buildProcess.on('error', reject); - buildProcess.on('exit', resolve); - }) - ) - ) -); +function validateResults(results) { + let success = true; + for (const result of results) { + if (result.code !== 0) success = false; + + if (DEBUG || !success) { + console.log(`Image \x1b[94m${result.name}\x1b[0m build process exited with: \x1b[35m${result.code}\x1b[0m\n`); + result.stdout !== '' && console.log('\x1b[32mstdout\x1b[0m:\n' + result.stdout + '\n'); + result.stderr !== '' && console.log('\x1b[31mstderr\x1b[0m:\n' + result.stderr + '\n'); + } + } -console.log(nextImageBuildResults); + if (!success) { + process.exit(1); + } +} diff --git a/util/tests.js b/util/tests.js index 7676893..7335be1 100644 --- a/util/tests.js +++ b/util/tests.js @@ -1,6 +1,6 @@ export const base = [ { - name: "should enable /Dog rest endpoint", + name: 'should enable /Dog rest endpoint', testFunction: async (t, ctx) => { const response = await fetch(`${ctx.rest}/Dog/0`, { headers: { @@ -9,11 +9,11 @@ export const base = [ }, }); const json = await response.json(); - + t.assert.deepStrictEqual(json, { id: '0', name: 'Lincoln', breed: 'Shepherd' }); - } - } -] + }, + }, +]; export const next15 = [ { @@ -22,11 +22,11 @@ export const next15 = [ const response = await fetch(`${ctx.rest}/`, { headers: { 'Content-Type': 'text/html', - } + }, }); - + const text = await response.text(); t.assert.match(text, /Next\.js v15/); - } - } -]; \ No newline at end of file + }, + }, +]; From 4270782153b33bc488704c44063d63314ffde756 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Sun, 1 Dec 2024 20:05:24 -0700 Subject: [PATCH 25/32] no more concurrency. breaks computer --- CONTRIBUTING.md | 3 ++ util/base.dockerfile | 34 +++++++++++----- util/cache-bust.js | 15 +++++++ util/pretest.js | 93 +++++++++++++++++++++----------------------- 4 files changed, 87 insertions(+), 58 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 util/cache-bust.js diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..39a0fff --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,3 @@ +## How publishing this module works + +HarperDB Components require the `config.yaml` to be in the root of the project; however, in order for the Docker \ No newline at end of file diff --git a/util/base.dockerfile b/util/base.dockerfile index 5324c9d..a8153e1 100644 --- a/util/base.dockerfile +++ b/util/base.dockerfile @@ -9,22 +9,36 @@ RUN apt-get update && apt-get install -y \ curl \ && rm -rf /var/lib/apt/lists/* -RUN mkdir -p /@harperdb/nextjs - -COPY --exclude=.github --exclude=fixtures --exclude=test --exclude=util --exclude=node_modules --exclude=.node-version --exclude=.git \ - . /@harperdb/nextjs - -RUN npm install -C /@harperdb/nextjs - +# Install HarperDB Globally RUN npm install -g harperdb -RUN mkdir -p /hdb/components - +# Set HarperDB Environment Variables ENV TC_AGREEMENT=yes ENV HDB_ADMIN_USERNAME=hdb_admin ENV HDB_ADMIN_PASSWORD=password ENV ROOTPATH=/hdb ENV OPERATIONSAPI_NETWORK_PORT=9925 ENV HTTP_PORT=9926 +ENV THREADS_COUNT=1 +ENV LOG_TO_STDSTREAMS=true +ENV LOG_TO_FILE=true + +# Create components directory +RUN mkdir -p /hdb/components + +# Add base component +COPY /fixtures/harperdb-base-component /hdb/components/harperdb-base-component + +# Create the @harperdb/nextjs module directory so it can be linked locally +RUN mkdir -p /@harperdb/nextjs + +# Cache Bust copying project files +ARG CACHE_BUST +RUN echo "${CACHE_BUST}" +COPY config.yaml extension.js cli.js schema.graphql package.json /@harperdb/nextjs/ + +# Install dependencies for the @harperdb/nextjs module +RUN npm install -C /@harperdb/nextjs -COPY /fixtures/harperdb-base-component /hdb/components/harperdb-base-component \ No newline at end of file +# Create link to the @harperdb/nextjs module +RUN npm link -C /@harperdb/nextjs \ No newline at end of file diff --git a/util/cache-bust.js b/util/cache-bust.js new file mode 100644 index 0000000..3244b50 --- /dev/null +++ b/util/cache-bust.js @@ -0,0 +1,15 @@ +import { spawn } from 'child_process'; +import { createHash } from 'crypto'; +import { join } from 'path'; +import { pipeline } from 'stream/promises'; + +export function getCacheBustValue() { + return new Promise((resolve, reject) => { + const proc = spawn('git', ['status', '--porcelain'], { cwd: join(import.meta.dirname, '..') }) + proc.on('error', reject); + const hash = createHash('sha1'); + pipeline(proc.stdout, hash).then(() => resolve(hash.digest('hex'))).catch(reject); + }); +} + +export const CACHE_BUST = getCacheBustValue(); diff --git a/util/pretest.js b/util/pretest.js index 90cf43c..38db212 100644 --- a/util/pretest.js +++ b/util/pretest.js @@ -3,13 +3,29 @@ import { join } from 'node:path'; import { containerEngine } from './get-container-engine.js'; import { getNextImageName } from './get-next-image-name.js'; import { CollectedTransform } from './collected-transform.js'; +import { CACHE_BUST } from './cache-bust.js'; const DEBUG = process.env.DEBUG === '1'; const NODE_MAJORS = ['18', '20', '22']; +const NEXT_MAJORS = ['13', '14', '15']; const getNodeBaseImageName = (nodeMajor) => `harperdb-nextjs/node-base-${nodeMajor}`; +function validateResult(result) { + const success = result.code === 0; + + if (DEBUG || !success) { + console.log(`Image \x1b[94m${result.name}\x1b[0m build process exited with: \x1b[35m${result.code}\x1b[0m\n`); + result.stdout !== '' && console.log('\x1b[32mstdout\x1b[0m:\n' + result.stdout + '\n'); + result.stderr !== '' && console.log('\x1b[31mstderr\x1b[0m:\n' + result.stderr + '\n'); + } + + if (!success) { + process.exit(1); + } +} + function build(name, args, options = {}) { return new Promise((resolve, reject) => { const buildProcess = spawn(containerEngine, args, { stdio: ['ignore', 'pipe', 'pipe'], ...options }); @@ -30,59 +46,40 @@ function build(name, args, options = {}) { } // Build Node Base Images -const nodeImagesBuildResults = await Promise.all( - NODE_MAJORS.map((nodeMajor) => - build(getNodeBaseImageName(nodeMajor), [ +for (const nodeMajor of NODE_MAJORS) { + const buildResult = await build(getNodeBaseImageName(nodeMajor), [ + 'build', + '--build-arg', + `NODE_MAJOR=${nodeMajor}`, + '--build-arg', + `CACHE_BUST=${CACHE_BUST}`, + '-t', + getNodeBaseImageName(nodeMajor), + '-f', + join(import.meta.dirname, 'base.dockerfile'), + join(import.meta.dirname, '..'), + ]); + + validateResult(buildResult); +} + +// Build Next.js Images + +for (const nextMajor of NEXT_MAJORS) { + for (const nodeMajor of NODE_MAJORS) { + const buildResult = await build(getNextImageName(nextMajor, nodeMajor), [ 'build', '--build-arg', - `NODE_MAJOR=${nodeMajor}`, + `BASE_IMAGE=${getNodeBaseImageName(nodeMajor)}`, + '--build-arg', + `NEXT_MAJOR=${nextMajor}`, '-t', - getNodeBaseImageName(nodeMajor), + getNextImageName(nextMajor, nodeMajor), '-f', - join(import.meta.dirname, 'base.dockerfile'), + join(import.meta.dirname, 'next.dockerfile'), join(import.meta.dirname, '..'), - ]) - ) -); - -validateResults(nodeImagesBuildResults); - -const NEXT_MAJORS = ['13', '14', '15']; + ]); -for (const nextMajor of NEXT_MAJORS) { - const nextImageBuildResults = await Promise.all( - NODE_MAJORS.map((nodeMajor) => - build(getNextImageName(nextMajor, nodeMajor), [ - 'build', - '--build-arg', - `BASE_IMAGE=${getNodeBaseImageName(nodeMajor)}`, - '--build-arg', - `NEXT_MAJOR=${nextMajor}`, - '-t', - getNextImageName(nextMajor, nodeMajor), - '-f', - join(import.meta.dirname, 'next.dockerfile'), - join(import.meta.dirname, '..'), - ]) - ) - ); - - validateResults(nextImageBuildResults); -} - -function validateResults(results) { - let success = true; - for (const result of results) { - if (result.code !== 0) success = false; - - if (DEBUG || !success) { - console.log(`Image \x1b[94m${result.name}\x1b[0m build process exited with: \x1b[35m${result.code}\x1b[0m\n`); - result.stdout !== '' && console.log('\x1b[32mstdout\x1b[0m:\n' + result.stdout + '\n'); - result.stderr !== '' && console.log('\x1b[31mstderr\x1b[0m:\n' + result.stderr + '\n'); - } - } - - if (!success) { - process.exit(1); + validateResult(buildResult); } } From 4371ab362b42744388bb8154a14a07524577c590 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 2 Dec 2024 16:03:11 -0700 Subject: [PATCH 26/32] prepare for review --- .gitignore | 4 +- CONTRIBUTING.md | 2 +- fixtures/next-13/.npmrc | 1 + fixtures/next-13/app/layout.js | 2 +- fixtures/next-13/app/page.js | 2 +- fixtures/next-13/package.json | 9 +- fixtures/next-14/.npmrc | 1 + fixtures/next-14/app/layout.js | 2 +- fixtures/next-14/app/page.js | 2 +- fixtures/next-14/package.json | 7 +- fixtures/next-15/.npmrc | 1 + fixtures/next-15/package-lock.json | 901 ------------------ fixtures/next-15/package.json | 7 +- package-lock.json | 64 ++ package.json | 5 +- playwright.config.js | 22 + test/next-13.test.js | 13 + test/next-14.test.js | 13 + test/next-15-node-18.test.js | 26 - test/next-15-node-20.test.js | 19 - test/next-15-node-22.test.js | 19 - test/next-15.test.js | 13 + util/cache-bust.js | 34 +- util/constants-and-names.js | 23 + ...ontainer-engine.js => container-engine.js} | 6 +- util/{ => docker}/base.dockerfile | 0 util/{ => docker}/next.dockerfile | 6 +- util/fixture.js | 48 +- util/get-next-image-name.js | 3 - util/{ => scripts}/pretest.js | 32 +- util/test-fixture.js | 29 + util/tests.js | 32 - 32 files changed, 275 insertions(+), 1073 deletions(-) create mode 100644 fixtures/next-13/.npmrc create mode 100644 fixtures/next-14/.npmrc create mode 100644 fixtures/next-15/.npmrc delete mode 100644 fixtures/next-15/package-lock.json create mode 100644 playwright.config.js create mode 100644 test/next-13.test.js create mode 100644 test/next-14.test.js delete mode 100644 test/next-15-node-18.test.js delete mode 100644 test/next-15-node-20.test.js delete mode 100644 test/next-15-node-22.test.js create mode 100644 test/next-15.test.js create mode 100644 util/constants-and-names.js rename util/{get-container-engine.js => container-engine.js} (64%) rename util/{ => docker}/base.dockerfile (100%) rename util/{ => docker}/next.dockerfile (52%) delete mode 100644 util/get-next-image-name.js rename util/{ => scripts}/pretest.js (67%) create mode 100644 util/test-fixture.js delete mode 100644 util/tests.js diff --git a/.gitignore b/.gitignore index 40b878d..289e347 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -node_modules/ \ No newline at end of file +node_modules/ +.next/ +test-results/ \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 39a0fff..729fe31 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,3 @@ ## How publishing this module works -HarperDB Components require the `config.yaml` to be in the root of the project; however, in order for the Docker \ No newline at end of file +HarperDB Components require the `config.yaml` to be in the root of the project; however, in order for the Docker diff --git a/fixtures/next-13/.npmrc b/fixtures/next-13/.npmrc new file mode 100644 index 0000000..9cf9495 --- /dev/null +++ b/fixtures/next-13/.npmrc @@ -0,0 +1 @@ +package-lock=false \ No newline at end of file diff --git a/fixtures/next-13/app/layout.js b/fixtures/next-13/app/layout.js index ee00d88..4ace4d7 100644 --- a/fixtures/next-13/app/layout.js +++ b/fixtures/next-13/app/layout.js @@ -1,5 +1,5 @@ export const metadata = { - title: 'HarperDB - Next.js v15 App', + title: 'HarperDB - Next.js v13 App', }; export default function RootLayout({ children }) { diff --git a/fixtures/next-13/app/page.js b/fixtures/next-13/app/page.js index 4a48cf9..b3b670a 100644 --- a/fixtures/next-13/app/page.js +++ b/fixtures/next-13/app/page.js @@ -1,7 +1,7 @@ export default async function Page() { return (
-

Next.js v15

+

Next.js v13

); } diff --git a/fixtures/next-13/package.json b/fixtures/next-13/package.json index a12eadb..83fa08e 100644 --- a/fixtures/next-13/package.json +++ b/fixtures/next-13/package.json @@ -5,10 +5,13 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "next lint" + "lint": "next lint", + "postinstall": "npm link @harperdb/nextjs" }, "dependencies": { - "@harperdb/nextjs": "file:/@harperdb/nextjs", - "next": "13.5.7" + "@harperdb/nextjs": "*", + "react": "^18", + "react-dom": "^18", + "next": "^13" } } diff --git a/fixtures/next-14/.npmrc b/fixtures/next-14/.npmrc new file mode 100644 index 0000000..9cf9495 --- /dev/null +++ b/fixtures/next-14/.npmrc @@ -0,0 +1 @@ +package-lock=false \ No newline at end of file diff --git a/fixtures/next-14/app/layout.js b/fixtures/next-14/app/layout.js index ee00d88..7fd8992 100644 --- a/fixtures/next-14/app/layout.js +++ b/fixtures/next-14/app/layout.js @@ -1,5 +1,5 @@ export const metadata = { - title: 'HarperDB - Next.js v15 App', + title: 'HarperDB - Next.js v14 App', }; export default function RootLayout({ children }) { diff --git a/fixtures/next-14/app/page.js b/fixtures/next-14/app/page.js index 4a48cf9..9d17389 100644 --- a/fixtures/next-14/app/page.js +++ b/fixtures/next-14/app/page.js @@ -1,7 +1,7 @@ export default async function Page() { return (
-

Next.js v15

+

Next.js v14

); } diff --git a/fixtures/next-14/package.json b/fixtures/next-14/package.json index db6467a..63ac233 100644 --- a/fixtures/next-14/package.json +++ b/fixtures/next-14/package.json @@ -5,12 +5,13 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "next lint" + "lint": "next lint", + "postinstall": "npm link @harperdb/nextjs" }, "dependencies": { - "@harperdb/nextjs": "file:/@harperdb/nextjs", + "@harperdb/nextjs": "*", "react": "^18", "react-dom": "^18", - "next": "14.2.18" + "next": "^14" } } diff --git a/fixtures/next-15/.npmrc b/fixtures/next-15/.npmrc new file mode 100644 index 0000000..9cf9495 --- /dev/null +++ b/fixtures/next-15/.npmrc @@ -0,0 +1 @@ +package-lock=false \ No newline at end of file diff --git a/fixtures/next-15/package-lock.json b/fixtures/next-15/package-lock.json deleted file mode 100644 index 0f8978f..0000000 --- a/fixtures/next-15/package-lock.json +++ /dev/null @@ -1,901 +0,0 @@ -{ - "name": "next-15", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "next-15", - "dependencies": { - "@harperdb/nextjs": "^0.0.12", - "next": "15.0.3", - "react": "19.0.0-rc-66855b96-20241106", - "react-dom": "19.0.0-rc-66855b96-20241106" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", - "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@harperdb/nextjs": { - "version": "0.0.12", - "resolved": "https://registry.npmjs.org/@harperdb/nextjs/-/nextjs-0.0.12.tgz", - "integrity": "sha512-QClIwYACdhv+iVyvyrJiq5/IFbR/GN4P5CvEi7FdCsu2yT3S/dJkUVni03HMKWGEmlE1NfiwcvJtQb3m4Az1sA==", - "license": "MIT", - "dependencies": { - "shell-quote": "^1.8.1" - }, - "bin": { - "harperdb-nextjs": "cli.js" - } - }, - "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-s390x": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", - "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", - "cpu": [ - "s390x" - ], - "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-libvips-linuxmusl-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", - "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", - "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", - "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-s390x": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", - "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", - "cpu": [ - "s390x" - ], - "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-s390x": "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-linuxmusl-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", - "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", - "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-linuxmusl-arm64": "1.0.4" - } - }, - "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", - "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", - "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-linuxmusl-x64": "1.0.4" - } - }, - "node_modules/@img/sharp-wasm32": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", - "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", - "cpu": [ - "wasm32" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", - "optional": true, - "dependencies": { - "@emnapi/runtime": "^1.2.0" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-ia32": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", - "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", - "cpu": [ - "ia32" - ], - "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/@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/@next/env": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.0.3.tgz", - "integrity": "sha512-t9Xy32pjNOvVn2AS+Utt6VmyrshbpfUMhIjFO60gI58deSo/KgLOp31XZ4O+kY/Is8WAGYwA5gR7kOb1eORDBA==", - "license": "MIT" - }, - "node_modules/@next/swc-darwin-arm64": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.0.3.tgz", - "integrity": "sha512-s3Q/NOorCsLYdCKvQlWU+a+GeAd3C8Rb3L1YnetsgwXzhc3UTWrtQpB/3eCjFOdGUj5QmXfRak12uocd1ZiiQw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-darwin-x64": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.0.3.tgz", - "integrity": "sha512-Zxl/TwyXVZPCFSf0u2BNj5sE0F2uR6iSKxWpq4Wlk/Sv9Ob6YCKByQTkV2y6BCic+fkabp9190hyrDdPA/dNrw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.0.3.tgz", - "integrity": "sha512-T5+gg2EwpsY3OoaLxUIofmMb7ohAUlcNZW0fPQ6YAutaWJaxt1Z1h+8zdl4FRIOr5ABAAhXtBcpkZNwUcKI2fw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.0.3.tgz", - "integrity": "sha512-WkAk6R60mwDjH4lG/JBpb2xHl2/0Vj0ZRu1TIzWuOYfQ9tt9NFsIinI1Epma77JVgy81F32X/AeD+B2cBu/YQA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.0.3.tgz", - "integrity": "sha512-gWL/Cta1aPVqIGgDb6nxkqy06DkwJ9gAnKORdHWX1QBbSZZB+biFYPFti8aKIQL7otCE1pjyPaXpFzGeG2OS2w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.0.3.tgz", - "integrity": "sha512-QQEMwFd8r7C0GxQS62Zcdy6GKx999I/rTO2ubdXEe+MlZk9ZiinsrjwoiBL5/57tfyjikgh6GOU2WRQVUej3UA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.0.3.tgz", - "integrity": "sha512-9TEp47AAd/ms9fPNgtgnT7F3M1Hf7koIYYWCMQ9neOwjbVWJsHZxrFbI3iEDJ8rf1TDGpmHbKxXf2IFpAvheIQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.0.3.tgz", - "integrity": "sha512-VNAz+HN4OGgvZs6MOoVfnn41kBzT+M+tB+OK4cww6DNyWS6wKaDpaAm/qLeOUbnMh0oVx1+mg0uoYARF69dJyA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@swc/counter": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "license": "Apache-2.0" - }, - "node_modules/@swc/helpers": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.13.tgz", - "integrity": "sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001684", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001684.tgz", - "integrity": "sha512-G1LRwLIQjBQoyq0ZJGqGIJUXzJ8irpbjHLpVRXDvBEScFJ9b17sgK6vlx0GAJFE21okD7zXl08rRRUfq6HdoEQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/client-only": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", - "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", - "license": "MIT" - }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "license": "MIT", - "optional": true, - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT", - "optional": true - }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "license": "MIT", - "optional": true, - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", - "license": "Apache-2.0", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "license": "MIT", - "optional": true - }, - "node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/next": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/next/-/next-15.0.3.tgz", - "integrity": "sha512-ontCbCRKJUIoivAdGB34yCaOcPgYXr9AAkV/IwqFfWWTXEPUgLYkSkqBhIk9KK7gGmgjc64B+RdoeIDM13Irnw==", - "license": "MIT", - "dependencies": { - "@next/env": "15.0.3", - "@swc/counter": "0.1.3", - "@swc/helpers": "0.5.13", - "busboy": "1.6.0", - "caniuse-lite": "^1.0.30001579", - "postcss": "8.4.31", - "styled-jsx": "5.1.6" - }, - "bin": { - "next": "dist/bin/next" - }, - "engines": { - "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" - }, - "optionalDependencies": { - "@next/swc-darwin-arm64": "15.0.3", - "@next/swc-darwin-x64": "15.0.3", - "@next/swc-linux-arm64-gnu": "15.0.3", - "@next/swc-linux-arm64-musl": "15.0.3", - "@next/swc-linux-x64-gnu": "15.0.3", - "@next/swc-linux-x64-musl": "15.0.3", - "@next/swc-win32-arm64-msvc": "15.0.3", - "@next/swc-win32-x64-msvc": "15.0.3", - "sharp": "^0.33.5" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.1.0", - "@playwright/test": "^1.41.2", - "babel-plugin-react-compiler": "*", - "react": "^18.2.0 || 19.0.0-rc-66855b96-20241106", - "react-dom": "^18.2.0 || 19.0.0-rc-66855b96-20241106", - "sass": "^1.3.0" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - }, - "@playwright/test": { - "optional": true - }, - "babel-plugin-react-compiler": { - "optional": true - }, - "sass": { - "optional": true - } - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/react": { - "version": "19.0.0-rc-66855b96-20241106", - "resolved": "https://registry.npmjs.org/react/-/react-19.0.0-rc-66855b96-20241106.tgz", - "integrity": "sha512-klH7xkT71SxRCx4hb1hly5FJB21Hz0ACyxbXYAECEqssUjtJeFUAaI2U1DgJAzkGEnvEm3DkxuBchMC/9K4ipg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "19.0.0-rc-66855b96-20241106", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0-rc-66855b96-20241106.tgz", - "integrity": "sha512-D25vdaytZ1wFIRiwNU98NPQ/upS2P8Co4/oNoa02PzHbh8deWdepjm5qwZM/46OdSiGv4WSWwxP55RO9obqJEQ==", - "license": "MIT", - "dependencies": { - "scheduler": "0.25.0-rc-66855b96-20241106" - }, - "peerDependencies": { - "react": "19.0.0-rc-66855b96-20241106" - } - }, - "node_modules/scheduler": { - "version": "0.25.0-rc-66855b96-20241106", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0-rc-66855b96-20241106.tgz", - "integrity": "sha512-HQXp/Mnp/MMRSXMQF7urNFla+gmtXW/Gr1KliuR0iboTit4KvZRY8KYaq5ccCTAOJiUqQh2rE2F3wgUekmgdlA==", - "license": "MIT" - }, - "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "license": "ISC", - "optional": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/sharp": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", - "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", - "hasInstallScript": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.3", - "semver": "^7.6.3" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.33.5", - "@img/sharp-darwin-x64": "0.33.5", - "@img/sharp-libvips-darwin-arm64": "1.0.4", - "@img/sharp-libvips-darwin-x64": "1.0.4", - "@img/sharp-libvips-linux-arm": "1.0.5", - "@img/sharp-libvips-linux-arm64": "1.0.4", - "@img/sharp-libvips-linux-s390x": "1.0.4", - "@img/sharp-libvips-linux-x64": "1.0.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", - "@img/sharp-libvips-linuxmusl-x64": "1.0.4", - "@img/sharp-linux-arm": "0.33.5", - "@img/sharp-linux-arm64": "0.33.5", - "@img/sharp-linux-s390x": "0.33.5", - "@img/sharp-linux-x64": "0.33.5", - "@img/sharp-linuxmusl-arm64": "0.33.5", - "@img/sharp-linuxmusl-x64": "0.33.5", - "@img/sharp-wasm32": "0.33.5", - "@img/sharp-win32-ia32": "0.33.5", - "@img/sharp-win32-x64": "0.33.5" - } - }, - "node_modules/shell-quote": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", - "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "license": "MIT", - "optional": true, - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/styled-jsx": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", - "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", - "license": "MIT", - "dependencies": { - "client-only": "0.0.1" - }, - "engines": { - "node": ">= 12.0.0" - }, - "peerDependencies": { - "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - } - } -} diff --git a/fixtures/next-15/package.json b/fixtures/next-15/package.json index ee2e14c..b5e33e1 100644 --- a/fixtures/next-15/package.json +++ b/fixtures/next-15/package.json @@ -5,12 +5,13 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "next lint" + "lint": "next lint", + "postinstall": "npm link @harperdb/nextjs" }, "dependencies": { - "@harperdb/nextjs": "file:/@harperdb/nextjs", + "@harperdb/nextjs": "*", "react": "19.0.0-rc-66855b96-20241106", "react-dom": "19.0.0-rc-66855b96-20241106", - "next": "15.0.3" + "next": "^15" } } diff --git a/package-lock.json b/package-lock.json index a749e34..4fbf814 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ }, "devDependencies": { "@harperdb/code-guidelines": "^0.0.2", + "@playwright/test": "^1.49.0", "prettier": "^3.3.3" } }, @@ -29,6 +30,69 @@ "prettier": "3.3.3" } }, + "node_modules/@playwright/test": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.0.tgz", + "integrity": "sha512-DMulbwQURa8rNIQrf94+jPJQ4FmOVdpE5ZppRNvWVjvhC+6sOeo28r8MgIpQRYouXRtt/FCCXU7zn20jnHR4Qw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.49.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.0.tgz", + "integrity": "sha512-eKpmys0UFDnfNb3vfsf8Vx2LEOtflgRebl0Im2eQQnYMA4Aqd+Zw8bEOB+7ZKvN76901mRnqdsiOGKxzVTbi7A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.49.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.0.tgz", + "integrity": "sha512-R+3KKTQF3npy5GTiKH/T+kdhoJfJojjHESR1YEWhYuEKRVfVaxH3+4+GvXE5xyCngCxhxnykk0Vlah9v8fs3jA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/prettier": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", diff --git a/package.json b/package.json index 5e3a493..ef40864 100644 --- a/package.json +++ b/package.json @@ -36,14 +36,15 @@ "format": "prettier .", "format:check": "npm run format -- --check", "format:fix": "npm run format -- --write", - "pretest": "node util/pretest.js", - "test": "node --test" + "pretest": "node util/scripts/pretest.js", + "test": "playwright test --workers 3" }, "dependencies": { "shell-quote": "^1.8.1" }, "devDependencies": { "@harperdb/code-guidelines": "^0.0.2", + "@playwright/test": "^1.49.0", "prettier": "^3.3.3" }, "prettier": "@harperdb/code-guidelines/prettier" diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 0000000..8a34c7c --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,22 @@ +import { defineConfig, devices } from '@playwright/test'; + +const NEXT_MAJORS = ['13', '14', '15']; +const NODE_MAJORS = ['18', '20', '22']; + +export default defineConfig({ + testDir: 'test', + fullyParallel: true, + forbidOnly: !!process.env.CI, + workers: 3, + retries: 2, + expect: { + timeout: 30000, + }, + projects: NEXT_MAJORS.flatMap((nextMajor) => + NODE_MAJORS.map((nodeMajor) => ({ + name: `Next.js v${nextMajor} - Node.js v${nodeMajor}`, + use: { versions: { nextMajor, nodeMajor }, ...devices['Desktop Chrome'] }, + testMatch: [`test/next-${nextMajor}.test.js`], + })) + ), +}); diff --git a/test/next-13.test.js b/test/next-13.test.js new file mode 100644 index 0000000..ba46df8 --- /dev/null +++ b/test/next-13.test.js @@ -0,0 +1,13 @@ +import { test, expect } from '../util/test-fixture.js'; + +test.describe.configure({ mode: 'serial' }); + +test('home page', async ({ nextApp, page }) => { + await page.goto(nextApp.rest.toString()); + await expect(page.locator('h1')).toHaveText('Next.js v13'); +}); + +test('title', async ({ nextApp, page }) => { + await page.goto(nextApp.rest.toString()); + await expect(page).toHaveTitle('HarperDB - Next.js v13 App'); +}); diff --git a/test/next-14.test.js b/test/next-14.test.js new file mode 100644 index 0000000..25e590e --- /dev/null +++ b/test/next-14.test.js @@ -0,0 +1,13 @@ +import { test, expect } from '../util/test-fixture.js'; + +test.describe.configure({ mode: 'serial' }); + +test('home page', async ({ nextApp, page }) => { + await page.goto(nextApp.rest.toString()); + await expect(page.locator('h1')).toHaveText('Next.js v14'); +}); + +test('title', async ({ nextApp, page }) => { + await page.goto(nextApp.rest.toString()); + await expect(page).toHaveTitle('HarperDB - Next.js v14 App'); +}); diff --git a/test/next-15-node-18.test.js b/test/next-15-node-18.test.js deleted file mode 100644 index 3e67c7b..0000000 --- a/test/next-15-node-18.test.js +++ /dev/null @@ -1,26 +0,0 @@ -import { suite, test, before, after } from 'node:test'; -import { base, next15 } from '../util/tests.js'; -import { Fixture } from '../util/fixture.js'; - -suite('Next.js v15 - Node.js v18', async () => { - const ctx = {}; - - before(async () => { - ctx.fixture = new Fixture({ nextMajor: '15', nodeMajor: '18' }); - await ctx.fixture.ready; - - const restPort = ctx.fixture.portMap.get('9926'); - - if (!restPort) { - throw new Error('Rest port not found'); - } - - ctx.rest = new URL(`http://${restPort}`); - }); - - await Promise.all(base.concat(next15).map(async ({ name, testFunction }) => test(name, (t) => testFunction(t, ctx)))); - - after(async () => { - await ctx.fixture.clear(); - }); -}); diff --git a/test/next-15-node-20.test.js b/test/next-15-node-20.test.js deleted file mode 100644 index 36ee7ac..0000000 --- a/test/next-15-node-20.test.js +++ /dev/null @@ -1,19 +0,0 @@ -import { suite, test, before, after } from 'node:test'; -import { base, next15 } from '../util/tests.js'; -import { Fixture } from '../util/fixture.js'; - -suite('Next.js v15 - Node.js v20', async () => { - const ctx = {}; - - before(async () => { - ctx.fixture = new Fixture({ nextMajor: '15', nodeMajor: '20' }); - await ctx.fixture.ready; - ctx.rest = new URL(`http://${ctx.fixture.portMap.get('9926')}`); - }); - - await Promise.all(base.concat(next15).map(async ({ name, testFunction }) => test(name, (t) => testFunction(t, ctx)))); - - after(async () => { - await ctx.fixture.clear(); - }); -}); diff --git a/test/next-15-node-22.test.js b/test/next-15-node-22.test.js deleted file mode 100644 index 8304ac6..0000000 --- a/test/next-15-node-22.test.js +++ /dev/null @@ -1,19 +0,0 @@ -import { suite, test, before, after } from 'node:test'; -import { base, next15 } from '../util/tests.js'; -import { Fixture } from '../util/fixture.js'; - -suite('Next.js v15 - Node.js v22', async () => { - const ctx = {}; - - before(async () => { - ctx.fixture = new Fixture({ nextMajor: '15', nodeMajor: '22' }); - await ctx.fixture.ready; - ctx.rest = new URL(`http://${ctx.fixture.portMap.get('9926')}`); - }); - - await Promise.all(base.concat(next15).map(async ({ name, testFunction }) => test(name, (t) => testFunction(t, ctx)))); - - after(async () => { - await ctx.fixture.clear(); - }); -}); diff --git a/test/next-15.test.js b/test/next-15.test.js new file mode 100644 index 0000000..bc985e3 --- /dev/null +++ b/test/next-15.test.js @@ -0,0 +1,13 @@ +import { test, expect } from '../util/test-fixture.js'; + +test.describe.configure({ mode: 'serial' }); + +test('home page', async ({ nextApp, page }) => { + await page.goto(nextApp.rest.toString()); + await expect(page.locator('h1')).toHaveText('Next.js v15'); +}); + +test('title', async ({ nextApp, page }) => { + await page.goto(nextApp.rest.toString()); + await expect(page).toHaveTitle('HarperDB - Next.js v15 App'); +}); diff --git a/util/cache-bust.js b/util/cache-bust.js index 3244b50..75c5edd 100644 --- a/util/cache-bust.js +++ b/util/cache-bust.js @@ -1,15 +1,33 @@ -import { spawn } from 'child_process'; -import { createHash } from 'crypto'; -import { join } from 'path'; -import { pipeline } from 'stream/promises'; +import { spawn } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { join } from 'node:path'; +import { pipeline } from 'node:stream/promises'; +import { readdirSync } from 'node:fs'; -export function getCacheBustValue() { +import { ROOT } from './constants-and-names.js'; + +export function getCacheBustValue(files) { return new Promise((resolve, reject) => { - const proc = spawn('git', ['status', '--porcelain'], { cwd: join(import.meta.dirname, '..') }) + const proc = spawn('git', ['status', '--porcelain', ...files], { cwd: ROOT }); + proc.on('error', reject); + const hash = createHash('sha1'); - pipeline(proc.stdout, hash).then(() => resolve(hash.digest('hex'))).catch(reject); + + pipeline(proc.stdout, hash) + .then(() => resolve(hash.digest('hex'))) + .catch(reject); }); } -export const CACHE_BUST = getCacheBustValue(); +export const MODULE_CACHE_BUST = getCacheBustValue([ + 'config.yaml', + 'cli.js', + 'extension.js', + 'schema.graphql', + 'package.json', +]); + +export function getNextFixtureCacheBustValue(nextMajor) { + return getCacheBustValue(readdirSync(join(ROOT, 'fixtures', `next-${nextMajor}`))); +} diff --git a/util/constants-and-names.js b/util/constants-and-names.js new file mode 100644 index 0000000..5d4d00b --- /dev/null +++ b/util/constants-and-names.js @@ -0,0 +1,23 @@ +import { join } from 'node:path'; + +export const ROOT = join(import.meta.dirname, '..'); + +export const NEXT_MAJORS = ['13', '14', '15']; + +export const NODE_MAJORS = ['18', '20', '22']; + +export const PORTS = ['9925', '9926']; + +export const CONTAINER_ENGINE_LIST = ['podman', 'docker']; + +export function getNodeBaseImageName(nodeMajor) { + return `harperdb-nextjs/node-base-${nodeMajor}`; +} + +export function getNextImageName(nextMajor, nodeMajor) { + return `harperdb-nextjs/test-image-next-${nextMajor}-node-${nodeMajor}`; +} + +export function getNextContainerName(nextMajor, nodeMajor) { + return `harperdb-nextjs-test-container-next-${nextMajor}-node-${nodeMajor}`; +} diff --git a/util/get-container-engine.js b/util/container-engine.js similarity index 64% rename from util/get-container-engine.js rename to util/container-engine.js index 0708692..185f5ec 100644 --- a/util/get-container-engine.js +++ b/util/container-engine.js @@ -1,6 +1,6 @@ -import { spawnSync } from 'child_process'; +import { spawnSync } from 'node:child_process'; -const CONTAINER_ENGINE_LIST = ['podman', 'docker']; +import { CONTAINER_ENGINE_LIST } from './constants-and-names.js'; export function getContainerEngine() { for (const engine of CONTAINER_ENGINE_LIST) { @@ -13,4 +13,4 @@ export function getContainerEngine() { throw new Error(`No container engine found in ${CONTAINER_ENGINE_LIST.join(', ')}`); } -export const containerEngine = getContainerEngine(); +export const CONTAINER_ENGINE = getContainerEngine(); diff --git a/util/base.dockerfile b/util/docker/base.dockerfile similarity index 100% rename from util/base.dockerfile rename to util/docker/base.dockerfile diff --git a/util/next.dockerfile b/util/docker/next.dockerfile similarity index 52% rename from util/next.dockerfile rename to util/docker/next.dockerfile index 0e41386..188d49b 100644 --- a/util/next.dockerfile +++ b/util/docker/next.dockerfile @@ -7,9 +7,11 @@ FROM ${BASE_IMAGE} ARG NEXT_MAJOR -COPY fixtures/next-${NEXT_MAJOR} /hdb/components/next-${NEXT_MAJOR} +ARG CACHE_BUST +RUN echo "${CACHE_BUST}" +COPY --exclude=node_modules --exclude=.next fixtures/next-${NEXT_MAJOR} /hdb/components/next-${NEXT_MAJOR} -RUN npm install -C hdb/components/next-${NEXT_MAJOR} +RUN cd hdb/components/next-${NEXT_MAJOR} && npm install EXPOSE 9925 9926 diff --git a/util/fixture.js b/util/fixture.js index 414da2e..87ecf5a 100644 --- a/util/fixture.js +++ b/util/fixture.js @@ -1,17 +1,26 @@ -import child_process from 'node:child_process'; +import { spawn, spawnSync } from 'node:child_process'; import { Transform } from 'node:stream'; -import { getNextImageName } from './get-next-image-name.js'; -import { containerEngine } from './get-container-engine.js'; + +import { getNextImageName, getNextContainerName, NEXT_MAJORS, NODE_MAJORS, PORTS } from './constants-and-names.js'; +import { CONTAINER_ENGINE } from './container-engine.js'; +import { CollectedTransform } from './collected-transform.js'; export class Fixture { constructor({ autoSetup = true, debug = false, nextMajor, nodeMajor }) { + if (!NEXT_MAJORS.includes(nextMajor)) { + throw new Error(`nextMajor must be one of ${NEXT_MAJORS.join(', ')}`); + } this.nextMajor = nextMajor; + + if (!NODE_MAJORS.includes(nodeMajor)) { + throw new Error(`nodeMajor must be one of ${NODE_MAJORS.join(', ')}`); + } this.nodeMajor = nodeMajor; this.debug = debug || process.env.DEBUG === '1'; this.imageName = getNextImageName(nextMajor, nodeMajor); - this.containerName = `hdb-next-integration-test-container-next-${nextMajor}-node-${nodeMajor}`; + this.containerName = getNextContainerName(nextMajor, nodeMajor); if (autoSetup) { this.ready = this.clear().then(() => this.run()); @@ -24,7 +33,7 @@ export class Fixture { #runCommand(args = [], options = {}) { return new Promise((resolve, reject) => { - const childProcess = child_process.spawn(containerEngine, args, { + const childProcess = spawn(CONTAINER_ENGINE, args, { stdio: this.#stdio, ...options, }); @@ -44,22 +53,11 @@ export class Fixture { clear() { return new Promise((resolve, reject) => { - const psProcess = child_process.spawn(containerEngine, ['ps', '-aq', '-f', `name=${this.containerName}`]); + const psProcess = spawn(CONTAINER_ENGINE, ['ps', '-aq', '-f', `name=${this.containerName}`]); psProcess.on('error', reject); - const collectedStdout = psProcess.stdout.pipe( - new Transform({ - construct(callback) { - this.chunks = []; - callback(null); - }, - transform(chunk, encoding, callback) { - this.chunks.push(chunk); - callback(null, chunk); - }, - }) - ); + const collectedStdout = psProcess.stdout.pipe(new CollectedTransform()); if (this.debug) { collectedStdout.pipe(process.stdout); @@ -67,7 +65,7 @@ export class Fixture { } psProcess.on('exit', (code) => { - if (code === 0 && collectedStdout.chunks.length !== 0) { + if (code === 0 && collectedStdout.output !== '') { return this.stop() .then(() => this.rm()) .then(resolve, reject); @@ -79,11 +77,9 @@ export class Fixture { run() { return new Promise((resolve, reject) => { - const runProcess = child_process.spawn( - containerEngine, - ['run', '-P', '--name', this.containerName, this.imageName], - { stdio: ['ignore', 'pipe', this.debug ? 'inherit' : 'ignore'] } - ); + const runProcess = spawn(CONTAINER_ENGINE, ['run', '-P', '--name', this.containerName, this.imageName], { + stdio: ['ignore', 'pipe', this.debug ? 'inherit' : 'ignore'], + }); runProcess.on('error', reject); runProcess.on('exit', resolve); @@ -107,8 +103,8 @@ export class Fixture { get portMap() { const portMap = new Map(); - for (const port of ['9925', '9926']) { - const { stdout } = child_process.spawnSync(containerEngine, ['port', this.containerName, port]); + for (const port of PORTS) { + const { stdout } = spawnSync(CONTAINER_ENGINE, ['port', this.containerName, port]); portMap.set(port, stdout.toString().trim()); } return portMap; diff --git a/util/get-next-image-name.js b/util/get-next-image-name.js deleted file mode 100644 index 0845860..0000000 --- a/util/get-next-image-name.js +++ /dev/null @@ -1,3 +0,0 @@ -export function getNextImageName(nextMajor, nodeMajor) { - return `harperdb-nextjs/test-image-next-${nextMajor}-node-${nodeMajor}`; -} diff --git a/util/pretest.js b/util/scripts/pretest.js similarity index 67% rename from util/pretest.js rename to util/scripts/pretest.js index 38db212..07d0139 100644 --- a/util/pretest.js +++ b/util/scripts/pretest.js @@ -1,16 +1,12 @@ import { spawn } from 'node:child_process'; import { join } from 'node:path'; -import { containerEngine } from './get-container-engine.js'; -import { getNextImageName } from './get-next-image-name.js'; -import { CollectedTransform } from './collected-transform.js'; -import { CACHE_BUST } from './cache-bust.js'; -const DEBUG = process.env.DEBUG === '1'; - -const NODE_MAJORS = ['18', '20', '22']; -const NEXT_MAJORS = ['13', '14', '15']; +import { CONTAINER_ENGINE } from '../container-engine.js'; +import { CollectedTransform } from '../collected-transform.js'; +import { MODULE_CACHE_BUST, getNextFixtureCacheBustValue } from '../cache-bust.js'; +import { NODE_MAJORS, NEXT_MAJORS, ROOT, getNodeBaseImageName, getNextImageName } from '../constants-and-names.js'; -const getNodeBaseImageName = (nodeMajor) => `harperdb-nextjs/node-base-${nodeMajor}`; +const DEBUG = process.env.DEBUG === '1'; function validateResult(result) { const success = result.code === 0; @@ -28,12 +24,12 @@ function validateResult(result) { function build(name, args, options = {}) { return new Promise((resolve, reject) => { - const buildProcess = spawn(containerEngine, args, { stdio: ['ignore', 'pipe', 'pipe'], ...options }); + const buildProcess = spawn(CONTAINER_ENGINE, args, { cwd: ROOT, stdio: ['ignore', 'pipe', 'pipe'], ...options }); const collectedStdout = buildProcess.stdout.pipe(new CollectedTransform()); const collectedStderr = buildProcess.stderr.pipe(new CollectedTransform()); - buildProcess.on('error', (error) => reject(error)); + buildProcess.on('error', reject); buildProcess.on('close', (code) => resolve({ name, @@ -45,19 +41,19 @@ function build(name, args, options = {}) { }); } -// Build Node Base Images +// Build Node.js Base Images for (const nodeMajor of NODE_MAJORS) { const buildResult = await build(getNodeBaseImageName(nodeMajor), [ 'build', '--build-arg', `NODE_MAJOR=${nodeMajor}`, '--build-arg', - `CACHE_BUST=${CACHE_BUST}`, + `CACHE_BUST=${MODULE_CACHE_BUST}`, '-t', getNodeBaseImageName(nodeMajor), '-f', - join(import.meta.dirname, 'base.dockerfile'), - join(import.meta.dirname, '..'), + join(ROOT, 'util', 'docker', 'base.dockerfile'), + ROOT, ]); validateResult(buildResult); @@ -73,11 +69,13 @@ for (const nextMajor of NEXT_MAJORS) { `BASE_IMAGE=${getNodeBaseImageName(nodeMajor)}`, '--build-arg', `NEXT_MAJOR=${nextMajor}`, + '--build-arg', + `CACHE_BUST=${getNextFixtureCacheBustValue(nextMajor)}`, '-t', getNextImageName(nextMajor, nodeMajor), '-f', - join(import.meta.dirname, 'next.dockerfile'), - join(import.meta.dirname, '..'), + join(ROOT, 'util', 'docker', 'next.dockerfile'), + ROOT, ]); validateResult(buildResult); diff --git a/util/test-fixture.js b/util/test-fixture.js new file mode 100644 index 0000000..e95765b --- /dev/null +++ b/util/test-fixture.js @@ -0,0 +1,29 @@ +import { test as base } from '@playwright/test'; + +import { Fixture } from './fixture'; + +export const test = base.extend({ + versions: [ + { nextMajor: '', nodeMajor: '' }, + { option: true, scope: 'worker' }, + ], + nextApp: [ + async ({ versions: { nextMajor, nodeMajor } }, use) => { + const fixture = new Fixture({ nextMajor, nodeMajor }); + await fixture.ready; + + const rest = fixture.portMap.get('9926'); + + if (!rest) { + throw new Error('Rest port not found'); + } + + await use({ rest: `http://${rest}` }); + + await fixture.clear(); + }, + { scope: 'worker', timeout: 60000 }, + ], +}); + +export { expect } from '@playwright/test'; diff --git a/util/tests.js b/util/tests.js deleted file mode 100644 index 7335be1..0000000 --- a/util/tests.js +++ /dev/null @@ -1,32 +0,0 @@ -export const base = [ - { - name: 'should enable /Dog rest endpoint', - testFunction: async (t, ctx) => { - const response = await fetch(`${ctx.rest}/Dog/0`, { - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'Basic aGRiX2FkbWluOnBhc3N3b3Jk', - }, - }); - const json = await response.json(); - - t.assert.deepStrictEqual(json, { id: '0', name: 'Lincoln', breed: 'Shepherd' }); - }, - }, -]; - -export const next15 = [ - { - name: 'should render home page', - testFunction: async (t, ctx) => { - const response = await fetch(`${ctx.rest}/`, { - headers: { - 'Content-Type': 'text/html', - }, - }); - - const text = await response.text(); - t.assert.match(text, /Next\.js v15/); - }, - }, -]; From ba18a38d41af35b6cbbc063fa2c6341d0fc8294c Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 2 Dec 2024 16:15:38 -0700 Subject: [PATCH 27/32] fix CONTRIBUTING --- CONTRIBUTING.md | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 729fe31..94c0398 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,38 @@ -## How publishing this module works +# Contributing -HarperDB Components require the `config.yaml` to be in the root of the project; however, in order for the Docker +## Code Organization + +All code should abide by the following organization rules: + +- `fixtures/` is for directories that will be utilized in testing. + - They should be fully executable locally for debugging purposes + - They should be as minimal as possible +- `test/` is for Playwright based test files that are executed by `npm run test` + - Test files should use `import { test, expect } from '../util/test-fixture.js';` so that the correct **Fixture** is managed for the test script + - Test files should execute serially, and relevant to the given Next.js versions +- `util/` is for any non-module source code. This includes scripts (`util/scripts/`), docker configurations (`util/docker/`), or any other utility based code. + - Prime examples include the source code responsible for [_building_](./util/scripts/pretest.js) the **Fixtures**, or the custom Playwright [test fixture](./util/test-fixture.js) + +The key source files for the repo are: + +- `cli.js` +- `extension.js` +- `config.yaml` +- `schema.graphql` + +## Testing + +Testing for this repo uses containers in order to generate stable, isolated environments containing: + +- HarperDB +- Node.js +- A HarperDB Base Component (responsible for seeding the database) +- A Next.js application Component (which uses this `@harperdb/nextjs` extension) + +To execute tests, run `npm run test` + +The first run may take some time as the pretest script is building 12 separate images (3 Node.js ones, 9 Next.js ones). Note, at the moment this operation is parallelized as building is very expensive and can result in the system running out of resources (and crashing the build processes). Subsequent runs utilize the Docker build step cache and are very fast. + +After the images are built, [Playwright](https://playwright.dev/) will run the tests. These tests each utilize an image, and will manage a container instance relevant to the given Next.js and Node.js pair. + +The tests are configured with generous timeouts and limited to 3 workers at a time to not cause the system to run out of resources. From 0903bca7daece7003a4708886c1821985b2e9a2f Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 2 Dec 2024 16:18:21 -0700 Subject: [PATCH 28/32] fix workflow --- .github/workflows/test.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 2716789..dbad1cd 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -13,6 +13,11 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + version: latest + - name: Setup Node.js uses: actions/setup-node@v4 with: From 94830901d59cdafd97a59325c58addcf5921c14b Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 2 Dec 2024 16:22:44 -0700 Subject: [PATCH 29/32] come on docker --- util/docker/base.dockerfile | 2 ++ util/docker/next.dockerfile | 2 ++ 2 files changed, 4 insertions(+) diff --git a/util/docker/base.dockerfile b/util/docker/base.dockerfile index a8153e1..b3384a9 100644 --- a/util/docker/base.dockerfile +++ b/util/docker/base.dockerfile @@ -1,3 +1,5 @@ +# syntax=docker/dockerfile:1.7-labs + # Base Dockerfile for HarperDB Next.js Integration Tests fixtures # Must be run from the root of the repository diff --git a/util/docker/next.dockerfile b/util/docker/next.dockerfile index 188d49b..c79d3fd 100644 --- a/util/docker/next.dockerfile +++ b/util/docker/next.dockerfile @@ -1,3 +1,5 @@ +# syntax=docker/dockerfile:1.7-labs + # Next.js Specific Dockerfile for HarperDB Next.js Integration Tests fixtures # Must be run from the root of the repository From a0e0e1feaf02124a053b99b125c023a73cec522a Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 2 Dec 2024 16:54:04 -0700 Subject: [PATCH 30/32] podman? --- .github/workflows/test.yaml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index dbad1cd..c62d408 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -13,10 +13,9 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - with: - version: latest + - name: Install Podman + run: | + sudo apt-get install -y podman - name: Setup Node.js uses: actions/setup-node@v4 From 8026d2a148949a400a581b5a474bf856c1ec3620 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 2 Dec 2024 16:57:45 -0700 Subject: [PATCH 31/32] ugh --- .github/workflows/test.yaml | 4 ---- util/docker/base.dockerfile | 2 -- util/docker/next.dockerfile | 4 +--- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index c62d408..2716789 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -13,10 +13,6 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Install Podman - run: | - sudo apt-get install -y podman - - name: Setup Node.js uses: actions/setup-node@v4 with: diff --git a/util/docker/base.dockerfile b/util/docker/base.dockerfile index b3384a9..a8153e1 100644 --- a/util/docker/base.dockerfile +++ b/util/docker/base.dockerfile @@ -1,5 +1,3 @@ -# syntax=docker/dockerfile:1.7-labs - # Base Dockerfile for HarperDB Next.js Integration Tests fixtures # Must be run from the root of the repository diff --git a/util/docker/next.dockerfile b/util/docker/next.dockerfile index c79d3fd..a5845d7 100644 --- a/util/docker/next.dockerfile +++ b/util/docker/next.dockerfile @@ -1,5 +1,3 @@ -# syntax=docker/dockerfile:1.7-labs - # Next.js Specific Dockerfile for HarperDB Next.js Integration Tests fixtures # Must be run from the root of the repository @@ -11,7 +9,7 @@ ARG NEXT_MAJOR ARG CACHE_BUST RUN echo "${CACHE_BUST}" -COPY --exclude=node_modules --exclude=.next fixtures/next-${NEXT_MAJOR} /hdb/components/next-${NEXT_MAJOR} +COPY fixtures/next-${NEXT_MAJOR} /hdb/components/next-${NEXT_MAJOR} RUN cd hdb/components/next-${NEXT_MAJOR} && npm install From 65b56287750a77a444c0efb1e0fd88b1725b5010 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 2 Dec 2024 17:11:04 -0700 Subject: [PATCH 32/32] playwright this time --- .github/workflows/test.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 2716789..12fece4 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -22,5 +22,15 @@ jobs: - name: Install dependencies run: npm ci + - name: Install Playwright Browsers + run: npx playwright install --with-deps + - name: Run tests run: npm run test + + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 \ No newline at end of file