@platformatic/next 3.4.1 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/config.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * and run json-schema-to-typescript to regenerate this file.
6
6
  */
7
7
 
8
- export interface PlatformaticNextJsStackable {
8
+ export interface PlatformaticNextJsConfig {
9
9
  $schema?: string;
10
10
  logger?: {
11
11
  level: (
@@ -29,8 +29,6 @@ export interface PlatformaticNextJsStackable {
29
29
  [k: string]: unknown;
30
30
  };
31
31
  level?: string;
32
- additionalProperties?: never;
33
- [k: string]: unknown;
34
32
  }[];
35
33
  options?: {
36
34
  [k: string]: unknown;
@@ -42,6 +40,21 @@ export interface PlatformaticNextJsStackable {
42
40
  [k: string]: unknown;
43
41
  };
44
42
  };
43
+ formatters?: {
44
+ path: string;
45
+ };
46
+ timestamp?: "epochTime" | "unixTime" | "nullTime" | "isoTime";
47
+ redact?: {
48
+ paths: string[];
49
+ censor?: string;
50
+ };
51
+ base?: {
52
+ [k: string]: unknown;
53
+ } | null;
54
+ messageKey?: string;
55
+ customLevels?: {
56
+ [k: string]: unknown;
57
+ };
45
58
  [k: string]: unknown;
46
59
  };
47
60
  server?: {
@@ -49,6 +62,7 @@ export interface PlatformaticNextJsStackable {
49
62
  port?: number | string;
50
63
  http2?: boolean;
51
64
  https?: {
65
+ allowHTTP1?: boolean;
52
66
  key:
53
67
  | string
54
68
  | {
@@ -71,6 +85,8 @@ export interface PlatformaticNextJsStackable {
71
85
  path?: string;
72
86
  }
73
87
  )[];
88
+ requestCert?: boolean;
89
+ rejectUnauthorized?: boolean;
74
90
  };
75
91
  };
76
92
  watch?:
@@ -95,4 +111,333 @@ export interface PlatformaticNextJsStackable {
95
111
  production?: string;
96
112
  };
97
113
  };
114
+ runtime?: {
115
+ preload?: string | string[];
116
+ basePath?: string;
117
+ services?: {
118
+ [k: string]: unknown;
119
+ }[];
120
+ workers?: number | string;
121
+ logger?: {
122
+ level: (
123
+ | ("fatal" | "error" | "warn" | "info" | "debug" | "trace" | "silent")
124
+ | {
125
+ [k: string]: unknown;
126
+ }
127
+ ) &
128
+ string;
129
+ transport?:
130
+ | {
131
+ target?: string;
132
+ options?: {
133
+ [k: string]: unknown;
134
+ };
135
+ }
136
+ | {
137
+ targets?: {
138
+ target?: string;
139
+ options?: {
140
+ [k: string]: unknown;
141
+ };
142
+ level?: string;
143
+ }[];
144
+ options?: {
145
+ [k: string]: unknown;
146
+ };
147
+ };
148
+ pipeline?: {
149
+ target?: string;
150
+ options?: {
151
+ [k: string]: unknown;
152
+ };
153
+ };
154
+ formatters?: {
155
+ path: string;
156
+ };
157
+ timestamp?: "epochTime" | "unixTime" | "nullTime" | "isoTime";
158
+ redact?: {
159
+ paths: string[];
160
+ censor?: string;
161
+ };
162
+ base?: {
163
+ [k: string]: unknown;
164
+ } | null;
165
+ messageKey?: string;
166
+ customLevels?: {
167
+ [k: string]: unknown;
168
+ };
169
+ [k: string]: unknown;
170
+ };
171
+ server?: {
172
+ hostname?: string;
173
+ port?: number | string;
174
+ http2?: boolean;
175
+ https?: {
176
+ allowHTTP1?: boolean;
177
+ key:
178
+ | string
179
+ | {
180
+ path?: string;
181
+ }
182
+ | (
183
+ | string
184
+ | {
185
+ path?: string;
186
+ }
187
+ )[];
188
+ cert:
189
+ | string
190
+ | {
191
+ path?: string;
192
+ }
193
+ | (
194
+ | string
195
+ | {
196
+ path?: string;
197
+ }
198
+ )[];
199
+ requestCert?: boolean;
200
+ rejectUnauthorized?: boolean;
201
+ };
202
+ };
203
+ startTimeout?: number;
204
+ restartOnError?: boolean | number;
205
+ exitOnUnhandledErrors?: boolean;
206
+ gracefulShutdown?: {
207
+ runtime: number | string;
208
+ application: number | string;
209
+ };
210
+ health?: {
211
+ enabled?: boolean | string;
212
+ interval?: number | string;
213
+ gracePeriod?: number | string;
214
+ maxUnhealthyChecks?: number | string;
215
+ maxELU?: number | string;
216
+ maxHeapUsed?: number | string;
217
+ maxHeapTotal?: number | string;
218
+ maxYoungGeneration?: number | string;
219
+ };
220
+ undici?: {
221
+ agentOptions?: {
222
+ [k: string]: unknown;
223
+ };
224
+ interceptors?:
225
+ | {
226
+ module: string;
227
+ options: {
228
+ [k: string]: unknown;
229
+ };
230
+ [k: string]: unknown;
231
+ }[]
232
+ | {
233
+ Client?: {
234
+ module: string;
235
+ options: {
236
+ [k: string]: unknown;
237
+ };
238
+ [k: string]: unknown;
239
+ }[];
240
+ Pool?: {
241
+ module: string;
242
+ options: {
243
+ [k: string]: unknown;
244
+ };
245
+ [k: string]: unknown;
246
+ }[];
247
+ Agent?: {
248
+ module: string;
249
+ options: {
250
+ [k: string]: unknown;
251
+ };
252
+ [k: string]: unknown;
253
+ }[];
254
+ [k: string]: unknown;
255
+ };
256
+ [k: string]: unknown;
257
+ };
258
+ httpCache?:
259
+ | boolean
260
+ | {
261
+ store?: string;
262
+ /**
263
+ * @minItems 1
264
+ */
265
+ methods?: [string, ...string[]];
266
+ cacheTagsHeader?: string;
267
+ maxSize?: number;
268
+ maxEntrySize?: number;
269
+ maxCount?: number;
270
+ [k: string]: unknown;
271
+ };
272
+ watch?: boolean | string;
273
+ managementApi?:
274
+ | boolean
275
+ | string
276
+ | {
277
+ logs?: {
278
+ maxSize?: number;
279
+ };
280
+ };
281
+ metrics?:
282
+ | boolean
283
+ | {
284
+ port?: number | string;
285
+ enabled?: boolean | string;
286
+ hostname?: string;
287
+ endpoint?: string;
288
+ auth?: {
289
+ username: string;
290
+ password: string;
291
+ };
292
+ labels?: {
293
+ [k: string]: string;
294
+ };
295
+ /**
296
+ * The label name to use for the application identifier in metrics (e.g., applicationId, serviceId)
297
+ */
298
+ applicationLabel?: string;
299
+ readiness?:
300
+ | boolean
301
+ | {
302
+ endpoint?: string;
303
+ success?: {
304
+ statusCode?: number;
305
+ body?: string;
306
+ };
307
+ fail?: {
308
+ statusCode?: number;
309
+ body?: string;
310
+ };
311
+ };
312
+ liveness?:
313
+ | boolean
314
+ | {
315
+ endpoint?: string;
316
+ success?: {
317
+ statusCode?: number;
318
+ body?: string;
319
+ };
320
+ fail?: {
321
+ statusCode?: number;
322
+ body?: string;
323
+ };
324
+ };
325
+ plugins?: string[];
326
+ };
327
+ telemetry?: {
328
+ enabled?: boolean | string;
329
+ /**
330
+ * The name of the application. Defaults to the folder name if not specified.
331
+ */
332
+ applicationName: string;
333
+ /**
334
+ * The version of the application (optional)
335
+ */
336
+ version?: string;
337
+ /**
338
+ * An array of paths to skip when creating spans. Useful for health checks and other endpoints that do not need to be traced.
339
+ */
340
+ skip?: {
341
+ /**
342
+ * The path to skip. Can be a string or a regex.
343
+ */
344
+ path?: string;
345
+ /**
346
+ * HTTP method to skip
347
+ */
348
+ method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS";
349
+ [k: string]: unknown;
350
+ }[];
351
+ exporter?:
352
+ | {
353
+ type?: "console" | "otlp" | "zipkin" | "memory" | "file";
354
+ /**
355
+ * Options for the exporter. These are passed directly to the exporter.
356
+ */
357
+ options?: {
358
+ /**
359
+ * The URL to send the traces to. Not used for console or memory exporters.
360
+ */
361
+ url?: string;
362
+ /**
363
+ * Headers to send to the exporter. Not used for console or memory exporters.
364
+ */
365
+ headers?: {
366
+ [k: string]: unknown;
367
+ };
368
+ /**
369
+ * The path to write the traces to. Only for file exporter.
370
+ */
371
+ path?: string;
372
+ [k: string]: unknown;
373
+ };
374
+ additionalProperties?: never;
375
+ [k: string]: unknown;
376
+ }[]
377
+ | {
378
+ type?: "console" | "otlp" | "zipkin" | "memory" | "file";
379
+ /**
380
+ * Options for the exporter. These are passed directly to the exporter.
381
+ */
382
+ options?: {
383
+ /**
384
+ * The URL to send the traces to. Not used for console or memory exporters.
385
+ */
386
+ url?: string;
387
+ /**
388
+ * Headers to send to the exporter. Not used for console or memory exporters.
389
+ */
390
+ headers?: {
391
+ [k: string]: unknown;
392
+ };
393
+ /**
394
+ * The path to write the traces to. Only for file exporter.
395
+ */
396
+ path?: string;
397
+ [k: string]: unknown;
398
+ };
399
+ additionalProperties?: never;
400
+ [k: string]: unknown;
401
+ };
402
+ };
403
+ inspectorOptions?: {
404
+ host?: string;
405
+ port?: number;
406
+ breakFirstLine?: boolean;
407
+ watchDisabled?: boolean;
408
+ [k: string]: unknown;
409
+ };
410
+ applicationTimeout?: number | string;
411
+ messagingTimeout?: number | string;
412
+ env?: {
413
+ [k: string]: string;
414
+ };
415
+ sourceMaps?: boolean;
416
+ scheduler?: {
417
+ enabled?: boolean | string;
418
+ name: string;
419
+ cron: string;
420
+ callbackUrl: string;
421
+ method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
422
+ headers?: {
423
+ [k: string]: string;
424
+ };
425
+ body?:
426
+ | string
427
+ | {
428
+ [k: string]: unknown;
429
+ };
430
+ maxRetries?: number;
431
+ [k: string]: unknown;
432
+ }[];
433
+ };
434
+ next?: {
435
+ trailingSlash?: boolean;
436
+ };
437
+ cache?: {
438
+ adapter: "redis" | "valkey";
439
+ url: string;
440
+ prefix?: string;
441
+ maxTTL?: number | string;
442
+ };
98
443
  }
package/index.js CHANGED
@@ -1,261 +1,37 @@
1
- import {
2
- BaseStackable,
3
- transformConfig as basicTransformConfig,
4
- ChildManager,
5
- cleanBasePath,
6
- createChildProcessListener,
7
- createServerListener,
8
- errors,
9
- getServerUrl,
10
- importFile,
11
- resolvePackage,
12
- schemaOptions
13
- } from '@platformatic/basic'
14
- import { ConfigManager } from '@platformatic/config'
15
- import { once } from 'node:events'
16
- import { readFile } from 'node:fs/promises'
17
- import { dirname, resolve as pathResolve } from 'node:path'
18
- import { pathToFileURL } from 'node:url'
19
- import { satisfies } from 'semver'
20
- import { packageJson, schema } from './lib/schema.js'
21
-
22
- const supportedVersions = '^14.0.0'
23
-
24
- export class NextStackable extends BaseStackable {
25
- #basePath
26
- #next
27
- #manager
28
- #child
29
- #server
30
-
31
- constructor (options, root, configManager) {
32
- super('next', packageJson.version, options, root, configManager)
33
- }
34
-
35
- async init () {
36
- this.#next = pathResolve(dirname(resolvePackage(this.root, 'next')), '../..')
37
- const nextPackage = JSON.parse(await readFile(pathResolve(this.#next, 'package.json'), 'utf-8'))
38
-
39
- /* c8 ignore next 3 */
40
- if (!satisfies(nextPackage.version, supportedVersions)) {
41
- throw new errors.UnsupportedVersion('next', nextPackage.version, supportedVersions)
42
- }
43
- }
44
-
45
- async start ({ listen }) {
46
- // Make this idempotent
47
- if (this.url) {
48
- return this.url
49
- }
50
-
51
- if (this.isProduction) {
52
- await this.#startProduction(listen)
53
- } else {
54
- await this.#startDevelopment(listen)
55
- }
56
- }
57
-
58
- async stop () {
59
- if (this.subprocess) {
60
- return this.stopCommand()
61
- }
62
-
63
- if (this.isProduction) {
64
- return new Promise((resolve, reject) => {
65
- this.#server.close(error => {
66
- /* c8 ignore next 3 */
67
- if (error) {
68
- return reject(error)
69
- }
70
-
71
- resolve()
72
- })
73
- })
74
- } else {
75
- const exitPromise = once(this.#child, 'exit')
76
- await this.#manager.close()
77
- process.kill(this.#child.pid, 'SIGKILL')
78
- await exitPromise
79
- }
80
- }
81
-
82
- async build () {
83
- const config = this.configManager.current
84
- const loader = new URL('./lib/loader.js', import.meta.url)
85
- this.#basePath = config.application?.basePath ? cleanBasePath(config.application?.basePath) : ''
86
-
87
- let command = config.application.commands.build
88
-
89
- if (!command) {
90
- await this.init()
91
- command = ['node', pathResolve(this.#next, './dist/bin/next'), 'build', this.root]
92
- }
93
-
94
- return this.buildWithCommand(command, this.#basePath, loader)
95
- }
96
-
97
- /* c8 ignore next 5 */
98
- async getWatchConfig () {
99
- return {
100
- enabled: false
101
- }
102
- }
103
-
104
- getMeta () {
105
- const composer = { prefix: this.basePath ?? this.#basePath, wantsAbsoluteUrls: true, needsRootRedirect: false }
106
-
107
- if (this.url) {
108
- composer.tcp = true
109
- composer.url = this.url
110
- }
111
-
112
- return { composer }
113
- }
114
-
115
- async #startDevelopment () {
116
- const config = this.configManager.current
117
- const loaderUrl = new URL('./lib/loader.js', import.meta.url)
118
- const command = this.configManager.current.application.commands.development
119
-
120
- this.#basePath = config.application?.basePath ? cleanBasePath(config.application?.basePath) : ''
121
-
122
- if (command) {
123
- return this.startWithCommand(command, loaderUrl)
124
- }
125
-
126
- const { hostname, port } = this.serverConfig ?? {}
127
- const serverOptions = {
128
- host: hostname || '127.0.0.1',
129
- port: port || 0
130
- }
131
-
132
- this.#manager = new ChildManager({
133
- loader: loaderUrl,
134
- context: {
135
- id: this.id,
136
- // Always use URL to avoid serialization problem in Windows
137
- root: pathToFileURL(this.root).toString(),
138
- basePath: this.#basePath,
139
- logLevel: this.logger.level,
140
- port: false
141
- }
142
- })
143
-
144
- const promise = once(this.#manager, 'url')
145
- await this.#startDevelopmentNext(serverOptions)
146
- this.url = (await promise)[0]
147
- }
148
-
149
- async #startDevelopmentNext (serverOptions) {
150
- const { nextDev } = await importFile(pathResolve(this.#next, './dist/cli/next-dev.js'))
151
-
152
- this.#manager.on('config', config => {
153
- this.#basePath = config.basePath
154
- })
155
-
156
- try {
157
- await this.#manager.inject()
158
- const childPromise = createChildProcessListener()
159
- await nextDev(serverOptions, 'default', this.root)
160
- this.#child = await childPromise
161
- } finally {
162
- await this.#manager.eject()
163
- }
164
- }
165
-
166
- async #startProduction (listen) {
167
- const config = this.configManager.current
168
- const loaderUrl = new URL('./lib/loader.js', import.meta.url)
169
- const command = this.configManager.current.application.commands.production
170
-
171
- this.#basePath = config.application?.basePath ? cleanBasePath(config.application?.basePath) : ''
172
-
173
- if (command) {
174
- return this.startWithCommand(command, loaderUrl)
175
- }
176
-
177
- this.#manager = new ChildManager({
178
- loader: loaderUrl,
179
- context: {
180
- id: this.id,
181
- // Always use URL to avoid serialization problem in Windows
182
- root: pathToFileURL(this.root).toString(),
183
- basePath: this.#basePath,
184
- logLevel: this.logger.level
185
- }
186
- })
187
-
188
- this.verifyOutputDirectory(pathResolve(this.root, '.next'))
189
- await this.#startProductionNext()
190
- }
191
-
192
- async #startProductionNext () {
193
- try {
194
- await this.#manager.inject()
195
- const { nextStart } = await importFile(pathResolve(this.#next, './dist/cli/next-start.js'))
196
-
197
- const { hostname, port } = this.serverConfig ?? {}
198
- const serverOptions = {
199
- hostname: hostname || '127.0.0.1',
200
- port: port || 0
201
- }
202
-
203
- // Since we are in the same process
204
- process.once('plt:next:config', config => {
205
- this.#basePath = config.basePath
206
- })
207
-
208
- this.#manager.register()
209
- const serverPromise = createServerListener(
210
- (this.isEntrypoint ? serverOptions?.port : undefined) ?? true,
211
- (this.isEntrypoint ? serverOptions?.hostname : undefined) ?? true
212
- )
213
-
214
- await nextStart(serverOptions, this.root)
215
-
216
- this.#server = await serverPromise
217
- this.url = getServerUrl(this.#server)
218
- } finally {
219
- await this.#manager.eject()
220
- }
221
- }
222
- }
1
+ import { transform as basicTransform, resolve, validationOptions } from '@platformatic/basic'
2
+ import { kMetadata, loadConfiguration as utilsLoadConfiguration } from '@platformatic/foundation'
3
+ import { NextCapability } from './lib/capability.js'
4
+ import { schema } from './lib/schema.js'
223
5
 
224
6
  /* c8 ignore next 9 */
225
- function transformConfig () {
226
- if (this.current.watch === undefined) {
227
- this.current.watch = { enabled: false }
228
- }
7
+ export async function transform (config, schema, options) {
8
+ config = await basicTransform(config, schema, options)
9
+ config.watch = { enabled: false }
229
10
 
230
- if (typeof this.current.watch !== 'object') {
231
- this.current.watch = { enabled: this.current.watch || false }
11
+ if (config.cache?.adapter === 'redis') {
12
+ config.cache.adapter = 'valkey'
232
13
  }
233
14
 
234
- basicTransformConfig.call(this)
15
+ return config
235
16
  }
236
17
 
237
- export async function buildStackable (opts) {
238
- const root = opts.context.directory
18
+ export async function loadConfiguration (configOrRoot, sourceOrConfig, context) {
19
+ const { root, source } = await resolve(configOrRoot, sourceOrConfig, 'application')
239
20
 
240
- const configManager = new ConfigManager({
241
- schema,
242
- source: opts.config ?? {},
243
- schemaOptions,
244
- transformConfig,
245
- dirname: root
21
+ return utilsLoadConfiguration(source, context?.schema ?? schema, {
22
+ validationOptions,
23
+ transform,
24
+ replaceEnv: true,
25
+ root,
26
+ ...context
246
27
  })
247
- await configManager.parseAndValidate()
248
-
249
- return new NextStackable(opts, root, configManager)
250
28
  }
251
29
 
252
- export default {
253
- configType: 'next',
254
- configManagerConfig: {
255
- schemaOptions,
256
- transformConfig
257
- },
258
- buildStackable,
259
- schema,
260
- version: packageJson.version
30
+ export async function create (configOrRoot, sourceOrConfig, context) {
31
+ const config = await loadConfiguration(configOrRoot, sourceOrConfig, context)
32
+ return new NextCapability(config[kMetadata].root, config, context)
261
33
  }
34
+
35
+ export * as cachingValkey from './lib/caching/valkey.js'
36
+ export * from './lib/capability.js'
37
+ export { packageJson, schema, schemaComponents, version } from './lib/schema.js'