Skip to content

Commit

Permalink
Node and auto adapters (#1218)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlecAivazis committed Oct 14, 2023
1 parent 95a0707 commit f824df7
Show file tree
Hide file tree
Showing 21 changed files with 334 additions and 143 deletions.
5 changes: 5 additions & 0 deletions .changeset/sweet-waves-tell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'houdini-adapter-auto': patch
---

Add smart adapter
5 changes: 5 additions & 0 deletions .changeset/ten-tips-mix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'houdini-adapter-node': patch
---

Add adapter for node
58 changes: 1 addition & 57 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -142,62 +142,6 @@ jobs:

- name: Run init
run: cd project && node ../houdini/packages/houdini/build/cmd-esm/index.js init -y

verify_create:
name: Verify Create
runs-on: ubuntu-latest
strategy:
matrix:
template: [react, react-typescript, sveltekit-demo]
steps:
- name: Checkout source
uses: actions/checkout@v3
with:
ref: ${{ github.ref }}

- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 18.17.1

- uses: pnpm/[email protected]
name: Install pnpm
id: pnpm-install
with:
version: 8

- name: Get pnpm store directory
id: pnpm-cache
run: |
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: cd packages/create-houdini && pnpm install

- name: Create template (local)
if: matrix.template != 'sveltekit-demo'
run: cd packages/create-houdini && pnpm dev test-${{ matrix.template }} -t ${{ matrix.template }} -s local

- name: Create template (not local)
if: matrix.template == 'sveltekit-demo'
run: cd packages/create-houdini && pnpm dev test-${{ matrix.template }} -t ${{ matrix.template }}

- run: cd packages/create-houdini/test-${{ matrix.template }} && npx playwright install

- name: e2e install
run: cd packages/create-houdini/test-${{ matrix.template }} && npm i

- name: e2e tests
if: matrix.template == 'sveltekit-demo'
run: cd packages/create-houdini/test-${{ matrix.template }} && npm run test:integration

e2e_tests:
name: End-to-End Tests
Expand Down Expand Up @@ -314,4 +258,4 @@ jobs:
run: pnpm --filter e2e-kit run lint

- name: End-to-End check
run: pnpm --filter e2e-kit run check
run: pnpm --filter e2e-kit run check
1 change: 1 addition & 0 deletions e2e/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"cross-env": "^7.0.3",
"e2e-api": "workspace:^",
"hono": "^3.6.0",
"houdini-adapter-node": "workspace:^",
"typescript": "^4.9.3",
"vite": "^4.1.0",
"wrangler": "^3.7.0"
Expand Down
55 changes: 55 additions & 0 deletions packages/adapter-auto/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"name": "houdini-adapter-auto",
"version": "1.2.27",
"description": "An adapter for deploying your Houdini application according to the build environment ",
"keywords": [
"houdini",
"adpter",
"cloudflare",
"workers",
"node"
],
"homepage": "https://github.com/HoudiniGraphql/houdini",
"funding": "https://github.com/sponsors/HoudiniGraphql",
"repository": {
"type": "git",
"url": "https://github.com/HoudiniGraphql/houdini.git"
},
"license": "MIT",
"type": "module",
"scripts": {
"build": "tsup src/* --format esm,cjs --external vite --minify --dts --clean --out-dir build",
"build:": "cd ../../ && ((run build && cd -) || (cd - && exit 1))",
"build:build": "pnpm build: && pnpm build"
},
"devDependencies": {
"scripts": "workspace:^",
"tsup": "^7.2.0"
},
"dependencies": {
"houdini": "workspace:^",
"import-meta-resolve": "^3.0.0"
},
"files": [
"build"
],
"exports": {
"./package.json": "./package.json",
".": {
"import": "./build/index.js",
"require": "./build/index.cjs"
},
"./app": {
"import": "./build/app.js",
"require": "./build/app.cjs"
}
},
"types": "./build/index.d.ts",
"typesVersions": {
"*": {
"app": [
"build/app.d.ts"
]
}
}
}
106 changes: 106 additions & 0 deletions packages/adapter-auto/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { type Adapter, detectTools } from 'houdini'
import { resolve } from 'import-meta-resolve'
import { execSync } from 'node:child_process'
import { pathToFileURL } from 'node:url'

const adapters = [
{
name: 'CloudFlare Pages',
test: () => Boolean(process.env.CF_PAGES),
module: 'houdini-adapter-cloudflare',
},
{
name: 'HoudiniCloud',
test: () => Boolean(process.env.HOUDINI_CLOUD),
module: 'houdini-cloud-adapter',
},
// putting this at the bottom makes it will be the default
{
name: 'Node',
test: () => true,
module: 'houdini-adapter-node',
},
]

const adapter: Adapter = async (ctx) => {
// find the matching adapter
let match: (typeof adapters)[number] | undefined
for (const adapter of adapters) {
if (adapter.test()) {
match = adapter
break
}
}

// make typescript happy even tho we have a default
if (!match) throw new Error('Could not identify environment')

// tell the user what we found
console.log(`🎩 Identified environment: ${match.name}`)

// load the adapter
const nextAdapter = await loadAdapter(match)

// run the adapter
return nextAdapter(ctx)
}

