@edgedev/firebase 2.1.79 → 2.2.80
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/package.json +1 -1
- package/src/.env.dev +25 -1
- package/src/.env.prod +25 -1
- package/src/cms.js +2358 -0
- package/src/functions.js +1 -0
- package/src/index.js +2 -1
- package/src/kv/kvClient.js +146 -0
- package/src/kv/kvMirror.js +212 -0
- package/src/package.json +40 -34
- package/src/postinstall.sh +76 -39
package/src/functions.js
CHANGED
package/src/index.js
CHANGED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// kvClient.js
|
|
2
|
+
const axios = require('axios')
|
|
3
|
+
const FormData = require('form-data')
|
|
4
|
+
|
|
5
|
+
const accountId = process.env.CF_ACCOUNT_ID
|
|
6
|
+
const namespaceId = process.env.CLOUDFLARE_NAMESPACE_ID
|
|
7
|
+
const apiKey = process.env.CLOUDFLARE_API_KEY
|
|
8
|
+
|
|
9
|
+
if (!accountId || !namespaceId || !apiKey) {
|
|
10
|
+
console.warn('[kvClient] Missing CF env vars: CF_ACCOUNT_ID, CLOUDFLARE_NAMESPACE_ID, CLOUDFLARE_API_KEY')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const base = `https://api.cloudflare.com/client/v4/accounts/${accountId}/storage/kv/namespaces/${namespaceId}`
|
|
14
|
+
|
|
15
|
+
function parseJsonIfNeeded(body) {
|
|
16
|
+
if (body === null || body === undefined) {
|
|
17
|
+
return null
|
|
18
|
+
}
|
|
19
|
+
if (typeof body === 'object') {
|
|
20
|
+
return body
|
|
21
|
+
}
|
|
22
|
+
if (typeof body === 'string' && body.trim().length > 0) {
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(body)
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
console.warn('[kvClient] Failed to parse JSON body:', body.slice(0, 200))
|
|
28
|
+
return null
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function withWriteParams(url, opts) {
|
|
35
|
+
if (!opts) {
|
|
36
|
+
return url
|
|
37
|
+
}
|
|
38
|
+
const u = new URL(url)
|
|
39
|
+
if (opts.expiration_ttl != null) {
|
|
40
|
+
u.searchParams.set('expiration_ttl', String(opts.expiration_ttl))
|
|
41
|
+
}
|
|
42
|
+
if (opts.expiration != null) {
|
|
43
|
+
u.searchParams.set('expiration', String(opts.expiration))
|
|
44
|
+
}
|
|
45
|
+
return u.toString()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* put(key, value, opts?)
|
|
50
|
+
* opts: { metadata?: object, expiration?: number, expiration_ttl?: number }
|
|
51
|
+
*/
|
|
52
|
+
async function put(key, value, opts = undefined) {
|
|
53
|
+
const url0 = `${base}/values/${encodeURIComponent(key)}`
|
|
54
|
+
const url = withWriteParams(url0, opts)
|
|
55
|
+
const headers = { Authorization: `Bearer ${apiKey}` }
|
|
56
|
+
|
|
57
|
+
if (opts && opts.metadata) {
|
|
58
|
+
const form = new FormData()
|
|
59
|
+
const val = value instanceof Buffer ? value : (typeof value === 'string' ? value : JSON.stringify(value))
|
|
60
|
+
form.append('value', val)
|
|
61
|
+
form.append('metadata', JSON.stringify(opts.metadata))
|
|
62
|
+
const formHeaders = form.getHeaders()
|
|
63
|
+
Object.assign(headers, formHeaders)
|
|
64
|
+
const res = await axios.put(url, form, { headers })
|
|
65
|
+
if (res.status !== 200) {
|
|
66
|
+
throw new Error(`KV put failed: ${res.status} ${res.statusText}`)
|
|
67
|
+
}
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let data = value
|
|
72
|
+
if (typeof value === 'object' && !(value instanceof Buffer)) {
|
|
73
|
+
data = JSON.stringify(value)
|
|
74
|
+
headers['Content-Type'] = 'application/json'
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
headers['Content-Type'] = 'text/plain'
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const res = await axios.put(url, data, { headers })
|
|
81
|
+
if (res.status !== 200) {
|
|
82
|
+
throw new Error(`KV put failed: ${res.status} ${res.statusText}`)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function putJson(key, obj, opts = undefined) {
|
|
87
|
+
return put(key, JSON.stringify(obj), opts)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Convenience for writing only metadata on an index key.
|
|
92
|
+
* Stores a tiny value ('1') and attaches the real JSON to metadata.
|
|
93
|
+
*/
|
|
94
|
+
async function putIndexMeta(key, metadata, opts = undefined) {
|
|
95
|
+
const meta = metadata && typeof metadata === 'object' ? metadata : {}
|
|
96
|
+
return put(key, '1', { ...(opts || {}), metadata: meta })
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function get(key, type = 'text') {
|
|
100
|
+
const url = `${base}/values/${encodeURIComponent(key)}`
|
|
101
|
+
try {
|
|
102
|
+
const res = await axios.get(url, {
|
|
103
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
104
|
+
responseType: 'text',
|
|
105
|
+
transformResponse: [x => x],
|
|
106
|
+
})
|
|
107
|
+
if (type === 'json') {
|
|
108
|
+
return parseJsonIfNeeded(res.data)
|
|
109
|
+
}
|
|
110
|
+
return res.data
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
if (err?.response?.status === 404) {
|
|
114
|
+
return null
|
|
115
|
+
}
|
|
116
|
+
throw new Error(`KV get failed for ${key}: ${err.message}`)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function del(key) {
|
|
121
|
+
const url = `${base}/values/${encodeURIComponent(key)}`
|
|
122
|
+
await Promise.allSettled([
|
|
123
|
+
axios.delete(url, { headers: { Authorization: `Bearer ${apiKey}` } }),
|
|
124
|
+
])
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* List keys with optional prefix/limit/cursor.
|
|
129
|
+
* Returns { result, success, errors, messages } where result[i] has { name, expiration, metadata }.
|
|
130
|
+
*/
|
|
131
|
+
async function listKeys({ prefix = '', limit = 1000, cursor = '' } = {}) {
|
|
132
|
+
const u = new URL(`${base}/keys`)
|
|
133
|
+
if (prefix) {
|
|
134
|
+
u.searchParams.set('prefix', prefix)
|
|
135
|
+
}
|
|
136
|
+
if (limit != null) {
|
|
137
|
+
u.searchParams.set('limit', String(limit))
|
|
138
|
+
}
|
|
139
|
+
if (cursor) {
|
|
140
|
+
u.searchParams.set('cursor', cursor)
|
|
141
|
+
}
|
|
142
|
+
const res = await axios.get(u.toString(), { headers: { Authorization: `Bearer ${apiKey}` } })
|
|
143
|
+
return res.data
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
module.exports = { put, putJson, putIndexMeta, get, del, listKeys }
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// kvMirror.js
|
|
2
|
+
// Generic Firestore→Cloudflare KV mirroring helper.
|
|
3
|
+
// - Writes a canonical KV key per Firestore doc (value = serialized data).
|
|
4
|
+
// - Optionally writes index keys that carry JSON in **metadata** (value = '1').
|
|
5
|
+
// - Index-key metadata always includes { canonical: <canonicalKey> }.
|
|
6
|
+
// - Keeps a small manifest per canonical key to clean up all index keys on delete.
|
|
7
|
+
|
|
8
|
+
const { onDocumentWritten } = require('../config.js')
|
|
9
|
+
const kv = require('./kvClient')
|
|
10
|
+
|
|
11
|
+
function json(x) {
|
|
12
|
+
return JSON.stringify(x)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function slugIndexValue(value, maxLength = 80) {
|
|
16
|
+
return String(value ?? '')
|
|
17
|
+
.trim()
|
|
18
|
+
.toLowerCase()
|
|
19
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
20
|
+
.replace(/^-+|-+$/g, '')
|
|
21
|
+
.slice(0, maxLength)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function setDiff(oldArr = [], newArr = []) {
|
|
25
|
+
const A = new Set(oldArr)
|
|
26
|
+
const B = new Set(newArr)
|
|
27
|
+
const toRemove = [...A].filter(x => !B.has(x))
|
|
28
|
+
const toAdd = [...B].filter(x => !A.has(x))
|
|
29
|
+
return { toRemove, toAdd }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* createKvMirrorHandler({
|
|
34
|
+
* document: 'organizations/{orgId}/sites/{siteId}/published_posts/{postId}',
|
|
35
|
+
* makeCanonicalKey: (params, data) => `post:${params.orgId}:${params.siteId}:${params.postId}`,
|
|
36
|
+
* makeIndexKeys: (params, data) => [...], // optional
|
|
37
|
+
* makeMetadata: (data, params) => ({ title: data.title }), // optional, merged with { canonical }
|
|
38
|
+
* serialize: (data) => JSON.stringify(data), // optional
|
|
39
|
+
* timeoutSeconds: 180 // optional
|
|
40
|
+
* })
|
|
41
|
+
*/
|
|
42
|
+
function createKvMirrorHandler({
|
|
43
|
+
document,
|
|
44
|
+
makeCanonicalKey,
|
|
45
|
+
makeIndexKeys,
|
|
46
|
+
makeMetadata,
|
|
47
|
+
serialize = json,
|
|
48
|
+
timeoutSeconds = 180,
|
|
49
|
+
}) {
|
|
50
|
+
return onDocumentWritten({ document, timeoutSeconds }, async (event) => {
|
|
51
|
+
const after = event.data?.after
|
|
52
|
+
const params = event.params || {}
|
|
53
|
+
const data = after?.exists ? after.data() : null
|
|
54
|
+
|
|
55
|
+
const canonicalKey = makeCanonicalKey(params, data)
|
|
56
|
+
const indexingEnabled = typeof makeIndexKeys === 'function'
|
|
57
|
+
const manifestKey = indexingEnabled ? `idx:manifest:${canonicalKey}` : null
|
|
58
|
+
|
|
59
|
+
if (!after?.exists) {
|
|
60
|
+
if (indexingEnabled) {
|
|
61
|
+
let prev = null
|
|
62
|
+
try {
|
|
63
|
+
prev = await kv.get(manifestKey, 'json')
|
|
64
|
+
}
|
|
65
|
+
catch (_) {
|
|
66
|
+
prev = null
|
|
67
|
+
}
|
|
68
|
+
const keys = Array.isArray(prev?.indexKeys) ? prev.indexKeys : []
|
|
69
|
+
const deletions = [
|
|
70
|
+
...keys.map(k => kv.del(k)),
|
|
71
|
+
kv.del(canonicalKey),
|
|
72
|
+
kv.del(manifestKey),
|
|
73
|
+
]
|
|
74
|
+
await Promise.allSettled(deletions)
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
await kv.del(canonicalKey)
|
|
78
|
+
}
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const baseMeta = { canonical: canonicalKey }
|
|
83
|
+
const customMetaCandidate = typeof makeMetadata === 'function' ? (makeMetadata(data, params) || null) : null
|
|
84
|
+
const metaValue = (customMetaCandidate && typeof customMetaCandidate === 'object')
|
|
85
|
+
? { ...customMetaCandidate, canonical: canonicalKey }
|
|
86
|
+
: baseMeta
|
|
87
|
+
|
|
88
|
+
await kv.put(canonicalKey, serialize(data), { metadata: metaValue })
|
|
89
|
+
|
|
90
|
+
if (!indexingEnabled) {
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const nextIndexKeys = (await Promise.resolve(makeIndexKeys(params, data)) || [])
|
|
95
|
+
.filter(Boolean)
|
|
96
|
+
.map(String)
|
|
97
|
+
|
|
98
|
+
let prev = null
|
|
99
|
+
try {
|
|
100
|
+
prev = await kv.get(manifestKey, 'json')
|
|
101
|
+
}
|
|
102
|
+
catch (_) {
|
|
103
|
+
prev = null
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const oldIndexKeys = Array.isArray(prev?.indexKeys) ? prev.indexKeys : []
|
|
107
|
+
const { toRemove } = setDiff(oldIndexKeys, nextIndexKeys)
|
|
108
|
+
|
|
109
|
+
const upserts = nextIndexKeys.map(k => kv.putIndexMeta(k, metaValue))
|
|
110
|
+
await Promise.allSettled(upserts)
|
|
111
|
+
|
|
112
|
+
const removals = toRemove.map(k => kv.del(k))
|
|
113
|
+
await Promise.allSettled(removals)
|
|
114
|
+
|
|
115
|
+
await kv.put(manifestKey, { indexKeys: nextIndexKeys })
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function createKvMirrorHandlerFromFields({
|
|
120
|
+
documentPath,
|
|
121
|
+
uniqueKey,
|
|
122
|
+
indexKeys = [],
|
|
123
|
+
metadataKeys = [],
|
|
124
|
+
serialize = json,
|
|
125
|
+
}) {
|
|
126
|
+
if (!uniqueKey || typeof uniqueKey !== 'string') {
|
|
127
|
+
throw new Error('createKvMirrorHandlerFromFields requires uniqueKey (e.g. "{orgId}:{siteId}")')
|
|
128
|
+
}
|
|
129
|
+
const docIdParam = 'docId'
|
|
130
|
+
const basePath = documentPath || ''
|
|
131
|
+
const document = basePath.includes(`{${docIdParam}}`) ? basePath : `${basePath}/{${docIdParam}}`
|
|
132
|
+
const collection = String(basePath || '')
|
|
133
|
+
.replace(new RegExp(`/{${docIdParam}}$`), '')
|
|
134
|
+
.split('/')
|
|
135
|
+
.filter(Boolean)
|
|
136
|
+
.pop()
|
|
137
|
+
|
|
138
|
+
const resolveUniqueKey = (params) => {
|
|
139
|
+
const template = String(uniqueKey || '').trim()
|
|
140
|
+
if (!template)
|
|
141
|
+
return ''
|
|
142
|
+
const tokens = template.match(/\{[^}]+\}/g) || []
|
|
143
|
+
let missing = false
|
|
144
|
+
const value = template.replace(/\{([^}]+)\}/g, (_, key) => {
|
|
145
|
+
const resolved = params?.[key]
|
|
146
|
+
if (resolved === undefined || resolved === null || resolved === '') {
|
|
147
|
+
missing = true
|
|
148
|
+
return ''
|
|
149
|
+
}
|
|
150
|
+
return String(resolved)
|
|
151
|
+
})
|
|
152
|
+
if (missing)
|
|
153
|
+
return ''
|
|
154
|
+
if (tokens.length === 0)
|
|
155
|
+
return value
|
|
156
|
+
return value
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const makeCanonicalKey = (params) => {
|
|
160
|
+
const resolvedKey = resolveUniqueKey(params)
|
|
161
|
+
const docId = params?.[docIdParam]
|
|
162
|
+
if (!collection || !docId || !resolvedKey)
|
|
163
|
+
return ''
|
|
164
|
+
return `${collection}:${resolvedKey}:${docId}`
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const makeIndexKeys = (params, data) => {
|
|
168
|
+
const docId = params?.[docIdParam]
|
|
169
|
+
const resolvedKey = resolveUniqueKey(params)
|
|
170
|
+
|
|
171
|
+
if (!collection || !docId || !resolvedKey)
|
|
172
|
+
return []
|
|
173
|
+
|
|
174
|
+
const keys = []
|
|
175
|
+
const fields = Array.isArray(indexKeys) ? indexKeys : []
|
|
176
|
+
for (const field of fields) {
|
|
177
|
+
if (!field || typeof field !== 'string')
|
|
178
|
+
continue
|
|
179
|
+
const rawValue = data?.[field]
|
|
180
|
+
const values = Array.isArray(rawValue) ? rawValue : [rawValue]
|
|
181
|
+
for (const value of values) {
|
|
182
|
+
if (value === undefined || value === null || value === '')
|
|
183
|
+
continue
|
|
184
|
+
const slug = slugIndexValue(value)
|
|
185
|
+
if (!slug)
|
|
186
|
+
continue
|
|
187
|
+
keys.push(`idx:${collection}:${field}:${resolvedKey}:${slug}:${docId}`)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return keys
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const makeMetadata = (data) => {
|
|
194
|
+
const meta = {}
|
|
195
|
+
const keys = Array.isArray(metadataKeys) ? metadataKeys : []
|
|
196
|
+
for (const key of keys) {
|
|
197
|
+
meta[key] = data?.[key] ?? ''
|
|
198
|
+
}
|
|
199
|
+
return meta
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return createKvMirrorHandler({
|
|
203
|
+
document,
|
|
204
|
+
makeCanonicalKey,
|
|
205
|
+
makeIndexKeys: indexKeys.length ? makeIndexKeys : undefined,
|
|
206
|
+
makeMetadata: metadataKeys.length ? makeMetadata : undefined,
|
|
207
|
+
serialize,
|
|
208
|
+
timeoutSeconds: 180,
|
|
209
|
+
})
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
module.exports = { createKvMirrorHandler, createKvMirrorHandlerFromFields }
|
package/src/package.json
CHANGED
|
@@ -1,35 +1,41 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
2
|
+
"name": "functions",
|
|
3
|
+
"type": "commonjs",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Cloud Functions for Firebase",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": "22"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"deploy": "firebase deploy --only functions",
|
|
12
|
+
"logs": "firebase functions:log",
|
|
13
|
+
"serve": "firebase emulators:start --only functions",
|
|
14
|
+
"shell": "firebase functions:shell",
|
|
15
|
+
"start": "npm run shell"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@aws-sdk/client-s3": "^3.582.0",
|
|
19
|
+
"@aws-sdk/s3-request-presigner": "^3.582.0",
|
|
20
|
+
"@google-cloud/pubsub": "^4.11.0",
|
|
21
|
+
"@google-cloud/functions-framework": "^3.4.5",
|
|
22
|
+
"@napi-rs/canvas": "^0.1.0",
|
|
23
|
+
"aws-sdk": "^2.1693.0",
|
|
24
|
+
"axios": "^1.7.2",
|
|
25
|
+
"cloudconvert": "^2.3.7",
|
|
26
|
+
"crypto": "^1.0.1",
|
|
27
|
+
"dotenv": "^16.6.1",
|
|
28
|
+
"exceljs": "^4.4.0",
|
|
29
|
+
"firebase-admin": "^13.6.0",
|
|
30
|
+
"firebase-functions": "^6.6.0",
|
|
31
|
+
"form-data": "^4.0.5",
|
|
32
|
+
"formidable-serverless": "^1.1.1",
|
|
33
|
+
"moment-timezone": "^0.5.48",
|
|
34
|
+
"openai": "^4.104.0",
|
|
35
|
+
"stripe": "^13.11.0",
|
|
36
|
+
"twilio": "^4.23.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"firebase-functions-test": "^0.2.0"
|
|
40
|
+
}
|
|
41
|
+
}
|
package/src/postinstall.sh
CHANGED
|
@@ -5,6 +5,63 @@ actual_dir=$(readlink -f $(dirname $0))
|
|
|
5
5
|
# Get the project root directory by removing the node_modules directory path
|
|
6
6
|
project_root=$(echo "$actual_dir" | awk '{gsub(/node_modules.*$/, ""); print}')
|
|
7
7
|
|
|
8
|
+
merge_env_file () {
|
|
9
|
+
local src="$1"
|
|
10
|
+
local dest="$2"
|
|
11
|
+
|
|
12
|
+
if [ ! -f "$src" ]; then
|
|
13
|
+
return
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
if [ ! -f "$dest" ]; then
|
|
17
|
+
cp "$src" "$dest"
|
|
18
|
+
return
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
[ "$(tail -c1 "$dest")" != "" ] && echo "" >> "$dest"
|
|
22
|
+
|
|
23
|
+
while IFS= read -r line; do
|
|
24
|
+
if [ -z "$line" ]; then
|
|
25
|
+
continue
|
|
26
|
+
fi
|
|
27
|
+
if echo "$line" | grep -qE '^[A-Za-z0-9_]+='; then
|
|
28
|
+
key="${line%%=*}"
|
|
29
|
+
if ! grep -qE "^${key}=" "$dest"; then
|
|
30
|
+
echo "$line" >> "$dest"
|
|
31
|
+
fi
|
|
32
|
+
fi
|
|
33
|
+
done < "$src"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
merge_package_json () {
|
|
37
|
+
local src="$1"
|
|
38
|
+
local dest="$2"
|
|
39
|
+
|
|
40
|
+
if [ ! -f "$src" ] || [ ! -f "$dest" ]; then
|
|
41
|
+
return
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
node - "$src" "$dest" <<'NODE'
|
|
45
|
+
const fs = require('fs')
|
|
46
|
+
const [,, srcPath, destPath] = process.argv
|
|
47
|
+
const src = JSON.parse(fs.readFileSync(srcPath, 'utf8'))
|
|
48
|
+
const dest = JSON.parse(fs.readFileSync(destPath, 'utf8'))
|
|
49
|
+
const sections = ['dependencies', 'devDependencies']
|
|
50
|
+
|
|
51
|
+
for (const section of sections) {
|
|
52
|
+
const srcDeps = src[section] || {}
|
|
53
|
+
if (!dest[section])
|
|
54
|
+
dest[section] = {}
|
|
55
|
+
for (const [name, version] of Object.entries(srcDeps)) {
|
|
56
|
+
if (!dest[section][name])
|
|
57
|
+
dest[section][name] = version
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
fs.writeFileSync(destPath, `${JSON.stringify(dest, null, 2)}\n`)
|
|
62
|
+
NODE
|
|
63
|
+
}
|
|
64
|
+
|
|
8
65
|
|
|
9
66
|
# Check if the destination file exists
|
|
10
67
|
if [ ! -f "$project_root/firestore.rules" ]; then
|
|
@@ -47,52 +104,32 @@ fi
|
|
|
47
104
|
|
|
48
105
|
cp ./src/edgeFirebase.js "$project_root/functions/edgeFirebase.js"
|
|
49
106
|
cp ./src/config.js "$project_root/functions/config.js"
|
|
107
|
+
cp ./src/cms.js "$project_root/functions/cms.js"
|
|
50
108
|
|
|
51
|
-
if [ ! -
|
|
52
|
-
|
|
53
|
-
fi
|
|
54
|
-
|
|
55
|
-
if [ ! -f "$project_root/functions/.env.dev" ]; then
|
|
56
|
-
cp ./src/.env.dev "$project_root/functions/.env.dev"
|
|
109
|
+
if [ ! -d "$project_root/functions/kv" ]; then
|
|
110
|
+
mkdir -p "$project_root/functions/kv"
|
|
57
111
|
fi
|
|
58
112
|
|
|
59
|
-
|
|
60
|
-
cp ./src/.env.prod "$project_root/functions/.env.prod"
|
|
61
|
-
fi
|
|
113
|
+
cp ./src/kv/*.js "$project_root/functions/kv/"
|
|
62
114
|
|
|
63
|
-
if [ ! -f "$project_root
|
|
64
|
-
cp ./src
|
|
115
|
+
if [ ! -f "$project_root/functions/index.js" ]; then
|
|
116
|
+
cp ./src/index.js "$project_root/functions/index.js"
|
|
117
|
+
else
|
|
118
|
+
sed -i.backup '/\/\/ START @edge\/firebase functions/,/\/\/ END @edge\/firebase functions/d' "$project_root/functions/index.js"
|
|
119
|
+
[ "$(tail -c1 $project_root/functions/index.js)" != "" ] && echo "" >> "$project_root/functions/index.js"
|
|
120
|
+
awk '/\/\/ START @edge\/firebase functions/,/\/\/ END @edge\/firebase functions/' ./src/functions.js | \
|
|
121
|
+
sed '1d;$d' | \
|
|
122
|
+
sed -e '1s/^/\/\/ START @edge\/firebase functions\n/' -e '$s/$/\n\/\/ END @edge\/firebase functions/' \
|
|
123
|
+
>> "$project_root/functions/index.js";
|
|
65
124
|
fi
|
|
66
125
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
126
|
+
merge_env_file "./src/.env.dev" "$project_root/functions/.env.dev"
|
|
127
|
+
merge_env_file "./src/.env.prod" "$project_root/functions/.env.prod"
|
|
128
|
+
merge_env_file "./src/.env.development" "$project_root/.env.dev"
|
|
129
|
+
merge_env_file "./src/.env.production" "$project_root/.env"
|
|
70
130
|
|
|
71
131
|
if [ ! -f "$project_root/functions/package.json" ]; then
|
|
72
132
|
cp ./src/package.json "$project_root/functions/package.json"
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
cd "$project_root"
|
|
133
|
+
else
|
|
134
|
+
merge_package_json "$actual_dir/package.json" "$project_root/functions/package.json"
|
|
76
135
|
fi
|
|
77
|
-
|
|
78
|
-
# Upgrade specific npm packages in the functions directory
|
|
79
|
-
cd "$project_root/functions"
|
|
80
|
-
|
|
81
|
-
# List of packages to upgrade
|
|
82
|
-
npm install --save \
|
|
83
|
-
"@google-cloud/pubsub@^4.9.0" \
|
|
84
|
-
"aws-sdk@^2.1692.0" \
|
|
85
|
-
"crypto@^1.0.1" \
|
|
86
|
-
"dotenv@^16.3.1" \
|
|
87
|
-
"exceljs@^4.4.0" \
|
|
88
|
-
"firebase-admin@^13.0.2" \
|
|
89
|
-
"firebase-functions@^6.2.0" \
|
|
90
|
-
"form-data@^4.0.0" \
|
|
91
|
-
"formidable-serverless@^1.1.1" \
|
|
92
|
-
"moment-timezone@^0.5.43" \
|
|
93
|
-
"openai@^4.11.1" \
|
|
94
|
-
"stripe@^13.8.0" \
|
|
95
|
-
"twilio@^4.18.0"
|
|
96
|
-
|
|
97
|
-
# Return to the project root
|
|
98
|
-
cd "$project_root"
|