feat: add support for Azure static web apps (#92)

This commit is contained in:
Daniel Roe 2021-02-01 09:24:49 +00:00 committed by GitHub
parent 97efab4270
commit 31a9bc2d18
6 changed files with 177 additions and 37 deletions

View File

@ -1,14 +1,16 @@
import archiver from 'archiver'
import consola from 'consola'
import { createWriteStream } from 'fs-extra'
import fse from 'fs-extra'
import globby from 'globby'
import { join, resolve } from 'upath'
import { prettyPath, writeFile } from '../utils'
import { writeFile } from '../utils'
import { NitroPreset, NitroContext } from '../context'
export const azure: NitroPreset = {
inlineChunks: false,
serveStatic: true,
entry: '{{ _internal.runtimeDir }}/entries/azure',
output: {
serverDir: '{{ output.dir }}/server/functions'
},
hooks: {
async 'nitro:compiled' (ctx: NitroContext) {
await writeRoutes(ctx)
@ -16,27 +18,64 @@ export const azure: NitroPreset = {
}
}
function zipDirectory (dir: string, outfile: string): Promise<undefined> {
const archive = archiver('zip', { zlib: { level: 9 } })
const stream = createWriteStream(outfile)
return new Promise((resolve, reject) => {
archive
.directory(dir, false)
.on('error', (err: Error) => reject(err))
.pipe(stream)
stream.on('close', () => resolve(undefined))
archive.finalize()
})
}
async function writeRoutes ({ output: { dir, serverDir } }: NitroContext) {
async function writeRoutes ({ output: { serverDir, publicDir } }: NitroContext) {
const host = {
version: '2.0',
extensions: { http: { routePrefix: '' } }
version: '2.0'
}
const routes = [
{
route: '/*',
serve: '/api/server'
}
]
const indexPath = resolve(publicDir, 'index.html')
const indexFileExists = fse.existsSync(indexPath)
if (!indexFileExists) {
routes.unshift(
{
route: '/',
serve: '/api/server'
},
{
route: '/index.html',
serve: '/api/server'
}
)
}
const folderFiles = await globby([
join(publicDir, 'index.html'),
join(publicDir, '**/index.html')
])
const prefix = publicDir.length
const suffix = '/index.html'.length
folderFiles.forEach(file =>
routes.unshift({
route: file.slice(prefix, -suffix) || '/',
serve: file.slice(prefix)
})
)
const otherFiles = await globby([join(publicDir, '**/*.html'), join(publicDir, '*.html')])
otherFiles.forEach((file) => {
if (file.endsWith('index.html')) {
return
}
const route = file.slice(prefix, -5)
const existingRouteIndex = routes.findIndex(_route => _route.route === route)
if (existingRouteIndex > -1) {
routes.splice(existingRouteIndex, 1)
}
routes.unshift(
{
route,
serve: file.slice(prefix)
}
)
})
const functionDefinition = {
entryPoint: 'handle',
bindings: [
@ -46,15 +85,7 @@ async function writeRoutes ({ output: { dir, serverDir } }: NitroContext) {
direction: 'in',
name: 'req',
route: '{*url}',
methods: [
'delete',
'get',
'head',
'options',
'patch',
'post',
'put'
]
methods: ['delete', 'get', 'head', 'options', 'patch', 'post', 'put']
},
{
type: 'http',
@ -65,10 +96,11 @@ async function writeRoutes ({ output: { dir, serverDir } }: NitroContext) {
}
await writeFile(resolve(serverDir, 'function.json'), JSON.stringify(functionDefinition))
await writeFile(resolve(dir, 'host.json'), JSON.stringify(host))
await zipDirectory(dir, join(dir, 'deploy.zip'))
const zipPath = prettyPath(resolve(dir, 'deploy.zip'))
consola.success(`Ready to run \`az functionapp deployment source config-zip -g <resource-group> -n <app-name> --src ${zipPath}\``)
await writeFile(resolve(serverDir, '../host.json'), JSON.stringify(host))
await writeFile(resolve(publicDir, 'routes.json'), JSON.stringify({ routes }))
if (!indexFileExists) {
await writeFile(indexPath, '')
}
consola.success('Ready to deploy.')
}

View File

@ -0,0 +1,75 @@
import archiver from 'archiver'
import consola from 'consola'
import { createWriteStream } from 'fs-extra'
import { join, resolve } from 'upath'
import { prettyPath, writeFile } from '../utils'
import { NitroPreset, NitroContext } from '../context'
// eslint-disable-next-line
export const azure_functions: NitroPreset = {
inlineChunks: false,
serveStatic: true,
entry: '{{ _internal.runtimeDir }}/entries/azure_functions',
hooks: {
async 'nitro:compiled' (ctx: NitroContext) {
await writeRoutes(ctx)
}
}
}
function zipDirectory (dir: string, outfile: string): Promise<undefined> {
const archive = archiver('zip', { zlib: { level: 9 } })
const stream = createWriteStream(outfile)
return new Promise((resolve, reject) => {
archive
.directory(dir, false)
.on('error', (err: Error) => reject(err))
.pipe(stream)
stream.on('close', () => resolve(undefined))
archive.finalize()
})
}
async function writeRoutes ({ output: { dir, serverDir } }: NitroContext) {
const host = {
version: '2.0',
extensions: { http: { routePrefix: '' } }
}
const functionDefinition = {
entryPoint: 'handle',
bindings: [
{
authLevel: 'anonymous',
type: 'httpTrigger',
direction: 'in',
name: 'req',
route: '{*url}',
methods: [
'delete',
'get',
'head',
'options',
'patch',
'post',
'put'
]
},
{
type: 'http',
direction: 'out',
name: 'res'
}
]
}
await writeFile(resolve(serverDir, 'function.json'), JSON.stringify(functionDefinition))
await writeFile(resolve(dir, 'host.json'), JSON.stringify(host))
await zipDirectory(dir, join(dir, 'deploy.zip'))
const zipPath = prettyPath(resolve(dir, 'deploy.zip'))
consola.success(`Ready to run \`az functionapp deployment source config-zip -g <resource-group> -n <app-name> --src ${zipPath}\``)
}

View File

@ -1,3 +1,4 @@
export * from './azure_functions'
export * from './azure'
export * from './browser'
export * from './cloudflare'

View File

@ -1,8 +1,17 @@
import '~polyfill'
import { parseURL } from 'ufo'
import { localCall } from '../server'
export default async function handle (context, req) {
const url = '/' + (req.params.url || '')
let url: string
if (req.headers['x-ms-original-url']) {
// This URL has been proxied as there was no static file matching it.
url = parseURL(req.headers['x-ms-original-url']).pathname
} else {
// Because Azure SWA handles /api/* calls differently they
// never hit the proxy and we have to reconstitute the URL.
url = '/api/' + (req.params.url || '')
}
const { body, status, statusText, headers } = await localCall({
url,

View File

@ -0,0 +1,19 @@
import '~polyfill'
import { localCall } from '../server'
export default async function handle (context, req) {
const url = '/' + (req.params.url || '')
const { body, status, statusText, headers } = await localCall({
url,
headers: req.headers,
method: req.method,
body: req.body
})
context.res = {
status,
headers,
body: body ? body.toString() : statusText
}
}

View File

@ -74,6 +74,10 @@ export function detectTarget () {
if (process.env.NOW_BUILDER) {
return 'vercel'
}
if (process.env.INPUT_AZURE_STATIC_WEB_APPS_API_TOKEN) {
return 'azure'
}
}
export async function isDirectory (path: string) {