@meistrari/mise-en-place 2.19.1 → 2.21.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 CHANGED
@@ -58,6 +58,94 @@ You can customize the update behaviour through a `miseEnPlace.updateMeistrariLib
58
58
 
59
59
  When `exact` is `true`, every updated package is written as an exact version: each package is still updated as far as its strategy allows, but the resulting version is written without a `^` or `~`.
60
60
 
61
+ ## Local proxy for Compose services
62
+ The `tela-proxy` mise tasks run one Traefik container shared by every project on the machine. It serves each Compose service attached to the `tela-network` Docker network at a `*.localhost` host, so services publish no HTTP ports and several projects run side by side.
63
+
64
+ Requires Docker; mise installs the `jq` the tasks use. The proxy listens on port 80 of the loopback interface only, and the dashboard of active routes is at `http://tela-proxy.localhost`.
65
+
66
+ ### Tasks
67
+ - **`mise run tela-proxy`** (alias `tela-proxy:start`): creates `tela-network` when missing and starts the proxy. Make it a dependency of every task that starts your Compose project (`#MISE depends=["tela-proxy"]`), so it also runs the conflict check below.
68
+ - **`mise run tela-proxy:stop`**: stops the proxy. It stays stopped across reboots until `tela-proxy` runs again.
69
+ - **`mise run tela-proxy:worktree [label] [-- command...]`** (aliases `worktree`, `wt`): runs the command under a separate Compose project for a worktree. Without a command, it opens `$SHELL` with the same environment.
70
+
71
+ ### Hosts
72
+ A service gets `http://{service}.{project}.localhost`, where `project` is the Compose project name. Set it with `name:` in the Compose file so it stays the same in every clone; otherwise Compose uses the directory name.
73
+
74
+ - A service named after the project prefix drops it: service `auth-api` in project `auth` is `api.auth.localhost`.
75
+ - A service named exactly like the project is `{project}.localhost`.
76
+ - Traefik uses the port the image exposes (`EXPOSE`).
77
+ - A service opts out with the `traefik.enable=false` label.
78
+
79
+ A `tela-proxy.host` label replaces a service's host. `{project}` in the value becomes the project's domain, including the worktree suffix, so a worktree keeps its own host:
80
+
81
+ ```yaml
82
+ services:
83
+ web:
84
+ labels:
85
+ tela-proxy.host: '{project}.localhost' # service.localhost, or service-feat-x.localhost in a worktree
86
+ admin:
87
+ labels:
88
+ tela-proxy.host: 'painel.{project}.localhost'
89
+ ```
90
+
91
+ A value without `{project}` is the same in every worktree, so the main checkout and its worktrees answer on one host.
92
+
93
+ `{project}.localhost` is the value of `TELA_LOCAL_DOMAIN` (see [Worktrees](#worktrees)), so the URL for the `admin` service above is `http://painel.${TELA_LOCAL_DOMAIN:-service.localhost}`.
94
+
95
+ Only HTTP is routed. Databases keep their published ports.
96
+
97
+ ### Networks
98
+ Keep the Compose `default` network private to the project, and attach only the services the proxy routes to `tela-network`:
99
+
100
+ ```yaml
101
+ services:
102
+ auth-api:
103
+ networks:
104
+ default:
105
+ aliases: [auth-api-internal]
106
+ tela-network: {}
107
+ auth-postgres: {} # default only
108
+
109
+ networks:
110
+ tela-network:
111
+ external: true
112
+ ```
113
+
114
+ Every project and worktree registers its service names on `tela-network`, so a name there can resolve to another checkout's container. On the private `default` network, names resolve only within the project:
115
+
116
+ - Databases and other internal services stay on `default` only, and their names are safe to use. Label them `traefik.enable=false`: the proxy sees every container and would otherwise create a route it cannot reach.
117
+ - Calls from one service of the project to an HTTP service of the same project use an alias declared only on `default` (`http://auth-api-internal:3001`), because the service name is also on `tela-network`.
118
+ - Another project reaches an HTTP service by its service name on `tela-network` (`http://auth-api:3001`).
119
+
120
+ Changing `networks:` needs the containers recreated: stop the stack and start it again.
121
+
122
+ Inside a container, `*.localhost` resolves to the container itself, so containers never call each other through the proxy hosts.
123
+
124
+ ### Worktrees
125
+ `tela-proxy:worktree` exports these variables before running the command:
126
+
127
+ - **`COMPOSE_PROJECT_NAME`**: `{project}_{label}`. It overrides `name:`, so the worktree gets its own containers and volumes.
128
+ - **`TELA_LOCAL_DOMAIN`**: `{project}-{label}.localhost`, the parent domain of the worktree's hosts (`api.auth-feat-x.localhost`).
129
+ - **`TELA_PROXY_WORKTREE`**: the label. The task refuses to open a shell inside another one.
130
+
131
+ The label defaults to the name of the current git worktree, lowercased and limited to `a-z`, `0-9` and `-`. In the main checkout, pass one explicitly.
132
+
133
+ Build the URLs a project gives its own services from `TELA_LOCAL_DOMAIN`, defaulting to the main checkout's domain:
134
+
135
+ ```yaml
136
+ services:
137
+ auth-api:
138
+ environment:
139
+ BETTER_AUTH_URL: http://api.${TELA_LOCAL_DOMAIN:-auth.localhost}
140
+ ```
141
+
142
+ Published database ports still collide with the main checkout. Override them through the variables your Compose file reads, for example `AUTH_POSTGRES_PORT=5434 mise run wt -- mise run dev`.
143
+
144
+ ### Conflict check
145
+ `tela-proxy` and `tela-proxy:worktree` look up the containers of the current Compose project. When they were started from another directory that still exists, the task fails instead of letting `up` recreate them and share their volumes. Running `mise run dev` inside a worktree without `tela-proxy:worktree` fails this way. Containers from a directory that no longer exists, such as a removed worktree, only produce a warning and are taken over.
146
+
147
+ The check only covers commands that go through these tasks. A plain `docker compose` call, or one that passes `-p`, bypasses it.
148
+
61
149
  ## Makefile
62
150
  The postinstall script does not create a `Makefile` automatically. To use the shared Makefile commands, create a `Makefile` and include this package's `Makefile`.
63
151
 
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env bash
2
- #MISE description="Login to the internal registry. Opens the UI so you can copy your CLI secret."
2
+ #MISE description="Login to the internal npm and container registries. Opens the browser for npm, then the registry UI so you can copy your CLI secret."
3
+ #USAGE flag "--npm-registry <url>" default="https://npm.internal.tela.tools/" help="npm registry to log in to through the browser." env="NPM_REGISTRY"
3
4
  #USAGE flag "-r --registry <registry>" default="registry.internal.tela.tools" help="Registry host to authenticate against." env="REGISTRY_HOST"
4
5
  #USAGE flag "-u --username <username>" default="" help="Registry OIDC username. Prompted for when omitted." env="REGISTRY_USERNAME"
5
6
  #USAGE flag "-f --force" default=#false help="Re-authenticate even if the stored credential still works."
@@ -9,6 +10,7 @@
9
10
 
10
11
  set -euo pipefail
11
12
 
13
+ NPM_REGISTRY="${usage_npm_registry:-${NPM_REGISTRY:-https://npm.internal.tela.tools/}}"
12
14
  REGISTRY="${usage_registry:-${REGISTRY_HOST:-registry.internal.tela.tools}}"
13
15
  USERNAME="${usage_username:-${REGISTRY_USERNAME:-}}"
14
16
 
@@ -60,6 +62,16 @@ open_browser() {
60
62
 
61
63
  command -v docker >/dev/null 2>&1 || die "docker is not installed"
62
64
  command -v jq >/dev/null 2>&1 || die "jq is not installed"
65
+ command -v npm >/dev/null 2>&1 || die "npm is not installed"
66
+
67
+ # The internal npm registry comes first: its web login only needs the browser,
68
+ # while the Harbor step below waits for a pasted secret. Like Harbor, it is only
69
+ # reachable over the NetBird VPN.
70
+ if [ "${usage_force:-false}" != "true" ] && npm whoami --registry "$NPM_REGISTRY" >/dev/null 2>&1; then
71
+ echo "Already logged in to ${NPM_REGISTRY}."
72
+ else
73
+ npm login --auth-type=web --registry "$NPM_REGISTRY"
74
+ fi
63
75
 
64
76
  check_reachable || die "cannot reach ${REGISTRY}. It is only routable over the NetBird VPN — connect and retry."
65
77
 
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env bash
2
+ #MISE description="Create tela-network and start the shared proxy that serves Compose services on *.localhost"
3
+ #MISE alias="tela-proxy:start"
4
+ #MISE tools={jq="1.8.2"}
5
+
6
+ set -euo pipefail
7
+
8
+ # shellcheck source=lib.sh
9
+ source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
10
+
11
+ enter_caller_dir
12
+
13
+ check_project_conflict
14
+
15
+ acquire_proxy_lock
16
+ ensure_network
17
+ if ! proxy_compose up -d --quiet-pull >/dev/null; then
18
+ die "could not start the proxy. If port 80 is taken, find who holds it with:
19
+ sudo ss -ltnp 'sport = :80' (Linux)
20
+ sudo lsof -iTCP:80 -sTCP:LISTEN (macOS)"
21
+ fi
22
+ release_proxy_lock
23
+
24
+ info "tela-proxy is running: http://<service>.<project>.localhost (dashboard: http://tela-proxy.localhost)"
@@ -0,0 +1,26 @@
1
+ # Shared local proxy. Started by `mise run tela-proxy`, never by hand: the task
2
+ # copies traefik.yml to TELA_PROXY_TRAEFIK_CONFIG and sets TELA_PROXY_CONFIG_HASH,
3
+ # so a changed config recreates the container.
4
+ name: tela-proxy
5
+
6
+ services:
7
+ traefik:
8
+ image: traefik:v3.7.18
9
+ restart: unless-stopped
10
+ command: [--configFile=/etc/traefik/traefik.yml]
11
+ ports:
12
+ # Loopback only, on both stacks: *.localhost resolves to ::1 on Linux.
13
+ - '127.0.0.1:80:80'
14
+ - '[::1]:80:80'
15
+ volumes:
16
+ - /var/run/docker.sock:/var/run/docker.sock:ro
17
+ - ${TELA_PROXY_TRAEFIK_CONFIG:?run mise run tela-proxy}:/etc/traefik/traefik.yml:ro
18
+ labels:
19
+ tela-proxy.config-hash: ${TELA_PROXY_CONFIG_HASH:-unset}
20
+ traefik.http.routers.tela-proxy.rule: Host(`tela-proxy.localhost`)
21
+ traefik.http.routers.tela-proxy.service: api@internal
22
+
23
+ networks:
24
+ default:
25
+ name: tela-network
26
+ external: true
@@ -0,0 +1,168 @@
1
+ # Shared helpers for the tela-proxy mise tasks. Sourced, not executed.
2
+
3
+ TELA_PROXY_HOME="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
4
+ TELA_PROXY_NETWORK="tela-network"
5
+ TELA_PROXY_PROJECT="tela-proxy"
6
+
7
+ die() { printf '\033[31merror:\033[0m %s\n' "$*" >&2; exit 1; }
8
+ warn() { printf '\033[33mwarning:\033[0m %s\n' "$*" >&2; }
9
+ info() { printf '\033[36m%s\033[0m\n' "$*" >&2; }
10
+
11
+ # mise runs a task from its config root, not from where it was called. It keeps
12
+ # the caller's directory in MISE_ORIGINAL_CWD.
13
+ enter_caller_dir() {
14
+ cd "${MISE_ORIGINAL_CWD:-$PWD}"
15
+ }
16
+
17
+ sha256() {
18
+ if command -v sha256sum >/dev/null 2>&1; then
19
+ sha256sum | cut -d' ' -f1
20
+ else
21
+ shasum -a 256 | cut -d' ' -f1
22
+ fi
23
+ }
24
+
25
+ # Each repository runs these tasks from its own node_modules, so traefik.yml is
26
+ # copied to a fixed path before mounting it. Mounting it from node_modules would
27
+ # change the container config with every repository and recreate the proxy.
28
+ TELA_PROXY_STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/tela-proxy"
29
+
30
+ # Starts from several repositories can run at once and touch the same network,
31
+ # config file and container. mkdir is atomic and works the same on Linux and
32
+ # macOS, which has no flock.
33
+ TELA_PROXY_LOCK_DIR="$TELA_PROXY_STATE_DIR/lock"
34
+ TELA_PROXY_LOCK_TIMEOUT_SECONDS=120
35
+
36
+ acquire_proxy_lock() {
37
+ mkdir -p "$TELA_PROXY_STATE_DIR"
38
+ local waited=0
39
+ until mkdir "$TELA_PROXY_LOCK_DIR" 2>/dev/null; do
40
+ if [ "$waited" -ge "$TELA_PROXY_LOCK_TIMEOUT_SECONDS" ]; then
41
+ die "another tela-proxy start has held $TELA_PROXY_LOCK_DIR for ${TELA_PROXY_LOCK_TIMEOUT_SECONDS}s. If no start is running, remove that directory."
42
+ fi
43
+ sleep 1
44
+ waited=$((waited + 1))
45
+ done
46
+ trap 'rmdir "$TELA_PROXY_LOCK_DIR" 2>/dev/null || true' EXIT
47
+ }
48
+
49
+ release_proxy_lock() {
50
+ rmdir "$TELA_PROXY_LOCK_DIR" 2>/dev/null || true
51
+ trap - EXIT
52
+ }
53
+
54
+ proxy_compose() {
55
+ mkdir -p "$TELA_PROXY_STATE_DIR"
56
+ if ! cmp -s "$TELA_PROXY_HOME/traefik.yml" "$TELA_PROXY_STATE_DIR/traefik.yml"; then
57
+ # Replace the file in one rename, so Traefik never reads a partial copy.
58
+ cp "$TELA_PROXY_HOME/traefik.yml" "$TELA_PROXY_STATE_DIR/traefik.yml.$$"
59
+ mv "$TELA_PROXY_STATE_DIR/traefik.yml.$$" "$TELA_PROXY_STATE_DIR/traefik.yml"
60
+ fi
61
+
62
+ # The caller's Compose env (a worktree's COMPOSE_PROJECT_NAME, COMPOSE_FILE)
63
+ # must not leak into the proxy project.
64
+ TELA_PROXY_CONFIG_HASH="$(cat "$TELA_PROXY_HOME/compose.yaml" "$TELA_PROXY_HOME/traefik.yml" | sha256)" \
65
+ TELA_PROXY_TRAEFIK_CONFIG="$TELA_PROXY_STATE_DIR/traefik.yml" \
66
+ env -u COMPOSE_PROJECT_NAME -u COMPOSE_FILE -u COMPOSE_PROFILES \
67
+ docker compose -p "$TELA_PROXY_PROJECT" -f "$TELA_PROXY_HOME/compose.yaml" "$@"
68
+ }
69
+
70
+ ensure_network() {
71
+ docker network inspect "$TELA_PROXY_NETWORK" >/dev/null 2>&1 \
72
+ || docker network create --driver bridge "$TELA_PROXY_NETWORK" >/dev/null
73
+ }
74
+
75
+ # Prints the Compose project name for the current directory, honoring
76
+ # COMPOSE_PROJECT_NAME and COMPOSE_FILE from the environment or `.env`, since
77
+ # Compose resolves them itself. Prints nothing when there is no Compose project
78
+ # here. --no-env-resolution keeps a missing env_file from failing the lookup.
79
+ compose_project_name() {
80
+ # stderr goes to its own file: Compose can warn (an unset variable, an
81
+ # obsolete `version`) and still succeed, and the warning must not reach jq.
82
+ local output errors status=0
83
+ errors="$(mktemp)"
84
+ output="$(docker compose config --no-env-resolution --format json 2>"$errors")" || status=$?
85
+ if [ "$status" -eq 0 ]; then
86
+ rm -f "$errors"
87
+ jq -r '.name // empty' <<<"$output"
88
+ return
89
+ fi
90
+ output="$(cat "$errors")"
91
+ rm -f "$errors"
92
+ case "$output" in
93
+ *"no configuration file provided"*) return 0 ;;
94
+ esac
95
+ die "could not read the Compose project in $(pwd): $output"
96
+ }
97
+
98
+ # Prints COMPOSE_FILE as Compose sees it: from the environment, else from the
99
+ # `.env` in the current directory.
100
+ compose_file_setting() {
101
+ if [ -n "${COMPOSE_FILE:-}" ]; then
102
+ printf '%s\n' "$COMPOSE_FILE"
103
+ elif [ -f .env ]; then
104
+ sed -n -E 's/^[[:space:]]*(export[[:space:]]+)?COMPOSE_FILE[[:space:]]*=[[:space:]]*["'\'']?([^"'\'']*)["'\'']?[[:space:]]*$/\2/p' .env | tail -n 1
105
+ fi
106
+ }
107
+
108
+ # Prints the Compose project directory for the current directory: the
109
+ # directory of the first COMPOSE_FILE entry, resolved from here, else the
110
+ # nearest ancestor with a default-named file. This is the working_dir label
111
+ # Compose writes on the containers it creates.
112
+ compose_project_dir() {
113
+ local compose_file first
114
+ compose_file="$(compose_file_setting)"
115
+ if [ -n "$compose_file" ]; then
116
+ first="${compose_file%%"${COMPOSE_PATH_SEPARATOR:-:}"*}"
117
+ (cd "$(dirname "$first")" && pwd -P)
118
+ return
119
+ fi
120
+
121
+ local dir name
122
+ dir="$(pwd -P)"
123
+ while :; do
124
+ for name in compose.yaml compose.yml docker-compose.yaml docker-compose.yml; do
125
+ if [ -f "$dir/$name" ]; then
126
+ printf '%s\n' "$dir"
127
+ return
128
+ fi
129
+ done
130
+ [ "$dir" = "/" ] && return
131
+ dir="$(dirname "$dir")"
132
+ done
133
+ }
134
+
135
+ # Fails when containers of this Compose project were started from another
136
+ # directory that still exists: `up` would recreate them and `down -v` would
137
+ # delete their volumes. Containers from a directory that is gone (a removed
138
+ # worktree) only warn, and are taken over.
139
+ check_project_conflict() {
140
+ local project project_dir
141
+ project="$(compose_project_name)"
142
+ [ -n "$project" ] || return 0
143
+
144
+ # Compose found a project, so failing to place it must not skip the check.
145
+ project_dir="$(compose_project_dir)"
146
+ [ -n "$project_dir" ] \
147
+ || die "could not find the directory of Compose project '$project' from $(pwd)"
148
+
149
+ local dirs other other_real
150
+ dirs="$(docker ps -a \
151
+ --filter "label=com.docker.compose.project=$project" \
152
+ --format '{{ .Label "com.docker.compose.project.working_dir" }}' | sort -u)" \
153
+ || die "could not list the containers of Compose project '$project'"
154
+
155
+ while IFS= read -r other; do
156
+ [ -n "$other" ] || continue
157
+ if [ ! -d "$other" ]; then
158
+ warn "Compose project '$project' has containers from $other, which no longer exists. They will be recreated from $project_dir and keep their volumes."
159
+ continue
160
+ fi
161
+ other_real="$(cd "$other" && pwd -P)"
162
+ [ "$other_real" = "$project_dir" ] && continue
163
+ die "Compose project '$project' is already used by $other_real.
164
+ Starting it from $project_dir would recreate those containers and share their volumes.
165
+ Run this checkout under its own project name instead:
166
+ mise run tela-proxy:worktree [label] -- <command>"
167
+ done <<< "$dirs"
168
+ }
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bash
2
+ #MISE description="Stop the shared proxy. It stays stopped across reboots until tela-proxy runs again"
3
+
4
+ set -euo pipefail
5
+
6
+ # shellcheck source=lib.sh
7
+ source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
8
+
9
+ proxy_compose stop
@@ -0,0 +1,59 @@
1
+ # Static config for the shared local proxy started by `mise run tela-proxy`.
2
+ #
3
+ # Every Compose container on tela-network gets an HTTP route without declaring
4
+ # anything. The host comes from the Compose project and service labels:
5
+ #
6
+ # service auth-api, project auth -> api.auth.localhost
7
+ # service auth, project auth -> auth.localhost
8
+ # service dashboard, project auth -> dashboard.auth.localhost
9
+ # service auth-api, project auth_feat-x -> api.auth-feat-x.localhost
10
+ #
11
+ # The `_` in a project name separates the base from a worktree label, set by
12
+ # `mise run tela-proxy:worktree`. A container opts out with traefik.enable=false.
13
+ #
14
+ # A tela-proxy.host label replaces the host. `{project}` in it becomes the
15
+ # project's domain, worktree suffix included:
16
+ #
17
+ # tela-proxy.host: '{project}.localhost', project auth_feat-x -> auth-feat-x.localhost
18
+ # tela-proxy.host: 'app.{project}.localhost', project auth -> app.auth.localhost
19
+
20
+ entryPoints:
21
+ web:
22
+ address: ':80'
23
+
24
+ providers:
25
+ docker:
26
+ network: tela-network
27
+ exposedByDefault: true
28
+ defaultRule: |-
29
+ {{- $project := index .Labels "com.docker.compose.project" -}}
30
+ {{- $service := index .Labels "com.docker.compose.service" -}}
31
+ {{- $override := index .Labels "tela-proxy.host" -}}
32
+ {{- if or (not $project) (not $service) -}}
33
+ {{- if $override -}}
34
+ Host(`{{ $override }}`)
35
+ {{- else -}}
36
+ Host(`{{ normalize .Name }}.localhost`)
37
+ {{- end -}}
38
+ {{- else -}}
39
+ {{- $parts := splitList "_" $project -}}
40
+ {{- $base := first $parts -}}
41
+ {{- /* Every part is kept, so team_app_one and team_app_two stay apart. */ -}}
42
+ {{- $domain := join "-" $parts -}}
43
+ {{- $host := printf "%s.%s.localhost" $service $domain -}}
44
+ {{- if eq $service $base -}}
45
+ {{- $host = printf "%s.localhost" $domain -}}
46
+ {{- else if hasPrefix (printf "%s-" $base) $service -}}
47
+ {{- $host = printf "%s.%s.localhost" (trimPrefix (printf "%s-" $base) $service) $domain -}}
48
+ {{- end -}}
49
+ {{- if $override -}}
50
+ {{- $host = replace "{project}" $domain $override -}}
51
+ {{- end -}}
52
+ Host(`{{ $host }}`)
53
+ {{- end -}}
54
+
55
+ api:
56
+ dashboard: true
57
+
58
+ log:
59
+ level: INFO
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env bash
2
+ #MISE description="Run a command, or open a shell, with Compose isolated under a worktree project name"
3
+ #MISE alias=["worktree", "wt"]
4
+ #MISE interactive=true
5
+ #MISE tools={jq="1.8.2"}
6
+ #USAGE arg "[label]" help="Worktree label. Defaults to the name of the current git worktree."
7
+ #USAGE arg "[cmd]..." var=#true double_dash="required" help="Command to run. Opens $SHELL when omitted."
8
+
9
+ set -euo pipefail
10
+
11
+ # shellcheck source=lib.sh
12
+ source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
13
+
14
+ enter_caller_dir
15
+
16
+ # mise passes the label (when given) followed by the command, and a single empty
17
+ # argument when there is neither.
18
+ label="${usage_label:-}"
19
+ if [ -n "$label" ] && [ "${1:-}" = "$label" ]; then
20
+ shift
21
+ fi
22
+ if [ "$#" -eq 1 ] && [ -z "$1" ]; then
23
+ shift
24
+ fi
25
+
26
+ if [ -n "${TELA_PROXY_WORKTREE:-}" ]; then
27
+ die "already inside worktree '$TELA_PROXY_WORKTREE'. Run commands directly, or exit this shell first."
28
+ fi
29
+
30
+ if [ -z "$label" ]; then
31
+ git_dir="$(git rev-parse --absolute-git-dir 2>/dev/null)" \
32
+ || die "not inside a git repository. Pass a label: mise run tela-proxy:worktree <label>"
33
+ common_dir="$(cd "$(git rev-parse --git-common-dir)" && pwd -P)"
34
+ if [ "$(cd "$git_dir" && pwd -P)" = "$common_dir" ]; then
35
+ die "this is the main checkout, not a linked worktree. Pass a label: mise run tela-proxy:worktree <label>"
36
+ fi
37
+ label="$(basename "$(git rev-parse --show-toplevel)")"
38
+ fi
39
+
40
+ label="$(printf '%s' "$label" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9-]+/-/g; s/-+/-/g; s/^-//; s/-$//')"
41
+ [ -n "$label" ] || die "the worktree label is empty after keeping only a-z, 0-9 and '-'"
42
+
43
+ base="$(unset COMPOSE_PROJECT_NAME; compose_project_name)"
44
+ [ -n "$base" ] || die "no Compose project found in $(pwd)"
45
+ case "$base" in
46
+ *_*) die "Compose project name '$base' contains '_', which the proxy uses to separate the worktree label. Rename it with 'name:' in the Compose file." ;;
47
+ esac
48
+
49
+ export COMPOSE_PROJECT_NAME="${base}_${label}"
50
+ export TELA_LOCAL_DOMAIN="${base}-${label}.localhost"
51
+ export TELA_PROXY_WORKTREE="$label"
52
+
53
+ check_project_conflict
54
+
55
+ info "Compose project: $COMPOSE_PROJECT_NAME"
56
+ info "Hosts: http://<service>.$TELA_LOCAL_DOMAIN"
57
+
58
+ if [ "$#" -gt 0 ]; then
59
+ exec "$@"
60
+ fi
61
+
62
+ info "Opening ${SHELL:-bash} for worktree '$label'. Exit it to leave."
63
+ exec "${SHELL:-bash}"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meistrari/mise-en-place",
3
- "version": "2.19.1",
3
+ "version": "2.21.0",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",