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
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ The AI powered Seam setup wizard.

TODO

This package is not a standalone command line program:
it deliberately publishes no `bin`.
The wizard is distributed as a library and mounted by the
[Seam CLI](https://github.com/seamapi/cli) under `seam wizard`.

## Installation

Add this as a dependency to your project using [npm] with
Expand All @@ -19,6 +24,25 @@ $ npm install @seamapi/wizard

[npm]: https://www.npmjs.com/

## Usage

Mount the entire wizard as a subcommand by forwarding the arguments
that belong to the wizard to the default export:

```ts
import wizard from '@seamapi/wizard'

// e.g., for `seam wizard --help`, argv is `['--help']`.
await wizard({
argv: process.argv.slice(3),
commandName: 'seam wizard',
})
```

The `commandName` option is only used in help output
so that the wizard describes itself using the command
that was actually run.

## Development and Testing

### Quickstart
Expand All @@ -31,6 +55,17 @@ $ npm install
$ npm run test:watch
```

Run the wizard locally with

```
$ npm run wizard
```

This runs the development CLI in `src/bin/cli.ts`,
which simply calls the wizard with the arguments given.
That file exists for local development only:
it is excluded from the build and from the published package.

Primary development tasks are defined under `scripts` in `package.json`
and available via `npm run`.
View them with
Expand Down
42 changes: 11 additions & 31 deletions package-lock.json

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

7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"index.d.ts",
"lib",
"src",
"!src/bin",
"!test",
"!**/*.test.ts"
],
Expand All @@ -52,6 +53,8 @@
"lint": "eslint .",
"postlint": "prettier --check --ignore-path .gitignore .",
"postversion": "git push --follow-tags",
"wizard": "tsx src/bin/cli.ts",
"inspect": "tsx --inspect src/bin/cli.ts",
"example": "tsx examples",
"example:inspect": "tsx --inspect examples",
"format": "prettier --write --ignore-path .gitignore .",
Expand All @@ -72,7 +75,11 @@
"version": "^11.0.0 || ^10.0.0"
}
},
"dependencies": {
"minimist": "^1.2.8"
},
"devDependencies": {
"@types/minimist": "^1.2.5",
"@types/node": "^24.10.9",
"@vitest/coverage-v8": "^4.1.10",
"del-cli": "^7.0.0",
Expand Down
15 changes: 15 additions & 0 deletions src/bin/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env node

// This CLI exists for local development of the wizard only:
// it is not compiled or published, and the package exposes no bin.
// Consumers, e.g., the Seam CLI, mount the wizard with the default export.
// Run it with 'npm run wizard'.

import wizard from 'lib/wizard.js'

wizard({ argv: process.argv.slice(2) }).catch((err: unknown) => {
const { message, stack } = err instanceof Error ? err : new Error(String(err))
// eslint-disable-next-line no-console
console.error(`Wizard Error: ${message}\n${stack ?? ''}`)
process.exitCode = 1
})
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from 'lib/index.js'
export { default } from 'lib/index.js'
1 change: 1 addition & 0 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { todo } from './todo.js'
export { default, type WizardOptions } from './wizard.js'
44 changes: 44 additions & 0 deletions src/lib/wizard.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { expect, test, vi } from 'vitest'

import wizard from './wizard.js'

const captureOutput = async (
options: Parameters<typeof wizard>[0],
): Promise<string> => {
const log = vi.spyOn(console, 'log').mockImplementation(() => {})
try {
await wizard(options)
return log.mock.calls.map(([message]) => String(message)).join('\n')
} finally {
log.mockRestore()
}
}

test('wizard: displays usage with the --help flag', async () => {
const output = await captureOutput({ argv: ['--help'] })
expect(output).toContain('Seam Wizard')
expect(output).toContain('$ wizard [options]')
})

test('wizard: displays usage with the -h alias', async () => {
const output = await captureOutput({ argv: ['-h'] })
expect(output).toContain('Seam Wizard')
})

test('wizard: uses the given command name in usage', async () => {
const output = await captureOutput({
argv: ['--help'],
commandName: 'seam wizard',
})
expect(output).toContain('$ seam wizard [options]')
})

test('wizard: runs with no arguments', async () => {
const output = await captureOutput({})
expect(output).toContain('not implemented yet')
})

test('wizard: reports forwarded arguments', async () => {
const output = await captureOutput({ argv: ['setup', 'devices'] })
expect(output).toContain('setup devices')
})
75 changes: 75 additions & 0 deletions src/lib/wizard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import parseArgs from 'minimist'

export interface WizardOptions {
/**
* Command line arguments for the wizard, e.g., `process.argv.slice(2)`.
*
* These are the arguments _after_ the command used to invoke the wizard,
* so a consumer mounting the wizard as a subcommand should forward only
* the arguments belonging to the wizard.
*/
argv?: readonly string[]

/**
* The command used to invoke the wizard, shown in help output.
*
* Defaults to `wizard`.
* The Seam CLI mounts this wizard and passes `seam wizard`.
*/
commandName?: string
}

/**
* Run the Seam setup wizard.
*
* This is the entrypoint used by the Seam CLI to mount the entire wizard
* as a subcommand. It is also used by the development CLI in `src/bin/cli.ts`.
*/
const wizard = async (options: WizardOptions = {}): Promise<void> => {
const { argv = [], commandName = 'wizard' } = options

const args = parseArgs([...argv], {
boolean: ['help'],
alias: { h: 'help' },
})

if (args['help'] === true) {
write(usage(commandName))
return
}

// TODO: Implement the wizard.
await Promise.resolve()

write(
[
`The ${commandName} is not implemented yet.`,
`Run '${commandName} --help' for usage.`,
...(args._.length > 0 ? [`Received arguments: ${args._.join(' ')}`] : []),
].join('\n'),
)
}

export default wizard

const usage = (commandName: string): string =>
[
'Seam Wizard',
'',
' The AI powered Seam setup wizard.',
'',
'Usage',
'',
` $ ${commandName} [options]`,
'',
'Options',
'',
' -h, --help Display this help guide.',
'',
].join('\n')

// TODO: Replace this with a logger wrapper.
const write = (message: string): void => {
// eslint-disable-next-line no-console
console.log(message)
}
2 changes: 1 addition & 1 deletion tsconfig.build.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@
},
"files": ["src/index.ts"],
"include": ["src/**/*"],
"exclude": ["**/*.test.ts"]
"exclude": ["**/*.test.ts", "src/bin/**/*"]
}
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"lib/*": ["./src/lib/*"]
}
},
"files": ["src/index.ts"],
"files": ["src/index.ts", "src/bin/cli.ts"],
"include": [
"src/**/*",
"test/**/*",
Expand Down
4 changes: 2 additions & 2 deletions vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import { defineConfig } from 'vitest/config'
export default defineConfig({
resolve: {
alias: {
'@seamapi/makenew-tsmodule': new URL('./src/index.ts', import.meta.url)
.pathname,
'@seamapi/wizard': new URL('./src/index.ts', import.meta.url).pathname,
lib: new URL('./src/lib', import.meta.url).pathname,
},
},
test: {
coverage: {
exclude: [
'**/index.ts',
'src/bin/cli.ts',
'package/**/*.ts',
'examples/**/*.ts',
'**/*.test.ts',
Expand Down
Loading