cosmolo 0.3.9 → 0.5.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.
@@ -0,0 +1,54 @@
1
+ import * as readline from 'readline';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { DEFAULT_CONFIG } from '../config.js';
5
+ async function loadConfig() {
6
+ const root = process.cwd();
7
+ for (const name of ['cosmolo.config.ts', 'cosmolo.config.js']) {
8
+ const p = path.join(root, name);
9
+ if (fs.existsSync(p)) {
10
+ try {
11
+ const mod = await import(p);
12
+ return mod.default;
13
+ }
14
+ catch {
15
+ // fall through to defaults
16
+ }
17
+ }
18
+ }
19
+ return DEFAULT_CONFIG;
20
+ }
21
+ function ask(rl, question) {
22
+ return new Promise((resolve) => rl.question(question, (ans) => resolve(ans.trim())));
23
+ }
24
+ export async function main() {
25
+ const config = await loadConfig();
26
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
27
+ console.log('\ncosmolo migrate:db\n');
28
+ console.log('Select migration type:');
29
+ console.log(' 1. Export as SQL files');
30
+ console.log(' 2. Execute SQL directly');
31
+ console.log(' 3. Full setup with Drizzle ORM + Cloudflare D1');
32
+ const choice = await ask(rl, '\n> ');
33
+ rl.close();
34
+ switch (choice) {
35
+ case '1': {
36
+ const { exportSqlFiles } = await import('./migrate/sql-export.js');
37
+ await exportSqlFiles(config);
38
+ break;
39
+ }
40
+ case '2': {
41
+ const { executeSqlDirect } = await import('./migrate/sql-execute.js');
42
+ await executeSqlDirect(config);
43
+ break;
44
+ }
45
+ case '3': {
46
+ const { drizzleSetup } = await import('./migrate/drizzle-setup.js');
47
+ await drizzleSetup(config);
48
+ break;
49
+ }
50
+ default:
51
+ console.error(`\nInvalid choice: "${choice}". Enter 1, 2, or 3.`);
52
+ process.exit(1);
53
+ }
54
+ }
@@ -0,0 +1 @@
1
+ export declare function main(): Promise<void>;
@@ -0,0 +1,90 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import * as readline from 'readline';
4
+ function ask(rl, question, fallback = '') {
5
+ const hint = fallback ? ` [${fallback}]` : '';
6
+ return new Promise((resolve) => rl.question(` ${question}${hint}: `, (ans) => resolve(ans.trim() || fallback)));
7
+ }
8
+ function r2HelperContent() {
9
+ return (`// Helper for serving assets stored in Cloudflare R2.\n` +
10
+ `// Usage: import in a +server.ts route and call getR2Asset.\n\n` +
11
+ `export async function getR2Asset(bucket: R2Bucket, key: string): Promise<Response> {\n` +
12
+ ` const obj = await bucket.get(key);\n` +
13
+ ` if (!obj) return new Response('Not found', { status: 404 });\n` +
14
+ ` const headers = new Headers();\n` +
15
+ ` obj.writeHttpMetadata(headers);\n` +
16
+ ` headers.set('etag', obj.httpEtag);\n` +
17
+ ` headers.set('cache-control', 'public, max-age=31536000, immutable');\n` +
18
+ ` return new Response(obj.body as ReadableStream, { headers });\n` +
19
+ `}\n`);
20
+ }
21
+ function r2RouteContent(binding) {
22
+ return (`import type { RequestHandler } from './$types';\n` +
23
+ `import { getR2Asset } from '$lib/r2';\n\n` +
24
+ `export const GET: RequestHandler = async ({ params, platform }) => {\n` +
25
+ ` return getR2Asset(platform!.env.${binding}, params.key);\n` +
26
+ `};\n`);
27
+ }
28
+ function appendR2Binding(wranglerPath, binding, bucketName) {
29
+ const existing = fs.readFileSync(wranglerPath, 'utf-8');
30
+ const section = `\n[[r2_buckets]]\n` +
31
+ `binding = "${binding}"\n` +
32
+ `bucket_name = "${bucketName}"\n`;
33
+ fs.writeFileSync(wranglerPath, existing + section);
34
+ }
35
+ export async function main() {
36
+ const root = process.cwd();
37
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
38
+ console.log('\ncosmolo setup:r2\n');
39
+ // wrangler.toml check
40
+ const wranglerPath = path.join(root, 'wrangler.toml');
41
+ if (!fs.existsSync(wranglerPath)) {
42
+ console.error(' wrangler.toml not found.\n' +
43
+ ' Run `cosmolo init` with the Cloudflare adapter first.\n');
44
+ rl.close();
45
+ process.exit(1);
46
+ }
47
+ const bucketName = await ask(rl, 'R2 bucket name', 'assets');
48
+ const binding = await ask(rl, 'Binding name (used in platform.env.*)', 'ASSETS');
49
+ // Conflict checks
50
+ const helperPath = path.join(root, 'src', 'lib', 'r2.ts');
51
+ const routePath = path.join(root, 'src', 'routes', 'assets', '[...key]', '+server.ts');
52
+ const conflicts = [];
53
+ if (fs.existsSync(helperPath))
54
+ conflicts.push('src/lib/r2.ts');
55
+ if (fs.existsSync(routePath))
56
+ conflicts.push('src/routes/assets/[...key]/+server.ts');
57
+ if (conflicts.length > 0) {
58
+ console.log('\n The following files already exist:');
59
+ for (const f of conflicts)
60
+ console.log(` ${f}`);
61
+ const ans = await new Promise((resolve) => rl.question('\n Overwrite? [y/N]: ', (a) => resolve(a.trim().toLowerCase() || 'n')));
62
+ if (ans !== 'y') {
63
+ console.log('\n Aborted. No files were written.\n');
64
+ rl.close();
65
+ process.exit(0);
66
+ }
67
+ }
68
+ rl.close();
69
+ // Write helper
70
+ fs.mkdirSync(path.dirname(helperPath), { recursive: true });
71
+ fs.writeFileSync(helperPath, r2HelperContent());
72
+ // Write route
73
+ fs.mkdirSync(path.dirname(routePath), { recursive: true });
74
+ fs.writeFileSync(routePath, r2RouteContent(binding));
75
+ // Update wrangler.toml
76
+ appendR2Binding(wranglerPath, binding, bucketName);
77
+ console.log('\n✓ Files generated:');
78
+ console.log(' src/lib/r2.ts');
79
+ console.log(' src/routes/assets/[...key]/+server.ts');
80
+ console.log(' wrangler.toml (r2_buckets appended)');
81
+ console.log('\nNext steps:\n');
82
+ console.log(` 1. Create the R2 bucket (if not done yet):`);
83
+ console.log(` bunx wrangler r2 bucket create ${bucketName}\n`);
84
+ console.log(` 2. Add the binding to src/app.d.ts:`);
85
+ console.log(` interface Platform { env: { ${binding}: R2Bucket } }\n`);
86
+ console.log(` 3. Upload assets (e.g. from static/images/):`);
87
+ console.log(` bunx wrangler r2 object put ${bucketName}/<key> --file <path>\n`);
88
+ console.log(` 4. Reference assets via /assets/<key> in your templates.\n`);
89
+ console.log(` See https://developers.cloudflare.com/r2/ for full R2 docs.`);
90
+ }
package/dist/markdown.js CHANGED
@@ -7,6 +7,9 @@ function slugifyHeading(text) {
7
7
  .trim()
8
8
  .replace(/\s+/g, '-');
9
9
  }
