Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
372 changes: 372 additions & 0 deletions src/commands/blocks/add.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,372 @@
import axios from 'axios';
import * as fs from 'fs';
import * as glob from 'glob';
import { GluegunCommand, filesystem } from 'gluegun';
import * as path from 'path';

import { ExtendedGluegunToolbox } from '../../interfaces/extended-gluegun-toolbox';

const AddComponentCommand: GluegunCommand = {
description: 'Adds a specific block to another Nuxt project',
name: 'add',
run: async (toolbox: ExtendedGluegunToolbox) => {
const { parameters } = toolbox;
const blockName = parameters.first;
await addBlock(toolbox, blockName);
process.exit();
return 'add';
},
};

async function getConfigForBlock(fileName: string, toolbox: ExtendedGluegunToolbox) {
const { print } = toolbox;
const configSpinner = print.spin('Checking the config for block...');

const data = await getConfig();
const name = fileName.split('.').slice(0, -1).join('.');
const rootName = name.split('/')[0];

configSpinner.succeed(`Config for ${rootName} loaded successfully`);
return data.config[rootName] || {};
}

async function getConfig() {
const githubApiUrl = 'https://raw.githubusercontent.com/lenneTech/nuxt-base-components/main/config.json';
const response = await axios.get(githubApiUrl);

if (response.status === 200) {
return response.data;
} else {
throw new Error(`Error when retrieving the configuration from GitHub: ${response.statusText}`);
}
}

async function processConfig(config: any, toolbox: ExtendedGluegunToolbox) {
if (config?.npm) {
const npmPackages = config.npm;
for (const npmPackage of npmPackages) {
await installPackage(npmPackage, toolbox);
}
}

if (config?.composables) {
const composables = config.composables;
for (const composable of composables) {
await copyComposable(composable, toolbox);
}
}

if (config?.components) {
const components = config.components;
for (const component of components) {
if (component.endsWith('/*')) {
const folderName = component.split('/')[0];
const directoryFiles = await getComponentsInfo(folderName);

for (const file of directoryFiles) {
await copyComponent({ name: `${folderName}/${file.name}`, type: 'dir' }, toolbox);
}
} else {
await copyComponent({ name: `${component}.vue`, type: 'file' }, toolbox);
}
}
}
}

async function installPackage(packageName: string, toolbox: ExtendedGluegunToolbox) {
const { print, prompt, system } = toolbox;

const nameWithoutVersion = packageName.split('@')[0] || packageName;
const packageJsonPath = path.resolve(process.cwd(), 'package.json');
const packageJson = filesystem.read(packageJsonPath, 'json');
const isInstalled
= (packageJson.dependencies && packageJson.dependencies[nameWithoutVersion])
|| (packageJson.devDependencies && packageJson.devDependencies[nameWithoutVersion]);

if (!isInstalled) {
const confirm = await prompt.confirm(`The npm package ${nameWithoutVersion} is required. Would you like to install it?`);

if (!confirm) {
return;
}

const installSpinner = print.spin(`Install npm package ${nameWithoutVersion}...`);
await system.run(`npm install ${packageName} --save-exact`);
installSpinner.succeed(`npm package ${nameWithoutVersion} successfully installed`);
} else {
print.info(`npm package ${nameWithoutVersion} is already installed`);
}
}

async function copyComposable(composable: string, toolbox: ExtendedGluegunToolbox) {
const { print, prompt } = toolbox;
const apiUrl = `https://raw.githubusercontent.com/lenneTech/nuxt-base-components/main/composables/${composable}.ts`;
const response = await axios.get(apiUrl);

if (response.status === 200) {
const sourceCode = response.data;
const cwd = process.cwd();
let targetDirectory: string;

if (fs.existsSync(path.resolve(cwd, 'composables'))) {
targetDirectory = path.resolve(cwd, 'composables');
} else {
const directories = glob.sync('*/composables', { cwd });

if (directories.length > 0) {
targetDirectory = path.join(cwd, directories[0]);
} else {
targetDirectory = cwd;
}
}

// check composable already exists
if (fs.existsSync(path.join(targetDirectory, `${composable}.ts`))) {
print.info(`The composable ${composable} already exists`);
return;
}

const confirm = await prompt.confirm(`The composable ${composable} is required. Would you like to add it?`);

if (!confirm) {
return;
}

const targetPath = path.join(targetDirectory, `${composable}.ts`);
const spinner = print.spin(`Copy the composable ${composable} to ${targetPath}...`);
fs.writeFileSync(targetPath, sourceCode);
spinner.succeed(`The composable ${composable} was successfully copied to ${targetPath}`);
} else {
print.error(`Error retrieving the file from GitHub: ${response.statusText}`);
}
}

