datagrok-tools 4.14.68 → 4.14.70

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.
@@ -18,6 +18,8 @@ var _ignoreWalk = _interopRequireDefault(require("ignore-walk"));
18
18
  var utils = _interopRequireWildcard(require("../utils/utils"));
19
19
  var color = _interopRequireWildcard(require("../utils/color-utils"));
20
20
  var testUtils = _interopRequireWildcard(require("../utils/test-utils"));
21
+ var _const = require("datagrok-api/src/const");
22
+ var _child_process = require("child_process");
21
23
  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
24
  const warns = ['Latest package version', 'Datagrok API version should contain'];
23
25
  const forbiddenNames = ['function', 'class', 'export'];
@@ -33,7 +35,7 @@ function check(args) {
33
35
  return runChecks(curDir, args.soft ?? false);
34
36
  }
35
37
  }
36
- function runChecks(packagePath, soft = false) {
38
+ function runChecks(packagePath, soft = false, noExit = false) {
37
39
  if (packagePath.includes(`${_path.default.sep}node_modules${_path.default.sep}`)) return true;
38
40
  const files = _ignoreWalk.default.sync({
39
41
  path: packagePath,
@@ -50,8 +52,12 @@ function runChecks(packagePath, soft = false) {
50
52
  }));
51
53
  const webpackConfigPath = _path.default.join(packagePath, 'webpack.config.js');
52
54
  const isWebpack = _fs.default.existsSync(webpackConfigPath);
55
+ const semver = json.version.split('-')[0];
56
+ const [major, minor, patch] = semver.split('.').map(Number);
57
+ let isPre1Version = false;
53
58
  let isReleaseCandidateVersion = false;
54
59
  let externals = null;
60
+ if (major === 0) isPre1Version = true;
55
61
  if (/\d+.\d+.\d+-rc(.[A-Za-z0-9]*.[A-Za-z0-9]*)?/.test(json.version)) isReleaseCandidateVersion = true;
56
62
  if (isWebpack) {
57
63
  const content = _fs.default.readFileSync(webpackConfigPath, {
@@ -71,7 +77,7 @@ function runChecks(packagePath, soft = false) {
71
77
  externals,
72
78
  isReleaseCandidateVersion
73
79
  }));
74
- if (!isReleaseCandidateVersion) warnings.push(...checkChangelog(packagePath, json));
80
+ if (!isReleaseCandidateVersion && !isPre1Version) warnings.push(...checkChangelog(packagePath, json));
75
81
  if (warnings.length) {
76
82
  console.log(`${_path.default.basename(packagePath)} warnings`);
77
83
  warn(warnings);
@@ -80,23 +86,30 @@ function runChecks(packagePath, soft = false) {
80
86
  console.log(`Checking package ${_path.default.basename(packagePath)}...`);
81
87
  showError(errors);
82
88
  if (soft || json.version.startsWith('0') || errors.every(w => warns.some(ww => w.includes(ww)))) return true;
83
- testUtils.exitWithCode(1);
89
+ if (noExit) return false;else testUtils.exitWithCode(1);
84
90
  }
85
91
  console.log(`Checking package ${_path.default.basename(packagePath)}...\t\t\t\u2713 OK`);
86
92
  return true;
87
93
  }
88
94
  function runChecksRec(dir, soft = false) {
89
95
  const files = _fs.default.readdirSync(dir);
96
+ let allPassed = true;
90
97
  for (const file of files) {
91
98
  const filepath = _path.default.join(dir, file);
92
99
  const stats = _fs.default.statSync(filepath);
93
100
  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);
101
+ if (utils.isPackageDir(filepath)) {
102
+ const passed = runChecks(filepath, soft, true);
103
+ allPassed = allPassed && passed;
104
+ } else {
105
+ if (file !== 'node_modules' && !file.startsWith('.')) {
106
+ const passed = runChecksRec(_path.default.join(dir, file), soft);
107
+ allPassed = allPassed && passed;
108
+ }
96
109
  }
97
110
  }
98
111
  }
99
- return false;
112
+ return allPassed;
100
113
  }
101
114
  function extractExternals(config) {
102
115
  const externalsRegex = /(?<=externals)\s*:\s*(\{[\S\s]*?\})/;
@@ -144,197 +157,141 @@ function checkImportStatements(packagePath, files, externals) {
144
157
  }
145
158
  return warnings;
146
159
  }
147
- function checkFuncSignatures(packagePath, files) {
148
- const warnings = [];
149
- 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`;
291
- }
292
- }
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
- }
160
+ const TYPE_ALIASES = {
161
+ file: ['fileinfo'],
162
+ dynamic: ['searchprovider']
163
+ };
164
+ function normalizeType(type) {
165
+ return type.toLowerCase().replace(/_/g, '');
166
+ }
167
+ function typesMatch(actual, expected) {
168
+ const a = normalizeType(actual);
169
+ const e = normalizeType(expected);
170
+ if (a === e) return true;
171
+ const aliases = TYPE_ALIASES[a];
172
+ return aliases ? aliases.includes(e) : false;
173
+ }
174
+ function parseSignature(sig) {
175
+ const match = sig.match(/^[^(]+\(([^)]*)\)\s*:\s*(.+)$/);
176
+ if (!match) throw new Error(`Invalid signature format: ${sig}`);
177
+ const paramsStr = match[1].trim();
178
+ const returnTypeStr = match[2].trim();
179
+ let variadicIndex = -1;
180
+ const parseParam = (p, index) => {
181
+ const trimmed = p.trim();
182
+ const isVariadic = trimmed.startsWith('...');
183
+ if (isVariadic) variadicIndex = index;
184
+ const paramBody = isVariadic ? trimmed.slice(3) : trimmed;
185
+ const [name, type] = paramBody.split(':').map(s => s.trim());
186
+ return {
187
+ name,
188
+ type: type || 'any'
189
+ };
190
+ };
191
+ const inputs = paramsStr ? paramsStr.split(',').map((p, i) => parseParam(p, i)) : [];
192
+ const outputs = returnTypeStr ? returnTypeStr.split('|').map(t => ({
193
+ name: '',
194
+ type: t.trim()
195
+ })) : [];
196
+ return {
197
+ inputs,
198
+ outputs,
199
+ variadicIndex
200
+ };
201
+ }
202
+ function validateFunctionSignature(func, roleDesc) {
203
+ let valid = true;
204
+ const messages = [];
205
+ const addError = msg => {
206
+ valid = false;
207
+ messages.push(msg);
208
+ };
209
+ const normalize = t => t?.toLowerCase();
210
+ const fmtParam = p => p ? `${p.name ?? '<unnamed>'}: ${p.type ?? '<unknown>'}` : '<missing>';
211
+ const fmtTypes = types => types.join(' | ');
212
+ const matchesExpected = (actual, expected) => {
213
+ if (!actual || !expected) return false;
214
+ const actualType = normalize(actual);
215
+ const expectedTypes = expected.split('|').map(t => normalize(t.trim()));
216
+ return expectedTypes.some(t => t === 'any' || typesMatch(actualType, t));
217
+ };
218
+ if (roleDesc.role === 'app' && func.name) {
219
+ const name = func.name.toLowerCase();
220
+ if (name.startsWith('app')) addError('Prefix "App" is not needed. Consider removing it.');
221
+ if (name.endsWith('app')) addError('Postfix "App" is not needed. Consider removing it.');
222
+ }
223
+ if (roleDesc.role === 'fileExporter' && (!func.description || func.description === '')) addError('File exporters should have a description parameter');
224
+ if (roleDesc.role === 'fileViewer') {
225
+ const hasFileViewerTag = func.tags?.length === 1 && func.tags[0] === 'fileViewer';
226
+ const hasFileViewerRole = func.meta?.role === 'fileViewer';
227
+ if (!(hasFileViewerTag || hasFileViewerRole)) addError('File viewers must have only one tag: "fileViewer"');
228
+ }
229
+ const parsed = parseSignature(roleDesc.signature);
230
+ const maxInputs = parsed.variadicIndex >= 0 ? parsed.variadicIndex : parsed.inputs.length;
231
+ for (let i = 0; i < maxInputs; i++) {
232
+ const expected = parsed.inputs[i];
233
+ const actual = func.inputs[i];
234
+ if (!actual) {
235
+ addError(`Input ${i} missing: expected ${fmtParam(expected)}`);
236
+ continue;
237
+ }
238
+ if (!matchesExpected(actual.type, expected?.type)) addError(`Input ${i} mismatch: expected ${fmtTypes(expected?.type?.split('|').map(t => t.trim()) ?? [])}, got ${actual.type}`);
239
+ }
240
+ if (parsed.outputs.length > 0) {
241
+ if (!func.outputs?.length) {
242
+ if (!parsed.outputs.some(o => o.type === 'void')) addError(`Output missing: expected one of (${fmtTypes(parsed.outputs.map(o => o.type))})`);
243
+ } else {
244
+ const matches = func.outputs.some(actual => parsed.outputs.some(expected => matchesExpected(actual.type, expected.type)));
245
+ if (!matches) {
246
+ addError(`Output mismatch: expected one of (${fmtTypes(parsed.outputs.map(o => o.type))}),
247
+ got (${fmtTypes(func.outputs.map(o => o.type))})`);
298
248
  }
299
- return {
300
- value,
301
- message
302
- };
303
249
  }
250
+ }
251
+ [...func.inputs, ...(func.outputs ?? [])].forEach((p, i) => {
252
+ if (!p.name || !p.type) addError(`Parameter ${i} is incomplete: name=${p.name ?? '<missing>'}, type=${p.type ?? '<missing>'}`);
253
+ });
254
+ return {
255
+ value: valid,
256
+ message: messages.join('\n')
304
257
  };
305
- const functionRoles = Object.keys(checkFunctions);
258
+ }
259
+ function checkFuncSignatures(packagePath, files) {
260
+ const warnings = [];
261
+ const errors = [];
262
+ const namesInFiles = new Map();
263
+ const roleMap = new Map(_const.functionRoles.map(r => [r.role, r]));
306
264
  for (const file of files) {
307
265
  if (file.includes('.min.')) continue;
308
- const content = _fs.default.readFileSync(_path.default.join(packagePath, file), {
309
- encoding: 'utf-8'
310
- });
266
+ const content = _fs.default.readFileSync(_path.default.join(packagePath, file), 'utf-8');
311
267
  const functions = getFuncMetadata(content, file.split('.').pop() ?? 'ts');
312
268
  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
- }
269
+ const allRoles = new Set([...(f.tags ?? []), ...(f.meta?.role?.split(',').map(r => r.trim()) ?? [])]);
270
+ const roles = [...allRoles].filter(r => roleMap.has(r));
271
+ for (const role of roles) {
272
+ const roleDesc = roleMap.get(role);
273
+ const vr = validateFunctionSignature(f, roleDesc);
274
+ if (!vr.value) warnings.push(`File ${file}, function ${f.name}:\n${vr.message}`);
321
275
  }
322
- let wrongInputNames = f.inputs.filter(e => forbiddenNames.includes(e?.name ?? ''));
276
+ const invalidNames = f.inputs.filter(e => forbiddenNames.includes(e?.name ?? ''));
277
+ if (invalidNames.length) errors.push(`File ${file}, function ${f.name}: Wrong input names: (${invalidNames.map(e => e.name).join(', ')})`);
323
278
  if (f.name && f.name !== 'postprocess') {
324
- if (namesInFiles.has(f.name)) namesInFiles.get(f.name)?.push(file);else namesInFiles.set(f.name, [file]);
279
+ if (!namesInFiles.has(f.name)) namesInFiles.set(f.name, []);
280
+ namesInFiles.get(f.name).push(file);
325
281
  }
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}`);
282
+ if (f.isInvalidateOnWithoutCache) errors.push(`File ${file}, function ${f.name}: Can't use invalidateOn without cache`);
283
+ if (f.cache && !utils.cacheValues.includes(f.cache)) errors.push(`File ${file}, function ${f.name}: unsupported cache variable: ${f.cache}`);
284
+ if (f.invalidateOn && !utils.isValidCron(f.invalidateOn)) errors.push(`File ${file}, function ${f.name}: unsupported invalidateOn: ${f.invalidateOn}`);
330
285
  }
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 ')}`);
286
+ functions.warnings.forEach(w => warnings.push(`${w} In the file: ${file}.`));
337
287
  }
