apify-cli 0.14.1-beta.0 → 0.14.2-beta.0

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.
@@ -1 +1 @@
1
- {"version":"0.14.1","commands":{"call":{"id":"call","description":"Runs a specific actor remotely on the Apify cloud platform.\nThe actor is run under your current Apify account. Therefore you need to be logged in by calling \"apify login\". It takes input for the actor from the default local key-value store by default.","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{"build":{"name":"build","type":"option","char":"b","description":"Tag or number of the build to run (e.g. \"latest\" or \"1.2.34\").","required":false},"timeout":{"name":"timeout","type":"option","char":"t","description":"Timeout for the actor run in seconds. Zero value means there is no timeout.","required":false},"memory":{"name":"memory","type":"option","char":"m","description":"Amount of memory allocated for the actor run, in megabytes.","required":false},"wait-for-finish":{"name":"wait-for-finish","type":"option","char":"w","description":"Seconds for waiting to run to finish, if no value passed, it waits forever.","required":false}},"args":[{"name":"actId","description":"Name or ID of the actor to run (e.g. \"apify/hello-world\" or \"E2jjCZBezvAZnX8Rb\"). If not provided, the command runs the remote actor specified in the \".actor/actor.json\" file.","required":false}]},"check-version":{"id":"check-version","description":"Checks that installed Apify CLI version is up to date.","pluginName":"apify-cli","pluginType":"core","hidden":true,"aliases":["cv"],"flags":{"enforce-update":{"name":"enforce-update","type":"boolean","char":"e","description":"[Optional] Enforce version update from NPM","required":false,"allowNo":false}},"args":[]},"create":{"id":"create","description":"Creates a new actor project directory from a selected boilerplate template.","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{"template":{"name":"template","type":"option","char":"t","description":"Template for the actor. If not provided, the command will prompt for it.\nVisit https://raw.githubusercontent.com/apify/actor-templates/master/templates/manifest.json to find available template names.","required":false},"skip-dependency-install":{"name":"skip-dependency-install","type":"boolean","description":"Skip installing actor dependencies.","required":false,"allowNo":false},"template-archive-url":{"name":"template-archive-url","type":"option","description":"Actor template archive url. Useful for developing new templates.","hidden":true,"required":false}},"args":[{"name":"actorName","description":"Name of the actor and its directory","required":false}]},"edit-input-schema":{"id":"edit-input-schema","description":"Lets you edit your input schema that would be used on the platform in a visual input schema editor.","pluginName":"apify-cli","pluginType":"core","hidden":true,"aliases":["eis"],"flags":{},"args":[{"name":"path","description":"Optional path to your INPUT_SCHEMA.json file. If not provided default platform location for input schema is used.","required":false}]},"info":{"id":"info","description":"Displays information about the currently active Apify account.\nThe information is printed to the console.","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{},"args":[]},"init":{"id":"init","description":"Initializes a new actor project in an existing directory.\nThe command only creates the \".actor/actor.json\" file and the \"storage\" directory in the current directory, but will not touch anything else.\n\nWARNING: The directory at \"storage\" will be overwritten if it already exists.","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"actorName","description":"Name of the actor. If not provided, you will be prompted for it.","required":false}]},"login":{"id":"login","description":"Logs in to your Apify account using a provided API token.\nThe API token and other account information is stored in the ~/.apify directory, from where it is read by all other \"apify\" commands. To log out, call \"apify logout\".","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{"token":{"name":"token","type":"option","char":"t","description":"[Optional] Apify API token","required":false}},"args":[]},"logout":{"id":"logout","description":"Logs out of your Apify account.\nThe command deletes the API token and all other account information stored in the ~/.apify directory. To log in again, call \"apify login\".","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{},"args":[]},"push":{"id":"push","description":"Uploads the actor to the Apify platform and builds it there.\nThe actor settings are read from the \".actor/actor.json\" file in the current directory, but they can be overridden using command-line options.\nNOTE: If the source files are smaller than 3 MB then they are uploaded as \n\"Multiple source files\", otherwise they are uploaded as \"Zip file\".\n\nWARNING: If the target actor already exists in your Apify account, it will be overwritten!","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{"version-number":{"name":"version-number","type":"option","description":"DEPRECATED: Use flag version instead. Actor version number to which the files should be pushed. By default, it is taken from the \".actor/actor.json\" file.","required":false},"version":{"name":"version","type":"option","char":"v","description":"Actor version number to which the files should be pushed. By default, it is taken from the \".actor/actor.json\" file.","required":false},"build-tag":{"name":"build-tag","type":"option","char":"b","description":"Build tag to be applied to the successful actor build. By default, it is taken from the \".actor/actor.json\" file","required":false},"wait-for-finish":{"name":"wait-for-finish","type":"option","char":"w","description":"Seconds for waiting to build to finish, if no value passed, it waits forever.","required":false}},"args":[{"name":"actorId","description":"ID of an existing actor on the Apify platform where the files will be pushed. If not provided, the command will create or modify the actor with the name specified in \".actor/actor.json\" file.","required":false}]},"run":{"id":"run","description":"Runs the actor locally in the current directory by executing \"npm start\".\nIt sets various APIFY_XYZ environment variables in order to provide a working execution environment for the actor. For example, this causes the actor input, as well as all other data in key-value stores, datasets or request queues to be stored in the \"storage\" directory, rather than on the Apify platform.\n\nNOTE: You can override the command's default behavior by overriding the npm start script value in a package.json file. You can set up your own main file or environment variables by changing it.","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{"purge":{"name":"purge","type":"boolean","char":"p","description":"Shortcut that combines the --purge-queue, --purge-dataset and --purge-key-value-store options.","required":false,"allowNo":false},"purge-queue":{"name":"purge-queue","type":"boolean","description":"Deletes the local directory containing the default request queue before the run starts.","required":false,"allowNo":false},"purge-dataset":{"name":"purge-dataset","type":"boolean","description":"Deletes the local directory containing the default dataset before the run starts.","required":false,"allowNo":false},"purge-key-value-store":{"name":"purge-key-value-store","type":"boolean","description":"Deletes all records from the default key-value store in the local directory before the run starts, except for the \"INPUT\" key.","required":false,"allowNo":false}},"args":[]},"vis":{"id":"vis","description":"Validates input schema and prints errors found.\nThe input schema for the actor is used from these locations in order of preference.\nThe first one found is validated as it would be the one used on the Apify platform.\n1. Directly embedded object in \".actor/actor.json\" under 'input' key\n2. Path to JSON file referenced in \".actor/actor.json\" under 'input' key\n3. JSON file at .actor/INPUT_SCHEMA.json\n4. JSON file at INPUT_SCHEMA.json\n\nYou can also pass any custom path to your input schema to have it validated instead.\n","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"path","description":"Optional path to your INPUT_SCHEMA.json file. If not provided ./INPUT_SCHEMA.json is used.","required":false}]},"actor:get-input":{"id":"actor:get-input","description":"Gets the actor input value from the default key-value store associated with the actor run.","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{},"args":[]},"actor:get-value":{"id":"actor:get-value","description":"Gets a value from the default key-value store associated with the actor run.","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"key","description":"Key of the record in key-value store","required":true}]},"actor":{"id":"actor","description":"Commands are designed to be used in actor runs. All commands are in PoC state, do not use in production environments.\n","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{},"args":[]},"actor:push-data":{"id":"actor:push-data","description":"Stores an object or an array of objects to the default dataset of the actor run.\nIt is possible to pass data using item argument or stdin.\nPassing data using argument:\n$ apify actor:push-data {\"foo\": \"bar\"}\nPassing data using stdin with pipe:\n$ cat ./test.json | apify actor:push-data\n","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"item","description":"JSON string with one object or array of objects containing data to be stored in the default dataset.","required":false}]},"actor:set-value":{"id":"actor:set-value","description":"Sets or removes record into the default KeyValueStore associated with the actor run.\nIt is possible to pass data using argument or stdin.\nPassing data using argument:\n$ apify actor:set-value KEY my-value\nPassing data using stdin with pipe:\n$ cat ./my-text-file.txt | apify actor:set-value KEY --contentType text/plain\n","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{"contentType":{"name":"contentType","type":"option","char":"c","description":"Specifies a custom MIME content type of the record. By default \"application/json\" is used.","required":false}},"args":[{"name":"key","description":"Key of the record in key-value store.","required":true},{"name":"value","description":"Record data, which can be one of the following values:\n- If empty, the record in the key-value store is deleted.\n- If no `contentType` flag is specified, value is expected to be any JSON string value.\n- If options.contentType is set, value is taken as is.","required":false}]},"secrets:add":{"id":"secrets:add","description":"Adds a new secret value.\nThe secrets are stored to a file at ~/.apify","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"name","description":"Name of the secret","required":true},{"name":"value","description":"Value of the secret","required":true}]},"secrets":{"id":"secrets","description":"Manages secret values for actor environment variables.\n\nExample:\n$ apify secrets:add mySecret TopSecretValue123\n\nNow the \"mySecret\" value can be used in an environment variable defined in \".actor/actor.json\" file by adding the \"@\" prefix:\n\n{\n \"actorSpecification\": 1,\n \"name\": \"my_actor\",\n \"environmentVariables\": { \"SECRET_ENV_VAR\": \"@mySecret\" },\n \"version\": \"0.1\n}\n\nWhen the actor is pushed to Apify cloud, the \"SECRET_ENV_VAR\" and its value is stored as a secret environment variable of the actor.","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{},"args":[]},"secrets:rm":{"id":"secrets:rm","description":"Removes the secret.","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"name","description":"Name of the secret","required":true}]}}}
1
+ {"version":"0.14.2","commands":{"call":{"id":"call","description":"Runs a specific actor remotely on the Apify cloud platform.\nThe actor is run under your current Apify account. Therefore you need to be logged in by calling \"apify login\". It takes input for the actor from the default local key-value store by default.","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{"build":{"name":"build","type":"option","char":"b","description":"Tag or number of the build to run (e.g. \"latest\" or \"1.2.34\").","required":false},"timeout":{"name":"timeout","type":"option","char":"t","description":"Timeout for the actor run in seconds. Zero value means there is no timeout.","required":false},"memory":{"name":"memory","type":"option","char":"m","description":"Amount of memory allocated for the actor run, in megabytes.","required":false},"wait-for-finish":{"name":"wait-for-finish","type":"option","char":"w","description":"Seconds for waiting to run to finish, if no value passed, it waits forever.","required":false}},"args":[{"name":"actId","description":"Name or ID of the actor to run (e.g. \"apify/hello-world\" or \"E2jjCZBezvAZnX8Rb\"). If not provided, the command runs the remote actor specified in the \".actor/actor.json\" file.","required":false}]},"check-version":{"id":"check-version","description":"Checks that installed Apify CLI version is up to date.","pluginName":"apify-cli","pluginType":"core","hidden":true,"aliases":["cv"],"flags":{"enforce-update":{"name":"enforce-update","type":"boolean","char":"e","description":"[Optional] Enforce version update from NPM","required":false,"allowNo":false}},"args":[]},"create":{"id":"create","description":"Creates a new actor project directory from a selected boilerplate template.","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{"template":{"name":"template","type":"option","char":"t","description":"Template for the actor. If not provided, the command will prompt for it.\nVisit https://raw.githubusercontent.com/apify/actor-templates/master/templates/manifest.json to find available template names.","required":false},"skip-dependency-install":{"name":"skip-dependency-install","type":"boolean","description":"Skip installing actor dependencies.","required":false,"allowNo":false},"template-archive-url":{"name":"template-archive-url","type":"option","description":"Actor template archive url. Useful for developing new templates.","hidden":true,"required":false}},"args":[{"name":"actorName","description":"Name of the actor and its directory","required":false}]},"edit-input-schema":{"id":"edit-input-schema","description":"Lets you edit your input schema that would be used on the platform in a visual input schema editor.","pluginName":"apify-cli","pluginType":"core","hidden":true,"aliases":["eis"],"flags":{},"args":[{"name":"path","description":"Optional path to your INPUT_SCHEMA.json file. If not provided default platform location for input schema is used.","required":false}]},"info":{"id":"info","description":"Displays information about the currently active Apify account.\nThe information is printed to the console.","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{},"args":[]},"init":{"id":"init","description":"Initializes a new actor project in an existing directory.\nThe command only creates the \".actor/actor.json\" file and the \"storage\" directory in the current directory, but will not touch anything else.\n\nWARNING: The directory at \"storage\" will be overwritten if it already exists.","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"actorName","description":"Name of the actor. If not provided, you will be prompted for it.","required":false}]},"login-new":{"id":"login-new","description":"Logs in to your Apify account using your API token.\nThe API token and other account information is stored in the ~/.apify directory, from where it is read by all other \"apify\" commands. To log out, call \"apify logout\".","pluginName":"apify-cli","pluginType":"core","hidden":true,"aliases":[],"flags":{"token":{"name":"token","type":"option","char":"t","description":"[Optional] Apify API token","required":false}},"args":[]},"login":{"id":"login","description":"Logs in to your Apify account using a provided API token.\nThe API token and other account information is stored in the ~/.apify directory, from where it is read by all other \"apify\" commands. To log out, call \"apify logout\".","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{"token":{"name":"token","type":"option","char":"t","description":"[Optional] Apify API token","required":false}},"args":[]},"logout":{"id":"logout","description":"Logs out of your Apify account.\nThe command deletes the API token and all other account information stored in the ~/.apify directory. To log in again, call \"apify login\".","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{},"args":[]},"push":{"id":"push","description":"Uploads the actor to the Apify platform and builds it there.\nThe actor settings are read from the \".actor/actor.json\" file in the current directory, but they can be overridden using command-line options.\nNOTE: If the source files are smaller than 3 MB then they are uploaded as \n\"Multiple source files\", otherwise they are uploaded as \"Zip file\".\n\nWARNING: If the target actor already exists in your Apify account, it will be overwritten!","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{"version-number":{"name":"version-number","type":"option","description":"DEPRECATED: Use flag version instead. Actor version number to which the files should be pushed. By default, it is taken from the \".actor/actor.json\" file.","required":false},"version":{"name":"version","type":"option","char":"v","description":"Actor version number to which the files should be pushed. By default, it is taken from the \".actor/actor.json\" file.","required":false},"build-tag":{"name":"build-tag","type":"option","char":"b","description":"Build tag to be applied to the successful actor build. By default, it is taken from the \".actor/actor.json\" file","required":false},"wait-for-finish":{"name":"wait-for-finish","type":"option","char":"w","description":"Seconds for waiting to build to finish, if no value passed, it waits forever.","required":false}},"args":[{"name":"actorId","description":"ID of an existing actor on the Apify platform where the files will be pushed. If not provided, the command will create or modify the actor with the name specified in \".actor/actor.json\" file.","required":false}]},"run":{"id":"run","description":"Runs the actor locally in the current directory by executing \"npm start\".\nIt sets various APIFY_XYZ environment variables in order to provide a working execution environment for the actor. For example, this causes the actor input, as well as all other data in key-value stores, datasets or request queues to be stored in the \"storage\" directory, rather than on the Apify platform.\n\nNOTE: You can override the command's default behavior by overriding the npm start script value in a package.json file. You can set up your own main file or environment variables by changing it.","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{"purge":{"name":"purge","type":"boolean","char":"p","description":"Shortcut that combines the --purge-queue, --purge-dataset and --purge-key-value-store options.","required":false,"allowNo":false},"purge-queue":{"name":"purge-queue","type":"boolean","description":"Deletes the local directory containing the default request queue before the run starts.","required":false,"allowNo":false},"purge-dataset":{"name":"purge-dataset","type":"boolean","description":"Deletes the local directory containing the default dataset before the run starts.","required":false,"allowNo":false},"purge-key-value-store":{"name":"purge-key-value-store","type":"boolean","description":"Deletes all records from the default key-value store in the local directory before the run starts, except for the \"INPUT\" key.","required":false,"allowNo":false}},"args":[]},"vis":{"id":"vis","description":"Validates input schema and prints errors found.\nThe input schema for the actor is used from these locations in order of preference.\nThe first one found is validated as it would be the one used on the Apify platform.\n1. Directly embedded object in \".actor/actor.json\" under 'input' key\n2. Path to JSON file referenced in \".actor/actor.json\" under 'input' key\n3. JSON file at .actor/INPUT_SCHEMA.json\n4. JSON file at INPUT_SCHEMA.json\n\nYou can also pass any custom path to your input schema to have it validated instead.\n","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"path","description":"Optional path to your INPUT_SCHEMA.json file. If not provided ./INPUT_SCHEMA.json is used.","required":false}]},"actor:get-input":{"id":"actor:get-input","description":"Gets the actor input value from the default key-value store associated with the actor run.","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{},"args":[]},"actor:get-value":{"id":"actor:get-value","description":"Gets a value from the default key-value store associated with the actor run.","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"key","description":"Key of the record in key-value store","required":true}]},"actor":{"id":"actor","description":"Commands are designed to be used in actor runs. All commands are in PoC state, do not use in production environments.\n","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{},"args":[]},"actor:push-data":{"id":"actor:push-data","description":"Stores an object or an array of objects to the default dataset of the actor run.\nIt is possible to pass data using item argument or stdin.\nPassing data using argument:\n$ apify actor:push-data {\"foo\": \"bar\"}\nPassing data using stdin with pipe:\n$ cat ./test.json | apify actor:push-data\n","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"item","description":"JSON string with one object or array of objects containing data to be stored in the default dataset.","required":false}]},"actor:set-value":{"id":"actor:set-value","description":"Sets or removes record into the default KeyValueStore associated with the actor run.\nIt is possible to pass data using argument or stdin.\nPassing data using argument:\n$ apify actor:set-value KEY my-value\nPassing data using stdin with pipe:\n$ cat ./my-text-file.txt | apify actor:set-value KEY --contentType text/plain\n","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{"contentType":{"name":"contentType","type":"option","char":"c","description":"Specifies a custom MIME content type of the record. By default \"application/json\" is used.","required":false}},"args":[{"name":"key","description":"Key of the record in key-value store.","required":true},{"name":"value","description":"Record data, which can be one of the following values:\n- If empty, the record in the key-value store is deleted.\n- If no `contentType` flag is specified, value is expected to be any JSON string value.\n- If options.contentType is set, value is taken as is.","required":false}]},"secrets:add":{"id":"secrets:add","description":"Adds a new secret value.\nThe secrets are stored to a file at ~/.apify","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"name","description":"Name of the secret","required":true},{"name":"value","description":"Value of the secret","required":true}]},"secrets":{"id":"secrets","description":"Manages secret values for actor environment variables.\n\nExample:\n$ apify secrets:add mySecret TopSecretValue123\n\nNow the \"mySecret\" value can be used in an environment variable defined in \".actor/actor.json\" file by adding the \"@\" prefix:\n\n{\n \"actorSpecification\": 1,\n \"name\": \"my_actor\",\n \"environmentVariables\": { \"SECRET_ENV_VAR\": \"@mySecret\" },\n \"version\": \"0.1\n}\n\nWhen the actor is pushed to Apify cloud, the \"SECRET_ENV_VAR\" and its value is stored as a secret environment variable of the actor.","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{},"args":[]},"secrets:rm":{"id":"secrets:rm","description":"Removes the secret.","pluginName":"apify-cli","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"name","description":"Name of the secret","required":true}]}}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apify-cli",
3
- "version": "0.14.1-beta.0",
3
+ "version": "0.14.2-beta.0",
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": {
@@ -8,7 +8,7 @@
8
8
  "lint": "eslint src test",
9
9
  "lint:fix": "eslint src test --fix",
10
10
  "commands-md": "npm run manifest && oclif-dev readme",
11
- "prepare-release": "npm run manifest && npm run commands-md && npm run prod-shrinkwrap",
11
+ "prepare-release": "npm run commands-md && npm run prod-shrinkwrap",
12
12
  "prod-shrinkwrap": "rm -rf node_modules && npm install --production && npm shrinkwrap",
13
13
  "manifest": "rm -f oclif.manifest.json && oclif-dev manifest",
14
14
  "postinstall": "node \"./src/bin/run\" check-version"
@@ -66,12 +66,12 @@
66
66
  "apify-client": "^2.2.0",
67
67
  "archiver-promise": "^1.0.0",
68
68
  "chalk": "^4.1.2",
69
+ "computer-name": "^0.1.0",
69
70
  "cors": "^2.8.5",
70
71
  "cross-spawn": "^7.0.3",
71
72
  "detect-indent": "^6.1.0",
72
73
  "express": "^4.18.2",
73
74
  "globby": "^11.1.0",
74
- "got-scraping": "^3.2.9",
75
75
  "inquirer": "^7.3.3",
76
76
  "is-online": "^10.0.0",
77
77
  "istextorbinary": "^6.0.0",
@@ -1,8 +1,6 @@
1
1
  const { flags: flagsHelper } = require('@oclif/command');
2
2
  const fs = require('fs');
3
3
  const path = require('path');
4
- const inquirer = require('inquirer');
5
- const { gotScraping } = require('got-scraping');
6
4
  const actorTemplates = require('@apify/actor-templates');
7
5
  const unzipper = require('unzipper');
8
6
  const { ApifyCommand } = require('../lib/apify_command');
@@ -13,64 +11,39 @@ const {
13
11
  setLocalConfig,
14
12
  setLocalEnv,
15
13
  getNpmCmd,
16
- validateActorName,
17
14
  getJsonFileContent,
18
15
  detectPythonVersion,
19
16
  isPythonVersionSupported,
20
17
  getPythonCommand,
21
18
  } = require('../lib/utils');
22
19
  const { EMPTY_LOCAL_CONFIG, LOCAL_CONFIG_PATH, PYTHON_VENV_PATH } = require('../lib/consts');
20
+ const { httpsGet, ensureValidActorName, getTemplateDefinition } = require('../lib/create-utils');
23
21
 
24
22
  class CreateCommand extends ApifyCommand {
25
23
  async run() {
26
24
  const { flags, args } = this.parse(CreateCommand);
27
25
  let { actorName } = args;
28
- let { templateArchiveUrl, template: templateName } = flags;
29
- const { skipDependencyInstall } = flags;
26
+ const {
27
+ template: templateName,
28
+ skipDependencyInstall,
29
+ } = flags;
30
+
31
+ // --template-archive-url is an internal, undocumented flag that's used
32
+ // for testing of templates that are not yet published in the manifest
33
+ let { templateArchiveUrl } = flags;
30
34
  let skipOptionalDeps = false;
31
35
 
32
- // Check proper format of actorName
33
- if (!actorName) {
34
- const actorNamePrompt = await inquirer.prompt([{
35
- name: 'actorName',
36
- message: 'Name of the new actor:',
37
- type: 'input',
38
- validate: (promptText) => {
39
- try {
40
- validateActorName(promptText);
41
- } catch (err) {
42
- return err.message;
43
- }
44
- return true;
45
- },
46
- }]);
47
- ({ actorName } = actorNamePrompt);
48
- } else {
49
- validateActorName(actorName);
50
- }
51
-
52
- if (!templateArchiveUrl) {
53
- const manifest = await actorTemplates.fetchManifest();
54
- if (!templateName) {
55
- const choices = manifest.templates.map((t) => ({
56
- value: t.name,
57
- name: t.description,
58
- }));
59
-
60
- const answer = await inquirer.prompt([{
61
- type: 'list',
62
- name: 'template',
63
- message: 'Please select the template for your new actor',
64
- default: choices[0],
65
- choices,
66
- loop: false,
67
- pageSize: 8, // Due to the answers wrapping, the prompt looks best if the `pageSize` is a multiple of 2
68
- }]);
69
- templateName = answer.template;
70
- }
71
-
72
- const templateObj = manifest.templates.find((t) => t.name === templateName);
73
- ({ archiveUrl: templateArchiveUrl, skipOptionalDeps } = templateObj);
36
+ // Start fetching manifest immediately to prevent
37
+ // annoying delays that sometimes happen on CLI startup.
38
+ const manifestPromise = templateArchiveUrl
39
+ ? undefined // not fetching manifest when we have direct template URL
40
+ : actorTemplates.fetchManifest().catch((err) => {
41
+ return new Error(`Could not fetch template list from server. Cause: ${err?.message}`);
42
+ });
43
+
44
+ actorName = await ensureValidActorName(actorName);
45
+ if (manifestPromise) {
46
+ ({ archiveUrl: templateArchiveUrl, skipOptionalDeps } = await getTemplateDefinition(templateName, manifestPromise));
74
47
  }
75
48
 
76
49
  const cwd = process.cwd();
@@ -88,10 +61,7 @@ class CreateCommand extends ApifyCommand {
88
61
  throw err;
89
62
  }
90
63
 
91
- const zipStream = await gotScraping({
92
- url: templateArchiveUrl,
93
- isStream: true,
94
- });
64
+ const zipStream = await httpsGet(templateArchiveUrl);
95
65
  const unzip = unzipper.Extract({ path: actFolderDir });
96
66
  await zipStream.pipe(unzip).promise();
97
67
 
@@ -0,0 +1,164 @@
1
+ const { flags: flagsHelper } = require('@oclif/command');
2
+ const computerName = require('computer-name');
3
+ const cors = require('cors');
4
+ const express = require('express');
5
+ const open = require('open');
6
+
7
+ const { cryptoRandomObjectId } = require('@apify/utilities');
8
+
9
+ const inquirer = require('inquirer');
10
+ const { ApifyCommand } = require('../lib/apify_command');
11
+ const outputs = require('../lib/outputs');
12
+ const { getLoggedClient } = require('../lib/utils');
13
+ const { getLocalUserInfo } = require('../lib/utils');
14
+
15
+ const CONSOLE_BASE_URL = 'https://console.apify.com/account?tab=integrations';
16
+ // const CONSOLE_BASE_URL = 'http://localhost:3000/account?tab=integrations';
17
+ const CONSOLE_URL_ORIGIN = new URL(CONSOLE_BASE_URL).origin;
18
+
19
+ const API_BASE_URL = CONSOLE_BASE_URL.includes('localhost') ? 'http://localhost:3333' : undefined;
20
+
21
+ // Not really checked right now, but it might come useful if we ever need to do some breaking changes
22
+ const API_VERSION = 'v1';
23
+
24
+ let tokenPrompt;
25
+ let promptUi;
26
+
27
+ const tryToLogin = async (token) => {
28
+ const isUserLogged = await getLoggedClient(token, API_BASE_URL);
29
+ const userInfo = getLocalUserInfo();
30
+ if (isUserLogged) {
31
+ outputs.success(`You are logged in to Apify as ${userInfo.username || userInfo.id}!`);
32
+ promptUi.close();
33
+ } else {
34
+ outputs.error('Login to Apify failed, the provided API token is not valid.');
35
+ }
36
+ return isUserLogged;
37
+ };
38
+
39
+ class LoginNewCommand extends ApifyCommand {
40
+ async run() {
41
+ outputs.warning('This command is still experimental and might break at any time. Use at your own risk.\n');
42
+
43
+ const { flags } = this.parse(LoginNewCommand);
44
+ let { token } = flags;
45
+ if (!token) {
46
+ let server;
47
+ const app = express();
48
+
49
+ // To send requests from browser to localhost, CORS has to be configured properly
50
+ app.use(cors({
51
+ origin: CONSOLE_URL_ORIGIN,
52
+ allowedHeaders: ['Content-Type', 'Authorization'],
53
+ }));
54
+
55
+ // Turn off keepalive, otherwise closing the server when command is finished is lagging
56
+ app.use((req, res, next) => {
57
+ res.set('Connection', 'close');
58
+ next();
59
+ });
60
+
61
+ app.use(express.json());
62
+
63
+ // Basic authorization via a random token, which is passed to the Apify Console,
64
+ // and that sends it back via the `token` query param, or `Authorization` header
65
+ const authToken = cryptoRandomObjectId();
66
+ app.use((req, res, next) => {
67
+ let { token: serverToken } = req.query;
68
+ if (!serverToken) {
69
+ const authorizationHeader = req.get('Authorization');
70
+ if (authorizationHeader) {
71
+ const [schema, tokenFromHeader, ...extra] = authorizationHeader.trim().split(/\s+/);
72
+ if (schema.toLowerCase() === 'bearer' && tokenFromHeader && extra.length === 0) {
73
+ serverToken = tokenFromHeader;
74
+ }
75
+ }
76
+ }
77
+
78
+ if (serverToken !== authToken) {
79
+ res.status(401);
80
+ res.send('Authorization failed');
81
+ } else {
82
+ next();
83
+ }
84
+ });
85
+
86
+ const apiRouter = express.Router();
87
+ app.use(`/api/${API_VERSION}`, apiRouter);
88
+
89
+ apiRouter.post('/login-token', async (req, res) => {
90
+ try {
91
+ if (req.body.apiToken) {
92
+ console.log('canceled');
93
+ outputs.info('Got token from console...');
94
+ await tryToLogin(req.body.apiToken);
95
+ } else {
96
+ throw new Error('Request did not contain API token');
97
+ }
98
+ res.end();
99
+ } catch (err) {
100
+ const errorMessage = `Login to Apify failed with error: ${err.message}`;
101
+ outputs.error(errorMessage);
102
+ res.status(500);
103
+ res.send(errorMessage);
104
+ }
105
+ server.close();
106
+ });
107
+
108
+ apiRouter.post('/exit', (req, res) => {
109
+ if (req.body.isWindowClosed) {
110
+ outputs.error('Login to Apify failed, the console window was closed.');
111
+ } else if (req.body.actionCanceled) {
112
+ outputs.error('Login to Apify failed, the action was canceled in the Apify Console.');
113
+ } else {
114
+ outputs.error('Login to Apify failed.');
115
+ }
116
+
117
+ res.end();
118
+ server.close();
119
+ });
120
+
121
+ // Listening on port 0 will assign a random available port
122
+ server = app.listen(0);
123
+ const { port } = server.address();
124
+ outputs.info(`Waiting for token from Apify console (on port ${port})...`);
125
+
126
+ const consoleUrl = new URL(CONSOLE_BASE_URL);
127
+ consoleUrl.searchParams.set('localCliCommand', 'login');
128
+ consoleUrl.searchParams.set('localCliPort', port);
129
+ consoleUrl.searchParams.set('localCliToken', authToken);
130
+ consoleUrl.searchParams.set('localCliApiVersion', API_VERSION);
131
+ consoleUrl.searchParams.set('localCliComputerName', encodeURIComponent(computerName()));
132
+
133
+ outputs.info(`Opening Apify Console at "${consoleUrl.href}"...`);
134
+ outputs.info('You can also paste your Apify token below');
135
+ await open(consoleUrl.href);
136
+ tokenPrompt = inquirer.prompt([{ name: 'token', message: 'token:', type: 'password' }]);
137
+ promptUi = tokenPrompt.ui;
138
+ const { token: insertedToken } = await tokenPrompt;
139
+ server.close();
140
+ const loginSuccessful = await tryToLogin(insertedToken);
141
+ if (loginSuccessful) {
142
+ token = insertedToken;
143
+ }
144
+ } else {
145
+ return tryToLogin(token);
146
+ }
147
+ }
148
+ }
149
+
150
+ LoginNewCommand.description = 'Logs in to your Apify account using your API token.\nThe API token and other account '
151
+ + 'information is stored in the ~/.apify directory, from where it is read by all other "apify" commands. '
152
+ + 'To log out, call "apify logout".';
153
+
154
+ LoginNewCommand.flags = {
155
+ token: flagsHelper.string({
156
+ char: 't',
157
+ description: '[Optional] Apify API token',
158
+ required: false,
159
+ }),
160
+ };
161
+
162
+ LoginNewCommand.hidden = true;
163
+
164
+ module.exports = LoginNewCommand;
@@ -0,0 +1,175 @@
1
+ const chalk = require('chalk');
2
+ const inquirer = require('inquirer');
3
+ const https = require('https');
4
+ const { validateActorName } = require('./utils');
5
+
6
+ const PROGRAMMING_LANGUAGES = ['JavaScript', 'TypeScript', 'Python'];
7
+
8
+ /**
9
+ * @param {string} url
10
+ * @returns {Promise<unknown>}
11
+ */
12
+ exports.httpsGet = async (url) => {
13
+ return new Promise((resolve, reject) => {
14
+ https.get(url, (response) => {
15
+ if (response.statusCode === 301 || response.statusCode === 302) {
16
+ resolve(exports.httpsGet(response.headers.location));
17
+ } else {
18
+ resolve(response);
19
+ }
20
+ }).on('error', reject);
21
+ });
22
+ };
23
+
24
+ /**
25
+ * @param {string} maybeActorName
26
+ * @returns {Promise<string>}
27
+ */
28
+ exports.ensureValidActorName = async (maybeActorName) => {
29
+ if (maybeActorName) {
30
+ validateActorName(maybeActorName);
31
+ return maybeActorName;
32
+ }
33
+ return promptActorName();
34
+ };
35
+
36
+ /**
37
+ * @param {string} maybeTemplateName
38
+ * @param {Promise<object>} manifestPromise
39
+ * @returns {Promise<object>}
40
+ */
41
+ exports.getTemplateDefinition = async (maybeTemplateName, manifestPromise) => {
42
+ const manifest = await manifestPromise;
43
+ // If the fetch failed earlier, the resolve value of
44
+ // the promise will be the error from fetching the manifest.
45
+ if (manifest instanceof Error) throw manifest;
46
+
47
+ if (maybeTemplateName) {
48
+ const templateDefinition = manifest.templates.find((t) => t.name === maybeTemplateName);
49
+ if (!templateDefinition) {
50
+ throw new Error(`Could not find the selected template: ${maybeTemplateName} in the list of templates.`);
51
+ }
52
+ return templateDefinition;
53
+ }
54
+
55
+ return executePrompts(manifest);
56
+ };
57
+
58
+ /**
59
+ * Inquirer does not have a native way to "go back" between prompts.
60
+ * @param {object} manifest
61
+ * @returns {Promise<object>}
62
+ */
63
+ async function executePrompts(manifest) {
64
+ const programmingLanguage = await promptProgrammingLanguage();
65
+ // eslint-disable-next-line no-constant-condition
66
+ while (true) {
67
+ const templateDefinition = await promptTemplateDefinition(manifest, programmingLanguage);
68
+ if (templateDefinition) {
69
+ const shouldInstall = await promptTemplateInstallation(templateDefinition);
70
+ if (shouldInstall) {
71
+ return templateDefinition;
72
+ }
73
+ } else {
74
+ return executePrompts(manifest);
75
+ }
76
+ }
77
+ }
78
+
79
+ /**
80
+ * @returns {Promise<string>}
81
+ */
82
+ async function promptActorName() {
83
+ const answer = await inquirer.prompt([{
84
+ name: 'actorName',
85
+ message: 'Name of your new actor:',
86
+ type: 'input',
87
+ validate: (promptText) => {
88
+ try {
89
+ validateActorName(promptText);
90
+ } catch (err) {
91
+ return err.message;
92
+ }
93
+ return true;
94
+ },
95
+ }]);
96
+ return answer.actorName;
97
+ }
98
+
99
+ /**
100
+ * @returns {Promise<string>}
101
+ */
102
+ async function promptProgrammingLanguage() {
103
+ const answer = await inquirer.prompt([{
104
+ type: 'list',
105
+ name: 'programmingLanguage',
106
+ message: 'Choose the programming language of your new actor:',
107
+ default: PROGRAMMING_LANGUAGES[0],
108
+ choices: PROGRAMMING_LANGUAGES,
109
+ loop: false,
110
+ }]);
111
+ return answer.programmingLanguage;
112
+ }
113
+
114
+ /**
115
+ * @param {object} manifest
116
+ * @param {string} programmingLanguage
117
+ * @returns {Promise<object>} template definition
118
+ */
119
+ async function promptTemplateDefinition(manifest, programmingLanguage) {
120
+ const choices = manifest.templates
121
+ .filter((t) => {
122
+ return t.category.toLowerCase() === programmingLanguage.toLowerCase();
123
+ })
124
+ .map((t) => {
125
+ return {
126
+ name: t.label,
127
+ value: t,
128
+ };
129
+ });
130
+
131
+ choices.push(new inquirer.Separator());
132
+ choices.push({
133
+ name: 'Go back',
134
+ value: false,
135
+ });
136
+
137
+ const answer = await inquirer.prompt([{
138
+ type: 'list',
139
+ name: 'templateDefinition',
140
+ message: 'Choose a template for your new actor. Detailed information about the template will be shown in the next step.',
141
+ default: choices[0],
142
+ choices,
143
+ loop: false,
144
+ pageSize: 8,
145
+ }]);
146
+
147
+ return answer.templateDefinition;
148
+ }
149
+
150
+ /**
151
+ * @param {object} templateDefinition
152
+ * @returns {Promise<string>}
153
+ */
154
+ async function promptTemplateInstallation(templateDefinition) {
155
+ const choices = [{ name: `Install template`, value: true }];
156
+ choices.push(new inquirer.Separator());
157
+ choices.push({ name: 'Go back', value: false });
158
+
159
+ const message = 'Do you want to install the following template?';
160
+ const label = chalk.underline(templateDefinition.label);
161
+ const description = chalk.dim(templateDefinition.description);
162
+ const suffix = `\n ${label}:\n ${description}`;
163
+
164
+ const answer = await inquirer.prompt([{
165
+ type: 'list',
166
+ name: 'shouldInstall',
167
+ message,
168
+ suffix,
169
+ default: choices[0],
170
+ choices,
171
+ loop: false,
172
+ }]);
173
+
174
+ return answer.shouldInstall;
175
+ }
package/src/lib/utils.js CHANGED
@@ -98,14 +98,14 @@ const getLoggedClientOrThrow = async () => {
98
98
  * @param {String|null|undefined} token
99
99
  * @returns {Object}
100
100
  */
101
- const getApifyClientOptions = (token) => {
101
+ const getApifyClientOptions = (token, apiBaseUrl) => {
102
102
  if (!token && fs.existsSync(GLOBAL_CONFIGS_FOLDER) && fs.existsSync(AUTH_FILE_PATH)) {
103
103
  ({ token } = loadJson.sync(AUTH_FILE_PATH));
104
104
  }
105
105
 
106
106
  return {
107
107
  token,
108
- baseUrl: process.env.APIFY_CLIENT_BASE_URL,
108
+ baseUrl: apiBaseUrl || process.env.APIFY_CLIENT_BASE_URL,
109
109
  requestInterceptors: [(config) => {
110
110
  config.headers = { ...APIFY_CLIENT_DEFAULT_HEADERS, ...config.headers };
111
111
  return config;
@@ -119,8 +119,12 @@ const getApifyClientOptions = (token) => {
119
119
  * @param [token]
120
120
  * @return {Promise<*>}
121
121
  */
122
- const getLoggedClient = async (token) => {
123
- const apifyClient = new ApifyClient(getApifyClientOptions(token));
122
+ const getLoggedClient = async (token, apiBaseUrl) => {
123
+ if (!token && fs.existsSync(GLOBAL_CONFIGS_FOLDER) && fs.existsSync(AUTH_FILE_PATH)) {
124
+ ({ token } = loadJson.sync(AUTH_FILE_PATH));
125
+ }
126
+
127
+ const apifyClient = new ApifyClient(getApifyClientOptions(token, apiBaseUrl));
124
128
  let userInfo;
125
129
  try {
126
130
  userInfo = await apifyClient.user('me').get();