@edgedev/create-edge-app 1.2.33 → 1.2.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +1 -0
  2. package/agents.md +95 -2
  3. package/deploy.sh +136 -0
  4. package/edge/components/cms/block.vue +977 -305
  5. package/edge/components/cms/blockApi.vue +3 -3
  6. package/edge/components/cms/blockEditor.vue +688 -86
  7. package/edge/components/cms/blockPicker.vue +31 -5
  8. package/edge/components/cms/blockRender.vue +3 -3
  9. package/edge/components/cms/blocksManager.vue +790 -82
  10. package/edge/components/cms/codeEditor.vue +15 -6
  11. package/edge/components/cms/fontUpload.vue +318 -2
  12. package/edge/components/cms/htmlContent.vue +825 -93
  13. package/edge/components/cms/init_blocks/contact_us.html +55 -47
  14. package/edge/components/cms/init_blocks/newsletter.html +56 -96
  15. package/edge/components/cms/menu.vue +96 -34
  16. package/edge/components/cms/page.vue +902 -58
  17. package/edge/components/cms/posts.vue +13 -4
  18. package/edge/components/cms/site.vue +638 -87
  19. package/edge/components/cms/siteSettingsForm.vue +19 -9
  20. package/edge/components/cms/sitesManager.vue +5 -4
  21. package/edge/components/cms/themeDefaultMenu.vue +20 -2
  22. package/edge/components/cms/themeEditor.vue +196 -162
  23. package/edge/components/editor.vue +5 -1
  24. package/edge/composables/global.ts +37 -5
  25. package/edge/composables/siteSettingsTemplate.js +2 -0
  26. package/edge/composables/useCmsNewDocs.js +100 -0
  27. package/edge/composables/useEdgeCmsDialogPositionFix.js +19 -0
  28. package/edge/routes/cms/dashboard/blocks/[block].vue +5 -0
  29. package/edge/routes/cms/dashboard/blocks/index.vue +12 -1
  30. package/edge/routes/cms/dashboard/media/index.vue +5 -0
  31. package/edge/routes/cms/dashboard/sites/[site]/[[page]].vue +4 -0
  32. package/edge/routes/cms/dashboard/sites/[site].vue +4 -0
  33. package/edge/routes/cms/dashboard/sites/index.vue +4 -0
  34. package/edge/routes/cms/dashboard/templates/[page].vue +4 -0
  35. package/edge/routes/cms/dashboard/templates/index.vue +4 -0
  36. package/edge/routes/cms/dashboard/themes/[theme].vue +5 -0
  37. package/edge/routes/cms/dashboard/themes/index.vue +330 -1
  38. package/edge-pull.sh +16 -2
  39. package/edge-push.sh +9 -1
  40. package/edge-remote.sh +20 -0
  41. package/edge-status.sh +9 -5
  42. package/edge-update-all.sh +127 -0
  43. package/firebase.json +4 -0
  44. package/nuxt.config.ts +1 -1
  45. package/package.json +2 -2
@@ -1,18 +1,281 @@
1
1
  <script setup>
2
+ import { useEdgeCmsDialogPositionFix } from '~/edge/composables/useEdgeCmsDialogPositionFix'
3
+
4
+ const edgeFirebase = inject('edgeFirebase')
5
+ const { themes: themeNewDocSchema } = useCmsNewDocs()
2
6
  const state = reactive({
3
7
  filter: '',
8
+ importingJson: false,
9
+ importDocIdDialogOpen: false,
10
+ importDocIdValue: '',
11
+ importConflictDialogOpen: false,
12
+ importConflictDocId: '',
13
+ importErrorDialogOpen: false,
14
+ importErrorMessage: '',
4
15
  })
16
+ const themeImportInputRef = ref(null)
17
+ const themeImportDocIdResolver = ref(null)
18
+ const themeImportConflictResolver = ref(null)
19
+ const INVALID_THEME_IMPORT_MESSAGE = 'Invalid file. Please import a valid theme file.'
20
+
21
+ useEdgeCmsDialogPositionFix()
22
+
23
+ useEdgeCmsDialogPositionFix()
5
24
 
