create-valaxy 0.14.61 → 0.15.1
Sign up to get free protection for your applications and to get access to all the features.
- package/bin/index.mjs +4 -0
- package/build.config.ts +3 -0
- package/dist/index.mjs +275 -0
- package/package.json +11 -4
- package/src/config.ts +45 -0
- package/src/index.ts +5 -203
- package/src/theme.ts +46 -0
- package/src/utils.ts +73 -0
- package/src/valaxy.ts +200 -0
- package/{template → template-blog}/package.json +2 -2
- package/{template → template-blog}/tsconfig.json +10 -10
- package/template-theme/README.md +17 -0
- package/index.js +0 -198
- /package/{template → template-blog}/.dockerignore +0 -0
- /package/{template → template-blog}/.editorconfig +0 -0
- /package/{template → template-blog}/.github/workflows/gh-pages.yml +0 -0
- /package/{template → template-blog}/.vscode/extensions.json +0 -0
- /package/{template → template-blog}/.vscode/settings.json +0 -0
- /package/{template → template-blog}/Dockerfile +0 -0
- /package/{template → template-blog}/README.md +0 -0
- /package/{template → template-blog}/_gitignore +0 -0
- /package/{template → template-blog}/_npmrc +0 -0
- /package/{template → template-blog}/components/README.md +0 -0
- /package/{template → template-blog}/layouts/README.md +0 -0
- /package/{template → template-blog}/locales/README.md +0 -0
- /package/{template → template-blog}/locales/en.yml +0 -0
- /package/{template → template-blog}/locales/zh-CN.yml +0 -0
- /package/{template → template-blog}/netlify.toml +0 -0
- /package/{template → template-blog}/pages/404.md +0 -0
- /package/{template → template-blog}/pages/about/index.md +0 -0
- /package/{template → template-blog}/pages/about/site.md +0 -0
- /package/{template → template-blog}/pages/archives/index.md +0 -0
- /package/{template → template-blog}/pages/categories/index.md +0 -0
- /package/{template → template-blog}/pages/links/index.md +0 -0
- /package/{template → template-blog}/pages/posts/hello-valaxy.md +0 -0
- /package/{template → template-blog}/pages/tags/index.md +0 -0
- /package/{template → template-blog}/public/_headers +0 -0
- /package/{template → template-blog}/public/favicon.svg +0 -0
- /package/{template → template-blog}/public/pwa-192x192.png +0 -0
- /package/{template → template-blog}/public/pwa-512x512.png +0 -0
- /package/{template → template-blog}/public/safari-pinned-tab.svg +0 -0
- /package/{template → template-blog}/site.config.ts +0 -0
- /package/{template → template-blog}/styles/README.md +0 -0
- /package/{template → template-blog}/styles/css-vars.scss +0 -0
- /package/{template → template-blog}/styles/index.scss +0 -0
- /package/{template → template-blog}/valaxy.config.ts +0 -0
- /package/{template → template-blog}/vercel.json +0 -0
package/bin/index.mjs
ADDED
package/build.config.ts
ADDED
package/dist/index.mjs
ADDED
@@ -0,0 +1,275 @@
|
|
1
|
+
import process from 'node:process';
|
2
|
+
import fs from 'node:fs';
|
3
|
+
import path from 'node:path';
|
4
|
+
import { fileURLToPath } from 'node:url';
|
5
|
+
import { execa } from 'execa';
|
6
|
+
import { red, dim, gray, blue, yellow, bold, green, cyan, reset } from 'kolorist';
|
7
|
+
import minimist from 'minimist';
|
8
|
+
import prompts from 'prompts';
|
9
|
+
|
10
|
+
const version = "0.15.1";
|
11
|
+
|
12
|
+
function formatTargetDir(targetDir) {
|
13
|
+
return targetDir?.trim().replace(/\/+$/g, "");
|
14
|
+
}
|
15
|
+
function isEmpty(path2) {
|
16
|
+
const files = fs.readdirSync(path2);
|
17
|
+
return files.length === 0 || files.length === 1 && files[0] === ".git";
|
18
|
+
}
|
19
|
+
function copy(src, dest) {
|
20
|
+
const stat = fs.statSync(src);
|
21
|
+
if (stat.isDirectory())
|
22
|
+
copyDir(src, dest);
|
23
|
+
else
|
24
|
+
fs.copyFileSync(src, dest);
|
25
|
+
}
|
26
|
+
function copyDir(srcDir, destDir) {
|
27
|
+
fs.mkdirSync(destDir, { recursive: true });
|
28
|
+
for (const file of fs.readdirSync(srcDir)) {
|
29
|
+
const srcFile = path.resolve(srcDir, file);
|
30
|
+
const destFile = path.resolve(destDir, file);
|
31
|
+
copy(srcFile, destFile);
|
32
|
+
}
|
33
|
+
}
|
34
|
+
function emptyDir(dir) {
|
35
|
+
console.log(`
|
36
|
+
${red(`Removing`)} ${dim(dir)}`);
|
37
|
+
if (!fs.existsSync(dir))
|
38
|
+
return;
|
39
|
+
for (const file of fs.readdirSync(dir)) {
|
40
|
+
if (file === ".git")
|
41
|
+
continue;
|
42
|
+
fs.rmSync(path.resolve(dir, file), { recursive: true, force: true });
|
43
|
+
}
|
44
|
+
}
|
45
|
+
function isValidPackageName(projectName) {
|
46
|
+
return /^(?:@[a-z\d\-*~][a-z\d\-*._~]*\/)?[a-z\d\-~][a-z\d\-._~]*$/.test(
|
47
|
+
projectName
|
48
|
+
);
|
49
|
+
}
|
50
|
+
function toValidPackageName(projectName) {
|
51
|
+
return projectName.trim().toLowerCase().replace(/\s+/g, "-").replace(/^[._]/, "").replace(/[^a-z\d\-~]+/g, "-");
|
52
|
+
}
|
53
|
+
function pkgFromUserAgent(userAgent) {
|
54
|
+
if (!userAgent)
|
55
|
+
return void 0;
|
56
|
+
const pkgSpec = userAgent.split(" ")[0];
|
57
|
+
const pkgSpecArr = pkgSpec.split("/");
|
58
|
+
return {
|
59
|
+
name: pkgSpecArr[0],
|
60
|
+
version: pkgSpecArr[1]
|
61
|
+
};
|
62
|
+
}
|
63
|
+
|
64
|
+
const starterTheme = {
|
65
|
+
name: "starter",
|
66
|
+
display: `Starter`,
|
67
|
+
repo: "https://github.com/valaxyjs/valaxy-theme-starter"
|
68
|
+
};
|
69
|
+
async function initTheme(options) {
|
70
|
+
const defaultThemeName = starterTheme.name;
|
71
|
+
let themeName = options.themeName || defaultThemeName;
|
72
|
+
if (!themeName) {
|
73
|
+
const { theme } = await prompts({
|
74
|
+
type: "text",
|
75
|
+
name: "theme",
|
76
|
+
message: "Theme name: valaxy-theme-",
|
77
|
+
initial: defaultThemeName
|
78
|
+
});
|
79
|
+
themeName = theme || defaultThemeName;
|
80
|
+
}
|
81
|
+
const targetDir = `valaxy-theme-${themeName.trim()}`;
|
82
|
+
console.log(` ${dim("npx")} ${gray("degit")} ${blue(starterTheme.repo)} ${yellow(targetDir)}`);
|
83
|
+
await execa("npx", ["degit", starterTheme.repo, targetDir], { stdio: "inherit" });
|
84
|
+
console.log();
|
85
|
+
console.log(` ${bold("Check it")}:`);
|
86
|
+
console.log();
|
87
|
+
console.log(`- Change ${bold("author")} name in ${yellow("LICENSE")} & ${green("package.json")} & ${blue(".github")}`);
|
88
|
+
console.log(`- Change ${blue("valaxy.config.ts")} theme: ${yellow("starter")} to ${cyan(`${themeName}`)}`);
|
89
|
+
console.log(`- Rename ${yellow(`valaxy-theme-${themeName}`)} to ${cyan(`valaxy-theme-${themeName}`)}`);
|
90
|
+
console.log();
|
91
|
+
console.log(` ${cyan("\u2728")}`);
|
92
|
+
console.log();
|
93
|
+
return `valaxy-theme-${themeName}`;
|
94
|
+
}
|
95
|
+
|
96
|
+
const renameFiles = {
|
97
|
+
_gitignore: ".gitignore",
|
98
|
+
_npmrc: ".npmrc"
|
99
|
+
};
|
100
|
+
const TEMPLATES = [
|
101
|
+
{
|
102
|
+
name: "blog",
|
103
|
+
display: `Blog`,
|
104
|
+
desc: "For Most Users",
|
105
|
+
message: "Project name:",
|
106
|
+
initial: "valaxy-blog",
|
107
|
+
color: cyan
|
108
|
+
},
|
109
|
+
{
|
110
|
+
name: "theme",
|
111
|
+
display: `Theme`,
|
112
|
+
desc: "For Theme Developers",
|
113
|
+
message: "Theme name: valaxy-theme-",
|
114
|
+
initial: "starter",
|
115
|
+
prefix: "valaxy-theme-",
|
116
|
+
color: green,
|
117
|
+
customInit: async (options) => {
|
118
|
+
return initTheme(options).catch((e) => {
|
119
|
+
console.error(e);
|
120
|
+
});
|
121
|
+
}
|
122
|
+
},
|
123
|
+
{
|
124
|
+
name: "addon",
|
125
|
+
display: `Addon`,
|
126
|
+
desc: "For Addon Developers",
|
127
|
+
message: "Addon name: valaxy-addon-",
|
128
|
+
initial: "template",
|
129
|
+
prefix: "valaxy-addon-",
|
130
|
+
color: yellow
|
131
|
+
}
|
132
|
+
];
|
133
|
+
const TEMPLATE_CHOICES = TEMPLATES.map((template) => template.name);
|
134
|
+
|
135
|
+
const argv = minimist(process.argv.slice(2));
|
136
|
+
const cwd = process.cwd();
|
137
|
+
const defaultTargetDir = "valaxy-blog";
|
138
|
+
async function init() {
|
139
|
+
console.log();
|
140
|
+
console.log(` ${bold("\u{1F30C} Valaxy")} ${blue(`v${version}`)}`);
|
141
|
+
console.log();
|
142
|
+
const argTargetDir = formatTargetDir(argv._[0]);
|
143
|
+
const argTemplate = argv.template || argv.t;
|
144
|
+
let targetDir = argTargetDir || defaultTargetDir;
|
145
|
+
const getProjectName = () => targetDir === "." ? path.basename(path.resolve()) : targetDir;
|
146
|
+
let result;
|
147
|
+
const { template } = await prompts({
|
148
|
+
type: argTemplate && TEMPLATE_CHOICES.includes(argTemplate) ? null : "select",
|
149
|
+
name: "template",
|
150
|
+
message: typeof argTemplate === "string" && !TEMPLATE_CHOICES.includes(argTemplate) ? reset(
|
151
|
+
`"${argTemplate}" isn't a valid template. Please choose from below: `
|
152
|
+
) : reset("Select a type:"),
|
153
|
+
initial: 0,
|
154
|
+
choices: TEMPLATES.map((template2) => {
|
155
|
+
const tColor = template2.color;
|
156
|
+
return {
|
157
|
+
title: tColor(template2.display || template2.name) + dim(` - ${template2.desc}`),
|
158
|
+
value: template2
|
159
|
+
};
|
160
|
+
})
|
161
|
+
});
|
162
|
+
try {
|
163
|
+
result = await prompts([
|
164
|
+
{
|
165
|
+
type: argTargetDir ? null : "text",
|
166
|
+
name: "projectName",
|
167
|
+
message: reset(template.message),
|
168
|
+
initial: template.initial,
|
169
|
+
onState: (state) => {
|
170
|
+
targetDir = formatTargetDir(template.prefix ? template.prefix + state.value : state.value) || template.initial;
|
171
|
+
}
|
172
|
+
},
|
173
|
+
{
|
174
|
+
type: () => !fs.existsSync(targetDir) || isEmpty(targetDir) ? null : "confirm",
|
175
|
+
name: "overwrite",
|
176
|
+
message: () => `${targetDir === "." ? "Current directory" : `Target directory "${targetDir}"`} is not empty. Remove existing files and continue?`
|
177
|
+
},
|
178
|
+
{
|
179
|
+
type: (_, { overwrite: overwrite2 }) => {
|
180
|
+
if (overwrite2 === false)
|
181
|
+
throw new Error(`${red("\u2716")} Operation cancelled`);
|
182
|
+
return null;
|
183
|
+
},
|
184
|
+
name: "overwriteChecker"
|
185
|
+
},
|
186
|
+
{
|
187
|
+
type: () => isValidPackageName(getProjectName()) ? null : "text",
|
188
|
+
name: "packageName",
|
189
|
+
message: reset("Package name:"),
|
190
|
+
initial: () => toValidPackageName(getProjectName()),
|
191
|
+
validate: (dir) => isValidPackageName(dir) || "Invalid package.json name"
|
192
|
+
}
|
193
|
+
], {
|
194
|
+
onCancel: () => {
|
195
|
+
throw new Error(`${red("\u2716")} Operation cancelled`);
|
196
|
+
}
|
197
|
+
});
|
198
|
+
} catch (cancelled) {
|
199
|
+
console.log(cancelled.message);
|
200
|
+
return;
|
201
|
+
}
|
202
|
+
const { projectName, overwrite } = result;
|
203
|
+
const dirName = template.prefix ? template.prefix + projectName : projectName;
|
204
|
+
const root = path.join(cwd, dirName);
|
205
|
+
console.log(root, overwrite);
|
206
|
+
if (overwrite)
|
207
|
+
emptyDir(root);
|
208
|
+
else if (!fs.existsSync(root))
|
209
|
+
fs.mkdirSync(root, { recursive: true });
|
210
|
+
if (template.customInit) {
|
211
|
+
await template.customInit({ themeName: projectName });
|
212
|
+
} else {
|
213
|
+
const templateDir = path.resolve(fileURLToPath(new URL(".", import.meta.url)), "..", `template-${template.name}`);
|
214
|
+
const write = (filename, content) => {
|
215
|
+
const targetPath = renameFiles[filename] ? path.join(root, renameFiles[filename]) : path.join(root, filename);
|
216
|
+
if (content)
|
217
|
+
fs.writeFileSync(targetPath, content);
|
218
|
+
else
|
219
|
+
copy(path.join(templateDir, filename), targetPath);
|
220
|
+
};
|
221
|
+
const files = fs.readdirSync(templateDir);
|
222
|
+
for (const file of files.filter((f) => f !== "package.json"))
|
223
|
+
write(file);
|
224
|
+
const pkg = await import(path.join(templateDir, "package.json"));
|
225
|
+
pkg.name = projectName || getProjectName();
|
226
|
+
write("package.json", JSON.stringify(pkg, null, 2));
|
227
|
+
}
|
228
|
+
console.log(` ${dim("\u{1F4C1}")} ${dim(root)}`);
|
229
|
+
console.log();
|
230
|
+
console.log(dim(" Scaffolding project in ") + targetDir + dim(" ..."));
|
231
|
+
const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent);
|
232
|
+
const pkgManager = pkgInfo ? pkgInfo.name : "npm";
|
233
|
+
const related = path.relative(cwd, root);
|
234
|
+
console.log(green(" Done.\n"));
|
235
|
+
if (template.name === "addon")
|
236
|
+
return;
|
237
|
+
const { yes } = await prompts({
|
238
|
+
type: "confirm",
|
239
|
+
name: "yes",
|
240
|
+
initial: "Y",
|
241
|
+
message: "Install and start it now?"
|
242
|
+
});
|
243
|
+
if (yes) {
|
244
|
+
const { agent } = await prompts({
|
245
|
+
name: "agent",
|
246
|
+
type: "select",
|
247
|
+
message: "Choose the agent",
|
248
|
+
choices: ["npm", "yarn", "pnpm"].map((i) => ({ value: i, title: i }))
|
249
|
+
});
|
250
|
+
if (!agent)
|
251
|
+
return;
|
252
|
+
await execa(agent, ["install"], { stdio: "inherit", cwd: root });
|
253
|
+
await execa(agent, ["run", "dev"], { stdio: "inherit", cwd: root });
|
254
|
+
} else {
|
255
|
+
console.log(dim("\n start it later by:\n"));
|
256
|
+
if (root !== cwd)
|
257
|
+
console.log(` ${green("cd")} ${blue(related)}`);
|
258
|
+
switch (pkgManager) {
|
259
|
+
case "yarn":
|
260
|
+
console.log(` ${green("yarn")}`);
|
261
|
+
console.log(` ${green("yarn")} dev`);
|
262
|
+
break;
|
263
|
+
default:
|
264
|
+
console.log(` ${green(pkgManager)} install`);
|
265
|
+
console.log(` ${green(pkgManager)} run dev`);
|
266
|
+
break;
|
267
|
+
}
|
268
|
+
console.log();
|
269
|
+
console.log(` ${cyan("\u2728")}`);
|
270
|
+
}
|
271
|
+
}
|
272
|
+
|
273
|
+
init().catch((e) => {
|
274
|
+
console.error(e);
|
275
|
+
});
|
package/package.json
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "create-valaxy",
|
3
|
-
"
|
3
|
+
"type": "module",
|
4
|
+
"version": "0.15.1",
|
4
5
|
"description": "Create Starter Template for Valaxy",
|
5
6
|
"author": {
|
6
7
|
"email": "me@yunyoujun.cn",
|
@@ -13,17 +14,23 @@
|
|
13
14
|
"type": "git",
|
14
15
|
"url": "https://github.com/YunYouJun/valaxy"
|
15
16
|
},
|
16
|
-
"main": "index.
|
17
|
+
"main": "bin/index.mjs",
|
17
18
|
"bin": {
|
18
|
-
"create-valaxy": "index.
|
19
|
+
"create-valaxy": "bin/index.mjs"
|
19
20
|
},
|
20
21
|
"engines": {
|
21
22
|
"node": "^14.18.0 || >=16.0.0"
|
22
23
|
},
|
23
24
|
"dependencies": {
|
24
|
-
"execa": "
|
25
|
+
"execa": "8.0.1",
|
25
26
|
"kolorist": "^1.8.0",
|
26
27
|
"minimist": "^1.2.8",
|
27
28
|
"prompts": "^2.4.2"
|
29
|
+
},
|
30
|
+
"scripts": {
|
31
|
+
"build": "unbuild",
|
32
|
+
"dev": "unbuild --stub",
|
33
|
+
"start": "tsx src/index.ts",
|
34
|
+
"typecheck": "tsc --noEmit"
|
28
35
|
}
|
29
36
|
}
|
package/src/config.ts
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
import { cyan, green, yellow } from 'kolorist'
|
2
|
+
import { initTheme } from './theme'
|
3
|
+
|
4
|
+
export const renameFiles: Record<string, string> = {
|
5
|
+
_gitignore: '.gitignore',
|
6
|
+
_npmrc: '.npmrc',
|
7
|
+
}
|
8
|
+
|
9
|
+
export const TEMPLATES = [
|
10
|
+
{
|
11
|
+
name: 'blog',
|
12
|
+
display: `Blog`,
|
13
|
+
desc: 'For Most Users',
|
14
|
+
message: 'Project name:',
|
15
|
+
initial: 'valaxy-blog',
|
16
|
+
color: cyan,
|
17
|
+
},
|
18
|
+
{
|
19
|
+
name: 'theme',
|
20
|
+
display: `Theme`,
|
21
|
+
desc: 'For Theme Developers',
|
22
|
+
message: 'Theme name: valaxy-theme-',
|
23
|
+
initial: 'starter',
|
24
|
+
prefix: 'valaxy-theme-',
|
25
|
+
color: green,
|
26
|
+
customInit: async (options: {
|
27
|
+
themeName?: string
|
28
|
+
}) => {
|
29
|
+
return initTheme(options).catch((e) => {
|
30
|
+
console.error(e)
|
31
|
+
})
|
32
|
+
},
|
33
|
+
},
|
34
|
+
{
|
35
|
+
name: 'addon',
|
36
|
+
display: `Addon`,
|
37
|
+
desc: 'For Addon Developers',
|
38
|
+
message: 'Addon name: valaxy-addon-',
|
39
|
+
initial: 'template',
|
40
|
+
prefix: 'valaxy-addon-',
|
41
|
+
color: yellow,
|
42
|
+
},
|
43
|
+
]
|
44
|
+
|
45
|
+
export const TEMPLATE_CHOICES = TEMPLATES.map(template => template.name)
|
package/src/index.ts
CHANGED
@@ -1,204 +1,6 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
// /* eslint-disable no-console */
|
1
|
+
/* eslint-disable no-console */
|
2
|
+
import { init } from './valaxy'
|
4
3
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
// import fs from 'node:fs'
|
9
|
-
// import path from 'node:path'
|
10
|
-
// import prompts from 'prompts'
|
11
|
-
// import execa from 'execa'
|
12
|
-
// import { blue, bold, cyan, dim, gray, green, red, yellow } from 'kolorist'
|
13
|
-
// import minimist from 'minimist'
|
14
|
-
// import { version } from '../package.json'
|
15
|
-
|
16
|
-
// const argv = minimist(process.argv.slice(2))
|
17
|
-
|
18
|
-
// const cwd = process.cwd()
|
19
|
-
|
20
|
-
// const renameFiles = {
|
21
|
-
// _gitignore: '.gitignore',
|
22
|
-
// _npmrc: '.npmrc',
|
23
|
-
// }
|
24
|
-
|
25
|
-
// async function init() {
|
26
|
-
// console.log()
|
27
|
-
// console.log(` ${bold('🌌 Valaxy')} ${blue(`v${version}`)}`)
|
28
|
-
// console.log()
|
29
|
-
|
30
|
-
// let targetDir = argv._[0]
|
31
|
-
// if (!targetDir) {
|
32
|
-
// /**
|
33
|
-
// * @type {{ projectName: string }}
|
34
|
-
// */
|
35
|
-
// const { projectName } = await prompts({
|
36
|
-
// type: 'text',
|
37
|
-
// name: 'projectName',
|
38
|
-
// message: 'Project name:',
|
39
|
-
// initial: 'valaxy-blog',
|
40
|
-
// })
|
41
|
-
// targetDir = projectName.trim()
|
42
|
-
|
43
|
-
// const packageName = await getValidPackageName(targetDir)
|
44
|
-
// const root = path.join(cwd, targetDir)
|
45
|
-
|
46
|
-
// if (!fs.existsSync(root)) {
|
47
|
-
// fs.mkdirSync(root, { recursive: true })
|
48
|
-
// }
|
49
|
-
// else {
|
50
|
-
// const existing = fs.readdirSync(root)
|
51
|
-
// if (existing.length) {
|
52
|
-
// console.log(yellow(` Target directory "${targetDir}" is not empty.`))
|
53
|
-
// /**
|
54
|
-
// * @type {{ yes: boolean }}
|
55
|
-
// */
|
56
|
-
// const { yes } = await prompts({
|
57
|
-
// type: 'confirm',
|
58
|
-
// name: 'yes',
|
59
|
-
// initial: 'Y',
|
60
|
-
// message: 'Remove existing files and continue?',
|
61
|
-
// })
|
62
|
-
// if (yes)
|
63
|
-
// emptyDir(root)
|
64
|
-
|
65
|
-
// else
|
66
|
-
// return
|
67
|
-
// }
|
68
|
-
// }
|
69
|
-
|
70
|
-
// console.log(` ${dim('📁')} ${dim(root)}`)
|
71
|
-
// console.log()
|
72
|
-
// console.log(dim(' Scaffolding project in ') + targetDir + dim(' ...'))
|
73
|
-
|
74
|
-
// const templateDir = path.join(__dirname, 'template')
|
75
|
-
// const write = (file, content) => {
|
76
|
-
// const targetPath = renameFiles[file]
|
77
|
-
// ? path.join(root, renameFiles[file])
|
78
|
-
// : path.join(root, file)
|
79
|
-
// if (content)
|
80
|
-
// fs.writeFileSync(targetPath, content)
|
81
|
-
|
82
|
-
// else
|
83
|
-
// copy(path.join(templateDir, file), targetPath)
|
84
|
-
// }
|
85
|
-
|
86
|
-
// const files = fs.readdirSync(templateDir)
|
87
|
-
// for (const file of files.filter(f => f !== 'package.json'))
|
88
|
-
// write(file)
|
89
|
-
|
90
|
-
// // write pkg name & version
|
91
|
-
// // eslint-disable-next-line @typescript-eslint/no-require-imports
|
92
|
-
// const pkg = require(path.join(templateDir, 'package.json'))
|
93
|
-
// if (packageName)
|
94
|
-
// pkg.name = packageName
|
95
|
-
// pkg.version = version
|
96
|
-
|
97
|
-
// write('package.json', JSON.stringify(pkg, null, 2))
|
98
|
-
|
99
|
-
// const pkgManager = (/pnpm/.test(process.env.npm_execpath) || /pnpm/.test(process.env.npm_config_user_agent))
|
100
|
-
// ? 'pnpm'
|
101
|
-
// : /yarn/.test(process.env.npm_execpath) ? 'yarn' : 'npm'
|
102
|
-
|
103
|
-
// const related = path.relative(cwd, root)
|
104
|
-
// console.log(green(' Done.\n'))
|
105
|
-
|
106
|
-
// /**
|
107
|
-
// * @type {{ yes: boolean }}
|
108
|
-
// */
|
109
|
-
// const { yes } = await prompts({
|
110
|
-
// type: 'confirm',
|
111
|
-
// name: 'yes',
|
112
|
-
// initial: 'Y',
|
113
|
-
// message: 'Install and start it now?',
|
114
|
-
// })
|
115
|
-
|
116
|
-
// if (yes) {
|
117
|
-
// const { agent } = await prompts({
|
118
|
-
// name: 'agent',
|
119
|
-
// type: 'select',
|
120
|
-
// message: 'Choose the agent',
|
121
|
-
// choices: ['npm', 'yarn', 'pnpm'].map(i => ({ value: i, title: i })),
|
122
|
-
// })
|
123
|
-
|
124
|
-
// if (!agent)
|
125
|
-
// return
|
126
|
-
|
127
|
-
// await execa(agent, ['install'], { stdio: 'inherit', cwd: root })
|
128
|
-
// await execa(agent, ['run', 'dev'], { stdio: 'inherit', cwd: root })
|
129
|
-
// }
|
130
|
-
// else {
|
131
|
-
// console.log(dim('\n start it later by:\n'))
|
132
|
-
// if (root !== cwd)
|
133
|
-
// console.log(blue(` cd ${bold(related)}`))
|
134
|
-
|
135
|
-
// console.log(blue(` ${pkgManager === 'yarn' ? 'yarn' : `${pkgManager} install`}`))
|
136
|
-
// console.log(blue(` ${pkgManager === 'yarn' ? 'yarn dev' : `${pkgManager} run dev`}`))
|
137
|
-
// console.log()
|
138
|
-
// console.log(` ${cyan('✨')}`)
|
139
|
-
// console.log()
|
140
|
-
// }
|
141
|
-
// }
|
142
|
-
// }
|
143
|
-
|
144
|
-
// function copy(src, dest) {
|
145
|
-
// const stat = fs.statSync(src)
|
146
|
-
// if (stat.isDirectory())
|
147
|
-
// copyDir(src, dest)
|
148
|
-
|
149
|
-
// else
|
150
|
-
// fs.copyFileSync(src, dest)
|
151
|
-
// }
|
152
|
-
|
153
|
-
// function copyDir(srcDir, destDir) {
|
154
|
-
// fs.mkdirSync(destDir, { recursive: true })
|
155
|
-
// for (const file of fs.readdirSync(srcDir)) {
|
156
|
-
// const srcFile = path.resolve(srcDir, file)
|
157
|
-
// const destFile = path.resolve(destDir, file)
|
158
|
-
// copy(srcFile, destFile)
|
159
|
-
// }
|
160
|
-
// }
|
161
|
-
|
162
|
-
// async function getValidPackageName(projectName) {
|
163
|
-
// projectName = path.basename(projectName)
|
164
|
-
// const packageNameRegExp = /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/
|
165
|
-
// if (packageNameRegExp.test(projectName)) {
|
166
|
-
// return projectName
|
167
|
-
// }
|
168
|
-
// else {
|
169
|
-
// const suggestedPackageName = projectName
|
170
|
-
// .trim()
|
171
|
-
// .toLowerCase()
|
172
|
-
// .replace(/\s+/g, '-')
|
173
|
-
// .replace(/^[._]/, '')
|
174
|
-
// .replace(/[^a-z0-9-~]+/g, '-')
|
175
|
-
|
176
|
-
// /**
|
177
|
-
// * @type {{ inputPackageName: string }}
|
178
|
-
// */
|
179
|
-
// const { inputPackageName } = await prompts({
|
180
|
-
// type: 'text',
|
181
|
-
// name: 'inputPackageName',
|
182
|
-
// message: 'Package name:',
|
183
|
-
// initial: suggestedPackageName,
|
184
|
-
// // validate: input =>
|
185
|
-
// // packageNameRegExp.test(input) ? true : 'Invalid package.json name',
|
186
|
-
// })
|
187
|
-
// return inputPackageName
|
188
|
-
// }
|
189
|
-
// }
|
190
|
-
|
191
|
-
// /**
|
192
|
-
// * @param {string} dir
|
193
|
-
// * @returns
|
194
|
-
// */
|
195
|
-
// function emptyDir(dir) {
|
196
|
-
// if (!fs.existsSync(dir))
|
197
|
-
// return
|
198
|
-
// console.log(red(' Remove ') + gray(dir))
|
199
|
-
// fs.rmSync(dir, { recursive: true, force: true })
|
200
|
-
// }
|
201
|
-
|
202
|
-
// init().catch((e) => {
|
203
|
-
// console.error(e)
|
204
|
-
// })
|
4
|
+
init().catch((e) => {
|
5
|
+
console.error(e)
|
6
|
+
})
|
package/src/theme.ts
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
/* eslint-disable no-console */
|
2
|
+
import prompts from 'prompts'
|
3
|
+
import { execa } from 'execa'
|
4
|
+
import { blue, bold, cyan, dim, gray, green, yellow } from 'kolorist'
|
5
|
+
|
6
|
+
const starterTheme = {
|
7
|
+
name: 'starter',
|
8
|
+
display: `Starter`,
|
9
|
+
repo: 'https://github.com/valaxyjs/valaxy-theme-starter',
|
10
|
+
}
|
11
|
+
|
12
|
+
export async function initTheme(options: {
|
13
|
+
themeName?: string
|
14
|
+
}) {
|
15
|
+
const defaultThemeName = starterTheme.name
|
16
|
+
let themeName = options.themeName || defaultThemeName
|
17
|
+
if (!themeName) {
|
18
|
+
/**
|
19
|
+
* @type {{ theme: string }}
|
20
|
+
*/
|
21
|
+
const { theme } = await prompts({
|
22
|
+
type: 'text',
|
23
|
+
name: 'theme',
|
24
|
+
message: 'Theme name: valaxy-theme-',
|
25
|
+
initial: defaultThemeName,
|
26
|
+
})
|
27
|
+
themeName = theme || defaultThemeName
|
28
|
+
}
|
29
|
+
|
30
|
+
const targetDir = `valaxy-theme-${themeName!.trim()}`
|
31
|
+
|
32
|
+
console.log(` ${dim('npx')} ${gray('degit')} ${blue(starterTheme.repo)} ${yellow(targetDir)}`)
|
33
|
+
await execa('npx', ['degit', starterTheme.repo, targetDir], { stdio: 'inherit' })
|
34
|
+
|
35
|
+
console.log()
|
36
|
+
console.log(` ${bold('Check it')}:`)
|
37
|
+
console.log()
|
38
|
+
console.log(`- Change ${bold('author')} name in ${yellow('LICENSE')} & ${green('package.json')} & ${blue('.github')}`)
|
39
|
+
console.log(`- Change ${blue('valaxy.config.ts')} theme: ${yellow('starter')} to ${cyan(`${themeName}`)}`)
|
40
|
+
console.log(`- Rename ${yellow(`valaxy-theme-${themeName}`)} to ${cyan(`valaxy-theme-${themeName}`)}`)
|
41
|
+
console.log()
|
42
|
+
console.log(` ${cyan('✨')}`)
|
43
|
+
console.log()
|
44
|
+
|
45
|
+
return `valaxy-theme-${themeName}`
|
46
|
+
}
|
package/src/utils.ts
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
import fs from 'node:fs'
|
2
|
+
import path from 'node:path'
|
3
|
+
import { dim, red } from 'kolorist'
|
4
|
+
|
5
|
+
/**
|
6
|
+
* remove trailing slash
|
7
|
+
*/
|
8
|
+
export function formatTargetDir(targetDir: string | undefined) {
|
9
|
+
return targetDir?.trim().replace(/\/+$/g, '')
|
10
|
+
}
|
11
|
+
|
12
|
+
export function isEmpty(path: string) {
|
13
|
+
const files = fs.readdirSync(path)
|
14
|
+
return files.length === 0 || (files.length === 1 && files[0] === '.git')
|
15
|
+
}
|
16
|
+
|
17
|
+
export function copy(src: string, dest: string) {
|
18
|
+
const stat = fs.statSync(src)
|
19
|
+
if (stat.isDirectory())
|
20
|
+
copyDir(src, dest)
|
21
|
+
|
22
|
+
else
|
23
|
+
fs.copyFileSync(src, dest)
|
24
|
+
}
|
25
|
+
|
26
|
+
export function copyDir(srcDir: string, destDir: string) {
|
27
|
+
fs.mkdirSync(destDir, { recursive: true })
|
28
|
+
for (const file of fs.readdirSync(srcDir)) {
|
29
|
+
const srcFile = path.resolve(srcDir, file)
|
30
|
+
const destFile = path.resolve(destDir, file)
|
31
|
+
copy(srcFile, destFile)
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
export function emptyDir(dir: string) {
|
36
|
+
// eslint-disable-next-line no-console
|
37
|
+
console.log(`\n ${red(`Removing`)} ${dim(dir)}`)
|
38
|
+
if (!fs.existsSync(dir))
|
39
|
+
return
|
40
|
+
|
41
|
+
for (const file of fs.readdirSync(dir)) {
|
42
|
+
if (file === '.git')
|
43
|
+
continue
|
44
|
+
|
45
|
+
fs.rmSync(path.resolve(dir, file), { recursive: true, force: true })
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
export function isValidPackageName(projectName: string) {
|
50
|
+
return /^(?:@[a-z\d\-*~][a-z\d\-*._~]*\/)?[a-z\d\-~][a-z\d\-._~]*$/.test(
|
51
|
+
projectName,
|
52
|
+
)
|
53
|
+
}
|
54
|
+
|
55
|
+
export function toValidPackageName(projectName: string) {
|
56
|
+
return projectName
|
57
|
+
.trim()
|
58
|
+
.toLowerCase()
|
59
|
+
.replace(/\s+/g, '-')
|
60
|
+
.replace(/^[._]/, '')
|
61
|
+
.replace(/[^a-z\d\-~]+/g, '-')
|
62
|
+
}
|
63
|
+
|
64
|
+
export function pkgFromUserAgent(userAgent: string | undefined) {
|
65
|
+
if (!userAgent)
|
66
|
+
return undefined
|
67
|
+
const pkgSpec = userAgent.split(' ')[0]
|
68
|
+
const pkgSpecArr = pkgSpec.split('/')
|
69
|
+
return {
|
70
|
+
name: pkgSpecArr[0],
|
71
|
+
version: pkgSpecArr[1],
|
72
|
+
}
|
73
|
+
}
|
package/src/valaxy.ts
ADDED
@@ -0,0 +1,200 @@
|
|
1
|
+
/* eslint-disable no-console */
|
2
|
+
import process from 'node:process'
|
3
|
+
import fs from 'node:fs'
|
4
|
+
import path from 'node:path'
|
5
|
+
import { fileURLToPath } from 'node:url'
|
6
|
+
import { execa } from 'execa'
|
7
|
+
import { blue, bold, cyan, dim, green, red, reset } from 'kolorist'
|
8
|
+
import minimist from 'minimist'
|
9
|
+
import prompts from 'prompts'
|
10
|
+
import { version } from '../package.json'
|
11
|
+
import { copy, emptyDir, formatTargetDir, isEmpty, isValidPackageName, pkgFromUserAgent, toValidPackageName } from './utils'
|
12
|
+
import { TEMPLATES, TEMPLATE_CHOICES, renameFiles } from './config'
|
13
|
+
|
14
|
+
const argv = minimist(process.argv.slice(2))
|
15
|
+
|
16
|
+
const cwd = process.cwd()
|
17
|
+
const defaultTargetDir = 'valaxy-blog'
|
18
|
+
|
19
|
+
export async function init() {
|
20
|
+
console.log()
|
21
|
+
console.log(` ${bold('🌌 Valaxy')} ${blue(`v${version}`)}`)
|
22
|
+
console.log()
|
23
|
+
|
24
|
+
const argTargetDir = formatTargetDir(argv._[0])
|
25
|
+
const argTemplate = argv.template || argv.t
|
26
|
+
|
27
|
+
let targetDir = argTargetDir || defaultTargetDir
|
28
|
+
const getProjectName = () =>
|
29
|
+
targetDir === '.' ? path.basename(path.resolve()) : targetDir
|
30
|
+
|
31
|
+
let result: prompts.Answers<
|
32
|
+
'projectName' | 'overwrite' | 'packageName'
|
33
|
+
>
|
34
|
+
|
35
|
+
// get template
|
36
|
+
const { template } = await prompts({
|
37
|
+
type:
|
38
|
+
argTemplate && TEMPLATE_CHOICES.includes(argTemplate) ? null : 'select',
|
39
|
+
name: 'template',
|
40
|
+
message:
|
41
|
+
typeof argTemplate === 'string' && !TEMPLATE_CHOICES.includes(argTemplate)
|
42
|
+
? reset(
|
43
|
+
`"${argTemplate}" isn't a valid template. Please choose from below: `,
|
44
|
+
)
|
45
|
+
: reset('Select a type:'),
|
46
|
+
initial: 0,
|
47
|
+
choices: TEMPLATES.map((template) => {
|
48
|
+
const tColor = template.color
|
49
|
+
return {
|
50
|
+
title: tColor(template.display || template.name) + dim(` - ${template.desc}`),
|
51
|
+
value: template,
|
52
|
+
}
|
53
|
+
}),
|
54
|
+
})
|
55
|
+
|
56
|
+
try {
|
57
|
+
result = await prompts([
|
58
|
+
{
|
59
|
+
type: argTargetDir ? null : 'text',
|
60
|
+
name: 'projectName',
|
61
|
+
message: reset(template.message),
|
62
|
+
initial: template.initial,
|
63
|
+
onState: (state) => {
|
64
|
+
targetDir = formatTargetDir(template.prefix ? template.prefix + state.value : state.value) || (template.initial)
|
65
|
+
},
|
66
|
+
},
|
67
|
+
{
|
68
|
+
type: () =>
|
69
|
+
!fs.existsSync(targetDir) || isEmpty(targetDir) ? null : 'confirm',
|
70
|
+
name: 'overwrite',
|
71
|
+
message: () =>
|
72
|
+
`${targetDir === '.'
|
73
|
+
? 'Current directory'
|
74
|
+
: `Target directory "${targetDir}"`
|
75
|
+
} is not empty. Remove existing files and continue?`,
|
76
|
+
},
|
77
|
+
{
|
78
|
+
type: (_, { overwrite }: { overwrite?: boolean }) => {
|
79
|
+
if (overwrite === false)
|
80
|
+
throw new Error(`${red('✖')} Operation cancelled`)
|
81
|
+
|
82
|
+
return null
|
83
|
+
},
|
84
|
+
name: 'overwriteChecker',
|
85
|
+
},
|
86
|
+
{
|
87
|
+
type: () => (isValidPackageName(getProjectName()) ? null : 'text'),
|
88
|
+
name: 'packageName',
|
89
|
+
message: reset('Package name:'),
|
90
|
+
initial: () => toValidPackageName(getProjectName()),
|
91
|
+
validate: dir =>
|
92
|
+
isValidPackageName(dir) || 'Invalid package.json name',
|
93
|
+
},
|
94
|
+
], {
|
95
|
+
onCancel: () => {
|
96
|
+
throw new Error(`${red('✖')} Operation cancelled`)
|
97
|
+
},
|
98
|
+
})
|
99
|
+
}
|
100
|
+
catch (cancelled: any) {
|
101
|
+
console.log(cancelled.message)
|
102
|
+
return
|
103
|
+
}
|
104
|
+
|
105
|
+
const { projectName, overwrite } = result
|
106
|
+
const dirName = template.prefix ? template.prefix + projectName : projectName
|
107
|
+
const root = path.join(cwd, dirName)
|
108
|
+
|
109
|
+
console.log(root, overwrite)
|
110
|
+
|
111
|
+
if (overwrite)
|
112
|
+
emptyDir(root)
|
113
|
+
else if (!fs.existsSync(root))
|
114
|
+
fs.mkdirSync(root, { recursive: true })
|
115
|
+
|
116
|
+
// custom
|
117
|
+
if (template.customInit) {
|
118
|
+
await template.customInit({ themeName: projectName })
|
119
|
+
}
|
120
|
+
else {
|
121
|
+
const templateDir = path.resolve(fileURLToPath(new URL('.', import.meta.url)), '..', `template-${template.name}`)
|
122
|
+
const write = (filename: string, content?: string) => {
|
123
|
+
const targetPath = renameFiles[filename]
|
124
|
+
? path.join(root, renameFiles[filename])
|
125
|
+
: path.join(root, filename)
|
126
|
+
|
127
|
+
if (content)
|
128
|
+
fs.writeFileSync(targetPath, content)
|
129
|
+
else
|
130
|
+
copy(path.join(templateDir, filename), targetPath)
|
131
|
+
}
|
132
|
+
|
133
|
+
const files = fs.readdirSync(templateDir)
|
134
|
+
for (const file of files.filter(f => f !== 'package.json'))
|
135
|
+
write(file)
|
136
|
+
|
137
|
+
// write pkg name & version
|
138
|
+
const pkg = await import(path.join(templateDir, 'package.json'))
|
139
|
+
pkg.name = projectName || getProjectName()
|
140
|
+
|
141
|
+
write('package.json', JSON.stringify(pkg, null, 2))
|
142
|
+
}
|
143
|
+
|
144
|
+
console.log(` ${dim('📁')} ${dim(root)}`)
|
145
|
+
console.log()
|
146
|
+
console.log(dim(' Scaffolding project in ') + targetDir + dim(' ...'))
|
147
|
+
|
148
|
+
const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent)
|
149
|
+
const pkgManager = pkgInfo ? pkgInfo.name : 'npm'
|
150
|
+
|
151
|
+
const related = path.relative(cwd, root)
|
152
|
+
console.log(green(' Done.\n'))
|
153
|
+
|
154
|
+
// addon not start
|
155
|
+
if (template.name === 'addon')
|
156
|
+
return
|
157
|
+
|
158
|
+
/**
|
159
|
+
* @type {{ yes: boolean }}
|
160
|
+
*/
|
161
|
+
const { yes } = await prompts({
|
162
|
+
type: 'confirm',
|
163
|
+
name: 'yes',
|
164
|
+
initial: 'Y',
|
165
|
+
message: 'Install and start it now?',
|
166
|
+
})
|
167
|
+
|
168
|
+
if (yes) {
|
169
|
+
const { agent } = await prompts({
|
170
|
+
name: 'agent',
|
171
|
+
type: 'select',
|
172
|
+
message: 'Choose the agent',
|
173
|
+
choices: ['npm', 'yarn', 'pnpm'].map(i => ({ value: i, title: i })),
|
174
|
+
})
|
175
|
+
|
176
|
+
if (!agent)
|
177
|
+
return
|
178
|
+
|
179
|
+
await execa(agent, ['install'], { stdio: 'inherit', cwd: root })
|
180
|
+
await execa(agent, ['run', 'dev'], { stdio: 'inherit', cwd: root })
|
181
|
+
}
|
182
|
+
else {
|
183
|
+
console.log(dim('\n start it later by:\n'))
|
184
|
+
if (root !== cwd)
|
185
|
+
console.log(` ${green('cd')} ${blue(related)}`)
|
186
|
+
|
187
|
+
switch (pkgManager) {
|
188
|
+
case 'yarn':
|
189
|
+
console.log(` ${green('yarn')}`)
|
190
|
+
console.log(` ${green('yarn')} dev`)
|
191
|
+
break
|
192
|
+
default:
|
193
|
+
console.log(` ${green(pkgManager)} install`)
|
194
|
+
console.log(` ${green(pkgManager)} run dev`)
|
195
|
+
break
|
196
|
+
}
|
197
|
+
console.log()
|
198
|
+
console.log(` ${cyan('✨')}`)
|
199
|
+
}
|
200
|
+
}
|
@@ -1,21 +1,21 @@
|
|
1
1
|
{
|
2
2
|
"compilerOptions": {
|
3
|
-
"baseUrl": ".",
|
4
|
-
"module": "ESNext",
|
5
3
|
"target": "ESNext",
|
6
4
|
"lib": ["DOM", "ESNext"],
|
7
|
-
"strict": true,
|
8
|
-
"esModuleInterop": true,
|
9
5
|
"jsx": "preserve",
|
10
|
-
"
|
6
|
+
"module": "ESNext",
|
11
7
|
"moduleResolution": "node",
|
12
|
-
"
|
13
|
-
"noUnusedLocals": true,
|
14
|
-
"strictNullChecks": true,
|
15
|
-
"forceConsistentCasingInFileNames": true,
|
8
|
+
"baseUrl": ".",
|
16
9
|
"paths": {
|
17
10
|
"~/*": ["./*"]
|
18
|
-
}
|
11
|
+
},
|
12
|
+
"resolveJsonModule": true,
|
13
|
+
"esModuleInterop": true,
|
14
|
+
"forceConsistentCasingInFileNames": true,
|
15
|
+
"strict": true,
|
16
|
+
"strictNullChecks": true,
|
17
|
+
"noUnusedLocals": true,
|
18
|
+
"skipLibCheck": true
|
19
19
|
},
|
20
20
|
"exclude": ["dist", "node_modules"]
|
21
21
|
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# create-valaxy-theme
|
2
|
+
|
3
|
+
Theme template generator.
|
4
|
+
|
5
|
+
> Make a valaxy theme is so easy!
|
6
|
+
|
7
|
+
```bash
|
8
|
+
npx degit valaxyjs/valaxy-theme-starter valaxy-theme-name
|
9
|
+
```
|
10
|
+
|
11
|
+
## FAQ
|
12
|
+
|
13
|
+
If you want to create a valaxy theme, you should use the theme name as prefix of components to avoid conflicts.
|
14
|
+
|
15
|
+
For example:
|
16
|
+
|
17
|
+
`valaxy-theme-yun` component name is `YunComponent`, `YunPost`, etc.
|
package/index.js
DELETED
@@ -1,198 +0,0 @@
|
|
1
|
-
#!/usr/bin/env node
|
2
|
-
/* eslint-disable no-console */
|
3
|
-
|
4
|
-
const process = require('node:process')
|
5
|
-
const fs = require('node:fs')
|
6
|
-
const path = require('node:path')
|
7
|
-
const argv = require('minimist')(process.argv.slice(2))
|
8
|
-
const prompts = require('prompts')
|
9
|
-
const execa = require('execa')
|
10
|
-
const { bold, dim, blue, yellow, green, cyan, red, gray } = require('kolorist')
|
11
|
-
const { version } = require('./package.json')
|
12
|
-
|
13
|
-
const cwd = process.cwd()
|
14
|
-
|
15
|
-
const renameFiles = {
|
16
|
-
_gitignore: '.gitignore',
|
17
|
-
_npmrc: '.npmrc',
|
18
|
-
}
|
19
|
-
|
20
|
-
async function init() {
|
21
|
-
console.log()
|
22
|
-
console.log(` ${bold('🌌 Valaxy')} ${blue(`v${version}`)}`)
|
23
|
-
console.log()
|
24
|
-
|
25
|
-
let targetDir = argv._[0]
|
26
|
-
if (!targetDir) {
|
27
|
-
/**
|
28
|
-
* @type {{ projectName: string }}
|
29
|
-
*/
|
30
|
-
const { projectName } = await prompts({
|
31
|
-
type: 'text',
|
32
|
-
name: 'projectName',
|
33
|
-
message: 'Project name:',
|
34
|
-
initial: 'valaxy-blog',
|
35
|
-
})
|
36
|
-
targetDir = projectName.trim()
|
37
|
-
|
38
|
-
const packageName = await getValidPackageName(targetDir)
|
39
|
-
const root = path.join(cwd, targetDir)
|
40
|
-
|
41
|
-
if (!fs.existsSync(root)) {
|
42
|
-
fs.mkdirSync(root, { recursive: true })
|
43
|
-
}
|
44
|
-
else {
|
45
|
-
const existing = fs.readdirSync(root)
|
46
|
-
if (existing.length) {
|
47
|
-
console.log(yellow(` Target directory "${targetDir}" is not empty.`))
|
48
|
-
/**
|
49
|
-
* @type {{ yes: boolean }}
|
50
|
-
*/
|
51
|
-
const { yes } = await prompts({
|
52
|
-
type: 'confirm',
|
53
|
-
name: 'yes',
|
54
|
-
initial: 'Y',
|
55
|
-
message: 'Remove existing files and continue?',
|
56
|
-
})
|
57
|
-
if (yes)
|
58
|
-
emptyDir(root)
|
59
|
-
|
60
|
-
else
|
61
|
-
return
|
62
|
-
}
|
63
|
-
}
|
64
|
-
|
65
|
-
console.log(` ${dim('📁')} ${dim(root)}`)
|
66
|
-
console.log()
|
67
|
-
console.log(dim(' Scaffolding project in ') + targetDir + dim(' ...'))
|
68
|
-
|
69
|
-
const templateDir = path.join(__dirname, 'template')
|
70
|
-
const write = (file, content) => {
|
71
|
-
const targetPath = renameFiles[file]
|
72
|
-
? path.join(root, renameFiles[file])
|
73
|
-
: path.join(root, file)
|
74
|
-
if (content)
|
75
|
-
fs.writeFileSync(targetPath, content)
|
76
|
-
|
77
|
-
else
|
78
|
-
copy(path.join(templateDir, file), targetPath)
|
79
|
-
}
|
80
|
-
|
81
|
-
const files = fs.readdirSync(templateDir)
|
82
|
-
for (const file of files.filter(f => f !== 'package.json'))
|
83
|
-
write(file)
|
84
|
-
|
85
|
-
// write pkg name & version
|
86
|
-
const pkg = require(path.join(templateDir, 'package.json'))
|
87
|
-
if (packageName)
|
88
|
-
pkg.name = packageName
|
89
|
-
pkg.version = version
|
90
|
-
|
91
|
-
write('package.json', JSON.stringify(pkg, null, 2))
|
92
|
-
|
93
|
-
const pkgManager = (/pnpm/.test(process.env.npm_execpath) || /pnpm/.test(process.env.npm_config_user_agent))
|
94
|
-
? 'pnpm'
|
95
|
-
: /yarn/.test(process.env.npm_execpath) ? 'yarn' : 'npm'
|
96
|
-
|
97
|
-
const related = path.relative(cwd, root)
|
98
|
-
console.log(green(' Done.\n'))
|
99
|
-
|
100
|
-
/**
|
101
|
-
* @type {{ yes: boolean }}
|
102
|
-
*/
|
103
|
-
const { yes } = await prompts({
|
104
|
-
type: 'confirm',
|
105
|
-
name: 'yes',
|
106
|
-
initial: 'Y',
|
107
|
-
message: 'Install and start it now?',
|
108
|
-
})
|
109
|
-
|
110
|
-
if (yes) {
|
111
|
-
const { agent } = await prompts({
|
112
|
-
name: 'agent',
|
113
|
-
type: 'select',
|
114
|
-
message: 'Choose the agent',
|
115
|
-
choices: ['npm', 'yarn', 'pnpm'].map(i => ({ value: i, title: i })),
|
116
|
-
})
|
117
|
-
|
118
|
-
if (!agent)
|
119
|
-
return
|
120
|
-
|
121
|
-
await execa(agent, ['install'], { stdio: 'inherit', cwd: root })
|
122
|
-
await execa(agent, ['run', 'dev'], { stdio: 'inherit', cwd: root })
|
123
|
-
}
|
124
|
-
else {
|
125
|
-
console.log(dim('\n start it later by:\n'))
|
126
|
-
if (root !== cwd)
|
127
|
-
console.log(blue(` cd ${bold(related)}`))
|
128
|
-
|
129
|
-
console.log(blue(` ${pkgManager === 'yarn' ? 'yarn' : `${pkgManager} install`}`))
|
130
|
-
console.log(blue(` ${pkgManager === 'yarn' ? 'yarn dev' : `${pkgManager} run dev`}`))
|
131
|
-
console.log()
|
132
|
-
console.log(` ${cyan('✨')}`)
|
133
|
-
console.log()
|
134
|
-
}
|
135
|
-
}
|
136
|
-
}
|
137
|
-
|
138
|
-
function copy(src, dest) {
|
139
|
-
const stat = fs.statSync(src)
|
140
|
-
if (stat.isDirectory())
|
141
|
-
copyDir(src, dest)
|
142
|
-
|
143
|
-
else
|
144
|
-
fs.copyFileSync(src, dest)
|
145
|
-
}
|
146
|
-
|
147
|
-
function copyDir(srcDir, destDir) {
|
148
|
-
fs.mkdirSync(destDir, { recursive: true })
|
149
|
-
for (const file of fs.readdirSync(srcDir)) {
|
150
|
-
const srcFile = path.resolve(srcDir, file)
|
151
|
-
const destFile = path.resolve(destDir, file)
|
152
|
-
copy(srcFile, destFile)
|
153
|
-
}
|
154
|
-
}
|
155
|
-
|
156
|
-
async function getValidPackageName(projectName) {
|
157
|
-
projectName = path.basename(projectName)
|
158
|
-
const packageNameRegExp = /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/
|
159
|
-
if (packageNameRegExp.test(projectName)) {
|
160
|
-
return projectName
|
161
|
-
}
|
162
|
-
else {
|
163
|
-
const suggestedPackageName = projectName
|
164
|
-
.trim()
|
165
|
-
.toLowerCase()
|
166
|
-
.replace(/\s+/g, '-')
|
167
|
-
.replace(/^[._]/, '')
|
168
|
-
.replace(/[^a-z0-9-~]+/g, '-')
|
169
|
-
|
170
|
-
/**
|
171
|
-
* @type {{ inputPackageName: string }}
|
172
|
-
*/
|
173
|
-
const { inputPackageName } = await prompts({
|
174
|
-
type: 'text',
|
175
|
-
name: 'inputPackageName',
|
176
|
-
message: 'Package name:',
|
177
|
-
initial: suggestedPackageName,
|
178
|
-
// validate: input =>
|
179
|
-
// packageNameRegExp.test(input) ? true : 'Invalid package.json name',
|
180
|
-
})
|
181
|
-
return inputPackageName
|
182
|
-
}
|
183
|
-
}
|
184
|
-
|
185
|
-
/**
|
186
|
-
* @param {string} dir
|
187
|
-
* @returns
|
188
|
-
*/
|
189
|
-
function emptyDir(dir) {
|
190
|
-
if (!fs.existsSync(dir))
|
191
|
-
return
|
192
|
-
console.log(red(' Remove ') + gray(dir))
|
193
|
-
fs.rmSync(dir, { recursive: true, force: true })
|
194
|
-
}
|
195
|
-
|
196
|
-
init().catch((e) => {
|
197
|
-
console.error(e)
|
198
|
-
})
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|