diff --git a/.changeset/fuzzy-browsers-connect.md b/.changeset/fuzzy-browsers-connect.md new file mode 100644 index 0000000..dbe1768 --- /dev/null +++ b/.changeset/fuzzy-browsers-connect.md @@ -0,0 +1,5 @@ +--- +"t3code-cli": minor +--- + +add public preview automation, pairing metadata, shell snapshot, and mirrored T3 package APIs diff --git a/README.md b/README.md index 3f2adf1..c638b33 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,92 @@ t3cli --environment ... # Use a spec - Set `T3CLI_ENV=` to select an environment when `--environment` is omitted - `T3CODE_URL` and `T3CODE_TOKEN` override the selected environment only when both are set +## Programmatic API + +Pairing accepts T3 client presentation metadata. Automation clients should identify themselves as +bots and provide a label that users can recognize in T3 Code: + +```ts +import * as Effect from "effect/Effect"; +import { T3AuthPairing } from "t3code-cli/auth"; +import { T3AuthPairingLayer } from "t3code-cli/runtime"; + +const pair = Effect.gen(function* () { + const auth = yield* T3AuthPairing; + return yield* auth.pair({ + pairingUrl, + clientMetadata: { + label: "aperture bridge", + deviceType: "bot", + os: "linux", + }, + }); +}).pipe(Effect.provide(T3AuthPairingLayer)); +``` + +`T3PreviewAutomation` registers a preview host and exposes T3's request stream, response RPC, and +focus RPC. Compose its live layer with the existing connection layers when supplying credentials +directly: + +```ts +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Stream from "effect/Stream"; +import { T3CodeConnectionProviderLive } from "t3code-cli/connection"; +import { T3CodeNodeRpcLayer } from "t3code-cli/node"; +import { T3PreviewAutomation, T3PreviewAutomationLive } from "t3code-cli/preview"; +import { T3RpcOperationsLive } from "t3code-cli/rpc"; + +const connectionLayer = T3CodeConnectionProviderLive({ + origin: { url }, + auth: { token }, +}); +const rpcLayer = T3CodeNodeRpcLayer.pipe(Layer.provide(connectionLayer)); +const previewLayer = T3PreviewAutomationLive.pipe( + Layer.provide(T3RpcOperationsLive.pipe(Layer.provide(rpcLayer))), +); + +const runHost = Effect.gen(function* () { + const preview = yield* T3PreviewAutomation; + yield* preview + .connect({ clientId, environmentId, supportedOperations }) + .pipe(Stream.runForEach(handlePreviewEvent)); +}).pipe(Effect.scoped, Effect.provide(previewLayer)); +``` + +`T3PreviewAutomationLayer` is the shorter CLI-config-backed layer for callers that use a selected +`t3cli` environment. + +`T3Orchestration.watchShellSnapshots()` emits the initial shell snapshot and a reduced snapshot for +each later project or thread event. A new full snapshot resets the reducer after reconnects. + +T3 Code's shared and client-runtime export maps are mirrored under `t3code-cli/shared/*` and +`t3code-cli/client-runtime/*`. For example, the viewport catalog and resolver are available through +the matching shared subpath: + +```ts +import { + PREVIEW_VIEWPORT_PRESETS, + resolvePreviewViewport, +} from "t3code-cli/shared/previewViewport"; +import { PreviewViewportSetting } from "t3code-cli/contracts"; +``` + +## Upstream Maintenance + +Synchronize dependency versions and patches with the current `upstream-t3code` revision: + +```sh +pnpm sync-upstream +``` + +Pass `--target` to update the submodule first. It accepts `stable`, `nightly`, `main`, a version such +as `0.0.31` or `v0.0.31`, or a Git ref or commit: + +```sh +pnpm sync-upstream --target stable +``` + ## Project Management ```sh diff --git a/package.json b/package.json index 299cc5d..7c430ff 100644 --- a/package.json +++ b/package.json @@ -26,72 +26,87 @@ "src" ], "type": "module", - "types": "./dist/src/index.d.ts", + "types": "./dist/index.d.ts", "exports": { ".": { - "types": "./dist/src/index.d.ts", + "types": "./dist/index.d.ts", "import": "./dist/index.js", "default": "./dist/index.js" }, "./application": { - "types": "./dist/src/application/index.d.ts", + "types": "./dist/application.d.ts", "import": "./dist/application.js", "default": "./dist/application.js" }, "./auth": { - "types": "./dist/src/auth/index.d.ts", + "types": "./dist/auth.d.ts", "import": "./dist/auth.js", "default": "./dist/auth.js" }, "./cli": { - "types": "./dist/src/cli/index.d.ts", + "types": "./dist/cli.d.ts", "import": "./dist/cli.js", "default": "./dist/cli.js" }, + "./client-runtime/*": { + "types": "./dist/client-runtime/*.d.ts", + "import": "./dist/client-runtime/*.js", + "default": "./dist/client-runtime/*.js" + }, "./config": { - "types": "./dist/src/config/index.d.ts", + "types": "./dist/config.d.ts", "import": "./dist/config.js", "default": "./dist/config.js" }, "./connection": { - "types": "./dist/src/connection/index.d.ts", + "types": "./dist/connection.d.ts", "import": "./dist/connection.js", "default": "./dist/connection.js" }, "./contracts": { - "types": "./dist/src/contracts/index.d.ts", + "types": "./dist/contracts.d.ts", "import": "./dist/contracts.js", "default": "./dist/contracts.js" }, "./node": { - "types": "./dist/src/node/index.d.ts", + "types": "./dist/node.d.ts", "import": "./dist/node.js", "default": "./dist/node.js" }, "./orchestration": { - "types": "./dist/src/orchestration/index.d.ts", + "types": "./dist/orchestration.d.ts", "import": "./dist/orchestration.js", "default": "./dist/orchestration.js" }, + "./preview": { + "types": "./dist/preview.d.ts", + "import": "./dist/preview.js", + "default": "./dist/preview.js" + }, "./rpc": { - "types": "./dist/src/rpc/index.d.ts", + "types": "./dist/rpc.d.ts", "import": "./dist/rpc.js", "default": "./dist/rpc.js" }, "./runtime": { - "types": "./dist/src/runtime/index.d.ts", + "types": "./dist/runtime.d.ts", "import": "./dist/runtime.js", "default": "./dist/runtime.js" }, + "./shared/*": { + "types": "./dist/shared/*.d.ts", + "import": "./dist/shared/*.js", + "default": "./dist/shared/*.js" + }, "./t3tools": { - "types": "./dist/src/t3tools/index.d.ts", + "types": "./dist/t3tools.d.ts", "import": "./dist/t3tools.js", "default": "./dist/t3tools.js" }, "./package.json": "./package.json" }, "scripts": { - "build": "vp pack && tsc -p tsconfig.dts.json", + "build": "vp pack", "check": "vp check", "format": "vp fmt", "format:check": "vp fmt --check", @@ -103,12 +118,13 @@ "release:version": "changeset version && pnpm format", "release:check": "pnpm check && pnpm typecheck && pnpm pack --dry-run", "release:publish": "changeset publish", + "sync-upstream": "node scripts/sync-upstream.ts", "typecheck": "tsc --noEmit" }, "dependencies": { - "@effect/platform-node": "4.0.0-beta.78", + "@effect/platform-node": "4.0.0-beta.102", "@napi-rs/keyring": "^1.3.0", - "effect": "4.0.0-beta.78", + "effect": "4.0.0-beta.102", "marked": "^15.0.12", "marked-terminal": "^7.3.0", "string-width": "^8.2.2", @@ -119,10 +135,12 @@ "@effect/vitest": "catalog:", "@t3tools/client-runtime": "link:upstream-t3code/packages/client-runtime", "@t3tools/contracts": "workspace:*", + "@t3tools/shared": "link:upstream-t3code/packages/shared", "@total-typescript/shoehorn": "^0.1.2", "@types/node": "^26.1.2", "typescript": "^7.0.2", - "vite-plus": "^0.2.6" + "vite-plus": "^0.2.6", + "yaml": "catalog:" }, "engines": { "node": ">=24.0.0" diff --git a/patches/@effect__vitest@4.0.0-beta.78.patch b/patches/@effect__vitest@4.0.0-beta.78.patch deleted file mode 100644 index 719d748..0000000 --- a/patches/@effect__vitest@4.0.0-beta.78.patch +++ /dev/null @@ -1,54 +0,0 @@ -diff --git a/dist/index.d.ts b/dist/index.d.ts ---- a/dist/index.d.ts -+++ b/dist/index.d.ts -@@ -7,8 +7,8 @@ - import type * as Schema from "effect/Schema"; - import type * as Scope from "effect/Scope"; - import type * as FC from "effect/testing/FastCheck"; --import * as V from "vitest"; -+import * as V from "vite-plus/test"; - /** - * @since 4.0.0 - */ --export * from "vitest"; -+export * from "vite-plus/test"; -diff --git a/dist/index.js b/dist/index.js ---- a/dist/index.js -+++ b/dist/index.js -@@ -1,6 +1,6 @@ --import * as V from "vitest"; -+import * as V from "vite-plus/test"; - import * as internal from "./internal/internal.js"; - /** - * @since 4.0.0 - */ --export * from "vitest"; -+export * from "vite-plus/test"; -diff --git a/dist/internal/internal.js b/dist/internal/internal.js ---- a/dist/internal/internal.js -+++ b/dist/internal/internal.js -@@ -1,5 +1,5 @@ - /** - * @since 4.0.0 - */ --import { getCurrentSuite } from "@vitest/runner"; -+import { getCurrentSuite } from "vite-plus/test/plugins/runner"; - import * as Cause from "effect/Cause"; -@@ -14,6 +14,6 @@ - import * as Scope from "effect/Scope"; - import * as fc from "effect/testing/FastCheck"; - import * as TestClock from "effect/testing/TestClock"; - import * as TestConsole from "effect/testing/TestConsole"; --import * as V from "vitest"; -+import * as V from "vite-plus/test"; - const runPromise = /*#__PURE__*/Effect.fnUntraced(function* (effect, _ctx) { -diff --git a/dist/utils.js b/dist/utils.js ---- a/dist/utils.js -+++ b/dist/utils.js -@@ -3,5 +3,5 @@ - import * as Option from "effect/Option"; - import * as Predicate from "effect/Predicate"; - import * as Result from "effect/Result"; - import * as assert from "node:assert"; --import { assert as vassert } from "vitest"; -+import { assert as vassert } from "vite-plus/test"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ae0ac8b..00fad57 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,26 +7,26 @@ settings: catalogs: default: '@effect/platform-node': - specifier: 4.0.0-beta.78 - version: 4.0.0-beta.78 + specifier: 4.0.0-beta.102 + version: 4.0.0-beta.102 '@effect/vitest': - specifier: 4.0.0-beta.78 - version: 4.0.0-beta.78 + specifier: 4.0.0-beta.102 + version: 4.0.0-beta.102 '@noble/curves': - specifier: 2.2.0 - version: 2.2.0 + specifier: 1.9.1 + version: 1.9.1 '@noble/hashes': - specifier: 2.2.0 - version: 2.2.0 + specifier: 1.8.0 + version: 1.8.0 '@types/node': specifier: ^26.1.2 version: 26.1.2 effect: - specifier: 4.0.0-beta.78 - version: 4.0.0-beta.78 + specifier: 4.0.0-beta.102 + version: 4.0.0-beta.102 jose: - specifier: 6.2.4 - version: 6.2.4 + specifier: 6.2.2 + version: 6.2.2 vite-plus: specifier: 0.2.6 version: 0.2.6 @@ -35,23 +35,29 @@ catalogs: version: 2.9.0 patchedDependencies: - '@effect/vitest@4.0.0-beta.78': - hash: 74fd480109c3bd975255bf5573a64718451bcae02b523c432bc18e9aa27b52fd - path: patches/@effect__vitest@4.0.0-beta.78.patch + '@effect/platform-node@4.0.0-beta.102': + hash: cae7efcda6fd29f4db2efd357cb7dc6c41cb6adc957901d437e13ed1f3c017f0 + path: upstream-t3code/patches/@effect__platform-node@4.0.0-beta.102.patch + '@effect/vitest@4.0.0-beta.102': + hash: a607339aab944136a084a05f159aa0f5a69776934d4b835dab8f9992f1c13425 + path: upstream-t3code/patches/@effect__vitest@4.0.0-beta.102.patch + effect@4.0.0-beta.102: + hash: 71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488 + path: upstream-t3code/patches/effect@4.0.0-beta.102.patch importers: .: dependencies: '@effect/platform-node': - specifier: 4.0.0-beta.78 - version: 4.0.0-beta.78(effect@4.0.0-beta.78)(ioredis@5.10.1) + specifier: 4.0.0-beta.102 + version: 4.0.0-beta.102(patch_hash=cae7efcda6fd29f4db2efd357cb7dc6c41cb6adc957901d437e13ed1f3c017f0)(effect@4.0.0-beta.102(patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488))(ioredis@5.10.1) '@napi-rs/keyring': specifier: ^1.3.0 version: 1.3.0 effect: - specifier: 4.0.0-beta.78 - version: 4.0.0-beta.78 + specifier: 4.0.0-beta.102 + version: 4.0.0-beta.102(patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488) marked: specifier: ^15.0.12 version: 15.0.12 @@ -70,13 +76,16 @@ importers: version: 2.31.1(@types/node@26.1.2) '@effect/vitest': specifier: 'catalog:' - version: 4.0.0-beta.78(patch_hash=74fd480109c3bd975255bf5573a64718451bcae02b523c432bc18e9aa27b52fd)(effect@4.0.0-beta.78)(vitest@4.1.10) + version: 4.0.0-beta.102(patch_hash=a607339aab944136a084a05f159aa0f5a69776934d4b835dab8f9992f1c13425)(effect@4.0.0-beta.102(patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488))(vitest@4.1.10) '@t3tools/client-runtime': specifier: link:upstream-t3code/packages/client-runtime version: link:upstream-t3code/packages/client-runtime '@t3tools/contracts': specifier: workspace:* version: link:upstream-t3code/packages/contracts + '@t3tools/shared': + specifier: link:upstream-t3code/packages/shared + version: link:upstream-t3code/packages/shared '@total-typescript/shoehorn': specifier: ^0.1.2 version: 0.1.2 @@ -89,6 +98,9 @@ importers: vite-plus: specifier: ^0.2.6 version: 0.2.6(@types/node@26.1.2)(typescript@7.0.2)(vite@8.0.14(@types/node@26.1.2)(yaml@2.9.0))(yaml@2.9.0) + yaml: + specifier: 'catalog:' + version: 2.9.0 upstream-t3code/packages/client-runtime: dependencies: @@ -100,11 +112,11 @@ importers: version: link:../shared effect: specifier: 'catalog:' - version: 4.0.0-beta.78 + version: 4.0.0-beta.102(patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488) devDependencies: '@effect/vitest': specifier: 'catalog:' - version: 4.0.0-beta.78(patch_hash=74fd480109c3bd975255bf5573a64718451bcae02b523c432bc18e9aa27b52fd)(effect@4.0.0-beta.78)(vitest@4.1.10) + version: 4.0.0-beta.102(patch_hash=a607339aab944136a084a05f159aa0f5a69776934d4b835dab8f9992f1c13425)(effect@4.0.0-beta.102(patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488))(vitest@4.1.10) vite-plus: specifier: 'catalog:' version: 0.2.6(@types/node@26.1.2)(typescript@7.0.2)(vite@8.0.14(@types/node@26.1.2)(yaml@2.9.0))(yaml@2.9.0) @@ -113,11 +125,11 @@ importers: dependencies: effect: specifier: 'catalog:' - version: 4.0.0-beta.78 + version: 4.0.0-beta.102(patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488) devDependencies: '@effect/vitest': specifier: 'catalog:' - version: 4.0.0-beta.78(patch_hash=74fd480109c3bd975255bf5573a64718451bcae02b523c432bc18e9aa27b52fd)(effect@4.0.0-beta.78)(vitest@4.1.10) + version: 4.0.0-beta.102(patch_hash=a607339aab944136a084a05f159aa0f5a69776934d4b835dab8f9992f1c13425)(effect@4.0.0-beta.102(patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488))(vitest@4.1.10) vite-plus: specifier: 'catalog:' version: 0.2.6(@types/node@26.1.2)(typescript@7.0.2)(vite@8.0.14(@types/node@26.1.2)(yaml@2.9.0))(yaml@2.9.0) @@ -126,29 +138,29 @@ importers: dependencies: '@noble/curves': specifier: 'catalog:' - version: 2.2.0 + version: 1.9.1 '@noble/hashes': specifier: 'catalog:' - version: 2.2.0 + version: 1.8.0 '@t3tools/contracts': specifier: workspace:* version: link:../contracts effect: specifier: 'catalog:' - version: 4.0.0-beta.78 + version: 4.0.0-beta.102(patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488) jose: specifier: 'catalog:' - version: 6.2.4 + version: 6.2.2 yaml: specifier: 'catalog:' version: 2.9.0 devDependencies: '@effect/platform-node': specifier: 'catalog:' - version: 4.0.0-beta.78(effect@4.0.0-beta.78)(ioredis@5.10.1) + version: 4.0.0-beta.102(patch_hash=cae7efcda6fd29f4db2efd357cb7dc6c41cb6adc957901d437e13ed1f3c017f0)(effect@4.0.0-beta.102(patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488))(ioredis@5.10.1) '@effect/vitest': specifier: 'catalog:' - version: 4.0.0-beta.78(patch_hash=74fd480109c3bd975255bf5573a64718451bcae02b523c432bc18e9aa27b52fd)(effect@4.0.0-beta.78)(vitest@4.1.10) + version: 4.0.0-beta.102(patch_hash=a607339aab944136a084a05f159aa0f5a69776934d4b835dab8f9992f1c13425)(effect@4.0.0-beta.102(patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488))(vitest@4.1.10) '@types/node': specifier: 'catalog:' version: 26.1.2 @@ -232,23 +244,23 @@ packages: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} - '@effect/platform-node-shared@4.0.0-beta.78': - resolution: {integrity: sha512-mo0ddTPATyCMyqzQasYDL7+NI29vozoMplom+qu9f/onDTd4xG5hvEEfGxfL0Ljygui6keG/YE/E9OZVf2z5WA==} + '@effect/platform-node-shared@4.0.0-beta.102': + resolution: {integrity: sha512-gVd793I72MrkX4dXo7eYtRKfNj0RW4eMRfVEKEJI16h2+mBDCzQ+gqMrog2hSTHQnaIbvbYShNQ4TVGuRCYZeQ==} engines: {node: '>=18.0.0'} peerDependencies: - effect: ^4.0.0-beta.78 + effect: ^4.0.0-beta.102 - '@effect/platform-node@4.0.0-beta.78': - resolution: {integrity: sha512-8ONrIS5/R9dq+0BJ6v3kUXNEkfjU6S3GzIYCH5gmHdiriRvIoBhXYNAITfRvZpfx1JPrKuP70cHyuQDjmJcDkQ==} + '@effect/platform-node@4.0.0-beta.102': + resolution: {integrity: sha512-wYVAU9jAePT+gouMr/EVz1CaW6yDLjPAgXYeEFLz7wugT5iZhFRag6mqajV/wwN3bzWP2fzZHokLuPmqD/rqeA==} engines: {node: '>=18.0.0'} peerDependencies: - effect: ^4.0.0-beta.78 + effect: ^4.0.0-beta.102 ioredis: ^5.7.0 - '@effect/vitest@4.0.0-beta.78': - resolution: {integrity: sha512-5KQsQYrQ/o7mfOVAxRtNnfD9M0W4OI6yQd0n/m2N7OOLxTdX4FwN4s/X4obykBC7ZEwH+bzMrFJiB4pq9lrQKQ==} + '@effect/vitest@4.0.0-beta.102': + resolution: {integrity: sha512-4dipFAYG6imOzrY3zy3BgzCJkbb9xESyzUef0WSx8bsK0/SpITqbJpdwKeOyDWLZ+rimjua8IbTFMAde865pIQ==} peerDependencies: - effect: ^4.0.0-beta.78 + effect: ^4.0.0-beta.102 vitest: ^3.0.0 || ^4.0.0 '@emnapi/core@1.10.0': @@ -398,13 +410,13 @@ packages: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 - '@noble/curves@2.2.0': - resolution: {integrity: sha512-T/BoHgFXirb0ENSPBquzX0rcjXeM6Lo892a2jlYJkqk83LqZx0l1Of7DzlKJ6jkpvMrkHSnAcgb5JegL8SeIkQ==} - engines: {node: '>= 20.19.0'} + '@noble/curves@1.9.1': + resolution: {integrity: sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==} + engines: {node: ^14.21.3 || >=16} - '@noble/hashes@2.2.0': - resolution: {integrity: sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==} - engines: {node: '>= 20.19.0'} + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -1381,8 +1393,8 @@ packages: dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} - effect@4.0.0-beta.78: - resolution: {integrity: sha512-j79Rl9QpHwMz/ZJWLNpZoiVj9N7zHqiLKN5EcYd/A8J1oqejILWQLfc4HPlvqHqKC8SK55LJ+X4gy4ONJ+JpfQ==} + effect@4.0.0-beta.102: + resolution: {integrity: sha512-z8Y+Q76Hh/kjLFZrXu8tGn6e+tDsg45R+UHhxd190pXxD53OGwf/G/zDxXTkse4HJ5mobNZfitLfUCp4fMvu6w==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1420,8 +1432,8 @@ packages: extendable-error@0.1.7: resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} - fast-check@4.8.0: - resolution: {integrity: sha512-GOJ158CUMnN6cSahsv4+ExARvIDuzzinFjkp0E9WtiBa5zcVeLozVkWaE4IzFcc+Y48Wp1EDlUZsXRyAztQcSg==} + fast-check@4.9.0: + resolution: {integrity: sha512-7ms6T7SybUev/PQITciI0yLM2pOSFy5zpG8Ty7tQofcVaQUvrMXp6CBwqF6fThLCLOrfBtuHAtwq6Yu4XPCllg==} engines: {node: '>=12.17.0'} fast-glob@3.3.3: @@ -1537,8 +1549,8 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - jose@6.2.4: - resolution: {integrity: sha512-N8acGzVsQy6M/fjFcxtysNc4Q379TcM5dM/qKkNtsHFji88yANnXTr7BLeP75iPnFwBfQzM/jg2BZ9+HZrHCZA==} + jose@6.2.2: + resolution: {integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==} js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -1690,11 +1702,11 @@ packages: resolution: {integrity: sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw==} hasBin: true - msgpackr@2.0.2: - resolution: {integrity: sha512-c5hYOXFbP79Slh6Dzd2wzk+jnV7mX1UxfMYtilnY1NmalXPqG8DGb5cYCMBrW4AsH3zekBBZd4QrKz9NhtvYLQ==} + msgpackr@2.0.5: + resolution: {integrity: sha512-cef05H/dSYpLpqp3sj/qyZh5vhUYCalnaLO7j1yOmpsR0y/XwLVtK7r5gn+U/F7CTEfMowcGhlUQJDLcLf7jcA==} - multipasta@0.2.7: - resolution: {integrity: sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA==} + multipasta@0.2.8: + resolution: {integrity: sha512-ZPWuMKyv0cSO29f7hozp+k6+crZbQijV8ipMvxNxRf2SwtYGTX1ZX89Kd20VV4H9Znonx+EQn+iy1wGQsJ+b+Q==} mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} @@ -1991,8 +2003,8 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - toml@4.1.1: - resolution: {integrity: sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw==} + toml@4.3.0: + resolution: {integrity: sha512-lVb8X9BsPVuH0M4BKeS91tXAmJvCjQ5UIyAbQFaxkKGyUFK2RPkhwaFSQH8vbpl1d23eu/IBH+dwVMHWaq9A5A==} engines: {node: '>=20'} totalist@3.0.1: @@ -2010,8 +2022,8 @@ packages: undici-types@8.3.0: resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} - undici@8.3.0: - resolution: {integrity: sha512-TkUDgb6tl7KOGZ+7e8E3d2FYgUQgF6z5YypqjWmixVQSQERFcVrVg0ySADm2LVLRh5ljAaHTCR5Fmz3Q34rB7Q==} + undici@8.9.0: + resolution: {integrity: sha512-aWZpUj7XoGonMClx4gdDRfgBjqeA+F473aDmROQQbM9n6PRfK/u1q/a0X4wMTgcHfT8H6fpbt98PFuDUwFg2YA==} engines: {node: '>=22.19.0'} unicode-emoji-modifier-base@1.0.0: @@ -2022,8 +2034,8 @@ packages: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} - uuid@14.0.0: - resolution: {integrity: sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==} + uuid@14.0.1: + resolution: {integrity: sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==} hasBin: true vite-plus@0.2.6: @@ -2336,29 +2348,29 @@ snapshots: '@colors/colors@1.5.0': optional: true - '@effect/platform-node-shared@4.0.0-beta.78(effect@4.0.0-beta.78)': + '@effect/platform-node-shared@4.0.0-beta.102(effect@4.0.0-beta.102(patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488))': dependencies: '@types/ws': 8.18.1 - effect: 4.0.0-beta.78 + effect: 4.0.0-beta.102(patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488) ws: 8.21.0 transitivePeerDependencies: - bufferutil - utf-8-validate - '@effect/platform-node@4.0.0-beta.78(effect@4.0.0-beta.78)(ioredis@5.10.1)': + '@effect/platform-node@4.0.0-beta.102(patch_hash=cae7efcda6fd29f4db2efd357cb7dc6c41cb6adc957901d437e13ed1f3c017f0)(effect@4.0.0-beta.102(patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488))(ioredis@5.10.1)': dependencies: - '@effect/platform-node-shared': 4.0.0-beta.78(effect@4.0.0-beta.78) - effect: 4.0.0-beta.78 + '@effect/platform-node-shared': 4.0.0-beta.102(effect@4.0.0-beta.102(patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488)) + effect: 4.0.0-beta.102(patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488) ioredis: 5.10.1 mime: 4.1.0 - undici: 8.3.0 + undici: 8.9.0 transitivePeerDependencies: - bufferutil - utf-8-validate - '@effect/vitest@4.0.0-beta.78(patch_hash=74fd480109c3bd975255bf5573a64718451bcae02b523c432bc18e9aa27b52fd)(effect@4.0.0-beta.78)(vitest@4.1.10)': + '@effect/vitest@4.0.0-beta.102(patch_hash=a607339aab944136a084a05f159aa0f5a69776934d4b835dab8f9992f1c13425)(effect@4.0.0-beta.102(patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488))(vitest@4.1.10)': dependencies: - effect: 4.0.0-beta.78 + effect: 4.0.0-beta.102(patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488) vitest: 4.1.10(@types/node@26.1.2)(@vitest/browser-preview@4.1.10)(vite@8.0.14(@types/node@26.1.2)(yaml@2.9.0)) '@emnapi/core@1.10.0': @@ -2480,11 +2492,11 @@ snapshots: '@tybys/wasm-util': 0.10.2 optional: true - '@noble/curves@2.2.0': + '@noble/curves@1.9.1': dependencies: - '@noble/hashes': 2.2.0 + '@noble/hashes': 1.8.0 - '@noble/hashes@2.2.0': {} + '@noble/hashes@1.8.0': {} '@nodelib/fs.scandir@2.1.5': dependencies: @@ -3084,17 +3096,17 @@ snapshots: dom-accessibility-api@0.5.16: {} - effect@4.0.0-beta.78: + effect@4.0.0-beta.102(patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488): dependencies: '@standard-schema/spec': 1.1.0 - fast-check: 4.8.0 + fast-check: 4.9.0 find-my-way-ts: 0.1.6 ini: 7.0.0 kubernetes-types: 1.30.0 - msgpackr: 2.0.2 - multipasta: 0.2.7 - toml: 4.1.1 - uuid: 14.0.0 + msgpackr: 2.0.5 + multipasta: 0.2.8 + toml: 4.3.0 + uuid: 14.0.1 yaml: 2.9.0 emoji-regex@8.0.0: {} @@ -3122,7 +3134,7 @@ snapshots: extendable-error@0.1.7: {} - fast-check@4.8.0: + fast-check@4.9.0: dependencies: pure-rand: 8.4.0 @@ -3233,7 +3245,7 @@ snapshots: isexe@2.0.0: {} - jose@6.2.4: {} + jose@6.2.2: {} js-tokens@4.0.0: {} @@ -3357,11 +3369,11 @@ snapshots: '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.4 optional: true - msgpackr@2.0.2: + msgpackr@2.0.5: optionalDependencies: msgpackr-extract: 3.0.4 - multipasta@0.2.7: {} + multipasta@0.2.8: {} mz@2.7.0: dependencies: @@ -3657,7 +3669,7 @@ snapshots: dependencies: is-number: 7.0.0 - toml@4.1.1: {} + toml@4.3.0: {} totalist@3.0.1: {} @@ -3689,13 +3701,13 @@ snapshots: undici-types@8.3.0: {} - undici@8.3.0: {} + undici@8.9.0: {} unicode-emoji-modifier-base@1.0.0: {} universalify@0.1.2: {} - uuid@14.0.0: {} + uuid@14.0.1: {} vite-plus@0.2.6(@types/node@26.1.2)(typescript@7.0.2)(vite@8.0.14(@types/node@26.1.2)(yaml@2.9.0))(yaml@2.9.0): dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 9b2d32a..92f7530 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -5,17 +5,19 @@ packages: - upstream-t3code/packages/shared catalog: - "@effect/platform-node": 4.0.0-beta.78 - "@effect/vitest": 4.0.0-beta.78 - "@noble/curves": 2.2.0 - "@noble/hashes": 2.2.0 + "@effect/platform-node": 4.0.0-beta.102 + "@effect/vitest": 4.0.0-beta.102 + "@noble/curves": 1.9.1 + "@noble/hashes": 1.8.0 "@types/node": ^26.1.2 - effect: 4.0.0-beta.78 - jose: 6.2.4 + effect: 4.0.0-beta.102 + jose: 6.2.2 vite-plus: 0.2.6 vite: npm:@voidzero-dev/vite-plus-core@0.2.6 vitest: npm:@voidzero-dev/vite-plus-test@0.1.24 yaml: ^2.9.0 patchedDependencies: - "@effect/vitest@4.0.0-beta.78": patches/@effect__vitest@4.0.0-beta.78.patch + "@effect/platform-node@4.0.0-beta.102": upstream-t3code/patches/@effect__platform-node@4.0.0-beta.102.patch + "@effect/vitest@4.0.0-beta.102": upstream-t3code/patches/@effect__vitest@4.0.0-beta.102.patch + effect@4.0.0-beta.102: upstream-t3code/patches/effect@4.0.0-beta.102.patch diff --git a/scripts/sync-upstream.ts b/scripts/sync-upstream.ts new file mode 100644 index 0000000..6ee5bb4 --- /dev/null +++ b/scripts/sync-upstream.ts @@ -0,0 +1,460 @@ +#!/usr/bin/env node + +import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; +import * as NodeServices from "@effect/platform-node/NodeServices"; +import * as Console from "effect/Console"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Option from "effect/Option"; +import * as Path from "effect/Path"; +import * as Schema from "effect/Schema"; +import * as Stream from "effect/Stream"; +import { Command, Flag } from "effect/unstable/cli"; +import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; +import { parseDocument } from "yaml"; + +const synchronizedDependencyPatterns = ["@effect/*", "@noble/*", "effect", "jose", "yaml"]; + +const WorkspaceConfig = Schema.Struct({ + catalog: Schema.Record(Schema.String, Schema.String), + patchedDependencies: Schema.optional(Schema.Record(Schema.String, Schema.String)), +}); + +const Dependencies = Schema.Record(Schema.String, Schema.String); +const PackageJson = Schema.StructWithRest( + Schema.Struct({ + dependencies: Dependencies, + devDependencies: Dependencies, + }), + [Schema.Record(Schema.String, Schema.Json)], +); +const decodePackageJson = Schema.decodeEffect(Schema.fromJsonString(PackageJson), { + propertyOrder: "original", +}); +const decodeWorkspaceConfig = Schema.decodeUnknownEffect(WorkspaceConfig); + +export class SyncUpstreamError extends Schema.TaggedErrorClass()( + "SyncUpstreamError", + { + message: Schema.String, + cause: Schema.optional(Schema.Defect()), + }, +) {} + +const syncError = (message: string) => (cause: unknown) => + new SyncUpstreamError({ message, cause }); + +const repoRoot = Effect.service(Path.Path).pipe( + Effect.flatMap((path) => path.fromFileUrl(new URL("..", import.meta.url))), + Effect.mapError(syncError("failed to resolve the repository root")), +); + +const collectStream = (stream: Stream.Stream) => + stream.pipe( + Stream.decodeText(), + Stream.runFold( + () => "", + (output, chunk) => output + chunk, + ), + ); + +const readWorkspaceConfig = Effect.fn("readWorkspaceConfig")(function* (filePath: string) { + const fs = yield* FileSystem.FileSystem; + const source = yield* fs + .readFileString(filePath) + .pipe(Effect.mapError(syncError(`failed to read '${filePath}'`))); + const document = yield* Effect.try({ + try: () => parseDocument(source), + catch: syncError(`failed to parse '${filePath}'`), + }); + if (document.errors.length > 0) { + return yield* new SyncUpstreamError({ message: `failed to parse '${filePath}'` }); + } + const value: unknown = document.toJS(); + const config = yield* decodeWorkspaceConfig(value).pipe( + Effect.mapError(syncError(`invalid workspace config in '${filePath}'`)), + ); + + return { config, document, source }; +}); + +const resolveUpstreamRef = Effect.fn("resolveUpstreamRef")(function* ( + submodulePath: string, + target: string, +) { + const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; + + if (target === "stable" || target === "nightly") { + const tagsProcess = yield* spawner.spawn( + ChildProcess.make( + "git", + [ + "tag", + "--list", + target === "stable" ? "v[0-9]*" : "v*-nightly.*", + "--sort=-version:refname", + ], + { cwd: submodulePath, stderr: "inherit" }, + ), + ); + const [tagsOutput] = yield* Effect.all( + [ + collectStream(tagsProcess.stdout), + tagsProcess.exitCode.pipe( + Effect.filterOrFail( + (exitCode) => exitCode === 0, + (exitCode) => + new SyncUpstreamError({ + message: `'git tag --list' exited with code ${exitCode}`, + }), + ), + ), + ], + { concurrency: "unbounded" }, + ); + const tag = tagsOutput + .trim() + .split("\n") + .find((candidate) => + target === "stable" + ? /^v\d+\.\d+\.\d+$/u.test(candidate) + : /^v\d+\.\d+\.\d+-nightly\..+$/u.test(candidate), + ); + if (tag === undefined) { + return yield* new SyncUpstreamError({ message: `no ${target} T3 Code tag was found` }); + } + return tag; + } + if (target === "main") { + return "origin/main"; + } + if (/^v?\d+\.\d+\.\d+(?:-.+)?$/u.test(target)) { + return target.startsWith("v") ? target : `v${target}`; + } + + yield* spawner + .exitCode( + ChildProcess.make("git", ["fetch", "origin", target], { + cwd: submodulePath, + stdin: "inherit", + stdout: "inherit", + stderr: "inherit", + }), + ) + .pipe( + Effect.filterOrFail( + (exitCode) => exitCode === 0, + (exitCode) => + new SyncUpstreamError({ + message: `'git fetch origin ${target}' exited with code ${exitCode}`, + }), + ), + ); + return "FETCH_HEAD"; +}); + +const updateSubmodule = Effect.fn("updateSubmodule")(function* ( + root: string, + submodulePath: string, + target: string | undefined, +) { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; + const initialized = yield* fs + .exists(path.join(submodulePath, ".git")) + .pipe(Effect.mapError(syncError("failed to inspect upstream-t3code"))); + if (!initialized) { + yield* spawner + .exitCode( + ChildProcess.make("git", ["submodule", "update", "--init", "--", "upstream-t3code"], { + cwd: root, + stdin: "inherit", + stdout: "inherit", + stderr: "inherit", + }), + ) + .pipe( + Effect.filterOrFail( + (exitCode) => exitCode === 0, + (exitCode) => + new SyncUpstreamError({ + message: `'git submodule update --init -- upstream-t3code' exited with code ${exitCode}`, + }), + ), + ); + } + + const statusProcess = yield* spawner.spawn( + ChildProcess.make("git", ["status", "--porcelain"], { + cwd: submodulePath, + stderr: "inherit", + }), + ); + const [status] = yield* Effect.all( + [ + collectStream(statusProcess.stdout), + statusProcess.exitCode.pipe( + Effect.filterOrFail( + (exitCode) => exitCode === 0, + (exitCode) => + new SyncUpstreamError({ + message: `'git status --porcelain' exited with code ${exitCode}`, + }), + ), + ), + ], + { concurrency: "unbounded" }, + ); + if (status.trim().length > 0) { + return yield* new SyncUpstreamError({ + message: "upstream-t3code has uncommitted changes; clean it before updating", + }); + } + + const currentCommitProcess = yield* spawner.spawn( + ChildProcess.make("git", ["rev-parse", "HEAD"], { + cwd: submodulePath, + stderr: "inherit", + }), + ); + const [currentCommitOutput] = yield* Effect.all( + [ + collectStream(currentCommitProcess.stdout), + currentCommitProcess.exitCode.pipe( + Effect.filterOrFail( + (exitCode) => exitCode === 0, + (exitCode) => + new SyncUpstreamError({ + message: `'git rev-parse HEAD' exited with code ${exitCode}`, + }), + ), + ), + ], + { concurrency: "unbounded" }, + ); + const currentCommit = currentCommitOutput.trim(); + + if (target === undefined) { + return currentCommit; + } + + yield* Console.log(`Fetching upstream T3 Code for target '${target}'...`); + yield* spawner + .exitCode( + ChildProcess.make("git", ["fetch", "origin", "--tags", "--force"], { + cwd: submodulePath, + stdin: "inherit", + stdout: "inherit", + stderr: "inherit", + }), + ) + .pipe( + Effect.filterOrFail( + (exitCode) => exitCode === 0, + (exitCode) => + new SyncUpstreamError({ + message: `'git fetch origin --tags --force' exited with code ${exitCode}`, + }), + ), + ); + + const ref = yield* resolveUpstreamRef(submodulePath, target); + + const commitProcess = yield* spawner.spawn( + ChildProcess.make("git", ["rev-parse", "--verify", `${ref}^{commit}`], { + cwd: submodulePath, + stderr: "inherit", + }), + ); + const [commitOutput] = yield* Effect.all( + [ + collectStream(commitProcess.stdout), + commitProcess.exitCode.pipe( + Effect.filterOrFail( + (exitCode) => exitCode === 0, + (exitCode) => + new SyncUpstreamError({ + message: `'git rev-parse --verify ${ref}^{commit}' exited with code ${exitCode}`, + }), + ), + ), + ], + { concurrency: "unbounded" }, + ); + const commit = commitOutput.trim(); + + if (commit !== currentCommit) { + yield* spawner + .exitCode( + ChildProcess.make("git", ["checkout", "--detach", commit], { + cwd: submodulePath, + stdin: "inherit", + stdout: "inherit", + stderr: "inherit", + }), + ) + .pipe( + Effect.filterOrFail( + (exitCode) => exitCode === 0, + (exitCode) => + new SyncUpstreamError({ + message: `'git checkout --detach ${commit}' exited with code ${exitCode}`, + }), + ), + ); + } + + return commit; +}); + +const synchronizeConfig = Effect.fn("synchronizeConfig")(function* ( + root: string, + submodulePath: string, +) { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const packageJsonPath = path.join(root, "package.json"); + const workspacePath = path.join(root, "pnpm-workspace.yaml"); + const upstreamWorkspacePath = path.join(submodulePath, "pnpm-workspace.yaml"); + const packageJsonSource = yield* fs + .readFileString(packageJsonPath) + .pipe(Effect.mapError(syncError(`failed to read '${packageJsonPath}'`))); + const packageJson = yield* decodePackageJson(packageJsonSource).pipe( + Effect.mapError(syncError(`invalid package manifest in '${packageJsonPath}'`)), + ); + const rootWorkspace = yield* readWorkspaceConfig(workspacePath); + const upstreamWorkspace = yield* readWorkspaceConfig(upstreamWorkspacePath); + const synchronizedDependencies = Object.keys(rootWorkspace.config.catalog).filter((dependency) => + synchronizedDependencyPatterns.some((pattern) => + pattern.endsWith("*") ? dependency.startsWith(pattern.slice(0, -1)) : dependency === pattern, + ), + ); + const versions: Record = {}; + + for (const dependency of synchronizedDependencies) { + const version = upstreamWorkspace.config.catalog[dependency]; + if (version === undefined) { + return yield* new SyncUpstreamError({ + message: `upstream catalog does not define '${dependency}'`, + }); + } + versions[dependency] = version; + rootWorkspace.document.setIn(["catalog", dependency], version); + } + + const patchedDependencies = Object.fromEntries( + Object.entries(rootWorkspace.config.patchedDependencies ?? {}).filter( + ([dependency]) => !synchronizedDependencies.some((name) => dependency.startsWith(`${name}@`)), + ), + ); + for (const [dependency, patchFile] of Object.entries( + upstreamWorkspace.config.patchedDependencies ?? {}, + )) { + if (!synchronizedDependencies.some((name) => dependency.startsWith(`${name}@`))) { + continue; + } + const patchPath = path.join("upstream-t3code", patchFile); + const patchExists = yield* fs + .exists(path.join(root, patchPath)) + .pipe(Effect.mapError(syncError(`failed to inspect '${patchPath}'`))); + if (!patchExists) { + return yield* new SyncUpstreamError({ message: `upstream patch '${patchFile}' is missing` }); + } + patchedDependencies[dependency] = patchPath; + } + rootWorkspace.document.set("patchedDependencies", patchedDependencies); + + const nextDependencies = { ...packageJson.dependencies }; + const nextDevDependencies = { ...packageJson.devDependencies }; + for (const [dependency, version] of Object.entries(versions)) { + if (nextDependencies[dependency] !== undefined && nextDependencies[dependency] !== "catalog:") { + nextDependencies[dependency] = version; + } + if ( + nextDevDependencies[dependency] !== undefined && + nextDevDependencies[dependency] !== "catalog:" + ) { + nextDevDependencies[dependency] = version; + } + } + + const nextPackageJsonSource = `${JSON.stringify( + { + ...packageJson, + dependencies: nextDependencies, + devDependencies: nextDevDependencies, + }, + null, + 2, + )}\n`; + const nextWorkspaceSource = rootWorkspace.document.toString(); + + if (nextPackageJsonSource !== packageJsonSource) { + yield* fs + .writeFileString(packageJsonPath, nextPackageJsonSource) + .pipe(Effect.mapError(syncError(`failed to write '${packageJsonPath}'`))); + } + if (nextWorkspaceSource !== rootWorkspace.source) { + yield* fs + .writeFileString(workspacePath, nextWorkspaceSource) + .pipe(Effect.mapError(syncError(`failed to write '${workspacePath}'`))); + } + + return versions; +}); + +const syncUpstream = Effect.fn("syncUpstream")(function* (target: string | undefined) { + const path = yield* Path.Path; + const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; + const root = yield* repoRoot; + const submodulePath = path.join(root, "upstream-t3code"); + const commit = yield* updateSubmodule(root, submodulePath, target).pipe(Effect.scoped); + const versions = yield* synchronizeConfig(root, submodulePath); + + yield* Console.log("Installing synchronized dependencies..."); + yield* spawner + .exitCode( + ChildProcess.make("pnpm", ["install"], { + cwd: root, + stdin: "inherit", + stdout: "inherit", + stderr: "inherit", + }), + ) + .pipe( + Effect.filterOrFail( + (exitCode) => exitCode === 0, + (exitCode) => + new SyncUpstreamError({ + message: `'pnpm install' exited with code ${exitCode}`, + }), + ), + ); + yield* Console.log(`T3 Code: ${commit}`); + for (const [dependency, version] of Object.entries(versions)) { + yield* Console.log(`${dependency}: ${version}`); + } +}); + +const syncUpstreamCommand = Command.make( + "sync-upstream", + { + target: Flag.string("target").pipe( + Flag.withDescription("T3 Code target: stable, nightly, main, a version, ref, or commit."), + Flag.optional, + ), + }, + ({ target }) => syncUpstream(Option.getOrUndefined(target)), +).pipe( + Command.withDescription( + "Update the upstream T3 Code submodule and synchronize required dependencies and patches.", + ), +); + +if (import.meta.main) { + Command.run(syncUpstreamCommand, { version: "0.0.0" }).pipe( + Effect.provide(NodeServices.layer), + NodeRuntime.runMain, + ); +} diff --git a/src/auth/index.ts b/src/auth/index.ts index a97c150..021cc71 100644 --- a/src/auth/index.ts +++ b/src/auth/index.ts @@ -1,4 +1,5 @@ export { T3Auth } from "./service.ts"; +export type { AuthClientPresentationMetadata } from "../contracts/index.ts"; export { T3AuthLive, makeT3Auth } from "./layer.ts"; export { T3LocalAuth, T3LocalAuthLive, makeT3LocalAuth } from "./local.ts"; export { T3LocalAuthOrigin, T3LocalAuthOriginLive, makeT3LocalAuthOrigin } from "./local-origin.ts"; @@ -29,6 +30,7 @@ export type { LocalAuthResult, LocalAuthTokenInput, LocalAuthTokenResult, + AuthPairInput, PairingUrl, PairResult, PersistEnvironmentInput, diff --git a/src/auth/pairing.ts b/src/auth/pairing.ts index 7bc613d..605d70b 100644 --- a/src/auth/pairing.ts +++ b/src/auth/pairing.ts @@ -5,13 +5,13 @@ import { Url } from "effect/unstable/http"; import { AuthPairingUrlError, AuthTransportError } from "./error.ts"; import { T3AuthTransport } from "./transport.ts"; -import type { PairingUrl, PairResult } from "./type.ts"; +import type { AuthPairInput, PairingUrl, PairResult } from "./type.ts"; export class T3AuthPairing extends Context.Service< T3AuthPairing, { readonly pair: ( - pairingUrl: string, + input: AuthPairInput, ) => Effect.Effect; } >()("t3cli/T3AuthPairing") {} @@ -19,9 +19,12 @@ export class T3AuthPairing extends Context.Service< export const makeT3AuthPairing = Effect.fn("makeT3AuthPairing")(function* () { const transport = yield* T3AuthTransport; - const pair = Effect.fn("T3AuthPairingLive.pair")(function* (pairingUrl: string) { - const parsed = yield* parsePairingUrl(pairingUrl); - const result = yield* transport.bootstrapBearer(parsed); + const pair = Effect.fn("T3AuthPairingLive.pair")(function* (input: AuthPairInput) { + const parsed = yield* parsePairingUrl(input.pairingUrl); + const result = yield* transport.bootstrapBearer({ + ...parsed, + ...(input.clientMetadata !== undefined ? { clientMetadata: input.clientMetadata } : {}), + }); return { url: parsed.baseUrl, token: result.sessionToken, diff --git a/src/auth/service.ts b/src/auth/service.ts index 04477c0..97a7159 100644 --- a/src/auth/service.ts +++ b/src/auth/service.ts @@ -11,6 +11,7 @@ import type { AuthUseResult, LocalAuthInput, LocalAuthResult, + AuthPairInput, PairResult, PersistEnvironmentInput, } from "./type.ts"; @@ -18,7 +19,7 @@ import type { export class T3Auth extends Context.Service< T3Auth, { - readonly pair: (value: string) => Effect.Effect; + readonly pair: (input: AuthPairInput) => Effect.Effect; readonly local: (input: LocalAuthInput) => Effect.Effect; readonly writeConfig: (input: AuthConfigInput) => Effect.Effect; readonly persistEnvironment: ( diff --git a/src/auth/transport.ts b/src/auth/transport.ts index 41eb775..695ddf9 100644 --- a/src/auth/transport.ts +++ b/src/auth/transport.ts @@ -9,6 +9,7 @@ import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import { HttpClient } from "effect/unstable/http"; +import type { AuthClientPresentationMetadata } from "../contracts/index.ts"; import { AuthTransportError } from "./error.ts"; import { type AuthBearerBootstrapResult, @@ -27,6 +28,7 @@ export class T3AuthTransport extends Context.Service< readonly bootstrapBearer: (input: { readonly baseUrl: string; readonly credential: string; + readonly clientMetadata?: AuthClientPresentationMetadata; }) => Effect.Effect; readonly getSession: ( connection: AuthTransportConnection, @@ -43,10 +45,12 @@ const makeT3AuthTransport = Effect.fn("makeT3AuthTransport")(function* () { const bootstrapBearer = Effect.fn("AuthTransport.bootstrapBearer")(function* (input: { readonly baseUrl: string; readonly credential: string; + readonly clientMetadata?: AuthClientPresentationMetadata; }) { const result = yield* bootstrapRemoteBearerSession({ httpBaseUrl: input.baseUrl, credential: input.credential, + ...(input.clientMetadata !== undefined ? { clientMetadata: input.clientMetadata } : {}), }).pipe( Effect.provideService(HttpClient.HttpClient, httpClient), Effect.mapError( diff --git a/src/auth/type.ts b/src/auth/type.ts index 63052f6..daa976a 100644 --- a/src/auth/type.ts +++ b/src/auth/type.ts @@ -1,3 +1,4 @@ +import type { AuthClientPresentationMetadata } from "../contracts/index.ts"; import type { AuthBearerBootstrapResult } from "./schema.ts"; import type { AuthSessionState } from "./schema.ts"; @@ -8,6 +9,11 @@ export type PairingUrl = { readonly credential: string; }; +export interface AuthPairInput { + readonly pairingUrl: string; + readonly clientMetadata?: AuthClientPresentationMetadata; +} + export type AuthConfigInput = { readonly name: string; readonly url: string; diff --git a/src/cli/auth.ts b/src/cli/auth.ts index d7b8901..e678bf9 100644 --- a/src/cli/auth.ts +++ b/src/cli/auth.ts @@ -75,7 +75,13 @@ const pairCommand = Command.make( const t3CliEnv = yield* loadT3CliEnv; const output = yield* T3Output; const resolvedFormat = resolveOutputFormat(format, cliRuntime, t3CliEnv, "json"); - const result = yield* auth.pair(url); + const result = yield* auth.pair({ + pairingUrl: url, + clientMetadata: { + label: "t3cli", + deviceType: "bot", + }, + }); const fallbackName = yield* auth.defaultNameFromUrl(result.url); const environmentName = yield* persistAuthEnvironment({ explicitName: name, diff --git a/src/contracts/index.ts b/src/contracts/index.ts index 03992d6..521d7d6 100644 --- a/src/contracts/index.ts +++ b/src/contracts/index.ts @@ -1,8 +1,16 @@ export type { + AuthClientPresentationMetadata, OrchestrationMessage, OrchestrationProjectShell, OrchestrationShellSnapshot, OrchestrationThread, OrchestrationThreadShell, + PreviewAutomationHost, + PreviewAutomationHostFocus, + PreviewAutomationResizeInput, + PreviewAutomationResponse, + PreviewAutomationStreamEvent, + PreviewViewportPresetId, ServerProvider, } from "@t3tools/contracts"; +export { PREVIEW_VIEWPORT_PRESET_IDS, PreviewViewportSetting } from "@t3tools/contracts"; diff --git a/src/orchestration/layer.ts b/src/orchestration/layer.ts index 9816249..e0f6fc3 100644 --- a/src/orchestration/layer.ts +++ b/src/orchestration/layer.ts @@ -8,9 +8,11 @@ import { ThreadId, WS_METHODS, type ClientOrchestrationCommand, + type OrchestrationShellSnapshot, type OrchestrationShellStreamItem, type OrchestrationThreadStreamItem, } from "@t3tools/contracts"; +import { applyShellStreamEvent } from "@t3tools/client-runtime/state/shell"; import { RpcError } from "../rpc/error.ts"; import { T3RpcOperations } from "../rpc/operation.ts"; @@ -19,6 +21,39 @@ import { T3Orchestration, type OpenThread, type Orchestration } from "./service. export const makeT3Orchestration = Effect.fn("makeT3Orchestration")(function* () { const rpc = yield* T3RpcOperations; + const watchShellSnapshots: Orchestration["watchShellSnapshots"] = () => + rpc + .subscribe(ORCHESTRATION_WS_METHODS.subscribeShell, (client) => + client[ORCHESTRATION_WS_METHODS.subscribeShell]({}), + ) + .pipe( + Stream.filter( + ( + item, + ): item is Exclude => + item.kind !== "synchronized", + ), + Stream.mapAccum( + () => Option.none(), + ( + current, + item, + ): readonly [ + Option.Option, + ReadonlyArray, + ] => { + if (item.kind === "snapshot") { + return [Option.some(item.snapshot), [item.snapshot]]; + } + if (Option.isNone(current)) { + return [current, []]; + } + const next = applyShellStreamEvent(current.value, item); + return [Option.some(next), [next]]; + }, + ), + ); + const watchShellSequence: Orchestration["watchShellSequence"] = () => rpc .subscribe(ORCHESTRATION_WS_METHODS.subscribeShell, (client) => @@ -126,6 +161,7 @@ export const makeT3Orchestration = Effect.fn("makeT3Orchestration")(function* () getShellSnapshot, getArchivedShellSnapshot, getThreadSnapshot, + watchShellSnapshots, watchShellSequence, watchThreadItems, openThread, diff --git a/src/orchestration/service.ts b/src/orchestration/service.ts index 0b415bf..021376b 100644 --- a/src/orchestration/service.ts +++ b/src/orchestration/service.ts @@ -38,6 +38,11 @@ export type Orchestration = { readonly getThreadSnapshot: ( threadId: string, ) => Effect.Effect; + readonly watchShellSnapshots: () => Stream.Stream< + OrchestrationShellSnapshot, + OrchestrationError, + Scope.Scope + >; readonly watchShellSequence: () => Stream.Stream; readonly watchThreadItems: ( threadId: string, diff --git a/src/preview/index.ts b/src/preview/index.ts new file mode 100644 index 0000000..57c5acb --- /dev/null +++ b/src/preview/index.ts @@ -0,0 +1,11 @@ +export { + T3PreviewAutomation, + T3PreviewAutomationLive, + makeT3PreviewAutomation, +} from "./service.ts"; +export type { + PreviewAutomationHost, + PreviewAutomationHostFocus, + PreviewAutomationResponse, + PreviewAutomationStreamEvent, +} from "../contracts/index.ts"; diff --git a/src/preview/service.ts b/src/preview/service.ts new file mode 100644 index 0000000..1bc2801 --- /dev/null +++ b/src/preview/service.ts @@ -0,0 +1,52 @@ +import { WS_METHODS } from "@t3tools/contracts"; +import * as Context from "effect/Context"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import type * as Stream from "effect/Stream"; + +import type { + PreviewAutomationHost, + PreviewAutomationHostFocus, + PreviewAutomationResponse, + PreviewAutomationStreamEvent, +} from "../contracts/index.ts"; +import type { RpcError } from "../rpc/error.ts"; +import { T3RpcOperations } from "../rpc/operation.ts"; + +export class T3PreviewAutomation extends Context.Service< + T3PreviewAutomation, + { + readonly connect: ( + host: PreviewAutomationHost, + ) => Stream.Stream; + readonly respond: (response: PreviewAutomationResponse) => Effect.Effect; + readonly focusHost: (input: PreviewAutomationHostFocus) => Effect.Effect; + } +>()("t3cli/T3PreviewAutomation") {} + +export const makeT3PreviewAutomation = Effect.fn("makeT3PreviewAutomation")(function* () { + const rpc = yield* T3RpcOperations; + + const connect: T3PreviewAutomation["Service"]["connect"] = (host) => + rpc.subscribe(WS_METHODS.previewAutomationConnect, (client) => + client[WS_METHODS.previewAutomationConnect](host), + ); + const respond = Effect.fn("T3PreviewAutomation.respond")(function* ( + response: PreviewAutomationResponse, + ) { + return yield* rpc.run(WS_METHODS.previewAutomationRespond, (client) => + client[WS_METHODS.previewAutomationRespond](response), + ); + }); + const focusHost = Effect.fn("T3PreviewAutomation.focusHost")(function* ( + input: PreviewAutomationHostFocus, + ) { + return yield* rpc.run(WS_METHODS.previewAutomationFocusHost, (client) => + client[WS_METHODS.previewAutomationFocusHost](input), + ); + }); + + return T3PreviewAutomation.of({ connect, respond, focusHost }); +}); + +export const T3PreviewAutomationLive = Layer.effect(T3PreviewAutomation, makeT3PreviewAutomation()); diff --git a/src/rpc/error.ts b/src/rpc/error.ts index abdf037..a895b3a 100644 --- a/src/rpc/error.ts +++ b/src/rpc/error.ts @@ -7,6 +7,7 @@ import { KeybindingsConfigError, OrchestrationDispatchCommandError, OrchestrationGetSnapshotError, + PreviewAutomationError, ServerSettingsError, TerminalError, } from "@t3tools/contracts"; @@ -21,6 +22,7 @@ const RpcErrorCauseSchema = Schema.Union([ KeybindingsConfigError, OrchestrationDispatchCommandError, OrchestrationGetSnapshotError, + PreviewAutomationError, ServerSettingsError, TerminalError, ConnectionBlockedError, diff --git a/src/rpc/layer.ts b/src/rpc/layer.ts index 1528422..6083870 100644 --- a/src/rpc/layer.ts +++ b/src/rpc/layer.ts @@ -1,3 +1,4 @@ +import type { ConnectionAttemptError } from "@t3tools/client-runtime/connection"; import * as Context from "effect/Context"; import * as Effect from "effect/Effect"; import * as Exit from "effect/Exit"; @@ -8,16 +9,16 @@ import * as Schedule from "effect/Schedule"; import * as Scope from "effect/Scope"; import * as SynchronizedRef from "effect/SynchronizedRef"; +import { T3CodeConnectionError } from "../connection/error.ts"; import { T3PreparedConnectionProvider } from "../connection/prepared.ts"; import { RpcError } from "./error.ts"; import { T3RpcSessionFactory } from "./session.ts"; import { T3Rpc, type WsClient } from "./service.ts"; const connectionRetrySchedule = Schedule.exponential("100 millis").pipe( - Schedule.take(4), - Schedule.collectWhile((metadata: Schedule.Metadata) => - Predicate.isTagged(metadata.input, "ConnectionTransientError"), - ), + Schedule.setInputType(), + Schedule.upTo({ times: 4 }), + Schedule.while((metadata) => Predicate.isTagged(metadata.input, "ConnectionTransientError")), ); type Connection = { diff --git a/src/rpc/operation.ts b/src/rpc/operation.ts index eff7e70..438410a 100644 --- a/src/rpc/operation.ts +++ b/src/rpc/operation.ts @@ -3,6 +3,7 @@ import type { KeybindingsConfigError, OrchestrationDispatchCommandError, OrchestrationGetSnapshotError, + PreviewAutomationError, ServerSettingsError, TerminalError, } from "@t3tools/contracts"; @@ -22,15 +23,15 @@ export type CliRpcOperationError = | KeybindingsConfigError | OrchestrationDispatchCommandError | OrchestrationGetSnapshotError + | PreviewAutomationError | RpcClientError.RpcClientError | ServerSettingsError | TerminalError; export const rpcRetrySchedule = Schedule.exponential("100 millis").pipe( - Schedule.take(4), - Schedule.collectWhile((metadata: Schedule.Metadata) => - Predicate.isTagged(metadata.input, "RpcClientError"), - ), + Schedule.setInputType(), + Schedule.upTo({ times: 4 }), + Schedule.while((metadata) => Predicate.isTagged(metadata.input, "RpcClientError")), ); export type T3RpcOperationsService = { diff --git a/src/rpc/ws-group.ts b/src/rpc/ws-group.ts index 39d512d..03591a5 100644 --- a/src/rpc/ws-group.ts +++ b/src/rpc/ws-group.ts @@ -8,6 +8,9 @@ import { WsOrchestrationGetArchivedShellSnapshotRpc, WsOrchestrationSubscribeShellRpc, WsOrchestrationSubscribeThreadRpc, + WsPreviewAutomationConnectRpc, + WsPreviewAutomationFocusHostRpc, + WsPreviewAutomationRespondRpc, WsServerProbeRpc, WsSubscribeTerminalEventsRpc, WsSubscribeTerminalMetadataRpc, @@ -48,6 +51,9 @@ export const CliWsRpcGroup = RpcGroup.make( WsOrchestrationGetArchivedShellSnapshotRpc, WsOrchestrationSubscribeShellRpc, WsOrchestrationSubscribeThreadRpc, + WsPreviewAutomationConnectRpc, + WsPreviewAutomationRespondRpc, + WsPreviewAutomationFocusHostRpc, WsServerProbeRpc, WsServerGetConfigRpc, ); diff --git a/src/runtime/index.ts b/src/runtime/index.ts index 50c5ad6..af12fb5 100644 --- a/src/runtime/index.ts +++ b/src/runtime/index.ts @@ -10,4 +10,5 @@ export { T3LocalAuthOriginLayer, T3LocalAuthTokenLayer, T3OrchestrationLayer, + T3PreviewAutomationLayer, } from "./layer.ts"; diff --git a/src/runtime/layer.ts b/src/runtime/layer.ts index a3f96d1..931134c 100644 --- a/src/runtime/layer.ts +++ b/src/runtime/layer.ts @@ -17,6 +17,7 @@ import { T3CodeConnectionError } from "../connection/error.ts"; import { T3PreparedConnectionProviderLive } from "../connection/prepared.ts"; import { T3CodeConnectionProvider, makeT3CodeConnectionProvider } from "../connection/service.ts"; import { T3OrchestrationLive } from "../orchestration/layer.ts"; +import { T3PreviewAutomationLive } from "../preview/service.ts"; import { T3RpcLive } from "../rpc/layer.ts"; import { T3RpcOperationsLive } from "../rpc/operation.ts"; import { T3RpcSessionFactoryLive } from "../rpc/session.ts"; @@ -74,6 +75,9 @@ const T3RpcLayer = T3RpcLive.pipe( ); const T3RpcOperationsLayer = T3RpcOperationsLive.pipe(Layer.provide(T3RpcLayer)); export const T3OrchestrationLayer = T3OrchestrationLive.pipe(Layer.provide(T3RpcOperationsLayer)); +export const T3PreviewAutomationLayer = T3PreviewAutomationLive.pipe( + Layer.provide(T3RpcOperationsLayer), +); const T3ApplicationLayer = T3ApplicationLive.pipe( Layer.provide(Layer.mergeAll(T3RpcOperationsLayer, T3OrchestrationLayer)), ); @@ -84,6 +88,7 @@ export const BaseAppLayer = Layer.mergeAll( T3RpcLayer, T3RpcOperationsLayer, T3OrchestrationLayer, + T3PreviewAutomationLayer, T3ApplicationLayer, NodeCliPathLayer, ); diff --git a/src/sql/node-sqlite-client.ts b/src/sql/node-sqlite-client.ts index 62fb57a..b84f647 100644 --- a/src/sql/node-sqlite-client.ts +++ b/src/sql/node-sqlite-client.ts @@ -91,6 +91,9 @@ export const makeNodeSqliteClient = Effect.fn("makeNodeSqliteClient")(function* executeValues(sql, params) { return runValues(sql, params); }, + executeValuesUnprepared(sql, params) { + return runValues(sql, params); + }, executeUnprepared(sql, params, rowTransform) { const effect = runRows(sql, params); return rowTransform === undefined ? effect : Effect.map(effect, rowTransform); diff --git a/tsconfig.dts.json b/tsconfig.dts.json index 779d888..f8dd121 100644 --- a/tsconfig.dts.json +++ b/tsconfig.dts.json @@ -2,12 +2,21 @@ "extends": "./tsconfig.json", "compilerOptions": { "declaration": true, - "declarationDir": "dist", "emitDeclarationOnly": true, "noEmit": false, + "paths": { + "@t3tools/client-runtime/*": [ + "./upstream-t3code/packages/client-runtime/src/*/index.ts", + "./upstream-t3code/packages/client-runtime/src/*.ts" + ], + "@t3tools/contracts": ["./upstream-t3code/packages/contracts/src/index.ts"], + "@t3tools/contracts/*": ["./upstream-t3code/packages/contracts/src/*.ts"], + "@t3tools/shared/*": ["./upstream-t3code/packages/shared/src/*.ts"] + }, "rootDir": "." }, "include": [ + "src/bin.ts", "src/index.ts", "src/application/index.ts", "src/auth/index.ts", @@ -17,8 +26,13 @@ "src/contracts/index.ts", "src/node/index.ts", "src/orchestration/index.ts", + "src/preview/index.ts", "src/rpc/index.ts", "src/runtime/index.ts", - "src/t3tools/index.ts" + "src/t3tools/index.ts", + "upstream-t3code/packages/client-runtime/src/*/index.ts", + "upstream-t3code/packages/client-runtime/src/operations/projects.ts", + "upstream-t3code/packages/client-runtime/src/state/*.ts", + "upstream-t3code/packages/shared/src/*.ts" ] } diff --git a/tsconfig.json b/tsconfig.json index 20d7423..0e63de2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,5 +12,5 @@ "target": "ES2024", "types": ["node"] }, - "include": ["src/**/*.ts", "vite.config.ts"] + "include": ["scripts/**/*.ts", "src/**/*.ts", "vite.config.ts"] } diff --git a/vite.config.ts b/vite.config.ts index be85981..1bec48e 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,6 +1,8 @@ import "vite-plus/test/config"; import { defineConfig } from "vite-plus"; import packageJson from "./package.json" with { type: "json" }; +import clientRuntimePackageJson from "./upstream-t3code/packages/client-runtime/package.json" with { type: "json" }; +import sharedPackageJson from "./upstream-t3code/packages/shared/package.json" with { type: "json" }; function shouldBundlePackDependency(id: string): boolean { if (id === "@napi-rs/keyring" || id.startsWith("@napi-rs/keyring-")) { @@ -30,15 +32,35 @@ export default defineConfig({ contracts: "src/contracts/index.ts", node: "src/node/index.ts", orchestration: "src/orchestration/index.ts", + preview: "src/preview/index.ts", rpc: "src/rpc/index.ts", runtime: "src/runtime/index.ts", t3tools: "src/t3tools/index.ts", + ...Object.fromEntries( + Object.entries(clientRuntimePackageJson.exports).map(([subpath, conditions]) => [ + `client-runtime/${subpath.slice(2)}`, + `upstream-t3code/packages/client-runtime/${conditions.default.slice(2)}`, + ]), + ), + ...Object.fromEntries( + Object.entries(sharedPackageJson.exports).map(([subpath, conditions]) => [ + `shared/${subpath.slice(2)}`, + `upstream-t3code/packages/shared/${conditions.import.slice(2)}`, + ]), + ), }, deps: { alwaysBundle: shouldBundlePackDependency, + dts: { + alwaysBundle: /^@t3tools\//, + neverBundle: true, + }, onlyBundle: false, }, - dts: false, + dts: { + eager: true, + tsconfig: "tsconfig.dts.json", + }, fixedExtension: false, format: "esm", hash: false, @@ -47,7 +69,7 @@ export default defineConfig({ codeSplitting: { groups: [ { - name: "shared", + name: (id) => (id.endsWith(".d.ts") ? "shared.d" : "shared"), minShareCount: 2, }, ],