@ackee/create-node-app 1.0.1 → 2.0.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.
Files changed (149) hide show
  1. package/AUTHORS +2 -1
  2. package/README.md +26 -18
  3. package/docs/development.md +42 -0
  4. package/lib/Bootstrap.js +106 -65
  5. package/lib/Bootstrap.js.map +1 -1
  6. package/lib/Builder.js +111 -0
  7. package/lib/Builder.js.map +1 -0
  8. package/lib/Files.js +21 -0
  9. package/lib/Files.js.map +1 -0
  10. package/lib/Logger.js +26 -8
  11. package/lib/Logger.js.map +1 -1
  12. package/lib/Mergers/ConfigMerger.js +22 -0
  13. package/lib/Mergers/ConfigMerger.js.map +1 -0
  14. package/lib/Mergers/ContainerMerger.js +172 -0
  15. package/lib/Mergers/ContainerMerger.js.map +1 -0
  16. package/lib/Mergers/EnvJsoncMerger.js +20 -0
  17. package/lib/Mergers/EnvJsoncMerger.js.map +1 -0
  18. package/lib/Mergers/Merger.js +36 -0
  19. package/lib/Mergers/Merger.js.map +1 -0
  20. package/lib/Mergers/PackageJsonMerger.js +36 -0
  21. package/lib/Mergers/PackageJsonMerger.js.map +1 -0
  22. package/lib/Npm.js +40 -12
  23. package/lib/Npm.js.map +1 -1
  24. package/lib/PackageJson.js +4 -4
  25. package/lib/PackageJson.js.map +1 -1
  26. package/lib/StarterLoader.js +86 -0
  27. package/lib/StarterLoader.js.map +1 -0
  28. package/package.json +8 -5
  29. package/src/Bootstrap.ts +123 -82
  30. package/src/Builder.ts +172 -0
  31. package/src/Files.ts +22 -0
  32. package/src/Logger.ts +26 -7
  33. package/src/Mergers/ConfigMerger.ts +28 -0
  34. package/src/Mergers/ContainerMerger.ts +241 -0
  35. package/src/Mergers/EnvJsoncMerger.ts +24 -0
  36. package/src/Mergers/Merger.ts +51 -0
  37. package/src/Mergers/PackageJsonMerger.ts +45 -0
  38. package/src/Npm.ts +60 -15
  39. package/src/PackageJson.ts +6 -4
  40. package/src/Starter.ts +2 -2
  41. package/src/StarterLoader.ts +148 -0
  42. package/starter/{cloudrun → _base}/.env.jsonc +1 -5
  43. package/starter/{cloudrun → _base}/.eslintrc.cjs +3 -2
  44. package/starter/_base/README.md +53 -0
  45. package/starter/_base/package.json +45 -0
  46. package/starter/{cloudrun → _base}/src/adapters/pino.logger.ts +1 -1
  47. package/starter/_base/src/config.ts +16 -0
  48. package/starter/{cloudrun-graphql → _base}/src/container.ts +3 -1
  49. package/starter/_base/src/index.ts +14 -0
  50. package/starter/{cloudrun → _base}/src/view/cli/README.md +2 -6
  51. package/starter/api/graphql/.env.jsonc +8 -0
  52. package/starter/{cloudrun-graphql → api/graphql}/.eslintrc.cjs +4 -5
  53. package/starter/api/graphql/node-app.jsonc +6 -0
  54. package/starter/api/graphql/package.json +36 -0
  55. package/starter/{cloudrun-graphql → api/graphql}/src/config.ts +0 -4
  56. package/starter/api/graphql/src/index.ts +11 -0
  57. package/starter/{cloudrun-graphql → api/graphql}/src/test/helloWorld.test.ts +14 -3
  58. package/starter/api/graphql/src/view/graphql/context-factory.ts +13 -0
  59. package/starter/{cloudrun-graphql → api/graphql}/src/view/server.ts +16 -6
  60. package/starter/api/rest/.env.jsonc +6 -0
  61. package/starter/api/rest/.eslintrc.cjs +8 -0
  62. package/starter/api/rest/node-app.jsonc +6 -0
  63. package/starter/api/rest/package.json +25 -0
  64. package/starter/{cloudrun → api/rest}/src/config.ts +0 -5
  65. package/starter/api/rest/src/container.ts +13 -0
  66. package/starter/{cloudrun → api/rest}/src/index.ts +4 -4
  67. package/starter/{cloudrun → api/rest}/src/test/health-check.test.ts +3 -5
  68. package/starter/{cloudrun → api/rest}/src/test/util/openapi-test.util.ts +3 -3
  69. package/starter/{cloudrun → api/rest}/src/view/rest/middleware/error-handler.ts +1 -1
  70. package/starter/{cloudrun → api/rest}/src/view/rest/routes.ts +1 -1
  71. package/starter/{cloudrun → api/rest}/src/view/rest/util/openapi.util.ts +19 -19
  72. package/starter/{cloudrun → api/rest}/src/view/server.ts +6 -4
  73. package/starter/infra/postgresql-knex/.env.jsonc +5 -0
  74. package/starter/{shared → infra/postgresql-knex}/docker-compose/docker-compose.yml +1 -1
  75. package/starter/infra/postgresql-knex/knexfile.ts +16 -0
  76. package/starter/infra/postgresql-knex/node-app.jsonc +6 -0
  77. package/starter/infra/postgresql-knex/package.json +13 -0
  78. package/starter/infra/postgresql-knex/src/adapters/knex.database.test.ts +21 -0
  79. package/starter/infra/postgresql-knex/src/adapters/knex.database.ts +14 -0
  80. package/starter/infra/postgresql-knex/src/adapters/repositories/migration.repository.ts +24 -0
  81. package/starter/infra/postgresql-knex/src/config.ts +14 -0
  82. package/starter/infra/postgresql-knex/src/container.ts +23 -0
  83. package/starter/infra/postgresql-knex/src/db/migration.template.ts +4 -0
  84. package/starter/infra/postgresql-knex/src/db/migrations/.gitkeep +0 -0
  85. package/starter/infra/postgresql-knex/src/db/seed.template.ts +3 -0
  86. package/starter/infra/postgresql-knex/src/db/seeds/.gitkeep +0 -0
  87. package/starter/infra/postgresql-knex/src/domain/ports/database.d.ts +4 -0
  88. package/starter/infra/postgresql-knex/src/domain/ports/repositories/migration.repository.d.ts +9 -0
  89. package/starter/infra/postgresql-knex/src/test/setup.ts +16 -0
  90. package/starter/{shared → pipeline/cloudrun-gitlab}/.gitlab-ci.yml +15 -6
  91. package/starter/pipeline/cloudrun-gitlab/node-app.jsonc +6 -0
  92. package/tsconfig.tsbuildinfo +1 -1
  93. package/lib/Toolbelt.js +0 -102
  94. package/lib/Toolbelt.js.map +0 -1
  95. package/lib/cloudrun/CloudRunStarter.js +0 -127
  96. package/lib/cloudrun/CloudRunStarter.js.map +0 -1
  97. package/lib/cloudrun-graphql/GraphQLStarter.js +0 -118
  98. package/lib/cloudrun-graphql/GraphQLStarter.js.map +0 -1
  99. package/src/Toolbelt.ts +0 -132
  100. package/src/cloudrun/CloudRunStarter.ts +0 -182
  101. package/src/cloudrun-graphql/GraphQLStarter.ts +0 -182
  102. package/starter/cloudrun/README.md +0 -69
  103. package/starter/cloudrun/src/container.ts +0 -18
  104. package/starter/cloudrun/src/context.ts +0 -39
  105. package/starter/cloudrun/src/domain/errors/codes.ts +0 -9
  106. package/starter/cloudrun/src/domain/errors/errors.ts +0 -25
  107. package/starter/cloudrun/src/domain/ports/logger.d.ts +0 -21
  108. package/starter/cloudrun-graphql/.env.jsonc +0 -12
  109. package/starter/cloudrun-graphql/README.md +0 -53
  110. package/starter/cloudrun-graphql/src/adapters/pino.logger.ts +0 -44
  111. package/starter/cloudrun-graphql/src/index.ts +0 -11
  112. package/starter/shared/.gitignore_ +0 -5
  113. package/starter/shared/ci-branch-config/common.env +0 -7
  114. package/starter/shared/ci-branch-config/development.env +0 -7
  115. package/starter/shared/ci-branch-config/master.env +0 -7
  116. package/starter/shared/ci-branch-config/stage.env +0 -7
  117. package/starter/shared/docker-compose/docker-compose.override.yml +0 -5
  118. package/starter/shared/jest.config.js +0 -12
  119. /package/starter/{shared → _base}/.dockerignore +0 -0
  120. /package/starter/{cloudrun → _base}/.eslint.tsconfig.json +0 -0
  121. /package/starter/{shared → _base}/.mocha-junit-config.json +0 -0
  122. /package/starter/{shared → _base}/.mocharc.json +0 -0
  123. /package/starter/{shared → _base}/.nvmrc +0 -0
  124. /package/starter/{shared → _base}/Dockerfile +0 -0
  125. /package/starter/{shared → _base}/prettier.config.cjs +0 -0
  126. /package/starter/{cloudrun-graphql → _base}/src/context.ts +0 -0
  127. /package/starter/{cloudrun-graphql → _base}/src/domain/errors/codes.ts +0 -0
  128. /package/starter/{cloudrun-graphql → _base}/src/domain/errors/errors.ts +0 -0
  129. /package/starter/{cloudrun-graphql → _base}/src/domain/ports/logger.d.ts +0 -0
  130. /package/starter/{shared → _base}/src/test/setup.ts +0 -0
  131. /package/starter/{cloudrun → _base}/src/view/cli/cli.ts +0 -0
  132. /package/starter/{shared → _base}/tsconfig.json +0 -0
  133. /package/starter/{cloudrun-graphql → api/graphql}/.eslint.tsconfig.json +0 -0
  134. /package/starter/{cloudrun-graphql → api/graphql}/codegen.yml +0 -0
  135. /package/starter/{cloudrun-graphql → api/graphql}/src/view/controller.ts +0 -0
  136. /package/starter/{cloudrun-graphql → api/graphql}/src/view/graphql/resolvers/greeting.resolver.ts +0 -0
  137. /package/starter/{cloudrun-graphql → api/graphql}/src/view/graphql/resolvers.ts +0 -0
  138. /package/starter/{cloudrun-graphql → api/graphql}/src/view/graphql/schema/schema.graphql +0 -0
  139. /package/starter/{cloudrun-graphql → api/graphql}/src/view/graphql/schema.ts +0 -0
  140. /package/starter/{cloudrun → api/rest}/src/domain/health-check.service.ts +0 -0
  141. /package/starter/{cloudrun → api/rest}/src/view/cli/openapi/generate.ts +0 -0
  142. /package/starter/{cloudrun/src/view/rest/controller → api/rest/src/view/rest/controllers}/health-check.controller.ts +0 -0
  143. /package/starter/{cloudrun → api/rest}/src/view/rest/middleware/context-middleware.ts +0 -0
  144. /package/starter/{cloudrun → api/rest}/src/view/rest/middleware/request-logger.ts +0 -0
  145. /package/starter/{cloudrun → api/rest}/src/view/rest/request.d.ts +0 -0
  146. /package/starter/{cloudrun → api/rest}/src/view/rest/spec/openapi.yml +0 -0
  147. /package/starter/{shared → infra/postgresql-knex}/docker-compose/docker-compose-entrypoint.sh +0 -0
  148. /package/starter/{shared → infra/postgresql-knex}/docker-compose/docker-compose.ci.yml +0 -0
  149. /package/starter/{shared → infra/postgresql-knex}/docker-compose/docker-compose.local.yml +0 -0
package/src/Bootstrap.ts CHANGED
@@ -1,99 +1,140 @@
1
- import { CloudRunStarter } from './cloudrun/CloudRunStarter.js'
2
- import { Npm } from './Npm.js'
3
- import { Toolbelt } from './Toolbelt.js'
1
+ import inquirer from 'inquirer'
4
2
  import * as path from 'path'
5
- import { PackageJson } from './PackageJson.js'
6
- import { GraphQLStarter } from './cloudrun-graphql/GraphQLStarter.js'
7
- import { Path } from './types.js'
3
+ import * as fs from 'fs'
8
4
  import yargs from 'yargs'
9
5
  import { hideBin } from 'yargs/helpers'
10
- import { logger } from './Logger.js'
11
- import { Starter } from './Starter.js'
6
+ import { Builder } from './Builder.js'
7
+ import { Logger } from './Logger.js'
8
+ import { Npm } from './Npm.js'
9
+ import { PackageJson } from './PackageJson.js'
10
+ import { LoadedStarter, StarterLoader } from './StarterLoader.js'
11
+ import { Path } from './types.js'
12
+
13
+ interface ParsedArgs {
14
+ dir: string
15
+ debug: boolean
16
+ force: boolean
17
+ projectName: string
18
+ [key: string]: unknown
19
+ }
12
20
 
