@syncular/cli 0.0.0-108 → 0.0.0-113
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/package.json +12 -15
- package/src/args.ts +0 -113
- 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 -685
- package/src/commands/auth.ts +0 -426
- package/src/commands/build.ts +0 -210
- package/src/commands/console.ts +0 -252
- package/src/commands/demo.ts +0 -1151
- package/src/commands/doctor.tsx +0 -133
- package/src/commands/migrate.ts +0 -271
- package/src/commands/project.ts +0 -381
- package/src/commands/target.ts +0 -62
- package/src/commands/typegen.ts +0 -404
- package/src/constants.ts +0 -7
- package/src/control-plane-token.ts +0 -46
- package/src/control-plane.ts +0 -64
- package/src/dev-logging.tsx +0 -415
- package/src/extensions/index.ts +0 -1
- package/src/extensions/manifest.ts +0 -37
- package/src/flags.ts +0 -92
- package/src/help.tsx +0 -239
- package/src/index.ts +0 -4
- package/src/interactive.tsx +0 -306
- package/src/main.tsx +0 -268
- 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/update-check.ts +0 -296
package/src/commands/auth.ts
DELETED
|
@@ -1,426 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import { randomUUID } from 'node:crypto';
|
|
3
|
-
import { createServer } from 'node:http';
|
|
4
|
-
import process from 'node:process';
|
|
5
|
-
import {
|
|
6
|
-
clearStoredControlPlaneToken,
|
|
7
|
-
writeStoredControlPlaneToken,
|
|
8
|
-
} from '../auth-storage';
|
|
9
|
-
import { CLI_NAME } from '../constants';
|
|
10
|
-
import { parseBearerToken, resolveControlPlaneBase } from '../control-plane';
|
|
11
|
-
import { resolveControlPlaneToken } from '../control-plane-token';
|
|
12
|
-
import {
|
|
13
|
-
optionalBooleanFlag,
|
|
14
|
-
optionalFlag,
|
|
15
|
-
optionalIntegerFlag,
|
|
16
|
-
} from '../flags';
|
|
17
|
-
import { printError, printInfo } from '../output';
|
|
18
|
-
|
|
19
|
-
interface AuthMeResponse {
|
|
20
|
-
actorId?: string;
|
|
21
|
-
source?: 'clerk' | 'local' | 'deploy_token';
|
|
22
|
-
clerkUserId?: string | null;
|
|
23
|
-
deployTokenId?: string | null;
|
|
24
|
-
deployTokenSpaceId?: string | null;
|
|
25
|
-
deployTokenScopes?: string[];
|
|
26
|
-
error?: string;
|
|
27
|
-
message?: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
interface WhoAmIOutput {
|
|
31
|
-
controlPlane: string;
|
|
32
|
-
actorId: string | null;
|
|
33
|
-
source: AuthMeResponse['source'] | null;
|
|
34
|
-
clerkUserId: string | null;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function defaultCliAuthUrl(controlPlaneBase: string): string {
|
|
38
|
-
return `${controlPlaneBase.replace(/\/$/, '')}/cli-login`;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function openBrowser(url: string): boolean {
|
|
42
|
-
try {
|
|
43
|
-
if (process.platform === 'darwin') {
|
|
44
|
-
const child = spawn('open', [url], {
|
|
45
|
-
detached: true,
|
|
46
|
-
stdio: 'ignore',
|
|
47
|
-
});
|
|
48
|
-
child.unref();
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (process.platform === 'win32') {
|
|
53
|
-
const child = spawn('cmd', ['/c', 'start', '', url], {
|
|
54
|
-
detached: true,
|
|
55
|
-
stdio: 'ignore',
|
|
56
|
-
windowsHide: true,
|
|
57
|
-
});
|
|
58
|
-
child.unref();
|
|
59
|
-
return true;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const child = spawn('xdg-open', [url], {
|
|
63
|
-
detached: true,
|
|
64
|
-
stdio: 'ignore',
|
|
65
|
-
});
|
|
66
|
-
child.unref();
|
|
67
|
-
return true;
|
|
68
|
-
} catch {
|
|
69
|
-
return false;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function isAllowedCallbackHost(hostname: string): boolean {
|
|
74
|
-
const normalized = hostname.trim().toLowerCase();
|
|
75
|
-
return normalized === '127.0.0.1' || normalized === 'localhost';
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async function waitForTokenViaBrowser(args: {
|
|
79
|
-
callbackHost: string;
|
|
80
|
-
callbackPort: number;
|
|
81
|
-
authUrl: string;
|
|
82
|
-
timeoutSeconds: number;
|
|
83
|
-
}): Promise<string> {
|
|
84
|
-
if (!isAllowedCallbackHost(args.callbackHost)) {
|
|
85
|
-
throw new Error(
|
|
86
|
-
`Invalid --callback-host "${args.callbackHost}". Use 127.0.0.1 or localhost.`
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const state = randomUUID();
|
|
91
|
-
let settled = false;
|
|
92
|
-
let resolveToken: ((token: string) => void) | null = null;
|
|
93
|
-
let rejectToken: ((error: Error) => void) | null = null;
|
|
94
|
-
|
|
95
|
-
const tokenPromise = new Promise<string>((resolve, reject) => {
|
|
96
|
-
resolveToken = resolve;
|
|
97
|
-
rejectToken = reject;
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
const server = createServer((req, res) => {
|
|
101
|
-
try {
|
|
102
|
-
const requestUrl = new URL(
|
|
103
|
-
req.url || '/',
|
|
104
|
-
`http://${args.callbackHost}:${listeningPort}`
|
|
105
|
-
);
|
|
106
|
-
if (requestUrl.pathname !== '/callback') {
|
|
107
|
-
res.statusCode = 404;
|
|
108
|
-
res.end('Not found');
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const responseHeaders = {
|
|
113
|
-
'Content-Type': 'text/html; charset=utf-8',
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
const callbackState = requestUrl.searchParams.get('state')?.trim() || '';
|
|
117
|
-
if (callbackState !== state) {
|
|
118
|
-
res.writeHead(400, responseHeaders);
|
|
119
|
-
res.end(
|
|
120
|
-
'<h2>CLI Login Failed</h2><p>Invalid state. Return to terminal.</p>'
|
|
121
|
-
);
|
|
122
|
-
if (!settled) {
|
|
123
|
-
settled = true;
|
|
124
|
-
rejectToken?.(new Error('CLI login state mismatch.'));
|
|
125
|
-
}
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const error = requestUrl.searchParams.get('error')?.trim();
|
|
130
|
-
if (error && error.length > 0) {
|
|
131
|
-
res.writeHead(400, responseHeaders);
|
|
132
|
-
res.end(
|
|
133
|
-
`<h2>CLI Login Failed</h2><p>${error}</p><p>Return to terminal.</p>`
|
|
134
|
-
);
|
|
135
|
-
if (!settled) {
|
|
136
|
-
settled = true;
|
|
137
|
-
rejectToken?.(new Error(`CLI login failed: ${error}`));
|
|
138
|
-
}
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const token = parseBearerToken(
|
|
143
|
-
requestUrl.searchParams.get('token')?.trim() || ''
|
|
144
|
-
);
|
|
145
|
-
if (token.length === 0) {
|
|
146
|
-
res.writeHead(400, responseHeaders);
|
|
147
|
-
res.end(
|
|
148
|
-
'<h2>CLI Login Failed</h2><p>Missing token in callback.</p><p>Return to terminal.</p>'
|
|
149
|
-
);
|
|
150
|
-
if (!settled) {
|
|
151
|
-
settled = true;
|
|
152
|
-
rejectToken?.(new Error('CLI login callback did not include token.'));
|
|
153
|
-
}
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
res.writeHead(200, responseHeaders);
|
|
158
|
-
res.end(
|
|
159
|
-
'<h2>CLI Login Complete</h2><p>You can close this tab and return to the terminal.</p>'
|
|
160
|
-
);
|
|
161
|
-
if (!settled) {
|
|
162
|
-
settled = true;
|
|
163
|
-
resolveToken?.(token);
|
|
164
|
-
}
|
|
165
|
-
} catch (error: unknown) {
|
|
166
|
-
res.statusCode = 500;
|
|
167
|
-
res.end('Unexpected callback error');
|
|
168
|
-
if (!settled) {
|
|
169
|
-
settled = true;
|
|
170
|
-
rejectToken?.(
|
|
171
|
-
error instanceof Error
|
|
172
|
-
? error
|
|
173
|
-
: new Error('Unexpected callback parsing error.')
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
let listeningPort = args.callbackPort;
|
|
180
|
-
await new Promise<void>((resolve, reject) => {
|
|
181
|
-
server.once('error', (error) =>
|
|
182
|
-
reject(error instanceof Error ? error : new Error(String(error)))
|
|
183
|
-
);
|
|
184
|
-
server.listen(args.callbackPort, args.callbackHost, () => {
|
|
185
|
-
const address = server.address();
|
|
186
|
-
if (!address || typeof address === 'string') {
|
|
187
|
-
reject(new Error('Failed to allocate callback server port.'));
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
listeningPort = address.port;
|
|
191
|
-
resolve();
|
|
192
|
-
});
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
const callbackUrl = `http://${args.callbackHost}:${listeningPort}/callback`;
|
|
196
|
-
const loginUrl = new URL(args.authUrl);
|
|
197
|
-
loginUrl.searchParams.set('callback', callbackUrl);
|
|
198
|
-
loginUrl.searchParams.set('state', state);
|
|
199
|
-
|
|
200
|
-
const opened = openBrowser(loginUrl.toString());
|
|
201
|
-
if (opened) {
|
|
202
|
-
printInfo(`Opening browser for CLI authentication: ${loginUrl}`);
|
|
203
|
-
} else {
|
|
204
|
-
printInfo('Failed to auto-open browser. Open this URL manually:');
|
|
205
|
-
console.log(loginUrl.toString());
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
try {
|
|
209
|
-
const timeoutMs = args.timeoutSeconds * 1000;
|
|
210
|
-
const timeoutPromise = new Promise<string>((_, reject) => {
|
|
211
|
-
setTimeout(() => {
|
|
212
|
-
reject(
|
|
213
|
-
new Error(
|
|
214
|
-
`Timed out waiting for browser login callback after ${args.timeoutSeconds}s.`
|
|
215
|
-
)
|
|
216
|
-
);
|
|
217
|
-
}, timeoutMs);
|
|
218
|
-
});
|
|
219
|
-
return await Promise.race([tokenPromise, timeoutPromise]);
|
|
220
|
-
} finally {
|
|
221
|
-
server.close();
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
async function fetchAuthMe(args: {
|
|
226
|
-
controlPlaneBase: string;
|
|
227
|
-
token: string;
|
|
228
|
-
}): Promise<{
|
|
229
|
-
ok: boolean;
|
|
230
|
-
status: number;
|
|
231
|
-
payload: AuthMeResponse | null;
|
|
232
|
-
}> {
|
|
233
|
-
const response = await fetch(
|
|
234
|
-
`${args.controlPlaneBase.replace(/\/$/, '')}/auth/me`,
|
|
235
|
-
{
|
|
236
|
-
method: 'GET',
|
|
237
|
-
headers: {
|
|
238
|
-
Authorization: `Bearer ${args.token}`,
|
|
239
|
-
},
|
|
240
|
-
}
|
|
241
|
-
);
|
|
242
|
-
|
|
243
|
-
let payload: AuthMeResponse | null = null;
|
|
244
|
-
try {
|
|
245
|
-
payload = (await response.json()) as AuthMeResponse;
|
|
246
|
-
} catch {
|
|
247
|
-
payload = null;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
return {
|
|
251
|
-
ok: response.ok,
|
|
252
|
-
status: response.status,
|
|
253
|
-
payload,
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
function printWhoAmIHuman(payload: WhoAmIOutput): void {
|
|
258
|
-
const rows: Array<{ label: string; value: string }> = [
|
|
259
|
-
{ label: 'Status', value: 'authenticated' },
|
|
260
|
-
{ label: 'Control Plane', value: payload.controlPlane },
|
|
261
|
-
{ label: 'Actor ID', value: payload.actorId ?? '(missing)' },
|
|
262
|
-
{ label: 'Auth Source', value: payload.source ?? '(unknown)' },
|
|
263
|
-
];
|
|
264
|
-
|
|
265
|
-
if (payload.clerkUserId) {
|
|
266
|
-
rows.push({ label: 'Clerk User ID', value: payload.clerkUserId });
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const keyWidth = rows.reduce(
|
|
270
|
-
(width, row) => Math.max(width, row.label.length),
|
|
271
|
-
0
|
|
272
|
-
);
|
|
273
|
-
const contentLines = rows.map(
|
|
274
|
-
(row) => `${row.label.padEnd(keyWidth)} : ${row.value}`
|
|
275
|
-
);
|
|
276
|
-
const title = 'syncular whoami';
|
|
277
|
-
const contentWidth = Math.max(
|
|
278
|
-
title.length,
|
|
279
|
-
...contentLines.map((line) => line.length)
|
|
280
|
-
);
|
|
281
|
-
const horizontal = '─'.repeat(contentWidth + 2);
|
|
282
|
-
|
|
283
|
-
console.log(`╭${horizontal}╮`);
|
|
284
|
-
console.log(`│ ${title.padEnd(contentWidth)} │`);
|
|
285
|
-
console.log(`├${horizontal}┤`);
|
|
286
|
-
for (const line of contentLines) {
|
|
287
|
-
console.log(`│ ${line.padEnd(contentWidth)} │`);
|
|
288
|
-
}
|
|
289
|
-
console.log(`╰${horizontal}╯`);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
export async function runLogin(
|
|
293
|
-
flagValues: Map<string, string>
|
|
294
|
-
): Promise<number> {
|
|
295
|
-
try {
|
|
296
|
-
const controlPlaneBase = resolveControlPlaneBase(flagValues);
|
|
297
|
-
const providedToken = await resolveControlPlaneToken({
|
|
298
|
-
flagValues,
|
|
299
|
-
controlPlaneBase,
|
|
300
|
-
includeStdinToken: true,
|
|
301
|
-
includeStoredToken: false,
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
let token = providedToken;
|
|
305
|
-
if (!token) {
|
|
306
|
-
const callbackHost = optionalFlag(
|
|
307
|
-
flagValues,
|
|
308
|
-
'--callback-host',
|
|
309
|
-
'127.0.0.1'
|
|
310
|
-
);
|
|
311
|
-
const callbackPort = optionalIntegerFlag(
|
|
312
|
-
flagValues,
|
|
313
|
-
'--callback-port',
|
|
314
|
-
0,
|
|
315
|
-
{ min: 0 }
|
|
316
|
-
);
|
|
317
|
-
const timeoutSeconds = optionalIntegerFlag(
|
|
318
|
-
flagValues,
|
|
319
|
-
'--timeout-seconds',
|
|
320
|
-
180,
|
|
321
|
-
{ min: 0 }
|
|
322
|
-
);
|
|
323
|
-
if (timeoutSeconds <= 0) {
|
|
324
|
-
throw new Error('--timeout-seconds must be greater than 0.');
|
|
325
|
-
}
|
|
326
|
-
const authUrl = optionalFlag(
|
|
327
|
-
flagValues,
|
|
328
|
-
'--auth-url',
|
|
329
|
-
defaultCliAuthUrl(controlPlaneBase)
|
|
330
|
-
);
|
|
331
|
-
token = await waitForTokenViaBrowser({
|
|
332
|
-
callbackHost,
|
|
333
|
-
callbackPort,
|
|
334
|
-
authUrl,
|
|
335
|
-
timeoutSeconds,
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
const me = await fetchAuthMe({ controlPlaneBase, token });
|
|
340
|
-
if (!me.ok) {
|
|
341
|
-
throw new Error(
|
|
342
|
-
`Login failed (${me.status}): ${me.payload?.message || me.payload?.error || 'UNAUTHENTICATED'}`
|
|
343
|
-
);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
await writeStoredControlPlaneToken({ controlPlaneBase, token });
|
|
347
|
-
console.log('CLI login saved.');
|
|
348
|
-
console.log(`Control plane: ${controlPlaneBase}`);
|
|
349
|
-
if (me.payload?.actorId) {
|
|
350
|
-
console.log(`Actor: ${me.payload.actorId}`);
|
|
351
|
-
}
|
|
352
|
-
if (me.payload?.source) {
|
|
353
|
-
console.log(`Auth source: ${me.payload.source}`);
|
|
354
|
-
}
|
|
355
|
-
return 0;
|
|
356
|
-
} catch (error: unknown) {
|
|
357
|
-
printError(
|
|
358
|
-
error instanceof Error ? error.message : 'Login failed unexpectedly.'
|
|
359
|
-
);
|
|
360
|
-
return 1;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
export async function runLogout(
|
|
365
|
-
flagValues: Map<string, string>
|
|
366
|
-
): Promise<number> {
|
|
367
|
-
try {
|
|
368
|
-
const controlPlaneBase = resolveControlPlaneBase(flagValues);
|
|
369
|
-
await clearStoredControlPlaneToken(controlPlaneBase);
|
|
370
|
-
console.log(`Cleared stored CLI token for ${controlPlaneBase}`);
|
|
371
|
-
return 0;
|
|
372
|
-
} catch (error: unknown) {
|
|
373
|
-
printError(
|
|
374
|
-
error instanceof Error ? error.message : 'Logout failed unexpectedly.'
|
|
375
|
-
);
|
|
376
|
-
return 1;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
export async function runWhoAmI(
|
|
381
|
-
flagValues: Map<string, string>
|
|
382
|
-
): Promise<number> {
|
|
383
|
-
try {
|
|
384
|
-
const jsonOutput = optionalBooleanFlag(flagValues, '--json', false);
|
|
385
|
-
const controlPlaneBase = resolveControlPlaneBase(flagValues);
|
|
386
|
-
const token = await resolveControlPlaneToken({
|
|
387
|
-
flagValues,
|
|
388
|
-
controlPlaneBase,
|
|
389
|
-
includeStdinToken: true,
|
|
390
|
-
includeStoredToken: true,
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
if (!token) {
|
|
394
|
-
printError(
|
|
395
|
-
`No control-plane token available. Run \`${CLI_NAME} login\` first (or pass --token).`
|
|
396
|
-
);
|
|
397
|
-
return 1;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
const me = await fetchAuthMe({ controlPlaneBase, token });
|
|
401
|
-
if (!me.ok) {
|
|
402
|
-
throw new Error(
|
|
403
|
-
`whoami failed (${me.status}): ${me.payload?.message || me.payload?.error || 'UNAUTHENTICATED'}`
|
|
404
|
-
);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
const output: WhoAmIOutput = {
|
|
408
|
-
controlPlane: controlPlaneBase,
|
|
409
|
-
actorId: me.payload?.actorId ?? null,
|
|
410
|
-
source: me.payload?.source ?? null,
|
|
411
|
-
clerkUserId: me.payload?.clerkUserId ?? null,
|
|
412
|
-
};
|
|
413
|
-
|
|
414
|
-
if (jsonOutput) {
|
|
415
|
-
console.log(JSON.stringify(output, null, 2));
|
|
416
|
-
} else {
|
|
417
|
-
printWhoAmIHuman(output);
|
|
418
|
-
}
|
|
419
|
-
return 0;
|
|
420
|
-
} catch (error: unknown) {
|
|
421
|
-
printError(
|
|
422
|
-
error instanceof Error ? error.message : 'whoami failed unexpectedly.'
|
|
423
|
-
);
|
|
424
|
-
return 1;
|
|
425
|
-
}
|
|
426
|
-
}
|
package/src/commands/build.ts
DELETED
|
@@ -1,210 +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 { optionalBooleanFlag, optionalFlag } from '../flags';
|
|
8
|
-
import { printError, printInfo } from '../output';
|
|
9
|
-
import type { TargetId } from '../targets';
|
|
10
|
-
import {
|
|
11
|
-
resolveEffectiveTargetId,
|
|
12
|
-
resolveTargetDefaultConfigPath,
|
|
13
|
-
} from '../targets';
|
|
14
|
-
|
|
15
|
-
function resolveBuildpack(args: {
|
|
16
|
-
targetId: TargetId;
|
|
17
|
-
flagValues: Map<string, string>;
|
|
18
|
-
}): ContractBuildpack {
|
|
19
|
-
const fallbackBuildpackId = 'contract-worker';
|
|
20
|
-
const buildpackId = optionalFlag(
|
|
21
|
-
args.flagValues,
|
|
22
|
-
'--buildpack',
|
|
23
|
-
fallbackBuildpackId
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
const buildpack = getBuildpackById(buildpackId);
|
|
27
|
-
if (buildpack) {
|
|
28
|
-
return buildpack;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
throw new Error(
|
|
32
|
-
`Unsupported --buildpack "${buildpackId}" for target "${args.targetId}". Supported buildpacks: ${listBuildpackIds().join(', ')}.`
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function resolveContractPaths(args: {
|
|
37
|
-
targetId: TargetId;
|
|
38
|
-
flagValues: Map<string, string>;
|
|
39
|
-
}): {
|
|
40
|
-
configPath: string;
|
|
41
|
-
explicitEntry: string | null;
|
|
42
|
-
} {
|
|
43
|
-
const cwd = process.cwd();
|
|
44
|
-
const explicitEntry = args.flagValues.get('--entry')?.trim() || null;
|
|
45
|
-
const configPath = optionalFlag(
|
|
46
|
-
args.flagValues,
|
|
47
|
-
'--config',
|
|
48
|
-
resolveTargetDefaultConfigPath({
|
|
49
|
-
cwd,
|
|
50
|
-
targetId: args.targetId,
|
|
51
|
-
})
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
if (!explicitEntry && !existsSync(configPath)) {
|
|
55
|
-
throw new Error(
|
|
56
|
-
`Syncular config not found: ${configPath}. Provide --config or --entry.`
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
configPath,
|
|
62
|
-
explicitEntry,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async function assertWritableOutputFile(args: {
|
|
67
|
-
filePath: string;
|
|
68
|
-
overwrite: boolean;
|
|
69
|
-
}): Promise<void> {
|
|
70
|
-
if (!args.overwrite && existsSync(args.filePath)) {
|
|
71
|
-
throw new Error(
|
|
72
|
-
`Refusing to overwrite existing file: ${args.filePath}. Use --force true to overwrite output files.`
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
await mkdir(dirname(args.filePath), { recursive: true });
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export async function runBuild(
|
|
79
|
-
flagValues: Map<string, string>
|
|
80
|
-
): Promise<number> {
|
|
81
|
-
try {
|
|
82
|
-
const cwd = process.cwd();
|
|
83
|
-
const targetResolution = await resolveEffectiveTargetId({
|
|
84
|
-
cwd,
|
|
85
|
-
explicitTargetId: flagValues.get('--target')?.trim() || null,
|
|
86
|
-
});
|
|
87
|
-
const targetId = targetResolution.targetId;
|
|
88
|
-
const overwrite = optionalBooleanFlag(flagValues, '--force', false);
|
|
89
|
-
const buildpack = resolveBuildpack({
|
|
90
|
-
targetId,
|
|
91
|
-
flagValues,
|
|
92
|
-
});
|
|
93
|
-
const { configPath, explicitEntry } = resolveContractPaths({
|
|
94
|
-
targetId,
|
|
95
|
-
flagValues,
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
const outPath = resolve(
|
|
99
|
-
cwd,
|
|
100
|
-
optionalFlag(
|
|
101
|
-
flagValues,
|
|
102
|
-
'--out',
|
|
103
|
-
`.syncular/build/${targetId}/artifact.js`
|
|
104
|
-
)
|
|
105
|
-
);
|
|
106
|
-
const manifestPath = resolve(
|
|
107
|
-
cwd,
|
|
108
|
-
optionalFlag(
|
|
109
|
-
flagValues,
|
|
110
|
-
'--manifest',
|
|
111
|
-
`.syncular/build/${targetId}/manifest.json`
|
|
112
|
-
)
|
|
113
|
-
);
|
|
114
|
-
|
|
115
|
-
await assertWritableOutputFile({
|
|
116
|
-
filePath: outPath,
|
|
117
|
-
overwrite,
|
|
118
|
-
});
|
|
119
|
-
await assertWritableOutputFile({
|
|
120
|
-
filePath: manifestPath,
|
|
121
|
-
overwrite,
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
const migrationMetadata = await buildpack.inspect({
|
|
125
|
-
configPath,
|
|
126
|
-
explicitEntry,
|
|
127
|
-
});
|
|
128
|
-
const manifest = {
|
|
129
|
-
generatedAt: new Date().toISOString(),
|
|
130
|
-
target: targetId,
|
|
131
|
-
targetSource: targetResolution.source,
|
|
132
|
-
buildpack: buildpack.id,
|
|
133
|
-
schemaVersion: migrationMetadata.schemaVersion,
|
|
134
|
-
migrationDigest: migrationMetadata.migrationDigest,
|
|
135
|
-
migrationCount: migrationMetadata.migrationCount,
|
|
136
|
-
...(explicitEntry ? { entry: explicitEntry } : { configPath }),
|
|
137
|
-
};
|
|
138
|
-
const { source, artifactHash } = await buildpack.build({
|
|
139
|
-
configPath,
|
|
140
|
-
explicitEntry,
|
|
141
|
-
commentTag: 'syncular-cli-build',
|
|
142
|
-
metadata: manifest,
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
await writeFile(outPath, source, 'utf8');
|
|
146
|
-
await writeFile(
|
|
147
|
-
manifestPath,
|
|
148
|
-
`${JSON.stringify(manifest, null, 2)}\n`,
|
|
149
|
-
'utf8'
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
printInfo('Build completed.');
|
|
153
|
-
console.log(`Target: ${targetId}`);
|
|
154
|
-
console.log(`Buildpack: ${buildpack.id}`);
|
|
155
|
-
console.log(`Artifact: ${outPath}`);
|
|
156
|
-
console.log(`Manifest: ${manifestPath}`);
|
|
157
|
-
console.log(`Artifact hash: ${artifactHash}`);
|
|
158
|
-
return 0;
|
|
159
|
-
} catch (error: unknown) {
|
|
160
|
-
printError(error instanceof Error ? error.message : 'Build failed.');
|
|
161
|
-
return 1;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export async function runEject(
|
|
166
|
-
flagValues: Map<string, string>
|
|
167
|
-
): Promise<number> {
|
|
168
|
-
try {
|
|
169
|
-
const cwd = process.cwd();
|
|
170
|
-
const targetResolution = await resolveEffectiveTargetId({
|
|
171
|
-
cwd,
|
|
172
|
-
explicitTargetId: flagValues.get('--target')?.trim() || null,
|
|
173
|
-
});
|
|
174
|
-
const targetId = targetResolution.targetId;
|
|
175
|
-
const overwrite = optionalBooleanFlag(flagValues, '--force', false);
|
|
176
|
-
const buildpack = resolveBuildpack({
|
|
177
|
-
targetId,
|
|
178
|
-
flagValues,
|
|
179
|
-
});
|
|
180
|
-
const { configPath, explicitEntry } = resolveContractPaths({
|
|
181
|
-
targetId,
|
|
182
|
-
flagValues,
|
|
183
|
-
});
|
|
184
|
-
const outDir = resolve(
|
|
185
|
-
cwd,
|
|
186
|
-
optionalFlag(flagValues, '--out-dir', `.syncular/eject/${targetId}`)
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
const result = await buildpack.eject({
|
|
190
|
-
configPath,
|
|
191
|
-
explicitEntry,
|
|
192
|
-
targetId,
|
|
193
|
-
outDir,
|
|
194
|
-
overwrite,
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
printInfo('Eject completed.');
|
|
198
|
-
console.log(`Target: ${targetId}`);
|
|
199
|
-
console.log(`Buildpack: ${buildpack.id}`);
|
|
200
|
-
console.log(`Output directory: ${outDir}`);
|
|
201
|
-
console.log('Files:');
|
|
202
|
-
for (const filePath of result.files) {
|
|
203
|
-
console.log(`- ${filePath}`);
|
|
204
|
-
}
|
|
205
|
-
return 0;
|
|
206
|
-
} catch (error: unknown) {
|
|
207
|
-
printError(error instanceof Error ? error.message : 'Eject failed.');
|
|
208
|
-
return 1;
|
|
209
|
-
}
|
|
210
|
-
}
|