freestyle-sandboxes 0.1.21 → 0.1.23
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 +17 -0
- package/cli.mjs +453 -31
- package/index.cjs +1662 -1328
- package/index.d.cts +1835 -1043
- package/index.d.mts +1835 -1043
- package/index.mjs +1662 -1328
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -108,6 +108,23 @@ const { repoId } = await freestyle.git.repos.create({
|
|
|
108
108
|
},
|
|
109
109
|
});
|
|
110
110
|
|
|
111
|
+
// Create a new branch from the default branch
|
|
112
|
+
const repo = freestyle.git.repos.ref({ repoId });
|
|
113
|
+
const { name, sha } = await repo.branches.create({
|
|
114
|
+
name: "feature/something",
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Create commits with files (text and binary)
|
|
118
|
+
const { commit } = await repo.commits.create({
|
|
119
|
+
message: "Add new feature",
|
|
120
|
+
branch: "feature/something",
|
|
121
|
+
files: [
|
|
122
|
+
{ path: "README.md", content: "# My Project" },
|
|
123
|
+
{ path: "logo.png", content: base64Image, encoding: "base64" }
|
|
124
|
+
],
|
|
125
|
+
author: { name: "John Doe", email: "john@example.com" }
|
|
126
|
+
});
|
|
127
|
+
|
|
111
128
|
// Develop code with VMs.
|
|
112
129
|
const { vm } = await freestyle.vms.create({
|
|
113
130
|
gitRepos: [{ repo: repoId, path: "/repo" }],
|
package/cli.mjs
CHANGED
|
@@ -6,17 +6,382 @@ import { Freestyle, VmSpec } from './index.mjs';
|
|
|
6
6
|
import * as fs from 'fs';
|
|
7
7
|
import * as path from 'path';
|
|
8
8
|
import * as dotenv from 'dotenv';
|
|
9
|
+
import * as os from 'os';
|
|
9
10
|
import { spawn } from 'child_process';
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
const DEFAULT_STACK_API_URL = "https://api.stack-auth.com";
|
|
13
|
+
const DEFAULT_STACK_APP_URL = "https://dash.freestyle.sh";
|
|
14
|
+
const DEFAULT_STACK_PROJECT_ID = "0edf478c-f123-46fb-818f-34c0024a9f35";
|
|
15
|
+
const DEFAULT_STACK_PUBLISHABLE_CLIENT_KEY = "pck_h2aft7g9pqjzrkdnzs199h1may5wjtdtdxeex7m2wzp1r";
|
|
16
|
+
const CLI_AUTH_TIMEOUT_MILLIS = 10 * 60 * 1e3;
|
|
17
|
+
const POLL_INTERVAL_MILLIS = 2e3;
|
|
18
|
+
const STACK_REFRESH_TOKEN_ENV_KEY = "FREESTYLE_STACK_REFRESH_TOKEN";
|
|
19
|
+
const STACK_SAVE_TO_DOTENV_ENV_KEY = "FREESTYLE_STACK_SAVE_TO_DOTENV";
|
|
20
|
+
function isTruthy(value) {
|
|
21
|
+
if (!value) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
const normalized = value.trim().toLowerCase();
|
|
25
|
+
return normalized === "1" || normalized === "true" || normalized === "yes";
|
|
26
|
+
}
|
|
27
|
+
function loadRefreshTokenFromDotenv() {
|
|
28
|
+
const refreshToken = process.env[STACK_REFRESH_TOKEN_ENV_KEY];
|
|
29
|
+
if (!refreshToken || typeof refreshToken !== "string") {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const trimmed = refreshToken.trim();
|
|
33
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
34
|
+
}
|
|
35
|
+
function shouldSaveToDotenv(options) {
|
|
36
|
+
if (typeof options?.saveToDotenv === "boolean") {
|
|
37
|
+
return options.saveToDotenv;
|
|
38
|
+
}
|
|
39
|
+
return isTruthy(process.env[STACK_SAVE_TO_DOTENV_ENV_KEY]);
|
|
40
|
+
}
|
|
41
|
+
function persistRefreshTokenToDotenv(refreshToken, options) {
|
|
42
|
+
if (!shouldSaveToDotenv(options)) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const envPath = path.join(process.cwd(), ".env");
|
|
46
|
+
const line = `${STACK_REFRESH_TOKEN_ENV_KEY}=${refreshToken}`;
|
|
47
|
+
let existing = "";
|
|
48
|
+
if (fs.existsSync(envPath)) {
|
|
49
|
+
existing = fs.readFileSync(envPath, "utf-8");
|
|
50
|
+
}
|
|
51
|
+
const pattern = new RegExp(`^${STACK_REFRESH_TOKEN_ENV_KEY}=.*$`, "m");
|
|
52
|
+
let next;
|
|
53
|
+
if (pattern.test(existing)) {
|
|
54
|
+
next = existing.replace(pattern, line);
|
|
55
|
+
} else if (existing.length === 0) {
|
|
56
|
+
next = `${line}
|
|
57
|
+
`;
|
|
58
|
+
} else if (existing.endsWith("\n")) {
|
|
59
|
+
next = `${existing}${line}
|
|
60
|
+
`;
|
|
61
|
+
} else {
|
|
62
|
+
next = `${existing}
|
|
63
|
+
${line}
|
|
64
|
+
`;
|
|
65
|
+
}
|
|
66
|
+
fs.writeFileSync(envPath, next, { encoding: "utf-8" });
|
|
67
|
+
}
|
|
68
|
+
function walkUpDirectories(startDir) {
|
|
69
|
+
const result = [];
|
|
70
|
+
let current = path.resolve(startDir);
|
|
71
|
+
while (true) {
|
|
72
|
+
result.push(current);
|
|
73
|
+
const parent = path.dirname(current);
|
|
74
|
+
if (parent === current) {
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
current = parent;
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
function readEnvFileValue(filePath, key) {
|
|
82
|
+
if (!fs.existsSync(filePath)) {
|
|
83
|
+
return void 0;
|
|
84
|
+
}
|
|
85
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
86
|
+
const pattern = new RegExp(`^${key}=(.*)$`, "m");
|
|
87
|
+
const match = content.match(pattern);
|
|
88
|
+
if (!match?.[1]) {
|
|
89
|
+
return void 0;
|
|
90
|
+
}
|
|
91
|
+
return match[1].trim().replace(/^['\"]|['\"]$/g, "");
|
|
92
|
+
}
|
|
93
|
+
function readYamlEnvValue(filePath, envName) {
|
|
94
|
+
if (!fs.existsSync(filePath)) {
|
|
95
|
+
return void 0;
|
|
96
|
+
}
|
|
97
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
98
|
+
const escapedEnv = envName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
99
|
+
const pattern = new RegExp(
|
|
100
|
+
`-\\s+name:\\s+${escapedEnv}\\s*[\\r\\n]+\\s*value:\\s*([^\\r\\n#]+)`,
|
|
101
|
+
"m"
|
|
102
|
+
);
|
|
103
|
+
const match = content.match(pattern);
|
|
104
|
+
if (!match?.[1]) {
|
|
105
|
+
return void 0;
|
|
106
|
+
}
|
|
107
|
+
return match[1].trim().replace(/^['\"]|['\"]$/g, "");
|
|
108
|
+
}
|
|
109
|
+
function discoverStackConfigFromWorkspace() {
|
|
110
|
+
const discovered = {};
|
|
111
|
+
const roots = walkUpDirectories(process.cwd());
|
|
112
|
+
for (const root of roots) {
|
|
113
|
+
if (!discovered.projectId || !discovered.publishableClientKey) {
|
|
114
|
+
const dashboardEnv = path.join(root, "freestyle-dashboard", ".env.local");
|
|
115
|
+
discovered.projectId ||= readEnvFileValue(
|
|
116
|
+
dashboardEnv,
|
|
117
|
+
"VITE_STACK_PROJECT_ID"
|
|
118
|
+
);
|
|
119
|
+
discovered.publishableClientKey ||= readEnvFileValue(
|
|
120
|
+
dashboardEnv,
|
|
121
|
+
"VITE_STACK_PUBLISHABLE_CLIENT_KEY"
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
if (!discovered.projectId || !discovered.publishableClientKey) {
|
|
125
|
+
const adminEnv = path.join(root, "freestyle-sandbox-admin", ".env.local");
|
|
126
|
+
discovered.projectId ||= readEnvFileValue(
|
|
127
|
+
adminEnv,
|
|
128
|
+
"NEXT_PUBLIC_STACK_PROJECT_ID"
|
|
129
|
+
);
|
|
130
|
+
discovered.publishableClientKey ||= readEnvFileValue(
|
|
131
|
+
adminEnv,
|
|
132
|
+
"NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY"
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
if (!discovered.projectId || !discovered.publishableClientKey) {
|
|
136
|
+
const dashK8s = path.join(root, "k8s", "freestyle-dash.yml");
|
|
137
|
+
discovered.projectId ||= readYamlEnvValue(
|
|
138
|
+
dashK8s,
|
|
139
|
+
"VITE_STACK_PROJECT_ID"
|
|
140
|
+
);
|
|
141
|
+
discovered.publishableClientKey ||= readYamlEnvValue(
|
|
142
|
+
dashK8s,
|
|
143
|
+
"VITE_STACK_PUBLISHABLE_CLIENT_KEY"
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
if (discovered.projectId && discovered.publishableClientKey) {
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return discovered;
|
|
151
|
+
}
|
|
152
|
+
function resolveStackConfig() {
|
|
153
|
+
const discovered = discoverStackConfigFromWorkspace();
|
|
154
|
+
const projectId = process.env.FREESTYLE_STACK_PROJECT_ID ?? process.env.NEXT_PUBLIC_STACK_PROJECT_ID ?? process.env.VITE_STACK_PROJECT_ID ?? discovered.projectId ?? DEFAULT_STACK_PROJECT_ID;
|
|
155
|
+
const publishableClientKey = process.env.FREESTYLE_STACK_PUBLISHABLE_CLIENT_KEY ?? process.env.NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY ?? process.env.VITE_STACK_PUBLISHABLE_CLIENT_KEY ?? discovered.publishableClientKey ?? DEFAULT_STACK_PUBLISHABLE_CLIENT_KEY;
|
|
156
|
+
if (!projectId || !publishableClientKey) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
const stackApiUrl = (process.env.FREESTYLE_STACK_API_URL ?? DEFAULT_STACK_API_URL).replace(/\/+$/, "");
|
|
160
|
+
const appUrl = (process.env.FREESTYLE_STACK_APP_URL ?? DEFAULT_STACK_APP_URL).replace(/\/+$/, "");
|
|
161
|
+
const authFilePath = process.env.FREESTYLE_STACK_AUTH_FILE ?? path.join(os.homedir(), ".freestyle", "stack-auth.json");
|
|
162
|
+
return {
|
|
163
|
+
stackApiUrl,
|
|
164
|
+
appUrl,
|
|
165
|
+
projectId,
|
|
166
|
+
publishableClientKey,
|
|
167
|
+
authFilePath
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function clientHeaders(config) {
|
|
171
|
+
return {
|
|
172
|
+
"Content-Type": "application/json",
|
|
173
|
+
"x-stack-project-id": config.projectId,
|
|
174
|
+
"x-stack-access-type": "client",
|
|
175
|
+
"x-stack-publishable-client-key": config.publishableClientKey
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function loadStoredAuth(config) {
|
|
179
|
+
try {
|
|
180
|
+
if (!fs.existsSync(config.authFilePath)) {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
const auth = JSON.parse(fs.readFileSync(config.authFilePath, "utf-8"));
|
|
184
|
+
if (!auth.refreshToken || typeof auth.refreshToken !== "string") {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
refreshToken: auth.refreshToken,
|
|
189
|
+
updatedAt: typeof auth.updatedAt === "number" ? auth.updatedAt : Date.now()
|
|
190
|
+
};
|
|
191
|
+
} catch {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function persistAuth(config, refreshToken) {
|
|
196
|
+
const dirPath = path.dirname(config.authFilePath);
|
|
197
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
198
|
+
fs.writeFileSync(
|
|
199
|
+
config.authFilePath,
|
|
200
|
+
JSON.stringify(
|
|
201
|
+
{
|
|
202
|
+
refreshToken,
|
|
203
|
+
updatedAt: Date.now()
|
|
204
|
+
},
|
|
205
|
+
null,
|
|
206
|
+
2
|
|
207
|
+
),
|
|
208
|
+
{ encoding: "utf-8", mode: 384 }
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
function clearStoredAuth(config) {
|
|
212
|
+
try {
|
|
213
|
+
if (fs.existsSync(config.authFilePath)) {
|
|
214
|
+
fs.unlinkSync(config.authFilePath);
|
|
215
|
+
}
|
|
216
|
+
} catch {
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function tryOpenBrowser(url) {
|
|
220
|
+
try {
|
|
221
|
+
if (process.platform === "darwin") {
|
|
222
|
+
const child2 = spawn("open", [url], {
|
|
223
|
+
stdio: "ignore",
|
|
224
|
+
detached: true
|
|
225
|
+
});
|
|
226
|
+
child2.unref();
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
if (process.platform === "win32") {
|
|
230
|
+
const child2 = spawn("cmd", ["/c", "start", "", url], {
|
|
231
|
+
stdio: "ignore",
|
|
232
|
+
detached: true
|
|
233
|
+
});
|
|
234
|
+
child2.unref();
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
const child = spawn("xdg-open", [url], {
|
|
238
|
+
stdio: "ignore",
|
|
239
|
+
detached: true
|
|
240
|
+
});
|
|
241
|
+
child.unref();
|
|
242
|
+
return true;
|
|
243
|
+
} catch {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
async function startCliLogin(config) {
|
|
248
|
+
const initResponse = await fetch(`${config.stackApiUrl}/api/v1/auth/cli`, {
|
|
249
|
+
method: "POST",
|
|
250
|
+
headers: clientHeaders(config),
|
|
251
|
+
body: JSON.stringify({
|
|
252
|
+
expires_in_millis: CLI_AUTH_TIMEOUT_MILLIS
|
|
253
|
+
})
|
|
254
|
+
});
|
|
255
|
+
if (!initResponse.ok) {
|
|
256
|
+
const errorText = await initResponse.text();
|
|
257
|
+
throw new Error(
|
|
258
|
+
`Failed to start Stack Auth CLI login (${initResponse.status}). ${errorText || "Check Stack project ID and publishable client key."}`
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
const initData = await initResponse.json();
|
|
262
|
+
if (!initData.polling_code || !initData.login_code) {
|
|
263
|
+
throw new Error("Stack Auth CLI login did not return polling/login codes.");
|
|
264
|
+
}
|
|
265
|
+
const loginUrl = `${config.appUrl}/handler/cli-auth-confirm?login_code=${encodeURIComponent(initData.login_code)}`;
|
|
266
|
+
console.log("\nStack authentication is required.");
|
|
267
|
+
console.log(`Open this URL to continue:
|
|
268
|
+
${loginUrl}
|
|
269
|
+
`);
|
|
270
|
+
const opened = tryOpenBrowser(loginUrl);
|
|
271
|
+
if (opened) {
|
|
272
|
+
console.log("Opened your browser for authentication...");
|
|
273
|
+
} else {
|
|
274
|
+
console.log("Could not open browser automatically. Open the URL manually.");
|
|
275
|
+
}
|
|
276
|
+
const deadline = Date.now() + CLI_AUTH_TIMEOUT_MILLIS;
|
|
277
|
+
while (Date.now() < deadline) {
|
|
278
|
+
const pollResponse = await fetch(
|
|
279
|
+
`${config.stackApiUrl}/api/v1/auth/cli/poll`,
|
|
280
|
+
{
|
|
281
|
+
method: "POST",
|
|
282
|
+
headers: clientHeaders(config),
|
|
283
|
+
body: JSON.stringify({
|
|
284
|
+
polling_code: initData.polling_code
|
|
285
|
+
})
|
|
286
|
+
}
|
|
287
|
+
);
|
|
288
|
+
if (![200, 201].includes(pollResponse.status)) {
|
|
289
|
+
throw new Error(
|
|
290
|
+
`Failed while polling Stack Auth CLI login (${pollResponse.status}).`
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
const pollData = await pollResponse.json();
|
|
294
|
+
if (pollData.status === "success") {
|
|
295
|
+
if (!pollData.refresh_token) {
|
|
296
|
+
throw new Error(
|
|
297
|
+
"Stack Auth login completed without a refresh token response."
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
return pollData.refresh_token;
|
|
301
|
+
}
|
|
302
|
+
if (pollData.status === "cancelled" || pollData.status === "expired" || pollData.status === "error") {
|
|
303
|
+
throw new Error(
|
|
304
|
+
pollData.error || `Stack Auth login ${pollData.status}. Please retry.`
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MILLIS));
|
|
308
|
+
}
|
|
309
|
+
throw new Error("Timed out waiting for Stack Auth CLI authentication.");
|
|
310
|
+
}
|
|
311
|
+
async function refreshStackAccessToken(config, refreshToken) {
|
|
312
|
+
const response = await fetch(
|
|
313
|
+
`${config.stackApiUrl}/api/v1/auth/sessions/current/refresh`,
|
|
314
|
+
{
|
|
315
|
+
method: "POST",
|
|
316
|
+
headers: {
|
|
317
|
+
...clientHeaders(config),
|
|
318
|
+
"x-stack-refresh-token": refreshToken
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
);
|
|
322
|
+
if (!response.ok) {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
const data = await response.json();
|
|
326
|
+
if (!data.access_token) {
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
return {
|
|
330
|
+
accessToken: data.access_token,
|
|
331
|
+
refreshToken: data.refresh_token
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
async function getStackAccessTokenForCli(options) {
|
|
335
|
+
const config = resolveStackConfig();
|
|
336
|
+
if (!config) {
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
let refreshTokenFromEnv = loadRefreshTokenFromDotenv();
|
|
340
|
+
const stored = loadStoredAuth(config);
|
|
341
|
+
if (options?.forceRelogin) {
|
|
342
|
+
refreshTokenFromEnv = null;
|
|
343
|
+
clearStoredAuth(config);
|
|
344
|
+
}
|
|
345
|
+
let refreshToken = refreshTokenFromEnv ?? stored?.refreshToken;
|
|
346
|
+
if (!refreshToken) {
|
|
347
|
+
refreshToken = await startCliLogin(config);
|
|
348
|
+
persistAuth(config, refreshToken);
|
|
349
|
+
persistRefreshTokenToDotenv(refreshToken, options);
|
|
350
|
+
}
|
|
351
|
+
let refreshed = await refreshStackAccessToken(config, refreshToken);
|
|
352
|
+
if (!refreshed) {
|
|
353
|
+
if (!refreshTokenFromEnv) {
|
|
354
|
+
clearStoredAuth(config);
|
|
355
|
+
}
|
|
356
|
+
refreshToken = await startCliLogin(config);
|
|
357
|
+
persistAuth(config, refreshToken);
|
|
358
|
+
persistRefreshTokenToDotenv(refreshToken, options);
|
|
359
|
+
refreshed = await refreshStackAccessToken(config, refreshToken);
|
|
360
|
+
}
|
|
361
|
+
if (!refreshed) {
|
|
362
|
+
throw new Error("Failed to authenticate with Stack Auth.");
|
|
363
|
+
}
|
|
364
|
+
if (refreshed.refreshToken && refreshed.refreshToken !== refreshToken) {
|
|
365
|
+
persistAuth(config, refreshed.refreshToken);
|
|
366
|
+
persistRefreshTokenToDotenv(refreshed.refreshToken, options);
|
|
367
|
+
}
|
|
368
|
+
return refreshed.accessToken;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
async function getFreestyleClient() {
|
|
12
372
|
const apiKey = process.env.FREESTYLE_API_KEY;
|
|
13
373
|
const baseUrl = process.env.FREESTYLE_API_URL;
|
|
14
|
-
if (
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
374
|
+
if (apiKey) {
|
|
375
|
+
return new Freestyle({ apiKey, baseUrl });
|
|
376
|
+
}
|
|
377
|
+
const accessToken = await getStackAccessTokenForCli();
|
|
378
|
+
if (accessToken) {
|
|
379
|
+
return new Freestyle({ accessToken, baseUrl });
|
|
18
380
|
}
|
|
19
|
-
|
|
381
|
+
console.error(
|
|
382
|
+
"Error: authentication is required. Set FREESTYLE_API_KEY, or configure Stack Auth via FREESTYLE_STACK_PROJECT_ID and FREESTYLE_STACK_PUBLISHABLE_CLIENT_KEY."
|
|
383
|
+
);
|
|
384
|
+
process.exit(1);
|
|
20
385
|
}
|
|
21
386
|
function handleError(error) {
|
|
22
387
|
if (error.response) {
|
|
@@ -44,12 +409,14 @@ function formatTable(headers, rows) {
|
|
|
44
409
|
console.log(headerRow);
|
|
45
410
|
console.log(separator);
|
|
46
411
|
rows.forEach((row) => {
|
|
47
|
-
console.log(
|
|
412
|
+
console.log(
|
|
413
|
+
row.map((cell, i) => (cell || "").padEnd(colWidths[i])).join(" ")
|
|
414
|
+
);
|
|
48
415
|
});
|
|
49
416
|
}
|
|
50
417
|
|
|
51
418
|
async function sshIntoVm(vmId, options = {}) {
|
|
52
|
-
const freestyle = getFreestyleClient();
|
|
419
|
+
const freestyle = await getFreestyleClient();
|
|
53
420
|
console.log("Setting up SSH connection...");
|
|
54
421
|
const { identity, identityId } = await freestyle.identities.create();
|
|
55
422
|
console.log(`Created identity: ${identityId}`);
|
|
@@ -139,7 +506,7 @@ const vmCommand = {
|
|
|
139
506
|
loadEnv();
|
|
140
507
|
const args = argv;
|
|
141
508
|
try {
|
|
142
|
-
const freestyle = getFreestyleClient();
|
|
509
|
+
const freestyle = await getFreestyleClient();
|
|
143
510
|
let createOptions = {};
|
|
144
511
|
if (args.snapshot) {
|
|
145
512
|
createOptions.snapshotId = args.snapshot;
|
|
@@ -229,7 +596,7 @@ Exec exit code: ${execResult.statusCode || 0}`);
|
|
|
229
596
|
loadEnv();
|
|
230
597
|
const args = argv;
|
|
231
598
|
try {
|
|
232
|
-
const freestyle = getFreestyleClient();
|
|
599
|
+
const freestyle = await getFreestyleClient();
|
|
233
600
|
const vms = await freestyle.vms.list();
|
|
234
601
|
if (args.json) {
|
|
235
602
|
console.log(JSON.stringify(vms, null, 2));
|
|
@@ -296,7 +663,7 @@ Exec exit code: ${execResult.statusCode || 0}`);
|
|
|
296
663
|
loadEnv();
|
|
297
664
|
const args = argv;
|
|
298
665
|
try {
|
|
299
|
-
const freestyle = getFreestyleClient();
|
|
666
|
+
const freestyle = await getFreestyleClient();
|
|
300
667
|
const vm = freestyle.vms.ref({ vmId: args.vmId });
|
|
301
668
|
console.log(`Executing command on VM ${args.vmId}...`);
|
|
302
669
|
const result = await vm.exec({
|
|
@@ -334,7 +701,7 @@ Exit code: ${result.statusCode || 0}`);
|
|
|
334
701
|
loadEnv();
|
|
335
702
|
const args = argv;
|
|
336
703
|
try {
|
|
337
|
-
const freestyle = getFreestyleClient();
|
|
704
|
+
const freestyle = await getFreestyleClient();
|
|
338
705
|
console.log(`Deleting VM ${args.vmId}...`);
|
|
339
706
|
await freestyle.vms.delete({ vmId: args.vmId });
|
|
340
707
|
console.log("\u2713 VM deleted successfully!");
|
|
@@ -378,9 +745,7 @@ const deployCommand = {
|
|
|
378
745
|
const hasFile = !!argv.file;
|
|
379
746
|
const hasRepo = !!argv.repo;
|
|
380
747
|
if (!hasCode && !hasFile && !hasRepo) {
|
|
381
|
-
throw new Error(
|
|
382
|
-
"You must specify one of --code, --file, or --repo"
|
|
383
|
-
);
|
|
748
|
+
throw new Error("You must specify one of --code, --file, or --repo");
|
|
384
749
|
}
|
|
385
750
|
if ([hasCode, hasFile, hasRepo].filter(Boolean).length > 1) {
|
|
386
751
|
throw new Error(
|
|
@@ -394,7 +759,7 @@ const deployCommand = {
|
|
|
394
759
|
loadEnv();
|
|
395
760
|
const args = argv;
|
|
396
761
|
try {
|
|
397
|
-
const freestyle = getFreestyleClient();
|
|
762
|
+
const freestyle = await getFreestyleClient();
|
|
398
763
|
let code;
|
|
399
764
|
let repo;
|
|
400
765
|
if (args.code) {
|
|
@@ -471,7 +836,7 @@ const runCommand = {
|
|
|
471
836
|
loadEnv();
|
|
472
837
|
const args = argv;
|
|
473
838
|
try {
|
|
474
|
-
const freestyle = getFreestyleClient();
|
|
839
|
+
const freestyle = await getFreestyleClient();
|
|
475
840
|
let code;
|
|
476
841
|
if (args.code) {
|
|
477
842
|
code = args.code;
|
|
@@ -544,7 +909,7 @@ const gitCommand = {
|
|
|
544
909
|
loadEnv();
|
|
545
910
|
const args = argv;
|
|
546
911
|
try {
|
|
547
|
-
const freestyle = getFreestyleClient();
|
|
912
|
+
const freestyle = await getFreestyleClient();
|
|
548
913
|
const body = {
|
|
549
914
|
public: args.public
|
|
550
915
|
};
|
|
@@ -592,7 +957,7 @@ const gitCommand = {
|
|
|
592
957
|
loadEnv();
|
|
593
958
|
const args = argv;
|
|
594
959
|
try {
|
|
595
|
-
const freestyle = getFreestyleClient();
|
|
960
|
+
const freestyle = await getFreestyleClient();
|
|
596
961
|
const repos = await freestyle.git.repos.list({
|
|
597
962
|
limit: args.limit,
|
|
598
963
|
cursor: args.cursor
|
|
@@ -642,7 +1007,7 @@ Total: ${repos.total}`);
|
|
|
642
1007
|
loadEnv();
|
|
643
1008
|
const args = argv;
|
|
644
1009
|
try {
|
|
645
|
-
const freestyle = getFreestyleClient();
|
|
1010
|
+
const freestyle = await getFreestyleClient();
|
|
646
1011
|
console.log(`Deleting repository ${args.repoId}...`);
|
|
647
1012
|
await freestyle.git.repos.delete({ repoId: args.repoId });
|
|
648
1013
|
console.log("\u2713 Repository deleted");
|
|
@@ -681,7 +1046,7 @@ const domainsCommand = {
|
|
|
681
1046
|
loadEnv();
|
|
682
1047
|
const args = argv;
|
|
683
1048
|
try {
|
|
684
|
-
const freestyle = getFreestyleClient();
|
|
1049
|
+
const freestyle = await getFreestyleClient();
|
|
685
1050
|
const domains = await freestyle.domains.list({
|
|
686
1051
|
limit: args.limit,
|
|
687
1052
|
cursor: args.cursor
|
|
@@ -722,7 +1087,7 @@ const domainsCommand = {
|
|
|
722
1087
|
loadEnv();
|
|
723
1088
|
const args = argv;
|
|
724
1089
|
try {
|
|
725
|
-
const freestyle = getFreestyleClient();
|
|
1090
|
+
const freestyle = await getFreestyleClient();
|
|
726
1091
|
const result = await freestyle.domains.verifications.create({
|
|
727
1092
|
domain: args.domain
|
|
728
1093
|
});
|
|
@@ -772,7 +1137,7 @@ const domainsCommand = {
|
|
|
772
1137
|
loadEnv();
|
|
773
1138
|
const args = argv;
|
|
774
1139
|
try {
|
|
775
|
-
const freestyle = getFreestyleClient();
|
|
1140
|
+
const freestyle = await getFreestyleClient();
|
|
776
1141
|
const result = args.verificationId ? await freestyle.domains.verifications.complete({
|
|
777
1142
|
verificationId: args.verificationId
|
|
778
1143
|
}) : await freestyle.domains.verifications.complete({
|
|
@@ -802,7 +1167,7 @@ const domainsCommand = {
|
|
|
802
1167
|
loadEnv();
|
|
803
1168
|
const args = argv;
|
|
804
1169
|
try {
|
|
805
|
-
const freestyle = getFreestyleClient();
|
|
1170
|
+
const freestyle = await getFreestyleClient();
|
|
806
1171
|
const verifications = await freestyle.domains.verifications.list();
|
|
807
1172
|
if (args.json) {
|
|
808
1173
|
console.log(JSON.stringify(verifications, null, 2));
|
|
@@ -865,7 +1230,7 @@ const domainsCommand = {
|
|
|
865
1230
|
loadEnv();
|
|
866
1231
|
const args = argv;
|
|
867
1232
|
try {
|
|
868
|
-
const freestyle = getFreestyleClient();
|
|
1233
|
+
const freestyle = await getFreestyleClient();
|
|
869
1234
|
const result = args.deploymentId ? await freestyle.domains.mappings.create({
|
|
870
1235
|
domain: args.domain,
|
|
871
1236
|
deploymentId: args.deploymentId
|
|
@@ -907,7 +1272,7 @@ const domainsCommand = {
|
|
|
907
1272
|
loadEnv();
|
|
908
1273
|
const args = argv;
|
|
909
1274
|
try {
|
|
910
|
-
const freestyle = getFreestyleClient();
|
|
1275
|
+
const freestyle = await getFreestyleClient();
|
|
911
1276
|
await freestyle.domains.mappings.delete({ domain: args.domain });
|
|
912
1277
|
console.log("\u2713 Domain mapping deleted");
|
|
913
1278
|
} catch (error) {
|
|
@@ -938,7 +1303,7 @@ const domainsCommand = {
|
|
|
938
1303
|
loadEnv();
|
|
939
1304
|
const args = argv;
|
|
940
1305
|
try {
|
|
941
|
-
const freestyle = getFreestyleClient();
|
|
1306
|
+
const freestyle = await getFreestyleClient();
|
|
942
1307
|
const { mappings } = await freestyle.domains.mappings.list({
|
|
943
1308
|
domain: args.domain,
|
|
944
1309
|
limit: args.limit,
|
|
@@ -974,7 +1339,7 @@ const domainsCommand = {
|
|
|
974
1339
|
};
|
|
975
1340
|
|
|
976
1341
|
async function getCronJobById(scheduleId) {
|
|
977
|
-
const freestyle = getFreestyleClient();
|
|
1342
|
+
const freestyle = await getFreestyleClient();
|
|
978
1343
|
const { jobs } = await freestyle.cron.list();
|
|
979
1344
|
const job = jobs.find((candidate) => candidate.schedule.id === scheduleId);
|
|
980
1345
|
if (!job) {
|
|
@@ -1018,7 +1383,7 @@ const cronCommand = {
|
|
|
1018
1383
|
loadEnv();
|
|
1019
1384
|
const args = argv;
|
|
1020
1385
|
try {
|
|
1021
|
-
const freestyle = getFreestyleClient();
|
|
1386
|
+
const freestyle = await getFreestyleClient();
|
|
1022
1387
|
let parsedPayload = {};
|
|
1023
1388
|
if (args.payload) {
|
|
1024
1389
|
try {
|
|
@@ -1065,7 +1430,7 @@ const cronCommand = {
|
|
|
1065
1430
|
loadEnv();
|
|
1066
1431
|
const args = argv;
|
|
1067
1432
|
try {
|
|
1068
|
-
const freestyle = getFreestyleClient();
|
|
1433
|
+
const freestyle = await getFreestyleClient();
|
|
1069
1434
|
const { jobs } = await freestyle.cron.list({
|
|
1070
1435
|
deploymentId: args.deploymentId
|
|
1071
1436
|
});
|
|
@@ -1258,4 +1623,61 @@ const cronCommand = {
|
|
|
1258
1623
|
}
|
|
1259
1624
|
};
|
|
1260
1625
|
|
|
1261
|
-
|
|
1626
|
+
const loginCommand = {
|
|
1627
|
+
command: "login",
|
|
1628
|
+
describe: "Authenticate the CLI with Stack Auth",
|
|
1629
|
+
builder: (yargs) => {
|
|
1630
|
+
return yargs.option("save-to-dotenv", {
|
|
1631
|
+
type: "boolean",
|
|
1632
|
+
description: "Save the Stack refresh token into the current folder's .env as FREESTYLE_STACK_REFRESH_TOKEN",
|
|
1633
|
+
default: false
|
|
1634
|
+
}).option("force", {
|
|
1635
|
+
type: "boolean",
|
|
1636
|
+
description: "Force a fresh login flow even if a stored token exists",
|
|
1637
|
+
default: false
|
|
1638
|
+
}).option("stack-project-id", {
|
|
1639
|
+
type: "string",
|
|
1640
|
+
description: "Stack project ID (overrides environment for this run)"
|
|
1641
|
+
}).option("stack-publishable-client-key", {
|
|
1642
|
+
type: "string",
|
|
1643
|
+
description: "Stack publishable client key (overrides environment for this run)"
|
|
1644
|
+
}).option("stack-app-url", {
|
|
1645
|
+
type: "string",
|
|
1646
|
+
description: "Stack app URL for browser confirmation (default: https://freestyle.sh)"
|
|
1647
|
+
});
|
|
1648
|
+
},
|
|
1649
|
+
handler: async (argv) => {
|
|
1650
|
+
loadEnv();
|
|
1651
|
+
const args = argv;
|
|
1652
|
+
if (args.stackProjectId) {
|
|
1653
|
+
process.env.FREESTYLE_STACK_PROJECT_ID = args.stackProjectId;
|
|
1654
|
+
}
|
|
1655
|
+
if (args.stackPublishableClientKey) {
|
|
1656
|
+
process.env.FREESTYLE_STACK_PUBLISHABLE_CLIENT_KEY = args.stackPublishableClientKey;
|
|
1657
|
+
}
|
|
1658
|
+
if (args.stackAppUrl) {
|
|
1659
|
+
process.env.FREESTYLE_STACK_APP_URL = args.stackAppUrl;
|
|
1660
|
+
}
|
|
1661
|
+
try {
|
|
1662
|
+
const accessToken = await getStackAccessTokenForCli({
|
|
1663
|
+
saveToDotenv: args.saveToDotenv,
|
|
1664
|
+
forceRelogin: args.force
|
|
1665
|
+
});
|
|
1666
|
+
if (!accessToken) {
|
|
1667
|
+
throw new Error(
|
|
1668
|
+
"Stack Auth is not configured. Set FREESTYLE_STACK_PROJECT_ID and FREESTYLE_STACK_PUBLISHABLE_CLIENT_KEY."
|
|
1669
|
+
);
|
|
1670
|
+
}
|
|
1671
|
+
console.log("\u2713 Authenticated with Stack Auth");
|
|
1672
|
+
if (args.saveToDotenv) {
|
|
1673
|
+
console.log("\u2713 Saved refresh token to .env in current directory");
|
|
1674
|
+
} else {
|
|
1675
|
+
console.log("\u2713 Saved refresh token to global CLI auth store");
|
|
1676
|
+
}
|
|
1677
|
+
} catch (error) {
|
|
1678
|
+
handleError(error);
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
};
|
|
1682
|
+
|
|
1683
|
+
yargs(hideBin(process.argv)).scriptName("freestyle").usage("$0 <command> [options]").command(vmCommand).command(gitCommand).command(domainsCommand).command(cronCommand).command(loginCommand).command(deployCommand).command(runCommand).demandCommand(1, "You need to specify a command").help().alias("help", "h").version().alias("version", "v").strict().parse();
|