Skip to content

Add module mode aliases and class groups#211

Merged
mmkal merged 11 commits into
mainfrom
module-mode-next
Jun 19, 2026
Merged

Add module mode aliases and class groups#211
mmkal merged 11 commits into
mainfrom
module-mode-next

Conversation

@mmkal

@mmkal mmkal commented Jun 19, 2026

Copy link
Copy Markdown
Owner

Summary

Adds the next scoped module-mode features:

  • JSDoc @alias tags on exported functions become command aliases.
  • JSDoc @alias tags on option properties become option aliases.
  • Alias tags are stripped from help descriptions.
  • Exported functions whose signatures cannot be converted into CLI inputs are ignored as ordinary non-command exports; NoInfer<...> parameters are a convenient opt-out for helper exports.
  • Direct command-group-shaped exported classes become same-file subcommand groups.
  • export default class Commands { ... } exposes public methods at the current router level.
  • Class groups are constrained to constructor-free instantiation: classes without a base class may omit the constructor, and classes with extends must declare an explicit zero-argument constructor.
  • Unsupported class shapes, including constructor parameters, unsupported inheritance, and classes with no public command methods, are ignored as ordinary non-command exports.
  • Public instance methods become commands; static methods, inherited methods, getters/setters, private/protected methods, and #private methods stay internal.
  • Class instances are created lazily inside command handlers, so help/schema generation does not run constructors.
  • File-backed module mode supports selected named re-exports like export {Users} from './users.ts'.
  • File-backed module mode resolves relative imported type/interface declarations, including common .js specifiers that point at sibling .ts source files.
  • Documents the current same-file type support boundary, including extended interfaces and object intersections.

Also widens .run({prompts}) so prompts: false and computed booleans like prompts: !isAgent() are valid; true still enables built-in prompts and false disables prompting.

Exported norpc procedure/router support remains out of scope for a separate change.

Examples

/**
 * install dependencies
 * @alias i
 */
export function install(options: {
  /** fail if the lockfile changed
   * @alias f
   */
  frozenLockfile?: boolean
}) {
  return options.frozenLockfile ? 'frozen' : 'normal'
}

export const loadSomeInternalThing = (params: NoInfer<{foo: string}>) => {
  return loadIt(params.foo) // helper export, not a command
}

class BaseUsers {
  protected prefix = 'invite'
}

export class Users extends BaseUsers {
  constructor() {
    super()
  }

  /** invite a user
   * @alias i
   */
  invite(options: {
    /** address to invite */
    email: string
  }) {
    const user = this.getOrCreate(options.email)
    return `${this.prefix} ${user.email}`
  }

  private getOrCreate(email: string) {
    return {email}
  }

  #audit(action: string) {
    // private implementation detail, not a command
  }
}
// commands.ts
import type {InviteOptions} from './types.js'

export {Users} from './users.ts'

export default class Commands {
  invite(options: InviteOptions) {
    return `invite ${options.email}`
  }
}
$ mycli i -f
frozen

$ tsx commands.ts invite --email [email protected]
invite [email protected]

$ tsx commands.ts users invite --email [email protected]
invite [email protected]

$ mycli users i --email [email protected]
invite [email protected]
await cli.run({
  prompts: !isAgent(),
})

Verification

  • pnpm exec vitest run test/typebox-module-commands.test.ts
  • pnpm exec vitest run test/prompts.test.ts test/types.test.ts
  • pnpm exec eslint README.md src/module-commands.ts src/types.ts test/typebox-module-commands.test.ts
  • pnpm exec eslint README.md src/types.ts test/prompts.test.ts test/types.test.ts
  • pnpm compile
  • pnpm test
  • pnpm lint

@pkg-pr-new

pkg-pr-new Bot commented Jun 19, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/mmkal/trpc-cli@211

commit: 13f3909