async function getComponentsInfo(path?: string): Promise<{ name: string; type: 'dir' | 'file' }[]> {
const githubApiUrl = `https://api.github.com/repos/lenneTech/nuxt-base-components/contents/components${path ? `/${path}` : ''}`;
const response = await axios.get(githubApiUrl);

if (response.status === 200) {
return response.data.map((file: any) => {
return {
name: file.name,
type: file.type,
};
});
} else {
throw new Error(`Error when retrieving the file list from GitHub: ${response.statusText}`);
}
}

async function getBlockInfo(path?: string): Promise<{ name: string; type: 'dir' | 'file' }[]> {
const githubApiUrl = `https://api.github.com/repos/lenneTech/nuxt-base-components/contents/blocks${path ? `/${path}` : ''}`;
const response = await axios.get(githubApiUrl);

if (response.status === 200) {
return response.data.map((file: any) => {
return {
name: file.name,
type: file.type,
};
});
} else {
throw new Error(`Error when retrieving the file list from GitHub: ${response.statusText}`);
}
}

async function addBlock(toolbox: ExtendedGluegunToolbox, blockName: string | undefined) {
const { print, prompt } = toolbox;

try {
const compSpinner = print.spin('Load block selection from GitHub...');
const possibleBlocks = await getBlockInfo();
compSpinner.succeed('Blocks selection successfully loaded from GitHub');

if (possibleBlocks.length > 0) {
let selectedBlock: string = '';

if (!blockName) {
const response = await prompt.ask({
choices: possibleBlocks,
message: 'Which block would you like to add?',
name: 'blockType',
type: 'select',
});
selectedBlock = response.blockType;
} else {
const foundComponent = possibleBlocks.find(e => e.name.toLowerCase() === `${blockName.toLowerCase()}.vue` || e.name.toLowerCase() === blockName.toLowerCase());
selectedBlock = foundComponent.name;
}

const selectedFile = possibleBlocks.find(e => e.name.toLowerCase() === selectedBlock.toLowerCase());
if (selectedFile?.type === 'dir') {
print.success(`The directory ${selectedFile.name} has been selected.`);
const directoryFiles = await getBlockInfo(selectedFile.name);

if (directoryFiles.length > 0) {
for (const file of directoryFiles) {
await copyBlock({
name: `${selectedFile.name}/${file.name}`,
type: 'dir',
}, toolbox);
}
print.success(`All files from the directory ${selectedFile.name} have been successfully copied.`);
} else {
print.error(`The directory ${selectedFile.name} is empty.`);
}
} else if (selectedFile?.type === 'file') {
print.success(`The block ${selectedFile.name} was selected.`);
await copyBlock(selectedFile, toolbox);
}
} else {
print.error('No block found on GitHub.');
}
} catch (error) {
print.error(`Error when adding/selecting the block: ${error.message}`);
}
}

