Skip to content

Commit d79bd45

Browse files
LadyBluenotesKyleAMathewsclaudeautofix-ci[bot]
authored
refactor: migrate CLI to cac and fix monorepo workflow generation (#85)
* feat: refactor CLI to use cac for command handling and improve command structure * feat: refactor CLI commands and improve error handling structure * feat: implement validation command for skill files and refactor CLI structure * feat: add runStaleCommand for handling stale targets in CLI * feat: implement runMetaCommand for managing meta-skills in CLI * feat: add commands for editing package.json and setting up GitHub actions in CLI * feat: enhance CLI help command structure and usage examples * feat: enhance setup for monorepos with workspace detection and workflow generation * feat: refactor CLI structure by moving utility functions to cli-support module * refactor: change readPackageName function to private scope in cli-support module * changeset * test: update `cac` * Apply code review and simplification fixes - Brand CliFailure with symbol to prevent duck-typing false positives - Handle CACError in catch block for clean CLI error output - Fix unsafe (err as Error).message cast in scanIntentsOrFail - Include YAML parse error details in validation messages - Add monorepo artifact rationale comment in validate.ts - Fix misleading src path comment in setup.ts - Complete template variable list in docs - Add comment explaining cac double-argument quirk - Extract shared printWarnings to cli-support module - Move ValidationError interface to module scope - Extract isInsideMonorepo helper from IIFE Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ci: apply automated fixes * Downgrade cac to keep older Node support --------- Co-authored-by: Kyle Mathews <mathews.kyle@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 18a8fc1 commit d79bd45

File tree

19 files changed

+1048
-825
lines changed

19 files changed

+1048
-825
lines changed

.changeset/thin-rings-behave.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@tanstack/intent': patch
3+
---
4+
5+
Refactored the CLI to use `cac`, replacing the previous hand-rolled parsing and dispatch logic with a more structured command system.
6+
7+
This update also fixes monorepo workflow generation behavior related to `setup-github-actions`, improving repo/package fallback handling and ensuring generated workflow watch paths are monorepo-aware.

docs/cli/intent-setup.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ npx @tanstack/intent@latest setup-github-actions
2424
- Preserves existing indentation
2525
- `setup-github-actions`
2626
- Copies templates from `@tanstack/intent/meta/templates/workflows` to `.github/workflows`
27-
- Applies variable substitution for `PACKAGE_NAME`, `REPO`, `DOCS_PATH`, `SRC_PATH`
27+
- Applies variable substitution (`PACKAGE_NAME`, `PACKAGE_LABEL`, `PAYLOAD_PACKAGE`, `REPO`, `DOCS_PATH`, `SRC_PATH`, `WATCH_PATHS`)
28+
- Detects the workspace root in monorepos and writes repo-level workflows there
29+
- Generates monorepo-aware watch paths for package `src/` and docs directories
2830
- Skips files that already exist at destination
2931

3032
## Required `files` entries
@@ -42,6 +44,7 @@ npx @tanstack/intent@latest setup-github-actions
4244
## Notes
4345

4446
- `setup-github-actions` skips existing files
47+
- In monorepos, run `setup-github-actions` from either the repo root or a package directory; Intent writes workflows to the workspace root
4548

4649
## Related
4750

packages/intent/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"meta"
2828
],
2929
"dependencies": {
30+
"cac": "^6.7.14",
3031
"yaml": "^2.7.0"
3132
},
3233
"devDependencies": {
@@ -37,6 +38,7 @@
3738
"scripts": {
3839
"prepack": "npm run build",
3940
"build": "tsdown src/index.ts src/cli.ts src/setup.ts src/intent-library.ts src/library-scanner.ts --format esm --dts",
41+
"test:smoke": "pnpm run build && node dist/cli.mjs --help > /dev/null",
4042
"test:lib": "vitest run --exclude 'tests/integration/**'",
4143
"test:integration": "vitest run tests/integration/",
4244
"test:types": "tsc --noEmit"

packages/intent/src/cli-error.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
const CLI_FAILURE = Symbol('CliFailure')
2+
3+
export type CliFailure = {
4+
readonly [CLI_FAILURE]: true
5+
message: string
6+
exitCode: number
7+
}
8+
9+
// Throws a structured CliFailure (not an Error) — this represents an expected
10+
// user-facing failure, not an internal bug. Stack traces are intentionally
11+
// omitted since these are anticipated exit paths (bad input, missing files, etc).
12+
export function fail(message: string, exitCode = 1): never {
13+
throw { [CLI_FAILURE]: true as const, message, exitCode } satisfies CliFailure
14+
}
15+
16+
export function isCliFailure(value: unknown): value is CliFailure {
17+
return !!value && typeof value === 'object' && CLI_FAILURE in value
18+
}

packages/intent/src/cli-support.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { existsSync, readFileSync } from 'node:fs'
2+
import { dirname, join, relative } from 'node:path'
3+
import { fileURLToPath } from 'node:url'
4+
import { fail } from './cli-error.js'
5+
import type { ScanResult, StalenessReport } from './types.js'
6+
7+
export function printWarnings(warnings: Array<string>): void {
8+
if (warnings.length === 0) return
9+
10+
console.log('Warnings:')
11+
for (const warning of warnings) {
12+
console.log(` ⚠ ${warning}`)
13+
}
14+
}
15+
16+
export function getMetaDir(): string {
17+
const thisDir = dirname(fileURLToPath(import.meta.url))
18+
return join(thisDir, '..', 'meta')
19+
}
20+
21+
export async function scanIntentsOrFail(): Promise<ScanResult> {
22+
const { scanForIntents } = await import('./scanner.js')
23+
24+
try {
25+
return scanForIntents()
26+
} catch (err) {
27+
fail(err instanceof Error ? err.message : String(err))
28+
}
29+
}
30+
31+
function readPackageName(root: string): string {
32+
try {
33+
const pkgJson = JSON.parse(
34+
readFileSync(join(root, 'package.json'), 'utf8'),
35+
) as {
36+
name?: unknown
37+
}
38+
return typeof pkgJson.name === 'string'
39+
? pkgJson.name
40+
: relative(process.cwd(), root) || 'unknown'
41+
} catch {
42+
return relative(process.cwd(), root) || 'unknown'
43+
}
44+
}
45+
46+
export async function resolveStaleTargets(
47+
targetDir?: string,
48+
): Promise<{ reports: Array<StalenessReport> }> {
49+
const resolvedRoot = targetDir
50+
? join(process.cwd(), targetDir)
51+
: process.cwd()
52+
const { checkStaleness } = await import('./staleness.js')
53+
54+
if (existsSync(join(resolvedRoot, 'skills'))) {
55+
return {
56+
reports: [
57+
await checkStaleness(resolvedRoot, readPackageName(resolvedRoot)),
58+
],
59+
}
60+
}
61+
62+
const { findPackagesWithSkills, findWorkspaceRoot } =
63+
await import('./setup.js')
64+
const workspaceRoot = findWorkspaceRoot(resolvedRoot)
65+
if (workspaceRoot) {
66+
const packageDirs = findPackagesWithSkills(workspaceRoot)
67+
if (packageDirs.length > 0) {
68+
return {
69+
reports: await Promise.all(
70+
packageDirs.map((packageDir) =>
71+
checkStaleness(packageDir, readPackageName(packageDir)),
72+
),
73+
),
74+
}
75+
}
76+
}
77+
78+
const staleResult = await scanIntentsOrFail()
79+
return {
80+
reports: await Promise.all(
81+
staleResult.packages.map((pkg) =>
82+
checkStaleness(pkg.packageRoot, pkg.name),
83+
),
84+
),
85+
}
86+
}

0 commit comments

Comments
 (0)