6
25
  definePageMeta({
7
26
  middleware: 'auth',
8
27
  })
28
+
29
+ const themesCollectionPath = computed(() => `${edgeGlobal.edgeState.organizationDocPath}/themes`)
30
+ const themesCollection = computed(() => edgeFirebase.data?.[themesCollectionPath.value] || {})
31
+
32
+ const readTextFile = file => new Promise((resolve, reject) => {
33
+ if (typeof FileReader === 'undefined') {
34
+ reject(new Error('File import is only available in the browser.'))
35
+ return
36
+ }
37
+ const reader = new FileReader()
38
+ reader.onload = () => resolve(String(reader.result || ''))
39
+ reader.onerror = () => reject(new Error('Could not read the selected file.'))
40
+ reader.readAsText(file)
41
+ })
42
+
43
+ const normalizeImportedDoc = (payload, fallbackDocId = '') => {
44
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload))
45
+ throw new Error(INVALID_THEME_IMPORT_MESSAGE)
46
+
47
+ if (payload.document && typeof payload.document === 'object' && !Array.isArray(payload.document)) {
48
+ const normalized = { ...payload.document }
49
+ if (!normalized.docId && payload.docId)
50
+ normalized.docId = payload.docId
51
+ if (!normalized.docId && fallbackDocId)
52
+ normalized.docId = fallbackDocId
53
+ return normalized
54
+ }
55
+
56
+ const normalized = { ...payload }
57
+ if (!normalized.docId && fallbackDocId)
58
+ normalized.docId = fallbackDocId
59
+ return normalized
60
+ }
61
+
62
+ const isPlainObject = value => !!value && typeof value === 'object' && !Array.isArray(value)
63
+
64
+ const cloneSchemaValue = (value) => {
65
+ if (isPlainObject(value) || Array.isArray(value))
66
+ return edgeGlobal.dupObject(value)
67
+ return value
68
+ }
69
+
70
+ const getDocDefaultsFromSchema = (schema = {}) => {
71
+ const defaults = {}
72
+ for (const [key, schemaEntry] of Object.entries(schema || {})) {
73
+ const hasValueProp = isPlainObject(schemaEntry) && Object.prototype.hasOwnProperty.call(schemaEntry, 'value')
74
+ const baseValue = hasValueProp ? schemaEntry.value : schemaEntry
75
+ defaults[key] = cloneSchemaValue(baseValue)
76
+ }
77
+ return defaults
78
+ }
79
+
80
+ const getThemeDocDefaults = () => getDocDefaultsFromSchema(themeNewDocSchema.value || {})
81
+
82
+ const validateImportedThemeDoc = (doc) => {
83
+ if (!isPlainObject(doc))
84
+ throw new Error(INVALID_THEME_IMPORT_MESSAGE)
85
+
86
+ const requiredKeys = Object.keys(themeNewDocSchema.value || {})
87
+ const missing = requiredKeys.filter(key => !Object.prototype.hasOwnProperty.call(doc, key))
88
+ if (missing.length)
89
+ throw new Error(INVALID_THEME_IMPORT_MESSAGE)
90
+
91
+ return doc
92
+ }
93
+
94
+ const makeUniqueDocId = (baseDocId, docsMap = {}) => {
95
+ const cleanBase = String(baseDocId || '').trim() || 'theme'
96
+ let nextDocId = `${cleanBase}-copy`
97
+ let suffix = 2
98
+ while (docsMap[nextDocId]) {
99
+ nextDocId = `${cleanBase}-copy-${suffix}`
100
+ suffix += 1
101
+ }
102
+ return nextDocId
103
+ }
104
+
105
+ const requestThemeImportDocId = (initialValue = '') => {
106
+ state.importDocIdValue = String(initialValue || '')
107
+ state.importDocIdDialogOpen = true
108
+ return new Promise((resolve) => {
109
+ themeImportDocIdResolver.value = resolve
110
+ })
111
+ }
112
+
113
+ const resolveThemeImportDocId = (value = '') => {
114
+ const resolver = themeImportDocIdResolver.value
115
+ themeImportDocIdResolver.value = null
116
+ state.importDocIdDialogOpen = false
117
+ if (resolver)
118
+ resolver(String(value || '').trim())
119
+ }
120
+
121
+ const requestThemeImportConflict = (docId) => {
122
+ state.importConflictDocId = String(docId || '')
123
+ state.importConflictDialogOpen = true
124
+ return new Promise((resolve) => {
125
+ themeImportConflictResolver.value = resolve
126
+ })
127
+ }
128
+
129
+ const resolveThemeImportConflict = (action = 'cancel') => {
130
+ const resolver = themeImportConflictResolver.value
131
+ themeImportConflictResolver.value = null
132
+ state.importConflictDialogOpen = false
133
+ if (resolver)
134
+ resolver(action)
135
+ }
136
+
137
+ watch(() => state.importDocIdDialogOpen, (open) => {
138
+ if (!open && themeImportDocIdResolver.value) {
139
+ const resolver = themeImportDocIdResolver.value
140
+ themeImportDocIdResolver.value = null
141
+ resolver('')
142
+ }
143
+ })
144
+
145
+ watch(() => state.importConflictDialogOpen, (open) => {
146
+ if (!open && themeImportConflictResolver.value) {
147
+ const resolver = themeImportConflictResolver.value
148
+ themeImportConflictResolver.value = null
149
+ resolver('cancel')
150
+ }
151
+ })
152
+
153
+ const getImportDocId = async (incomingDoc, fallbackDocId = '') => {
154
+ let nextDocId = String(incomingDoc?.docId || '').trim()
155
+ if (!nextDocId)
156
+ nextDocId = await requestThemeImportDocId(fallbackDocId)
157
+ if (!nextDocId)
158
+ throw new Error('Import canceled. A docId is required.')
159
+ if (nextDocId.includes('/'))
160
+ throw new Error('docId cannot include "/".')
161
+ return nextDocId
162
+ }
163
+
164
+ const openImportErrorDialog = (message) => {
165
+ state.importErrorMessage = String(message || 'Failed to import theme JSON.')
166
+ state.importErrorDialogOpen = true
167
+ }
168
+
169
+ const triggerThemeImport = () => {
170
+ themeImportInputRef.value?.click()
171
+ }
172
+
173
+ const importSingleThemeFile = async (file, existingThemes = {}) => {
174
+ const fileText = await readTextFile(file)
175
+ const parsed = JSON.parse(fileText)
176
+ const importedDoc = validateImportedThemeDoc(normalizeImportedDoc(parsed, ''))
177
+ const incomingDocId = await getImportDocId(importedDoc, '')
178
+ let targetDocId = incomingDocId
179
+ let importDecision = 'create'
180
+
181
+ if (existingThemes[targetDocId]) {
182
+ const decision = await requestThemeImportConflict(targetDocId)
183
+ if (decision === 'cancel')
184
+ return
185
+ if (decision === 'new') {
186
+ targetDocId = makeUniqueDocId(targetDocId, existingThemes)
187
+ if (typeof importedDoc.name === 'string' && importedDoc.name.trim() && !/\(Copy\)$/i.test(importedDoc.name.trim()))
188
+ importedDoc.name = `${importedDoc.name} (Copy)`
189
+ importDecision = 'new'
190
+ }
191
+ else {
192
+ importDecision = 'overwrite'
193
+ }
194
+ }
195
+
196
+ const payload = { ...getThemeDocDefaults(), ...importedDoc, docId: targetDocId }
197
+ await edgeFirebase.storeDoc(themesCollectionPath.value, payload, targetDocId)
198
+ existingThemes[targetDocId] = payload
199
+
200
+ if (importDecision === 'overwrite')
201
+ edgeFirebase?.toast?.success?.(`Overwrote theme "${targetDocId}".`)
202
+ else if (importDecision === 'new')
203
+ edgeFirebase?.toast?.success?.(`Imported theme as new "${targetDocId}".`)
204
+ else
205
+ edgeFirebase?.toast?.success?.(`Imported theme "${targetDocId}".`)
206
+ }
207
+
208
+ const handleThemeImport = async (event) => {
209
+ const input = event?.target
210
+ const files = Array.from(input?.files || [])
211
+ if (!files.length)
212
+ return
213
+
214
+ state.importingJson = true
215
+ const existingThemes = { ...(themesCollection.value || {}) }
216
+ try {
217
+ for (const file of files) {
218
+ try {
219
+ await importSingleThemeFile(file, existingThemes)
220
+ }
221
+ catch (error) {
222
+ console.error('Failed to import theme JSON', error)
223
+ const message = error?.message || 'Failed to import theme JSON.'
224
+ if (/^Import canceled\./i.test(message))
225
+ continue
226
+ if (error instanceof SyntaxError || message === INVALID_THEME_IMPORT_MESSAGE)
227
+ openImportErrorDialog(INVALID_THEME_IMPORT_MESSAGE)
228
+ else
229
+ openImportErrorDialog(message)
230
+ }
231
+ }
232
+ }
233
+ finally {
234
+ state.importingJson = false
235
+ if (input)
236
+ input.value = ''
237
+ }
238
+ }
9
239
  </script>
