@platformatic/service 0.29.0 → 0.30.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.
Files changed (28) hide show
  1. package/fixtures/hello-client-ts/tsconfig.json +3 -2
  2. package/index.js +9 -1
  3. package/lib/compile.js +14 -11
  4. package/lib/plugins/plugins.js +23 -13
  5. package/lib/schema.js +25 -0
  6. package/package.json +6 -6
  7. package/test/cli/{compile.test.mjs → compile-1.test.mjs} +150 -33
  8. package/test/cli/compile-2.test.mjs +310 -0
  9. package/test/cli/helper.mjs +15 -0
  10. package/test/cli/tap-parallel-not-ok +0 -0
  11. package/test/clients.test.js +4 -32
  12. package/test/fixtures/bad-typescript-plugin/platformatic.service.json +1 -1
  13. package/test/fixtures/bad-typescript-plugin/tsconfig.json +1 -1
  14. package/test/fixtures/typescript-autoload/tsconfig.json +3 -3
  15. package/test/fixtures/typescript-compiled/compiled/plugin.js +6 -0
  16. package/test/fixtures/typescript-compiled/platformatic.service.json +18 -0
  17. package/test/fixtures/typescript-plugin-custom-flags/platformatic.service.json +18 -0
  18. package/test/fixtures/typescript-plugin-custom-flags/plugin.ts +5 -0
  19. package/test/fixtures/typescript-plugin-custom-flags/tsconfig.json +22 -0
  20. package/test/fixtures/typescript-plugin-custom-tsconfig/a-config-for-ts.json +22 -0
  21. package/test/fixtures/typescript-plugin-custom-tsconfig/platformatic.service.json +18 -0
  22. package/test/fixtures/typescript-plugin-custom-tsconfig/plugin.ts +5 -0
  23. package/test/fixtures/typescript-plugin-nocompile/tsconfig.json +3 -3
  24. package/test/fixtures/typescript-plugin-nocompile-enabled/platformatic.service.json +18 -0
  25. package/test/fixtures/typescript-plugin-nocompile-enabled/plugin.ts +5 -0
  26. package/test/fixtures/typescript-plugin-nocompile-enabled/tsconfig.json +22 -0
  27. package/test/fixtures/typescript-plugin-string/tsconfig.json +3 -3
  28. package/test/tap-parallel-ok +0 -0
