typeferry 0.7.5 → 0.9.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.
Files changed (41) hide show
  1. package/README.md +62 -1
  2. package/dist/application/build.d.ts +2 -0
  3. package/dist/application/build.js +21 -0
  4. package/dist/application/build.js.map +1 -0
  5. package/dist/application/cli-arguments.d.ts +13 -0
  6. package/dist/application/cli-arguments.js +46 -0
  7. package/dist/application/cli-arguments.js.map +1 -0
  8. package/dist/application/config.d.ts +86 -0
  9. package/dist/application/config.js +177 -0
  10. package/dist/application/config.js.map +1 -0
  11. package/dist/application/develop.d.ts +4 -0
  12. package/dist/application/develop.js +119 -0
  13. package/dist/application/develop.js.map +1 -0
  14. package/dist/application/paths.d.ts +1 -0
  15. package/dist/application/paths.js +19 -0
  16. package/dist/application/paths.js.map +1 -0
  17. package/dist/application/proxy.d.ts +6 -0
  18. package/dist/application/proxy.js +51 -0
  19. package/dist/application/proxy.js.map +1 -0
  20. package/dist/application/run-tests.d.ts +3 -0
  21. package/dist/application/run-tests.js +13 -0
  22. package/dist/application/run-tests.js.map +1 -0
  23. package/dist/application/server-build.d.ts +3 -0
  24. package/dist/application/server-build.js +17 -0
  25. package/dist/application/server-build.js.map +1 -0
  26. package/dist/application/test-config.d.ts +4 -0
  27. package/dist/application/test-config.js +91 -0
  28. package/dist/application/test-config.js.map +1 -0
  29. package/dist/application/vite-config.d.ts +3 -0
  30. package/dist/application/vite-config.js +43 -0
  31. package/dist/application/vite-config.js.map +1 -0
  32. package/dist/cli/index.d.ts +2 -0
  33. package/dist/cli/index.js +10 -0
  34. package/dist/cli/index.js.map +1 -0
  35. package/dist/cli/run.d.ts +1 -0
  36. package/dist/cli/run.js +21 -0
  37. package/dist/cli/run.js.map +1 -0
  38. package/dist/testing/index.d.ts +7 -0
  39. package/dist/testing/index.js +8 -0
  40. package/dist/testing/index.js.map +1 -0
  41. package/package.json +29 -10
package/README.md CHANGED
@@ -4,6 +4,11 @@ The TypeScript implementation of TypeFerry provides the reference Node.js server
4
4
 
5
5
  Install the public TypeScript package with `npm install typeferry`. Repository contributors can use the local [`template/`](../template/) workflow described in the [quickstart](../docs/getting-started.md).
6
6
 