10
240
 
11
241
  <template>
12
242
  <div
13
243
  v-if="edgeGlobal.edgeState.organizationDocPath"
14
244
  >
15
- <edge-dashboard :filter="state.filter" collection="themes" class="pt-0 flex-1">
245
+ <edge-dashboard
246
+ :filter="state.filter"
247
+ collection="themes"
248
+ class="pt-0 flex-1"
249
+ header-class="bg-secondary py-2 border"
250
+ >
251
+ <template #header-end>
252
+ <div class="flex items-center gap-2">
253
+ <input
254
+ ref="themeImportInputRef"
255
+ type="file"
256
+ multiple
257
+ accept=".json,application/json"
258
+ class="hidden"
259
+ @change="handleThemeImport"
260
+ >
261
+ <edge-shad-button
262
+ type="button"
263
+ size="icon"
264
+ variant="outline"
265
+ class="h-9 w-9"
266
+ :disabled="state.importingJson"
267
+ title="Import Themes"
268
+ aria-label="Import Themes"
269
+ @click="triggerThemeImport"
270
+ >
271
+ <Loader2 v-if="state.importingJson" class="h-4 w-4 animate-spin" />
272
+ <Upload v-else class="h-4 w-4" />
273
+ </edge-shad-button>
274
+ <edge-shad-button class="uppercase bg-primary" to="/app/dashboard/themes/new">
275
+ Add Theme
276
+ </edge-shad-button>
277
+ </div>
278
+ </template>
16
279
  <template #list="slotProps">
