Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add generator for virtual piece loader #274

Merged
merged 13 commits into from
Nov 20, 2023
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
},
"dependencies": {
"@favware/colorette-spinner": "^1.0.1",
"@sapphire/node-utilities": "^1.0.0",
"@sapphire/result": "^2.6.4",
"colorette": "^2.0.20",
"commander": "^11.1.0",
Expand Down
7 changes: 7 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env node

import generateCmd from '#commands/generate';
import generateLoaderCmd from '#commands/generate-loader';
import initCmd from '#commands/init';
import newCmd from '#commands/new';
import { createColors } from 'colorette';
Expand Down Expand Up @@ -35,6 +36,12 @@ sapphire
.argument('<name>', 'file name')
.action(generateCmd);

sapphire //
.command('generate-loader')
.description('generates a piece loader')
.alias('gl')
.action(generateLoaderCmd);

sapphire //
.command('init')
.description('creates a config file on an existing Sapphire project')
Expand Down
32 changes: 32 additions & 0 deletions src/commands/generate-loader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { locationReplacement } from '#constants';
import { CreateComponentLoaders } from '#functions/CreateComponentLoader';
import { generateCommandFlow } from '#functions/generateCommandFlow';
import type { Config } from '#lib/types';
import { join } from 'node:path';

/**
* Generates loaders based on the Sapphire CLI config.
* @returns A promise that resolves when the loaders are created.
*/
export default async (): Promise<void> => {
return generateCommandFlow('Creating loaders...', (config, configLocation) => createLoader(config, configLocation));
};

/**
* Creates a loader component based on the provided configuration.
* @param config - The configuration object.
* @param configLoc - The location of the configuration file.
* @returns A promise that resolves to the created loader component.
* @throws An error if the 'projectLanguage' field is missing in the configuration file or if a template file for the loader component cannot be found.
*/
export async function createLoader(config: Config, configLoc: string) {
const { projectLanguage } = config;

if (!projectLanguage) {
throw new Error("There is no 'projectLanguage' field in .sapphirerc.json");
}

const targetDir = join(configLoc, config.locations.base, locationReplacement);

return CreateComponentLoaders(targetDir, config);
}
95 changes: 35 additions & 60 deletions src/commands/generate.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,43 @@
import { componentsFolder } from '#constants';
import { componentsFolder, locationReplacement } from '#constants';
import { CreateFileFromTemplate } from '#functions/CreateFileFromTemplate';
import { fileExists } from '#functions/FileExists';
import { generateCommandFlow } from '#functions/generateCommandFlow';
import { commandNames, componentCommandNames, componentInteractionHandlerNames, interactionHandlerNames } from '#lib/aliases';
import type { Config } from '#lib/types';
import { Spinner } from '@favware/colorette-spinner';
import { Result } from '@sapphire/result';
import { blueBright, red } from 'colorette';
import findUp from 'find-up';
import { load } from 'js-yaml';
import { readFile } from 'node:fs/promises';
import { basename, join } from 'node:path';
import { setTimeout as sleep } from 'node:timers/promises';

/**
* Generates a component based on the given component type and name.
* @param component The type of component to generate.
* @param name The name of the component.
* @returns A Promise that resolves when the component generation is complete.
*/
export default async (component: string, name: string): Promise<void> => {
return generateCommandFlow('Creating loaders...', (config, configLocation) => createComponent(component, name, config, configLocation));
};

/**
* Joins an array of component names into a single string.
*
* @param components - An array of component names.
* @returns The joined string with component names.
*/
function joinComponentNames(components: string[]): string {
const lastComponent = components.pop();
return `"${components.join('", "')}" or "${lastComponent}"`;
}

/**
* Creates a component based on the specified parameters.
*
* @param component - The type of component to create.
* @param name - The name of the component.
* @param config - The configuration object.
* @param configLoc - The location of the configuration file.
* @returns A Promise that resolves when the component is created.
* @throws An error if the 'projectLanguage' field is missing in the configuration file,
* or if a template file for the component type cannot be found.
*/
async function createComponent(component: string, name: string, config: Config, configLoc: string) {
const { projectLanguage } = config;

Expand All @@ -23,7 +49,7 @@ async function createComponent(component: string, name: string, config: Config,

const corePath = `${componentsFolder}${template}`;
const userPath = config.customFileTemplates.enabled ? join(configLoc, config.customFileTemplates.location, template) : null;
const target = join(configLoc, config.locations.base, '%L%', `${name}.${projectLanguage}`);
const target = join(configLoc, config.locations.base, locationReplacement, `${name}.${projectLanguage}`);
const params = { name: basename(name) };

if (userPath && (await fileExists(userPath))) {
Expand All @@ -35,21 +61,6 @@ async function createComponent(component: string, name: string, config: Config,
throw new Error(`Couldn't find a template file for that component type.${parseCommonHints(component)}`);
}

async function fetchConfig() {
const configFileAsJson = await Promise.race([findUp('.sapphirerc.json', { cwd: '.' }), sleep(5000)]);

if (configFileAsJson) {
return configFileAsJson;
}

return Promise.race([findUp('.sapphirerc.yml', { cwd: '.' }), sleep(5000)]);
}

function joinComponentNames(components: string[]): string {
const lastComponent = components.pop();
return `"${components.join('", "')}" or "${lastComponent}"`;
}

/**
* Parses common hints for the user
* @param component Component name
Expand All @@ -70,39 +81,3 @@ function parseCommonHints(component: string): string {

return '';
}

export default async (component: string, name: string) => {
const spinner = new Spinner(`Creating a ${component.toLowerCase()}`).start();

const fail = (error: string, additionalExecution?: () => void) => {
spinner.error({ text: error });
additionalExecution?.();
process.exit(1);
};

const configLoc = await fetchConfig();

if (!configLoc) {
return fail("Can't find the Sapphire CLI config.");
}

const config: Config = configLoc.endsWith('json') ? JSON.parse(await readFile(configLoc, 'utf8')) : load(await readFile(configLoc, 'utf8'));

if (!config) {
return fail("Can't parse the Sapphire CLI config.");
}

const result = await Result.fromAsync<boolean, Error>(() =>
createComponent(component, name, config, configLoc.replace(/.sapphirerc.(json|yml)/g, ''))
);

return result.match({
ok: () => {
spinner.success();

console.log(blueBright('Done!'));
process.exit(0);
},
err: (error) => fail(error.message, () => console.log(red(error.message)))
});
};
6 changes: 5 additions & 1 deletion src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import { dump } from 'js-yaml';
import { writeFile } from 'node:fs/promises';
import prompts from 'prompts';

export default async () => {
/**
* Initializes the project by prompting the user for configuration options and generating a configuration file.
* @returns A promise that resolves when the initialization is complete.
*/
export default async (): Promise<void> => {
const packageJson = await findUp('package.json');

if (!packageJson) {
Expand Down
Loading