syncorejs 0.2.1 → 0.2.2

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.
Files changed (135) hide show
  1. package/README.md +2 -1
  2. package/dist/_vendor/cli/app.d.mts.map +1 -1
  3. package/dist/_vendor/cli/app.mjs +323 -42
  4. package/dist/_vendor/cli/app.mjs.map +1 -1
  5. package/dist/_vendor/cli/context.mjs +27 -9
  6. package/dist/_vendor/cli/context.mjs.map +1 -1
  7. package/dist/_vendor/cli/doctor.mjs +513 -46
  8. package/dist/_vendor/cli/doctor.mjs.map +1 -1
  9. package/dist/_vendor/cli/messages.mjs +5 -4
  10. package/dist/_vendor/cli/messages.mjs.map +1 -1
  11. package/dist/_vendor/cli/project.mjs +110 -12
  12. package/dist/_vendor/cli/project.mjs.map +1 -1
  13. package/dist/_vendor/cli/render.mjs +57 -9
  14. package/dist/_vendor/cli/render.mjs.map +1 -1
  15. package/dist/_vendor/cli/targets.mjs +4 -3
  16. package/dist/_vendor/cli/targets.mjs.map +1 -1
  17. package/dist/_vendor/core/cli.d.mts +13 -3
  18. package/dist/_vendor/core/cli.d.mts.map +1 -1
  19. package/dist/_vendor/core/cli.mjs +242 -91
  20. package/dist/_vendor/core/cli.mjs.map +1 -1
  21. package/dist/_vendor/core/devtools-auth.mjs +60 -0
  22. package/dist/_vendor/core/devtools-auth.mjs.map +1 -0
  23. package/dist/_vendor/core/index.d.mts +5 -3
  24. package/dist/_vendor/core/index.mjs +22 -2
  25. package/dist/_vendor/core/index.mjs.map +1 -1
  26. package/dist/_vendor/core/runtime/components.d.mts +111 -0
  27. package/dist/_vendor/core/runtime/components.d.mts.map +1 -0
  28. package/dist/_vendor/core/runtime/components.mjs +186 -0
  29. package/dist/_vendor/core/runtime/components.mjs.map +1 -0
  30. package/dist/_vendor/core/runtime/devtools.d.mts +4 -4
  31. package/dist/_vendor/core/runtime/devtools.d.mts.map +1 -1
  32. package/dist/_vendor/core/runtime/devtools.mjs +52 -41
  33. package/dist/_vendor/core/runtime/devtools.mjs.map +1 -1
  34. package/dist/_vendor/core/runtime/functions.d.mts +10 -10
  35. package/dist/_vendor/core/runtime/functions.d.mts.map +1 -1
  36. package/dist/_vendor/core/runtime/functions.mjs +2 -2
  37. package/dist/_vendor/core/runtime/functions.mjs.map +1 -1
  38. package/dist/_vendor/core/runtime/internal/engines/devtoolsEngine.mjs +77 -0
  39. package/dist/_vendor/core/runtime/internal/engines/devtoolsEngine.mjs.map +1 -0
  40. package/dist/_vendor/core/runtime/internal/engines/executionEngine.mjs +617 -0
  41. package/dist/_vendor/core/runtime/internal/engines/executionEngine.mjs.map +1 -0
  42. package/dist/_vendor/core/runtime/internal/engines/reactivityEngine.mjs +186 -0
  43. package/dist/_vendor/core/runtime/internal/engines/reactivityEngine.mjs.map +1 -0
  44. package/dist/_vendor/core/runtime/internal/engines/schedulerEngine.mjs +220 -0
  45. package/dist/_vendor/core/runtime/internal/engines/schedulerEngine.mjs.map +1 -0
  46. package/dist/_vendor/core/runtime/internal/engines/schemaEngine.mjs +203 -0
  47. package/dist/_vendor/core/runtime/internal/engines/schemaEngine.mjs.map +1 -0
  48. package/dist/_vendor/core/runtime/internal/engines/shared.mjs +177 -0
  49. package/dist/_vendor/core/runtime/internal/engines/shared.mjs.map +1 -0
  50. package/dist/_vendor/core/runtime/internal/engines/storageEngine.mjs +144 -0
  51. package/dist/_vendor/core/runtime/internal/engines/storageEngine.mjs.map +1 -0
  52. package/dist/_vendor/core/runtime/internal/runtimeKernel.mjs +220 -0
  53. package/dist/_vendor/core/runtime/internal/runtimeKernel.mjs.map +1 -0
  54. package/dist/_vendor/core/runtime/internal/runtimeStatus.mjs +32 -0
  55. package/dist/_vendor/core/runtime/internal/runtimeStatus.mjs.map +1 -0
  56. package/dist/_vendor/core/runtime/internal/systemMeta.mjs +61 -0
  57. package/dist/_vendor/core/runtime/internal/systemMeta.mjs.map +1 -0
  58. package/dist/_vendor/core/runtime/internal/transactionCoordinator.mjs +37 -0
  59. package/dist/_vendor/core/runtime/internal/transactionCoordinator.mjs.map +1 -0
  60. package/dist/_vendor/core/runtime/runtime.d.mts +159 -205
  61. package/dist/_vendor/core/runtime/runtime.d.mts.map +1 -1
  62. package/dist/_vendor/core/runtime/runtime.mjs +16 -1371
  63. package/dist/_vendor/core/runtime/runtime.mjs.map +1 -1
  64. package/dist/_vendor/core/transport.d.mts +111 -0
  65. package/dist/_vendor/core/transport.d.mts.map +1 -0
  66. package/dist/_vendor/core/transport.mjs +419 -0
  67. package/dist/_vendor/core/transport.mjs.map +1 -0
  68. package/dist/_vendor/devtools-protocol/index.d.ts +39 -1
  69. package/dist/_vendor/devtools-protocol/index.d.ts.map +1 -1
  70. package/dist/_vendor/devtools-protocol/index.js +25 -9
  71. package/dist/_vendor/devtools-protocol/index.js.map +1 -1
  72. package/dist/_vendor/next/index.d.ts +1 -1
  73. package/dist/_vendor/next/index.d.ts.map +1 -1
  74. package/dist/_vendor/next/index.js +31 -13
  75. package/dist/_vendor/next/index.js.map +1 -1
  76. package/dist/_vendor/platform-expo/index.d.ts +12 -12
  77. package/dist/_vendor/platform-expo/index.d.ts.map +1 -1
  78. package/dist/_vendor/platform-expo/index.js +4 -2
  79. package/dist/_vendor/platform-expo/index.js.map +1 -1
  80. package/dist/_vendor/platform-expo/react.d.ts.map +1 -1
  81. package/dist/_vendor/platform-expo/react.js +11 -10
  82. package/dist/_vendor/platform-expo/react.js.map +1 -1
  83. package/dist/_vendor/platform-node/index.d.mts +23 -19
  84. package/dist/_vendor/platform-node/index.d.mts.map +1 -1
  85. package/dist/_vendor/platform-node/index.mjs +13 -5
  86. package/dist/_vendor/platform-node/index.mjs.map +1 -1
  87. package/dist/_vendor/platform-node/ipc-react.d.mts.map +1 -1
  88. package/dist/_vendor/platform-node/ipc-react.mjs +15 -2
  89. package/dist/_vendor/platform-node/ipc-react.mjs.map +1 -1
  90. package/dist/_vendor/platform-node/ipc.d.mts +11 -35
  91. package/dist/_vendor/platform-node/ipc.d.mts.map +1 -1
  92. package/dist/_vendor/platform-node/ipc.mjs +3 -273
  93. package/dist/_vendor/platform-node/ipc.mjs.map +1 -1
  94. package/dist/_vendor/platform-web/external-change.d.ts +2 -1
  95. package/dist/_vendor/platform-web/external-change.d.ts.map +1 -1
  96. package/dist/_vendor/platform-web/external-change.js +2 -1
  97. package/dist/_vendor/platform-web/external-change.js.map +1 -1
  98. package/dist/_vendor/platform-web/index.d.ts +21 -21
  99. package/dist/_vendor/platform-web/index.d.ts.map +1 -1
  100. package/dist/_vendor/platform-web/index.js +44 -7
  101. package/dist/_vendor/platform-web/index.js.map +1 -1
  102. package/dist/_vendor/platform-web/react.d.ts.map +1 -1
  103. package/dist/_vendor/platform-web/react.js +29 -13
  104. package/dist/_vendor/platform-web/react.js.map +1 -1
  105. package/dist/_vendor/platform-web/worker.d.ts +11 -35
  106. package/dist/_vendor/platform-web/worker.d.ts.map +1 -1
  107. package/dist/_vendor/platform-web/worker.js +3 -267
  108. package/dist/_vendor/platform-web/worker.js.map +1 -1
  109. package/dist/_vendor/react/index.d.ts +36 -20
  110. package/dist/_vendor/react/index.d.ts.map +1 -1
  111. package/dist/_vendor/react/index.js +279 -57
  112. package/dist/_vendor/react/index.js.map +1 -1
  113. package/dist/_vendor/schema/definition.d.ts +48 -63
  114. package/dist/_vendor/schema/definition.d.ts.map +1 -1
  115. package/dist/_vendor/schema/definition.js +22 -39
  116. package/dist/_vendor/schema/definition.js.map +1 -1
  117. package/dist/_vendor/schema/index.d.ts +4 -4
  118. package/dist/_vendor/schema/index.js +2 -2
  119. package/dist/_vendor/schema/planner.d.ts +19 -2
  120. package/dist/_vendor/schema/planner.d.ts.map +1 -1
  121. package/dist/_vendor/schema/planner.js +79 -3
  122. package/dist/_vendor/schema/planner.js.map +1 -1
  123. package/dist/_vendor/schema/validators.d.ts +141 -121
  124. package/dist/_vendor/schema/validators.d.ts.map +1 -1
  125. package/dist/_vendor/schema/validators.js +300 -42
  126. package/dist/_vendor/schema/validators.js.map +1 -1
  127. package/dist/_vendor/svelte/index.d.ts +47 -19
  128. package/dist/_vendor/svelte/index.d.ts.map +1 -1
  129. package/dist/_vendor/svelte/index.js +250 -20
  130. package/dist/_vendor/svelte/index.js.map +1 -1
  131. package/dist/components.d.ts +2 -0
  132. package/dist/components.js +2 -0
  133. package/dist/index.d.ts +3 -2
  134. package/dist/index.js +2 -1
  135. package/package.json +8 -3
