create-ortha-app 0.4.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 (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +7 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +277 -0
  6. package/dist/index.d.ts +11 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +32 -0
  9. package/dist/lib/conditionals.d.ts +37 -0
  10. package/dist/lib/conditionals.d.ts.map +1 -0
  11. package/dist/lib/conditionals.js +115 -0
  12. package/dist/lib/features.d.ts +185 -0
  13. package/dist/lib/features.d.ts.map +1 -0
  14. package/dist/lib/features.js +328 -0
  15. package/dist/lib/template.d.ts +46 -0
  16. package/dist/lib/template.d.ts.map +1 -0
  17. package/dist/lib/template.js +115 -0
  18. package/dist/lib/ui.d.ts +76 -0
  19. package/dist/lib/ui.d.ts.map +1 -0
  20. package/dist/lib/ui.js +310 -0
  21. package/dist/lib/validate.d.ts +13 -0
  22. package/dist/lib/validate.d.ts.map +1 -0
  23. package/dist/lib/validate.js +51 -0
  24. package/package.json +37 -0
  25. package/templates/default/README.md.tmpl +199 -0
  26. package/templates/default/_gitignore +8 -0
  27. package/templates/default/apps/admin/index.html +38 -0
  28. package/templates/default/apps/admin/src/main.tsx +5 -0
  29. package/templates/default/apps/admin/src/plugins.spec.ts +58 -0
  30. package/templates/default/apps/admin/src/plugins.ts +57 -0
  31. package/templates/default/apps/admin/src/styles.css +254 -0
  32. package/templates/default/apps/admin/tsconfig.json +29 -0
  33. package/templates/default/apps/admin/vite.config.mts +67 -0
  34. package/templates/default/apps/admin-e2e/playwright.config.ts +51 -0
  35. package/templates/default/apps/admin-e2e/src/auth.spec.ts +55 -0
  36. package/templates/default/apps/admin-e2e/src/support/seed.ts +62 -0
  37. package/templates/default/apps/admin-e2e/tsconfig.json +23 -0
  38. package/templates/default/apps/server/jest.config.js +38 -0
  39. package/templates/default/apps/server/jest.setup.js +20 -0
  40. package/templates/default/apps/server/ortha.config.ts +505 -0
  41. package/templates/default/apps/server/src/main.ts +26 -0
  42. package/templates/default/apps/server/src/plugins.spec.ts +71 -0
  43. package/templates/default/apps/server/src/plugins.ts +229 -0
  44. package/templates/default/apps/server/tsconfig.json +31 -0
  45. package/templates/default/apps/server-e2e/jest.config.js +47 -0
  46. package/templates/default/apps/server-e2e/src/api.spec.ts +107 -0
  47. package/templates/default/apps/server-e2e/src/global-setup.ts +41 -0
  48. package/templates/default/apps/server-e2e/src/jest.setup.ts +28 -0
  49. package/templates/default/apps/server-e2e/src/support/db.ts +143 -0
  50. package/templates/default/apps/server-e2e/src/support/test-app.ts +64 -0
  51. package/templates/default/apps/server-e2e/tsconfig.json +26 -0
  52. package/templates/default/docker-compose.yml +21 -0
  53. package/templates/default/env.tmpl +140 -0
  54. package/templates/default/package.json.tmpl +51 -0
  55. package/templates/default/tsconfig.json +18 -0
@@ -0,0 +1,64 @@
1
+ import type { NestExpressApplication } from '@nestjs/platform-express';
2
+ import { createServer } from '@orthacms/bootstrap-server';
3
+ import { closeDatabase } from '@orthacms/database';
4
+ import type { Server } from 'node:http';
5
+ import config from '../../../server/ortha.config';
6
+ import { buildPlugins } from '../../../server/src/plugins';
7
+
8
+ /**
9
+ * An origin the login route accepts.
10
+ *
11
+ * Login is guarded against cross-site POSTs by an allow-list, so a request
12
+ * with no `Origin` — or the wrong one — is refused with `403` before the
13
+ * credentials are ever looked at. Read from the config rather than hardcoded:
14
+ * the list follows `ADMIN_PORT`, so a hardcoded `:4200` fails on any app whose
15
+ * admin runs somewhere else, and fails as a confusing `403` rather than
16
+ * "wrong origin".
17
+ */
18
+ export const ALLOWED_ORIGIN =
19
+ config.plugins.identity.allowedOrigins?.[0] ?? 'http://localhost:4200';
20
+
21
+ /** A booted app plus the raw server to hand to supertest. */
22
+ export interface TestApp {
23
+ app: NestExpressApplication;
24
+ server: Server;
25
+ }
26
+
27
+ /**
28
+ * Boots **this app** — the real `buildPlugins`, the real config — on an
29
+ * ephemeral port.
30
+ *
31
+ * Deliberately `createServer` rather than a hand-assembled Nest app: a harness
32
+ * that mirrors the bootstrap can never fail on a bootstrap defect, and this
33
+ * suite exists to catch exactly the things that only appear once everything is
34
+ * wired together. Port `0` keeps parallel runs from colliding; supertest is
35
+ * handed the server object, so nothing depends on the number.
36
+ *
37
+ * `createServer` also runs the `OnApplicationBootstrap` seeders, so the system
38
+ * roles and the root admin from `.env.e2e` exist by the time a test runs.
39
+ */
40
+ export async function createTestApp(): Promise<TestApp> {
41
+ const app = await createServer({
42
+ plugins: buildPlugins(config),
43
+ port: 0,
44
+ globalPrefix: config.globalPrefix,
45
+ bodyLimit: config.bodyLimit,
46
+ // The reference is developer tooling and adds a route surface these
47
+ // tests do not describe. Off, whatever the environment says.
48
+ docs: { ...config.docs, enabled: false }
49
+ });
50
+
51
+ return { app, server: app.getHttpServer() as Server };
52
+ }
53
+
54
+ /**
55
+ * Closes the app and the database pool.
56
+ *
57
+ * `app.close()` does not end the pool — nothing binds a shutdown hook to it —
58
+ * so without `closeDatabase` the Jest worker stays alive on an open handle and
59
+ * the run hangs after the last assertion passes.
60
+ */
61
+ export async function closeTestApp(harness: TestApp): Promise<void> {
62
+ await harness.app.close();
63
+ await closeDatabase();
64
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2022",
4
+ "lib": [
5
+ "es2023"
6
+ ],
7
+ "module": "commonjs",
8
+ "moduleResolution": "node",
9
+ "types": [
10
+ "node",
11
+ "jest"
12
+ ],
13
+ "strict": true,
14
+ "skipLibCheck": true,
15
+ "esModuleInterop": true,
16
+ "forceConsistentCasingInFileNames": true,
17
+ "resolveJsonModule": true,
18
+ "experimentalDecorators": true,
19
+ "emitDecoratorMetadata": true,
20
+ "noEmit": true
21
+ },
22
+ "include": [
23
+ "src/**/*.ts",
24
+ "jest.config.js"
25
+ ]
26
+ }
@@ -0,0 +1,21 @@
1
+ # Postgres for local development. `docker compose up -d`, then `npm run migrate`.
2
+ services:
3
+ postgres:
4
+ image: postgres:17-alpine
5
+ restart: unless-stopped
6
+ environment:
7
+ POSTGRES_USER: ortha
8
+ POSTGRES_PASSWORD: ortha
9
+ POSTGRES_DB: __DATABASE_NAME__
10
+ ports:
11
+ - '5432:5432'
12
+ volumes:
13
+ - postgres-data:/var/lib/postgresql/data
14
+ healthcheck:
15
+ test: ['CMD-SHELL', 'pg_isready -U ortha']
16
+ interval: 5s
17
+ timeout: 5s
18
+ retries: 10
19
+
20
+ volumes:
21
+ postgres-data:
@@ -0,0 +1,140 @@
1
+ # Port the API listens on. `ortha start` serves the admin from here too.
2
+ PORT=3000
3
+ # Port the Vite dev server listens on (development only).
4
+ ADMIN_PORT=4200
5
+
6
+ # PostgreSQL connection string.
7
+ DATABASE_URL=__DATABASE_URL__
8
+
9
+ # How many reverse proxies sit in front of the app (Express `trust proxy`).
10
+ # REQUIRED behind a load balancer, ingress or CDN: left unset, Express ignores
11
+ # X-Forwarded-For, every request reports the proxy's address, and the login
12
+ # rate limit collapses into ONE bucket for the whole deployment. Prefer the hop
13
+ # count (1 for a single proxy) — a client cannot forge past it.
14
+ TRUST_PROXY=
15
+
16
+ # Serve the API reference on /reference. On outside production by default.
17
+ API_DOCS=
18
+
19
+ # Largest JSON request body the API accepts. Defaults to 1mb.
20
+ MAX_REQUEST_BODY=
21
+
22
+ # --- media ---
23
+ # ortha:if media-local
24
+ # Where uploads are written. Point this at a persistent volume in production:
25
+ # a container's own disk is wiped on every deploy.
26
+ MEDIA_LOCAL_ROOT=./.storage/media
27
+ # ortha:end
28
+ # ortha:if media-vercel-blob
29
+ # Vercel injects this on its own platform; set it only when running elsewhere.
30
+ # NOTE: every blob in this store gets a permanent, public URL — anyone who
31
+ # obtains one can fetch that asset without signing in. Do not pick this backend
32
+ # for confidential media.
33
+ BLOB_READ_WRITE_TOKEN=
34
+ # ortha:end
35
+ # ortha:if media-gcs
36
+ # The bucket every upload lands in. Required.
37
+ MEDIA_GCS_BUCKET=
38
+ # Leave the rest blank on GKE or Cloud Run: the client falls back to
39
+ # Application Default Credentials, which is the point of using this adapter
40
+ # instead of the S3-compatible one.
41
+ MEDIA_GCS_PROJECT_ID=
42
+ MEDIA_GCS_KEY_FILE=
43
+ # Sign URLs through IAM instead of a private key (Workload Identity). Needs
44
+ # the iam.serviceAccounts.signBlob permission.
45
+ MEDIA_GCS_SIGN_WITH_IAM=false
46
+ # ortha:end
47
+ # ortha:if media-azure
48
+ # The container every upload lands in, and how to reach the account. The portal
49
+ # hands you the connection string; Azurite prints one too. For managed identity,
50
+ # build the container client yourself in plugins.ts — see the package docs.
51
+ MEDIA_AZURE_CONTAINER=media
52
+ MEDIA_AZURE_CONNECTION_STRING=
53
+ # ortha:end
54
+ # ortha:if media-s3
55
+ # The bucket every upload lands in. Required.
56
+ MEDIA_S3_BUCKET=
57
+ # Leave the endpoint blank for AWS S3 itself. For anything else, set it:
58
+ # Cloudflare R2 https://<account>.r2.cloudflarestorage.com
59
+ # MinIO http://localhost:9000 (also set FORCE_PATH_STYLE=true)
60
+ MEDIA_S3_ENDPOINT=
61
+ MEDIA_S3_REGION=auto
62
+ MEDIA_S3_FORCE_PATH_STYLE=false
63
+ # Leave both blank on a host with an instance role or IRSA — the SDK finds
64
+ # those itself, and blanks here would shadow them with keys that cannot sign.
65
+ MEDIA_S3_ACCESS_KEY_ID=
66
+ MEDIA_S3_SECRET_ACCESS_KEY=
67
+ # ortha:end
68
+ MEDIA_MAX_UPLOAD_BYTES=52428800
69
+
70
+ # --- first admin ---
71
+ # With an email set, an active admin is provisioned on boot — idempotent and
72
+ # non-destructive, so an existing account is left alone. This is how you get
73
+ # your first login; clear it once you have one.
74
+ ORTHA_ROOT_ADMIN_EMAIL=__ADMIN_EMAIL__
75
+ ORTHA_ROOT_ADMIN_PASSWORD=__ADMIN_PASSWORD__
76
+
77
+ # --- copilot ---
78
+ # Global kill switch. OFF by default: enabling a hosted provider sends workspace
79
+ # content to a third party, so an operator opts in explicitly.
80
+ COPILOT_ENABLED=false
81
+ # Ceiling on a single model response, in tokens.
82
+ COPILOT_MAX_OUTPUT_TOKENS=8192
83
+ # ortha:if copilot-anthropic
84
+ # Native Claude. Setting the key is what REGISTERS this backend — leave it empty
85
+ # and there is no `claude` in the picker at all, rather than one that fails on
86
+ # the first message. The key never leaves the server.
87
+ ANTHROPIC_API_KEY=
88
+ # Comma-separated. The FIRST is the default; the rest are what a user can switch
89
+ # to mid-conversation without a redeploy.
90
+ COPILOT_ANTHROPIC_MODELS=claude-sonnet-5
91
+ # ortha:end
92
+ # ortha:if copilot-openai
93
+ # An OpenAI-wire endpoint. Setting this REGISTERS the backend, and there is no
94
+ # default: an endpoint nobody named is a backend that can only time out. A local
95
+ # Ollama is http://localhost:11434/v1.
96
+ COPILOT_OPENAI_BASE_URL=
97
+ COPILOT_OPENAI_MODELS=llama3.1
98
+ # Leave empty for a local runtime that wants no auth.
99
+ COPILOT_OPENAI_API_KEY=
100
+ # ortha:end
101
+ # ortha:if mcp
102
+ # --- mcp ---
103
+ # Global kill switch. OFF by default: once on, any holder of a `full`-scope API
104
+ # token can drive content CRUD from an external agent. Agents connect to
105
+ # POST /api/v1/mcp with `Authorization: Bearer <token>`.
106
+ MCP_ENABLED=false
107
+ # Ceiling on one tools/call, in milliseconds. Defaults to 30000.
108
+ MCP_CALL_TIMEOUT_MS=
109
+ # Ceiling on one tool result, in bytes. Defaults to 4194304 (4 MB).
110
+ MCP_MAX_RESULT_BYTES=
111
+ # ortha:end
112
+
113
+ # --- single sign-on ---
114
+ # ortha:if sso-oidc
115
+ # Generic OpenID Connect — Okta, Auth0, Keycloak, Google, Entra ID and the rest.
116
+ # Both of these are what make the provider configured at all: an issuer with no
117
+ # client id is a sign-in button that can only fail.
118
+ #
119
+ # The issuer must match the `iss` claim byte for byte, trailing slash included
120
+ # (Auth0 issues with one). Register
121
+ # <origin>/api/auth/sso/<SSO_OIDC_NAME>/callback with the provider.
122
+ SSO_OIDC_ISSUER=
123
+ SSO_OIDC_CLIENT_ID=
124
+ SSO_OIDC_CLIENT_SECRET=
125
+ # What the route and every sso_identities row call this provider. Renaming it
126
+ # orphans the links that name it. Default: oidc
127
+ SSO_OIDC_NAME=
128
+ # The sign-in button's text. Defaults to the issuer's host.
129
+ SSO_OIDC_LABEL=
130
+ # Set to true ONLY if your provider never sends `email_verified` and you are
131
+ # asserting that this directory owns the addresses it reports (Microsoft Entra
132
+ # ID is the common case). That claim is the only gate on a first sign-in
133
+ # claiming an existing account.
134
+ SSO_OIDC_EMAIL_VERIFIED_WHEN_ABSENT=
135
+ # ortha:end
136
+ # The origin browsers reach the API on, when the admin is served from a
137
+ # different one. Leave empty for the usual same-origin deployment.
138
+ SSO_PUBLIC_BASE_URL=
139
+ # How long one sign-in attempt stays live, in seconds. Default 600.
140
+ SSO_REQUEST_TTL_SECONDS=
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "__APP_NAME__",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "commonjs",
6
+ "scripts": {
7
+ "dev": "ortha dev",
8
+ "build": "ortha build",
9
+ "start": "ortha start",
10
+ "migrate": "ortha migrate",
11
+ "generate": "ortha generate",
12
+ "studio": "ortha studio",
13
+ "test": "npm run test:server && npm run test:admin",
14
+ "test:server": "jest --config apps/server/jest.config.js",
15
+ "test:admin": "vitest run --config apps/admin/vite.config.mts",
16
+ "e2e": "npm run e2e:server && npm run e2e:admin",
17
+ "typecheck": "tsc --build tsconfig.json",
18
+ "e2e:server": "jest --config apps/server-e2e/jest.config.js",
19
+ "e2e:admin": "playwright test --config apps/admin-e2e/playwright.config.ts"
20
+ },
21
+ "dependencies": {
22
+ "react": "^19.0.0",
23
+ "react-dom": "^19.0.0",
24
+ "react-router-dom": "6.30.3",
25
+ "reflect-metadata": "^0.1.13",
26
+ "rxjs": "^7.8.0"
27
+ },
28
+ "devDependencies": {
29
+ "@playwright/test": "^1.36.0",
30
+ "@swc/core": "~1.15.5",
31
+ "@swc/jest": "~0.2.38",
32
+ "@tailwindcss/vite": "^4.3.0",
33
+ "@testing-library/dom": "10.4.0",
34
+ "@testing-library/react": "16.3.0",
35
+ "@types/jest": "~30.0.0",
36
+ "@types/node": "^20.19.9",
37
+ "@types/pg": "^8.11.0",
38
+ "@types/react": "^19.0.0",
39
+ "@types/react-dom": "^19.0.0",
40
+ "@types/supertest": "^6.0.0",
41
+ "@vitejs/plugin-react": "^6.0.0",
42
+ "jest": "~30.3.0",
43
+ "jsdom": "~22.1.0",
44
+ "pg": "^8.13.0",
45
+ "supertest": "^7.0.0",
46
+ "tailwindcss": "^4.3.0",
47
+ "typescript": "~5.9.2",
48
+ "vite": "^8.0.0",
49
+ "vitest": "~4.1.0"
50
+ }
51
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "files": [],
3
+ "include": [],
4
+ "references": [
5
+ {
6
+ "path": "./apps/server"
7
+ },
8
+ {
9
+ "path": "./apps/admin"
10
+ },
11
+ {
12
+ "path": "./apps/server-e2e"
13
+ },
14
+ {
15
+ "path": "./apps/admin-e2e"
16
+ }
17
+ ]
18
+ }