openship 0.1.7 → 0.1.9
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/dist/index.js +282 -80
- package/dist/server/index.js +2340 -1818
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -102,7 +102,10 @@ var LANGUAGES = {
|
|
|
102
102
|
},
|
|
103
103
|
java: {
|
|
104
104
|
name: "Java",
|
|
105
|
-
|
|
105
|
+
// Maven image bundles both `mvn` and JDK 21, so the generated Dockerfile
|
|
106
|
+
// builds Maven projects out of the box; Gradle/Kotlin projects build via
|
|
107
|
+
// their `./gradlew` wrapper (needs only the JDK, which this image has).
|
|
108
|
+
buildImage: "maven:3.9-eclipse-temurin-21",
|
|
106
109
|
runtimeImage: "eclipse-temurin:21-jre-alpine",
|
|
107
110
|
packageManagers: ["maven", "gradle"],
|
|
108
111
|
requiredTools: ["java", "javac"]
|
|
@@ -573,15 +576,18 @@ var STACKS = {
|
|
|
573
576
|
}
|
|
574
577
|
},
|
|
575
578
|
// ── PHP ────────────────────────────────────────────────────────────────────
|
|
579
|
+
// PHP stacks run php-fpm behind nginx (docroot public/). The generated
|
|
580
|
+
// Dockerfile (docker-build-plan.ts PHP branch) installs nginx + writes the
|
|
581
|
+
// config template; this start command renders it for the injected $PORT and
|
|
582
|
+
// launches both processes. Runtime image inherits php:8.3-fpm from the language.
|
|
576
583
|
laravel: {
|
|
577
584
|
name: "Laravel",
|
|
578
585
|
language: "php",
|
|
579
586
|
category: "fullstack",
|
|
580
|
-
runtimeImage: "php:8.3-apache",
|
|
581
587
|
outputDirectory: "public",
|
|
582
588
|
defaultPort: 8e3,
|
|
583
589
|
defaultBuildCommand: "composer install --no-dev --optimize-autoloader",
|
|
584
|
-
defaultStartCommand: "
|
|
590
|
+
defaultStartCommand: "envsubst '$PORT' < /etc/nginx/app.conf.template > /etc/nginx/conf.d/default.conf && php-fpm -D && nginx -g 'daemon off;'",
|
|
585
591
|
detection: {
|
|
586
592
|
rootMarkers: ["artisan", "composer.json"],
|
|
587
593
|
deps: ["laravel/framework"]
|
|
@@ -591,11 +597,10 @@ var STACKS = {
|
|
|
591
597
|
name: "Symfony",
|
|
592
598
|
language: "php",
|
|
593
599
|
category: "fullstack",
|
|
594
|
-
runtimeImage: "php:8.3-apache",
|
|
595
600
|
outputDirectory: "public",
|
|
596
601
|
defaultPort: 8e3,
|
|
597
602
|
defaultBuildCommand: "composer install --no-dev --optimize-autoloader",
|
|
598
|
-
defaultStartCommand: "
|
|
603
|
+
defaultStartCommand: "envsubst '$PORT' < /etc/nginx/app.conf.template > /etc/nginx/conf.d/default.conf && php-fpm -D && nginx -g 'daemon off;'",
|
|
599
604
|
detection: {
|
|
600
605
|
rootMarkers: ["composer.json", "symfony.lock"],
|
|
601
606
|
deps: ["symfony/framework-bundle"]
|
|
@@ -612,6 +617,9 @@ var STACKS = {
|
|
|
612
617
|
defaultStartCommand: "java -jar target/*.jar",
|
|
613
618
|
productionPaths: ["target"],
|
|
614
619
|
defaultBuildStrategy: "local",
|
|
620
|
+
// Predominantly a Maven stack; bare-metal builds need `mvn` ensured. Gradle
|
|
621
|
+
// Spring Boot projects still build via their `./gradlew` wrapper (JDK-only).
|
|
622
|
+
requiredTools: ["java", "javac", "maven"],
|
|
615
623
|
detection: {
|
|
616
624
|
rootMarkers: ["pom.xml", "build.gradle", "build.gradle.kts"],
|
|
617
625
|
deps: ["org.springframework.boot:spring-boot-starter-web", "spring-boot"],
|
|
@@ -632,6 +640,7 @@ var STACKS = {
|
|
|
632
640
|
defaultStartCommand: "java -jar target/quarkus-app/quarkus-run.jar",
|
|
633
641
|
productionPaths: ["target"],
|
|
634
642
|
defaultBuildStrategy: "local",
|
|
643
|
+
requiredTools: ["java", "javac", "maven"],
|
|
635
644
|
detection: {
|
|
636
645
|
rootMarkers: ["pom.xml", "build.gradle", "build.gradle.kts"],
|
|
637
646
|
deps: ["io.quarkus:quarkus-core", "quarkus"],
|
|
@@ -642,15 +651,44 @@ var STACKS = {
|
|
|
642
651
|
}
|
|
643
652
|
}
|
|
644
653
|
},
|
|
654
|
+
// ── Kotlin (JVM, Gradle) ─────────────────────────────────────────────────
|
|
655
|
+
// Plain Kotlin/JVM services (Ktor, http4k, or a bare `main`). A Kotlin *Spring
|
|
656
|
+
// Boot* project still matches `springboot` first (its content pattern wins in
|
|
657
|
+
// the rule order), so this catches Kotlin projects that aren't Spring/Quarkus.
|
|
658
|
+
kotlin: {
|
|
659
|
+
name: "Kotlin",
|
|
660
|
+
language: "java",
|
|
661
|
+
category: "backend",
|
|
662
|
+
outputDirectory: "build/libs",
|
|
663
|
+
defaultPort: 8080,
|
|
664
|
+
defaultBuildCommand: "gradle build -x test",
|
|
665
|
+
defaultStartCommand: "java -jar build/libs/*.jar",
|
|
666
|
+
productionPaths: ["build/libs"],
|
|
667
|
+
defaultBuildStrategy: "local",
|
|
668
|
+
// Gradle-based; bare-metal builds need `gradle` ensured (or the `./gradlew`
|
|
669
|
+
// wrapper, which the detector prefers when present).
|
|
670
|
+
requiredTools: ["java", "javac", "gradle"],
|
|
671
|
+
detection: {
|
|
672
|
+
rootMarkers: ["build.gradle.kts", "build.gradle"],
|
|
673
|
+
contentPatterns: {
|
|
674
|
+
"build.gradle.kts": "kotlin\\s*\\(|org\\.jetbrains\\.kotlin",
|
|
675
|
+
"build.gradle": "org\\.jetbrains\\.kotlin|kotlin[- ]"
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
},
|
|
645
679
|
// ── C# / .NET ──────────────────────────────────────────────────────────────
|
|
646
680
|
dotnet: {
|
|
647
681
|
name: ".NET",
|
|
648
682
|
language: "csharp",
|
|
649
683
|
category: "backend",
|
|
650
|
-
|
|
684
|
+
// Build runs `dotnet publish -c Release -o publish`, so the artifact is ./publish.
|
|
685
|
+
outputDirectory: "publish",
|
|
651
686
|
defaultPort: 5e3,
|
|
652
687
|
defaultBuildCommand: "dotnet publish -c Release -o publish",
|
|
653
|
-
|
|
688
|
+
// .NET reads ASPNETCORE_URLS (not $PORT) and defaults to :8080, so bind it
|
|
689
|
+
// explicitly to the injected port. The detector rewrites `app.dll` to the
|
|
690
|
+
// real assembly name (from the .csproj); `app` is the fallback.
|
|
691
|
+
defaultStartCommand: "ASPNETCORE_URLS=http://0.0.0.0:$PORT dotnet publish/app.dll",
|
|
654
692
|
productionPaths: ["publish"],
|
|
655
693
|
detection: {
|
|
656
694
|
// .csproj/.fsproj/.sln are detected by suffix; rootMarkers is decorative
|
|
@@ -660,12 +698,14 @@ var STACKS = {
|
|
|
660
698
|
blazor: {
|
|
661
699
|
name: "Blazor",
|
|
662
700
|
language: "csharp",
|
|
663
|
-
|
|
664
|
-
|
|
701
|
+
// Blazor WebAssembly compiles to a static bundle under publish/wwwroot —
|
|
702
|
+
// it's served as files, not a running server (Blazor Server folds into `dotnet`).
|
|
703
|
+
category: "static",
|
|
704
|
+
outputDirectory: "publish/wwwroot",
|
|
665
705
|
defaultPort: 5e3,
|
|
666
706
|
defaultBuildCommand: "dotnet publish -c Release -o publish",
|
|
667
|
-
defaultStartCommand: "
|
|
668
|
-
productionPaths: ["publish"],
|
|
707
|
+
defaultStartCommand: "",
|
|
708
|
+
productionPaths: ["publish/wwwroot"],
|
|
669
709
|
detection: {
|
|
670
710
|
deps: ["Microsoft.AspNetCore.Components.WebAssembly"]
|
|
671
711
|
}
|
|
@@ -1541,11 +1581,39 @@ var goLanguageDetector = {
|
|
|
1541
1581
|
function parseJavaManifest(_filename, _content) {
|
|
1542
1582
|
return {};
|
|
1543
1583
|
}
|
|
1584
|
+
function clampPort(raw) {
|
|
1585
|
+
const port = parseInt(raw, 10);
|
|
1586
|
+
return port > 0 && port <= 65535 ? port : null;
|
|
1587
|
+
}
|
|
1588
|
+
function detectJavaPort(context) {
|
|
1589
|
+
const fc = context.fileContents;
|
|
1590
|
+
if (!fc) return null;
|
|
1591
|
+
const props = fc["application.properties"];
|
|
1592
|
+
if (props) {
|
|
1593
|
+
const m = props.match(/^\s*server\.port\s*=\s*(\d{2,5})\s*$/m);
|
|
1594
|
+
if (m) return clampPort(m[1]);
|
|
1595
|
+
}
|
|
1596
|
+
for (const name of ["application.yml", "application.yaml"]) {
|
|
1597
|
+
const yml = fc[name];
|
|
1598
|
+
if (!yml) continue;
|
|
1599
|
+
const m = yml.match(/server:[\s\S]*?\bport:\s*["']?(\d{2,5})/) ?? yml.match(/^\s*port:\s*["']?(\d{2,5})/m);
|
|
1600
|
+
if (m) return clampPort(m[1]);
|
|
1601
|
+
}
|
|
1602
|
+
return null;
|
|
1603
|
+
}
|
|
1544
1604
|
var javaLanguageDetector = {
|
|
1545
1605
|
id: "java",
|
|
1546
|
-
label: "Java",
|
|
1547
|
-
manifestFiles: [
|
|
1548
|
-
|
|
1606
|
+
label: "Java / Kotlin",
|
|
1607
|
+
manifestFiles: [
|
|
1608
|
+
"pom.xml",
|
|
1609
|
+
"build.gradle",
|
|
1610
|
+
"build.gradle.kts",
|
|
1611
|
+
"application.properties",
|
|
1612
|
+
"application.yml",
|
|
1613
|
+
"application.yaml"
|
|
1614
|
+
],
|
|
1615
|
+
parseManifest: parseJavaManifest,
|
|
1616
|
+
detectPort: detectJavaPort
|
|
1549
1617
|
};
|
|
1550
1618
|
|
|
1551
1619
|
// ../../packages/core/src/languages/javascript.ts
|
|
@@ -2397,7 +2465,7 @@ var doctorCommand = new Command8("doctor").description("Diagnose the CLI setup (
|
|
|
2397
2465
|
|
|
2398
2466
|
// src/commands/deploy.ts
|
|
2399
2467
|
import { Command as Command9 } from "commander";
|
|
2400
|
-
import { execFileSync as
|
|
2468
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
2401
2469
|
import ora2 from "ora";
|
|
2402
2470
|
|
|
2403
2471
|
// src/lib/project-link.ts
|
|
@@ -2424,6 +2492,113 @@ function readProjectLink(from) {
|
|
|
2424
2492
|
}
|
|
2425
2493
|
}
|
|
2426
2494
|
|
|
2495
|
+
// src/lib/folder-deploy.ts
|
|
2496
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
2497
|
+
import { readFileSync as readFileSync4, existsSync as existsSync6, rmSync } from "fs";
|
|
2498
|
+
import { tmpdir } from "os";
|
|
2499
|
+
import { join as join5, basename } from "path";
|
|
2500
|
+
function detectPackageManager(dir) {
|
|
2501
|
+
if (existsSync6(join5(dir, "bun.lockb")) || existsSync6(join5(dir, "bun.lock"))) return "bun";
|
|
2502
|
+
if (existsSync6(join5(dir, "pnpm-lock.yaml"))) return "pnpm";
|
|
2503
|
+
if (existsSync6(join5(dir, "yarn.lock"))) return "yarn";
|
|
2504
|
+
if (existsSync6(join5(dir, "package.json"))) return "npm";
|
|
2505
|
+
return void 0;
|
|
2506
|
+
}
|
|
2507
|
+
function detectStack(dir) {
|
|
2508
|
+
if (existsSync6(join5(dir, "go.mod"))) return "go";
|
|
2509
|
+
if (existsSync6(join5(dir, "Cargo.toml"))) return "rust";
|
|
2510
|
+
if (existsSync6(join5(dir, "requirements.txt")) || existsSync6(join5(dir, "pyproject.toml"))) return "python";
|
|
2511
|
+
if (existsSync6(join5(dir, "package.json"))) return "node";
|
|
2512
|
+
return void 0;
|
|
2513
|
+
}
|
|
2514
|
+
async function deployFolder(opts) {
|
|
2515
|
+
const { cwd } = opts;
|
|
2516
|
+
const step = opts.onStep ?? (() => {
|
|
2517
|
+
});
|
|
2518
|
+
const name = opts.name || basename(cwd) || "app";
|
|
2519
|
+
step("Creating upload session");
|
|
2520
|
+
const session = await apiRequest("/projects/folder/session", {
|
|
2521
|
+
method: "POST",
|
|
2522
|
+
body: JSON.stringify({ name, packageManager: detectPackageManager(cwd), stack: detectStack(cwd) })
|
|
2523
|
+
});
|
|
2524
|
+
if (!session.sessionId || !session.upload) {
|
|
2525
|
+
throw new Error(session.error || "Failed to open upload session");
|
|
2526
|
+
}
|
|
2527
|
+
step("Packaging folder");
|
|
2528
|
+
const tarball = join5(tmpdir(), `openship-upload-${session.sessionId}.tar.gz`);
|
|
2529
|
+
execFileSync2(
|
|
2530
|
+
"tar",
|
|
2531
|
+
[
|
|
2532
|
+
"-czf",
|
|
2533
|
+
tarball,
|
|
2534
|
+
"--exclude=./node_modules",
|
|
2535
|
+
"--exclude=./.git",
|
|
2536
|
+
"--exclude=./dist",
|
|
2537
|
+
"--exclude=./.next",
|
|
2538
|
+
"--exclude=./.DS_Store",
|
|
2539
|
+
"-C",
|
|
2540
|
+
cwd,
|
|
2541
|
+
"."
|
|
2542
|
+
],
|
|
2543
|
+
{ stdio: "ignore" }
|
|
2544
|
+
);
|
|
2545
|
+
step("Uploading source");
|
|
2546
|
+
try {
|
|
2547
|
+
const body = readFileSync4(tarball);
|
|
2548
|
+
const up = session.upload;
|
|
2549
|
+
const method = up.method || "POST";
|
|
2550
|
+
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 });
|
|
2551
|
+
if (!res.ok) throw new Error(`upload failed (HTTP ${res.status})`);
|
|
2552
|
+
} finally {
|
|
2553
|
+
try {
|
|
2554
|
+
rmSync(tarball, { force: true });
|
|
2555
|
+
} catch {
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
step("Detecting build config");
|
|
2559
|
+
const scan = await apiRequest(`/projects/folder/scan/${session.sessionId}`, {
|
|
2560
|
+
method: "POST",
|
|
2561
|
+
body: "{}"
|
|
2562
|
+
});
|
|
2563
|
+
if (scan.success === false) throw new Error(scan.error || "Failed to scan uploaded source");
|
|
2564
|
+
step("Creating project");
|
|
2565
|
+
const hasBuild = Boolean(scan.buildCommand);
|
|
2566
|
+
const hasServer = Boolean(scan.startCommand);
|
|
2567
|
+
const ensured = await apiRequest("/projects/ensure", {
|
|
2568
|
+
method: "POST",
|
|
2569
|
+
body: JSON.stringify({
|
|
2570
|
+
projectId: opts.projectId,
|
|
2571
|
+
name: scan.name || name,
|
|
2572
|
+
gitProvider: "upload",
|
|
2573
|
+
framework: scan.stack,
|
|
2574
|
+
projectType: scan.projectType,
|
|
2575
|
+
packageManager: scan.packageManager,
|
|
2576
|
+
installCommand: scan.installCommand,
|
|
2577
|
+
buildCommand: scan.buildCommand,
|
|
2578
|
+
startCommand: scan.startCommand || void 0,
|
|
2579
|
+
outputDirectory: scan.outputDirectory,
|
|
2580
|
+
rootDirectory: scan.rootDirectory,
|
|
2581
|
+
buildImage: scan.buildImage,
|
|
2582
|
+
hasBuild,
|
|
2583
|
+
hasServer,
|
|
2584
|
+
productionMode: hasServer ? "standalone" : "static",
|
|
2585
|
+
...hasServer && scan.port ? { port: scan.port } : {}
|
|
2586
|
+
})
|
|
2587
|
+
});
|
|
2588
|
+
if (!ensured.project_id) throw new Error(ensured.error || "Failed to create project");
|
|
2589
|
+
step("Deploying");
|
|
2590
|
+
const dep = await apiRequest("/deployments/build/access", {
|
|
2591
|
+
method: "POST",
|
|
2592
|
+
body: JSON.stringify({
|
|
2593
|
+
projectId: ensured.project_id,
|
|
2594
|
+
uploadSessionId: session.sessionId,
|
|
2595
|
+
...opts.environment ? { environment: opts.environment } : {}
|
|
2596
|
+
})
|
|
2597
|
+
});
|
|
2598
|
+
if (!dep.deployment_id) throw new Error(dep.error || "Failed to start deployment");
|
|
2599
|
+
return { deploymentId: dep.deployment_id, projectId: dep.project_id };
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2427
2602
|
// src/lib/sse.ts
|
|
2428
2603
|
async function* parseSSE(stream) {
|
|
2429
2604
|
const reader = stream.getReader();
|
|
@@ -2545,51 +2720,78 @@ ${payload.error ?? "Stream error"}`);
|
|
|
2545
2720
|
// src/commands/deploy.ts
|
|
2546
2721
|
function git(args) {
|
|
2547
2722
|
try {
|
|
2548
|
-
return
|
|
2723
|
+
return execFileSync3("git", args, { stdio: ["ignore", "pipe", "ignore"] }).toString().trim() || void 0;
|
|
2549
2724
|
} catch {
|
|
2550
2725
|
return void 0;
|
|
2551
2726
|
}
|
|
2552
2727
|
}
|
|
2553
|
-
var deployCommand = new Command9("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("--watch", "Stream the deployment logs until it finishes").action(async (opts) => {
|
|
2728
|
+
var deployCommand = new Command9("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) => {
|
|
2554
2729
|
const link = readProjectLink();
|
|
2555
|
-
const projectId = opts.project || link?.projectId;
|
|
2556
|
-
if (!projectId) {
|
|
2557
|
-
err("No project specified. Pass --project <id> or run `openship init` to link one.");
|
|
2558
|
-
process.exit(1);
|
|
2559
|
-
}
|
|
2560
2730
|
const env = opts.env;
|
|
2561
2731
|
if (env !== "production" && env !== "preview") {
|
|
2562
2732
|
err(`Invalid --env "${env}". Must be "production" or "preview".`);
|
|
2563
2733
|
process.exit(1);
|
|
2564
2734
|
}
|
|
2565
|
-
const
|
|
2566
|
-
const
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2735
|
+
const inGitRepo = git(["rev-parse", "--is-inside-work-tree"]) === "true";
|
|
2736
|
+
const gitOnlyFlags = opts.commit || opts.serviceIds || opts.smartRoute || opts.refresh;
|
|
2737
|
+
let deploymentId;
|
|
2738
|
+
let payload;
|
|
2739
|
+
if (!inGitRepo && !gitOnlyFlags) {
|
|
2740
|
+
const spinner2 = isJsonMode() ? null : ora2("Deploying folder").start();
|
|
2741
|
+
try {
|
|
2742
|
+
const result2 = await deployFolder({
|
|
2743
|
+
cwd: process.cwd(),
|
|
2744
|
+
name: opts.name,
|
|
2745
|
+
projectId: opts.project || link?.projectId,
|
|
2746
|
+
environment: env,
|
|
2747
|
+
onStep: (m) => {
|
|
2748
|
+
if (spinner2) spinner2.text = m;
|
|
2749
|
+
}
|
|
2750
|
+
});
|
|
2751
|
+
deploymentId = result2.deploymentId;
|
|
2752
|
+
payload = { success: true, deployment_id: result2.deploymentId, project_id: result2.projectId };
|
|
2753
|
+
spinner2?.succeed(deploymentId ? `Deployment queued: ${deploymentId}` : "Deployment queued");
|
|
2754
|
+
} catch (e) {
|
|
2755
|
+
spinner2?.fail("Folder deploy failed");
|
|
2756
|
+
err(e instanceof ApiError ? e.message : String(e));
|
|
2757
|
+
process.exit(1);
|
|
2758
|
+
}
|
|
2759
|
+
} else {
|
|
2760
|
+
const projectId = opts.project || link?.projectId;
|
|
2761
|
+
if (!projectId) {
|
|
2762
|
+
err("No project specified. Pass --project <id> or run `openship init` to link one.");
|
|
2763
|
+
process.exit(1);
|
|
2764
|
+
}
|
|
2765
|
+
const branch = opts.branch || link?.branch || git(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
2766
|
+
const serviceIds = opts.serviceIds ? opts.serviceIds.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
2767
|
+
const body = {
|
|
2768
|
+
projectId,
|
|
2769
|
+
branch,
|
|
2770
|
+
commitSha: opts.commit || void 0,
|
|
2771
|
+
environment: env,
|
|
2772
|
+
forceAll: opts.forceAll || void 0,
|
|
2773
|
+
serviceIds,
|
|
2774
|
+
smartRoute: opts.smartRoute || void 0,
|
|
2775
|
+
refresh: opts.refresh || void 0
|
|
2776
|
+
};
|
|
2777
|
+
const spinner2 = isJsonMode() ? null : ora2("Triggering deployment").start();
|
|
2778
|
+
let res;
|
|
2779
|
+
try {
|
|
2780
|
+
res = await apiRequest("/deployments", {
|
|
2781
|
+
method: "POST",
|
|
2782
|
+
body: JSON.stringify(body)
|
|
2783
|
+
});
|
|
2784
|
+
} catch (e) {
|
|
2785
|
+
spinner2?.fail("Deployment failed to start");
|
|
2786
|
+
err(e instanceof ApiError ? e.message : String(e));
|
|
2787
|
+
process.exit(1);
|
|
2788
|
+
}
|
|
2789
|
+
deploymentId = res.data?.deployment_id;
|
|
2790
|
+
payload = res.data ?? res;
|
|
2791
|
+
spinner2?.succeed(deploymentId ? `Deployment queued: ${deploymentId}` : "Deployment queued");
|
|
2588
2792
|
}
|
|
2589
|
-
const deploymentId = res.data?.deployment_id;
|
|
2590
|
-
spinner2?.succeed(deploymentId ? `Deployment queued: ${deploymentId}` : "Deployment queued");
|
|
2591
2793
|
if (isJsonMode() && !opts.watch) {
|
|
2592
|
-
printJson(
|
|
2794
|
+
printJson(payload ?? {});
|
|
2593
2795
|
return;
|
|
2594
2796
|
}
|
|
2595
2797
|
if (!deploymentId) {
|
|
@@ -4240,7 +4442,7 @@ var serverCommand = server;
|
|
|
4240
4442
|
// src/commands/system.ts
|
|
4241
4443
|
import { Command as Command16 } from "commander";
|
|
4242
4444
|
import ora5 from "ora";
|
|
4243
|
-
import { readFileSync as
|
|
4445
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
4244
4446
|
import { createInterface as createInterface6 } from "readline/promises";
|
|
4245
4447
|
import { stdin as input5, stdout as output5 } from "process";
|
|
4246
4448
|
async function guarded(fn) {
|
|
@@ -4566,7 +4768,7 @@ dataTransferCommand.command("import").description("Import an instance export fil
|
|
|
4566
4768
|
}
|
|
4567
4769
|
let file;
|
|
4568
4770
|
try {
|
|
4569
|
-
file = JSON.parse(
|
|
4771
|
+
file = JSON.parse(readFileSync5(opts.file, "utf8"));
|
|
4570
4772
|
} catch {
|
|
4571
4773
|
err(`
|
|
4572
4774
|
Could not read or parse ${opts.file}.
|
|
@@ -4909,7 +5111,7 @@ var mailCommand = new Command17("mail").description("Self-hosted mail server (iR
|
|
|
4909
5111
|
// src/commands/backup.ts
|
|
4910
5112
|
import { Command as Command18 } from "commander";
|
|
4911
5113
|
import ora7 from "ora";
|
|
4912
|
-
import { readFileSync as
|
|
5114
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
4913
5115
|
async function guard3(fn) {
|
|
4914
5116
|
try {
|
|
4915
5117
|
await fn();
|
|
@@ -5202,7 +5404,7 @@ destinationCmd.command("create").description("Create a backup destination").requ
|
|
|
5202
5404
|
let sftpPrivateKey = opts.sftpPrivateKey;
|
|
5203
5405
|
if (opts.sftpPrivateKeyFile) {
|
|
5204
5406
|
try {
|
|
5205
|
-
sftpPrivateKey =
|
|
5407
|
+
sftpPrivateKey = readFileSync6(opts.sftpPrivateKeyFile, "utf8");
|
|
5206
5408
|
} catch {
|
|
5207
5409
|
throw new Error(`Cannot read key file: ${opts.sftpPrivateKeyFile}`);
|
|
5208
5410
|
}
|
|
@@ -5392,21 +5594,21 @@ var apiCommand = new Command20("api").description("Make an authenticated request
|
|
|
5392
5594
|
|
|
5393
5595
|
// src/commands/install.ts
|
|
5394
5596
|
import { Command as Command21 } from "commander";
|
|
5395
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
5597
|
+
import { chmodSync as chmodSync2, existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
5396
5598
|
import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
|
|
5397
5599
|
import { homedir as homedir4 } from "os";
|
|
5398
|
-
import { join as
|
|
5600
|
+
import { join as join7 } from "path";
|
|
5399
5601
|
import ora9 from "ora";
|
|
5400
5602
|
|
|
5401
5603
|
// src/lib/cache.ts
|
|
5402
5604
|
import { createHash } from "crypto";
|
|
5403
5605
|
import { createReadStream, createWriteStream, mkdirSync as mkdirSync4 } from "fs";
|
|
5404
5606
|
import { homedir as homedir3 } from "os";
|
|
5405
|
-
import { dirname as dirname3, join as
|
|
5406
|
-
var CACHE_DIR =
|
|
5407
|
-
var RELEASES_DIR =
|
|
5607
|
+
import { dirname as dirname3, join as join6 } from "path";
|
|
5608
|
+
var CACHE_DIR = join6(homedir3(), ".openship", "cache");
|
|
5609
|
+
var RELEASES_DIR = join6(CACHE_DIR, "releases");
|
|
5408
5610
|
function releaseDir(tag) {
|
|
5409
|
-
return
|
|
5611
|
+
return join6(RELEASES_DIR, tag);
|
|
5410
5612
|
}
|
|
5411
5613
|
function parseSha256(content) {
|
|
5412
5614
|
const token = content.trim().split(/\s+/)[0]?.toLowerCase();
|
|
@@ -5491,7 +5693,7 @@ async function fetchSidecar(url) {
|
|
|
5491
5693
|
return res.text();
|
|
5492
5694
|
}
|
|
5493
5695
|
function installDmg(dmg) {
|
|
5494
|
-
const homeApps =
|
|
5696
|
+
const homeApps = join7(homedir4(), "Applications");
|
|
5495
5697
|
let dest = homeApps;
|
|
5496
5698
|
try {
|
|
5497
5699
|
mkdirSync5(homeApps, { recursive: true });
|
|
@@ -5504,14 +5706,14 @@ function installDmg(dmg) {
|
|
|
5504
5706
|
if (attach.status !== 0) throw new Error(`hdiutil attach failed: ${attach.stderr?.trim()}`);
|
|
5505
5707
|
const mount = (attach.stdout.match(/\/Volumes\/[^\n]*/g) ?? []).pop()?.trim();
|
|
5506
5708
|
if (!mount) throw new Error("Could not determine the mounted volume");
|
|
5507
|
-
let target =
|
|
5709
|
+
let target = join7(dest, "Openship.app");
|
|
5508
5710
|
try {
|
|
5509
|
-
const appInDmg =
|
|
5510
|
-
if (!
|
|
5711
|
+
const appInDmg = join7(mount, "Openship.app");
|
|
5712
|
+
if (!existsSync7(appInDmg)) throw new Error("Openship.app not found in the disk image");
|
|
5511
5713
|
spawnSync2("rm", ["-rf", target]);
|
|
5512
5714
|
let copy = spawnSync2("ditto", [appInDmg, target], { encoding: "utf8" });
|
|
5513
5715
|
if (copy.status !== 0 && dest === homeApps) {
|
|
5514
|
-
target =
|
|
5716
|
+
target = join7("/Applications", "Openship.app");
|
|
5515
5717
|
spawnSync2("rm", ["-rf", target]);
|
|
5516
5718
|
copy = spawnSync2("ditto", [appInDmg, target], { encoding: "utf8" });
|
|
5517
5719
|
}
|
|
@@ -5526,8 +5728,8 @@ function installAppImage(appImage) {
|
|
|
5526
5728
|
return appImage;
|
|
5527
5729
|
}
|
|
5528
5730
|
function installZip(zip) {
|
|
5529
|
-
const localAppData = process.env.LOCALAPPDATA ||
|
|
5530
|
-
const target =
|
|
5731
|
+
const localAppData = process.env.LOCALAPPDATA || join7(homedir4(), "AppData", "Local");
|
|
5732
|
+
const target = join7(localAppData, "Programs", "Openship");
|
|
5531
5733
|
mkdirSync5(target, { recursive: true });
|
|
5532
5734
|
const expand = spawnSync2(
|
|
5533
5735
|
"powershell",
|
|
@@ -5555,8 +5757,8 @@ function launch(kind, target) {
|
|
|
5555
5757
|
child.unref();
|
|
5556
5758
|
return;
|
|
5557
5759
|
}
|
|
5558
|
-
const exe =
|
|
5559
|
-
const path2 =
|
|
5760
|
+
const exe = join7(target, "Openship.exe");
|
|
5761
|
+
const path2 = existsSync7(exe) ? exe : target;
|
|
5560
5762
|
spawnSync2("cmd", ["/c", "start", "", path2]);
|
|
5561
5763
|
}
|
|
5562
5764
|
var installCommand = new Command21("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) => {
|
|
@@ -5582,13 +5784,13 @@ var installCommand = new Command21("install").description("Download and install
|
|
|
5582
5784
|
process.exit(1);
|
|
5583
5785
|
}
|
|
5584
5786
|
const dir = releaseDir(tag);
|
|
5585
|
-
const assetPath =
|
|
5787
|
+
const assetPath = join7(dir, asset.name);
|
|
5586
5788
|
const sidecarPath = `${assetPath}.sha256`;
|
|
5587
5789
|
const assetUrl = `${RELEASES}/download/${tag}/${asset.name}`;
|
|
5588
5790
|
const sidecarUrl = `${assetUrl}.sha256`;
|
|
5589
5791
|
let downloaded = false;
|
|
5590
5792
|
let sha;
|
|
5591
|
-
const cachedUsable = !opts.force &&
|
|
5793
|
+
const cachedUsable = !opts.force && existsSync7(assetPath) && (existsSync7(sidecarPath) || opts.verify === false);
|
|
5592
5794
|
if (cachedUsable) {
|
|
5593
5795
|
info(` Using cached ${asset.name} (${tag}).`);
|
|
5594
5796
|
} else {
|
|
@@ -5610,8 +5812,8 @@ var installCommand = new Command21("install").description("Download and install
|
|
|
5610
5812
|
const s2 = spin4("Verifying checksum\u2026");
|
|
5611
5813
|
try {
|
|
5612
5814
|
let sidecarBody;
|
|
5613
|
-
if (
|
|
5614
|
-
sidecarBody =
|
|
5815
|
+
if (existsSync7(sidecarPath) && !downloaded) {
|
|
5816
|
+
sidecarBody = readFileSync7(sidecarPath, "utf8");
|
|
5615
5817
|
} else {
|
|
5616
5818
|
sidecarBody = await fetchSidecar(sidecarUrl);
|
|
5617
5819
|
}
|
|
@@ -5675,17 +5877,17 @@ var installCommand = new Command21("install").description("Download and install
|
|
|
5675
5877
|
|
|
5676
5878
|
// src/commands/cache.ts
|
|
5677
5879
|
import { Command as Command22 } from "commander";
|
|
5678
|
-
import { existsSync as
|
|
5679
|
-
import { join as
|
|
5880
|
+
import { existsSync as existsSync8, readdirSync, readFileSync as readFileSync8, rmSync as rmSync2, statSync } from "fs";
|
|
5881
|
+
import { join as join8 } from "path";
|
|
5680
5882
|
function listAssets() {
|
|
5681
|
-
if (!
|
|
5883
|
+
if (!existsSync8(RELEASES_DIR)) return [];
|
|
5682
5884
|
const out = [];
|
|
5683
5885
|
for (const tag of readdirSync(RELEASES_DIR)) {
|
|
5684
5886
|
const dir = releaseDir(tag);
|
|
5685
5887
|
if (!statSync(dir).isDirectory()) continue;
|
|
5686
5888
|
for (const name of readdirSync(dir)) {
|
|
5687
5889
|
if (name.endsWith(".sha256")) continue;
|
|
5688
|
-
const path2 =
|
|
5890
|
+
const path2 = join8(dir, name);
|
|
5689
5891
|
const st = statSync(path2);
|
|
5690
5892
|
if (!st.isFile()) continue;
|
|
5691
5893
|
out.push({
|
|
@@ -5693,7 +5895,7 @@ function listAssets() {
|
|
|
5693
5895
|
name,
|
|
5694
5896
|
path: path2,
|
|
5695
5897
|
size: st.size,
|
|
5696
|
-
hasSidecar:
|
|
5898
|
+
hasSidecar: existsSync8(`${path2}.sha256`)
|
|
5697
5899
|
});
|
|
5698
5900
|
}
|
|
5699
5901
|
}
|
|
@@ -5724,7 +5926,7 @@ var verifyCmd2 = new Command22("verify").description("Re-hash cached assets and
|
|
|
5724
5926
|
results.push({ tag: a.tag, asset: a.name, result: "no-sidecar" });
|
|
5725
5927
|
continue;
|
|
5726
5928
|
}
|
|
5727
|
-
const expected = parseSha256(
|
|
5929
|
+
const expected = parseSha256(readFileSync8(`${a.path}.sha256`, "utf8"));
|
|
5728
5930
|
const actual = await hashFile(a.path);
|
|
5729
5931
|
const okMatch = expected !== null && expected === actual;
|
|
5730
5932
|
if (!okMatch) bad += 1;
|
|
@@ -5740,12 +5942,12 @@ var verifyCmd2 = new Command22("verify").description("Re-hash cached assets and
|
|
|
5740
5942
|
});
|
|
5741
5943
|
var cleanCmd = new Command22("clean").description("Delete cached release assets").argument("[tag]", "Only remove this release tag (default: all)").action((tag) => {
|
|
5742
5944
|
const target = tag ? releaseDir(tag) : RELEASES_DIR;
|
|
5743
|
-
if (!
|
|
5945
|
+
if (!existsSync8(target)) {
|
|
5744
5946
|
if (isJsonMode()) printJson({ removed: false, path: target });
|
|
5745
5947
|
else info(` Nothing to clean (${target}).`);
|
|
5746
5948
|
return;
|
|
5747
5949
|
}
|
|
5748
|
-
|
|
5950
|
+
rmSync2(target, { recursive: true, force: true });
|
|
5749
5951
|
if (isJsonMode()) printJson({ removed: true, path: target });
|
|
5750
5952
|
else ok(`
|
|
5751
5953
|
Removed ${target}
|
|
@@ -5758,7 +5960,7 @@ var cacheCommand = new Command22("cache").description("Manage the local download
|
|
|
5758
5960
|
|
|
5759
5961
|
// src/index.ts
|
|
5760
5962
|
var program = new Command23();
|
|
5761
|
-
program.name("openship").description("Openship CLI \u2014 install, run, and manage Openship from your terminal").version("0.1.
|
|
5963
|
+
program.name("openship").description("Openship CLI \u2014 install, run, and manage Openship from your terminal").version("0.1.9").option("--json", "Machine-readable JSON output (stdout data only)").hook("preAction", (thisCommand) => {
|
|
5762
5964
|
if (thisCommand.opts().json) setJsonMode(true);
|
|
5763
5965
|
});
|
|
5764
5966
|
program.addCommand(upCommand);
|