@@ -1 +1 @@
1
- {"version":3,"file":"context.mjs","names":[],"sources":["../src/context.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport path from \"node:path\";\nimport process from \"node:process\";\nimport readline from \"node:readline/promises\";\nimport { CliError, type CliErrorCategory, normalizeCliError } from \"./errors.js\";\n\nexport interface GlobalCliOptions {\n cwd?: string;\n json?: boolean;\n verbose?: boolean;\n interactive?: boolean;\n yes?: boolean;\n}\n\nexport interface CliChoice<TValue> {\n label: string;\n value: TValue;\n description?: string;\n}\n\nexport interface CliResult<TValue = unknown> {\n command?: string;\n summary?: string;\n target?: string;\n data?: TValue;\n nextSteps?: string[];\n}\n\nexport class CliContext {\n readonly cwd: string;\n readonly json: boolean;\n readonly verbose: boolean;\n readonly interactive: boolean;\n readonly yes: boolean;\n\n constructor(options: GlobalCliOptions = {}) {\n this.cwd = path.resolve(options.cwd ?? process.cwd());\n this.json = options.json ?? false;\n this.verbose = options.verbose ?? false;\n this.yes = options.yes ?? false;\n this.interactive =\n options.interactive === false\n ? false\n : process.env.SYNCORE_FORCE_INTERACTIVE === \"1\"\n ? true\n : Boolean(process.stdin.isTTY && process.stdout.isTTY && !this.json);\n }\n\n info(message: string): void {\n if (!this.json) {\n process.stdout.write(`[info] ${message}\\n`);\n }\n }\n\n success(message: string): void {\n if (!this.json) {\n process.stdout.write(`[done] ${message}\\n`);\n }\n }\n\n warn(message: string): void {\n if (!this.json) {\n process.stderr.write(`[warn] ${message}\\n`);\n }\n }\n\n error(message: string): void {\n if (!this.json) {\n process.stderr.write(`[error] ${message}\\n`);\n }\n }\n\n nextStep(message: string): void {\n if (!this.json) {\n process.stdout.write(`[next] ${message}\\n`);\n }\n }\n\n printJson(payload: unknown): void {\n process.stdout.write(`${JSON.stringify(payload, null, 2)}\\n`);\n }\n\n printResult<TValue>(result: CliResult<TValue>): void {\n if (this.json) {\n this.printJson({\n ok: true,\n cwd: this.cwd,\n ...(result.command ? { command: result.command } : {}),\n ...(result.target ? { target: result.target } : {}),\n ...(result.summary ? { summary: result.summary } : {}),\n ...(result.data !== undefined ? { data: result.data } : {}),\n ...(result.nextSteps ? { nextSteps: result.nextSteps } : {})\n });\n return;\n }\n\n if (result.summary) {\n this.success(result.summary);\n }\n if (result.nextSteps) {\n for (const step of result.nextSteps) {\n this.nextStep(step);\n }\n }\n }\n\n fail(\n message: string,\n exitCode = 1,\n details?: unknown,\n options: {\n category?: CliErrorCategory;\n nextSteps?: string[];\n } = {}\n ): never {\n const errorOptions = {\n exitCode,\n details,\n ...(options.category ? { category: options.category } : {}),\n ...(options.nextSteps ? { nextSteps: options.nextSteps } : {})\n };\n throw new CliError(message, errorOptions);\n }\n\n handleError(error: unknown): void {\n const cliError = normalizeCliError(error);\n\n if (this.json) {\n this.printJson({\n ok: false,\n cwd: this.cwd,\n error: {\n category: cliError.category,\n message: cliError.message,\n exitCode: cliError.exitCode,\n ...(cliError.details !== undefined ? { details: cliError.details } : {}),\n ...(cliError.nextSteps ? { nextSteps: cliError.nextSteps } : {})\n }\n });\n } else {\n this.error(cliError.message);\n if (cliError.nextSteps) {\n for (const step of cliError.nextSteps) {\n this.nextStep(step);\n }\n }\n if (this.verbose && cliError.details !== undefined) {\n this.error(JSON.stringify(cliError.details, null, 2));\n }\n }\n\n process.exitCode = cliError.exitCode;\n }\n\n async confirm(message: string, defaultValue = true): Promise<boolean> {\n if (this.yes) {\n return true;\n }\n if (!this.interactive) {\n return false;\n }\n\n const suffix = defaultValue ? \"Y/n\" : \"y/N\";\n const answer = await this.input(`${message} [${suffix}]`, {\n defaultValue: defaultValue ? \"y\" : \"n\"\n });\n const normalized = answer.trim().toLowerCase();\n if (normalized.length === 0) {\n return defaultValue;\n }\n return normalized === \"y\" || normalized === \"yes\";\n }\n\n async input(\n message: string,\n options: {\n defaultValue?: string;\n allowEmpty?: boolean;\n } = {}\n ): Promise<string> {\n if (!this.interactive) {\n this.fail(`Cannot prompt in non-interactive mode: ${message}`);\n }\n\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout\n });\n\n try {\n const prompt =\n options.defaultValue !== undefined\n ? `${message} (${options.defaultValue}): `\n : `${message}: `;\n const answer = await rl.question(prompt);\n if (answer.length === 0 && options.defaultValue !== undefined) {\n return options.defaultValue;\n }\n if (answer.length === 0 && !options.allowEmpty) {\n this.fail(`A value is required for: ${message}`);\n }\n return answer;\n } finally {\n rl.close();\n }\n }\n\n async select<TValue>(\n message: string,\n choices: CliChoice<TValue>[],\n defaultValue?: TValue\n ): Promise<TValue> {\n if (!this.interactive) {\n this.fail(`Cannot prompt in non-interactive mode: ${message}`);\n }\n if (choices.length === 0) {\n this.fail(`No choices are available for: ${message}`);\n }\n\n this.info(message);\n choices.forEach((choice, index) => {\n const suffix = choice.description ? ` - ${choice.description}` : \"\";\n process.stdout.write(` ${index + 1}. ${choice.label}${suffix}\\n`);\n });\n\n const defaultIndex = defaultValue\n ? Math.max(\n choices.findIndex((choice) => choice.value === defaultValue),\n 0\n )\n : 0;\n const rawValue = await this.input(`Choose 1-${choices.length}`, {\n defaultValue: String(defaultIndex + 1)\n });\n const index = Number.parseInt(rawValue, 10);\n if (Number.isNaN(index) || index < 1 || index > choices.length) {\n this.fail(`Expected a value between 1 and ${choices.length}.`);\n }\n return choices[index - 1]!.value;\n }\n\n async withSpinner<TValue>(\n label: string,\n action: () => Promise<TValue>\n ): Promise<TValue> {\n if (!this.interactive) {\n this.info(label);\n return await action();\n }\n\n const frames = [\"-\", \"\\\\\", \"|\", \"/\"];\n let index = 0;\n process.stderr.write(`[work] ${label}`);\n const timer = setInterval(() => {\n process.stderr.write(`\\r[${frames[index % frames.length]}] ${label}`);\n index += 1;\n }, 80);\n\n try {\n const result = await action();\n clearInterval(timer);\n process.stderr.write(`\\r[done] ${label}\\n`);\n return result;\n } catch (error) {\n clearInterval(timer);\n process.stderr.write(`\\r[fail] ${label}\\n`);\n throw error;\n }\n }\n}\n\nexport async function openTarget(target: string): Promise<boolean> {\n const command =\n process.platform === \"win32\"\n ? {\n file: \"cmd\",\n args: [\"/c\", \"start\", \"\", target]\n }\n : process.platform === \"darwin\"\n ? {\n file: \"open\",\n args: [target]\n }\n : {\n file: \"xdg-open\",\n args: [target]\n };\n\n return await new Promise((resolve) => {\n const child = spawn(command.file, command.args, {\n detached: true,\n stdio: \"ignore\"\n });\n child.once(\"error\", () => resolve(false));\n child.once(\"spawn\", () => {\n child.unref();\n resolve(true);\n });\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"context.mjs","names":[],"sources":["../src/context.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport path from \"node:path\";\nimport process from \"node:process\";\nimport readline from \"node:readline/promises\";\nimport { CliError, type CliErrorCategory, normalizeCliError } from \"./errors.js\";\n\nconst ANSI = {\n reset: \"\\u001b[0m\",\n bold: \"\\u001b[1m\",\n yellow: \"\\u001b[33m\",\n cyan: \"\\u001b[36m\",\n green: \"\\u001b[32m\",\n red: \"\\u001b[31m\",\n magenta: \"\\u001b[35m\",\n dim: \"\\u001b[2m\"\n} as const;\n\nfunction supportsColor(stream: NodeJS.WriteStream): boolean {\n return Boolean(stream.isTTY) && process.env.NO_COLOR !== \"1\" && process.env.TERM !== \"dumb\";\n}\n\nfunction formatPrefix(\n stream: NodeJS.WriteStream,\n level: \"info\" | \"done\" | \"warn\" | \"error\" | \"next\" | \"work\" | \"fail\"\n): string {\n const plain = `[${level}]`;\n if (!supportsColor(stream)) {\n return plain;\n }\n const levelColor =\n level === \"info\"\n ? ANSI.cyan\n : level === \"done\"\n ? ANSI.green\n : level === \"warn\"\n ? ANSI.yellow\n : level === \"error\" || level === \"fail\"\n ? ANSI.red\n : level === \"next\"\n ? ANSI.magenta\n : ANSI.dim;\n return `${levelColor}[${level}]${ANSI.reset}`;\n}\n\nexport interface GlobalCliOptions {\n cwd?: string;\n json?: boolean;\n verbose?: boolean;\n interactive?: boolean;\n yes?: boolean;\n}\n\nexport interface CliChoice<TValue> {\n label: string;\n value: TValue;\n description?: string;\n}\n\nexport interface CliResult<TValue = unknown> {\n command?: string;\n summary?: string;\n target?: string;\n data?: TValue;\n nextSteps?: string[];\n}\n\nexport class CliContext {\n readonly cwd: string;\n readonly json: boolean;\n readonly verbose: boolean;\n readonly interactive: boolean;\n readonly yes: boolean;\n\n constructor(options: GlobalCliOptions = {}) {\n this.cwd = path.resolve(options.cwd ?? process.cwd());\n this.json = options.json ?? false;\n this.verbose = options.verbose ?? false;\n this.yes = options.yes ?? false;\n this.interactive =\n options.interactive === false\n ? false\n : process.env.SYNCORE_FORCE_INTERACTIVE === \"1\"\n ? true\n : Boolean(process.stdin.isTTY && process.stdout.isTTY && !this.json);\n }\n\n info(message: string): void {\n if (!this.json) {\n process.stdout.write(`${formatPrefix(process.stdout, \"info\")} ${message}\\n`);\n }\n }\n\n success(message: string): void {\n if (!this.json) {\n process.stdout.write(`${formatPrefix(process.stdout, \"done\")} ${message}\\n`);\n }\n }\n\n warn(message: string): void {\n if (!this.json) {\n process.stderr.write(`${formatPrefix(process.stderr, \"warn\")} ${message}\\n`);\n }\n }\n\n error(message: string): void {\n if (!this.json) {\n process.stderr.write(`${formatPrefix(process.stderr, \"error\")} ${message}\\n`);\n }\n }\n\n nextStep(message: string): void {\n if (!this.json) {\n process.stdout.write(`${formatPrefix(process.stdout, \"next\")} ${message}\\n`);\n }\n }\n\n printJson(payload: unknown): void {\n process.stdout.write(`${JSON.stringify(payload, null, 2)}\\n`);\n }\n\n printResult<TValue>(result: CliResult<TValue>): void {\n if (this.json) {\n this.printJson({\n ok: true,\n cwd: this.cwd,\n ...(result.command ? { command: result.command } : {}),\n ...(result.target ? { target: result.target } : {}),\n ...(result.summary ? { summary: result.summary } : {}),\n ...(result.data !== undefined ? { data: result.data } : {}),\n ...(result.nextSteps ? { nextSteps: result.nextSteps } : {})\n });\n return;\n }\n\n if (result.summary) {\n this.success(result.summary);\n }\n if (result.nextSteps) {\n for (const step of result.nextSteps) {\n this.nextStep(step);\n }\n }\n }\n\n fail(\n message: string,\n exitCode = 1,\n details?: unknown,\n options: {\n category?: CliErrorCategory;\n nextSteps?: string[];\n } = {}\n ): never {\n const errorOptions = {\n exitCode,\n details,\n ...(options.category ? { category: options.category } : {}),\n ...(options.nextSteps ? { nextSteps: options.nextSteps } : {})\n };\n throw new CliError(message, errorOptions);\n }\n\n handleError(error: unknown): void {\n const cliError = normalizeCliError(error);\n\n if (this.json) {\n this.printJson({\n ok: false,\n cwd: this.cwd,\n error: {\n category: cliError.category,\n message: cliError.message,\n exitCode: cliError.exitCode,\n ...(cliError.details !== undefined ? { details: cliError.details } : {}),\n ...(cliError.nextSteps ? { nextSteps: cliError.nextSteps } : {})\n }\n });\n } else {\n this.error(cliError.message);\n if (cliError.nextSteps) {\n for (const step of cliError.nextSteps) {\n this.nextStep(step);\n }\n }\n if (this.verbose && cliError.details !== undefined) {\n this.error(JSON.stringify(cliError.details, null, 2));\n }\n }\n\n process.exitCode = cliError.exitCode;\n }\n\n async confirm(message: string, defaultValue = true): Promise<boolean> {\n if (this.yes) {\n return true;\n }\n if (!this.interactive) {\n return false;\n }\n\n const suffix = defaultValue ? \"Y/n\" : \"y/N\";\n const answer = await this.input(`${message} [${suffix}]`, {\n defaultValue: defaultValue ? \"y\" : \"n\"\n });\n const normalized = answer.trim().toLowerCase();\n if (normalized.length === 0) {\n return defaultValue;\n }\n return normalized === \"y\" || normalized === \"yes\";\n }\n\n async input(\n message: string,\n options: {\n defaultValue?: string;\n allowEmpty?: boolean;\n } = {}\n ): Promise<string> {\n if (!this.interactive) {\n this.fail(`Cannot prompt in non-interactive mode: ${message}`);\n }\n\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout\n });\n\n try {\n const prompt =\n options.defaultValue !== undefined\n ? `${message} (${options.defaultValue}): `\n : `${message}: `;\n const answer = await rl.question(prompt);\n if (answer.length === 0 && options.defaultValue !== undefined) {\n return options.defaultValue;\n }\n if (answer.length === 0 && !options.allowEmpty) {\n this.fail(`A value is required for: ${message}`);\n }\n return answer;\n } finally {\n rl.close();\n }\n }\n\n async select<TValue>(\n message: string,\n choices: CliChoice<TValue>[],\n defaultValue?: TValue\n ): Promise<TValue> {\n if (!this.interactive) {\n this.fail(`Cannot prompt in non-interactive mode: ${message}`);\n }\n if (choices.length === 0) {\n this.fail(`No choices are available for: ${message}`);\n }\n\n this.info(message);\n choices.forEach((choice, index) => {\n const suffix = choice.description ? ` - ${choice.description}` : \"\";\n process.stdout.write(` ${index + 1}. ${choice.label}${suffix}\\n`);\n });\n\n const defaultIndex = defaultValue\n ? Math.max(\n choices.findIndex((choice) => choice.value === defaultValue),\n 0\n )\n : 0;\n const rawValue = await this.input(`Choose 1-${choices.length}`, {\n defaultValue: String(defaultIndex + 1)\n });\n const index = Number.parseInt(rawValue, 10);\n if (Number.isNaN(index) || index < 1 || index > choices.length) {\n this.fail(`Expected a value between 1 and ${choices.length}.`);\n }\n return choices[index - 1]!.value;\n }\n\n async withSpinner<TValue>(\n label: string,\n action: () => Promise<TValue>\n ): Promise<TValue> {\n if (!this.interactive) {\n this.info(label);\n return await action();\n }\n\n const frames = [\"-\", \"\\\\\", \"|\", \"/\"];\n let index = 0;\n process.stderr.write(`${formatPrefix(process.stderr, \"work\")} ${label}`);\n const timer = setInterval(() => {\n process.stderr.write(\n `\\r${formatPrefix(process.stderr, \"work\")} ${frames[index % frames.length]} ${label}`\n );\n index += 1;\n }, 80);\n\n try {\n const result = await action();\n clearInterval(timer);\n process.stderr.write(`\\r${formatPrefix(process.stderr, \"done\")} ${label}\\n`);\n return result;\n } catch (error) {\n clearInterval(timer);\n process.stderr.write(`\\r${formatPrefix(process.stderr, \"fail\")} ${label}\\n`);\n throw error;\n }\n }\n}\n\nexport async function openTarget(target: string): Promise<boolean> {\n const command =\n process.platform === \"win32\"\n ? {\n file: \"cmd\",\n args: [\"/c\", \"start\", \"\", target]\n }\n : process.platform === \"darwin\"\n ? {\n file: \"open\",\n args: [target]\n }\n : {\n file: \"xdg-open\",\n args: [target]\n };\n\n return await new Promise((resolve) => {\n const child = spawn(command.file, command.args, {\n detached: true,\n stdio: \"ignore\"\n });\n child.once(\"error\", () => resolve(false));\n child.once(\"spawn\", () => {\n child.unref();\n resolve(true);\n });\n });\n}\n"],"mappings":"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"}
@@ -1,39 +1,72 @@
1
- import { findWorkspaceSyncoreProjects, listAvailableTargets, resolveDashboardUrl, resolveDevtoolsUrl, resolveProjectTargetDescriptor } from "./project.mjs";
2
- import { templateUsesConnectedClients } from "./messages.mjs";
1
+ import { findWorkspaceSyncoreProjects, listConnectedClientTargets, resolveDashboardUrl, resolveDevtoolsUrl, resolveProjectTargetDescriptor } from "./project.mjs";
2
+ import { templateUsesConnectedClients as templateUsesConnectedClients$1 } from "./messages.mjs";
3
+ import { readFile } from "node:fs/promises";
3
4
  import path from "node:path";
