@sapphire/cli 1.9.0-pr-274.95d19e9.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +1 -0
- package/dist/cli.js +6 -0
- package/dist/commands/generate-loader.js +20 -52
- package/dist/commands/generate.js +33 -45
- package/dist/commands/init.js +4 -0
- package/dist/commands/new.js +99 -55
- package/dist/constants.js +1 -0
- package/dist/functions/CreateComponentLoader.js +65 -53
- package/dist/functions/CreateFileFromTemplate.js +4 -4
- package/dist/functions/fetchConfig.js +17 -0
- package/dist/functions/generateCommandFlow.js +49 -0
- package/dist/prompts/PromptNew.js +2 -2
- package/package.json +11 -10
- package/templates/.sapphirerc.json.sapphire +1 -0
- package/templates/.sapphirerc.yml.sapphire +1 -0
- package/templates/schemas/.sapphirerc.scheme.json +3 -2
- package/templates/components/_load.js.sapphire +0 -3
- package/templates/components/_load.ts.sapphire +0 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
# [1.9.0](https://github.com/sapphiredev/cli/compare/v1.9.0...v1.9.0) - (2023-12-03)
|
|
6
|
+
|
|
7
|
+
## 🐛 Bug Fixes
|
|
8
|
+
|
|
9
|
+
- Specify schema for both template schema files ([30a80bb](https://github.com/sapphiredev/cli/commit/30a80bb561f61b8fba78c8cfc4157ca579e73f7c))
|
|
10
|
+
- **json schema:** Specify enum for projectLanguage ([2f1ba0d](https://github.com/sapphiredev/cli/commit/2f1ba0d7fb4597c82581601ee60391898a3b100f))
|
|
11
|
+
- **json schema:** Fixed base schema url ([bd37a6c](https://github.com/sapphiredev/cli/commit/bd37a6c5b590de90c2a1efa7cb19f16f38403273))
|
|
12
|
+
- Install yarn v4, not v3 ([938ca99](https://github.com/sapphiredev/cli/commit/938ca99fe6dedea04978ca1ac38ac9a4505f0651))
|
|
13
|
+
|
|
14
|
+
## 📝 Documentation
|
|
15
|
+
|
|
16
|
+
- **readme:** Update readme to mention virtual components ([23aa8c5](https://github.com/sapphiredev/cli/commit/23aa8c5e6b0f07e4b8028c21ecb243e81d845e03))
|
|
17
|
+
|
|
18
|
+
## 🚀 Features
|
|
19
|
+
|
|
20
|
+
- Add generator for virtual piece loader (#274) ([f9fa2a3](https://github.com/sapphiredev/cli/commit/f9fa2a359c9a1bdb3b512a7484e0da12e20f6c8b))
|
|
21
|
+
|
|
5
22
|
# [1.8.0](https://github.com/sapphiredev/cli/compare/v1.8.0...v1.8.0) - (2023-11-16)
|
|
6
23
|
|
|
7
24
|
## 🚀 Features
|
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
- Generate Sapphire projects easily
|
|
18
18
|
- Generate components (commands, listeners, etc.)
|
|
19
19
|
- Create your own templates for components
|
|
20
|
+
- Generate loaders for [virtual components](https://www.sapphirejs.dev/docs/Guide/additional-information/registering-virtual-pieces)
|
|
20
21
|
|
|
21
22
|
## [Get Started with the CLI](https://www.sapphirejs.dev/docs/Guide/CLI/introduction)
|
|
22
23
|
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import generateCmd from '#commands/generate';
|
|
3
|
+
import generateLoaderCmd from '#commands/generate-loader';
|
|
3
4
|
import initCmd from '#commands/init';
|
|
4
5
|
import newCmd from '#commands/new';
|
|
5
6
|
import { createColors } from 'colorette';
|
|
@@ -27,6 +28,11 @@ sapphire
|
|
|
27
28
|
.argument('<component>', 'component/piece name')
|
|
28
29
|
.argument('<name>', 'file name')
|
|
29
30
|
.action(generateCmd);
|
|
31
|
+
sapphire //
|
|
32
|
+
.command('generate-loader')
|
|
33
|
+
.description('generates a piece loader')
|
|
34
|
+
.alias('gl')
|
|
35
|
+
.action(generateLoaderCmd);
|
|
30
36
|
sapphire //
|
|
31
37
|
.command('init')
|
|
32
38
|
.description('creates a config file on an existing Sapphire project')
|
|
@@ -1,59 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { Result } from '@sapphire/result';
|
|
5
|
-
import { blueBright, red } from 'colorette';
|
|
6
|
-
import findUp from 'find-up';
|
|
7
|
-
import { load } from 'js-yaml';
|
|
8
|
-
import { readFile } from 'node:fs/promises';
|
|
1
|
+
import { locationReplacement } from '#constants';
|
|
2
|
+
import { CreateComponentLoaders } from '#functions/CreateComponentLoader';
|
|
3
|
+
import { generateCommandFlow } from '#functions/generateCommandFlow';
|
|
9
4
|
import { join } from 'node:path';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Generates loaders based on the Sapphire CLI config.
|
|
7
|
+
* @returns A promise that resolves when the loaders are created.
|
|
8
|
+
*/
|
|
9
|
+
export default async () => {
|
|
10
|
+
return generateCommandFlow('Creating loaders...', (config, configLocation) => createLoader(config, configLocation));
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Creates a loader component based on the provided configuration.
|
|
14
|
+
* @param config - The configuration object.
|
|
15
|
+
* @param configLoc - The location of the configuration file.
|
|
16
|
+
* @returns A promise that resolves to the created loader component.
|
|
17
|
+
* @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.
|
|
18
|
+
*/
|
|
19
|
+
export async function createLoader(config, configLoc) {
|
|
15
20
|
const { projectLanguage } = config;
|
|
16
21
|
if (!projectLanguage) {
|
|
17
22
|
throw new Error("There is no 'projectLanguage' field in .sapphirerc.json");
|
|
18
23
|
}
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
const target = join(configLoc, config.locations.base, '%L%', `${name}.${projectLanguage}`);
|
|
22
|
-
if (await fileExists(corePath)) {
|
|
23
|
-
return CreateComponentLoaders(target, config);
|
|
24
|
-
}
|
|
25
|
-
throw new Error(`Couldn't find a template file for loader component.`);
|
|
26
|
-
}
|
|
27
|
-
async function fetchConfig() {
|
|
28
|
-
const configFileAsJson = await Promise.race([findUp('.sapphirerc.json', { cwd: '.' }), sleep(5000)]);
|
|
29
|
-
if (configFileAsJson) {
|
|
30
|
-
return configFileAsJson;
|
|
31
|
-
}
|
|
32
|
-
return Promise.race([findUp('.sapphirerc.yml', { cwd: '.' }), sleep(5000)]);
|
|
24
|
+
const targetDir = join(configLoc, config.locations.base, locationReplacement);
|
|
25
|
+
return CreateComponentLoaders(targetDir, config);
|
|
33
26
|
}
|
|
34
|
-
export default async () => {
|
|
35
|
-
const spinner = new Spinner(`Creating loaders...`).start();
|
|
36
|
-
const fail = (error, additionalExecution) => {
|
|
37
|
-
spinner.error({ text: error });
|
|
38
|
-
additionalExecution?.();
|
|
39
|
-
process.exit(1);
|
|
40
|
-
};
|
|
41
|
-
const configLoc = await fetchConfig();
|
|
42
|
-
if (!configLoc) {
|
|
43
|
-
return fail("Can't find the Sapphire CLI config.");
|
|
44
|
-
}
|
|
45
|
-
const config = configLoc.endsWith('json') ? JSON.parse(await readFile(configLoc, 'utf8')) : load(await readFile(configLoc, 'utf8'));
|
|
46
|
-
if (!config) {
|
|
47
|
-
return fail("Can't parse the Sapphire CLI config.");
|
|
48
|
-
}
|
|
49
|
-
const result = await Result.fromAsync(() => createLoader(config, configLoc.replace(/.sapphirerc.(json|yml)/g, '')));
|
|
50
|
-
return result.match({
|
|
51
|
-
ok: () => {
|
|
52
|
-
spinner.success();
|
|
53
|
-
console.log(blueBright('Done!'));
|
|
54
|
-
process.exit(0);
|
|
55
|
-
},
|
|
56
|
-
err: (error) => fail(error.message, () => console.log(red(error.message)))
|
|
57
|
-
});
|
|
58
|
-
};
|
|
59
27
|
//# sourceMappingURL=generate-loader.js.map
|
|
@@ -1,15 +1,39 @@
|
|
|
1
|
-
import { componentsFolder } from '#constants';
|
|
1
|
+
import { componentsFolder, locationReplacement } from '#constants';
|
|
2
2
|
import { CreateFileFromTemplate } from '#functions/CreateFileFromTemplate';
|
|
3
3
|
import { fileExists } from '#functions/FileExists';
|
|
4
|
+
import { generateCommandFlow } from '#functions/generateCommandFlow';
|
|
4
5
|
import { commandNames, componentCommandNames, componentInteractionHandlerNames, interactionHandlerNames } from '#lib/aliases';
|
|
5
|
-
import { Spinner } from '@favware/colorette-spinner';
|
|
6
|
-
import { Result } from '@sapphire/result';
|
|
7
|
-
import { blueBright, red } from 'colorette';
|
|
8
|
-
import findUp from 'find-up';
|
|
9
|
-
import { load } from 'js-yaml';
|
|
10
|
-
import { readFile } from 'node:fs/promises';
|
|
11
6
|
import { basename, join } from 'node:path';
|
|
12
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Generates a component based on the given component type and name.
|
|
9
|
+
* @param component The type of component to generate.
|
|
10
|
+
* @param name The name of the component.
|
|
11
|
+
* @returns A Promise that resolves when the component generation is complete.
|
|
12
|
+
*/
|
|
13
|
+
export default async (component, name) => {
|
|
14
|
+
return generateCommandFlow('Creating loaders...', (config, configLocation) => createComponent(component, name, config, configLocation));
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Joins an array of component names into a single string.
|
|
18
|
+
*
|
|
19
|
+
* @param components - An array of component names.
|
|
20
|
+
* @returns The joined string with component names.
|
|
21
|
+
*/
|
|
22
|
+
function joinComponentNames(components) {
|
|
23
|
+
const lastComponent = components.pop();
|
|
24
|
+
return `"${components.join('", "')}" or "${lastComponent}"`;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Creates a component based on the specified parameters.
|
|
28
|
+
*
|
|
29
|
+
* @param component - The type of component to create.
|
|
30
|
+
* @param name - The name of the component.
|
|
31
|
+
* @param config - The configuration object.
|
|
32
|
+
* @param configLoc - The location of the configuration file.
|
|
33
|
+
* @returns A Promise that resolves when the component is created.
|
|
34
|
+
* @throws An error if the 'projectLanguage' field is missing in the configuration file,
|
|
35
|
+
* or if a template file for the component type cannot be found.
|
|
36
|
+
*/
|
|
13
37
|
async function createComponent(component, name, config, configLoc) {
|
|
14
38
|
const { projectLanguage } = config;
|
|
15
39
|
if (!projectLanguage) {
|
|
@@ -18,7 +42,7 @@ async function createComponent(component, name, config, configLoc) {
|
|
|
18
42
|
const template = `${component.toLowerCase()}.${projectLanguage}.sapphire`;
|
|
19
43
|
const corePath = `${componentsFolder}${template}`;
|
|
20
44
|
const userPath = config.customFileTemplates.enabled ? join(configLoc, config.customFileTemplates.location, template) : null;
|
|
21
|
-
const target = join(configLoc, config.locations.base,
|
|
45
|
+
const target = join(configLoc, config.locations.base, locationReplacement, `${name}.${projectLanguage}`);
|
|
22
46
|
const params = { name: basename(name) };
|
|
23
47
|
if (userPath && (await fileExists(userPath))) {
|
|
24
48
|
return CreateFileFromTemplate(userPath, target, config, params, true, true);
|
|
@@ -28,17 +52,6 @@ async function createComponent(component, name, config, configLoc) {
|
|
|
28
52
|
}
|
|
29
53
|
throw new Error(`Couldn't find a template file for that component type.${parseCommonHints(component)}`);
|
|
30
54
|
}
|
|
31
|
-
async function fetchConfig() {
|
|
32
|
-
const configFileAsJson = await Promise.race([findUp('.sapphirerc.json', { cwd: '.' }), sleep(5000)]);
|
|
33
|
-
if (configFileAsJson) {
|
|
34
|
-
return configFileAsJson;
|
|
35
|
-
}
|
|
36
|
-
return Promise.race([findUp('.sapphirerc.yml', { cwd: '.' }), sleep(5000)]);
|
|
37
|
-
}
|
|
38
|
-
function joinComponentNames(components) {
|
|
39
|
-
const lastComponent = components.pop();
|
|
40
|
-
return `"${components.join('", "')}" or "${lastComponent}"`;
|
|
41
|
-
}
|
|
42
55
|
/**
|
|
43
56
|
* Parses common hints for the user
|
|
44
57
|
* @param component Component name
|
|
@@ -56,29 +69,4 @@ function parseCommonHints(component) {
|
|
|
56
69
|
}
|
|
57
70
|
return '';
|
|
58
71
|
}
|
|
59
|
-
export default async (component, name) => {
|
|
60
|
-
const spinner = new Spinner(`Creating a ${component.toLowerCase()}`).start();
|
|
61
|
-
const fail = (error, additionalExecution) => {
|
|
62
|
-
spinner.error({ text: error });
|
|
63
|
-
additionalExecution?.();
|
|
64
|
-
process.exit(1);
|
|
65
|
-
};
|
|
66
|
-
const configLoc = await fetchConfig();
|
|
67
|
-
if (!configLoc) {
|
|
68
|
-
return fail("Can't find the Sapphire CLI config.");
|
|
69
|
-
}
|
|
70
|
-
const config = configLoc.endsWith('json') ? JSON.parse(await readFile(configLoc, 'utf8')) : load(await readFile(configLoc, 'utf8'));
|
|
71
|
-
if (!config) {
|
|
72
|
-
return fail("Can't parse the Sapphire CLI config.");
|
|
73
|
-
}
|
|
74
|
-
const result = await Result.fromAsync(() => createComponent(component, name, config, configLoc.replace(/.sapphirerc.(json|yml)/g, '')));
|
|
75
|
-
return result.match({
|
|
76
|
-
ok: () => {
|
|
77
|
-
spinner.success();
|
|
78
|
-
console.log(blueBright('Done!'));
|
|
79
|
-
process.exit(0);
|
|
80
|
-
},
|
|
81
|
-
err: (error) => fail(error.message, () => console.log(red(error.message)))
|
|
82
|
-
});
|
|
83
|
-
};
|
|
84
72
|
//# sourceMappingURL=generate.js.map
|
package/dist/commands/init.js
CHANGED
|
@@ -4,6 +4,10 @@ import findUp from 'find-up';
|
|
|
4
4
|
import { dump } from 'js-yaml';
|
|
5
5
|
import { writeFile } from 'node:fs/promises';
|
|
6
6
|
import prompts from 'prompts';
|
|
7
|
+
/**
|
|
8
|
+
* Initializes the project by prompting the user for configuration options and generating a configuration file.
|
|
9
|
+
* @returns A promise that resolves when the initialization is complete.
|
|
10
|
+
*/
|
|
7
11
|
export default async () => {
|
|
8
12
|
const packageJson = await findUp('package.json');
|
|
9
13
|
if (!packageJson) {
|
package/dist/commands/new.js
CHANGED
|
@@ -10,6 +10,60 @@ import { execa } from 'execa';
|
|
|
10
10
|
import { cp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
11
11
|
import { resolve } from 'node:path';
|
|
12
12
|
import prompts from 'prompts';
|
|
13
|
+
/**
|
|
14
|
+
* Creates a new project with the given name and flags.
|
|
15
|
+
* @param name - The name of the project.
|
|
16
|
+
* @param flags - The flags for the project.
|
|
17
|
+
* @returns A promise that resolves when the project setup is complete.
|
|
18
|
+
*/
|
|
19
|
+
export default async (name, flags) => {
|
|
20
|
+
const response = await prompts(PromptNew(name, await CommandExists('yarn'), await CommandExists('pnpm')));
|
|
21
|
+
if (!response.projectName || !response.projectLang || !response.projectTemplate || !response.packageManager) {
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
const projectName = response.projectName === '.' ? '' : response.projectName;
|
|
25
|
+
const stpJob = async () => {
|
|
26
|
+
await cp(`./${response.projectName}/ghr/examples/${response.projectTemplate}/.`, `./${response.projectName}/`, { recursive: true });
|
|
27
|
+
for (const p of ['.gitignore', '.prettierignore']) {
|
|
28
|
+
await cp(`./${response.projectName}/ghr/${p}`, `./${response.projectName}/${p}`, { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
await rm(`./${response.projectName}/ghr`, { recursive: true, force: true });
|
|
31
|
+
await CreateFileFromTemplate(`.sapphirerc.${response.configFormat}.sapphire`, resolve(`./${response.projectName}/.sapphirerc.${response.configFormat}`), null, {
|
|
32
|
+
language: response.projectLang
|
|
33
|
+
});
|
|
34
|
+
await editPackageJson(response.projectName, projectName);
|
|
35
|
+
if (response.packageManager === 'pnpm') {
|
|
36
|
+
await writeFile(`./${response.projectName}/.npmrc`, '# pnpm only\nshamefully-hoist=true\npublic-hoist-pattern[]=@sapphire/*');
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const jobs = [
|
|
40
|
+
[() => cloneRepo(response.projectName, flags.verbose), 'Cloning the repository'],
|
|
41
|
+
[stpJob, 'Setting up the project']
|
|
42
|
+
];
|
|
43
|
+
if (response.git) {
|
|
44
|
+
jobs.push([() => initializeGitRepo(response.projectName), 'Initializing git repo']);
|
|
45
|
+
}
|
|
46
|
+
if (response.yarnV4) {
|
|
47
|
+
jobs.push([() => installYarnV4(response.projectName, flags.verbose), 'Installing Yarn v4']);
|
|
48
|
+
jobs.push([() => configureYarnRc(response.projectName, 'enableGlobalCache', 'true'), 'Enabling Yarn v4 global cache']);
|
|
49
|
+
jobs.push([() => configureYarnRc(response.projectName, 'nodeLinker', 'node-modules'), 'Configuring Yarn v4 to use node-modules']);
|
|
50
|
+
}
|
|
51
|
+
jobs.push([
|
|
52
|
+
() => installDeps(response.projectName, response.packageManager, flags.verbose),
|
|
53
|
+
`Installing dependencies using ${response.packageManager}`
|
|
54
|
+
]);
|
|
55
|
+
for (const [job, name] of jobs) {
|
|
56
|
+
await runJob(job, name).catch(() => process.exit(1));
|
|
57
|
+
}
|
|
58
|
+
console.log(blueBright('Done!'));
|
|
59
|
+
process.exit(0);
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Edits the package.json file at the specified location by updating the "name" field.
|
|
63
|
+
* @param location - The location of the package.json file.
|
|
64
|
+
* @param name - The new value for the "name" field.
|
|
65
|
+
* @returns A boolean indicating whether the operation was successful.
|
|
66
|
+
*/
|
|
13
67
|
async function editPackageJson(location, name) {
|
|
14
68
|
const pjLocation = `./${location}/package.json`;
|
|
15
69
|
const output = JSON.parse(await readFile(pjLocation, 'utf8'));
|
|
@@ -19,6 +73,13 @@ async function editPackageJson(location, name) {
|
|
|
19
73
|
const result = await Result.fromAsync(() => writeFile(pjLocation, JSON.stringify(output, null, 2)));
|
|
20
74
|
return result.isOk();
|
|
21
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Installs dependencies using the specified package manager.
|
|
78
|
+
* @param location The location where the dependencies should be installed.
|
|
79
|
+
* @param pm The package manager to use ('npm', 'Yarn', or 'pnpm').
|
|
80
|
+
* @param verbose Whether to display the installation output.
|
|
81
|
+
* @returns A boolean indicating whether the installation was successful.
|
|
82
|
+
*/
|
|
22
83
|
async function installDeps(location, pm, verbose) {
|
|
23
84
|
const value = await execa(pm.toLowerCase(), ['install'], {
|
|
24
85
|
stdio: verbose ? 'inherit' : undefined,
|
|
@@ -40,32 +101,52 @@ async function installDeps(location, pm, verbose) {
|
|
|
40
101
|
}
|
|
41
102
|
return true;
|
|
42
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Configures the yarnrc file with the specified name and value.
|
|
106
|
+
*
|
|
107
|
+
* @param location - The location of the yarnrc file.
|
|
108
|
+
* @param name - The name of the configuration to set.
|
|
109
|
+
* @param value - The value to set for the configuration.
|
|
110
|
+
* @returns A promise that resolves to true if the configuration was successfully set, otherwise false.
|
|
111
|
+
*/
|
|
43
112
|
async function configureYarnRc(location, name, value) {
|
|
44
113
|
await execa('yarn', ['config', 'set', name, value], { cwd: `./${location}/` });
|
|
45
114
|
return true;
|
|
46
115
|
}
|
|
47
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Installs Yarn v4 at the specified location.
|
|
118
|
+
*
|
|
119
|
+
* @param location - The location where Yarn v4 will be installed.
|
|
120
|
+
* @param verbose - Whether to display verbose output during installation.
|
|
121
|
+
* @returns A boolean indicating whether the installation was successful.
|
|
122
|
+
* @throws An error if an unknown error occurs during installation.
|
|
123
|
+
*/
|
|
124
|
+
async function installYarnV4(location, verbose) {
|
|
48
125
|
const valueSetVersion = await execa('yarn', ['set', 'version', 'berry'], {
|
|
49
126
|
stdio: verbose ? 'inherit' : undefined,
|
|
50
127
|
cwd: `./${location}/`
|
|
51
128
|
});
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
cwd: `./${location}/`
|
|
55
|
-
});
|
|
56
|
-
if (valueSetVersion.exitCode !== 0 || valueInstallPlugins.exitCode !== 0) {
|
|
57
|
-
throw new Error('An unknown error occurred while installing Yarn v3. Try running Sapphire CLI with "--verbose" flag.');
|
|
129
|
+
if (valueSetVersion.exitCode !== 0) {
|
|
130
|
+
throw new Error('An unknown error occurred while installing Yarn v4. Try running Sapphire CLI with "--verbose" flag.');
|
|
58
131
|
}
|
|
59
132
|
return true;
|
|
60
133
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
134
|
+
/**
|
|
135
|
+
* Initializes a Git repository at the specified location.
|
|
136
|
+
* @param location - The location where the Git repository should be initialized.
|
|
137
|
+
* @returns A boolean indicating whether the Git repository was successfully initialized.
|
|
138
|
+
*/
|
|
65
139
|
async function initializeGitRepo(location) {
|
|
66
140
|
await execa('git', ['init'], { cwd: `./${location}/` });
|
|
67
141
|
return true;
|
|
68
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* Runs a job asynchronously and handles the result.
|
|
145
|
+
*
|
|
146
|
+
* @param job - The job to be executed.
|
|
147
|
+
* @param name - The name of the job.
|
|
148
|
+
* @returns A boolean indicating whether the job was successful or not.
|
|
149
|
+
*/
|
|
69
150
|
async function runJob(job, name) {
|
|
70
151
|
const spinner = new Spinner(name).start();
|
|
71
152
|
const result = await Result.fromAsync(() => job());
|
|
@@ -81,6 +162,13 @@ async function runJob(job, name) {
|
|
|
81
162
|
}
|
|
82
163
|
});
|
|
83
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Clones a repository to the specified location.
|
|
167
|
+
* @param location - The location where the repository will be cloned.
|
|
168
|
+
* @param verbose - Whether to display the output of the cloning process.
|
|
169
|
+
* @returns A boolean indicating whether the cloning was successful.
|
|
170
|
+
* @throws An error if an unknown error occurred while cloning the repository.
|
|
171
|
+
*/
|
|
84
172
|
async function cloneRepo(location, verbose) {
|
|
85
173
|
const value = await execa('git', ['clone', repoUrl, `${location}/ghr`], { stdio: verbose ? 'inherit' : undefined });
|
|
86
174
|
if (value.exitCode !== 0) {
|
|
@@ -88,48 +176,4 @@ async function cloneRepo(location, verbose) {
|
|
|
88
176
|
}
|
|
89
177
|
return true;
|
|
90
178
|
}
|
|
91
|
-
export default async (name, flags) => {
|
|
92
|
-
const response = await prompts(PromptNew(name, await CommandExists('yarn'), await CommandExists('pnpm')));
|
|
93
|
-
if (!response.projectName || !response.projectLang || !response.projectTemplate || !response.packageManager) {
|
|
94
|
-
process.exit(1);
|
|
95
|
-
}
|
|
96
|
-
const projectName = response.projectName === '.' ? '' : response.projectName;
|
|
97
|
-
const stpJob = async () => {
|
|
98
|
-
await cp(`./${response.projectName}/ghr/examples/${response.projectTemplate}/.`, `./${response.projectName}/`, { recursive: true });
|
|
99
|
-
for (const p of ['.gitignore', '.prettierignore']) {
|
|
100
|
-
await cp(`./${response.projectName}/ghr/${p}`, `./${response.projectName}/${p}`, { recursive: true });
|
|
101
|
-
}
|
|
102
|
-
await rm(`./${response.projectName}/ghr`, { recursive: true, force: true });
|
|
103
|
-
await CreateFileFromTemplate(`.sapphirerc.${response.configFormat}.sapphire`, resolve(`./${response.projectName}/.sapphirerc.${response.configFormat}`), null, {
|
|
104
|
-
language: response.projectLang
|
|
105
|
-
});
|
|
106
|
-
await editPackageJson(response.projectName, projectName);
|
|
107
|
-
if (response.packageManager === 'pnpm') {
|
|
108
|
-
await writeFile(`./${response.projectName}/.npmrc`, '# pnpm only\nshamefully-hoist=true\npublic-hoist-pattern[]=@sapphire/*');
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
const jobs = [
|
|
112
|
-
[() => cloneRepo(response.projectName, flags.verbose), 'Cloning the repository'],
|
|
113
|
-
[stpJob, 'Setting up the project']
|
|
114
|
-
];
|
|
115
|
-
if (response.git) {
|
|
116
|
-
jobs.push([() => initializeGitRepo(response.projectName), 'Initializing git repo']);
|
|
117
|
-
}
|
|
118
|
-
if (response.yarnV3) {
|
|
119
|
-
jobs.push([() => installYarnV3(response.projectName, flags.verbose), 'Installing Yarn v3']);
|
|
120
|
-
if (response.projectLang === 'ts')
|
|
121
|
-
jobs.push([() => installYarnTypescriptPlugin(response.projectName), 'Installing Yarn Typescript Plugin']);
|
|
122
|
-
jobs.push([() => configureYarnRc(response.projectName, 'enableGlobalCache', 'true'), 'Enabling Yarn v3 global cache']);
|
|
123
|
-
jobs.push([() => configureYarnRc(response.projectName, 'nodeLinker', 'node-modules'), 'Configuring Yarn v3 to use node-modules']);
|
|
124
|
-
}
|
|
125
|
-
jobs.push([
|
|
126
|
-
() => installDeps(response.projectName, response.packageManager, flags.verbose),
|
|
127
|
-
`Installing dependencies using ${response.packageManager}`
|
|
128
|
-
]);
|
|
129
|
-
for (const [job, name] of jobs) {
|
|
130
|
-
await runJob(job, name).catch(() => process.exit(1));
|
|
131
|
-
}
|
|
132
|
-
console.log(blueBright('Done!'));
|
|
133
|
-
process.exit(0);
|
|
134
|
-
};
|
|
135
179
|
//# sourceMappingURL=new.js.map
|
package/dist/constants.js
CHANGED
|
@@ -6,4 +6,5 @@ export const rootFolder = fileURLToPath(rootURL);
|
|
|
6
6
|
export const templatesFolder = fileURLToPath(templatesURL);
|
|
7
7
|
export const componentsFolder = fileURLToPath(componentsURL);
|
|
8
8
|
export const repoUrl = 'https://github.com/sapphiredev/examples.git';
|
|
9
|
+
export const locationReplacement = '{{LOCATION_REPLACEMENT}}';
|
|
9
10
|
//# sourceMappingURL=constants.js.map
|
|
@@ -1,68 +1,80 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import { locationReplacement } from '#constants';
|
|
2
|
+
import { findFilesRecursivelyRegex } from '@sapphire/node-utilities';
|
|
3
|
+
import { Result } from '@sapphire/result';
|
|
4
|
+
import { accessSync } from 'node:fs';
|
|
5
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
6
|
+
import { dirname, join, relative, resolve } from 'node:path';
|
|
6
7
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* Regular expression pattern for matching files ending with JavaScript-like extensions.
|
|
9
|
+
* - `.js`
|
|
10
|
+
* - `.ts`
|
|
11
|
+
* - `.mjs`
|
|
12
|
+
* - `.cjs`
|
|
13
|
+
* - `.mts`
|
|
14
|
+
* - `.cts`
|
|
10
15
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return getJSTSfiles(fullPath, baseDir);
|
|
17
|
-
}
|
|
18
|
-
if (file.endsWith('.ts') || file.endsWith('.js')) {
|
|
19
|
-
return relative(baseDir, fullPath);
|
|
20
|
-
}
|
|
21
|
-
return [];
|
|
22
|
-
});
|
|
23
|
-
return (await Promise.all(tsFiles).then((files) => files.flat()))
|
|
24
|
-
.filter((file) => !file.includes('_load'))
|
|
25
|
-
.map((file) => file.replace(/.ts|.js/gimu, ''))
|
|
26
|
-
.sort();
|
|
27
|
-
}
|
|
16
|
+
const regexForFilesEndingWithJavaScriptLikeExtensions = /\.([mc])?[jt]s$/;
|
|
17
|
+
/**
|
|
18
|
+
* Regular expression used to match loader files.
|
|
19
|
+
*/
|
|
20
|
+
const regexForLoaderFiles = new RegExp(`_load${regexForFilesEndingWithJavaScriptLikeExtensions.source}`);
|
|
28
21
|
/**
|
|
29
22
|
* Generates loader file content
|
|
30
|
-
* @param
|
|
23
|
+
* @param dir The directory to generate the loader for
|
|
24
|
+
* @param targetDir The directory that the `_load.<ext>` file will be written to
|
|
31
25
|
*/
|
|
32
|
-
async function generateVirtualPieceLoader(dir) {
|
|
33
|
-
console.
|
|
34
|
-
const files =
|
|
35
|
-
|
|
26
|
+
async function generateVirtualPieceLoader(dir, targetDir) {
|
|
27
|
+
console.log(`Generating virtual piece loader for ${targetDir}`);
|
|
28
|
+
const files = [];
|
|
29
|
+
for await (const file of findFilesRecursivelyRegex(targetDir, regexForFilesEndingWithJavaScriptLikeExtensions)) {
|
|
30
|
+
if (regexForLoaderFiles.test(file))
|
|
31
|
+
continue;
|
|
32
|
+
files.push(relative(targetDir, file).replace(regexForFilesEndingWithJavaScriptLikeExtensions, '.$1js'));
|
|
33
|
+
}
|
|
34
|
+
console.log(`Found ${files.length} ${dir} files`);
|
|
36
35
|
return `${files.map((file) => `import './${file}';`).join('\n')}\n`;
|
|
37
36
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return
|
|
37
|
+
/**
|
|
38
|
+
* Creates component loaders
|
|
39
|
+
*
|
|
40
|
+
* We wrap the bulk of this function in a {@link Result.fromAsync} so that if any of the file writing fails
|
|
41
|
+
* that will bubble up as a failure overall.
|
|
42
|
+
*
|
|
43
|
+
* @param targetDir The directory that the `_load.<ext>` file will be written to
|
|
44
|
+
* @param config The config
|
|
45
|
+
* @returns Whether the loaders were created successfully
|
|
46
|
+
*/
|
|
47
|
+
export async function CreateComponentLoaders(targetDir, config) {
|
|
48
|
+
const templateHeader = `// import this file in your entry point (index.${config.projectLanguage}) to load respective pieces`;
|
|
49
|
+
return (await Result.fromAsync(async () => {
|
|
50
|
+
const dirs = Object.entries(config.locations)
|
|
51
|
+
.filter(([key]) => key !== 'base')
|
|
52
|
+
.map(([, value]) => value)
|
|
53
|
+
.filter((dir) => Result.from(() => accessSync(injectDirIntoTargetDir(dir, targetDir))).isOk());
|
|
54
|
+
for (const dir of dirs) {
|
|
55
|
+
const dirInjectedTarget = injectDirIntoTargetDir(dir, targetDir);
|
|
56
|
+
const target = join(targetDir, `_load.${config.projectLanguage}`);
|
|
57
|
+
const content = `${templateHeader}\n${await generateVirtualPieceLoader(dir, dirInjectedTarget)}`;
|
|
58
|
+
await writeFileRecursive(target, content);
|
|
59
|
+
}
|
|
60
|
+
})).isOk();
|
|
51
61
|
}
|
|
52
62
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* @
|
|
63
|
+
* Replaces the placeholder {@link locationReplacement} in the target directory with the specified directory.
|
|
64
|
+
*
|
|
65
|
+
* @param dir The directory to be injected into the target directory.
|
|
66
|
+
* @param targetDir The target directory containing the placeholder {@link locationReplacement}.
|
|
67
|
+
* @returns The target directory with the placeholder replaced by the specified directory.
|
|
56
68
|
*/
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const fa = file.split(/---(\r\n|\r|\n|)/gm);
|
|
60
|
-
return [JSON.parse(fa[0]), fa[2]];
|
|
69
|
+
function injectDirIntoTargetDir(dir, targetDir) {
|
|
70
|
+
return targetDir.replace(locationReplacement, dir);
|
|
61
71
|
}
|
|
62
72
|
/**
|
|
63
|
-
* Writes a file recursively
|
|
64
|
-
*
|
|
65
|
-
* @param
|
|
73
|
+
* Writes data to a file recursively.
|
|
74
|
+
*
|
|
75
|
+
* @param target - The target file path.
|
|
76
|
+
* @param data - The data to write to the file.
|
|
77
|
+
* @returns A promise that resolves when the file is written.
|
|
66
78
|
*/
|
|
67
79
|
async function writeFileRecursive(target, data) {
|
|
68
80
|
const resolvedTarget = resolve(target);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { templatesFolder } from '#constants';
|
|
1
|
+
import { locationReplacement, templatesFolder } from '#constants';
|
|
2
2
|
import { fileExists } from '#functions/FileExists';
|
|
3
3
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
4
|
-
import { dirname, resolve } from 'node:path';
|
|
4
|
+
import { dirname, join, resolve } from 'node:path';
|
|
5
5
|
export async function CreateFileFromTemplate(template, target, config, params, custom = false, component = false) {
|
|
6
|
-
const location = custom ? template :
|
|
6
|
+
const location = custom ? template : join(templatesFolder, template);
|
|
7
7
|
const output = {};
|
|
8
8
|
if (component) {
|
|
9
9
|
const [config, templateContent] = await getComponentTemplateWithConfig(location);
|
|
@@ -23,7 +23,7 @@ export async function CreateFileFromTemplate(template, target, config, params, c
|
|
|
23
23
|
throw new Error('The template is invalid. Please create a valid template structure.');
|
|
24
24
|
}
|
|
25
25
|
const directoryForOutput = component ? config?.locations[output.config.category] : null;
|
|
26
|
-
const targetPath = component ? target.replace(
|
|
26
|
+
const targetPath = component ? target.replace(locationReplacement, directoryForOutput) : target;
|
|
27
27
|
if (await fileExists(targetPath)) {
|
|
28
28
|
throw new Error('A component with the provided name already exists. Please provide a unique name.');
|
|
29
29
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import findUp from 'find-up';
|
|
2
|
+
import { setTimeout as sleep } from 'node:timers/promises';
|
|
3
|
+
/**
|
|
4
|
+
* Fetches the configuration file asynchronously.
|
|
5
|
+
* It first tries to find the '.sapphirerc.json' file in the current working directory.
|
|
6
|
+
* If not found, it then tries to find the '.sapphirerc.yml' file.
|
|
7
|
+
* Returns a Promise that resolves to the configuration file as JSON if found, otherwise resolves to null.
|
|
8
|
+
* @returns A Promise that resolves to the configuration file as JSON if found, otherwise resolves to null.
|
|
9
|
+
*/
|
|
10
|
+
export async function fetchConfig() {
|
|
11
|
+
const configFileAsJson = await Promise.race([findUp('.sapphirerc.json', { cwd: '.' }), sleep(5000)]);
|
|
12
|
+
if (configFileAsJson) {
|
|
13
|
+
return configFileAsJson;
|
|
14
|
+
}
|
|
15
|
+
return Promise.race([findUp('.sapphirerc.yml', { cwd: '.' }), sleep(5000)]);
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=fetchConfig.js.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Spinner } from '@favware/colorette-spinner';
|
|
2
|
+
import { Result } from '@sapphire/result';
|
|
3
|
+
import { blueBright, red } from 'colorette';
|
|
4
|
+
import findUp from 'find-up';
|
|
5
|
+
import { load } from 'js-yaml';
|
|
6
|
+
import { readFile } from 'node:fs/promises';
|
|
7
|
+
import { setTimeout as sleep } from 'node:timers/promises';
|
|
8
|
+
export async function generateCommandFlow(spinnerMessage, callback) {
|
|
9
|
+
const spinner = new Spinner(spinnerMessage).start();
|
|
10
|
+
const fail = (error, additionalExecution) => {
|
|
11
|
+
spinner.error({ text: error });
|
|
12
|
+
additionalExecution?.();
|
|
13
|
+
process.exit(1);
|
|
14
|
+
};
|
|
15
|
+
const configLocation = await fetchConfig();
|
|
16
|
+
if (!configLocation) {
|
|
17
|
+
return fail("Can't find the Sapphire CLI config.");
|
|
18
|
+
}
|
|
19
|
+
const config = configLocation.endsWith('json')
|
|
20
|
+
? JSON.parse(await readFile(configLocation, 'utf8'))
|
|
21
|
+
: load(await readFile(configLocation, 'utf8'));
|
|
22
|
+
if (!config) {
|
|
23
|
+
return fail("Can't parse the Sapphire CLI config.");
|
|
24
|
+
}
|
|
25
|
+
const result = await Result.fromAsync(() => callback(config, configLocation.replace(/.sapphirerc.(json|yml)/g, '')));
|
|
26
|
+
return result.match({
|
|
27
|
+
ok: () => {
|
|
28
|
+
spinner.success();
|
|
29
|
+
console.log(blueBright('Done!'));
|
|
30
|
+
process.exit(0);
|
|
31
|
+
},
|
|
32
|
+
err: (error) => fail(error.message, () => console.log(red(error.message)))
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Fetches the configuration file asynchronously.
|
|
37
|
+
* It first tries to find the '.sapphirerc.json' file in the current working directory.
|
|
38
|
+
* If not found, it then tries to find the '.sapphirerc.yml' file.
|
|
39
|
+
* Returns a Promise that resolves to the configuration file as JSON if found, otherwise resolves to null.
|
|
40
|
+
* @returns A Promise that resolves to the configuration file as JSON if found, otherwise resolves to null.
|
|
41
|
+
*/
|
|
42
|
+
async function fetchConfig() {
|
|
43
|
+
const configFileAsJson = await Promise.race([findUp('.sapphirerc.json', { cwd: '.' }), sleep(5000)]);
|
|
44
|
+
if (configFileAsJson) {
|
|
45
|
+
return configFileAsJson;
|
|
46
|
+
}
|
|
47
|
+
return Promise.race([findUp('.sapphirerc.yml', { cwd: '.' }), sleep(5000)]);
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=generateCommandFlow.js.map
|
|
@@ -61,8 +61,8 @@ export const PromptNew = (projectName, yarn, pnpm) => {
|
|
|
61
61
|
},
|
|
62
62
|
{
|
|
63
63
|
type: (prev) => (prev === 'Yarn' ? 'confirm' : false),
|
|
64
|
-
name: '
|
|
65
|
-
message: 'Do you want to use Yarn
|
|
64
|
+
name: 'yarnV4',
|
|
65
|
+
message: 'Do you want to use Yarn v4?',
|
|
66
66
|
initial: true
|
|
67
67
|
},
|
|
68
68
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sapphire/cli",
|
|
3
|
-
"version": "1.9.0
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "CLI for Sapphire Framework",
|
|
5
5
|
"author": "@sapphire",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@favware/colorette-spinner": "^1.0.1",
|
|
37
|
+
"@sapphire/node-utilities": "^1.0.0",
|
|
37
38
|
"@sapphire/result": "^2.6.4",
|
|
38
39
|
"colorette": "^2.0.20",
|
|
39
40
|
"commander": "^11.1.0",
|
|
@@ -44,8 +45,8 @@
|
|
|
44
45
|
"tslib": "^2.6.2"
|
|
45
46
|
},
|
|
46
47
|
"devDependencies": {
|
|
47
|
-
"@commitlint/cli": "^18.4.
|
|
48
|
-
"@commitlint/config-conventional": "^18.4.
|
|
48
|
+
"@commitlint/cli": "^18.4.3",
|
|
49
|
+
"@commitlint/config-conventional": "^18.4.3",
|
|
49
50
|
"@favware/cliff-jumper": "^2.2.3",
|
|
50
51
|
"@favware/npm-deprecate": "^1.0.7",
|
|
51
52
|
"@sapphire/decorators": "*",
|
|
@@ -55,20 +56,20 @@
|
|
|
55
56
|
"@sapphire/prettier-config": "^2.0.0",
|
|
56
57
|
"@sapphire/ts-config": "^5.0.0",
|
|
57
58
|
"@types/js-yaml": "^4.0.9",
|
|
58
|
-
"@types/node": "^20.
|
|
59
|
-
"@types/prompts": "^2.4.
|
|
60
|
-
"@typescript-eslint/eslint-plugin": "^6.
|
|
61
|
-
"@typescript-eslint/parser": "^6.
|
|
59
|
+
"@types/node": "^20.10.2",
|
|
60
|
+
"@types/prompts": "^2.4.9",
|
|
61
|
+
"@typescript-eslint/eslint-plugin": "^6.13.1",
|
|
62
|
+
"@typescript-eslint/parser": "^6.13.1",
|
|
62
63
|
"cz-conventional-changelog": "^3.3.0",
|
|
63
64
|
"discord.js": "*",
|
|
64
|
-
"eslint": "^8.
|
|
65
|
-
"eslint-config-prettier": "^9.
|
|
65
|
+
"eslint": "^8.55.0",
|
|
66
|
+
"eslint-config-prettier": "^9.1.0",
|
|
66
67
|
"eslint-plugin-prettier": "^5.0.1",
|
|
67
68
|
"globby": "^14.0.0",
|
|
68
69
|
"lint-staged": "^15.1.0",
|
|
69
70
|
"prettier": "^3.1.0",
|
|
70
71
|
"ts-node": "^10.9.1",
|
|
71
|
-
"typescript": "^5.
|
|
72
|
+
"typescript": "^5.3.2"
|
|
72
73
|
},
|
|
73
74
|
"resolutions": {
|
|
74
75
|
"ansi-regex": "^5.0.1",
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$schema": "https://json-schema.org/draft
|
|
2
|
+
"$schema": "https://json-schema.org/draft-04/schema#",
|
|
3
3
|
"title": "Sapphire CLI Config",
|
|
4
4
|
"description": "Scheme for Sapphire CLI Config (@sapphire/cli)",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"properties": {
|
|
7
7
|
"projectLanguage": {
|
|
8
8
|
"description": "Project language (ts | js)",
|
|
9
|
-
"type": "string"
|
|
9
|
+
"type": "string",
|
|
10
|
+
"enum": ["ts", "js"]
|
|
10
11
|
},
|
|
11
12
|
"locations": {
|
|
12
13
|
"description": "Categories and their locations",
|