create-mercato-app 0.5.1-develop.2744.9c8be0dd93 → 0.5.1-develop.2762.90c271efe2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-mercato-app",
3
- "version": "0.5.1-develop.2744.9c8be0dd93",
3
+ "version": "0.5.1-develop.2762.90c271efe2",
4
4
  "type": "module",
5
5
  "description": "Create a new Open Mercato application",
6
6
  "main": "./dist/index.js",
@@ -178,10 +178,10 @@ CACHE_TTL=300000
178
178
  #CACHE_REDIS_URL=redis://localhost:6379
179
179
 
180
180
  # SQLite configuration (for sqlite strategy)
181
- CACHE_SQLITE_PATH=./data/cache.db
181
+ CACHE_SQLITE_PATH=./.mercato/cache/cache.db
182
182
 
183
183
  # JSON file configuration (for jsonfile strategy)
184
- #CACHE_JSON_FILE_PATH=./data/cache.json
184
+ #CACHE_JSON_FILE_PATH=./.mercato/cache/cache.json
185
185
 
186
186
  # Database pooling settings
187
187
  DB_POOL_MIN=5
@@ -55,9 +55,9 @@ const {
55
55
  stripAnsi,
56
56
  wrapListLines,
57
57
  } = await import(resolveSplashHelpersImport())
58
- const { resolveSpawnCommand } = await import(resolveSpawnUtilsImport())
58
+ const { resolveProjectBinary, resolveSpawnCommand } = await import(resolveSpawnUtilsImport())
59
59
 
60
- const command = process.platform === 'win32' ? 'mercato.cmd' : 'mercato'
60
+ const command = resolveProjectBinary(process.platform === 'win32' ? 'mercato.cmd' : 'mercato')
61
61
  const classic = process.argv.includes('--classic') || isEnabledEnvFlag(process.env.OM_DEV_CLASSIC)
62
62
  const verbose = !classic && (process.argv.includes('--verbose') || process.env.MERCATO_DEV_OUTPUT === 'verbose')
63
63
  const rawPassthrough = classic || verbose
@@ -412,10 +412,10 @@ function spawnMercato(args) {
412
412
  return child
413
413
  }
414
414
 
