@platformatic/generators 3.4.1 → 3.5.1

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.
@@ -0,0 +1,228 @@
1
+ import { findConfigurationFileRecursive, safeRemove } from '@platformatic/foundation'
2
+ import { spawnSync } from 'node:child_process'
3
+ import { readFile, readdir, stat } from 'node:fs/promises'
4
+ import { dirname, join, relative, resolve } from 'node:path'
5
+ import { BaseGenerator } from './base-generator.js'
6
+
7
+ export class ImportGenerator extends BaseGenerator {
8
+ constructor (options = {}) {
9
+ const { applicationName, module, version, parent: runtime, ...opts } = options
10
+ super({ ...opts, module })
11
+
12
+ this.runtime = runtime
13
+ this.setConfig({
14
+ applicationName,
15
+ applicationPathEnvName: `PLT_APPLICATION_${applicationName.toUpperCase().replaceAll(/[^A-Z0-9_]/g, '_')}_PATH`,
16
+ module,
17
+ version
18
+ })
19
+ }
20
+
21
+ async prepareQuestions () {
22
+ await super.prepareQuestions()
23
+
24
+ this.questions.push({
25
+ type: 'input',
26
+ name: 'applicationPath',
27
+ message: 'Where is your application located?',
28
+ async validate (value) {
29
+ if (value.length === 0) {
30
+ return 'Please enter a path'
31
+ }
32
+
33
+ try {
34
+ const pathStat = await stat(value)
35
+
36
+ if (!pathStat?.isDirectory()) {
37
+ return 'Please enter a valid path'
38
+ }
39
+ } catch {
40
+ return 'Please enter a valid path'
41
+ }
42
+
43
+ return true
44
+ }
45
+ })
46
+
47
+ this.questions.push({
48
+ type: 'list',
49
+ name: 'operation',
50
+ message: 'Do you want to import or copy your application?',
51
+ default: 'import',
52
+ choices: [
53
+ { name: 'import', value: 'import' },
54
+ { name: 'copy', value: 'copy' }
55
+ ]
56
+ })
57
+ }
58
+
59
+ // We can't use prepare as it is not invoked from the runtime when dealing with existing applications
60
+ /* c8 ignore next 3 - Invoked from base-generator */
61
+ async prepare () {
62
+ return { targetDirectory: this.targetDirectory, env: this.config.env }
63
+ }
64
+
65
+ async _beforeWriteFiles (runtime) {
66
+ const { module: pkg, version, applicationPath: path } = this.config
67
+
68
+ const packageJsonPath = join(path, 'package.json')
69
+
70
+ if (this.config.operation === 'copy') {
71
+ await this.#copy(path)
72
+ await this.#generateConfigFile(path, '')
73
+ await this.#updatePackageJson(packageJsonPath, 'package.json', pkg, version)
74
+ } else {
75
+ await this.#detectGitUrl(path)
76
+ await this.#generateConfigFile(path, path)
77
+
78
+ await this.#updatePackageJson(packageJsonPath, packageJsonPath, pkg, version)
79
+ await this.#updateRuntime(runtime)
80
+ }
81
+ }
82
+
83
+ async _afterWriteFiles (runtime) {
84
+ // No need for an empty folder in the applications folder
85
+ if (this.config.operation === 'import') {
86
+ await safeRemove(join(runtime.applicationsBasePath, this.config.applicationName))
87
+ }
88
+ }
89
+
90
+ async #detectGitUrl (path) {
91
+ let url
92
+
93
+ // First of all, determine if there is a git repository in the application path
94
+ // Detect if there is a git folder and eventually get the remote
95
+ for (const candidate of ['origin', 'upstream']) {
96
+ try {
97
+ const result = spawnSync('git', ['remote', 'get-url', candidate], { cwd: path })
98
+
99
+ /* c8 ignore next 3 - Hard to test */
100
+ if (result.error || result.status !== 0) {
101
+ continue
102
+ }
103
+
104
+ url = result.stdout.toString().trim()
105
+ break
106
+ /* c8 ignore next 3 - Hard to test */
107
+ } catch (e) {
108
+ // No-op
109
+ }
110
+ }
111
+
112
+ this.setConfig({ gitUrl: url })
113
+ return url
114
+ }
115
+
116
+ async #generateConfigFile (originalPath, updatedPath) {
117
+ // Determine if there is a watt.json file in the application path - If it's missing, insert one
118
+ // For import it means we don't update the file, for copy it means it was already copied in #copy.
119
+ const existingConfig = await findConfigurationFileRecursive(originalPath)
120
+
121
+ if (existingConfig) {
122
+ return
123
+ }
124
+
125
+ const { module: pkg, version } = this.config
126
+
127
+ if (pkg.startsWith('@platformatic/')) {
128
+ this.addFile({
129
+ path: '',
130
+ file: join(updatedPath, this.runtimeConfig),
131
+ contents: JSON.stringify({ $schema: `https://schemas.platformatic.dev/${pkg}/${version}.json` }, null, 2)
132
+ })
133
+ } else {
134
+ this.addFile({
135
+ path: '',
136
+ file: join(updatedPath, this.runtimeConfig),
137
+ contents: JSON.stringify({ module: pkg }, null, 2)
138
+ })
139
+ }
140
+ }
141
+
142
+ async #updatePackageJson (originalPath, updatedPath, pkg, version) {
143
+ // Add the module to the package.json dependencies
144
+ let packageJson = {}
145
+
146
+ try {
147
+ packageJson = JSON.parse(await readFile(originalPath, 'utf-8'))
148
+ } catch (e) {
149
+ // No-op, we will create a new package.json
150
+ }
151
+
152
+ packageJson.dependencies ??= {}
153
+ packageJson.dependencies[pkg] = `^${version}`
154
+ if (packageJson.devDependencies?.[pkg]) {
155
+ packageJson.devDependencies[pkg] = undefined
156
+ }
157
+
158
+ this.addFile({ path: '', file: updatedPath, contents: JSON.stringify(packageJson, null, 2) })
159
+ }
160
+
161
+ async #updateRuntime (runtime) {
162
+ const configObject = runtime.getRuntimeConfigFileObject()
163
+ /* c8 ignore next - else */
164
+ const config = JSON.parse(configObject?.contents ?? '{}')
165
+ const envObject = runtime.getRuntimeEnvFileObject()
166
+ /* c8 ignore next - else */
167
+ let env = envObject?.contents ?? ''
168
+
169
+ // Find which key is being used for the manual applications
170
+ let key
171
+ for (const candidate of new Set([runtime.applicationsFolder, 'applications', 'services', 'web'])) {
172
+ if (Array.isArray(config[candidate])) {
173
+ key = candidate
174
+ break
175
+ }
176
+ }
177
+
178
+ /* c8 ignore next - else */
179
+ key ??= runtime.applicationsFolder ?? 'applications'
180
+ const applications = config[key] ?? []
181
+
182
+ if (!applications.some(application => application.id === this.config.applicationName)) {
183
+ applications.push({
184
+ id: this.config.applicationName,
185
+ path: `{${this.config.applicationPathEnvName}}`,
186
+ url: this.config.gitUrl
187
+ })
188
+ }
189
+
190
+ config[key] = applications
191
+
192
+ if (env.length > 0) {
193
+ env += '\n'
194
+ }
195
+ env += `${this.config.applicationPathEnvName}=${this.config.applicationPath}`
196
+
197
+ runtime.updateRuntimeConfig(config)
198
+ runtime.updateRuntimeEnv(env)
199
+ }
200
+
201
+ async #copy (root) {
202
+ const files = await readdir(root, { withFileTypes: true, recursive: true })
203
+
204
+ for await (const file of files) {
205
+ const absolutePath = resolve(file.parentPath, file.name)
206
+ let path = relative(root, dirname(absolutePath))
207
+
208
+ if (
209
+ file.isDirectory() ||
210
+ ['package-lock.json', 'pnpm-lock.yaml', 'yarn.lock'].includes(file.name) ||
211
+ path.includes('node_modules')
212
+ ) {
213
+ continue
214
+ }
215
+
216
+ /* c8 ignore next 6 */
217
+ if (path === '.') {
218
+ path = ''
219
+ }
220
+
221
+ this.addFile({
222
+ path,
223
+ file: file.name,
224
+ contents: await readFile(resolve(file.parentPath, file.name))
225
+ })
226
+ }
227
+ }
228
+ }
package/lib/utils.js CHANGED
@@ -1,31 +1,30 @@
1
- 'use strict'
1
+ import { EOL } from 'node:os'
2
+ import { join } from 'node:path'
3
+ import { setTimeout } from 'timers/promises'
4
+ import { request } from 'undici'
5
+ import { WrongTypeError } from './errors.js'
2
6
 
