From e9f4cc963ecc5a1f1d8a8ccd29f5567414591e9a Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Thu, 6 Apr 2023 13:24:25 -0700 Subject: [PATCH 01/12] Auto format CLI template --- templates/cli/lib/commands/deploy.js.twig | 70 +-- templates/cli/lib/commands/init.js.twig | 428 +++++++-------- templates/cli/lib/config.js.twig | 8 +- templates/cli/lib/questions.js.twig | 606 +++++++++++----------- 4 files changed, 556 insertions(+), 556 deletions(-) diff --git a/templates/cli/lib/commands/deploy.js.twig b/templates/cli/lib/commands/deploy.js.twig index 111da26d5..ba0f50d27 100644 --- a/templates/cli/lib/commands/deploy.js.twig +++ b/templates/cli/lib/commands/deploy.js.twig @@ -48,7 +48,7 @@ const awaitPools = { const { attributes: remoteAttributes } = await databasesListAttributes({ databaseId, collectionId, - queries: [ 'limit(100)' ], + queries: ['limit(100)'], parseOutput: false }); @@ -68,7 +68,7 @@ const awaitPools = { const { indexes: remoteIndexes } = await databasesListIndexes({ databaseId, collectionId, - queries: [ 'limit(100)' ], + queries: ['limit(100)'], parseOutput: false }); @@ -88,7 +88,7 @@ const awaitPools = { const { attributes: remoteAttributes } = await databasesListAttributes({ databaseId, collectionId, - queries: [ 'limit(100)' ], + queries: ['limit(100)'], parseOutput: false }); @@ -120,7 +120,7 @@ const awaitPools = { const { indexes: remoteIndexes } = await databasesListIndexes({ databaseId, collectionId, - queries: [ 'limit(100)' ], + queries: ['limit(100)'], parseOutput: false }); @@ -159,9 +159,9 @@ const deployFunction = async ({ functionId, all, yes } = {}) => { const functionIds = []; - if(functionId) { + if (functionId) { functionIds.push(functionId); - } else if(all) { + } else if (all) { const functions = localConfig.getFunctions(); if (functions.length === 0) { throw new Error("No functions found in the current directory."); @@ -171,22 +171,22 @@ const deployFunction = async ({ functionId, all, yes } = {}) => { })); } - if(functionIds.length <= 0) { + if (functionIds.length <= 0) { const answers = await inquirer.prompt(questionsDeployFunctions[0]); functionIds.push(...answers.functions); } let functions = functionIds.map((id) => { - const functions = localConfig.getFunctions(); - const func = functions.find((f) => f.$id === id); + const functions = localConfig.getFunctions(); + const func = functions.find((f) => f.$id === id); - if(!func) { - throw new Error("Function '" + id + "' not found.") - } + if (!func) { + throw new Error("Function '" + id + "' not found.") + } - return func; + return func; }); - + for (let func of functions) { log(`Deploying function ${func.name} ( ${func['$id']} )`) @@ -196,22 +196,22 @@ const deployFunction = async ({ functionId, all, yes } = {}) => { parseOutput: false, }); - if(response.runtime !== func.runtime) { + if (response.runtime !== func.runtime) { throw new Error(`Runtime missmatch! (local=${func.runtime},remote=${response.runtime}) Please delete remote function or update your appwrite.json`); } - if(func.variables) { + if (func.variables) { // Delete existing variables // TODO: Pagination? const { variables: remoteVariables } = await functionsListVariables({ functionId: func['$id'], - queries: [ 'limit(100)' ], + queries: ['limit(100)'], parseOutput: false }); - if(remoteVariables.length > 0) { - if(!yes) { + if (remoteVariables.length > 0) { + if (!yes) { const variableAnswers = await inquirer.prompt(questionsDeployFunctions[1]) if (variableAnswers.override !== "YES") { @@ -417,21 +417,21 @@ const deployCollection = async ({ all, yes } = {}) => { let collectionIds = []; const configCollections = localConfig.getCollections(); - if(all) { + if (all) { if (configCollections.length === 0) { throw new Error("No collections found in the current directory. Run `{{ language.params.executableName }} init collection` to fetch all your collections."); } collectionIds.push(...configCollections.map((c) => c.$id)); } - if(collectionIds.length <= 0) { + if (collectionIds.length <= 0) { let answers = await inquirer.prompt(questionsDeployCollections[0]) collectionIds.push(...answers.collections); } let collections = []; - for(const collectionId of collectionIds) { + for (const collectionId of collectionIds) { const idCollections = configCollections.filter((c) => c.$id === collectionId); collections.push(...idCollections); } @@ -457,7 +457,7 @@ const deployCollection = async ({ all, yes } = {}) => { }) success(`Updated ${localDatabase.name} ( ${collection.databaseId} )`); - } catch(err) { + } catch (err) { log(`Database ${collection.databaseId} not found. Creating it now...`); const database = await databasesCreate({ databaseId: collection.databaseId, @@ -475,7 +475,7 @@ const deployCollection = async ({ all, yes } = {}) => { }) log(`Collection ${collection.name} ( ${collection['$id']} ) already exists.`); - if(!yes) { + if (!yes) { answers = await inquirer.prompt(questionsDeployCollections[1]) if (answers.override !== "YES") { log(`Received "${answers.override}". Skipping ${collection.name} ( ${collection['$id']} )`); @@ -489,7 +489,7 @@ const deployCollection = async ({ all, yes } = {}) => { const { indexes: remoteIndexes } = await databasesListIndexes({ databaseId, collectionId: collection['$id'], - queries: [ 'limit(100)' ], + queries: ['limit(100)'], parseOutput: false }); @@ -511,10 +511,10 @@ const deployCollection = async ({ all, yes } = {}) => { const { attributes: remoteAttributes } = await databasesListAttributes({ databaseId, collectionId: collection['$id'], - queries: [ 'limit(100)' ], + queries: ['limit(100)'], parseOutput: false }); - + await Promise.all(remoteAttributes.map(async attribute => { await databasesDeleteAttribute({ databaseId, @@ -633,21 +633,21 @@ const deployBucket = async ({ all, yes } = {}) => { let bucketIds = []; const configBuckets = localConfig.getBuckets(); - if(all) { + if (all) { if (configBuckets.length === 0) { throw new Error("No buckets found in the current directory. Run `appwrite init bucket` to fetch all your buckets."); } bucketIds.push(...configBuckets.map((b) => b.$id)); } - if(bucketIds.length === 0) { + if (bucketIds.length === 0) { let answers = await inquirer.prompt(questionsDeployBuckets[0]) bucketIds.push(...answers.buckets); } let buckets = []; - for(const bucketId of bucketIds) { + for (const bucketId of bucketIds) { const idBuckets = configBuckets.filter((b) => b.$id === bucketId); buckets.push(...idBuckets); } @@ -662,7 +662,7 @@ const deployBucket = async ({ all, yes } = {}) => { }) log(`Bucket ${bucket.name} ( ${bucket['$id']} ) already exists.`); - if(!yes) { + if (!yes) { answers = await inquirer.prompt(questionsDeployBuckets[1]) if (answers.override !== "YES") { log(`Received "${answers.override}". Skipping ${bucket.name} ( ${bucket['$id']} )`); @@ -720,21 +720,21 @@ const deployTeam = async ({ all, yes } = {}) => { let teamIds = []; const configTeams = localConfig.getTeams(); - if(all) { + if (all) { if (configTeams.length === 0) { throw new Error("No teams found in the current directory. Run `appwrite init team` to fetch all your teams."); } teamIds.push(...configTeams.map((t) => t.$id)); } - if(teamIds.length === 0) { + if (teamIds.length === 0) { let answers = await inquirer.prompt(questionsDeployTeams[0]) teamIds.push(...answers.teams); } let teams = []; - for(const teamId of teamIds) { + for (const teamId of teamIds) { const idTeams = configTeams.filter((t) => t.$id === teamId); teams.push(...idTeams); } @@ -749,7 +749,7 @@ const deployTeam = async ({ all, yes } = {}) => { }) log(`Team ${team.name} ( ${team['$id']} ) already exists.`); - if(!yes) { + if (!yes) { answers = await inquirer.prompt(questionsDeployTeams[1]) if (answers.override !== "YES") { log(`Received "${answers.override}". Skipping ${team.name} ( ${team['$id']} )`); diff --git a/templates/cli/lib/commands/init.js.twig b/templates/cli/lib/commands/init.js.twig index 233f0f731..2bdb6d055 100644 --- a/templates/cli/lib/commands/init.js.twig +++ b/templates/cli/lib/commands/init.js.twig @@ -14,261 +14,261 @@ const { questionsInitProject, questionsInitFunction, questionsInitCollection } = const { success, log, actionRunner, commandDescriptions } = require("../parser"); const init = new Command("init") - .description(commandDescriptions['init']) - .configureHelp({ + .description(commandDescriptions['init']) + .configureHelp({ helpWidth: process.stdout.columns || 80 - }) - .action(actionRunner(async (_options, command) => { - command.help(); - })); - -const initProject = async () => { - let response = {} - let answers = await inquirer.prompt(questionsInitProject) - 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 }) + .action(actionRunner(async (_options, command) => { + command.help(); + })); - 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); - } - success(); +const initProject = async () => { + let response = {} + let answers = await inquirer.prompt(questionsInitProject) + 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); + } + success(); } const initFunction = async () => { - // TODO: Add CI/CD support (ID, name, runtime) - let answers = await inquirer.prompt(questionsInitFunction) - let functionFolder = path.join(process.cwd(), 'functions'); - - if (!fs.existsSync(functionFolder)) { - fs.mkdirSync(functionFolder, { - recursive: true - }); - } - - const functionDir = path.join(functionFolder, answers.name); - - if (fs.existsSync(functionDir)) { - throw new Error(`( ${answers.name} ) 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 deploy the function.`); - } - - let response = await functionsCreate({ - functionId: answers.id, - name: answers.name, - runtime: answers.runtime.id, - parseOutput: false - }) - - fs.mkdirSync(functionDir, "777"); - - let gitInitCommands = "git clone --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; + // TODO: Add CI/CD support (ID, name, runtime) + let answers = await inquirer.prompt(questionsInitFunction) + let functionFolder = path.join(process.cwd(), 'functions'); + + if (!fs.existsSync(functionFolder)) { + fs.mkdirSync(functionFolder, { + recursive: true + }); } - } - - 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); + + const functionDir = path.join(functionFolder, answers.name); + + if (fs.existsSync(functionDir)) { + throw new Error(`( ${answers.name} ) already exists in the current directory. Please choose another name.`); } - }; - 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', answers.name, '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, - path: `functions/${answers.name}`, - entrypoint: answers.runtime.entrypoint || '', - ignore: answers.runtime.ignore || null, - execute: response.execute, - events: response.events, - schedule: response.schedule, - timeout: response.timeout, - }; - - localConfig.addFunction(data); - success(); -} -const initCollection = async ({ all, databaseId } = {}) => { - const databaseIds = []; + if (!answers.runtime.entrypoint) { + log(`Entrypoint for this runtime not found. You will be asked to configure entrypoint when you first deploy the function.`); + } - if(databaseId) { - databaseIds.push(databaseId); - } else if(all) { - let allDatabases = await databasesList({ + let response = await functionsCreate({ + functionId: answers.id, + name: answers.name, + runtime: answers.runtime.id, parseOutput: false - }) + }) - databaseIds.push(...allDatabases.databases.map((d) => d.$id)); - } + fs.mkdirSync(functionDir, "777"); - if(databaseIds.length <= 0) { - let answers = await inquirer.prompt(questionsInitCollection) - if (!answers.databases) process.exit(1) - databaseIds.push(...answers.databases); - } + let gitInitCommands = "git clone --depth 1 --sparse https://github.com/{{ sdk.gitUserName }}/functions-starter ."; // depth prevents fetching older commits reducing the amount fetched - for(const databaseId of databaseIds) { - const database = await databasesGet({ - databaseId, - parseOutput: false - }); + let gitPullCommands = `git sparse-checkout add ${answers.runtime.id}`; - localConfig.addDatabase(database); + /* Force use CMD as powershell does not support && */ + if (process.platform == 'win32') { + gitInitCommands = 'cmd /c "' + gitInitCommands + '"'; + gitPullCommands = 'cmd /c "' + gitPullCommands + '"'; + } - // TODO: Pagination? - let response = await databasesListCollections({ - databaseId, - queries: [ 'limit(100)' ], - parseOutput: false - }) + /* 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; + } + } - let collections = response.collections; - log(`Found ${collections.length} collections`); + 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', answers.name, '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, + path: `functions/${answers.name}`, + entrypoint: answers.runtime.entrypoint || '', + ignore: answers.runtime.ignore || null, + execute: response.execute, + events: response.events, + schedule: response.schedule, + timeout: response.timeout, + }; + + localConfig.addFunction(data); + success(); +} - collections.forEach(async collection => { - log(`Fetching ${collection.name} ...`); - localConfig.addCollection({ - ...collection, - '$createdAt': undefined, - '$updatedAt': undefined, - }); - }); - } +const initCollection = async ({ all, databaseId } = {}) => { + const databaseIds = []; + + if (databaseId) { + databaseIds.push(databaseId); + } else if (all) { + let allDatabases = await databasesList({ + parseOutput: false + }) + + databaseIds.push(...allDatabases.databases.map((d) => d.$id)); + } + + if (databaseIds.length <= 0) { + let answers = await inquirer.prompt(questionsInitCollection) + if (!answers.databases) process.exit(1) + databaseIds.push(...answers.databases); + } - success(); + for (const databaseId of databaseIds) { + const database = await databasesGet({ + databaseId, + parseOutput: false + }); + + localConfig.addDatabase(database); + + // TODO: Pagination? + let response = await databasesListCollections({ + databaseId, + queries: ['limit(100)'], + parseOutput: false + }) + + let collections = response.collections; + log(`Found ${collections.length} collections`); + + collections.forEach(async collection => { + log(`Fetching ${collection.name} ...`); + localConfig.addCollection({ + ...collection, + '$createdAt': undefined, + '$updatedAt': undefined, + }); + }); + } + + success(); } const initBucket = async () => { - // TODO: Pagination? - let response = await storageListBuckets({ - queries: [ 'limit(100)' ], - parseOutput: false - }) + // TODO: Pagination? + let response = await storageListBuckets({ + queries: ['limit(100)'], + parseOutput: false + }) - let buckets = response.buckets; - log(`Found ${buckets.length} buckets`); + let buckets = response.buckets; + log(`Found ${buckets.length} buckets`); - buckets.forEach(async bucket => { - log(`Fetching ${bucket.name} ...`); - localConfig.addBucket(bucket); - }); + buckets.forEach(async bucket => { + log(`Fetching ${bucket.name} ...`); + localConfig.addBucket(bucket); + }); - success(); + success(); } const initTeam = async () => { - // TODO: Pagination? - let response = await teamsList({ - queries: [ 'limit(100)' ], - parseOutput: false - }) - - let teams = response.teams; - log(`Found ${teams.length} teams`); - - teams.forEach(async team => { - log(`Fetching ${team.name} ...`); - localConfig.addTeam({ - ...team, - '$createdAt': undefined, - '$updatedAt': undefined, - 'total': undefined + // TODO: Pagination? + let response = await teamsList({ + queries: ['limit(100)'], + parseOutput: false + }) + + let teams = response.teams; + log(`Found ${teams.length} teams`); + + teams.forEach(async team => { + log(`Fetching ${team.name} ...`); + localConfig.addTeam({ + ...team, + '$createdAt': undefined, + '$updatedAt': undefined, + 'total': undefined + }); }); - }); - success(); + success(); } init - .command("project") - .description("Initialise your {{ spec.title|caseUcfirst }} project") - .action(actionRunner(initProject)); + .command("project") + .description("Initialise your {{ spec.title|caseUcfirst }} project") + .action(actionRunner(initProject)); init - .command("function") - .description("Initialise your {{ spec.title|caseUcfirst }} cloud function") - .action(actionRunner(initFunction)) + .command("function") + .description("Initialise your {{ spec.title|caseUcfirst }} cloud function") + .action(actionRunner(initFunction)) init - .command("collection") - .description("Initialise your {{ spec.title|caseUcfirst }} collections") - .option(`--databaseId `, `Database ID`) - .option(`--all`, `Flag to initialize all databases`) - .action(actionRunner(initCollection)) + .command("collection") + .description("Initialise your {{ spec.title|caseUcfirst }} collections") + .option(`--databaseId `, `Database ID`) + .option(`--all`, `Flag to initialize all databases`) + .action(actionRunner(initCollection)) init - .command("bucket") - .description("Initialise your Appwrite buckets") - .action(actionRunner(initBucket)) + .command("bucket") + .description("Initialise your Appwrite buckets") + .action(actionRunner(initBucket)) init - .command("team") - .description("Initialise your Appwrite teams") - .action(actionRunner(initTeam)) + .command("team") + .description("Initialise your Appwrite teams") + .action(actionRunner(initTeam)) module.exports = { - init, + init, }; diff --git a/templates/cli/lib/config.js.twig b/templates/cli/lib/config.js.twig index 1f1297b25..24e665d68 100644 --- a/templates/cli/lib/config.js.twig +++ b/templates/cli/lib/config.js.twig @@ -22,7 +22,7 @@ class Config { write() { let dir = _path.dirname(this.path) - if (!fs.existsSync(dir)){ + if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } fs.writeFileSync(this.path, JSONbig.stringify(this.data, null, 4)); @@ -265,7 +265,7 @@ class Local extends Config { return {}; } - + addTeam(props) { if (!this.has("teams")) { this.set("teams", []); @@ -378,6 +378,6 @@ class Global extends Config { } module.exports = { - localConfig : new Local(), - globalConfig : new Global(), + localConfig: new Local(), + globalConfig: new Global(), }; diff --git a/templates/cli/lib/questions.js.twig b/templates/cli/lib/questions.js.twig index 8c5014e3a..77d820561 100644 --- a/templates/cli/lib/questions.js.twig +++ b/templates/cli/lib/questions.js.twig @@ -5,361 +5,361 @@ const { databasesList } = require('./commands/databases'); const JSONbig = require("json-bigint")({ storeAsString: false }); const getIgnores = (runtime) => { - const languge = runtime.split('-')[0]; + const languge = runtime.split('-')[0]; - switch (languge) { - case 'cpp': - return ['build', 'CMakeFiles', 'CMakeCaches.txt']; - case 'dart': - return ['.packages', '.dart_tool']; - case 'deno': - return []; - case 'dotnet': - return ['bin', 'obj', '.nuget']; - case 'java': - case 'kotlin': - return ['build']; - case 'node': - return ['node_modules', '.npm']; - case 'php': - return ['vendor']; - case 'python': - return ['__pypackages__']; - case 'ruby': - return ['vendor']; - case 'rust': - return ['target', 'debug', '*.rs.bk', '*.pdb']; - case 'swift': - return ['.build', '.swiftpm']; - } + switch (languge) { + case 'cpp': + return ['build', 'CMakeFiles', 'CMakeCaches.txt']; + case 'dart': + return ['.packages', '.dart_tool']; + case 'deno': + return []; + case 'dotnet': + return ['bin', 'obj', '.nuget']; + case 'java': + case 'kotlin': + return ['build']; + case 'node': + return ['node_modules', '.npm']; + case 'php': + return ['vendor']; + case 'python': + return ['__pypackages__']; + case 'ruby': + return ['vendor']; + case 'rust': + return ['target', 'debug', '*.rs.bk', '*.pdb']; + case 'swift': + return ['.build', '.swiftpm']; + } - return undefined; + return undefined; }; const getEntrypoint = (runtime) => { - const languge = runtime.split('-')[0]; + const languge = runtime.split('-')[0]; - switch (languge) { - case 'dart': - return 'lib/main.dart'; - case 'deno': - return 'src/mod.ts'; - case 'node': - return 'src/index.js'; - case 'php': - return 'src/index.php'; - case 'python': - return 'src/index.py'; - case 'ruby': - return 'src/index.rb'; - case 'rust': - return 'main.rs'; - case 'swift': - return 'Sources/swift-5.5/main.swift'; - case 'cpp': - return 'src/index.cc'; - case 'dotnet': - return 'src/Index.cs'; - case 'java': - return 'src/Index.java'; - case 'kotlin': - return 'src/Index.kt'; - } + switch (languge) { + case 'dart': + return 'lib/main.dart'; + case 'deno': + return 'src/mod.ts'; + case 'node': + return 'src/index.js'; + case 'php': + return 'src/index.php'; + case 'python': + return 'src/index.py'; + case 'ruby': + return 'src/index.rb'; + case 'rust': + return 'main.rs'; + case 'swift': + return 'Sources/swift-5.5/main.swift'; + case 'cpp': + return 'src/index.cc'; + case 'dotnet': + return 'src/Index.cs'; + case 'java': + return 'src/Index.java'; + case 'kotlin': + return 'src/Index.kt'; + } - return undefined; + return undefined; }; const questionsInitProject = [ - { - type: "confirm", - name: "override", - message: - `An {{ spec.title|caseUcfirst }} project ( ${localConfig.getProject()['projectName']} ) is already associated with the current directory. Would you like to override`, - when() { - return Object.keys(localConfig.getProject()).length !== 0; - } - }, - { - type: "list", - name: "start", - when(answers) { - if (answers.override == undefined) { - return true - } - return answers.override; + { + type: "confirm", + name: "override", + message: + `An {{ spec.title|caseUcfirst }} project ( ${localConfig.getProject()['projectName']} ) is already associated with the current directory. Would you like to override`, + when() { + return Object.keys(localConfig.getProject()).length !== 0; + } }, - 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(answers) { - return answers.start == "new"; + { + 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: "id", - message: "What ID would you like to have for your project?", - default: "unique()", - when(answers) { - return answers.start == "new"; + { + type: "input", + name: "project", + message: "What would you like to name your project?", + default: "My Awesome Project", + when(answers) { + return answers.start == "new"; + }, }, - }, - { - type: "list", - name: "project", - message: "Choose your {{ spec.title|caseUcfirst }} project.", - when(answers) { - return answers.start == "existing"; + { + type: "input", + name: "id", + message: "What ID would you like to have for your project?", + default: "unique()", + when(answers) { + return answers.start == "new"; + }, }, - choices: async () => { - let response = await projectsList({ - parseOutput: false - }) - let projects = response["projects"] - let choices = projects.map((project, idx) => { - return { - name: `${project.name} (${project['$id']})`, - value: { - name: project.name, - id: project['$id'] - } - } - }) + { + type: "list", + name: "project", + message: "Choose your {{ spec.title|caseUcfirst }} project.", + when(answers) { + return answers.start == "existing"; + }, + choices: async () => { + let response = await projectsList({ + parseOutput: false + }) + 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.") - } + if (choices.length == 0) { + throw new Error("No projects found. Please create a new project.") + } - return choices; + return choices; + } } - } ]; const questionsInitFunction = [ - { - 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'])}, + { + 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']) }, + } + }) + return choices; } - }) - return choices; } - } ]; const questionsInitCollection = [ - { - type: "checkbox", - name: "databases", - message: "From which database would you like to init collections?", - choices: async () => { - let response = await databasesList({ - parseOutput: false - }) - let databases = response["databases"] + { + type: "checkbox", + name: "databases", + message: "From which database would you like to init collections?", + choices: async () => { + let response = await databasesList({ + parseOutput: false + }) + let databases = response["databases"] - if(databases.length <= 0) { - throw new Error("No databases found. Please create one in project console.") - } - let choices = databases.map((database, idx) => { - return { - name: `${database.name} (${database.$id})`, - value: database.$id + if (databases.length <= 0) { + throw new Error("No databases found. Please create one in project console.") + } + let choices = databases.map((database, idx) => { + return { + name: `${database.name} (${database.$id})`, + value: database.$id + } + }) + return choices; } - }) - return choices; } - } ]; const questionsLogin = [ - { - type: "input", - name: "email", - message: "Enter your email", - validate(value) { - if (!value) { - return "Please enter your email"; - } - return true; + { + type: "input", + name: "email", + message: "Enter your email", + validate(value) { + if (!value) { + return "Please enter your email"; + } + return true; + }, + }, + { + type: "password", + name: "password", + message: "Enter your password", + mask: "*", + validate(value) { + if (!value) { + return "Please enter your password"; + } + return true; + } }, - }, - { - type: "password", - name: "password", - message: "Enter your password", - mask: "*", - validate(value) { - if (!value) { - return "Please enter your password"; - } - return true; - } - }, ]; const questionsDeployFunctions = [ - { - type: "checkbox", - name: "functions", - message: "Which functions would you like to deploy?", - choices: () => { - let functions = localConfig.getFunctions(); - if (functions.length === 0) { - throw new Error("No functions found in the current directory."); - } - let choices = functions.map((func, idx) => { - return { - name: `${func.name} (${func.$id})`, - value: func.$id + { + type: "checkbox", + name: "functions", + message: "Which functions would you like to deploy?", + choices: () => { + let functions = localConfig.getFunctions(); + if (functions.length === 0) { + throw new Error("No functions found in the current directory."); + } + let choices = functions.map((func, idx) => { + return { + name: `${func.name} (${func.$id})`, + value: func.$id + } + }) + return choices; } - }) - return choices; - } - }, - { - type: "input", - name: "override", - message: 'Are you sure you want to override this function\'s variables? This can lead to loss of secrets! Type "YES" to confirm.' - }, + }, + { + type: "input", + name: "override", + message: 'Are you sure you want to override this function\'s variables? This can lead to loss of secrets! Type "YES" to confirm.' + }, ] const questionsDeployCollections = [ - { - type: "checkbox", - name: "collections", - message: "Which collections would you like to deploy?", - choices: () => { - let collections = localConfig.getCollections(); - if (collections.length === 0) { - throw new Error("No collections found in the current directory. Run `{{ language.params.executableName }} init collection` to fetch all your collections."); - } - let choices = collections.map((collection, idx) => { - return { - name: `${collection.name} (${collection['$id']})`, - value: collection.$id + { + type: "checkbox", + name: "collections", + message: "Which collections would you like to deploy?", + choices: () => { + let collections = localConfig.getCollections(); + if (collections.length === 0) { + throw new Error("No collections found in the current directory. Run `{{ language.params.executableName }} init collection` to fetch all your collections."); + } + let choices = collections.map((collection, idx) => { + return { + name: `${collection.name} (${collection['$id']})`, + value: collection.$id + } + }) + return choices; } - }) - return choices; - } - }, - { - type: "input", - name: "override", - message: 'Are you sure you want to override this collection? This can lead to loss of data! Type "YES" to confirm.' - }, + }, + { + type: "input", + name: "override", + message: 'Are you sure you want to override this collection? This can lead to loss of data! Type "YES" to confirm.' + }, ] const questionsDeployBuckets = [ - { - type: "checkbox", - name: "buckets", - message: "Which buckets would you like to deploy?", - choices: () => { - let buckets = localConfig.getBuckets(); - if (buckets.length === 0) { - throw new Error("No buckets found in the current directory. Run `appwrite init bucket` to fetch all your buckets."); - } - let choices = buckets.map((bucket, idx) => { - return { - name: `${bucket.name} (${bucket['$id']})`, - value: bucket.$id + { + type: "checkbox", + name: "buckets", + message: "Which buckets would you like to deploy?", + choices: () => { + let buckets = localConfig.getBuckets(); + if (buckets.length === 0) { + throw new Error("No buckets found in the current directory. Run `appwrite init bucket` to fetch all your buckets."); + } + let choices = buckets.map((bucket, idx) => { + return { + name: `${bucket.name} (${bucket['$id']})`, + value: bucket.$id + } + }) + return choices; } - }) - return choices; - } - }, - { - type: "input", - name: "override", - message: 'Are you sure you want to override this bucket? This can lead to loss of data! Type "YES" to confirm.' - }, + }, + { + type: "input", + name: "override", + message: 'Are you sure you want to override this bucket? This can lead to loss of data! Type "YES" to confirm.' + }, ] const questionsGetEntrypoint = [ - { - type: "input", - name: "entrypoint", - message: "Enter the entrypoint", - default: null, - validate(value) { - if (!value) { - return "Please enter your enrtypoint"; - } - return true; - } - }, + { + type: "input", + name: "entrypoint", + message: "Enter the entrypoint", + default: null, + validate(value) { + if (!value) { + return "Please enter your enrtypoint"; + } + return true; + } + }, ] const questionsDeployTeams = [ - { - type: "checkbox", - name: "teams", - message: "Which teams would you like to deploy?", - choices: () => { - let teams = localConfig.getTeams(); - if (teams.length === 0) { - throw new Error("No teams found in the current directory. Run `appwrite init team` to fetch all your teams."); - } - let choices = teams.map((team, idx) => { - return { - name: `${team.name} (${team['$id']})`, - value: team.$id + { + type: "checkbox", + name: "teams", + message: "Which teams would you like to deploy?", + choices: () => { + let teams = localConfig.getTeams(); + if (teams.length === 0) { + throw new Error("No teams found in the current directory. Run `appwrite init team` to fetch all your teams."); + } + let choices = teams.map((team, idx) => { + return { + name: `${team.name} (${team['$id']})`, + value: team.$id + } + }) + return choices; } - }) - return choices; - } - }, - { - type: "input", - name: "override", - message: 'Are you sure you want to override this team? This can lead to loss of data! Type "YES" to confirm.' - }, + }, + { + type: "input", + name: "override", + message: 'Are you sure you want to override this team? This can lead to loss of data! Type "YES" to confirm.' + }, ] module.exports = { - questionsInitProject, - questionsLogin, - questionsInitFunction, - questionsInitCollection, - questionsDeployFunctions, - questionsDeployCollections, - questionsDeployBuckets, - questionsDeployTeams, - questionsGetEntrypoint + questionsInitProject, + questionsLogin, + questionsInitFunction, + questionsInitCollection, + questionsDeployFunctions, + questionsDeployCollections, + questionsDeployBuckets, + questionsDeployTeams, + questionsGetEntrypoint }; From 5fc13ab306edd7e5cfb9120e53ef11b4b22f8f4a Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Thu, 6 Apr 2023 13:29:33 -0700 Subject: [PATCH 02/12] Ensure team preferences aren't pulled down Team preferences are considered data and can change frequently. It doesn't make sense to be persisted in the appwrite.json. --- templates/cli/lib/commands/init.js.twig | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/templates/cli/lib/commands/init.js.twig b/templates/cli/lib/commands/init.js.twig index 2bdb6d055..245114203 100644 --- a/templates/cli/lib/commands/init.js.twig +++ b/templates/cli/lib/commands/init.js.twig @@ -231,12 +231,8 @@ const initTeam = async () => { teams.forEach(async team => { log(`Fetching ${team.name} ...`); - localConfig.addTeam({ - ...team, - '$createdAt': undefined, - '$updatedAt': undefined, - 'total': undefined - }); + const { total, $updatedAt, $createdAt, prefs, ...rest } = team; + localConfig.addTeam(rest); }); success(); From fc4233cab94e036563bdd2609b72aa97b7062015 Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Thu, 6 Apr 2023 14:08:59 -0700 Subject: [PATCH 03/12] Fix missing databasesGet import --- 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 245114203..06e400d70 100644 --- a/templates/cli/lib/commands/init.js.twig +++ b/templates/cli/lib/commands/init.js.twig @@ -6,7 +6,7 @@ const inquirer = require("inquirer"); const { teamsCreate, teamsList } = require("./teams"); const { projectsCreate } = require("./projects"); const { functionsCreate } = require("./functions"); -const { databasesListCollections, databasesList } = require("./databases"); +const { databasesGet, databasesListCollections, databasesList } = require("./databases"); const { storageListBuckets } = require("./storage"); const { sdkForConsole } = require("../sdks"); const { localConfig } = require("../config"); From 4e4d122b48fd41eff57e34dfa265070336c4719d Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Thu, 6 Apr 2023 14:20:11 -0700 Subject: [PATCH 04/12] Correctly import collections with same ID from different databases --- templates/cli/lib/config.js.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/cli/lib/config.js.twig b/templates/cli/lib/config.js.twig index 24e665d68..744013e38 100644 --- a/templates/cli/lib/config.js.twig +++ b/templates/cli/lib/config.js.twig @@ -156,7 +156,7 @@ class Local extends Config { let collections = this.get("collections"); for (let i = 0; i < collections.length; i++) { - if (collections[i]['$id'] == props['$id']) { + if (collections[i]['$id'] == props['$id'] && collections[i]['databaseId'] == props['databaseId']) { collections[i] = props; this.set("collections", collections); return; From bb6c34761200025aaf7b34cf775ab99ca99978e4 Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Thu, 6 Apr 2023 15:39:05 -0700 Subject: [PATCH 05/12] Update deploy collection to deploy relationship attributes --- templates/cli/lib/commands/deploy.js.twig | 138 +++++++++++----------- 1 file changed, 70 insertions(+), 68 deletions(-) diff --git a/templates/cli/lib/commands/deploy.js.twig b/templates/cli/lib/commands/deploy.js.twig index ba0f50d27..191778864 100644 --- a/templates/cli/lib/commands/deploy.js.twig +++ b/templates/cli/lib/commands/deploy.js.twig @@ -8,6 +8,7 @@ const { functionsGet, functionsCreate, functionsUpdate, functionsCreateDeploymen const { databasesGet, databasesCreate, + databasesUpdate, databasesCreateBooleanAttribute, databasesGetCollection, databasesCreateCollection, @@ -20,6 +21,7 @@ const { databasesCreateUrlAttribute, databasesCreateIpAttribute, databasesCreateEnumAttribute, + databasesCreateRelationshipAttribute, databasesDeleteAttribute, databasesListAttributes, databasesListIndexes, @@ -408,6 +410,18 @@ const createAttribute = async (databaseId, collectionId, attribute) => { array: attribute.array, parseOutput: false }) + case 'relationship': + return databasesCreateRelationshipAttribute({ + databaseId, + collectionId, + relatedCollectionId: attribute.relatedCollection, + type: attribute.relationType, + twoWay: attribute.twoWay, + key: attribute.key, + twoWayKey: attribute.twoWayKey, + onDelete: attribute.onDelete, + parseOutput: false + }) } } @@ -483,7 +497,7 @@ const deployCollection = async ({ all, yes } = {}) => { } } - log(`Updating attributes ... `); + log(`Deleting indexes and attributes ... `); // TODO: Pagination? const { indexes: remoteIndexes } = await databasesListIndexes({ @@ -529,39 +543,6 @@ const deployCollection = async ({ all, yes } = {}) => { throw new Error("Attribute deletion did not finish for too long."); } - await Promise.all(collection.attributes.map(async attribute => { - await createAttribute(databaseId, collection['$id'], attribute); - })); - - const attributeKeys = collection.attributes.map(attribute => attribute.key); - const createPoolStatus = await awaitPools.expectAttributes(databaseId, collection['$id'], attributeKeys); - if (!createPoolStatus) { - throw new Error("Attribute creation did not finish for too long."); - } - - success(`Created ${collection.attributes.length} attributes`); - - log(`Creating indexes ...`) - await Promise.all(collection.indexes.map(async index => { - await databasesCreateIndex({ - databaseId, - collectionId: collection['$id'], - key: index.key, - type: index.type, - attributes: index.attributes, - orders: index.orders, - parseOutput: false - }); - })); - - const indexKeys = collection.indexes.map(attribute => attribute.key); - const indexPoolStatus = await awaitPools.expectIndexes(databaseId, collection['$id'], indexKeys); - if (!indexPoolStatus) { - throw new Error("Index creation did not finish for too long."); - } - - success(`Created ${collection.indexes.length} indexes`); - await databasesUpdateCollection({ databaseId, collectionId: collection['$id'], @@ -571,8 +552,6 @@ const deployCollection = async ({ all, yes } = {}) => { enabled: collection.enabled, parseOutput: false }) - - success(`Deployed ${collection.name} ( ${collection['$id']} )`); } catch (e) { if (e.code == 404) { log(`Collection ${collection.name} does not exist in the project. Creating ... `); @@ -585,45 +564,68 @@ const deployCollection = async ({ all, yes } = {}) => { parseOutput: false }) - log(`Creating attributes ... `); - await Promise.all(collection.attributes.map(async attribute => { - await createAttribute(databaseId, collection['$id'], attribute); - })); + } else { + throw e; + } + } - const attributeKeys = collection.attributes.map(attribute => attribute.key); - const attributePoolStatus = await awaitPools.expectAttributes(databaseId, collection['$id'], attributeKeys); - if (!attributePoolStatus) { - throw new Error("Attribute creation did not finish for too long."); - } + // Create all non-relationship attributes first + const nonRelationshipAttributes = collection.attributes.filter(attribute => attribute.type !== 'relationship'); + await Promise.all(nonRelationshipAttributes.map(attribute => { + return createAttribute(databaseId, collection['$id'], attribute); + })); - success(`Created ${collection.attributes.length} attributes`); + const nonRelationshipAttributeKeys = nonRelationshipAttributes.map(attribute => attribute.key); + const createPoolStatus = await awaitPools.expectAttributes(databaseId, collection['$id'], nonRelationshipAttributeKeys); + if (!createPoolStatus) { + throw new Error("Attribute creation did not finish for too long."); + } - log(`Creating indexes ...`); - await Promise.all(collection.indexes.map(async index => { - await databasesCreateIndex({ - databaseId, - collectionId: collection['$id'], - key: index.key, - type: index.type, - attributes: index.attributes, - orders: index.orders, - parseOutput: false - }); - })); + success(`Created ${nonRelationshipAttributeKeys.length} non-relationship attributes`); - const indexKeys = collection.indexes.map(attribute => attribute.key); - const indexPoolStatus = await awaitPools.expectIndexes(databaseId, collection['$id'], indexKeys); - if (!indexPoolStatus) { - throw new Error("Index creation did not finish for too long."); - } + log(`Creating indexes ...`) + await Promise.all(collection.indexes.map(async index => { + await databasesCreateIndex({ + databaseId, + collectionId: collection['$id'], + key: index.key, + type: index.type, + attributes: index.attributes, + orders: index.orders, + parseOutput: false + }); + })); + + const indexKeys = collection.indexes.map(attribute => attribute.key); + const indexPoolStatus = await awaitPools.expectIndexes(databaseId, collection['$id'], indexKeys); + if (!indexPoolStatus) { + throw new Error("Index creation did not finish for too long."); + } - success(`Created ${collection.indexes.length} indexes`); + success(`Created ${collection.indexes.length} indexes`); - success(`Deployed ${collection.name} ( ${collection['$id']} )`); - } else { - throw e; - } + success(`Deployed ${collection.name} ( ${collection['$id']} )`); + } + + // Create the relationship attributes + for (let collection of collections) { + const relationshipAttributes = collection.attributes.filter(attribute => attribute.type === 'relationship' && attribute.side === 'parent'); + + if (relationshipAttributes.length === 0) continue; + + log(`Deploying relationships for collection ${collection.name} ( ${collection['$id']} )`); + + await Promise.all(relationshipAttributes.map(attribute => { + return createAttribute(collection['databaseId'], collection['$id'], attribute); + })); + + const nonRelationshipAttributeKeys = relationshipAttributes.map(attribute => attribute.key); + const createPoolStatus = await awaitPools.expectAttributes(collection['databaseId'], collection['$id'], nonRelationshipAttributeKeys); + if (!createPoolStatus) { + throw new Error("Attribute creation did not finish for too long."); } + + success(`Created ${nonRelationshipAttributeKeys.length} relationship attributes`); } } From 31a484f76ef0a2c245f4bd778cdfc83c12891d81 Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Thu, 6 Apr 2023 17:24:20 -0700 Subject: [PATCH 06/12] Make sure to deploy collection from the correct database --- templates/cli/lib/commands/deploy.js.twig | 31 ++++++++++------------- templates/cli/lib/questions.js.twig | 4 +-- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/templates/cli/lib/commands/deploy.js.twig b/templates/cli/lib/commands/deploy.js.twig index 191778864..1f2f27e06 100644 --- a/templates/cli/lib/commands/deploy.js.twig +++ b/templates/cli/lib/commands/deploy.js.twig @@ -428,30 +428,27 @@ const createAttribute = async (databaseId, collectionId, attribute) => { const deployCollection = async ({ all, yes } = {}) => { let response = {}; - let collectionIds = []; - const configCollections = localConfig.getCollections(); + const collections = []; if (all) { - if (configCollections.length === 0) { + if (localConfig.getCollections().length === 0) { throw new Error("No collections found in the current directory. Run `{{ language.params.executableName }} init collection` to fetch all your collections."); } - collectionIds.push(...configCollections.map((c) => c.$id)); - } - - if (collectionIds.length <= 0) { - let answers = await inquirer.prompt(questionsDeployCollections[0]) - collectionIds.push(...answers.collections); - } - - let collections = []; - - for (const collectionId of collectionIds) { - const idCollections = configCollections.filter((c) => c.$id === collectionId); - collections.push(...idCollections); + collections.push(...localConfig.getCollections()); + } else { + const answers = await inquirer.prompt(questionsDeployCollections[0]) + const configCollections = new Map(); + localConfig.getCollections().forEach((c) => { + configCollections.set(`${c['databaseId']}|${c['$id']}`, c); + }); + answers.collections.forEach((a) => { + const collection = configCollections.get(a); + collections.push(collection); + }) } for (let collection of collections) { - log(`Deploying collection ${collection.name} ( ${collection['$id']} )`) + log(`Deploying collection ${collection.name} ( ${collection['databaseId']} - ${collection['$id']} )`) let databaseId; diff --git a/templates/cli/lib/questions.js.twig b/templates/cli/lib/questions.js.twig index 77d820561..0a8fb5857 100644 --- a/templates/cli/lib/questions.js.twig +++ b/templates/cli/lib/questions.js.twig @@ -271,8 +271,8 @@ const questionsDeployCollections = [ } let choices = collections.map((collection, idx) => { return { - name: `${collection.name} (${collection['$id']})`, - value: collection.$id + name: `${collection.name} (${collection['databaseId']} - ${collection['$id']})`, + value: `${collection['databaseId']}|${collection['$id']}` } }) return choices; From 2f2b56e580b4f679f532662261627ee8a62db6ea Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Thu, 6 Apr 2023 18:14:58 -0700 Subject: [PATCH 07/12] Ensure function is still deployed even if variables are not --- templates/cli/lib/commands/deploy.js.twig | 47 ++++++++++++----------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/templates/cli/lib/commands/deploy.js.twig b/templates/cli/lib/commands/deploy.js.twig index 1f2f27e06..e07f14f1c 100644 --- a/templates/cli/lib/commands/deploy.js.twig +++ b/templates/cli/lib/commands/deploy.js.twig @@ -213,33 +213,36 @@ const deployFunction = async ({ functionId, all, yes } = {}) => { }); if (remoteVariables.length > 0) { + let override = yes; if (!yes) { const variableAnswers = await inquirer.prompt(questionsDeployFunctions[1]) - - if (variableAnswers.override !== "YES") { - log(`Received "${variableAnswers.override}". Skipping ${func.name} ( ${func['$id']} )`); - continue; - } + override = variableAnswers.override === "YES"; } - await Promise.all(remoteVariables.map(async remoteVariable => { - await functionsDeleteVariable({ - functionId: func['$id'], - variableId: remoteVariable['$id'], - parseOutput: false - }); - })); + if (!override) { + log(`Skipping variables for ${func.name} ( ${func['$id']} )`); + } else { + log(`Deploying variables for ${func.name} ( ${func['$id']} )`); + + await Promise.all(remoteVariables.map(async remoteVariable => { + await functionsDeleteVariable({ + functionId: func['$id'], + variableId: remoteVariable['$id'], + parseOutput: false + }); + })); + + // Deploy local variables + await Promise.all(Object.keys(func.variables).map(async localVariableKey => { + await functionsCreateVariable({ + functionId: func['$id'], + key: localVariableKey, + value: func.variables[localVariableKey], + parseOutput: false + }); + })); + } } - - // Deploy local variables - await Promise.all(Object.keys(func.variables).map(async localVariableKey => { - await functionsCreateVariable({ - functionId: func['$id'], - key: localVariableKey, - value: func.variables[localVariableKey], - parseOutput: false - }); - })); } response = await functionsUpdate({ From d6ee32db085f9aef27f639211cf76b27866ded0c Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Thu, 6 Apr 2023 18:43:26 -0700 Subject: [PATCH 08/12] Clarify how many functions are deployed --- templates/cli/lib/commands/deploy.js.twig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/templates/cli/lib/commands/deploy.js.twig b/templates/cli/lib/commands/deploy.js.twig index e07f14f1c..349bfd891 100644 --- a/templates/cli/lib/commands/deploy.js.twig +++ b/templates/cli/lib/commands/deploy.js.twig @@ -309,6 +309,8 @@ const deployFunction = async ({ functionId, all, yes } = {}) => { } } } + + success(`Deployed ${functions.length} functions`); } const createAttribute = async (databaseId, collectionId, attribute) => { From 71b59a69f2726947ae999b0969ce22dafdbe1f29 Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Thu, 6 Apr 2023 18:58:17 -0700 Subject: [PATCH 09/12] Fix BigNumber output --- templates/cli/lib/parser.js.twig | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/templates/cli/lib/parser.js.twig b/templates/cli/lib/parser.js.twig index bf4149f4e..84304823b 100644 --- a/templates/cli/lib/parser.js.twig +++ b/templates/cli/lib/parser.js.twig @@ -23,8 +23,12 @@ const parse = (data) => { drawJSON(data[key]); } } else if (typeof data[key] === 'object') { - console.log(`${chalk.yellow.bold.underline(key)}`) - parse(data[key]); + if (data[key] && data[key].constructor.name === 'BigNumber') { + console.log(`${chalk.yellow.bold(key)} : ${data[key]}`); + } else { + console.log(`${chalk.yellow.bold.underline(key)}`) + parse(data[key]); + } } else { console.log(`${chalk.yellow.bold(key)} : ${data[key]}`); } From e821aa1634aeb702482922d000166d6fb29642b0 Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Thu, 6 Apr 2023 19:08:39 -0700 Subject: [PATCH 10/12] Support setting self signed endpoint --- templates/cli/lib/commands/generic.js.twig | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/templates/cli/lib/commands/generic.js.twig b/templates/cli/lib/commands/generic.js.twig index e1e248c63..cdd1bea2c 100644 --- a/templates/cli/lib/commands/generic.js.twig +++ b/templates/cli/lib/commands/generic.js.twig @@ -80,13 +80,16 @@ const client = new Command("client") if (url.protocol !== "http:" && url.protocol !== "https:") { throw new Error(); } - + let client = new Client().setEndpoint(endpoint); + if (selfSigned || globalConfig.getSelfSigned()) { + client.setSelfSigned(true); + } let response = await client.call('GET', '/health/version'); - if(!response.version) { + if (!response.version) { throw new Error(); } - + globalConfig.setEndpoint(endpoint); } catch (_) { throw new Error("Invalid endpoint or your Appwrite server is not running as expected."); From a6ea1618e451501813c737b60dd3b598787c1f73 Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Thu, 6 Apr 2023 21:21:32 -0700 Subject: [PATCH 11/12] Ensure function variables are deployed on function create --- templates/cli/lib/commands/deploy.js.twig | 86 +++++++++++------------ 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/templates/cli/lib/commands/deploy.js.twig b/templates/cli/lib/commands/deploy.js.twig index 349bfd891..f7c5ec043 100644 --- a/templates/cli/lib/commands/deploy.js.twig +++ b/templates/cli/lib/commands/deploy.js.twig @@ -202,49 +202,6 @@ const deployFunction = async ({ functionId, all, yes } = {}) => { throw new Error(`Runtime missmatch! (local=${func.runtime},remote=${response.runtime}) Please delete remote function or update your appwrite.json`); } - if (func.variables) { - // Delete existing variables - - // TODO: Pagination? - const { variables: remoteVariables } = await functionsListVariables({ - functionId: func['$id'], - queries: ['limit(100)'], - parseOutput: false - }); - - if (remoteVariables.length > 0) { - let override = yes; - if (!yes) { - const variableAnswers = await inquirer.prompt(questionsDeployFunctions[1]) - override = variableAnswers.override === "YES"; - } - - if (!override) { - log(`Skipping variables for ${func.name} ( ${func['$id']} )`); - } else { - log(`Deploying variables for ${func.name} ( ${func['$id']} )`); - - await Promise.all(remoteVariables.map(async remoteVariable => { - await functionsDeleteVariable({ - functionId: func['$id'], - variableId: remoteVariable['$id'], - parseOutput: false - }); - })); - - // Deploy local variables - await Promise.all(Object.keys(func.variables).map(async localVariableKey => { - await functionsCreateVariable({ - functionId: func['$id'], - key: localVariableKey, - value: func.variables[localVariableKey], - parseOutput: false - }); - })); - } - } - } - response = await functionsUpdate({ functionId: func['$id'], name: func.name, @@ -281,6 +238,49 @@ const deployFunction = async ({ functionId, all, yes } = {}) => { } } + if (func.variables) { + // Delete existing variables + + // TODO: Pagination? + const { variables: remoteVariables } = await functionsListVariables({ + functionId: func['$id'], + queries: ['limit(100)'], + parseOutput: false + }); + + let deployVariables = yes; + if (remoteVariables.length == 0) { + deployVariables = true; + } else if (remoteVariables.length > 0 && !yes) { + const variableAnswers = await inquirer.prompt(questionsDeployFunctions[1]) + deployVariables = variableAnswers.override === "YES"; + } + + if (!deployVariables) { + log(`Skipping variables for ${func.name} ( ${func['$id']} )`); + } else { + log(`Deploying variables for ${func.name} ( ${func['$id']} )`); + + await Promise.all(remoteVariables.map(async remoteVariable => { + await functionsDeleteVariable({ + functionId: func['$id'], + variableId: remoteVariable['$id'], + parseOutput: false + }); + })); + + // Deploy local variables + await Promise.all(Object.keys(func.variables).map(async localVariableKey => { + await functionsCreateVariable({ + functionId: func['$id'], + key: localVariableKey, + value: func.variables[localVariableKey], + parseOutput: false + }); + })); + } + } + // Create tag if (!func.entrypoint) { answers = await inquirer.prompt(questionsGetEntrypoint) From d9f106ad775f2e7be899645b726105337d2ddae4 Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Fri, 7 Apr 2023 08:26:21 -0700 Subject: [PATCH 12/12] Allow self signed without showing node warning --- templates/cli/lib/client.js.twig | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/templates/cli/lib/client.js.twig b/templates/cli/lib/client.js.twig index aca94bb5e..32142337f 100644 --- a/templates/cli/lib/client.js.twig +++ b/templates/cli/lib/client.js.twig @@ -1,4 +1,5 @@ const os = require('os'); +const https = require("https"); const axios = require("axios"); const JSONbig = require("json-bigint")({ storeAsString: false }); const FormData = require("form-data"); @@ -100,11 +101,6 @@ class Client { params = {}, responseType = "json" ) { - if (this.selfSigned == true) { - // Allow self signed requests - process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0; - } - headers = Object.assign({}, this.headers, headers); let contentType = headers["content-type"].toLowerCase(); @@ -140,6 +136,10 @@ class Client { transformResponse: [ (data) => data ? JSONbig.parse(data) : data ], responseType: responseType, }; + if (this.selfSigned == true) { + // Allow self signed requests + options.httpsAgent = new https.Agent({ rejectUnauthorized: false }); + } try { let response = await axios(options); if (response.headers["set-cookie"]) {