skuba 12.0.0-enforce-file-extensions-20250618112549 → 12.0.0-subpath-imports-20250710010820

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 (102) hide show
  1. package/config/tsconfig.json +4 -3
  2. package/jest/transform.js +1 -15
  3. package/jest-preset.js +4 -7
  4. package/lib/cli/buildPackage/index.js +2 -2
  5. package/lib/cli/buildPackage/index.js.map +1 -1
  6. package/lib/cli/configure/ensureTemplateCompletion.js +1 -1
  7. package/lib/cli/configure/ensureTemplateCompletion.js.map +2 -2
  8. package/lib/cli/configure/processing/prettier.d.ts +1 -1
  9. package/lib/cli/configure/processing/prettier.js +8 -6
  10. package/lib/cli/configure/processing/prettier.js.map +3 -3
  11. package/lib/cli/init/getConfig.d.ts +1 -1
  12. package/lib/cli/init/getConfig.js +7 -5
  13. package/lib/cli/init/getConfig.js.map +3 -3
  14. package/lib/cli/init/prompts.d.ts +5 -5
  15. package/lib/cli/init/prompts.js +5 -4
  16. package/lib/cli/init/prompts.js.map +3 -3
  17. package/lib/cli/lint/internalLints/upgrade/patches/11.1.0/index.d.ts +2 -0
  18. package/lib/cli/lint/internalLints/upgrade/patches/11.1.0/index.js +35 -0
  19. package/lib/cli/lint/internalLints/upgrade/patches/11.1.0/index.js.map +7 -0
  20. package/lib/cli/lint/internalLints/upgrade/patches/11.1.0/rewriteSrcImports.d.ts +6 -0
  21. package/lib/cli/lint/internalLints/upgrade/patches/11.1.0/rewriteSrcImports.js +104 -0
  22. package/lib/cli/lint/internalLints/upgrade/patches/11.1.0/rewriteSrcImports.js.map +7 -0
  23. package/lib/cli/migrate/nodeVersion/index.js +18 -18
  24. package/lib/cli/migrate/nodeVersion/index.js.map +2 -2
  25. package/package.json +33 -26
  26. package/template/base/_.prettierrc.js +1 -1
  27. package/template/base/_eslint.config.js +1 -1
  28. package/template/base/_pnpm-workspace.yaml +2 -0
  29. package/template/base/jest.config.ts +17 -3
  30. package/template/base/jest.setup.ts +0 -2
  31. package/template/base/tsconfig.json +3 -3
  32. package/template/express-rest-api/.buildkite/pipeline.yml +1 -1
  33. package/template/express-rest-api/Dockerfile.dev-deps +1 -1
  34. package/template/express-rest-api/package.json +8 -1
  35. package/template/express-rest-api/src/app.ts +0 -2
  36. package/template/express-rest-api/src/framework/logging.ts +1 -1
  37. package/template/express-rest-api/src/framework/metrics.ts +2 -2
  38. package/template/express-rest-api/src/listen.ts +0 -2
  39. package/template/greeter/.buildkite/pipeline.yml +1 -1
  40. package/template/greeter/Dockerfile +1 -1
  41. package/template/greeter/package.json +10 -3
  42. package/template/greeter/src/app.ts +2 -2
  43. package/template/koa-rest-api/.buildkite/pipeline.yml +1 -1
  44. package/template/koa-rest-api/Dockerfile.dev-deps +1 -1
  45. package/template/koa-rest-api/package.json +14 -7
  46. package/template/koa-rest-api/src/api/healthCheck.ts +1 -1
  47. package/template/koa-rest-api/src/api/jobs/getJobs.test.ts +2 -2
  48. package/template/koa-rest-api/src/api/jobs/getJobs.ts +4 -4
  49. package/template/koa-rest-api/src/api/jobs/index.ts +2 -2
  50. package/template/koa-rest-api/src/api/jobs/postJob.test.ts +4 -4
  51. package/template/koa-rest-api/src/api/jobs/postJob.ts +6 -6
  52. package/template/koa-rest-api/src/api/smokeTest.ts +2 -2
  53. package/template/koa-rest-api/src/app.test.ts +2 -2
  54. package/template/koa-rest-api/src/app.ts +0 -2
  55. package/template/koa-rest-api/src/config.ts +1 -1
  56. package/template/koa-rest-api/src/framework/logging.ts +22 -13
  57. package/template/koa-rest-api/src/framework/metrics.ts +2 -2
  58. package/template/koa-rest-api/src/framework/server.test.ts +95 -64
  59. package/template/koa-rest-api/src/framework/server.ts +3 -3
  60. package/template/koa-rest-api/src/framework/validation.test.ts +36 -36
  61. package/template/koa-rest-api/src/framework/validation.ts +15 -22
  62. package/template/koa-rest-api/src/listen.ts +0 -2
  63. package/template/koa-rest-api/src/register.ts +0 -1
  64. package/template/koa-rest-api/src/storage/jobs.ts +1 -1
  65. package/template/koa-rest-api/src/testing/metrics.ts +1 -1
  66. package/template/koa-rest-api/src/testing/server.ts +1 -1
  67. package/template/koa-rest-api/src/testing/types.ts +2 -2
  68. package/template/koa-rest-api/src/types/jobs.ts +1 -1
  69. package/template/lambda-sqs-worker-cdk/.buildkite/pipeline.yml +2 -2
  70. package/template/lambda-sqs-worker-cdk/Dockerfile +1 -1
  71. package/template/lambda-sqs-worker-cdk/infra/__snapshots__/appStack.test.ts.snap +6 -0
  72. package/template/lambda-sqs-worker-cdk/infra/appStack.test.ts +1 -1
  73. package/template/lambda-sqs-worker-cdk/package.json +13 -6
  74. package/template/lambda-sqs-worker-cdk/src/app.test.ts +81 -22
  75. package/template/lambda-sqs-worker-cdk/src/app.ts +7 -9
  76. package/template/lambda-sqs-worker-cdk/src/config.ts +1 -1
  77. package/template/lambda-sqs-worker-cdk/src/framework/handler.test.ts +38 -18
  78. package/template/lambda-sqs-worker-cdk/src/framework/handler.ts +2 -2
  79. package/template/lambda-sqs-worker-cdk/src/framework/logging.ts +23 -12
  80. package/template/lambda-sqs-worker-cdk/src/framework/metrics.ts +1 -1
  81. package/template/lambda-sqs-worker-cdk/src/framework/validation.test.ts +9 -12
  82. package/template/lambda-sqs-worker-cdk/src/framework/validation.ts +3 -7
  83. package/template/lambda-sqs-worker-cdk/src/services/jobScorer.test.ts +3 -3
  84. package/template/lambda-sqs-worker-cdk/src/services/jobScorer.ts +7 -6
  85. package/template/lambda-sqs-worker-cdk/src/services/pipelineEventSender.test.ts +3 -3
  86. package/template/lambda-sqs-worker-cdk/src/services/pipelineEventSender.ts +2 -2
  87. package/template/lambda-sqs-worker-cdk/src/testing/services.ts +2 -2
  88. package/template/lambda-sqs-worker-cdk/src/testing/types.ts +1 -1
  89. package/template/lambda-sqs-worker-cdk/src/types/jobScorer.ts +1 -1
  90. package/template/lambda-sqs-worker-cdk/src/types/pipelineEvents.ts +1 -1
  91. package/template/lambda-sqs-worker-cdk/tsconfig.json +3 -3
  92. package/template/oss-npm-package/_package.json +8 -0
  93. package/template/oss-npm-package/skuba.template.js +1 -1
  94. package/template/oss-npm-package/tsconfig.json +3 -0
  95. package/template/private-npm-package/_package.json +7 -0
  96. package/lib/cli/test/reporters/prettier/index.d.ts +0 -4
  97. package/lib/cli/test/reporters/prettier/index.js +0 -67
  98. package/lib/cli/test/reporters/prettier/index.js.map +0 -7
  99. package/template/express-rest-api/src/register.ts +0 -1
  100. package/template/koa-rest-api/src/testing/logging.ts +0 -16
  101. package/template/koa-rest-api/tsconfig.json +0 -18
  102. package/template/lambda-sqs-worker-cdk/src/testing/logging.ts +0 -19
