@x-withu/page-withu 0.0.5 → 0.1.1

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 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 = ['.gitignore', 'index.html', 'vite.config.js'];
15
19
 
16
20
  const program = new Command();
17
21
 
18
- program
19
- .name('page-withu')
20
- .description('CLI to scaffold a lightweight, elegant personal homepage')
21
- .version(pkg.version);
22
+ function hashContent(content) {
23
+ return createHash('sha256').update(content).digest('hex');
24
+ }
22
25
 
23
- program
24
- .command('create [project-name]')
25
- .description('create a new WithU project')
26
- .option('-t, --title <title>', 'site title')
27
- .option('--tab-title <title>', 'browser tab title')
28
- .option('--favicon <path-or-url>', 'browser tab favicon path or URL')
29
- .option('-a, --author <author>', 'author name')
30
- .option('-g, --github <github>', 'github url')
31
- .option('-e, --email <email>', 'email address')
32
- .action(async (projectName, options) => {
33
- let targetDir = projectName;
34
-
35
- if (!targetDir) {
36
- const answers = await inquirer.prompt([
37
- {
38
- type: 'input',
39
- name: 'projectName',
40
- message: 'What is your project named?',
41
- default: 'my-homepage',
42
- },
43
- ]);
44
- targetDir = answers.projectName;
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
- const fullPath = path.resolve(process.cwd(), targetDir);
60
+ return files.sort((a, b) => a.localeCompare(b));
61
+ }
48
62
 
49
- if (fs.existsSync(fullPath)) {
50
- console.error(chalk.red(`\nError: Directory ${targetDir} already exists.`));
51
- process.exit(1);
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
- const title = options.title || (await inquirer.prompt([{
55
- type: 'input',
56
- name: 'title',
57
- message: 'Site Title:',
58
- default: 'WithU',
59
- }])).title;
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
- ${footerLinks.join(',\n ')}
104
+ ${links.join(',\n')}
113
105
  ],
114
106
  pagination: {
115
107
  pageSize: 5
116
108
  }
117
109
  }`;
118
- await fs.writeFile(path.join(fullPath, 'config.js'), configContent);
119
-
120
- // 3. Rename package.json project name
121
- const pkgPath = path.join(fullPath, 'package.json');
122
- const pkg = await fs.readJson(pkgPath);
123
- pkg.name = targetDir.toLowerCase().replace(/[^a-z0-9-]/g, '-');
124
- await fs.writeJson(pkgPath, pkg, { spaces: 2 });
125
-
126
- // 4. Create empty default directories
127
- await fs.ensureDir(path.join(fullPath, 'content/blog'));
128
-
129
- spinner.succeed(chalk.green(`Successfully created WithU project in ${targetDir}!`));
130
- console.log('\nNext steps:');
131
- console.log(chalk.cyan(` cd ${targetDir}`));
132
- console.log(chalk.cyan(' npm install'));
133
- console.log(chalk.cyan(' npm run dev\n'));
134
-
135
- } catch (err) {
136
- spinner.fail(chalk.red('Failed to create project.'));
137
- console.error(err);
138
- process.exit(1);
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
- program.parse();
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/deploy.md CHANGED
@@ -38,19 +38,60 @@ npm run preview
38
38
 
39
39
  ## 部署到 GitHub Pages
40
40
 
41
- 本仓库已经内置 GitHub Pages 自动部署流程:
41
+ GitHub Pages 适合把你生成好的个人主页托管到 GitHub 仓库中。
42
+
43
+ ### 方式一:使用 GitHub Actions 自动部署
44
+
45
+ 在你自己的主页项目中创建部署工作流:
42
46
 
43
47
  ```text
44
48
  .github/workflows/deploy-pages.yml
45
49
  ```
46
50
 
47
- 它会在推送到 `main` 分支时自动执行:
48
-
49
- 1. 进入 `template/`。
50
- 2. 执行 `npm ci` 安装模板依赖。
51
- 3. 使用 `BASE_PATH=/page-withu/ npm run build` 构建模板。
52
- 4. 上传 `template/dist/`。
53
- 5. 发布到 GitHub Pages。
51
+ 示例内容:
52
+
53
+ ```yaml
54
+ name: Deploy to GitHub Pages
55
+
56
+ on:
57
+ push:
58
+ branches: [main]
59
+ workflow_dispatch:
60
+
61
+ permissions:
62
+ contents: read
63
+ pages: write
64
+ id-token: write
65
+
66
+ concurrency:
67
+ group: pages
68
+ cancel-in-progress: false
69
+
70
+ jobs:
71
+ build:
72
+ runs-on: ubuntu-latest
73
+ steps:
74
+ - uses: actions/checkout@v4
75
+ - uses: actions/setup-node@v4
76
+ with:
77
+ node-version: 20
78
+ cache: npm
79
+ - run: npm ci
80
+ - run: npm run build
81
+ - uses: actions/upload-pages-artifact@v3
82
+ with:
83
+ path: dist
84
+
85
+ deploy:
86
+ needs: build
87
+ runs-on: ubuntu-latest
88
+ environment:
89
+ name: github-pages
90
+ url: ${{ steps.deployment.outputs.page_url }}
91
+ steps:
92
+ - id: deployment
93
+ uses: actions/deploy-pages@v4
94
+ ```
54
95
 
55
96
  首次使用前,需要在 GitHub 仓库中打开:
56
97
 
@@ -58,24 +99,23 @@ npm run preview
58
99
  Settings → Pages → Build and deployment → Source → GitHub Actions
59
100
  ```
60
101
 
61
- 配置完成后,每次推送到 `main` 分支都会自动更新:
102
+ 配置完成后,每次推送到 `main` 分支都会自动构建并发布你的站点。
62
103
 
63
- ```text
64
- https://explorer-dong.github.io/page-withu/
65
- ```
104
+ 如果仓库名不是用于根域名的 `用户名.github.io`,通常需要给构建命令设置子路径。例如仓库名是 `my-homepage`:
66
105
 
67
- 如果想手动触发部署,也可以在 GitHub Actions 页面选择 `Deploy Template to GitHub Pages`,然后点击 `Run workflow`。
106
+ ```yaml
107
+ - run: BASE_PATH=/my-homepage/ npm run build
108
+ ```
68
109
 
69
- ### 手动上传 dist
110
+ ### 方式二:手动上传 dist
70
111
 
71
112
  如果不想使用 GitHub Actions,也可以本地构建后手动上传 `dist/`。
72
113
 
73
114
  ```bash
74
- cd template
75
- BASE_PATH=/page-withu/ npm run build
115
+ npm run build
76
116
  ```
77
117
 
78
- 然后把 `template/dist/` 内容上传到用于 GitHub Pages 的发布分支或目录。
118
+ 然后把 `dist/` 内容上传到用于 GitHub Pages 的发布分支或目录。
79
119
 
80
120
  ## 部署到 Cloudflare Pages
81
121
 
package/docs/develop.md CHANGED
@@ -57,6 +57,7 @@ bin/page-withu.js
57
57
  ```bash
58
58
  node bin/page-withu.js --help
59
59
  node bin/page-withu.js create my-homepage
60
+ node bin/page-withu.js update --dry-run
60
61
  ```
61
62
 
62
63
  如果想全局挂载本地版本:
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@x-withu/page-withu",
3
- "version": "0.0.5",
3
+ "version": "0.1.1",
4
4
  "description": "A lightweight, elegant personal homepage generator.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -16,6 +16,7 @@
16
16
  "template/content/",
17
17
  "template/src/",
18
18
  "template/index.html",
19
+ "template/.gitignore",
19
20
  "template/package.json",
20
21
  "template/vite.config.js",
21
22
  "docs/",
@@ -0,0 +1,10 @@
1
+ node_modules/
2
+ dist/
3
+
4
+ .env
5
+ .env.*
6
+ !.env.example
7
+ *.local
8
+
9
+ .DS_Store
10
+ .idea/
@@ -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(a.date) - new Date(b.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(() => {