@corgelabs/corge 0.0.1 → 1.0.1
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/ai/index.d.ts +3 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +3 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/schema.d.ts +83 -0
- package/dist/ai/schema.d.ts.map +1 -0
- package/dist/ai/schema.js +19 -0
- package/dist/ai/schema.js.map +1 -0
- package/dist/ai/service.d.ts +14 -0
- package/dist/ai/service.d.ts.map +1 -0
- package/dist/ai/service.js +266 -0
- package/dist/ai/service.js.map +1 -0
- package/dist/ai/utils.d.ts +10 -0
- package/dist/ai/utils.d.ts.map +1 -0
- package/dist/ai/utils.js +27 -0
- package/dist/ai/utils.js.map +1 -0
- package/dist/attachments/index.d.ts +4 -0
- package/dist/attachments/index.d.ts.map +1 -0
- package/dist/attachments/index.js +4 -0
- package/dist/attachments/index.js.map +1 -0
- package/dist/attachments/routes.d.ts +75 -0
- package/dist/attachments/routes.d.ts.map +1 -0
- package/dist/attachments/routes.js +18 -0
- package/dist/attachments/routes.js.map +1 -0
- package/dist/attachments/schema.d.ts +61 -0
- package/dist/attachments/schema.d.ts.map +1 -0
- package/dist/attachments/schema.js +30 -0
- package/dist/attachments/schema.js.map +1 -0
- package/dist/attachments/service.d.ts +27 -0
- package/dist/attachments/service.d.ts.map +1 -0
- package/dist/attachments/service.js +224 -0
- package/dist/attachments/service.js.map +1 -0
- package/dist/attachments/utils.d.ts +9 -0
- package/dist/attachments/utils.d.ts.map +1 -0
- package/dist/attachments/utils.js +80 -0
- package/dist/attachments/utils.js.map +1 -0
- package/dist/auth/index.d.ts +4 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +4 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/oidc.d.ts +25 -0
- package/dist/auth/oidc.d.ts.map +1 -0
- package/dist/auth/oidc.js +298 -0
- package/dist/auth/oidc.js.map +1 -0
- package/dist/auth/recovery.d.ts +41 -0
- package/dist/auth/recovery.d.ts.map +1 -0
- package/dist/auth/recovery.js +250 -0
- package/dist/auth/recovery.js.map +1 -0
- package/dist/auth/routes.d.ts +226 -0
- package/dist/auth/routes.d.ts.map +1 -0
- package/dist/auth/routes.js +96 -0
- package/dist/auth/routes.js.map +1 -0
- package/dist/auth/schema.d.ts +158 -0
- package/dist/auth/schema.d.ts.map +1 -0
- package/dist/auth/schema.js +82 -0
- package/dist/auth/schema.js.map +1 -0
- package/dist/auth/service.d.ts +96 -0
- package/dist/auth/service.d.ts.map +1 -0
- package/dist/auth/service.js +129 -0
- package/dist/auth/service.js.map +1 -0
- package/dist/auth/session.d.ts +51 -0
- package/dist/auth/session.d.ts.map +1 -0
- package/dist/auth/session.js +344 -0
- package/dist/auth/session.js.map +1 -0
- package/dist/auth/utils.d.ts +17 -0
- package/dist/auth/utils.d.ts.map +1 -0
- package/dist/auth/utils.js +96 -0
- package/dist/auth/utils.js.map +1 -0
- package/dist/bin/corge.js +542 -0
- package/dist/chat/context.d.ts +10 -0
- package/dist/chat/context.d.ts.map +1 -0
- package/dist/chat/context.js +221 -0
- package/dist/chat/context.js.map +1 -0
- package/dist/chat/index.d.ts +4 -0
- package/dist/chat/index.d.ts.map +1 -0
- package/dist/chat/index.js +4 -0
- package/dist/chat/index.js.map +1 -0
- package/dist/chat/routes.d.ts +160 -0
- package/dist/chat/routes.d.ts.map +1 -0
- package/dist/chat/routes.js +210 -0
- package/dist/chat/routes.js.map +1 -0
- package/dist/chat/schema.d.ts +285 -0
- package/dist/chat/schema.d.ts.map +1 -0
- package/dist/chat/schema.js +74 -0
- package/dist/chat/schema.js.map +1 -0
- package/dist/chat/service.d.ts +64 -0
- package/dist/chat/service.d.ts.map +1 -0
- package/dist/chat/service.js +279 -0
- package/dist/chat/service.js.map +1 -0
- package/dist/chat/stream.d.ts +53 -0
- package/dist/chat/stream.d.ts.map +1 -0
- package/dist/chat/stream.js +339 -0
- package/dist/chat/stream.js.map +1 -0
- package/dist/chat/utils.d.ts +61 -0
- package/dist/chat/utils.d.ts.map +1 -0
- package/dist/chat/utils.js +285 -0
- package/dist/chat/utils.js.map +1 -0
- package/dist/cli/builder.d.ts +6 -0
- package/dist/cli/builder.d.ts.map +1 -0
- package/dist/cli/builder.js +91 -0
- package/dist/cli/builder.js.map +1 -0
- package/dist/cli/deployer.d.ts +2 -0
- package/dist/cli/deployer.d.ts.map +1 -0
- package/dist/cli/deployer.js +152 -0
- package/dist/cli/deployer.js.map +1 -0
- package/dist/cli/dev-server.d.ts +2 -0
- package/dist/cli/dev-server.d.ts.map +1 -0
- package/dist/cli/dev-server.js +70 -0
- package/dist/cli/dev-server.js.map +1 -0
- package/dist/cli/env.d.ts +4 -0
- package/dist/cli/env.d.ts.map +1 -0
- package/dist/cli/env.js +68 -0
- package/dist/cli/env.js.map +1 -0
- package/dist/cli/init.d.ts +6 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +97 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/config.d.ts +262 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +212 -0
- package/dist/config.js.map +1 -0
- package/dist/db/index.d.ts +3 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +3 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/schema.d.ts +410 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +294 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/db/service.d.ts +42 -0
- package/dist/db/service.d.ts.map +1 -0
- package/dist/db/service.js +372 -0
- package/dist/db/service.js.map +1 -0
- package/dist/db/utils.d.ts +25 -0
- package/dist/db/utils.d.ts.map +1 -0
- package/dist/db/utils.js +188 -0
- package/dist/db/utils.js.map +1 -0
- package/dist/emails/index.d.ts +4 -0
- package/dist/emails/index.d.ts.map +1 -0
- package/dist/emails/index.js +4 -0
- package/dist/emails/index.js.map +1 -0
- package/dist/emails/routes.d.ts +191 -0
- package/dist/emails/routes.d.ts.map +1 -0
- package/dist/emails/routes.js +39 -0
- package/dist/emails/routes.js.map +1 -0
- package/dist/emails/schema.d.ts +237 -0
- package/dist/emails/schema.d.ts.map +1 -0
- package/dist/emails/schema.js +98 -0
- package/dist/emails/schema.js.map +1 -0
- package/dist/emails/service.d.ts +22 -0
- package/dist/emails/service.d.ts.map +1 -0
- package/dist/emails/service.js +337 -0
- package/dist/emails/service.js.map +1 -0
- package/dist/emails/utils.d.ts +49 -0
- package/dist/emails/utils.d.ts.map +1 -0
- package/dist/emails/utils.js +341 -0
- package/dist/emails/utils.js.map +1 -0
- package/dist/files/index.d.ts +4 -0
- package/dist/files/index.d.ts.map +1 -0
- package/dist/files/index.js +4 -0
- package/dist/files/index.js.map +1 -0
- package/dist/files/routes.d.ts +72 -0
- package/dist/files/routes.d.ts.map +1 -0
- package/dist/files/routes.js +38 -0
- package/dist/files/routes.js.map +1 -0
- package/dist/files/schema.d.ts +57 -0
- package/dist/files/schema.d.ts.map +1 -0
- package/dist/files/schema.js +39 -0
- package/dist/files/schema.js.map +1 -0
- package/dist/files/service.d.ts +27 -0
- package/dist/files/service.d.ts.map +1 -0
- package/dist/files/service.js +175 -0
- package/dist/files/service.js.map +1 -0
- package/dist/files/utils.d.ts +3 -0
- package/dist/files/utils.d.ts.map +1 -0
- package/dist/files/utils.js +63 -0
- package/dist/files/utils.js.map +1 -0
- package/dist/index.d.ts +875 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9919 -0
- package/dist/index.js.map +7 -0
- package/dist/payments/billing.d.ts +29 -0
- package/dist/payments/billing.d.ts.map +1 -0
- package/dist/payments/billing.js +378 -0
- package/dist/payments/billing.js.map +1 -0
- package/dist/payments/index.d.ts +4 -0
- package/dist/payments/index.d.ts.map +1 -0
- package/dist/payments/index.js +4 -0
- package/dist/payments/index.js.map +1 -0
- package/dist/payments/routes.d.ts +340 -0
- package/dist/payments/routes.d.ts.map +1 -0
- package/dist/payments/routes.js +56 -0
- package/dist/payments/routes.js.map +1 -0
- package/dist/payments/schema.d.ts +412 -0
- package/dist/payments/schema.d.ts.map +1 -0
- package/dist/payments/schema.js +260 -0
- package/dist/payments/schema.js.map +1 -0
- package/dist/payments/service.d.ts +31 -0
- package/dist/payments/service.d.ts.map +1 -0
- package/dist/payments/service.js +322 -0
- package/dist/payments/service.js.map +1 -0
- package/dist/payments/utils.d.ts +61 -0
- package/dist/payments/utils.d.ts.map +1 -0
- package/dist/payments/utils.js +369 -0
- package/dist/payments/utils.js.map +1 -0
- package/dist/router/guard.d.ts +19 -0
- package/dist/router/guard.d.ts.map +1 -0
- package/dist/router/guard.js +243 -0
- package/dist/router/guard.js.map +1 -0
- package/dist/router/index.d.ts +3 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/router/index.js +3 -0
- package/dist/router/index.js.map +1 -0
- package/dist/router/schema.d.ts +103 -0
- package/dist/router/schema.d.ts.map +1 -0
- package/dist/router/schema.js +282 -0
- package/dist/router/schema.js.map +1 -0
- package/dist/router/service.d.ts +25 -0
- package/dist/router/service.d.ts.map +1 -0
- package/dist/router/service.js +339 -0
- package/dist/router/service.js.map +1 -0
- package/dist/tasks/index.d.ts +4 -0
- package/dist/tasks/index.d.ts.map +1 -0
- package/dist/tasks/index.js +4 -0
- package/dist/tasks/index.js.map +1 -0
- package/dist/tasks/routes.d.ts +54 -0
- package/dist/tasks/routes.d.ts.map +1 -0
- package/dist/tasks/routes.js +195 -0
- package/dist/tasks/routes.js.map +1 -0
- package/dist/tasks/schema.d.ts +261 -0
- package/dist/tasks/schema.d.ts.map +1 -0
- package/dist/tasks/schema.js +35 -0
- package/dist/tasks/schema.js.map +1 -0
- package/dist/tasks/service.d.ts +32 -0
- package/dist/tasks/service.d.ts.map +1 -0
- package/dist/tasks/service.js +201 -0
- package/dist/tasks/service.js.map +1 -0
- package/dist/tasks/steps.d.ts +18 -0
- package/dist/tasks/steps.d.ts.map +1 -0
- package/dist/tasks/steps.js +365 -0
- package/dist/tasks/steps.js.map +1 -0
- package/dist/tasks/utils.d.ts +75 -0
- package/dist/tasks/utils.d.ts.map +1 -0
- package/dist/tasks/utils.js +311 -0
- package/dist/tasks/utils.js.map +1 -0
- package/dist/tasks/watcher.d.ts +13 -0
- package/dist/tasks/watcher.d.ts.map +1 -0
- package/dist/tasks/watcher.js +255 -0
- package/dist/tasks/watcher.js.map +1 -0
- package/dist/tasks/worker.d.ts +28 -0
- package/dist/tasks/worker.d.ts.map +1 -0
- package/dist/tasks/worker.js +326 -0
- package/dist/tasks/worker.js.map +1 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/schema.d.ts +79 -0
- package/dist/utils/schema.d.ts.map +1 -0
- package/dist/utils/schema.js +41 -0
- package/dist/utils/schema.js.map +1 -0
- package/dist/utils/service.d.ts +32 -0
- package/dist/utils/service.d.ts.map +1 -0
- package/dist/utils/service.js +384 -0
- package/dist/utils/service.js.map +1 -0
- package/docs/SDK_SPEC.md +1645 -0
- package/infra/firestore.base.indexes.json +138 -0
- package/infra/firestore.base.rules +152 -0
- package/infra/storage.base.rules +8 -0
- package/package.json +58 -13
- package/src/ai/index.ts +2 -0
- package/src/ai/schema.ts +64 -0
- package/src/ai/service.ts +317 -0
- package/src/ai/utils.ts +38 -0
- package/src/attachments/index.ts +3 -0
- package/src/attachments/routes.ts +28 -0
- package/src/attachments/schema.ts +45 -0
- package/src/attachments/service.ts +315 -0
- package/src/attachments/utils.ts +115 -0
- package/src/auth/index.ts +3 -0
- package/src/auth/oidc.ts +419 -0
- package/src/auth/recovery.ts +365 -0
- package/src/auth/routes.ts +142 -0
- package/src/auth/schema.ts +181 -0
- package/src/auth/service.ts +183 -0
- package/src/auth/session.ts +414 -0
- package/src/auth/utils.ts +106 -0
- package/src/chat/context.ts +310 -0
- package/src/chat/index.ts +3 -0
- package/src/chat/routes.ts +264 -0
- package/src/chat/schema.ts +295 -0
- package/src/chat/service.ts +360 -0
- package/src/chat/stream.ts +481 -0
- package/src/chat/utils.ts +370 -0
- package/src/cli/builder.ts +93 -0
- package/src/cli/deployer.ts +184 -0
- package/src/cli/dev-server.ts +77 -0
- package/src/cli/env.ts +89 -0
- package/src/cli/init.ts +121 -0
- package/src/config.ts +447 -0
- package/src/db/index.ts +2 -0
- package/src/db/schema.ts +409 -0
- package/src/db/service.ts +477 -0
- package/src/db/utils.ts +250 -0
- package/src/emails/index.ts +3 -0
- package/src/emails/routes.ts +64 -0
- package/src/emails/schema.ts +239 -0
- package/src/emails/service.ts +488 -0
- package/src/emails/utils.ts +449 -0
- package/src/files/index.ts +3 -0
- package/src/files/routes.ts +44 -0
- package/src/files/schema.ts +76 -0
- package/src/files/service.ts +238 -0
- package/src/files/utils.ts +77 -0
- package/src/index.ts +425 -0
- package/src/payments/billing.ts +488 -0
- package/src/payments/index.ts +3 -0
- package/src/payments/routes.ts +92 -0
- package/src/payments/schema.ts +401 -0
- package/src/payments/service.ts +445 -0
- package/src/payments/utils.ts +472 -0
- package/src/router/guard.ts +319 -0
- package/src/router/index.ts +2 -0
- package/src/router/schema.ts +458 -0
- package/src/router/service.ts +453 -0
- package/src/tasks/index.ts +11 -0
- package/src/tasks/routes.ts +230 -0
- package/src/tasks/schema.ts +310 -0
- package/src/tasks/service.ts +254 -0
- package/src/tasks/steps.ts +472 -0
- package/src/tasks/utils.ts +445 -0
- package/src/tasks/watcher.ts +307 -0
- package/src/tasks/worker.ts +461 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/schema.ts +69 -0
- package/src/utils/service.ts +461 -0
- package/tests/e2e.test.ts +2531 -0
- package/tsconfig.base.json +12 -0
- package/README.md +0 -2
- package/index.d.ts +0 -1
- package/index.js +0 -1
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import { FieldValue, Timestamp, type DocumentReference, type Firestore } from 'firebase-admin/firestore'
|
|
2
|
+
import { createUIMessageStreamResponse, toUIMessageStream, type LanguageModelUsage, type ModelMessage, type streamText } from 'ai'
|
|
3
|
+
import { z } from 'zod'
|
|
4
|
+
import type { ProcessedAttachment } from '../attachments'
|
|
5
|
+
import type { EntryId } from '../db'
|
|
6
|
+
import type { FileRef } from '../files'
|
|
7
|
+
import type {
|
|
8
|
+
ChatMessagePublic,
|
|
9
|
+
ChatMessageRole,
|
|
10
|
+
ContextEntry,
|
|
11
|
+
ContextItemSpec,
|
|
12
|
+
DynamicContextBlockInput,
|
|
13
|
+
RawAttachmentPart,
|
|
14
|
+
UserContext,
|
|
15
|
+
streamChatBody
|
|
16
|
+
} from './schema'
|
|
17
|
+
|
|
18
|
+
export const FORBIDDEN_TOOL_SCHEMA_KEYS = new Set([
|
|
19
|
+
'userId',
|
|
20
|
+
'chatId',
|
|
21
|
+
'role',
|
|
22
|
+
'userRole',
|
|
23
|
+
'token',
|
|
24
|
+
'idToken',
|
|
25
|
+
'apiKey',
|
|
26
|
+
'session'
|
|
27
|
+
])
|
|
28
|
+
|
|
29
|
+
export const sanitizeToolInputSchema = (schema: z.ZodType<any>): z.ZodType<any> => {
|
|
30
|
+
if (schema instanceof z.ZodObject) {
|
|
31
|
+
const shape = schema.shape as Record<string, z.ZodType<any>>
|
|
32
|
+
const keysToOmit: Record<string, true> = {}
|
|
33
|
+
let hasForbidden = false
|
|
34
|
+
for (const key of Object.keys(shape)) {
|
|
35
|
+
if (FORBIDDEN_TOOL_SCHEMA_KEYS.has(key)) {
|
|
36
|
+
keysToOmit[key] = true
|
|
37
|
+
hasForbidden = true
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (hasForbidden) {
|
|
41
|
+
return schema.omit(keysToOmit)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return schema
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const isUserContextShape = (value: unknown): value is UserContext =>
|
|
48
|
+
Boolean(value) &&
|
|
49
|
+
typeof value === 'object' &&
|
|
50
|
+
typeof (value as UserContext).state === 'object' &&
|
|
51
|
+
(value as UserContext).state !== null
|
|
52
|
+
|
|
53
|
+
export const formatAttachmentLine = (filename: string, item: ProcessedAttachment): string =>
|
|
54
|
+
'error' in item
|
|
55
|
+
? `[attached ${filename} (ref: ${item.ref}) (error: ${item.error})]\n`
|
|
56
|
+
: `[attached ${filename} (ref: ${item.ref}) (type: ${item.scan.type}) (fields: ${JSON.stringify(item.scan.fields ?? {})}) (markdown: ${item.scan.markdown ?? ''})]\n`
|
|
57
|
+
|
|
58
|
+
export const isSafePublicUrl = (raw?: string): boolean => {
|
|
59
|
+
if (!raw) return false
|
|
60
|
+
try {
|
|
61
|
+
const u = new URL(raw)
|
|
62
|
+
const host = u.hostname.toLowerCase()
|
|
63
|
+
return (
|
|
64
|
+
(u.protocol === 'https:' || u.protocol === 'http:') &&
|
|
65
|
+
!/^(\d+|0x[0-9a-f]+)$/i.test(host) &&
|
|
66
|
+
!/^(localhost|127\.|10\.|100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\.|169\.254\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.|0\.|metadata\.|\[(::1?|fc|fd|fe80:|::ffff:))/i.test(
|
|
67
|
+
host
|
|
68
|
+
) &&
|
|
69
|
+
!/(\.internal|\.local|\.nip\.io|\.sslip\.io)\.?$/i.test(host)
|
|
70
|
+
)
|
|
71
|
+
} catch {
|
|
72
|
+
return false
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const extractPart = (p: Record<string, unknown>): RawAttachmentPart | undefined => {
|
|
77
|
+
if (!p || typeof p !== 'object' || (p.type !== 'file' && !('url' in p))) return undefined
|
|
78
|
+
const url = String(p.url ?? '')
|
|
79
|
+
const dataUrl = url.startsWith('data:') ? url : undefined
|
|
80
|
+
const ref = url.match(/^\/api\/files\/([A-Za-z0-9_-]+_[a-f0-9]{24})$/)?.[1]
|
|
81
|
+
return dataUrl || ref ? { dataUrl, ref, filename: typeof p.filename === 'string' ? p.filename : 'file' } : undefined
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const extractTurnAttachmentParts = (msg: z.infer<typeof streamChatBody>['message']): RawAttachmentPart[] => {
|
|
85
|
+
const fromParts = (msg.parts ?? [])
|
|
86
|
+
.map((p) => extractPart(p))
|
|
87
|
+
.filter((p): p is RawAttachmentPart => Boolean(p))
|
|
88
|
+
const seenRefs = new Set(fromParts.map((p) => p.ref).filter(Boolean))
|
|
89
|
+
const fromRefs: RawAttachmentPart[] = (msg.attachments ?? [])
|
|
90
|
+
.filter((ref) => !seenRefs.has(ref))
|
|
91
|
+
.map((ref) => ({ ref, filename: ref }))
|
|
92
|
+
return [...fromParts, ...fromRefs]
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const extractMessageText = (msg: { content?: unknown; parts?: readonly unknown[] }): string => {
|
|
96
|
+
if (typeof msg.content === 'string' && msg.content.trim()) return msg.content.trim()
|
|
97
|
+
const items = (Array.isArray(msg.content) ? msg.content : Array.isArray(msg.parts) ? msg.parts : []) as Record<
|
|
98
|
+
string,
|
|
99
|
+
unknown
|
|
100
|
+
>[]
|
|
101
|
+
return items
|
|
102
|
+
.filter((p) => p && typeof p === 'object' && p.type === 'text' && typeof p.text === 'string')
|
|
103
|
+
.map((p) => (p.text as string).trim())
|
|
104
|
+
.filter(Boolean)
|
|
105
|
+
.join('\n')
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export const deriveTitle = (
|
|
109
|
+
msgOrList: { content?: unknown; parts?: readonly unknown[]; role?: string } | readonly Record<string, unknown>[]
|
|
110
|
+
): string => {
|
|
111
|
+
const list = Array.isArray(msgOrList) ? msgOrList : [msgOrList]
|
|
112
|
+
for (const m of list) {
|
|
113
|
+
if (m.role && m.role !== 'user') continue
|
|
114
|
+
const text = extractMessageText(m as { content?: unknown; parts?: readonly unknown[] })
|
|
115
|
+
if (text) return text.slice(0, 80)
|
|
116
|
+
}
|
|
117
|
+
return 'New Chat'
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export const extractLatestUserPrompt = (messages: readonly (Record<string, unknown> | ModelMessage)[]): string => {
|
|
121
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
122
|
+
const m = messages[i] as Record<string, unknown>
|
|
123
|
+
if (m?.role !== 'user') continue
|
|
124
|
+
const text = extractMessageText(m as { content?: unknown; parts?: readonly unknown[] })
|
|
125
|
+
if (text) return text
|
|
126
|
+
}
|
|
127
|
+
return ''
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export const formatUsage = (usage: LanguageModelUsage | null) => ({
|
|
131
|
+
prompt_tokens: usage?.inputTokens ?? 0,
|
|
132
|
+
completion_tokens: usage?.outputTokens ?? 0,
|
|
133
|
+
total_tokens: usage?.totalTokens ?? (usage?.inputTokens ?? 0) + (usage?.outputTokens ?? 0)
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
export const isContextItemVisible = (item: ContextItemSpec, audiences?: readonly string[]): boolean =>
|
|
137
|
+
!audiences || !item.audiences || item.audiences.some((a) => audiences.includes(a))
|
|
138
|
+
|
|
139
|
+
export const formatContextBlock = (entries: readonly ContextEntry[]): string => {
|
|
140
|
+
if (entries.length === 0) return ''
|
|
141
|
+
const sources = entries
|
|
142
|
+
.map(
|
|
143
|
+
(entry) =>
|
|
144
|
+
`<source path=${JSON.stringify(entry.path)} description=${JSON.stringify(entry.description)}>\n${entry.markdown.trim()}\n</source>`
|
|
145
|
+
)
|
|
146
|
+
.join('\n')
|
|
147
|
+
return `<infobase_context>\n${sources}\n</infobase_context>`
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export const buildDynamicContextBlock = (input: DynamicContextBlockInput): string => {
|
|
151
|
+
const sections: string[] = [`Current UTC Date: ${input.currentTimeIso ?? new Date().toISOString()}`]
|
|
152
|
+
|
|
153
|
+
if (input.userContext) {
|
|
154
|
+
sections.push(`<user_state>\n${JSON.stringify(input.userContext.state)}\n</user_state>`)
|
|
155
|
+
if (input.userContext.details && Object.keys(input.userContext.details).length > 0) {
|
|
156
|
+
sections.push(`<user_records>\n${JSON.stringify(input.userContext.details)}\n</user_records>`)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (input.recalledMemories && input.recalledMemories.length > 0) {
|
|
161
|
+
const items = input.recalledMemories
|
|
162
|
+
.map(
|
|
163
|
+
(m) =>
|
|
164
|
+
`<memory scope=${JSON.stringify(m.scope)} chatId=${JSON.stringify(m.chatId)} role=${JSON.stringify(m.role)}>\n${m.content.trim()}\n</memory>`
|
|
165
|
+
)
|
|
166
|
+
.join('\n')
|
|
167
|
+
sections.push(`<recalled_memory>\n${items}\n</recalled_memory>`)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (input.infobaseContextEntries && input.infobaseContextEntries.length > 0) {
|
|
171
|
+
const block = formatContextBlock(input.infobaseContextEntries)
|
|
172
|
+
if (block) sections.push(block)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (input.stagedAttachmentSummaries && input.stagedAttachmentSummaries.length > 0) {
|
|
176
|
+
sections.push(`<staged_attachments>\n${input.stagedAttachmentSummaries.join('\n').trim()}\n</staged_attachments>`)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return sections.join('\n\n')
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export const PUBLIC_MESSAGE_FIELDS = [
|
|
183
|
+
'id',
|
|
184
|
+
'chatId',
|
|
185
|
+
'userId',
|
|
186
|
+
'role',
|
|
187
|
+
'content',
|
|
188
|
+
'parts',
|
|
189
|
+
'toolInvocations',
|
|
190
|
+
'attachments',
|
|
191
|
+
'tokenCount',
|
|
192
|
+
'cancelled',
|
|
193
|
+
'createdAt'
|
|
194
|
+
] as const
|
|
195
|
+
|
|
196
|
+
export const RECALL_MESSAGE_FIELDS = ['id', 'chatId', 'role', 'content'] as const
|
|
197
|
+
|
|
198
|
+
export const mapFirestoreMessageDoc = (
|
|
199
|
+
id: string,
|
|
200
|
+
raw: Record<string, unknown>
|
|
201
|
+
): ChatMessagePublic => {
|
|
202
|
+
const createdRaw = raw.createdAt
|
|
203
|
+
const createdAt =
|
|
204
|
+
createdRaw instanceof Timestamp
|
|
205
|
+
? createdRaw.toDate()
|
|
206
|
+
: createdRaw instanceof Date
|
|
207
|
+
? createdRaw
|
|
208
|
+
: typeof createdRaw === 'string' || typeof createdRaw === 'number'
|
|
209
|
+
? new Date(createdRaw)
|
|
210
|
+
: new Date()
|
|
211
|
+
|
|
212
|
+
const role: ChatMessageRole =
|
|
213
|
+
raw.role === 'assistant' || raw.role === 'system' || raw.role === 'tool' ? raw.role : 'user'
|
|
214
|
+
const content = typeof raw.content === 'string' ? raw.content : ''
|
|
215
|
+
const parts = Array.isArray(raw.parts) ? raw.parts : content ? [{ type: 'text', text: content }] : []
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
id,
|
|
219
|
+
chatId: typeof raw.chatId === 'string' ? raw.chatId : '',
|
|
220
|
+
userId: typeof raw.userId === 'string' ? raw.userId : '',
|
|
221
|
+
role,
|
|
222
|
+
content,
|
|
223
|
+
parts,
|
|
224
|
+
...(Array.isArray(raw.toolInvocations) ? { toolInvocations: raw.toolInvocations } : {}),
|
|
225
|
+
...(Array.isArray(raw.attachments) ? { attachments: raw.attachments as string[] } : {}),
|
|
226
|
+
...(typeof raw.tokenCount === 'number' ? { tokenCount: raw.tokenCount } : {}),
|
|
227
|
+
...(typeof raw.cancelled === 'boolean' ? { cancelled: raw.cancelled } : {}),
|
|
228
|
+
createdAt
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export const toModelMessageFromDoc = (doc: ChatMessagePublic): ModelMessage | undefined => {
|
|
233
|
+
const text = doc.content.trim()
|
|
234
|
+
if (!text) return undefined
|
|
235
|
+
if (doc.role === 'assistant') {
|
|
236
|
+
return { role: 'assistant', content: [{ type: 'text', text }] }
|
|
237
|
+
}
|
|
238
|
+
if (doc.role === 'user') {
|
|
239
|
+
return { role: 'user', content: [{ type: 'text', text }] }
|
|
240
|
+
}
|
|
241
|
+
return undefined
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export const extractToolInvocations = (messages: readonly { content: unknown }[]): unknown[] => {
|
|
245
|
+
const toolInvocations: unknown[] = []
|
|
246
|
+
for (const m of messages) {
|
|
247
|
+
if (!Array.isArray(m.content)) continue
|
|
248
|
+
for (const part of m.content) {
|
|
249
|
+
if (part && typeof part === 'object' && 'type' in part && (part.type === 'tool-call' || part.type === 'tool-result')) {
|
|
250
|
+
toolInvocations.push(part)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return toolInvocations
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export const buildUIMessageResponse = (
|
|
258
|
+
turn: ReturnType<typeof streamText>,
|
|
259
|
+
chatId: string,
|
|
260
|
+
generationId: string,
|
|
261
|
+
completionPromise: Promise<void>,
|
|
262
|
+
onDelta: (delta: string) => void
|
|
263
|
+
): Response => {
|
|
264
|
+
const baseRes = createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: turn.stream }) })
|
|
265
|
+
const headers = new Headers(baseRes.headers)
|
|
266
|
+
headers.set('x-chat-id', chatId)
|
|
267
|
+
headers.set('x-generation-id', generationId)
|
|
268
|
+
|
|
269
|
+
const decoder = new TextDecoder()
|
|
270
|
+
const [clientBranch, monitorBranch] = baseRes.body!.tee()
|
|
271
|
+
|
|
272
|
+
void (async () => {
|
|
273
|
+
const reader = monitorBranch.getReader()
|
|
274
|
+
try {
|
|
275
|
+
while (true) {
|
|
276
|
+
const { done, value } = await reader.read()
|
|
277
|
+
if (done) break
|
|
278
|
+
for (const line of decoder.decode(value, { stream: true }).split('\n')) {
|
|
279
|
+
if (!line.startsWith('data: ')) continue
|
|
280
|
+
try {
|
|
281
|
+
const payload = JSON.parse(line.slice(6)) as { type?: string; delta?: string; textDelta?: string }
|
|
282
|
+
if (payload.type === 'text-delta') onDelta(payload.delta ?? payload.textDelta ?? '')
|
|
283
|
+
} catch {}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
} catch {
|
|
287
|
+
} finally {
|
|
288
|
+
reader.releaseLock()
|
|
289
|
+
}
|
|
290
|
+
})()
|
|
291
|
+
|
|
292
|
+
const syncedClientStream = clientBranch.pipeThrough(
|
|
293
|
+
new TransformStream<Uint8Array, Uint8Array>({
|
|
294
|
+
async flush() {
|
|
295
|
+
await completionPromise.catch(() => {})
|
|
296
|
+
}
|
|
297
|
+
})
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
return new Response(syncedClientStream, { status: baseRes.status, headers })
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export const buildCompletedTurnWriteBatch = (
|
|
304
|
+
db: Firestore,
|
|
305
|
+
opts: {
|
|
306
|
+
chatDocRef: DocumentReference
|
|
307
|
+
chatId: string
|
|
308
|
+
userId: EntryId
|
|
309
|
+
resolvedAgentId: string
|
|
310
|
+
title: string
|
|
311
|
+
selectedModel: string
|
|
312
|
+
userMsgId: string
|
|
313
|
+
assistantMsgId: string
|
|
314
|
+
userCreatedAtMs: number
|
|
315
|
+
finalUserText: string
|
|
316
|
+
userPartsForStorage: unknown[]
|
|
317
|
+
stagedRefs: FileRef[]
|
|
318
|
+
finalAssistantText: string
|
|
319
|
+
toolInvocations: unknown[]
|
|
320
|
+
totalTokens?: number
|
|
321
|
+
cancelled: boolean
|
|
322
|
+
userVector: number[] | null
|
|
323
|
+
assistantVector: number[] | null
|
|
324
|
+
}
|
|
325
|
+
) => {
|
|
326
|
+
const messagesCol = opts.chatDocRef.collection('messages')
|
|
327
|
+
const batch = db.batch()
|
|
328
|
+
batch.set(messagesCol.doc(opts.userMsgId), {
|
|
329
|
+
id: opts.userMsgId,
|
|
330
|
+
chatId: opts.chatId,
|
|
331
|
+
userId: opts.userId,
|
|
332
|
+
role: 'user',
|
|
333
|
+
content: opts.finalUserText,
|
|
334
|
+
parts: opts.userPartsForStorage,
|
|
335
|
+
...(opts.stagedRefs.length > 0 ? { attachments: opts.stagedRefs } : {}),
|
|
336
|
+
createdAt: Timestamp.fromMillis(opts.userCreatedAtMs),
|
|
337
|
+
...(opts.userVector ? { embedding: FieldValue.vector(opts.userVector) } : {})
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
batch.set(messagesCol.doc(opts.assistantMsgId), {
|
|
341
|
+
id: opts.assistantMsgId,
|
|
342
|
+
chatId: opts.chatId,
|
|
343
|
+
userId: opts.userId,
|
|
344
|
+
role: 'assistant',
|
|
345
|
+
content: opts.finalAssistantText,
|
|
346
|
+
parts: opts.finalAssistantText ? [{ type: 'text', text: opts.finalAssistantText }] : [],
|
|
347
|
+
...(opts.toolInvocations.length > 0 ? { toolInvocations: opts.toolInvocations } : {}),
|
|
348
|
+
...(opts.totalTokens !== undefined ? { tokenCount: opts.totalTokens } : {}),
|
|
349
|
+
cancelled: opts.cancelled,
|
|
350
|
+
createdAt: Timestamp.fromMillis(Math.max(opts.userCreatedAtMs + 1, Date.now())),
|
|
351
|
+
...(opts.assistantVector ? { embedding: FieldValue.vector(opts.assistantVector) } : {})
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
batch.set(
|
|
355
|
+
opts.chatDocRef,
|
|
356
|
+
{
|
|
357
|
+
id: opts.chatId,
|
|
358
|
+
userId: opts.userId,
|
|
359
|
+
agentId: opts.resolvedAgentId,
|
|
360
|
+
title: opts.title,
|
|
361
|
+
model: opts.selectedModel,
|
|
362
|
+
deleted: false,
|
|
363
|
+
activeGenerationId: null,
|
|
364
|
+
messageCount: FieldValue.increment(2),
|
|
365
|
+
updatedAt: FieldValue.serverTimestamp()
|
|
366
|
+
},
|
|
367
|
+
{ merge: true }
|
|
368
|
+
)
|
|
369
|
+
return batch
|
|
370
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { createRequire } from 'node:module'
|
|
3
|
+
import { dirname, join, resolve } from 'node:path'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import { build, type Plugin } from 'esbuild'
|
|
6
|
+
|
|
7
|
+
export const getCorgeRootDir = (): string => {
|
|
8
|
+
const currentFile = fileURLToPath(import.meta.url)
|
|
9
|
+
const currentDir = dirname(currentFile)
|
|
10
|
+
for (const candidate of [
|
|
11
|
+
resolve(currentDir, '../..'),
|
|
12
|
+
resolve(currentDir, '..')
|
|
13
|
+
]) {
|
|
14
|
+
if (existsSync(join(candidate, 'package.json')) && existsSync(join(candidate, 'tsconfig.base.json'))) {
|
|
15
|
+
return candidate
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return resolve(currentDir, '../..')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const resolveCorgeEntrypoint = (cwd = process.cwd()): string => {
|
|
22
|
+
try {
|
|
23
|
+
const req = createRequire(join(cwd, 'package.json'))
|
|
24
|
+
return req.resolve('@corgelabs/corge')
|
|
25
|
+
} catch {
|
|
26
|
+
const root = getCorgeRootDir()
|
|
27
|
+
const distEntry = join(root, 'dist/index.js')
|
|
28
|
+
return existsSync(distEntry) ? distEntry : join(root, 'src/index.ts')
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const resolveWithExtensions = (basePath: string): string | undefined => {
|
|
33
|
+
for (const candidate of [
|
|
34
|
+
basePath,
|
|
35
|
+
`${basePath}.ts`,
|
|
36
|
+
`${basePath}.js`,
|
|
37
|
+
join(basePath, 'index.ts'),
|
|
38
|
+
join(basePath, 'index.js')
|
|
39
|
+
]) {
|
|
40
|
+
if (existsSync(candidate)) return candidate
|
|
41
|
+
}
|
|
42
|
+
return undefined
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const corgeAliasPlugin = (cwd = process.cwd()): Plugin => ({
|
|
46
|
+
name: 'corge-alias',
|
|
47
|
+
setup(b) {
|
|
48
|
+
b.onResolve({ filter: /^(@core|@corgelabs\/corge)$/ }, () => ({
|
|
49
|
+
path: resolveCorgeEntrypoint(cwd),
|
|
50
|
+
external: false
|
|
51
|
+
}))
|
|
52
|
+
|
|
53
|
+
b.onResolve({ filter: /^@(config|routes|services|utils)\/(.+)$/ }, (args) => {
|
|
54
|
+
const match = /^@(config|routes|services|utils)\/(.+)$/.exec(args.path)
|
|
55
|
+
if (!match) return undefined
|
|
56
|
+
const [, folder, rest] = match
|
|
57
|
+
const target = resolveWithExtensions(join(cwd, 'src', folder!, rest!))
|
|
58
|
+
if (target) return { path: target, external: false }
|
|
59
|
+
return undefined
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
b.onResolve({ filter: /^@app-entry$/ }, () => {
|
|
63
|
+
const target = resolveWithExtensions(join(cwd, 'src/index'))
|
|
64
|
+
if (target) return { path: target, external: false }
|
|
65
|
+
return { path: 'corge-fallback-app', namespace: 'corge-virtual' }
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
b.onLoad({ filter: /.*/, namespace: 'corge-virtual' }, () => ({
|
|
69
|
+
contents: [
|
|
70
|
+
"import { core } from '@core'",
|
|
71
|
+
'export const { devApp, app, functions } = core.createApp({})'
|
|
72
|
+
].join('\n'),
|
|
73
|
+
loader: 'ts',
|
|
74
|
+
resolveDir: cwd
|
|
75
|
+
}))
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
export const buildConsumerApp = async (cwd = process.cwd()): Promise<void> => {
|
|
80
|
+
await build({
|
|
81
|
+
absWorkingDir: cwd,
|
|
82
|
+
entryPoints: ['src/index.ts'],
|
|
83
|
+
bundle: true,
|
|
84
|
+
platform: 'node',
|
|
85
|
+
target: 'node24',
|
|
86
|
+
format: 'cjs',
|
|
87
|
+
packages: 'external',
|
|
88
|
+
plugins: [corgeAliasPlugin(cwd)],
|
|
89
|
+
footer: { js: 'module.exports = functions;' },
|
|
90
|
+
outfile: 'dist/index.cjs',
|
|
91
|
+
sourcemap: true
|
|
92
|
+
})
|
|
93
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { execSync, spawnSync } from 'node:child_process'
|
|
2
|
+
import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
import { buildConsumerApp, getCorgeRootDir } from './builder'
|
|
5
|
+
|
|
6
|
+
interface FirestoreIndexesFile {
|
|
7
|
+
indexes?: unknown[]
|
|
8
|
+
fieldOverrides?: unknown[]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const mergeFirestoreIndexes = (base: FirestoreIndexesFile, custom: FirestoreIndexesFile): FirestoreIndexesFile => {
|
|
12
|
+
const seenIndexes = new Set<string>()
|
|
13
|
+
const mergedIndexes: unknown[] = []
|
|
14
|
+
for (const item of [...(base.indexes ?? []), ...(custom.indexes ?? [])]) {
|
|
15
|
+
const key = JSON.stringify(item)
|
|
16
|
+
if (!seenIndexes.has(key)) {
|
|
17
|
+
seenIndexes.add(key)
|
|
18
|
+
mergedIndexes.push(item)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const seenOverrides = new Set<string>()
|
|
23
|
+
const mergedOverrides: unknown[] = []
|
|
24
|
+
for (const item of [...(base.fieldOverrides ?? []), ...(custom.fieldOverrides ?? [])]) {
|
|
25
|
+
const key = JSON.stringify(item)
|
|
26
|
+
if (!seenOverrides.has(key)) {
|
|
27
|
+
seenOverrides.add(key)
|
|
28
|
+
mergedOverrides.push(item)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
indexes: mergedIndexes,
|
|
34
|
+
fieldOverrides: mergedOverrides
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const deployConsumerApp = async (
|
|
39
|
+
target: 'prod' | 'dev' | undefined,
|
|
40
|
+
cwd = process.cwd()
|
|
41
|
+
): Promise<void> => {
|
|
42
|
+
try {
|
|
43
|
+
process.loadEnvFile(join(cwd, '.env'))
|
|
44
|
+
} catch {}
|
|
45
|
+
|
|
46
|
+
if (target !== 'prod' && target !== 'dev') {
|
|
47
|
+
console.error('Usage: corge deploy <prod|dev>')
|
|
48
|
+
process.exit(1)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const requiredBranch = target === 'prod' ? 'main' : 'dev'
|
|
52
|
+
const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', { cwd, encoding: 'utf8' }).trim()
|
|
53
|
+
|
|
54
|
+
if (currentBranch !== requiredBranch) {
|
|
55
|
+
console.error(
|
|
56
|
+
`Deploy blocked: "deploy ${target}" can only run on branch "${requiredBranch}" (current branch: "${currentBranch}").`
|
|
57
|
+
)
|
|
58
|
+
process.exit(1)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const project = process.env.GCP_PROJECT
|
|
62
|
+
if (!project) {
|
|
63
|
+
console.error('Missing GCP_PROJECT in .env')
|
|
64
|
+
process.exit(1)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
await buildConsumerApp(cwd)
|
|
68
|
+
|
|
69
|
+
const corgeRoot = getCorgeRootDir()
|
|
70
|
+
const firebaseJsonPath = join(cwd, 'firebase.json')
|
|
71
|
+
const pkgJsonPath = join(cwd, 'package.json')
|
|
72
|
+
const firestoreRulesPath = join(cwd, 'firestore.rules')
|
|
73
|
+
const firestoreIndexesPath = join(cwd, 'firestore.indexes.json')
|
|
74
|
+
const storageRulesPath = join(cwd, 'storage.rules')
|
|
75
|
+
|
|
76
|
+
const rawFirebaseJson = readFileSync(firebaseJsonPath, 'utf8')
|
|
77
|
+
const rawPkgJson = readFileSync(pkgJsonPath, 'utf8')
|
|
78
|
+
const hadFirestoreRules = existsSync(firestoreRulesPath)
|
|
79
|
+
const rawFirestoreRules = hadFirestoreRules ? readFileSync(firestoreRulesPath, 'utf8') : null
|
|
80
|
+
const hadFirestoreIndexes = existsSync(firestoreIndexesPath)
|
|
81
|
+
const rawFirestoreIndexes = hadFirestoreIndexes ? readFileSync(firestoreIndexesPath, 'utf8') : null
|
|
82
|
+
const hadStorageRules = existsSync(storageRulesPath)
|
|
83
|
+
const rawStorageRules = hadStorageRules ? readFileSync(storageRulesPath, 'utf8') : null
|
|
84
|
+
|
|
85
|
+
const restoreFiles = () => {
|
|
86
|
+
try {
|
|
87
|
+
writeFileSync(firebaseJsonPath, rawFirebaseJson, 'utf8')
|
|
88
|
+
} catch {}
|
|
89
|
+
try {
|
|
90
|
+
writeFileSync(pkgJsonPath, rawPkgJson, 'utf8')
|
|
91
|
+
} catch {}
|
|
92
|
+
try {
|
|
93
|
+
if (hadFirestoreRules && rawFirestoreRules !== null) {
|
|
94
|
+
writeFileSync(firestoreRulesPath, rawFirestoreRules, 'utf8')
|
|
95
|
+
} else if (!hadFirestoreRules && existsSync(firestoreRulesPath)) {
|
|
96
|
+
unlinkSync(firestoreRulesPath)
|
|
97
|
+
}
|
|
98
|
+
} catch {}
|
|
99
|
+
try {
|
|
100
|
+
if (hadFirestoreIndexes && rawFirestoreIndexes !== null) {
|
|
101
|
+
writeFileSync(firestoreIndexesPath, rawFirestoreIndexes, 'utf8')
|
|
102
|
+
} else if (!hadFirestoreIndexes && existsSync(firestoreIndexesPath)) {
|
|
103
|
+
unlinkSync(firestoreIndexesPath)
|
|
104
|
+
}
|
|
105
|
+
} catch {}
|
|
106
|
+
try {
|
|
107
|
+
if (hadStorageRules && rawStorageRules !== null) {
|
|
108
|
+
writeFileSync(storageRulesPath, rawStorageRules, 'utf8')
|
|
109
|
+
} else if (!hadStorageRules && existsSync(storageRulesPath)) {
|
|
110
|
+
unlinkSync(storageRulesPath)
|
|
111
|
+
}
|
|
112
|
+
} catch {}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
process.once('SIGINT', () => {
|
|
116
|
+
restoreFiles()
|
|
117
|
+
process.exit(130)
|
|
118
|
+
})
|
|
119
|
+
process.once('SIGTERM', () => {
|
|
120
|
+
restoreFiles()
|
|
121
|
+
process.exit(143)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
// 1. Provision or merge base Firestore/Storage rules & composite indexes from @corgelabs/corge/infra
|
|
126
|
+
const baseFirestoreRules = readFileSync(join(corgeRoot, 'infra/firestore.base.rules'), 'utf8')
|
|
127
|
+
const baseStorageRules = readFileSync(join(corgeRoot, 'infra/storage.base.rules'), 'utf8')
|
|
128
|
+
const baseIndexes = JSON.parse(
|
|
129
|
+
readFileSync(join(corgeRoot, 'infra/firestore.base.indexes.json'), 'utf8')
|
|
130
|
+
) as FirestoreIndexesFile
|
|
131
|
+
|
|
132
|
+
if (!hadFirestoreRules) {
|
|
133
|
+
writeFileSync(firestoreRulesPath, baseFirestoreRules, 'utf8')
|
|
134
|
+
}
|
|
135
|
+
if (!hadStorageRules) {
|
|
136
|
+
writeFileSync(storageRulesPath, baseStorageRules, 'utf8')
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const customIndexes = hadFirestoreIndexes && rawFirestoreIndexes
|
|
140
|
+
? (JSON.parse(rawFirestoreIndexes) as FirestoreIndexesFile)
|
|
141
|
+
: {}
|
|
142
|
+
const mergedIndexes = mergeFirestoreIndexes(baseIndexes, customIndexes)
|
|
143
|
+
writeFileSync(firestoreIndexesPath, JSON.stringify(mergedIndexes, null, 2) + '\n', 'utf8')
|
|
144
|
+
|
|
145
|
+
// 2. Rewrite firebase.json for target environment
|
|
146
|
+
const cfg = JSON.parse(rawFirebaseJson) as Record<string, unknown>
|
|
147
|
+
const prodDb = { database: project, rules: 'firestore.rules', indexes: 'firestore.indexes.json' }
|
|
148
|
+
const devDb = { database: `${project}-dev`, rules: 'firestore.rules', indexes: 'firestore.indexes.json' }
|
|
149
|
+
const prodBucket = { bucket: `${project}.firebasestorage.app`, rules: 'storage.rules' }
|
|
150
|
+
const devBucket = { bucket: `${project}-dev`, rules: 'storage.rules' }
|
|
151
|
+
|
|
152
|
+
cfg.firestore = target === 'prod' ? [prodDb] : [devDb]
|
|
153
|
+
cfg.storage = target === 'prod' ? [prodBucket] : [devBucket]
|
|
154
|
+
writeFileSync(firebaseJsonPath, JSON.stringify(cfg, null, 2) + '\n', 'utf8')
|
|
155
|
+
|
|
156
|
+
// 3. Expand @corgelabs/corge runtime dependencies into consumer package.json so Cloud Build needs zero npm auth
|
|
157
|
+
const corgePkg = JSON.parse(readFileSync(join(corgeRoot, 'package.json'), 'utf8')) as {
|
|
158
|
+
dependencies?: Record<string, string>
|
|
159
|
+
}
|
|
160
|
+
const consumerPkg = JSON.parse(rawPkgJson) as {
|
|
161
|
+
dependencies?: Record<string, string>
|
|
162
|
+
[k: string]: unknown
|
|
163
|
+
}
|
|
164
|
+
const expandedDeps: Record<string, string> = { ...(corgePkg.dependencies ?? {}), ...(consumerPkg.dependencies ?? {}) }
|
|
165
|
+
delete expandedDeps['@corgelabs/corge']
|
|
166
|
+
delete expandedDeps.esbuild
|
|
167
|
+
consumerPkg.dependencies = expandedDeps
|
|
168
|
+
writeFileSync(pkgJsonPath, JSON.stringify(consumerPkg, null, 2) + '\n', 'utf8')
|
|
169
|
+
|
|
170
|
+
const onlyTargets =
|
|
171
|
+
target === 'prod'
|
|
172
|
+
? `functions:${project},functions:${project}-tasks,firestore:${project},storage`
|
|
173
|
+
: `functions:${project}-dev,functions:${project}-tasks-dev,firestore:${project}-dev,storage`
|
|
174
|
+
|
|
175
|
+
const deployRes = spawnSync(
|
|
176
|
+
'npx',
|
|
177
|
+
['-y', 'firebase-tools@latest', 'deploy', '-P', project, '--only', onlyTargets],
|
|
178
|
+
{ cwd, stdio: 'inherit' }
|
|
179
|
+
)
|
|
180
|
+
process.exitCode = deployRes.status ?? 1
|
|
181
|
+
} finally {
|
|
182
|
+
restoreFiles()
|
|
183
|
+
}
|
|
184
|
+
}
|