freestyle 0.1.49 → 0.1.51
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 +9 -0
- package/cli.mjs +830 -12
- package/index.cjs +2059 -1496
- package/index.d.cts +1965 -1233
- package/index.d.mts +1965 -1233
- package/index.mjs +2057 -1497
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import * as dotenv from 'dotenv';
|
|
3
3
|
import yargs from 'yargs';
|
|
4
4
|
import { hideBin } from 'yargs/helpers';
|
|
5
|
-
import { Freestyle, VmSpec } from './index.mjs';
|
|
5
|
+
import { Freestyle, VmSpec, readFiles } from './index.mjs';
|
|
6
6
|
import * as fs from 'fs';
|
|
7
7
|
import * as path from 'path';
|
|
8
8
|
import * as os from 'os';
|
|
@@ -686,6 +686,94 @@ async function sshIntoVm(vmId, options = {}) {
|
|
|
686
686
|
});
|
|
687
687
|
});
|
|
688
688
|
}
|
|
689
|
+
function buildSubcommands(yargs, resolveBuild, idName) {
|
|
690
|
+
return yargs.command(
|
|
691
|
+
`get <${idName}>`,
|
|
692
|
+
"Show the build record",
|
|
693
|
+
(y) => y.positional(idName, { type: "string", demandOption: true }).option("json", { type: "boolean", default: false }),
|
|
694
|
+
async (argv) => {
|
|
695
|
+
loadEnv();
|
|
696
|
+
try {
|
|
697
|
+
const build = await resolveBuild(argv[idName]);
|
|
698
|
+
const record = await build.get();
|
|
699
|
+
console.log(JSON.stringify(record, null, 2));
|
|
700
|
+
} catch (e) {
|
|
701
|
+
handleError(e);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
).command(
|
|
705
|
+
`phases <${idName}>`,
|
|
706
|
+
"List build phases",
|
|
707
|
+
(y) => y.positional(idName, { type: "string", demandOption: true }).option("json", { type: "boolean", default: false }),
|
|
708
|
+
async (argv) => {
|
|
709
|
+
loadEnv();
|
|
710
|
+
try {
|
|
711
|
+
const build = await resolveBuild(argv[idName]);
|
|
712
|
+
const phases = await build.phases();
|
|
713
|
+
if (argv.json) {
|
|
714
|
+
console.log(JSON.stringify(phases, null, 2));
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
console.log(
|
|
718
|
+
formatTable(
|
|
719
|
+
["Phase ID", "Name", "Snapshot", "Started"],
|
|
720
|
+
phases.map((p) => [
|
|
721
|
+
p.phaseId,
|
|
722
|
+
p.name,
|
|
723
|
+
p.snapshotId ?? "\u2014",
|
|
724
|
+
p.startedAt
|
|
725
|
+
])
|
|
726
|
+
)
|
|
727
|
+
);
|
|
728
|
+
} catch (e) {
|
|
729
|
+
handleError(e);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
).command(
|
|
733
|
+
`debug <${idName}>`,
|
|
734
|
+
"Boot a debug VM from the failed phase's snapshot and SSH in",
|
|
735
|
+
(y) => y.positional(idName, { type: "string", demandOption: true }),
|
|
736
|
+
async (argv) => {
|
|
737
|
+
loadEnv();
|
|
738
|
+
try {
|
|
739
|
+
const fs = await getFreestyleClient();
|
|
740
|
+
const build = await resolveBuild(argv[idName]);
|
|
741
|
+
const failed = await build.failedPhase();
|
|
742
|
+
if (!failed) {
|
|
743
|
+
console.error(
|
|
744
|
+
`No bookable failed-phase snapshot for build ${build.buildId} \u2014 nothing to debug.`
|
|
745
|
+
);
|
|
746
|
+
process.exit(1);
|
|
747
|
+
}
|
|
748
|
+
console.log(
|
|
749
|
+
`Failed phase: ${failed.name} (snapshot ${failed.snapshotId})`
|
|
750
|
+
);
|
|
751
|
+
console.log("Booting debug VM\u2026");
|
|
752
|
+
const result = await fs.vms.create({
|
|
753
|
+
snapshotId: failed.snapshotId
|
|
754
|
+
});
|
|
755
|
+
console.log(`\u2713 VM ${result.vmId} running`);
|
|
756
|
+
await sshIntoVm(result.vmId);
|
|
757
|
+
} catch (e) {
|
|
758
|
+
handleError(e);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
).command(
|
|
762
|
+
`wait <${idName}>`,
|
|
763
|
+
"Wait until the build reaches a terminal state",
|
|
764
|
+
(y) => y.positional(idName, { type: "string", demandOption: true }),
|
|
765
|
+
async (argv) => {
|
|
766
|
+
loadEnv();
|
|
767
|
+
try {
|
|
768
|
+
const build = await resolveBuild(argv[idName]);
|
|
769
|
+
const record = await build.wait();
|
|
770
|
+
console.log(JSON.stringify(record, null, 2));
|
|
771
|
+
} catch (e) {
|
|
772
|
+
handleError(e);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
).demandCommand(1, "Specify a build action");
|
|
776
|
+
}
|
|
689
777
|
const vmCommand = {
|
|
690
778
|
command: "vm <action>",
|
|
691
779
|
describe: "Manage Virtual Machines",
|
|
@@ -940,12 +1028,330 @@ Exit code: ${result.statusCode || 0}`);
|
|
|
940
1028
|
handleError(error);
|
|
941
1029
|
}
|
|
942
1030
|
}
|
|
1031
|
+
).command(
|
|
1032
|
+
"build <action>",
|
|
1033
|
+
"Inspect the build that produced a VM",
|
|
1034
|
+
(yargs2) => buildSubcommands(yargs2, async (vmId) => {
|
|
1035
|
+
const fs = await getFreestyleClient();
|
|
1036
|
+
return fs.vms.ref({ vmId }).getBuild();
|
|
1037
|
+
}, "vmId"),
|
|
1038
|
+
() => {
|
|
1039
|
+
}
|
|
1040
|
+
).command(
|
|
1041
|
+
"snapshot <action>",
|
|
1042
|
+
"Manage snapshots",
|
|
1043
|
+
(yargs2) => yargs2.command(
|
|
1044
|
+
"list",
|
|
1045
|
+
"List snapshots",
|
|
1046
|
+
(y) => y.option("show-failed", { type: "boolean", default: true }).option("show-deleted", { type: "boolean", default: false }).option("show-cancelled", {
|
|
1047
|
+
type: "boolean",
|
|
1048
|
+
default: false
|
|
1049
|
+
}).option("show-lost", { type: "boolean", default: false }).option("json", { type: "boolean", default: false }),
|
|
1050
|
+
async (argv) => {
|
|
1051
|
+
loadEnv();
|
|
1052
|
+
try {
|
|
1053
|
+
const fs = await getFreestyleClient();
|
|
1054
|
+
const result = await fs.vms.snapshots.list({
|
|
1055
|
+
includeFailed: argv["show-failed"],
|
|
1056
|
+
includeDeleted: argv["show-deleted"],
|
|
1057
|
+
includeCancelled: argv["show-cancelled"],
|
|
1058
|
+
includeLost: argv["show-lost"]
|
|
1059
|
+
});
|
|
1060
|
+
if (argv.json) {
|
|
1061
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
console.log(
|
|
1065
|
+
formatTable(
|
|
1066
|
+
["Snapshot ID", "Name", "Source VM", "State", "Created"],
|
|
1067
|
+
result.snapshots.map((s) => [
|
|
1068
|
+
s.snapshotId,
|
|
1069
|
+
s.name ?? "\u2014",
|
|
1070
|
+
s.sourceVmId ?? "\u2014",
|
|
1071
|
+
s.state ?? (s.failed ? "failed" : "ready"),
|
|
1072
|
+
s.createdAt
|
|
1073
|
+
])
|
|
1074
|
+
)
|
|
1075
|
+
);
|
|
1076
|
+
} catch (e) {
|
|
1077
|
+
handleError(e);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
).command(
|
|
1081
|
+
"get <snapshotId>",
|
|
1082
|
+
"Show a snapshot record",
|
|
1083
|
+
(y) => y.positional("snapshotId", {
|
|
1084
|
+
type: "string",
|
|
1085
|
+
demandOption: true
|
|
1086
|
+
}).option("json", { type: "boolean", default: false }),
|
|
1087
|
+
async (argv) => {
|
|
1088
|
+
loadEnv();
|
|
1089
|
+
try {
|
|
1090
|
+
const fs = await getFreestyleClient();
|
|
1091
|
+
const info = await fs.vms.snapshots.ref({ snapshotId: argv.snapshotId }).get();
|
|
1092
|
+
console.log(JSON.stringify(info, null, 2));
|
|
1093
|
+
} catch (e) {
|
|
1094
|
+
handleError(e);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
).command(
|
|
1098
|
+
"delete <snapshotId>",
|
|
1099
|
+
"Delete a snapshot",
|
|
1100
|
+
(y) => y.positional("snapshotId", {
|
|
1101
|
+
type: "string",
|
|
1102
|
+
demandOption: true
|
|
1103
|
+
}),
|
|
1104
|
+
async (argv) => {
|
|
1105
|
+
loadEnv();
|
|
1106
|
+
try {
|
|
1107
|
+
const fs = await getFreestyleClient();
|
|
1108
|
+
await fs.vms.snapshots.ref({ snapshotId: argv.snapshotId }).delete();
|
|
1109
|
+
console.log("\u2713 Snapshot deleted");
|
|
1110
|
+
} catch (e) {
|
|
1111
|
+
handleError(e);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
).command(
|
|
1115
|
+
"rename <snapshotId>",
|
|
1116
|
+
"Rename a snapshot",
|
|
1117
|
+
(y) => y.positional("snapshotId", {
|
|
1118
|
+
type: "string",
|
|
1119
|
+
demandOption: true
|
|
1120
|
+
}).option("name", {
|
|
1121
|
+
type: "string",
|
|
1122
|
+
demandOption: true,
|
|
1123
|
+
description: "New name"
|
|
1124
|
+
}),
|
|
1125
|
+
async (argv) => {
|
|
1126
|
+
loadEnv();
|
|
1127
|
+
try {
|
|
1128
|
+
const fs = await getFreestyleClient();
|
|
1129
|
+
await fs.vms.snapshots.ref({ snapshotId: argv.snapshotId }).update({ name: argv.name });
|
|
1130
|
+
console.log("\u2713 Snapshot renamed");
|
|
1131
|
+
} catch (e) {
|
|
1132
|
+
handleError(e);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
).command(
|
|
1136
|
+
"boot <snapshotId>",
|
|
1137
|
+
"Boot a VM from a snapshot",
|
|
1138
|
+
(y) => y.positional("snapshotId", {
|
|
1139
|
+
type: "string",
|
|
1140
|
+
demandOption: true
|
|
1141
|
+
}).option("ssh", { type: "boolean", default: false }),
|
|
1142
|
+
async (argv) => {
|
|
1143
|
+
loadEnv();
|
|
1144
|
+
try {
|
|
1145
|
+
const fs = await getFreestyleClient();
|
|
1146
|
+
console.log(
|
|
1147
|
+
`Booting VM from snapshot ${argv.snapshotId}...`
|
|
1148
|
+
);
|
|
1149
|
+
const result = await fs.vms.create({
|
|
1150
|
+
snapshotId: argv.snapshotId
|
|
1151
|
+
});
|
|
1152
|
+
console.log(`\u2713 VM ${result.vmId} created`);
|
|
1153
|
+
if (argv.ssh) {
|
|
1154
|
+
await sshIntoVm(result.vmId);
|
|
1155
|
+
}
|
|
1156
|
+
} catch (e) {
|
|
1157
|
+
handleError(e);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
).command(
|
|
1161
|
+
"debug <snapshotId>",
|
|
1162
|
+
"Boot a debug VM from a (possibly failed) snapshot and SSH in",
|
|
1163
|
+
(y) => y.positional("snapshotId", {
|
|
1164
|
+
type: "string",
|
|
1165
|
+
demandOption: true
|
|
1166
|
+
}),
|
|
1167
|
+
async (argv) => {
|
|
1168
|
+
loadEnv();
|
|
1169
|
+
try {
|
|
1170
|
+
const fs = await getFreestyleClient();
|
|
1171
|
+
console.log(
|
|
1172
|
+
`Booting debug VM from snapshot ${argv.snapshotId}...`
|
|
1173
|
+
);
|
|
1174
|
+
const result = await fs.vms.create({
|
|
1175
|
+
snapshotId: argv.snapshotId
|
|
1176
|
+
});
|
|
1177
|
+
console.log(`\u2713 VM ${result.vmId} running`);
|
|
1178
|
+
await sshIntoVm(result.vmId);
|
|
1179
|
+
} catch (e) {
|
|
1180
|
+
handleError(e);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
).command(
|
|
1184
|
+
"build <action>",
|
|
1185
|
+
"Inspect the build that produced a snapshot",
|
|
1186
|
+
(yy) => buildSubcommands(
|
|
1187
|
+
yy,
|
|
1188
|
+
async (snapshotId) => {
|
|
1189
|
+
const fs = await getFreestyleClient();
|
|
1190
|
+
return fs.vms.snapshots.ref({ snapshotId }).getBuild();
|
|
1191
|
+
},
|
|
1192
|
+
"snapshotId"
|
|
1193
|
+
),
|
|
1194
|
+
() => {
|
|
1195
|
+
}
|
|
1196
|
+
).demandCommand(1, "Specify a snapshot action"),
|
|
1197
|
+
() => {
|
|
1198
|
+
}
|
|
943
1199
|
).demandCommand(1, "You need to specify a vm action");
|
|
944
1200
|
},
|
|
945
1201
|
handler: () => {
|
|
946
1202
|
}
|
|
947
1203
|
};
|
|
948
1204
|
|
|
1205
|
+
const ALWAYS_IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
1206
|
+
".git",
|
|
1207
|
+
".hg",
|
|
1208
|
+
".svn",
|
|
1209
|
+
".idea",
|
|
1210
|
+
".vscode",
|
|
1211
|
+
"node_modules"
|
|
1212
|
+
]);
|
|
1213
|
+
const ALWAYS_IGNORED_FILES = /* @__PURE__ */ new Set([
|
|
1214
|
+
".DS_Store"
|
|
1215
|
+
]);
|
|
1216
|
+
function joinPosix(...parts) {
|
|
1217
|
+
return path.posix.join(...parts.map((part) => part.replace(/\\/g, "/")));
|
|
1218
|
+
}
|
|
1219
|
+
function shouldIgnorePath(filePath, options) {
|
|
1220
|
+
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
1221
|
+
const segments = normalizedPath.split("/").filter(Boolean);
|
|
1222
|
+
const basename = segments[segments.length - 1] || "";
|
|
1223
|
+
for (const segment of segments.slice(0, -1)) {
|
|
1224
|
+
if (ALWAYS_IGNORED_DIRS.has(segment)) {
|
|
1225
|
+
return `ignored directory '${segment}'`;
|
|
1226
|
+
}
|
|
1227
|
+
if (options?.excludeNextArtifacts && segment === ".next") {
|
|
1228
|
+
return "ignored build artifact directory '.next'";
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
if (ALWAYS_IGNORED_FILES.has(basename)) {
|
|
1232
|
+
return `ignored file '${basename}'`;
|
|
1233
|
+
}
|
|
1234
|
+
if (basename === ".env" || basename.startsWith(".env.")) {
|
|
1235
|
+
return "ignored sensitive env file";
|
|
1236
|
+
}
|
|
1237
|
+
if (options?.excludeNextArtifacts && basename === ".next") {
|
|
1238
|
+
return "ignored build artifact directory '.next'";
|
|
1239
|
+
}
|
|
1240
|
+
return null;
|
|
1241
|
+
}
|
|
1242
|
+
function filterDeploymentFiles(files, options) {
|
|
1243
|
+
const ignoredSummary = {};
|
|
1244
|
+
const filtered = [];
|
|
1245
|
+
for (const file of files) {
|
|
1246
|
+
const reason = shouldIgnorePath(file.path, options);
|
|
1247
|
+
if (reason) {
|
|
1248
|
+
ignoredSummary[reason] = (ignoredSummary[reason] || 0) + 1;
|
|
1249
|
+
continue;
|
|
1250
|
+
}
|
|
1251
|
+
filtered.push(file);
|
|
1252
|
+
}
|
|
1253
|
+
return {
|
|
1254
|
+
files: filtered,
|
|
1255
|
+
ignoredSummary
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
async function readFilesWithPrefix(dir, prefix) {
|
|
1259
|
+
const files = await readFiles(dir);
|
|
1260
|
+
return files.map((file) => ({
|
|
1261
|
+
...file,
|
|
1262
|
+
path: joinPosix(prefix, file.path)
|
|
1263
|
+
}));
|
|
1264
|
+
}
|
|
1265
|
+
function detectLockfile(projectRoot) {
|
|
1266
|
+
const lockfiles = [
|
|
1267
|
+
"package-lock.json",
|
|
1268
|
+
"yarn.lock",
|
|
1269
|
+
"pnpm-lock.yaml",
|
|
1270
|
+
"bun.lock",
|
|
1271
|
+
"bun.lockb"
|
|
1272
|
+
];
|
|
1273
|
+
return lockfiles.find(
|
|
1274
|
+
(lockfile) => fs.existsSync(path.join(projectRoot, lockfile))
|
|
1275
|
+
);
|
|
1276
|
+
}
|
|
1277
|
+
function detectNextJsProject(projectRoot) {
|
|
1278
|
+
const nextConfigCandidates = [
|
|
1279
|
+
"next.config.js",
|
|
1280
|
+
"next.config.mjs",
|
|
1281
|
+
"next.config.ts",
|
|
1282
|
+
"next.config.cjs"
|
|
1283
|
+
];
|
|
1284
|
+
if (nextConfigCandidates.some(
|
|
1285
|
+
(fileName) => fs.existsSync(path.join(projectRoot, fileName))
|
|
1286
|
+
)) {
|
|
1287
|
+
return true;
|
|
1288
|
+
}
|
|
1289
|
+
const packageJsonPath = path.join(projectRoot, "package.json");
|
|
1290
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
1291
|
+
return false;
|
|
1292
|
+
}
|
|
1293
|
+
try {
|
|
1294
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
1295
|
+
return Boolean(
|
|
1296
|
+
packageJson.dependencies?.next || packageJson.devDependencies?.next
|
|
1297
|
+
);
|
|
1298
|
+
} catch {
|
|
1299
|
+
return false;
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
async function prepareNextJsBuiltFiles(projectRoot) {
|
|
1303
|
+
const standaloneDir = path.join(projectRoot, ".next", "standalone");
|
|
1304
|
+
const standaloneEntrypoint = path.join(standaloneDir, "server.js");
|
|
1305
|
+
if (!fs.existsSync(standaloneDir) || !fs.statSync(standaloneDir).isDirectory() || !fs.existsSync(standaloneEntrypoint)) {
|
|
1306
|
+
return null;
|
|
1307
|
+
}
|
|
1308
|
+
const files = await readFiles(standaloneDir);
|
|
1309
|
+
const existingPaths = new Set(files.map((file) => file.path));
|
|
1310
|
+
const projectPublicDir = path.join(projectRoot, "public");
|
|
1311
|
+
if (fs.existsSync(projectPublicDir) && fs.statSync(projectPublicDir).isDirectory()) {
|
|
1312
|
+
const publicFiles = await readFilesWithPrefix(projectPublicDir, "public");
|
|
1313
|
+
for (const file of publicFiles) {
|
|
1314
|
+
if (!existingPaths.has(file.path)) {
|
|
1315
|
+
files.push(file);
|
|
1316
|
+
existingPaths.add(file.path);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
const projectStaticDir = path.join(projectRoot, ".next", "static");
|
|
1321
|
+
if (fs.existsSync(projectStaticDir) && fs.statSync(projectStaticDir).isDirectory()) {
|
|
1322
|
+
const staticFiles = await readFilesWithPrefix(
|
|
1323
|
+
projectStaticDir,
|
|
1324
|
+
".next/static"
|
|
1325
|
+
);
|
|
1326
|
+
for (const file of staticFiles) {
|
|
1327
|
+
if (!existingPaths.has(file.path)) {
|
|
1328
|
+
files.push(file);
|
|
1329
|
+
existingPaths.add(file.path);
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
const lockfile = detectLockfile(projectRoot);
|
|
1334
|
+
if (lockfile && !existingPaths.has(lockfile)) {
|
|
1335
|
+
const lockfileContent = fs.readFileSync(path.join(projectRoot, lockfile), "base64");
|
|
1336
|
+
files.push({
|
|
1337
|
+
path: lockfile,
|
|
1338
|
+
content: lockfileContent,
|
|
1339
|
+
encoding: "base64"
|
|
1340
|
+
});
|
|
1341
|
+
}
|
|
1342
|
+
const freestyleJsonPath = path.join(projectRoot, "freestyle.json");
|
|
1343
|
+
if (fs.existsSync(freestyleJsonPath) && !existingPaths.has("freestyle.json")) {
|
|
1344
|
+
files.push({
|
|
1345
|
+
path: "freestyle.json",
|
|
1346
|
+
content: fs.readFileSync(freestyleJsonPath, "utf-8"),
|
|
1347
|
+
encoding: "utf-8"
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
return {
|
|
1351
|
+
files,
|
|
1352
|
+
entrypointPath: "server.js"
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
949
1355
|
const deployCommand = {
|
|
950
1356
|
command: "deploy",
|
|
951
1357
|
describe: "Deploy a serverless function",
|
|
@@ -958,15 +1364,36 @@ const deployCommand = {
|
|
|
958
1364
|
alias: "f",
|
|
959
1365
|
type: "string",
|
|
960
1366
|
description: "File path containing code to deploy"
|
|
1367
|
+
}).option("dir", {
|
|
1368
|
+
alias: "d",
|
|
1369
|
+
type: "string",
|
|
1370
|
+
description: "Directory path to deploy (prebuilt files, or source files when used with --build)"
|
|
961
1371
|
}).option("repo", {
|
|
962
1372
|
alias: "r",
|
|
963
1373
|
type: "string",
|
|
964
1374
|
description: "Git repository ID to deploy"
|
|
1375
|
+
}).option("domain", {
|
|
1376
|
+
type: "array",
|
|
1377
|
+
description: "Domains to assign to the deployment (can be specified multiple times)",
|
|
1378
|
+
default: []
|
|
965
1379
|
}).option("env", {
|
|
966
1380
|
alias: "e",
|
|
967
1381
|
type: "array",
|
|
968
1382
|
description: "Environment variables (KEY=VALUE)",
|
|
969
1383
|
default: []
|
|
1384
|
+
}).option("build", {
|
|
1385
|
+
type: "boolean",
|
|
1386
|
+
description: "Enable server-side build (use with --repo or --dir source deployments)"
|
|
1387
|
+
}).option("build-command", {
|
|
1388
|
+
type: "string",
|
|
1389
|
+
description: "Custom build command (for example: npm run build)"
|
|
1390
|
+
}).option("build-out-dir", {
|
|
1391
|
+
type: "string",
|
|
1392
|
+
description: "Build output directory (for example: dist or .next/standalone)"
|
|
1393
|
+
}).option("build-env", {
|
|
1394
|
+
type: "array",
|
|
1395
|
+
description: "Build environment variables (KEY=VALUE)",
|
|
1396
|
+
default: []
|
|
970
1397
|
}).option("json", {
|
|
971
1398
|
type: "boolean",
|
|
972
1399
|
description: "Output as JSON",
|
|
@@ -974,15 +1401,41 @@ const deployCommand = {
|
|
|
974
1401
|
}).check((argv) => {
|
|
975
1402
|
const hasCode = !!argv.code;
|
|
976
1403
|
const hasFile = !!argv.file;
|
|
1404
|
+
const hasDir = !!argv.dir;
|
|
977
1405
|
const hasRepo = !!argv.repo;
|
|
978
|
-
|
|
979
|
-
|
|
1406
|
+
const hasBuildConfig = !!argv.build || !!argv.buildCommand || !!argv.buildOutDir;
|
|
1407
|
+
if (!hasCode && !hasFile && !hasDir && !hasRepo) {
|
|
1408
|
+
throw new Error(
|
|
1409
|
+
"You must specify one of --code, --file, --dir, or --repo"
|
|
1410
|
+
);
|
|
1411
|
+
}
|
|
1412
|
+
if ([hasCode, hasFile, hasDir, hasRepo].filter(Boolean).length > 1) {
|
|
1413
|
+
throw new Error(
|
|
1414
|
+
"You can only specify one of --code, --file, --dir, or --repo"
|
|
1415
|
+
);
|
|
1416
|
+
}
|
|
1417
|
+
if (hasBuildConfig && (hasCode || hasFile)) {
|
|
1418
|
+
throw new Error(
|
|
1419
|
+
"--build options are only supported with --repo or --dir"
|
|
1420
|
+
);
|
|
980
1421
|
}
|
|
981
|
-
if (
|
|
1422
|
+
if (argv.buildEnv && argv.buildEnv.length > 0 && !hasBuildConfig) {
|
|
982
1423
|
throw new Error(
|
|
983
|
-
"
|
|
1424
|
+
"--build-env requires --build, --build-command, or --build-out-dir"
|
|
984
1425
|
);
|
|
985
1426
|
}
|
|
1427
|
+
if (!!argv.buildCommand && !argv.build) {
|
|
1428
|
+
throw new Error("--build-command requires --build");
|
|
1429
|
+
}
|
|
1430
|
+
if (!!argv.buildOutDir && !argv.build) {
|
|
1431
|
+
throw new Error("--build-out-dir requires --build");
|
|
1432
|
+
}
|
|
1433
|
+
if (!!argv.buildOutDir && !argv.buildCommand) {
|
|
1434
|
+
throw new Error("--build-out-dir requires --build-command");
|
|
1435
|
+
}
|
|
1436
|
+
if (argv.buildEnv && argv.buildEnv.length > 0 && !argv.buildCommand) {
|
|
1437
|
+
throw new Error("--build-env requires --build-command");
|
|
1438
|
+
}
|
|
986
1439
|
return true;
|
|
987
1440
|
});
|
|
988
1441
|
},
|
|
@@ -992,11 +1445,55 @@ const deployCommand = {
|
|
|
992
1445
|
try {
|
|
993
1446
|
const freestyle = await getFreestyleClient();
|
|
994
1447
|
let code;
|
|
1448
|
+
let files;
|
|
995
1449
|
let repo;
|
|
1450
|
+
let entrypointPath;
|
|
1451
|
+
let nextjsOptimization;
|
|
996
1452
|
if (args.code) {
|
|
997
1453
|
code = args.code;
|
|
998
1454
|
} else if (args.file) {
|
|
999
1455
|
code = fs.readFileSync(args.file, "utf-8");
|
|
1456
|
+
} else if (args.dir) {
|
|
1457
|
+
if (!fs.existsSync(args.dir)) {
|
|
1458
|
+
throw new Error(`Directory not found: ${args.dir}`);
|
|
1459
|
+
}
|
|
1460
|
+
if (!fs.statSync(args.dir).isDirectory()) {
|
|
1461
|
+
throw new Error(`Path is not a directory: ${args.dir}`);
|
|
1462
|
+
}
|
|
1463
|
+
const nextJsBuiltFiles = await prepareNextJsBuiltFiles(args.dir);
|
|
1464
|
+
let ignoredSummary = {};
|
|
1465
|
+
if (nextJsBuiltFiles) {
|
|
1466
|
+
const filteredBuiltFiles = filterDeploymentFiles(nextJsBuiltFiles.files, {
|
|
1467
|
+
excludeNextArtifacts: false
|
|
1468
|
+
});
|
|
1469
|
+
files = filteredBuiltFiles.files;
|
|
1470
|
+
ignoredSummary = filteredBuiltFiles.ignoredSummary;
|
|
1471
|
+
entrypointPath = nextJsBuiltFiles.entrypointPath;
|
|
1472
|
+
nextjsOptimization = true;
|
|
1473
|
+
} else {
|
|
1474
|
+
const rawFiles = await readFiles(args.dir);
|
|
1475
|
+
const filteredSourceFiles = filterDeploymentFiles(rawFiles, {
|
|
1476
|
+
// When uploading source for server-side build, ignore stale local Next.js build output.
|
|
1477
|
+
excludeNextArtifacts: !!args.build
|
|
1478
|
+
});
|
|
1479
|
+
files = filteredSourceFiles.files;
|
|
1480
|
+
ignoredSummary = filteredSourceFiles.ignoredSummary;
|
|
1481
|
+
if (detectNextJsProject(args.dir)) {
|
|
1482
|
+
nextjsOptimization = true;
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
const ignoredEntries = Object.entries(ignoredSummary);
|
|
1486
|
+
if (ignoredEntries.length > 0 && !args.json) {
|
|
1487
|
+
console.log("Ignoring files not meant for deployment:");
|
|
1488
|
+
for (const [reason, count] of ignoredEntries) {
|
|
1489
|
+
console.log(` - ${reason}: ${count}`);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
if (files.length === 0) {
|
|
1493
|
+
throw new Error(
|
|
1494
|
+
`No deployable files found in directory: ${args.dir}. Check your path and ignore rules.`
|
|
1495
|
+
);
|
|
1496
|
+
}
|
|
1000
1497
|
} else if (args.repo) {
|
|
1001
1498
|
repo = args.repo;
|
|
1002
1499
|
}
|
|
@@ -1009,19 +1506,53 @@ const deployCommand = {
|
|
|
1009
1506
|
}
|
|
1010
1507
|
}
|
|
1011
1508
|
}
|
|
1509
|
+
const buildEnvVars = {};
|
|
1510
|
+
if (args.buildEnv) {
|
|
1511
|
+
for (const envVar of args.buildEnv) {
|
|
1512
|
+
const [key, ...valueParts] = envVar.split("=");
|
|
1513
|
+
if (key) {
|
|
1514
|
+
buildEnvVars[key] = valueParts.join("=");
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
let build;
|
|
1519
|
+
if (args.build) {
|
|
1520
|
+
if (args.buildCommand) {
|
|
1521
|
+
build = {
|
|
1522
|
+
command: args.buildCommand,
|
|
1523
|
+
...args.buildOutDir ? { outDir: args.buildOutDir } : {},
|
|
1524
|
+
...Object.keys(buildEnvVars).length > 0 ? { envVars: buildEnvVars } : {}
|
|
1525
|
+
};
|
|
1526
|
+
} else {
|
|
1527
|
+
build = true;
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1012
1530
|
console.log("Creating deployment...");
|
|
1013
|
-
const
|
|
1531
|
+
const domains = args.domain?.filter(Boolean);
|
|
1532
|
+
const createDeploymentRequest = {
|
|
1014
1533
|
...code ? { code } : {},
|
|
1534
|
+
...files ? { files } : {},
|
|
1015
1535
|
...repo ? { repo } : {},
|
|
1016
|
-
|
|
1017
|
-
|
|
1536
|
+
...build ? { build } : {},
|
|
1537
|
+
...entrypointPath ? { entrypointPath } : {},
|
|
1538
|
+
...nextjsOptimization ? {
|
|
1539
|
+
experimental: {
|
|
1540
|
+
nextjsOptimization: true
|
|
1541
|
+
}
|
|
1542
|
+
} : {},
|
|
1543
|
+
...domains && domains.length > 0 ? { domains } : {},
|
|
1544
|
+
envVars: Object.keys(env).length > 0 ? env : void 0
|
|
1545
|
+
};
|
|
1546
|
+
const result = await freestyle.serverless.deployments.create(
|
|
1547
|
+
createDeploymentRequest
|
|
1548
|
+
);
|
|
1018
1549
|
if (args.json) {
|
|
1019
1550
|
console.log(JSON.stringify(result, null, 2));
|
|
1020
1551
|
} else {
|
|
1021
1552
|
console.log("\n\u2713 Deployment created successfully!");
|
|
1022
1553
|
console.log(` Deployment ID: ${result.deploymentId}`);
|
|
1023
|
-
if (result.
|
|
1024
|
-
console.log(`
|
|
1554
|
+
if (result.domains.length > 0) {
|
|
1555
|
+
console.log(` Domains: ${result.domains.join(", ")}`);
|
|
1025
1556
|
}
|
|
1026
1557
|
}
|
|
1027
1558
|
} catch (error) {
|
|
@@ -1708,6 +2239,9 @@ const cronCommand = {
|
|
|
1708
2239
|
type: "string",
|
|
1709
2240
|
description: "Cron expression",
|
|
1710
2241
|
demandOption: true
|
|
2242
|
+
}).option("name", {
|
|
2243
|
+
type: "string",
|
|
2244
|
+
description: "Optional display name for the cron schedule"
|
|
1711
2245
|
}).option("timezone", {
|
|
1712
2246
|
type: "string",
|
|
1713
2247
|
description: "Timezone (default: UTC)",
|
|
@@ -1739,6 +2273,7 @@ const cronCommand = {
|
|
|
1739
2273
|
}
|
|
1740
2274
|
const { job } = await freestyle.cron.schedule({
|
|
1741
2275
|
deploymentId: args.deploymentId,
|
|
2276
|
+
name: args.name,
|
|
1742
2277
|
cron: args.cron,
|
|
1743
2278
|
timezone: args.timezone,
|
|
1744
2279
|
payload: parsedPayload,
|
|
@@ -1750,6 +2285,7 @@ const cronCommand = {
|
|
|
1750
2285
|
console.log("\u2713 Cron schedule created");
|
|
1751
2286
|
console.log(` Schedule ID: ${job.schedule.id}`);
|
|
1752
2287
|
console.log(` Deployment ID: ${job.schedule.deploymentId}`);
|
|
2288
|
+
console.log(` Name: ${job.schedule.name ?? "-"}`);
|
|
1753
2289
|
console.log(` Cron: ${job.schedule.cron}`);
|
|
1754
2290
|
console.log(` Timezone: ${job.schedule.timezone}`);
|
|
1755
2291
|
console.log(` Active: ${job.schedule.active ? "yes" : "no"}`);
|
|
@@ -1795,13 +2331,14 @@ const cronCommand = {
|
|
|
1795
2331
|
}
|
|
1796
2332
|
const rows = jobs.map((job) => [
|
|
1797
2333
|
job.schedule.id,
|
|
2334
|
+
job.schedule.name ?? "-",
|
|
1798
2335
|
job.schedule.deploymentId,
|
|
1799
2336
|
job.schedule.cron,
|
|
1800
2337
|
job.schedule.timezone,
|
|
1801
2338
|
job.schedule.active ? "active" : "disabled"
|
|
1802
2339
|
]);
|
|
1803
2340
|
formatTable(
|
|
1804
|
-
["Schedule ID", "Deployment", "Cron", "Timezone", "Status"],
|
|
2341
|
+
["Schedule ID", "Name", "Deployment", "Cron", "Timezone", "Status"],
|
|
1805
2342
|
rows
|
|
1806
2343
|
);
|
|
1807
2344
|
} catch (error) {
|
|
@@ -2096,6 +2633,287 @@ const whoamiCommand = {
|
|
|
2096
2633
|
}
|
|
2097
2634
|
};
|
|
2098
2635
|
|
|
2636
|
+
const FILTER_OPTIONS = [
|
|
2637
|
+
"vm",
|
|
2638
|
+
"deployment",
|
|
2639
|
+
"domain",
|
|
2640
|
+
"run",
|
|
2641
|
+
"request",
|
|
2642
|
+
"build"
|
|
2643
|
+
];
|
|
2644
|
+
function normalizeTimeOption(value) {
|
|
2645
|
+
if (!value) {
|
|
2646
|
+
return void 0;
|
|
2647
|
+
}
|
|
2648
|
+
const match = value.match(/^(\d+)(ms|s|m|h|d)$/);
|
|
2649
|
+
if (!match) {
|
|
2650
|
+
return value;
|
|
2651
|
+
}
|
|
2652
|
+
const amount = Number(match[1]);
|
|
2653
|
+
const unit = match[2];
|
|
2654
|
+
const multiplier = unit === "ms" ? 1 : unit === "s" ? 1e3 : unit === "m" ? 6e4 : unit === "h" ? 36e5 : 864e5;
|
|
2655
|
+
return new Date(Date.now() - amount * multiplier).toISOString();
|
|
2656
|
+
}
|
|
2657
|
+
function parseDurationMs(value) {
|
|
2658
|
+
if (!value) {
|
|
2659
|
+
return void 0;
|
|
2660
|
+
}
|
|
2661
|
+
const numericValue = Number(value);
|
|
2662
|
+
if (Number.isFinite(numericValue)) {
|
|
2663
|
+
return numericValue;
|
|
2664
|
+
}
|
|
2665
|
+
const match = value.match(/^(\d+)(ms|s|m|h|d)$/);
|
|
2666
|
+
if (!match) {
|
|
2667
|
+
throw new Error(
|
|
2668
|
+
`Invalid --timeout value '${value}'. Use milliseconds or a duration like 30s, 5m, or 1h.`
|
|
2669
|
+
);
|
|
2670
|
+
}
|
|
2671
|
+
const amount = Number(match[1]);
|
|
2672
|
+
const unit = match[2];
|
|
2673
|
+
const multiplier = unit === "ms" ? 1 : unit === "s" ? 1e3 : unit === "m" ? 6e4 : unit === "h" ? 36e5 : 864e5;
|
|
2674
|
+
return amount * multiplier;
|
|
2675
|
+
}
|
|
2676
|
+
function formatDurationMs(ms) {
|
|
2677
|
+
if (ms % 864e5 === 0) {
|
|
2678
|
+
return `${ms / 864e5}d`;
|
|
2679
|
+
}
|
|
2680
|
+
if (ms % 36e5 === 0) {
|
|
2681
|
+
return `${ms / 36e5}h`;
|
|
2682
|
+
}
|
|
2683
|
+
if (ms % 6e4 === 0) {
|
|
2684
|
+
return `${ms / 6e4}m`;
|
|
2685
|
+
}
|
|
2686
|
+
if (ms % 1e3 === 0) {
|
|
2687
|
+
return `${ms / 1e3}s`;
|
|
2688
|
+
}
|
|
2689
|
+
return `${ms}ms`;
|
|
2690
|
+
}
|
|
2691
|
+
function buildLogsQuery(args) {
|
|
2692
|
+
const query = {
|
|
2693
|
+
pageSize: args.limit,
|
|
2694
|
+
startTime: normalizeTimeOption(args.since),
|
|
2695
|
+
endTime: normalizeTimeOption(args.until),
|
|
2696
|
+
search: args.search,
|
|
2697
|
+
instanceId: args.instance,
|
|
2698
|
+
vmService: args.service,
|
|
2699
|
+
resourceType: args.resourceType
|
|
2700
|
+
};
|
|
2701
|
+
if (args.vm) {
|
|
2702
|
+
query.vmId = args.vm;
|
|
2703
|
+
} else if (args.deployment) {
|
|
2704
|
+
query.deploymentId = args.deployment;
|
|
2705
|
+
} else if (args.domain) {
|
|
2706
|
+
query.domain = args.domain;
|
|
2707
|
+
} else if (args.run) {
|
|
2708
|
+
query.runId = args.run;
|
|
2709
|
+
} else if (args.request) {
|
|
2710
|
+
query.requestId = args.request;
|
|
2711
|
+
} else if (args.build) {
|
|
2712
|
+
query.buildId = args.build;
|
|
2713
|
+
}
|
|
2714
|
+
return query;
|
|
2715
|
+
}
|
|
2716
|
+
function logEntryKey(entry) {
|
|
2717
|
+
return [
|
|
2718
|
+
entry.timestamp,
|
|
2719
|
+
entry.resourceType ?? "",
|
|
2720
|
+
entry.resourceId ?? "",
|
|
2721
|
+
entry.instanceId ?? "",
|
|
2722
|
+
entry.vmService ?? "",
|
|
2723
|
+
entry.source ?? "",
|
|
2724
|
+
entry.message
|
|
2725
|
+
].join(" ");
|
|
2726
|
+
}
|
|
2727
|
+
function sortLogsAscending(logs) {
|
|
2728
|
+
return [...logs].sort((left, right) => {
|
|
2729
|
+
const leftTime = Date.parse(left.timestamp);
|
|
2730
|
+
const rightTime = Date.parse(right.timestamp);
|
|
2731
|
+
if (!Number.isNaN(leftTime) && !Number.isNaN(rightTime)) {
|
|
2732
|
+
return leftTime - rightTime;
|
|
2733
|
+
}
|
|
2734
|
+
return left.timestamp.localeCompare(right.timestamp);
|
|
2735
|
+
});
|
|
2736
|
+
}
|
|
2737
|
+
function formatLogEntry(entry) {
|
|
2738
|
+
const labels = [
|
|
2739
|
+
entry.resourceType && entry.resourceId ? `${entry.resourceType}:${entry.resourceId}` : entry.resourceId,
|
|
2740
|
+
entry.instanceId ? `instance:${entry.instanceId}` : void 0,
|
|
2741
|
+
entry.vmService ? `service:${entry.vmService}` : void 0,
|
|
2742
|
+
entry.buildId ? `build:${entry.buildId}` : void 0,
|
|
2743
|
+
entry.level,
|
|
2744
|
+
entry.source
|
|
2745
|
+
].filter((value) => Boolean(value));
|
|
2746
|
+
const prefix = labels.length > 0 ? ` [${labels.join(" ")}]` : "";
|
|
2747
|
+
const message = entry.message.replace(/\n$/, "");
|
|
2748
|
+
return `${entry.timestamp}${prefix} ${message}`;
|
|
2749
|
+
}
|
|
2750
|
+
function printLogEntry(entry, json) {
|
|
2751
|
+
if (json) {
|
|
2752
|
+
console.log(JSON.stringify(entry));
|
|
2753
|
+
return;
|
|
2754
|
+
}
|
|
2755
|
+
console.log(formatLogEntry(entry));
|
|
2756
|
+
}
|
|
2757
|
+
const logsCommand = {
|
|
2758
|
+
command: "logs",
|
|
2759
|
+
describe: "Read observability logs",
|
|
2760
|
+
builder: (yargs) => {
|
|
2761
|
+
return yargs.option("vm", {
|
|
2762
|
+
type: "string",
|
|
2763
|
+
description: "VM ID to read logs for"
|
|
2764
|
+
}).option("deployment", {
|
|
2765
|
+
type: "string",
|
|
2766
|
+
description: "Deployment ID to read logs for"
|
|
2767
|
+
}).option("domain", {
|
|
2768
|
+
type: "string",
|
|
2769
|
+
description: "Domain to read deployment logs for"
|
|
2770
|
+
}).option("run", {
|
|
2771
|
+
type: "string",
|
|
2772
|
+
description: "Run ID to read logs for"
|
|
2773
|
+
}).option("request", {
|
|
2774
|
+
type: "string",
|
|
2775
|
+
description: "Background request ID to read logs for"
|
|
2776
|
+
}).option("build", {
|
|
2777
|
+
type: "string",
|
|
2778
|
+
description: "Build ID to read logs for"
|
|
2779
|
+
}).option("instance", {
|
|
2780
|
+
type: "string",
|
|
2781
|
+
description: "VM or deployment instance ID filter"
|
|
2782
|
+
}).option("service", {
|
|
2783
|
+
type: "string",
|
|
2784
|
+
description: "VM systemd service name filter"
|
|
2785
|
+
}).option("search", {
|
|
2786
|
+
type: "string",
|
|
2787
|
+
description: "Case-insensitive substring search"
|
|
2788
|
+
}).option("since", {
|
|
2789
|
+
type: "string",
|
|
2790
|
+
description: "Start time as RFC3339 or duration like 5m, 2h, 1d"
|
|
2791
|
+
}).option("until", {
|
|
2792
|
+
type: "string",
|
|
2793
|
+
description: "End time as RFC3339 or duration like 5m, 2h, 1d"
|
|
2794
|
+
}).option("limit", {
|
|
2795
|
+
type: "number",
|
|
2796
|
+
description: "Maximum logs per poll",
|
|
2797
|
+
default: 100
|
|
2798
|
+
}).option("stream", {
|
|
2799
|
+
type: "boolean",
|
|
2800
|
+
description: "Poll for new logs until interrupted",
|
|
2801
|
+
default: false
|
|
2802
|
+
}).option("interval", {
|
|
2803
|
+
type: "number",
|
|
2804
|
+
description: "Polling interval in milliseconds when streaming",
|
|
2805
|
+
default: 2e3
|
|
2806
|
+
}).option("timeout", {
|
|
2807
|
+
type: "string",
|
|
2808
|
+
description: "Stop streaming after this long without new logs, like 30s, 5m, or milliseconds"
|
|
2809
|
+
}).option("json", {
|
|
2810
|
+
type: "boolean",
|
|
2811
|
+
description: "Output JSON",
|
|
2812
|
+
default: false
|
|
2813
|
+
}).option("resource-type", {
|
|
2814
|
+
choices: ["deployment", "run", "vm"],
|
|
2815
|
+
description: "Account-wide resource type filter"
|
|
2816
|
+
}).check((argv) => {
|
|
2817
|
+
const filters = FILTER_OPTIONS.filter((option) => Boolean(argv[option]));
|
|
2818
|
+
if (filters.length > 1) {
|
|
2819
|
+
throw new Error(
|
|
2820
|
+
`Use only one log target filter: ${filters.map((filter) => `--${filter}`).join(", ")}`
|
|
2821
|
+
);
|
|
2822
|
+
}
|
|
2823
|
+
if (argv.service && !argv.vm) {
|
|
2824
|
+
throw new Error("--service can only be used with --vm");
|
|
2825
|
+
}
|
|
2826
|
+
if (argv.until && argv.stream) {
|
|
2827
|
+
throw new Error("--until cannot be used with --stream");
|
|
2828
|
+
}
|
|
2829
|
+
if (argv.timeout && !argv.stream) {
|
|
2830
|
+
throw new Error("--timeout can only be used with --stream");
|
|
2831
|
+
}
|
|
2832
|
+
const timeoutMs = parseDurationMs(
|
|
2833
|
+
typeof argv.timeout === "string" ? argv.timeout : void 0
|
|
2834
|
+
);
|
|
2835
|
+
if (timeoutMs !== void 0 && timeoutMs <= 0) {
|
|
2836
|
+
throw new Error("--timeout must be greater than 0");
|
|
2837
|
+
}
|
|
2838
|
+
if (typeof argv.interval === "number" && argv.interval <= 0) {
|
|
2839
|
+
throw new Error("--interval must be greater than 0");
|
|
2840
|
+
}
|
|
2841
|
+
if (typeof argv.limit === "number" && argv.limit <= 0) {
|
|
2842
|
+
throw new Error("--limit must be greater than 0");
|
|
2843
|
+
}
|
|
2844
|
+
return true;
|
|
2845
|
+
});
|
|
2846
|
+
},
|
|
2847
|
+
handler: async (argv) => {
|
|
2848
|
+
loadEnv();
|
|
2849
|
+
const args = argv;
|
|
2850
|
+
try {
|
|
2851
|
+
const freestyle = await getFreestyleClient();
|
|
2852
|
+
const query = buildLogsQuery(args);
|
|
2853
|
+
if (!args.stream) {
|
|
2854
|
+
const response = await freestyle.observability.getLogs(query);
|
|
2855
|
+
if (args.json) {
|
|
2856
|
+
console.log(JSON.stringify(response, null, 2));
|
|
2857
|
+
return;
|
|
2858
|
+
}
|
|
2859
|
+
for (const entry of sortLogsAscending(response.logs ?? [])) {
|
|
2860
|
+
printLogEntry(entry, false);
|
|
2861
|
+
}
|
|
2862
|
+
return;
|
|
2863
|
+
}
|
|
2864
|
+
const abortController = new AbortController();
|
|
2865
|
+
const timeoutMs = parseDurationMs(args.timeout);
|
|
2866
|
+
const seenLogs = /* @__PURE__ */ new Set();
|
|
2867
|
+
let timeout;
|
|
2868
|
+
let stoppedAfterTimeout = false;
|
|
2869
|
+
const resetTimeout = () => {
|
|
2870
|
+
if (timeoutMs === void 0) {
|
|
2871
|
+
return;
|
|
2872
|
+
}
|
|
2873
|
+
if (timeout) {
|
|
2874
|
+
clearTimeout(timeout);
|
|
2875
|
+
}
|
|
2876
|
+
timeout = setTimeout(() => {
|
|
2877
|
+
stoppedAfterTimeout = true;
|
|
2878
|
+
abortController.abort();
|
|
2879
|
+
}, timeoutMs);
|
|
2880
|
+
};
|
|
2881
|
+
const handleSigint = () => abortController.abort();
|
|
2882
|
+
process.once("SIGINT", handleSigint);
|
|
2883
|
+
if (!args.json) {
|
|
2884
|
+
console.error("Streaming logs. Press Ctrl-C to stop.");
|
|
2885
|
+
}
|
|
2886
|
+
try {
|
|
2887
|
+
resetTimeout();
|
|
2888
|
+
for await (const entry of freestyle.observability.streamLogs({
|
|
2889
|
+
...query,
|
|
2890
|
+
intervalMs: args.interval,
|
|
2891
|
+
signal: abortController.signal,
|
|
2892
|
+
includeExisting: Boolean(query.startTime)
|
|
2893
|
+
})) {
|
|
2894
|
+
const key = logEntryKey(entry);
|
|
2895
|
+
if (seenLogs.has(key)) {
|
|
2896
|
+
continue;
|
|
2897
|
+
}
|
|
2898
|
+
seenLogs.add(key);
|
|
2899
|
+
printLogEntry(entry, Boolean(args.json));
|
|
2900
|
+
resetTimeout();
|
|
2901
|
+
}
|
|
2902
|
+
} finally {
|
|
2903
|
+
if (timeout) {
|
|
2904
|
+
clearTimeout(timeout);
|
|
2905
|
+
}
|
|
2906
|
+
process.off("SIGINT", handleSigint);
|
|
2907
|
+
}
|
|
2908
|
+
if (stoppedAfterTimeout && !args.json && timeoutMs !== void 0) {
|
|
2909
|
+
console.error(`No logs received for ${formatDurationMs(timeoutMs)}; stopped streaming.`);
|
|
2910
|
+
}
|
|
2911
|
+
} catch (error) {
|
|
2912
|
+
handleError(error);
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
};
|
|
2916
|
+
|
|
2099
2917
|
dotenv.config({ quiet: true });
|
|
2100
2918
|
yargs(hideBin(process.argv)).scriptName("freestyle").usage("$0 <command> [options]").option("team", {
|
|
2101
2919
|
type: "string",
|
|
@@ -2105,4 +2923,4 @@ yargs(hideBin(process.argv)).scriptName("freestyle").usage("$0 <command> [option
|
|
|
2105
2923
|
if (argv.team && typeof argv.team === "string") {
|
|
2106
2924
|
process.env.FREESTYLE_TEAM_ID = argv.team;
|
|
2107
2925
|
}
|
|
2108
|
-
}).command(vmCommand).command(gitCommand).command(domainsCommand).command(cronCommand).command(loginCommand).command(logoutCommand).command(whoamiCommand).command(deployCommand).command(runCommand).demandCommand(1, "You need to specify a command").help().alias("help", "h").version().alias("version", "v").strict().parse();
|
|
2926
|
+
}).command(vmCommand).command(gitCommand).command(domainsCommand).command(cronCommand).command(loginCommand).command(logoutCommand).command(whoamiCommand).command(logsCommand).command(deployCommand).command(runCommand).demandCommand(1, "You need to specify a command").help().alias("help", "h").version().alias("version", "v").strict().parse();
|