@syncular/cli 0.0.0-44 → 0.0.0-48
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/npm/install.mjs +26 -0
- package/npm/run.mjs +32 -0
- package/npm/shared.mjs +271 -0
- package/package.json +14 -15
- package/src/args.ts +0 -112
- package/src/auth-storage.ts +0 -57
- package/src/buildpacks/index.ts +0 -2
- package/src/buildpacks/registry.ts +0 -47
- package/src/buildpacks/types.ts +0 -1
- package/src/command-registry.ts +0 -700
- package/src/commands/auth.ts +0 -514
- package/src/commands/build.ts +0 -251
- package/src/commands/console.ts +0 -288
- package/src/commands/demo.ts +0 -1154
- package/src/commands/doctor.tsx +0 -133
- package/src/commands/migrate.ts +0 -312
- package/src/commands/project.ts +0 -437
- package/src/commands/target.ts +0 -62
- package/src/commands/typegen.ts +0 -406
- package/src/constants.ts +0 -4
- package/src/control-plane.ts +0 -23
- package/src/dev-logging.tsx +0 -415
- package/src/extensions/index.ts +0 -1
- package/src/extensions/manifest.ts +0 -37
- package/src/help.tsx +0 -236
- package/src/index.ts +0 -4
- package/src/interactive.tsx +0 -306
- package/src/main.tsx +0 -257
- package/src/output.tsx +0 -47
- package/src/paths.ts +0 -11
- package/src/spaces-config.ts +0 -2
- package/src/targets/index.ts +0 -13
- package/src/targets/state.ts +0 -99
- package/src/targets/types.ts +0 -8
- package/src/templates/index.ts +0 -2
- package/src/templates/registry.ts +0 -42
- package/src/templates/syncular-types.ts +0 -10
- package/src/types.ts +0 -67
package/src/commands/typegen.ts
DELETED
|
@@ -1,406 +0,0 @@
|
|
|
1
|
-
import { dirname, resolve } from 'node:path';
|
|
2
|
-
import { pathToFileURL } from 'node:url';
|
|
3
|
-
import type { ColumnCodecSource } from 'syncular/core';
|
|
4
|
-
import type { DefinedMigrations } from 'syncular/migrations';
|
|
5
|
-
import {
|
|
6
|
-
type GenerateTypesOptions,
|
|
7
|
-
generateTypes,
|
|
8
|
-
type SyncularImportType,
|
|
9
|
-
type TypegenDialect,
|
|
10
|
-
} from 'syncular/typegen';
|
|
11
|
-
import { printError, printInfo } from '../output';
|
|
12
|
-
import { resolveDefaultSyncularConfigPath } from '../paths';
|
|
13
|
-
|
|
14
|
-
type GenericDb = Record<string, never>;
|
|
15
|
-
|
|
16
|
-
interface SyncConfigLike {
|
|
17
|
-
codecs?: ColumnCodecSource;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface TypegenConfigLike {
|
|
21
|
-
output?: string;
|
|
22
|
-
dialect?: TypegenDialect;
|
|
23
|
-
extendsSyncClientDb?: boolean;
|
|
24
|
-
includeVersionHistory?: boolean;
|
|
25
|
-
tables?: string[];
|
|
26
|
-
syncularImportType?: SyncularImportType;
|
|
27
|
-
codecs?: ColumnCodecSource;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
interface SyncularConfigLike {
|
|
31
|
-
migrations?: DefinedMigrations<GenericDb>;
|
|
32
|
-
sync?: SyncConfigLike;
|
|
33
|
-
typegen?: TypegenConfigLike;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
37
|
-
return typeof value === 'object' && value !== null;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function isColumnCodecSource(value: unknown): value is ColumnCodecSource {
|
|
41
|
-
return typeof value === 'function';
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function isDefinedMigrationsShape(
|
|
45
|
-
value: unknown
|
|
46
|
-
): value is DefinedMigrations<GenericDb> {
|
|
47
|
-
if (!isRecord(value)) {
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const currentVersion = value.currentVersion;
|
|
52
|
-
const migrations = value.migrations;
|
|
53
|
-
const getMigration = value.getMigration;
|
|
54
|
-
if (
|
|
55
|
-
typeof currentVersion !== 'number' ||
|
|
56
|
-
!Number.isInteger(currentVersion) ||
|
|
57
|
-
currentVersion < 0 ||
|
|
58
|
-
!Array.isArray(migrations) ||
|
|
59
|
-
typeof getMigration !== 'function'
|
|
60
|
-
) {
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return migrations.every((migration) => {
|
|
65
|
-
if (!isRecord(migration)) {
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
68
|
-
const version = migration.version;
|
|
69
|
-
const name = migration.name;
|
|
70
|
-
const up = migration.up;
|
|
71
|
-
const down = migration.down;
|
|
72
|
-
return (
|
|
73
|
-
typeof version === 'number' &&
|
|
74
|
-
Number.isInteger(version) &&
|
|
75
|
-
version > 0 &&
|
|
76
|
-
typeof name === 'string' &&
|
|
77
|
-
typeof up === 'function' &&
|
|
78
|
-
(down === undefined || typeof down === 'function')
|
|
79
|
-
);
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function isTypegenDialect(value: unknown): value is TypegenDialect {
|
|
84
|
-
return value === 'sqlite' || value === 'postgres';
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function isSyncularImportType(value: unknown): value is SyncularImportType {
|
|
88
|
-
if (value === 'scoped' || value === 'umbrella') {
|
|
89
|
-
return true;
|
|
90
|
-
}
|
|
91
|
-
if (!isRecord(value)) {
|
|
92
|
-
return false;
|
|
93
|
-
}
|
|
94
|
-
const clientValue = value.client;
|
|
95
|
-
if (typeof clientValue !== 'string' || clientValue.trim().length === 0) {
|
|
96
|
-
return false;
|
|
97
|
-
}
|
|
98
|
-
return Object.values(value).every(
|
|
99
|
-
(entry) => typeof entry === 'string' && entry.trim().length > 0
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function isStringArray(value: unknown): value is string[] {
|
|
104
|
-
return (
|
|
105
|
-
Array.isArray(value) &&
|
|
106
|
-
value.every((entry) => typeof entry === 'string' && entry.trim().length > 0)
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function isBoolean(value: unknown): value is boolean {
|
|
111
|
-
return typeof value === 'boolean';
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function parseBooleanFlag(
|
|
115
|
-
flagValues: Map<string, string>,
|
|
116
|
-
flag: string,
|
|
117
|
-
fallback: boolean | null
|
|
118
|
-
): boolean | null {
|
|
119
|
-
const raw = flagValues.get(flag);
|
|
120
|
-
if (!raw) {
|
|
121
|
-
return fallback;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const value = raw.trim().toLowerCase();
|
|
125
|
-
if (value === '1' || value === 'true' || value === 'yes' || value === 'on') {
|
|
126
|
-
return true;
|
|
127
|
-
}
|
|
128
|
-
if (value === '0' || value === 'false' || value === 'no' || value === 'off') {
|
|
129
|
-
return false;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
throw new Error(
|
|
133
|
-
`Invalid ${flag} value "${raw}". Use true/false, yes/no, on/off, or 1/0.`
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function parseCsvFlag(
|
|
138
|
-
flagValues: Map<string, string>,
|
|
139
|
-
flag: string
|
|
140
|
-
): string[] | null {
|
|
141
|
-
const raw = flagValues.get(flag)?.trim();
|
|
142
|
-
if (!raw) {
|
|
143
|
-
return null;
|
|
144
|
-
}
|
|
145
|
-
const values = raw
|
|
146
|
-
.split(',')
|
|
147
|
-
.map((entry) => entry.trim())
|
|
148
|
-
.filter((entry) => entry.length > 0);
|
|
149
|
-
if (values.length === 0) {
|
|
150
|
-
throw new Error(`Invalid ${flag}: expected a non-empty CSV list.`);
|
|
151
|
-
}
|
|
152
|
-
return values;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function parseConfigValue(
|
|
156
|
-
configPath: string,
|
|
157
|
-
value: unknown
|
|
158
|
-
): SyncularConfigLike {
|
|
159
|
-
if (!isRecord(value)) {
|
|
160
|
-
throw new Error(`Config default export must be an object (${configPath}).`);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const config: SyncularConfigLike = {};
|
|
164
|
-
|
|
165
|
-
const migrationsValue = value.migrations;
|
|
166
|
-
if (!isDefinedMigrationsShape(migrationsValue)) {
|
|
167
|
-
throw new Error(
|
|
168
|
-
`Config is missing a valid "migrations" export (${configPath}).`
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
config.migrations = migrationsValue;
|
|
172
|
-
|
|
173
|
-
const syncValue = value.sync;
|
|
174
|
-
if (syncValue !== undefined) {
|
|
175
|
-
if (!isRecord(syncValue)) {
|
|
176
|
-
throw new Error(`Config field "sync" must be an object (${configPath}).`);
|
|
177
|
-
}
|
|
178
|
-
const codecsValue = syncValue.codecs;
|
|
179
|
-
if (codecsValue !== undefined && !isColumnCodecSource(codecsValue)) {
|
|
180
|
-
throw new Error(
|
|
181
|
-
`Config field "sync.codecs" must be a function when provided (${configPath}).`
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
|
-
config.sync = {
|
|
185
|
-
codecs: codecsValue,
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const typegenValue = value.typegen;
|
|
190
|
-
if (typegenValue !== undefined) {
|
|
191
|
-
if (!isRecord(typegenValue)) {
|
|
192
|
-
throw new Error(
|
|
193
|
-
`Config field "typegen" must be an object when provided (${configPath}).`
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const output = typegenValue.output;
|
|
198
|
-
if (output !== undefined && typeof output !== 'string') {
|
|
199
|
-
throw new Error(
|
|
200
|
-
`Config field "typegen.output" must be a string (${configPath}).`
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const dialect = typegenValue.dialect;
|
|
205
|
-
if (dialect !== undefined && !isTypegenDialect(dialect)) {
|
|
206
|
-
throw new Error(
|
|
207
|
-
`Config field "typegen.dialect" must be sqlite or postgres (${configPath}).`
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const includeVersionHistory = typegenValue.includeVersionHistory;
|
|
212
|
-
if (
|
|
213
|
-
includeVersionHistory !== undefined &&
|
|
214
|
-
!isBoolean(includeVersionHistory)
|
|
215
|
-
) {
|
|
216
|
-
throw new Error(
|
|
217
|
-
`Config field "typegen.includeVersionHistory" must be boolean (${configPath}).`
|
|
218
|
-
);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const extendsSyncClientDb = typegenValue.extendsSyncClientDb;
|
|
222
|
-
if (extendsSyncClientDb !== undefined && !isBoolean(extendsSyncClientDb)) {
|
|
223
|
-
throw new Error(
|
|
224
|
-
`Config field "typegen.extendsSyncClientDb" must be boolean (${configPath}).`
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
const tables = typegenValue.tables;
|
|
229
|
-
if (tables !== undefined && !isStringArray(tables)) {
|
|
230
|
-
throw new Error(
|
|
231
|
-
`Config field "typegen.tables" must be a non-empty string array (${configPath}).`
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const syncularImportType = typegenValue.syncularImportType;
|
|
236
|
-
if (
|
|
237
|
-
syncularImportType !== undefined &&
|
|
238
|
-
!isSyncularImportType(syncularImportType)
|
|
239
|
-
) {
|
|
240
|
-
throw new Error(
|
|
241
|
-
`Config field "typegen.syncularImportType" is invalid (${configPath}).`
|
|
242
|
-
);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const codecs = typegenValue.codecs;
|
|
246
|
-
if (codecs !== undefined && !isColumnCodecSource(codecs)) {
|
|
247
|
-
throw new Error(
|
|
248
|
-
`Config field "typegen.codecs" must be a function (${configPath}).`
|
|
249
|
-
);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
config.typegen = {
|
|
253
|
-
output,
|
|
254
|
-
dialect,
|
|
255
|
-
includeVersionHistory,
|
|
256
|
-
extendsSyncClientDb,
|
|
257
|
-
tables,
|
|
258
|
-
syncularImportType,
|
|
259
|
-
codecs,
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
return config;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
async function loadSyncularConfig(
|
|
267
|
-
configPath: string
|
|
268
|
-
): Promise<SyncularConfigLike> {
|
|
269
|
-
const resolvedConfigPath = resolve(configPath);
|
|
270
|
-
const configUrl = new URL(pathToFileURL(resolvedConfigPath).href);
|
|
271
|
-
configUrl.searchParams.set('t', String(Date.now()));
|
|
272
|
-
|
|
273
|
-
let moduleValue: Record<string, unknown>;
|
|
274
|
-
try {
|
|
275
|
-
const imported = await import(configUrl.href);
|
|
276
|
-
if (!isRecord(imported)) {
|
|
277
|
-
throw new Error('Module did not load as an object');
|
|
278
|
-
}
|
|
279
|
-
moduleValue = imported;
|
|
280
|
-
} catch (error: unknown) {
|
|
281
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
282
|
-
throw new Error(
|
|
283
|
-
`Failed to import config module (${resolvedConfigPath}): ${message}`
|
|
284
|
-
);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
return parseConfigValue(resolvedConfigPath, moduleValue.default);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
function resolveConfigPath(flagValues: Map<string, string>): string {
|
|
291
|
-
const raw = flagValues.get('--config')?.trim();
|
|
292
|
-
if (raw && raw.length > 0) {
|
|
293
|
-
return resolve(process.cwd(), raw);
|
|
294
|
-
}
|
|
295
|
-
return resolveDefaultSyncularConfigPath(process.cwd());
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
function resolveDialect(args: {
|
|
299
|
-
flagValues: Map<string, string>;
|
|
300
|
-
config: SyncularConfigLike;
|
|
301
|
-
}): TypegenDialect {
|
|
302
|
-
const fromFlag = args.flagValues.get('--dialect')?.trim();
|
|
303
|
-
if (fromFlag) {
|
|
304
|
-
if (!isTypegenDialect(fromFlag)) {
|
|
305
|
-
throw new Error(
|
|
306
|
-
`Invalid --dialect value "${fromFlag}". Use sqlite or postgres.`
|
|
307
|
-
);
|
|
308
|
-
}
|
|
309
|
-
return fromFlag;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
return args.config.typegen?.dialect ?? 'sqlite';
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
function resolveOutputPath(args: {
|
|
316
|
-
flagValues: Map<string, string>;
|
|
317
|
-
configPath: string;
|
|
318
|
-
config: SyncularConfigLike;
|
|
319
|
-
}): string {
|
|
320
|
-
const fromFlag = args.flagValues.get('--out')?.trim();
|
|
321
|
-
const fromConfig = args.config.typegen?.output?.trim();
|
|
322
|
-
const rawOutput = fromFlag || fromConfig || './src/types.generated.ts';
|
|
323
|
-
return resolve(dirname(args.configPath), rawOutput);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
function resolveSyncularImportType(args: {
|
|
327
|
-
flagValues: Map<string, string>;
|
|
328
|
-
config: SyncularConfigLike;
|
|
329
|
-
}): SyncularImportType {
|
|
330
|
-
const fromFlag = args.flagValues.get('--syncular-import-type')?.trim();
|
|
331
|
-
if (fromFlag) {
|
|
332
|
-
if (fromFlag !== 'scoped' && fromFlag !== 'umbrella') {
|
|
333
|
-
throw new Error(
|
|
334
|
-
`Invalid --syncular-import-type "${fromFlag}". Use scoped or umbrella.`
|
|
335
|
-
);
|
|
336
|
-
}
|
|
337
|
-
return fromFlag;
|
|
338
|
-
}
|
|
339
|
-
return args.config.typegen?.syncularImportType ?? 'umbrella';
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
export async function runTypegen(
|
|
343
|
-
flagValues: Map<string, string>
|
|
344
|
-
): Promise<number> {
|
|
345
|
-
try {
|
|
346
|
-
const configPath = resolveConfigPath(flagValues);
|
|
347
|
-
const config = await loadSyncularConfig(configPath);
|
|
348
|
-
const migrations = config.migrations;
|
|
349
|
-
if (!migrations) {
|
|
350
|
-
throw new Error(
|
|
351
|
-
`Config is missing a valid "migrations" export (${configPath}).`
|
|
352
|
-
);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
const outputPath = resolveOutputPath({
|
|
356
|
-
flagValues,
|
|
357
|
-
configPath,
|
|
358
|
-
config,
|
|
359
|
-
});
|
|
360
|
-
const dialect = resolveDialect({ flagValues, config });
|
|
361
|
-
|
|
362
|
-
const configTables = config.typegen?.tables;
|
|
363
|
-
const flagTables = parseCsvFlag(flagValues, '--tables');
|
|
364
|
-
const tables = flagTables ?? configTables;
|
|
365
|
-
|
|
366
|
-
const includeVersionHistory = parseBooleanFlag(
|
|
367
|
-
flagValues,
|
|
368
|
-
'--include-version-history',
|
|
369
|
-
config.typegen?.includeVersionHistory ?? false
|
|
370
|
-
);
|
|
371
|
-
const extendsSyncClientDb = parseBooleanFlag(
|
|
372
|
-
flagValues,
|
|
373
|
-
'--extends-sync-client-db',
|
|
374
|
-
config.typegen?.extendsSyncClientDb ?? true
|
|
375
|
-
);
|
|
376
|
-
const syncularImportType = resolveSyncularImportType({
|
|
377
|
-
flagValues,
|
|
378
|
-
config,
|
|
379
|
-
});
|
|
380
|
-
const codecs = config.typegen?.codecs ?? config.sync?.codecs ?? undefined;
|
|
381
|
-
|
|
382
|
-
const options: GenerateTypesOptions<GenericDb> = {
|
|
383
|
-
migrations,
|
|
384
|
-
output: outputPath,
|
|
385
|
-
dialect,
|
|
386
|
-
extendsSyncClientDb: extendsSyncClientDb ?? true,
|
|
387
|
-
includeVersionHistory: includeVersionHistory ?? false,
|
|
388
|
-
syncularImportType,
|
|
389
|
-
tables,
|
|
390
|
-
codecs,
|
|
391
|
-
};
|
|
392
|
-
|
|
393
|
-
const result = await generateTypes(options);
|
|
394
|
-
printInfo('Type generation completed.');
|
|
395
|
-
console.log(`Config: ${configPath}`);
|
|
396
|
-
console.log(`Output: ${result.outputPath}`);
|
|
397
|
-
console.log(`Schema version: ${result.currentVersion}`);
|
|
398
|
-
console.log(`Tables: ${result.tableCount}`);
|
|
399
|
-
return 0;
|
|
400
|
-
} catch (error: unknown) {
|
|
401
|
-
printError(
|
|
402
|
-
error instanceof Error ? error.message : 'Type generation failed.'
|
|
403
|
-
);
|
|
404
|
-
return 1;
|
|
405
|
-
}
|
|
406
|
-
}
|
package/src/constants.ts
DELETED
package/src/control-plane.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
export function normalizeControlPlaneBaseUrl(raw: string): string {
|
|
2
|
-
const trimmed = raw.trim();
|
|
3
|
-
if (trimmed.length === 0) {
|
|
4
|
-
return trimmed;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
try {
|
|
8
|
-
const parsed = new URL(trimmed);
|
|
9
|
-
const normalizedPath = parsed.pathname.replace(/\/+$/, '');
|
|
10
|
-
if (normalizedPath === '' || normalizedPath === '/') {
|
|
11
|
-
parsed.pathname = '/api/control';
|
|
12
|
-
} else if (normalizedPath === '/api') {
|
|
13
|
-
parsed.pathname = '/api/control';
|
|
14
|
-
} else {
|
|
15
|
-
parsed.pathname = normalizedPath;
|
|
16
|
-
}
|
|
17
|
-
parsed.search = '';
|
|
18
|
-
parsed.hash = '';
|
|
19
|
-
return parsed.toString().replace(/\/$/, '');
|
|
20
|
-
} catch {
|
|
21
|
-
return trimmed.replace(/\/$/, '');
|
|
22
|
-
}
|
|
23
|
-
}
|