@edgedev/create-edge-app 1.1.28 → 1.2.29

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/agents.md CHANGED
@@ -7,6 +7,7 @@ This project is Nuxt 3 + Vue 3, SPA mode. Follow these rules so new code matches
7
7
  - Components and composables from `edge/composables/**` are auto-imported; avoid manual imports unless needed.
8
8
  - Utilities: use `cn` from `@/lib/utils` for class merging, `lucide-vue-next` for icons, Tailwind for layout/styling. Keep comments minimal and useful.
9
9
  - Components under `edge/components` are globally registered with the `edge-` prefix (e.g., `edge-dashboard`, `edge-editor`, `edge-shad-button`).
10
+ - Check the project's lint tooling (package.json scripts + config files) before coding and follow its rules; avoid using functions before they are defined.
10
11
 
11
12
  ## Firebase and data access
12
13
  - Never import Firebase SDKs directly. All Auth/Firestore/Functions/Storage access goes through the injected `edgeFirebase` plugin (`plugins/firebase.client.ts` from `@edgedev/firebase`).
@@ -78,3 +79,4 @@ Adjust props (search, filters, pagination, save overrides) using the existing co
78
79
  ## Firebase Functions guidance
79
80
  - Review `functions/config.js`, `functions/edgeFirebase.js`, and `functions/cms.js` to mirror established patterns, but do not edit those files.
80
81
  - When adding new cloud functions, create a new JS file under `functions/` and export handlers using the shared imports from `config.js`. Wire it up by requiring it in `functions/index.js` (same pattern as `stripe.js`), instead of modifying restricted files.
