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/dist/index.js +172 -172
- package/package.json +1 -1
- package/templates/backends/gateway/README.md +13 -0
- package/templates/backends/gateway/package.json +22 -0
- package/templates/backends/gateway/src/index.ts +16 -0
- package/templates/backends/gateway/tsconfig.json +13 -0
- package/templates/backends/python/README.md +16 -0
- package/templates/backends/python/_shared/__init__.py +5 -0
- package/templates/backends/python/pyproject.toml +13 -0
- package/templates/e2e/README.md +11 -0
- package/templates/e2e/golden/signup.spec.ts +6 -0
- package/templates/e2e/playwright.config.ts +14 -0
- package/templates/e2e/sleptons/community.spec.ts +6 -0
- package/templates/e2e/smoke/health.spec.ts +6 -0
- package/templates/infra/README.md +10 -0
- package/templates/infra/data/README.md +3 -0
- package/templates/infra/iac/README.md +3 -0
- package/templates/infra/ops/README.md +3 -0
- package/templates/infra/runtime/README.md +3 -0
- package/templates/tests/README.md +8 -0
- package/templates/tests/architecture/no-cycles.test.ts +7 -0
- package/templates/tests/load/smoke.k6.js +13 -0
- package/templates/workflows/README.md +9 -0
- package/templates/workflows/inngest/README.md +3 -0
- package/templates/workflows/inngest/welcome-email.ts +14 -0
- package/templates/workflows/n8n/README.md +3 -0
- package/templates/workflows/n8n/example-flow.json +6 -0
- package/templates/workflows/pusher/README.md +3 -0
- package/templates/workflows/pusher/events.ts +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-sailor",
|
|
3
|
-
"version": "1.
|
|
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,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,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,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,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,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,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
|
+
);
|