diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 45a8289dd7e..fc3cb8dc17d 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -76,7 +76,7 @@ runs: id: set-up-homebrew uses: Homebrew/actions/setup-homebrew@master - - name: 'Setup SSL Cert' + - name: 'Setup SSL Cert Infra' if: ${{ inputs.with-cert }} shell: bash run: | @@ -105,6 +105,13 @@ runs: env: ADTL_ARGS: ${{ inputs.adtl-install-args }} + - name: 'Generate SSL Cert' + if: ${{ inputs.with-cert }} + shell: bash + # Generates the cert if needed, using our local copy of @warp-drive/holodeck + run: | + node ./packages/holodeck/server/ensure-cert.js + - name: Setup Broccoli Caching if: ${{ inputs.restore-broccoli-cache == 'true' }} shell: bash diff --git a/packages/diagnostic/server/index.js b/packages/diagnostic/server/index.js index a1a9becd155..b75e3bcd5a4 100644 --- a/packages/diagnostic/server/index.js +++ b/packages/diagnostic/server/index.js @@ -12,11 +12,7 @@ const isBun = typeof Bun !== 'undefined'; export default async function launch(config) { if (isBun) { debug(`Bun detected, using Bun.serve()`); - if (config.setup) { - debug(`Running configured setup hook`); - await config.setup(); - debug(`Configured setup hook completed`); - } + const { checkPort } = await import('./bun/port.js'); const hostname = config.hostname ?? 'localhost'; const protocol = config.protocol ?? 'http'; @@ -67,6 +63,17 @@ export default async function launch(config) { protocol, url: `${protocol}://${hostname}:${port}`, }; + + if (config.setup) { + debug(`Running configured setup hook`); + await config.setup({ + port, + hostname, + protocol, + }); + debug(`Configured setup hook completed`); + } + await launchBrowsers(config, state); } catch (e) { error(`Error: ${e?.message ?? e}`); diff --git a/packages/diagnostic/src/-types.ts b/packages/diagnostic/src/-types.ts index 62767828acf..364e91da156 100644 --- a/packages/diagnostic/src/-types.ts +++ b/packages/diagnostic/src/-types.ts @@ -50,6 +50,7 @@ export type GlobalConfig = { _current: SuiteReport | null; useTestem: boolean; useDiagnostic: boolean; + testTimeoutMs: number; concurrency: number; globalHooks: GlobalHooksStorage; totals: { @@ -77,6 +78,14 @@ export interface Diagnostic { expect(count: number): void; step(name: string): void; verifySteps(steps: string[], message?: string): void; + /** + * Asserts that the actual value has at least the properties of the expected value. + * If additional properties are present on the actual value, they are ignored. + * + * @param actual + * @param expected + * @param message + */ satisfies(actual: J, expected: T, message?: string): void; } diff --git a/packages/diagnostic/src/internals/config.ts b/packages/diagnostic/src/internals/config.ts index 01a7b374373..cdc4fc7e916 100644 --- a/packages/diagnostic/src/internals/config.ts +++ b/packages/diagnostic/src/internals/config.ts @@ -23,6 +23,7 @@ export const Config: GlobalConfig = { useTestem: typeof Testem !== 'undefined', // @ts-expect-error useDiagnostic: typeof Testem === 'undefined', + testTimeoutMs: 50, concurrency: 1, params: { hideReport: { @@ -148,6 +149,7 @@ export type ConfigOptions = { params: Record; useTestem: boolean; useDiagnostic: boolean; + testTimeoutMs: number; }; const configOptions = [ 'concurrency', @@ -189,6 +191,8 @@ export function configure(options: Partial): void { } }); + Config.testTimeoutMs = options.testTimeoutMs ?? 0; + // copy over any remaining params Object.assign(Config.params, options.params); } diff --git a/packages/diagnostic/src/internals/run.ts b/packages/diagnostic/src/internals/run.ts index e3947dcd2f5..50f869e6b25 100644 --- a/packages/diagnostic/src/internals/run.ts +++ b/packages/diagnostic/src/internals/run.ts @@ -7,6 +7,15 @@ import { Diagnostic } from './diagnostic'; export const PublicTestInfo = Symbol('TestInfo'); +function cancellable(promise: Promise, timeout: number): Promise { + return new Promise((resolve, reject) => { + const id = setTimeout(() => { + reject(new Error('Test Timeout Exceeded: ' + timeout + 'ms')); + }, timeout); + promise.then(resolve, reject).finally(() => clearTimeout(id)); + }); +} + export async function runTest( moduleReport: ModuleReport, beforeChain: HooksCallback[], @@ -56,7 +65,13 @@ export async function runTest( } try { - await test.cb.call(testContext, Assert); + const promise = test.cb.call(testContext, Assert); + + if (promise instanceof Promise && Config.testTimeoutMs > 0) { + await cancellable(promise, Config.testTimeoutMs); + } + + await promise; } catch (err) { Assert.pushResult({ message: `Unexpected Test Failure: ${(err as Error).message}`, @@ -68,20 +83,20 @@ export async function runTest( if (!Config.params.tryCatch.value) { throw err; } - } - - for (const hook of afterChain) { - await hook.call(testContext, Assert); - } - Assert._finalize(); + } finally { + for (const hook of afterChain) { + await hook.call(testContext, Assert); + } + Assert._finalize(); - groupLogs() && console.groupEnd(); - testReport.end = instrument() && performance.mark(`test:${test.module.moduleName} > ${test.name}:end`); - testReport.measure = - instrument() && - performance.measure(`test:${test.module.moduleName} > ${test.name}`, testReport.start.name, testReport.end.name); + groupLogs() && console.groupEnd(); + testReport.end = instrument() && performance.mark(`test:${test.module.moduleName} > ${test.name}:end`); + testReport.measure = + instrument() && + performance.measure(`test:${test.module.moduleName} > ${test.name}`, testReport.start.name, testReport.end.name); - DelegatingReporter.onTestFinish(testReport); + DelegatingReporter.onTestFinish(testReport); + } } export async function runModule( diff --git a/packages/holodeck/bin/cmd/_start.js b/packages/holodeck/bin/cmd/_start.js deleted file mode 100644 index fe814f1ff73..00000000000 --- a/packages/holodeck/bin/cmd/_start.js +++ /dev/null @@ -1,3 +0,0 @@ -import pm2 from './pm2.js'; - -await pm2('start', process.argv.slice(2)); diff --git a/packages/holodeck/bin/cmd/_stop.js b/packages/holodeck/bin/cmd/_stop.js deleted file mode 100644 index f74b6cea349..00000000000 --- a/packages/holodeck/bin/cmd/_stop.js +++ /dev/null @@ -1,3 +0,0 @@ -import pm2 from './pm2.js'; - -await pm2('stop', process.argv.slice(2)); diff --git a/packages/holodeck/bin/cmd/pm2.js b/packages/holodeck/bin/cmd/pm2.js deleted file mode 100755 index c4529eaf74f..00000000000 --- a/packages/holodeck/bin/cmd/pm2.js +++ /dev/null @@ -1,39 +0,0 @@ -/* eslint-disable no-console */ -/* global Bun, globalThis */ -import fs from 'fs'; -import pm2 from 'pm2'; - -const { process } = globalThis; - -export default async function pm2Delegate(cmd, _args) { - const pkg = JSON.parse(fs.readFileSync('./package.json'), 'utf8'); - - return new Promise((resolve, reject) => { - pm2.connect((err) => { - if (err) { - console.log('not able to connect to pm2'); - console.error(err); - process.exit(2); - } - - const options = { - script: './holodeck.mjs', - name: pkg.name + '::holodeck', - cwd: process.cwd(), - args: cmd === 'start' ? '-f' : '', - }; - - pm2[cmd](cmd === 'start' ? options : options.name, (err, apps) => { - pm2.disconnect(); // Disconnects from PM2 - if (err) { - console.log(`not able to ${cmd} pm2 for ${options.name}`); - console.error(err); - reject(err); - } else { - console.log(`pm2 ${cmd} successful for ${options.name}`); - resolve(); - } - }); - }); - }); -} diff --git a/packages/holodeck/bin/cmd/run.js b/packages/holodeck/bin/cmd/run.js deleted file mode 100755 index b996a779cb5..00000000000 --- a/packages/holodeck/bin/cmd/run.js +++ /dev/null @@ -1,50 +0,0 @@ -/* eslint-disable no-console */ -/* global Bun, globalThis */ -const isBun = typeof Bun !== 'undefined'; -const { process } = globalThis; -import { spawn } from './spawn.js'; -import fs from 'fs'; - -export default async function run(args) { - const pkg = JSON.parse(fs.readFileSync('./package.json'), 'utf8'); - const cmd = args[0]; - const isPkgScript = pkg.scripts[cmd]; - - if (isBun) { - await spawn(['bun', 'run', 'holodeck:start-program']); - - let exitCode = 0; - try { - await spawn(['bun', 'run', ...args]); - } catch (e) { - exitCode = e; - } - await spawn(['bun', 'run', 'holodeck:end-program']); - if (exitCode !== 0) { - process.exit(exitCode); - } - return; - } else { - await spawn(['pnpm', 'run', 'holodeck:start-program']); - - let exitCode = 0; - try { - if (isPkgScript) { - const cmdArgs = pkg.scripts[cmd].split(' '); - if (args.length > 1) { - cmdArgs.push(...args.slice(1)); - } - console.log({ cmdArgs }); - await spawn(cmdArgs); - } else { - await spawn(['pnpm', 'exec', ...args]); - } - } catch (e) { - exitCode = e; - } - await spawn(['pnpm', 'run', 'holodeck:end-program']); - if (exitCode !== 0) { - process.exit(exitCode); - } - } -} diff --git a/packages/holodeck/bin/cmd/spawn.js b/packages/holodeck/bin/cmd/spawn.js deleted file mode 100644 index 5eae2352674..00000000000 --- a/packages/holodeck/bin/cmd/spawn.js +++ /dev/null @@ -1,40 +0,0 @@ -/* eslint-disable no-console */ -/* global Bun, globalThis */ -const isBun = typeof Bun !== 'undefined'; - -export async function spawn(args, options) { - if (isBun) { - const proc = Bun.spawn(args, { - env: process.env, - cwd: process.cwd(), - stdout: 'inherit', - stderr: 'inherit', - }); - await proc.exited; - if (proc.exitCode !== 0) { - throw proc.exitCode; - } - return; - } - - const { spawn } = await import('node:child_process'); - - // eslint-disable-next-line no-inner-declarations - function pSpawn(cmd, args, opts) { - return new Promise((resolve, reject) => { - const proc = spawn(cmd, args, opts); - proc.on('exit', (code) => { - if (code === 0) { - resolve(); - } else { - reject(code); - } - }); - }); - } - - await pSpawn(args.shift(), args, { - stdio: 'inherit', - shell: true, - }); -} diff --git a/packages/holodeck/bin/holodeck.js b/packages/holodeck/bin/holodeck.js deleted file mode 100755 index 281ad9f3956..00000000000 --- a/packages/holodeck/bin/holodeck.js +++ /dev/null @@ -1,64 +0,0 @@ -#!/bin/sh - -':'; /*- -test1=$(bun --version 2>&1) && exec bun "$0" "$@" -test2=$(node --version 2>&1) && exec node "$0" "$@" -exec printf '%s\n' "$test1" "$test2" 1>&2 -*/ -/* eslint-disable no-console */ -/* global Bun, globalThis */ - -import chalk from 'chalk'; - -import { spawn } from './cmd/spawn.js'; - -const isBun = typeof Bun !== 'undefined'; -const { process } = globalThis; - -const args = isBun ? Bun.argv.slice(2) : process.argv.slice(2); -const command = args.shift(); - -const BUN_SUPPORTS_PM2 = false; -const BUN_SUPPORTS_HTTP2 = false; - -if (command === 'run') { - console.log( - chalk.grey( - `\n\t@${chalk.greenBright('warp-drive')}/${chalk.magentaBright( - 'holodeck' - )} 🌅\n\t=================================}\n` - ) + - chalk.grey( - `\n\tHolodeck Access Granted\n\t\tprogram: ${chalk.green(args.join(' '))}\n\t\tengine: ${chalk.cyan( - isBun ? 'bun@' + Bun.version : 'node' - )}` - ) - ); - const run = await import('./cmd/run.js'); - await run.default(args); -} else if (command === 'start') { - console.log(chalk.grey(`\n\tStarting Subroutines (mode:${chalk.cyan(isBun ? 'bun' : 'node')})`)); - - if (!isBun || (BUN_SUPPORTS_HTTP2 && BUN_SUPPORTS_PM2)) { - const pm2 = await import('./cmd/pm2.js'); - await pm2.default('start', args); - } else { - console.log(`Downgrading to node to run pm2 due lack of http/2 or pm2 support in Bun`); - const __dirname = import.meta.dir; - const programPath = __dirname + '/cmd/_start.js'; - await spawn(['node', programPath, ...args]); - } -} else if (command === 'end') { - console.log(chalk.grey(`\n\tEnding Subroutines (mode:${chalk.cyan(isBun ? 'bun' : 'node')})`)); - - if (!isBun || (BUN_SUPPORTS_HTTP2 && BUN_SUPPORTS_PM2)) { - const pm2 = await import('./cmd/pm2.js'); - await pm2.default('stop', args); - } else { - console.log(`Downgrading to node to run pm2 due lack of http/2 or pm2 support in Bun`); - const __dirname = import.meta.dir; - const programPath = __dirname + '/cmd/_stop.js'; - await spawn(['node', programPath, ...args]); - } - - console.log(`\n\t${chalk.grey('The Computer has ended the program')}\n`); -} diff --git a/packages/holodeck/client/index.ts b/packages/holodeck/client/index.ts index 372551b8340..98a2008bce3 100644 --- a/packages/holodeck/client/index.ts +++ b/packages/holodeck/client/index.ts @@ -4,6 +4,11 @@ import type { ScaffoldGenerator } from './mock'; const TEST_IDS = new WeakMap(); +let HOST = 'https://localhost:1135/'; +export function setConfig({ host }: { host: string }) { + HOST = host.endsWith('/') ? host : `${host}/`; +} + export function setTestId(context: object, str: string | null) { if (str && TEST_IDS.has(context)) { throw new Error(`MockServerHandler is already configured with a testId.`); @@ -68,7 +73,8 @@ export async function mock(owner: object, generate: ScaffoldGenerator, isRecordi } const testMockNum = test.mock++; if (getIsRecording() || isRecording) { - const url = `https://localhost:1135/__record?__xTestId=${test.id}&__xTestRequestNumber=${testMockNum}`; + const port = window.location.port ? `:${window.location.port}` : ''; + const url = `${HOST}__record?__xTestId=${test.id}&__xTestRequestNumber=${testMockNum}`; await fetch(url, { method: 'POST', body: JSON.stringify(generate()), diff --git a/packages/holodeck/package.json b/packages/holodeck/package.json index 6768f05524c..d903a97a55c 100644 --- a/packages/holodeck/package.json +++ b/packages/holodeck/package.json @@ -23,8 +23,7 @@ "dependencies": { "@hono/node-server": "^1.10.0", "chalk": "^5.3.0", - "hono": "^4.2.4", - "pm2": "^5.3.1" + "hono": "^4.2.4" }, "files": [ "bin", @@ -36,7 +35,7 @@ "NCC-1701-a-blue.svg" ], "bin": { - "holodeck": "./bin/holodeck.js" + "ensure-cert": "./server/ensure-cert.js" }, "scripts": { "build:types": "tsc --build --force", diff --git a/packages/holodeck/server/ensure-cert.js b/packages/holodeck/server/ensure-cert.js new file mode 100755 index 00000000000..fcdde732fce --- /dev/null +++ b/packages/holodeck/server/ensure-cert.js @@ -0,0 +1,55 @@ +#!/usr/bin/env node +import { execSync } from 'node:child_process'; +import fs from 'node:fs'; +import { homedir, userInfo } from 'os'; +import path from 'path'; + +function getShellConfigFilePath() { + const shell = userInfo().shell; + switch (shell) { + case '/bin/zsh': + return path.join(homedir(), '.zshrc'); + case '/bin/bash': + return path.join(homedir(), '.bashrc'); + default: + throw Error( + `Unable to determine configuration file for shell: ${shell}. Manual SSL Cert Setup Required for Holodeck.` + ); + } +} + +function main() { + let CERT_PATH = process.env.HOLODECK_SSL_CERT_PATH; + let KEY_PATH = process.env.HOLODECK_SSL_KEY_PATH; + const configFilePath = getShellConfigFilePath(); + + if (!CERT_PATH) { + CERT_PATH = path.join(homedir(), 'holodeck-localhost.pem'); + process.env.HOLODECK_SSL_CERT_PATH = CERT_PATH; + execSync(`echo '\nexport HOLODECK_SSL_CERT_PATH="${CERT_PATH}"' >> ${configFilePath}`); + console.log(`Added HOLODECK_SSL_CERT_PATH to ${configFilePath}`); + } + + if (!KEY_PATH) { + KEY_PATH = path.join(homedir(), 'holodeck-localhost-key.pem'); + process.env.HOLODECK_SSL_KEY_PATH = KEY_PATH; + execSync(`echo '\nexport HOLODECK_SSL_KEY_PATH="${KEY_PATH}"' >> ${configFilePath}`); + console.log(`Added HOLODECK_SSL_KEY_PATH to ${configFilePath}`); + } + + if (!fs.existsSync(CERT_PATH) || !fs.existsSync(KEY_PATH)) { + console.log('SSL certificate or key not found, generating new ones...'); + + execSync(`mkcert -install`); + execSync(`mkcert -key-file ${KEY_PATH} -cert-file ${CERT_PATH} localhost`); + + console.log('SSL certificate and key generated.'); + } else { + console.log('SSL certificate and key found, using existing.'); + } + + console.log(`Certificate path: ${CERT_PATH}`); + console.log(`Key path: ${KEY_PATH}`); +} + +main(); diff --git a/packages/holodeck/server/index.js b/packages/holodeck/server/index.js index 9bd547748c7..350f21792f7 100644 --- a/packages/holodeck/server/index.js +++ b/packages/holodeck/server/index.js @@ -1,3 +1,4 @@ +/* global Bun */ import { serve } from '@hono/node-server'; import chalk from 'chalk'; import { Hono } from 'hono'; @@ -12,6 +13,11 @@ import zlib from 'node:zlib'; import { homedir, userInfo } from 'os'; import path from 'path'; +/** @type {import('bun-types')} */ +const isBun = typeof Bun !== 'undefined'; +const DEBUG = process.env.DEBUG?.includes('holodeck') || process.env.DEBUG === '*'; +const CURRENT_FILE = new URL(import.meta.url).pathname; + function getShellConfigFilePath() { const shell = userInfo().shell; switch (shell) { @@ -29,26 +35,24 @@ function getShellConfigFilePath() { function getCertInfo() { let CERT_PATH = process.env.HOLODECK_SSL_CERT_PATH; let KEY_PATH = process.env.HOLODECK_SSL_KEY_PATH; + const configFilePath = getShellConfigFilePath(); if (!CERT_PATH) { CERT_PATH = path.join(homedir(), 'holodeck-localhost.pem'); process.env.HOLODECK_SSL_CERT_PATH = CERT_PATH; - execSync(`echo '\nexport HOLODECK_SSL_CERT_PATH="${CERT_PATH}"' >> ${getShellConfigFilePath()}`); - console.log(`Added HOLODECK_SSL_CERT_PATH to ${getShellConfigFilePath()}`); + execSync(`echo '\nexport HOLODECK_SSL_CERT_PATH="${CERT_PATH}"' >> ${configFilePath}`); + console.log(`Added HOLODECK_SSL_CERT_PATH to ${configFilePath}`); } if (!KEY_PATH) { KEY_PATH = path.join(homedir(), 'holodeck-localhost-key.pem'); process.env.HOLODECK_SSL_KEY_PATH = KEY_PATH; - execSync(`echo '\nexport HOLODECK_SSL_KEY_PATH="${KEY_PATH}"' >> ${getShellConfigFilePath()}`); - console.log(`Added HOLODECK_SSL_KEY_PATH to ${getShellConfigFilePath()}`); + execSync(`echo '\nexport HOLODECK_SSL_KEY_PATH="${KEY_PATH}"' >> ${configFilePath}`); + console.log(`Added HOLODECK_SSL_KEY_PATH to ${configFilePath}`); } if (!fs.existsSync(CERT_PATH) || !fs.existsSync(KEY_PATH)) { - console.log('SSL certificate or key not found, generating new ones...'); - - execSync(`mkcert -install`); - execSync(`mkcert -key-file ${KEY_PATH} -cert-file ${CERT_PATH} localhost`); + throw new Error('SSL certificate or key not found, you may need to run `npx -p @warp-drive/holodeck ensure-cert`'); } return { @@ -243,7 +247,9 @@ function createTestHandler(projectRoot) { */ export function createServer(options) { const app = new Hono(); - app.use('*', logger()); + if (DEBUG) { + app.use('*', logger()); + } app.use( '*', cors({ @@ -263,19 +269,107 @@ export function createServer(options) { serve({ fetch: app.fetch, createServer: (_, requestListener) => { - return http2.createSecureServer( - { - key: KEY, - cert: CERT, - }, - requestListener - ); + try { + return http2.createSecureServer( + { + key: KEY, + cert: CERT, + }, + requestListener + ); + } catch (e) { + console.log(chalk.yellow(`Failed to create secure server, falling back to http server. Error: ${e.message}`)); + return http2.createServer(requestListener); + } }, port: options.port ?? DEFAULT_PORT, hostname: 'localhost', + // bun uses TLS options + // tls: { + // key: Bun.file(KEY_PATH), + // cert: Bun.file(CERT_PATH), + // }, }); console.log( `\tMock server running at ${chalk.magenta('https://localhost:') + chalk.yellow(options.port ?? DEFAULT_PORT)}` ); } + +const servers = new Map(); + +export default { + async launchProgram(config = {}) { + const projectRoot = process.cwd(); + const name = await import(path.join(projectRoot, 'package.json'), { with: { type: 'json' } }).then( + (pkg) => pkg.name + ); + const options = { name, projectRoot, ...config }; + console.log( + chalk.grey( + `\n\t@${chalk.greenBright('warp-drive')}/${chalk.magentaBright( + 'holodeck' + )} 🌅\n\t=================================\n` + ) + + chalk.grey( + `\n\tHolodeck Access Granted\n\t\tprogram: ${chalk.magenta(name)}\n\t\tsettings: ${chalk.green(JSON.stringify(config).split('\n').join(' '))}\n\t\tdirectory: ${chalk.cyan(projectRoot)}\n\t\tengine: ${chalk.cyan( + isBun ? 'bun@' + Bun.version : 'node' + )}` + ) + ); + console.log(chalk.grey(`\n\tStarting Subroutines (mode:${chalk.cyan(isBun ? 'bun' : 'node')})`)); + + if (isBun) { + const serverProcess = Bun.spawn( + ['node', '--experimental-default-type=module', CURRENT_FILE, JSON.stringify(options)], + { + env: process.env, + cwd: process.cwd(), + stdout: 'inherit', + stderr: 'inherit', + } + ); + servers.set(projectRoot, serverProcess); + return; + } + + if (servers.has(projectRoot)) { + throw new Error(`Holodeck is already running for project '${name}' at '${projectRoot}'`); + } + + servers.set(projectRoot, createServer(options)); + }, + async endProgram() { + console.log(chalk.grey(`\n\tEnding Subroutines (mode:${chalk.cyan(isBun ? 'bun' : 'node')})`)); + const projectRoot = process.cwd(); + const name = await import(path.join(projectRoot, 'package.json'), { with: { type: 'json' } }).then( + (pkg) => pkg.name + ); + + if (!servers.has(projectRoot)) { + throw new Error(`Holodeck was not running for project '${name}' at '${projectRoot}'`); + } + + if (isBun) { + const serverProcess = servers.get(projectRoot); + serverProcess.kill(); + return; + } + + servers.get(projectRoot).close(); + servers.delete(projectRoot); + }, +}; + +function main() { + const args = process.argv.slice(); + if (!isBun && args.length) { + if (args[1] !== CURRENT_FILE) { + return; + } + const options = JSON.parse(args[2]); + createServer(options); + } +} + +main(); diff --git a/packages/request/src/-private/utils.ts b/packages/request/src/-private/utils.ts index 99f57b74975..b6eb1a93f87 100644 --- a/packages/request/src/-private/utils.ts +++ b/packages/request/src/-private/utils.ts @@ -98,10 +98,15 @@ export function handleOutcome( if (isDoc(error)) { owner.setStream(owner.god.stream); } - if (!error) { + if (!error || !(error instanceof Error)) { try { - throw new Error(`Request Rejected with an Unknown Error`); + throw new Error(error ? error : `Request Rejected with an Unknown Error`); } catch (e: unknown) { + if (error && typeof error === 'object') { + Object.assign(e as Error, error); + (e as Error & StructuredErrorDocument).message = + (error as Error).message || `Request Rejected with an Unknown Error`; + } error = e as Error & StructuredErrorDocument; } } diff --git a/packages/schema/parse b/packages/schema/parse index ba8c2dab16f..198d5408a41 100755 Binary files a/packages/schema/parse and b/packages/schema/parse differ diff --git a/packages/store/src/-private/cache-handler.ts b/packages/store/src/-private/cache-handler.ts index 401b91f99dc..45a681ce58d 100644 --- a/packages/store/src/-private/cache-handler.ts +++ b/packages/store/src/-private/cache-handler.ts @@ -422,13 +422,25 @@ function fetchContentAndHydrate( }); } -function cloneError(error: Error & { error: string | object }) { - const cloned: Error & { error: string | object; content?: object } = new Error(error.message) as Error & { - error: string | object; - content?: object; - }; +function isAggregateError(error: Error & { errors?: ApiError[] }): error is AggregateError & { errors: ApiError[] } { + return error instanceof AggregateError || (error.name === 'AggregateError' && Array.isArray(error.errors)); +} + +type RobustError = Error & { error: string | object; errors?: ApiError[]; content?: unknown }; + +// TODO @runspired, consider if we should deep freeze errors (potentially only in debug) vs cloning them +function cloneError(error: RobustError) { + const isAggregate = isAggregateError(error); + + const cloned = ( + isAggregate ? new AggregateError(structuredClone(error.errors), error.message) : new Error(error.message) + ) as RobustError; cloned.stack = error.stack!; cloned.error = error.error; + + // copy over enumerable properties + Object.assign(cloned, error); + return cloned; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 58e73747267..cbb1b46d54b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -986,9 +986,6 @@ importers: hono: specifier: ^4.2.4 version: 4.2.5 - pm2: - specifier: ^5.3.1 - version: 5.3.1 devDependencies: '@babel/cli': specifier: ^7.24.1 @@ -3030,6 +3027,9 @@ importers: '@warp-drive/diagnostic': specifier: workspace:0.0.0-alpha.44 version: file:packages/diagnostic(@ember/test-helpers@3.3.0)(@embroider/addon-shim@1.8.7)(ember-cli-test-loader@3.1.0) + '@warp-drive/holodeck': + specifier: workspace:0.0.0-alpha.44 + version: file:packages/holodeck(@ember-data/request@5.4.0-alpha.58)(@warp-drive/core-types@0.0.0-alpha.44) '@warp-drive/internal-config': specifier: workspace:5.4.0-alpha.58 version: link:../../config @@ -3119,6 +3119,8 @@ importers: injected: true '@warp-drive/diagnostic': injected: true + '@warp-drive/holodeck': + injected: true ember-inflector: injected: true @@ -3284,6 +3286,9 @@ importers: '@ember-data/request': specifier: workspace:5.4.0-alpha.58 version: file:packages/request(@babel/core@7.24.4)(@glint/template@1.4.0)(@warp-drive/core-types@0.0.0-alpha.44) + '@ember-data/request-utils': + specifier: workspace:5.4.0-alpha.58 + version: file:packages/request-utils(@babel/core@7.24.4)(@warp-drive/core-types@0.0.0-alpha.44) '@ember/edition-utils': specifier: ^1.2.0 version: 1.2.0 @@ -3388,6 +3393,8 @@ importers: injected: true '@ember-data/request': injected: true + '@ember-data/request-utils': + injected: true '@ember/string': injected: true '@warp-drive/core-types': @@ -6914,6 +6921,7 @@ packages: semver: 5.7.2 shimmer: 1.2.1 uuid: 3.4.0 + dev: true /@opencensus/core@0.0.9: resolution: {integrity: sha512-31Q4VWtbzXpVUd2m9JS6HEaPjlKvNMOiF7lWKNmXF84yUcgfAFL5re7/hjDmdyQbOp32oGc+RFV78jXIldVz6Q==} @@ -6924,6 +6932,7 @@ packages: semver: 5.7.2 shimmer: 1.2.1 uuid: 3.4.0 + dev: true /@opencensus/propagation-b3@0.0.8: resolution: {integrity: sha512-PffXX2AL8Sh0VHQ52jJC4u3T0H6wDK6N/4bg7xh4ngMYOIi13aR1kzVvX1sVDBgfGwDOkMbl4c54Xm3tlPx/+A==} @@ -6931,6 +6940,7 @@ packages: dependencies: '@opencensus/core': 0.0.8 uuid: 3.4.0 + dev: true /@pkgjs/parseargs@0.11.0: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} @@ -6959,6 +6969,7 @@ packages: - bufferutil - supports-color - utf-8-validate + dev: true /@pm2/io@5.0.2: resolution: {integrity: sha512-XAvrNoQPKOyO/jJyCu8jPhLzlyp35MEf7w/carHXmWKddPzeNOFSEpSEqMzPDawsvpxbE+i918cNN+MwgVsStA==} @@ -6976,6 +6987,7 @@ packages: tslib: 1.9.3 transitivePeerDependencies: - supports-color + dev: true /@pm2/js-api@0.8.0: resolution: {integrity: sha512-nmWzrA/BQZik3VBz+npRcNIu01kdBhWL0mxKmP1ciF/gTcujPTQqt027N9fc1pK9ERM8RipFhymw7RcmCyOEYA==} @@ -6990,6 +7002,7 @@ packages: - bufferutil - supports-color - utf-8-validate + dev: true /@pm2/pm2-version-check@1.0.4: resolution: {integrity: sha512-SXsM27SGH3yTWKc2fKR4SYNxsmnvuBQ9dd6QHtEWmiZ/VqaOYPAIlS8+vMcn27YLtAEBGvNRSh3TPNvtjZgfqA==} @@ -6997,6 +7010,7 @@ packages: debug: 4.3.4(supports-color@8.1.1) transitivePeerDependencies: - supports-color + dev: true /@pnpm/cli-meta@5.0.1: resolution: {integrity: sha512-s7rVArn3s78w2ZDWC2/NzMaYBzq39QBmo1BQ4+qq1liX+ltSErDyAx3M/wvvJQgc+Ur3dZJYuc9t96roPnW3XQ==} @@ -7615,6 +7629,7 @@ packages: /@tootallnate/quickjs-emscripten@0.23.0: resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} + dev: true /@types/babel__code-frame@7.0.6: resolution: {integrity: sha512-Anitqkl3+KrzcW2k77lRlg/GfLZLWXBuNgbEcIOU6M92yw42vsd3xV/Z/yAHEj8m+KUjL6bWOVOFqX8PFPJ4LA==} @@ -8156,6 +8171,7 @@ packages: debug: 4.3.4(supports-color@8.1.1) transitivePeerDependencies: - supports-color + dev: true /agentkeepalive@4.5.0: resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} @@ -8223,9 +8239,11 @@ packages: resolution: {integrity: sha512-JqutcFwoU1+jhv7ArgW38bqrE+LQdcRv4NxNw0mp0JHQyB6tXesWRjtYKlDgHRY2o3JE5UTaBGUK8kSWUdxWUg==} dependencies: amp: 0.3.1 + dev: true /amp@0.3.1: resolution: {integrity: sha512-OwIuC4yZaRogHKiuU5WlMR5Xk/jAcpPtawWL05Gj8Lvm2F6mwoJt4O/bHI+DHwG79vWd+8OFYM4/BzYqyRd3qw==} + dev: true /ansi-align@3.0.1: resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} @@ -8240,6 +8258,7 @@ packages: /ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} + dev: true /ansi-diff@1.1.1: resolution: {integrity: sha512-XnTdFDQzbEewrDx8epWXdw7oqHMvv315vEtfqDiEhhWghIf4++h26c3/FMz7iTLhNrnj56DNIXpbxHZq+3s6qw==} @@ -8506,6 +8525,7 @@ packages: engines: {node: '>=4'} dependencies: tslib: 2.6.2 + dev: true /async-disk-cache@1.3.5: resolution: {integrity: sha512-VZpqfR0R7CEOJZ/0FOTgWq70lCrZyS1rkI8PXugDUkTKyyAUgZ2zQ09gLhMkEn+wN8LYeUTPxZdXtlX/kmbXKQ==} @@ -8540,6 +8560,7 @@ packages: dependencies: semver: 5.7.2 shimmer: 1.2.1 + dev: true /async-promise-queue@1.0.5: resolution: {integrity: sha512-xi0aQ1rrjPWYmqbwr18rrSKbSaXIeIwSd1J4KAgVfkq8utNbdZoht7GfvfY6swFUAMJ9obkc4WPJmtGwl+B8dw==} @@ -8565,6 +8586,7 @@ packages: /async@3.2.5: resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==} + dev: true /asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -9226,6 +9248,7 @@ packages: /basic-ftp@5.0.5: resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} engines: {node: '>=10.0.0'} + dev: true /better-path-resolve@1.0.0: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} @@ -9240,6 +9263,7 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} requiresBuild: true + dev: true /binaryextensions@2.3.0: resolution: {integrity: sha512-nAihlQsYGyc5Bwq6+EsubvANYGExeJKHDO3RjnvwU042fawQTQfM3Kxn7IHUXQOz4bzfwsGYYHGSvXyW4zOGLg==} @@ -9260,6 +9284,7 @@ packages: resolution: {integrity: sha512-LoF5gae+hlmfORcG1M5+5XZi4LBmvlXTzwJWzUlPryN/SJdSflZvROM2TwkT0GMpq7oqT48NRd4GS7BiVBc5OQ==} engines: {node: '>= 0.8.0'} hasBin: true + dev: true /bluebird@3.7.2: resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} @@ -9270,6 +9295,7 @@ packages: /bodec@0.1.0: resolution: {integrity: sha512-Ylo+MAo5BDUq1KA3f3R/MFhh+g8cnHmo8bz3YPGhI1znrMaf77ol1sfvYJzsw3nTE+Y2GryfDxBaR+AqpAkEHQ==} + dev: true /body-parser@1.20.2: resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==} @@ -10104,6 +10130,7 @@ packages: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 + dev: true /chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} @@ -10125,6 +10152,7 @@ packages: /charm@0.1.2: resolution: {integrity: sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==} + dev: true /charm@1.0.2: resolution: {integrity: sha512-wqW3VdPnlSWT4eRiYX+hcs+C6ViBPUWk1qTCd+37qw9kEm/a5n2qcyQDMBWvSYKN/ctqZzeXNQaeBjOetJJUkw==} @@ -10164,6 +10192,7 @@ packages: readdirp: 3.6.0 optionalDependencies: fsevents: 2.3.3 + dev: true /chownr@2.0.0: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} @@ -10274,6 +10303,7 @@ packages: engines: {node: '>=8.10.0'} dependencies: chalk: 3.0.0 + dev: true /cli-width@2.2.1: resolution: {integrity: sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==} @@ -10381,6 +10411,7 @@ packages: /commander@2.15.1: resolution: {integrity: sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==} + dev: true /commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -10674,6 +10705,7 @@ packages: dependencies: async-listener: 0.6.10 emitter-listener: 1.1.2 + dev: true /convert-source-map@1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} @@ -10726,6 +10758,7 @@ packages: /croner@4.1.97: resolution: {integrity: sha512-/f6gpQuxDaqXu+1kwQYSckUglPaOrHdbIlBAu0YuW8/Cdb45XwXYNUBXg3r/9Mo6n540Kn/smKcZWko5x99KrQ==} + dev: true /cross-spawn@6.0.5: resolution: {integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==} @@ -10828,6 +10861,7 @@ packages: /culvert@0.1.2: resolution: {integrity: sha512-yi1x3EAWKjQTreYWeSd98431AV+IEE0qoDyOoaHJ7KJ21gv6HtBXHVLX74opVSGqcR8/AbjJBHAHpcOy2bj5Gg==} + dev: true /dag-map@2.0.2: resolution: {integrity: sha512-xnsprIzYuDeiyu5zSKwilV/ajRHxnoMlAhEREfyfTgTSViMVY2fGP1ZcHJbtwup26oCkofySU/m6oKJ3HrkW7w==} @@ -10838,6 +10872,7 @@ packages: /data-uri-to-buffer@6.0.2: resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} engines: {node: '>= 14'} + dev: true /data-urls@2.0.0: resolution: {integrity: sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==} @@ -10883,9 +10918,11 @@ packages: /dayjs@1.11.10: resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==} + dev: true /dayjs@1.8.36: resolution: {integrity: sha512-3VmRXEtw7RZKAf+4Tv1Ym9AGeo8r8+CjDi26x+7SYQil1UqtqdaokhzoEJohqlzt0m5kacJSDhJQkG/LWhpRBw==} + dev: true /debug@2.6.9(supports-color@8.1.1): resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} @@ -11036,6 +11073,7 @@ packages: ast-types: 0.13.4 escodegen: 2.1.0 esprima: 4.0.1 + dev: true /del@5.1.0: resolution: {integrity: sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA==} @@ -12128,6 +12166,7 @@ packages: resolution: {integrity: sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==} dependencies: shimmer: 1.2.1 + dev: true /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -12192,6 +12231,7 @@ packages: engines: {node: '>=8.6'} dependencies: ansi-colors: 4.1.3 + dev: true /ensure-posix-path@1.1.1: resolution: {integrity: sha512-VWU0/zXzVbeJNXvME/5EmLuEj2TauvoaTz6aFYK1Z92JCBlDlZ3Gu0tuGR42kpW1754ywTs+QB0g5TP0oj9Zaw==} @@ -12342,6 +12382,7 @@ packages: esutils: 2.0.3 optionalDependencies: source-map: 0.6.1 + dev: true /eslint-compat-utils@0.5.0(eslint@8.57.0): resolution: {integrity: sha512-dc6Y8tzEcSYZMHa+CMPLi/hyo1FzNeonbhJL7Ol0ccuKQkwopJcJBA9YL/xmMTLU1eKigXo9vj9nALElWYSowg==} @@ -12637,12 +12678,15 @@ packages: /eventemitter2@0.4.14: resolution: {integrity: sha512-K7J4xq5xAD5jHsGM5ReWXRTFa3JRGofHiMcVgQ8PRwgWxzjHpMWCIzsmyf60+mh8KLsqYPcjUMa0AC4hd6lPyQ==} + dev: true /eventemitter2@5.0.1: resolution: {integrity: sha1-YZegldX7a1folC9v1+qtY6CclFI=} + dev: true /eventemitter2@6.4.9: resolution: {integrity: sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==} + dev: true /eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} @@ -12868,6 +12912,7 @@ packages: follow-redirects: 1.15.6(debug@4.3.4) transitivePeerDependencies: - debug + dev: true /fake-xml-http-request@2.1.2: resolution: {integrity: sha512-HaFMBi7r+oEC9iJNpc3bvcW7Z7iLmM26hPDmlb0mFwyANSsOQAtJxbdWsXITKOzZUyMYK0zYCv3h5yDj9TsiXg==} @@ -12888,6 +12933,7 @@ packages: /fast-json-patch@3.1.1: resolution: {integrity: sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==} + dev: true /fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} @@ -12986,6 +13032,7 @@ packages: /fclone@1.0.11: resolution: {integrity: sha512-GDqVQezKzRABdeqflsgMr7ktzgF9CyS+p2oe0jJqUY6izSSbhPIQJDpoU4PtGcD7VPM9xh/dVrTu6z1nwgmEGw==} + dev: true /figures@2.0.0: resolution: {integrity: sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==} @@ -13427,6 +13474,7 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] requiresBuild: true + dev: true optional: true /function-bind@1.1.2: @@ -13548,6 +13596,7 @@ packages: fs-extra: 11.2.0 transitivePeerDependencies: - supports-color + dev: true /get-value@2.0.6: resolution: {integrity: sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==} @@ -13565,6 +13614,7 @@ packages: optional: true dependencies: js-git: 0.7.8 + dev: true /git-repo-info@1.4.1: resolution: {integrity: sha512-oqzBH6cNvE8Cq3p61ps4m0POZrVMKlARntc2BxLnuqTK+HeWpKfUMJQ7H1CvescHRINj+0a7TKA+Pp/bOq5F1Q==} @@ -13583,6 +13633,7 @@ packages: /git-sha1@0.1.2: resolution: {integrity: sha512-2e/nZezdVlyCopOCYHeW0onkbZg7xP1Ad6pndPy1rCygeRykefUS6r7oA5cJRGEFvseiaz5a/qUHFVX1dd6Isg==} + dev: true /glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} @@ -14029,6 +14080,7 @@ packages: debug: 4.3.4(supports-color@8.1.1) transitivePeerDependencies: - supports-color + dev: true /http-proxy@1.18.1: resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} @@ -14069,6 +14121,7 @@ packages: debug: 4.3.4(supports-color@8.1.1) transitivePeerDependencies: - supports-color + dev: true /https@1.0.0: resolution: {integrity: sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg==} @@ -14261,6 +14314,7 @@ packages: dependencies: jsbn: 1.1.0 sprintf-js: 1.1.3 + dev: true /ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} @@ -14292,6 +14346,7 @@ packages: engines: {node: '>=8'} dependencies: binary-extensions: 2.3.0 + dev: true /is-boolean-object@1.1.2: resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} @@ -14610,6 +14665,7 @@ packages: culvert: 0.1.2 git-sha1: 0.1.2 pako: 0.2.9 + dev: true /js-string-escape@1.0.1: resolution: {integrity: sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==} @@ -14637,6 +14693,7 @@ packages: /jsbn@1.1.0: resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} + dev: true /jsdom@16.7.0(supports-color@8.1.1): resolution: {integrity: sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==} @@ -14842,6 +14899,7 @@ packages: /lazy@1.0.11: resolution: {integrity: sha512-Y+CjUfLmIpoUCCRl0ub4smrYtGGr5AOa2AKOaWelGHOGz33X/Y/KizefGqbkwfz44+cnq/+9habclf8vOmu2LA==} engines: {node: '>=0.2.0'} + dev: true /lcid@3.1.1: resolution: {integrity: sha512-M6T051+5QCGLBQb8id3hdvIW8+zeFV2FyBGFS9IEK5H9Wt4MueD4bW1eWikpHgZp+5xR3l5c8pZUkQsIA0BFZg==} @@ -15035,6 +15093,7 @@ packages: /log-driver@1.2.7: resolution: {integrity: sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==} engines: {node: '>=0.8.6'} + dev: true /log-symbols@2.2.0: resolution: {integrity: sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==} @@ -15523,6 +15582,7 @@ packages: /module-details-from-path@1.0.3: resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==} + dev: true /morgan@1.10.0: resolution: {integrity: sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==} @@ -15614,6 +15674,7 @@ packages: sax: 1.3.0 transitivePeerDependencies: - supports-color + dev: true /negotiator@0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} @@ -15625,6 +15686,7 @@ packages: /netmask@2.0.2: resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} engines: {node: '>= 0.4.0'} + dev: true /nice-try@1.0.5: resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} @@ -15762,6 +15824,7 @@ packages: dependencies: eventemitter2: 0.4.14 lazy: 1.0.11 + dev: true /nwsapi@2.2.7: resolution: {integrity: sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==} @@ -16050,6 +16113,7 @@ packages: socks-proxy-agent: 8.0.2 transitivePeerDependencies: - supports-color + dev: true /pac-resolver@7.0.1: resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} @@ -16057,6 +16121,7 @@ packages: dependencies: degenerator: 5.0.1 netmask: 2.0.2 + dev: true /package-json@6.5.0: resolution: {integrity: sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==} @@ -16070,6 +16135,7 @@ packages: /pako@0.2.9: resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} + dev: true /parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} @@ -16213,6 +16279,7 @@ packages: requiresBuild: true dependencies: safe-buffer: 5.2.1 + dev: true optional: true /pidusage@3.0.2: @@ -16220,6 +16287,7 @@ packages: engines: {node: '>=10'} dependencies: safe-buffer: 5.2.1 + dev: true /pify@4.0.1: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} @@ -16264,6 +16332,7 @@ packages: debug: 4.3.4(supports-color@8.1.1) transitivePeerDependencies: - supports-color + dev: true /pm2-axon@4.0.1: resolution: {integrity: sha512-kES/PeSLS8orT8dR5jMlNl+Yu4Ty3nbvZRmaAtROuVm9nYYGiaoXqqKQqQYzWQzMYWUKHMQTvBlirjE5GIIxqg==} @@ -16275,6 +16344,7 @@ packages: escape-string-regexp: 4.0.0 transitivePeerDependencies: - supports-color + dev: true /pm2-deploy@1.0.2: resolution: {integrity: sha512-YJx6RXKrVrWaphEYf++EdOOx9EH18vM8RSZN/P1Y+NokTKqYAca/ejXwVLyiEpNju4HPZEk3Y2uZouwMqUlcgg==} @@ -16282,11 +16352,13 @@ packages: dependencies: run-series: 1.1.9 tv4: 1.3.0 + dev: true /pm2-multimeter@0.1.2: resolution: {integrity: sha512-S+wT6XfyKfd7SJIBqRgOctGxaBzUOmVQzTAS+cg04TsEUObJVreha7lvCfX8zzGVr871XwCSnHUU7DQQ5xEsfA==} dependencies: charm: 0.1.2 + dev: true /pm2-sysmonit@1.2.8: resolution: {integrity: sha512-ACOhlONEXdCTVwKieBIQLSi2tQZ8eKinhcr9JpZSUAL8Qy0ajIgRtsLxG/lwPOW3JEKqPyw/UaHmTWhUzpP4kA==} @@ -16299,6 +16371,7 @@ packages: tx2: 1.0.5 transitivePeerDependencies: - supports-color + dev: true optional: true /pm2@5.3.1: @@ -16341,6 +16414,7 @@ packages: - bufferutil - supports-color - utf-8-validate + dev: true /pnpm-sync-dependencies-meta-injected@0.0.10: resolution: {integrity: sha512-kPcYZLaLgo5WhlgWciCJFqdxprIqaR52cY1C8KH3RGdia1YwT1wO/AOKyvOydNBJmpdcLxka2a0La9CaStxG/A==} @@ -16557,6 +16631,7 @@ packages: resolution: {integrity: sha512-aC9j+BZsRSSzEsXBNBwDnAxujdx19HycZoKgRgzWnS8eOHg1asuf9heuLprfbe739zY3IdUQx+Egv6Jn135WHA==} dependencies: read: 1.0.7 + dev: true /propagate@2.0.1: resolution: {integrity: sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==} @@ -16594,9 +16669,11 @@ packages: socks-proxy-agent: 8.0.2 transitivePeerDependencies: - supports-color + dev: true /proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + dev: true /psl@1.9.0: resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} @@ -16740,6 +16817,7 @@ packages: engines: {node: '>=0.8'} dependencies: mute-stream: 0.0.8 + dev: true /readable-stream@1.0.34: resolution: {integrity: sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==} @@ -16762,6 +16840,7 @@ packages: engines: {node: '>=8.10.0'} dependencies: picomatch: 2.3.1 + dev: true /realpath-missing@1.1.0: resolution: {integrity: sha512-wnWtnywepjg/eHIgWR97R7UuM5i+qHLA195qdN9UPKvcMqfn60+67S8sPPW3vDlSEfYHoFkKU8IvpCNty3zQvQ==} @@ -16953,6 +17032,7 @@ packages: resolve: 1.22.8 transitivePeerDependencies: - supports-color + dev: true /requireindex@1.2.0: resolution: {integrity: sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==} @@ -17179,6 +17259,7 @@ packages: /run-series@1.1.9: resolution: {integrity: sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g==} + dev: true /rxjs@6.6.7: resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} @@ -17272,6 +17353,7 @@ packages: /sax@1.3.0: resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==} + dev: true /saxes@5.0.1: resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==} @@ -17319,6 +17401,7 @@ packages: hasBin: true dependencies: lru-cache: 6.0.0 + dev: true /semver@7.6.0: resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==} @@ -17432,6 +17515,7 @@ packages: /shimmer@1.2.1: resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==} + dev: true /side-channel@1.0.6: resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} @@ -17486,6 +17570,7 @@ packages: /smart-buffer@4.2.0: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + dev: true /snapdragon-node@2.1.1: resolution: {integrity: sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==} @@ -17581,6 +17666,7 @@ packages: socks: 2.8.1 transitivePeerDependencies: - supports-color + dev: true /socks@2.8.1: resolution: {integrity: sha512-B6w7tkwNid7ToxjZ08rQMT8M9BJAf8DKx8Ft4NivzH0zBUfd6jldGcisJn/RLgxcX3FPNDdNQCUEMMT79b+oCQ==} @@ -17588,6 +17674,7 @@ packages: dependencies: ip-address: 9.0.5 smart-buffer: 4.2.0 + dev: true /sort-keys@4.2.0: resolution: {integrity: sha512-aUYIEU/UviqPgc8mHR6IW1EGxkAXpeRETYcrzg8cLAvUPZcpAlleSXHV2mY7G12GphSH6Gzv+4MMVSSkbdteHg==} @@ -17691,6 +17778,7 @@ packages: /sprintf-js@1.1.2: resolution: {integrity: sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==} + dev: true /sprintf-js@1.1.3: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} @@ -17988,6 +18076,7 @@ packages: os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android] hasBin: true requiresBuild: true + dev: true optional: true /tap-parser@7.0.0: @@ -18388,6 +18477,7 @@ packages: /tslib@1.9.3: resolution: {integrity: sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==} + dev: true /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} @@ -18461,12 +18551,14 @@ packages: /tv4@1.3.0: resolution: {integrity: sha512-afizzfpJgvPr+eDkREK4MxJ/+r8nEEHcmitwgnPUqpaP+FpwQyadnxNoSACbgc/b1LsZYtODGoPiFxQrgJgjvw==} engines: {node: '>= 0.8.0'} + dev: true /tx2@1.0.5: resolution: {integrity: sha512-sJ24w0y03Md/bxzK4FU8J8JveYYUbSs2FViLJ2D/8bytSiyPRbuE3DyL/9UKYXTZlV3yXq0L8GLlhobTnekCVg==} requiresBuild: true dependencies: json-stringify-safe: 5.0.1 + dev: true optional: true /type-check@0.4.0: @@ -18731,6 +18823,7 @@ packages: resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. hasBin: true + dev: true /uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} @@ -18754,6 +18847,7 @@ packages: git-node-fs: 1.0.0(js-git@0.7.8) ini: 1.3.8 js-git: 0.7.8 + dev: true /vscode-jsonrpc@8.1.0: resolution: {integrity: sha512-6TDy/abTQk+zDGYazgbIPc+4JoXdwC8NHU9Pbn4UJP1fehUyZmM4RHp5IthX7A6L5KS30PRui+j+tbbMMMafdw==} @@ -19145,6 +19239,7 @@ packages: optional: true utf-8-validate: optional: true + dev: true /ws@7.5.9: resolution: {integrity: sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==} @@ -19157,6 +19252,7 @@ packages: optional: true utf-8-validate: optional: true + dev: true /ws@8.11.0: resolution: {integrity: sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==} @@ -19228,6 +19324,7 @@ packages: dependencies: argparse: 1.0.10 glob: 7.2.3 + dev: true /yargs-parser@20.2.4: resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==} @@ -19532,10 +19629,6 @@ packages: chalk: 5.3.0 hono: 4.2.5 pm2: 5.3.1 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate dev: true file:packages/json-api(@babel/core@7.24.4)(@ember-data/graph@5.4.0-alpha.58)(@ember-data/request-utils@5.4.0-alpha.58)(@ember-data/store@5.4.0-alpha.58)(@glint/template@1.4.0)(@warp-drive/core-types@0.0.0-alpha.44)(ember-inflector@4.0.2): diff --git a/tests/ember-data__json-api/.mock-cache/546c9e3a/GET-0-users/1/res.body.br b/tests/ember-data__json-api/.mock-cache/546c9e3a/GET-0-users/1/res.body.br new file mode 100644 index 00000000000..8332a7f4e94 Binary files /dev/null and b/tests/ember-data__json-api/.mock-cache/546c9e3a/GET-0-users/1/res.body.br differ diff --git a/tests/ember-data__json-api/.mock-cache/546c9e3a/GET-0-users/1/res.meta.json b/tests/ember-data__json-api/.mock-cache/546c9e3a/GET-0-users/1/res.meta.json new file mode 100644 index 00000000000..080c75c05cf --- /dev/null +++ b/tests/ember-data__json-api/.mock-cache/546c9e3a/GET-0-users/1/res.meta.json @@ -0,0 +1,12 @@ +{ + "url": "users/1", + "status": 404, + "statusText": "Not Found", + "headers": { + "Content-Type": "application/vnd.api+json", + "Content-Encoding": "br", + "Cache-Control": "no-store" + }, + "method": "GET", + "requestBody": null +} \ No newline at end of file diff --git a/tests/ember-data__json-api/diagnostic.js b/tests/ember-data__json-api/diagnostic.js index ede75dbb1ad..28c6a933ba9 100644 --- a/tests/ember-data__json-api/diagnostic.js +++ b/tests/ember-data__json-api/diagnostic.js @@ -1,3 +1,13 @@ import launch from '@warp-drive/diagnostic/server/default-setup.js'; +import holodeck from '@warp-drive/holodeck'; -await launch(); +await launch({ + async setup(options) { + await holodeck.launchProgram({ + port: options.port + 1, + }); + }, + async cleanup() { + await holodeck.endProgram(); + }, +}); diff --git a/tests/ember-data__json-api/package.json b/tests/ember-data__json-api/package.json index af3772238d8..4d46cd3f2cc 100644 --- a/tests/ember-data__json-api/package.json +++ b/tests/ember-data__json-api/package.json @@ -16,6 +16,7 @@ }, "scripts": { "build:tests": "IS_TESTING=true EMBER_CLI_TEST_COMMAND=true ember build --output-path=dist-test --suppress-sizes", + "start": "bun run build:tests --watch", "build:production": "pnpm build:tests -e production", "lint": "eslint . --quiet --cache --cache-strategy=content --ext .js,.ts,.mjs,.cjs --report-unused-disable-directives", "check:types": "tsc --noEmit", @@ -27,6 +28,9 @@ "@warp-drive/diagnostic": { "injected": true }, + "@warp-drive/holodeck": { + "injected": true + }, "@ember-data/json-api": { "injected": true }, @@ -94,6 +98,7 @@ "@glimmer/tracking": "^1.1.2", "@warp-drive/core-types": "workspace:0.0.0-alpha.44", "@warp-drive/diagnostic": "workspace:0.0.0-alpha.44", + "@warp-drive/holodeck": "workspace:0.0.0-alpha.44", "@warp-drive/internal-config": "workspace:5.4.0-alpha.58", "ember-auto-import": "^2.7.2", "ember-cli": "~5.7.0", diff --git a/tests/ember-data__json-api/tests/integration/cache/error-documents-test.ts b/tests/ember-data__json-api/tests/integration/cache/error-documents-test.ts new file mode 100644 index 00000000000..27813e68af6 --- /dev/null +++ b/tests/ember-data__json-api/tests/integration/cache/error-documents-test.ts @@ -0,0 +1,109 @@ +import Cache from '@ember-data/json-api'; +import RequestManager from '@ember-data/request'; +import Fetch from '@ember-data/request/fetch'; +import Store, { CacheHandler } from '@ember-data/store'; +import type { CacheCapabilitiesManager } from '@ember-data/store/-types/q/cache-store-wrapper'; +import { module, test } from '@warp-drive/diagnostic'; +import { mock, MockServerHandler } from '@warp-drive/holodeck'; + +const RECORD = false; + +function isNetworkError(e: unknown): asserts e is Error & { + status: number; + statusText: string; + code: number; + name: string; + isRequestError: boolean; + content?: object; + errors?: object[]; +} { + if (!(e instanceof Error)) { + throw new Error('Expected a network error'); + } +} + +class TestStore extends Store { + override createCache(wrapper: CacheCapabilitiesManager) { + return new Cache(wrapper); + } +} + +module('Integration | @ember-data/json-api Cach.put()', function (hooks) { + test('Useful errors are propagated by the CacheHandler', async function (assert) { + const manager = new RequestManager(); + const store = new TestStore(); + + manager.use([new MockServerHandler(this), Fetch]); + manager.useCache(CacheHandler); + store.requestManager = manager; + + await mock( + this, + () => ({ + url: 'users/1', + status: 404, + headers: {}, + method: 'GET', + statusText: 'Not Found', + body: null, + response: { + errors: [ + { + status: '404', + title: 'Not Found', + detail: 'The resource does not exist.', + }, + ], + }, + }), + RECORD + ); + + try { + await store.request({ url: 'https://localhost:1135/users/1' }); + assert.ok(false, 'Should have thrown'); + } catch (e) { + isNetworkError(e); + assert.true(e instanceof AggregateError, 'The error is an AggregateError'); + assert.equal( + e.message, + '[404 Not Found] GET (cors) - https://localhost:1135/users/1', + 'The error message is correct' + ); + assert.equal(e.status, 404, 'The error status is correct'); + assert.equal(e.statusText, 'Not Found', 'The error statusText is correct'); + assert.equal(e.code, 404, 'The error code is correct'); + assert.equal(e.name, 'NotFoundError', 'The error code is correct'); + assert.true(e.isRequestError, 'The error is a request error'); + + // error.content is present + assert.satisfies( + // @ts-expect-error content property is loosely typed + e.content, + { + errors: [ + { + status: '404', + title: 'Not Found', + detail: 'The resource does not exist.', + }, + ], + }, + 'The error.content is present' + ); + + // error.errors is present + assert.deepEqual( + e.errors, + [ + { + status: '404', + title: 'Not Found', + detail: 'The resource does not exist.', + }, + ], + 'The error.errors is present' + ); + } + }); +}); diff --git a/tests/ember-data__json-api/tests/test-helper.js b/tests/ember-data__json-api/tests/test-helper.js index bcb6a580430..9cd875e8169 100644 --- a/tests/ember-data__json-api/tests/test-helper.js +++ b/tests/ember-data__json-api/tests/test-helper.js @@ -1,10 +1,29 @@ +import { setBuildURLConfig } from '@ember-data/request-utils'; +import { setupGlobalHooks } from '@warp-drive/diagnostic'; import { configure } from '@warp-drive/diagnostic/ember'; import { start } from '@warp-drive/diagnostic/runners/dom'; +import { setConfig, setTestId } from '@warp-drive/holodeck'; + +const MockHost = `https://${window.location.hostname}:${Number(window.location.port) + 1}`; +setBuildURLConfig({ + host: MockHost, + namespace: '', +}); +setConfig({ host: MockHost }); +setupGlobalHooks((hooks) => { + hooks.beforeEach(function (assert) { + setTestId(this, assert.test.testId); + }); + hooks.afterEach(function () { + setTestId(this, null); + }); +}); configure(); start({ tryCatch: false, + debug: false, groupLogs: false, instrument: true, hideReport: true, diff --git a/tests/ember-data__request/diagnostic.js b/tests/ember-data__request/diagnostic.js index b207883e528..3d888b65b69 100644 --- a/tests/ember-data__request/diagnostic.js +++ b/tests/ember-data__request/diagnostic.js @@ -1,23 +1,13 @@ -/* global Bun */ import launch from '@warp-drive/diagnostic/server/default-setup.js'; - -/** @type {import('bun-types')} */ +import holodeck from '@warp-drive/holodeck'; await launch({ - async setup() { - Bun.spawnSync(['holodeck', 'start'], { - env: process.env, - cwd: process.cwd(), - stdout: 'inherit', - stderr: 'inherit', + async setup(info) { + await holodeck.launchProgram({ + port: info.port + 1, }); }, async cleanup() { - Bun.spawnSync(['holodeck', 'end'], { - env: process.env, - cwd: process.cwd(), - stdout: 'inherit', - stderr: 'inherit', - }); + await holodeck.endProgram(); }, }); diff --git a/tests/ember-data__request/holodeck.mjs b/tests/ember-data__request/holodeck.mjs deleted file mode 100644 index 610871e25dc..00000000000 --- a/tests/ember-data__request/holodeck.mjs +++ /dev/null @@ -1,10 +0,0 @@ -import { dirname } from 'node:path'; -import { fileURLToPath } from 'url'; - -import { createServer } from '@warp-drive/holodeck'; - -const __dirname = dirname(fileURLToPath(import.meta.url)); - -export default createServer({ - projectRoot: __dirname, -}); diff --git a/tests/ember-data__request/package.json b/tests/ember-data__request/package.json index b8995aad455..54a81b64593 100644 --- a/tests/ember-data__request/package.json +++ b/tests/ember-data__request/package.json @@ -16,6 +16,7 @@ }, "scripts": { "build:tests": "IS_TESTING=true EMBER_CLI_TEST_COMMAND=true ember build --output-path=dist-test --suppress-sizes", + "start": "bun run build:tests --watch", "_build:production": "pnpm build:tests -e production", "lint": "eslint . --quiet --cache --cache-strategy=content --ext .js,.ts,.mjs,.cjs --report-unused-disable-directives", "check:types": "tsc --noEmit", @@ -30,6 +31,9 @@ "@ember-data/request": { "injected": true }, + "@ember-data/request-utils": { + "injected": true + }, "@ember-data/private-build-infra": { "injected": true }, @@ -51,6 +55,7 @@ "@babel/runtime": "^7.24.4", "@ember-data/private-build-infra": "workspace:5.4.0-alpha.58", "@ember-data/request": "workspace:5.4.0-alpha.58", + "@ember-data/request-utils": "workspace:5.4.0-alpha.58", "@ember/edition-utils": "^1.2.0", "@ember/optional-features": "^2.1.0", "@ember/string": "3.1.1", diff --git a/tests/ember-data__request/tests/integration/abort-test.ts b/tests/ember-data__request/tests/integration/abort-test.ts index 24f2122618a..39e39d8fccd 100644 --- a/tests/ember-data__request/tests/integration/abort-test.ts +++ b/tests/ember-data__request/tests/integration/abort-test.ts @@ -148,7 +148,7 @@ module('RequestManager | Abort', function () { assert.ok(false, 'aborting a request should result in the promise rejecting'); } catch (e) { assert.true(e instanceof Error); - assert.equal((e as Error).message, 'The user aborted a request.', 'We got the correct error'); + assert.equal((e as Error).message, 'Root Controller Aborted', 'We got the correct error'); } }); @@ -201,7 +201,7 @@ module('RequestManager | Abort', function () { assert.ok(false, 'aborting a request should result in the promise rejecting'); } catch (e) { assert.true(e instanceof Error); - assert.equal((e as Error).message, 'The user aborted a request.', 'We got the correct error'); + assert.equal((e as Error).message, 'Root Controller Aborted', 'We got the correct error'); assert.false(signal.aborted, 'The root signal is not aborted'); } }); diff --git a/tests/ember-data__request/tests/integration/fetch-handler-test.ts b/tests/ember-data__request/tests/integration/fetch-handler-test.ts index a4a5cabbddc..cea431bdc6f 100644 --- a/tests/ember-data__request/tests/integration/fetch-handler-test.ts +++ b/tests/ember-data__request/tests/integration/fetch-handler-test.ts @@ -1,9 +1,12 @@ import RequestManager from '@ember-data/request'; import Fetch from '@ember-data/request/fetch'; +import { buildBaseURL } from '@ember-data/request-utils'; import { module, test } from '@warp-drive/diagnostic'; import { mock, MockServerHandler } from '@warp-drive/holodeck'; import { GET } from '@warp-drive/holodeck/mock'; +const RECORD = false; + function isNetworkError(e: unknown): asserts e is Error & { status: number; statusText: string; @@ -23,17 +26,22 @@ module('RequestManager | Fetch Handler', function (hooks) { const manager = new RequestManager(); manager.use([new MockServerHandler(this), Fetch]); - await GET(this, 'users/1', () => ({ - data: { - id: '1', - type: 'user', - attributes: { - name: 'Chris Thoburn', + await GET( + this, + 'users/1', + () => ({ + data: { + id: '1', + type: 'user', + attributes: { + name: 'Chris Thoburn', + }, }, - }, - })); + }), + { RECORD } + ); - const doc = await manager.request({ url: 'https://localhost:1135/users/1' }); + const doc = await manager.request({ url: buildBaseURL({ resourcePath: 'users/1' }) }); const serialized = JSON.parse(JSON.stringify(doc)) as unknown; // @ts-expect-error // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access @@ -55,7 +63,7 @@ module('RequestManager | Fetch Handler', function (hooks) { }, }, request: { - url: 'https://localhost:1135/users/1', + url: buildBaseURL({ resourcePath: 'users/1' }), }, response: { headers: [ @@ -78,33 +86,37 @@ module('RequestManager | Fetch Handler', function (hooks) { const manager = new RequestManager(); manager.use([new MockServerHandler(this), Fetch]); - await mock(this, () => ({ - url: 'users/1', - status: 404, - headers: {}, - method: 'GET', - statusText: 'Not Found', - body: null, - response: { - errors: [ - { - status: '404', - title: 'Not Found', - detail: 'The resource does not exist.', - }, - ], - }, - })); + await mock( + this, + () => ({ + url: 'users/1', + status: 404, + headers: {}, + method: 'GET', + statusText: 'Not Found', + body: null, + response: { + errors: [ + { + status: '404', + title: 'Not Found', + detail: 'The resource does not exist.', + }, + ], + }, + }), + RECORD + ); try { - await manager.request({ url: 'https://localhost:1135/users/1' }); + await manager.request({ url: buildBaseURL({ resourcePath: 'users/1' }) }); assert.ok(false, 'Should have thrown'); } catch (e) { isNetworkError(e); assert.true(e instanceof AggregateError, 'The error is an AggregateError'); assert.equal( e.message, - '[404 Not Found] GET (cors) - https://localhost:1135/users/1', + `[404 Not Found] GET (cors) - ${buildBaseURL({ resourcePath: 'users/1' })}`, 'The error message is correct' ); assert.equal(e.status, 404, 'The error status is correct'); @@ -147,18 +159,23 @@ module('RequestManager | Fetch Handler', function (hooks) { const manager = new RequestManager(); manager.use([new MockServerHandler(this), Fetch]); - await GET(this, 'users/1', () => ({ - data: { - id: '1', - type: 'user', - attributes: { - name: 'Chris Thoburn', + await GET( + this, + 'users/1', + () => ({ + data: { + id: '1', + type: 'user', + attributes: { + name: 'Chris Thoburn', + }, }, - }, - })); + }), + { RECORD } + ); try { - const future = manager.request({ url: 'https://localhost:1135/users/1' }); + const future = manager.request({ url: buildBaseURL({ resourcePath: 'users/1' }) }); await Promise.resolve(); future.abort(); await future; diff --git a/tests/ember-data__request/tests/test-helper.js b/tests/ember-data__request/tests/test-helper.js index e6dfb61d16a..90ae7c5a125 100644 --- a/tests/ember-data__request/tests/test-helper.js +++ b/tests/ember-data__request/tests/test-helper.js @@ -1,7 +1,15 @@ +import { setBuildURLConfig } from '@ember-data/request-utils'; import { setupGlobalHooks } from '@warp-drive/diagnostic'; import { configure } from '@warp-drive/diagnostic/ember'; import { start } from '@warp-drive/diagnostic/runners/dom'; -import { setTestId } from '@warp-drive/holodeck'; +import { setConfig, setTestId } from '@warp-drive/holodeck'; + +const MockHost = `https://${window.location.hostname}:${Number(window.location.port) + 1}`; +setBuildURLConfig({ + host: MockHost, + namespace: '', +}); +setConfig({ host: MockHost }); setupGlobalHooks((hooks) => { hooks.beforeEach(function (assert) { @@ -15,11 +23,11 @@ setupGlobalHooks((hooks) => { configure(); start({ - tryCatch: true, - debug: true, + tryCatch: false, + debug: false, concurrency: 10, groupLogs: false, instrument: true, - hideReport: false, + hideReport: true, useDiagnostic: true, }); diff --git a/tests/ember-data__request/tsconfig.json b/tests/ember-data__request/tsconfig.json index 3305ef94072..915e6879cc8 100644 --- a/tests/ember-data__request/tsconfig.json +++ b/tests/ember-data__request/tsconfig.json @@ -30,5 +30,5 @@ "types": ["ember-source/types"], "paths": {} }, - "references": [] + "references": [{ "path": "../../packages/request" }, { "path": "../../packages/request-utils" }] } diff --git a/tests/main/holodeck.mjs b/tests/main/holodeck.mjs deleted file mode 100644 index 2454015718e..00000000000 --- a/tests/main/holodeck.mjs +++ /dev/null @@ -1,11 +0,0 @@ -import { dirname } from 'node:path'; -import { fileURLToPath } from 'url'; - -import { createServer } from '@warp-drive/holodeck'; - -const __dirname = dirname(fileURLToPath(import.meta.url)); - -export default createServer({ - projectRoot: __dirname, - port: 1136, -}); diff --git a/tests/main/package.json b/tests/main/package.json index 371bd19618f..7ecdf7f077f 100644 --- a/tests/main/package.json +++ b/tests/main/package.json @@ -19,12 +19,10 @@ "check:types": "tsc --noEmit", "examine": "export EXAM_PARALLEL_COUNT=$(./bin/calculate-test-jobs); ember exam --test-port=0 --path=dist-test --parallel=$EXAM_PARALLEL_COUNT --load-balance", "test:try-one": "ember try:one", - "holodeck:start-program": "holodeck start", - "holodeck:end-program": "holodeck end", "launch:tests": "ember test --test-port=0 --serve --no-launch", - "start": "holodeck run launch:tests", - "test": "holodeck run examine", - "test:production": "holodeck run examine", + "start": "bun run build:tests --watch", + "test": "bun run examine", + "test:production": "bun run examine", "typescript": "^5.2.2", "_syncPnpm": "bun run sync-dependencies-meta-injected" }, diff --git a/tests/main/testem.js b/tests/main/testem.js index b65e2597d27..900bc1252b7 100644 --- a/tests/main/testem.js +++ b/tests/main/testem.js @@ -1,3 +1,14 @@ const TestemConfig = require('@ember-data/unpublished-test-infra/src/testem/testem'); -module.exports = TestemConfig; +module.exports = async function () { + const holodeck = (await import('@warp-drive/holodeck')).default; + await holodeck.launchProgram({ + port: 7373, + }); + + process.on('beforeExit', async () => { + await holodeck.endProgram(); + }); + + return TestemConfig; +}; diff --git a/tests/main/tests/test-helper.js b/tests/main/tests/test-helper.js index a5e9252a732..592e17b9154 100644 --- a/tests/main/tests/test-helper.js +++ b/tests/main/tests/test-helper.js @@ -8,12 +8,20 @@ import { setup } from 'qunit-dom'; import start from 'ember-exam/test-support/start'; +import { setBuildURLConfig } from '@ember-data/request-utils'; import configureAsserts from '@ember-data/unpublished-test-infra/test-support/asserts'; -import { setTestId } from '@warp-drive/holodeck'; +import { setConfig, setTestId } from '@warp-drive/holodeck'; import Application from '../app'; import config from '../config/environment'; +const MockHost = `https://${window.location.hostname}:${Number(window.location.port) + 1}`; +setBuildURLConfig({ + host: MockHost, + namespace: '', +}); +setConfig({ host: MockHost }); + QUnit.config.urlConfig.push( { id: 'debugMemory', diff --git a/tests/warp-drive__ember/diagnostic.js b/tests/warp-drive__ember/diagnostic.js index b207883e528..28c6a933ba9 100644 --- a/tests/warp-drive__ember/diagnostic.js +++ b/tests/warp-drive__ember/diagnostic.js @@ -1,23 +1,13 @@ -/* global Bun */ import launch from '@warp-drive/diagnostic/server/default-setup.js'; - -/** @type {import('bun-types')} */ +import holodeck from '@warp-drive/holodeck'; await launch({ - async setup() { - Bun.spawnSync(['holodeck', 'start'], { - env: process.env, - cwd: process.cwd(), - stdout: 'inherit', - stderr: 'inherit', + async setup(options) { + await holodeck.launchProgram({ + port: options.port + 1, }); }, async cleanup() { - Bun.spawnSync(['holodeck', 'end'], { - env: process.env, - cwd: process.cwd(), - stdout: 'inherit', - stderr: 'inherit', - }); + await holodeck.endProgram(); }, }); diff --git a/tests/warp-drive__ember/holodeck.mjs b/tests/warp-drive__ember/holodeck.mjs deleted file mode 100644 index 610871e25dc..00000000000 --- a/tests/warp-drive__ember/holodeck.mjs +++ /dev/null @@ -1,10 +0,0 @@ -import { dirname } from 'node:path'; -import { fileURLToPath } from 'url'; - -import { createServer } from '@warp-drive/holodeck'; - -const __dirname = dirname(fileURLToPath(import.meta.url)); - -export default createServer({ - projectRoot: __dirname, -}); diff --git a/tests/warp-drive__ember/tests/integration/request-component-test.gts b/tests/warp-drive__ember/tests/integration/request-component-test.gts index 14da774279d..1b8865c1c3e 100644 --- a/tests/warp-drive__ember/tests/integration/request-component-test.gts +++ b/tests/warp-drive__ember/tests/integration/request-component-test.gts @@ -48,7 +48,7 @@ function setupOnError(cb: (message: Error | string) => void) { return cleanup; } -const RECORD = true; +const RECORD = false; type UserResource = { data: { diff --git a/tests/warp-drive__ember/tests/test-helper.ts b/tests/warp-drive__ember/tests/test-helper.ts index 127c586691b..dc75f247c67 100644 --- a/tests/warp-drive__ember/tests/test-helper.ts +++ b/tests/warp-drive__ember/tests/test-helper.ts @@ -1,14 +1,22 @@ import { setApplication } from '@ember/test-helpers'; +import { setBuildURLConfig } from '@ember-data/request-utils'; import configureAsserts from '@ember-data/unpublished-test-infra/test-support/asserts'; import { setupGlobalHooks } from '@warp-drive/diagnostic'; import { configure } from '@warp-drive/diagnostic/ember'; import { start } from '@warp-drive/diagnostic/runners/dom'; -import { setTestId } from '@warp-drive/holodeck'; +import { setConfig, setTestId } from '@warp-drive/holodeck'; import Application from 'warp-drive__ember/app'; import config from 'warp-drive__ember/config/environment'; +const MockHost = `https://${window.location.hostname}:${Number(window.location.port) + 1}`; +setBuildURLConfig({ + host: MockHost, + namespace: '', +}); +setConfig({ host: MockHost }); + setupGlobalHooks((hooks) => { configureAsserts(hooks); });