@fishawack/lab-env 5.7.0-beta.3 → 5.7.0-beta.5

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 CHANGED
@@ -1,5 +1,22 @@
1
1
  ## Changelog
2
2
 
3
+ ### 5.7.0-beta.5 (2026-07-03)
4
+
5
+ #### Bug Fixes
6
+
7
+ * allow rekey outside of projects ([94b511b](https://bitbucket.org/fishawackdigital/lab-env/commits/94b511b629fbf80b842a9ab5db9f3671f1f603f0))
8
+ * retry on rate limits and aws errors on key commands ([0244221](https://bitbucket.org/fishawackdigital/lab-env/commits/0244221b8a263256f015380280024c862213b885))
9
+ * safe check for misc bitbucket vars ([cee035c](https://bitbucket.org/fishawackdigital/lab-env/commits/cee035c25f927d1b5f55c00f10fa35f08733e4bf))
10
+
11
+ ### 5.7.0-beta.4 (2026-07-03)
12
+
13
+ #### Bug Fixes
14
+
15
+ * added missing ses permission for fullstack roles ([9da2562](https://bitbucket.org/fishawackdigital/lab-env/commits/9da256264ef3ac923bcf4acf0b531e28c3aa154f))
16
+ * correctly tag iam ec2 role for eb with tags ([204aced](https://bitbucket.org/fishawackdigital/lab-env/commits/204acedd6f63af0b6c72875b12b6e97660dfa6a3))
17
+ * safe check the fail property on scans as sometimes the file doesnt exist ([e5b5e39](https://bitbucket.org/fishawackdigital/lab-env/commits/e5b5e39251e6c1c22ec7f9cf155ad5c86492e9cb))
18
+ * session domain now the domain given exactly ([8071c91](https://bitbucket.org/fishawackdigital/lab-env/commits/8071c911a54c869661469fe2fdcdcc3ffc10cb2b))
19
+
3
20
  ### 5.7.0-beta.3 (2026-07-01)
4
21
 
5
22
  #### Features
@@ -3,6 +3,33 @@ const chalk = require("chalk");
3
3
  const { Spinner } = require("./utilities");
4
4
 
5
5
  const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
6
+ const MAX_RETRIES = 15;
7
+ const INITIAL_DELAY = 2000;
8
+ const MAX_DELAY = 60000;
9
+
10
+ const RETRYABLE_ERRORS = [
11
+ "Throttling",
12
+ "ThrottlingException",
13
+ "TooManyRequestsException",
14
+ "RequestLimitExceeded",
15
+ "ServiceUnavailable",
16
+ "InternalFailure",
17
+ "RequestTimeout",
18
+ "RequestTimeoutException",
19
+ ];
20
+
21
+ function isRetryable(err) {
22
+ if (
23
+ RETRYABLE_ERRORS.includes(err.name) ||
24
+ RETRYABLE_ERRORS.includes(err.Code)
25
+ ) {
26
+ return true;
27
+ }
28
+
29
+ const statusCode = err.$metadata?.httpStatusCode;
30
+
31
+ return statusCode === 429 || statusCode === 503 || statusCode === 500;
32
+ }
6
33
 
7
34
  function formatElapsed(ms) {
8
35
  const secs = Math.floor(ms / 1000);
@@ -78,7 +105,20 @@ module.exports = async function parallelRunner(
78
105
  continue;
79
106
  }
80
107
 
81
- if (state.status === "active") {
108
+ if (state.status === "retrying") {
109
+ const remaining = Math.max(
110
+ 0,
111
+ Math.ceil(
112
+ (state.retryDelay - (Date.now() - state.retryStart)) /
113
+ 1000,
114
+ ),
115
+ );
116
+ lines.push(
117
+ chalk.yellow(
118
+ ` ↻ ${verb}: ${user} - ${state.client} (${state.batchesDone + 1}/${totalBatches}) retry ${state.retryAttempt}/${MAX_RETRIES} ${remaining}s`,
119
+ ),
120
+ );
121
+ } else if (state.status === "active") {
82
122
  const taskElapsed = Date.now() - state.taskStart;
83
123
  let suffix = "";
84
124
  if (taskElapsed > 5000) {
@@ -130,24 +170,58 @@ module.exports = async function parallelRunner(
130
170
  const groupUsers = group.filter((u) => batch.users.includes(u));
131
171
 
132
172
  await Promise.all(
133
- groupUsers.map(async (user) => {
173
+ groupUsers.map(async (user, userIndex) => {
134
174
  const state = groupStates.get(user);
135
175
  state.client = batch.client;
136
- state.status = "active";
137
176
  state.taskStart = Date.now();
138
177
 
139
- try {
140
- const res = await batch.fn(user);
141
- results.get(user).push({ client: batch.client, res });
142
- state.status = "done";
143
- } catch (err) {
144
- state.status = "error";
145
- state.hasError = true;
146
- state.errorMsg = err.message || String(err);
147
- errors.push({ user, client: batch.client, error: err });
148
- } finally {
149
- state.batchesDone++;
178
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
179
+ state.status = "active";
180
+ state.taskStart = Date.now();
181
+
182
+ try {
183
+ const res = await batch.fn(user);
184
+ results
185
+ .get(user)
186
+ .push({ client: batch.client, res });
187
+ state.status = "done";
188
+ state.retryAttempt = 0;
189
+ break;
190
+ } catch (err) {
191
+ if (!isRetryable(err) || attempt === MAX_RETRIES) {
192
+ state.status = "error";
193
+ state.hasError = true;
194
+ state.errorMsg = err.message || String(err);
195
+ errors.push({
196
+ user,
197
+ client: batch.client,
198
+ error: err,
199
+ });
200
+ break;
201
+ }
202
+
203
+ let delay = Math.min(
204
+ INITIAL_DELAY * Math.pow(2, attempt),
205
+ MAX_DELAY,
206
+ );
207
+ // Stagger retries per user to avoid thundering herd
208
+ delay +=
209
+ Math.floor(Math.random() * delay * 0.3) +
210
+ userIndex * 1000;
211
+
212
+ state.status = "retrying";
213
+ state.retryAttempt = attempt + 1;
214
+ state.retryDelay = delay;
215
+ state.retryStart = Date.now();
216
+ throttleCount++;
217
+
218
+ await new Promise((resolve) =>
219
+ setTimeout(resolve, delay),
220
+ );
221
+ }
150
222
  }
223
+
224
+ state.batchesDone++;
151
225
  }),
152
226
  );
153
227
  }
@@ -68,17 +68,17 @@ const bitbucketApi = (module.exports.bitbucketApi =
68
68
  "https://api.bitbucket.org/2.0/repositories");
69
69
 
70
70
  module.exports.apis = {
71
- bbWorkspaceAPI: `${bitbucketApi}/${misc.bitbucket.workspace}`,
71
+ bbWorkspaceAPI: `${bitbucketApi}/${misc?.bitbucket?.workspace}`,
72
72
  };
73
73
 
74
74
  // URLs
75
75
  module.exports.urls = {
76
- bitbucketSSH: `git@bitbucket.org:${misc.bitbucket.workspace}`,
76
+ bitbucketSSH: `git@bitbucket.org:${misc?.bitbucket?.workspace}`,
77
77
  };
78
78
 
79
79
  // Request Headers
80
80
  module.exports.headers = {
81
- bbHeaders: encode(misc.bitbucket.username, misc.bitbucket.token),
81
+ bbHeaders: encode(misc?.bitbucket?.username, misc?.bitbucket?.token),
82
82
  };
83
83
 
84
84
  // Required global node modules
@@ -170,6 +170,7 @@ module.exports.syncFWIAMPolicies = async (
170
170
  "arn:aws:iam::aws:policy/AmazonRDSFullAccess",
171
171
  "arn:aws:iam::aws:policy/AmazonElastiCacheFullAccess",
172
172
  "arn:aws:iam::aws:policy/SecretsManagerReadWrite",
173
+ "arn:aws:iam::aws:policy/AmazonSESFullAccess",
173
174
  );
174
175
  }
175
176
 
@@ -356,12 +357,20 @@ module.exports.getRole = async (RoleName) => {
356
357
  return res;
357
358
  };
358
359
 
359
- module.exports.createRole = async (RoleName, AssumeRolePolicyDocument) => {
360
+ module.exports.createRole = async (
361
+ RoleName,
362
+ AssumeRolePolicyDocument,
363
+ Tags,
364
+ ) => {
360
365
  let res = await Spinner.prototype.simple(
361
366
  `Creating the role ${RoleName}`,
362
367
  () => {
363
368
  return getClient().send(
364
- new CreateRoleCommand({ RoleName, AssumeRolePolicyDocument }),
369
+ new CreateRoleCommand({
370
+ RoleName,
371
+ AssumeRolePolicyDocument,
372
+ ...(Tags && Tags.length ? { Tags } : {}),
373
+ }),
365
374
  );
366
375
  },
367
376
  );
@@ -369,12 +378,15 @@ module.exports.createRole = async (RoleName, AssumeRolePolicyDocument) => {
369
378
  return res;
370
379
  };
371
380
 
372
- module.exports.createInstanceProfile = async (InstanceProfileName) => {
381
+ module.exports.createInstanceProfile = async (InstanceProfileName, Tags) => {
373
382
  let res = await Spinner.prototype.simple(
374
383
  `Creating the instance profile ${InstanceProfileName}`,
375
384
  () => {
376
385
  return getClient().send(
377
- new CreateInstanceProfileCommand({ InstanceProfileName }),
386
+ new CreateInstanceProfileCommand({
387
+ InstanceProfileName,
388
+ ...(Tags && Tags.length ? { Tags } : {}),
389
+ }),
378
390
  );
379
391
  },
380
392
  );
@@ -588,6 +600,7 @@ module.exports.ensureEnvironmentInstanceProfile = async (
588
600
  ebSlug,
589
601
  s3BucketName,
590
602
  opensearchDomainArn,
603
+ tags,
591
604
  ) => {
592
605
  const role = `${ebSlug}-role`;
593
606
 
@@ -608,9 +621,10 @@ module.exports.ensureEnvironmentInstanceProfile = async (
608
621
  },
609
622
  ],
610
623
  }),
624
+ tags,
611
625
  );
612
626
 
613
- await module.exports.createInstanceProfile(role);
627
+ await module.exports.createInstanceProfile(role, tags);
614
628
  await module.exports.attachRoleToInstanceProfile(role, role);
615
629
  }
616
630
 
@@ -814,6 +814,7 @@ module.exports.fullstack = async (
814
814
  name,
815
815
  s3Slug,
816
816
  opensearchDomainArn,
817
+ tags,
817
818
  );
818
819
  } else {
819
820
  await aws.iam.ensureEBInstanceProfileExists();
@@ -960,11 +961,7 @@ module.exports.fullstack = async (
960
961
  ...mailCreds,
961
962
  APP_NAME: appName,
962
963
  APP_DOMAIN: appDomain,
963
- SESSION_DOMAIN: appDomain
964
- ? appDomain.split(".").length > 2
965
- ? appDomain.split(".").slice(1).join(".")
966
- : appDomain
967
- : undefined,
964
+ SESSION_DOMAIN: appDomain || undefined,
968
965
  },
969
966
  );
970
967
 
@@ -1099,11 +1096,7 @@ module.exports.fullstack = async (
1099
1096
  ...cacheData,
1100
1097
  APP_NAME: appName,
1101
1098
  APP_DOMAIN: appDomain,
1102
- SESSION_DOMAIN: appDomain
1103
- ? appDomain.split(".").length > 2
1104
- ? appDomain.split(".").slice(1).join(".")
1105
- : appDomain
1106
- : undefined,
1099
+ SESSION_DOMAIN: appDomain || undefined,
1107
1100
  },
1108
1101
  );
1109
1102
 
package/commands/scan.js CHANGED
@@ -32,7 +32,7 @@ const run = () => {
32
32
  });
33
33
 
34
34
  if (failed) {
35
- if (_.coreConfig.attributes.failOnScanErrors) {
35
+ if (_.coreConfig?.attributes?.failOnScanErrors) {
36
36
  throw new Error(
37
37
  "Scan found issues. Failing the build as failOnScanErrors flag is set to true.",
38
38
  );
package/globals.js CHANGED
@@ -279,6 +279,7 @@ if (composer && composer.require && composer.require["laravel/framework"]) {
279
279
  args[0] !== "new" &&
280
280
  args[0] !== "key" &&
281
281
  args[0] !== "dekey" &&
282
+ args[0] !== "rekey" &&
282
283
  args[0] !== "lint" &&
283
284
  args[0] !== "workspace" &&
284
285
  args[0] !== "prune"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fishawack/lab-env",
3
- "version": "5.7.0-beta.3",
3
+ "version": "5.7.0-beta.5",
4
4
  "description": "Docker manager for FW",
5
5
  "main": "cli.js",
6
6
  "scripts": {