Skip to content

Commit

Permalink
feat: add support for config export (#133)
Browse files Browse the repository at this point in the history
* feat: add support for `config` export

* refactor: add collector file

* refactor: use file URL

* feat: add exit codes

* fix: pass logger

* chore: setup Deno in integration workflow

* refactor: move config extractor to separate file

* feat: print stdout

* refactor: run config after bundling

* chore: add comments

* chore: improve tests
  • Loading branch information
eduardoboucas committed Sep 30, 2022
1 parent 71db0a4 commit adb4f82
Show file tree
Hide file tree
Showing 18 changed files with 481 additions and 55 deletions.
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module.exports = {
'max-lines': 'off',
'max-statements': 'off',
'node/no-missing-import': 'off',
'unicorn/prefer-json-parse-buffer': 'off',
},
overrides: [
...overrides,
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ jobs:
node-version: ${{ matrix.node-version }}
cache: 'npm'
check-latest: true
- name: Setup Deno
uses: denoland/setup-deno@v1
with:
# Should match the `DENO_VERSION_RANGE` constant in `node/bridge.ts`.
deno-version: v1.22.0
- name: Install dependencies
run: npm ci
- name: Tests
Expand Down
38 changes: 38 additions & 0 deletions deno/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const [functionURL, collectorURL, rawExitCodes] = Deno.args
const exitCodes = JSON.parse(rawExitCodes)

let func

try {
func = await import(functionURL)
} catch {
Deno.exit(exitCodes.ImportError)
}

if (func.config === undefined) {
Deno.exit(exitCodes.NoConfig)
}

if (typeof func.config !== 'function') {
Deno.exit(exitCodes.InvalidExport)
}

let config

try {
config = await func.config()
} catch (error) {
console.error(error)

Deno.exit(exitCodes.RuntimeError)
}

try {
const result = JSON.stringify(config)

await Deno.writeTextFile(new URL(collectorURL), result)
} catch {
Deno.exit(exitCodes.SerializationError)
}

Deno.exit(exitCodes.Success)
5 changes: 3 additions & 2 deletions node/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ interface RunOptions {
pipeOutput?: boolean
env?: NodeJS.ProcessEnv
extendEnv?: boolean
rejectOnExitCode?: boolean
}

class DenoBridge {
Expand Down Expand Up @@ -214,10 +215,10 @@ class DenoBridge {

// Runs the Deno CLI in the background and returns a reference to the child
// process, awaiting its execution.
async run(args: string[], { pipeOutput, env: inputEnv, extendEnv = true }: RunOptions = {}) {
async run(args: string[], { pipeOutput, env: inputEnv, extendEnv = true, rejectOnExitCode = true }: RunOptions = {}) {
const { path: binaryPath } = await this.getBinaryPath()
const env = this.getEnvironmentVariables(inputEnv)
const options: Options = { env, extendEnv }
const options: Options = { env, extendEnv, reject: rejectOnExitCode }

return DenoBridge.runWithBinary(binaryPath, args, options, pipeOutput)
}
Expand Down
13 changes: 5 additions & 8 deletions node/bundler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { BundleError } from './bundle_error.js'
import { bundle, BundleOptions } from './bundler.js'

test('Produces a JavaScript bundle and a manifest file', async () => {
const sourceDirectory = resolve(fixturesDir, 'project_1', 'functions')
const sourceDirectory = resolve(fixturesDir, 'with_import_maps', 'functions')
const tmpDir = await tmp.dir()
const declarations = [
{
Expand All @@ -37,7 +37,6 @@ test('Produces a JavaScript bundle and a manifest file', async () => {
expect(result.functions.length).toBe(1)
expect(generatedFiles.length).toBe(2)

// eslint-disable-next-line unicorn/prefer-json-parse-buffer
const manifestFile = await fs.readFile(resolve(tmpDir.path, 'manifest.json'), 'utf8')
const manifest = JSON.parse(manifestFile)
const { bundles } = manifest
Expand All @@ -51,7 +50,7 @@ test('Produces a JavaScript bundle and a manifest file', async () => {
}, 10_000)

test('Produces only a ESZIP bundle when the `edge_functions_produce_eszip` feature flag is set', async () => {
const sourceDirectory = resolve(fixturesDir, 'project_1', 'functions')
const sourceDirectory = resolve(fixturesDir, 'with_import_maps', 'functions')
const tmpDir = await tmp.dir()
const declarations = [
{
Expand All @@ -78,7 +77,6 @@ test('Produces only a ESZIP bundle when the `edge_functions_produce_eszip` featu
expect(result.functions.length).toBe(1)
expect(generatedFiles.length).toBe(2)

// eslint-disable-next-line unicorn/prefer-json-parse-buffer
const manifestFile = await fs.readFile(resolve(tmpDir.path, 'manifest.json'), 'utf8')
const manifest = JSON.parse(manifestFile)
const { bundles } = manifest
Expand Down Expand Up @@ -130,7 +128,7 @@ test('Does not add a custom error property to system errors during bundling', as
test('Uses the cache directory as the `DENO_DIR` value if the `edge_functions_cache_deno_dir` feature flag is set', async () => {
expect.assertions(7)

const sourceDirectory = resolve(fixturesDir, 'project_1', 'functions')
const sourceDirectory = resolve(fixturesDir, 'with_import_maps', 'functions')
const outDir = await tmp.dir()
const cacheDir = await tmp.dir()
const declarations = [
Expand Down Expand Up @@ -186,7 +184,7 @@ test('Uses the cache directory as the `DENO_DIR` value if the `edge_functions_ca
}, 10_000)

test('Supports import maps with relative paths', async () => {
const sourceDirectory = resolve(fixturesDir, 'project_1', 'functions')
const sourceDirectory = resolve(fixturesDir, 'with_import_maps', 'functions')
const tmpDir = await tmp.dir()
const declarations = [
{
Expand All @@ -213,7 +211,6 @@ test('Supports import maps with relative paths', async () => {
expect(result.functions.length).toBe(1)
expect(generatedFiles.length).toBe(2)

// eslint-disable-next-line unicorn/prefer-json-parse-buffer
const manifestFile = await fs.readFile(resolve(tmpDir.path, 'manifest.json'), 'utf8')
const manifest = JSON.parse(manifestFile)
const { bundles } = manifest
Expand All @@ -226,7 +223,7 @@ test('Supports import maps with relative paths', async () => {
}, 10_000)

test('Ignores any user-defined `deno.json` files', async () => {
const fixtureDir = join(fixturesDir, 'project_1')
const fixtureDir = join(fixturesDir, 'with_import_maps')
const tmpDir = await tmp.dir()
const declarations = [
{
Expand Down
79 changes: 45 additions & 34 deletions node/bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { v4 as uuidv4 } from 'uuid'

import { DenoBridge, DenoOptions, OnAfterDownloadHook, OnBeforeDownloadHook } from './bridge.js'
import type { Bundle } from './bundle.js'
import type { Declaration } from './declaration.js'
import { FunctionConfig, getFunctionConfig } from './config.js'
import { Declaration, getDeclarationsFromConfig } from './declaration.js'
import { EdgeFunction } from './edge_function.js'
import { FeatureFlags, getFlags } from './feature_flags.js'
import { findFunctions } from './finder.js'
Expand Down Expand Up @@ -40,7 +41,7 @@ interface BundleFormatOptions {
basePath: string
}

const createBundleOps = ({
const createBundle = ({
basePath,
buildID,
debug,
Expand All @@ -50,40 +51,32 @@ const createBundleOps = ({
importMap,
featureFlags,
}: BundleFormatOptions) => {
const bundleOps = []

if (featureFlags.edge_functions_produce_eszip) {
bundleOps.push(
bundleESZIP({
basePath,
buildID,
debug,
deno,
distDirectory,
functions,
importMap,
}),
)
} else {
bundleOps.push(
bundleJS({
buildID,
debug,
deno,
distDirectory,
functions,
importMap,
}),
)
return bundleESZIP({
basePath,
buildID,
debug,
deno,
distDirectory,
functions,
importMap,
})
}

return bundleOps
return bundleJS({
buildID,
debug,
deno,
distDirectory,
functions,
importMap,
})
}

const bundle = async (
sourceDirectories: string[],
distDirectory: string,
declarations: Declaration[] = [],
tomlDeclarations: Declaration[] = [],
{
basePath: inputBasePath,
cacheDirectory,
Expand Down Expand Up @@ -124,8 +117,7 @@ const bundle = async (
// if any.
const importMap = new ImportMap(importMaps)
const functions = await findFunctions(sourceDirectories)

const bundleOps = createBundleOps({
const functionBundle = await createBundle({
basePath,
buildID,
debug,
Expand All @@ -136,15 +128,34 @@ const bundle = async (
featureFlags,
})

const bundles = await Promise.all(bundleOps)

// The final file name of the bundles contains a SHA256 hash of the contents,
// which we can only compute now that the files have been generated. So let's
// rename the bundles to their permanent names.
await createFinalBundles(bundles, distDirectory, buildID)
await createFinalBundles([functionBundle], distDirectory, buildID)

// Retrieving a configuration object for each function.
const functionsConfig = await Promise.all(
functions.map((func) => {
if (!featureFlags.edge_functions_config_export) {
return {}
}

return getFunctionConfig(func, deno, logger)
}),
)

// Creating a hash of function names to configuration objects.
const functionsWithConfig = functions.reduce(
(acc, func, index) => ({ ...acc, [func.name]: functionsConfig[index] }),
{} as Record<string, FunctionConfig>,
)

// Creating a final declarations array by combining the TOML entries with the
// function configuration objects.
const declarations = getDeclarationsFromConfig(tomlDeclarations, functionsWithConfig)

const manifest = await writeManifest({
bundles,
bundles: [functionBundle],
declarations,
distDirectory,
functions,
Expand Down

0 comments on commit adb4f82

Please sign in to comment.