cosmolo 0.5.0 → 0.5.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/dist/articles.js CHANGED
@@ -2,7 +2,7 @@ import { z } from 'zod';
2
2
  import matter from 'gray-matter';
3
3
  import { renderMarkdown, generateToc } from './markdown.js';
4
4
  import { isKnownCategory } from './categories.js';
5
- import { rawMdFiles, svxModules } from 'cosmolo:content';
5
+ import { rawMdFiles, svxModules, updatedAtMap } from 'cosmolo:content';
6
6
  export const articleFrontmatterSchema = z.object({
7
7
  title: z.string(),
8
8
  category: z.string(),
@@ -30,14 +30,14 @@ export function getArticles(config) {
30
30
  const frontmatter = articleFrontmatterSchema.parse(data);
31
31
  if (frontmatter.draft)
32
32
  continue;
33
- articles.push({ ...frontmatter, slug, html: '', markdown: '', toc: [] });
33
+ articles.push({ ...frontmatter, slug, html: '', markdown: '', toc: [], updatedAt: updatedAtMap[slug] ?? '' });
34
34
  }
35
35
  for (const [filePath, mod] of Object.entries(svxModules)) {
36
36
  const slug = slugFromPath(filePath, config.articlesDir);
37
37
  const frontmatter = articleFrontmatterSchema.parse(mod.metadata);
38
38
  if (frontmatter.draft)
39
39
  continue;
40
- articles.push({ ...frontmatter, slug, html: '', markdown: '', toc: [] });
40
+ articles.push({ ...frontmatter, slug, html: '', markdown: '', toc: [], updatedAt: updatedAtMap[slug] ?? '' });
41
41
  }
42
42
  return articles.sort((a, b) => b.sort - a.sort);
43
43
  }
@@ -56,11 +56,11 @@ export async function getArticle(config, slug) {
56
56
  const frontmatter = articleFrontmatterSchema.parse(data);
57
57
  const html = await renderMarkdown(content);
58
58
  const toc = generateToc(content);
59
- return { ...frontmatter, slug, html, markdown: content, toc };
59
+ return { ...frontmatter, slug, html, markdown: content, toc, updatedAt: updatedAtMap[slug] ?? '' };
60
60
  }
61
61
  if (svxModules[svxPath] !== undefined) {
62
62
  const frontmatter = articleFrontmatterSchema.parse(svxModules[svxPath].metadata);
63
- return { ...frontmatter, slug, html: '', markdown: '', toc: [] };
63
+ return { ...frontmatter, slug, html: '', markdown: '', toc: [], updatedAt: updatedAtMap[slug] ?? '' };
64
64
  }
65
65
  throw new Error(`Article not found: ${slug}`);
66
66
  }
package/dist/cli/init.js CHANGED
@@ -220,6 +220,7 @@ export async function main() {
220
220
  const projectName = readProjectName(PROJECT_ROOT);
221
221
  const wranglerToml = [
222
222
  `name = "${projectName}"`,
223
+ `pages_build_output_dir = ".svelte-kit/cloudflare"`,
223
224
  `compatibility_date = "${new Date().toISOString().slice(0, 10)}"`,
224
225
  `compatibility_flags = ["nodejs_compat"]`,
225
226
  ``,
@@ -96,7 +96,11 @@ export async function updateArticle(
96
96
  slug: string,
97
97
  data: Partial<typeof articles.$inferInsert>
98
98
  ) {
99
- return createDb(d1).update(articles).set(data).where(eq(articles.slug, slug)).returning();
99
+ return createDb(d1)
100
+ .update(articles)
101
+ .set({ ...data, updated_at: new Date().toISOString().split('T')[0] })
102
+ .where(eq(articles.slug, slug))
103
+ .returning();
100
104
  }
101
105
 
102
106
  export async function deleteArticle(d1: D1Database, slug: string) {
@@ -13,6 +13,7 @@ export const ARTICLE_COLUMNS = [
13
13
  { name: 'draft', type: 'INTEGER', notNull: true, defaultValue: 0 },
14
14
  { name: 'related', type: 'TEXT', notNull: true, defaultValue: '[]' },
15
15
  { name: 'body', type: 'TEXT', notNull: true },
16
+ { name: 'updated_at', type: 'TEXT', notNull: true, defaultValue: '' },
16
17
  ];
17
18
  export const CATEGORY_COLUMNS = [
18
19
  { name: 'key', type: 'TEXT', notNull: true, primaryKey: true },
@@ -1,7 +1,17 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
+ import { execSync } from 'child_process';
3
4
  import matter from 'gray-matter';
4
5
  import { ARTICLE_COLUMNS, CATEGORY_COLUMNS, createTableSql, escapeSql, toSqlLiteral, } from './schema.js';
6
+ function gitUpdatedAt(filePath) {
7
+ try {
8
+ const result = execSync(`git log -1 --format=%cI -- "${filePath}"`, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
9
+ return result ? result.split('T')[0] : '';
10
+ }
11
+ catch {
12
+ return '';
13
+ }
14
+ }
5
15
  export function buildCategoriesInserts(categoriesPath) {
6
16
  if (!fs.existsSync(categoriesPath))
7
17
  return [];
@@ -36,6 +46,7 @@ export function buildArticlesInserts(articlesDir) {
36
46
  const dateVal = data.date instanceof Date
37
47
  ? data.date.toISOString().split('T')[0]
38
48
  : (data.date ?? '');
49
+ const updatedAt = gitUpdatedAt(filePath);
39
50
  const values = [
40
51
  toSqlLiteral(slug),
41
52
  toSqlLiteral(data.title ?? ''),
@@ -49,8 +60,9 @@ export function buildArticlesInserts(articlesDir) {
49
60
  toSqlLiteral(data.draft ?? false),
50
61
  toSqlLiteral(data.related ?? []),
51
62
  toSqlLiteral(content),
63
+ toSqlLiteral(updatedAt),
52
64
  ].join(', ');
53
- const cols = 'slug, title, category, excerpt, sort, date, tags, series, series_order, draft, related, body';
65
+ const cols = 'slug, title, category, excerpt, sort, date, tags, series, series_order, draft, related, body, updated_at';
54
66
  return `INSERT INTO articles (${cols}) VALUES (${values});`;
55
67
  });
56
68
  }
package/dist/loaders.js CHANGED
@@ -30,7 +30,7 @@ export function createArticlesLoader(config) {
30
30
  export function createArticleLoader(config, options = {}) {
31
31
  return async ({ params }) => {
32
32
  const article = await getArticle(config, params.slug);
33
- const updatedAt = options.getUpdatedAt?.(params.slug) ?? '';
33
+ const updatedAt = options.getUpdatedAt?.(params.slug) ?? article.updatedAt;
34
34
  let related;
35
35
  if (article.related.length > 0) {
36
36
  const all = getArticles(config);
package/dist/plugin.js CHANGED
@@ -1,7 +1,33 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
+ import { execSync } from 'child_process';
3
4
  import { resolveConfig } from './config.js';
4
5
  export { resolveConfig } from './config.js';
6
+ function collectArticleSlugs(dir) {
7
+ const results = [];
8
+ function walk(current) {
9
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
10
+ const full = path.join(current, entry.name);
11
+ if (entry.isDirectory())
12
+ walk(full);
13
+ else if (/\.(md|svx)$/.test(entry.name)) {
14
+ const slug = path.relative(dir, full).replace(/\.(md|svx)$/, '').replace(/\\/g, '/');
15
+ results.push({ filePath: full, slug });
16
+ }
17
+ }
18
+ }
19
+ walk(dir);
20
+ return results;
21
+ }
22
+ function gitUpdatedAt(filePath, cwd) {
23
+ try {
24
+ const result = execSync(`git log -1 --format=%cI -- "${filePath}"`, { encoding: 'utf-8', cwd, stdio: ['ignore', 'pipe', 'ignore'] }).trim();
25
+ return result ? result.split('T')[0] : '';
26
+ }
27
+ catch {
28
+ return '';
29
+ }
30
+ }
5
31
  const VIRTUAL_ID = 'cosmolo:content';
6
32
  const RESOLVED_ID = '\0cosmolo:content';
7
33
  /**
@@ -22,8 +48,12 @@ export function cosmoloPlugin(userConfig = {}) {
22
48
  const pagesDir = '/' + config.pagesDir.replace(/^\/|\/$/g, '');
23
49
  const categoriesAbsPath = path.resolve(process.cwd(), config.categoriesConfigPath);
24
50
  const siteConfigAbsPath = path.resolve(process.cwd(), config.siteConfigPath);
51
+ let isBuild = false;
25
52
  return {
26
53
  name: 'cosmolo',
54
+ configResolved(resolved) {
55
+ isBuild = resolved.command === 'build';
56
+ },
27
57
  config() {
28
58
  return {
29
59
  ssr: { noExternal: ['cosmolo'] },
@@ -45,20 +75,29 @@ export function cosmoloPlugin(userConfig = {}) {
45
75
  const articlesExist = fs.existsSync(articlesDirAbs);
46
76
  const pagesExist = fs.existsSync(pagesDirAbs);
47
77
  const rawMdFilesExpr = articlesExist
48
- ? `import.meta.glob('${articlesDir}/*.md', { query: '?raw', import: 'default', eager: true })`
78
+ ? `import.meta.glob('${articlesDir}/**/*.md', { query: '?raw', import: 'default', eager: true })`
49
79
  : '{}';
50
80
  const svxModulesExpr = articlesExist
51
- ? `import.meta.glob('${articlesDir}/*.svx', { eager: true })`
81
+ ? `import.meta.glob('${articlesDir}/**/*.svx', { eager: true })`
52
82
  : '{}';
53
83
  const rawPageFilesExpr = pagesExist
54
- ? `import.meta.glob('${pagesDir}/*.md', { query: '?raw', import: 'default', eager: true })`
84
+ ? `import.meta.glob('${pagesDir}/**/*.md', { query: '?raw', import: 'default', eager: true })`
55
85
  : '{}';
86
+ // Compute git updated-at dates at build time only (skipped in dev for speed)
87
+ const updatedAtMap = {};
88
+ if (isBuild && articlesExist) {
89
+ const cwd = process.cwd();
90
+ for (const { filePath, slug } of collectArticleSlugs(articlesDirAbs)) {
91
+ updatedAtMap[slug] = gitUpdatedAt(filePath, cwd);
92
+ }
93
+ }
56
94
  return `
57
95
  export const rawMdFiles = ${rawMdFilesExpr};
58
96
  export const svxModules = ${svxModulesExpr};
59
97
  export const rawPageFiles = ${rawPageFilesExpr};
60
98
  export const categoriesData = ${JSON.stringify(categoriesData)};
61
99
  export const siteConfigData = ${JSON.stringify(siteConfigData)};
100
+ export const updatedAtMap = ${JSON.stringify(updatedAtMap)};
62
101
  `.trim();
63
102
  },
64
103
  };
package/dist/types.d.ts CHANGED
@@ -31,6 +31,8 @@ export interface Article extends ArticleFrontmatter {
31
31
  html: string;
32
32
  markdown: string;
33
33
  toc: TocEntry[];
34
+ /** Last git commit date (YYYY-MM-DD). Empty string when unavailable or in dev mode. */
35
+ updatedAt: string;
34
36
  }
35
37
  export interface Page {
36
38
  slug: string;
package/dist/virtual.d.ts CHANGED
@@ -4,5 +4,7 @@ declare module 'cosmolo:content' {
4
4
  const rawPageFiles: Record<string, string>;
5
5
  const categoriesData: Record<string, { label: string; description: string }>;
6
6
  const siteConfigData: import('./types.js').SiteConfig;
7
- export { rawMdFiles, svxModules, rawPageFiles, categoriesData, siteConfigData };
7
+ /** slug ISO date string (YYYY-MM-DD). Computed from git at build time; empty string in dev mode or when git is unavailable. */
8
+ const updatedAtMap: Record<string, string>;
9
+ export { rawMdFiles, svxModules, rawPageFiles, categoriesData, siteConfigData, updatedAtMap };
8
10
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cosmolo",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "cosmolo": "dist/cli/index.js"
@@ -6,7 +6,7 @@
6
6
 
7
7
  const { data }: { data: PageData } = $props();
8
8
 
9
- const perPage = data.articlesPerPage;
9
+ const perPage = $derived(data.articlesPerPage);
10
10
 
11
11
  let query = $state('');
12
12
  let currentPage = $state(1);
@@ -4,7 +4,7 @@
4
4
 
5
5
  const { data }: { data: PageData } = $props();
6
6
 
7
- const perPage = data.articlesPerPage;
7
+ const perPage = $derived(data.articlesPerPage);
8
8
  let currentPage = $state(1);
9
9
 
10
10
  $effect(() => {
@@ -6,7 +6,7 @@
6
6
 
7
7
  const { data }: { data: PageData } = $props();
8
8
 
9
- const perPage = data.articlesPerPage;
9
+ const perPage = $derived(data.articlesPerPage);
10
10
  let currentPage = $state(1);
11
11
 
12
12
  $effect(() => {
@@ -1,21 +1,18 @@
1
1
  import { createArticleLoader, createArticleEntries } from 'cosmolo';
2
- import { execSync } from 'child_process';
3
2
  import config from '../../../../cosmolo.config';
4
3
 
5
- function getUpdatedAt(slug: string): string {
6
- for (const ext of ['md', 'svx']) {
7
- try {
8
- const result = execSync(
9
- `git log -1 --format=%cI -- "src/content/articles/${slug}.${ext}"`,
10
- { encoding: 'utf-8' }
11
- ).trim();
12
- if (result) return result.split('T')[0];
13
- } catch {
14
- // git not available or file not tracked
15
- }
16
- }
17
- return '';
18
- }
4
+ // To show a git-based "updated" date (SSG / Node.js only — not Cloudflare Workers):
5
+ // import { execSync } from 'child_process';
6
+ // export const load = createArticleLoader(config, {
7
+ // getUpdatedAt(slug) {
8
+ // try {
9
+ // return execSync(
10
+ // `git log -1 --format=%cI -- "src/content/articles/${slug}.md"`,
11
+ // { encoding: 'utf-8' }
12
+ // ).trim().split('T')[0];
13
+ // } catch { return ''; }
14
+ // },
15
+ // });
19
16
 
20
17
  export const entries = createArticleEntries(config);
21
- export const load = createArticleLoader(config, { getUpdatedAt });
18
+ export const load = createArticleLoader(config);