@platformatic/foundation 3.0.0-alpha.2

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/lib/cli.js ADDED
@@ -0,0 +1,231 @@
1
+ import { bgGreen, black, bold, green, isColorSupported } from 'colorette'
2
+ import { resolve } from 'node:path'
3
+ import { parseArgs as nodeParseArgs } from 'node:util'
4
+ import { pino } from 'pino'
5
+ import pinoPretty from 'pino-pretty'
6
+ import { findConfigurationFileRecursive, loadConfigurationModule, saveConfigurationFile } from './configuration.js'
7
+ import { hasJavascriptFiles } from './file-system.js'
8
+ import { detectApplicationType, getPlatformaticVersion } from './module.js'
9
+
10
+ /* c8 ignore next 3 - else branches */
11
+ let verbose = false
12
+ let executableId = ''
13
+ let executableName = ''
14
+
15
+ export function isVerbose () {
16
+ return verbose
17
+ }
18
+
19
+ export function getExecutableId () {
20
+ return executableId
21
+ }
22
+
23
+ export function getExecutableName () {
24
+ return executableName
25
+ }
26
+
27
+ export function setVerbose (value) {
28
+ verbose = value
29
+ }
30
+
31
+ export function setExecutableId (id) {
32
+ executableId = id
33
+ }
34
+
35
+ export function setExecutableName (name) {
36
+ executableName = name
37
+ }
38
+
39
+ export function logo (color = true) {
40
+ /* c8 ignore next - else */
41
+ const executableName = color && isColorSupported ? bold(getExecutableName()) : getExecutableName()
42
+ const str = `
43
+
44
+ //////
45
+ /////////////
46
+ /////// ///////
47
+ /////// ///////
48
+ /////// ///////
49
+ //// ////
50
+ &&&& //// //// &&&&
51
+ &&&&&&& //// //// &&&&&&&
52
+ &&&&&&& //// //// &&&&&&&
53
+ &&&& //// //// &&&&&&&
54
+ &&&& //// //// &&&& &&
55
+ &&&& //// //// &&& &&&&&&
56
+ &&&& //// //// &&& &&&&&&
57
+ &&&& ///// ///// &&& &&&&
58
+ &&&& /////// /////// &&& &&&&
59
+ &&&& ////// /////// &&& &&&&&&&
60
+ &&&& //// ///////// //////// &&& &&&&&&
61
+ &&&& //// /// //////////// &&&& &&& &&&
62
+ &&&&&&& //// /// ///// &&&& &&&&
63
+ &&&&&&& //// &&& &&&&& &&&&&&&
64
+ &&& //// &&&& &&&&&&& &&&&&&&
65
+ //// &&& &&&& &&&&&&&&&&
66
+ //// &&& &&&& &&&&
67
+ // &&&&& &&&&
68
+ &&&&&&& &&&&&&
69
+ &&&&&&&&&&&&&
70
+ &&&&&&
71
+
72
+ Welcome to ${executableName}!
73
+ `
74
+
75
+ /* c8 ignore next - else */
76
+ return color && isColorSupported ? str.replace(/\//g, s => green(s)) : str
77
+ }
78
+
79
+ export function createCliLogger (level) {
80
+ return pino(
81
+ {
82
+ level,
83
+ customLevels: {
84
+ done: 35
85
+ }
86
+ },
87
+ pinoPretty({
88
+ colorize: process.env.NO_COLOR !== 'true',
89
+ customPrettifiers: {
90
+ level (logLevel, _u1, _u2, { label, labelColorized }) {
91
+ /* c8 ignore next - else */
92
+ return logLevel === 35 ? bgGreen(black(label)) : labelColorized
93
+ }
94
+ },
95
+ sync: true
96
+ })
97
+ )
98
+ }
99
+
100
+ export function logFatalError (logger, ...args) {
101
+ process.exitCode = 1
102
+ logger.fatal(...args)
103
+ return false
104
+ }
105
+
106
+ export function parseArgs (args, options, stopAtFirstPositional = true, strict = true) {
107
+ let unparsed = []
108
+
109
+ if (stopAtFirstPositional) {
110
+ // Parse a first time to get tokens and see where the first positional, if any, is
111
+ const { tokens } = nodeParseArgs({
112
+ args,
113
+ options,
114
+ allowPositionals: true,
115
+ allowNegative: true,
116
+ strict: false,
117
+ tokens: true
118
+ })
119
+
120
+ const firstPositional = tokens.find(t => t.kind === 'positional')
121
+
122
+ if (firstPositional) {
123
+ unparsed = args.slice(firstPositional.index)
124
+ args = args.slice(0, firstPositional.index)
125
+ }
126
+ }
127
+
128
+ const { tokens, values, positionals } = nodeParseArgs({
129
+ args,
130
+ options,
131
+ allowPositionals: true,
132
+ allowNegative: true,
133
+ strict,
134
+ tokens: true
135
+ })
136
+
137
+ return {
138
+ values,
139
+ positionals,
140
+ unparsed,
141
+ tokens
142
+ }
143
+ }
144
+
145
+ export function getRoot (positionals) {
146
+ let root = process.cwd()
147
+
148
+ if (positionals?.[0]) {
149
+ root = resolve(root, positionals[0])
150
+ }
151
+
152
+ return root
153
+ }
154
+
155
+ export function serviceToEnvVariable (service) {
156
+ return `PLT_SERVICE_${service.toUpperCase().replaceAll(/[^A-Z0-9_]/g, '_')}_PATH`
157
+ }
158
+
159
+ export async function findRuntimeConfigurationFile (
160
+ logger,
161
+ root,
162
+ configurationFile,
163
+ fallback = true,
164
+ throwOnError = true,
165
+ verifyPackages = true
166
+ ) {
167
+ let configFile = await findConfigurationFileRecursive(root, configurationFile, '@platformatic/runtime')
168
+
169
+ // If a runtime was not found, search for service file that we wrap in a runtime
170
+ if (!configFile && !configurationFile) {
171
+ configFile = await findConfigurationFileRecursive(root, configurationFile)
172
+ }
173
+
174
+ // No configuration yet, try to create a new one
175
+ if (!configFile) {
176
+ if (fallback) {
177
+ configurationFile = await fallbackToTemporaryConfigFile(logger, root, verifyPackages)
178
+
179
+ /* c8 ignore next - else */
180
+ if (configurationFile || configurationFile === false) {
181
+ return configurationFile
182
+ }
183
+ }
184
+
185
+ if (throwOnError) {
186
+ return logFatalError(
187
+ logger,
188
+ `Cannot find a supported ${getExecutableName()} configuration file (like ${bold('watt.json')}, a ${bold('wattpm.json')} or a ${bold(
189
+ 'platformatic.json'
190
+ )}) in ${bold(resolve(root))}.`
191
+ )
192
+ }
193
+ }
194
+
195
+ return configFile
196
+ }
197
+
198
+ export async function fallbackToTemporaryConfigFile (logger, root, verifyPackages) {
199
+ const hasJsFiles = await hasJavascriptFiles(root)
200
+
201
+ if (!hasJsFiles) {
202
+ // Do not return false here, that is reserved below to signal that a file was created but no module was available.
203
+ return
204
+ }
205
+
206
+ const { name, label } = await detectApplicationType(root)
207
+
208
+ /* c8 ignore next - else */
209
+ const autodetectDescription = name === '@platformatic/node' ? 'is a generic Node.js application' : `is using ${label}`
210
+
211
+ logger.warn(
212
+ `We have auto-detected that the current folder ${bold(autodetectDescription)} so we have created a ${bold('watt.json')} file for you automatically.`
213
+ )
214
+
215
+ const schema = `https://schemas.platformatic.dev/${name}/${await getPlatformaticVersion()}.json?autogenerated=true`
216
+ const configurationFile = resolve(root, 'watt.json')
217
+ await saveConfigurationFile(configurationFile, { $schema: schema })
218
+
219
+ // Try to load the module, if it is missing, we will throw an error
220
+ if (verifyPackages) {
221
+ try {
222
+ await loadConfigurationModule(root, { $schema: schema })
223
+ /* c8 ignore next 4 - covered */
224
+ } catch (error) {
225
+ logFatalError(logger, `Cannot load module ${bold(name)}. Please add it to your package.json and try again.`)
226
+ return false
227
+ }
228
+ }
229
+
230
+ return configurationFile
231
+ }