create-mikstack 0.1.36 → 0.1.38
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 → index.mjs} +7 -1
- package/package.json +4 -4
- package/templates/adapters/node/Dockerfile +10 -4
- package/templates/adapters/node/docker-compose.prod.yml +1 -1
- package/templates/base/src/app.d.ts +3 -0
- package/templates/base/src/hooks.server.ts +4 -0
- package/templates/base/src/lib/server/auth.ts +1 -1
- package/templates/base/src/lib/server/db/index.ts +1 -2
- package/templates/base/src/lib/server/db/schema.ts +6 -6
- package/templates/base/src/lib/server/emails/send.ts +13 -9
- package/templates/base/src/lib/server/env.ts +32 -0
- package/templates/base/src/lib/zero/mutators.ts +2 -2
- package/templates/base/src/routes/api/health/+server.ts +5 -0
- package/templates/github-actions-bun/.github/workflows/ci.yml +3 -0
- package/templates/github-actions-npm/.github/workflows/ci.yml +3 -0
- package/templates/github-actions-pnpm/.github/workflows/ci.yml +3 -0
- package/templates/i18n/src/routes/+layout.server.ts +5 -0
- package/templates/ui/src/routes/+layout.svelte +7 -2
|
@@ -180,6 +180,10 @@ function scaffold(config, onStatus) {
|
|
|
180
180
|
fs.rmSync(path.join(target, "agents.md"), { force: true });
|
|
181
181
|
fs.rmSync(path.join(target, ".npmrc"), { force: true });
|
|
182
182
|
fs.rmSync(path.join(target, "src", "lib", "index.ts"), { force: true });
|
|
183
|
+
const pkgPath = path.join(target, "package.json");
|
|
184
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
185
|
+
delete pkg.devDependencies?.["@sveltejs/adapter-auto"];
|
|
186
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
183
187
|
onStatus?.("Applying templates...");
|
|
184
188
|
const overlay = (dir) => {
|
|
185
189
|
copyDir(dir, target);
|
|
@@ -377,12 +381,14 @@ async function cli() {
|
|
|
377
381
|
]
|
|
378
382
|
});
|
|
379
383
|
const cols = process.stdout.columns || 80;
|
|
384
|
+
const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
380
385
|
const handleData = (data) => {
|
|
381
386
|
if (!onLine) return;
|
|
382
387
|
const line = data.toString().trim().split("\n").pop();
|
|
383
388
|
if (!line) return;
|
|
389
|
+
const clean = stripAnsi(line);
|
|
384
390
|
const maxLen = cols - 5;
|
|
385
|
-
onLine(
|
|
391
|
+
onLine(clean.length > maxLen ? clean.slice(0, maxLen - 1) + "…" : clean);
|
|
386
392
|
};
|
|
387
393
|
child.stdout.on("data", handleData);
|
|
388
394
|
child.stderr.on("data", handleData);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-mikstack",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.38",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"directory": "packages/create-mikstack"
|
|
9
9
|
},
|
|
10
10
|
"bin": {
|
|
11
|
-
"create-mikstack": "./dist/index.
|
|
11
|
+
"create-mikstack": "./dist/index.mjs"
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
14
|
"dist",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"test": "bun test"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@clack/prompts": "^0.
|
|
28
|
+
"@clack/prompts": "^1.0.0",
|
|
29
29
|
"picocolors": "^1.1.1",
|
|
30
30
|
"sv": "0.12.1"
|
|
31
31
|
},
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"oxfmt": "^0.28.0",
|
|
38
38
|
"oxlint": "^1.43.0",
|
|
39
39
|
"oxlint-tsgolint": "^0.11.4",
|
|
40
|
-
"tsdown": "^0.
|
|
40
|
+
"tsdown": "^0.20.0",
|
|
41
41
|
"typescript": "^5.9.3"
|
|
42
42
|
}
|
|
43
43
|
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
FROM node:22-slim AS base
|
|
2
2
|
WORKDIR /app
|
|
3
|
+
# {{#if:pmIsPnpm}}
|
|
4
|
+
RUN corepack enable
|
|
5
|
+
# {{/if:pmIsPnpm}}
|
|
6
|
+
# {{#if:pmIsBun}}
|
|
7
|
+
RUN npm i -g bun
|
|
8
|
+
# {{/if:pmIsBun}}
|
|
3
9
|
|
|
4
10
|
FROM base AS deps
|
|
5
11
|
COPY package.json {{lockfile}} ./
|
|
@@ -7,10 +13,10 @@ COPY package.json {{lockfile}} ./
|
|
|
7
13
|
RUN npm ci
|
|
8
14
|
# {{/if:pmIsNpm}}
|
|
9
15
|
# {{#if:pmIsPnpm}}
|
|
10
|
-
RUN
|
|
16
|
+
RUN pnpm install --frozen-lockfile
|
|
11
17
|
# {{/if:pmIsPnpm}}
|
|
12
18
|
# {{#if:pmIsBun}}
|
|
13
|
-
RUN
|
|
19
|
+
RUN bun install --frozen-lockfile
|
|
14
20
|
# {{/if:pmIsBun}}
|
|
15
21
|
|
|
16
22
|
FROM base AS build
|
|
@@ -22,11 +28,11 @@ RUN npm run build
|
|
|
22
28
|
RUN npm prune --omit=dev
|
|
23
29
|
# {{/if:pmIsNpm}}
|
|
24
30
|
# {{#if:pmIsPnpm}}
|
|
25
|
-
RUN
|
|
31
|
+
RUN pnpm run build
|
|
26
32
|
RUN pnpm prune --prod
|
|
27
33
|
# {{/if:pmIsPnpm}}
|
|
28
34
|
# {{#if:pmIsBun}}
|
|
29
|
-
RUN
|
|
35
|
+
RUN bun run build
|
|
30
36
|
RUN bun install --production
|
|
31
37
|
# {{/if:pmIsBun}}
|
|
32
38
|
|
|
@@ -4,6 +4,10 @@ import { svelteKitHandler } from "better-auth/svelte-kit";
|
|
|
4
4
|
import { building } from "$app/environment";
|
|
5
5
|
|
|
6
6
|
export const handle: Handle = async ({ event, resolve }) => {
|
|
7
|
+
// {{#if:i18n}}
|
|
8
|
+
event.locals.locale = event.cookies.get("locale") ?? "en";
|
|
9
|
+
// {{/if:i18n}}
|
|
10
|
+
|
|
7
11
|
const session = await auth.api.getSession({
|
|
8
12
|
headers: event.request.headers,
|
|
9
13
|
});
|
|
@@ -4,7 +4,7 @@ import { magicLink } from "better-auth/plugins";
|
|
|
4
4
|
import { sveltekitCookies } from "better-auth/svelte-kit";
|
|
5
5
|
import { getRequestEvent } from "$app/server";
|
|
6
6
|
import { building } from "$app/environment";
|
|
7
|
-
import { env } from "$
|
|
7
|
+
import { env } from "$lib/server/env";
|
|
8
8
|
import { db } from "./db";
|
|
9
9
|
import * as schema from "./db/schema";
|
|
10
10
|
import { notif } from "./notifications";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { drizzle } from "drizzle-orm/postgres-js";
|
|
2
2
|
import postgres from "postgres";
|
|
3
3
|
import * as schema from "./schema";
|
|
4
|
-
import { env } from "$
|
|
4
|
+
import { env } from "$lib/server/env";
|
|
5
5
|
|
|
6
6
|
export type DrizzleDB = ReturnType<typeof drizzle<typeof schema>>;
|
|
7
7
|
|
|
@@ -10,7 +10,6 @@ let _db: DrizzleDB | undefined;
|
|
|
10
10
|
export const db = new Proxy({} as DrizzleDB, {
|
|
11
11
|
get(_, prop) {
|
|
12
12
|
if (!_db) {
|
|
13
|
-
if (!env.DATABASE_URL) throw new Error("DATABASE_URL is not set");
|
|
14
13
|
const client = postgres(env.DATABASE_URL);
|
|
15
14
|
_db = drizzle(client, { schema, casing: "snake_case" });
|
|
16
15
|
}
|
|
@@ -19,7 +19,7 @@ export const session = pgTable("session", {
|
|
|
19
19
|
userAgent: text(),
|
|
20
20
|
userId: text()
|
|
21
21
|
.notNull()
|
|
22
|
-
.references(() => user.id),
|
|
22
|
+
.references(() => user.id, { onDelete: "cascade" }),
|
|
23
23
|
createdAt: timestamp().notNull().defaultNow(),
|
|
24
24
|
updatedAt: timestamp().notNull().defaultNow(),
|
|
25
25
|
});
|
|
@@ -30,7 +30,7 @@ export const account = pgTable("account", {
|
|
|
30
30
|
providerId: text().notNull(),
|
|
31
31
|
userId: text()
|
|
32
32
|
.notNull()
|
|
33
|
-
.references(() => user.id),
|
|
33
|
+
.references(() => user.id, { onDelete: "cascade" }),
|
|
34
34
|
accessToken: text(),
|
|
35
35
|
refreshToken: text(),
|
|
36
36
|
idToken: text(),
|
|
@@ -56,7 +56,7 @@ export const notificationDelivery = pgTable("notification_delivery", {
|
|
|
56
56
|
id: text()
|
|
57
57
|
.primaryKey()
|
|
58
58
|
.$defaultFn(() => crypto.randomUUID()),
|
|
59
|
-
userId: text().references(() => user.id),
|
|
59
|
+
userId: text().references(() => user.id, { onDelete: "set null" }),
|
|
60
60
|
type: text().notNull(),
|
|
61
61
|
channel: text().notNull(),
|
|
62
62
|
status: text({ enum: ["pending", "sent", "delivered", "failed"] })
|
|
@@ -78,7 +78,7 @@ export const inAppNotification = pgTable("in_app_notification", {
|
|
|
78
78
|
.$defaultFn(() => crypto.randomUUID()),
|
|
79
79
|
userId: text()
|
|
80
80
|
.notNull()
|
|
81
|
-
.references(() => user.id),
|
|
81
|
+
.references(() => user.id, { onDelete: "cascade" }),
|
|
82
82
|
type: text().notNull(),
|
|
83
83
|
title: text().notNull(),
|
|
84
84
|
body: text(),
|
|
@@ -94,7 +94,7 @@ export const notificationPreference = pgTable("notification_preference", {
|
|
|
94
94
|
.$defaultFn(() => crypto.randomUUID()),
|
|
95
95
|
userId: text()
|
|
96
96
|
.notNull()
|
|
97
|
-
.references(() => user.id),
|
|
97
|
+
.references(() => user.id, { onDelete: "cascade" }),
|
|
98
98
|
notificationType: text().notNull(),
|
|
99
99
|
channel: text().notNull(),
|
|
100
100
|
enabled: boolean().notNull(),
|
|
@@ -109,7 +109,7 @@ export const note = pgTable("note", {
|
|
|
109
109
|
content: text().notNull().default(""),
|
|
110
110
|
userId: text()
|
|
111
111
|
.notNull()
|
|
112
|
-
.references(() => user.id),
|
|
112
|
+
.references(() => user.id, { onDelete: "cascade" }),
|
|
113
113
|
createdAt: timestamp().notNull(),
|
|
114
114
|
updatedAt: timestamp().notNull(),
|
|
115
115
|
});
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { createTransport } from "nodemailer";
|
|
1
|
+
import { createTransport, type Transporter } from "nodemailer";
|
|
2
2
|
import { desc, eq } from "drizzle-orm";
|
|
3
3
|
import { dev } from "$app/environment";
|
|
4
|
-
import { env } from "$
|
|
4
|
+
import { env } from "$lib/server/env";
|
|
5
5
|
import { db } from "../db";
|
|
6
6
|
import { notificationDelivery } from "../db/schema";
|
|
7
7
|
|
|
8
|
+
let cachedTransport: Transporter | undefined;
|
|
9
|
+
|
|
8
10
|
interface Email {
|
|
9
11
|
subject: string;
|
|
10
12
|
html: string;
|
|
@@ -58,14 +60,16 @@ export async function sendEmail(to: string, email: Email): Promise<void> {
|
|
|
58
60
|
throw new Error("SMTP not configured. Set SMTP_HOST, SMTP_USER, and SMTP_PASS in .env");
|
|
59
61
|
}
|
|
60
62
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
if (!cachedTransport) {
|
|
64
|
+
cachedTransport = createTransport({
|
|
65
|
+
host,
|
|
66
|
+
port,
|
|
67
|
+
secure: port === 465,
|
|
68
|
+
auth: { user, pass },
|
|
69
|
+
});
|
|
70
|
+
}
|
|
67
71
|
|
|
68
|
-
await
|
|
72
|
+
await cachedTransport.sendMail({
|
|
69
73
|
from,
|
|
70
74
|
to,
|
|
71
75
|
subject: email.subject,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { env as privateEnv } from "$env/dynamic/private";
|
|
2
|
+
import * as v from "valibot";
|
|
3
|
+
|
|
4
|
+
const envSchema = v.object({
|
|
5
|
+
DATABASE_URL: v.pipe(v.string(), v.minLength(1, "DATABASE_URL is required")),
|
|
6
|
+
BETTER_AUTH_SECRET: v.pipe(v.string(), v.minLength(1, "BETTER_AUTH_SECRET is required")),
|
|
7
|
+
BETTER_AUTH_URL: v.optional(v.string(), "http://localhost:5173"),
|
|
8
|
+
SMTP_HOST: v.optional(v.string()),
|
|
9
|
+
SMTP_PORT: v.optional(v.string()),
|
|
10
|
+
SMTP_USER: v.optional(v.string()),
|
|
11
|
+
SMTP_PASS: v.optional(v.string()),
|
|
12
|
+
SMTP_FROM: v.optional(v.string()),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const parsed = v.safeParse(envSchema, {
|
|
16
|
+
DATABASE_URL: privateEnv.DATABASE_URL,
|
|
17
|
+
BETTER_AUTH_SECRET: privateEnv.BETTER_AUTH_SECRET,
|
|
18
|
+
BETTER_AUTH_URL: privateEnv.BETTER_AUTH_URL || undefined,
|
|
19
|
+
SMTP_HOST: privateEnv.SMTP_HOST || undefined,
|
|
20
|
+
SMTP_PORT: privateEnv.SMTP_PORT || undefined,
|
|
21
|
+
SMTP_USER: privateEnv.SMTP_USER || undefined,
|
|
22
|
+
SMTP_PASS: privateEnv.SMTP_PASS || undefined,
|
|
23
|
+
SMTP_FROM: privateEnv.SMTP_FROM || undefined,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if (!parsed.success) {
|
|
27
|
+
console.error("Invalid environment variables:");
|
|
28
|
+
console.error(JSON.stringify(v.flatten(parsed.issues), null, 2));
|
|
29
|
+
throw new Error("Environment validation failed. Check the errors above.");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const env = parsed.output;
|
|
@@ -24,7 +24,7 @@ export const mutators = defineMutators({
|
|
|
24
24
|
const note = await tx.run(
|
|
25
25
|
zql.note.where("id", args.id).where("userId", ctx.userID).one(),
|
|
26
26
|
);
|
|
27
|
-
if (!note)
|
|
27
|
+
if (!note) throw new Error("Note not found");
|
|
28
28
|
await tx.mutate.note.update({
|
|
29
29
|
id: args.id,
|
|
30
30
|
title: args.title,
|
|
@@ -37,7 +37,7 @@ export const mutators = defineMutators({
|
|
|
37
37
|
const note = await tx.run(
|
|
38
38
|
zql.note.where("id", args.id).where("userId", ctx.userID).one(),
|
|
39
39
|
);
|
|
40
|
-
if (!note)
|
|
40
|
+
if (!note) throw new Error("Note not found");
|
|
41
41
|
await tx.mutate.note.delete({ id: args.id });
|
|
42
42
|
}),
|
|
43
43
|
},
|
|
@@ -3,10 +3,15 @@
|
|
|
3
3
|
import "../app.css";
|
|
4
4
|
// {{#if:i18n}}
|
|
5
5
|
import { initI18n } from "$lib/i18n";
|
|
6
|
-
initI18n();
|
|
7
|
-
// {{/if:i18n}}
|
|
8
6
|
|
|
7
|
+
let { children, data }: { children: Snippet; data: { locale: string } } = $props();
|
|
8
|
+
$effect(() => {
|
|
9
|
+
initI18n(data.locale);
|
|
10
|
+
});
|
|
11
|
+
// {{/if:i18n}}
|
|
12
|
+
// {{#if:!i18n}}
|
|
9
13
|
let { children }: { children: Snippet } = $props();
|
|
14
|
+
// {{/if:!i18n}}
|
|
10
15
|
</script>
|
|
11
16
|
|
|
12
17
|
{@render children()}
|