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.
Files changed (32) hide show
  1. package/dist/cli.js +6 -267
  2. package/dist/lib/features.d.ts +42 -20
  3. package/dist/lib/features.d.ts.map +1 -1
  4. package/dist/lib/features.js +56 -22
  5. package/dist/lib/run.d.ts +22 -0
  6. package/dist/lib/run.d.ts.map +1 -0
  7. package/dist/lib/run.js +318 -0
  8. package/dist/lib/template.d.ts.map +1 -1
  9. package/dist/lib/template.js +11 -1
  10. package/package.json +1 -1
  11. package/templates/default/README.md.tmpl +38 -12
  12. package/templates/default/apps/admin/src/plugins.spec.ts +5 -1
  13. package/templates/default/apps/admin/src/plugins.ts +21 -2
  14. package/templates/default/apps/admin/tsconfig.json +2 -1
  15. package/templates/default/apps/server/config/copilot-anthropic.ts +22 -0
  16. package/templates/default/apps/server/config/copilot-openai.ts +21 -0
  17. package/templates/default/apps/server/config/copilot.ts +61 -0
  18. package/templates/default/apps/server/config/docs.ts +14 -0
  19. package/templates/default/apps/server/config/i18n.ts +16 -0
  20. package/templates/default/apps/server/config/identity.ts +129 -0
  21. package/templates/default/apps/server/config/mcp.ts +25 -0
  22. package/templates/default/apps/server/config/media-storage.ts +84 -0
  23. package/templates/default/apps/server/config/media.ts +67 -0
  24. package/templates/default/apps/server/config/sso-github.ts +42 -0
  25. package/templates/default/apps/server/config/sso-oidc.ts +34 -0
  26. package/templates/default/apps/server/config/sso-saml.ts +46 -0
  27. package/templates/default/apps/server/ortha.config.ts +60 -449
  28. package/templates/default/apps/server/src/plugins.spec.ts +7 -0
  29. package/templates/default/apps/server/src/plugins.ts +84 -19
  30. package/templates/default/apps/server/tsconfig.spec.json +25 -0
  31. package/templates/default/env.tmpl +84 -2
  32. 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-oidc
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
- * `fake` is last and unconditional. It is a shipped adapter, not test
58
- * scaffolding it needs no key and no network, so it is what makes the chat
59
- * work offline, and being last it is the default only when it is the only one.
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-oidc
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 issuer or client id is missing, and an unconfigured one is skipped here
67
- * too — it would appear on the sign-in page as a button that can only fail.
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 oidc = config.plugins.identity.ssoProviders?.oidc;
78
- if (!oidc) {
79
- return [];
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
- const { name, ...settings } = oidc;
82
- return [{ name, provider: createOidcProvider(settings) }];
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-oidc
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-oidc
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 `src/server/content/`, pass them
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: enabling a hosted provider sends workspace
79
- # content to a third party, so an operator opts in explicitly.
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
@@ -5,6 +5,9 @@
5
5
  {
6
6
  "path": "./apps/server"
7
7
  },
8
+ {
9
+ "path": "./apps/server/tsconfig.spec.json"
10
+ },
8
11
  {
9
12
  "path": "./apps/admin"
10
13
  },