Skip to content

Commit 6dc94ff

Browse files
committed
CLI changes for init, push, pull sites
1 parent e274190 commit 6dc94ff

File tree

4 files changed

+723
-5
lines changed

4 files changed

+723
-5
lines changed

templates/cli/lib/commands/init.js.twig

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const { localConfig, globalConfig } = require("../config");
1515
const {
1616
questionsCreateFunction,
1717
questionsCreateFunctionSelectTemplate,
18+
questionsCreateSite,
1819
questionsCreateBucket,
1920
questionsCreateMessagingTopic,
2021
questionsCreateCollection,
@@ -25,11 +26,13 @@ const {
2526
} = require("../questions");
2627
const { cliConfig, success, log, hint, error, actionRunner, commandDescriptions } = require("../parser");
2728
const { accountGet } = require("./account");
29+
const { sitesListTemplates } = require("./sites");
2830
const { sdkForConsole } = require("../sdks");
2931

3032
const initResources = async () => {
3133
const actions = {
3234
function: initFunction,
35+
site: initSite,
3336
collection: initCollection,
3437
bucket: initBucket,
3538
team: initTeam,
@@ -318,6 +321,153 @@ const initFunction = async () => {
318321
log("Next you can use 'appwrite run function' to develop a function locally. To deploy the function, use 'appwrite push function'");
319322
}
320323

324+
const initSite = async () => {
325+
process.chdir(localConfig.configDirectoryPath)
326+
327+
const answers = await inquirer.prompt(questionsCreateSite);
328+
const siteFolder = path.join(process.cwd(), 'sites');
329+
330+
if (!fs.existsSync(siteFolder)) {
331+
fs.mkdirSync(siteFolder, {
332+
recursive: true
333+
});
334+
}
335+
336+
const siteId = answers.id === 'unique()' ? ID.unique() : answers.id;
337+
const siteName = answers.name;
338+
const siteDir = path.join(siteFolder, siteName);
339+
const templatesDir = path.join(siteFolder, `${siteId}-templates`);
340+
341+
if (fs.existsSync(siteDir)) {
342+
throw new Error(`( ${siteName} ) already exists in the current directory. Please choose another name.`);
343+
}
344+
345+
let templateDetails;
346+
try {
347+
const response = await sitesListTemplates({
348+
frameworks: [answers.framework.key],
349+
useCases: ['starter'],
350+
limit: 1,
351+
parseOutput: false
352+
});
353+
if (response.total == 0) {
354+
throw new Error(`No starter template found for framework ${answers.framework.key}`);
355+
}
356+
templateDetails = response.templates[0];
357+
} catch (error) {
358+
throw new Error(`Failed to fetch template for framework ${answers.framework.key}: ${error.message}`);
359+
}
360+
361+
fs.mkdirSync(siteDir, "777");
362+
fs.mkdirSync(templatesDir, "777");
363+
const repo = `https://github.com/${templateDetails.providerOwner}/${templateDetails.providerRepositoryId}`;
364+
let selected = { template: templateDetails.frameworks[0].providerRootDirectory };
365+
366+
let gitInitCommands = '';
367+
let gitPullCommands = '';
368+
369+
const sparse = selected.template.startsWith('./') ? selected.template.substring(2) : selected.template;
370+
371+
log('Fetching site code ...');
372+
373+
if(selected.template === './') {
374+
gitInitCommands = `git clone --single-branch --depth 1 ${repo} .`;
375+
} else {
376+
gitInitCommands = `git clone --single-branch --depth 1 --sparse ${repo} .`; // depth prevents fetching older commits reducing the amount fetched
377+
gitPullCommands = `git sparse-checkout add ${sparse}`;
378+
}
379+
380+
/* Force use CMD as powershell does not support && */
381+
if (process.platform === 'win32') {
382+
gitInitCommands = 'cmd /c "' + gitInitCommands + '"';
383+
if(gitPullCommands)
384+
gitPullCommands = 'cmd /c "' + gitPullCommands + '"';
385+
}
386+
387+
/* Execute the child process but do not print any std output */
388+
try {
389+
childProcess.execSync(gitInitCommands, { stdio: 'pipe', cwd: templatesDir });
390+
if(gitPullCommands)
391+
childProcess.execSync(gitPullCommands, { stdio: 'pipe', cwd: templatesDir });
392+
} catch (error) {
393+
/* Specialised errors with recommended actions to take */
394+
if (error.message.includes('error: unknown option')) {
395+
throw new Error(`${error.message} \n\nSuggestion: Try updating your git to the latest version, then trying to run this command again.`)
396+
} else if (error.message.includes('is not recognized as an internal or external command,') || error.message.includes('command not found')) {
397+
throw new Error(`${error.message} \n\nSuggestion: It appears that git is not installed, try installing git then trying to run this command again.`)
398+
} else {
399+
throw error;
400+
}
401+
}
402+
403+
fs.rmSync(path.join(templatesDir, ".git"), { recursive: true });
404+
405+
const copyRecursiveSync = (src, dest) => {
406+
let exists = fs.existsSync(src);
407+
let stats = exists && fs.statSync(src);
408+
let isDirectory = exists && stats.isDirectory();
409+
if (isDirectory) {
410+
if (!fs.existsSync(dest)) {
411+
fs.mkdirSync(dest);
412+
}
413+
414+
fs.readdirSync(src).forEach(function (childItemName) {
415+
copyRecursiveSync(path.join(src, childItemName), path.join(dest, childItemName));
416+
});
417+
} else {
418+
fs.copyFileSync(src, dest);
419+
}
420+
};
421+
copyRecursiveSync(selected.template === './' ? templatesDir : path.join(templatesDir, selected.template), siteDir);
422+
423+
fs.rmSync(templatesDir, { recursive: true, force: true });
424+
425+
const readmePath = path.join(process.cwd(), 'sites', siteName, 'README.md');
426+
const readmeFile = fs.readFileSync(readmePath).toString();
427+
const newReadmeFile = readmeFile.split('\n');
428+
newReadmeFile[0] = `# ${answers.key}`;
429+
newReadmeFile.splice(1, 2);
430+
fs.writeFileSync(readmePath, newReadmeFile.join('\n'));
431+
432+
let data = {
433+
$id: siteId,
434+
name: answers.name,
435+
framework: answers.framework.key,
436+
adapter: templateDetails.frameworks[0].adapter || '',
437+
buildRuntime: templateDetails.frameworks[0].buildRuntime || '',
438+
installCommand: templateDetails.frameworks[0].installCommand || '',
439+
buildCommand: templateDetails.frameworks[0].buildCommand || '',
440+
outputDirectory: templateDetails.frameworks[0].outputDirectory || '',
441+
fallbackFile: templateDetails.frameworks[0].fallbackFile || '',
442+
specification: answers.specification,
443+
enabled: true,
444+
timeout: 30,
445+
logging: true,
446+
ignore: answers.framework.ignore || null,
447+
path: `sites/${siteName}`,
448+
};
449+
450+
if (!data.buildRuntime) {
451+
log(`Build runtime for this framework not found. You will be asked to configure build runtime when you first push the site.`);
452+
}
453+
454+
if (!data.installCommand) {
455+
log(`Installation command for this framework not found. You will be asked to configure the install command when you first push the site.`);
456+
}
457+
458+
if (!data.buildCommand) {
459+
log(`Build command for this framework not found. You will be asked to configure the build command when you first push the site.`);
460+
}
461+
462+
if (!data.outputDirectory) {
463+
log(`Output directory for this framework not found. You will be asked to configure the output directory when you first push the site.`);
464+
}
465+
466+
localConfig.addSite(data);
467+
success("Initializing site");
468+
log("Next you can use 'appwrite push site' to deploy the changes.");
469+
};
470+
321471
const init = new Command("init")
322472
.description(commandDescriptions['init'])
323473
.action(actionRunner(initResources));
@@ -336,6 +486,12 @@ init
336486
.description("Init a new {{ spec.title|caseUcfirst }} function")
337487
.action(actionRunner(initFunction));
338488

489+
init
490+
.command("site")
491+
.alias("sites")
492+
.description("Init a new {{ spec.title|caseUcfirst }} site")
493+
.action(actionRunner(initSite));
494+
339495
init
340496
.command("bucket")
341497
.alias("buckets")

templates/cli/lib/commands/pull.js.twig

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,19 @@ const { messagingListTopics } = require("./messaging");
77
const { teamsList } = require("./teams");
88
const { projectsGet } = require("./projects");
99
const { functionsList, functionsGetDeploymentDownload, functionsListDeployments } = require("./functions");
10+
const { sitesList, sitesGetDeploymentDownload, sitesListDeployments } = require("./sites");
1011
const { databasesGet, databasesListCollections, databasesList } = require("./databases");
1112
const { storageListBuckets } = require("./storage");
1213
const { localConfig } = require("../config");
1314
const { paginate } = require("../paginate");
14-
const { questionsPullCollection, questionsPullFunctions, questionsPullFunctionsCode, questionsPullResources } = require("../questions");
15+
const { questionsPullCollection, questionsPullFunctions, questionsPullFunctionsCode, questionsPullSites, questionsPullSitesCode, questionsPullResources } = require("../questions");
1516
const { cliConfig, success, log, warn, actionRunner, commandDescriptions } = require("../parser");
1617

1718
const pullResources = async () => {
1819
const actions = {
1920
settings: pullSettings,
2021
functions: pullFunctions,
22+
sites: pullSites,
2123
collections: pullCollection,
2224
buckets: pullBucket,
2325
teams: pullTeam,
@@ -169,6 +171,119 @@ const pullFunctions = async ({ code, withVariables }) => {
169171
success(`Successfully pulled ${chalk.bold(total)} functions.`);
170172
}
171173

174+
const pullSites = async ({ code, withVariables }) => {
175+
process.chdir(localConfig.configDirectoryPath)
176+
177+
log("Fetching sites ...");
178+
let total = 0;
179+
180+
const fetchResponse = await sitesList({
181+
queries: [JSON.stringify({ method: 'limit', values: [1] })],
182+
parseOutput: false
183+
});
184+
if (fetchResponse["sites"].length <= 0) {
185+
log("No sites found.");
186+
success(`Successfully pulled ${chalk.bold(total)} sites.`);
187+
return;
188+
}
189+
190+
const sites = cliConfig.all
191+
? (await paginate(sitesList, { parseOutput: false }, 100, 'sites')).sites
192+
: (await inquirer.prompt(questionsPullSites)).sites;
193+
194+
let allowCodePull = cliConfig.force === true ? true : null;
195+
196+
for (let site of sites) {
197+
total++;
198+
log(`Pulling site ${chalk.bold(site['name'])} ...`);
199+
200+
const localSite = localConfig.getSite(site.$id);
201+
202+
site['path'] = localSite['path'];
203+
if (!localSite['path']) {
204+
site['path'] = `sites/${site.name}`;
205+
}
206+
const holdingVars = site['vars'];
207+
// We don't save var in to the config
208+
delete site['vars'];
209+
localConfig.addSite(site);
210+
211+
if (!fs.existsSync(site['path'])) {
212+
fs.mkdirSync(site['path'], { recursive: true });
213+
}
214+
215+
if (code === false) {
216+
warn("Source code download skipped.");
217+
continue;
218+
}
219+
220+
if (allowCodePull === null) {
221+
const codeAnswer = await inquirer.prompt(questionsPullSitesCode);
222+
allowCodePull = codeAnswer.override;
223+
}
224+
225+
if (!allowCodePull) {
226+
continue;
227+
}
228+
229+
let deploymentId = null;
230+
231+
try {
232+
const fetchResponse = await sitesListDeployments({
233+
siteId: site['$id'],
234+
queries: [
235+
JSON.stringify({ method: 'limit', values: [1] }),
236+
JSON.stringify({ method: 'orderDesc', values: ['$id'] })
237+
],
238+
parseOutput: false
239+
});
240+
241+
if (fetchResponse['total'] > 0) {
242+
deploymentId = fetchResponse['deployments'][0]['$id'];
243+
}
244+
245+
} catch {
246+
}
247+
248+
if (deploymentId === null) {
249+
log("Source code download skipped because site doesn't have any available deployment");
250+
continue;
251+
}
252+
253+
log("Pulling latest deployment code ...");
254+
255+
const compressedFileName = `${site['$id']}-${+new Date()}.tar.gz`
256+
await sitesGetDeploymentDownload({
257+
siteId: site['$id'],
258+
deploymentId,
259+
destination: compressedFileName,
260+
overrideForCli: true,
261+
parseOutput: false
262+
});
263+
264+
tar.extract({
265+
sync: true,
266+
cwd: site['path'],
267+
file: compressedFileName,
268+
strict: false,
269+
});
270+
271+
fs.rmSync(compressedFileName);
272+
273+
if (withVariables) {
274+
const envFileLocation = `${site['path']}/.env`
275+
try {
276+
fs.rmSync(envFileLocation);
277+
} catch {
278+
}
279+
280+
fs.writeFileSync(envFileLocation, holdingVars.map(r => `${r.key}=${r.value}\n`).join(''))
281+
}
282+
}
283+
284+
success(`Successfully pulled ${chalk.bold(total)} sites.`);
285+
}
286+
172287
const pullCollection = async () => {
173288
log("Fetching collections ...");
174289
let total = 0;
@@ -321,6 +436,14 @@ pull
321436
.option("--with-variables", `Pull function variables. ${chalk.red('recommend for testing purposes only')}`)
322437
.action(actionRunner(pullFunctions))
323438

439+
pull
440+
.command("site")
441+
.alias("sites")
442+
.description("Pull your {{ spec.title|caseUcfirst }} site")
443+
.option("--no-code", "Don't pull the site's code")
444+
.option("--with-variables", `Pull site variables. ${chalk.red('recommend for testing purposes only')}`)
445+
.action(actionRunner(pullSites))
446+
324447
pull
325448
.command("collection")
326449
.alias("collections")

0 commit comments

Comments
 (0)