create-ortha-app 0.4.2 → 0.5.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/cli.js +6 -267
- package/dist/lib/features.d.ts +42 -20
- package/dist/lib/features.d.ts.map +1 -1
- package/dist/lib/features.js +56 -22
- package/dist/lib/run.d.ts +22 -0
- package/dist/lib/run.d.ts.map +1 -0
- package/dist/lib/run.js +318 -0
- package/dist/lib/template.d.ts.map +1 -1
- package/dist/lib/template.js +11 -1
- package/package.json +1 -1
- package/templates/default/README.md.tmpl +38 -12
- package/templates/default/apps/admin/src/plugins.spec.ts +5 -1
- package/templates/default/apps/admin/src/plugins.ts +21 -2
- package/templates/default/apps/admin/tsconfig.json +2 -1
- package/templates/default/apps/server/config/copilot-anthropic.ts +22 -0
- package/templates/default/apps/server/config/copilot-openai.ts +21 -0
- package/templates/default/apps/server/config/copilot.ts +61 -0
- package/templates/default/apps/server/config/docs.ts +14 -0
- package/templates/default/apps/server/config/i18n.ts +16 -0
- package/templates/default/apps/server/config/identity.ts +129 -0
- package/templates/default/apps/server/config/mcp.ts +25 -0
- package/templates/default/apps/server/config/media-storage.ts +84 -0
- package/templates/default/apps/server/config/media.ts +67 -0
- package/templates/default/apps/server/config/sso-github.ts +42 -0
- package/templates/default/apps/server/config/sso-oidc.ts +34 -0
- package/templates/default/apps/server/config/sso-saml.ts +46 -0
- package/templates/default/apps/server/ortha.config.ts +60 -449
- package/templates/default/apps/server/src/plugins.spec.ts +7 -0
- package/templates/default/apps/server/src/plugins.ts +84 -19
- package/templates/default/apps/server/tsconfig.spec.json +25 -0
- package/templates/default/env.tmpl +84 -2
- package/templates/default/tsconfig.json +3 -0
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import type { ServerPlugin } from '@orthacms/bootstrap-server';
|
|
2
2
|
import { ActivityPlugin } from '@orthacms/activity-server';
|
|
3
|
-
import { ContentPlugin } from '@orthacms/content-server';
|
|
3
|
+
import { ContentPlugin, ContentViewsPlugin } from '@orthacms/content-server';
|
|
4
4
|
import { DatabasePlugin } from '@orthacms/database';
|
|
5
5
|
import { I18nServerPlugin } from '@orthacms/i18n-server';
|
|
6
6
|
import { IdentityPlugin } from '@orthacms/identity-server';
|
|
7
|
-
// ortha:if sso
|
|
7
|
+
// ortha:if sso
|
|
8
8
|
import type { SsoRegistration } from '@orthacms/identity-domain';
|
|
9
|
+
// ortha:end
|
|
10
|
+
// ortha:if sso-oidc
|
|
9
11
|
import { createOidcProvider } from '@orthacms/identity-provider-oidc';
|
|
10
12
|
// ortha:end
|
|
13
|
+
// ortha:if sso-github
|
|
14
|
+
import { createGithubProvider } from '@orthacms/identity-provider-github';
|
|
15
|
+
// ortha:end
|
|
16
|
+
// ortha:if sso-saml
|
|
17
|
+
import { createSamlProvider } from '@orthacms/identity-provider-saml';
|
|
18
|
+
// ortha:end
|
|
11
19
|
import { MediaServerPlugin } from '@orthacms/media-server';
|
|
12
20
|
// ortha:if media-local
|
|
13
21
|
import { createLocalStorageProvider } from '@orthacms/media-provider-local';
|
|
@@ -25,6 +33,10 @@ import { createGcsStorageProvider } from '@orthacms/media-provider-gcs';
|
|
|
25
33
|
import { createVercelBlobStorageProvider } from '@orthacms/media-provider-vercel-blob';
|
|
26
34
|
// ortha:end
|
|
27
35
|
import { UsersPlugin } from '@orthacms/users-server';
|
|
36
|
+
import { AlarmsPlugin } from '@orthacms/alarms-server';
|
|
37
|
+
import { SegmentsPlugin } from '@orthacms/segments-server';
|
|
38
|
+
import { TransferPlugin } from '@orthacms/transfer-server';
|
|
39
|
+
import { WebhooksPlugin } from '@orthacms/webhooks-server';
|
|
28
40
|
// ortha:if graphql
|
|
29
41
|
import { ContentGraphqlPlugin } from '@orthacms/content-graphql';
|
|
30
42
|
// ortha:end
|
|
@@ -35,7 +47,6 @@ import {
|
|
|
35
47
|
CopilotPlugin,
|
|
36
48
|
type ProviderRegistration
|
|
37
49
|
} from '@orthacms/copilot-server';
|
|
38
|
-
import { createFakeProvider } from '@orthacms/copilot-provider-fake';
|
|
39
50
|
// ortha:if copilot-anthropic
|
|
40
51
|
import { createAnthropicProvider } from '@orthacms/copilot-provider-anthropic';
|
|
41
52
|
// ortha:end
|
|
@@ -54,17 +65,19 @@ import type { OrthaConfig } from '../ortha.config';
|
|
|
54
65
|
* one at the top of the list would be the house default and would fail on the
|
|
55
66
|
* first message.
|
|
56
67
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
68
|
+
* **An app that configured no backend registers none**, and has no copilot.
|
|
69
|
+
* There is no scripted offline adapter to fall back on, so `COPILOT_ENABLED`
|
|
70
|
+
* must stay `false` until a backend is configured — enabling it with an empty
|
|
71
|
+
* list fails at boot rather than shipping a chat that cannot answer.
|
|
60
72
|
*/
|
|
61
|
-
// ortha:if sso
|
|
73
|
+
// ortha:if sso
|
|
62
74
|
/**
|
|
63
75
|
* The identity providers this app can actually reach.
|
|
64
76
|
*
|
|
65
77
|
* **Only what is configured is registered.** `ortha.config.ts` omits a provider
|
|
66
|
-
* whose
|
|
67
|
-
* too — it would appear on the sign-in page as a button that can only
|
|
78
|
+
* whose connection settings are missing, and an unconfigured one is skipped
|
|
79
|
+
* here too — it would appear on the sign-in page as a button that can only
|
|
80
|
+
* fail.
|
|
68
81
|
*
|
|
69
82
|
* Running two directories at once is another entry. The name is what
|
|
70
83
|
* `/api/auth/sso/<name>/start` and every `sso_identities` row refer to the
|
|
@@ -74,12 +87,28 @@ import type { OrthaConfig } from '../ortha.config';
|
|
|
74
87
|
* string, which matters because most providers match it byte for byte.
|
|
75
88
|
*/
|
|
76
89
|
export function ssoProviders(config: OrthaConfig): SsoRegistration[] {
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
90
|
+
const configured = config.plugins.identity.ssoProviders;
|
|
91
|
+
const providers: SsoRegistration[] = [];
|
|
92
|
+
// ortha:if sso-oidc
|
|
93
|
+
if (configured?.oidc) {
|
|
94
|
+
const { name, ...settings } = configured.oidc;
|
|
95
|
+
providers.push({ name, provider: createOidcProvider(settings) });
|
|
96
|
+
}
|
|
97
|
+
// ortha:end
|
|
98
|
+
// ortha:if sso-github
|
|
99
|
+
if (configured?.github) {
|
|
100
|
+
const { name, ...settings } = configured.github;
|
|
101
|
+
providers.push({ name, provider: createGithubProvider(settings) });
|
|
80
102
|
}
|
|
81
|
-
|
|
82
|
-
|
|
103
|
+
// ortha:end
|
|
104
|
+
// ortha:if sso-saml
|
|
105
|
+
if (configured?.saml) {
|
|
106
|
+
const { name, ...settings } = configured.saml;
|
|
107
|
+
providers.push({ name, provider: createSamlProvider(settings) });
|
|
108
|
+
}
|
|
109
|
+
// ortha:end
|
|
110
|
+
|
|
111
|
+
return providers;
|
|
83
112
|
}
|
|
84
113
|
// ortha:end
|
|
85
114
|
|
|
@@ -105,7 +134,6 @@ export function copilotProviders(config: OrthaConfig): ProviderRegistration[] {
|
|
|
105
134
|
});
|
|
106
135
|
}
|
|
107
136
|
// ortha:end
|
|
108
|
-
providers.push({ name: 'fake', provider: createFakeProvider() });
|
|
109
137
|
|
|
110
138
|
return providers;
|
|
111
139
|
}
|
|
@@ -138,7 +166,7 @@ export function buildPlugins(config: OrthaConfig): ServerPlugin[] {
|
|
|
138
166
|
return [
|
|
139
167
|
// First: the only plugin that opens a resource in `onPluginInit`.
|
|
140
168
|
DatabasePlugin({ connectionString: config.database.url }),
|
|
141
|
-
// ortha:if sso
|
|
169
|
+
// ortha:if sso
|
|
142
170
|
// Identity, plus the identity providers this app offers. The second
|
|
143
171
|
// argument is where constructed adapters go: `ortha.config.ts` holds
|
|
144
172
|
// the typed view of the environment, and an adapter instance is not an
|
|
@@ -147,14 +175,14 @@ export function buildPlugins(config: OrthaConfig): ServerPlugin[] {
|
|
|
147
175
|
sso: { providers: ssoProviders(config) }
|
|
148
176
|
}),
|
|
149
177
|
// ortha:end
|
|
150
|
-
// ortha:ifnot sso
|
|
178
|
+
// ortha:ifnot sso
|
|
151
179
|
IdentityPlugin(config.plugins.identity),
|
|
152
180
|
// ortha:end
|
|
153
181
|
WorkspacesPlugin(),
|
|
154
182
|
ActivityPlugin(),
|
|
155
183
|
UsersPlugin(),
|
|
156
|
-
// No content types yet. Define some in `
|
|
157
|
-
// here as `types`, then add a `drizzle.config.ts` pointing at them and
|
|
184
|
+
// No content types yet. Define some in `apps/server/src/content/`, pass
|
|
185
|
+
// them here as `types`, then add a `drizzle.config.ts` pointing at them and
|
|
158
186
|
// a `migrations` descriptor so `ortha generate` / `ortha migrate` can
|
|
159
187
|
// manage their tables:
|
|
160
188
|
//
|
|
@@ -169,6 +197,12 @@ export function buildPlugins(config: OrthaConfig): ServerPlugin[] {
|
|
|
169
197
|
// Until then the plugin serves its generic routes with an empty
|
|
170
198
|
// registry, and owns no tables of its own.
|
|
171
199
|
content,
|
|
200
|
+
// Saved list views — the named filter/sort/column slices an editor
|
|
201
|
+
// returns to. A second plugin entry from the content package because
|
|
202
|
+
// `ServerPlugin.migrations` holds one descriptor per entry; this one
|
|
203
|
+
// ships the feature's own tables. Must follow identity and workspaces:
|
|
204
|
+
// its foreign keys point at their tables and migrations run in order.
|
|
205
|
+
ContentViewsPlugin({ content }),
|
|
172
206
|
// ortha:if graphql
|
|
173
207
|
// The same public content API over GraphQL, on /api/v1/graphql. It owns
|
|
174
208
|
// no schema and adds no credential — it reuses content's bearer guards
|
|
@@ -187,6 +221,14 @@ export function buildPlugins(config: OrthaConfig): ServerPlugin[] {
|
|
|
187
221
|
// provider, writing every upload to local disk. A deployment runs
|
|
188
222
|
// exactly one; swapping backend is swapping this expression (and the
|
|
189
223
|
// type of `media.storage` with it).
|
|
224
|
+
// Content alarms — rules that flag content problems without ever
|
|
225
|
+
// blocking a save or a publish. After content, whose registry and
|
|
226
|
+
// filter surface it evaluates rules through.
|
|
227
|
+
AlarmsPlugin(),
|
|
228
|
+
// Outgoing webhooks. Inert until someone adds an endpoint in the admin,
|
|
229
|
+
// and it only subscribes to the outbox, so nothing depends on it being
|
|
230
|
+
// registered any earlier than this.
|
|
231
|
+
WebhooksPlugin(),
|
|
190
232
|
MediaServerPlugin({
|
|
191
233
|
// ortha:if media-local
|
|
192
234
|
provider: createLocalStorageProvider(config.plugins.media.storage),
|
|
@@ -207,6 +249,29 @@ export function buildPlugins(config: OrthaConfig): ServerPlugin[] {
|
|
|
207
249
|
// ortha:end
|
|
208
250
|
config: config.plugins.media
|
|
209
251
|
}),
|
|
252
|
+
// Content export and import, one hop deep: relations, files and
|
|
253
|
+
// locales travel with a record, relations-of-relations stay as
|
|
254
|
+
// references. After content (every write goes through its writer, so
|
|
255
|
+
// an import cannot outrun validation or your own permissions) and
|
|
256
|
+
// after media (files travel with the records that use them). Owns no
|
|
257
|
+
// tables.
|
|
258
|
+
//
|
|
259
|
+
// The setting worth filling in per install is `identity`: it says
|
|
260
|
+
// which field identifies a record of each type, which is what lets an
|
|
261
|
+
// import recognise "this is that record" instead of adding a
|
|
262
|
+
// duplicate. Without it the natural key is a heuristic —
|
|
263
|
+
// `TransferPlugin({ identity: { post: ['slug'] } })`.
|
|
264
|
+
TransferPlugin(),
|
|
265
|
+
// Reader entitlements — who may *read* published content, as against
|
|
266
|
+
// who may touch it. After content, whose read-scope port it binds, so
|
|
267
|
+
// one decision covers REST, GraphQL and MCP at once.
|
|
268
|
+
//
|
|
269
|
+
// Registering it changes nothing on its own: with no audience created
|
|
270
|
+
// in the admin no predicate is emitted and every read costs what it
|
|
271
|
+
// did before. The line to fill in per install is `resolver` — it says
|
|
272
|
+
// where a reader's tags come from, and its absence means every reader
|
|
273
|
+
// is anonymous, which serves unrestricted content and nothing else.
|
|
274
|
+
SegmentsPlugin(),
|
|
210
275
|
// Registered after workspaces (runs are workspace-scoped) and identity
|
|
211
276
|
// (runs execute as the calling user, gated on `copilot:use`). The
|
|
212
277
|
// composition root is the single place that selects a backend: the
|
|
@@ -0,0 +1,25 @@
|
|
|
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/**/*.spec.ts"
|
|
24
|
+
]
|
|
25
|
+
}
|
|
@@ -75,8 +75,13 @@ ORTHA_ROOT_ADMIN_EMAIL=__ADMIN_EMAIL__
|
|
|
75
75
|
ORTHA_ROOT_ADMIN_PASSWORD=__ADMIN_PASSWORD__
|
|
76
76
|
|
|
77
77
|
# --- copilot ---
|
|
78
|
-
# Global kill switch. OFF by default:
|
|
79
|
-
#
|
|
78
|
+
# Global kill switch. OFF by default: whether this app offers Ortha AI at all is
|
|
79
|
+
# your call. `false` unregisters every /api/copilot route, so the endpoints 404
|
|
80
|
+
# and the admin's chat panel, Agents view and Skills page stand down.
|
|
81
|
+
#
|
|
82
|
+
# It is not what keeps your content in-house: a backend is registered only when
|
|
83
|
+
# it is configured below, so an app with no key reaches no third party whatever
|
|
84
|
+
# this says.
|
|
80
85
|
COPILOT_ENABLED=false
|
|
81
86
|
# Ceiling on a single model response, in tokens.
|
|
82
87
|
COPILOT_MAX_OUTPUT_TOKENS=8192
|
|
@@ -133,8 +138,85 @@ SSO_OIDC_LABEL=
|
|
|
133
138
|
# claiming an existing account.
|
|
134
139
|
SSO_OIDC_EMAIL_VERIFIED_WHEN_ABSENT=
|
|
135
140
|
# ortha:end
|
|
141
|
+
# ortha:if sso-github
|
|
142
|
+
# GitHub, or GitHub Enterprise Server. Both of these are what make the provider
|
|
143
|
+
# configured at all — and unlike OIDC the secret is not optional: GitHub's code
|
|
144
|
+
# exchange has no PKCE, so the secret is the only thing proving the code is
|
|
145
|
+
# being redeemed by this application.
|
|
146
|
+
# Register <origin>/api/auth/sso/<SSO_GITHUB_NAME>/callback as the OAuth app's
|
|
147
|
+
# authorization callback URL.
|
|
148
|
+
SSO_GITHUB_CLIENT_ID=
|
|
149
|
+
SSO_GITHUB_CLIENT_SECRET=
|
|
150
|
+
# What the route and every sso_identities row call this provider. Renaming it
|
|
151
|
+
# orphans the links that name it. Default: github
|
|
152
|
+
SSO_GITHUB_NAME=
|
|
153
|
+
# The sign-in button's text. Default: GitHub
|
|
154
|
+
SSO_GITHUB_LABEL=
|
|
155
|
+
# Comma-separated. Defaults to `read:user,user:email` — a profile and the
|
|
156
|
+
# verified addresses on it. GitHub's scopes are coarse, so anything wider hands
|
|
157
|
+
# this CMS access it has no use for.
|
|
158
|
+
SSO_GITHUB_SCOPES=
|
|
159
|
+
# For GitHub Enterprise Server, e.g. https://github.acme.com. Leave empty for
|
|
160
|
+
# github.com.
|
|
161
|
+
SSO_GITHUB_ENTERPRISE_BASE_URL=
|
|
162
|
+
# Restricts the account chooser to one organisation's members. Cosmetic: who
|
|
163
|
+
# actually gets in is this CMS's decision, not GitHub's.
|
|
164
|
+
SSO_GITHUB_ORGANIZATION=
|
|
165
|
+
# ortha:end
|
|
166
|
+
# ortha:if sso-saml
|
|
167
|
+
# SAML 2.0. All three are required and none has a fallback: SAML has no
|
|
168
|
+
# discovery document and no key endpoint, so the certificate you copy out of
|
|
169
|
+
# your identity provider is the whole of the trust relationship.
|
|
170
|
+
# The IdP's single-sign-on URL, where the AuthnRequest is sent.
|
|
171
|
+
SSO_SAML_ENTRY_POINT=
|
|
172
|
+
# The IdP's signing certificate, PEM or bare base64. An environment variable
|
|
173
|
+
# cannot hold real newlines, so `\n` escapes are accepted and put back.
|
|
174
|
+
SSO_SAML_IDP_CERT=
|
|
175
|
+
# This CMS's entity id — the Issuer on the AuthnRequest, and what the IdP has
|
|
176
|
+
# registered as the service provider.
|
|
177
|
+
SSO_SAML_ISSUER=
|
|
178
|
+
# What the route and every sso_identities row call this provider. Renaming it
|
|
179
|
+
# orphans the links that name it. Default: saml
|
|
180
|
+
SSO_SAML_NAME=
|
|
181
|
+
# The sign-in button's text. Default: SAML
|
|
182
|
+
SSO_SAML_LABEL=
|
|
183
|
+
# The attribute holding a STABLE identifier for the person. Worth setting
|
|
184
|
+
# whenever your IdP's NameID format is emailAddress: an address is not a stable
|
|
185
|
+
# identifier, and a profile whose subject is one is refused.
|
|
186
|
+
SSO_SAML_SUBJECT_ATTRIBUTE=
|
|
187
|
+
# Which attributes to read, when the usual spellings are not what your IdP
|
|
188
|
+
# sends. Left empty, each falls back to a list of the common ones.
|
|
189
|
+
SSO_SAML_EMAIL_ATTRIBUTE=
|
|
190
|
+
SSO_SAML_NAME_ATTRIBUTE=
|
|
191
|
+
SSO_SAML_GROUPS_ATTRIBUTE=
|
|
192
|
+
# Set to true ONLY if you are asserting that this directory owns the addresses
|
|
193
|
+
# it reports. SAML carries no verification claim at all — there is nothing to
|
|
194
|
+
# read — and this is the only gate on a first sign-in claiming an existing
|
|
195
|
+
# account.
|
|
196
|
+
SSO_SAML_EMAIL_VERIFIED=
|
|
197
|
+
# ortha:end
|
|
136
198
|
# The origin browsers reach the API on, when the admin is served from a
|
|
137
199
|
# different one. Leave empty for the usual same-origin deployment.
|
|
138
200
|
SSO_PUBLIC_BASE_URL=
|
|
139
201
|
# How long one sign-in attempt stays live, in seconds. Default 600.
|
|
140
202
|
SSO_REQUEST_TTL_SECONDS=
|
|
203
|
+
|
|
204
|
+
# --- webhooks (outgoing notifications on content changes) ---
|
|
205
|
+
# Endpoints live in the database — add them in the admin's Webhooks page
|
|
206
|
+
# (administrator-only). These settings are how hard the sender pushes and where
|
|
207
|
+
# it is allowed to reach.
|
|
208
|
+
#
|
|
209
|
+
# How often the sender looks for queued deliveries, in milliseconds. 0 queues
|
|
210
|
+
# but never sends from this process.
|
|
211
|
+
WEBHOOKS_DELIVERY_INTERVAL=2000
|
|
212
|
+
# Per-request timeout, in milliseconds.
|
|
213
|
+
WEBHOOKS_TIMEOUT=10000
|
|
214
|
+
# How long a finished delivery stays in the log. Nothing else prunes it.
|
|
215
|
+
WEBHOOKS_RETENTION_DAYS=30
|
|
216
|
+
# Permit http:// destinations — for local development only; the payload is
|
|
217
|
+
# readable to anyone on the path even though it is signed.
|
|
218
|
+
WEBHOOKS_ALLOW_INSECURE_URLS=false
|
|
219
|
+
# Permit loopback and RFC 1918 destinations. OFF by default: a webhook is the
|
|
220
|
+
# server making a request to an address a user typed, which is the shape of
|
|
221
|
+
# every SSRF. Turn it on only if your receiver really is inside this network.
|
|
222
|
+
WEBHOOKS_ALLOW_PRIVATE_NETWORKS=false
|