create-mercato-app 0.6.1-develop.3291.1.6fad645fd0 โ†’ 0.6.1

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.6.1-develop.3291.1.6fad645fd0",
3
+ "version": "0.6.1",
4
4
  "type": "module",
5
5
  "description": "Create a new Open Mercato application",
6
6
  "main": "./dist/index.js",
@@ -41,6 +41,5 @@
41
41
  "scaffolding",
42
42
  "cli"
43
43
  ],
44
- "license": "MIT",
45
- "stableVersion": "0.6.0"
44
+ "license": "MIT"
46
45
  }
@@ -76,6 +76,10 @@ export function isIgnorableBootstrapNoiseLine(line) {
76
76
  return normalized.startsWith('[Bootstrap] Entity IDs re-registered')
77
77
  || normalized.startsWith('๐Ÿš€ Starting scheduler')
78
78
  || normalized.startsWith('โœ“ Local scheduler started')
79
+ || normalized.startsWith('[lazy-scheduler] Watching for enabled schedules')
80
+ || normalized.startsWith('[lazy-scheduler] Schedule probe failed')
81
+ || normalized.startsWith('[lazy-scheduler] Poll cycle failed')
82
+ || normalized.startsWith('[lazy-scheduler] Initial poll failed')
79
83
  || normalized.startsWith('๐Ÿ’ก Tip:')
80
84
  }
81
85
 
@@ -42,6 +42,27 @@ function isEnabledEnvFlag(value) {
42
42
  return ['1', 'true', 'yes', 'on'].includes(value.trim().toLowerCase())
43
43
  }
44
44
 
