@adobe/helix-deploy 4.12.2 → 4.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/README.md +1 -1
- package/index.js +1 -1
- package/package.json +28 -16
- package/src/ActionBuilder.js +76 -22
- package/src/BaseConfig.js +59 -4
- package/src/DevelopmentServer.js +10 -4
- package/src/bundler/BaseBundler.js +189 -0
- package/src/bundler/EdgeBundler.js +118 -0
- package/src/bundler/RollupBundler.js +165 -0
- package/src/{Bundler.js → bundler/WebpackBundler.js} +18 -158
- package/src/cli.js +4 -0
- package/src/deploy/AWSDeployer.js +4 -2
- package/src/deploy/AzureDeployer.js +8 -4
- package/src/deploy/BaseDeployer.js +11 -4
- package/src/deploy/CloudflareConfig.js +71 -0
- package/src/deploy/CloudflareDeployer.js +145 -0
- package/src/deploy/ComputeAtEdgeConfig.js +93 -0
- package/src/deploy/ComputeAtEdgeDeployer.js +190 -0
- package/src/deploy/GoogleDeployer.js +15 -19
- package/src/gateway/FastlyGateway.js +245 -166
- package/src/template/aws-esm-adapter.js +20 -0
- package/src/template/cloudflare-adapter.js +62 -0
- package/src/template/fastly-adapter.js +99 -0
- package/src/template/{index.js → node-index.js} +2 -0
- package/src/template/node-index.mjs +25 -0
- package/src/template/polyfills/helix-fetch.js +19 -0
- package/src/template/serviceworker-index.js +24 -0
- package/src/template/validate-bundle.js +32 -0
- package/src/utils.js +33 -1
package/src/cli.js
CHANGED
|
@@ -20,6 +20,8 @@ const OpenWhiskDeployer = require('./deploy/OpenWhiskDeployer');
|
|
|
20
20
|
const AWSDeployer = require('./deploy/AWSDeployer');
|
|
21
21
|
const AzureDeployer = require('./deploy/AzureDeployer');
|
|
22
22
|
const GoogleDeployer = require('./deploy/GoogleDeployer');
|
|
23
|
+
const CloudflareDeployer = require('./deploy/CloudflareDeployer');
|
|
24
|
+
const ComputeAtEdgeDeployer = require('./deploy/ComputeAtEdgeDeployer');
|
|
23
25
|
const FastlyGateway = require('./gateway/FastlyGateway');
|
|
24
26
|
|
|
25
27
|
const PLUGINS = [
|
|
@@ -27,6 +29,8 @@ const PLUGINS = [
|
|
|
27
29
|
AWSDeployer,
|
|
28
30
|
AzureDeployer,
|
|
29
31
|
GoogleDeployer,
|
|
32
|
+
CloudflareDeployer,
|
|
33
|
+
ComputeAtEdgeDeployer,
|
|
30
34
|
FastlyGateway,
|
|
31
35
|
];
|
|
32
36
|
|
|
@@ -223,7 +223,7 @@ class AWSDeployer extends BaseDeployer {
|
|
|
223
223
|
Environment: {
|
|
224
224
|
Variables: cfg.params,
|
|
225
225
|
},
|
|
226
|
-
Handler: 'index.lambda',
|
|
226
|
+
Handler: cfg.esm ? 'esm-adapter/index.handler' : 'index.lambda',
|
|
227
227
|
};
|
|
228
228
|
|
|
229
229
|
this.log.info(`--: using lambda role "${this._cfg.role}"`);
|
|
@@ -705,7 +705,9 @@ class AWSDeployer extends BaseDeployer {
|
|
|
705
705
|
return;
|
|
706
706
|
}
|
|
707
707
|
// eslint-disable-next-line no-await-in-loop
|
|
708
|
-
await new Promise((resolve) =>
|
|
708
|
+
await new Promise((resolve) => {
|
|
709
|
+
setTimeout(resolve, 1500);
|
|
710
|
+
});
|
|
709
711
|
} catch (e) {
|
|
710
712
|
this.log.error(chalk`{red error}: error checking function state`);
|
|
711
713
|
throw e;
|
|
@@ -178,19 +178,23 @@ class AzureDeployer extends BaseDeployer {
|
|
|
178
178
|
const { cfg } = this;
|
|
179
179
|
this.log.info('--: updating app (package) parameters ...');
|
|
180
180
|
|
|
181
|
-
const result = await this._client.webApps.listApplicationSettings(
|
|
182
|
-
this.
|
|
181
|
+
const result = await this._client.webApps.listApplicationSettings(
|
|
182
|
+
this._app.resourceGroup,
|
|
183
|
+
this._cfg.appName,
|
|
184
|
+
);
|
|
183
185
|
|
|
184
186
|
const update = {
|
|
185
187
|
...cfg.packageParams,
|
|
186
188
|
...result.properties,
|
|
187
189
|
};
|
|
188
190
|
|
|
189
|
-
await this._client.webApps.updateApplicationSettings(
|
|
191
|
+
await this._client.webApps.updateApplicationSettings(
|
|
192
|
+
this._app.resourceGroup,
|
|
190
193
|
this._cfg.appName,
|
|
191
194
|
{
|
|
192
195
|
properties: update,
|
|
193
|
-
}
|
|
196
|
+
},
|
|
197
|
+
);
|
|
194
198
|
|
|
195
199
|
this.log.info(`${Object.keys(update).length} package parameters have been updated.`);
|
|
196
200
|
}
|
|
@@ -24,6 +24,11 @@ class BaseDeployer {
|
|
|
24
24
|
return this.cfg.log;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
// eslint-disable-next-line class-methods-use-this
|
|
28
|
+
async init() {
|
|
29
|
+
// nothing to do
|
|
30
|
+
}
|
|
31
|
+
|
|
27
32
|
ready() {
|
|
28
33
|
return this.cfg && false;
|
|
29
34
|
}
|
|
@@ -37,9 +42,7 @@ class BaseDeployer {
|
|
|
37
42
|
getOrCreateFetchContext() {
|
|
38
43
|
if (!this._fetchContext) {
|
|
39
44
|
this._fetchContext = process.env.HELIX_FETCH_FORCE_HTTP1
|
|
40
|
-
? fetchAPI.
|
|
41
|
-
alpnProtocols: [fetchAPI.ALPN_HTTP1_1],
|
|
42
|
-
})
|
|
45
|
+
? fetchAPI.h1()
|
|
43
46
|
: fetchAPI.context();
|
|
44
47
|
}
|
|
45
48
|
return this._fetchContext;
|
|
@@ -109,6 +112,8 @@ class BaseDeployer {
|
|
|
109
112
|
if (ret.ok) {
|
|
110
113
|
this.log.info(`id: ${chalk.grey(id)}`);
|
|
111
114
|
this.log.info(`${chalk.green('ok:')} ${ret.status}`);
|
|
115
|
+
this.log.debug(chalk.grey(JSON.stringify(ret.headers.plain(), null, 2)));
|
|
116
|
+
this.log.debug('');
|
|
112
117
|
this.log.debug(chalk.grey(body));
|
|
113
118
|
return;
|
|
114
119
|
}
|
|
@@ -123,7 +128,9 @@ class BaseDeployer {
|
|
|
123
128
|
// eslint-disable-next-line no-param-reassign
|
|
124
129
|
retry404 -= 1;
|
|
125
130
|
// eslint-disable-next-line no-await-in-loop
|
|
126
|
-
await new Promise((resolve) =>
|
|
131
|
+
await new Promise((resolve) => {
|
|
132
|
+
setTimeout(resolve, 1500);
|
|
133
|
+
});
|
|
127
134
|
} else {
|
|
128
135
|
// this.log.info(`${chalk.red('error:')} test failed: ${ret.status} ${body}`);
|
|
129
136
|
throw new Error(`test failed: ${ret.status} ${body}`);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2021 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
class CloudflareConfig {
|
|
13
|
+
constructor() {
|
|
14
|
+
Object.assign(this, {});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
configure(argv) {
|
|
18
|
+
return this
|
|
19
|
+
.withEmail(argv.cloudflareEmail)
|
|
20
|
+
.withAuth(argv.cloudflareAuth)
|
|
21
|
+
.withTestDomain(argv.cloudflareTestDomain)
|
|
22
|
+
.withAccountID(argv.cloudflareAccountId);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
withAccountID(value) {
|
|
26
|
+
this.accountID = value;
|
|
27
|
+
return this;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
withEmail(value) {
|
|
31
|
+
this.email = value;
|
|
32
|
+
return this;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
withTestDomain(value) {
|
|
36
|
+
this.testDomain = value;
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
withAuth(value) {
|
|
41
|
+
this.auth = value;
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static yarg(yargs) {
|
|
46
|
+
return yargs
|
|
47
|
+
.group(['cloudflare-account-id', 'cloudflare-auth', 'cloudflare-email', 'cloudflare-test-domain'], 'Cloudflare Workers Deployment Options')
|
|
48
|
+
.option('cloudflare-account-id', {
|
|
49
|
+
description: 'the Cloudflare account ID to deploy to',
|
|
50
|
+
type: 'string',
|
|
51
|
+
default: '',
|
|
52
|
+
})
|
|
53
|
+
.option('cloudflare-email', {
|
|
54
|
+
description: 'the Cloudflare email address belonging to the authentication token',
|
|
55
|
+
type: 'string',
|
|
56
|
+
default: '',
|
|
57
|
+
})
|
|
58
|
+
.option('cloudflare-test-domain', {
|
|
59
|
+
description: 'the *.workers.dev subdomain to use for testing deployed scripts',
|
|
60
|
+
type: 'string',
|
|
61
|
+
default: '',
|
|
62
|
+
})
|
|
63
|
+
.option('cloudflare-auth', {
|
|
64
|
+
description: 'the Cloudflare API token from https://dash.cloudflare.com/profile/api-tokens',
|
|
65
|
+
type: 'string',
|
|
66
|
+
default: '',
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = CloudflareConfig;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2021 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const FormData = require('form-data');
|
|
15
|
+
const BaseDeployer = require('./BaseDeployer');
|
|
16
|
+
const CloudflareConfig = require('./CloudflareConfig');
|
|
17
|
+
|
|
18
|
+
class CloudflareDeployer extends BaseDeployer {
|
|
19
|
+
constructor(baseConfig, config) {
|
|
20
|
+
super(baseConfig);
|
|
21
|
+
Object.assign(this, {
|
|
22
|
+
id: 'cloudflare',
|
|
23
|
+
name: 'Cloudflare',
|
|
24
|
+
_cfg: config,
|
|
25
|
+
noGatewayBackend: true,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
ready() {
|
|
30
|
+
return !!this._cfg.auth && !!this._cfg.accountID && !!this.cfg.edgeBundle;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
validate() {
|
|
34
|
+
if (!this.ready()) {
|
|
35
|
+
throw new Error('Cloudflare target needs email, token, and account ID');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get fullFunctionName() {
|
|
40
|
+
return `${this.cfg.packageName}--${this.cfg.name}`
|
|
41
|
+
.replace(/\./g, '_')
|
|
42
|
+
.replace('@', '_');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async deploy() {
|
|
46
|
+
const body = fs.readFileSync(path.relative(this.cfg.cwd, this.cfg.edgeBundle));
|
|
47
|
+
const { id } = await this.createKVNamespace(`${this.cfg.packageName}--secrets`);
|
|
48
|
+
|
|
49
|
+
const metadata = {
|
|
50
|
+
body_part: 'script',
|
|
51
|
+
bindings: [
|
|
52
|
+
...Object.entries(this.cfg.params).map(([key, value]) => ({
|
|
53
|
+
name: key,
|
|
54
|
+
type: 'secret_text',
|
|
55
|
+
text: value,
|
|
56
|
+
})),
|
|
57
|
+
{
|
|
58
|
+
name: 'PACKAGE',
|
|
59
|
+
namespace_id: id,
|
|
60
|
+
type: 'kv_namespace',
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// what https://api.cloudflare.com/#worker-script-upload-worker won't tell you:
|
|
66
|
+
// you can use multipart/formdata to set metadata according to
|
|
67
|
+
// https://community.cloudflare.com/t/bind-kv-and-workers-via-api/221391
|
|
68
|
+
const form = new FormData();
|
|
69
|
+
form.append('script', body, {
|
|
70
|
+
contentType: 'application/javascript',
|
|
71
|
+
});
|
|
72
|
+
form.append('metadata', JSON.stringify(metadata), {
|
|
73
|
+
contentType: 'application/json',
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const res = await this.fetch(`https://api.cloudflare.com/client/v4/accounts/${this._cfg.accountID}/workers/scripts/${this.fullFunctionName}`, {
|
|
77
|
+
method: 'PUT',
|
|
78
|
+
headers: form.getHeaders({
|
|
79
|
+
Authorization: `Bearer ${this._cfg.auth}`,
|
|
80
|
+
}),
|
|
81
|
+
body: form.getBuffer(),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
if (!res.ok) {
|
|
85
|
+
const { errors } = await res.json();
|
|
86
|
+
throw new Error(`Unable to upload worker to Cloudflare: ${errors[0].message}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
await this.updatePackageParams(id, this.cfg.packageParams);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async updatePackageParams(id, params) {
|
|
93
|
+
const kvlist = Object.entries(params).map(([key, value]) => ({
|
|
94
|
+
key, value,
|
|
95
|
+
}));
|
|
96
|
+
|
|
97
|
+
const res = await this.fetch(`https://api.cloudflare.com/client/v4/accounts/${this._cfg.accountID}/storage/kv/namespaces/${id}/bulk`, {
|
|
98
|
+
method: 'PUT',
|
|
99
|
+
headers: {
|
|
100
|
+
Authorization: `Bearer ${this._cfg.auth}`,
|
|
101
|
+
'content-type': 'application/json',
|
|
102
|
+
},
|
|
103
|
+
body: JSON.stringify(kvlist),
|
|
104
|
+
});
|
|
105
|
+
return res.ok;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async createKVNamespace(name) {
|
|
109
|
+
const postres = await this.fetch(`https://api.cloudflare.com/client/v4/accounts/${this._cfg.accountID}/storage/kv/namespaces`, {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
headers: {
|
|
112
|
+
Authorization: `Bearer ${this._cfg.auth}`,
|
|
113
|
+
'content-type': 'application/json',
|
|
114
|
+
},
|
|
115
|
+
body: {
|
|
116
|
+
title: name,
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
let { result } = await postres.json();
|
|
120
|
+
if (!result) {
|
|
121
|
+
const listres = await this.fetch(`https://api.cloudflare.com/client/v4/accounts/${this._cfg.accountID}/storage/kv/namespaces`, {
|
|
122
|
+
method: 'GET',
|
|
123
|
+
headers: {
|
|
124
|
+
Authorization: `Bearer ${this._cfg.auth}`,
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
const { result: results } = await listres.json();
|
|
128
|
+
result = results.find((r) => r.title === name);
|
|
129
|
+
}
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async test() {
|
|
134
|
+
return this._cfg.testDomain
|
|
135
|
+
? this.testRequest({
|
|
136
|
+
url: `https://${this.fullFunctionName}.${this._cfg.testDomain}.workers.dev`,
|
|
137
|
+
idHeader: 'CF-RAY',
|
|
138
|
+
retry404: 0,
|
|
139
|
+
})
|
|
140
|
+
: undefined;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
CloudflareDeployer.Config = CloudflareConfig;
|
|
145
|
+
module.exports = CloudflareDeployer;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2021 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
class ComputeAtEdgeConfig {
|
|
13
|
+
constructor() {
|
|
14
|
+
Object.assign(this, {});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
configure(argv) {
|
|
18
|
+
return this
|
|
19
|
+
.withServiceID(argv.computeServiceId)
|
|
20
|
+
.withAuth(argv.fastlyAuth)
|
|
21
|
+
.withCoralogixToken(argv.coralogixToken)
|
|
22
|
+
.withFastlyGateway(argv.fastlyGateway)
|
|
23
|
+
.withComputeDomain(argv.computeTestDomain)
|
|
24
|
+
.withCoralogixApp(argv.computeCoralogixApp);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
withServiceID(value) {
|
|
28
|
+
this.service = value;
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
withAuth(value) {
|
|
33
|
+
this.auth = value;
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
withCoralogixToken(value) {
|
|
38
|
+
this.coralogixToken = value;
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
withCoralogixApp(value) {
|
|
43
|
+
this.coralogixApp = value;
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
withFastlyGateway(value) {
|
|
48
|
+
this.fastlyGateway = value;
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
withComputeDomain(value) {
|
|
53
|
+
this.testDomain = value;
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static yarg(yargs) {
|
|
58
|
+
return yargs
|
|
59
|
+
.group(['compute-service-id', 'compute-domain', 'fastly-auth', 'coralogix-token', 'compute-coralogix-app'], 'Fastly Compute@Edge Options')
|
|
60
|
+
.option('compute-service-id', {
|
|
61
|
+
description: 'the Fastly Service to deploy the action to',
|
|
62
|
+
type: 'string',
|
|
63
|
+
default: '',
|
|
64
|
+
})
|
|
65
|
+
.option('compute-test-domain', {
|
|
66
|
+
description: 'the domain name of the Compute@Edge service (used for testing)',
|
|
67
|
+
type: 'string',
|
|
68
|
+
default: '',
|
|
69
|
+
})
|
|
70
|
+
.option('fastly-auth', {
|
|
71
|
+
description: 'the Fastly token',
|
|
72
|
+
type: 'string',
|
|
73
|
+
default: '',
|
|
74
|
+
})
|
|
75
|
+
.option('coralogix-token', {
|
|
76
|
+
description: 'the Coralogix token (to enable logging)',
|
|
77
|
+
type: 'string',
|
|
78
|
+
default: '',
|
|
79
|
+
})
|
|
80
|
+
.option('fastly-gateway', {
|
|
81
|
+
description: 'the hostname of the Fastly gateway for package params',
|
|
82
|
+
type: 'string',
|
|
83
|
+
default: '',
|
|
84
|
+
})
|
|
85
|
+
.option('compute-coralogix-app', {
|
|
86
|
+
description: 'the Application name',
|
|
87
|
+
type: 'string',
|
|
88
|
+
default: 'fastly-compute',
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = ComputeAtEdgeConfig;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2021 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
const { fork } = require('child_process');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const fs = require('fs/promises');
|
|
15
|
+
const tar = require('tar');
|
|
16
|
+
const getStream = require('get-stream');
|
|
17
|
+
const Fastly = require('@adobe/fastly-native-promises');
|
|
18
|
+
const BaseDeployer = require('./BaseDeployer');
|
|
19
|
+
const ComputeAtEdgeConfig = require('./ComputeAtEdgeConfig');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The class ComputeAtEdgeDeployer deploys to Fastly's Compute(at)Edge (WASM) runtime.
|
|
23
|
+
* It should be seen as a functional equivalent to the CloudflareDeployer
|
|
24
|
+
* and not confused with the FastlyGateway (which only routes requests, but
|
|
25
|
+
* does not handle them.)
|
|
26
|
+
*/
|
|
27
|
+
class ComputeAtEdgeDeployer extends BaseDeployer {
|
|
28
|
+
constructor(baseConfig, config) {
|
|
29
|
+
super(baseConfig);
|
|
30
|
+
Object.assign(this, {
|
|
31
|
+
id: 'c@e',
|
|
32
|
+
name: 'Fastly Compute@Edge',
|
|
33
|
+
_cfg: config,
|
|
34
|
+
_fastly: null,
|
|
35
|
+
noGatewayBackend: true,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
ready() {
|
|
40
|
+
return !!this._cfg.service && !!this._cfg.auth && !!this.cfg.edgeBundle;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
validate() {
|
|
44
|
+
if (!this.ready()) {
|
|
45
|
+
throw new Error('Compute@Edge target needs token and service ID');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
init() {
|
|
50
|
+
if (this.ready() && !this._fastly) {
|
|
51
|
+
this._fastly = Fastly(this._cfg.auth, this._cfg.service, 60000);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get log() {
|
|
56
|
+
return this.cfg.log;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
*
|
|
61
|
+
* @returns
|
|
62
|
+
*/
|
|
63
|
+
async bundle() {
|
|
64
|
+
const bundleDir = path.dirname(this.cfg.edgeBundle);
|
|
65
|
+
this.log.debug(`Creating fastly.toml in ${bundleDir}`);
|
|
66
|
+
fs.writeFile(path.resolve(bundleDir, 'fastly.toml'), `
|
|
67
|
+
# This file describes a Fastly Compute@Edge package. To learn more visit:
|
|
68
|
+
# https://developer.fastly.com/reference/fastly-toml/
|
|
69
|
+
|
|
70
|
+
authors = ["Helix Deploy"]
|
|
71
|
+
description = "Test Project"
|
|
72
|
+
language = "javascript"
|
|
73
|
+
manifest_version = 2
|
|
74
|
+
name = "Test"
|
|
75
|
+
service_id = ""
|
|
76
|
+
`);
|
|
77
|
+
|
|
78
|
+
return new Promise((resolve, reject) => {
|
|
79
|
+
const child = fork(
|
|
80
|
+
path.resolve(
|
|
81
|
+
__dirname,
|
|
82
|
+
'..',
|
|
83
|
+
'..',
|
|
84
|
+
'node_modules',
|
|
85
|
+
'@fastly',
|
|
86
|
+
'js-compute',
|
|
87
|
+
'js-compute-runtime-cli.js',
|
|
88
|
+
),
|
|
89
|
+
[this.cfg.edgeBundle, 'bin/main.wasm'],
|
|
90
|
+
{
|
|
91
|
+
cwd: bundleDir,
|
|
92
|
+
},
|
|
93
|
+
);
|
|
94
|
+
child.on('data', (data) => resolve(data));
|
|
95
|
+
child.on('error', (err) => reject(err));
|
|
96
|
+
child.on('close', (err) => {
|
|
97
|
+
if (err) {
|
|
98
|
+
// non-zero status code
|
|
99
|
+
reject(err);
|
|
100
|
+
} else {
|
|
101
|
+
this.log.debug(`Created WASM bundle of script and interpreter in ${bundleDir}/bin/main.wasm`);
|
|
102
|
+
const stream = tar.c({
|
|
103
|
+
gzip: true,
|
|
104
|
+
// sync: true,
|
|
105
|
+
cwd: bundleDir,
|
|
106
|
+
prefix: 'Test',
|
|
107
|
+
// file: path.resolve(bundleDir, 'fastly-bundle.tar.gz')
|
|
108
|
+
}, ['bin/main.wasm', 'fastly.toml']);
|
|
109
|
+
// this.log.debug(`Created tar file in ${bundleDir}/fastly-bundle.tar.gz`);
|
|
110
|
+
resolve(getStream.buffer(stream));
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async deploy() {
|
|
117
|
+
const buf = await this.bundle();
|
|
118
|
+
|
|
119
|
+
this.init();
|
|
120
|
+
|
|
121
|
+
await this._fastly.transact(async (version) => {
|
|
122
|
+
await this._fastly.writePackage(version, buf);
|
|
123
|
+
|
|
124
|
+
await this._fastly.writeDictionary(version, 'secrets', {
|
|
125
|
+
name: 'secrets',
|
|
126
|
+
write_only: 'true',
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const host = this._cfg.fastlyGateway;
|
|
130
|
+
console.log('Host', host);
|
|
131
|
+
const backend = {
|
|
132
|
+
hostname: host,
|
|
133
|
+
ssl_cert_hostname: host,
|
|
134
|
+
ssl_sni_hostname: host,
|
|
135
|
+
address: host,
|
|
136
|
+
override_host: host,
|
|
137
|
+
name: 'gateway',
|
|
138
|
+
error_threshold: 0,
|
|
139
|
+
first_byte_timeout: 60000,
|
|
140
|
+
weight: 100,
|
|
141
|
+
connect_timeout: 5000,
|
|
142
|
+
port: 443,
|
|
143
|
+
between_bytes_timeout: 10000,
|
|
144
|
+
shield: '', // 'bwi-va-us',
|
|
145
|
+
max_conn: 200,
|
|
146
|
+
use_ssl: true,
|
|
147
|
+
};
|
|
148
|
+
await this._fastly.writeBackend(version, 'gateway', backend);
|
|
149
|
+
}, true);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async updatePackage() {
|
|
153
|
+
this.log.info('--: updating app (gateway) config ...');
|
|
154
|
+
|
|
155
|
+
this.init();
|
|
156
|
+
|
|
157
|
+
const functionparams = Object
|
|
158
|
+
.entries(this.cfg.params)
|
|
159
|
+
.map(([key, value]) => ({
|
|
160
|
+
item_key: key,
|
|
161
|
+
item_value: value,
|
|
162
|
+
op: 'update',
|
|
163
|
+
}));
|
|
164
|
+
|
|
165
|
+
await this._fastly.bulkUpdateDictItems(undefined, 'secrets', ...functionparams);
|
|
166
|
+
await this._fastly.updateDictItem(undefined, 'secrets', '_token', this.cfg.packageToken);
|
|
167
|
+
console.log('package', `https://${this._cfg.fastlyGateway}/${this.cfg.packageName}/`);
|
|
168
|
+
await this._fastly.updateDictItem(undefined, 'secrets', '_package', `https://${this._cfg.fastlyGateway}/${this.cfg.packageName}/`);
|
|
169
|
+
|
|
170
|
+
this._fastly.discard();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
get fullFunctionName() {
|
|
174
|
+
return `${this.cfg.packageName}--${this.cfg.name}`
|
|
175
|
+
.replace(/\./g, '_')
|
|
176
|
+
.replace('@', '_');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async test() {
|
|
180
|
+
return this._cfg.testDomain
|
|
181
|
+
? this.testRequest({
|
|
182
|
+
url: `https://${this._cfg.testDomain}.edgecompute.app`,
|
|
183
|
+
retry404: 0,
|
|
184
|
+
})
|
|
185
|
+
: undefined;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
ComputeAtEdgeDeployer.Config = ComputeAtEdgeConfig;
|
|
190
|
+
module.exports = ComputeAtEdgeDeployer;
|