@syncular/cli 0.0.0-108 → 0.0.0-114

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.
@@ -1,415 +0,0 @@
1
- import { spawn } from 'node:child_process';
2
- import { dirname } from 'node:path';
3
- import process from 'node:process';
4
- import type {
5
- SpaceDevLogEvent,
6
- SpaceDevLogger,
7
- } from '@syncular/cli-runtime-dev';
8
- import { Box, renderToString, Text } from 'ink';
9
- import type { ReactNode } from 'react';
10
- import {
11
- configureSyncTelemetry,
12
- getSyncTelemetry,
13
- type SyncTelemetry,
14
- type SyncTelemetryEvent,
15
- } from 'syncular/core';
16
-
17
- function writeStdout(node: ReactNode): void {
18
- const rendered = renderToString(node, {
19
- columns: Math.max(process.stdout.columns ?? 120, 120),
20
- });
21
- process.stdout.write(rendered);
22
- if (!rendered.endsWith('\n')) {
23
- process.stdout.write('\n');
24
- }
25
- }
26
-
27
- function writeStderr(node: ReactNode): void {
28
- const rendered = renderToString(node, {
29
- columns: Math.max(process.stderr.columns ?? 120, 120),
30
- });
31
- process.stderr.write(rendered);
32
- if (!rendered.endsWith('\n')) {
33
- process.stderr.write('\n');
34
- }
35
- }
36
-
37
- function nowStamp(): string {
38
- return new Date().toISOString().slice(11, 19);
39
- }
40
-
41
- function clearTerminalScreen(): void {
42
- if (!process.stdout.isTTY) {
43
- return;
44
- }
45
- process.stdout.write('\x1b[2J\x1b[H');
46
- }
47
-
48
- async function openInBrowser(url: string): Promise<void> {
49
- await new Promise<void>((resolvePromise, rejectPromise) => {
50
- let command = '';
51
- let commandArgs: string[] = [];
52
-
53
- if (process.platform === 'darwin') {
54
- command = 'open';
55
- commandArgs = [url];
56
- } else if (process.platform === 'win32') {
57
- command = 'cmd';
58
- commandArgs = ['/c', 'start', '', url];
59
- } else {
60
- command = 'xdg-open';
61
- commandArgs = [url];
62
- }
63
-
64
- const child = spawn(command, commandArgs, {
65
- stdio: 'ignore',
66
- detached: true,
67
- });
68
-
69
- child.once('error', rejectPromise);
70
- child.once('spawn', () => {
71
- child.unref();
72
- resolvePromise();
73
- });
74
- });
75
- }
76
-
77
- function levelColor(
78
- level: 'info' | 'warn' | 'error'
79
- ): 'cyan' | 'yellow' | 'redBright' {
80
- if (level === 'error') return 'redBright';
81
- if (level === 'warn') return 'yellow';
82
- return 'cyan';
83
- }
84
-
85
- function detectLevelFromSyncEvent(
86
- event: SyncTelemetryEvent
87
- ): 'info' | 'warn' | 'error' {
88
- const level = event.level ?? (event.error ? 'error' : 'info');
89
- if (level === 'warn') return 'warn';
90
- if (level === 'error' || level === 'fatal') return 'error';
91
- return 'info';
92
- }
93
-
94
- function summarizeSyncEvent(event: SyncTelemetryEvent): string {
95
- if (event.event === 'sync.push') {
96
- const status = typeof event.status === 'string' ? event.status : 'unknown';
97
- const operationCount =
98
- typeof event.operationCount === 'number' ? event.operationCount : 0;
99
- const commitSeq =
100
- typeof event.commitSeq === 'number' ? ` commit#${event.commitSeq}` : '';
101
- const duration =
102
- typeof event.durationMs === 'number' ? ` ${event.durationMs}ms` : '';
103
- return `push ${status} ops=${operationCount}${commitSeq}${duration}`;
104
- }
105
-
106
- if (event.event === 'sync.pull') {
107
- const subscriptionCount =
108
- typeof event.subscriptionCount === 'number' ? event.subscriptionCount : 0;
109
- const cursor =
110
- typeof event.clientCursor === 'number'
111
- ? ` cursor=${event.clientCursor}`
112
- : '';
113
- const duration =
114
- typeof event.durationMs === 'number' ? ` ${event.durationMs}ms` : '';
115
- return `pull subs=${subscriptionCount}${cursor}${duration}`;
116
- }
117
-
118
- if (event.event === 'sync.realtime.connect') {
119
- return 'realtime connected';
120
- }
121
-
122
- if (event.event === 'sync.realtime.disconnect') {
123
- return 'realtime disconnected';
124
- }
125
-
126
- if (event.event === 'sync.realtime.rejected') {
127
- const reason =
128
- typeof event.reason === 'string' ? ` reason=${event.reason}` : '';
129
- return `realtime rejected${reason}`;
130
- }
131
-
132
- if (event.event === 'sync.exception') {
133
- return `exception ${String(event.error ?? 'unknown error')}`;
134
- }
135
-
136
- const duration =
137
- typeof event.durationMs === 'number' ? ` ${event.durationMs}ms` : '';
138
- return `${event.event}${duration}`;
139
- }
140
-
141
- function printSyncEventLine(args: {
142
- level: 'info' | 'warn' | 'error';
143
- eventName: string;
144
- summary: string;
145
- actorId?: string;
146
- }): void {
147
- const tag = args.level.toUpperCase();
148
- const color = levelColor(args.level);
149
- writeStdout(
150
- <Box>
151
- <Text dimColor>[{nowStamp()}] </Text>
152
- <Text color={color} bold>
153
- {tag.padEnd(5)}
154
- </Text>
155
- <Text color="magentaBright"> sync </Text>
156
- <Text color="gray">{args.eventName} </Text>
157
- <Text>{args.summary}</Text>
158
- {args.actorId ? <Text dimColor> actor={args.actorId}</Text> : null}
159
- </Box>
160
- );
161
- }
162
-
163
- function summarizeDevEvent(event: SpaceDevLogEvent): string {
164
- if (event.type === 'reloading') {
165
- return event.reason ?? 'source change';
166
- }
167
-
168
- if (event.type === 'watching') {
169
- return event.watchRoot ?? '(watch root)';
170
- }
171
-
172
- if (event.type === 'startup_failed' || event.type === 'restart_failed') {
173
- const detail = event.error ?? event.message;
174
- const firstLine = detail.split('\n')[0];
175
- return firstLine ? firstLine.trim() : 'runtime error';
176
- }
177
-
178
- if (event.type === 'starting') {
179
- return `${event.reason ?? 'start'} -> ${event.host ?? '127.0.0.1'}:${String(event.port ?? 0)}`;
180
- }
181
-
182
- if (event.type === 'stopping') {
183
- return event.url ?? 'runtime server';
184
- }
185
-
186
- if (event.type === 'stopped') {
187
- return event.url ?? 'runtime server';
188
- }
189
-
190
- return event.message;
191
- }
192
-
193
- function renderDatabaseDetails(event: SpaceDevLogEvent): ReactNode {
194
- if (!event.provider) {
195
- return <Text color="gray">database: unknown</Text>;
196
- }
197
-
198
- if (event.provider === 'sqlite') {
199
- return (
200
- <Text color="gray">
201
- database: sqlite path={event.databaseDetails ?? '(unknown)'}
202
- </Text>
203
- );
204
- }
205
-
206
- return (
207
- <Text color="gray">
208
- database: {event.provider}{' '}
209
- {event.databaseDetails ? `(${event.databaseDetails})` : ''}
210
- </Text>
211
- );
212
- }
213
-
214
- function renderStartedPanel(event: SpaceDevLogEvent): void {
215
- const projectPath =
216
- event.cwd || (event.modulePath ? dirname(event.modulePath) : '(unknown)');
217
- const listenUrl =
218
- event.url ||
219
- `http://${event.host ?? '127.0.0.1'}:${String(event.port ?? 0)}`;
220
- const consoleUrl = event.consoleUrl ?? `${listenUrl}/console`;
221
-
222
- writeStdout(
223
- <Box
224
- flexDirection="column"
225
- borderStyle="round"
226
- borderColor="greenBright"
227
- paddingX={1}
228
- paddingY={0}
229
- >
230
- <Text color="greenBright" bold>
231
- syncular dev started
232
- </Text>
233
- <Text color="gray">project: {projectPath}</Text>
234
- <Text color="gray">module: {event.module ?? '(unknown)'}</Text>
235
- <Text color="gray">listen: {listenUrl}</Text>
236
- <Text color="gray">console: {consoleUrl}</Text>
237
- {renderDatabaseDetails(event)}
238
- </Box>
239
- );
240
- }
241
-
242
- function printDevEvent(event: SpaceDevLogEvent): void {
243
- if (event.type === 'started') {
244
- clearTerminalScreen();
245
- renderStartedPanel(event);
246
- return;
247
- }
248
-
249
- const color = levelColor(event.level);
250
- const tag = event.level.toUpperCase();
251
- writeStdout(
252
- <Box flexDirection="column">
253
- <Box>
254
- <Text dimColor>[{nowStamp()}] </Text>
255
- <Text color={color} bold>
256
- {tag.padEnd(5)}
257
- </Text>
258
- <Text color="cyanBright"> dev </Text>
259
- <Text color="gray">{event.type} </Text>
260
- <Text>{summarizeDevEvent(event)}</Text>
261
- </Box>
262
- </Box>
263
- );
264
- }
265
-
266
- export function createSpacesDevLogger(options?: {
267
- openConsoleOnStarted?: boolean;
268
- }): SpaceDevLogger {
269
- let consoleOpened = false;
270
- return {
271
- info(message) {
272
- printDevEvent({
273
- level: 'info',
274
- type: 'starting',
275
- message,
276
- });
277
- },
278
- error(message) {
279
- writeStderr(
280
- <Box>
281
- <Text dimColor>[{nowStamp()}] </Text>
282
- <Text color="redBright" bold>
283
- ERROR
284
- </Text>
285
- <Text color="redBright"> dev </Text>
286
- <Text>{message}</Text>
287
- </Box>
288
- );
289
- },
290
- event(event) {
291
- printDevEvent(event);
292
- if (options?.openConsoleOnStarted && event.type === 'started') {
293
- if (consoleOpened) {
294
- return;
295
- }
296
-
297
- consoleOpened = true;
298
- const listenUrl =
299
- event.url ||
300
- `http://${event.host ?? '127.0.0.1'}:${String(event.port ?? 0)}`;
301
- const consoleUrl = event.consoleUrl ?? `${listenUrl}/console`;
302
-
303
- void openInBrowser(consoleUrl).catch((error: unknown) => {
304
- const message =
305
- error instanceof Error ? error.message : String(error);
306
- writeStderr(
307
- <Box>
308
- <Text dimColor>[{nowStamp()}] </Text>
309
- <Text color="yellow">WARN </Text>
310
- <Text color="yellow">dev </Text>
311
- <Text>unable to open browser automatically: {message}</Text>
312
- </Box>
313
- );
314
- writeStderr(
315
- <Box>
316
- <Text dimColor>[{nowStamp()}] </Text>
317
- <Text color="yellow">WARN </Text>
318
- <Text color="yellow">dev </Text>
319
- <Text>open manually: {consoleUrl}</Text>
320
- </Box>
321
- );
322
- });
323
- }
324
- },
325
- };
326
- }
327
-
328
- export function configureSpacesDevTelemetry(): () => void {
329
- const previousTelemetry = getSyncTelemetry();
330
- let lastPullCursor: number | null = null;
331
- let lastPullLogAt = 0;
332
- let suppressedPullCount = 0;
333
-
334
- const telemetry: SyncTelemetry = {
335
- log(event) {
336
- if (event.event === 'console.stats') {
337
- return;
338
- }
339
-
340
- if (event.event === 'sync.pull') {
341
- const cursor =
342
- typeof event.clientCursor === 'number' ? event.clientCursor : null;
343
- const now = Date.now();
344
- const isRepetitive =
345
- cursor !== null &&
346
- cursor === lastPullCursor &&
347
- now - lastPullLogAt < 2_000;
348
-
349
- if (isRepetitive) {
350
- suppressedPullCount += 1;
351
- return;
352
- }
353
-
354
- lastPullCursor = cursor;
355
- lastPullLogAt = now;
356
- }
357
-
358
- const summary = summarizeSyncEvent(event);
359
- const level = detectLevelFromSyncEvent(event);
360
- const actorId =
361
- typeof event.userId === 'string' && event.userId.length > 0
362
- ? event.userId
363
- : undefined;
364
- const eventName = event.event;
365
-
366
- if (suppressedPullCount > 0 && event.event !== 'sync.pull') {
367
- printSyncEventLine({
368
- level: 'info',
369
- eventName: 'sync.pull',
370
- summary: `suppressed ${suppressedPullCount} repetitive pull logs`,
371
- });
372
- suppressedPullCount = 0;
373
- }
374
-
375
- printSyncEventLine({
376
- level,
377
- eventName,
378
- summary,
379
- actorId,
380
- });
381
- },
382
- tracer: previousTelemetry.tracer,
383
- metrics: previousTelemetry.metrics,
384
- captureException(error, context) {
385
- const message =
386
- error instanceof Error
387
- ? error.message
388
- : `Unknown error: ${String(error)}`;
389
- writeStderr(
390
- <Box flexDirection="column">
391
- <Box>
392
- <Text dimColor>[{nowStamp()}] </Text>
393
- <Text color="redBright" bold>
394
- ERROR
395
- </Text>
396
- <Text color="magentaBright"> sync </Text>
397
- <Text>{message}</Text>
398
- </Box>
399
- {context ? (
400
- <Box paddingLeft={16}>
401
- <Text color="gray">{JSON.stringify(context)}</Text>
402
- </Box>
403
- ) : null}
404
- </Box>
405
- );
406
- previousTelemetry.captureException(error, context);
407
- },
408
- };
409
-
410
- configureSyncTelemetry(telemetry);
411
-
412
- return () => {
413
- configureSyncTelemetry(previousTelemetry);
414
- };
415
- }
@@ -1 +0,0 @@
1
- export * from './manifest';
@@ -1,37 +0,0 @@
1
- import { contractWorkerBuildpack } from '@syncular/cli-buildpack-contract-worker';
2
- import type { ContractBuildpack } from '@syncular/cli-core';
3
- import { spacesAppTemplate } from '@syncular/cli-template-spaces-app';
4
- import { spacesDemoTemplate } from '@syncular/cli-template-spaces-demo';
5
- import { syncularDemoTemplate } from '@syncular/cli-template-syncular-demo';
6
- import { syncularLibrariesTemplate } from '@syncular/cli-template-syncular-libraries';
7
-
8
- interface BuildpackExtensionManifestEntry {
9
- packageName: string;
10
- buildpack: ContractBuildpack;
11
- }
12
-
13
- export const TEMPLATE_EXTENSION_MANIFEST = {
14
- spacesApp: {
15
- packageName: '@syncular/cli-template-spaces-app',
16
- template: spacesAppTemplate,
17
- },
18
- spacesDemo: {
19
- packageName: '@syncular/cli-template-spaces-demo',
20
- template: spacesDemoTemplate,
21
- },
22
- syncularDemo: {
23
- packageName: '@syncular/cli-template-syncular-demo',
24
- template: syncularDemoTemplate,
25
- },
26
- syncularLibraries: {
27
- packageName: '@syncular/cli-template-syncular-libraries',
28
- template: syncularLibrariesTemplate,
29
- },
30
- } as const;
31
-
32
- export const BUILDPACK_EXTENSION_MANIFEST = {
33
- contractWorker: {
34
- packageName: '@syncular/cli-buildpack-contract-worker',
35
- buildpack: contractWorkerBuildpack,
36
- },
37
- } as const satisfies Record<string, BuildpackExtensionManifestEntry>;
package/src/flags.ts DELETED
@@ -1,92 +0,0 @@
1
- const TRUE_FLAG_VALUES = new Set(['1', 'true', 'yes', 'on']);
2
- const FALSE_FLAG_VALUES = new Set(['0', 'false', 'no', 'off']);
3
-
4
- export function parseBooleanInput(value: string): boolean | null {
5
- const normalized = value.trim().toLowerCase();
6
- if (TRUE_FLAG_VALUES.has(normalized)) {
7
- return true;
8
- }
9
- if (FALSE_FLAG_VALUES.has(normalized)) {
10
- return false;
11
- }
12
- return null;
13
- }
14
-
15
- export function optionalFlag(
16
- flagValues: Map<string, string>,
17
- flag: string,
18
- fallback: string
19
- ): string {
20
- const value = flagValues.get(flag)?.trim();
21
- return value && value.length > 0 ? value : fallback;
22
- }
23
-
24
- export function optionalNullableFlag(
25
- flagValues: Map<string, string>,
26
- flag: string
27
- ): string | null {
28
- const value = flagValues.get(flag)?.trim();
29
- return value && value.length > 0 ? value : null;
30
- }
31
-
32
- export function optionalBooleanFlag(
33
- flagValues: Map<string, string>,
34
- flag: string,
35
- fallback: boolean
36
- ): boolean {
37
- const value = flagValues.get(flag);
38
- if (!value) {
39
- return fallback;
40
- }
41
-
42
- const parsed = parseBooleanInput(value);
43
- if (parsed !== null) {
44
- return parsed;
45
- }
46
-
47
- throw new Error(
48
- `Invalid ${flag} value "${value}". Use true/false, yes/no, on/off, or 1/0.`
49
- );
50
- }
51
-
52
- export function optionalIntegerFlag(
53
- flagValues: Map<string, string>,
54
- flag: string,
55
- fallback: number,
56
- options?: { min?: number }
57
- ): number {
58
- const raw = flagValues.get(flag)?.trim();
59
- if (!raw) {
60
- return fallback;
61
- }
62
-
63
- const parsed = Number.parseInt(raw, 10);
64
- const minimum = options?.min ?? 1;
65
- if (!Number.isFinite(parsed) || parsed < minimum) {
66
- throw new Error(`Invalid ${flag} value: ${raw}`);
67
- }
68
-
69
- return parsed;
70
- }
71
-
72
- export function optionalEnumFlag<T extends string>(
73
- flagValues: Map<string, string>,
74
- flag: string,
75
- allowed: readonly T[],
76
- fallback: T
77
- ): T {
78
- const rawValue = flagValues.get(flag)?.trim();
79
- if (!rawValue) {
80
- return fallback;
81
- }
82
-
83
- for (const candidate of allowed) {
84
- if (candidate === rawValue) {
85
- return candidate;
86
- }
87
- }
88
-
89
- throw new Error(
90
- `Invalid ${flag} value "${rawValue}". Use: ${allowed.join(', ')}`
91
- );
92
- }