create-sailor 1.5.0 → 1.6.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-sailor",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "Governed AI-native SaaS scaffolder for Nebutra Sailor. Bootstrap a production-ready Next.js + Hono + Prisma monorepo with multi-tenant foundations, region-aware defaults, and AI integrations.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -0,0 +1,13 @@
1
+ # backends/gateway
2
+
3
+ TypeScript / Hono BFF. The **default** backend for `{PRODUCT_NAME}`.
4
+
5
+ Handles auth, tenancy, rate-limiting, and routing in front of all other services.
6
+
7
+ CRUD, webhooks, billing, third-party API proxies, and any new backend work goes here unless one of the Python exceptions applies (see `backends/python/README.md`).
8
+
9
+ ## Develop
10
+
11
+ ```bash
12
+ pnpm --filter @{PRODUCT_NAME}/gateway dev
13
+ ```
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@{PRODUCT_NAME}/gateway",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "main": "src/index.ts",
7
+ "scripts": {
8
+ "dev": "tsx watch src/index.ts",
9
+ "build": "tsup src/index.ts --format esm --target node20",
10
+ "typecheck": "tsc --noEmit"
11
+ },
12
+ "dependencies": {
13
+ "hono": "^4.6.0",
14
+ "@hono/node-server": "^1.13.0"
15
+ },
16
+ "devDependencies": {
17
+ "@types/node": "catalog:",
18
+ "tsx": "^4.19.0",
19
+ "tsup": "catalog:",
20
+ "typescript": "catalog:"
21
+ }
22
+ }
@@ -0,0 +1,16 @@
1
+ import { serve } from "@hono/node-server";
2
+ import { Hono } from "hono";
3
+
4
+ // import { tenantMiddleware } from "@nebutra/tenant/middleware";
5
+ // import { fromHeader } from "@nebutra/tenant/resolvers";
6
+
7
+ const app = new Hono();
8
+
9
+ // app.use("*", tenantMiddleware({ resolvers: [fromHeader("x-tenant-id")] }));
10
+
11
+ app.get("/health", (c) => c.json({ status: "ok" }));
12
+
13
+ const port = Number(process.env.PORT ?? 8080);
14
+ serve({ fetch: app.fetch, port });
15
+
16
+ export default app;
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "resolveJsonModule": true,
10
+ "noEmit": true
11
+ },
12
+ "include": ["src/**/*.ts"]
13
+ }
@@ -0,0 +1,16 @@
1
+ # backends/python
2
+
3
+ FastAPI-based Python backends. **Use only when justified.**
4
+
5
+ Per the TS-by-Default ADR (`docs/architecture/2026-05-10-ts-by-default-python-only-when-justified.md`), a new Python service is acceptable only when it cites at least one of:
6
+
7
+ 1. **Batch / queued work** too long for edge runtimes (>5s typical)
8
+ 2. **ML / scientific compute** that depends on the Python ecosystem (transformers, vLLM, etc.)
9
+ 3. **Specialized libraries** with no comparable TS port
10
+
11
+ For CRUD, webhooks, billing, content management — use `backends/gateway/` (TS) instead.
12
+
13
+ ## Layout
14
+
15
+ - `_shared/` — shared primitives (queue client, db, logger) consumed by services
16
+ - `<service>/` — one folder per active service (must have a real caller)
@@ -0,0 +1,5 @@
1
+ """Shared primitives for {PRODUCT_NAME} Python backends.
2
+
3
+ Active modules go here once they have real callers. Empty stubs should stay
4
+ in `incubator/` (excluded from workspaces / CI) until promoted.
5
+ """
@@ -0,0 +1,13 @@
1
+ [project]
2
+ name = "{PRODUCT_NAME}-python-backends"
3
+ version = "0.1.0"
4
+ description = "Python backends for {PRODUCT_NAME} — batch / ML / specialized only."
5
+ requires-python = ">=3.11"
6
+ dependencies = [
7
+ "fastapi>=0.115.0",
8
+ "uvicorn[standard]>=0.32.0",
9
+ "pydantic>=2.9.0",
10
+ ]
11
+
12
+ [tool.uv.workspace]
13
+ members = ["_shared", "*"]
@@ -0,0 +1,11 @@
1
+ # e2e
2
+
3
+ Playwright end-to-end tests for `{PRODUCT_NAME}`.
4
+
5
+ | Folder | Scope |
6
+ |--------|-------|
7
+ | `smoke/` | Fast smoke tests — must pass on every PR |
8
+ | `golden/` | Critical-path "golden" flows (signup → first action → billing) |
9
+ | `sleptons/` | Sleptons-specific scenarios (skip if Sleptons is disabled) |
10
+
11
+ Run with `pnpm exec playwright test --config=playwright.config.ts`.
@@ -0,0 +1,6 @@
1
+ import { expect, test } from "@playwright/test";
2
+
3
+ test.skip("golden: signup → dashboard → first action", async ({ page }) => {
4
+ await page.goto("/sign-up");
5
+ await expect(page.getByRole("heading")).toBeVisible();
6
+ });
@@ -0,0 +1,14 @@
1
+ import { defineConfig, devices } from "@playwright/test";
2
+
3
+ export default defineConfig({
4
+ testDir: ".",
5
+ fullyParallel: true,
6
+ forbidOnly: !!process.env.CI,
7
+ retries: process.env.CI ? 2 : 0,
8
+ reporter: process.env.CI ? "github" : "list",
9
+ use: {
10
+ baseURL: process.env.E2E_BASE_URL ?? "http://localhost:3000",
11
+ trace: "on-first-retry",
12
+ },
13
+ projects: [{ name: "chromium", use: { ...devices["Desktop Chrome"] } }],
14
+ });
@@ -0,0 +1,6 @@
1
+ import { expect, test } from "@playwright/test";
2
+
3
+ test.skip("sleptons: community feed renders", async ({ page }) => {
4
+ await page.goto("/sleptons");
5
+ await expect(page).toHaveURL(/sleptons/);
6
+ });
@@ -0,0 +1,6 @@
1
+ import { expect, test } from "@playwright/test";
2
+
3
+ test("homepage renders", async ({ page }) => {
4
+ await page.goto("/");
5
+ await expect(page).toHaveTitle(/.+/);
6
+ });
@@ -0,0 +1,10 @@
1
+ # infra
2
+
3
+ Infrastructure-as-code, runtime configs, data pipelines, and ops scripts for `{PRODUCT_NAME}`.
4
+
5
+ | Folder | Purpose |
6
+ |--------|---------|
7
+ | `iac/` | Terraform / Pulumi / CDK — provisioning cloud resources |
8
+ | `runtime/` | Container, edge, and serverless runtime configs (Dockerfiles, fly.toml, etc.) |
9
+ | `data/` | DB migrations, ETL definitions, ClickHouse schemas |
10
+ | `ops/` | Operational scripts (backup, restore, on-call runbooks) |
@@ -0,0 +1,3 @@
1
+ # infra/data
2
+
3
+ DB migrations, ETL definitions, ClickHouse schemas, and other persistent-data assets that aren't owned by a single app.
@@ -0,0 +1,3 @@
1
+ # infra/iac
2
+
3
+ Provisioning: Terraform / Pulumi / CDK. Put one subfolder per stack (e.g. `aws/`, `cloudflare/`).
@@ -0,0 +1,3 @@
1
+ # infra/ops
2
+
3
+ Operational scripts and runbooks — backup, restore, on-call playbooks, incident response.
@@ -0,0 +1,3 @@
1
+ # infra/runtime
2
+
3
+ Container, edge, and serverless runtime configs — Dockerfiles, `fly.toml`, ECS task definitions, etc.
@@ -0,0 +1,8 @@
1
+ # tests
2
+
3
+ Cross-cutting tests that don't belong to a single package.
4
+
5
+ | Folder | Tool | Purpose |
6
+ |--------|------|---------|
7
+ | `architecture/` | vitest | Architecture rules — import boundaries, layering, no-cycle |
8
+ | `load/` | k6 | Load + perf tests against deployed environments |
@@ -0,0 +1,7 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ describe("architecture: package boundaries", () => {
4
+ it("apps do not import from each other", () => {
5
+ expect(true).toBe(true);
6
+ });
7
+ });
@@ -0,0 +1,13 @@
1
+ import { check, sleep } from "k6";
2
+ import http from "k6/http";
3
+
4
+ export const options = {
5
+ vus: 5,
6
+ duration: "30s",
7
+ };
8
+
9
+ export default function () {
10
+ const res = http.get(__ENV.BASE_URL ?? "http://localhost:3000");
11
+ check(res, { "status is 200": (r) => r.status === 200 });
12
+ sleep(1);
13
+ }
@@ -0,0 +1,9 @@
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 |
@@ -0,0 +1,3 @@
1
+ # workflows/inngest
2
+
3
+ Inngest functions — durable steps, cron, fan-out. Register from `backends/gateway/`.
@@ -0,0 +1,14 @@
1
+ import { Inngest } from "inngest";
2
+
3
+ export const inngest = new Inngest({ id: "{PRODUCT_NAME}" });
4
+
5
+ export const welcomeEmail = inngest.createFunction(
6
+ { id: "welcome-email" },
7
+ { event: "user/created" },
8
+ async ({ event, step }) => {
9
+ await step.run("send-welcome", async () => {
10
+ // TODO: dispatch via @nebutra/email
11
+ return { to: event.data.email };
12
+ });
13
+ },
14
+ );
@@ -0,0 +1,3 @@
1
+ # workflows/n8n
2
+
3
+ n8n flow exports. Commit `.json` workflow files here and import them into your n8n instance.
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "{PRODUCT_NAME} — example flow",
3
+ "nodes": [],
4
+ "connections": {},
5
+ "active": false
6
+ }
@@ -0,0 +1,3 @@
1
+ # workflows/pusher
2
+
3
+ Pusher Channels event maps. Use for real-time fan-out (presence, live updates).
@@ -0,0 +1,6 @@
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];