syncular 0.0.6-95 → 0.1.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/README.md +21 -7
- package/dist/cli.js +473 -0
- package/package.json +12 -297
- package/dist/client-plugin-encryption.d.ts +0 -2
- package/dist/client-plugin-encryption.d.ts.map +0 -1
- package/dist/client-plugin-encryption.js +0 -2
- package/dist/client-plugin-encryption.js.map +0 -1
- package/dist/client-react.d.ts +0 -2
- package/dist/client-react.d.ts.map +0 -1
- package/dist/client-react.js +0 -2
- package/dist/client-react.js.map +0 -1
- package/dist/client.d.ts +0 -2
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js +0 -2
- package/dist/client.js.map +0 -1
- package/dist/console-server.d.ts +0 -2
- package/dist/console-server.d.ts.map +0 -1
- package/dist/console-server.js +0 -2
- package/dist/console-server.js.map +0 -1
- package/dist/console.d.ts +0 -2
- package/dist/console.d.ts.map +0 -1
- package/dist/console.js +0 -2
- package/dist/console.js.map +0 -1
- package/dist/core.d.ts +0 -2
- package/dist/core.d.ts.map +0 -1
- package/dist/core.js +0 -2
- package/dist/core.js.map +0 -1
- package/dist/dialect-better-sqlite3.d.ts +0 -2
- package/dist/dialect-better-sqlite3.d.ts.map +0 -1
- package/dist/dialect-better-sqlite3.js +0 -2
- package/dist/dialect-better-sqlite3.js.map +0 -1
- package/dist/dialect-bun-sqlite.d.ts +0 -2
- package/dist/dialect-bun-sqlite.d.ts.map +0 -1
- package/dist/dialect-bun-sqlite.js +0 -2
- package/dist/dialect-bun-sqlite.js.map +0 -1
- package/dist/dialect-d1.d.ts +0 -2
- package/dist/dialect-d1.d.ts.map +0 -1
- package/dist/dialect-d1.js +0 -2
- package/dist/dialect-d1.js.map +0 -1
- package/dist/dialect-electron-sqlite.d.ts +0 -2
- package/dist/dialect-electron-sqlite.d.ts.map +0 -1
- package/dist/dialect-electron-sqlite.js +0 -2
- package/dist/dialect-electron-sqlite.js.map +0 -1
- package/dist/dialect-expo-sqlite.d.ts +0 -2
- package/dist/dialect-expo-sqlite.d.ts.map +0 -1
- package/dist/dialect-expo-sqlite.js +0 -2
- package/dist/dialect-expo-sqlite.js.map +0 -1
- package/dist/dialect-libsql.d.ts +0 -2
- package/dist/dialect-libsql.d.ts.map +0 -1
- package/dist/dialect-libsql.js +0 -2
- package/dist/dialect-libsql.js.map +0 -1
- package/dist/dialect-neon.d.ts +0 -2
- package/dist/dialect-neon.d.ts.map +0 -1
- package/dist/dialect-neon.js +0 -2
- package/dist/dialect-neon.js.map +0 -1
- package/dist/dialect-pglite.d.ts +0 -2
- package/dist/dialect-pglite.d.ts.map +0 -1
- package/dist/dialect-pglite.js +0 -2
- package/dist/dialect-pglite.js.map +0 -1
- package/dist/dialect-react-native-nitro-sqlite.d.ts +0 -2
- package/dist/dialect-react-native-nitro-sqlite.d.ts.map +0 -1
- package/dist/dialect-react-native-nitro-sqlite.js +0 -2
- package/dist/dialect-react-native-nitro-sqlite.js.map +0 -1
- package/dist/dialect-sqlite3.d.ts +0 -2
- package/dist/dialect-sqlite3.d.ts.map +0 -1
- package/dist/dialect-sqlite3.js +0 -2
- package/dist/dialect-sqlite3.js.map +0 -1
- package/dist/dialect-wa-sqlite.d.ts +0 -2
- package/dist/dialect-wa-sqlite.d.ts.map +0 -1
- package/dist/dialect-wa-sqlite.js +0 -2
- package/dist/dialect-wa-sqlite.js.map +0 -1
- package/dist/encryption.d.ts +0 -2
- package/dist/encryption.d.ts.map +0 -1
- package/dist/encryption.js +0 -2
- package/dist/encryption.js.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +0 -1
- package/dist/migrations.d.ts +0 -2
- package/dist/migrations.d.ts.map +0 -1
- package/dist/migrations.js +0 -2
- package/dist/migrations.js.map +0 -1
- package/dist/observability-sentry.d.ts +0 -2
- package/dist/observability-sentry.d.ts.map +0 -1
- package/dist/observability-sentry.js +0 -2
- package/dist/observability-sentry.js.map +0 -1
- package/dist/relay.d.ts +0 -2
- package/dist/relay.d.ts.map +0 -1
- package/dist/relay.js +0 -2
- package/dist/relay.js.map +0 -1
- package/dist/server-cloudflare.d.ts +0 -2
- package/dist/server-cloudflare.d.ts.map +0 -1
- package/dist/server-cloudflare.js +0 -3
- package/dist/server-cloudflare.js.map +0 -1
- package/dist/server-dialect-postgres.d.ts +0 -2
- package/dist/server-dialect-postgres.d.ts.map +0 -1
- package/dist/server-dialect-postgres.js +0 -2
- package/dist/server-dialect-postgres.js.map +0 -1
- package/dist/server-dialect-sqlite.d.ts +0 -2
- package/dist/server-dialect-sqlite.d.ts.map +0 -1
- package/dist/server-dialect-sqlite.js +0 -2
- package/dist/server-dialect-sqlite.js.map +0 -1
- package/dist/server-hono.d.ts +0 -2
- package/dist/server-hono.d.ts.map +0 -1
- package/dist/server-hono.js +0 -2
- package/dist/server-hono.js.map +0 -1
- package/dist/server-service-worker.d.ts +0 -2
- package/dist/server-service-worker.d.ts.map +0 -1
- package/dist/server-service-worker.js +0 -2
- package/dist/server-service-worker.js.map +0 -1
- package/dist/server-storage-filesystem.d.ts +0 -2
- package/dist/server-storage-filesystem.d.ts.map +0 -1
- package/dist/server-storage-filesystem.js +0 -2
- package/dist/server-storage-filesystem.js.map +0 -1
- package/dist/server-storage-s3.d.ts +0 -2
- package/dist/server-storage-s3.d.ts.map +0 -1
- package/dist/server-storage-s3.js +0 -2
- package/dist/server-storage-s3.js.map +0 -1
- package/dist/server.d.ts +0 -2
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js +0 -2
- package/dist/server.js.map +0 -1
- package/dist/testkit.d.ts +0 -2
- package/dist/testkit.d.ts.map +0 -1
- package/dist/testkit.js +0 -2
- package/dist/testkit.js.map +0 -1
- package/dist/transport-http.d.ts +0 -2
- package/dist/transport-http.d.ts.map +0 -1
- package/dist/transport-http.js +0 -2
- package/dist/transport-http.js.map +0 -1
- package/dist/transport-ws.d.ts +0 -2
- package/dist/transport-ws.d.ts.map +0 -1
- package/dist/transport-ws.js +0 -2
- package/dist/transport-ws.js.map +0 -1
- package/dist/typegen.d.ts +0 -2
- package/dist/typegen.d.ts.map +0 -1
- package/dist/typegen.js +0 -2
- package/dist/typegen.js.map +0 -1
- package/dist/ui-observable-universe.d.ts +0 -2
- package/dist/ui-observable-universe.d.ts.map +0 -1
- package/dist/ui-observable-universe.js +0 -2
- package/dist/ui-observable-universe.js.map +0 -1
- package/dist/ui.d.ts +0 -2
- package/dist/ui.d.ts.map +0 -1
- package/dist/ui.js +0 -2
- package/dist/ui.js.map +0 -1
- package/src/client-plugin-encryption.ts +0 -1
- package/src/client-react.ts +0 -1
- package/src/client.ts +0 -1
- package/src/console-server.ts +0 -1
- package/src/console-styles.css +0 -1
- package/src/console-styles.source.css +0 -1
- package/src/console.ts +0 -1
- package/src/core.ts +0 -1
- package/src/dialect-better-sqlite3.ts +0 -1
- package/src/dialect-bun-sqlite.ts +0 -1
- package/src/dialect-d1.ts +0 -1
- package/src/dialect-electron-sqlite.ts +0 -1
- package/src/dialect-expo-sqlite.ts +0 -1
- package/src/dialect-libsql.ts +0 -1
- package/src/dialect-neon.ts +0 -1
- package/src/dialect-pglite.ts +0 -1
- package/src/dialect-react-native-nitro-sqlite.ts +0 -1
- package/src/dialect-sqlite3.ts +0 -1
- package/src/dialect-wa-sqlite.ts +0 -1
- package/src/encryption.ts +0 -1
- package/src/index.ts +0 -1
- package/src/migrations.ts +0 -1
- package/src/observability-sentry.ts +0 -1
- package/src/relay.ts +0 -1
- package/src/server-cloudflare.ts +0 -2
- package/src/server-dialect-postgres.ts +0 -1
- package/src/server-dialect-sqlite.ts +0 -1
- package/src/server-hono.ts +0 -1
- package/src/server-service-worker.ts +0 -1
- package/src/server-storage-filesystem.ts +0 -1
- package/src/server-storage-s3.ts +0 -1
- package/src/server.ts +0 -1
- package/src/testkit.ts +0 -1
- package/src/transport-http.ts +0 -1
- package/src/transport-ws.ts +0 -1
- package/src/typegen.ts +0 -1
- package/src/ui-observable-universe.ts +0 -1
- package/src/ui-styles.css +0 -1
- package/src/ui.ts +0 -1
package/README.md
CHANGED
|
@@ -1,19 +1,34 @@
|
|
|
1
1
|
# syncular
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CLI for Syncular app code generation. The `syncular` package ships only the
|
|
4
|
+
`syncular` command; all runtime libraries live in the scoped `@syncular/*`
|
|
5
|
+
packages (for example `@syncular/client`, `@syncular/server`,
|
|
6
|
+
`@syncular/client/react`).
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
## Usage
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
Run the app-facing generator with your package runner — no install required:
|
|
8
11
|
|
|
9
12
|
```bash
|
|
10
|
-
|
|
13
|
+
npx syncular codegen install
|
|
14
|
+
npx syncular generate --manifest-dir .
|
|
15
|
+
npx syncular generate --manifest-dir . --check
|
|
11
16
|
```
|
|
12
17
|
|
|
18
|
+
`syncular generate` refreshes `generated/syncular.codegen.json` from
|
|
19
|
+
`syncular.app.ts` (via `syncular-typegen`) and then runs the Rust
|
|
20
|
+
`syncular-codegen` binary to generate language clients.
|
|
21
|
+
|
|
22
|
+
When `syncular.app.ts` is absent and `generated/syncular.codegen.json` does not
|
|
23
|
+
exist, the command initializes a starter config from migrations before
|
|
24
|
+
generating clients. `syncular generate` can also install the Rust generator on
|
|
25
|
+
demand when Cargo is available; `syncular codegen install` prewarms the same
|
|
26
|
+
tool cache explicitly.
|
|
27
|
+
|
|
13
28
|
## Documentation
|
|
14
29
|
|
|
15
|
-
- Quick start: https://syncular.dev/docs/
|
|
16
|
-
-
|
|
30
|
+
- Quick start: https://syncular.dev/docs/start/quick-start
|
|
31
|
+
- CLI reference: https://syncular.dev/docs/reference/cli/generate
|
|
17
32
|
|
|
18
33
|
## Links
|
|
19
34
|
|
|
@@ -21,4 +36,3 @@ npm install syncular
|
|
|
21
36
|
- Issues: https://github.com/syncular/syncular/issues
|
|
22
37
|
|
|
23
38
|
> Status: Alpha. APIs and storage layouts may change between releases.
|
|
24
|
-
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import {
|
|
6
|
+
accessSync,
|
|
7
|
+
constants,
|
|
8
|
+
existsSync,
|
|
9
|
+
mkdirSync,
|
|
10
|
+
readFileSync
|
|
11
|
+
} from "node:fs";
|
|
12
|
+
import { homedir } from "node:os";
|
|
13
|
+
import { delimiter, dirname, join, resolve, sep } from "node:path";
|
|
14
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
15
|
+
var DEFAULT_APP_FILE = "syncular.app.ts";
|
|
16
|
+
var DEFAULT_CODEGEN_CONFIG_FILE = "generated/syncular.codegen.json";
|
|
17
|
+
var SYNCULAR_CODEGEN_BIN = "syncular-codegen";
|
|
18
|
+
function usage() {
|
|
19
|
+
return `usage: syncular <command>
|
|
20
|
+
|
|
21
|
+
commands:
|
|
22
|
+
generate [--check] [--manifest-dir <path>] [--migrations-dir <path>] [--rust-output-dir <path>] [--app <path>]
|
|
23
|
+
codegen install [--version <version>] [--root <path>] [--force]
|
|
24
|
+
|
|
25
|
+
examples:
|
|
26
|
+
syncular generate
|
|
27
|
+
syncular generate --check
|
|
28
|
+
syncular generate --manifest-dir ./syncular-app --app ./syncular.app.ts
|
|
29
|
+
syncular codegen install
|
|
30
|
+
`;
|
|
31
|
+
}
|
|
32
|
+
function generateUsage() {
|
|
33
|
+
return `usage: syncular generate [--check] [--manifest-dir <path>] [--migrations-dir <path>] [--rust-output-dir <path>] [--app <path>]
|
|
34
|
+
|
|
35
|
+
Generates the Syncular app handoff and language clients in one app-facing command.
|
|
36
|
+
|
|
37
|
+
When <manifest-dir>/syncular.app.ts exists, or --app is provided, the typed
|
|
38
|
+
TypeScript app contract is used to refresh generated/syncular.codegen.json.
|
|
39
|
+
Rust-only apps can omit syncular.app.ts; when generated/syncular.codegen.json
|
|
40
|
+
is missing, syncular generate initializes it from migrations before generating
|
|
41
|
+
clients.
|
|
42
|
+
|
|
43
|
+
Use --check in CI to verify generated outputs are current without rewriting
|
|
44
|
+
files.
|
|
45
|
+
`;
|
|
46
|
+
}
|
|
47
|
+
function codegenInstallUsage() {
|
|
48
|
+
return `usage: syncular codegen install [--version <version>] [--root <path>] [--force]
|
|
49
|
+
|
|
50
|
+
Installs the Rust syncular-codegen binary with Cargo into Syncular's tool cache.
|
|
51
|
+
|
|
52
|
+
By default the installed crate version matches the installed syncular npm
|
|
53
|
+
package version. Use --root to install into a custom Cargo root, or set
|
|
54
|
+
SYNCULAR_CODEGEN_BIN to point syncular generate at a custom binary.
|
|
55
|
+
`;
|
|
56
|
+
}
|
|
57
|
+
function readOptionValue(argv, index, arg, name) {
|
|
58
|
+
if (arg === name) {
|
|
59
|
+
const value = argv[index + 1];
|
|
60
|
+
if (!value || value.startsWith("-")) {
|
|
61
|
+
throw new Error(`${name} requires a value`);
|
|
62
|
+
}
|
|
63
|
+
return { value, nextIndex: index + 1 };
|
|
64
|
+
}
|
|
65
|
+
const prefix = `${name}=`;
|
|
66
|
+
if (arg.startsWith(prefix)) {
|
|
67
|
+
const value = arg.slice(prefix.length);
|
|
68
|
+
if (value.length === 0) {
|
|
69
|
+
throw new Error(`${name} requires a value`);
|
|
70
|
+
}
|
|
71
|
+
return { value, nextIndex: index };
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
function parseSyncularCliArgs(argv) {
|
|
76
|
+
const [command, ...rest] = argv;
|
|
77
|
+
if (!command || command === "--help" || command === "-h" || command === "help") {
|
|
78
|
+
return { kind: "help" };
|
|
79
|
+
}
|
|
80
|
+
if (command === "codegen") {
|
|
81
|
+
const [subcommand, ...codegenArgs] = rest;
|
|
82
|
+
if (!subcommand || subcommand === "--help" || subcommand === "-h") {
|
|
83
|
+
return { kind: "help", topic: "codegen-install" };
|
|
84
|
+
}
|
|
85
|
+
if (subcommand !== "install") {
|
|
86
|
+
throw new Error(`Unknown syncular codegen command: ${subcommand}
|
|
87
|
+
|
|
88
|
+
${usage()}`);
|
|
89
|
+
}
|
|
90
|
+
if (codegenArgs.includes("--help") || codegenArgs.includes("-h")) {
|
|
91
|
+
return { kind: "help", topic: "codegen-install" };
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
kind: "codegen-install",
|
|
95
|
+
options: parseCodegenInstallArgs(codegenArgs)
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
if (command !== "generate") {
|
|
99
|
+
throw new Error(`Unknown syncular command: ${command}
|
|
100
|
+
|
|
101
|
+
${usage()}`);
|
|
102
|
+
}
|
|
103
|
+
const options = {
|
|
104
|
+
check: false,
|
|
105
|
+
manifestDir: "."
|
|
106
|
+
};
|
|
107
|
+
for (let index = 0;index < rest.length; index += 1) {
|
|
108
|
+
const arg = rest[index];
|
|
109
|
+
if (arg === "--help" || arg === "-h") {
|
|
110
|
+
return { kind: "help", topic: "generate" };
|
|
111
|
+
}
|
|
112
|
+
if (arg === "--check") {
|
|
113
|
+
options.check = true;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const manifestDir = readOptionValue(rest, index, arg, "--manifest-dir");
|
|
117
|
+
if (manifestDir) {
|
|
118
|
+
options.manifestDir = manifestDir.value;
|
|
119
|
+
index = manifestDir.nextIndex;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const migrationsDir = readOptionValue(rest, index, arg, "--migrations-dir");
|
|
123
|
+
if (migrationsDir) {
|
|
124
|
+
options.migrationsDir = migrationsDir.value;
|
|
125
|
+
index = migrationsDir.nextIndex;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const rustOutputDir = readOptionValue(rest, index, arg, "--rust-output-dir");
|
|
129
|
+
if (rustOutputDir) {
|
|
130
|
+
options.rustOutputDir = rustOutputDir.value;
|
|
131
|
+
index = rustOutputDir.nextIndex;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const app = readOptionValue(rest, index, arg, "--app");
|
|
135
|
+
if (app) {
|
|
136
|
+
options.app = app.value;
|
|
137
|
+
index = app.nextIndex;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
throw new Error(`Unknown syncular generate option: ${arg}
|
|
141
|
+
|
|
142
|
+
${generateUsage()}`);
|
|
143
|
+
}
|
|
144
|
+
return { kind: "generate", options };
|
|
145
|
+
}
|
|
146
|
+
function parseCodegenInstallArgs(args) {
|
|
147
|
+
const options = {
|
|
148
|
+
force: false
|
|
149
|
+
};
|
|
150
|
+
for (let index = 0;index < args.length; index += 1) {
|
|
151
|
+
const arg = args[index];
|
|
152
|
+
if (arg === "--force") {
|
|
153
|
+
options.force = true;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const version = readOptionValue(args, index, arg, "--version");
|
|
157
|
+
if (version) {
|
|
158
|
+
options.version = version.value;
|
|
159
|
+
index = version.nextIndex;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const root = readOptionValue(args, index, arg, "--root");
|
|
163
|
+
if (root) {
|
|
164
|
+
options.root = root.value;
|
|
165
|
+
index = root.nextIndex;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
throw new Error(`Unknown syncular codegen install option: ${arg}
|
|
169
|
+
|
|
170
|
+
${codegenInstallUsage()}`);
|
|
171
|
+
}
|
|
172
|
+
return options;
|
|
173
|
+
}
|
|
174
|
+
function resolveFrom(cwd, path) {
|
|
175
|
+
return resolve(cwd, path);
|
|
176
|
+
}
|
|
177
|
+
function buildGenerateSteps(options, context = {}) {
|
|
178
|
+
const cwd = context.cwd ?? process.cwd();
|
|
179
|
+
const env = context.env ?? process.env;
|
|
180
|
+
const fileExists = context.fileExists ?? existsSync;
|
|
181
|
+
const typegenBin = env.SYNCULAR_TYPEGEN_BIN ?? "syncular-typegen";
|
|
182
|
+
const codegenBin = env.SYNCULAR_CODEGEN_BIN ?? SYNCULAR_CODEGEN_BIN;
|
|
183
|
+
const manifestDir = resolveFrom(cwd, options.manifestDir);
|
|
184
|
+
const appPath = options.app ? resolveFrom(cwd, options.app) : resolveFrom(manifestDir, DEFAULT_APP_FILE);
|
|
185
|
+
const codegenConfigPath = resolveFrom(manifestDir, DEFAULT_CODEGEN_CONFIG_FILE);
|
|
186
|
+
const hasAppDefinition = options.app !== undefined || fileExists(appPath);
|
|
187
|
+
if (options.app !== undefined && !fileExists(appPath)) {
|
|
188
|
+
throw new Error(`Syncular app definition not found: ${appPath}. Create syncular.app.ts, pass the correct --app path, or omit --app for a Rust-only project that already has generated/syncular.codegen.json.`);
|
|
189
|
+
}
|
|
190
|
+
const steps = [];
|
|
191
|
+
if (hasAppDefinition) {
|
|
192
|
+
steps.push({
|
|
193
|
+
label: "Generate Syncular codegen config",
|
|
194
|
+
command: typegenBin,
|
|
195
|
+
args: [
|
|
196
|
+
"codegen-config",
|
|
197
|
+
"--app",
|
|
198
|
+
appPath,
|
|
199
|
+
"--out",
|
|
200
|
+
codegenConfigPath,
|
|
201
|
+
...options.check ? ["--check"] : []
|
|
202
|
+
]
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
if (!hasAppDefinition && !fileExists(codegenConfigPath)) {
|
|
206
|
+
steps.push({
|
|
207
|
+
label: "Initialize Syncular codegen config",
|
|
208
|
+
command: codegenBin,
|
|
209
|
+
args: [
|
|
210
|
+
"init",
|
|
211
|
+
"--manifest-dir",
|
|
212
|
+
manifestDir,
|
|
213
|
+
...options.migrationsDir ? ["--migrations-dir", resolveFrom(cwd, options.migrationsDir)] : [],
|
|
214
|
+
...options.check ? ["--check"] : []
|
|
215
|
+
]
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
steps.push({
|
|
219
|
+
label: "Generate Syncular app clients",
|
|
220
|
+
command: codegenBin,
|
|
221
|
+
args: [
|
|
222
|
+
"--manifest-dir",
|
|
223
|
+
manifestDir,
|
|
224
|
+
...options.migrationsDir ? ["--migrations-dir", resolveFrom(cwd, options.migrationsDir)] : [],
|
|
225
|
+
...options.rustOutputDir ? ["--rust-output-dir", resolveFrom(cwd, options.rustOutputDir)] : [],
|
|
226
|
+
...options.check ? ["--check"] : []
|
|
227
|
+
]
|
|
228
|
+
});
|
|
229
|
+
return steps;
|
|
230
|
+
}
|
|
231
|
+
function readPackageVersion() {
|
|
232
|
+
try {
|
|
233
|
+
const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
234
|
+
const version = packageJson.version?.trim();
|
|
235
|
+
return version && version !== "0.0.0" ? version : undefined;
|
|
236
|
+
} catch {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function defaultCacheDir() {
|
|
241
|
+
if (process.env.SYNCULAR_CACHE_DIR) {
|
|
242
|
+
return process.env.SYNCULAR_CACHE_DIR;
|
|
243
|
+
}
|
|
244
|
+
if (process.platform === "win32" && process.env.LOCALAPPDATA) {
|
|
245
|
+
return join(process.env.LOCALAPPDATA, "Syncular");
|
|
246
|
+
}
|
|
247
|
+
if (process.platform === "darwin") {
|
|
248
|
+
return join(homedir(), "Library", "Caches", "syncular");
|
|
249
|
+
}
|
|
250
|
+
return join(process.env.XDG_CACHE_HOME ?? join(homedir(), ".cache"), "syncular");
|
|
251
|
+
}
|
|
252
|
+
function defaultCodegenVersion(version) {
|
|
253
|
+
const resolved = version?.trim() || process.env.SYNCULAR_CODEGEN_VERSION?.trim() || readPackageVersion();
|
|
254
|
+
return resolved && resolved !== "0.0.0" ? resolved : undefined;
|
|
255
|
+
}
|
|
256
|
+
function defaultCodegenInstallRoot(version) {
|
|
257
|
+
return join(defaultCacheDir(), "codegen", version ?? "latest");
|
|
258
|
+
}
|
|
259
|
+
function codegenBinaryPath(root) {
|
|
260
|
+
return join(root, "bin", process.platform === "win32" ? `${SYNCULAR_CODEGEN_BIN}.exe` : SYNCULAR_CODEGEN_BIN);
|
|
261
|
+
}
|
|
262
|
+
function buildCodegenInstallArgs(options) {
|
|
263
|
+
return [
|
|
264
|
+
"install",
|
|
265
|
+
SYNCULAR_CODEGEN_BIN,
|
|
266
|
+
...options.version ? ["--version", options.version] : [],
|
|
267
|
+
"--locked",
|
|
268
|
+
"--root",
|
|
269
|
+
options.root,
|
|
270
|
+
...options.force ? ["--force"] : []
|
|
271
|
+
];
|
|
272
|
+
}
|
|
273
|
+
function hasPathSeparator(command) {
|
|
274
|
+
return command.includes("/") || command.includes("\\");
|
|
275
|
+
}
|
|
276
|
+
function executableCandidates(command, env = process.env) {
|
|
277
|
+
if (hasPathSeparator(command)) {
|
|
278
|
+
return [command];
|
|
279
|
+
}
|
|
280
|
+
const pathDirs = (env.PATH ?? "").split(delimiter).filter(Boolean);
|
|
281
|
+
const extensions = process.platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";") : [""];
|
|
282
|
+
return pathDirs.flatMap((dir) => extensions.map((extension) => join(dir, `${command}${extension}`)));
|
|
283
|
+
}
|
|
284
|
+
function findExecutable(command, env = process.env) {
|
|
285
|
+
for (const candidate of executableCandidates(command, env)) {
|
|
286
|
+
try {
|
|
287
|
+
accessSync(candidate, constants.X_OK);
|
|
288
|
+
return candidate;
|
|
289
|
+
} catch {
|
|
290
|
+
if (process.platform === "win32" && existsSync(candidate)) {
|
|
291
|
+
return candidate;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
function localRepoCodegenManifest() {
|
|
298
|
+
const cliPath = fileURLToPath(import.meta.url);
|
|
299
|
+
let current = dirname(cliPath);
|
|
300
|
+
while (true) {
|
|
301
|
+
const cargoManifest = join(current, "rust/Cargo.toml");
|
|
302
|
+
const codegenManifest = join(current, "rust/crates/codegen/Cargo.toml");
|
|
303
|
+
const syncularPackageDir = join(current, "packages/syncular");
|
|
304
|
+
if (existsSync(cargoManifest) && existsSync(codegenManifest) && cliPath.startsWith(`${syncularPackageDir}${sep}`)) {
|
|
305
|
+
return cargoManifest;
|
|
306
|
+
}
|
|
307
|
+
const parent = dirname(current);
|
|
308
|
+
if (parent === current) {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
current = parent;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function codegenAutoInstallEnabled() {
|
|
315
|
+
const value = (process.env.SYNCULAR_CODEGEN_AUTO_INSTALL ?? "1").trim().toLowerCase();
|
|
316
|
+
return !["0", "false", "no", "off"].includes(value);
|
|
317
|
+
}
|
|
318
|
+
function missingCodegenMessage(version, root) {
|
|
319
|
+
const installCommand = version ? `npx syncular codegen install --version ${version}` : "npx syncular codegen install";
|
|
320
|
+
const cargoCommand = version ? `cargo install ${SYNCULAR_CODEGEN_BIN} --version ${version} --locked` : `cargo install ${SYNCULAR_CODEGEN_BIN} --locked`;
|
|
321
|
+
return [
|
|
322
|
+
`Required generator command not found: ${SYNCULAR_CODEGEN_BIN}.`,
|
|
323
|
+
`Run \`${installCommand}\` to install it into ${root},`,
|
|
324
|
+
`run \`${cargoCommand}\`,`,
|
|
325
|
+
`or set SYNCULAR_CODEGEN_BIN to an existing ${SYNCULAR_CODEGEN_BIN} binary.`
|
|
326
|
+
].join(" ");
|
|
327
|
+
}
|
|
328
|
+
async function runProcess(command, args) {
|
|
329
|
+
await new Promise((resolvePromise, reject) => {
|
|
330
|
+
const child = spawn(command, args, {
|
|
331
|
+
stdio: "inherit",
|
|
332
|
+
env: process.env
|
|
333
|
+
});
|
|
334
|
+
child.on("error", (error) => {
|
|
335
|
+
if (error.code === "ENOENT") {
|
|
336
|
+
reject(new Error(`Required generator command not found: ${command}. Install it and ensure it is on PATH before running syncular generate.`));
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
reject(error);
|
|
340
|
+
});
|
|
341
|
+
child.on("exit", (code) => {
|
|
342
|
+
if (code === 0) {
|
|
343
|
+
resolvePromise();
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
reject(new Error(`${command} exited with status ${code ?? "unknown"}`));
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
async function installSyncularCodegen(options) {
|
|
351
|
+
const cargo = findExecutable("cargo");
|
|
352
|
+
if (!cargo) {
|
|
353
|
+
throw new Error(missingCodegenMessage(options.version, options.root));
|
|
354
|
+
}
|
|
355
|
+
mkdirSync(options.root, { recursive: true });
|
|
356
|
+
const args = buildCodegenInstallArgs(options);
|
|
357
|
+
console.log(`[syncular] Installing ${SYNCULAR_CODEGEN_BIN}`);
|
|
358
|
+
console.log(`$ ${[cargo, ...args].join(" ")}`);
|
|
359
|
+
await runProcess(cargo, args);
|
|
360
|
+
const binary = codegenBinaryPath(options.root);
|
|
361
|
+
if (!findExecutable(binary)) {
|
|
362
|
+
throw new Error(`${SYNCULAR_CODEGEN_BIN} install completed but ${binary} is not executable`);
|
|
363
|
+
}
|
|
364
|
+
return binary;
|
|
365
|
+
}
|
|
366
|
+
async function resolveStep(step) {
|
|
367
|
+
const explicitCodegenBin = process.env.SYNCULAR_CODEGEN_BIN;
|
|
368
|
+
const isCodegenStep = step.command === SYNCULAR_CODEGEN_BIN || explicitCodegenBin !== undefined && step.command === explicitCodegenBin;
|
|
369
|
+
if (!isCodegenStep) {
|
|
370
|
+
return step;
|
|
371
|
+
}
|
|
372
|
+
if (explicitCodegenBin) {
|
|
373
|
+
const explicitBinary = findExecutable(explicitCodegenBin);
|
|
374
|
+
if (!explicitBinary) {
|
|
375
|
+
throw new Error(`SYNCULAR_CODEGEN_BIN points to a missing or non-executable command: ${explicitCodegenBin}`);
|
|
376
|
+
}
|
|
377
|
+
return { ...step, command: explicitBinary };
|
|
378
|
+
}
|
|
379
|
+
const repoManifest = localRepoCodegenManifest();
|
|
380
|
+
if (repoManifest) {
|
|
381
|
+
return {
|
|
382
|
+
...step,
|
|
383
|
+
command: "cargo",
|
|
384
|
+
args: [
|
|
385
|
+
"run",
|
|
386
|
+
"--quiet",
|
|
387
|
+
"--manifest-path",
|
|
388
|
+
repoManifest,
|
|
389
|
+
"-p",
|
|
390
|
+
SYNCULAR_CODEGEN_BIN,
|
|
391
|
+
"--",
|
|
392
|
+
...step.args
|
|
393
|
+
]
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
const version = defaultCodegenVersion();
|
|
397
|
+
const root = defaultCodegenInstallRoot(version);
|
|
398
|
+
const cachedBinary = codegenBinaryPath(root);
|
|
399
|
+
if (findExecutable(cachedBinary)) {
|
|
400
|
+
return { ...step, command: cachedBinary };
|
|
401
|
+
}
|
|
402
|
+
const pathBinary = findExecutable(SYNCULAR_CODEGEN_BIN);
|
|
403
|
+
if (pathBinary) {
|
|
404
|
+
return { ...step, command: pathBinary };
|
|
405
|
+
}
|
|
406
|
+
if (codegenAutoInstallEnabled() && findExecutable("cargo")) {
|
|
407
|
+
const installedBinary = await installSyncularCodegen({ version, root });
|
|
408
|
+
return { ...step, command: installedBinary };
|
|
409
|
+
}
|
|
410
|
+
throw new Error(missingCodegenMessage(version, root));
|
|
411
|
+
}
|
|
412
|
+
async function runStep(step) {
|
|
413
|
+
const resolvedStep = await resolveStep(step);
|
|
414
|
+
console.log(`[syncular] ${step.label}`);
|
|
415
|
+
console.log(`$ ${[resolvedStep.command, ...resolvedStep.args].join(" ")}`);
|
|
416
|
+
await runProcess(resolvedStep.command, resolvedStep.args);
|
|
417
|
+
}
|
|
418
|
+
async function runGenerateCommand(options) {
|
|
419
|
+
const steps = buildGenerateSteps(options);
|
|
420
|
+
for (const step of steps) {
|
|
421
|
+
await runStep(step);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
async function runCodegenInstallCommand(options) {
|
|
425
|
+
const version = defaultCodegenVersion(options.version);
|
|
426
|
+
const root = resolve(options.root ?? defaultCodegenInstallRoot(version));
|
|
427
|
+
const binary = await installSyncularCodegen({
|
|
428
|
+
version,
|
|
429
|
+
root,
|
|
430
|
+
force: options.force
|
|
431
|
+
});
|
|
432
|
+
console.log(`[syncular] ${SYNCULAR_CODEGEN_BIN} installed at ${binary}`);
|
|
433
|
+
}
|
|
434
|
+
async function runSyncularCli(argv = process.argv.slice(2)) {
|
|
435
|
+
try {
|
|
436
|
+
const parsed = parseSyncularCliArgs(argv);
|
|
437
|
+
if (parsed.kind === "help") {
|
|
438
|
+
if (parsed.topic === "generate") {
|
|
439
|
+
console.log(generateUsage());
|
|
440
|
+
} else if (parsed.topic === "codegen-install") {
|
|
441
|
+
console.log(codegenInstallUsage());
|
|
442
|
+
} else {
|
|
443
|
+
console.log(usage());
|
|
444
|
+
}
|
|
445
|
+
return 0;
|
|
446
|
+
}
|
|
447
|
+
if (parsed.kind === "generate") {
|
|
448
|
+
await runGenerateCommand(parsed.options);
|
|
449
|
+
} else {
|
|
450
|
+
await runCodegenInstallCommand(parsed.options);
|
|
451
|
+
}
|
|
452
|
+
return 0;
|
|
453
|
+
} catch (error) {
|
|
454
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
455
|
+
console.error(`[syncular] ${message}`);
|
|
456
|
+
return 1;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
function isMainModule() {
|
|
460
|
+
const entrypoint = process.argv[1];
|
|
461
|
+
return entrypoint !== undefined && import.meta.url === pathToFileURL(entrypoint).href;
|
|
462
|
+
}
|
|
463
|
+
if (isMainModule()) {
|
|
464
|
+
process.exitCode = await runSyncularCli();
|
|
465
|
+
}
|
|
466
|
+
export {
|
|
467
|
+
runSyncularCli,
|
|
468
|
+
runGenerateCommand,
|
|
469
|
+
runCodegenInstallCommand,
|
|
470
|
+
parseSyncularCliArgs,
|
|
471
|
+
buildGenerateSteps,
|
|
472
|
+
buildCodegenInstallArgs
|
|
473
|
+
};
|