diff --git a/test-runtimes/.gitignore b/test-runtimes/.gitignore new file mode 100644 index 00000000..98483fb0 --- /dev/null +++ b/test-runtimes/.gitignore @@ -0,0 +1,2 @@ +# Exclude lockfiles for packaging tests to reduce file churn +/*/package-lock.json diff --git a/test-runtimes/README.md b/test-runtimes/README.md new file mode 100644 index 00000000..30ed2ab5 --- /dev/null +++ b/test-runtimes/README.md @@ -0,0 +1,63 @@ +## Test Runtime Fixtures + +The Javascript language has a _LOT_ of runtimes. We try to support them all. If you're having trouble using Stytch due to +a runtime-related reason, please [open an issue](https://github.com/stytchauth/stytch-node/issues/new) and we'll take a look. + +This directory contains test fixtures that make it easier to quickly use the `stytch` package in a number of locations. +This directory is a scratch space - expect things to be slightly broken or cumbersome to use. For example, we don't have +any CI/CD set up for these projects, and if you want to contribute you'll need to create your own credentials for use with +various runtime providers. + +When adding a new runtime, try to keep the fixture as close to minimal as possible. For example, prefer a `npx create-project...` style command. + +Runtime validation is defined as: + +- The Stytch client can be successfully imported +- The Stytch client can make a successful network request to the Stytch servers + +We can use the following project ID and secret, which are special values that return a pre-canned response from Stytch: + +```javascript +// Find these values at https://stytch.com/dashboard/api-keys +// These ones will trigger a well-known erorr message +const client = new stytch.Client({ + project_id: "project-live-c60c0abe-c25a-4472-a9ed-320c6667d317", + secret: "secret-live-80JASucyk7z_G8Z-7dVwZVGXL5NT_qGAQ2I=", +}); +``` + +## Runtime Notes + +All Runtimes: + +- Each runtime maintains its own `package.json` and set of node modules. We'll use real workspaces SomeDay™️ +- The `stytch` version in the package might need manual adjusting. It could be a prerelease version of something you'd like to check, or `../..` to use the local copy of `stytch`. +- Local copies don't seem to work with Vercel production builds, only, well, locally + +### Cloudflare Workers + +- You'll need a Cloudflare account +- `npm run start` for local development +- `npm run deploy` will build and deploy the worker. The CLI should guide you through credential setupneed to publish a package + +### Remix + +- You'll need a Vercel account +- `npm run dev` for local development +- `npx vercel` to deploy to vercel + +## NextJS + +- You'll need a Vercel account +- `npm run dev` for local development +- `npx vercel` to deploy to vercel +- `/api/hello` runs a serverless request +- `/api/edge` runs an edge request + +## Deno + +TODO! + +## Bun + +TODO! diff --git a/test-runtimes/bun/.gitignore b/test-runtimes/bun/.gitignore new file mode 100644 index 00000000..ab5afb29 --- /dev/null +++ b/test-runtimes/bun/.gitignore @@ -0,0 +1,176 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +\*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +\*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +\*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +\*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.cache +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +.cache/ + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp +.cache + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.\* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store + diff --git a/test-runtimes/bun/README.md b/test-runtimes/bun/README.md new file mode 100644 index 00000000..2172c079 --- /dev/null +++ b/test-runtimes/bun/README.md @@ -0,0 +1,15 @@ +# bun + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.0.4. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/test-runtimes/bun/bun.lockb b/test-runtimes/bun/bun.lockb new file mode 100755 index 00000000..15816dba Binary files /dev/null and b/test-runtimes/bun/bun.lockb differ diff --git a/test-runtimes/bun/index.ts b/test-runtimes/bun/index.ts new file mode 100644 index 00000000..16b846ce --- /dev/null +++ b/test-runtimes/bun/index.ts @@ -0,0 +1,16 @@ +import * as stytch from "stytch"; + +export function doStytchRequest(): Promise { + // Find these values at https://stytch.com/dashboard/api-keys + // These ones will trigger a well-known erorr message + return new stytch.Client({ + project_id: "project-live-c60c0abe-c25a-4472-a9ed-320c6667d317", + secret: "secret-live-80JASucyk7z_G8Z-7dVwZVGXL5NT_qGAQ2I=", + }).magicLinks + .authenticate({ + session_token: "WJtR5BCy38Szd5AfoDpf0iqFKEt4EE5JhjlWUY7l3FtY", + }) + .catch((err: unknown) => console.log(JSON.stringify(err))); +} + +doStytchRequest(); diff --git a/test-runtimes/bun/package.json b/test-runtimes/bun/package.json new file mode 100644 index 00000000..f0c1e7bd --- /dev/null +++ b/test-runtimes/bun/package.json @@ -0,0 +1,14 @@ +{ + "name": "bun", + "module": "index.ts", + "type": "module", + "devDependencies": { + "bun-types": "latest" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "dependencies": { + "stytch": "next" + } +} diff --git a/test-runtimes/bun/tsconfig.json b/test-runtimes/bun/tsconfig.json new file mode 100644 index 00000000..7556e1d4 --- /dev/null +++ b/test-runtimes/bun/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "lib": ["ESNext"], + "module": "esnext", + "target": "esnext", + "moduleResolution": "bundler", + "moduleDetection": "force", + "allowImportingTsExtensions": true, + "noEmit": true, + "composite": true, + "strict": true, + "downlevelIteration": true, + "skipLibCheck": true, + "jsx": "react-jsx", + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "allowJs": true, + "types": [ + "bun-types" // add Bun global + ] + } +} diff --git a/test-runtimes/cloudflare-workers/.editorconfig b/test-runtimes/cloudflare-workers/.editorconfig new file mode 100644 index 00000000..64ab2601 --- /dev/null +++ b/test-runtimes/cloudflare-workers/.editorconfig @@ -0,0 +1,13 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = tab +tab_width = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.yml] +indent_style = space diff --git a/test-runtimes/cloudflare-workers/.prettierrc b/test-runtimes/cloudflare-workers/.prettierrc new file mode 100644 index 00000000..5c7b5d3c --- /dev/null +++ b/test-runtimes/cloudflare-workers/.prettierrc @@ -0,0 +1,6 @@ +{ + "printWidth": 140, + "singleQuote": true, + "semi": true, + "useTabs": true +} diff --git a/test-runtimes/cloudflare-workers/package.json b/test-runtimes/cloudflare-workers/package.json new file mode 100644 index 00000000..76b263da --- /dev/null +++ b/test-runtimes/cloudflare-workers/package.json @@ -0,0 +1,17 @@ +{ + "name": "cloudflare-workers", + "version": "0.0.0", + "private": true, + "scripts": { + "deploy": "node_modules/.bin/wrangler deploy", + "start": "node_modules/.bin/wrangler dev" + }, + "devDependencies": { + "@cloudflare/workers-types": "^4.20230419.0", + "typescript": "^5.0.4", + "wrangler": "^3.0.0" + }, + "dependencies": { + "stytch": "^9.0.0-rc.1" + } +} diff --git a/test-runtimes/cloudflare-workers/src/index.ts b/test-runtimes/cloudflare-workers/src/index.ts new file mode 100644 index 00000000..806c4a25 --- /dev/null +++ b/test-runtimes/cloudflare-workers/src/index.ts @@ -0,0 +1,46 @@ +import * as stytch from 'stytch'; + +const client = new stytch.Client({ + // Find these values at https://stytch.com/dashboard/api-keys + // These ones will trigger a well-known erorr message + project_id: 'project-live-c60c0abe-c25a-4472-a9ed-320c6667d317', + secret: 'secret-live-80JASucyk7z_G8Z-7dVwZVGXL5NT_qGAQ2I=', +}); + +/** + * Welcome to Cloudflare Workers! This is your first worker. + * + * - Run `npm run dev` in your terminal to start a development server + * - Open a browser tab at http://localhost:8787/ to see your worker in action + * - Run `npm run deploy` to publish your worker + * + * Learn more at https://developers.cloudflare.com/workers/ + */ + +export interface Env { + // Example binding to KV. Learn more at https://developers.cloudflare.com/workers/runtime-apis/kv/ + // MY_KV_NAMESPACE: KVNamespace; + // + // Example binding to Durable Object. Learn more at https://developers.cloudflare.com/workers/runtime-apis/durable-objects/ + // MY_DURABLE_OBJECT: DurableObjectNamespace; + // + // Example binding to R2. Learn more at https://developers.cloudflare.com/workers/runtime-apis/r2/ + // MY_BUCKET: R2Bucket; + // + // Example binding to a Service. Learn more at https://developers.cloudflare.com/workers/runtime-apis/service-bindings/ + // MY_SERVICE: Fetcher; + // + // Example binding to a Queue. Learn more at https://developers.cloudflare.com/queues/javascript-apis/ + // MY_QUEUE: Queue; +} + +export default { + async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise { + const authenticateResponse = await client.sessions + .authenticate({ + session_token: 'WJtR5BCy38Szd5AfoDpf0iqFKEt4EE5JhjlWUY7l3FtY', + }) + .catch((err) => err); + return new Response(JSON.stringify(authenticateResponse)); + }, +}; diff --git a/test-runtimes/cloudflare-workers/tsconfig.json b/test-runtimes/cloudflare-workers/tsconfig.json new file mode 100644 index 00000000..2cb9189a --- /dev/null +++ b/test-runtimes/cloudflare-workers/tsconfig.json @@ -0,0 +1,101 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Projects */ + // "incremental": true, /* Enable incremental compilation */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + "lib": ["es2021"] /* Specify a set of bundled library declaration files that describe the target runtime environment. */, + "jsx": "react" /* Specify what JSX code is generated. */, + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ + // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + + /* Modules */ + "module": "es2022" /* Specify what module code is generated. */, + // "rootDir": "./", /* Specify the root folder within your source files. */ + "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ + "types": ["@cloudflare/workers-types"] /* Specify type package names to be included without being referenced in a source file. */, + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + "resolveJsonModule": true /* Enable importing .json files */, + // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + "allowJs": true /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */, + "checkJs": false /* Enable error reporting in type-checked JavaScript files. */, + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + "noEmit": true /* Disable emitting files from a compilation. */, + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + "isolatedModules": true /* Ensure that each file can be safely transpiled without relying on other imports. */, + "allowSyntheticDefaultImports": true /* Allow 'import x from y' when a module doesn't have a default export. */, + // "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */, + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, + + /* Type Checking */ + "strict": true /* Enable all strict type-checking options. */, + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ + // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ + // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} diff --git a/test-runtimes/cloudflare-workers/wrangler.toml b/test-runtimes/cloudflare-workers/wrangler.toml new file mode 100644 index 00000000..edb99c2f --- /dev/null +++ b/test-runtimes/cloudflare-workers/wrangler.toml @@ -0,0 +1,51 @@ +name = "cloudflare-workers" +main = "src/index.ts" +compatibility_date = "2023-10-02" + +# Variable bindings. These are arbitrary, plaintext strings (similar to environment variables) +# Note: Use secrets to store sensitive data. +# Docs: https://developers.cloudflare.com/workers/platform/environment-variables +# [vars] +# MY_VARIABLE = "production_value" + +# Bind a KV Namespace. Use KV as persistent storage for small key-value pairs. +# Docs: https://developers.cloudflare.com/workers/runtime-apis/kv +# [[kv_namespaces]] +# binding = "MY_KV_NAMESPACE" +# id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + +# Bind an R2 Bucket. Use R2 to store arbitrarily large blobs of data, such as files. +# Docs: https://developers.cloudflare.com/r2/api/workers/workers-api-usage/ +# [[r2_buckets]] +# binding = "MY_BUCKET" +# bucket_name = "my-bucket" + +# Bind a Queue producer. Use this binding to schedule an arbitrary task that may be processed later by a Queue consumer. +# Docs: https://developers.cloudflare.com/queues/get-started +# [[queues.producers]] +# binding = "MY_QUEUE" +# queue = "my-queue" + +# Bind a Queue consumer. Queue Consumers can retrieve tasks scheduled by Producers to act on them. +# Docs: https://developers.cloudflare.com/queues/get-started +# [[queues.consumers]] +# queue = "my-queue" + +# Bind another Worker service. Use this binding to call another Worker without network overhead. +# Docs: https://developers.cloudflare.com/workers/platform/services +# [[services]] +# binding = "MY_SERVICE" +# service = "/api/*" + +# Bind a Durable Object. Durable objects are a scale-to-zero compute primitive based on the actor model. +# Durable Objects can live for as long as needed. Use these when you need a long-running "server", such as in realtime apps. +# Docs: https://developers.cloudflare.com/workers/runtime-apis/durable-objects +# [[durable_objects.bindings]] +# name = "MY_DURABLE_OBJECT" +# class_name = "MyDurableObject" + +# Durable Object migrations. +# Docs: https://developers.cloudflare.com/workers/learning/using-durable-objects#configure-durable-object-classes-with-migrations +# [[migrations]] +# tag = "v1" +# new_classes = ["MyDurableObject"] diff --git a/test-runtimes/deno/deno.json b/test-runtimes/deno/deno.json new file mode 100644 index 00000000..3c5130f1 --- /dev/null +++ b/test-runtimes/deno/deno.json @@ -0,0 +1,5 @@ +{ + "tasks": { + "dev": "deno run --watch main.ts" + } +} diff --git a/test-runtimes/deno/deno.lock b/test-runtimes/deno/deno.lock new file mode 100644 index 00000000..5863dbd5 --- /dev/null +++ b/test-runtimes/deno/deno.lock @@ -0,0 +1,54 @@ +{ + "version": "3", + "packages": { + "specifiers": { + "npm:stytch": "npm:stytch@8.4.2" + }, + "npm": { + "isomorphic-unfetch@3.1.0": { + "integrity": "sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==", + "dependencies": { + "node-fetch": "node-fetch@2.7.0", + "unfetch": "unfetch@4.2.0" + } + }, + "jose@4.15.2": { + "integrity": "sha512-IY73F228OXRl9ar3jJagh7Vnuhj/GzBunPiZP13K0lOl7Am9SoWW3kEzq3MCllJMTtZqHTiDXQvoRd4U95aU6A==", + "dependencies": {} + }, + "node-fetch@2.7.0": { + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "whatwg-url@5.0.0" + } + }, + "stytch@8.4.2": { + "integrity": "sha512-AL46Rtct731Pha7VUOKPZ0gZSsr43dBCVihcO00hAXX17TntQSY0MXdOqYTJAI0KimmvpR/UD+zmAf/ofig1pQ==", + "dependencies": { + "isomorphic-unfetch": "isomorphic-unfetch@3.1.0", + "jose": "jose@4.15.2" + } + }, + "tr46@0.0.3": { + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dependencies": {} + }, + "unfetch@4.2.0": { + "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==", + "dependencies": {} + }, + "webidl-conversions@3.0.1": { + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dependencies": {} + }, + "whatwg-url@5.0.0": { + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "tr46@0.0.3", + "webidl-conversions": "webidl-conversions@3.0.1" + } + } + } + }, + "remote": {} +} diff --git a/test-runtimes/deno/main.ts b/test-runtimes/deno/main.ts new file mode 100644 index 00000000..3ebb621a --- /dev/null +++ b/test-runtimes/deno/main.ts @@ -0,0 +1,19 @@ +import * as stytch from "npm:stytch"; + +export function doStytchRequest(): Promise { + // Find these values at https://stytch.com/dashboard/api-keys + // These ones will trigger a well-known erorr message + return new stytch.Client({ + project_id: "project-live-c60c0abe-c25a-4472-a9ed-320c6667d317", + secret: "secret-live-80JASucyk7z_G8Z-7dVwZVGXL5NT_qGAQ2I=", + }).magicLinks + .authenticate({ + session_token: "WJtR5BCy38Szd5AfoDpf0iqFKEt4EE5JhjlWUY7l3FtY", + }) + .catch((err) => console.log(JSON.stringify(err))); +} + +// Learn more at https://deno.land/manual/examples/module_metadata#concepts +if (import.meta.main) { + doStytchRequest(); +} diff --git a/test-runtimes/nextjs/.gitignore b/test-runtimes/nextjs/.gitignore new file mode 100644 index 00000000..8f322f0d --- /dev/null +++ b/test-runtimes/nextjs/.gitignore @@ -0,0 +1,35 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/test-runtimes/nextjs/README.md b/test-runtimes/nextjs/README.md new file mode 100644 index 00000000..a75ac524 --- /dev/null +++ b/test-runtimes/nextjs/README.md @@ -0,0 +1,40 @@ +This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. + +[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. + +The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. + +This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. diff --git a/test-runtimes/nextjs/next.config.js b/test-runtimes/nextjs/next.config.js new file mode 100644 index 00000000..91ef62f0 --- /dev/null +++ b/test-runtimes/nextjs/next.config.js @@ -0,0 +1,6 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, +}; + +module.exports = nextConfig; diff --git a/test-runtimes/nextjs/package.json b/test-runtimes/nextjs/package.json new file mode 100644 index 00000000..a46f7bff --- /dev/null +++ b/test-runtimes/nextjs/package.json @@ -0,0 +1,23 @@ +{ + "name": "nextjs", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "next": "13.5.4", + "react": "^18", + "react-dom": "^18", + "stytch": "^9.0.0-rc.1" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "typescript": "^5" + } +} diff --git a/test-runtimes/nextjs/public/favicon.ico b/test-runtimes/nextjs/public/favicon.ico new file mode 100644 index 00000000..718d6fea Binary files /dev/null and b/test-runtimes/nextjs/public/favicon.ico differ diff --git a/test-runtimes/nextjs/public/next.svg b/test-runtimes/nextjs/public/next.svg new file mode 100644 index 00000000..5174b28c --- /dev/null +++ b/test-runtimes/nextjs/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test-runtimes/nextjs/public/vercel.svg b/test-runtimes/nextjs/public/vercel.svg new file mode 100644 index 00000000..d2f84222 --- /dev/null +++ b/test-runtimes/nextjs/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test-runtimes/nextjs/src/pages/_app.tsx b/test-runtimes/nextjs/src/pages/_app.tsx new file mode 100644 index 00000000..a7a790fb --- /dev/null +++ b/test-runtimes/nextjs/src/pages/_app.tsx @@ -0,0 +1,6 @@ +import "@/styles/globals.css"; +import type { AppProps } from "next/app"; + +export default function App({ Component, pageProps }: AppProps) { + return ; +} diff --git a/test-runtimes/nextjs/src/pages/_document.tsx b/test-runtimes/nextjs/src/pages/_document.tsx new file mode 100644 index 00000000..b2fff8b4 --- /dev/null +++ b/test-runtimes/nextjs/src/pages/_document.tsx @@ -0,0 +1,13 @@ +import { Html, Head, Main, NextScript } from "next/document"; + +export default function Document() { + return ( + + + +
+ + + + ); +} diff --git a/test-runtimes/nextjs/src/pages/api/edge.ts b/test-runtimes/nextjs/src/pages/api/edge.ts new file mode 100644 index 00000000..701a97fc --- /dev/null +++ b/test-runtimes/nextjs/src/pages/api/edge.ts @@ -0,0 +1,28 @@ +import { NextResponse } from "next/server"; + +import * as stytch from "stytch"; + +const client = new stytch.Client({ + // Find these values at https://stytch.com/dashboard/api-keys + // These ones will trigger a well-known erorr message + project_id: "project-live-c60c0abe-c25a-4472-a9ed-320c6667d317", + secret: "secret-live-80JASucyk7z_G8Z-7dVwZVGXL5NT_qGAQ2I=", +}); + +export const config = { + runtime: "edge", +}; + +export default async function GET() { + const authenticateResponse = await client.sessions + .authenticate({ + session_token: "WJtR5BCy38Szd5AfoDpf0iqFKEt4EE5JhjlWUY7l3FtY", + }) + .catch((err) => err); + + return new NextResponse(JSON.stringify(authenticateResponse), { + headers: { + "Content-Type": "application/json", + }, + }); +} diff --git a/test-runtimes/nextjs/src/pages/api/hello.ts b/test-runtimes/nextjs/src/pages/api/hello.ts new file mode 100644 index 00000000..2acdc4b4 --- /dev/null +++ b/test-runtimes/nextjs/src/pages/api/hello.ts @@ -0,0 +1,23 @@ +// Next.js API route support: https://nextjs.org/docs/api-routes/introduction +import type { NextApiRequest, NextApiResponse } from "next"; + +import * as stytch from "stytch"; + +const client = new stytch.Client({ + // Find these values at https://stytch.com/dashboard/api-keys + // These ones will trigger a well-known erorr message + project_id: "project-live-c60c0abe-c25a-4472-a9ed-320c6667d317", + secret: "secret-live-80JASucyk7z_G8Z-7dVwZVGXL5NT_qGAQ2I=", +}); + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const authenticateResponse = await client.sessions + .authenticate({ + session_token: "WJtR5BCy38Szd5AfoDpf0iqFKEt4EE5JhjlWUY7l3FtY", + }) + .catch((err) => err); + res.status(200).json(authenticateResponse); +} diff --git a/test-runtimes/nextjs/src/pages/index.tsx b/test-runtimes/nextjs/src/pages/index.tsx new file mode 100644 index 00000000..ab1c4dd2 --- /dev/null +++ b/test-runtimes/nextjs/src/pages/index.tsx @@ -0,0 +1,114 @@ +import Head from "next/head"; +import Image from "next/image"; +import { Inter } from "next/font/google"; +import styles from "@/styles/Home.module.css"; + +const inter = Inter({ subsets: ["latin"] }); + +export default function Home() { + return ( + <> + + Create Next App + + + + +
+
+

+ Get started by editing  + src/pages/index.tsx +

+ +
+ +
+ Next.js Logo +
+ + +
+ + ); +} diff --git a/test-runtimes/nextjs/src/styles/Home.module.css b/test-runtimes/nextjs/src/styles/Home.module.css new file mode 100644 index 00000000..eee920e6 --- /dev/null +++ b/test-runtimes/nextjs/src/styles/Home.module.css @@ -0,0 +1,229 @@ +.main { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + padding: 6rem; + min-height: 100vh; +} + +.description { + display: inherit; + justify-content: inherit; + align-items: inherit; + font-size: 0.85rem; + max-width: var(--max-width); + width: 100%; + z-index: 2; + font-family: var(--font-mono); +} + +.description a { + display: flex; + justify-content: center; + align-items: center; + gap: 0.5rem; +} + +.description p { + position: relative; + margin: 0; + padding: 1rem; + background-color: rgba(var(--callout-rgb), 0.5); + border: 1px solid rgba(var(--callout-border-rgb), 0.3); + border-radius: var(--border-radius); +} + +.code { + font-weight: 700; + font-family: var(--font-mono); +} + +.grid { + display: grid; + grid-template-columns: repeat(4, minmax(25%, auto)); + max-width: 100%; + width: var(--max-width); +} + +.card { + padding: 1rem 1.2rem; + border-radius: var(--border-radius); + background: rgba(var(--card-rgb), 0); + border: 1px solid rgba(var(--card-border-rgb), 0); + transition: background 200ms, border 200ms; +} + +.card span { + display: inline-block; + transition: transform 200ms; +} + +.card h2 { + font-weight: 600; + margin-bottom: 0.7rem; +} + +.card p { + margin: 0; + opacity: 0.6; + font-size: 0.9rem; + line-height: 1.5; + max-width: 30ch; +} + +.center { + display: flex; + justify-content: center; + align-items: center; + position: relative; + padding: 4rem 0; +} + +.center::before { + background: var(--secondary-glow); + border-radius: 50%; + width: 480px; + height: 360px; + margin-left: -400px; +} + +.center::after { + background: var(--primary-glow); + width: 240px; + height: 180px; + z-index: -1; +} + +.center::before, +.center::after { + content: ""; + left: 50%; + position: absolute; + filter: blur(45px); + transform: translateZ(0); +} + +.logo { + position: relative; +} +/* Enable hover only on non-touch devices */ +@media (hover: hover) and (pointer: fine) { + .card:hover { + background: rgba(var(--card-rgb), 0.1); + border: 1px solid rgba(var(--card-border-rgb), 0.15); + } + + .card:hover span { + transform: translateX(4px); + } +} + +@media (prefers-reduced-motion) { + .card:hover span { + transform: none; + } +} + +/* Mobile */ +@media (max-width: 700px) { + .content { + padding: 4rem; + } + + .grid { + grid-template-columns: 1fr; + margin-bottom: 120px; + max-width: 320px; + text-align: center; + } + + .card { + padding: 1rem 2.5rem; + } + + .card h2 { + margin-bottom: 0.5rem; + } + + .center { + padding: 8rem 0 6rem; + } + + .center::before { + transform: none; + height: 300px; + } + + .description { + font-size: 0.8rem; + } + + .description a { + padding: 1rem; + } + + .description p, + .description div { + display: flex; + justify-content: center; + position: fixed; + width: 100%; + } + + .description p { + align-items: center; + inset: 0 0 auto; + padding: 2rem 1rem 1.4rem; + border-radius: 0; + border: none; + border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25); + background: linear-gradient( + to bottom, + rgba(var(--background-start-rgb), 1), + rgba(var(--callout-rgb), 0.5) + ); + background-clip: padding-box; + backdrop-filter: blur(24px); + } + + .description div { + align-items: flex-end; + pointer-events: none; + inset: auto 0 0; + padding: 2rem; + height: 200px; + background: linear-gradient( + to bottom, + transparent 0%, + rgb(var(--background-end-rgb)) 40% + ); + z-index: 1; + } +} + +/* Tablet and Smaller Desktop */ +@media (min-width: 701px) and (max-width: 1120px) { + .grid { + grid-template-columns: repeat(2, 50%); + } +} + +@media (prefers-color-scheme: dark) { + .vercelLogo { + filter: invert(1); + } + + .logo { + filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70); + } +} + +@keyframes rotate { + from { + transform: rotate(360deg); + } + to { + transform: rotate(0deg); + } +} diff --git a/test-runtimes/nextjs/src/styles/globals.css b/test-runtimes/nextjs/src/styles/globals.css new file mode 100644 index 00000000..f4bd77c0 --- /dev/null +++ b/test-runtimes/nextjs/src/styles/globals.css @@ -0,0 +1,107 @@ +:root { + --max-width: 1100px; + --border-radius: 12px; + --font-mono: ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono", + "Roboto Mono", "Oxygen Mono", "Ubuntu Monospace", "Source Code Pro", + "Fira Mono", "Droid Sans Mono", "Courier New", monospace; + + --foreground-rgb: 0, 0, 0; + --background-start-rgb: 214, 219, 220; + --background-end-rgb: 255, 255, 255; + + --primary-glow: conic-gradient( + from 180deg at 50% 50%, + #16abff33 0deg, + #0885ff33 55deg, + #54d6ff33 120deg, + #0071ff33 160deg, + transparent 360deg + ); + --secondary-glow: radial-gradient( + rgba(255, 255, 255, 1), + rgba(255, 255, 255, 0) + ); + + --tile-start-rgb: 239, 245, 249; + --tile-end-rgb: 228, 232, 233; + --tile-border: conic-gradient( + #00000080, + #00000040, + #00000030, + #00000020, + #00000010, + #00000010, + #00000080 + ); + + --callout-rgb: 238, 240, 241; + --callout-border-rgb: 172, 175, 176; + --card-rgb: 180, 185, 188; + --card-border-rgb: 131, 134, 135; +} + +@media (prefers-color-scheme: dark) { + :root { + --foreground-rgb: 255, 255, 255; + --background-start-rgb: 0, 0, 0; + --background-end-rgb: 0, 0, 0; + + --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0)); + --secondary-glow: linear-gradient( + to bottom right, + rgba(1, 65, 255, 0), + rgba(1, 65, 255, 0), + rgba(1, 65, 255, 0.3) + ); + + --tile-start-rgb: 2, 13, 46; + --tile-end-rgb: 2, 5, 19; + --tile-border: conic-gradient( + #ffffff80, + #ffffff40, + #ffffff30, + #ffffff20, + #ffffff10, + #ffffff10, + #ffffff80 + ); + + --callout-rgb: 20, 20, 20; + --callout-border-rgb: 108, 108, 108; + --card-rgb: 100, 100, 100; + --card-border-rgb: 200, 200, 200; + } +} + +* { + box-sizing: border-box; + padding: 0; + margin: 0; +} + +html, +body { + max-width: 100vw; + overflow-x: hidden; +} + +body { + color: rgb(var(--foreground-rgb)); + background: linear-gradient( + to bottom, + transparent, + rgb(var(--background-end-rgb)) + ) + rgb(var(--background-start-rgb)); +} + +a { + color: inherit; + text-decoration: none; +} + +@media (prefers-color-scheme: dark) { + html { + color-scheme: dark; + } +} diff --git a/test-runtimes/nextjs/tsconfig.json b/test-runtimes/nextjs/tsconfig.json new file mode 100644 index 00000000..df82954a --- /dev/null +++ b/test-runtimes/nextjs/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +} diff --git a/test-runtimes/remix/.eslintrc.cjs b/test-runtimes/remix/.eslintrc.cjs new file mode 100644 index 00000000..2061cd22 --- /dev/null +++ b/test-runtimes/remix/.eslintrc.cjs @@ -0,0 +1,4 @@ +/** @type {import('eslint').Linter.Config} */ +module.exports = { + extends: ["@remix-run/eslint-config", "@remix-run/eslint-config/node"], +}; diff --git a/test-runtimes/remix/.gitignore b/test-runtimes/remix/.gitignore new file mode 100644 index 00000000..ad93f065 --- /dev/null +++ b/test-runtimes/remix/.gitignore @@ -0,0 +1,7 @@ +node_modules + +/.cache +/build +/public/build +.env +/.vercel diff --git a/test-runtimes/remix/.nvmrc b/test-runtimes/remix/.nvmrc new file mode 100644 index 00000000..7950a445 --- /dev/null +++ b/test-runtimes/remix/.nvmrc @@ -0,0 +1 @@ +v18.17.0 diff --git a/test-runtimes/remix/README.md b/test-runtimes/remix/README.md new file mode 100644 index 00000000..da8d02ad --- /dev/null +++ b/test-runtimes/remix/README.md @@ -0,0 +1,38 @@ +# Welcome to Remix! + +- [Remix Docs](https://remix.run/docs) + +## Development + +From your terminal: + +```sh +npm run dev +``` + +This starts your app in development mode, rebuilding assets on file changes. + +## Deployment + +First, build your app for production: + +```sh +npm run build +``` + +Then run the app in production mode: + +```sh +npm start +``` + +Now you'll need to pick a host to deploy it to. + +### DIY + +If you're familiar with deploying node applications, the built-in Remix app server is production-ready. + +Make sure to deploy the output of `remix build` + +- `build/` +- `public/build/` diff --git a/test-runtimes/remix/app/entry.client.tsx b/test-runtimes/remix/app/entry.client.tsx new file mode 100644 index 00000000..94d5dc0d --- /dev/null +++ b/test-runtimes/remix/app/entry.client.tsx @@ -0,0 +1,18 @@ +/** + * By default, Remix will handle hydrating your app on the client for you. + * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨ + * For more information, see https://remix.run/file-conventions/entry.client + */ + +import { RemixBrowser } from "@remix-run/react"; +import { startTransition, StrictMode } from "react"; +import { hydrateRoot } from "react-dom/client"; + +startTransition(() => { + hydrateRoot( + document, + + + + ); +}); diff --git a/test-runtimes/remix/app/entry.server.tsx b/test-runtimes/remix/app/entry.server.tsx new file mode 100644 index 00000000..0c7712b0 --- /dev/null +++ b/test-runtimes/remix/app/entry.server.tsx @@ -0,0 +1,137 @@ +/** + * By default, Remix will handle generating the HTTP Response for you. + * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨ + * For more information, see https://remix.run/file-conventions/entry.server + */ + +import { PassThrough } from "node:stream"; + +import type { AppLoadContext, EntryContext } from "@remix-run/node"; +import { createReadableStreamFromReadable } from "@remix-run/node"; +import { RemixServer } from "@remix-run/react"; +import isbot from "isbot"; +import { renderToPipeableStream } from "react-dom/server"; + +const ABORT_DELAY = 5_000; + +export default function handleRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext, + loadContext: AppLoadContext +) { + return isbot(request.headers.get("user-agent")) + ? handleBotRequest( + request, + responseStatusCode, + responseHeaders, + remixContext + ) + : handleBrowserRequest( + request, + responseStatusCode, + responseHeaders, + remixContext + ); +} + +function handleBotRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext +) { + return new Promise((resolve, reject) => { + let shellRendered = false; + const { pipe, abort } = renderToPipeableStream( + , + { + onAllReady() { + shellRendered = true; + const body = new PassThrough(); + const stream = createReadableStreamFromReadable(body); + + responseHeaders.set("Content-Type", "text/html"); + + resolve( + new Response(stream, { + headers: responseHeaders, + status: responseStatusCode, + }) + ); + + pipe(body); + }, + onShellError(error: unknown) { + reject(error); + }, + onError(error: unknown) { + responseStatusCode = 500; + // Log streaming rendering errors from inside the shell. Don't log + // errors encountered during initial shell rendering since they'll + // reject and get logged in handleDocumentRequest. + if (shellRendered) { + console.error(error); + } + }, + } + ); + + setTimeout(abort, ABORT_DELAY); + }); +} + +function handleBrowserRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext +) { + return new Promise((resolve, reject) => { + let shellRendered = false; + const { pipe, abort } = renderToPipeableStream( + , + { + onShellReady() { + shellRendered = true; + const body = new PassThrough(); + const stream = createReadableStreamFromReadable(body); + + responseHeaders.set("Content-Type", "text/html"); + + resolve( + new Response(stream, { + headers: responseHeaders, + status: responseStatusCode, + }) + ); + + pipe(body); + }, + onShellError(error: unknown) { + reject(error); + }, + onError(error: unknown) { + responseStatusCode = 500; + // Log streaming rendering errors from inside the shell. Don't log + // errors encountered during initial shell rendering since they'll + // reject and get logged in handleDocumentRequest. + if (shellRendered) { + console.error(error); + } + }, + } + ); + + setTimeout(abort, ABORT_DELAY); + }); +} diff --git a/test-runtimes/remix/app/root.tsx b/test-runtimes/remix/app/root.tsx new file mode 100644 index 00000000..b46b8fb1 --- /dev/null +++ b/test-runtimes/remix/app/root.tsx @@ -0,0 +1,33 @@ +import { cssBundleHref } from "@remix-run/css-bundle"; +import type { LinksFunction } from "@remix-run/node"; +import { + Links, + LiveReload, + Meta, + Outlet, + Scripts, + ScrollRestoration, +} from "@remix-run/react"; + +export const links: LinksFunction = () => [ + ...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []), +]; + +export default function App() { + return ( + + + + + + + + + + + + + + + ); +} diff --git a/test-runtimes/remix/app/routes/_index.tsx b/test-runtimes/remix/app/routes/_index.tsx new file mode 100644 index 00000000..9085ec7d --- /dev/null +++ b/test-runtimes/remix/app/routes/_index.tsx @@ -0,0 +1,64 @@ +import type { MetaFunction } from "@remix-run/node"; +import { useLoaderData } from "@remix-run/react"; +import * as stytch from "stytch"; +import { json } from "@remix-run/node"; + +export const meta: MetaFunction = () => { + return [ + { title: "New Remix App" }, + { name: "description", content: "Welcome to Remix!" }, + ]; +}; + +export async function loader() { + const client = new stytch.Client({ + // Find these values at https://stytch.com/dashboard/api-keys + // These ones will trigger a well-known erorr message + project_id: "project-live-c60c0abe-c25a-4472-a9ed-320c6667d317", + secret: "secret-live-80JASucyk7z_G8Z-7dVwZVGXL5NT_qGAQ2I=", + }); + + const sessionsAuthenticateResponse = await client.sessions + .authenticate({ + session_token: "WJtR5BCy38Szd5AfoDpf0iqFKEt4EE5JhjlWUY7l3FtY", + }) + .catch((err) => err); + + return json(sessionsAuthenticateResponse); +} + +export default function _index() { + const data = useLoaderData(); + + return ( +
+

Welcome to Remix

+ {JSON.stringify(data)} + +
+ ); +} diff --git a/test-runtimes/remix/package.json b/test-runtimes/remix/package.json new file mode 100644 index 00000000..aed324b4 --- /dev/null +++ b/test-runtimes/remix/package.json @@ -0,0 +1,34 @@ +{ + "name": "remix", + "private": true, + "sideEffects": false, + "type": "module", + "scripts": { + "build": "remix build", + "dev": "remix dev --manual", + "start": "remix-serve ./build/index.js", + "typecheck": "tsc" + }, + "dependencies": { + "@remix-run/css-bundle": "^2.0.1", + "@remix-run/node": "^2.0.1", + "@remix-run/react": "^2.0.1", + "@remix-run/serve": "^2.0.1", + "@vercel/remix": "^2.0.1", + "isbot": "^3.6.8", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "stytch": "^9.0.0-rc.1" + }, + "devDependencies": { + "@remix-run/dev": "^2.0.1", + "@remix-run/eslint-config": "^2.0.1", + "@types/react": "^18.2.20", + "@types/react-dom": "^18.2.7", + "eslint": "^8.38.0", + "typescript": "^5.1.6" + }, + "engines": { + "node": ">=18.0.0" + } +} diff --git a/test-runtimes/remix/public/favicon.ico b/test-runtimes/remix/public/favicon.ico new file mode 100644 index 00000000..8830cf68 Binary files /dev/null and b/test-runtimes/remix/public/favicon.ico differ diff --git a/test-runtimes/remix/remix.config.js b/test-runtimes/remix/remix.config.js new file mode 100644 index 00000000..c17c45f3 --- /dev/null +++ b/test-runtimes/remix/remix.config.js @@ -0,0 +1,8 @@ +/** @type {import('@remix-run/dev').AppConfig} */ +export default { + // ignoredRouteFiles: ["**/.*"], + // appDirectory: "app", + // assetsBuildDirectory: "public/build", + // publicPath: "/build/", + // serverBuildPath: "build/index.js", +}; diff --git a/test-runtimes/remix/remix.env.d.ts b/test-runtimes/remix/remix.env.d.ts new file mode 100644 index 00000000..dcf8c45e --- /dev/null +++ b/test-runtimes/remix/remix.env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/test-runtimes/remix/tsconfig.json b/test-runtimes/remix/tsconfig.json new file mode 100644 index 00000000..28cce918 --- /dev/null +++ b/test-runtimes/remix/tsconfig.json @@ -0,0 +1,22 @@ +{ + "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"], + "compilerOptions": { + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "isolatedModules": true, + "esModuleInterop": true, + "jsx": "react-jsx", + "moduleResolution": "Bundler", + "resolveJsonModule": true, + "target": "ES2022", + "strict": true, + "allowJs": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": ".", + "paths": { + "~/*": ["./app/*"] + }, + + // Remix takes care of building everything in `remix build`. + "noEmit": true + } +}