@@ -2,9 +2,10 @@
2
2
  "compilerOptions": {
3
3
  "module": "commonjs",
4
4
  "esModuleInterop": true,
5
- "target": "es6",
5
+ "target": "es2020",
6
6
  "moduleResolution": "node",
7
- "sourceMap": true,
7
+ "sourceMap": false,
8
+ "incremental": true,
8
9
  "pretty": true,
9
10
  "noEmitOnError": true,
10
11
  "outDir": "dist"
package/index.js CHANGED
@@ -46,7 +46,15 @@ async function platformaticService (app, opts, toLoad = []) {
46
46
  }
47
47
 
48
48
  if (config.plugins) {
49
- if (config.plugins.typescript) {
49
+ let registerTsCompiler = false
50
+ const typescript = config.plugins.typescript
51
+ if (typescript === true) {
52
+ registerTsCompiler = true
53
+ } else if (typeof typescript === 'object') {
54
+ registerTsCompiler = typescript.enabled === true || typescript.enabled === undefined
55
+ }
56
+
57
+ if (registerTsCompiler) {
50
58
  await app.register(setupTsCompiler)
51
59
  }
52
60
  await app.register(loadPlugins)
package/lib/compile.js CHANGED
@@ -48,29 +48,30 @@ async function setup (cwd, config, logger) {
48
48
  /* c8 ignore next 4 */
49
49
  if (tscExecutablePath === undefined) {
50
50
  const msg = 'The tsc executable was not found.'
51
- logger.error(msg)
51
+ logger.warn(msg)
52
52
  }
53
53
 
54
- const tsconfigPath = resolve(cwd, 'tsconfig.json')
55
- const tsconfigExists = await isFileAccessible(tsconfigPath)
54
+ const tsConfigPath = config?.plugins?.typescript?.tsConfig || resolve(cwd, 'tsconfig.json')
55
+ const tsConfigExists = await isFileAccessible(tsConfigPath)
56
56
 
57
- if (!tsconfigExists) {
58
- const msg = 'The tsconfig.json file was not found.'
59
- logger.error(msg)
57
+ if (!tsConfigExists) {
58
+ const msg = 'No typescript configuration file was found.'
59
+ logger.warn(msg)
60
60
  }
61
61
 
62
- return { execa, logger, tscExecutablePath }
62
+ return { execa, logger, tscExecutablePath, tsConfigPath, tsConfigExists }
63
63
  }
64
64
 
65
65
  async function compile (cwd, config, originalLogger) {
66
- const { execa, logger, tscExecutablePath } = await setup(cwd, config, originalLogger)
66
+ const { execa, logger, tscExecutablePath, tsConfigPath, tsConfigExists } = await setup(cwd, config, originalLogger)
67
67
  /* c8 ignore next 3 */
68
- if (!tscExecutablePath) {
68
+ if (!tscExecutablePath || !tsConfigExists) {
69
69
  return false
70
70
  }
71
71
 
72
72
  try {
73
- await execa(tscExecutablePath, ['--project', 'tsconfig.json', '--rootDir', '.'], { cwd })
73
+ const tsFlags = config?.plugins?.typescript?.flags || ['--project', tsConfigPath, '--rootDir', '.']
74
+ await execa(tscExecutablePath, tsFlags, { cwd })
74
75
  logger.info('Typescript compilation completed successfully.')
75
76
  return true
76
77
  } catch (error) {
@@ -82,11 +83,13 @@ async function compile (cwd, config, originalLogger) {
82
83
  function buildCompileCmd (app) {
83
84
  return async function compileCmd (_args) {
84
85
  let fullPath = null
86
+ let config = null
85
87
  try {
86
88
  const { configManager } = await loadConfig({}, _args, app, {
87
89
  watch: false
88
90
  })
89
91
  await configManager.parseAndValidate()
92
+ config = configManager.current
90
93
  fullPath = dirname(configManager.fullPath)
91
94
  /* c8 ignore next 4 */
92
95
  } catch (err) {
@@ -94,7 +97,7 @@ function buildCompileCmd (app) {
94
97
  process.exit(1)
95
98
  }
96
99
 
97
- if (!await compile(fullPath)) {
100
+ if (!await compile(fullPath, config)) {
98
101
  process.exit(1)
99
102
  }
100
103
  }
@@ -12,26 +12,36 @@ async function loadPlugins (app) {
12
12
  const configManager = app.platformatic.configManager
13
13
  const config = configManager.current
14
14
 
15
+ let isOutDirAccessible = false
16
+ let outDir = null
17
+
15
18
  const workingDir = configManager.dirname
16
- const tsConfigPath = join(workingDir, 'tsconfig.json')
19
+ const tsConfigPath = configManager.current.plugins.typescript?.tsConfig || join(workingDir, 'tsconfig.json')
17
20
 
18
21
  // If the tsconfig.json file exists, then we need to adjust the plugin paths
19
22
  // to point to the compiled JS files.
20
23
  const isTsConfigAccessible = await isFileAccessible(tsConfigPath)
21
24
  if (isTsConfigAccessible) {
22
25
  const tsConfig = JSON.parse(await readFile(tsConfigPath, 'utf8'))
23
- const outDir = resolve(workingDir, tsConfig.compilerOptions.outDir)
24
-
25
- const isOutDirAccessible = await isFileAccessible(outDir)
26
-
27
- if (isOutDirAccessible) {
28
- config.plugins.paths = config.plugins.paths.map((plugin) => {
29
- /* c8 ignore next 3 */
30
- return typeof plugin === 'string'
31
- ? getJSPluginPath(workingDir, plugin, outDir)
32
- : { ...plugin, path: getJSPluginPath(workingDir, plugin.path, outDir) }
33
- })
34
- }
26
+ outDir = resolve(workingDir, tsConfig.compilerOptions.outDir)
27
+ }
28
+
29
+ /* c8 ignore next 3 */
30
+ if (configManager.current.plugins.typescript?.outDir) {
31
+ outDir = configManager.current.plugins.typescript.outDir
32
+ }
33
+
34
+ if (outDir) {
35
+ isOutDirAccessible = await isFileAccessible(outDir)
36
+ }
37
+
38
+ if (isOutDirAccessible) {
39
+ config.plugins.paths = config.plugins.paths.map((plugin) => {
40
+ /* c8 ignore next 3 */
41
+ return typeof plugin === 'string'
42
+ ? getJSPluginPath(workingDir, plugin, outDir)
43
+ : { ...plugin, path: getJSPluginPath(workingDir, plugin.path, outDir) }
44
+ })
35
45
  }
36
46
 
37
47
  await app.register(require(wrapperPath), { paths: config.plugins.paths })
package/lib/schema.js CHANGED
@@ -391,6 +391,31 @@ const plugins = {
391
391
  },
392
392
  typescript: {
393
393
  anyOf: [{
394
+ type: 'object',
395
+ properties: {
396
+ enabled: {
397
+ anyOf: [{
398
+ type: 'boolean'
399
+ }, {
400
+ type: 'string'
401
+ }]
402
+ },
403
+ tsConfig: {
404
+ type: 'string',
405
+ resolvePath: true
406
+ },
407
+ outDir: {
408
+ type: 'string',
409
+ resolvePath: true
410
+ },
411
+ flags: {
412
+ type: 'array',
413
+ items: {
414
+ type: 'string'
415
+ }
416
+ }
417
+ }
418
+ }, {
394
419
  type: 'boolean'
395
420
  }, {
396
421
  type: 'string'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/service",
3
- "version": "0.29.0",
3
+ "version": "0.30.1",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -64,11 +64,11 @@
64
64
  "pino-pretty": "^10.0.0",
65
65
  "rfdc": "^1.3.0",
66
66
  "ua-parser-js": "^1.0.35",
67
- "@platformatic/client": "0.29.0",
68
- "@platformatic/config": "0.29.0",
69
- "@platformatic/swagger-ui-theme": "0.29.0",
70
- "@platformatic/types": "0.29.0",
71
- "@platformatic/utils": "0.29.0"
67
+ "@platformatic/client": "0.30.1",
68
+ "@platformatic/config": "0.30.1",
69
+ "@platformatic/swagger-ui-theme": "0.30.1",
70
+ "@platformatic/types": "0.30.1",
71
+ "@platformatic/utils": "0.30.1"
72
72
  },
73
73
  "standard": {
74
74
  "ignore": [
@@ -1,34 +1,49 @@
1
1
  import path from 'path'
2
2
  import os from 'os'
3
- import { access, rename, cp } from 'fs/promises'
3
+ import { access, rename, cp, rm, mkdir } from 'fs/promises'
4
4
  import t from 'tap'
5
5
  import { execa } from 'execa'
6
6
  import stripAnsi from 'strip-ansi'
7
7
  import split from 'split2'
8
- import { cliPath } from './helper.mjs'
8
+ import { cliPath, safeKill } from './helper.mjs'
9
9
  import { fileURLToPath } from 'url'
10
10
 
11
+ let count = 0
12
+
13
+ if (os.platform() !== 'win32') {
14
+ t.jobs = 5
15
+ }
16
+ t.setTimeout(360000)
17
+
11
18
  function urlDirname (url) {
12
19
  return path.dirname(fileURLToPath(url))
13
20
  }
14
21
 
22
+ async function getCWD (t) {
23
+ const dir = path.join(urlDirname(import.meta.url), '..', 'tmp', `typescript-plugin-clone-${count++}`)
24
+ try {
25
+ await rm(dir, { recursive: true })
26
+ } catch {}
27
+
28
+ await mkdir(dir, { recursive: true })
29
+
30
+ t.teardown(async () => {
31
+ try {
32
+ await rm(dir, { recursive: true })
33
+ } catch {}
34
+ })
35
+ return dir
36
+ }
37
+
15
38
  function exitOnTeardown (child) {
16
39
  return async () => {
17
- if (os.platform() === 'win32') {
18
- try {
19
- await execa('taskkill', ['/pid', child.pid, '/f', '/t'])
20
- } catch (err) {
21
- console.error(`Failed to kill process ${child.pid})`)
22
- }
23
- } else {
24
- child.kill('SIGINT')
25
- }
40
+ await safeKill(child)
26
41
  }
27
42
  }
28
43
 
29
44
  t.test('should compile typescript plugin', async (t) => {
30
45
  const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'typescript-plugin')
31
- const cwd = path.join(urlDirname(import.meta.url), '..', 'tmp', 'typescript-plugin-clone-1')
46
+ const cwd = await getCWD(t)
32
47
 
33
48
  await cp(testDir, cwd, { recursive: true })
34
49
 
@@ -58,7 +73,7 @@ t.test('should compile typescript plugin', async (t) => {
58
73
 
59
74
  t.test('should compile typescript plugin even if typescript is `false`', async (t) => {
60
75
  const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'typescript-plugin-nocompile')
61
- const cwd = path.join(urlDirname(import.meta.url), '..', 'tmp', 'typescript-plugin-clone-2')
76
+ const cwd = await getCWD(t)
62
77
 
63
78
  await cp(testDir, cwd, { recursive: true })
64
79
 
@@ -88,7 +103,7 @@ t.test('should compile typescript plugin even if typescript is `false`', async (
88
103
 
89
104
  t.test('should compile typescript plugin with start command', async (t) => {
90
105
  const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'typescript-plugin')
91
- const cwd = path.join(urlDirname(import.meta.url), '..', 'tmp', 'typescript-plugin-clone-3')
106
+ const cwd = await getCWD(t)
92
107
 
93
108
  await cp(testDir, cwd, { recursive: true })
94
109
 
@@ -108,7 +123,9 @@ t.test('should compile typescript plugin with start command', async (t) => {
108
123
  })
109
124
 
110
125
  t.test('should not compile bad typescript plugin', async (t) => {
111
- const cwd = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'bad-typescript-plugin')
126
+ const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'bad-typescript-plugin')
127
+ const cwd = await getCWD(t)
128
+ await cp(testDir, cwd, { recursive: true })
112
129
 
113
130
  try {
114
131
  await execa('node', [cliPath, 'compile'], { cwd })
@@ -130,7 +147,7 @@ t.test('should not compile bad typescript plugin', async (t) => {
130
147
 
131
148
  t.test('missing tsconfig file', async (t) => {
132
149
  const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'typescript-plugin')
133
- const cwd = path.join(urlDirname(import.meta.url), '..', 'tmp', 'typescript-plugin-clone-4')
150
+ const cwd = await getCWD(t)
134
151
 
135
152
  await cp(testDir, cwd, { recursive: true })
136
153
 
@@ -145,20 +162,21 @@ t.test('missing tsconfig file', async (t) => {
145
162
  } catch (err) {
146
163
  t.comment(err.stdout)
147
164
  t.comment(err.stderr)
148
- t.equal(err.stdout.includes('The tsconfig.json file was not found.'), true)
165
+ t.equal(err.stdout.includes('No typescript configuration file was found.'), true)
149
166
  }
150
167
 
151
168
  t.pass()
152
169
  })
153
170
 
154
171
  t.test('start command should not compile typescript plugin with errors', async (t) => {
155
- const cwd = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'bad-typescript-plugin')
172
+ const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'bad-typescript-plugin')
173
+ const cwd = await getCWD(t)
174
+
175
+ await cp(testDir, cwd, { recursive: true })
156
176
 
157
177
  const childProcess = execa('node', [cliPath, 'start'], { cwd })
158
178
 
159
- t.teardown(() => {
160
- childProcess.kill('SIGINT')
161
- })
179
+ t.teardown(exitOnTeardown(childProcess))
162
180
 
163
181
  try {
164
182
  await childProcess
@@ -167,9 +185,10 @@ t.test('start command should not compile typescript plugin with errors', async (
167
185
  if (!err.stdout.includes('Found 1 error')) {
168
186
  t.comment(err.stdout)
169
187
  t.comment(err.stderr)
188
+ console.error(err)
170
189
  t.fail('should throw one ts error')
171
190
  }
172
- childProcess.kill('SIGINT')
191
+ await safeKill(childProcess)
173
192
  }
174
193
 
175
194
  const jsPluginPath = path.join(cwd, 'dist', 'plugin.js')
@@ -183,7 +202,7 @@ t.test('start command should not compile typescript plugin with errors', async (
183
202
 
184
203
  t.test('should not compile typescript plugin with start without tsconfig', async (t) => {
185
204
  const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'typescript-plugin')
186
- const cwd = path.join(urlDirname(import.meta.url), '..', 'tmp', 'typescript-plugin-clone-5')
205
+ const cwd = await getCWD(t)
187
206
 
188
207
  await cp(testDir, cwd, { recursive: true })
189
208
 
@@ -194,17 +213,17 @@ t.test('should not compile typescript plugin with start without tsconfig', async
194
213
 
195
214
  try {
196
215
  const child = await execa('node', [cliPath, 'start'], { cwd })
197
- t.teardown(() => child.kill('SIGINT'))
216
+ t.teardown(exitOnTeardown(child))
198
217
  t.fail('should not compile typescript plugin with start without tsconfig')
199
218
  } catch (err) {
200
219
  t.comment(err.stdout)
201
- t.equal(err.stdout.includes('The tsconfig.json file was not found.'), true)
220
+ t.equal(err.stdout.includes('No typescript configuration file was found'), true)
202
221
  }
203
222
  })
204
223
 
205
224
  t.test('start command should not compile typescript if `typescript` is false', async (t) => {
206
225
  const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'typescript-plugin-nocompile')
207
- const cwd = path.join(urlDirname(import.meta.url), '..', 'tmp', 'typescript-plugin-clone-6')
226
+ const cwd = await getCWD(t)
208
227
 
209
228
  await cp(testDir, cwd, { recursive: true })
210
229
 
@@ -225,7 +244,7 @@ t.test('start command should not compile typescript if `typescript` is false', a
225
244
 
226
245
  t.test('should compile typescript plugin with start command with different cwd', async (t) => {
227
246
  const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'typescript-plugin')
228
- const dest = path.join(urlDirname(import.meta.url), '..', 'tmp', 'typescript-plugin-clone-4')
247
+ const dest = path.join(urlDirname(import.meta.url), '..', 'tmp', `typescript-plugin-clone-${count++}`)
229
248
 
230
249
  await cp(testDir, dest, { recursive: true })
231
250
 
@@ -249,7 +268,7 @@ t.test('should compile typescript plugin with start command with different cwd',
249
268
 
250
269
  t.test('valid tsconfig file inside an inner folder', async (t) => {
251
270
  const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'typescript-plugin')
252
- const cwd = path.join(urlDirname(import.meta.url), '..', 'tmp', 'typescript-plugin-clone-7/inner-folder')
271
+ const cwd = await getCWD(t)
253
272
 
254
273
  await cp(testDir, cwd, { recursive: true })
255
274
 
@@ -264,7 +283,7 @@ t.test('valid tsconfig file inside an inner folder', async (t) => {
264
283
 
265
284
  t.test('should compile typescript plugin with start command from a folder', async (t) => {
266
285
  const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'typescript-autoload')
267
- const cwd = path.join(urlDirname(import.meta.url), '..', 'tmp', 'typescript-plugin-clone-8')
286
+ const cwd = await getCWD(t)
268
287
 
269
288
  await cp(testDir, cwd, { recursive: true })
270
289
 
@@ -287,7 +306,7 @@ t.test('should compile typescript plugin with start command from a folder', asyn
287
306
 
288
307
  t.test('should start the service if it was precompiled and typescript is `false`', async (t) => {
289
308
  const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'typescript-plugin-nocompile')
290
- const cwd = path.join(urlDirname(import.meta.url), '..', 'tmp', 'typescript-plugin-clone-9')
309
+ const cwd = await getCWD(t)
291
310
 
292
311
  await cp(testDir, cwd, { recursive: true })
293
312
 
@@ -310,7 +329,7 @@ t.test('should start the service if it was precompiled and typescript is `false`
310
329
 
311
330
  t.test('should not start the service if it was not precompiled and typescript is `false`', async (t) => {
312
331
  const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'typescript-plugin-nocompile')
313
- const cwd = path.join(urlDirname(import.meta.url), '..', 'tmp', 'typescript-plugin-clone-10')
332
+ const cwd = await getCWD(t)
314
333
 
315
334
  await cp(testDir, cwd, { recursive: true })
316
335
 
@@ -332,7 +351,7 @@ t.test('should not start the service if it was not precompiled and typescript is
332
351
 
333
352
  t.test('should compile typescript plugin with string config', async (t) => {
334
353
  const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'typescript-plugin-string')
335
- const cwd = path.join(urlDirname(import.meta.url), '..', 'tmp', 'typescript-plugin-clone-11')
354
+ const cwd = await getCWD(t)
336
355
 
337
356
  await cp(testDir, cwd, { recursive: true })
338
357
 
@@ -362,7 +381,7 @@ t.test('should compile typescript plugin with string config', async (t) => {
362
381
 
363
382
  t.test('should not start the service if it was not precompiled and typescript is `"false"`', async (t) => {
364
383
  const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'typescript-plugin-nocompile')
365
- const cwd = path.join(urlDirname(import.meta.url), '..', 'tmp', 'typescript-plugin-clone-12')
384
+ const cwd = await getCWD(t)
366
385
 
367
386
  await cp(testDir, cwd, { recursive: true })
368
387
 
@@ -381,3 +400,101 @@ t.test('should not start the service if it was not precompiled and typescript is
381
400
  }
382
401
  t.fail('should load the typescript plugin without compiling it')
383
402
  })
403
+
404
+ t.test('should not compile bad typescript plugin', async (t) => {
405
+ const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'bad-typescript-plugin')
406
+ const cwd = await getCWD(t)
407
+ await cp(testDir, cwd, { recursive: true })
408
+
409
+ try {
410
+ await execa('node', [cliPath, 'compile'], { cwd })
411
+ t.fail('should not compile bad typescript plugin')
412
+ } catch (err) {
413
+ t.comment(err.stdout)
414
+ t.comment(err.stderr)
415
+ t.equal(err.stdout.includes('Found 1 error in plugin.ts'), true)
416
+ }
417
+
418
+ const jsPluginPath = path.join(cwd, 'dist', 'plugin.js')
419
+ try {
420
+ await access(jsPluginPath)
421
+ t.fail('should not compile bad typescript plugin')
422
+ } catch (err) {
423
+ t.pass(err)
424
+ }
425
+ })
426
+
427
+ t.test('missing tsconfig file', async (t) => {
428
+ const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'typescript-plugin')
429
+ const cwd = await getCWD(t)
430
+
431
+ await cp(testDir, cwd, { recursive: true })
432
+
433
+ const pathToTSConfig = path.join(cwd, 'tsconfig.json')
434
+ const pathToTSConfigBackup = path.join(cwd, 'tsconfig.json.backup')
435
+
436
+ await rename(pathToTSConfig, pathToTSConfigBackup)
437
+
438
+ try {
439
+ await execa('node', [cliPath, 'compile'], { cwd })
440
+ t.fail('should not compile typescript plugin')
441
+ } catch (err) {
442
+ t.comment(err.stdout)
443
+ t.comment(err.stderr)
444
+ t.equal(err.stdout.includes('No typescript configuration file was found.'), true)
445
+ }
446
+
447
+ t.pass()
448
+ })
449
+
450
+ t.test('start command should not compile typescript plugin with errors', async (t) => {
451
+ const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'bad-typescript-plugin')
452
+ const cwd = await getCWD(t)
453
+ await cp(testDir, cwd, { recursive: true })
454
+
455
+ const childProcess = execa('node', [cliPath, 'start'], { cwd })
456
+
457
+ t.teardown(exitOnTeardown(childProcess))
458
+
459
+ try {
460
+ await childProcess
461
+ t.fail('should not compile bad typescript plugin')
462
+ } catch (err) {
463
+ if (!err.stdout.includes('Found 1 error')) {
464
+ t.comment(err.stdout)
465
+ t.comment(err.stderr)
466
+ console.error(err)
467
+ t.fail('should throw one ts error')
468
+ }
469
+ safeKill(childProcess)
470
+ }
471
+
472
+ const jsPluginPath = path.join(cwd, 'dist', 'plugin.js')
473
+ try {
474
+ await access(jsPluginPath)
475
+ t.fail('should not compile bad typescript plugin')
476
+ } catch (err) {
477
+ t.pass(err)
478
+ }
479
+ })
480
+
481
+ t.test('should not compile typescript plugin with start without tsconfig', async (t) => {
482
+ const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'typescript-plugin')
483
+ const cwd = await getCWD(t)
484
+
485
+ await cp(testDir, cwd, { recursive: true })
486
+
487
+ const pathToTSConfig = path.join(cwd, 'tsconfig.json')
488
+ const pathToTSConfigBackup = path.join(cwd, 'tsconfig.json.backup')
489
+
490
+ await rename(pathToTSConfig, pathToTSConfigBackup)
491
+
492
+ try {
493
+ const child = await execa('node', [cliPath, 'start'], { cwd })
494
+ t.teardown(exitOnTeardown(child))
495
+ t.fail('should not compile typescript plugin with start without tsconfig')
496
+ } catch (err) {
497
+ t.comment(err.stdout)
498
+ t.equal(err.stdout.includes('No typescript configuration file was found.'), true)
499
+ }
500
+ })
@@ -0,0 +1,310 @@
1
+ import path from 'path'
2
+ import os from 'os'
3
+ import { access, cp, rm, mkdir } from 'fs/promises'
4
+ import t from 'tap'
5
+ import { execa } from 'execa'
6
+ import stripAnsi from 'strip-ansi'
7
+ import split from 'split2'
8
+ import { cliPath, safeKill } from './helper.mjs'
9
+ import { fileURLToPath } from 'url'
10
+
11
+ let count = 0
12
+
13
+ if (os.platform() !== 'win32') {
14
+ t.jobs = 5
15
+ }
16
+ t.setTimeout(360000)
17
+
18
+ function urlDirname (url) {
19
+ return path.dirname(fileURLToPath(url))
20
+ }
21
+
22
+ async function getCWD (t) {
23
+ const dir = path.join(urlDirname(import.meta.url), '..', 'tmp', `typescript-plugin-clone2-${count++}`)
24
+ try {
25
+ await rm(dir, { recursive: true })
26
+ } catch {}
27
+
28
+ await mkdir(dir, { recursive: true })
29
+
30
+ t.teardown(async () => {
31
+ try {
32
+ await rm(dir, { recursive: true })
33
+ } catch {}
34
+ })
35
+ return dir
36
+ }
37
+
38
+ function exitOnTeardown (child) {
39
+ return async () => {
40
+ await safeKill(child)
41
+ }
42
+ }
43
+
44
+ t.test('start command should not compile typescript if `typescript` is false', async (t) => {
45
+ const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'typescript-plugin-nocompile')
46
+ const cwd = await getCWD(t)
47
+
48
+ await cp(testDir, cwd, { recursive: true })
49
+
50
+ const child = execa('node', [cliPath, 'start'], { cwd })
51
+ t.teardown(exitOnTeardown(child))
52
+
53
+ const jsPluginPath = path.join(cwd, 'dist', 'plugin.js')
54
+ try {
55
+ await access(jsPluginPath)
56
+ t.fail("should not have created 'dist/plugin.js'")
57
+ } catch (err) {
58
+ // cannot start because the plugin is not compiled
59
+ t.equal(err.code, 'ENOENT')
60
+ t.equal(err.path, jsPluginPath)
61
+ t.pass()
62
+ }
63
+ })
64
+
65
+ t.test('should compile typescript plugin with start command with different cwd', async (t) => {
66
+ const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'typescript-plugin')
67
+ const dest = path.join(urlDirname(import.meta.url), '..', 'tmp', `typescript-plugin-clone-${count++}`)
68
+
69
+ await cp(testDir, dest, { recursive: true })
70
+
71
+ const child = execa('node', [cliPath, 'start', '-c', path.join(dest, 'platformatic.service.json')])
72
+
73
+ t.teardown(exitOnTeardown(child))
74
+
75
+ const splitter = split()
76
+ child.stdout.pipe(splitter)
77
+ child.stderr.pipe(process.stderr)
78
+
79
+ for await (const data of splitter) {
80
+ const sanitized = stripAnsi(data)
81
+ if (sanitized.includes('Typescript plugin loaded')) {
82
+ t.pass()
83
+ return
84
+ }
85
+ }
86
+ t.fail('should compile typescript plugin with start command')
87
+ })
88
+
89
+ t.test('valid tsconfig file inside an inner folder', async (t) => {
90
+ const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'typescript-plugin')
91
+ const cwd = await getCWD(t)
92
+
93
+ await cp(testDir, cwd, { recursive: true })
94
+
95
+ try {
96
+ await execa('node', [cliPath, 'compile'], { cwd, stdio: 'inherit' })
97
+ } catch (err) {
98
+ t.fail('should not catch any error')
99
+ }
100
+
101
+ t.pass()
102
+ })
103
+
104
+ t.test('should compile typescript plugin with start command from a folder', async (t) => {
105
+ const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'typescript-autoload')
106
+ const cwd = await getCWD(t)
107
+
108
+ await cp(testDir, cwd, { recursive: true })
109
+
110
+ const child = execa('node', [cliPath, 'start'], { cwd })
111
+
112
+ t.teardown(exitOnTeardown(child))
113
+
114
+ const splitter = split()
115
+ child.stdout.pipe(splitter)
116
+
117
+ for await (const data of splitter) {
118
+ const sanitized = stripAnsi(data)
119
+ if (sanitized.includes('Typescript plugin loaded')) {
120
+ t.pass()
121
+ return
122
+ }
123
+ }
124
+ t.fail('should compile typescript plugin with start command')
125
+ })
126
+
127
+ t.test('should start the service if it was precompiled and typescript is `false`', async (t) => {
128
+ const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'typescript-plugin-nocompile')
129
+ const cwd = await getCWD(t)
130
+
131
+ await cp(testDir, cwd, { recursive: true })
132
+
133
+ await execa('node', [cliPath, 'compile'], { cwd })
134
+
135
+ const child = execa('node', [cliPath, 'start'], { cwd })
136
+
137
+ const splitter = split()
138
+ child.stdout.pipe(splitter)
139
+
140
+ for await (const data of splitter) {
141
+ const sanitized = stripAnsi(data)
142
+ if (sanitized.includes('Typescript plugin loaded')) {
143
+ t.pass()
144
+ return
145
+ }
146
+ }
147
+ t.fail('should load the typescript plugin without compiling it')
148
+ })
149
+
150
+ t.test('should not start the service if it was not precompiled and typescript is `false`', async (t) => {
151
+ const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'typescript-plugin-nocompile')
152
+ const cwd = await getCWD(t)
153
+
154
+ await cp(testDir, cwd, { recursive: true })
155
+
156
+ const child = execa('node', [cliPath, 'start'], { cwd })
157
+
158
+ const splitter = split()
159
+ child.stdout.pipe(splitter)
160
+ child.stderr.pipe(splitter)
161
+
162
+ for await (const data of splitter) {
163
+ const sanitized = stripAnsi(data)
164
+ if (sanitized.includes('Unknown file extension ".ts" for')) {
165
+ t.pass()
166
+ return
167
+ }
168
+ }
169
+ t.fail('should load the typescript plugin without compiling it')
170
+ })
171
+
172
+ t.test('should compile typescript plugin with string config', async (t) => {
173
+ const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'typescript-plugin-string')
174
+ const cwd = await getCWD(t)
175
+
176
+ await cp(testDir, cwd, { recursive: true })
177
+
178
+ const child = execa('node', [cliPath, 'compile'], { cwd })
179
+
180
+ t.teardown(exitOnTeardown(child))
181
+
182
+ const splitter = split()
183
+ child.stdout.pipe(splitter)
184
+
185
+ for await (const data of splitter) {
186
+ const sanitized = stripAnsi(data)
187
+ if (sanitized.includes('Typescript compilation completed successfully.')) {
188
+ const jsPluginPath = path.join(cwd, 'dist', 'plugin.js')
189
+ try {
190
+ await access(jsPluginPath)
191
+ } catch (err) {
192
+ t.fail(err)
193
+ }
194
+
195
+ t.pass()
196
+ return
197
+ }
198
+ }
199
+ t.fail('should compile typescript plugin with a compile command')
200
+ })
201
+
202
+ t.test('should not start the service if it was not precompiled and typescript is `"false"`', async (t) => {
203
+ const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'typescript-plugin-nocompile')
204
+ const cwd = await getCWD(t)
205
+
206
+ await cp(testDir, cwd, { recursive: true })
207
+
208
+ const child = execa('node', [cliPath, 'start'], { cwd })
209
+
210
+ const splitter = split()
211
+ child.stdout.pipe(splitter)
212
+ child.stderr.pipe(splitter)
213
+
214
+ for await (const data of splitter) {
215
+ const sanitized = stripAnsi(data)
216
+ if (sanitized.includes('Unknown file extension ".ts" for')) {
217
+ t.pass()
218
+ return
219
+ }
220
+ }
221
+ t.fail('should load the typescript plugin without compiling it')
222
+ })
223
+
224
+ t.test('should compile typescript plugin with start command with custom tsconfig', async (t) => {
225
+ const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'typescript-plugin-custom-tsconfig')
226
+ const cwd = await getCWD(t)
227
+
228
+ await cp(testDir, cwd, { recursive: true })
229
+
230
+ const child = execa('node', [cliPath, 'start'], { cwd })
231
+
232
+ const splitter = split()
233
+ child.stdout.pipe(splitter)
234
+ child.stderr.pipe(splitter)
235
+
236
+ for await (const data of splitter) {
237
+ const sanitized = stripAnsi(data)
238
+ if (sanitized.includes('Typescript plugin loaded')) {
239
+ t.pass()
240
+ return
241
+ }
242
+ }
243
+ t.fail('should compile typescript plugin with start command')
244
+ })
245
+
246
+ t.test('should not start the service if it was not precompiled and typescript is `false`', async (t) => {
247
+ const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'typescript-plugin-nocompile-enabled')
248
+ const cwd = await getCWD(t)
249
+
250
+ await cp(testDir, cwd, { recursive: true })
251
+
252
+ const child = execa('node', [cliPath, 'start'], { cwd })
253
+
254
+ const splitter = split()
255
+ child.stdout.pipe(splitter)
256
+ child.stderr.pipe(splitter)
257
+
258
+ for await (const data of splitter) {
259
+ const sanitized = stripAnsi(data)
260
+ if (sanitized.includes('Unknown file extension ".ts" for')) {
261
+ t.pass()
262
+ return
263
+ }
264
+ }
265
+ t.fail('should load the typescript plugin without compiling it')
266
+ })
267
+
268
+ t.test('should start without a tsconfig but with a outDir configuration', async (t) => {
269
+ const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'typescript-compiled')
270
+ const cwd = await getCWD(t)
271
+
272
+ await cp(testDir, cwd, { recursive: true })
273
+
274
+ const child = execa('node', [cliPath, 'start'], { cwd })
275
+
276
+ const splitter = split()
277
+ child.stdout.pipe(splitter)
278
+ child.stderr.pipe(splitter)
279
+
280
+ for await (const data of splitter) {
281
+ const sanitized = stripAnsi(data)
282
+ if (sanitized.includes('Typescript plugin loaded')) {
283
+ t.pass()
284
+ return
285
+ }
286
+ }
287
+ t.fail('should compile typescript plugin with start command')
288
+ })
289
+
290
+ t.test('should compile typescript plugin with start command with custom flags', async (t) => {
291
+ const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'typescript-plugin-custom-flags')
292
+ const cwd = await getCWD(t)
293
+
294
+ await cp(testDir, cwd, { recursive: true })
295
+
296
+ const child = execa('node', [cliPath, 'start'], { cwd })
297
+
298
+ const splitter = split()
299
+ child.stdout.pipe(splitter)
300
+ child.stderr.pipe(splitter)
301
+
302
+ for await (const data of splitter) {
303
+ const sanitized = stripAnsi(data)
304
+ if (sanitized.includes('Typescript plugin loaded')) {
305
+ t.pass()
306
+ return
307
+ }
308
+ }
309
+ t.fail('should compile typescript plugin with start command')
310
+ })
@@ -4,6 +4,7 @@ import { execa } from 'execa'
4
4
  import split from 'split2'
5
5
  import { join } from 'desm'
6
6
  import tap from 'tap'
7
+ import os from 'node:os'
7
8
 
8
9
  setGlobalDispatcher(new Agent({
9
10
  keepAliveTimeout: 10,
@@ -50,3 +51,17 @@ export async function start (commandOpts, exacaOpts = {}) {
50
51
  }
51
52
  }
52
53
  }
54
+
55
+ export async function safeKill (child) {
56
+ child.kill('SIGINT')
57
+ if (os.platform() === 'win32') {
58
+ try {
59
+ await execa('taskkill', ['/pid', child.pid, '/f', '/t'])
60
+ } catch (err) {
61
+ if (err.stderr.indexOf('not found') === 0) {
62
+ console.error(`Failed to kill process ${child.pid}`)
63
+ console.error(err)
64
+ }
65
+ }
66
+ }
67
+ }
File without changes
@@ -6,7 +6,7 @@ const { buildServer } = require('..')
6
6
  const { join } = require('path')
7
7
  const { request } = require('undici')
8
8
  const { compile } = require('../lib/compile')
9
- const { rmdir } = require('fs/promises')
9
+ const { rm } = require('fs/promises')
10
10
 
11
11
  test('client is loaded', async ({ teardown, equal, same }) => {
12
12
  const app1 = await buildServer(join(__dirname, '..', 'fixtures', 'hello', 'warn-log.service.json'))
@@ -44,10 +44,12 @@ test('client is loaded (ts)', async ({ teardown, equal, pass, same }) => {
44
44
  const targetDir = join(__dirname, '..', 'fixtures', 'hello-client-ts')
45
45
 
46
46
  try {
47
- await rmdir(join(targetDir, 'dist'))
47
+ await rm(join(targetDir, 'dist'), { recursive: true })
48
48
  } catch {}
49
49
 
50
+ console.time('compile')
50
51
  await compile(targetDir, { server: { logger: { level: 'warn' } } })
52
+ console.timeEnd('compile')
51
53
 
52
54
  const app2 = await buildServer(join(targetDir, 'platformatic.service.json'))
53
55
  teardown(async () => {
@@ -83,33 +85,3 @@ test('client is loaded dependencyless', async ({ teardown, equal, same }) => {
83
85
  const data = await res.body.json()
84
86
  same(data, { hello: 'world' })
85
87
  })
86
-
87
- test('client is loaded (ts) depencyless', async ({ teardown, equal, pass, same }) => {
88
- const app1 = await buildServer(join(__dirname, '..', 'fixtures', 'hello', 'warn-log.service.json'))
89
-
90
- teardown(async () => {
91
- await app1.close()
92
- })
93
- await app1.start()
94
-
95
- process.env.PLT_CLIENT_URL = app1.url
96
-
97
- const targetDir = join(__dirname, '..', 'fixtures', 'hello-client-ts-without-deps')
98
-
99
- try {
100
- await rmdir(join(targetDir, 'dist'))
101
- } catch {}
102
-
103
- await compile(targetDir, { server: { logger: { level: 'warn' } } })
104
-
105
- const app2 = await buildServer(join(targetDir, 'platformatic.service.json'))
106
- teardown(async () => {
107
- await app2.close()
108
- })
109
- await app2.start()
110
-
111
- const res = await request(`${app2.url}/`)
112
- equal(res.statusCode, 200, 'status code')
113
- const data = await res.body.json()
114
- same(data, { hello: 'world' })
115
- })
@@ -5,7 +5,7 @@
5
5
  },
6
6
  "hostname": "127.0.0.1",
7
7
  "port": "3042",
8
- "pluginTimeout": 30000,
8
+ "pluginTimeout": 60000,
9
9
  "keepAliveTimeout": 1
10
10
  },
11
11
  "plugins": {
@@ -2,7 +2,7 @@
2
2
  "compilerOptions": {
3
3
  "module": "commonjs",
4
4
  "esModuleInterop": true,
5
- "target": "es6",
5
+ "target": "es2020",
6
6
  "moduleResolution": "node",
7
7
  "sourceMap": true,
8
8
  "pretty": true,
@@ -2,10 +2,10 @@
2
2
  "compilerOptions": {
3
3
  "module": "commonjs",
4
4
  "esModuleInterop": true,
5
- "target": "es2019",
5
+ "target": "es2020",
6
6
  "moduleResolution": "node",
7
- "sourceMap": true,
8
- "pretty": true,
7
+ "sourceMap": false,
8
+ "pretty": false,
9
9
  "noEmitOnError": true,
10
10
  "outDir": "dist"
11
11
  },
@@ -0,0 +1,6 @@
1
+ 'use strict'
2
+
3
+ async function plugin (app) {
4
+ app.log.info('Typescript plugin loaded')
5
+ }
6
+ module.exports = plugin
@@ -0,0 +1,18 @@
1
+ {
2
+ "server": {
3
+ "logger": {
4
+ "level": "info"
5
+ },
6
+ "hostname": "127.0.0.1",
7
+ "port": "0",
8
+ "pluginTimeout": 60000,
9
+ "keepAliveTimeout": 1
10
+ },
11
+ "plugins": {
12
+ "paths": ["plugin.ts"],
13
+ "typescript": {
14
+ "outDir": "compiled"
15
+ }
16
+ },
17
+ "watch": false
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "server": {
3
+ "logger": {
4
+ "level": "info"
5
+ },
6
+ "hostname": "127.0.0.1",
7
+ "port": "0",
8
+ "pluginTimeout": 60000,
9
+ "keepAliveTimeout": 1
10
+ },
11
+ "plugins": {
12
+ "paths": ["plugin.ts"],
13
+ "typescript": {
14
+ "flags": ["-b"]
15
+ }
16
+ },
17
+ "watch": false
18
+ }
@@ -0,0 +1,5 @@
1
+ import { FastifyInstance } from 'fastify'
2
+
3
+ export default async function (app: FastifyInstance) {
4
+ app.log.info('Typescript plugin loaded')
5
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "commonjs",
4
+ "esModuleInterop": true,
5
+ "target": "es2020",
6
+ "moduleResolution": "node",
7
+ "sourceMap": false,
8
+ "pretty": false,
9
+ "noEmitOnError": true,
10
+ "outDir": "dist"
11
+ },
12
+ "watchOptions": {
13
+ "watchFile": "fixedPollingInterval",
14
+ "watchDirectory": "fixedPollingInterval",
15
+ "fallbackPolling": "dynamicPriority",
16
+ "synchronousWatchDirectory": true,
17
+ "excludeDirectories": [
18
+ "**/node_modules",
19
+ "dist"
20
+ ]
21
+ }
22
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "commonjs",
4
+ "esModuleInterop": true,
5
+ "target": "es6",
6
+ "moduleResolution": "node",
7
+ "sourceMap": true,
8
+ "pretty": true,
9
+ "noEmitOnError": true,
10
+ "outDir": "dist"
11
+ },
12
+ "watchOptions": {
13
+ "watchFile": "fixedPollingInterval",
14
+ "watchDirectory": "fixedPollingInterval",
15
+ "fallbackPolling": "dynamicPriority",
16
+ "synchronousWatchDirectory": true,
17
+ "excludeDirectories": [
18
+ "**/node_modules",
19
+ "dist"
20
+ ]
21
+ }
22
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "server": {
3
+ "logger": {
4
+ "level": "info"
5
+ },
6
+ "hostname": "127.0.0.1",
7
+ "port": "0",
8
+ "pluginTimeout": 60000,
9
+ "keepAliveTimeout": 1
10
+ },
11
+ "plugins": {
12
+ "paths": ["plugin.ts"],
13
+ "typescript": {
14
+ "tsConfig": "./a-config-for-ts.json"
15
+ }
16
+ },
17
+ "watch": false
18
+ }
@@ -0,0 +1,5 @@
1
+ import { FastifyInstance } from 'fastify'
2
+
3
+ export default async function (app: FastifyInstance) {
4
+ app.log.info('Typescript plugin loaded')
5
+ }
@@ -2,10 +2,10 @@
2
2
  "compilerOptions": {
3
3
  "module": "commonjs",
4
4
  "esModuleInterop": true,
5
- "target": "es6",
5
+ "target": "es2020",
6
6
  "moduleResolution": "node",
7
- "sourceMap": true,
8
- "pretty": true,
7
+ "sourceMap": false,
8
+ "pretty": false,
9
9
  "noEmitOnError": true,
10
10
  "outDir": "dist"
11
11
  },
@@ -0,0 +1,18 @@
1
+ {
2
+ "server": {
3
+ "logger": {
4
+ "level": "info"
5
+ },
6
+ "hostname": "127.0.0.1",
7
+ "port": "0",
8
+ "pluginTimeout": 60000,
9
+ "keepAliveTimeout": 1
10
+ },
11
+ "plugins": {
12
+ "paths": ["plugin.ts"],
13
+ "typescript": {
14
+ "enabled": "false"
15
+ }
16
+ },
17
+ "watch": false
18
+ }
@@ -0,0 +1,5 @@
1
+ import { FastifyInstance } from 'fastify'
2
+
3
+ export default async function (app: FastifyInstance) {
4
+ app.log.info('Typescript plugin loaded')
5
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "commonjs",
4
+ "esModuleInterop": true,
5
+ "target": "es2020",
6
+ "moduleResolution": "node",
7
+ "sourceMap": false,
8
+ "pretty": false,
9
+ "noEmitOnError": true,
10
+ "outDir": "dist"
11
+ },
12
+ "watchOptions": {
13
+ "watchFile": "fixedPollingInterval",
14
+ "watchDirectory": "fixedPollingInterval",
15
+ "fallbackPolling": "dynamicPriority",
16
+ "synchronousWatchDirectory": true,
17
+ "excludeDirectories": [
18
+ "**/node_modules",
19
+ "dist"
20
+ ]
21
+ }
22
+ }
@@ -2,10 +2,10 @@
2
2
  "compilerOptions": {
3
3
  "module": "commonjs",
4
4
  "esModuleInterop": true,
5
- "target": "es6",
5
+ "target": "es2020",
6
6
  "moduleResolution": "node",
7
- "sourceMap": true,
8
- "pretty": true,
7
+ "sourceMap": false,
8
+ "pretty": false,
9
9
  "noEmitOnError": true,
10
10
  "outDir": "dist"
11
11
  },
File without changes