create-mercato-app 0.6.6-develop.6317.1.b3be10ab84 → 0.6.6-develop.6331.1.a33b8e99b7

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.6-develop.6317.1.b3be10ab84",
3
+ "version": "0.6.6-develop.6331.1.a33b8e99b7",
4
4
  "type": "module",
5
5
  "description": "Create a new Open Mercato application",
6
6
  "main": "./dist/index.js",
@@ -12,6 +12,7 @@
12
12
  "dev": "node ./scripts/dev.mjs",
13
13
  "dev:classic": "node ./scripts/dev.mjs --classic",
14
14
  "dev:verbose": "node ./scripts/dev.mjs --verbose",
15
+ "dev:fix-wsl-watchers": "node ./scripts/fix-wsl-inotify.mjs",
15
16
  "dev:reset": "node ./scripts/dev-reset.mjs",
16
17
  "build": "yarn generate && cross-env NODE_OPTIONS=--max-old-space-size=8192 next build",
17
18
  "start": "mercato server start",
@@ -0,0 +1,270 @@
1
+ import fs from 'node:fs'
2
+ import { spawnSync as defaultSpawnSync } from 'node:child_process'
3
+
4
+ export const DEFAULT_INOTIFY_LIMITS = {
5
+ max_user_watches: 4194304,
6
+ max_user_instances: 4096,
7
+ max_queued_events: 65536,
8
+ }
9
+
10
+ const INOTIFY_PROC_DIR = '/proc/sys/fs/inotify'
11
+ const WSL_MARKER_PATHS = [
12
+ '/proc/sys/kernel/osrelease',
13
+ '/proc/version',
14
+ ]
15
+
16
+ export const VSCODE_WATCHER_EXCLUDES = {
17
+ '**/.git/**': true,
18
+ '**/.turbo/**': true,
19
+ '**/dist/**': true,
20
+ '**/node_modules/**': true,
21
+ '.ai/tmp/**': true,
22
+ '.claude/**': true,
23
+ '.mercato/**': true,
24
+ 'apps/mercato/.mercato/**': true,
25
+ }
26
+
27
+ function parseInteger(value) {
28
+ const parsed = Number.parseInt(String(value ?? '').trim(), 10)
29
+ return Number.isInteger(parsed) ? parsed : null
30
+ }
31
+
32
+ export function detectWsl({
33
+ platform = process.platform,
34
+ readFileSync = fs.readFileSync,
35
+ } = {}) {
36
+ if (platform !== 'linux') return false
37
+
38
+ for (const markerPath of WSL_MARKER_PATHS) {
39
+ try {
40
+ const marker = String(readFileSync(markerPath, 'utf8')).toLowerCase()
41
+ if (marker.includes('microsoft') || marker.includes('wsl')) {
42
+ return true
43
+ }
44
+ } catch {
45
+ // Non-WSL Linux images may not expose both marker files.
46
+ }
47
+ }
48
+
49
+ return false
50
+ }
51
+
52
+ export function readCurrentInotifyLimits({
53
+ readFileSync = fs.readFileSync,
54
+ } = {}) {
55
+ const values = {}
56
+ const errors = {}
57
+
58
+ for (const name of Object.keys(DEFAULT_INOTIFY_LIMITS)) {
59
+ const filePath = `${INOTIFY_PROC_DIR}/${name}`
60
+ try {
61
+ const value = parseInteger(readFileSync(filePath, 'utf8'))
62
+ if (value === null) {
63
+ errors[name] = `invalid value in ${filePath}`
64
+ } else {
65
+ values[name] = value
66
+ }
67
+ } catch (error) {
68
+ errors[name] = error?.message ?? String(error)
69
+ }
70
+ }
71
+
72
+ return { values, errors }
73
+ }
74
+
75
+ export function findInotifyLimitIssues(current, required = DEFAULT_INOTIFY_LIMITS) {
76
+ const issues = []
77
+
78
+ for (const [name, minimum] of Object.entries(required)) {
79
+ const currentValue = current?.[name]
80
+ if (typeof currentValue !== 'number') continue
81
+ if (currentValue < minimum) {
82
+ issues.push({ name, current: currentValue, required: minimum })
83
+ }
84
+ }
85
+
86
+ return issues
87
+ }
88
+
89
+ export function buildSysctlAssignments(
90
+ issues,
91
+ required = DEFAULT_INOTIFY_LIMITS,
92
+ ) {
93
+ return issues.map((issue) => `fs.inotify.${issue.name}=${required[issue.name]}`)
94
+ }
95
+
96
+ export function buildPersistentSysctlConfig(required = DEFAULT_INOTIFY_LIMITS) {
97
+ return [
98
+ '# Open Mercato dev server file-watch limits',
99
+ ...Object.entries(required).map(([name, value]) => `fs.inotify.${name}=${value}`),
100
+ '',
101
+ ].join('\n')
102
+ }
103
+
104
+ export function buildManualInotifyFix({ required = DEFAULT_INOTIFY_LIMITS } = {}) {
105
+ const assignments = Object.entries(required)
106
+ .map(([name, value]) => `fs.inotify.${name}=${value}`)
107
+ .join(' ')
108
+ const config = buildPersistentSysctlConfig(required).replace(/\n/g, '\\n')
109
+
110
+ return [
111
+ `sudo sysctl -w ${assignments}`,
112
+ `printf '${config}' | sudo tee /etc/sysctl.d/99-open-mercato-inotify.conf >/dev/null`,
113
+ 'sudo sysctl --system',
114
+ ]
115
+ }
116
+
117
+ export function mergeVsCodeWatcherExcludes(settings, excludes = VSCODE_WATCHER_EXCLUDES) {
118
+ const next = {
119
+ ...(settings && typeof settings === 'object' && !Array.isArray(settings) ? settings : {}),
120
+ }
121
+ let changed = false
122
+
123
+ for (const key of ['files.watcherExclude', 'search.exclude']) {
124
+ const current = next[key] && typeof next[key] === 'object' && !Array.isArray(next[key])
125
+ ? next[key]
126
+ : {}
127
+ const merged = { ...current }
128
+
129
+ for (const [pattern, value] of Object.entries(excludes)) {
130
+ if (merged[pattern] !== value) {
131
+ merged[pattern] = value
132
+ changed = true
133
+ }
134
+ }
135
+
136
+ if (next[key] !== merged) {
137
+ next[key] = merged
138
+ }
139
+ }
140
+
141
+ return { settings: next, changed }
142
+ }
143
+
144
+ function resolveSysctlCommand({ getuid = process.getuid?.bind(process), nonInteractive = true } = {}) {
145
+ if (typeof getuid === 'function' && getuid() === 0) {
146
+ return { command: 'sysctl', args: ['-w'] }
147
+ }
148
+
149
+ return {
150
+ command: 'sudo',
151
+ args: [nonInteractive ? '-n' : null, 'sysctl', '-w'].filter(Boolean),
152
+ }
153
+ }
154
+
155
+ export function applyInotifyLimits({
156
+ issues,
157
+ required = DEFAULT_INOTIFY_LIMITS,
158
+ spawnSync = defaultSpawnSync,
159
+ getuid = process.getuid?.bind(process),
160
+ nonInteractive = true,
161
+ stdio = 'pipe',
162
+ } = {}) {
163
+ if (!Array.isArray(issues) || issues.length === 0) {
164
+ return { ok: true, skipped: true }
165
+ }
166
+
167
+ const { command, args } = resolveSysctlCommand({ getuid, nonInteractive })
168
+ const assignments = buildSysctlAssignments(issues, required)
169
+ const result = spawnSync(command, [...args, ...assignments], {
170
+ encoding: 'utf8',
171
+ stdio,
172
+ })
173
+
174
+ return {
175
+ ok: result.status === 0,
176
+ command,
177
+ args: [...args, ...assignments],
178
+ status: result.status,
179
+ error: result.error,
180
+ stderr: result.stderr,
181
+ stdout: result.stdout,
182
+ }
183
+ }
184
+
185
+ export function formatInotifyLimitFailure({
186
+ current,
187
+ issues,
188
+ manualCommands = buildManualInotifyFix(),
189
+ isWsl = false,
190
+ } = {}) {
191
+ const location = isWsl ? 'WSL2/Linux' : 'Linux'
192
+ const issueLines = issues.map((issue) =>
193
+ ` fs.inotify.${issue.name}: ${issue.current} < ${issue.required}`,
194
+ )
195
+
196
+ return [
197
+ `❌ ${location} file-watch limits are too low for Turbopack.`,
198
+ 'Turbopack will panic with "OS file watch limit reached" unless these sysctl values are raised.',
199
+ '',
200
+ ...issueLines,
201
+ '',
202
+ 'Run this once in your WSL/Linux terminal:',
203
+ ...manualCommands.map((line) => ` ${line}`),
204
+ '',
205
+ `Current values: ${JSON.stringify(current)}`,
206
+ ].join('\n')
207
+ }
208
+
209
+ export function ensureDevInotifyLimits({
210
+ env = process.env,
211
+ platform = process.platform,
212
+ readFileSync = fs.readFileSync,
213
+ spawnSync = defaultSpawnSync,
214
+ getuid = process.getuid?.bind(process),
215
+ required = DEFAULT_INOTIFY_LIMITS,
216
+ } = {}) {
217
+ const rawMode = String(env.OM_DEV_INOTIFY_CHECK ?? '').trim().toLowerCase()
218
+ if (['0', 'false', 'off', 'skip'].includes(rawMode)) {
219
+ return { ok: true, skipped: true, reason: 'disabled' }
220
+ }
221
+
222
+ if (platform !== 'linux') {
223
+ return { ok: true, skipped: true, reason: 'non-linux' }
224
+ }
225
+
226
+ const isWsl = detectWsl({ platform, readFileSync })
227
+ const { values, errors } = readCurrentInotifyLimits({ readFileSync })
228
+ const issues = findInotifyLimitIssues(values, required)
229
+
230
+ if (issues.length === 0) {
231
+ return { ok: true, current: values, isWsl }
232
+ }
233
+
234
+ const applyResult = applyInotifyLimits({
235
+ issues,
236
+ required,
237
+ spawnSync,
238
+ getuid,
239
+ nonInteractive: true,
240
+ stdio: 'pipe',
241
+ })
242
+
243
+ if (applyResult.ok) {
244
+ return {
245
+ ok: true,
246
+ fixed: true,
247
+ current: values,
248
+ issues,
249
+ isWsl,
250
+ applyResult,
251
+ }
252
+ }
253
+
254
+ const manualCommands = buildManualInotifyFix({ required })
255
+ return {
256
+ ok: false,
257
+ current: values,
258
+ errors,
259
+ issues,
260
+ isWsl,
261
+ applyResult,
262
+ manualCommands,
263
+ message: formatInotifyLimitFailure({
264
+ current: values,
265
+ issues,
266
+ manualCommands,
267
+ isWsl,
268
+ }),
269
+ }
270
+ }
@@ -23,6 +23,7 @@ import {
23
23
  stripAnsi,
24
24
  } from './dev-splash-helpers.mjs'
