apify-cli 0.19.2-beta.0 → 0.19.2-beta.10
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/package.json +6 -9
- package/src/commands/create.js +2 -1
- package/src/commands/init.js +14 -3
- package/src/commands/run.js +31 -8
- package/src/lib/consts.js +2 -0
- package/src/lib/project_analyzer.js +11 -0
- package/src/lib/projects/CrawleeAnalyzer.js +37 -0
- package/src/lib/projects/OldApifySDKAnalyzer.js +61 -0
- package/src/lib/scrapy-wrapper/index.js +2 -2
- package/src/lib/utils.js +24 -2
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apify-cli",
|
|
3
|
-
"version": "0.19.2-beta.
|
|
3
|
+
"version": "0.19.2-beta.10",
|
|
4
4
|
"description": "Apify command-line interface helps you create, develop, build and run Apify actors, and manage the Apify cloud platform.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"test": "cross-env APIFY_CLI_SKIP_UPDATE_CHECK=1
|
|
8
|
-
"test-python": "
|
|
7
|
+
"test": "cross-env APIFY_CLI_SKIP_UPDATE_CHECK=1 vitest run",
|
|
8
|
+
"test-python": "cross-env APIFY_CLI_SKIP_UPDATE_CHECK=1 vitest run -t '.*\\[python\\]'",
|
|
9
9
|
"lint": "eslint src test",
|
|
10
10
|
"lint:fix": "eslint src test --fix",
|
|
11
11
|
"commands-md": "npm run manifest && oclif-dev readme",
|
|
@@ -66,9 +66,9 @@
|
|
|
66
66
|
"@root/walk": "^1.1.0",
|
|
67
67
|
"adm-zip": "^0.5.10",
|
|
68
68
|
"ajv": "^8.12.0",
|
|
69
|
-
"apify-client": "^2.
|
|
69
|
+
"apify-client": "^2.9.0",
|
|
70
70
|
"archiver-promise": "^1.0.0",
|
|
71
|
-
"axios": "^1.6.
|
|
71
|
+
"axios": "^1.6.7",
|
|
72
72
|
"chalk": "^4.1.2",
|
|
73
73
|
"computer-name": "^0.1.0",
|
|
74
74
|
"configparser": "^0.3.10",
|
|
@@ -98,12 +98,9 @@
|
|
|
98
98
|
"@apify/eslint-config": "^0.4.0",
|
|
99
99
|
"@oclif/dev-cli": "^1.26.0",
|
|
100
100
|
"@oclif/test": "^2.1.0",
|
|
101
|
-
"chai": "^4.3.4",
|
|
102
|
-
"chai-match": "^1.1.1",
|
|
103
101
|
"cross-env": "^7.0.3",
|
|
104
102
|
"eslint": "^8.53.0",
|
|
105
|
-
"
|
|
106
|
-
"sinon": "^17.0.0"
|
|
103
|
+
"vitest": "^1.0.4"
|
|
107
104
|
},
|
|
108
105
|
"oclif": {
|
|
109
106
|
"bin": "apify",
|
package/src/commands/create.js
CHANGED
|
@@ -70,7 +70,7 @@ class CreateCommand extends ApifyCommand {
|
|
|
70
70
|
try {
|
|
71
71
|
fs.mkdirSync(actFolderDir);
|
|
72
72
|
} catch (err) {
|
|
73
|
-
if (err
|
|
73
|
+
if (err?.code === 'EEXIST') {
|
|
74
74
|
outputs.error(`Cannot create new actor, directory '${actorName}' already exists. `
|
|
75
75
|
+ 'You can use "apify init" to create a local actor environment inside an existing directory.');
|
|
76
76
|
return;
|
|
@@ -162,6 +162,7 @@ class CreateCommand extends ApifyCommand {
|
|
|
162
162
|
|
|
163
163
|
if (dependenciesInstalled) {
|
|
164
164
|
outputs.success(`Actor '${actorName}' was created. To run it, run "cd ${actorName}" and "apify run".`);
|
|
165
|
+
outputs.success('To run your code in the cloud, run "apify push" and deploy your code to Apify Console.');
|
|
165
166
|
if (messages?.postCreate) {
|
|
166
167
|
outputs.info(messages?.postCreate);
|
|
167
168
|
}
|
package/src/commands/init.js
CHANGED
|
@@ -9,7 +9,7 @@ const { createPrefilledInputFileFromInputSchema } = require('../lib/input_schema
|
|
|
9
9
|
const outputs = require('../lib/outputs');
|
|
10
10
|
const { ProjectAnalyzer } = require('../lib/project_analyzer');
|
|
11
11
|
const { wrapScrapyProject } = require('../lib/scrapy-wrapper');
|
|
12
|
-
const { setLocalConfig, setLocalEnv, getLocalConfig, getLocalConfigOrThrow, detectLocalActorLanguage } = require('../lib/utils');
|
|
12
|
+
const { setLocalConfig, setLocalEnv, getLocalConfig, getLocalConfigOrThrow, detectLocalActorLanguage, validateActorName } = require('../lib/utils');
|
|
13
13
|
|
|
14
14
|
class InitCommand extends ApifyCommand {
|
|
15
15
|
async run() {
|
|
@@ -34,8 +34,19 @@ class InitCommand extends ApifyCommand {
|
|
|
34
34
|
outputs.warning(`Skipping creation of "${LOCAL_CONFIG_PATH}", the file already exists in the current directory.`);
|
|
35
35
|
} else {
|
|
36
36
|
if (!actorName) {
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
let response = null;
|
|
38
|
+
|
|
39
|
+
while (!response) {
|
|
40
|
+
try {
|
|
41
|
+
const answer = await inquirer.prompt([{ name: 'actName', message: 'Actor name:', default: path.basename(cwd) }]);
|
|
42
|
+
validateActorName(answer.actName);
|
|
43
|
+
response = answer;
|
|
44
|
+
} catch (err) {
|
|
45
|
+
outputs.error(err.message);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
({ actName: actorName } = response);
|
|
39
50
|
}
|
|
40
51
|
// Migrate apify.json to .actor/actor.json
|
|
41
52
|
const localConfig = { ...EMPTY_LOCAL_CONFIG, ...await getLocalConfigOrThrow() };
|
package/src/commands/run.js
CHANGED
|
@@ -16,7 +16,7 @@ const { replaceSecretsValue } = require('../lib/secrets');
|
|
|
16
16
|
const {
|
|
17
17
|
getLocalUserInfo, purgeDefaultQueue, purgeDefaultKeyValueStore,
|
|
18
18
|
purgeDefaultDataset, getLocalConfigOrThrow, getNpmCmd, checkIfStorageIsEmpty,
|
|
19
|
-
detectLocalActorLanguage, isPythonVersionSupported, getPythonCommand, isNodeVersionSupported,
|
|
19
|
+
detectLocalActorLanguage, isPythonVersionSupported, getPythonCommand, isNodeVersionSupported, getLocalStorageDir,
|
|
20
20
|
} = require('../lib/utils');
|
|
21
21
|
|
|
22
22
|
class RunCommand extends ApifyCommand {
|
|
@@ -29,9 +29,12 @@ class RunCommand extends ApifyCommand {
|
|
|
29
29
|
const packageJsonPath = path.join(cwd, 'package.json');
|
|
30
30
|
const mainPyPath = path.join(cwd, 'src/__main__.py');
|
|
31
31
|
|
|
32
|
+
const projectType = ProjectAnalyzer.getProjectType(cwd);
|
|
33
|
+
const actualStoragePath = getLocalStorageDir();
|
|
34
|
+
|
|
32
35
|
const packageJsonExists = fs.existsSync(packageJsonPath);
|
|
33
36
|
const mainPyExists = fs.existsSync(mainPyPath);
|
|
34
|
-
const isScrapyProject =
|
|
37
|
+
const isScrapyProject = projectType === PROJECT_TYPES.SCRAPY;
|
|
35
38
|
|
|
36
39
|
if (!packageJsonExists && !mainPyExists && !isScrapyProject) {
|
|
37
40
|
throw new Error(
|
|
@@ -40,25 +43,43 @@ class RunCommand extends ApifyCommand {
|
|
|
40
43
|
);
|
|
41
44
|
}
|
|
42
45
|
|
|
43
|
-
if (fs.existsSync(LEGACY_LOCAL_STORAGE_DIR) && !fs.existsSync(
|
|
44
|
-
fs.renameSync(LEGACY_LOCAL_STORAGE_DIR,
|
|
45
|
-
warning(
|
|
46
|
+
if (fs.existsSync(LEGACY_LOCAL_STORAGE_DIR) && !fs.existsSync(actualStoragePath)) {
|
|
47
|
+
fs.renameSync(LEGACY_LOCAL_STORAGE_DIR, actualStoragePath);
|
|
48
|
+
warning(`The legacy 'apify_storage' directory was renamed to '${actualStoragePath}' to align it with Apify SDK v3.`
|
|
46
49
|
+ ' Contents were left intact.');
|
|
47
50
|
}
|
|
48
51
|
|
|
52
|
+
let CRAWLEE_PURGE_ON_START = '0';
|
|
53
|
+
|
|
49
54
|
// Purge stores
|
|
50
55
|
if (flags.purge) {
|
|
51
|
-
|
|
52
|
-
|
|
56
|
+
switch (projectType) {
|
|
57
|
+
case PROJECT_TYPES.CRAWLEE: {
|
|
58
|
+
CRAWLEE_PURGE_ON_START = '1';
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
case PROJECT_TYPES.PRE_CRAWLEE_APIFY_SDK: {
|
|
62
|
+
await Promise.all([purgeDefaultQueue(), purgeDefaultKeyValueStore(), purgeDefaultDataset()]);
|
|
63
|
+
info('All default local stores were purged.');
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
default: {
|
|
67
|
+
// TODO: Python SDK too
|
|
68
|
+
}
|
|
69
|
+
}
|
|
53
70
|
}
|
|
71
|
+
|
|
72
|
+
// TODO: deprecate these flags
|
|
54
73
|
if (flags.purgeQueue) {
|
|
55
74
|
await purgeDefaultQueue();
|
|
56
75
|
info('Default local request queue was purged.');
|
|
57
76
|
}
|
|
77
|
+
|
|
58
78
|
if (flags.purgeDataset) {
|
|
59
79
|
await purgeDefaultDataset();
|
|
60
80
|
info('Default local dataset was purged.');
|
|
61
81
|
}
|
|
82
|
+
|
|
62
83
|
if (flags.purgeKeyValueStore) {
|
|
63
84
|
await purgeDefaultKeyValueStore();
|
|
64
85
|
info('Default local key-value store was purged.');
|
|
@@ -74,7 +95,9 @@ class RunCommand extends ApifyCommand {
|
|
|
74
95
|
|
|
75
96
|
// Attach env vars from local config files
|
|
76
97
|
const localEnvVars = {
|
|
77
|
-
[APIFY_ENV_VARS.LOCAL_STORAGE_DIR]:
|
|
98
|
+
[APIFY_ENV_VARS.LOCAL_STORAGE_DIR]: actualStoragePath,
|
|
99
|
+
CRAWLEE_STORAGE_DIR: actualStoragePath,
|
|
100
|
+
CRAWLEE_PURGE_ON_START,
|
|
78
101
|
};
|
|
79
102
|
if (proxy && proxy.password) localEnvVars[APIFY_ENV_VARS.PROXY_PASSWORD] = proxy.password;
|
|
80
103
|
if (userId) localEnvVars[APIFY_ENV_VARS.USER_ID] = userId;
|
package/src/lib/consts.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const { PROJECT_TYPES } = require('./consts');
|
|
2
|
+
const { CrawleeAnalyzer } = require('./projects/CrawleeAnalyzer');
|
|
3
|
+
const { OldApifySDKAnalyzer } = require('./projects/OldApifySDKAnalyzer');
|
|
2
4
|
const { ScrapyProjectAnalyzer } = require('./scrapy-wrapper/ScrapyProjectAnalyzer');
|
|
3
5
|
|
|
4
6
|
const analyzers = [
|
|
@@ -6,6 +8,14 @@ const analyzers = [
|
|
|
6
8
|
type: PROJECT_TYPES.SCRAPY,
|
|
7
9
|
analyzer: ScrapyProjectAnalyzer,
|
|
8
10
|
},
|
|
11
|
+
{
|
|
12
|
+
type: PROJECT_TYPES.CRAWLEE,
|
|
13
|
+
analyzer: CrawleeAnalyzer,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
type: PROJECT_TYPES.PRE_CRAWLEE_APIFY_SDK,
|
|
17
|
+
analyzer: OldApifySDKAnalyzer,
|
|
18
|
+
},
|
|
9
19
|
];
|
|
10
20
|
|
|
11
21
|
class ProjectAnalyzer {
|
|
@@ -17,6 +27,7 @@ class ProjectAnalyzer {
|
|
|
17
27
|
|
|
18
28
|
return a.analyzer.isApplicable(pathname);
|
|
19
29
|
});
|
|
30
|
+
|
|
20
31
|
return analyzer?.type || PROJECT_TYPES.UNKNOWN;
|
|
21
32
|
}
|
|
22
33
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const { existsSync, readFileSync } = require('fs');
|
|
2
|
+
const { join } = require('path');
|
|
3
|
+
|
|
4
|
+
const CRAWLEE_PACKAGES = [
|
|
5
|
+
'crawlee',
|
|
6
|
+
'@crawlee/core',
|
|
7
|
+
'@crawlee/puppeteer',
|
|
8
|
+
'@crawlee/playwright',
|
|
9
|
+
'@crawlee/cheerio',
|
|
10
|
+
'@crawlee/jsdom',
|
|
11
|
+
'@crawlee/linkedom',
|
|
12
|
+
'@crawlee/http',
|
|
13
|
+
'@crawlee/browser',
|
|
14
|
+
'@crawlee/basic',
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
class CrawleeAnalyzer {
|
|
18
|
+
static isApplicable(pathname) {
|
|
19
|
+
const hasPackageJson = existsSync(join(pathname, 'package.json'));
|
|
20
|
+
|
|
21
|
+
if (!hasPackageJson) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const packageJson = readFileSync(join(pathname, 'package.json'), 'utf8');
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const packageJsonParsed = JSON.parse(packageJson);
|
|
29
|
+
|
|
30
|
+
return CRAWLEE_PACKAGES.some((pkg) => packageJsonParsed?.dependencies?.[pkg] !== undefined);
|
|
31
|
+
} catch (err) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
exports.CrawleeAnalyzer = CrawleeAnalyzer;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const { existsSync, readFileSync } = require('fs');
|
|
2
|
+
const { join } = require('path');
|
|
3
|
+
|
|
4
|
+
const { lt } = require('semver');
|
|
5
|
+
|
|
6
|
+
const VERSION_WHEN_APIFY_MOVED_TO_CRAWLEE = '3.0.0';
|
|
7
|
+
const CRAWLEE_PACKAGES = [
|
|
8
|
+
'crawlee',
|
|
9
|
+
'@crawlee/core',
|
|
10
|
+
'@crawlee/puppeteer',
|
|
11
|
+
'@crawlee/playwright',
|
|
12
|
+
'@crawlee/cheerio',
|
|
13
|
+
'@crawlee/jsdom',
|
|
14
|
+
'@crawlee/linkedom',
|
|
15
|
+
'@crawlee/http',
|
|
16
|
+
'@crawlee/browser',
|
|
17
|
+
'@crawlee/basic',
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
class OldApifySDKAnalyzer {
|
|
21
|
+
static isApplicable(pathname) {
|
|
22
|
+
const hasPackageJson = existsSync(join(pathname, 'package.json'));
|
|
23
|
+
|
|
24
|
+
if (!hasPackageJson) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const packageJson = readFileSync(join(pathname, 'package.json'), 'utf8');
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const packageJsonParsed = JSON.parse(packageJson);
|
|
32
|
+
|
|
33
|
+
// If they have crawlee as a dependency, likely to use crawlee
|
|
34
|
+
if (CRAWLEE_PACKAGES.some((pkg) => packageJsonParsed?.dependencies?.[pkg] !== undefined)) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const apifyVersion = packageJsonParsed?.dependencies?.apify;
|
|
39
|
+
if (!apifyVersion) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// We cannot infer
|
|
44
|
+
if (apifyVersion === '*') {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let actualVersion = apifyVersion;
|
|
49
|
+
|
|
50
|
+
if (apifyVersion.startsWith('~') || apifyVersion.startsWith('^')) {
|
|
51
|
+
actualVersion = apifyVersion.slice(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return lt(actualVersion, VERSION_WHEN_APIFY_MOVED_TO_CRAWLEE);
|
|
55
|
+
} catch (err) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
exports.OldApifySDKAnalyzer = OldApifySDKAnalyzer;
|
|
@@ -9,7 +9,7 @@ const inquirer = require('inquirer');
|
|
|
9
9
|
|
|
10
10
|
const { ScrapyProjectAnalyzer } = require('./ScrapyProjectAnalyzer');
|
|
11
11
|
const outputs = require('../outputs');
|
|
12
|
-
const { downloadAndUnzip } = require('../utils');
|
|
12
|
+
const { downloadAndUnzip, sanitizeActorName } = require('../utils');
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Files that should be concatenated instead of copied (and overwritten).
|
|
@@ -86,7 +86,7 @@ async function wrapScrapyProject({ projectPath }) {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
const templateBindings = {
|
|
89
|
-
botName: analyzer.settings.BOT_NAME,
|
|
89
|
+
botName: sanitizeActorName(analyzer.settings.BOT_NAME),
|
|
90
90
|
scrapy_settings_module: analyzer.configuration.get('settings', 'default'),
|
|
91
91
|
apify_module_path: `${analyzer.settings.BOT_NAME}.apify`,
|
|
92
92
|
spider_class_name: analyzer.getAvailableSpiders()[spiderIndex].class_name,
|
package/src/lib/utils.js
CHANGED
|
@@ -21,6 +21,7 @@ const {
|
|
|
21
21
|
const AdmZip = require('adm-zip');
|
|
22
22
|
const { ApifyClient } = require('apify-client');
|
|
23
23
|
const archiver = require('archiver-promise');
|
|
24
|
+
const axios = require('axios');
|
|
24
25
|
const escapeStringRegexp = require('escape-string-regexp');
|
|
25
26
|
const globby = require('globby');
|
|
26
27
|
const inquirer = require('inquirer');
|
|
@@ -80,7 +81,7 @@ const MIGRATED_APIFY_JSON_PROPERTIES = ['name', 'version', 'buildTag'];
|
|
|
80
81
|
const getLocalStorageDir = () => {
|
|
81
82
|
const envVar = APIFY_ENV_VARS.LOCAL_STORAGE_DIR;
|
|
82
83
|
|
|
83
|
-
return process.env[envVar] || DEFAULT_LOCAL_STORAGE_DIR;
|
|
84
|
+
return process.env[envVar] || process.env.CRAWLEE_STORAGE_DIR || DEFAULT_LOCAL_STORAGE_DIR;
|
|
84
85
|
};
|
|
85
86
|
const getLocalKeyValueStorePath = (storeId) => {
|
|
86
87
|
const envVar = ACTOR_ENV_VARS.DEFAULT_KEY_VALUE_STORE_ID;
|
|
@@ -139,7 +140,14 @@ const getApifyClientOptions = (token, apiBaseUrl) => {
|
|
|
139
140
|
token,
|
|
140
141
|
baseUrl: apiBaseUrl || process.env.APIFY_CLIENT_BASE_URL,
|
|
141
142
|
requestInterceptors: [(config) => {
|
|
142
|
-
config.headers
|
|
143
|
+
if (!config.headers) {
|
|
144
|
+
config.headers = new axios.AxiosHeaders();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
for (const [key, value] of Object.entries(APIFY_CLIENT_DEFAULT_HEADERS)) {
|
|
148
|
+
config.headers[key] = value;
|
|
149
|
+
}
|
|
150
|
+
|
|
143
151
|
return config;
|
|
144
152
|
}],
|
|
145
153
|
};
|
|
@@ -536,6 +544,19 @@ const validateActorName = (actorName) => {
|
|
|
536
544
|
}
|
|
537
545
|
};
|
|
538
546
|
|
|
547
|
+
const sanitizeActorName = (actorName) => {
|
|
548
|
+
let sanitizedName = actorName
|
|
549
|
+
.replaceAll(/[^a-zA-Z0-9-]/g, '-');
|
|
550
|
+
|
|
551
|
+
if (sanitizedName.length < ACTOR_NAME.MIN_LENGTH) {
|
|
552
|
+
sanitizedName = `${sanitizedName}-apify-actor`;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
sanitizedName = sanitizedName.replaceAll(/^-+/g, '').replaceAll(/-+$/g, '');
|
|
556
|
+
|
|
557
|
+
return sanitizedName.slice(0, ACTOR_NAME.MAX_LENGTH);
|
|
558
|
+
};
|
|
559
|
+
|
|
539
560
|
const getPythonCommand = (directory) => {
|
|
540
561
|
const pythonVenvPath = /^win/.test(process.platform)
|
|
541
562
|
? 'Scripts/python.exe'
|
|
@@ -665,4 +686,5 @@ module.exports = {
|
|
|
665
686
|
detectNpmVersion,
|
|
666
687
|
detectLocalActorLanguage,
|
|
667
688
|
downloadAndUnzip,
|
|
689
|
+
sanitizeActorName,
|
|
668
690
|
};
|