@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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +45 -10
  3. 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 0.1.0-beta.2` for a fixed release.
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.1.4";
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 ?? "ghcr.io/vytral/harly:0.1.0-beta.2";
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 ?? "ghcr.io/vytral/harly:0.1.0-beta.2";
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 image = "ghcr.io/vytral/harly:0.1.0-beta.2";
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
- "services:",
993
+ `services:`,
964
994
  " - name: web",
965
- " image: { registry_type: GHCR, registry: vytral, repository: harly, tag: 0.1.0-beta.1 }",
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
- " image: { registry_type: GHCR, registry: vytral, repository: harly, tag: 0.1.0-beta.1 }",
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
- " image: { registry_type: GHCR, registry: vytral, repository: harly, tag: 0.1.0-beta.1 }",
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.1",
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
  },