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

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/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "@tsparticles/cli-create-utils",
3
+ "version": "4.0.0-beta.12",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "publishConfig": {
7
+ "access": "public",
8
+ "tagVersionPrefix": "v"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/tsparticles/tsparticles.git",
13
+ "directory": "cli/commands/create-utils"
14
+ },
15
+ "prettier": "@tsparticles/prettier-config",
16
+ "dependencies": {
17
+ "@swc/core": "^1.15.32",
18
+ "commander": "^14.0.3",
19
+ "eslint": "^10.3.0",
20
+ "eslint-config-prettier": "^10.1.8",
21
+ "eslint-plugin-jsdoc": "^62.9.0",
22
+ "eslint-plugin-prettier": "^5.5.5",
23
+ "eslint-plugin-tsdoc": "^0.5.2",
24
+ "klaw": "^4.1.0",
25
+ "lookpath": "^1.2.3",
26
+ "dependency-cruiser": "^17.3.10",
27
+ "path-scurry": "^2.0.2",
28
+ "prettier": "^3.8.3",
29
+ "prettier-plugin-multiline-arrays": "^4.1.7",
30
+ "prompts": "^2.4.2",
31
+ "rimraf": "^6.1.3",
32
+ "swc-loader": "^0.2.7",
33
+ "typescript": "^6.0.3",
34
+ "typescript-eslint": "^8.59.1",
35
+ "webpack": "^5.106.2",
36
+ "@tsparticles/depcruise-config": "^4.0.0-beta.12",
37
+ "@tsparticles/prettier-config": "^4.0.0-beta.12",
38
+ "@tsparticles/eslint-config": "^4.0.0-beta.12",
39
+ "@tsparticles/webpack-plugin": "^4.0.0-beta.12",
40
+ "@tsparticles/tsconfig": "^4.0.0-beta.12"
41
+ },
42
+ "devDependencies": {
43
+ "@types/estree": "^1.0.8",
44
+ "@types/klaw": "^3.0.7",
45
+ "@types/node": "^25.6.0",
46
+ "@types/prompts": "^2.4.9",
47
+ "@types/webpack-env": "^1.18.8",
48
+ "browserslist": "^4.28.2",
49
+ "copyfiles": "^2.4.1",
50
+ "cross-env": "^10.1.0",
51
+ "terser-webpack-plugin": "^5.5.0",
52
+ "ts-node": "^10.9.2",
53
+ "vitest": "^4.1.5",
54
+ "webpack-bundle-analyzer": "^5.3.0",
55
+ "webpack-cli": "^7.0.2"
56
+ },
57
+ "description": "tsParticles CLI Utils",
58
+ "main": "dist/index.js",
59
+ "author": "Matteo Bruni <matteo.bruni@me.com>",
60
+ "scripts": {
61
+ "prettify:ci:src": "prettier --check ./src/*",
62
+ "prettify:ci:readme": "prettier --check ./README.md",
63
+ "prettify:src": "prettier --write ./src/*",
64
+ "prettify:readme": "prettier --write ./README.md",
65
+ "lint": "eslint src --ext .js,.jsx,.ts,.tsx --cache --cache-location .cache/eslint/.eslintcache --cache-strategy metadata --fix",
66
+ "lint:ci": "eslint src --ext .js,.jsx,.ts,.tsx --cache --cache-location .cache/eslint/.eslintcache --cache-strategy metadata",
67
+ "circular-deps": "depcruise src --include-only '^src' --validate --output-type err-long",
68
+ "compile": "pnpm run build:ts",
69
+ "compile:ci": "pnpm run build:ts",
70
+ "build:ts": "pnpm run build:ts:cjs",
71
+ "build:ts:cjs": "tsc -p src",
72
+ "test": "vitest run",
73
+ "build": "pnpm run clear:dist && pnpm run prettify:src && pnpm run lint && pnpm run compile && pnpm run circular-deps && pnpm run prettify:readme",
74
+ "build:ci": "pnpm run clear:dist && pnpm run prettify:ci:src && pnpm run lint:ci && pnpm run compile:ci && pnpm run prettify:ci:readme",
75
+ "clear:dist": "rimraf ./dist"
76
+ }
77
+ }
package/renovate.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3
+ "baseBranches": [
4
+ "dev"
5
+ ],
6
+ "extends": [
7
+ "config:base"
8
+ ]
9
+ }
@@ -0,0 +1,87 @@
1
+ import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
2
+ import { exec } from "node:child_process";
3
+ import { existsSync } from "node:fs";
4
+ import { lookpath } from "lookpath";
5
+ import path from "node:path";
6
+
7
+ export interface ReplaceTokensOptions {
8
+ path: string;
9
+ tokens: ReplaceTokensData[];
10
+ }
11
+
12
+ export interface ReplaceTokensData {
13
+ from: string | RegExp;
14
+ to: string;
15
+ }
16
+
17
+ /**
18
+ *
19
+ * @param options -
20
+ */
21
+ export async function replaceTokensInFiles(options: ReplaceTokensOptions[]): Promise<void> {
22
+ for (const item of options) {
23
+ const filePath = item.path;
24
+
25
+ let data = await readFile(filePath, "utf-8");
26
+
27
+ for (const token of item.tokens) {
28
+ const regex = token.from instanceof RegExp ? token.from : new RegExp(token.from, "g");
29
+
30
+ data = data.replace(regex, token.to);
31
+ }
32
+
33
+ await writeFile(filePath, data);
34
+ }
35
+ }
36
+
37
+ /**
38
+ *
39
+ * @param options -
40
+ */
41
+ export async function replaceTokensInFile(options: ReplaceTokensOptions): Promise<void> {
42
+ await replaceTokensInFiles([options]);
43
+ }
44
+
45
+ /**
46
+ *
47
+ * @param destination -
48
+ * @returns the destination directory path
49
+ */
50
+ export async function getDestinationDir(destination: string): Promise<string> {
51
+ const destPath = path.join(process.cwd(), destination),
52
+ destExists = existsSync(destPath);
53
+
54
+ if (destExists) {
55
+ const destContents = await readdir(destPath),
56
+ destContentsNoGit = destContents.filter(t => t !== ".git" && t !== ".gitignore");
57
+
58
+ if (destContentsNoGit.length) {
59
+ throw new Error("Destination folder already exists and is not empty");
60
+ }
61
+ }
62
+
63
+ await mkdir(destPath, { recursive: true });
64
+
65
+ return destPath;
66
+ }
67
+
68
+ /**
69
+ * @returns the repository URL
70
+ */
71
+ export async function getRepositoryUrl(): Promise<string> {
72
+ if (!(await lookpath("git"))) {
73
+ return "";
74
+ }
75
+
76
+ return new Promise<string>((resolve, reject) => {
77
+ exec("git config --get remote.origin.url", (error, stdout) => {
78
+ if (error) {
79
+ reject(error);
80
+
81
+ return;
82
+ }
83
+
84
+ resolve(stdout);
85
+ });
86
+ });
87
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./file-utils.js";
2
+ export * from "./string-utils.js";
3
+ export * from "./template-utils.js";
@@ -0,0 +1,41 @@
1
+ /**
2
+ * This function is used to capitalize a string.
3
+ * @param str - the string to capitalize (e.g. "my-string" -\> "MyString")
4
+ * @param splits - the characters used to split the string, if not provided the string will be considered a single word
5
+ * @returns the capitalized string
6
+ */
7
+ export function capitalize(str: string, ...splits: string[]): string {
8
+ let res = str.replace(/./, c => c.toUpperCase());
9
+
10
+ for (const split of splits) {
11
+ res = res
12
+ .split(split)
13
+ .map(w => w.replace(/./, c => c.toUpperCase()))
14
+ .join("");
15
+ }
16
+
17
+ return res;
18
+ }
19
+
20
+ /**
21
+ * This function is used to camelcase a string.
22
+ * @param str - the string to camelcase (e.g. "my-string" -\> "myString")
23
+ * @param splits - the characters used to split the string, if not provided the string will be considered a single word
24
+ * @returns the camelized string
25
+ */
26
+ export function camelize(str: string, ...splits: string[]): string {
27
+ return capitalize(str, ...splits).replace(/./, c => c.toLowerCase());
28
+ }
29
+
30
+ /**
31
+ * This function is used to dash a string.
32
+ * @param str - the string to dash (e.g. "myString" -\> "my-string")
33
+ * @returns the dashed string
34
+ */
35
+ export function dash(str: string): string {
36
+ const index = 0,
37
+ dashed = str.replace(/([A-Z])/g, g => `-${g[index]?.toLowerCase() ?? ""}`),
38
+ startPos = 1;
39
+
40
+ return dashed.startsWith("-") ? dashed.substring(startPos) : dashed;
41
+ }
@@ -0,0 +1,206 @@
1
+ import { cp } from "node:fs/promises";
2
+ import { exec } from "node:child_process";
3
+ import { lookpath } from "lookpath";
4
+ import path from "node:path";
5
+ import { replaceTokensInFile } from "./file-utils.js";
6
+
7
+ /**
8
+ * Updates the package.json file
9
+ * @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
14
+ */
15
+ export async function updatePackageFile(
16
+ destPath: string,
17
+ packageName: string,
18
+ description: string,
19
+ fileName: string,
20
+ repoUrl: string,
21
+ ): Promise<void> {
22
+ await replaceTokensInFile({
23
+ path: path.join(destPath, "package.json"),
24
+ tokens: [
25
+ {
26
+ from: /"tsParticles empty template"/g,
27
+ to: `"${description}"`,
28
+ },
29
+ {
30
+ 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")}"`,
48
+ },
49
+ ],
50
+ });
51
+ }
52
+
53
+ /**
54
+ * Updates the package.dist.json file with the new project name and description
55
+ * @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
60
+ */
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
+ ],
96
+ });
97
+ }
98
+
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
+ await replaceTokensInFile({
113
+ path: path.join(destPath, "webpack.config.js"),
114
+ tokens: [
115
+ {
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,
126
+ },
127
+ ],
128
+ });
129
+ }
130
+
131
+ /**
132
+ * Copies the empty template files to the destination path
133
+ * @param destPath - The path where the project will be created
134
+ */
135
+ export async function copyEmptyTemplateFiles(destPath: string): Promise<void> {
136
+ await cp(path.join(__dirname, "..", "files", "empty-project"), destPath, {
137
+ recursive: true,
138
+ force: true,
139
+ filter: copyFilter,
140
+ });
141
+ }
142
+
143
+ /**
144
+ * Filters the files to copy
145
+ * @param src - The source file path
146
+ * @returns true if the file should be copied
147
+ */
148
+ export function copyFilter(src: string): boolean {
149
+ return !(src.endsWith("node_modules") || src.endsWith("dist"));
150
+ }
151
+
152
+ /**
153
+ * Runs npm install in the given path
154
+ * @param destPath - The path where the project will be created
155
+ */
156
+ export async function runInstall(destPath: string): Promise<void> {
157
+ if (!(await lookpath("npm"))) {
158
+ return;
159
+ }
160
+
161
+ return new Promise((resolve, reject) => {
162
+ exec(
163
+ "npm install",
164
+ {
165
+ cwd: destPath,
166
+ },
167
+ error => {
168
+ if (error) {
169
+ reject(error);
170
+
171
+ return;
172
+ }
173
+
174
+ resolve();
175
+ },
176
+ );
177
+ });
178
+ }
179
+
180
+ /**
181
+ * Runs npm run build in the given path
182
+ * @param destPath - The path where the project will be build
183
+ */
184
+ export async function runBuild(destPath: string): Promise<void> {
185
+ if (!(await lookpath("npm"))) {
186
+ return;
187
+ }
188
+
189
+ return new Promise((resolve, reject) => {
190
+ exec(
191
+ "npm run build",
192
+ {
193
+ cwd: destPath,
194
+ },
195
+ error => {
196
+ if (error) {
197
+ reject(error);
198
+
199
+ return;
200
+ }
201
+
202
+ resolve();
203
+ },
204
+ );
205
+ });
206
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": ".",
5
+ "outDir": "../dist"
6
+ },
7
+ "references": [{ "path": "../" }],
8
+ "include": ["**/*"]
9
+ }
@@ -0,0 +1,109 @@
1
+ import { afterAll, describe, it, expect } from "vitest";
2
+ import {
3
+ getDestinationDir,
4
+ getRepositoryUrl,
5
+ replaceTokensInFile,
6
+ replaceTokensInFiles,
7
+ } from "../src";
8
+ import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
9
+ import path from "node:path";
10
+
11
+ describe("file-utils", async () => {
12
+ const baseDir = path.resolve("tmp-files");
13
+
14
+ await mkdir(baseDir, { recursive: true });
15
+
16
+ describe("replace tokens in files", async () => {
17
+ await writeFile(path.join(baseDir, "files1.txt"), "test");
18
+ await writeFile(path.join(baseDir, "files2.txt"), "test");
19
+
20
+ await replaceTokensInFiles([
21
+ {
22
+ path: path.join(baseDir, "files1.txt"),
23
+ tokens: [
24
+ {
25
+ from: "test",
26
+ to: "test1",
27
+ },
28
+ ],
29
+ },
30
+ {
31
+ path: path.join(baseDir, "files2.txt"),
32
+ tokens: [
33
+ {
34
+ from: "test",
35
+ to: "test2",
36
+ },
37
+ ],
38
+ },
39
+ ]);
40
+
41
+ it("should replace tokens in files", async () => {
42
+ const data1 = await readFile(path.join(baseDir, "files1.txt"), "utf8"),
43
+ data2 = await readFile(path.join(baseDir, "files2.txt"), "utf8");
44
+
45
+ expect(data1).toBe("test1");
46
+ expect(data2).toBe("test2");
47
+ });
48
+ });
49
+
50
+ describe("replace tokens in file", async () => {
51
+ await writeFile(path.join(baseDir, "file1.txt"), "test");
52
+
53
+ await replaceTokensInFile({
54
+ path: path.join(baseDir, "file1.txt"),
55
+ tokens: [
56
+ {
57
+ from: "test",
58
+ to: "test1",
59
+ },
60
+ ],
61
+ });
62
+
63
+ it("should replace tokens in files", async () => {
64
+ const data = await readFile(path.join(baseDir, "file1.txt"), "utf8");
65
+
66
+ expect(data).toBe("test1");
67
+ });
68
+ });
69
+
70
+ describe("get destination dir", async () => {
71
+ const destDir = await getDestinationDir(path.join("tmp-files", "baz"));
72
+
73
+ it("should return the destination dir", () => {
74
+ expect(destDir).toBe(path.join(baseDir, "baz"));
75
+ });
76
+
77
+ it("should return the destination dir", async () => {
78
+ const destDir2 = await getDestinationDir(path.join("tmp-files", "baz"));
79
+
80
+ expect(destDir2).toBe(path.join(baseDir, "baz"));
81
+ });
82
+
83
+ it("should throw exception", async () => {
84
+ await writeFile(path.join(baseDir, "baz", "tmp.txt"), "");
85
+
86
+ let ex = false;
87
+
88
+ try {
89
+ await getDestinationDir(path.join("tmp-files", "baz"));
90
+
91
+ console.log("never");
92
+ } catch {
93
+ ex = true;
94
+ }
95
+
96
+ expect(ex).toBe(true);
97
+ });
98
+ });
99
+
100
+ describe("get repository url", () => {
101
+ it("should return the repository url", async () => {
102
+ expect(await getRepositoryUrl()).not.toBe("");
103
+ });
104
+ });
105
+
106
+ afterAll(async () => {
107
+ await rm(baseDir, { recursive: true, force: true });
108
+ });
109
+ });
@@ -0,0 +1,130 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { camelize, capitalize, dash } from "../src";
3
+
4
+ describe("capitalize", () => {
5
+ describe("empty string", () => {
6
+ it("should successfully compare empty strings", () => {
7
+ expect(capitalize("")).toBe("");
8
+ });
9
+ });
10
+
11
+ describe("lowercase string", () => {
12
+ it("should return capitalized string", () => {
13
+ expect(capitalize("test")).toBe("Test");
14
+ });
15
+ });
16
+
17
+ describe("capitalized string", () => {
18
+ it("should return capitalized string", () => {
19
+ expect(capitalize("Test")).toBe("Test");
20
+ });
21
+ });
22
+
23
+ describe("multiple lowercase words string", () => {
24
+ it("should return capitalized string, no split", () => {
25
+ expect(capitalize("test test")).toBe("Test test");
26
+ });
27
+
28
+ it("should return capitalized string, split", () => {
29
+ expect(capitalize("test test", " ")).toBe("TestTest");
30
+ });
31
+
32
+ it("should return capitalized string, wrong split", () => {
33
+ expect(capitalize("test test", ";")).toBe("Test test");
34
+ });
35
+ });
36
+
37
+ describe("multiple uppercase words string", () => {
38
+ it("should return capitalized string, no split", () => {
39
+ expect(capitalize("Test Test")).toBe("Test Test");
40
+ });
41
+
42
+ it("should return capitalized string, split", () => {
43
+ expect(capitalize("Test Test", " ")).toBe("TestTest");
44
+ });
45
+
46
+ it("should return capitalized string, wrong split", () => {
47
+ expect(capitalize("Test Test", ";")).toBe("Test Test");
48
+ });
49
+ });
50
+ });
51
+
52
+ describe("camelize", () => {
53
+ describe("empty string", () => {
54
+ it("should successfully compare empty strings", () => {
55
+ expect(camelize("")).toBe("");
56
+ });
57
+ });
58
+
59
+ describe("lowercase string", () => {
60
+ it("should return camelized string", () => {
61
+ expect(camelize("test")).toBe("test");
62
+ });
63
+ });
64
+
65
+ describe("uppercase string", () => {
66
+ it("should return camelized string", () => {
67
+ expect(camelize("Test")).toBe("test");
68
+ });
69
+ });
70
+
71
+ describe("multiple lowercase words string", () => {
72
+ it("should return camelized string, no split", () => {
73
+ expect(camelize("test test")).toBe("test test");
74
+ });
75
+
76
+ it("should return camelized string, split", () => {
77
+ expect(camelize("test test", " ")).toBe("testTest");
78
+ });
79
+
80
+ it("should return camelized string, wrong split", () => {
81
+ expect(camelize("test test", ";")).toBe("test test");
82
+ });
83
+ });
84
+
85
+ describe("multiple uppercase words string", () => {
86
+ it("should return camelized string, no split", () => {
87
+ expect(camelize("Test Test")).toBe("test Test");
88
+ });
89
+
90
+ it("should return camelized string, split", () => {
91
+ expect(camelize("Test Test", " ")).toBe("testTest");
92
+ });
93
+
94
+ it("should return camelized string, wrong split", () => {
95
+ expect(camelize("Test Test", ";")).toBe("test Test");
96
+ });
97
+ });
98
+ });
99
+
100
+ describe("dash", () => {
101
+ describe("empty string", () => {
102
+ it("should successfully compare empty strings", () => {
103
+ expect(dash("")).toBe("");
104
+ });
105
+ });
106
+
107
+ describe("lowercase string", () => {
108
+ it("should return dashed string", () => {
109
+ expect(dash("test")).toBe("test");
110
+ });
111
+ });
112
+
113
+ describe("uppercase string", () => {
114
+ it("should return dashed string", () => {
115
+ expect(dash("Test")).toBe("test");
116
+ });
117
+ });
118
+
119
+ describe("capitalized word string", () => {
120
+ it("should return dashed string", () => {
121
+ expect(dash("TestTest")).toBe("test-test");
122
+ });
123
+ });
124
+
125
+ describe("camelized word string", () => {
126
+ it("should return dashed string", () => {
127
+ expect(dash("testTest")).toBe("test-test");
128
+ });
129
+ });
130
+ });
@@ -0,0 +1,15 @@
1
+ {
2
+ "extends": "../tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": ".",
5
+ "target": "ES2021",
6
+ "types": ["node", "vitest"],
7
+ "allowJs": true,
8
+ "declaration": false,
9
+ "removeComments": true,
10
+ "importHelpers": false,
11
+ "esModuleInterop": true
12
+ },
13
+ "references": [{ "path": "../src" }],
14
+ "include": ["**/*.ts"]
15
+ }