async function copyBlock(file: { name: string; type: 'dir' | 'file' }, toolbox: ExtendedGluegunToolbox) {
const { print } = toolbox;
const apiUrl = `https://raw.githubusercontent.com/lenneTech/nuxt-base-components/main/blocks/${file.name}`;

return new Promise(async (resolve, reject) => {
try {
const config = await getConfigForBlock(file.name, toolbox);

if (config) {
await processConfig(config, toolbox);
}

const compSpinner = print.spin(`Load block ${file.name} from GitHub...`);
const response = await axios.get(apiUrl);
compSpinner.succeed(`Block ${file.name} successfully loaded from GitHub`);

if (response.status === 200) {
const sourceCode = response.data;
const cwd = process.cwd();
let targetDirectory: string;

if (fs.existsSync(path.resolve(cwd, 'pages'))) {
targetDirectory = path.resolve(cwd, 'pages');
} else {
const directories = glob.sync('*/pages', { cwd });

if (directories.length > 0) {
targetDirectory = path.join(cwd, directories[0]);
} else {
targetDirectory = cwd;
}
}

const targetName = file.name.replace(/([a-z])([A-Z])/g, '$1-$2')
.toLowerCase()
.replace(/^block-/, '');
const targetPath = path.join(targetDirectory, `${targetName}`);

// check if block already exists
if (fs.existsSync(targetPath)) {
print.info(`The block ${file.name} already exists`);
resolve(targetPath);
return;
}

if (!fs.existsSync(targetDirectory)) {
const targetDirSpinner = print.spin('Creating the target directory...');
fs.mkdirSync(targetDirectory, { recursive: true });
targetDirSpinner.succeed();
}

if (file.type === 'dir') {
const dirName = file.name.split('/')[0];
const dirPath = path.join(targetDirectory, dirName);
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
}

const spinner = print.spin(`Copy the block ${targetName} to ${targetPath}...`);
fs.writeFileSync(targetPath, sourceCode);
spinner.succeed(`The block ${targetName} was successfully copied to ${targetPath}`);
resolve(targetPath);
} else {
print.error(`Error retrieving the file from GitHub: ${response.statusText}`);
reject(response.statusText);
}
} catch (error) {
print.error(`Error when copying the block ${file.name}: ${error.message}`);
reject(error);
}
});
}

async function copyComponent(file: { name: string; type: 'dir' | 'file' }, toolbox: ExtendedGluegunToolbox) {
const { print } = toolbox;
const apiUrl = `https://raw.githubusercontent.com/lenneTech/nuxt-base-components/main/components/${file.name}`;

return new Promise(async (resolve, reject) => {
try {
const config = await getConfigForBlock(file.name, toolbox);

if (config) {
await processConfig(config, toolbox);
}

const compSpinner = print.spin(`Load component ${file.name} from GitHub...`);
const response = await axios.get(apiUrl);
compSpinner.succeed(`Component ${file.name} successfully loaded from GitHub`);

if (response.status === 200) {
const sourceCode = response.data;
const cwd = process.cwd();
let targetDirectory: string;

if (fs.existsSync(path.resolve(cwd, 'components'))) {
targetDirectory = path.resolve(cwd, 'components');
} else {
const directories = glob.sync('*/components', { cwd });

if (directories.length > 0) {
targetDirectory = path.join(cwd, directories[0]);
} else {
targetDirectory = cwd;
}
}

const targetPath = path.join(targetDirectory, `${file.name}`);

if (fs.existsSync(targetPath)) {
print.info(`The component ${file.name} already exists`);
resolve(targetPath);
return;
}

if (!fs.existsSync(targetDirectory)) {
const targetDirSpinner = print.spin('Creating the target directory...');
fs.mkdirSync(targetDirectory, { recursive: true });
targetDirSpinner.succeed();
}

if (file.type === 'dir' || file.name.split('/').length > 1) {
const dirName = file.name.split('/')[0];
const dirPath = path.join(targetDirectory, dirName);
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
}

const spinner = print.spin(`Copy the component ${file.name} to ${targetPath}...`);
fs.writeFileSync(targetPath, sourceCode);
spinner.succeed(`The component ${file.name} was successfully copied to ${targetPath}`);
resolve(targetPath);
} else {
print.error(`Error retrieving the file from GitHub: ${response.statusText}`);
reject(response.statusText);
}
} catch (error) {
print.error(`Error when copying the component ${file.name}: ${error.message}`);
reject(error);
}
});
}

export default AddComponentCommand;
15 changes: 15 additions & 0 deletions src/commands/blocks/blocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ExtendedGluegunToolbox } from '../../interfaces/extended-gluegun-toolbox';

/**
* Blocks commands
*/
module.exports = {
alias: ['n'],
description: 'Base blocks for Nuxt',
hidden: true,
name: 'blocks',
run: async (toolbox: ExtendedGluegunToolbox) => {
await toolbox.helper.showMenu('blocks');
return 'blocks';
},
};
2 changes: 1 addition & 1 deletion src/commands/components/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ async function copyComponent(file: { name: string; type: 'dir' | 'file' }, toolb
targetDirSpinner.succeed();
}

if (file.type === 'dir') {
if (file.type === 'dir' || file.name.split('/').length > 1) {
const dirName = file.name.split('/')[0];
const dirPath = path.join(targetDirectory, dirName);
if (!fs.existsSync(dirPath)) {
Expand Down