25
25
  import { purgeAppBuildCaches } from './dev-cache-purge.mjs'
26
+ import { ensureDevInotifyLimits } from './dev-inotify-limits.mjs'
26
27
  import { killProcessTree } from './dev-shutdown-utils.mjs'
27
28
  import { resolveSpawnCommand } from './dev-spawn-utils.mjs'
28
29
  import { createDevSplashCodingFlow } from './dev-splash-coding-flow.mjs'
@@ -317,6 +318,32 @@ function printDevLogLocation() {
317
318
  process.env.OM_DEV_LOG_ANNOUNCED = '1'
318
319
  }
319
320
 
321
+ function ensureDevFileWatchLimits() {
322
+ const result = ensureDevInotifyLimits()
323
+ if (result.fixed) {
324
+ console.log('🔧 Raised Linux inotify file-watch limits for Turbopack')
325
+ return true
326
+ }
327
+ if (result.ok) {
328
+ return true
329
+ }
330
+
331
+ updateSplashState({
332
+ phase: 'File-watch limits too low',
333
+ detail: 'Raise Linux inotify limits before starting Turbopack',
334
+ failed: true,
335
+ failureLines: result.message.split('\n').filter(Boolean).slice(0, 10),
336
+ failureCommand: 'yarn dev:fix-wsl-watchers',
337
+ ready: false,
338
+ readyUrl: null,
339
+ loginUrl: null,
340
+ activity: 'File-watch limit preflight failed',
341
+ })
342
+ console.error(result.message)
343
+ shutdown(1)
344
+ return false
345
+ }
346
+
320
347
  function spawnCommand(command, commandArgs, options = {}) {
321
348
  const resolvedSpawn = resolveSpawnCommand(command, commandArgs)
322
349
  const teeRequested = options.mirrorOutput === true
@@ -1756,6 +1783,7 @@ async function runClassicStandaloneDev() {
1756
1783
  async function main() {
1757
1784
  printDevLogLocation()
1758
1785
  await startSplashServer()
1786
+ if (!ensureDevFileWatchLimits()) return
1759
1787
 
1760
1788
  if (!isMonorepo) {
1761
1789
  if (setupMode) {
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from 'node:child_process'
3
+ import fs from 'node:fs'
4
+ import path from 'node:path'
5
+ import readline from 'node:readline/promises'
6
+ import {
7
+ DEFAULT_INOTIFY_LIMITS,
8
+ buildPersistentSysctlConfig,
9
+ buildSysctlAssignments,
10
+ buildManualInotifyFix,
11
+ detectWsl,
12
+ findInotifyLimitIssues,
13
+ mergeVsCodeWatcherExcludes,
14
+ readCurrentInotifyLimits,
15
+ } from './dev-inotify-limits.mjs'
16
+
17
+ const PERSIST_PATH = '/etc/sysctl.d/99-open-mercato-inotify.conf'
18
+ const noPersist = process.argv.includes('--no-persist')
19
+ const assumeYes = process.argv.includes('--yes') || process.argv.includes('-y')
20
+ const applyVsCode = process.argv.includes('--vscode')
21
+ const skipVsCode = process.argv.includes('--no-vscode')
22
+
23
+ async function confirm(question, { defaultYes = false } = {}) {
24
+ if (assumeYes) return true
25
+ if (!process.stdin.isTTY || !process.stdout.isTTY) return false
26
+
27
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
28
+ try {
29
+ const suffix = defaultYes ? ' [Y/n] ' : ' [y/N] '
30
+ const answer = (await rl.question(`${question}${suffix}`)).trim().toLowerCase()
31
+ if (!answer) return defaultYes
32
+ return answer === 'y' || answer === 'yes'
33
+ } finally {
34
+ rl.close()
35
+ }
36
+ }
37
+
38
+ function run(command, args, options = {}) {
39
+ const result = spawnSync(command, args, {
40
+ stdio: options.input ? ['pipe', 'inherit', 'inherit'] : 'inherit',
41
+ input: options.input,
42
+ encoding: 'utf8',
43
+ })
44
+ if (result.status !== 0) {
45
+ process.exit(result.status ?? 1)
46
+ }
47
+ }
48
+
49
+ async function maybeUpdateVsCodeSettings() {
50
+ if (skipVsCode) return
51
+
52
+ const shouldApply = applyVsCode || (!assumeYes && await confirm(
53
+ 'Optionally update .vscode/settings.json to exclude generated/cache/worktree folders from VS Code file watching? This modifies a tracked workspace file.',
54
+ ))
55
+ if (!shouldApply) return
56
+
57
+ const settingsPath = path.join(process.cwd(), '.vscode', 'settings.json')
58
+ let settings = {}
59
+ if (fs.existsSync(settingsPath)) {
60
+ try {
61
+ settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'))
62
+ } catch (error) {
63
+ console.warn(`Could not parse ${settingsPath}; leaving VS Code settings unchanged: ${error?.message ?? error}`)
64
+ return
65
+ }
66
+ }
67
+
68
+ const { settings: nextSettings, changed } = mergeVsCodeWatcherExcludes(settings)
69
+ if (!changed) {
70
+ console.log('VS Code watcher excludes already include the recommended Open Mercato patterns.')
71
+ return
72
+ }
73
+
74
+ fs.mkdirSync(path.dirname(settingsPath), { recursive: true })
75
+ fs.writeFileSync(settingsPath, `${JSON.stringify(nextSettings, null, 4)}\n`)
76
+ console.log(`Updated ${settingsPath}. Reload the WSL VS Code window so existing watcher handles are released.`)
77
+ }
78
+
79
+ function printCurrent(prefix) {
80
+ const { values } = readCurrentInotifyLimits()
81
+ console.log(`${prefix} ${JSON.stringify(values)}`)
82
+ return values
83
+ }
84
+
85
+ if (process.platform !== 'linux') {
86
+ console.log('Linux inotify limits do not apply on this platform; no sysctl changes are needed.')
87
+ process.exit(0)
88
+ }
89
+
90
+ const current = printCurrent('Current inotify limits:')
91
+ const issues = findInotifyLimitIssues(current)
92
+
93
+ if (issues.length === 0) {
94
+ console.log('Inotify limits already satisfy Open Mercato dev requirements.')
95
+ await maybeUpdateVsCodeSettings()
96
+ process.exit(0)
97
+ }
98
+
99
+ if (detectWsl()) {
100
+ console.log('WSL detected; raising Linux inotify limits for Turbopack.')
101
+ } else {
102
+ console.log('Raising Linux inotify limits for Turbopack.')
103
+ }
104
+
105
+ for (const command of buildManualInotifyFix()) {
106
+ console.log(` ${command}`)
107
+ }
108
+
109
+ if (!await confirm('Apply these sysctl changes now?', { defaultYes: true })) {
110
+ console.log('No changes applied.')
111
+ process.exit(1)
112
+ }
113
+
114
+ run('sudo', ['sysctl', '-w', ...buildSysctlAssignments(issues)])
115
+
116
+ if (!noPersist) {
117
+ run('sudo', ['tee', PERSIST_PATH], {
118
+ input: buildPersistentSysctlConfig(DEFAULT_INOTIFY_LIMITS),
119
+ })
120
+ run('sudo', ['sysctl', '--system'])
121
+ }
122
+
123
+ printCurrent('Updated inotify limits:')
124
+ await maybeUpdateVsCodeSettings()