@harly/cli 0.2.1 → 0.2.3
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 +1 -1
- package/dist/index.js +45 -10
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ cd harly
|
|
|
26
26
|
npx @harly/cli update
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
Use `--to edge` for preview builds or `--to
|
|
29
|
+
Use `--to edge` for preview builds or `--to <release-version>` for a fixed release.
|
|
30
30
|
The command creates a private local rollback point first, pulls the target,
|
|
31
31
|
pins its immutable digest, runs migrations once, recreates the services, and
|
|
32
32
|
waits for readiness. This works without extra system dependencies. Use
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,16 @@ import process from "node:process";
|
|
|
11
11
|
import { spawnSync } from "node:child_process";
|
|
12
12
|
import * as p from "@clack/prompts";
|
|
13
13
|
import pc from "picocolors";
|
|
14
|
+
|
|
15
|
+
// src/release.ts
|
|
16
|
+
var embeddedRelease = {
|
|
17
|
+
version: "0.1.0-beta.2",
|
|
18
|
+
image: "ghcr.io/vytral/harly",
|
|
19
|
+
digest: "sha256:f0b999a76fa170887f625d12835379ef57772903c792fb0b66faea9270776a16"
|
|
20
|
+
};
|
|
21
|
+
var releaseImage = (release) => `${release.image}@${release.digest}`;
|
|
22
|
+
|
|
23
|
+
// src/index.ts
|
|
14
24
|
var resourceProfiles = {
|
|
15
25
|
compact: {
|
|
16
26
|
app: "1024m",
|
|
@@ -63,7 +73,26 @@ var force = flags.has("--force");
|
|
|
63
73
|
var yes = flags.has("--yes");
|
|
64
74
|
var json = flags.has("--json");
|
|
65
75
|
var interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY && !process.env.CI);
|
|
66
|
-
var cliVersion = "0.
|
|
76
|
+
var cliVersion = "0.2.3";
|
|
77
|
+
var releaseManifestUrl = process.env.HARLY_RELEASE_MANIFEST_URL ?? "https://raw.githubusercontent.com/Vytral/harly/main/release-manifest.json";
|
|
78
|
+
var currentOfficialRelease;
|
|
79
|
+
var releaseManifestChecked = false;
|
|
80
|
+
function validRelease(value) {
|
|
81
|
+
if (!value || typeof value !== "object") return false;
|
|
82
|
+
const release = value;
|
|
83
|
+
return typeof release.version === "string" && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(release.version) && release.image === "ghcr.io/vytral/harly" && typeof release.digest === "string" && /^sha256:[a-f0-9]{64}$/.test(release.digest);
|
|
84
|
+
}
|
|
85
|
+
async function officialRelease() {
|
|
86
|
+
if (releaseManifestChecked) return currentOfficialRelease ?? embeddedRelease;
|
|
87
|
+
releaseManifestChecked = true;
|
|
88
|
+
try {
|
|
89
|
+
const response = await fetch(releaseManifestUrl, { signal: AbortSignal.timeout(2e3) });
|
|
90
|
+
const candidate = response.ok ? await response.json() : null;
|
|
91
|
+
if (validRelease(candidate)) currentOfficialRelease = candidate;
|
|
92
|
+
} catch {
|
|
93
|
+
}
|
|
94
|
+
return currentOfficialRelease ?? embeddedRelease;
|
|
95
|
+
}
|
|
67
96
|
var logo = `
|
|
68
97
|
\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557
|
|
69
98
|
\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D
|
|
@@ -246,7 +275,7 @@ async function collectNonInteractiveAnswers(directory) {
|
|
|
246
275
|
endpoint: process.env.S3_ENDPOINT?.trim() || "",
|
|
247
276
|
publicUrl: process.env.S3_PUBLIC_URL?.trim() || ""
|
|
248
277
|
} : null;
|
|
249
|
-
const image = process.env.HARLY_IMAGE_REF ??
|
|
278
|
+
const image = process.env.HARLY_IMAGE_REF ?? releaseImage(await officialRelease());
|
|
250
279
|
if (image.endsWith(":latest")) throw new CliError("Installations must pin a version or digest, never latest.", 2);
|
|
251
280
|
return { mode, url, email, organization, storage, s3, resourceProfile, image };
|
|
252
281
|
}
|
|
@@ -320,7 +349,7 @@ async function collectInteractiveAnswers(directory) {
|
|
|
320
349
|
{ value: "performance", label: "Performance", hint: "8 GB RAM or more" }
|
|
321
350
|
]
|
|
322
351
|
}));
|
|
323
|
-
const image = process.env.HARLY_IMAGE_REF ??
|
|
352
|
+
const image = process.env.HARLY_IMAGE_REF ?? releaseImage(await officialRelease());
|
|
324
353
|
if (image.endsWith(":latest")) throw new CliError("Installations must pin a version or digest, never latest.", 2);
|
|
325
354
|
const services = `PostgreSQL, migrator, app, scheduler${mode === "caddy" ? ", Caddy" : ""}`;
|
|
326
355
|
const localPort = process.env.HARLY_PORT ?? (mode === "local" && url.port ? url.port : "3000");
|
|
@@ -411,7 +440,7 @@ services:
|
|
|
411
440
|
cpus: \${HARLY_APP_CPUS:-2.0}
|
|
412
441
|
logging: *logging
|
|
413
442
|
read_only: true
|
|
414
|
-
tmpfs: [/tmp]
|
|
443
|
+
tmpfs: [/tmp:size=256m,mode=1777]
|
|
415
444
|
environment: { <<: *env, NODE_OPTIONS: "\${HARLY_APP_NODE_OPTIONS:---max-old-space-size=1024}" }
|
|
416
445
|
depends_on: { migrate: { condition: service_completed_successfully } }
|
|
417
446
|
ports: ["127.0.0.1:\${HARLY_PORT:-3000}:3000"]
|
|
@@ -426,7 +455,7 @@ services:
|
|
|
426
455
|
cpus: \${HARLY_SCHEDULER_CPUS:-0.5}
|
|
427
456
|
logging: *logging
|
|
428
457
|
read_only: true
|
|
429
|
-
tmpfs: [/tmp]
|
|
458
|
+
tmpfs: [/tmp:size=64m,mode=1777]
|
|
430
459
|
environment: { <<: *env, HARLY_INTERNAL_URL: http://app:3000, NODE_OPTIONS: "\${HARLY_SCHEDULER_NODE_OPTIONS:---max-old-space-size=160}" }
|
|
431
460
|
depends_on: { app: { condition: service_healthy } }
|
|
432
461
|
healthcheck: { test: [CMD, node, /app/runtime.mjs, doctor], interval: 30s, timeout: 10s, start_period: 45s, retries: 3 }
|
|
@@ -906,7 +935,8 @@ async function cloudGuide(provider) {
|
|
|
906
935
|
...databaseUrl ? [`DATABASE_URL=${envLine(databaseUrl)}`] : []
|
|
907
936
|
].join("\n") + "\n";
|
|
908
937
|
await atomicWrite(path.join(directory, ".env"), env, 384);
|
|
909
|
-
const
|
|
938
|
+
const deploymentRelease = await officialRelease();
|
|
939
|
+
const image = releaseImage(deploymentRelease);
|
|
910
940
|
if (provider === "fly") {
|
|
911
941
|
await atomicWrite(path.join(directory, "fly.toml"), `app = "replace-with-your-harly-app-name"
|
|
912
942
|
primary_region = "iad"
|
|
@@ -960,9 +990,9 @@ primary_region = "iad"
|
|
|
960
990
|
secretEnv("STORAGE_UPLOAD_SECRET", runtimeSecrets.storageUpload),
|
|
961
991
|
secretEnv("CRON_SECRET", runtimeSecrets.cron),
|
|
962
992
|
secretEnv("HARLY_SETUP_SECRET", runtimeSecrets.setup),
|
|
963
|
-
|
|
993
|
+
`services:`,
|
|
964
994
|
" - name: web",
|
|
965
|
-
|
|
995
|
+
` image: { registry_type: GHCR, registry: vytral, repository: harly, tag: ${deploymentRelease.version} }`,
|
|
966
996
|
" run_command: node /app/runtime.mjs serve",
|
|
967
997
|
" http_port: 3000",
|
|
968
998
|
" instance_count: 1",
|
|
@@ -970,14 +1000,14 @@ primary_region = "iad"
|
|
|
970
1000
|
" health_check: { http_path: /api/health/ready, port: 3000, initial_delay_seconds: 20, period_seconds: 30, timeout_seconds: 5, failure_threshold: 5 }",
|
|
971
1001
|
"workers:",
|
|
972
1002
|
" - name: scheduler",
|
|
973
|
-
|
|
1003
|
+
` image: { registry_type: GHCR, registry: vytral, repository: harly, tag: ${deploymentRelease.version} }`,
|
|
974
1004
|
" run_command: node /app/runtime.mjs scheduler",
|
|
975
1005
|
" instance_count: 1",
|
|
976
1006
|
" instance_size_slug: apps-s-1vcpu-0.5gb",
|
|
977
1007
|
"jobs:",
|
|
978
1008
|
" - name: migrate",
|
|
979
1009
|
" kind: PRE_DEPLOY",
|
|
980
|
-
|
|
1010
|
+
` image: { registry_type: GHCR, registry: vytral, repository: harly, tag: ${deploymentRelease.version} }`,
|
|
981
1011
|
" run_command: node /app/runtime.mjs migrate",
|
|
982
1012
|
" instance_size_slug: apps-s-1vcpu-0.5gb",
|
|
983
1013
|
""
|
|
@@ -1059,6 +1089,11 @@ async function main() {
|
|
|
1059
1089
|
case "-h":
|
|
1060
1090
|
usage();
|
|
1061
1091
|
return;
|
|
1092
|
+
case "--version":
|
|
1093
|
+
case "-v":
|
|
1094
|
+
process.stdout.write(`${cliVersion}
|
|
1095
|
+
`);
|
|
1096
|
+
return;
|
|
1062
1097
|
default:
|
|
1063
1098
|
usage();
|
|
1064
1099
|
throw new CliError(`Unknown command: ${command}`, 2);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@harly/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Official operations CLI for self-hosted Harly.",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"scripts": {
|
|
17
17
|
"build": "esbuild src/index.ts --bundle --packages=external --platform=node --format=esm --target=node20 --outfile=dist/index.js",
|
|
18
18
|
"typecheck": "tsc --noEmit --module NodeNext --moduleResolution NodeNext --target ES2022 --types node src/index.ts",
|
|
19
|
-
"test": "pnpm build && node --test test/*.test.mjs",
|
|
19
|
+
"test": "pnpm build && node scripts/sync-release-assets.mjs --check && node --test test/*.test.mjs",
|
|
20
20
|
"test:backup-restore": "pnpm build && ./test/backup-restore.destructive.sh",
|
|
21
21
|
"pack:check": "pnpm build && npm pack --dry-run"
|
|
22
22
|
},
|