nowebcli 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/package.json +2 -6
- package/scripts/noweb.mjs +37 -21
- package/README.md +0 -188
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nowebcli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "Create, preview and publish community templates for Noweb",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -35,10 +35,8 @@
|
|
|
35
35
|
"screenshots": "tsx scripts/generate-template-screenshots.ts",
|
|
36
36
|
"template": "node scripts/noweb.mjs",
|
|
37
37
|
"template:publish": "node --env-file-if-exists=.env --env-file-if-exists=.env.local scripts/noweb.mjs publish",
|
|
38
|
-
"migrate
|
|
39
|
-
"migrate:project-snapshots": "node --env-file-if-exists=.env --env-file-if-exists=.env.local --import tsx scripts/migrate-project-snapshots.ts",
|
|
38
|
+
"migrate": "node --env-file-if-exists=.env --env-file-if-exists=.env.local --import tsx scripts/migrate-database.ts",
|
|
40
39
|
"migrate:project-data": "node --env-file-if-exists=.env --env-file-if-exists=.env.local --import tsx scripts/migrate-project-data.ts",
|
|
41
|
-
"migrate:template-ids": "tsx scripts/migrate-positive-template-ids.ts",
|
|
42
40
|
"ci": "npm audit --omit=dev --audit-level=high && npm run typecheck && npm run lint && npm run test && npm run build && npm run test:e2e"
|
|
43
41
|
},
|
|
44
42
|
"dependencies": {
|
|
@@ -60,8 +58,6 @@
|
|
|
60
58
|
"better-auth": "1.7.2",
|
|
61
59
|
"clsx": "^2.1.1",
|
|
62
60
|
"esbuild": "^0.28.1",
|
|
63
|
-
"fumadocs-core": "^16.15.11",
|
|
64
|
-
"fumadocs-ui": "^16.15.11",
|
|
65
61
|
"geist": "^1.7.2",
|
|
66
62
|
"gsap": "^3.15.0",
|
|
67
63
|
"html2canvas-pro": "^2.3.3",
|
package/scripts/noweb.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import { createServer } from "node:http";
|
|
|
7
7
|
import { access, chmod, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
8
8
|
import { homedir } from "node:os";
|
|
9
9
|
import { basename, dirname, extname, join, resolve, sep } from "node:path";
|
|
10
|
+
import { createInterface } from "node:readline/promises";
|
|
10
11
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
11
12
|
import { register } from "tsx/esm/api";
|
|
12
13
|
|
|
@@ -34,15 +35,15 @@ function usage() {
|
|
|
34
35
|
check validate and compile this template
|
|
35
36
|
login connect the CLI to your Noweb account
|
|
36
37
|
list list your uploaded templates
|
|
37
|
-
download <slug
|
|
38
|
+
download <slug> restore one into the current folder
|
|
38
39
|
upload publish this template directly to Noweb
|
|
40
|
+
delete [slug] delete one of your templates
|
|
39
41
|
publish [slug] publish validated templates (deployment only)`);
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
async function createTemplate(value) {
|
|
43
45
|
const validSlug = requireSlug(value || basename(root));
|
|
44
46
|
const directory = root;
|
|
45
|
-
const name = validSlug.split("-").map((part) => part[0].toUpperCase() + part.slice(1)).join(" ");
|
|
46
47
|
|
|
47
48
|
for (const file of ["Template.tsx", "customizations.json", "template.json"]) {
|
|
48
49
|
if (existsSync(join(directory, file))) throw new Error(`Template file already exists: ${file}`);
|
|
@@ -50,7 +51,7 @@ async function createTemplate(value) {
|
|
|
50
51
|
|
|
51
52
|
await mkdir(join(directory, "public"), { recursive: true });
|
|
52
53
|
await Promise.all([
|
|
53
|
-
writeFile(join(directory, "template.json"), `${JSON.stringify({ slug: validSlug,
|
|
54
|
+
writeFile(join(directory, "template.json"), `${JSON.stringify({ slug: validSlug, type: "other" }, null, 2)}\n`),
|
|
54
55
|
writeFile(join(directory, "Template.tsx"), starterTemplate),
|
|
55
56
|
writeFile(join(directory, "customizations.json"), starterCustomizations),
|
|
56
57
|
writeFile(join(directory, "public/.gitkeep"), ""),
|
|
@@ -101,9 +102,7 @@ async function buildTemplate(directory, slugOverride, assetBase = "/assets") {
|
|
|
101
102
|
]);
|
|
102
103
|
const metadata = JSON.parse(metadataSource);
|
|
103
104
|
const currentSlug = requireSlug(slugOverride || metadata?.slug);
|
|
104
|
-
if (!metadata ||
|
|
105
|
-
throw new Error(`${currentSlug}: template.json needs a name and type (${[...types].join(", ")}).`);
|
|
106
|
-
}
|
|
105
|
+
if (!metadata || !types.has(metadata.type)) throw new Error(`${currentSlug}: template.json needs a type (${[...types].join(", ")}).`);
|
|
107
106
|
|
|
108
107
|
const [{ compileTemplateAssets }, { validateTemplateCode }, { validateTemplateCustomizations }] = await Promise.all([
|
|
109
108
|
load("templates/templateCompiler.ts"),
|
|
@@ -167,7 +166,6 @@ async function upload() {
|
|
|
167
166
|
const credentials = await requireCredentials();
|
|
168
167
|
const form = new FormData();
|
|
169
168
|
form.set("slug", template.slug);
|
|
170
|
-
form.set("name", template.name);
|
|
171
169
|
form.set("type", template.type);
|
|
172
170
|
form.set("source", template.source);
|
|
173
171
|
form.set("customizations", JSON.stringify(template.customizations));
|
|
@@ -185,6 +183,29 @@ async function upload() {
|
|
|
185
183
|
console.log(`Published ${template.slug}: ${new URL(result.url, apiBase)}`);
|
|
186
184
|
}
|
|
187
185
|
|
|
186
|
+
async function deleteTemplate(value) {
|
|
187
|
+
const selectedSlug = value
|
|
188
|
+
? requireSlug(value)
|
|
189
|
+
: requireSlug(JSON.parse(await readFile(join(root, "template.json"), "utf8")).slug);
|
|
190
|
+
|
|
191
|
+
if (!cliArgs.includes("--yes")) {
|
|
192
|
+
if (!process.stdin.isTTY) throw new Error("Pass --yes to confirm deletion.");
|
|
193
|
+
const prompt = createInterface({ input: process.stdin, output: process.stdout });
|
|
194
|
+
const answer = await prompt.question(`Delete ${selectedSlug} permanently? [y/N] `);
|
|
195
|
+
prompt.close();
|
|
196
|
+
if (!/^y(?:es)?$/i.test(answer.trim())) return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const credentials = await requireCredentials();
|
|
200
|
+
const response = await fetch(`${apiBase}/api/templates/download?slug=${encodeURIComponent(selectedSlug)}`, {
|
|
201
|
+
method: "DELETE",
|
|
202
|
+
headers: { Authorization: `Bearer ${credentials.token}` },
|
|
203
|
+
});
|
|
204
|
+
const result = await response.json().catch(() => ({}));
|
|
205
|
+
if (!response.ok) throw new Error(result.error || `Delete failed (${response.status}).`);
|
|
206
|
+
console.log(`Deleted ${selectedSlug}`);
|
|
207
|
+
}
|
|
208
|
+
|
|
188
209
|
async function listTemplates() {
|
|
189
210
|
const credentials = await requireCredentials();
|
|
190
211
|
const response = await fetch(`${apiBase}/api/templates/download`, {
|
|
@@ -223,7 +244,7 @@ async function downloadTemplate(value) {
|
|
|
223
244
|
if (!response.ok) throw new Error(result.error || `Download failed (${response.status}).`);
|
|
224
245
|
const template = result.template;
|
|
225
246
|
if (
|
|
226
|
-
!template || typeof template.metadata?.slug !== "string" ||
|
|
247
|
+
!template || typeof template.metadata?.slug !== "string" ||
|
|
227
248
|
!types.has(template.metadata?.type) || typeof template.source !== "string" ||
|
|
228
249
|
!Array.isArray(template.customizations) || !Array.isArray(template.assets) || template.assets.length > 200
|
|
229
250
|
) throw new Error("The server returned an invalid template.");
|
|
@@ -369,28 +390,22 @@ async function publish(value) {
|
|
|
369
390
|
await uploadTemplateAssets(template, storageApi.createObjectStorageClient(storage), storage.bucketName);
|
|
370
391
|
const [published] = await sql`
|
|
371
392
|
INSERT INTO templates (
|
|
372
|
-
|
|
373
|
-
author_id, approved_by, source_slug
|
|
393
|
+
slug, type, component, compiled_js, compiled_css, customizations, author_id
|
|
374
394
|
) VALUES (
|
|
375
|
-
${template.
|
|
376
|
-
${template.css}, ${sql.json(template.customizations)}, 'github'
|
|
377
|
-
${template.slug}
|
|
395
|
+
${template.slug}, ${template.type}, ${template.source}, ${template.js},
|
|
396
|
+
${template.css}, ${sql.json(template.customizations)}, 'github'
|
|
378
397
|
)
|
|
379
|
-
ON CONFLICT (
|
|
380
|
-
name = EXCLUDED.name,
|
|
398
|
+
ON CONFLICT (slug) DO UPDATE SET
|
|
381
399
|
type = EXCLUDED.type,
|
|
382
400
|
component = EXCLUDED.component,
|
|
383
401
|
compiled_js = EXCLUDED.compiled_js,
|
|
384
402
|
compiled_css = EXCLUDED.compiled_css,
|
|
385
|
-
customizations = EXCLUDED.customizations
|
|
386
|
-
|
|
387
|
-
WHERE templates.name IS DISTINCT FROM EXCLUDED.name
|
|
388
|
-
OR templates.type IS DISTINCT FROM EXCLUDED.type
|
|
403
|
+
customizations = EXCLUDED.customizations
|
|
404
|
+
WHERE templates.type IS DISTINCT FROM EXCLUDED.type
|
|
389
405
|
OR templates.component IS DISTINCT FROM EXCLUDED.component
|
|
390
406
|
OR templates.compiled_js IS DISTINCT FROM EXCLUDED.compiled_js
|
|
391
407
|
OR templates.compiled_css IS DISTINCT FROM EXCLUDED.compiled_css
|
|
392
408
|
OR templates.customizations::jsonb IS DISTINCT FROM EXCLUDED.customizations::jsonb
|
|
393
|
-
OR templates.approved_by IS DISTINCT FROM 'github'
|
|
394
409
|
RETURNING id
|
|
395
410
|
`;
|
|
396
411
|
if (!published) {
|
|
@@ -498,7 +513,7 @@ async function uploadTemplateAssets(template, client, bucketName) {
|
|
|
498
513
|
}
|
|
499
514
|
|
|
500
515
|
function previewHtml(template) {
|
|
501
|
-
return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${escapeHtml(template.
|
|
516
|
+
return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${escapeHtml(template.slug)}</title><style>${escapeClosingTag(template.css, "style")}</style></head><body><div id="root"></div><script>${escapeClosingTag(template.js, "script")}</script><script>window.__nowebRenderTemplate(document.getElementById("root"), {});</script></body></html>`;
|
|
502
517
|
}
|
|
503
518
|
|
|
504
519
|
function requireSlug(value) {
|
|
@@ -617,6 +632,7 @@ try {
|
|
|
617
632
|
else if (command === "list") await listTemplates();
|
|
618
633
|
else if (command === "download") await downloadTemplate(slug);
|
|
619
634
|
else if (command === "upload") await upload();
|
|
635
|
+
else if (command === "delete") await deleteTemplate(slug);
|
|
620
636
|
else if (command === "publish") await publish(slug);
|
|
621
637
|
else if (command === "help" || command === "--help" || command === "-h") usage();
|
|
622
638
|
else throw new Error(`Unknown command: ${command}`);
|
package/README.md
DELETED
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
# Noweb
|
|
2
|
-
|
|
3
|
-
Noweb ist eine Next.js-App fuer Website-Templates, Projekte, Auth, Checkout und
|
|
4
|
-
Deployment. Nutzer koennen Templates auswaehlen, Inhalte im Editor anpassen,
|
|
5
|
-
Bilder hochladen und Projekte mit eigener Domain veroeffentlichen.
|
|
6
|
-
|
|
7
|
-
## Schnellstart
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npm install
|
|
11
|
-
npm run dev
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
Die App laeuft lokal unter `http://localhost:3000`.
|
|
15
|
-
|
|
16
|
-
Vor einem Pull Request oder Deploy:
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
npm run ci
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
## Community-Templates
|
|
23
|
-
|
|
24
|
-
Die vollständige Anleitung für Erstellung, Assets, lokale Vorschau und
|
|
25
|
-
automatische Veröffentlichung steht in [`templates/README.md`](templates/README.md).
|
|
26
|
-
|
|
27
|
-
## Wichtige Befehle
|
|
28
|
-
|
|
29
|
-
- `npm run dev` startet Next lokal mit Webpack.
|
|
30
|
-
- `npm run build` baut die Produktionsversion.
|
|
31
|
-
- `npm run start` startet den gebauten Next-Server.
|
|
32
|
-
- `npm run lint` prueft den Code mit ESLint und erlaubt keine Warnings.
|
|
33
|
-
- `npm run typecheck` prueft TypeScript ohne Emit.
|
|
34
|
-
- `npm run test` fuehrt Unit-Tests aus.
|
|
35
|
-
- `npm run test:e2e` fuehrt den Playwright-Smoke gegen `npm run start` aus.
|
|
36
|
-
- `npx nowebcli create` erstellt ein Community-Template; danach laufen die Befehle lokal ueber `npx noweb`.
|
|
37
|
-
- `npm run ci` fuehrt Audit, Typecheck, Lint, Tests, Build und E2E aus.
|
|
38
|
-
|
|
39
|
-
## Struktur
|
|
40
|
-
|
|
41
|
-
- `app/` enthaelt Seiten, Routen und Server Actions.
|
|
42
|
-
- `app/page.tsx`, `app/HomeSections.tsx`, `app/Footer.tsx` und `app/menu.tsx`
|
|
43
|
-
bilden die Startseite und Navigation.
|
|
44
|
-
- `app/docs/` enthaelt die oeffentliche Doku im Produkt.
|
|
45
|
-
- `lib/auth/` konfiguriert Better Auth.
|
|
46
|
-
- `lib/db.ts` stellt die Postgres-Verbindung bereit.
|
|
47
|
-
- `lib/object-storage.ts` kapselt RustFS/R2-kompatiblen Object Storage.
|
|
48
|
-
- `lib/env.ts` validiert serverseitige Runtime-Konfiguration.
|
|
49
|
-
- `lib/domain.ts` normalisiert und validiert Domains.
|
|
50
|
-
- `lib/templates/` enthaelt Template-Typen, Runtime-Helfer und lokale Templates.
|
|
51
|
-
- `templates/index.ts` ist generiert und soll nicht per Hand editiert werden.
|
|
52
|
-
- `db.txt` dokumentiert das aktuelle Datenbankschema.
|
|
53
|
-
- `scripts/deploy-worker.mjs` veroeffentlicht Projekte als statische Sites.
|
|
54
|
-
- `.github/workflows/deploy.yaml` deployed `main` per SSH auf den Server.
|
|
55
|
-
|
|
56
|
-
## Umgebung
|
|
57
|
-
|
|
58
|
-
Mindestens benoetigt:
|
|
59
|
-
|
|
60
|
-
```env
|
|
61
|
-
DATABASE_URL=postgresql://...
|
|
62
|
-
BETTER_AUTH_SECRET=...
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
Je nach Feature zusaetzlich:
|
|
66
|
-
|
|
67
|
-
```env
|
|
68
|
-
RESEND_API_KEY=...
|
|
69
|
-
EMAIL_FROM=Noweb <mail@example.com>
|
|
70
|
-
|
|
71
|
-
GOOGLE_CLIENT_ID=...
|
|
72
|
-
GOOGLE_CLIENT_SECRET=...
|
|
73
|
-
GITHUB_CLIENT_ID=...
|
|
74
|
-
GITHUB_CLIENT_SECRET=...
|
|
75
|
-
|
|
76
|
-
POLAR_ACCESS_TOKEN=...
|
|
77
|
-
POLAR_PRODUCT_ID=...
|
|
78
|
-
POLAR_SERVER=sandbox
|
|
79
|
-
|
|
80
|
-
RUSTFS_ACCESS_KEY=...
|
|
81
|
-
RUSTFS_SECRET_KEY=...
|
|
82
|
-
RUSTFS_BUCKET_NAME=...
|
|
83
|
-
RUSTFS_ENDPOINT=...
|
|
84
|
-
RUSTFS_PUBLIC_URL=...
|
|
85
|
-
|
|
86
|
-
SENTRY_DSN=...
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
Object Storage kann alternativ ueber die `R2_*` Variablen konfiguriert werden.
|
|
90
|
-
Details stehen in [docs/object-storage.md](docs/object-storage.md).
|
|
91
|
-
|
|
92
|
-
## Wie die Hauptteile zusammenhaengen
|
|
93
|
-
|
|
94
|
-
Auth laeuft ueber Better Auth in `lib/auth/server.ts`. Die Daten liegen in
|
|
95
|
-
Postgres, E-Mails werden ueber Resend verschickt, OAuth ist optional ueber
|
|
96
|
-
Google und GitHub aktiv.
|
|
97
|
-
|
|
98
|
-
Projekte und Templates werden in Postgres gespeichert. Das aktuelle Schema
|
|
99
|
-
steht in `db.txt`; Schema-Aenderungen werden direkt in Postgres ausgefuehrt und
|
|
100
|
-
danach dort dokumentiert.
|
|
101
|
-
|
|
102
|
-
Checkout laeuft ueber Polar. Der kanonische Einstieg ist
|
|
103
|
-
`app/checkout/route.ts`, nicht direkt der Better-Auth-Checkout. Details stehen
|
|
104
|
-
in [docs/checkout.md](docs/checkout.md).
|
|
105
|
-
|
|
106
|
-
Bild-Uploads gehen ueber `app/api/uploads/r2/route.ts` in einen S3-kompatiblen
|
|
107
|
-
Storage. Die oeffentliche URL kommt aus `RUSTFS_PUBLIC_URL`, `R2_PUBLIC_URL`
|
|
108
|
-
oder `PUBLIC_DEV_URL`.
|
|
109
|
-
|
|
110
|
-
Template-Previews laufen in einer sandboxed `iframe/srcDoc` Runtime. Oeffentlich
|
|
111
|
-
sichtbar werden Community-Templates nach erfolgreicher automatischer Validierung.
|
|
112
|
-
|
|
113
|
-
Der Deploy Worker nimmt unveroeffentlichte Projekte aus der Datenbank, liest die
|
|
114
|
-
kompilierten Template-Dateien, validiert Domains und schreibt statische Sites
|
|
115
|
-
nach `SITES_DIR` Standard: `/etc/caddy/sites`.
|
|
116
|
-
|
|
117
|
-
## Deployment
|
|
118
|
-
|
|
119
|
-
Bei jedem Push auf `main` fuehrt GitHub Actions `.github/workflows/deploy.yaml`
|
|
120
|
-
aus:
|
|
121
|
-
|
|
122
|
-
1. Per SSH auf den Server verbinden und `origin/main` auschecken.
|
|
123
|
-
2. `npm ci` ausfuehren.
|
|
124
|
-
3. Repository-Templates in die Datenbank und den Preview-Storage publizieren.
|
|
125
|
-
4. `npm run build` ausfuehren.
|
|
126
|
-
5. Den `noweb`-Service neu starten.
|
|
127
|
-
|
|
128
|
-
Die GitHub-Secrets muessen gesetzt sein:
|
|
129
|
-
|
|
130
|
-
- `SERVER_HOST`
|
|
131
|
-
- `SERVER_USER`
|
|
132
|
-
- `SSH_PRIVATE_KEY`
|
|
133
|
-
|
|
134
|
-
Auf dem Server muessen ausserdem Node, npm, die Runtime-Env-Variablen und der
|
|
135
|
-
`noweb` systemd-Service vorhanden sein.
|
|
136
|
-
|
|
137
|
-
## Aenderungs-Checkliste
|
|
138
|
-
|
|
139
|
-
Wenn du etwas aenderst, pruefe die abhaengigen Stellen mit:
|
|
140
|
-
|
|
141
|
-
- Startseite, Preise oder Marketingtext:
|
|
142
|
-
`app/page.tsx`, `app/HomeSections.tsx`, `app/Footer.tsx`, `app/menu.tsx`,
|
|
143
|
-
`docs/checkout.md` bei Preis-/Checkout-Aenderungen.
|
|
144
|
-
- Polar-Produkte oder Checkout:
|
|
145
|
-
`app/checkout/route.ts`, `lib/auth/server.ts`, `lib/polar.ts`,
|
|
146
|
-
`docs/checkout.md`, Env-Variablen auf Server/GitHub.
|
|
147
|
-
- Auth, Login, Verifizierung oder OAuth:
|
|
148
|
-
`lib/auth/server.ts`, `app/auth/**`, Datenbanktabellen und Resend/OAuth-Env.
|
|
149
|
-
- Datenbank-Schema oder Query-Format:
|
|
150
|
-
`lib/db.ts`, Server Actions in `app/**/actions.ts`, `scripts/deploy-worker.mjs`,
|
|
151
|
-
`db.txt`.
|
|
152
|
-
- Template-Felder oder Customizations:
|
|
153
|
-
Template-Definition, Editor-Komponenten in `lib/editor/` und
|
|
154
|
-
`app/my-templates/[id]/`, Deploy Worker, `templates/index.ts` nur ueber den
|
|
155
|
-
Sync-Prozess aktualisieren.
|
|
156
|
-
- Template-Doku:
|
|
157
|
-
passende Seite in `app/docs/` aktualisieren und den Eintrag in
|
|
158
|
-
`app/docs/guides.ts` pruefen.
|
|
159
|
-
- Bild-Uploads oder Storage:
|
|
160
|
-
`lib/object-storage.ts`, `app/api/uploads/r2/route.ts`,
|
|
161
|
-
`docs/object-storage.md`, Storage-Env auf dem Server.
|
|
162
|
-
- Projekt-Deployment oder Domains:
|
|
163
|
-
`scripts/deploy-worker.mjs`, Caddy/systemd-Konfiguration, `docs/deploy-worker.md`.
|
|
164
|
-
- GitHub Deploy:
|
|
165
|
-
`.github/workflows/deploy.yaml`, GitHub-Secrets, Serverpfad `~/noweb`,
|
|
166
|
-
systemd-Service `noweb`.
|
|
167
|
-
- Neue oder geaenderte Produktfunktion:
|
|
168
|
-
vorhandene Doku aktualisieren oder eine kleine neue Seite in `docs/` oder
|
|
169
|
-
`app/docs/` anlegen.
|
|
170
|
-
|
|
171
|
-
## Launch-Blocker
|
|
172
|
-
|
|
173
|
-
- Echte Betreiberangaben in `app/imprint/page.tsx`, `app/privacy/page.tsx` und
|
|
174
|
-
`app/terms/page.tsx` eintragen.
|
|
175
|
-
- Lokale und Server-Env pruefen. `POLAR_SERVER` darf nur `sandbox` oder
|
|
176
|
-
`production` sein.
|
|
177
|
-
- Monitoring setzen: `SENTRY_DSN` oder dokumentierte Alternative.
|
|
178
|
-
- `npm run ci` muss vor Production gruen sein.
|
|
179
|
-
|
|
180
|
-
## Wichtige Hinweise
|
|
181
|
-
|
|
182
|
-
- Dieses Projekt nutzt Next.js 16. Vor groesseren Next-Aenderungen die lokalen
|
|
183
|
-
Docs in `node_modules/next/dist/docs/` lesen.
|
|
184
|
-
- `templates/index.ts` ist generiert. Nicht manuell pflegen.
|
|
185
|
-
- JSON-Spalten muessen beim Schreiben mit `JSON.stringify(...)` gespeichert
|
|
186
|
-
werden.
|
|
187
|
-
- Secrets gehoeren nicht ins Repository.
|
|
188
|
-
- Vor einem Deploy `npm run ci` ausfuehren.
|