Skip to content

Conversation

@pkspyder007
Copy link

Modern Deploy Experiment - What's Worth Keeping

Hey! I spent some time experimenting with a rewrite of the deploy CLI on the modern-deploy branch. The goal was to see what improvements we could make without going overboard with architecture. After comparing it with master, here's what I found.

The Good Stuff

UI improvements are solid. The switch from inquirer/gauge to @clack/prompts makes a real difference. The prompts look cleaner, handle non-interactive mode better, and the progress indicators are way nicer. Files like src/ui/prompts.ts and src/ui/progress.ts are drop-in replacements for what we have in src/cli/.

Config validation is actually useful. The ConfigManager with Zod schemas (src/schemas/ConfigSchema.ts) catches config errors early and gives better error messages. The current src/config.ts doesn't validate anything, so invalid configs can cause weird failures later.

Error handling got better. The command suggestions using Levenshtein distance (src/utils/commandSuggestions.ts) is a nice touch. When someone types metacall-deploy deply, it suggests deploy instead of just saying "unknown command". Small thing, but it helps.

Logging is more structured. The logger in src/utils/logger.ts has proper levels, colors that fall back gracefully, and timestamps. Our current src/cli/messages.ts is fine but this is more flexible.

Inspect output looks better. The table formatting in src/utils/inspectUtils.ts using console-table-printer is cleaner than what we have. Better alignment, color-coded status, that kind of thing.

Utilities are better organized. Instead of one big src/utils.ts, it's split into focused modules like fileUtils.ts, envUtils.ts, zipUtils.ts. Easier to find things.

TypeScript got upgraded. Moving from TS 4.3.2 to 5.5.4 and adding proper types where they matter. Not overdoing it with interfaces everywhere, just using types where they help.

Task lists for deployment progress. Using listr2 to show a nice step-by-step progress indicator during deployment (src/tasks/deploymentTasks.ts). Instead of just printing "Deploying..." and hoping for the best, it shows "Validating deployment configuration", "Preparing deployment", "Deploying to MetaCall" with checkmarks as each step completes. It's currently tied to the strategy pattern, but the concept is solid - we could use it with the simpler function-based approach to give users better visibility into what's happening.

What Went Too Far

The strategy pattern for deployments (src/strategies/) feels like overkill. We have PackageDeploymentStrategy and RepositoryDeploymentStrategy with a DeploymentContext wrapper. The original deployPackage and deployFromRepository functions are simpler and easier to follow. This abstraction doesn't add value.

Factories everywhere. CommandFactory is just a switch statement. ProtocolServiceFactory is a simple conditional. These don't need to be classes. Just use functions or conditionals directly.

Too many interfaces. There are interfaces for everything (ICommand, IProtocolService, IDeploymentStrategy). Some are useful, but a lot of them just add indirection without making the code clearer.

Base command classes. BaseCommand and BaseClipanionCommand use inheritance for shared options. Composition or helper functions would be simpler.

Service layer explosion. We have DeploymentService, ForceDeploymentService, DeploymentVerificationService, AuthService, PlanService... for operations that were just functions before. The function-based approach is more direct.

Builder pattern for config. DeploymentConfigBuilder for building a config object? Just use object literals with validation.

What I'd Actually Do

If we're going to adopt parts of this, I'd start with the easy wins:

  1. Replace the UI stuff first - @clack/prompts is a direct upgrade. Swap out src/cli/selection.ts, src/cli/progress.ts, and src/cli/inputs.ts with the new prompt system (src/ui/prompts.ts, src/ui/progress.ts). Users will notice the difference immediately.

  2. Add command suggestions - The Levenshtein distance thing is clever and makes errors less frustrating. Drop src/utils/commandSuggestions.ts into the existing error handling.

  3. Improve inspect output - The table formatting is better. Replace src/cli/inspect.ts with src/utils/inspectUtils.ts.

  4. Add config validation - Replace src/config.ts with the ConfigManager approach. The Zod validation catches issues early.

  5. Maybe migrate to Clipanion - This one's optional. Clipanion is cleaner than ts-command-line-args, but it's a bigger change. Could migrate commands one at a time if we want. See src/utils/cliSetup.ts for the setup.

  6. Better logging - Replace src/cli/messages.ts with the structured logger (src/utils/logger.ts). More flexible for debugging.

  7. Organize utilities - Split up src/utils.ts into focused modules (src/utils/fileUtils.ts, src/utils/envUtils.ts, src/utils/zipUtils.ts). Makes the codebase easier to navigate.

  8. Task lists for deployment - The listr2 task list UI is nice. Shows users what's happening step-by-step during deployment. Currently it's wrapped around the strategy pattern (src/tasks/deploymentTasks.ts, see usage in DeployCommand), but we could use it with the original function-based approach. Just wrap the deployment steps in a Listr task list - "Validating", "Preparing", "Deploying", etc. Much better than the current "Deploying..." message.

The retry policy logic is fine, but the class structure is more complex than it needs to be. Keep the exponential backoff idea, simplify the implementation.

Dependencies

We'd need to add:

  • @clack/prompts (replaces inquirer and gauge)
  • zod (for validation)
  • clipanion (optional, if we migrate commands)
  • listr2 (for task lists - nice UX improvement for deployment progress)

And could remove:

  • ts-command-line-args (if we go with Clipanion)
  • inquirer and gauge (replaced by @clack/prompts)

Bottom Line

The experiment shows we can improve the UX and code quality without the architectural overhead. The UI improvements, validation, and better error handling are all worth keeping. The strategy patterns, factories, and excessive abstractions? Not so much.

I'd suggest we pick the good parts incrementally. Start with the UI stuff since that's the most visible improvement, then add validation and better error handling. Skip the over-engineered patterns and keep the simple, direct approach that works.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant