cosmolo 0.5.0 → 0.5.2
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 +5 -5
- package/dist/cli/init.js +1 -0
- package/dist/cli/migrate/drizzle-setup.js +21 -6
- package/dist/cli/migrate/schema.js +1 -0
- package/dist/cli/migrate/sql-export.js +13 -1
- package/dist/loaders.js +1 -1
- package/dist/plugin.js +42 -3
- package/dist/types.d.ts +2 -0
- package/dist/virtual.d.ts +3 -1
- package/package.json +1 -1
- package/templates/full/routes/+page.svelte +1 -1
- package/templates/full/routes/categories/[slug]/+page.svelte +1 -1
- package/templates/full/routes/tags/[tag]/+page.svelte +1 -1
- package/templates/shared/routes/articles/[slug]/+page.server.ts +13 -16
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
|
``,
|
|
@@ -39,7 +39,7 @@ function generateDrizzleSchema() {
|
|
|
39
39
|
function generateArticlesCrud() {
|
|
40
40
|
return `import { drizzle } from 'drizzle-orm/d1';
|
|
41
41
|
import { and, desc, eq, sql } from 'drizzle-orm';
|
|
42
|
-
import { articles } from '
|
|
42
|
+
import { articles } from '../../../drizzle/schema';
|
|
43
43
|
|
|
44
44
|
export function createDb(d1: D1Database) {
|
|
45
45
|
return drizzle(d1);
|
|
@@ -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)
|
|
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) {
|
|
@@ -107,7 +111,7 @@ export async function deleteArticle(d1: D1Database, slug: string) {
|
|
|
107
111
|
function generateCategoriesCrud() {
|
|
108
112
|
return `import { drizzle } from 'drizzle-orm/d1';
|
|
109
113
|
import { eq } from 'drizzle-orm';
|
|
110
|
-
import { categories } from '
|
|
114
|
+
import { categories } from '../../../drizzle/schema';
|
|
111
115
|
|
|
112
116
|
export function createDb(d1: D1Database) {
|
|
113
117
|
return drizzle(d1);
|
|
@@ -170,9 +174,18 @@ export const load: PageServerLoad = async ({ params, platform }) => {
|
|
|
170
174
|
if (!raw) error(404, 'Article not found');
|
|
171
175
|
const article = {
|
|
172
176
|
...parseArticle(raw),
|
|
173
|
-
|
|
177
|
+
html: await marked(raw.body ?? ''),
|
|
178
|
+
toc: [],
|
|
179
|
+
};
|
|
180
|
+
return {
|
|
181
|
+
article,
|
|
182
|
+
categories,
|
|
183
|
+
updatedAt: raw.updated_at ?? '',
|
|
184
|
+
related: [],
|
|
185
|
+
seriesTotal: 0,
|
|
186
|
+
seriesPrev: null,
|
|
187
|
+
seriesNext: null,
|
|
174
188
|
};
|
|
175
|
-
return { article, categories };
|
|
176
189
|
};
|
|
177
190
|
`;
|
|
178
191
|
}
|
|
@@ -192,7 +205,9 @@ export const load: PageServerLoad = async ({ params, platform }) => {
|
|
|
192
205
|
if (!category) error(404, 'Category not found');
|
|
193
206
|
return {
|
|
194
207
|
articles: rawArticles.map(parseArticle),
|
|
195
|
-
category,
|
|
208
|
+
label: category.label,
|
|
209
|
+
description: category.description ?? '',
|
|
210
|
+
slug: params.slug,
|
|
196
211
|
articlesPerPage: siteConfig.articlesPerPage ?? 10,
|
|
197
212
|
};
|
|
198
213
|
};
|
|
@@ -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}
|
|
78
|
+
? `import.meta.glob('${articlesDir}/**/*.md', { query: '?raw', import: 'default', eager: true })`
|
|
49
79
|
: '{}';
|
|
50
80
|
const svxModulesExpr = articlesExist
|
|
51
|
-
? `import.meta.glob('${articlesDir}
|
|
81
|
+
? `import.meta.glob('${articlesDir}/**/*.svx', { eager: true })`
|
|
52
82
|
: '{}';
|
|
53
83
|
const rawPageFilesExpr = pagesExist
|
|
54
|
-
? `import.meta.glob('${pagesDir}
|
|
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
|
-
|
|
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,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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
18
|
+
export const load = createArticleLoader(config);
|