17
280
  <template v-for="item in slotProps.filtered" :key="item.docId">
18
281
  <edge-shad-button variant="text" class="cursor-pointer w-full flex justify-between items-center py-2 gap-3" :to="`/app/dashboard/themes/${item.docId}`">
@@ -40,5 +303,71 @@ definePageMeta({
40
303
  </template>
41
304
  </template>
42
305
  </edge-dashboard>
306
+ <edge-shad-dialog v-model="state.importDocIdDialogOpen">
307
+ <DialogContent class="pt-8">
308
+ <DialogHeader>
309
+ <DialogTitle class="text-left">
310
+ Enter Theme Doc ID
311
+ </DialogTitle>
312
+ <DialogDescription>
313
+ This JSON file does not include a <code>docId</code>. Enter the doc ID you want to import into.
314
+ </DialogDescription>
315
+ </DialogHeader>
316
+ <edge-shad-input
317
+ v-model="state.importDocIdValue"
318
+ name="theme-import-doc-id"
319
+ label="Doc ID"
320
+ placeholder="example-theme-id"
321
+ />
322
+ <DialogFooter class="pt-2 flex justify-between">
323
+ <edge-shad-button variant="outline" @click="resolveThemeImportDocId('')">
324
+ Cancel
325
+ </edge-shad-button>
326
+ <edge-shad-button @click="resolveThemeImportDocId(state.importDocIdValue)">
327
+ Continue
328
+ </edge-shad-button>
329
+ </DialogFooter>
330
+ </DialogContent>
331
+ </edge-shad-dialog>
332
+ <edge-shad-dialog v-model="state.importConflictDialogOpen">
333
+ <DialogContent class="pt-8">
334
+ <DialogHeader>
335
+ <DialogTitle class="text-left">
336
+ Theme Already Exists
337
+ </DialogTitle>
338
+ <DialogDescription>
339
+ <code>{{ state.importConflictDocId }}</code> already exists. Choose to overwrite it or import as a new theme.
340
+ </DialogDescription>
341
+ </DialogHeader>
342
+ <DialogFooter class="pt-2 flex justify-between">
343
+ <edge-shad-button variant="outline" @click="resolveThemeImportConflict('cancel')">
344
+ Cancel
345
+ </edge-shad-button>
346
+ <edge-shad-button variant="outline" @click="resolveThemeImportConflict('new')">
347
+ Add As New
348
+ </edge-shad-button>
349
+ <edge-shad-button @click="resolveThemeImportConflict('overwrite')">
350
+ Overwrite
351
+ </edge-shad-button>
352
+ </DialogFooter>
353
+ </DialogContent>
354
+ </edge-shad-dialog>
355
+ <edge-shad-dialog v-model="state.importErrorDialogOpen">
356
+ <DialogContent class="pt-8">
357
+ <DialogHeader>
358
+ <DialogTitle class="text-left">
359
+ Import Failed
360
+ </DialogTitle>
361
+ <DialogDescription class="text-left">
362
+ {{ state.importErrorMessage }}
363
+ </DialogDescription>
364
+ </DialogHeader>
365
+ <DialogFooter class="pt-2">
366
+ <edge-shad-button @click="state.importErrorDialogOpen = false">
367
+ Close
368
+ </edge-shad-button>
369
+ </DialogFooter>
370
+ </DialogContent>
371
+ </edge-shad-dialog>
43
372
  </div>
44
373
  </template>
package/edge-pull.sh CHANGED
@@ -1,2 +1,16 @@
1
- git fetch edge-vue-components
2
- git subtree pull --prefix=edge edge-vue-components main --squash
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
5
+ # shellcheck source=./edge-remote.sh
6
+ source "$SCRIPT_DIR/edge-remote.sh"
7
+
8
+ fetch_edge_remote
9
+
10
+ if [ -d "$SCRIPT_DIR/edge" ]
11
+ then
12
+ git subtree pull --prefix=edge "$EDGE_REMOTE_NAME" main --squash
13
+ else
14
+ echo "No edge subtree found. Initializing with git subtree add."
15
+ git subtree add --prefix=edge "$EDGE_REMOTE_NAME" main --squash
16
+ fi
package/edge-push.sh CHANGED
@@ -1 +1,9 @@
1
- git subtree push --prefix=edge edge-vue-components main
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
5
+ # shellcheck source=./edge-remote.sh
6
+ source "$SCRIPT_DIR/edge-remote.sh"
7
+
8
+ ensure_edge_remote
9
+ git subtree push --prefix=edge "$EDGE_REMOTE_NAME" main
package/edge-remote.sh ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ EDGE_REMOTE_NAME="${EDGE_REMOTE_NAME:-edge-vue-components}"
5
+ EDGE_REMOTE_URL="${EDGE_REMOTE_URL:-https://github.com/Edge-Marketing-and-Design/edge-vue-components.git}"
6
+
7
+ ensure_edge_remote() {
8
+ if git remote get-url "$EDGE_REMOTE_NAME" >/dev/null 2>&1
9
+ then
10
+ return 0
11
+ fi
12
+
13
+ echo "Remote '$EDGE_REMOTE_NAME' is not configured. Adding '$EDGE_REMOTE_URL'."
14
+ git remote add "$EDGE_REMOTE_NAME" "$EDGE_REMOTE_URL"
15
+ }
16
+
17
+ fetch_edge_remote() {
18
+ ensure_edge_remote
19
+ git fetch "$EDGE_REMOTE_NAME"
20
+ }
package/edge-status.sh CHANGED
@@ -1,14 +1,18 @@
1
1
  #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
- git fetch edge-vue-components >/dev/null 2>&1
4
+ SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
5
+ # shellcheck source=./edge-remote.sh
6
+ source "$SCRIPT_DIR/edge-remote.sh"
5
7
 
6
- UP_TREE="$(git rev-parse edge-vue-components/main^{tree})"
8
+ fetch_edge_remote >/dev/null 2>&1
9
+
10
+ UP_TREE="$(git rev-parse "$EDGE_REMOTE_NAME/main^{tree}")"
7
11
  LOCAL_TREE="$(git rev-parse HEAD:edge)"
8
12
 
9
13
  if [ "$UP_TREE" = "$LOCAL_TREE" ]
10
14
  then
11
- echo "edge is in sync with edge-vue-components/main"
15
+ echo "edge is in sync with $EDGE_REMOTE_NAME/main"
12
16
  else
13
- echo "edge differs from edge-vue-components/main"
14
- fi
17
+ echo "edge differs from $EDGE_REMOTE_NAME/main"
18
+ fi
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env sh
2
+ set -eu
3
+
4
+ SCRIPT_DIR="$(cd -- "$(dirname -- "$0")" && pwd)"
5
+ cd "$SCRIPT_DIR"
6
+
7
+ usage() {
8
+ cat <<'EOF'
9
+ Usage: ./edge-update-all.sh
10
+
11
+ Updates:
12
+ 1) edge subtree (via edge-pull.sh)
13
+ 2) remove @edgedev/template-engine and @edgedev/firebase
14
+ 3) install latest @edgedev/template-engine and @edgedev/firebase
15
+ EOF
16
+ }
17
+
18
+ for arg in "$@"
19
+ do
20
+ case "$arg" in
21
+ -h|--help)
22
+ usage
23
+ exit 0
24
+ ;;
25
+ *)
26
+ echo "Unknown argument: $arg"
27
+ echo
28
+ usage
29
+ exit 1
30
+ ;;
31
+ esac
32
+ done
33
+
34
+ if ! command -v pnpm >/dev/null 2>&1
35
+ then
36
+ echo "pnpm is required (pnpm-lock.yaml is present), but it is not installed."
37
+ echo "Install pnpm and rerun."
38
+ exit 1
39
+ fi
40
+
41
+ sync_edge_functions() {
42
+ edge_functions_dir="$SCRIPT_DIR/edge/functions"
43
+ local_functions_dir="$SCRIPT_DIR/functions"
44
+
45
+ if [ ! -d "$edge_functions_dir" ]; then
46
+ return
47
+ fi
48
+
49
+ echo "==> Syncing edge/functions into local functions/"
50
+
51
+ find "$edge_functions_dir" -type f | sort | while IFS= read -r src_file; do
52
+ rel_path="${src_file#$edge_functions_dir/}"
53
+ dest_file="$local_functions_dir/$rel_path"
54
+ dest_dir="$(dirname "$dest_file")"
55
+
56
+ mkdir -p "$dest_dir"
57
+
58
+ cp "$src_file" "$dest_file"
59
+ done
60
+ }
61
+
62
+ merge_firestore_indexes() {
63
+ edge_indexes="$SCRIPT_DIR/edge/root/firestore.indexes.json"
64
+ local_indexes="$SCRIPT_DIR/firestore.indexes.json"
65
+
66
+ if [ ! -f "$edge_indexes" ]; then
67
+ return
68
+ fi
69
+
70
+ if [ ! -f "$local_indexes" ]; then
71
+ echo "==> Writing firestore.indexes.json from edge/root"
72
+ cp "$edge_indexes" "$local_indexes"
73
+ return
74
+ fi
75
+
76
+ echo "==> Merging firestore.indexes.json with edge/root priority"
77
+ EDGE_INDEXES_PATH="$edge_indexes" LOCAL_INDEXES_PATH="$local_indexes" node <<'EOF'
78
+ const fs = require('fs')
79
+
80
+ const edgePath = process.env.EDGE_INDEXES_PATH
81
+ const localPath = process.env.LOCAL_INDEXES_PATH
82
+
83
+ const readJson = (filePath) => JSON.parse(fs.readFileSync(filePath, 'utf8'))
84
+ const edgeJson = readJson(edgePath)
85
+ const localJson = readJson(localPath)
86
+
87
+ const normalizeArray = value => Array.isArray(value) ? value : []
88
+ const makeKey = index => JSON.stringify({
89
+ collectionGroup: index?.collectionGroup || '',
90
+ queryScope: index?.queryScope || 'COLLECTION',
91
+ fields: normalizeArray(index?.fields).map(field => ({
92
+ fieldPath: field?.fieldPath || '',
93
+ order: field?.order || '',
94
+ arrayConfig: field?.arrayConfig || '',
95
+ vectorConfig: field?.vectorConfig || null,
96
+ })),
97
+ })
98
+
99
+ const mergedMap = new Map()
100
+ for (const index of normalizeArray(localJson.indexes))
101
+ mergedMap.set(makeKey(index), index)
102
+ for (const index of normalizeArray(edgeJson.indexes))
103
+ mergedMap.set(makeKey(index), index)
104
+
105
+ const merged = {
106
+ ...localJson,
107
+ ...edgeJson,
108
+ indexes: Array.from(mergedMap.values()),
109
+ }
110
+
111
+ fs.writeFileSync(localPath, `${JSON.stringify(merged, null, 2)}\n`)
112
+ EOF
113
+ }
114
+
115
+ echo "==> Updating edge subtree"
116
+ "$SCRIPT_DIR/edge-pull.sh"
117
+
118
+ sync_edge_functions
119
+ merge_firestore_indexes
120
+
121
+ echo "==> Removing @edgedev packages"
122
+ pnpm remove @edgedev/template-engine @edgedev/firebase
123
+
124
+ echo "==> Installing latest @edgedev packages"
125
+ pnpm add @edgedev/template-engine@latest @edgedev/firebase@latest
126
+
127
+ echo "==> Done"
package/firebase.json CHANGED
@@ -23,6 +23,10 @@
23
23
  "**/node_modules/**"