async function loadAdapter({ module }: { module: string }): Promise<Adapter> {
// if we have the required module loaded, we're good
try {
return (await importFromCwd(module)) as Adapter
} catch (err) {
// if the error indicates we were missing the module, let's keep going
const error = err as Error & { code: string }
if (
error.code !== 'ERR_MODULE_NOT_FOUND' ||
!error.message.startsWith(`Cannot find package '${module}'`)
) {
throw err
}
}

// if we didn't have the module loaded we can try installing it with the users package manager
const { package_manager } = await detectTools()

// the command to run to install the adapter
const installCmds = {
yarn: 'add -D',
npm: 'install -D',
pnpm: 'add -D',
}

// something might go wrong during installation
try {
// install the pacakge we need to
execSync(`${package_manager} ${installCmds[package_manager]} ${module}`, {
stdio: 'inherit',
env: {
...process.env,
NODE_ENV: undefined,
},
})

console.log(`Successfully installed ${module}!`)
console.warn(
`If you plan on staying in this environment, consider adding ${module} to your project so you don't have to install it every time you build your application.`
)

// we should be able to import it now
return (await importFromCwd(module)) as Adapter
} catch (err) {
throw new Error(
`Could not install package ${module}. Please install it manually or maybe consider replacing houdini-adapter-auto with ${module}.` +
`\n${(err as Error).message}`
)
}
}

async function importFromCwd(name: string) {
const cwd = pathToFileURL(process.cwd()).href
const url = resolve(name, cwd + '/x.js')

return (await import(url)).default
}

export default adapter
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,18 @@
},
"license": "MIT",
"type": "module",
"devDependencies": {
"@cloudflare/workers-types": "^4.20230904.0",
"@types/cookie": "^0.5.2",
"scripts": "workspace:^",
"tsup": "^7.2.0",
"vitest": "^0.28.3"
},
"scripts": {
"build": "tsup src/index.ts src/worker.ts --format esm,cjs --external vite --external ../\\$houdini --external ../src --external graphql --minify --dts --clean --out-dir build",
"build": "tsup src/index.ts src/worker.ts --format esm,cjs --minify --dts --clean --out-dir build",
"build:": "cd ../../ && ((run build && cd -) || (cd - && exit 1))",
"build:build": "pnpm build: && pnpm build"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20230904.0",
"scripts": "workspace:^",
"tsup": "^7.2.0"
},
"dependencies": {
"@cloudflare/kv-asset-handler": "^0.3.0",
"@types/react": "^18.2.21",
"@types/react-dom": "^18.0.10",
"cookie": "^0.5.0",
"graphql-yoga": "^4.0.4",
"houdini": "workspace:^",
"itty-router": "^4.0.23",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"houdini": "workspace:^"
},
"files": [
"build"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { type Adapter, fs, path } from 'houdini'
import { fileURLToPath } from 'node:url'

const adapter: Adapter = async ({ adapterPath, outDir, sourceDir }) => {
// the first thing we have to do is copy the source directory over
await fs.recursiveCopy(sourceDir, path.join(outDir, 'assets'))

// the only thing we need to do for the cloudflare adapter is to copy the worker file
// to the dist directory
const adapter: Adapter = async ({ adapterPath, outDir }) => {
// read the contents of the worker file
let workerContents = (await fs.readFile(sourcePath('./worker.js')))!
let workerContents = (await fs.readFile(
fileURLToPath(new URL('./worker.js', import.meta.url).href)
))!

// make sure that the adapter module imports from the correct path
workerContents = workerContents.replaceAll('houdini/adapter', adapterPath)
Expand All @@ -15,7 +16,3 @@ const adapter: Adapter = async ({ adapterPath, outDir, sourceDir }) => {
}

export default adapter

function sourcePath(path: string) {
return fileURLToPath(new URL(path, import.meta.url).href)
}
File renamed without changes.
48 changes: 48 additions & 0 deletions packages/adapter-node/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"name": "houdini-adapter-node",
"version": "1.2.27",
"description": "The adapter for deploying your Houdini application as a standalone node server",
"keywords": [
"houdini",
"adpter",
"node"
],
"homepage": "https://github.com/HoudiniGraphql/houdini",
"funding": "https://github.com/sponsors/HoudiniGraphql",
"repository": {
"type": "git",
"url": "https://github.com/HoudiniGraphql/houdini.git"
},
"license": "MIT",
"type": "module",
"scripts": {
"build": "tsup src/index.ts src/app.ts --format esm,cjs --minify --dts --clean --out-dir build",
"build:": "cd ../../ && ((run build && cd -) || (cd - && exit 1))",
"build:build": "pnpm build: && pnpm build"
},
"devDependencies": {
"scripts": "workspace:^",
"tsup": "^7.2.0"
},
"dependencies": {
"houdini": "workspace:^"
},
"files": [
"build"
],
"exports": {
"./package.json": "./package.json",
".": {
"import": "./build/index.js",
"require": "./build/index.cjs"
}
},
"types": "./build/index.d.ts",
"typesVersions": {
"*": {
"app": [
"build/app.d.ts"
]
}
}
}
14 changes: 14 additions & 0 deletions packages/adapter-node/src/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createServerAdapter } from 'houdini/adapter'
import { createServer } from 'node:http'

// create the production server adapter
const serverAdapter = createServerAdapter({
production: true,
assetPrefix: '/assets',
})

// wrap the server adapter in a node http server
const nodeServer = createServer(serverAdapter)

// start listening on the designated port
nodeServer.listen(process.env.PORT ?? 3000)
16 changes: 16 additions & 0 deletions packages/adapter-node/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { type Adapter, fs, path } from 'houdini'
import { fileURLToPath } from 'node:url'

const adapter: Adapter = async ({ outDir, adapterPath }) => {
// read the contents of the app file
let workerContents = (await fs.readFile(
fileURLToPath(new URL('./app.js', import.meta.url).href)
))!

// make sure that the adapter module imports from the correct path
workerContents = workerContents.replaceAll('houdini/adapter', adapterPath)

await fs.writeFile(path.join(outDir, 'index.js'), workerContents!)
}

export default adapter
Loading

0 comments on commit f824df7

Please sign in to comment.