datagrok-tools 4.1.8 → 4.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # datagrok-tools
1
+ # Datagrok-tools
2
2
 
3
3
  Utility to upload and publish [packages](https://datagrok.ai/help/develop/develop#packages) to Datagrok.
4
4
 
@@ -48,6 +48,7 @@ Read more about package development in [Datagrok's documentation](https://datagr
48
48
  Package name may only include letters, numbers, underscores, or hyphens.
49
49
  Read more about naming conventions [here](https://datagrok.ai/help/develop/develop#naming-conventions).
50
50
  Options:
51
+ - `--eslint` adds a basic configuration for `eslint`
51
52
  - `--ide` adds an IDE-specific configuration for debugging (vscode)
52
53
  - `--ts` creates a TypeScript package template
53
54
  - `add` puts an object template to your package:
@@ -66,6 +67,7 @@ Read more about package development in [Datagrok's documentation](https://datagr
66
67
  we recommend that you postfix them with 'View' and 'Viewer' respectively.
67
68
  Supported languages for scripts are `javascript`, `julia`, `node`, `octave`, `python`, `r`.
68
69
  Available function tags: `panel`, `init`.
70
+ - `api` creates wrapper functions for package scripts. The output is stored in `/src/scripts-api.ts`.
69
71
  - `publish` uploads a package to the specified server (pass either a URL or a server alias from the `config.yaml` file).
70
72
  ```
71
73
  cd <package-name>
@@ -29,9 +29,6 @@ function add(args) {
29
29
  if (!fs.existsSync(packagePath)) return console.log('`package.json` not found');
30
30
  try {
31
31
  const package = JSON.parse(fs.readFileSync(packagePath));
32
- if (package.friendlyName !== curFolder && package.fullName !== curFolder) {
33
- return console.log('The package name differs from the one in `package.json`');
34
- }
35
32
  } catch (error) {
36
33
  console.error(`Error while reading ${packagePath}:`)
37
34
  console.error(error);
@@ -0,0 +1,69 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const utils = require('../utils.js');
4
+ const walk = require('ignore-walk');
5
+
6
+
7
+ function generateWrappers() {
8
+ const curDir = process.cwd();
9
+ const scriptsDir = path.join(curDir, 'scripts');
10
+ if (!fs.existsSync(scriptsDir)) {
11
+ console.log(`Directory ${scriptsDir} not found\n` +
12
+ 'Place your scripts there before running this command');
13
+ return;
14
+ }
15
+
16
+ const files = walk.sync({
17
+ path: './scripts',
18
+ ignoreFiles: ['.npmignore', '.gitignore'],
19
+ });
20
+
21
+ const wrappers = [];
22
+ for (const file of files) {
23
+ let extension = null;
24
+ if (!utils.scriptExtensions.some((ext) => (extension = ext, file.endsWith(ext)))) continue;
25
+
26
+ const filepath = path.join(scriptsDir, file);
27
+ const script = fs.readFileSync(filepath, 'utf8');
28
+ if (!script) continue;
29
+
30
+ const name = utils.getScriptName(script);
31
+ if (!name) continue;
32
+
33
+ let tb = new utils.TemplateBuilder(utils.scriptWrapperTemplate)
34
+ .replace('FUNC_NAME', name)
35
+ .replace('FUNC_NAME_LOWERCASE', name);
36
+
37
+ const packagePath = path.join(curDir, 'package.json');
38
+ const package = JSON.parse(fs.readFileSync(packagePath));
39
+ tb.replace('PACKAGE_NAMESPACE', package.name);
40
+
41
+ const inputs = utils.getScriptInputs(script);
42
+ const outputType = utils.getScriptOutputType(script);
43
+ tb.replace('PARAMS_OBJECT', inputs)
44
+ .replace('TYPED_PARAMS', inputs)
45
+ .replace('OUTPUT_TYPE', outputType);
46
+
47
+ wrappers.push(tb.build());
48
+ }
49
+
50
+ const srcDir = path.join(curDir, 'src');
51
+ let funcFilePath = path.join(fs.existsSync(srcDir) ? srcDir : curDir, 'scripts-api.ts');
52
+ if (fs.existsSync(funcFilePath)) {
53
+ console.log(`The file ${funcFilePath} already exists\nRewriting its contents...`);
54
+ }
55
+
56
+ const sep = '\n';
57
+ fs.writeFileSync(funcFilePath, utils.dgImports + sep + wrappers.join(sep.repeat(2)) + sep, 'utf8');
58
+ }
59
+
60
+ function api(args) {
61
+ const nOptions = Object.keys(args).length - 1;
62
+ if (args['_'].length !== 1 || nOptions > 0) return false;
63
+ generateWrappers();
64
+ return true;
65
+ }
66
+
67
+ module.exports = {
68
+ api: api,
69
+ };
@@ -25,7 +25,7 @@ const confTemplate = yaml.safeLoad(fs.readFileSync(confTemplateDir));
25
25
 
26
26
  let dependencies = [];
27
27
 
28
- function createDirectoryContents(name, config, templateDir, packageDir, ide = '', ts = false) {
28
+ function createDirectoryContents(name, config, templateDir, packageDir, ide = '', ts = false, eslint = false) {
29
29
  const filesToCreate = fs.readdirSync(templateDir);
30
30
 
31
31
  filesToCreate.forEach(file => {
@@ -55,6 +55,19 @@ function createDirectoryContents(name, config, templateDir, packageDir, ide = ''
55
55
  package['scripts'][`release-${name.toLowerCase()}-${server}`] = `grok publish ${server} --rebuild --release`;
56
56
  }
57
57
  if (ts) Object.assign(package.dependencies, { 'ts-loader': 'latest', 'typescript': 'latest' });
58
+ if (eslint) {
59
+ Object.assign(package.devDependencies, {
60
+ 'eslint': 'latest',
61
+ 'eslint-config-google': 'latest',
62
+ }, ts ? {
63
+ '@typescript-eslint/eslint-plugin': 'latest',
64
+ '@typescript-eslint/parser': 'latest',
65
+ } : {});
66
+ Object.assign(package.scripts, {
67
+ 'lint': `eslint src${ts ? ' --ext .ts' : ''}`,
68
+ 'lint-fix': `eslint src${ts ? ' --ext .ts' : ''} --fix`,
69
+ });
70
+ }
58
71
  // Save module names for installation prompt
59
72
  for (let [module, tag] of Object.entries(Object.assign({}, package.dependencies, package.devDependencies)))
60
73
  dependencies.push(`${module}@${tag}`);
@@ -63,6 +76,15 @@ function createDirectoryContents(name, config, templateDir, packageDir, ide = ''
63
76
  if (file === 'package.js' && ts) copyFilePath = path.join(packageDir, 'package.ts');
64
77
  if (file === 'tsconfig.json' && !ts) return false;
65
78
  if (file === 'ts.webpack.config.js') return false;
79
+ if (file === '.eslintrc.json') {
80
+ if (!eslint) return false;
81
+ if (ts) {
82
+ let eslintConf = JSON.parse(contents);
83
+ eslintConf.parser = '@typescript-eslint/parser';
84
+ eslintConf.plugins = ['@typescript-eslint'];
85
+ contents = JSON.stringify(eslintConf, null, '\t');
86
+ }
87
+ }
66
88
  if (file === 'gitignore') {
67
89
  copyFilePath = path.join(packageDir, '.gitignore');
68
90
  if (ts) contents += '\n# Emitted *.js files\nsrc/**/*.js\n';
@@ -80,8 +102,8 @@ function createDirectoryContents(name, config, templateDir, packageDir, ide = ''
80
102
  function create(args) {
81
103
  const nOptions = Object.keys(args).length - 1;
82
104
  const nArgs = args['_'].length;
83
- if (nArgs > 2 || nOptions > 2) return false;
84
- if (nOptions && !Object.keys(args).slice(1).every(op => op == 'ide' || op == 'ts')) return false;
105
+ if (nArgs > 2 || nOptions > 3) return false;
106
+ if (nOptions && !Object.keys(args).slice(1).every(op => ['ide', 'ts', 'eslint'].includes(op))) return false;
85
107
 
86
108
  // Create `config.yaml` if it doesn't exist yet
87
109
  if (!fs.existsSync(grokDir)) fs.mkdirSync(grokDir);
@@ -105,7 +127,7 @@ function create(args) {
105
127
  console.log('The package directory should be empty');
106
128
  return false;
107
129
  }
108
- createDirectoryContents(name, config, templateDir, packageDir, args.ide, args.ts);
130
+ createDirectoryContents(name, config, templateDir, packageDir, args.ide, args.ts, args.eslint);
109
131
  console.log(help.package(name, args.ts));
110
132
  console.log(`\nThe package has the following dependencies:\n${dependencies.join(' ')}\n`);
111
133
 
@@ -40,6 +40,12 @@ Available tags:
40
40
  panel, init
41
41
  `;
42
42
 
43
+ const HELP_API = `
44
+ Usage: grok api
45
+
46
+ Create wrapper functions for package scripts
47
+ `;
48
+
43
49
  const HELP_CONFIG = `
44
50
  Usage: grok config
45
51
 
@@ -62,8 +68,9 @@ grok create <name> Create a package in a folder with the specified name
62
68
  Please note that the package name may only include letters, numbers, underscores, or hyphens
63
69
 
64
70
  Options:
65
- [--ide] [--ts]
71
+ [--eslint] [--ide] [--ts]
66
72
 
73
+ --eslint Add a configuration for eslint
67
74
  --ide Add an IDE-specific configuration for debugging (vscode)
68
75
  --ts Create a TypeScript package
69
76
  `;
@@ -88,6 +95,7 @@ file and converting your scripts in the \`package.json\` file
88
95
 
89
96
  module.exports = {
90
97
  add: HELP_ADD,
98
+ api: HELP_API,
91
99
  config: HELP_CONFIG,
92
100
  create: HELP_CREATE,
93
101
  publish: HELP_PUBLISH,
@@ -9,7 +9,8 @@ const utils = require('../utils.js');
9
9
 
10
10
 
11
11
  module.exports = {
12
- publish: publish
12
+ publish: publish,
13
+ processPackage: processPackage
13
14
  };
14
15
 
15
16
  const grokDir = path.join(os.homedir(), '.grok');
package/bin/grok.js CHANGED
@@ -4,6 +4,7 @@ const help = require('./commands/help');
4
4
 
5
5
  const commands = {
6
6
  add: require('./commands/add').add,
7
+ api: require('./commands/api').api,
7
8
  config: require('./commands/config').config,
8
9
  create: require('./commands/create').create,
9
10
  publish: require('./commands/publish').publish,
package/bin/utils.js CHANGED
@@ -2,9 +2,20 @@ const fs = require('fs');
2
2
 
3
3
  exports.isEmpty = (dir) => fs.readdirSync(dir).length === 0;
4
4
 
5
- exports.kebabToCamelCase = (s) => {
5
+ exports.kebabToCamelCase = (s, firstUpper = true) => {
6
6
  s = s.replace(/-./g, x => x.toUpperCase()[1]);
7
- return s[0].toUpperCase() + s.slice(1);
7
+ return (firstUpper ? s[0].toUpperCase() : s[0].toLowerCase()) + s.slice(1);
8
+ }
9
+
10
+ exports.spaceToCamelCase = (s, firstUpper = true) => {
11
+ s = s.replace(/\s+./g, x => x[x.length - 1].toUpperCase());
12
+ return (firstUpper ? s[0].toUpperCase() : s[0].toLowerCase()) + s.slice(1);
13
+ }
14
+
15
+ exports.wordsToCamelCase = (s, firstUpper = true) => {
16
+ const m = s.match(/^[A-Z]+/); // only abbreviation handling
17
+ const lastIndex = m ? m[0].length : 1;
18
+ return s.slice(0, lastIndex).toLowerCase() + s.slice(lastIndex);
8
19
  }
9
20
 
10
21
  exports.camelCaseToKebab = (s) => {
@@ -25,8 +36,32 @@ exports.replacers = {
25
36
  NAME_LOWERCASE: (s, name) => s.replace(/#{NAME_LOWERCASE}/g, name.toLowerCase()),
26
37
  NAME_PREFIX: (s, name) => s.replace(/#{NAME_PREFIX}/g, name.slice(0, 3)),
27
38
  PACKAGE_DETECTORS_NAME: (s, name) => s.replace(/#{PACKAGE_DETECTORS_NAME}/g, this.kebabToCamelCase(name)),
39
+ PACKAGE_NAMESPACE: (s, name) => s.replace(/#{PACKAGE_NAMESPACE}/g, this.kebabToCamelCase(name)),
40
+ FUNC_NAME: (s, name) => s.replace(/#{FUNC_NAME}/g, name.includes('-') ? this.kebabToCamelCase(name)
41
+ : name.includes(' ') ? this.spaceToCamelCase(name) : name[0].toUpperCase() + name.slice(1)),
42
+ FUNC_NAME_LOWERCASE: (s, name) => s.replace(/#{FUNC_NAME_LOWERCASE}/g, name.includes('-') ?
43
+ this.kebabToCamelCase(name, false) : name.includes(' ') ?
44
+ this.spaceToCamelCase(name, false) : this.wordsToCamelCase(name, false)),
45
+ PARAMS_OBJECT: (s, params) => s.replace(/#{PARAMS_OBJECT}/g, params.length ? `{ ${params.map((p) => p.name).join(', ')} }` : `{}`),
46
+ OUTPUT_TYPE: (s, type) => s.replace(/#{OUTPUT_TYPE}/g, type),
47
+ TYPED_PARAMS: (s, params) => s.replace(/#{TYPED_PARAMS}/g, params.map((p) => `${p.name}: ${p.type}`).join(', '))
28
48
  };
29
49
 
50
+ exports.TemplateBuilder = class TemplateBuilder {
51
+ constructor(template) {
52
+ this.template = template;
53
+ }
54
+
55
+ replace(pattern, value) {
56
+ this.template = exports.replacers[pattern](this.template, value);
57
+ return this;
58
+ }
59
+
60
+ build() {
61
+ return this.template;
62
+ }
63
+ }
64
+
30
65
  exports.scriptLangExtMap = {
31
66
  javascript: 'js',
32
67
  julia: 'jl',
@@ -44,3 +79,51 @@ exports.checkScriptLocation = (filepath) => {
44
79
  }
45
80
  return true;
46
81
  };
82
+
83
+ exports.getScriptName = (script, comment = '#') => {
84
+ const regex = new RegExp(`${comment}\\s*name:\\s*(.*)`);
85
+ const match = script.match(regex);
86
+ return match ? match[1]?.trim() : null;
87
+ };
88
+
89
+ exports.dgToTsTypeMap = {
90
+ int: 'number',
91
+ double: 'number',
92
+ bigint: 'bigint',
93
+ bool: 'boolean',
94
+ string: 'string',
95
+ dataframe: 'DG.DataFrame',
96
+ column: 'DG.Column',
97
+ column_list: 'string[]',
98
+ file: 'DG.FileInfo',
99
+ // graphics: '',
100
+ // datetime: '',
101
+ // blob: '',
102
+ // map: '',
103
+ // list: '',
104
+ //'list<T>'
105
+ };
106
+
107
+ exports.getScriptOutputType = (script, comment = '#') => {
108
+ const regex = new RegExp(`${comment}\\s*output:\\s?([a-z_]+)\\s*`);
109
+ const match = script.match(regex);
110
+ if (!match) return 'void';
111
+ return this.dgToTsTypeMap[match[1]] || 'any';
112
+ };
113
+
114
+ exports.getScriptInputs = (script, comment = '#') => {
115
+ const regex = new RegExp(`${comment}\\s*input:\\s?([a-z_]+)\\s+(\\w+)`, 'g');
116
+ const inputs = [];
117
+ for (let match of script.matchAll(regex)) {
118
+ const type = this.dgToTsTypeMap[match[1]] || 'any';
119
+ const name = match[2];
120
+ inputs.push({ type, name });
121
+ }
122
+ return inputs;
123
+ };
124
+
125
+ exports.dgImports = `import * as grok from 'datagrok-api/grok';\nimport * as DG from 'datagrok-api/dg';\n\n`;
126
+
127
+ exports.scriptWrapperTemplate = `export async function #{FUNC_NAME_LOWERCASE}(#{TYPED_PARAMS}): Promise<#{OUTPUT_TYPE}> {
128
+ return await grok.functions.call('#{PACKAGE_NAMESPACE}:#{FUNC_NAME}', #{PARAMS_OBJECT});
129
+ }`;
@@ -0,0 +1,25 @@
1
+ {
2
+ "env": {
3
+ "browser": true,
4
+ "es2021": true
5
+ },
6
+ "extends": [
7
+ "google"
8
+ ],
9
+ "parserOptions": {
10
+ "ecmaVersion": 12,
11
+ "sourceType": "module"
12
+ },
13
+ "rules": {
14
+ "indent": [
15
+ "error",
16
+ 2
17
+ ],
18
+ "max-len": [
19
+ "error",
20
+ 120
21
+ ],
22
+ "require-jsdoc": "off",
23
+ "spaced-comment": "off"
24
+ }
25
+ }
@@ -1,11 +1,11 @@
1
- /* Do not change these import lines to match external modules in webpack configuration */
2
- import * as grok from 'datagrok-api/grok';
3
- import * as ui from 'datagrok-api/ui';
4
- import * as DG from 'datagrok-api/dg';
5
-
6
- export let _package = new DG.Package();
7
-
8
- //name: test
9
- export function test() {
10
- grok.shell.info(_package.webRoot);
11
- }
1
+ /* Do not change these import lines to match external modules in webpack configuration */
2
+ import * as grok from 'datagrok-api/grok';
3
+ import * as ui from 'datagrok-api/ui';
4
+ import * as DG from 'datagrok-api/dg';
5
+
6
+ export const _package = new DG.Package();
7
+
8
+ //name: test
9
+ export function test() {
10
+ grok.shell.info(_package.webRoot);
11
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "datagrok-tools",
3
- "version": "4.1.8",
3
+ "version": "4.1.12",
4
4
  "description": "Utility to upload and publish packages to Datagrok",
5
5
  "homepage": "https://github.com/datagrok-ai/public/tree/master/tools#readme",
6
6
  "dependencies": {
@@ -0,0 +1,74 @@
1
+ import path from "path";
2
+ import os from "os";
3
+ import fs from "fs";
4
+ // @ts-ignore
5
+ import * as yaml from 'js-yaml';
6
+ // @ts-ignore
7
+ import * as utils from '../bin/utils'
8
+
9
+ export async function getToken(devKey: string) {
10
+ return 'GGuDtdZ1Qu3sHOwscchQdl2ozmzXwzcjJ52VByCyYqOQS6v5XzMHcLtOs8ehgqJeWlMkU48AXWkLwEkx2L516gRoRzXBQd5oT3784YyeysOP1za9BebKpJ3ZxsQAb1SJ';
11
+ }
12
+
13
+ const grokDir = path.join(os.homedir(), '.grok');
14
+ const confPath = path.join(grokDir, 'config.yaml');
15
+
16
+ function mapURL(conf: object):object {
17
+ let urls = {};
18
+ // @ts-ignore
19
+ for (let server in conf.servers) {
20
+ // @ts-ignore
21
+ urls[conf['servers'][server]['url']] = conf['servers'][server];
22
+ }
23
+ return urls;
24
+ }
25
+
26
+ export function getDevKey(hostKey: string): {url: string, key: string} {
27
+ let config = yaml.safeLoad(fs.readFileSync(confPath));
28
+ let host = hostKey == '' ? config.default : hostKey;
29
+ host = host.trim();
30
+ let urls = mapURL(config);
31
+ let key = '';
32
+ let url = '';
33
+ try {
34
+ let url = new URL(host).href;
35
+ if (url.endsWith('/')) url = url.slice(0, -1);
36
+ if (url in urls)
37
+ { // @ts-ignore
38
+ key = config['servers'][urls[url]]['key'];
39
+ }
40
+ } catch (error) {
41
+ if (config['servers'][host] == null)
42
+ throw `Unknown server alias. Please add it to ${confPath}`;
43
+ url = config['servers'][host]['url'];
44
+ key = config['servers'][host]['key'];
45
+ }
46
+ return {url, key};
47
+ }
48
+
49
+ export async function getBrowserPage(puppeteer: any): Promise<{browser: any, page: any}> {
50
+ let url:string = process.env.HOST ?? '';
51
+ let cfg = getDevKey(url);
52
+ url = cfg.url;
53
+ if (url.endsWith('/api'))
54
+ url = url.slice(0, -4);
55
+ let key = cfg.key;
56
+ let browser = await puppeteer.launch({
57
+ args: ['--disable-dev-shm-usage', '--disable-features=site-per-process'],
58
+ ignoreHTTPSErrors: true,
59
+ });
60
+ let page = await browser.newPage();
61
+ let token = await getToken(key);
62
+ await page.goto(`${url}/api/info/server`);
63
+ await page.setCookie({name: 'auth', value: token});
64
+ await page.evaluate((token: any) => {
65
+ window.localStorage.setItem('auth', token);
66
+ }, token);
67
+ await page.goto(url);
68
+ try {
69
+ await page.waitForSelector('.grok-view');
70
+ } catch (error) {
71
+ throw 'Can`t load the page';
72
+ }
73
+ return {browser, page};
74
+ }