datagrok-tools 4.14.6 → 4.14.8
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/CHANGELOG.md +9 -0
- package/bin/commands/api.js +25 -19
- package/bin/commands/check.js +20 -13
- package/bin/commands/publish.js +22 -17
- package/bin/utils/utils.js +7 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Datagrok-tools changelog
|
|
2
2
|
|
|
3
|
+
## 4.14.6 (2025-06-06)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
* Grok Check added collision checks
|
|
8
|
+
* Grok Publish runs check before invocation
|
|
9
|
+
* Grok Api creates api for the scripts in the python directory.
|
|
10
|
+
|
|
11
|
+
|
|
3
12
|
## 4.14.6 (2025-05-29)
|
|
4
13
|
|
|
5
14
|
### Features
|
package/bin/commands/api.js
CHANGED
|
@@ -55,31 +55,37 @@ function generateQueryWrappers() {
|
|
|
55
55
|
}
|
|
56
56
|
function generateScriptWrappers() {
|
|
57
57
|
const scriptsDir = _path.default.join(curDir, 'scripts');
|
|
58
|
+
const pythonDir = _fs.default.existsSync(srcDir) ? _path.default.join(srcDir, 'python') : _path.default.join(curDir, 'python');
|
|
58
59
|
if (!_fs.default.existsSync(scriptsDir)) {
|
|
59
60
|
color.warn(`Directory ${scriptsDir} not found`);
|
|
60
61
|
console.log('Skipping API generation for scripts...');
|
|
61
62
|
return;
|
|
62
63
|
}
|
|
63
|
-
const files = _ignoreWalk.default.sync({
|
|
64
|
-
path: scriptsDir,
|
|
65
|
-
ignoreFiles: ['.npmignore', '.gitignore']
|
|
66
|
-
});
|
|
67
64
|
const wrappers = [];
|
|
68
|
-
for (
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
65
|
+
for (let dir of [scriptsDir, pythonDir]) {
|
|
66
|
+
const files = _ignoreWalk.default.sync({
|
|
67
|
+
path: dir,
|
|
68
|
+
ignoreFiles: ['.npmignore', '.gitignore']
|
|
69
|
+
});
|
|
70
|
+
console.log(files);
|
|
71
|
+
for (const file of files) {
|
|
72
|
+
let extension;
|
|
73
|
+
if (!utils.scriptExtensions.some(ext => (extension = ext, file.endsWith(ext)))) continue;
|
|
74
|
+
const filepath = _path.default.join(dir, file);
|
|
75
|
+
const script = _fs.default.readFileSync(filepath, 'utf8');
|
|
76
|
+
if (!script) continue;
|
|
77
|
+
console.log(filepath);
|
|
78
|
+
console.log(script);
|
|
79
|
+
const name = utils.getScriptName(script, utils.commentMap[extension]);
|
|
80
|
+
if (!name) continue;
|
|
81
|
+
const description = utils.getScriptDescription(script);
|
|
82
|
+
checkNameColision(name);
|
|
83
|
+
const tb = new utils.TemplateBuilder(utils.scriptWrapperTemplate).replace('FUNC_NAME', name).replace('FUNC_NAME_LOWERCASE', name).replace('PACKAGE_NAMESPACE', _package.name);
|
|
84
|
+
const inputs = utils.getScriptInputs(script);
|
|
85
|
+
const outputType = utils.getScriptOutputType(script);
|
|
86
|
+
tb.replace('PARAMS_OBJECT', inputs).replace('TYPED_PARAMS', inputs).replace('FUNC_DESCRIPTION', description).replace('OUTPUT_TYPE', outputType);
|
|
87
|
+
wrappers.push(tb.build(1));
|
|
88
|
+
}
|
|
83
89
|
}
|
|
84
90
|
saveWrappersToFile('scripts', wrappers);
|
|
85
91
|
}
|
package/bin/commands/check.js
CHANGED
|
@@ -21,20 +21,21 @@ var testUtils = _interopRequireWildcard(require("../utils/test-utils"));
|
|
|
21
21
|
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
22
22
|
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
|
23
23
|
const warns = ['Latest package version', 'Datagrok API version should contain'];
|
|
24
|
-
const
|
|
24
|
+
const forbiddenNames = ['function', 'class', 'export'];
|
|
25
|
+
const namesInFiles = new Map();
|
|
25
26
|
function check(args) {
|
|
26
27
|
const nOptions = Object.keys(args).length - 1;
|
|
27
28
|
if (args['_'].length !== 1 || nOptions > 2 || nOptions > 0 && !args.r && !args.recursive) return false;
|
|
28
29
|
const curDir = process.cwd();
|
|
29
|
-
if (args.recursive) return runChecksRec(curDir);else {
|
|
30
|
+
if (args.recursive) return runChecksRec(curDir, args.soft ?? false);else {
|
|
30
31
|
if (!utils.isPackageDir(curDir)) {
|
|
31
32
|
color.error('File `package.json` not found. Run the command from the package directory');
|
|
32
33
|
return false;
|
|
33
34
|
}
|
|
34
|
-
return runChecks(curDir);
|
|
35
|
+
return runChecks(curDir, args.soft ?? false);
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
|
-
function runChecks(packagePath) {
|
|
38
|
+
function runChecks(packagePath, soft = false) {
|
|
38
39
|
const files = _ignoreWalk.default.sync({
|
|
39
40
|
path: packagePath,
|
|
40
41
|
ignoreFiles: ['.npmignore', '.gitignore']
|
|
@@ -77,20 +78,20 @@ function runChecks(packagePath) {
|
|
|
77
78
|
if (errors.length) {
|
|
78
79
|
console.log(`Checking package ${_path.default.basename(packagePath)}...`);
|
|
79
80
|
showError(errors);
|
|
80
|
-
if (json.version.startsWith('0') || errors.every(w => warns.some(ww => w.includes(ww)))) return true;
|
|
81
|
+
if (soft || json.version.startsWith('0') || errors.every(w => warns.some(ww => w.includes(ww)))) return true;
|
|
81
82
|
testUtils.exitWithCode(1);
|
|
82
83
|
}
|
|
83
84
|
console.log(`Checking package ${_path.default.basename(packagePath)}...\t\t\t\u2713 OK`);
|
|
84
85
|
return true;
|
|
85
86
|
}
|
|
86
|
-
function runChecksRec(dir) {
|
|
87
|
+
function runChecksRec(dir, soft = false) {
|
|
87
88
|
const files = _fs.default.readdirSync(dir);
|
|
88
89
|
for (const file of files) {
|
|
89
90
|
const filepath = _path.default.join(dir, file);
|
|
90
91
|
const stats = _fs.default.statSync(filepath);
|
|
91
92
|
if (stats.isDirectory()) {
|
|
92
|
-
if (utils.isPackageDir(filepath)) return runChecks(filepath);else {
|
|
93
|
-
if (file !== 'node_modules' && !file.startsWith('.')) runChecksRec(_path.default.join(dir, file));
|
|
93
|
+
if (utils.isPackageDir(filepath)) return runChecks(filepath, soft);else {
|
|
94
|
+
if (file !== 'node_modules' && !file.startsWith('.')) runChecksRec(_path.default.join(dir, file), soft);
|
|
94
95
|
}
|
|
95
96
|
}
|
|
96
97
|
}
|
|
@@ -315,13 +316,19 @@ function checkFuncSignatures(packagePath, files) {
|
|
|
315
316
|
warnings.push(`File ${file}, function ${f.name}:\n${vr.message}`);
|
|
316
317
|
}
|
|
317
318
|
}
|
|
318
|
-
let wrongInputNames = f.inputs.filter(e =>
|
|
319
|
+
let wrongInputNames = f.inputs.filter(e => forbiddenNames.includes(e?.name ?? ''));
|
|
320
|
+
if (f.name) {
|
|
321
|
+
if (namesInFiles.has(f.name)) namesInFiles.get(f.name)?.push(file);else namesInFiles.set(f.name, [file]);
|
|
322
|
+
}
|
|
319
323
|
if (wrongInputNames.length > 0) warnings.push(`File ${file}, function ${f.name}: Wrong input names: (${wrongInputNames.map(e => e.name).join(', ')})`);
|
|
320
324
|
if (f.isInvalidateOnWithoutCache) warnings.push(`File ${file}, function ${f.name}: Can't use invalidateOn without cache, please follow this example: 'meta.cache.invalidateOn'`);
|
|
321
|
-
if (f.cache) if (!utils.
|
|
325
|
+
if (f.cache) if (!utils.cacheValues.includes(f.cache)) warnings.push(`File ${file}, function ${f.name}: unsupposed variable for cache : ${f.cache}`);
|
|
322
326
|
if (f.invalidateOn) if (!utils.isValidCron(f.invalidateOn)) warnings.push(`File ${file}, function ${f.name}: unsupposed variable for invalidateOn : ${f.invalidateOn}`);
|
|
323
327
|
}
|
|
324
328
|
}
|
|
329
|
+
for (const [name, files] of namesInFiles) {
|
|
330
|
+
if (files.length > 1) warnings.push(`Duplicate names ('${name}'): \n ${files.join('\n ')}`);
|
|
331
|
+
}
|
|
325
332
|
return warnings;
|
|
326
333
|
}
|
|
327
334
|
const sharedLibExternals = {
|
|
@@ -519,7 +526,7 @@ function getFuncMetadata(script, fileExtention) {
|
|
|
519
526
|
if (match) {
|
|
520
527
|
if (!isHeader) isHeader = true;
|
|
521
528
|
const param = match[1];
|
|
522
|
-
if (param === 'name') data.name = line.match(utils.nameAnnRegex)?.[2];else if (param === 'description') data.description = match[2];else if (param === 'input') {
|
|
529
|
+
if (param === 'name') data.name = line.match(utils.nameAnnRegex)?.[2]?.toLocaleLowerCase();else if (param === 'description') data.description = match[2];else if (param === 'input') {
|
|
523
530
|
data.inputs.push({
|
|
524
531
|
type: match[2],
|
|
525
532
|
name: match[3]
|
|
@@ -539,8 +546,8 @@ function getFuncMetadata(script, fileExtention) {
|
|
|
539
546
|
}
|
|
540
547
|
if (isHeader) {
|
|
541
548
|
const nm = line.match(utils.nameRegex);
|
|
542
|
-
if (nm
|
|
543
|
-
|
|
549
|
+
if (nm) data.name = nm[1]?.toLocaleLowerCase();
|
|
550
|
+
if (data.name && !match) {
|
|
544
551
|
funcData.push(data);
|
|
545
552
|
data = {
|
|
546
553
|
name: '',
|
package/bin/commands/publish.js
CHANGED
|
@@ -14,9 +14,9 @@ var _path = _interopRequireDefault(require("path"));
|
|
|
14
14
|
var _ignoreWalk = _interopRequireDefault(require("ignore-walk"));
|
|
15
15
|
var _jsYaml = _interopRequireDefault(require("js-yaml"));
|
|
16
16
|
var _testUtils = require("../utils/test-utils");
|
|
17
|
-
var _check = require("./check");
|
|
18
17
|
var utils = _interopRequireWildcard(require("../utils/utils"));
|
|
19
18
|
var color = _interopRequireWildcard(require("../utils/color-utils"));
|
|
19
|
+
var _check = require("./check");
|
|
20
20
|
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
21
21
|
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
|
22
22
|
// @ts-ignore
|
|
@@ -81,7 +81,8 @@ async function processPackage(debug, rebuild, host, devKey, packageName, suffix)
|
|
|
81
81
|
rebuild = true;
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
|
-
|
|
84
|
+
|
|
85
|
+
// let contentValidationLog = '';
|
|
85
86
|
console.log('Starting package checks...');
|
|
86
87
|
const checkStart = Date.now();
|
|
87
88
|
const jsTsFiles = files.filter(f => !f.startsWith('dist/') && (f.endsWith('.js') || f.endsWith('.ts')));
|
|
@@ -94,19 +95,19 @@ async function processPackage(debug, rebuild, host, devKey, packageName, suffix)
|
|
|
94
95
|
const content = _fs.default.readFileSync(webpackConfigPath, {
|
|
95
96
|
encoding: 'utf-8'
|
|
96
97
|
});
|
|
97
|
-
const externals =
|
|
98
|
-
if (externals) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
98
|
+
// const externals = extractExternals(content);
|
|
99
|
+
// if (externals) {
|
|
100
|
+
// const importWarnings = checkImportStatements(curDir, jsTsFiles, externals);
|
|
101
|
+
// contentValidationLog += importWarnings.join('\n') + (importWarnings.length ? '\n' : '');
|
|
102
|
+
// }
|
|
102
103
|
}
|
|
103
104
|
const funcFiles = jsTsFiles.filter(f => packageFiles.includes(f));
|
|
104
|
-
const funcWarnings =
|
|
105
|
-
contentValidationLog += funcWarnings.join('\n') + (funcWarnings.length ? '\n' : '');
|
|
106
|
-
const packageWarnings =
|
|
107
|
-
contentValidationLog += packageWarnings.join('\n') + (packageWarnings.length ? '\n' : '');
|
|
108
|
-
const changelogWarnings =
|
|
109
|
-
contentValidationLog += changelogWarnings.join('\n') +
|
|
105
|
+
// const funcWarnings = checkFuncSignatures(curDir, funcFiles);
|
|
106
|
+
// contentValidationLog += funcWarnings.join('\n') + (funcWarnings.length ? '\n' : '');
|
|
107
|
+
// const packageWarnings = checkPackageFile(curDir, json);
|
|
108
|
+
// contentValidationLog += packageWarnings.join('\n') + (packageWarnings.length ? '\n' : '');
|
|
109
|
+
// const changelogWarnings = checkChangelog(curDir, json);
|
|
110
|
+
// contentValidationLog += changelogWarnings.join('\n') + '';
|
|
110
111
|
console.log(`Checks finished in ${Date.now() - checkStart} ms`);
|
|
111
112
|
const reg = new RegExp(/\${(\w*)}/g);
|
|
112
113
|
const errs = [];
|
|
@@ -123,9 +124,10 @@ async function processPackage(debug, rebuild, host, devKey, packageName, suffix)
|
|
|
123
124
|
}
|
|
124
125
|
if (relativePath.startsWith('upload.keys.json')) return;
|
|
125
126
|
if (relativePath === 'zip') return;
|
|
126
|
-
if (!utils.checkScriptLocation(canonicalRelativePath)) {
|
|
127
|
-
|
|
128
|
-
}
|
|
127
|
+
// if (!utils.checkScriptLocation(canonicalRelativePath)) {
|
|
128
|
+
// contentValidationLog += `Warning: file \`${canonicalRelativePath}\`` +
|
|
129
|
+
// ` should be in directory \`${path.basename(curDir)}/scripts/\`\n`;
|
|
130
|
+
// }
|
|
129
131
|
const t = _fs.default.statSync(fullPath).mtime.toUTCString();
|
|
130
132
|
localTimestamps[canonicalRelativePath] = t;
|
|
131
133
|
if (debug && timestamps[canonicalRelativePath] === t) {
|
|
@@ -196,7 +198,7 @@ async function processPackage(debug, rebuild, host, devKey, packageName, suffix)
|
|
|
196
198
|
return 1;
|
|
197
199
|
} else {
|
|
198
200
|
console.log(log);
|
|
199
|
-
color.warn(contentValidationLog);
|
|
201
|
+
// color.warn(contentValidationLog);
|
|
200
202
|
}
|
|
201
203
|
} catch (error) {
|
|
202
204
|
console.error(error);
|
|
@@ -205,6 +207,9 @@ async function processPackage(debug, rebuild, host, devKey, packageName, suffix)
|
|
|
205
207
|
return 0;
|
|
206
208
|
}
|
|
207
209
|
async function publish(args) {
|
|
210
|
+
if (!args['skip-check']) (0, _check.check)({
|
|
211
|
+
_: ['check']
|
|
212
|
+
});
|
|
208
213
|
config = _jsYaml.default.load(_fs.default.readFileSync(confPath, {
|
|
209
214
|
encoding: 'utf-8'
|
|
210
215
|
}));
|
package/bin/utils/utils.js
CHANGED
|
@@ -4,7 +4,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
|
|
|
4
4
|
Object.defineProperty(exports, "__esModule", {
|
|
5
5
|
value: true
|
|
6
6
|
});
|
|
7
|
-
exports.
|
|
7
|
+
exports.cacheValues = exports.absUrlRegex = exports.TemplateBuilder = void 0;
|
|
8
8
|
exports.camelCaseToKebab = camelCaseToKebab;
|
|
9
9
|
exports.checkScriptLocation = checkScriptLocation;
|
|
10
10
|
exports.commentMap = void 0;
|
|
@@ -149,7 +149,7 @@ const queryExtension = exports.queryExtension = '.sql';
|
|
|
149
149
|
const jsExtention = exports.jsExtention = '.js';
|
|
150
150
|
const scriptExtensions = exports.scriptExtensions = ['.jl', '.m', '.py', '.R'];
|
|
151
151
|
function checkScriptLocation(filepath) {
|
|
152
|
-
if (!(filepath.startsWith('scripts/') || filepath.startsWith('projects/') || filepath.startsWith('dockerfiles/')) && scriptExtensions.some(ext => filepath.endsWith(ext))) return false;
|
|
152
|
+
if (!(filepath.startsWith('scripts/') || filepath.startsWith('projects/') || filepath.startsWith('dockerfiles/') || filepath.startsWith('python/')) && scriptExtensions.some(ext => filepath.endsWith(ext))) return false;
|
|
153
153
|
return true;
|
|
154
154
|
}
|
|
155
155
|
;
|
|
@@ -165,7 +165,7 @@ function getParam(name, script, comment = '#') {
|
|
|
165
165
|
return match ? match[1]?.trim() : null;
|
|
166
166
|
}
|
|
167
167
|
;
|
|
168
|
-
const
|
|
168
|
+
const cacheValues = exports.cacheValues = ['all', 'server', 'client', 'true'];
|
|
169
169
|
function isValidCron(cronExpression) {
|
|
170
170
|
const cronRegex = /^(\*|([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])|\*\/([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])) (\*|([0-9]|1[0-9]|2[0-3])|\*\/([0-9]|1[0-9]|2[0-3])) (\*|([1-9]|1[0-9]|2[0-9]|3[0-1])|\*\/([1-9]|1[0-9]|2[0-9]|3[0-1])) (\*|([1-9]|1[0-2])|\*\/([1-9]|1[0-2])) (\*|([0-6])|\*\/([0-6]))$/;
|
|
171
171
|
return cronRegex.test(cronExpression);
|
|
@@ -183,15 +183,15 @@ const dgToTsTypeMap = exports.dgToTsTypeMap = {
|
|
|
183
183
|
view: 'DG.View'
|
|
184
184
|
};
|
|
185
185
|
const propertyTypes = exports.propertyTypes = ['bool', 'int', 'double', 'string', 'datetime', 'object', 'column', 'dataframe', 'bitset', 'cell', 'string_list', 'map'];
|
|
186
|
-
const headerTags = exports.headerTags = ['name', 'description', 'help-url', 'input', 'output', 'tags', 'sample', 'language', 'returns', 'test', 'sidebar', 'condition', 'top-menu', 'environment', 'require', 'editor-for', 'schedule', 'reference', 'editor', 'meta'];
|
|
186
|
+
const headerTags = exports.headerTags = ['name', 'description', 'help-url', 'input', 'output', 'tags', 'sample', 'language', 'returns', 'test', 'sidebar', 'condition', 'top-menu', 'environment', 'require', 'editor-for', 'schedule', 'reference', 'editor', 'meta', 'connection', 'friendlyName'];
|
|
187
187
|
const fileParamRegex = exports.fileParamRegex = {
|
|
188
188
|
py: new RegExp(`^\#\\s*((?:${headerTags.join('|')})[^:]*): *([^\\s\\[\\{]+) ?([^\\s\\[\\{]+)?[^\\n]*$`),
|
|
189
189
|
ts: new RegExp(`^\/\/\\s*((?:${headerTags.join('|')})[^:]*): *([^\\s\\[\\{]+) ?([^\\s\\[\\{]+)?[^\\n]*$`),
|
|
190
190
|
js: new RegExp(`^\/\/\\s*((?:${headerTags.join('|')})[^:]*): *([^\\s\\[\\{]+) ?([^\\s\\[\\{]+)?[^\\n]*$`),
|
|
191
191
|
sql: new RegExp(`^--\\s*((?:${headerTags.join('|')})[^:]*): *([^\\s\\[\\{]+) ?([^\\s\\[\\{]+)?[^\\n]*$`)
|
|
192
192
|
};
|
|
193
|
-
const nameAnnRegex = exports.nameAnnRegex =
|
|
194
|
-
const nameRegex = exports.nameRegex = /(?:|(?:static)(?:export )(?:async )
|
|
193
|
+
const nameAnnRegex = exports.nameAnnRegex = /\s*(name[^:]*): ([^\n\r\[\{]+)/;
|
|
194
|
+
const nameRegex = exports.nameRegex = /(?:|(?:static)(?:export )(?:async ))\s+function\s+([a-zA-Z_][a-zA-Z0-9_$]*)\s*\((.*?).*/;
|
|
195
195
|
const absUrlRegex = exports.absUrlRegex = new RegExp('^(?:[a-z+]+:)?//', 'i');
|
|
196
196
|
function getScriptOutputType(script, comment = '#') {
|
|
197
197
|
const regex = new RegExp(`${comment}\\s*output:\\s?([a-z_]+)\\s*`);
|
|
@@ -245,7 +245,7 @@ async function runScript(script, path, verbose = false) {
|
|
|
245
245
|
}
|
|
246
246
|
} catch (error) {
|
|
247
247
|
console.error(`Execution failed: ${error.message}`);
|
|
248
|
-
throw new Error(`
|
|
248
|
+
throw new Error(`Error executing '${script}'. Error message: ${error.message}`);
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
251
|
function setHost(host, configFile) {
|
package/package.json
CHANGED