7
+ AI coding agents building consumer applications should begin with the
8
+ [agent application guide](https://github.com/leonardoventurini/typeferry/blob/main/docs/agents/application-development.md).
9
+ It defines the supported project structure, public imports, package-owned
10
+ commands, optional configuration, safety boundaries, and completion checks.
11
+
7
12
  ## Runtime requirements
8
13
 
9
14
  - Node.js `24.19.0`
@@ -26,9 +31,65 @@ Install the public TypeScript package with `npm install typeferry`. Repository c
26
31
  | `typeferry/ejson` | Extended JSON namespace |
27
32
  | `typeferry/mongodb` | Native-driver MongoDB extension |
28
33
  | `typeferry/mongodb/decorators` | MongoDB collection decorators |
34
+ | `typeferry/config` | Optional typed application configuration |
35
+ | `typeferry/test` | Unstable mirror of the installed Vitest API |
29
36
 
30
37
  Applications should import these compiled package exports. Do not alias or import TypeFerry source files from application code.
31
38
 
39
+ ## Application commands
40
+
41
+ Applications using the conventional root-level `client/`, `common/`,
42
+ `server/`, and `test/` directories need no TypeFerry configuration:
43
+
44
+ ```json
45
+ {
46
+ "scripts": {
47
+ "develop": "typeferry develop",
48
+ "build": "typeferry build",
49
+ "test": "typeferry test"
50
+ }
51
+ }
52
+ ```
53
+
54
+ `typeferry test unit`, `typeferry test integration`, and
55
+ `typeferry test browser` select one project. Add `--watch` for watch mode.
56
+ Tests can import Vitest through `typeferry/test`:
57
+
58
+ ```ts
59
+ import { describe, expect, it } from 'typeferry/test'
60
+ ```
61
+
62
+ That entry point mirrors TypeFerry's installed Vitest release. Individual
63
+ mirrored exports follow Vitest and do not carry independent TypeFerry
64
+ compatibility guarantees.
65
+
66
+ Add `typeferry.config.ts` only when overriding supported high-level defaults:
67
+
68
+ ```ts
69
+ import { defineConfig } from 'typeferry/config'
70
+
71
+ export default defineConfig({
72
+ development: {
73
+ clientPort: 8000,
74
+ serverPort: 8002,
75
+ },
76
+ build: {
77
+ server: {
78
+ external: ['sharp'],
79
+ },
80
+ },
81
+ })
82
+ ```
83
+
84
+ Server dependencies are bundled by default. Configured externals must be direct
85
+ application `dependencies`, and the application must ship them in its
86
+ production runtime installation.
87
+
88
+ See the full
89
+ [application framework guide](https://github.com/leonardoventurini/typeferry/blob/main/docs/typescript/application-framework.md)
90
+ for command behavior, test discovery, every configuration field, migration,
91
+ ownership boundaries, upgrades, and troubleshooting.
92
+
32
93
  ## Define and serve a typed method
33
94
 
34
95
  ```ts
@@ -85,4 +146,4 @@ npm pack --dry-run
85
146
 
86
147
  ## Guides
87
148
 
88
- Start at the [documentation home](../docs/README.md) or jump to [server and RPC](../docs/typescript/server-rpc.md), [client](../docs/typescript/client.md), [React](../docs/typescript/react.md), [authentication](../docs/typescript/authentication.md), [events and channels](../docs/typescript/events-and-channels.md), [MongoDB](../docs/typescript/mongodb.md), [EJSON](../docs/typescript/ejson.md), or [deployment](../docs/typescript/deployment.md).
149
+ Start at the [documentation home](../docs/README.md) or jump to the [application framework](../docs/typescript/application-framework.md), [server and RPC](../docs/typescript/server-rpc.md), [client](../docs/typescript/client.md), [React](../docs/typescript/react.md), [authentication](../docs/typescript/authentication.md), [events and channels](../docs/typescript/events-and-channels.md), [MongoDB](../docs/typescript/mongodb.md), [EJSON](../docs/typescript/ejson.md), or [deployment](../docs/typescript/deployment.md).
@@ -0,0 +1,2 @@
1
+ import type { ResolvedApplicationConfig } from './config.js';
2
+ export declare function runBuild(config: ResolvedApplicationConfig): Promise<void>;
@@ -0,0 +1,21 @@
1
+ import { access, rm } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { build as buildServer } from 'esbuild';
4
+ import { build as buildClient } from 'vite';
5
+ import { createServerBuildOptions } from './server-build.js';
6
+ import { createViteConfig } from './vite-config.js';
7
+ export async function runBuild(config) {
8
+ await Promise.all([
9
+ access(path.join(config.root, config.paths.client, 'index.html')),
10
+ access(path.join(config.root, config.paths.server, 'index.ts')),
11
+ ]);
12
+ await rm(path.join(config.root, config.paths.output), {
13
+ force: true,
14
+ recursive: true,
15
+ });
16
+ await Promise.all([
17
+ buildClient(createViteConfig(config, 'build')),
18
+ buildServer(createServerBuildOptions(config)),
19
+ ]);
20
+ }
21
+ //# sourceMappingURL=build.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/application/build.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,OAAO,EAAE,KAAK,IAAI,WAAW,EAAE,MAAM,SAAS,CAAA;AAC9C,OAAO,EAAE,KAAK,IAAI,WAAW,EAAE,MAAM,MAAM,CAAA;AAG3C,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAA;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAEhD,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,MAAiC;IAEjC,MAAM,OAAO,CAAC,GAAG,CAAC;QAChB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;QACjE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;KAChE,CAAC,CAAA;IAEF,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE;QACpD,KAAK,EAAE,IAAI;QACX,SAAS,EAAE,IAAI;KAChB,CAAC,CAAA;IAEF,MAAM,OAAO,CAAC,GAAG,CAAC;QAChB,WAAW,CAAC,gBAAgB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC9C,WAAW,CAAC,wBAAwB,CAAC,MAAM,CAAC,CAAC;KAC9C,CAAC,CAAA;AACJ,CAAC"}
@@ -0,0 +1,13 @@
1
+ export type TestProjectName = 'unit' | 'integration' | 'browser';
2
+ export type CliArguments = {
3
+ readonly command: 'build';
4
+ } | {
5
+ readonly command: 'develop';
6
+ readonly serverArguments: readonly string[];
7
+ } | {
8
+ readonly command: 'test';
9
+ readonly project?: TestProjectName;
10
+ readonly watch: boolean;
11
+ };
12
+ export declare const CLI_USAGE = "Usage: typeferry <develop [-- server-args...]|build|test [unit|integration|browser] [--watch]>";
13
+ export declare function parseCliArguments(arguments_: readonly string[]): CliArguments;
@@ -0,0 +1,46 @@
1
+ const TEST_PROJECTS = new Set([
2
+ 'unit',
3
+ 'integration',
4
+ 'browser',
5
+ ]);
6
+ export const CLI_USAGE = 'Usage: typeferry <develop [-- server-args...]|build|test [unit|integration|browser] [--watch]>';
7
+ export function parseCliArguments(arguments_) {
8
+ const [command, ...rest] = arguments_;
9
+ if (command === 'build') {
10
+ if (rest.length > 0)
11
+ throw new Error(`Unexpected build arguments. ${CLI_USAGE}`);
12
+ return { command };
13
+ }
14
+ if (command === 'develop') {
15
+ if (rest.length === 0)
16
+ return { command, serverArguments: [] };
17
+ if (rest[0] !== '--') {
18
+ throw new Error(`Server arguments must follow --. ${CLI_USAGE}`);
19
+ }
20
+ return { command, serverArguments: rest.slice(1) };
21
+ }
22
+ if (command === 'test')
23
+ return parseTestArguments(rest);
24
+ throw new Error(`Unknown command: ${command ?? '(missing)'}. ${CLI_USAGE}`);
25
+ }
26
+ function parseTestArguments(arguments_) {
27
+ let project;
28
+ let watch = false;
29
+ for (const argument of arguments_) {
30
+ if (argument === '--watch') {
31
+ watch = true;
32
+ continue;
33
+ }
34
+ if (!TEST_PROJECTS.has(argument)) {
35
+ throw new Error(`Unknown test project: ${argument}. ${CLI_USAGE}`);
36
+ }
37
+ if (project !== undefined) {
38
+ throw new Error(`Only one test project can be selected. ${CLI_USAGE}`);
39
+ }
40
+ project = argument;
41
+ }
42
+ return project === undefined
43
+ ? { command: 'test', watch }
44
+ : { command: 'test', project, watch };
45
+ }
46
+ //# sourceMappingURL=cli-arguments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-arguments.js","sourceRoot":"","sources":["../../src/application/cli-arguments.ts"],"names":[],"mappings":"AAWA,MAAM,aAAa,GAAG,IAAI,GAAG,CAAkB;IAC7C,MAAM;IACN,aAAa;IACb,SAAS;CACV,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,SAAS,GACpB,gGAAgG,CAAA;AAElG,MAAM,UAAU,iBAAiB,CAAC,UAA6B;IAC7D,MAAM,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,UAAU,CAAA;IAErC,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;QACxB,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,SAAS,EAAE,CAAC,CAAA;QAChF,OAAO,EAAE,OAAO,EAAE,CAAA;IACpB,CAAC;IAED,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,EAAE,EAAE,CAAA;QAC9D,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CAAC,oCAAoC,SAAS,EAAE,CAAC,CAAA;QAClE,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;IACpD,CAAC;IAED,IAAI,OAAO,KAAK,MAAM;QAAE,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAA;IAEvD,MAAM,IAAI,KAAK,CAAC,oBAAoB,OAAO,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC,CAAA;AAC7E,CAAC;AAED,SAAS,kBAAkB,CAAC,UAA6B;IACvD,IAAI,OAAoC,CAAA;IACxC,IAAI,KAAK,GAAG,KAAK,CAAA;IAEjB,KAAK,MAAM,QAAQ,IAAI,UAAU,EAAE,CAAC;QAClC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,KAAK,GAAG,IAAI,CAAA;YACZ,SAAQ;QACV,CAAC;QAED,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,QAA2B,CAAC,EAAE,CAAC;YACpD,MAAM,IAAI,KAAK,CAAC,yBAAyB,QAAQ,KAAK,SAAS,EAAE,CAAC,CAAA;QACpE,CAAC;QACD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CAAC,0CAA0C,SAAS,EAAE,CAAC,CAAA;QACxE,CAAC;QACD,OAAO,GAAG,QAA2B,CAAA;IACvC,CAAC;IAED,OAAO,OAAO,KAAK,SAAS;QAC1B,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE;QAC5B,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,CAAA;AACzC,CAAC"}
@@ -0,0 +1,86 @@
1
+ export type BrowserName = 'chromium' | 'firefox' | 'webkit';
2
+ export interface TypeFerryConfig {
3
+ readonly development?: {
4
+ readonly clientPort?: number;
5
+ readonly serverPort?: number;
6
+ readonly serverEnvironmentFile?: string;
7
+ };
8
+ readonly build?: {
9
+ readonly target?: string;
10
+ readonly sourceMaps?: boolean;
11
+ readonly server?: {
12
+ readonly external?: readonly string[];
13
+ };
14
+ };
15
+ readonly test?: {
16
+ readonly integration?: {
17
+ readonly timeout?: number;
18
+ };
19
+ readonly browser?: {
20
+ readonly browser?: BrowserName;
21
+ };
22
+ };
23
+ }
24
+ export interface ResolvedApplicationConfig {
25
+ readonly root: string;
26
+ readonly paths: {
27
+ readonly client: string;
28
+ readonly common: string;
29
+ readonly server: string;
30
+ readonly tests: string;
31
+ readonly output: string;
32
+ };
33
+ readonly development: {
34
+ readonly clientPort: number;
35
+ readonly serverPort: number;
36
+ readonly serverEnvironmentFile: string;
37
+ };
38
+ readonly build: {
39
+ readonly target: string;
40
+ readonly sourceMaps: boolean;
41
+ readonly server: {
42
+ readonly external: readonly string[];
43
+ };
44
+ };
45
+ readonly test: {
46
+ readonly integration: {
47
+ readonly timeout: number;
48
+ };
49
+ readonly browser: {
50
+ readonly browser: BrowserName;
51
+ };
52
+ };
53
+ }
54
+ export declare const DEFAULT_APPLICATION_CONFIG: {
55
+ readonly paths: {
56
+ readonly client: "client";
57
+ readonly common: "common";
58
+ readonly server: "server";
59
+ readonly tests: "test";
60
+ readonly output: "dist";
61
+ };
62
+ readonly development: {
63
+ readonly clientPort: 8000;
64
+ readonly serverPort: 8002;
65
+ readonly serverEnvironmentFile: ".env.server";
66
+ };
67
+ readonly build: {
68
+ readonly target: "es2023";
69
+ readonly sourceMaps: true;
70
+ readonly server: {
71
+ readonly external: readonly string[];
72
+ };
73
+ };
74
+ readonly test: {
75
+ readonly integration: {
76
+ readonly timeout: 30000;
77
+ };
78
+ readonly browser: {
79
+ readonly browser: BrowserName;
80
+ };
81
+ };
82
+ };
83
+ /** Provides contextual typing without transforming application configuration. */
84
+ export declare function defineConfig(config: TypeFerryConfig): TypeFerryConfig;
85
+ export declare function resolveApplicationConfig(root: string, input?: unknown): ResolvedApplicationConfig;
86
+ export declare function loadApplicationConfig(root: string): Promise<ResolvedApplicationConfig>;
@@ -0,0 +1,177 @@
1
+ import { access, readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { createJiti } from 'jiti';
4
+ import { z } from 'zod';
5
+ const browserNameSchema = z.enum(['chromium', 'firefox', 'webkit']);
6
+ const positivePortSchema = z.number().int().min(1).max(65_535);
7
+ const packageManifestSchema = z.object({
8
+ dependencies: z.record(z.string(), z.string()).optional(),
9
+ devDependencies: z.record(z.string(), z.string()).optional(),
10
+ });
11
+ const serverExternalSchema = z
12
+ .array(z
13
+ .string()
14
+ .min(1)
15
+ .refine(isPackageSpecifier, 'Expected an npm package specifier'))
16
+ .refine(values => new Set(values).size === values.length, {
17
+ message: 'Server external package specifiers must not contain duplicates',
18
+ });
19
+ const configSchema = z
20
+ .object({
21
+ development: z
22
+ .object({
23
+ clientPort: positivePortSchema.optional(),
24
+ serverPort: positivePortSchema.optional(),
25
+ serverEnvironmentFile: z.string().min(1).optional(),
26
+ })
27
+ .strict()
28
+ .optional(),
29
+ build: z
30
+ .object({
31
+ target: z.string().min(1).optional(),
32
+ sourceMaps: z.boolean().optional(),
33
+ server: z
34
+ .object({ external: serverExternalSchema.optional() })
35
+ .strict()
36
+ .optional(),
37
+ })
38
+ .strict()
39
+ .optional(),
40
+ test: z
41
+ .object({
42
+ integration: z
43
+ .object({ timeout: z.number().int().positive().optional() })
44
+ .strict()
45
+ .optional(),
46
+ browser: z
47
+ .object({ browser: browserNameSchema.optional() })
48
+ .strict()
49
+ .optional(),
50
+ })
51
+ .strict()
52
+ .optional(),
53
+ })
54
+ .strict();
55
+ export const DEFAULT_APPLICATION_CONFIG = {
56
+ paths: {
57
+ client: 'client',
58
+ common: 'common',
59
+ server: 'server',
60
+ tests: 'test',
61
+ output: 'dist',
62
+ },
63
+ development: {
64
+ clientPort: 8000,
65
+ serverPort: 8002,
66
+ serverEnvironmentFile: '.env.server',
67
+ },
68
+ build: {
69
+ target: 'es2023',
70
+ sourceMaps: true,
71
+ server: {
72
+ external: [],
73
+ },
74
+ },
75
+ test: {
76
+ integration: { timeout: 30_000 },
77
+ browser: { browser: 'chromium' },
78
+ },
79
+ };
80
+ /** Provides contextual typing without transforming application configuration. */
81
+ export function defineConfig(config) {
82
+ return config;
83
+ }
84
+ export function resolveApplicationConfig(root, input = {}) {
85
+ const config = configSchema.parse(input);
86
+ return {
87
+ root: path.resolve(root),
88
+ paths: DEFAULT_APPLICATION_CONFIG.paths,
89
+ development: {
90
+ ...DEFAULT_APPLICATION_CONFIG.development,
91
+ ...config.development,
92
+ },
93
+ build: {
94
+ ...DEFAULT_APPLICATION_CONFIG.build,
95
+ ...config.build,
96
+ server: {
97
+ ...DEFAULT_APPLICATION_CONFIG.build.server,
98
+ ...config.build?.server,
99
+ },
100
+ },
101
+ test: {
102
+ integration: {
103
+ ...DEFAULT_APPLICATION_CONFIG.test.integration,
104
+ ...config.test?.integration,
105
+ },
106
+ browser: {
107
+ ...DEFAULT_APPLICATION_CONFIG.test.browser,
108
+ ...config.test?.browser,
109
+ },
110
+ },
111
+ };
112
+ }
113
+ export async function loadApplicationConfig(root) {
114
+ const configPath = path.join(root, 'typeferry.config.ts');
115
+ try {
116
+ await access(configPath);
117
+ }
118
+ catch {
119
+ return resolveApplicationConfig(root, withEnvironmentFile({}));
120
+ }
121
+ const jiti = createJiti(import.meta.url, { interopDefault: true });
122
+ const loaded = await jiti.import(configPath, { default: true });
123
+ const parsed = configSchema.parse(loaded);
124
+ const resolved = resolveApplicationConfig(root, withEnvironmentFile(parsed));
125
+ await validateServerExternalDependencies(resolved);
126
+ return resolved;
127
+ }
128
+ async function validateServerExternalDependencies(config) {
129
+ if (config.build.server.external.length === 0)
130
+ return;
131
+ const manifestPath = path.join(config.root, 'package.json');
132
+ const manifest = packageManifestSchema.parse(JSON.parse(await readFile(manifestPath, 'utf8')));
133
+ for (const external of config.build.server.external) {
134
+ const packageName = packageNameFromSpecifier(external);
135
+ if (manifest.dependencies?.[packageName] !== undefined)
136
+ continue;
137
+ if (manifest.devDependencies?.[packageName] !== undefined) {
138
+ throw new Error(`Server external "${external}" must be declared in dependencies, not devDependencies`);
139
+ }
140
+ throw new Error(`Server external "${external}" must be declared in package.json dependencies`);
141
+ }
142
+ }
143
+ function isPackageSpecifier(value) {
144
+ if (value.includes('*') || value.includes('\\') || value.includes(':')) {
145
+ return false;
146
+ }
147
+ const segments = value.split('/');
148
+ if (value.startsWith('@')) {
149
+ return (segments.length >= 2 &&
150
+ isPackageNameSegment(segments[0]?.slice(1) ?? '') &&
151
+ segments.every((segment, index) => index === 0 ? true : isPackageNameSegment(segment)));
152
+ }
153
+ return segments.every(isPackageNameSegment);
154
+ }
155
+ function isPackageNameSegment(value) {
156
+ return /^[a-z0-9][a-z0-9._~-]*$/u.test(value);
157
+ }
158
+ function packageNameFromSpecifier(specifier) {
159
+ const segments = specifier.split('/');
160
+ return specifier.startsWith('@')
161
+ ? `${segments[0]}/${segments[1]}`
162
+ : (segments[0] ?? specifier);
163
+ }
164
+ function withEnvironmentFile(config) {
165
+ if (config.development?.serverEnvironmentFile !== undefined ||
166
+ process.env['DEVELOP_ENV_FILE'] === undefined) {
167
+ return config;
168
+ }
169
+ return {
170
+ ...config,
171
+ development: {
172
+ ...config.development,
173
+ serverEnvironmentFile: process.env['DEVELOP_ENV_FILE'],
174
+ },
175
+ };
176
+ }
177
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/application/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAA;AACjC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AA0DvB,MAAM,iBAAiB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAA;AACnE,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;AAC9D,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC;IACrC,YAAY,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IACzD,eAAe,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;CAC7D,CAAC,CAAA;AACF,MAAM,oBAAoB,GAAG,CAAC;KAC3B,KAAK,CACJ,CAAC;KACE,MAAM,EAAE;KACR,GAAG,CAAC,CAAC,CAAC;KACN,MAAM,CAAC,kBAAkB,EAAE,mCAAmC,CAAC,CACnE;KACA,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,MAAM,EAAE;IACxD,OAAO,EAAE,gEAAgE;CAC1E,CAAC,CAAA;AACJ,MAAM,YAAY,GAAG,CAAC;KACnB,MAAM,CAAC;IACN,WAAW,EAAE,CAAC;SACX,MAAM,CAAC;QACN,UAAU,EAAE,kBAAkB,CAAC,QAAQ,EAAE;QACzC,UAAU,EAAE,kBAAkB,CAAC,QAAQ,EAAE;QACzC,qBAAqB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;KACpD,CAAC;SACD,MAAM,EAAE;SACR,QAAQ,EAAE;IACb,KAAK,EAAE,CAAC;SACL,MAAM,CAAC;QACN,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;QACpC,UAAU,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;QAClC,MAAM,EAAE,CAAC;aACN,MAAM,CAAC,EAAE,QAAQ,EAAE,oBAAoB,CAAC,QAAQ,EAAE,EAAE,CAAC;aACrD,MAAM,EAAE;aACR,QAAQ,EAAE;KACd,CAAC;SACD,MAAM,EAAE;SACR,QAAQ,EAAE;IACb,IAAI,EAAE,CAAC;SACJ,MAAM,CAAC;QACN,WAAW,EAAE,CAAC;aACX,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC;aAC3D,MAAM,EAAE;aACR,QAAQ,EAAE;QACb,OAAO,EAAE,CAAC;aACP,MAAM,CAAC,EAAE,OAAO,EAAE,iBAAiB,CAAC,QAAQ,EAAE,EAAE,CAAC;aACjD,MAAM,EAAE;aACR,QAAQ,EAAE;KACd,CAAC;SACD,MAAM,EAAE;SACR,QAAQ,EAAE;CACd,CAAC;KACD,MAAM,EAAE,CAAA;AAEX,MAAM,CAAC,MAAM,0BAA0B,GAAG;IACxC,KAAK,EAAE;QACL,MAAM,EAAE,QAAQ;QAChB,MAAM,EAAE,QAAQ;QAChB,MAAM,EAAE,QAAQ;QAChB,KAAK,EAAE,MAAM;QACb,MAAM,EAAE,MAAM;KACf;IACD,WAAW,EAAE;QACX,UAAU,EAAE,IAAI;QAChB,UAAU,EAAE,IAAI;QAChB,qBAAqB,EAAE,aAAa;KACrC;IACD,KAAK,EAAE;QACL,MAAM,EAAE,QAAQ;QAChB,UAAU,EAAE,IAAI;QAChB,MAAM,EAAE;YACN,QAAQ,EAAE,EAAuB;SAClC;KACF;IACD,IAAI,EAAE;QACJ,WAAW,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE;QAChC,OAAO,EAAE,EAAE,OAAO,EAAE,UAAyB,EAAE;KAChD;CACO,CAAA;AAEV,iFAAiF;AACjF,MAAM,UAAU,YAAY,CAAC,MAAuB;IAClD,OAAO,MAAM,CAAA;AACf,CAAC;AAED,MAAM,UAAU,wBAAwB,CACtC,IAAY,EACZ,QAAiB,EAAE;IAEnB,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IAExC,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;QACxB,KAAK,EAAE,0BAA0B,CAAC,KAAK;QACvC,WAAW,EAAE;YACX,GAAG,0BAA0B,CAAC,WAAW;YACzC,GAAG,MAAM,CAAC,WAAW;SACtB;QACD,KAAK,EAAE;YACL,GAAG,0BAA0B,CAAC,KAAK;YACnC,GAAG,MAAM,CAAC,KAAK;YACf,MAAM,EAAE;gBACN,GAAG,0BAA0B,CAAC,KAAK,CAAC,MAAM;gBAC1C,GAAG,MAAM,CAAC,KAAK,EAAE,MAAM;aACxB;SACF;QACD,IAAI,EAAE;YACJ,WAAW,EAAE;gBACX,GAAG,0BAA0B,CAAC,IAAI,CAAC,WAAW;gBAC9C,GAAG,MAAM,CAAC,IAAI,EAAE,WAAW;aAC5B;YACD,OAAO,EAAE;gBACP,GAAG,0BAA0B,CAAC,IAAI,CAAC,OAAO;gBAC1C,GAAG,MAAM,CAAC,IAAI,EAAE,OAAO;aACxB;SACF;KACF,CAAA;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,IAAY;IAEZ,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,qBAAqB,CAAC,CAAA;IAEzD,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,UAAU,CAAC,CAAA;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,wBAAwB,CAAC,IAAI,EAAE,mBAAmB,CAAC,EAAE,CAAC,CAAC,CAAA;IAChE,CAAC;IAED,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAA;IAClE,MAAM,MAAM,GAAY,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;IACxE,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;IACzC,MAAM,QAAQ,GAAG,wBAAwB,CAAC,IAAI,EAAE,mBAAmB,CAAC,MAAM,CAAC,CAAC,CAAA;IAC5E,MAAM,kCAAkC,CAAC,QAAQ,CAAC,CAAA;IAClD,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED,KAAK,UAAU,kCAAkC,CAC/C,MAAiC;IAEjC,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAM;IAErD,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,cAAc,CAAC,CAAA;IAC3D,MAAM,QAAQ,GAAG,qBAAqB,CAAC,KAAK,CAC1C,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CACjD,CAAA;IAED,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpD,MAAM,WAAW,GAAG,wBAAwB,CAAC,QAAQ,CAAC,CAAA;QACtD,IAAI,QAAQ,CAAC,YAAY,EAAE,CAAC,WAAW,CAAC,KAAK,SAAS;YAAE,SAAQ;QAEhE,IAAI,QAAQ,CAAC,eAAe,EAAE,CAAC,WAAW,CAAC,KAAK,SAAS,EAAE,CAAC;YAC1D,MAAM,IAAI,KAAK,CACb,oBAAoB,QAAQ,yDAAyD,CACtF,CAAA;QACH,CAAC;QAED,MAAM,IAAI,KAAK,CACb,oBAAoB,QAAQ,iDAAiD,CAC9E,CAAA;IACH,CAAC;AACH,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAa;IACvC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACvE,OAAO,KAAK,CAAA;IACd,CAAC;IAED,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACjC,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1B,OAAO,CACL,QAAQ,CAAC,MAAM,IAAI,CAAC;YACpB,oBAAoB,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YACjD,QAAQ,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,CAChC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,oBAAoB,CAAC,OAAO,CAAC,CACnD,CACF,CAAA;IACH,CAAC;IAED,OAAO,QAAQ,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAA;AAC7C,CAAC;AAED,SAAS,oBAAoB,CAAC,KAAa;IACzC,OAAO,0BAA0B,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;AAC/C,CAAC;AAED,SAAS,wBAAwB,CAAC,SAAiB;IACjD,MAAM,QAAQ,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACrC,OAAO,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC;QAC9B,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,EAAE;QACjC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,CAAA;AAChC,CAAC;AAED,SAAS,mBAAmB,CAAC,MAAuB;IAClD,IACE,MAAM,CAAC,WAAW,EAAE,qBAAqB,KAAK,SAAS;QACvD,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,KAAK,SAAS,EAC7C,CAAC;QACD,OAAO,MAAM,CAAA;IACf,CAAC;IAED,OAAO;QACL,GAAG,MAAM;QACT,WAAW,EAAE;YACX,GAAG,MAAM,CAAC,WAAW;YACrB,qBAAqB,EAAE,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;SACvD;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { ResolvedApplicationConfig } from './config.js';
2
+ export declare const SHUTDOWN_SIGNALS: readonly ["SIGUSR2", "SIGINT", "SIGQUIT", "SIGTERM", "SIGHUP"];
3
+ export declare function buildServerArguments(config: ResolvedApplicationConfig, arguments_: readonly string[]): string[];
4
+ export declare function runDevelop(config: ResolvedApplicationConfig, serverArguments: readonly string[]): Promise<void>;
@@ -0,0 +1,119 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { createWriteStream } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { context } from 'esbuild';
5
+ import kill from 'tree-kill';
6
+ import { createServer } from 'vite';
7
+ import { createServerBuildOptions } from './server-build.js';
8
+ import { createViteConfig } from './vite-config.js';
9
+ export const SHUTDOWN_SIGNALS = [
10
+ 'SIGUSR2',
11
+ 'SIGINT',
12
+ 'SIGQUIT',
13
+ 'SIGTERM',
14
+ 'SIGHUP',
15
+ ];
16
+ export function buildServerArguments(config, arguments_) {
17
+ return [
18
+ '--watch',
19
+ `--env-file=${config.development.serverEnvironmentFile}`,
20
+ path.join(config.paths.output, 'server', 'index.cjs'),
21
+ ...arguments_,
22
+ ];
23
+ }
24
+ export async function runDevelop(config, serverArguments) {
25
+ const logStream = createWriteStream(path.join(config.root, 'dev.log'), {
26
+ flags: 'w',
27
+ });
28
+ logStream.write(`--- started ${new Date().toISOString()} ---\n`);
29
+ const vite = await startVite(config);
30
+ const backendCompiler = await startBackendCompiler(config);
31
+ const backend = startBackend(config, serverArguments, logStream);
32
+ process.stdout.write(`TypeFerry application running at http://localhost:${config.development.clientPort}/\n`);
33
+ let shuttingDown = false;
34
+ const shutdown = async (signal) => {
35
+ if (shuttingDown)
36
+ return;
37
+ shuttingDown = true;
38
+ writeTaggedOutput(logStream, 'develop', `received ${signal}; shutting down\n`);
39
+ await Promise.all([
40
+ stopProcess({ name: 'backend', process: backend }, logStream),
41
+ backendCompiler.dispose(),
42
+ vite.close(),
43
+ ]);
44
+ logStream.end();
45
+ };
46
+ for (const signal of SHUTDOWN_SIGNALS) {
47
+ process.on(signal, () => {
48
+ void shutdown(signal).then(() => {
49
+ process.exit(0);
50
+ });
51
+ });
52
+ }
53
+ backend.once('exit', code => {
54
+ if (!shuttingDown && code !== 0) {
55
+ void shutdown(`backend exit ${code ?? 'unknown'}`).then(() => {
56
+ process.exit(code ?? 1);
57
+ });
58
+ }
59
+ });
60
+ }
61
+ async function startVite(config) {
62
+ const server = await createServer(createViteConfig(config, 'develop'));
63
+ await server.listen();
64
+ return server;
65
+ }
66
+ async function startBackendCompiler(config) {
67
+ const compiler = await context(createServerBuildOptions(config));
68
+ await compiler.rebuild();
69
+ await compiler.watch();
70
+ return compiler;
71
+ }
72
+ function startBackend(config, arguments_, logStream) {
73
+ const child = spawn(process.execPath, buildServerArguments(config, arguments_), {
74
+ cwd: config.root,
75
+ detached: false,
76
+ stdio: ['inherit', 'pipe', 'pipe'],
77
+ });
78
+ child.stdout?.on('data', (chunk) => writeTaggedOutput(logStream, 'server', chunk.toString()));
79
+ child.stderr?.on('data', (chunk) => writeTaggedOutput(logStream, 'server', chunk.toString()));
80
+ return child;
81
+ }
82
+ function writeTaggedOutput(logStream, tag, data) {
83
+ const line = `[${tag}] ${data}`;
84
+ process.stdout.write(line);
85
+ logStream.write(line);
86
+ }
87
+ function stopProcess({ name, process: child }, logStream) {
88
+ return new Promise(resolveStop => {
89
+ const processId = child.pid;
90
+ if (processId === undefined || child.exitCode !== null) {
91
+ resolveStop();
92
+ return;
93
+ }
94
+ let completed = false;
95
+ const finish = () => {
96
+ if (!completed) {
97
+ completed = true;
98
+ resolveStop();
99
+ }
100
+ };
101
+ child.once('exit', finish);
102
+ kill(processId, 'SIGTERM', error => {
103
+ if (!error)
104
+ return;
105
+ writeTaggedOutput(logStream, 'develop', `${name} SIGTERM failed: ${error.message}\n`);
106
+ kill(processId, 'SIGKILL', killError => {
107
+ if (killError) {
108
+ writeTaggedOutput(logStream, 'develop', `${name} SIGKILL failed: ${killError.message}\n`);
109
+ }
110
+ finish();
111
+ });
112
+ });
113
+ setTimeout(() => {
114
+ kill(processId, 'SIGKILL', () => finish());
115
+ }, 2_000).unref();
116
+ setTimeout(finish, 10_000).unref();
117
+ });
118
+ }
119
+ //# sourceMappingURL=develop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"develop.js","sourceRoot":"","sources":["../../src/application/develop.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAC7D,OAAO,EAAE,iBAAiB,EAAoB,MAAM,SAAS,CAAA;AAC7D,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,OAAO,EAAE,OAAO,EAAqB,MAAM,SAAS,CAAA;AACpD,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,YAAY,EAAsB,MAAM,MAAM,CAAA;AAGvD,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAA;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAOhD,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,SAAS;IACT,QAAQ;IACR,SAAS;IACT,SAAS;IACT,QAAQ;CACA,CAAA;AAEV,MAAM,UAAU,oBAAoB,CAClC,MAAiC,EACjC,UAA6B;IAE7B,OAAO;QACL,SAAS;QACT,cAAc,MAAM,CAAC,WAAW,CAAC,qBAAqB,EAAE;QACxD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,QAAQ,EAAE,WAAW,CAAC;QACrD,GAAG,UAAU;KACd,CAAA;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,MAAiC,EACjC,eAAkC;IAElC,MAAM,SAAS,GAAG,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE;QACrE,KAAK,EAAE,GAAG;KACX,CAAC,CAAA;IACF,SAAS,CAAC,KAAK,CAAC,eAAe,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAA;IAEhE,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,CAAA;IACpC,MAAM,eAAe,GAAG,MAAM,oBAAoB,CAAC,MAAM,CAAC,CAAA;IAC1D,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,EAAE,eAAe,EAAE,SAAS,CAAC,CAAA;IAChE,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,qDAAqD,MAAM,CAAC,WAAW,CAAC,UAAU,KAAK,CACxF,CAAA;IAED,IAAI,YAAY,GAAG,KAAK,CAAA;IACxB,MAAM,QAAQ,GAAG,KAAK,EAAE,MAAc,EAAiB,EAAE;QACvD,IAAI,YAAY;YAAE,OAAM;QACxB,YAAY,GAAG,IAAI,CAAA;QACnB,iBAAiB,CACf,SAAS,EACT,SAAS,EACT,YAAY,MAAM,mBAAmB,CACtC,CAAA;QACD,MAAM,OAAO,CAAC,GAAG,CAAC;YAChB,WAAW,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,SAAS,CAAC;YAC7D,eAAe,CAAC,OAAO,EAAE;YACzB,IAAI,CAAC,KAAK,EAAE;SACb,CAAC,CAAA;QACF,SAAS,CAAC,GAAG,EAAE,CAAA;IACjB,CAAC,CAAA;IAED,KAAK,MAAM,MAAM,IAAI,gBAAgB,EAAE,CAAC;QACtC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;YACtB,KAAK,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE;gBAC9B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YACjB,CAAC,CAAC,CAAA;QACJ,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE;QAC1B,IAAI,CAAC,YAAY,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;YAChC,KAAK,QAAQ,CAAC,gBAAgB,IAAI,IAAI,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE;gBAC3D,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,CAAA;YACzB,CAAC,CAAC,CAAA;QACJ,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,KAAK,UAAU,SAAS,CACtB,MAAiC;IAEjC,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,gBAAgB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAA;IACtE,MAAM,MAAM,CAAC,MAAM,EAAE,CAAA;IACrB,OAAO,MAAM,CAAA;AACf,CAAC;AAED,KAAK,UAAU,oBAAoB,CACjC,MAAiC;IAEjC,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,wBAAwB,CAAC,MAAM,CAAC,CAAC,CAAA;IAChE,MAAM,QAAQ,CAAC,OAAO,EAAE,CAAA;IACxB,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAA;IACtB,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED,SAAS,YAAY,CACnB,MAAiC,EACjC,UAA6B,EAC7B,SAAsB;IAEtB,MAAM,KAAK,GAAG,KAAK,CACjB,OAAO,CAAC,QAAQ,EAChB,oBAAoB,CAAC,MAAM,EAAE,UAAU,CAAC,EACxC;QACE,GAAG,EAAE,MAAM,CAAC,IAAI;QAChB,QAAQ,EAAE,KAAK;QACf,KAAK,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC;KACnC,CACF,CAAA;IAED,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CACzC,iBAAiB,CAAC,SAAS,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CACzD,CAAA;IACD,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CACzC,iBAAiB,CAAC,SAAS,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CACzD,CAAA;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,SAAS,iBAAiB,CACxB,SAAsB,EACtB,GAAW,EACX,IAAY;IAEZ,MAAM,IAAI,GAAG,IAAI,GAAG,KAAK,IAAI,EAAE,CAAA;IAC/B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IAC1B,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;AACvB,CAAC;AAED,SAAS,WAAW,CAClB,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAkB,EACxC,SAAsB;IAEtB,OAAO,IAAI,OAAO,CAAC,WAAW,CAAC,EAAE;QAC/B,MAAM,SAAS,GAAG,KAAK,CAAC,GAAG,CAAA;QAC3B,IAAI,SAAS,KAAK,SAAS,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;YACvD,WAAW,EAAE,CAAA;YACb,OAAM;QACR,CAAC;QAED,IAAI,SAAS,GAAG,KAAK,CAAA;QACrB,MAAM,MAAM,GAAG,GAAS,EAAE;YACxB,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,SAAS,GAAG,IAAI,CAAA;gBAChB,WAAW,EAAE,CAAA;YACf,CAAC;QACH,CAAC,CAAA;QAED,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QAC1B,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE;YACjC,IAAI,CAAC,KAAK;gBAAE,OAAM;YAElB,iBAAiB,CACf,SAAS,EACT,SAAS,EACT,GAAG,IAAI,oBAAoB,KAAK,CAAC,OAAO,IAAI,CAC7C,CAAA;YACD,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,CAAC,EAAE;gBACrC,IAAI,SAAS,EAAE,CAAC;oBACd,iBAAiB,CACf,SAAS,EACT,SAAS,EACT,GAAG,IAAI,oBAAoB,SAAS,CAAC,OAAO,IAAI,CACjD,CAAA;gBACH,CAAC;gBACD,MAAM,EAAE,CAAA;YACV,CAAC,CAAC,CAAA;QACJ,CAAC,CAAC,CAAA;QAEF,UAAU,CAAC,GAAG,EAAE;YACd,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC,CAAA;QAC5C,CAAC,EAAE,KAAK,CAAC,CAAC,KAAK,EAAE,CAAA;QACjB,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KAAK,EAAE,CAAA;IACpC,CAAC,CAAC,CAAA;AACJ,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function findApplicationRoot(start: string): Promise<string>;
@@ -0,0 +1,19 @@
1
+ import { access } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ export async function findApplicationRoot(start) {
4
+ let candidate = path.resolve(start);
5
+ while (true) {
6
+ try {
7
+ await access(path.join(candidate, 'package.json'));
8
+ return candidate;
9
+ }
10
+ catch {
11
+ const parent = path.dirname(candidate);
12
+ if (parent === candidate) {
13
+ throw new Error(`Could not find an application package.json from ${start}`);
14
+ }
15
+ candidate = parent;
16
+ }
17
+ }
18
+ }
19
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/application/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,KAAa;IACrD,IAAI,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;IAEnC,OAAO,IAAI,EAAE,CAAC;QACZ,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC,CAAA;YAClD,OAAO,SAAS,CAAA;QAClB,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;YACtC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,MAAM,IAAI,KAAK,CACb,mDAAmD,KAAK,EAAE,CAC3D,CAAA;YACH,CAAC;YACD,SAAS,GAAG,MAAM,CAAA;QACpB,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { type IncomingHttpHeaders } from 'node:http';
2
+ import type { Plugin } from 'vite';
3
+ export declare function isTypeFerryHttpPath(url: string | undefined): boolean;
4
+ export declare function createTypeFerryDevProxy(backendPort: number): Plugin;
5
+ /** Removes backend-only cookie domains without adding undefined headers. */
6
+ export declare function rewriteProxyHeaders(proxyHeaders: IncomingHttpHeaders): IncomingHttpHeaders;
@@ -0,0 +1,51 @@
1
+ import { request as createHttpRequest, } from 'node:http';
2
+ export function isTypeFerryHttpPath(url) {
3
+ return url === '/__h' || url?.startsWith('/__h/') === true;
4
+ }
5
+ export function createTypeFerryDevProxy(backendPort) {
6
+ return {
7
+ name: 'typeferry-development-proxy',
8
+ configureServer(server) {
9
+ server.middlewares.use((request, response, next) => {
10
+ if (!isTypeFerryHttpPath(request.url)) {
11
+ next();
12
+ return;
13
+ }
14
+ proxyRequest(request, response, backendPort);
15
+ });
16
+ },
17
+ };
18
+ }
19
+ /** Removes backend-only cookie domains without adding undefined headers. */
20
+ export function rewriteProxyHeaders(proxyHeaders) {
21
+ const headers = { ...proxyHeaders };
22
+ const setCookie = headers['set-cookie'];
23
+ if (setCookie) {
24
+ headers['set-cookie'] = setCookie.map(cookie => cookie.replace(/;\s*Domain=[^;]+/giu, ''));
25
+ }
26
+ return headers;
27
+ }
28
+ function proxyRequest(clientRequest, clientResponse, backendPort) {
29
+ const proxyRequest = createHttpRequest({
30
+ hostname: '127.0.0.1',
31
+ port: backendPort,
32
+ method: clientRequest.method,
33
+ path: clientRequest.url,
34
+ headers: {
35
+ ...clientRequest.headers,
36
+ host: `localhost:${backendPort}`,
37
+ },
38
+ }, proxyResponse => {
39
+ clientResponse.writeHead(proxyResponse.statusCode ?? 502, rewriteProxyHeaders(proxyResponse.headers));
40
+ proxyResponse.pipe(clientResponse);
41
+ });
42
+ proxyRequest.on('error', (error) => {
43
+ if (!clientResponse.headersSent) {
44
+ clientResponse.writeHead(502, { 'content-type': 'text/plain' });
45
+ }
46
+ clientResponse.end(`TypeFerry development proxy error: ${error.message}`);
47
+ });
48
+ clientRequest.on('aborted', () => proxyRequest.destroy());
49
+ clientRequest.pipe(proxyRequest);
50
+ }
51
+ //# sourceMappingURL=proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.js","sourceRoot":"","sources":["../../src/application/proxy.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,OAAO,IAAI,iBAAiB,GAE7B,MAAM,WAAW,CAAA;AAIlB,MAAM,UAAU,mBAAmB,CAAC,GAAuB;IACzD,OAAO,GAAG,KAAK,MAAM,IAAI,GAAG,EAAE,UAAU,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;AAC5D,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,WAAmB;IACzD,OAAO;QACL,IAAI,EAAE,6BAA6B;QACnC,eAAe,CAAC,MAAM;YACpB,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;gBACjD,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;oBACtC,IAAI,EAAE,CAAA;oBACN,OAAM;gBACR,CAAC;gBAED,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAA;YAC9C,CAAC,CAAC,CAAA;QACJ,CAAC;KACF,CAAA;AACH,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,mBAAmB,CACjC,YAAiC;IAEjC,MAAM,OAAO,GAAG,EAAE,GAAG,YAAY,EAAE,CAAA;IACnC,MAAM,SAAS,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;IACvC,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,CAAC,YAAY,CAAC,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAC7C,MAAM,CAAC,OAAO,CAAC,qBAAqB,EAAE,EAAE,CAAC,CAC1C,CAAA;IACH,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,SAAS,YAAY,CACnB,aAA8B,EAC9B,cAA8B,EAC9B,WAAmB;IAEnB,MAAM,YAAY,GAAG,iBAAiB,CACpC;QACE,QAAQ,EAAE,WAAW;QACrB,IAAI,EAAE,WAAW;QACjB,MAAM,EAAE,aAAa,CAAC,MAAM;QAC5B,IAAI,EAAE,aAAa,CAAC,GAAG;QACvB,OAAO,EAAE;YACP,GAAG,aAAa,CAAC,OAAO;YACxB,IAAI,EAAE,aAAa,WAAW,EAAE;SACjC;KACF,EACD,aAAa,CAAC,EAAE;QACd,cAAc,CAAC,SAAS,CACtB,aAAa,CAAC,UAAU,IAAI,GAAG,EAC/B,mBAAmB,CAAC,aAAa,CAAC,OAAO,CAAC,CAC3C,CAAA;QACD,aAAa,CAAC,IAAI,CAAC,cAAc,CAAC,CAAA;IACpC,CAAC,CACF,CAAA;IAED,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAY,EAAE,EAAE;QACxC,IAAI,CAAC,cAAc,CAAC,WAAW,EAAE,CAAC;YAChC,cAAc,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC,CAAA;QACjE,CAAC;QACD,cAAc,CAAC,GAAG,CAAC,sCAAsC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAA;IAC3E,CAAC,CAAC,CAAA;IACF,aAAa,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC,CAAA;IACzD,aAAa,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;AAClC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { TestProjectName } from './cli-arguments.js';
2
+ import type { ResolvedApplicationConfig } from './config.js';
3
+ export declare function runTests(config: ResolvedApplicationConfig, project: TestProjectName | undefined, watch: boolean): Promise<void>;
@@ -0,0 +1,13 @@
1
+ import { startVitest } from 'vitest/node';
2
+ import { createTestConfig } from './test-config.js';
3
+ export async function runTests(config, project, watch) {
4
+ const vitest = await startVitest('test', [], {
5
+ project: project === undefined ? undefined : [project],
6
+ root: config.root,
7
+ run: !watch,
8
+ watch,
9
+ }, createTestConfig(config));
10
+ if (!watch)
11
+ await vitest.close();
12
+ }
13
+ //# sourceMappingURL=run-tests.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-tests.js","sourceRoot":"","sources":["../../src/application/run-tests.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAIzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAEhD,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,MAAiC,EACjC,OAAoC,EACpC,KAAc;IAEd,MAAM,MAAM,GAAG,MAAM,WAAW,CAC9B,MAAM,EACN,EAAE,EACF;QACE,OAAO,EAAE,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;QACtD,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,GAAG,EAAE,CAAC,KAAK;QACX,KAAK;KACN,EACD,gBAAgB,CAAC,MAAM,CAAC,CACzB,CAAA;IAED,IAAI,CAAC,KAAK;QAAE,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;AAClC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { BuildOptions } from 'esbuild';
2
+ import type { ResolvedApplicationConfig } from './config.js';
3
+ export declare function createServerBuildOptions(config: ResolvedApplicationConfig): BuildOptions;
@@ -0,0 +1,17 @@
1
+ import path from 'node:path';
2
+ export function createServerBuildOptions(config) {
3
+ return {
4
+ bundle: true,
5
+ external: [...config.build.server.external],
6
+ entryPoints: [
7
+ path.join(config.root, config.paths.server, 'index.ts'),
8
+ ],
9
+ format: 'cjs',
10
+ outfile: path.join(config.root, config.paths.output, 'server', 'index.cjs'),
11
+ platform: 'node',
12
+ sourcemap: config.build.sourceMaps,
13
+ supported: { decorators: false },
14
+ target: config.build.target,
15
+ };
16
+ }
17
+ //# sourceMappingURL=server-build.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-build.js","sourceRoot":"","sources":["../../src/application/server-build.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAA;AAM5B,MAAM,UAAU,wBAAwB,CACtC,MAAiC;IAEjC,OAAO;QACL,MAAM,EAAE,IAAI;QACZ,QAAQ,EAAE,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC;QAC3C,WAAW,EAAE;YACX,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,UAAU,CAAC;SACxD;QACD,MAAM,EAAE,KAAK;QACb,OAAO,EAAE,IAAI,CAAC,IAAI,CAChB,MAAM,CAAC,IAAI,EACX,MAAM,CAAC,KAAK,CAAC,MAAM,EACnB,QAAQ,EACR,WAAW,CACZ;QACD,QAAQ,EAAE,MAAM;QAChB,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,UAAU;QAClC,SAAS,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE;QAChC,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM;KAC5B,CAAA;AACH,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { InlineConfig } from 'vite';
2
+ import type { ResolvedApplicationConfig } from './config.js';
3
+ export type ApplicationTestConfig = InlineConfig;
4
+ export declare function createTestConfig(config: ResolvedApplicationConfig): ApplicationTestConfig;
@@ -0,0 +1,91 @@
1
+ import { existsSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { babel } from '@rollup/plugin-babel';
4
+ import tailwindcss from '@tailwindcss/vite';
5
+ import react from '@vitejs/plugin-react';
6
+ import { playwright } from '@vitest/browser-playwright';
7
+ export function createTestConfig(config) {
8
+ const alias = { '@': config.root };
9
+ const projectRoots = '{client,common,server,test}';
10
+ return {
11
+ configFile: false,
12
+ root: config.root,
13
+ resolve: { alias },
14
+ test: {
15
+ projects: [
16
+ {
17
+ resolve: { alias },
18
+ test: {
19
+ name: 'unit',
20
+ include: [
21
+ `${projectRoots}/**/*.unit.spec.ts`,
22
+ `${projectRoots}/**/*.unit.spec.tsx`,
23
+ ],
24
+ setupFiles: optionalSetup(config.root, 'setup-unit.ts'),
25
+ },
26
+ },
27
+ {
28
+ plugins: [
29
+ babel({
30
+ babelHelpers: 'bundled',
31
+ babelrc: false,
32
+ configFile: false,
33
+ extensions: ['.ts', '.tsx'],
34
+ plugins: [
35
+ [
36
+ '@babel/plugin-proposal-decorators',
37
+ { version: '2023-11' },
38
+ ],
39
+ ],
40
+ }),
41
+ ],
42
+ resolve: { alias },
43
+ test: {
44
+ name: 'integration',
45
+ fileParallelism: false,
46
+ hookTimeout: config.test.integration.timeout,
47
+ include: [
48
+ `${projectRoots}/**/*.integration.spec.ts`,
49
+ `${projectRoots}/**/*.integration.spec.tsx`,
50
+ ],
51
+ setupFiles: optionalSetup(config.root, 'setup-integration.ts'),
52
+ testTimeout: config.test.integration.timeout,
53
+ },
54
+ },
55
+ {
56
+ plugins: [react(), tailwindcss()],
57
+ resolve: {
58
+ alias: {
59
+ ...alias,
60
+ react: path.join(config.root, 'node_modules', 'react'),
61
+ 'react-dom': path.join(config.root, 'node_modules', 'react-dom'),
62
+ },
63
+ },
64
+ test: {
65
+ name: 'browser',
66
+ // Vitest's browser mock API requires globals when it is mirrored
67
+ // through the public typeferry/test entry point.
68
+ globals: true,
69
+ browser: {
70
+ enabled: true,
71
+ headless: true,
72
+ instances: [{ browser: config.test.browser.browser }],
73
+ provider: playwright(),
74
+ screenshotFailures: false,
75
+ },
76
+ include: [
77
+ `${projectRoots}/**/*.browser.spec.ts`,
78
+ `${projectRoots}/**/*.browser.spec.tsx`,
79
+ ],
80
+ setupFiles: optionalSetup(config.root, 'setup-browser.ts'),
81
+ },
82
+ },
83
+ ],
84
+ },
85
+ };
86
+ }
87
+ function optionalSetup(root, fileName) {
88
+ const setupPath = path.join(root, 'test', fileName);
89
+ return existsSync(setupPath) ? [setupPath] : [];
90
+ }
91
+ //# sourceMappingURL=test-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-config.js","sourceRoot":"","sources":["../../src/application/test-config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { InlineConfig } from 'vite';
2
+ import type { ResolvedApplicationConfig } from './config.js';
3
+ export declare function createViteConfig(config: ResolvedApplicationConfig, command: 'develop' | 'build'): InlineConfig;
@@ -0,0 +1,43 @@
1
+ import path from 'node:path';
2
+ import tailwindcss from '@tailwindcss/vite';
3
+ import react from '@vitejs/plugin-react';
4
+ import { createTypeFerryDevProxy } from './proxy.js';
5
+ export function createViteConfig(config, command) {
6
+ const plugins = [react(), tailwindcss()];
7
+ if (command === 'develop') {
8
+ plugins.unshift(createTypeFerryDevProxy(config.development.serverPort));
9
+ }
10
+ return {
11
+ configFile: false,
12
+ root: path.join(config.root, config.paths.client),
13
+ plugins,
14
+ server: {
15
+ allowedHosts: true,
16
+ host: '0.0.0.0',
17
+ port: config.development.clientPort,
18
+ hmr: { overlay: true },
19
+ watch: {
20
+ ignored: ['**/*.spec.ts', '**/*.spec.tsx'],
21
+ },
22
+ },
23
+ resolve: {
24
+ preserveSymlinks: true,
25
+ alias: {
26
+ react: path.join(config.root, 'node_modules', 'react'),
27
+ 'react-dom': path.join(config.root, 'node_modules', 'react-dom'),
28
+ '@': config.root,
29
+ },
30
+ },
31
+ build: {
32
+ outDir: path.relative(path.join(config.root, config.paths.client), path.join(config.root, config.paths.output, 'client')),
33
+ emptyOutDir: true,
34
+ manifest: true,
35
+ target: config.build.target,
36
+ sourcemap: config.build.sourceMaps,
37
+ rollupOptions: {
38
+ input: path.join(config.root, config.paths.client, 'index.html'),
39
+ },
40
+ },
41
+ };
42
+ }
43
+ //# sourceMappingURL=vite-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite-config.js","sourceRoot":"","sources":["../../src/application/vite-config.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,OAAO,WAAW,MAAM,mBAAmB,CAAA;AAC3C,OAAO,KAAK,MAAM,sBAAsB,CAAA;AAIxC,OAAO,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAA;AAEjD,MAAM,UAAU,gBAAgB,CAC9B,MAAiC,EACjC,OAA4B;IAE5B,MAAM,OAAO,GAAmB,CAAC,KAAK,EAAE,EAAE,WAAW,EAAE,CAAC,CAAA;IACxD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,OAAO,CAAC,OAAO,CAAC,uBAAuB,CAAC,MAAM,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,CAAA;IACzE,CAAC;IAED,OAAO;QACL,UAAU,EAAE,KAAK;QACjB,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC;QACjD,OAAO;QACP,MAAM,EAAE;YACN,YAAY,EAAE,IAAI;YAClB,IAAI,EAAE,SAAS;YACf,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,UAAU;YACnC,GAAG,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;YACtB,KAAK,EAAE;gBACL,OAAO,EAAE,CAAC,cAAc,EAAE,eAAe,CAAC;aAC3C;SACF;QACD,OAAO,EAAE;YACP,gBAAgB,EAAE,IAAI;YACtB,KAAK,EAAE;gBACL,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,cAAc,EAAE,OAAO,CAAC;gBACtD,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,cAAc,EAAE,WAAW,CAAC;gBAChE,GAAG,EAAE,MAAM,CAAC,IAAI;aACjB;SACF;QACD,KAAK,EAAE;YACL,MAAM,EAAE,IAAI,CAAC,QAAQ,CACnB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,EAC3C,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC,CACtD;YACD,WAAW,EAAE,IAAI;YACjB,QAAQ,EAAE,IAAI;YACd,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM;YAC3B,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,UAAU;YAClC,aAAa,EAAE;gBACb,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,YAAY,CAAC;aACjE;SACF;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import { CLI_USAGE, } from '../application/cli-arguments.js';
3
+ import { runCli } from './run.js';
4
+ runCli(process.argv.slice(2)).catch((error) => {
5
+ const message = error instanceof Error ? error.message : String(error);
6
+ console.error(message);
7
+ console.error(CLI_USAGE);
8
+ process.exitCode = 1;
9
+ });
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAEA,OAAO,EACL,SAAS,GACV,MAAM,8BAA8B,CAAA;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAE9B,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IACrD,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IACtE,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;IACtB,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;IACxB,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAA;AACtB,CAAC,CAAC,CAAA"}
@@ -0,0 +1 @@
1
+ export declare function runCli(arguments_: readonly string[]): Promise<void>;
@@ -0,0 +1,21 @@
1
+ import { runBuild } from '../application/build.js';
2
+ import { parseCliArguments } from '../application/cli-arguments.js';
3
+ import { loadApplicationConfig } from '../application/config.js';
4
+ import { runDevelop } from '../application/develop.js';
5
+ import { findApplicationRoot } from '../application/paths.js';
6
+ import { runTests } from '../application/run-tests.js';
7
+ export async function runCli(arguments_) {
8
+ const parsed = parseCliArguments(arguments_);
9
+ const root = await findApplicationRoot(process.cwd());
10
+ const config = await loadApplicationConfig(root);
11
+ if (parsed.command === 'build') {
12
+ await runBuild(config);
13
+ return;
14
+ }
15
+ if (parsed.command === 'develop') {
16
+ await runDevelop(config, parsed.serverArguments);
17
+ return;
18
+ }
19
+ await runTests(config, parsed.project, parsed.watch);
20
+ }
21
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/cli/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAA;AAChE,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AAEnD,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,UAA6B;IACxD,MAAM,MAAM,GAAG,iBAAiB,CAAC,UAAU,CAAC,CAAA;IAC5C,MAAM,IAAI,GAAG,MAAM,mBAAmB,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;IACrD,MAAM,MAAM,GAAG,MAAM,qBAAqB,CAAC,IAAI,CAAC,CAAA;IAEhD,IAAI,MAAM,CAAC,OAAO,KAAK,OAAO,EAAE,CAAC;QAC/B,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAA;QACtB,OAAM;IACR,CAAC;IAED,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QACjC,MAAM,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,eAAe,CAAC,CAAA;QAChD,OAAM;IACR,CAAC;IAED,MAAM,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,CAAA;AACtD,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Mirrors the installed Vitest release for TypeFerry application tests.
3
+ *
4
+ * This surface intentionally follows Vitest and does not provide independent
5
+ * TypeFerry compatibility guarantees for individual exports.
6
+ */
7
+ export * from 'vitest';
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Mirrors the installed Vitest release for TypeFerry application tests.
3
+ *
4
+ * This surface intentionally follows Vitest and does not provide independent
5
+ * TypeFerry compatibility guarantees for individual exports.
6
+ */
7
+ export * from 'vitest';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,cAAc,QAAQ,CAAA"}
package/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "typeferry",
3
- "version": "0.7.5",
3
+ "version": "0.9.0",
4
4
  "description": "Type-safe real-time RPC framework for TypeScript",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "license": "MIT",
8
+ "bin": {
9
+ "typeferry": "./dist/cli/index.js"
10
+ },
8
11
  "files": [
9
12
  "dist"
10
13
  ],
@@ -57,6 +60,14 @@
57
60
  "types": "./dist/utils/index.d.ts",
58
61
  "import": "./dist/utils/index.js"
59
62
  },
63
+ "./config": {
64
+ "types": "./dist/application/config.d.ts",
65
+ "import": "./dist/application/config.js"
66
+ },
67
+ "./test": {
68
+ "types": "./dist/testing/index.d.ts",
69
+ "import": "./dist/testing/index.js"
70
+ },
60
71
  "./*": {
61
72
  "types": "./dist/*.d.ts",
62
73
  "import": "./dist/*.js"
@@ -64,6 +75,7 @@
64
75
  },
65
76
  "scripts": {
66
77
  "build": "rm -rf dist && tsc -p tsconfig.build.json && node scripts/prepare-dist.mjs",
78
+ "verify:consumer": "node ../scripts/verify-application-consumer.mjs",
67
79
  "typecheck": "tsc --noEmit",
68
80
  "test": "npm run test:unit && npm run test:integration && npm run test:browser",
69
81
  "test:watch": "npm run test:unit:watch",
@@ -78,18 +90,32 @@
78
90
  "lint:fix": "eslint src/ --fix"
79
91
  },
80
92
  "dependencies": {
93
+ "@babel/core": "^7.29.7",
94
+ "@babel/plugin-proposal-decorators": "^7.29.7",
81
95
  "@hono/node-server": "^2.1.1",
82
96
  "@lukeed/uuid": "^2.0.1",
97
+ "@rollup/plugin-babel": "^7.1.0",
98
+ "@tailwindcss/vite": "^4.3.3",
99
+ "@vitejs/plugin-react": "5.2.0",
100
+ "@vitest/browser": "4.1.11",
101
+ "@vitest/browser-playwright": "4.1.11",
102
+ "esbuild": "0.28.1",
83
103
  "fast-deep-equal": "^3.1.3",
84
104
  "google-auth-library": "^10.9.0",
85
105
  "hono": "^4.13.3",
106
+ "jiti": "^2.6.1",
86
107
  "jsonwebtoken": "^9.0.2",
87
108
  "limiter": "^2.1.0",
88
109
  "memoizee": "^0.4.15",
110
+ "playwright": "1.62.1",
89
111
  "query-string": "^9.5.0",
90
112
  "redis": "^4.6.7",
113
+ "tailwindcss": "^4.3.3",
114
+ "tree-kill": "^1.2.2",
91
115
  "ua-parser-js": "^2.0.10",
92
116
  "use-debounce": "^10.1.1",
117
+ "vite": "7.3.6",
118
+ "vitest": "4.1.11",
93
119
  "ws": "^8.21.3",
94
120
  "zod": "^4.4.3"
95
121
  },
@@ -120,24 +146,18 @@
120
146
  "@types/sinon": "^10.0.16",
121
147
  "@types/ua-parser-js": "^0.7.39",
122
148
  "@types/ws": "^8",
123
- "@vitest/browser": "^4.1.11",
124
- "@vitest/browser-playwright": "^4.1.11",
125
149
  "@vitest/coverage-v8": "^4.1.11",
126
- "esbuild": "^0.28.1",
127
150
  "eslint": "^10.9.0",
128
151
  "eslint-plugin-security": "^4.0.1",
129
152
  "globals": "^17.7.0",
130
153
  "jsdom": "^30.0.1",
131
154
  "mongodb": "^7.5.0",
132
- "playwright": "^1.61.1",
133
155
  "postcss": "^8.5.26",
134
156
  "react": "^19.2.3",
135
157
  "react-dom": "^19.2.3",
136
158
  "sinon": "^18.0.0",
137
159
  "typescript": "^5.9.3",
138
- "typescript-eslint": "^8.67.0",
139
- "vite": "^7.3.3",
140
- "vitest": "^4.1.11"
160
+ "typescript-eslint": "^8.67.0"
141
161
  },
142
162
  "overrides": {
143
163
  "body-parser": "^2.3.0",
@@ -147,8 +167,7 @@
147
167
  "nanoid": "^6.0.1",
148
168
  "postcss": "^8.5.26",
149
169
  "qs": "^6.15.3",
150
- "undici": "^7.29.0",
151
- "vite": "^7.3.3"
170
+ "undici": "^7.29.0"
152
171
  },
153
172
  "engines": {
154
173
  "node": ">=24.19.0 <27",