@postxl/generator 0.16.7 → 0.17.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.
- package/dist/generator.js +2 -0
- package/dist/generators/indices/businesslogicmodule.generator.js +33 -7
- package/dist/generators/indices/datamockmodule.generator.js +6 -13
- package/dist/generators/indices/datamodule.generator.js +24 -11
- package/dist/generators/indices/seed-service.generator.d.ts +9 -0
- package/dist/generators/indices/seed-service.generator.js +162 -0
- package/dist/generators/indices/seed-template-decoder.generator.js +1 -3
- package/dist/lib/meta.d.ts +8 -0
- package/dist/lib/meta.js +5 -3
- package/package.json +2 -2
package/dist/generator.js
CHANGED
|
@@ -67,6 +67,7 @@ const businesslogicmodule_generator_1 = require("./generators/indices/businesslo
|
|
|
67
67
|
const businesslogic_generator_1 = require("./generators/models/businesslogic.generator");
|
|
68
68
|
const seed_template_decoder_generator_1 = require("./generators/indices/seed-template-decoder.generator");
|
|
69
69
|
const seed_template_generator_1 = require("./generators/indices/seed-template.generator");
|
|
70
|
+
const seed_service_generator_1 = require("./generators/indices/seed-service.generator");
|
|
70
71
|
const CONFIG_SCHEMA = zod_1.z
|
|
71
72
|
.object({
|
|
72
73
|
project: zod_1.z.string(),
|
|
@@ -206,6 +207,7 @@ function generate({ models, enums, config, prismaClientPath, logger, }) {
|
|
|
206
207
|
}
|
|
207
208
|
if (!config.disableGenerators.seed) {
|
|
208
209
|
generated.write(`/${meta.seed.indexFilePath}.ts`, (0, seed_generator_1.generateSeedIndex)({ models, meta }));
|
|
210
|
+
generated.write(`/${meta.seed.serviceFilePath}.ts`, (0, seed_service_generator_1.generateSeedService)({ models, meta }));
|
|
209
211
|
generated.write(`/${meta.seed.templateExcelFilePath}`, yield (0, seed_template_generator_1.generateSeedExcelTemplate)({ models }));
|
|
210
212
|
generated.write(`/${meta.seed.templateDecoderFilePath}.ts`, (0, seed_template_decoder_generator_1.generateSeedTemplateDecoder)({ models, meta }));
|
|
211
213
|
}
|
|
@@ -27,19 +27,45 @@ function generateBusinessLogicModule({ models, meta }) {
|
|
|
27
27
|
});
|
|
28
28
|
providers.push(meta.businessLogic.serviceClassName);
|
|
29
29
|
}
|
|
30
|
+
const moduleName = meta.businessLogic.moduleName;
|
|
30
31
|
return `
|
|
31
|
-
import {
|
|
32
|
+
import { DynamicModule } from '@${meta.config.project}/common'
|
|
32
33
|
|
|
33
34
|
${imports.generate()}
|
|
34
35
|
|
|
35
36
|
const providers = [${providers.join(', ')}]
|
|
36
|
-
@Module({
|
|
37
|
-
imports: [${meta.data.moduleName}],
|
|
38
|
-
providers,
|
|
39
|
-
exports: providers,
|
|
40
|
-
})
|
|
41
|
-
export class BusinessLogicModule {}
|
|
42
37
|
|
|
38
|
+
export class ${moduleName} {
|
|
39
|
+
/**
|
|
40
|
+
* Internal cache for the module instance.
|
|
41
|
+
*/
|
|
42
|
+
private static cachedModule: DynamicModule | undefined = undefined
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The getInstance method should be called by any module that needs the module.
|
|
46
|
+
*/
|
|
47
|
+
static getInstance(): DynamicModule {
|
|
48
|
+
if (!${moduleName}.cachedModule) throw new Error('${moduleName} must be called via .provide first!')
|
|
49
|
+
return ${moduleName}.cachedModule
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The forRoot method should only be called once by the root module.
|
|
54
|
+
*/
|
|
55
|
+
static forRoot(): DynamicModule {
|
|
56
|
+
if (${moduleName}.cachedModule) {
|
|
57
|
+
throw new Error('${moduleName} is already instantiated, please call .forRoot only once from root...')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
${moduleName}.cachedModule = {
|
|
61
|
+
module: ${moduleName},
|
|
62
|
+
providers,
|
|
63
|
+
imports: [${meta.data.moduleName}.getInstance()],
|
|
64
|
+
exports: providers,
|
|
65
|
+
}
|
|
66
|
+
return ${moduleName}.cachedModule
|
|
67
|
+
}
|
|
68
|
+
}
|
|
43
69
|
`;
|
|
44
70
|
}
|
|
45
71
|
exports.generateBusinessLogicModule = generateBusinessLogicModule;
|
|
@@ -69,16 +69,7 @@ import { DbModule } from '@${meta.config.project}/db'
|
|
|
69
69
|
${imports.generate()}
|
|
70
70
|
|
|
71
71
|
export class DataMockModule {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
static isInstantiated(): boolean {
|
|
75
|
-
return !!DataMockModule.cachedModule
|
|
76
|
-
}
|
|
77
|
-
static getInstance(): DataModule {
|
|
78
|
-
if (!DataMockModule.cachedModule) throw new Error('DataMockModule must be called via .mock first!')
|
|
79
|
-
return DataMockModule.cachedModule
|
|
80
|
-
}
|
|
81
|
-
|
|
72
|
+
|
|
82
73
|
// eslint-disable-next-line @typescript-eslint/require-await, @typescript-eslint/no-unused-vars
|
|
83
74
|
static async mock(seed?: MockData): Promise<DynamicModule> {
|
|
84
75
|
const providers = [
|
|
@@ -87,15 +78,17 @@ export class DataMockModule {
|
|
|
87
78
|
${providers}
|
|
88
79
|
]
|
|
89
80
|
|
|
90
|
-
|
|
81
|
+
const cachedModule = {
|
|
91
82
|
module: DataModule,
|
|
92
83
|
imports: [DbModule.provideMock()],
|
|
93
84
|
providers: providers,
|
|
94
85
|
exports: providers,
|
|
95
86
|
global: true,
|
|
96
87
|
}
|
|
97
|
-
|
|
98
|
-
|
|
88
|
+
|
|
89
|
+
${meta.data.moduleName}._storeMockModule(cachedModule)
|
|
90
|
+
|
|
91
|
+
return cachedModule
|
|
99
92
|
}
|
|
100
93
|
}
|
|
101
94
|
|
|
@@ -49,7 +49,8 @@ function generateDataModule({ models, meta }) {
|
|
|
49
49
|
}
|
|
50
50
|
const moduleName = meta.data.moduleName;
|
|
51
51
|
return `
|
|
52
|
-
import {
|
|
52
|
+
import { Provider, Type } from '@nestjs/common'
|
|
53
|
+
import { DynamicModule } from '@${meta.config.project}/common'
|
|
53
54
|
import { DbModule, DbService } from '@${meta.config.project}/db'
|
|
54
55
|
|
|
55
56
|
import { AsyncInit, Repository } from './repository.type'
|
|
@@ -70,21 +71,33 @@ const createRepositoryProvider = <T extends AsyncInit>(t: Type<T>, loadData: boo
|
|
|
70
71
|
})
|
|
71
72
|
|
|
72
73
|
export class ${moduleName} {
|
|
74
|
+
/**
|
|
75
|
+
* Internal cache for the module instance.
|
|
76
|
+
*/
|
|
73
77
|
private static cachedModule: DynamicModule | undefined = undefined
|
|
74
78
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
static getInstance(): ${moduleName} {
|
|
79
|
+
/**
|
|
80
|
+
* The getInstance method should be called by any module that needs the module.
|
|
81
|
+
*/
|
|
82
|
+
static getInstance(): DynamicModule {
|
|
80
83
|
if (!${moduleName}.cachedModule) throw new Error('${moduleName} must be called via .provide first!')
|
|
81
84
|
return ${moduleName}.cachedModule
|
|
82
85
|
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Stores another module instance in the cache.
|
|
89
|
+
* Note: This should only be used for testing purposes.
|
|
90
|
+
*/
|
|
91
|
+
static _storeMockModule(module: DynamicModule): void {
|
|
92
|
+
${moduleName}.cachedModule = module
|
|
93
|
+
}
|
|
83
94
|
|
|
84
|
-
|
|
95
|
+
/**
|
|
96
|
+
* The forRoot method should only be called once by the root module.
|
|
97
|
+
*/
|
|
98
|
+
static forRoot({ loadData }: { loadData: boolean }): DynamicModule {
|
|
85
99
|
if (${moduleName}.cachedModule) {
|
|
86
|
-
|
|
87
|
-
return ${moduleName}.cachedModule
|
|
100
|
+
throw new Error('${moduleName} is already instantiated, please call .forRoot only once from root...')
|
|
88
101
|
}
|
|
89
102
|
|
|
90
103
|
const repositoryProviders = [
|
|
@@ -94,7 +107,7 @@ export class ${moduleName} {
|
|
|
94
107
|
${moduleName}.cachedModule = {
|
|
95
108
|
module: ${moduleName},
|
|
96
109
|
global: true,
|
|
97
|
-
imports: [DbModule.
|
|
110
|
+
imports: [DbModule.forRoot()],
|
|
98
111
|
providers: [DataService, ...repositoryProviders],
|
|
99
112
|
exports: [DataService, ...repositoryProviders],
|
|
100
113
|
}
|
|
@@ -116,7 +129,7 @@ export class ${moduleName} {
|
|
|
116
129
|
${moduleName}.cachedModule = {
|
|
117
130
|
module: ${moduleName},
|
|
118
131
|
global: true,
|
|
119
|
-
imports: [DbModule.
|
|
132
|
+
imports: [DbModule.forRoot()],
|
|
120
133
|
providers: [...providers],
|
|
121
134
|
exports: providers,
|
|
122
135
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { SchemaMetaData } from '../../lib/meta';
|
|
2
|
+
import { Model } from '../../lib/schema/schema';
|
|
3
|
+
/**
|
|
4
|
+
* Generates index file for all seed files.
|
|
5
|
+
*/
|
|
6
|
+
export declare function generateSeedService({ models, meta }: {
|
|
7
|
+
models: Model[];
|
|
8
|
+
meta: SchemaMetaData;
|
|
9
|
+
}): string;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateSeedService = void 0;
|
|
4
|
+
const imports_1 = require("../../lib/imports");
|
|
5
|
+
const meta_1 = require("../../lib/meta");
|
|
6
|
+
/**
|
|
7
|
+
* Generates index file for all seed files.
|
|
8
|
+
*/
|
|
9
|
+
function generateSeedService({ models, meta }) {
|
|
10
|
+
const imports = imports_1.ImportsGenerator.from(meta.seed.serviceFilePath).addImport({
|
|
11
|
+
from: meta.seed.templateDecoderFilePath,
|
|
12
|
+
items: [meta.seed.templateDecoderName],
|
|
13
|
+
});
|
|
14
|
+
const dataLoader = [];
|
|
15
|
+
for (const model of models) {
|
|
16
|
+
const modelMeta = (0, meta_1.getModelMetadata)({ model });
|
|
17
|
+
dataLoader.push(`await this.upload({ name: '${modelMeta.userFriendlyName}', data: data.${modelMeta.seed.constantName}, repo: this.dataService.${modelMeta.data.dataServiceName}, log })`);
|
|
18
|
+
}
|
|
19
|
+
return `
|
|
20
|
+
import { Injectable, Logger } from '@nestjs/common'
|
|
21
|
+
import { format, pluralize } from '@pxl/common'
|
|
22
|
+
import { DataService, Repository } from '@pxl/data'
|
|
23
|
+
import { DbService } from '@pxl/db'
|
|
24
|
+
import { XlPortService } from '@pxl/xlport'
|
|
25
|
+
${imports.generate()}
|
|
26
|
+
|
|
27
|
+
import * as SEEDDATA from './data'
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Configuration options for seeding
|
|
31
|
+
*/
|
|
32
|
+
export type SeedServiceOptions = {
|
|
33
|
+
/**
|
|
34
|
+
* If true, the database will be emptied before seeding.
|
|
35
|
+
* This is only possible if the database is set to test database!
|
|
36
|
+
*/
|
|
37
|
+
reset?: boolean
|
|
38
|
+
/**
|
|
39
|
+
* If true, the seed data will be logged to the console.
|
|
40
|
+
* Note: This is significantly slower - but can be useful for debugging.
|
|
41
|
+
*/
|
|
42
|
+
log?: boolean
|
|
43
|
+
/**
|
|
44
|
+
* If true, data will be loaded into the repositories after seeding.
|
|
45
|
+
* This should be used on startup of the application, but typically
|
|
46
|
+
* not when running tests or from the CLI.
|
|
47
|
+
*/
|
|
48
|
+
loadDataAfterSeeding?: boolean
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const DEFAULTOPTIONS: SeedServiceOptions = { reset: false, log: false, loadDataAfterSeeding: false }
|
|
52
|
+
@Injectable()
|
|
53
|
+
export class SeedService {
|
|
54
|
+
private readonly logger = new Logger(SeedService.name)
|
|
55
|
+
constructor(
|
|
56
|
+
private readonly dataService: DataService,
|
|
57
|
+
private readonly xlPortService: XlPortService,
|
|
58
|
+
private dbService: DbService,
|
|
59
|
+
) {}
|
|
60
|
+
/**
|
|
61
|
+
* Seeds the database with the given data. Any data not provided will be taken from the default seed data.
|
|
62
|
+
*/
|
|
63
|
+
public async seed({ data, options = DEFAULTOPTIONS }: { data?: Partial<typeof SEEDDATA>; options?: SeedServiceOptions }) {
|
|
64
|
+
return this._seed({ data: { ...SEEDDATA, ...data }, options })
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Seeds the database with the given data from an Excel file.
|
|
69
|
+
*/
|
|
70
|
+
public async seedFromExcel({
|
|
71
|
+
templatePath = './template.xlsx',
|
|
72
|
+
options = DEFAULTOPTIONS,
|
|
73
|
+
}: {
|
|
74
|
+
templatePath?: string
|
|
75
|
+
options?: SeedServiceOptions
|
|
76
|
+
}) {
|
|
77
|
+
const xlData = this.xlPortService.importFromFile(templatePath)
|
|
78
|
+
|
|
79
|
+
const dataParsed = ${meta.seed.templateDecoderName}.safeParse(xlData)
|
|
80
|
+
if (!dataParsed.success) {
|
|
81
|
+
throw new Error(\`Error in parsing Excel seed data: \${JSON.stringify(dataParsed.error.message)}\`)
|
|
82
|
+
}
|
|
83
|
+
return this._seed({ data: dataParsed.data, options })
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private async _seed({ data, options: { loadDataAfterSeeding, reset, log } }: { data: typeof SEEDDATA; options: SeedServiceOptions }) {
|
|
87
|
+
if (reset) {
|
|
88
|
+
this.logger.log('Resetting database')
|
|
89
|
+
await this.dbService.emptyDatabase()
|
|
90
|
+
} else {
|
|
91
|
+
const config = await this.dbService.config.findFirst({ where: { id: true } })
|
|
92
|
+
if (!config) {
|
|
93
|
+
throw new Error('Database is not initialized')
|
|
94
|
+
}
|
|
95
|
+
if (config.isSeeded) {
|
|
96
|
+
this.logger.log('Database is already seeded')
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
await this.uploadData({ data, log })
|
|
102
|
+
|
|
103
|
+
await this.dbService.config.update({
|
|
104
|
+
where: { id: true },
|
|
105
|
+
data: { isSeeded: true },
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
if (loadDataAfterSeeding) {
|
|
109
|
+
await this.dataService.init()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
this.logger.log('✅ Done')
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private async uploadData({ log = false, data }: { data: typeof SEEDDATA; log?: boolean }) {
|
|
116
|
+
// NOTE: the order of these calls is important, because of foreign key constraints
|
|
117
|
+
// The current order is based on the order of the models in the schema
|
|
118
|
+
// Change the order based on your needs.
|
|
119
|
+
// Attention: Depending on the dependencies in seed data, you may also have to take care of relations,
|
|
120
|
+
// e.g. by removing any foreign key first - and then update these after all data is created.
|
|
121
|
+
${dataLoader.join('\n')}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private async upload<T extends { id: ID }, ID extends number | string | boolean>({
|
|
125
|
+
name,
|
|
126
|
+
data,
|
|
127
|
+
repo,
|
|
128
|
+
log = false,
|
|
129
|
+
onlyOnReset = true,
|
|
130
|
+
}: {
|
|
131
|
+
name: string
|
|
132
|
+
data: T[]
|
|
133
|
+
repo: Repository<T, ID>
|
|
134
|
+
log?: boolean
|
|
135
|
+
onlyOnReset?: boolean
|
|
136
|
+
}): Promise<void> {
|
|
137
|
+
if (repo.count() > 0 && onlyOnReset) {
|
|
138
|
+
this.logger.log(\`\${pluralize(name)} already exist, skipping\`)
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
if (!log) {
|
|
142
|
+
await repo.createMany(data)
|
|
143
|
+
} else {
|
|
144
|
+
let i = 0
|
|
145
|
+
for (const item of data) {
|
|
146
|
+
i++
|
|
147
|
+
console.log(name, i, item.id)
|
|
148
|
+
try {
|
|
149
|
+
await repo.createWithId?.(item)
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.log(item)
|
|
152
|
+
throw error
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
this.logger.log(\`✅ Created \${format(data.length)} \${pluralize(name)}\`)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
`;
|
|
161
|
+
}
|
|
162
|
+
exports.generateSeedService = generateSeedService;
|
|
@@ -28,7 +28,7 @@ function generateSeedTemplateDecoder({ models, meta }) {
|
|
|
28
28
|
|
|
29
29
|
${decoders.join('\n')}
|
|
30
30
|
|
|
31
|
-
export const
|
|
31
|
+
export const ${meta.seed.templateDecoderName} = z
|
|
32
32
|
.object({${tableDecoders.join(',\n')}})
|
|
33
33
|
.transform((item) => ({${renameTransforms.join(',\n')}}))
|
|
34
34
|
`;
|
|
@@ -73,8 +73,6 @@ function generateTableDecoder({ model, meta, imports, }) {
|
|
|
73
73
|
break;
|
|
74
74
|
}
|
|
75
75
|
case 'enum': {
|
|
76
|
-
const refEnumMeta = (0, meta_1.getEnumMetadata)({ enumerator: field.enumerator });
|
|
77
|
-
imports.addImport({ items: [field.enumerator.tsTypeName], from: refEnumMeta.types.importPath });
|
|
78
76
|
fieldDecoders.push(`${fieldMeta.excelColumnName}: z.enum([${field.enumerator.values.map((v) => `'${v}'`).join(', ')}])${field.isRequired ? '' : '.nullable()'}`);
|
|
79
77
|
break;
|
|
80
78
|
}
|
package/dist/lib/meta.d.ts
CHANGED
|
@@ -73,6 +73,10 @@ export type SchemaMetaData = {
|
|
|
73
73
|
* Path to the index file for the seed package.
|
|
74
74
|
*/
|
|
75
75
|
indexFilePath: Types.Path;
|
|
76
|
+
/**
|
|
77
|
+
* Path to the SeedService file.
|
|
78
|
+
*/
|
|
79
|
+
serviceFilePath: Types.Path;
|
|
76
80
|
/**
|
|
77
81
|
* Path to seed Excel template file.
|
|
78
82
|
*/
|
|
@@ -81,6 +85,10 @@ export type SchemaMetaData = {
|
|
|
81
85
|
* Path to template decoder file.
|
|
82
86
|
*/
|
|
83
87
|
templateDecoderFilePath: Types.Path;
|
|
88
|
+
/**
|
|
89
|
+
* The name of the template decoder function.
|
|
90
|
+
*/
|
|
91
|
+
templateDecoderName: Types.Fnction;
|
|
84
92
|
/**
|
|
85
93
|
* Path that may be used in the import statement.
|
|
86
94
|
*/
|
package/dist/lib/meta.js
CHANGED
|
@@ -55,9 +55,11 @@ function getSchemaMetadata({ config }) {
|
|
|
55
55
|
importPath: Types.toPath(`@${config.project}/trpc`),
|
|
56
56
|
},
|
|
57
57
|
seed: {
|
|
58
|
-
indexFilePath: Types.toPath(`${config.paths.seedPath}index`),
|
|
58
|
+
indexFilePath: Types.toPath(`${config.paths.seedPath}data/index`),
|
|
59
|
+
serviceFilePath: Types.toPath(`${config.paths.seedPath}seed.service`),
|
|
59
60
|
templateExcelFilePath: Types.toPath(`${config.paths.seedPath}template.xlsx`),
|
|
60
|
-
templateDecoderFilePath: Types.toPath(`${config.paths.seedPath}seed
|
|
61
|
+
templateDecoderFilePath: Types.toPath(`${config.paths.seedPath}seed.decoder`),
|
|
62
|
+
templateDecoderName: Types.toFunction(`seedTemplateDecoder`),
|
|
61
63
|
importPath: Types.toPath(`@${config.project}/seed`),
|
|
62
64
|
randomSeed: config.randomSeed,
|
|
63
65
|
},
|
|
@@ -108,7 +110,7 @@ function getModelMetadata({ model }) {
|
|
|
108
110
|
dataRepositoryVariableName: Types.toVariableName(`data`),
|
|
109
111
|
},
|
|
110
112
|
seed: {
|
|
111
|
-
filePath: Types.toPath(`${config.paths.seedPath}
|
|
113
|
+
filePath: Types.toPath(`${config.paths.seedPath}data/${uncapitalizedPlural}`),
|
|
112
114
|
constantName: Types.toVariableName(`${uncapitalizedPlural}`),
|
|
113
115
|
importPath: Types.toPath(`@${config.project}/seed`),
|
|
114
116
|
excel: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@postxl/generator",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"main": "./dist/generator.js",
|
|
5
5
|
"typings": "./dist/generator.d.ts",
|
|
6
6
|
"bin": {
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "tsc -p tsconfig.build.json",
|
|
47
47
|
"prepublish": "tsc -p tsconfig.build.json",
|
|
48
|
-
"dev": "tsc -
|
|
48
|
+
"dev": "tsc -p tsconfig.build.json -w",
|
|
49
49
|
"test": "jest",
|
|
50
50
|
"test:watch": "jest --watch",
|
|
51
51
|
"test:types": "tsc --noEmit",
|