create-sailor 1.10.0 → 2.0.0

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.
Files changed (78) hide show
  1. package/README.md +34 -135
  2. package/dist/index.js +119 -2002
  3. package/package.json +16 -17
  4. package/templates/infra/ops/README.md +2 -1
  5. package/templates/infra/ops/platform-expected.example.json +10 -1
  6. package/templates/backends/python/README.md +0 -16
  7. package/templates/backends/python/_shared/__init__.py +0 -5
  8. package/templates/backends/python/pyproject.toml +0 -13
  9. package/templates/deploy/railway.toml +0 -13
  10. package/templates/deploy/vercel.json +0 -37
  11. package/templates/deploy/wrangler.gateway.toml +0 -11
  12. package/templates/deploy/wrangler.toml +0 -8
  13. package/templates/docs/docusaurus/README.md +0 -24
  14. package/templates/docs/docusaurus/docs/getting-started/develop.md +0 -20
  15. package/templates/docs/docusaurus/docs/getting-started/install.md +0 -11
  16. package/templates/docs/docusaurus/docs/intro.md +0 -15
  17. package/templates/docs/docusaurus/docusaurus.config.ts +0 -66
  18. package/templates/docs/docusaurus/package.json +0 -44
  19. package/templates/docs/docusaurus/sidebars.ts +0 -14
  20. package/templates/docs/docusaurus/src/css/custom.css +0 -22
  21. package/templates/docs/docusaurus/tsconfig.json +0 -7
  22. package/templates/docs/fumadocs/README.md +0 -46
  23. package/templates/docs/fumadocs/app/(home)/page.tsx +0 -22
  24. package/templates/docs/fumadocs/app/api/search/route.ts +0 -4
  25. package/templates/docs/fumadocs/app/docs/[[...slug]]/page.tsx +0 -39
  26. package/templates/docs/fumadocs/app/docs/layout.tsx +0 -11
  27. package/templates/docs/fumadocs/app/global.css +0 -10
  28. package/templates/docs/fumadocs/app/layout.tsx +0 -18
  29. package/templates/docs/fumadocs/content/docs/api.mdx +0 -31
  30. package/templates/docs/fumadocs/content/docs/concepts.mdx +0 -19
  31. package/templates/docs/fumadocs/content/docs/getting-started.mdx +0 -56
  32. package/templates/docs/fumadocs/content/docs/index.mdx +0 -17
  33. package/templates/docs/fumadocs/content/docs/meta.json +0 -4
  34. package/templates/docs/fumadocs/lib/source.ts +0 -7
  35. package/templates/docs/fumadocs/next.config.mjs +0 -20
  36. package/templates/docs/fumadocs/package.json +0 -33
  37. package/templates/docs/fumadocs/source.config.ts +0 -17
  38. package/templates/docs/fumadocs/tsconfig.json +0 -25
  39. package/templates/docs/mintlify/README.md +0 -25
  40. package/templates/docs/mintlify/api-reference/introduction.mdx +0 -25
  41. package/templates/docs/mintlify/introduction.mdx +0 -21
  42. package/templates/docs/mintlify/mint.json +0 -37
  43. package/templates/docs/mintlify/package.json +0 -14
  44. package/templates/docs/mintlify/quickstart.mdx +0 -26
  45. package/templates/docs/nextra/README.md +0 -35
  46. package/templates/docs/nextra/app/[[...mdxPath]]/page.tsx +0 -23
  47. package/templates/docs/nextra/app/layout.tsx +0 -36
  48. package/templates/docs/nextra/content/_meta.ts +0 -4
  49. package/templates/docs/nextra/content/getting-started.mdx +0 -35
  50. package/templates/docs/nextra/content/index.mdx +0 -18
  51. package/templates/docs/nextra/mdx-components.tsx +0 -10
  52. package/templates/docs/nextra/next.config.mjs +0 -9
  53. package/templates/docs/nextra/package.json +0 -25
  54. package/templates/docs/nextra/tsconfig.json +0 -21
  55. package/templates/docs/vitepress/.vitepress/config.ts +0 -35
  56. package/templates/docs/vitepress/README.md +0 -23
  57. package/templates/docs/vitepress/guide/getting-started.md +0 -26
  58. package/templates/docs/vitepress/guide/introduction.md +0 -12
  59. package/templates/docs/vitepress/index.md +0 -23
  60. package/templates/docs/vitepress/package.json +0 -18
  61. package/templates/docs/vitepress/tsconfig.json +0 -15
  62. package/templates/orm/drizzle/README.md +0 -58
  63. package/templates/orm/drizzle/drizzle.config.ts +0 -16
  64. package/templates/orm/drizzle/migrations/.gitkeep +0 -0
  65. package/templates/orm/drizzle/package.json +0 -30
  66. package/templates/orm/drizzle/src/index.ts +0 -31
  67. package/templates/orm/drizzle/src/schema/auth.ts +0 -73
  68. package/templates/orm/drizzle/src/schema/billing.ts +0 -37
  69. package/templates/orm/drizzle/src/schema/index.ts +0 -3
  70. package/templates/orm/drizzle/src/schema/tenant.ts +0 -57
  71. package/templates/orm/drizzle/tsconfig.json +0 -16
  72. package/templates/workflows/README.md +0 -9
  73. package/templates/workflows/inngest/README.md +0 -3
  74. package/templates/workflows/inngest/welcome-email.ts +0 -30
  75. package/templates/workflows/n8n/README.md +0 -3
  76. package/templates/workflows/n8n/example-flow.json +0 -6
  77. package/templates/workflows/pusher/README.md +0 -3
  78. package/templates/workflows/pusher/events.ts +0 -6
