TGViewer
Khraks dev Khraks dev @khraks_dev · 108 subscribers
Post #18 284
Захотелось поупражняться с effect и решил сделать CLI
Постановку взял тут https://t.me/we_use_js/3891
Подсчет статистики наивный ⋮)
Gist - https://gist.github.com/KhraksMamtsov/c29c112a5e1bf01e21e309646528445e
Из крутого:
- --wizard из коробки — возможность интерактивно составить какую-либо конкретную команду для cli
- --help коробочный
- --completions всякого рода — sh, bash, fish, zsh (сам не пользуюсь)
- типобезопасность (не знаю нужно ли упоминать — это effect)

import { Args, Command } from "@effect/cli";
import { NodeContext, NodeRuntime } from "@effect/platform-node";
import { Path, FileSystem as FS } from "@effect/platform";
import { Effect, Stream } from "effect";
import * as Schema from "@effect/schema/Schema";
// Схема статистики файла
const StatsContract = Schema.Struct({
chars: Schema.Number,
lines: Schema.Number,
words: Schema.Number,
});
// Функция энкода статистики в JSON
const encodeStatsContractToJSON = Schema.encodeEither(
Schema.parseJson(StatsContract)
);
// Подсчет статистики файла
const calculateFileStats = (path: string) =>
Effect.gen(function* () {
// импорт сервиса файловой системы
const fs = yield* FS.FileSystem;

return yield* fs.stream(path).pipe(
Stream.decodeText("utf8"),
Stream.flatMap((x) => Stream.fromIterable(x.split(""))),
Stream.runFold({ lines: 0, words: 0, chars: 0 }, (acc, cur) => ({
chars: acc.chars + 1,
lines: acc.lines + (cur === "\n" ? 1 : 0),
words: acc.words + (cur === " " ? 1 : 0),
}))
);
});
// Описание аргумента команды
const pathArg = Args.file({ exists: "yes" }).pipe(
Args.withDescription("Path to file")
);
// Описание подкоманды с обработчиком
const reportSubCommand = Command.make("report", { pathArg }, (x) =>
Effect.gen(function* () {
const fs = yield* FS.FileSystem;
const path = yield* Path.Path;

const stats = yield* calculateFileStats(x.pathArg);

const cwd = path.resolve();
const statsPath = path.join(cwd, "stats.json");
const data = yield* encodeStatsContractToJSON(stats);

yield* fs.writeFileString(statsPath, data);
})
);

const printSubCommand = Command.make("print", { pathArg }, (x) =>
Effect.gen(function* () {
const stats = yield* calculateFileStats(x.pathArg);
yield* Effect.log(stats);
})
);
// Собираем приложение
const appCommand = Command.make("app").pipe(
Command.withDescription("Node Kata solution"),
Command.withSubcommands([reportSubCommand, printSubCommand])
);
// Конвертим в effect
const cli = Command.run(appCommand, {
name: "Node Kata",
version: "0.0.1",
});
// Предоставляем зависимости
const program = cli(process.argv).pipe(Effect.provide(NodeContext.layer));
// Запускаем
NodeRuntime.runMain(program);
Telegram Node.JS [ru] | Серверный JavaScript 👩‍💻 Задачка по NodeJS Создайте приложение на Node.js, которое принимает путь к текстовому файлу в качестве аргумента командной строки, подсчитывает количество строк, слов и символов в файле, а затем сохраняет результат в stats.json. Программа также должна…
  • 🔥 6
More from @khraks_dev
  1. Sep 10, 2026Добавил еще PR с Context.Mixin, чтобы подмешивать к классу поведение Context.Service Тепер…
  2. Sep 10, 2026Post #56
  3. Sep 7, 2026Post #55
  4. Sep 3, 2026Post #54
  5. Sep 3, 2026Итак HKT на основе TypeLambda interface TypeLambda { readonly Arg1: unknown; readonly Arg2…
  6. Aug 30, 2026# HKT Дефункционализация В TypeScript нет настоящих Higher-Kinded Types (HKT). А они приме…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →