create-mikstack 0.1.30 → 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 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)) fs.copyFileSync(envExample, 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-mikstack",
3
- "version": "0.1.30",
3
+ "version": "0.1.32",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -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 via `tx.run()`:
35
+ Mutators verify ownership before update/delete by including `userId` in the query:
36
36
 
37
37
  ```typescript
38
- const entity = await tx.run(zql.note.where("id", args.id).one());
39
- if (!entity || entity.userId !== ctx.userID) return;
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 (@mikstack/form)
51
+ ### Forms
50
52
 
51
- Use `createForm` from `@mikstack/form` for client-side forms with Valibot validation.
52
- Ideal for Zero mutators where forms don't use SvelteKit form actions.
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">
@@ -2,6 +2,11 @@
2
2
 
3
3
  Built with [mikstack](https://github.com/mikaelsiidorow/mikstack).
4
4
 
5
+ ## Prerequisites
6
+
7
+ - [Docker](https://docs.docker.com/get-docker/) (for local Postgres)
8
+ - Node.js 22+
9
+
5
10
  ## Getting Started
6
11
 
7
12
  ```bash
@@ -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(zql.note.where("id", args.id).one());
25
- if (!note || note.userId !== ctx.userID) return;
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(zql.note.where("id", args.id).one());
36
- if (!note || note.userId !== ctx.userID) return;
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
  },
@@ -18,7 +18,7 @@ msgstr "Peruuta"
18
18
 
19
19
  #: src/routes/(public)/sign-in/+page.svelte
20
20
  msgid "Check your email for a magic link to sign in."
21
- msgstr "Tarkista sähköpostisi kirjautumislinkin varalta."
21
+ msgstr "Tarkista kirjautumislinkki sähköpostistasi."
22
22
 
23
23
  #: src/routes/(app)/+page.svelte
24
24
  #: src/routes/(app)/+page.svelte
@@ -43,7 +43,7 @@ msgstr "Muokkaa"
43
43
 
44
44
  #: src/routes/(public)/sign-in/+page.svelte
45
45
  msgid "Email"
46
- msgstr "Sähköposti"
46
+ msgstr "Sähköpostiosoite"
47
47
 
48
48
  #: src/lib/server/emails/magic-link.ts
49
49
  msgid "If you didn't try to sign in, you can safely ignore this email."
@@ -87,7 +87,7 @@ msgstr "Lähetetään kirjautumislinkkiä..."
87
87
 
88
88
  #: src/lib/server/emails/magic-link.ts
89
89
  msgid "Sign in"
90
- msgstr "Kirjaudu"
90
+ msgstr "Kirjaudu sisään"
91
91
 
92
92
  #: src/lib/server/emails/magic-link.ts
93
93
  #: src/lib/server/emails/magic-link.ts
@@ -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
+ });
@@ -1,7 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- describe("example", () => {
4
- it("should work", () => {
5
- expect(1 + 1).toBe(2);
6
- });
7
- });