10
+ function escapeAttr(val) {
11
+ return val.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
12
+ }
10
13
  marked.use({
11
14
  extensions: [
12
15
  {
@@ -22,7 +25,8 @@ marked.use({
22
25
  },
23
26
  renderer(token) {
24
27
  const { videoId } = token;
25
- return `<div class="youtube-embed"><iframe src="https://www.youtube.com/embed/${videoId}" title="YouTube video" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>\n`;
28
+ const safeId = videoId.replace(/[^a-zA-Z0-9_-]/g, '');
29
+ return `<div class="youtube-embed"><iframe src="https://www.youtube.com/embed/${safeId}" title="YouTube video" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>\n`;
26
30
  },
27
31
  },
28
32
  ],
@@ -32,8 +36,8 @@ marked.use({
32
36
  link({ href, title, text }) {
33
37
  const isExternal = /^https?:\/\//.test(href ?? '');
34
38
  const rel = isExternal ? ' target="_blank" rel="noopener noreferrer"' : '';
35
- const titleAttr = title ? ` title="${title}"` : '';
36
- return `<a href="${href}"${titleAttr}${rel}>${text}</a>`;
39
+ const titleAttr = title ? ` title="${escapeAttr(title)}"` : '';
40
+ return `<a href="${escapeAttr(href ?? '')}"${titleAttr}${rel}>${text}</a>`;
37
41
  },
38
42
  heading({ text, depth }) {
39
43
  const id = slugifyHeading(text);
package/dist/types.d.ts CHANGED
@@ -49,7 +49,4 @@ export interface SiteConfig {
49
49
  twitterHandle: string;
50
50
  fallbackCategoryLabel: string;
51
51
  articlesPerPage: number;
52
- ogImage: {
53
- mode: 'static' | 'generated';
54
- };
55
52
  }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "cosmolo",
3
- "version": "0.3.9",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "bin": {
6
- "cosmolo": "./dist/cli/index.js"
6
+ "cosmolo": "dist/cli/index.js"
7
7
  },
8
8
  "exports": {
9
9
  ".": {
@@ -4,6 +4,5 @@
4
4
  "description": "A content site built with Cosmolo.",
5
5
  "twitterHandle": "@yourhandle",
6
6
  "fallbackCategoryLabel": "Other",
7
- "articlesPerPage": 10,
8
- "ogImage": { "mode": "static" }
7
+ "articlesPerPage": 10
9
8
  }