@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.
@@ -1,251 +0,0 @@
1
- import { existsSync } from 'node:fs';
2
- import { mkdir, writeFile } from 'node:fs/promises';
3
- import { dirname, resolve } from 'node:path';
4
- import process from 'node:process';
5
- import type { ContractBuildpack } from '../buildpacks';
6
- import { getBuildpackById, listBuildpackIds } from '../buildpacks';
7
- import { printError, printInfo } from '../output';
8
- import type { TargetId } from '../targets';
9
- import {
10
- resolveEffectiveTargetId,
11
- resolveTargetDefaultConfigPath,
12
- } from '../targets';
13
-
14
- function optionalFlag(
15
- flagValues: Map<string, string>,
16
- flag: string,
17
- fallback: string
18
- ): string {
19
- const value = flagValues.get(flag)?.trim();
20
- return value && value.length > 0 ? value : fallback;
21
- }
22
-
23
- function optionalBooleanFlag(
24
- flagValues: Map<string, string>,
25
- flag: string,
26
- fallback: boolean
27
- ): boolean {
28
- const value = flagValues.get(flag);
29
- if (!value) {
30
- return fallback;
31
- }
32
-
33
- const normalized = value.trim().toLowerCase();
34
- if (
35
- normalized === '1' ||
36
- normalized === 'true' ||
37
- normalized === 'yes' ||
38
- normalized === 'on'
39
- ) {
40
- return true;
41
- }
42
- if (
43
- normalized === '0' ||
44
- normalized === 'false' ||
45
- normalized === 'no' ||
46
- normalized === 'off'
47
- ) {
48
- return false;
49
- }
50
-
51
- throw new Error(
52
- `Invalid ${flag} value "${value}". Use true/false, yes/no, on/off, or 1/0.`
53
- );
54
- }
55
-
56
- function resolveBuildpack(args: {
57
- targetId: TargetId;
58
- flagValues: Map<string, string>;
59
- }): ContractBuildpack {
60
- const fallbackBuildpackId = 'contract-worker';
61
- const buildpackId = optionalFlag(
62
- args.flagValues,
63
- '--buildpack',
64
- fallbackBuildpackId
65
- );
66
-
67
- const buildpack = getBuildpackById(buildpackId);
68
- if (buildpack) {
69
- return buildpack;
70
- }
71
-
72
- throw new Error(
73
- `Unsupported --buildpack "${buildpackId}" for target "${args.targetId}". Supported buildpacks: ${listBuildpackIds().join(', ')}.`
74
- );
75
- }
76
-
77
- function resolveContractPaths(args: {
78
- targetId: TargetId;
79
- flagValues: Map<string, string>;
80
- }): {
81
- configPath: string;
82
- explicitEntry: string | null;
83
- } {
84
- const cwd = process.cwd();
85
- const explicitEntry = args.flagValues.get('--entry')?.trim() || null;
86
- const configPath = optionalFlag(
87
- args.flagValues,
88
- '--config',
89
- resolveTargetDefaultConfigPath({
90
- cwd,
91
- targetId: args.targetId,
92
- })
93
- );
94
-
95
- if (!explicitEntry && !existsSync(configPath)) {
96
- throw new Error(
97
- `Syncular config not found: ${configPath}. Provide --config or --entry.`
98
- );
99
- }
100
-
101
- return {
102
- configPath,
103
- explicitEntry,
104
- };
105
- }
106
-
107
- async function assertWritableOutputFile(args: {
108
- filePath: string;
109
- overwrite: boolean;
110
- }): Promise<void> {
111
- if (!args.overwrite && existsSync(args.filePath)) {
112
- throw new Error(
113
- `Refusing to overwrite existing file: ${args.filePath}. Use --force true to overwrite output files.`
114
- );
115
- }
116
- await mkdir(dirname(args.filePath), { recursive: true });
117
- }
118
-
119
- export async function runBuild(
120
- flagValues: Map<string, string>
121
- ): Promise<number> {
122
- try {
123
- const cwd = process.cwd();
124
- const targetResolution = await resolveEffectiveTargetId({
125
- cwd,
126
- explicitTargetId: flagValues.get('--target')?.trim() || null,
127
- });
128
- const targetId = targetResolution.targetId;
129
- const overwrite = optionalBooleanFlag(flagValues, '--force', false);
130
- const buildpack = resolveBuildpack({
131
- targetId,
132
- flagValues,
133
- });
134
- const { configPath, explicitEntry } = resolveContractPaths({
135
- targetId,
136
- flagValues,
137
- });
138
-
139
- const outPath = resolve(
140
- cwd,
141
- optionalFlag(
142
- flagValues,
143
- '--out',
144
- `.syncular/build/${targetId}/artifact.js`
145
- )
146
- );
147
- const manifestPath = resolve(
148
- cwd,
149
- optionalFlag(
150
- flagValues,
151
- '--manifest',
152
- `.syncular/build/${targetId}/manifest.json`
153
- )
154
- );
155
-
156
- await assertWritableOutputFile({
157
- filePath: outPath,
158
- overwrite,
159
- });
160
- await assertWritableOutputFile({
161
- filePath: manifestPath,
162
- overwrite,
163
- });
164
-
165
- const migrationMetadata = await buildpack.inspect({
166
- configPath,
167
- explicitEntry,
168
- });
169
- const manifest = {
170
- generatedAt: new Date().toISOString(),
171
- target: targetId,
172
- targetSource: targetResolution.source,
173
- buildpack: buildpack.id,
174
- schemaVersion: migrationMetadata.schemaVersion,
175
- migrationDigest: migrationMetadata.migrationDigest,
176
- migrationCount: migrationMetadata.migrationCount,
177
- ...(explicitEntry ? { entry: explicitEntry } : { configPath }),
178
- };
179
- const { source, artifactHash } = await buildpack.build({
180
- configPath,
181
- explicitEntry,
182
- commentTag: 'syncular-cli-build',
183
- metadata: manifest,
184
- });
185
-
186
- await writeFile(outPath, source, 'utf8');
187
- await writeFile(
188
- manifestPath,
189
- `${JSON.stringify(manifest, null, 2)}\n`,
190
- 'utf8'
191
- );
192
-
193
- printInfo('Build completed.');
194
- console.log(`Target: ${targetId}`);
195
- console.log(`Buildpack: ${buildpack.id}`);
196
- console.log(`Artifact: ${outPath}`);
197
- console.log(`Manifest: ${manifestPath}`);
198
- console.log(`Artifact hash: ${artifactHash}`);
199
- return 0;
200
- } catch (error: unknown) {
201
- printError(error instanceof Error ? error.message : 'Build failed.');
202
- return 1;
203
- }
204
- }
205
-
206
- export async function runEject(
207
- flagValues: Map<string, string>
208
- ): Promise<number> {
209
- try {
210
- const cwd = process.cwd();
211
- const targetResolution = await resolveEffectiveTargetId({
212
- cwd,
213
- explicitTargetId: flagValues.get('--target')?.trim() || null,
214
- });
215
- const targetId = targetResolution.targetId;
216
- const overwrite = optionalBooleanFlag(flagValues, '--force', false);
217
- const buildpack = resolveBuildpack({
218
- targetId,
219
- flagValues,
220
- });
221
- const { configPath, explicitEntry } = resolveContractPaths({
222
- targetId,
223
- flagValues,
224
- });
225
- const outDir = resolve(
226
- cwd,
227
- optionalFlag(flagValues, '--out-dir', `.syncular/eject/${targetId}`)
228
- );
229
-
230
- const result = await buildpack.eject({
231
- configPath,
232
- explicitEntry,
233
- targetId,
234
- outDir,
235
- overwrite,
236
- });
237
-
238
- printInfo('Eject completed.');
239
- console.log(`Target: ${targetId}`);
240
- console.log(`Buildpack: ${buildpack.id}`);
241
- console.log(`Output directory: ${outDir}`);
242
- console.log('Files:');
243
- for (const filePath of result.files) {
244
- console.log(`- ${filePath}`);
245
- }
246
- return 0;
247
- } catch (error: unknown) {
248
- printError(error instanceof Error ? error.message : 'Eject failed.');
249
- return 1;
250
- }
251
- }
@@ -1,288 +0,0 @@
1
- import { spawn } from 'node:child_process';
2
- import {
3
- createServer,
4
- type IncomingHttpHeaders,
5
- type IncomingMessage,
6
- type ServerResponse,
7
- } from 'node:http';
8
- import process from 'node:process';
9
- import { createConsoleStaticResponder } from 'syncular/console-server';
10
- import { printError } from '../output';
11
-
12
- const DEFAULT_HOST = '127.0.0.1';
13
- const DEFAULT_PORT = 4310;
14
-
15
- function optionalFlag(
16
- flagValues: Map<string, string>,
17
- flag: string
18
- ): string | null {
19
- const value = flagValues.get(flag)?.trim();
20
- return value && value.length > 0 ? value : null;
21
- }
22
-
23
- function optionalBooleanFlag(
24
- flagValues: Map<string, string>,
25
- flag: string,
26
- fallback: boolean
27
- ): boolean {
28
- const value = flagValues.get(flag);
29
- if (!value) {
30
- return fallback;
31
- }
32
-
33
- const normalized = value.trim().toLowerCase();
34
- if (
35
- normalized === '1' ||
36
- normalized === 'true' ||
37
- normalized === 'yes' ||
38
- normalized === 'on'
39
- ) {
40
- return true;
41
- }
42
- if (
43
- normalized === '0' ||
44
- normalized === 'false' ||
45
- normalized === 'no' ||
46
- normalized === 'off'
47
- ) {
48
- return false;
49
- }
50
- throw new Error(
51
- `Invalid ${flag} value "${value}". Use true/false, yes/no, on/off, or 1/0.`
52
- );
53
- }
54
-
55
- function parsePort(value: string | null): number | null {
56
- if (!value) {
57
- return DEFAULT_PORT;
58
- }
59
- const parsed = Number(value);
60
- if (!Number.isInteger(parsed) || parsed < 0 || parsed > 65535) {
61
- return null;
62
- }
63
- return parsed;
64
- }
65
-
66
- function resolveToken(flagValues: Map<string, string>): string | undefined {
67
- const directToken = optionalFlag(flagValues, '--token');
68
- if (directToken) {
69
- return directToken;
70
- }
71
-
72
- const tokenEnvName = optionalFlag(flagValues, '--token-env');
73
- if (!tokenEnvName) {
74
- return undefined;
75
- }
76
-
77
- const envToken = process.env[tokenEnvName]?.trim();
78
- if (!envToken) {
79
- throw new Error(
80
- `Environment variable ${tokenEnvName} is empty or undefined.`
81
- );
82
- }
83
-
84
- return envToken;
85
- }
86
-
87
- function normalizeHost(value: string | null): string {
88
- return value ?? DEFAULT_HOST;
89
- }
90
-
91
- function formatHostForUrl(host: string): string {
92
- return host.includes(':') ? `[${host}]` : host;
93
- }
94
-
95
- function browserHost(host: string): string {
96
- if (host === '0.0.0.0' || host === '::') {
97
- return 'localhost';
98
- }
99
- return host;
100
- }
101
-
102
- function toHeaders(headers: IncomingHttpHeaders): Headers {
103
- const result = new Headers();
104
- for (const [name, value] of Object.entries(headers)) {
105
- if (value === undefined) continue;
106
- if (Array.isArray(value)) {
107
- for (const entry of value) {
108
- result.append(name, entry);
109
- }
110
- continue;
111
- }
112
- result.set(name, value);
113
- }
114
- return result;
115
- }
116
-
117
- function toRequest(request: IncomingMessage): Request {
118
- const origin = `http://${request.headers.host ?? 'localhost'}`;
119
- const requestUrl = new URL(request.url ?? '/', origin);
120
- return new Request(requestUrl, {
121
- method: request.method ?? 'GET',
122
- headers: toHeaders(request.headers),
123
- });
124
- }
125
-
126
- async function writeFetchResponse(
127
- response: ServerResponse,
128
- fetchResponse: Response,
129
- method: string
130
- ): Promise<void> {
131
- response.statusCode = fetchResponse.status;
132
- for (const [name, value] of fetchResponse.headers.entries()) {
133
- response.setHeader(name, value);
134
- }
135
-
136
- if (method === 'HEAD' || fetchResponse.body === null) {
137
- response.end();
138
- return;
139
- }
140
-
141
- const body = Buffer.from(await fetchResponse.arrayBuffer());
142
- response.end(body);
143
- }
144
-
145
- async function openInBrowser(url: string): Promise<void> {
146
- await new Promise<void>((resolvePromise, rejectPromise) => {
147
- let command = '';
148
- let commandArgs: string[] = [];
149
-
150
- if (process.platform === 'darwin') {
151
- command = 'open';
152
- commandArgs = [url];
153
- } else if (process.platform === 'win32') {
154
- command = 'cmd';
155
- commandArgs = ['/c', 'start', '', url];
156
- } else {
157
- command = 'xdg-open';
158
- commandArgs = [url];
159
- }
160
-
161
- const child = spawn(command, commandArgs, {
162
- stdio: 'ignore',
163
- detached: true,
164
- });
165
- child.once('error', rejectPromise);
166
- child.once('spawn', () => {
167
- child.unref();
168
- resolvePromise();
169
- });
170
- });
171
- }
172
-
173
- export async function runConsole(
174
- flagValues: Map<string, string>
175
- ): Promise<number> {
176
- try {
177
- const host = normalizeHost(optionalFlag(flagValues, '--host'));
178
- const port = parsePort(optionalFlag(flagValues, '--port'));
179
- if (port === null) {
180
- throw new Error('Invalid --port value. Use an integer from 0 to 65535.');
181
- }
182
-
183
- const serverUrl = optionalFlag(flagValues, '--server-url');
184
- if (serverUrl) {
185
- try {
186
- new URL(serverUrl);
187
- } catch {
188
- throw new Error(`Invalid --server-url: ${serverUrl}`);
189
- }
190
- }
191
-
192
- const token = resolveToken(flagValues);
193
- const shouldOpen = optionalBooleanFlag(flagValues, '--open', false);
194
-
195
- const consoleStaticResponder = createConsoleStaticResponder({
196
- mountPath: '/',
197
- defaultPrefill: {
198
- basePath: '/',
199
- serverUrl: serverUrl ?? undefined,
200
- token,
201
- },
202
- });
203
-
204
- const server = createServer((request, response) => {
205
- const method = request.method ?? 'GET';
206
- if (method !== 'GET' && method !== 'HEAD') {
207
- response.statusCode = 405;
208
- response.end('Method Not Allowed');
209
- return;
210
- }
211
-
212
- void (async () => {
213
- const fetchRequest = toRequest(request);
214
- const fetchResponse = await consoleStaticResponder(fetchRequest);
215
- if (!fetchResponse) {
216
- response.statusCode = 404;
217
- response.end('Not Found');
218
- return;
219
- }
220
-
221
- await writeFetchResponse(response, fetchResponse, method);
222
- })().catch((error: unknown) => {
223
- const message = error instanceof Error ? error.message : String(error);
224
- printError(`Console request failed: ${message}`);
225
- if (!response.headersSent) {
226
- response.statusCode = 500;
227
- }
228
- response.end('Internal Server Error');
229
- });
230
- });
231
-
232
- await new Promise<void>((resolvePromise, rejectPromise) => {
233
- server.once('error', rejectPromise);
234
- server.listen(port, host, () => {
235
- server.off('error', rejectPromise);
236
- resolvePromise();
237
- });
238
- }).catch((error: unknown) => {
239
- const message = error instanceof Error ? error.message : String(error);
240
- throw new Error(`Failed to start console server: ${message}`);
241
- });
242
-
243
- const address = server.address();
244
- if (!address || typeof address === 'string') {
245
- throw new Error('Failed to resolve console server address.');
246
- }
247
-
248
- const localUrl = `http://${formatHostForUrl(host)}:${address.port}`;
249
- console.log(`Console running at ${localUrl}`);
250
- if (serverUrl) {
251
- console.log(`Using API server: ${serverUrl}`);
252
- } else {
253
- console.log(
254
- 'No API server configured. Set --server-url to preconfigure connection.'
255
- );
256
- }
257
- if (token) {
258
- console.log('Using preconfigured token.');
259
- }
260
-
261
- if (shouldOpen) {
262
- const openUrl = `http://${browserHost(host)}:${address.port}`;
263
- try {
264
- await openInBrowser(openUrl);
265
- } catch (error: unknown) {
266
- const message = error instanceof Error ? error.message : String(error);
267
- printError(`Unable to open browser automatically: ${message}`);
268
- console.log(`Open manually: ${openUrl}`);
269
- }
270
- }
271
-
272
- await new Promise<void>((resolvePromise) => {
273
- const shutdown = () => {
274
- process.off('SIGINT', shutdown);
275
- process.off('SIGTERM', shutdown);
276
- server.close(() => resolvePromise());
277
- };
278
-
279
- process.on('SIGINT', shutdown);
280
- process.on('SIGTERM', shutdown);
281
- });
282
-
283
- return 0;
284
- } catch (error: unknown) {
285
- printError(error instanceof Error ? error.message : 'Console failed.');
286
- return 1;
287
- }
288
- }