@@ -1,8 +1,8 @@
1
- import { agentFromRouter } from 'src/testing/server.js';
2
- import { mockJobInput } from 'src/testing/types.js';
3
-
4
1
  import { jobRouter } from './index.js';
5
2
 
3
+ import { agentFromRouter } from '#src/testing/server.js';
4
+ import { mockJobInput } from '#src/testing/types.js';
5
+
6
6
  const agent = agentFromRouter(jobRouter);
7
7
 
8
8
  describe('postJobHandler', () => {
@@ -27,7 +27,7 @@ describe('postJobHandler', () => {
27
27
  .expect(422)
28
28
  .expect(({ text }) =>
29
29
  expect(text).toMatchInlineSnapshot(
30
- `"{"message":"Input validation failed","invalidFields":{"/hirer":"Required"}}"`,
30
+ `"{"message":"Input validation failed","invalidFields":{"/hirer":"Invalid input: expected object, received undefined"}}"`,
31
31
  ),
32
32
  );
33
33
  });
@@ -1,9 +1,9 @@
1
- import { logger } from 'src/framework/logging.js';
2
- import { metricsClient } from 'src/framework/metrics.js';
3
- import { validateRequestBody } from 'src/framework/validation.js';
4
- import * as storage from 'src/storage/jobs.js';
5
- import { JobInputSchema } from 'src/types/jobs.js';
6
- import type { Middleware } from 'src/types/koa.js';
1
+ import { logger } from '#src/framework/logging.js';
2
+ import { metricsClient } from '#src/framework/metrics.js';
3
+ import { validateRequestBody } from '#src/framework/validation.js';
4
+ import * as storage from '#src/storage/jobs.js';
5
+ import { JobInputSchema } from '#src/types/jobs.js';
6
+ import type { Middleware } from '#src/types/koa.js';
7
7
 
8
8
  export const postJobHandler: Middleware = async (ctx) => {
9
9
  const jobInput = validateRequestBody(ctx, JobInputSchema);
@@ -1,5 +1,5 @@
1
- import { smokeTestJobStorage } from 'src/storage/jobs.js';
2
- import type { Middleware } from 'src/types/koa.js';
1
+ import { smokeTestJobStorage } from '#src/storage/jobs.js';
2
+ import type { Middleware } from '#src/types/koa.js';
3
3
 
4
4
  /**
5
5
  * Tests connectivity to ensure appropriate access and network configuration.
@@ -1,7 +1,7 @@
1
- import { agentFromApp } from 'src/testing/server.js';
2
-
3
1
  import app from './app.js';
4
2
 
3
+ import { agentFromApp } from '#src/testing/server.js';
4
+
5
5
  const agent = agentFromApp(app);
6
6
 
7
7
  describe('app', () => {
@@ -1,5 +1,3 @@
1
- import './register.js';
2
-
3
1
  import { router } from './api/index.js';
4
2
  import { createApp } from './framework/server.js';
5
3
 
@@ -32,7 +32,7 @@ const configs: Record<Environment, () => Omit<Config, 'environment'>> = {
32
32
  }),
33
33
 
34
34
  test: () => ({
35
- logLevel: Env.string('LOG_LEVEL', { default: 'silent' }),
35
+ logLevel: 'debug',
36
36
  name: '<%- serviceName %>',
37
37
  version: 'test',
38
38
 
@@ -1,25 +1,34 @@
1
- import createLogger from '@seek/logger';
1
+ import createLogger, { createDestination } from '@seek/logger';
2
2
  import { RequestLogging } from 'seek-koala';
3
3
 
4
- import { config } from 'src/config.js';
4
+ import { config } from '#src/config.js';
5
5
 
6
6
  const { createContextMiddleware, mixin } =
7
7
  RequestLogging.createContextStorage();
8
8
 
9
9
  export const contextMiddleware = createContextMiddleware();
10
10
 
11
- export const logger = createLogger({
12
- base: {
13
- environment: config.environment,
14
- version: config.version,
15
- },
11
+ const { destination, stdoutMock } = createDestination({
12
+ mock: config.environment === 'test',
13
+ });
16
14
 
17
- mixin,
15
+ export { stdoutMock };
18
16
 
19
- level: config.logLevel,
17
+ export const logger = createLogger(
18
+ {
19
+ base: {
20
+ environment: config.environment,
21
+ version: config.version,
22
+ },
20
23
 
21
- name: config.name,
24
+ mixin,
22
25
 
23
- transport:
24
- config.environment === 'local' ? { target: 'pino-pretty' } : undefined,
25
- });
26
+ level: config.logLevel,
27
+
28
+ name: config.name,
29
+
30
+ transport:
31
+ config.environment === 'local' ? { target: 'pino-pretty' } : undefined,
32
+ },
33
+ destination,
34
+ );
@@ -1,10 +1,10 @@
1
1
  import { StatsD } from 'hot-shots';
2
2
  import { createStatsDClient } from 'seek-datadog-custom-metrics';
3
3
 
4
- import { config } from 'src/config.js';
5
-
6
4
  import { logger } from './logging.js';
7
5
 
6
+ import { config } from '#src/config.js';
7
+
8
8
  /* istanbul ignore next: StatsD client is not our responsibility */
9
9
  export const metricsClient = createStatsDClient(StatsD, config, (err) =>
10
10
  logger.error({ err }, 'StatsD error'),
@@ -1,10 +1,11 @@
1
1
  import Router from '@koa/router';
2
2
 
3
- import { logger } from 'src/testing/logging.js';
4
- import { metricsClient } from 'src/testing/metrics.js';
5
- import { agentFromRouter } from 'src/testing/server.js';
6
- import { chance } from 'src/testing/types.js';
7
- import type { Middleware } from 'src/types/koa.js';
3
+ import { stdoutMock } from './logging.js';
4
+
5
+ import { metricsClient } from '#src/testing/metrics.js';
6
+ import { agentFromRouter } from '#src/testing/server.js';
7
+ import { chance } from '#src/testing/types.js';
8
+ import type { Middleware } from '#src/types/koa.js';
8
9
 
9
10
  const middleware = jest.fn<void, Parameters<Middleware>>();
10
11
 
@@ -15,10 +16,10 @@ const router = new Router()
15
16
  const agent = agentFromRouter(router);
16
17
 
17
18
  describe('createApp', () => {
18
- beforeAll(logger.spy);
19
-
20
- afterEach(metricsClient.clear);
21
- afterEach(logger.clear);
19
+ afterEach(() => {
20
+ metricsClient.clear();
21
+ stdoutMock.clear();
22
+ });
22
23
 
23
24
  it('handles root route', async () => {
24
25
  middleware.mockImplementation((ctx) => (ctx.body = ''));
@@ -29,9 +30,7 @@ describe('createApp', () => {
29
30
  .expect('server', /.+/)
30
31
  .expect('x-api-version', /.+/);
31
32
 
32
- expect(logger.error).not.toHaveBeenCalled();
33
-
34
- expect(logger.info).not.toHaveBeenCalled();
33
+ expect(stdoutMock.calls).toHaveLength(0);
35
34
 
36
35
  metricsClient.expectTagSubset(['env:test', 'version:test']);
37
36
  metricsClient.expectTagSubset([
@@ -51,9 +50,7 @@ describe('createApp', () => {
51
50
  .expect('server', /.+/)
52
51
  .expect('x-api-version', /.+/);
53
52
 
54
- expect(logger.error).not.toHaveBeenCalled();
55
-
56
- expect(logger.info).not.toHaveBeenCalled();
53
+ expect(stdoutMock.calls).toHaveLength(0);
57
54
 
58
55
  metricsClient.expectTagSubset([
59
56
  'http_method:put',
@@ -72,13 +69,15 @@ describe('createApp', () => {
72
69
  .expect('server', /.+/)
73
70
  .expect('x-api-version', /.+/);
74
71
 
75
- expect(logger.error).not.toHaveBeenCalled();
76
-
77
- expect(logger.info).toHaveBeenNthCalledWith(
78
- 1,
79
- expect.objectContaining({ status: 404 }),
80
- 'Client error',
81
- );
72
+ expect(stdoutMock.calls).toMatchObject([
73
+ {
74
+ level: 30,
75
+ method: 'GET',
76
+ msg: 'Client error',
77
+ status: 404,
78
+ url: '/unknown',
79
+ },
80
+ ]);
82
81
 
83
82
  metricsClient.expectTagSubset([
84
83
  'http_method:get',
@@ -102,13 +101,16 @@ describe('createApp', () => {
102
101
  .expect('server', /.+/)
103
102
  .expect('x-api-version', /.+/);
104
103
 
105
- expect(logger.error).not.toHaveBeenCalled();
106
-
107
- expect(logger.info).toHaveBeenNthCalledWith(
108
- 1,
109
- expect.objectContaining({ status: 400 }),
110
- 'Client error',
111
- );
104
+ expect(stdoutMock.calls).toMatchObject([
105
+ {
106
+ level: 30,
107
+ method: 'GET',
108
+ msg: 'Client error',
109
+ route: '/',
110
+ status: 400,
111
+ url: '/',
112
+ },
113
+ ]);
112
114
 
113
115
  metricsClient.expectTagSubset([
114
116
  'http_method:get',
@@ -129,13 +131,20 @@ describe('createApp', () => {
129
131
  .expect('server', /.+/)
130
132
  .expect('x-api-version', /.+/);
131
133
 
132
- expect(logger.error).not.toHaveBeenCalled();
133
-
134
- expect(logger.info).toHaveBeenNthCalledWith(
135
- 1,
136
- expect.objectContaining({ err: expect.any(Error), status: 400 }),
137
- 'Client error',
138
- );
134
+ expect(stdoutMock.calls).toMatchObject([
135
+ {
136
+ err: {
137
+ statusCode: 400,
138
+ type: 'BadRequestError',
139
+ },
140
+ level: 30,
141
+ method: 'GET',
142
+ msg: 'Client error',
143
+ route: '/',
144
+ status: 400,
145
+ url: '/',
146
+ },
147
+ ]);
139
148
 
140
149
  metricsClient.expectTagSubset([
141
150
  'http_method:get',
@@ -156,13 +165,20 @@ describe('createApp', () => {
156
165
  .expect('server', /.+/)
157
166
  .expect('x-api-version', /.+/);
158
167
 
159
- expect(logger.error).toHaveBeenNthCalledWith(
160
- 1,
161
- expect.objectContaining({ err: expect.any(Error), status: 500 }),
162
- 'Server error',
163
- );
164
-
165
- expect(logger.info).not.toHaveBeenCalled();
168
+ expect(stdoutMock.calls).toMatchObject([
169
+ {
170
+ err: {
171
+ statusCode: 500,
172
+ type: 'InternalServerError',
173
+ },
174
+ level: 50,
175
+ method: 'GET',
176
+ msg: 'Server error',
177
+ route: '/',
178
+ status: 500,
179
+ url: '/',
180
+ },
181
+ ]);
166
182
 
167
183
  metricsClient.expectTagSubset([
168
184
  'http_method:get',
@@ -185,13 +201,20 @@ describe('createApp', () => {
185
201
  .expect('server', /.+/)
186
202
  .expect('x-api-version', /.+/);
187
203
 
188
- expect(logger.error).toHaveBeenNthCalledWith(
189
- 1,
190
- expect.objectContaining({ err, status: 500 }),
191
- 'Server error',
192
- );
193
-
194
- expect(logger.info).not.toHaveBeenCalled();
204
+ expect(stdoutMock.calls).toMatchObject([
205
+ {
206
+ err: {
207
+ message: err.message,
208
+ type: 'Error',
209
+ },
210
+ level: 50,
211
+ method: 'GET',
212
+ msg: 'Server error',
213
+ route: '/',
214
+ status: 500,
215
+ url: '/',
216
+ },
217
+ ]);
195
218
 
196
219
  metricsClient.expectTagSubset([
197
220
  'http_method:get',
@@ -212,13 +235,17 @@ describe('createApp', () => {
212
235
  .expect('server', /.+/)
213
236
  .expect('x-api-version', /.+/);
214
237
 
215
- expect(logger.error).toHaveBeenNthCalledWith(
216
- 1,
217
- expect.objectContaining({ err: null, status: 500 }),
218
- 'Server error',
219
- );
220
-
221
- expect(logger.info).not.toHaveBeenCalled();
238
+ expect(stdoutMock.calls).toMatchObject([
239
+ {
240
+ err: null,
241
+ level: 50,
242
+ method: 'GET',
243
+ msg: 'Server error',
244
+ route: '/',
245
+ status: 500,
246
+ url: '/',
247
+ },
248
+ ]);
222
249
 
223
250
  metricsClient.expectTagSubset([
224
251
  'http_method:get',
@@ -241,13 +268,17 @@ describe('createApp', () => {
241
268
  .expect('server', /.+/)
242
269
  .expect('x-api-version', /.+/);
243
270
 
244
- expect(logger.error).toHaveBeenNthCalledWith(
245
- 1,
246
- expect.objectContaining({ err, status: 500 }),
247
- 'Server error',
248
- );
249
-
250
- expect(logger.info).not.toHaveBeenCalled();
271
+ expect(stdoutMock.calls).toMatchObject([
272
+ {
273
+ err,
274
+ level: 50,
275
+ method: 'GET',
276
+ msg: 'Server error',
277
+ route: '/',
278
+ status: 500,
279
+ url: '/',
280
+ },
281
+ ]);
251
282
 
252
283
  metricsClient.expectTagSubset([
253
284
  'http_method:get',
@@ -8,9 +8,9 @@ import {
8
8
  VersionMiddleware,
9
9
  } from 'seek-koala';
10
10
 
11
- import { config } from 'src/config.js';
12
- import { contextMiddleware, logger } from 'src/framework/logging.js';
13
- import { metricsClient } from 'src/framework/metrics.js';
11
+ import { config } from '#src/config.js';
12
+ import { contextMiddleware, logger } from '#src/framework/logging.js';
13
+ import { metricsClient } from '#src/framework/metrics.js';
14
14
 
15
15
  const metrics = MetricsMiddleware.create(
16
16
  metricsClient,
@@ -1,12 +1,12 @@
1
- import { agentFromMiddleware } from 'src/testing/server.js';
1
+ import { jsonBodyParser } from './bodyParser.js';
2
+ import { validate } from './validation.js';
3
+
4
+ import { agentFromMiddleware } from '#src/testing/server.js';
2
5
  import {
3
6
  IdDescriptionSchema,
4
7
  chance,
5
8
  mockIdDescription,
6
- } from 'src/testing/types.js';
7
-
8
- import { jsonBodyParser } from './bodyParser.js';
9
- import { validate } from './validation.js';
9
+ } from '#src/testing/types.js';
10
10
 
11
11
  const agent = agentFromMiddleware(jsonBodyParser, (ctx) => {
12
12
  const result = validate({
@@ -43,15 +43,15 @@ describe('validate', () => {
43
43
  .expect(422)
44
44
  .expect(({ body }) =>
45
45
  expect(body).toMatchInlineSnapshot(`
46
- {
47
- "invalidFields": {
48
- "~union0/id": "Expected string, received null",
49
- "~union1/id": "Expected number, received null",
50
- "~union1/summary": "Required",
51
- },
52
- "message": "Input validation failed",
53
- }
54
- `),
46
+ {
47
+ "invalidFields": {
48
+ "~union0/id": "Invalid input: expected string, received null",
49
+ "~union1/id": "Invalid input: expected number, received null",
50
+ "~union1/summary": "Invalid input: expected string, received undefined",
51
+ },
52
+ "message": "Input validation failed",
53
+ }
54
+ `),
55
55
  );
56
56
  });
57
57
 
@@ -62,17 +62,17 @@ describe('validate', () => {
62
62
  .expect(422)
63
63
  .expect(({ body }) =>
64
64
  expect(body).toMatchInlineSnapshot(`
65
- {
66
- "invalidFields": {
67
- "~union0/description~union0": "Required",
68
- "~union0/description~union1": "Required",
69
- "~union0/id": "Required",
70
- "~union1/id": "Required",
71
- "~union1/summary": "Required",
72
- },
73
- "message": "Input validation failed",
74
- }
75
- `),
65
+ {
66
+ "invalidFields": {
67
+ "~union0/description~union0": "Invalid input: expected string, received undefined",
68
+ "~union0/description~union1": "Invalid input: expected object, received undefined",
69
+ "~union0/id": "Invalid input: expected string, received undefined",
70
+ "~union1/id": "Invalid input: expected number, received undefined",
71
+ "~union1/summary": "Invalid input: expected string, received undefined",
72
+ },
73
+ "message": "Input validation failed",
74
+ }
75
+ `),
76
76
  ));
77
77
 
78
78
  it('blocks invalid nested union prop', () => {
@@ -89,17 +89,17 @@ describe('validate', () => {
89
89
  .expect(422)
90
90
  .expect(({ body }) =>
91
91
  expect(body).toMatchInlineSnapshot(`
92
- {
93
- "invalidFields": {
94
- "~union0/description~union0": "Expected string, received object",
95
- "~union0/description~union1/content": "Required",
96
- "~union0/id": "Expected string, received null",
97
- "~union1/id": "Expected number, received null",
98
- "~union1/summary": "Required",
99
- },
100
- "message": "Input validation failed",
101
- }
102
- `),
92
+ {
93
+ "invalidFields": {
94
+ "~union0/description~union0": "Invalid input: expected string, received object",
95
+ "~union0/description~union1/content": "Invalid input: expected string, received undefined",
96
+ "~union0/id": "Invalid input: expected string, received null",
97
+ "~union1/id": "Invalid input: expected number, received null",
98
+ "~union1/summary": "Invalid input: expected string, received undefined",
99
+ },
100
+ "message": "Input validation failed",
101
+ }
102
+ `),
103
103
  );
104
104
  });
105
105
  });
@@ -1,7 +1,7 @@
1
1
  import { ErrorMiddleware } from 'seek-koala';
2
- import { ZodIssueCode, type z } from 'zod';
2
+ import type { core, z } from 'zod/v4';
3
3
 
4
- import type { Context } from 'src/types/koa.js';
4
+ import type { Context } from '#src/types/koa.js';
5
5
 
6
6
  type InvalidFields = Record<string, string>;
7
7
 
@@ -35,41 +35,38 @@ type InvalidFields = Record<string, string>;
35
35
  * @see [union error example](./validation.test.ts)
36
36
  */
37
37
  const parseInvalidFieldsFromError = (err: z.ZodError): InvalidFields =>
38
- Object.fromEntries(parseTuples(err, {}));
38
+ Object.fromEntries(parseTuples(err.issues));
39
39
 
40
40
  const parseTuples = (
41
- { errors }: z.ZodError,
42
- unions: Record<number, number[]>,
41
+ errors: core.$ZodIssue[],
42
+ basePath: Array<string | number | symbol> = [],
43
+ unions: Record<number, number[]> = {},
43
44
  ): Array<readonly [string, string]> =>
44
45
  errors.flatMap((issue) => {
45
- if (issue.code === ZodIssueCode.invalid_union) {
46
- return issue.unionErrors.flatMap((err, idx) =>
47
- parseTuples(err, {
46
+ if (issue.code === 'invalid_union') {
47
+ return issue.errors.flatMap((err, idx) =>
48
+ parseTuples(err, issue.path, {
48
49
  ...unions,
49
50
  [issue.path.length]: [...(unions[issue.path.length] ?? []), idx],
50
51
  }),
51
52
  );
52
53
  }
53
54
 
54
- const path = ['', ...issue.path]
55
+ const path = ['', ...basePath, ...issue.path]
55
56
  .map((prop, idx) => [prop, ...(unions[idx] ?? [])].join('~union'))
56
57
  .join('/');
57
58
 
58
59
  return [[path, issue.message]] as const;
59
60
  });
60
61
 
61
- export const validate = <
62
- Output,
63
- Def extends z.ZodTypeDef = z.ZodTypeDef,
64
- Input = Output,
65
- >({
62
+ export const validate = <Output, Input = Output>({
66
63
  ctx,
67
64
  input,
68
65
  schema,
69
66
  }: {
70
67
  ctx: Context;
71
68
  input: unknown;
72
- schema: z.ZodSchema<Output, Def, Input>;
69
+ schema: z.ZodSchema<Output, Input>;
73
70
  }): Output => {
74
71
  const parseResult = schema.safeParse(input);
75
72
  if (parseResult.success === false) {
@@ -85,15 +82,11 @@ export const validate = <
85
82
  return parseResult.data;
86
83
  };
87
84
 
88
- export const validateRequestBody = <
89
- Output,
90
- Def extends z.ZodTypeDef = z.ZodTypeDef,
91
- Input = Output,
92
- >(
85
+ export const validateRequestBody = <Output, Input = Output>(
93
86
  ctx: Context,
94
- schema: z.ZodSchema<Output, Def, Input>,
87
+ schema: z.ZodSchema<Output, Input>,
95
88
  ): Output =>
96
- validate<Output, Def, Input>({
89
+ validate<Output, Input>({
97
90
  ctx,
98
91
  input: ctx.request.body as unknown,
99
92
  schema,
@@ -1,5 +1,3 @@
1
- import './register.js';
2
-
3
1
  import app from './app.js';
4
2
  import { config } from './config.js';
5
3
  import { logger } from './framework/logging.js';
@@ -1 +0,0 @@
1
- import 'skuba-dive/register';
@@ -1,6 +1,6 @@
1
1
  import { randomUUID } from 'crypto';
2
2
 
3
- import type { Job, JobInput } from 'src/types/jobs.js';
3
+ import type { Job, JobInput } from '#src/types/jobs.js';
4
4
 
5
5
  const jobStore: Record<string, Job> = {};
6
6
 
@@ -1,4 +1,4 @@
1
- import * as metrics from 'src/framework/metrics.js';
1
+ import * as metrics from '#src/framework/metrics.js';
2
2
 
3
3
  function assertDefined<T>(value: T | undefined): asserts value is T {
4
4
  expect(value).toBeDefined();
@@ -2,7 +2,7 @@ import type Router from '@koa/router';
2
2
  import type Koa from 'koa';
3
3
  import request from 'supertest';
4
4
 
5
- import { createApp } from 'src/framework/server.js';
5
+ import { createApp } from '#src/framework/server.js';
6
6
 
7
7
  /**
8
8
  * Create a new SuperTest agent from a Koa application.
@@ -1,7 +1,7 @@
1
1
  import { Chance } from 'chance';
2
- import { z } from 'zod';
2
+ import { z } from 'zod/v4';
3
3
 
4
- import type { JobInput } from 'src/types/jobs.js';
4
+ import type { JobInput } from '#src/types/jobs.js';
5
5
 
6
6
  export type IdDescription = z.infer<typeof IdDescriptionSchema>;
7
7
 
@@ -1,4 +1,4 @@
1
- import { z } from 'zod';
1
+ import { z } from 'zod/v4';
2
2
 
3
3
  export interface Job {
4
4
  id: string;
@@ -23,7 +23,7 @@ configs:
23
23
  concurrency: 1
24
24
  plugins:
25
25
  - *docker-ecr-cache
26
- - docker-compose#v5.9.0:
26
+ - docker-compose#v5.10.0:
27
27
  dependencies: false
28
28
  run: app
29
29
  environment:
@@ -52,7 +52,7 @@ steps:
52
52
  GET_NPM_TOKEN: please
53
53
  plugins:
54
54
  - *docker-ecr-cache
55
- - docker-compose#v5.9.0:
55
+ - docker-compose#v5.10.0:
56
56
  run: app
57
57
  environment:
58
58
  - GITHUB_API_TOKEN
@@ -1,4 +1,4 @@
1
- # syntax=docker/dockerfile:1.16
1
+ # syntax=docker/dockerfile:1.17
2
2
 
3
3
  FROM public.ecr.aws/docker/library/node:22-alpine AS dev-deps
4
4