skuba 12.1.0-hoist-less-20250722131939 → 12.1.0-main-20250812041011

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.
Files changed (81) hide show
  1. package/README.md +1 -2
  2. package/config/tsconfig.json +3 -2
  3. package/lib/cli/build/assets.js +1 -1
  4. package/lib/cli/build/assets.js.map +2 -2
  5. package/lib/cli/build/tsc.d.ts +5 -1
  6. package/lib/cli/build/tsc.js +12 -0
  7. package/lib/cli/build/tsc.js.map +3 -3
  8. package/lib/cli/init/getConfig.js +1 -1
  9. package/lib/cli/init/getConfig.js.map +2 -2
  10. package/lib/cli/lint/internal.js +1 -1
  11. package/lib/cli/lint/internal.js.map +2 -2
  12. package/lib/cli/lint/internalLints/upgrade/patches/12.0.2/index.d.ts +2 -0
  13. package/lib/cli/lint/internalLints/upgrade/patches/12.0.2/index.js +35 -0
  14. package/lib/cli/lint/internalLints/upgrade/patches/12.0.2/index.js.map +7 -0
  15. package/lib/cli/lint/internalLints/upgrade/patches/12.0.2/unhandledRejections.d.ts +4 -0
  16. package/lib/cli/lint/internalLints/upgrade/patches/12.0.2/unhandledRejections.js +162 -0
  17. package/lib/cli/lint/internalLints/upgrade/patches/12.0.2/unhandledRejections.js.map +7 -0
  18. package/lib/cli/lint/internalLints/upgrade/patches/8.2.1/upgradeESLint.js +1 -1
  19. package/lib/cli/lint/internalLints/upgrade/patches/8.2.1/upgradeESLint.js.map +2 -2
  20. package/lib/cli/node/index.js +8 -2
  21. package/lib/cli/node/index.js.map +2 -2
  22. package/lib/cli/start/index.js +8 -2
  23. package/lib/cli/start/index.js.map +2 -2
  24. package/lib/cli/test/index.d.ts +1 -1
  25. package/lib/cli/test/index.js +18 -4
  26. package/lib/cli/test/index.js.map +2 -2
  27. package/lib/utils/args.d.ts +2 -0
  28. package/lib/utils/args.js +5 -0
  29. package/lib/utils/args.js.map +2 -2
  30. package/package.json +16 -17
  31. package/template/base/_pnpm-workspace.yaml +1 -0
  32. package/template/base/jest.setup.ts +1 -1
  33. package/template/express-rest-api/.buildkite/pipeline.yml +6 -0
  34. package/template/express-rest-api/.env +1 -1
  35. package/template/express-rest-api/.gantry/dev.yml +5 -1
  36. package/template/express-rest-api/.gantry/prod.yml +5 -1
  37. package/template/express-rest-api/Dockerfile +1 -1
  38. package/template/express-rest-api/README.md +5 -5
  39. package/template/express-rest-api/gantry.apply.yml +17 -1
  40. package/template/express-rest-api/package.json +11 -5
  41. package/template/express-rest-api/src/api/healthCheck.ts +2 -2
  42. package/template/express-rest-api/src/config.ts +7 -7
  43. package/template/express-rest-api/src/framework/logging.ts +11 -7
  44. package/template/express-rest-api/src/framework/metrics.ts +1 -1
  45. package/template/express-rest-api/src/listen.ts +6 -0
  46. package/template/express-rest-api/src/tracing.ts +56 -0
  47. package/template/greeter/README.md +2 -2
  48. package/template/greeter/package.json +2 -2
  49. package/template/koa-rest-api/.buildkite/pipeline.yml +6 -0
  50. package/template/koa-rest-api/.env +1 -1
  51. package/template/koa-rest-api/.gantry/dev.yml +3 -3
  52. package/template/koa-rest-api/.gantry/prod.yml +3 -3
  53. package/template/koa-rest-api/README.md +6 -6
  54. package/template/koa-rest-api/gantry.apply.yml +15 -3
  55. package/template/koa-rest-api/package.json +9 -10
  56. package/template/koa-rest-api/src/api/healthCheck.ts +2 -2
  57. package/template/koa-rest-api/src/config.ts +7 -7
  58. package/template/koa-rest-api/src/framework/logging.ts +12 -8
  59. package/template/koa-rest-api/src/framework/metrics.ts +1 -1
  60. package/template/koa-rest-api/src/framework/server.test.ts +7 -8
  61. package/template/koa-rest-api/src/framework/server.ts +1 -4
  62. package/template/koa-rest-api/src/listen.ts +6 -0
  63. package/template/lambda-sqs-worker-cdk/.buildkite/pipeline.yml +6 -2
  64. package/template/lambda-sqs-worker-cdk/.env +1 -1
  65. package/template/lambda-sqs-worker-cdk/README.md +8 -8
  66. package/template/lambda-sqs-worker-cdk/infra/__snapshots__/appStack.test.ts.snap +50 -10
  67. package/template/lambda-sqs-worker-cdk/infra/appStack.test.ts +5 -8
  68. package/template/lambda-sqs-worker-cdk/infra/appStack.ts +15 -5
  69. package/template/lambda-sqs-worker-cdk/infra/config.ts +30 -18
  70. package/template/lambda-sqs-worker-cdk/infra/index.ts +1 -1
  71. package/template/lambda-sqs-worker-cdk/package.json +7 -7
  72. package/template/lambda-sqs-worker-cdk/src/app.test.ts +91 -51
  73. package/template/lambda-sqs-worker-cdk/src/app.ts +7 -9
  74. package/template/lambda-sqs-worker-cdk/src/config.ts +11 -16
  75. package/template/lambda-sqs-worker-cdk/src/framework/handler.test.ts +10 -5
  76. package/template/lambda-sqs-worker-cdk/src/framework/handler.ts +44 -24
  77. package/template/lambda-sqs-worker-cdk/src/framework/logging.ts +23 -11
  78. package/template/lambda-sqs-worker-cdk/src/framework/metrics.ts +1 -4
  79. package/template/lambda-sqs-worker-cdk/src/testing/handler.ts +4 -1
  80. package/template/oss-npm-package/.github/workflows/release.yml +1 -1
  81. package/template/oss-npm-package/.github/workflows/validate.yml +1 -1
@@ -37,11 +37,16 @@ var import_args = require("../../utils/args.js");
37
37
  var import_exec = require("../../utils/exec.js");
38
38
  var import_manifest = require("../../utils/manifest.js");
39
39
  var import_validation = require("../../utils/validation.js");
