Skip to content
This repository was archived by the owner on Sep 10, 2025. It is now read-only.
Merged
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
93 changes: 90 additions & 3 deletions src/commands/diff.command.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,96 @@
import { Command } from "@effect/cli"
import { Console, Effect } from "effect"
import { componentNames, componentType } from "./add.command"
import {
HttpClient,
HttpClientRequest,
HttpClientResponse,
} from "@effect/platform"
import * as FS from "node:fs/promises"
import * as Path from "node:path"

export const diffCommand = Command.make("diff", { componentNames, componentType }, (config) =>
const REMOTE_BASE =
"https://raw.githubusercontent.com/irsyadadl/intentui/2.x/components/ui"

function simpleDiff(local: string, remote: string): string {
const l = local.split(/\r?\n/)
const r = remote.split(/\r?\n/)
const max = Math.max(l.length, r.length)
let out = ""
for (let i = 0; i < max; i++) {
const a = l[i]
const b = r[i]
if (a !== b) {
if (a !== undefined) out += `- ${a}\n`
if (b !== undefined) out += `+ ${b}\n`
}
}
return out
}

async function listFiles(dir: string): Promise<string[]> {
const entries = await FS.readdir(dir, { withFileTypes: true })
const files: string[] = []
for (const entry of entries) {
const res = Path.join(dir, entry.name)
if (entry.isDirectory()) {
files.push(...(await listFiles(res)))
} else {
files.push(res)
}
}
return files
}

export const diffCommand = Command.make("diff", {}, () =>
Effect.gen(function* () {
yield* Console.log("Coming soon...")
const client = yield* HttpClient.HttpClient
const cwd = process.cwd()
let config
try {
const jsonStr = yield* Effect.tryPromise(() => FS.readFile("components.json", "utf8"))
config = JSON.parse(jsonStr)
} catch {
yield* Console.error("components.json not found or invalid")
return
}

const alias: string = config.aliases?.ui ?? "components/ui"
const uiPath = Path.isAbsolute(alias.replace(/^@\//, ""))
? alias.replace(/^@\//, "")
: Path.join(cwd, alias.replace(/^@\//, ""))

const filesResult = yield* Effect.tryPromise(() => listFiles(uiPath)).pipe(
Effect.catchAll(() => Effect.succeed([] as string[])),
)
if (filesResult.length === 0) {
yield* Console.error(`Unable to read local components at ${uiPath}`)
return
}
const files = filesResult

const diffs: Array<{ file: string; diff: string }> = []
for (const file of files) {
const relative = Path.relative(uiPath, file).replace(/\\/g, "/")
const remoteUrl = `${REMOTE_BASE}/${relative}`
const localContent = yield* Effect.tryPromise(() => FS.readFile(file, "utf8"))
const remoteContent = yield* HttpClientRequest.get(remoteUrl).pipe(
client.execute,
Effect.flatMap(HttpClientResponse.text),
Effect.catchAll(() => Effect.succeed("")),
)
if (localContent !== remoteContent) {
diffs.push({ file: relative, diff: simpleDiff(localContent, remoteContent) })
}
}

if (diffs.length === 0) {
yield* Console.log("All components are up to date.")
} else {
for (const d of diffs) {
yield* Console.log(`--- ${d.file} ---`)
yield* Console.log(d.diff)
}
yield* Console.log("Some components differ from the registry. Run 'intentui add <component>' to sync.")
}
}),
).pipe(Command.withDescription("Compares your local components with the registry versions."))