@@ -1,23 +0,0 @@
1
- ---
2
- layout: home
3
-
4
- hero:
5
- name: "{PRODUCT_NAME}"
6
- text: "Documentation"
7
- tagline: Built with VitePress
8
- actions:
9
- - theme: brand
10
- text: Get started
11
- link: /guide/introduction
12
- - theme: alt
13
- text: View on GitHub
14
- link: https://github.com/your-org/your-repo
15
-
16
- features:
17
- - title: Fast
18
- details: Vite-powered dev server with instant HMR.
19
- - title: Markdown
20
- details: Write content in markdown with Vue component support.
21
- - title: SEO friendly
22
- details: SSG output is statically pre-rendered for search engines.
23
- ---
@@ -1,18 +0,0 @@
1
- {
2
- "name": "@nebutra/docs",
3
- "private": true,
4
- "version": "0.0.1",
5
- "type": "module",
6
- "scripts": {
7
- "dev": "vitepress dev --port 3001",
8
- "build": "vitepress build",
9
- "preview": "vitepress preview --port 3001",
10
- "typecheck": "tsc --noEmit"
11
- },
12
- "devDependencies": {
13
- "typescript": "^5.9.3",
14
- "vitepress": "^1.5.0",
15
- "vue": "^3.5.13"
16
- },
17
- "license": "MIT"
18
- }
@@ -1,15 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "ESNext",
5
- "moduleResolution": "Bundler",
6
- "strict": true,
7
- "skipLibCheck": true,
8
- "esModuleInterop": true,
9
- "resolveJsonModule": true,
10
- "noEmit": true,
11
- "types": ["vitepress/client"]
12
- },
13
- "include": [".vitepress/**/*.ts", "**/*.md"],
14
- "exclude": ["node_modules", ".vitepress/dist", ".vitepress/cache"]
15
- }
@@ -1,58 +0,0 @@
1
- # @nebutra/db-drizzle
2
-
3
- Opt-in Drizzle ORM access layer. Lives alongside `@nebutra/db` (Prisma).
4
-
5
- ## When to use which
6
-
7
- | Scenario | Use |
8
- |---|---|
9
- | New code, want SQL-shaped query builder | `@nebutra/db-drizzle` |
10
- | Existing auth / billing / audit / oauth flows | `@nebutra/db` (Prisma — they're built on it) |
11
- | Migrations + schema source of truth | `@nebutra/db` (Prisma owns schema) |
12
-
13
- Both connect to the same `DATABASE_URL`. Treat Drizzle's `src/schema/*.ts` as
14
- **read-mostly mirrors** of the Prisma schema until the dual-ORM era ends.
15
-
16
- ## Setup
17
-
18
- ```bash
19
- pnpm --filter @nebutra/db-drizzle db:generate # generate migrations from schema
20
- pnpm --filter @nebutra/db-drizzle db:push # push schema directly (dev only)
21
- pnpm --filter @nebutra/db-drizzle db:studio # open Drizzle Studio
22
- ```
23
-
24
- ## Usage
25
-
26
- ```ts
27
- import { db, schema } from "@nebutra/db-drizzle";
28
- import { eq } from "drizzle-orm";
29
-
30
- // Read
31
- const user = await db.query.users.findFirst({
32
- where: eq(schema.users.email, "alice@example.com"),
33
- });
34
-
35
- // Insert
36
- await db.insert(schema.users).values({
37
- id: crypto.randomUUID(),
38
- name: "Alice",
39
- email: "alice@example.com",
40
- });
41
- ```
42
-
43
- ## Schema coverage
44
-
45
- Currently mirrored (read-safe with the Prisma side):
46
-
47
- - `auth.user` / `auth.session` / `auth.account` / `auth.verification`
48
- - `auth.organization` / `auth.member` / `auth.invitation`
49
- - `public.subscriptions` / `public.usage_ledger`
50
-
51
- Add more in `src/schema/*.ts` as you migrate features off Prisma. Keep
52
- column names + types in lock-step with `packages/platform/db/prisma/schema.prisma`.
53
-
54
- ## Migration path (dual-ORM → Drizzle primary)
55
-
56
- Not on the roadmap until enough new code lands on Drizzle to make the swap
57
- worth the risk. Until then, write against whichever client matches your use
58
- case and don't double-write the same table from both.
@@ -1,16 +0,0 @@
1
- import { defineConfig } from "drizzle-kit";
2
-
3
- if (!process.env.DATABASE_URL) {
4
- throw new Error("DATABASE_URL is not set — required for drizzle-kit");
5
- }
6
-
7
- export default defineConfig({
8
- schema: "./src/schema/*.ts",
9
- out: "./migrations",
10
- dialect: "postgresql",
11
- dbCredentials: {
12
- url: process.env.DATABASE_URL,
13
- },
14
- verbose: true,
15
- strict: true,
16
- });
File without changes
@@ -1,30 +0,0 @@
1
- {
2
- "name": "@nebutra/db-drizzle",
3
- "version": "0.1.0",
4
- "private": true,
5
- "type": "module",
6
- "main": "./src/index.ts",
7
- "types": "./src/index.ts",
8
- "exports": {
9
- ".": "./src/index.ts",
10
- "./schema": "./src/schema/index.ts",
11
- "./schema/*": "./src/schema/*.ts"
12
- },
13
- "scripts": {
14
- "db:generate": "drizzle-kit generate",
15
- "db:migrate": "drizzle-kit migrate",
16
- "db:push": "drizzle-kit push",
17
- "db:studio": "drizzle-kit studio",
18
- "typecheck": "tsc --noEmit"
19
- },
20
- "dependencies": {
21
- "drizzle-orm": "^0.36.4",
22
- "postgres": "^3.4.5"
23
- },
24
- "devDependencies": {
25
- "drizzle-kit": "^0.30.1",
26
- "typescript": "^5.7.2",
27
- "@types/node": "^22.10.5"
28
- },
29
- "license": "MIT"
30
- }
@@ -1,31 +0,0 @@
1
- import { drizzle } from "drizzle-orm/postgres-js";
2
- import postgres from "postgres";
3
- import * as schema from "./schema";
4
-
5
- /**
6
- * @nebutra/db-drizzle — opt-in Drizzle ORM access layer.
7
- *
8
- * Lives alongside the primary Prisma client at @nebutra/db. Both connect to
9
- * the same DATABASE_URL — Drizzle is for new code that wants the
10
- * lighter-weight, SQL-shaped query builder; Prisma stays as the entry point
11
- * for the auth / billing / audit packages that were built on top of it.
12
- *
13
- * Migration policy: Prisma owns the schema until the dual-ORM era ends.
14
- * `drizzle-kit generate` here is for *adding* Drizzle-only tables; existing
15
- * Prisma tables get mirrored into src/schema/*.ts for read access only.
16
- */
17
-
18
- const connectionString = process.env.DATABASE_URL;
19
- if (!connectionString) {
20
- throw new Error("[@nebutra/db-drizzle] DATABASE_URL is not set. Drizzle cannot connect.");
21
- }
22
-
23
- const client = postgres(connectionString, {
24
- max: Number(process.env.DATABASE_POOL_MAX ?? 10),
25
- idle_timeout: 30,
26
- prepare: false,
27
- });
28
-
29
- export const db = drizzle(client, { schema, logger: false });
30
- export { schema };
31
- export type Database = typeof db;
@@ -1,73 +0,0 @@
1
- import { boolean, pgSchema, text, timestamp, uniqueIndex } from "drizzle-orm/pg-core";
2
-
3
- /**
4
- * `auth` schema — mirrors the Better Auth tables defined in
5
- * @nebutra/db's Prisma schema (auth.user / auth.session / auth.account /
6
- * auth.verification). Drizzle reads from the same physical tables.
7
- *
8
- * Adding NEW columns here without a corresponding Prisma change is unsafe —
9
- * Prisma's generator + RLS policies must stay in sync. Use this file as a
10
- * read-mostly mirror until Drizzle becomes primary.
11
- */
12
- export const authSchema = pgSchema("auth");
13
-
14
- export const users = authSchema.table(
15
- "user",
16
- {
17
- id: text("id").primaryKey(),
18
- name: text("name").notNull(),
19
- email: text("email").notNull(),
20
- emailVerified: boolean("email_verified").notNull().default(false),
21
- image: text("image"),
22
- createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
23
- updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
24
- },
25
- (t) => ({
26
- emailIdx: uniqueIndex("user_email_unique").on(t.email),
27
- }),
28
- );
29
-
30
- export const sessions = authSchema.table("session", {
31
- id: text("id").primaryKey(),
32
- userId: text("user_id")
33
- .notNull()
34
- .references(() => users.id, { onDelete: "cascade" }),
35
- token: text("token").notNull(),
36
- expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
37
- ipAddress: text("ip_address"),
38
- userAgent: text("user_agent"),
39
- createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
40
- updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
41
- });
42
-
43
- export const accounts = authSchema.table("account", {
44
- id: text("id").primaryKey(),
45
- userId: text("user_id")
46
- .notNull()
47
- .references(() => users.id, { onDelete: "cascade" }),
48
- providerId: text("provider_id").notNull(),
49
- accountId: text("account_id").notNull(),
50
- accessToken: text("access_token"),
51
- refreshToken: text("refresh_token"),
52
- idToken: text("id_token"),
53
- accessTokenExpiresAt: timestamp("access_token_expires_at", { withTimezone: true }),
54
- refreshTokenExpiresAt: timestamp("refresh_token_expires_at", { withTimezone: true }),
55
- scope: text("scope"),
56
- password: text("password"),
57
- createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
58
- updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
59
- });
60
-
61
- export const verifications = authSchema.table("verification", {
62
- id: text("id").primaryKey(),
63
- identifier: text("identifier").notNull(),
64
- value: text("value").notNull(),
65
- expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
66
- createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
67
- updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
68
- });
69
-
70
- export type User = typeof users.$inferSelect;
71
- export type NewUser = typeof users.$inferInsert;
72
- export type Session = typeof sessions.$inferSelect;
73
- export type Account = typeof accounts.$inferSelect;
@@ -1,37 +0,0 @@
1
- import { integer, jsonb, numeric, pgTable, text, timestamp } from "drizzle-orm/pg-core";
2
- import { organizations } from "./tenant";
3
-
4
- /**
5
- * Subscription + UsageLedger mirrors for the commerce/billing flow. The
6
- * Prisma side owns writes; Drizzle is read-mostly for now.
7
- */
8
-
9
- export const subscriptions = pgTable("subscriptions", {
10
- id: text("id").primaryKey(),
11
- organizationId: text("organization_id")
12
- .notNull()
13
- .references(() => organizations.id, { onDelete: "cascade" }),
14
- plan: text("plan").notNull(),
15
- status: text("status").notNull(),
16
- provider: text("provider").notNull(),
17
- externalId: text("external_id"),
18
- seats: integer("seats").notNull().default(1),
19
- currentPeriodEnd: timestamp("current_period_end", { withTimezone: true }),
20
- metadata: jsonb("metadata"),
21
- createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
22
- updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
23
- });
24
-
25
- export const usageLedger = pgTable("usage_ledger", {
26
- id: text("id").primaryKey(),
27
- organizationId: text("organization_id")
28
- .notNull()
29
- .references(() => organizations.id, { onDelete: "cascade" }),
30
- meter: text("meter").notNull(),
31
- quantity: numeric("quantity", { precision: 20, scale: 6 }).notNull(),
32
- metadata: jsonb("metadata"),
33
- recordedAt: timestamp("recorded_at", { withTimezone: true }).notNull().defaultNow(),
34
- });
35
-
36
- export type Subscription = typeof subscriptions.$inferSelect;
37
- export type UsageLedgerEntry = typeof usageLedger.$inferSelect;
@@ -1,3 +0,0 @@
1
- export * from "./auth";
2
- export * from "./billing";
3
- export * from "./tenant";
@@ -1,57 +0,0 @@
1
- import { pgSchema, text, timestamp, uniqueIndex } from "drizzle-orm/pg-core";
2
- import { users } from "./auth";
3
-
4
- /**
5
- * `auth.organization` + `auth.member` + `auth.invitation` — Better Auth's
6
- * tenancy primitives. Mirrors the Prisma BAOrganization / BAMember /
7
- * BAInvitation models.
8
- */
9
- export const orgSchema = pgSchema("auth");
10
-
11
- export const organizations = orgSchema.table(
12
- "organization",
13
- {
14
- id: text("id").primaryKey(),
15
- name: text("name").notNull(),
16
- slug: text("slug").notNull(),
17
- logo: text("logo"),
18
- metadata: text("metadata"),
19
- createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
20
- updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
21
- },
22
- (t) => ({
23
- slugIdx: uniqueIndex("organization_slug_unique").on(t.slug),
24
- }),
25
- );
26
-
27
- export const members = orgSchema.table("member", {
28
- id: text("id").primaryKey(),
29
- userId: text("user_id")
30
- .notNull()
31
- .references(() => users.id, { onDelete: "cascade" }),
32
- organizationId: text("organization_id")
33
- .notNull()
34
- .references(() => organizations.id, { onDelete: "cascade" }),
35
- role: text("role").notNull().default("member"),
36
- createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
37
- });
38
-
39
- export const invitations = orgSchema.table("invitation", {
40
- id: text("id").primaryKey(),
41
- email: text("email").notNull(),
42
- inviterId: text("inviter_id").notNull(),
43
- organizationId: text("organization_id")
44
- .notNull()
45
- .references(() => organizations.id, { onDelete: "cascade" }),
46
- role: text("role"),
47
- status: text("status").notNull().default("pending"),
48
- expiresAt: timestamp("expires_at", { withTimezone: true }),
49
- token: text("token"),
50
- acceptedAt: timestamp("accepted_at", { withTimezone: true }),
51
- declinedAt: timestamp("declined_at", { withTimezone: true }),
52
- createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
53
- });
54
-
55
- export type Organization = typeof organizations.$inferSelect;
56
- export type Member = typeof members.$inferSelect;
57
- export type Invitation = typeof invitations.$inferSelect;
@@ -1,16 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "outDir": "./dist",
5
- "rootDir": "./src",
6
- "module": "ESNext",
7
- "moduleResolution": "Bundler",
8
- "target": "ES2022",
9
- "strict": true,
10
- "skipLibCheck": true,
11
- "esModuleInterop": true,
12
- "incremental": false
13
- },
14
- "include": ["src/**/*.ts", "drizzle.config.ts"],
15
- "exclude": ["node_modules", "dist", "migrations"]
16
- }
@@ -1,9 +0,0 @@
1
- # workflows
2
-
3
- Durable workflow & event-orchestration definitions for `{PRODUCT_NAME}`.
4
-
5
- | Folder | Purpose |
6
- |--------|---------|
7
- | `inngest/` | Inngest functions (durable steps, cron, fan-out) |
8
- | `n8n/` | n8n flow exports (low-code automations) |
9
- | `pusher/` | Pusher Channels event maps for real-time fan-out |
@@ -1,3 +0,0 @@
1
- # workflows/inngest
2
-
3
- Inngest functions — durable steps, cron, fan-out. Register from `backends/gateway/`.
@@ -1,30 +0,0 @@
1
- /**
2
- * Sample Inngest welcome-email function for scaffolds.
3
- *
4
- * Production path: swap the step body for `@nebutra/email` once the
5
- * generated project has a real email provider env (RESEND_API_KEY etc.):
6
- *
7
- * import { sendEmail } from "@nebutra/email";
8
- * await sendEmail({ to: event.data.email, template: "welcome", ... });
9
- *
10
- * Until then this is intentionally sample-only — returns the intended
11
- * recipient so the workflow is testable without sending mail.
12
- */
13
- import { Inngest } from "inngest";
14
-
15
- export const inngest = new Inngest({ id: "{PRODUCT_NAME}" });
16
-
17
- export const welcomeEmail = inngest.createFunction(
18
- { id: "welcome-email" },
19
- { event: "user/created" },
20
- async ({ event, step }) => {
21
- await step.run("send-welcome", async () => {
22
- // Sample-only: do not send until @nebutra/email + provider keys are wired.
23
- return {
24
- to: event.data.email,
25
- status: "sample-only",
26
- hint: "Replace with sendEmail() from @nebutra/email",
27
- };
28
- });
29
- },
30
- );
@@ -1,3 +0,0 @@
1
- # workflows/n8n
2
-
3
- n8n flow exports. Commit `.json` workflow files here and import them into your n8n instance.
@@ -1,6 +0,0 @@
1
- {
2
- "name": "{PRODUCT_NAME} — example flow",
3
- "nodes": [],
4
- "connections": {},
5
- "active": false
6
- }
@@ -1,3 +0,0 @@
1
- # workflows/pusher
2
-
3
- Pusher Channels event maps. Use for real-time fan-out (presence, live updates).
@@ -1,6 +0,0 @@
1
- export const PUSHER_EVENTS = {
2
- NOTIFICATION_CREATED: "notification.created",
3
- TENANT_UPDATED: "tenant.updated",
4
- } as const;
5
-
6
- export type PusherEvent = (typeof PUSHER_EVENTS)[keyof typeof PUSHER_EVENTS];