3
- const { WrongTypeError } = require('./errors')
4
- const { join } = require('node:path')
5
- const { request } = require('undici')
6
- const { setTimeout } = require('timers/promises')
7
- const PLT_ROOT = 'PLT_ROOT'
8
- const { EOL } = require('node:os')
9
- const { createDirectory } = require('@platformatic/utils')
7
+ export const PLT_ROOT = 'PLT_ROOT'
10
8
 
11
9
  /**
12
10
  * Strip all extra characters from a simple semver version string
13
11
  * @param {string} version
14
12
  * @returns string
15
13
  */
16
- function stripVersion (version) {
14
+ export function stripVersion (version) {
17
15
  const match = version.match(/(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)/)
18
16
  if (match) {
19
17
  return match[0]
20
18
  }
19
+ /* c8 ignore next */
21
20
  return version
22
21
  }
23
22
 
24
- function convertServiceNameToPrefix (serviceName) {
25
- return serviceName.replace(/-/g, '_').toUpperCase()
23
+ export function convertApplicationNameToPrefix (applicationName) {
24
+ return applicationName.replace(/-/g, '_').toUpperCase()
26
25
  }
27
26
 
28
- function addPrefixToString (input, prefix) {
27
+ export function addPrefixToString (input, prefix) {
29
28
  if (!prefix) {
30
29
  return input
31
30
  }
@@ -39,7 +38,7 @@ function addPrefixToString (input, prefix) {
39
38
  }
40
39
  }
41
40
 
42
- function envObjectToString (env) {
41
+ export function envObjectToString (env) {
43
42
  const output = []
44
43
  Object.entries(env).forEach(kv => {
45
44
  output.push(`${kv[0]}=${kv[1]}`)
@@ -47,7 +46,7 @@ function envObjectToString (env) {
47
46
  return output.join(EOL)
48
47
  }
49
48
 
50
- function envStringToObject (envString) {
49
+ export function envStringToObject (envString) {
51
50
  const output = {}
52
51
  const split = envString.split(/\r?\n/)
53
52
  split
@@ -60,17 +59,19 @@ function envStringToObject (envString) {
60
59
  })
61
60
  return output
62
61
  }
63
- function extractEnvVariablesFromText (text) {
62
+
63
+ export function extractEnvVariablesFromText (text) {
64
64
  const match = text.match(/\{[a-zA-Z0-9-_]*\}/g)
65
65
  if (match) {
66
66
  return match.map(found => found.replace('{', '').replace('}', '')).filter(found => found !== '')
67
67
  }
68
68
  return []
69
69
  }
70
- function getPackageConfigurationObject (config, serviceName = '') {
70
+
71
+ export function getPackageConfigurationObject (config, applicationName = '') {
71
72
  const output = {
72
73
  config: {},
73
- env: {},
74
+ env: {}
74
75
  }
75
76
  let current = output.config
76
77
  for (const param of config) {
@@ -99,7 +100,7 @@ function getPackageConfigurationObject (config, serviceName = '') {
99
100
  if (!param.name) {
100
101
  current[prop] = value
101
102
  } else {
102
- const key = addPrefixToString(param.name, convertServiceNameToPrefix(serviceName))
103
+ const key = addPrefixToString(param.name, convertApplicationNameToPrefix(applicationName))
103
104
  // If it's a path, we need to add it to the env only the relative part of the path
104
105
  if (isPath) {
105
106
  current[prop] = `${join(`{${PLT_ROOT}}`, `{${key}}`)}`
@@ -121,7 +122,7 @@ function getPackageConfigurationObject (config, serviceName = '') {
121
122
  return output
122
123
  }
123
124
 
124
- async function getLatestNpmVersion (pkg) {
125
+ export async function getLatestNpmVersion (pkg) {
125
126
  const npmCall = request(`https://registry.npmjs.org/${pkg}`)
126
127
  const timeout = setTimeout(1000, null)
127
128
  const res = await Promise.race([npmCall, timeout])
@@ -135,6 +136,7 @@ async function getLatestNpmVersion (pkg) {
135
136
  }
136
137
  return null
137
138
  }
139
+
138
140
  /**
139
141
  * Flatten a deep-nested object to a single level depth one
140
142
  * i.e from
@@ -154,7 +156,7 @@ async function getLatestNpmVersion (pkg) {
154
156
  * @param {Object} ob
155
157
  * @returns Object
156
158
  */
157
- function flattenObject (ob) {
159
+ export function flattenObject (ob) {
158
160
  const result = {}
159
161
  for (const i in ob) {
160
162
  if (typeof ob[i] === 'object' && !Array.isArray(ob[i])) {
@@ -169,26 +171,12 @@ function flattenObject (ob) {
169
171
  return result
170
172
  }
171
173
 
172
- function getServiceTemplateFromSchemaUrl (schemaUrl) {
174
+ export function getApplicationTemplateFromSchemaUrl (schemaUrl) {
173
175
  const splitted = schemaUrl.split('/')
174
176
 
177
+ /* c8 ignore next 3 - Legacy interface */
175
178
  if (schemaUrl.startsWith('https://platformatic.dev/schemas')) {
176
179
  return `@platformatic/${splitted[splitted.length - 1]}`
177
180
  }
178
181
  return `@platformatic/${splitted[splitted.length - 2]}`
179
182
  }
180
-
181
- module.exports = {
182
- addPrefixToString,
183
- convertServiceNameToPrefix,
184
- getPackageConfigurationObject,
185
- envObjectToString,
186
- envStringToObject,
187
- extractEnvVariablesFromText,
188
- flattenObject,
189
- getServiceTemplateFromSchemaUrl,
190
- createDirectory,
191
- stripVersion,
192
- PLT_ROOT,
193
- getLatestNpmVersion,
194
- }
package/package.json CHANGED
@@ -1,31 +1,45 @@
1
1
  {
2
2
  "name": "@platformatic/generators",
3
- "version": "3.4.1",
3
+ "version": "3.5.1",
4
4
  "description": "Main classes and utils for generators.",
5
5
  "main": "index.js",
6
- "keywords": [],
7
- "author": "",
6
+ "type": "module",
7
+ "types": "index.d.ts",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/platformatic/platformatic.git"
11
+ },
12
+ "author": "Platformatic Inc. <oss@platformatic.dev> (https://platformatic.dev)",
8
13
  "license": "Apache-2.0",
14
+ "bugs": {
15
+ "url": "https://github.com/platformatic/platformatic/issues"
16
+ },
17
+ "homepage": "https://docs.platformatic.dev/docs/getting-started/quick-start",
18
+ "keywords": [],
9
19
  "dependencies": {
10
20
  "@fastify/error": "^4.0.0",
11
- "boring-name-generator": "^1.0.3",
12
21
  "change-case-all": "^2.1.0",
22
+ "execa": "^9.6.0",
13
23
  "fastify": "^5.0.0",
14
- "pino": "^9.0.0",
15
- "undici": "^6.9.0",
16
- "@platformatic/utils": "3.4.1"
24
+ "pino": "^9.9.0",
25
+ "undici": "^7.0.0",
26
+ "@platformatic/foundation": "3.5.1"
17
27
  },
18
28
  "devDependencies": {
19
29
  "@types/inquirer": "^9.0.7",
20
- "borp": "^0.17.0",
21
30
  "c8": "^10.0.0",
31
+ "cleaner-spec-reporter": "^0.5.0",
22
32
  "eslint": "9",
23
- "neostandard": "^0.11.1",
24
- "tsd": "^0.31.0",
33
+ "neostandard": "^0.12.0",
34
+ "tsd": "^0.33.0",
25
35
  "typescript": "^5.5.4"
26
36
  },
37
+ "engines": {
38
+ "node": ">=22.19.0"
39
+ },
27
40
  "scripts": {
28
41
  "lint": "eslint",
29
- "test": "pnpm run lint && borp --timeout=180000 -C -X fixtures -X test --concurrency=1 && tsd"
42
+ "test": "node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/*.test.js test/**/*.test.js",
43
+ "posttest": "tsd"
30
44
  }
31
45
  }
package/tsconfig.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "esnext",
4
+ "esModuleInterop": true,
5
+ "lib": ["es2022"],
6
+ "target": "esnext",
7
+ "sourceMap": true,
8
+ "pretty": true,
9
+ "noEmitOnError": true,
10
+ "incremental": true,
11
+ "strict": true,
12
+ "outDir": "dist",
13
+ "skipLibCheck": true
14
+ },
15
+ "watchOptions": {
16
+ "watchFile": "fixedPollingInterval",
17
+ "watchDirectory": "fixedPollingInterval",
18
+ "fallbackPolling": "dynamicPriority",
19
+ "synchronousWatchDirectory": true,
20
+ "excludeDirectories": ["**/node_modules", "dist"]
21
+ }
22
+ }
@@ -1,122 +0,0 @@
1
- import { BaseLogger } from 'pino'
2
- import { FileGenerator } from './file-generator'
3
- import { PackageConfiguration } from './utils'
4
- export namespace BaseGenerator {
5
- export type BaseGeneratorOptions = FileGenerator.FileGeneratorOptions & {
6
- module: string
7
- inquirer?: object
8
- }
9
-
10
- export type EnvVarValue = string | number | boolean
11
- export type Env = {
12
- [key: string]: EnvVarValue
13
- }
14
- type KeyValue = {
15
- [key: string]: string | number | undefined | null | boolean | object
16
- }
17
- type JSONValue =
18
- | string
19
- | number
20
- | boolean
21
- | { [x: string]: JSONValue }
22
- | object
23
- | Array<JSONValue>
24
-
25
- type Dependency = {
26
- [key: string]: string
27
- }
28
-
29
- type PackageDefinition = {
30
- name: string,
31
- options: PackageConfiguration
32
- }
33
- type BaseGeneratorConfig = Record<string, any> & {
34
- port?: number
35
- hostname?: string
36
- plugin?: boolean
37
- dependencies?: Dependency
38
- devDependencies?: Dependency
39
- typescript?: boolean
40
- initGitRepository?: boolean
41
- env?: KeyValue,
42
- isRuntimeContext?: boolean,
43
- serviceName?: string,
44
- envPrefix?: string
45
- }
46
-
47
- type WhereClause = {
48
- before?: string
49
- after?: string
50
- }
51
-
52
- type GeneratorMetadata = {
53
- targetDirectory: string
54
- env: KeyValue
55
- }
56
-
57
- type ConfigFieldDefinition = {
58
- label: string
59
- var: string
60
- default: string
61
- type: 'number' | 'string' | 'boolean' | 'path'
62
- configValue?: string
63
- }
64
-
65
- type ConfigField = {
66
- var: string
67
- configValue?: string
68
- value: string
69
- }
70
-
71
- type AddEnvVarOptions = {
72
- overwrite: boolean
73
- }
74
-
75
- export class BaseGenerator extends FileGenerator.FileGenerator {
76
- logger: BaseLogger
77
- platformaticVersion: string
78
- fastifyVersion: string
79
-
80
- config: BaseGeneratorConfig
81
- questions: Array<object>
82
-
83
- packages: PackageConfiguration[]
84
- constructor (opts?: BaseGeneratorOptions)
85
-
86
- setConfig (config?: BaseGeneratorConfig): void
87
-
88
- getEnvVarName (envVarName: string): string
89
- addEnvVars (envVars: Env, opts: AddEnvVarOptions): void
90
- addEnvVar (envVarName: string, envVarValue: EnvVarValue, opts: AddEnvVarOptions): void
91
- getEnvVar (envVarName: string): EnvVarValue
92
- setEnvVars (env?: Env): void
93
-
94
- getDefaultConfig (): { [x: string]: JSONValue }
95
-
96
- getFastifyVersion (): Promise<string>
97
- getPlatformaticVersion (): Promise<string>
98
-
99
- addPackage (pkg: PackageDefinition): Promise<void>
100
-
101
- loadFromDir (dir: string): Promise<void>
102
- prepare (): Promise<GeneratorMetadata>
103
- run (): Promise<GeneratorMetadata>
104
- addQuestion (question: any, where?: WhereClause): Promise<void>
105
- removeQuestion (variableName: string): void
106
- getTSConfig (): { [x: string]: JSONValue }
107
-
108
- getConfigFieldsDefinitions (): ConfigFieldDefinition[]
109
- setConfigFields (fields: ConfigField[]): void
110
-
111
- generateConfigFile (): Promise<void>
112
- readPackageJsonFile (): Promise<JSONValue>
113
- generatePackageJson (): Promise<{ [x: string]: JSONValue }>
114
- getConfigFileName (): string
115
- checkEnvVariablesInConfigFile (): boolean
116
- _beforePrepare (): Promise<void>
117
- _afterPrepare (): Promise<void | JSONValue>
118
- _getConfigFileContents (): Promise<{ [x: string]: BaseGenerator.JSONValue }>
119
-
120
- postInstallActions (): Promise<void>
121
- }
122
- }
@@ -1,13 +0,0 @@
1
- import { FileGenerator } from './file-generator'
2
-
3
- type HelperCustomization = {
4
- pre: string
5
- post: string
6
- config: string
7
- requires: string
8
- }
9
-
10
- export function generatePluginWithTypesSupport (typescript: boolean): FileGenerator.FileObject
11
- export function generateRouteWithTypesSupport (typescript: boolean): FileGenerator.FileObject
12
- export function generateTests (typescript: boolean, type: string, customization?: HelperCustomization): Array<FileGenerator.FileObject>
13
- export function generatePlugins (typescript: boolean): Array<FileGenerator.FileObject>