openship 0.2.2 → 0.3.0
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 +208 -0
- package/dist/index.js +1114 -325
- package/dist/server/index.js +4801 -55475
- package/dist/server/migrations/0056_moaning_daredevil.sql +2 -0
- package/dist/server/migrations/meta/0056_snapshot.json +9191 -0
- package/dist/server/migrations/meta/_journal.json +7 -0
- package/package.json +8 -4
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command27 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/lib/output.ts
|
|
7
7
|
import chalk from "chalk";
|
|
@@ -1216,7 +1216,13 @@ function findClosingBracket(body, start) {
|
|
|
1216
1216
|
const ch = body[i];
|
|
1217
1217
|
const prev = body[i - 1];
|
|
1218
1218
|
if (inDouble) {
|
|
1219
|
-
if (ch === '"'
|
|
1219
|
+
if (ch === '"') {
|
|
1220
|
+
let backslashRun = 0;
|
|
1221
|
+
for (let j = i - 1; j >= 0 && body[j] === "\\"; j--) {
|
|
1222
|
+
backslashRun++;
|
|
1223
|
+
}
|
|
1224
|
+
if (backslashRun % 2 === 0) inDouble = false;
|
|
1225
|
+
}
|
|
1220
1226
|
continue;
|
|
1221
1227
|
}
|
|
1222
1228
|
if (inSingle) {
|
|
@@ -1807,6 +1813,425 @@ var LANGUAGE_MANIFEST_FILES = Array.from(
|
|
|
1807
1813
|
new Set(LANGUAGE_DETECTORS.flatMap((d) => d.manifestFiles.map((f) => f.toLowerCase())))
|
|
1808
1814
|
);
|
|
1809
1815
|
|
|
1816
|
+
// ../../packages/core/src/openship-config/schema.ts
|
|
1817
|
+
var OPENSHIP_RUNTIMES = ["bare", "docker"];
|
|
1818
|
+
var OPENSHIP_PRODUCTION_MODES = [
|
|
1819
|
+
"host",
|
|
1820
|
+
"static",
|
|
1821
|
+
"standalone"
|
|
1822
|
+
];
|
|
1823
|
+
var OPENSHIP_DOMAIN_TYPES = ["free", "custom"];
|
|
1824
|
+
var OPENSHIP_RESTARTS = [
|
|
1825
|
+
"no",
|
|
1826
|
+
"always",
|
|
1827
|
+
"on-failure",
|
|
1828
|
+
"unless-stopped"
|
|
1829
|
+
];
|
|
1830
|
+
var OPENSHIP_RESOURCE_TIERS = [
|
|
1831
|
+
"micro",
|
|
1832
|
+
"low",
|
|
1833
|
+
"medium",
|
|
1834
|
+
"high"
|
|
1835
|
+
];
|
|
1836
|
+
|
|
1837
|
+
// ../../packages/core/src/openship-config/parse.ts
|
|
1838
|
+
var TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
1839
|
+
"$schema",
|
|
1840
|
+
"framework",
|
|
1841
|
+
"packageManager",
|
|
1842
|
+
"rootDirectory",
|
|
1843
|
+
"installCommand",
|
|
1844
|
+
"buildCommand",
|
|
1845
|
+
"startCommand",
|
|
1846
|
+
"outputDirectory",
|
|
1847
|
+
"buildImage",
|
|
1848
|
+
"productionPaths",
|
|
1849
|
+
"runtime",
|
|
1850
|
+
"productionMode",
|
|
1851
|
+
"port",
|
|
1852
|
+
"env",
|
|
1853
|
+
"domains",
|
|
1854
|
+
"routes",
|
|
1855
|
+
"resources",
|
|
1856
|
+
"services",
|
|
1857
|
+
"monorepo"
|
|
1858
|
+
]);
|
|
1859
|
+
var Ctx = class {
|
|
1860
|
+
errors = [];
|
|
1861
|
+
warnings = [];
|
|
1862
|
+
err(path2, msg) {
|
|
1863
|
+
this.errors.push(`${path2}: ${msg}`);
|
|
1864
|
+
}
|
|
1865
|
+
isObj(v) {
|
|
1866
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1867
|
+
}
|
|
1868
|
+
str(v, path2) {
|
|
1869
|
+
if (v === void 0) return void 0;
|
|
1870
|
+
if (typeof v !== "string") {
|
|
1871
|
+
this.err(path2, "must be a string");
|
|
1872
|
+
return void 0;
|
|
1873
|
+
}
|
|
1874
|
+
return v;
|
|
1875
|
+
}
|
|
1876
|
+
bool(v, path2) {
|
|
1877
|
+
if (v === void 0) return void 0;
|
|
1878
|
+
if (typeof v !== "boolean") {
|
|
1879
|
+
this.err(path2, "must be a boolean");
|
|
1880
|
+
return void 0;
|
|
1881
|
+
}
|
|
1882
|
+
return v;
|
|
1883
|
+
}
|
|
1884
|
+
int(v, path2, min, max) {
|
|
1885
|
+
if (v === void 0) return void 0;
|
|
1886
|
+
const n = typeof v === "string" ? Number(v) : v;
|
|
1887
|
+
if (typeof n !== "number" || !Number.isFinite(n)) {
|
|
1888
|
+
this.err(path2, "must be a number");
|
|
1889
|
+
return void 0;
|
|
1890
|
+
}
|
|
1891
|
+
if (n < min || n > max) {
|
|
1892
|
+
this.err(path2, `must be between ${min} and ${max}`);
|
|
1893
|
+
return void 0;
|
|
1894
|
+
}
|
|
1895
|
+
return n;
|
|
1896
|
+
}
|
|
1897
|
+
strArray(v, path2) {
|
|
1898
|
+
if (v === void 0) return void 0;
|
|
1899
|
+
if (!Array.isArray(v)) {
|
|
1900
|
+
this.err(path2, "must be an array of strings");
|
|
1901
|
+
return void 0;
|
|
1902
|
+
}
|
|
1903
|
+
const out = [];
|
|
1904
|
+
v.forEach((item, i) => {
|
|
1905
|
+
if (typeof item !== "string") this.err(`${path2}[${i}]`, "must be a string");
|
|
1906
|
+
else out.push(item);
|
|
1907
|
+
});
|
|
1908
|
+
return out;
|
|
1909
|
+
}
|
|
1910
|
+
enumOf(v, path2, allowed) {
|
|
1911
|
+
if (v === void 0) return void 0;
|
|
1912
|
+
if (typeof v !== "string" || !allowed.includes(v)) {
|
|
1913
|
+
this.err(path2, `must be one of: ${allowed.join(", ")}`);
|
|
1914
|
+
return void 0;
|
|
1915
|
+
}
|
|
1916
|
+
return v;
|
|
1917
|
+
}
|
|
1918
|
+
};
|
|
1919
|
+
function parseEnv(ctx, v, path2) {
|
|
1920
|
+
if (v === void 0) return void 0;
|
|
1921
|
+
if (!ctx.isObj(v)) {
|
|
1922
|
+
ctx.err(path2, "must be an object of environment variables");
|
|
1923
|
+
return void 0;
|
|
1924
|
+
}
|
|
1925
|
+
const out = {};
|
|
1926
|
+
for (const [key, val] of Object.entries(v)) {
|
|
1927
|
+
if (typeof val === "string") {
|
|
1928
|
+
out[key] = val;
|
|
1929
|
+
} else if (ctx.isObj(val)) {
|
|
1930
|
+
if (val.value === void 0) ctx.err(`${path2}.${key}.value`, "is required");
|
|
1931
|
+
const value = ctx.str(val.value, `${path2}.${key}.value`);
|
|
1932
|
+
const secret = ctx.bool(val.secret, `${path2}.${key}.secret`);
|
|
1933
|
+
if (value !== void 0) out[key] = { value, ...secret !== void 0 ? { secret } : {} };
|
|
1934
|
+
} else {
|
|
1935
|
+
ctx.err(`${path2}.${key}`, 'must be a string or { "value", "secret" }');
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
return out;
|
|
1939
|
+
}
|
|
1940
|
+
function parseDomains(ctx, v, path2) {
|
|
1941
|
+
if (v === void 0) return void 0;
|
|
1942
|
+
if (!Array.isArray(v)) {
|
|
1943
|
+
ctx.err(path2, "must be an array of hostnames or domain objects");
|
|
1944
|
+
return void 0;
|
|
1945
|
+
}
|
|
1946
|
+
const out = [];
|
|
1947
|
+
v.forEach((item, i) => {
|
|
1948
|
+
const p = `${path2}[${i}]`;
|
|
1949
|
+
if (typeof item === "string") {
|
|
1950
|
+
out.push({ domain: item });
|
|
1951
|
+
} else if (ctx.isObj(item)) {
|
|
1952
|
+
const domain = ctx.str(item.domain, `${p}.domain`);
|
|
1953
|
+
if (!domain) {
|
|
1954
|
+
ctx.err(p, "requires a `domain`");
|
|
1955
|
+
return;
|
|
1956
|
+
}
|
|
1957
|
+
out.push({
|
|
1958
|
+
domain,
|
|
1959
|
+
port: ctx.int(item.port, `${p}.port`, 1, 65535),
|
|
1960
|
+
targetPath: ctx.str(item.targetPath, `${p}.targetPath`),
|
|
1961
|
+
type: ctx.enumOf(item.type, `${p}.type`, OPENSHIP_DOMAIN_TYPES)
|
|
1962
|
+
});
|
|
1963
|
+
} else {
|
|
1964
|
+
ctx.err(p, "must be a hostname string or a domain object");
|
|
1965
|
+
}
|
|
1966
|
+
});
|
|
1967
|
+
return out;
|
|
1968
|
+
}
|
|
1969
|
+
function parseRoutes(ctx, v, path2) {
|
|
1970
|
+
if (v === void 0) return void 0;
|
|
1971
|
+
if (!ctx.isObj(v)) {
|
|
1972
|
+
ctx.err(path2, "must be an object");
|
|
1973
|
+
return void 0;
|
|
1974
|
+
}
|
|
1975
|
+
const routes = {};
|
|
1976
|
+
const rule = (item, p) => {
|
|
1977
|
+
if (!ctx.isObj(item)) {
|
|
1978
|
+
ctx.err(p, "must be an object with `source` and `destination`");
|
|
1979
|
+
return null;
|
|
1980
|
+
}
|
|
1981
|
+
const source = ctx.str(item.source, `${p}.source`);
|
|
1982
|
+
const destination = ctx.str(item.destination, `${p}.destination`);
|
|
1983
|
+
return source && destination ? { source, destination } : null;
|
|
1984
|
+
};
|
|
1985
|
+
if (Array.isArray(v.rewrites)) {
|
|
1986
|
+
routes.rewrites = v.rewrites.map((r, i) => rule(r, `${path2}.rewrites[${i}]`)).filter(Boolean);
|
|
1987
|
+
} else if (v.rewrites !== void 0) ctx.err(`${path2}.rewrites`, "must be an array");
|
|
1988
|
+
if (Array.isArray(v.redirects)) {
|
|
1989
|
+
routes.redirects = v.redirects.map((r, i) => {
|
|
1990
|
+
const base = rule(r, `${path2}.redirects[${i}]`);
|
|
1991
|
+
if (!base) return null;
|
|
1992
|
+
const o = r;
|
|
1993
|
+
return {
|
|
1994
|
+
...base,
|
|
1995
|
+
permanent: ctx.bool(o.permanent, `${path2}.redirects[${i}].permanent`),
|
|
1996
|
+
statusCode: ctx.int(o.statusCode, `${path2}.redirects[${i}].statusCode`, 300, 399)
|
|
1997
|
+
};
|
|
1998
|
+
}).filter(Boolean);
|
|
1999
|
+
} else if (v.redirects !== void 0) ctx.err(`${path2}.redirects`, "must be an array");
|
|
2000
|
+
if (Array.isArray(v.headers)) {
|
|
2001
|
+
routes.headers = v.headers.map((h, i) => {
|
|
2002
|
+
const p = `${path2}.headers[${i}]`;
|
|
2003
|
+
if (!ctx.isObj(h)) {
|
|
2004
|
+
ctx.err(p, "must be an object");
|
|
2005
|
+
return null;
|
|
2006
|
+
}
|
|
2007
|
+
const source = ctx.str(h.source, `${p}.source`);
|
|
2008
|
+
const list2 = Array.isArray(h.headers) ? h.headers.map((kv, j) => {
|
|
2009
|
+
const key = ctx.str(kv?.key, `${p}.headers[${j}].key`);
|
|
2010
|
+
const value = ctx.str(kv?.value, `${p}.headers[${j}].value`);
|
|
2011
|
+
return key && value !== void 0 ? { key, value } : null;
|
|
2012
|
+
}).filter(Boolean) : [];
|
|
2013
|
+
return source ? { source, headers: list2 } : null;
|
|
2014
|
+
}).filter(Boolean);
|
|
2015
|
+
} else if (v.headers !== void 0) ctx.err(`${path2}.headers`, "must be an array");
|
|
2016
|
+
const cleanUrls = ctx.bool(v.cleanUrls, `${path2}.cleanUrls`);
|
|
2017
|
+
const trailingSlash = ctx.bool(v.trailingSlash, `${path2}.trailingSlash`);
|
|
2018
|
+
if (cleanUrls !== void 0) routes.cleanUrls = cleanUrls;
|
|
2019
|
+
if (trailingSlash !== void 0) routes.trailingSlash = trailingSlash;
|
|
2020
|
+
return routes;
|
|
2021
|
+
}
|
|
2022
|
+
function parseResources(ctx, v, path2) {
|
|
2023
|
+
if (v === void 0) return void 0;
|
|
2024
|
+
if (!ctx.isObj(v)) {
|
|
2025
|
+
ctx.err(path2, "must be an object");
|
|
2026
|
+
return void 0;
|
|
2027
|
+
}
|
|
2028
|
+
const r = {
|
|
2029
|
+
tier: ctx.enumOf(v.tier, `${path2}.tier`, OPENSHIP_RESOURCE_TIERS),
|
|
2030
|
+
cpuCores: ctx.int(v.cpuCores, `${path2}.cpuCores`, 0.25, 4),
|
|
2031
|
+
memoryMb: ctx.int(v.memoryMb, `${path2}.memoryMb`, 128, 8192),
|
|
2032
|
+
diskMb: ctx.int(v.diskMb, `${path2}.diskMb`, 64, 204800)
|
|
2033
|
+
};
|
|
2034
|
+
return r;
|
|
2035
|
+
}
|
|
2036
|
+
function parseHealthcheck(ctx, v, path2) {
|
|
2037
|
+
if (v === void 0) return void 0;
|
|
2038
|
+
if (!ctx.isObj(v)) {
|
|
2039
|
+
ctx.err(path2, "must be an object");
|
|
2040
|
+
return void 0;
|
|
2041
|
+
}
|
|
2042
|
+
const test = typeof v.test === "string" ? v.test : Array.isArray(v.test) ? ctx.strArray(v.test, `${path2}.test`) : v.test !== void 0 ? (ctx.err(`${path2}.test`, "must be a string or array of strings"), void 0) : void 0;
|
|
2043
|
+
return {
|
|
2044
|
+
test,
|
|
2045
|
+
interval: ctx.str(v.interval, `${path2}.interval`),
|
|
2046
|
+
timeout: ctx.str(v.timeout, `${path2}.timeout`),
|
|
2047
|
+
retries: ctx.int(v.retries, `${path2}.retries`, 0, 100),
|
|
2048
|
+
startPeriod: ctx.str(v.startPeriod, `${path2}.startPeriod`),
|
|
2049
|
+
disable: ctx.bool(v.disable, `${path2}.disable`)
|
|
2050
|
+
};
|
|
2051
|
+
}
|
|
2052
|
+
function parseServices(ctx, v, path2) {
|
|
2053
|
+
if (v === void 0) return void 0;
|
|
2054
|
+
if (!Array.isArray(v)) {
|
|
2055
|
+
ctx.err(path2, "must be an array of service objects");
|
|
2056
|
+
return void 0;
|
|
2057
|
+
}
|
|
2058
|
+
const out = [];
|
|
2059
|
+
v.forEach((item, i) => {
|
|
2060
|
+
const p = `${path2}[${i}]`;
|
|
2061
|
+
if (!ctx.isObj(item)) {
|
|
2062
|
+
ctx.err(p, "must be an object");
|
|
2063
|
+
return;
|
|
2064
|
+
}
|
|
2065
|
+
const name = ctx.str(item.name, `${p}.name`);
|
|
2066
|
+
if (!name) {
|
|
2067
|
+
ctx.err(p, "requires a `name`");
|
|
2068
|
+
return;
|
|
2069
|
+
}
|
|
2070
|
+
out.push({
|
|
2071
|
+
name,
|
|
2072
|
+
image: ctx.str(item.image, `${p}.image`),
|
|
2073
|
+
build: ctx.str(item.build, `${p}.build`),
|
|
2074
|
+
dockerfile: ctx.str(item.dockerfile, `${p}.dockerfile`),
|
|
2075
|
+
ports: ctx.strArray(item.ports, `${p}.ports`),
|
|
2076
|
+
volumes: ctx.strArray(item.volumes, `${p}.volumes`),
|
|
2077
|
+
dependsOn: ctx.strArray(item.dependsOn, `${p}.dependsOn`),
|
|
2078
|
+
env: parseEnv(ctx, item.env, `${p}.env`),
|
|
2079
|
+
command: ctx.str(item.command, `${p}.command`),
|
|
2080
|
+
restart: ctx.enumOf(item.restart, `${p}.restart`, OPENSHIP_RESTARTS),
|
|
2081
|
+
exposed: ctx.bool(item.exposed, `${p}.exposed`),
|
|
2082
|
+
exposedPort: ctx.str(item.exposedPort, `${p}.exposedPort`),
|
|
2083
|
+
domain: ctx.str(item.domain, `${p}.domain`),
|
|
2084
|
+
healthcheck: parseHealthcheck(ctx, item.healthcheck, `${p}.healthcheck`)
|
|
2085
|
+
});
|
|
2086
|
+
});
|
|
2087
|
+
return out;
|
|
2088
|
+
}
|
|
2089
|
+
function parseMonorepo(ctx, v, path2) {
|
|
2090
|
+
if (v === void 0) return void 0;
|
|
2091
|
+
if (!ctx.isObj(v)) {
|
|
2092
|
+
ctx.err(path2, "must be an object");
|
|
2093
|
+
return void 0;
|
|
2094
|
+
}
|
|
2095
|
+
const mono = {};
|
|
2096
|
+
if (v.workspace !== void 0) {
|
|
2097
|
+
if (!ctx.isObj(v.workspace)) ctx.err(`${path2}.workspace`, "must be an object");
|
|
2098
|
+
else {
|
|
2099
|
+
const pm = ctx.str(v.workspace.packageManager, `${path2}.workspace.packageManager`);
|
|
2100
|
+
if (pm) {
|
|
2101
|
+
mono.workspace = {
|
|
2102
|
+
packageManager: pm,
|
|
2103
|
+
prepareCommand: ctx.str(v.workspace.prepareCommand, `${path2}.workspace.prepareCommand`)
|
|
2104
|
+
};
|
|
2105
|
+
} else {
|
|
2106
|
+
ctx.err(`${path2}.workspace`, "requires a `packageManager`");
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
if (v.sharedPaths !== void 0) {
|
|
2111
|
+
ctx.warnings.push(`${path2}.sharedPaths is not applied yet (ignored)`);
|
|
2112
|
+
}
|
|
2113
|
+
if (v.apps !== void 0) {
|
|
2114
|
+
if (!Array.isArray(v.apps)) ctx.err(`${path2}.apps`, "must be an array");
|
|
2115
|
+
else {
|
|
2116
|
+
const apps = [];
|
|
2117
|
+
v.apps.forEach((a, i) => {
|
|
2118
|
+
const p = `${path2}.apps[${i}]`;
|
|
2119
|
+
if (!ctx.isObj(a)) {
|
|
2120
|
+
ctx.err(p, "must be an object");
|
|
2121
|
+
return;
|
|
2122
|
+
}
|
|
2123
|
+
const name = ctx.str(a.name, `${p}.name`);
|
|
2124
|
+
const rootDirectory = ctx.str(a.rootDirectory, `${p}.rootDirectory`);
|
|
2125
|
+
if (!name || !rootDirectory) {
|
|
2126
|
+
ctx.err(p, "requires `name` and `rootDirectory`");
|
|
2127
|
+
return;
|
|
2128
|
+
}
|
|
2129
|
+
apps.push({
|
|
2130
|
+
name,
|
|
2131
|
+
rootDirectory,
|
|
2132
|
+
framework: ctx.enumOf(a.framework, `${p}.framework`, STACK_IDS),
|
|
2133
|
+
packageManager: parsePackageManager(ctx, a.packageManager, `${p}.packageManager`),
|
|
2134
|
+
installCommand: ctx.str(a.installCommand, `${p}.installCommand`),
|
|
2135
|
+
buildCommand: ctx.str(a.buildCommand, `${p}.buildCommand`),
|
|
2136
|
+
startCommand: ctx.str(a.startCommand, `${p}.startCommand`),
|
|
2137
|
+
outputDirectory: ctx.str(a.outputDirectory, `${p}.outputDirectory`),
|
|
2138
|
+
buildImage: ctx.str(a.buildImage, `${p}.buildImage`),
|
|
2139
|
+
port: ctx.int(a.port, `${p}.port`, 1, 65535)
|
|
2140
|
+
});
|
|
2141
|
+
});
|
|
2142
|
+
mono.apps = apps;
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
return mono;
|
|
2146
|
+
}
|
|
2147
|
+
function parsePackageManager(ctx, v, path2) {
|
|
2148
|
+
const s = ctx.str(v, path2);
|
|
2149
|
+
if (s === void 0) return void 0;
|
|
2150
|
+
if (!ALL_PACKAGE_MANAGERS.includes(s)) {
|
|
2151
|
+
ctx.err(path2, `must be one of: ${ALL_PACKAGE_MANAGERS.join(", ")}`);
|
|
2152
|
+
return void 0;
|
|
2153
|
+
}
|
|
2154
|
+
return s;
|
|
2155
|
+
}
|
|
2156
|
+
function parseOpenshipConfig(raw) {
|
|
2157
|
+
const ctx = new Ctx();
|
|
2158
|
+
if (!ctx.isObj(raw)) {
|
|
2159
|
+
return { config: null, errors: ["openship.json must be a JSON object"], warnings: [] };
|
|
2160
|
+
}
|
|
2161
|
+
for (const key of Object.keys(raw)) {
|
|
2162
|
+
if (!TOP_LEVEL_KEYS.has(key)) ctx.warnings.push(`Unknown field "${key}" (ignored)`);
|
|
2163
|
+
}
|
|
2164
|
+
const config = {
|
|
2165
|
+
framework: ctx.enumOf(raw.framework, "framework", STACK_IDS),
|
|
2166
|
+
packageManager: parsePackageManager(ctx, raw.packageManager, "packageManager"),
|
|
2167
|
+
rootDirectory: ctx.str(raw.rootDirectory, "rootDirectory"),
|
|
2168
|
+
installCommand: ctx.str(raw.installCommand, "installCommand"),
|
|
2169
|
+
buildCommand: ctx.str(raw.buildCommand, "buildCommand"),
|
|
2170
|
+
startCommand: ctx.str(raw.startCommand, "startCommand"),
|
|
2171
|
+
outputDirectory: ctx.str(raw.outputDirectory, "outputDirectory"),
|
|
2172
|
+
buildImage: ctx.str(raw.buildImage, "buildImage"),
|
|
2173
|
+
productionPaths: ctx.strArray(raw.productionPaths, "productionPaths"),
|
|
2174
|
+
runtime: ctx.enumOf(raw.runtime, "runtime", OPENSHIP_RUNTIMES),
|
|
2175
|
+
productionMode: ctx.enumOf(raw.productionMode, "productionMode", OPENSHIP_PRODUCTION_MODES),
|
|
2176
|
+
port: ctx.int(raw.port, "port", 1, 65535),
|
|
2177
|
+
env: parseEnv(ctx, raw.env, "env"),
|
|
2178
|
+
domains: parseDomains(ctx, raw.domains, "domains"),
|
|
2179
|
+
routes: parseRoutes(ctx, raw.routes, "routes"),
|
|
2180
|
+
resources: parseResources(ctx, raw.resources, "resources"),
|
|
2181
|
+
services: parseServices(ctx, raw.services, "services"),
|
|
2182
|
+
monorepo: parseMonorepo(ctx, raw.monorepo, "monorepo")
|
|
2183
|
+
};
|
|
2184
|
+
for (const k of Object.keys(config)) {
|
|
2185
|
+
if (config[k] === void 0) delete config[k];
|
|
2186
|
+
}
|
|
2187
|
+
return { config, errors: ctx.errors, warnings: ctx.warnings };
|
|
2188
|
+
}
|
|
2189
|
+
function parseOpenshipConfigJson(text2) {
|
|
2190
|
+
let raw;
|
|
2191
|
+
try {
|
|
2192
|
+
raw = JSON.parse(text2);
|
|
2193
|
+
} catch (err2) {
|
|
2194
|
+
return {
|
|
2195
|
+
config: null,
|
|
2196
|
+
errors: [`invalid JSON: ${err2 instanceof Error ? err2.message : String(err2)}`],
|
|
2197
|
+
warnings: []
|
|
2198
|
+
};
|
|
2199
|
+
}
|
|
2200
|
+
return parseOpenshipConfig(raw);
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
// ../../packages/core/src/metadata/openship.ts
|
|
2204
|
+
var openshipMetadataParser = {
|
|
2205
|
+
source: "openship",
|
|
2206
|
+
files: ["openship.json"],
|
|
2207
|
+
parse(fileContents) {
|
|
2208
|
+
const raw = fileContents["openship.json"];
|
|
2209
|
+
if (!raw) return null;
|
|
2210
|
+
const { config } = parseOpenshipConfigJson(raw);
|
|
2211
|
+
if (!config) return null;
|
|
2212
|
+
const metadata = { source: "openship" };
|
|
2213
|
+
if (config.installCommand) metadata.installCommand = config.installCommand;
|
|
2214
|
+
if (config.buildCommand) metadata.buildCommand = config.buildCommand;
|
|
2215
|
+
if (config.outputDirectory) metadata.outputDirectory = config.outputDirectory;
|
|
2216
|
+
if (config.startCommand) metadata.startCommand = config.startCommand;
|
|
2217
|
+
if (config.framework) metadata.framework = config.framework;
|
|
2218
|
+
if (config.routes) metadata.routing = config.routes;
|
|
2219
|
+
const hasSignal = config.installCommand || config.buildCommand || config.outputDirectory || config.startCommand || config.framework || config.routes;
|
|
2220
|
+
return hasSignal ? metadata : null;
|
|
2221
|
+
}
|
|
2222
|
+
};
|
|
2223
|
+
|
|
2224
|
+
// ../../packages/core/src/metadata/text.ts
|
|
2225
|
+
function stripBom2(content) {
|
|
2226
|
+
return content.charCodeAt(0) === 65279 ? content.slice(1) : content;
|
|
2227
|
+
}
|
|
2228
|
+
function trimmed(value) {
|
|
2229
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
2230
|
+
}
|
|
2231
|
+
function splitLines(raw) {
|
|
2232
|
+
return stripBom2(raw).split(/\r?\n/);
|
|
2233
|
+
}
|
|
2234
|
+
|
|
1810
2235
|
// ../../packages/core/src/metadata/vercel.ts
|
|
1811
2236
|
var VERCEL_FRAMEWORK_TO_STACK = {
|
|
1812
2237
|
nextjs: "nextjs",
|
|
@@ -1821,9 +2246,6 @@ var VERCEL_FRAMEWORK_TO_STACK = {
|
|
|
1821
2246
|
angular: "angular",
|
|
1822
2247
|
"create-react-app": "cra"
|
|
1823
2248
|
};
|
|
1824
|
-
function trimmed(value) {
|
|
1825
|
-
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
1826
|
-
}
|
|
1827
2249
|
function isConditional(entry) {
|
|
1828
2250
|
return "has" in entry || "missing" in entry;
|
|
1829
2251
|
}
|
|
@@ -1942,10 +2364,152 @@ var vercelMetadataParser = {
|
|
|
1942
2364
|
}
|
|
1943
2365
|
};
|
|
1944
2366
|
|
|
1945
|
-
// ../../packages/core/src/metadata/
|
|
1946
|
-
function
|
|
1947
|
-
|
|
2367
|
+
// ../../packages/core/src/metadata/railway.ts
|
|
2368
|
+
function tomlScalar(rest) {
|
|
2369
|
+
const s = rest.trim();
|
|
2370
|
+
const quote = s[0];
|
|
2371
|
+
if (quote === '"' || quote === "'") {
|
|
2372
|
+
let out = "";
|
|
2373
|
+
for (let i = 1; i < s.length; i++) {
|
|
2374
|
+
const ch = s[i];
|
|
2375
|
+
if (quote === '"' && ch === "\\" && i + 1 < s.length) {
|
|
2376
|
+
const next = s[++i];
|
|
2377
|
+
switch (next) {
|
|
2378
|
+
case "t":
|
|
2379
|
+
out += " ";
|
|
2380
|
+
break;
|
|
2381
|
+
case "n":
|
|
2382
|
+
out += "\n";
|
|
2383
|
+
break;
|
|
2384
|
+
case "r":
|
|
2385
|
+
out += "\r";
|
|
2386
|
+
break;
|
|
2387
|
+
case "b":
|
|
2388
|
+
out += "\b";
|
|
2389
|
+
break;
|
|
2390
|
+
case "f":
|
|
2391
|
+
out += "\f";
|
|
2392
|
+
break;
|
|
2393
|
+
case '"':
|
|
2394
|
+
out += '"';
|
|
2395
|
+
break;
|
|
2396
|
+
case "\\":
|
|
2397
|
+
out += "\\";
|
|
2398
|
+
break;
|
|
2399
|
+
case "/":
|
|
2400
|
+
out += "/";
|
|
2401
|
+
break;
|
|
2402
|
+
case "u":
|
|
2403
|
+
case "U": {
|
|
2404
|
+
const width = next === "u" ? 4 : 8;
|
|
2405
|
+
const hex = s.slice(i + 1, i + 1 + width);
|
|
2406
|
+
if (hex.length === width && /^[0-9a-fA-F]+$/.test(hex)) {
|
|
2407
|
+
out += String.fromCodePoint(parseInt(hex, 16));
|
|
2408
|
+
i += width;
|
|
2409
|
+
} else {
|
|
2410
|
+
out += next;
|
|
2411
|
+
}
|
|
2412
|
+
break;
|
|
2413
|
+
}
|
|
2414
|
+
default:
|
|
2415
|
+
out += next;
|
|
2416
|
+
}
|
|
2417
|
+
continue;
|
|
2418
|
+
}
|
|
2419
|
+
if (ch === quote) return out;
|
|
2420
|
+
out += ch;
|
|
2421
|
+
}
|
|
2422
|
+
return void 0;
|
|
2423
|
+
}
|
|
2424
|
+
const bare = s.split("#")[0].trim();
|
|
2425
|
+
return bare.length > 0 ? bare : void 0;
|
|
1948
2426
|
}
|
|
2427
|
+
function parseRailwayToml(raw) {
|
|
2428
|
+
const cfg = {};
|
|
2429
|
+
let section = "";
|
|
2430
|
+
const lines = splitLines(raw);
|
|
2431
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2432
|
+
const line = lines[i];
|
|
2433
|
+
const header = line.match(/^\s*\[([^\]]+)\]\s*(?:#.*)?$/);
|
|
2434
|
+
if (header) {
|
|
2435
|
+
section = header[1].trim().toLowerCase();
|
|
2436
|
+
continue;
|
|
2437
|
+
}
|
|
2438
|
+
const kv = line.match(/^\s*([A-Za-z_][A-Za-z0-9_.-]*)\s*=\s*(.+)$/);
|
|
2439
|
+
if (!kv) continue;
|
|
2440
|
+
const [, rawKey, rest] = kv;
|
|
2441
|
+
const dot = rawKey.lastIndexOf(".");
|
|
2442
|
+
const table = dot >= 0 ? rawKey.slice(0, dot).toLowerCase() : section;
|
|
2443
|
+
const field = dot >= 0 ? rawKey.slice(dot + 1) : rawKey;
|
|
2444
|
+
let value;
|
|
2445
|
+
const triple = rest.match(/^("""|''')/);
|
|
2446
|
+
if (triple) {
|
|
2447
|
+
const delim = triple[1];
|
|
2448
|
+
const afterOpen = rest.slice(3);
|
|
2449
|
+
const close = afterOpen.indexOf(delim);
|
|
2450
|
+
if (close >= 0) {
|
|
2451
|
+
value = afterOpen.slice(0, close).trim() || void 0;
|
|
2452
|
+
} else {
|
|
2453
|
+
while (++i < lines.length && !lines[i].includes(delim)) {
|
|
2454
|
+
}
|
|
2455
|
+
value = void 0;
|
|
2456
|
+
}
|
|
2457
|
+
} else {
|
|
2458
|
+
value = tomlScalar(rest);
|
|
2459
|
+
}
|
|
2460
|
+
if (!value) continue;
|
|
2461
|
+
if (table === "build") {
|
|
2462
|
+
if (field === "buildCommand") cfg.buildCommand ??= value;
|
|
2463
|
+
else if (field === "builder") cfg.builder ??= value;
|
|
2464
|
+
} else if (table === "deploy") {
|
|
2465
|
+
if (field === "startCommand") cfg.startCommand ??= value;
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
return cfg;
|
|
2469
|
+
}
|
|
2470
|
+
function parseRailwayJson(raw) {
|
|
2471
|
+
let parsed;
|
|
2472
|
+
try {
|
|
2473
|
+
parsed = JSON.parse(raw);
|
|
2474
|
+
} catch {
|
|
2475
|
+
return null;
|
|
2476
|
+
}
|
|
2477
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
2478
|
+
const obj = parsed;
|
|
2479
|
+
const build = obj.build ?? {};
|
|
2480
|
+
const deploy = obj.deploy ?? {};
|
|
2481
|
+
return {
|
|
2482
|
+
builder: trimmed(build.builder),
|
|
2483
|
+
buildCommand: trimmed(build.buildCommand),
|
|
2484
|
+
startCommand: trimmed(deploy.startCommand)
|
|
2485
|
+
};
|
|
2486
|
+
}
|
|
2487
|
+
function toMetadata(cfg) {
|
|
2488
|
+
const buildCommand = trimmed(cfg.buildCommand);
|
|
2489
|
+
const startCommand = trimmed(cfg.startCommand);
|
|
2490
|
+
const framework = cfg.builder?.toUpperCase() === "DOCKERFILE" ? "docker" : void 0;
|
|
2491
|
+
if (!buildCommand && !startCommand && !framework) return null;
|
|
2492
|
+
const metadata = { source: "railway" };
|
|
2493
|
+
if (buildCommand) metadata.buildCommand = buildCommand;
|
|
2494
|
+
if (startCommand) metadata.startCommand = startCommand;
|
|
2495
|
+
if (framework) metadata.framework = framework;
|
|
2496
|
+
if (extractCdTargets(buildCommand).length > 0) metadata.nonLocal = true;
|
|
2497
|
+
return metadata;
|
|
2498
|
+
}
|
|
2499
|
+
var railwayMetadataParser = {
|
|
2500
|
+
source: "railway",
|
|
2501
|
+
files: ["railway.toml", "railway.json"],
|
|
2502
|
+
parse(fileContents) {
|
|
2503
|
+
const tomlRaw = fileContents["railway.toml"];
|
|
2504
|
+
const jsonRaw = fileContents["railway.json"];
|
|
2505
|
+
const fromToml = tomlRaw ? toMetadata(parseRailwayToml(tomlRaw)) : null;
|
|
2506
|
+
if (fromToml) return fromToml;
|
|
2507
|
+
const jsonCfg = jsonRaw ? parseRailwayJson(jsonRaw) : null;
|
|
2508
|
+
return jsonCfg ? toMetadata(jsonCfg) : null;
|
|
2509
|
+
}
|
|
2510
|
+
};
|
|
2511
|
+
|
|
2512
|
+
// ../../packages/core/src/metadata/render.ts
|
|
1949
2513
|
function unquote(value) {
|
|
1950
2514
|
const trimmed2 = value.trim();
|
|
1951
2515
|
const m = trimmed2.match(/^(['"])(.*)\1$/);
|
|
@@ -1957,7 +2521,7 @@ var renderMetadataParser = {
|
|
|
1957
2521
|
parse(fileContents) {
|
|
1958
2522
|
const raw = fileContents["render.yaml"];
|
|
1959
2523
|
if (!raw) return null;
|
|
1960
|
-
const lines =
|
|
2524
|
+
const lines = splitLines(raw);
|
|
1961
2525
|
let startCommand;
|
|
1962
2526
|
let buildCommand;
|
|
1963
2527
|
const env = {};
|
|
@@ -1991,7 +2555,9 @@ var renderMetadataParser = {
|
|
|
1991
2555
|
|
|
1992
2556
|
// ../../packages/core/src/metadata/index.ts
|
|
1993
2557
|
var METADATA_PARSERS = [
|
|
2558
|
+
openshipMetadataParser,
|
|
1994
2559
|
vercelMetadataParser,
|
|
2560
|
+
railwayMetadataParser,
|
|
1995
2561
|
renderMetadataParser
|
|
1996
2562
|
];
|
|
1997
2563
|
var METADATA_FILES = new Set(
|
|
@@ -2378,11 +2944,11 @@ var openCommand = new Command3("open").description("Open the Openship dashboard
|
|
|
2378
2944
|
import { Command as Command4 } from "commander";
|
|
2379
2945
|
import chalk5 from "chalk";
|
|
2380
2946
|
import ora from "ora";
|
|
2381
|
-
import { spawn } from "child_process";
|
|
2947
|
+
import { spawn as spawn2 } from "child_process";
|
|
2382
2948
|
import { randomBytes } from "crypto";
|
|
2383
|
-
import { createWriteStream as createWriteStream2, existsSync as
|
|
2384
|
-
import { homedir as
|
|
2385
|
-
import { dirname as dirname2, join as
|
|
2949
|
+
import { createWriteStream as createWriteStream2, existsSync as existsSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
2950
|
+
import { homedir as homedir6 } from "os";
|
|
2951
|
+
import { dirname as dirname2, join as join7 } from "path";
|
|
2386
2952
|
import { fileURLToPath } from "url";
|
|
2387
2953
|
|
|
2388
2954
|
// src/lib/dashboard.ts
|
|
@@ -2432,8 +2998,8 @@ async function downloadToFile(url, dest, onProgress) {
|
|
|
2432
2998
|
} finally {
|
|
2433
2999
|
file.end();
|
|
2434
3000
|
}
|
|
2435
|
-
await new Promise((
|
|
2436
|
-
file.on("finish", () =>
|
|
3001
|
+
await new Promise((resolve3, reject2) => {
|
|
3002
|
+
file.on("finish", () => resolve3());
|
|
2437
3003
|
file.on("error", reject2);
|
|
2438
3004
|
});
|
|
2439
3005
|
return { sha256: hash.digest("hex"), size: received };
|
|
@@ -2838,10 +3404,10 @@ function readInstanceUrl() {
|
|
|
2838
3404
|
var DEFAULT_API = 4e3;
|
|
2839
3405
|
var DEFAULT_DASHBOARD = 3001;
|
|
2840
3406
|
function isPortFree(port) {
|
|
2841
|
-
return new Promise((
|
|
3407
|
+
return new Promise((resolve3) => {
|
|
2842
3408
|
const srv = createServer();
|
|
2843
|
-
srv.once("error", () =>
|
|
2844
|
-
srv.listen(port, "127.0.0.1", () => srv.close(() =>
|
|
3409
|
+
srv.once("error", () => resolve3(false));
|
|
3410
|
+
srv.listen(port, "127.0.0.1", () => srv.close(() => resolve3(true)));
|
|
2845
3411
|
});
|
|
2846
3412
|
}
|
|
2847
3413
|
async function waitPortFree(port, opts = {}) {
|
|
@@ -2855,13 +3421,13 @@ async function waitPortFree(port, opts = {}) {
|
|
|
2855
3421
|
}
|
|
2856
3422
|
}
|
|
2857
3423
|
function getFreePort() {
|
|
2858
|
-
return new Promise((
|
|
3424
|
+
return new Promise((resolve3, reject2) => {
|
|
2859
3425
|
const srv = createServer();
|
|
2860
3426
|
srv.once("error", reject2);
|
|
2861
3427
|
srv.listen(0, "127.0.0.1", () => {
|
|
2862
3428
|
const addr = srv.address();
|
|
2863
3429
|
const port = addr && typeof addr === "object" ? addr.port : 0;
|
|
2864
|
-
srv.close(() => port ?
|
|
3430
|
+
srv.close(() => port ? resolve3(port) : reject2(new Error("no free port")));
|
|
2865
3431
|
});
|
|
2866
3432
|
});
|
|
2867
3433
|
}
|
|
@@ -2902,6 +3468,101 @@ async function resolvePorts(prefs) {
|
|
|
2902
3468
|
};
|
|
2903
3469
|
}
|
|
2904
3470
|
|
|
3471
|
+
// src/lib/from-source.ts
|
|
3472
|
+
import { spawn, spawnSync as spawnSync3 } from "child_process";
|
|
3473
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync6 } from "fs";
|
|
3474
|
+
import { homedir as homedir5 } from "os";
|
|
3475
|
+
import { join as join6, resolve as resolve2 } from "path";
|
|
3476
|
+
var OS_DIR3 = join6(homedir5(), ".openship");
|
|
3477
|
+
var DEFAULT_REPO = "https://github.com/oblien/openship.git";
|
|
3478
|
+
function has(cmd) {
|
|
3479
|
+
try {
|
|
3480
|
+
return spawnSync3(cmd, ["--version"], { stdio: "ignore" }).status === 0;
|
|
3481
|
+
} catch {
|
|
3482
|
+
return false;
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3485
|
+
function run2(cmd, args, cwd, env) {
|
|
3486
|
+
return new Promise((res, rej) => {
|
|
3487
|
+
const child = spawn(cmd, args, {
|
|
3488
|
+
cwd,
|
|
3489
|
+
stdio: "inherit",
|
|
3490
|
+
env: env ? { ...process.env, ...env } : process.env
|
|
3491
|
+
});
|
|
3492
|
+
child.on("error", rej);
|
|
3493
|
+
child.on(
|
|
3494
|
+
"exit",
|
|
3495
|
+
(code) => code === 0 ? res() : rej(new Error(`\`${cmd} ${args.join(" ")}\` (cwd=${cwd}) exited ${code ?? "?"}`))
|
|
3496
|
+
);
|
|
3497
|
+
});
|
|
3498
|
+
}
|
|
3499
|
+
function shortSha(cwd) {
|
|
3500
|
+
const r = spawnSync3("git", ["rev-parse", "--short", "HEAD"], { cwd, encoding: "utf8" });
|
|
3501
|
+
return r.status === 0 ? (r.stdout ?? "").trim() || "unknown" : "unknown";
|
|
3502
|
+
}
|
|
3503
|
+
function isMonorepo(dir) {
|
|
3504
|
+
return existsSync5(join6(dir, "package.json")) && existsSync5(join6(dir, "apps/api/package.json")) && existsSync5(join6(dir, "apps/dashboard/package.json"));
|
|
3505
|
+
}
|
|
3506
|
+
async function prepareFromSource(opts) {
|
|
3507
|
+
if (!has("bun")) {
|
|
3508
|
+
throw new Error(
|
|
3509
|
+
"`bun` is required to build from source but wasn't found on PATH \u2014 install it: https://bun.sh"
|
|
3510
|
+
);
|
|
3511
|
+
}
|
|
3512
|
+
let sourceDir;
|
|
3513
|
+
let ref;
|
|
3514
|
+
if (opts.source) {
|
|
3515
|
+
sourceDir = resolve2(opts.source);
|
|
3516
|
+
if (!isMonorepo(sourceDir)) {
|
|
3517
|
+
throw new Error(
|
|
3518
|
+
`--source ${sourceDir} doesn't look like an Openship checkout (missing package.json / apps/api / apps/dashboard).`
|
|
3519
|
+
);
|
|
3520
|
+
}
|
|
3521
|
+
ref = "local";
|
|
3522
|
+
} else {
|
|
3523
|
+
if (!has("git")) {
|
|
3524
|
+
throw new Error("`git` is required to clone the source but wasn't found on PATH.");
|
|
3525
|
+
}
|
|
3526
|
+
ref = (opts.ref || "main").trim();
|
|
3527
|
+
const repoUrl = opts.repo || DEFAULT_REPO;
|
|
3528
|
+
sourceDir = join6(OS_DIR3, "src");
|
|
3529
|
+
mkdirSync6(OS_DIR3, { recursive: true });
|
|
3530
|
+
if (!existsSync5(join6(sourceDir, ".git"))) {
|
|
3531
|
+
console.log(` Cloning ${repoUrl} \u2192 ${sourceDir}`);
|
|
3532
|
+
await run2("git", ["clone", repoUrl, sourceDir], OS_DIR3);
|
|
3533
|
+
}
|
|
3534
|
+
console.log(` Fetching + checking out ${ref}`);
|
|
3535
|
+
await run2("git", ["fetch", "origin", ref, "--tags"], sourceDir);
|
|
3536
|
+
await run2("git", ["checkout", ref], sourceDir);
|
|
3537
|
+
await run2("git", ["pull", "--ff-only", "origin", ref], sourceDir).catch(() => {
|
|
3538
|
+
console.log(" (pinned ref \u2014 not fast-forwarding)");
|
|
3539
|
+
});
|
|
3540
|
+
}
|
|
3541
|
+
const sha = shortSha(sourceDir);
|
|
3542
|
+
console.log(` Source: ${sourceDir} @ ${ref} (${sha})`);
|
|
3543
|
+
console.log(" Installing workspace dependencies (bun install)\u2026");
|
|
3544
|
+
await run2("bun", ["install"], sourceDir);
|
|
3545
|
+
const distDir = join6(OS_DIR3, "from-source-dist");
|
|
3546
|
+
console.log(" Building release dist (compiles the dashboard \u2014 needs RAM/CPU)\u2026");
|
|
3547
|
+
await run2(
|
|
3548
|
+
"bun",
|
|
3549
|
+
["run", join6(sourceDir, "apps/api/scripts/build-release.ts")],
|
|
3550
|
+
sourceDir,
|
|
3551
|
+
{ DIST_DIR: distDir, NODE_ENV: "production", CLOUD_MODE: "false", OPENSHIP_TARGET: "local" }
|
|
3552
|
+
);
|
|
3553
|
+
console.log(" Installing runtime dependencies in the dist\u2026");
|
|
3554
|
+
await run2("bun", ["install", "--production", "--frozen-lockfile"], distDir);
|
|
3555
|
+
const apiDir = join6(distDir, "api");
|
|
3556
|
+
const dashboardDir = join6(distDir, "dashboard");
|
|
3557
|
+
if (!existsSync5(join6(apiDir, "src/index.ts"))) {
|
|
3558
|
+
throw new Error(`Build produced no API at ${apiDir}/src/index.ts \u2014 build-release layout drift?`);
|
|
3559
|
+
}
|
|
3560
|
+
if (!existsSync5(join6(dashboardDir, "apps/dashboard/server.js"))) {
|
|
3561
|
+
throw new Error(`Build produced no dashboard at ${dashboardDir}/apps/dashboard/server.js.`);
|
|
3562
|
+
}
|
|
3563
|
+
return { apiDir, dashboardDir, ref, sha, sourceDir };
|
|
3564
|
+
}
|
|
3565
|
+
|
|
2905
3566
|
// src/commands/up.ts
|
|
2906
3567
|
function normalizeUrl(raw) {
|
|
2907
3568
|
const value = /^https?:\/\//i.test(raw) ? raw : `https://${raw}`;
|
|
@@ -2924,20 +3585,20 @@ function normalizePublicUrl(raw) {
|
|
|
2924
3585
|
return url;
|
|
2925
3586
|
}
|
|
2926
3587
|
var DIST_DIR = dirname2(fileURLToPath(import.meta.url));
|
|
2927
|
-
var SERVER_DIR =
|
|
2928
|
-
var
|
|
3588
|
+
var SERVER_DIR = join7(DIST_DIR, "server");
|
|
3589
|
+
var OS_DIR4 = join7(homedir6(), ".openship");
|
|
2929
3590
|
function ensureAuthSecret() {
|
|
2930
|
-
const path2 =
|
|
2931
|
-
if (
|
|
2932
|
-
|
|
3591
|
+
const path2 = join7(OS_DIR4, "auth-secret");
|
|
3592
|
+
if (existsSync6(path2)) return readFileSync4(path2, "utf8").trim();
|
|
3593
|
+
mkdirSync7(OS_DIR4, { recursive: true, mode: 448 });
|
|
2933
3594
|
const secret = randomBytes(32).toString("hex");
|
|
2934
3595
|
writeFileSync5(path2, secret, { mode: 384 });
|
|
2935
3596
|
return secret;
|
|
2936
3597
|
}
|
|
2937
3598
|
function ensureInternalToken() {
|
|
2938
|
-
const path2 =
|
|
2939
|
-
if (
|
|
2940
|
-
|
|
3599
|
+
const path2 = join7(OS_DIR4, "internal-token");
|
|
3600
|
+
if (existsSync6(path2)) return readFileSync4(path2, "utf8").trim();
|
|
3601
|
+
mkdirSync7(OS_DIR4, { recursive: true, mode: 448 });
|
|
2941
3602
|
const token = randomBytes(32).toString("hex");
|
|
2942
3603
|
writeFileSync5(path2, token, { mode: 384 });
|
|
2943
3604
|
return token;
|
|
@@ -2951,10 +3612,32 @@ var upCommand = new Command4("up").description("Start Openship as a persistent s
|
|
|
2951
3612
|
).option(
|
|
2952
3613
|
"--managed-edge",
|
|
2953
3614
|
"Managed edge: install OpenResty + a free Let's Encrypt cert on this box and route --public-url's domain to the dashboard (no reverse proxy needed)"
|
|
2954
|
-
).option("--acme-email <email>", "Contact email for Let's Encrypt certificates (managed edge)").action(async (opts) => {
|
|
3615
|
+
).option("--acme-email <email>", "Contact email for Let's Encrypt certificates (managed edge)").option("--from-source", "Preview: build + run Openship from source (a branch) instead of a published release \u2014 runs attached").option("--ref <branch>", "Git branch/tag/sha to build with --from-source (default: main)").option("--source <path>", "Build from an existing local Openship checkout instead of cloning").option("--repo <url>", "Git remote to clone for --from-source (default: oblien/openship)").action(async (opts) => {
|
|
3616
|
+
if (opts.fromSource || opts.source) return runFromSource(opts);
|
|
2955
3617
|
if (opts.foreground) return runForeground(opts);
|
|
2956
3618
|
await startService(opts);
|
|
2957
3619
|
});
|
|
3620
|
+
async function runFromSource(opts) {
|
|
3621
|
+
console.log(chalk5.cyan("\n Building Openship from source (preview mode)\u2026"));
|
|
3622
|
+
console.log(
|
|
3623
|
+
chalk5.dim(" Unverified dev build \u2014 for previewing a branch, not production self-hosting.\n")
|
|
3624
|
+
);
|
|
3625
|
+
let src;
|
|
3626
|
+
try {
|
|
3627
|
+
src = await prepareFromSource({ ref: opts.ref, source: opts.source, repo: opts.repo });
|
|
3628
|
+
} catch (e) {
|
|
3629
|
+
console.error(
|
|
3630
|
+
chalk5.red(`
|
|
3631
|
+
Build from source failed: ${e.message}
|
|
3632
|
+
`) + chalk5.dim(" Small boxes can OOM on the dashboard build \u2014 build on a bigger machine and pass --source, or use a published release.\n")
|
|
3633
|
+
);
|
|
3634
|
+
process.exit(1);
|
|
3635
|
+
}
|
|
3636
|
+
console.log(chalk5.green(`
|
|
3637
|
+
Built ${src.ref} (${src.sha}). Starting\u2026
|
|
3638
|
+
`));
|
|
3639
|
+
await runForeground(opts, src);
|
|
3640
|
+
}
|
|
2958
3641
|
async function startService(opts, runOpts = {}) {
|
|
2959
3642
|
const publicUrl = opts.publicUrl ? normalizePublicUrl(opts.publicUrl) : void 0;
|
|
2960
3643
|
if (opts.dryRun) {
|
|
@@ -3029,13 +3712,23 @@ async function startService(opts, runOpts = {}) {
|
|
|
3029
3712
|
process.exit(1);
|
|
3030
3713
|
}
|
|
3031
3714
|
}
|
|
3032
|
-
async function runForeground(opts) {
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3715
|
+
async function runForeground(opts, source) {
|
|
3716
|
+
let apiCmd = process.execPath;
|
|
3717
|
+
let apiArgs;
|
|
3718
|
+
let apiCwd;
|
|
3719
|
+
if (source) {
|
|
3720
|
+
apiCmd = "bun";
|
|
3721
|
+
apiArgs = ["run", "src/index.ts"];
|
|
3722
|
+
apiCwd = source.apiDir;
|
|
3723
|
+
} else {
|
|
3724
|
+
const serverEntry = join7(SERVER_DIR, "index.js");
|
|
3725
|
+
if (!existsSync6(serverEntry)) {
|
|
3726
|
+
console.error(
|
|
3727
|
+
chalk5.red("\n Bundled server not found in this install.") + chalk5.dim("\n Reinstall with `openship update` (or `npm i -g openship`).\n")
|
|
3728
|
+
);
|
|
3729
|
+
process.exit(1);
|
|
3730
|
+
}
|
|
3731
|
+
apiArgs = [serverEntry];
|
|
3039
3732
|
}
|
|
3040
3733
|
const resolved = await resolvePorts({
|
|
3041
3734
|
api: opts.port ? Number(opts.port) : void 0,
|
|
@@ -3045,11 +3738,11 @@ async function runForeground(opts) {
|
|
|
3045
3738
|
const dashPort = String(resolved.dashboard);
|
|
3046
3739
|
const publicUrl = opts.publicUrl ? normalizePublicUrl(opts.publicUrl) : void 0;
|
|
3047
3740
|
const managedEdge = Boolean(opts.managedEdge && publicUrl);
|
|
3048
|
-
const dataDir = opts.dataDir ||
|
|
3049
|
-
|
|
3050
|
-
const logDir =
|
|
3051
|
-
|
|
3052
|
-
const instanceLogPath =
|
|
3741
|
+
const dataDir = opts.dataDir || join7(OS_DIR4, "data");
|
|
3742
|
+
mkdirSync7(dataDir, { recursive: true });
|
|
3743
|
+
const logDir = join7(OS_DIR4, "logs");
|
|
3744
|
+
mkdirSync7(logDir, { recursive: true });
|
|
3745
|
+
const instanceLogPath = join7(logDir, "instance.log");
|
|
3053
3746
|
const instanceLog = createWriteStream2(instanceLogPath, { flags: "w" });
|
|
3054
3747
|
const env = {
|
|
3055
3748
|
...process.env,
|
|
@@ -3060,10 +3753,12 @@ async function runForeground(opts) {
|
|
|
3060
3753
|
OPENSHIP_TARGET: "local",
|
|
3061
3754
|
OPENSHIP_JOB_RUNNER: "in-process",
|
|
3062
3755
|
PGLITE_DATA_DIR: dataDir,
|
|
3063
|
-
OPENSHIP_MIGRATIONS_DIR: join6(SERVER_DIR, "migrations"),
|
|
3064
|
-
OPENSHIP_PGLITE_ASSETS_DIR: join6(SERVER_DIR, "pglite"),
|
|
3065
3756
|
BETTER_AUTH_SECRET: ensureAuthSecret()
|
|
3066
3757
|
};
|
|
3758
|
+
if (!source) {
|
|
3759
|
+
env.OPENSHIP_MIGRATIONS_DIR = join7(SERVER_DIR, "migrations");
|
|
3760
|
+
env.OPENSHIP_PGLITE_ASSETS_DIR = join7(SERVER_DIR, "pglite");
|
|
3761
|
+
}
|
|
3067
3762
|
env.OPENSHIP_REQUIRE_AUTH = "true";
|
|
3068
3763
|
env.INTERNAL_TOKEN = ensureInternalToken();
|
|
3069
3764
|
env.OPENSHIP_API_HOST = "127.0.0.1";
|
|
@@ -3082,7 +3777,8 @@ async function runForeground(opts) {
|
|
|
3082
3777
|
delete env.DATABASE_URL;
|
|
3083
3778
|
delete env.POSTGRES_URL;
|
|
3084
3779
|
const spinner3 = ora(`Starting Openship on http://localhost:${port} \u2026`).start();
|
|
3085
|
-
const child =
|
|
3780
|
+
const child = spawn2(apiCmd, apiArgs, {
|
|
3781
|
+
cwd: apiCwd,
|
|
3086
3782
|
env,
|
|
3087
3783
|
stdio: ["ignore", "pipe", "pipe"],
|
|
3088
3784
|
detached: process.platform !== "win32"
|
|
@@ -3146,10 +3842,11 @@ async function runForeground(opts) {
|
|
|
3146
3842
|
};
|
|
3147
3843
|
let dashboardUrl = null;
|
|
3148
3844
|
if (opts.ui !== false) {
|
|
3845
|
+
if (source) process.env.OPENSHIP_DASHBOARD_DIR = source.dashboardDir;
|
|
3149
3846
|
const uiSpinner = ora("Preparing the dashboard\u2026").start();
|
|
3150
3847
|
try {
|
|
3151
3848
|
const bundle = await ensureDashboard({
|
|
3152
|
-
tag: opts.uiVersion || `v${"0.
|
|
3849
|
+
tag: source ? "local" : opts.uiVersion || `v${"0.3.0"}`,
|
|
3153
3850
|
onProgress: (received, total) => {
|
|
3154
3851
|
if (total) {
|
|
3155
3852
|
uiSpinner.text = `Downloading dashboard\u2026 ${Math.round(received / total * 100)}%`;
|
|
@@ -3157,7 +3854,7 @@ async function runForeground(opts) {
|
|
|
3157
3854
|
}
|
|
3158
3855
|
});
|
|
3159
3856
|
uiSpinner.text = "Starting the dashboard\u2026";
|
|
3160
|
-
const dash =
|
|
3857
|
+
const dash = spawn2(process.execPath, [bundle.entry], {
|
|
3161
3858
|
cwd: bundle.cwd,
|
|
3162
3859
|
detached: process.platform !== "win32",
|
|
3163
3860
|
env: {
|
|
@@ -3271,15 +3968,15 @@ var stopCommand = new Command5("stop").description("Stop the Openship service (s
|
|
|
3271
3968
|
|
|
3272
3969
|
// src/commands/init.ts
|
|
3273
3970
|
import { Command as Command6 } from "commander";
|
|
3274
|
-
import { existsSync as
|
|
3971
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync8, writeFileSync as writeFileSync6 } from "fs";
|
|
3275
3972
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
3276
3973
|
import { stdin as input2, stdout as output2 } from "process";
|
|
3277
|
-
import { join as
|
|
3974
|
+
import { join as join8 } from "path";
|
|
3278
3975
|
var initCommand = new Command6("init").description("Link the current directory to an Openship project (.openship/project.json)").option("--project <id>", "Project id to link (skips the picker)").option("--environment <name>", "Default deploy environment", "production").option("--dir <path>", "Directory to initialize", process.cwd()).option("--force", "Overwrite an existing project link").option("-y, --yes", "Non-interactive: fail instead of prompting").action(async (opts) => {
|
|
3279
3976
|
const root = opts.dir || process.cwd();
|
|
3280
|
-
const linkDir =
|
|
3281
|
-
const linkPath =
|
|
3282
|
-
if (
|
|
3977
|
+
const linkDir = join8(root, ".openship");
|
|
3978
|
+
const linkPath = join8(linkDir, "project.json");
|
|
3979
|
+
if (existsSync7(linkPath) && !opts.force) {
|
|
3283
3980
|
err(`Already linked (${linkPath}). Re-run with --force to overwrite.`);
|
|
3284
3981
|
process.exit(1);
|
|
3285
3982
|
}
|
|
@@ -3332,7 +4029,7 @@ var initCommand = new Command6("init").description("Link the current directory t
|
|
|
3332
4029
|
context: getActiveContext(),
|
|
3333
4030
|
defaults: { environment: opts.environment || "production" }
|
|
3334
4031
|
};
|
|
3335
|
-
|
|
4032
|
+
mkdirSync8(linkDir, { recursive: true });
|
|
3336
4033
|
writeFileSync6(linkPath, JSON.stringify(link, null, 2) + "\n");
|
|
3337
4034
|
if (isJsonMode()) {
|
|
3338
4035
|
printJson({ path: linkPath, link });
|
|
@@ -3343,8 +4040,86 @@ var initCommand = new Command6("init").description("Link the current directory t
|
|
|
3343
4040
|
`);
|
|
3344
4041
|
});
|
|
3345
4042
|
|
|
3346
|
-
// src/commands/
|
|
4043
|
+
// src/commands/config.ts
|
|
3347
4044
|
import { Command as Command7 } from "commander";
|
|
4045
|
+
import { existsSync as existsSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync7 } from "fs";
|
|
4046
|
+
import { join as join9 } from "path";
|
|
4047
|
+
var SCHEMA_URL = "https://openship.io/openship.schema.json";
|
|
4048
|
+
var CONFIG_FILE = "openship.json";
|
|
4049
|
+
function detectHints(dir) {
|
|
4050
|
+
const hints = {};
|
|
4051
|
+
const lock = [
|
|
4052
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
4053
|
+
["yarn.lock", "yarn"],
|
|
4054
|
+
["bun.lockb", "bun"],
|
|
4055
|
+
["package-lock.json", "npm"]
|
|
4056
|
+
];
|
|
4057
|
+
for (const [file, pm] of lock) {
|
|
4058
|
+
if (existsSync8(join9(dir, file))) {
|
|
4059
|
+
hints.packageManager = pm;
|
|
4060
|
+
break;
|
|
4061
|
+
}
|
|
4062
|
+
}
|
|
4063
|
+
const pkgPath = join9(dir, "package.json");
|
|
4064
|
+
if (existsSync8(pkgPath)) {
|
|
4065
|
+
try {
|
|
4066
|
+
const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
|
|
4067
|
+
const runner = hints.packageManager ?? "npm";
|
|
4068
|
+
const exec = runner === "npm" ? "npm run" : runner;
|
|
4069
|
+
if (pkg.scripts?.build) hints.buildCommand = `${exec} build`;
|
|
4070
|
+
if (pkg.scripts?.start) hints.startCommand = `${exec} start`;
|
|
4071
|
+
} catch {
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
return hints;
|
|
4075
|
+
}
|
|
4076
|
+
var initCmd = new Command7("init").description("Scaffold an openship.json in the current directory").option("--dir <path>", "Directory to write into", process.cwd()).option("--force", "Overwrite an existing openship.json").action((opts) => {
|
|
4077
|
+
const dir = opts.dir || process.cwd();
|
|
4078
|
+
const path2 = join9(dir, CONFIG_FILE);
|
|
4079
|
+
if (existsSync8(path2) && !opts.force) {
|
|
4080
|
+
err(`${CONFIG_FILE} already exists. Re-run with --force to overwrite.`);
|
|
4081
|
+
process.exit(1);
|
|
4082
|
+
}
|
|
4083
|
+
const hints = detectHints(dir);
|
|
4084
|
+
const scaffold = { $schema: SCHEMA_URL, ...hints };
|
|
4085
|
+
const text2 = JSON.stringify(scaffold, null, 2) + "\n";
|
|
4086
|
+
writeFileSync7(path2, text2);
|
|
4087
|
+
if (isJsonMode()) {
|
|
4088
|
+
printJson({ path: path2, config: scaffold });
|
|
4089
|
+
return;
|
|
4090
|
+
}
|
|
4091
|
+
ok(`
|
|
4092
|
+
Wrote ${CONFIG_FILE} \u2192 ${path2}`);
|
|
4093
|
+
info(" Edit it to declare framework, env, domains, resources, services\u2026 then `openship config validate`.\n");
|
|
4094
|
+
});
|
|
4095
|
+
var validateCmd = new Command7("validate").description("Validate an openship.json against the deploy schema").argument("[file]", "Path to the config file", CONFIG_FILE).action((file) => {
|
|
4096
|
+
const path2 = file === CONFIG_FILE ? join9(process.cwd(), CONFIG_FILE) : file;
|
|
4097
|
+
if (!existsSync8(path2)) {
|
|
4098
|
+
if (isJsonMode()) printJson({ valid: false, errors: [`${path2} not found`], warnings: [] });
|
|
4099
|
+
else err(`Not found: ${path2}`);
|
|
4100
|
+
process.exit(1);
|
|
4101
|
+
}
|
|
4102
|
+
const { config, errors, warnings } = parseOpenshipConfigJson(readFileSync5(path2, "utf8"));
|
|
4103
|
+
const valid = errors.length === 0 && config !== null;
|
|
4104
|
+
if (isJsonMode()) {
|
|
4105
|
+
printJson({ valid, errors, warnings });
|
|
4106
|
+
process.exit(valid ? 0 : 1);
|
|
4107
|
+
}
|
|
4108
|
+
for (const w of warnings) info(` \u26A0 ${w}`);
|
|
4109
|
+
if (!valid) {
|
|
4110
|
+
err(`
|
|
4111
|
+
${errors.length} error${errors.length === 1 ? "" : "s"} in ${CONFIG_FILE}:`);
|
|
4112
|
+
for (const e of errors) err(` \u2022 ${e}`);
|
|
4113
|
+
process.exit(1);
|
|
4114
|
+
}
|
|
4115
|
+
ok(`
|
|
4116
|
+
${CONFIG_FILE} is valid${warnings.length ? ` (${warnings.length} warning${warnings.length === 1 ? "" : "s"})` : ""}.
|
|
4117
|
+
`);
|
|
4118
|
+
});
|
|
4119
|
+
var configCommand = new Command7("config").description("Author and validate openship.json (declarative deploy config)").addCommand(initCmd).addCommand(validateCmd);
|
|
4120
|
+
|
|
4121
|
+
// src/commands/context.ts
|
|
4122
|
+
import { Command as Command8 } from "commander";
|
|
3348
4123
|
function renderContexts() {
|
|
3349
4124
|
const rows = listContexts().map((c) => ({
|
|
3350
4125
|
current: c.current ? "*" : "",
|
|
@@ -3355,8 +4130,8 @@ function renderContexts() {
|
|
|
3355
4130
|
}));
|
|
3356
4131
|
printTable(rows, ["current", "name", "apiUrl", "dashboardUrl", "auth"]);
|
|
3357
4132
|
}
|
|
3358
|
-
var listCmd = new
|
|
3359
|
-
var useCmd = new
|
|
4133
|
+
var listCmd = new Command8("list").alias("ls").description("List configured contexts").action(renderContexts);
|
|
4134
|
+
var useCmd = new Command8("use").description("Switch the active context").argument("<name>", "Context name").action((name) => {
|
|
3360
4135
|
try {
|
|
3361
4136
|
setActiveContext(name);
|
|
3362
4137
|
ok(`
|
|
@@ -3367,7 +4142,7 @@ var useCmd = new Command7("use").description("Switch the active context").argume
|
|
|
3367
4142
|
process.exit(1);
|
|
3368
4143
|
}
|
|
3369
4144
|
});
|
|
3370
|
-
var addCmd = new
|
|
4145
|
+
var addCmd = new Command8("add").description("Create or update a context's endpoints/token").argument("<name>", "Context name").option("--api-url <url>", "API base URL").option("--dashboard-url <url>", "Dashboard base URL").option("--token <token>", "Personal Access Token to store").option("--use", "Switch to this context after adding").action((name, opts) => {
|
|
3371
4146
|
addContext(name, {
|
|
3372
4147
|
apiUrl: opts.apiUrl,
|
|
3373
4148
|
dashboardUrl: opts.dashboardUrl,
|
|
@@ -3378,7 +4153,7 @@ var addCmd = new Command7("add").description("Create or update a context's endpo
|
|
|
3378
4153
|
Saved context "${name}"${opts.use ? " (now active)" : ""}.
|
|
3379
4154
|
`);
|
|
3380
4155
|
});
|
|
3381
|
-
var rmCmd = new
|
|
4156
|
+
var rmCmd = new Command8("rm").alias("remove").description("Remove a context (cannot remove the active one)").argument("<name>", "Context name").action((name) => {
|
|
3382
4157
|
try {
|
|
3383
4158
|
removeContext(name);
|
|
3384
4159
|
ok(`
|
|
@@ -3389,25 +4164,25 @@ var rmCmd = new Command7("rm").alias("remove").description("Remove a context (ca
|
|
|
3389
4164
|
process.exit(1);
|
|
3390
4165
|
}
|
|
3391
4166
|
});
|
|
3392
|
-
var contextCommand = new
|
|
4167
|
+
var contextCommand = new Command8("context").alias("ctx").description("Manage connection contexts (list/use/add/rm)").action(() => {
|
|
3393
4168
|
ok(` Active context: ${getActiveContext()}`);
|
|
3394
4169
|
renderContexts();
|
|
3395
4170
|
}).addCommand(listCmd).addCommand(useCmd).addCommand(addCmd).addCommand(rmCmd);
|
|
3396
4171
|
|
|
3397
4172
|
// src/commands/status.ts
|
|
3398
|
-
import { Command as
|
|
4173
|
+
import { Command as Command9 } from "commander";
|
|
3399
4174
|
import chalk7 from "chalk";
|
|
3400
|
-
import { readFileSync as
|
|
3401
|
-
import { homedir as
|
|
3402
|
-
import { join as
|
|
4175
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
4176
|
+
import { homedir as homedir7 } from "os";
|
|
4177
|
+
import { join as join10 } from "path";
|
|
3403
4178
|
function readPorts() {
|
|
3404
4179
|
try {
|
|
3405
|
-
return JSON.parse(
|
|
4180
|
+
return JSON.parse(readFileSync6(join10(homedir7(), ".openship", "ports.json"), "utf8"));
|
|
3406
4181
|
} catch {
|
|
3407
4182
|
return {};
|
|
3408
4183
|
}
|
|
3409
4184
|
}
|
|
3410
|
-
var statusCommand = new
|
|
4185
|
+
var statusCommand = new Command9("status").description("Show the local Openship service (installed/running, ports) and the active context's API health").action(async () => {
|
|
3411
4186
|
const context = getActiveContext();
|
|
3412
4187
|
const apiUrl = getApiUrl2();
|
|
3413
4188
|
const svc = serviceStatus();
|
|
@@ -3442,8 +4217,8 @@ var statusCommand = new Command8("status").description("Show the local Openship
|
|
|
3442
4217
|
});
|
|
3443
4218
|
|
|
3444
4219
|
// src/commands/doctor.ts
|
|
3445
|
-
import { Command as
|
|
3446
|
-
import { existsSync as
|
|
4220
|
+
import { Command as Command10 } from "commander";
|
|
4221
|
+
import { existsSync as existsSync9 } from "fs";
|
|
3447
4222
|
import { execFileSync } from "child_process";
|
|
3448
4223
|
import chalk8 from "chalk";
|
|
3449
4224
|
function bunVersion() {
|
|
@@ -3455,9 +4230,9 @@ function bunVersion() {
|
|
|
3455
4230
|
return null;
|
|
3456
4231
|
}
|
|
3457
4232
|
}
|
|
3458
|
-
var doctorCommand = new
|
|
4233
|
+
var doctorCommand = new Command10("doctor").description("Diagnose the CLI setup (config, active context, runtime)").action(async () => {
|
|
3459
4234
|
const checks = [];
|
|
3460
|
-
const hasConfig =
|
|
4235
|
+
const hasConfig = existsSync9(CONFIG_PATH);
|
|
3461
4236
|
checks.push({
|
|
3462
4237
|
name: "config",
|
|
3463
4238
|
status: hasConfig ? "pass" : "warn",
|
|
@@ -3505,20 +4280,20 @@ var doctorCommand = new Command9("doctor").description("Diagnose the CLI setup (
|
|
|
3505
4280
|
});
|
|
3506
4281
|
|
|
3507
4282
|
// src/commands/deploy.ts
|
|
3508
|
-
import { Command as
|
|
4283
|
+
import { Command as Command11 } from "commander";
|
|
3509
4284
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
3510
4285
|
import ora2 from "ora";
|
|
3511
4286
|
|
|
3512
4287
|
// src/lib/project-link.ts
|
|
3513
|
-
import { readFileSync as
|
|
3514
|
-
import { join as
|
|
3515
|
-
var LINK_REL =
|
|
4288
|
+
import { readFileSync as readFileSync7, existsSync as existsSync10 } from "fs";
|
|
4289
|
+
import { join as join11, dirname as dirname3, parse } from "path";
|
|
4290
|
+
var LINK_REL = join11(".openship", "project.json");
|
|
3516
4291
|
function findProjectLinkPath(from = process.cwd()) {
|
|
3517
4292
|
let dir = from;
|
|
3518
4293
|
const root = parse(dir).root;
|
|
3519
4294
|
for (; ; ) {
|
|
3520
|
-
const candidate =
|
|
3521
|
-
if (
|
|
4295
|
+
const candidate = join11(dir, LINK_REL);
|
|
4296
|
+
if (existsSync10(candidate)) return candidate;
|
|
3522
4297
|
if (dir === root) return null;
|
|
3523
4298
|
dir = dirname3(dir);
|
|
3524
4299
|
}
|
|
@@ -3527,7 +4302,7 @@ function readProjectLink(from) {
|
|
|
3527
4302
|
const path2 = findProjectLinkPath(from);
|
|
3528
4303
|
if (!path2) return null;
|
|
3529
4304
|
try {
|
|
3530
|
-
return JSON.parse(
|
|
4305
|
+
return JSON.parse(readFileSync7(path2, "utf8"));
|
|
3531
4306
|
} catch {
|
|
3532
4307
|
return null;
|
|
3533
4308
|
}
|
|
@@ -3535,21 +4310,21 @@ function readProjectLink(from) {
|
|
|
3535
4310
|
|
|
3536
4311
|
// src/lib/folder-deploy.ts
|
|
3537
4312
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
3538
|
-
import { readFileSync as
|
|
4313
|
+
import { readFileSync as readFileSync8, existsSync as existsSync11, rmSync as rmSync3 } from "fs";
|
|
3539
4314
|
import { tmpdir } from "os";
|
|
3540
|
-
import { join as
|
|
4315
|
+
import { join as join12, basename } from "path";
|
|
3541
4316
|
function detectPackageManager(dir) {
|
|
3542
|
-
if (
|
|
3543
|
-
if (
|
|
3544
|
-
if (
|
|
3545
|
-
if (
|
|
4317
|
+
if (existsSync11(join12(dir, "bun.lockb")) || existsSync11(join12(dir, "bun.lock"))) return "bun";
|
|
4318
|
+
if (existsSync11(join12(dir, "pnpm-lock.yaml"))) return "pnpm";
|
|
4319
|
+
if (existsSync11(join12(dir, "yarn.lock"))) return "yarn";
|
|
4320
|
+
if (existsSync11(join12(dir, "package.json"))) return "npm";
|
|
3546
4321
|
return void 0;
|
|
3547
4322
|
}
|
|
3548
4323
|
function detectStack(dir) {
|
|
3549
|
-
if (
|
|
3550
|
-
if (
|
|
3551
|
-
if (
|
|
3552
|
-
if (
|
|
4324
|
+
if (existsSync11(join12(dir, "go.mod"))) return "go";
|
|
4325
|
+
if (existsSync11(join12(dir, "Cargo.toml"))) return "rust";
|
|
4326
|
+
if (existsSync11(join12(dir, "requirements.txt")) || existsSync11(join12(dir, "pyproject.toml"))) return "python";
|
|
4327
|
+
if (existsSync11(join12(dir, "package.json"))) return "node";
|
|
3553
4328
|
return void 0;
|
|
3554
4329
|
}
|
|
3555
4330
|
async function deployFolder(opts) {
|
|
@@ -3566,7 +4341,7 @@ async function deployFolder(opts) {
|
|
|
3566
4341
|
throw new Error(session.error || "Failed to open upload session");
|
|
3567
4342
|
}
|
|
3568
4343
|
step("Packaging folder");
|
|
3569
|
-
const tarball =
|
|
4344
|
+
const tarball = join12(tmpdir(), `openship-upload-${session.sessionId}.tar.gz`);
|
|
3570
4345
|
execFileSync2(
|
|
3571
4346
|
"tar",
|
|
3572
4347
|
[
|
|
@@ -3585,7 +4360,7 @@ async function deployFolder(opts) {
|
|
|
3585
4360
|
);
|
|
3586
4361
|
step("Uploading source");
|
|
3587
4362
|
try {
|
|
3588
|
-
const body =
|
|
4363
|
+
const body = readFileSync8(tarball);
|
|
3589
4364
|
const up = session.upload;
|
|
3590
4365
|
const method = up.method || "POST";
|
|
3591
4366
|
const res = /^https?:\/\//i.test(up.url) ? await fetch(up.url, { method, headers: up.headers, body }) : await apiRaw(`/${up.url.replace(/^\/+/, "")}`, { method, headers: up.headers, body });
|
|
@@ -3766,7 +4541,7 @@ function git(args) {
|
|
|
3766
4541
|
return void 0;
|
|
3767
4542
|
}
|
|
3768
4543
|
}
|
|
3769
|
-
var deployCommand = new
|
|
4544
|
+
var deployCommand = new Command11("deploy").description("Trigger a deployment for the current project").option("--project <id>", "Project ID (defaults to the linked project in .openship/project.json)").option("--branch <name>", "Git branch to deploy (defaults to the current branch)").option("--commit <sha>", "Specific commit SHA (defaults to the latest commit on the branch)").option("--env <environment>", "Target environment: production | preview", "production").option("--force-all", "Rebuild every enabled service (skip smart per-service routing)").option("--service-ids <ids>", "Comma-separated service IDs to deploy (smart routing)").option("--smart-route", "Rebuild only services changed since the active deploy").option("--refresh", "Re-apply current env to the active deploy (no git pull, no rebuild)").option("--name <name>", "Project name for a folder (non-git) deploy (defaults to the directory name)").option("--watch", "Stream the deployment logs until it finishes").action(async (opts) => {
|
|
3770
4545
|
const link = readProjectLink();
|
|
3771
4546
|
const env = opts.env;
|
|
3772
4547
|
if (env !== "production" && env !== "preview") {
|
|
@@ -3848,9 +4623,9 @@ var deployCommand = new Command10("deploy").description("Trigger a deployment fo
|
|
|
3848
4623
|
});
|
|
3849
4624
|
|
|
3850
4625
|
// src/commands/deployment.ts
|
|
3851
|
-
import { Command as
|
|
4626
|
+
import { Command as Command12 } from "commander";
|
|
3852
4627
|
import { createInterface as createInterface3 } from "readline";
|
|
3853
|
-
function
|
|
4628
|
+
function run3(fn) {
|
|
3854
4629
|
return async (...args) => {
|
|
3855
4630
|
try {
|
|
3856
4631
|
await fn(...args);
|
|
@@ -3864,18 +4639,18 @@ function report(res, message) {
|
|
|
3864
4639
|
if (isJsonMode()) printJson(res);
|
|
3865
4640
|
else ok(message);
|
|
3866
4641
|
}
|
|
3867
|
-
function
|
|
4642
|
+
function shortSha2(v) {
|
|
3868
4643
|
return typeof v === "string" ? v.slice(0, 7) : "";
|
|
3869
4644
|
}
|
|
3870
4645
|
async function confirm(question) {
|
|
3871
4646
|
if (!process.stdin.isTTY) return true;
|
|
3872
4647
|
const rl = createInterface3({ input: process.stdin, output: process.stderr });
|
|
3873
|
-
const answer = await new Promise((
|
|
4648
|
+
const answer = await new Promise((resolve3) => rl.question(`${question} [y/N] `, resolve3));
|
|
3874
4649
|
rl.close();
|
|
3875
4650
|
return /^y(es)?$/i.test(answer.trim());
|
|
3876
4651
|
}
|
|
3877
|
-
var list = new
|
|
3878
|
-
|
|
4652
|
+
var list = new Command12("list").description("List deployments (org-wide, or scoped to a project)").option("--project <id>", "Scope to a project (defaults to the linked project)").option("--env <environment>", "Filter by environment: production | preview").option("--limit <n>", "Max rows to fetch", "50").action(
|
|
4653
|
+
run3(async (opts) => {
|
|
3879
4654
|
const projectId = opts.project || readProjectLink()?.projectId;
|
|
3880
4655
|
const params = new URLSearchParams();
|
|
3881
4656
|
if (projectId) params.set("projectId", projectId);
|
|
@@ -3890,15 +4665,15 @@ var list = new Command11("list").description("List deployments (org-wide, or sco
|
|
|
3890
4665
|
status: d.status,
|
|
3891
4666
|
env: d.environment,
|
|
3892
4667
|
branch: d.branch,
|
|
3893
|
-
commit:
|
|
4668
|
+
commit: shortSha2(d.commitSha),
|
|
3894
4669
|
active: d.isActive ? "*" : "",
|
|
3895
4670
|
created: d.createdAt
|
|
3896
4671
|
}));
|
|
3897
4672
|
printTable(rows, ["id", "status", "env", "branch", "commit", "active", "created"]);
|
|
3898
4673
|
})
|
|
3899
4674
|
);
|
|
3900
|
-
var get = new
|
|
3901
|
-
|
|
4675
|
+
var get = new Command12("get").description("Show a single deployment").argument("<id>", "Deployment ID").action(
|
|
4676
|
+
run3(async (id) => {
|
|
3902
4677
|
const res = await apiRequest(`/deployments/${id}`);
|
|
3903
4678
|
const d = res.data ?? {};
|
|
3904
4679
|
if (isJsonMode()) return printJson(d);
|
|
@@ -3918,20 +4693,20 @@ var get = new Command11("get").description("Show a single deployment").argument(
|
|
|
3918
4693
|
);
|
|
3919
4694
|
})
|
|
3920
4695
|
);
|
|
3921
|
-
var info2 = new
|
|
3922
|
-
|
|
4696
|
+
var info2 = new Command12("info").description("Show container info for a deployment").argument("<id>", "Deployment ID").action(
|
|
4697
|
+
run3(async (id) => {
|
|
3923
4698
|
const res = await apiRequest(`/deployments/${id}/info`);
|
|
3924
4699
|
printJson(res.data ?? res);
|
|
3925
4700
|
})
|
|
3926
4701
|
);
|
|
3927
|
-
var usage = new
|
|
3928
|
-
|
|
4702
|
+
var usage = new Command12("usage").description("Show container resource usage for a deployment").argument("<id>", "Deployment ID").action(
|
|
4703
|
+
run3(async (id) => {
|
|
3929
4704
|
const res = await apiRequest(`/deployments/${id}/usage`);
|
|
3930
4705
|
printJson(res.data ?? res);
|
|
3931
4706
|
})
|
|
3932
4707
|
);
|
|
3933
|
-
var redeploy = new
|
|
3934
|
-
|
|
4708
|
+
var redeploy = new Command12("redeploy").description("Redeploy from an existing deployment").argument("<id>", "Deployment ID").option("--use-existing-commit", "Rebuild the same commit instead of the latest on the branch").action(
|
|
4709
|
+
run3(async (id, opts) => {
|
|
3935
4710
|
const res = await apiRequest(`/deployments/${id}/redeploy`, {
|
|
3936
4711
|
method: "POST",
|
|
3937
4712
|
body: JSON.stringify({ useExistingCommit: opts.useExistingCommit === true })
|
|
@@ -3939,14 +4714,14 @@ var redeploy = new Command11("redeploy").description("Redeploy from an existing
|
|
|
3939
4714
|
report(res, `Redeploy triggered for ${id}`);
|
|
3940
4715
|
})
|
|
3941
4716
|
);
|
|
3942
|
-
var rollback = new
|
|
3943
|
-
|
|
4717
|
+
var rollback = new Command12("rollback").description("Roll back to a previous deployment").argument("<id>", "Deployment ID to roll back to").action(
|
|
4718
|
+
run3(async (id) => {
|
|
3944
4719
|
const res = await apiRequest(`/deployments/${id}/rollback`, { method: "POST" });
|
|
3945
4720
|
report(res, `Rolled back to ${id}`);
|
|
3946
4721
|
})
|
|
3947
4722
|
);
|
|
3948
|
-
var pin = new
|
|
3949
|
-
|
|
4723
|
+
var pin = new Command12("pin").description("Pin (or unpin) a deployment's rollback artifact").argument("<id>", "Deployment ID").option("--off", "Unpin instead of pin").action(
|
|
4724
|
+
run3(async (id, opts) => {
|
|
3950
4725
|
const pinned = !opts.off;
|
|
3951
4726
|
const res = await apiRequest(`/deployments/${id}/pin`, {
|
|
3952
4727
|
method: "POST",
|
|
@@ -3955,32 +4730,32 @@ var pin = new Command11("pin").description("Pin (or unpin) a deployment's rollba
|
|
|
3955
4730
|
report(res, `${pinned ? "Pinned" : "Unpinned"} ${id}`);
|
|
3956
4731
|
})
|
|
3957
4732
|
);
|
|
3958
|
-
var cancel = new
|
|
3959
|
-
|
|
4733
|
+
var cancel = new Command12("cancel").description("Cancel an in-progress deployment").argument("<id>", "Deployment ID").action(
|
|
4734
|
+
run3(async (id) => {
|
|
3960
4735
|
const res = await apiRequest(`/deployments/${id}/cancel`, { method: "POST" });
|
|
3961
4736
|
report(res, `Cancelled ${id}`);
|
|
3962
4737
|
})
|
|
3963
4738
|
);
|
|
3964
|
-
var restart2 = new
|
|
3965
|
-
|
|
4739
|
+
var restart2 = new Command12("restart").description("Restart a deployment's container").argument("<id>", "Deployment ID").action(
|
|
4740
|
+
run3(async (id) => {
|
|
3966
4741
|
const res = await apiRequest(`/deployments/${id}/restart`, { method: "POST" });
|
|
3967
4742
|
report(res, `Restarted ${id}`);
|
|
3968
4743
|
})
|
|
3969
4744
|
);
|
|
3970
|
-
var reject = new
|
|
3971
|
-
|
|
4745
|
+
var reject = new Command12("reject").description("Reject a finished deployment awaiting a keep/reject decision").argument("<id>", "Deployment ID").action(
|
|
4746
|
+
run3(async (id) => {
|
|
3972
4747
|
const res = await apiRequest(`/deployments/${id}/reject`, { method: "POST" });
|
|
3973
4748
|
report(res, `Rejected ${id}`);
|
|
3974
4749
|
})
|
|
3975
4750
|
);
|
|
3976
|
-
var keep = new
|
|
3977
|
-
|
|
4751
|
+
var keep = new Command12("keep").description("Keep a finished deployment awaiting a keep/reject decision").argument("<id>", "Deployment ID").action(
|
|
4752
|
+
run3(async (id) => {
|
|
3978
4753
|
const res = await apiRequest(`/deployments/${id}/keep`, { method: "POST" });
|
|
3979
4754
|
report(res, `Kept ${id}`);
|
|
3980
4755
|
})
|
|
3981
4756
|
);
|
|
3982
|
-
var rm = new
|
|
3983
|
-
|
|
4757
|
+
var rm = new Command12("rm").description("Delete a deployment").argument("<id>", "Deployment ID").option("-y, --yes", "Skip the confirmation prompt").action(
|
|
4758
|
+
run3(async (id, opts) => {
|
|
3984
4759
|
if (!opts.yes && !isJsonMode() && !await confirm(`Delete deployment ${id}?`)) {
|
|
3985
4760
|
err("Aborted.");
|
|
3986
4761
|
process.exit(1);
|
|
@@ -3989,8 +4764,8 @@ var rm = new Command11("rm").description("Delete a deployment").argument("<id>",
|
|
|
3989
4764
|
report(res, `Deleted ${id}`);
|
|
3990
4765
|
})
|
|
3991
4766
|
);
|
|
3992
|
-
var sslStatus = new
|
|
3993
|
-
|
|
4767
|
+
var sslStatus = new Command12("status").description("Check SSL certificate status for a domain").argument("<domain>", "Domain to probe").action(
|
|
4768
|
+
run3(async (domain) => {
|
|
3994
4769
|
const res = await apiRequest("/deployments/ssl/status", {
|
|
3995
4770
|
method: "POST",
|
|
3996
4771
|
body: JSON.stringify({ domain })
|
|
@@ -3998,8 +4773,8 @@ var sslStatus = new Command11("status").description("Check SSL certificate statu
|
|
|
3998
4773
|
printJson(res);
|
|
3999
4774
|
})
|
|
4000
4775
|
);
|
|
4001
|
-
var sslRenew = new
|
|
4002
|
-
|
|
4776
|
+
var sslRenew = new Command12("renew").description("Renew (issue) an SSL certificate for a domain").argument("<domain>", "Domain to renew").option("--www", "Also include the www subdomain").action(
|
|
4777
|
+
run3(async (domain, opts) => {
|
|
4003
4778
|
const res = await apiRequest("/deployments/ssl/renew", {
|
|
4004
4779
|
method: "POST",
|
|
4005
4780
|
body: JSON.stringify({ domain, includeWww: opts.www === true })
|
|
@@ -4007,12 +4782,12 @@ var sslRenew = new Command11("renew").description("Renew (issue) an SSL certific
|
|
|
4007
4782
|
report(res, `SSL renewal requested for ${domain}`);
|
|
4008
4783
|
})
|
|
4009
4784
|
);
|
|
4010
|
-
var ssl = new
|
|
4011
|
-
var deploymentCommand = new
|
|
4785
|
+
var ssl = new Command12("ssl").description("SSL certificate operations").addCommand(sslStatus).addCommand(sslRenew);
|
|
4786
|
+
var deploymentCommand = new Command12("deployment").alias("deployments").description("Manage deployments (list, inspect, redeploy, rollback, \u2026)").addCommand(list).addCommand(get).addCommand(info2).addCommand(usage).addCommand(redeploy).addCommand(rollback).addCommand(pin).addCommand(cancel).addCommand(restart2).addCommand(reject).addCommand(keep).addCommand(rm).addCommand(ssl);
|
|
4012
4787
|
|
|
4013
4788
|
// src/commands/logs.ts
|
|
4014
|
-
import { Command as
|
|
4015
|
-
var logsCommand = new
|
|
4789
|
+
import { Command as Command13 } from "commander";
|
|
4790
|
+
var logsCommand = new Command13("logs").description("View or stream a deployment's logs").argument("<deploymentId>", "Deployment ID").option("-f, --follow", "Stream live logs via SSE until the deployment finishes").option("--tail <n>", "Show only the last N log lines (snapshot mode)").action(async (deploymentId, opts) => {
|
|
4016
4791
|
if (opts.follow) {
|
|
4017
4792
|
try {
|
|
4018
4793
|
const result = await streamDeploymentLogs(deploymentId);
|
|
@@ -4044,7 +4819,7 @@ var logsCommand = new Command12("logs").description("View or stream a deployment
|
|
|
4044
4819
|
});
|
|
4045
4820
|
|
|
4046
4821
|
// src/commands/project.ts
|
|
4047
|
-
import { Command as
|
|
4822
|
+
import { Command as Command14 } from "commander";
|
|
4048
4823
|
import chalk9 from "chalk";
|
|
4049
4824
|
import { createInterface as createInterface4 } from "readline/promises";
|
|
4050
4825
|
import { stdin as input3, stdout as output3 } from "process";
|
|
@@ -4081,7 +4856,7 @@ function printProject(project) {
|
|
|
4081
4856
|
}
|
|
4082
4857
|
}
|
|
4083
4858
|
var ENVIRONMENTS = ["production", "preview", "development"];
|
|
4084
|
-
var listCmd2 = new
|
|
4859
|
+
var listCmd2 = new Command14("list").alias("ls").description("List projects in the active organization").action(
|
|
4085
4860
|
action(async () => {
|
|
4086
4861
|
const rows = [];
|
|
4087
4862
|
for await (const p of paginate("/projects")) {
|
|
@@ -4096,7 +4871,7 @@ var listCmd2 = new Command13("list").alias("ls").description("List projects in t
|
|
|
4096
4871
|
printTable(rows, ["id", "name", "slug", "repo", "source"]);
|
|
4097
4872
|
})
|
|
4098
4873
|
);
|
|
4099
|
-
var getCmd = new
|
|
4874
|
+
var getCmd = new Command14("get").description("Show a single project").argument("<id>", "Project ID").action(
|
|
4100
4875
|
action(async (id) => {
|
|
4101
4876
|
const { data } = await apiRequest(
|
|
4102
4877
|
`/projects/${encodeURIComponent(id)}`
|
|
@@ -4104,7 +4879,7 @@ var getCmd = new Command13("get").description("Show a single project").argument(
|
|
|
4104
4879
|
printProject(data);
|
|
4105
4880
|
})
|
|
4106
4881
|
);
|
|
4107
|
-
var createCmd = new
|
|
4882
|
+
var createCmd = new Command14("create").description("Create a project").requiredOption("--name <name>", "Project name").option("--slug <slug>", "Free-subdomain slug (slug.opsh.io)").option("--git-owner <owner>", "GitHub owner/org").option("--git-repo <repo>", "GitHub repository name").option("--git-branch <branch>", "Git branch to deploy").option("--framework <framework>", "Stack/framework id").option("--local-path <path>", "Local source path").option("--port <port>", "Container port", (v) => Number(v)).option(
|
|
4108
4883
|
"--type <type>",
|
|
4109
4884
|
"Project type: app | docker | services | monorepo"
|
|
4110
4885
|
).action(
|
|
@@ -4128,7 +4903,7 @@ var createCmd = new Command13("create").description("Create a project").required
|
|
|
4128
4903
|
printProject(data);
|
|
4129
4904
|
})
|
|
4130
4905
|
);
|
|
4131
|
-
var deleteCmd = new
|
|
4906
|
+
var deleteCmd = new Command14("delete").alias("rm").description("Delete a project (tears down all resources)").argument("<id>", "Project ID").option("--force", "Cancel active work and delete anyway").option("--force-orphan", "Orphan resources that won't destroy, then drop the row").option("--wipe-volumes", "Also destroy persistent volumes").option("-y, --yes", "Skip the confirmation prompt").action(
|
|
4132
4907
|
action(async (id, opts) => {
|
|
4133
4908
|
if (!opts.yes) {
|
|
4134
4909
|
const rl = createInterface4({ input: input3, output: output3 });
|
|
@@ -4159,7 +4934,7 @@ var deleteCmd = new Command13("delete").alias("rm").description("Delete a projec
|
|
|
4159
4934
|
`);
|
|
4160
4935
|
})
|
|
4161
4936
|
);
|
|
4162
|
-
var envCmd = new
|
|
4937
|
+
var envCmd = new Command14("env").description("Manage project environment variables");
|
|
4163
4938
|
envCmd.command("get").description("List env vars (secret values are masked by the API)").argument("<id>", "Project ID").option("--environment <env>", "Filter by environment (production|preview|development)").action(
|
|
4164
4939
|
action(async (id, opts) => {
|
|
4165
4940
|
const qs = opts.environment ? `?environment=${encodeURIComponent(opts.environment)}` : "";
|
|
@@ -4233,7 +5008,7 @@ envCmd.command("set").description("Merge env vars: upsert KEY=VALUE pairs and/or
|
|
|
4233
5008
|
);
|
|
4234
5009
|
})
|
|
4235
5010
|
);
|
|
4236
|
-
var gitCmd = new
|
|
5011
|
+
var gitCmd = new Command14("git").description("Manage git linkage and auto-deploy");
|
|
4237
5012
|
gitCmd.command("link").description("Link a GitHub repository to a project").argument("<id>", "Project ID").requiredOption("--owner <owner>", "GitHub owner/org").requiredOption("--repo <repo>", "Repository name").option("--branch <branch>", "Branch (defaults to the repo's default branch)").option("--installation-id <id>", "GitHub App installation id", (v) => Number(v)).action(
|
|
4238
5013
|
action(async (id, opts) => {
|
|
4239
5014
|
const result = await apiRequest(
|
|
@@ -4321,7 +5096,7 @@ gitCmd.command("webhook-domain").description("Set or clear the domain that recei
|
|
|
4321
5096
|
`);
|
|
4322
5097
|
})
|
|
4323
5098
|
);
|
|
4324
|
-
var connectCmd = new
|
|
5099
|
+
var connectCmd = new Command14("connect").description("Connect a custom domain to a project").argument("<id>", "Project ID").argument("<domain>", "Custom domain hostname").option("--include-www", "Also connect the www. variant").action(
|
|
4325
5100
|
action(async (id, domain, opts) => {
|
|
4326
5101
|
const result = await apiRequest(
|
|
4327
5102
|
`/projects/${encodeURIComponent(id)}/connect`,
|
|
@@ -4341,7 +5116,7 @@ var connectCmd = new Command13("connect").description("Connect a custom domain t
|
|
|
4341
5116
|
printJson(result.records);
|
|
4342
5117
|
})
|
|
4343
5118
|
);
|
|
4344
|
-
var enableCmd = new
|
|
5119
|
+
var enableCmd = new Command14("enable").description("Start a stopped project").argument("<id>", "Project ID").action(
|
|
4345
5120
|
action(async (id) => {
|
|
4346
5121
|
const result = await apiRequest(
|
|
4347
5122
|
`/projects/${encodeURIComponent(id)}/enable`,
|
|
@@ -4353,7 +5128,7 @@ var enableCmd = new Command13("enable").description("Start a stopped project").a
|
|
|
4353
5128
|
`);
|
|
4354
5129
|
})
|
|
4355
5130
|
);
|
|
4356
|
-
var disableCmd = new
|
|
5131
|
+
var disableCmd = new Command14("disable").description("Stop a running project").argument("<id>", "Project ID").action(
|
|
4357
5132
|
action(async (id) => {
|
|
4358
5133
|
const result = await apiRequest(
|
|
4359
5134
|
`/projects/${encodeURIComponent(id)}/disable`,
|
|
@@ -4366,7 +5141,7 @@ var disableCmd = new Command13("disable").description("Stop a running project").
|
|
|
4366
5141
|
})
|
|
4367
5142
|
);
|
|
4368
5143
|
var SLEEP_MODES = ["auto_sleep", "always_on"];
|
|
4369
|
-
var sleepModeCmd = new
|
|
5144
|
+
var sleepModeCmd = new Command14("sleep-mode").description("Set the project sleep mode").argument("<id>", "Project ID").argument("<mode>", `One of: ${SLEEP_MODES.join(", ")}`).action(
|
|
4370
5145
|
action(async (id, mode) => {
|
|
4371
5146
|
if (!SLEEP_MODES.includes(mode)) {
|
|
4372
5147
|
err(` mode must be one of: ${SLEEP_MODES.join(", ")}`);
|
|
@@ -4384,7 +5159,7 @@ var sleepModeCmd = new Command13("sleep-mode").description("Set the project slee
|
|
|
4384
5159
|
})
|
|
4385
5160
|
);
|
|
4386
5161
|
var TRANSFER_DIRS = ["to-cloud", "to-self-hosted"];
|
|
4387
|
-
var transferCmd = new
|
|
5162
|
+
var transferCmd = new Command14("transfer").description("Promote a project to Openship Cloud, or bring it back (self-hosted only)").argument("<id>", "Project ID").argument("<direction>", `One of: ${TRANSFER_DIRS.join(", ")}`).action(
|
|
4388
5163
|
action(async (id, direction) => {
|
|
4389
5164
|
if (!TRANSFER_DIRS.includes(direction)) {
|
|
4390
5165
|
err(` direction must be one of: ${TRANSFER_DIRS.join(", ")}`);
|
|
@@ -4407,7 +5182,7 @@ var transferCmd = new Command13("transfer").description("Promote a project to Op
|
|
|
4407
5182
|
);
|
|
4408
5183
|
})
|
|
4409
5184
|
);
|
|
4410
|
-
var logsCmd = new
|
|
5185
|
+
var logsCmd = new Command14("logs").description("Show or stream runtime (container) logs").argument("<id>", "Project ID").option("--tail <n>", "Number of recent lines", (v) => Number(v)).option("-f, --follow", "Stream logs until interrupted").action(
|
|
4411
5186
|
action(async (id, opts) => {
|
|
4412
5187
|
const tailQs = opts.tail ? `?tail=${opts.tail}` : "";
|
|
4413
5188
|
if (!opts.follow) {
|
|
@@ -4435,7 +5210,7 @@ var logsCmd = new Command13("logs").description("Show or stream runtime (contain
|
|
|
4435
5210
|
}
|
|
4436
5211
|
})
|
|
4437
5212
|
);
|
|
4438
|
-
var serverLogsCmd = new
|
|
5213
|
+
var serverLogsCmd = new Command14("server-logs").description("Show or stream HTTP request logs (edge/OpenResty)").argument("<id>", "Project ID").option("--limit <n>", "Number of recent entries (max 200)", (v) => Number(v)).option("--domain <domain>", "Restrict to a specific domain").option("-f, --follow", "Stream request logs until interrupted").action(
|
|
4439
5214
|
action(async (id, opts) => {
|
|
4440
5215
|
const base = `/projects/${encodeURIComponent(id)}/server-logs`;
|
|
4441
5216
|
const domainQs = opts.domain ? `domain=${encodeURIComponent(opts.domain)}` : "";
|
|
@@ -4483,7 +5258,7 @@ function printLogEntry(entry) {
|
|
|
4483
5258
|
process.stdout.write(` ${ts} ${color(level.padEnd(5))} ${String(msg)}
|
|
4484
5259
|
`);
|
|
4485
5260
|
}
|
|
4486
|
-
var projectCommand = new
|
|
5261
|
+
var projectCommand = new Command14("project").alias("projects").description("Manage Openship projects");
|
|
4487
5262
|
projectCommand.addCommand(listCmd2);
|
|
4488
5263
|
projectCommand.addCommand(getCmd);
|
|
4489
5264
|
projectCommand.addCommand(createCmd);
|
|
@@ -4499,9 +5274,9 @@ projectCommand.addCommand(logsCmd);
|
|
|
4499
5274
|
projectCommand.addCommand(serverLogsCmd);
|
|
4500
5275
|
|
|
4501
5276
|
// src/commands/service.ts
|
|
4502
|
-
import { Command as
|
|
5277
|
+
import { Command as Command15 } from "commander";
|
|
4503
5278
|
import chalk10 from "chalk";
|
|
4504
|
-
import { spawnSync as
|
|
5279
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
4505
5280
|
import path from "path";
|
|
4506
5281
|
import { createInterface as createInterface5 } from "readline/promises";
|
|
4507
5282
|
import { stdin as input4, stdout as output4 } from "process";
|
|
@@ -4520,7 +5295,7 @@ function fail(e) {
|
|
|
4520
5295
|
process.exit(1);
|
|
4521
5296
|
}
|
|
4522
5297
|
function stackCommand(name) {
|
|
4523
|
-
return new
|
|
5298
|
+
return new Command15(name).requiredOption(
|
|
4524
5299
|
"-p, --project <id|slug|name>",
|
|
4525
5300
|
"Stack (project) id, slug, or name"
|
|
4526
5301
|
);
|
|
@@ -4768,7 +5543,7 @@ function mapComposeService(name, def, baseDir) {
|
|
|
4768
5543
|
var syncCmd = stackCommand("sync").description("Sync a stack's services from a docker-compose file (services not in the file are removed)").argument("<compose-file>", "Path to docker-compose.yml / compose.yaml").option("-y, --yes", "Skip the confirmation prompt").action(async (composeFile, opts) => {
|
|
4769
5544
|
requireAuth();
|
|
4770
5545
|
const abs = path.resolve(composeFile);
|
|
4771
|
-
const proc =
|
|
5546
|
+
const proc = spawnSync4(
|
|
4772
5547
|
"docker",
|
|
4773
5548
|
["compose", "-f", abs, "config", "--format", "json"],
|
|
4774
5549
|
{ encoding: "utf8", maxBuffer: 64 * 1024 * 1024 }
|
|
@@ -4862,7 +5637,7 @@ var containersCmd = stackCommand("containers").description("List the stack's act
|
|
|
4862
5637
|
fail(e);
|
|
4863
5638
|
}
|
|
4864
5639
|
});
|
|
4865
|
-
var driftCmd = new
|
|
5640
|
+
var driftCmd = new Command15("drift").description(
|
|
4866
5641
|
"Resolve compose drift on a service (upstream compose changed a value you edited)"
|
|
4867
5642
|
);
|
|
4868
5643
|
function driftActionCommand(action2) {
|
|
@@ -4890,7 +5665,7 @@ function driftActionCommand(action2) {
|
|
|
4890
5665
|
}
|
|
4891
5666
|
driftCmd.addCommand(driftActionCommand("accept"));
|
|
4892
5667
|
driftCmd.addCommand(driftActionCommand("keep"));
|
|
4893
|
-
var envCmd2 = new
|
|
5668
|
+
var envCmd2 = new Command15("env").description("Read and write a service's environment variables");
|
|
4894
5669
|
var envGetCmd = stackCommand("get").description("List a service's environment variables (secrets masked)").argument("<service>", "Service name or id").option("-e, --env <environment>", "Environment: production | preview | development").action(async (service, opts) => {
|
|
4895
5670
|
requireAuth();
|
|
4896
5671
|
try {
|
|
@@ -5023,7 +5798,7 @@ var execCmd = stackCommand("exec").description("Open an interactive shell in a s
|
|
|
5023
5798
|
);
|
|
5024
5799
|
process.exit(1);
|
|
5025
5800
|
});
|
|
5026
|
-
var serviceCommand = new
|
|
5801
|
+
var serviceCommand = new Command15("service").alias("services").description("Manage the services in a compose stack (a multi-service project)");
|
|
5027
5802
|
serviceCommand.addCommand(listCmd3);
|
|
5028
5803
|
serviceCommand.addCommand(getCmd2);
|
|
5029
5804
|
serviceCommand.addCommand(createCmd2);
|
|
@@ -5039,7 +5814,7 @@ serviceCommand.addCommand(logsCmd2);
|
|
|
5039
5814
|
serviceCommand.addCommand(execCmd);
|
|
5040
5815
|
|
|
5041
5816
|
// src/commands/domain.ts
|
|
5042
|
-
import { Command as
|
|
5817
|
+
import { Command as Command16 } from "commander";
|
|
5043
5818
|
import chalk11 from "chalk";
|
|
5044
5819
|
import ora3 from "ora";
|
|
5045
5820
|
function spin(text2) {
|
|
@@ -5075,7 +5850,7 @@ function printRecords(result) {
|
|
|
5075
5850
|
["type", "host", "value"]
|
|
5076
5851
|
);
|
|
5077
5852
|
}
|
|
5078
|
-
var listCmd4 = new
|
|
5853
|
+
var listCmd4 = new Command16("list").description("List a project's custom domains").requiredOption("-p, --project <id>", "Project ID to list domains for").action(async (opts) => {
|
|
5079
5854
|
try {
|
|
5080
5855
|
const res = await apiRequest(
|
|
5081
5856
|
`/domains?projectId=${encodeURIComponent(opts.project)}`
|
|
@@ -5090,7 +5865,7 @@ var listCmd4 = new Command15("list").description("List a project's custom domain
|
|
|
5090
5865
|
fail2(e);
|
|
5091
5866
|
}
|
|
5092
5867
|
});
|
|
5093
|
-
var addCmd2 = new
|
|
5868
|
+
var addCmd2 = new Command16("add").description("Add a custom domain to a project").argument("<hostname>", "Domain hostname (e.g. app.example.com)").requiredOption("-p, --project <id>", "Project ID to attach the domain to").option("--primary", "Mark this domain as the project's primary", false).action(async (hostname, opts) => {
|
|
5094
5869
|
const sp = spin(`Adding ${hostname}\u2026`);
|
|
5095
5870
|
try {
|
|
5096
5871
|
const res = await apiRequest("/domains", {
|
|
@@ -5109,7 +5884,7 @@ var addCmd2 = new Command15("add").description("Add a custom domain to a project
|
|
|
5109
5884
|
fail2(e);
|
|
5110
5885
|
}
|
|
5111
5886
|
});
|
|
5112
|
-
var previewCmd = new
|
|
5887
|
+
var previewCmd = new Command16("preview").description("Preview the DNS records a hostname would need (no changes saved)").argument("<hostname>", "Domain hostname to preview").action(async (hostname) => {
|
|
5113
5888
|
try {
|
|
5114
5889
|
const res = await apiRequest("/domains/preview", {
|
|
5115
5890
|
method: "POST",
|
|
@@ -5120,7 +5895,7 @@ var previewCmd = new Command15("preview").description("Preview the DNS records a
|
|
|
5120
5895
|
fail2(e);
|
|
5121
5896
|
}
|
|
5122
5897
|
});
|
|
5123
|
-
var verifyCmd = new
|
|
5898
|
+
var verifyCmd = new Command16("verify").description("Run DNS verification for a domain").argument("<id>", "Domain ID").action(async (id) => {
|
|
5124
5899
|
const sp = spin("Checking DNS records\u2026");
|
|
5125
5900
|
try {
|
|
5126
5901
|
const res = await apiRaw(`/domains/${encodeURIComponent(id)}/verify`, { method: "POST" });
|
|
@@ -5146,7 +5921,7 @@ var verifyCmd = new Command15("verify").description("Run DNS verification for a
|
|
|
5146
5921
|
fail2(e);
|
|
5147
5922
|
}
|
|
5148
5923
|
});
|
|
5149
|
-
var primaryCmd = new
|
|
5924
|
+
var primaryCmd = new Command16("primary").description("Make a domain the project's primary hostname").argument("<id>", "Domain ID").action(async (id) => {
|
|
5150
5925
|
const sp = spin("Setting primary\u2026");
|
|
5151
5926
|
try {
|
|
5152
5927
|
const res = await apiRequest(`/domains/${encodeURIComponent(id)}/primary`, {
|
|
@@ -5159,7 +5934,7 @@ var primaryCmd = new Command15("primary").description("Make a domain the project
|
|
|
5159
5934
|
fail2(e);
|
|
5160
5935
|
}
|
|
5161
5936
|
});
|
|
5162
|
-
var recordsCmd = new
|
|
5937
|
+
var recordsCmd = new Command16("records").description("Show the DNS records for an existing domain").argument("<id>", "Domain ID").action(async (id) => {
|
|
5163
5938
|
try {
|
|
5164
5939
|
const res = await apiRequest(`/domains/${encodeURIComponent(id)}/records`);
|
|
5165
5940
|
printRecords(res.data);
|
|
@@ -5177,7 +5952,7 @@ function printSsl(data) {
|
|
|
5177
5952
|
if (data.issuer) info(` issuer: ${data.issuer}`);
|
|
5178
5953
|
if (data.expiresAt) info(` expires: ${data.expiresAt}`);
|
|
5179
5954
|
}
|
|
5180
|
-
var renewCmd = new
|
|
5955
|
+
var renewCmd = new Command16("renew").description("Renew the SSL certificate for a domain").argument("<id>", "Domain ID").action(async (id) => {
|
|
5181
5956
|
const sp = spin("Renewing certificate\u2026");
|
|
5182
5957
|
try {
|
|
5183
5958
|
const res = await apiRequest(`/domains/${encodeURIComponent(id)}/renew`, {
|
|
@@ -5190,7 +5965,7 @@ var renewCmd = new Command15("renew").description("Renew the SSL certificate for
|
|
|
5190
5965
|
fail2(e);
|
|
5191
5966
|
}
|
|
5192
5967
|
});
|
|
5193
|
-
var verifySslCmd = new
|
|
5968
|
+
var verifySslCmd = new Command16("verify-ssl").description("Recheck that a domain's SSL certificate is issued and valid (no reissue)").argument("<id>", "Domain ID").action(async (id) => {
|
|
5194
5969
|
const sp = spin("Checking certificate\u2026");
|
|
5195
5970
|
try {
|
|
5196
5971
|
const res = await apiRequest(`/domains/${encodeURIComponent(id)}/verify-ssl`, {
|
|
@@ -5205,7 +5980,7 @@ var verifySslCmd = new Command15("verify-ssl").description("Recheck that a domai
|
|
|
5205
5980
|
fail2(e);
|
|
5206
5981
|
}
|
|
5207
5982
|
});
|
|
5208
|
-
var renewAllCmd = new
|
|
5983
|
+
var renewAllCmd = new Command16("renew-all").description("Renew SSL for every near-expiry domain in your organization").action(async () => {
|
|
5209
5984
|
const sp = spin("Renewing expiring certificates\u2026");
|
|
5210
5985
|
try {
|
|
5211
5986
|
const res = await apiRequest("/domains/renew-all", { method: "POST" });
|
|
@@ -5227,10 +6002,10 @@ var renewAllCmd = new Command15("renew-all").description("Renew SSL for every ne
|
|
|
5227
6002
|
fail2(e);
|
|
5228
6003
|
}
|
|
5229
6004
|
});
|
|
5230
|
-
var domainCommand = new
|
|
6005
|
+
var domainCommand = new Command16("domain").description("Manage custom domains, DNS verification, and SSL certificates").addCommand(listCmd4).addCommand(addCmd2).addCommand(previewCmd).addCommand(verifyCmd).addCommand(primaryCmd).addCommand(recordsCmd).addCommand(renewCmd).addCommand(verifySslCmd).addCommand(renewAllCmd);
|
|
5231
6006
|
|
|
5232
6007
|
// src/commands/server.ts
|
|
5233
|
-
import { Command as
|
|
6008
|
+
import { Command as Command17 } from "commander";
|
|
5234
6009
|
import chalk12 from "chalk";
|
|
5235
6010
|
import ora4 from "ora";
|
|
5236
6011
|
var INSTALLABLE = ["docker", "git", "openresty", "certbot", "rsync"];
|
|
@@ -5263,7 +6038,7 @@ function connBody(o) {
|
|
|
5263
6038
|
sshArgs: o.sshArgs
|
|
5264
6039
|
};
|
|
5265
6040
|
}
|
|
5266
|
-
var server = new
|
|
6041
|
+
var server = new Command17("server").description("Manage self-hosted SSH servers");
|
|
5267
6042
|
server.command("list").alias("ls").description("List servers in the active organization").action(
|
|
5268
6043
|
guard(async () => {
|
|
5269
6044
|
const servers = await apiRequest("/system/servers");
|
|
@@ -5525,9 +6300,9 @@ function fmtUptime(seconds) {
|
|
|
5525
6300
|
var serverCommand = server;
|
|
5526
6301
|
|
|
5527
6302
|
// src/commands/system.ts
|
|
5528
|
-
import { Command as
|
|
6303
|
+
import { Command as Command18 } from "commander";
|
|
5529
6304
|
import ora5 from "ora";
|
|
5530
|
-
import { readFileSync as
|
|
6305
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
5531
6306
|
import { createInterface as createInterface6 } from "readline/promises";
|
|
5532
6307
|
import { stdin as input5, stdout as output5 } from "process";
|
|
5533
6308
|
async function guarded(fn) {
|
|
@@ -5573,7 +6348,7 @@ async function promptHidden(query) {
|
|
|
5573
6348
|
output5.write("\n");
|
|
5574
6349
|
return answer;
|
|
5575
6350
|
}
|
|
5576
|
-
var settingsCommand = new
|
|
6351
|
+
var settingsCommand = new Command18("settings").description("Read or update instance settings");
|
|
5577
6352
|
settingsCommand.command("get").description("Show current instance settings").action(async () => {
|
|
5578
6353
|
await guarded(async () => {
|
|
5579
6354
|
const s = await apiRequest("/system/settings");
|
|
@@ -5612,7 +6387,7 @@ settingsCommand.command("set").description("Update instance-level settings").opt
|
|
|
5612
6387
|
`));
|
|
5613
6388
|
});
|
|
5614
6389
|
});
|
|
5615
|
-
var onboardingCommand = new
|
|
6390
|
+
var onboardingCommand = new Command18("onboarding").description("First-run instance setup");
|
|
5616
6391
|
onboardingCommand.command("apply").description("Configure a fresh instance (fails once already configured)").option("--ssh-host <host>", "SSH host of the target server").option("--ssh-port <n>", "SSH port (default 22)").option("--ssh-user <user>", "SSH user (default root)").option("--ssh-auth-method <method>", "SSH auth method").option("--ssh-password <password>", "SSH password").option("--ssh-key-path <path>", "SSH private key path").option("--ssh-key-passphrase <pass>", "SSH key passphrase").option("--ssh-jump-host <host>", "SSH jump host").option("--ssh-args <args>", "Extra SSH args").option("--server-name <name>", "Display name for the server").option("--auth-mode <mode>", "Initial auth mode: none | local | cloud").option("--tunnel-provider <provider>", "Tunnel provider").option("--tunnel-token <token>", "Tunnel token").option("--default-build-mode <mode>", "Default build mode").option("--default-rollback-window <n>", "Default rollback window").action(async (opts) => {
|
|
5617
6392
|
await guarded(async () => {
|
|
5618
6393
|
const body = {
|
|
@@ -5646,7 +6421,7 @@ onboardingCommand.command("apply").description("Configure a fresh instance (fail
|
|
|
5646
6421
|
}
|
|
5647
6422
|
});
|
|
5648
6423
|
});
|
|
5649
|
-
var upgradeToAuthCommand = new
|
|
6424
|
+
var upgradeToAuthCommand = new Command18("upgrade-to-auth").description("Promote a zero-auth instance to email/password login").option("--name <name>", "Account display name").option("--email <email>", "Account email").option("--password <password>", "Account password (prompted if omitted)").option("--use-own-mail-server", "Warm the self-hosted mail server for auth emails").action(async (opts) => {
|
|
5650
6425
|
await guarded(async () => {
|
|
5651
6426
|
const name = opts.name;
|
|
5652
6427
|
const email = opts.email;
|
|
@@ -5675,7 +6450,7 @@ var upgradeToAuthCommand = new Command17("upgrade-to-auth").description("Promote
|
|
|
5675
6450
|
`));
|
|
5676
6451
|
});
|
|
5677
6452
|
});
|
|
5678
|
-
var browseCommand = new
|
|
6453
|
+
var browseCommand = new Command18("browse").description("List directories on the instance host (defaults to home)").argument("[path]", "Directory to list").action(async (path2) => {
|
|
5679
6454
|
await guarded(async () => {
|
|
5680
6455
|
const qs = path2 ? `?path=${encodeURIComponent(path2)}` : "";
|
|
5681
6456
|
const res = await apiRequest(`/system/browse${qs}`);
|
|
@@ -5704,7 +6479,7 @@ function buildDomain(opts) {
|
|
|
5704
6479
|
err("\n A domain is required: pass --hostname <host> or --slug <slug>.\n");
|
|
5705
6480
|
process.exit(1);
|
|
5706
6481
|
}
|
|
5707
|
-
var migrationCommand = new
|
|
6482
|
+
var migrationCommand = new Command18("migration").description("Team-mode migration lifecycle");
|
|
5708
6483
|
migrationCommand.command("preflight").description("Read-only readiness check for the own-server migration").requiredOption("--server-id <id>", "Target server id").option("--hostname <host>", "Custom domain pointing at the server").option("--slug <slug>", "Free <slug>.opsh.io subdomain").action(async (opts) => {
|
|
5709
6484
|
await guarded(async () => {
|
|
5710
6485
|
const domain = buildDomain(opts);
|
|
@@ -5811,7 +6586,7 @@ migrationCommand.command("switch-back").description("Reverse migration back to s
|
|
|
5811
6586
|
}
|
|
5812
6587
|
});
|
|
5813
6588
|
});
|
|
5814
|
-
var dataTransferCommand = new
|
|
6589
|
+
var dataTransferCommand = new Command18("data-transfer").description(
|
|
5815
6590
|
"Whole-instance export / import (owner-only)"
|
|
5816
6591
|
);
|
|
5817
6592
|
dataTransferCommand.command("export").description("Export the entire instance to a JSON file").option("--passphrase <passphrase>", "Seal secrets under this passphrase").option("--out <file>", "Write the export to this file instead of stdout").action(async (opts) => {
|
|
@@ -5827,7 +6602,7 @@ dataTransferCommand.command("export").description("Export the entire instance to
|
|
|
5827
6602
|
);
|
|
5828
6603
|
spin4?.succeed("Export ready.");
|
|
5829
6604
|
if (opts.out) {
|
|
5830
|
-
|
|
6605
|
+
writeFileSync8(opts.out, JSON.stringify(file));
|
|
5831
6606
|
const tables = Object.keys(file.dump?.tables ?? {}).length;
|
|
5832
6607
|
report2(
|
|
5833
6608
|
{ out: opts.out, tables },
|
|
@@ -5853,7 +6628,7 @@ dataTransferCommand.command("import").description("Import an instance export fil
|
|
|
5853
6628
|
}
|
|
5854
6629
|
let file;
|
|
5855
6630
|
try {
|
|
5856
|
-
file = JSON.parse(
|
|
6631
|
+
file = JSON.parse(readFileSync9(opts.file, "utf8"));
|
|
5857
6632
|
} catch {
|
|
5858
6633
|
err(`
|
|
5859
6634
|
Could not read or parse ${opts.file}.
|
|
@@ -5881,10 +6656,10 @@ dataTransferCommand.command("import").description("Import an instance export fil
|
|
|
5881
6656
|
}
|
|
5882
6657
|
});
|
|
5883
6658
|
});
|
|
5884
|
-
var systemCommand = new
|
|
6659
|
+
var systemCommand = new Command18("system").description("Instance settings, onboarding, migration, and data transfer").addCommand(settingsCommand).addCommand(onboardingCommand).addCommand(upgradeToAuthCommand).addCommand(browseCommand).addCommand(migrationCommand).addCommand(dataTransferCommand);
|
|
5885
6660
|
|
|
5886
6661
|
// src/commands/mail.ts
|
|
5887
|
-
import { Command as
|
|
6662
|
+
import { Command as Command19 } from "commander";
|
|
5888
6663
|
import chalk13 from "chalk";
|
|
5889
6664
|
import ora6 from "ora";
|
|
5890
6665
|
import { createInterface as createInterface7 } from "readline/promises";
|
|
@@ -5926,7 +6701,7 @@ function printRecordsObject(records) {
|
|
|
5926
6701
|
if (rows.length === 0) return info(" (no DNS records)");
|
|
5927
6702
|
printTable(rows, ["key", "type", "host", "value"]);
|
|
5928
6703
|
}
|
|
5929
|
-
var stepsCmd = new
|
|
6704
|
+
var stepsCmd = new Command19("steps").description("List the mail setup steps").action(
|
|
5930
6705
|
guard2(async () => {
|
|
5931
6706
|
const res = await apiRequest(
|
|
5932
6707
|
"/mail/steps"
|
|
@@ -5939,7 +6714,7 @@ var stepsCmd = new Command18("steps").description("List the mail setup steps").a
|
|
|
5939
6714
|
info(` ${res.total} steps total.`);
|
|
5940
6715
|
})
|
|
5941
6716
|
);
|
|
5942
|
-
var statusCmd = new
|
|
6717
|
+
var statusCmd = new Command19("status").description("Show the setup progress for a mail server").argument("[serverId]", "Mail server ID (omit for the empty welcome shell)").action(
|
|
5943
6718
|
guard2(async (serverId) => {
|
|
5944
6719
|
const q = serverId ? `?serverId=${encodeURIComponent(serverId)}` : "";
|
|
5945
6720
|
const res = await apiRequest(`/mail/status${q}`);
|
|
@@ -5960,7 +6735,7 @@ var statusCmd = new Command18("status").description("Show the setup progress for
|
|
|
5960
6735
|
}
|
|
5961
6736
|
})
|
|
5962
6737
|
);
|
|
5963
|
-
var serversCmd = new
|
|
6738
|
+
var serversCmd = new Command19("servers").description("List every server the mail stack is installed on").action(
|
|
5964
6739
|
guard2(async () => {
|
|
5965
6740
|
const res = await apiRequest(
|
|
5966
6741
|
"/mail/servers"
|
|
@@ -5981,7 +6756,7 @@ var serversCmd = new Command18("servers").description("List every server the mai
|
|
|
5981
6756
|
);
|
|
5982
6757
|
})
|
|
5983
6758
|
);
|
|
5984
|
-
var scanCmd = new
|
|
6759
|
+
var scanCmd = new Command19("scan").description("Probe a server for an existing mail install (read-only)").argument("<serverId>", "Server ID to scan").action(
|
|
5985
6760
|
guard2(async (serverId) => {
|
|
5986
6761
|
const sp = spin2("Scanning server\u2026");
|
|
5987
6762
|
const res = await apiRequest("/mail/scan", { method: "POST", body: JSON.stringify({ serverId }) });
|
|
@@ -5996,7 +6771,7 @@ var scanCmd = new Command18("scan").description("Probe a server for an existing
|
|
|
5996
6771
|
else info(" Nothing to adopt on this server.");
|
|
5997
6772
|
})
|
|
5998
6773
|
);
|
|
5999
|
-
var adoptCmd = new
|
|
6774
|
+
var adoptCmd = new Command19("adopt").description("Re-adopt an existing mail install whose orchestrator state was lost").argument("<serverId>", "Server ID to adopt").action(
|
|
6000
6775
|
guard2(async (serverId) => {
|
|
6001
6776
|
const sp = spin2("Adopting mail server\u2026");
|
|
6002
6777
|
const res = await apiRequest(
|
|
@@ -6009,7 +6784,7 @@ var adoptCmd = new Command18("adopt").description("Re-adopt an existing mail ins
|
|
|
6009
6784
|
info(` completed: ${res.completed ? "yes" : "no"}`);
|
|
6010
6785
|
})
|
|
6011
6786
|
);
|
|
6012
|
-
var setupCmd = new
|
|
6787
|
+
var setupCmd = new Command19("setup").description("Start or resume the mail setup wizard (streams over SSE)").argument("<serverId>", "Server ID to install the mail stack on").requiredOption("-d, --domain <domain>", "Mail domain (e.g. example.com)").option("--start-step <n>", "Resume from a specific step (1-13)").option("--config <json>", "iRedMail config overrides as a JSON object").action(
|
|
6013
6788
|
guard2(async (serverId, opts) => {
|
|
6014
6789
|
let config;
|
|
6015
6790
|
if (opts.config) {
|
|
@@ -6078,7 +6853,7 @@ var setupCmd = new Command18("setup").description("Start or resume the mail setu
|
|
|
6078
6853
|
if (failed) process.exit(1);
|
|
6079
6854
|
})
|
|
6080
6855
|
);
|
|
6081
|
-
var cancelCmd = new
|
|
6856
|
+
var cancelCmd = new Command19("cancel").description("Cancel the mail setup currently running").action(
|
|
6082
6857
|
guard2(async () => {
|
|
6083
6858
|
const res = await apiRequest("/mail/setup/cancel", {
|
|
6084
6859
|
method: "POST"
|
|
@@ -6088,7 +6863,7 @@ var cancelCmd = new Command18("cancel").description("Cancel the mail setup curre
|
|
|
6088
6863
|
})
|
|
6089
6864
|
);
|
|
6090
6865
|
function ackCommand(name, path2, description, successMsg) {
|
|
6091
|
-
return new
|
|
6866
|
+
return new Command19(name).description(description).argument("<serverId>", "Mail server ID").action(
|
|
6092
6867
|
guard2(async (serverId) => {
|
|
6093
6868
|
const res = await apiRequest(path2, {
|
|
6094
6869
|
method: "POST",
|
|
@@ -6111,7 +6886,7 @@ var ptrAckCmd = ackCommand(
|
|
|
6111
6886
|
"Acknowledge that reverse DNS (PTR) is configured",
|
|
6112
6887
|
"PTR acknowledged. Re-run `mail setup` with --start-step to continue."
|
|
6113
6888
|
);
|
|
6114
|
-
var resetCmd = new
|
|
6889
|
+
var resetCmd = new Command19("reset").description("Wipe the on-server setup state file (does NOT touch installed daemons)").argument("<serverId>", "Mail server ID").option("-y, --yes", "Skip the confirmation prompt").action(
|
|
6115
6890
|
guard2(async (serverId, opts) => {
|
|
6116
6891
|
if (!opts.yes && !isJsonMode()) {
|
|
6117
6892
|
const rl = createInterface7({ input: input6, output: output6 });
|
|
@@ -6127,7 +6902,7 @@ var resetCmd = new Command18("reset").description("Wipe the on-server setup stat
|
|
|
6127
6902
|
ok(" Setup state reset.");
|
|
6128
6903
|
})
|
|
6129
6904
|
);
|
|
6130
|
-
var forgetCmd = new
|
|
6905
|
+
var forgetCmd = new Command19("forget").description("Stop managing a mail server (drops the DB row; leaves the stack + state intact)").argument("<serverId>", "Mail server ID").action(
|
|
6131
6906
|
guard2(async (serverId) => {
|
|
6132
6907
|
const res = await apiRequest(`/mail/servers/${encodeURIComponent(serverId)}`, {
|
|
6133
6908
|
method: "DELETE"
|
|
@@ -6136,7 +6911,7 @@ var forgetCmd = new Command18("forget").description("Stop managing a mail server
|
|
|
6136
6911
|
ok(` Forgot mail server ${serverId} (re-adopt with \`mail scan\` + \`mail adopt\`).`);
|
|
6137
6912
|
})
|
|
6138
6913
|
);
|
|
6139
|
-
var healthCmd = new
|
|
6914
|
+
var healthCmd = new Command19("health").description("Show live status of every mail daemon").argument("<serverId>", "Mail server ID").action(
|
|
6140
6915
|
guard2(async (serverId) => {
|
|
6141
6916
|
const sp = spin2("Checking mail daemons\u2026");
|
|
6142
6917
|
const res = await apiRequest(`/mail/health/${encodeURIComponent(serverId)}`);
|
|
@@ -6153,7 +6928,7 @@ var healthCmd = new Command18("health").description("Show live status of every m
|
|
|
6153
6928
|
);
|
|
6154
6929
|
})
|
|
6155
6930
|
);
|
|
6156
|
-
var logsCmd3 = new
|
|
6931
|
+
var logsCmd3 = new Command19("logs").description("Tail a mail component's journal (snapshot)").argument("<serverId>", "Mail server ID").argument("<component>", "Component key (postfix|dovecot|amavis|clamav|iredapd|postgresql|\u2026)").option("-n, --lines <n>", "Number of lines (max 1000)", "200").action(
|
|
6157
6932
|
guard2(async (serverId, component, opts) => {
|
|
6158
6933
|
const res = await apiRequest(
|
|
6159
6934
|
`/mail/admin/${encodeURIComponent(serverId)}/components/${encodeURIComponent(component)}/logs?lines=${encodeURIComponent(opts.lines)}`
|
|
@@ -6163,9 +6938,9 @@ var logsCmd3 = new Command18("logs").description("Tail a mail component's journa
|
|
|
6163
6938
|
for (const line of res.lines) process.stdout.write(line + "\n");
|
|
6164
6939
|
})
|
|
6165
6940
|
);
|
|
6166
|
-
var postmasterCmd = new
|
|
6941
|
+
var postmasterCmd = new Command19("postmaster").description("Manage the postmaster mailbox");
|
|
6167
6942
|
postmasterCmd.addCommand(
|
|
6168
|
-
new
|
|
6943
|
+
new Command19("set-password").description("Rotate the postmaster password").argument("<serverId>", "Mail server ID").option("--password <password>", "New password (min 12 chars); prompted if omitted").action(
|
|
6169
6944
|
guard2(async (serverId, opts) => {
|
|
6170
6945
|
let password2 = opts.password;
|
|
6171
6946
|
if (!password2) {
|
|
@@ -6191,12 +6966,12 @@ postmasterCmd.addCommand(
|
|
|
6191
6966
|
})
|
|
6192
6967
|
)
|
|
6193
6968
|
);
|
|
6194
|
-
var mailCommand = new
|
|
6969
|
+
var mailCommand = new Command19("mail").description("Self-hosted mail server (iRedMail) setup and admin [self-host]").addCommand(stepsCmd).addCommand(statusCmd).addCommand(serversCmd).addCommand(scanCmd).addCommand(adoptCmd).addCommand(setupCmd).addCommand(cancelCmd).addCommand(dnsAckCmd).addCommand(ptrAckCmd).addCommand(resetCmd).addCommand(forgetCmd).addCommand(healthCmd).addCommand(logsCmd3).addCommand(postmasterCmd);
|
|
6195
6970
|
|
|
6196
6971
|
// src/commands/backup.ts
|
|
6197
|
-
import { Command as
|
|
6972
|
+
import { Command as Command20 } from "commander";
|
|
6198
6973
|
import ora7 from "ora";
|
|
6199
|
-
import { readFileSync as
|
|
6974
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
6200
6975
|
async function guard3(fn) {
|
|
6201
6976
|
try {
|
|
6202
6977
|
await fn();
|
|
@@ -6279,7 +7054,7 @@ async function followStream(path2, label) {
|
|
|
6279
7054
|
spinner3?.stop();
|
|
6280
7055
|
return status;
|
|
6281
7056
|
}
|
|
6282
|
-
var policyCmd = new
|
|
7057
|
+
var policyCmd = new Command20("policy").description("Backup policies (schedules) for a project");
|
|
6283
7058
|
policyCmd.command("list").description("List backup policies for a project").requiredOption("--project <id>", "Project ID").action(
|
|
6284
7059
|
(opts) => guard3(async () => {
|
|
6285
7060
|
const { data } = await apiRequest(
|
|
@@ -6349,7 +7124,7 @@ policyCmd.command("run").description("Trigger a policy's backup now").argument("
|
|
|
6349
7124
|
}
|
|
6350
7125
|
})
|
|
6351
7126
|
);
|
|
6352
|
-
var runCmd = new
|
|
7127
|
+
var runCmd = new Command20("run").description("Backup runs (executions)");
|
|
6353
7128
|
runCmd.command("list").description("List backup runs for a project").requiredOption("--project <id>", "Project ID").option("--service <id>", "Filter to a single service").option("--limit <n>", "Max rows (default 50)").action(
|
|
6354
7129
|
(opts) => guard3(async () => {
|
|
6355
7130
|
const qs = new URLSearchParams();
|
|
@@ -6425,7 +7200,7 @@ runCmd.command("restore").description("Prepare a restore from a run (stages it;
|
|
|
6425
7200
|
}
|
|
6426
7201
|
})
|
|
6427
7202
|
);
|
|
6428
|
-
var restoreCmd = new
|
|
7203
|
+
var restoreCmd = new Command20("restore").description("Manage staged restores");
|
|
6429
7204
|
restoreCmd.command("apply").description("Apply a staged restore (destructive)").argument("<restoreId>", "Restore ID from `backup run restore`").requiredOption("--token <token>", "Confirmation token from prepare").option("--follow", "Stream the restore to completion").action(
|
|
6430
7205
|
(restoreId, opts) => guard3(async () => {
|
|
6431
7206
|
await apiRequest(
|
|
@@ -6468,7 +7243,7 @@ restoreCmd.command("get").description("Show one restore (optionally stream it)")
|
|
|
6468
7243
|
show(data);
|
|
6469
7244
|
})
|
|
6470
7245
|
);
|
|
6471
|
-
var destinationCmd = new
|
|
7246
|
+
var destinationCmd = new Command20("destination").description("Backup destinations (storage targets)");
|
|
6472
7247
|
destinationCmd.command("list").description("List backup destinations").action(
|
|
6473
7248
|
() => guard3(async () => {
|
|
6474
7249
|
const { data } = await apiRequest("/backup-destinations");
|
|
@@ -6489,7 +7264,7 @@ destinationCmd.command("create").description("Create a backup destination").requ
|
|
|
6489
7264
|
let sftpPrivateKey = opts.sftpPrivateKey;
|
|
6490
7265
|
if (opts.sftpPrivateKeyFile) {
|
|
6491
7266
|
try {
|
|
6492
|
-
sftpPrivateKey =
|
|
7267
|
+
sftpPrivateKey = readFileSync10(opts.sftpPrivateKeyFile, "utf8");
|
|
6493
7268
|
} catch {
|
|
6494
7269
|
throw new Error(`Cannot read key file: ${opts.sftpPrivateKeyFile}`);
|
|
6495
7270
|
}
|
|
@@ -6540,10 +7315,10 @@ destinationCmd.command("preflight").description("Verify a destination (write + r
|
|
|
6540
7315
|
}
|
|
6541
7316
|
})
|
|
6542
7317
|
);
|
|
6543
|
-
var backupCommand = new
|
|
7318
|
+
var backupCommand = new Command20("backup").description("Manage backups: policies, runs, restores, destinations").addCommand(policyCmd).addCommand(runCmd).addCommand(restoreCmd).addCommand(destinationCmd);
|
|
6544
7319
|
|
|
6545
7320
|
// src/commands/token.ts
|
|
6546
|
-
import { Command as
|
|
7321
|
+
import { Command as Command21 } from "commander";
|
|
6547
7322
|
import chalk15 from "chalk";
|
|
6548
7323
|
|
|
6549
7324
|
// src/lib/cmd-helpers.ts
|
|
@@ -6571,7 +7346,7 @@ function collectGrant(value, acc) {
|
|
|
6571
7346
|
acc.push({ resourceType, resourceId, permissions });
|
|
6572
7347
|
return acc;
|
|
6573
7348
|
}
|
|
6574
|
-
var listCmd5 = new
|
|
7349
|
+
var listCmd5 = new Command21("list").description("List your personal access tokens").action(async () => {
|
|
6575
7350
|
try {
|
|
6576
7351
|
const res = await apiRequest("/tokens");
|
|
6577
7352
|
const rows = res.data ?? [];
|
|
@@ -6596,7 +7371,7 @@ var listCmd5 = new Command20("list").description("List your personal access toke
|
|
|
6596
7371
|
fail3(e);
|
|
6597
7372
|
}
|
|
6598
7373
|
});
|
|
6599
|
-
var createCmd3 = new
|
|
7374
|
+
var createCmd3 = new Command21("create").description("Mint a new personal access token (the secret is shown once)").argument("<name>", "Human-readable token name").option("--read-only", "Reject mutation methods (POST/PUT/PATCH/DELETE)", false).option("--expires <days>", "Expire after N days (1\u2013365); omit for non-expiring", (v) => parseInt(v, 10)).option(
|
|
6600
7375
|
"--grant <type:id:perms>",
|
|
6601
7376
|
"Scope the token to a resource (repeatable), e.g. project:abc123:read,write",
|
|
6602
7377
|
collectGrant,
|
|
@@ -6628,7 +7403,7 @@ var createCmd3 = new Command20("create").description("Mint a new personal access
|
|
|
6628
7403
|
fail3(e);
|
|
6629
7404
|
}
|
|
6630
7405
|
});
|
|
6631
|
-
var revokeCmd = new
|
|
7406
|
+
var revokeCmd = new Command21("revoke").description("Revoke one of your tokens").argument("<id>", "Token ID").action(async (id) => {
|
|
6632
7407
|
const sp = spin3("Revoking token\u2026");
|
|
6633
7408
|
try {
|
|
6634
7409
|
await apiRequest(`/tokens/${encodeURIComponent(id)}`, { method: "DELETE" });
|
|
@@ -6640,11 +7415,11 @@ var revokeCmd = new Command20("revoke").description("Revoke one of your tokens")
|
|
|
6640
7415
|
fail3(e);
|
|
6641
7416
|
}
|
|
6642
7417
|
});
|
|
6643
|
-
var tokenCommand = new
|
|
7418
|
+
var tokenCommand = new Command21("token").description("Manage personal access tokens").addCommand(listCmd5).addCommand(createCmd3).addCommand(revokeCmd);
|
|
6644
7419
|
|
|
6645
7420
|
// src/commands/api.ts
|
|
6646
|
-
import { Command as
|
|
6647
|
-
var apiCommand = new
|
|
7421
|
+
import { Command as Command22 } from "commander";
|
|
7422
|
+
var apiCommand = new Command22("api").description("Make an authenticated request to any Openship API route (like `gh api`)").argument("<path>", "Path under /api, e.g. /projects or /deployments/<id>").option("-X, --method <method>", "HTTP method (defaults to GET, or POST when --data is given)").option("-d, --data <json>", "Request body as a JSON string").option("-q, --query <kv...>", "Query parameter key=value (repeatable)").action(async (path2, opts) => {
|
|
6648
7423
|
const method = (opts.method || (opts.data ? "POST" : "GET")).toUpperCase();
|
|
6649
7424
|
let url = path2.startsWith("/") ? path2 : `/${path2}`;
|
|
6650
7425
|
if (opts.query?.length) {
|
|
@@ -6678,20 +7453,20 @@ var apiCommand = new Command21("api").description("Make an authenticated request
|
|
|
6678
7453
|
});
|
|
6679
7454
|
|
|
6680
7455
|
// src/commands/reset-admin.ts
|
|
6681
|
-
import { Command as
|
|
7456
|
+
import { Command as Command23 } from "commander";
|
|
6682
7457
|
import chalk16 from "chalk";
|
|
6683
7458
|
import { intro, outro, password as passwordPrompt, isCancel, cancel as cancel2, log } from "@clack/prompts";
|
|
6684
|
-
import { readFileSync as
|
|
6685
|
-
import { homedir as
|
|
6686
|
-
import { join as
|
|
7459
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
7460
|
+
import { homedir as homedir8 } from "os";
|
|
7461
|
+
import { join as join13 } from "path";
|
|
6687
7462
|
function resolvedApiPort() {
|
|
6688
7463
|
try {
|
|
6689
|
-
return JSON.parse(
|
|
7464
|
+
return JSON.parse(readFileSync11(join13(homedir8(), ".openship", "ports.json"), "utf8")).api;
|
|
6690
7465
|
} catch {
|
|
6691
7466
|
return void 0;
|
|
6692
7467
|
}
|
|
6693
7468
|
}
|
|
6694
|
-
var resetAdminCommand = new
|
|
7469
|
+
var resetAdminCommand = new Command23("reset-admin-password").description("Reset the local admin login on THIS machine (no sign-in required)").option("--port <port>", "API port of the running service (default: the resolved port from ~/.openship/ports.json, else 4000)").option("--email <email>", "Also set the admin email").option("--name <name>", "Also set the admin display name").option("--password <password>", "New password (prompted if omitted)").action(async (opts) => {
|
|
6695
7470
|
intro(chalk16.cyan("Reset Openship admin password"));
|
|
6696
7471
|
let pw = opts.password;
|
|
6697
7472
|
if (!pw) {
|
|
@@ -6739,11 +7514,11 @@ var resetAdminCommand = new Command22("reset-admin-password").description("Reset
|
|
|
6739
7514
|
});
|
|
6740
7515
|
|
|
6741
7516
|
// src/commands/install.ts
|
|
6742
|
-
import { Command as
|
|
6743
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
6744
|
-
import { spawn as
|
|
6745
|
-
import { homedir as
|
|
6746
|
-
import { join as
|
|
7517
|
+
import { Command as Command24 } from "commander";
|
|
7518
|
+
import { chmodSync as chmodSync2, existsSync as existsSync12, mkdirSync as mkdirSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "fs";
|
|
7519
|
+
import { spawn as spawn3, spawnSync as spawnSync5 } from "child_process";
|
|
7520
|
+
import { homedir as homedir9 } from "os";
|
|
7521
|
+
import { join as join14 } from "path";
|
|
6747
7522
|
import ora9 from "ora";
|
|
6748
7523
|
function assetForPlatform() {
|
|
6749
7524
|
const { platform, arch } = process;
|
|
@@ -6751,37 +7526,39 @@ function assetForPlatform() {
|
|
|
6751
7526
|
return { name: arch === "arm64" ? "Openship-arm64.dmg" : "Openship-x64.dmg", kind: "dmg" };
|
|
6752
7527
|
}
|
|
6753
7528
|
if (platform === "win32") return { name: "Openship-win32-x64.zip", kind: "zip" };
|
|
6754
|
-
if (platform === "linux")
|
|
7529
|
+
if (platform === "linux") {
|
|
7530
|
+
return { name: arch === "arm64" ? "Openship-arm64.AppImage" : "Openship.AppImage", kind: "appimage" };
|
|
7531
|
+
}
|
|
6755
7532
|
throw new Error(`Unsupported platform: ${platform} (${arch})`);
|
|
6756
7533
|
}
|
|
6757
7534
|
function installDmg(dmg) {
|
|
6758
|
-
const homeApps =
|
|
7535
|
+
const homeApps = join14(homedir9(), "Applications");
|
|
6759
7536
|
let dest = homeApps;
|
|
6760
7537
|
try {
|
|
6761
|
-
|
|
7538
|
+
mkdirSync9(homeApps, { recursive: true });
|
|
6762
7539
|
} catch {
|
|
6763
7540
|
dest = "/Applications";
|
|
6764
7541
|
}
|
|
6765
|
-
const attach =
|
|
7542
|
+
const attach = spawnSync5("hdiutil", ["attach", "-nobrowse", "-readonly", "-noverify", dmg], {
|
|
6766
7543
|
encoding: "utf8"
|
|
6767
7544
|
});
|
|
6768
7545
|
if (attach.status !== 0) throw new Error(`hdiutil attach failed: ${attach.stderr?.trim()}`);
|
|
6769
7546
|
const mount = (attach.stdout.match(/\/Volumes\/[^\n]*/g) ?? []).pop()?.trim();
|
|
6770
7547
|
if (!mount) throw new Error("Could not determine the mounted volume");
|
|
6771
|
-
let target =
|
|
7548
|
+
let target = join14(dest, "Openship.app");
|
|
6772
7549
|
try {
|
|
6773
|
-
const appInDmg =
|
|
6774
|
-
if (!
|
|
6775
|
-
|
|
6776
|
-
let copy =
|
|
7550
|
+
const appInDmg = join14(mount, "Openship.app");
|
|
7551
|
+
if (!existsSync12(appInDmg)) throw new Error("Openship.app not found in the disk image");
|
|
7552
|
+
spawnSync5("rm", ["-rf", target]);
|
|
7553
|
+
let copy = spawnSync5("ditto", [appInDmg, target], { encoding: "utf8" });
|
|
6777
7554
|
if (copy.status !== 0 && dest === homeApps) {
|
|
6778
|
-
target =
|
|
6779
|
-
|
|
6780
|
-
copy =
|
|
7555
|
+
target = join14("/Applications", "Openship.app");
|
|
7556
|
+
spawnSync5("rm", ["-rf", target]);
|
|
7557
|
+
copy = spawnSync5("ditto", [appInDmg, target], { encoding: "utf8" });
|
|
6781
7558
|
}
|
|
6782
7559
|
if (copy.status !== 0) throw new Error(`ditto copy failed: ${copy.stderr?.trim()}`);
|
|
6783
7560
|
} finally {
|
|
6784
|
-
|
|
7561
|
+
spawnSync5("hdiutil", ["detach", mount, "-quiet"]);
|
|
6785
7562
|
}
|
|
6786
7563
|
return target;
|
|
6787
7564
|
}
|
|
@@ -6790,10 +7567,10 @@ function installAppImage(appImage) {
|
|
|
6790
7567
|
return appImage;
|
|
6791
7568
|
}
|
|
6792
7569
|
function installZip(zip) {
|
|
6793
|
-
const localAppData = process.env.LOCALAPPDATA ||
|
|
6794
|
-
const target =
|
|
6795
|
-
|
|
6796
|
-
const expand =
|
|
7570
|
+
const localAppData = process.env.LOCALAPPDATA || join14(homedir9(), "AppData", "Local");
|
|
7571
|
+
const target = join14(localAppData, "Programs", "Openship");
|
|
7572
|
+
mkdirSync9(target, { recursive: true });
|
|
7573
|
+
const expand = spawnSync5(
|
|
6797
7574
|
"powershell",
|
|
6798
7575
|
[
|
|
6799
7576
|
"-NoProfile",
|
|
@@ -6808,22 +7585,22 @@ function installZip(zip) {
|
|
|
6808
7585
|
}
|
|
6809
7586
|
function launch(kind, target) {
|
|
6810
7587
|
if (kind === "dmg") {
|
|
6811
|
-
|
|
7588
|
+
spawnSync5("open", [target]);
|
|
6812
7589
|
return;
|
|
6813
7590
|
}
|
|
6814
7591
|
if (kind === "appimage") {
|
|
6815
|
-
const child =
|
|
7592
|
+
const child = spawn3(target, [], { detached: true, stdio: "ignore" });
|
|
6816
7593
|
child.on("error", () => {
|
|
6817
|
-
|
|
7594
|
+
spawn3(target, ["--appimage-extract-and-run"], { detached: true, stdio: "ignore" }).unref();
|
|
6818
7595
|
});
|
|
6819
7596
|
child.unref();
|
|
6820
7597
|
return;
|
|
6821
7598
|
}
|
|
6822
|
-
const exe =
|
|
6823
|
-
const path2 =
|
|
6824
|
-
|
|
7599
|
+
const exe = join14(target, "Openship.exe");
|
|
7600
|
+
const path2 = existsSync12(exe) ? exe : target;
|
|
7601
|
+
spawnSync5("cmd", ["/c", "start", "", path2]);
|
|
6825
7602
|
}
|
|
6826
|
-
var installCommand = new
|
|
7603
|
+
var installCommand = new Command24("install").description("Download and install the Openship desktop app for this OS").option("--version <tag>", "Release tag to install (e.g. v1.2.3)").option("--latest", "Install the latest release (default)").option("--force", "Re-download even if a verified copy is cached").option("--no-verify", "Skip SHA-256 verification (allowed when no sidecar exists)").option("--no-launch", "Install without launching the app").action(async (opts) => {
|
|
6827
7604
|
let asset;
|
|
6828
7605
|
try {
|
|
6829
7606
|
asset = assetForPlatform();
|
|
@@ -6846,13 +7623,13 @@ var installCommand = new Command23("install").description("Download and install
|
|
|
6846
7623
|
process.exit(1);
|
|
6847
7624
|
}
|
|
6848
7625
|
const dir = releaseDir(tag);
|
|
6849
|
-
const assetPath =
|
|
7626
|
+
const assetPath = join14(dir, asset.name);
|
|
6850
7627
|
const sidecarPath = `${assetPath}.sha256`;
|
|
6851
7628
|
const assetUrl2 = `${RELEASES}/download/${tag}/${asset.name}`;
|
|
6852
7629
|
const sidecarUrl = `${assetUrl2}.sha256`;
|
|
6853
7630
|
let downloaded = false;
|
|
6854
7631
|
let sha;
|
|
6855
|
-
const cachedUsable = !opts.force &&
|
|
7632
|
+
const cachedUsable = !opts.force && existsSync12(assetPath) && (existsSync12(sidecarPath) || opts.verify === false);
|
|
6856
7633
|
if (cachedUsable) {
|
|
6857
7634
|
info(` Using cached ${asset.name} (${tag}).`);
|
|
6858
7635
|
} else {
|
|
@@ -6874,8 +7651,8 @@ var installCommand = new Command23("install").description("Download and install
|
|
|
6874
7651
|
const s2 = spin4("Verifying checksum\u2026");
|
|
6875
7652
|
try {
|
|
6876
7653
|
let sidecarBody;
|
|
6877
|
-
if (
|
|
6878
|
-
sidecarBody =
|
|
7654
|
+
if (existsSync12(sidecarPath) && !downloaded) {
|
|
7655
|
+
sidecarBody = readFileSync12(sidecarPath, "utf8");
|
|
6879
7656
|
} else {
|
|
6880
7657
|
sidecarBody = await fetchSidecar(sidecarUrl);
|
|
6881
7658
|
}
|
|
@@ -6898,8 +7675,8 @@ var installCommand = new Command23("install").description("Download and install
|
|
|
6898
7675
|
err(`Expected ${expected}, got ${actual}. The download may be corrupt or tampered with.`);
|
|
6899
7676
|
process.exit(1);
|
|
6900
7677
|
}
|
|
6901
|
-
|
|
6902
|
-
|
|
7678
|
+
mkdirSync9(dir, { recursive: true });
|
|
7679
|
+
writeFileSync9(sidecarPath, sidecarBody);
|
|
6903
7680
|
s2?.succeed("Checksum verified");
|
|
6904
7681
|
} catch (e) {
|
|
6905
7682
|
s2?.fail("Verification failed");
|
|
@@ -6938,15 +7715,15 @@ var installCommand = new Command23("install").description("Download and install
|
|
|
6938
7715
|
});
|
|
6939
7716
|
|
|
6940
7717
|
// src/commands/update.ts
|
|
6941
|
-
import { Command as
|
|
6942
|
-
import { spawnSync as
|
|
7718
|
+
import { Command as Command25 } from "commander";
|
|
7719
|
+
import { spawnSync as spawnSync6 } from "child_process";
|
|
6943
7720
|
function detectPackageManager2(override) {
|
|
6944
7721
|
if (override === "bun" || override === "npm") return override;
|
|
6945
|
-
const hasBun =
|
|
7722
|
+
const hasBun = spawnSync6("bun", ["--version"], { stdio: "ignore" }).status === 0;
|
|
6946
7723
|
return hasBun ? "bun" : "npm";
|
|
6947
7724
|
}
|
|
6948
|
-
var updateCommand = new
|
|
6949
|
-
const current = "0.
|
|
7725
|
+
var updateCommand = new Command25("update").description("Update the Openship CLI + bundled server to the latest release").option("--check", "Only report the current + latest version; don't install").option("--via <manager>", "Package manager to update with: bun | npm").action(async (opts) => {
|
|
7726
|
+
const current = "0.3.0";
|
|
6950
7727
|
let latest;
|
|
6951
7728
|
try {
|
|
6952
7729
|
latest = (await resolveLatestTag()).replace(/^v/, "");
|
|
@@ -6974,7 +7751,7 @@ var updateCommand = new Command24("update").description("Update the Openship CLI
|
|
|
6974
7751
|
const ref = `openship@${latest}`;
|
|
6975
7752
|
const argv = pm === "bun" ? ["add", "-g", ref] : ["install", "-g", ref];
|
|
6976
7753
|
info(`Updating v${current} \u2192 v${latest} (${cliInstallCommand(pm, latest)})...`);
|
|
6977
|
-
const res =
|
|
7754
|
+
const res = spawnSync6(pm, argv, { stdio: "inherit", shell: process.platform === "win32" });
|
|
6978
7755
|
if (res.status !== 0) {
|
|
6979
7756
|
err(`Update failed (${pm} exited ${res.status ?? "with a signal"}). Reinstall manually: ${cliInstallCommand(pm, latest)}`);
|
|
6980
7757
|
process.exitCode = 1;
|
|
@@ -6991,18 +7768,18 @@ var updateCommand = new Command24("update").description("Update the Openship CLI
|
|
|
6991
7768
|
});
|
|
6992
7769
|
|
|
6993
7770
|
// src/commands/cache.ts
|
|
6994
|
-
import { Command as
|
|
6995
|
-
import { existsSync as
|
|
6996
|
-
import { join as
|
|
7771
|
+
import { Command as Command26 } from "commander";
|
|
7772
|
+
import { existsSync as existsSync13, readdirSync, readFileSync as readFileSync13, rmSync as rmSync4, statSync } from "fs";
|
|
7773
|
+
import { join as join15 } from "path";
|
|
6997
7774
|
function listAssets() {
|
|
6998
|
-
if (!
|
|
7775
|
+
if (!existsSync13(RELEASES_DIR)) return [];
|
|
6999
7776
|
const out = [];
|
|
7000
7777
|
for (const tag of readdirSync(RELEASES_DIR)) {
|
|
7001
7778
|
const dir = releaseDir(tag);
|
|
7002
7779
|
if (!statSync(dir).isDirectory()) continue;
|
|
7003
7780
|
for (const name of readdirSync(dir)) {
|
|
7004
7781
|
if (name.endsWith(".sha256")) continue;
|
|
7005
|
-
const path2 =
|
|
7782
|
+
const path2 = join15(dir, name);
|
|
7006
7783
|
const st = statSync(path2);
|
|
7007
7784
|
if (!st.isFile()) continue;
|
|
7008
7785
|
out.push({
|
|
@@ -7010,17 +7787,17 @@ function listAssets() {
|
|
|
7010
7787
|
name,
|
|
7011
7788
|
path: path2,
|
|
7012
7789
|
size: st.size,
|
|
7013
|
-
hasSidecar:
|
|
7790
|
+
hasSidecar: existsSync13(`${path2}.sha256`)
|
|
7014
7791
|
});
|
|
7015
7792
|
}
|
|
7016
7793
|
}
|
|
7017
7794
|
return out;
|
|
7018
7795
|
}
|
|
7019
|
-
var pathCmd = new
|
|
7796
|
+
var pathCmd = new Command26("path").description("Print the cache directory path").action(() => {
|
|
7020
7797
|
if (isJsonMode()) printJson({ path: CACHE_DIR });
|
|
7021
7798
|
else process.stdout.write(CACHE_DIR + "\n");
|
|
7022
7799
|
});
|
|
7023
|
-
var listCmd6 = new
|
|
7800
|
+
var listCmd6 = new Command26("list").alias("ls").description("List cached release assets").action(() => {
|
|
7024
7801
|
const assets = listAssets();
|
|
7025
7802
|
printTable(
|
|
7026
7803
|
assets.map((a) => ({
|
|
@@ -7032,7 +7809,7 @@ var listCmd6 = new Command25("list").alias("ls").description("List cached releas
|
|
|
7032
7809
|
["tag", "asset", "size", "sidecar"]
|
|
7033
7810
|
);
|
|
7034
7811
|
});
|
|
7035
|
-
var verifyCmd2 = new
|
|
7812
|
+
var verifyCmd2 = new Command26("verify").description("Re-hash cached assets and compare to their .sha256 sidecar").argument("[tag]", "Only verify assets under this release tag").action(async (tag) => {
|
|
7036
7813
|
const assets = listAssets().filter((a) => !tag || a.tag === tag);
|
|
7037
7814
|
const results = [];
|
|
7038
7815
|
let bad = 0;
|
|
@@ -7041,7 +7818,7 @@ var verifyCmd2 = new Command25("verify").description("Re-hash cached assets and
|
|
|
7041
7818
|
results.push({ tag: a.tag, asset: a.name, result: "no-sidecar" });
|
|
7042
7819
|
continue;
|
|
7043
7820
|
}
|
|
7044
|
-
const expected = parseSha256(
|
|
7821
|
+
const expected = parseSha256(readFileSync13(`${a.path}.sha256`, "utf8"));
|
|
7045
7822
|
const actual = await hashFile(a.path);
|
|
7046
7823
|
const okMatch = expected !== null && expected === actual;
|
|
7047
7824
|
if (!okMatch) bad += 1;
|
|
@@ -7055,9 +7832,9 @@ var verifyCmd2 = new Command25("verify").description("Re-hash cached assets and
|
|
|
7055
7832
|
}
|
|
7056
7833
|
if (bad > 0) process.exit(1);
|
|
7057
7834
|
});
|
|
7058
|
-
var cleanCmd = new
|
|
7835
|
+
var cleanCmd = new Command26("clean").description("Delete cached release assets").argument("[tag]", "Only remove this release tag (default: all)").action((tag) => {
|
|
7059
7836
|
const target = tag ? releaseDir(tag) : RELEASES_DIR;
|
|
7060
|
-
if (!
|
|
7837
|
+
if (!existsSync13(target)) {
|
|
7061
7838
|
if (isJsonMode()) printJson({ removed: false, path: target });
|
|
7062
7839
|
else info(` Nothing to clean (${target}).`);
|
|
7063
7840
|
return;
|
|
@@ -7068,7 +7845,7 @@ var cleanCmd = new Command25("clean").description("Delete cached release assets"
|
|
|
7068
7845
|
Removed ${target}
|
|
7069
7846
|
`);
|
|
7070
7847
|
});
|
|
7071
|
-
var cacheCommand = new
|
|
7848
|
+
var cacheCommand = new Command26("cache").description("Manage the local download cache (list/verify/clean/path)").action(() => {
|
|
7072
7849
|
err("Specify a subcommand: path | list | verify | clean");
|
|
7073
7850
|
process.exit(1);
|
|
7074
7851
|
}).addCommand(pathCmd).addCommand(listCmd6).addCommand(verifyCmd2).addCommand(cleanCmd);
|
|
@@ -7076,11 +7853,10 @@ var cacheCommand = new Command25("cache").description("Manage the local download
|
|
|
7076
7853
|
// src/commands/wizard.ts
|
|
7077
7854
|
import chalk17 from "chalk";
|
|
7078
7855
|
import open from "open";
|
|
7079
|
-
import { createServer as createServer2 } from "http";
|
|
7080
7856
|
import { createHash as createHash2, randomBytes as randomBytes2 } from "crypto";
|
|
7081
|
-
import { existsSync as
|
|
7082
|
-
import { homedir as
|
|
7083
|
-
import { join as
|
|
7857
|
+
import { existsSync as existsSync14, readFileSync as readFileSync14 } from "fs";
|
|
7858
|
+
import { homedir as homedir10 } from "os";
|
|
7859
|
+
import { join as join16 } from "path";
|
|
7084
7860
|
import {
|
|
7085
7861
|
intro as intro2,
|
|
7086
7862
|
outro as outro2,
|
|
@@ -7135,10 +7911,10 @@ async function bootstrapAdmin(apiPort, admin) {
|
|
|
7135
7911
|
}
|
|
7136
7912
|
function lastServiceError() {
|
|
7137
7913
|
for (const name of ["up.err.log", "up.log"]) {
|
|
7138
|
-
const p =
|
|
7139
|
-
if (!
|
|
7914
|
+
const p = join16(homedir10(), ".openship", "logs", name);
|
|
7915
|
+
if (!existsSync14(p)) continue;
|
|
7140
7916
|
try {
|
|
7141
|
-
const lines =
|
|
7917
|
+
const lines = readFileSync14(p, "utf8").trim().split("\n");
|
|
7142
7918
|
const hit = [...lines].reverse().find((l) => /error|locked|EADDRINUSE|throw|cannot/i.test(l));
|
|
7143
7919
|
if (hit) return hit.trim().slice(0, 200);
|
|
7144
7920
|
} catch {
|
|
@@ -7198,58 +7974,46 @@ async function connectOpenshipCloud(port) {
|
|
|
7198
7974
|
}
|
|
7199
7975
|
const verifier = b64url(randomBytes2(32));
|
|
7200
7976
|
const challenge = b64url(createHash2("sha256").update(verifier).digest());
|
|
7201
|
-
const state = b64url(randomBytes2(
|
|
7202
|
-
const
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
return `<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Openship</title><style>:root{color-scheme:dark}*{box-sizing:border-box}body{margin:0;min-height:100vh;display:grid;place-items:center;background:#09090b;color:#e7e7ea;font:15px/1.55 -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}.card{width:min(92vw,420px);padding:40px 36px;text-align:center}.badge{margin:0 auto 20px;display:grid;place-items:center}svg{width:40px;height:40px}h1{margin:0 0 8px;font-size:19px;font-weight:600;letter-spacing:-.2px}p{margin:0;color:#9a9aa2;font-size:14px}.hint{margin-top:22px;font-size:12.5px;color:#6a6a72}</style></head><body><div class="card"><div class="badge"><svg viewBox="0 0 24 24" fill="none" stroke="#e7e7ea" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">${icon}</svg></div><h1>${title}</h1><p>${msg}</p><div class="hint">You can close this tab and return to your terminal.</div></div><script>setTimeout(function(){try{window.close()}catch(e){}},1200)</script></body></html>`;
|
|
7208
|
-
};
|
|
7209
|
-
const server2 = createServer2((req, res2) => {
|
|
7210
|
-
const u = new URL(req.url || "/", "http://127.0.0.1");
|
|
7211
|
-
if (!u.pathname.startsWith("/callback")) {
|
|
7212
|
-
res2.writeHead(404).end();
|
|
7213
|
-
return;
|
|
7214
|
-
}
|
|
7215
|
-
const code2 = u.searchParams.get("code");
|
|
7216
|
-
const gotState = u.searchParams.get("state");
|
|
7217
|
-
const ok3 = !!(code2 && gotState === state);
|
|
7218
|
-
res2.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }).end(page(ok3));
|
|
7219
|
-
server2.close();
|
|
7220
|
-
resolve2(ok3 ? code2 : null);
|
|
7221
|
-
});
|
|
7222
|
-
server2.on("error", () => resolve2(null));
|
|
7223
|
-
server2.listen(0, "127.0.0.1", () => {
|
|
7224
|
-
const cbPort = server2.address().port;
|
|
7225
|
-
const redirect = `http://127.0.0.1:${cbPort}/callback`;
|
|
7226
|
-
const handoff = `${cloudApiUrl.replace(/\/$/, "")}/api/cloud/connect-handoff?redirect=${encodeURIComponent(redirect)}&state=${state}&code_challenge=${challenge}`;
|
|
7227
|
-
note(handoff, "Open this URL to authorize (opening your browser\u2026)");
|
|
7228
|
-
void open(handoff).catch(() => {
|
|
7229
|
-
});
|
|
7230
|
-
});
|
|
7231
|
-
setTimeout(() => {
|
|
7232
|
-
try {
|
|
7233
|
-
server2.close();
|
|
7234
|
-
} catch {
|
|
7235
|
-
}
|
|
7236
|
-
resolve2(null);
|
|
7237
|
-
}, 3e5);
|
|
7977
|
+
const state = b64url(randomBytes2(24));
|
|
7978
|
+
const apiBase = cloudApiUrl.replace(/\/$/, "");
|
|
7979
|
+
const handoff = `${apiBase}/api/cloud/connect-handoff?redirect=${encodeURIComponent(apiBase)}&state=${encodeURIComponent(state)}&code_challenge=${challenge}&mode=device`;
|
|
7980
|
+
const overSsh = !!(process.env.SSH_CONNECTION || process.env.SSH_TTY || process.env.SSH_CLIENT);
|
|
7981
|
+
note(handoff, "Open this URL in your browser to authorize (then click Authorize)");
|
|
7982
|
+
if (!overSsh) void open(handoff).catch(() => {
|
|
7238
7983
|
});
|
|
7239
7984
|
const s = spinner2();
|
|
7240
|
-
s.start("Waiting for
|
|
7241
|
-
|
|
7985
|
+
s.start("Waiting for you to authorize in the browser");
|
|
7986
|
+
let code = null;
|
|
7987
|
+
const deadline = Date.now() + 3e5;
|
|
7988
|
+
while (Date.now() < deadline) {
|
|
7989
|
+
await new Promise((r) => setTimeout(r, 2500));
|
|
7990
|
+
try {
|
|
7991
|
+
const res2 = await fetch(
|
|
7992
|
+
`${apiBase}/api/cloud/connect-poll?state=${encodeURIComponent(state)}`,
|
|
7993
|
+
{ signal: AbortSignal.timeout(5e3) }
|
|
7994
|
+
);
|
|
7995
|
+
if (!res2.ok) continue;
|
|
7996
|
+
const data = await res2.json();
|
|
7997
|
+
if (data.status === "ready" && data.code) {
|
|
7998
|
+
code = data.code;
|
|
7999
|
+
break;
|
|
8000
|
+
}
|
|
8001
|
+
} catch {
|
|
8002
|
+
}
|
|
8003
|
+
}
|
|
7242
8004
|
if (!code) {
|
|
7243
|
-
s.stop("Openship Cloud wasn't authorized.", 1);
|
|
8005
|
+
s.stop("Openship Cloud wasn't authorized in time \u2014 re-run the connect step to try again.", 1);
|
|
7244
8006
|
return null;
|
|
7245
8007
|
}
|
|
7246
|
-
s.
|
|
8008
|
+
s.stop("Authorized.");
|
|
8009
|
+
const linking = spinner2();
|
|
8010
|
+
linking.start("Linking this instance to Openship Cloud");
|
|
7247
8011
|
const res = await internalPost(port, "/api/system/cloud-connect", { code, codeVerifier: verifier });
|
|
7248
8012
|
if (!res.ok) {
|
|
7249
|
-
|
|
8013
|
+
linking.stop(`Couldn't link Openship Cloud: ${res.data?.error || "failed"}`, 1);
|
|
7250
8014
|
return null;
|
|
7251
8015
|
}
|
|
7252
|
-
|
|
8016
|
+
linking.stop(`Connected to Openship Cloud${res.data?.email ? ` as ${res.data.email}` : ""}.`);
|
|
7253
8017
|
return { email: res.data?.email ?? null };
|
|
7254
8018
|
}
|
|
7255
8019
|
async function promptLocalAdmin() {
|
|
@@ -7269,12 +8033,13 @@ async function promptLocalAdmin() {
|
|
|
7269
8033
|
}
|
|
7270
8034
|
async function streamProvision(port, sessionId, s) {
|
|
7271
8035
|
let ok3 = false;
|
|
8036
|
+
let detail;
|
|
7272
8037
|
try {
|
|
7273
8038
|
const res = await fetch(`http://127.0.0.1:${port}/api/system/self-register/stream?id=${sessionId}`, {
|
|
7274
8039
|
headers: { "X-Internal-Token": ensureInternalToken() },
|
|
7275
8040
|
signal: AbortSignal.timeout(3e5)
|
|
7276
8041
|
});
|
|
7277
|
-
if (!res.ok || !res.body) return false;
|
|
8042
|
+
if (!res.ok || !res.body) return { ok: false };
|
|
7278
8043
|
const reader = res.body.getReader();
|
|
7279
8044
|
const decoder = new TextDecoder();
|
|
7280
8045
|
let buffer = "";
|
|
@@ -7292,23 +8057,29 @@ async function streamProvision(port, sessionId, s) {
|
|
|
7292
8057
|
if (event === "log" && dataRaw) {
|
|
7293
8058
|
try {
|
|
7294
8059
|
const d = JSON.parse(dataRaw);
|
|
7295
|
-
if (d.message)
|
|
8060
|
+
if (d.message) {
|
|
8061
|
+
const msg = String(d.message).replace(/\s+/g, " ");
|
|
8062
|
+
s.message(msg.slice(0, 68));
|
|
8063
|
+
if (d.level === "warn" || d.level === "error") detail = msg;
|
|
8064
|
+
}
|
|
7296
8065
|
} catch {
|
|
7297
8066
|
}
|
|
7298
8067
|
} else if (event === "complete" && dataRaw) {
|
|
7299
8068
|
try {
|
|
7300
|
-
|
|
8069
|
+
const d = JSON.parse(dataRaw);
|
|
8070
|
+
ok3 = d.status === "completed";
|
|
8071
|
+
if (!ok3 && typeof d.error === "string") detail = d.error;
|
|
7301
8072
|
} catch {
|
|
7302
8073
|
}
|
|
7303
8074
|
} else if (event === "end") {
|
|
7304
|
-
return ok3;
|
|
8075
|
+
return { ok: ok3, detail };
|
|
7305
8076
|
}
|
|
7306
8077
|
}
|
|
7307
8078
|
}
|
|
7308
8079
|
} catch {
|
|
7309
|
-
return ok3;
|
|
8080
|
+
return { ok: ok3, detail };
|
|
7310
8081
|
}
|
|
7311
|
-
return ok3;
|
|
8082
|
+
return { ok: ok3, detail };
|
|
7312
8083
|
}
|
|
7313
8084
|
async function runWizard() {
|
|
7314
8085
|
intro2(`${chalk17.bgCyan(chalk17.black(" Openship "))}${chalk17.dim(" setup")}`);
|
|
@@ -7513,7 +8284,7 @@ async function runWizard() {
|
|
|
7513
8284
|
domainPlan = { type: "byo", hostname };
|
|
7514
8285
|
break planning;
|
|
7515
8286
|
}
|
|
7516
|
-
const uiTag = `v${"0.
|
|
8287
|
+
const uiTag = `v${"0.3.0"}`;
|
|
7517
8288
|
const dl = spinner2();
|
|
7518
8289
|
dl.start("Pulling the Openship dist from GitHub");
|
|
7519
8290
|
try {
|
|
@@ -7632,6 +8403,20 @@ async function runWizard() {
|
|
|
7632
8403
|
if (status && !status.canProceedClean && status.occupants?.length) {
|
|
7633
8404
|
const owner = status.occupants.map((o) => o.command ?? `port ${o.port}`).join(", ");
|
|
7634
8405
|
const known = status.classification === "known";
|
|
8406
|
+
const sites = pf.ok && Array.isArray(pf.data?.sites) ? pf.data.sites : [];
|
|
8407
|
+
if (sites.length > 0) {
|
|
8408
|
+
const lines = sites.map((st) => {
|
|
8409
|
+
const host = (st.serverNames ?? []).join(", ") || "(no server_name)";
|
|
8410
|
+
const dest = st.target?.kind === "static" ? `static: ${st.target?.root ?? ""}` : st.target?.url ?? "";
|
|
8411
|
+
return `${chalk17.bold(host)} \u2192 ${chalk17.dim(dest)}${st.ssl ? chalk17.green(" [TLS]") : ""}`;
|
|
8412
|
+
});
|
|
8413
|
+
note(lines.join("\n"), `Detected ${sites.length} site${sites.length === 1 ? "" : "s"} on ${owner}`);
|
|
8414
|
+
}
|
|
8415
|
+
const warns = pf.ok && Array.isArray(pf.data?.warnings) ? pf.data.warnings : [];
|
|
8416
|
+
if (warns.length > 0) {
|
|
8417
|
+
log2.warn(`${warns.length} config item${warns.length === 1 ? "" : "s"} won't migrate automatically:`);
|
|
8418
|
+
for (const w of warns.slice(0, 8)) log2.message(chalk17.dim(`\u2022 ${w}`));
|
|
8419
|
+
}
|
|
7635
8420
|
const choice = ensure(
|
|
7636
8421
|
await select({
|
|
7637
8422
|
message: known ? `An existing reverse proxy (${owner}) is serving ports 80/443.` : `Ports 80/443 are in use by ${owner}, which we couldn't identify.`,
|
|
@@ -7678,10 +8463,13 @@ async function runWizard() {
|
|
|
7678
8463
|
if (res.ok && res.data?.sessionId) {
|
|
7679
8464
|
const s2 = spinner2();
|
|
7680
8465
|
s2.start("Issuing HTTPS certificate (OpenResty + Let's Encrypt)");
|
|
7681
|
-
const done = await streamProvision(port, res.data.sessionId, s2);
|
|
8466
|
+
const { ok: done, detail } = await streamProvision(port, res.data.sessionId, s2);
|
|
7682
8467
|
liveUrl = res.data.url ?? liveUrl;
|
|
7683
8468
|
if (done) s2.stop(`HTTPS ready: ${liveUrl}`);
|
|
7684
|
-
else
|
|
8469
|
+
else {
|
|
8470
|
+
s2.stop("HTTPS isn't ready yet \u2014 it retries on reboot; the site serves over HTTP meanwhile.", 1);
|
|
8471
|
+
if (detail) log2.warn(detail);
|
|
8472
|
+
}
|
|
7685
8473
|
} else {
|
|
7686
8474
|
log2.warn(`Couldn't start domain provisioning: ${res.data?.error || "failed"}`);
|
|
7687
8475
|
}
|
|
@@ -7714,9 +8502,9 @@ ${pad("Login")}${admin.email} ${chalk17.dim("(email + password you set)")}
|
|
|
7714
8502
|
);
|
|
7715
8503
|
}
|
|
7716
8504
|
function storedPorts() {
|
|
7717
|
-
const p =
|
|
8505
|
+
const p = join16(homedir10(), ".openship", "ports.json");
|
|
7718
8506
|
try {
|
|
7719
|
-
return
|
|
8507
|
+
return existsSync14(p) ? JSON.parse(readFileSync14(p, "utf8")) : {};
|
|
7720
8508
|
} catch {
|
|
7721
8509
|
return {};
|
|
7722
8510
|
}
|
|
@@ -7788,8 +8576,8 @@ ${chalk17.dim("Dashboard".padEnd(11))}${dashUrl}
|
|
|
7788
8576
|
}
|
|
7789
8577
|
|
|
7790
8578
|
// src/index.ts
|
|
7791
|
-
var program = new
|
|
7792
|
-
program.name("openship").description("Openship CLI \u2014 install, run, and manage Openship from your terminal").version("0.
|
|
8579
|
+
var program = new Command27();
|
|
8580
|
+
program.name("openship").description("Openship CLI \u2014 install, run, and manage Openship from your terminal").version("0.3.0").option("--json", "Machine-readable JSON output (stdout data only)").hook("preAction", (thisCommand) => {
|
|
7793
8581
|
if (thisCommand.opts().json) setJsonMode(true);
|
|
7794
8582
|
}).action(async () => {
|
|
7795
8583
|
if (serviceStatus().installed) await runControl();
|
|
@@ -7803,6 +8591,7 @@ program.addCommand(openCommand);
|
|
|
7803
8591
|
program.addCommand(loginCommand);
|
|
7804
8592
|
program.addCommand(logoutCommand);
|
|
7805
8593
|
program.addCommand(initCommand);
|
|
8594
|
+
program.addCommand(configCommand);
|
|
7806
8595
|
program.addCommand(contextCommand);
|
|
7807
8596
|
program.addCommand(statusCommand);
|
|
7808
8597
|
program.addCommand(doctorCommand);
|