create-mercato-app 0.6.8-develop.7100.1.fbf66fca35 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/agentic/shared/AGENTS.md.template +3 -3
- package/agentic/shared/ai/harness/README.md +2 -2
- package/agentic/shared/ai/harness/RELEASE.md +6 -8
- package/agentic/shared/ai/harness/cases.json +0 -83
- package/agentic/shared/ai/harness/cases.schema.json +4 -4
- package/agentic/shared/ai/harness/validators.json +1 -1
- package/agentic/shared/ai/skills/om-auto-upgrade-0.6.7-to-0.7.0/SKILL.md +12 -3
- package/agentic/shared/scripts/run-agent-harness-release.mjs +11 -37
- package/dist/agentic/guides/module-facts.json +598 -5508
- package/dist/agentic/guides/module-facts.v2.json +598 -5594
- package/dist/agentic/guides/modules/ai_assistant/index.md +1 -1
- package/dist/agentic/guides/modules/api_docs/index.md +1 -1
- package/dist/agentic/guides/modules/api_keys/exact-override-targets.md +0 -1
- package/dist/agentic/guides/modules/api_keys/index.md +2 -4
- package/dist/agentic/guides/modules/attachments/di-registrations-rich.md +5 -6
- package/dist/agentic/guides/modules/attachments/di-service-tokens.md +4 -5
- package/dist/agentic/guides/modules/attachments/exact-override-targets.md +5 -6
- package/dist/agentic/guides/modules/attachments/index.md +2 -2
- package/dist/agentic/guides/modules/audit_logs/index.md +1 -1
- package/dist/agentic/guides/modules/auth/di-registrations-rich.md +2 -3
- package/dist/agentic/guides/modules/auth/di-service-tokens.md +2 -3
- package/dist/agentic/guides/modules/auth/exact-override-targets.md +2 -3
- package/dist/agentic/guides/modules/auth/index.md +1 -1
- package/dist/agentic/guides/modules/business_rules/index.md +1 -1
- package/dist/agentic/guides/modules/catalog/incoming-installed-contributions.md +0 -3
- package/dist/agentic/guides/modules/catalog/index.md +2 -2
- package/dist/agentic/guides/modules/channel_apns/index.md +1 -1
- package/dist/agentic/guides/modules/channel_expo/index.md +1 -1
- package/dist/agentic/guides/modules/channel_fcm/index.md +1 -1
- package/dist/agentic/guides/modules/channel_gmail/index.md +1 -1
- package/dist/agentic/guides/modules/channel_imap/index.md +1 -1
- package/dist/agentic/guides/modules/checkout/index.md +1 -1
- package/dist/agentic/guides/modules/communication_channels/di-registrations-rich.md +8 -9
- package/dist/agentic/guides/modules/communication_channels/exact-override-targets.md +8 -9
- package/dist/agentic/guides/modules/communication_channels/incoming-installed-contributions.md +0 -2
- package/dist/agentic/guides/modules/communication_channels/index.md +3 -3
- package/dist/agentic/guides/modules/configs/index.md +1 -1
- package/dist/agentic/guides/modules/content/index.md +1 -1
- package/dist/agentic/guides/modules/currencies/index.md +1 -1
- package/dist/agentic/guides/modules/customer_accounts/index.md +1 -1
- package/dist/agentic/guides/modules/customers/active-extension-bindings.md +3 -6
- package/dist/agentic/guides/modules/customers/contribution-resolutions.md +0 -1
- package/dist/agentic/guides/modules/customers/events.md +1 -2
- package/dist/agentic/guides/modules/customers/incoming-installed-contributions.md +0 -7
- package/dist/agentic/guides/modules/customers/index.md +4 -4
- package/dist/agentic/guides/modules/customers/umes-contributions.md +0 -1
- package/dist/agentic/guides/modules/customers/umes-hosts.md +0 -1
- package/dist/agentic/guides/modules/dashboards/index.md +1 -1
- package/dist/agentic/guides/modules/data_sync/exact-override-targets.md +2 -2
- package/dist/agentic/guides/modules/data_sync/index.md +1 -1
- package/dist/agentic/guides/modules/data_sync/workers.md +2 -2
- package/dist/agentic/guides/modules/design_system/index.md +1 -1
- package/dist/agentic/guides/modules/devices/index.md +1 -1
- package/dist/agentic/guides/modules/dictionaries/index.md +1 -1
- package/dist/agentic/guides/modules/directory/exact-override-targets.md +0 -2
- package/dist/agentic/guides/modules/directory/index.md +3 -5
- package/dist/agentic/guides/modules/entities/index.md +1 -1
- package/dist/agentic/guides/modules/eudr/index.md +1 -1
- package/dist/agentic/guides/modules/events/index.md +1 -1
- package/dist/agentic/guides/modules/feature_toggles/index.md +1 -1
- package/dist/agentic/guides/modules/gateway_stripe/exact-override-targets.md +1 -1
- package/dist/agentic/guides/modules/gateway_stripe/index.md +1 -1
- package/dist/agentic/guides/modules/gateway_stripe/workers.md +1 -1
- package/dist/agentic/guides/modules/generators/index.md +1 -1
- package/dist/agentic/guides/modules/inbox_ops/index.md +1 -1
- package/dist/agentic/guides/modules/integrations/index.md +1 -1
- package/dist/agentic/guides/modules/messages/index.md +1 -1
- package/dist/agentic/guides/modules/notifications/index.md +1 -1
- package/dist/agentic/guides/modules/onboarding/index.md +1 -1
- package/dist/agentic/guides/modules/payment_gateways/index.md +1 -1
- package/dist/agentic/guides/modules/perspectives/index.md +1 -1
- package/dist/agentic/guides/modules/planner/index.md +1 -1
- package/dist/agentic/guides/modules/portal/index.md +1 -1
- package/dist/agentic/guides/modules/progress/index.md +1 -1
- package/dist/agentic/guides/modules/push_notifications/index.md +1 -1
- package/dist/agentic/guides/modules/query_index/index.md +1 -1
- package/dist/agentic/guides/modules/record_locks/index.md +1 -1
- package/dist/agentic/guides/modules/resources/index.md +1 -1
- package/dist/agentic/guides/modules/sales/active-extension-bindings.md +0 -1
- package/dist/agentic/guides/modules/sales/contribution-resolutions.md +0 -1
- package/dist/agentic/guides/modules/sales/exact-override-targets.md +4 -5
- package/dist/agentic/guides/modules/sales/host-extension-points.md +1 -1
- package/dist/agentic/guides/modules/sales/incoming-installed-contributions.md +0 -2
- package/dist/agentic/guides/modules/sales/index.md +3 -3
- package/dist/agentic/guides/modules/sales/setup.md +1 -1
- package/dist/agentic/guides/modules/sales/umes-contributions.md +0 -1
- package/dist/agentic/guides/modules/sales/umes-diagnostics.md +0 -1
- package/dist/agentic/guides/modules/sales/umes-hosts.md +0 -9
- package/dist/agentic/guides/modules/scheduler/cli-commands.md +0 -1
- package/dist/agentic/guides/modules/scheduler/exact-override-targets.md +1 -3
- package/dist/agentic/guides/modules/scheduler/index.md +2 -2
- package/dist/agentic/guides/modules/scheduler/workers.md +1 -2
- package/dist/agentic/guides/modules/search/index.md +1 -1
- package/dist/agentic/guides/modules/security/index.md +1 -1
- package/dist/agentic/guides/modules/security/owned-contract-module-metadata.md +1 -1
- package/dist/agentic/guides/modules/shipping_carriers/index.md +1 -1
- package/dist/agentic/guides/modules/sso/index.md +1 -1
- package/dist/agentic/guides/modules/staff/index.md +1 -1
- package/dist/agentic/guides/modules/storage_s3/index.md +1 -1
- package/dist/agentic/guides/modules/sync_akeneo/index.md +1 -1
- package/dist/agentic/guides/modules/sync_excel/index.md +1 -1
- package/dist/agentic/guides/modules/system_status_overlays/index.md +1 -1
- package/dist/agentic/guides/modules/translations/index.md +1 -1
- package/dist/agentic/guides/modules/warranty_claims/index.md +1 -1
- package/dist/agentic/guides/modules/webhooks/index.md +1 -1
- package/dist/agentic/guides/modules/wms/index.md +1 -1
- package/dist/agentic/guides/modules/workflows/index.md +1 -1
- package/dist/agentic/guides/reference-module-facts.json +2 -2
- package/dist/agentic/guides/reference-modules/example/index.md +1 -1
- package/dist/agentic/guides/upstream/manifest.json +1 -1
- package/dist/agentic/shared/AGENTS.md.template +3 -3
- package/dist/agentic/shared/ai/harness/README.md +2 -2
- package/dist/agentic/shared/ai/harness/RELEASE.md +6 -8
- package/dist/agentic/shared/ai/harness/cases.json +0 -83
- package/dist/agentic/shared/ai/harness/cases.schema.json +4 -4
- package/dist/agentic/shared/ai/harness/validators.json +1 -1
- package/dist/agentic/shared/ai/skills/om-auto-upgrade-0.6.7-to-0.7.0/SKILL.md +12 -3
- package/dist/agentic/shared/scripts/run-agent-harness-release.mjs +11 -37
- package/package.json +4 -5
- package/template/.env.example +5 -65
- package/template/.yarnrc.yml.template +5 -9
- package/template/AGENTS.md +3 -3
- package/template/Dockerfile +4 -15
- package/template/docker/scripts/init-or-migrate.sh +27 -3
- package/template/docker-compose.fullapp.dev.yml +0 -56
- package/template/docker-compose.fullapp.yml +0 -78
- package/template/next.config.ts +0 -3
- package/template/package.json.template +0 -3
- package/template/src/components/DemoFeedbackWidget.tsx +12 -4
- package/template/src/components/__tests__/DemoFeedbackWidget.test.tsx +53 -0
- package/template/src/components/__tests__/demoFeedbackFlag.test.ts +38 -0
- package/template/src/components/demoFeedbackFlag.ts +15 -0
- package/template/src/i18n/de.json +11 -13
- package/template/src/i18n/en.json +2 -4
- package/template/src/i18n/es.json +11 -13
- package/template/src/i18n/ko.json +2 -4
- package/template/src/i18n/pl.json +2 -4
- package/template/src/modules/example/__integration__/TC-EXAMPLE-017-bound-extension-ui.spec.ts +19 -31
- package/template/src/modules/example/components/TodosTable.tsx +0 -2
- package/template/src/modules.ts +0 -12
- package/dist/agentic/guides/modules/api_keys/di-registrations-rich.md +0 -11
- package/dist/agentic/guides/modules/api_keys/di-service-tokens.md +0 -11
- package/dist/agentic/guides/modules/channel_discord/acl-features.md +0 -13
- package/dist/agentic/guides/modules/channel_discord/ai-agents.md +0 -11
- package/dist/agentic/guides/modules/channel_discord/backend-pages.md +0 -11
- package/dist/agentic/guides/modules/channel_discord/cli-commands.md +0 -13
- package/dist/agentic/guides/modules/channel_discord/contribution-resolutions.md +0 -14
- package/dist/agentic/guides/modules/channel_discord/di-registrations-rich.md +0 -12
- package/dist/agentic/guides/modules/channel_discord/exact-override-targets.md +0 -26
- package/dist/agentic/guides/modules/channel_discord/index.md +0 -22
- package/dist/agentic/guides/modules/channel_discord/owned-contract-module-metadata.md +0 -11
- package/dist/agentic/guides/modules/channel_discord/setup.md +0 -11
- package/dist/agentic/guides/modules/channel_discord/umes-contributions.md +0 -14
- package/dist/agentic/guides/modules/channel_discord/workers.md +0 -12
- package/dist/agentic/guides/modules/directory/di-registrations-rich.md +0 -12
- package/dist/agentic/guides/modules/directory/di-service-tokens.md +0 -12
- package/dist/agentic/guides/modules/documents/acl-features.md +0 -17
- package/dist/agentic/guides/modules/documents/backend-pages.md +0 -13
- package/dist/agentic/guides/modules/documents/contribution-resolutions.md +0 -27
- package/dist/agentic/guides/modules/documents/di-registrations-rich.md +0 -21
- package/dist/agentic/guides/modules/documents/domain-commands.md +0 -37
- package/dist/agentic/guides/modules/documents/encryption.md +0 -11
- package/dist/agentic/guides/modules/documents/entities.md +0 -21
- package/dist/agentic/guides/modules/documents/events.md +0 -25
- package/dist/agentic/guides/modules/documents/exact-override-targets.md +0 -39
- package/dist/agentic/guides/modules/documents/index.md +0 -25
- package/dist/agentic/guides/modules/documents/notifications.md +0 -13
- package/dist/agentic/guides/modules/documents/owned-contract-module-metadata.md +0 -11
- package/dist/agentic/guides/modules/documents/setup.md +0 -11
- package/dist/agentic/guides/modules/documents/umes-contributions.md +0 -27
- package/dist/agentic/guides/modules/documents/umes-hosts.md +0 -36
package/template/.env.example
CHANGED
|
@@ -46,33 +46,6 @@ NEXTAUTH_SECRET=
|
|
|
46
46
|
|
|
47
47
|
# Public URL used in emails and onboarding flows
|
|
48
48
|
APP_URL=http://localhost:3000
|
|
49
|
-
|
|
50
|
-
# Documents realtime collaboration (optional — run `yarn documents:collab`)
|
|
51
|
-
# All collab vars are optional: leave NEXT_PUBLIC_DOCUMENTS_COLLAB_URL unset to
|
|
52
|
-
# disable realtime collaboration; the document editor keeps working in
|
|
53
|
-
# single-user mode. The URL is embedded at app build time; use a
|
|
54
|
-
# browser-reachable wss:// URL in production. App and sidecar must share the
|
|
55
|
-
# v2 secret. When DOCUMENTS_COLLAB_ALLOWED_ORIGINS is unset, the sidecar trusts
|
|
56
|
-
# APP_URL / NEXT_PUBLIC_APP_URL.
|
|
57
|
-
DOCUMENTS_COLLAB_PORT=4101
|
|
58
|
-
DOCUMENTS_COLLAB_REDIS_URL=redis://localhost:6379
|
|
59
|
-
# Required with collaboration Redis in production. Every sidecar replica in one
|
|
60
|
-
# deployment must share this value, and deployments sharing Redis must use
|
|
61
|
-
# different values. Allowed: letters, numbers, dot, underscore, colon, dash.
|
|
62
|
-
DOCUMENTS_COLLAB_REDIS_PREFIX=open-mercato:documents:collab:local
|
|
63
|
-
# Local development example: ws://localhost:4101. Production runtime ignores a
|
|
64
|
-
# loopback value and falls back to single-user editing without failing the build.
|
|
65
|
-
NEXT_PUBLIC_DOCUMENTS_COLLAB_URL=
|
|
66
|
-
# Optional exact comma-separated browser origins; defaults to APP_URL.
|
|
67
|
-
DOCUMENTS_COLLAB_ALLOWED_ORIGINS=
|
|
68
|
-
# Required only when collaboration is enabled; generate a unique high-entropy
|
|
69
|
-
# value containing at least 32 UTF-8 bytes. The sidecar fails closed when the
|
|
70
|
-
# secret is missing or too short.
|
|
71
|
-
DOCUMENTS_COLLAB_JWT_SECRET_V2=
|
|
72
|
-
# Optional v1 rollout secret; when set it must differ from the v2 secret.
|
|
73
|
-
# DOCUMENTS_COLLAB_JWT_SECRET=
|
|
74
|
-
# Standalone sidecars set this to the app root (for example /app in Docker).
|
|
75
|
-
DOCUMENTS_COLLAB_APP_ROOT=.
|
|
76
49
|
# Additional trusted origins for app requests that must pass host validation.
|
|
77
50
|
# APP_URL is required in production for security-sensitive email links.
|
|
78
51
|
# In dev mode these values also govern the HMR WebSocket (/_next/webpack-hmr):
|
|
@@ -91,7 +64,11 @@ DOCUMENTS_COLLAB_APP_ROOT=.
|
|
|
91
64
|
|
|
92
65
|
# Enable self-service onboarding (default: false)
|
|
93
66
|
SELF_SERVICE_ONBOARDING_ENABLED=false
|
|
94
|
-
# Enable Open Mercato demo widgets
|
|
67
|
+
# Enable Open Mercato demo widgets. The code default when this is unset is
|
|
68
|
+
# `true` — scaffolds ship it off explicitly, so keep the line rather than
|
|
69
|
+
# deleting it. To hide just the contact/feedback widget while keeping demo mode
|
|
70
|
+
# on, set the `ff_om_hide_contact` key in the browser's localStorage:
|
|
71
|
+
# https://docs.open-mercato.dev/architecture/configuration-decision-guide#ff-om-hide-contact
|
|
95
72
|
DEMO_MODE=false
|
|
96
73
|
|
|
97
74
|
# Hide the backend footer status bar (app version + terms/privacy links) (default: false)
|
|
@@ -135,10 +112,6 @@ OM_SECURITY_WEBAUTHN_RP_NAME=Open Mercato
|
|
|
135
112
|
# Defaults to the hostname from APP_URL / NEXT_PUBLIC_APP_URL when left blank.
|
|
136
113
|
OM_SECURITY_WEBAUTHN_RP_ID=
|
|
137
114
|
OM_SECURITY_RECOVERY_CODE_COUNT=10
|
|
138
|
-
# ⚠️ EMERGENCY BREAK-GLASS ONLY — when true, MFA enforcement is DISABLED platform-wide:
|
|
139
|
-
# all MFA challenges and enrollment redirects are skipped and sudo step-up always falls back
|
|
140
|
-
# to password. Keep false in normal operation; when enabled the app emits a warn-level
|
|
141
|
-
# structured log at startup and on every bypassed challenge so the event is auditable.
|
|
142
115
|
OM_SECURITY_MFA_EMERGENCY_BYPASS=false
|
|
143
116
|
|
|
144
117
|
# Outgoing email sender priority:
|
|
@@ -276,11 +249,6 @@ OM_QUERY_INDEX_AUTO_REINDEX_DEBOUNCE_MS=30000
|
|
|
276
249
|
# Skip immediate coverage recalculation on read (default: false)
|
|
277
250
|
OPTIMIZE_INDEX_COVERAGE_STATS=false
|
|
278
251
|
|
|
279
|
-
# Maximum reported list total (default: 10000). Counting stops after this many
|
|
280
|
-
# matching rows; capped totals render as "10 000+" and responses carry
|
|
281
|
-
# totalIsCapped: true. 0 disables capping and restores exact counts everywhere.
|
|
282
|
-
OM_LIST_COUNT_CAP=10000
|
|
283
|
-
|
|
284
252
|
# Continue serving results from entity indexes when coverage gaps are detected (default: true)
|
|
285
253
|
FORCE_QUERY_INDEX_ON_PARTIAL_INDEXES=true
|
|
286
254
|
|
|
@@ -290,11 +258,6 @@ OM_SEARCH_MIN_LEN=3
|
|
|
290
258
|
OM_SEARCH_ENABLE_PARTIAL=true
|
|
291
259
|
OM_SEARCH_HASH_ALGO=sha256
|
|
292
260
|
OM_SEARCH_STORE_RAW_TOKENS=false
|
|
293
|
-
# Run like/ilike on PLAINTEXT base columns as exact SQL ILIKE instead of the approximate
|
|
294
|
-
# search-token match (encrypted columns always keep the token path). Off by default: token
|
|
295
|
-
# matching avoids the full scan an unanchored ILIKE may need, but it is approximate --
|
|
296
|
-
# fragments shorter than OM_SEARCH_MIN_LEN vanish, so short document numbers over-match.
|
|
297
|
-
OM_SEARCH_USE_ILIKE_FOR_NON_ENCRYPTED_FIELDS=false
|
|
298
261
|
# Bound token fan-out per indexed record (0 disables an individual limit)
|
|
299
262
|
OM_SEARCH_MAX_FIELD_CHARS=20000
|
|
300
263
|
OM_SEARCH_MAX_TOKENS_PER_FIELD=5000
|
|
@@ -306,14 +269,6 @@ OM_SEARCH_MAX_TOKENS_PER_RECORD=20000
|
|
|
306
269
|
# OM_SEARCH_FIELD_BLOCKLIST=body,customers:customer_interaction@notes
|
|
307
270
|
# TTL for the process-level "does this entity have search tokens?" probe cache (ms, 0 disables).
|
|
308
271
|
OM_SEARCH_TOKEN_PRESENCE_CACHE_MS=30000
|
|
309
|
-
# Offer base `customers:customer_entity` rows as search results alongside the person/company
|
|
310
|
-
# profiles. Off by default: a customer is found through their profile, which is what carries the
|
|
311
|
-
# detail-page link, so the base row only adds a second, navigation-less copy of the same customer
|
|
312
|
-
# to global search. Turn it on to restore the previous behavior -- both rows are returned and the
|
|
313
|
-
# presenter enricher merges the pair back into one canonical result. Read-side only: the rows stay
|
|
314
|
-
# in search_tokens either way, because the People/Companies list search and the query engines'
|
|
315
|
-
# encrypted like/ilike rewrite resolve ids through them.
|
|
316
|
-
OM_SEARCH_CUSTOMERS_INDEX_BASE_ENTITY=false
|
|
317
272
|
|
|
318
273
|
# Search module debug logging (Meilisearch, vector search, reindex operations)
|
|
319
274
|
OM_SEARCH_DEBUG=false
|
|
@@ -795,21 +750,6 @@ ACME_EMAIL=
|
|
|
795
750
|
# OM_CHANNEL_IMAP_ALLOW_INTERNAL_HOSTS=false
|
|
796
751
|
# OM_CHANNEL_IMAP_HARD_CAP_PER_POLL=200
|
|
797
752
|
# OM_CHANNEL_IMAP_SOCKET_TIMEOUT_MS=60000
|
|
798
|
-
# Discord bot channel. Normally connected interactively in /backend/integrations;
|
|
799
|
-
# set the three credential vars only for deployment-managed bootstrap. They are
|
|
800
|
-
# applied on tenant creation and can be re-applied with
|
|
801
|
-
# `yarn mercato channel_discord configure-from-env --tenant <id> --org <id>`.
|
|
802
|
-
# OM_CHANNEL_DISCORD_BOT_TOKEN=
|
|
803
|
-
# OM_CHANNEL_DISCORD_APPLICATION_ID=
|
|
804
|
-
# OM_CHANNEL_DISCORD_PUBLIC_KEY=
|
|
805
|
-
# OM_CHANNEL_DISCORD_GUILD_ID=
|
|
806
|
-
# OM_CHANNEL_DISCORD_DEFAULT_CHANNEL_ID=
|
|
807
|
-
# Overwrite credentials that already exist for the tenant (default: keep them).
|
|
808
|
-
# OM_CHANNEL_DISCORD_FORCE_PRECONFIGURE=false
|
|
809
|
-
# Skip opening gateway WebSockets (CI / send-only deployments).
|
|
810
|
-
# OM_CHANNEL_DISCORD_GATEWAY_DISABLED=false
|
|
811
|
-
# Discord REST request timeout in milliseconds.
|
|
812
|
-
# OM_CHANNEL_DISCORD_REQUEST_TIMEOUT_MS=15000
|
|
813
753
|
|
|
814
754
|
# --- Logging (structured logging facade, @open-mercato/shared/lib/logger) ---
|
|
815
755
|
# Docs: https://docs.openmercato.com/framework/runtime/logging
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
nodeLinker: node-modules
|
|
2
2
|
|
|
3
3
|
# Yarn refuses versions younger than `npmMinimalAgeGate` (default 1 day) with
|
|
4
|
-
# "All versions satisfying ... are quarantined".
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# third-party package keeps the cooldown. A per-scope `npmRegistryServer` entry
|
|
10
|
-
# shadows a top-level `npmMinimalAgeGate: 0`, so the opt-out has to be
|
|
11
|
-
# expressed as a preapproval.
|
|
12
|
-
npmMinimalAgeGate: 5d
|
|
4
|
+
# "All versions satisfying ... are quarantined". This app pins the exact
|
|
5
|
+
# @open-mercato release it was scaffolded from, which is usually minutes old, so
|
|
6
|
+
# the first-party scope opts out while every third-party package keeps the
|
|
7
|
+
# cooldown. A per-scope `npmRegistryServer` entry shadows a top-level
|
|
8
|
+
# `npmMinimalAgeGate: 0`, so the opt-out has to be expressed as a preapproval.
|
|
13
9
|
npmPreapprovedPackages:
|
|
14
10
|
- "@open-mercato/*"
|
|
15
11
|
{{REGISTRY_CONFIG}}
|
package/template/AGENTS.md
CHANGED
|
@@ -12,7 +12,7 @@ Route first; never probe unmatched context.
|
|
|
12
12
|
- Put entities in `src/modules/<id>/data/entities.ts`; API routes need per-method `metadata` + `openApi`.
|
|
13
13
|
- Editable records expose `updated_at`/`updatedAt`; custom update/delete clients send the version and surface 409s.
|
|
14
14
|
- Run `yarn db:generate`, review scoped SQL/snapshot, and ask before applying it.
|
|
15
|
-
- Run `yarn generate` after discovery files
|
|
15
|
+
- Run `yarn generate` after discovery files, `src/modules.ts`, routes, pages, events, widgets, agents, tools, or workflows change.
|
|
16
16
|
- Contract-surface changes (route/schema/ID/export/seam/signature/event payload/CLI) MUST read `.ai/guides/upstream/BACKWARD_COMPATIBILITY.md`; tenant/org scope alone is not a contract.
|
|
17
17
|
- Localize strings; use shared UI/tokens and cover loading/empty/error/conflict/keyboard/a11y.
|
|
18
18
|
|
|
@@ -23,8 +23,8 @@ Route first; never probe unmatched context.
|
|
|
23
23
|
## Never
|
|
24
24
|
|
|
25
25
|
- Never leak tenants, trust payload scope, or treat missing scope as unrestricted.
|
|
26
|
-
- Never edit `node_modules
|
|
27
|
-
- Never use cross-module ORM relations; use IDs/snapshots
|
|
26
|
+
- Never edit `node_modules`, `.mercato/generated/**`, generated facts, or shipped migrations.
|
|
27
|
+
- Never use cross-module ORM relations; use IDs/snapshots, events, enrichers, extensions, or optional DI.
|
|
28
28
|
- Never use raw admin `fetch`/`<form>`, ad hoc crypto/cache/queues, role-name guards, or direct mutations when helpers exist.
|
|
29
29
|
- Never hard-code user strings/status colors; expose secrets/transcripts; or guess answerable contracts.
|
|
30
30
|
|
package/template/Dockerfile
CHANGED
|
@@ -3,8 +3,6 @@ FROM node:24-alpine AS builder
|
|
|
3
3
|
ENV NEXT_TELEMETRY_DISABLED=1 \
|
|
4
4
|
PATH=/app/node_modules/.bin:$PATH
|
|
5
5
|
ARG OPEN_MERCATO_DOCKER_REGISTRY_HOST=host.docker.internal
|
|
6
|
-
ARG NEXT_PUBLIC_DOCUMENTS_COLLAB_URL
|
|
7
|
-
ENV NEXT_PUBLIC_DOCUMENTS_COLLAB_URL=${NEXT_PUBLIC_DOCUMENTS_COLLAB_URL}
|
|
8
6
|
|
|
9
7
|
WORKDIR /app
|
|
10
8
|
|
|
@@ -60,31 +58,22 @@ COPY docker/scripts/init-or-migrate.sh /app/docker/scripts/init-or-migrate.sh
|
|
|
60
58
|
RUN chmod +x /app/docker/scripts/dev-entrypoint.sh
|
|
61
59
|
RUN chmod +x /app/docker/scripts/init-or-migrate.sh
|
|
62
60
|
|
|
63
|
-
EXPOSE 3000
|
|
61
|
+
EXPOSE 3000
|
|
64
62
|
CMD ["/bin/sh", "/app/docker/scripts/dev-entrypoint.sh"]
|
|
65
63
|
|
|
66
64
|
FROM node:24-alpine AS runner
|
|
67
65
|
|
|
68
66
|
ARG CONTAINER_PORT=3000
|
|
69
|
-
ARG DOCUMENTS_COLLAB_PORT=4101
|
|
70
67
|
ARG OPEN_MERCATO_DOCKER_REGISTRY_HOST=host.docker.internal
|
|
71
|
-
# Chromium backs the Documents PDF export (puppeteer-core). Build with
|
|
72
|
-
# --build-arg INSTALL_CHROMIUM=1 to include it; PDF export otherwise returns 503.
|
|
73
|
-
ARG INSTALL_CHROMIUM=0
|
|
74
68
|
|
|
75
69
|
ENV NODE_ENV=production \
|
|
76
70
|
NEXT_TELEMETRY_DISABLED=1 \
|
|
77
71
|
PATH=/app/node_modules/.bin:$PATH \
|
|
78
|
-
PORT=${CONTAINER_PORT}
|
|
79
|
-
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
|
|
72
|
+
PORT=${CONTAINER_PORT}
|
|
80
73
|
|
|
81
74
|
WORKDIR /app
|
|
82
75
|
|
|
83
|
-
RUN
|
|
84
|
-
apk add --no-cache ca-certificates chromium openssl; \
|
|
85
|
-
else \
|
|
86
|
-
apk add --no-cache ca-certificates openssl; \
|
|
87
|
-
fi
|
|
76
|
+
RUN apk add --no-cache ca-certificates openssl
|
|
88
77
|
RUN corepack enable
|
|
89
78
|
|
|
90
79
|
COPY package.json yarn.lock .yarnrc.yml ./
|
|
@@ -118,5 +107,5 @@ RUN adduser -D -u 1001 omuser \
|
|
|
118
107
|
|
|
119
108
|
USER omuser
|
|
120
109
|
|
|
121
|
-
EXPOSE ${CONTAINER_PORT}
|
|
110
|
+
EXPOSE ${CONTAINER_PORT}
|
|
122
111
|
CMD ["yarn", "start"]
|
|
@@ -4,6 +4,7 @@ set -e
|
|
|
4
4
|
MARKER_FILE="${INIT_MARKER_FILE:-/tmp/init-marker/.seeded}"
|
|
5
5
|
INIT_COMMAND="${INIT_COMMAND:-yarn initialize}"
|
|
6
6
|
MIGRATE_COMMAND="${MIGRATE_COMMAND:-yarn db:migrate}"
|
|
7
|
+
SYNC_ROLE_ACLS_COMMAND="${SYNC_ROLE_ACLS_COMMAND:-yarn mercato auth sync-role-acls}"
|
|
7
8
|
ALREADY_INITIALIZED_PATTERN='Initialization aborted: found [0-9][0-9]* existing user\(s\) in the database\.'
|
|
8
9
|
CLI_NOT_FOUND_PATTERN='command not found: mercato'
|
|
9
10
|
|
|
@@ -11,12 +12,16 @@ run_command_with_cli_recovery() {
|
|
|
11
12
|
COMMAND="$1"
|
|
12
13
|
LOG_FILE="$2"
|
|
13
14
|
|
|
14
|
-
if
|
|
15
|
+
# `STATUS=$?` after a failed `if ...; then ...; fi` reads the compound statement's own
|
|
16
|
+
# status, which POSIX defines as 0 — so every failure used to be reported as success and
|
|
17
|
+
# a broken migration silently fell through to `yarn start`. Capture the status directly.
|
|
18
|
+
STATUS=0
|
|
19
|
+
sh -lc "${COMMAND}" >"${LOG_FILE}" 2>&1 || STATUS=$?
|
|
20
|
+
|
|
21
|
+
if [ "${STATUS}" -eq 0 ]; then
|
|
15
22
|
return 0
|
|
16
23
|
fi
|
|
17
24
|
|
|
18
|
-
STATUS=$?
|
|
19
|
-
|
|
20
25
|
if ! grep -Fq "${CLI_NOT_FOUND_PATTERN}" "${LOG_FILE}"; then
|
|
21
26
|
return "${STATUS}"
|
|
22
27
|
fi
|
|
@@ -26,6 +31,23 @@ run_command_with_cli_recovery() {
|
|
|
26
31
|
sh -lc "${COMMAND}" >"${LOG_FILE}" 2>&1
|
|
27
32
|
}
|
|
28
33
|
|
|
34
|
+
# Modules enabled after the database was first seeded declare new `defaultRoleFeatures`
|
|
35
|
+
# that existing tenants never received — `mercato init` applies them only at bootstrap.
|
|
36
|
+
# The sync command is idempotent, so run it after every migration and keep it non-fatal:
|
|
37
|
+
# a stale grant set must not block the container from starting.
|
|
38
|
+
sync_role_acls() {
|
|
39
|
+
SYNC_LOG_FILE="$(mktemp)"
|
|
40
|
+
|
|
41
|
+
if run_command_with_cli_recovery "${SYNC_ROLE_ACLS_COMMAND}" "${SYNC_LOG_FILE}"; then
|
|
42
|
+
cat "${SYNC_LOG_FILE}"
|
|
43
|
+
else
|
|
44
|
+
echo "WARNING: role ACL sync failed; newly enabled modules may be inaccessible until it is run manually."
|
|
45
|
+
cat "${SYNC_LOG_FILE}"
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
rm -f "${SYNC_LOG_FILE}"
|
|
49
|
+
}
|
|
50
|
+
|
|
29
51
|
if [ ! -f "${MARKER_FILE}" ]; then
|
|
30
52
|
echo "First run: full initialization..."
|
|
31
53
|
LOG_FILE="$(mktemp)"
|
|
@@ -48,6 +70,7 @@ if [ ! -f "${MARKER_FILE}" ]; then
|
|
|
48
70
|
if run_command_with_cli_recovery "${MIGRATE_COMMAND}" "${RECOVERY_LOG_FILE}"; then
|
|
49
71
|
cat "${RECOVERY_LOG_FILE}"
|
|
50
72
|
rm -f "${RECOVERY_LOG_FILE}"
|
|
73
|
+
sync_role_acls
|
|
51
74
|
else
|
|
52
75
|
RECOVERY_STATUS=$?
|
|
53
76
|
cat "${RECOVERY_LOG_FILE}"
|
|
@@ -70,6 +93,7 @@ LOG_FILE="$(mktemp)"
|
|
|
70
93
|
if run_command_with_cli_recovery "${MIGRATE_COMMAND}" "${LOG_FILE}"; then
|
|
71
94
|
cat "${LOG_FILE}"
|
|
72
95
|
rm -f "${LOG_FILE}"
|
|
96
|
+
sync_role_acls
|
|
73
97
|
exit 0
|
|
74
98
|
fi
|
|
75
99
|
|
|
@@ -40,8 +40,6 @@ services:
|
|
|
40
40
|
APP_URL: ${APP_URL:-http://localhost:3000}
|
|
41
41
|
# Additional trusted app/dev origins for the Open Mercato origin allowlist (resolveAllowedDevOrigins)
|
|
42
42
|
APP_ALLOWED_ORIGINS: ${APP_ALLOWED_ORIGINS:-}
|
|
43
|
-
NEXT_PUBLIC_DOCUMENTS_COLLAB_URL: ${NEXT_PUBLIC_DOCUMENTS_COLLAB_URL:-}
|
|
44
|
-
DOCUMENTS_COLLAB_JWT_SECRET_V2: ${DOCUMENTS_COLLAB_JWT_SECRET_V2:-dev-only-documents-collab-v2-secret}
|
|
45
43
|
CACHE_REDIS_URL: redis://redis:6379
|
|
46
44
|
CACHE_STRATEGY: ${CACHE_STRATEGY:-redis}
|
|
47
45
|
ENABLE_CRUD_API_CACHE: ${ENABLE_CRUD_API_CACHE:-true}
|
|
@@ -78,60 +76,6 @@ services:
|
|
|
78
76
|
networks:
|
|
79
77
|
- mercato-network-fullapp
|
|
80
78
|
|
|
81
|
-
documents-collab:
|
|
82
|
-
profiles:
|
|
83
|
-
- documents-collab
|
|
84
|
-
build:
|
|
85
|
-
context: .
|
|
86
|
-
target: dev
|
|
87
|
-
args:
|
|
88
|
-
- OPEN_MERCATO_DOCKER_REGISTRY_HOST=host.docker.internal
|
|
89
|
-
extra_hosts:
|
|
90
|
-
- "host.docker.internal:host-gateway"
|
|
91
|
-
command: ["yarn", "documents:collab"]
|
|
92
|
-
working_dir: /app
|
|
93
|
-
# Restart so a first boot that races the shared app build comes back up
|
|
94
|
-
# once its dependencies are present, instead of exiting and staying dead.
|
|
95
|
-
restart: unless-stopped
|
|
96
|
-
volumes:
|
|
97
|
-
- .:/app
|
|
98
|
-
- app_node_modules:/app/node_modules
|
|
99
|
-
ports:
|
|
100
|
-
- "${DOCUMENTS_COLLAB_PORT:-4101}:${DOCUMENTS_COLLAB_PORT:-4101}"
|
|
101
|
-
environment:
|
|
102
|
-
NODE_ENV: development
|
|
103
|
-
DATABASE_URL: postgres://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@postgres:5432/${POSTGRES_DB:-open-mercato}
|
|
104
|
-
JWT_SECRET: ${JWT_SECRET:-JWT}
|
|
105
|
-
APP_URL: ${APP_URL:-http://localhost:3000}
|
|
106
|
-
NEXT_PUBLIC_APP_URL: ${APP_URL:-http://localhost:3000}
|
|
107
|
-
DOCUMENTS_COLLAB_PORT: ${DOCUMENTS_COLLAB_PORT:-4101}
|
|
108
|
-
DOCUMENTS_COLLAB_REDIS_URL: redis://redis:6379
|
|
109
|
-
DOCUMENTS_COLLAB_REDIS_PREFIX: ${DOCUMENTS_COLLAB_REDIS_PREFIX:-open-mercato:documents:collab:${DEPLOY_ENV:-local}}
|
|
110
|
-
# Same Redis as the app so multi-instance collaboration sync works out of the box
|
|
111
|
-
# (DOCUMENTS_COLLAB_REDIS_URL wins when both are set).
|
|
112
|
-
REDIS_URL: redis://redis:6379
|
|
113
|
-
DOCUMENTS_COLLAB_ALLOWED_ORIGINS: ${DOCUMENTS_COLLAB_ALLOWED_ORIGINS:-http://localhost:3000}
|
|
114
|
-
DOCUMENTS_COLLAB_JWT_SECRET_V2: ${DOCUMENTS_COLLAB_JWT_SECRET_V2:-dev-only-documents-collab-v2-secret}
|
|
115
|
-
DOCUMENTS_COLLAB_JWT_SECRET: ${DOCUMENTS_COLLAB_JWT_SECRET:-}
|
|
116
|
-
DOCUMENTS_COLLAB_APP_ROOT: /app
|
|
117
|
-
# Mirror the app service's tenant-encryption configuration so the sidecar
|
|
118
|
-
# can decrypt the same data as the app.
|
|
119
|
-
TENANT_DATA_ENCRYPTION: ${TENANT_DATA_ENCRYPTION:-true}
|
|
120
|
-
TENANT_DATA_ENCRYPTION_DEBUG: ${TENANT_DATA_ENCRYPTION_DEBUG:-false}
|
|
121
|
-
TENANT_DATA_ENCRYPTION_FALLBACK_KEY: ${TENANT_DATA_ENCRYPTION_FALLBACK_KEY:-dev-tenant-encryption-fallback-key-32chars}
|
|
122
|
-
TENANT_DATA_ENCRYPTION_KEY: ${TENANT_DATA_ENCRYPTION_KEY:-}
|
|
123
|
-
CACHE_REDIS_URL: redis://redis:6379
|
|
124
|
-
CACHE_STRATEGY: ${CACHE_STRATEGY:-redis}
|
|
125
|
-
depends_on:
|
|
126
|
-
app:
|
|
127
|
-
condition: service_started
|
|
128
|
-
postgres:
|
|
129
|
-
condition: service_healthy
|
|
130
|
-
redis:
|
|
131
|
-
condition: service_healthy
|
|
132
|
-
networks:
|
|
133
|
-
- mercato-network-fullapp
|
|
134
|
-
|
|
135
79
|
postgres:
|
|
136
80
|
image: pgvector/pgvector:pg17-trixie
|
|
137
81
|
restart: unless-stopped
|
|
@@ -5,12 +5,6 @@ services:
|
|
|
5
5
|
context: .
|
|
6
6
|
args:
|
|
7
7
|
- CONTAINER_PORT=${CONTAINER_PORT:-3000}
|
|
8
|
-
- INSTALL_CHROMIUM=${INSTALL_CHROMIUM:-0}
|
|
9
|
-
# Optional: browser-reachable ws(s):// endpoint baked in at build time.
|
|
10
|
-
# Leave unset to disable realtime document collaboration (the editor
|
|
11
|
-
# falls back to single-user editing). Invalid production values are
|
|
12
|
-
# ignored at runtime instead of failing the image build.
|
|
13
|
-
- NEXT_PUBLIC_DOCUMENTS_COLLAB_URL=${NEXT_PUBLIC_DOCUMENTS_COLLAB_URL:-}
|
|
14
8
|
- OPEN_MERCATO_DOCKER_REGISTRY_HOST=host.docker.internal
|
|
15
9
|
extra_hosts:
|
|
16
10
|
- "host.docker.internal:host-gateway"
|
|
@@ -59,13 +53,7 @@ services:
|
|
|
59
53
|
JWT_LEGACY_CUTOVER_AT: "${JWT_LEGACY_CUTOVER_AT:-}"
|
|
60
54
|
NEXTAUTH_SECRET: ${NEXTAUTH_SECRET:-}
|
|
61
55
|
CONSENT_INTEGRITY_SECRET: ${CONSENT_INTEGRITY_SECRET:-}
|
|
62
|
-
# Set APP_URL to the public application origin in production deployments.
|
|
63
56
|
APP_URL: ${APP_URL:-http://localhost:3000}
|
|
64
|
-
# Optional: unset disables realtime document collaboration (single-user editing keeps working).
|
|
65
|
-
NEXT_PUBLIC_DOCUMENTS_COLLAB_URL: ${NEXT_PUBLIC_DOCUMENTS_COLLAB_URL:-}
|
|
66
|
-
# Required only when collaboration is enabled: a unique high-entropy shared secret
|
|
67
|
-
# (>= 32 UTF-8 bytes). The sidecar fails closed at runtime when it is missing or too short.
|
|
68
|
-
DOCUMENTS_COLLAB_JWT_SECRET_V2: ${DOCUMENTS_COLLAB_JWT_SECRET_V2:-}
|
|
69
57
|
# Additional trusted app/dev origins for the Open Mercato origin allowlist (resolveAllowedDevOrigins)
|
|
70
58
|
APP_ALLOWED_ORIGINS: ${APP_ALLOWED_ORIGINS:-}
|
|
71
59
|
REDIS_URL: ${REDIS_URL:-}
|
|
@@ -112,72 +100,6 @@ services:
|
|
|
112
100
|
networks:
|
|
113
101
|
- mercato-network-fullapp
|
|
114
102
|
|
|
115
|
-
documents-collab:
|
|
116
|
-
profiles:
|
|
117
|
-
- documents-collab
|
|
118
|
-
build:
|
|
119
|
-
context: .
|
|
120
|
-
args:
|
|
121
|
-
- CONTAINER_PORT=${CONTAINER_PORT:-3000}
|
|
122
|
-
- DOCUMENTS_COLLAB_PORT=${DOCUMENTS_COLLAB_PORT:-4101}
|
|
123
|
-
- INSTALL_CHROMIUM=${INSTALL_CHROMIUM:-0}
|
|
124
|
-
- NEXT_PUBLIC_DOCUMENTS_COLLAB_URL=${NEXT_PUBLIC_DOCUMENTS_COLLAB_URL:-}
|
|
125
|
-
- OPEN_MERCATO_DOCKER_REGISTRY_HOST=host.docker.internal
|
|
126
|
-
extra_hosts:
|
|
127
|
-
- "host.docker.internal:host-gateway"
|
|
128
|
-
command: ["yarn", "documents:collab"]
|
|
129
|
-
working_dir: /app
|
|
130
|
-
restart: unless-stopped
|
|
131
|
-
ports:
|
|
132
|
-
- "${DOCUMENTS_COLLAB_PORT:-4101}:${DOCUMENTS_COLLAB_PORT:-4101}"
|
|
133
|
-
# Liveness only: any HTTP reply (including 503 when collaboration is not
|
|
134
|
-
# configured) means the process is serving; a connection error means it is
|
|
135
|
-
# down. Readiness would false-negative on the optional collab feature.
|
|
136
|
-
healthcheck:
|
|
137
|
-
test: ["CMD-SHELL", "node -e \"require('http').get('http://127.0.0.1:'+(process.env.DOCUMENTS_COLLAB_PORT||4101)+'/healthz',r=>process.exit(0)).on('error',()=>process.exit(1))\""]
|
|
138
|
-
interval: 30s
|
|
139
|
-
timeout: 5s
|
|
140
|
-
retries: 3
|
|
141
|
-
environment:
|
|
142
|
-
NODE_ENV: production
|
|
143
|
-
DATABASE_URL: postgres://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@postgres:5432/${POSTGRES_DB:-open-mercato}
|
|
144
|
-
# Same required secret and bounded legacy-token window as the app service:
|
|
145
|
-
# the sidecar boots the app container and must never fall back to a
|
|
146
|
-
# published signing secret either.
|
|
147
|
-
JWT_SECRET: "${JWT_SECRET:?set JWT_SECRET in the .env file next to this compose file - generate one with: openssl rand -hex 32}"
|
|
148
|
-
JWT_LEGACY_GRACE_MINUTES: "${JWT_LEGACY_GRACE_MINUTES:-480}"
|
|
149
|
-
JWT_LEGACY_CUTOVER_AT: "${JWT_LEGACY_CUTOVER_AT:-}"
|
|
150
|
-
APP_URL: ${APP_URL:-http://localhost:3000}
|
|
151
|
-
NEXT_PUBLIC_APP_URL: ${APP_URL:-http://localhost:3000}
|
|
152
|
-
DOCUMENTS_COLLAB_PORT: ${DOCUMENTS_COLLAB_PORT:-4101}
|
|
153
|
-
DOCUMENTS_COLLAB_REDIS_URL: redis://redis:6379
|
|
154
|
-
DOCUMENTS_COLLAB_REDIS_PREFIX: ${DOCUMENTS_COLLAB_REDIS_PREFIX:-open-mercato:documents:collab:${DEPLOY_ENV:-local}}
|
|
155
|
-
# Same Redis as the app so multi-instance collaboration sync works out of the box
|
|
156
|
-
# (DOCUMENTS_COLLAB_REDIS_URL wins when both are set).
|
|
157
|
-
REDIS_URL: redis://redis:6379
|
|
158
|
-
DOCUMENTS_COLLAB_ALLOWED_ORIGINS: ${DOCUMENTS_COLLAB_ALLOWED_ORIGINS:-${APP_URL}}
|
|
159
|
-
# Required only when collaboration is enabled; the sidecar fails closed when missing/too short.
|
|
160
|
-
DOCUMENTS_COLLAB_JWT_SECRET_V2: ${DOCUMENTS_COLLAB_JWT_SECRET_V2:-}
|
|
161
|
-
DOCUMENTS_COLLAB_JWT_SECRET: ${DOCUMENTS_COLLAB_JWT_SECRET:-}
|
|
162
|
-
DOCUMENTS_COLLAB_APP_ROOT: /app
|
|
163
|
-
# Mirror the app service's tenant-encryption configuration so the sidecar
|
|
164
|
-
# can decrypt the same data as the app.
|
|
165
|
-
TENANT_DATA_ENCRYPTION: ${TENANT_DATA_ENCRYPTION:-true}
|
|
166
|
-
TENANT_DATA_ENCRYPTION_DEBUG: ${TENANT_DATA_ENCRYPTION_DEBUG:-false}
|
|
167
|
-
TENANT_DATA_ENCRYPTION_FALLBACK_KEY: ${TENANT_DATA_ENCRYPTION_FALLBACK_KEY:-dev-tenant-encryption-fallback-key-32chars}
|
|
168
|
-
TENANT_DATA_ENCRYPTION_KEY: ${TENANT_DATA_ENCRYPTION_KEY:-}
|
|
169
|
-
CACHE_REDIS_URL: redis://redis:6379
|
|
170
|
-
CACHE_STRATEGY: ${CACHE_STRATEGY:-redis}
|
|
171
|
-
depends_on:
|
|
172
|
-
app:
|
|
173
|
-
condition: service_started
|
|
174
|
-
postgres:
|
|
175
|
-
condition: service_healthy
|
|
176
|
-
redis:
|
|
177
|
-
condition: service_healthy
|
|
178
|
-
networks:
|
|
179
|
-
- mercato-network-fullapp
|
|
180
|
-
|
|
181
103
|
postgres:
|
|
182
104
|
image: pgvector/pgvector:pg17-trixie
|
|
183
105
|
restart: unless-stopped
|
package/template/next.config.ts
CHANGED
|
@@ -59,9 +59,6 @@ const nextConfig: NextConfig & { agentRules?: boolean } = {
|
|
|
59
59
|
'esbuild',
|
|
60
60
|
'@esbuild/darwin-arm64',
|
|
61
61
|
'@open-mercato/cli',
|
|
62
|
-
'puppeteer-core',
|
|
63
|
-
'jszip',
|
|
64
|
-
'yjs',
|
|
65
62
|
// Telemetry: the OTEL SDK + instrumentations must run as real Node modules,
|
|
66
63
|
// not be bundled — the auto-instrumentations (pg/undici) monkey-patch the
|
|
67
64
|
// underlying drivers at runtime. The full list is owned by
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
"dev:reset": "node ./scripts/dev-reset.mjs",
|
|
17
17
|
"build": "yarn generate && cross-env NODE_ENV=production NODE_OPTIONS=--max-old-space-size=8192 next build",
|
|
18
18
|
"start": "mercato server start",
|
|
19
|
-
"documents:collab": "node ./node_modules/@open-mercato/documents/dist/server/documents-collab-server.js",
|
|
20
19
|
"lint": "eslint .",
|
|
21
20
|
"ds:check": "node scripts/ds-check.mjs",
|
|
22
21
|
"i18n:check-hardcoded": "node scripts/i18n-check-hardcoded.mjs",
|
|
@@ -53,7 +52,6 @@
|
|
|
53
52
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
54
53
|
"@open-mercato/ai-assistant": "{{PACKAGE_VERSION}}",
|
|
55
54
|
"@open-mercato/cache": "{{PACKAGE_VERSION}}",
|
|
56
|
-
"@open-mercato/channel-discord": "{{PACKAGE_VERSION}}",
|
|
57
55
|
"@open-mercato/channel-gmail": "{{PACKAGE_VERSION}}",
|
|
58
56
|
"@open-mercato/channel-imap": "{{PACKAGE_VERSION}}",
|
|
59
57
|
"@open-mercato/channel-apns": "{{PACKAGE_VERSION}}",
|
|
@@ -63,7 +61,6 @@
|
|
|
63
61
|
"@open-mercato/cli": "{{PACKAGE_VERSION}}",
|
|
64
62
|
"@open-mercato/content": "{{PACKAGE_VERSION}}",
|
|
65
63
|
"@open-mercato/core": "{{PACKAGE_VERSION}}",
|
|
66
|
-
"@open-mercato/documents": "{{PACKAGE_VERSION}}",
|
|
67
64
|
"@open-mercato/events": "{{PACKAGE_VERSION}}",
|
|
68
65
|
"@open-mercato/onboarding": "{{PACKAGE_VERSION}}",
|
|
69
66
|
"@open-mercato/queue": "{{PACKAGE_VERSION}}",
|
|
@@ -14,6 +14,7 @@ import { Spinner } from '@open-mercato/ui/primitives/spinner'
|
|
|
14
14
|
import { useAiDock } from '@open-mercato/ui/ai/AiDock'
|
|
15
15
|
import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
|
|
16
16
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
17
|
+
import { isContactWidgetHidden } from './demoFeedbackFlag'
|
|
17
18
|
|
|
18
19
|
const SUPPRESS_COOKIE = 'om_feedback_suppress'
|
|
19
20
|
const SHOWN_TODAY_COOKIE = 'om_feedback_shown'
|
|
@@ -48,6 +49,7 @@ export function DemoFeedbackWidget({ demoModeEnabled }: { demoModeEnabled: boole
|
|
|
48
49
|
const [open, setOpen] = useState(false)
|
|
49
50
|
const [captionIndex, setCaptionIndex] = useState(0)
|
|
50
51
|
const [mounted, setMounted] = useState(false)
|
|
52
|
+
const [hiddenByFlag, setHiddenByFlag] = useState(false)
|
|
51
53
|
|
|
52
54
|
// form state
|
|
53
55
|
const [email, setEmail] = useState('')
|
|
@@ -63,7 +65,13 @@ export function DemoFeedbackWidget({ demoModeEnabled }: { demoModeEnabled: boole
|
|
|
63
65
|
const autoShownRef = useRef(false)
|
|
64
66
|
const [otherModalOpen, setOtherModalOpen] = useState(false)
|
|
65
67
|
|
|
66
|
-
|
|
68
|
+
// The `ff_om_hide_contact` localStorage flag lets a demo tenant hide the
|
|
69
|
+
// contact/feedback surface entirely (floating CTA and the inactivity popup).
|
|
70
|
+
// It is read after mount because localStorage is unavailable during SSR.
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
setMounted(true)
|
|
73
|
+
setHiddenByFlag(isContactWidgetHidden())
|
|
74
|
+
}, [])
|
|
67
75
|
|
|
68
76
|
// Track whether another Radix Dialog or native <dialog> modal is currently open.
|
|
69
77
|
// The floating button + auto-popup must defer while the user is mid-task in another modal —
|
|
@@ -99,7 +107,7 @@ export function DemoFeedbackWidget({ demoModeEnabled }: { demoModeEnabled: boole
|
|
|
99
107
|
// operator is mid-conversation with an assistant and a popup would be
|
|
100
108
|
// disruptive on top of (or competing with) the dock surface.
|
|
101
109
|
useEffect(() => {
|
|
102
|
-
if (!demoModeEnabled || !mounted) return
|
|
110
|
+
if (!demoModeEnabled || !mounted || hiddenByFlag) return
|
|
103
111
|
if (aiDockActive) return
|
|
104
112
|
if (getCookie(SUPPRESS_COOKIE) === '1') return
|
|
105
113
|
if (getCookie(SHOWN_TODAY_COOKIE) === todayKey()) return
|
|
@@ -133,7 +141,7 @@ export function DemoFeedbackWidget({ demoModeEnabled }: { demoModeEnabled: boole
|
|
|
133
141
|
events.forEach((ev) => window.removeEventListener(ev, resetTimer))
|
|
134
142
|
if (inactivityTimer.current) clearTimeout(inactivityTimer.current)
|
|
135
143
|
}
|
|
136
|
-
}, [demoModeEnabled, mounted, aiDockActive])
|
|
144
|
+
}, [demoModeEnabled, mounted, aiDockActive, hiddenByFlag])
|
|
137
145
|
|
|
138
146
|
const handleSubmit = useCallback(async () => {
|
|
139
147
|
setFieldErrors({})
|
|
@@ -205,7 +213,7 @@ export function DemoFeedbackWidget({ demoModeEnabled }: { demoModeEnabled: boole
|
|
|
205
213
|
}
|
|
206
214
|
}, [submitState, resetForm])
|
|
207
215
|
|
|
208
|
-
if (!mounted) return null
|
|
216
|
+
if (!mounted || hiddenByFlag) return null
|
|
209
217
|
|
|
210
218
|
const caption = CAPTIONS[captionIndex]
|
|
211
219
|
const currentCaption = t(caption.key, caption.fallback)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import '@testing-library/jest-dom'
|
|
6
|
+
import { render, screen } from '@testing-library/react'
|
|
7
|
+
import { DemoFeedbackWidget } from '../DemoFeedbackWidget'
|
|
8
|
+
import { HIDE_CONTACT_FLAG_KEY } from '../demoFeedbackFlag'
|
|
9
|
+
|
|
10
|
+
jest.mock('next/image', () => ({
|
|
11
|
+
__esModule: true,
|
|
12
|
+
default: ({ alt }: { alt: string }) => <span data-testid="logo">{alt}</span>,
|
|
13
|
+
}))
|
|
14
|
+
|
|
15
|
+
jest.mock('@open-mercato/shared/lib/i18n/context', () => ({
|
|
16
|
+
useT: () => (_key: string, fallback?: string) => fallback ?? '',
|
|
17
|
+
}))
|
|
18
|
+
|
|
19
|
+
jest.mock('@open-mercato/ui/ai/AiDock', () => ({
|
|
20
|
+
useAiDock: () => ({ state: {} }),
|
|
21
|
+
}))
|
|
22
|
+
|
|
23
|
+
jest.mock('@open-mercato/ui/backend/utils/apiCall', () => ({
|
|
24
|
+
apiCall: jest.fn(),
|
|
25
|
+
}))
|
|
26
|
+
|
|
27
|
+
describe('DemoFeedbackWidget', () => {
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
window.localStorage.clear()
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('renders the floating contact button by default', () => {
|
|
33
|
+
render(<DemoFeedbackWidget demoModeEnabled={true} />)
|
|
34
|
+
|
|
35
|
+
expect(screen.getByRole('button', { name: 'Open feedback form' })).toBeInTheDocument()
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('hides the floating contact button when ff_om_hide_contact is set', () => {
|
|
39
|
+
window.localStorage.setItem(HIDE_CONTACT_FLAG_KEY, '1')
|
|
40
|
+
|
|
41
|
+
render(<DemoFeedbackWidget demoModeEnabled={true} />)
|
|
42
|
+
|
|
43
|
+
expect(screen.queryByRole('button', { name: 'Open feedback form' })).not.toBeInTheDocument()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('keeps the button when the flag is explicitly disabled', () => {
|
|
47
|
+
window.localStorage.setItem(HIDE_CONTACT_FLAG_KEY, 'false')
|
|
48
|
+
|
|
49
|
+
render(<DemoFeedbackWidget demoModeEnabled={true} />)
|
|
50
|
+
|
|
51
|
+
expect(screen.getByRole('button', { name: 'Open feedback form' })).toBeInTheDocument()
|
|
52
|
+
})
|
|
53
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { HIDE_CONTACT_FLAG_KEY, isContactWidgetHidden } from '../demoFeedbackFlag'
|
|
6
|
+
|
|
7
|
+
describe('isContactWidgetHidden', () => {
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
window.localStorage.clear()
|
|
10
|
+
jest.restoreAllMocks()
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('is false when the flag is absent', () => {
|
|
14
|
+
expect(isContactWidgetHidden()).toBe(false)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('is true when the flag is present with an empty value', () => {
|
|
18
|
+
window.localStorage.setItem(HIDE_CONTACT_FLAG_KEY, '')
|
|
19
|
+
expect(isContactWidgetHidden()).toBe(true)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it.each(['1', 'true', 'yes', 'on', 'whatever'])('is true for value %s', (value) => {
|
|
23
|
+
window.localStorage.setItem(HIDE_CONTACT_FLAG_KEY, value)
|
|
24
|
+
expect(isContactWidgetHidden()).toBe(true)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it.each(['0', 'false', 'off', 'no'])('is false for explicit opt-out value %s', (value) => {
|
|
28
|
+
window.localStorage.setItem(HIDE_CONTACT_FLAG_KEY, value)
|
|
29
|
+
expect(isContactWidgetHidden()).toBe(false)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('is false when localStorage access throws', () => {
|
|
33
|
+
jest.spyOn(Storage.prototype, 'getItem').mockImplementation(() => {
|
|
34
|
+
throw new Error('denied')
|
|
35
|
+
})
|
|
36
|
+
expect(isContactWidgetHidden()).toBe(false)
|
|
37
|
+
})
|
|
38
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'
|
|
2
|
+
|
|
3
|
+
export const HIDE_CONTACT_FLAG_KEY = 'ff_om_hide_contact'
|
|
4
|
+
|
|
5
|
+
export function isContactWidgetHidden(): boolean {
|
|
6
|
+
if (typeof window === 'undefined') return false
|
|
7
|
+
let raw: string | null = null
|
|
8
|
+
try {
|
|
9
|
+
raw = window.localStorage.getItem(HIDE_CONTACT_FLAG_KEY)
|
|
10
|
+
} catch {
|
|
11
|
+
return false
|
|
12
|
+
}
|
|
13
|
+
if (raw === null) return false
|
|
14
|
+
return parseBooleanWithDefault(raw, true)
|
|
15
|
+
}
|