@codenotch/codenotch.cli 1.0.36 → 1.0.37
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/README.md +2 -2
- package/dist/commands/generate/app.d.ts +2 -0
- package/dist/commands/generate/app.js +45 -0
- package/dist/commands/generate/app.js.map +1 -0
- package/dist/commands/generate/appmanifest.d.ts +2 -0
- package/dist/commands/generate/appmanifest.js +34 -0
- package/dist/commands/generate/appmanifest.js.map +1 -0
- package/dist/commands/generate/dbcontext.d.ts +2 -0
- package/dist/commands/generate/dbcontext.js +32 -0
- package/dist/commands/generate/dbcontext.js.map +1 -0
- package/dist/commands/generate/i18n.d.ts +2 -0
- package/dist/commands/generate/i18n.js +29 -0
- package/dist/commands/generate/i18n.js.map +1 -0
- package/dist/commands/generate/index.d.ts +0 -1
- package/dist/commands/generate/index.js +14 -33
- package/dist/commands/generate/index.js.map +1 -1
- package/dist/commands/generate/manifest.d.ts +2 -0
- package/dist/commands/generate/manifest.js +29 -0
- package/dist/commands/generate/manifest.js.map +1 -0
- package/dist/commands/generate/openapi.d.ts +2 -0
- package/dist/commands/generate/openapi.js +32 -0
- package/dist/commands/generate/openapi.js.map +1 -0
- package/dist/commands/generate/pkg.d.ts +2 -0
- package/dist/commands/generate/pkg.js +34 -0
- package/dist/commands/generate/pkg.js.map +1 -0
- package/dist/commands/generate/shared.d.ts +17 -0
- package/dist/commands/generate/shared.js +70 -0
- package/dist/commands/generate/shared.js.map +1 -0
- package/dist/commands/project/new.js +108 -96
- package/dist/commands/project/new.js.map +1 -1
- package/dist/commands/react/build.js +11 -5
- package/dist/commands/react/build.js.map +1 -1
- package/dist/commands/react/clean.d.ts +2 -0
- package/dist/commands/react/clean.js +46 -0
- package/dist/commands/react/clean.js.map +1 -0
- package/dist/commands/react/dev.d.ts +2 -0
- package/dist/commands/react/dev.js +27 -0
- package/dist/commands/react/dev.js.map +1 -0
- package/dist/commands/react/index.js +5 -1
- package/dist/commands/react/index.js.map +1 -1
- package/dist/commands/react/serve.js +33 -4
- package/dist/commands/react/serve.js.map +1 -1
- package/dist/commands/react/shared.d.ts +21 -0
- package/dist/commands/react/shared.js +80 -0
- package/dist/commands/react/shared.js.map +1 -0
- package/dist/commands/validation/ValidationUtils.d.ts +1 -1
- package/dist/commands/validation/ValidationUtils.js +3 -0
- package/dist/commands/validation/ValidationUtils.js.map +1 -1
- package/dist/commands/validation/files/I18nValidator.d.ts +29 -0
- package/dist/commands/validation/files/I18nValidator.js +198 -0
- package/dist/commands/validation/files/I18nValidator.js.map +1 -0
- package/dist/commands/validation/files/ProjectManifestValidator.d.ts +5 -0
- package/dist/commands/validation/files/ProjectManifestValidator.js +31 -0
- package/dist/commands/validation/files/ProjectManifestValidator.js.map +1 -0
- package/dist/commands/validation/files/ScriptValidator.js +1 -1
- package/dist/commands/validation/files/ScriptValidator.js.map +1 -1
- package/dist/commands/validation/validate.js +11 -6
- package/dist/commands/validation/validate.js.map +1 -1
- package/dist/utils/BuildUtils.d.ts +61 -7
- package/dist/utils/BuildUtils.js +289 -222
- package/dist/utils/BuildUtils.js.map +1 -1
- package/dist/utils/ConsoleUtils.d.ts +8 -0
- package/dist/utils/ConsoleUtils.js +25 -0
- package/dist/utils/ConsoleUtils.js.map +1 -1
- package/dist/utils/DbSchemaUtils.d.ts +49 -0
- package/dist/utils/DbSchemaUtils.js +246 -0
- package/dist/utils/DbSchemaUtils.js.map +1 -0
- package/dist/utils/FileUtils.d.ts +104 -15
- package/dist/utils/FileUtils.js +285 -100
- package/dist/utils/FileUtils.js.map +1 -1
- package/dist/utils/I18nTsUtils.d.ts +66 -0
- package/dist/utils/I18nTsUtils.js +221 -0
- package/dist/utils/I18nTsUtils.js.map +1 -0
- package/dist/utils/OpenApiUtils.d.ts +81 -0
- package/dist/utils/OpenApiUtils.js +123 -0
- package/dist/utils/OpenApiUtils.js.map +1 -0
- package/dist/utils/ProjectCompilationUtils.d.ts +0 -1
- package/dist/utils/ProjectCompilationUtils.js +4 -52
- package/dist/utils/ProjectCompilationUtils.js.map +1 -1
- package/dist/utils/ProjectGeneratorUtils.d.ts +35 -0
- package/dist/utils/ProjectGeneratorUtils.js +119 -0
- package/dist/utils/ProjectGeneratorUtils.js.map +1 -0
- package/dist/utils/ProjectTemplates.d.ts +102 -0
- package/dist/utils/ProjectTemplates.js +284 -0
- package/dist/utils/ProjectTemplates.js.map +1 -0
- package/dist/utils/ProjectUtils.d.ts +216 -3
- package/dist/utils/ProjectUtils.js +387 -22
- package/dist/utils/ProjectUtils.js.map +1 -1
- package/dist/utils/RuntimeUtils.js +14 -8
- package/dist/utils/RuntimeUtils.js.map +1 -1
- package/dist/utils/ScriptDeclarationUtils.d.ts +169 -0
- package/dist/utils/ScriptDeclarationUtils.js +398 -0
- package/dist/utils/ScriptDeclarationUtils.js.map +1 -0
- package/dist/utils/ServeUtils.js +10 -5
- package/dist/utils/ServeUtils.js.map +1 -1
- package/dist/utils/ShellUtils.d.ts +6 -0
- package/dist/utils/ShellUtils.js +13 -0
- package/dist/utils/ShellUtils.js.map +1 -1
- package/dist/utils/TemplateUtils.d.ts +62 -0
- package/dist/utils/TemplateUtils.js +218 -0
- package/dist/utils/TemplateUtils.js.map +1 -0
- package/dist/utils/TypeSchemaUtils.d.ts +42 -0
- package/dist/utils/TypeSchemaUtils.js +171 -0
- package/dist/utils/TypeSchemaUtils.js.map +1 -0
- package/package.json +3 -2
|
@@ -1,5 +1,140 @@
|
|
|
1
1
|
import { CompilationResult } from './ProjectCompilationUtils';
|
|
2
|
-
import {
|
|
2
|
+
import { LiteralValue } from './ScriptDeclarationUtils';
|
|
3
|
+
import { IJsonSchema } from './TypeSchemaUtils';
|
|
4
|
+
import { IAppManifest, ICodenotchProjectManifest } from '@codenotch/codenotch.core';
|
|
5
|
+
/** A *.i18n.csv file found in a project */
|
|
6
|
+
export interface IDiscoveredI18n {
|
|
7
|
+
/** Absolute path of the file */
|
|
8
|
+
filePath: string;
|
|
9
|
+
/** Translation keys it declares, in file order. Empty when its header has no 'key' column */
|
|
10
|
+
keys: string[];
|
|
11
|
+
/** Languages of its columns, e.g. ['en', 'de'] */
|
|
12
|
+
languages: string[];
|
|
13
|
+
}
|
|
14
|
+
/** A call applied to the type of a column, e.g. `.default('')` or `.primary()` */
|
|
15
|
+
export interface IColumnModifier {
|
|
16
|
+
name: string;
|
|
17
|
+
arguments: LiteralValue[];
|
|
18
|
+
}
|
|
19
|
+
/** A column of a table, read from `fullName: p.string()` */
|
|
20
|
+
export interface ITableColumn {
|
|
21
|
+
/** Name of the column, e.g. 'fullName' */
|
|
22
|
+
name: string;
|
|
23
|
+
/** Type it is built with, e.g. 'string', 'integer', 'text' */
|
|
24
|
+
type?: string;
|
|
25
|
+
/** Arguments given to the type itself, e.g. the 255 of p.string(255) */
|
|
26
|
+
typeArguments: LiteralValue[];
|
|
27
|
+
/** True when '.primary()' is applied */
|
|
28
|
+
isPrimary: boolean;
|
|
29
|
+
/** Value given to '.default(...)' when it is a literal */
|
|
30
|
+
defaultValue?: LiteralValue;
|
|
31
|
+
/** Every call applied after the type, in source order */
|
|
32
|
+
modifiers: IColumnModifier[];
|
|
33
|
+
}
|
|
34
|
+
/** A `defineEntity({ ... })` declaration found in a project */
|
|
35
|
+
export interface IDiscoveredTable {
|
|
36
|
+
/** Absolute path of the script declaring it */
|
|
37
|
+
filePath: string;
|
|
38
|
+
/** Value of its 'name' property, e.g. 'User' */
|
|
39
|
+
name?: string;
|
|
40
|
+
/** False when the table has a name that is built at runtime, so it cannot be known from the sources */
|
|
41
|
+
isNameStatic: boolean;
|
|
42
|
+
/** Name of the variable it is assigned to, e.g. 'User' */
|
|
43
|
+
variableName?: string;
|
|
44
|
+
/** True when that variable is exported */
|
|
45
|
+
isExported: boolean;
|
|
46
|
+
/** 1-based line of the declaration, to point at it in a diagnostic */
|
|
47
|
+
line: number;
|
|
48
|
+
/** Columns of the table, in source order */
|
|
49
|
+
columns: ITableColumn[];
|
|
50
|
+
/** Name of its exported row type, e.g. 'IUser' from `export type IUser = InferEntity<typeof User>` */
|
|
51
|
+
typeName?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Line to paste to use that type, with a path relative to the root of the project, e.g.
|
|
54
|
+
* "import { IUser } from './tables/user';". Use {@link ProjectUtils.getTableTypeImport}
|
|
55
|
+
* to get it relative to another file.
|
|
56
|
+
*/
|
|
57
|
+
importStatement?: string;
|
|
58
|
+
/** Every property written literally, for whatever is not covered by the fields above */
|
|
59
|
+
properties: Record<string, LiteralValue>;
|
|
60
|
+
}
|
|
61
|
+
/** A field of a document. Same shape as a table column, since both are declared the same way */
|
|
62
|
+
export type IDocumentField = ITableColumn;
|
|
63
|
+
/** A `defineDocument({ ... })` declaration found in a project */
|
|
64
|
+
export interface IDiscoveredDocument extends Omit<IDiscoveredTable, 'columns'> {
|
|
65
|
+
/** Fields of the document, in source order */
|
|
66
|
+
fields: IDocumentField[];
|
|
67
|
+
}
|
|
68
|
+
/** A React app found in a project: a tsx/jsx script and its sibling manifest */
|
|
69
|
+
export interface IDiscoveredApp {
|
|
70
|
+
/** Absolute path of the '<App>.manifest.json' */
|
|
71
|
+
manifestPath: string;
|
|
72
|
+
/** Absolute path of the '<App>.tsx' or '<App>.jsx' */
|
|
73
|
+
scriptPath: string;
|
|
74
|
+
/** Syntax of the script */
|
|
75
|
+
type: 'jsx' | 'tsx';
|
|
76
|
+
/** Name shared by both files, e.g. 'Dashboard' for 'Dashboard.tsx' */
|
|
77
|
+
name: string;
|
|
78
|
+
/** Content of the manifest, empty when it cannot be parsed */
|
|
79
|
+
manifest: IAppManifest;
|
|
80
|
+
}
|
|
81
|
+
/** How a process is started, read from its `trigger` property */
|
|
82
|
+
export type IProcessTrigger =
|
|
83
|
+
/** trigger: api.post('/books') */
|
|
84
|
+
{
|
|
85
|
+
kind: 'api';
|
|
86
|
+
method: string;
|
|
87
|
+
route: string;
|
|
88
|
+
}
|
|
89
|
+
/** trigger: timer('0 8 * * *') */
|
|
90
|
+
| {
|
|
91
|
+
kind: 'timer';
|
|
92
|
+
cron: string;
|
|
93
|
+
}
|
|
94
|
+
/** trigger: signal('book.returned') */
|
|
95
|
+
| {
|
|
96
|
+
kind: 'signal';
|
|
97
|
+
ref: string;
|
|
98
|
+
}
|
|
99
|
+
/** trigger: install({ once: true }) */
|
|
100
|
+
| {
|
|
101
|
+
kind: 'install';
|
|
102
|
+
options: Record<string, LiteralValue>;
|
|
103
|
+
}
|
|
104
|
+
/** A trigger that is not one of the known factories, or that is built at runtime */
|
|
105
|
+
| {
|
|
106
|
+
kind: 'unknown';
|
|
107
|
+
callee: string;
|
|
108
|
+
};
|
|
109
|
+
/** A `process({ ... })` declaration found in a project */
|
|
110
|
+
export interface IDiscoveredProcess {
|
|
111
|
+
/** Absolute path of the script declaring it */
|
|
112
|
+
filePath: string;
|
|
113
|
+
/** Value of its 'id', undefined when absent or computed (see isIdStatic) */
|
|
114
|
+
id?: string;
|
|
115
|
+
/** False when the process has an id that is built at runtime, so it cannot be known from the sources */
|
|
116
|
+
isIdStatic: boolean;
|
|
117
|
+
/** Name of the variable it is assigned to, e.g. 'createBook' */
|
|
118
|
+
variableName?: string;
|
|
119
|
+
/** True when that variable is exported, which a process needs to be reachable */
|
|
120
|
+
isExported: boolean;
|
|
121
|
+
/** 1-based line of the declaration, to point at it in a diagnostic */
|
|
122
|
+
line: number;
|
|
123
|
+
/** Value of 'auth.scope', e.g. 'internal' or 'system' */
|
|
124
|
+
scope?: string;
|
|
125
|
+
/** How it is started, undefined when it has no trigger and is only reachable through ctx.call */
|
|
126
|
+
trigger?: IProcessTrigger;
|
|
127
|
+
/** True when 'run' declares a second parameter, whether or not it is typed */
|
|
128
|
+
takesInput: boolean;
|
|
129
|
+
/** Schema of the second parameter of 'run' (its input), when it carries a type annotation */
|
|
130
|
+
input?: IJsonSchema;
|
|
131
|
+
/** Schema of the declared return type of 'run', `Promise<T>` unwrapped, when it carries one */
|
|
132
|
+
output?: IJsonSchema;
|
|
133
|
+
/** Literal 'description' of the declaration, when any */
|
|
134
|
+
description?: string;
|
|
135
|
+
/** Every property written literally, for whatever is not covered by the fields above */
|
|
136
|
+
properties: Record<string, LiteralValue>;
|
|
137
|
+
}
|
|
3
138
|
export default abstract class ProjectUtils {
|
|
4
139
|
/**
|
|
5
140
|
* Compile and package the project to prepare it for deployment
|
|
@@ -7,9 +142,87 @@ export default abstract class ProjectUtils {
|
|
|
7
142
|
* Output a zip file that can be deployed to Codenotch
|
|
8
143
|
*/
|
|
9
144
|
static packageProject(sourcePath: string, outputPath: string, ignoreErrors?: boolean): Promise<CompilationResult>;
|
|
10
|
-
static isManifestCompiled(projectPath: string): Promise<boolean>;
|
|
11
145
|
private static getCollectionCount;
|
|
12
146
|
static getManifest(projectPath: string): Promise<ICodenotchProjectManifest>;
|
|
13
147
|
static getProjectServiceName(projectPath: string): Promise<string>;
|
|
14
|
-
static inspectProject(projectPath: string): Promise<
|
|
148
|
+
static inspectProject(projectPath: string): Promise<{
|
|
149
|
+
manifest: ICodenotchProjectManifest | undefined;
|
|
150
|
+
processes: IDiscoveredProcess[];
|
|
151
|
+
tables: IDiscoveredTable[];
|
|
152
|
+
documents: IDiscoveredDocument[];
|
|
153
|
+
apps: IDiscoveredApp[];
|
|
154
|
+
i18ns: IDiscoveredI18n[];
|
|
155
|
+
pkgJson: {
|
|
156
|
+
name?: string;
|
|
157
|
+
version?: string;
|
|
158
|
+
} | undefined;
|
|
159
|
+
}>;
|
|
160
|
+
/** Short human readable form of a process trigger, e.g. 'POST /books' or 'cron 0 8 * * *' */
|
|
161
|
+
private static describeTrigger;
|
|
162
|
+
/**
|
|
163
|
+
* Find all file (recursivly) in a given folder (workspacePath) that contains a table declaration ('defineEntity').
|
|
164
|
+
*/
|
|
165
|
+
static discoverTableFiles(workspacePath: string): Promise<IDiscoveredTable[]>;
|
|
166
|
+
/**
|
|
167
|
+
* Shared by the tables and the documents, which only differ by the factory they are declared with
|
|
168
|
+
*
|
|
169
|
+
* @param factoryName 'defineEntity' or 'defineDocument'
|
|
170
|
+
* @param typeHelperNames helpers building their row type, e.g. ['InferEntity']
|
|
171
|
+
*/
|
|
172
|
+
private static discoverOrmDeclarations;
|
|
173
|
+
/**
|
|
174
|
+
* Line to paste to use the type of a table from another file, e.g.
|
|
175
|
+
* "import { IUser } from '../tables/user';"
|
|
176
|
+
*
|
|
177
|
+
* @param table table to import the type of
|
|
178
|
+
* @param fromFilePath file the import would be written in
|
|
179
|
+
*/
|
|
180
|
+
static getTableTypeImport(table: IDiscoveredTable, fromFilePath: string): string | undefined;
|
|
181
|
+
private static buildTypeImport;
|
|
182
|
+
/**
|
|
183
|
+
* Turns the `properties` of a table into columns: `bio: p.text().default('')` gives the
|
|
184
|
+
* type 'text' and the modifier 'default' with its value.
|
|
185
|
+
*/
|
|
186
|
+
private static readColumns;
|
|
187
|
+
/**
|
|
188
|
+
* Find all file (recursivly) in a given folder (workspacePath) that contains a document declaration ('defineDocument').
|
|
189
|
+
*/
|
|
190
|
+
static discoverDocumentFiles(workspacePath: string): Promise<IDiscoveredDocument[]>;
|
|
191
|
+
/**
|
|
192
|
+
* Line to paste to use the type of a document from another file, e.g.
|
|
193
|
+
* "import { ISettings } from '../documents/settings';"
|
|
194
|
+
*
|
|
195
|
+
* @param document document to import the type of
|
|
196
|
+
* @param fromFilePath file the import would be written in
|
|
197
|
+
*/
|
|
198
|
+
static getDocumentTypeImport(document: IDiscoveredDocument, fromFilePath: string): string | undefined;
|
|
199
|
+
/**
|
|
200
|
+
* Find all file (recursivly) in a given folder (workspacePath) that contains a table declaration ('process').
|
|
201
|
+
*/
|
|
202
|
+
static discoverProcessFiles(workspacePath: string): Promise<IDiscoveredProcess[]>;
|
|
203
|
+
/** Value of `auth: { scope: 'internal' }` when it is written literally */
|
|
204
|
+
private static readScope;
|
|
205
|
+
/**
|
|
206
|
+
* Turns the `trigger` property into something usable, e.g. `api.post('/books')` into
|
|
207
|
+
* { kind: 'api', method: 'POST', route: '/books' }.
|
|
208
|
+
* A process without trigger is only reachable through ctx.call, and returns undefined.
|
|
209
|
+
*/
|
|
210
|
+
private static readTrigger;
|
|
211
|
+
private static getAllScriptFile;
|
|
212
|
+
/**
|
|
213
|
+
* An app is a tsx/jsx file with a sibling *.manifest.json.
|
|
214
|
+
*
|
|
215
|
+
* A script without its manifest is not an app and is left out, whereas an app whose manifest
|
|
216
|
+
* cannot be parsed is still reported, with an empty manifest: it exists on disk, and saying
|
|
217
|
+
* why its manifest is wrong is the job of the validators.
|
|
218
|
+
*/
|
|
219
|
+
static discoverAppsFiles(workspacePath: string): Promise<IDiscoveredApp[]>;
|
|
220
|
+
/**
|
|
221
|
+
* Find all *.i18n.csv files (recursively) in a given folder (workspacePath), with the keys
|
|
222
|
+
* they declare and the languages they translate them into.
|
|
223
|
+
*
|
|
224
|
+
* A file whose first column is not 'key' is reported with no key and no language rather than
|
|
225
|
+
* being read wrong: saying why it is malformed is the job of the validators.
|
|
226
|
+
*/
|
|
227
|
+
static discoverI18nsFiles(workspacePath: string): Promise<IDiscoveredI18n[]>;
|
|
15
228
|
}
|
|
@@ -9,6 +9,20 @@ const node_os_1 = __importDefault(require("node:os"));
|
|
|
9
9
|
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
10
10
|
const FileUtils_1 = __importDefault(require("./FileUtils"));
|
|
11
11
|
const ProjectCompilationUtils_1 = __importDefault(require("./ProjectCompilationUtils"));
|
|
12
|
+
const ScriptDeclarationUtils_1 = __importDefault(require("./ScriptDeclarationUtils"));
|
|
13
|
+
const I18nTsUtils_1 = __importDefault(require("./I18nTsUtils"));
|
|
14
|
+
const papaparse_1 = __importDefault(require("papaparse"));
|
|
15
|
+
/** Module the process factories are imported from */
|
|
16
|
+
const PROCESS_MODULE = '@codenotch/process';
|
|
17
|
+
/** Module the table factories are imported from */
|
|
18
|
+
const ORM_MODULE = '@codenotch/orm';
|
|
19
|
+
/** Property of a table declaration holding its columns */
|
|
20
|
+
const TABLE_COLUMNS_PROPERTY = 'properties';
|
|
21
|
+
/**
|
|
22
|
+
* Helpers a document row type can be built with. 'InferDocument' is the counterpart of
|
|
23
|
+
* 'InferEntity', and the latter is accepted too in case both kinds share the same helper.
|
|
24
|
+
*/
|
|
25
|
+
const DOCUMENT_TYPE_HELPERS = ['InferDocument', 'InferEntity'];
|
|
12
26
|
class ProjectUtils {
|
|
13
27
|
/**
|
|
14
28
|
* Compile and package the project to prepare it for deployment
|
|
@@ -32,7 +46,7 @@ class ProjectUtils {
|
|
|
32
46
|
console.warn(chalk_1.default.yellow("Ignoring compilation errors and proceeding with packaging..."));
|
|
33
47
|
}
|
|
34
48
|
// Zip the project folder
|
|
35
|
-
|
|
49
|
+
FileUtils_1.default.zipFolder(tempRoot, outputPath);
|
|
36
50
|
return compilation;
|
|
37
51
|
}
|
|
38
52
|
catch (e) {
|
|
@@ -40,14 +54,14 @@ class ProjectUtils {
|
|
|
40
54
|
}
|
|
41
55
|
finally {
|
|
42
56
|
// Clean up temp folder
|
|
43
|
-
await FileUtils_1.default.
|
|
57
|
+
await FileUtils_1.default.deleteRecursively(tempRoot);
|
|
44
58
|
}
|
|
45
59
|
}
|
|
46
60
|
// Check if the manifest is full (meaning with process, apps, etc...) or if it's a minimal manifest that only contains metadata, this can be used to determine if the project has been compiled or not
|
|
47
|
-
static async isManifestCompiled(projectPath) {
|
|
48
|
-
const manifest = await ProjectUtils.getManifest(projectPath);
|
|
61
|
+
/*static async isManifestCompiled(projectPath: string): Promise<boolean> {
|
|
62
|
+
const manifest = await ProjectUtils.getManifest(projectPath) as IExtendedScenarioProjectManifest;
|
|
49
63
|
return !!(manifest.processes);
|
|
50
|
-
}
|
|
64
|
+
}*/
|
|
51
65
|
static getCollectionCount(value) {
|
|
52
66
|
return Array.isArray(value) ? value.length : 0;
|
|
53
67
|
}
|
|
@@ -62,24 +76,375 @@ class ProjectUtils {
|
|
|
62
76
|
return manifest.serviceName ?? manifest.projectName.toLowerCase();
|
|
63
77
|
}
|
|
64
78
|
static async inspectProject(projectPath) {
|
|
65
|
-
//TODO will only work on a compiler project since we rely on the full manifest
|
|
66
|
-
const manifest = await ProjectUtils.getManifest(projectPath);
|
|
67
|
-
const projectName = manifest.projectName || "(unknown)";
|
|
68
|
-
const serviceName = manifest.serviceName || projectName.toLowerCase();
|
|
69
|
-
const version = manifest.version || "(unknown)";
|
|
70
|
-
const index = manifest.index || "(not set)";
|
|
71
|
-
const languages = ProjectUtils.getCollectionCount(manifest.languages);
|
|
72
|
-
const roles = ProjectUtils.getCollectionCount(manifest.roles);
|
|
73
79
|
console.log(chalk_1.default.blue("Project inspection"));
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
let manifest;
|
|
81
|
+
try {
|
|
82
|
+
manifest = await ProjectUtils.getManifest(projectPath);
|
|
83
|
+
const projectName = manifest.projectName || "(unknown)";
|
|
84
|
+
const serviceName = manifest.serviceName || projectName.toLowerCase();
|
|
85
|
+
const version = manifest.version || "(unknown)";
|
|
86
|
+
const index = manifest.index || "(not set)";
|
|
87
|
+
const languages = ProjectUtils.getCollectionCount(manifest.languages);
|
|
88
|
+
const roles = ProjectUtils.getCollectionCount(manifest.roles);
|
|
89
|
+
console.log(`${chalk_1.default.gray("Path:")} ${node_path_1.default.resolve(projectPath)}`);
|
|
90
|
+
console.log(`${chalk_1.default.gray("Project:")} ${chalk_1.default.cyan(projectName)}`);
|
|
91
|
+
console.log(`${chalk_1.default.gray("Service:")} ${chalk_1.default.cyan(serviceName)}`);
|
|
92
|
+
console.log(`${chalk_1.default.gray("Version:")} ${chalk_1.default.cyan(version)}`);
|
|
93
|
+
console.log(`${chalk_1.default.gray("Index:")} ${chalk_1.default.cyan(index)}`);
|
|
94
|
+
console.log("");
|
|
95
|
+
console.log(chalk_1.default.blue("Manifest metadata"));
|
|
96
|
+
console.log(`- languages: ${chalk_1.default.yellow(languages.toString())}`);
|
|
97
|
+
console.log(`- roles: ${chalk_1.default.yellow(roles.toString())}`);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
console.log(`- manifest.json not found.`);
|
|
101
|
+
console.log(`${chalk_1.default.gray("Path:")} unknown`);
|
|
102
|
+
console.log(`${chalk_1.default.gray("Project:")} unknown`);
|
|
103
|
+
console.log(`${chalk_1.default.gray("Service:")} unknown`);
|
|
104
|
+
console.log(`${chalk_1.default.gray("Version:")} unknown`);
|
|
105
|
+
console.log(`${chalk_1.default.gray("Index:")} unknown`);
|
|
106
|
+
console.log("");
|
|
107
|
+
console.log(chalk_1.default.blue("Manifest metadata"));
|
|
108
|
+
console.log(`- languages: unknown`);
|
|
109
|
+
console.log(`- roles: unknown`);
|
|
110
|
+
}
|
|
111
|
+
// Find ./package.json
|
|
112
|
+
const pkgJson = await FileUtils_1.default.readJsonFileAsync(node_path_1.default.join(projectPath, 'package.json'));
|
|
113
|
+
if (pkgJson) {
|
|
114
|
+
console.log(`- package.json found:`);
|
|
115
|
+
console.log(`\tname: ${chalk_1.default.cyan(pkgJson.name ?? "(not set)")}`);
|
|
116
|
+
console.log(`\tversion: ${chalk_1.default.cyan(pkgJson.version ?? "(not set)")}`);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
console.log(`- package.json not found.`);
|
|
120
|
+
}
|
|
121
|
+
// Paths are printed relative to the project, absolute ones would push the useful part off screen
|
|
122
|
+
const relativeToProject = (filePath) => node_path_1.default.relative(node_path_1.default.resolve(projectPath), filePath) || filePath;
|
|
123
|
+
// Find processes
|
|
124
|
+
const processes = await ProjectUtils.discoverProcessFiles(projectPath);
|
|
125
|
+
if (processes.length === 0) {
|
|
126
|
+
console.log(`- processes: No process discovered.`);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
console.log(`- processes: ${processes.length}`);
|
|
130
|
+
for (const p of processes) {
|
|
131
|
+
const name = p.id ?? chalk_1.default.yellow("(computed id)");
|
|
132
|
+
const trigger = ProjectUtils.describeTrigger(p.trigger);
|
|
133
|
+
console.log(`\t${chalk_1.default.cyan(name)} ${chalk_1.default.gray(trigger)} at ${relativeToProject(p.filePath)}:${p.line}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Find tables
|
|
137
|
+
const tables = await ProjectUtils.discoverTableFiles(projectPath);
|
|
138
|
+
if (tables.length === 0) {
|
|
139
|
+
console.log(`- tables: No table discovered.`);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
console.log(`- tables: ${tables.length}`);
|
|
143
|
+
for (const t of tables) {
|
|
144
|
+
const name = t.name ?? chalk_1.default.yellow("(computed name)");
|
|
145
|
+
const columns = `${t.columns.length} column${t.columns.length === 1 ? '' : 's'}`;
|
|
146
|
+
console.log(`\t${chalk_1.default.cyan(name)} ${chalk_1.default.gray(columns)} at ${relativeToProject(t.filePath)}:${t.line}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Find documents
|
|
150
|
+
const documents = await ProjectUtils.discoverDocumentFiles(projectPath);
|
|
151
|
+
if (documents.length === 0) {
|
|
152
|
+
console.log(`- documents: No table discovered.`);
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
console.log(`- documents: ${documents.length}`);
|
|
156
|
+
for (const d of documents) {
|
|
157
|
+
const name = d.name ?? chalk_1.default.yellow("(computed name)");
|
|
158
|
+
const columns = `${d.fields.length} column${d.fields.length === 1 ? '' : 's'}`;
|
|
159
|
+
console.log(`\t${chalk_1.default.cyan(name)} ${chalk_1.default.gray(columns)} at ${relativeToProject(d.filePath)}:${d.line}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// Find apps
|
|
163
|
+
const apps = await ProjectUtils.discoverAppsFiles(projectPath);
|
|
164
|
+
if (apps.length === 0) {
|
|
165
|
+
console.log(`- apps: No app discovered.`);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
console.log(`- apps: ${apps.length}`);
|
|
169
|
+
for (const a of apps) {
|
|
170
|
+
const title = a.manifest.app?.name ?? a.manifest.html?.title ?? "(untitled)";
|
|
171
|
+
console.log(`\t${chalk_1.default.cyan(a.name)} ${chalk_1.default.gray(`${a.type}, ${title}`)} at ${relativeToProject(a.scriptPath)}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Find i18ns
|
|
175
|
+
const i18ns = await ProjectUtils.discoverI18nsFiles(projectPath);
|
|
176
|
+
if (i18ns.length === 0) {
|
|
177
|
+
console.log(`- i18ns: No i18ns file discovered.`);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
console.log(`- i18ns: ${i18ns.length}`);
|
|
181
|
+
for (const a of i18ns) {
|
|
182
|
+
console.log(`\t${chalk_1.default.cyan(a.keys.length)} keys (${chalk_1.default.gray(`${a.languages.length}`)} langs) at ${relativeToProject(a.filePath)}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// Could be usefull so returns all
|
|
186
|
+
return {
|
|
187
|
+
manifest,
|
|
188
|
+
processes,
|
|
189
|
+
tables,
|
|
190
|
+
documents,
|
|
191
|
+
apps,
|
|
192
|
+
i18ns,
|
|
193
|
+
pkgJson
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
/** Short human readable form of a process trigger, e.g. 'POST /books' or 'cron 0 8 * * *' */
|
|
197
|
+
static describeTrigger(trigger) {
|
|
198
|
+
switch (trigger?.kind) {
|
|
199
|
+
case 'api': return `${trigger.method} ${trigger.route}`;
|
|
200
|
+
case 'timer': return `cron ${trigger.cron}`;
|
|
201
|
+
case 'signal': return `signal ${trigger.ref}`;
|
|
202
|
+
case 'install': return 'on install';
|
|
203
|
+
case 'unknown': return `trigger ${trigger.callee}`;
|
|
204
|
+
default: return 'no trigger';
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Find all file (recursivly) in a given folder (workspacePath) that contains a table declaration ('defineEntity').
|
|
209
|
+
*/
|
|
210
|
+
static async discoverTableFiles(workspacePath) {
|
|
211
|
+
return await this.discoverOrmDeclarations(workspacePath, 'defineEntity', ['InferEntity']);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Shared by the tables and the documents, which only differ by the factory they are declared with
|
|
215
|
+
*
|
|
216
|
+
* @param factoryName 'defineEntity' or 'defineDocument'
|
|
217
|
+
* @param typeHelperNames helpers building their row type, e.g. ['InferEntity']
|
|
218
|
+
*/
|
|
219
|
+
static async discoverOrmDeclarations(workspacePath, factoryName, typeHelperNames) {
|
|
220
|
+
const files = await this.getAllScriptFile(workspacePath);
|
|
221
|
+
const results = [];
|
|
222
|
+
for (const filePath of files) {
|
|
223
|
+
const content = await FileUtils_1.default.readFileAsync(filePath);
|
|
224
|
+
// Cheap way out: a file that does not even name the module declares nothing
|
|
225
|
+
if (!content || !content.includes(ORM_MODULE)) {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const analysis = ScriptDeclarationUtils_1.default.analyzeScript(filePath, content, ORM_MODULE, [factoryName], typeHelperNames);
|
|
229
|
+
for (const declaration of analysis.declarations) {
|
|
230
|
+
// 'export type IUser = InferEntity<typeof User>' next to 'export const User = defineEntity(...)'
|
|
231
|
+
const typeAlias = analysis.typeAliases.find(alias => alias.targetName === declaration.variableName && alias.isExported);
|
|
232
|
+
results.push({
|
|
233
|
+
filePath,
|
|
234
|
+
name: typeof declaration.properties['name'] === 'string' ? declaration.properties['name'] : undefined,
|
|
235
|
+
isNameStatic: typeof declaration.properties['name'] === 'string'
|
|
236
|
+
|| declaration.properties['name'] === undefined,
|
|
237
|
+
variableName: declaration.variableName,
|
|
238
|
+
isExported: declaration.isExported,
|
|
239
|
+
line: declaration.line,
|
|
240
|
+
columns: this.readColumns(declaration),
|
|
241
|
+
typeName: typeAlias?.name,
|
|
242
|
+
importStatement: typeAlias === undefined
|
|
243
|
+
? undefined
|
|
244
|
+
: this.buildTypeImport(typeAlias.name, filePath, workspacePath),
|
|
245
|
+
properties: declaration.properties
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return results;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Line to paste to use the type of a table from another file, e.g.
|
|
253
|
+
* "import { IUser } from '../tables/user';"
|
|
254
|
+
*
|
|
255
|
+
* @param table table to import the type of
|
|
256
|
+
* @param fromFilePath file the import would be written in
|
|
257
|
+
*/
|
|
258
|
+
static getTableTypeImport(table, fromFilePath) {
|
|
259
|
+
if (table.typeName === undefined) {
|
|
260
|
+
return undefined;
|
|
261
|
+
}
|
|
262
|
+
return this.buildTypeImport(table.typeName, table.filePath, node_path_1.default.dirname(fromFilePath));
|
|
263
|
+
}
|
|
264
|
+
static buildTypeImport(typeName, targetFilePath, fromFolderPath) {
|
|
265
|
+
// Module specifiers are posix and carry no extension
|
|
266
|
+
let specifier = node_path_1.default.relative(fromFolderPath, targetFilePath).replaceAll('\\', '/').replace(/\.[^./]+$/, '');
|
|
267
|
+
if (!specifier.startsWith('.')) {
|
|
268
|
+
specifier = `./${specifier}`;
|
|
269
|
+
}
|
|
270
|
+
return `import { ${typeName} } from '${specifier}';`;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Turns the `properties` of a table into columns: `bio: p.text().default('')` gives the
|
|
274
|
+
* type 'text' and the modifier 'default' with its value.
|
|
275
|
+
*/
|
|
276
|
+
static readColumns(declaration) {
|
|
277
|
+
const columns = [];
|
|
278
|
+
for (const [path, call] of Object.entries(declaration.calls)) {
|
|
279
|
+
// Only the calls of the 'properties' object are columns
|
|
280
|
+
if (!path.startsWith(`${TABLE_COLUMNS_PROPERTY}.`)) {
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
const name = path.substring(TABLE_COLUMNS_PROPERTY.length + 1);
|
|
284
|
+
// p.integer().primary() -> the first step is the type, the next ones are modifiers
|
|
285
|
+
const [type, ...modifiers] = call.steps;
|
|
286
|
+
const defaultModifier = modifiers.find(modifier => modifier.name === 'default');
|
|
287
|
+
columns.push({
|
|
288
|
+
name,
|
|
289
|
+
type: type?.name,
|
|
290
|
+
typeArguments: type?.arguments ?? [],
|
|
291
|
+
isPrimary: modifiers.some(modifier => modifier.name === 'primary'),
|
|
292
|
+
defaultValue: defaultModifier?.arguments[0],
|
|
293
|
+
modifiers: modifiers.map(modifier => ({ name: modifier.name, arguments: modifier.arguments }))
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
return columns;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Find all file (recursivly) in a given folder (workspacePath) that contains a document declaration ('defineDocument').
|
|
300
|
+
*/
|
|
301
|
+
static async discoverDocumentFiles(workspacePath) {
|
|
302
|
+
const declarations = await this.discoverOrmDeclarations(workspacePath, 'defineDocument', DOCUMENT_TYPE_HELPERS);
|
|
303
|
+
// A document holds fields rather than columns, everything else is read the same way
|
|
304
|
+
return declarations.map(({ columns, ...declaration }) => ({ ...declaration, fields: columns }));
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Line to paste to use the type of a document from another file, e.g.
|
|
308
|
+
* "import { ISettings } from '../documents/settings';"
|
|
309
|
+
*
|
|
310
|
+
* @param document document to import the type of
|
|
311
|
+
* @param fromFilePath file the import would be written in
|
|
312
|
+
*/
|
|
313
|
+
static getDocumentTypeImport(document, fromFilePath) {
|
|
314
|
+
if (document.typeName === undefined) {
|
|
315
|
+
return undefined;
|
|
316
|
+
}
|
|
317
|
+
return this.buildTypeImport(document.typeName, document.filePath, node_path_1.default.dirname(fromFilePath));
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Find all file (recursivly) in a given folder (workspacePath) that contains a table declaration ('process').
|
|
321
|
+
*/
|
|
322
|
+
static async discoverProcessFiles(workspacePath) {
|
|
323
|
+
const files = await this.getAllScriptFile(workspacePath);
|
|
324
|
+
const results = [];
|
|
325
|
+
for (const filePath of files) {
|
|
326
|
+
const content = await FileUtils_1.default.readFileAsync(filePath);
|
|
327
|
+
// Cheap way out: a file that does not even name the module declares no process
|
|
328
|
+
if (!content || !content.includes(PROCESS_MODULE)) {
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
for (const declaration of ScriptDeclarationUtils_1.default.findDeclarationCalls(filePath, content, PROCESS_MODULE, ['process'])) {
|
|
332
|
+
results.push({
|
|
333
|
+
filePath,
|
|
334
|
+
id: declaration.id,
|
|
335
|
+
isIdStatic: declaration.isIdStatic,
|
|
336
|
+
variableName: declaration.variableName,
|
|
337
|
+
isExported: declaration.isExported,
|
|
338
|
+
line: declaration.line,
|
|
339
|
+
scope: this.readScope(declaration),
|
|
340
|
+
trigger: this.readTrigger(declaration),
|
|
341
|
+
// run(ctx, input): the context comes first, the input of the process second
|
|
342
|
+
takesInput: (declaration.functions['run']?.parameters.length ?? 0) >= 2,
|
|
343
|
+
input: declaration.functions['run']?.parameters[1]?.schema,
|
|
344
|
+
output: declaration.functions['run']?.returnSchema,
|
|
345
|
+
description: typeof declaration.properties['description'] === 'string' ? declaration.properties['description'] : undefined,
|
|
346
|
+
properties: declaration.properties
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return results;
|
|
351
|
+
}
|
|
352
|
+
/** Value of `auth: { scope: 'internal' }` when it is written literally */
|
|
353
|
+
static readScope(declaration) {
|
|
354
|
+
const auth = declaration.properties['auth'];
|
|
355
|
+
if (auth !== null && typeof auth === 'object' && !Array.isArray(auth) && typeof auth['scope'] === 'string') {
|
|
356
|
+
return auth['scope'];
|
|
357
|
+
}
|
|
358
|
+
return undefined;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Turns the `trigger` property into something usable, e.g. `api.post('/books')` into
|
|
362
|
+
* { kind: 'api', method: 'POST', route: '/books' }.
|
|
363
|
+
* A process without trigger is only reachable through ctx.call, and returns undefined.
|
|
364
|
+
*/
|
|
365
|
+
static readTrigger(declaration) {
|
|
366
|
+
const trigger = declaration.calls['trigger'];
|
|
367
|
+
if (trigger === undefined) {
|
|
368
|
+
return undefined;
|
|
369
|
+
}
|
|
370
|
+
const [first] = trigger.arguments;
|
|
371
|
+
if (trigger.callee.startsWith('api.') && typeof first === 'string') {
|
|
372
|
+
return { kind: 'api', method: trigger.callee.substring('api.'.length).toUpperCase(), route: first };
|
|
373
|
+
}
|
|
374
|
+
if (trigger.callee === 'timer' && typeof first === 'string') {
|
|
375
|
+
return { kind: 'timer', cron: first };
|
|
376
|
+
}
|
|
377
|
+
if (trigger.callee === 'signal' && typeof first === 'string') {
|
|
378
|
+
return { kind: 'signal', ref: first };
|
|
379
|
+
}
|
|
380
|
+
if (trigger.callee === 'install') {
|
|
381
|
+
const options = first !== null && typeof first === 'object' && !Array.isArray(first) ? first : {};
|
|
382
|
+
return { kind: 'install', options };
|
|
383
|
+
}
|
|
384
|
+
// A trigger that is not one of the known factories, or built at runtime: reported as is rather than guessed
|
|
385
|
+
return { kind: 'unknown', callee: trigger.callee };
|
|
386
|
+
}
|
|
387
|
+
static async getAllScriptFile(workspacePath, fileExtensions = ['.tsx', '.jsx', '.ts', '.js']) {
|
|
388
|
+
const ignoredFolders = await FileUtils_1.default.getIgnoredFolderPaths(workspacePath);
|
|
389
|
+
const results = FileUtils_1.default.listFilesRecursively(workspacePath, {
|
|
390
|
+
ignoredFolderPaths: ignoredFolders,
|
|
391
|
+
fileExtensions: fileExtensions
|
|
392
|
+
});
|
|
393
|
+
return results;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* An app is a tsx/jsx file with a sibling *.manifest.json.
|
|
397
|
+
*
|
|
398
|
+
* A script without its manifest is not an app and is left out, whereas an app whose manifest
|
|
399
|
+
* cannot be parsed is still reported, with an empty manifest: it exists on disk, and saying
|
|
400
|
+
* why its manifest is wrong is the job of the validators.
|
|
401
|
+
*/
|
|
402
|
+
static async discoverAppsFiles(workspacePath) {
|
|
403
|
+
const files = await this.getAllScriptFile(workspacePath, ['.tsx', '.jsx']);
|
|
404
|
+
const results = [];
|
|
405
|
+
for (const scriptPath of files) {
|
|
406
|
+
// 'apps/Dashboard.tsx' is an app when 'apps/Dashboard.manifest.json' sits next to it
|
|
407
|
+
const appName = FileUtils_1.default.getNameFromPath(scriptPath, true);
|
|
408
|
+
const manifestPath = node_path_1.default.join(node_path_1.default.dirname(scriptPath), `${appName}.manifest.json`);
|
|
409
|
+
if (await FileUtils_1.default.exists(manifestPath) === false) {
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
results.push({
|
|
413
|
+
manifestPath,
|
|
414
|
+
scriptPath,
|
|
415
|
+
type: FileUtils_1.default.getExtensionFromPath(scriptPath).toLowerCase() === 'jsx' ? 'jsx' : 'tsx',
|
|
416
|
+
name: appName,
|
|
417
|
+
manifest: await FileUtils_1.default.readJsonFileAsync(manifestPath) ?? {}
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
return results;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Find all *.i18n.csv files (recursively) in a given folder (workspacePath), with the keys
|
|
424
|
+
* they declare and the languages they translate them into.
|
|
425
|
+
*
|
|
426
|
+
* A file whose first column is not 'key' is reported with no key and no language rather than
|
|
427
|
+
* being read wrong: saying why it is malformed is the job of the validators.
|
|
428
|
+
*/
|
|
429
|
+
static async discoverI18nsFiles(workspacePath) {
|
|
430
|
+
const files = FileUtils_1.default.listFilesRecursively(workspacePath, {
|
|
431
|
+
ignoredFolderPaths: await FileUtils_1.default.getIgnoredFolderPaths(workspacePath),
|
|
432
|
+
fileExtensions: ['.i18n.csv']
|
|
433
|
+
});
|
|
434
|
+
const results = [];
|
|
435
|
+
for (const filePath of files) {
|
|
436
|
+
const content = await FileUtils_1.default.readFileAsync(filePath);
|
|
437
|
+
if (content === undefined) {
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
const parsed = papaparse_1.default.parse(content, { header: true, skipEmptyLines: 'greedy' });
|
|
441
|
+
const columns = parsed.meta.fields ?? [];
|
|
442
|
+
// Languages come from the header rather than from the rows, so an empty file still reports them
|
|
443
|
+
const languages = columns[0] === 'key' ? columns.slice(1).filter(column => column.trim() !== '') : [];
|
|
444
|
+
const keys = columns[0] === 'key' ? I18nTsUtils_1.default.getEntries(parsed.data).map(entry => entry.key) : [];
|
|
445
|
+
results.push({ filePath, keys, languages });
|
|
446
|
+
}
|
|
447
|
+
return results;
|
|
83
448
|
}
|
|
84
449
|
}
|
|
85
450
|
exports.default = ProjectUtils;
|