From 2029e8a90d49920aa8abdf1ccfa5909e6a3073fe Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Mon, 20 May 2024 09:33:20 -0400 Subject: [PATCH 01/26] feat(cli): Split project creation and pulling --- src/SDK/Language/CLI.php | 5 +++ templates/cli/index.js.twig | 2 + templates/cli/lib/commands/create.js.twig | 41 +++++++++++++++++++ templates/cli/lib/commands/pull.js.twig | 25 +----------- templates/cli/lib/parser.js.twig | 1 + templates/cli/lib/questions.js.twig | 50 +++++++---------------- 6 files changed, 65 insertions(+), 59 deletions(-) create mode 100644 templates/cli/lib/commands/create.js.twig diff --git a/src/SDK/Language/CLI.php b/src/SDK/Language/CLI.php index adc4ec214..9ee58a8b5 100644 --- a/src/SDK/Language/CLI.php +++ b/src/SDK/Language/CLI.php @@ -167,6 +167,11 @@ public function getFiles(): array 'destination' => 'lib/utils.js', 'template' => 'cli/lib/utils.js.twig', ], + [ + 'scope' => 'default', + 'destination' => 'lib/commands/create.js', + 'template' => 'cli/lib/commands/create.js.twig', + ], [ 'scope' => 'default', 'destination' => 'lib/commands/pull.js', diff --git a/templates/cli/index.js.twig b/templates/cli/index.js.twig index 47725d266..9e5e9d714 100644 --- a/templates/cli/index.js.twig +++ b/templates/cli/index.js.twig @@ -12,6 +12,7 @@ const { commandDescriptions, cliConfig } = require("./lib/parser"); const { client } = require("./lib/commands/generic"); {% if sdk.test != "true" %} const { login, logout } = require("./lib/commands/generic"); +const { create } = require("./lib/commands/create"); const { pull } = require("./lib/commands/pull"); const { push } = require("./lib/commands/push"); {% endif %} @@ -37,6 +38,7 @@ program .showSuggestionAfterError() {% if sdk.test != "true" %} .addCommand(login) + .addCommand(create) .addCommand(pull) .addCommand(push) .addCommand(logout) diff --git a/templates/cli/lib/commands/create.js.twig b/templates/cli/lib/commands/create.js.twig new file mode 100644 index 000000000..339a21ace --- /dev/null +++ b/templates/cli/lib/commands/create.js.twig @@ -0,0 +1,41 @@ +const { Command } = require("commander"); +const inquirer = require("inquirer"); +const { projectsCreate } = require("./projects"); +const { sdkForConsole } = require("../sdks"); +const { localConfig } = require("../config"); +const { questionsCreateProject } = require("../questions"); +const { success, actionRunner, commandDescriptions } = require("../parser"); + +const create = new Command("create") + .description(commandDescriptions['create']) + .configureHelp({ + helpWidth: process.stdout.columns || 80 + }) + .action(actionRunner(async (_options, command) => { + command.help(); + })); + +const createProject = async () => { + let response = {} + const answers = await inquirer.prompt(questionsCreateProject) + if (!answers.project || !answers.organization) process.exit(1) + + response = await projectsCreate({ + projectId: answers.id, + name: answers.project, + teamId: answers.organization.id, + parseOutput: false + }) + + localConfig.setProject(response['$id'], response.name); + success(); +} + +create + .command("project") + .description("Create a new {{ spec.title|caseUcfirst }} project") + .action(actionRunner(createProject)); + +module.exports = { + create, +}; diff --git a/templates/cli/lib/commands/pull.js.twig b/templates/cli/lib/commands/pull.js.twig index 2c1bad6c9..a4e99161d 100644 --- a/templates/cli/lib/commands/pull.js.twig +++ b/templates/cli/lib/commands/pull.js.twig @@ -10,7 +10,7 @@ const { databasesGet, databasesListCollections, databasesList } = require("./dat const { storageListBuckets } = require("./storage"); const { sdkForConsole } = require("../sdks"); const { localConfig } = require("../config"); -const ID = require("../id"); +const ID = require("../id"); const { paginate } = require("../paginate"); const { questionsPullProject, questionsPullFunction, questionsPullCollection } = require("../questions"); const { success, log, actionRunner, commandDescriptions } = require("../parser"); @@ -25,31 +25,10 @@ const pull = new Command("pull") })); const pullProject = async () => { - let response = {} const answers = await inquirer.prompt(questionsPullProject) if (!answers.project) process.exit(1) - let sdk = await sdkForConsole(); - if (answers.start === "new") { - response = await teamsCreate({ - teamId: 'unique()', - name: answers.project, - sdk, - parseOutput: false - }) - - let teamId = response['$id']; - response = await projectsCreate({ - projectId: answers.id, - name: answers.project, - teamId, - parseOutput: false - }) - - localConfig.setProject(response['$id'], response.name); - } else { - localConfig.setProject(answers.project.id, answers.project.name); - } + localConfig.setProject(answers.project.id, answers.project.name); success(); } diff --git a/templates/cli/lib/parser.js.twig b/templates/cli/lib/parser.js.twig index 5dde5aa3e..cf94b3a8b 100644 --- a/templates/cli/lib/parser.js.twig +++ b/templates/cli/lib/parser.js.twig @@ -156,6 +156,7 @@ const commandDescriptions = { "graphql": `The graphql command allows you to query and mutate any resource type on your Appwrite server.`, "avatars": `The avatars command aims to help you complete everyday tasks related to your app image, icons, and avatars.`, "databases": `The databases command allows you to create structured collections of documents, query and filter lists of documents.`, + "create": `The create command provides a convenient wrapper for creating projects functions, collections, buckets, teams and messaging.`, "push": `The push command provides a convenient wrapper for pushing your functions, collections, buckets, teams and messaging.`, "functions": `The functions command allows you view, create and manage your Cloud Functions.`, "health": `The health command allows you to both validate and monitor your {{ spec.title|caseUcfirst }} server's health.`, diff --git a/templates/cli/lib/questions.js.twig b/templates/cli/lib/questions.js.twig index 11b203dff..31fec7192 100644 --- a/templates/cli/lib/questions.js.twig +++ b/templates/cli/lib/questions.js.twig @@ -110,7 +110,7 @@ const getInstallCommand = (runtime) => { return undefined; }; -const questionsPullProject = [ +const questionsProject = [ { type: "confirm", name: "override", @@ -126,7 +126,7 @@ const questionsPullProject = [ message: "Choose the project organization", choices: async () => { let client = await sdkForConsole(true); - const { teams } = await paginate(teamsList, { parseOutput: false , sdk: client}, 100, 'teams'); + const { teams } = await paginate(teamsList, { parseOutput: false, sdk: client }, 100, 'teams'); let choices = teams.map((team, idx) => { return { @@ -145,56 +145,33 @@ const questionsPullProject = [ return choices; } }, - { - type: "list", - name: "start", - when(answers) { - if (answers.override == undefined) { - return true - } - return answers.override; - }, - message: "How would you like to start?", - choices: [ - { - name: "Create a new {{ spec.title|caseUcfirst }} project", - value: "new", - }, - { - name: "Link this directory to an existing {{ spec.title|caseUcfirst }} project", - value: "existing", - }, - ], - }, +]; + +const questionsCreateProject = [ + ...questionsProject, { type: "input", name: "project", message: "What would you like to name your project?", - default: "My Awesome Project", - when(answers) { - return answers.start == "new"; - }, + default: "My Awesome Project" }, { type: "input", name: "id", message: "What ID would you like to have for your project?", - default: "unique()", - when(answers) { - return answers.start == "new"; - }, - }, + default: "unique()" + } +]; +const questionsPullProject = [ + ...questionsProject, { type: "list", name: "project", message: "Choose your {{ spec.title|caseUcfirst }} project.", - when(answers) { - return answers.start == "existing"; - }, choices: async (answers) => { let response = await projectsList({ parseOutput: false, - queries: [JSON.stringify({ method: 'equal', attribute:'teamId', values: [answers.organization.id] })], + queries: [JSON.stringify({ method: 'equal', attribute: 'teamId', values: [answers.organization.id] })], }) let projects = response["projects"] let choices = projects.map((project, idx) => { @@ -476,6 +453,7 @@ const questionsMfaChallenge = [ ]; module.exports = { + questionsCreateProject, questionsPullProject, questionsLogin, questionsPullFunction, From 75a01c83c9ef127473a4ea2d7bb08e05c2c372dc Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Mon, 20 May 2024 10:48:55 -0400 Subject: [PATCH 02/26] feat(cli): Interactive bucket creation --- templates/cli/lib/commands/create.js.twig | 59 ++++++++++++++++++++++- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/templates/cli/lib/commands/create.js.twig b/templates/cli/lib/commands/create.js.twig index 339a21ace..a764ac988 100644 --- a/templates/cli/lib/commands/create.js.twig +++ b/templates/cli/lib/commands/create.js.twig @@ -1,10 +1,11 @@ const { Command } = require("commander"); const inquirer = require("inquirer"); const { projectsCreate } = require("./projects"); +const { storageCreateBucket } = require("./storage"); const { sdkForConsole } = require("../sdks"); const { localConfig } = require("../config"); -const { questionsCreateProject } = require("../questions"); -const { success, actionRunner, commandDescriptions } = require("../parser"); +const { questionsCreateProject, questionsCreateBucket } = require("../questions"); +const { success, error, actionRunner, commandDescriptions } = require("../parser"); const create = new Command("create") .description(commandDescriptions['create']) @@ -31,11 +32,65 @@ const createProject = async () => { success(); } + +const createBucket = async () => { + let response = {} + const answers = await inquirer.prompt(questionsCreateBucket) + if (!answers.bucket || !answers.id || !answers.fileSecurity) process.exit(1) + + try { + response = await storageCreateBucket({ + bucketId: answers.id, + name: answers.bucket, + fileSecurity: answers.fileSecurity.toLowerCase() === 'yes', + enabled: true, + parseOutput: false + }) + + localConfig.addBucket(response); + success(); + } catch (e) { + error(e.getMessage ?? 'Unknown error occurred. Please try again'); + } +}; + +const createFunction = async () => { + +}; + +const createCollection = async () => { + +}; + +const createTopic = async () => { + +}; + create .command("project") .description("Create a new {{ spec.title|caseUcfirst }} project") .action(actionRunner(createProject)); +create + .command("function") + .description("Create a new {{ spec.title|caseUcfirst }} function") + .action(actionRunner(createFunction)); + +create + .command("bucket") + .description("Create a new {{ spec.title|caseUcfirst }} bucket") + .action(actionRunner(createBucket)); + +create + .command("collection") + .description("Create a new {{ spec.title|caseUcfirst }} collection") + .action(actionRunner(createCollection)); + +create + .command("topic") + .description("Create a new {{ spec.title|caseUcfirst }} topic") + .action(actionRunner(createTopic)); + module.exports = { create, }; From 05df5dd61c031e3210ea879a7a65cdb09ea8dfb6 Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Mon, 20 May 2024 10:55:23 -0400 Subject: [PATCH 03/26] feat(cli): Interactive messaging-topic creation --- templates/cli/lib/commands/create.js.twig | 29 +++++++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/templates/cli/lib/commands/create.js.twig b/templates/cli/lib/commands/create.js.twig index a764ac988..b48be56c5 100644 --- a/templates/cli/lib/commands/create.js.twig +++ b/templates/cli/lib/commands/create.js.twig @@ -2,9 +2,10 @@ const { Command } = require("commander"); const inquirer = require("inquirer"); const { projectsCreate } = require("./projects"); const { storageCreateBucket } = require("./storage"); +const { messagingCreateTopic } = require("./messaging"); const { sdkForConsole } = require("../sdks"); const { localConfig } = require("../config"); -const { questionsCreateProject, questionsCreateBucket } = require("../questions"); +const { questionsCreateProject, questionsCreateBucket, questionsCreateMessagingTopic } = require("../questions"); const { success, error, actionRunner, commandDescriptions } = require("../parser"); const create = new Command("create") @@ -32,7 +33,6 @@ const createProject = async () => { success(); } - const createBucket = async () => { let response = {} const answers = await inquirer.prompt(questionsCreateBucket) @@ -54,18 +54,37 @@ const createBucket = async () => { } }; -const createFunction = async () => { +const createCollection = async () => { }; -const createCollection = async () => { +const createTopic = async () => { + let response = {} + const answers = await inquirer.prompt(questionsCreateMessagingTopic) + if (!answers.topic || !answers.id) process.exit(1) + + try { + response = await messagingCreateTopic({ + topicId: answers.id, + name: answers.topic, + parseOutput: false + }) + {#localConfig.addMessagingTopic(response);#} + success(); + } catch (e) { + error(e.getMessage ?? 'Unknown error occurred. Please try again'); + } }; -const createTopic = async () => { +const createFunction = async () => { }; + + + + create .command("project") .description("Create a new {{ spec.title|caseUcfirst }} project") From 01d7bc586244205f7f8ef6721b87cccfb35dd342 Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Mon, 20 May 2024 11:01:32 -0400 Subject: [PATCH 04/26] feat(cli): Interactive collection creation --- templates/cli/lib/commands/create.js.twig | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/templates/cli/lib/commands/create.js.twig b/templates/cli/lib/commands/create.js.twig index b48be56c5..aabebf280 100644 --- a/templates/cli/lib/commands/create.js.twig +++ b/templates/cli/lib/commands/create.js.twig @@ -3,9 +3,10 @@ const inquirer = require("inquirer"); const { projectsCreate } = require("./projects"); const { storageCreateBucket } = require("./storage"); const { messagingCreateTopic } = require("./messaging"); +const { databasesCreateCollection } = require("./databases"); const { sdkForConsole } = require("../sdks"); const { localConfig } = require("../config"); -const { questionsCreateProject, questionsCreateBucket, questionsCreateMessagingTopic } = require("../questions"); +const { questionsCreateProject, questionsCreateBucket, questionsCreateMessagingTopic, questionsCreateCollection } = require("../questions"); const { success, error, actionRunner, commandDescriptions } = require("../parser"); const create = new Command("create") @@ -55,7 +56,25 @@ const createBucket = async () => { }; const createCollection = async () => { + let response = {} + const answers = await inquirer.prompt(questionsCreateCollection) + if (!answers.database || !answers.collection || !answers.id || !answers.documentSecurity) process.exit(1) + + try { + response = await databasesCreateCollection({ + databaseId: answers.database, + collectionId: answers.id, + name: answers.collection, + documentSecurity: answers.documentSecurity.toLowerCase() === 'yes', + enabled: true, + parseOutput: false + }) + localConfig.addCollection(response); + success(); + } catch (e) { + error(e.getMessage ?? 'Unknown error occurred. Please try again'); + } }; const createTopic = async () => { @@ -70,7 +89,7 @@ const createTopic = async () => { parseOutput: false }) - {#localConfig.addMessagingTopic(response);#} + {# localConfig.addMessagingTopic(response); #} success(); } catch (e) { error(e.getMessage ?? 'Unknown error occurred. Please try again'); From e2e80e6f233e8928cc62128ff2a9b5df0be97518 Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Mon, 20 May 2024 11:03:04 -0400 Subject: [PATCH 05/26] feat(cli): Interactive questions --- templates/cli/lib/questions.js.twig | 87 ++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 2 deletions(-) diff --git a/templates/cli/lib/questions.js.twig b/templates/cli/lib/questions.js.twig index 31fec7192..a52c11555 100644 --- a/templates/cli/lib/questions.js.twig +++ b/templates/cli/lib/questions.js.twig @@ -162,6 +162,86 @@ const questionsCreateProject = [ default: "unique()" } ]; + +const questionsCreateBucket = [ + { + type: "input", + name: "bucket", + message: "What would you like to name your bucket?", + default: "My Awesome Bucket" + }, + { + type: "input", + name: "id", + message: "What ID would you like to have for your bucket?", + default: "unique()" + }, + { + type: "list", + name: "fileSecurity", + message: "Enable File-Security configuring permissions for individual file", + choices: ["No", "Yes"] + } +]; + +const questionsCreateCollection = [ + { + type: "list", + name: "database", + message: "Choose the collection database", + choices: async () => { + const { databases } = await paginate(databasesList, { parseOutput: false }, 100, 'databases'); + + let choices = databases.map((database, idx) => { + return { + name: `${database.name} (${database.$id})`, + value: database.$id + } + }) + + if (choices.length === 0) { + throw new Error("No databases found. Please create one in project console.") + } + + return choices; + } + }, + { + type: "input", + name: "collection", + message: "What would you like to name your collection?", + default: "My Awesome Collection" + }, + { + type: "input", + name: "id", + message: "What ID would you like to have for your collection?", + default: "unique()" + }, + { + type: "list", + name: "documentSecurity", + message: "Enable Document-Security for configuring permissions for individual documents", + choices: ["No", "Yes"] + } +]; + +const questionsCreateMessagingTopic = [ + { + type: "input", + name: "topic", + message: "What would you like to name your messaging topic?", + default: "My Awesome Topic" + }, + { + type: "input", + name: "id", + message: "What ID would you like to have for your messaging topic?", + default: "unique()" + } +]; + + const questionsPullProject = [ ...questionsProject, { @@ -222,7 +302,7 @@ const questionsPullFunction = [ id: runtime['$id'], entrypoint: getEntrypoint(runtime['$id']), ignore: getIgnores(runtime['$id']), - commands : getInstallCommand(runtime['$id']) + commands: getInstallCommand(runtime['$id']) }, } }) @@ -431,7 +511,7 @@ const questionsListFactors = [ name: `Recovery code`, value: 'recoveryCode' } - ].filter((ch) => factors[ch.value] === true); + ].filter((ch) => factors[ch.value] === true); return choices; } @@ -454,6 +534,9 @@ const questionsMfaChallenge = [ module.exports = { questionsCreateProject, + questionsCreateBucket, + questionsCreateCollection, + questionsCreateMessagingTopic, questionsPullProject, questionsLogin, questionsPullFunction, From d4b2c3319756c05949d5451e598c99238c725b25 Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Mon, 20 May 2024 11:06:27 -0400 Subject: [PATCH 06/26] refactor(cli): Function `pull` to `create` --- templates/cli/lib/commands/create.js.twig | 118 ++++++++++++++++++++- templates/cli/lib/commands/pull.js.twig | 121 +--------------------- templates/cli/lib/questions.js.twig | 78 +++++++------- 3 files changed, 156 insertions(+), 161 deletions(-) diff --git a/templates/cli/lib/commands/create.js.twig b/templates/cli/lib/commands/create.js.twig index aabebf280..9f96d9275 100644 --- a/templates/cli/lib/commands/create.js.twig +++ b/templates/cli/lib/commands/create.js.twig @@ -1,12 +1,22 @@ +const fs = require("fs"); +const path = require("path"); +const childProcess = require('child_process'); const { Command } = require("commander"); const inquirer = require("inquirer"); const { projectsCreate } = require("./projects"); const { storageCreateBucket } = require("./storage"); const { messagingCreateTopic } = require("./messaging"); +const { functionsCreate } = require("./functions"); const { databasesCreateCollection } = require("./databases"); const { sdkForConsole } = require("../sdks"); const { localConfig } = require("../config"); -const { questionsCreateProject, questionsCreateBucket, questionsCreateMessagingTopic, questionsCreateCollection } = require("../questions"); +const { + questionsCreateProject, + questionsCreateFunction, + questionsCreateBucket, + questionsCreateMessagingTopic, + questionsCreateCollection +} = require("../questions"); const { success, error, actionRunner, commandDescriptions } = require("../parser"); const create = new Command("create") @@ -97,10 +107,114 @@ const createTopic = async () => { }; const createFunction = async () => { + // TODO: Add CI/CD support (ID, name, runtime) + const answers = await inquirer.prompt(questionsCreateFunction) + const functionFolder = path.join(process.cwd(), 'functions'); + + if (!fs.existsSync(functionFolder)) { + fs.mkdirSync(functionFolder, { + recursive: true + }); + } -}; + const functionId = answers.id === 'unique()' ? ID.unique() : answers.id; + const functionDir = path.join(functionFolder, functionId); + + if (fs.existsSync(functionDir)) { + throw new Error(`( ${functionId} ) already exists in the current directory. Please choose another name.`); + } + + if (!answers.runtime.entrypoint) { + log(`Entrypoint for this runtime not found. You will be asked to configure entrypoint when you first push the function.`); + } + + if (!answers.runtime.commands) { + log(`Installation command for this runtime not found. You will be asked to configure the install command when you first push the function.`); + } + + let response = await functionsCreate({ + functionId, + name: answers.name, + runtime: answers.runtime.id, + entrypoint: answers.runtime.entrypoint || '', + commands: answers.runtime.commands || '', + parseOutput: false + }) + + fs.mkdirSync(functionDir, "777"); + + let gitInitCommands = "git clone -b v3 --single-branch --depth 1 --sparse https://github.com/{{ sdk.gitUserName }}/functions-starter ."; // depth prevents fetching older commits reducing the amount fetched + + let gitPullCommands = `git sparse-checkout add ${answers.runtime.id}`; + + /* Force use CMD as powershell does not support && */ + if (process.platform === 'win32') { + gitInitCommands = 'cmd /c "' + gitInitCommands + '"'; + gitPullCommands = 'cmd /c "' + gitPullCommands + '"'; + } + /* Execute the child process but do not print any std output */ + try { + childProcess.execSync(gitInitCommands, { stdio: 'pipe', cwd: functionDir }); + childProcess.execSync(gitPullCommands, { stdio: 'pipe', cwd: functionDir }); + } catch (error) { + /* Specialised errors with recommended actions to take */ + if (error.message.includes('error: unknown option')) { + throw new Error(`${error.message} \n\nSuggestion: Try updating your git to the latest version, then trying to run this command again.`) + } else if (error.message.includes('is not recognized as an internal or external command,') || error.message.includes('command not found')) { + throw new Error(`${error.message} \n\nSuggestion: It appears that git is not installed, try installing git then trying to run this command again.`) + } else { + throw error; + } + } + fs.rmSync(path.join(functionDir, ".git"), { recursive: true }); + const copyRecursiveSync = (src, dest) => { + let exists = fs.existsSync(src); + let stats = exists && fs.statSync(src); + let isDirectory = exists && stats.isDirectory(); + if (isDirectory) { + if (!fs.existsSync(dest)) { + fs.mkdirSync(dest); + } + + fs.readdirSync(src).forEach(function (childItemName) { + copyRecursiveSync(path.join(src, childItemName), path.join(dest, childItemName)); + }); + } else { + fs.copyFileSync(src, dest); + } + }; + copyRecursiveSync(path.join(functionDir, answers.runtime.id), functionDir); + + fs.rmSync(`${functionDir}/${answers.runtime.id}`, { recursive: true, force: true }); + + const readmePath = path.join(process.cwd(), 'functions', functionId, 'README.md'); + const readmeFile = fs.readFileSync(readmePath).toString(); + const newReadmeFile = readmeFile.split('\n'); + newReadmeFile[0] = `# ${answers.name}`; + newReadmeFile.splice(1, 2); + fs.writeFileSync(readmePath, newReadmeFile.join('\n')); + + let data = { + $id: response['$id'], + name: response.name, + runtime: response.runtime, + execute: response.execute, + events: response.events, + schedule: response.schedule, + timeout: response.timeout, + enabled: response.enabled, + logging: response.logging, + entrypoint: response.entrypoint, + commands: response.commands, + ignore: answers.runtime.ignore || null, + path: `functions/${functionId}`, + }; + + localConfig.addFunction(data); + success(); +} diff --git a/templates/cli/lib/commands/pull.js.twig b/templates/cli/lib/commands/pull.js.twig index a4e99161d..84a865b80 100644 --- a/templates/cli/lib/commands/pull.js.twig +++ b/templates/cli/lib/commands/pull.js.twig @@ -1,18 +1,14 @@ -const fs = require("fs"); -const path = require("path"); -const childProcess = require('child_process'); const { Command } = require("commander"); const inquirer = require("inquirer"); const { teamsCreate, teamsList } = require("./teams"); const { projectsCreate } = require("./projects"); -const { functionsCreate } = require("./functions"); const { databasesGet, databasesListCollections, databasesList } = require("./databases"); const { storageListBuckets } = require("./storage"); const { sdkForConsole } = require("../sdks"); const { localConfig } = require("../config"); const ID = require("../id"); const { paginate } = require("../paginate"); -const { questionsPullProject, questionsPullFunction, questionsPullCollection } = require("../questions"); +const { questionsPullProject, questionsPullCollection } = require("../questions"); const { success, log, actionRunner, commandDescriptions } = require("../parser"); const pull = new Command("pull") @@ -32,116 +28,6 @@ const pullProject = async () => { success(); } -const pullFunction = async () => { - // TODO: Add CI/CD support (ID, name, runtime) - const answers = await inquirer.prompt(questionsPullFunction) - const functionFolder = path.join(process.cwd(), 'functions'); - - if (!fs.existsSync(functionFolder)) { - fs.mkdirSync(functionFolder, { - recursive: true - }); - } - - const functionId = answers.id === 'unique()' ? ID.unique() : answers.id; - const functionDir = path.join(functionFolder, functionId); - - if (fs.existsSync(functionDir)) { - throw new Error(`( ${functionId} ) already exists in the current directory. Please choose another name.`); - } - - if (!answers.runtime.entrypoint) { - log(`Entrypoint for this runtime not found. You will be asked to configure entrypoint when you first push the function.`); - } - - if (!answers.runtime.commands) { - log(`Installation command for this runtime not found. You will be asked to configure the install command when you first push the function.`); - } - - let response = await functionsCreate({ - functionId, - name: answers.name, - runtime: answers.runtime.id, - entrypoint: answers.runtime.entrypoint || '', - commands: answers.runtime.commands || '', - parseOutput: false - }) - - fs.mkdirSync(functionDir, "777"); - - let gitInitCommands = "git clone -b v3 --single-branch --depth 1 --sparse https://github.com/{{ sdk.gitUserName }}/functions-starter ."; // depth prevents fetching older commits reducing the amount fetched - - let gitPullCommands = `git sparse-checkout add ${answers.runtime.id}`; - - /* Force use CMD as powershell does not support && */ - if (process.platform === 'win32') { - gitInitCommands = 'cmd /c "' + gitInitCommands + '"'; - gitPullCommands = 'cmd /c "' + gitPullCommands + '"'; - } - - /* Execute the child process but do not print any std output */ - try { - childProcess.execSync(gitInitCommands, { stdio: 'pipe', cwd: functionDir }); - childProcess.execSync(gitPullCommands, { stdio: 'pipe', cwd: functionDir }); - } catch (error) { - /* Specialised errors with recommended actions to take */ - if (error.message.includes('error: unknown option')) { - throw new Error(`${error.message} \n\nSuggestion: Try updating your git to the latest version, then trying to run this command again.`) - } else if (error.message.includes('is not recognized as an internal or external command,') || error.message.includes('command not found')) { - throw new Error(`${error.message} \n\nSuggestion: It appears that git is not installed, try installing git then trying to run this command again.`) - } else { - throw error; - } - } - - fs.rmSync(path.join(functionDir, ".git"), { recursive: true }); - const copyRecursiveSync = (src, dest) => { - let exists = fs.existsSync(src); - let stats = exists && fs.statSync(src); - let isDirectory = exists && stats.isDirectory(); - if (isDirectory) { - if (!fs.existsSync(dest)) { - fs.mkdirSync(dest); - } - - fs.readdirSync(src).forEach(function (childItemName) { - copyRecursiveSync(path.join(src, childItemName), path.join(dest, childItemName)); - }); - } else { - fs.copyFileSync(src, dest); - } - }; - copyRecursiveSync(path.join(functionDir, answers.runtime.id), functionDir); - - fs.rmSync(`${functionDir}/${answers.runtime.id}`, { recursive: true, force: true }); - - const readmePath = path.join(process.cwd(), 'functions', functionId, 'README.md'); - const readmeFile = fs.readFileSync(readmePath).toString(); - const newReadmeFile = readmeFile.split('\n'); - newReadmeFile[0] = `# ${answers.name}`; - newReadmeFile.splice(1, 2); - fs.writeFileSync(readmePath, newReadmeFile.join('\n')); - - let data = { - $id: response['$id'], - name: response.name, - runtime: response.runtime, - execute: response.execute, - events: response.events, - schedule: response.schedule, - timeout: response.timeout, - enabled: response.enabled, - logging: response.logging, - entrypoint: response.entrypoint, - commands: response.commands, - ignore: answers.runtime.ignore || null, - path: `functions/${functionId}`, - }; - - localConfig.addFunction(data); - success(); -} - const pullCollection = async ({ all, databaseId } = {}) => { const databaseIds = []; @@ -221,11 +107,6 @@ pull .description("Pulling your {{ spec.title|caseUcfirst }} project") .action(actionRunner(pullProject)); -pull - .command("function") - .description("Pulling your {{ spec.title|caseUcfirst }} cloud function") - .action(actionRunner(pullFunction)) - pull .command("collection") .description("Pulling your {{ spec.title|caseUcfirst }} collections") diff --git a/templates/cli/lib/questions.js.twig b/templates/cli/lib/questions.js.twig index a52c11555..b8f8724aa 100644 --- a/templates/cli/lib/questions.js.twig +++ b/templates/cli/lib/questions.js.twig @@ -163,6 +163,44 @@ const questionsCreateProject = [ } ]; +const questionsCreateFunction = [ + { + type: "input", + name: "name", + message: "What would you like to name your function?", + default: "My Awesome Function" + }, + { + type: "input", + name: "id", + message: "What ID would you like to have for your function?", + default: "unique()" + }, + { + type: "list", + name: "runtime", + message: "What runtime would you like to use?", + choices: async () => { + let response = await functionsListRuntimes({ + parseOutput: false + }) + let runtimes = response["runtimes"] + let choices = runtimes.map((runtime, idx) => { + return { + name: `${runtime.name} (${runtime['$id']})`, + value: { + id: runtime['$id'], + entrypoint: getEntrypoint(runtime['$id']), + ignore: getIgnores(runtime['$id']), + commands: getInstallCommand(runtime['$id']) + }, + } + }) + return choices; + } + } +]; + const questionsCreateBucket = [ { type: "input", @@ -273,44 +311,6 @@ const questionsPullProject = [ } ]; -const questionsPullFunction = [ - { - type: "input", - name: "name", - message: "What would you like to name your function?", - default: "My Awesome Function" - }, - { - type: "input", - name: "id", - message: "What ID would you like to have for your function?", - default: "unique()" - }, - { - type: "list", - name: "runtime", - message: "What runtime would you like to use?", - choices: async () => { - let response = await functionsListRuntimes({ - parseOutput: false - }) - let runtimes = response["runtimes"] - let choices = runtimes.map((runtime, idx) => { - return { - name: `${runtime.name} (${runtime['$id']})`, - value: { - id: runtime['$id'], - entrypoint: getEntrypoint(runtime['$id']), - ignore: getIgnores(runtime['$id']), - commands: getInstallCommand(runtime['$id']) - }, - } - }) - return choices; - } - } -]; - const questionsPullCollection = [ { type: "checkbox", @@ -534,12 +534,12 @@ const questionsMfaChallenge = [ module.exports = { questionsCreateProject, + questionsCreateFunction, questionsCreateBucket, questionsCreateCollection, questionsCreateMessagingTopic, questionsPullProject, questionsLogin, - questionsPullFunction, questionsPullCollection, questionsPushFunctions, questionsPushCollections, From c90817e038ba808981b4c388a93ba6f10f82c10d Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Mon, 20 May 2024 11:07:29 -0400 Subject: [PATCH 07/26] chore(cli): Marking as todo --- templates/cli/lib/commands/create.js.twig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/templates/cli/lib/commands/create.js.twig b/templates/cli/lib/commands/create.js.twig index 9f96d9275..418879b14 100644 --- a/templates/cli/lib/commands/create.js.twig +++ b/templates/cli/lib/commands/create.js.twig @@ -99,7 +99,8 @@ const createTopic = async () => { parseOutput: false }) - {# localConfig.addMessagingTopic(response); #} + // TODO: after https://github.com/appwrite/sdk-generator/pull/839 + // localConfig.addMessagingTopic(response); success(); } catch (e) { error(e.getMessage ?? 'Unknown error occurred. Please try again'); From 3a47c6625cdc737b320514dc0973ff2f4c513758 Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Mon, 20 May 2024 13:17:07 -0400 Subject: [PATCH 08/26] feat(cli): Pulling function code and definition --- templates/cli/base/params.twig | 16 +++-- templates/cli/base/requests/api.twig | 8 ++- templates/cli/lib/commands/command.js.twig | 5 +- templates/cli/lib/commands/pull.js.twig | 76 ++++++++++++++++++++-- templates/cli/lib/questions.js.twig | 29 ++++++++- 5 files changed, 117 insertions(+), 17 deletions(-) diff --git a/templates/cli/base/params.twig b/templates/cli/base/params.twig index e36ae2f9c..d0a8b685e 100644 --- a/templates/cli/base/params.twig +++ b/templates/cli/base/params.twig @@ -11,7 +11,7 @@ if (!fs.lstatSync(folderPath).isDirectory()) { throw new Error('The path is not a directory.'); } - + const ignorer = ignore(); const func = localConfig.getFunction(functionId); @@ -23,7 +23,7 @@ ignorer.add(fs.readFileSync(pathLib.join({{ parameter.name | caseCamel | escapeKeyword }}, '.gitignore')).toString()); log('Ignoring files in .gitignore'); } - + const files = getAllFiles({{ parameter.name | caseCamel | escapeKeyword }}).map((file) => pathLib.relative({{ parameter.name | caseCamel | escapeKeyword }}, file)).filter((file) => !ignorer.ignores(file)); await tar @@ -77,8 +77,10 @@ {% endif %} {% endfor %} {% if method.type == 'location' %} - payload['project'] = localConfig.getProject().projectId - payload['key'] = globalConfig.getKey(); - const queryParams = new URLSearchParams(payload); - apiPath = `${globalConfig.getEndpoint()}${apiPath}?${queryParams.toString()}`; -{% endif %} \ No newline at end of file + if (!overrideForCli) { + payload['project'] = localConfig.getProject().projectId + payload['key'] = globalConfig.getKey(); + const queryParams = new URLSearchParams(payload); + apiPath = `${globalConfig.getEndpoint()}${apiPath}?${queryParams.toString()}`; + } +{% endif %} diff --git a/templates/cli/base/requests/api.twig b/templates/cli/base/requests/api.twig index c8e97c064..608e10980 100644 --- a/templates/cli/base/requests/api.twig +++ b/templates/cli/base/requests/api.twig @@ -10,6 +10,10 @@ }, payload{% if method.type == 'location' %}, 'arraybuffer'{% endif %}); {% if method.type == 'location' %} + if (overrideForCli) { + response = Buffer.from(response); + } + fs.writeFileSync(destination, response); {% endif %} @@ -17,5 +21,5 @@ parse(response) success() } - - return response; \ No newline at end of file + + return response; diff --git a/templates/cli/lib/commands/command.js.twig b/templates/cli/lib/commands/command.js.twig index 3607fb3d3..7ad257951 100644 --- a/templates/cli/lib/commands/command.js.twig +++ b/templates/cli/lib/commands/command.js.twig @@ -45,6 +45,7 @@ const {{ service.name | caseLower }} = new Command("{{ service.name | caseLower {% for parameter in method.parameters.all %} * @property {{ "{" }}{{ parameter | typeName }}{{ "}" }} {{ parameter.name | caseCamel | escapeKeyword }} {{ parameter.description | replace({'`':'\''}) | replace({'\n':' '}) | replace({'\n \n':' '}) }} {% endfor %} + * @property {boolean} overrideForCli * @property {boolean} parseOutput * @property {libClient | undefined} sdk {% if 'multipart/form-data' in method.consumes %} @@ -58,7 +59,7 @@ const {{ service.name | caseLower }} = new Command("{{ service.name | caseLower /** * @param {{ "{" }}{{ service.name | caseUcfirst }}{{ method.name | caseUcfirst }}RequestParams{{ "}" }} params */ -const {{ service.name | caseLower }}{{ method.name | caseUcfirst }} = async ({ {% for parameter in method.parameters.all %}{{ parameter.name | caseCamel | escapeKeyword }}, {% endfor %}parseOutput = true, sdk = undefined{% if 'multipart/form-data' in method.consumes %}, onProgress = () => {}{% endif %}{% if method.type == 'location' %}, destination{% endif %}}) => { +const {{ service.name | caseLower }}{{ method.name | caseUcfirst }} = async ({ {% for parameter in method.parameters.all %}{{ parameter.name | caseCamel | escapeKeyword }}, {% endfor %}parseOutput = true, overrideForCli = false, sdk = undefined{% if 'multipart/form-data' in method.consumes %}, onProgress = () => {}{% endif %}{% if method.type == 'location' %}, destination{% endif %}}) => { let client = !sdk ? await {% if service.name == "projects" %}sdkForConsole(){% else %}sdkForProject(){% endif %} : sdk; let apiPath = '{{ method.path }}'{% for parameter in method.parameters.path %}.replace('{{ '{' }}{{ parameter.name | caseCamel }}{{ '}' }}', {{ parameter.name | caseCamel | escapeKeyword }}){% endfor %}; {{ include ('cli/base/params.twig') }} @@ -90,4 +91,4 @@ module.exports = { {{ service.name | caseLower }}{{ method.name | caseUcfirst }}{% if not loop.last %},{% endif %} {% endfor %} -}; \ No newline at end of file +}; diff --git a/templates/cli/lib/commands/pull.js.twig b/templates/cli/lib/commands/pull.js.twig index 84a865b80..457981372 100644 --- a/templates/cli/lib/commands/pull.js.twig +++ b/templates/cli/lib/commands/pull.js.twig @@ -1,14 +1,17 @@ +const fs = require("fs"); +const tar = require("tar"); const { Command } = require("commander"); const inquirer = require("inquirer"); const { teamsCreate, teamsList } = require("./teams"); const { projectsCreate } = require("./projects"); +const { functionsList, functionsDownloadDeployment } = require("./functions"); const { databasesGet, databasesListCollections, databasesList } = require("./databases"); const { storageListBuckets } = require("./storage"); const { sdkForConsole } = require("../sdks"); const { localConfig } = require("../config"); const ID = require("../id"); const { paginate } = require("../paginate"); -const { questionsPullProject, questionsPullCollection } = require("../questions"); +const { questionsPullProject, questionsPullCollection, questionsPullFunctions } = require("../questions"); const { success, log, actionRunner, commandDescriptions } = require("../parser"); const pull = new Command("pull") @@ -28,6 +31,64 @@ const pullProject = async () => { success(); } +const pullFunctions = async ({ all, yes } = {}) => { + let functions = []; + let questions = questionsPullFunctions; + + const localFunctions = localConfig.getFunctions(); + + if (all) { + questions = yes ? [] : questionsPullFunctions[1]; + functions = (await paginate(functionsList, { parseOutput: false }, 100, 'functions')).functions; + } + + const answers = await inquirer.prompt(questions); + + const overridingLocalChanges = yes ?? answers.override.toLowerCase() === "yes"; + const selectedFunctions = functions.length === 0 ? answers.functions : functions; + + for (let func of selectedFunctions) { + const functionExistLocally = localFunctions.find((localFunc) => localFunc['$id'] === func['$id']) !== undefined; + + if (!overridingLocalChanges && functionExistLocally) { + log(`Skipping locally found implementation of ${func['name']}`) + continue; + } + if (functionExistLocally) { + localConfig.updateFunction(func['$id'], func); + } else { + func['path'] = `functions/${func['$id']}`; + localConfig.addFunction(func); + } + + const localFunction = localFunctions.find((localFunc) => localFunc['$id'] === func['$id']); + + if (localFunction['deployment'] === '') { + continue + } + + const compressedFileName = `${+new Date()}.tar.gz` + + await functionsDownloadDeployment({ + functionId: func['$id'], + deploymentId: func['deployment'], + destination: compressedFileName, + overrideForCli: true, + parseOutput: false + }) + + tar.extract({ + sync: true, + cwd: localFunction['path'], + file: compressedFileName, + strict: false, + }); + + fs.rmSync(compressedFileName); + success(`Pulled ${func['name']} code and definition`) + } +} + const pullCollection = async ({ all, databaseId } = {}) => { const databaseIds = []; @@ -104,14 +165,21 @@ const pullTeam = async () => { pull .command("project") - .description("Pulling your {{ spec.title|caseUcfirst }} project") + .description("Pulling your Appwrite project") .action(actionRunner(pullProject)); +pull + .command("function") + .description(`Pulling your Appwrite functions`) + .option(`--yes`, `Flag to confirm all warnings`) + .option(`--all`, `Flag to pull all functions`) + .action(actionRunner(pullFunctions)); + pull .command("collection") - .description("Pulling your {{ spec.title|caseUcfirst }} collections") + .description("Pulling your Appwrite collections") .option(`--databaseId `, `Database ID`) - .option(`--all`, `Flag to pullialize all databases`) + .option(`--all`, `Flag to pull all databases`) .action(actionRunner(pullCollection)) pull diff --git a/templates/cli/lib/questions.js.twig b/templates/cli/lib/questions.js.twig index b8f8724aa..491d0bac5 100644 --- a/templates/cli/lib/questions.js.twig +++ b/templates/cli/lib/questions.js.twig @@ -1,12 +1,12 @@ const { localConfig } = require('./config'); const { projectsList } = require('./commands/projects'); const { teamsList } = require('./commands/teams'); -const { functionsListRuntimes } = require('./commands/functions'); +const { functionsListRuntimes, functionsList } = require('./commands/functions'); const { accountListMfaFactors } = require("./commands/account"); const { sdkForConsole } = require("./sdks"); const { validateRequired } = require("./validations"); const { paginate } = require('./paginate'); - +const chalk = require('chalk'); const { databasesList } = require('./commands/databases'); const JSONbig = require("json-bigint")({ storeAsString: false }); @@ -311,6 +311,30 @@ const questionsPullProject = [ } ]; + +const questionsPullFunctions = [ + { + type: "checkbox", + name: "functions", + message: "Which functions would you like to pull?", + choices: async () => { + const { functions } = await paginate(functionsList, { parseOutput: false }, 100, 'functions'); + + return functions.map(func => { + return { + name: `${func.name} (${func.$id})`, + value: func + } + }); + } + }, + { + type: "input", + name: "override", + message: `Do you want to override local functions code and definition? ${chalk.red('all local changes will lost!')} Type "YES" to confirm.` + } +]; + const questionsPullCollection = [ { type: "checkbox", @@ -539,6 +563,7 @@ module.exports = { questionsCreateCollection, questionsCreateMessagingTopic, questionsPullProject, + questionsPullFunctions, questionsLogin, questionsPullCollection, questionsPushFunctions, From c0766e49f632397050a1e9ee16af1d0e2d40a4ac Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Mon, 20 May 2024 13:19:58 -0400 Subject: [PATCH 09/26] fix(cli): replacing ID import --- templates/cli/lib/commands/create.js.twig | 1 + templates/cli/lib/commands/pull.js.twig | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/cli/lib/commands/create.js.twig b/templates/cli/lib/commands/create.js.twig index 418879b14..bd3c86af9 100644 --- a/templates/cli/lib/commands/create.js.twig +++ b/templates/cli/lib/commands/create.js.twig @@ -9,6 +9,7 @@ const { messagingCreateTopic } = require("./messaging"); const { functionsCreate } = require("./functions"); const { databasesCreateCollection } = require("./databases"); const { sdkForConsole } = require("../sdks"); +const ID = require("../id"); const { localConfig } = require("../config"); const { questionsCreateProject, diff --git a/templates/cli/lib/commands/pull.js.twig b/templates/cli/lib/commands/pull.js.twig index 84a865b80..95698a39e 100644 --- a/templates/cli/lib/commands/pull.js.twig +++ b/templates/cli/lib/commands/pull.js.twig @@ -6,7 +6,6 @@ const { databasesGet, databasesListCollections, databasesList } = require("./dat const { storageListBuckets } = require("./storage"); const { sdkForConsole } = require("../sdks"); const { localConfig } = require("../config"); -const ID = require("../id"); const { paginate } = require("../paginate"); const { questionsPullProject, questionsPullCollection } = require("../questions"); const { success, log, actionRunner, commandDescriptions } = require("../parser"); From 0a0a67100d78f0b17828d3669ec2b536751aa386 Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Mon, 20 May 2024 14:59:49 -0400 Subject: [PATCH 10/26] feat(cli): Interactive topics creation --- templates/cli/lib/commands/create.js.twig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/templates/cli/lib/commands/create.js.twig b/templates/cli/lib/commands/create.js.twig index bd3c86af9..8a3cbf11d 100644 --- a/templates/cli/lib/commands/create.js.twig +++ b/templates/cli/lib/commands/create.js.twig @@ -100,8 +100,7 @@ const createTopic = async () => { parseOutput: false }) - // TODO: after https://github.com/appwrite/sdk-generator/pull/839 - // localConfig.addMessagingTopic(response); + localConfig.addMessagingTopic(response); success(); } catch (e) { error(e.getMessage ?? 'Unknown error occurred. Please try again'); From 60c2bd5172b0618b8e504603e7f09b4d260f5a26 Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Tue, 21 May 2024 08:55:29 -0400 Subject: [PATCH 11/26] refactor(cli): twig tags --- templates/cli/base/requests/api.twig | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/templates/cli/base/requests/api.twig b/templates/cli/base/requests/api.twig index 608e10980..aee246c41 100644 --- a/templates/cli/base/requests/api.twig +++ b/templates/cli/base/requests/api.twig @@ -9,14 +9,13 @@ {% endfor %} }, payload{% if method.type == 'location' %}, 'arraybuffer'{% endif %}); -{% if method.type == 'location' %} + {%~ if method.type == 'location' %} if (overrideForCli) { response = Buffer.from(response); } fs.writeFileSync(destination, response); - -{% endif %} + {%~ endif %} if (parseOutput) { parse(response) success() From 4dd832f2d1e2d67382626967fb839b22e07a70fb Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Tue, 21 May 2024 09:19:07 -0400 Subject: [PATCH 12/26] refactor(cli): twig multiple lines --- templates/cli/lib/commands/command.js.twig | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/templates/cli/lib/commands/command.js.twig b/templates/cli/lib/commands/command.js.twig index 7ad257951..7751e1804 100644 --- a/templates/cli/lib/commands/command.js.twig +++ b/templates/cli/lib/commands/command.js.twig @@ -59,8 +59,21 @@ const {{ service.name | caseLower }} = new Command("{{ service.name | caseLower /** * @param {{ "{" }}{{ service.name | caseUcfirst }}{{ method.name | caseUcfirst }}RequestParams{{ "}" }} params */ -const {{ service.name | caseLower }}{{ method.name | caseUcfirst }} = async ({ {% for parameter in method.parameters.all %}{{ parameter.name | caseCamel | escapeKeyword }}, {% endfor %}parseOutput = true, overrideForCli = false, sdk = undefined{% if 'multipart/form-data' in method.consumes %}, onProgress = () => {}{% endif %}{% if method.type == 'location' %}, destination{% endif %}}) => { - let client = !sdk ? await {% if service.name == "projects" %}sdkForConsole(){% else %}sdkForProject(){% endif %} : sdk; +{%~ block decleration -%} +const {{ service.name | caseLower }}{{ method.name | caseUcfirst }} = async ({ + {%- for parameter in method.parameters.all -%} + {{ parameter.name | caseCamel | escapeKeyword }}, + {%- endfor -%} + + {%- block baseParams -%}parseOutput = true, overrideForCli = false, sdk = undefined {%- endblock -%} + + {%- if 'multipart/form-data' in method.consumes -%},onProgress = () => {}{%- endif -%} + + {%- if method.type == 'location' -%}, destination{%- endif -%} +}) => { +{%~ endblock %} + let client = !sdk ? await {% if service.name == "projects" %}sdkForConsole(){% else %}sdkForProject(){% endif %} : + sdk; let apiPath = '{{ method.path }}'{% for parameter in method.parameters.path %}.replace('{{ '{' }}{{ parameter.name | caseCamel }}{{ '}' }}', {{ parameter.name | caseCamel | escapeKeyword }}){% endfor %}; {{ include ('cli/base/params.twig') }} {% if 'multipart/form-data' in method.consumes %} From 2d8fe79e61cea4f20fec75c61a936d51c99613d1 Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Tue, 21 May 2024 16:28:58 -0400 Subject: [PATCH 13/26] feat(cli): Init project forces login --- src/SDK/Language/CLI.php | 4 +- templates/cli/index.js.twig | 4 +- templates/cli/lib/commands/generic.js.twig | 91 ++++++++++--------- .../commands/{create.js.twig => init.js.twig} | 35 +++++-- templates/cli/lib/parser.js.twig | 3 +- templates/cli/lib/questions.js.twig | 14 ++- 6 files changed, 88 insertions(+), 63 deletions(-) rename templates/cli/lib/commands/{create.js.twig => init.js.twig} (51%) diff --git a/src/SDK/Language/CLI.php b/src/SDK/Language/CLI.php index e78bcbeb8..9f91cccf3 100644 --- a/src/SDK/Language/CLI.php +++ b/src/SDK/Language/CLI.php @@ -174,8 +174,8 @@ public function getFiles(): array ], [ 'scope' => 'default', - 'destination' => 'lib/commands/create.js', - 'template' => 'cli/lib/commands/create.js.twig', + 'destination' => 'lib/commands/init.js', + 'template' => 'cli/lib/commands/init.js.twig', ], [ 'scope' => 'default', diff --git a/templates/cli/index.js.twig b/templates/cli/index.js.twig index 9e5e9d714..87dd70298 100644 --- a/templates/cli/index.js.twig +++ b/templates/cli/index.js.twig @@ -12,7 +12,7 @@ const { commandDescriptions, cliConfig } = require("./lib/parser"); const { client } = require("./lib/commands/generic"); {% if sdk.test != "true" %} const { login, logout } = require("./lib/commands/generic"); -const { create } = require("./lib/commands/create"); +const { init } = require("./lib/commands/init"); const { pull } = require("./lib/commands/pull"); const { push } = require("./lib/commands/push"); {% endif %} @@ -38,7 +38,7 @@ program .showSuggestionAfterError() {% if sdk.test != "true" %} .addCommand(login) - .addCommand(create) + .addCommand(init) .addCommand(pull) .addCommand(push) .addCommand(logout) diff --git a/templates/cli/lib/commands/generic.js.twig b/templates/cli/lib/commands/generic.js.twig index f66a36ea3..ce052b681 100644 --- a/templates/cli/lib/commands/generic.js.twig +++ b/templates/cli/lib/commands/generic.js.twig @@ -8,62 +8,64 @@ const { actionRunner, success, parseBool, commandDescriptions, log, parse } = re const { questionsLogin, questionsListFactors, questionsMfaChallenge } = require("../questions"); const { accountUpdateMfaChallenge, accountCreateMfaChallenge, accountGet, accountCreateEmailPasswordSession, accountDeleteSession } = require("./account"); -const login = new Command("login") - .description(commandDescriptions['login']) - .configureHelp({ - helpWidth: process.stdout.columns || 80 +const loginCommand = async () => { + const answers = await inquirer.prompt(questionsLogin) + + let client = await sdkForConsole(false); + + await accountCreateEmailPasswordSession({ + email: answers.email, + password: answers.password, + parseOutput: false, + sdk: client }) - .action(actionRunner(async () => { - const answers = await inquirer.prompt(questionsLogin) - let client = await sdkForConsole(false); + client.setCookie(globalConfig.getCookie()); - await accountCreateEmailPasswordSession({ - email: answers.email, - password: answers.password, - parseOutput: false, - sdk: client - }) + let account; + + try { + account = await accountGet({ + sdk: client, + parseOutput: false + }); + } catch(error) { + if (error.response === 'user_more_factors_required') { + const { factor } = await inquirer.prompt(questionsListFactors); - client.setCookie(globalConfig.getCookie()); + const challenge = await accountCreateMfaChallenge({ + factor, + parseOutput: false, + sdk: client + }); - let account; + const { otp } = await inquirer.prompt(questionsMfaChallenge); + + await accountUpdateMfaChallenge({ + challengeId: challenge.$id, + otp, + parseOutput: false, + sdk: client + }); - try { account = await accountGet({ sdk: client, parseOutput: false }); - } catch(error) { - if (error.response === 'user_more_factors_required') { - const { factor } = await inquirer.prompt(questionsListFactors); - - const challenge = await accountCreateMfaChallenge({ - factor, - parseOutput: false, - sdk: client - }); - - const { otp } = await inquirer.prompt(questionsMfaChallenge); - - await accountUpdateMfaChallenge({ - challengeId: challenge.$id, - otp, - parseOutput: false, - sdk: client - }); - - account = await accountGet({ - sdk: client, - parseOutput: false - }); - } else { - throw error; - } + } else { + throw error; } + } - success("Signed in as user with ID: " + account.$id); - })); + success("Signed in as user with ID: " + account.$id); +}; + +const login = new Command("login") + .description(commandDescriptions['login']) + .configureHelp({ + helpWidth: process.stdout.columns || 80 + }) + .action(actionRunner(loginCommand)); const logout = new Command("logout") .description(commandDescriptions['logout']) @@ -159,6 +161,7 @@ const client = new Command("client") module.exports = { {% if sdk.test != "true" %} + loginCommand, login, logout, {% endif %} diff --git a/templates/cli/lib/commands/create.js.twig b/templates/cli/lib/commands/init.js.twig similarity index 51% rename from templates/cli/lib/commands/create.js.twig rename to templates/cli/lib/commands/init.js.twig index 339a21ace..e163c0363 100644 --- a/templates/cli/lib/commands/create.js.twig +++ b/templates/cli/lib/commands/init.js.twig @@ -2,12 +2,14 @@ const { Command } = require("commander"); const inquirer = require("inquirer"); const { projectsCreate } = require("./projects"); const { sdkForConsole } = require("../sdks"); -const { localConfig } = require("../config"); +const { localConfig, globalConfig } = require("../config"); const { questionsCreateProject } = require("../questions"); -const { success, actionRunner, commandDescriptions } = require("../parser"); +const { success, error, actionRunner, commandDescriptions } = require("../parser"); +const { accountGet } = require("./account"); +const { loginCommand } = require("./generic"); -const create = new Command("create") - .description(commandDescriptions['create']) +const init = new Command("init") + .description(commandDescriptions['init']) .configureHelp({ helpWidth: process.stdout.columns || 80 }) @@ -15,8 +17,21 @@ const create = new Command("create") command.help(); })); -const createProject = async () => { - let response = {} +const initProject = async () => { + let response = {}; + + try { + if (globalConfig.getEndpoint() === '' || globalConfig.getCookie() === '') { + throw ''; + } + await accountGet({ + parseOutput: false + }); + } catch (e) { + error('You must login first') + await loginCommand() + } + const answers = await inquirer.prompt(questionsCreateProject) if (!answers.project || !answers.organization) process.exit(1) @@ -31,11 +46,11 @@ const createProject = async () => { success(); } -create +init .command("project") - .description("Create a new {{ spec.title|caseUcfirst }} project") - .action(actionRunner(createProject)); + .description("Init and create a new {{ spec.title|caseUcfirst }} project") + .action(actionRunner(initProject)); module.exports = { - create, + init, }; diff --git a/templates/cli/lib/parser.js.twig b/templates/cli/lib/parser.js.twig index cf94b3a8b..cc380e789 100644 --- a/templates/cli/lib/parser.js.twig +++ b/templates/cli/lib/parser.js.twig @@ -156,7 +156,8 @@ const commandDescriptions = { "graphql": `The graphql command allows you to query and mutate any resource type on your Appwrite server.`, "avatars": `The avatars command aims to help you complete everyday tasks related to your app image, icons, and avatars.`, "databases": `The databases command allows you to create structured collections of documents, query and filter lists of documents.`, - "create": `The create command provides a convenient wrapper for creating projects functions, collections, buckets, teams and messaging.`, + "init": `The init command provides a convenient wrapper for creating and initializing project in Appwrite.`, + "create": `The create command provides a convenient wrapper for creating functions, collections, buckets, teams and messaging.`, "push": `The push command provides a convenient wrapper for pushing your functions, collections, buckets, teams and messaging.`, "functions": `The functions command allows you view, create and manage your Cloud Functions.`, "health": `The health command allows you to both validate and monitor your {{ spec.title|caseUcfirst }} server's health.`, diff --git a/templates/cli/lib/questions.js.twig b/templates/cli/lib/questions.js.twig index a2c66965b..ae0744dd1 100644 --- a/templates/cli/lib/questions.js.twig +++ b/templates/cli/lib/questions.js.twig @@ -10,6 +10,8 @@ const { paginate } = require('./paginate'); const { databasesList } = require('./commands/databases'); const JSONbig = require("json-bigint")({ storeAsString: false }); +const whenOverride = (answers)=> answers.override === undefined ? true : answers.override; + const getIgnores = (runtime) => { const languge = runtime.split('-')[0]; @@ -143,7 +145,8 @@ const questionsProject = [ } return choices; - } + }, + when: whenOverride }, ]; @@ -153,13 +156,15 @@ const questionsCreateProject = [ type: "input", name: "project", message: "What would you like to name your project?", - default: "My Awesome Project" + default: "My Awesome Project", + when: whenOverride }, { type: "input", name: "id", message: "What ID would you like to have for your project?", - default: "unique()" + default: "unique()", + when: whenOverride } ]; const questionsPullProject = [ @@ -189,7 +194,8 @@ const questionsPullProject = [ } return choices; - } + }, + when: whenOverride } ]; From abef847d882d9f6a57b59e84c7d00fb04b1fbc9d Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Wed, 22 May 2024 08:29:33 -0400 Subject: [PATCH 14/26] refactor(cli): Refactor message text --- templates/cli/lib/questions.js.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/cli/lib/questions.js.twig b/templates/cli/lib/questions.js.twig index 830039785..197d81edd 100644 --- a/templates/cli/lib/questions.js.twig +++ b/templates/cli/lib/questions.js.twig @@ -331,7 +331,7 @@ const questionsPullFunctions = [ { type: "input", name: "override", - message: `Do you want to override local functions code and definition? ${chalk.red('all local changes will lost!')} Type "YES" to confirm.` + message: `Are you sure you want to override local functions code and definition? ${chalk.red('All local changes will be lost!')} Type "YES" to confirm.` } ]; From 634679a06a6a9a974dcb3961f9db955cd67a921e Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Wed, 22 May 2024 08:49:50 -0400 Subject: [PATCH 15/26] chore(cli): rebasing head --- src/SDK/Language/CLI.php | 5 + templates/cli/index.js.twig | 2 + templates/cli/lib/commands/create.js.twig | 224 ++++++++++++++++++++++ templates/cli/lib/commands/init.js.twig | 210 +------------------- templates/cli/lib/questions.js.twig | 32 +--- 5 files changed, 233 insertions(+), 240 deletions(-) create mode 100644 templates/cli/lib/commands/create.js.twig diff --git a/src/SDK/Language/CLI.php b/src/SDK/Language/CLI.php index 9f91cccf3..5b36eea70 100644 --- a/src/SDK/Language/CLI.php +++ b/src/SDK/Language/CLI.php @@ -177,6 +177,11 @@ public function getFiles(): array 'destination' => 'lib/commands/init.js', 'template' => 'cli/lib/commands/init.js.twig', ], + [ + 'scope' => 'default', + 'destination' => 'lib/commands/create.js', + 'template' => 'cli/lib/commands/create.js.twig', + ], [ 'scope' => 'default', 'destination' => 'lib/commands/pull.js', diff --git a/templates/cli/index.js.twig b/templates/cli/index.js.twig index 87dd70298..3798dedc6 100644 --- a/templates/cli/index.js.twig +++ b/templates/cli/index.js.twig @@ -12,6 +12,7 @@ const { commandDescriptions, cliConfig } = require("./lib/parser"); const { client } = require("./lib/commands/generic"); {% if sdk.test != "true" %} const { login, logout } = require("./lib/commands/generic"); +const { create } = require("./lib/commands/create"); const { init } = require("./lib/commands/init"); const { pull } = require("./lib/commands/pull"); const { push } = require("./lib/commands/push"); @@ -38,6 +39,7 @@ program .showSuggestionAfterError() {% if sdk.test != "true" %} .addCommand(login) + .addCommand(create) .addCommand(init) .addCommand(pull) .addCommand(push) diff --git a/templates/cli/lib/commands/create.js.twig b/templates/cli/lib/commands/create.js.twig new file mode 100644 index 000000000..852116822 --- /dev/null +++ b/templates/cli/lib/commands/create.js.twig @@ -0,0 +1,224 @@ +const fs = require("fs"); +const path = require("path"); +const childProcess = require('child_process'); +const { Command } = require("commander"); +const inquirer = require("inquirer"); +const { storageCreateBucket } = require("./storage"); +const { messagingCreateTopic } = require("./messaging"); +const { functionsCreate } = require("./functions"); +const { databasesCreateCollection } = require("./databases"); +const ID = require("../id"); +const { localConfig } = require("../config"); +const { + questionsCreateFunction, + questionsCreateBucket, + questionsCreateMessagingTopic, + questionsCreateCollection +} = require("../questions"); +const { success, error, actionRunner, commandDescriptions } = require("../parser"); + +const create = new Command("create") + .description(commandDescriptions['create']) + .configureHelp({ + helpWidth: process.stdout.columns || 80 + }) + .action(actionRunner(async (_options, command) => { + command.help(); + })); + + +const createBucket = async () => { + let response = {} + const answers = await inquirer.prompt(questionsCreateBucket) + if (!answers.bucket || !answers.id || !answers.fileSecurity) process.exit(1) + + try { + response = await storageCreateBucket({ + bucketId: answers.id, + name: answers.bucket, + fileSecurity: answers.fileSecurity.toLowerCase() === 'yes', + enabled: true, + parseOutput: false + }) + + localConfig.addBucket(response); + success(); + } catch (e) { + error(e.getMessage ?? 'Unknown error occurred. Please try again'); + } +}; + +const createCollection = async () => { + let response = {} + const answers = await inquirer.prompt(questionsCreateCollection) + if (!answers.database || !answers.collection || !answers.id || !answers.documentSecurity) process.exit(1) + + try { + response = await databasesCreateCollection({ + databaseId: answers.database, + collectionId: answers.id, + name: answers.collection, + documentSecurity: answers.documentSecurity.toLowerCase() === 'yes', + enabled: true, + parseOutput: false + }) + + localConfig.addCollection(response); + success(); + } catch (e) { + error(e.getMessage ?? 'Unknown error occurred. Please try again'); + } +}; + +const createTopic = async () => { + let response = {} + const answers = await inquirer.prompt(questionsCreateMessagingTopic) + if (!answers.topic || !answers.id) process.exit(1) + + try { + response = await messagingCreateTopic({ + topicId: answers.id, + name: answers.topic, + parseOutput: false + }) + + localConfig.addMessagingTopic(response); + success(); + } catch (e) { + error(e.getMessage ?? 'Unknown error occurred. Please try again'); + } +}; + +const createFunction = async () => { + // TODO: Add CI/CD support (ID, name, runtime) + const answers = await inquirer.prompt(questionsCreateFunction) + const functionFolder = path.join(process.cwd(), 'functions'); + + if (!fs.existsSync(functionFolder)) { + fs.mkdirSync(functionFolder, { + recursive: true + }); + } + + const functionId = answers.id === 'unique()' ? ID.unique() : answers.id; + const functionDir = path.join(functionFolder, functionId); + + if (fs.existsSync(functionDir)) { + throw new Error(`( ${functionId} ) already exists in the current directory. Please choose another name.`); + } + + if (!answers.runtime.entrypoint) { + log(`Entrypoint for this runtime not found. You will be asked to configure entrypoint when you first push the function.`); + } + + if (!answers.runtime.commands) { + log(`Installation command for this runtime not found. You will be asked to configure the install command when you first push the function.`); + } + + let response = await functionsCreate({ + functionId, + name: answers.name, + runtime: answers.runtime.id, + entrypoint: answers.runtime.entrypoint || '', + commands: answers.runtime.commands || '', + parseOutput: false + }) + + fs.mkdirSync(functionDir, "777"); + + let gitInitCommands = "git clone -b v3 --single-branch --depth 1 --sparse https://github.com/{{ sdk.gitUserName }}/functions-starter ."; // depth prevents fetching older commits reducing the amount fetched + + let gitPullCommands = `git sparse-checkout add ${answers.runtime.id}`; + + /* Force use CMD as powershell does not support && */ + if (process.platform === 'win32') { + gitInitCommands = 'cmd /c "' + gitInitCommands + '"'; + gitPullCommands = 'cmd /c "' + gitPullCommands + '"'; + } + + /* Execute the child process but do not print any std output */ + try { + childProcess.execSync(gitInitCommands, { stdio: 'pipe', cwd: functionDir }); + childProcess.execSync(gitPullCommands, { stdio: 'pipe', cwd: functionDir }); + } catch (error) { + /* Specialised errors with recommended actions to take */ + if (error.message.includes('error: unknown option')) { + throw new Error(`${error.message} \n\nSuggestion: Try updating your git to the latest version, then trying to run this command again.`) + } else if (error.message.includes('is not recognized as an internal or external command,') || error.message.includes('command not found')) { + throw new Error(`${error.message} \n\nSuggestion: It appears that git is not installed, try installing git then trying to run this command again.`) + } else { + throw error; + } + } + + fs.rmSync(path.join(functionDir, ".git"), { recursive: true }); + const copyRecursiveSync = (src, dest) => { + let exists = fs.existsSync(src); + let stats = exists && fs.statSync(src); + let isDirectory = exists && stats.isDirectory(); + if (isDirectory) { + if (!fs.existsSync(dest)) { + fs.mkdirSync(dest); + } + + fs.readdirSync(src).forEach(function (childItemName) { + copyRecursiveSync(path.join(src, childItemName), path.join(dest, childItemName)); + }); + } else { + fs.copyFileSync(src, dest); + } + }; + copyRecursiveSync(path.join(functionDir, answers.runtime.id), functionDir); + + fs.rmSync(`${functionDir}/${answers.runtime.id}`, { recursive: true, force: true }); + + const readmePath = path.join(process.cwd(), 'functions', functionId, 'README.md'); + const readmeFile = fs.readFileSync(readmePath).toString(); + const newReadmeFile = readmeFile.split('\n'); + newReadmeFile[0] = `# ${answers.name}`; + newReadmeFile.splice(1, 2); + fs.writeFileSync(readmePath, newReadmeFile.join('\n')); + + let data = { + $id: response['$id'], + name: response.name, + runtime: response.runtime, + execute: response.execute, + events: response.events, + schedule: response.schedule, + timeout: response.timeout, + enabled: response.enabled, + logging: response.logging, + entrypoint: response.entrypoint, + commands: response.commands, + ignore: answers.runtime.ignore || null, + path: `functions/${functionId}`, + }; + + localConfig.addFunction(data); + success(); +} + +create + .command("function") + .description("Create a new {{ spec.title|caseUcfirst }} function") + .action(actionRunner(createFunction)); + +create + .command("bucket") + .description("Create a new {{ spec.title|caseUcfirst }} bucket") + .action(actionRunner(createBucket)); + +create + .command("collection") + .description("Create a new {{ spec.title|caseUcfirst }} collection") + .action(actionRunner(createCollection)); + +create + .command("topic") + .description("Create a new {{ spec.title|caseUcfirst }} topic") + .action(actionRunner(createTopic)); + +module.exports = { + create, +}; diff --git a/templates/cli/lib/commands/init.js.twig b/templates/cli/lib/commands/init.js.twig index 523cdee53..a55ee0f36 100644 --- a/templates/cli/lib/commands/init.js.twig +++ b/templates/cli/lib/commands/init.js.twig @@ -1,23 +1,8 @@ -const fs = require("fs"); -const path = require("path"); -const childProcess = require('child_process'); const { Command } = require("commander"); const inquirer = require("inquirer"); const { projectsCreate } = require("./projects"); -const { storageCreateBucket } = require("./storage"); -const { messagingCreateTopic } = require("./messaging"); -const { functionsCreate } = require("./functions"); -const { databasesCreateCollection } = require("./databases"); -const { sdkForConsole } = require("../sdks"); -const ID = require("../id"); const { localConfig, globalConfig } = require("../config"); -const { - questionsCreateProject, - questionsCreateFunction, - questionsCreateBucket, - questionsCreateMessagingTopic, - questionsCreateCollection -} = require("../questions"); +const { questionsCreateProject, } = require("../questions"); const { success, error, actionRunner, commandDescriptions } = require("../parser"); const { accountGet } = require("./account"); const { loginCommand } = require("./generic"); @@ -60,205 +45,12 @@ const initProject = async () => { success(); } -const createBucket = async () => { - let response = {} - const answers = await inquirer.prompt(questionsCreateBucket) - if (!answers.bucket || !answers.id || !answers.fileSecurity) process.exit(1) - - try { - response = await storageCreateBucket({ - bucketId: answers.id, - name: answers.bucket, - fileSecurity: answers.fileSecurity.toLowerCase() === 'yes', - enabled: true, - parseOutput: false - }) - - localConfig.addBucket(response); - success(); - } catch (e) { - error(e.getMessage ?? 'Unknown error occurred. Please try again'); - } -}; - -const createCollection = async () => { - let response = {} - const answers = await inquirer.prompt(questionsCreateCollection) - if (!answers.database || !answers.collection || !answers.id || !answers.documentSecurity) process.exit(1) - - try { - response = await databasesCreateCollection({ - databaseId: answers.database, - collectionId: answers.id, - name: answers.collection, - documentSecurity: answers.documentSecurity.toLowerCase() === 'yes', - enabled: true, - parseOutput: false - }) - - localConfig.addCollection(response); - success(); - } catch (e) { - error(e.getMessage ?? 'Unknown error occurred. Please try again'); - } -}; - -const createTopic = async () => { - let response = {} - const answers = await inquirer.prompt(questionsCreateMessagingTopic) - if (!answers.topic || !answers.id) process.exit(1) - - try { - response = await messagingCreateTopic({ - topicId: answers.id, - name: answers.topic, - parseOutput: false - }) - - localConfig.addMessagingTopic(response); - success(); - } catch (e) { - error(e.getMessage ?? 'Unknown error occurred. Please try again'); - } -}; - -const createFunction = async () => { - // TODO: Add CI/CD support (ID, name, runtime) - const answers = await inquirer.prompt(questionsCreateFunction) - const functionFolder = path.join(process.cwd(), 'functions'); - - if (!fs.existsSync(functionFolder)) { - fs.mkdirSync(functionFolder, { - recursive: true - }); - } - - const functionId = answers.id === 'unique()' ? ID.unique() : answers.id; - const functionDir = path.join(functionFolder, functionId); - - if (fs.existsSync(functionDir)) { - throw new Error(`( ${functionId} ) already exists in the current directory. Please choose another name.`); - } - - if (!answers.runtime.entrypoint) { - log(`Entrypoint for this runtime not found. You will be asked to configure entrypoint when you first push the function.`); - } - - if (!answers.runtime.commands) { - log(`Installation command for this runtime not found. You will be asked to configure the install command when you first push the function.`); - } - - let response = await functionsCreate({ - functionId, - name: answers.name, - runtime: answers.runtime.id, - entrypoint: answers.runtime.entrypoint || '', - commands: answers.runtime.commands || '', - parseOutput: false - }) - - fs.mkdirSync(functionDir, "777"); - - let gitInitCommands = "git clone -b v3 --single-branch --depth 1 --sparse https://github.com/{{ sdk.gitUserName }}/functions-starter ."; // depth prevents fetching older commits reducing the amount fetched - - let gitPullCommands = `git sparse-checkout add ${answers.runtime.id}`; - - /* Force use CMD as powershell does not support && */ - if (process.platform === 'win32') { - gitInitCommands = 'cmd /c "' + gitInitCommands + '"'; - gitPullCommands = 'cmd /c "' + gitPullCommands + '"'; - } - - /* Execute the child process but do not print any std output */ - try { - childProcess.execSync(gitInitCommands, { stdio: 'pipe', cwd: functionDir }); - childProcess.execSync(gitPullCommands, { stdio: 'pipe', cwd: functionDir }); - } catch (error) { - /* Specialised errors with recommended actions to take */ - if (error.message.includes('error: unknown option')) { - throw new Error(`${error.message} \n\nSuggestion: Try updating your git to the latest version, then trying to run this command again.`) - } else if (error.message.includes('is not recognized as an internal or external command,') || error.message.includes('command not found')) { - throw new Error(`${error.message} \n\nSuggestion: It appears that git is not installed, try installing git then trying to run this command again.`) - } else { - throw error; - } - } - - fs.rmSync(path.join(functionDir, ".git"), { recursive: true }); - const copyRecursiveSync = (src, dest) => { - let exists = fs.existsSync(src); - let stats = exists && fs.statSync(src); - let isDirectory = exists && stats.isDirectory(); - if (isDirectory) { - if (!fs.existsSync(dest)) { - fs.mkdirSync(dest); - } - - fs.readdirSync(src).forEach(function (childItemName) { - copyRecursiveSync(path.join(src, childItemName), path.join(dest, childItemName)); - }); - } else { - fs.copyFileSync(src, dest); - } - }; - copyRecursiveSync(path.join(functionDir, answers.runtime.id), functionDir); - - fs.rmSync(`${functionDir}/${answers.runtime.id}`, { recursive: true, force: true }); - - const readmePath = path.join(process.cwd(), 'functions', functionId, 'README.md'); - const readmeFile = fs.readFileSync(readmePath).toString(); - const newReadmeFile = readmeFile.split('\n'); - newReadmeFile[0] = `# ${answers.name}`; - newReadmeFile.splice(1, 2); - fs.writeFileSync(readmePath, newReadmeFile.join('\n')); - - let data = { - $id: response['$id'], - name: response.name, - runtime: response.runtime, - execute: response.execute, - events: response.events, - schedule: response.schedule, - timeout: response.timeout, - enabled: response.enabled, - logging: response.logging, - entrypoint: response.entrypoint, - commands: response.commands, - ignore: answers.runtime.ignore || null, - path: `functions/${functionId}`, - }; - - localConfig.addFunction(data); - success(); -} - - init .command("project") .description("Init and create a new {{ spec.title|caseUcfirst }} project") .action(actionRunner(initProject)); -create - .command("function") - .description("Create a new {{ spec.title|caseUcfirst }} function") - .action(actionRunner(createFunction)); - -create - .command("bucket") - .description("Create a new {{ spec.title|caseUcfirst }} bucket") - .action(actionRunner(createBucket)); - -create - .command("collection") - .description("Create a new {{ spec.title|caseUcfirst }} collection") - .action(actionRunner(createCollection)); - -create - .command("topic") - .description("Create a new {{ spec.title|caseUcfirst }} topic") - .action(actionRunner(createTopic)); - module.exports = { init, }; diff --git a/templates/cli/lib/questions.js.twig b/templates/cli/lib/questions.js.twig index 63f3a93ed..f407c382b 100644 --- a/templates/cli/lib/questions.js.twig +++ b/templates/cli/lib/questions.js.twig @@ -167,6 +167,7 @@ const questionsCreateProject = [ when: whenOverride } ]; + const questionsPullProject = [ ...questionsProject, { @@ -316,37 +317,6 @@ const questionsCreateMessagingTopic = [ ]; -const questionsPullProject = [ - ...questionsProject, - { - type: "list", - name: "project", - message: "Choose your {{ spec.title|caseUcfirst }} project.", - choices: async (answers) => { - let response = await projectsList({ - parseOutput: false, - queries: [JSON.stringify({ method: 'equal', attribute: 'teamId', values: [answers.organization.id] })], - }) - let projects = response["projects"] - let choices = projects.map((project, idx) => { - return { - name: `${project.name} (${project['$id']})`, - value: { - name: project.name, - id: project['$id'] - } - } - }) - - if (choices.length == 0) { - throw new Error("No projects found. Please create a new project.") - } - - return choices; - } - } -]; - const questionsPullCollection = [ { type: "checkbox", From 283a0631d8182dd35ddf784d079bf1926590470d Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Wed, 22 May 2024 08:59:46 -0400 Subject: [PATCH 16/26] chore(cli): rebasing head --- templates/cli/lib/questions.js.twig | 32 ----------------------------- 1 file changed, 32 deletions(-) diff --git a/templates/cli/lib/questions.js.twig b/templates/cli/lib/questions.js.twig index 30c9594c8..17b070598 100644 --- a/templates/cli/lib/questions.js.twig +++ b/templates/cli/lib/questions.js.twig @@ -339,38 +339,6 @@ const questionsCreateMessagingTopic = [ } ]; - -const questionsPullProject = [ - ...questionsProject, - { - type: "list", - name: "project", - message: "Choose your {{ spec.title|caseUcfirst }} project.", - choices: async (answers) => { - let response = await projectsList({ - parseOutput: false, - queries: [JSON.stringify({ method: 'equal', attribute: 'teamId', values: [answers.organization.id] })], - }) - let projects = response["projects"] - let choices = projects.map((project, idx) => { - return { - name: `${project.name} (${project['$id']})`, - value: { - name: project.name, - id: project['$id'] - } - } - }) - - if (choices.length == 0) { - throw new Error("No projects found. Please create a new project.") - } - - return choices; - } - } -]; - const questionsPullCollection = [ { type: "checkbox", From f8926736a4318a6a355ecadd61749bbcbbc6db19 Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Wed, 22 May 2024 10:00:50 -0400 Subject: [PATCH 17/26] refactor(cli): Omitting the `project` sub-command --- templates/cli/lib/commands/init.js.twig | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/templates/cli/lib/commands/init.js.twig b/templates/cli/lib/commands/init.js.twig index e163c0363..c42f7aff0 100644 --- a/templates/cli/lib/commands/init.js.twig +++ b/templates/cli/lib/commands/init.js.twig @@ -8,15 +8,6 @@ const { success, error, actionRunner, commandDescriptions } = require("../parser const { accountGet } = require("./account"); const { loginCommand } = require("./generic"); -const init = new Command("init") - .description(commandDescriptions['init']) - .configureHelp({ - helpWidth: process.stdout.columns || 80 - }) - .action(actionRunner(async (_options, command) => { - command.help(); - })); - const initProject = async () => { let response = {}; @@ -46,9 +37,11 @@ const initProject = async () => { success(); } -init - .command("project") - .description("Init and create a new {{ spec.title|caseUcfirst }} project") +const init = new Command("init") + .description('Init and create a new {{ spec.title|caseUcfirst }} project') + .configureHelp({ + helpWidth: process.stdout.columns || 80 + }) .action(actionRunner(initProject)); module.exports = { From 848e222d7a6b1e6e2b9c5f8d9994161d0d32ada0 Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Wed, 22 May 2024 12:20:48 -0400 Subject: [PATCH 18/26] refactor(cli): Changing error to info --- templates/cli/lib/commands/init.js.twig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/cli/lib/commands/init.js.twig b/templates/cli/lib/commands/init.js.twig index c42f7aff0..5af7a4c61 100644 --- a/templates/cli/lib/commands/init.js.twig +++ b/templates/cli/lib/commands/init.js.twig @@ -4,7 +4,7 @@ const { projectsCreate } = require("./projects"); const { sdkForConsole } = require("../sdks"); const { localConfig, globalConfig } = require("../config"); const { questionsCreateProject } = require("../questions"); -const { success, error, actionRunner, commandDescriptions } = require("../parser"); +const { success, log, actionRunner, commandDescriptions } = require("../parser"); const { accountGet } = require("./account"); const { loginCommand } = require("./generic"); @@ -19,7 +19,7 @@ const initProject = async () => { parseOutput: false }); } catch (e) { - error('You must login first') + log('You must login first') await loginCommand() } From a48c3e28496c35406f848172f38d0d7ff7771a4f Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Thu, 23 May 2024 12:11:20 -0400 Subject: [PATCH 19/26] refactor(cli): Grouping init functionality Project init will do both, create and choose an existing one. --- src/SDK/Language/CLI.php | 5 - templates/cli/index.js.twig | 2 - templates/cli/lib/commands/create.js.twig | 224 --------------------- templates/cli/lib/commands/init.js.twig | 234 +++++++++++++++++++++- templates/cli/lib/parser.js.twig | 3 +- templates/cli/lib/questions.js.twig | 42 ++-- 6 files changed, 251 insertions(+), 259 deletions(-) delete mode 100644 templates/cli/lib/commands/create.js.twig diff --git a/src/SDK/Language/CLI.php b/src/SDK/Language/CLI.php index 5b36eea70..9f91cccf3 100644 --- a/src/SDK/Language/CLI.php +++ b/src/SDK/Language/CLI.php @@ -177,11 +177,6 @@ public function getFiles(): array 'destination' => 'lib/commands/init.js', 'template' => 'cli/lib/commands/init.js.twig', ], - [ - 'scope' => 'default', - 'destination' => 'lib/commands/create.js', - 'template' => 'cli/lib/commands/create.js.twig', - ], [ 'scope' => 'default', 'destination' => 'lib/commands/pull.js', diff --git a/templates/cli/index.js.twig b/templates/cli/index.js.twig index 3798dedc6..87dd70298 100644 --- a/templates/cli/index.js.twig +++ b/templates/cli/index.js.twig @@ -12,7 +12,6 @@ const { commandDescriptions, cliConfig } = require("./lib/parser"); const { client } = require("./lib/commands/generic"); {% if sdk.test != "true" %} const { login, logout } = require("./lib/commands/generic"); -const { create } = require("./lib/commands/create"); const { init } = require("./lib/commands/init"); const { pull } = require("./lib/commands/pull"); const { push } = require("./lib/commands/push"); @@ -39,7 +38,6 @@ program .showSuggestionAfterError() {% if sdk.test != "true" %} .addCommand(login) - .addCommand(create) .addCommand(init) .addCommand(pull) .addCommand(push) diff --git a/templates/cli/lib/commands/create.js.twig b/templates/cli/lib/commands/create.js.twig deleted file mode 100644 index 852116822..000000000 --- a/templates/cli/lib/commands/create.js.twig +++ /dev/null @@ -1,224 +0,0 @@ -const fs = require("fs"); -const path = require("path"); -const childProcess = require('child_process'); -const { Command } = require("commander"); -const inquirer = require("inquirer"); -const { storageCreateBucket } = require("./storage"); -const { messagingCreateTopic } = require("./messaging"); -const { functionsCreate } = require("./functions"); -const { databasesCreateCollection } = require("./databases"); -const ID = require("../id"); -const { localConfig } = require("../config"); -const { - questionsCreateFunction, - questionsCreateBucket, - questionsCreateMessagingTopic, - questionsCreateCollection -} = require("../questions"); -const { success, error, actionRunner, commandDescriptions } = require("../parser"); - -const create = new Command("create") - .description(commandDescriptions['create']) - .configureHelp({ - helpWidth: process.stdout.columns || 80 - }) - .action(actionRunner(async (_options, command) => { - command.help(); - })); - - -const createBucket = async () => { - let response = {} - const answers = await inquirer.prompt(questionsCreateBucket) - if (!answers.bucket || !answers.id || !answers.fileSecurity) process.exit(1) - - try { - response = await storageCreateBucket({ - bucketId: answers.id, - name: answers.bucket, - fileSecurity: answers.fileSecurity.toLowerCase() === 'yes', - enabled: true, - parseOutput: false - }) - - localConfig.addBucket(response); - success(); - } catch (e) { - error(e.getMessage ?? 'Unknown error occurred. Please try again'); - } -}; - -const createCollection = async () => { - let response = {} - const answers = await inquirer.prompt(questionsCreateCollection) - if (!answers.database || !answers.collection || !answers.id || !answers.documentSecurity) process.exit(1) - - try { - response = await databasesCreateCollection({ - databaseId: answers.database, - collectionId: answers.id, - name: answers.collection, - documentSecurity: answers.documentSecurity.toLowerCase() === 'yes', - enabled: true, - parseOutput: false - }) - - localConfig.addCollection(response); - success(); - } catch (e) { - error(e.getMessage ?? 'Unknown error occurred. Please try again'); - } -}; - -const createTopic = async () => { - let response = {} - const answers = await inquirer.prompt(questionsCreateMessagingTopic) - if (!answers.topic || !answers.id) process.exit(1) - - try { - response = await messagingCreateTopic({ - topicId: answers.id, - name: answers.topic, - parseOutput: false - }) - - localConfig.addMessagingTopic(response); - success(); - } catch (e) { - error(e.getMessage ?? 'Unknown error occurred. Please try again'); - } -}; - -const createFunction = async () => { - // TODO: Add CI/CD support (ID, name, runtime) - const answers = await inquirer.prompt(questionsCreateFunction) - const functionFolder = path.join(process.cwd(), 'functions'); - - if (!fs.existsSync(functionFolder)) { - fs.mkdirSync(functionFolder, { - recursive: true - }); - } - - const functionId = answers.id === 'unique()' ? ID.unique() : answers.id; - const functionDir = path.join(functionFolder, functionId); - - if (fs.existsSync(functionDir)) { - throw new Error(`( ${functionId} ) already exists in the current directory. Please choose another name.`); - } - - if (!answers.runtime.entrypoint) { - log(`Entrypoint for this runtime not found. You will be asked to configure entrypoint when you first push the function.`); - } - - if (!answers.runtime.commands) { - log(`Installation command for this runtime not found. You will be asked to configure the install command when you first push the function.`); - } - - let response = await functionsCreate({ - functionId, - name: answers.name, - runtime: answers.runtime.id, - entrypoint: answers.runtime.entrypoint || '', - commands: answers.runtime.commands || '', - parseOutput: false - }) - - fs.mkdirSync(functionDir, "777"); - - let gitInitCommands = "git clone -b v3 --single-branch --depth 1 --sparse https://github.com/{{ sdk.gitUserName }}/functions-starter ."; // depth prevents fetching older commits reducing the amount fetched - - let gitPullCommands = `git sparse-checkout add ${answers.runtime.id}`; - - /* Force use CMD as powershell does not support && */ - if (process.platform === 'win32') { - gitInitCommands = 'cmd /c "' + gitInitCommands + '"'; - gitPullCommands = 'cmd /c "' + gitPullCommands + '"'; - } - - /* Execute the child process but do not print any std output */ - try { - childProcess.execSync(gitInitCommands, { stdio: 'pipe', cwd: functionDir }); - childProcess.execSync(gitPullCommands, { stdio: 'pipe', cwd: functionDir }); - } catch (error) { - /* Specialised errors with recommended actions to take */ - if (error.message.includes('error: unknown option')) { - throw new Error(`${error.message} \n\nSuggestion: Try updating your git to the latest version, then trying to run this command again.`) - } else if (error.message.includes('is not recognized as an internal or external command,') || error.message.includes('command not found')) { - throw new Error(`${error.message} \n\nSuggestion: It appears that git is not installed, try installing git then trying to run this command again.`) - } else { - throw error; - } - } - - fs.rmSync(path.join(functionDir, ".git"), { recursive: true }); - const copyRecursiveSync = (src, dest) => { - let exists = fs.existsSync(src); - let stats = exists && fs.statSync(src); - let isDirectory = exists && stats.isDirectory(); - if (isDirectory) { - if (!fs.existsSync(dest)) { - fs.mkdirSync(dest); - } - - fs.readdirSync(src).forEach(function (childItemName) { - copyRecursiveSync(path.join(src, childItemName), path.join(dest, childItemName)); - }); - } else { - fs.copyFileSync(src, dest); - } - }; - copyRecursiveSync(path.join(functionDir, answers.runtime.id), functionDir); - - fs.rmSync(`${functionDir}/${answers.runtime.id}`, { recursive: true, force: true }); - - const readmePath = path.join(process.cwd(), 'functions', functionId, 'README.md'); - const readmeFile = fs.readFileSync(readmePath).toString(); - const newReadmeFile = readmeFile.split('\n'); - newReadmeFile[0] = `# ${answers.name}`; - newReadmeFile.splice(1, 2); - fs.writeFileSync(readmePath, newReadmeFile.join('\n')); - - let data = { - $id: response['$id'], - name: response.name, - runtime: response.runtime, - execute: response.execute, - events: response.events, - schedule: response.schedule, - timeout: response.timeout, - enabled: response.enabled, - logging: response.logging, - entrypoint: response.entrypoint, - commands: response.commands, - ignore: answers.runtime.ignore || null, - path: `functions/${functionId}`, - }; - - localConfig.addFunction(data); - success(); -} - -create - .command("function") - .description("Create a new {{ spec.title|caseUcfirst }} function") - .action(actionRunner(createFunction)); - -create - .command("bucket") - .description("Create a new {{ spec.title|caseUcfirst }} bucket") - .action(actionRunner(createBucket)); - -create - .command("collection") - .description("Create a new {{ spec.title|caseUcfirst }} collection") - .action(actionRunner(createCollection)); - -create - .command("topic") - .description("Create a new {{ spec.title|caseUcfirst }} topic") - .action(actionRunner(createTopic)); - -module.exports = { - create, -}; diff --git a/templates/cli/lib/commands/init.js.twig b/templates/cli/lib/commands/init.js.twig index 5af7a4c61..5cc9e8723 100644 --- a/templates/cli/lib/commands/init.js.twig +++ b/templates/cli/lib/commands/init.js.twig @@ -1,10 +1,23 @@ +const fs = require("fs"); +const path = require("path"); +const childProcess = require('child_process'); const { Command } = require("commander"); const inquirer = require("inquirer"); const { projectsCreate } = require("./projects"); -const { sdkForConsole } = require("../sdks"); +const { storageCreateBucket } = require("./storage"); +const { messagingCreateTopic } = require("./messaging"); +const { functionsCreate } = require("./functions"); +const { databasesCreateCollection } = require("./databases"); +const ID = require("../id"); const { localConfig, globalConfig } = require("../config"); -const { questionsCreateProject } = require("../questions"); -const { success, log, actionRunner, commandDescriptions } = require("../parser"); +const { + questionsCreateFunction, + questionsCreateBucket, + questionsCreateMessagingTopic, + questionsCreateCollection, + questionsInitProject +} = require("../questions"); +const { success, log, error, actionRunner, commandDescriptions } = require("../parser"); const { accountGet } = require("./account"); const { loginCommand } = require("./generic"); @@ -23,27 +36,226 @@ const initProject = async () => { await loginCommand() } - const answers = await inquirer.prompt(questionsCreateProject) - if (!answers.project || !answers.organization) process.exit(1) + const answers = await inquirer.prompt(questionsInitProject) + if (!answers.project || !answers.organization) { + process.exit(1) + } + + if (answers.start === 'new') { + response = await projectsCreate({ + projectId: answers.id, + name: answers.project, + teamId: answers.organization.id, + parseOutput: false + }) + + localConfig.setProject(response['$id'], response.name); + } else { + localConfig.setProject(answers.project.id, answers.project.name); + } + + success(); +} + +const initBucket = async () => { + let response = {} + const answers = await inquirer.prompt(questionsCreateBucket) + if (!answers.bucket || !answers.id || !answers.fileSecurity) process.exit(1) + + try { + response = await storageCreateBucket({ + bucketId: answers.id, + name: answers.bucket, + fileSecurity: answers.fileSecurity.toLowerCase() === 'yes', + enabled: true, + parseOutput: false + }) + + localConfig.addBucket(response); + success(); + } catch (e) { + error(e.getMessage ?? 'Unknown error occurred. Please try again'); + } +}; + +const initCollection = async () => { + let response = {} + const answers = await inquirer.prompt(questionsCreateCollection) + if (!answers.database || !answers.collection || !answers.id || !answers.documentSecurity) process.exit(1) + + try { + response = await databasesCreateCollection({ + databaseId: answers.database, + collectionId: answers.id, + name: answers.collection, + documentSecurity: answers.documentSecurity.toLowerCase() === 'yes', + enabled: true, + parseOutput: false + }) + + localConfig.addCollection(response); + success(); + } catch (e) { + error(e.getMessage ?? 'Unknown error occurred. Please try again'); + } +}; + +const initTopic = async () => { + let response = {} + const answers = await inquirer.prompt(questionsCreateMessagingTopic) + if (!answers.topic || !answers.id) process.exit(1) + + try { + response = await messagingCreateTopic({ + topicId: answers.id, + name: answers.topic, + parseOutput: false + }) + + localConfig.addMessagingTopic(response); + success(); + } catch (e) { + error(e.getMessage ?? 'Unknown error occurred. Please try again'); + } +}; + +const initFunction = async () => { + // TODO: Add CI/CD support (ID, name, runtime) + const answers = await inquirer.prompt(questionsCreateFunction) + const functionFolder = path.join(process.cwd(), 'functions'); + + if (!fs.existsSync(functionFolder)) { + fs.mkdirSync(functionFolder, { + recursive: true + }); + } + + const functionId = answers.id === 'unique()' ? ID.unique() : answers.id; + const functionDir = path.join(functionFolder, functionId); + + if (fs.existsSync(functionDir)) { + throw new Error(`( ${functionId} ) already exists in the current directory. Please choose another name.`); + } - response = await projectsCreate({ - projectId: answers.id, - name: answers.project, - teamId: answers.organization.id, + if (!answers.runtime.entrypoint) { + log(`Entrypoint for this runtime not found. You will be asked to configure entrypoint when you first push the function.`); + } + + if (!answers.runtime.commands) { + log(`Installation command for this runtime not found. You will be asked to configure the install command when you first push the function.`); + } + + let response = await functionsCreate({ + functionId, + name: answers.name, + runtime: answers.runtime.id, + entrypoint: answers.runtime.entrypoint || '', + commands: answers.runtime.commands || '', parseOutput: false }) - localConfig.setProject(response['$id'], response.name); + fs.mkdirSync(functionDir, "777"); + + let gitInitCommands = "git clone -b v3 --single-branch --depth 1 --sparse https://github.com/{{ sdk.gitUserName }}/functions-starter ."; // depth prevents fetching older commits reducing the amount fetched + + let gitPullCommands = `git sparse-checkout add ${answers.runtime.id}`; + + /* Force use CMD as powershell does not support && */ + if (process.platform === 'win32') { + gitInitCommands = 'cmd /c "' + gitInitCommands + '"'; + gitPullCommands = 'cmd /c "' + gitPullCommands + '"'; + } + + /* Execute the child process but do not print any std output */ + try { + childProcess.execSync(gitInitCommands, { stdio: 'pipe', cwd: functionDir }); + childProcess.execSync(gitPullCommands, { stdio: 'pipe', cwd: functionDir }); + } catch (error) { + /* Specialised errors with recommended actions to take */ + if (error.message.includes('error: unknown option')) { + throw new Error(`${error.message} \n\nSuggestion: Try updating your git to the latest version, then trying to run this command again.`) + } else if (error.message.includes('is not recognized as an internal or external command,') || error.message.includes('command not found')) { + throw new Error(`${error.message} \n\nSuggestion: It appears that git is not installed, try installing git then trying to run this command again.`) + } else { + throw error; + } + } + + fs.rmSync(path.join(functionDir, ".git"), { recursive: true }); + const copyRecursiveSync = (src, dest) => { + let exists = fs.existsSync(src); + let stats = exists && fs.statSync(src); + let isDirectory = exists && stats.isDirectory(); + if (isDirectory) { + if (!fs.existsSync(dest)) { + fs.mkdirSync(dest); + } + + fs.readdirSync(src).forEach(function (childItemName) { + copyRecursiveSync(path.join(src, childItemName), path.join(dest, childItemName)); + }); + } else { + fs.copyFileSync(src, dest); + } + }; + copyRecursiveSync(path.join(functionDir, answers.runtime.id), functionDir); + + fs.rmSync(`${functionDir}/${answers.runtime.id}`, { recursive: true, force: true }); + + const readmePath = path.join(process.cwd(), 'functions', functionId, 'README.md'); + const readmeFile = fs.readFileSync(readmePath).toString(); + const newReadmeFile = readmeFile.split('\n'); + newReadmeFile[0] = `# ${answers.name}`; + newReadmeFile.splice(1, 2); + fs.writeFileSync(readmePath, newReadmeFile.join('\n')); + + let data = { + $id: response['$id'], + name: response.name, + runtime: response.runtime, + execute: response.execute, + events: response.events, + schedule: response.schedule, + timeout: response.timeout, + enabled: response.enabled, + logging: response.logging, + entrypoint: response.entrypoint, + commands: response.commands, + ignore: answers.runtime.ignore || null, + path: `functions/${functionId}`, + }; + + localConfig.addFunction(data); success(); } const init = new Command("init") - .description('Init and create a new {{ spec.title|caseUcfirst }} project') + .description('Init an {{ spec.title|caseUcfirst }} project') .configureHelp({ helpWidth: process.stdout.columns || 80 }) .action(actionRunner(initProject)); +init + .command("function") + .description("Init a new {{ spec.title|caseUcfirst }} function") + .action(actionRunner(initFunction)); + +init + .command("bucket") + .description("Init a new {{ spec.title|caseUcfirst }} bucket") + .action(actionRunner(initBucket)); + +init + .command("collection") + .description("Init a new {{ spec.title|caseUcfirst }} collection") + .action(actionRunner(initCollection)); + +init + .command("topic") + .description("Init a new {{ spec.title|caseUcfirst }} topic") + .action(actionRunner(initTopic)); + module.exports = { init, }; diff --git a/templates/cli/lib/parser.js.twig b/templates/cli/lib/parser.js.twig index cc380e789..7b6e1b22f 100644 --- a/templates/cli/lib/parser.js.twig +++ b/templates/cli/lib/parser.js.twig @@ -156,8 +156,7 @@ const commandDescriptions = { "graphql": `The graphql command allows you to query and mutate any resource type on your Appwrite server.`, "avatars": `The avatars command aims to help you complete everyday tasks related to your app image, icons, and avatars.`, "databases": `The databases command allows you to create structured collections of documents, query and filter lists of documents.`, - "init": `The init command provides a convenient wrapper for creating and initializing project in Appwrite.`, - "create": `The create command provides a convenient wrapper for creating functions, collections, buckets, teams and messaging.`, + "init": `The init command provides a convenient wrapper for creating and initializing project, functions, collections, buckets, teams and messaging in Appwrite.`, "push": `The push command provides a convenient wrapper for pushing your functions, collections, buckets, teams and messaging.`, "functions": `The functions command allows you view, create and manage your Cloud Functions.`, "health": `The health command allows you to both validate and monitor your {{ spec.title|caseUcfirst }} server's health.`, diff --git a/templates/cli/lib/questions.js.twig b/templates/cli/lib/questions.js.twig index 17b070598..96806b2c3 100644 --- a/templates/cli/lib/questions.js.twig +++ b/templates/cli/lib/questions.js.twig @@ -112,7 +112,7 @@ const getInstallCommand = (runtime) => { return undefined; }; -const questionsProject = [ +const questionsInitProject = [ { type: "confirm", name: "override", @@ -148,28 +148,41 @@ const questionsProject = [ }, when: whenOverride }, -]; - -const questionsCreateProject = [ - ...questionsProject, + { + type: "list", + name: "start", + when(answers) { + if (answers.override == undefined) { + return true + } + return answers.override; + }, + message: "How would you like to start?", + choices: [ + { + name: "Create a new {{ spec.title|caseUcfirst }} project", + value: "new" + }, + { + name: "Link this directory to an existing {{ spec.title|caseUcfirst }} project", + value: "existing" + } + ] + }, { type: "input", name: "project", message: "What would you like to name your project?", default: "My Awesome Project", - when: whenOverride + when: (answer) => answer.start === 'new' }, { type: "input", name: "id", message: "What ID would you like to have for your project?", default: "unique()", - when: whenOverride - } -]; - -const questionsPullProject = [ - ...questionsProject, + when: (answer) => answer.start === 'new' + }, { type: "list", name: "project", @@ -196,7 +209,7 @@ const questionsPullProject = [ return choices; }, - when: whenOverride + when: (answer) => answer.start === 'existing' } ]; @@ -601,12 +614,11 @@ const questionsMfaChallenge = [ ]; module.exports = { - questionsCreateProject, + questionsInitProject, questionsCreateFunction, questionsCreateBucket, questionsCreateCollection, questionsCreateMessagingTopic, - questionsPullProject, questionsPullFunctions, questionsLogin, questionsPullCollection, From 2bb8da35644e60c0aa2bead063f947e8589fd16e Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Thu, 23 May 2024 12:19:14 -0400 Subject: [PATCH 20/26] refactor(cli): Project pull to update project name --- templates/cli/lib/commands/pull.js.twig | 27 ++++++++++++++++--------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/templates/cli/lib/commands/pull.js.twig b/templates/cli/lib/commands/pull.js.twig index dd79410ba..885393967 100644 --- a/templates/cli/lib/commands/pull.js.twig +++ b/templates/cli/lib/commands/pull.js.twig @@ -2,16 +2,15 @@ const fs = require("fs"); const tar = require("tar"); const { Command } = require("commander"); const inquirer = require("inquirer"); -const { messagingCreateTopic, messagingListTopics } = require("./messaging"); -const { teamsCreate, teamsList } = require("./teams"); -const { projectsCreate } = require("./projects"); +const { messagingListTopics } = require("./messaging"); +const { teamsList } = require("./teams"); +const { projectsList } = require("./projects"); const { functionsList, functionsDownloadDeployment } = require("./functions"); const { databasesGet, databasesListCollections, databasesList } = require("./databases"); const { storageListBuckets } = require("./storage"); -const { sdkForConsole } = require("../sdks"); const { localConfig } = require("../config"); const { paginate } = require("../paginate"); -const { questionsPullProject, questionsPullCollection, questionsPullFunctions } = require("../questions"); +const { questionsPullCollection, questionsPullFunctions } = require("../questions"); const { success, log, actionRunner, commandDescriptions } = require("../parser"); const pull = new Command("pull") @@ -24,11 +23,19 @@ const pull = new Command("pull") })); const pullProject = async () => { - const answers = await inquirer.prompt(questionsPullProject) - if (!answers.project) process.exit(1) + try { + let response = await projectsList({ + parseOutput: false, + queries: [JSON.stringify({ method: 'equal', attribute: '$id', values: [localConfig.getProject().projectId] })] - localConfig.setProject(answers.project.id, answers.project.name); - success(); + }) + if(response.total === 1){ + localConfig.setProject(response.projects[0].$id, response.projects[0].name); + } + success(); + } catch (e) { + throw e; + } } const pullFunctions = async ({ all, yes } = {}) => { @@ -178,7 +185,7 @@ const pullMessagingTopic = async () => { pull .command("project") - .description("Pulling your Appwrite project") + .description("Pulling your Appwrite project name") .action(actionRunner(pullProject)); pull From cd505f33f36957ad4999f29339d19c000b9d45ff Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Thu, 23 May 2024 12:25:37 -0400 Subject: [PATCH 21/26] refactor(cli): Project push to update remote project name --- templates/cli/lib/commands/push.js.twig | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/templates/cli/lib/commands/push.js.twig b/templates/cli/lib/commands/push.js.twig index 8e269cdd4..c9b5b099e 100644 --- a/templates/cli/lib/commands/push.js.twig +++ b/templates/cli/lib/commands/push.js.twig @@ -41,6 +41,7 @@ const { teamsUpdate, teamsCreate } = require("./teams"); +const { projectsUpdate } = require("./projects"); const STEP_SIZE = 100; // Resources const POLL_DEBOUNCE = 2000; // Milliseconds @@ -254,6 +255,19 @@ const pushResources = async ({ all, yes } = {}) => { } }; +const pushProject = async () => { + try { + await projectsUpdate({ + projectId: localConfig.getProject().projectId, + name: localConfig.getProject().projectName, + parseOutput: false + }) + success(); + } catch (e) { + throw e; + } +} + const pushFunction = async ({ functionId, all, yes, async } = {}) => { let response = {}; @@ -1106,6 +1120,11 @@ const push = new Command("push") .option(`--yes`, `Flag to confirm all warnings`) .action(actionRunner(pushResources)); +push + .command("project") + .description("Push project name.") + .action(actionRunner(pushProject)); + push .command("function") .description("Push functions in the current directory.") From bce40e8ba28fda8f6231de71dba89a6189b63c42 Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Fri, 24 May 2024 12:38:49 -0400 Subject: [PATCH 22/26] feat(cli): Pulling function without questions, and review refactoring --- templates/cli/index.js.twig | 1 + templates/cli/lib/commands/init.js.twig | 15 ++++---- templates/cli/lib/commands/pull.js.twig | 50 +++++++++---------------- templates/cli/lib/questions.js.twig | 11 +++--- 4 files changed, 33 insertions(+), 44 deletions(-) diff --git a/templates/cli/index.js.twig b/templates/cli/index.js.twig index 220286bd3..02bacb99e 100644 --- a/templates/cli/index.js.twig +++ b/templates/cli/index.js.twig @@ -12,6 +12,7 @@ const { commandDescriptions, cliConfig } = require("./lib/parser"); const { client } = require("./lib/commands/generic"); {% if sdk.test != "true" %} const { login, logout, whoami } = require("./lib/commands/generic"); +const { init } = require("./lib/commands/init"); const { pull } = require("./lib/commands/pull"); const { push } = require("./lib/commands/push"); {% endif %} diff --git a/templates/cli/lib/commands/init.js.twig b/templates/cli/lib/commands/init.js.twig index 5cc9e8723..e0711ee03 100644 --- a/templates/cli/lib/commands/init.js.twig +++ b/templates/cli/lib/commands/init.js.twig @@ -20,6 +20,7 @@ const { const { success, log, error, actionRunner, commandDescriptions } = require("../parser"); const { accountGet } = require("./account"); const { loginCommand } = require("./generic"); +const { sdkForConsole } = require("../sdks"); const initProject = async () => { let response = {}; @@ -28,16 +29,19 @@ const initProject = async () => { if (globalConfig.getEndpoint() === '' || globalConfig.getCookie() === '') { throw ''; } + const client = await sdkForConsole(); + await accountGet({ - parseOutput: false + parseOutput: false, + sdk: client }); } catch (e) { log('You must login first') - await loginCommand() + await loginCommand(); } const answers = await inquirer.prompt(questionsInitProject) - if (!answers.project || !answers.organization) { + if (answers.override === false) { process.exit(1) } @@ -60,7 +64,6 @@ const initProject = async () => { const initBucket = async () => { let response = {} const answers = await inquirer.prompt(questionsCreateBucket) - if (!answers.bucket || !answers.id || !answers.fileSecurity) process.exit(1) try { response = await storageCreateBucket({ @@ -81,7 +84,6 @@ const initBucket = async () => { const initCollection = async () => { let response = {} const answers = await inquirer.prompt(questionsCreateCollection) - if (!answers.database || !answers.collection || !answers.id || !answers.documentSecurity) process.exit(1) try { response = await databasesCreateCollection({ @@ -103,7 +105,6 @@ const initCollection = async () => { const initTopic = async () => { let response = {} const answers = await inquirer.prompt(questionsCreateMessagingTopic) - if (!answers.topic || !answers.id) process.exit(1) try { response = await messagingCreateTopic({ @@ -115,7 +116,7 @@ const initTopic = async () => { localConfig.addMessagingTopic(response); success(); } catch (e) { - error(e.getMessage ?? 'Unknown error occurred. Please try again'); + error(e.message ?? 'Unknown error occurred. Please try again'); } }; diff --git a/templates/cli/lib/commands/pull.js.twig b/templates/cli/lib/commands/pull.js.twig index 885393967..45e517a42 100644 --- a/templates/cli/lib/commands/pull.js.twig +++ b/templates/cli/lib/commands/pull.js.twig @@ -4,7 +4,7 @@ const { Command } = require("commander"); const inquirer = require("inquirer"); const { messagingListTopics } = require("./messaging"); const { teamsList } = require("./teams"); -const { projectsList } = require("./projects"); +const { projectsGet } = require("./projects"); const { functionsList, functionsDownloadDeployment } = require("./functions"); const { databasesGet, databasesListCollections, databasesList } = require("./databases"); const { storageListBuckets } = require("./storage"); @@ -24,43 +24,31 @@ const pull = new Command("pull") const pullProject = async () => { try { - let response = await projectsList({ + let response = await projectsGet({ parseOutput: false, - queries: [JSON.stringify({ method: 'equal', attribute: '$id', values: [localConfig.getProject().projectId] })] + projectId: localConfig.getProject().projectId }) - if(response.total === 1){ - localConfig.setProject(response.projects[0].$id, response.projects[0].name); - } + + localConfig.setProject(response.$id, response.name); success(); } catch (e) { throw e; } } -const pullFunctions = async ({ all, yes } = {}) => { - let functions = []; - let questions = questionsPullFunctions; - +const pullFunctions = async ({ all } = {}) => { const localFunctions = localConfig.getFunctions(); - if (all) { - questions = yes ? [] : questionsPullFunctions[1]; - functions = (await paginate(functionsList, { parseOutput: false }, 100, 'functions')).functions; - } - - const answers = await inquirer.prompt(questions); + const functions = all + ? (await paginate(functionsList, { parseOutput: false }, 100, 'functions')).functions + : (await inquirer.prompt(questionsPullFunctions)).functions; - const overridingLocalChanges = yes ?? answers.override.toLowerCase() === "yes"; - const selectedFunctions = functions.length === 0 ? answers.functions : functions; + log(`Pulling ${functions.length} functions`); - for (let func of selectedFunctions) { + for (let func of functions) { const functionExistLocally = localFunctions.find((localFunc) => localFunc['$id'] === func['$id']) !== undefined; - if (!overridingLocalChanges && functionExistLocally) { - log(`Skipping locally found implementation of ${func['name']}`) - continue; - } if (functionExistLocally) { localConfig.updateFunction(func['$id'], func); } else { @@ -74,7 +62,7 @@ const pullFunctions = async ({ all, yes } = {}) => { continue } - const compressedFileName = `${+new Date()}.tar.gz` + const compressedFileName = `${func['$id']}-${+new Date()}.tar.gz` await functionsDownloadDeployment({ functionId: func['$id'], @@ -92,7 +80,7 @@ const pullFunctions = async ({ all, yes } = {}) => { }); fs.rmSync(compressedFileName); - success(`Pulled ${func['name']} code and definition`) + success(`Pulled ${func['name']} code and settings`) } } @@ -111,7 +99,6 @@ const pullCollection = async ({ all, databaseId } = {}) => { if (databaseIds.length <= 0) { let answers = await inquirer.prompt(questionsPullCollection) - if (!answers.databases) process.exit(1) databaseIds.push(...answers.databases); } @@ -185,31 +172,30 @@ const pullMessagingTopic = async () => { pull .command("project") - .description("Pulling your Appwrite project name") + .description("Pulling your {{ spec.title|caseUcfirst }} project name") .action(actionRunner(pullProject)); pull .command("function") - .description(`Pulling your Appwrite functions`) - .option(`--yes`, `Flag to confirm all warnings`) + .description(`Pulling your {{ spec.title|caseUcfirst }} functions`) .option(`--all`, `Flag to pull all functions`) .action(actionRunner(pullFunctions)); pull .command("collection") - .description("Pulling your Appwrite collections") + .description("Pulling your {{ spec.title|caseUcfirst }} collections") .option(`--databaseId `, `Database ID`) .option(`--all`, `Flag to pull all databases`) .action(actionRunner(pullCollection)) pull .command("bucket") - .description("Pulling your Appwrite buckets") + .description("Pulling your {{ spec.title|caseUcfirst }} buckets") .action(actionRunner(pullBucket)) pull .command("team") - .description("Pulling your Appwrite teams") + .description("Pulling your {{ spec.title|caseUcfirst }} teams") .action(actionRunner(pullTeam)) pull diff --git a/templates/cli/lib/questions.js.twig b/templates/cli/lib/questions.js.twig index 96806b2c3..2f93e5956 100644 --- a/templates/cli/lib/questions.js.twig +++ b/templates/cli/lib/questions.js.twig @@ -218,9 +218,14 @@ const questionsPullFunctions = [ type: "checkbox", name: "functions", message: "Which functions would you like to pull?", + validate: (value) => validateRequired('function', value), choices: async () => { const { functions } = await paginate(functionsList, { parseOutput: false }, 100, 'functions'); + if(functions.length === 0){ + throw "We couldn't find any functions in your {{ spec.title|caseUcfirst }} project"; + } + return functions.map(func => { return { name: `${func.name} (${func.$id})`, @@ -228,11 +233,6 @@ const questionsPullFunctions = [ } }); } - }, - { - type: "input", - name: "override", - message: `Are you sure you want to override local functions code and definition? ${chalk.red('All local changes will be lost!')} Type "YES" to confirm.` } ]; @@ -357,6 +357,7 @@ const questionsPullCollection = [ type: "checkbox", name: "databases", message: "From which database would you like to pull collections?", + validate: (value) => validateRequired('collection', value), choices: async () => { let response = await databasesList({ parseOutput: false From 051891b422887df5f0f4e7c3fdf22d87ebeda903 Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Fri, 24 May 2024 15:13:39 -0400 Subject: [PATCH 23/26] chore(cli): Allowing the use of `deploy` like `push` --- templates/cli/lib/commands/push.js.twig | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/cli/lib/commands/push.js.twig b/templates/cli/lib/commands/push.js.twig index c9b5b099e..d6fe7efe4 100644 --- a/templates/cli/lib/commands/push.js.twig +++ b/templates/cli/lib/commands/push.js.twig @@ -1115,6 +1115,7 @@ const pushMessagingTopic = async ({ all, yes } = {}) => { } const push = new Command("push") + .alias('deploy') .description(commandDescriptions['push']) .option(`--all`, `Flag to push all resources`) .option(`--yes`, `Flag to confirm all warnings`) From 768c4ed33753de9bae080e474d017688c0b19763 Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Fri, 24 May 2024 15:24:45 -0400 Subject: [PATCH 24/26] refactor(cli): Changing commands from single to plural when applicable --- templates/cli/lib/commands/pull.js.twig | 25 +++++++++++-------------- templates/cli/lib/commands/push.js.twig | 16 +++++++++------- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/templates/cli/lib/commands/pull.js.twig b/templates/cli/lib/commands/pull.js.twig index 45e517a42..578046c4e 100644 --- a/templates/cli/lib/commands/pull.js.twig +++ b/templates/cli/lib/commands/pull.js.twig @@ -13,15 +13,6 @@ const { paginate } = require("../paginate"); const { questionsPullCollection, questionsPullFunctions } = require("../questions"); const { success, log, actionRunner, commandDescriptions } = require("../parser"); -const pull = new Command("pull") - .description(commandDescriptions['pull']) - .configureHelp({ - helpWidth: process.stdout.columns || 80 - }) - .action(actionRunner(async (_options, command) => { - command.help(); - })); - const pullProject = async () => { try { let response = await projectsGet({ @@ -170,36 +161,42 @@ const pullMessagingTopic = async () => { success(); } +const pull = new Command("pull") + .description(commandDescriptions['pull']) + .configureHelp({ + helpWidth: process.stdout.columns || 80 + }); + pull .command("project") .description("Pulling your {{ spec.title|caseUcfirst }} project name") .action(actionRunner(pullProject)); pull - .command("function") + .command("functions") .description(`Pulling your {{ spec.title|caseUcfirst }} functions`) .option(`--all`, `Flag to pull all functions`) .action(actionRunner(pullFunctions)); pull - .command("collection") + .command("collections") .description("Pulling your {{ spec.title|caseUcfirst }} collections") .option(`--databaseId `, `Database ID`) .option(`--all`, `Flag to pull all databases`) .action(actionRunner(pullCollection)) pull - .command("bucket") + .command("buckets") .description("Pulling your {{ spec.title|caseUcfirst }} buckets") .action(actionRunner(pullBucket)) pull - .command("team") + .command("teams") .description("Pulling your {{ spec.title|caseUcfirst }} teams") .action(actionRunner(pullTeam)) pull - .command("topic") + .command("topics") .description("Initialise your Appwrite messaging topics") .action(actionRunner(pullMessagingTopic)) diff --git a/templates/cli/lib/commands/push.js.twig b/templates/cli/lib/commands/push.js.twig index d6fe7efe4..f94eb935c 100644 --- a/templates/cli/lib/commands/push.js.twig +++ b/templates/cli/lib/commands/push.js.twig @@ -1117,8 +1117,10 @@ const pushMessagingTopic = async ({ all, yes } = {}) => { const push = new Command("push") .alias('deploy') .description(commandDescriptions['push']) - .option(`--all`, `Flag to push all resources`) - .option(`--yes`, `Flag to confirm all warnings`) + +push + .command("all") + .description("Push all resource.") .action(actionRunner(pushResources)); push @@ -1127,7 +1129,7 @@ push .action(actionRunner(pushProject)); push - .command("function") + .command("functions") .description("Push functions in the current directory.") .option(`--functionId `, `Function ID`) .option(`--all`, `Flag to push all functions`) @@ -1136,28 +1138,28 @@ push .action(actionRunner(pushFunction)); push - .command("collection") + .command("collections") .description("Push collections in the current project.") .option(`--all`, `Flag to push all collections`) .option(`--yes`, `Flag to confirm all warnings`) .action(actionRunner(pushCollection)); push - .command("bucket") + .command("buckets") .description("Push buckets in the current project.") .option(`--all`, `Flag to push all buckets`) .option(`--yes`, `Flag to confirm all warnings`) .action(actionRunner(pushBucket)); push - .command("team") + .command("teams") .description("Push teams in the current project.") .option(`--all`, `Flag to push all teams`) .option(`--yes`, `Flag to confirm all warnings`) .action(actionRunner(pushTeam)); push - .command("topic") + .command("topics") .description("Push messaging topics in the current project.") .option(`--all`, `Flag to deploy all topics`) .option(`--yes`, `Flag to confirm all warnings`) From ca5c806affc2caa661df92a6ed8720b776a0e6c3 Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Fri, 24 May 2024 16:43:14 -0400 Subject: [PATCH 25/26] feat(cli): Creating function from all available templates --- templates/cli/lib/commands/init.js.twig | 33 +++++++++++++++++++------ templates/cli/lib/questions.js.twig | 19 ++++++++++++++ 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/templates/cli/lib/commands/init.js.twig b/templates/cli/lib/commands/init.js.twig index e0711ee03..4712f6ea6 100644 --- a/templates/cli/lib/commands/init.js.twig +++ b/templates/cli/lib/commands/init.js.twig @@ -12,6 +12,7 @@ const ID = require("../id"); const { localConfig, globalConfig } = require("../config"); const { questionsCreateFunction, + questionsCreateFunctionSelectTemplate, questionsCreateBucket, questionsCreateMessagingTopic, questionsCreateCollection, @@ -133,6 +134,8 @@ const initFunction = async () => { const functionId = answers.id === 'unique()' ? ID.unique() : answers.id; const functionDir = path.join(functionFolder, functionId); + const templatesDir = path.join(functionFolder, `${functionId}-templates`); + const runtimeDir = path.join(templatesDir, answers.runtime.name); if (fs.existsSync(functionDir)) { throw new Error(`( ${functionId} ) already exists in the current directory. Please choose another name.`); @@ -156,10 +159,12 @@ const initFunction = async () => { }) fs.mkdirSync(functionDir, "777"); + fs.mkdirSync(templatesDir, "777"); - let gitInitCommands = "git clone -b v3 --single-branch --depth 1 --sparse https://github.com/{{ sdk.gitUserName }}/functions-starter ."; // depth prevents fetching older commits reducing the amount fetched + let gitInitCommands = "git clone --single-branch --depth 1 --sparse https://github.com/{{ sdk.gitUserName }}/templates ."; // depth prevents fetching older commits reducing the amount fetched + + let gitPullCommands = `git sparse-checkout add ${answers.runtime.name}`; - let gitPullCommands = `git sparse-checkout add ${answers.runtime.id}`; /* Force use CMD as powershell does not support && */ if (process.platform === 'win32') { @@ -167,10 +172,11 @@ const initFunction = async () => { gitPullCommands = 'cmd /c "' + gitPullCommands + '"'; } + log('Loading templates...'); /* Execute the child process but do not print any std output */ try { - childProcess.execSync(gitInitCommands, { stdio: 'pipe', cwd: functionDir }); - childProcess.execSync(gitPullCommands, { stdio: 'pipe', cwd: functionDir }); + childProcess.execSync(gitInitCommands, { stdio: 'pipe', cwd: templatesDir }); + childProcess.execSync(gitPullCommands, { stdio: 'pipe', cwd: templatesDir }); } catch (error) { /* Specialised errors with recommended actions to take */ if (error.message.includes('error: unknown option')) { @@ -182,7 +188,20 @@ const initFunction = async () => { } } - fs.rmSync(path.join(functionDir, ".git"), { recursive: true }); + fs.rmSync(path.join(templatesDir, ".git"), { recursive: true }); + const templates = ['Starter']; + templates.push(...fs.readdirSync(runtimeDir, { withFileTypes: true }) + .filter(dirent => dirent.isDirectory() && dirent.name !== 'starter') + .map(dirent => dirent.name)); + + let selected = { template: 'starter' }; + + if (templates.length > 1) { + selected = await inquirer.prompt(questionsCreateFunctionSelectTemplate(templates)) + } + + + const copyRecursiveSync = (src, dest) => { let exists = fs.existsSync(src); let stats = exists && fs.statSync(src); @@ -199,9 +218,9 @@ const initFunction = async () => { fs.copyFileSync(src, dest); } }; - copyRecursiveSync(path.join(functionDir, answers.runtime.id), functionDir); + copyRecursiveSync(path.join(runtimeDir, selected.template), functionDir); - fs.rmSync(`${functionDir}/${answers.runtime.id}`, { recursive: true, force: true }); + fs.rmSync(templatesDir, { recursive: true, force: true }); const readmePath = path.join(process.cwd(), 'functions', functionId, 'README.md'); const readmeFile = fs.readFileSync(readmePath).toString(); diff --git a/templates/cli/lib/questions.js.twig b/templates/cli/lib/questions.js.twig index 2f93e5956..28522ba9a 100644 --- a/templates/cli/lib/questions.js.twig +++ b/templates/cli/lib/questions.js.twig @@ -263,6 +263,7 @@ const questionsCreateFunction = [ name: `${runtime.name} (${runtime['$id']})`, value: { id: runtime['$id'], + name: runtime['$id'].split('-')[0], entrypoint: getEntrypoint(runtime['$id']), ignore: getIgnores(runtime['$id']), commands: getInstallCommand(runtime['$id']) @@ -274,6 +275,23 @@ const questionsCreateFunction = [ } ]; +const questionsCreateFunctionSelectTemplate = (templates) => { + return [ + { + type: "list", + name: "template", + message: "What template would you like to use?", + choices: templates.map((template) => { + const name =`${template[0].toUpperCase()}${template.split('').slice(1).join('')}`.replace(/[-_]/g,' '); + + return { value: template, name } + }) + } + ]; +}; + + + const questionsCreateBucket = [ { type: "input", @@ -617,6 +635,7 @@ const questionsMfaChallenge = [ module.exports = { questionsInitProject, questionsCreateFunction, + questionsCreateFunctionSelectTemplate, questionsCreateBucket, questionsCreateCollection, questionsCreateMessagingTopic, From 64726b2b81d7ce9518ae0d5b6c4615b73e7b58b1 Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Mon, 27 May 2024 20:19:52 -0400 Subject: [PATCH 26/26] refactor(cli): Review fixing --- templates/cli/lib/commands/init.js.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/cli/lib/commands/init.js.twig b/templates/cli/lib/commands/init.js.twig index 4712f6ea6..424ed1d6b 100644 --- a/templates/cli/lib/commands/init.js.twig +++ b/templates/cli/lib/commands/init.js.twig @@ -191,7 +191,7 @@ const initFunction = async () => { fs.rmSync(path.join(templatesDir, ".git"), { recursive: true }); const templates = ['Starter']; templates.push(...fs.readdirSync(runtimeDir, { withFileTypes: true }) - .filter(dirent => dirent.isDirectory() && dirent.name !== 'starter') + .filter(item => item.isDirectory() && item.name !== 'starter') .map(dirent => dirent.name)); let selected = { template: 'starter' };