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.
- package/README.md +2 -1
- package/dist/_vendor/cli/app.d.mts.map +1 -1
- package/dist/_vendor/cli/app.mjs +323 -42
- package/dist/_vendor/cli/app.mjs.map +1 -1
- package/dist/_vendor/cli/context.mjs +27 -9
- package/dist/_vendor/cli/context.mjs.map +1 -1
- package/dist/_vendor/cli/doctor.mjs +513 -46
- package/dist/_vendor/cli/doctor.mjs.map +1 -1
- package/dist/_vendor/cli/messages.mjs +5 -4
- package/dist/_vendor/cli/messages.mjs.map +1 -1
- package/dist/_vendor/cli/project.mjs +110 -12
- package/dist/_vendor/cli/project.mjs.map +1 -1
- package/dist/_vendor/cli/render.mjs +57 -9
- package/dist/_vendor/cli/render.mjs.map +1 -1
- package/dist/_vendor/cli/targets.mjs +4 -3
- package/dist/_vendor/cli/targets.mjs.map +1 -1
- package/dist/_vendor/core/cli.d.mts +13 -3
- package/dist/_vendor/core/cli.d.mts.map +1 -1
- package/dist/_vendor/core/cli.mjs +242 -91
- package/dist/_vendor/core/cli.mjs.map +1 -1
- package/dist/_vendor/core/devtools-auth.mjs +60 -0
- package/dist/_vendor/core/devtools-auth.mjs.map +1 -0
- package/dist/_vendor/core/index.d.mts +5 -3
- package/dist/_vendor/core/index.mjs +22 -2
- package/dist/_vendor/core/index.mjs.map +1 -1
- package/dist/_vendor/core/runtime/components.d.mts +111 -0
- package/dist/_vendor/core/runtime/components.d.mts.map +1 -0
- package/dist/_vendor/core/runtime/components.mjs +186 -0
- package/dist/_vendor/core/runtime/components.mjs.map +1 -0
- package/dist/_vendor/core/runtime/devtools.d.mts +4 -4
- package/dist/_vendor/core/runtime/devtools.d.mts.map +1 -1
- package/dist/_vendor/core/runtime/devtools.mjs +52 -41
- package/dist/_vendor/core/runtime/devtools.mjs.map +1 -1
- package/dist/_vendor/core/runtime/functions.d.mts +10 -10
- package/dist/_vendor/core/runtime/functions.d.mts.map +1 -1
- package/dist/_vendor/core/runtime/functions.mjs +2 -2
- package/dist/_vendor/core/runtime/functions.mjs.map +1 -1
- package/dist/_vendor/core/runtime/internal/engines/devtoolsEngine.mjs +77 -0
- package/dist/_vendor/core/runtime/internal/engines/devtoolsEngine.mjs.map +1 -0
- package/dist/_vendor/core/runtime/internal/engines/executionEngine.mjs +617 -0
- package/dist/_vendor/core/runtime/internal/engines/executionEngine.mjs.map +1 -0
- package/dist/_vendor/core/runtime/internal/engines/reactivityEngine.mjs +186 -0
- package/dist/_vendor/core/runtime/internal/engines/reactivityEngine.mjs.map +1 -0
- package/dist/_vendor/core/runtime/internal/engines/schedulerEngine.mjs +220 -0
- package/dist/_vendor/core/runtime/internal/engines/schedulerEngine.mjs.map +1 -0
- package/dist/_vendor/core/runtime/internal/engines/schemaEngine.mjs +203 -0
- package/dist/_vendor/core/runtime/internal/engines/schemaEngine.mjs.map +1 -0
- package/dist/_vendor/core/runtime/internal/engines/shared.mjs +177 -0
- package/dist/_vendor/core/runtime/internal/engines/shared.mjs.map +1 -0
- package/dist/_vendor/core/runtime/internal/engines/storageEngine.mjs +144 -0
- package/dist/_vendor/core/runtime/internal/engines/storageEngine.mjs.map +1 -0
- package/dist/_vendor/core/runtime/internal/runtimeKernel.mjs +220 -0
- package/dist/_vendor/core/runtime/internal/runtimeKernel.mjs.map +1 -0
- package/dist/_vendor/core/runtime/internal/runtimeStatus.mjs +32 -0
- package/dist/_vendor/core/runtime/internal/runtimeStatus.mjs.map +1 -0
- package/dist/_vendor/core/runtime/internal/systemMeta.mjs +61 -0
- package/dist/_vendor/core/runtime/internal/systemMeta.mjs.map +1 -0
- package/dist/_vendor/core/runtime/internal/transactionCoordinator.mjs +37 -0
- package/dist/_vendor/core/runtime/internal/transactionCoordinator.mjs.map +1 -0
- package/dist/_vendor/core/runtime/runtime.d.mts +159 -205
- package/dist/_vendor/core/runtime/runtime.d.mts.map +1 -1
- package/dist/_vendor/core/runtime/runtime.mjs +16 -1371
- package/dist/_vendor/core/runtime/runtime.mjs.map +1 -1
- package/dist/_vendor/core/transport.d.mts +111 -0
- package/dist/_vendor/core/transport.d.mts.map +1 -0
- package/dist/_vendor/core/transport.mjs +419 -0
- package/dist/_vendor/core/transport.mjs.map +1 -0
- package/dist/_vendor/devtools-protocol/index.d.ts +39 -1
- package/dist/_vendor/devtools-protocol/index.d.ts.map +1 -1
- package/dist/_vendor/devtools-protocol/index.js +25 -9
- package/dist/_vendor/devtools-protocol/index.js.map +1 -1
- package/dist/_vendor/next/index.d.ts +1 -1
- package/dist/_vendor/next/index.d.ts.map +1 -1
- package/dist/_vendor/next/index.js +31 -13
- package/dist/_vendor/next/index.js.map +1 -1
- package/dist/_vendor/platform-expo/index.d.ts +12 -12
- package/dist/_vendor/platform-expo/index.d.ts.map +1 -1
- package/dist/_vendor/platform-expo/index.js +4 -2
- package/dist/_vendor/platform-expo/index.js.map +1 -1
- package/dist/_vendor/platform-expo/react.d.ts.map +1 -1
- package/dist/_vendor/platform-expo/react.js +11 -10
- package/dist/_vendor/platform-expo/react.js.map +1 -1
- package/dist/_vendor/platform-node/index.d.mts +23 -19
- package/dist/_vendor/platform-node/index.d.mts.map +1 -1
- package/dist/_vendor/platform-node/index.mjs +13 -5
- package/dist/_vendor/platform-node/index.mjs.map +1 -1
- package/dist/_vendor/platform-node/ipc-react.d.mts.map +1 -1
- package/dist/_vendor/platform-node/ipc-react.mjs +15 -2
- package/dist/_vendor/platform-node/ipc-react.mjs.map +1 -1
- package/dist/_vendor/platform-node/ipc.d.mts +11 -35
- package/dist/_vendor/platform-node/ipc.d.mts.map +1 -1
- package/dist/_vendor/platform-node/ipc.mjs +3 -273
- package/dist/_vendor/platform-node/ipc.mjs.map +1 -1
- package/dist/_vendor/platform-web/external-change.d.ts +2 -1
- package/dist/_vendor/platform-web/external-change.d.ts.map +1 -1
- package/dist/_vendor/platform-web/external-change.js +2 -1
- package/dist/_vendor/platform-web/external-change.js.map +1 -1
- package/dist/_vendor/platform-web/index.d.ts +21 -21
- package/dist/_vendor/platform-web/index.d.ts.map +1 -1
- package/dist/_vendor/platform-web/index.js +44 -7
- package/dist/_vendor/platform-web/index.js.map +1 -1
- package/dist/_vendor/platform-web/react.d.ts.map +1 -1
- package/dist/_vendor/platform-web/react.js +29 -13
- package/dist/_vendor/platform-web/react.js.map +1 -1
- package/dist/_vendor/platform-web/worker.d.ts +11 -35
- package/dist/_vendor/platform-web/worker.d.ts.map +1 -1
- package/dist/_vendor/platform-web/worker.js +3 -267
- package/dist/_vendor/platform-web/worker.js.map +1 -1
- package/dist/_vendor/react/index.d.ts +36 -20
- package/dist/_vendor/react/index.d.ts.map +1 -1
- package/dist/_vendor/react/index.js +279 -57
- package/dist/_vendor/react/index.js.map +1 -1
- package/dist/_vendor/schema/definition.d.ts +48 -63
- package/dist/_vendor/schema/definition.d.ts.map +1 -1
- package/dist/_vendor/schema/definition.js +22 -39
- package/dist/_vendor/schema/definition.js.map +1 -1
- package/dist/_vendor/schema/index.d.ts +4 -4
- package/dist/_vendor/schema/index.js +2 -2
- package/dist/_vendor/schema/planner.d.ts +19 -2
- package/dist/_vendor/schema/planner.d.ts.map +1 -1
- package/dist/_vendor/schema/planner.js +79 -3
- package/dist/_vendor/schema/planner.js.map +1 -1
- package/dist/_vendor/schema/validators.d.ts +141 -121
- package/dist/_vendor/schema/validators.d.ts.map +1 -1
- package/dist/_vendor/schema/validators.js +300 -42
- package/dist/_vendor/schema/validators.js.map +1 -1
- package/dist/_vendor/svelte/index.d.ts +47 -19
- package/dist/_vendor/svelte/index.d.ts.map +1 -1
- package/dist/_vendor/svelte/index.js +250 -20
- package/dist/_vendor/svelte/index.js.map +1 -1
- package/dist/components.d.ts +2 -0
- package/dist/components.js +2 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- 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,
|
|
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 {
|
|
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
|
|
36
|
-
|
|
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
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
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
|