24
24
  ],
25
25
  "rewrites": [
26
+ {
27
+ "source": "/api/history/**",
28
+ "function": "cms-trackHistory"
29
+ },
26
30
  {
27
31
  "source": "/api/stripe",
28
32
  "function": {
package/nuxt.config.ts CHANGED
@@ -24,7 +24,7 @@ export default defineNuxtConfig({
24
24
  meta: [
25
25
  {
26
26
  'http-equiv': 'Content-Security-Policy',
27
- 'content': 'font-src \'self\' https://files.edgemarketingdesign.com https://use.typekit.net https://p.typekit.net data:;',
27
+ 'content': 'font-src \'self\' https://files.edgemarketingdesign.com https://use.typekit.net https://p.typekit.net https://fonts.gstatic.com data:;',
28
28
  },
29
29
  ],
30
30
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edgedev/create-edge-app",
3
- "version": "1.2.33",
3
+ "version": "1.2.35",
4
4
  "description": "Create Edge Starter App",
5
5
  "bin": {
6
6
  "create-edge-app": "./bin/cli.js"
@@ -22,7 +22,7 @@
22
22
  "@capacitor/push-notifications": "5.1.0",
23
23
  "@chenfengyuan/vue-number-input": "2",
24
24
  "@edgedev/firebase": "latest",
25
- "@edgedev/template-engine": "^0.1.12",
25
+ "@edgedev/template-engine": "latest",
26
26
  "@guolao/vue-monaco-editor": "^1.5.5",
27
27
  "@tiptap/extension-image": "^2.11.5",
28
28
  "@tiptap/extension-text-style": "^2.11.5",