@platformatic/node 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 +343 -4
- package/index.js +23 -399
- package/lib/capability.js +407 -0
- package/lib/generator.js +134 -0
- package/lib/schema.js +23 -8
- package/lib/utils.js +69 -0
- package/package.json +17 -15
- package/schema.json +1376 -13
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BaseCapability,
|
|
3
|
+
cleanBasePath,
|
|
4
|
+
createServerListener,
|
|
5
|
+
ensureTrailingSlash,
|
|
6
|
+
getServerUrl,
|
|
7
|
+
importFile,
|
|
8
|
+
injectViaRequest
|
|
9
|
+
} from '@platformatic/basic'
|
|
10
|
+
import { features } from '@platformatic/foundation'
|
|
11
|
+
import inject from 'light-my-request'
|
|
12
|
+
import { existsSync } from 'node:fs'
|
|
13
|
+
import { readFile } from 'node:fs/promises'
|
|
14
|
+
import { Server } from 'node:http'
|
|
15
|
+
import { resolve as resolvePath } from 'node:path'
|
|
16
|
+
import { version } from './schema.js'
|
|
17
|
+
import { getTsconfig, ignoreDirs, isApplicationBuildable } from './utils.js'
|
|
18
|
+
|
|
19
|
+
const validFields = [
|
|
20
|
+
'main',
|
|
21
|
+
'exports',
|
|
22
|
+
'exports',
|
|
23
|
+
'exports#node',
|
|
24
|
+
'exports#import',
|
|
25
|
+
'exports#require',
|
|
26
|
+
'exports#default',
|
|
27
|
+
'exports#.#node',
|
|
28
|
+
'exports#.#import',
|
|
29
|
+
'exports#.#require',
|
|
30
|
+
'exports#.#default'
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
const validFilesBasenames = ['index', 'main', 'app', 'application', 'server', 'start', 'bundle', 'run', 'entrypoint']
|
|
34
|
+
|
|
35
|
+
// Paolo: This is kinda hackish but there is no better way. I apologize.
|
|
36
|
+
function isFastify (app) {
|
|
37
|
+
return Object.getOwnPropertySymbols(app).some(s => s.description === 'fastify.state')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isKoa (app) {
|
|
41
|
+
return typeof app.callback === 'function'
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function getEntrypointInformation (root) {
|
|
45
|
+
let entrypoint
|
|
46
|
+
let packageJson
|
|
47
|
+
let hadEntrypointField = false
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
packageJson = JSON.parse(await readFile(resolvePath(root, 'package.json'), 'utf-8'))
|
|
51
|
+
} catch {
|
|
52
|
+
// No package.json, we only load the index.js file
|
|
53
|
+
packageJson = {}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
for (const field of validFields) {
|
|
57
|
+
let current = packageJson
|
|
58
|
+
const sequence = field.split('#')
|
|
59
|
+
|
|
60
|
+
while (current && sequence.length && typeof current !== 'string') {
|
|
61
|
+
current = current[sequence.shift()]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (typeof current === 'string') {
|
|
65
|
+
entrypoint = current
|
|
66
|
+
hadEntrypointField = true
|
|
67
|
+
break
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!entrypoint) {
|
|
72
|
+
for (const basename of validFilesBasenames) {
|
|
73
|
+
for (const ext of ['js', 'mjs', 'cjs']) {
|
|
74
|
+
const file = `${basename}.${ext}`
|
|
75
|
+
|
|
76
|
+
if (existsSync(resolvePath(root, file))) {
|
|
77
|
+
entrypoint = file
|
|
78
|
+
break
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (entrypoint) {
|
|
83
|
+
break
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return { entrypoint, hadEntrypointField }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export class NodeCapability extends BaseCapability {
|
|
92
|
+
#module
|
|
93
|
+
#app
|
|
94
|
+
#server
|
|
95
|
+
#basePath
|
|
96
|
+
#dispatcher
|
|
97
|
+
#isFastify
|
|
98
|
+
#isKoa
|
|
99
|
+
#useHttpForDispatch
|
|
100
|
+
|
|
101
|
+
constructor (root, config, context) {
|
|
102
|
+
super('nodejs', version, root, config, context)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async start ({ listen }) {
|
|
106
|
+
// Make this idempotent
|
|
107
|
+
if (this.url) {
|
|
108
|
+
return this.url
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Listen if entrypoint
|
|
112
|
+
if (this.#app && listen) {
|
|
113
|
+
await this._listen()
|
|
114
|
+
return this.url
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const config = this.config
|
|
118
|
+
|
|
119
|
+
if (!this.isProduction && (await isApplicationBuildable(this.root, config))) {
|
|
120
|
+
this.logger.info(`Building application "${this.applicationId}" before starting in development mode ...`)
|
|
121
|
+
try {
|
|
122
|
+
await this.build()
|
|
123
|
+
this.childManager = null
|
|
124
|
+
} catch (e) {
|
|
125
|
+
this.logger.error(`Error while building application "${this.applicationId}": ${e.message}`)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const command = config.application.commands[this.isProduction ? 'production' : 'development']
|
|
130
|
+
|
|
131
|
+
if (command) {
|
|
132
|
+
return this.startWithCommand(command)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Resolve the entrypoint
|
|
136
|
+
// The priority is platformatic.application.json, then package.json and finally autodetect.
|
|
137
|
+
// Only when autodetecting we eventually search in the dist folder when in production mode
|
|
138
|
+
const finalEntrypoint = await this._findEntrypoint()
|
|
139
|
+
|
|
140
|
+
// Require the application
|
|
141
|
+
this.#basePath = config.application?.basePath
|
|
142
|
+
? ensureTrailingSlash(cleanBasePath(config.application?.basePath))
|
|
143
|
+
: undefined
|
|
144
|
+
|
|
145
|
+
this.registerGlobals({
|
|
146
|
+
basePath: this.#basePath
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
// The server promise must be created before requiring the entrypoint even if it's not going to be used
|
|
150
|
+
// at all. Otherwise there is chance we miss the listen event.
|
|
151
|
+
const serverOptions = this.serverConfig
|
|
152
|
+
const serverPromise = createServerListener(
|
|
153
|
+
(this.isEntrypoint ? serverOptions?.port : undefined) ?? true,
|
|
154
|
+
(this.isEntrypoint ? serverOptions?.hostname : undefined) ?? true
|
|
155
|
+
)
|
|
156
|
+
this.#module = await importFile(finalEntrypoint)
|
|
157
|
+
this.#module = this.#module.default || this.#module
|
|
158
|
+
|
|
159
|
+
// Deal with application
|
|
160
|
+
const factory = ['build', 'create'].find(f => typeof this.#module[f] === 'function')
|
|
161
|
+
|
|
162
|
+
if (config.node?.hasServer !== false && this.#module.hasServer !== false) {
|
|
163
|
+
if (factory) {
|
|
164
|
+
// We have build function, this Capability will not use HTTP unless it is the entrypoint
|
|
165
|
+
serverPromise.cancel()
|
|
166
|
+
|
|
167
|
+
this.#app = await this.#module[factory]()
|
|
168
|
+
this.#isFastify = isFastify(this.#app)
|
|
169
|
+
this.#isKoa = isKoa(this.#app)
|
|
170
|
+
|
|
171
|
+
if (this.#isFastify) {
|
|
172
|
+
await this.#app.ready()
|
|
173
|
+
} else if (this.#isKoa) {
|
|
174
|
+
this.#dispatcher = this.#app.callback()
|
|
175
|
+
} else if (this.#app instanceof Server) {
|
|
176
|
+
this.#server = this.#app
|
|
177
|
+
this.#dispatcher = this.#server.listeners('request')[0]
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (listen) {
|
|
181
|
+
await this._listen()
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
// User blackbox function, we wait for it to listen on a port
|
|
185
|
+
this.#server = await serverPromise
|
|
186
|
+
this.#dispatcher = this.#server.listeners('request')[0]
|
|
187
|
+
|
|
188
|
+
this.url = getServerUrl(this.#server)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
await this._collectMetrics()
|
|
193
|
+
return this.url
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async stop () {
|
|
197
|
+
await super.stop()
|
|
198
|
+
|
|
199
|
+
if (this.childManager) {
|
|
200
|
+
return this.stopCommand()
|
|
201
|
+
// This is needed if the capability was subclassed
|
|
202
|
+
} else if (!this.#server) {
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (this.#isFastify && this.#app) {
|
|
207
|
+
return this.#app.close()
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/* c8 ignore next 3 */
|
|
211
|
+
if (!this.#server?.listening) {
|
|
212
|
+
return
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return new Promise((resolve, reject) => {
|
|
216
|
+
this.#server.close(error => {
|
|
217
|
+
/* c8 ignore next 3 */
|
|
218
|
+
if (error) {
|
|
219
|
+
return reject(error)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
resolve()
|
|
223
|
+
})
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async build () {
|
|
228
|
+
const config = this.config
|
|
229
|
+
const disableChildManager = config.node?.disablePlatformaticInBuild
|
|
230
|
+
const command = config.application?.commands?.build
|
|
231
|
+
|
|
232
|
+
if (command) {
|
|
233
|
+
return this.buildWithCommand(command, null, { disableChildManager })
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// If no command was specified, we try to see if there is a build script defined in package.json.
|
|
237
|
+
const hasBuildScript = await this.#hasBuildScript()
|
|
238
|
+
|
|
239
|
+
if (!hasBuildScript) {
|
|
240
|
+
this.logger.debug(
|
|
241
|
+
'No "application.commands.build" configuration value specified and no build script found in package.json. Skipping build ...'
|
|
242
|
+
)
|
|
243
|
+
return
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return this.buildWithCommand('npm run build', null, { disableChildManager })
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async inject (injectParams, onInject) {
|
|
250
|
+
let res
|
|
251
|
+
|
|
252
|
+
if (this.#useHttpForDispatch) {
|
|
253
|
+
this.logger.trace({ injectParams, url: this.url }, 'injecting via request')
|
|
254
|
+
res = await injectViaRequest(this.url, injectParams, onInject)
|
|
255
|
+
} else {
|
|
256
|
+
if (this.#isFastify) {
|
|
257
|
+
this.logger.trace({ injectParams }, 'injecting via fastify')
|
|
258
|
+
res = await this.#app.inject(injectParams, onInject)
|
|
259
|
+
} else {
|
|
260
|
+
this.logger.trace({ injectParams }, 'injecting via light-my-request')
|
|
261
|
+
res = await inject(this.#dispatcher ?? this.#app, injectParams, onInject)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/* c8 ignore next 3 */
|
|
266
|
+
if (onInject) {
|
|
267
|
+
return
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Since inject might be called from the main thread directly via ITC, let's clean it up
|
|
271
|
+
const { statusCode, headers, body, payload, rawPayload } = res
|
|
272
|
+
|
|
273
|
+
return { statusCode, headers, body, payload, rawPayload }
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
_getWantsAbsoluteUrls () {
|
|
277
|
+
const config = this.config
|
|
278
|
+
return config.node.absoluteUrl
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
getMeta () {
|
|
282
|
+
return {
|
|
283
|
+
gateway: {
|
|
284
|
+
tcp: typeof this.url !== 'undefined',
|
|
285
|
+
url: this.url,
|
|
286
|
+
prefix: this.basePath ?? this.#basePath,
|
|
287
|
+
wantsAbsoluteUrls: this._getWantsAbsoluteUrls(),
|
|
288
|
+
needsRootTrailingSlash: true
|
|
289
|
+
},
|
|
290
|
+
connectionStrings: this.connectionString ? [this.connectionString] : []
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async getDispatchTarget () {
|
|
295
|
+
this.#useHttpForDispatch = this.childManager || (this.url && this.config.node?.dispatchViaHttp === true)
|
|
296
|
+
|
|
297
|
+
if (this.#useHttpForDispatch) {
|
|
298
|
+
return this.getUrl()
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return this.getDispatchFunc()
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async _listen () {
|
|
305
|
+
// Make this idempotent
|
|
306
|
+
/* c8 ignore next 3 */
|
|
307
|
+
if (this.url) {
|
|
308
|
+
return this.url
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const serverOptions = this.serverConfig
|
|
312
|
+
const listenOptions = { host: serverOptions?.hostname || '127.0.0.1', port: serverOptions?.port || 0 }
|
|
313
|
+
|
|
314
|
+
if (this.isProduction && features.node.reusePort) {
|
|
315
|
+
listenOptions.reusePort = true
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (this.#isFastify) {
|
|
319
|
+
await this.#app.listen(listenOptions)
|
|
320
|
+
this.url = getServerUrl(this.#app.server)
|
|
321
|
+
} else {
|
|
322
|
+
// Express / Node / Koa
|
|
323
|
+
this.#server = await new Promise((resolve, reject) => {
|
|
324
|
+
return this.#app
|
|
325
|
+
.listen(listenOptions, function () {
|
|
326
|
+
resolve(this)
|
|
327
|
+
})
|
|
328
|
+
.on('error', reject)
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
this.url = getServerUrl(this.#server)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return this.url
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
_getApplication () {
|
|
338
|
+
return this.#app
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async _findEntrypoint () {
|
|
342
|
+
const config = this.config
|
|
343
|
+
|
|
344
|
+
if (config.node.main) {
|
|
345
|
+
return resolvePath(this.root, config.node.main)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const { entrypoint, hadEntrypointField } = await getEntrypointInformation(this.root)
|
|
349
|
+
|
|
350
|
+
if (typeof this.workerId === 'undefined' || this.workerId === 0) {
|
|
351
|
+
if (!entrypoint) {
|
|
352
|
+
this.logger.error(
|
|
353
|
+
`The application "${this.applicationId}" had no valid entrypoint defined in the package.json file and no valid entrypoint file was found.`
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
process.exit(1)
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (!hadEntrypointField) {
|
|
360
|
+
this.logger.warn(
|
|
361
|
+
`The application "${this.applicationId}" had no valid entrypoint defined in the package.json file. Falling back to the file "${entrypoint}".`
|
|
362
|
+
)
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return resolvePath(this.root, entrypoint)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async #hasBuildScript () {
|
|
370
|
+
// If no command was specified, we try to see if there is a build script defined in package.json.
|
|
371
|
+
let hasBuildScript
|
|
372
|
+
try {
|
|
373
|
+
const packageJson = JSON.parse(await readFile(resolvePath(this.root, 'package.json'), 'utf-8'))
|
|
374
|
+
hasBuildScript = typeof packageJson.scripts.build === 'string' && packageJson.scripts.build
|
|
375
|
+
} catch (e) {
|
|
376
|
+
// No-op
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return hasBuildScript
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
async getWatchConfig () {
|
|
383
|
+
const config = this.config
|
|
384
|
+
|
|
385
|
+
const enabled = config.watch?.enabled !== false
|
|
386
|
+
|
|
387
|
+
if (!enabled) {
|
|
388
|
+
return { enabled, path: this.root }
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// ignore the outDir from tsconfig or application config if any
|
|
392
|
+
let ignore = config.watch?.ignore
|
|
393
|
+
if (!ignore) {
|
|
394
|
+
const tsConfig = await getTsconfig(this.root, config)
|
|
395
|
+
if (tsConfig) {
|
|
396
|
+
ignore = ignoreDirs(tsConfig?.compilerOptions?.outDir, tsConfig?.watchOptions?.excludeDirectories)
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return {
|
|
401
|
+
enabled,
|
|
402
|
+
path: this.root,
|
|
403
|
+
allow: config.watch?.allow,
|
|
404
|
+
ignore
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
package/lib/generator.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
import { BaseGenerator } from '@platformatic/generators'
|
|
4
|
+
import { basename, dirname, sep } from 'node:path'
|
|
5
|
+
|
|
6
|
+
const indexFileJS = `
|
|
7
|
+
import { createServer } from 'node:http'
|
|
8
|
+
|
|
9
|
+
export function create() {
|
|
10
|
+
return createServer((_, res) => {
|
|
11
|
+
globalThis.platformatic.logger.debug('Serving request.')
|
|
12
|
+
res.writeHead(200, { 'content-type': 'application/json', connection: 'close' })
|
|
13
|
+
res.end(JSON.stringify({ hello: 'world' }))
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
`
|
|
17
|
+
|
|
18
|
+
const indexFileTS = `
|
|
19
|
+
import { getGlobal } from '@platformatic/globals'
|
|
20
|
+
import { createServer, type IncomingMessage, type ServerResponse } from 'node:http'
|
|
21
|
+
|
|
22
|
+
export function create() {
|
|
23
|
+
const platformatic = getGlobal()
|
|
24
|
+
|
|
25
|
+
return createServer((_: IncomingMessage, res: ServerResponse) => {
|
|
26
|
+
platformatic.logger.debug('Serving request.')
|
|
27
|
+
res.writeHead(200, { 'content-type': 'application/json', connection: 'close' })
|
|
28
|
+
res.end(JSON.stringify({ hello: 'world' }))
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
`
|
|
32
|
+
|
|
33
|
+
export class Generator extends BaseGenerator {
|
|
34
|
+
constructor (opts = {}) {
|
|
35
|
+
super({
|
|
36
|
+
...opts,
|
|
37
|
+
module: '@platformatic/node'
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async prepareQuestions () {
|
|
42
|
+
await super.prepareQuestions()
|
|
43
|
+
|
|
44
|
+
if (!this.config.skipTypescript) {
|
|
45
|
+
this.questions.push({
|
|
46
|
+
type: 'list',
|
|
47
|
+
name: 'typescript',
|
|
48
|
+
message: 'Do you want to use TypeScript?',
|
|
49
|
+
default: false,
|
|
50
|
+
choices: [
|
|
51
|
+
{ name: 'yes', value: true },
|
|
52
|
+
{ name: 'no', value: false }
|
|
53
|
+
]
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async prepare () {
|
|
59
|
+
await this.getPlatformaticVersion()
|
|
60
|
+
|
|
61
|
+
if (this.config.isUpdating) {
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const main = this.config.main || (this.config.typescript ? 'index.ts' : 'index.js')
|
|
66
|
+
let indexPath = ''
|
|
67
|
+
let indexName = main
|
|
68
|
+
|
|
69
|
+
if (main.indexOf(sep) !== -1) {
|
|
70
|
+
indexPath = dirname(main)
|
|
71
|
+
indexName = basename(main)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let indexTemplate = indexFileJS
|
|
75
|
+
const dependencies = {
|
|
76
|
+
'@platformatic/node': `^${this.platformaticVersion}`
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const devDependencies = {}
|
|
80
|
+
|
|
81
|
+
if (this.config.typescript) {
|
|
82
|
+
indexTemplate = indexFileTS
|
|
83
|
+
|
|
84
|
+
dependencies['@platformatic/globals'] = `^${this.platformaticVersion}`
|
|
85
|
+
devDependencies['@platformatic/tsconfig'] = '^0.1.0'
|
|
86
|
+
devDependencies['@types/node'] = '^22.0.0'
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
this.addFile({ path: indexPath, file: indexName, contents: indexTemplate.trim() + '\n' })
|
|
90
|
+
|
|
91
|
+
this.addFile({
|
|
92
|
+
path: '',
|
|
93
|
+
file: 'package.json',
|
|
94
|
+
contents: JSON.stringify(
|
|
95
|
+
{
|
|
96
|
+
name: `${this.config.applicationName}`,
|
|
97
|
+
main,
|
|
98
|
+
type: 'module',
|
|
99
|
+
dependencies,
|
|
100
|
+
devDependencies
|
|
101
|
+
},
|
|
102
|
+
null,
|
|
103
|
+
2
|
|
104
|
+
)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
if (this.config.typescript) {
|
|
108
|
+
this.addFile({
|
|
109
|
+
path: '',
|
|
110
|
+
file: 'tsconfig.json',
|
|
111
|
+
contents: JSON.stringify({ extends: '@platformatic/tsconfig' }, null, 2)
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
this.addFile({
|
|
116
|
+
path: '',
|
|
117
|
+
file: 'watt.json',
|
|
118
|
+
contents: JSON.stringify(
|
|
119
|
+
{
|
|
120
|
+
$schema: `https://schemas.platformatic.dev/@platformatic/node/${this.platformaticVersion}.json`
|
|
121
|
+
},
|
|
122
|
+
null,
|
|
123
|
+
2
|
|
124
|
+
)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
targetDirectory: this.targetDirectory,
|
|
129
|
+
env: this.config.env
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async _getConfigFileContents () {}
|
|
134
|
+
}
|
package/lib/schema.js
CHANGED
|
@@ -1,19 +1,33 @@
|
|
|
1
1
|
import { schemaComponents as basicSchemaComponents } from '@platformatic/basic'
|
|
2
|
-
import { schemaComponents as utilsSchemaComponents } from '@platformatic/
|
|
2
|
+
import { schemaComponents as utilsSchemaComponents } from '@platformatic/foundation'
|
|
3
3
|
import { readFileSync } from 'node:fs'
|
|
4
|
+
import { resolve } from 'node:path'
|
|
4
5
|
|
|
5
|
-
export const packageJson = JSON.parse(readFileSync(
|
|
6
|
+
export const packageJson = JSON.parse(readFileSync(resolve(import.meta.dirname, '../package.json'), 'utf8'))
|
|
7
|
+
export const version = packageJson.version
|
|
6
8
|
|
|
7
9
|
const node = {
|
|
8
10
|
type: 'object',
|
|
9
11
|
properties: {
|
|
10
12
|
main: {
|
|
11
|
-
type: 'string'
|
|
13
|
+
type: 'string'
|
|
12
14
|
},
|
|
13
15
|
absoluteUrl: {
|
|
14
|
-
description: 'This Node.js application requires the Absolute URL from the
|
|
16
|
+
description: 'This Node.js application requires the Absolute URL from the Gateway',
|
|
17
|
+
type: 'boolean',
|
|
18
|
+
default: false
|
|
19
|
+
},
|
|
20
|
+
dispatchViaHttp: {
|
|
21
|
+
type: 'boolean',
|
|
22
|
+
default: false
|
|
23
|
+
},
|
|
24
|
+
disablePlatformaticInBuild: {
|
|
25
|
+
type: 'boolean',
|
|
26
|
+
default: false
|
|
27
|
+
},
|
|
28
|
+
hasServer: {
|
|
15
29
|
type: 'boolean',
|
|
16
|
-
default:
|
|
30
|
+
default: true
|
|
17
31
|
}
|
|
18
32
|
},
|
|
19
33
|
default: {},
|
|
@@ -23,9 +37,9 @@ const node = {
|
|
|
23
37
|
export const schemaComponents = { node }
|
|
24
38
|
|
|
25
39
|
export const schema = {
|
|
26
|
-
$id: `https://schemas.platformatic.dev/@platformatic/node/${
|
|
40
|
+
$id: `https://schemas.platformatic.dev/@platformatic/node/${version}.json`,
|
|
27
41
|
$schema: 'http://json-schema.org/draft-07/schema#',
|
|
28
|
-
title: 'Platformatic Node.js
|
|
42
|
+
title: 'Platformatic Node.js Config',
|
|
29
43
|
type: 'object',
|
|
30
44
|
properties: {
|
|
31
45
|
$schema: {
|
|
@@ -34,7 +48,8 @@ export const schema = {
|
|
|
34
48
|
logger: utilsSchemaComponents.logger,
|
|
35
49
|
server: utilsSchemaComponents.server,
|
|
36
50
|
watch: basicSchemaComponents.watch,
|
|
37
|
-
application: basicSchemaComponents.
|
|
51
|
+
application: basicSchemaComponents.buildableApplication,
|
|
52
|
+
runtime: utilsSchemaComponents.wrappedRuntime,
|
|
38
53
|
node
|
|
39
54
|
},
|
|
40
55
|
additionalProperties: false
|
package/lib/utils.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import json5 from 'json5'
|
|
2
|
+
import { readFile } from 'node:fs/promises'
|
|
3
|
+
import path, { join } from 'node:path'
|
|
4
|
+
|
|
5
|
+
export async function isApplicationBuildable (applicationRoot, config) {
|
|
6
|
+
// skip vite as capability as it has its own build command
|
|
7
|
+
if (config?.vite) {
|
|
8
|
+
return false
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (config?.application?.commands?.build) {
|
|
12
|
+
return true
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Check if package.json exists and has a build command
|
|
16
|
+
const packageJsonPath = join(applicationRoot, 'package.json')
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
// File exists, try to read and parse it
|
|
20
|
+
try {
|
|
21
|
+
const content = await readFile(packageJsonPath, 'utf8')
|
|
22
|
+
const packageJson = JSON.parse(content)
|
|
23
|
+
if (packageJson.scripts && packageJson.scripts.build) {
|
|
24
|
+
return true
|
|
25
|
+
}
|
|
26
|
+
} catch {
|
|
27
|
+
// Invalid JSON or other read error
|
|
28
|
+
}
|
|
29
|
+
} catch {
|
|
30
|
+
// package.json doesn't exist
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return false
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function getTsconfig (root, config) {
|
|
37
|
+
try {
|
|
38
|
+
const tsConfigPath = config?.plugins?.typescript?.tsConfig || path.resolve(root, 'tsconfig.json')
|
|
39
|
+
const tsConfig = json5.parse(await readFile(tsConfigPath, 'utf8'))
|
|
40
|
+
|
|
41
|
+
return Object.assign(tsConfig.compilerOptions, config?.plugins?.typescript)
|
|
42
|
+
} catch {
|
|
43
|
+
return null
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function ignoreDirs (outDir, watchOptionsExcludeDirectories) {
|
|
48
|
+
const ignore = new Set()
|
|
49
|
+
|
|
50
|
+
if (watchOptionsExcludeDirectories) {
|
|
51
|
+
for (const dir of watchOptionsExcludeDirectories) {
|
|
52
|
+
ignore.add(dir)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (outDir) {
|
|
57
|
+
ignore.add(outDir)
|
|
58
|
+
if (!outDir.endsWith('/**')) {
|
|
59
|
+
ignore.add(`${outDir}/*`)
|
|
60
|
+
ignore.add(`${outDir}/**/*`)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (ignore.size === 0) {
|
|
65
|
+
return ['dist', 'dist/*', 'dist/**/*']
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return Array.from(ignore)
|
|
69
|
+
}
|