shopify-tunnel-dev 0.1.0 → 0.2.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
@@ -1,47 +1,46 @@
1
1
  # shopify-tunnel-dev
2
2
 
3
- Chạy `shopify app dev` sau một Cloudflare named tunnel có **hostname cố định**. Cả team dùng chung
4
- một bộ credential, dev mới chỉ cần đặt `VITE_HOST` rồi chạy, không ai phải cấp tunnel hay cert.
3
+ Run `shopify app dev` behind a Cloudflare named tunnel with a **fixed hostname**. The team shares one
4
+ set of credentials; a new dev picks a hostname and runs. Nobody provisions tunnels or certificates.
5
5
 
6
- Cùng cơ chế và cùng tên biến với `@bss-sbc/shopify-dev`, nên repo đang dùng tool đó đổi sang được
7
- mà không phải sửa `.env` hay `account.json`. Không có dependency, cần Node >= 20.12.
6
+ Zero dependencies. Node >= 20.12.
8
7
 
9
- ## Cách chạy
8
+ ## How it works
10
9
 
11
- ```
12
- pnpm dev
13
- ├─ đọc web/.env hoặc .env, sau đó .cloudflared/account.json (account.json đè lên)
14
- ├─ tìm tunnel tên = phần đầu của VITE_HOST, chưa có thì tạo bằng secret chung
15
- ├─ trỏ DNS VITE_HOST về tunnel (không ghi đè record của tunnel khác)
16
- ├─ chạy connector về http://localhost:$VITE_PORT, chờ edge nhận kết nối
17
- └─ npx shopify app dev --tunnel-url https://$VITE_HOST:$VITE_PORT
18
- ```
10
+ 1. Reads `DEV_HOST` / `DEV_PORT` from `.env`, shared credentials from `.cloudflared/`.
11
+ 2. Finds the tunnel named after the first label of `DEV_HOST`, creates it on first run.
12
+ 3. Points DNS for `DEV_HOST` at the tunnel.
13
+ 4. Starts the connector to `http://localhost:$DEV_PORT`.
14
+ 5. Runs `shopify app dev --tunnel-url https://$DEV_HOST:$DEV_PORT`.
19
15
 
20
- HTTPS do Cloudflare xử lý, máy dev không cần cert TLS. Nếu không có `cloudflared` trên máy, lần
21
- chạy đầu tool tự tải bản mới nhất về `~/.cache/shopify-tunnel-dev/`. Muốn dùng binary khác thì đặt
22
- `CLOUDFLARED_BIN`.
16
+ TLS terminates at Cloudflare, so no local certificate is needed. `cloudflared` is used from `PATH`,
17
+ or downloaded to `~/.cache/shopify-tunnel-dev/` on first run (override with `CLOUDFLARED_BIN`).
23
18
 
24
- ## Setup một lần cho cả team
19
+ ## Team setup (once)
25
20
 
