Skip to content

Commit 033fc73

Browse files
authored
feat: add manifest validation (netlify/edge-bundler#208)
* feat: add manifest validation * chore: disable color in test
1 parent dabe71a commit 033fc73

File tree

13 files changed

+1342
-803
lines changed

13 files changed

+1342
-803
lines changed

packages/edge-bundler/.eslintrc.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ module.exports = {
1010
},
1111
rules: {
1212
complexity: 'off',
13+
'import/extensions': 'off',
1314
'max-lines': 'off',
1415
'max-statements': 'off',
1516
'node/no-missing-import': 'off',

packages/edge-bundler/node/bundle.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
interface Bundle {
1+
export enum BundleFormat {
2+
ESZIP2 = 'eszip2',
3+
JS = 'js',
4+
}
5+
6+
export interface Bundle {
27
extension: string
3-
format: string
8+
format: BundleFormat
49
hash: string
510
}
6-
7-
export type { Bundle }

packages/edge-bundler/node/formats/eszip.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { join } from 'path'
22

33
import type { WriteStage2Options } from '../../shared/stage2.js'
44
import { DenoBridge } from '../bridge.js'
5-
import type { Bundle } from '../bundle.js'
5+
import { Bundle, BundleFormat } from '../bundle.js'
66
import { wrapBundleError } from '../bundle_error.js'
77
import { EdgeFunction } from '../edge_function.js'
88
import { FeatureFlags } from '../feature_flags.js'
@@ -58,7 +58,7 @@ const bundleESZIP = async ({
5858

5959
const hash = await getFileHash(destPath)
6060

61-
return { extension, format: 'eszip2', hash }
61+
return { extension, format: BundleFormat.ESZIP2, hash }
6262
}
6363

6464
const getESZIPPaths = () => {

packages/edge-bundler/node/formats/javascript.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { pathToFileURL } from 'url'
66
import { deleteAsync } from 'del'
77

88
import { DenoBridge } from '../bridge.js'
9-
import type { Bundle } from '../bundle.js'
9+
import { Bundle, BundleFormat } from '../bundle.js'
1010
import { wrapBundleError } from '../bundle_error.js'
1111
import { EdgeFunction } from '../edge_function.js'
1212
import { ImportMap } from '../import_map.js'
@@ -52,7 +52,7 @@ const bundleJS = async ({
5252

5353
const hash = await getFileHash(jsBundlePath)
5454

55-
return { extension, format: 'js', hash }
55+
return { extension, format: BundleFormat.JS, hash }
5656
}
5757

5858
const defaultFormatExportTypeError: FormatFunction = (name) =>

packages/edge-bundler/node/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ export { DenoBridge } from './bridge.js'
33
export { findFunctions as find } from './finder.js'
44
export { generateManifest } from './manifest.js'
55
export { serve } from './server/server.js'
6+
export { validateManifest, ManifestValidationError } from './validation/manifest/index.js'

packages/edge-bundler/node/manifest.test.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,18 @@ import { env } from 'process'
22

33
import { test, expect } from 'vitest'
44

5+
import { BundleFormat } from './bundle.js'
56
import { generateManifest } from './manifest.js'
67

78
test('Generates a manifest with different bundles', () => {
89
const bundle1 = {
910
extension: '.ext1',
10-
format: 'format1',
11+
format: BundleFormat.ESZIP2,
1112
hash: '123456',
1213
}
1314
const bundle2 = {
1415
extension: '.ext2',
15-
format: 'format2',
16+
format: BundleFormat.ESZIP2,
1617
hash: '654321',
1718
}
1819
const functions = [{ name: 'func-1', path: '/path/to/func-1.ts' }]
@@ -53,7 +54,7 @@ test('Generates a manifest with display names', () => {
5354
test('Excludes functions for which there are function files but no matching config declarations', () => {
5455
const bundle1 = {
5556
extension: '.ext2',
56-
format: 'format1',
57+
format: BundleFormat.ESZIP2,
5758
hash: '123456',
5859
}
5960
const functions = [
@@ -71,7 +72,7 @@ test('Excludes functions for which there are function files but no matching conf
7172
test('Excludes functions for which there are config declarations but no matching function files', () => {
7273
const bundle1 = {
7374
extension: '.ext2',
74-
format: 'format1',
75+
format: BundleFormat.ESZIP2,
7576
hash: '123456',
7677
}
7778
const functions = [{ name: 'func-2', path: '/path/to/func-2.ts' }]
@@ -101,12 +102,12 @@ test('Generates a manifest without bundles', () => {
101102
test('Generates a manifest with pre and post-cache routes', () => {
102103
const bundle1 = {
103104
extension: '.ext1',
104-
format: 'format1',
105+
format: BundleFormat.ESZIP2,
105106
hash: '123456',
106107
}
107108
const bundle2 = {
108109
extension: '.ext2',
109-
format: 'format2',
110+
format: BundleFormat.ESZIP2,
110111
hash: '654321',
111112
}
112113
const functions = [
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
// Vitest Snapshot v1
2+
3+
exports[`bundle > should throw on additional property in bundle 1`] = `
4+
"Validation of Edge Functions manifest failed
5+
ADDTIONAL PROPERTY must NOT have additional properties
6+
7+
4 | \\"asset\\": \\"f35baff44129a8f6be7db68590b2efd86ed4ba29000e2edbcaddc5d620d7d043.js\\",
8+
5 | \\"format\\": \\"js\\",
9+
> 6 | \\"foo\\": \\"bar\\"
10+
| ^^^^^ 😲 foo is not expected to be here!
11+
7 | }
12+
8 | ],
13+
9 | \\"routes\\": ["
14+
`;
15+
16+
exports[`bundle > should throw on invalid format 1`] = `
17+
"Validation of Edge Functions manifest failed
18+
ENUM must be equal to one of the allowed values
19+
(eszip2, js)
20+
21+
3 | {
22+
4 | \\"asset\\": \\"f35baff44129a8f6be7db68590b2efd86ed4ba29000e2edbcaddc5d620d7d043.js\\",
23+
> 5 | \\"format\\": \\"foo\\"
24+
| ^^^^^ 👈🏽 Unexpected value, should be equal to one of the allowed values
25+
6 | }
26+
7 | ],
27+
8 | \\"routes\\": ["
28+
`;
29+
30+
exports[`bundle > should throw on missing asset 1`] = `
31+
"Validation of Edge Functions manifest failed
32+
REQUIRED must have required property 'asset'
33+
34+
1 | {
35+
2 | \\"bundles\\": [
36+
> 3 | {
37+
| ^ ☹️ asset is missing here!
38+
4 | \\"format\\": \\"js\\"
39+
5 | }
40+
6 | ],"
41+
`;
42+
43+
exports[`bundle > should throw on missing format 1`] = `
44+
"Validation of Edge Functions manifest failed
45+
REQUIRED must have required property 'format'
46+
47+
1 | {
48+
2 | \\"bundles\\": [
49+
> 3 | {
50+
| ^ ☹️ format is missing here!
51+
4 | \\"asset\\": \\"f35baff44129a8f6be7db68590b2efd86ed4ba29000e2edbcaddc5d620d7d043.js\\"
52+
5 | }
53+
6 | ],"
54+
`;
55+
56+
exports[`layers > should throw on additional property 1`] = `
57+
"Validation of Edge Functions manifest failed
58+
ADDTIONAL PROPERTY must NOT have additional properties
59+
60+
25 | \\"name\\": \\"name\\",
61+
26 | \\"local\\": \\"local\\",
62+
> 27 | \\"foo\\": \\"bar\\"
63+
| ^^^^^ 😲 foo is not expected to be here!
64+
28 | }
65+
29 | ],
66+
30 | \\"bundler_version\\": \\"1.6.0\\""
67+
`;
68+
69+
exports[`layers > should throw on missing flag 1`] = `
70+
"Validation of Edge Functions manifest failed
71+
REQUIRED must have required property 'flag'
72+
73+
21 | ],
74+
22 | \\"layers\\": [
75+
> 23 | {
76+
| ^ ☹️ flag is missing here!
77+
24 | \\"name\\": \\"name\\",
78+
25 | \\"local\\": \\"local\\"
79+
26 | }"
80+
`;
81+
82+
exports[`layers > should throw on missing name 1`] = `
83+
"Validation of Edge Functions manifest failed
84+
REQUIRED must have required property 'name'
85+
86+
21 | ],
87+
22 | \\"layers\\": [
88+
> 23 | {
89+
| ^ ☹️ name is missing here!
90+
24 | \\"flag\\": \\"flag\\",
91+
25 | \\"local\\": \\"local\\"
92+
26 | }"
93+
`;
94+
95+
exports[`route > should throw on additional property 1`] = `
96+
"Validation of Edge Functions manifest failed
97+
ADDTIONAL PROPERTY must NOT have additional properties
98+
99+
11 | \\"function\\": \\"hello\\",
100+
12 | \\"pattern\\": \\"^/hello/?$\\",
101+
> 13 | \\"foo\\": \\"bar\\"
102+
| ^^^^^ 😲 foo is not expected to be here!
103+
14 | }
104+
15 | ],
105+
16 | \\"post_cache_routes\\": ["
106+
`;
107+
108+
exports[`route > should throw on invalid pattern 1`] = `
109+
"Validation of Edge Functions manifest failed
110+
FORMAT pattern needs to be a regex that starts with ^ and ends with $ without any additional slashes before and afterwards
111+
112+
10 | \\"name\\": \\"name\\",
113+
11 | \\"function\\": \\"hello\\",
114+
> 12 | \\"pattern\\": \\"/^/hello/?$/\\"
115+
| ^^^^^^^^^^^^^^ 👈🏽 format pattern needs to be a regex that starts with ^ and ends with $ without any additional slashes before and afterwards
116+
13 | }
117+
14 | ],
118+
15 | \\"post_cache_routes\\": ["
119+
`;
120+
121+
exports[`route > should throw on missing function 1`] = `
122+
"Validation of Edge Functions manifest failed
123+
REQUIRED must have required property 'function'
124+
125+
7 | ],
126+
8 | \\"routes\\": [
127+
> 9 | {
128+
| ^ ☹️ function is missing here!
129+
10 | \\"name\\": \\"name\\",
130+
11 | \\"pattern\\": \\"^/hello/?$\\"
131+
12 | }"
132+
`;
133+
134+
exports[`route > should throw on missing pattern 1`] = `
135+
"Validation of Edge Functions manifest failed
136+
REQUIRED must have required property 'pattern'
137+
138+
7 | ],
139+
8 | \\"routes\\": [
140+
> 9 | {
141+
| ^ ☹️ pattern is missing here!
142+
10 | \\"name\\": \\"name\\",
143+
11 | \\"function\\": \\"hello\\"
144+
12 | }"
145+
`;
146+
147+
exports[`should show multiple errors 1`] = `
148+
"Validation of Edge Functions manifest failed
149+
ADDTIONAL PROPERTY must NOT have additional properties
150+
151+
28 | ],
152+
29 | \\"bundler_version\\": \\"1.6.0\\",
153+
> 30 | \\"foo\\": \\"bar\\",
154+
| ^^^^^ 😲 foo is not expected to be here!
155+
31 | \\"baz\\": \\"bar\\"
156+
32 | }
157+
158+
ADDTIONAL PROPERTY must NOT have additional properties
159+
160+
29 | \\"bundler_version\\": \\"1.6.0\\",
161+
30 | \\"foo\\": \\"bar\\",
162+
> 31 | \\"baz\\": \\"bar\\"
163+
| ^^^^^ 😲 baz is not expected to be here!
164+
32 | }"
165+
`;
166+
167+
exports[`should throw on additional property on root level 1`] = `
168+
"Validation of Edge Functions manifest failed
169+
ADDTIONAL PROPERTY must NOT have additional properties
170+
171+
28 | ],
172+
29 | \\"bundler_version\\": \\"1.6.0\\",
173+
> 30 | \\"foo\\": \\"bar\\"
174+
| ^^^^^ 😲 foo is not expected to be here!
175+
31 | }"
176+
`;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export default class ManifestValidationError extends Error {
2+
constructor(message: string | undefined) {
3+
super(`Validation of Edge Functions manifest failed\n${message}`)
4+
5+
this.name = 'ManifestValidationError'
6+
7+
// https://github.com/microsoft/TypeScript-wiki/blob/0fecbda7263f130c57394d779b8ca13f0a2e9123/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
8+
Object.setPrototypeOf(this, ManifestValidationError.prototype)
9+
}
10+
}

0 commit comments

Comments
 (0)