Skip to content
This repository has been archived by the owner on Sep 5, 2023. It is now read-only.

Commit

Permalink
feat: Netlify backend type
Browse files Browse the repository at this point in the history
  • Loading branch information
mrkurt committed Jan 4, 2019
1 parent b9bdb0b commit f1891a3
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 128 deletions.
1 change: 1 addition & 0 deletions src/backends.ts
Expand Up @@ -6,4 +6,5 @@ export * from "./backends/glitch";
export * from "./backends/github_pages";
export * from "./backends/ghost_pro";
export * from "./backends/heroku";
export * from "./backends/netlify";
export * from "./backends/echo";
38 changes: 2 additions & 36 deletions src/backends/ghost_pro.ts
Expand Up @@ -3,26 +3,13 @@
*/

import { proxy, ProxyFunction } from "../proxy";
import { isObject, merge } from "../util";
import * as errors from "../errors";

/**
* Ghost Pro configugration.
*/
export interface GhostProOptions {
/** Blog's subdomain: <subdomain>.ghost.io */
subdomain: string,
/** Subdirectory blog is served from (if any) */
directory?: string,
/** Ghost Pro blogs can be configured with a custom hostname, we need that to proxy properly */
hostname?: string
}
import { SubdomainOptions, normalizeOptions } from "./subdomain_service";