4
- import { detectProjectTemplate, fileExists, isLocalPortInUse } from "../core/cli.mjs";
5
+ import { createSchemaSnapshot, diffSchemaSnapshots } from "../core/index.mjs";
6
+ import { detectProjectTemplate, fileExists, formatError, hasSyncoreProject, isLocalPortInUse, loadProjectSchema, readStoredSnapshot, runCodegen, writeStoredSnapshot } from "../core/cli.mjs";
5
7
  //#region src/doctor.ts
8
+ const STRUCTURE_CHECKS = [
9
+ {
10
+ category: "project",
11
+ path: "syncore.config.ts"
12
+ },
13
+ {
14
+ category: "schema",
15
+ path: path.join("syncore", "schema.ts")
16
+ },
17
+ {
18
+ category: "project",
19
+ path: path.join("syncore", "components.ts"),
20
+ optional: true
21
+ },
22
+ {
23
+ category: "project",
24
+ path: path.join("syncore", "functions")
25
+ },
26
+ {
27
+ category: "generated",
28
+ path: path.join("syncore", "_generated", "api.ts")
29
+ },
30
+ {
31
+ category: "generated",
32
+ path: path.join("syncore", "_generated", "components.ts")
33
+ },
34
+ {
35
+ category: "generated",
36
+ path: path.join("syncore", "_generated", "schema.ts")
37
+ },
38
+ {
39
+ category: "generated",
40
+ path: path.join("syncore", "_generated", "functions.ts")
41
+ },
42
+ {
43
+ category: "generated",
44
+ path: path.join("syncore", "_generated", "server.ts")
45
+ },
46
+ {
47
+ category: "schema",
48
+ path: path.join("syncore", "migrations")
49
+ }
50
+ ];
6
51
  async function buildDoctorReport(cwd) {
7
52
  const template = await detectProjectTemplate(cwd);
8
- const checks = [
9
- {
10
- category: "project",
11
- path: "syncore.config.ts"
12
- },
13
- {
14
- category: "schema",
15
- path: path.join("syncore", "schema.ts")
16
- },
17
- {
18
- category: "project",
19
- path: path.join("syncore", "functions")
20
- },
21
- {
22
- category: "generated",
23
- path: path.join("syncore", "_generated", "functions.ts")
24
- },
25
- {
26
- category: "schema",
27
- path: path.join("syncore", "migrations")
28
- }
29
- ];
30
- const checkResults = await Promise.all(checks.map(async (entry) => ({
53
+ const checks = await Promise.all(STRUCTURE_CHECKS.map(async (entry) => ({
31
54
  category: entry.category,
32
55
  path: entry.path.replaceAll("\\", "/"),
33
- ok: await fileExists(path.join(cwd, entry.path))
56
+ ok: await fileExists(path.join(cwd, entry.path)) || "optional" in entry && entry.optional === true
34
57
  })));
35
- const projectTarget = await resolveProjectTargetDescriptor(cwd);
36
- const targets = await listAvailableTargets(cwd);
58
+ const hasProject = await hasSyncoreProject(cwd);
59
+ let projectTarget = null;
60
+ let persistenceDetails;
61
+ try {
62
+ projectTarget = await resolveProjectTargetDescriptor(cwd);
63
+ if (projectTarget) persistenceDetails = `Database path: ${projectTarget.databasePath}. Storage directory: ${projectTarget.storageDirectory}.`;
64
+ } catch (error) {
65
+ persistenceDetails = formatError(error);
66
+ }
67
+ const usesConnectedClients = templateUsesConnectedClients$1(template) || !projectTarget && template !== "node";
68
+ const clientTargets = await listConnectedClientTargets();
69
+ const targets = [...projectTarget ? [projectTarget] : [], ...clientTargets];
37
70
  const devtoolsUrl = resolveDevtoolsUrl();
38
71
  const dashboardUrl = resolveDashboardUrl();
39
72
  const hubPort = Number.parseInt(new URL(devtoolsUrl).port, 10);
@@ -48,25 +81,50 @@ async function buildDoctorReport(cwd) {
48
81
  dashboard: dashboardPort
49
82
  }
50
83
  };
51
- const ready = checkResults.every((entry) => entry.ok);
52
- const workspaceMatches = ready ? [] : await findWorkspaceSyncoreProjects(cwd);
53
- const clientTargets = targets.filter((target) => target.kind === "client");
54
- const status = ready ? !projectTarget && clientTargets.length === 0 ? "waiting-for-client" : "ready" : workspaceMatches.length > 0 ? "workspace-root" : checkResults.some((entry) => entry.ok) ? "incomplete" : "missing";
55
- const suggestions = [];
56
- if (status === "workspace-root") suggestions.push(`Run the command with --cwd ${workspaceMatches[0].relativePath} or change into a package directory.`);
57
- if (status === "missing") suggestions.push("Run `npx syncorejs init` to scaffold a new Syncore project.");
58
- if (status === "incomplete") {
59
- suggestions.push("Run `npx syncorejs codegen` after restoring missing files.");
60
- suggestions.push("Run `npx syncorejs migrate status` to inspect schema state.");
61
- }
62
- if (!projectTarget && templateUsesConnectedClients(template)) suggestions.push("Use `npx syncorejs dev` and a connected client target for run/data/import/export in this template.");
63
- if (status === "waiting-for-client") suggestions.push("Start your app host, then run `npx syncorejs targets` to inspect connected client targets.");
64
- if (!hub.running) suggestions.push("Run `npx syncorejs dev` to start the local devtools hub.");
84
+ const workspaceMatches = hasProject ? [] : await findWorkspaceSyncoreProjects(cwd);
85
+ const runtimeSignals = await inspectRuntimeSignals(cwd);
86
+ const drift = (await loadSchemaDrift(cwd, checks))?.drift ?? {
87
+ state: "unavailable",
88
+ currentSchemaHash: null,
89
+ storedSchemaHash: null,
90
+ statements: [],
91
+ warnings: [],
92
+ destructiveChanges: [],
93
+ details: "Syncore could not inspect schema drift yet."
94
+ };
95
+ const diagnostics = buildDiagnostics({
96
+ checks,
97
+ drift,
98
+ hasProject,
99
+ hub,
100
+ projectTarget,
101
+ runtimeSignals,
102
+ template,
103
+ usesConnectedClients,
104
+ clientTargets,
105
+ ...persistenceDetails ? { persistenceDetails } : {}
106
+ });
107
+ const primaryIssue = resolvePrimaryIssue({
108
+ checks,
109
+ diagnostics,
110
+ drift,
111
+ hasProject,
112
+ hubRunning: hub.running,
113
+ usesConnectedClients,
114
+ clientTargets,
115
+ workspaceMatches
116
+ });
117
+ const suggestions = collectSuggestions(primaryIssue, diagnostics, workspaceMatches);
65
118
  return {
66
119
  cwd,
67
120
  template,
68
- status,
69
- checks: checkResults,
121
+ status: primaryIssue.code,
122
+ primaryIssue,
123
+ diagnostics,
124
+ autoFixesAvailable: diagnostics.some((diagnostic) => diagnostic.canAutoFix && diagnostic.status !== "pass"),
125
+ drift,
126
+ runtimeSignals,
127
+ checks,
70
128
  workspaceMatches,
71
129
  suggestions,
72
130
  projectTarget,
@@ -74,7 +132,416 @@ async function buildDoctorReport(cwd) {
74
132
  hub
75
133
  };
76
134
  }
135
+ async function applyDoctorFixes(cwd, report) {
136
+ const appliedFixes = [];
137
+ const currentReport = report ?? await buildDoctorReport(cwd);
138
+ const missingGenerated = currentReport.checks.some((check) => check.category === "generated" && !check.ok);
139
+ if (missingGenerated) {
140
+ await runCodegen(cwd);
141
+ appliedFixes.push("Regenerated syncore/_generated/*.");
142
+ }
143
+ const refreshedReport = missingGenerated || !report ? await buildDoctorReport(cwd) : currentReport;
144
+ if (refreshedReport.drift.state === "missing-snapshot" || refreshedReport.drift.state === "snapshot-outdated" || refreshedReport.drift.state === "migration-pending") {
145
+ if (await fileExists(path.join(cwd, "syncore", "_generated", "schema.ts"))) {
146
+ await writeStoredSnapshot(cwd, createSchemaSnapshot(await loadProjectSchema(cwd)));
147
+ appliedFixes.push("Refreshed the stored schema snapshot.");
148
+ }
149
+ }
150
+ return appliedFixes;
151
+ }
152
+ async function inspectRuntimeSignals(cwd) {
153
+ const session = await inspectDevtoolsSession(cwd);
154
+ const logFilePath = path.join(cwd, ".syncore", "logs", "runtime.jsonl");
155
+ if (!await fileExists(logFilePath)) return {
156
+ logFilePath,
157
+ logFilePresent: false,
158
+ logEntryCount: 0,
159
+ recent: [],
160
+ sessionState: session.state,
161
+ sessionPath: session.path
162
+ };
163
+ try {
164
+ const entries = (await readFile(logFilePath, "utf8")).split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => JSON.parse(line)).filter((entry) => entry.version === 2);
165
+ return {
166
+ logFilePath,
167
+ logFilePresent: true,
168
+ logEntryCount: entries.length,
169
+ recent: entries.slice(-5).map((entry) => ({
170
+ timestamp: entry.timestamp,
171
+ category: entry.category,
172
+ message: entry.message,
173
+ targetId: entry.targetId ?? "unknown",
174
+ ...entry.runtimeLabel ? { runtimeLabel: entry.runtimeLabel } : {}
175
+ })),
176
+ sessionState: session.state,
177
+ sessionPath: session.path
178
+ };
179
+ } catch (error) {
180
+ return {
181
+ logFilePath,
182
+ logFilePresent: true,
183
+ logEntryCount: 0,
184
+ recent: [{
185
+ timestamp: Date.now(),
186
+ category: "system",
187
+ message: `Unable to read runtime log history: ${formatError(error)}`,
188
+ targetId: "system"
189
+ }],
190
+ sessionState: session.state,
191
+ sessionPath: session.path
192
+ };
193
+ }
194
+ }
195
+ async function inspectDevtoolsSession(cwd) {
196
+ const sessionPath = path.join(cwd, ".syncore", "devtools-session.json");
197
+ if (!await fileExists(sessionPath)) return {
198
+ state: "missing",
199
+ path: sessionPath
200
+ };
201
+ try {
202
+ const source = await readFile(sessionPath, "utf8");
203
+ const parsed = JSON.parse(source);
204
+ return {
205
+ state: typeof parsed.dashboardUrl === "string" && typeof parsed.authenticatedDashboardUrl === "string" && typeof parsed.devtoolsUrl === "string" && typeof parsed.token === "string" ? "present" : "invalid",
206
+ path: sessionPath
207
+ };
208
+ } catch {
209
+ return {
210
+ state: "invalid",
211
+ path: sessionPath
212
+ };
213
+ }
214
+ }
215
+ async function loadSchemaDrift(cwd, checks) {
216
+ if (!checks.some((check) => check.path === "syncore/_generated/schema.ts" && check.ok)) return null;
217
+ try {
218
+ const currentSnapshot = createSchemaSnapshot(await loadProjectSchema(cwd));
219
+ const storedSnapshot = await readStoredSnapshot(cwd);
220
+ const plan = diffSchemaSnapshots(storedSnapshot, currentSnapshot);
221
+ const state = plan.destructiveChanges.length > 0 ? "destructive" : !storedSnapshot ? "missing-snapshot" : storedSnapshot.hash !== currentSnapshot.hash && plan.statements.length > 0 ? "migration-pending" : storedSnapshot.hash !== currentSnapshot.hash ? "snapshot-outdated" : "clean";
222
+ return {
223
+ currentSnapshot,
224
+ storedSnapshot,
225
+ drift: {
226
+ state,
227
+ currentSchemaHash: currentSnapshot.hash,
228
+ storedSchemaHash: storedSnapshot?.hash ?? null,
229
+ statements: plan.statements,
230
+ warnings: plan.warnings,
231
+ destructiveChanges: plan.destructiveChanges,
232
+ details: state === "clean" ? "Local schema snapshot matches the generated Syncore schema." : describeDriftState(state, plan.statements.length, plan.warnings.length)
233
+ }
234
+ };
235
+ } catch (error) {
236
+ return {
237
+ currentSnapshot: null,
238
+ storedSnapshot: null,
239
+ drift: {
240
+ state: "unavailable",
241
+ currentSchemaHash: null,
242
+ storedSchemaHash: null,
243
+ statements: [],
244
+ warnings: [],
245
+ destructiveChanges: [],
246
+ details: `Syncore could not load the generated schema: ${formatError(error)}`
247
+ }
248
+ };
249
+ }
250
+ }
251
+ function describeDriftState(state, statementCount, warningCount) {
252
+ if (state === "missing-snapshot") return "No stored schema snapshot was found yet.";
253
+ if (state === "migration-pending") return `Schema drift detected with ${statementCount} SQL statement(s) pending and ${warningCount} warning(s).`;
254
+ if (state === "snapshot-outdated") return "The stored schema snapshot differs from the generated schema, but no SQL statements are pending.";
255
+ if (state === "destructive") return "The current schema diff includes destructive changes that require manual review.";
256
+ return "Syncore could not inspect schema drift.";
257
+ }
258
+ function buildDiagnostics(input) {
259
+ const diagnostics = [];
260
+ const missingProjectPaths = input.checks.filter((check) => (check.category === "project" || check.category === "schema") && !check.ok);
261
+ diagnostics.push(missingProjectPaths.length === 0 && input.hasProject ? {
262
+ id: "project.structure",
263
+ category: "project",
264
+ severity: "info",
265
+ status: "pass",
266
+ summary: "Syncore project structure is present.",
267
+ details: "Core project files are available in this directory.",
268
+ canAutoFix: false
269
+ } : {
270
+ id: "project.structure",
271
+ category: "project",
272
+ severity: "error",
273
+ status: "fail",
274
+ summary: "Syncore project structure is incomplete or missing.",
275
+ details: missingProjectPaths.length > 0 ? `Missing: ${missingProjectPaths.map((check) => check.path).join(", ")}.` : "This directory does not contain a full Syncore project yet.",
276
+ suggestedAction: "Run `npx syncorejs init` or restore the missing project files.",
277
+ canAutoFix: false
278
+ });
279
+ const missingGenerated = input.checks.filter((check) => check.category === "generated" && !check.ok);
280
+ diagnostics.push(missingGenerated.length === 0 ? {
281
+ id: "generated.files",
282
+ category: "generated",
283
+ severity: "info",
284
+ status: "pass",
285
+ summary: "Generated Syncore files are present.",
286
+ details: "syncore/_generated looks available for runtime and type-driven tooling.",
287
+ canAutoFix: false
288
+ } : {
289
+ id: "generated.files",
290
+ category: "generated",
291
+ severity: "error",
292
+ status: "fail",
293
+ summary: "Generated Syncore files are missing.",
294
+ details: `Missing: ${missingGenerated.map((check) => check.path).join(", ")}.`,
295
+ suggestedAction: "Run `npx syncorejs codegen` or `npx syncorejs doctor --fix`.",
296
+ canAutoFix: true,
297
+ fixCommand: "npx syncorejs doctor --fix"
298
+ });
299
+ diagnostics.push(buildSchemaDiagnostic(input.drift));
300
+ diagnostics.push(input.projectTarget ? {
301
+ id: "persistence.project-target",
302
+ category: "persistence",
303
+ severity: "info",
304
+ status: "pass",
305
+ summary: "Project target persistence is configured.",
306
+ ...input.persistenceDetails ? { details: input.persistenceDetails } : {},
307
+ canAutoFix: false
308
+ } : input.usesConnectedClients ? {
309
+ id: "persistence.project-target",
310
+ category: "persistence",
311
+ severity: "info",
312
+ status: "pass",
313
+ summary: "Persistence is client-managed for this template.",
314
+ details: "This app template expects a connected client runtime to provide local storage and runtime state.",
315
+ canAutoFix: false
316
+ } : {
317
+ id: "persistence.project-target",
318
+ category: "persistence",
319
+ severity: "error",
320
+ status: "fail",
321
+ summary: "No local project target is configured.",
322
+ details: input.persistenceDetails ?? "Syncore could not resolve a project target database and storage directory.",
323
+ suggestedAction: "Check syncore.config.ts and make sure projectTarget.databasePath and projectTarget.storageDirectory are valid.",
324
+ canAutoFix: false
325
+ });
326
+ diagnostics.push(input.hub.running ? {
327
+ id: "hub.devtools",
328
+ category: "hub",
329
+ severity: "info",
330
+ status: "pass",
331
+ summary: "Local devtools hub is running.",
332
+ details: `Devtools: ${input.hub.url}. Dashboard: ${input.hub.dashboardUrl}.`,
333
+ canAutoFix: false
334
+ } : {
335
+ id: "hub.devtools",
336
+ category: "hub",
337
+ severity: "warning",
338
+ status: "warn",
339
+ summary: "Local devtools hub is not running.",
340
+ details: `Expected devtools endpoint: ${input.hub.url}.`,
341
+ suggestedAction: "Run `npx syncorejs dev` to start the local hub and dashboard.",
342
+ canAutoFix: false
343
+ });
344
+ diagnostics.push(input.hub.dashboardRunning ? {
345
+ id: "hub.dashboard",
346
+ category: "hub",
347
+ severity: "info",
348
+ status: "pass",
349
+ summary: "Dashboard shell is responding.",
350
+ details: input.hub.dashboardUrl,
351
+ canAutoFix: false
352
+ } : {
353
+ id: "hub.dashboard",
354
+ category: "hub",
355
+ severity: "warning",
356
+ status: "warn",
357
+ summary: "Dashboard shell is not responding yet.",
358
+ details: `Expected dashboard URL: ${input.hub.dashboardUrl}.`,
359
+ suggestedAction: "Run `npx syncorejs dev` or inspect whether the dashboard port is already taken.",
360
+ canAutoFix: false
361
+ });
362
+ diagnostics.push(input.usesConnectedClients ? input.clientTargets.length > 0 ? {
363
+ id: "client.runtime",
364
+ category: "client",
365
+ severity: "info",
366
+ status: "pass",
367
+ summary: "At least one client runtime is connected.",
368
+ details: `Connected targets: ${input.clientTargets.map((target) => target.id).join(", ")}.`,
369
+ canAutoFix: false
370
+ } : {
371
+ id: "client.runtime",
372
+ category: "client",
373
+ severity: "warning",
374
+ status: "warn",
375
+ summary: "This template is waiting for a connected client runtime.",
376
+ details: "Syncore is ready to inspect or operate on your app once a browser tab, worker, Electron shell, or Expo runtime connects.",
377
+ suggestedAction: "Start your app host, then run `npx syncorejs targets` to inspect connected client targets.",
378
+ canAutoFix: false
379
+ } : {
380
+ id: "client.runtime",
381
+ category: "client",
382
+ severity: "info",
383
+ status: "pass",
384
+ summary: "This template does not require a connected client runtime.",
385
+ canAutoFix: false
386
+ });
387
+ diagnostics.push(buildRuntimeDiagnostic(input.runtimeSignals));
388
+ return diagnostics;
389
+ }
390
+ function buildSchemaDiagnostic(drift) {
391
+ if (drift.state === "clean") return {
392
+ id: "schema.drift",
393
+ category: "schema",
394
+ severity: "info",
395
+ status: "pass",
396
+ summary: "Schema snapshot is in sync.",
397
+ ...drift.details ? { details: drift.details } : {},
398
+ canAutoFix: false
399
+ };
400
+ if (drift.state === "destructive") return {
401
+ id: "schema.drift",
402
+ category: "schema",
403
+ severity: "error",
404
+ status: "fail",
405
+ summary: "Schema drift includes destructive changes.",
406
+ details: drift.destructiveChanges.join("; "),
407
+ suggestedAction: "Review the schema change manually and create or edit a migration before continuing.",
408
+ canAutoFix: false
409
+ };
410
+ if (drift.state === "missing-snapshot") return {
411
+ id: "schema.drift",
412
+ category: "schema",
413
+ severity: "warning",
414
+ status: "warn",
415
+ summary: "No stored schema snapshot was found.",
416
+ ...drift.details ? { details: drift.details } : {},
417
+ suggestedAction: "Run `npx syncorejs doctor --fix` to refresh the snapshot, or generate a migration if you intend to persist the change.",
418
+ canAutoFix: true,
419
+ fixCommand: "npx syncorejs doctor --fix"
420
+ };
421
+ if (drift.state === "snapshot-outdated" || drift.state === "migration-pending") return {
422
+ id: "schema.drift",
423
+ category: "schema",
424
+ severity: "warning",
425
+ status: "warn",
426
+ summary: drift.state === "migration-pending" ? "Schema drift has pending migration statements." : "Stored schema snapshot differs from the generated schema.",
427
+ ...drift.details ? { details: drift.details } : {},
428
+ suggestedAction: drift.state === "migration-pending" ? "Run `npx syncorejs migrate status` to inspect the diff, then `npx syncorejs doctor --fix` only if you just want to refresh the stored snapshot." : "Run `npx syncorejs doctor --fix` to refresh the stored snapshot safely.",
429
+ canAutoFix: true,
430
+ fixCommand: "npx syncorejs doctor --fix"
431
+ };
432
+ return {
433
+ id: "schema.drift",
434
+ category: "schema",
435
+ severity: "warning",
436
+ status: "warn",
437
+ summary: "Schema drift could not be inspected yet.",
438
+ ...drift.details ? { details: drift.details } : {},
439
+ suggestedAction: "Restore generated files or rerun `npx syncorejs codegen`, then run `npx syncorejs doctor` again.",
440
+ canAutoFix: false
441
+ };
442
+ }
443
+ function buildRuntimeDiagnostic(runtimeSignals) {
444
+ if (runtimeSignals.sessionState === "invalid") return {
445
+ id: "runtime.session",
446
+ category: "runtime",
447
+ severity: "warning",
448
+ status: "warn",
449
+ summary: "The saved devtools session file is invalid.",
450
+ details: runtimeSignals.sessionPath,
451
+ suggestedAction: "Restart `npx syncorejs dev` to regenerate the session state for the local hub.",
452
+ canAutoFix: false
453
+ };
454
+ if (runtimeSignals.logEntryCount === 0) return {
455
+ id: "runtime.signals",
456
+ category: "runtime",
457
+ severity: "info",
458
+ status: "pass",
459
+ summary: "No recent runtime signals were recorded yet.",
460
+ details: runtimeSignals.logFilePresent ? "The runtime log file exists but has no recorded entries yet." : "No runtime log file has been created yet.",
461
+ canAutoFix: false
462
+ };
463
+ const latest = runtimeSignals.recent[runtimeSignals.recent.length - 1];
464
+ return {
465
+ id: "runtime.signals",
466
+ category: "runtime",
467
+ severity: "info",
468
+ status: "pass",
469
+ summary: "Recent runtime signals are available.",
470
+ ...latest ? { details: `Latest: ${latest.category} on ${latest.targetId} at ${new Date(latest.timestamp).toISOString()}: ${latest.message}` } : {},
471
+ canAutoFix: false
472
+ };
473
+ }
474
+ function resolvePrimaryIssue(input) {
475
+ if (!input.hasProject && input.workspaceMatches.length > 0) return {
476
+ code: "workspace-root",
477
+ summary: "You are at a workspace root, not inside a Syncore app package.",
478
+ details: `Found ${input.workspaceMatches.length} Syncore package(s) under this workspace.`,
479
+ impact: "Project-specific diagnostics and runtime operations are ambiguous from the workspace root.",
480
+ suggestedAction: `Run the command with --cwd ${input.workspaceMatches[0].relativePath} or change into that package directory.`
481
+ };
482
+ const missingProjectPaths = input.checks.filter((check) => (check.category === "project" || check.category === "schema") && !check.ok);
483
+ if (!input.hasProject || missingProjectPaths.length > 0) return {
484
+ code: "missing-project",
485
+ summary: "Syncore project files are missing or incomplete.",
486
+ details: missingProjectPaths.length > 0 ? `Missing: ${missingProjectPaths.map((check) => check.path).join(", ")}.` : "This directory does not contain a complete Syncore project yet.",
487
+ impact: "The CLI cannot bootstrap the local runtime or inspect schema and persistence reliably.",
488
+ suggestedAction: "Run `npx syncorejs init` or restore the missing project files."
489
+ };
490
+ const missingGenerated = input.checks.filter((check) => check.category === "generated" && !check.ok);
491
+ if (missingGenerated.length > 0) return {
492
+ code: "missing-generated",
493
+ summary: "Generated Syncore files are missing.",
494
+ details: `Missing: ${missingGenerated.map((check) => check.path).join(", ")}.`,
495
+ impact: "Type-driven runtime loading and schema inspection may be stale or unavailable.",
496
+ suggestedAction: "Run `npx syncorejs doctor --fix` or `npx syncorejs codegen`."
497
+ };
498
+ if (input.drift.state === "destructive") return {
499
+ code: "schema-destructive-drift",
500
+ summary: "Schema drift is blocked by destructive changes.",
501
+ details: input.drift.destructiveChanges.join("; "),
502
+ impact: "Syncore cannot safely advance the local schema automatically.",
503
+ suggestedAction: "Review the schema change manually and generate a migration before continuing."
504
+ };
505
+ if (input.usesConnectedClients && input.clientTargets.length === 0) return {
506
+ code: "waiting-for-client",
507
+ summary: "This app is waiting for a connected local runtime.",
508
+ details: input.hubRunning ? "Client-managed templates only become fully operational after the app host connects to the local Syncore hub." : "This template depends on a client-managed runtime, and no app runtime is connected yet.",
509
+ impact: "Worker, bridge IPC, storage, and client-side data inspection stay unavailable until a runtime connects.",
510
+ suggestedAction: input.hubRunning ? "Start your app host, then run `npx syncorejs targets` to inspect connected runtimes." : "Run `npx syncorejs dev`, start your app host, then run `npx syncorejs targets` to inspect connected runtimes."
511
+ };
512
+ if (input.drift.state === "missing-snapshot" || input.drift.state === "snapshot-outdated" || input.drift.state === "migration-pending") return {
513
+ code: "schema-drift",
514
+ summary: "The local schema snapshot is out of sync.",
515
+ details: input.drift.details ?? "Schema drift was detected.",
516
+ impact: "The local dev loop can become confusing because the stored snapshot no longer matches the generated schema.",
517
+ suggestedAction: input.drift.state === "migration-pending" ? "Run `npx syncorejs migrate status` to inspect the diff, then use `npx syncorejs doctor --fix` if you only need to refresh the stored snapshot." : "Run `npx syncorejs doctor --fix` to refresh the stored snapshot safely."
518
+ };
519
+ if (!input.hubRunning) return {
520
+ code: "hub-down",
521
+ summary: "The local devtools hub is not running.",
522
+ details: "Syncore can inspect project state, but runtime and client diagnostics are limited until the hub starts.",
523
+ impact: "Commands that depend on live targets, logs, or IPC visibility will have reduced signal.",
524
+ suggestedAction: "Run `npx syncorejs dev` to start the local hub."
525
+ };
526
+ return {
527
+ code: "ready",
528
+ summary: "Syncore is ready for the local development loop.",
529
+ details: "Project structure, generated files, schema state, and runtime prerequisites look healthy.",
530
+ impact: "You can use `syncorejs dev`, inspect targets, and operate on the local runtime.",
531
+ suggestedAction: "Run `npx syncorejs dev` to keep codegen, schema, and the local hub in sync."
532
+ };
533
+ }
534
+ function collectSuggestions(primaryIssue, diagnostics, workspaceMatches) {
535
+ const suggestions = /* @__PURE__ */ new Set();
536
+ if (primaryIssue.suggestedAction) suggestions.add(primaryIssue.suggestedAction);
537
+ if (primaryIssue.code === "workspace-root" && workspaceMatches.length > 0) suggestions.add(`Run the command with --cwd ${workspaceMatches[0].relativePath} or change into a package directory.`);
538
+ for (const diagnostic of diagnostics) {
539
+ if (diagnostic.status === "pass") continue;
540
+ if (diagnostic.suggestedAction) suggestions.add(diagnostic.suggestedAction);
541
+ }
542
+ return [...suggestions];
543
+ }
77
544
  //#endregion
78
- export { buildDoctorReport };
545
+ export { applyDoctorFixes, buildDoctorReport };
79
546
 
80
547
  //# sourceMappingURL=doctor.mjs.map