datagrok-tools 5.1.7 → 5.1.9
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/bin/commands/api.js +5 -2
- package/bin/commands/build.js +209 -0
- package/bin/commands/check.js +53 -2
- package/bin/commands/help.js +37 -4
- package/bin/commands/test.js +141 -3
- package/bin/grok.js +2 -1
- package/bin/utils/test-utils.js +19 -0
- package/bin/utils/utils.js +3 -3
- package/package.json +1 -1
- package/plugins/func-gen-plugin.js +8 -4
package/bin/commands/api.js
CHANGED
|
@@ -15,6 +15,9 @@ const annotationForApiFile = `/**\nThis file is auto-generated by the grok api c
|
|
|
15
15
|
const sep = '\n';
|
|
16
16
|
const packageFuncDirs = ['package.ts', 'package.g.ts'];
|
|
17
17
|
const apiFile = 'package-api.ts';
|
|
18
|
+
function normEol(s) {
|
|
19
|
+
return s.replace(/\r\n/g, '\n');
|
|
20
|
+
}
|
|
18
21
|
const curDir = process.cwd();
|
|
19
22
|
const srcDir = _path.default.join(curDir, 'src');
|
|
20
23
|
const funcFilePath = _path.default.join(_fs.default.existsSync(srcDir) ? srcDir : curDir, apiFile);
|
|
@@ -107,11 +110,11 @@ function saveWrappersToFile(namespaceName, wrappers) {
|
|
|
107
110
|
if (wrappers.length === 0) return;
|
|
108
111
|
if (!_fs.default.existsSync(funcFilePath)) createApiFile();
|
|
109
112
|
const scriptApi = new utils.TemplateBuilder(utils.namespaceTemplate).replace('PACKAGE_NAMESPACE', namespaceName).replace('NAME', wrappers.join(sep.repeat(2)));
|
|
110
|
-
_fs.default.appendFileSync(funcFilePath, sep + scriptApi.build() + sep);
|
|
113
|
+
_fs.default.appendFileSync(funcFilePath, normEol(sep + scriptApi.build() + sep));
|
|
111
114
|
color.log(`Successfully generated file ${apiFile}${sep}`, 'success');
|
|
112
115
|
}
|
|
113
116
|
function createApiFile() {
|
|
114
|
-
_fs.default.writeFileSync(funcFilePath, annotationForApiFile + utils.dgImports + sep, 'utf8');
|
|
117
|
+
_fs.default.writeFileSync(funcFilePath, normEol(annotationForApiFile + utils.dgImports + sep), 'utf8');
|
|
115
118
|
}
|
|
116
119
|
function checkNameColision(name) {
|
|
117
120
|
if (names.has(name)) console.log('There is collision in name ' + name);
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.applyFilter = applyFilter;
|
|
8
|
+
exports.build = build;
|
|
9
|
+
exports.confirm = confirm;
|
|
10
|
+
exports.discoverPackages = discoverPackages;
|
|
11
|
+
exports.getNestedValue = getNestedValue;
|
|
12
|
+
var _fs = _interopRequireDefault(require("fs"));
|
|
13
|
+
var _path = _interopRequireDefault(require("path"));
|
|
14
|
+
var _util = require("util");
|
|
15
|
+
var _child_process = require("child_process");
|
|
16
|
+
var readline = _interopRequireWildcard(require("readline"));
|
|
17
|
+
var utils = _interopRequireWildcard(require("../utils/utils"));
|
|
18
|
+
var color = _interopRequireWildcard(require("../utils/color-utils"));
|
|
19
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
20
|
+
const execAsync = (0, _util.promisify)(_child_process.exec);
|
|
21
|
+
async function build(args) {
|
|
22
|
+
if (args.verbose) color.setVerbose(true);
|
|
23
|
+
const buildCmd = args['no-incremental'] ? 'npm run build' : 'npm run build -- --env incremental';
|
|
24
|
+
if (args.recursive) return await buildRecursive(process.cwd(), args, buildCmd);else return await buildSingle(process.cwd(), buildCmd);
|
|
25
|
+
}
|
|
26
|
+
async function buildSingle(dir, buildCmd) {
|
|
27
|
+
if (!utils.isPackageDir(dir)) {
|
|
28
|
+
color.error('Not a package directory (no package.json found)');
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
const packageJson = JSON.parse(_fs.default.readFileSync(_path.default.join(dir, 'package.json'), 'utf-8'));
|
|
32
|
+
const name = packageJson.friendlyName || packageJson.name;
|
|
33
|
+
console.log(`Building ${name}...`);
|
|
34
|
+
try {
|
|
35
|
+
await utils.runScript('npm install', dir, color.isVerbose());
|
|
36
|
+
await utils.runScript(buildCmd, dir, color.isVerbose());
|
|
37
|
+
color.success(`Successfully built ${name}`);
|
|
38
|
+
return true;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
color.error(`Failed to build ${name}`);
|
|
41
|
+
if (error.message) color.error(error.message);
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function buildRecursive(baseDir, args, buildCmd) {
|
|
46
|
+
const packages = discoverPackages(baseDir);
|
|
47
|
+
if (packages.length === 0) {
|
|
48
|
+
color.warn('No packages found in the current directory');
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
const filtered = args.filter ? applyFilter(packages, args.filter) : packages;
|
|
52
|
+
if (filtered.length === 0) {
|
|
53
|
+
color.warn('No packages match the filter');
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
console.log(`Found ${filtered.length} package(s): ${filtered.map(p => p.friendlyName).join(', ')}`);
|
|
57
|
+
if (!args.silent) {
|
|
58
|
+
const confirmed = await confirm(`\nBuild ${filtered.length} package(s)?`);
|
|
59
|
+
if (!confirmed) {
|
|
60
|
+
console.log('Aborted.');
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const maxParallel = args.parallel || 4;
|
|
65
|
+
const results = await buildParallel(filtered, buildCmd, maxParallel);
|
|
66
|
+
return results.every(r => r.success);
|
|
67
|
+
}
|
|
68
|
+
function discoverPackages(baseDir) {
|
|
69
|
+
const entries = _fs.default.readdirSync(baseDir);
|
|
70
|
+
const packages = [];
|
|
71
|
+
for (const entry of entries) {
|
|
72
|
+
if (entry.startsWith('.') || entry === 'node_modules') continue;
|
|
73
|
+
const dir = _path.default.join(baseDir, entry);
|
|
74
|
+
try {
|
|
75
|
+
if (!_fs.default.statSync(dir).isDirectory()) continue;
|
|
76
|
+
} catch (_) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const packageJsonPath = _path.default.join(dir, 'package.json');
|
|
80
|
+
if (!_fs.default.existsSync(packageJsonPath)) continue;
|
|
81
|
+
try {
|
|
82
|
+
const packageJson = JSON.parse(_fs.default.readFileSync(packageJsonPath, 'utf-8'));
|
|
83
|
+
packages.push({
|
|
84
|
+
dir,
|
|
85
|
+
name: packageJson.name || entry,
|
|
86
|
+
friendlyName: packageJson.friendlyName || packageJson.name || entry,
|
|
87
|
+
version: packageJson.version || '0.0.0',
|
|
88
|
+
packageJson
|
|
89
|
+
});
|
|
90
|
+
} catch (_) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return packages.sort((a, b) => a.friendlyName.localeCompare(b.friendlyName));
|
|
95
|
+
}
|
|
96
|
+
function applyFilter(packages, filterStr) {
|
|
97
|
+
const conditions = filterStr.split('&&').map(s => s.trim());
|
|
98
|
+
const parsedConditions = conditions.map(cond => {
|
|
99
|
+
const colonIdx = cond.indexOf(':');
|
|
100
|
+
if (colonIdx === -1) return {
|
|
101
|
+
field: cond,
|
|
102
|
+
pattern: new RegExp('.')
|
|
103
|
+
};
|
|
104
|
+
const field = cond.substring(0, colonIdx).trim();
|
|
105
|
+
const pattern = new RegExp(cond.substring(colonIdx + 1).trim());
|
|
106
|
+
return {
|
|
107
|
+
field,
|
|
108
|
+
pattern
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
return packages.filter(pkg => {
|
|
112
|
+
for (const cond of parsedConditions) {
|
|
113
|
+
const value = getNestedValue(pkg.packageJson, cond.field);
|
|
114
|
+
if (value === undefined || !cond.pattern.test(String(value))) return false;
|
|
115
|
+
}
|
|
116
|
+
return true;
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
function getNestedValue(obj, path) {
|
|
120
|
+
const parts = path.split('.');
|
|
121
|
+
let current = obj;
|
|
122
|
+
for (const part of parts) {
|
|
123
|
+
if (current == null || typeof current !== 'object') return undefined;
|
|
124
|
+
current = current[part];
|
|
125
|
+
}
|
|
126
|
+
return current;
|
|
127
|
+
}
|
|
128
|
+
async function buildParallel(packages, buildCmd, maxParallel) {
|
|
129
|
+
const results = [];
|
|
130
|
+
const headers = ['Plugin', 'Version', 'Build time', 'Bundle size'];
|
|
131
|
+
const widths = [Math.max(headers[0].length, ...packages.map(p => p.friendlyName.length)), Math.max(headers[1].length, ...packages.map(p => p.version.length)), Math.max(headers[2].length, 10), Math.max(headers[3].length, 40)];
|
|
132
|
+
const pad = (s, w) => s + ' '.repeat(Math.max(0, w - s.length));
|
|
133
|
+
console.log(`\nBuilding with ${maxParallel} parallel job(s)...`);
|
|
134
|
+
console.log(headers.map((h, i) => pad(h, widths[i])).join(' | '));
|
|
135
|
+
console.log(widths.map(w => '-'.repeat(w)).join('-+-'));
|
|
136
|
+
const buildOne = async pkg => {
|
|
137
|
+
const start = Date.now();
|
|
138
|
+
let success = true;
|
|
139
|
+
let buildTime = '';
|
|
140
|
+
let bundleSize = '';
|
|
141
|
+
try {
|
|
142
|
+
await execAsync('npm install', {
|
|
143
|
+
cwd: pkg.dir,
|
|
144
|
+
maxBuffer: 10 * 1024 * 1024
|
|
145
|
+
});
|
|
146
|
+
await execAsync(buildCmd, {
|
|
147
|
+
cwd: pkg.dir,
|
|
148
|
+
maxBuffer: 10 * 1024 * 1024
|
|
149
|
+
});
|
|
150
|
+
const elapsed = (Date.now() - start) / 1000;
|
|
151
|
+
buildTime = `${elapsed.toFixed(1)}s`;
|
|
152
|
+
bundleSize = getBundleSize(pkg.dir);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
success = false;
|
|
155
|
+
buildTime = 'Error';
|
|
156
|
+
const raw = (error.stderr || error.stdout || error.message || 'Unknown error').trim();
|
|
157
|
+
bundleSize = raw.replace(/\r?\n/g, ' ').replace(/\s+/g, ' ').substring(0, 40);
|
|
158
|
+
}
|
|
159
|
+
const result = {
|
|
160
|
+
name: pkg.friendlyName,
|
|
161
|
+
version: pkg.version,
|
|
162
|
+
buildTime,
|
|
163
|
+
bundleSize,
|
|
164
|
+
success
|
|
165
|
+
};
|
|
166
|
+
results.push(result);
|
|
167
|
+
const cells = [result.name, result.version, result.buildTime, result.bundleSize];
|
|
168
|
+
const line = cells.map((cell, j) => pad(cell, widths[j])).join(' | ');
|
|
169
|
+
if (success) color.info(line);else color.error(line);
|
|
170
|
+
return result;
|
|
171
|
+
};
|
|
172
|
+
let idx = 0;
|
|
173
|
+
const next = async () => {
|
|
174
|
+
while (idx < packages.length) {
|
|
175
|
+
const pkg = packages[idx++];
|
|
176
|
+
await buildOne(pkg);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
const workers = Array.from({
|
|
180
|
+
length: Math.min(maxParallel, packages.length)
|
|
181
|
+
}, () => next());
|
|
182
|
+
await Promise.all(workers);
|
|
183
|
+
const succeeded = results.filter(r => r.success).length;
|
|
184
|
+
const failed = results.length - succeeded;
|
|
185
|
+
console.log('');
|
|
186
|
+
if (failed === 0) color.success(`All ${results.length} package(s) built successfully`);else color.warn(`${succeeded} succeeded, ${failed} failed`);
|
|
187
|
+
return results;
|
|
188
|
+
}
|
|
189
|
+
function getBundleSize(dir) {
|
|
190
|
+
const bundlePath = _path.default.join(dir, 'dist', 'package.js');
|
|
191
|
+
try {
|
|
192
|
+
const stats = _fs.default.statSync(bundlePath);
|
|
193
|
+
return `${(stats.size / 1024).toFixed(1)} KB`;
|
|
194
|
+
} catch (_) {
|
|
195
|
+
return 'N/A';
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function confirm(message) {
|
|
199
|
+
const rl = readline.createInterface({
|
|
200
|
+
input: process.stdin,
|
|
201
|
+
output: process.stdout
|
|
202
|
+
});
|
|
203
|
+
return new Promise(resolve => {
|
|
204
|
+
rl.question(`${message} [Y/n] `, answer => {
|
|
205
|
+
rl.close();
|
|
206
|
+
resolve(answer.trim().toLowerCase() !== 'n');
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
}
|
package/bin/commands/check.js
CHANGED
|
@@ -8,6 +8,7 @@ exports.check = check;
|
|
|
8
8
|
exports.checkChangelog = checkChangelog;
|
|
9
9
|
exports.checkDatagrokApiImports = checkDatagrokApiImports;
|
|
10
10
|
exports.checkFuncSignatures = checkFuncSignatures;
|
|
11
|
+
exports.checkHeavyImports = checkHeavyImports;
|
|
11
12
|
exports.checkImportStatements = checkImportStatements;
|
|
12
13
|
exports.checkNpmIgnore = checkNpmIgnore;
|
|
13
14
|
exports.checkPackageFile = checkPackageFile;
|
|
@@ -23,6 +24,30 @@ var _const = require("../utils/const");
|
|
|
23
24
|
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
24
25
|
const warns = ['Latest package version', 'Datagrok API version should contain'];
|
|
25
26
|
const forbiddenNames = ['function', 'class', 'export'];
|
|
27
|
+
/** Static imports of these libraries significantly increase the main bundle size. */
|
|
28
|
+
const HEAVY_IMPORT_RULES = [
|
|
29
|
+
// Test utilities — should never reach production bundles
|
|
30
|
+
{
|
|
31
|
+
pattern: '@datagrok-libraries/test',
|
|
32
|
+
reason: 'test-only library imported in production code',
|
|
33
|
+
suggestion: 'replace delay() with DG.delay(), testEvent() with alternatives from utils'
|
|
34
|
+
},
|
|
35
|
+
// Heavy UI/editor libraries — only flagged when not the package\'s core purpose
|
|
36
|
+
{
|
|
37
|
+
pattern: 'codemirror',
|
|
38
|
+
reason: 'heavy text editor (~870 KB) statically bundled',
|
|
39
|
+
suggestion: 'use dynamic import() inside the function that opens the editor'
|
|
40
|
+
}, {
|
|
41
|
+
pattern: 'konva',
|
|
42
|
+
reason: 'heavy canvas library (~410 KB) statically bundled',
|
|
43
|
+
suggestion: 'use dynamic import() to load on demand'
|
|
44
|
+
},
|
|
45
|
+
// Heavy data-processing libraries
|
|
46
|
+
{
|
|
47
|
+
pattern: 'exceljs',
|
|
48
|
+
reason: 'heavy Excel library (~940 KB) statically bundled',
|
|
49
|
+
suggestion: 'offload to a web worker (see PowerPack/src/workers/exceljs-worker.ts)'
|
|
50
|
+
}];
|
|
26
51
|
const namesInFiles = new Map();
|
|
27
52
|
function check(args) {
|
|
28
53
|
if (args.verbose !== undefined || args.v !== undefined) color.setVerbose(args.verbose || args.v || false);
|
|
@@ -41,9 +66,9 @@ function runChecks(packagePath, soft = false, noExit = false) {
|
|
|
41
66
|
path: packagePath,
|
|
42
67
|
ignoreFiles: ['.npmignore', '.gitignore']
|
|
43
68
|
}).filter(e => !e.includes('node_modules'));
|
|
44
|
-
const jsTsFiles = files.filter(f => (f.startsWith('src'
|
|
69
|
+
const jsTsFiles = files.filter(f => (f.startsWith('src/') || f.startsWith('queries/') || f.startsWith('scripts/')) && (f.endsWith('.js') || f.endsWith('.ts') || f.endsWith('.sql') || f.endsWith('.py')));
|
|
45
70
|
const packageFiles = ['src/package.ts', 'src/detectors.ts', 'src/package.js', 'src/detectors.js', 'src/package-test.ts', 'src/package-test.js', 'package.js', 'detectors.js'];
|
|
46
|
-
// const funcFiles = jsTsFiles.filter((f) => packageFiles.includes(f));
|
|
71
|
+
// const funcFiles = jsTsFiles.filter((f) => packageFiles.includes(f));
|
|
47
72
|
const errors = [];
|
|
48
73
|
const warnings = [];
|
|
49
74
|
const packageFilePath = _path.default.join(packagePath, 'package.json');
|
|
@@ -67,6 +92,7 @@ function runChecks(packagePath, soft = false, noExit = false) {
|
|
|
67
92
|
if (externals) errors.push(...checkImportStatements(packagePath, jsTsFiles, externals));
|
|
68
93
|
}
|
|
69
94
|
errors.push(...checkDatagrokApiImports(packagePath, jsTsFiles));
|
|
95
|
+
warnings.push(...checkHeavyImports(packagePath, jsTsFiles, _path.default.basename(packagePath)));
|
|
70
96
|
if (!soft) errors.push(...checkSourceMap(packagePath));
|
|
71
97
|
errors.push(...checkNpmIgnore(packagePath));
|
|
72
98
|
warnings.push(...checkScriptNames(packagePath));
|
|
@@ -188,6 +214,31 @@ function checkDatagrokApiImports(packagePath, files) {
|
|
|
188
214
|
}
|
|
189
215
|
return errors;
|
|
190
216
|
}
|
|
217
|
+
function checkHeavyImports(packagePath, files, packageName = '') {
|
|
218
|
+
const warnings = [];
|
|
219
|
+
const isTestPackage = /tests?$/i.test(packageName);
|
|
220
|
+
const staticImportRegex = /^\s*import\s+(?!type\s).*['"]([^'"]+)['"]/;
|
|
221
|
+
for (const file of files) {
|
|
222
|
+
const isTestFile = file.includes('/tests/') || file.includes('/test/') || _path.default.basename(file).includes('-test.') || _path.default.basename(file).includes('-testing.') || _path.default.basename(file) === 'package-test.ts' || _path.default.basename(file) === 'package-test.js';
|
|
223
|
+
const isWorkerFile = file.includes('/workers/');
|
|
224
|
+
const isVendoredLib = file.includes('/libs/');
|
|
225
|
+
if (isWorkerFile || isVendoredLib) continue;
|
|
226
|
+
const content = _fs.default.readFileSync(_path.default.join(packagePath, file), {
|
|
227
|
+
encoding: 'utf-8'
|
|
228
|
+
});
|
|
229
|
+
for (const line of content.split('\n')) {
|
|
230
|
+
const m = line.match(staticImportRegex);
|
|
231
|
+
if (!m) continue;
|
|
232
|
+
const specifier = m[1];
|
|
233
|
+
for (const rule of HEAVY_IMPORT_RULES) {
|
|
234
|
+
if (!specifier.includes(rule.pattern)) continue;
|
|
235
|
+
if (isTestFile || isTestPackage) continue;
|
|
236
|
+
warnings.push(`File "${file}": ${rule.reason}.\n` + ` Found: ${line.trim()}\n` + ` Hint: ${rule.suggestion}\n`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return warnings;
|
|
241
|
+
}
|
|
191
242
|
const TYPE_ALIASES = {
|
|
192
243
|
file: ['fileinfo'],
|
|
193
244
|
dynamic: ['searchprovider']
|
package/bin/commands/help.js
CHANGED
|
@@ -12,6 +12,7 @@ Datagrok's package management tool
|
|
|
12
12
|
Commands:
|
|
13
13
|
add Add an object template
|
|
14
14
|
api Create wrapper functions
|
|
15
|
+
build Build a package or multiple packages
|
|
15
16
|
check Check package content (function signatures, etc.)
|
|
16
17
|
config Create and manage config files
|
|
17
18
|
create Create a package
|
|
@@ -144,15 +145,15 @@ const HELP_TEST = `
|
|
|
144
145
|
Usage: grok test
|
|
145
146
|
|
|
146
147
|
Options:
|
|
147
|
-
[--package] [--category] [--test] [--host] [--csv] [--gui] [--skip-build] [--skip-publish] [--link] [--catchUnhandled] [--report] [--record] [--verbose] [--platform] [--benchmark] [--stress-test] [--debug] [--all]
|
|
148
|
+
[--package] [--category] [--test] [--host] [--csv] [--gui] [--skip-build] [--skip-publish] [--link] [--catchUnhandled] [--report] [--record] [--verbose] [--platform] [--benchmark] [--stress-test] [--debug] [--all] [-r | --recursive] [--filter] [--parallel N]
|
|
148
149
|
|
|
149
150
|
--package Specify a package name to run tests for
|
|
150
151
|
--category Specify a category name to run tests for
|
|
151
|
-
--test Specify a test name to run
|
|
152
|
+
--test Specify a test name to run
|
|
152
153
|
--host Host alias as in the config file
|
|
153
154
|
--csv Save the test report in a CSV file
|
|
154
155
|
--gui Launch graphical interface (non-headless mode)
|
|
155
|
-
--debug Enables debug point on tests run (useless without gui mode)
|
|
156
|
+
--debug Enables debug point on tests run (useless without gui mode)
|
|
156
157
|
--verbose Show debug information
|
|
157
158
|
--retry --no-retry Enables or disables browser reload after a failed test
|
|
158
159
|
--report Report failed tests to audit, notifies package author (default=false)
|
|
@@ -165,9 +166,18 @@ Options:
|
|
|
165
166
|
--benchmark Runs tests in benchmark mode
|
|
166
167
|
--stress-test Runs shuffled stress-test only
|
|
167
168
|
--all Runs tests for all available packages(run in packages directory)
|
|
168
|
-
|
|
169
|
+
--recursive Test all packages in the current directory (parallel, table output)
|
|
170
|
+
--filter Filter packages by package.json fields (e.g. --filter "category:Cheminformatics")
|
|
171
|
+
--parallel N Max parallel test jobs (default: 4)
|
|
172
|
+
|
|
169
173
|
Run package tests
|
|
170
174
|
|
|
175
|
+
Examples:
|
|
176
|
+
grok test -r Test all packages
|
|
177
|
+
grok test -r --filter "category:Cheminformatics" Test matching packages
|
|
178
|
+
grok test -r --parallel 2 --host dev Test with 2 jobs against dev
|
|
179
|
+
grok test -r --skip-build --skip-publish Test without rebuilding
|
|
180
|
+
|
|
171
181
|
See instructions:
|
|
172
182
|
https://datagrok.ai/help/develop/how-to/test-packages#local-testing
|
|
173
183
|
`;
|
|
@@ -227,6 +237,28 @@ Example:
|
|
|
227
237
|
⟶
|
|
228
238
|
meta: { role: 'viewer,ml' }
|
|
229
239
|
`;
|
|
240
|
+
const HELP_BUILD = `
|
|
241
|
+
Usage: grok build
|
|
242
|
+
|
|
243
|
+
Build a package in the current directory, or recursively build multiple packages.
|
|
244
|
+
|
|
245
|
+
Options:
|
|
246
|
+
[-r | --recursive] [-s | --silent] [--filter] [--no-incremental] [--parallel N] [-v | --verbose]
|
|
247
|
+
|
|
248
|
+
--recursive Build all packages in the current directory
|
|
249
|
+
--silent Skip confirmation prompt (for recursive builds)
|
|
250
|
+
--filter Filter packages by package.json fields (e.g. --filter "category:Cheminformatics")
|
|
251
|
+
--no-incremental Run a full build instead of the default incremental build
|
|
252
|
+
--parallel N Max parallel builds (default: 4)
|
|
253
|
+
--verbose Print detailed output
|
|
254
|
+
|
|
255
|
+
Examples:
|
|
256
|
+
grok build Build the current package
|
|
257
|
+
grok build -r Build all packages in the current directory
|
|
258
|
+
grok build -r -s Build all packages without confirmation
|
|
259
|
+
grok build -r --filter "category:Cheminformatics" Build only matching packages
|
|
260
|
+
grok build -r --parallel 8 Build with 8 parallel jobs
|
|
261
|
+
`;
|
|
230
262
|
|
|
231
263
|
// const HELP_MIGRATE = `
|
|
232
264
|
// Usage: grok migrate
|
|
@@ -238,6 +270,7 @@ Example:
|
|
|
238
270
|
const help = exports.help = {
|
|
239
271
|
add: HELP_ADD,
|
|
240
272
|
api: HELP_API,
|
|
273
|
+
build: HELP_BUILD,
|
|
241
274
|
check: HELP_CHECK,
|
|
242
275
|
config: HELP_CONFIG,
|
|
243
276
|
create: HELP_CREATE,
|
package/bin/commands/test.js
CHANGED
|
@@ -6,18 +6,22 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
});
|
|
7
7
|
exports.test = test;
|
|
8
8
|
var _child_process = require("child_process");
|
|
9
|
+
var _util = require("util");
|
|
9
10
|
var _fs = _interopRequireDefault(require("fs"));
|
|
10
11
|
var _os = _interopRequireDefault(require("os"));
|
|
11
12
|
var _path = _interopRequireDefault(require("path"));
|
|
12
13
|
var _jsYaml = _interopRequireDefault(require("js-yaml"));
|
|
13
14
|
var utils = _interopRequireWildcard(require("../utils/utils"));
|
|
14
15
|
var color = _interopRequireWildcard(require("../utils/color-utils"));
|
|
16
|
+
var _build = require("./build");
|
|
15
17
|
var Papa = _interopRequireWildcard(require("papaparse"));
|
|
16
18
|
var _testUtils = _interopRequireWildcard(require("../utils/test-utils"));
|
|
17
19
|
var testUtils = _testUtils;
|
|
18
20
|
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
19
21
|
/* eslint-disable max-len */
|
|
20
22
|
|
|
23
|
+
const execAsync = (0, _util.promisify)(_child_process.exec);
|
|
24
|
+
const execFileAsync = (0, _util.promisify)(_child_process.execFile);
|
|
21
25
|
const testInvocationTimeout = 3600000;
|
|
22
26
|
const availableCommandOptions = ['host', 'package', 'csv', 'gui', 'catchUnhandled', 'platform', 'core', 'report', 'skip-build', 'skip-publish', 'path', 'record', 'verbose', 'benchmark', 'category', 'test', 'stress-test', 'link', 'tag', 'ci-cd', 'debug', 'no-retry'];
|
|
23
27
|
const curDir = process.cwd();
|
|
@@ -53,11 +57,11 @@ function findGitRoot(startDir) {
|
|
|
53
57
|
}
|
|
54
58
|
}
|
|
55
59
|
async function test(args) {
|
|
60
|
+
if (args.recursive) return await testRecursive(process.cwd(), args);
|
|
56
61
|
const config = _jsYaml.default.load(_fs.default.readFileSync(confPath, {
|
|
57
62
|
encoding: 'utf-8'
|
|
58
63
|
}));
|
|
59
64
|
isArgsValid(args);
|
|
60
|
-
utils.setHost(args.host, config);
|
|
61
65
|
|
|
62
66
|
// If running from a core Dart library directory, delegate to DevTools package
|
|
63
67
|
if (!args.package) {
|
|
@@ -71,7 +75,8 @@ async function test(args) {
|
|
|
71
75
|
color.error(`Run 'grok test --category="${category}"' from 'public/packages/DevTools' instead.`);
|
|
72
76
|
process.exit(1);
|
|
73
77
|
}
|
|
74
|
-
|
|
78
|
+
const hostAlias = args.host ?? config.default;
|
|
79
|
+
color.info(`Detected core library directory. Delegating to DevTools with category: "${category}"${hostAlias ? ` (host: ${hostAlias})` : ''}`);
|
|
75
80
|
const cmdArgs = ['test', `--category=${category}`];
|
|
76
81
|
if (args.host) cmdArgs.push(`--host=${args.host}`);
|
|
77
82
|
if (args.test) cmdArgs.push(`--test=${args.test}`);
|
|
@@ -89,6 +94,13 @@ async function test(args) {
|
|
|
89
94
|
if (args.debug) cmdArgs.push('--debug');
|
|
90
95
|
if (args['ci-cd']) cmdArgs.push('--ci-cd');
|
|
91
96
|
if (args['no-retry']) cmdArgs.push('--no-retry');
|
|
97
|
+
if (!args['skip-publish']) {
|
|
98
|
+
const isDevToolsOnServer = await testUtils.isPackageOnServer(args.host ?? '', 'DevTools');
|
|
99
|
+
if (isDevToolsOnServer) {
|
|
100
|
+
cmdArgs.push('--skip-publish');
|
|
101
|
+
if (!args['skip-build']) cmdArgs.push('--skip-build');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
92
104
|
const grokScript = _path.default.resolve(__dirname, '..', 'grok.js');
|
|
93
105
|
const result = (0, _child_process.spawnSync)(process.execPath, [grokScript, ...cmdArgs], {
|
|
94
106
|
cwd: devToolsDir,
|
|
@@ -97,13 +109,14 @@ async function test(args) {
|
|
|
97
109
|
process.exit(result.status ?? 1);
|
|
98
110
|
}
|
|
99
111
|
}
|
|
112
|
+
utils.setHost(args.host, config, true);
|
|
100
113
|
let packageJsonData = undefined;
|
|
101
114
|
if (!args.package) packageJsonData = JSON.parse(_fs.default.readFileSync(_path.default.join(curDir, 'package.json'), {
|
|
102
115
|
encoding: 'utf-8'
|
|
103
116
|
}));
|
|
104
117
|
const packageName = args.package ? utils.kebabToCamelCase(args.package) : utils.kebabToCamelCase(utils.removeScope(packageJsonData.name));
|
|
105
118
|
const packagesDir = _path.default.basename(curDir) === 'packages' ? curDir : _path.default.dirname(curDir);
|
|
106
|
-
console.log(
|
|
119
|
+
console.log(`HOST: ${process.env.HOST}, TARGET_PACKAGE: ${packageName}`);
|
|
107
120
|
if (args.platform && packageName !== 'ApiTests') color.warn('--platform flag can only be used in the ApiTests package');
|
|
108
121
|
// if (args.core && packageName !== 'DevTools')
|
|
109
122
|
// color.warn('--core flag can only be used in the DevTools package');
|
|
@@ -312,4 +325,129 @@ function readCSVResultData(data) {
|
|
|
312
325
|
headers: parsed.meta.fields || [],
|
|
313
326
|
rows: parsed.data
|
|
314
327
|
};
|
|
328
|
+
}
|
|
329
|
+
async function testRecursive(baseDir, args) {
|
|
330
|
+
const packages = (0, _build.discoverPackages)(baseDir);
|
|
331
|
+
if (packages.length === 0) {
|
|
332
|
+
color.warn('No packages found in the current directory');
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
const filtered = args.filter ? (0, _build.applyFilter)(packages, args.filter) : packages;
|
|
336
|
+
if (filtered.length === 0) {
|
|
337
|
+
color.warn('No packages match the filter');
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
console.log(`Found ${filtered.length} package(s): ${filtered.map(p => p.friendlyName).join(', ')}`);
|
|
341
|
+
const confirmed = await (0, _build.confirm)(`\nTest ${filtered.length} package(s)?`);
|
|
342
|
+
if (!confirmed) {
|
|
343
|
+
console.log('Aborted.');
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
const maxParallel = args.parallel || 4;
|
|
347
|
+
const results = await testParallel(filtered, args, maxParallel);
|
|
348
|
+
return results.every(r => r.success);
|
|
349
|
+
}
|
|
350
|
+
function buildTestArgs(args) {
|
|
351
|
+
const grokScript = _path.default.resolve(__dirname, '..', 'grok.js');
|
|
352
|
+
const parts = [grokScript, 'test'];
|
|
353
|
+
if (args.host) parts.push(`--host=${args.host}`);
|
|
354
|
+
if (args['skip-build']) parts.push('--skip-build');
|
|
355
|
+
if (args['skip-publish']) parts.push('--skip-publish');
|
|
356
|
+
if (args.link) parts.push('--link');
|
|
357
|
+
if (args.verbose) parts.push('--verbose');
|
|
358
|
+
if (args.csv) parts.push('--csv');
|
|
359
|
+
if (args.catchUnhandled) parts.push('--catchUnhandled');
|
|
360
|
+
if (args.report) parts.push('--report');
|
|
361
|
+
if (args.benchmark) parts.push('--benchmark');
|
|
362
|
+
if (args['stress-test']) parts.push('--stress-test');
|
|
363
|
+
if (args['ci-cd']) parts.push('--ci-cd');
|
|
364
|
+
if (args['no-retry']) parts.push('--no-retry');
|
|
365
|
+
return parts;
|
|
366
|
+
}
|
|
367
|
+
function parseTestOutput(stdout) {
|
|
368
|
+
const passedMatch = stdout.match(/Passed amount:\s*(\d+)/);
|
|
369
|
+
const failedMatch = stdout.match(/Failed amount:\s*(\d+)/);
|
|
370
|
+
const skippedMatch = stdout.match(/Skipped amount:\s*(\d+)/);
|
|
371
|
+
if (!passedMatch && !failedMatch) return null;
|
|
372
|
+
return {
|
|
373
|
+
passed: passedMatch ? parseInt(passedMatch[1]) : 0,
|
|
374
|
+
failed: failedMatch ? parseInt(failedMatch[1]) : 0,
|
|
375
|
+
skipped: skippedMatch ? parseInt(skippedMatch[1]) : 0
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
async function testParallel(packages, args, maxParallel) {
|
|
379
|
+
const results = [];
|
|
380
|
+
const cmdArgs = buildTestArgs(args);
|
|
381
|
+
const headers = ['Plugin', 'Version', 'Tests', 'Time'];
|
|
382
|
+
const widths = [Math.max(headers[0].length, ...packages.map(p => p.friendlyName.length)), Math.max(headers[1].length, ...packages.map(p => p.version.length)), Math.max(headers[2].length, 18), Math.max(headers[3].length, 8)];
|
|
383
|
+
const pad = (s, w) => s + ' '.repeat(Math.max(0, w - s.length));
|
|
384
|
+
console.log(`\nTesting with ${maxParallel} parallel job(s)...`);
|
|
385
|
+
console.log(headers.map((h, i) => pad(h, widths[i])).join(' | '));
|
|
386
|
+
console.log(widths.map(w => '-'.repeat(w)).join('-+-'));
|
|
387
|
+
const testOne = async pkg => {
|
|
388
|
+
const start = Date.now();
|
|
389
|
+
let success = true;
|
|
390
|
+
let tests = '';
|
|
391
|
+
let time = '';
|
|
392
|
+
try {
|
|
393
|
+
const {
|
|
394
|
+
stdout
|
|
395
|
+
} = await execFileAsync(process.execPath, cmdArgs, {
|
|
396
|
+
cwd: pkg.dir,
|
|
397
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
398
|
+
timeout: testInvocationTimeout
|
|
399
|
+
});
|
|
400
|
+
const elapsed = (Date.now() - start) / 1000;
|
|
401
|
+
time = `${elapsed.toFixed(1)}s`;
|
|
402
|
+
const counts = parseTestOutput(stdout);
|
|
403
|
+
if (counts) {
|
|
404
|
+
const total = counts.passed + counts.failed + counts.skipped;
|
|
405
|
+
tests = `${counts.passed} of ${total} passed`;
|
|
406
|
+
success = counts.failed === 0;
|
|
407
|
+
} else {
|
|
408
|
+
tests = 'Done (no counts)';
|
|
409
|
+
}
|
|
410
|
+
} catch (error) {
|
|
411
|
+
success = false;
|
|
412
|
+
const elapsed = (Date.now() - start) / 1000;
|
|
413
|
+
time = `${elapsed.toFixed(1)}s`;
|
|
414
|
+
const stdout = error.stdout || '';
|
|
415
|
+
const counts = parseTestOutput(stdout);
|
|
416
|
+
if (counts) {
|
|
417
|
+
const total = counts.passed + counts.failed + counts.skipped;
|
|
418
|
+
tests = `${counts.passed} of ${total} passed`;
|
|
419
|
+
} else {
|
|
420
|
+
const raw = (error.stderr || error.stdout || error.message || 'Unknown error').trim();
|
|
421
|
+
tests = raw.replace(/\r?\n/g, ' ').replace(/\s+/g, ' ').substring(0, 30);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
const result = {
|
|
425
|
+
name: pkg.friendlyName,
|
|
426
|
+
version: pkg.version,
|
|
427
|
+
tests,
|
|
428
|
+
time,
|
|
429
|
+
success
|
|
430
|
+
};
|
|
431
|
+
results.push(result);
|
|
432
|
+
const cells = [result.name, result.version, result.tests, result.time];
|
|
433
|
+
const line = cells.map((cell, j) => pad(cell, widths[j])).join(' | ');
|
|
434
|
+
if (success) color.info(line);else color.error(line);
|
|
435
|
+
return result;
|
|
436
|
+
};
|
|
437
|
+
let idx = 0;
|
|
438
|
+
const next = async () => {
|
|
439
|
+
while (idx < packages.length) {
|
|
440
|
+
const pkg = packages[idx++];
|
|
441
|
+
await testOne(pkg);
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
const workers = Array.from({
|
|
445
|
+
length: Math.min(maxParallel, packages.length)
|
|
446
|
+
}, () => next());
|
|
447
|
+
await Promise.all(workers);
|
|
448
|
+
const succeeded = results.filter(r => r.success).length;
|
|
449
|
+
const failed = results.length - succeeded;
|
|
450
|
+
console.log('');
|
|
451
|
+
if (failed === 0) color.success(`All ${results.length} package(s) passed`);else color.warn(`${succeeded} passed, ${failed} failed`);
|
|
452
|
+
return results;
|
|
315
453
|
}
|
package/bin/grok.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
const argv = require('minimist')(process.argv.slice(2), {
|
|
3
|
-
alias: {k: 'key', h: 'help', r: 'recursive'},
|
|
3
|
+
alias: {k: 'key', h: 'help', r: 'recursive', s: 'silent'},
|
|
4
4
|
});
|
|
5
5
|
const help = require('./commands/help').help;
|
|
6
6
|
const runAllCommand = require('./utils/utils').runAll;
|
|
@@ -8,6 +8,7 @@ const runAllCommand = require('./utils/utils').runAll;
|
|
|
8
8
|
const commands = {
|
|
9
9
|
add: require('./commands/add').add,
|
|
10
10
|
api: require('./commands/api').api,
|
|
11
|
+
build: require('./commands/build').build,
|
|
11
12
|
check: require('./commands/check').check,
|
|
12
13
|
config: require('./commands/config').config,
|
|
13
14
|
create: require('./commands/create').create,
|
package/bin/utils/test-utils.js
CHANGED
|
@@ -15,6 +15,7 @@ exports.getDevKey = getDevKey;
|
|
|
15
15
|
exports.getToken = getToken;
|
|
16
16
|
exports.getWebUrl = getWebUrl;
|
|
17
17
|
exports.getWebUrlFromPage = getWebUrlFromPage;
|
|
18
|
+
exports.isPackageOnServer = isPackageOnServer;
|
|
18
19
|
exports.loadPackage = loadPackage;
|
|
19
20
|
exports.loadPackages = loadPackages;
|
|
20
21
|
exports.loadTestsList = loadTestsList;
|
|
@@ -59,6 +60,24 @@ async function getToken(url, key) {
|
|
|
59
60
|
const json = await response.json();
|
|
60
61
|
if (json.isSuccess == true) return json.token;else throw new Error('Unable to login to server. Check your dev key');
|
|
61
62
|
}
|
|
63
|
+
async function isPackageOnServer(hostKey, packageName) {
|
|
64
|
+
try {
|
|
65
|
+
const {
|
|
66
|
+
url,
|
|
67
|
+
key
|
|
68
|
+
} = getDevKey(hostKey);
|
|
69
|
+
const token = await getToken(url, key);
|
|
70
|
+
const response = await fetch(`${url}/packages/published/current`, {
|
|
71
|
+
headers: {
|
|
72
|
+
Authorization: token
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
const packages = await response.json();
|
|
76
|
+
return Array.isArray(packages) && packages.some(p => p.name?.toLowerCase() === packageName.toLowerCase());
|
|
77
|
+
} catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
62
81
|
async function getWebUrl(url, token) {
|
|
63
82
|
const response = await fetch(`${url}/admin/plugins/admin/settings`, {
|
|
64
83
|
headers: {
|
package/bin/utils/utils.js
CHANGED
|
@@ -280,18 +280,18 @@ async function runScript(script, path, verbose = false) {
|
|
|
280
280
|
throw new Error(output);
|
|
281
281
|
}
|
|
282
282
|
}
|
|
283
|
-
function setHost(host, configFile) {
|
|
283
|
+
function setHost(host, configFile, quiet = false) {
|
|
284
284
|
if (host) {
|
|
285
285
|
if (host in configFile.servers) {
|
|
286
286
|
process.env.HOST = host;
|
|
287
|
-
console.log('Environment variable `HOST` is set to', host);
|
|
287
|
+
if (!quiet) console.log('Environment variable `HOST` is set to', host);
|
|
288
288
|
} else {
|
|
289
289
|
console.error(`Unknown server alias. Please add it to Config File`);
|
|
290
290
|
return false;
|
|
291
291
|
}
|
|
292
292
|
} else if (configFile.default) {
|
|
293
293
|
process.env.HOST = configFile.default;
|
|
294
|
-
console.log('Environment variable `HOST` is set to', configFile.default);
|
|
294
|
+
if (!quiet) console.log('Environment variable `HOST` is set to', configFile.default);
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
297
|
async function runAll(packagesDir, command, options, packagesToLoad = ['all']) {
|
package/package.json
CHANGED
|
@@ -20,6 +20,10 @@ const {toCamelCase} = require('../bin/commands/migrate');
|
|
|
20
20
|
const {api} = require('../bin/commands/api');
|
|
21
21
|
|
|
22
22
|
const baseImport = 'import * as DG from \'datagrok-api/dg\';\n';
|
|
23
|
+
|
|
24
|
+
function normEol(s) {
|
|
25
|
+
return s.replace(/\r\n/g, '\n');
|
|
26
|
+
}
|
|
23
27
|
// eslint-disable-next-line max-len
|
|
24
28
|
const annotationForGeneratedFile = `/**\nThis file is auto-generated by the webpack command.\nIf you notice any changes, please push them to the repository.\nDo not edit this file manually.\n*/\n`;
|
|
25
29
|
|
|
@@ -85,7 +89,7 @@ class FuncGeneratorPlugin {
|
|
|
85
89
|
}
|
|
86
90
|
});
|
|
87
91
|
this._insertImports([...imports]);
|
|
88
|
-
fs.appendFileSync(this.options.outputPath, functions.join(''), 'utf-8');
|
|
92
|
+
fs.appendFileSync(this.options.outputPath, normEol(functions.join('')), 'utf-8');
|
|
89
93
|
}
|
|
90
94
|
this._checkPackageFileForDecoratorsExport(packageFilePath);
|
|
91
95
|
// Uncommment to add obvious import/export
|
|
@@ -512,7 +516,7 @@ class FuncGeneratorPlugin {
|
|
|
512
516
|
}
|
|
513
517
|
|
|
514
518
|
_clearGeneratedFile() {
|
|
515
|
-
fs.writeFileSync(this.options.outputPath, baseImport);
|
|
519
|
+
fs.writeFileSync(this.options.outputPath, normEol(baseImport));
|
|
516
520
|
}
|
|
517
521
|
|
|
518
522
|
_checkPackageFileForDecoratorsExport(packagePath) {
|
|
@@ -543,11 +547,11 @@ class FuncGeneratorPlugin {
|
|
|
543
547
|
const content = fs.readFileSync(this.options.outputPath, 'utf-8');
|
|
544
548
|
if (content) importArray.push(content);
|
|
545
549
|
const output = importArray.join('');
|
|
546
|
-
fs.writeFileSync(this.options.outputPath,
|
|
550
|
+
fs.writeFileSync(this.options.outputPath, normEol(output), 'utf-8');
|
|
547
551
|
} else {
|
|
548
552
|
fs.writeFileSync(
|
|
549
553
|
this.options.outputPath,
|
|
550
|
-
`${annotationForGeneratedFile}${baseImport}\n${importArray.join('')}
|
|
554
|
+
normEol(`${annotationForGeneratedFile}${baseImport}\n${importArray.join('')}`),
|
|
551
555
|
'utf-8',
|
|
552
556
|
);
|
|
553
557
|
}
|