@r8s/outline 0.2.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.
@@ -0,0 +1,213 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { render, jsx } from '@r8s/core'
3
+ import { OperatorContext, SecretContext, RoutingContext } from '@r8s/core/defaults'
4
+ import { runGuardrails, noPlaintextSecrets, validateResource } from '@r8s/core'
5
+ import { operators } from '@r8s/crds'
6
+ import type { r8sElement } from '@r8s/core'
7
+
8
+ // Outline recipe tests:
9
+ // 1. Operator declarations (deduped via OperatorContext)
10
+ // 2. Rendering: defaults, all props, gateway/ingress adaptation
11
+ // 3. Security: no plaintext credentials in rendered output
12
+ import { Outline } from '../src/index'
13
+
14
+ const openbao = { backend: 'openbao', mount: 'kv', path: 'test' }
15
+
16
+ /** Render Outline inside a Platform-like secrets backend (OpenBao). */
17
+ function renderOutline(props: Record<string, unknown>): ReturnType<typeof render> {
18
+ return render(
19
+ jsx(SecretContext.Provider, {
20
+ value: openbao as never,
21
+ children: jsx(Outline, props as never),
22
+ })
23
+ )
24
+ }
25
+
26
+ /** Wrap Outline in an OperatorContext (no secrets backend). */
27
+ function elementWithContext(ops: any[], props: Record<string, unknown>): r8sElement {
28
+ return jsx(OperatorContext.Provider, {
29
+ value: ops,
30
+ children: jsx(Outline, props as never),
31
+ })
32
+ }
33
+
34
+ const objectStorage = {
35
+ endpoint: 'https://s3.internal.example.com',
36
+ bucket: 'wiki-attachments',
37
+ credentialsSecret: 'wiki-attachments-credentials',
38
+ }
39
+
40
+ const sso = {
41
+ issuer: 'https://keycloak.example.com/realms/platform',
42
+ clientId: 'outline',
43
+ clientSecretRef: { secret: 'outline-sso', key: 'clientSecret' },
44
+ }
45
+
46
+ describe('operator declarations', () => {
47
+ it('declares the redis operator when cache is enabled', () => {
48
+ const result = renderOutline({ host: 'wiki.example.com' })
49
+ expect(result.operators.some((op) => op.name === 'redis-operator')).toBe(true)
50
+ })
51
+
52
+ it('declares the cnpg operator via the Database recipe', () => {
53
+ const result = renderOutline({ host: 'wiki.example.com' })
54
+ expect(result.operators.some((op) => op.name === 'cnpg')).toBe(true)
55
+ })
56
+
57
+ it('skips the redis operator when cache is disabled', () => {
58
+ const result = renderOutline({ host: 'wiki.example.com', cache: false })
59
+ expect(result.operators.some((op) => op.name === 'redis-operator')).toBe(false)
60
+ })
61
+
62
+ it('deduplicates operators provided via context', () => {
63
+ const result = render(
64
+ elementWithContext([operators['redis-operator'](), operators['cnpg']()], {
65
+ host: 'wiki.example.com',
66
+ secretsName: 'existing-secrets',
67
+ })
68
+ )
69
+ const names = result.operators.map((op) => op.name)
70
+ expect(names.filter((n) => n === 'redis-operator')).toHaveLength(1)
71
+ expect(names.filter((n) => n === 'cnpg')).toHaveLength(1)
72
+ })
73
+ })
74
+
75
+ describe('rendering defaults', () => {
76
+ it('renders deployment, service, ingress, database and redis', () => {
77
+ const result = renderOutline({ host: 'wiki.example.com' })
78
+ const kinds = result.resources.map((r) => r.kind)
79
+ expect(kinds).toContain('Deployment')
80
+ expect(kinds).toContain('Service')
81
+ expect(kinds).toContain('Ingress')
82
+ expect(kinds).toContain('Cluster')
83
+ expect(kinds).toContain('RedisReplication')
84
+ })
85
+
86
+ it('renders gateway resources when platform uses gateway routing', () => {
87
+ const result = render(
88
+ jsx(RoutingContext.Provider, {
89
+ value: { mode: 'gateway', gatewayClassName: 'eg' },
90
+ children: jsx(SecretContext.Provider, {
91
+ value: openbao as never,
92
+ children: jsx(Outline, { host: 'wiki.example.com' }),
93
+ }),
94
+ })
95
+ )
96
+ const kinds = result.resources.map((r) => r.kind)
97
+ expect(kinds).toContain('HTTPRoute')
98
+ })
99
+
100
+ it('renders a valid Ingress when platform uses ingress routing', () => {
101
+ const result = render(
102
+ jsx(RoutingContext.Provider, {
103
+ value: { mode: 'ingress' },
104
+ children: jsx(SecretContext.Provider, {
105
+ value: openbao as never,
106
+ children: jsx(Outline, { host: 'wiki.example.com' }),
107
+ }),
108
+ })
109
+ )
110
+ const ingress = result.resources.find((r) => r.kind === 'Ingress') as any
111
+ expect(ingress).toBeDefined()
112
+ expect(ingress.spec.rules[0].host).toBe('wiki.example.com')
113
+ })
114
+
115
+ it('passes resource validation', () => {
116
+ const result = renderOutline({
117
+ host: 'wiki.example.com',
118
+ objectStorage,
119
+ sso,
120
+ })
121
+ for (const resource of result.resources) {
122
+ expect(validateResource(resource)).toEqual([])
123
+ }
124
+ })
125
+ })
126
+
127
+ describe('rendering with all props', () => {
128
+ it('accepts the full prop surface', () => {
129
+ const result = renderOutline({
130
+ name: 'wiki',
131
+ namespace: 'docs',
132
+ version: '0.78.0',
133
+ host: 'docs.example.com',
134
+ replicas: 3,
135
+ objectStorage: { ...objectStorage, bucket: 'docs' },
136
+ sso,
137
+ resources: {
138
+ requests: { memory: '1Gi', cpu: '500m' },
139
+ limits: { memory: '4Gi', cpu: '2000m' },
140
+ },
141
+ tls: { secretName: 'docs-tls', clusterIssuer: 'letsencrypt-prod' },
142
+ })
143
+
144
+ const app = result.resources.find(
145
+ (r: any) => r.kind === 'Deployment' && r.metadata.name === 'wiki'
146
+ ) as any
147
+ expect(app).toBeDefined()
148
+ expect(app.spec.replicas).toBe(3)
149
+ expect(app.spec.template.spec.containers[0].image).toContain('0.78.0')
150
+ expect(app.spec.template.spec.containers[0].resources.limits.memory).toBe('4Gi')
151
+ })
152
+
153
+ it('wires S3 and SSO on the app when configured', () => {
154
+ const result = renderOutline({
155
+ host: 'wiki.example.com',
156
+ objectStorage,
157
+ sso,
158
+ })
159
+ const app = result.resources.find(
160
+ (r: any) => r.kind === 'Deployment' && r.metadata.name === 'outline'
161
+ ) as any
162
+ const env = app.spec.template.spec.containers[0].env
163
+ expect(env.find((e: any) => e.name === 'AWS_S3_UPLOAD_BUCKET_NAME').value).toBe(
164
+ 'wiki-attachments'
165
+ )
166
+ expect(env.find((e: any) => e.name === 'OIDC_ISSUER').value).toBe(sso.issuer)
167
+ })
168
+ })
169
+
170
+ describe('secrets handling', () => {
171
+ it('provisions app secrets through a secrets backend', () => {
172
+ const result = renderOutline({ host: 'wiki.example.com' })
173
+ const kinds = result.resources.map((r) => r.kind)
174
+ expect(kinds).toContain('OpenBaoStaticSecret')
175
+ })
176
+
177
+ it('throws when no secrets backend and no secretsName', () => {
178
+ expect(() => render(jsx(Outline, { host: 'wiki.example.com' }))).toThrow(/application secrets/)
179
+ })
180
+
181
+ it('accepts an existing secretsName without a backend', () => {
182
+ expect(() =>
183
+ render(jsx(Outline, { host: 'wiki.example.com', secretsName: 'existing-secrets' }))
184
+ ).not.toThrow()
185
+ })
186
+
187
+ it('wires credentials via secretKeyRef (never plaintext env)', () => {
188
+ const result = renderOutline({ host: 'wiki.example.com', secretsName: 'existing-secrets' })
189
+ const app = result.resources.find(
190
+ (r: any) => r.kind === 'Deployment' && r.metadata.name === 'outline'
191
+ ) as any
192
+ const env = app.spec.template.spec.containers[0].env
193
+ const secretKey = env.find((e: any) => e.name === 'SECRET_KEY')
194
+ const pgPassword = env.find((e: any) => e.name === 'PGPASSWORD')
195
+ expect(secretKey.valueFrom.secretKeyRef.name).toBe('existing-secrets')
196
+ expect(pgPassword.valueFrom.secretKeyRef.name).toBe('outline-db-credentials')
197
+ expect(secretKey.value).toBeUndefined()
198
+ expect(pgPassword.value).toBeUndefined()
199
+ })
200
+
201
+ it('renders no plaintext credentials anywhere', () => {
202
+ const result = renderOutline({
203
+ host: 'wiki.example.com',
204
+ objectStorage,
205
+ sso,
206
+ })
207
+ const { passed, errors } = runGuardrails(result.resources as any[], [noPlaintextSecrets])
208
+ if (!passed) {
209
+ console.error('Plaintext credential violations:', errors)
210
+ }
211
+ expect(passed).toBe(true)
212
+ })
213
+ })
@@ -0,0 +1,21 @@
1
+ import { Platform } from '@r8s/recipes'
2
+ import { Outline } from '@r8s/outline'
3
+
4
+ export default (
5
+ <Platform secrets={{ backend: 'openbao', mount: 'kv', path: 'apps' }}>
6
+ <Outline
7
+ name="wiki"
8
+ host="wiki.example.com"
9
+ objectStorage={{
10
+ endpoint: 'https://s3.internal.example.com',
11
+ bucket: 'wiki-attachments',
12
+ credentialsSecret: 'wiki-attachments-credentials',
13
+ }}
14
+ sso={{
15
+ issuer: 'https://keycloak.example.com/realms/platform',
16
+ clientId: 'outline',
17
+ clientSecretRef: { secret: 'outline-sso', key: 'clientSecret' },
18
+ }}
19
+ />
20
+ </Platform>
21
+ )
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@r8s/outline",
3
+ "version": "0.2.0",
4
+ "description": "Outline wiki — Postgres persistence, Redis queue, S3 attachments, OIDC SSO via Keycloak",
5
+ "license": "MIT",
6
+ "author": "Berget AI AB",
7
+ "homepage": "https://r8s.berget.ai",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/berget-ai/r8s.git",
11
+ "directory": "packages/outline"
12
+ },
13
+ "bugs": "https://github.com/berget-ai/r8s/issues",
14
+ "keywords": [
15
+ "outline",
16
+ "wiki",
17
+ "documentation",
18
+ "knowledge"
19
+ ],
20
+ "r8s": {
21
+ "category": "Knowledge & Documentation"
22
+ },
23
+ "main": "./dist/index.js",
24
+ "types": "./dist/index.d.ts",
25
+ "scripts": {
26
+ "build": "tsc --build",
27
+ "test": "vitest run",
28
+ "clean": "rm -rf dist tsconfig.tsbuildinfo"
29
+ },
30
+ "category": "Collaboration & Productivity",
31
+ "dependencies": {
32
+ "@r8s/core": "^0.2.0",
33
+ "@r8s/crds": "^0.2.0",
34
+ "@r8s/k8s-types": "^0.2.0",
35
+ "@r8s/recipes": "^0.2.0"
36
+ },
37
+ "devDependencies": {
38
+ "typescript": "^5.3.0",
39
+ "vitest": "^1.0.0"
40
+ }
41
+ }
package/src/index.tsx ADDED
@@ -0,0 +1,289 @@
1
+ import { jsx, Fragment, useContext, declareOperator } from '@r8s/core'
2
+ import { Namespace, OperatorContext, SecretContext } from '@r8s/core/defaults'
3
+ import { operators } from '@r8s/crds'
4
+ import { Database, WebService, Endpoint } from '@r8s/recipes'
5
+ import { RedisReplicationComponent } from '@r8s/crds/redis'
6
+ import type { SecretRef } from '@r8s/recipes'
7
+
8
+ export interface OutlineProps {
9
+ /** Resource name (defaults to 'outline') */
10
+ name?: string
11
+ /** Kubernetes namespace (defaults to 'default') */
12
+ namespace?: string
13
+ /** Container image tag (defaults to 'latest' — pin a version in production) */
14
+ version?: string
15
+ /** Public hostname for the wiki */
16
+ host: string
17
+ /** Storage request for the CNPG Postgres cluster (defaults to '10Gi') */
18
+ storage?: string
19
+ /** Number of replicas (Outline is stateless — scale freely) */
20
+ replicas?: number
21
+ /** Provision a Redis cluster for the queue and rate limiting (default: true) */
22
+ cache?: boolean
23
+ /**
24
+ * S3-compatible object storage for attachments (RustFS in the platform).
25
+ * Reference a bucket whose credentials live in a Secret provisioned by
26
+ * the secrets backend (keys: accessKey, secretKey) — never plaintext.
27
+ */
28
+ objectStorage?: {
29
+ /** S3 endpoint URL, e.g. https://s3.internal.example.com */
30
+ endpoint: string
31
+ /** Bucket name for attachments */
32
+ bucket: string
33
+ /** Name of the Secret holding accessKey / secretKey */
34
+ credentialsSecret: string
35
+ /** Region string for Outline's S3 client (defaults to 'us-east-1') */
36
+ region?: string
37
+ }
38
+ /**
39
+ * OIDC SSO client — register Outline as a client in Keycloak (the
40
+ * Auth recipe) and reference the client secret through the backend.
41
+ */
42
+ sso?: {
43
+ issuer: string
44
+ clientId: string
45
+ clientSecretRef: SecretRef
46
+ scopes?: string
47
+ }
48
+ /**
49
+ * Name of an existing Secret holding `secretKey` and `utilsSecret`.
50
+ * Required unless a secrets backend (openbao/vault) is configured on
51
+ * the surrounding Platform — the backend then provisions them.
52
+ */
53
+ secretsName?: string
54
+ /** Requested resources */
55
+ resources?: {
56
+ requests?: { cpu?: string; memory?: string }
57
+ limits?: { cpu?: string; memory?: string }
58
+ }
59
+ /** TLS configuration (defaults to letsencrypt-prod cluster issuer) */
60
+ tls?: {
61
+ secretName: string
62
+ clusterIssuer: string
63
+ }
64
+ }
65
+
66
+ const OPERATOR_REDIS = 'redis-operator'
67
+
68
+ /**
69
+ * Outline — team wiki with Postgres, Redis, S3 attachments and OIDC SSO.
70
+ *
71
+ * @title Outline
72
+ * @category Knowledge & Documentation
73
+ *
74
+ * Composes:
75
+ * - CNPG Postgres cluster (documents, revisions, users)
76
+ * - Redis cluster for queue + rate limiting (default on)
77
+ * - Outline Deployment + Service + Endpoint
78
+ * - S3/RustFS bucket reference for attachments
79
+ * - SECRET_KEY / UTILS_SECRET provisioned by the Platform secrets
80
+ * backend (openbao / vault), or referenced from an existing Secret
81
+ * - OIDC SSO against the Keycloak `Auth` recipe
82
+ *
83
+ * @example
84
+ * import { Platform } from '@r8s/recipes'
85
+ * import { Outline } from '@r8s/outline'
86
+ *
87
+ * export default (
88
+ * <Platform secrets={{ backend: 'openbao', mount: 'kv', path: 'apps' }}>
89
+ * <Outline
90
+ * name="wiki"
91
+ * host="wiki.example.com"
92
+ * objectStorage={{
93
+ * endpoint: 'https://s3.internal.example.com',
94
+ * bucket: 'wiki-attachments',
95
+ * credentialsSecret: 'wiki-attachments-credentials',
96
+ * }}
97
+ * sso={{
98
+ * issuer: 'https://keycloak.example.com/realms/platform',
99
+ * clientId: 'outline',
100
+ * clientSecretRef: { secret: 'outline-sso', key: 'clientSecret' },
101
+ * }}
102
+ * />
103
+ * </Platform>
104
+ * )
105
+ */
106
+ export function Outline(props: OutlineProps) {
107
+ const {
108
+ name = 'outline',
109
+ namespace: namespaceProp,
110
+ version = 'latest',
111
+ host,
112
+ storage = '10Gi',
113
+ replicas = 1,
114
+ cache = true,
115
+ objectStorage,
116
+ sso,
117
+ secretsName,
118
+ resources = {
119
+ requests: { memory: '512Mi', cpu: '250m' },
120
+ limits: { memory: '2Gi', cpu: '1000m' },
121
+ },
122
+ tls = { secretName: `${name}-tls`, clusterIssuer: 'letsencrypt-prod' },
123
+ } = props
124
+
125
+ const sharedOperators = useContext(OperatorContext)
126
+ const secretProvider = useContext(SecretContext)
127
+ // Inherit namespace from <Platform> context — mirrors recipes/database.tsx
128
+ const contextNamespace = useContext(Namespace)
129
+ const namespace =
130
+ namespaceProp ?? (contextNamespace !== 'default' ? contextNamespace : undefined) ?? 'default'
131
+ const resources_: ReturnType<typeof jsx>[] = []
132
+
133
+ const dbHost = `${name}-rw`
134
+ const dbCredentialsName = `${name}-db-credentials`
135
+ const platformSecretsName = secretsName ?? `${name}-app-secrets`
136
+
137
+ // --- App secrets (SECRET_KEY / UTILS_SECRET) ------------------------------
138
+ if (!secretsName) {
139
+ if (
140
+ !secretProvider ||
141
+ (secretProvider.backend !== 'vault' && secretProvider.backend !== 'openbao')
142
+ ) {
143
+ throw new Error(
144
+ `Outline "${name}" requires application secrets (SECRET_KEY, UTILS_SECRET).\n` +
145
+ `\n` +
146
+ `These must not be rendered as plaintext.\n` +
147
+ `\n` +
148
+ `Fix: configure a secrets backend on the Platform:\n` +
149
+ ` <Platform secrets={{ backend: 'openbao', mount: 'kv', path: 'apps' }}>\n` +
150
+ ` <Outline name="${name}" host="${host}" />\n` +
151
+ ` </Platform>\n` +
152
+ `\n` +
153
+ `Or reference a pre-created Secret (keys: secretKey, utilsSecret):\n` +
154
+ ` <Outline name="${name}" host="${host}" secretsName="${name}-app-secrets" />`
155
+ )
156
+ }
157
+
158
+ const spec = {
159
+ ...(secretProvider.backend === 'vault'
160
+ ? { vaultAuthRef: secretProvider.authRef }
161
+ : { openbaoAuthRef: secretProvider.authRef }),
162
+ mount: secretProvider.mount,
163
+ type: 'kv-v2' as const,
164
+ path: `${secretProvider.path ?? name}/${name}/secrets`,
165
+ destination: { create: true, name: platformSecretsName },
166
+ }
167
+ resources_.push(
168
+ secretProvider.backend === 'vault'
169
+ ? jsx('VaultStaticSecret', {
170
+ apiVersion: 'secrets.hashicorp.com/v1beta1',
171
+ kind: 'VaultStaticSecret',
172
+ metadata: { name: `${name}-secrets`, namespace },
173
+ spec,
174
+ })
175
+ : jsx('OpenBaoStaticSecret', {
176
+ apiVersion: 'secrets.openbao.org/v1beta1',
177
+ kind: 'OpenBaoStaticSecret',
178
+ metadata: { name: `${name}-secrets`, namespace },
179
+ spec,
180
+ })
181
+ )
182
+ }
183
+
184
+ // --- Operators -------------------------------------------------------------
185
+ if (cache && !sharedOperators.some((op) => op.name === OPERATOR_REDIS)) {
186
+ resources_.push(declareOperator(operators[OPERATOR_REDIS]()))
187
+ }
188
+
189
+ // Redis — queue + rate limiting (OT-Container-Kit operator)
190
+ if (cache) {
191
+ resources_.push(
192
+ RedisReplicationComponent({
193
+ metadata: { name: `${name}-redis`, namespace },
194
+ spec: {
195
+ clusterSize: 3,
196
+ kubernetesConfig: { image: 'redis:7.2-alpine' },
197
+ },
198
+ })
199
+ )
200
+ }
201
+
202
+ // --- Env wiring --------------------------------------------------------------
203
+ // Every credential is referenced with $(VAR) expansion or secretKeyRef —
204
+ // no plaintext in the manifest. The WebService declares secret-backed
205
+ // vars before plain env vars, so dependent expansion resolves.
206
+ const env: Record<string, string> = {
207
+ PORT: '3000',
208
+ // Deterministic parts inlined; password arrives via PGPASSWORD
209
+ DATABASE_URL: `postgresql://${name}:$(PGPASSWORD)@${dbHost}:5432/${name}`,
210
+ SECRET_KEY: '$(SECRET_KEY)',
211
+ UTILS_SECRET: '$(UTILS_SECRET)',
212
+ URL: `https://${host}`,
213
+ APP_URL: `https://${host}`,
214
+ PROXY_HEADERS_TRUSTED: 'true',
215
+ ...(cache ? { REDIS_URL: `redis://${name}-redis:6379` } : {}),
216
+ ...(objectStorage
217
+ ? {
218
+ FILE_STORAGE: 's3',
219
+ AWS_REGION: objectStorage.region ?? 'us-east-1',
220
+ AWS_S3_UPLOAD_BUCKET_URL: `${objectStorage.endpoint}/${objectStorage.bucket}`,
221
+ AWS_S3_UPLOAD_BUCKET_NAME: objectStorage.bucket,
222
+ AWS_S3_FORCE_PATH_STYLE: 'true',
223
+ AWS_S3_ACL: 'private',
224
+ }
225
+ : {}),
226
+ ...(sso
227
+ ? {
228
+ OIDC_ISSUER: sso.issuer,
229
+ OIDC_CLIENT_ID: sso.clientId,
230
+ OIDC_CLIENT_SECRET: '$(OIDC_CLIENT_SECRET)',
231
+ OIDC_SCOPES: sso.scopes ?? 'openid email profile',
232
+ OIDC_AUTH_URI: '$(OIDC_ISSUER)/protocol/openid-connect/auth',
233
+ OIDC_TOKEN_URI: '$(OIDC_ISSUER)/protocol/openid-connect/token',
234
+ OIDC_USERINFO_URI: '$(OIDC_ISSUER)/protocol/openid-connect/userinfo',
235
+ }
236
+ : {}),
237
+ }
238
+
239
+ // Credentials delivered via secretKeyRef (runtime injection)
240
+ const secrets: Record<string, SecretRef | string> = {
241
+ SECRET_KEY: { secret: platformSecretsName, key: 'secretKey' },
242
+ UTILS_SECRET: { secret: platformSecretsName, key: 'utilsSecret' },
243
+ PGPASSWORD: { secret: dbCredentialsName, key: 'password' },
244
+ ...(objectStorage
245
+ ? {
246
+ AWS_ACCESS_KEY_ID: { secret: objectStorage.credentialsSecret, key: 'accessKey' },
247
+ AWS_SECRET_ACCESS_KEY: { secret: objectStorage.credentialsSecret, key: 'secretKey' },
248
+ }
249
+ : {}),
250
+ ...(sso ? { OIDC_CLIENT_SECRET: sso.clientSecretRef } : {}),
251
+ }
252
+
253
+ // --- Database + app + endpoint ------------------------------------------------
254
+ // Database wraps the app so credentials stay consistent with the r8s
255
+ // Database recipe (CNPG dedicated cluster provisions the secret).
256
+ resources_.push(
257
+ jsx(Database, {
258
+ name,
259
+ namespace,
260
+ storage,
261
+ children: (
262
+ <WebService
263
+ name={name}
264
+ namespace={namespace}
265
+ image={`outlinewiki/outline:${version}`}
266
+ port={3000}
267
+ replicas={replicas}
268
+ resources={resources}
269
+ probes={{ liveness: { tcp: true }, readiness: { tcp: true, initialDelaySeconds: 15 } }}
270
+ env={env}
271
+ secrets={secrets}
272
+ />
273
+ ),
274
+ })
275
+ )
276
+
277
+ resources_.push(
278
+ <Endpoint
279
+ name={`${name}-endpoint`}
280
+ namespace={namespace}
281
+ host={host}
282
+ serviceName={name}
283
+ servicePort={3000}
284
+ tls={tls}
285
+ />
286
+ )
287
+
288
+ return jsx(Fragment, { children: resources_ })
289
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src",
6
+ "composite": true
7
+ },
8
+ "include": ["src/**/*"],
9
+ "references": [
10
+ { "path": "../core" },
11
+ { "path": "../k8s-types" },
12
+ { "path": "../crds" },
13
+ { "path": "../recipes" }
14
+ ]
15
+ }