@fishawack/lab-env 5.2.1-beta.1 → 5.3.0-beta.2
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 +19 -0
- package/commands/create/cmds/dekey.js +5 -0
- package/commands/create/cmds/key.js +5 -0
- package/commands/create/cmds/provision.js +195 -14
- package/commands/create/libs/aws-cloudfront-request.js +156 -0
- package/commands/create/libs/aws-cloudfront-response.js +19 -8
- package/commands/create/services/aws/index.js +12 -5
- package/core/1/CHANGELOG.md +12 -0
- package/core/1/Dockerfile +3 -0
- package/core/1/docker-compose.yml +1 -0
- package/core/1/package.json +1 -1
- package/package.json +1 -1
- package/commands/create/libs/aws-cloudfront-auth.js +0 -93
- package/commands/create/libs/aws-cloudfront-simple.js +0 -61
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
## Changelog
|
|
2
2
|
|
|
3
|
+
### 5.3.0-beta.2 (2026-02-20)
|
|
4
|
+
|
|
5
|
+
#### Build Updates
|
|
6
|
+
|
|
7
|
+
* **core/1:** Bumped lab-env-core-1 ([e3e151a](https://bitbucket.org/fishawackdigital/lab-env/commits/e3e151aacb75c360f36f7b8303966a99f1c202d0))
|
|
8
|
+
|
|
9
|
+
### 5.3.0-beta.1 (2026-02-04)
|
|
10
|
+
|
|
11
|
+
#### Features
|
|
12
|
+
|
|
13
|
+
* can now customize headers for static provisions ([a3bc776](https://bitbucket.org/fishawackdigital/lab-env/commits/a3bc776c6acca3555de4ef5667ffdf9f8b37a518))
|
|
14
|
+
* can now specify redirects for use in cloudfront functions ([67706d7](https://bitbucket.org/fishawackdigital/lab-env/commits/67706d76300fc6743f7a5ceb9bf31748cf8acb31))
|
|
15
|
+
|
|
16
|
+
#### Bug Fixes
|
|
17
|
+
|
|
18
|
+
* deprov now correctly removes the response function for static sites ([69fbc2b](https://bitbucket.org/fishawackdigital/lab-env/commits/69fbc2b8f60898d3d33416328752897dff6f752b))
|
|
19
|
+
* prompt if redirects are needed first ([e087bf0](https://bitbucket.org/fishawackdigital/lab-env/commits/e087bf0f6d86c543f31d9f0c401793222f8f0d9a))
|
|
20
|
+
* set aws client on key command ([3b15512](https://bitbucket.org/fishawackdigital/lab-env/commits/3b15512b168d4ba35871047fd0ea9230cf394804))
|
|
21
|
+
|
|
3
22
|
### 5.2.1-beta.1 (2026-01-26)
|
|
4
23
|
|
|
5
24
|
#### Bug Fixes
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
const _ = require("../../../globals.js");
|
|
2
2
|
const inquirer = require("inquirer");
|
|
3
3
|
const aws = require("../services/aws/index.js");
|
|
4
|
+
const {
|
|
5
|
+
setAWSClientDefaults,
|
|
6
|
+
} = require("../../../commands/create/services/aws/misc.js");
|
|
4
7
|
|
|
5
8
|
module.exports = [
|
|
6
9
|
"dekey",
|
|
@@ -63,6 +66,8 @@ module.exports = [
|
|
|
63
66
|
for (let i = 0; i < clients.length; i++) {
|
|
64
67
|
let client = clients[i];
|
|
65
68
|
|
|
69
|
+
setAWSClientDefaults(client);
|
|
70
|
+
|
|
66
71
|
for (let j = 0; j < users.length; j++) {
|
|
67
72
|
let user = users[j];
|
|
68
73
|
|
|
@@ -7,6 +7,9 @@ const htmlEmail = require("fs").readFileSync(
|
|
|
7
7
|
`${__dirname}/../templates/credentials.html`,
|
|
8
8
|
{ encoding: "utf8" },
|
|
9
9
|
);
|
|
10
|
+
const {
|
|
11
|
+
setAWSClientDefaults,
|
|
12
|
+
} = require("../../../commands/create/services/aws/misc.js");
|
|
10
13
|
|
|
11
14
|
module.exports = [
|
|
12
15
|
"key",
|
|
@@ -83,6 +86,8 @@ module.exports = [
|
|
|
83
86
|
for (let i = 0; i < clients.length; i++) {
|
|
84
87
|
let client = clients[i];
|
|
85
88
|
|
|
89
|
+
setAWSClientDefaults(client);
|
|
90
|
+
|
|
86
91
|
for (let j = 0; j < users.length; j++) {
|
|
87
92
|
let user = users[j];
|
|
88
93
|
|
|
@@ -32,12 +32,169 @@ module.exports = [
|
|
|
32
32
|
},
|
|
33
33
|
]);
|
|
34
34
|
|
|
35
|
+
// Check for existing redirects in config
|
|
36
|
+
let redirects = _.coreConfig?.attributes?.deploy?.redirects || [];
|
|
37
|
+
|
|
35
38
|
if (!answer.check) {
|
|
36
39
|
process.exit(1);
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
let answers = await inquirer.prompt([stack]);
|
|
40
43
|
|
|
44
|
+
// Prompt for redirects if static stack and no existing redirects
|
|
45
|
+
if (answers.stack === "static" && redirects.length === 0) {
|
|
46
|
+
let wantsRedirects = await inquirer.prompt([
|
|
47
|
+
{
|
|
48
|
+
type: "confirm",
|
|
49
|
+
name: "setup",
|
|
50
|
+
message: "Setup redirects?",
|
|
51
|
+
default: false,
|
|
52
|
+
},
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
if (wantsRedirects.setup) {
|
|
56
|
+
let addRedirect = { continue: true };
|
|
57
|
+
|
|
58
|
+
while (addRedirect.continue) {
|
|
59
|
+
addRedirect = await inquirer.prompt([
|
|
60
|
+
{
|
|
61
|
+
type: "input",
|
|
62
|
+
name: "src",
|
|
63
|
+
message:
|
|
64
|
+
"Redirect source path (e.g., /old-path or /news/*)",
|
|
65
|
+
validate: (input) => {
|
|
66
|
+
if (!input || !input.startsWith("/")) {
|
|
67
|
+
return "Source must start with /";
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
type: "input",
|
|
74
|
+
name: "dest",
|
|
75
|
+
message:
|
|
76
|
+
"Redirect destination (e.g., /new-path or https://example.com/news/*)",
|
|
77
|
+
validate: (input) => !!input.length,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
type: "confirm",
|
|
81
|
+
name: "continue",
|
|
82
|
+
message: "Add another redirect?",
|
|
83
|
+
default: true,
|
|
84
|
+
},
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
if (addRedirect.src && addRedirect.dest) {
|
|
88
|
+
redirects.push({
|
|
89
|
+
src: addRedirect.src,
|
|
90
|
+
dest: addRedirect.dest,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Prompt for custom headers if static stack and no existing headers
|
|
98
|
+
let headers = _.coreConfig?.attributes?.deploy?.headers || [];
|
|
99
|
+
|
|
100
|
+
if (answers.stack === "static" && headers.length === 0) {
|
|
101
|
+
let customHeaders = await inquirer.prompt([
|
|
102
|
+
{
|
|
103
|
+
type: "confirm",
|
|
104
|
+
name: "customize",
|
|
105
|
+
message: "Customize response headers?",
|
|
106
|
+
default: false,
|
|
107
|
+
},
|
|
108
|
+
]);
|
|
109
|
+
|
|
110
|
+
if (customHeaders.customize) {
|
|
111
|
+
// Prompt for standard headers with defaults
|
|
112
|
+
let standardHeaders = await inquirer.prompt([
|
|
113
|
+
{
|
|
114
|
+
type: "input",
|
|
115
|
+
name: "strict-transport-security",
|
|
116
|
+
message: "strict-transport-security header value:",
|
|
117
|
+
default: "max-age=31536000; includeSubDomains",
|
|
118
|
+
validate: (input) => !!input.length,
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
type: "input",
|
|
122
|
+
name: "content-security-policy",
|
|
123
|
+
message: "content-security-policy header value:",
|
|
124
|
+
default:
|
|
125
|
+
"default-src 'self' https: data: 'unsafe-inline';",
|
|
126
|
+
validate: (input) => !!input.length,
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
type: "input",
|
|
130
|
+
name: "x-content-type-options",
|
|
131
|
+
message: "x-content-type-options header value:",
|
|
132
|
+
default: "nosniff",
|
|
133
|
+
validate: (input) => !!input.length,
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
type: "input",
|
|
137
|
+
name: "x-frame-options",
|
|
138
|
+
message: "x-frame-options header value:",
|
|
139
|
+
default: "sameorigin",
|
|
140
|
+
validate: (input) => !!input.length,
|
|
141
|
+
},
|
|
142
|
+
]);
|
|
143
|
+
|
|
144
|
+
// Add standard headers to array
|
|
145
|
+
Object.keys(standardHeaders).forEach((name) => {
|
|
146
|
+
headers.push({
|
|
147
|
+
name: name,
|
|
148
|
+
value: standardHeaders[name],
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Prompt for additional custom headers
|
|
153
|
+
let wantsCustomHeaders = await inquirer.prompt([
|
|
154
|
+
{
|
|
155
|
+
type: "confirm",
|
|
156
|
+
name: "add",
|
|
157
|
+
message: "Add custom headers?",
|
|
158
|
+
default: false,
|
|
159
|
+
},
|
|
160
|
+
]);
|
|
161
|
+
|
|
162
|
+
if (wantsCustomHeaders.add) {
|
|
163
|
+
let addHeader = { continue: true };
|
|
164
|
+
|
|
165
|
+
while (addHeader.continue) {
|
|
166
|
+
addHeader = await inquirer.prompt([
|
|
167
|
+
{
|
|
168
|
+
type: "input",
|
|
169
|
+
name: "name",
|
|
170
|
+
message: "Custom header name:",
|
|
171
|
+
validate: (input) => !!input.length,
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
type: "input",
|
|
175
|
+
name: "value",
|
|
176
|
+
message: "Custom header value:",
|
|
177
|
+
validate: (input) => !!input.length,
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
type: "confirm",
|
|
181
|
+
name: "continue",
|
|
182
|
+
message: "Add another custom header?",
|
|
183
|
+
default: false,
|
|
184
|
+
},
|
|
185
|
+
]);
|
|
186
|
+
|
|
187
|
+
if (addHeader.name && addHeader.value) {
|
|
188
|
+
headers.push({
|
|
189
|
+
name: addHeader.name,
|
|
190
|
+
value: addHeader.value,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
41
198
|
if (answers.stack === "fullstack") {
|
|
42
199
|
answers = {
|
|
43
200
|
...answers,
|
|
@@ -135,20 +292,36 @@ module.exports = [
|
|
|
135
292
|
setAWSClientDefaults(answers.client, answers.region);
|
|
136
293
|
|
|
137
294
|
try {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
295
|
+
if (answers.stack === "static") {
|
|
296
|
+
infastructure = await aws.static(
|
|
297
|
+
slug,
|
|
298
|
+
[
|
|
299
|
+
{ Key: "repository", Value: _.repo },
|
|
300
|
+
{ Key: "environment", Value: branch },
|
|
301
|
+
{ Key: "automated", Value: true },
|
|
302
|
+
],
|
|
303
|
+
credentials,
|
|
304
|
+
_.repoSafe,
|
|
305
|
+
branch,
|
|
306
|
+
redirects,
|
|
307
|
+
headers,
|
|
308
|
+
);
|
|
309
|
+
} else {
|
|
310
|
+
infastructure = await aws.fullstack(
|
|
311
|
+
slug,
|
|
312
|
+
[
|
|
313
|
+
{ Key: "repository", Value: _.repo },
|
|
314
|
+
{ Key: "environment", Value: branch },
|
|
315
|
+
{ Key: "automated", Value: true },
|
|
316
|
+
],
|
|
317
|
+
credentials,
|
|
318
|
+
_.repoSafe,
|
|
319
|
+
branch,
|
|
320
|
+
answers.framework,
|
|
321
|
+
answers.availability,
|
|
322
|
+
answers.database,
|
|
323
|
+
);
|
|
324
|
+
}
|
|
152
325
|
} catch (e) {
|
|
153
326
|
console.log(e.message);
|
|
154
327
|
process.exit(1);
|
|
@@ -175,6 +348,14 @@ module.exports = [
|
|
|
175
348
|
config[branch].deploy.users = credentials;
|
|
176
349
|
}
|
|
177
350
|
|
|
351
|
+
if (redirects.length) {
|
|
352
|
+
config[branch].deploy.redirects = redirects;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (headers.length) {
|
|
356
|
+
config[branch].deploy.headers = headers;
|
|
357
|
+
}
|
|
358
|
+
|
|
178
359
|
let stringify = JSON.stringify(config, null, 4);
|
|
179
360
|
let output = stringify.substring(1, stringify.length - 1).trim();
|
|
180
361
|
execSync(`printf '${output}' | pbcopy`);
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// eslint-disable-next-line no-unused-vars
|
|
2
|
+
function handler(event) {
|
|
3
|
+
var query, index, key, response;
|
|
4
|
+
|
|
5
|
+
// Redirect if www to non-www
|
|
6
|
+
if (event.request.headers.host.value.includes("www.")) {
|
|
7
|
+
query = "";
|
|
8
|
+
index = 0;
|
|
9
|
+
|
|
10
|
+
for (key in event.request.querystring) {
|
|
11
|
+
query += `${index ? "&" : "?"}${key}=${event.request.querystring[key].value}`;
|
|
12
|
+
index++;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
response = {
|
|
16
|
+
statusCode: 301,
|
|
17
|
+
statusDescription: "Moved Permanently",
|
|
18
|
+
headers: {
|
|
19
|
+
location: {
|
|
20
|
+
value: `https://${event.request.headers.host.value.split("www.")[1]}${event.request.uri}${query}`,
|
|
21
|
+
},
|
|
22
|
+
"strict-transport-security": {
|
|
23
|
+
value: "max-age=31536000; includeSubDomains",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return response;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Check authentication if credentials are configured
|
|
32
|
+
var expected = "<%= credentials %>";
|
|
33
|
+
|
|
34
|
+
if (expected && expected.length > 0) {
|
|
35
|
+
var authHeaders = event.request.headers.authorization;
|
|
36
|
+
|
|
37
|
+
// If no auth header or credentials don't match, return 401
|
|
38
|
+
if (!authHeaders || !expected.find((d) => d === authHeaders.value)) {
|
|
39
|
+
response = {
|
|
40
|
+
statusCode: 401,
|
|
41
|
+
statusDescription: "Unauthorized",
|
|
42
|
+
headers: {
|
|
43
|
+
"www-authenticate": { value: "Basic" },
|
|
44
|
+
"strict-transport-security": {
|
|
45
|
+
value: "max-age=31536000; includeSubDomains",
|
|
46
|
+
},
|
|
47
|
+
"content-security-policy": {
|
|
48
|
+
value: "default-src 'self' https: data: 'unsafe-inline';",
|
|
49
|
+
},
|
|
50
|
+
"x-content-type-options": { value: "nosniff" },
|
|
51
|
+
"x-frame-options": { value: "sameorigin" },
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return response;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Redirect if no trailing slash (BEFORE custom redirects)
|
|
60
|
+
if (!event.request.uri.includes(".") && !event.request.uri.endsWith("/")) {
|
|
61
|
+
query = "";
|
|
62
|
+
index = 0;
|
|
63
|
+
|
|
64
|
+
for (key in event.request.querystring) {
|
|
65
|
+
query += `${index ? "&" : "?"}${key}=${event.request.querystring[key].value}`;
|
|
66
|
+
index++;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
statusCode: 301,
|
|
71
|
+
statusDescription: "Moved Permanently",
|
|
72
|
+
headers: { location: { value: `${event.request.uri}/${query}` } },
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Check for custom redirects (URI is guaranteed to have trailing slash now)
|
|
77
|
+
var customRedirects = "<%= redirects %>";
|
|
78
|
+
var currentUri = event.request.uri;
|
|
79
|
+
var i, redirect, srcPattern, captured, destination;
|
|
80
|
+
|
|
81
|
+
for (i = 0; i < customRedirects.length; i++) {
|
|
82
|
+
redirect = customRedirects[i];
|
|
83
|
+
|
|
84
|
+
// Handle wildcard redirects
|
|
85
|
+
if (redirect.src.endsWith("/*")) {
|
|
86
|
+
srcPattern = redirect.src.slice(0, -2);
|
|
87
|
+
if (
|
|
88
|
+
currentUri.startsWith(srcPattern + "/") ||
|
|
89
|
+
currentUri === srcPattern
|
|
90
|
+
) {
|
|
91
|
+
captured = currentUri.slice(srcPattern.length);
|
|
92
|
+
destination = redirect.dest;
|
|
93
|
+
|
|
94
|
+
// Replace wildcard in destination with captured path
|
|
95
|
+
if (destination.endsWith("/*")) {
|
|
96
|
+
destination = destination.slice(0, -2) + captured;
|
|
97
|
+
} else {
|
|
98
|
+
destination = destination + captured;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Handle relative vs absolute destinations
|
|
102
|
+
if (!destination.startsWith("http")) {
|
|
103
|
+
destination =
|
|
104
|
+
"https://" +
|
|
105
|
+
event.request.headers.host.value +
|
|
106
|
+
destination;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
statusCode: 301,
|
|
111
|
+
statusDescription: "Moved Permanently",
|
|
112
|
+
headers: { location: { value: destination } },
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Handle exact match redirects (normalize src to have trailing slash)
|
|
117
|
+
else {
|
|
118
|
+
var normalizedSrc = redirect.src.endsWith("/")
|
|
119
|
+
? redirect.src
|
|
120
|
+
: redirect.src + "/";
|
|
121
|
+
|
|
122
|
+
if (normalizedSrc === currentUri) {
|
|
123
|
+
destination = redirect.dest;
|
|
124
|
+
|
|
125
|
+
// Handle relative vs absolute destinations
|
|
126
|
+
if (!destination.startsWith("http")) {
|
|
127
|
+
destination =
|
|
128
|
+
"https://" +
|
|
129
|
+
event.request.headers.host.value +
|
|
130
|
+
destination;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
statusCode: 301,
|
|
135
|
+
statusDescription: "Moved Permanently",
|
|
136
|
+
headers: { location: { value: destination } },
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Rewrite url to append index.html if not present
|
|
143
|
+
var request = event.request;
|
|
144
|
+
var uri = request.uri;
|
|
145
|
+
|
|
146
|
+
// Check whether the URI is missing a file name.
|
|
147
|
+
if (uri.endsWith("/")) {
|
|
148
|
+
request.uri += "index.html";
|
|
149
|
+
}
|
|
150
|
+
// Check whether the URI is missing a file extension.
|
|
151
|
+
else if (!uri.includes(".")) {
|
|
152
|
+
request.uri += "/index.html";
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return request;
|
|
156
|
+
}
|
|
@@ -3,15 +3,26 @@ function handler(event) {
|
|
|
3
3
|
// Add security headers
|
|
4
4
|
var response = event.response;
|
|
5
5
|
var headers = response.headers;
|
|
6
|
+
var customHeaders = "<%= headers %>";
|
|
7
|
+
var i, header;
|
|
6
8
|
|
|
7
|
-
headers
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
// If custom headers provided, use them; otherwise use defaults
|
|
10
|
+
if (customHeaders && customHeaders.length > 0) {
|
|
11
|
+
for (i = 0; i < customHeaders.length; i++) {
|
|
12
|
+
header = customHeaders[i];
|
|
13
|
+
headers[header.name] = { value: header.value };
|
|
14
|
+
}
|
|
15
|
+
} else {
|
|
16
|
+
// Default headers
|
|
17
|
+
headers["strict-transport-security"] = {
|
|
18
|
+
value: "max-age=31536000; includeSubDomains",
|
|
19
|
+
};
|
|
20
|
+
headers["content-security-policy"] = {
|
|
21
|
+
value: "default-src 'self' https: data: 'unsafe-inline';",
|
|
22
|
+
};
|
|
23
|
+
headers["x-content-type-options"] = { value: "nosniff" };
|
|
24
|
+
headers["x-frame-options"] = { value: "sameorigin" };
|
|
25
|
+
}
|
|
15
26
|
|
|
16
27
|
return response;
|
|
17
28
|
}
|
|
@@ -66,6 +66,8 @@ module.exports.static = async (
|
|
|
66
66
|
credentials = [],
|
|
67
67
|
repo,
|
|
68
68
|
branch,
|
|
69
|
+
redirects = [],
|
|
70
|
+
headers = [],
|
|
69
71
|
) => {
|
|
70
72
|
let s3 = await module.exports.s3.createS3Bucket(name, tags);
|
|
71
73
|
|
|
@@ -76,14 +78,13 @@ module.exports.static = async (
|
|
|
76
78
|
(
|
|
77
79
|
await module.exports.cloudfront.createCloudFrontFunction(
|
|
78
80
|
name,
|
|
79
|
-
|
|
80
|
-
? "aws-cloudfront-auth"
|
|
81
|
-
: "aws-cloudfront-simple",
|
|
81
|
+
"aws-cloudfront-request",
|
|
82
82
|
{
|
|
83
83
|
credentials: credentials.map(
|
|
84
84
|
(d) =>
|
|
85
85
|
`Basic ${Buffer.from(`${d.username}:${d.password}`).toString("base64")}`,
|
|
86
86
|
),
|
|
87
|
+
redirects: redirects,
|
|
87
88
|
},
|
|
88
89
|
)
|
|
89
90
|
).FunctionSummary.FunctionMetadata.FunctionARN,
|
|
@@ -95,7 +96,9 @@ module.exports.static = async (
|
|
|
95
96
|
branch,
|
|
96
97
|
),
|
|
97
98
|
"aws-cloudfront-response",
|
|
98
|
-
{
|
|
99
|
+
{
|
|
100
|
+
headers: headers,
|
|
101
|
+
},
|
|
99
102
|
)
|
|
100
103
|
).FunctionSummary.FunctionMetadata.FunctionARN,
|
|
101
104
|
);
|
|
@@ -143,7 +146,11 @@ module.exports.staticTerminate = async (name, repo, branch, id) => {
|
|
|
143
146
|
|
|
144
147
|
try {
|
|
145
148
|
await module.exports.cloudfront.removeCloudFrontFunction(
|
|
146
|
-
module.exports.slug(
|
|
149
|
+
module.exports.slug(
|
|
150
|
+
`${repo}-response`,
|
|
151
|
+
process.env.AWS_PROFILE,
|
|
152
|
+
branch,
|
|
153
|
+
),
|
|
147
154
|
);
|
|
148
155
|
} catch {
|
|
149
156
|
/* empty */
|
package/core/1/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
## Changelog
|
|
2
2
|
|
|
3
|
+
### 1.10.0 (2026-02-20)
|
|
4
|
+
|
|
5
|
+
#### Features
|
|
6
|
+
|
|
7
|
+
* added support for synk globally installed packages ([2c70875](https://bitbucket.org/fishawackdigital/lab-env-core-1/commits/2c70875cd811cb325b9f0fe6f140d699b13c1bc2))
|
|
8
|
+
|
|
9
|
+
### 1.10.0-beta.1 (2026-02-20)
|
|
10
|
+
|
|
11
|
+
#### Features
|
|
12
|
+
|
|
13
|
+
* added support for synk globally installed packages ([2c70875](https://bitbucket.org/fishawackdigital/lab-env-core-1/commits/2c70875cd811cb325b9f0fe6f140d699b13c1bc2))
|
|
14
|
+
|
|
3
15
|
### 1.9.1 (2025-10-29)
|
|
4
16
|
|
|
5
17
|
#### Bug Fixes
|
package/core/1/Dockerfile
CHANGED
|
@@ -55,6 +55,9 @@ RUN npm install are-you-es5 -g
|
|
|
55
55
|
# Install git branch fetcher
|
|
56
56
|
RUN npm install git-branch -g
|
|
57
57
|
|
|
58
|
+
# Install snyk package
|
|
59
|
+
RUN npm install snyk snyk-to-html -g
|
|
60
|
+
|
|
58
61
|
# Link root global node_modules to ~/.node_modules
|
|
59
62
|
RUN ln -s /usr/local/lib/node_modules/ /home/node/.node_modules
|
|
60
63
|
|
package/core/1/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "lab-env docker config for the @fishawack/core/1 npm module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"setup": "ln -sfn ../../.commitlintrc.json .commitlintrc.json && ln -sfn ../../.editorconfig .editorconfig && ln -sfn ../../.prettierignore .prettierignore && ln -sfn ../../.husky/ .husky && husky &>/dev/null || true",
|
package/package.json
CHANGED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
// eslint-disable-next-line no-unused-vars
|
|
2
|
-
function handler(event) {
|
|
3
|
-
var query, index, key, response;
|
|
4
|
-
|
|
5
|
-
// Redirect if www to non-www
|
|
6
|
-
if (event.request.headers.host.value.includes("www.")) {
|
|
7
|
-
query = "";
|
|
8
|
-
index = 0;
|
|
9
|
-
|
|
10
|
-
for (key in event.request.querystring) {
|
|
11
|
-
query += `${index ? "&" : "?"}${key}=${event.request.querystring[key].value}`;
|
|
12
|
-
index++;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
response = {
|
|
16
|
-
statusCode: 301,
|
|
17
|
-
statusDescription: "Moved Permanently",
|
|
18
|
-
headers: {
|
|
19
|
-
location: {
|
|
20
|
-
value: `https://${event.request.headers.host.value.split("www.")[1]}${event.request.uri}${query}`,
|
|
21
|
-
},
|
|
22
|
-
"strict-transport-security": {
|
|
23
|
-
value: "max-age=31536000; includeSubDomains",
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
return response;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Redirect if no trailing slash
|
|
32
|
-
if (!event.request.uri.includes(".") && !event.request.uri.endsWith("/")) {
|
|
33
|
-
query = "";
|
|
34
|
-
index = 0;
|
|
35
|
-
|
|
36
|
-
for (key in event.request.querystring) {
|
|
37
|
-
query += `${index ? "&" : "?"}${key}=${event.request.querystring[key].value}`;
|
|
38
|
-
index++;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
statusCode: 301,
|
|
43
|
-
statusDescription: "Moved Permanently",
|
|
44
|
-
headers: { location: { value: `${event.request.uri}/${query}` } },
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
var authHeaders = event.request.headers.authorization;
|
|
49
|
-
|
|
50
|
-
// The Base64-encoded Auth string that should be present.
|
|
51
|
-
// It is an encoding of `Basic base64([username]:[password])`
|
|
52
|
-
var expected = "<%= credentials %>";
|
|
53
|
-
|
|
54
|
-
// If an Authorization header is supplied and it's an exact match, pass the
|
|
55
|
-
// request on through to CF/the origin without any modification.
|
|
56
|
-
if (authHeaders && expected.find((d) => d === authHeaders.value)) {
|
|
57
|
-
// Rewrite url to append index.html if not present
|
|
58
|
-
var request = event.request;
|
|
59
|
-
var uri = request.uri;
|
|
60
|
-
|
|
61
|
-
// Check whether the URI is missing a file name.
|
|
62
|
-
if (uri.endsWith("/")) {
|
|
63
|
-
request.uri += "index.html";
|
|
64
|
-
}
|
|
65
|
-
// Check whether the URI is missing a file extension.
|
|
66
|
-
else if (!uri.includes(".")) {
|
|
67
|
-
request.uri += "/index.html";
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return request;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// But if we get here, we must either be missing the auth header or the
|
|
74
|
-
// credentials failed to match what we expected.
|
|
75
|
-
// Request the browser present the Basic Auth dialog.
|
|
76
|
-
response = {
|
|
77
|
-
statusCode: 401,
|
|
78
|
-
statusDescription: "Unauthorized",
|
|
79
|
-
headers: {
|
|
80
|
-
"www-authenticate": { value: "Basic" },
|
|
81
|
-
"strict-transport-security": {
|
|
82
|
-
value: "max-age=31536000; includeSubDomains",
|
|
83
|
-
},
|
|
84
|
-
"content-security-policy": {
|
|
85
|
-
value: "default-src 'self' https: data: 'unsafe-inline';",
|
|
86
|
-
},
|
|
87
|
-
"x-content-type-options": { value: "nosniff" },
|
|
88
|
-
"x-frame-options": { value: "sameorigin" },
|
|
89
|
-
},
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
return response;
|
|
93
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
// eslint-disable-next-line no-unused-vars
|
|
2
|
-
function handler(event) {
|
|
3
|
-
var query, index, key, response;
|
|
4
|
-
|
|
5
|
-
// Redirect if www to non-www
|
|
6
|
-
if (event.request.headers.host.value.includes("www.")) {
|
|
7
|
-
query = "";
|
|
8
|
-
index = 0;
|
|
9
|
-
|
|
10
|
-
for (key in event.request.querystring) {
|
|
11
|
-
query += `${index ? "&" : "?"}${key}=${event.request.querystring[key].value}`;
|
|
12
|
-
index++;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
response = {
|
|
16
|
-
statusCode: 301,
|
|
17
|
-
statusDescription: "Moved Permanently",
|
|
18
|
-
headers: {
|
|
19
|
-
location: {
|
|
20
|
-
value: `https://${event.request.headers.host.value.split("www.")[1]}${event.request.uri}${query}`,
|
|
21
|
-
},
|
|
22
|
-
"strict-transport-security": {
|
|
23
|
-
value: "max-age=31536000; includeSubDomains",
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
return response;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Redirect if no trailing slash
|
|
32
|
-
if (!event.request.uri.includes(".") && !event.request.uri.endsWith("/")) {
|
|
33
|
-
query = "";
|
|
34
|
-
index = 0;
|
|
35
|
-
|
|
36
|
-
for (key in event.request.querystring) {
|
|
37
|
-
query += `${index ? "&" : "?"}${key}=${event.request.querystring[key].value}`;
|
|
38
|
-
index++;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
statusCode: 301,
|
|
43
|
-
statusDescription: "Moved Permanently",
|
|
44
|
-
headers: { location: { value: `${event.request.uri}/${query}` } },
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
var request = event.request;
|
|
49
|
-
var uri = request.uri;
|
|
50
|
-
|
|
51
|
-
// Check whether the URI is missing a file name.
|
|
52
|
-
if (uri.endsWith("/")) {
|
|
53
|
-
request.uri += "index.html";
|
|
54
|
-
}
|
|
55
|
-
// Check whether the URI is missing a file extension.
|
|
56
|
-
else if (!uri.includes(".")) {
|
|
57
|
-
request.uri += "/index.html";
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return request;
|
|
61
|
-
}
|