45
+ function parseEnvBooleanToken(value) {
46
+ if (typeof value !== 'string') return null
47
+ const normalized = value.trim().toLowerCase()
48
+ if (['1', 'true', 'yes', 'on'].includes(normalized)) return true
49
+ if (['0', 'false', 'no', 'off'].includes(normalized)) return false
50
+ return null
51
+ }
52
+
53
+ function resolveAutoSpawnEnabled(env, legacyName, aliasedName) {
54
+ const legacy = parseEnvBooleanToken(env[legacyName])
55
+ if (legacy !== null) return legacy
56
+ const aliased = parseEnvBooleanToken(env[aliasedName])
57
+ if (aliased !== null) return aliased
58
+ return true
59
+ }
60
+
61
+ function resolveAutoSpawnMode(env, legacyName, aliasedName, lazyName) {
62
+ if (!resolveAutoSpawnEnabled(env, legacyName, aliasedName)) return 'off'
63
+ return parseEnvBooleanToken(env[lazyName]) === true ? 'lazy' : 'eager'
64
+ }
65
+
45
66
  const {
46
67
  clampPercent,
47
68
  connectLineStream,
@@ -83,6 +104,10 @@ const CYAN_BORDER = '\u001B[46m\u001B[30m'
83
104
  const ERROR_BANNER = '\u001B[41m\u001B[97m'
84
105
  const warmupRequestTimeoutsMs = [45000, 120000]
85
106
  const maxWarmupRetryAttempts = 3
107
+ const backgroundServiceModes = {
108
+ workers: resolveAutoSpawnMode(process.env, 'AUTO_SPAWN_WORKERS', 'OM_AUTO_SPAWN_WORKERS', 'OM_AUTO_SPAWN_WORKERS_LAZY'),
109
+ scheduler: resolveAutoSpawnMode(process.env, 'AUTO_SPAWN_SCHEDULER', 'OM_AUTO_SPAWN_SCHEDULER', 'OM_AUTO_SPAWN_SCHEDULER_LAZY'),
110
+ }
86
111
  const splashState = {
87
112
  mode: splashMode,
88
113
  phase: startupSplashPhase,
@@ -98,6 +123,8 @@ const splashState = {
98
123
  packageNames: [],
99
124
  workerQueues: [],
100
125
  schedulerActive: false,
126
+ workerMode: backgroundServiceModes.workers,
127
+ schedulerMode: backgroundServiceModes.scheduler,
101
128
  progressCurrent: runtimeProgressCurrent,
102
129
  progressTotal: runtimeProgressTotal,
103
130
  progressPercent: 0,
@@ -120,6 +147,8 @@ const runtimeSummaryState = {
120
147
  packageNames: [],
121
148
  workerQueues: [],
122
149
  schedulerActive: false,
150
+ workerMode: backgroundServiceModes.workers,
151
+ schedulerMode: backgroundServiceModes.scheduler,
123
152
  packagesPrinted: false,
124
153
  workersPrinted: false,
125
154
  lastWorkersSignature: '',
@@ -145,6 +174,22 @@ function printCompactSummary(icon, title, lines) {
145
174
  }
146
175
  }
147
176
 
177
+ function formatBackgroundServiceMode(modes = runtimeSummaryState) {
178
+ const activeModes = []
179
+ if (modes.workerMode !== 'off') activeModes.push(['workers', modes.workerMode])
180
+ if (modes.schedulerMode !== 'off') activeModes.push(['scheduler', modes.schedulerMode])
181
+ if (activeModes.length === 0) return 'off'
182
+
183
+ const uniqueModes = new Set(activeModes.map(([, mode]) => mode))
184
+ if (uniqueModes.size === 1) return activeModes[0][1]
185
+
186
+ return activeModes.map(([service, mode]) => `${service} ${mode}`).join(', ')
187
+ }
188
+
189
+ function formatBackgroundServiceStatus(action = 'Starting background services', modes = runtimeSummaryState) {
190
+ return `${action} (${formatBackgroundServiceMode(modes)})`
191
+ }
192
+
148
193
  function loadRuntimePackageNames() {
149
194
  const pkg = readJsonFile(path.join(process.cwd(), 'package.json'))
150
195
  if (!pkg || typeof pkg !== 'object') return []
@@ -168,6 +213,8 @@ function updateRuntimeSummaryState() {
168
213
  packageNames: runtimeSummaryState.packageNames,
169
214
  workerQueues: runtimeSummaryState.workerQueues,
170
215
  schedulerActive: runtimeSummaryState.schedulerActive,
216
+ workerMode: runtimeSummaryState.workerMode,
217
+ schedulerMode: runtimeSummaryState.schedulerMode,
171
218
  })
172
219
  }
173
220
 
@@ -193,6 +240,8 @@ function printBackgroundServicesSummary() {
193
240
 
194
241
  const signature = JSON.stringify({
195
242
  schedulerActive: runtimeSummaryState.schedulerActive,
243
+ workerMode: runtimeSummaryState.workerMode,
244
+ schedulerMode: runtimeSummaryState.schedulerMode,
196
245
  workerQueues: runtimeSummaryState.workerQueues,
197
246
  })
198
247
 
@@ -204,7 +253,7 @@ function printBackgroundServicesSummary() {
204
253
  runtimeSummaryState.workersPrinted = true
205
254
  printCompactSummary(
206
255
  'โš™๏ธ',
207
- `Background services (${detailItems.length})`,
256
+ `Background services (${formatBackgroundServiceMode()}, ${detailItems.length} active)`,
208
257
  detailItems.map((item, index) => `${index === 0 ? '๐Ÿ•’' : '๐Ÿงต'} ${item}`),
209
258
  )
210
259
  }
@@ -215,6 +264,32 @@ function initializeRuntimeSummary() {
215
264
  }
216
265
 
217
266
  function captureBackgroundServiceLine(line) {
267
+ if (
268
+ line.startsWith('[server] Lazy worker auto-spawn enabled')
269
+ || line.startsWith('[lazy-supervisor] Watching')
270
+ || line.startsWith('[lazy-supervisor] Pending job detected')
271
+ ) {
272
+ runtimeSummaryState.workerMode = 'lazy'
273
+ updateRuntimeSummaryState()
274
+ return true
275
+ }
276
+
277
+ if (
278
+ line === '[server] Starting workers for all queues...'
279
+ || line === '[server] Eager worker auto-spawn enabled - starting workers for all queues...'
280
+ || line.startsWith('๐Ÿš€ Running queue:worker')
281
+ ) {
282
+ runtimeSummaryState.workerMode = 'eager'
283
+ updateRuntimeSummaryState()
284
+ return true
285
+ }
286
+
287
+ if (line.startsWith('[server] Lazy scheduler auto-spawn enabled')) {
288
+ runtimeSummaryState.schedulerMode = 'lazy'
289
+ updateRuntimeSummaryState()
290
+ return true
291
+ }
292
+
218
293
  const queuesMatch = line.match(/^\[worker\] Starting workers for all queues: (.+)$/)
219
294
  if (queuesMatch) {
220
295
  const queueNames = queuesMatch[1].split(',').map((item) => item.trim()).filter(Boolean)
@@ -241,7 +316,24 @@ function captureBackgroundServiceLine(line) {
241
316
  return true
242
317
  }
243
318
 
244
- if (line === '[server] Starting scheduler polling engine...' || line.startsWith('โœ“ Local scheduler started')) {
319
+ if (
320
+ line === '[server] Starting scheduler polling engine...'
321
+ || line === '[server] Eager scheduler auto-spawn enabled - starting scheduler polling engine...'
322
+ || line.startsWith('๐Ÿš€ Running scheduler:start')
323
+ ) {
324
+ runtimeSummaryState.schedulerMode = 'eager'
325
+ runtimeSummaryState.schedulerActive = true
326
+ updateRuntimeSummaryState()
327
+ return true
328
+ }
329
+
330
+ if (
331
+ line === '[lazy-scheduler] Enabled schedule detected - starting scheduler polling engine.'
332
+ || line.startsWith('โœ“ Local scheduler started')
333
+ ) {
334
+ if (line === '[lazy-scheduler] Enabled schedule detected - starting scheduler polling engine.') {
335
+ runtimeSummaryState.schedulerMode = 'lazy'
336
+ }
245
337
  runtimeSummaryState.schedulerActive = true
246
338
  updateRuntimeSummaryState()
247
339
  return true
@@ -315,6 +407,8 @@ function updateSplashState(patch) {
315
407
  if (Array.isArray(patch.packageNames)) splashState.packageNames = patch.packageNames
316
408
  if (Array.isArray(patch.workerQueues)) splashState.workerQueues = patch.workerQueues
317
409
  if (typeof patch.schedulerActive === 'boolean') splashState.schedulerActive = patch.schedulerActive
410
+ if (typeof patch.workerMode === 'string') splashState.workerMode = patch.workerMode
411
+ if (typeof patch.schedulerMode === 'string') splashState.schedulerMode = patch.schedulerMode
318
412
  if (typeof patch.progressCurrent === 'number') splashState.progressCurrent = patch.progressCurrent
319
413
  if (typeof patch.progressTotal === 'number') splashState.progressTotal = patch.progressTotal
320
414
  if (typeof patch.progressPercent === 'number') splashState.progressPercent = clampPercent(patch.progressPercent)
@@ -1377,18 +1471,63 @@ function classifyServerLine(line) {
1377
1471
  }
1378
1472
  if (
1379
1473
  line === '[server] Starting workers for all queues...'
1474
+ || line === '[server] Eager worker auto-spawn enabled - starting workers for all queues...'
1380
1475
  || line === '[server] Starting scheduler polling engine...'
1476
+ || line === '[server] Eager scheduler auto-spawn enabled - starting scheduler polling engine...'
1477
+ || line === '[lazy-scheduler] Enabled schedule detected - starting scheduler polling engine.'
1381
1478
  || line.startsWith('๐Ÿš€ Running queue:worker')
1382
1479
  || line.startsWith('๐Ÿš€ Running scheduler:start')
1383
1480
  ) {
1481
+ const isLazyTrigger = line === '[lazy-scheduler] Enabled schedule detected - starting scheduler polling engine.'
1482
+ const modes = isLazyTrigger
1483
+ ? { workerMode: runtimeSummaryState.workerMode, schedulerMode: 'lazy' }
1484
+ : runtimeSummaryState
1485
+ const status = formatBackgroundServiceStatus('Starting background services', modes)
1486
+ return {
1487
+ type: 'status',
1488
+ message: `โš™๏ธ ${status}`,
1489
+ splashPhase: startupSplashPhase,
1490
+ splashDetail: status,
1491
+ activity: status,
1492
+ progressCurrent: 3,
1493
+ progressLabel: status,
1494
+ }
1495
+ }
1496
+ if (line.startsWith('[server] Lazy worker auto-spawn enabled')) {
1497
+ const status = 'Background workers armed (lazy)'
1498
+ return {
1499
+ type: 'status',
1500
+ message: `โš™๏ธ ${status}`,
1501
+ splashPhase: startupSplashPhase,
1502
+ splashDetail: status,
1503
+ activity: status,
1504
+ progressCurrent: 3,
1505
+ progressLabel: 'Background services (lazy)',
1506
+ }
1507
+ }
1508
+ if (line.startsWith('[server] Lazy scheduler auto-spawn enabled')) {
1509
+ const status = 'Scheduler armed (lazy)'
1510
+ return {
1511
+ type: 'status',
1512
+ message: `โš™๏ธ ${status}`,
1513
+ splashPhase: startupSplashPhase,
1514
+ splashDetail: status,
1515
+ activity: status,
1516
+ progressCurrent: 3,
1517
+ progressLabel: 'Background services (lazy)',
1518
+ }
1519
+ }
1520
+ const lazyWorkerStartMatch = line.match(/^\[lazy-supervisor\] Pending job detected .+ starting worker for queue "(.+)"$/)
1521
+ if (lazyWorkerStartMatch) {
1522
+ const status = `Starting worker "${lazyWorkerStartMatch[1]}" (lazy)`
1384
1523
  return {
1385
1524
  type: 'status',
1386
- message: 'โš™๏ธ Starting background services',
1525
+ message: `โš™๏ธ ${status}`,
1387
1526
  splashPhase: startupSplashPhase,
1388
- splashDetail: 'Starting background services',
1389
- activity: 'Starting queue workers and scheduler',
1527
+ splashDetail: status,
1528
+ activity: status,
1390
1529
  progressCurrent: 3,
1391
- progressLabel: 'Starting background services',
1530
+ progressLabel: 'Background services (lazy)',
1392
1531
  }
1393
1532
  }
1394
1533
 
@@ -509,6 +509,27 @@ function buildSplashChildEnv() {
509
509
  }
510
510
  }
511
511
 
512
+ function applyLocalDevBackgroundServiceDefaults(childEnv) {
513
+ const env = childEnv ?? {}
514
+ if (
515
+ typeof process.env.OM_AUTO_SPAWN_WORKERS_LAZY !== 'string'
516
+ || process.env.OM_AUTO_SPAWN_WORKERS_LAZY.trim() === ''
517
+ ) {
518
+ env.OM_AUTO_SPAWN_WORKERS_LAZY = 'true'
519
+ }
520
+ if (
521
+ typeof process.env.OM_AUTO_SPAWN_SCHEDULER_LAZY !== 'string'
522
+ || process.env.OM_AUTO_SPAWN_SCHEDULER_LAZY.trim() === ''
523
+ ) {
524
+ env.OM_AUTO_SPAWN_SCHEDULER_LAZY = 'true'
525
+ }
526
+ return env
527
+ }
528
+
529
+ function buildAppDevEnv() {
530
+ return applyLocalDevBackgroundServiceDefaults(buildSplashChildEnv() ?? {})
531
+ }
532
+
512
533
  function launchStandaloneDev(options = {}) {
513
534
  if (!fs.existsSync(standaloneRuntimeScript)) {
514
535
  console.error(`โŒ Standalone dev runtime not found at ${standaloneRuntimeScript}`)
@@ -543,7 +564,7 @@ function launchStandaloneDev(options = {}) {
543
564
 
544
565
  const app = spawnCommand(process.execPath, runtimeArgs, {
545
566
  stdio: 'inherit',
546
- env: buildSplashChildEnv(),
567
+ env: buildAppDevEnv(),
547
568
  })
548
569
 
549
570
  app.on('close', (code) => {
@@ -1590,7 +1611,7 @@ function launchMonorepoAppDev() {
1590
1611
  })
1591
1612
  const app = spawnCommand(yarnCommand, appArgs, {
1592
1613
  stdio: 'inherit',
1593
- env: buildSplashChildEnv(),
1614
+ env: buildAppDevEnv(),
1594
1615
  })
1595
1616
 
1596
1617
  app.on('close', (code, signal) => {
@@ -5,17 +5,49 @@
5
5
  // duplication is deliberate. Keep both copies in sync when editing; CI
6
6
  // runs `yarn template:sync` to enforce parity.
7
7
 
8
- function readOriginHostname(raw: string | undefined): string | null {
8
+ const localDevHostAliases = ['localhost', '127.0.0.1', '[::1]', '0.0.0.0', 'host.docker.internal'] as const
9
+ const localDevHosts = new Set<string>([...localDevHostAliases, '::1'])
10
+
11
+ function normalizeHostname(hostname: string): string {
12
+ const normalized = hostname.trim().toLowerCase()
13
+ return normalized === '::1' ? '[::1]' : normalized
14
+ }
15
+
16
+ function isLocalDevHost(hostname: string): boolean {
17
+ return localDevHosts.has(hostname)
18
+ }
19
+
20
+ function isAllowedBareHostname(hostname: string): boolean {
21
+ return isLocalDevHost(hostname) || hostname.includes('.') || hostname.startsWith('*.') || hostname.startsWith('**.')
22
+ }
23
+
24
+ function readUrlOriginHostname(raw: string | undefined): string | null {
9
25
  const value = raw?.trim()
10
26
  if (!value) return null
11
27
 
12
28
  try {
13
- return new URL(value).hostname
29
+ return normalizeHostname(new URL(value).hostname)
14
30
  } catch {
15
31
  return null
16
32
  }
17
33
  }
18
34
 
35
+ function readAllowedOriginHostname(raw: string | undefined): string | null {
36
+ const value = raw?.trim()
37
+ if (!value || /\s/.test(value)) return null
38
+
39
+ const urlHostname = readUrlOriginHostname(value)
40
+ if (urlHostname) return urlHostname
41
+
42
+ try {
43
+ const hostname = normalizeHostname(new URL(`http://${value}`).hostname)
44
+ return isAllowedBareHostname(hostname) ? hostname : null
45
+ } catch {
46
+ const hostname = normalizeHostname(value)
47
+ return isAllowedBareHostname(hostname) ? hostname : null
48
+ }
49
+ }
50
+
19
51
  function readCsv(value: string | undefined): string[] {
20
52
  return (value ?? '')
21
53
  .split(',')
@@ -23,13 +55,31 @@ function readCsv(value: string | undefined): string[] {
23
55
  .filter((item) => item.length > 0)
24
56
  }
25
57
 
58
+ function addOriginHostname(origins: Set<string>, hostname: string): void {
59
+ const normalized = normalizeHostname(hostname)
60
+ origins.add(normalized)
61
+
62
+ if (isLocalDevHost(normalized)) {
63
+ for (const alias of localDevHostAliases) {
64
+ origins.add(alias)
65
+ }
66
+ }
67
+ }
68
+
26
69
  export function resolveAllowedDevOrigins(env: NodeJS.ProcessEnv = process.env): string[] {
27
70
  const origins = new Set<string>()
28
71
 
29
- for (const raw of [env.APP_URL, env.NEXT_PUBLIC_APP_URL, ...readCsv(env.APP_ALLOWED_ORIGINS)]) {
30
- const hostname = readOriginHostname(raw)
72
+ for (const raw of [env.APP_URL, env.NEXT_PUBLIC_APP_URL]) {
73
+ const hostname = readUrlOriginHostname(raw)
74
+ if (hostname) {
75
+ addOriginHostname(origins, hostname)
76
+ }
77
+ }
78
+
79
+ for (const raw of readCsv(env.APP_ALLOWED_ORIGINS)) {
80
+ const hostname = readAllowedOriginHostname(raw)
31
81
  if (hostname) {
32
- origins.add(hostname)
82
+ addOriginHostname(origins, hostname)
33
83
  }
34
84
  }
35
85