create-ortha-app 0.4.3 → 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 -299
- package/dist/lib/features.d.ts +21 -1
- package/dist/lib/features.d.ts.map +1 -1
- package/dist/lib/features.js +30 -2
- 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/package.json +1 -1
- package/templates/default/README.md.tmpl +26 -7
- package/templates/default/apps/admin/src/plugins.spec.ts +4 -1
- package/templates/default/apps/admin/src/plugins.ts +17 -2
- package/templates/default/apps/server/config/copilot-openai.ts +1 -1
- package/templates/default/apps/server/config/identity.ts +38 -5
- package/templates/default/apps/server/config/media-storage.ts +3 -2
- package/templates/default/apps/server/config/sso-github.ts +42 -0
- package/templates/default/apps/server/config/sso-oidc.ts +1 -1
- package/templates/default/apps/server/config/sso-saml.ts +46 -0
- package/templates/default/apps/server/ortha.config.ts +6 -1
- package/templates/default/apps/server/src/plugins.spec.ts +6 -0
- package/templates/default/apps/server/src/plugins.ts +68 -13
- package/templates/default/env.tmpl +77 -0
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Typed configuration for this app.
|
|
3
3
|
*
|
|
4
|
-
* **`config/` is the single place that reads
|
|
4
|
+
* **`config/` is the single place that reads the environment**, and it reads it
|
|
5
|
+
* only through the readers in `@orthacms/utils-server` — never `process.env`
|
|
6
|
+
* directly. `readEnv` is where "an empty value means the setting is absent" is
|
|
7
|
+
* decided, and `.env` ships keys with nothing on the right-hand side; a raw
|
|
8
|
+
* `process.env['X'] ?? default` skips that decision and lets a blank line win
|
|
9
|
+
* over the default. Everything
|
|
5
10
|
* downstream — the host, every plugin — receives typed values, so "where does
|
|
6
11
|
* this setting come from" has exactly one answer. Deploy-specific values come
|
|
7
12
|
* from the environment; stable product tuning lives in the builders as literals.
|
|
@@ -29,12 +29,18 @@ const EXPECTED_PLUGINS = [
|
|
|
29
29
|
'activity',
|
|
30
30
|
'users',
|
|
31
31
|
'content',
|
|
32
|
+
// A second entry from the content package: `ServerPlugin.migrations` holds
|
|
33
|
+
// one descriptor per entry, and saved list views ship their own tables.
|
|
34
|
+
'content-views',
|
|
32
35
|
// ortha:if graphql
|
|
33
36
|
'content-graphql',
|
|
34
37
|
// ortha:end
|
|
35
38
|
'i18n',
|
|
36
39
|
'alarms',
|
|
40
|
+
'webhooks',
|
|
37
41
|
'media',
|
|
42
|
+
'transfer',
|
|
43
|
+
'segments',
|
|
38
44
|
'copilot'
|
|
39
45
|
// ortha:if mcp
|
|
40
46
|
,
|
|
@@ -4,10 +4,18 @@ 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';
|
|
@@ -26,6 +34,9 @@ import { createVercelBlobStorageProvider } from '@orthacms/media-provider-vercel
|
|
|
26
34
|
// ortha:end
|
|
27
35
|
import { UsersPlugin } from '@orthacms/users-server';
|
|
28
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';
|
|
29
40
|
// ortha:if graphql
|
|
30
41
|
import { ContentGraphqlPlugin } from '@orthacms/content-graphql';
|
|
31
42
|
// ortha:end
|
|
@@ -59,13 +70,14 @@ import type { OrthaConfig } from '../ortha.config';
|
|
|
59
70
|
* must stay `false` until a backend is configured — enabling it with an empty
|
|
60
71
|
* list fails at boot rather than shipping a chat that cannot answer.
|
|
61
72
|
*/
|
|
62
|
-
// ortha:if sso
|
|
73
|
+
// ortha:if sso
|
|
63
74
|
/**
|
|
64
75
|
* The identity providers this app can actually reach.
|
|
65
76
|
*
|
|
66
77
|
* **Only what is configured is registered.** `ortha.config.ts` omits a provider
|
|
67
|
-
* whose
|
|
68
|
-
* 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.
|
|
69
81
|
*
|
|
70
82
|
* Running two directories at once is another entry. The name is what
|
|
71
83
|
* `/api/auth/sso/<name>/start` and every `sso_identities` row refer to the
|
|
@@ -75,12 +87,28 @@ import type { OrthaConfig } from '../ortha.config';
|
|
|
75
87
|
* string, which matters because most providers match it byte for byte.
|
|
76
88
|
*/
|
|
77
89
|
export function ssoProviders(config: OrthaConfig): SsoRegistration[] {
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
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) });
|
|
81
102
|
}
|
|
82
|
-
|
|
83
|
-
|
|
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;
|
|
84
112
|
}
|
|
85
113
|
// ortha:end
|
|
86
114
|
|
|
@@ -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
|
//
|
|
@@ -197,6 +225,10 @@ export function buildPlugins(config: OrthaConfig): ServerPlugin[] {
|
|
|
197
225
|
// blocking a save or a publish. After content, whose registry and
|
|
198
226
|
// filter surface it evaluates rules through.
|
|
199
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(),
|
|
200
232
|
MediaServerPlugin({
|
|
201
233
|
// ortha:if media-local
|
|
202
234
|
provider: createLocalStorageProvider(config.plugins.media.storage),
|
|
@@ -217,6 +249,29 @@ export function buildPlugins(config: OrthaConfig): ServerPlugin[] {
|
|
|
217
249
|
// ortha:end
|
|
218
250
|
config: config.plugins.media
|
|
219
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(),
|
|
220
275
|
// Registered after workspaces (runs are workspace-scoped) and identity
|
|
221
276
|
// (runs execute as the calling user, gated on `copilot:use`). The
|
|
222
277
|
// composition root is the single place that selects a backend: the
|
|
@@ -138,8 +138,85 @@ SSO_OIDC_LABEL=
|
|
|
138
138
|
# claiming an existing account.
|
|
139
139
|
SSO_OIDC_EMAIL_VERIFIED_WHEN_ABSENT=
|
|
140
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
|
|
141
198
|
# The origin browsers reach the API on, when the admin is served from a
|
|
142
199
|
# different one. Leave empty for the usual same-origin deployment.
|
|
143
200
|
SSO_PUBLIC_BASE_URL=
|
|
144
201
|
# How long one sign-in attempt stays live, in seconds. Default 600.
|
|
145
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
|