Skip to content

Commit 4f96104

Browse files
committed
feat: Enhance OpenNext.js CLI with @clack/prompts integration and improved user experience
- Replaced Inquirer with @clack/prompts for interactive prompts, enhancing the CLI's user interface. - Updated command descriptions and help texts for better clarity and usability. - Added new options for commands to allow customization of worker names and caching strategies. - Implemented spinners for asynchronous operations to provide feedback during long-running tasks. - Improved project initialization and configuration update processes with clearer prompts and success messages. - Streamlined the logging utility to utilize @clack/prompts for consistent output formatting. These changes significantly improve the user experience and maintainability of the OpenNext.js CLI.
1 parent e87cd84 commit 4f96104

9 files changed

Lines changed: 591 additions & 586 deletions

File tree

packages/opennextjs-cli/package.json

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
"description": "Interactive CLI/TUI tool for setting up and configuring OpenNext.js projects for Cloudflare Workers",
55
"type": "module",
66
"bin": {
7-
"opennextjs-cli": "./dist/index.js"
7+
"opennextjs-cli": "./dist/index.js",
8+
"onjs": "./dist/index.js"
89
},
910
"main": "./dist/index.js",
1011
"types": "./dist/index.d.ts",
@@ -27,21 +28,19 @@
2728
"bump:patch": "tsx ../../scripts/bump-version.ts patch"
2829
},
2930
"dependencies": {
31+
"@clack/prompts": "^0.11.0",
3032
"@opennextjs/cloudflare": "^1.14.7",
31-
"chalk": "^5.3.0",
32-
"commander": "^13.1.0",
33+
"commander": "^14.0.2",
3334
"fs-extra": "^11.2.0",
3435
"glob": "^11.0.0",
35-
"inquirer": "^9.2.12",
3636
"zod": "^4.3.4"
3737
},
3838
"devDependencies": {
3939
"@types/fs-extra": "^11.0.4",
40-
"@types/inquirer": "^9.0.7",
4140
"@types/node": "^22.10.7",
42-
"eslint": "^9.18.0",
4341
"@typescript-eslint/eslint-plugin": "^8.21.0",
4442
"@typescript-eslint/parser": "^8.21.0",
43+
"eslint": "^9.18.0",
4544
"prettier": "^3.4.2",
4645
"tsx": "^4.19.2",
4746
"typescript": "^5.7.3"

packages/opennextjs-cli/src/cli.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,31 @@ export const program = new Command();
3030

3131
program
3232
.name('opennextjs-cli')
33-
.description('Interactive CLI/TUI tool for setting up and configuring OpenNext.js projects for Cloudflare Workers')
34-
.version('0.1.0');
33+
.description(
34+
'Interactive CLI tool for setting up and configuring OpenNext.js projects for Cloudflare Workers deployments'
35+
)
36+
.version('0.1.0', '-v, --version', 'Display version number')
37+
.addHelpText(
38+
'after',
39+
`
40+
Examples:
41+
opennextjs-cli init Create a new Next.js project with OpenNext.js
42+
opennextjs-cli init my-app Create a project with a specific name
43+
opennextjs-cli add Add OpenNext.js to an existing Next.js project
44+
opennextjs-cli config Update configuration for an existing project
45+
46+
Documentation:
47+
https://github.com/JSONbored/opennextjs-cli
48+
49+
Quick Start:
50+
1. Create a new project: opennextjs-cli init
51+
2. Or add to existing: opennextjs-cli add
52+
3. Deploy: pnpm deploy
53+
`
54+
)
55+
.configureHelp({
56+
subcommandTerm: (cmd) => cmd.name(),
57+
});
3558

3659
// Register commands
3760
program.addCommand(initCommand());

packages/opennextjs-cli/src/commands/add.ts

Lines changed: 100 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import { Command } from 'commander';
10+
import * as p from '@clack/prompts';
1011
import { detectNextJsProject, getNextJsVersion } from '../utils/project-detector.js';
1112
import { promptCloudflareConfig } from '../platforms/cloudflare/prompts.js';
1213
import { generateCloudflareConfig } from '../platforms/cloudflare/index.js';
@@ -34,15 +35,86 @@ export function addCommand(): Command {
3435
const command = new Command('add');
3536

3637
command
37-
.description('Add OpenNext.js Cloudflare to an existing Next.js project')
38-
.option('-y, --yes', 'Skip interactive prompts and use defaults')
39-
.action(async (_options: { yes?: boolean }) => {
38+
.description('Add OpenNext.js Cloudflare configuration to an existing Next.js project')
39+
.summary('Add OpenNext.js to your current Next.js project')
40+
.option(
41+
'-y, --yes',
42+
'Skip all interactive prompts and use default configuration values'
43+
)
44+
.option(
45+
'--worker-name <name>',
46+
'Cloudflare Worker name (default: detected from package.json)'
47+
)
48+
.option(
49+
'--caching-strategy <strategy>',
50+
'Caching strategy: static-assets, r2, r2-do-queue, r2-do-queue-tag-cache',
51+
'r2'
52+
)
53+
.option(
54+
'--skip-cloudflare-check',
55+
'Skip Cloudflare authentication and wrangler installation check'
56+
)
57+
.option(
58+
'--skip-backup',
59+
'Skip creating backups of existing configuration files'
60+
)
61+
.addHelpText(
62+
'after',
63+
`
64+
Examples:
65+
opennextjs-cli add Interactive setup with prompts
66+
opennextjs-cli add --yes Use all defaults, no prompts
67+
opennextjs-cli add --worker-name my-worker Custom worker name
68+
opennextjs-cli add --caching-strategy r2 Specify caching strategy
69+
70+
Prerequisites:
71+
• Must be run from a Next.js project directory
72+
• Project should have package.json with Next.js dependency
73+
• Cloudflare account and wrangler CLI recommended (but not required)
74+
75+
What it does:
76+
1. Detects your Next.js project and version
77+
2. Backs up existing configuration files (wrangler.toml, etc.)
78+
3. Prompts for Cloudflare configuration options
79+
4. Generates wrangler.toml and open-next.config.ts
80+
5. Installs @opennextjs/cloudflare and wrangler
81+
6. Updates package.json with preview and deploy scripts
82+
83+
Caching Strategies:
84+
static-assets SSG-only, no R2 needed
85+
r2 R2 Incremental Cache (recommended)
86+
r2-do-queue ISR with time-based revalidation
87+
r2-do-queue-tag-cache Full-featured with on-demand revalidation
88+
89+
Next Steps:
90+
After adding OpenNext.js, you can:
91+
pnpm preview # Preview with Cloudflare Workers
92+
pnpm deploy # Deploy to Cloudflare
93+
94+
Troubleshooting:
95+
If you encounter issues:
96+
• Ensure you're in a Next.js project directory
97+
• Check that package.json exists and has Next.js dependency
98+
• Verify Node.js version is 18+ (check with: node --version)
99+
`
100+
)
101+
.action(async (_options: {
102+
yes?: boolean;
103+
workerName?: string;
104+
cachingStrategy?: string;
105+
skipCloudflareCheck?: boolean;
106+
skipBackup?: boolean;
107+
}) => {
40108
try {
41-
logger.info('Detecting Next.js project...');
109+
logger.section('Project Detection');
110+
const detectSpinner = p.spinner();
111+
detectSpinner.start('Detecting Next.js project...');
42112

43113
// Detect project
44114
const detection = detectNextJsProject();
45115
const nextJsVersion = getNextJsVersion();
116+
117+
detectSpinner.stop('Project detected');
46118

47119
if (!detection.isNextJsProject) {
48120
logger.error('No Next.js project detected in the current directory.');
@@ -62,9 +134,12 @@ export function addCommand(): Command {
62134
}
63135
}
64136

65-
logger.info(`Detected Next.js ${nextJsVersion || 'unknown version'}`);
137+
if (detection.isNextJsProject) {
138+
logger.success(`Detected Next.js ${nextJsVersion || 'unknown version'}`);
139+
}
66140

67141
// Verify Cloudflare setup
142+
logger.section('Cloudflare Setup');
68143
const cloudflareCheck = verifyCloudflareSetup();
69144
if (!cloudflareCheck.wranglerInstalled || !cloudflareCheck.authenticated) {
70145
logger.warning(cloudflareCheck.message || 'Cloudflare setup issue detected');
@@ -86,32 +161,45 @@ export function addCommand(): Command {
86161
}
87162

88163
// Prompt for Cloudflare configuration
164+
logger.section('Configuration');
89165
const config = await promptCloudflareConfig({
90166
nextJsVersion: nextJsVersion || '15.1.0',
91167
});
168+
169+
logger.section('Setup');
92170

93171
// Generate configuration files
94-
logger.info('Generating configuration files...');
172+
const configSpinner = p.spinner();
173+
configSpinner.start('Generating configuration files...');
95174
await generateCloudflareConfig(config, process.cwd());
175+
configSpinner.stop('Configuration files generated');
96176

97177
// Install OpenNext.js Cloudflare if not already installed
98178
if (!detection.hasOpenNext) {
99-
logger.info('Installing @opennextjs/cloudflare...');
179+
const installSpinner = p.spinner();
180+
installSpinner.start('Installing @opennextjs/cloudflare...');
100181
await addDependency('@opennextjs/cloudflare', false);
182+
installSpinner.stop('@opennextjs/cloudflare installed');
101183
}
102184

103185
// Install wrangler as dev dependency
104-
logger.info('Installing wrangler...');
186+
const wranglerSpinner = p.spinner();
187+
wranglerSpinner.start('Installing wrangler...');
105188
await addDependency('wrangler', true);
189+
wranglerSpinner.stop('wrangler installed');
106190

107191
// Install all dependencies
108-
logger.info('Installing dependencies...');
192+
const depsSpinner = p.spinner();
193+
depsSpinner.start('Installing dependencies...');
109194
await installDependencies();
195+
depsSpinner.stop('Dependencies installed');
110196

111197
logger.success('OpenNext.js Cloudflare has been added to your project!');
112-
logger.info(`Next steps:`);
113-
logger.info(` pnpm preview # Preview with Cloudflare Workers`);
114-
logger.info(` pnpm deploy # Deploy to Cloudflare`);
198+
p.note(
199+
`Next steps:\n\n pnpm preview # Preview with Cloudflare Workers\n pnpm deploy # Deploy to Cloudflare`,
200+
'🚀 Ready to Deploy'
201+
);
202+
p.outro('OpenNext.js Cloudflare added successfully!');
115203
} catch (error) {
116204
logger.error('Failed to add OpenNext.js Cloudflare', error);
117205
process.exit(1);

packages/opennextjs-cli/src/commands/config.ts

Lines changed: 108 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@
77
*/
88

99
import { Command } from 'commander';
10+
import * as p from '@clack/prompts';
11+
import { logger } from '../utils/logger.js';
12+
import { detectNextJsProject } from '../utils/project-detector.js';
13+
import { promptCloudflareConfig } from '../platforms/cloudflare/prompts.js';
14+
import { generateCloudflareConfig } from '../platforms/cloudflare/index.js';
15+
import { backupFiles } from '../utils/backup.js';
1016

1117
/**
1218
* Creates the `config` command for updating configuration
@@ -26,12 +32,108 @@ export function configCommand(): Command {
2632
const command = new Command('config');
2733

2834
command
29-
.description('Update OpenNext.js Cloudflare configuration')
30-
.option('-y, --yes', 'Skip interactive prompts and use defaults')
31-
.action(async (options: { yes?: boolean }) => {
32-
// TODO: Implement config update logic
33-
console.log('Config command - coming soon');
34-
console.log('Skip prompts:', options.yes || false);
35+
.description('Update or reconfigure OpenNext.js Cloudflare settings for your project')
36+
.summary('Update OpenNext.js Cloudflare configuration')
37+
.option(
38+
'-y, --yes',
39+
'Skip all interactive prompts and use default configuration values'
40+
)
41+
.option(
42+
'--worker-name <name>',
43+
'Update Cloudflare Worker name'
44+
)
45+
.option(
46+
'--caching-strategy <strategy>',
47+
'Update caching strategy: static-assets, r2, r2-do-queue, r2-do-queue-tag-cache'
48+
)
49+
.option(
50+
'--reset',
51+
'Reset configuration to defaults (will prompt for confirmation)'
52+
)
53+
.addHelpText(
54+
'after',
55+
`
56+
Examples:
57+
opennextjs-cli config Interactive configuration update
58+
opennextjs-cli config --worker-name new-name Update worker name only
59+
opennextjs-cli config --caching-strategy r2 Update caching strategy
60+
opennextjs-cli config --reset Reset to defaults
61+
62+
What it does:
63+
1. Detects your current OpenNext.js configuration
64+
2. Backs up existing configuration files
65+
3. Prompts for updated configuration options
66+
4. Regenerates wrangler.toml and open-next.config.ts
67+
5. Preserves your existing settings where possible
68+
69+
Configuration Options:
70+
• Worker name and account ID
71+
• Caching strategy (R2, Durable Objects, etc.)
72+
• Database bindings (D1, Hyperdrive)
73+
• Image optimization settings
74+
• Analytics Engine configuration
75+
• Environment-specific observability settings
76+
77+
Note:
78+
This command requires an existing OpenNext.js project.
79+
Use opennextjs-cli add if you haven't set up OpenNext.js yet.
80+
`
81+
)
82+
.action(async (_options: {
83+
yes?: boolean;
84+
workerName?: string;
85+
cachingStrategy?: string;
86+
reset?: boolean;
87+
}) => {
88+
try {
89+
// Check if OpenNext.js is configured
90+
const detection = detectNextJsProject();
91+
92+
if (!detection.isNextJsProject) {
93+
logger.error('No Next.js project detected in the current directory.');
94+
logger.info('Please run this command from a Next.js project directory.');
95+
process.exit(1);
96+
}
97+
98+
if (!detection.hasOpenNext) {
99+
logger.error('OpenNext.js Cloudflare is not configured in this project.');
100+
logger.info('Use opennextjs-cli add to set up OpenNext.js first.');
101+
process.exit(1);
102+
}
103+
104+
logger.section('Configuration Update');
105+
logger.info('Updating OpenNext.js Cloudflare configuration...');
106+
107+
// Backup existing files
108+
const filesToBackup = ['wrangler.toml', 'open-next.config.ts', 'next.config.mjs'];
109+
const backups = backupFiles(filesToBackup);
110+
if (backups.some((b) => b !== undefined)) {
111+
logger.info('Created backups of existing configuration files');
112+
}
113+
114+
// Prompt for new configuration
115+
logger.section('New Configuration');
116+
const config = await promptCloudflareConfig({
117+
nextJsVersion: '15.1.0', // TODO: Detect from package.json
118+
});
119+
120+
// Generate updated configuration files
121+
logger.section('Updating Files');
122+
const configSpinner = p.spinner();
123+
configSpinner.start('Updating configuration files...');
124+
await generateCloudflareConfig(config, process.cwd());
125+
configSpinner.stop('Configuration files updated');
126+
127+
logger.success('OpenNext.js Cloudflare configuration has been updated!');
128+
p.note(
129+
`Next steps:\n\n pnpm preview # Preview with updated configuration\n pnpm deploy # Deploy to Cloudflare`,
130+
'✅ Configuration Updated'
131+
);
132+
p.outro('Configuration updated successfully!');
133+
} catch (error) {
134+
logger.error('Failed to update configuration', error);
135+
process.exit(1);
136+
}
35137
});
36138

37139
return command;

0 commit comments

Comments
 (0)