complete-cli 1.0.1 → 1.0.3
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.js +1 -0
- package/dist/140.js +1 -0
- package/dist/242.js +1 -0
- package/dist/269.js +1 -0
- package/dist/309.js +1 -0
- package/dist/367.js +1 -0
- package/dist/541.js +1 -0
- package/dist/55.js +1 -0
- package/dist/717.js +1 -0
- package/dist/742.js +1 -0
- package/dist/769.js +1 -0
- package/dist/770.js +1 -0
- package/dist/914.js +1 -0
- package/dist/951.js +1 -0
- package/dist/main.js +3 -25
- package/dist/main.js.LICENSE.txt +78 -0
- package/file-templates/dynamic/package.json +9 -10
- package/file-templates/static/scripts/build.ts +1 -2
- package/package.json +18 -20
- package/src/commands/CheckCommand.ts +1 -1
- package/src/commands/InitCommand.ts +1 -1
- package/src/commands/NukeCommand.ts +1 -2
- package/src/commands/PublishCommand.ts +4 -4
- package/src/commands/init/createProject.ts +3 -3
- package/src/commands/init/getAuthorName.ts +1 -1
- package/src/commands/init/packageManager.ts +8 -8
- package/src/commands/init/vsCodeInit.ts +27 -8
- package/src/git.ts +12 -9
- package/src/main.ts +0 -3
- package/dist/commands/CheckCommand.js +0 -142
- package/dist/commands/InitCommand.js +0 -64
- package/dist/commands/NukeCommand.js +0 -13
- package/dist/commands/PublishCommand.js +0 -163
- 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 -131
- 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 -75
- package/dist/constants.js +0 -17
- package/dist/git.js +0 -129
- package/dist/interfaces/GitHubCLIHostsYAML.js +0 -1
- package/dist/prompt.js +0 -53
- package/dist/validateNoteVersion.js +0 -25
- package/src/validateNoteVersion.ts +0 -39
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { Command, Option } from "clipanion";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { promptGitHubRepoOrGitRemoteURL } from "../git.js";
|
|
4
|
-
import { promptEnd, promptStart } from "../prompt.js";
|
|
5
|
-
import { checkIfProjectPathExists } from "./init/checkIfProjectPathExists.js";
|
|
6
|
-
import { createProject } from "./init/createProject.js";
|
|
7
|
-
import { getAuthorName } from "./init/getAuthorName.js";
|
|
8
|
-
import { getProjectPath } from "./init/getProjectPath.js";
|
|
9
|
-
import { getPackageManagerUsedForNewProject } from "./init/packageManager.js";
|
|
10
|
-
import { vsCodeInit } from "./init/vsCodeInit.js";
|
|
11
|
-
export class InitCommand extends Command {
|
|
12
|
-
static paths = [["init"], ["i"]];
|
|
13
|
-
// The first positional argument.
|
|
14
|
-
name = Option.String({
|
|
15
|
-
required: false,
|
|
16
|
-
});
|
|
17
|
-
yes = Option.Boolean("-y,--yes", false, {
|
|
18
|
-
description: 'Answer yes to every dialog option, similar to how "npm init --yes" works.',
|
|
19
|
-
});
|
|
20
|
-
useCurrentDirectory = Option.Boolean("--use-current-directory", false, {
|
|
21
|
-
description: "Use the current directory as the root for the project.",
|
|
22
|
-
});
|
|
23
|
-
customDirectory = Option.String("--custom-directory", {
|
|
24
|
-
description: "Initialize the project into the specified directory (instead of creating a new one based on the name or using the current working directory).",
|
|
25
|
-
});
|
|
26
|
-
vsCode = Option.Boolean("--vscode", false, {
|
|
27
|
-
description: "Open the project in VSCode after initialization.",
|
|
28
|
-
});
|
|
29
|
-
npm = Option.Boolean("--npm", false, {
|
|
30
|
-
description: "Use npm as the package manager.",
|
|
31
|
-
});
|
|
32
|
-
yarn = Option.Boolean("--yarn", false, {
|
|
33
|
-
description: "Use yarn as the package manager.",
|
|
34
|
-
});
|
|
35
|
-
pnpm = Option.Boolean("--pnpm", false, {
|
|
36
|
-
description: "Use pnpm as the package manager.",
|
|
37
|
-
});
|
|
38
|
-
skipGit = Option.Boolean("--skip-git", false, {
|
|
39
|
-
description: "Do not initialize Git.",
|
|
40
|
-
});
|
|
41
|
-
skipInstall = Option.Boolean("--skip-install", false, {
|
|
42
|
-
description: "Do not automatically install the dependencies after initializing the project.",
|
|
43
|
-
});
|
|
44
|
-
forceName = Option.Boolean("-f,--force-name", false, {
|
|
45
|
-
description: "Allow project names that are normally illegal.",
|
|
46
|
-
});
|
|
47
|
-
static usage = Command.Usage({
|
|
48
|
-
description: "Initialize a new TypeScript project.",
|
|
49
|
-
});
|
|
50
|
-
async execute() {
|
|
51
|
-
promptStart();
|
|
52
|
-
const packageManager = getPackageManagerUsedForNewProject(this);
|
|
53
|
-
// Prompt the end-user for some information (and validate it as we go).
|
|
54
|
-
const { projectPath, createNewDir } = await getProjectPath(this.name, this.useCurrentDirectory, this.customDirectory, this.yes, this.forceName);
|
|
55
|
-
await checkIfProjectPathExists(projectPath, this.yes);
|
|
56
|
-
const projectName = path.basename(projectPath);
|
|
57
|
-
const authorName = await getAuthorName();
|
|
58
|
-
const gitRemoteURL = await promptGitHubRepoOrGitRemoteURL(projectName, this.yes, this.skipGit);
|
|
59
|
-
// Now that we have asked the user all of the questions we need, we can create the project.
|
|
60
|
-
await createProject(projectName, authorName, projectPath, createNewDir, gitRemoteURL, this.skipInstall, packageManager);
|
|
61
|
-
await vsCodeInit(projectPath, this.vsCode, this.yes);
|
|
62
|
-
promptEnd("Initialization completed.");
|
|
63
|
-
}
|
|
64
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { Command } from "clipanion";
|
|
2
|
-
import { nukeDependencies } from "complete-node";
|
|
3
|
-
export class NukeCommand extends Command {
|
|
4
|
-
static paths = [["nuke"], ["n"]];
|
|
5
|
-
static usage = Command.Usage({
|
|
6
|
-
description: 'Delete the "node_modules" directory and the package lock file, then reinstall the dependencies for the project.',
|
|
7
|
-
});
|
|
8
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
9
|
-
async execute() {
|
|
10
|
-
nukeDependencies();
|
|
11
|
-
console.log("Successfully reinstalled dependencies from npm.");
|
|
12
|
-
}
|
|
13
|
-
}
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import { Command, Option } from "clipanion";
|
|
2
|
-
import { isSemanticVersion } from "complete-common";
|
|
3
|
-
import { fatalError, getPackageJSONField, getPackageJSONVersion, getPackageManagerInstallCommand, getPackageManagerLockFileName, getPackageManagersForProject, isFileAsync, isGitRepository, isGitRepositoryClean, isLoggedInToNPM, readFile, updatePackageJSONDependencies, writeFileAsync, } from "complete-node";
|
|
4
|
-
import { $ } from "execa";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import { CWD, DEFAULT_PACKAGE_MANAGER } from "../constants.js";
|
|
7
|
-
export class PublishCommand extends Command {
|
|
8
|
-
static paths = [["publish"], ["p"]];
|
|
9
|
-
// The first positional argument.
|
|
10
|
-
versionBumpType = Option.String({
|
|
11
|
-
required: true,
|
|
12
|
-
});
|
|
13
|
-
dryRun = Option.Boolean("--dry-run", false, {
|
|
14
|
-
description: "Skip committing/uploading & perform a Git reset afterward.",
|
|
15
|
-
});
|
|
16
|
-
skipLint = Option.Boolean("--skip-lint", false, {
|
|
17
|
-
description: "Skip linting before publishing.",
|
|
18
|
-
});
|
|
19
|
-
skipUpdate = Option.Boolean("--skip-update", false, {
|
|
20
|
-
description: "Skip updating the npm dependencies.",
|
|
21
|
-
});
|
|
22
|
-
static usage = Command.Usage({
|
|
23
|
-
description: "Bump the version & publish a new release.",
|
|
24
|
-
});
|
|
25
|
-
async execute() {
|
|
26
|
-
await validate();
|
|
27
|
-
await prePublish(this.versionBumpType, this.dryRun, this.skipLint, this.skipUpdate);
|
|
28
|
-
await publish(this.dryRun);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
async function validate() {
|
|
32
|
-
const isRepository = await isGitRepository(CWD);
|
|
33
|
-
if (!isRepository) {
|
|
34
|
-
fatalError("Failed to publish since the current working directory is not inside of a git repository.");
|
|
35
|
-
}
|
|
36
|
-
const isRepositoryClean = await isGitRepositoryClean(CWD);
|
|
37
|
-
if (!isRepositoryClean) {
|
|
38
|
-
fatalError("Failed to publish since the Git repository was dirty. Before publishing, you must push any current changes to git. (Version commits should not contain any code changes.)");
|
|
39
|
-
}
|
|
40
|
-
const packageJSONExists = await isFileAsync("package.json");
|
|
41
|
-
if (!packageJSONExists) {
|
|
42
|
-
fatalError('Failed to find the "package.json" file in the current working directory.');
|
|
43
|
-
}
|
|
44
|
-
const isLoggedIn = await isLoggedInToNPM();
|
|
45
|
-
if (!isLoggedIn) {
|
|
46
|
-
fatalError('Failed to publish since you are not logged in to npm. Try doing "npm login".');
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Before uploading the project, we want to update dependencies, increment the version, and perform
|
|
51
|
-
* some other steps.
|
|
52
|
-
*/
|
|
53
|
-
async function prePublish(versionBumpType, dryRun, skipLint, skipUpdate) {
|
|
54
|
-
const packageManager = getPackageManagerUsedForExistingProject();
|
|
55
|
-
await $ `git pull --rebase`;
|
|
56
|
-
await $ `git push`;
|
|
57
|
-
await updateDependencies(skipUpdate, dryRun, packageManager);
|
|
58
|
-
await incrementVersion(versionBumpType);
|
|
59
|
-
await unsetDevelopmentConstants();
|
|
60
|
-
await tryRunNPMScript("build");
|
|
61
|
-
if (!skipLint) {
|
|
62
|
-
await tryRunNPMScript("lint");
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
function getPackageManagerUsedForExistingProject() {
|
|
66
|
-
const packageManagers = getPackageManagersForProject(CWD);
|
|
67
|
-
if (packageManagers.length > 1) {
|
|
68
|
-
const packageManagerLockFileNames = packageManagers
|
|
69
|
-
.map((packageManager) => getPackageManagerLockFileName(packageManager))
|
|
70
|
-
.map((packageManagerLockFileName) => `"${packageManagerLockFileName}"`)
|
|
71
|
-
.join(" & ");
|
|
72
|
-
fatalError(`Multiple different kinds of package manager lock files were found (${packageManagerLockFileNames}). You should delete the ones that you are not using so that this program can correctly detect your package manager.`);
|
|
73
|
-
}
|
|
74
|
-
const packageManager = packageManagers[0];
|
|
75
|
-
if (packageManager !== undefined) {
|
|
76
|
-
return packageManager;
|
|
77
|
-
}
|
|
78
|
-
return DEFAULT_PACKAGE_MANAGER;
|
|
79
|
-
}
|
|
80
|
-
async function updateDependencies(skipUpdate, dryRun, packageManager) {
|
|
81
|
-
if (skipUpdate) {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
console.log('Updating dependencies in the "package.json" file...');
|
|
85
|
-
const hasNewDependencies = await updatePackageJSONDependencies(undefined);
|
|
86
|
-
if (hasNewDependencies) {
|
|
87
|
-
const command = getPackageManagerInstallCommand(packageManager);
|
|
88
|
-
const commandParts = command.split(" ");
|
|
89
|
-
await $ `${commandParts}`;
|
|
90
|
-
if (!dryRun) {
|
|
91
|
-
await gitCommitAllAndPush("chore: update dependencies");
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
async function gitCommitAllAndPush(message) {
|
|
96
|
-
await $ `git add --all`;
|
|
97
|
-
await $ `git commit --message ${message}`;
|
|
98
|
-
await $ `git push`;
|
|
99
|
-
console.log(`Committed and pushed to the git repository with a message of: ${message}`);
|
|
100
|
-
}
|
|
101
|
-
async function incrementVersion(versionBumpType) {
|
|
102
|
-
if (versionBumpType === "none") {
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
if (versionBumpType === "dev") {
|
|
106
|
-
throw new Error('The version bump type of "dev" is not currently supported.');
|
|
107
|
-
}
|
|
108
|
-
if (versionBumpType !== "major" &&
|
|
109
|
-
versionBumpType !== "minor" &&
|
|
110
|
-
versionBumpType !== "patch" &&
|
|
111
|
-
versionBumpType !== "dev" &&
|
|
112
|
-
!isSemanticVersion(versionBumpType)) {
|
|
113
|
-
fatalError('The version must be one of "major", "minor", "patch", "dev", "none", or a specific semantic version like "1.2.3".');
|
|
114
|
-
}
|
|
115
|
-
// We always use `npm` here to avoid differences with the version command between package
|
|
116
|
-
// managers. The "--no-git-tag-version" flag will prevent npm from both making a commit and adding
|
|
117
|
-
// a tag.
|
|
118
|
-
await $ `npm version ${versionBumpType} --no-git-tag-version`;
|
|
119
|
-
}
|
|
120
|
-
async function unsetDevelopmentConstants() {
|
|
121
|
-
const constantsTSPath = path.join(CWD, "src", "constants.ts");
|
|
122
|
-
const constantsTSExists = await isFileAsync(constantsTSPath);
|
|
123
|
-
if (!constantsTSExists) {
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
const constantsTS = readFile(constantsTSPath);
|
|
127
|
-
const newConstantsTS = constantsTS
|
|
128
|
-
.replace("const IS_DEV = true", "const IS_DEV = false")
|
|
129
|
-
.replace("const DEBUG = true", "const DEBUG = false");
|
|
130
|
-
await writeFileAsync(constantsTSPath, newConstantsTS);
|
|
131
|
-
}
|
|
132
|
-
async function tryRunNPMScript(scriptName) {
|
|
133
|
-
console.log(`Running: ${scriptName}`);
|
|
134
|
-
const $$ = $({
|
|
135
|
-
reject: false,
|
|
136
|
-
});
|
|
137
|
-
const { exitCode } = await $$ `npm run ${scriptName}`;
|
|
138
|
-
if (exitCode !== 0) {
|
|
139
|
-
await $ `git reset --hard`; // Revert the version changes.
|
|
140
|
-
fatalError(`Failed to run "${scriptName}".`);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
async function publish(dryRun) {
|
|
144
|
-
const projectName = getPackageJSONField(undefined, "name");
|
|
145
|
-
const version = getPackageJSONVersion(undefined);
|
|
146
|
-
if (dryRun) {
|
|
147
|
-
await $ `git reset --hard`; // Revert the version changes.
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
const releaseGitCommitMessage = getReleaseGitCommitMessage(version);
|
|
151
|
-
await gitCommitAllAndPush(releaseGitCommitMessage);
|
|
152
|
-
// - The "--access=public" flag is only technically needed for the first publish (unless the
|
|
153
|
-
// package is a scoped package), but it is saved here for posterity.
|
|
154
|
-
// - The "--ignore-scripts" flag is needed since the "npm publish" command will run the
|
|
155
|
-
// "publish" script in the "package.json" file, causing an infinite loop.
|
|
156
|
-
await $ `npm publish --access=public --ignore-scripts`;
|
|
157
|
-
}
|
|
158
|
-
const dryRunSuffix = dryRun ? " (dry-run)" : "";
|
|
159
|
-
console.log(`Published ${projectName} version ${version} successfully${dryRunSuffix}.`);
|
|
160
|
-
}
|
|
161
|
-
function getReleaseGitCommitMessage(version) {
|
|
162
|
-
return `chore: release ${version}`;
|
|
163
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { Command } from "clipanion";
|
|
2
|
-
import { updatePackageJSONDependencies } from "complete-node";
|
|
3
|
-
export class UpdateCommand extends Command {
|
|
4
|
-
static paths = [["update"], ["u"]];
|
|
5
|
-
static usage = Command.Usage({
|
|
6
|
-
description: 'Invoke "npm-check-updates" to update the dependencies inside of the "package.json" file and then install them.',
|
|
7
|
-
});
|
|
8
|
-
async execute() {
|
|
9
|
-
const hasNewDependencies = await updatePackageJSONDependencies();
|
|
10
|
-
const msg = hasNewDependencies
|
|
11
|
-
? "Successfully installed new Node.js dependencies."
|
|
12
|
-
: "There were no new dependency updates from npm.";
|
|
13
|
-
console.log(msg);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { strictEqual } from "node:assert";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import { getTruncatedText } from "./getTruncatedText.js";
|
|
4
|
-
test("no markers", () => {
|
|
5
|
-
const templateText = `
|
|
6
|
-
line 1
|
|
7
|
-
line 2
|
|
8
|
-
line 3
|
|
9
|
-
`.trim();
|
|
10
|
-
const { text } = getTruncatedText("test", templateText, new Set(), new Set());
|
|
11
|
-
strictEqual(text, templateText);
|
|
12
|
-
});
|
|
13
|
-
test("customization marker", () => {
|
|
14
|
-
const templateText = `
|
|
15
|
-
line 1
|
|
16
|
-
@template-customization-start
|
|
17
|
-
line 2
|
|
18
|
-
@template-customization-end
|
|
19
|
-
line 3
|
|
20
|
-
`.trim();
|
|
21
|
-
const expectedTemplateText = `
|
|
22
|
-
line 1
|
|
23
|
-
line 3
|
|
24
|
-
`.trim();
|
|
25
|
-
const { text } = getTruncatedText("test", templateText, new Set(), new Set());
|
|
26
|
-
strictEqual(text, expectedTemplateText);
|
|
27
|
-
});
|
|
28
|
-
test("ignore block marker part 1", () => {
|
|
29
|
-
const templateText = `
|
|
30
|
-
line 1
|
|
31
|
-
@template-ignore-block-start
|
|
32
|
-
// line 2
|
|
33
|
-
@template-ignore-block-end
|
|
34
|
-
line 3
|
|
35
|
-
`.trim();
|
|
36
|
-
const parsedTemplateText = `
|
|
37
|
-
line 1
|
|
38
|
-
line 3
|
|
39
|
-
`.trim();
|
|
40
|
-
const { text, ignoreLines } = getTruncatedText("test", templateText, new Set(), new Set());
|
|
41
|
-
strictEqual(text, parsedTemplateText);
|
|
42
|
-
strictEqual(ignoreLines.size, 1);
|
|
43
|
-
strictEqual([...ignoreLines][0], "line 2");
|
|
44
|
-
});
|
|
45
|
-
test("ignore block marker part 2", () => {
|
|
46
|
-
const templateText = `
|
|
47
|
-
line 1
|
|
48
|
-
line 2
|
|
49
|
-
line 3
|
|
50
|
-
`.trim();
|
|
51
|
-
const expectedTemplateText = `
|
|
52
|
-
line 1
|
|
53
|
-
line 3
|
|
54
|
-
`.trim();
|
|
55
|
-
const { text } = getTruncatedText("test", templateText, new Set(["line 2"]), new Set());
|
|
56
|
-
strictEqual(text, expectedTemplateText);
|
|
57
|
-
});
|
|
58
|
-
test("ignore next line part 1", () => {
|
|
59
|
-
const templateText = `
|
|
60
|
-
line 1
|
|
61
|
-
@template-ignore-next-line
|
|
62
|
-
line 2
|
|
63
|
-
line 3
|
|
64
|
-
`.trim();
|
|
65
|
-
const expectedTemplateText = `
|
|
66
|
-
line 1
|
|
67
|
-
line 3
|
|
68
|
-
`.trim();
|
|
69
|
-
const { text, linesBeforeIgnore } = getTruncatedText("test", templateText, new Set(), new Set());
|
|
70
|
-
strictEqual(linesBeforeIgnore.size, 1);
|
|
71
|
-
strictEqual([...linesBeforeIgnore][0], "line 1");
|
|
72
|
-
strictEqual(text, expectedTemplateText);
|
|
73
|
-
});
|
|
74
|
-
test("ignore next line part 2", () => {
|
|
75
|
-
const templateText = `
|
|
76
|
-
line 1
|
|
77
|
-
line 2
|
|
78
|
-
line 3
|
|
79
|
-
`.trim();
|
|
80
|
-
const expectedTemplateText = `
|
|
81
|
-
line 1
|
|
82
|
-
line 3
|
|
83
|
-
`.trim();
|
|
84
|
-
const { text } = getTruncatedText("test", templateText, new Set(), new Set(["line 1"]));
|
|
85
|
-
strictEqual(text, expectedTemplateText);
|
|
86
|
-
});
|
|
@@ -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,131 +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 { $ } from "execa";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import { ACTION_YML, ACTION_YML_TEMPLATE_PATH, TEMPLATES_DYNAMIC_DIR, TEMPLATES_STATIC_DIR, } from "../../constants.js";
|
|
7
|
-
import { initGitRepository } from "../../git.js";
|
|
8
|
-
import { promptError, promptLog, promptSpinnerStart } from "../../prompt.js";
|
|
9
|
-
export async function createProject(projectName, authorName, projectPath, createNewDir, gitRemoteURL, skipInstall, packageManager) {
|
|
10
|
-
if (createNewDir) {
|
|
11
|
-
makeDirectory(projectPath);
|
|
12
|
-
}
|
|
13
|
-
copyStaticFiles(projectPath);
|
|
14
|
-
copyDynamicFiles(projectName, authorName, projectPath, packageManager);
|
|
15
|
-
// There is no package manager lock files yet, so we have to pass "false" to this function.
|
|
16
|
-
const updated = await updatePackageJSONDependencies(projectPath, false, true);
|
|
17
|
-
if (!updated) {
|
|
18
|
-
promptError('Failed to update the dependencies in the "package.json" file.');
|
|
19
|
-
}
|
|
20
|
-
await installNodeModules(projectPath, skipInstall, packageManager);
|
|
21
|
-
await formatFiles(projectPath);
|
|
22
|
-
// Only make the initial commit once all of the files have been copied and formatted.
|
|
23
|
-
await initGitRepository(projectPath, gitRemoteURL);
|
|
24
|
-
promptLog(`Successfully created project: ${chalk.green(projectName)}`);
|
|
25
|
-
}
|
|
26
|
-
/** Copy static files, like "eslint.config.mjs", "tsconfig.json", etc. */
|
|
27
|
-
function copyStaticFiles(projectPath) {
|
|
28
|
-
copyTemplateDirectoryWithoutOverwriting(TEMPLATES_STATIC_DIR, projectPath);
|
|
29
|
-
// Rename "_gitattributes" to ".gitattributes". (If it is kept as ".gitattributes", then it won't
|
|
30
|
-
// be committed to git.)
|
|
31
|
-
const gitAttributesPath = path.join(projectPath, "_gitattributes");
|
|
32
|
-
const correctGitAttributesPath = path.join(projectPath, ".gitattributes");
|
|
33
|
-
renameFile(gitAttributesPath, correctGitAttributesPath);
|
|
34
|
-
// Rename "_cspell.config.jsonc" to "cspell.config.jsonc". (If it is kept as
|
|
35
|
-
// "cspell.config.jsonc", then local spell checking will fail.)
|
|
36
|
-
const cSpellConfigPath = path.join(projectPath, "_cspell.config.jsonc");
|
|
37
|
-
const correctCSpellConfigPath = path.join(projectPath, "cspell.config.jsonc");
|
|
38
|
-
renameFile(cSpellConfigPath, correctCSpellConfigPath);
|
|
39
|
-
}
|
|
40
|
-
function copyTemplateDirectoryWithoutOverwriting(templateDirPath, projectPath) {
|
|
41
|
-
const fileNames = getFileNamesInDirectory(templateDirPath);
|
|
42
|
-
for (const fileName of fileNames) {
|
|
43
|
-
const templateFilePath = path.join(templateDirPath, fileName);
|
|
44
|
-
const destinationFilePath = path.join(projectPath, fileName);
|
|
45
|
-
if (!isFile(destinationFilePath)) {
|
|
46
|
-
copyFileOrDirectory(templateFilePath, destinationFilePath);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
/** Copy files that need to have text replaced inside of them. */
|
|
51
|
-
function copyDynamicFiles(projectName, authorName, projectPath, packageManager) {
|
|
52
|
-
// `.github/workflows/setup/action.yml`
|
|
53
|
-
{
|
|
54
|
-
const fileName = ACTION_YML;
|
|
55
|
-
const templatePath = ACTION_YML_TEMPLATE_PATH;
|
|
56
|
-
const template = readFile(templatePath);
|
|
57
|
-
const installCommand = getPackageManagerInstallCICommand(packageManager);
|
|
58
|
-
const actionYML = template
|
|
59
|
-
.replaceAll("PACKAGE_MANAGER_NAME", packageManager)
|
|
60
|
-
.replaceAll("PACKAGE_MANAGER_INSTALL_COMMAND", installCommand);
|
|
61
|
-
const setupPath = path.join(projectPath, ".github", "workflows", "setup");
|
|
62
|
-
makeDirectory(setupPath);
|
|
63
|
-
const destinationPath = path.join(setupPath, fileName);
|
|
64
|
-
writeFile(destinationPath, actionYML);
|
|
65
|
-
}
|
|
66
|
-
// `.gitignore`
|
|
67
|
-
{
|
|
68
|
-
const templatePath = path.join(TEMPLATES_DYNAMIC_DIR, "_gitignore");
|
|
69
|
-
const template = readFile(templatePath);
|
|
70
|
-
// Prepend a header with the project name.
|
|
71
|
-
let separatorLine = "# ";
|
|
72
|
-
repeat(projectName.length, () => {
|
|
73
|
-
separatorLine += "-";
|
|
74
|
-
});
|
|
75
|
-
separatorLine += "\n";
|
|
76
|
-
const gitIgnoreHeader = `${separatorLine}# ${projectName}\n${separatorLine}\n`;
|
|
77
|
-
const nodeGitIgnorePath = path.join(TEMPLATES_DYNAMIC_DIR, "Node.gitignore");
|
|
78
|
-
const nodeGitIgnore = readFile(nodeGitIgnorePath);
|
|
79
|
-
// eslint-disable-next-line prefer-template
|
|
80
|
-
const gitignore = gitIgnoreHeader + template + "\n" + nodeGitIgnore;
|
|
81
|
-
// We need to replace the underscore with a period.
|
|
82
|
-
const destinationPath = path.join(projectPath, ".gitignore");
|
|
83
|
-
writeFile(destinationPath, gitignore);
|
|
84
|
-
}
|
|
85
|
-
// `package.json`
|
|
86
|
-
{
|
|
87
|
-
const templatePath = path.join(TEMPLATES_DYNAMIC_DIR, "package.json");
|
|
88
|
-
const template = readFile(templatePath);
|
|
89
|
-
const packageJSON = template
|
|
90
|
-
.replaceAll("PROJECT_NAME", projectName)
|
|
91
|
-
.replaceAll("AUTHOR_NAME", authorName ?? "unknown");
|
|
92
|
-
const destinationPath = path.join(projectPath, "package.json");
|
|
93
|
-
writeFile(destinationPath, packageJSON);
|
|
94
|
-
}
|
|
95
|
-
// `README.md`
|
|
96
|
-
{
|
|
97
|
-
const templatePath = path.join(TEMPLATES_DYNAMIC_DIR, "README.md");
|
|
98
|
-
const template = readFile(templatePath);
|
|
99
|
-
// "PROJECT-NAME" must be hyphenated, as using an underscore will break Prettier for some
|
|
100
|
-
// reason.
|
|
101
|
-
const command = getPackageManagerInstallCICommand(packageManager);
|
|
102
|
-
const readmeMD = template
|
|
103
|
-
.replaceAll("PROJECT-NAME", projectName)
|
|
104
|
-
.replaceAll("PACKAGE-MANAGER-INSTALL-COMMAND", command);
|
|
105
|
-
const destinationPath = path.join(projectPath, "README.md");
|
|
106
|
-
writeFile(destinationPath, readmeMD);
|
|
107
|
-
}
|
|
108
|
-
const srcPath = path.join(projectPath, "src");
|
|
109
|
-
makeDirectory(srcPath);
|
|
110
|
-
}
|
|
111
|
-
async function installNodeModules(projectPath, skipInstall, packageManager) {
|
|
112
|
-
if (skipInstall) {
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
const command = getPackageManagerInstallCommand(packageManager);
|
|
116
|
-
const s = promptSpinnerStart(`Installing the project dependencies with "${command}". (This can take a long time.)`);
|
|
117
|
-
try {
|
|
118
|
-
const $$ = $({ cwd: projectPath });
|
|
119
|
-
const commandParts = command.split(" ");
|
|
120
|
-
await $$ `${commandParts}`;
|
|
121
|
-
s.stop("Installed.");
|
|
122
|
-
}
|
|
123
|
-
catch {
|
|
124
|
-
s.stop("Failed to install.");
|
|
125
|
-
promptError("Exiting.");
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
async function formatFiles(projectPath) {
|
|
129
|
-
const $$ = $({ cwd: projectPath });
|
|
130
|
-
await $$ `prettier --write ${projectPath}`;
|
|
131
|
-
}
|
|
@@ -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 = 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
|
-
}
|