syncorejs 0.1.0 → 0.2.0
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/dist/{core/src/cli.d.ts → _vendor/cli/app.d.mts} +4 -2
- package/dist/_vendor/cli/app.d.mts.map +1 -0
- package/dist/_vendor/cli/app.mjs +997 -0
- package/dist/_vendor/cli/app.mjs.map +1 -0
- package/dist/_vendor/cli/context.mjs +180 -0
- package/dist/_vendor/cli/context.mjs.map +1 -0
- package/dist/_vendor/cli/dev-session.mjs +49 -0
- package/dist/_vendor/cli/dev-session.mjs.map +1 -0
- package/dist/_vendor/cli/doctor.mjs +80 -0
- package/dist/_vendor/cli/doctor.mjs.map +1 -0
- package/dist/_vendor/cli/errors.mjs +22 -0
- package/dist/_vendor/cli/errors.mjs.map +1 -0
- package/dist/_vendor/cli/help.mjs +26 -0
- package/dist/_vendor/cli/help.mjs.map +1 -0
- package/dist/_vendor/cli/index.d.mts +2 -0
- package/dist/_vendor/cli/index.mjs +23 -0
- package/dist/_vendor/cli/index.mjs.map +1 -0
- package/dist/_vendor/cli/messages.mjs +32 -0
- package/dist/_vendor/cli/messages.mjs.map +1 -0
- package/dist/_vendor/cli/preflight.mjs +35 -0
- package/dist/_vendor/cli/preflight.mjs.map +1 -0
- package/dist/_vendor/cli/project.mjs +583 -0
- package/dist/_vendor/cli/project.mjs.map +1 -0
- package/dist/_vendor/cli/render.mjs +133 -0
- package/dist/_vendor/cli/render.mjs.map +1 -0
- package/dist/_vendor/cli/targets.mjs +87 -0
- package/dist/_vendor/cli/targets.mjs.map +1 -0
- package/dist/_vendor/core/cli.d.mts +59 -1
- package/dist/_vendor/core/cli.d.mts.map +1 -1
- package/dist/_vendor/core/cli.mjs +528 -75
- package/dist/_vendor/core/cli.mjs.map +1 -1
- package/dist/_vendor/core/index.d.mts +12 -4
- package/dist/_vendor/core/index.d.mts.map +1 -0
- package/dist/_vendor/core/index.mjs +4 -3
- package/dist/_vendor/core/index.mjs.map +1 -1
- package/dist/_vendor/core/runtime/devtools.d.mts +32 -6
- package/dist/_vendor/core/runtime/devtools.d.mts.map +1 -1
- package/dist/_vendor/core/runtime/devtools.mjs +397 -182
- package/dist/_vendor/core/runtime/devtools.mjs.map +1 -1
- package/dist/_vendor/core/runtime/functions.mjs.map +1 -1
- package/dist/_vendor/core/runtime/runtime.d.mts +89 -7
- package/dist/_vendor/core/runtime/runtime.d.mts.map +1 -1
- package/dist/_vendor/core/runtime/runtime.mjs +303 -32
- package/dist/_vendor/core/runtime/runtime.mjs.map +1 -1
- package/dist/_vendor/devtools-protocol/index.d.ts +189 -82
- package/dist/_vendor/devtools-protocol/index.d.ts.map +1 -1
- package/dist/_vendor/devtools-protocol/index.js +39 -0
- package/dist/_vendor/devtools-protocol/index.js.map +1 -0
- package/dist/_vendor/next/config.d.ts.map +1 -1
- package/dist/_vendor/next/config.js +2 -5
- package/dist/_vendor/next/config.js.map +1 -1
- package/dist/_vendor/platform-expo/index.d.ts +15 -5
- package/dist/_vendor/platform-expo/index.d.ts.map +1 -1
- package/dist/_vendor/platform-expo/index.js +33 -3
- package/dist/_vendor/platform-expo/index.js.map +1 -1
- package/dist/_vendor/platform-expo/react.js.map +1 -1
- package/dist/_vendor/platform-node/index.d.mts +10 -5
- package/dist/_vendor/platform-node/index.d.mts.map +1 -1
- package/dist/_vendor/platform-node/index.mjs +145 -35
- package/dist/_vendor/platform-node/index.mjs.map +1 -1
- package/dist/_vendor/platform-node/ipc-react.mjs.map +1 -1
- package/dist/_vendor/platform-web/external-change.d.ts +39 -0
- package/dist/_vendor/platform-web/external-change.d.ts.map +1 -0
- package/dist/_vendor/platform-web/external-change.js +61 -0
- package/dist/_vendor/platform-web/external-change.js.map +1 -0
- package/dist/_vendor/platform-web/index.d.ts +27 -5
- package/dist/_vendor/platform-web/index.d.ts.map +1 -1
- package/dist/_vendor/platform-web/index.js +310 -44
- package/dist/_vendor/platform-web/index.js.map +1 -1
- package/dist/_vendor/platform-web/indexeddb.js.map +1 -1
- package/dist/_vendor/platform-web/opfs.js.map +1 -1
- package/dist/_vendor/platform-web/persistence.js.map +1 -1
- package/dist/_vendor/platform-web/react.js.map +1 -1
- package/dist/_vendor/platform-web/sqljs.js +22 -2
- package/dist/_vendor/platform-web/sqljs.js.map +1 -1
- package/dist/_vendor/schema/definition.js.map +1 -1
- package/dist/_vendor/schema/planner.js.map +1 -1
- package/dist/_vendor/schema/validators.js.map +1 -1
- package/dist/browser-react.d.ts +1 -1
- package/dist/browser-react.js +1 -1
- package/dist/browser.d.ts +6 -7
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +4 -5
- package/dist/browser.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +12 -3
- package/dist/cli.js.map +1 -1
- package/dist/expo-react.d.ts +1 -1
- package/dist/expo-react.js +1 -1
- package/dist/expo.d.ts +1 -2
- package/dist/expo.js +1 -2
- package/dist/index.d.ts +3 -7
- package/dist/index.js +3 -8
- package/dist/next-config.d.ts +1 -2
- package/dist/next-config.js +1 -2
- package/dist/next.d.ts +1 -3
- package/dist/next.js +1 -3
- package/dist/node-ipc-react.d.ts +1 -1
- package/dist/node-ipc-react.js +1 -1
- package/dist/node-ipc.d.ts +1 -2
- package/dist/node-ipc.js +1 -2
- package/dist/node.d.ts +1 -4
- package/dist/node.js +1 -3
- package/dist/react.d.ts +1 -2
- package/dist/react.js +1 -2
- package/dist/svelte.d.ts +1 -2
- package/dist/svelte.js +1 -2
- package/package.json +6 -3
- package/dist/core/src/cli.d.ts.map +0 -1
- package/dist/core/src/cli.js +0 -1196
- package/dist/core/src/cli.js.map +0 -1
- package/dist/core/src/index.js +0 -7
- package/dist/core/src/runtime/devtools.d.ts +0 -7
- package/dist/core/src/runtime/devtools.d.ts.map +0 -1
- package/dist/core/src/runtime/devtools.js +0 -300
- package/dist/core/src/runtime/devtools.js.map +0 -1
- package/dist/core/src/runtime/functions.d.ts +0 -123
- package/dist/core/src/runtime/functions.d.ts.map +0 -1
- package/dist/core/src/runtime/functions.js +0 -71
- package/dist/core/src/runtime/functions.js.map +0 -1
- package/dist/core/src/runtime/id.d.ts +0 -13
- package/dist/core/src/runtime/id.d.ts.map +0 -1
- package/dist/core/src/runtime/id.js +0 -28
- package/dist/core/src/runtime/id.js.map +0 -1
- package/dist/core/src/runtime/runtime.d.ts +0 -371
- package/dist/core/src/runtime/runtime.d.ts.map +0 -1
- package/dist/core/src/runtime/runtime.js +0 -1143
- package/dist/core/src/runtime/runtime.js.map +0 -1
- package/dist/devtools-protocol/src/index.d.ts +0 -201
- package/dist/devtools-protocol/src/index.d.ts.map +0 -1
- package/dist/next/src/config.d.ts +0 -17
- package/dist/next/src/config.d.ts.map +0 -1
- package/dist/next/src/config.js +0 -73
- package/dist/next/src/config.js.map +0 -1
- package/dist/next/src/index.d.ts +0 -80
- package/dist/next/src/index.d.ts.map +0 -1
- package/dist/next/src/index.js +0 -82
- package/dist/next/src/index.js.map +0 -1
- package/dist/platform-expo/src/index.d.ts +0 -96
- package/dist/platform-expo/src/index.d.ts.map +0 -1
- package/dist/platform-expo/src/index.js +0 -198
- package/dist/platform-expo/src/index.js.map +0 -1
- package/dist/platform-expo/src/react.d.ts +0 -26
- package/dist/platform-expo/src/react.d.ts.map +0 -1
- package/dist/platform-expo/src/react.js +0 -30
- package/dist/platform-expo/src/react.js.map +0 -1
- package/dist/platform-node/src/index.d.ts +0 -145
- package/dist/platform-node/src/index.d.ts.map +0 -1
- package/dist/platform-node/src/index.js +0 -407
- package/dist/platform-node/src/index.js.map +0 -1
- package/dist/platform-node/src/ipc-react.d.ts +0 -25
- package/dist/platform-node/src/ipc-react.d.ts.map +0 -1
- package/dist/platform-node/src/ipc-react.js +0 -21
- package/dist/platform-node/src/ipc-react.js.map +0 -1
- package/dist/platform-node/src/ipc.d.ts +0 -76
- package/dist/platform-node/src/ipc.d.ts.map +0 -1
- package/dist/platform-node/src/ipc.js +0 -344
- package/dist/platform-node/src/ipc.js.map +0 -1
- package/dist/platform-web/src/index.d.ts +0 -106
- package/dist/platform-web/src/index.d.ts.map +0 -1
- package/dist/platform-web/src/index.js +0 -311
- package/dist/platform-web/src/index.js.map +0 -1
- package/dist/platform-web/src/indexeddb.js +0 -125
- package/dist/platform-web/src/indexeddb.js.map +0 -1
- package/dist/platform-web/src/opfs.js +0 -146
- package/dist/platform-web/src/opfs.js.map +0 -1
- package/dist/platform-web/src/persistence.d.ts +0 -20
- package/dist/platform-web/src/persistence.d.ts.map +0 -1
- package/dist/platform-web/src/persistence.js +0 -23
- package/dist/platform-web/src/persistence.js.map +0 -1
- package/dist/platform-web/src/react.d.ts +0 -35
- package/dist/platform-web/src/react.d.ts.map +0 -1
- package/dist/platform-web/src/react.js +0 -42
- package/dist/platform-web/src/react.js.map +0 -1
- package/dist/platform-web/src/sqljs.js +0 -133
- package/dist/platform-web/src/sqljs.js.map +0 -1
- package/dist/platform-web/src/worker.d.ts +0 -79
- package/dist/platform-web/src/worker.d.ts.map +0 -1
- package/dist/platform-web/src/worker.js +0 -308
- package/dist/platform-web/src/worker.js.map +0 -1
- package/dist/react/src/index.d.ts +0 -59
- package/dist/react/src/index.d.ts.map +0 -1
- package/dist/react/src/index.js +0 -151
- package/dist/react/src/index.js.map +0 -1
- package/dist/schema/src/definition.d.ts +0 -98
- package/dist/schema/src/definition.d.ts.map +0 -1
- package/dist/schema/src/definition.js +0 -84
- package/dist/schema/src/definition.js.map +0 -1
- package/dist/schema/src/planner.d.ts +0 -42
- package/dist/schema/src/planner.d.ts.map +0 -1
- package/dist/schema/src/planner.js +0 -131
- package/dist/schema/src/planner.js.map +0 -1
- package/dist/schema/src/validators.d.ts +0 -194
- package/dist/schema/src/validators.d.ts.map +0 -1
- package/dist/schema/src/validators.js +0 -158
- package/dist/schema/src/validators.js.map +0 -1
- package/dist/svelte/src/index.d.ts +0 -44
- package/dist/svelte/src/index.d.ts.map +0 -1
- package/dist/svelte/src/index.js +0 -75
- package/dist/svelte/src/index.js.map +0 -1
|
@@ -0,0 +1,997 @@
|
|
|
1
|
+
import { CliContext, openTarget } from "./context.mjs";
|
|
2
|
+
import { printCompactDevPhase, printDevSessionIntro, runShellCommand, withConsoleCapture } from "./dev-session.mjs";
|
|
3
|
+
import { buildRuntimeLookup, connectToProjectHub, createManagedProjectClient, createPublicRuntimeId, exportProjectData, importProjectData, isKnownTemplate, listAvailableTargets, listConnectedClientTargets, listProjectTables, loadImportDocumentBatches, readProjectTable, resolveDashboardUrl, resolveDevtoolsUrl, resolveDocsTarget, resolveProjectFunction, targetSupportsCapability, writeExportData } from "./project.mjs";
|
|
4
|
+
import { buildDevBootstrapNextSteps, buildHubUnavailableNextSteps, buildInitNextSteps, buildTargetCommandNextSteps, templateUsesConnectedClients } from "./messages.mjs";
|
|
5
|
+
import { buildDoctorReport } from "./doctor.mjs";
|
|
6
|
+
import { applyRootHelp } from "./help.mjs";
|
|
7
|
+
import { printDevReadySummary, printDoctorReport, printTargetsTable, renderOutput } from "./render.mjs";
|
|
8
|
+
import { resolveClientRuntime, resolveOperationalTarget } from "./targets.mjs";
|
|
9
|
+
import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { Command } from "commander";
|
|
12
|
+
import { createSchemaSnapshot, diffSchemaSnapshots, renderMigrationSql } from "../core/index.mjs";
|
|
13
|
+
import { VALID_SYNCORE_TEMPLATES, applyProjectMigrations, detectProjectTemplate, formatError, getNextMigrationNumber, hasSyncoreProject, isLocalPortInUse, loadProjectSchema, readStoredSnapshot, resolveRequestedTemplate, runCodegen, runDevProjectBootstrap, scaffoldProject, slugify, startDevHub, writeStoredSnapshot } from "../core/cli.mjs";
|
|
14
|
+
//#region src/app.ts
|
|
15
|
+
async function runSyncoreCli(argv = process.argv) {
|
|
16
|
+
const program = buildProgram();
|
|
17
|
+
if (argv.length <= 2) argv = [...argv, "--help"];
|
|
18
|
+
try {
|
|
19
|
+
await program.parseAsync(argv);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
new CliContext(parseGlobalOptionsFromArgv(argv)).handleError(error);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function buildProgram() {
|
|
25
|
+
const program = new Command();
|
|
26
|
+
program.name("syncorejs").usage("<command> [options]").option("--cwd <path>", "Run the command as if started from the given directory").option("--json", "Emit machine-readable JSON output").option("--verbose", "Print additional diagnostics").option("--no-interactive", "Disable prompts and terminal UX").option("-y, --yes", "Assume yes for confirmations").showHelpAfterError().showSuggestionAfterError().helpCommand("help <command>", "Show help for a command");
|
|
27
|
+
applyRootHelp(program);
|
|
28
|
+
addInitCommand(program);
|
|
29
|
+
addCodegenCommand(program);
|
|
30
|
+
addDoctorCommand(program);
|
|
31
|
+
addTargetsCommand(program);
|
|
32
|
+
addDevCommand(program);
|
|
33
|
+
addMigrateCommand(program);
|
|
34
|
+
addRunCommand(program);
|
|
35
|
+
addDataCommand(program);
|
|
36
|
+
addImportCommand(program);
|
|
37
|
+
addExportCommand(program);
|
|
38
|
+
addLogsCommand(program);
|
|
39
|
+
addDashboardCommand(program);
|
|
40
|
+
addDocsCommand(program);
|
|
41
|
+
return program;
|
|
42
|
+
}
|
|
43
|
+
function addInitCommand(program) {
|
|
44
|
+
program.command("init").summary("Scaffold Syncore into the current project").description("Scaffold Syncore files, scripts, and generated types into the selected directory.").option("--template <template>", `Template to scaffold (${VALID_SYNCORE_TEMPLATES.join(", ")}, or auto)`, "auto").option("--force", "Overwrite Syncore-managed files").addHelpText("after", [
|
|
45
|
+
"",
|
|
46
|
+
"Examples:",
|
|
47
|
+
" npx syncorejs init",
|
|
48
|
+
" npx syncorejs init --template react-web",
|
|
49
|
+
" npx syncorejs init --cwd ./examples/my-app"
|
|
50
|
+
].join("\n")).action(async (options, command) => {
|
|
51
|
+
const ctx = createContext(command);
|
|
52
|
+
await executeCommand(ctx, async () => {
|
|
53
|
+
if (!options.force && !await isDirectoryEmpty(ctx.cwd) && !await ctx.confirm("The target directory is not empty. Continue scaffolding into it?", false)) ctx.fail("Scaffolding cancelled by user.", 1);
|
|
54
|
+
let template = options.template;
|
|
55
|
+
if (template === "auto") {
|
|
56
|
+
const detectedTemplate = await detectProjectTemplate(ctx.cwd);
|
|
57
|
+
template = detectedTemplate === "minimal" && ctx.interactive ? await promptForTemplate(ctx, detectedTemplate) : detectedTemplate;
|
|
58
|
+
}
|
|
59
|
+
if (!isKnownTemplate(template)) ctx.fail(`Unknown template ${JSON.stringify(template)}. Expected one of ${VALID_SYNCORE_TEMPLATES.join(", ")} or auto.`);
|
|
60
|
+
const resolvedTemplate = template;
|
|
61
|
+
const result = await ctx.withSpinner("Scaffolding Syncore", async () => scaffoldProject(ctx.cwd, {
|
|
62
|
+
template: resolvedTemplate,
|
|
63
|
+
...options.force ? { force: true } : {}
|
|
64
|
+
}));
|
|
65
|
+
await ctx.withSpinner("Generating typed references", async () => runCodegen(ctx.cwd));
|
|
66
|
+
ctx.printResult({
|
|
67
|
+
summary: `Syncore scaffolded with the ${resolvedTemplate} template.`,
|
|
68
|
+
command: "init",
|
|
69
|
+
data: result,
|
|
70
|
+
nextSteps: buildInitNextSteps(resolvedTemplate)
|
|
71
|
+
});
|
|
72
|
+
if (!ctx.json) printScaffoldChanges(ctx, result);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
function addCodegenCommand(program) {
|
|
77
|
+
program.command("codegen").summary("Generate typed Syncore references").description("Regenerate syncore/_generated from the current syncore/functions tree.").addHelpText("after", [
|
|
78
|
+
"",
|
|
79
|
+
"Examples:",
|
|
80
|
+
" npx syncorejs codegen",
|
|
81
|
+
" npx syncorejs codegen --cwd ./apps/web"
|
|
82
|
+
].join("\n")).action(async (_options, command) => {
|
|
83
|
+
const ctx = createContext(command);
|
|
84
|
+
await executeCommand(ctx, async () => {
|
|
85
|
+
await ctx.withSpinner("Generating typed references", async () => runCodegen(ctx.cwd));
|
|
86
|
+
ctx.printResult({
|
|
87
|
+
summary: "Generated syncore/_generated files.",
|
|
88
|
+
command: "codegen"
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function addDoctorCommand(program) {
|
|
94
|
+
program.command("doctor").summary("Inspect the current Syncore project state").description("Check project structure, template capabilities, hub state, and available targets.").addHelpText("after", [
|
|
95
|
+
"",
|
|
96
|
+
"Examples:",
|
|
97
|
+
" npx syncorejs doctor",
|
|
98
|
+
" npx syncorejs doctor --json"
|
|
99
|
+
].join("\n")).action(async (_options, command) => {
|
|
100
|
+
const ctx = createContext(command);
|
|
101
|
+
await executeCommand(ctx, async () => {
|
|
102
|
+
const report = await buildDoctorReport(ctx.cwd);
|
|
103
|
+
if (ctx.json) {
|
|
104
|
+
ctx.printResult({
|
|
105
|
+
command: "doctor",
|
|
106
|
+
data: report
|
|
107
|
+
});
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
ctx.info(`Detected template: ${report.template}`);
|
|
111
|
+
ctx.info(`Project status: ${report.status}`);
|
|
112
|
+
ctx.info(`Project target: ${report.projectTarget ? report.projectTarget.databasePath : "not configured"}`);
|
|
113
|
+
ctx.info(`Devtools hub: ${report.hub.running ? report.hub.url : "not running"}`);
|
|
114
|
+
printDoctorReport(report, { verbose: ctx.verbose });
|
|
115
|
+
if (report.workspaceMatches.length > 0) {
|
|
116
|
+
ctx.warn("You appear to be at a workspace root instead of inside an app package.");
|
|
117
|
+
for (const match of report.workspaceMatches) process.stdout.write(` - ${match.relativePath} (${match.template}) -> use --cwd ${match.relativePath}\n`);
|
|
118
|
+
}
|
|
119
|
+
for (const suggestion of report.suggestions) ctx.nextStep(suggestion);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
function addTargetsCommand(program) {
|
|
124
|
+
program.command("targets").summary("List available Syncore targets").description("Inspect project and connected client targets for run, data, import, export, and logs.").option("--online-only", "Only show online targets").option("--capability <capability>", "Filter targets by capability: run, readData, writeData, exportData, streamLogs").addHelpText("after", [
|
|
125
|
+
"",
|
|
126
|
+
"Examples:",
|
|
127
|
+
" npx syncorejs targets",
|
|
128
|
+
" npx syncorejs targets --capability run",
|
|
129
|
+
" npx syncorejs targets --json"
|
|
130
|
+
].join("\n")).action(async (options, command) => {
|
|
131
|
+
const ctx = createContext(command);
|
|
132
|
+
await executeCommand(ctx, async () => {
|
|
133
|
+
if (options.capability && !isTargetCapability(options.capability)) ctx.fail(`Unknown capability ${JSON.stringify(options.capability)}. Expected run, readData, writeData, exportData, or streamLogs.`);
|
|
134
|
+
const filtered = (await listAvailableTargets(ctx.cwd)).filter((target) => {
|
|
135
|
+
if (options.onlineOnly && !target.online) return false;
|
|
136
|
+
if (options.capability && !targetSupportsCapability(target, options.capability)) return false;
|
|
137
|
+
return true;
|
|
138
|
+
});
|
|
139
|
+
ctx.printResult({
|
|
140
|
+
command: "targets",
|
|
141
|
+
summary: `Found ${filtered.length} target(s).`,
|
|
142
|
+
data: filtered,
|
|
143
|
+
nextSteps: buildTargetCommandNextSteps(filtered[0]?.id)
|
|
144
|
+
});
|
|
145
|
+
if (!ctx.json) printTargetsTable(filtered, { verbose: ctx.verbose });
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
function addDevCommand(program) {
|
|
150
|
+
program.command("dev").summary("Run the Syncore development loop").description("Bootstrap the local Syncore project, start the hub, discover targets, and keep the local workflow in sync.").option("--template <template>", `Template to scaffold when Syncore is missing (${VALID_SYNCORE_TEMPLATES.join(", ")}, or auto)`, "auto").option("--once", "Run bootstrap once and exit").option("--until-success", "Retry bootstrap until it succeeds").option("--run <function>", "Run a Syncore function after bootstrap succeeds").option("--run-sh <command>", "Run a shell command after bootstrap succeeds").option("--open-dashboard", "Open the dashboard URL even in non-interactive mode").addHelpText("after", [
|
|
151
|
+
"",
|
|
152
|
+
"Examples:",
|
|
153
|
+
" npx syncorejs dev",
|
|
154
|
+
" npx syncorejs dev --once",
|
|
155
|
+
" npx syncorejs dev --until-success",
|
|
156
|
+
" npx syncorejs dev --run tasks/list",
|
|
157
|
+
" npx syncorejs dev --open-dashboard",
|
|
158
|
+
" npx syncorejs dev --run-sh \"npm run dev\""
|
|
159
|
+
].join("\n")).action(async (options, command) => {
|
|
160
|
+
const ctx = createContext(command);
|
|
161
|
+
await executeCommand(ctx, async () => {
|
|
162
|
+
if (options.run && options.runSh) ctx.fail("`syncorejs dev` accepts either --run or --run-sh, not both.");
|
|
163
|
+
const shouldOpenDashboard = Boolean(options.openDashboard) || isRealInteractiveTerminal(ctx);
|
|
164
|
+
await ensureLocalPortConfiguration(ctx);
|
|
165
|
+
const template = await resolveRequestedTemplate(ctx.cwd, options.template);
|
|
166
|
+
printDevSessionIntro(ctx);
|
|
167
|
+
await ensureDevProjectExists(ctx, template);
|
|
168
|
+
if (options.once) {
|
|
169
|
+
await runDevBootstrapLoop(ctx, template, options.untilSuccess ?? false);
|
|
170
|
+
await runDevFollowup(ctx, options);
|
|
171
|
+
const targets = await listAvailableTargets(ctx.cwd);
|
|
172
|
+
ctx.printResult({
|
|
173
|
+
summary: "Syncore dev bootstrap completed.",
|
|
174
|
+
command: "dev",
|
|
175
|
+
nextSteps: buildDevBootstrapNextSteps()
|
|
176
|
+
});
|
|
177
|
+
if (!ctx.json) printDevReadySummary(ctx, {
|
|
178
|
+
template,
|
|
179
|
+
projectTargetConfigured: targets.some((target) => target.kind === "project"),
|
|
180
|
+
dashboardUrl: resolveDashboardUrl(),
|
|
181
|
+
devtoolsUrl: resolveDevtoolsUrl(),
|
|
182
|
+
targets
|
|
183
|
+
});
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
await runDevBootstrapLoop(ctx, template, options.untilSuccess ?? false);
|
|
187
|
+
const targets = await listAvailableTargets(ctx.cwd);
|
|
188
|
+
printDevReadySummary(ctx, {
|
|
189
|
+
template,
|
|
190
|
+
projectTargetConfigured: targets.some((target) => target.kind === "project"),
|
|
191
|
+
dashboardUrl: resolveDashboardUrl(),
|
|
192
|
+
devtoolsUrl: resolveDevtoolsUrl(),
|
|
193
|
+
targets
|
|
194
|
+
});
|
|
195
|
+
await startManagedDevHub(ctx, template);
|
|
196
|
+
await maybeOpenDashboard(ctx, shouldOpenDashboard);
|
|
197
|
+
await monitorLiveDevSession(ctx, template);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
function isRealInteractiveTerminal(context) {
|
|
202
|
+
return context.interactive && Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
203
|
+
}
|
|
204
|
+
async function maybeOpenDashboard(context, shouldOpenDashboard) {
|
|
205
|
+
if (!shouldOpenDashboard) return;
|
|
206
|
+
if (await openTarget(resolveDashboardUrl())) {
|
|
207
|
+
context.info(`Opened dashboard at ${resolveDashboardUrl()}.`);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
context.warn("Unable to open the dashboard automatically.");
|
|
211
|
+
}
|
|
212
|
+
function addMigrateCommand(program) {
|
|
213
|
+
const migrate = program.command("migrate").summary("Generate and apply local SQL migrations").description("Work with schema diffs, migration SQL, and the local Syncore database.");
|
|
214
|
+
migrate.command("status").summary("Show the current schema diff status").action(async (_options, command) => {
|
|
215
|
+
const ctx = createContext(command);
|
|
216
|
+
await executeCommand(ctx, async () => {
|
|
217
|
+
const currentSnapshot = createSchemaSnapshot(await loadProjectSchema(ctx.cwd));
|
|
218
|
+
const storedSnapshot = await readStoredSnapshot(ctx.cwd);
|
|
219
|
+
const plan = diffSchemaSnapshots(storedSnapshot, currentSnapshot);
|
|
220
|
+
ctx.printResult({
|
|
221
|
+
summary: "Migration status computed.",
|
|
222
|
+
command: "migrate status",
|
|
223
|
+
data: {
|
|
224
|
+
currentSchemaHash: currentSnapshot.hash,
|
|
225
|
+
storedSchemaHash: storedSnapshot?.hash ?? null,
|
|
226
|
+
statements: plan.statements,
|
|
227
|
+
warnings: plan.warnings,
|
|
228
|
+
destructiveChanges: plan.destructiveChanges
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
if (!ctx.json) {
|
|
232
|
+
process.stdout.write(`Current schema hash: ${currentSnapshot.hash}\n`);
|
|
233
|
+
process.stdout.write(`Stored snapshot: ${storedSnapshot?.hash ?? "none"}\n`);
|
|
234
|
+
process.stdout.write(`Statements to generate: ${plan.statements.length}\n`);
|
|
235
|
+
process.stdout.write(`Warnings: ${plan.warnings.length}\n`);
|
|
236
|
+
process.stdout.write(`Destructive changes: ${plan.destructiveChanges.length}\n`);
|
|
237
|
+
for (const warning of plan.warnings) ctx.warn(warning);
|
|
238
|
+
for (const change of plan.destructiveChanges) ctx.error(change);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
migrate.command("generate").argument("[name]", "Optional migration name", "auto").summary("Generate a SQL migration from the current schema diff").action(async (name, _options, command) => {
|
|
243
|
+
const ctx = createContext(command);
|
|
244
|
+
await executeCommand(ctx, async () => {
|
|
245
|
+
const currentSnapshot = createSchemaSnapshot(await loadProjectSchema(ctx.cwd));
|
|
246
|
+
const plan = diffSchemaSnapshots(await readStoredSnapshot(ctx.cwd), currentSnapshot);
|
|
247
|
+
if (plan.destructiveChanges.length > 0) ctx.fail(`Destructive schema changes require a manual migration: ${plan.destructiveChanges.join("; ")}`);
|
|
248
|
+
if (plan.statements.length === 0 && plan.warnings.length === 0) {
|
|
249
|
+
ctx.printResult({ summary: "No schema changes detected." });
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const migrationsDirectory = path.join(ctx.cwd, "syncore", "migrations");
|
|
253
|
+
await mkdir(migrationsDirectory, { recursive: true });
|
|
254
|
+
const migrationNumber = await getNextMigrationNumber(migrationsDirectory);
|
|
255
|
+
const slug = slugify(name);
|
|
256
|
+
const fileName = `${String(migrationNumber).padStart(4, "0")}_${slug}.sql`;
|
|
257
|
+
const migrationSql = renderMigrationSql(plan, { title: `Syncore migration ${fileName}` });
|
|
258
|
+
await writeFile(path.join(migrationsDirectory, fileName), migrationSql);
|
|
259
|
+
await writeStoredSnapshot(ctx.cwd, currentSnapshot);
|
|
260
|
+
ctx.printResult({
|
|
261
|
+
summary: `Generated syncore/migrations/${fileName}.`,
|
|
262
|
+
command: "migrate generate",
|
|
263
|
+
data: {
|
|
264
|
+
path: path.join("syncore", "migrations", fileName),
|
|
265
|
+
statements: plan.statements,
|
|
266
|
+
warnings: plan.warnings
|
|
267
|
+
},
|
|
268
|
+
nextSteps: ["Run `npx syncorejs migrate apply` to apply pending migrations."]
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
migrate.command("apply").summary("Apply SQL migrations to the local database").action(async (_options, command) => {
|
|
273
|
+
const ctx = createContext(command);
|
|
274
|
+
await executeCommand(ctx, async () => {
|
|
275
|
+
const appliedCount = await ctx.withSpinner("Applying migrations", async () => applyProjectMigrations(ctx.cwd));
|
|
276
|
+
ctx.printResult({
|
|
277
|
+
summary: `Applied ${appliedCount} migration(s).`,
|
|
278
|
+
command: "migrate apply"
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
function addRunCommand(program) {
|
|
284
|
+
program.command("run").summary("Run a local Syncore function").description("Execute a query, mutation, or action against the local runtime.").argument("<functionName>", "Function name like tasks/list or api.tasks.list").argument("[args]", "JSON object of arguments", "{}").option("--watch", "Watch a query for local changes").option("--target <target>", "Target id: project or a 5-digit client target id").option("--runtime <runtime>", "Runtime id inside the selected client target").option("--format <format>", "Output format: pretty, json, or jsonl", "pretty").addHelpText("after", [
|
|
285
|
+
"",
|
|
286
|
+
"Examples:",
|
|
287
|
+
" npx syncorejs run tasks/list",
|
|
288
|
+
" npx syncorejs run api.tasks.create '{\"text\":\"Ship Syncore\"}' --target project",
|
|
289
|
+
" npx syncorejs run tasks/list --watch --target 10427 --runtime 20318 --format json"
|
|
290
|
+
].join("\n")).action(async (functionName, argsText, options, command) => {
|
|
291
|
+
const ctx = createContext(command);
|
|
292
|
+
await executeCommand(ctx, async () => {
|
|
293
|
+
if (options.runtime && !options.target) ctx.fail("`syncorejs run --runtime` requires --target.");
|
|
294
|
+
const resolved = await resolveProjectFunction(ctx.cwd, functionName);
|
|
295
|
+
const args = parseJsonObject(argsText, "Function arguments");
|
|
296
|
+
if (options.watch && resolved.definition.kind !== "query") ctx.fail("`syncorejs run --watch` only supports query functions.");
|
|
297
|
+
const target = await resolveOperationalTarget(ctx, options.target, {
|
|
298
|
+
command: "run",
|
|
299
|
+
capability: "run"
|
|
300
|
+
});
|
|
301
|
+
const runtime = resolveClientRuntime(target, options.runtime, { command: "run" });
|
|
302
|
+
ctx.info(options.watch ? `Watching ${resolved.name} on ${target.id}${runtime ? ` (${runtime.id} ${runtime.label})` : ""}.` : `Running ${resolved.name} on ${target.id}${runtime ? ` (${runtime.id} ${runtime.label})` : ""}.`);
|
|
303
|
+
if (target.kind === "project") {
|
|
304
|
+
const managed = await createManagedProjectClient(ctx.cwd);
|
|
305
|
+
try {
|
|
306
|
+
if (options.watch) {
|
|
307
|
+
const watch = managed.client.watchQuery(resolved.reference, args);
|
|
308
|
+
const render = () => {
|
|
309
|
+
const error = watch.localQueryError();
|
|
310
|
+
if (error) {
|
|
311
|
+
ctx.handleError(error);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
renderOutput(ctx, watch.localQueryResult(), options.format);
|
|
315
|
+
};
|
|
316
|
+
const unsubscribe = watch.onUpdate(render);
|
|
317
|
+
ctx.info("Watching query. Press Ctrl+C to stop.");
|
|
318
|
+
await waitForSignal();
|
|
319
|
+
unsubscribe();
|
|
320
|
+
watch.dispose?.();
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
renderOutput(ctx, resolved.definition.kind === "query" ? await managed.client.query(resolved.reference, args) : resolved.definition.kind === "mutation" ? await managed.client.mutation(resolved.reference, args) : await managed.client.action(resolved.reference, args), options.format);
|
|
324
|
+
return;
|
|
325
|
+
} finally {
|
|
326
|
+
await managed.dispose();
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
const hub = await requireHubConnection(ctx);
|
|
330
|
+
try {
|
|
331
|
+
if (options.watch) {
|
|
332
|
+
const unsubscribe = hub.subscribe(runtime.runtimeId, {
|
|
333
|
+
kind: "fn.watch",
|
|
334
|
+
functionName: resolved.name,
|
|
335
|
+
functionType: "query",
|
|
336
|
+
args
|
|
337
|
+
}, {
|
|
338
|
+
onData(payload) {
|
|
339
|
+
if (payload.kind !== "fn.watch.result") return;
|
|
340
|
+
if (payload.error) {
|
|
341
|
+
ctx.handleError(new Error(payload.error));
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
renderOutput(ctx, payload.result, options.format);
|
|
345
|
+
},
|
|
346
|
+
onError(error) {
|
|
347
|
+
ctx.handleError(new Error(error));
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
ctx.info(`Watching query on ${target.id}. Press Ctrl+C to stop.`);
|
|
351
|
+
await waitForSignal();
|
|
352
|
+
unsubscribe();
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
const result = await hub.sendCommand(runtime.runtimeId, {
|
|
356
|
+
kind: "fn.run",
|
|
357
|
+
functionName: resolved.name,
|
|
358
|
+
functionType: resolved.definition.kind,
|
|
359
|
+
args
|
|
360
|
+
});
|
|
361
|
+
if (result.kind === "fn.run.result") {
|
|
362
|
+
if (result.error) ctx.fail(result.error);
|
|
363
|
+
renderOutput(ctx, result.result, options.format);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
if (result.kind === "error") ctx.fail(result.message, 1, result);
|
|
367
|
+
ctx.fail(`Unexpected response from ${target.id}.`, 1, result);
|
|
368
|
+
} finally {
|
|
369
|
+
await hub.dispose();
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
function addDataCommand(program) {
|
|
375
|
+
program.command("data").summary("Inspect local Syncore data").description("List tables or print local rows from a specific table.").argument("[table]", "Optional table to inspect").option("--target <target>", "Target id: project or a 5-digit client target id").option("--runtime <runtime>", "Runtime id inside the selected client target").option("--limit <n>", "Maximum rows to print", "100").option("--order <choice>", "Order by _creationTime", "desc").option("--watch", "Watch a table for changes on the selected target").option("--format <format>", "Output format: pretty, json, or jsonl", "pretty").addHelpText("after", [
|
|
376
|
+
"",
|
|
377
|
+
"Examples:",
|
|
378
|
+
" npx syncorejs data",
|
|
379
|
+
" npx syncorejs data tasks --target project --limit 10",
|
|
380
|
+
" npx syncorejs data tasks --target 10427 --runtime 20318 --watch --format jsonl"
|
|
381
|
+
].join("\n")).action(async (table, options, command) => {
|
|
382
|
+
const ctx = createContext(command);
|
|
383
|
+
await executeCommand(ctx, async () => {
|
|
384
|
+
if (options.runtime && !options.target) ctx.fail("`syncorejs data --runtime` requires --target.");
|
|
385
|
+
const target = await resolveOperationalTarget(ctx, options.target, {
|
|
386
|
+
command: "data",
|
|
387
|
+
capability: "readData"
|
|
388
|
+
});
|
|
389
|
+
const runtime = resolveClientRuntime(target, options.runtime, { command: "data" });
|
|
390
|
+
if (!table) {
|
|
391
|
+
const tables = target.kind === "project" ? await listProjectTables(ctx.cwd) : await listRemoteTables(runtime.runtimeId, ctx);
|
|
392
|
+
ctx.printResult({
|
|
393
|
+
summary: `Found ${tables.length} table(s) on ${target.id}.`,
|
|
394
|
+
command: "data",
|
|
395
|
+
data: tables,
|
|
396
|
+
target: target.id
|
|
397
|
+
});
|
|
398
|
+
if (!ctx.json) for (const entry of tables) process.stdout.write(` ${entry.name} (${entry.documentCount} document(s))\n`);
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
if (target.kind === "project") {
|
|
402
|
+
renderOutput(ctx, (await readProjectTable(ctx.cwd, table, {
|
|
403
|
+
limit: Number.parseInt(options.limit, 10),
|
|
404
|
+
order: options.order === "asc" ? "asc" : "desc"
|
|
405
|
+
})).rows, options.format);
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
const hub = await requireHubConnection(ctx);
|
|
409
|
+
try {
|
|
410
|
+
renderOutput(ctx, (await readRemoteTable(hub, runtime.runtimeId, table, { limit: Number.parseInt(options.limit, 10) })).rows, options.format);
|
|
411
|
+
if (!options.watch) return;
|
|
412
|
+
const unsubscribe = hub.subscribe(runtime.runtimeId, {
|
|
413
|
+
kind: "data.table",
|
|
414
|
+
table,
|
|
415
|
+
limit: Number.parseInt(options.limit, 10)
|
|
416
|
+
}, {
|
|
417
|
+
onData(result) {
|
|
418
|
+
if (result.kind !== "data.table.result") return;
|
|
419
|
+
renderOutput(ctx, result.rows, options.format);
|
|
420
|
+
},
|
|
421
|
+
onError(error) {
|
|
422
|
+
ctx.handleError(new Error(error));
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
ctx.info(`Watching table ${table} on ${target.id} (${runtime.id} ${runtime.label}). Press Ctrl+C to stop.`);
|
|
426
|
+
await waitForSignal();
|
|
427
|
+
unsubscribe();
|
|
428
|
+
} finally {
|
|
429
|
+
await hub.dispose();
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
function addImportCommand(program) {
|
|
435
|
+
program.command("import").summary("Import local data into Syncore").description("Import JSON, JSONL, directory, or ZIP data into the local Syncore database.").argument("<path>", "Path to a .json, .jsonl, directory, or .zip input").option("--table <table>", "Destination table for single-file imports").option("--target <target>", "Target id: project or a 5-digit client target id").option("--runtime <runtime>", "Runtime id inside the selected client target").addHelpText("after", [
|
|
436
|
+
"",
|
|
437
|
+
"Examples:",
|
|
438
|
+
" npx syncorejs import --table tasks sample.jsonl --target project",
|
|
439
|
+
" npx syncorejs import --table tasks sample.json --target 10427 --runtime 20318",
|
|
440
|
+
" npx syncorejs import backups/export.zip"
|
|
441
|
+
].join("\n")).action(async (sourcePath, options, command) => {
|
|
442
|
+
const ctx = createContext(command);
|
|
443
|
+
await executeCommand(ctx, async () => {
|
|
444
|
+
if (options.runtime && !options.target) ctx.fail("`syncorejs import --runtime` requires --target.");
|
|
445
|
+
const target = await resolveOperationalTarget(ctx, options.target, {
|
|
446
|
+
command: "import",
|
|
447
|
+
capability: "writeData"
|
|
448
|
+
});
|
|
449
|
+
const runtime = resolveClientRuntime(target, options.runtime, { command: "import" });
|
|
450
|
+
const preview = await previewImportPlan(ctx, sourcePath, options, target.id);
|
|
451
|
+
if (ctx.interactive && !await ctx.confirm(`Import ${preview.totalRows} row(s) into ${target.id}?`, true)) ctx.fail("Import cancelled by user.");
|
|
452
|
+
const imported = target.kind === "project" ? await ctx.withSpinner("Importing local data", async () => importProjectData(ctx.cwd, sourcePath, { ...options.table ? { table: options.table } : {} })) : await ctx.withSpinner(`Importing data into ${target.id}`, async () => importIntoClientTarget(ctx, target, runtime, sourcePath, options));
|
|
453
|
+
ctx.printResult({
|
|
454
|
+
summary: `Imported ${imported.reduce((sum, entry) => sum + entry.importedCount, 0)} row(s).`,
|
|
455
|
+
command: "import",
|
|
456
|
+
data: imported,
|
|
457
|
+
target: target.id
|
|
458
|
+
});
|
|
459
|
+
if (!ctx.json) for (const entry of imported) process.stdout.write(` ${entry.table}: ${entry.importedCount} row(s)\n`);
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
function addExportCommand(program) {
|
|
464
|
+
program.command("export").summary("Export local Syncore data").description("Export one or more local tables to JSON, JSONL, a directory, or a ZIP file.").requiredOption("--path <path>", "Output path (.json, .jsonl, directory, or .zip)").option("--table <table>", "Export a single table").option("--target <target>", "Target id: project or a 5-digit client target id").option("--runtime <runtime>", "Runtime id inside the selected client target").addHelpText("after", [
|
|
465
|
+
"",
|
|
466
|
+
"Examples:",
|
|
467
|
+
" npx syncorejs export --table tasks --path tasks.jsonl --target project",
|
|
468
|
+
" npx syncorejs export --path ./exports --target 10427 --runtime 20318",
|
|
469
|
+
" npx syncorejs export --path ./exports.zip"
|
|
470
|
+
].join("\n")).action(async (options, command) => {
|
|
471
|
+
const ctx = createContext(command);
|
|
472
|
+
await executeCommand(ctx, async () => {
|
|
473
|
+
if (options.runtime && !options.target) ctx.fail("`syncorejs export --runtime` requires --target.");
|
|
474
|
+
const target = await resolveOperationalTarget(ctx, options.target, {
|
|
475
|
+
command: "export",
|
|
476
|
+
capability: "exportData"
|
|
477
|
+
});
|
|
478
|
+
const runtime = resolveClientRuntime(target, options.runtime, { command: "export" });
|
|
479
|
+
const result = target.kind === "project" ? await ctx.withSpinner("Exporting local data", async () => exportProjectData(ctx.cwd, options.path, { ...options.table ? { table: options.table } : {} })) : await ctx.withSpinner(`Exporting data from ${target.id}`, async () => exportClientTargetData(ctx, target, runtime, options));
|
|
480
|
+
ctx.printResult({
|
|
481
|
+
summary: `Exported ${result.tables.length} table(s) to ${result.path}.`,
|
|
482
|
+
command: "export",
|
|
483
|
+
data: result,
|
|
484
|
+
target: target.id
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
function addLogsCommand(program) {
|
|
490
|
+
program.command("logs").summary("Inspect Syncore runtime logs").description("Read persisted hub logs and optionally watch live runtime events.").option("--target <target>", "Target id, or all", "all").option("--runtime <runtime>", "Runtime id inside the selected client target").option("--limit <n>", "Maximum log lines to print", "100").option("--watch", "Stream new logs from the local devtools hub").option("--kind <kind>", "Filter by event kind: query, mutation, action, system").option("--format <format>", "Output format: pretty, json, or jsonl", "pretty").addHelpText("after", [
|
|
491
|
+
"",
|
|
492
|
+
"Examples:",
|
|
493
|
+
" npx syncorejs logs",
|
|
494
|
+
" npx syncorejs logs --target 10427 --runtime 20318 --watch",
|
|
495
|
+
" npx syncorejs logs --kind mutation --format jsonl"
|
|
496
|
+
].join("\n")).action(async (options, command) => {
|
|
497
|
+
const ctx = createContext(command);
|
|
498
|
+
await executeCommand(ctx, async () => {
|
|
499
|
+
if (options.runtime && (!options.target || options.target === "all")) ctx.fail("`syncorejs logs --runtime` requires a specific --target.");
|
|
500
|
+
await runLogsCommand(ctx, options);
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
function addDashboardCommand(program) {
|
|
505
|
+
program.command("dashboard").summary("Print or open the local dashboard URL").description("Show the local Syncore dashboard URL, optionally opening it in the browser.").option("--open", "Open the dashboard URL").action(async (options, command) => {
|
|
506
|
+
const ctx = createContext(command);
|
|
507
|
+
await executeCommand(ctx, async () => {
|
|
508
|
+
const url = resolveDashboardUrl();
|
|
509
|
+
if (options.open) {
|
|
510
|
+
if (!await openTarget(url)) ctx.warn("Unable to open the dashboard automatically.");
|
|
511
|
+
}
|
|
512
|
+
ctx.printResult({
|
|
513
|
+
summary: "Dashboard URL resolved.",
|
|
514
|
+
command: "dashboard",
|
|
515
|
+
data: { url },
|
|
516
|
+
nextSteps: [`Open ${url}`]
|
|
517
|
+
});
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
function addDocsCommand(program) {
|
|
522
|
+
program.command("docs").summary("Print or open the most relevant Syncore docs").description("Resolve the best local docs target for the detected template and optionally open it.").option("--open", "Open the docs target").action(async (options, command) => {
|
|
523
|
+
const ctx = createContext(command);
|
|
524
|
+
await executeCommand(ctx, async () => {
|
|
525
|
+
const url = await resolveDocsTarget(ctx.cwd);
|
|
526
|
+
if (options.open) {
|
|
527
|
+
if (!await openTarget(url)) ctx.warn("Unable to open the docs target automatically.");
|
|
528
|
+
}
|
|
529
|
+
ctx.printResult({
|
|
530
|
+
summary: "Docs target resolved.",
|
|
531
|
+
command: "docs",
|
|
532
|
+
data: { url }
|
|
533
|
+
});
|
|
534
|
+
});
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
function createContext(command) {
|
|
538
|
+
return new CliContext(command.optsWithGlobals());
|
|
539
|
+
}
|
|
540
|
+
async function executeCommand(context, action) {
|
|
541
|
+
try {
|
|
542
|
+
await action();
|
|
543
|
+
} catch (error) {
|
|
544
|
+
context.handleError(error);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
async function ensureDevProjectExists(context, template) {
|
|
548
|
+
if (await hasSyncoreProject(context.cwd)) return;
|
|
549
|
+
if (!context.interactive) context.fail("No Syncore project was found in this directory. Run `npx syncorejs init` first or rerun in an interactive terminal.");
|
|
550
|
+
if (!await context.confirm("No Syncore project was found. Scaffold one now?", true)) context.fail("Syncore dev cancelled because no project exists.");
|
|
551
|
+
const result = await context.withSpinner("Scaffolding Syncore", async () => scaffoldProject(context.cwd, { template }));
|
|
552
|
+
if (!context.json) printScaffoldChanges(context, result);
|
|
553
|
+
}
|
|
554
|
+
async function ensureLocalPortConfiguration(context) {
|
|
555
|
+
const dashboardUrl = resolveDashboardUrl();
|
|
556
|
+
const devtoolsUrl = resolveDevtoolsUrl();
|
|
557
|
+
const dashboardPort = Number.parseInt(new URL(dashboardUrl).port, 10);
|
|
558
|
+
const devtoolsPort = Number.parseInt(new URL(devtoolsUrl).port, 10);
|
|
559
|
+
if (Number.isFinite(dashboardPort) && Number.isFinite(devtoolsPort) && dashboardPort === devtoolsPort) context.fail([`Dashboard and devtools cannot share the same port (${dashboardPort}).`, "Set different values for SYNCORE_DASHBOARD_PORT and SYNCORE_DEVTOOLS_PORT, then rerun `npx syncorejs dev`."].join(" "));
|
|
560
|
+
if (Number.isFinite(dashboardPort) && dashboardPort > 0 && Number.isFinite(devtoolsPort) && devtoolsPort > 0 && await isLocalPortInUse(dashboardPort) && !await isLocalPortInUse(devtoolsPort)) context.warn(`Dashboard port ${dashboardPort} is already in use. If Syncore does not start cleanly, set SYNCORE_DASHBOARD_PORT to a different value.`);
|
|
561
|
+
}
|
|
562
|
+
async function runDevBootstrapLoop(context, template, untilSuccess) {
|
|
563
|
+
while (true) try {
|
|
564
|
+
printCompactDevPhase(context, "Project");
|
|
565
|
+
printCompactDevPhase(context, "Codegen");
|
|
566
|
+
printCompactDevPhase(context, "Schema");
|
|
567
|
+
await withConsoleCapture((method, message) => {
|
|
568
|
+
if (/destructive schema changes/i.test(message)) {
|
|
569
|
+
context.error("Syncore dev blocked by destructive schema changes.");
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
if (/Syncore dev warning:/i.test(message) || method === "warn") {
|
|
573
|
+
context.warn(message.replace(/^Syncore dev warning:\s*/i, ""));
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
if (/bootstrap failed/i.test(message) || method === "error") context.error(message);
|
|
577
|
+
}, async () => runDevProjectBootstrap(context.cwd, template));
|
|
578
|
+
return;
|
|
579
|
+
} catch (error) {
|
|
580
|
+
if (!untilSuccess) throw error;
|
|
581
|
+
context.warn(`Syncore dev bootstrap failed, retrying: ${formatError(error)}`);
|
|
582
|
+
await new Promise((resolve) => setTimeout(resolve, 1200));
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
async function startManagedDevHub(context, template) {
|
|
586
|
+
printCompactDevPhase(context, "Hub");
|
|
587
|
+
printCompactDevPhase(context, "Targets");
|
|
588
|
+
await withConsoleCapture((method, message) => {
|
|
589
|
+
if (/already running/i.test(message)) {
|
|
590
|
+
context.info(message.replaceAll("127.0.0.1", "localhost"));
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
if (/Dashboard shell:/i.test(message) || /devtools hub:/i.test(message)) return;
|
|
594
|
+
if (/Watching syncore\//i.test(message)) {
|
|
595
|
+
context.info("Watching syncore/ for changes.");
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
if (/runtime\.disconnected/i.test(message)) {
|
|
599
|
+
context.warn(message);
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
if (method === "warn") {
|
|
603
|
+
context.warn(message);
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
if (method === "error") context.error(message);
|
|
607
|
+
}, async () => startDevHub({
|
|
608
|
+
cwd: context.cwd,
|
|
609
|
+
template
|
|
610
|
+
}));
|
|
611
|
+
}
|
|
612
|
+
async function runDevFollowup(context, options) {
|
|
613
|
+
if (options.run) {
|
|
614
|
+
const target = await resolveOperationalTarget(context, void 0, {
|
|
615
|
+
command: "run",
|
|
616
|
+
capability: "run"
|
|
617
|
+
});
|
|
618
|
+
const resolved = await resolveProjectFunction(context.cwd, options.run);
|
|
619
|
+
if (target.kind === "project") {
|
|
620
|
+
const managed = await createManagedProjectClient(context.cwd);
|
|
621
|
+
try {
|
|
622
|
+
renderOutput(context, resolved.definition.kind === "query" ? await managed.client.query(resolved.reference, {}) : resolved.definition.kind === "mutation" ? await managed.client.mutation(resolved.reference, {}) : await managed.client.action(resolved.reference, {}), "pretty");
|
|
623
|
+
} finally {
|
|
624
|
+
await managed.dispose();
|
|
625
|
+
}
|
|
626
|
+
} else {
|
|
627
|
+
const hub = await requireHubConnection(context);
|
|
628
|
+
try {
|
|
629
|
+
const result = await hub.sendCommand(target.runtimeId, {
|
|
630
|
+
kind: "fn.run",
|
|
631
|
+
functionName: resolved.name,
|
|
632
|
+
functionType: resolved.definition.kind,
|
|
633
|
+
args: {}
|
|
634
|
+
});
|
|
635
|
+
if (result.kind !== "fn.run.result") context.fail(`Unexpected response from ${target.id}.`, 1, result);
|
|
636
|
+
if (result.error) context.fail(result.error, 1, result);
|
|
637
|
+
renderOutput(context, result.result, "pretty");
|
|
638
|
+
} finally {
|
|
639
|
+
await hub.dispose();
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
if (options.runSh) await runShellCommand(context, options.runSh);
|
|
644
|
+
}
|
|
645
|
+
async function monitorLiveDevSession(context, template) {
|
|
646
|
+
if (!templateUsesConnectedClients(template)) {
|
|
647
|
+
await waitForSignal();
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
let knownTargets = /* @__PURE__ */ new Set();
|
|
651
|
+
let waitingNoticeVisible = false;
|
|
652
|
+
const refreshTargets = async () => {
|
|
653
|
+
const nextTargets = await listConnectedClientTargets();
|
|
654
|
+
const nextIds = new Set(nextTargets.map((target) => target.id));
|
|
655
|
+
for (const target of nextTargets) if (!knownTargets.has(target.id)) context.info(`Client target connected: ${target.id}`);
|
|
656
|
+
for (const targetId of knownTargets) if (!nextIds.has(targetId)) context.warn(`Client target disconnected: ${targetId}`);
|
|
657
|
+
if (nextTargets.length === 0 && !waitingNoticeVisible) {
|
|
658
|
+
context.info("Hub ready. Start your app to connect a client target.");
|
|
659
|
+
waitingNoticeVisible = true;
|
|
660
|
+
} else if (nextTargets.length > 0) waitingNoticeVisible = false;
|
|
661
|
+
knownTargets = nextIds;
|
|
662
|
+
};
|
|
663
|
+
await refreshTargets();
|
|
664
|
+
const interval = setInterval(() => {
|
|
665
|
+
refreshTargets();
|
|
666
|
+
}, 1500);
|
|
667
|
+
try {
|
|
668
|
+
await waitForSignal();
|
|
669
|
+
} finally {
|
|
670
|
+
clearInterval(interval);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
async function requireHubConnection(context) {
|
|
674
|
+
const hub = await connectToProjectHub();
|
|
675
|
+
if (!hub) context.fail("The local devtools hub is not running.", 1, void 0, {
|
|
676
|
+
category: "hub",
|
|
677
|
+
nextSteps: buildHubUnavailableNextSteps()
|
|
678
|
+
});
|
|
679
|
+
return hub;
|
|
680
|
+
}
|
|
681
|
+
async function listRemoteTables(runtimeId, context) {
|
|
682
|
+
const hub = await requireHubConnection(context);
|
|
683
|
+
try {
|
|
684
|
+
const result = await subscribeOnce(hub, runtimeId, { kind: "schema.tables" });
|
|
685
|
+
if (result.kind !== "schema.tables.result") context.fail("Unexpected response while listing remote tables.", 1, result);
|
|
686
|
+
return result.tables.map((table) => ({
|
|
687
|
+
name: table.name,
|
|
688
|
+
documentCount: table.documentCount
|
|
689
|
+
}));
|
|
690
|
+
} finally {
|
|
691
|
+
await hub.dispose();
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
async function readRemoteTable(hub, runtimeId, table, options) {
|
|
695
|
+
const result = await subscribeOnce(hub, runtimeId, {
|
|
696
|
+
kind: "data.table",
|
|
697
|
+
table,
|
|
698
|
+
limit: options.limit
|
|
699
|
+
});
|
|
700
|
+
if (result.kind !== "data.table.result") throw new Error(`Unexpected response while reading table ${table}.`);
|
|
701
|
+
return result;
|
|
702
|
+
}
|
|
703
|
+
async function importIntoClientTarget(context, target, runtime, sourcePath, options) {
|
|
704
|
+
const batches = await loadImportDocumentBatches(context.cwd, sourcePath, { ...options.table ? { table: options.table } : {} });
|
|
705
|
+
const hub = await requireHubConnection(context);
|
|
706
|
+
try {
|
|
707
|
+
const results = [];
|
|
708
|
+
for (const batch of batches) {
|
|
709
|
+
let importedCount = 0;
|
|
710
|
+
for (const row of batch.rows) {
|
|
711
|
+
const payload = { ...row };
|
|
712
|
+
delete payload._id;
|
|
713
|
+
delete payload._creationTime;
|
|
714
|
+
const result = await hub.sendCommand(runtime.runtimeId, {
|
|
715
|
+
kind: "data.insert",
|
|
716
|
+
table: batch.table,
|
|
717
|
+
document: payload
|
|
718
|
+
});
|
|
719
|
+
if (result.kind !== "data.mutate.result" || !result.success) {
|
|
720
|
+
const message = result.kind === "data.mutate.result" ? result.error ?? `Failed to import into ${batch.table}.` : `Unexpected response while importing into ${batch.table}.`;
|
|
721
|
+
context.fail(message, 1, result);
|
|
722
|
+
}
|
|
723
|
+
importedCount += 1;
|
|
724
|
+
}
|
|
725
|
+
results.push({
|
|
726
|
+
table: batch.table,
|
|
727
|
+
importedCount
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
return results;
|
|
731
|
+
} finally {
|
|
732
|
+
await hub.dispose();
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
async function exportClientTargetData(context, target, runtime, options) {
|
|
736
|
+
const hub = await requireHubConnection(context);
|
|
737
|
+
try {
|
|
738
|
+
const tables = options.table ? [options.table] : (await listRemoteTables(runtime.runtimeId, context)).map((entry) => entry.name);
|
|
739
|
+
const payloads = await Promise.all(tables.map(async (table) => ({
|
|
740
|
+
table,
|
|
741
|
+
rows: (await readRemoteTable(hub, runtime.runtimeId, table, { limit: Number.MAX_SAFE_INTEGER })).rows
|
|
742
|
+
})));
|
|
743
|
+
return await writeExportData(path.resolve(context.cwd, options.path), payloads);
|
|
744
|
+
} finally {
|
|
745
|
+
await hub.dispose();
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
async function runLogsCommand(context, options) {
|
|
749
|
+
const availableTargets = await listAvailableTargets(context.cwd);
|
|
750
|
+
const runtimeLookup = buildRuntimeLookup(availableTargets);
|
|
751
|
+
const selectedTarget = options.target && options.target !== "all" ? availableTargets.find((target) => target.id === options.target) : void 0;
|
|
752
|
+
if (options.target && options.target !== "all" && !selectedTarget) context.fail(`Unknown target ${JSON.stringify(options.target)}. Available targets: ${availableTargets.map((target) => target.id).join(", ")}`);
|
|
753
|
+
const selectedRuntime = selectedTarget ? resolveClientRuntime(selectedTarget, options.runtime, { command: "logs" }) : null;
|
|
754
|
+
const allowedRuntimeIds = selectedTarget?.kind === "client" ? new Set(selectedTarget.runtimeIds) : void 0;
|
|
755
|
+
renderOutput(context, (await readPersistedLogs(context.cwd)).map((entry) => decoratePersistedLogEntry(entry, runtimeLookup)).filter((entry) => options.target && options.target !== "all" ? entry.targetId === options.target || (allowedRuntimeIds ? allowedRuntimeIds.has(entry.runtimeId) : false) : true).filter((entry) => selectedRuntime ? entry.runtimeId === selectedRuntime.runtimeId : true).filter((entry) => options.kind ? entry.category === options.kind : true).slice(-Number.parseInt(options.limit, 10)), options.format);
|
|
756
|
+
if (!options.watch) return;
|
|
757
|
+
const hub = await requireHubConnection(context);
|
|
758
|
+
const unsubscribe = hub.onEvent((event) => {
|
|
759
|
+
const entry = normalizeRuntimeEvent(event, runtimeLookup.get(event.runtimeId));
|
|
760
|
+
if (!entry) return;
|
|
761
|
+
if (options.target && options.target !== "all" && entry.targetId !== options.target && !(allowedRuntimeIds ? allowedRuntimeIds.has(entry.runtimeId) : false)) return;
|
|
762
|
+
if (options.kind && entry.category !== options.kind) return;
|
|
763
|
+
if (selectedRuntime && entry.runtimeId !== selectedRuntime.runtimeId) return;
|
|
764
|
+
renderOutput(context, entry, options.format);
|
|
765
|
+
});
|
|
766
|
+
context.info("Streaming logs. Press Ctrl+C to stop.");
|
|
767
|
+
await waitForSignal();
|
|
768
|
+
unsubscribe();
|
|
769
|
+
await hub.dispose();
|
|
770
|
+
}
|
|
771
|
+
async function readPersistedLogs(cwd) {
|
|
772
|
+
const logPath = path.join(cwd, ".syncore", "logs", "runtime.jsonl");
|
|
773
|
+
try {
|
|
774
|
+
await stat(logPath);
|
|
775
|
+
} catch {
|
|
776
|
+
return [];
|
|
777
|
+
}
|
|
778
|
+
return (await readFile(logPath, "utf8")).split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => JSON.parse(line)).filter((entry) => entry.version === 2).filter((entry) => !shouldSuppressLogEntry(entry));
|
|
779
|
+
}
|
|
780
|
+
function normalizeRuntimeEvent(event, runtimeEntry) {
|
|
781
|
+
const functionName = typeof event.functionName === "string" ? event.functionName : "unknown";
|
|
782
|
+
const logMessage = typeof event.message === "string" ? event.message : "Syncore log";
|
|
783
|
+
const resolvedTargetId = event.runtimeId === "syncore-dev-hub" ? "all" : runtimeEntry?.targetId ?? event.runtimeId;
|
|
784
|
+
const runtimeLabel = event.runtimeId === "syncore-dev-hub" ? "dashboard" : runtimeEntry?.label ?? "runtime";
|
|
785
|
+
const publicRuntimeId = event.runtimeId === "syncore-dev-hub" ? void 0 : runtimeEntry?.id ?? createPublicRuntimeId(event.runtimeId);
|
|
786
|
+
const targetLabel = runtimeEntry?.targetLabel;
|
|
787
|
+
const origin = event.origin === "dashboard" || event.runtimeId === "syncore-dev-hub" ? "dashboard" : "runtime";
|
|
788
|
+
const entryBase = {
|
|
789
|
+
timestamp: event.timestamp,
|
|
790
|
+
runtimeId: event.runtimeId,
|
|
791
|
+
targetId: resolvedTargetId,
|
|
792
|
+
...targetLabel ? { targetLabel } : {},
|
|
793
|
+
...publicRuntimeId ? { publicRuntimeId } : {},
|
|
794
|
+
...runtimeLabel ? { runtimeLabel } : {},
|
|
795
|
+
origin
|
|
796
|
+
};
|
|
797
|
+
if (event.type === "log" && shouldSuppressLogEntry({
|
|
798
|
+
...entryBase,
|
|
799
|
+
eventType: event.type,
|
|
800
|
+
category: "system",
|
|
801
|
+
message: logMessage,
|
|
802
|
+
event
|
|
803
|
+
})) return null;
|
|
804
|
+
switch (event.type) {
|
|
805
|
+
case "query.executed": return {
|
|
806
|
+
...entryBase,
|
|
807
|
+
eventType: event.type,
|
|
808
|
+
category: "query",
|
|
809
|
+
message: `${functionName} executed`,
|
|
810
|
+
event
|
|
811
|
+
};
|
|
812
|
+
case "query.invalidated": return {
|
|
813
|
+
...entryBase,
|
|
814
|
+
eventType: event.type,
|
|
815
|
+
category: "system",
|
|
816
|
+
message: `${formatInvalidatedQueryId(event.queryId)} invalidated${typeof event.reason === "string" ? ` (${event.reason})` : ""}`,
|
|
817
|
+
event
|
|
818
|
+
};
|
|
819
|
+
case "mutation.committed": return {
|
|
820
|
+
...entryBase,
|
|
821
|
+
eventType: event.type,
|
|
822
|
+
category: "mutation",
|
|
823
|
+
message: Array.isArray(event.changedTables) && event.changedTables.length > 0 ? `${functionName} committed (${event.changedTables.join(", ")})` : `${functionName} committed`,
|
|
824
|
+
event
|
|
825
|
+
};
|
|
826
|
+
case "action.completed": return {
|
|
827
|
+
...entryBase,
|
|
828
|
+
eventType: event.type,
|
|
829
|
+
category: "action",
|
|
830
|
+
message: typeof event.error === "string" && event.error.length > 0 ? `${functionName} failed: ${event.error}` : `${functionName} completed`,
|
|
831
|
+
event
|
|
832
|
+
};
|
|
833
|
+
case "runtime.connected": return {
|
|
834
|
+
...entryBase,
|
|
835
|
+
eventType: event.type,
|
|
836
|
+
category: "system",
|
|
837
|
+
message: `${publicRuntimeId ?? "runtime"} ${runtimeLabel} connected`,
|
|
838
|
+
event
|
|
839
|
+
};
|
|
840
|
+
case "runtime.disconnected": return {
|
|
841
|
+
...entryBase,
|
|
842
|
+
eventType: event.type,
|
|
843
|
+
category: "system",
|
|
844
|
+
message: `${publicRuntimeId ?? "runtime"} ${runtimeLabel} disconnected`,
|
|
845
|
+
event
|
|
846
|
+
};
|
|
847
|
+
case "storage.updated": return {
|
|
848
|
+
...entryBase,
|
|
849
|
+
eventType: event.type,
|
|
850
|
+
category: "system",
|
|
851
|
+
message: `${typeof event.operation === "string" ? event.operation : "update"} ${typeof event.storageId === "string" ? event.storageId : "storage"}`,
|
|
852
|
+
event
|
|
853
|
+
};
|
|
854
|
+
default: return {
|
|
855
|
+
...entryBase,
|
|
856
|
+
eventType: event.type,
|
|
857
|
+
category: "system",
|
|
858
|
+
message: event.type === "log" ? logMessage : humanizeRuntimeEvent(event),
|
|
859
|
+
event
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
function decoratePersistedLogEntry(entry, runtimeLookup) {
|
|
864
|
+
const runtime = runtimeLookup.get(entry.runtimeId);
|
|
865
|
+
return {
|
|
866
|
+
...entry,
|
|
867
|
+
targetId: entry.targetId === "all" ? "all" : runtime?.targetId ?? entry.targetId ?? entry.runtimeId,
|
|
868
|
+
...runtime?.targetLabel ? { targetLabel: runtime.targetLabel } : {},
|
|
869
|
+
...runtime?.id ? { publicRuntimeId: runtime.id } : {},
|
|
870
|
+
...runtime?.label ? { runtimeLabel: runtime.label } : {},
|
|
871
|
+
...entry.origin ? {} : { origin: entry.runtimeId === "syncore-dev-hub" ? "dashboard" : "runtime" }
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
function shouldSuppressLogEntry(entry) {
|
|
875
|
+
return entry.eventType === "log" && /syncore devtools hub is alive/i.test(entry.message);
|
|
876
|
+
}
|
|
877
|
+
function formatInvalidatedQueryId(queryId) {
|
|
878
|
+
if (typeof queryId !== "string" || queryId.length === 0) return "query";
|
|
879
|
+
const separatorIndex = queryId.indexOf(":");
|
|
880
|
+
if (separatorIndex === -1) return queryId;
|
|
881
|
+
return queryId.slice(0, separatorIndex);
|
|
882
|
+
}
|
|
883
|
+
function humanizeRuntimeEvent(event) {
|
|
884
|
+
if (event.type === "scheduler.tick" && Array.isArray(event.executedJobIds)) return `scheduler tick (${event.executedJobIds.length} job(s))`;
|
|
885
|
+
return event.type.replaceAll(".", " ");
|
|
886
|
+
}
|
|
887
|
+
async function subscribeOnce(hub, runtimeId, payload) {
|
|
888
|
+
return await new Promise((resolve, reject) => {
|
|
889
|
+
const unsubscribe = hub.subscribe(runtimeId, payload, {
|
|
890
|
+
onData(result) {
|
|
891
|
+
unsubscribe();
|
|
892
|
+
resolve(result);
|
|
893
|
+
},
|
|
894
|
+
onError(error) {
|
|
895
|
+
unsubscribe();
|
|
896
|
+
reject(new Error(error));
|
|
897
|
+
}
|
|
898
|
+
});
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
async function promptForTemplate(context, detectedTemplate) {
|
|
902
|
+
const choices = VALID_SYNCORE_TEMPLATES.map((template) => template === detectedTemplate ? {
|
|
903
|
+
label: template,
|
|
904
|
+
value: template,
|
|
905
|
+
description: "Detected from the current project"
|
|
906
|
+
} : {
|
|
907
|
+
label: template,
|
|
908
|
+
value: template
|
|
909
|
+
});
|
|
910
|
+
return await context.select("Choose a Syncore template for this directory.", choices, isKnownTemplate(detectedTemplate) ? detectedTemplate : VALID_SYNCORE_TEMPLATES[0]);
|
|
911
|
+
}
|
|
912
|
+
async function isDirectoryEmpty(directory) {
|
|
913
|
+
try {
|
|
914
|
+
return (await readdir(directory)).length === 0;
|
|
915
|
+
} catch {
|
|
916
|
+
return true;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
function printScaffoldChanges(context, result) {
|
|
920
|
+
if (result.created.length > 0) context.info(`Created: ${result.created.join(", ")}`);
|
|
921
|
+
if (result.updated.length > 0) context.info(`Updated: ${result.updated.join(", ")}`);
|
|
922
|
+
if (result.skipped.length > 0) context.warn(`Kept existing: ${result.skipped.join(", ")}`);
|
|
923
|
+
}
|
|
924
|
+
function isTargetCapability(value) {
|
|
925
|
+
return value === "run" || value === "readData" || value === "writeData" || value === "exportData" || value === "streamLogs";
|
|
926
|
+
}
|
|
927
|
+
async function previewImportPlan(context, sourcePath, options, targetId) {
|
|
928
|
+
const batches = await loadImportDocumentBatches(context.cwd, sourcePath, { ...options.table ? { table: options.table } : {} });
|
|
929
|
+
const preview = {
|
|
930
|
+
target: targetId,
|
|
931
|
+
format: path.extname(sourcePath).toLowerCase() || "directory",
|
|
932
|
+
totalRows: batches.reduce((sum, batch) => sum + batch.rows.length, 0),
|
|
933
|
+
batches: batches.map((batch) => ({
|
|
934
|
+
table: batch.table,
|
|
935
|
+
rowCount: batch.rows.length
|
|
936
|
+
}))
|
|
937
|
+
};
|
|
938
|
+
if (!context.json && context.interactive) {
|
|
939
|
+
process.stdout.write("Import preview:\n");
|
|
940
|
+
process.stdout.write(` target: ${preview.target}\n`);
|
|
941
|
+
process.stdout.write(` source: ${sourcePath}\n`);
|
|
942
|
+
process.stdout.write(` format: ${preview.format}\n`);
|
|
943
|
+
for (const batch of preview.batches) process.stdout.write(` - ${batch.table}: ${batch.rowCount} row(s)\n`);
|
|
944
|
+
}
|
|
945
|
+
return preview;
|
|
946
|
+
}
|
|
947
|
+
function parseJsonObject(input, label) {
|
|
948
|
+
let parsed;
|
|
949
|
+
try {
|
|
950
|
+
parsed = JSON.parse(input);
|
|
951
|
+
} catch (error) {
|
|
952
|
+
throw new Error(`${label} must be valid JSON: ${formatError(error)}`);
|
|
953
|
+
}
|
|
954
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`${label} must be a JSON object.`);
|
|
955
|
+
return parsed;
|
|
956
|
+
}
|
|
957
|
+
async function waitForSignal() {
|
|
958
|
+
await new Promise((resolve) => {
|
|
959
|
+
const onSignal = () => {
|
|
960
|
+
process.off("SIGINT", onSignal);
|
|
961
|
+
process.off("SIGTERM", onSignal);
|
|
962
|
+
resolve();
|
|
963
|
+
};
|
|
964
|
+
process.on("SIGINT", onSignal);
|
|
965
|
+
process.on("SIGTERM", onSignal);
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
function parseGlobalOptionsFromArgv(argv) {
|
|
969
|
+
const parsed = {};
|
|
970
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
971
|
+
const value = argv[index];
|
|
972
|
+
if (value === "--cwd") {
|
|
973
|
+
const nextValue = argv[index + 1];
|
|
974
|
+
if (nextValue) parsed.cwd = nextValue;
|
|
975
|
+
index += 1;
|
|
976
|
+
continue;
|
|
977
|
+
}
|
|
978
|
+
if (value === "--json") {
|
|
979
|
+
parsed.json = true;
|
|
980
|
+
continue;
|
|
981
|
+
}
|
|
982
|
+
if (value === "--verbose") {
|
|
983
|
+
parsed.verbose = true;
|
|
984
|
+
continue;
|
|
985
|
+
}
|
|
986
|
+
if (value === "--no-interactive") {
|
|
987
|
+
parsed.interactive = false;
|
|
988
|
+
continue;
|
|
989
|
+
}
|
|
990
|
+
if (value === "--yes" || value === "-y") parsed.yes = true;
|
|
991
|
+
}
|
|
992
|
+
return parsed;
|
|
993
|
+
}
|
|
994
|
+
//#endregion
|
|
995
|
+
export { runSyncoreCli };
|
|
996
|
+
|
|
997
|
+
//# sourceMappingURL=app.mjs.map
|