@platformatic/basic 2.0.0-alpha.3 → 2.0.0-alpha.4
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 +159 -0
- package/eslint.config.js +3 -1
- package/index.js +15 -2
- package/lib/base.js +7 -5
- package/lib/errors.js +8 -0
- package/lib/next.js +105 -0
- package/lib/schema.js +23 -0
- package/lib/server.js +8 -9
- package/lib/utils.js +5 -34
- package/lib/vite.js +105 -0
- package/lib/worker/child-manager.js +78 -0
- package/lib/worker/child-process.js +84 -0
- package/lib/worker/next-loader.js +137 -0
- package/lib/worker/server-listener.js +29 -0
- package/package.json +22 -8
- package/schema.json +522 -0
- package/test/express.test.js +9 -9
- package/test/fastify.test.js +9 -9
- package/test/fixtures/next/composer-autodetect-prefix/next.config.mjs +5 -0
- package/test/fixtures/next/composer-autodetect-prefix/package.json +15 -0
- package/test/fixtures/next/composer-autodetect-prefix/platformatic.application.json +8 -0
- package/test/fixtures/next/composer-autodetect-prefix/platformatic.runtime.json +21 -0
- package/test/fixtures/next/composer-autodetect-prefix/src/app/layout.js +7 -0
- package/test/fixtures/next/composer-autodetect-prefix/src/app/page.js +5 -0
- package/test/fixtures/next/composer-with-prefix/next.config.js +1 -0
- package/test/fixtures/next/composer-with-prefix/package.json +15 -0
- package/test/fixtures/next/composer-with-prefix/platformatic.application.json +11 -0
- package/test/fixtures/next/composer-with-prefix/platformatic.runtime.json +21 -0
- package/test/fixtures/next/composer-with-prefix/src/app/layout.js +7 -0
- package/test/fixtures/next/composer-with-prefix/src/app/page.js +5 -0
- package/test/fixtures/next/composer-without-prefix/next.config.mjs +3 -0
- package/test/fixtures/next/composer-without-prefix/package.json +15 -0
- package/test/fixtures/next/composer-without-prefix/platformatic.application.json +8 -0
- package/test/fixtures/next/composer-without-prefix/platformatic.runtime.json +21 -0
- package/test/fixtures/next/composer-without-prefix/src/app/layout.js +7 -0
- package/test/fixtures/next/composer-without-prefix/src/app/page.js +5 -0
- package/test/fixtures/next/standalone/next.config.mjs +3 -0
- package/test/fixtures/next/standalone/package.json +15 -0
- package/test/fixtures/next/standalone/platformatic.runtime.json +18 -0
- package/test/fixtures/next/standalone/src/app/layout.js +7 -0
- package/test/fixtures/next/standalone/src/app/page.js +5 -0
- package/test/fixtures/platformatic-composer/platformatic.composer.json +20 -0
- package/test/fixtures/platformatic-composer/platformatic.no-prefix.composer.json +23 -0
- package/test/fixtures/platformatic-composer/plugin.js +9 -0
- package/test/fixtures/vite/composer-autodetect-prefix/custom.vite.config.js +3 -0
- package/test/fixtures/vite/composer-autodetect-prefix/index.html +13 -0
- package/test/fixtures/vite/composer-autodetect-prefix/main.js +3 -0
- package/test/fixtures/vite/composer-autodetect-prefix/package.json +14 -0
- package/test/fixtures/vite/composer-autodetect-prefix/platformatic.application.json +11 -0
- package/test/fixtures/vite/composer-autodetect-prefix/platformatic.runtime.json +21 -0
- package/test/fixtures/vite/composer-with-prefix/index.html +13 -0
- package/test/fixtures/vite/composer-with-prefix/main.js +3 -0
- package/test/fixtures/vite/composer-with-prefix/package.json +14 -0
- package/test/fixtures/vite/composer-with-prefix/platformatic.application.json +11 -0
- package/test/fixtures/vite/composer-with-prefix/platformatic.runtime.json +21 -0
- package/test/fixtures/vite/composer-without-prefix/index.html +13 -0
- package/test/fixtures/vite/composer-without-prefix/main.js +3 -0
- package/test/fixtures/vite/composer-without-prefix/package.json +14 -0
- package/test/fixtures/vite/composer-without-prefix/platformatic.application.json +8 -0
- package/test/fixtures/vite/composer-without-prefix/platformatic.runtime.json +21 -0
- package/test/fixtures/vite/standalone/custom.vite.config.js +3 -0
- package/test/fixtures/vite/standalone/index.html +13 -0
- package/test/fixtures/vite/standalone/main.js +3 -0
- package/test/fixtures/vite/standalone/package.json +14 -0
- package/test/fixtures/vite/standalone/platformatic.runtime.json +18 -0
- package/test/helper.js +85 -14
- package/test/next.test.js +85 -0
- package/test/node.test.js +13 -13
- package/test/vite.test.js +89 -0
package/config.d.ts
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
/**
|
|
3
|
+
* This file was automatically generated by json-schema-to-typescript.
|
|
4
|
+
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
|
|
5
|
+
* and run json-schema-to-typescript to regenerate this file.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface PlatformaticStackable {
|
|
9
|
+
$schema?: string;
|
|
10
|
+
server?: {
|
|
11
|
+
hostname?: string;
|
|
12
|
+
port?: number | string;
|
|
13
|
+
pluginTimeout?: number;
|
|
14
|
+
healthCheck?:
|
|
15
|
+
| boolean
|
|
16
|
+
| {
|
|
17
|
+
enabled?: boolean;
|
|
18
|
+
interval?: number;
|
|
19
|
+
[k: string]: unknown;
|
|
20
|
+
};
|
|
21
|
+
ignoreTrailingSlash?: boolean;
|
|
22
|
+
ignoreDuplicateSlashes?: boolean;
|
|
23
|
+
connectionTimeout?: number;
|
|
24
|
+
keepAliveTimeout?: number;
|
|
25
|
+
maxRequestsPerSocket?: number;
|
|
26
|
+
forceCloseConnections?: boolean | string;
|
|
27
|
+
requestTimeout?: number;
|
|
28
|
+
bodyLimit?: number;
|
|
29
|
+
maxParamLength?: number;
|
|
30
|
+
disableRequestLogging?: boolean;
|
|
31
|
+
exposeHeadRoutes?: boolean;
|
|
32
|
+
logger?:
|
|
33
|
+
| boolean
|
|
34
|
+
| {
|
|
35
|
+
level?: string;
|
|
36
|
+
transport?:
|
|
37
|
+
| {
|
|
38
|
+
target?: string;
|
|
39
|
+
options?: {
|
|
40
|
+
[k: string]: unknown;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
| {
|
|
44
|
+
targets?: {
|
|
45
|
+
target?: string;
|
|
46
|
+
options?: {
|
|
47
|
+
[k: string]: unknown;
|
|
48
|
+
};
|
|
49
|
+
level?: string;
|
|
50
|
+
additionalProperties?: never;
|
|
51
|
+
[k: string]: unknown;
|
|
52
|
+
}[];
|
|
53
|
+
options?: {
|
|
54
|
+
[k: string]: unknown;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
pipeline?: {
|
|
58
|
+
target?: string;
|
|
59
|
+
options?: {
|
|
60
|
+
[k: string]: unknown;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
[k: string]: unknown;
|
|
64
|
+
};
|
|
65
|
+
serializerOpts?: {
|
|
66
|
+
schema?: {
|
|
67
|
+
[k: string]: unknown;
|
|
68
|
+
};
|
|
69
|
+
ajv?: {
|
|
70
|
+
[k: string]: unknown;
|
|
71
|
+
};
|
|
72
|
+
rounding?: "floor" | "ceil" | "round" | "trunc";
|
|
73
|
+
debugMode?: boolean;
|
|
74
|
+
mode?: "debug" | "standalone";
|
|
75
|
+
largeArraySize?: number | string;
|
|
76
|
+
largeArrayMechanism?: "default" | "json-stringify";
|
|
77
|
+
[k: string]: unknown;
|
|
78
|
+
};
|
|
79
|
+
caseSensitive?: boolean;
|
|
80
|
+
requestIdHeader?: string | false;
|
|
81
|
+
requestIdLogLabel?: string;
|
|
82
|
+
jsonShorthand?: boolean;
|
|
83
|
+
trustProxy?: boolean | string | string[] | number;
|
|
84
|
+
http2?: boolean;
|
|
85
|
+
https?: {
|
|
86
|
+
allowHTTP1?: boolean;
|
|
87
|
+
key:
|
|
88
|
+
| string
|
|
89
|
+
| {
|
|
90
|
+
path?: string;
|
|
91
|
+
}
|
|
92
|
+
| (
|
|
93
|
+
| string
|
|
94
|
+
| {
|
|
95
|
+
path?: string;
|
|
96
|
+
}
|
|
97
|
+
)[];
|
|
98
|
+
cert:
|
|
99
|
+
| string
|
|
100
|
+
| {
|
|
101
|
+
path?: string;
|
|
102
|
+
}
|
|
103
|
+
| (
|
|
104
|
+
| string
|
|
105
|
+
| {
|
|
106
|
+
path?: string;
|
|
107
|
+
}
|
|
108
|
+
)[];
|
|
109
|
+
requestCert?: boolean;
|
|
110
|
+
rejectUnauthorized?: boolean;
|
|
111
|
+
};
|
|
112
|
+
cors?: {
|
|
113
|
+
origin?:
|
|
114
|
+
| boolean
|
|
115
|
+
| string
|
|
116
|
+
| (
|
|
117
|
+
| string
|
|
118
|
+
| {
|
|
119
|
+
regexp: string;
|
|
120
|
+
[k: string]: unknown;
|
|
121
|
+
}
|
|
122
|
+
)[]
|
|
123
|
+
| {
|
|
124
|
+
regexp: string;
|
|
125
|
+
[k: string]: unknown;
|
|
126
|
+
};
|
|
127
|
+
methods?: string[];
|
|
128
|
+
/**
|
|
129
|
+
* Comma separated string of allowed headers.
|
|
130
|
+
*/
|
|
131
|
+
allowedHeaders?: string;
|
|
132
|
+
exposedHeaders?: string[] | string;
|
|
133
|
+
credentials?: boolean;
|
|
134
|
+
maxAge?: number;
|
|
135
|
+
preflightContinue?: boolean;
|
|
136
|
+
optionsSuccessStatus?: number;
|
|
137
|
+
preflight?: boolean;
|
|
138
|
+
strictPreflight?: boolean;
|
|
139
|
+
hideOptionsRoute?: boolean;
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
watch?:
|
|
143
|
+
| {
|
|
144
|
+
enabled?: boolean | string;
|
|
145
|
+
/**
|
|
146
|
+
* @minItems 1
|
|
147
|
+
*/
|
|
148
|
+
allow?: [string, ...string[]];
|
|
149
|
+
ignore?: string[];
|
|
150
|
+
}
|
|
151
|
+
| boolean
|
|
152
|
+
| string;
|
|
153
|
+
application?: {
|
|
154
|
+
base?: string;
|
|
155
|
+
};
|
|
156
|
+
vite?: {
|
|
157
|
+
configFile?: string | boolean;
|
|
158
|
+
};
|
|
159
|
+
}
|
package/eslint.config.js
CHANGED
package/index.js
CHANGED
|
@@ -6,8 +6,10 @@ import { resolve } from 'node:path'
|
|
|
6
6
|
|
|
7
7
|
import { ConfigManager } from '@platformatic/config'
|
|
8
8
|
|
|
9
|
+
import { NextStackable } from './lib/next.js'
|
|
9
10
|
import { packageJson, schema } from './lib/schema.js'
|
|
10
11
|
import { ServerStackable } from './lib/server.js'
|
|
12
|
+
import { ViteStackable } from './lib/vite.js'
|
|
11
13
|
|
|
12
14
|
const validFields = [
|
|
13
15
|
'main',
|
|
@@ -84,12 +86,23 @@ function transformConfig () {
|
|
|
84
86
|
|
|
85
87
|
export async function buildStackable (opts) {
|
|
86
88
|
const root = opts.context.directory
|
|
87
|
-
const {
|
|
89
|
+
const {
|
|
90
|
+
entrypoint,
|
|
91
|
+
hadEntrypointField,
|
|
92
|
+
packageJson: { dependencies, devDependencies },
|
|
93
|
+
} = await parsePackageJson(root)
|
|
88
94
|
|
|
89
95
|
const configManager = new ConfigManager({ schema, source: opts.config ?? {} })
|
|
90
96
|
await configManager.parseAndValidate()
|
|
91
97
|
|
|
92
|
-
|
|
98
|
+
let stackable
|
|
99
|
+
if (dependencies?.next || devDependencies?.next) {
|
|
100
|
+
stackable = new NextStackable(opts, root, configManager)
|
|
101
|
+
} else if (dependencies?.vite || devDependencies?.vite) {
|
|
102
|
+
stackable = new ViteStackable(opts, root, configManager)
|
|
103
|
+
} else {
|
|
104
|
+
stackable = new ServerStackable(opts, root, configManager, entrypoint, hadEntrypointField)
|
|
105
|
+
}
|
|
93
106
|
|
|
94
107
|
return stackable
|
|
95
108
|
}
|
package/lib/base.js
CHANGED
|
@@ -12,7 +12,9 @@ export class BaseStackable {
|
|
|
12
12
|
this.getGraphqlSchema = null
|
|
13
13
|
|
|
14
14
|
// Setup the logger
|
|
15
|
-
const pinoOptions = {
|
|
15
|
+
const pinoOptions = {
|
|
16
|
+
level: (this.configManager.current.server ?? this.serverConfig)?.logger?.level ?? 'trace',
|
|
17
|
+
}
|
|
16
18
|
|
|
17
19
|
if (this.id) {
|
|
18
20
|
pinoOptions.name = this.id
|
|
@@ -21,8 +23,8 @@ export class BaseStackable {
|
|
|
21
23
|
|
|
22
24
|
// Setup globals
|
|
23
25
|
globalThis.platformatic = {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
setOpenapiSchema: this.setOpenapiSchema.bind(this),
|
|
27
|
+
setGraphqlSchema: this.setGraphqlSchema.bind(this),
|
|
26
28
|
}
|
|
27
29
|
}
|
|
28
30
|
|
|
@@ -67,11 +69,11 @@ export class BaseStackable {
|
|
|
67
69
|
return this.graphqlSchema
|
|
68
70
|
}
|
|
69
71
|
|
|
70
|
-
|
|
72
|
+
setOpenapiSchema (schema) {
|
|
71
73
|
this.openapiSchema = schema
|
|
72
74
|
}
|
|
73
75
|
|
|
74
|
-
|
|
76
|
+
setGraphqlSchema (schema) {
|
|
75
77
|
this.graphqlSchema = schema
|
|
76
78
|
}
|
|
77
79
|
|
package/lib/errors.js
ADDED
package/lib/next.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { once } from 'node:events'
|
|
2
|
+
import { readFile } from 'node:fs/promises'
|
|
3
|
+
import { createRequire } from 'node:module'
|
|
4
|
+
import { dirname, resolve as pathResolve } from 'node:path'
|
|
5
|
+
import { pathToFileURL } from 'node:url'
|
|
6
|
+
import { satisfies } from 'semver'
|
|
7
|
+
import { BaseStackable } from './base.js'
|
|
8
|
+
import { UnsupportedVersion } from './errors.js'
|
|
9
|
+
import { importFile } from './utils.js'
|
|
10
|
+
import { ChildManager } from './worker/child-manager.js'
|
|
11
|
+
|
|
12
|
+
const supportedVersions = '^14.0.0'
|
|
13
|
+
|
|
14
|
+
export class NextStackable extends BaseStackable {
|
|
15
|
+
#basePath
|
|
16
|
+
#next
|
|
17
|
+
#manager
|
|
18
|
+
|
|
19
|
+
constructor (options, root, configManager) {
|
|
20
|
+
super(options, root, configManager)
|
|
21
|
+
this.type = 'next'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async init () {
|
|
25
|
+
globalThis[Symbol.for('plt.runtime.itc')].handle('getServiceMeta', this.getMeta.bind(this))
|
|
26
|
+
|
|
27
|
+
this.#next = pathResolve(dirname(createRequire(this.root).resolve('next')), '../..')
|
|
28
|
+
const nextPackage = JSON.parse(await readFile(pathResolve(this.#next, 'package.json')))
|
|
29
|
+
|
|
30
|
+
if (!satisfies(nextPackage.version, supportedVersions)) {
|
|
31
|
+
throw new UnsupportedVersion('next', nextPackage.version, supportedVersions)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async start () {
|
|
36
|
+
// Make this idempotent
|
|
37
|
+
if (this.url) {
|
|
38
|
+
return this.url
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const config = this.configManager.current
|
|
42
|
+
const require = createRequire(this.root)
|
|
43
|
+
const nextRoot = require.resolve('next')
|
|
44
|
+
|
|
45
|
+
const { hostname, port } = this.serverConfig ?? {}
|
|
46
|
+
const serverOptions = {
|
|
47
|
+
host: hostname || '127.0.0.1',
|
|
48
|
+
port: port || 0,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.#basePath = config.application?.base
|
|
52
|
+
? `/${config.application?.base}`.replaceAll(/\/+/g, '/').replace(/\/$/, '')
|
|
53
|
+
: ''
|
|
54
|
+
|
|
55
|
+
this.#manager = new ChildManager({
|
|
56
|
+
loader: new URL('./worker/next-loader.js', import.meta.url),
|
|
57
|
+
context: {
|
|
58
|
+
// Always use URL to avoid serialization problem in Windows
|
|
59
|
+
root: pathToFileURL(this.root),
|
|
60
|
+
basePath: this.#basePath,
|
|
61
|
+
logger: { id: this.id, level: this.logger.level },
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
this.#manager.on('config', config => {
|
|
66
|
+
this.#basePath = config.basePath.replace(/(^\/)|(\/$)/g, '')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const promise = once(this.#manager, 'url')
|
|
70
|
+
await this.#startNext(nextRoot, serverOptions)
|
|
71
|
+
this.url = (await promise)[0]
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async stop () {
|
|
75
|
+
const exitPromise = once(this.#manager, 'exit')
|
|
76
|
+
|
|
77
|
+
this.#manager.close()
|
|
78
|
+
await exitPromise
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async getWatchConfig () {
|
|
82
|
+
return {
|
|
83
|
+
enabled: false,
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
getMeta () {
|
|
88
|
+
return {
|
|
89
|
+
composer: {
|
|
90
|
+
tcp: true,
|
|
91
|
+
url: this.url,
|
|
92
|
+
prefix: this.#basePath,
|
|
93
|
+
wantsAbsoluteUrls: true,
|
|
94
|
+
},
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async #startNext (nextRoot, serverOptions) {
|
|
99
|
+
const { nextDev } = await importFile(pathResolve(this.#next, './dist/cli/next-dev.js'))
|
|
100
|
+
|
|
101
|
+
this.#manager.inject()
|
|
102
|
+
await nextDev(serverOptions, 'default', this.root)
|
|
103
|
+
this.#manager.eject()
|
|
104
|
+
}
|
|
105
|
+
}
|
package/lib/schema.js
CHANGED
|
@@ -25,6 +25,29 @@ export const schema = {
|
|
|
25
25
|
},
|
|
26
26
|
],
|
|
27
27
|
},
|
|
28
|
+
application: {
|
|
29
|
+
type: 'object',
|
|
30
|
+
properties: {
|
|
31
|
+
base: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
additionalProperties: false,
|
|
36
|
+
},
|
|
37
|
+
vite: {
|
|
38
|
+
type: 'object',
|
|
39
|
+
properties: {
|
|
40
|
+
configFile: {
|
|
41
|
+
oneOf: [{ type: 'string' }, { type: 'boolean' }],
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
additionalProperties: false,
|
|
45
|
+
},
|
|
28
46
|
},
|
|
29
47
|
additionalProperties: false,
|
|
30
48
|
}
|
|
49
|
+
|
|
50
|
+
/* c8 ignore next 3 */
|
|
51
|
+
if (process.argv[1] === import.meta.filename) {
|
|
52
|
+
console.log(JSON.stringify(schema, null, 2))
|
|
53
|
+
}
|
package/lib/server.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import inject from 'light-my-request'
|
|
2
2
|
import { Server } from 'node:http'
|
|
3
|
-
import {
|
|
4
|
-
import { pathToFileURL } from 'node:url'
|
|
3
|
+
import { resolve as pathResolve } from 'node:path'
|
|
5
4
|
import { BaseStackable } from './base.js'
|
|
6
|
-
import {
|
|
5
|
+
import { getServerUrl, importFile, injectViaRequest, isFastify } from './utils.js'
|
|
6
|
+
import { createServerListener } from './worker/server-listener.js'
|
|
7
7
|
|
|
8
8
|
export class ServerStackable extends BaseStackable {
|
|
9
9
|
#entrypoint
|
|
@@ -39,16 +39,16 @@ export class ServerStackable extends BaseStackable {
|
|
|
39
39
|
)
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
// The
|
|
42
|
+
// The server promise must be created before requiring the entrypoint even if it's not going to be used
|
|
43
43
|
// at all. Otherwise there is chance we miss the listen event.
|
|
44
|
-
const
|
|
45
|
-
this.#module = await
|
|
44
|
+
const serverPromise = createServerListener()
|
|
45
|
+
this.#module = await importFile(pathResolve(this.root, this.#entrypoint))
|
|
46
46
|
this.#module = this.#module.default || this.#module
|
|
47
47
|
|
|
48
48
|
// Deal with application
|
|
49
49
|
if (typeof this.#module.build === 'function') {
|
|
50
50
|
// We have build function, this Stackable will not use HTTP unless it is the entrypoint
|
|
51
|
-
|
|
51
|
+
serverPromise.cancel()
|
|
52
52
|
|
|
53
53
|
this.#app = await this.#module.build()
|
|
54
54
|
this.#isFastify = isFastify(this.#app)
|
|
@@ -61,9 +61,8 @@ export class ServerStackable extends BaseStackable {
|
|
|
61
61
|
}
|
|
62
62
|
} else {
|
|
63
63
|
// User blackbox function, we wait for it to listen on a port
|
|
64
|
-
this.#server = await
|
|
64
|
+
this.#server = await serverPromise
|
|
65
65
|
this.url = getServerUrl(this.#server)
|
|
66
|
-
portManager.destroy()
|
|
67
66
|
}
|
|
68
67
|
|
|
69
68
|
return this.url
|
package/lib/utils.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { tracingChannel } from 'node:diagnostics_channel'
|
|
1
|
+
import { pathToFileURL } from 'node:url'
|
|
4
2
|
import { request } from 'undici'
|
|
5
3
|
|
|
6
4
|
export function getServerUrl (server) {
|
|
@@ -14,37 +12,6 @@ export function isFastify (app) {
|
|
|
14
12
|
return Object.getOwnPropertySymbols(app).some(s => s.description === 'fastify.state')
|
|
15
13
|
}
|
|
16
14
|
|
|
17
|
-
export function createPortManager () {
|
|
18
|
-
let resolve
|
|
19
|
-
let reject
|
|
20
|
-
|
|
21
|
-
const promise = new Promise((_resolve, _reject) => {
|
|
22
|
-
resolve = _resolve
|
|
23
|
-
reject = _reject
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
const subscribers = {
|
|
27
|
-
asyncStart ({ options }) {
|
|
28
|
-
options.port = 0
|
|
29
|
-
},
|
|
30
|
-
asyncEnd: ({ server }) => {
|
|
31
|
-
resolve(server)
|
|
32
|
-
},
|
|
33
|
-
error: reject,
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
tracingChannel('net.server.listen').subscribe(subscribers)
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
getServer () {
|
|
40
|
-
return promise
|
|
41
|
-
},
|
|
42
|
-
destroy () {
|
|
43
|
-
tracingChannel('net.server.listen').unsubscribe(subscribers)
|
|
44
|
-
},
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
15
|
export async function injectViaRequest (baseUrl, injectParams, onInject) {
|
|
49
16
|
const url = new URL(injectParams.url, baseUrl).href
|
|
50
17
|
const requestParams = { method: injectParams.method, headers: injectParams.headers }
|
|
@@ -75,3 +42,7 @@ export async function injectViaRequest (baseUrl, injectParams, onInject) {
|
|
|
75
42
|
throw error
|
|
76
43
|
}
|
|
77
44
|
}
|
|
45
|
+
|
|
46
|
+
export function importFile (path) {
|
|
47
|
+
return import(pathToFileURL(path))
|
|
48
|
+
}
|
package/lib/vite.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { createRequire } from 'node:module'
|
|
2
|
+
import { dirname, resolve as pathResolve } from 'node:path'
|
|
3
|
+
import { satisfies } from 'semver'
|
|
4
|
+
import { BaseStackable } from './base.js'
|
|
5
|
+
import { getServerUrl, importFile } from './utils.js'
|
|
6
|
+
import { createServerListener } from './worker/server-listener.js'
|
|
7
|
+
|
|
8
|
+
import { readFile } from 'node:fs/promises'
|
|
9
|
+
import { UnsupportedVersion } from './errors.js'
|
|
10
|
+
|
|
11
|
+
const supportedVersions = '^5.0.0'
|
|
12
|
+
|
|
13
|
+
export class ViteStackable extends BaseStackable {
|
|
14
|
+
#vite
|
|
15
|
+
#app
|
|
16
|
+
#server
|
|
17
|
+
#basePath
|
|
18
|
+
|
|
19
|
+
constructor (options, root, configManager) {
|
|
20
|
+
super(options, root, configManager)
|
|
21
|
+
this.type = 'vite'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async init () {
|
|
25
|
+
globalThis[Symbol.for('plt.runtime.itc')].handle('getServiceMeta', this.getMeta.bind(this))
|
|
26
|
+
|
|
27
|
+
this.#vite = dirname(createRequire(this.root).resolve('vite'))
|
|
28
|
+
const vitePackage = JSON.parse(await readFile(pathResolve(this.#vite, 'package.json')))
|
|
29
|
+
|
|
30
|
+
if (!satisfies(vitePackage.version, supportedVersions)) {
|
|
31
|
+
throw new UnsupportedVersion('vite', vitePackage.version, supportedVersions)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async start () {
|
|
36
|
+
// Make this idempotent
|
|
37
|
+
if (this.url) {
|
|
38
|
+
return this.url
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const config = this.configManager.current
|
|
42
|
+
|
|
43
|
+
// Prepare options
|
|
44
|
+
const { hostname, port, https, cors } = this.serverConfig ?? {}
|
|
45
|
+
const configFile = config.vite?.configFile ? pathResolve(this.root, config.vite?.configFile) : undefined
|
|
46
|
+
const basePath = config.application?.base
|
|
47
|
+
? `/${config.application?.base}`.replaceAll(/\/+/g, '/').replace(/\/$/, '')
|
|
48
|
+
: undefined
|
|
49
|
+
|
|
50
|
+
const serverOptions = {
|
|
51
|
+
host: hostname || '127.0.0.1',
|
|
52
|
+
port: port || 0,
|
|
53
|
+
strictPort: false,
|
|
54
|
+
https,
|
|
55
|
+
cors,
|
|
56
|
+
origin: 'http://localhost',
|
|
57
|
+
hmr: true,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Require Vite
|
|
61
|
+
const serverPromise = createServerListener()
|
|
62
|
+
const { createServer } = await importFile(pathResolve(this.#vite, 'dist/node/index.js'))
|
|
63
|
+
|
|
64
|
+
// Create the server and listen
|
|
65
|
+
this.#app = await createServer({
|
|
66
|
+
root: this.root,
|
|
67
|
+
base: basePath,
|
|
68
|
+
mode: 'development',
|
|
69
|
+
configFile,
|
|
70
|
+
logLevel: this.logger.level,
|
|
71
|
+
clearScreen: false,
|
|
72
|
+
optimizeDeps: { force: false },
|
|
73
|
+
server: serverOptions,
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
await this.#app.listen()
|
|
77
|
+
this.#server = await serverPromise
|
|
78
|
+
this.url = getServerUrl(this.#server)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async stop () {
|
|
82
|
+
return this.#app.close()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async getWatchConfig () {
|
|
86
|
+
return {
|
|
87
|
+
enabled: false,
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
getMeta () {
|
|
92
|
+
if (!this.#basePath) {
|
|
93
|
+
this.#basePath = this.#app.config.base.replace(/(^\/)|(\/$)/g, '')
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
composer: {
|
|
98
|
+
tcp: true,
|
|
99
|
+
url: this.url,
|
|
100
|
+
prefix: this.#basePath,
|
|
101
|
+
wantsAbsoluteUrls: true,
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { ITC } from '@platformatic/itc'
|
|
2
|
+
import { subscribe, unsubscribe } from 'node:diagnostics_channel'
|
|
3
|
+
import { once } from 'node:events'
|
|
4
|
+
import { workerData } from 'node:worker_threads'
|
|
5
|
+
|
|
6
|
+
export const childProcessWorkerFile = new URL('./child-process.js', import.meta.url)
|
|
7
|
+
|
|
8
|
+
export class ChildManager extends ITC {
|
|
9
|
+
#child
|
|
10
|
+
#listener
|
|
11
|
+
#injectedNodeOptions
|
|
12
|
+
#originalNodeOptions
|
|
13
|
+
|
|
14
|
+
constructor ({ loader, context }) {
|
|
15
|
+
super({})
|
|
16
|
+
|
|
17
|
+
const childHandler = ({ process: child }) => {
|
|
18
|
+
unsubscribe('child_process', childHandler)
|
|
19
|
+
|
|
20
|
+
this.#child = child
|
|
21
|
+
this.#child.once('exit', () => {
|
|
22
|
+
this.emit('exit')
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
this.listen()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
subscribe('child_process', childHandler)
|
|
29
|
+
|
|
30
|
+
this.handle('log', message => {
|
|
31
|
+
workerData.loggingPort.postMessage(JSON.parse(message))
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
this.#prepareChildEnvironment(loader, context)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
inject () {
|
|
38
|
+
process.env.NODE_OPTIONS = this.#injectedNodeOptions
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
eject () {
|
|
42
|
+
process.env.NODE_OPTIONS = this.#originalNodeOptions
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
_setupListener (listener) {
|
|
46
|
+
this.#listener = listener
|
|
47
|
+
this.#child.on('message', this.#listener)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
_send (request) {
|
|
51
|
+
this.#child.send(request)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
_createClosePromise () {
|
|
55
|
+
return once(this.#child, 'exit')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
_close () {
|
|
59
|
+
this.#child.removeListener('message', this.#listener)
|
|
60
|
+
this.#child.kill('SIGKILL')
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
#prepareChildEnvironment (loader, context) {
|
|
64
|
+
this.#originalNodeOptions = process.env.NODE_OPTIONS
|
|
65
|
+
|
|
66
|
+
const loaderScript = `
|
|
67
|
+
import { register } from 'node:module';
|
|
68
|
+
globalThis.platformatic=${JSON.stringify(context).replaceAll('"', '\\"')};
|
|
69
|
+
register('${loader}',{ data: globalThis.platformatic });
|
|
70
|
+
`
|
|
71
|
+
|
|
72
|
+
this.#injectedNodeOptions = [
|
|
73
|
+
`--import="data:text/javascript,${loaderScript.replaceAll(/\n/g, '')}"`,
|
|
74
|
+
`--import=${childProcessWorkerFile}`,
|
|
75
|
+
process.env.NODE_OPTIONS ?? '',
|
|
76
|
+
].join(' ')
|
|
77
|
+
}
|
|
78
|
+
}
|