@x-withu/page-withu 0.0.5 → 0.1.0
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/bin/page-withu.js +348 -110
- package/docs/develop.md +1 -0
- package/docs/setup.md +31 -0
- package/package.json +1 -1
- package/template/src/components/blog-list.vue +1 -1
package/bin/page-withu.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
2
3
|
import { Command } from 'commander';
|
|
3
4
|
import inquirer from 'inquirer';
|
|
4
5
|
import fs from 'fs-extra';
|
|
@@ -12,131 +13,368 @@ const pkg = await fs.readJson(new URL('../package.json', import.meta.url));
|
|
|
12
13
|
const __filename = fileURLToPath(import.meta.url);
|
|
13
14
|
const __dirname = path.dirname(__filename);
|
|
14
15
|
const templateDir = path.resolve(__dirname, '../template');
|
|
16
|
+
const manifestName = '.page-withu.json';
|
|
17
|
+
const managedRoots = ['src'];
|
|
18
|
+
const managedFiles = ['index.html', 'vite.config.js'];
|
|
15
19
|
|
|
16
20
|
const program = new Command();
|
|
17
21
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
.version(pkg.version);
|
|
22
|
+
function hashContent(content) {
|
|
23
|
+
return createHash('sha256').update(content).digest('hex');
|
|
24
|
+
}
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
{
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
26
|
+
async function hashFile(filePath) {
|
|
27
|
+
return hashContent(await fs.readFile(filePath));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function toPosixPath(value) {
|
|
31
|
+
return value.split(path.sep).join('/');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function listFiles(dir) {
|
|
35
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
36
|
+
const files = [];
|
|
37
|
+
|
|
38
|
+
for (const entry of entries) {
|
|
39
|
+
const entryPath = path.join(dir, entry.name);
|
|
40
|
+
if (entry.isDirectory()) {
|
|
41
|
+
files.push(...await listFiles(entryPath));
|
|
42
|
+
} else if (entry.isFile()) {
|
|
43
|
+
files.push(entryPath);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return files;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function listManagedTemplateFiles() {
|
|
51
|
+
const files = [...managedFiles];
|
|
52
|
+
|
|
53
|
+
for (const root of managedRoots) {
|
|
54
|
+
const rootPath = path.join(templateDir, root);
|
|
55
|
+
if (!await fs.pathExists(rootPath)) continue;
|
|
56
|
+
const rootFiles = await listFiles(rootPath);
|
|
57
|
+
files.push(...rootFiles.map((file) => toPosixPath(path.relative(templateDir, file))));
|
|
45
58
|
}
|
|
46
59
|
|
|
47
|
-
|
|
60
|
+
return files.sort((a, b) => a.localeCompare(b));
|
|
61
|
+
}
|
|
48
62
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
63
|
+
async function createManifest(projectDir, previousManifest = null) {
|
|
64
|
+
const files = {};
|
|
65
|
+
|
|
66
|
+
for (const file of await listManagedTemplateFiles()) {
|
|
67
|
+
const projectFile = path.join(projectDir, file);
|
|
68
|
+
const templateFile = path.join(templateDir, file);
|
|
69
|
+
if (!await fs.pathExists(projectFile)) continue;
|
|
70
|
+
|
|
71
|
+
const projectHash = await hashFile(projectFile);
|
|
72
|
+
const templateHash = await hashFile(templateFile);
|
|
73
|
+
if (projectHash === templateHash) {
|
|
74
|
+
files[file] = templateHash;
|
|
75
|
+
} else if (previousManifest?.files?.[file]) {
|
|
76
|
+
files[file] = previousManifest.files[file];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
templateVersion: pkg.version,
|
|
82
|
+
updatedAt: new Date().toISOString(),
|
|
83
|
+
files,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function writeManifest(projectDir, dryRun, previousManifest = null) {
|
|
88
|
+
const manifest = await createManifest(projectDir, previousManifest);
|
|
89
|
+
if (!dryRun) {
|
|
90
|
+
await fs.writeJson(path.join(projectDir, manifestName), manifest, { spaces: 2 });
|
|
52
91
|
}
|
|
92
|
+
return manifest;
|
|
93
|
+
}
|
|
53
94
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const author = options.author || (await inquirer.prompt([{
|
|
62
|
-
type: 'input',
|
|
63
|
-
name: 'author',
|
|
64
|
-
message: 'Author Name:',
|
|
65
|
-
default: 'Your Name',
|
|
66
|
-
}])).author;
|
|
67
|
-
|
|
68
|
-
const tabTitle = options.tabTitle || (await inquirer.prompt([{
|
|
69
|
-
type: 'input',
|
|
70
|
-
name: 'tabTitle',
|
|
71
|
-
message: 'Browser Tab Title:',
|
|
72
|
-
default: title,
|
|
73
|
-
}])).tabTitle;
|
|
74
|
-
|
|
75
|
-
const favicon = options.favicon || (await inquirer.prompt([{
|
|
76
|
-
type: 'input',
|
|
77
|
-
name: 'favicon',
|
|
78
|
-
message: 'Favicon path or URL:',
|
|
79
|
-
default: '/src/assets/bulb.svg',
|
|
80
|
-
}])).favicon;
|
|
81
|
-
|
|
82
|
-
const github = options.github !== undefined ? options.github : (await inquirer.prompt([{
|
|
83
|
-
type: 'input',
|
|
84
|
-
name: 'github',
|
|
85
|
-
message: 'GitHub URL (optional):',
|
|
86
|
-
}])).github;
|
|
87
|
-
|
|
88
|
-
const email = options.email !== undefined ? options.email : (await inquirer.prompt([{
|
|
89
|
-
type: 'input',
|
|
90
|
-
name: 'email',
|
|
91
|
-
message: 'Email (optional):',
|
|
92
|
-
}])).email;
|
|
93
|
-
|
|
94
|
-
const spinner = ora('Creating project...').start();
|
|
95
|
-
|
|
96
|
-
try {
|
|
97
|
-
// 1. Copy template
|
|
98
|
-
await fs.copy(templateDir, fullPath);
|
|
99
|
-
|
|
100
|
-
// 2. Generate config.js based on answers
|
|
101
|
-
const footerLinks = [];
|
|
102
|
-
if (github) footerLinks.push(`{ label: 'GitHub', url: '${github}' }`);
|
|
103
|
-
if (email) footerLinks.push(`{ label: 'Email', url: 'mailto:${email}' }`);
|
|
104
|
-
|
|
105
|
-
const configContent = `export default {
|
|
106
|
-
title: "${title}",
|
|
107
|
-
tabTitle: "${tabTitle}",
|
|
108
|
-
favicon: "${favicon}",
|
|
109
|
-
author: "${author}",
|
|
95
|
+
function renderConfig({ title, tabTitle, favicon, author, footerLinks }) {
|
|
96
|
+
const links = footerLinks.map((link) => ` { label: ${JSON.stringify(link.label)}, url: ${JSON.stringify(link.url)} }`);
|
|
97
|
+
return `export default {
|
|
98
|
+
title: ${JSON.stringify(title)},
|
|
99
|
+
tabTitle: ${JSON.stringify(tabTitle)},
|
|
100
|
+
favicon: ${JSON.stringify(favicon)},
|
|
101
|
+
author: ${JSON.stringify(author)},
|
|
110
102
|
year: new Date().getFullYear(),
|
|
111
103
|
footerLinks: [
|
|
112
|
-
|
|
104
|
+
${links.join(',\n')}
|
|
113
105
|
],
|
|
114
106
|
pagination: {
|
|
115
107
|
pageSize: 5
|
|
116
108
|
}
|
|
117
109
|
}`;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function detectProject(projectDir) {
|
|
113
|
+
const required = ['package.json', 'config.js', 'content', 'src', 'vite.config.js'];
|
|
114
|
+
const missing = [];
|
|
115
|
+
|
|
116
|
+
for (const item of required) {
|
|
117
|
+
if (!await fs.pathExists(path.join(projectDir, item))) missing.push(item);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return missing;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function loadManifest(projectDir) {
|
|
124
|
+
const manifestPath = path.join(projectDir, manifestName);
|
|
125
|
+
if (!await fs.pathExists(manifestPath)) return null;
|
|
126
|
+
return fs.readJson(manifestPath);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function shouldOverwriteConflict(file, options) {
|
|
130
|
+
if (options.force) return true;
|
|
131
|
+
if (options.yes || options.dryRun) return false;
|
|
132
|
+
|
|
133
|
+
const answer = await inquirer.prompt([{
|
|
134
|
+
type: 'confirm',
|
|
135
|
+
name: 'overwrite',
|
|
136
|
+
message: `${file} has local changes. Overwrite it?`,
|
|
137
|
+
default: false,
|
|
138
|
+
}]);
|
|
139
|
+
|
|
140
|
+
return answer.overwrite;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function planTemplateUpdates(projectDir, options) {
|
|
144
|
+
const manifest = await loadManifest(projectDir);
|
|
145
|
+
const files = await listManagedTemplateFiles();
|
|
146
|
+
const result = {
|
|
147
|
+
added: [],
|
|
148
|
+
updated: [],
|
|
149
|
+
skipped: [],
|
|
150
|
+
unchanged: [],
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
for (const file of files) {
|
|
154
|
+
const sourceFile = path.join(templateDir, file);
|
|
155
|
+
const targetFile = path.join(projectDir, file);
|
|
156
|
+
const sourceHash = await hashFile(sourceFile);
|
|
157
|
+
const exists = await fs.pathExists(targetFile);
|
|
158
|
+
|
|
159
|
+
if (!exists) {
|
|
160
|
+
result.added.push(file);
|
|
161
|
+
if (!options.dryRun) await fs.copy(sourceFile, targetFile);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const currentHash = await hashFile(targetFile);
|
|
166
|
+
if (currentHash === sourceHash) {
|
|
167
|
+
result.unchanged.push(file);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const previousHash = manifest?.files?.[file];
|
|
172
|
+
const safeToUpdate = previousHash && currentHash === previousHash;
|
|
173
|
+
const overwrite = safeToUpdate || await shouldOverwriteConflict(file, options);
|
|
174
|
+
|
|
175
|
+
if (overwrite) {
|
|
176
|
+
result.updated.push(file);
|
|
177
|
+
if (!options.dryRun) await fs.copy(sourceFile, targetFile);
|
|
178
|
+
} else {
|
|
179
|
+
result.skipped.push(file);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function mergeSection(projectPkg, templatePkg, key) {
|
|
187
|
+
const next = { ...(projectPkg[key] || {}) };
|
|
188
|
+
let changed = false;
|
|
189
|
+
|
|
190
|
+
for (const [name, value] of Object.entries(templatePkg[key] || {})) {
|
|
191
|
+
if (next[name] !== value) {
|
|
192
|
+
next[name] = value;
|
|
193
|
+
changed = true;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (changed) projectPkg[key] = next;
|
|
198
|
+
return changed;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function mergePackage(projectDir, dryRun) {
|
|
202
|
+
const projectPkgPath = path.join(projectDir, 'package.json');
|
|
203
|
+
const templatePkg = await fs.readJson(path.join(templateDir, 'package.json'));
|
|
204
|
+
const projectPkg = await fs.readJson(projectPkgPath);
|
|
205
|
+
const before = JSON.stringify(projectPkg);
|
|
206
|
+
|
|
207
|
+
mergeSection(projectPkg, templatePkg, 'scripts');
|
|
208
|
+
mergeSection(projectPkg, templatePkg, 'dependencies');
|
|
209
|
+
mergeSection(projectPkg, templatePkg, 'devDependencies');
|
|
210
|
+
|
|
211
|
+
const changed = JSON.stringify(projectPkg) !== before;
|
|
212
|
+
if (changed && !dryRun) {
|
|
213
|
+
await fs.writeJson(projectPkgPath, projectPkg, { spaces: 2 });
|
|
139
214
|
}
|
|
140
|
-
});
|
|
141
215
|
|
|
142
|
-
|
|
216
|
+
return changed;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function printUpdateSummary(summary, dryRun) {
|
|
220
|
+
const label = dryRun ? 'Would update' : 'Updated';
|
|
221
|
+
|
|
222
|
+
if (summary.added.length) console.log(chalk.green(`${label} ${summary.added.length} missing file(s).`));
|
|
223
|
+
if (summary.updated.length) console.log(chalk.green(`${label} ${summary.updated.length} template file(s).`));
|
|
224
|
+
if (summary.packageChanged) console.log(chalk.green(`${label} package.json dependencies and scripts.`));
|
|
225
|
+
if (summary.skipped.length) {
|
|
226
|
+
console.log(chalk.yellow(`Skipped ${summary.skipped.length} file(s) with local changes:`));
|
|
227
|
+
for (const file of summary.skipped) console.log(chalk.yellow(` - ${file}`));
|
|
228
|
+
}
|
|
229
|
+
if (!summary.added.length && !summary.updated.length && !summary.packageChanged && !summary.skipped.length) {
|
|
230
|
+
console.log(chalk.green('Page With U project is already up to date.'));
|
|
231
|
+
}
|
|
232
|
+
if (!dryRun && (summary.added.length || summary.updated.length || summary.packageChanged)) {
|
|
233
|
+
console.log(chalk.cyan('\nNext steps:'));
|
|
234
|
+
console.log(chalk.cyan(' npm install'));
|
|
235
|
+
console.log(chalk.cyan(' npm run build'));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
program
|
|
240
|
+
.name('page-withu')
|
|
241
|
+
.description('CLI to scaffold a lightweight, elegant personal homepage')
|
|
242
|
+
.version(pkg.version);
|
|
243
|
+
|
|
244
|
+
program
|
|
245
|
+
.command('create [project-name]')
|
|
246
|
+
.description('create a new PageWithU project')
|
|
247
|
+
.option('-t, --title <title>', 'site title')
|
|
248
|
+
.option('--tab-title <title>', 'browser tab title')
|
|
249
|
+
.option('--favicon <path-or-url>', 'browser tab favicon path or URL')
|
|
250
|
+
.option('-a, --author <author>', 'author name')
|
|
251
|
+
.option('-g, --github <github>', 'github url')
|
|
252
|
+
.option('-e, --email <email>', 'email address')
|
|
253
|
+
.action(async (projectName, options) => {
|
|
254
|
+
let targetDir = projectName;
|
|
255
|
+
|
|
256
|
+
if (!targetDir) {
|
|
257
|
+
const answers = await inquirer.prompt([
|
|
258
|
+
{
|
|
259
|
+
type: 'input',
|
|
260
|
+
name: 'projectName',
|
|
261
|
+
message: 'What is your project named?',
|
|
262
|
+
default: 'my-homepage',
|
|
263
|
+
},
|
|
264
|
+
]);
|
|
265
|
+
targetDir = answers.projectName;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const fullPath = path.resolve(process.cwd(), targetDir);
|
|
269
|
+
|
|
270
|
+
if (fs.existsSync(fullPath)) {
|
|
271
|
+
console.error(chalk.red(`\nError: Directory ${targetDir} already exists.`));
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const title = options.title || (await inquirer.prompt([{
|
|
276
|
+
type: 'input',
|
|
277
|
+
name: 'title',
|
|
278
|
+
message: 'Site Title:',
|
|
279
|
+
default: 'PageWithU',
|
|
280
|
+
}])).title;
|
|
281
|
+
|
|
282
|
+
const author = options.author || (await inquirer.prompt([{
|
|
283
|
+
type: 'input',
|
|
284
|
+
name: 'author',
|
|
285
|
+
message: 'Author Name:',
|
|
286
|
+
default: 'Your Name',
|
|
287
|
+
}])).author;
|
|
288
|
+
|
|
289
|
+
const tabTitle = options.tabTitle || (await inquirer.prompt([{
|
|
290
|
+
type: 'input',
|
|
291
|
+
name: 'tabTitle',
|
|
292
|
+
message: 'Browser Tab Title:',
|
|
293
|
+
default: title,
|
|
294
|
+
}])).tabTitle;
|
|
295
|
+
|
|
296
|
+
const favicon = options.favicon || (await inquirer.prompt([{
|
|
297
|
+
type: 'input',
|
|
298
|
+
name: 'favicon',
|
|
299
|
+
message: 'Favicon path or URL:',
|
|
300
|
+
default: '/src/assets/bulb.svg',
|
|
301
|
+
}])).favicon;
|
|
302
|
+
|
|
303
|
+
const github = options.github !== undefined ? options.github : (await inquirer.prompt([{
|
|
304
|
+
type: 'input',
|
|
305
|
+
name: 'github',
|
|
306
|
+
message: 'GitHub URL (optional):',
|
|
307
|
+
}])).github;
|
|
308
|
+
|
|
309
|
+
const email = options.email !== undefined ? options.email : (await inquirer.prompt([{
|
|
310
|
+
type: 'input',
|
|
311
|
+
name: 'email',
|
|
312
|
+
message: 'Email (optional):',
|
|
313
|
+
}])).email;
|
|
314
|
+
|
|
315
|
+
const spinner = ora('Creating project...').start();
|
|
316
|
+
|
|
317
|
+
try {
|
|
318
|
+
await fs.copy(templateDir, fullPath);
|
|
319
|
+
|
|
320
|
+
const footerLinks = [];
|
|
321
|
+
if (github) footerLinks.push({ label: 'GitHub', url: github });
|
|
322
|
+
if (email) footerLinks.push({ label: 'Email', url: `mailto:${email}` });
|
|
323
|
+
|
|
324
|
+
await fs.writeFile(path.join(fullPath, 'config.js'), renderConfig({ title, tabTitle, favicon, author, footerLinks }));
|
|
325
|
+
|
|
326
|
+
const pkgPath = path.join(fullPath, 'package.json');
|
|
327
|
+
const projectPkg = await fs.readJson(pkgPath);
|
|
328
|
+
projectPkg.name = targetDir.toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
329
|
+
await fs.writeJson(pkgPath, projectPkg, { spaces: 2 });
|
|
330
|
+
|
|
331
|
+
await fs.ensureDir(path.join(fullPath, 'content/blog'));
|
|
332
|
+
await writeManifest(fullPath, false);
|
|
333
|
+
|
|
334
|
+
spinner.succeed(chalk.green(`Successfully created PageWithU project in ${targetDir}!`));
|
|
335
|
+
console.log('\nNext steps:');
|
|
336
|
+
console.log(chalk.cyan(` cd ${targetDir}`));
|
|
337
|
+
console.log(chalk.cyan(' npm install'));
|
|
338
|
+
console.log(chalk.cyan(' npm run dev\n'));
|
|
339
|
+
|
|
340
|
+
} catch (err) {
|
|
341
|
+
spinner.fail(chalk.red('Failed to create project.'));
|
|
342
|
+
console.error(err);
|
|
343
|
+
process.exit(1);
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
program
|
|
348
|
+
.command('update')
|
|
349
|
+
.description('update an existing PageWithU project with the latest template')
|
|
350
|
+
.option('--dry-run', 'show what would change without writing files')
|
|
351
|
+
.option('--yes', 'skip prompts and apply safe updates')
|
|
352
|
+
.option('--force', 'overwrite changed template-managed files')
|
|
353
|
+
.action(async (options) => {
|
|
354
|
+
const projectDir = process.cwd();
|
|
355
|
+
const spinner = ora(options.dryRun ? 'Checking project update...' : 'Updating project...').start();
|
|
356
|
+
|
|
357
|
+
try {
|
|
358
|
+
const missing = await detectProject(projectDir);
|
|
359
|
+
if (missing.length) {
|
|
360
|
+
spinner.fail(chalk.red('Current directory does not look like a PageWithU project.'));
|
|
361
|
+
console.error(chalk.yellow(`Missing: ${missing.join(', ')}`));
|
|
362
|
+
process.exit(1);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
spinner.stop();
|
|
366
|
+
const previousManifest = await loadManifest(projectDir);
|
|
367
|
+
const updates = await planTemplateUpdates(projectDir, options);
|
|
368
|
+
const packageChanged = await mergePackage(projectDir, options.dryRun);
|
|
369
|
+
|
|
370
|
+
if (!options.dryRun) await writeManifest(projectDir, false, previousManifest);
|
|
371
|
+
|
|
372
|
+
printUpdateSummary({ ...updates, packageChanged }, options.dryRun);
|
|
373
|
+
} catch (err) {
|
|
374
|
+
spinner.fail(chalk.red('Failed to update project.'));
|
|
375
|
+
console.error(err);
|
|
376
|
+
process.exit(1);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
program.parse();
|
package/docs/develop.md
CHANGED
package/docs/setup.md
CHANGED
|
@@ -83,6 +83,37 @@ index_img: https://example.com/cover.jpg
|
|
|
83
83
|
- `tags`:文章标签。
|
|
84
84
|
- `index_img`:博客列表封面图,可选;不填会保留占位布局。
|
|
85
85
|
|
|
86
|
+
## 更新项目
|
|
87
|
+
|
|
88
|
+
如果你已经用 `page-withu create` 创建过项目,后续升级 CLI 后可以在项目目录中更新模板能力:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
npm install -g @x-withu/page-withu
|
|
92
|
+
cd my-homepage
|
|
93
|
+
page-withu update --dry-run
|
|
94
|
+
page-withu update
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`page-withu update` 会更新模板管理的文件,例如 `src/`、`index.html` 和 `vite.config.js`,并合并 `package.json` 中模板需要的脚本和依赖。
|
|
98
|
+
|
|
99
|
+
为了避免覆盖你的内容,以下文件默认不会被更新:
|
|
100
|
+
|
|
101
|
+
- `config.js`
|
|
102
|
+
- `content/`
|
|
103
|
+
|
|
104
|
+
如果你修改过模板源码,更新时会跳过有冲突的文件并在终端列出来。确认要覆盖这些模板源码时,可以使用:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
page-withu update --force
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
更新完成后建议执行:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
npm install
|
|
114
|
+
npm run build
|
|
115
|
+
```
|
|
116
|
+
|
|
86
117
|
## Markdown
|
|
87
118
|
|
|
88
119
|
文章中可以使用常见 Markdown 语法,也支持以下增强能力:
|
package/package.json
CHANGED
|
@@ -84,7 +84,7 @@ const activeMonth = ref('')
|
|
|
84
84
|
const monthRefs = new Map()
|
|
85
85
|
|
|
86
86
|
const orderedPosts = computed(() => {
|
|
87
|
-
return [...props.posts].sort((a, b) => new Date(
|
|
87
|
+
return [...props.posts].sort((a, b) => new Date(b.date) - new Date(a.date))
|
|
88
88
|
})
|
|
89
89
|
|
|
90
90
|
const tags = computed(() => {
|