288
+
289
+ // Temporarily skip name-checking logic, as duplicate names are allowed
290
+ // for (const [name, files] of namesInFiles) {
291
+ // if (files.length > 1)
292
+ // errors.push(`Duplicate names ('${name}'): \n ${files.join('\n ')}`);
293
+ // }
294
+
338
295
  return [warnings, errors];
339
296
  }
340
297
  const sharedLibExternals = {
@@ -405,14 +362,14 @@ function checkPackageFile(packagePath, json, options) {
405
362
  }
406
363
  if (options?.isReleaseCandidateVersion === true) {
407
364
  let hasRCDependency = false;
408
- for (let dependency of Object.keys(json.dependencies ?? {})) {
365
+ for (const dependency of Object.keys(json.dependencies ?? {})) {
409
366
  if (/\d+.\d+.\d+-rc(.[A-Za-z0-9]*.[A-Za-z0-9]*)?/.test((json.dependencies ?? {})[dependency])) {
410
367
  hasRCDependency = true;
411
368
  break;
412
369
  }
413
370
  }
414
371
  if (!hasRCDependency) {
415
- for (let dependency of Object.keys(json.dependencies ?? {})) {
372
+ for (const dependency of Object.keys(json.dependencies ?? {})) {
416
373
  if (/\d+.\d+.\d+-rc(.[A-Za-z0-9]*.[A-Za-z0-9]*)?/.test((json.devDependencies ?? {})[dependency])) {
417
374
  hasRCDependency = true;
418
375
  break;
@@ -463,8 +420,27 @@ function checkSourceMap(packagePath) {
463
420
  }); // cant convert to json because file contains comments
464
421
 
465
422
  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');
423
+
424
+ // Check if dist files exist
425
+ const distPackage = _path.default.join(packagePath, 'dist', 'package.js');
426
+ const distPackageTest = _path.default.join(packagePath, 'dist', 'package-test.js');
427
+ let missingFiles = [distPackage, distPackageTest].filter(f => !_fs.default.existsSync(f));
428
+
429
+ // If any dist files are missing, try to build automatically
430
+ if (missingFiles.length > 0) {
431
+ try {
432
+ (0, _child_process.execSync)('npm run build', {
433
+ cwd: packagePath,
434
+ stdio: 'inherit'
435
+ });
436
+ } catch (e) {
437
+ console.warn('Build failed:', e);
438
+ }
439
+
440
+ // Recheck dist files after build
441
+ missingFiles = [distPackage, distPackageTest].filter(f => !_fs.default.existsSync(f));
442
+ missingFiles.forEach(f => warnings.push(`${_path.default.relative(packagePath, f)} file doesnt exist even after build`));
443
+ }
468
444
  }
469
445
  return warnings;
470
446
  }
@@ -502,9 +478,7 @@ function getAllFilesInDirectory(directoryPath) {
502
478
  entries.forEach(entry => {
503
479
  const entryPath = _path.default.join(directoryPath, entry);
504
480
  const stat = _fs.default.statSync(entryPath);
505
- if (stat.isFile()) {
506
- fileNames.push(entry);
507
- } else if (stat.isDirectory() && !excludedFilesToCheck.includes(entry)) {
481
+ if (stat.isFile()) fileNames.push(entry);else if (stat.isDirectory() && !excludedFilesToCheck.includes(entry)) {
508
482
  const subDirectoryFiles = getAllFilesInDirectory(entryPath);
509
483
  fileNames = fileNames.concat(subDirectoryFiles);
510
484
  }
@@ -517,66 +491,94 @@ function showError(errors) {
517
491
  function warn(warnings) {
518
492
  warnings.forEach(w => color.warn(w));
519
493
  }
520
- function getFuncMetadata(script, fileExtention) {
494
+ function getFuncMetadata(script, fileExtension) {
521
495
  const funcData = [];
522
496
  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;
497
+ const lines = script.split('\n');
498
+ 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*=>/;
499
+ function parseHeaderLines(headerLines) {
500
+ const data = {
501
+ name: '',
502
+ inputs: [],
503
+ outputs: []
504
+ };
505
+ let hasContent = false;
506
+ for (const headerLine of headerLines) {
507
+ const match = headerLine.match(utils.fileParamRegex[fileExtension]);
508
+ if (!match) continue;
535
509
  const param = match[1];
536
- if (!utils.headerTags.includes(param) && !param.includes('meta.')) {
537
- warnings.push(`Unknown header tag: ${param},`);
510
+ if (!utils.headerTags.includes(param) && !param.startsWith('meta.')) {
511
+ warnings.push(`Unknown header tag: ${param}`);
538
512
  continue;
539
513
  }
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;
514
+ switch (param) {
515
+ case 'name':
516
+ data.name = headerLine.match(utils.nameAnnRegex)?.[2]?.toLocaleLowerCase() || '';
517
+ hasContent = true;
518
+ break;
519
+ case 'description':
520
+ data.description = match[2];
521
+ hasContent = true;
522
+ break;
523
+ case 'input':
524
+ data.inputs.push({
525
+ type: match[2],
526
+ name: match[3]
527
+ });
528
+ hasContent = true;
529
+ break;
530
+ case 'output':
531
+ data.outputs.push({
532
+ type: match[2],
533
+ name: match[3]
534
+ });
535
+ hasContent = true;
536
+ break;
537
+ case 'tags':
538
+ data.tags = match.input && match[3] ? match.input.split(':')[1].split(',').map(t => t.trim()) : [match[2]];
539
+ hasContent = true;
540
+ break;
541
+ case 'meta.role':
542
+ data.meta = data.meta || {};
543
+ data.meta.role = match[2];
544
+ hasContent = true;
545
+ break;
546
+ case 'meta.cache':
547
+ data.cache = headerLine.split(':').pop()?.trim();
548
+ hasContent = true;
549
+ break;
550
+ case 'meta.cache.invalidateOn':
551
+ data.invalidateOn = headerLine.split(':').pop()?.trim();
552
+ hasContent = true;
553
+ break;
554
+ case 'meta.invalidateOn':
555
+ data.isInvalidateOnWithoutCache = true;
556
+ hasContent = true;
557
+ break;
556
558
  }
557
559
  }
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
- }
560
+ return hasContent ? data : null;
561
+ }
562
+ let i = 0;
563
+ const scriptHeaderLines = [];
564
+ while (i < lines.length) {
565
+ const line = lines[i].trim();
566
+ if (line.startsWith('//')) scriptHeaderLines.push(line);else if (line === '') break;else break;
567
+ i++;
568
+ }
569
+ const scriptData = parseHeaderLines(scriptHeaderLines);
570
+ if (scriptData) funcData.push(scriptData);
571
+ for (; i < lines.length; i++) {
572
+ const line = lines[i].trim();
573
+ if (!line.match(funcStartRegex)) continue;
574
+ const headerLines = [];
575
+ for (let j = i - 1; j >= 0; j--) {
576
+ const prevLine = lines[j].trim();
577
+ if (!prevLine.startsWith('//')) break;
578
+ headerLines.unshift(prevLine);
579
579
  }
580
+ const funcMeta = parseHeaderLines(headerLines);
581
+ if (funcMeta) funcData.push(funcMeta);
580
582
  }
581
583
  return {
582
584
  meta: funcData,
@@ -20,6 +20,7 @@ Commands:
20
20
  publish Upload a package
21
21
  test Run package tests
22
22
  testall Run packages tests
23
+ migrate Migrate legacy tags to meta.role
23
24
 
24
25
  To get help on a particular command, use:
25
26
  grok <command> --help
@@ -209,6 +210,16 @@ Options:
209
210
  --verbose Prints detailed information about linked packages
210
211
  --all Links all available packages(run in packages directory)
211
212
  `;
213
+ const HELP_MIGRATE = `
214
+ Usage: grok migrate
215
+
216
+ Migrates legacy function tags into the meta.role field.
217
+
218
+ Example:
219
+ tags: ['viewer', 'ml']
220
+
221
+ meta: { role: 'viewer,ml' }
222
+ `;
212
223
 
213
224
  // const HELP_MIGRATE = `
214
225
  // Usage: grok migrate
@@ -228,5 +239,6 @@ const help = exports.help = {
228
239
  publish: HELP_PUBLISH,
229
240
  test: HELP_TEST,
230
241
  testall: HELP_TESTALL,
242
+ migrate: HELP_MIGRATE,
231
243
  help: HELP
232
244
  };