/**
* Creates a `fetch` like function for proxying requests to hosted Ghost Pro blogs.
* @param options Ghost Pro blog information. Accepts subdomain as a string..
*/
export function ghostProBlog(options: GhostProOptions | string): ProxyFunction<GhostProOptions> {
export function ghostProBlog(options: SubdomainOptions | string): ProxyFunction<SubdomainOptions> {
const config = normalizeOptions(options);

const ghostHost = `${config.subdomain}.ghost.io`
Expand All @@ -36,25 +23,4 @@ export function ghostProBlog(options: GhostProOptions | string): ProxyFunction<G
return Object.assign(fn, { proxyConfig: config})
}

function normalizeOptions(input: unknown): GhostProOptions {
const options: GhostProOptions = {
subdomain: "",
directory: "/"
};

if (typeof input === "string") {
options.subdomain = input;
} else if (isObject(input)) {
merge(options, input, ["subdomain", "directory", "hostname"]);
} else {
throw errors.invalidInput("options must be a GhostProOptions object or string");
}

if (!options.subdomain) {
throw errors.invalidProperty("subdomain", "is required");
}

return options;
}

ghostProBlog.normalizeOptions = normalizeOptions;
33 changes: 7 additions & 26 deletions src/backends/glitch.ts
Expand Up @@ -3,15 +3,13 @@
*/

import { proxy, ProxyFunction } from "../proxy";
import { isObject, merge } from "../util";
import * as errors from "../errors";
import { SubdomainOptions, normalizeOptions } from "./subdomain_service";

/**
* Glitch configugration.
*/
export interface GlitchOptions {
/** Blog's subdomain: <subdomain>.glitch.com */
subdomain: string
export interface GlitchOptions extends SubdomainOptions {
hostname?: undefined
}

/**
Expand All @@ -20,6 +18,9 @@ export interface GlitchOptions {
*/
export function glitch(options: GlitchOptions | string): ProxyFunction<GlitchOptions> {
const config = normalizeOptions(options);
if(config.hostname){
delete config.hostname
}

const glitchHost = `${config.subdomain}.glitch.me`
const uri = `https://${glitchHost}`
Expand All @@ -28,27 +29,7 @@ export function glitch(options: GlitchOptions | string): ProxyFunction<GlitchOpt
}

const fn = proxy(uri, { headers: headers })
return Object.assign(fn, { proxyConfig: config})
}

function normalizeOptions(input: unknown): GlitchOptions {
const options: GlitchOptions = {
subdomain: ""
};

if (typeof input === "string") {
options.subdomain = input;
} else if (isObject(input)) {
merge(options, input, ["subdomain"]);
} else {
throw errors.invalidInput("options must be a GlitchOptions object or string");
}

if (!options.subdomain) {
throw errors.invalidProperty("subdomain", "is required");
}

return options;
return Object.assign(fn, { proxyConfig: (config as GlitchOptions)})
}

glitch.normalizeOptions = normalizeOptions;
26 changes: 26 additions & 0 deletions src/backends/netlify.ts
@@ -0,0 +1,26 @@
/**
* @module Backends
*/

import { proxy, ProxyFunction } from "../proxy";
import { normalizeOptions, SubdomainOptions } from "./subdomain_service";

/**
* Creates a `fetch` like function for proxying requests to hosted Netlify sites.
* @param options Netlify site information. Accepts subdomain as a string.
*/
export function netlify(options: SubdomainOptions | string): ProxyFunction<SubdomainOptions> {
const config = normalizeOptions(options);

const netlifyHost = `${config.subdomain}.netlify.com`
const uri = `https://${netlifyHost}${config.directory}`
const headers = {
"host": netlifyHost,
"x-forwarded-host": config.hostname || false
}

const fn = proxy(uri, { headers: headers })
return Object.assign(fn, { proxyConfig: config})
}

netlify.normalizeOptions = normalizeOptions;
31 changes: 31 additions & 0 deletions src/backends/subdomain_service.ts
@@ -0,0 +1,31 @@
import { isObject, merge } from "../util";
import * as errors from "../errors";
export interface SubdomainOptions {
/** Blog's subdomain: <subdomain>.netlify.com */
subdomain: string,
/** Subdirectory site is served from (if any) */
directory?: string,
/** Netlify sites can be configured with a custom hostname, we need that to proxy properly */
hostname?: string
}

export function normalizeOptions(input: unknown): SubdomainOptions {
const options: SubdomainOptions = {
subdomain: "",
directory: "/"
};

if (typeof input === "string") {
options.subdomain = input;
} else if (isObject(input)) {
merge(options, input, ["subdomain", "directory", "hostname"]);
} else {
throw errors.invalidInput("options must be a NetlifyProOptions object or string");
}

if (!options.subdomain) {
throw errors.invalidProperty("subdomain", "is required");
}

return options;
}
122 changes: 56 additions & 66 deletions test/backends/subdomain_services.spec.ts
@@ -1,82 +1,72 @@
import { expect } from "chai";
import { ghostProBlog, glitch } from "../../src/backends"
import { ghostProBlog, glitch, netlify } from "../../src/backends"
import * as errors from "../../src/errors";
import { normalizeOptions } from "../../src/backends/subdomain_service";

const defs = [
{ backend: ghostProBlog, subdomain: "fly-io" },
{ backend: glitch, subdomain: "fly-example", options: ["subdomain"] }
{ backend: ghostProBlog, tests: [
{ subdomain: "fly-io", hostname: 'fly.io', directory: "/articles/" },
{ subdomain: "demo" }
]},
{ backend: glitch, options: ["subdomain"], tests: [
{subdomain: "fly-example" },
]},
{ backend: netlify, options: ["subdomain", "directory"], tests: [{
subdomain: "example"}
]}
]
for(const d of defs){
const backend = d.backend
describe(`backends/${backend.name}`, function() {
this.timeout(15000)

describe("options", () => {
const validOptions = [
[
"subdomain",
{ subdomain: "subdomain", directory: "/" }
],
[
{ subdomain: "subdomain" },
{ subdomain: "subdomain", directory: "/" }
],
[
{ subdomain: "subdomain", hostname: "host.name" },
{ subdomain: "subdomain", directory: "/", hostname: "host.name" }
],
[
{ subdomain: "subdomain", directory: "/" },
{ subdomain: "subdomain", directory: "/" }
]
];

for (const [input, c] of validOptions) {
const config:any = Object.assign({}, c)
console.log("checking:", input, config)
it(`accepts ${JSON.stringify(input)}`, () => {
if(d.options){
for(const k of Object.getOwnPropertyNames(config)){
if(!d.options.includes(k)){
console.log("deleting key:", k)
delete config[k]
}
}
}
expect(backend(input as any).proxyConfig).to.eql(config);
})
}

const invalidOptions = [
[undefined, errors.InputError],
["", /subdomain is required/],
[{}, /subdomain is required/],
[{ subdomain: "" }, /subdomain is required/],
];

for (const [input, err] of invalidOptions) {
it(`rejects ${JSON.stringify(input)}`, () => {
expect(() => { ghostProBlog(input as any) }).throw(err as any);
})
}
})
for(const t of d.tests){
it(`works with settings: ${JSON.stringify(t)}`, async () => {
const fn = backend(t as any);

const resp = await fn("https://backend/", { method: "HEAD"})
expect(resp.status).to.eq(200)
})
}
})
}

it('works with just a subdomain', async () => {
const fn = backend({ subdomain: "demo" });
describe("backends/subdomainService", () => {
const validOptions = [
[
"subdomain",
{ subdomain: "subdomain", directory: "/" }
],
[
{ subdomain: "subdomain" },
{ subdomain: "subdomain", directory: "/" }
],
[
{ subdomain: "subdomain", hostname: "host.name" },
{ subdomain: "subdomain", directory: "/", hostname: "host.name" }
],
[
{ subdomain: "subdomain", directory: "/" },
{ subdomain: "subdomain", directory: "/" }
]
];

const resp = await fn("https://backend/", { method: "HEAD"})
expect(resp.status).to.eq(200)
for (const [input, config] of validOptions) {
it(`normalizes ${JSON.stringify(input)}`, () => {
expect(normalizeOptions(input)).to.eql(config);
})
}

it('works with a custom domain and directory', async () =>{
const fn = backend(<any>{
subdomain: d.subdomain,
hostname: "fly.io",
directory: "/articles/"
})
const invalidOptions = [
[undefined, errors.InputError],
["", /subdomain is required/],
[{}, /subdomain is required/],
[{ subdomain: "" }, /subdomain is required/],
];

const resp = await fn("https://backend/", { method: "HEAD"})
expect(resp.status).to.eq(200)
for (const [input, err] of invalidOptions) {
it(`rejects ${JSON.stringify(input)}`, () => {
expect(() => { normalizeOptions(input) }).throw(err as any);
})
})
}
}
})

0 comments on commit f1891a3

Please sign in to comment.