-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature/issue 1007 api routes (#1017)
* API Routes * rename ApiRoutes resource class * add skeleton Request support to handler signature * Response example from API route * add local development cache busting for API routes * refactor out dependency on path * fix typo * API Routes * add local development cache busting for API routes * refactor out dependency on path * handle APIs for serve command * add API development spec * refactor API development spec to use fetch * refactor specs * API route serve specs * TODOs tracking * add API routes documentation * restore API specs using request instead of native fetch * remove demo code * app exists check for API routes
- Loading branch information
1 parent
1de4b7b
commit 201b5ae
Showing
13 changed files
with
370 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
/* | ||
* | ||
* Manages routing to API routes. | ||
* | ||
*/ | ||
import fs from 'fs'; | ||
import { ResourceInterface } from '../../lib/resource-interface.js'; | ||
|
||
class ApiRoutesResource extends ResourceInterface { | ||
constructor(compilation, options) { | ||
super(compilation, options); | ||
} | ||
|
||
async shouldServe(url) { | ||
// TODO Could this existance check be derived from the graph instead? | ||
// https://github.com/ProjectEvergreen/greenwood/issues/946 | ||
return url.startsWith('/api') && fs.existsSync(this.compilation.context.apisDir, url); | ||
} | ||
|
||
async serve(url) { | ||
// TODO we assume host here, but eventually we will be getting a Request | ||
// https://github.com/ProjectEvergreen/greenwood/issues/948 | ||
const host = `https://localhost:${this.compilation.config.port}`; | ||
let href = new URL(`${this.getBareUrlPath(url).replace('/api/', '')}.js`, `file://${this.compilation.context.apisDir}`).href; | ||
|
||
// https://github.com/nodejs/modules/issues/307#issuecomment-1165387383 | ||
if (process.env.__GWD_COMMAND__ === 'develop') { // eslint-disable-line no-underscore-dangle | ||
href = `${href}?t=${Date.now()}`; | ||
} | ||
|
||
const { handler } = await import(href); | ||
// TODO we need to pass in headers here | ||
// https://github.com/ProjectEvergreen/greenwood/issues/948 | ||
const req = new Request(new URL(`${host}${url}`)); | ||
const resp = await handler(req); | ||
const contents = resp.headers.get('content-type').indexOf('application/json') >= 0 | ||
? await resp.json() | ||
: await resp.text(); | ||
|
||
return { | ||
body: contents, | ||
resp | ||
}; | ||
} | ||
} | ||
|
||
const greenwoodApiRoutesPlugin = { | ||
type: 'resource', | ||
name: 'plugin-api-routes', | ||
provider: (compilation, options) => new ApiRoutesResource(compilation, options) | ||
}; | ||
|
||
export { greenwoodApiRoutesPlugin }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
11 changes: 11 additions & 0 deletions
11
packages/cli/test/cases/develop.default/src/api/greeting.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
export async function handler(request) { | ||
const params = new URLSearchParams(request.url.slice(request.url.indexOf('?'))); | ||
const name = params.has('name') ? params.get('name') : 'World'; | ||
const body = { message: `Hello ${name}!!!` }; | ||
|
||
return new Response(JSON.stringify(body), { | ||
headers: { | ||
'Content-Type': 'application/json' | ||
} | ||
}); | ||
} |
137 changes: 137 additions & 0 deletions
137
packages/cli/test/cases/serve.default.api/serve.default.api.spec.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
/* | ||
* Use Case | ||
* Run Greenwood serve command with no config. | ||
* | ||
* User Result | ||
* Should start the production server and render a bare bones Greenwood build. | ||
* | ||
* User Command | ||
* greenwood serve | ||
* | ||
* User Config | ||
* N / A | ||
* | ||
* User Workspace | ||
* src/ | ||
* api/ | ||
* greeting.js | ||
*/ | ||
import chai from 'chai'; | ||
import path from 'path'; | ||
import { getSetupFiles, getOutputTeardownFiles } from '../../../../../test/utils.js'; | ||
import request from 'request'; | ||
import { runSmokeTest } from '../../../../../test/smoke-test.js'; | ||
import { Runner } from 'gallinago'; | ||
import { fileURLToPath, URL } from 'url'; | ||
|
||
const expect = chai.expect; | ||
|
||
// TODO why does this test keep stalling out and not closing the command? | ||
describe('Serve Greenwood With: ', function() { | ||
const LABEL = 'API Routes'; | ||
const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js'); | ||
const outputPath = fileURLToPath(new URL('.', import.meta.url)); | ||
const hostname = 'http://127.0.0.1:8080'; | ||
let runner; | ||
|
||
before(function() { | ||
this.context = { | ||
hostname | ||
}; | ||
runner = new Runner(); | ||
}); | ||
|
||
describe(LABEL, function() { | ||
|
||
before(async function() { | ||
await runner.setup(outputPath, getSetupFiles(outputPath)); | ||
|
||
return new Promise(async (resolve) => { | ||
setTimeout(() => { | ||
resolve(); | ||
}, 10000); | ||
|
||
await runner.runCommand(cliPath, 'serve'); | ||
}); | ||
}); | ||
|
||
runSmokeTest(['serve'], LABEL); | ||
|
||
describe('Serve command with API specific behaviors for a JSON API', function() { | ||
const name = 'Greenwood'; | ||
let response = {}; | ||
|
||
before(async function() { | ||
// TODO not sure why native `fetch` doesn't seem to work here, just hangs the test runner | ||
return new Promise((resolve, reject) => { | ||
request.get(`${hostname}/api/greeting?name=${name}`, (err, res, body) => { | ||
if (err) { | ||
reject(); | ||
} | ||
|
||
response = res; | ||
response.body = JSON.parse(body); | ||
|
||
resolve(); | ||
}); | ||
}); | ||
}); | ||
|
||
it('should return a 200 status', function(done) { | ||
expect(response.statusCode).to.equal(200); | ||
done(); | ||
}); | ||
|
||
it('should return the correct content type', function(done) { | ||
expect(response.headers['content-type']).to.equal('application/json; charset=utf-8'); | ||
done(); | ||
}); | ||
|
||
it('should return the correct response body', function(done) { | ||
expect(response.body.message).to.equal(`Hello ${name}!!!`); | ||
done(); | ||
}); | ||
}); | ||
|
||
describe('Serve command with API specific behaviors for an HTML ("fragment") API', function() { | ||
const name = 'Greenwood'; | ||
let response = {}; | ||
|
||
before(async function() { | ||
// TODO not sure why native `fetch` doesn't seem to work here, just hangs the test runner | ||
return new Promise((resolve, reject) => { | ||
request.get(`${hostname}/api/fragment?name=${name}`, (err, res, body) => { | ||
if (err) { | ||
reject(); | ||
} | ||
|
||
response = res; | ||
response.body = body; | ||
|
||
resolve(); | ||
}); | ||
}); | ||
}); | ||
|
||
it('should return a 200 status', function(done) { | ||
expect(response.statusCode).to.equal(200); | ||
done(); | ||
}); | ||
|
||
it('should return the correct content type', function(done) { | ||
expect(response.headers['content-type']).to.equal('text/html'); | ||
done(); | ||
}); | ||
|
||
it('should return the correct response body', function(done) { | ||
expect(response.body).to.contain(`<h1>Hello ${name}!!!</h1>`); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
|
||
after(function() { | ||
runner.stopCommand(); | ||
runner.teardown(getOutputTeardownFiles(outputPath)); | ||
}); | ||
}); |
18 changes: 18 additions & 0 deletions
18
packages/cli/test/cases/serve.default.api/src/api/fragment.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { renderFromHTML } from 'wc-compiler'; | ||
|
||
export async function handler(request) { | ||
const headers = new Headers(); | ||
const params = new URLSearchParams(request.url.slice(request.url.indexOf('?'))); | ||
const name = params.has('name') ? params.get('name') : 'World'; | ||
const { html } = await renderFromHTML(` | ||
<x-card name="${name}"></x-card> | ||
`, [ | ||
new URL('../components/card.js', import.meta.url) | ||
]); | ||
|
||
headers.append('Content-Type', 'text/html'); | ||
|
||
return new Response(html, { | ||
headers | ||
}); | ||
} |
11 changes: 11 additions & 0 deletions
11
packages/cli/test/cases/serve.default.api/src/api/greeting.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
export async function handler(request) { | ||
const params = new URLSearchParams(request.url.slice(request.url.indexOf('?'))); | ||
const name = params.has('name') ? params.get('name') : 'World'; | ||
const body = { message: `Hello ${name}!!!` }; | ||
|
||
return new Response(JSON.stringify(body), { | ||
headers: { | ||
'Content-Type': 'application/json' | ||
} | ||
}); | ||
} |
11 changes: 11 additions & 0 deletions
11
packages/cli/test/cases/serve.default.api/src/components/card.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
export default class Card extends HTMLElement { | ||
connectedCallback() { | ||
const name = this.getAttribute('name'); | ||
|
||
this.innerHTML = ` | ||
<h1>Hello ${name}!!!</h1> | ||
`; | ||
} | ||
} | ||
|
||
customElements.define('x-card', Card); |
Oops, something went wrong.