@tnldotdev/tnl 0.1.0-rc.17 → 0.1.0-rc.20
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 +112 -43
- package/dist/index.d.ts +2 -10
- package/dist/internal/dev.d.ts +3 -8
- package/dist/internal/dev.js +12 -53
- package/dist/internal/runtime.d.ts +3 -0
- package/dist/internal/runtime.js +6 -1
- package/dist/next.js +3 -13
- package/lib/launcher.mjs +7 -6
- package/lib/native-targets.mjs +12 -0
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -15,65 +15,125 @@ platform. It has no install script and does not download executable code from a
|
|
|
15
15
|
third-party host. Node.js 22.18 or newer is required by the launcher,
|
|
16
16
|
integrations, and TypeScript configuration loader.
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
The native CLI enables pseudonymous telemetry by default. Use
|
|
19
|
+
`TNL_NO_TELEMETRY=true` or `--no-telemetry` to disable it; see the
|
|
20
|
+
[telemetry disclosure](../../README.md#telemetry).
|
|
21
|
+
|
|
22
|
+
## Configuration
|
|
23
|
+
|
|
24
|
+
Run `tnl init` to add the package and missing project/framework configuration.
|
|
25
|
+
It preserves existing Next.js, Vite, and TypeScript configuration and reports
|
|
26
|
+
remaining integration actions. Configure an integration below before starting
|
|
27
|
+
a framework-discovered tunnel.
|
|
19
28
|
|
|
20
|
-
|
|
21
|
-
module during development:
|
|
29
|
+
For a project with existing `apps/api` and `apps/web` directories:
|
|
22
30
|
|
|
23
31
|
```ts
|
|
24
|
-
|
|
32
|
+
// tnl.config.ts
|
|
33
|
+
import { defineConfig } from "@tnldotdev/tnl/config";
|
|
25
34
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
35
|
+
export default defineConfig({
|
|
36
|
+
dev: { command: ["pnpm", "dev"] },
|
|
37
|
+
services: {
|
|
38
|
+
api: { directory: "apps/api", publish: { target: 3001 } },
|
|
39
|
+
web: { directory: "apps/web" },
|
|
40
|
+
},
|
|
41
|
+
});
|
|
32
42
|
```
|
|
33
43
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
false. If generated metadata is absent, `tnl` is undefined.
|
|
44
|
+
Each service overrides root defaults. Commands run in the service directory,
|
|
45
|
+
which must exist within the project root. Choose explicitly when several
|
|
46
|
+
services exist: `tnl dev web` or `tnl publish api`. A single service is selected
|
|
47
|
+
automatically. Service names are 1-32 lowercase ASCII letters/digits/hyphens,
|
|
48
|
+
begin with a letter, and cannot end with a hyphen; at most 32 services are allowed.
|
|
40
49
|
|
|
41
|
-
|
|
42
|
-
application's TypeScript inputs to get exact service keys and literal hostname,
|
|
43
|
-
URL, and common member-namespace values. Without the generated declaration,
|
|
44
|
-
the root module uses safe broad string and service-record types.
|
|
50
|
+
`defineConfig` also accepts a synchronous or asynchronous factory:
|
|
45
51
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
```ts
|
|
53
|
+
export default defineConfig(({ worktree }) => ({
|
|
54
|
+
tunnel: { subdomain: worktree.label },
|
|
55
|
+
dev: { command: ["pnpm", "dev"], startupTimeout: "90s" },
|
|
56
|
+
}));
|
|
57
|
+
```
|
|
49
58
|
|
|
50
|
-
|
|
59
|
+
`worktree.label` combines a readable name with an eight-character hash, stable
|
|
60
|
+
for one client state directory but distinct across worktrees and installations.
|
|
61
|
+
Its private random input is not exposed. Factories receive deeply frozen
|
|
62
|
+
`cwd`, `env`, and `worktree` context. `cwd` is the invocation directory; Node
|
|
63
|
+
executes from the configuration directory. Both the loader environment and
|
|
64
|
+
`context.env` omit `TNL_*` and `TNLD_*`, not arbitrary application secrets.
|
|
65
|
+
|
|
66
|
+
Configuration executes trusted project code, not a sandbox. `defineConfig` is
|
|
67
|
+
type assistance, not runtime validation; the native client validates the result.
|
|
68
|
+
TypeScript uses camel-case fields and implicitly version 1. Static YAML/JSON
|
|
69
|
+
requires `version: 1` and snake-case fields; see
|
|
70
|
+
[discovery and precedence](../../README.md#project-configuration) and the
|
|
71
|
+
[JSON Schema](https://tnl.dev/schema/v1.json).
|
|
72
|
+
|
|
73
|
+
`tunnel.host` and `tunnel.subdomain` are alternatives, as are `public: true` and
|
|
74
|
+
`allowIP`. Service overrides replace the corresponding inherited alternative.
|
|
75
|
+
`dev.port` forces the exact listener port. `dev.startupTimeout` defaults to two
|
|
76
|
+
minutes and must be positive and at most ten minutes. `dev.command` is an
|
|
77
|
+
argument array, not a shell command string.
|
|
51
78
|
|
|
52
|
-
|
|
79
|
+
## Project Runtime
|
|
80
|
+
|
|
81
|
+
Authenticate to the configured server, then generate metadata from the project
|
|
82
|
+
root:
|
|
83
|
+
|
|
84
|
+
```console
|
|
85
|
+
tnl login https://control.tnl.example.com --token
|
|
86
|
+
tnl config generate
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Generation resolves the authenticated membership and ready domain for the root
|
|
90
|
+
and every service, including services with server/team overrides. It writes
|
|
91
|
+
`.tnl/project.json` and `.tnl/project.d.ts`. Regenerate after changing service,
|
|
92
|
+
server, team, or domain configuration; `tnl dev` also generates metadata when
|
|
93
|
+
project configuration is present. Keep `.tnl` ignored by Git.
|
|
94
|
+
|
|
95
|
+
Add the declaration to the application's existing TypeScript `include` list.
|
|
96
|
+
For an app at the project root, include `.tnl/project.d.ts`; for the example's
|
|
97
|
+
`apps/web/tsconfig.json`, include `../../.tnl/project.d.ts`:
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"include": ["**/*.ts", "**/*.tsx", "../../.tnl/project.d.ts"]
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Preserve other framework-required includes. The augmentation gives exact
|
|
106
|
+
service keys and literal hostname/URL types. Without it, types remain broad;
|
|
107
|
+
check that a service exists before accessing it.
|
|
108
|
+
|
|
109
|
+
The integrations expose this browser-safe runtime during development:
|
|
53
110
|
|
|
54
111
|
```ts
|
|
55
|
-
|
|
56
|
-
import { defineConfig } from "@tnldotdev/tnl/config";
|
|
112
|
+
import { tnl } from "@tnldotdev/tnl";
|
|
57
113
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
114
|
+
if (tnl) {
|
|
115
|
+
tnl.memberNamespace;
|
|
116
|
+
tnl.services.api.hostname;
|
|
117
|
+
tnl.services.api.url;
|
|
118
|
+
tnl.runningUnderTnlDev;
|
|
119
|
+
}
|
|
62
120
|
```
|
|
63
121
|
|
|
64
|
-
`
|
|
65
|
-
|
|
66
|
-
installations. Its private random input is not exposed to the configuration
|
|
67
|
-
factory.
|
|
122
|
+
The `tnl` value is undefined during builds, previews, and production. During
|
|
123
|
+
development, behavior depends on discovery:
|
|
68
124
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
125
|
+
| Development context | Runtime and network behavior |
|
|
126
|
+
| ---------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
127
|
+
| No generated metadata or explicit bootstrap | `tnl` is undefined; no tunnel configuration |
|
|
128
|
+
| Metadata without a matching development socket | Frozen metadata, `runningUnderTnlDev: false`; no tunnel configuration |
|
|
129
|
+
| Explicit bootstrap or discovered matching `tnl dev` socket | Assigned metadata, `runningUnderTnlDev: true`; configure and register the actual listener |
|
|
72
130
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
131
|
+
Socket discovery supports starting `tnl dev web` first and the framework from
|
|
132
|
+
its service directory in another terminal. Discovery happens when framework
|
|
133
|
+
configuration loads, not continuously. Metadata is a snapshot, not route
|
|
134
|
+
readiness or service health. Values are deeply frozen; malformed metadata throws
|
|
135
|
+
rather than silently becoming undefined. `tnl publish` cannot inject metadata
|
|
136
|
+
into an already-running application.
|
|
77
137
|
|
|
78
138
|
## Next.js
|
|
79
139
|
|
|
@@ -115,7 +175,7 @@ During `tnl dev`, the plugin allows the assigned hostname, injects the project
|
|
|
115
175
|
runtime, and registers Vite's actual post-bind target. It preserves Vite's
|
|
116
176
|
default or configured host and port behavior, including occupied-port retries;
|
|
117
177
|
a port forced by `tnl dev --port` remains exact. User host settings can
|
|
118
|
-
independently expose Vite on the LAN.
|
|
178
|
+
independently expose Vite on the LAN. Without a development socket the plugin
|
|
119
179
|
only injects generated metadata. Builds and previews remain inert. Vite 6.0.9
|
|
120
180
|
or newer is supported.
|
|
121
181
|
|
|
@@ -123,5 +183,14 @@ Next.js and Vite are optional peers, so only the framework already used by the
|
|
|
123
183
|
project is required. Keep hostname, policy, server, service, and command
|
|
124
184
|
settings in project configuration rather than passing integration options.
|
|
125
185
|
|
|
186
|
+
### Listener Requirements
|
|
187
|
+
|
|
188
|
+
The target must be loopback HTTP. Wildcard bindings (`0.0.0.0` or `::`) allow
|
|
189
|
+
LAN exposure while tnl connects through loopback; binding only a specific LAN
|
|
190
|
+
address is rejected. Vite middleware mode has no supported listening target.
|
|
191
|
+
Next.js must report an HTTP listener origin. Forced ports are checked against
|
|
192
|
+
the actual listener, not assumed from configuration. Vite's `allowedHosts: true`
|
|
193
|
+
is preserved; the integration does not re-enable host filtering you disabled.
|
|
194
|
+
|
|
126
195
|
Deploy `tnld` with the release container or install it from Homebrew or a
|
|
127
196
|
release archive.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
readonly memberNamespace: string;
|
|
3
|
-
readonly hostname: string;
|
|
4
|
-
readonly url: `https://${string}`;
|
|
5
|
-
}
|
|
6
|
-
interface BroadTnlProject {
|
|
7
|
-
readonly memberNamespace: string;
|
|
8
|
-
readonly services: Readonly<Record<string, TnlServiceMetadata>>;
|
|
9
|
-
}
|
|
1
|
+
import { type ProjectMetadata } from "./internal/runtime.js";
|
|
10
2
|
/** Augmented by the `.tnl/project.d.ts` file generated by the tnl client. */
|
|
11
3
|
export interface TnlProjectMetadata {
|
|
12
4
|
}
|
|
13
|
-
type RegisteredTnlProject = keyof TnlProjectMetadata extends never ?
|
|
5
|
+
type RegisteredTnlProject = keyof TnlProjectMetadata extends never ? ProjectMetadata : Readonly<TnlProjectMetadata>;
|
|
14
6
|
type TnlProject = RegisteredTnlProject & {
|
|
15
7
|
readonly runningUnderTnlDev: boolean;
|
|
16
8
|
};
|
package/dist/internal/dev.d.ts
CHANGED
|
@@ -4,17 +4,10 @@ export interface TnlDevBootstrap {
|
|
|
4
4
|
readonly port?: number;
|
|
5
5
|
readonly socket: string;
|
|
6
6
|
}
|
|
7
|
-
export interface
|
|
8
|
-
readonly memberNamespace: string;
|
|
9
|
-
readonly hostname: string;
|
|
10
|
-
readonly url: `https://${string}`;
|
|
11
|
-
}
|
|
12
|
-
export interface ProjectDocument {
|
|
13
|
-
readonly memberNamespace: string;
|
|
7
|
+
export interface ProjectDocument extends ProjectMetadata {
|
|
14
8
|
readonly projectRoot: string;
|
|
15
9
|
readonly runningUnderTnlDev: boolean;
|
|
16
10
|
readonly serviceDirectories: Readonly<Record<string, string>>;
|
|
17
|
-
readonly services: Readonly<Record<string, ProjectDocumentService>>;
|
|
18
11
|
readonly version: 1;
|
|
19
12
|
}
|
|
20
13
|
export interface ProjectDiscovery {
|
|
@@ -44,3 +37,5 @@ export declare function registerLocalTarget(assignment: TnlTunnelAssignment, tar
|
|
|
44
37
|
export declare function runtimePayload(project: ProjectMetadata, runningUnderTnlDev: boolean): string;
|
|
45
38
|
export declare function discoverProject(cwd: string): ProjectDiscovery | null;
|
|
46
39
|
export declare function socketIdentity(projectRoot: string, service: string | null): string;
|
|
40
|
+
/** Parses digit-only listener ports in [1, 65535], preserving source-specific diagnostics. */
|
|
41
|
+
export declare function parseListenerPort(value: string, source: string): number;
|
package/dist/internal/dev.js
CHANGED
|
@@ -4,13 +4,11 @@ import * as http from "node:http";
|
|
|
4
4
|
import { isIP } from "node:net";
|
|
5
5
|
import * as os from "node:os";
|
|
6
6
|
import * as path from "node:path";
|
|
7
|
-
import { exactKeys, parseProjectMetadata, parseProjectRuntime, record, requiredHostname, serializeRuntimePayload, } from "./runtime.js";
|
|
7
|
+
import { exactKeys, parseProjectMetadata, parseProjectRuntime, record, requiredHostname, serializeRuntimePayload, validServiceName, } from "./runtime.js";
|
|
8
8
|
const protocolVersion = "1";
|
|
9
9
|
const maximumDocumentBytes = 64 * 1024;
|
|
10
10
|
const maximumResponseBytes = 64 * 1024;
|
|
11
|
-
const maximumServices = 32;
|
|
12
11
|
const registrationTimeoutMilliseconds = 10 * 60 * 1000;
|
|
13
|
-
const serviceNamePattern = /^[a-z](?:[a-z0-9-]{0,30}[a-z0-9])?$/;
|
|
14
12
|
export function readDevelopmentContext(environment = process.env, cwd = process.cwd()) {
|
|
15
13
|
const protocol = environment.TNL_DEV_PROTOCOL;
|
|
16
14
|
if (protocol !== undefined) {
|
|
@@ -112,7 +110,7 @@ function parseBootstrapEnvironment(environment) {
|
|
|
112
110
|
throw new Error(`TNL_DEV_SOCKET is required by tnl dev protocol ${protocolVersion}`);
|
|
113
111
|
}
|
|
114
112
|
const rawPort = environment.TNL_DEV_PORT;
|
|
115
|
-
const port = rawPort === undefined ? undefined :
|
|
113
|
+
const port = rawPort === undefined ? undefined : parseListenerPort(rawPort, "TNL_DEV_PORT");
|
|
116
114
|
return Object.freeze({ port, socket });
|
|
117
115
|
}
|
|
118
116
|
function discoverDevSocket(discovery, environment) {
|
|
@@ -211,64 +209,27 @@ function parseProjectDocumentValue(value, description, projectRoot) {
|
|
|
211
209
|
if (object.runningUnderTnlDev) {
|
|
212
210
|
throw new Error(`${description} cannot be marked as running under tnl dev`);
|
|
213
211
|
}
|
|
214
|
-
const
|
|
215
|
-
const serviceValues = record(object.services, `${description} services`);
|
|
212
|
+
const project = parseProjectMetadata({ memberNamespace: object.memberNamespace, services: object.services }, description);
|
|
216
213
|
const directoryValues = record(object.serviceDirectories, `${description} service directories`);
|
|
217
|
-
const
|
|
218
|
-
if (
|
|
219
|
-
|
|
220
|
-
}
|
|
221
|
-
if (Object.keys(directoryValues).length !== entries.length ||
|
|
222
|
-
entries.some(([name]) => !Object.hasOwn(directoryValues, name))) {
|
|
214
|
+
const names = Object.keys(project.services);
|
|
215
|
+
if (Object.keys(directoryValues).length !== names.length ||
|
|
216
|
+
names.some((name) => !Object.hasOwn(directoryValues, name))) {
|
|
223
217
|
throw new Error(`${description} requires one directory for every service`);
|
|
224
218
|
}
|
|
225
|
-
const services = {};
|
|
226
219
|
const serviceDirectories = {};
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
if (!validServiceName(name)) {
|
|
230
|
-
throw new Error(`${description} contains an invalid service name ${JSON.stringify(name)}`);
|
|
231
|
-
}
|
|
232
|
-
const service = record(value, `${description} service ${JSON.stringify(name)}`);
|
|
233
|
-
exactKeys(service, ["hostname", "memberNamespace", "url"], `${description} service ${JSON.stringify(name)}`);
|
|
234
|
-
const directory = relativeDirectory(directoryValues[name], `${description} service ${JSON.stringify(name)} directory`);
|
|
235
|
-
const serviceMemberNamespace = requiredHostname(service.memberNamespace, `${description} service ${JSON.stringify(name)} member namespace`);
|
|
236
|
-
const hostname = requiredHostname(service.hostname, `${description} service ${JSON.stringify(name)} hostname`);
|
|
237
|
-
if (service.url !== `https://${hostname}`) {
|
|
238
|
-
throw new Error(`${description} service ${JSON.stringify(name)} has an invalid URL`);
|
|
239
|
-
}
|
|
240
|
-
if (hostnames.has(hostname)) {
|
|
241
|
-
throw new Error(`${description} contains duplicate service hostname ${hostname}`);
|
|
242
|
-
}
|
|
243
|
-
hostnames.add(hostname);
|
|
244
|
-
services[name] = Object.freeze({
|
|
245
|
-
memberNamespace: serviceMemberNamespace,
|
|
246
|
-
hostname,
|
|
247
|
-
url: service.url,
|
|
248
|
-
});
|
|
249
|
-
serviceDirectories[name] = directory;
|
|
220
|
+
for (const name of names) {
|
|
221
|
+
serviceDirectories[name] = relativeDirectory(directoryValues[name], `${description} service ${JSON.stringify(name)} directory`);
|
|
250
222
|
}
|
|
251
223
|
return Object.freeze({
|
|
252
|
-
|
|
224
|
+
...project,
|
|
253
225
|
projectRoot,
|
|
254
226
|
runningUnderTnlDev: object.runningUnderTnlDev,
|
|
255
227
|
serviceDirectories: Object.freeze(serviceDirectories),
|
|
256
|
-
services: Object.freeze(services),
|
|
257
228
|
version: 1,
|
|
258
229
|
});
|
|
259
230
|
}
|
|
260
231
|
function projectMetadata(document) {
|
|
261
|
-
return
|
|
262
|
-
memberNamespace: document.memberNamespace,
|
|
263
|
-
services: Object.fromEntries(Object.entries(document.services).map(([name, service]) => [
|
|
264
|
-
name,
|
|
265
|
-
{
|
|
266
|
-
hostname: service.hostname,
|
|
267
|
-
memberNamespace: service.memberNamespace,
|
|
268
|
-
url: service.url,
|
|
269
|
-
},
|
|
270
|
-
])),
|
|
271
|
-
}, "tnl project metadata");
|
|
232
|
+
return Object.freeze({ memberNamespace: document.memberNamespace, services: document.services });
|
|
272
233
|
}
|
|
273
234
|
function selectService(document, cwd) {
|
|
274
235
|
const absoluteCwd = absoluteNormalizedPath(cwd, "working directory");
|
|
@@ -398,7 +359,8 @@ function absoluteNormalizedPath(value, description) {
|
|
|
398
359
|
}
|
|
399
360
|
return value;
|
|
400
361
|
}
|
|
401
|
-
|
|
362
|
+
/** Parses digit-only listener ports in [1, 65535], preserving source-specific diagnostics. */
|
|
363
|
+
export function parseListenerPort(value, source) {
|
|
402
364
|
if (!/^[0-9]+$/.test(value)) {
|
|
403
365
|
throw new Error(`${source} must be a port between 1 and 65535`);
|
|
404
366
|
}
|
|
@@ -408,9 +370,6 @@ function parsePort(value, source) {
|
|
|
408
370
|
}
|
|
409
371
|
return port;
|
|
410
372
|
}
|
|
411
|
-
function validServiceName(value) {
|
|
412
|
-
return typeof value === "string" && serviceNamePattern.test(value);
|
|
413
|
-
}
|
|
414
373
|
function pathWithin(value, root) {
|
|
415
374
|
const relative = path.relative(root, value);
|
|
416
375
|
return relative === "" || (relative !== ".." && !relative.startsWith(`..${path.sep}`));
|
|
@@ -10,7 +10,10 @@ export interface ProjectMetadata {
|
|
|
10
10
|
export interface ProjectRuntime extends ProjectMetadata {
|
|
11
11
|
readonly runningUnderTnlDev: boolean;
|
|
12
12
|
}
|
|
13
|
+
/** Validates and deep-freezes public metadata, excluding runtime flags and Node-only discovery fields. */
|
|
13
14
|
export declare function parseProjectMetadata(value: unknown, description: string): ProjectMetadata;
|
|
15
|
+
/** Matches the native client's 1-32 byte ASCII service-name grammar without normalization. */
|
|
16
|
+
export declare function validServiceName(value: unknown): value is string;
|
|
14
17
|
export declare function parseRuntimePayload(serialized: string | undefined): ProjectRuntime | undefined;
|
|
15
18
|
export declare function parseProjectRuntime(value: unknown, description: string): ProjectRuntime;
|
|
16
19
|
export declare function serializeRuntimePayload(project: ProjectMetadata, runningUnderTnlDev: boolean): string;
|
package/dist/internal/runtime.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const maximumRuntimeBytes = 64 * 1024;
|
|
2
2
|
const maximumServices = 32;
|
|
3
3
|
const serviceNamePattern = /^[a-z](?:[a-z0-9-]{0,30}[a-z0-9])?$/;
|
|
4
|
+
/** Validates and deep-freezes public metadata, excluding runtime flags and Node-only discovery fields. */
|
|
4
5
|
export function parseProjectMetadata(value, description) {
|
|
5
6
|
const object = record(value, description);
|
|
6
7
|
exactKeys(object, ["memberNamespace", "services"], description);
|
|
@@ -13,7 +14,7 @@ export function parseProjectMetadata(value, description) {
|
|
|
13
14
|
const services = {};
|
|
14
15
|
const hostnames = new Set();
|
|
15
16
|
for (const [name, value] of entries) {
|
|
16
|
-
if (!
|
|
17
|
+
if (!validServiceName(name)) {
|
|
17
18
|
throw new Error(`${description} contains an invalid service name ${JSON.stringify(name)}`);
|
|
18
19
|
}
|
|
19
20
|
const service = record(value, `${description} service ${JSON.stringify(name)}`);
|
|
@@ -38,6 +39,10 @@ export function parseProjectMetadata(value, description) {
|
|
|
38
39
|
services: Object.freeze(services),
|
|
39
40
|
});
|
|
40
41
|
}
|
|
42
|
+
/** Matches the native client's 1-32 byte ASCII service-name grammar without normalization. */
|
|
43
|
+
export function validServiceName(value) {
|
|
44
|
+
return typeof value === "string" && serviceNamePattern.test(value);
|
|
45
|
+
}
|
|
41
46
|
export function parseRuntimePayload(serialized) {
|
|
42
47
|
if (serialized === undefined) {
|
|
43
48
|
return undefined;
|
package/dist/next.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { canonicalLoopbackTarget, readDevelopmentContext, registerLocalTarget, requestTunnelAssignment, runtimePayload, } from "./internal/dev.js";
|
|
1
|
+
import { canonicalLoopbackTarget, parseListenerPort, readDevelopmentContext, registerLocalTarget, requestTunnelAssignment, runtimePayload, } from "./internal/dev.js";
|
|
2
2
|
const developmentServerPhase = "phase-development-server";
|
|
3
3
|
const runtimeEnvironmentName = "TNL_PROJECT_RUNTIME";
|
|
4
4
|
/** Adds tnl project metadata and safe `tnl dev` routing to a Next.js development server. */
|
|
@@ -63,23 +63,13 @@ function nextTarget(environment) {
|
|
|
63
63
|
catch (error) {
|
|
64
64
|
throw new Error("Next.js reported an invalid development listener", { cause: error });
|
|
65
65
|
}
|
|
66
|
-
const port =
|
|
66
|
+
const port = parseListenerPort(origin.port, "Next.js listener");
|
|
67
67
|
const reportedPort = environment.PORT;
|
|
68
|
-
if (reportedPort !== undefined &&
|
|
68
|
+
if (reportedPort !== undefined && parseListenerPort(reportedPort, "PORT") !== port) {
|
|
69
69
|
throw new Error("Next.js reported inconsistent development listener ports");
|
|
70
70
|
}
|
|
71
71
|
return canonicalLoopbackTarget(origin.hostname, port);
|
|
72
72
|
}
|
|
73
|
-
function parsePort(value, source) {
|
|
74
|
-
if (!/^[0-9]+$/.test(value)) {
|
|
75
|
-
throw new Error(`${source} must be a port between 1 and 65535`);
|
|
76
|
-
}
|
|
77
|
-
const port = Number(value);
|
|
78
|
-
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
79
|
-
throw new Error(`${source} must be a port between 1 and 65535`);
|
|
80
|
-
}
|
|
81
|
-
return port;
|
|
82
|
-
}
|
|
83
73
|
function unique(values) {
|
|
84
74
|
return [...new Set(values)];
|
|
85
75
|
}
|
package/lib/launcher.mjs
CHANGED
|
@@ -2,16 +2,17 @@ import { constants, accessSync, readFileSync } from "node:fs";
|
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import process from "node:process";
|
|
5
|
+
import { nativeTargets } from "./native-targets.mjs";
|
|
5
6
|
|
|
6
7
|
const require = createRequire(import.meta.url);
|
|
7
8
|
const launcherManifest = new URL("../package.json", import.meta.url);
|
|
8
9
|
|
|
9
|
-
const nativePackages = new Map(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
const nativePackages = new Map(
|
|
11
|
+
nativeTargets.map(({ platform, architecture, packageName }) => [
|
|
12
|
+
`${platform}-${architecture}`,
|
|
13
|
+
packageName,
|
|
14
|
+
]),
|
|
15
|
+
);
|
|
15
16
|
|
|
16
17
|
export function nativePackageName(platform, architecture) {
|
|
17
18
|
const packageName = nativePackages.get(`${platform}-${architecture}`);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Internal distribution catalog shared by the launcher and release scripts.
|
|
2
|
+
* Node architecture names belong here; Go artifact paths and versions do not.
|
|
3
|
+
* Entries retain native publish order. This module is not a public package export.
|
|
4
|
+
*/
|
|
5
|
+
export const nativeTargets = Object.freeze(
|
|
6
|
+
[
|
|
7
|
+
{ platform: "darwin", architecture: "arm64", packageName: "@tnldotdev/tnl-darwin-arm64" },
|
|
8
|
+
{ platform: "darwin", architecture: "x64", packageName: "@tnldotdev/tnl-darwin-x64" },
|
|
9
|
+
{ platform: "linux", architecture: "arm64", packageName: "@tnldotdev/tnl-linux-arm64" },
|
|
10
|
+
{ platform: "linux", architecture: "x64", packageName: "@tnldotdev/tnl-linux-x64" },
|
|
11
|
+
].map((target) => Object.freeze(target)),
|
|
12
|
+
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tnldotdev/tnl",
|
|
3
|
-
"version": "0.1.0-rc.
|
|
3
|
+
"version": "0.1.0-rc.20",
|
|
4
4
|
"description": "The tnl client and framework integrations for project-local development.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -71,12 +71,12 @@
|
|
|
71
71
|
"node": ">=22.18"
|
|
72
72
|
},
|
|
73
73
|
"optionalDependencies": {
|
|
74
|
-
"@tnldotdev/tnl-darwin-arm64": "0.1.0-rc.
|
|
75
|
-
"@tnldotdev/tnl-darwin-x64": "0.1.0-rc.
|
|
76
|
-
"@tnldotdev/tnl-linux-arm64": "0.1.0-rc.
|
|
77
|
-
"@tnldotdev/tnl-linux-x64": "0.1.0-rc.
|
|
74
|
+
"@tnldotdev/tnl-darwin-arm64": "0.1.0-rc.20",
|
|
75
|
+
"@tnldotdev/tnl-darwin-x64": "0.1.0-rc.20",
|
|
76
|
+
"@tnldotdev/tnl-linux-arm64": "0.1.0-rc.20",
|
|
77
|
+
"@tnldotdev/tnl-linux-x64": "0.1.0-rc.20"
|
|
78
78
|
},
|
|
79
79
|
"tnl": {
|
|
80
|
-
"commit": "
|
|
80
|
+
"commit": "a2073944387d90f6b7a83ec28a409964b06a28a7"
|
|
81
81
|
}
|
|
82
82
|
}
|