26
- 1. Dùng một Cloudflare account chỉ dành cho dev (lý do ở mục Bảo mật), thêm domain vào account đó.
27
- 2. Chạy `cloudflared tunnel login`, chọn zone. Lệnh này sinh `~/.cloudflared/cert.pem`.
28
- 3. Tạo secret: `openssl rand -base64 32`.
29
- 4. Lưu cả hai lên password manager (Passbolt...), kèm `account.json`:
21
+ Use a Cloudflare account dedicated to dev (see [Security](#security)).
22
+
23
+ ```bash
24
+ cloudflared tunnel login # pick your zone, writes ~/.cloudflared/cert.pem
25
+ openssl rand -base64 32 # tunnel secret
26
+ ```
27
+
28
+ Store `cert.pem` and this `account.json` in your password manager:
30
29
 
31
30
  ```json
32
31
  {
33
32
  "CF_ACCOUNT_TAG": "<account id>",
34
- "CF_SECRET_KEY": "<base64 32 byte>",
35
- "CF_HOST_PATTERN": "^dev-banner-[a-zA-Z0-9-]+\\.yourdomain\\.com$"
33
+ "CF_SECRET_KEY": "<base64 secret>",
34
+ "CF_HOST_PATTERN": "^dev-myapp-[a-zA-Z0-9-]+\\.example\\.com$"
36
35
  }
37
36
  ```
38
37
 
39
- `CF_HOST_PATTERN` là tuỳ chọn. Nên đặt để mỗi app có tiền tố riêng và không đè DNS của nhau.
38
+ `CF_HOST_PATTERN` is optional; use it to give each app its own prefix.
40
39
 
41
- ## Setup cho repo app
40
+ ## App repo setup
42
41
 
43
42
  ```bash
44
- pnpm add -D shopify-tunnel-dev # hoặc git URL / pnpm link khi chưa publish
43
+ pnpm add -D shopify-tunnel-dev
45
44
  ```
46
45
 
47
46
  ```json
@@ -51,68 +50,52 @@ pnpm add -D shopify-tunnel-dev # hoặc git URL / pnpm link khi chưa publ
51
50
  `.gitignore`:
52
51
 
53
52
  ```
54
- .cloudflared/*
55
- !.cloudflared/*.example
53
+ .cloudflared/
56
54
  ```
57
55
 
58
- `shopify.app.toml` của từng dev nên đặt `automatically_update_urls_on_dev = true` (mặc định), để
59
- Shopify CLI tự cập nhật URL app theo `VITE_HOST`. Mỗi dev nên có app dev riêng trên Partner, vì URL
60
- app nằm trên Shopify chứ không nằm ở tunnel.
61
-
62
- ## Dev mới
56
+ Give each dev their own app in the Partner Dashboard; the app URL lives on Shopify, not in the tunnel.
63
57
 
64
- ```bash
65
- # lấy từ password manager
66
- .cloudflared/cert.pem
67
- .cloudflared/account.json
68
- chmod 600 .cloudflared/*
69
- ```
58
+ ## Per dev
70
59
 
71
- `.env`:
60
+ Copy `cert.pem` and `account.json` from the password manager into `.cloudflared/`
61
+ (`chmod 600 .cloudflared/*`), then add to `.env`:
72
62
 
73
63
  ```
74
- VITE_HOST=dev-banner-huy.yourdomain.com
75
- VITE_PORT=9000
64
+ DEV_HOST=dev-myapp-alice.example.com
65
+ DEV_PORT=9000
76
66
  ```
77
67
 
78
- Chạy `pnpm dev`.
68
+ Run `pnpm dev`.
79
69
 
80
- ## Tuỳ chọn
81
-
82
- | Flag | Tác dụng |
70
+ | Variable | Meaning |
83
71
  |---|---|
84
- | `-q`, `--quick` | URL `*.trycloudflare.com` ngẫu nhiên, chỉ cần `VITE_PORT`, không cần credential |
85
- | `-f`, `--force` | Chiếm `VITE_HOST` dù DNS đang trỏ tunnel khác, hoặc tunnel đang có kết nối |
86
- | còn lại | Chuyển tiếp cho `shopify app dev` (`--reset`, `--store=...`) |
87
-
88
- ## Chống trùng hostname
72
+ | `DEV_HOST` | Public hostname for your dev app. Its first label is the tunnel name (3-63 chars). |
73
+ | `DEV_PORT` | Local port Shopify CLI listens on; the tunnel forwards to it. |
89
74
 
90
- `@bss-sbc/shopify-dev` luôn ghi đè DNS và không kiểm tra connector đang chạy, nên hai người đặt
91
- cùng `VITE_HOST` sẽ bị chia request ngẫu nhiên cho nhau. Tool này dừng với lỗi khi:
75
+ ## Options
92
76
 
93
- - tunnel của `VITE_HOST` đang có kết nối sống từ máy khác;
94
- - DNS của `VITE_HOST` đang trỏ về tunnel khác.
77
+ | Flag | Effect |
78
+ |---|---|
79
+ | `-q`, `--quick` | Random `*.trycloudflare.com` URL. Needs only `DEV_PORT`, no credentials. |
80
+ | `-f`, `--force` | Take over `DEV_HOST` even if it is in use. |
81
+ | anything else | Passed to `shopify app dev` (`--reset`, `--store=...`). |
95
82
 
96
- Nếu chính process của bạn vừa chết và tunnel còn giữ kết nối cũ, chờ vài giây hoặc chạy với `--force`.
83
+ ## Hostname collisions
97
84
 
98
- ## Khác với `@bss-sbc/shopify-dev`
85
+ The tool stops with an error instead of hijacking a hostname when:
99
86
 
100
- - Đọc `.env` bằng `util.parseEnv` của Node: dòng comment và giá trị có dấu `=` không còn làm hỏng.
101
- - `account.json` và `cert.pem` chỉ bắt buộc ở named mode, `--quick` không cần.
102
- - Credential của tunnel truyền qua env (`TUNNEL_CRED_CONTENTS`), không ghi file `<id>.json` ra đĩa.
103
- - Không có lệnh `start` (prod), vì prod không nên chạy qua tunnel dev.
87
+ - the tunnel already has a live connection from another machine, or
88
+ - the DNS record points at a different tunnel.
104
89
 
105
- ## Bảo mật
90
+ If your own process just died and left a stale connection, wait a few seconds or use `--force`.
106
91
 
107
- - `cert.pem` cho quyền tạo, xoá và trỏ DNS **mọi** tunnel trong account và zone đã chọn.
108
- - Ai có `CF_SECRET_KEY` thì chạy được connector cho mọi tunnel mà tool đã tạo.
92
+ ## Security
109
93
 
110
- Vì vậy dùng account Cloudflare riêng cho dev, và không commit hai giá trị này. Rotate: đổi secret
111
- trên password manager, xoá các tunnel cũ (`cloudflared tunnel delete`), lần `pnpm dev` sau sẽ tự
112
- tạo lại.
94
+ - `cert.pem` can create, delete and route **every** tunnel in the account and zone.
95
+ - `CF_SECRET_KEY` lets anyone run a connector for any tunnel this tool created.
113
96
 
114
- ## Test
97
+ Use a dedicated dev account and never commit either. Tunnel credentials are passed to `cloudflared`
98
+ via environment, never written to disk.
115
99
 
116
- ```bash
117
- node --test
118
- ```
100
+ To rotate: change the secret in the password manager, delete old tunnels
101
+ (`cloudflared tunnel delete <name>`); the next `pnpm dev` recreates them.
@@ -9,7 +9,7 @@ const USAGE = `Usage: shopify-tunnel-dev [dev] [options] [-- shopify app dev arg
9
9
 
10
10
  Options:
11
11
  -q, --quick Random *.trycloudflare.com URL, no Cloudflare credentials needed
12
- -f, --force Take over VITE_HOST even if its DNS record or tunnel is in use
12
+ -f, --force Take over DEV_HOST even if its DNS record or tunnel is in use
13
13
  -h, --help Show this help
14
14
 
15
15
  Any other argument (e.g. --reset, --store=foo) is passed to \`shopify app dev\`.`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shopify-tunnel-dev",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Run `shopify app dev` behind a fixed-hostname Cloudflare named tunnel that the whole team shares one credential set for",
5
5
  "type": "module",
6
6
  "bin": {
@@ -4,9 +4,8 @@ import { parseEnv } from 'node:util';
4
4
 
5
5
  export class ConfigError extends Error {}
6
6
 
7
- // Same file layout and variable names as @bss-sbc/shopify-dev, so an existing repo can switch
8
- // without renaming anything: web/.env wins over ./.env, and .cloudflared/account.json overrides
9
- // keys from the env file.
7
+ // Per-dev values come from web/.env (if present) or ./.env; team-shared values come from
8
+ // .cloudflared/account.json, which overrides keys from the env file.
10
9
  export function loadConfig({ cwd = process.cwd(), quick = false } = {}) {
11
10
  const envPath = [path.join(cwd, 'web/.env'), path.join(cwd, '.env')].find((p) => existsSync(p));
12
11
  const cloudflaredDir = path.join(cwd, '.cloudflared');
@@ -17,28 +16,28 @@ export function loadConfig({ cwd = process.cwd(), quick = false } = {}) {
17
16
  const account = existsSync(accountPath) ? readJson(accountPath) : {};
18
17
  const vars = { ...env, ...account };
19
18
 
20
- const port = Number(vars.VITE_PORT);
19
+ const port = Number(vars.DEV_PORT);
21
20
  if (!Number.isInteger(port) || port < 1 || port > 65535) {
22
- throw new ConfigError(`VITE_PORT must be a port number, got "${vars.VITE_PORT ?? ''}"`);
21
+ throw new ConfigError(`DEV_PORT must be a port number, got "${vars.DEV_PORT ?? ''}"`);
23
22
  }
24
23
  if (quick) return { quick, port };
25
24
 
26
- const missing = ['VITE_HOST', 'CF_ACCOUNT_TAG', 'CF_SECRET_KEY'].filter((key) => !vars[key]);
25
+ const missing = ['DEV_HOST', 'CF_ACCOUNT_TAG', 'CF_SECRET_KEY'].filter((key) => !vars[key]);
27
26
  if (missing.length) {
28
- throw new ConfigError(`Missing ${missing.join(', ')}. Put VITE_HOST in .env and CF_* in ${accountPath}`);
27
+ throw new ConfigError(`Missing ${missing.join(', ')}. Put DEV_HOST in .env and CF_* in ${accountPath}`);
29
28
  }
30
29
  if (!existsSync(certPath)) {
31
30
  throw new ConfigError(`Missing ${certPath} (origin cert from \`cloudflared tunnel login\`)`);
32
31
  }
33
32
 
34
- const host = vars.VITE_HOST.trim();
33
+ const host = vars.DEV_HOST.trim();
35
34
  if (vars.CF_HOST_PATTERN && !hostPattern(vars.CF_HOST_PATTERN).test(host)) {
36
- throw new ConfigError(`VITE_HOST "${host}" does not match CF_HOST_PATTERN ${vars.CF_HOST_PATTERN}`);
35
+ throw new ConfigError(`DEV_HOST "${host}" does not match CF_HOST_PATTERN ${vars.CF_HOST_PATTERN}`);
37
36
  }
38
- // Tunnel name = first DNS label, same rule as @bss-sbc/shopify-dev, so both tools reuse one tunnel.
37
+ // Tunnel name = first DNS label, so the same hostname always maps to the same tunnel on any machine.
39
38
  const tunnelName = host.split('.')[0];
40
39
  if (!host.includes('.') || !/^[a-zA-Z0-9-]{3,63}$/.test(tunnelName)) {
41
- throw new ConfigError(`VITE_HOST "${host}" must be a full hostname whose first label is 3-63 chars of [a-zA-Z0-9-]`);
40
+ throw new ConfigError(`DEV_HOST "${host}" must be a full hostname whose first label is 3-63 chars of [a-zA-Z0-9-]`);
42
41
  }
43
42
  // cloudflared rejects tunnel secrets that decode to fewer than 32 bytes.
44
43
  if (Buffer.from(vars.CF_SECRET_KEY, 'base64').length < 32) {
@@ -37,8 +37,8 @@ export async function startNamedTunnel(bin, cfg, { force = false } = {}) {
37
37
  // Two connectors on one tunnel = Cloudflare load-balances requests between two laptops.
38
38
  throw new Error(
39
39
  `Tunnel "${cfg.tunnelName}" already has ${tunnel.connections.length} live connection(s): ` +
40
- `someone else is probably running with VITE_HOST=${cfg.host}. ` +
41
- 'Pick another VITE_HOST, or pass --force if it is your own process that just died.',
40
+ `someone else is probably running with DEV_HOST=${cfg.host}. ` +
41
+ 'Pick another DEV_HOST, or pass --force if it is your own process that just died.',
42
42
  );
43
43
  }
44
44
 
@@ -84,7 +84,7 @@ async function routeDns(cf, tunnelId, host, force) {
84
84
  const detail = (err.stderr || err.message).trim();
85
85
  throw new Error(
86
86
  `Cannot point ${host} at tunnel ${tunnelId}: ${detail}\n` +
87
- 'If the record belongs to another tunnel, pick another VITE_HOST or pass --force to take it over.',
87
+ 'If the record belongs to another tunnel, pick another DEV_HOST or pass --force to take it over.',
88
88
  );
89
89
  }
90
90
  }