@tsparticles/cli-create-utils 4.0.0-beta.12 → 4.0.0-beta.16

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/src/file-utils.ts CHANGED
@@ -4,6 +4,8 @@ import { existsSync } from "node:fs";
4
4
  import { lookpath } from "lookpath";
5
5
  import path from "node:path";
6
6
 
7
+ const jsonIndentation = 2;
8
+
7
9
  export interface ReplaceTokensOptions {
8
10
  path: string;
9
11
  tokens: ReplaceTokensData[];
@@ -14,6 +16,8 @@ export interface ReplaceTokensData {
14
16
  to: string;
15
17
  }
16
18
 
19
+ export type JsonUpdater<T> = (data: T) => T;
20
+
17
21
  /**
18
22
  *
19
23
  * @param options -
@@ -42,6 +46,18 @@ export async function replaceTokensInFile(options: ReplaceTokensOptions): Promis
42
46
  await replaceTokensInFiles([options]);
43
47
  }
44
48
 
49
+ /**
50
+ * Updates a JSON file preserving standard indentation.
51
+ * @param filePath - The JSON file path
52
+ * @param updater - The updater callback
53
+ */
54
+ export async function updateJsonFile<T>(filePath: string, updater: JsonUpdater<T>): Promise<void> {
55
+ const data = JSON.parse(await readFile(filePath, "utf-8")) as T,
56
+ updatedData = updater(data);
57
+
58
+ await writeFile(filePath, `${JSON.stringify(updatedData, undefined, jsonIndentation)}\n`);
59
+ }
60
+
45
61
  /**
46
62
  *
47
63
  * @param destination -
@@ -73,15 +89,14 @@ export async function getRepositoryUrl(): Promise<string> {
73
89
  return "";
74
90
  }
75
91
 
76
- return new Promise<string>((resolve, reject) => {
92
+ return new Promise<string>(resolve => {
77
93
  exec("git config --get remote.origin.url", (error, stdout) => {
78
94
  if (error) {
79
- reject(error);
80
-
95
+ resolve("");
81
96
  return;
82
97
  }
83
98
 
84
- resolve(stdout);
99
+ resolve(stdout.trim());
85
100
  });
86
101
  });
87
102
  }
package/src/index.ts CHANGED
@@ -1,3 +1,5 @@
1
+ export * from "./create-project.js";
1
2
  export * from "./file-utils.js";
3
+ export * from "./prompt-utils.js";
2
4
  export * from "./string-utils.js";
3
5
  export * from "./template-utils.js";
@@ -0,0 +1,89 @@
1
+ import { getDestinationDir, getRepositoryUrl } from "./file-utils.js";
2
+ import prompts, { type PromptObject } from "prompts";
3
+ import { capitalize } from "./string-utils.js";
4
+ import path from "node:path";
5
+
6
+ export interface ISelectChoice<T extends string> {
7
+ title: string;
8
+ value: T;
9
+ }
10
+
11
+ export interface IProjectPromptOptions<T extends string = string> {
12
+ destination: string;
13
+ nameLabel: string;
14
+ select?: {
15
+ choices: ISelectChoice<T>[];
16
+ initial: T;
17
+ message: string;
18
+ name: string;
19
+ };
20
+ }
21
+
22
+ export interface IProjectPromptResult<T extends string = string> {
23
+ description: string;
24
+ destinationPath: string;
25
+ name: string;
26
+ repositoryUrl: string;
27
+ type: T | undefined;
28
+ }
29
+
30
+ /**
31
+ * Prompts for common project creation data.
32
+ * @param options - Prompt options
33
+ * @returns Prompt result with normalized values
34
+ */
35
+ export async function promptProjectData<T extends string = string>(
36
+ options: IProjectPromptOptions<T>,
37
+ ): Promise<IProjectPromptResult<T>> {
38
+ const destinationPath = await getDestinationDir(options.destination),
39
+ repositoryUrl = await getRepositoryUrl(),
40
+ initialName = destinationPath.split(path.sep).pop() ?? "",
41
+ questions: PromptObject[] = [
42
+ {
43
+ type: "text",
44
+ name: "name",
45
+ message: `What is the name of the ${options.nameLabel}?`,
46
+ validate: (value: string) => (value ? true : "The name can't be empty"),
47
+ initial: initialName,
48
+ },
49
+ {
50
+ type: "text",
51
+ name: "description",
52
+ message: `What is the description of the ${options.nameLabel}?`,
53
+ validate: (value: string) => (value ? true : "The description can't be empty"),
54
+ initial: capitalize(initialName),
55
+ },
56
+ {
57
+ initial: repositoryUrl,
58
+ message: "What is the repository URL? (optional)",
59
+ name: "repositoryUrl",
60
+ type: "text",
61
+ },
62
+ ];
63
+
64
+ if (options.select) {
65
+ questions.push({
66
+ type: "select",
67
+ name: options.select.name,
68
+ message: options.select.message,
69
+ choices: options.select.choices,
70
+ initial: options.select.choices.findIndex(t => t.value === options.select?.initial),
71
+ });
72
+ }
73
+
74
+ const answers = (await prompts(questions)) as {
75
+ [key: string]: unknown;
76
+ description: string;
77
+ name: string;
78
+ repositoryUrl: string;
79
+ },
80
+ type = options.select ? (answers[options.select.name] as T | undefined) : undefined;
81
+
82
+ return {
83
+ description: answers.description.trim(),
84
+ destinationPath,
85
+ name: answers.name.trim(),
86
+ repositoryUrl: answers.repositoryUrl.trim(),
87
+ type,
88
+ };
89
+ }
@@ -1,50 +1,52 @@
1
- import { cp } from "node:fs/promises";
2
- import { exec } from "node:child_process";
1
+ import * as childProcess from "node:child_process";
2
+ import * as fsPromises from "node:fs/promises";
3
+ import { replaceTokensInFile, updateJsonFile } from "./file-utils.js";
3
4
  import { lookpath } from "lookpath";
4
5
  import path from "node:path";
5
- import { replaceTokensInFile } from "./file-utils.js";
6
+
7
+ export interface IProjectMetadata {
8
+ description: string;
9
+ directory: string;
10
+ packageName: string;
11
+ repoUrl: string;
12
+ unpkgFileName: string;
13
+ }
6
14
 
7
15
  /**
8
16
  * Updates the package.json file
9
17
  * @param destPath - The path where the package.json file is located
10
- * @param packageName - The name of the package
11
- * @param description - The description of the package
12
- * @param fileName - The name of the output file
13
- * @param repoUrl - The repository URL
18
+ * @param metadata - The project metadata used for updates
14
19
  */
15
- export async function updatePackageFile(
16
- destPath: string,
17
- packageName: string,
18
- description: string,
19
- fileName: string,
20
- repoUrl: string,
21
- ): Promise<void> {
20
+ export async function updatePackageFile(destPath: string, metadata: IProjectMetadata): Promise<void> {
21
+ await updateJsonFile<Record<string, unknown>>(path.join(destPath, "package.json"), data => {
22
+ const publishConfig = (data["publishConfig"] ?? {}) as Record<string, unknown>;
23
+
24
+ return {
25
+ ...data,
26
+ name: metadata.packageName,
27
+ description: metadata.description,
28
+ repository: {
29
+ type: "git",
30
+ url: `git+${metadata.repoUrl}`,
31
+ directory: metadata.directory,
32
+ },
33
+ bugs: {
34
+ url: metadata.repoUrl.replace(/\.git$/, "/issues"),
35
+ },
36
+ publishConfig: {
37
+ ...publishConfig,
38
+ access: "public",
39
+ },
40
+ private: undefined,
41
+ };
42
+ });
43
+
22
44
  await replaceTokensInFile({
23
45
  path: path.join(destPath, "package.json"),
24
46
  tokens: [
25
- {
26
- from: /"tsParticles empty template"/g,
27
- to: `"${description}"`,
28
- },
29
47
  {
30
48
  from: /"tsparticles.empty.template.min.js"/g,
31
- to: `"${fileName}"`,
32
- },
33
- {
34
- from: /\s{4}"private": true,\r?\n?/g,
35
- to: "",
36
- },
37
- {
38
- from: /"@tsparticles\/empty-template"/g,
39
- to: `"${packageName}"`,
40
- },
41
- {
42
- from: /"url": "git\+https:\/\/github\.com\/tsparticles\/empty-template\.git"/g,
43
- to: `"url": "git+${repoUrl}"`,
44
- },
45
- {
46
- from: /"url": "https:\/\/github\.com\/tsparticles\/empty-template\/issues"/g,
47
- to: `"url": "${repoUrl.replace(".git", "/issues")}"`,
49
+ to: `"${metadata.unpkgFileName}"`,
48
50
  },
49
51
  ],
50
52
  });
@@ -53,76 +55,38 @@ export async function updatePackageFile(
53
55
  /**
54
56
  * Updates the package.dist.json file with the new project name and description
55
57
  * @param destPath - The path where the package.dist.json file is located
56
- * @param packageName - The name of the package
57
- * @param description - The description of the package
58
- * @param fileName - The name of the output file
59
- * @param repoUrl - The url of the repository
58
+ * @param metadata - The project metadata used for updates
60
59
  */
61
- export async function updatePackageDistFile(
62
- destPath: string,
63
- packageName: string,
64
- description: string,
65
- fileName: string,
66
- repoUrl: string,
67
- ): Promise<void> {
68
- await replaceTokensInFile({
69
- path: path.join(destPath, "package.dist.json"),
70
- tokens: [
71
- {
72
- from: /"tsParticles empty template"/g,
73
- to: `"${description}"`,
74
- },
75
- {
76
- from: /"tsparticles.empty.template.min.js"/g,
77
- to: `"${fileName}"`,
78
- },
79
- {
80
- from: /\s{4}"private": true,\r?\n?/g,
81
- to: "",
82
- },
83
- {
84
- from: /"@tsparticles\/empty-template"/g,
85
- to: `"${packageName}"`,
86
- },
87
- {
88
- from: /"url": "git\+https:\/\/github\.com\/tsparticles\/empty-template\.git"/g,
89
- to: `"url": "git+${repoUrl}"`,
90
- },
91
- {
92
- from: /"url": "https:\/\/github\.com\/tsparticles\/empty-template\/issues"/g,
93
- to: `"url": "${repoUrl.replace(".git", "/issues")}"`,
94
- },
95
- ],
60
+ export async function updatePackageDistFile(destPath: string, metadata: IProjectMetadata): Promise<void> {
61
+ await updateJsonFile<Record<string, unknown>>(path.join(destPath, "package.dist.json"), data => {
62
+ const publishConfig = (data["publishConfig"] ?? {}) as Record<string, unknown>;
63
+
64
+ return {
65
+ ...data,
66
+ name: metadata.packageName,
67
+ description: metadata.description,
68
+ repository: {
69
+ type: "git",
70
+ url: `git+${metadata.repoUrl}`,
71
+ directory: metadata.directory,
72
+ },
73
+ bugs: {
74
+ url: metadata.repoUrl.replace(/\.git$/, "/issues"),
75
+ },
76
+ publishConfig: {
77
+ ...publishConfig,
78
+ access: "public",
79
+ },
80
+ private: undefined,
81
+ };
96
82
  });
97
- }
98
83
 
99
- /**
100
- * Updates the webpack file with the new project name and description
101
- * @param destPath - The path where the project will be created
102
- * @param name - The name of the project
103
- * @param description - The description of the project
104
- * @param fnName - The name of the function to load the template
105
- */
106
- export async function updateWebpackFile(
107
- destPath: string,
108
- name: string,
109
- description: string,
110
- fnName: string,
111
- ): Promise<void> {
112
84
  await replaceTokensInFile({
113
- path: path.join(destPath, "webpack.config.js"),
85
+ path: path.join(destPath, "package.dist.json"),
114
86
  tokens: [
115
87
  {
116
- from: /"Empty"/g,
117
- to: `"${description}"`,
118
- },
119
- {
120
- from: /"empty"/g,
121
- to: `"${name}"`,
122
- },
123
- {
124
- from: /loadParticlesTemplate/g,
125
- to: fnName,
88
+ from: /"tsparticles.empty.template.min.js"/g,
89
+ to: `"${metadata.unpkgFileName}"`,
126
90
  },
127
91
  ],
128
92
  });
@@ -133,7 +97,7 @@ export async function updateWebpackFile(
133
97
  * @param destPath - The path where the project will be created
134
98
  */
135
99
  export async function copyEmptyTemplateFiles(destPath: string): Promise<void> {
136
- await cp(path.join(__dirname, "..", "files", "empty-project"), destPath, {
100
+ await fsPromises.cp(path.join(__dirname, "..", "files", "empty-project"), destPath, {
137
101
  recursive: true,
138
102
  force: true,
139
103
  filter: copyFilter,
@@ -159,7 +123,7 @@ export async function runInstall(destPath: string): Promise<void> {
159
123
  }
160
124
 
161
125
  return new Promise((resolve, reject) => {
162
- exec(
126
+ childProcess.exec(
163
127
  "npm install",
164
128
  {
165
129
  cwd: destPath,
@@ -187,7 +151,7 @@ export async function runBuild(destPath: string): Promise<void> {
187
151
  }
188
152
 
189
153
  return new Promise((resolve, reject) => {
190
- exec(
154
+ childProcess.exec(
191
155
  "npm run build",
192
156
  {
193
157
  cwd: destPath,
@@ -1 +0,0 @@
1
- [{"/Users/matteo/Projects/GitHub/tsparticles/tsparticles/cli/commands/create-utils/src/file-utils.ts":"1","/Users/matteo/Projects/GitHub/tsparticles/tsparticles/cli/commands/create-utils/src/index.ts":"2","/Users/matteo/Projects/GitHub/tsparticles/tsparticles/cli/commands/create-utils/src/string-utils.ts":"3","/Users/matteo/Projects/GitHub/tsparticles/tsparticles/cli/commands/create-utils/src/template-utils.ts":"4"},{"size":2011,"mtime":1777824895206,"results":"5","hashOfConfig":"6"},{"size":105,"mtime":1777824895206,"results":"7","hashOfConfig":"6"},{"size":1382,"mtime":1777824895206,"results":"8","hashOfConfig":"6"},{"size":5115,"mtime":1777824895207,"results":"9","hashOfConfig":"6"},{"filePath":"10","messages":"11","suppressedMessages":"12","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"1n6spun",{"filePath":"13","messages":"14","suppressedMessages":"15","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"16","messages":"17","suppressedMessages":"18","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"19","messages":"20","suppressedMessages":"21","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/Users/matteo/Projects/GitHub/tsparticles/tsparticles/cli/commands/create-utils/src/file-utils.ts",[],[],"/Users/matteo/Projects/GitHub/tsparticles/tsparticles/cli/commands/create-utils/src/index.ts",[],[],"/Users/matteo/Projects/GitHub/tsparticles/tsparticles/cli/commands/create-utils/src/string-utils.ts",[],[],"/Users/matteo/Projects/GitHub/tsparticles/tsparticles/cli/commands/create-utils/src/template-utils.ts",[],[]]