create-ortha-app 0.4.2 → 0.4.3

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.
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Where uploads are written.
3
+ *
4
+ * One block per adapter, and the wizard picks exactly one — so a generated app
5
+ * has the body it chose and nothing else. Swapping backends later means
6
+ * swapping this file, the type on `AppMediaConfig.storage`, and the factory
7
+ * `src/plugins.ts` imports; nothing inside the media package changes.
8
+ */
9
+ // ortha:if media-local
10
+ import type { LocalStorageConfig } from '@orthacms/media-provider-local';
11
+
12
+ /** Local-filesystem blobs. */
13
+ export function mediaStorage(): LocalStorageConfig {
14
+ return {
15
+ // Point MEDIA_LOCAL_ROOT at a persistent volume in production: a
16
+ // container's own disk is wiped on every deploy.
17
+ rootDir: process.env['MEDIA_LOCAL_ROOT'] ?? './.storage/media'
18
+ };
19
+ }
20
+ // ortha:end
21
+ // ortha:if media-vercel-blob
22
+ import type { VercelBlobStorageConfig } from '@orthacms/media-provider-vercel-blob';
23
+ import { defined, readEnv } from '@orthacms/utils-server';
24
+ /** Vercel Blob. */
25
+ export function mediaStorage(): VercelBlobStorageConfig {
26
+ return defined({
27
+ // On Vercel the SDK reads BLOB_READ_WRITE_TOKEN itself, so this is only
28
+ // for running the app elsewhere.
29
+ token: readEnv('BLOB_READ_WRITE_TOKEN')
30
+ });
31
+ }
32
+ // ortha:end
33
+ // ortha:if media-gcs
34
+ import type { GcsStorageConfig } from '@orthacms/media-provider-gcs';
35
+ import { defined, readEnv, readFlag, requireEnv } from '@orthacms/utils-server';
36
+ /** Google Cloud Storage. */
37
+ export function mediaStorage(): GcsStorageConfig {
38
+ return defined({
39
+ bucket: requireEnv('MEDIA_GCS_BUCKET'),
40
+ // Everything else is optional: with no key file and no inline
41
+ // credentials the client uses Application Default Credentials, which is
42
+ // what a GKE or Cloud Run deployment wants.
43
+ projectId: readEnv('MEDIA_GCS_PROJECT_ID'),
44
+ keyFilename: readEnv('MEDIA_GCS_KEY_FILE'),
45
+ signWithIam: readFlag('MEDIA_GCS_SIGN_WITH_IAM', false)
46
+ });
47
+ }
48
+ // ortha:end
49
+ // ortha:if media-azure
50
+ import type { AzureStorageConfig } from '@orthacms/media-provider-azure';
51
+ import { requireEnv } from '@orthacms/utils-server';
52
+ /** Azure Blob Storage. */
53
+ export function mediaStorage(): AzureStorageConfig {
54
+ return {
55
+ container: requireEnv('MEDIA_AZURE_CONTAINER'),
56
+ connectionString: requireEnv('MEDIA_AZURE_CONNECTION_STRING')
57
+ };
58
+ }
59
+ // ortha:end
60
+ // ortha:if media-s3
61
+ import type { S3StorageConfig } from '@orthacms/media-provider-s3';
62
+ import { defined, readEnv, readFlag, requireEnv } from '@orthacms/utils-server';
63
+ /** S3 or an S3-compatible endpoint — R2, MinIO, Spaces, B2. */
64
+ export function mediaStorage(): S3StorageConfig {
65
+ const accessKeyId = readEnv('MEDIA_S3_ACCESS_KEY_ID');
66
+ const secretAccessKey = readEnv('MEDIA_S3_SECRET_ACCESS_KEY');
67
+ return defined({
68
+ bucket: requireEnv('MEDIA_S3_BUCKET'),
69
+ // `auto` is what R2 expects; AWS needs its real region.
70
+ region: process.env['MEDIA_S3_REGION'] ?? 'auto',
71
+ // Omit for AWS S3 itself; set it for R2, MinIO, Spaces, B2…
72
+ endpoint: readEnv('MEDIA_S3_ENDPOINT'),
73
+ forcePathStyle: readFlag('MEDIA_S3_FORCE_PATH_STYLE', false),
74
+ // Absent means "use the SDK's own provider chain" — an instance role,
75
+ // IRSA, a shared config file. Passing blanks instead would shadow all
76
+ // of that with credentials that cannot sign.
77
+ credentials:
78
+ accessKeyId && secretAccessKey
79
+ ? { accessKeyId, secretAccessKey }
80
+ : undefined
81
+ });
82
+ }
83
+ // ortha:end
@@ -0,0 +1,67 @@
1
+ /** Media — the storage backend, plus how downloads and uploads are bounded. */
2
+ import type { MediaPluginConfig } from '@orthacms/media-server';
3
+ // ortha:if media-local
4
+ import type { LocalStorageConfig } from '@orthacms/media-provider-local';
5
+ // ortha:end
6
+ // ortha:if media-s3
7
+ import type { S3StorageConfig } from '@orthacms/media-provider-s3';
8
+ // ortha:end
9
+ // ortha:if media-azure
10
+ import type { AzureStorageConfig } from '@orthacms/media-provider-azure';
11
+ // ortha:end
12
+ // ortha:if media-gcs
13
+ import type { GcsStorageConfig } from '@orthacms/media-provider-gcs';
14
+ // ortha:end
15
+ // ortha:if media-vercel-blob
16
+ import type { VercelBlobStorageConfig } from '@orthacms/media-provider-vercel-blob';
17
+ // ortha:end
18
+ import { readEnv, readPositiveInt } from '@orthacms/utils-server';
19
+
20
+ import { mediaStorage } from './media-storage';
21
+
22
+ /**
23
+ * Media settings, plus whatever the storage backend `src/plugins.ts`
24
+ * constructs needs. The two move together: the type below is the one exported
25
+ * by the adapter that file imports.
26
+ */
27
+ export interface AppMediaConfig extends MediaPluginConfig {
28
+ // ortha:if media-local
29
+ storage: LocalStorageConfig;
30
+ // ortha:end
31
+ // ortha:if media-s3
32
+ storage: S3StorageConfig;
33
+ // ortha:end
34
+ // ortha:if media-azure
35
+ storage: AzureStorageConfig;
36
+ // ortha:end
37
+ // ortha:if media-gcs
38
+ storage: GcsStorageConfig;
39
+ // ortha:end
40
+ // ortha:if media-vercel-blob
41
+ storage: VercelBlobStorageConfig;
42
+ // ortha:end
43
+ }
44
+
45
+ /** Media — the storage backend, plus how downloads and uploads are bounded. */
46
+ export function mediaConfig(): AppMediaConfig {
47
+ return {
48
+ storage: mediaStorage(),
49
+ // Redirect an already-authorized download straight to the storage
50
+ // backend instead of streaming it through the app. Off unless asked
51
+ // for, and only possible on a backend that can sign a URL — the plugin
52
+ // refuses the combination at boot rather than proxying while the
53
+ // operator believes otherwise.
54
+ directServe:
55
+ readEnv('MEDIA_DIRECT_SERVE') === 'signed-url'
56
+ ? 'signed-url'
57
+ : 'off',
58
+ directServeTtlSeconds: readPositiveInt(
59
+ 'MEDIA_DIRECT_SERVE_TTL_SECONDS',
60
+ 300
61
+ ),
62
+ maxUploadBytes: readPositiveInt(
63
+ 'MEDIA_MAX_UPLOAD_BYTES',
64
+ 50 * 1024 * 1024
65
+ )
66
+ };
67
+ }
@@ -0,0 +1,34 @@
1
+ // ortha:if sso-oidc
2
+ import type { OidcProviderConfig } from '@orthacms/identity-provider-oidc';
3
+ import { defined, readEnv, readFlag } from '@orthacms/utils-server';
4
+
5
+ /**
6
+ * The OIDC provider, or nothing.
7
+ *
8
+ * Present only when both values are set: an issuer with no client id becomes a
9
+ * sign-in button that can only fail, and every SSO failure looks the same, so
10
+ * whoever clicks it learns nothing.
11
+ */
12
+ export function oidcProvider(): (OidcProviderConfig & { name: string }) | undefined {
13
+ const issuer = readEnv('SSO_OIDC_ISSUER');
14
+ const clientId = readEnv('SSO_OIDC_CLIENT_ID');
15
+ if (!issuer || !clientId) {
16
+ return undefined;
17
+ }
18
+ return defined({
19
+ name: process.env['SSO_OIDC_NAME'] ?? 'oidc',
20
+ issuer,
21
+ clientId,
22
+ clientSecret: readEnv('SSO_OIDC_CLIENT_SECRET'),
23
+ label: readEnv('SSO_OIDC_LABEL'),
24
+ // The only gate on a first sign-in claiming an existing account. A
25
+ // provider that omits the claim — Entra ID, notably — links nobody
26
+ // until an operator asserts that this directory owns the addresses it
27
+ // reports.
28
+ emailVerifiedWhenAbsent: readFlag(
29
+ 'SSO_OIDC_EMAIL_VERIFIED_WHEN_ABSENT',
30
+ false
31
+ )
32
+ });
33
+ }
34
+ // ortha:end