create-jant 0.2.3 → 0.2.5
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/index.js +35 -8
- package/package.json +1 -1
- package/template/_env.example +8 -1
- package/template/scripts/export-demo.mjs +77 -0
- package/template/scripts/export-seed.mjs +21 -5
- package/template/scripts/{reset.sql → reset-demo.sql} +6 -6
- package/template/scripts/{reset-dev.sql → reset-local.sql} +1 -1
- package/template/scripts/seed-demo.sql +195 -0
- package/template/scripts/{seed-dev.sql → seed-local.sql} +2 -2
- package/template/wrangler.demo.toml +1 -0
- package/template/wrangler.toml +13 -1
- package/template/scripts/seed.sql +0 -100
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import path from "path";
|
|
|
8
8
|
import { fileURLToPath } from "url";
|
|
9
9
|
var __filename = fileURLToPath(import.meta.url);
|
|
10
10
|
var __dirname = path.dirname(__filename);
|
|
11
|
-
var CORE_VERSION = "0.3.
|
|
11
|
+
var CORE_VERSION = "0.3.9";
|
|
12
12
|
var TEMPLATE_DIR = fs.existsSync(path.resolve(__dirname, "../template")) ? path.resolve(__dirname, "../template") : path.resolve(__dirname, "../../../templates/jant-site");
|
|
13
13
|
function isValidProjectName(name) {
|
|
14
14
|
return /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(name);
|
|
@@ -32,14 +32,19 @@ async function copyTemplate(config) {
|
|
|
32
32
|
if (basename === "pnpm-lock.yaml") return false;
|
|
33
33
|
if (basename === "dist") return false;
|
|
34
34
|
if (basename === "wrangler.demo.toml") return false;
|
|
35
|
-
if (basename === "reset.sql") return false;
|
|
36
|
-
if (basename === "seed.sql") return false;
|
|
35
|
+
if (basename === "reset-demo.sql") return false;
|
|
36
|
+
if (basename === "seed-demo.sql") return false;
|
|
37
|
+
if (basename === "reset-local.sql") return false;
|
|
38
|
+
if (basename === "seed-local.sql") return false;
|
|
39
|
+
if (basename === "export-demo.mjs") return false;
|
|
37
40
|
return true;
|
|
38
41
|
}
|
|
39
42
|
});
|
|
43
|
+
const secretsExampleFile = ".dev.vars.example";
|
|
44
|
+
const secretsFile = ".dev.vars";
|
|
40
45
|
const renames = [
|
|
41
46
|
["_gitignore", ".gitignore"],
|
|
42
|
-
["_env.example",
|
|
47
|
+
["_env.example", secretsExampleFile],
|
|
43
48
|
["_github", ".github"]
|
|
44
49
|
];
|
|
45
50
|
for (const [from, to] of renames) {
|
|
@@ -72,15 +77,36 @@ async function copyTemplate(config) {
|
|
|
72
77
|
return `${prefix}"${value}"`;
|
|
73
78
|
}
|
|
74
79
|
);
|
|
80
|
+
content = content.replace(/^.*#\s*@create-jant:\s*@remove\s*\n?/gm, "");
|
|
75
81
|
await fs.writeFile(wranglerPath, content, "utf-8");
|
|
76
82
|
}
|
|
77
83
|
const authSecret = generateAuthSecret();
|
|
78
|
-
|
|
84
|
+
let devVarsContent = `# Generated by create-jant
|
|
79
85
|
# AUTH_SECRET is used for session encryption (better-auth)
|
|
80
86
|
AUTH_SECRET=${authSecret}
|
|
81
87
|
`;
|
|
88
|
+
if (config.s3) {
|
|
89
|
+
if (await fs.pathExists(wranglerPath)) {
|
|
90
|
+
let wContent = await fs.readFile(wranglerPath, "utf-8");
|
|
91
|
+
wContent = wContent.replace(
|
|
92
|
+
/^# STORAGE_DRIVER = "s3"/m,
|
|
93
|
+
'STORAGE_DRIVER = "s3"'
|
|
94
|
+
);
|
|
95
|
+
wContent = wContent.replace(/^# S3_ENDPOINT = /m, "S3_ENDPOINT = ");
|
|
96
|
+
wContent = wContent.replace(/^# S3_BUCKET = /m, "S3_BUCKET = ");
|
|
97
|
+
wContent = wContent.replace(/^# S3_REGION = /m, "S3_REGION = ");
|
|
98
|
+
wContent = wContent.replace(/^# S3_PUBLIC_URL = /m, "S3_PUBLIC_URL = ");
|
|
99
|
+
wContent = wContent.replace(/\n\[\[r2_buckets\]\][^[]*/s, "\n");
|
|
100
|
+
await fs.writeFile(wranglerPath, wContent, "utf-8");
|
|
101
|
+
}
|
|
102
|
+
devVarsContent += `
|
|
103
|
+
# S3-compatible storage credentials
|
|
104
|
+
S3_ACCESS_KEY_ID=
|
|
105
|
+
S3_SECRET_ACCESS_KEY=
|
|
106
|
+
`;
|
|
107
|
+
}
|
|
82
108
|
await fs.writeFile(
|
|
83
|
-
path.join(targetDir,
|
|
109
|
+
path.join(targetDir, secretsFile),
|
|
84
110
|
devVarsContent,
|
|
85
111
|
"utf-8"
|
|
86
112
|
);
|
|
@@ -101,7 +127,7 @@ AUTH_SECRET=${authSecret}
|
|
|
101
127
|
async function main() {
|
|
102
128
|
console.log();
|
|
103
129
|
p.intro(chalk.bgCyan.black(" create-jant "));
|
|
104
|
-
program.name("create-jant").description("Create a new Jant project").argument("[project-name]", "Name of the project").option("-y, --yes", "Skip prompts and use defaults").parse();
|
|
130
|
+
program.name("create-jant").description("Create a new Jant project").argument("[project-name]", "Name of the project").option("-y, --yes", "Skip prompts and use defaults").option("--s3", "Use S3-compatible storage instead of Cloudflare R2").parse();
|
|
105
131
|
const args = program.args;
|
|
106
132
|
const opts = program.opts();
|
|
107
133
|
let projectName;
|
|
@@ -159,7 +185,8 @@ async function main() {
|
|
|
159
185
|
}
|
|
160
186
|
const config = {
|
|
161
187
|
projectName,
|
|
162
|
-
targetDir
|
|
188
|
+
targetDir,
|
|
189
|
+
s3: opts.s3
|
|
163
190
|
};
|
|
164
191
|
const spinner2 = p.spinner();
|
|
165
192
|
spinner2.start("Creating project...");
|
package/package.json
CHANGED
package/template/_env.example
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Secrets — do not commit this file
|
|
2
|
+
#
|
|
3
|
+
# Cloudflare Workers: copy to .dev.vars
|
|
4
|
+
# VPS / Docker: copy to .env
|
|
2
5
|
|
|
3
6
|
# AUTH_SECRET must be at least 32 characters
|
|
4
7
|
# Generate one with: openssl rand -base64 32
|
|
5
8
|
AUTH_SECRET=your-secret-key-at-least-32-chars-long
|
|
9
|
+
|
|
10
|
+
# Required when STORAGE_DRIVER=s3:
|
|
11
|
+
# S3_ACCESS_KEY_ID=your-access-key
|
|
12
|
+
# S3_SECRET_ACCESS_KEY=your-secret-key
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import { writeFileSync } from "fs";
|
|
3
|
+
import { resolve, dirname } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
|
|
8
|
+
function sqlValue(v) {
|
|
9
|
+
if (v === null) return "NULL";
|
|
10
|
+
if (typeof v === "number") return String(v);
|
|
11
|
+
return "'" + String(v).replaceAll("'", "''") + "'";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function queryRemote(sql) {
|
|
15
|
+
let stdout;
|
|
16
|
+
try {
|
|
17
|
+
stdout = execSync(
|
|
18
|
+
`pnpm exec wrangler d1 execute DB --remote --config wrangler.demo.toml --command "${sql}" --json`,
|
|
19
|
+
{ encoding: "utf-8", cwd: resolve(__dirname, "..") }
|
|
20
|
+
);
|
|
21
|
+
} catch (err) {
|
|
22
|
+
// Wrangler returns JSON errors on stdout even with non-zero exit codes
|
|
23
|
+
const output = err.stdout || err.stderr || "";
|
|
24
|
+
try {
|
|
25
|
+
const errJson = JSON.parse(output.trim());
|
|
26
|
+
if (errJson.error?.text) {
|
|
27
|
+
console.error(`Wrangler error: ${errJson.error.text}`);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
// Not JSON, fall through
|
|
32
|
+
}
|
|
33
|
+
console.error(`Failed to query remote database: ${output || err.message}`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
const parsed = JSON.parse(stdout);
|
|
37
|
+
if (parsed.error?.text) {
|
|
38
|
+
console.error(`Wrangler error: ${parsed.error.text}`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
return parsed[0]?.results || [];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function dumpTable(name, query) {
|
|
45
|
+
const rows = queryRemote(query || `SELECT * FROM ${name}`);
|
|
46
|
+
return rows
|
|
47
|
+
.map(
|
|
48
|
+
(row) =>
|
|
49
|
+
`INSERT INTO ${name} VALUES(${Object.values(row).map(sqlValue).join(",")});`
|
|
50
|
+
)
|
|
51
|
+
.join("\n");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const header = `-- =============================================================================
|
|
55
|
+
-- Demo seed data for Jant (demo.jant.me)
|
|
56
|
+
-- Exported from remote demo D1 database via: mise run demo-backup
|
|
57
|
+
-- Usage: mise run demo-reset
|
|
58
|
+
-- =============================================================================
|
|
59
|
+
`;
|
|
60
|
+
|
|
61
|
+
const tables = [
|
|
62
|
+
// settings, user, account are preserved by reset-demo.sql — don't export
|
|
63
|
+
["posts", "SELECT * FROM posts WHERE deleted_at IS NULL"],
|
|
64
|
+
["collections"],
|
|
65
|
+
["post_collections"],
|
|
66
|
+
["media"],
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
let sql = header;
|
|
70
|
+
for (const [name, query] of tables) {
|
|
71
|
+
const data = dumpTable(name, query);
|
|
72
|
+
if (data) sql += `\n-- ${name}\n${data}\n`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const out = resolve(__dirname, "seed-demo.sql");
|
|
76
|
+
writeFileSync(out, sql);
|
|
77
|
+
console.log("Exported demo database to templates/jant-site/scripts/seed-demo.sql");
|
|
@@ -3,6 +3,13 @@ import { readdirSync, writeFileSync } from "fs";
|
|
|
3
3
|
import { resolve, dirname } from "path";
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
5
5
|
|
|
6
|
+
// Parse flags
|
|
7
|
+
const args = process.argv.slice(2);
|
|
8
|
+
const noMedia = args.includes("--no-media");
|
|
9
|
+
const outputIndex = args.indexOf("--output");
|
|
10
|
+
const outputFile =
|
|
11
|
+
outputIndex !== -1 ? args[outputIndex + 1] : "seed-local.sql";
|
|
12
|
+
|
|
6
13
|
// better-sqlite3 is installed in packages/core
|
|
7
14
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
15
|
const coreRequire = createRequire(
|
|
@@ -39,7 +46,7 @@ function dumpTable(name, query) {
|
|
|
39
46
|
}
|
|
40
47
|
|
|
41
48
|
const header = `-- =============================================================================
|
|
42
|
-
--
|
|
49
|
+
-- ${noMedia ? "Seed data (without media)" : "Local development seed data"} for Jant
|
|
43
50
|
-- Exported from local D1 database
|
|
44
51
|
-- Usage: mise run db-seed
|
|
45
52
|
-- =============================================================================
|
|
@@ -49,19 +56,28 @@ const tables = [
|
|
|
49
56
|
["settings"],
|
|
50
57
|
["user"],
|
|
51
58
|
["account"],
|
|
52
|
-
[
|
|
59
|
+
[
|
|
60
|
+
"posts",
|
|
61
|
+
noMedia
|
|
62
|
+
? "SELECT * FROM posts WHERE deleted_at IS NULL AND type != 'image'"
|
|
63
|
+
: "SELECT * FROM posts WHERE deleted_at IS NULL",
|
|
64
|
+
],
|
|
53
65
|
["collections"],
|
|
54
66
|
["post_collections"],
|
|
55
|
-
["media"],
|
|
56
67
|
];
|
|
57
68
|
|
|
69
|
+
// Include media table only when --no-media is not set
|
|
70
|
+
if (!noMedia) {
|
|
71
|
+
tables.push(["media"]);
|
|
72
|
+
}
|
|
73
|
+
|
|
58
74
|
let sql = header;
|
|
59
75
|
for (const [name, query] of tables) {
|
|
60
76
|
const data = dumpTable(name, query);
|
|
61
77
|
if (data) sql += `\n-- ${name}\n${data}\n`;
|
|
62
78
|
}
|
|
63
79
|
|
|
64
|
-
const out = resolve(__dirname,
|
|
80
|
+
const out = resolve(__dirname, outputFile);
|
|
65
81
|
writeFileSync(out, sql);
|
|
66
82
|
db.close();
|
|
67
|
-
console.log(
|
|
83
|
+
console.log(`Exported to templates/jant-site/scripts/${outputFile}`);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
-- Reset script for Jant demo site
|
|
2
|
-
-- Clears all user-created data while preserving schema
|
|
1
|
+
-- Reset script for Jant demo site (demo.jant.me)
|
|
2
|
+
-- Clears all user-created data while preserving users/schema
|
|
3
|
+
-- Usage: mise run demo-reset (runs this then seed-demo.sql)
|
|
3
4
|
|
|
4
5
|
-- Clear FTS index first (to avoid foreign key issues)
|
|
5
6
|
DELETE FROM posts_fts;
|
|
@@ -13,12 +14,11 @@ DELETE FROM posts;
|
|
|
13
14
|
DELETE FROM collections;
|
|
14
15
|
DELETE FROM redirects;
|
|
15
16
|
|
|
16
|
-
--
|
|
17
|
-
|
|
18
|
-
DELETE FROM verification;
|
|
17
|
+
-- Sessions, users, accounts, and settings are preserved
|
|
18
|
+
-- (only content data is reset)
|
|
19
19
|
|
|
20
20
|
-- Reset auto-increment counters
|
|
21
21
|
DELETE FROM sqlite_sequence WHERE name IN ('posts', 'media', 'collections', 'redirects');
|
|
22
22
|
|
|
23
23
|
-- Note: Settings and users are preserved
|
|
24
|
-
-- Seed data will be re-inserted by seed.sql
|
|
24
|
+
-- Seed data will be re-inserted by seed-demo.sql
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
-- =============================================================================
|
|
2
2
|
-- Reset script for local development
|
|
3
3
|
-- Clears ALL data (including users) to prepare for re-seeding
|
|
4
|
-
-- Usage: mise run db-seed
|
|
4
|
+
-- Usage: mise run db-seed (runs this then seed-local.sql)
|
|
5
5
|
-- =============================================================================
|
|
6
6
|
|
|
7
7
|
-- Clear FTS index first
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
-- =============================================================================
|
|
2
|
+
-- Demo seed data for Jant (demo.jant.me)
|
|
3
|
+
-- Exported from remote demo D1 database via: mise run demo-backup
|
|
4
|
+
-- Usage: mise run demo-reset
|
|
5
|
+
-- =============================================================================
|
|
6
|
+
|
|
7
|
+
-- posts
|
|
8
|
+
INSERT INTO posts VALUES(1,'article','featured','Welcome to Jant',NULL,'# Welcome to Jant Demo
|
|
9
|
+
|
|
10
|
+
Jant is a modern microblog platform built for Cloudflare Workers. This demo site resets daily at 00:00 UTC.
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- **Multiple post types**: Notes, articles, links, quotes, images, and pages
|
|
15
|
+
- **Collections**: Organize posts into collections
|
|
16
|
+
- **Full-text search**: Search across all your content
|
|
17
|
+
- **Internationalization**: Built-in i18n support
|
|
18
|
+
- **Fast**: Edge-deployed on Cloudflare Workers
|
|
19
|
+
|
|
20
|
+
## Getting Started
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pnpm create jant my-blog
|
|
24
|
+
cd my-blog
|
|
25
|
+
pnpm install
|
|
26
|
+
pnpm dev
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Visit the [dashboard](/dash) to create your own posts!','<h1>Welcome to Jant Demo</h1>
|
|
30
|
+
<p>Jant is a modern microblog platform built for Cloudflare Workers. This demo site resets daily at 00:00 UTC.</p>
|
|
31
|
+
<h2>Features</h2>
|
|
32
|
+
<ul>
|
|
33
|
+
<li><strong>Multiple post types</strong>: Notes, articles, links, quotes, images, and pages</li>
|
|
34
|
+
<li><strong>Collections</strong>: Organize posts into collections</li>
|
|
35
|
+
<li><strong>Full-text search</strong>: Search across all your content</li>
|
|
36
|
+
<li><strong>Internationalization</strong>: Built-in i18n support</li>
|
|
37
|
+
<li><strong>Fast</strong>: Edge-deployed on Cloudflare Workers</li>
|
|
38
|
+
</ul>
|
|
39
|
+
<h2>Getting Started</h2>
|
|
40
|
+
<pre><code class="language-bash">pnpm create jant my-blog
|
|
41
|
+
cd my-blog
|
|
42
|
+
pnpm install
|
|
43
|
+
pnpm dev
|
|
44
|
+
</code></pre>
|
|
45
|
+
<p>Visit the <a href="/dash">dashboard</a> to create your own posts!</p>',NULL,NULL,NULL,NULL,NULL,NULL,1770689095,1770689095,1770689095);
|
|
46
|
+
INSERT INTO posts VALUES(2,'note','quiet',NULL,NULL,'This is a demo note. Notes are short posts without titles, perfect for quick thoughts and updates.','<p>This is a demo note. Notes are short posts without titles, perfect for quick thoughts and updates.</p>',NULL,NULL,NULL,NULL,NULL,NULL,1770685495,1770685495,1770685495);
|
|
47
|
+
INSERT INTO posts VALUES(3,'link','quiet','Jant on GitHub',NULL,'Check out the source code and documentation for Jant.','<p>Check out the source code and documentation for Jant.</p>','https://github.com/nicepkg/jant','GitHub','github.com',NULL,NULL,NULL,1770681895,1770681895,1770681895);
|
|
48
|
+
INSERT INTO posts VALUES(4,'quote','quiet',NULL,NULL,'The best way to predict the future is to invent it.','<p>The best way to predict the future is to invent it.</p>',NULL,'Alan Kay',NULL,NULL,NULL,NULL,1770678295,1770678295,1770678295);
|
|
49
|
+
INSERT INTO posts VALUES(5,'image','quiet',NULL,NULL,'Image 1','<p>Image 1</p>
|
|
50
|
+
',NULL,NULL,NULL,NULL,NULL,NULL,1770758516,1770758516,1770758516);
|
|
51
|
+
INSERT INTO posts VALUES(6,'image','quiet',NULL,NULL,'Image 2','<p>Image 2</p>
|
|
52
|
+
',NULL,NULL,NULL,NULL,NULL,NULL,1770758537,1770758537,1770759299);
|
|
53
|
+
INSERT INTO posts VALUES(7,'page','unlisted','About','about','> **Work in Progress**: This project is still under active development and not yet ready for use. See the latest build at [demo.jant.me](https://demo.jant.me).
|
|
54
|
+
>
|
|
55
|
+
> Demo login: `demo@jant.me` / `demodemo` — Dashboard: [demo.jant.me/dash](https://demo.jant.me/dash)
|
|
56
|
+
|
|
57
|
+
A personal microblogging system as smooth as <https://threads.com>.
|
|
58
|
+
|
|
59
|
+
> **Jant** = Jantelagen (Law of Jante)
|
|
60
|
+
> Low-key, de-socialized personal expression.
|
|
61
|
+
|
|
62
|
+
## What is Jant?
|
|
63
|
+
|
|
64
|
+
Jant is a single-author microblog for people who want to share thoughts without the noise of social media. No followers, no likes, no retweets—just your words.
|
|
65
|
+
|
|
66
|
+
**Features**:
|
|
67
|
+
|
|
68
|
+
- Multiple content types: notes, articles, links, quotes, images
|
|
69
|
+
- Thread support for longer thoughts
|
|
70
|
+
- Collections for curated topics
|
|
71
|
+
- Beautiful, themeable design
|
|
72
|
+
- Deploys to Cloudflare Workers in minutes
|
|
73
|
+
|
|
74
|
+
## Quick Start
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# Create a new Jant site
|
|
78
|
+
pnpm create jant my-blog
|
|
79
|
+
|
|
80
|
+
# Start development
|
|
81
|
+
cd my-blog
|
|
82
|
+
pnpm dev
|
|
83
|
+
|
|
84
|
+
# Deploy to Cloudflare
|
|
85
|
+
pnpm deploy
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Documentation
|
|
89
|
+
|
|
90
|
+
- [Getting Started](docs/getting-started.md)
|
|
91
|
+
- [Deployment](docs/deployment.md)
|
|
92
|
+
- [Configuration](docs/configuration.md)
|
|
93
|
+
- [Theming](docs/theming.md)
|
|
94
|
+
- [API Reference](docs/API.md)
|
|
95
|
+
|
|
96
|
+
## Development
|
|
97
|
+
|
|
98
|
+
Requires [mise](https://mise.jdx.dev/) — it manages Node.js and pnpm automatically.
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# Install mise (macOS/Linux)
|
|
102
|
+
curl https://mise.run | sh
|
|
103
|
+
|
|
104
|
+
# Clone and setup
|
|
105
|
+
git clone https://github.com/jant-me/jant.git
|
|
106
|
+
cd jant
|
|
107
|
+
mise install # installs Node.js and pnpm
|
|
108
|
+
pnpm install # installs dependencies
|
|
109
|
+
|
|
110
|
+
# Start development server (http://localhost:9019)
|
|
111
|
+
mise run dev
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for code style, PR process, and release workflow.
|
|
115
|
+
|
|
116
|
+
## Philosophy
|
|
117
|
+
|
|
118
|
+
Jant is built on the idea that not everything needs to be optimized for engagement. Write for yourself. Share if you want. No metrics, no pressure.
|
|
119
|
+
|
|
120
|
+
## License
|
|
121
|
+
|
|
122
|
+
AGPL-3.0
|
|
123
|
+
','<blockquote>
|
|
124
|
+
<p><strong>Work in Progress</strong>: This project is still under active development and not yet ready for use. See the latest build at <a href="https://demo.jant.me">demo.jant.me</a>.</p>
|
|
125
|
+
<p>Demo login: <code>demo@jant.me</code> / <code>demodemo</code> — Dashboard: <a href="https://demo.jant.me/dash">demo.jant.me/dash</a></p>
|
|
126
|
+
</blockquote>
|
|
127
|
+
<p>A personal microblogging system as smooth as <a href="https://threads.com">https://threads.com</a>.</p>
|
|
128
|
+
<blockquote>
|
|
129
|
+
<p><strong>Jant</strong> = Jantelagen (Law of Jante)<br>Low-key, de-socialized personal expression.</p>
|
|
130
|
+
</blockquote>
|
|
131
|
+
<h2>What is Jant?</h2>
|
|
132
|
+
<p>Jant is a single-author microblog for people who want to share thoughts without the noise of social media. No followers, no likes, no retweets—just your words.</p>
|
|
133
|
+
<p><strong>Features</strong>:</p>
|
|
134
|
+
<ul>
|
|
135
|
+
<li>Multiple content types: notes, articles, links, quotes, images</li>
|
|
136
|
+
<li>Thread support for longer thoughts</li>
|
|
137
|
+
<li>Collections for curated topics</li>
|
|
138
|
+
<li>Beautiful, themeable design</li>
|
|
139
|
+
<li>Deploys to Cloudflare Workers in minutes</li>
|
|
140
|
+
</ul>
|
|
141
|
+
<h2>Quick Start</h2>
|
|
142
|
+
<pre><code class="language-bash"># Create a new Jant site
|
|
143
|
+
pnpm create jant my-blog
|
|
144
|
+
|
|
145
|
+
# Start development
|
|
146
|
+
cd my-blog
|
|
147
|
+
pnpm dev
|
|
148
|
+
|
|
149
|
+
# Deploy to Cloudflare
|
|
150
|
+
pnpm deploy
|
|
151
|
+
</code></pre>
|
|
152
|
+
<h2>Documentation</h2>
|
|
153
|
+
<ul>
|
|
154
|
+
<li><a href="docs/getting-started.md">Getting Started</a></li>
|
|
155
|
+
<li><a href="docs/deployment.md">Deployment</a></li>
|
|
156
|
+
<li><a href="docs/configuration.md">Configuration</a></li>
|
|
157
|
+
<li><a href="docs/theming.md">Theming</a></li>
|
|
158
|
+
<li><a href="docs/API.md">API Reference</a></li>
|
|
159
|
+
</ul>
|
|
160
|
+
<h2>Development</h2>
|
|
161
|
+
<p>Requires <a href="https://mise.jdx.dev/">mise</a> — it manages Node.js and pnpm automatically.</p>
|
|
162
|
+
<pre><code class="language-bash"># Install mise (macOS/Linux)
|
|
163
|
+
curl https://mise.run | sh
|
|
164
|
+
|
|
165
|
+
# Clone and setup
|
|
166
|
+
git clone https://github.com/jant-me/jant.git
|
|
167
|
+
cd jant
|
|
168
|
+
mise install # installs Node.js and pnpm
|
|
169
|
+
pnpm install # installs dependencies
|
|
170
|
+
|
|
171
|
+
# Start development server (http://localhost:9019)
|
|
172
|
+
mise run dev
|
|
173
|
+
</code></pre>
|
|
174
|
+
<p>See <a href="CONTRIBUTING.md">CONTRIBUTING.md</a> for code style, PR process, and release workflow.</p>
|
|
175
|
+
<h2>Philosophy</h2>
|
|
176
|
+
<p>Jant is built on the idea that not everything needs to be optimized for engagement. Write for yourself. Share if you want. No metrics, no pressure.</p>
|
|
177
|
+
<h2>License</h2>
|
|
178
|
+
<p>AGPL-3.0</p>
|
|
179
|
+
',NULL,NULL,NULL,NULL,NULL,NULL,1770759271,1770759271,1770759271);
|
|
180
|
+
|
|
181
|
+
-- collections
|
|
182
|
+
INSERT INTO collections VALUES(1,'getting-started','Getting Started','Resources for getting started with Jant',1770689095,1770689095);
|
|
183
|
+
INSERT INTO collections VALUES(2,'inspires','Inspires',NULL,1770758555,1770758555);
|
|
184
|
+
|
|
185
|
+
-- post_collections
|
|
186
|
+
INSERT INTO post_collections VALUES(1,1,1770689095);
|
|
187
|
+
INSERT INTO post_collections VALUES(6,2,1770758568);
|
|
188
|
+
|
|
189
|
+
-- media
|
|
190
|
+
INSERT INTO media VALUES('019c496c-46bd-7954-bd6a-77b1b8f1d451',NULL,'019c496c-46bd-7954-bd6a-77b1b8f1d451.webp','tegan-conway-KaFfNTw8OYQ-unsplash.webp','image/webp',715364,'media/2026/02/019c496c-46bd-7954-bd6a-77b1b8f1d451.webp',NULL,NULL,NULL,1770758358,0,NULL);
|
|
191
|
+
INSERT INTO media VALUES('019c496c-5e44-70d2-ac8a-c0c0bdaab65c',6,'019c496c-5e44-70d2-ac8a-c0c0bdaab65c.webp','land-o-lakes-inc-9w6Qb-dqBwE-unsplash.webp','image/webp',306042,'media/2026/02/019c496c-5e44-70d2-ac8a-c0c0bdaab65c.webp',NULL,NULL,NULL,1770758364,3,NULL);
|
|
192
|
+
INSERT INTO media VALUES('019c496d-5011-7981-89a0-b4373a695d78',6,'019c496d-5011-7981-89a0-b4373a695d78.webp','land-o-lakes-inc-k71TQkbVIgI-unsplash.webp','image/webp',597680,'media/2026/02/019c496d-5011-7981-89a0-b4373a695d78.webp',NULL,NULL,NULL,1770758426,2,NULL);
|
|
193
|
+
INSERT INTO media VALUES('019c496d-630c-70b4-9004-51a194746566',6,'019c496d-630c-70b4-9004-51a194746566.webp','thingsneverchange-CgHNmQ0c2w4-unsplash.webp','image/webp',358320,'media/2026/02/019c496d-630c-70b4-9004-51a194746566.webp',NULL,NULL,NULL,1770758431,1,NULL);
|
|
194
|
+
INSERT INTO media VALUES('019c496d-720f-70d2-98b8-3779457de73c',6,'019c496d-720f-70d2-98b8-3779457de73c.webp','willian-justen-de-vasconcellos-7jg7Y_Mlf2Q-unsplash.webp','image/webp',478956,'media/2026/02/019c496d-720f-70d2-98b8-3779457de73c.webp',NULL,NULL,NULL,1770758435,0,NULL);
|
|
195
|
+
INSERT INTO media VALUES('019c496e-6904-7f61-b14f-080035ffe23f',5,'019c496e-6904-7f61-b14f-080035ffe23f.webp','richard-stachmann-Es--yoQocSM-unsplash.webp','image/webp',381534,'media/2026/02/019c496e-6904-7f61-b14f-080035ffe23f.webp',NULL,NULL,NULL,1770758498,0,NULL);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
-- =============================================================================
|
|
2
|
-
--
|
|
3
|
-
-- Exported from local D1 database
|
|
2
|
+
-- Local development seed data for Jant
|
|
3
|
+
-- Exported from local D1 database via: mise run db-export
|
|
4
4
|
-- Usage: mise run db-seed
|
|
5
5
|
-- =============================================================================
|
|
6
6
|
|
package/template/wrangler.toml
CHANGED
|
@@ -2,6 +2,7 @@ name = "jant-site" # @create-jant: "${name}"
|
|
|
2
2
|
main = "src/index.ts"
|
|
3
3
|
compatibility_date = "2026-01-20"
|
|
4
4
|
compatibility_flags = ["nodejs_compat"]
|
|
5
|
+
account_id = "03e7294bdb3750ed5a0d6afef6d770e4" # @create-jant: @remove
|
|
5
6
|
|
|
6
7
|
[dev]
|
|
7
8
|
port = 9019
|
|
@@ -28,6 +29,7 @@ SITE_URL = "http://localhost:9019"
|
|
|
28
29
|
|
|
29
30
|
# Optional: R2 Storage (for media uploads)
|
|
30
31
|
# R2_PUBLIC_URL = "https://cdn.example.com"
|
|
32
|
+
R2_PUBLIC_URL = "https://demo-media.jant.me" # @create-jant: @remove
|
|
31
33
|
|
|
32
34
|
# Optional: Cloudflare Image Transformations
|
|
33
35
|
# For automatic thumbnail generation and image optimization
|
|
@@ -37,6 +39,15 @@ SITE_URL = "http://localhost:9019"
|
|
|
37
39
|
# DEMO_EMAIL = "demo@example.com"
|
|
38
40
|
# DEMO_PASSWORD = "demo123"
|
|
39
41
|
|
|
42
|
+
# Optional: S3-compatible storage (alternative to R2)
|
|
43
|
+
# Set STORAGE_DRIVER = "s3" and configure the options below.
|
|
44
|
+
# When using S3, the [[r2_buckets]] section can be removed.
|
|
45
|
+
STORAGE_DRIVER = "s3"
|
|
46
|
+
S3_ENDPOINT = "https://s3.us-east-005.backblazeb2.com"
|
|
47
|
+
S3_BUCKET = "jant-media"
|
|
48
|
+
S3_REGION = "us-east-005"
|
|
49
|
+
S3_PUBLIC_URL = "https://files.owenyoung.com/file/jant-media"
|
|
50
|
+
|
|
40
51
|
[[d1_databases]]
|
|
41
52
|
binding = "DB"
|
|
42
53
|
database_name = "jant-site-db" # @create-jant: "${name}-db"
|
|
@@ -45,4 +56,5 @@ migrations_dir = "../../packages/core/src/db/migrations" # @create-jant: "node_m
|
|
|
45
56
|
|
|
46
57
|
[[r2_buckets]]
|
|
47
58
|
binding = "R2"
|
|
48
|
-
bucket_name = "jant-
|
|
59
|
+
bucket_name = "jant-demo-media" # @create-jant: "${name}-media"
|
|
60
|
+
remote = true # @create-jant: @remove
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
-- Seed data for Jant demo site
|
|
2
|
-
-- This data will be restored after each daily reset
|
|
3
|
-
|
|
4
|
-
-- Settings
|
|
5
|
-
INSERT OR REPLACE INTO settings (key, value, updated_at) VALUES
|
|
6
|
-
('siteName', 'Jant Demo', strftime('%s', 'now')),
|
|
7
|
-
('siteDescription', 'A demo site for Jant - Modern microblog for Cloudflare Workers', strftime('%s', 'now')),
|
|
8
|
-
('siteUrl', 'https://demo.jant.me', strftime('%s', 'now')),
|
|
9
|
-
('postsPerPage', '10', strftime('%s', 'now')),
|
|
10
|
-
('timezone', 'UTC', strftime('%s', 'now')),
|
|
11
|
-
('language', 'en', strftime('%s', 'now'));
|
|
12
|
-
|
|
13
|
-
-- Demo posts
|
|
14
|
-
INSERT INTO posts (type, visibility, title, content, content_html, published_at, created_at, updated_at) VALUES
|
|
15
|
-
-- Welcome article
|
|
16
|
-
('article', 'featured', 'Welcome to Jant',
|
|
17
|
-
'# Welcome to Jant Demo
|
|
18
|
-
|
|
19
|
-
Jant is a modern microblog platform built for Cloudflare Workers. This demo site resets daily at 00:00 UTC.
|
|
20
|
-
|
|
21
|
-
## Features
|
|
22
|
-
|
|
23
|
-
- **Multiple post types**: Notes, articles, links, quotes, images, and pages
|
|
24
|
-
- **Collections**: Organize posts into collections
|
|
25
|
-
- **Full-text search**: Search across all your content
|
|
26
|
-
- **Internationalization**: Built-in i18n support
|
|
27
|
-
- **Fast**: Edge-deployed on Cloudflare Workers
|
|
28
|
-
|
|
29
|
-
## Getting Started
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
pnpm create jant my-blog
|
|
33
|
-
cd my-blog
|
|
34
|
-
pnpm install
|
|
35
|
-
pnpm dev
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
Visit the [dashboard](/dash) to create your own posts!',
|
|
39
|
-
'<h1>Welcome to Jant Demo</h1>
|
|
40
|
-
<p>Jant is a modern microblog platform built for Cloudflare Workers. This demo site resets daily at 00:00 UTC.</p>
|
|
41
|
-
<h2>Features</h2>
|
|
42
|
-
<ul>
|
|
43
|
-
<li><strong>Multiple post types</strong>: Notes, articles, links, quotes, images, and pages</li>
|
|
44
|
-
<li><strong>Collections</strong>: Organize posts into collections</li>
|
|
45
|
-
<li><strong>Full-text search</strong>: Search across all your content</li>
|
|
46
|
-
<li><strong>Internationalization</strong>: Built-in i18n support</li>
|
|
47
|
-
<li><strong>Fast</strong>: Edge-deployed on Cloudflare Workers</li>
|
|
48
|
-
</ul>
|
|
49
|
-
<h2>Getting Started</h2>
|
|
50
|
-
<pre><code class="language-bash">pnpm create jant my-blog
|
|
51
|
-
cd my-blog
|
|
52
|
-
pnpm install
|
|
53
|
-
pnpm dev
|
|
54
|
-
</code></pre>
|
|
55
|
-
<p>Visit the <a href="/dash">dashboard</a> to create your own posts!</p>',
|
|
56
|
-
strftime('%s', 'now'), strftime('%s', 'now'), strftime('%s', 'now')),
|
|
57
|
-
|
|
58
|
-
-- A note
|
|
59
|
-
('note', 'quiet', NULL,
|
|
60
|
-
'This is a demo note. Notes are short posts without titles, perfect for quick thoughts and updates.',
|
|
61
|
-
'<p>This is a demo note. Notes are short posts without titles, perfect for quick thoughts and updates.</p>',
|
|
62
|
-
strftime('%s', 'now') - 3600, strftime('%s', 'now') - 3600, strftime('%s', 'now') - 3600),
|
|
63
|
-
|
|
64
|
-
-- A link post
|
|
65
|
-
('link', 'quiet', 'Jant on GitHub',
|
|
66
|
-
'Check out the source code and documentation for Jant.',
|
|
67
|
-
'<p>Check out the source code and documentation for Jant.</p>',
|
|
68
|
-
strftime('%s', 'now') - 7200, strftime('%s', 'now') - 7200, strftime('%s', 'now') - 7200),
|
|
69
|
-
|
|
70
|
-
-- A quote
|
|
71
|
-
('quote', 'quiet', NULL,
|
|
72
|
-
'The best way to predict the future is to invent it.',
|
|
73
|
-
'<p>The best way to predict the future is to invent it.</p>',
|
|
74
|
-
strftime('%s', 'now') - 10800, strftime('%s', 'now') - 10800, strftime('%s', 'now') - 10800);
|
|
75
|
-
|
|
76
|
-
-- Update the link post with source info
|
|
77
|
-
UPDATE posts SET
|
|
78
|
-
source_url = 'https://github.com/nicepkg/jant',
|
|
79
|
-
source_name = 'GitHub',
|
|
80
|
-
source_domain = 'github.com'
|
|
81
|
-
WHERE type = 'link' AND title = 'Jant on GitHub';
|
|
82
|
-
|
|
83
|
-
-- Update the quote with source info
|
|
84
|
-
UPDATE posts SET
|
|
85
|
-
source_name = 'Alan Kay'
|
|
86
|
-
WHERE type = 'quote' AND content LIKE '%predict the future%';
|
|
87
|
-
|
|
88
|
-
-- Demo collection
|
|
89
|
-
INSERT INTO collections (path, title, description, created_at, updated_at) VALUES
|
|
90
|
-
('getting-started', 'Getting Started', 'Resources for getting started with Jant', strftime('%s', 'now'), strftime('%s', 'now'));
|
|
91
|
-
|
|
92
|
-
-- Add the welcome article to the collection
|
|
93
|
-
INSERT INTO post_collections (post_id, collection_id, added_at)
|
|
94
|
-
SELECT p.id, c.id, strftime('%s', 'now')
|
|
95
|
-
FROM posts p, collections c
|
|
96
|
-
WHERE p.title = 'Welcome to Jant' AND c.path = 'getting-started';
|
|
97
|
-
|
|
98
|
-
-- Update FTS index
|
|
99
|
-
INSERT INTO posts_fts (rowid, title, content)
|
|
100
|
-
SELECT id, COALESCE(title, ''), COALESCE(content, '') FROM posts WHERE deleted_at IS NULL;
|