Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions apps/server/scripts/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import {
} from "../../../scripts/lib/brand-assets.ts";
import { resolveCatalogDependencies } from "../../../scripts/lib/resolve-catalog.ts";
import { fromJsonStringPretty } from "@t3tools/shared/schemaJson";
import { fromYaml } from "@t3tools/shared/schemaYaml";
import serverPackageJson from "../package.json" with { type: "json" };
import { parse as parseYaml } from "yaml";

interface PackageJson {
name: string;
Expand All @@ -39,10 +39,12 @@ interface PackageJson {
const PackageJsonPrettyJson = fromJsonStringPretty(Schema.Unknown);
const encodePackageJson = Schema.encodeEffect(PackageJsonPrettyJson);

interface WorkspaceConfig {
readonly catalog?: Record<string, string>;
readonly overrides?: Record<string, string>;
}
const WorkspaceConfig = Schema.Struct({
catalog: Schema.optional(Schema.Record(Schema.String, Schema.String)),
overrides: Schema.optional(Schema.Record(Schema.String, Schema.String)),
});
type WorkspaceConfig = typeof WorkspaceConfig.Type;
const decodeWorkspaceConfig = Schema.decodeEffect(fromYaml(WorkspaceConfig));

class CliError extends Data.TaggedError("CliError")<{
readonly message: string;
Expand All @@ -58,7 +60,7 @@ const readWorkspaceConfig = Effect.fn("readWorkspaceConfig")(function* () {
const fs = yield* FileSystem.FileSystem;
const repoRoot = yield* RepoRoot;
const workspaceYaml = yield* fs.readFileString(path.join(repoRoot, "pnpm-workspace.yaml"));
return parseYaml(workspaceYaml) as WorkspaceConfig;
return yield* decodeWorkspaceConfig(workspaceYaml);
});

const runCommand = Effect.fn("runCommand")(function* (command: ChildProcess.Command) {
Expand Down
48 changes: 0 additions & 48 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,35 +1,6 @@
{
"name": "@t3tools/monorepo",
"private": true,
"workspaces": {
"packages": [
"apps/*",
"oxlint-plugin-t3code",
"packages/*",
"scripts"
],
"catalog": {
"effect": "4.0.0-beta.73",
"@effect/atom-react": "4.0.0-beta.73",
"@effect/openapi-generator": "4.0.0-beta.73",
"@effect/platform-bun": "4.0.0-beta.73",
"@effect/platform-node": "4.0.0-beta.73",
"@effect/platform-node-shared": "4.0.0-beta.73",
"@effect/sql-sqlite-bun": "4.0.0-beta.73",
"@effect/vitest": "4.0.0-beta.73",
"@effect/tsgo": "0.11.4",
"@pierre/diffs": "1.1.20",
"@vitest/runner": "^4.1.8",
"@types/bun": "^1.3.11",
"@types/node": "24.12.4",
"@typescript/native-preview": "7.0.0-dev.20260527.2",
"typescript": "~6.0.3",
"vitest": "npm:@voidzero-dev/vite-plus-test@latest",
"vite": "npm:@voidzero-dev/vite-plus-core@latest",
"vite-plus": "latest",
"yaml": "^2.9.0"
}
},
"type": "module",
"scripts": {
"prepare": "vp config && effect-tsgo patch",
Expand Down Expand Up @@ -75,19 +46,6 @@
"@vitest/runner": "catalog:",
"vite": "catalog:",
"vite-plus": "catalog:",
"vitest": "catalog:",
"yaml": "catalog:"
},
"overrides": {
"@effect/atom-react": "catalog:",
"@effect/platform-bun": "catalog:",
"@effect/platform-node": "catalog:",
"@effect/platform-node-shared": "catalog:",
"@effect/sql-sqlite-bun": "catalog:",
"@effect/vitest": "catalog:",
"@types/node": "catalog:",
"effect": "catalog:",
"vite": "catalog:",
"vitest": "catalog:"
},
"engines": {
Expand All @@ -98,11 +56,5 @@
"workerDirectory": [
"apps/web/public"
]
},
"patchedDependencies": {
"@expo/metro-config@56.0.13": "patches/@expo%2Fmetro-config@56.0.13.patch",
"@pierre/diffs@1.1.20@1.1.20": "patches/@pierre%2Fdiffs@1.1.20.patch",
"effect@4.0.0-beta.73@4.0.0-beta.73": "patches/effect@4.0.0-beta.73.patch",
"react-native-nitro-modules@0.35.9@0.35.9": "patches/react-native-nitro-modules@0.35.9.patch"
}
}
7 changes: 6 additions & 1 deletion packages/shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@
"types": "./src/schemaJson.ts",
"import": "./src/schemaJson.ts"
},
"./schemaYaml": {
"types": "./src/schemaYaml.ts",
"import": "./src/schemaYaml.ts"
},
"./toolActivity": {
"types": "./src/toolActivity.ts",
"import": "./src/toolActivity.ts"
Expand Down Expand Up @@ -118,7 +122,8 @@
},
"dependencies": {
"@t3tools/contracts": "workspace:*",
"effect": "catalog:"
"effect": "catalog:",
"yaml": "catalog:"
},
"devDependencies": {
"@effect/platform-node": "catalog:",
Expand Down
58 changes: 58 additions & 0 deletions packages/shared/src/schemaYaml.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import * as Schema from "effect/Schema";
import { describe, expect, it } from "vite-plus/test";

import { fromYaml, fromYamlString } from "./schemaYaml.ts";

const ProjectConfig = Schema.Struct({
name: Schema.String,
enabled: Schema.Boolean,
tags: Schema.Array(Schema.String),
});

describe("schemaYaml helpers", () => {
it("decodes YAML through a schema", () => {
const decodeConfig = Schema.decodeUnknownSync(fromYaml(ProjectConfig));

expect(
decodeConfig(`name: t3code
enabled: true
tags:
- codex
- effect
`),
).toEqual({
name: "t3code",
enabled: true,
tags: ["codex", "effect"],
});
});

it("encodes values as YAML text", () => {
const encodeConfig = Schema.encodeSync(fromYaml(ProjectConfig));

expect(
encodeConfig({
name: "t3code",
enabled: true,
tags: ["codex"],
}),
).toBe(`name: t3code
enabled: true
tags:
- codex
`);
});

it("can be used as a schema transformation directly", () => {
const schema = Schema.String.pipe(Schema.decodeTo(Schema.Unknown, fromYamlString));
const decodeYaml = Schema.decodeUnknownSync(schema);

expect(decodeYaml("answer: 42\n")).toEqual({ answer: 42 });
});

it("rejects malformed YAML", () => {
const decodeYaml = Schema.decodeUnknownSync(fromYaml(Schema.Unknown));

expect(() => decodeYaml("name: ok\n bad-indent: nope\n")).toThrow();
});
});
132 changes: 132 additions & 0 deletions packages/shared/src/schemaYaml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import * as Effect from "effect/Effect";
import * as Option from "effect/Option";
import * as Schema from "effect/Schema";
import * as SchemaGetter from "effect/SchemaGetter";
import * as SchemaIssue from "effect/SchemaIssue";
import * as SchemaTransformation from "effect/SchemaTransformation";
import {
parse as parseYamlString,
stringify as stringifyYamlValue,
type CreateNodeOptions,
type DocumentOptions,
type ParseOptions,
type SchemaOptions,
type ToJSOptions,
type ToStringOptions,
} from "yaml";

export type YamlParseOptions = ParseOptions & DocumentOptions & SchemaOptions & ToJSOptions;
export type YamlStringifyOptions = DocumentOptions &
SchemaOptions &
ParseOptions &
CreateNodeOptions &
ToStringOptions;

function formatYamlError(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}

/**
* Parses a YAML string into a value.
*
* **When to use**
*
* Use when you need a schema getter to parse a present encoded YAML string
* during decoding.
*
* **Details**
*
* Parse failures become `SchemaIssue.InvalidValue` values.
*
* **Example** (Parse YAML)
*
* ```ts
* import { parseYaml } from "@t3tools/shared/schemaYaml"
*
* const parse = parseYaml<string>()
* // Getter<unknown, string>
* ```
*
* @see {@link stringifyYaml} for the inverse operation
*/
export function parseYaml<E extends string>(
options?: YamlParseOptions,
): SchemaGetter.Getter<unknown, E> {
return SchemaGetter.transformOrFail((input: E) =>
Effect.try({
try: () => parseYamlString(input, options) as unknown,
catch: (error) =>
new SchemaIssue.InvalidValue(Option.some(input), { message: formatYamlError(error) }),
}),
);
}

/**
* Stringifies a present value as YAML.
*
* **When to use**
*
* Use when you need a schema getter to serialize a present decoded value to
* YAML text during encoding.
*
* **Details**
*
* Stringify failures become `SchemaIssue.InvalidValue` values.
*
* **Example** (Stringify YAML)
*
* ```ts
* import { stringifyYaml } from "@t3tools/shared/schemaYaml"
*
* const stringify = stringifyYaml()
* // Getter<string, unknown>
* ```
*
* @see {@link parseYaml} for the inverse operation
*/
export function stringifyYaml(
options?: YamlStringifyOptions,
): SchemaGetter.Getter<string, unknown> {
return SchemaGetter.transformOrFail((input: unknown) =>
Effect.try({
try: () => stringifyYamlValue(input, options),
catch: (error) =>
new SchemaIssue.InvalidValue(Option.some(input), { message: formatYamlError(error) }),
}),
);
}

/**
* Decodes a YAML string and encodes a value as YAML text.
*
* **When to use**
*
* Use when you need a schema transformation to decode YAML stored or
* transmitted as a string before validating the parsed structure.
*
* **Details**
*
* Decode and encode failures become `InvalidValue` schema issues.
*
* **Example** (Parsing YAML)
*
* ```ts
* import * as Schema from "effect/Schema"
* import { fromYamlString } from "@t3tools/shared/schemaYaml"
*
* const schema = Schema.String.pipe(Schema.decodeTo(Schema.Unknown, fromYamlString))
* ```
*/
export const fromYamlString = new SchemaTransformation.Transformation<unknown, string>(
parseYaml(),
stringifyYaml(),
);

/**
* Build a schema that decodes a YAML string into `A`.
*
* Decode parses the input as YAML before validating the parsed value with the
* provided schema. Encode validates the value and serializes it as YAML text.
*/
export const fromYaml = <S extends Schema.Top>(schema: S) =>
Schema.String.pipe(Schema.decodeTo(schema, fromYamlString));
23 changes: 8 additions & 15 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading