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.
Files changed (41) hide show
  1. package/README.md +4 -0
  2. package/oclif.manifest.json +1 -1
  3. package/package.json +25 -22
  4. package/src/commands/actor/get-input.js +1 -1
  5. package/src/commands/actor/get-value.js +1 -1
  6. package/src/commands/actor/index.js +1 -0
  7. package/src/commands/actor/push-data.js +1 -1
  8. package/src/commands/actor/set-value.js +2 -1
  9. package/src/commands/call.js +4 -3
  10. package/src/commands/check-version.js +1 -0
  11. package/src/commands/create.js +13 -19
  12. package/src/commands/edit-input-schema.js +3 -4
  13. package/src/commands/info.js +1 -0
  14. package/src/commands/init-wrap-scrapy.js +34 -0
  15. package/src/commands/init.js +30 -5
  16. package/src/commands/login-new.js +3 -4
  17. package/src/commands/login.js +2 -1
  18. package/src/commands/logout.js +1 -1
  19. package/src/commands/pull.js +6 -4
  20. package/src/commands/push.js +8 -6
  21. package/src/commands/run.js +24 -10
  22. package/src/commands/secrets/index.js +1 -0
  23. package/src/commands/vis.js +1 -0
  24. package/src/lib/actor.js +5 -3
  25. package/src/lib/apify_command.js +6 -4
  26. package/src/lib/consts.js +6 -0
  27. package/src/lib/create-utils.js +6 -25
  28. package/src/lib/exec.js +1 -0
  29. package/src/lib/files.js +3 -2
  30. package/src/lib/input_schema.js +5 -3
  31. package/src/lib/local_state.js +1 -0
  32. package/src/lib/project_analyzer.js +24 -0
  33. package/src/lib/scrapy-wrapper/ScrapyProjectAnalyzer.js +90 -0
  34. package/src/lib/scrapy-wrapper/Spider.js +10 -0
  35. package/src/lib/scrapy-wrapper/SpiderFileAnalyzer.js +26 -0
  36. package/src/lib/scrapy-wrapper/index.js +139 -0
  37. package/src/lib/secrets.js +2 -2
  38. package/src/lib/telemetry.js +4 -2
  39. package/src/lib/utils.js +55 -18
  40. package/src/lib/version_check.js +6 -13
  41. package/npm-shrinkwrap.json +0 -14224
@@ -1,11 +1,13 @@
1
- const { Command } = require('@oclif/command');
2
1
  const { finished } = require('stream');
3
2
  const { promisify } = require('util');
4
- const { argsToCamelCase } = require('./utils');
3
+
4
+ const { Command } = require('@oclif/command');
5
+
6
+ const { LANGUAGE, COMMANDS_WITHIN_ACTOR } = require('./consts');
5
7
  const { maybeTrackTelemetry } = require('./telemetry');
6
- const { detectInstallationType } = require('./version_check');
8
+ const { argsToCamelCase } = require('./utils');
7
9
  const { detectLocalActorLanguage } = require('./utils');
8
- const { LANGUAGE, COMMANDS_WITHIN_ACTOR } = require('./consts');
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
@@ -1,36 +1,17 @@
1
- const chalk = require('chalk');
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 { validateActorName } = require('./utils');
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 this.httpsGet(manifest.localReadmeSuffixUrl);
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
@@ -1,4 +1,5 @@
1
1
  const { spawn } = require('child_process');
2
+
2
3
  const outputs = require('./outputs');
3
4
 
4
5
  /**
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);
@@ -1,13 +1,15 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
- const Ajv = require('ajv');
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',
@@ -1,5 +1,6 @@
1
1
  const loadJson = require('load-json-file');
2
2
  const writeJson = require('write-json-file');
3
+
3
4
  const {
4
5
  STATE_FILE_PATH,
5
6
  } = require('./consts');
@@ -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,10 @@
1
+ class Spider {
2
+ constructor(data) {
3
+ this.name = data.name;
4
+ this.class_name = data.class_name;
5
+ this.start_urls = data.start_urls;
6
+ this.pathname = data.pathname;
7
+ }
8
+ }
9
+
10
+ module.exports = { Spider };
@@ -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 };
@@ -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 {
@@ -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
- const { cryptoRandomObjectId } = require('@apify/utilities');
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 path = require('path');
1
+ const {
2
+ execSync,
3
+ spawnSync,
4
+ } = require('child_process');
2
5
  const fs = require('fs');
3
- const mime = require('mime');
4
- const { getEncoding } = require('istextorbinary');
5
- const _ = require('underscore');
6
- const globby = require('globby');
7
- const archiver = require('archiver-promise');
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 https = require('https');
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
- // eslint-disable-next-line max-len
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(process.cwd(), 'package.json'));
583
- const isActorInPython = fs.existsSync(path.join(process.cwd(), 'src/__main__.py'));
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
  };
@@ -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
- const {
7
- CHECK_VERSION_EVERY_MILLIS,
8
- } = require('./consts');
9
- const {
10
- warning,
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.