vela 0.15.0 → 0.15.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vela",
3
- "version": "0.15.0",
3
+ "version": "0.15.2",
4
4
  "type": "module",
5
5
  "description": "A CLI for creating and updating SvelteKit projects",
6
6
  "license": "MIT",
@@ -30,19 +30,20 @@
30
30
  "format": "prettier --write .",
31
31
  "test": "vitest run",
32
32
  "prepare": "npm run build",
33
+ "version": "npm run build",
33
34
  "lint:sh": "shellcheck -x -P SCRIPTDIR -S warning templates/server/*.sh",
34
35
  "test:scripts": "bash scripts/test-server-scripts.sh"
35
36
  },
36
37
  "dependencies": {
37
38
  "@clack/prompts": "^1.7.0",
38
39
  "@faker-js/faker": "^10.6.0",
39
- "@velastack/patterns": "^0.4.0",
40
+ "@velastack/patterns": "^0.4.1",
40
41
  "@velastack/pocketbase-codegen": "^0.1.0",
41
42
  "annotate-json-schema": "^0.1.1",
42
- "commander": "^13.1.0",
43
+ "commander": "^15.0.0",
43
44
  "cross-spawn": "^7.0.6",
44
45
  "dedent": "^1.7.2",
45
- "dotenv": "^17.4.2",
46
+ "dotenv": "^18.0.1",
46
47
  "fetch-cookie": "^3.2.0",
47
48
  "json-schema-faker": "^0.6.3",
48
49
  "magic-string": "^1.2.3",
@@ -77,6 +77,20 @@ PB_UNIT=$(unit_pb "$INSTANCE")
77
77
 
78
78
  lock_instance "$INSTANCE" "$LOCK_WAIT"
79
79
 
80
+ # The instance was removed after this deploy began: a pull request closed while
81
+ # its last push was still building, say. Going ahead would recreate what the
82
+ # removal was for, so the upload is dropped instead - and with it the instance
83
+ # directory, when the upload is all that is in it. A `shared/` left by a
84
+ # non-purge destroy is kept. Checked before anything else, since a purge that
85
+ # ran mid-upload may have taken part of the release with it.
86
+ if removed_since_release "$INSTANCE" "$RELEASE"; then
87
+ rm -rf "${RELEASE_DIR:?}"
88
+ rmdir "$APP/releases" "$APP" 2>/dev/null || true
89
+ printf 'error: %s was removed at %s, after this deploy began (release %s) - not recreating it\n' \
90
+ "$INSTANCE" "$(removed_at "$INSTANCE")" "$RELEASE" >&2
91
+ exit "$REMOVED_EXIT"
92
+ fi
93
+
80
94
  [ -d "$RELEASE_DIR" ] || die "release $RELEASE was not uploaded to $RELEASE_DIR"
81
95
  [ -f "$RELEASE_DIR/build/index.js" ] || die \
82
96
  "release $RELEASE has no build/index.js - the app must build with @sveltejs/adapter-node"
@@ -447,6 +461,8 @@ state_merge "$INSTANCE" "$(jq -c -n \
447
461
  managed: $managed, url: $url,
448
462
  healthCheckPath: $health, pocketbaseVersion: $pb, gitSha: $sha,
449
463
  webPort: $web, pbPort: $pbport, backend: ($backend == 1), deployedAt: $at}')"
464
+ # A deploy that began after the last removal is live; the mark has done its job.
465
+ clear_removed "$INSTANCE"
450
466
 
451
467
  # ------------------------------------------------------------------ routing
452
468
  #
@@ -470,7 +486,17 @@ if [ -n "$DOMAIN" ]; then
470
486
  tmp=$(mktemp "$VELA_ETC/caddy/.snippet.XXXXXX")
471
487
  {
472
488
  printf '# Managed by vela - app %s (%s)\n' "$APP_NAME" "$INSTANCE"
473
- printf '%s {\n\treverse_proxy 127.0.0.1:%s\n}\n' "$hosts" "$WEB_PORT"
489
+ printf '%s {\n' "$hosts"
490
+ # The app precompresses its built assets but not rendered pages; Caddy
491
+ # passes an already-encoded response through untouched.
492
+ printf '\tencode zstd gzip\n'
493
+ # Files from static/ come back with an ETag and no Cache-Control, so a
494
+ # browser asks again on every view. Unhashed, so an hour rather than
495
+ # forever; `?` leaves whatever the app or PocketBase set alone.
496
+ printf '\t@static path *.jpg *.jpeg *.png *.gif *.webp *.avif *.svg *.ico *.woff *.woff2\n'
497
+ printf '\theader @static ?Cache-Control "public, max-age=3600"\n'
498
+ printf '\treverse_proxy 127.0.0.1:%s\n' "$WEB_PORT"
499
+ printf '}\n'
474
500
  } > "$tmp"