415
- function waitForExit(child) {
415
+ function waitForExit(child, label = 'Child process') {
416
416
  return new Promise((resolve) => {
417
417
  child.on('exit', (code, signal) => {
418
- resolve({ code, signal })
418
+ resolve({ label, code, signal })
419
419
  })
420
420
  })
421
421
  }
@@ -441,6 +441,32 @@ function resolveChildExitCode(result, fallback = 1) {
441
441
  return fallback
442
442
  }
443
443
 
444
+ function formatChildExitStatus(result) {
445
+ if (typeof result?.code === 'number') {
446
+ return `exit code ${result.code}`
447
+ }
448
+ if (result?.signal) {
449
+ return `signal ${result.signal}`
450
+ }
451
+ return 'an unknown status'
452
+ }
453
+
454
+ function resolveUnexpectedExitCode(result) {
455
+ const exitCode = resolveChildExitCode(result, 1)
456
+ return exitCode === 0 ? 1 : exitCode
457
+ }
458
+
459
+ function reportUnexpectedChildExit(result) {
460
+ const message = `❌ ${result?.label ?? 'Child process'} exited unexpectedly with ${formatChildExitStatus(result)}`
461
+ console.error(message)
462
+ rememberRawLog(message)
463
+ publishRuntimeFailure(message, {
464
+ progressCurrent: splashState.progressCurrent >= runtimeProgressCurrent ? splashState.progressCurrent : runtimeProgressCurrent,
465
+ progressLabel: splashState.progressLabel || startupProgress.label,
466
+ failureLines: [...collectRuntimeFailureLines(), message].slice(-10),
467
+ })
468
+ }
469
+
444
470
  function joinBaseUrl(baseUrl, pathname) {
445
471
  return `${String(baseUrl ?? '').replace(/\/$/, '')}${pathname}`
446
472
  }
@@ -1521,12 +1547,16 @@ async function runClassicRuntime() {
1521
1547
 
1522
1548
  const watch = spawnMercato(['generate', 'watch', '--skip-initial'])
1523
1549
  const server = spawnMercato(['server', 'dev'])
1524
- const result = await Promise.race([waitForExit(watch), waitForExit(server)])
1550
+ const result = await Promise.race([
1551
+ waitForExit(watch, 'Generator watch'),
1552
+ waitForExit(server, 'App runtime'),
1553
+ ])
1525
1554
  if (isGracefulShutdownResult(result)) {
1526
1555
  return
1527
1556
  }
1528
1557
 
1529
- shutdown(resolveChildExitCode(result, 0))
1558
+ reportUnexpectedChildExit(result)
1559
+ shutdown(resolveUnexpectedExitCode(result))
1530
1560
  }
1531
1561
 
1532
1562
  if (classic) {
@@ -1541,7 +1571,11 @@ printRuntimePackagesSummary()
1541
1571
  const watch = startFilteredChild(['generate', 'watch', '--skip-initial'], 'Generator watch', classifyWatchLine)
1542
1572
  const server = startFilteredChild(['server', 'dev'], 'App runtime', classifyServerLine)
1543
1573
 
1544
- const result = await Promise.race([waitForExit(watch), waitForExit(server)])
1574
+ const result = await Promise.race([
1575
+ waitForExit(watch, 'Generator watch'),
1576
+ waitForExit(server, 'App runtime'),
1577
+ ])
1545
1578
  if (!isGracefulShutdownResult(result)) {
1546
- shutdown(resolveChildExitCode(result, 0))
1579
+ reportUnexpectedChildExit(result)
1580
+ shutdown(resolveUnexpectedExitCode(result))
1547
1581
  }
@@ -1,3 +1,6 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+
1
4
  function isWindowsCmdScript(command, platform = process.platform) {
2
5
  return platform === 'win32' && /\.(cmd|bat)$/i.test(String(command))
3
6
  }
@@ -23,6 +26,34 @@ function assertWindowsCmdSafeValue(value, label) {
23
26
  return stringValue
24
27
  }
25
28
 
29
+ export function resolveProjectBinary(command, options = {}) {
30
+ const safeCommand = assertProcessSafeValue(command, 'Process command')
31
+ const cwd = options.cwd ?? process.cwd()
32
+ const platform = options.platform ?? process.platform
33
+
34
+ if (path.isAbsolute(safeCommand) || safeCommand.includes('/') || safeCommand.includes('\\')) {
35
+ return safeCommand
36
+ }
37
+
38
+ const binDir = path.join(cwd, 'node_modules', '.bin')
39
+ const candidates = platform === 'win32'
40
+ ? [
41
+ path.join(binDir, safeCommand),
42
+ path.join(binDir, `${safeCommand}.cmd`),
43
+ path.join(binDir, `${safeCommand}.bat`),
44
+ path.join(binDir, `${safeCommand}.exe`),
45
+ ]
46
+ : [path.join(binDir, safeCommand)]
47
+
48
+ for (const candidate of candidates) {
49
+ if (fs.existsSync(candidate)) {
50
+ return candidate
51
+ }
52
+ }
53
+
54
+ return safeCommand
55
+ }
56
+
26
57
  export function resolveSpawnCommand(command, commandArgs = [], options = {}) {
27
58
  const platform = options.platform ?? process.platform
28
59
  const safeCommand = assertProcessSafeValue(command, 'Process command')
@@ -1,7 +1,17 @@
1
1
  import { spawnSync } from 'node:child_process'
2
+ import { existsSync } from 'node:fs'
2
3
 
3
4
  const reinstall = process.argv.includes('--reinstall')
4
5
  const classic = process.argv.includes('--classic')
6
+
7
+ if (!existsSync('node_modules/cross-spawn')) {
8
+ const bootstrap = spawnSync('yarn', ['install'], {
9
+ stdio: 'inherit',
10
+ shell: process.platform === 'win32',
11
+ })
12
+ if (bootstrap.status !== 0) process.exit(bootstrap.status ?? 1)
13
+ }
14
+
5
15
  const result = spawnSync(
6
16
  process.execPath,
7
17
  ['./scripts/dev.mjs', '--setup', ...(reinstall ? ['--reinstall'] : []), ...(classic ? ['--classic'] : [])],
@@ -27,13 +27,13 @@ import {
27
27
 
28
28
  export const todoCreateSchema = z.object({
29
29
  id: z.string().uuid().optional(),
30
- title: z.string().min(1),
30
+ title: z.string().min(1).max(200),
31
31
  is_done: z.boolean().optional(),
32
32
  })
33
33
 
34
34
  export const todoUpdateSchema = z.object({
35
35
  id: z.string().uuid(),
36
- title: z.string().min(1).optional(),
36
+ title: z.string().min(1).max(200).optional(),
37
37
  is_done: z.boolean().optional(),
38
38
  })
39
39
 
@@ -24,11 +24,11 @@ type MappingRow = {
24
24
  interaction_id: string
25
25
  todo_id: string
26
26
  sync_status: string
27
- last_synced_at: Date | null
27
+ last_synced_at: Date | string | null
28
28
  last_error: string | null
29
- source_updated_at: Date | null
30
- created_at: Date
31
- updated_at: Date
29
+ source_updated_at: Date | string | null
30
+ created_at: Date | string
31
+ updated_at: Date | string
32
32
  organization_id: string
33
33
  tenant_id: string
34
34
  }
@@ -53,6 +53,15 @@ function decodeCursor(token: string | undefined): CursorPayload | null {
53
53
  }
54
54
  }
55
55
 
56
+ function toIsoString(value: Date | string | null | undefined): string | null {
57
+ if (!value) return null
58
+ if (value instanceof Date) {
59
+ return Number.isNaN(value.getTime()) ? null : value.toISOString()
60
+ }
61
+ const parsed = new Date(value)
62
+ return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString()
63
+ }
64
+
56
65
  export async function GET(request: Request) {
57
66
  const { translate } = await resolveTranslations()
58
67
  try {
@@ -163,11 +172,11 @@ export async function GET(request: Request) {
163
172
  interactionId: row.interaction_id,
164
173
  todoId: row.todo_id,
165
174
  syncStatus: row.sync_status,
166
- lastSyncedAt: row.last_synced_at?.toISOString() ?? null,
175
+ lastSyncedAt: toIsoString(row.last_synced_at),
167
176
  lastError: row.last_error ?? null,
168
- sourceUpdatedAt: row.source_updated_at?.toISOString() ?? null,
169
- createdAt: row.created_at.toISOString(),
170
- updatedAt: row.updated_at.toISOString(),
177
+ sourceUpdatedAt: toIsoString(row.source_updated_at),
178
+ createdAt: toIsoString(row.created_at),
179
+ updatedAt: toIsoString(row.updated_at),
171
180
  organizationId: row.organization_id,
172
181
  tenantId: row.tenant_id,
173
182
  exampleHref: `/backend/todos/${encodeURIComponent(row.todo_id)}/edit`,
@@ -186,7 +195,7 @@ export async function GET(request: Request) {
186
195
 
187
196
  return NextResponse.json({
188
197
  items,
189
- ...(last ? { nextCursor: encodeCursor({ updatedAt: last.updated_at.toISOString(), id: last.id }) } : {}),
198
+ ...(last ? { nextCursor: encodeCursor({ updatedAt: toIsoString(last.updated_at) ?? new Date(0).toISOString(), id: last.id }) } : {}),
190
199
  })
191
200
  } catch (error) {
192
201
  if (error instanceof z.ZodError) {
@@ -1,6 +1,8 @@
1
+ import { setTimeout as sleep } from 'node:timers/promises'
1
2
  import type { EntityManager } from '@mikro-orm/postgresql'
2
3
  import { type Kysely } from 'kysely'
3
4
  import type { CommandBus } from '@open-mercato/shared/lib/commands'
5
+ import '@open-mercato/core/modules/customers/commands/index'
4
6
  import { loadCustomFieldSnapshot } from '@open-mercato/shared/lib/commands/customFieldSnapshots'
5
7
  import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
6
8
  import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
@@ -66,7 +68,7 @@ type LegacyExampleTodoLinkRow = {
66
68
  entityId: string
67
69
  todoId: string
68
70
  createdByUserId: string | null
69
- createdAt: Date
71
+ createdAt: Date | string
70
72
  }
71
73
 
72
74
  export type ExampleCustomersSyncReconcileItem = {
@@ -92,6 +94,8 @@ type CursorPayload = {
92
94
  }
93
95
 
94
96
  const DEFAULT_TASK_TITLE = 'Untitled task'
97
+ const LEGACY_INBOUND_BOOTSTRAP_ATTEMPTS = 10
98
+ const LEGACY_INBOUND_BOOTSTRAP_DELAY_MS = 100
95
99
 
96
100
  function isSyncOriginFromBridge(syncOrigin: unknown): boolean {
97
101
  return typeof syncOrigin === 'string' && syncOrigin.startsWith('example_customers_sync:')
@@ -110,6 +114,10 @@ function parseDateOrNull(value: string | Date | null | undefined): Date | null {
110
114
  return Number.isNaN(parsed.getTime()) ? null : parsed
111
115
  }
112
116
 
117
+ function toIsoString(value: Date | string | null | undefined): string | null {
118
+ return parseDateOrNull(value)?.toISOString() ?? null
119
+ }
120
+
113
121
  function trimErrorMessage(value: unknown): string {
114
122
  const message = value instanceof Error ? value.message : String(value ?? 'Unknown sync error')
115
123
  return message.length > 2000 ? `${message.slice(0, 1997)}...` : message
@@ -394,6 +402,38 @@ async function loadLegacyExampleTodoLinkRow(
394
402
  }
395
403
  }
396
404
 
405
+ async function waitForLegacyExampleTodoLinkRow(
406
+ em: EntityManager,
407
+ scope: ExampleCustomersSyncScope,
408
+ todoId: string,
409
+ ): Promise<LegacyExampleTodoLinkRow | null> {
410
+ for (let attempt = 0; attempt < LEGACY_INBOUND_BOOTSTRAP_ATTEMPTS; attempt += 1) {
411
+ const link = await loadLegacyExampleTodoLinkRow(em, scope, todoId)
412
+ if (link) return link
413
+ if (attempt < LEGACY_INBOUND_BOOTSTRAP_ATTEMPTS - 1) {
414
+ await sleep(LEGACY_INBOUND_BOOTSTRAP_DELAY_MS)
415
+ em.clear()
416
+ }
417
+ }
418
+ return null
419
+ }
420
+
421
+ async function waitForExampleTodoSnapshot(
422
+ em: EntityManager,
423
+ scope: ExampleCustomersSyncScope,
424
+ todoId: string,
425
+ ): Promise<ExampleTodoSnapshot | null> {
426
+ for (let attempt = 0; attempt < LEGACY_INBOUND_BOOTSTRAP_ATTEMPTS; attempt += 1) {
427
+ const todo = await loadExampleTodoSnapshot(em, scope, todoId)
428
+ if (todo) return todo
429
+ if (attempt < LEGACY_INBOUND_BOOTSTRAP_ATTEMPTS - 1) {
430
+ await sleep(LEGACY_INBOUND_BOOTSTRAP_DELAY_MS)
431
+ em.clear()
432
+ }
433
+ }
434
+ return null
435
+ }
436
+
397
437
  async function ensureLegacyExampleMapping(
398
438
  em: EntityManager,
399
439
  scope: ExampleCustomersSyncScope,
@@ -416,7 +456,7 @@ async function ensureLegacyExampleMapping(
416
456
  ...scope,
417
457
  interactionId,
418
458
  todoId: legacyLink.todoId,
419
- sourceUpdatedAt: legacyLink.createdAt ?? null,
459
+ sourceUpdatedAt: parseDateOrNull(legacyLink.createdAt),
420
460
  })
421
461
  }
422
462
 
@@ -755,7 +795,7 @@ async function loadLegacyExampleTodoLinks(
755
795
  const next = rows.length > limit ? pageRows[pageRows.length - 1] : null
756
796
  return {
757
797
  rows: pageRows,
758
- ...(next ? { nextCursor: encodeCursor({ createdAt: next.createdAt.toISOString(), id: next.id }) } : {}),
798
+ ...(next ? { nextCursor: encodeCursor({ createdAt: toIsoString(next.createdAt) ?? new Date(0).toISOString(), id: next.id }) } : {}),
759
799
  }
760
800
  }
761
801
 
@@ -781,14 +821,14 @@ async function ensureCanonicalInteractionForLegacyLink(
781
821
  return { interactionId: existing.id, created: false }
782
822
  }
783
823
 
784
- const todo = await loadExampleTodoSnapshot(em, scope, link.todoId)
824
+ const todo = await waitForExampleTodoSnapshot(em, scope, link.todoId)
785
825
  if (!todo) return null
786
826
 
787
827
  const patch = buildInteractionUpdateFromExampleTodo({
788
828
  title: todo.title,
789
829
  isDone: todo.isDone,
790
830
  customValues: todo.customValues,
791
- occurredAt: todo.isDone ? (todo.updatedAt ?? link.createdAt) : null,
831
+ occurredAt: todo.isDone ? parseDateOrNull(todo.updatedAt ?? link.createdAt) : null,
792
832
  })
793
833
 
794
834
  const commandBus = container.resolve('commandBus') as CommandBus
@@ -801,6 +841,8 @@ async function ensureCanonicalInteractionForLegacyLink(
801
841
  const result = await commandBus.execute<Record<string, unknown>, { interactionId: string }>('customers.interactions.create', {
802
842
  input: {
803
843
  id: link.todoId,
844
+ tenantId: scope.tenantId,
845
+ organizationId: scope.organizationId,
804
846
  entityId: link.entityId,
805
847
  interactionType: CUSTOMER_INTERACTION_TASK_TYPE,
806
848
  title: patch.title,
@@ -840,16 +882,16 @@ async function ensureMappingForLegacyExampleTodo(
840
882
  todoId: string,
841
883
  ): Promise<ExampleCustomerInteractionMapping | null> {
842
884
  const em = (container.resolve('em') as EntityManager).fork()
843
- const legacyLink = await loadLegacyExampleTodoLinkRow(em, scope, todoId)
885
+ const legacyLink = await waitForLegacyExampleTodoLinkRow(em, scope, todoId)
844
886
  if (!legacyLink) return null
845
887
  const canonical = await ensureCanonicalInteractionForLegacyLink(container, scope, legacyLink)
846
888
  if (!canonical) return null
847
- const todo = await loadExampleTodoSnapshot(em, scope, todoId)
889
+ const todo = await waitForExampleTodoSnapshot(em, scope, todoId)
848
890
  return await updateMappingAfterSync(em, {
849
891
  ...scope,
850
892
  interactionId: canonical.interactionId,
851
893
  todoId,
852
- sourceUpdatedAt: todo?.updatedAt ?? legacyLink.createdAt,
894
+ sourceUpdatedAt: parseDateOrNull(todo?.updatedAt ?? legacyLink.createdAt),
853
895
  })
854
896
  }
855
897
 
@@ -888,7 +930,7 @@ export async function reconcileLegacyExampleTodoLinks(
888
930
  ...scope,
889
931
  interactionId: canonical.interactionId,
890
932
  todoId: row.todoId,
891
- sourceUpdatedAt: todo?.updatedAt ?? row.createdAt,
933
+ sourceUpdatedAt: parseDateOrNull(todo?.updatedAt ?? row.createdAt),
892
934
  })
893
935
  items.push({
894
936
  linkId: row.id,