13
21
  export class Bootstrap {
14
- protected starters: Starter[] = [new CloudRunStarter(), new GraphQLStarter()]
22
+ protected starterLoader = new StarterLoader()
15
23
 
16
- public runCLI(args: string[]) {
17
- const cli = yargs(hideBin(args))
18
- .usage('create-node-app <starter> [options]')
19
- .positional('starter', {
24
+ private async askMissingOptions(parsedArgs: ParsedArgs) {
25
+ const starters: LoadedStarter[] = []
26
+
27
+ for (const module of this.starterLoader.getOptions()) {
28
+ const moduleOption = parsedArgs[module.name.toLowerCase()] as string
29
+ if (moduleOption) {
30
+ if (moduleOption !== 'none') {
31
+ starters.push(this.starterLoader.getStarter(moduleOption))
32
+ }
33
+ continue
34
+ }
35
+
36
+ const answer = await inquirer.prompt<{ starter: string }>({
37
+ type: 'list',
20
38
  name: 'starter',
21
- type: 'string',
22
- required: true,
23
- description: 'Which template to setup (required)',
24
- choices: this.starters.map(starter => starter.name),
25
- })
26
- .option('dir', {
27
- type: 'string',
28
- alias: 'd',
29
- default: './node-app',
30
- description: 'Destination directory',
39
+ message: `Which ${module.name} would you like to use?`,
40
+ choices: [...module.starters, 'none'],
31
41
  })
32
- .option('project-name', {
33
- type: 'string',
34
- alias: 'n',
35
- default: 'node-app',
36
- description: 'Google Cloud project name',
37
- })
38
- .option('force', {
39
- type: 'boolean',
40
- alias: 'f',
41
- default: false,
42
- description:
43
- "Overwrite existing destination directory if it's not empty",
44
- })
45
- .version('1.0.0')
46
- .help()
47
42
 
48
- const parsedArgs = cli.parseSync()
49
- const starterArg = parsedArgs._[0]
43
+ if (answer.starter !== 'none') {
44
+ starters.push(this.starterLoader.getStarter(answer.starter))
45
+ }
46
+ }
47
+ return starters
48
+ }
50
49
 
51
- const starter = this.starters.find(x => x.name === starterArg)
52
- const destination = path.normalize(parsedArgs.dir) as Path
50
+ public async runCLI(args: string[]) {
51
+ try {
52
+ let cli = yargs(hideBin(args))
53
+ .usage('create-node-app [options]')
54
+ .option('dir', {
55
+ type: 'string',
56
+ alias: 'd',
57
+ default: './node-app',
58
+ description: 'Destination directory',
59
+ })
60
+ .option('debug', {
61
+ type: 'boolean',
62
+ alias: 'D',
63
+ default: false,
64
+ description: 'Enables debug logs',
65
+ })
66
+ .option('project-name', {
67
+ type: 'string',
68
+ alias: 'n',
69
+ default: 'node-app',
70
+ description: 'Google Cloud project name',
71
+ })
72
+ .option('force', {
73
+ type: 'boolean',
74
+ alias: 'f',
75
+ default: false,
76
+ description:
77
+ "Overwrite existing destination directory if it's not empty",
78
+ })
53
79
 
54
- if (!starter) {
55
- logger.info('Invalid starter')
56
- cli.showHelp()
57
- process.exit(1)
58
- }
80
+ const starterOptions = this.starterLoader.getOptions()
81
+ for (const module of starterOptions) {
82
+ cli = cli.option(module.name.toLowerCase(), {
83
+ type: 'string',
84
+ choices: ['none', ...module.starters],
85
+ description: `Selects ${module.name}`,
86
+ })
87
+ }
88
+
89
+ cli = cli
90
+ .version('1.0.0')
91
+ .help()
92
+ .check(argv => {
93
+ for (const [key, val] of Object.entries(argv)) {
94
+ if (Array.isArray(val) && key !== '_') {
95
+ throw new Error(`Option --${key} specified multiple times`)
96
+ }
97
+ }
98
+ return true
99
+ })
100
+
101
+ const parsedArgs = cli.parseSync()
102
+
103
+ const destination = path.normalize(parsedArgs.dir) as Path
59
104
 
60
- logger.info(`starter=${starter.name}, destination=${destination}`)
61
-
62
- const npm = new Npm({ dir: destination })
63
- const packageJson = new PackageJson(npm)
64
- const toolbelt = new Toolbelt({
65
- npm,
66
- packageJson,
67
- assetDirectory: path.join(
68
- import.meta.dirname,
69
- '..',
70
- 'starter',
71
- starter.name
72
- ),
73
- sharedDirectory: path.join(
74
- import.meta.dirname,
75
- '..',
76
- 'starter',
77
- 'shared'
78
- ),
79
- destination: destination,
80
- projectName: parsedArgs.projectName,
81
- })
82
- starter.setToolbelt(toolbelt)
83
-
84
- if (!toolbelt.isDirectoryEmpty(destination)) {
85
- if (!parsedArgs.force) {
86
- logger.info(
87
- `Directory '${destination}' already exists and is not empty. Use --force or -f flag to overwrite the existing directory.`
88
- )
89
- process.exit(1)
105
+ const logger = new Logger(parsedArgs.debug)
106
+ const npm = new Npm({ dir: destination, logger })
107
+ const packageJson = new PackageJson(npm, logger)
108
+
109
+ if (fs.existsSync(destination) && !parsedArgs.force) {
110
+ const answer = await inquirer.prompt<{ force: boolean }>({
111
+ type: 'confirm',
112
+ name: 'force',
113
+ message: `Destination directory "${destination}" already exists. Do you want to overwrite everything in it?`,
114
+ })
115
+ if (!answer.force) {
116
+ process.exit(0)
117
+ }
118
+ }
119
+
120
+ const starters = await this.askMissingOptions(parsedArgs)
121
+
122
+ const builder = new Builder({
123
+ npm,
124
+ logger,
125
+ packageJson,
126
+ starters,
127
+ destination: destination,
128
+ projectName: parsedArgs.projectName,
129
+ })
130
+
131
+ await builder.build()
132
+ } catch (error) {
133
+ if (error instanceof Error && error.name === 'ExitPromptError') {
134
+ process.exit(0)
90
135
  } else {
91
- logger.info(`Overwriting existing directory '${destination}'`)
136
+ throw error
92
137
  }
93
138
  }
94
-
95
- toolbelt.mkdir(destination, { overwrite: parsedArgs.force })
96
- toolbelt.npm.init()
97
- starter.install()
98
139
  }
99
140
  }
package/src/Builder.ts ADDED
@@ -0,0 +1,172 @@
1
+ import glob from 'fast-glob'
2
+ import * as fs from 'fs/promises'
3
+ import * as path from 'path'
4
+ import { Logger } from './Logger.js'
5
+ import { Npm } from './Npm.js'
6
+ import { PackageJson } from './PackageJson.js'
7
+ import { LoadedStarter, StarterConfig } from './StarterLoader.js'
8
+ import { Merger } from './Mergers/Merger.js'
9
+ import { PackageJsonMerger } from './Mergers/PackageJsonMerger.js'
10
+ import { EnvJsoncMerger } from './Mergers/EnvJsoncMerger.js'
11
+ import { ConfigMerger } from './Mergers/ConfigMerger.js'
12
+ import { ContainerMerger } from './Mergers/ContainerMerger.js'
13
+ import { Files } from './Files.js'
14
+
15
+ export class Builder {
16
+ public static readonly BASE_STARTER_DIR = path.normalize(
17
+ path.join(import.meta.dirname, '..', 'starter', '_base')
18
+ )
19
+ public static readonly IGNORED_FILES = ['node-app.jsonc']
20
+
21
+ public readonly npm: Npm
22
+ protected readonly logger: Logger
23
+ protected readonly starters: LoadedStarter[]
24
+ protected readonly fileMergers: Merger[]
25
+ protected readonly destination: string
26
+ protected readonly projectName: string
27
+ protected readonly replacements: Record<string, string>
28
+
29
+ constructor(params: {
30
+ npm: Npm
31
+ logger: Logger
32
+ packageJson: PackageJson
33
+ starters: LoadedStarter[]
34
+ destination: string
35
+ projectName: string
36
+ }) {
37
+ this.npm = params.npm
38
+ this.logger = params.logger
39
+ this.starters = params.starters
40
+ this.destination = params.destination
41
+ this.projectName = params.projectName
42
+ this.replacements = {
43
+ '{{PROJECT_NAME}}': this.projectName,
44
+ }
45
+ this.fileMergers = [
46
+ new PackageJsonMerger(this.projectName, this.destination, 'package.json'),
47
+ new EnvJsoncMerger(this.destination, '.env.jsonc'),
48
+ new ConfigMerger(this.destination, 'src/config.ts'),
49
+ new ContainerMerger(this.destination, 'src/container.ts'),
50
+ ]
51
+ }
52
+
53
+ protected async prepareFolder() {
54
+ if (await Files.existsAndIsDir(this.destination)) {
55
+ await fs.rm(this.destination, { recursive: true })
56
+ }
57
+ await fs.mkdir(this.destination, { recursive: true })
58
+ }
59
+
60
+ public async build() {
61
+ try {
62
+ await this.logger.loader(`Preparing clean folder`, this.prepareFolder())
63
+
64
+ await this.logger.loader(
65
+ `Preparing folder structure`,
66
+ this.buildStarter(Builder.BASE_STARTER_DIR)
67
+ )
68
+
69
+ for (const starter of this.starters) {
70
+ await this.logger.loader(
71
+ `Adding ${starter.config.name} ${starter.config.module}`,
72
+ this.buildStarter(starter.path, starter.config)
73
+ )
74
+ }
75
+
76
+ await this.logger.loader(`npm install`, this.npm.run(['install']))
77
+
78
+ const prebuildScripts: Array<string[]> = this.starters
79
+ .map(starter => starter.config.prebuild)
80
+ .filter(script => script !== undefined)
81
+
82
+ for (const script of prebuildScripts) {
83
+ await this.logger.loader(
84
+ `npm run ${script.join(' ')}`,
85
+ this.npm.run(['run', ...script])
86
+ )
87
+ }
88
+
89
+ await this.logger.loader(`npm run build`, this.npm.run(['run', 'build']))
90
+
91
+ this.logger.info(
92
+ `Your app is ready in ${path.relative(
93
+ process.cwd(),
94
+ this.destination
95
+ )}! 🚀`
96
+ )
97
+ } catch (error) {
98
+ this.logger.error(error)
99
+ process.exit(1)
100
+ }
101
+ }
102
+
103
+ protected async buildStarter(starterDir: string, config?: StarterConfig) {
104
+ const destDir = path.normalize(path.resolve(this.destination))
105
+ const files = await glob(`${starterDir}/*`, {
106
+ cwd: starterDir,
107
+ dot: true,
108
+ onlyFiles: false,
109
+ })
110
+
111
+ const ignoredFiles = Builder.IGNORED_FILES.map(file =>
112
+ path.join(starterDir, file)
113
+ )
114
+
115
+ const mergedFiles = await Promise.all(
116
+ this.fileMergers.map(async merger => {
117
+ return {
118
+ path: merger.getDestPath(),
119
+ content: await merger.merge(starterDir),
120
+ }
121
+ })
122
+ )
123
+
124
+ await Promise.all(
125
+ files.map(async filePath => {
126
+ if (ignoredFiles.includes(filePath)) {
127
+ return
128
+ }
129
+ const destFilePath = path.join(destDir, path.basename(filePath))
130
+ if (await Files.existsAndIsDir(filePath)) {
131
+ await fs.cp(filePath, destFilePath, { recursive: true })
132
+ return
133
+ }
134
+ await fs.cp(filePath, destFilePath)
135
+ })
136
+ )
137
+
138
+ await Promise.all(
139
+ mergedFiles.map(async ({ path, content }) => fs.writeFile(path, content))
140
+ )
141
+
142
+ if (config?.replace) {
143
+ await Promise.all(
144
+ config.replace.map(async filePath => this.replaceInFile(filePath))
145
+ )
146
+ }
147
+ }
148
+
149
+ public async replaceInFile(filePath: string) {
150
+ filePath = path.normalize(path.join(this.destination, filePath))
151
+ let content = await Files.readUtf8File(filePath)
152
+ content = Object.keys(this.replacements).reduce((acc, key) => {
153
+ return acc.replaceAll(key, this.replacements[key])
154
+ }, content)
155
+ return fs.writeFile(filePath, content)
156
+ }
157
+
158
+ public async symlink(linkName: string, linkedFile: string) {
159
+ linkName = path.normalize(linkName)
160
+ linkedFile = path.normalize(linkedFile)
161
+ this.logger.info(`> ln -s ${linkName} ${linkedFile}`)
162
+ try {
163
+ await fs.symlink(linkedFile, linkName)
164
+ } catch (error) {
165
+ if ('code' in error && error.code === 'EEXIST') {
166
+ // OK
167
+ } else {
168
+ throw error
169
+ }
170
+ }
171
+ }
172
+ }
package/src/Files.ts ADDED
@@ -0,0 +1,22 @@
1
+ import * as fsp from 'fs/promises'
2
+ import path from 'path'
3
+
4
+ export class Files {
5
+ public static async exists(filepath: string) {
6
+ const stat = await fsp.stat(filepath).catch(() => undefined)
7
+ return Boolean(stat)
8
+ }
9
+
10
+ public static async existsAndIsDir(filepath: string) {
11
+ const stat = await fsp.stat(filepath).catch(() => undefined)
12
+ return Boolean(stat?.isDirectory())
13
+ }
14
+ public static async readUtf8File(filepath: string) {
15
+ return fsp.readFile(filepath, 'utf8')
16
+ }
17
+ public static isInSameTree(tree: string, filepath: string) {
18
+ const file = path.normalize(filepath)
19
+ const fileTree = path.normalize(tree)
20
+ return file.startsWith(fileTree)
21
+ }
22
+ }
package/src/Logger.ts CHANGED
@@ -1,11 +1,30 @@
1
- export const logger = {
2
- info: (message: string) => {
1
+ import { oraPromise } from 'ora'
2
+
3
+ export class Logger {
4
+ constructor(public readonly enableDebug: boolean = false) {}
5
+
6
+ info(message: string) {
3
7
  console.log(message)
4
- },
5
- verbose: (message: string) => {
8
+ }
9
+ verbose(message: string) {
6
10
  console.log(message)
7
- },
8
- error: (message: string) => {
11
+ }
12
+ error(message: string) {
9
13
  console.log(message)
10
- },
14
+ }
15
+ debug(message: string) {
16
+ if (this.enableDebug) {
17
+ console.log(message)
18
+ }
19
+ }
20
+ loader(message: string, promise: Promise<any>) {
21
+ if (this.enableDebug) {
22
+ this.info(message)
23
+ return promise
24
+ }
25
+
26
+ return oraPromise(promise, {
27
+ text: message,
28
+ })
29
+ }
11
30
  }
@@ -0,0 +1,28 @@
1
+ import { Files } from '../Files.js'
2
+ import { Merger } from './Merger.js'
3
+
4
+ export class ConfigMerger extends Merger {
5
+ name = 'Config'
6
+
7
+ async merge(originDir: string): Promise<string> {
8
+ const content = await this.getWhichExistsOrNull(originDir)
9
+ if (content) {
10
+ return content
11
+ }
12
+
13
+ const { originPath, destPath } = this.getPaths(originDir)
14
+
15
+ const [destTsConfig, originTsConfig] = await Promise.all([
16
+ Files.readUtf8File(destPath),
17
+ Files.readUtf8File(originPath),
18
+ ])
19
+ const configContent = /configSchema = {\n(.*?)\n}\n/s.exec(
20
+ originTsConfig
21
+ )?.[1]
22
+
23
+ return destTsConfig.replace(
24
+ '\n}\n',
25
+ configContent ? `${configContent}\n}\n` : '}\n'
26
+ )
27
+ }
28
+ }