apify-cli 0.18.2-beta.1 → 0.18.2-beta.11
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 +4 -0
- package/oclif.manifest.json +1 -1
- package/package.json +25 -22
- package/src/commands/actor/get-input.js +1 -1
- package/src/commands/actor/get-value.js +1 -1
- package/src/commands/actor/index.js +1 -0
- package/src/commands/actor/push-data.js +1 -1
- package/src/commands/actor/set-value.js +2 -1
- package/src/commands/call.js +4 -3
- package/src/commands/check-version.js +1 -0
- package/src/commands/create.js +13 -19
- package/src/commands/edit-input-schema.js +3 -4
- package/src/commands/info.js +1 -0
- package/src/commands/init-wrap-scrapy.js +34 -0
- package/src/commands/init.js +30 -5
- package/src/commands/login-new.js +3 -4
- package/src/commands/login.js +2 -1
- package/src/commands/logout.js +1 -1
- package/src/commands/pull.js +6 -4
- package/src/commands/push.js +8 -6
- package/src/commands/run.js +24 -10
- package/src/commands/secrets/index.js +1 -0
- package/src/commands/vis.js +1 -0
- package/src/lib/actor.js +5 -3
- package/src/lib/apify_command.js +6 -4
- package/src/lib/consts.js +6 -0
- package/src/lib/create-utils.js +6 -25
- package/src/lib/exec.js +1 -0
- package/src/lib/files.js +3 -2
- package/src/lib/input_schema.js +5 -3
- package/src/lib/local_state.js +1 -0
- package/src/lib/project_analyzer.js +24 -0
- package/src/lib/scrapy-wrapper/ScrapyProjectAnalyzer.js +90 -0
- package/src/lib/scrapy-wrapper/Spider.js +10 -0
- package/src/lib/scrapy-wrapper/SpiderFileAnalyzer.js +26 -0
- package/src/lib/scrapy-wrapper/index.js +139 -0
- package/src/lib/secrets.js +2 -2
- package/src/lib/telemetry.js +4 -2
- package/src/lib/utils.js +55 -18
- package/src/lib/version_check.js +6 -13
- package/npm-shrinkwrap.json +0 -14224
package/src/lib/apify_command.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
const { Command } = require('@oclif/command');
|
|
2
1
|
const { finished } = require('stream');
|
|
3
2
|
const { promisify } = require('util');
|
|
4
|
-
|
|
3
|
+
|
|
4
|
+
const { Command } = require('@oclif/command');
|
|
5
|
+
|
|
6
|
+
const { LANGUAGE, COMMANDS_WITHIN_ACTOR } = require('./consts');
|
|
5
7
|
const { maybeTrackTelemetry } = require('./telemetry');
|
|
6
|
-
const {
|
|
8
|
+
const { argsToCamelCase } = require('./utils');
|
|
7
9
|
const { detectLocalActorLanguage } = require('./utils');
|
|
8
|
-
const {
|
|
10
|
+
const { detectInstallationType } = require('./version_check');
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* Adding parsing flags to oclif Command class
|
package/src/lib/consts.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const os = require('os');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
|
|
3
4
|
const { KEY_VALUE_STORE_KEYS, META_ORIGINS } = require('@apify/consts');
|
|
4
5
|
|
|
5
6
|
exports.DEFAULT_LOCAL_STORAGE_DIR = 'storage';
|
|
@@ -22,6 +23,11 @@ exports.LANGUAGE = {
|
|
|
22
23
|
UNKNOWN: 'n/a',
|
|
23
24
|
};
|
|
24
25
|
|
|
26
|
+
exports.PROJECT_TYPES = {
|
|
27
|
+
SCRAPY: 'scrapy',
|
|
28
|
+
UNKNOWN: 'unknown',
|
|
29
|
+
};
|
|
30
|
+
|
|
25
31
|
exports.COMMANDS_WITHIN_ACTOR = ['init', 'run', 'push', 'pull', 'call'];
|
|
26
32
|
|
|
27
33
|
exports.CHECK_VERSION_EVERY_MILLIS = 24 * 60 * 60 * 1000; // Once a day
|
package/src/lib/create-utils.js
CHANGED
|
@@ -1,36 +1,17 @@
|
|
|
1
|
-
const
|
|
2
|
-
const inquirer = require('inquirer');
|
|
3
|
-
const https = require('https');
|
|
1
|
+
const fs = require('fs');
|
|
4
2
|
const { pipeline } = require('stream');
|
|
5
3
|
const { promisify } = require('util');
|
|
6
|
-
const fs = require('fs');
|
|
7
4
|
|
|
8
|
-
const
|
|
5
|
+
const chalk = require('chalk');
|
|
6
|
+
const inquirer = require('inquirer');
|
|
7
|
+
|
|
9
8
|
const {
|
|
10
9
|
warning,
|
|
11
10
|
} = require('./outputs');
|
|
11
|
+
const { validateActorName, httpsGet } = require('./utils');
|
|
12
12
|
|
|
13
13
|
const PROGRAMMING_LANGUAGES = ['JavaScript', 'TypeScript', 'Python'];
|
|
14
14
|
|
|
15
|
-
/**
|
|
16
|
-
* @param {string} url
|
|
17
|
-
* @returns {Promise<unknown>}
|
|
18
|
-
*/
|
|
19
|
-
exports.httpsGet = async (url) => {
|
|
20
|
-
return new Promise((resolve, reject) => {
|
|
21
|
-
https.get(url, (response) => {
|
|
22
|
-
// Handle redirects
|
|
23
|
-
if (response.statusCode === 301 || response.statusCode === 302) {
|
|
24
|
-
resolve(exports.httpsGet(response.headers.location));
|
|
25
|
-
// Destroy the response to close the HTTP connection, otherwise this hangs for a long time with Node 19+ (due to HTTP keep-alive).
|
|
26
|
-
response.destroy();
|
|
27
|
-
} else {
|
|
28
|
-
resolve(response);
|
|
29
|
-
}
|
|
30
|
-
}).on('error', reject);
|
|
31
|
-
});
|
|
32
|
-
};
|
|
33
|
-
|
|
34
15
|
/**
|
|
35
16
|
* @param {string} maybeActorName
|
|
36
17
|
* @returns {Promise<string>}
|
|
@@ -77,7 +58,7 @@ exports.enhanceReadmeWithLocalSuffix = async (readmePath, manifestPromise) => {
|
|
|
77
58
|
if (manifest instanceof Error) throw manifest;
|
|
78
59
|
|
|
79
60
|
try {
|
|
80
|
-
const suffixStream = await
|
|
61
|
+
const suffixStream = await httpsGet(manifest.localReadmeSuffixUrl);
|
|
81
62
|
const readmeStream = fs.createWriteStream(readmePath, { flags: 'a' });
|
|
82
63
|
readmeStream.write('\n\n');
|
|
83
64
|
await promisify(pipeline)(suffixStream, readmeStream);
|
package/src/lib/exec.js
CHANGED
package/src/lib/files.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
const loadJson = require('load-json-file');
|
|
2
|
-
const writeJson = require('write-json-file');
|
|
3
1
|
const fs = require('fs');
|
|
4
2
|
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const loadJson = require('load-json-file');
|
|
5
5
|
const rimraf = require('rimraf');
|
|
6
|
+
const writeJson = require('write-json-file');
|
|
6
7
|
|
|
7
8
|
const updateLocalJson = async (jsonFilePath, updateAttrs = {}, nestedObjectAttr = null) => {
|
|
8
9
|
const currentObject = await loadJson(jsonFilePath);
|
package/src/lib/input_schema.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
const { KEY_VALUE_STORE_KEYS } = require('@apify/consts');
|
|
4
5
|
const { validateInputSchema } = require('@apify/input_schema');
|
|
6
|
+
const Ajv = require('ajv');
|
|
5
7
|
const _ = require('underscore');
|
|
6
|
-
const { KEY_VALUE_STORE_KEYS } = require('@apify/consts');
|
|
7
8
|
const writeJsonFile = require('write-json-file');
|
|
9
|
+
|
|
8
10
|
const { ACTOR_SPECIFICATION_FOLDER } = require('./consts');
|
|
9
|
-
const { getLocalConfig, getJsonFileContent, getLocalKeyValueStorePath } = require('./utils');
|
|
10
11
|
const { warning } = require('./outputs');
|
|
12
|
+
const { getLocalConfig, getJsonFileContent, getLocalKeyValueStorePath } = require('./utils');
|
|
11
13
|
|
|
12
14
|
const DEFAULT_INPUT_SCHEMA_PATHS = [
|
|
13
15
|
'.actor/INPUT_SCHEMA.json',
|
package/src/lib/local_state.js
CHANGED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const { PROJECT_TYPES } = require('./consts');
|
|
2
|
+
const { ScrapyProjectAnalyzer } = require('./scrapy-wrapper/ScrapyProjectAnalyzer');
|
|
3
|
+
|
|
4
|
+
const analyzers = [
|
|
5
|
+
{
|
|
6
|
+
type: PROJECT_TYPES.SCRAPY,
|
|
7
|
+
analyzer: ScrapyProjectAnalyzer,
|
|
8
|
+
},
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
class ProjectAnalyzer {
|
|
12
|
+
static getProjectType(pathname) {
|
|
13
|
+
const analyzer = analyzers.find((a) => {
|
|
14
|
+
if (!a.analyzer.isApplicable) {
|
|
15
|
+
throw new Error(`Analyzer ${a.analyzer} does not have isApplicable method.`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return a.analyzer.isApplicable(pathname);
|
|
19
|
+
});
|
|
20
|
+
return analyzer?.type || PROJECT_TYPES.UNKNOWN;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = { ProjectAnalyzer };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const { readdirSync } = require('fs');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const ConfigParser = require('configparser');
|
|
6
|
+
const inquirer = require('inquirer');
|
|
7
|
+
|
|
8
|
+
const { SpiderFileAnalyzer } = require('./SpiderFileAnalyzer');
|
|
9
|
+
|
|
10
|
+
class ScrapyProjectAnalyzer {
|
|
11
|
+
constructor(pathname) {
|
|
12
|
+
this.pathname = pathname;
|
|
13
|
+
this.configuration = null;
|
|
14
|
+
this.settings = null;
|
|
15
|
+
this.loadScrapyCfg();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static isApplicable(pathname) {
|
|
19
|
+
return fs.existsSync(path.join(pathname, 'scrapy.cfg'));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async init() {
|
|
23
|
+
await this.loadSettings();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
loadScrapyCfg() {
|
|
27
|
+
const config = new ConfigParser();
|
|
28
|
+
const scrapyCfgPath = path.resolve(path.join(this.pathname, 'scrapy.cfg'));
|
|
29
|
+
|
|
30
|
+
if (!fs.existsSync(scrapyCfgPath)) {
|
|
31
|
+
throw new Error(`scrapy.cfg not found in "${scrapyCfgPath}".
|
|
32
|
+
Are you sure there is a Scrapy project there?`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
config.read(scrapyCfgPath);
|
|
36
|
+
this.configuration = config;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async loadSettings() {
|
|
40
|
+
const assumedBotName = this.configuration.get('settings', 'default').split('.')[0];
|
|
41
|
+
|
|
42
|
+
const settings = await inquirer.prompt([
|
|
43
|
+
{
|
|
44
|
+
type: 'input',
|
|
45
|
+
name: 'BOT_NAME',
|
|
46
|
+
message: 'Enter the Scrapy BOT_NAME (see settings.py):',
|
|
47
|
+
default: assumedBotName,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
type: 'input',
|
|
51
|
+
name: 'SPIDER_MODULES',
|
|
52
|
+
message: 'What folder are the Scrapy spider modules stored in? (see SPIDER_MODULES in settings.py):',
|
|
53
|
+
default: [`${assumedBotName}.spiders`],
|
|
54
|
+
},
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
if (typeof settings.SPIDER_MODULES === 'string') settings.SPIDER_MODULES = [settings.SPIDER_MODULES];
|
|
58
|
+
|
|
59
|
+
this.settings = settings;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
getName() {
|
|
63
|
+
return this.settings?.BOT_NAME;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
getAvailableSpiders() {
|
|
67
|
+
const spiderPaths = this.settings?.SPIDER_MODULES;
|
|
68
|
+
|
|
69
|
+
if (!spiderPaths) {
|
|
70
|
+
throw new Error('SPIDER_MODULES path not found in settings.');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const spiders = [];
|
|
74
|
+
|
|
75
|
+
for (const spiderPath of spiderPaths) {
|
|
76
|
+
const spidersDir = path.join(this.pathname, spiderPath.replaceAll('.', '/'));
|
|
77
|
+
|
|
78
|
+
const files = readdirSync(spidersDir, { withFileTypes: true });
|
|
79
|
+
for (const file of files) {
|
|
80
|
+
if (file.isFile() && file.name.endsWith('.py') && file.name !== '__init__.py') {
|
|
81
|
+
spiders.push(...(new SpiderFileAnalyzer(path.join(spidersDir, file.name)).getSpiders()));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return spiders;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = { ScrapyProjectAnalyzer };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
|
|
3
|
+
const { Spider } = require('./Spider');
|
|
4
|
+
|
|
5
|
+
class SpiderFileAnalyzer {
|
|
6
|
+
constructor(pathname) {
|
|
7
|
+
this.pathname = pathname;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
getSpiders() {
|
|
11
|
+
const file = fs.readFileSync(this.pathname, 'utf8');
|
|
12
|
+
|
|
13
|
+
const regex = /class\s+(\w+)/g;
|
|
14
|
+
const spiders = [];
|
|
15
|
+
|
|
16
|
+
let match = regex.exec(file);
|
|
17
|
+
while (match) {
|
|
18
|
+
spiders.push(new Spider({ class_name: match[1], pathname: this.pathname }));
|
|
19
|
+
match = regex.exec(file);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return spiders;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = { SpiderFileAnalyzer };
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const { fetchManifest, wrapperManifestUrl } = require('@apify/actor-templates');
|
|
5
|
+
const { walk } = require('@root/walk');
|
|
6
|
+
const ConfigParser = require('configparser');
|
|
7
|
+
const handlebars = require('handlebars');
|
|
8
|
+
const inquirer = require('inquirer');
|
|
9
|
+
|
|
10
|
+
const { ScrapyProjectAnalyzer } = require('./ScrapyProjectAnalyzer');
|
|
11
|
+
const outputs = require('../outputs');
|
|
12
|
+
const { downloadAndUnzip } = require('../utils');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Files that should be concatenated instead of copied (and overwritten).
|
|
16
|
+
*/
|
|
17
|
+
const concatenableFiles = ['.dockerignore', '.gitignore'];
|
|
18
|
+
|
|
19
|
+
async function merge(fromPath, toPath, options = { bindings: {} }) {
|
|
20
|
+
await walk(fromPath, async (err, pathname, dirent) => {
|
|
21
|
+
if (pathname === fromPath) return;
|
|
22
|
+
const relPath = path.relative(fromPath, pathname);
|
|
23
|
+
const toRelPath = relPath.split(path.sep).map((part) => {
|
|
24
|
+
if (part.startsWith('{') && part.endsWith('}')) {
|
|
25
|
+
part = part.replace('{', '').replace('}', '');
|
|
26
|
+
const binding = options.bindings[part];
|
|
27
|
+
if (!binding) {
|
|
28
|
+
throw new Error(`Binding for ${part} not found.`);
|
|
29
|
+
}
|
|
30
|
+
return binding;
|
|
31
|
+
}
|
|
32
|
+
return part;
|
|
33
|
+
}).join(path.sep);
|
|
34
|
+
|
|
35
|
+
const targetPath = path.join(toPath, toRelPath);
|
|
36
|
+
|
|
37
|
+
if (dirent.isDirectory()) {
|
|
38
|
+
if (!fs.existsSync(targetPath)) {
|
|
39
|
+
fs.mkdirSync(targetPath);
|
|
40
|
+
}
|
|
41
|
+
return merge(pathname, targetPath);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (relPath.includes('.template')) {
|
|
45
|
+
fs.writeFileSync(
|
|
46
|
+
path.join(
|
|
47
|
+
toPath,
|
|
48
|
+
toRelPath.replace('.template', ''),
|
|
49
|
+
),
|
|
50
|
+
handlebars.compile(fs.readFileSync(pathname, 'utf8'))(options.bindings));
|
|
51
|
+
} else if (fs.existsSync(targetPath) && concatenableFiles.includes(path.basename(toRelPath))) {
|
|
52
|
+
fs.appendFileSync(targetPath, fs.readFileSync(pathname));
|
|
53
|
+
} else {
|
|
54
|
+
fs.copyFileSync(pathname, targetPath);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function wrapScrapyProject({ projectPath }) {
|
|
60
|
+
if (!projectPath) projectPath = '.';
|
|
61
|
+
|
|
62
|
+
const analyzer = new ScrapyProjectAnalyzer(projectPath);
|
|
63
|
+
|
|
64
|
+
if (analyzer.configuration.hasSection('apify')) {
|
|
65
|
+
throw new Error(`The Scrapy project configuration already contains Apify settings. Are you sure you didn't already wrap this project?`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
await analyzer.init();
|
|
69
|
+
|
|
70
|
+
const { spiderIndex } = await inquirer.prompt([
|
|
71
|
+
{
|
|
72
|
+
type: 'list',
|
|
73
|
+
name: 'spiderIndex',
|
|
74
|
+
message: 'Pick the Scrapy spider you want to wrap:',
|
|
75
|
+
choices: analyzer.getAvailableSpiders().map((spider, i) => ({
|
|
76
|
+
name: `${spider.class_name} (${spider.pathname})`,
|
|
77
|
+
value: i,
|
|
78
|
+
})),
|
|
79
|
+
},
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
function translatePathToRelativeModuleName(pathname) {
|
|
83
|
+
const relPath = path.relative(projectPath, pathname);
|
|
84
|
+
|
|
85
|
+
return `.${relPath.split(path.sep).slice(1).join('.').replace('.py', '')}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const templateBindings = {
|
|
89
|
+
botName: analyzer.settings.BOT_NAME,
|
|
90
|
+
scrapy_settings_module: analyzer.configuration.get('settings', 'default'),
|
|
91
|
+
apify_module_path: `${analyzer.settings.BOT_NAME}.apify`,
|
|
92
|
+
spider_class_name: analyzer.getAvailableSpiders()[spiderIndex].class_name,
|
|
93
|
+
spider_module_name: `${translatePathToRelativeModuleName(analyzer.getAvailableSpiders()[spiderIndex].pathname)}`,
|
|
94
|
+
projectFolder: analyzer.settings.BOT_NAME,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const manifest = await fetchManifest(wrapperManifestUrl);
|
|
98
|
+
|
|
99
|
+
outputs.info('Downloading the latest Scrapy wrapper template...');
|
|
100
|
+
|
|
101
|
+
const { archiveUrl } = manifest.templates.find(({ id }) => id === 'python-scrapy');
|
|
102
|
+
const templatePath = path.join(__dirname, 'templates', 'python-scrapy');
|
|
103
|
+
|
|
104
|
+
if (fs.existsSync(templatePath)) fs.rmSync(templatePath, { recursive: true });
|
|
105
|
+
|
|
106
|
+
await downloadAndUnzip({
|
|
107
|
+
url: archiveUrl,
|
|
108
|
+
pathTo: templatePath,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
outputs.info('Wrapping the Scrapy project...');
|
|
112
|
+
|
|
113
|
+
merge(
|
|
114
|
+
path.join(__dirname, 'templates', 'python-scrapy'),
|
|
115
|
+
projectPath,
|
|
116
|
+
{
|
|
117
|
+
bindings: templateBindings,
|
|
118
|
+
},
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const apifyConf = new ConfigParser();
|
|
122
|
+
apifyConf.addSection('apify');
|
|
123
|
+
apifyConf.set('apify', 'mainpy_location', analyzer.settings.BOT_NAME);
|
|
124
|
+
|
|
125
|
+
const s = fs.createWriteStream(path.join(projectPath, 'scrapy.cfg'), { flags: 'a' });
|
|
126
|
+
|
|
127
|
+
await new Promise((r) => {
|
|
128
|
+
s.on('open', (fd) => {
|
|
129
|
+
s.write('\n', () => {
|
|
130
|
+
apifyConf.write(fd);
|
|
131
|
+
r();
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
outputs.success('The Scrapy project has been wrapped successfully.');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
module.exports = { wrapScrapyProject };
|
package/src/lib/secrets.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const loadJson = require('load-json-file');
|
|
2
|
-
const writeJson = require('write-json-file');
|
|
3
2
|
const _ = require('underscore');
|
|
3
|
+
const writeJson = require('write-json-file');
|
|
4
|
+
|
|
4
5
|
const { SECRETS_FILE_PATH } = require('./consts');
|
|
5
6
|
const { warning } = require('./outputs');
|
|
6
7
|
|
|
@@ -62,7 +63,6 @@ const replaceSecretsValue = (env, secrets) => {
|
|
|
62
63
|
if (secrets[secretKey]) {
|
|
63
64
|
updatedEnv[key] = secrets[secretKey];
|
|
64
65
|
} else {
|
|
65
|
-
// eslint-disable-next-line max-len
|
|
66
66
|
warning(`Value for ${secretKey} not found in local secrets. Set it by calling "apify secrets:add ${secretKey} [SECRET_VALUE]"`);
|
|
67
67
|
}
|
|
68
68
|
} else {
|
package/src/lib/telemetry.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
const Mixpanel = require('mixpanel');
|
|
2
1
|
const { promisify } = require('util');
|
|
2
|
+
|
|
3
|
+
const { cryptoRandomObjectId } = require('@apify/utilities');
|
|
3
4
|
const loadJson = require('load-json-file');
|
|
5
|
+
const Mixpanel = require('mixpanel');
|
|
4
6
|
const writeJson = require('write-json-file');
|
|
5
|
-
|
|
7
|
+
|
|
6
8
|
const { MIXPANEL_TOKEN, TELEMETRY_FILE_PATH } = require('./consts');
|
|
7
9
|
const outputs = require('./outputs');
|
|
8
10
|
const { getLocalUserInfo } = require('./utils');
|
package/src/lib/utils.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
const
|
|
1
|
+
const {
|
|
2
|
+
execSync,
|
|
3
|
+
spawnSync,
|
|
4
|
+
} = require('child_process');
|
|
2
5
|
const fs = require('fs');
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
const loadJson = require('load-json-file');
|
|
9
|
-
const writeJson = require('write-json-file');
|
|
10
|
-
const inquirer = require('inquirer');
|
|
6
|
+
const https = require('https');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { finished } = require('stream');
|
|
9
|
+
const { promisify } = require('util');
|
|
10
|
+
|
|
11
11
|
const {
|
|
12
12
|
ACT_JOB_TERMINAL_STATUSES,
|
|
13
13
|
ACTOR_ENV_VARS,
|
|
@@ -18,14 +18,19 @@ const {
|
|
|
18
18
|
LOCAL_STORAGE_SUBDIRS,
|
|
19
19
|
SOURCE_FILE_FORMATS,
|
|
20
20
|
} = require('@apify/consts');
|
|
21
|
-
const
|
|
21
|
+
const AdmZip = require('adm-zip');
|
|
22
22
|
const { ApifyClient } = require('apify-client');
|
|
23
|
-
const
|
|
24
|
-
execSync,
|
|
25
|
-
spawnSync,
|
|
26
|
-
} = require('child_process');
|
|
27
|
-
const semver = require('semver');
|
|
23
|
+
const archiver = require('archiver-promise');
|
|
28
24
|
const escapeStringRegexp = require('escape-string-regexp');
|
|
25
|
+
const globby = require('globby');
|
|
26
|
+
const inquirer = require('inquirer');
|
|
27
|
+
const { getEncoding } = require('istextorbinary');
|
|
28
|
+
const loadJson = require('load-json-file');
|
|
29
|
+
const mime = require('mime');
|
|
30
|
+
const semver = require('semver');
|
|
31
|
+
const _ = require('underscore');
|
|
32
|
+
const writeJson = require('write-json-file');
|
|
33
|
+
|
|
29
34
|
const {
|
|
30
35
|
GLOBAL_CONFIGS_FOLDER,
|
|
31
36
|
AUTH_FILE_PATH,
|
|
@@ -38,6 +43,7 @@ const {
|
|
|
38
43
|
SUPPORTED_NODEJS_VERSION,
|
|
39
44
|
MINIMUM_SUPPORTED_PYTHON_VERSION,
|
|
40
45
|
LANGUAGE,
|
|
46
|
+
PROJECT_TYPES,
|
|
41
47
|
} = require('./consts');
|
|
42
48
|
const {
|
|
43
49
|
ensureFolderExistsSync,
|
|
@@ -47,6 +53,26 @@ const {
|
|
|
47
53
|
const {
|
|
48
54
|
info,
|
|
49
55
|
} = require('./outputs');
|
|
56
|
+
const { ProjectAnalyzer } = require('./project_analyzer');
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @param {string} url
|
|
60
|
+
* @returns {Promise<unknown>}
|
|
61
|
+
*/
|
|
62
|
+
const httpsGet = async (url) => {
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
https.get(url, (response) => {
|
|
65
|
+
// Handle redirects
|
|
66
|
+
if (response.statusCode === 301 || response.statusCode === 302) {
|
|
67
|
+
resolve(httpsGet(response.headers.location));
|
|
68
|
+
// Destroy the response to close the HTTP connection, otherwise this hangs for a long time with Node 19+ (due to HTTP keep-alive).
|
|
69
|
+
response.destroy();
|
|
70
|
+
} else {
|
|
71
|
+
resolve(response);
|
|
72
|
+
}
|
|
73
|
+
}).on('error', reject);
|
|
74
|
+
});
|
|
75
|
+
};
|
|
50
76
|
|
|
51
77
|
// Properties from apify.json file that will me migrated to actor specs in .actor/actor.json
|
|
52
78
|
const MIGRATED_APIFY_JSON_PROPERTIES = ['name', 'version', 'buildTag'];
|
|
@@ -175,7 +201,7 @@ const getLocalConfigOrThrow = async () => {
|
|
|
175
201
|
const answer = await inquirer.prompt([{
|
|
176
202
|
name: 'isConfirm',
|
|
177
203
|
type: 'confirm',
|
|
178
|
-
|
|
204
|
+
|
|
179
205
|
message: `The new version of Apify CLI uses the "${LOCAL_CONFIG_PATH}" instead of the "apify.json" file. Since we have found both files in your actor directory, "apify.json" will be renamed to "apify.json.deprecated". Going forward, all commands will use "${LOCAL_CONFIG_PATH}". You can read about the differences between the old and the new config at https://github.com/apify/apify-cli/blob/master/MIGRATIONS.md. Do you want to continue?`,
|
|
180
206
|
}]);
|
|
181
207
|
if (!answer.isConfirm) {
|
|
@@ -579,8 +605,8 @@ const detectNpmVersion = () => {
|
|
|
579
605
|
|
|
580
606
|
const detectLocalActorLanguage = () => {
|
|
581
607
|
const cwd = process.cwd();
|
|
582
|
-
const isActorInNode = fs.existsSync(path.join(
|
|
583
|
-
const isActorInPython = fs.existsSync(path.join(
|
|
608
|
+
const isActorInNode = fs.existsSync(path.join(cwd, 'package.json'));
|
|
609
|
+
const isActorInPython = fs.existsSync(path.join(cwd, 'src/__main__.py')) || ProjectAnalyzer.getProjectType(cwd) === PROJECT_TYPES.SCRAPY;
|
|
584
610
|
const result = {};
|
|
585
611
|
if (isActorInNode) {
|
|
586
612
|
result.language = LANGUAGE.NODEJS;
|
|
@@ -594,7 +620,17 @@ const detectLocalActorLanguage = () => {
|
|
|
594
620
|
return result;
|
|
595
621
|
};
|
|
596
622
|
|
|
623
|
+
const downloadAndUnzip = async ({ url, pathTo }) => {
|
|
624
|
+
const zipStream = await httpsGet(url);
|
|
625
|
+
const chunks = [];
|
|
626
|
+
zipStream.on('data', (chunk) => chunks.push(chunk));
|
|
627
|
+
await promisify(finished)(zipStream);
|
|
628
|
+
const zip = new AdmZip(Buffer.concat(chunks));
|
|
629
|
+
zip.extractAllTo(pathTo, true);
|
|
630
|
+
};
|
|
631
|
+
|
|
597
632
|
module.exports = {
|
|
633
|
+
httpsGet,
|
|
598
634
|
getLoggedClientOrThrow,
|
|
599
635
|
getLocalConfig,
|
|
600
636
|
setLocalConfig,
|
|
@@ -628,4 +664,5 @@ module.exports = {
|
|
|
628
664
|
isNodeVersionSupported,
|
|
629
665
|
detectNpmVersion,
|
|
630
666
|
detectLocalActorLanguage,
|
|
667
|
+
downloadAndUnzip,
|
|
631
668
|
};
|
package/src/lib/version_check.js
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const process = require('process');
|
|
3
|
+
|
|
3
4
|
const axios = require('axios');
|
|
4
5
|
const chalk = require('chalk');
|
|
5
6
|
const semver = require('semver');
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
} = require('./
|
|
9
|
-
const {
|
|
10
|
-
|
|
11
|
-
info,
|
|
12
|
-
} = require('./outputs');
|
|
13
|
-
const {
|
|
14
|
-
getLocalState,
|
|
15
|
-
extendLocalState,
|
|
16
|
-
} = require('./local_state');
|
|
7
|
+
|
|
8
|
+
const { CHECK_VERSION_EVERY_MILLIS } = require('./consts');
|
|
9
|
+
const { getLocalState, extendLocalState } = require('./local_state');
|
|
10
|
+
const { warning, info } = require('./outputs');
|
|
11
|
+
const { version: CURRENT_APIFY_CLI_VERSION } = require('../../package.json');
|
|
17
12
|
|
|
18
13
|
const INSTALLATION_TYPE = {
|
|
19
14
|
HOMEBREW: 'HOMEBREW',
|
|
@@ -30,8 +25,6 @@ const SKIP_UPDATE_CHECK = (
|
|
|
30
25
|
&& !['0', 'false'].includes(process.env.APIFY_CLI_SKIP_UPDATE_CHECK.toLowerCase())
|
|
31
26
|
);
|
|
32
27
|
|
|
33
|
-
const CURRENT_APIFY_CLI_VERSION = require('../../package.json').version;
|
|
34
|
-
|
|
35
28
|
/**
|
|
36
29
|
* Detect through which package manager the Apify CLI was installed.
|
|
37
30
|
* @returns {INSTALLATION_TYPE} The installation type of the CLI.
|