82
+ - For every `onCall` function, always enforce both checks up front: `request.auth?.uid` must exist, and `request.data?.uid` must exactly match `request.auth.uid`. Throw `HttpsError('unauthenticated', ...)` when auth is missing and `HttpsError('permission-denied', ...)` when the uid does not match.
package/bin/cli.js CHANGED
@@ -63,8 +63,9 @@ const cleanGitignore = (repoName) => {
63
63
 
64
64
  const repoName = process.argv[2]
65
65
 
66
- const gitCheckoutCommand = `git clone --depth 1 https://github.com/Edge-Marketing-and-Design/edgeApp.git ${repoName}`
67
- const removeGitDirCommand = `rm -rf ${repoName}/.git`
66
+ const gitCheckoutCommand = `git clone https://github.com/Edge-Marketing-and-Design/edgeApp.git ${repoName}`
67
+ const removeOriginRemoteCommand = `cd ${repoName} && git remote remove origin`
68
+ const addEdgeComponentsRemoteCommand = `cd ${repoName} && git remote add edge-vue-components https://github.com/Edge-Marketing-and-Design/edge-vue-components.git`
68
69
  const installDependenciesCommand = `cd ${repoName} && pnpm store prune && pnpm install --force --ignore-scripts=false`
69
70
  const installFunctionDependenciesCommand = `cd ${repoName}/functions && npm install`
70
71
  // const cloneFirebaseFrameworkCommand = `cd ${repoName} && git clone https://github.com/Edge-Marketing-and-Design/edge-vue-components.git edge`
@@ -75,9 +76,15 @@ if (!checkedOut) {
75
76
  process.exit(1)
76
77
  }
77
78
 
78
- console.log(`Removing .git directory from ${repoName}...`)
79
- const removedGitDir = runCommand(removeGitDirCommand)
80
- if (!removedGitDir) {
79
+ console.log(`Detaching template origin remote from ${repoName}...`)
80
+ const removedOriginRemote = runCommand(removeOriginRemoteCommand)
81
+ if (!removedOriginRemote) {
82
+ process.exit(1)
83
+ }
84
+
85
+ console.log(`Adding edge-vue-components remote to ${repoName}...`)
86
+ const addedEdgeComponentsRemote = runCommand(addEdgeComponentsRemoteCommand)
87
+ if (!addedEdgeComponentsRemote) {
81
88
  process.exit(1)
82
89
  }
83
90
 
@@ -112,4 +119,5 @@ if (!installedFunctionDeps) {
112
119
  // }
113
120
 
114
121
  console.log(`Successfully created ${repoName}!`)
122
+ console.log(`Add your own git origin with: cd ${repoName} && git remote add origin <your-repo-url>`)
115
123
  console.log(`cd into ${repoName} and run 'sh firebase_init.sh' to initialize your firebase project.`)
@@ -0,0 +1,237 @@
1
+ #!/usr/bin/env sh
2
+ set -eu
3
+
4
+ ROOT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
5
+ SERVICES_DIR="$ROOT_DIR/services"
6
+ ROOT_ENV_FILE="$ROOT_DIR/.env"
7
+ SHARED_DEPLOY_ENV_FILE="$SERVICES_DIR/.deploy.shared.env"
8
+ FIREBASERC_FILE="$ROOT_DIR/.firebaserc"
9
+ FUNCTIONS_PROD_ENV_FILE="$ROOT_DIR/functions/.env.prod"
10
+
11
+ trim() {
12
+ printf '%s' "$1" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//'
13
+ }
14
+
15
+ load_env_file() {
16
+ file="$1"
17
+ if [ ! -f "$file" ]; then
18
+ return 0
19
+ fi
20
+ set -a
21
+ # shellcheck disable=SC1090
22
+ . "$file"
23
+ set +a
24
+ }
25
+
26
+ extract_default_project_id() {
27
+ if [ ! -f "$FIREBASERC_FILE" ]; then
28
+ printf ''
29
+ return 0
30
+ fi
31
+ node -e "const fs=require('fs');const p=process.argv[1];const data=JSON.parse(fs.readFileSync(p,'utf8'));process.stdout.write((data.projects&&data.projects.default)||'')" "$FIREBASERC_FILE"
32
+ }
33
+
34
+ service_name_to_env_key() {
35
+ service_name="$1"
36
+ normalized=''
37
+ normalized="$(printf '%s' "$service_name" \
38
+ | tr '[:lower:]' '[:upper:]' \
39
+ | sed -E 's/[^A-Z0-9]+/_/g; s/^_+//; s/_+$//')"
40
+ printf '%s_SERVICE' "$normalized"
41
+ }
42
+
43
+ upsert_env_key_value() {
44
+ file="$1"
45
+ key="$2"
46
+ value="$3"
47
+ tmp=''
48
+
49
+ mkdir -p "$(dirname "$file")"
50
+ if [ ! -f "$file" ]; then
51
+ printf '%s=%s\n' "$key" "$value" > "$file"
52
+ return 0
53
+ fi
54
+
55
+ tmp="$(mktemp)"
56
+ awk -v key="$key" -v value="$value" '
57
+ BEGIN { updated = 0 }
58
+ $0 ~ ("^" key "=") {
59
+ if (!updated) {
60
+ print key "=" value
61
+ updated = 1
62
+ }
63
+ next
64
+ }
65
+ { print }
66
+ END {
67
+ if (!updated) {
68
+ print key "=" value
69
+ }
70
+ }
71
+ ' "$file" > "$tmp"
72
+ mv "$tmp" "$file"
73
+ }
74
+
75
+ build_update_env_vars_arg() {
76
+ env_file="$1"
77
+ joined=''
78
+ keys="$(grep -E '^[A-Za-z_][A-Za-z0-9_]*=' "$env_file" | cut -d= -f1 || true)"
79
+
80
+ for key in $keys; do
81
+ case "$key" in
82
+ SERVICE_NAME|SOURCE_DIR|PROJECT_ID|REGION|ALLOW_UNAUTHENTICATED|INVOKER_MEMBERS)
83
+ continue
84
+ ;;
85
+ esac
86
+ value="$(eval "printf '%s' \"\${$key-}\"")"
87
+ if [ -n "$joined" ]; then
88
+ joined="${joined},${key}=${value}"
89
+ else
90
+ joined="${key}=${value}"
91
+ fi
92
+ done
93
+
94
+ printf '%s' "$joined"
95
+ }
96
+
97
+ if ! command -v gcloud >/dev/null 2>&1; then
98
+ echo "Deploy aborted: gcloud CLI not found." >&2
99
+ exit 1
100
+ fi
101
+
102
+ DEFAULT_PROJECT_ID="$(extract_default_project_id)"
103
+ if [ -z "${PROJECT_ID:-}" ] && [ -z "$DEFAULT_PROJECT_ID" ]; then
104
+ echo "Deploy aborted: PROJECT_ID is not set and .firebaserc has no default project." >&2
105
+ exit 1
106
+ fi
107
+
108
+ deployed_count=0
109
+
110
+ for service_env_file in "$SERVICES_DIR"/*/.deploy.env; do
111
+ if [ ! -f "$service_env_file" ]; then
112
+ continue
113
+ fi
114
+
115
+ service_dir="$(cd "$(dirname "$service_env_file")" && pwd)"
116
+
117
+ (
118
+ load_env_file "$ROOT_ENV_FILE"
119
+ load_env_file "$SHARED_DEPLOY_ENV_FILE"
120
+ load_env_file "$service_env_file"
121
+
122
+ service_name="${SERVICE_NAME:-$(basename "$service_dir")}"
123
+ project_id="${PROJECT_ID:-$DEFAULT_PROJECT_ID}"
124
+ region="${REGION:-${VITE_FIREBASE_FUNCTIONS_REGION:-us-central1}}"
125
+ source_dir="${SOURCE_DIR:-$service_dir}"
126
+ allow_unauthenticated="$(printf '%s' "${ALLOW_UNAUTHENTICATED:-false}" | tr '[:upper:]' '[:lower:]')"
127
+ invoker_members_raw="${INVOKER_MEMBERS:-}"
128
+
129
+ if [ -z "$service_name" ]; then
130
+ echo "Skipping '$service_dir': SERVICE_NAME is empty." >&2
131
+ exit 0
132
+ fi
133
+ if [ -z "$project_id" ]; then
134
+ echo "Skipping '$service_dir': PROJECT_ID resolved empty." >&2
135
+ exit 0
136
+ fi
137
+ if [ -z "$region" ]; then
138
+ echo "Skipping '$service_dir': REGION resolved empty." >&2
139
+ exit 0
140
+ fi
141
+
142
+ case "$source_dir" in
143
+ /*) ;;
144
+ *) source_dir="$ROOT_DIR/$source_dir" ;;
145
+ esac
146
+ if [ ! -d "$source_dir" ]; then
147
+ echo "Skipping '$service_dir': SOURCE_DIR '$source_dir' not found." >&2
148
+ exit 0
149
+ fi
150
+
151
+ update_env_vars_arg="$(build_update_env_vars_arg "$service_env_file")"
152
+
153
+ echo "Deploying service '$service_name' from '$source_dir' (project=$project_id, region=$region)..."
154
+ if [ -n "$update_env_vars_arg" ]; then
155
+ if [ "$allow_unauthenticated" = 'true' ]; then
156
+ gcloud run deploy "$service_name" \
157
+ --project "$project_id" \
158
+ --region "$region" \
159
+ --source "$source_dir" \
160
+ --allow-unauthenticated \
161
+ --update-env-vars "$update_env_vars_arg"
162
+ else
163
+ gcloud run deploy "$service_name" \
164
+ --project "$project_id" \
165
+ --region "$region" \
166
+ --source "$source_dir" \
167
+ --no-allow-unauthenticated \
168
+ --update-env-vars "$update_env_vars_arg"
169
+ fi
170
+ else
171
+ if [ "$allow_unauthenticated" = 'true' ]; then
172
+ gcloud run deploy "$service_name" \
173
+ --project "$project_id" \
174
+ --region "$region" \
175
+ --source "$source_dir" \
176
+ --allow-unauthenticated
177
+ else
178
+ gcloud run deploy "$service_name" \
179
+ --project "$project_id" \
180
+ --region "$region" \
181
+ --source "$source_dir" \
182
+ --no-allow-unauthenticated
183
+ fi
184
+ fi
185
+
186
+ service_url="$(gcloud run services describe "$service_name" \
187
+ --project "$project_id" \
188
+ --region "$region" \
189
+ --format='value(status.url)')"
190
+ if [ -n "$service_url" ]; then
191
+ service_env_key="$(service_name_to_env_key "$service_name")"
192
+ upsert_env_key_value "$FUNCTIONS_PROD_ENV_FILE" "$service_env_key" "$service_url"
193
+ echo "Updated $FUNCTIONS_PROD_ENV_FILE with ${service_env_key}."
194
+ else
195
+ echo "Warning: could not resolve URL for '$service_name'; skipped functions/.env.prod update." >&2
196
+ fi
197
+
198
+ if [ "$allow_unauthenticated" != 'true' ]; then
199
+ gcloud run services remove-iam-policy-binding "$service_name" \
200
+ --project "$project_id" \
201
+ --region "$region" \
202
+ --member="allUsers" \
203
+ --role="roles/run.invoker" \
204
+ --quiet >/dev/null 2>&1 || true
205
+
206
+ if [ -n "$invoker_members_raw" ]; then
207
+ printf '%s\n' "$invoker_members_raw" | tr ',' '\n' | while IFS= read -r member; do
208
+ clean_member="$(trim "$member")"
209
+ if [ -z "$clean_member" ]; then
210
+ continue
211
+ fi
212
+ case "$clean_member" in
213
+ *:*) ;;
214
+ *) clean_member="serviceAccount:${clean_member}" ;;
215
+ esac
216
+ gcloud run services add-iam-policy-binding "$service_name" \
217
+ --project "$project_id" \
218
+ --region "$region" \
219
+ --member="$clean_member" \
220
+ --role="roles/run.invoker" \
221
+ --quiet >/dev/null
222
+ done
223
+ else
224
+ echo "Info: '$service_name' is private and INVOKER_MEMBERS is empty. Using existing Cloud Run invokers (for example, the default compute service account if already granted)." >&2
225
+ fi
226
+ fi
227
+ )
228
+
229
+ deployed_count=$((deployed_count + 1))
230
+ done
231
+
232
+ if [ "$deployed_count" -eq 0 ]; then
233
+ echo "No services deployed. Add a .deploy.env file under services/<service>/ to opt in."
234
+ exit 0
235
+ fi
236
+
237
+ echo "Services deploy complete. Deployed $deployed_count service(s)."
package/deploy.sh CHANGED
@@ -1,5 +1,92 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ check_repo() {
5
+ local repo_path="$1"
6
+ local label="$2"
7
+
8
+ if ! git -C "$repo_path" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
9
+ echo "Deploy aborted: ${label} repo not found at '$repo_path'." >&2
10
+ exit 1
11
+ fi
12
+
13
+ local current_branch
14
+ current_branch="$(git -C "$repo_path" rev-parse --abbrev-ref HEAD)"
15
+ if [ "$current_branch" != "main" ]; then
16
+ echo "Deploy aborted: ${label} branch is '$current_branch'. Switch to 'main' before deploying." >&2
17
+ exit 1
18
+ fi
19
+
20
+ if ! git -C "$repo_path" diff --quiet || ! git -C "$repo_path" diff --cached --quiet; then
21
+ echo "Deploy aborted: ${label} has uncommitted changes. Commit or stash before deploying." >&2
22
+ exit 1
23
+ fi
24
+
25
+ if ! git -C "$repo_path" rev-parse --abbrev-ref --symbolic-full-name @{upstream} >/dev/null 2>&1; then
26
+ echo "Deploy aborted: ${label} has no upstream set for 'main'. Set upstream before deploying." >&2
27
+ exit 1
28
+ fi
29
+
30
+ git -C "$repo_path" fetch --quiet
31
+
32
+ local counts behind ahead
33
+ counts="$(git -C "$repo_path" rev-list --left-right --count @{upstream}...HEAD)"
34
+ set -- $counts
35
+ behind="${1:-0}"
36
+ ahead="${2:-0}"
37
+ if [ "$behind" -ne 0 ] && [ "$ahead" -ne 0 ]; then
38
+ echo "Deploy aborted: ${label} branch has diverged (ahead $ahead, behind $behind). Pull/rebase and push." >&2
39
+ exit 1
40
+ fi
41
+ if [ "$behind" -ne 0 ]; then
42
+ echo "Deploy aborted: ${label} main is behind upstream by $behind commits. Pull before deploying." >&2
43
+ exit 1
44
+ fi
45
+ if [ "$ahead" -ne 0 ]; then
46
+ echo "Deploy aborted: ${label} main is ahead of upstream by $ahead commits. Push before deploying." >&2
47
+ exit 1
48
+ fi
49
+ }
50
+
51
+ warn_edge_subtree_sync() {
52
+ if ! git rev-parse --verify HEAD:edge >/dev/null 2>&1; then
53
+ echo "Deploy aborted: edge subtree path ('edge') is missing from HEAD." >&2
54
+ exit 1
55
+ fi
56
+
57
+ if ! git remote get-url edge-vue-components >/dev/null 2>&1; then
58
+ echo "Deploy aborted: remote 'edge-vue-components' is not configured." >&2
59
+ exit 1
60
+ fi
61
+
62
+ if ! git fetch --quiet edge-vue-components; then
63
+ echo "Deploy aborted: failed to fetch 'edge-vue-components'." >&2
64
+ exit 1
65
+ fi
66
+
67
+ local up_tree local_tree
68
+ if ! up_tree="$(git rev-parse edge-vue-components/main^{tree} 2>/dev/null)"; then
69
+ echo "Deploy aborted: could not resolve edge-vue-components/main." >&2
70
+ exit 1
71
+ fi
72
+ if ! local_tree="$(git rev-parse HEAD:edge 2>/dev/null)"; then
73
+ echo "Deploy aborted: could not resolve HEAD:edge." >&2
74
+ exit 1
75
+ fi
76
+
77
+ if [ "$up_tree" = "$local_tree" ]; then
78
+ echo "edge subtree is in sync with edge-vue-components/main"
79
+ else
80
+ echo "Warning: edge subtree differs from edge-vue-components/main. Continuing deploy." >&2
81
+ fi
82
+ }
83
+
84
+ check_repo "." "root"
85
+ warn_edge_subtree_sync
86
+
1
87
  pnpm run generate
2
88
  export NODE_ENV=production
3
89
  firebase deploy --only functions
4
90
  firebase deploy --only hosting
5
- firebase deploy --only firestore
91
+ firebase deploy --only firestore
92
+ firebase deploy --only storage
@@ -77,6 +77,47 @@ const register = reactive({
77
77
  requestedOrgId: '',
78
78
  })
79
79
 
80
+ const resolveAuthEmail = () => {
81
+ return (
82
+ edgeFirebase?.user?.email
83
+ || edgeFirebase?.user?.firebaseUser?.email
84
+ || edgeFirebase?.user?.firebaseUser?.providerData?.find(p => p?.email)?.email
85
+ || ''
86
+ )
87
+ }
88
+
89
+ const waitForUserSnapshot = async (timeoutMs = 8000) => {
90
+ const findUser = () => {
91
+ const users = Object.values(edgeFirebase.state?.users || {})
92
+ const stagedDocId = edgeFirebase?.user?.stagedDocId
93
+ const uid = edgeFirebase?.user?.uid
94
+ return users.find(u => (stagedDocId && u?.docId === stagedDocId) || (uid && u?.userId === uid))
95
+ }
96
+
97
+ if (findUser())
98
+ return true
99
+
100
+ return await new Promise((resolve) => {
101
+ let timeoutId = null
102
+ const stop = watch(
103
+ () => edgeFirebase.state?.users,
104
+ () => {
105
+ if (findUser()) {
106
+ stop()
107
+ if (timeoutId)
108
+ clearTimeout(timeoutId)
109
+ resolve(true)
110
+ }
111
+ },
112
+ { immediate: true, deep: true },
113
+ )
114
+ timeoutId = setTimeout(() => {
115
+ stop()
116
+ resolve(false)
117
+ }, timeoutMs)
118
+ })
119
+ }
120
+
80
121
  const onSubmit = async () => {
81
122
  state.registering = true
82
123
 
@@ -108,6 +149,9 @@ const onSubmit = async () => {
108
149
  if (state.showRegistrationCode || !props.registrationCode) {
109
150
  register.registrationCode = state.registrationCode
110
151
  }
152
+ if (state.provider === 'email' && register.email) {
153
+ register.meta.email = register.email
154
+ }
111
155
  const result = await edgeFirebase.registerUser(register, state.provider)
112
156
  state.error.error = !result.success
113
157
  if (result.message === `${props.requestedOrgIdLabel} already exists.`) {
@@ -118,6 +162,13 @@ const onSubmit = async () => {
118
162
  result.message = `${orgLabel} already exists. Please choose another.`
119
163
  }
120
164
  state.error.message = result.message.code ? result.message.code : result.message
165
+ if (result.success) {
166
+ const authEmail = resolveAuthEmail()
167
+ if (authEmail && (!register.meta.email || register.meta.email !== authEmail)) {
168
+ await waitForUserSnapshot()
169
+ await edgeFirebase.setUserMeta({ email: authEmail })
170
+ }
171
+ }
121
172
  }
122
173
 
123
174
  state.registering = false
@@ -122,11 +122,13 @@ const sanitizeQueryItems = (meta) => {
122
122
  return cleaned
123
123
  }
124
124
 
125
- const resetArrayItems = (field) => {
125
+ const resetArrayItems = (field, metaSource = null) => {
126
+ const meta = metaSource || modelValue.value?.meta || {}
127
+ const fieldMeta = meta?.[field]
126
128
  if (!state.arrayItems?.[field]) {
127
129
  state.arrayItems[field] = {}
128
130
  }
129
- for (const schemaItem of modelValue.value.meta[field].schema) {
131
+ for (const schemaItem of (fieldMeta?.schema || [])) {
130
132
  if (schemaItem.type === 'text') {
131
133
  state.arrayItems[field][schemaItem.field] = ''
132
134
  }
@@ -148,25 +150,35 @@ const resetArrayItems = (field) => {
148
150
  const openEditor = async () => {
149
151
  if (!props.editMode)
150
152
  return
151
- for (const key of Object.keys(modelValue.value?.meta || {})) {
152
- if (modelValue.value.meta[key]?.type === 'array' && modelValue.value.meta[key]?.schema) {
153
- if (!modelValue.value.meta[key]?.api) {
154
- resetArrayItems(key)
155
- }
153
+ const blockData = edgeFirebase.data[`${edgeGlobal.edgeState.organizationDocPath}/blocks`]?.[modelValue.value.blockId]
154
+ const templateMeta = blockData?.meta || modelValue.value?.meta || {}
155
+ const storedMeta = modelValue.value?.meta || {}
156
+ const mergedMeta = edgeGlobal.dupObject(templateMeta) || {}
157
+
158
+ for (const key of Object.keys(mergedMeta)) {
159
+ const storedField = storedMeta?.[key]
160
+ if (!storedField || typeof storedField !== 'object')
161
+ continue
162
+ if (storedField.queryItems && typeof storedField.queryItems === 'object') {
163
+ mergedMeta[key].queryItems = edgeGlobal.dupObject(storedField.queryItems)
164
+ }
165
+ if (storedField.limit !== undefined) {
166
+ mergedMeta[key].limit = storedField.limit
156
167
  }
157
168
  }
158
- state.draft = JSON.parse(JSON.stringify(modelValue.value?.values || {}))
159
- state.meta = JSON.parse(JSON.stringify(modelValue.value?.meta || {}))
160
- ensureQueryItemsDefaults(state.meta)
161
- const blockData = edgeFirebase.data[`${edgeGlobal.edgeState.organizationDocPath}/blocks`]?.[modelValue.value.blockId]
162
- state.metaUpdate = edgeGlobal.dupObject(modelValue.value?.meta) || {}
163
- if (blockData?.meta) {
164
- for (const key of Object.keys(blockData.meta)) {
165
- if (!(key in state.metaUpdate)) {
166
- state.metaUpdate[key] = blockData.meta[key]
169
+
170
+ for (const key of Object.keys(mergedMeta || {})) {
171
+ if (mergedMeta[key]?.type === 'array' && mergedMeta[key]?.schema) {
172
+ if (!mergedMeta[key]?.api) {
173
+ resetArrayItems(key, mergedMeta)
167
174
  }
168
175
  }
169
176
  }
177
+
178
+ state.draft = JSON.parse(JSON.stringify(modelValue.value?.values || {}))
179
+ state.meta = JSON.parse(JSON.stringify(mergedMeta || {}))
180
+ ensureQueryItemsDefaults(state.meta)
181
+ state.metaUpdate = edgeGlobal.dupObject(mergedMeta) || {}
170
182
  if (blockData?.values) {
171
183
  for (const key of Object.keys(blockData.values)) {
172
184
  if (!(key in state.draft)) {
@@ -682,6 +694,7 @@ const getTagsFromPosts = computed(() => {
682
694
  v-model="state.meta[entry.field].queryItems[option.field]"
683
695
  :option="option"
684
696
  :label="genTitleFromField(option)"
697
+ :multiple="option?.multiple || false"
685
698
  />
686
699
  </div>
687
700
  </template>