@mmkal mmkal changed the title Proposal: push module mode further Add module mode aliases and class groups Jun 19, 2026
@mmkal mmkal marked this pull request as ready for review June 19, 2026 10:00

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes — Adds JSDoc @alias tag support and exported class command groups to module mode, along with documentation of the same-file type support boundary.

  • Add @alias JSDoc tag support for commands and options@alias tags on exported functions produce meta.aliases.command, and on option properties produce schema-level alias fields, fed through the existing Commander aliasing pipeline. Tags are stripped from help descriptions so only prose renders.
  • Add exported class command groupsexport class Group { method() {} } creates a lazy-instantiated subcommand group. No extends, no constructor args, public instance methods only. The class is NOT instantiated during help/schema generation; a fresh instance is created per-command invocation.
  • Document same-file type support boundary — Interface extends, multiple bases, and object intersections are documented as supported; imported types remain out of scope and fail loudly.
  • New extraction primitivesextractModuleClasses, extractClassMethods, parseCliJsdoc, applySchemaJsdocMetadata follow the same hand-rolled scanSource/masked pattern as the existing function extractor.

Pullfrog  | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@mmkal mmkal merged commit 0259dc5 into main Jun 19, 2026
57 checks passed
mmkal added a commit to iterate/iterate that referenced this pull request Jun 19, 2026
## Summary

- pins existing trpc-cli consumers to the pkg-pr-new build from
mmkal/trpc-cli#211
- restores `pnpm cli itx run -e ...` through trpc-cli option alias
metadata on the plain `itx` function module
- keeps OS script command groups as normal TypeScript modules with
exported functions: `itx`, `artifacts`, and `dev`
- loads `pnpm dev` Doppler secrets into the dev server env directly
instead of recursively rerunning `scripts/dev.ts` with reconstructed CLI
args
- exposes that Doppler JSON loading as `doppler.loadOsSecrets` from
`apps/os/scripts/dev.ts`, so Playwright forged-session setup and the dev
server use the same implementation without adding a helper command
- adds a small CLI help smoke test for the mounted OS script subcommands

## Verification

- `DOPPLER_CONFIG=dev pnpm --dir apps/os exec tsx ./scripts/cli.ts
--help`
- `DOPPLER_CONFIG=dev pnpm --dir apps/os exec tsx ./scripts/cli.ts itx
run --help`
- `DOPPLER_CONFIG=dev pnpm --dir apps/os exec tsx ./scripts/cli.ts
artifacts --help`
- `DOPPLER_CONFIG=dev pnpm --dir apps/os exec tsx ./scripts/cli.ts dev
--help`
- `DOPPLER_CONFIG=dev pnpm --dir apps/os exec tsx ./scripts/dev.ts
--help`
- `DOPPLER_CONFIG=dev pnpm --dir apps/os exec tsx ./scripts/dev.ts start
--help`
- `DOPPLER_CONFIG=dev pnpm --dir apps/os exec tsx ./scripts/dev.ts
status`
- `env -u DOPPLER_CONFIG pnpm --dir apps/os exec tsx ./scripts/dev.ts
start --detach --timeout-ms 1000`
- `node --import tsx --input-type=module -e "const { doppler } = await
import('./apps/os/scripts/dev.ts'); const result =
doppler.loadOsSecrets({ config: 'dev' }); console.log(result.ok,
result.ok && Boolean(result.secrets.ALCHEMY_LOCAL));"`
- `pnpm --dir apps/os exec vitest run scripts/cli.test.ts`
- `pnpm --dir apps/os typecheck`
- `pnpm lint`
- `pnpm format:check`
- `pnpm knip`

Attempted but did not reach test execution:

- `pnpm exec playwright test specs/dashboard.spec.ts --config
playwright.config.ts --project chromium` timed out waiting for the
existing recorded local dev server; pid 19724 is alive, but
`http://localhost:62555/api/health` is not accepting connections.

<!-- CLOUDFLARE_PREVIEW -->
## Environment Config Lease

