datagrok-tools 4.14.69 → 4.14.71

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.
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  });
7
7
  exports.check = check;
8
8
  exports.checkChangelog = checkChangelog;
9
+ exports.checkDatagrokApiImports = checkDatagrokApiImports;
9
10
  exports.checkFuncSignatures = checkFuncSignatures;
10
11
  exports.checkImportStatements = checkImportStatements;
11
12
  exports.checkNpmIgnore = checkNpmIgnore;
@@ -18,6 +19,8 @@ var _ignoreWalk = _interopRequireDefault(require("ignore-walk"));
18
19
  var utils = _interopRequireWildcard(require("../utils/utils"));
19
20
  var color = _interopRequireWildcard(require("../utils/color-utils"));
20
21
  var testUtils = _interopRequireWildcard(require("../utils/test-utils"));
22
+ var _const = require("datagrok-api/src/const");
23
+ var _child_process = require("child_process");
21
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); }
22
25
  const warns = ['Latest package version', 'Datagrok API version should contain'];
23
26
  const forbiddenNames = ['function', 'class', 'export'];
@@ -33,7 +36,7 @@ function check(args) {
33
36
  return runChecks(curDir, args.soft ?? false);
34
37
  }
35
38
  }
36
- function runChecks(packagePath, soft = false) {
39
+ function runChecks(packagePath, soft = false, noExit = false) {
37
40
  if (packagePath.includes(`${_path.default.sep}node_modules${_path.default.sep}`)) return true;
38
41
  const files = _ignoreWalk.default.sync({
39
42
  path: packagePath,
@@ -50,8 +53,12 @@ function runChecks(packagePath, soft = false) {
50
53
  }));
51
54
  const webpackConfigPath = _path.default.join(packagePath, 'webpack.config.js');
52
55
  const isWebpack = _fs.default.existsSync(webpackConfigPath);
56
+ const semver = json.version.split('-')[0];
57
+ const [major, minor, patch] = semver.split('.').map(Number);
58
+ let isPre1Version = false;
53
59
  let isReleaseCandidateVersion = false;
54
60
  let externals = null;
61
+ if (major === 0) isPre1Version = true;
55
62
  if (/\d+.\d+.\d+-rc(.[A-Za-z0-9]*.[A-Za-z0-9]*)?/.test(json.version)) isReleaseCandidateVersion = true;
56
63
  if (isWebpack) {
57
64
  const content = _fs.default.readFileSync(webpackConfigPath, {
@@ -60,6 +67,7 @@ function runChecks(packagePath, soft = false) {
60
67
  externals = extractExternals(content);
61
68
  if (externals) errors.push(...checkImportStatements(packagePath, jsTsFiles, externals));
62
69
  }
70
+ errors.push(...checkDatagrokApiImports(packagePath, jsTsFiles));
63
71
  if (!soft) errors.push(...checkSourceMap(packagePath));
64
72
  errors.push(...checkNpmIgnore(packagePath));
65
73
  warnings.push(...checkScriptNames(packagePath));
@@ -71,7 +79,7 @@ function runChecks(packagePath, soft = false) {
71
79
  externals,
72
80
  isReleaseCandidateVersion
73
81
  }));
74
- if (!isReleaseCandidateVersion) warnings.push(...checkChangelog(packagePath, json));
82
+ if (!isReleaseCandidateVersion && !isPre1Version) warnings.push(...checkChangelog(packagePath, json));
75
83
  if (warnings.length) {
76
84
  console.log(`${_path.default.basename(packagePath)} warnings`);
77
85
  warn(warnings);
@@ -80,23 +88,30 @@ function runChecks(packagePath, soft = false) {
80
88
  console.log(`Checking package ${_path.default.basename(packagePath)}...`);
81
89
  showError(errors);
82
90
  if (soft || json.version.startsWith('0') || errors.every(w => warns.some(ww => w.includes(ww)))) return true;
83
- testUtils.exitWithCode(1);
91
+ if (noExit) return false;else testUtils.exitWithCode(1);
84
92
  }
85
93
  console.log(`Checking package ${_path.default.basename(packagePath)}...\t\t\t\u2713 OK`);
86
94
  return true;
87
95
  }
88
96
  function runChecksRec(dir, soft = false) {
89
97
  const files = _fs.default.readdirSync(dir);
98
+ let allPassed = true;
90
99
  for (const file of files) {
91
100
  const filepath = _path.default.join(dir, file);
92
101
  const stats = _fs.default.statSync(filepath);
93
102
  if (stats.isDirectory()) {
94
- if (utils.isPackageDir(filepath)) return runChecks(filepath, soft);else {
95
- if (file !== 'node_modules' && !file.startsWith('.')) runChecksRec(_path.default.join(dir, file), soft);
103
+ if (utils.isPackageDir(filepath)) {
104
+ const passed = runChecks(filepath, soft, true);
105
+ allPassed = allPassed && passed;
106
+ } else {
107
+ if (file !== 'node_modules' && !file.startsWith('.')) {
108
+ const passed = runChecksRec(_path.default.join(dir, file), soft);
109
+ allPassed = allPassed && passed;
110
+ }
96
111
  }
97
112
  }
98
113
  }
99
- return false;
114
+ return allPassed;
100
115
  }
101
116
  function extractExternals(config) {
102
117
  const externalsRegex = /(?<=externals)\s*:\s*(\{[\S\s]*?\})/;
@@ -144,197 +159,174 @@ function checkImportStatements(packagePath, files, externals) {
144
159
  }
145
160
  return warnings;
146
161
  }
147
- function checkFuncSignatures(packagePath, files) {
148
- const warnings = [];
162
+ function checkDatagrokApiImports(packagePath, files) {
149
163
  const errors = [];
150
- const checkFunctions = {
151
- app: ({
152
- name
153
- }) => {
154
- let value = true;
155
- let message = '';
156
- if (name && typeof name === 'string') {
157
- const lowerCaseName = name.toLocaleLowerCase();
158
- if (lowerCaseName.startsWith('app')) {
159
- value = false;
160
- message += 'Prefix "App" is not needed. Consider removing it.\n';
161
- }
162
- if (lowerCaseName.endsWith('app')) {
163
- value = false;
164
- message += 'Postfix "App" is not needed. Consider removing it.\n';
165
- }
166
- }
167
- return {
168
- value,
169
- message
170
- };
171
- },
172
- semTypeDetector: ({
173
- inputs,
174
- outputs
175
- }) => {
176
- let value = true;
177
- let message = '';
178
- if (inputs.length !== 1 || inputs[0].type !== 'column') {
179
- value = false;
180
- message += 'Semantic type detectors must have one input of type "column"\n';
181
- }
182
- if (outputs.length !== 1 || outputs[0].type !== 'string') {
183
- value = false;
184
- message += 'Semantic type detectors must have one output of type "string"\n';
185
- }
186
- return {
187
- value,
188
- message
189
- };
190
- },
191
- cellRenderer: ({
192
- inputs,
193
- outputs
194
- }) => {
195
- let value = true;
196
- let message = '';
197
- if (inputs.length !== 0) {
198
- value = false;
199
- message += 'Cell renderer functions should take no arguments\n';
200
- }
201
- if (outputs.length !== 1 || outputs[0].type !== 'grid_cell_renderer') {
202
- value = false;
203
- message += 'Cell renderer functions must have one output of type "grid_cell_renderer"\n';
204
- }
205
- return {
206
- value,
207
- message
208
- };
209
- },
210
- viewer: ({
211
- inputs,
212
- outputs
213
- }) => {
214
- let value = true;
215
- let message = '';
216
- if (inputs.length !== 0) {
217
- value = false;
218
- message += 'Viewer functions should take no arguments\n';
219
- }
220
- if (outputs.length > 1 || outputs.length === 1 && outputs[0].type !== 'viewer') {
221
- value = false;
222
- message += 'Viewers must have one output of type "viewer"\n';
223
- }
224
- return {
225
- value,
226
- message
227
- };
228
- },
229
- fileViewer: ({
230
- inputs,
231
- outputs,
232
- tags
233
- }) => {
234
- let value = true;
235
- let message = '';
236
- if (tags == null || tags.length !== 1 && tags[0] !== 'fileViewer') {
237
- value = false;
238
- message += 'File viewers must have only one tag: "fileViewer"\n';
239
- }
240
- if (inputs.length !== 1 || inputs[0].type !== 'file') {
241
- value = false;
242
- message += 'File viewers must have one input of type "file"\n';
243
- }
244
- if (outputs.length !== 1 || outputs[0].type !== 'view') {
245
- value = false;
246
- message += 'File viewers must have one output of type "view"\n';
247
- }
248
- return {
249
- value,
250
- message
251
- };
252
- },
253
- fileExporter: ({
254
- description
255
- }) => {
256
- let value = true;
257
- let message = '';
258
- if (description == null || description === '') {
259
- value = false;
260
- message += 'File exporters should have a description parameter\n';
261
- }
262
- return {
263
- value,
264
- message
265
- };
266
- },
267
- packageSettingsEditor: ({
268
- outputs
269
- }) => {
270
- let value = true;
271
- let message = '';
272
- if (!(outputs.length === 1 && outputs[0].type === 'widget')) {
273
- value = false;
274
- message += 'Package settings editors must have one output of type "widget"\n';
275
- }
276
- return {
277
- value,
278
- message
279
- };
280
- },
281
- params: ({
282
- inputs,
283
- outputs
284
- }) => {
285
- let value = true;
286
- let message = '';
287
- for (const input of inputs) {
288
- if (!(input.name && input.type)) {
289
- value = false;
290
- message += `Function has no name or type of input parameter\n`;
164
+
165
+ // Regex to find all datagrok-api imports/exports (including re-exports)
166
+ const datagrokApiImportRegex = /^\s*(import|export)\s+.*['"]datagrok-api\/[^'"]+['"]/gm;
167
+
168
+ // Regex to validate if import/export is allowed (only dg, grok, ui)
169
+ const allowedImportRegex = /^\s*(import|export)\s+.*['"]datagrok-api\/(dg|grok|ui)['"]/;
170
+
171
+ // Regex to extract the import path for error messages
172
+ const importPathRegex = /['"]datagrok-api\/([^'"]+)['"]/;
173
+ for (const file of files) {
174
+ const content = _fs.default.readFileSync(_path.default.join(packagePath, file), {
175
+ encoding: 'utf-8'
176
+ });
177
+ const matchedImports = content.match(datagrokApiImportRegex);
178
+ if (matchedImports) {
179
+ for (const match of matchedImports) {
180
+ // Check if this import/export is allowed
181
+ if (!allowedImportRegex.test(match)) {
182
+ // Extract the problematic path for error message
183
+ const pathMatch = match.match(importPathRegex);
184
+ const importedPath = pathMatch ? `datagrok-api/${pathMatch[1]}` : 'unknown';
185
+ errors.push(`File "${file}": Invalid datagrok-api import.
186
+ ` + ` Found: ${match.trim()}
187
+ ` + ` Only these paths are allowed: 'datagrok-api/dg', 'datagrok-api/grok', 'datagrok-api/ui'
188
+ ` + ` Deep imports like '${importedPath}' are not permitted.`);
291
189
  }
292
190
  }
293
- for (const output of outputs) {
294
- if (!(output.name && output.type)) {
295
- value = false;
296
- message += `Function has no name or type of output parameter\n`;
297
- }
191
+ }
192
+ }
193
+ return errors;
194
+ }
195
+ const TYPE_ALIASES = {
196
+ file: ['fileinfo'],
197
+ dynamic: ['searchprovider']
198
+ };
199
+ function normalizeType(type) {
200
+ return type.toLowerCase().replace(/_/g, '');
201
+ }
202
+ function typesMatch(actual, expected) {
203
+ const a = normalizeType(actual);
204
+ const e = normalizeType(expected);
205
+ if (a === e) return true;
206
+ const aliases = TYPE_ALIASES[a];
207
+ return aliases ? aliases.includes(e) : false;
208
+ }
209
+ function parseSignature(sig) {
210
+ const match = sig.match(/^[^(]+\(([^)]*)\)\s*:\s*(.+)$/);
211
+ if (!match) throw new Error(`Invalid signature format: ${sig}`);
212
+ const paramsStr = match[1].trim();
213
+ const returnTypeStr = match[2].trim();
214
+ let variadicIndex = -1;
215
+ const parseParam = (p, index) => {
216
+ const trimmed = p.trim();
217
+ const isVariadic = trimmed.startsWith('...');
218
+ if (isVariadic) variadicIndex = index;
219
+ const paramBody = isVariadic ? trimmed.slice(3) : trimmed;
220
+ const [name, type] = paramBody.split(':').map(s => s.trim());
221
+ return {
222
+ name,
223
+ type: type || 'any'
224
+ };
225
+ };
226
+ const inputs = paramsStr ? paramsStr.split(',').map((p, i) => parseParam(p, i)) : [];
227
+ const outputs = returnTypeStr ? returnTypeStr.split('|').map(t => ({
228
+ name: '',
229
+ type: t.trim()
230
+ })) : [];
231
+ return {
232
+ inputs,
233
+ outputs,
234
+ variadicIndex
235
+ };
236
+ }
237
+ function validateFunctionSignature(func, roleDesc) {
238
+ let valid = true;
239
+ const messages = [];
240
+ const addError = msg => {
241
+ valid = false;
242
+ messages.push(msg);
243
+ };
244
+ const normalize = t => t?.toLowerCase();
245
+ const fmtParam = p => p ? `${p.name ?? '<unnamed>'}: ${p.type ?? '<unknown>'}` : '<missing>';
246
+ const fmtTypes = types => types.join(' | ');
247
+ const matchesExpected = (actual, expected) => {
248
+ if (!actual || !expected) return false;
249
+ const actualType = normalize(actual);
250
+ const expectedTypes = expected.split('|').map(t => normalize(t.trim()));
251
+ return expectedTypes.some(t => t === 'any' || typesMatch(actualType, t));
252
+ };
253
+ if (roleDesc.role === 'app' && func.name) {
254
+ const name = func.name.toLowerCase();
255
+ if (name.startsWith('app')) addError('Prefix "App" is not needed. Consider removing it.');
256
+ if (name.endsWith('app')) addError('Postfix "App" is not needed. Consider removing it.');
257
+ }
258
+ if (roleDesc.role === 'fileExporter' && (!func.description || func.description === '')) addError('File exporters should have a description parameter');
259
+ if (roleDesc.role === 'fileViewer') {
260
+ const hasFileViewerTag = func.tags?.length === 1 && func.tags[0] === 'fileViewer';
261
+ const hasFileViewerRole = func.meta?.role === 'fileViewer';
262
+ if (!(hasFileViewerTag || hasFileViewerRole)) addError('File viewers must have only one tag: "fileViewer"');
263
+ }
264
+ const parsed = parseSignature(roleDesc.signature);
265
+ const maxInputs = parsed.variadicIndex >= 0 ? parsed.variadicIndex : parsed.inputs.length;
266
+ for (let i = 0; i < maxInputs; i++) {
267
+ const expected = parsed.inputs[i];
268
+ const actual = func.inputs[i];
269
+ if (!actual) {
270
+ addError(`Input ${i} missing: expected ${fmtParam(expected)}`);
271
+ continue;
272
+ }
273
+ if (!matchesExpected(actual.type, expected?.type)) addError(`Input ${i} mismatch: expected ${fmtTypes(expected?.type?.split('|').map(t => t.trim()) ?? [])}, got ${actual.type}`);
274
+ }
275
+ if (parsed.outputs.length > 0) {
276
+ if (!func.outputs?.length) {
277
+ if (!parsed.outputs.some(o => o.type === 'void')) addError(`Output missing: expected one of (${fmtTypes(parsed.outputs.map(o => o.type))})`);
278
+ } else {
279
+ const matches = func.outputs.some(actual => parsed.outputs.some(expected => matchesExpected(actual.type, expected.type)));
280
+ if (!matches) {
281
+ addError(`Output mismatch: expected one of (${fmtTypes(parsed.outputs.map(o => o.type))}),
282
+ got (${fmtTypes(func.outputs.map(o => o.type))})`);
298
283
  }
299
- return {
300
- value,
301
- message
302
- };
303
284
  }
285
+ }
286
+ [...func.inputs, ...(func.outputs ?? [])].forEach((p, i) => {
287
+ if (!p.name || !p.type) addError(`Parameter ${i} is incomplete: name=${p.name ?? '<missing>'}, type=${p.type ?? '<missing>'}`);
288
+ });
289
+ return {
290
+ value: valid,
291
+ message: messages.join('\n')
304
292
  };
305
- const functionRoles = Object.keys(checkFunctions);
293
+ }
294
+ function checkFuncSignatures(packagePath, files) {
295
+ const warnings = [];
296
+ const errors = [];
297
+ const namesInFiles = new Map();
298
+ const roleMap = new Map(_const.functionRoles.map(r => [r.role, r]));
306
299
  for (const file of files) {
307
300
  if (file.includes('.min.')) continue;
308
- const content = _fs.default.readFileSync(_path.default.join(packagePath, file), {
309
- encoding: 'utf-8'
310
- });
301
+ const content = _fs.default.readFileSync(_path.default.join(packagePath, file), 'utf-8');
311
302
  const functions = getFuncMetadata(content, file.split('.').pop() ?? 'ts');
312
303
  for (const f of functions.meta) {
313
- const paramsCheck = checkFunctions.params(f);
314
- if (!paramsCheck.value) warnings.push(`File ${file}, function ${f.name}:\n${paramsCheck.message}`);
315
- const roles = functionRoles.filter(role => f.tags?.includes(role));
316
- if (roles.length > 1) warnings.push(`File ${file}, function ${f.name}: several function roles are used (${roles.join(', ')})`);else if (roles.length === 1) {
317
- const vr = checkFunctions[roles[0]](f);
318
- if (!vr.value) {
319
- warnings.push(`File ${file}, function ${f.name}:\n${vr.message}`);
320
- }
304
+ const allRoles = new Set([...(f.tags ?? []), ...(f.meta?.role?.split(',').map(r => r.trim()) ?? [])]);
305
+ const roles = [...allRoles].filter(r => roleMap.has(r));
306
+ for (const role of roles) {
307
+ const roleDesc = roleMap.get(role);
308
+ const vr = validateFunctionSignature(f, roleDesc);
309
+ if (!vr.value) warnings.push(`File ${file}, function ${f.name}:\n${vr.message}`);
321
310
  }
322
- let wrongInputNames = f.inputs.filter(e => forbiddenNames.includes(e?.name ?? ''));
311
+ const invalidNames = f.inputs.filter(e => forbiddenNames.includes(e?.name ?? ''));
312
+ if (invalidNames.length) errors.push(`File ${file}, function ${f.name}: Wrong input names: (${invalidNames.map(e => e.name).join(', ')})`);
323
313
  if (f.name && f.name !== 'postprocess') {
324
- if (namesInFiles.has(f.name)) namesInFiles.get(f.name)?.push(file);else namesInFiles.set(f.name, [file]);
314
+ if (!namesInFiles.has(f.name)) namesInFiles.set(f.name, []);
315
+ namesInFiles.get(f.name).push(file);
325
316
  }
326
- if (wrongInputNames.length > 0) errors.push(`File ${file}, function ${f.name}: Wrong input names: (${wrongInputNames.map(e => e.name).join(', ')})`);
327
- if (f.isInvalidateOnWithoutCache) errors.push(`File ${file}, function ${f.name}: Can't use invalidateOn without cache, please follow this example: 'meta.cache.invalidateOn'`);
328
- if (f.cache) if (!utils.cacheValues.includes(f.cache)) errors.push(`File ${file}, function ${f.name}: unsupposed variable for cache : ${f.cache}`);
329
- if (f.invalidateOn) if (!utils.isValidCron(f.invalidateOn)) errors.push(`File ${file}, function ${f.name}: unsupposed variable for invalidateOn : ${f.invalidateOn}`);
317
+ if (f.isInvalidateOnWithoutCache) errors.push(`File ${file}, function ${f.name}: Can't use invalidateOn without cache`);
318
+ if (f.cache && !utils.cacheValues.includes(f.cache)) errors.push(`File ${file}, function ${f.name}: unsupported cache variable: ${f.cache}`);
319
+ if (f.invalidateOn && !utils.isValidCron(f.invalidateOn)) errors.push(`File ${file}, function ${f.name}: unsupported invalidateOn: ${f.invalidateOn}`);
330
320
  }
331
- functions.warnings.forEach(e => {
332
- warnings.push(`${e} In the file: ${file}.`);
333
- });
334
- }
335
- for (const [name, files] of namesInFiles) {
336
- if (files.length > 1) errors.push(`Duplicate names ('${name}'): \n ${files.join('\n ')}`);
321
+ functions.warnings.forEach(w => warnings.push(`${w} In the file: ${file}.`));
337
322
  }
323
+
324
+ // Temporarily skip name-checking logic, as duplicate names are allowed
325
+ // for (const [name, files] of namesInFiles) {
326
+ // if (files.length > 1)
327
+ // errors.push(`Duplicate names ('${name}'): \n ${files.join('\n ')}`);
328
+ // }
329
+
338
330
  return [warnings, errors];
339
331
  }
340
332
  const sharedLibExternals = {
@@ -405,14 +397,14 @@ function checkPackageFile(packagePath, json, options) {
405
397
  }
406
398
  if (options?.isReleaseCandidateVersion === true) {
407
399
  let hasRCDependency = false;
408
- for (let dependency of Object.keys(json.dependencies ?? {})) {
400
+ for (const dependency of Object.keys(json.dependencies ?? {})) {
409
401
  if (/\d+.\d+.\d+-rc(.[A-Za-z0-9]*.[A-Za-z0-9]*)?/.test((json.dependencies ?? {})[dependency])) {
410
402
  hasRCDependency = true;
411
403
  break;
412
404
  }
413
405
  }
414
406
  if (!hasRCDependency) {
415
- for (let dependency of Object.keys(json.dependencies ?? {})) {
407
+ for (const dependency of Object.keys(json.dependencies ?? {})) {
416
408
  if (/\d+.\d+.\d+-rc(.[A-Za-z0-9]*.[A-Za-z0-9]*)?/.test((json.devDependencies ?? {})[dependency])) {
417
409
  hasRCDependency = true;
418
410
  break;
@@ -463,8 +455,27 @@ function checkSourceMap(packagePath) {
463
455
  }); // cant convert to json because file contains comments
464
456
 
465
457
  if (!new RegExp(`devtool\\s*:\\s*(([^\\n]*?[^\\n]*source-map[^\\n]*:[^\\n]*source-map[^\\n]*)|('(inline-)?source-map'))\\s*`).test(webpackConfigJson)) warnings.push('webpack config doesnt contain source map');
466
- if (!_fs.default.existsSync(packagePath + '/dist/package.js')) warnings.push('dist\\package.js file doesnt exists');
467
- if (!_fs.default.existsSync(packagePath + '/dist/package-test.js')) warnings.push('dist\\package-test.js file doesnt exists');
458
+
459
+ // Check if dist files exist
460
+ const distPackage = _path.default.join(packagePath, 'dist', 'package.js');
461
+ const distPackageTest = _path.default.join(packagePath, 'dist', 'package-test.js');
462
+ let missingFiles = [distPackage, distPackageTest].filter(f => !_fs.default.existsSync(f));
463
+
464
+ // If any dist files are missing, try to build automatically
465
+ if (missingFiles.length > 0) {
466
+ try {
467
+ (0, _child_process.execSync)('npm run build', {
468
+ cwd: packagePath,
469
+ stdio: 'inherit'
470
+ });
471
+ } catch (e) {
472
+ console.warn('Build failed:', e);
473
+ }
474
+
475
+ // Recheck dist files after build
476
+ missingFiles = [distPackage, distPackageTest].filter(f => !_fs.default.existsSync(f));
477
+ missingFiles.forEach(f => warnings.push(`${_path.default.relative(packagePath, f)} file doesnt exist even after build`));
478
+ }
468
479
  }
469
480
  return warnings;
470
481
  }
@@ -502,9 +513,7 @@ function getAllFilesInDirectory(directoryPath) {
502
513
  entries.forEach(entry => {
503
514
  const entryPath = _path.default.join(directoryPath, entry);
504
515
  const stat = _fs.default.statSync(entryPath);
505
- if (stat.isFile()) {
506
- fileNames.push(entry);
507
- } else if (stat.isDirectory() && !excludedFilesToCheck.includes(entry)) {
516
+ if (stat.isFile()) fileNames.push(entry);else if (stat.isDirectory() && !excludedFilesToCheck.includes(entry)) {
508
517
  const subDirectoryFiles = getAllFilesInDirectory(entryPath);
509
518
  fileNames = fileNames.concat(subDirectoryFiles);
510
519
  }
@@ -517,66 +526,94 @@ function showError(errors) {
517
526
  function warn(warnings) {
518
527
  warnings.forEach(w => color.warn(w));
519
528
  }
520
- function getFuncMetadata(script, fileExtention) {
529
+ function getFuncMetadata(script, fileExtension) {
521
530
  const funcData = [];
522
531
  const warnings = [];
523
- let isHeader = false;
524
- let data = {
525
- name: '',
526
- inputs: [],
527
- outputs: []
528
- };
529
- for (const line of script.split('\n')) {
530
- if (!line) continue;
531
- //@ts-ignore
532
- const match = line.match(utils.fileParamRegex[fileExtention]);
533
- if (match) {
534
- if (!isHeader) isHeader = true;
532
+ const lines = script.split('\n');
533
+ const funcStartRegex = /^\s*function\s+\w+\s*\(|^\s*(export\s+)?(async\s+)?function\s+\w+\s*\(|^\s*(export\s+)?(const|let)\s+\w+\s*=\s*\(.*\)\s*=>/;
534
+ function parseHeaderLines(headerLines) {
535
+ const data = {
536
+ name: '',
537
+ inputs: [],
538
+ outputs: []
539
+ };
540
+ let hasContent = false;
541
+ for (const headerLine of headerLines) {
542
+ const match = headerLine.match(utils.fileParamRegex[fileExtension]);
543
+ if (!match) continue;
535
544
  const param = match[1];
536
- if (!utils.headerTags.includes(param) && !param.includes('meta.')) {
537
- warnings.push(`Unknown header tag: ${param},`);
545
+ if (!utils.headerTags.includes(param) && !param.startsWith('meta.')) {
546
+ warnings.push(`Unknown header tag: ${param}`);
538
547
  continue;
539
548
  }
540
- if (param === 'name') data.name = line.match(utils.nameAnnRegex)?.[2]?.toLocaleLowerCase();else if (param === 'description') data.description = match[2];else if (param === 'input') {
541
- data.inputs.push({
542
- type: match[2],
543
- name: match[3]
544
- });
545
- } else if (param === 'output') data.outputs.push({
546
- type: match[2],
547
- name: match[3]
548
- });else if (param === 'tags') {
549
- data.tags = match.input && match[3] ? match.input.split(':')[1].split(',').map(t => t.trim()) : [match[2]];
550
- } else if (param === 'meta.cache') {
551
- data.cache = line.split(':').pop()?.trim();
552
- } else if (param === 'meta.cache.invalidateOn') {
553
- data.invalidateOn = line.split(':').pop()?.trim();
554
- } else if (param === 'meta.invalidateOn') {
555
- data.isInvalidateOnWithoutCache = true;
549
+ switch (param) {
550
+ case 'name':
551
+ data.name = headerLine.match(utils.nameAnnRegex)?.[2]?.toLocaleLowerCase() || '';
552
+ hasContent = true;
553
+ break;
554
+ case 'description':
555
+ data.description = match[2];
556
+ hasContent = true;
557
+ break;
558
+ case 'input':
559
+ data.inputs.push({
560
+ type: match[2],
561
+ name: match[3]
562
+ });
563
+ hasContent = true;
564
+ break;
565
+ case 'output':
566
+ data.outputs.push({
567
+ type: match[2],
568
+ name: match[3]
569
+ });
570
+ hasContent = true;
571
+ break;
572
+ case 'tags':
573
+ data.tags = match.input && match[3] ? match.input.split(':')[1].split(',').map(t => t.trim()) : [match[2]];
574
+ hasContent = true;
575
+ break;
576
+ case 'meta.role':
577
+ data.meta = data.meta || {};
578
+ data.meta.role = match[2];
579
+ hasContent = true;
580
+ break;
581
+ case 'meta.cache':
582
+ data.cache = headerLine.split(':').pop()?.trim();
583
+ hasContent = true;
584
+ break;
585
+ case 'meta.cache.invalidateOn':
586
+ data.invalidateOn = headerLine.split(':').pop()?.trim();
587
+ hasContent = true;
588
+ break;
589
+ case 'meta.invalidateOn':
590
+ data.isInvalidateOnWithoutCache = true;
591
+ hasContent = true;
592
+ break;
556
593
  }
557
594
  }
558
- if (isHeader) {
559
- const nm = line.match(utils.nameRegex);
560
- if (data.name === '') {
561
- data = {
562
- name: '',
563
- inputs: [],
564
- outputs: []
565
- };
566
- isHeader = false;
567
- continue;
568
- }
569
- if (nm) data.name = nm[1]?.toLocaleLowerCase();
570
- if (data.name && !match) {
571
- funcData.push(data);
572
- data = {
573
- name: '',
574
- inputs: [],
575
- outputs: []
576
- };
577
- isHeader = false;
578
- }
595
+ return hasContent ? data : null;
596
+ }
597
+ let i = 0;
598
+ const scriptHeaderLines = [];
599
+ while (i < lines.length) {
600
+ const line = lines[i].trim();
601
+ if (line.startsWith('//')) scriptHeaderLines.push(line);else if (line === '') break;else break;
602
+ i++;
603
+ }
604
+ const scriptData = parseHeaderLines(scriptHeaderLines);
605
+ if (scriptData) funcData.push(scriptData);
606
+ for (; i < lines.length; i++) {
607
+ const line = lines[i].trim();
608
+ if (!line.match(funcStartRegex)) continue;
609
+ const headerLines = [];
610
+ for (let j = i - 1; j >= 0; j--) {
611
+ const prevLine = lines[j].trim();
612
+ if (!prevLine.startsWith('//')) break;
613
+ headerLines.unshift(prevLine);
579
614
  }
615
+ const funcMeta = parseHeaderLines(headerLines);
616
+ if (funcMeta) funcData.push(funcMeta);
580
617
  }
581
618
  return {
582
619
  meta: funcData,