complete-cli 1.0.2 → 1.0.4
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/dist/107.main.cjs +1 -0
- package/dist/140.main.cjs +1 -0
- package/dist/242.main.cjs +1 -0
- package/dist/269.main.cjs +1 -0
- package/dist/309.main.cjs +1 -0
- package/dist/367.main.cjs +1 -0
- package/dist/541.main.cjs +1 -0
- package/dist/55.main.cjs +1 -0
- package/dist/717.main.cjs +1 -0
- package/dist/742.main.cjs +1 -0
- package/dist/769.main.cjs +1 -0
- package/dist/770.main.cjs +1 -0
- package/dist/914.main.cjs +1 -0
- package/dist/951.main.cjs +1 -0
- package/dist/main.cjs +4 -0
- package/dist/main.cjs.LICENSE.txt +78 -0
- package/package.json +5 -2
- package/dist/commands/CheckCommand.js +0 -141
- package/dist/commands/InitCommand.js +0 -64
- package/dist/commands/NukeCommand.js +0 -12
- package/dist/commands/PublishCommand.js +0 -162
- package/dist/commands/UpdateCommand.js +0 -15
- package/dist/commands/check/check.test.js +0 -86
- package/dist/commands/check/getTruncatedText.js +0 -139
- package/dist/commands/init/checkIfProjectPathExists.js +0 -21
- package/dist/commands/init/createProject.js +0 -130
- package/dist/commands/init/getAuthorName.js +0 -17
- package/dist/commands/init/getProjectPath.js +0 -80
- package/dist/commands/init/packageManager.js +0 -35
- package/dist/commands/init/vsCodeInit.js +0 -85
- package/dist/constants.js +0 -17
- package/dist/git.js +0 -132
- package/dist/interfaces/GitHubCLIHostsYAML.js +0 -1
- package/dist/main.js +0 -24
- package/dist/prompt.js +0 -53
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import { getEnumValues, trimPrefix } from "complete-common";
|
|
2
|
-
import { fatalError, getPackageManagerLockFileNames, PackageManager, } from "complete-node";
|
|
3
|
-
const MARKER_CUSTOMIZATION_START = "@template-customization-start";
|
|
4
|
-
const MARKER_CUSTOMIZATION_END = "@template-customization-end";
|
|
5
|
-
const MARKER_IGNORE_BLOCK_START = "@template-ignore-block-start";
|
|
6
|
-
const MARKER_IGNORE_BLOCK_END = "@template-ignore-block-end";
|
|
7
|
-
const MARKER_IGNORE_NEXT_LINE = "@template-ignore-next-line";
|
|
8
|
-
const PACKAGE_MANAGER_STRINGS = [
|
|
9
|
-
"PACKAGE_MANAGER_NAME",
|
|
10
|
-
"PACKAGE_MANAGER_INSTALL_COMMAND",
|
|
11
|
-
"PACKAGE_MANAGER_LOCK_FILE_NAME",
|
|
12
|
-
...getEnumValues(PackageManager),
|
|
13
|
-
...getPackageManagerLockFileNames(),
|
|
14
|
-
];
|
|
15
|
-
/**
|
|
16
|
-
* @param fileName Used to perform some specific rules based on the template file name.
|
|
17
|
-
* @param text The text to parse.
|
|
18
|
-
* @param ignoreLines A set of lines to remove from the text.
|
|
19
|
-
* @param linesBeforeIgnore A set of lines that will trigger the subsequent line to be ignored.
|
|
20
|
-
* @returns The text of the file with all text removed between any flagged markers (and other
|
|
21
|
-
* specific hard-coded exclusions), as well as an array of lines that had a
|
|
22
|
-
* "ignore-next-line" marker below them.
|
|
23
|
-
*/
|
|
24
|
-
export function getTruncatedText(fileName, text, ignoreLines, linesBeforeIgnore) {
|
|
25
|
-
const lines = text.split("\n");
|
|
26
|
-
const newLines = [];
|
|
27
|
-
const newIgnoreLines = new Set();
|
|
28
|
-
const newLinesBeforeIgnore = new Set();
|
|
29
|
-
let isSkipping = false;
|
|
30
|
-
let isIgnoring = false;
|
|
31
|
-
let shouldIgnoreNextLine = false;
|
|
32
|
-
let previousLine = "";
|
|
33
|
-
for (const line of lines) {
|
|
34
|
-
if (line.trim() === "") {
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
if (ignoreLines.has(line.trim())) {
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
if (shouldIgnoreNextLine) {
|
|
41
|
-
shouldIgnoreNextLine = false;
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
if (linesBeforeIgnore.has(line)) {
|
|
45
|
-
shouldIgnoreNextLine = true;
|
|
46
|
-
}
|
|
47
|
-
// -------------
|
|
48
|
-
// Marker checks
|
|
49
|
-
// -------------
|
|
50
|
-
if (line.includes(MARKER_CUSTOMIZATION_START)) {
|
|
51
|
-
isSkipping = true;
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
if (line.includes(MARKER_CUSTOMIZATION_END)) {
|
|
55
|
-
isSkipping = false;
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
if (line.includes(MARKER_IGNORE_BLOCK_START)) {
|
|
59
|
-
isIgnoring = true;
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
if (line.includes(MARKER_IGNORE_BLOCK_END)) {
|
|
63
|
-
isIgnoring = false;
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
if (line.includes(MARKER_IGNORE_NEXT_LINE)) {
|
|
67
|
-
shouldIgnoreNextLine = true;
|
|
68
|
-
// We mark the previous line so that we know the next line to skip in the template.
|
|
69
|
-
if (previousLine.trim() === "") {
|
|
70
|
-
fatalError(`You cannot have a "${MARKER_IGNORE_NEXT_LINE}" marker before a blank line in the "${fileName}" file.`);
|
|
71
|
-
}
|
|
72
|
-
newLinesBeforeIgnore.add(previousLine);
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
if (isIgnoring) {
|
|
76
|
-
const baseLine = trimPrefix(line.trim(), "// ");
|
|
77
|
-
newIgnoreLines.add(baseLine);
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
// --------------------
|
|
81
|
-
// Specific file checks
|
|
82
|
-
// --------------------
|
|
83
|
-
// We should ignore imports in JavaScript or TypeScript files.
|
|
84
|
-
if (fileName.endsWith(".js") || fileName.endsWith(".ts")) {
|
|
85
|
-
if (line === "import {") {
|
|
86
|
-
isSkipping = true;
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
if (line.startsWith("} from ")) {
|
|
90
|
-
isSkipping = false;
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
if (line.startsWith("import ")) {
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
// End-users can have different ignored words.
|
|
98
|
-
if (fileName === "cspell.config.jsonc" ||
|
|
99
|
-
fileName === "_cspell.config.jsonc") {
|
|
100
|
-
if (line.match(/"words": \[.*]/) !== null) {
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
if (line.includes('"words": [')) {
|
|
104
|
-
isSkipping = true;
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
if ((line.endsWith("]") || line.endsWith("],")) && isSkipping) {
|
|
108
|
-
isSkipping = false;
|
|
109
|
-
continue;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
if (fileName === "ci.yml" || fileName === "action.yml") {
|
|
113
|
-
// End-users can have different package managers.
|
|
114
|
-
if (hasPackageManagerString(line)) {
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
|
-
// Ignore comments, since end-users are expected to delete the explanations.
|
|
118
|
-
if (line.match(/^\s*#/) !== null) {
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
// ------------
|
|
123
|
-
// Final checks
|
|
124
|
-
// ------------
|
|
125
|
-
if (!isSkipping) {
|
|
126
|
-
newLines.push(line);
|
|
127
|
-
previousLine = line;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
const newText = newLines.join("\n");
|
|
131
|
-
return {
|
|
132
|
-
text: newText,
|
|
133
|
-
ignoreLines: newIgnoreLines,
|
|
134
|
-
linesBeforeIgnore: newLinesBeforeIgnore,
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
function hasPackageManagerString(line) {
|
|
138
|
-
return PACKAGE_MANAGER_STRINGS.some((string) => line.includes(string));
|
|
139
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
|
-
import { deleteFileOrDirectory, fileOrDirectoryExists, isDirectory, } from "complete-node";
|
|
3
|
-
import { CWD } from "../../constants.js";
|
|
4
|
-
import { getInputYesNo, promptEnd, promptLog } from "../../prompt.js";
|
|
5
|
-
export async function checkIfProjectPathExists(projectPath, yes) {
|
|
6
|
-
if (projectPath === CWD || !fileOrDirectoryExists(projectPath)) {
|
|
7
|
-
return;
|
|
8
|
-
}
|
|
9
|
-
const fileType = isDirectory(projectPath) ? "directory" : "file";
|
|
10
|
-
if (yes) {
|
|
11
|
-
deleteFileOrDirectory(projectPath);
|
|
12
|
-
promptLog(`Deleted ${fileType}: ${chalk.green(projectPath)}`);
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
promptLog(`A ${fileType} already exists with a name of: ${chalk.green(projectPath)}`);
|
|
16
|
-
const shouldDelete = await getInputYesNo("Do you want me to delete it?");
|
|
17
|
-
if (!shouldDelete) {
|
|
18
|
-
promptEnd("Ok then. Goodbye.");
|
|
19
|
-
}
|
|
20
|
-
deleteFileOrDirectory(projectPath);
|
|
21
|
-
}
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
|
-
import { repeat } from "complete-common";
|
|
3
|
-
import { $, copyFileOrDirectory, getFileNamesInDirectory, getPackageManagerInstallCICommand, getPackageManagerInstallCommand, isFile, makeDirectory, readFile, renameFile, updatePackageJSONDependencies, writeFile, } from "complete-node";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { ACTION_YML, ACTION_YML_TEMPLATE_PATH, TEMPLATES_DYNAMIC_DIR, TEMPLATES_STATIC_DIR, } from "../../constants.js";
|
|
6
|
-
import { initGitRepository } from "../../git.js";
|
|
7
|
-
import { promptError, promptLog, promptSpinnerStart } from "../../prompt.js";
|
|
8
|
-
export async function createProject(projectName, authorName, projectPath, createNewDir, gitRemoteURL, skipInstall, packageManager) {
|
|
9
|
-
if (createNewDir) {
|
|
10
|
-
makeDirectory(projectPath);
|
|
11
|
-
}
|
|
12
|
-
copyStaticFiles(projectPath);
|
|
13
|
-
copyDynamicFiles(projectName, authorName, projectPath, packageManager);
|
|
14
|
-
// There is no package manager lock files yet, so we have to pass "false" to this function.
|
|
15
|
-
const updated = await updatePackageJSONDependencies(projectPath, false, true);
|
|
16
|
-
if (!updated) {
|
|
17
|
-
promptError('Failed to update the dependencies in the "package.json" file.');
|
|
18
|
-
}
|
|
19
|
-
await installNodeModules(projectPath, skipInstall, packageManager);
|
|
20
|
-
await formatFiles(projectPath);
|
|
21
|
-
// Only make the initial commit once all of the files have been copied and formatted.
|
|
22
|
-
await initGitRepository(projectPath, gitRemoteURL);
|
|
23
|
-
promptLog(`Successfully created project: ${chalk.green(projectName)}`);
|
|
24
|
-
}
|
|
25
|
-
/** Copy static files, like "eslint.config.mjs", "tsconfig.json", etc. */
|
|
26
|
-
function copyStaticFiles(projectPath) {
|
|
27
|
-
copyTemplateDirectoryWithoutOverwriting(TEMPLATES_STATIC_DIR, projectPath);
|
|
28
|
-
// Rename "_gitattributes" to ".gitattributes". (If it is kept as ".gitattributes", then it won't
|
|
29
|
-
// be committed to git.)
|
|
30
|
-
const gitAttributesPath = path.join(projectPath, "_gitattributes");
|
|
31
|
-
const correctGitAttributesPath = path.join(projectPath, ".gitattributes");
|
|
32
|
-
renameFile(gitAttributesPath, correctGitAttributesPath);
|
|
33
|
-
// Rename "_cspell.config.jsonc" to "cspell.config.jsonc". (If it is kept as
|
|
34
|
-
// "cspell.config.jsonc", then local spell checking will fail.)
|
|
35
|
-
const cSpellConfigPath = path.join(projectPath, "_cspell.config.jsonc");
|
|
36
|
-
const correctCSpellConfigPath = path.join(projectPath, "cspell.config.jsonc");
|
|
37
|
-
renameFile(cSpellConfigPath, correctCSpellConfigPath);
|
|
38
|
-
}
|
|
39
|
-
function copyTemplateDirectoryWithoutOverwriting(templateDirPath, projectPath) {
|
|
40
|
-
const fileNames = getFileNamesInDirectory(templateDirPath);
|
|
41
|
-
for (const fileName of fileNames) {
|
|
42
|
-
const templateFilePath = path.join(templateDirPath, fileName);
|
|
43
|
-
const destinationFilePath = path.join(projectPath, fileName);
|
|
44
|
-
if (!isFile(destinationFilePath)) {
|
|
45
|
-
copyFileOrDirectory(templateFilePath, destinationFilePath);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
/** Copy files that need to have text replaced inside of them. */
|
|
50
|
-
function copyDynamicFiles(projectName, authorName, projectPath, packageManager) {
|
|
51
|
-
// `.github/workflows/setup/action.yml`
|
|
52
|
-
{
|
|
53
|
-
const fileName = ACTION_YML;
|
|
54
|
-
const templatePath = ACTION_YML_TEMPLATE_PATH;
|
|
55
|
-
const template = readFile(templatePath);
|
|
56
|
-
const installCommand = getPackageManagerInstallCICommand(packageManager);
|
|
57
|
-
const actionYML = template
|
|
58
|
-
.replaceAll("PACKAGE_MANAGER_NAME", packageManager)
|
|
59
|
-
.replaceAll("PACKAGE_MANAGER_INSTALL_COMMAND", installCommand);
|
|
60
|
-
const setupPath = path.join(projectPath, ".github", "workflows", "setup");
|
|
61
|
-
makeDirectory(setupPath);
|
|
62
|
-
const destinationPath = path.join(setupPath, fileName);
|
|
63
|
-
writeFile(destinationPath, actionYML);
|
|
64
|
-
}
|
|
65
|
-
// `.gitignore`
|
|
66
|
-
{
|
|
67
|
-
const templatePath = path.join(TEMPLATES_DYNAMIC_DIR, "_gitignore");
|
|
68
|
-
const template = readFile(templatePath);
|
|
69
|
-
// Prepend a header with the project name.
|
|
70
|
-
let separatorLine = "# ";
|
|
71
|
-
repeat(projectName.length, () => {
|
|
72
|
-
separatorLine += "-";
|
|
73
|
-
});
|
|
74
|
-
separatorLine += "\n";
|
|
75
|
-
const gitIgnoreHeader = `${separatorLine}# ${projectName}\n${separatorLine}\n`;
|
|
76
|
-
const nodeGitIgnorePath = path.join(TEMPLATES_DYNAMIC_DIR, "Node.gitignore");
|
|
77
|
-
const nodeGitIgnore = readFile(nodeGitIgnorePath);
|
|
78
|
-
// eslint-disable-next-line prefer-template
|
|
79
|
-
const gitignore = gitIgnoreHeader + template + "\n" + nodeGitIgnore;
|
|
80
|
-
// We need to replace the underscore with a period.
|
|
81
|
-
const destinationPath = path.join(projectPath, ".gitignore");
|
|
82
|
-
writeFile(destinationPath, gitignore);
|
|
83
|
-
}
|
|
84
|
-
// `package.json`
|
|
85
|
-
{
|
|
86
|
-
const templatePath = path.join(TEMPLATES_DYNAMIC_DIR, "package.json");
|
|
87
|
-
const template = readFile(templatePath);
|
|
88
|
-
const packageJSON = template
|
|
89
|
-
.replaceAll("project-name", projectName)
|
|
90
|
-
.replaceAll("author-name", authorName ?? "unknown");
|
|
91
|
-
const destinationPath = path.join(projectPath, "package.json");
|
|
92
|
-
writeFile(destinationPath, packageJSON);
|
|
93
|
-
}
|
|
94
|
-
// `README.md`
|
|
95
|
-
{
|
|
96
|
-
const templatePath = path.join(TEMPLATES_DYNAMIC_DIR, "README.md");
|
|
97
|
-
const template = readFile(templatePath);
|
|
98
|
-
// "PROJECT-NAME" must be hyphenated, as using an underscore will break Prettier for some
|
|
99
|
-
// reason.
|
|
100
|
-
const command = getPackageManagerInstallCICommand(packageManager);
|
|
101
|
-
const readmeMD = template
|
|
102
|
-
.replaceAll("PROJECT-NAME", projectName)
|
|
103
|
-
.replaceAll("PACKAGE-MANAGER-INSTALL-COMMAND", command);
|
|
104
|
-
const destinationPath = path.join(projectPath, "README.md");
|
|
105
|
-
writeFile(destinationPath, readmeMD);
|
|
106
|
-
}
|
|
107
|
-
const srcPath = path.join(projectPath, "src");
|
|
108
|
-
makeDirectory(srcPath);
|
|
109
|
-
}
|
|
110
|
-
async function installNodeModules(projectPath, skipInstall, packageManager) {
|
|
111
|
-
if (skipInstall) {
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
const command = getPackageManagerInstallCommand(packageManager);
|
|
115
|
-
const s = promptSpinnerStart(`Installing the project dependencies with "${command}". (This can take a long time.)`);
|
|
116
|
-
try {
|
|
117
|
-
const $$ = $({ cwd: projectPath });
|
|
118
|
-
const commandParts = command.split(" ");
|
|
119
|
-
await $$ `${commandParts}`;
|
|
120
|
-
s.stop("Installed.");
|
|
121
|
-
}
|
|
122
|
-
catch {
|
|
123
|
-
s.stop("Failed to install.");
|
|
124
|
-
promptError("Exiting.");
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
async function formatFiles(projectPath) {
|
|
128
|
-
const $$ = $({ cwd: projectPath });
|
|
129
|
-
await $$ `prettier --write ${projectPath}`;
|
|
130
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { getGitHubUsername } from "../../git.js";
|
|
2
|
-
import { getInputString, promptError, promptLog } from "../../prompt.js";
|
|
3
|
-
export async function getAuthorName() {
|
|
4
|
-
const gitHubUsername = await getGitHubUsername();
|
|
5
|
-
if (gitHubUsername !== undefined) {
|
|
6
|
-
return gitHubUsername;
|
|
7
|
-
}
|
|
8
|
-
return await getNewAuthorName();
|
|
9
|
-
}
|
|
10
|
-
async function getNewAuthorName() {
|
|
11
|
-
promptLog("The author name was not found from the GitHub CLI configuration file.");
|
|
12
|
-
const authorName = await getInputString("Enter the author of the project:");
|
|
13
|
-
if (authorName === "") {
|
|
14
|
-
promptError("You must enter an author name.");
|
|
15
|
-
}
|
|
16
|
-
return authorName;
|
|
17
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
|
-
import { hasWhitespace, isKebabCase } from "complete-common";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { CURRENT_DIRECTORY_NAME, CWD } from "../../constants.js";
|
|
5
|
-
import { getInputString, getInputYesNo, promptError, promptLog, } from "../../prompt.js";
|
|
6
|
-
// From: https://gist.github.com/doctaphred/d01d05291546186941e1b7ddc02034d3
|
|
7
|
-
const ILLEGAL_CHARACTERS_FOR_WINDOWS_FILENAMES = [
|
|
8
|
-
"<",
|
|
9
|
-
">",
|
|
10
|
-
":",
|
|
11
|
-
'"',
|
|
12
|
-
"/",
|
|
13
|
-
"\\",
|
|
14
|
-
"|",
|
|
15
|
-
"?",
|
|
16
|
-
"*",
|
|
17
|
-
];
|
|
18
|
-
export async function getProjectPath(name, useCurrentDirectory, customDirectory, yes, forceName) {
|
|
19
|
-
let projectName = name;
|
|
20
|
-
let projectPath;
|
|
21
|
-
let createNewDir;
|
|
22
|
-
if (useCurrentDirectory) {
|
|
23
|
-
// The "--use-current-directory" command-line flag was specified, so there is no need to prompt
|
|
24
|
-
// the user.
|
|
25
|
-
projectName = CURRENT_DIRECTORY_NAME;
|
|
26
|
-
projectPath = CWD;
|
|
27
|
-
createNewDir = false;
|
|
28
|
-
}
|
|
29
|
-
else if (projectName !== undefined) {
|
|
30
|
-
// The project name was specified on the command-line.
|
|
31
|
-
const baseDirectory = customDirectory === undefined ? CWD : path.join(CWD, customDirectory);
|
|
32
|
-
projectPath = path.join(baseDirectory, projectName);
|
|
33
|
-
createNewDir = true;
|
|
34
|
-
}
|
|
35
|
-
else if (yes) {
|
|
36
|
-
// The "--yes" command-line flag was specified and the project name was not specified on the
|
|
37
|
-
// command-line, so default to using the current directory.
|
|
38
|
-
projectName = CURRENT_DIRECTORY_NAME;
|
|
39
|
-
projectPath = CWD;
|
|
40
|
-
createNewDir = false;
|
|
41
|
-
}
|
|
42
|
-
else {
|
|
43
|
-
// The project name was not specified on the command-line, so prompt the user for it.
|
|
44
|
-
[projectName, projectPath, createNewDir] = await getNewProjectName();
|
|
45
|
-
}
|
|
46
|
-
validateProjectName(projectName, forceName);
|
|
47
|
-
promptLog(`Using a project name of: ${chalk.green(projectName)}`);
|
|
48
|
-
return { projectPath, createNewDir };
|
|
49
|
-
}
|
|
50
|
-
async function getNewProjectName() {
|
|
51
|
-
promptLog("You did not specify a project name as a command-line argument.");
|
|
52
|
-
const shouldUseCurrentDir = await getInputYesNo(`Would you like to create a new project using the current directory "${chalk.green(CURRENT_DIRECTORY_NAME)}" as the root?`);
|
|
53
|
-
if (shouldUseCurrentDir) {
|
|
54
|
-
return [CURRENT_DIRECTORY_NAME, CWD, false];
|
|
55
|
-
}
|
|
56
|
-
const projectName = await getInputString("Enter the name of the project:");
|
|
57
|
-
const projectPath = path.join(CWD, projectName);
|
|
58
|
-
return [projectName, projectPath, true];
|
|
59
|
-
}
|
|
60
|
-
function validateProjectName(projectName, forceName) {
|
|
61
|
-
if (projectName === "") {
|
|
62
|
-
promptError("You cannot have a blank project name.");
|
|
63
|
-
}
|
|
64
|
-
if (process.platform === "win32") {
|
|
65
|
-
for (const character of ILLEGAL_CHARACTERS_FOR_WINDOWS_FILENAMES) {
|
|
66
|
-
if (projectName.includes(character)) {
|
|
67
|
-
promptError(`The "${character}" character is not allowed in a Windows file name.`);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
if (forceName) {
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
if (hasWhitespace(projectName)) {
|
|
75
|
-
promptError('The project name has whitespace in it, which is not allowed. Use kebab-case for your project name. (e.g. "green-candle")');
|
|
76
|
-
}
|
|
77
|
-
if (!isKebabCase(projectName)) {
|
|
78
|
-
promptError('The project name is not in kebab-case. (Kebab-case is the style of using all lowercase letters, with words separated by hyphens.) Project names must use kebab-case to match GitHub repository standards. If necessary, you can override this check with the "--force-name" flag.');
|
|
79
|
-
}
|
|
80
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
|
-
import { commandExists, PackageManager } from "complete-node";
|
|
3
|
-
import { DEFAULT_PACKAGE_MANAGER } from "../../constants.js";
|
|
4
|
-
import { promptError } from "../../prompt.js";
|
|
5
|
-
export async function getPackageManagerUsedForNewProject(options) {
|
|
6
|
-
const packageManagerFromOptions = await getPackageManagerFromOptions(options);
|
|
7
|
-
if (packageManagerFromOptions !== undefined) {
|
|
8
|
-
return packageManagerFromOptions;
|
|
9
|
-
}
|
|
10
|
-
return DEFAULT_PACKAGE_MANAGER;
|
|
11
|
-
}
|
|
12
|
-
async function getPackageManagerFromOptions(options) {
|
|
13
|
-
if (options.npm) {
|
|
14
|
-
const npmExists = await commandExists("npm");
|
|
15
|
-
if (!npmExists) {
|
|
16
|
-
promptError(`You specified the "--npm" option, but "${chalk.green("npm")}" does not seem to be a valid command.`);
|
|
17
|
-
}
|
|
18
|
-
return PackageManager.npm;
|
|
19
|
-
}
|
|
20
|
-
if (options.yarn) {
|
|
21
|
-
const yarnExists = await commandExists("yarn");
|
|
22
|
-
if (!yarnExists) {
|
|
23
|
-
promptError(`You specified the "--yarn" option, but "${chalk.green("yarn")}" does not seem to be a valid command.`);
|
|
24
|
-
}
|
|
25
|
-
return PackageManager.yarn;
|
|
26
|
-
}
|
|
27
|
-
if (options.pnpm) {
|
|
28
|
-
const pnpmExists = await commandExists("pnpm");
|
|
29
|
-
if (!pnpmExists) {
|
|
30
|
-
promptError(`You specified the "--pnpm" option, but "${chalk.green("pnpm")}" does not seem to be a valid command.`);
|
|
31
|
-
}
|
|
32
|
-
return PackageManager.pnpm;
|
|
33
|
-
}
|
|
34
|
-
return undefined;
|
|
35
|
-
}
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { $, commandExists, getJSONC, isFile } from "complete-node";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { getInputYesNo, promptError, promptLog } from "../../prompt.js";
|
|
4
|
-
const VS_CODE_COMMANDS = [
|
|
5
|
-
"code",
|
|
6
|
-
"codium",
|
|
7
|
-
"code-oss",
|
|
8
|
-
"code-insiders",
|
|
9
|
-
];
|
|
10
|
-
export async function vsCodeInit(projectPath, vscode, yes) {
|
|
11
|
-
const VSCodeCommand = await getVSCodeCommand();
|
|
12
|
-
if (VSCodeCommand === undefined) {
|
|
13
|
-
promptLog('VSCode does not seem to be installed. (The "code" command is not in the path.) Skipping VSCode-related things.');
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
await installVSCodeExtensions(projectPath, VSCodeCommand);
|
|
17
|
-
await promptVSCode(projectPath, VSCodeCommand, vscode, yes);
|
|
18
|
-
}
|
|
19
|
-
async function getVSCodeCommand() {
|
|
20
|
-
const commandCheckPromises = VS_CODE_COMMANDS.map((command) => ({
|
|
21
|
-
command,
|
|
22
|
-
existsPromise: commandExists(command),
|
|
23
|
-
}));
|
|
24
|
-
const commandChecks = await Promise.all(commandCheckPromises.map(async (check) => ({
|
|
25
|
-
command: check.command,
|
|
26
|
-
exists: await check.existsPromise,
|
|
27
|
-
})));
|
|
28
|
-
const existingCommands = commandChecks
|
|
29
|
-
.filter((check) => check.exists)
|
|
30
|
-
.map((check) => check.command);
|
|
31
|
-
return existingCommands[0];
|
|
32
|
-
}
|
|
33
|
-
async function installVSCodeExtensions(projectPath, vsCodeCommand) {
|
|
34
|
-
// Installing extensions from inside WSL on Windows will result in the VSCode process never
|
|
35
|
-
// exiting for some reason. Thus, skip this step on Linux. (Linux users will probably be smart
|
|
36
|
-
// enough to install the extensions on their own.)
|
|
37
|
-
if (process.platform === "linux") {
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
const extensions = await getExtensionsFromJSON(projectPath);
|
|
41
|
-
for (const extensionName of extensions) {
|
|
42
|
-
// eslint-disable-next-line no-await-in-loop
|
|
43
|
-
await $ `${vsCodeCommand} --install-extension ${extensionName}`;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
async function getExtensionsFromJSON(projectPath) {
|
|
47
|
-
const extensionsJSONPath = path.join(projectPath, ".vscode", "extensions.json");
|
|
48
|
-
if (!isFile(extensionsJSONPath)) {
|
|
49
|
-
return [];
|
|
50
|
-
}
|
|
51
|
-
const extensionsJSON = await getJSONC(extensionsJSONPath);
|
|
52
|
-
const { recommendations } = extensionsJSON;
|
|
53
|
-
if (!Array.isArray(recommendations)) {
|
|
54
|
-
promptError('The "recommendations" field in the "extensions.json" file is not an array.');
|
|
55
|
-
}
|
|
56
|
-
for (const recommendation of recommendations) {
|
|
57
|
-
if (typeof recommendation !== "string") {
|
|
58
|
-
promptError('One of the entries in the "recommendations" field in the "extensions.json" file is not a string.');
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return recommendations;
|
|
62
|
-
}
|
|
63
|
-
async function promptVSCode(projectPath, VSCodeCommand, vscode, yes) {
|
|
64
|
-
if (vscode) {
|
|
65
|
-
// They supplied the "--vscode" command-line flag, so there is no need to prompt the user.
|
|
66
|
-
await openVSCode(projectPath, VSCodeCommand);
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
if (yes) {
|
|
70
|
-
// They supplied the "--yes" command-line flag, which implies that they want a silent install,
|
|
71
|
-
// so skip opening VSCode.
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
// The VSCode command does not work properly inside WSL on Windows.
|
|
75
|
-
if (process.platform === "linux") {
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
const shouldOpenVSCode = await getInputYesNo("Do you want to open your new project in VSCode now?");
|
|
79
|
-
if (shouldOpenVSCode) {
|
|
80
|
-
await openVSCode(projectPath, VSCodeCommand);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
async function openVSCode(projectPath, VSCodeCommand) {
|
|
84
|
-
await $ `${VSCodeCommand} ${projectPath}`;
|
|
85
|
-
}
|
package/dist/constants.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { findPackageRoot, getPackageJSONFieldsMandatory, PackageManager, } from "complete-node";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
export const CWD = process.cwd();
|
|
5
|
-
export const CURRENT_DIRECTORY_NAME = path.basename(CWD);
|
|
6
|
-
export const HOME_DIR = os.homedir();
|
|
7
|
-
const packageRoot = findPackageRoot();
|
|
8
|
-
const { name, version } = getPackageJSONFieldsMandatory(packageRoot, "name", "version");
|
|
9
|
-
export const PROJECT_NAME = name;
|
|
10
|
-
export const PROJECT_VERSION = version;
|
|
11
|
-
export const DEFAULT_PACKAGE_MANAGER = PackageManager.npm;
|
|
12
|
-
// ---------
|
|
13
|
-
const TEMPLATES_DIR = path.join(packageRoot, "file-templates");
|
|
14
|
-
export const TEMPLATES_STATIC_DIR = path.join(TEMPLATES_DIR, "static");
|
|
15
|
-
export const TEMPLATES_DYNAMIC_DIR = path.join(TEMPLATES_DIR, "dynamic");
|
|
16
|
-
export const ACTION_YML = "action.yml";
|
|
17
|
-
export const ACTION_YML_TEMPLATE_PATH = path.join(TEMPLATES_DYNAMIC_DIR, ".github", "workflows", "setup", ACTION_YML);
|
package/dist/git.js
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
|
-
import { $, commandExists, isFileAsync, readFileAsync } from "complete-node";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import yaml from "yaml";
|
|
5
|
-
import { HOME_DIR, PROJECT_NAME, PROJECT_VERSION } from "./constants.js";
|
|
6
|
-
import { getInputString, getInputYesNo, promptLog } from "./prompt.js";
|
|
7
|
-
/**
|
|
8
|
-
* If the GitHub CLI is installed, we can derive the user's GitHub username from their YAML
|
|
9
|
-
* configuration.
|
|
10
|
-
*/
|
|
11
|
-
export async function getGitHubUsername() {
|
|
12
|
-
const ghExists = await commandExists("gh");
|
|
13
|
-
if (!ghExists) {
|
|
14
|
-
return undefined;
|
|
15
|
-
}
|
|
16
|
-
const githubCLIHostsPath = getGithubCLIHostsPath();
|
|
17
|
-
if (githubCLIHostsPath === undefined) {
|
|
18
|
-
return undefined;
|
|
19
|
-
}
|
|
20
|
-
const hostsPathExists = await isFileAsync(githubCLIHostsPath);
|
|
21
|
-
if (!hostsPathExists) {
|
|
22
|
-
return undefined;
|
|
23
|
-
}
|
|
24
|
-
const configYAMLRaw = await readFileAsync(githubCLIHostsPath);
|
|
25
|
-
const configYAML = yaml.parse(configYAMLRaw);
|
|
26
|
-
const githubCom = configYAML["github.com"];
|
|
27
|
-
if (githubCom === undefined) {
|
|
28
|
-
return undefined;
|
|
29
|
-
}
|
|
30
|
-
const { user } = githubCom;
|
|
31
|
-
if (user === undefined || user === "") {
|
|
32
|
-
return undefined;
|
|
33
|
-
}
|
|
34
|
-
return user;
|
|
35
|
-
}
|
|
36
|
-
function getGithubCLIHostsPath() {
|
|
37
|
-
if (process.platform === "win32") {
|
|
38
|
-
const appData = process.env["APPDATA"];
|
|
39
|
-
if (appData === undefined || appData === "") {
|
|
40
|
-
return undefined;
|
|
41
|
-
}
|
|
42
|
-
return path.join(appData, "GitHub CLI", "hosts.yml");
|
|
43
|
-
}
|
|
44
|
-
// The location is the same on both macOS and Linux.
|
|
45
|
-
return path.join(HOME_DIR, ".config", "gh", "hosts.yml");
|
|
46
|
-
}
|
|
47
|
-
/** @returns The git remote URL. For example: git@github.com:alice/foo.git */
|
|
48
|
-
export async function promptGitHubRepoOrGitRemoteURL(projectName, yes, skipGit) {
|
|
49
|
-
if (skipGit) {
|
|
50
|
-
return undefined;
|
|
51
|
-
}
|
|
52
|
-
// Hard-code certain project names as never causing a Git repository to be initialized.
|
|
53
|
-
if (projectName.startsWith("test") || projectName === "foo") {
|
|
54
|
-
return undefined;
|
|
55
|
-
}
|
|
56
|
-
// We do not need to prompt the user if they do not have Git installed.
|
|
57
|
-
const gitExists = await commandExists("git");
|
|
58
|
-
if (!gitExists) {
|
|
59
|
-
promptLog('Git does not seem to be installed. (The "git" command is not in the path.) Skipping Git-related things.');
|
|
60
|
-
return undefined;
|
|
61
|
-
}
|
|
62
|
-
const gitHubUsername = await getGitHubUsername();
|
|
63
|
-
if (gitHubUsername !== undefined) {
|
|
64
|
-
const { exitCode } = await $ `gh repo view ${projectName}`;
|
|
65
|
-
const gitHubRepoExists = exitCode === 0;
|
|
66
|
-
const url = `https://github.com/${gitHubUsername}/${projectName}`;
|
|
67
|
-
if (gitHubRepoExists) {
|
|
68
|
-
promptLog(`Detected an existing GitHub repository at: ${chalk.green(url)}`);
|
|
69
|
-
const guessedRemoteURL = getGitRemoteURL(projectName, gitHubUsername);
|
|
70
|
-
if (yes) {
|
|
71
|
-
promptLog(`Using a Git remote URL of: ${chalk.green(guessedRemoteURL)}`);
|
|
72
|
-
return guessedRemoteURL;
|
|
73
|
-
}
|
|
74
|
-
const shouldUseGuessedURL = await getInputYesNo(`Do you want to use a Git remote URL of: ${chalk.green(guessedRemoteURL)}`);
|
|
75
|
-
if (shouldUseGuessedURL) {
|
|
76
|
-
return guessedRemoteURL;
|
|
77
|
-
}
|
|
78
|
-
// Assume that since they do not want to connect this project to the existing GitHub
|
|
79
|
-
// repository, they do not want to initialize a remote Git URL at all.
|
|
80
|
-
return undefined;
|
|
81
|
-
}
|
|
82
|
-
if (yes) {
|
|
83
|
-
await $ `gh repo create ${projectName} --public`;
|
|
84
|
-
promptLog(`Created a new GitHub repository at: ${chalk.green(url)}`);
|
|
85
|
-
return getGitRemoteURL(projectName, gitHubUsername);
|
|
86
|
-
}
|
|
87
|
-
const createNewGitHubRepo = await getInputYesNo(`Would you like to create a new GitHub repository at: ${chalk.green(url)}`);
|
|
88
|
-
if (createNewGitHubRepo) {
|
|
89
|
-
await $ `gh repo create ${projectName} --public`;
|
|
90
|
-
promptLog("Successfully created a new GitHub repository.");
|
|
91
|
-
return getGitRemoteURL(projectName, gitHubUsername);
|
|
92
|
-
}
|
|
93
|
-
// Assume that since they do not want to create a new GitHub repository, they do not want to
|
|
94
|
-
// initialize a remote Git URL at all.
|
|
95
|
-
return undefined;
|
|
96
|
-
}
|
|
97
|
-
const gitRemoteURL = await getInputString(`Paste in the remote Git URL for your project.
|
|
98
|
-
For example, if you have an SSH key, it would be something like:
|
|
99
|
-
${chalk.green("git@github.com:Alice/green-candle.git")}
|
|
100
|
-
If you don't have an SSH key, it would be something like:
|
|
101
|
-
${chalk.green("https://github.com/Alice/green-candle.git")}
|
|
102
|
-
If you don't want to initialize a Git repository for this project, press enter to skip.
|
|
103
|
-
`);
|
|
104
|
-
return gitRemoteURL === "" ? undefined : gitRemoteURL;
|
|
105
|
-
}
|
|
106
|
-
function getGitRemoteURL(projectName, gitHubUsername) {
|
|
107
|
-
return `git@github.com:${gitHubUsername}/${projectName}.git`;
|
|
108
|
-
}
|
|
109
|
-
export async function initGitRepository(projectPath, gitRemoteURL) {
|
|
110
|
-
const gitExists = await commandExists("git");
|
|
111
|
-
if (!gitExists) {
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
if (gitRemoteURL === undefined) {
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
const $$ = $({ cwd: projectPath });
|
|
118
|
-
await $$ `git init --initial-branch main`;
|
|
119
|
-
await $$ `git remote add origin ${gitRemoteURL}`;
|
|
120
|
-
const gitNameAndEmailConfigured = await isGitNameAndEmailConfigured();
|
|
121
|
-
if (gitNameAndEmailConfigured) {
|
|
122
|
-
await $$ `git add --all`;
|
|
123
|
-
const commitMessage = `chore: add files from ${PROJECT_NAME} ${PROJECT_VERSION} template`;
|
|
124
|
-
await $$ `git commit --message ${commitMessage}`;
|
|
125
|
-
await $$ `git push --set-upstream origin main`;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
async function isGitNameAndEmailConfigured() {
|
|
129
|
-
const { exitCode: nameExitCode } = await $ `git config --global user.name`;
|
|
130
|
-
const { exitCode: emailExitCode } = await $ `git config --global user.email`;
|
|
131
|
-
return nameExitCode === 0 && emailExitCode === 0;
|
|
132
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|