<!-- CLOUDFLARE_PREVIEW_STATE -->
<!--
{
  "apps": {
    "semaphore": {
      "appDisplayName": "Semaphore",
      "appSlug": "semaphore",
      "status": "deployed",
      "updatedAt": "2026-06-19T13:10:52.732Z",
      "headSha": "5a9282b43c5b919377dd24f6e545e3288f120a57",
      "message": null,
      "publicUrl": "https://semaphore.iterate-preview-3.com",
"runUrl": "https://github.com/iterate/iterate/actions/runs/27827601839",
      "shortSha": "5a9282b",
      "deployDurationMs": 31135,
      "testDurationMs": 7963
    },
    "auth": {
      "appDisplayName": "Auth",
      "appSlug": "auth",
      "status": "deployed",
      "updatedAt": "2026-06-19T15:15:21.081Z",
      "headSha": "ab2c3a1b9543e12c139a7a9910a5aae72b81d09b",
      "message": null,
      "publicUrl": "https://auth.iterate-preview-3.com",
"runUrl": "https://github.com/iterate/iterate/actions/runs/27833799986",
      "shortSha": "ab2c3a1",
      "deployDurationMs": 27110,
      "testDurationMs": 754
    },
    "os": {
      "appDisplayName": "OS",
      "appSlug": "os",
      "status": "deployed",
      "updatedAt": "2026-06-19T15:20:26.912Z",
      "headSha": "ab2c3a1b9543e12c139a7a9910a5aae72b81d09b",
      "message": null,
      "publicUrl": "https://os.iterate-preview-3.com",
"runUrl": "https://github.com/iterate/iterate/actions/runs/27833799986",
      "shortSha": "ab2c3a1",
      "deployDurationMs": 50225,
      "testDurationMs": 305831
    }
  },
  "environmentConfigLease": {
    "dopplerConfig": "preview_3",
    "leasedUntil": 1781885636336,
    "leaseId": "8d330831-b071-41d6-8853-61d36737af0e",
    "slug": "preview-3",
    "type": "environment-config-lease"
  }
}
-->
<!-- /CLOUDFLARE_PREVIEW_STATE -->

<details>
<summary>Lease: preview-3 | Doppler config: preview_3 | Type:
environment-config-lease | Leased until:
2026-06-19T16:13:56.336Z</summary>

| app | status | commit | preview | deploy duration | test duration |
cleanup duration | workflow run | updated | summary |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Auth | deployed | `ab2c3a1` |
[https://auth.iterate-preview-3.com](https://auth.iterate-preview-3.com)
| 27.1s | 754ms | | [Workflow
run](https://github.com/iterate/iterate/actions/runs/27833799986) |
2026-06-19T15:15:21.081Z | |
| OS | deployed | `ab2c3a1` |
[https://os.iterate-preview-3.com](https://os.iterate-preview-3.com) |
50.2s | 305.8s | | [Workflow
run](https://github.com/iterate/iterate/actions/runs/27833799986) |
2026-06-19T15:20:26.912Z | |
| Semaphore | deployed | `5a9282b` |
[https://semaphore.iterate-preview-3.com](https://semaphore.iterate-preview-3.com)
| 31.1s | 8.0s | | [Workflow
run](https://github.com/iterate/iterate/actions/runs/27827601839) |
2026-06-19T13:10:52.732Z | |

</details>
<!-- /CLOUDFLARE_PREVIEW -->

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Local dev startup path changed (no recursive `doppler run`), and a
pre-release trpc-cli URL affects all CLI consumers; wrong Doppler merge
could break `ALCHEMY_LOCAL` guards or tests.
> 
> **Overview**
> Pins **trpc-cli** to the pkg.pr.new build from mmkal/trpc-cli#211
across the monorepo so option aliases (e.g. **`itx run -e`**) work again
via JSDoc `@alias e` on the `eval` option.
> 
> **`pnpm dev`** no longer shells out to `doppler run` and re-invokes
`scripts/dev.ts`. It downloads OS secrets with `doppler secrets
download` and merges them into the env passed to the Alchemy child.
**`doppler.loadOsSecrets`** is exported from `dev.ts` for reuse;
Playwright forged-session setup drops its duplicate Doppler helper and
calls that instead.
> 
> Docs now describe secret loading into the spawned server rather than
nested Doppler entry. **`artifacts.ts`** uses `||` instead of `??` for
API response fallbacks. A CLI help smoke test asserts **`artifacts`**,
**`dev`**, and **`itx`** appear under `pnpm cli`.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
ab2c3a1. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

This is included in v0.15.1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant