Skip to content

Wave/3 full interactive mode#4

Merged
YoungMayor merged 3 commits intomainfrom
wave/3-full-interactive-mode
Apr 3, 2026
Merged

Wave/3 full interactive mode#4
YoungMayor merged 3 commits intomainfrom
wave/3-full-interactive-mode

Conversation

@YoungMayor
Copy link
Copy Markdown
Owner

This pull request introduces Devlink version 0.0.3, focusing on a major upgrade to the interactive CLI experience, improved workflows for all commands, and several bug fixes and enhancements. The changes include a comprehensive refactor of the interactive command handlers, improved package management flows, a premium welcome screen, and fixes for versioning and publish issues. The .npmignore is also removed in favor of explicit files configuration in package.json.

Interactive CLI and Workflow Improvements

  • All main commands now have guided, interactive workflows, including add, update, remove, restore, retreat, and update-all, providing project-aware package selection and multi-select where appropriate. (src/interactive.ts F5bb7c26L41R185, F5bb7c26L90R433, [1] [2] [3] [4] [5] [6] [7] [8]
  • The interactive mode now features a premium welcome screen using the "Graceful" font and a "by MayR Labs" subtitle. (src/interactive.ts src/interactive.tsL280-R438)
  • The interactive menu includes additional commands: update-all, retreat, restore, and improved navigation for installations and package store browsing. (src/interactive.ts src/interactive.tsR454-L307)

Bug Fixes and Quality of Life Enhancements

  • Fixed an infinite publish loop in publish:watch mode by improving file ignoring and debouncing logic. (CHANGELOG.md CHANGELOG.mdR3-R15)
  • Corrected version reporting for devlink --version. (CHANGELOG.md [1] package.json [2] [3]
  • Improved store browsing and version/flag selection for the add command, including parsing package names and handling versions interactively. (src/interactive.ts F5bb7c26L87R99, src/interactive.tsL99-R141)

Package Management and Distribution

  • Added a new update-all command to sync all devlinked packages to their latest versions, available both as a CLI and interactive command. (src/interactive.ts [1] [2]
  • The .npmignore file was removed, and a files array was added to package.json to explicitly control published content. (.npmignore [1] package.json [2]

Documentation

  • Updated CHANGELOG.md to reflect all new features, fixes, and improvements in version 0.0.3. (CHANGELOG.md CHANGELOG.mdR3-R15)

Refactor CLI to wire up interactive handlers and improve user experience.

- devlink.ts: register --version alias, show version string, and delegate to interactive handlers when commands are invoked with no or minimal args (publish, add, installations, update, update-all, restore, remove, retreat). Keeps previous non-interactive flows when args provided.
- interactive.ts: export numerous interactive handlers (handlePublish, handleAdd, handleInstallations, handleUpdate, handleUpdateAll, handleRetreat, handleRestore, handleRemove, handleStore) and enhance their behavior (package name parsing, lockfile reading, multiselect/version prompts, spinners, better messages). Improve startInteractive UI (figlet header, author line, added menu entries such as Update All, Retreat, Restore, Remove).
- publish.ts: clean up unused imports/whitespace.

These changes enable a richer interactive mode and smoother defaults when users run commands without arguments, while preserving existing scripted behavior when arguments are provided.
Bump package version to 0.0.3 and add changelog entries for the release. Remove .npmignore and add a files whitelist to package.json so only dist, README.md, CHANGELOG.md and package.json are published. Improve publish watch behavior by defaulting options.changed=true, adding a 200ms debounce to avoid repeated/infinite publishes, expanding ignored patterns (dist/build/out/lib/target/vendor/.next/.nuxt/.output and lock files), and ignoring devlink.lock/devlink.sig. Ensure the debounce timer is cleared on watcher cleanup.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR bumps Devlink to v0.0.3 and substantially expands the interactive CLI experience, while also improving publish watch-mode behavior and adjusting npm publish packaging.

Changes:

  • Refactors CLI command routing to support guided interactive flows for publish, add, update, remove, retreat, restore, and update-all.
  • Improves publish:watch stability via additional ignored paths and debounced republish triggering.
  • Updates distribution/versioning metadata (version bump, files allowlist, changelog updates, remove .npmignore).

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/publish.ts Adds debounce + expanded ignore list in watch mode; defaults changed behavior for watch.
src/interactive.ts Adds/exports interactive handlers for main workflows; improves add/update/remove flows and welcome screen.
src/devlink.ts Routes several commands into interactive handlers when invoked without args; adds yargs --version handling.
package.json Bumps version to 0.0.3 and switches to explicit npm files allowlist.
CHANGELOG.md Documents v0.0.3 features/fixes.
.npmignore Removed in favor of package.json#files.
Comments suppressed due to low confidence (1)

src/devlink.ts:307

  • devlink remove --retreat with no package args currently falls into handleRemove() and will perform a normal remove flow, ignoring --retreat. If --retreat is supported on this command, route to handleRetreat() (or otherwise preserve the flag’s semantics) when no packages are specified.
      handler: async (argv) => {
        const packages = argv._.slice(1) as string[];
        if (packages.length === 0 && !argv.all) {
          await handleRemove();
          return;
        }
        await removePackages(packages, {
          retreat: !!argv.retreat,
          workingDir: process.cwd(),
          all: !!argv.all,
        });

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/publish.ts Outdated
Comment on lines +142 to +145
// Default to changed check in watch mode to skip redundant publishes
if (options.changed === undefined) {
options.changed = true;
}
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

This assigns to the options parameter (options.changed = true), which violates the repo’s Biome rule style.noParameterAssign (and also mutates the caller’s object). Create a derived options object for watch-mode defaults instead (e.g., compute changed = options.changed ?? true and pass { ...options, changed } to publishPackage).

Copilot uses AI. Check for mistakes.
Comment thread src/devlink.ts
Comment on lines 131 to +137
.boolean(['push', 'watch'].concat(publishFlags));
},
handler: async (argv) => {
if (argv._.length <= 1) {
await handlePublish();
return;
}
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

When devlink publish is run with flags but no positional args (e.g. devlink publish --watch), this branch always invokes the interactive handler and ignores the provided flags. Consider only falling back to handlePublish() when there are no extra CLI tokens after the command (or when an explicit --interactive flag is set), and otherwise keep the existing non-interactive behavior.

Suggested change
.boolean(['push', 'watch'].concat(publishFlags));
},
handler: async (argv) => {
if (argv._.length <= 1) {
await handlePublish();
return;
}
.boolean(['push', 'watch', 'interactive'].concat(publishFlags));
},
handler: async (argv) => {
// Determine if there are any CLI tokens after the "publish" command.
// process.argv layout: [node, script, 'publish', ...tokensAfterCommand]
const hasExtraCliTokensAfterCommand = process.argv.length > 3;
if (argv.interactive || !hasExtraCliTokensAfterCommand) {
await handlePublish();
return;
}

Copilot uses AI. Check for mistakes.
Comment thread src/devlink.ts
Comment on lines 256 to 264
command: 'update-all',
describe: 'Update all devlinked packages to latest version',
handler: async () => {
await updateAllPackages(process.cwd());
if (process.argv.length <= 3) {
await handleUpdateAll();
} else {
await updateAllPackages(process.cwd());
}
},
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

Using process.argv.length to decide between interactive vs non-interactive makes behavior depend on unrelated global flags (e.g. devlink update-all --quiet will skip the interactive flow). Prefer deciding based on the parsed argv for this command (e.g. argv._.length) or an explicit --interactive/--no-interactive option.

Copilot uses AI. Check for mistakes.
Comment thread src/devlink.ts
await updatePackages(argv._.slice(1) as string[], {
const packages = argv._.slice(1) as string[];
if (packages.length === 0) {
await handleUpdate();
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

This changes devlink update with no package args from non-interactive behavior (updating/syncing all devlinked packages) to an interactive prompt, which can break automation/CI scripts. If interactive is intended, consider gating it behind TTY detection or an explicit --interactive flag, and keep the current non-interactive default when stdin/stdout isn’t a TTY.

Suggested change
await handleUpdate();
if (process.stdout.isTTY && process.stdin.isTTY) {
await handleUpdate();
} else {
await updateAllPackages(process.cwd());
}

Copilot uses AI. Check for mistakes.
Comment thread src/publish.ts
Comment on lines 169 to +198
const watcher = chokidar.watch(workingDir, {
ignored: [
'**/node_modules/**',
'**/.git/**',
'**/dist/**',
'**/build/**',
'**/out/**',
'**/lib/**',
'**/target/**',
'**/vendor/**',
'**/.next/**',
'**/.nuxt/**',
'**/.output/**',
'**/package-lock.json',
'**/pnpm-lock.yaml',
'**/yarn.lock',
join(workingDir, 'package.json'),
join(workingDir, 'devlink.lock'),
join(workingDir, 'devlink.sig'),
],
persistent: true,
ignoreInitial: true,
});

watcher.on('all', async (event, path) => {
console.log(`File ${path} ${event}, republishing...`);
await runPublish();
if (debounceTimeout) clearTimeout(debounceTimeout);
debounceTimeout = setTimeout(async () => {
console.log(`File ${path} ${event}, republishing...`);
await runPublish();
}, 200);
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

The new debounce behavior and expanded ignore patterns in watch mode aren’t covered by tests. There’s already a watch-mode test; consider extending it to assert that multiple rapid changes result in a single republish (debounce) and that changes to ignored paths don’t trigger republish.

Copilot uses AI. Check for mistakes.
Comment thread src/interactive.ts
Comment on lines +105 to +141
if (packageName) {
const parsed = parsePackageName(packageName);
selectedPackage = parsed.name;
selectedVersion = parsed.version;
}

if (!selectedPackage) {
const selected = await select({
message: 'Select a package to add from store:',
options: packageNames.map((name) => ({ value: name, label: name })),
});

if (isCancel(selected)) return;
selectedPackage = selected as string;
}

const pkgData = store.packages[selectedPackage];
if (!pkgData) {
note(pc.red(`Package ${selectedPackage} not found in store.`), 'Error');
return;
}

const pkgData = store.packages[selectedPackage as string];
const versions = Object.keys(pkgData.versions).sort((a, b) => {
const timeA = new Date(pkgData.versions[a].publishedAt).getTime();
const timeB = new Date(pkgData.versions[b].publishedAt).getTime();
return timeB - timeA;
});

const selectedVersion = await select({
message: `Select version for ${pc.cyan(selectedPackage as string)}:`,
options: versions.map((v) => ({ value: v, label: v })),
});
if (!selectedVersion) {
const version = await select({
message: `Select version for ${pc.cyan(selectedPackage)}:`,
options: versions.map((v) => ({ value: v, label: v })),
});

if (isCancel(selectedVersion)) return;
if (isCancel(version)) return;
selectedVersion = version as string;
}
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

When handleAdd() is invoked with a packageName that includes a version (e.g. via devlink add pkg@1.2.3), selectedVersion skips the version picker but is never validated against pkgData.versions. If the version doesn’t exist in the store, addPackages() will just warn/skip internally, but this flow will still print a success message. Validate that the requested version exists (or fall back to prompting) before calling addPackages.

Copilot uses AI. Check for mistakes.
Detect interactive TTY sessions and add a --interactive option; introduce shouldRunInteractive to auto-launch interactive handlers when appropriate across publish/add/install/update/restore/remove/retreat commands. Validate explicit package versions in handleAdd to surface errors when a provided version is missing. Refactor publishPackageWatch to default to changed=true in watch mode, use a function-based ignored matcher with normalized relative paths and regex patterns, ensure debounce timers are cleared on close, and pass normalized watch options into publish. Update tests to use an isolated store location, adjust timing, and add tests for debounce behavior and ignored-directory handling.
@YoungMayor YoungMayor merged commit 07372ab into main Apr 3, 2026
2 checks passed
@YoungMayor YoungMayor deleted the wave/3-full-interactive-mode branch April 3, 2026 00:48
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.

2 participants