40
+ var import_tsc = require("../build/tsc.js");
40
41
  const start = async () => {
42
+ const customConditions = (0, import_tsc.getCustomConditions)();
41
43
  const [args, availablePort] = await Promise.all([
42
44
  (0, import_args.parseRunArgs)(process.argv.slice(2)),
43
45
  (0, import_get_port.default)()
44
46
  ]);
47
+ const uniqueConditions = [
48
+ .../* @__PURE__ */ new Set([...args.conditions ?? [], ...customConditions])
49
+ ];
45
50
  args.entryPoint ??= await (0, import_manifest.getEntryPointFromManifest)();
46
51
  const execProcess = (0, import_exec.createExec)({
47
52
  env: {
@@ -54,8 +59,9 @@ const start = async () => {
54
59
  "watch",
55
60
  "--clear-screen=false",
56
61
  ...args.node,
57
- "--require",
58
- "dotenv/config",
62
+ ...uniqueConditions.map((condition) => `--conditions=${condition}`),
63
+ "--env-file-if-exists",
64
+ ".env",
59
65
  "--require",
60
66
  "tsconfig-paths/register",
61
67
  import_path.default.join(__dirname, "..", "..", "wrapper", "index.js"),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/cli/start/index.ts"],
4
- "sourcesContent": ["import path from 'path';\n\nimport getPort from 'get-port';\n\nimport { parseRunArgs } from '../../utils/args.js';\nimport { createExec } from '../../utils/exec.js';\nimport { getEntryPointFromManifest } from '../../utils/manifest.js';\nimport { isIpPort } from '../../utils/validation.js';\n\nexport const start = async () => {\n const [args, availablePort] = await Promise.all([\n parseRunArgs(process.argv.slice(2)),\n getPort(),\n ]);\n\n args.entryPoint ??= await getEntryPointFromManifest();\n\n const execProcess = createExec({\n env: {\n __SKUBA_ENTRY_POINT: args.entryPoint,\n __SKUBA_PORT: String(isIpPort(args.port) ? args.port : availablePort),\n },\n });\n\n return execProcess(\n 'tsx',\n 'watch',\n '--clear-screen=false',\n ...args.node,\n '--require',\n 'dotenv/config',\n '--require',\n 'tsconfig-paths/register',\n path.join(__dirname, '..', '..', 'wrapper', 'index.js'),\n ...args.script,\n );\n};\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAAiB;AAEjB,sBAAoB;AAEpB,kBAA6B;AAC7B,kBAA2B;AAC3B,sBAA0C;AAC1C,wBAAyB;AAElB,MAAM,QAAQ,YAAY;AAC/B,QAAM,CAAC,MAAM,aAAa,IAAI,MAAM,QAAQ,IAAI;AAAA,QAC9C,0BAAa,QAAQ,KAAK,MAAM,CAAC,CAAC;AAAA,QAClC,gBAAAA,SAAQ;AAAA,EACV,CAAC;AAED,OAAK,eAAe,UAAM,2CAA0B;AAEpD,QAAM,kBAAc,wBAAW;AAAA,IAC7B,KAAK;AAAA,MACH,qBAAqB,KAAK;AAAA,MAC1B,cAAc,WAAO,4BAAS,KAAK,IAAI,IAAI,KAAK,OAAO,aAAa;AAAA,IACtE;AAAA,EACF,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG,KAAK;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAAC,QAAK,KAAK,WAAW,MAAM,MAAM,WAAW,UAAU;AAAA,IACtD,GAAG,KAAK;AAAA,EACV;AACF;",
4
+ "sourcesContent": ["import path from 'path';\n\nimport getPort from 'get-port';\n\nimport { parseRunArgs } from '../../utils/args.js';\nimport { createExec } from '../../utils/exec.js';\nimport { getEntryPointFromManifest } from '../../utils/manifest.js';\nimport { isIpPort } from '../../utils/validation.js';\nimport { getCustomConditions } from '../build/tsc.js';\n\nexport const start = async () => {\n const customConditions = getCustomConditions();\n const [args, availablePort] = await Promise.all([\n parseRunArgs(process.argv.slice(2)),\n getPort(),\n ]);\n\n const uniqueConditions = [\n ...new Set([...(args.conditions ?? []), ...customConditions]),\n ];\n\n args.entryPoint ??= await getEntryPointFromManifest();\n\n const execProcess = createExec({\n env: {\n __SKUBA_ENTRY_POINT: args.entryPoint,\n __SKUBA_PORT: String(isIpPort(args.port) ? args.port : availablePort),\n },\n });\n\n return execProcess(\n 'tsx',\n 'watch',\n '--clear-screen=false',\n ...args.node,\n ...uniqueConditions.map((condition) => `--conditions=${condition}`),\n '--env-file-if-exists',\n '.env',\n '--require',\n 'tsconfig-paths/register',\n path.join(__dirname, '..', '..', 'wrapper', 'index.js'),\n ...args.script,\n );\n};\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAAiB;AAEjB,sBAAoB;AAEpB,kBAA6B;AAC7B,kBAA2B;AAC3B,sBAA0C;AAC1C,wBAAyB;AACzB,iBAAoC;AAE7B,MAAM,QAAQ,YAAY;AAC/B,QAAM,uBAAmB,gCAAoB;AAC7C,QAAM,CAAC,MAAM,aAAa,IAAI,MAAM,QAAQ,IAAI;AAAA,QAC9C,0BAAa,QAAQ,KAAK,MAAM,CAAC,CAAC;AAAA,QAClC,gBAAAA,SAAQ;AAAA,EACV,CAAC;AAED,QAAM,mBAAmB;AAAA,IACvB,GAAG,oBAAI,IAAI,CAAC,GAAI,KAAK,cAAc,CAAC,GAAI,GAAG,gBAAgB,CAAC;AAAA,EAC9D;AAEA,OAAK,eAAe,UAAM,2CAA0B;AAEpD,QAAM,kBAAc,wBAAW;AAAA,IAC7B,KAAK;AAAA,MACH,qBAAqB,KAAK;AAAA,MAC1B,cAAc,WAAO,4BAAS,KAAK,IAAI,IAAI,KAAK,OAAO,aAAa;AAAA,IACtE;AAAA,EACF,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG,KAAK;AAAA,IACR,GAAG,iBAAiB,IAAI,CAAC,cAAc,gBAAgB,SAAS,EAAE;AAAA,IAClE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAAC,QAAK,KAAK,WAAW,MAAM,MAAM,WAAW,UAAU;AAAA,IACtD,GAAG,KAAK;AAAA,EACV;AACF;",
6
6
  "names": ["getPort", "path"]
7
7
  }
@@ -1 +1 @@
1
- export declare const test: () => Promise<void>;
1
+ export declare const test: () => Promise<import("execa").ExecaReturnValue<string>>;
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,18 +17,30 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
  var test_exports = {};
20
30
  __export(test_exports, {
21
31
  test: () => test
22
32
  });
23
33
  module.exports = __toCommonJS(test_exports);
24
- var import_jest = require("jest");
34
+ var import_exec = require("../../utils/exec.js");
25
35
  const test = async () => {
26
- process.env.NODE_ENV ??= "test";
27
- process.env.TS_JEST_LOG ??= "stdout:error";
28
36
  const argv = process.argv.slice(2);
29
- return (0, import_jest.run)(argv);
37
+ const nodeOptions = process.env.NODE_OPTIONS ?? "";
38
+ const execWithEnv = (0, import_exec.createExec)({
39
+ env: {
40
+ NODE_OPTIONS: !nodeOptions.includes("--experimental-vm-modules") ? `${nodeOptions} --experimental-vm-modules --no-warnings=ExperimentalWarning` : nodeOptions
41
+ }
42
+ });
43
+ return execWithEnv(require.resolve("jest/bin/jest"), ...argv);
30
44
  };
31
45
  // Annotate the CommonJS export names for ESM import in node:
32
46
  0 && (module.exports = {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/cli/test/index.ts"],
4
- "sourcesContent": ["import { run } from 'jest';\n\nexport const test = async () => {\n // This is usually set in `jest-cli`'s binary wrapper\n process.env.NODE_ENV ??= 'test';\n\n // ts-jest is logging a warning about `isolatedModules`.\n // This is a workaround until we can remove the `isolatedModules` option.\n // https://github.com/seek-oss/skuba/issues/1841\n process.env.TS_JEST_LOG ??= 'stdout:error';\n\n const argv = process.argv.slice(2);\n\n return run(argv);\n};\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAAoB;AAEb,MAAM,OAAO,YAAY;AAE9B,UAAQ,IAAI,aAAa;AAKzB,UAAQ,IAAI,gBAAgB;AAE5B,QAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AAEjC,aAAO,iBAAI,IAAI;AACjB;",
4
+ "sourcesContent": ["import { createExec } from '../../utils/exec.js';\n\nexport const test = async () => {\n const argv = process.argv.slice(2);\n\n const nodeOptions = process.env.NODE_OPTIONS ?? '';\n\n const execWithEnv = createExec({\n env: {\n NODE_OPTIONS: !nodeOptions.includes('--experimental-vm-modules')\n ? `${nodeOptions} --experimental-vm-modules --no-warnings=ExperimentalWarning`\n : nodeOptions,\n },\n });\n\n // Run Jest in a child process with proper environment\n return execWithEnv(require.resolve('jest/bin/jest'), ...argv);\n};\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAA2B;AAEpB,MAAM,OAAO,YAAY;AAC9B,QAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AAEjC,QAAM,cAAc,QAAQ,IAAI,gBAAgB;AAEhD,QAAM,kBAAc,wBAAW;AAAA,IAC7B,KAAK;AAAA,MACH,cAAc,CAAC,YAAY,SAAS,2BAA2B,IAC3D,GAAG,WAAW,iEACd;AAAA,IACN;AAAA,EACF,CAAC;AAGD,SAAO,YAAY,gBAAgB,eAAe,GAAG,GAAG,IAAI;AAC9D;",
6
6
  "names": []
7
7
  }
@@ -18,6 +18,8 @@ export declare const parseProcessArgs: (args?: string[]) => {
18
18
  args: string[];
19
19
  };
20
20
  interface RunArgs {
21
+ /** The conditions to pass to the entry point script. */
22
+ conditions?: string[];
21
23
  /** The path to the entry point script. */
22
24
  entryPoint?: string;
23
25
  /** The port to start an HTTP server on. */
package/lib/utils/args.js CHANGED
@@ -100,6 +100,11 @@ const parseRunArgsIteration = (state, args) => {
100
100
  }
101
101
  return args.slice(1);
102
102
  }
103
+ if (arg1.startsWith("--conditions=")) {
104
+ state.conditions ??= [];
105
+ state.conditions.push(arg1.slice(13));
106
+ return args.slice(1);
107
+ }
103
108
  state.entryPoint = arg1;
104
109
  state.script.push(...args.slice(1));
105
110
  return [];
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/utils/args.ts"],
4
- "sourcesContent": ["import assert from 'assert';\n\nimport { COMMAND_ALIASES } from './command.js';\n\nexport const hasDebugFlag = (args = process.argv) =>\n args.some((arg) => arg.toLocaleLowerCase() === '--debug');\n\nexport const hasSerialFlag = (args = process.argv, env = process.env) =>\n args.some((arg) => arg.toLocaleLowerCase() === '--serial') ||\n Boolean(\n // Run serially on SEEK's central npm publishing pipeline.\n // Exhausting agents here can cause grief.\n env.BUILDKITE_AGENT_META_DATA_QUEUE?.split(',').some((queueName) =>\n queueName.startsWith('artefacts:npm'),\n ),\n );\n\n/**\n * Parse process arguments.\n *\n * This function mutates the input list by removing the command name element.\n * Downstream commands can access their args with an ordinary\n * `process.argv.slice(2)`.\n *\n * Example input:\n *\n * ```typescript\n * ['/bin/node', 'node_modules/.bin/skuba', 'test', '--foo', '--bar']\n * ```\n */\nexport const parseProcessArgs = (args = process.argv) => {\n const skubaIdx = args.findIndex((chunk) => /skuba(\\.[jt]s)?$/.test(chunk));\n\n assert(skubaIdx >= 0, 'Cannot parse args for `skuba`');\n\n const rawCommand = (args[skubaIdx + 1] ?? 'help').toLocaleLowerCase();\n\n const commandName = COMMAND_ALIASES[rawCommand] ?? rawCommand;\n\n const payload = {\n commandName,\n args: args.slice(skubaIdx + 2),\n };\n\n args.splice(skubaIdx + 1, 1);\n\n return payload;\n};\n\ninterface RunArgs {\n /** The path to the entry point script. */\n entryPoint?: string;\n\n /** The port to start an HTTP server on. */\n port?: number;\n\n /** Arguments passed through to the Node.js executable. */\n node: string[];\n\n /** Arguments passed through to the entry point script. */\n script: string[];\n}\n\n/**\n * Make a best effort to parse \"run\" args.\n *\n * These are arguments that would be passed to `skuba node` or `skuba start`.\n * Parsing is handrolled because we support some weird and wonderful behaviour:\n *\n * - The `--port` option may be intended for skuba itself\n * - The `--inspect` options may be intended for the Node.js inspector\n * - The entry point may be omitted in favour of `package.json` inference\n * - Other args may be intended for propagation to the entry point\n */\nexport const parseRunArgs = (argv: string[]): RunArgs => {\n const state: RunArgs = {\n node: [],\n script: [],\n };\n\n let args = argv.filter((element) => element.length);\n\n while (args.length) {\n args = parseRunArgsIteration(state, args);\n }\n\n return state;\n};\n\nconst isDigits = (arg: unknown): arg is string =>\n typeof arg === 'string' && /^\\d+$/.test(arg);\n\nconst parseRunArgsIteration = (state: RunArgs, args: string[]): string[] => {\n const [arg1, arg2] = args;\n\n if (!arg1) {\n return [];\n }\n\n if (/^--inspect(-brk)?=\\d+$/.test(arg1)) {\n state.node.push(arg1);\n return args.slice(1);\n }\n\n // Node.js inspector options that are optionally followed by a numeric port.\n if (['--inspect', '--inspect-brk'].includes(arg1)) {\n if (isDigits(arg2)) {\n state.node.push(`${arg1}=${arg2}`);\n return args.slice(2);\n }\n\n state.node.push(arg1);\n\n if (arg2) {\n // Some other string that doesn't relate to the Node.js inspector option.\n // This is presumably the entry point script to run.\n state.entryPoint = arg2;\n state.script.push(...args.slice(2));\n }\n\n return [];\n }\n\n if (/^--port=\\d+$/.test(arg1)) {\n state.port = Number(arg1.slice(7));\n return args.slice(1);\n }\n\n if (arg1 === '--port') {\n if (isDigits(arg2)) {\n state.port = Number(arg2);\n return args.slice(2);\n }\n\n // Invalid port argument; eat it.\n return args.slice(1);\n }\n\n state.entryPoint = arg1;\n state.script.push(...args.slice(1));\n return [];\n};\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAAmB;AAEnB,qBAAgC;AAEzB,MAAM,eAAe,CAAC,OAAO,QAAQ,SAC1C,KAAK,KAAK,CAAC,QAAQ,IAAI,kBAAkB,MAAM,SAAS;AAEnD,MAAM,gBAAgB,CAAC,OAAO,QAAQ,MAAM,MAAM,QAAQ,QAC/D,KAAK,KAAK,CAAC,QAAQ,IAAI,kBAAkB,MAAM,UAAU,KACzD;AAAA;AAAA;AAAA,EAGE,IAAI,iCAAiC,MAAM,GAAG,EAAE;AAAA,IAAK,CAAC,cACpD,UAAU,WAAW,eAAe;AAAA,EACtC;AACF;AAeK,MAAM,mBAAmB,CAAC,OAAO,QAAQ,SAAS;AACvD,QAAM,WAAW,KAAK,UAAU,CAAC,UAAU,mBAAmB,KAAK,KAAK,CAAC;AAEzE,oBAAAA,SAAO,YAAY,GAAG,+BAA+B;AAErD,QAAM,cAAc,KAAK,WAAW,CAAC,KAAK,QAAQ,kBAAkB;AAEpE,QAAM,cAAc,+BAAgB,UAAU,KAAK;AAEnD,QAAM,UAAU;AAAA,IACd;AAAA,IACA,MAAM,KAAK,MAAM,WAAW,CAAC;AAAA,EAC/B;AAEA,OAAK,OAAO,WAAW,GAAG,CAAC;AAE3B,SAAO;AACT;AA2BO,MAAM,eAAe,CAAC,SAA4B;AACvD,QAAM,QAAiB;AAAA,IACrB,MAAM,CAAC;AAAA,IACP,QAAQ,CAAC;AAAA,EACX;AAEA,MAAI,OAAO,KAAK,OAAO,CAAC,YAAY,QAAQ,MAAM;AAElD,SAAO,KAAK,QAAQ;AAClB,WAAO,sBAAsB,OAAO,IAAI;AAAA,EAC1C;AAEA,SAAO;AACT;AAEA,MAAM,WAAW,CAAC,QAChB,OAAO,QAAQ,YAAY,QAAQ,KAAK,GAAG;AAE7C,MAAM,wBAAwB,CAAC,OAAgB,SAA6B;AAC1E,QAAM,CAAC,MAAM,IAAI,IAAI;AAErB,MAAI,CAAC,MAAM;AACT,WAAO,CAAC;AAAA,EACV;AAEA,MAAI,yBAAyB,KAAK,IAAI,GAAG;AACvC,UAAM,KAAK,KAAK,IAAI;AACpB,WAAO,KAAK,MAAM,CAAC;AAAA,EACrB;AAGA,MAAI,CAAC,aAAa,eAAe,EAAE,SAAS,IAAI,GAAG;AACjD,QAAI,SAAS,IAAI,GAAG;AAClB,YAAM,KAAK,KAAK,GAAG,IAAI,IAAI,IAAI,EAAE;AACjC,aAAO,KAAK,MAAM,CAAC;AAAA,IACrB;AAEA,UAAM,KAAK,KAAK,IAAI;AAEpB,QAAI,MAAM;AAGR,YAAM,aAAa;AACnB,YAAM,OAAO,KAAK,GAAG,KAAK,MAAM,CAAC,CAAC;AAAA,IACpC;AAEA,WAAO,CAAC;AAAA,EACV;AAEA,MAAI,eAAe,KAAK,IAAI,GAAG;AAC7B,UAAM,OAAO,OAAO,KAAK,MAAM,CAAC,CAAC;AACjC,WAAO,KAAK,MAAM,CAAC;AAAA,EACrB;AAEA,MAAI,SAAS,UAAU;AACrB,QAAI,SAAS,IAAI,GAAG;AAClB,YAAM,OAAO,OAAO,IAAI;AACxB,aAAO,KAAK,MAAM,CAAC;AAAA,IACrB;AAGA,WAAO,KAAK,MAAM,CAAC;AAAA,EACrB;AAEA,QAAM,aAAa;AACnB,QAAM,OAAO,KAAK,GAAG,KAAK,MAAM,CAAC,CAAC;AAClC,SAAO,CAAC;AACV;",
4
+ "sourcesContent": ["import assert from 'assert';\n\nimport { COMMAND_ALIASES } from './command.js';\n\nexport const hasDebugFlag = (args = process.argv) =>\n args.some((arg) => arg.toLocaleLowerCase() === '--debug');\n\nexport const hasSerialFlag = (args = process.argv, env = process.env) =>\n args.some((arg) => arg.toLocaleLowerCase() === '--serial') ||\n Boolean(\n // Run serially on SEEK's central npm publishing pipeline.\n // Exhausting agents here can cause grief.\n env.BUILDKITE_AGENT_META_DATA_QUEUE?.split(',').some((queueName) =>\n queueName.startsWith('artefacts:npm'),\n ),\n );\n\n/**\n * Parse process arguments.\n *\n * This function mutates the input list by removing the command name element.\n * Downstream commands can access their args with an ordinary\n * `process.argv.slice(2)`.\n *\n * Example input:\n *\n * ```typescript\n * ['/bin/node', 'node_modules/.bin/skuba', 'test', '--foo', '--bar']\n * ```\n */\nexport const parseProcessArgs = (args = process.argv) => {\n const skubaIdx = args.findIndex((chunk) => /skuba(\\.[jt]s)?$/.test(chunk));\n\n assert(skubaIdx >= 0, 'Cannot parse args for `skuba`');\n\n const rawCommand = (args[skubaIdx + 1] ?? 'help').toLocaleLowerCase();\n\n const commandName = COMMAND_ALIASES[rawCommand] ?? rawCommand;\n\n const payload = {\n commandName,\n args: args.slice(skubaIdx + 2),\n };\n\n args.splice(skubaIdx + 1, 1);\n\n return payload;\n};\n\ninterface RunArgs {\n /** The conditions to pass to the entry point script. */\n conditions?: string[];\n\n /** The path to the entry point script. */\n entryPoint?: string;\n\n /** The port to start an HTTP server on. */\n port?: number;\n\n /** Arguments passed through to the Node.js executable. */\n node: string[];\n\n /** Arguments passed through to the entry point script. */\n script: string[];\n}\n\n/**\n * Make a best effort to parse \"run\" args.\n *\n * These are arguments that would be passed to `skuba node` or `skuba start`.\n * Parsing is handrolled because we support some weird and wonderful behaviour:\n *\n * - The `--port` option may be intended for skuba itself\n * - The `--inspect` options may be intended for the Node.js inspector\n * - The entry point may be omitted in favour of `package.json` inference\n * - Other args may be intended for propagation to the entry point\n */\nexport const parseRunArgs = (argv: string[]): RunArgs => {\n const state: RunArgs = {\n node: [],\n script: [],\n };\n\n let args = argv.filter((element) => element.length);\n\n while (args.length) {\n args = parseRunArgsIteration(state, args);\n }\n\n return state;\n};\n\nconst isDigits = (arg: unknown): arg is string =>\n typeof arg === 'string' && /^\\d+$/.test(arg);\n\nconst parseRunArgsIteration = (state: RunArgs, args: string[]): string[] => {\n const [arg1, arg2] = args;\n\n if (!arg1) {\n return [];\n }\n\n if (/^--inspect(-brk)?=\\d+$/.test(arg1)) {\n state.node.push(arg1);\n return args.slice(1);\n }\n\n // Node.js inspector options that are optionally followed by a numeric port.\n if (['--inspect', '--inspect-brk'].includes(arg1)) {\n if (isDigits(arg2)) {\n state.node.push(`${arg1}=${arg2}`);\n return args.slice(2);\n }\n\n state.node.push(arg1);\n\n if (arg2) {\n // Some other string that doesn't relate to the Node.js inspector option.\n // This is presumably the entry point script to run.\n state.entryPoint = arg2;\n state.script.push(...args.slice(2));\n }\n\n return [];\n }\n\n if (/^--port=\\d+$/.test(arg1)) {\n state.port = Number(arg1.slice(7));\n return args.slice(1);\n }\n\n if (arg1 === '--port') {\n if (isDigits(arg2)) {\n state.port = Number(arg2);\n return args.slice(2);\n }\n\n // Invalid port argument; eat it.\n return args.slice(1);\n }\n\n if (arg1.startsWith('--conditions=')) {\n state.conditions ??= [];\n state.conditions.push(arg1.slice(13));\n return args.slice(1);\n }\n\n state.entryPoint = arg1;\n state.script.push(...args.slice(1));\n return [];\n};\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAAmB;AAEnB,qBAAgC;AAEzB,MAAM,eAAe,CAAC,OAAO,QAAQ,SAC1C,KAAK,KAAK,CAAC,QAAQ,IAAI,kBAAkB,MAAM,SAAS;AAEnD,MAAM,gBAAgB,CAAC,OAAO,QAAQ,MAAM,MAAM,QAAQ,QAC/D,KAAK,KAAK,CAAC,QAAQ,IAAI,kBAAkB,MAAM,UAAU,KACzD;AAAA;AAAA;AAAA,EAGE,IAAI,iCAAiC,MAAM,GAAG,EAAE;AAAA,IAAK,CAAC,cACpD,UAAU,WAAW,eAAe;AAAA,EACtC;AACF;AAeK,MAAM,mBAAmB,CAAC,OAAO,QAAQ,SAAS;AACvD,QAAM,WAAW,KAAK,UAAU,CAAC,UAAU,mBAAmB,KAAK,KAAK,CAAC;AAEzE,oBAAAA,SAAO,YAAY,GAAG,+BAA+B;AAErD,QAAM,cAAc,KAAK,WAAW,CAAC,KAAK,QAAQ,kBAAkB;AAEpE,QAAM,cAAc,+BAAgB,UAAU,KAAK;AAEnD,QAAM,UAAU;AAAA,IACd;AAAA,IACA,MAAM,KAAK,MAAM,WAAW,CAAC;AAAA,EAC/B;AAEA,OAAK,OAAO,WAAW,GAAG,CAAC;AAE3B,SAAO;AACT;AA8BO,MAAM,eAAe,CAAC,SAA4B;AACvD,QAAM,QAAiB;AAAA,IACrB,MAAM,CAAC;AAAA,IACP,QAAQ,CAAC;AAAA,EACX;AAEA,MAAI,OAAO,KAAK,OAAO,CAAC,YAAY,QAAQ,MAAM;AAElD,SAAO,KAAK,QAAQ;AAClB,WAAO,sBAAsB,OAAO,IAAI;AAAA,EAC1C;AAEA,SAAO;AACT;AAEA,MAAM,WAAW,CAAC,QAChB,OAAO,QAAQ,YAAY,QAAQ,KAAK,GAAG;AAE7C,MAAM,wBAAwB,CAAC,OAAgB,SAA6B;AAC1E,QAAM,CAAC,MAAM,IAAI,IAAI;AAErB,MAAI,CAAC,MAAM;AACT,WAAO,CAAC;AAAA,EACV;AAEA,MAAI,yBAAyB,KAAK,IAAI,GAAG;AACvC,UAAM,KAAK,KAAK,IAAI;AACpB,WAAO,KAAK,MAAM,CAAC;AAAA,EACrB;AAGA,MAAI,CAAC,aAAa,eAAe,EAAE,SAAS,IAAI,GAAG;AACjD,QAAI,SAAS,IAAI,GAAG;AAClB,YAAM,KAAK,KAAK,GAAG,IAAI,IAAI,IAAI,EAAE;AACjC,aAAO,KAAK,MAAM,CAAC;AAAA,IACrB;AAEA,UAAM,KAAK,KAAK,IAAI;AAEpB,QAAI,MAAM;AAGR,YAAM,aAAa;AACnB,YAAM,OAAO,KAAK,GAAG,KAAK,MAAM,CAAC,CAAC;AAAA,IACpC;AAEA,WAAO,CAAC;AAAA,EACV;AAEA,MAAI,eAAe,KAAK,IAAI,GAAG;AAC7B,UAAM,OAAO,OAAO,KAAK,MAAM,CAAC,CAAC;AACjC,WAAO,KAAK,MAAM,CAAC;AAAA,EACrB;AAEA,MAAI,SAAS,UAAU;AACrB,QAAI,SAAS,IAAI,GAAG;AAClB,YAAM,OAAO,OAAO,IAAI;AACxB,aAAO,KAAK,MAAM,CAAC;AAAA,IACrB;AAGA,WAAO,KAAK,MAAM,CAAC;AAAA,EACrB;AAEA,MAAI,KAAK,WAAW,eAAe,GAAG;AACpC,UAAM,eAAe,CAAC;AACtB,UAAM,WAAW,KAAK,KAAK,MAAM,EAAE,CAAC;AACpC,WAAO,KAAK,MAAM,CAAC;AAAA,EACrB;AAEA,QAAM,aAAa;AACnB,QAAM,OAAO,KAAK,GAAG,KAAK,MAAM,CAAC,CAAC;AAClC,SAAO,CAAC;AACV;",
6
6
  "names": ["assert"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skuba",
3
- "version": "12.1.0-hoist-less-20250722131939",
3
+ "version": "12.1.0-main-20250812041011",
4
4
  "private": false,
5
5
  "description": "SEEK development toolkit for backend applications and packages",
6
6
  "homepage": "https://github.com/seek-oss/skuba#readme",
@@ -61,7 +61,6 @@
61
61
  "@types/node": "^22.0.0",
62
62
  "chalk": "^4.1.0",
63
63
  "concurrently": "^9.0.0",
64
- "dotenv": "^16.0.0",
65
64
  "ejs": "^3.1.6",
66
65
  "enquirer": "^2.3.6",
67
66
  "esbuild": "~0.25.0",
@@ -96,38 +95,38 @@
96
95
  "tsconfig-paths": "^4.0.0",
97
96
  "tsconfig-seek": "2.0.0",
98
97
  "tsx": "^4.16.2",
99
- "typescript": "~5.8.0",
100
- "zod": "^3.25.67",
101
- "eslint-config-skuba": "7.0.2"
98
+ "typescript": "~5.9.0",
99
+ "zod": "^4.0.0",
100
+ "eslint-config-skuba": "7.1.0-main-20250812041011"
102
101
  },
103
102
  "devDependencies": {
104
103
  "@changesets/cli": "2.29.5",
105
104
  "@changesets/get-github-info": "0.6.0",
106
- "@jest/reporters": "30.0.4",
107
- "@jest/test-result": "30.0.4",
105
+ "@jest/reporters": "30.0.5",
106
+ "@jest/test-result": "30.0.5",
108
107
  "@types/ejs": "3.1.5",
109
108
  "@types/express": "5.0.3",
110
109
  "@types/fs-extra": "11.0.4",
111
- "@types/koa": "2.15.0",
110
+ "@types/koa": "3.0.0",
112
111
  "@types/lodash.mergewith": "4.6.9",
113
112
  "@types/minimist": "1.2.5",
114
113
  "@types/module-alias": "2.0.4",
115
114
  "@types/npm-registry-fetch": "8.0.8",
116
115
  "@types/npm-which": "3.0.4",
117
- "@types/picomatch": "4.0.0",
116
+ "@types/picomatch": "4.0.2",
118
117
  "@types/semver": "7.7.0",
119
118
  "@types/supertest": "6.0.3",
120
- "enhanced-resolve": "5.18.2",
119
+ "enhanced-resolve": "5.18.3",
121
120
  "express": "5.1.0",
122
- "fastify": "5.4.0",
123
- "jest-diff": "30.0.4",
121
+ "fastify": "5.5.0",
122
+ "jest-diff": "30.0.5",
124
123
  "jsonfile": "6.1.0",
125
- "koa": "3.0.0",
126
- "memfs": "4.17.2",
124
+ "koa": "3.0.1",
125
+ "memfs": "4.36.0",
127
126
  "remark-cli": "12.0.1",
128
127
  "remark-preset-lint-recommended": "7.0.1",
129
128
  "semver": "7.7.2",
130
- "supertest": "7.1.3",
129
+ "supertest": "7.1.4",
131
130
  "type-fest": "2.19.0"
132
131
  },
133
132
  "peerDependencies": {
@@ -149,7 +148,7 @@
149
148
  "entryPoint": "src/index.ts",
150
149
  "template": null,
151
150
  "type": "package",
152
- "version": "11.1.0"
151
+ "version": "12.0.2"
153
152
  },
154
153
  "scripts": {
155
154
  "build": "scripts/build.sh",
@@ -163,7 +162,7 @@
163
162
  "lint:packages": "pnpm --filter '!./template/**' lint",
164
163
  "release": "pnpm --silent build && changeset publish",
165
164
  "skuba": "pnpm --silent build && pnpm --silent skuba:exec",
166
- "skuba:exec": "node --experimental-vm-modules --no-warnings=ExperimentalWarning lib/skuba",
165
+ "skuba:exec": "node lib/skuba",
167
166
  "stage": "changeset version && node ./.changeset/inject.js && pnpm format",
168
167
  "test": "pnpm --silent skuba test --selectProjects unit",
169
168
  "test:ci": "pnpm --silent skuba test --runInBand",
@@ -8,4 +8,5 @@ publicHoistPattern:
8
8
  - esbuild
9
9
  - jest
10
10
  - tsconfig-seek
11
+ - typescript
11
12
  # end managed by skuba
@@ -1,3 +1,3 @@
1
- process.env.ENVIRONMENT = 'test';
1
+ process.env.DEPLOYMENT = 'test';
2
2
 
3
3
  export {};
@@ -77,6 +77,9 @@ steps:
77
77
  queue: <%- devBuildkiteQueueName %>
78
78
  label: 🤞 Deploy Dev
79
79
  concurrency_group: <%- teamName %>/deploy/gantry/<%- devGantryEnvironmentName %>
80
+ env:
81
+ DD_DEPLOYMENT_ENVIRONMENT: development
82
+ DD_DEPLOYMENT_SERVICE: <%- serviceName %>
80
83
  key: deploy-dev
81
84
  plugins:
82
85
  - seek-jobs/gantry#v4.0.0:
@@ -93,6 +96,9 @@ steps:
93
96
  branches: ${BUILDKITE_PIPELINE_DEFAULT_BRANCH}
94
97
  concurrency_group: <%- teamName %>/deploy/gantry/<%- prodGantryEnvironmentName %>
95
98
  depends_on: deploy-dev
99
+ env:
100
+ DD_DEPLOYMENT_ENVIRONMENT: production
101
+ DD_DEPLOYMENT_SERVICE: <%- serviceName %>
96
102
  plugins:
97
103
  - seek-jobs/gantry#v4.0.0:
98
104
  command: apply
@@ -1 +1 @@
1
- ENVIRONMENT=local
1
+ DEPLOYMENT=local
@@ -1,7 +1,11 @@
1
- environment: dev
1
+ deployment: dev
2
+ env: development
2
3
 
3
4
  maxInstanceCount: 1
4
5
  minInstanceCount: 1
5
6
 
6
7
  # Disable dashboard for cost savings
7
8
  cloudwatchDashboardDisabled: true
9
+
10
+ openTelemetry:
11
+ enabled: true
@@ -1,4 +1,8 @@
1
- environment: prod
1
+ deployment: prod
2
+ env: production
2
3
 
3
4
  maxInstanceCount: 10
4
5
  minInstanceCount: 3
6
+
7
+ openTelemetry:
8
+ enabled: true
@@ -28,4 +28,4 @@ ARG PORT=8001
28
28
  ENV PORT=${PORT}
29
29
  EXPOSE ${PORT}
30
30
 
31
- CMD ["lib/listen.js"]
31
+ CMD ["--require", "./lib/tracing.js", "./lib/listen.js"]
@@ -70,19 +70,19 @@ pnpm start:debug
70
70
 
71
71
  This project is deployed through a [Buildkite pipeline](.buildkite/pipeline.yml).
72
72
 
73
- - Commits to a feature branch can be deployed to the dev environment by unblocking a step in the Buildkite UI
74
- - Commits to the default branch are automatically deployed to the dev and prod environments in sequence
73
+ - Commits to a feature branch can be deployed to the development environment by unblocking a step in the Buildkite UI
74
+ - Commits to the default branch are automatically deployed to the development and production environments in sequence
75
75
 
76
76
  To rapidly roll back a change,
77
77
  retry an individual deployment step from the previous build in Buildkite.
78
- Note that this will introduce drift between the head of the default Git branch and the live environment;
78
+ Note that this will introduce drift between the head of the default Git branch and the live deployments;
79
79
  use with caution and always follow up with a proper revert or fix in Git history.
80
80
 
81
81
  ## Support
82
82
 
83
83
  ### Dev
84
84
 
85
- TODO: add support links for the dev environment.
85
+ TODO: add support links for the dev deployment.
86
86
 
87
87
  <!--
88
88
  - CloudWatch dashboard
@@ -92,7 +92,7 @@ TODO: add support links for the dev environment.
92
92
 
93
93
  ### Prod
94
94
 
95
- TODO: add support links for the prod environment.
95
+ TODO: add support links for the prod deployment.
96
96
 
97
97
  <!--
98
98
  - CloudWatch dashboard
@@ -9,9 +9,14 @@ owner: '{{values "owner"}}'
9
9
  image: '{{values "image"}}'
10
10
 
11
11
  env:
12
- ENVIRONMENT: '{{values "environment"}}'
12
+ DEPLOYMENT: '{{values "deployment"}}'
13
13
  SERVICE: '{{values "service"}}'
14
14
 
15
+ DD_ENV: '{{values "env"}}'
16
+ DD_SERVICE: '{{values "service"}}'
17
+
18
+ OPENTELEMETRY_ENABLED: '{{values "openTelemetry.enabled"}}'
19
+
15
20
  {{if .Values.cloudwatchDashboardDisabled}}
16
21
  cloudwatchDashboardDisabled: {{values "cloudwatchDashboardDisabled"}}
17
22
  {{end}}
@@ -20,6 +25,17 @@ cloudwatchDashboardDisabled: {{values "cloudwatchDashboardDisabled"}}
20
25
  datadogSecretId: '{{values "datadogSecretId"}}'
21
26
  {{end}}
22
27
 
28
+ # Uncomment if the `tin` tier is acceptable for your SEEK Auth Sidecar logs
29
+ # The eeeoh log forwarder bypasses Amazon CloudWatch for cost savings
30
+ # logSink:
31
+ # forwarder: eeeoh
32
+ # splunkIndex: '{{values "logSink.splunkIndex"}}'
33
+
34
+ openTelemetry:
35
+ datadogEnvironmentName: '{{values "env"}}'
36
+ enabled: {{values "openTelemetry.enabled"}}
37
+ useGantryServiceName: true
38
+
23
39
  {{if .Values.pagerDutyEndpoint}}
24
40
  pagerDutyEndpoint: '{{values "pagerDutyEndpoint"}}'
25
41
  {{end}}
@@ -13,22 +13,28 @@
13
13
  "test:watch": "skuba test --watch"
14
14
  },
15
15
  "dependencies": {
16
- "@seek/logger": "^10.0.0",
16
+ "@opentelemetry/api": "^1.9.0",
17
+ "@opentelemetry/core": "^2.0.0",
18
+ "@opentelemetry/exporter-trace-otlp-grpc": "^0.203.0",
19
+ "@opentelemetry/instrumentation-aws-sdk": "^0.56.0",
20
+ "@opentelemetry/instrumentation-http": "^0.203.0",
21
+ "@opentelemetry/propagator-b3": "^2.0.0",
22
+ "@opentelemetry/sdk-node": "^0.203.0",
23
+ "@seek/logger": "11.0.0",
17
24
  "express": "^5.0.0",
18
- "hot-shots": "^10.0.0",
19
- "seek-datadog-custom-metrics": "^4.6.3",
25
+ "hot-shots": "^11.0.0",
26
+ "seek-datadog-custom-metrics": "^6.0.0",
20
27
  "skuba-dive": "^2.0.0"
21
28
  },
22
29
  "devDependencies": {
23
30
  "@types/express": "^5.0.0",
24
31
  "@types/node": "^22.13.10",
25
32
  "@types/supertest": "^6.0.0",
26
- "mime": "^4.0.1",
27
33
  "pino-pretty": "^13.0.0",
28
34
  "skuba": "*",
29
35
  "supertest": "^7.0.0"
30
36
  },
31
- "packageManager": "pnpm@10.12.4",
37
+ "packageManager": "pnpm@10.14.0",
32
38
  "engines": {
33
39
  "node": ">=22"
34
40
  }
@@ -3,8 +3,8 @@ import type { Handler } from 'express';
3
3
  /**
4
4
  * Signifies that the API is available to serve requests.
5
5
  *
6
- * The deployment environment calls this endpoint to see if the container is
7
- * unhealthy and needs to be recycled.
6
+ * The workload hosting environment calls this endpoint to see if the container
7
+ * is unhealthy and needs to be recycled.
8
8
  */
9
9
  export const healthCheckHandler: Handler = (_req, res) => {
10
10
  res.send('');
@@ -1,7 +1,7 @@
1
1
  import { Env } from 'skuba-dive';
2
2
 
3
3
  interface Config {
4
- environment: Environment;
4
+ deployment: Deployment;
5
5
 
6
6
  logLevel: string;
7
7
  name: string;
@@ -11,17 +11,17 @@ interface Config {
11
11
  port: number | null;
12
12
  }
13
13
 
14
- type Environment = (typeof environments)[number];
14
+ type Deployment = (typeof deployments)[number];
15
15
 
16
16
  const dev = '<%- devGantryEnvironmentName %>';
17
17
  const prod = '<%- prodGantryEnvironmentName %>';
18
18
 
19
- const environments = ['local', 'test', dev, prod] as const;
19
+ const deployments = ['local', 'test', dev, prod] as const;
20
20
 
21
- const environment = Env.oneOf(environments)('ENVIRONMENT');
21
+ const deployment = Env.oneOf(deployments)('DEPLOYMENT');
22
22
 
23
23
  /* istanbul ignore next: config verification makes more sense in a smoke test */
24
- const configs: Record<Environment, () => Omit<Config, 'environment'>> = {
24
+ const configs: Record<Deployment, () => Omit<Config, 'deployment'>> = {
25
25
  local: () => ({
26
26
  logLevel: 'debug',
27
27
  name: '<%- serviceName %>',
@@ -60,6 +60,6 @@ const configs: Record<Environment, () => Omit<Config, 'environment'>> = {
60
60
  };
61
61
 
62
62
  export const config: Config = {
63
- ...configs[environment](),
64
- environment,
63
+ ...configs[deployment](),
64
+ deployment,
65
65
  };
@@ -1,17 +1,21 @@
1
- import createLogger from '@seek/logger';
1
+ import { createLogger } from '@seek/logger';
2
2
 
3
3
  import { config } from 'src/config.js';
4
4
 
5
5
  export const logger = createLogger({
6
- base: {
7
- environment: config.environment,
8
- version: config.version,
6
+ eeeoh: {
7
+ /**
8
+ * TODO: choose an appropriate Datadog log tier.
9
+ *
10
+ * https://github.com/seek-oss/logger/blob/master/docs/eeeoh.md#datadog-log-tiers
11
+ */
12
+ datadog: 'tin',
13
+ team: '<%- teamName %>',
14
+ use: 'environment',
9
15
  },
10
16
 
11
17
  level: config.logLevel,
12
18
 
13
- name: config.name,
14
-
15
19
  transport:
16
- config.environment === 'local' ? { target: 'pino-pretty' } : undefined,
20
+ config.deployment === 'local' ? { target: 'pino-pretty' } : undefined,
17
21
  });
@@ -7,5 +7,5 @@ import { logger } from './logging.js';
7
7
 
8
8
  /* istanbul ignore next: StatsD client is not our responsibility */
9
9
  export const metricsClient = createStatsDClient(StatsD, config, (err) =>
10
- logger.error({ err }, 'StatsD error'),
10
+ logger.error(err, 'StatsD error'),
11
11
  );
@@ -21,3 +21,9 @@ const listener = app.listen(config.port, () => {
21
21
  // https://docs.aws.amazon.com/elasticloadbalancing/latest/application/application-load-balancers.html#connection-idle-timeout
22
22
  // AWS recommends setting an application timeout larger than the load balancer
23
23
  listener.keepAliveTimeout = 31000;
24
+
25
+ // Report unhandled rejections instead of crashing the process
26
+ // Make sure to monitor these reports and alert as appropriate
27
+ process.on('unhandledRejection', (err) =>
28
+ logger.error(err, 'Unhandled promise rejection'),
29
+ );
@@ -0,0 +1,56 @@
1
+ /**
2
+ * OpenTelemetry tracing initialisation. This is a standalone TS/JS module that is not
3
+ * referenced by application source code directly. It is required at runtime using the
4
+ * node command's `--require` argument, see Dockerfile for details.
5
+ */
6
+
7
+ import { propagation } from '@opentelemetry/api';
8
+ import { CompositePropagator } from '@opentelemetry/core';
9
+ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc';
10
+ import { AwsInstrumentation } from '@opentelemetry/instrumentation-aws-sdk';
11
+ import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
12
+ import { B3InjectEncoding, B3Propagator } from '@opentelemetry/propagator-b3';
13
+ import { NodeSDK } from '@opentelemetry/sdk-node';
14
+
15
+ const app = 'opentelemetry';
16
+ const log = (level: string, msg: string, extra = {}) => {
17
+ const toLog = { msg, level, app, time: new Date().toISOString(), ...extra };
18
+ console.log(JSON.stringify(toLog)); // eslint-disable-line no-console
19
+ };
20
+
21
+ const main = () => {
22
+ // Use B3 propagation to ensure proper propagation between systems that use
23
+ // OpenTelemetry and native Datadog APM, such as Istio/Envoy.
24
+ propagation.setGlobalPropagator(
25
+ new CompositePropagator({
26
+ propagators: [
27
+ new B3Propagator(),
28
+ new B3Propagator({ injectEncoding: B3InjectEncoding.MULTI_HEADER }),
29
+ ],
30
+ }),
31
+ );
32
+
33
+ const sdk = new NodeSDK({
34
+ traceExporter: new OTLPTraceExporter(),
35
+ autoDetectResources: false,
36
+ instrumentations: [new HttpInstrumentation(), new AwsInstrumentation()],
37
+ });
38
+
39
+ sdk.start();
40
+
41
+ process.on('SIGTERM', () => {
42
+ sdk
43
+ .shutdown()
44
+ .then(() => log('info', 'OpenTelemetry successfully terminated'))
45
+ .catch((err: Error) =>
46
+ log('error', 'OpenTelemetry failed to terminate', { err }),
47
+ )
48
+ .finally(() => process.exit(0)); // eslint-disable-line no-process-exit
49
+ });
50
+ };
51
+
52
+ if (process.env.OPENTELEMETRY_ENABLED === 'true') {
53
+ main();
54
+ } else {
55
+ log('info', 'OpenTelemetry not enabled');
56
+ }
@@ -75,7 +75,7 @@ For inspiration in this space, check out:
75
75
 
76
76
  ### Dev
77
77
 
78
- TODO: add support links for the dev environment.
78
+ TODO: add support links for the dev deployment.
79
79
 
80
80
  <!--
81
81
  - CloudWatch dashboard
@@ -85,7 +85,7 @@ TODO: add support links for the dev environment.
85
85
 
86
86
  ### Prod
87
87
 
88
- TODO: add support links for the prod environment.
88
+ TODO: add support links for the prod deployment.
89
89
 
90
90
  <!--
91
91
  - CloudWatch dashboard
@@ -17,9 +17,9 @@
17
17
  },
18
18
  "devDependencies": {
19
19
  "@types/node": "^22.13.10",
20
- "skuba": "12.1.0-hoist-less-20250722131939"
20
+ "skuba": "12.1.0-main-20250812041011"
21
21
  },
22
- "packageManager": "pnpm@10.12.4",
22
+ "packageManager": "pnpm@10.14.0",
23
23
  "engines": {
24
24
  "node": ">=22"
25
25
  }
@@ -77,6 +77,9 @@ steps:
77
77
  queue: <%- devBuildkiteQueueName %>
78
78
  label: 🤞 Deploy Dev
79
79
  concurrency_group: <%- teamName %>/deploy/gantry/<%- devGantryEnvironmentName %>
80
+ env:
81
+ DD_DEPLOYMENT_ENVIRONMENT: development
82
+ DD_DEPLOYMENT_SERVICE: <%- serviceName %>
80
83
  key: deploy-dev
81
84
  plugins:
82
85
  - seek-jobs/gantry#v4.0.0:
@@ -93,6 +96,9 @@ steps:
93
96
  branches: ${BUILDKITE_PIPELINE_DEFAULT_BRANCH}
94
97
  concurrency_group: <%- teamName %>/deploy/gantry/<%- prodGantryEnvironmentName %>
95
98
  depends_on: deploy-dev
99
+ env:
100
+ DD_DEPLOYMENT_ENVIRONMENT: production
101
+ DD_DEPLOYMENT_SERVICE: <%- serviceName %>
96
102
  plugins:
97
103
  - seek-jobs/gantry#v4.0.0:
98
104
  command: apply