create-mikstack 0.1.31 → 0.1.32
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 +7 -1
- package/package.json +1 -1
- package/templates/base/AGENTS.md +10 -6
- package/templates/base/README.md +5 -0
- package/templates/base/src/lib/zero/mutators.ts +10 -6
- package/templates/testing/src/lib/server/db/schema.test.ts +39 -0
- package/templates/testing/src/example.test.ts +0 -7
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import * as p from "@clack/prompts";
|
|
|
3
3
|
import { execSync, spawn } from "node:child_process";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { parseArgs } from "node:util";
|
|
6
|
+
import crypto from "node:crypto";
|
|
6
7
|
import fs from "node:fs";
|
|
7
8
|
|
|
8
9
|
//#region src/config.ts
|
|
@@ -178,6 +179,7 @@ function scaffold(config, onStatus) {
|
|
|
178
179
|
});
|
|
179
180
|
fs.rmSync(path.join(target, "agents.md"), { force: true });
|
|
180
181
|
fs.rmSync(path.join(target, ".npmrc"), { force: true });
|
|
182
|
+
fs.rmSync(path.join(target, "src", "lib", "index.ts"), { force: true });
|
|
181
183
|
onStatus?.("Applying templates...");
|
|
182
184
|
const overlay = (dir) => {
|
|
183
185
|
copyDir(dir, target);
|
|
@@ -199,7 +201,11 @@ function scaffold(config, onStatus) {
|
|
|
199
201
|
fs.symlinkSync("AGENTS.md", path.join(target, "CLAUDE.md"));
|
|
200
202
|
const envExample = path.join(target, ".env.example");
|
|
201
203
|
const envFile = path.join(target, ".env");
|
|
202
|
-
if (fs.existsSync(envExample) && !fs.existsSync(envFile))
|
|
204
|
+
if (fs.existsSync(envExample) && !fs.existsSync(envFile)) {
|
|
205
|
+
fs.copyFileSync(envExample, envFile);
|
|
206
|
+
const envContent = fs.readFileSync(envFile, "utf-8");
|
|
207
|
+
fs.writeFileSync(envFile, envContent.replace("BETTER_AUTH_SECRET=\"change-me-generate-a-real-secret\"", `BETTER_AUTH_SECRET="${crypto.randomBytes(32).toString("base64url")}"`));
|
|
208
|
+
}
|
|
203
209
|
}
|
|
204
210
|
function copyDir(src, dest) {
|
|
205
211
|
if (!fs.existsSync(src)) return;
|
package/package.json
CHANGED
package/templates/base/AGENTS.md
CHANGED
|
@@ -32,11 +32,13 @@ const data = $derived(query.data);
|
|
|
32
32
|
await z.mutate(mutators.myMutation(args));
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
Mutators verify ownership before update/delete
|
|
35
|
+
Mutators verify ownership before update/delete by including `userId` in the query:
|
|
36
36
|
|
|
37
37
|
```typescript
|
|
38
|
-
const entity = await tx.run(
|
|
39
|
-
|
|
38
|
+
const entity = await tx.run(
|
|
39
|
+
zql.note.where("id", args.id).where("userId", ctx.userID).one(),
|
|
40
|
+
);
|
|
41
|
+
if (!entity) return;
|
|
40
42
|
```
|
|
41
43
|
|
|
42
44
|
### Auth
|
|
@@ -46,10 +48,12 @@ Client: `src/lib/auth-client.ts` (magic link + client helpers)
|
|
|
46
48
|
Session is available in `locals.user` and `locals.session` via `src/hooks.server.ts`.
|
|
47
49
|
Routes under `(app)/` require authentication — unauthenticated requests redirect to `/sign-in`.
|
|
48
50
|
|
|
49
|
-
### Forms
|
|
51
|
+
### Forms
|
|
50
52
|
|
|
51
|
-
Use `createForm` from `@mikstack/form` for client-side forms with Valibot validation.
|
|
52
|
-
|
|
53
|
+
**Zero mutations:** Use `createForm` from `@mikstack/form` for client-side forms with Valibot validation.
|
|
54
|
+
|
|
55
|
+
**Server submissions:** Use SvelteKit remote functions (`*.remote.ts` files) with the `form()` helper and Valibot validation.
|
|
56
|
+
For docs, call the Svelte MCP tool: `list-sections`, then `get-documentation` for the "Remote functions" section.
|
|
53
57
|
|
|
54
58
|
```svelte
|
|
55
59
|
<script lang="ts">
|
package/templates/base/README.md
CHANGED
|
@@ -5,7 +5,7 @@ import { zql } from "./schema";
|
|
|
5
5
|
export const mutators = defineMutators({
|
|
6
6
|
note: {
|
|
7
7
|
create: defineMutator(
|
|
8
|
-
v.object({ id: v.string(), title: v.string(), content: v.string() }),
|
|
8
|
+
v.object({ id: v.string(), title: v.pipe(v.string(), v.minLength(1)), content: v.string() }),
|
|
9
9
|
async ({ tx, ctx, args }) => {
|
|
10
10
|
const now = Date.now();
|
|
11
11
|
await tx.mutate.note.insert({
|
|
@@ -19,10 +19,12 @@ export const mutators = defineMutators({
|
|
|
19
19
|
},
|
|
20
20
|
),
|
|
21
21
|
update: defineMutator(
|
|
22
|
-
v.object({ id: v.string(), title: v.string(), content: v.string() }),
|
|
22
|
+
v.object({ id: v.string(), title: v.pipe(v.string(), v.minLength(1)), content: v.string() }),
|
|
23
23
|
async ({ tx, ctx, args }) => {
|
|
24
|
-
const note = await tx.run(
|
|
25
|
-
|
|
24
|
+
const note = await tx.run(
|
|
25
|
+
zql.note.where("id", args.id).where("userId", ctx.userID).one(),
|
|
26
|
+
);
|
|
27
|
+
if (!note) return;
|
|
26
28
|
await tx.mutate.note.update({
|
|
27
29
|
id: args.id,
|
|
28
30
|
title: args.title,
|
|
@@ -32,8 +34,10 @@ export const mutators = defineMutators({
|
|
|
32
34
|
},
|
|
33
35
|
),
|
|
34
36
|
delete: defineMutator(v.object({ id: v.string() }), async ({ tx, ctx, args }) => {
|
|
35
|
-
const note = await tx.run(
|
|
36
|
-
|
|
37
|
+
const note = await tx.run(
|
|
38
|
+
zql.note.where("id", args.id).where("userId", ctx.userID).one(),
|
|
39
|
+
);
|
|
40
|
+
if (!note) return;
|
|
37
41
|
await tx.mutate.note.delete({ id: args.id });
|
|
38
42
|
}),
|
|
39
43
|
},
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { pushSchema } from "drizzle-kit/api";
|
|
2
|
+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
3
|
+
import { createTestDatabase, stopTestDatabase, type TestDatabase } from "./test-utils";
|
|
4
|
+
import * as schema from "./schema";
|
|
5
|
+
|
|
6
|
+
describe("database schema", () => {
|
|
7
|
+
let testDb: TestDatabase;
|
|
8
|
+
|
|
9
|
+
beforeAll(async () => {
|
|
10
|
+
testDb = await createTestDatabase();
|
|
11
|
+
}, 60_000);
|
|
12
|
+
|
|
13
|
+
afterAll(async () => {
|
|
14
|
+
if (testDb) await stopTestDatabase(testDb);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("pushes schema without data loss", async () => {
|
|
18
|
+
const result = await pushSchema(schema, testDb.db);
|
|
19
|
+
expect(result.warnings).toEqual([]);
|
|
20
|
+
expect(result.hasDataLoss).toBe(false);
|
|
21
|
+
await result.apply();
|
|
22
|
+
|
|
23
|
+
const tables = await testDb.client`
|
|
24
|
+
SELECT table_name FROM information_schema.tables
|
|
25
|
+
WHERE table_schema = 'public'
|
|
26
|
+
ORDER BY table_name
|
|
27
|
+
`;
|
|
28
|
+
const tableNames = tables.map((row) => row.table_name);
|
|
29
|
+
|
|
30
|
+
expect(tableNames).toContain("user");
|
|
31
|
+
expect(tableNames).toContain("session");
|
|
32
|
+
expect(tableNames).toContain("account");
|
|
33
|
+
expect(tableNames).toContain("verification");
|
|
34
|
+
expect(tableNames).toContain("note");
|
|
35
|
+
expect(tableNames).toContain("notification_delivery");
|
|
36
|
+
expect(tableNames).toContain("in_app_notification");
|
|
37
|
+
expect(tableNames).toContain("notification_preference");
|
|
38
|
+
});
|
|
39
|
+
});
|