@hatchkit/dev-plugin-next 0.1.45

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.
@@ -0,0 +1,17 @@
1
+ export interface LocalDevOptions {
2
+ /** Override slug resolution. Useful when the package.json name doesn't
3
+ * match the desired subdomain (e.g. scoped packages, monorepo
4
+ * workspaces). When unset, the plugin walks up from cwd looking for
5
+ * `.hatchkit.json` (preferred) then package.json. */
6
+ slug?: string;
7
+ /** Default port to assume when the dev server hasn't told us yet.
8
+ * Next.js defaults to 3000; hatchkit's scaffold pins a random 3xxx
9
+ * per project, so callers usually override this. */
10
+ defaultPort?: number;
11
+ /** Disable banner output. Side effects (Caddy fragment write,
12
+ * tailscale probe) still run. */
13
+ silent?: boolean;
14
+ }
15
+ /** Wrap a Next.js config with hatchkit's local-dev integration. */
16
+ export declare function withLocalDev<TConfig>(nextConfig: TConfig, options?: LocalDevOptions): TConfig;
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAuDA,MAAM,WAAW,eAAe;IAC9B;;;0DAGsD;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;yDAEqD;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;sCACkC;IAClC,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,mEAAmE;AACnE,wBAAgB,YAAY,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAUjG"}
package/dist/index.js ADDED
@@ -0,0 +1,185 @@
1
+ /*
2
+ * @hatchkit/dev-plugin-next — Next.js integration for hatchkit's
3
+ * Tailscale-served dev URL flow.
4
+ *
5
+ * Usage:
6
+ *
7
+ * // next.config.ts
8
+ * import { withLocalDev } from "@hatchkit/dev-plugin-next";
9
+ *
10
+ * export default withLocalDev({
11
+ * reactStrictMode: true,
12
+ * // ...your normal Next config
13
+ * }, {
14
+ * slug: "raptor-runner", // optional override
15
+ * });
16
+ *
17
+ * What it does on `next dev` startup (only — `next build` / `next start`
18
+ * are pass-through no-ops):
19
+ *
20
+ * 1. Resolves the project's slug from (in order): the explicit
21
+ * `slug` option → `.hatchkit.json` (walking up from cwd) →
22
+ * package.json `name`.
23
+ * 2. Sniffs the dev port from `process.env.PORT` or `--port` argv
24
+ * (Next's own resolution path).
25
+ * 3. Writes ~/.config/dev/projects/<slug>.caddy with a
26
+ * `reverse_proxy 127.0.0.1:<port>` directive. Caddy's --watch
27
+ * picks the change up without a restart.
28
+ * 4. Probes `tailscale serve status` for the TCP=443 bridge that
29
+ * `hatchkit dev-setup init` registers once per machine.
30
+ * 5. Prints a one-line `Tailscale: https://<slug>.local.ricoslabs.com/`
31
+ * banner alongside Next's own startup output.
32
+ *
33
+ * Failure modes (no caddy fragment, no TCP bridge, tailscale offline)
34
+ * surface as inline hints. The plugin never throws — a broken local-dev
35
+ * pipeline should not break the dev server.
36
+ *
37
+ * Opt out entirely with `HATCHKIT_LOCAL_DEV=0`.
38
+ *
39
+ * The plugin does NOT touch Next's `basePath` / `assetPrefix` /
40
+ * routing. Caddy proxies verbatim and Next serves at `/` — HMR/WS
41
+ * paths stay unchanged, no app-side config needed.
42
+ */
43
+ import { isLocalDevActive, localDevUrl, readCaddyPort, resolveSlug, tailscaleIdentity, tailscaleServeTcpTarget, writeProjectFragment, } from "@hatchkit/dev-shared";
44
+ /** Wrap a Next.js config with hatchkit's local-dev integration. */
45
+ export function withLocalDev(nextConfig, options = {}) {
46
+ // Only fire side effects in `next dev`. Production builds + `next start`
47
+ // get the bare config back; same behaviour as if the plugin weren't there.
48
+ if (isDevCommand() && process.env.HATCHKIT_LOCAL_DEV !== "0") {
49
+ // Schedule async so we don't block Next's config loader. The Promise
50
+ // resolves into stdout — Next's logger has already cleared by the
51
+ // time we print, so our banner shows up below Next's "Ready" line.
52
+ void initLocalDev(options);
53
+ }
54
+ return nextConfig;
55
+ }
56
+ function isDevCommand() {
57
+ // Next sets NODE_ENV="development" during `next dev`. The argv check
58
+ // is the belt-and-braces fallback for harnesses (turbo, custom
59
+ // servers) that load the config without setting NODE_ENV first.
60
+ if (process.env.NODE_ENV === "development")
61
+ return true;
62
+ const argv = process.argv.slice(2);
63
+ return argv.includes("dev") || argv.includes("--dev");
64
+ }
65
+ async function initLocalDev(options) {
66
+ // Wait one tick so Next's own banner ("▲ Next.js …", "Ready in …ms")
67
+ // has flushed before we tack our line on. Picking a fixed delay is
68
+ // unavoidable here — Next 14/15 don't expose a "server-listening"
69
+ // hook to a config consumer. 1.5s is the empirically reliable point
70
+ // across Next 14, 15, and Turbopack on Rico's machine; if the user's
71
+ // dev server takes longer to come up our banner still prints, just
72
+ // mixed in with later HMR logs (harmless).
73
+ await sleep(1500);
74
+ const resolved = resolveSlug({ explicit: options.slug });
75
+ if (!resolved) {
76
+ log("[hatchkit] local-dev disabled: no slug found (set { slug } in withLocalDev or add a name to package.json).");
77
+ return;
78
+ }
79
+ const port = detectDevPort(options.defaultPort);
80
+ if (port === null) {
81
+ log("[hatchkit] local-dev disabled: couldn't detect dev port from PORT env or argv.");
82
+ return;
83
+ }
84
+ // Write/update the fragment first so Caddy reload picks up the right port
85
+ // before we probe for the bridge — saves the user one re-run when the
86
+ // port changed.
87
+ let fragmentResult = "unchanged";
88
+ try {
89
+ fragmentResult = writeProjectFragment(resolved.slug, port);
90
+ }
91
+ catch (err) {
92
+ log(`[hatchkit] local-dev: failed to write Caddy fragment (${err.message}). Banner suppressed.`);
93
+ return;
94
+ }
95
+ // Probe the host-wide bridge. Three outcomes drive the banner shape:
96
+ // 1. host-feature not active (no Caddyfile marker) → silent encouragement
97
+ // 2. active but no TCP=443 → red hint with one-liner fix
98
+ // 3. active + TCP=443 → green banner with the public URL
99
+ if (!isLocalDevActive()) {
100
+ log(`[hatchkit] local-dev: ${resolved.slug}.caddy ${fragmentResult}, but host bridge not configured.`);
101
+ log(" Run `hatchkit dev-setup init` once to enable Tailscale URLs.");
102
+ return;
103
+ }
104
+ const caddyPort = readCaddyPort();
105
+ if (caddyPort === null) {
106
+ log("[hatchkit] local-dev: Caddyfile is hatchkit-managed but has no https_port. Re-run `hatchkit dev-setup init`.");
107
+ return;
108
+ }
109
+ const tsId = await tailscaleIdentity();
110
+ if (!tsId) {
111
+ // Tailscale offline / not installed — local-dev only matters when
112
+ // the user wants peers to reach this machine. Treat as silent no-op.
113
+ return;
114
+ }
115
+ const tcpTarget = await tailscaleServeTcpTarget();
116
+ if (tcpTarget === null) {
117
+ log(`[hatchkit] local-dev: no port-443 bridge configured.`);
118
+ log(` Run once: tailscale serve --bg --tcp=443 tcp://localhost:${caddyPort}`);
119
+ return;
120
+ }
121
+ if (tcpTarget !== caddyPort) {
122
+ log(`[hatchkit] local-dev: tailscale serve points at localhost:${tcpTarget} but Caddy listens on ${caddyPort}.`);
123
+ log(" Run: `hatchkit dev-setup init` to reconcile.");
124
+ return;
125
+ }
126
+ if (!options.silent) {
127
+ log(`[hatchkit] Tailscale: ${localDevUrl(resolved.slug)} (slug from ${describeSource(resolved)})`);
128
+ }
129
+ }
130
+ function detectDevPort(fallback) {
131
+ // Order matches Next's own resolution: explicit env var, then -p / --port
132
+ // argv, then the framework's default.
133
+ const fromEnv = numericEnv(process.env.PORT);
134
+ if (fromEnv !== null)
135
+ return fromEnv;
136
+ const fromArgv = numericPortArgv(process.argv);
137
+ if (fromArgv !== null)
138
+ return fromArgv;
139
+ if (fallback !== undefined)
140
+ return fallback;
141
+ return 3000;
142
+ }
143
+ function numericEnv(v) {
144
+ if (!v)
145
+ return null;
146
+ const n = Number(v);
147
+ return Number.isFinite(n) && n > 0 ? n : null;
148
+ }
149
+ function numericPortArgv(argv) {
150
+ for (let i = 0; i < argv.length; i++) {
151
+ const a = argv[i];
152
+ if (a === "-p" || a === "--port") {
153
+ const next = argv[i + 1];
154
+ const n = next ? Number(next) : Number.NaN;
155
+ if (Number.isFinite(n) && n > 0)
156
+ return n;
157
+ }
158
+ else if (a?.startsWith("--port=")) {
159
+ const n = Number(a.slice("--port=".length));
160
+ if (Number.isFinite(n) && n > 0)
161
+ return n;
162
+ }
163
+ }
164
+ return null;
165
+ }
166
+ function describeSource(resolved) {
167
+ switch (resolved.source) {
168
+ case "explicit":
169
+ return "withLocalDev option";
170
+ case "manifest":
171
+ return ".hatchkit.json";
172
+ case "package-json":
173
+ return "package.json";
174
+ }
175
+ }
176
+ function sleep(ms) {
177
+ return new Promise((resolve) => setTimeout(resolve, ms));
178
+ }
179
+ function log(line) {
180
+ // stderr so HMR/output paths stay on stdout and tools that pipe Next's
181
+ // output (e.g. concurrently, mprocs) don't merge our banner into their
182
+ // stdout buffer in surprising ways.
183
+ process.stderr.write(`${line}\n`);
184
+ }
185
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,OAAO,EACL,gBAAgB,EAChB,WAAW,EAEX,aAAa,EACb,WAAW,EAEX,iBAAiB,EACjB,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAiB9B,mEAAmE;AACnE,MAAM,UAAU,YAAY,CAAU,UAAmB,EAAE,UAA2B,EAAE;IACtF,yEAAyE;IACzE,2EAA2E;IAC3E,IAAI,YAAY,EAAE,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,KAAK,GAAG,EAAE,CAAC;QAC7D,qEAAqE;QACrE,kEAAkE;QAClE,mEAAmE;QACnE,KAAK,YAAY,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,SAAS,YAAY;IACnB,qEAAqE;IACrE,+DAA+D;IAC/D,gEAAgE;IAChE,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,aAAa;QAAE,OAAO,IAAI,CAAC;IACxD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;AACxD,CAAC;AAED,KAAK,UAAU,YAAY,CAAC,OAAwB;IAClD,qEAAqE;IACrE,mEAAmE;IACnE,kEAAkE;IAClE,oEAAoE;IACpE,qEAAqE;IACrE,mEAAmE;IACnE,2CAA2C;IAC3C,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;IAElB,MAAM,QAAQ,GAAG,WAAW,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IACzD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,GAAG,CAAC,4GAA4G,CAAC,CAAC;QAClH,OAAO;IACT,CAAC;IAED,MAAM,IAAI,GAAG,aAAa,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAChD,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,GAAG,CAAC,gFAAgF,CAAC,CAAC;QACtF,OAAO;IACT,CAAC;IAED,0EAA0E;IAC1E,sEAAsE;IACtE,gBAAgB;IAChB,IAAI,cAAc,GAAkD,WAAW,CAAC;IAChF,IAAI,CAAC;QACH,cAAc,GAAG,oBAAoB,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC7D,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,GAAG,CACD,yDAA0D,GAAa,CAAC,OAAO,uBAAuB,CACvG,CAAC;QACF,OAAO;IACT,CAAC;IAED,qEAAqE;IACrE,4EAA4E;IAC5E,2DAA2D;IAC3D,2DAA2D;IAC3D,IAAI,CAAC,gBAAgB,EAAE,EAAE,CAAC;QACxB,GAAG,CACD,yBAAyB,QAAQ,CAAC,IAAI,UAAU,cAAc,mCAAmC,CAClG,CAAC;QACF,GAAG,CAAC,0EAA0E,CAAC,CAAC;QAChF,OAAO;IACT,CAAC;IAED,MAAM,SAAS,GAAG,aAAa,EAAE,CAAC;IAClC,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;QACvB,GAAG,CACD,8GAA8G,CAC/G,CAAC;QACF,OAAO;IACT,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,iBAAiB,EAAE,CAAC;IACvC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,kEAAkE;QAClE,qEAAqE;QACrE,OAAO;IACT,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,uBAAuB,EAAE,CAAC;IAClD,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;QACvB,GAAG,CAAC,sDAAsD,CAAC,CAAC;QAC5D,GAAG,CAAC,wEAAwE,SAAS,EAAE,CAAC,CAAC;QACzF,OAAO;IACT,CAAC;IACD,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,GAAG,CACD,6DAA6D,SAAS,yBAAyB,SAAS,GAAG,CAC5G,CAAC;QACF,GAAG,CAAC,0DAA0D,CAAC,CAAC;QAChE,OAAO;IACT,CAAC;IAED,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;QACpB,GAAG,CAAC,yBAAyB,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,gBAAgB,cAAc,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IACtG,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,QAAiB;IACtC,0EAA0E;IAC1E,sCAAsC;IACtC,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC7C,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,OAAO,CAAC;IACrC,MAAM,QAAQ,GAAG,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,IAAI,QAAQ,KAAK,IAAI;QAAE,OAAO,QAAQ,CAAC;IACvC,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAC;IAC5C,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,UAAU,CAAC,CAAqB;IACvC,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACpB,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAChD,CAAC;AAED,SAAS,eAAe,CAAC,IAAc;IACrC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,QAAQ,EAAE,CAAC;YACjC,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACzB,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;YAC3C,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;gBAAE,OAAO,CAAC,CAAC;QAC5C,CAAC;aAAM,IAAI,CAAC,EAAE,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YACpC,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;YAC5C,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;gBAAE,OAAO,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,cAAc,CAAC,QAAsB;IAC5C,QAAQ,QAAQ,CAAC,MAAM,EAAE,CAAC;QACxB,KAAK,UAAU;YACb,OAAO,qBAAqB,CAAC;QAC/B,KAAK,UAAU;YACb,OAAO,gBAAgB,CAAC;QAC1B,KAAK,cAAc;YACjB,OAAO,cAAc,CAAC;IAC1B,CAAC;AACH,CAAC;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,SAAS,GAAG,CAAC,IAAY;IACvB,uEAAuE;IACvE,uEAAuE;IACvE,oCAAoC;IACpC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;AACpC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@hatchkit/dev-plugin-next",
3
+ "version": "0.1.45",
4
+ "description": "Next.js plugin for hatchkit's local-dev integration. Writes the project's Caddy fragment on `next dev` startup and prints a Tailscale-URL banner alongside Next's own startup output.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md"
17
+ ],
18
+ "dependencies": {
19
+ "@hatchkit/dev-shared": "0.1.45"
20
+ },
21
+ "peerDependencies": {
22
+ "next": ">=14"
23
+ },
24
+ "peerDependenciesMeta": {
25
+ "next": {
26
+ "optional": false
27
+ }
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^22.0.0",
31
+ "next": "^15.1.0",
32
+ "typescript": "^5.6.0"
33
+ },
34
+ "scripts": {
35
+ "build": "tsc",
36
+ "typecheck": "tsc --noEmit"
37
+ }
38
+ }