475
501
  caddy_install "$tmp" "$CADDY_SNIPPET" \
476
502
  || die "release $RELEASE is live, but the generated Caddy config for $DOMAIN is invalid and was not installed - routing is unchanged"
@@ -35,6 +35,10 @@ ETC=$(etc_dir "$INSTANCE")
35
35
 
36
36
  lock_instance "$INSTANCE" "$LOCK_WAIT"
37
37
 
38
+ # Before anything else, and whether or not there turns out to be anything to
39
+ # remove: a deploy still building elsewhere must not put the instance back.
40
+ mark_removed "$INSTANCE"
41
+
38
42
  # Nothing to remove is a result, not an error: a preview that never deployed
39
43
  # still gets a cleanup run when its pull request closes. Nothing is touched,
40
44
  # so a mistyped name cannot do harm either way.
@@ -58,6 +58,58 @@ require_provisioned() {
58
58
  [ -f "$VELA_ETC/provisioned" ] || die "server is not provisioned - run 'vela provision' first"
59
59
  }
60
60
 
61
+ # When an instance was last removed, so a deploy that was already under way
62
+ # cannot bring it back.
63
+ #
64
+ # A deploy spends minutes building on its own machine before it reaches the
65
+ # server and takes the instance lock; a destroy that lands in that window finds
66
+ # nothing to remove, and the deploy then creates the instance that was meant
67
+ # to be gone. CI does this whenever a pull request closes while a push to it
68
+ # is still building: the cleanup runs first and the preview appears after it.
69
+ #
70
+ # destroy.sh records the server's clock here, whether or not there was anything
71
+ # to remove. A release id starts with the server's clock at the moment its
72
+ # deploy began, so apply.sh can tell a deploy that started before the removal
73
+ # (refused) from one that started after it (a fresh deploy - the pull request
74
+ # was reopened, say - which goes ahead and clears the mark). Kept under state/,
75
+ # not the instance's own directory, which a purge deletes.
76
+
77
+ # Exit status of that refusal, so the CLI can tell it apart from a failed deploy.
78
+ # shellcheck disable=SC2034 # read by apply.sh
79
+ REMOVED_EXIT=3
80
+ removed_file() { printf '%s/state/removed/%s' "$VELA_ROOT" "$1"; }
81
+
82
+ # usage: mark_removed <instance>
83
+ mark_removed() {
84
+ local file
85
+ file=$(removed_file "$1")
86
+ mkdir -p "$(dirname "$file")"
87
+ # A mark only matters to a deploy that began before it, and no build takes a
88
+ # month; pruning keeps one file per pull request from piling up forever.
89
+ find "$(dirname "$file")" -maxdepth 1 -type f -mtime +30 -delete 2>/dev/null || true
90
+ date -u +%Y%m%dT%H%M%SZ > "$file"
91
+ }
92
+
93
+ # The stamp of the instance's last removal, or nothing.
94
+ removed_at() {
95
+ local file
96
+ file=$(removed_file "$1")
97
+ if [ -f "$file" ]; then head -n1 "$file"; fi
98
+ }
99
+
100
+ # Whether `release` belongs to a deploy that began no later than the instance's
101
+ # last removal. A tie counts as before: a deploy and a destroy in the same
102
+ # second are far more likely to be the race than a deliberate redeploy.
103
+ #
104
+ # usage: removed_since_release <instance> <release>
105
+ removed_since_release() {
106
+ local removed
107
+ removed=$(removed_at "$1")
108
+ [ -n "$removed" ] && ! [[ "${2%%-*}" > "$removed" ]]
109
+ }
110
+
111
+ clear_removed() { rm -f "$(removed_file "$1")"; }
112
+
61
113
  # Whether an instance was last deployed with a PocketBase backend: `true` or
62
114
  # `false`. Absent state, or state from before the flag existed, is treated as
63
115
  # having one, so this only ever answers `false` for an instance that was